微服务核心技术总结

虚拟化技术将一台服务器虚拟出多个虚拟机来提供服务。
虚拟化技术包括计算虚拟化(服务器虚拟化)、存储虚拟化、网络虚拟化等。
在实际讲解虚拟化之前,我们需要先解释一下隔离技术,在隔离的基础上我们才能任意粒度、自由地分配资源。

隔离的作用

  • 实现更细粒度的资源管理,比如将一个物理 CPU 的计算能力分给多台云主机,再将这台云主机卖给多个人。
  • 控制故障的影响范围,一个 Tomcat 中可以运行多个 Web 服务器,一台 Web 服务器崩溃并不会令其他服务器一并崩溃。

隔离的分类

广义的隔离技术包括如下几类

  • 硬件:虚拟机,如 KVM、Xen
  • 操作系统:容器,如 LXC、Docker
  • Web 服务器:如 Servlet 容器
  • 依赖版本:虚拟环境
  • 运行环境:语言虚拟机,如JVM
  • 语言:DSL

前两种隔离是虚拟化技术的基础,主要用于物理资源的池化,进而弹性地分配给用户。
作为一个例子,下面是一个运行时服务器中各抽象层次所采取的隔离技术的示意图:
隔离层次

上述隔离技术探讨的是单机环境下的资源分配,在微服务中聚焦的是如何提供更好的服务,因此可以在以下几个方向实施数据隔离

  • 应用数据隔离
    多个业务服务器使用不同的数据库服务器,虽然有隔离故障、提高数据安全等好处,但是也引入了分布式事务的问题。
  • 租户隔离
    提供统一的云服务,但是对不同的用户分组单独使用一些服务实例来提供服务,这样这些服务实例挂掉了也只会影响对应分组的用户,这样的用户分组称为租户
    对于单独的一个租户,有独立服务独立数据库、共享服务独立数据库和共享服务共享数据库三种方式,可以根据成本和安全性来考虑选择哪种方案。

对于业务服务器来说,更重要的是业务层面上的隔离:

  • 业务层
    平台与业务,业务与业务。
  • Provider,即服务提供者
    如订单服务和库存服务,使用上面提到的虚拟化来实现隔离,如果;
  • Consumer,即服务消费者
    下游服务调用多个上游服务(upstream)的时候,如果不对上游服务做服务隔离,一个服务出现问题,就会导致下游服务不可用,这种情况下,可选的隔离方案可以是给每个上游服务都准备一个线程池,称为线程池隔离
    服务消费者端隔离
    这种情况下,就算其中一个服务不可用导致线程池被迅速占满,下游服务仍可以根据预定义的降级方案来忽略这个服务。
    另一种可行的隔离方式是信号量隔离,常见的框架包括 Hytrix、Sentinel、以及 Resilience4j。

网络虚拟化

我们暴露服务时使用的 IP 一般不是业务服务器网卡的真实 IP,而是另外配置的一个虚拟 IP,请求先被打到该 IP,然后由 LVS 等负载均衡技术来找到一个真实 IP。

虚拟网卡通过实现一个字符设备来支持物理层,从而使应用层和物理层就通过这个字符设备联系起来,从这个字符设备读出来的就是虚拟网卡发往物理层的字节流,写入字符设备的数据作为字节流被虚拟网卡接收。
虚拟网卡可以像网卡一样进行配置,常见的虚拟网卡有TUN/TAPVEth
虚拟网桥(Bridge)也是一种虚拟设备,用于将多块网卡(包括虚拟网卡)连接起来。
值得注意的是, Linux 中虚拟网桥是通用网络设备抽象的一种,能够绑定 IP 地址。因此在把网卡接入到网桥上后,网卡原来绑定的 IP 会失效,如果还要像原来那样收发数据,需要把该 IP 绑定到网桥上。

1
2
3
4
5
6
7
# 添加网桥br0
ip link add br0 type bridge
# 把网卡接入网桥,在把eth0和eth1接入到br0后,两个网卡就可以进行通信了。
ip link set eth0 master br0
ip link set eth1 master br0
# 为网桥绑定ip
ip addr add xx.xx.xx.xx/xx dev br0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:17ff:fe6b:bb81 prefixlen 64 scopeid 0x20<link>
ether 02:42:17:6b:bb:81 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 151 bytes 16620 (16.6 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

veth780318a: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet6 fe80::2c08:2dff:fea1:b996 prefixlen 64 scopeid 0x20<link>
ether 2e:08:2d:a1:b9:96 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 227 bytes 24338 (24.3 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

由 Docker Daemon 创建的虚拟网卡 docker0 其实就是一个网桥,可以使用 ethtool 查看设备类型。

1
2
3
4
5
6
7
8
9
10
11
12
$ ethtool -i docker0
# 输出
driver: bridge
version: 2.3
firmware-version: N/A
expansion-rom-version:
bus-info: N/A
supports-statistics: no
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no

根据官方文档,Docker 通过 docker0 网桥在内核层连通了其他的物理或虚拟网卡,从而将所有容器和宿主都放到同一个物理网络下。
veth780318a 是一个 VEth 设备,在容器启动后动态创建。每次启动一个容器的时候,Docker 会新建一对 VETH 设备,其中一个插在 docker0 上,另一个插在该容器里,然后从可用的地址段中选择一个空闲的 IP 地址分配给容器的 VEth,使用 docker0 的 IP 作为默认网关,从而实现宿主机和容器的双向数据通讯。

1
2
3
4
5
6
7
8
9
$ ethtool -S veth780318a
# 输出
NIC statistics:
peer_ifindex: 8

$ ip link
# 输出
9: veth780318a@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 2e:08:2d:a1:b9:96 brd ff:ff:ff:ff:ff:ff link-netnsid 0

上边的 veth780318a@if8 的 peer_ifindex 为 9,和其对应的 VEth 设备的 peer_ifindex 为 8。

接下来可以进入容器查看是否存在对应的 peer_ifindex 为 9 的虚拟网卡(下面的是教程中的执行效果,我并没有执行成功,因为普通的容器镜像里面一般没有安装常用的网络工具)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
root@4c04df175784:/# ip route show
default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.1

# 从这里可以看出veth741a889的“伙伴”确实是在容器中,被重命名为eth0作为容器的网卡,并绑定了ip172.17.0.5,网关为网桥的ip172.17.42.1。
root@4c04df175784:/# ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:05
inet addr:172.17.0.5 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe11:5/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:648 (648.0 B) TX bytes:648 (648.0 B)

root@4c04df175784:/# ethtool -S eth0
NIC statistics:
peer_ifindex: 24

root@4c04df175784:/# ethtool -i eth0
driver: veth
version: 1.0
firmware-version:
bus-info:
supports-statistics: yes
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no

TUN/TAP(Tunnel)

TUN 工作在 OSI 第三层(network),实现了 IP 包的转发,相当于路由。
TAP 工作在 OSI 第二层(data link),实现了 Ethernet 帧的转发,相当于网桥。
操作系统通过 TUN/TAP 设备向绑定该设备的用户空间的程序发送数据,反之,用户空间的程序也可以像操作硬件网络设备那样通过 TUN/TAP 设备发送数据,然后 TUN/TAP 设备会向操作系统的网络栈 push(或 inject)数据包,从而模拟从外部接受数据的过程。
TUN 常用于 VPN,通过使用 TUN,VPN 能够在 IP 包被发出去之前将其进行加密。
TAP 常用于虚拟机,为虚拟机提供网卡。

1
2
ip tuntap add mode tap # 创建tap
ip tuntap add mode tun # 创建tun

VEth(Virtual Ethernet)

VEth 是成对出现的,它的作用是反转通讯数据的方向,当数据从网络栈发送到 VEth 的一端时,数据被传送到 VEth 的另外一端流出,然后放回网络栈,相当于把需要接受的数据转换成需要发送的数据,
常用于虚拟化中穿透 network namespace,把从一个 network namespace 发出的数据包转发到另一个 namespace。

1
ip link add veth1 type veth peer name veth2 # 创建一对VEth,名为veth1和veth2

服务器虚拟化

插槽、内核和内核线程

虚拟机如何分配 CPU 数:vSphere 中 CPU 资源如何分配

Xen、KVM 与 LXC

TODO
服务器虚拟化的三种比较常见的技术。
Xen:
KVM:
LXC:

LXC 的使用方法

  1. 安装
    1
    2
    3
    4
    apt-get install lxc
    lxc-checkconfig # 安装完成后, 用这个命令检查系统是否可以使用 lxc
    # 我执行后没有发现missing的情况,如果没有挂载cgroup可能会出现“Cgroup namespace: CONFIG_CGROUP_NSmissing”的错误,可以挂载一个cgroup
    mount -t cgroup cgroup /mnt/cgroup
  2. 创建容器
    1
    sudo lxc-create -n test -t debian # # 创建一个 debian 系统
    这样创建的容器默认在 /var/lib/lxc/test 中, 为了将容器创建在我们指定的位置, 可以写个简单的配置文件 lxc.conf, 里面只需要一句:
    1
    lxc.rootfs = /home/lxc/test
    然后重新创建容器:
    1
    sudo lxc-create -n test -t debian -f /path/to/lxc.conf
    这样, 就把容器创建在了 /home/lxc/test 中了, /var/lib/lxc/test 中只有一个 config 文件(这个 config 文件可以作为 lxc-create 命令 -f 参数对应配置文件的参考)
  3. 启动容器
    启动后就进行入了虚拟机的控制台了. (果然像传说一样, 几秒就启动完成了 ^_^)
    1
    lxc-start -n test
  4. 停止容器
    在主机中输入停止的命令.
    1
    lxc-stop -n test
  5. 销毁容器
    销毁之前, 可以通过 lxc-ls 来查看有几个容器
    1
    2
    3
    4
    lxc-ls
    test
    lxc-destroy -n test
    lxc-ls

使用示例(配置 python uliweb 开发环境)

尝试在容器配置一次开发环境, 然后通过复制容器, 形成多个虚拟机.

1
2
3
4
5
6
7
8
9
10
11
# 主机中
root@debian-113:~# uliweb # 主机中没有安装uliweb 软件包
-bash: uliweb: command not found
root@debian-113:~# lxc-start -n test
# 虚拟机登录界面, 输入用户名和密码
# 虚拟机中
root@test:~# apt-get install python
root@test:~# apt-get install python-pip
root@test:~# pip install Uliweb
root@test:~# uliweb --version
Uliweb version is 0.3.1

主机中设置网桥, 虚拟机用桥接方式上网, 确保每个虚拟机有独立的 IP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 主机中
root@debian-113:~# lxc-stop -n test
root@debian-113:~# apt-cache search bridge-utils
root@debian-113:~# brctl addbr br0
# 配置主机的网桥
root@debian-113:/var/lib/lxc/test# cat /etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
#auto eth0
iface lo inet loopback

# 追加的网桥配置
auto br0
iface br0 inet static
address 192.168.1.113
netmask 255.255.255.0
gateway 192.168.1.1
bridge_ports eth0
bridge_stp on
bridge_fd 0

root@debian-113:/var/lib/lxc/test# /etc/init.d/networking restart

配置容器的网络(也是在主机中修改容器的配置文件)

1
2
3
4
5
6
7
root@debian-113:/var/lib/lxc/test# cat /var/lib/lxc/test/config
... ... (很多默认生成的配置)
# network <-- 这个 network 相关的是要追加的
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0

启动 Linux 容器, 进入虚拟机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@debian-113:/var/lib/lxc/test# lxc-start -n test
# 登录进入虚拟机, 确认虚拟机的IP
root@test:~# cat /etc/network/interfaces <-- 默认是自动获取IP
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
root@test:~# ifconfig <-- 我的机器自动分配的 192.168.1.167
# 创建一个简单的uliweb工程
root@test:~# cd /home/
root@test:/home# mkdir CM-web
root@test:/home# cd CM-web/
root@test:/home/CM-web# uliweb makeproject test
root@test:/home/CM-web# cd test/
root@test:/home/CM-web/test# uliweb makeapp first_app
root@test:/home/CM-web/test# uliweb runserver -h 0.0.0.0

启动 Web 服务后, 就可以在主机的浏览器中 通过 http://192.168.1.167:8000/ 来访问虚拟机中的 web 服务了.
最后, 复制一个新的容器, 也就是再重新生成一个上面的 python uliweb 开发环境

1
2
3
4
5
6
7
8
9
10
11
# 在主机中
root@debian-113:~# cd /var/lib/lxc
root@debian-113:/var/lib/lxc# cp -r test test2
# 修改 test2/config 如下
lxc.utsname = test2 <-- 修改名称
xc.rootfs = /home/lxc/test2 <-- 修改 rootfs位置
... ... <-- 其它部分不用修改, 和 test 一样就行
root@debian-113:/var/lib/lxc# cd /home/lxc/
root@debian-113:/home/lxc# cp -r test test2 <-- 重新复制一份 rootfs
root@debian-113:/home/lxc# lxc-start -n test2 <-- 启动 test2 虚拟机, 其中环境和 test一样, IP会不一样, 自动获取的
# 进入 test2 虚拟机中, 可以直接启动之前的 uliweb 测试工程, 也可以从主机中访问其web服务.

LXC

原理

通过 namespace 进行资源的隔离,Gust1 下的进程与 Guset2 下的进程是独立的,可以看作运行在两台物理机上一样。Contaniner 管理工具就是对 Guest 进行管理的(创建、销毁)。
下图是对 LXC 架构的介绍。

虚拟化管理平台

虚拟化虽然可以用于自由分配资源,但是在生产环境内如果要一台一台机器安装及配置并不现实,一般来说都会尝试使用一个管理平台来自动化、批量化虚拟机的管理,或者从零开始使用 Libvirt 这样的库来实现自己需要的功能。
Libvirt 库提供了一套用于管理虚拟机和其他虚拟化功能的 Linux API,它支持各种虚拟机监控程序,包括 Xen 和 KVM,以及 QEMU 和用于其他操作系统的一些虚拟产品。

容器管理平台

容器管理平台与其他虚拟化平台有所不同,因为和微服务关系密切,所以更关注高可用、弹性扩展等特性,如Docker Swam、Kubernetes。

参考

  1. 隔离
    猿学~程序员必知的六种隔离技术
    谈谈怎么做【服务隔离】
  2. 网络设备虚拟化
    网络设备设备虚拟化
    一文搞懂网络虚拟化
    从 Bridge 到 OVS,探索虚拟交换机
    虚拟网络设备
    Linux 上的基础网络设备详解
    Linux-虚拟网络设备-LinuxBridge
    Linux-虚拟网络设备-tun/tap
    Linux-虚拟网络设备-veth pair
    Linux-虚拟网络设备-OpenvSwitch(持续更新)
  3. 硬件虚拟化
    硬件虚拟化技术浅析
    Compare of Xen, KVM, LXC and Traditional VM
  4. Libvirt
    Libvirt
  5. Xen
    Xen
  6. KVM
    Kernel Virtual Machine(KVM)
    KVM 源代码分析 1:基本工作原理
    在 Centos6.5 上部署 kvm 虚拟化技术
  7. LXC
    LXC 的介绍
    LinuX Container(LXC)
    Linux 容器的使用
  8. Docker
    使用 NGINX 和 NGINX Plus 进行 Docker Swarm 负载均衡
    DockerSwarm 提供的负载均衡运行于每个节点上(应该是 DockerService 中的某个 Job),提供有限的负载均衡服务(TCP 层负载均衡),因此引入 Nginx 是有道理的。
    据说 Nginx Plus 可以实现服务弹性伸缩的功能,但还没试过。