docker入门基础(三)
四、docker-machine
前面我们的实验环境中只有一个 docker host,所有的容器都是运行在这一个 host 上的。但在真正的环境中会有多个 host,容器在这些 host 中启动、运行、停止和销毁,相关容器会通过网络相互通信,无论它们是否位于相同的 host。
对于这样一个 multi-host 环境,我们将如何高效地进行管理呢?
我们面临的第一个问题是:为所有的 host 安装和配置 docker。
在前面我们手工安装了第一个 docker host,步骤包括:
- 安装 https CA 证书
- 添加 GPG key
- 添加 docker apt 源
- 安装 docker
可见步骤还是挺多的,对于多主机环境手工方式效率低且不容易保证一致性,针对这个问题,docker 给出的解决方案是 Docker Machine。
用 Docker Machine 可以批量安装和配置 docker host,这个 host 可以是本地的虚拟机、物理机,也可以是公有云中的云主机。
实验环境
node1 192.168.2.110 Centos 7.5
host1 192.168.2.120 Centos 7.5
host2 192.168.2.130 Centos 7.5
在node1上部署docker-machine通过dockermachine在其余两个host上部署docker
1、安装docker-machine
安装方法很简单,执行如下命令:
curl -L https://github.com/docker/machine/releases/download/v0.9.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine &&
chmod +x /tmp/docker-machine &&
sudo cp /tmp/docker-machine /usr/local/bin/docker-machine
[root@host1 ~]# curl -L https://github.com/docker/machine/releases/download/v0.9.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 617 0 617 0 0 551 0 --:--:-- 0:00:01 --:--:-- 551
100 24.1M 100 24.1M 0 0 85576 0 0:04:55 0:04:55 --:--:-- 85442
[root@host1 ~]# chmod +x /tmp/docker-machine
[root@host1 ~]# sudo cp /tmp/docker-machine /usr/local/bin/docker-machine
下载的执行文件被放到 /usr/local/bin 中,执行docker-mahine version
验证命令是否可用:
[root@host1 ~]# docker-machine version
docker-machine version 0.9.0, build 15fd4c7
为了得到更好的体验,我们可以安装 bash completion script,这样在 bash 能够通过 tab
键补全 docker-mahine
的子命令和参数。安装方法是从https://github.com/docker/machine/tree/master/contrib/completion/bash下载 completion script:
将其放置到 /etc/bash_completion.d
目录下。然后将如下代码添加到$HOME/.bashrc
:
[root@host1 bash_completion.d]# PS1='[\u@\h \W$(__docker_machine_ps1)]\$ '
#下载completion script:docker-machine-prompt.bash docker-machine-wrapper.bash docker-machine.bash 将其放在/etc/bash_completion.d目录下
#命令为:
[root@host1 bash_completion.d]# scripts=( docker-machine-prompt.bash docker-machine-wrapper.bash docker-machine.bash ); for i in "${scripts[@]}"; do sudo wget https://raw.githubusercontent.com/docker/machine/v0.13.0/contrib/completion/bash/${i} -P /etc/bash_completion.d; done
其作用是设置 docker-machine 的命令行提示符,不过要等到部署完其他两个 host 才能看出效果。
若提示command not found 命令未找到,
更改~/.bashrc,在bashrc中添加以下三行:
source /etc/bash_completion.d/docker-machine-wrapper.bash
source /etc/bash_completion.d/docker-machine-prompt.bash
source /etc/bash_completion.d/docker-machine.bash
重新source下bashrc
source /root/.bashrc
2、创建docker-machine
对于 Docker Machine 来说,术语 Machine
就是运行 docker daemon 的主机。“创建 Machine” 指的就是在 host 上安装和部署 docker。先执行 docker-machine ls
查看一下当前的 machine:
[root@host1 bash_completion.d]# docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
当前还没有 machine,接下来我们创建第一个 machine: host1 - 192.168.56.120。
创建 machine 要求能够无密码登录远程主机,所以需要先通过如下命令将 ssh key 拷贝到 192.168.56.120:
ssh-copy-id 192.168.56.120
[root@host1 bash_completion.d]# ssh-copy-id 192.168.2.120
/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '192.168.2.120 (192.168.2.120)' can't be established.
ECDSA key fingerprint is SHA256:e+1rZSlP7YYndeivr7Hjikf3D+Yg9it2KBNxmpjpkQ8.
ECDSA key fingerprint is MD5:a8:ab:5e:ac:55:e5:d6:fe:bb:1a:9d:41:19:d5:de:7a.
Are you sure you want to continue connecting (yes/no)? yes
/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.2.120's password:
Permission denied, please try again.
root@192.168.2.120's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '192.168.2.120'"
and check to make sure that only the key(s) you wanted were added.
因为我们是往普通的 Linux 中部署 docker,所以使用 generic
driver,其他 driver 可以参考文档 https://docs.docker.com/machine/drivers/。
--generic-ip-address
指定目标系统的 IP,并命名为 host1
。命令执行过程如下:
[root@node1 ~]# docker-machine create --driver=generic --generic-ip-address=192.168.2.120 host1
Creating CA: /root/.docker/machine/certs/ca.pem
Creating client certificate: /root/.docker/machine/certs/cert.pem
Running pre-create checks...
Creating machine...
#ssh登陆到远程主机
(host1) No SSH key specified. Assuming an existing key at the default location.
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
#安装docker
Provisioning with centos...
#拷贝证书
Copying certs to the local machine directory...
Copying certs to the remote machine...
#配置docker daemon
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
#启动docker
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env host1
[root@node1 ~]# docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
host1 - generic Running tcp://192.168.2.120:2376 v18.09.0
[root@node1 ~]# docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
host1 - generic Running tcp://192.168.2.120:2376 v18.09.0
已经能看到 host1 了。 我们可以登录到 host1 查看 docker daemon 的具体配置 /etc/systemd/system/docker.service。
[root@host1 ~]# vim /etc/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
After=network.target
[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver devicemapper --tlsverify --tlscacert /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic
ExecReload=/bin/kill -s HUP
MountFlags=slave
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process
Environment=
[Install]
WantedBy=multi-user.target
-H tcp://0.0.0.0:2376
使 docker daemon 接受远程连接。--tls*
对远程连接启用安全认证和加密。
同时我们也看到 hostname 已经设置为 host1
:
使用同样的方法创建 host2:
[root@node1 ~]# docker-machine create --driver=generic --generic-ip-address=192.168.2.130 host2
Running pre-create checks...
Creating machine...
(host2) No SSH key specified. Assuming an existing key at the default location.
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with centos...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env host2
[root@node1 ~]# docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
host1 - generic Running tcp://192.168.2.120:2376 v18.09.0
host2 - generic Running tcp://192.168.2.130:2376 v18.09.0
3、管理docker-machine
用 docker-machine
创建 machine 的过程很简洁,非常适合多主机环境。除此之外,Docker Machine 也提供了一些子命令方便对 machine 进行管理。其中最常用的就是无需登录到 machine 就能执行 docker 相关操作。
我们前面学过,要执行远程 docker 命令我们需要通过 -H
指定目标主机的连接字符串,比如:
docker -H tcp://192.168.2.120:2376 ps
Docker Machine则让这个过程更简单
docker-machine env host1 直接访问host1的所有环境变量
[root@node1 ~]# docker-machine env host1
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.2.120:2376"
export DOCKER_CERT_PATH="/root/.docker/machine/machines/host1"
export DOCKER_MACHINE_NAME="host1"
# Run this command to configure your shell:
# eval $(docker-machine env host1)
[root@node1 ~]# docker-machine env host2
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.2.130:2376"
export DOCKER_CERT_PATH="/root/.docker/machine/machines/host2"
export DOCKER_MACHINE_NAME="host2"
# Run this command to configure your shell:
# eval $(docker-machine env host2)
根据提示,执行 eval $(docker-machine env host1)
:
[root@node1 ~]# eval $(docker-machine env host1)
[root@node1 ~ [host1]]#
[root@node1 ~ [host1]]#
然后,就可以看到命令行提示符已经变了,其原因是我们之前在$HOME/.bashrc
中配置了 PS1='[\u@\h \W$(__docker_machine_ps1)]\$ '
,用于显示当前 docker host。
[root@node1 ~ [host1]]# docker run --name w1 -itd busybox
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
57c14dd66db0: Pull complete
Digest: sha256:7964ad52e396a6e045c39b5a44438424ac52e12e4d5a25d94895f2058cb863a0
Status: Downloaded newer image for busybox:latest
0dada375dee242eca50ab534cf39efe44cd5c502c4bc19e9888b94092faebcf2
docker: Error response from daemon: OCI runtime create failed: rootfs (/var/lib/docker/devicemapper/mnt/6f719b81e2a0c68c48629815696efb4e596575694176f424941d06545164569d/rootfs) does not exist: unknown.
[root@node1 ~ [host1]]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
执行 eval $(docker-machine env host2)
切换到 host2:
[root@node1 ~ [host1]]# eval $(docker-machine env host2)
[root@node1 ~ [host2]]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
介绍几个有用的 docker-machine 子命令:
docker-machine upgrade
更新 machine 的 docker 到最新版本,可以批量执行:
[root@node1 ~ [host2]]# docker-machine upgrade host1 host2
Waiting for SSH to be available...
Waiting for SSH to be available...
Detecting the provisioner...
Detecting the provisioner...
Upgrading docker...
Upgrading docker...
Restarting docker...
Restarting docker...
docker-machine config
查看 machine 的 docker daemon 配置:
[root@node1 ~]# docker-machine config host1
--tlsverify
--tlscacert="/root/.docker/machine/machines/host1/ca.pem"
--tlscert="/root/.docker/machine/machines/host1/cert.pem"
--tlskey="/root/.docker/machine/machines/host1/key.pem"
-H=tcp://192.168.2.120:2376
[root@node1 ~]# docker-machine config host2
--tlsverify
--tlscacert="/root/.docker/machine/machines/host2/ca.pem"
--tlscert="/root/.docker/machine/machines/host2/cert.pem"
--tlskey="/root/.docker/machine/machines/host2/key.pem"
-H=tcp://192.168.2.130:2376
docker-machine scp
可以在不同 machine 之间拷贝文件,比如:
docker-machine scp host1:/tmp/a host2:/tmp/b
五、docker跨主机网络方案
docker跨主机网络方案包括:
- docker 原生的 overlay 和 macvlan。
- 第三方方案:常用的包括 flannel、weave 和 calico。
docker 网络是一个非常活跃的技术领域,不断有新的方案开发出来,那么要问个非常重要的问题了:
如此众多的方案是如何与 docker 集成在一起的?
答案是:libnetwork 以及 CNM。
libnetwork & CNM
libnetwork 是 docker 容器网络库,最核心的内容是其定义的 Container Network Model (CNM),这个模型对容器网络进行了抽象,由以下三类组件组成:
Sandbox
Sandbox 是容器的网络栈,包含容器的 interface、路由表和 DNS 设置。 Linux Network Namespace 是 Sandbox 的标准实现。Sandbox 可以包含来自不同 Network 的 Endpoint。
Endpoint
Endpoint 的作用是将 Sandbox 接入 Network。Endpoint 的典型实现是 veth pair,后面我们会举例。一个 Endpoint 只能属于一个网络,也只能属于一个 Sandbox。
Network
Network 包含一组 Endpoint,同一 Network 的 Endpoint 可以直接通信。Network 的实现可以是 Linux Bridge、VLAN 等
libnetwork CNM 定义了 docker 容器的网络模型,按照该模型开发出的 driver 就能与 docker daemon 协同工作,实现容器网络。docker 原生的 driver 包括 none、bridge、overlay 和 macvlan,第三方 driver 包括 flannel、weave、calico 等。
为支持容器跨主机通信,Docker 提供了 overlay driver,使用户可以创建基于 VxLAN 的 overlay 网络。VxLAN 可将二层数据封装到 UDP 进行传输,VxLAN 提供与 VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。有关 VxLAN 更详细的内容可参考 CloudMan 在《每天5分钟玩转 OpenStack》中的相关章节。
Docerk overlay 网络需要一个 key-value 数据库用于保存网络状态信息,包括 Network、Endpoint、IP 等。Consul、Etcd 和 ZooKeeper 都是 Docker 支持的 key-vlaue 软件,我们这里使用 Consul。
1、overlay
1)实验环境描述
node1 192.168.2.110 host1 192.168.2.120 host2 192.168.2.130
最简单的方式是以容器方式运行 Consul:
在node1上部署支持的组件 consul
[root@node1 ~]# docker run -d -p 8500:8500 -h consul --name consul progrium/consul -server -bootstrap
容器启动后,可以通过 http://192.168.2.110:8500 访问 Consul。
接下来修改 host1 和 host2 的 docker daemon 的配置文件/etc/systemd/system/docker.service.d/10-machine.conf
。
[root@host1 ~]# vim /etc/systemd/system/docker.service.d/10-machine.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
/etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--cluster-store=consul://192.168.2.110:8500 --cluster-advertise=ens33:2376
Environment=
重启 docker daemon。
systemctl daemon-reload
systemctl restart docker.service
host1 和 host2 将自动注册到 Consul 数据库中。
http://192.168.2.110:8500/ui/#/dc1/kv/docker/nodes/
在 host1 中创建 overlay 网络 ov_net1:
[root@host1 ~]# docker network create -d overlay ov_net1
c82b3b1be3e8035d0088952ff2e2ab42d323496dcde08bd813aaea72f09b991a
[root@host1 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
9e1bc9528540 bridge bridge local
12e0fcdac081 host host local
797eee7fca29 none null local
c82b3b1be3e8 ov_net1 overlay global
-d overlay
指定 driver 为 overaly
注意到 ov_net1
的 SCOPE 为 global,而其他网络为 local。在 host2 上查看存在的网络:
[root@host2 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
a8b54548ffa0 bridge bridge local
146fe14636bd host host local
3ef7d580564a none null local
c82b3b1be3e8 ov_net1 overlay global
host2 上也能看到 ov_net1。这是因为创建 ov_net1 时 host1 将 overlay 网络信息存入了 consul,host2 从 consul 读取到了新网络的数据。之后 ov_net 的任何变化都会同步到 host1 和 host2。
docker network inspect
查看 ov_net1 的详细信息:
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
]
},
IPAM 是指 IP Address Management,docker 自动为 ov_net1 分配的 IP 空间为 10.0.0.0/24。
在ov_net1网络上创建运行容器
[root@host1 ~]# docker run --name bbox1 -itd --network ov_net1 busybox
99e3ce359a1ae41667f7b09b544a4a8c6e3009b18da40cd96832b8aedb76dc7d
[root@host1 ~]# docker exec bbox1 ip r
default via 172.18.0.1 dev eth1
10.0.0.0/24 dev eth0 scope link src 10.0.0.2
172.18.0.0/16 dev eth1 scope link src 172.18.0.2
bbox1 有两个网络接口 eth0 和 eth1。eth0 IP 为 10.0.0.2,连接的是 overlay 网络 ov_net1。eth1 IP 172.18.0.2,容器的默认路由是走 eth1,eth1 是哪儿来的呢?
其实,docker 会创建一个 bridge 网络 “docker_gwbridge”,为所有连接到 overlay 网络的容器提供访问外网的能力。
[root@host1 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
9e1bc9528540 bridge bridge local
a4d35bce9b5a docker_gwbridge bridge local
12e0fcdac081 host host local
797eee7fca29 none null local
c82b3b1be3e8 ov_net1 overlay global
查看网络
[root@host1 ~]# docker network inspect docker_gwbridge
[
{
"Name": "docker_gwbridge",
"Id": "a4d35bce9b5a03ef3c3cbaf8139873aad139b7c6ca222cbb59d72cfa05c96675",
"Created": "2018-12-17T16:12:45.850674561+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"99e3ce359a1ae41667f7b09b544a4a8c6e3009b18da40cd96832b8aedb76dc7d": {
"Name": "gateway_1c1435110045",
"EndpointID": "f876cab800aecfc9eaf4a8573b662ef3305891a8c64eab52a2fefefc858b059e",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.enable_icc": "false",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.name": "docker_gwbridge"
},
"Labels": {}
}
]
而且此网络的网关就是网桥 docker_gwbridge 的 IP 172.18.0.1
[root@host1 ~]# ifconfig docker_gwbridge
docker_gwbridge: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.18.0.1 netmask 255.255.0.0 broadcast 172.18.255.255
inet6 fe80::42:61ff:fe3d:13fa prefixlen 64 scopeid 0x20<link>
ether 02:42:61:3d:13:fa txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
这样容器 bbox1 就可以通过 docker_gwbridge 访问外网。
[root@host1 ~]# docker exec bbox1 ping -c 3 www.baidu.com
PING www.baidu.com (163.177.151.109): 56 data bytes
64 bytes from 163.177.151.109: seq=0 ttl=55 time=12.698 ms
64 bytes from 163.177.151.109: seq=1 ttl=55 time=11.510 ms
64 bytes from 163.177.151.109: seq=2 ttl=55 time=12.060 ms
--- www.baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 11.510/12.089/12.698 ms
如果外网要访问容器,可通过主机端口映射,比如:
docker run -p 80:80 -d --net ov_net1 --name web1 httpd
2)跨主机通信
在 host2 中运行容器 bbox2:
[root@host2 ~]# docker run --name bbox2 -itd --network ov_net1 busybox
1e004591c73ffc6e76098a70fa26ca6db7acf8d386bcc7dbdcce872f741404a5
[root@host2 ~]# docker exec bbox2 ip r
default via 172.18.0.1 dev eth1
10.0.0.0/24 dev eth0 scope link src 10.0.0.3
172.18.0.0/16 dev eth1 scope link src 172.18.0.2
[root@host2 ~]# docker exec bbox2 ping -c 2 bbox1
PING bbox1 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=5.838 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.657 ms
bbox2 IP 为 10.0.0.3,可以直接 ping bbox1:
可见 overlay 网络中的容器可以直接通信,同时 docker 也实现了 DNS 服务。
3)overlay 网络的连通:
docker 会为每个 overlay 网络创建一个独立的 network namespace,其中会有一个 linux bridge br0,endpoint 还是由 veth pair 实现,一端连接到容器中(即 eth0),另一端连接到 namespace 的 br0 上。
br0 除了连接所有的 endpoint,还会连接一个 vxlan 设备,用于与其他 host 建立 vxlan tunnel。容器之间的数据就是通过这个 tunnel 通信的。
要查看 overlay 网络的 namespace 可以在 host1 和 host2 上执行 ip netns
(请确保在此之前执行过 ln -s /var/run/docker/netns /var/run/netns
),可以看到两个 host 上有一个相同的 namespace :1-c82b3b1be3,
[root@host2 ~]# ln -s /var/run/docker/netns/ /var/run/netns
[root@host2 ~]# ip netns
c6bd1da0cdcf (id: 2)
1-c82b3b1be3 (id: 1)
535be03f749a (id: 0)
[root@host1 ~]# ln -s /var/run/docker/netns/ /var/run/netns
[root@host1 ~]# ip netns
1c1435110045 (id: 1)
1-c82b3b1be3 (id: 0)
这就是 ov_net1 的 namespace,查看 namespace 中的 br0 上的设备。
[root@host1 ~]yum install -y bridge-utils
[root@host1 ~]# ip netns exec 1-c82b3b1be3 brctl show
bridge name bridge id STP enabled interfaces
br0 8000.be0804033d22 no veth0
vxlan0
查看 vxlan1 设备的具体配置信息可知此 overlay 使用的 VNI为256
9: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN mode DEFAULT group default
link/ether ca:6b:e3:85:45:b9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 1
vxlan id 256
4)overlay 网络隔离
不同的 overlay 网络是相互隔离的
我们创建第二个 overlay 网络 ov_net2 并运行容器 bbox3。
[root@host1 ~]# docker network create -d overlay ov_net2
9dce57a6c3430c29c214787cdee62b81d5c2af1509b0712864946d62b9fc01d5
[root@host1 ~]# docker run --name bbox3 -itd --network ov_net2 busybox
e247d50629401d9a1c2e3fc5a2b852cb3bc99cb08d1afaf9c0312a025d08d8e2
[root@host1 ~]# docker exec -it bbox3 ip r
default via 172.18.0.1 dev eth1
10.0.1.0/24 dev eth0 scope link src 10.0.1.2
172.18.0.0/16 dev eth1 scope link src 172.18.0.3
bbox3 分配到的 IP 是 10.0.1.2,尝试 ping bbox1(10.0.0.2)
[root@host1 ~]# docker exec -it bbox3 ping -c 2 10.0.0.2
PING 10.0.0.2 (10.0.0.2): 56 data bytes
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
ping 失败,可见不同 overlay 网络之间是隔离的。即便是通过 docker_gwbridge 也不能通信。如果要实现 bbox3 与 bbox1 通信,可以将 bbox3 也连接到 ov_net1。
[root@host1 ~]# docker network connect ov_net1 bbox3
[root@host1 ~]# docker exec -it bbox3 ping -c 2 10.0.0.2
PING 10.0.0.2 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=0.423 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.127 ms
--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.127/0.275/0.423 ms
2、 macvlan
除了 overlay,docker 还开发了另一个支持跨主机容器网络的 driver:macvlan。
macvlan 本身是 linxu kernel 模块,其功能是允许在同一个物理网卡上配置多个 MAC 地址,即多个 interface,每个 interface 可以配置自己的 IP。macvlan 本质上是一种网卡虚拟化技术,Docker 用 macvlan 实现容器网络就不奇怪了。
macvlan 的最大优点是性能极好,相比其他实现,macvlan 不需要创建 Linux bridge,而是直接通过以太 interface 连接到物理网络。
1)实验环境描述
我们会使用 host1 和 host2 上单独的网卡 ens33创建 macvlan。为保证多个 MAC 地址的网络包都可以从 ens33 通过,我们需要打开网卡的混杂模式。
[root@host1 ~]# ip link set ens33 promisc on
在 host1 和 host2 中创建 macvlan 网络 mac_net1
[root@host1 ~]# docker network create -d macvlan \
> --subnet=172.16.100.0/24 \
> --gateway=172.16.100.1 \
> -o parent=ens33 mac_net1
2cb22bc8ead6b896e2b2e8e8a2e4f35ec26a61937ede424c7ea1e42d5be24c2b
注意:在 host2 中也要执行相同的命令。
① -d macvlan
指定 driver 为 macvlan。
② macvlan 网络是 local 网络,为了保证跨主机能够通信,用户需要自己管理 IP subnet。
③ 与其他网络不同,docker 不会为 macvlan 创建网关,这里的网关应该是真实存在的,否则容器无法路由。
④ -o parent
指定使用的网络 interface。
在 host1 中运行容器 bbox1 并连接到 mac_net1。
[root@host1 ~]# docker run --name bbox1 -itd --ip=172.16.100.10 --network mac_net1 busybox
090490a6ea47d773ae865620766cb8f959f68320375568688d8f81a1257362dc
由于 host1 中的 mac_net1 与 host2 中的 mac_net1 本质上是独立的,为了避免自动分配造成 IP 冲突,我们最好通过 --ip
指定 bbox1 地址为 172.16.100.10
2)跨主机通信
在 host2 中运行容器 bbox2,指定 IP 172.16.86.11。
验证 bbox1 和 bbox2 的连通性。
root@host2 ~]# docker exec bbox2 ping -c 2 172.16.100.10
PING 172.16.100.10 (172.16.100.10): 56 data bytes
64 bytes from 172.16.100.10: seq=0 ttl=64 time=8.227 ms
64 bytes from 172.16.100.10: seq=1 ttl=64 time=0.618 ms
--- 172.16.100.10 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.618/4.422/8.227 ms
bbox2 能够 ping 到 bbox1 的 IP 172.16.86.10,但无法解析 “bbox1” 主机名。
可见 docker 没有为 macvlan 提供 DNS 服务,这点与 overlay 网络是不同的。
3)网络结构分析
macvlan 不依赖 Linux bridge,brctl show
可以确认没有创建新的 bridge。
查看一下容器 bbox1 的网络设备:
oot@host1 ~]# docker exec bbox1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
22: eth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:10:64:0a brd ff:ff:ff:ff:ff:ff
除了 lo,容器只有一个 eth0,请注意 eth0 后面的 @if2
,这表明该 interface 有一个对应的 interface,其全局的编号为 2
。根据 macvlan 的原理,我们有理由猜测这个 interface 就是主机的 ens33,确认如下:
[root@host1 ~]# ip link show ens33
2: ens33: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:92:32:12 brd ff:ff:ff:ff:ff:ff
可见,容器的 eth0 就是 ens33 通过 macvlan 虚拟出来的 interface。容器的 interface 直接与主机的网卡连接,这种方案使得容器无需通过 NAT 和端口映射就能与外网直接通信(只要有网关),在网络上与其他独立主机没有区别。
4)用 sub-interface 实现多 macvlan 网络
macvlan 会独占主机的网卡,也就是说一个网卡只能创建一个 macvlan 网络,否则会报错:
但主机的网卡数量是有限的,如何支持更多的 macvlan 网络呢?
好在 macvlan 不仅可以连接到 interface(如ens33),也可以连接到 sub-interface(如ens33.xxx)。
VLAN 是现代网络常用的网络虚拟化技术,它可以将物理的二层网络划分成多达 4094 个逻辑网络,这些逻辑网络在二层上是隔离的,每个逻辑网络(即 VLAN)由 VLAN ID 区分,VLAN ID 的取值为 1-4094。
Linux 的网卡也能支持 VLAN(yum install vlan
),同一个 interface 可以收发多个 VLAN 的数据包,不过前提是要创建 VLAN 的 sub-interface。
比如希望ens33 同时支持 VLAN10 和 VLAN20,则需创建 sub-interfaceens33.10 和ens33.20。
在交换机上,如果某个 port 只能收发单个 VLAN 的数据,该 port 为 Access 模式,如果支持多 VLAN,则为 Trunk 模式,所以接下来实验的前提是:
ens33 要接在交换机的 trunk 口上。虚拟机测试跳过
配置Vlan (host1 host2都要配置)
[root@host1 network-scripts]# pwd
/etc/sysconfig/network-scripts
[root@host1 network-scripts]# vim ifcfg-ens33.10
DEVICE=ens33.10
BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.2.211
PREFIX=24
NETWORK=192.168.2.0
VLAN=yes
[root@host1 network-scripts]# vim ifcfg-ens33.20
DEVICE=ens33.20
BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.2.311
PREFIX=24
NETWORK=192.168.2.0
VLAN=yes
创建VLAN网卡(host1 host2都要配置)
[root@host1 network-scripts]# ip link add link ens33 name ens33.10 type vlan id 10
root@host1 network-scripts]# systemctl restart network
[root@host1 network-scripts]# ip a
......
23: ens33.10@ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 00:0c:29:92:32:12 brd ff:ff:ff:ff:ff:ff
inet 192.168.2.211/24 brd 192.168.2.255 scope global noprefixroute ens33.10
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fe92:3212/64 scope link
[root@host1 network-scripts]# ip link add link ens33 name ens33.20 type vlan id 20
[root@host1 network-scripts]# systemctl restart network
创建 macvlan 网络:(host1 host2都要配置)
[root@host1 network-scripts]# docker network create -d macvlan --subnet=172.16.10.0/24 --gateway=172.16.10.1 -o parent=ens33.10 mac_net10
86eb515908d06ddabab7c2a5ed1313c6a7ad821df10c1585804e678137b46ea2
[root@host1 network-scripts]# docker network create -d macvlan --subnet=172.16.20.0/24 --gateway=172.16.20.1 -o parent=ens33.20 mac_net20
8cf792fe4f85730027796a6eb2d0a02d39893a7462d3bdab023182c58fd78dec
在 host1 中运行容器:
[root@host1 ~]# docker run --name bbox1 -itd --ip=172.16.10.10 --network mac_net10 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
6f58a52b1dcb643074d56294505089b0bc98838809a2bf66a0be44f02a2a51cd
[root@host1 ~]# docker run --name bbox2 -itd --ip=172.16.20.10 --network mac_net20 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
83774612f7947b8cd019ebcfb7dde2af0f40043340d7c1fcb7bd865609c65adf
docker: Error response from daemon: network mac_net20 not found.
在 host2 中运行容器:
[root@host2 ~]# docker run --name bbox3 -itd --ip=172.16.10.11 --network mac_net10 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
2ea31530a8c05d37d7b50aba54be467625b9258c5bd0ac658540dda69b3c4751
[root@host2 ~]# docker run --name bbox4 -itd --ip=172.16.20.11 --network mac_net20 busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
80a04f49f098fba413957fa95d6256d717e4a8756e4da712407393d7a699c123
验证 macvlan 之间的连通性。
[root@host1 ~]# docker exec bbox1 ping -c 2 172.16.10.11
PING 172.16.10.11 (172.16.10.11): 56 data bytes
64 bytes from 172.16.10.11: seq=0 ttl=64 time=8.106 ms
64 bytes from 172.16.10.11: seq=1 ttl=64 time=0.687 ms
bbox1 能 ping 通 bbox3,bbox2 能 ping 通 bbox4。即:同一 macvlan 网络能通信。
bbox1 无法 ping 通 bbox2 和 bbox4。即:不同 macvlan 网络之间不能通信。但更准确的说法应该是:不同 macvlan 网络不能 在二层上 通信。在三层上可以通过网关将 macvlan 连通,下面我们就启用网关。
我们会将 Host 192.168.2.110配置成一个虚拟路由器,设置网关并转发 VLAN10 和 VLAN20 的流量。当然也可以使用物理路由器达到同样的效果。首先确保操作系统 IP Forwarding 已经启用。
[root@host1 ~]# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
在node1配置Vlan
[root@node1 network-scripts]# pwd
/etc/sysconfig/network-scripts
[root@host1 network-scripts]# vim ifcfg-ens33.10
DEVICE=ens33.10
BOOTPROTO=none
ONBOOT=yes
PREFIX=24
VLAN=yes
[root@host1 network-scripts]# vim ifcfg-ens33.20
DEVICE=ens33.20
BOOTPROTO=none
ONBOOT=yes
PREFIX=24
VLAN=yes
将网关 IP 配置到
[root@host1 network-scripts]#ip link add link ens33 name ens33.10 type vlan id 10
[root@host1 network-scripts]#ip link add link ens33 name ens33.20 type vlan id 20
[root@host1 network-scripts]#systemctl restart network
开启VLAN
[root@node1 network-scripts]#ifconfig ens33.10 172.16.10.1 netmask 255.255.255.0 up
[root@host1 network-scripts]#ifconfig ens33.20 172.16.20.1 netmask 255.255.255.0 up
设置iptables路由转发
root@node1 ~]# iptables -t nat -A POSTROUTING -o ens33.10 -j MASQUERADE
[root@node1 ~]# iptables -t nat -A POSTROUTING -o ens33.20 -j MASQUERADE
[root@node1 ~]# iptables -A FORWARD -i ens33.10 -o ens33.20 -m state --state RELATED,ESTABLISHED -j ACCEPT
[root@node1 ~]# iptables -A FORWARD -i ens33.20 -o ens33.10 -m state --state RELATED,ESTABLISHED -j ACCEPT
[root@node1 ~]# iptables -A FORWARD -i ens33.10 -o ens33.20 -j ACCEPT
[root@node1 ~]# iptables -A FORWARD -i ens33.20 -o ens33.10 -j ACCEPT
创建 macvlan 网络:(host1 host2都要配置)
[root@node1 network-scripts]# docker network create -d macvlan --subnet=172.16.10.0/24 --gateway=172.16.10.1 -o parent=ens33.10 mac_net10
86eb515908d06ddabab7c2a5ed1313c6a7ad821df10c1585804e678137b46ea2
[root@node1 network-scripts]# docker network create -d macvlan --subnet=172.16.20.0/24 --gateway=172.16.20.1 -o parent=ens33.20 mac_net20
8cf792fe4f85730027796a6eb2d0a02d39893a7462d3bdab023182c58fd78dec
现在 host1 上位于 mac_net10 的 bbox1 已经可以与 host2 上位于 mac_net20 的 bbox4 通信了。
macvlan 网络的连通和隔离完全依赖 VLAN、IP subnet 和路由,docker 本身不做任何限制,用户可以像管理传统 VLAN 网络那样管理 macvlan。
3、flannel
flannel 是 CoreOS 开发的容器网络解决方案。flannel 为每个 host 分配一个 subnet,容器从此 subnet 中分配 IP,这些 IP 可以在 host 间路由,容器间无需 NAT 和 port mapping 就可以跨主机通信。
每个 subnet 都是从一个更大的 IP 池中划分的,flannel 会在每个主机上运行一个叫 flanneld 的 agent,其职责就是从池子中分配 subnet。为了在各个主机间共享信息,flannel 用 etcd(与 consul 类似的 key-value 分布式数据库)存放网络配置、已分配的 subnet、host 的 IP 等信息。
数据包如何在主机间转发是由 backend 实现的。flannel 提供了多种 backend,最常用的有 vxlan 和 host-gw
1)实验环境描述
192.168.2.110 node1 backend
192.168.2.120 host1 flanel
192.168.2.130 host2 flanel
etcd 部署在 192.168.2.110,host1 和 host2 上运行 flanneld,首先安装配置 etcd。
在node1 配置脚本
[root@node1 ~]# cd /server/scripts/
[root@node1 scripts]# vim etcd.sh
#!/bin/sh
ETCD_VER=v2.3.7
DOWNLOAD_URL=https://github.com/coreos/etcd/releases/download
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
mkdir -p /tmp/test-etcd && tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/test-etcd --strip-components=1
cp /tmp/test-etcd/etcd* /usr/local/bin/
该脚本从 github 上下载 etcd 的可执行文件并保存到 /usr/local/bin/,启动 etcd 并打开 2379 监听端口。
[root@node1 scripts]# etcd -listen-client-urls http://192.168.2.110:2379 -advertise-client-urls http://192.168.2.110:2379
测试 etcd 是否可用:
[root@node1 ~]# etcdctl --endpoint=192.168.2.110:2379 set foo "hello"
hello
[root@node1 ~]# etcdctl --endpoint=192.168.2.110:2379 get foo
hello
可以正常在 etcd 中存取数据了。
2)配置flannel vxlan
flannel 没有现成的执行文件可用,必须自己 build,最可靠的方法是在 Docker 容器中 build。不过用于做 build 的 docker 镜像托管在 gcr.io,国内可能无法直接访问,为方便大家,cloudman把它 mirror 到了 docker hub,构建步骤如下:
1\下载并命名image
[root@node1 ~]# docker pull cloudman6/kube-cross:v1.6.2-2
[root@node1 ~]# docker tag cloudman6/kube-cross:v1.6.2-2 gcr.io/google_containers/kube-cross:v1.6.2-2
2\ 下载 flannel 源码
[root@node1 ~]# git clone https://github.com/coreos/flannel.git
正克隆到 'flannel'...
remote: Enumerating objects: 363, done.
remote: Counting objects: 100% (363/363), done.
remote: Compressing objects: 100% (290/290), done.
remote: Total 25600 (delta 83), reused 212 (delta 62), pack-reused 25237
接收对象中: 100% (25600/25600), 45.50 MiB | 2.37 MiB/s, done.
处理 delta 中: 100% (9187/9187), done.
3\ 开始构建
[root@node1 ~]# cd flannel/
[root@node1 flannel]# make dist/flanneld-amd64
if [ "qemu-amd64-static" = "qemu-amd64-static" ]; then \
wget -O dist/qemu-amd64-static https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-x86_64-static; \
elif [ "qemu-amd64-static" = "qemu-arm64-static"]; then \
wget -O dist/qemu-arm64-static https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-aarch64-static; \
else \
wget -O dist/qemu-amd64-static https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-amd64-static; \
fi
--2018-12-18 12:29:32-- https://github.com/multiarch/qemu-user-static/releases/download/v3.0.0/qemu-x86_64-static
正在解析主机 github.com (github.com)... 13.229.188.59, 13.250.177.223, 52.74.223.119
正在连接 github.com (github.com)|13.229.188.59|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 302 Found
位置:https://github-production-release-asset-2e65be.s3.amazonaws.com/47342812/d4478d00-cffb-11e8-8a74-22686fad33ae?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20181218%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20181218T042934Z&X-Amz-Expires=300&X-Amz-Signature=6ef4ed517480ee30e58b7f6874a05dc3a6c26b015beb11c478e756941deb1048&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dqemu-x86_64-static&response-content-type=application%2Foctet-stream [跟随至新的 URL]
--2018-12-18 12:29:34-- https://github-production-release-asset-2e65be.s3.amazonaws.com/47342812/d4478d00-cffb-11e8-8a74-22686fad33ae?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20181218%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20181218T042934Z&X-Amz-Expires=300&X-Amz-Signature=6ef4ed517480ee30e58b7f6874a05dc3a6c26b015beb11c478e756941deb1048&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3Dqemu-x86_64-static&response-content-type=application%2Foctet-stream
正在解析主机 github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)... 52.216.82.248
正在连接 github-production-release-asset-2e65be.s3.amazonaws.com (github-production-release-asset-2e65be.s3.amazonaws.com)|52.216.82.248|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:4443728 (4.2M) [application/octet-stream]
正在保存至: “dist/qemu-amd64-static”
100%[=======================================================>] 4,443,728 1.90MB/s 用时 2.2s
2018-12-18 12:29:38 (1.90 MB/s) - 已保存 “dist/qemu-amd64-static” [4443728/4443728])
# valid values for ARCH are [amd64 arm arm64 ppc64le s390x]
docker run -e CGO_ENABLED=1 -e GOARCH=amd64 \
-u 0:0 \
-v /root/flannel/dist/qemu-amd64-static:/usr/bin/qemu-amd64-static \
-v /root/flannel:/go/src/github.com/coreos/flannel:ro \
-v /root/flannel/dist:/go/src/github.com/coreos/flannel/dist \
golang:1.10.3 /bin/bash -c '\
cd /go/src/github.com/coreos/flannel && \
make -e dist/flanneld && \
mv dist/flanneld dist/flanneld-amd64'
Unable to find image 'golang:1.10.3' locally
1.10.3: Pulling from library/golang
55cbf04beb70: Pull complete
1607093a898c: Pull complete
9a8ea045c926: Pull complete
d4eee24d4dac: Pull complete
9c35c9787a2f: Pull complete
6a66653f6388: Pull complete
102f6b19f797: Pull complete
Digest: sha256:3c54fa85d6262d2ef7695ee2f8793f1f4f9809ce4a08ca2e213235ef4cfdcb66
Status: Downloaded newer image for golang:1.10.3
go build -o dist/flanneld \
-ldflags '-s -w -X github.com/coreos/flannel/version.Version=v0.10.0-69-g39af3d7e -extldflags "-static"'
# github.com/coreos/flannel
/tmp/go-link-965670900/000022.o: In function `mygetgrouplist':
/workdir/go/src/os/user/getgrouplist_unix.go:15: warning: Using 'getgrouplist' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetgrgid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:38: warning: Using 'getgrgid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetgrnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:43: warning: Using 'getgrnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetpwnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:33: warning: Using 'getpwnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000021.o: In function `mygetpwuid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:28: warning: Using 'getpwuid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-965670900/000004.o: In function `_cgo_f7895c2c5a3a_C2func_getaddrinfo':
/tmp/go-build/cgo-gcc-prolog:46: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
4\将 flanneld 执行文件拷贝到 host1 和 host2。
[root@node1 flannel]# scp dist/flanneld-amd64 192.168.2.120:/usr/local/bin/flanneld
flanneld-amd64 100% 34MB 21.2MB/s 00:01
[root@node1 flannel]# scp dist/flanneld-amd64 192.168.2.130:/usr/local/bin/flanneld
flanneld-amd64 100% 34MB 22.2MB/s 00:01
将 flannel 网络的配置信息保存到 etcd
先将配置信息写到文件 flannel-config.json 中,内容为:
[root@node1 flannel]# vim flannel-config.json
{
"Network": "10.2.0.0/16",
"SubnetLen": 24,
"Backend": {
"Type": "vxlan"
}
}
Network
定义该网络的 IP 池为10.2.0.0/16
。SubnetLen
指定每个主机分配到的 subnet 大小为 24 位,即10.2.X.0/24
。Backend
为vxlan
,即主机间通过 vxlan 通信,后面我们还会讨论host-gw
。
将配置存入 etcd:
[root@node1 flannel]# etcdctl --endpoint=192.168.2.110:2379 set /docker-test/network/config < flannel-config.json
{
"Network": "10.2.0.0/16",
"SubnetLen": 24,
"Backend": {
"Type": "vxlan"
}
}
/docker-test/network/config
是此 etcd 数据项的 key,其 value 为 flannel-config.json 的内容。key 可以任意指定,这个 key 后面会作为 flanneld 的一个启动参数。执行 etcdctl get
确保设置成功
5\启动flannel
在 host1 和 host2 上执行如下命令:
[root@host1 ~]# flanneld -etcd-endpoints=http://192.168.2.110:2379 -iface=ens33 -etcd-prefix=/docker-test/network
#ens33被选作与外部通信的interface
I1218 13:53:33.082312 9063 main.go:527] Using interface with name ens33 and address 192.168.2.120
I1218 13:53:33.082516 9063 main.go:544] Defaulting external address to interface address (192.168.2.120)
I1218 13:53:33.085850 9063 main.go:244] Created subnet
manager: Etcd Local Manager with Previous Subnet: None
I1218 13:53:33.085886 9063 main.go:247] Installing signal handlers
I1218 13:53:33.093993 9063 main.go:386] Found network config - Backend type: vxlan
I1218 13:53:33.094100 9063 vxlan.go:120] VXLAN config: VNI=1 Port=0 GBP=false DirectRouting=false
#识别 flannel 网络池 10.2.0.0/16。
I1218 13:53:33.311792 9063 local_manager.go:234] Picking subnet in range 10.2.1.0 ... 10.2.255.0
# 分配的 subnet 为 10.2.19.0/24。
I1218 13:53:33.315619 9063 local_manager.go:220] Allocated lease (10.2.19.0/24) to current node (192.168.2.120)
I1218 13:53:33.317893 9063 main.go:317] Wrote subnet file to /run/flannel/subnet.env
I1218 13:53:33.317920 9063 main.go:321] Running backend.
I1218 13:53:33.323052 9063 vxlan_network.go:60] watching for new subnet leases
I1218 13:53:33.325031 9063 main.go:429] Waiting for 22h59m59.953184611s to renew lease
I1218 13:53:33.380670 9063 iptables.go:145] Some iptables rules are missing; deleting and recreating rules
I1218 13:53:33.380727 9063 iptables.go:167] Deleting iptables rule: -s 10.2.0.0/16 -j ACCEPT
I1218 13:53:33.390787 9063 iptables.go:167] Deleting iptables rule: -d 10.2.0.0/16 -j ACCEPT
I1218 13:53:33.396709 9063 iptables.go:155] Adding iptables rule: -s 10.2.0.0/16 -j ACCEPT
I1218 13:53:33.424823 9063 iptables.go:155] Adding iptables rule: -d 10.2.0.0/16 -j ACCEPT
-etcd-endpoints
指定 etcd url。
-iface
指定主机间数据传输使用的 interface。
-etcd-prefix
指定 etcd 存放 flannel 网络配置信息的 key。
6\ flanneld 启动后
flanneld 启动后,host1 内部网络会发生一些变化:
- 一个新的 interface
flannel.1
被创建,而且配置上 subnet 的第一个 IP 10.2.19.0。 - host1 添加了一条路由:目的地址为 flannel 网络 10.2.54.0/24 的数据包都由 flannel.1 转发。
[root@host1 ~]# ip addr show flannel.1
17: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether 3a:de:9e:a8:2f:ea brd ff:ff:ff:ff:ff:ff
inet 10.2.19.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::38de:9eff:fea8:2fea/64 scope link
valid_lft forever preferred_lft forever
[root@host1 ~]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.54.0/24 via 10.2.54.0 dev flannel.1 onlink
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.120 metric 101
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.0.0/16 dev docker_gwbridge proto kernel scope link src 172.18.0.1
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.120 metric 100
host2 输出类似,主要区别是 host2 的 subnet 为 10.2.54.0/24:
目的地址为 flannel 网络 10.2.19.0/24 的数据包都由 flannel.1 转发。
[root@host2 ~]# ip addr show flannel.1
8: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether d2:06:eb:04:17:62 brd ff:ff:ff:ff:ff:ff
inet 10.2.54.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::d006:ebff:fe04:1762/64 scope link
valid_lft forever preferred_lft forever
[root@host2 ~]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.19.0/24 via 10.2.19.0 dev flannel.1 onlink
169.254.0.0/16 dev ens33.10 scope link metric 1004
169.254.0.0/16 dev ens33.20 scope link metric 1005
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.130 metric 101
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.0.0/16 dev docker_gwbridge proto kernel scope link src 172.18.0.1
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.130 metric 100
3)配置 Docker 连接 flannel
编辑 host1 的 Docker 配置文件/etc/systemd/system/docker.service.d/10-machine.conf,设置 --bip
和 --mtu
。
[root@host1 docker.service.d]# pwd
/etc/systemd/system/docker.service.d
[root@host1 docker.service.d]# cp 10-machine.conf 10-machine.conf.bak
[root@host1 docker.service.d]# vim 10-machine.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
/etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--bip=10.2.19.1/24 --mtu=1450
Environment=
这两个参数的值必须与 /run/flannel/subnet.env 中 FLANNEL_SUBNET
和FLANNEL_MTU
一致。
[root@host1 ~]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.2.0.0/16
FLANNEL_SUBNET=10.2.19.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=false
重启 Docker daemon。
[root@host1 docker.service.d]# systemctl daemon-reload
[root@host1 docker.service.d]# systemctl restart docker
docker 会将10.2.19.1配置到Linux bridge docker0上,并添加10.2.19.0/24的路由
[root@host1 docker.service.d]# ip r
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1
host2 配置类似:
[root@host2 ~]# cd /etc/systemd/system/docker.service.d/
[root@host2 docker.service.d]# cp 10-machine.conf 10-machine.conf.bak
[root@host2 docker.service.d]# vim 10-machine.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
/etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--bip=10.2.54.1/24 --mtu=1450
Environment=
[root@host2 ~]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.2.0.0/16
FLANNEL_SUBNET=10.2.54.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=false
[root@host2 docker.service.d]# systemctl daemon-reload
[root@host2 docker.service.d]# systemctl restart docker
[root@host2 docker.service.d]# ip r
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.19.0/24 via 10.2.19.0 dev flannel.1 onlink
10.2.54.0/24 dev docker0 proto kernel scope link src 10.2.54.1
可见:flannel 没有创建新的 docker 网络,而是直接使用默认的 bridge 网络。同一主机的容器通过 docker0 连接,跨主机流量通过 flannel.1 转发。
4)将容器连接到flannel
在 host1 中运行容器 bbox1并查看IP:
在 host2 中运行容器 bbox2并查看IP:
[root@host1 ~]# docker run --name bbox1 -itd busybox
ea50e600956f0dba7a321913b01b0977e45c34ee694be1a17b5329c6395024aa
[root@host1 ~]# docker exec bbox1 ip r
default via 10.2.19.1 dev eth0
10.2.19.0/24 dev eth0 scope link src 10.2.19.2
[root@host2 ~]# docker run --name bbox2 -itd busybox
0aa6b6f1461707d9f15b49e66dd240f7611ed4f831e450f7ce18b365cb9a13b0
[root@host2 ~]# docker exec bbox2 ip r
default via 10.2.54.1 dev eth0
10.2.54.0/24 dev eth0 scope link src 10.2.54.2
bbox1 和 bbox2 的 IP 分别为10.2.19.2 10.2.54.2
5)flannel vxlan网络连通性
测试 bbox1 和 bbxo2 的连通性:
[root@host1 ~]# docker exec bbox1 ping -c 3 10.2.54.2
PING 10.2.54.2 (10.2.54.2): 56 data bytes
64 bytes from 10.2.54.2: seq=0 ttl=62 time=6.290 ms
64 bytes from 10.2.54.2: seq=1 ttl=62 time=0.828 ms
64 bytes from 10.2.54.2: seq=2 ttl=62 time=0.738 ms
--- 10.2.54.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.738/2.618/6.290 ms
bbox1 能够 ping 到位于不同 subnet 的 bbox2,通过 traceroute
分析一下 bbox1 到 bbox2 的路径。
[root@host1 ~]# docker exec bbox1 traceroute 10.2.54.2
traceroute to 10.2.54.2 (10.2.54.2), 30 hops max, 46 byte packets
1 bogon (10.2.19.1) 0.033 ms 0.023 ms 0.021 ms
2 bogon (10.2.54.0) 2.005 ms 0.611 ms 2.684 ms
3 bogon (10.2.54.2) 1.844 ms 1.881 ms 1.843 ms
1)bbox1和bbox2不是一个subnet 数据包发送给默认网关10.2.19.1(docker0)
2)根据host1的路由表,数据包会发给flannel.1
3)flannel.1将数据包封装成VxLAN,通过ens33发送给host2
3)host2收到解封,发现数据包目的地址为10.2.54.2,根据路由表将数据包发送给flannel.1 通过docker0到达bbox2
另外,flannel 是没有 DNS 服务的,容器无法通过 hostname 通信。
6)flannel vxlan 网络隔离
flannel 为每个主机分配了独立的 subnet,但 flannel.1 将这些 subnet 连接起来了,相互之间可以路由。本质上,flannel 将各主机上相互独立的 docker0 容器网络组成了一个互通的大网络,实现了容器跨主机通信。flannel 没有提供隔离。
因为 flannel 网络利用的是默认的 bridge 网络,所以容器与外网的连通方式与 bridge 网络一样,即:
- 容器通过 docker0 NAT 访问外网
- 通过主机端口映射,外网可以访问容器
7)使用 flannel host-gw backend
flannel 支持多种 backend,前面我们讨论的是 vxlan backend,host-gw 是 flannel 的另一个 backend。
与 vxlan 不同,host-gw 不会封装数据包,而是在主机的路由表中创建到其他主机 subnet 的路由条目,从而实现容器跨主机通信。要使用 host-gw 首先修改 flannel 的配置 flannel-config.json:
[root@node1 flannel]# cp flannel-config.json flannel-config.json.bak
[root@node1 flannel]# vim flannel-config.json
{
"Network": "10.2.0.0/16",
"SubnetLen": 24,
"Backend": {
"Type": "host-gw"
}
}
Type
用 host-gw
替换原先的 vxlan
。更新 etcd 数据库:
[root@node1 flannel]# etcdctl --endpoint=192.168.2.110:2379 set /docker-test/network/config < flannel-config.json
{
"Network": "10.2.0.0/16",
"SubnetLen": 24,
"Backend": {
"Type": "host-gw"
}
}
Ctrl+C 掉之前 host1 和 host2 的 flanneld 进程并重启。
host1上输入如下
[root@host1 ~]# flanneld -etcd-endpoints=http://192.168.2.110:2379 -iface=ens33 -etcd-prefix=/docker-test/network
I1218 14:44:37.393295 11092 main.go:527] Using interface with name ens33 and address 192.168.2.120
I1218 14:44:37.393941 11092 main.go:544] Defaulting external address to interface address (192.168.2.120)
I1218 14:44:37.394544 11092 main.go:244] Created subnet manager: Etcd Local Manager with Previous Subnet: 10.2.19.0/24
I1218 14:44:37.394572 11092 main.go:247] Installing signal handlers
I1218 14:44:37.400960 11092 main.go:386] Found network config - Backend type: host-gw
#flanneld 检查到原先已分配的 subnet 10.2.19.0/24,重用之
I1218 14:44:37.405021 11092 local_manager.go:147] Found lease (10.2.19.0/24) for current IP (192.168.2.120), reusing
I1218 14:44:37.407613 11092 main.go:317] Wrote subnet file to /run/flannel/subnet.env
I1218 14:44:37.407644 11092 main.go:321] Running backend.
I1218 14:44:37.419064 11092 route_network.go:53] Watching for new subnet leases
I1218 14:44:37.420677 11092 main.go:429] Waiting for 22h59m59.97814884s to renew lease
I1218 14:44:37.422758 11092 route_network.go:85] Subnet added: 10.2.54.0/24 via 192.168.2.130
#flanneld 从 etcd 数据库中检索到 host2 的 subnet 10.2.54.0/24,但因为其 type=vxlan,立即忽略。
W1218 14:44:37.422806 11092 route_network.go:88] Ignoring non-host-gw subnet: type=vxlan
#再次发现 subnet 10.2.17.0/24,将其加到路由表中。这次没有忽略 subnet 的原因是此时我们在 host2 上重启了 flanneld,根据当前 etcd 的配置使用 host-gw backend
I1218 14:44:39.939055 11092 route_network.go:85] Subnet added: 10.2.54.0/24 via 192.168.2.130
W1218 14:44:39.939498 11092 route_network.go:102] Replacing existing route to 10.2.54.0/24 via 10.2.54.0 dev index 17 with 10.2.54.0/24 via 192.168.2.130 dev index 2.
查看 host1 的路由表,增加了一条到 10.2.19.0/24 的路由,网关为 host2 的 IP 192.168.2.120。
[root@host1 ~]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1
10.2.54.0/24 via 192.168.2.130 dev ens33
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.120 metric 101
172.18.0.0/16 dev docker_gwbridge proto kernel scope link src 172.18.0.1
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.120 metric 100
类似的,host2 启动 flanneld 时会重用 subnet 10.2.54.0/24,并将 host1 的 subnet 10.2.19.0/24 添加到路由表中,网关为 host1 IP 192.168.2.110。
从 /run/flannel/subnet.env 可以看到 host-gw 使用的 MTU 为 1500:
[root@host1 ~]# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.2.0.0/16
FLANNEL_SUBNET=10.2.19.1/24
FLANNEL_MTU=1500
FLANNEL_IPMASQ=false
这与 vxlan MTU=1450 不同,所以应该修改 docker 启动参数 --mtu=1500
并重启 docker daemon。
[root@host1 ~]# vim /etc/systemd/system/docker.service.d/10-machine.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
/etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--bip=10.2.19.1/24 --mtu=1500
Environment=
8)比较host-gw和vxlan
下面对 host-gw 和 vxlan 这两种 backend 做个简单比较。
- host-gw 把每个主机都配置成网关,主机知道其他主机的 subnet 和转发地址。vxlan 则在主机间建立隧道,不同主机的容器都在一个大的网段内(比如 10.2.0.0/16)。
- 虽然 vxlan 与 host-gw 使用不同的机制建立主机之间连接,但对于容器则无需任何改变,bbox1 仍然可以与 bbox2 通信。
- 由于 vxlan 需要对数据进行额外打包和拆包,性能会稍逊于 host-gw。
4、weave
weave 是 Weaveworks 开发的容器网络解决方案。weave 创建的虚拟网络可以将部署在多个主机上的容器连接起来。对容器来说,weave 就像一个巨大的以太网交换机,所有容器都被接入这个交换机,容器可以直接通信,无需 NAT 和端口映射。除此之外,weave 的 DNS 模块使容器可以通过 hostname 访问。
1)实验环境描述
weave 不依赖分布式数据库(例如 etcd 和 consul)交换网络信息,每个主机上只需运行 weave 组件就能建立起跨主机容器网络。我们会在 host1 和 host2 上部署 weave 并实践 weave 的各项特性。
2)部署 weave
weave 安装非常简单,在 host1 和 host2 上执行如下命令:
[root@host1 ~]# curl -L git.io/weave -o /usr/local/bin/weave
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
0 0 0 0 0 0 0 0 --:--:-- 0:00:02 --:--:-- 0
0 0 0 595 0 0 177 0 --:--:-- 0:00:03 --:--:-- 581k
100 52227 100 52227 0 0 8402 0 0:00:06 0:00:06 --:--:-- 38772
[root@host1 ~]# chmod a+x /usr/local/bin/weave
在host1中启动weave
在 host1 中执行 weave launch
命令,启动 weave 相关服务。weave 的所有组件都是以容器方式运行的,weave 会从 docker hub 下载最新的 image 并启动容器。
weave 运行了三个容器:
weave
是主程序,负责建立 weave 网络,收发数据 ,提供 DNS 服务等。
weaveplugin
是 libnetwork CNM driver,实现 Docker 网络。
weaveproxy
提供 Docker 命令的代理服务,当用户运行 Docker CLI 创建容器时,它会自动将容器添加到 weave 网络。
[root@host1 ~]# weave launch
2.5.0: Pulling from weaveworks/weave
605ce1bd3f31: Pull complete
18e9c1482d54: Pull complete
20978932838c: Pull complete
4738e62f8d03: Pull complete
68add50beeee: Pull complete
Digest: sha256:3a6086f15bf1f68092e372bfbb08d2d3679cf8a2b0f501ceb11c2fccd06a4b03
Status: Downloaded newer image for weaveworks/weave:2.5.0
latest: Pulling from weaveworks/weavedb
9b0681f946a1: Pull complete
Digest: sha256:c280cf4e7208f4ca0d2514539e0f476dd12db70beacdc368793b7736de023d8d
Status: Downloaded newer image for weaveworks/weavedb:latest
Unable to find image 'weaveworks/weaveexec:2.5.0' locally
2.5.0: Pulling from weaveworks/weaveexec
605ce1bd3f31: Already exists
18e9c1482d54: Already exists
20978932838c: Already exists
4738e62f8d03: Already exists
68add50beeee: Already exists
c10a1d502a6f: Pull complete
bec5b671028d: Pull complete
0467a09afdc2: Pull complete
ade22b35f72f: Pull complete
Digest: sha256:425c74052faaf6e76525f5a088a584a44353fb04fa51f6d800644e0acd64fce1
Status: Downloaded newer image for weaveworks/weaveexec:2.5.0
16a9df891e0091dfc06563f66c1c0c542b8f76c697049f74a214a978e488653f
[root@host1 ~]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
weaveworks/weavedb latest 4ac51c93545a 6 weeks ago 698B
weaveworks/weaveexec 2.5.0 6568ae41694a 6 weeks ago 166MB
weaveworks/weave 2.5.0 a5fd9a080afc 6 weeks ago 111MB
busybox latest 59788edf1f3e 2 months ago 1.15MB
weave 会创建一个新的 Docker 网络 weave
:
driver 为 weavemesh
,IP 范围 10.32.0.0/12
。
[root@host1 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
07644a612080 bridge bridge local
a4d35bce9b5a docker_gwbridge bridge local
12e0fcdac081 host host local
2cb22bc8ead6 mac_net1 macvlan local
ad9f7224b03d mac_net20 macvlan local
797eee7fca29 none null local
2d6fa187b80f weave weavemesh local
[root@host1 ~]# docker network inspect weave
[
{
"Name": "weave",
"Id": "2d6fa187b80fb2ca5ca072bec52b01c5370de2c1a420b143169d23299c64bfee",
"Created": "2018-12-18T15:07:13.669491609+08:00",
"Scope": "local",
"Driver": "weavemesh",
"EnableIPv6": false,
"IPAM": {
"Driver": "weavemesh",
"Options": null,
"Config": [
{
"Subnet": "10.32.0.0/12"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {
"works.weave.multicast": "true"
},
"Labels": {}
}
]
weave 已经安装配置完毕.
3)网络结构分析
在 host1 中运行容器 bbox1:
[root@host1 ~]# eval $(weave env)
[root@host1 ~]# docker run --name bbox1 -itd busybox
81e6e68e09921da4363f87274ef2a75a376a49dc53a9dbd8e3fbc8c385b927f0
首先执行 eval $(weave env)
很重要,其作用是将后续的 docker 命令发给 weave proxy 处理。如果要恢复之前的环境,可执行 eval $(weave env --restore)
。
查看一下当前容器 bbox1 的网络配置:
[root@host1 ~]# docker exec -it bbox1 ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
28: eth0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:0a:02:13:02 brd ff:ff:ff:ff:ff:ff
inet 10.2.19.2/24 brd 10.2.19.255 scope global eth0
valid_lft forever preferred_lft forever
30: ethwe@if31: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1376 qdisc noqueue
link/ether 32:ca:97:e1:98:7a brd ff:ff:ff:ff:ff:ff
inet 10.32.0.1/12 brd 10.47.255.255 scope global ethwe
valid_lft forever preferred_lft forever
bbox1有两个网络接口eth0和ethwe,其中eth0连接的就是默认的bridge网络,即docker0。
现在重点分析ethwe,看分配的ip和名字猜测ethwe和weave相关,ethwe@if31告诉我们与ethwe对应的是编号31的interface,从host1的ip link命令找出该interface
[root@host1 ~]# ip link |grep 31
31: vethwepl12678@if30: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue master weave state UP mode DEFAULT group default
vethwepl12678和ethwe是一丢veth pair,而且 vethwepl12678挂载host1的Linux bridge weave上
[root@host1 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02423482878d no veth5568f50
docker_gwbridge 8000.024266baa276 no
weave 8000.86413d6585b5 no vethwe-bridge
vethwepl12678
除了 vethwepl12678,weave 上还挂了一个 vethwe-bridge
,这是什么?让我们更深入的分析一下,查看 ip -d link
输出:
这里出现了多个新 interface:
① vethwe-bridge
与 vethwe-datapath
是 veth pair。
② vethwe-datapath
的父设备(master)是 datapath
。
③ datapath
是一个 openvswitch。
④ vxlan-6784
是 vxlan interface,其 master 也是 datapath
,weave 主机间是通过 VxLAN 通信的。
weave 网络包含两个虚拟交换机:Linux bridge weave
和 Open vSwitch datapath
,veth pair vethwe-bridge
和 vethwe-datapath
将二者连接在一起。
weave
和 datapath
分工不同,weave
负责将容器接入 weave 网络,datapath
负责在主机间 VxLAN 隧道中并收发数据。
在host1内再运行一个容器 bbox2。
[root@host1 ~]# docker run --name bbox2 -itd busybox
7f7dbf50a7d69ad65e35e7802e2b95e4712b7b14729f27da025f8a410749619b
weave DNS 为容器创建了默认域名 weave.local
,bbox1 能够直接通过 hostname 与 bbox2 通信。
[root@host1 ~]# docker exec bbox1 hostname
bbox1.weave.local
[root@host1 ~]# docker exec bbox1 ping -c 3 bbox2
PING bbox2 (10.32.0.2): 56 data bytes
64 bytes from 10.32.0.2: seq=0 ttl=64 time=0.240 ms
64 bytes from 10.32.0.2: seq=1 ttl=64 time=0.133 ms
64 bytes from 10.32.0.2: seq=2 ttl=64 time=0.133 ms
--- bbox2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.133/0.168/0.240 ms
4)Weave 的连通
首先在host2 执行如下命令:
[root@host2 ~]# weave launch 192.168.2.120
这里必须指定 host1 的 IP 192.168.2.120
,这样 host1 和 host2 才能加入到同一个 weave 网络。
运行容器 bbox3:
[root@host2 ~]# eval $(weave env)
[root@host2 ~]# docker run --name bbox3 -itd busybox
c4583ee7ddca631a6997d34a268d3437836f752a8501afada020418219da2c58
bbox3 能够直接 ping bbox1 和 bbox2。
[root@host2 ~]# docker exec bbox3 ping -c 3 bbox1
PING bbox1 (10.32.0.1): 56 data bytes
64 bytes from 10.32.0.1: seq=0 ttl=64 time=6.829 ms
64 bytes from 10.32.0.1: seq=1 ttl=64 time=1.001 ms
64 bytes from 10.32.0.1: seq=2 ttl=64 time=0.898 ms
--- bbox1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.898/2.909/6.829 ms
bbox1、bbox2 和 bbox3 的 IP 分别为 10.32.0.1/12、10.32.0.2/12 和 10.44.0.0/12,注意掩码为 12 位,实际上这三个 IP 位于同一个 subnet 10.32.0.0/12。通过 host1 和 host2 之间的 VxLAN 隧道,三个容器逻辑上是在同一个 LAN 中的,当然能直接通信了。
[root@host2 ~]# docker exec bbox3 ip route
default via 10.2.54.1 dev eth0
10.2.54.0/24 dev eth0 scope link src 10.2.54.2
10.32.0.0/12 dev ethwe scope link src 10.44.0.0
224.0.0.0/4 dev ethwe scope link
流程如下:
1、host2 weave查询到目的地主机 将数据通过VxLAN发送给host1 。
2、host1 weave 接收到数据,根据目的IP将数据转发给bbox1
5)weave网络隔离
默认配置下,weave 使用一个大 subnet(例如 10.32.0.0/12),所有主机的容器都从这个地址空间中分配 IP,因为同属一个 subnet,容器可以直接通信。如果要实现网络隔离,可以通过环境变量 WEAVE_CIDR
为容器分配不同 subnet 的 IP,
本人测试未成功,net和ip均无效
[root@host1 ~]# docker run -e WEAVE_CDIR=net:10.32.2.0/24 -it busybox
[root@host1 ~]# docker run -e WEAVE_CDIR=ip:10.32.2.8/24 -it busybox
6)weave外部通信
weave 是一个私有的 VxLAN 网络,默认与外部网络隔离。外部网络如何才能访问到 weave 中的容器呢?
答案是:
- 首先将主机加入到 weave 网络。
- 然后把主机当作访问 weave 网络的网关。
要将主机加入到 weave,执行 weave expose
。
[root@host1 ~]# weave expose
10.32.0.3
这个 IP 10.32.0.3
会被配置到 host1 的 weave 网桥上。
[root@host1 ~]# ip addr show weave
22: weave: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1376 qdisc noqueue state UP group default qlen 1000
link/ether 86:41:3d:65:85:b5 brd ff:ff:ff:ff:ff:ff
inet 10.32.0.3/12 brd 10.47.255.255 scope global weave
valid_lft forever preferred_lft forever
inet6 fe80::8441:3dff:fe65:85b5/64 scope link
valid_lft forever preferred_lft forever
weave 网桥位于 root namespace,它负责将容器接入 weave 网络。给 weave 配置同一 subnet 的 IP 其本质就是将 host1 接入 weave 网络。 host1 现在已经可以直接与同一 weave 网络中的容器通信了,无论容器是否位于 host1。
在 host1 中 ping 同一主机的 bbox1:
[root@host1 ~]# ping -c 3 10.32.0.1
PING 10.32.0.1 (10.32.0.1) 56(84) bytes of data.
64 bytes from 10.32.0.1: icmp_seq=1 ttl=64 time=0.511 ms
64 bytes from 10.32.0.1: icmp_seq=2 ttl=64 time=0.074 ms
64 bytes from 10.32.0.1: icmp_seq=3 ttl=64 time=0.084 ms
--- 10.32.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.074/0.223/0.511/0.203 ms
ping host2上的bbox3
[root@host1 ~]# ping -c 3 10.44.0.0
PING 10.44.0.0 (10.44.0.0) 56(84) bytes of data.
64 bytes from 10.44.0.0: icmp_seq=1 ttl=64 time=2.28 ms
64 bytes from 10.44.0.0: icmp_seq=2 ttl=64 time=0.780 ms
64 bytes from 10.44.0.0: icmp_seq=3 ttl=64 time=0.691 ms
--- 10.44.0.0 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 0.691/1.253/2.288/0.732 ms
接下来要让其他非 weave 主机访问到 bbox1 和 bbox3,只需将网关指向 host1。例如在 node1 192.168.2.110 上添加如下路由:
[root@node1 ~]# ip route add 10.32.0.0/12 via 192.168.2.120
[root@node1 ~]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.32.0.0/12 via 192.168.2.120 dev ens33
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.110 metric 101
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.110 metric 100
[root@node1 ~]# ping -c 3 10.44.0.0
PING 10.44.0.0 (10.44.0.0) 56(84) bytes of data.
64 bytes from 10.44.0.0: icmp_seq=1 ttl=63 time=1.62 ms
64 bytes from 10.44.0.0: icmp_seq=2 ttl=63 time=2.52 ms
64 bytes from 10.44.0.0: icmp_seq=3 ttl=63 time=1.08 ms
--- 10.44.0.0 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 4008ms
rtt min/avg/max/mdev = 1.087/1.585/2.521/0.497 ms
通过上面的配置我们实现了外网到 weave 这个方向的通信,反方向呢?
其实答案很简单:因为容器本身就挂在默认的 bridge 网络上,docker0 已经实现了 NAT,所以容器无需额外配置就能访问外网。
10.32.0.0/12 是 weave 网络使用的默认 subnet,如果此地址空间与现有 IP 冲突,可以通过 --ipalloc-range
分配特定的 subnet。
weave launch --ipalloc-range 10.2.0.0/16
不过请确保所有 host 都使用相同的 subnet。
5、calico
Calico 是一个纯三层的虚拟网络方案,Calico 为每个容器分配一个 IP,每个 host 都是 router,把不同 host 的容器连接起来。与 VxLAN 不同的是,Calico 不对数据包做额外封装,不需要 NAT 和端口映射,扩展性和性能都很好。
与其他容器网络方案相比,Calico 还有一大优势:network policy。用户可以动态定义 ACL 规则,控制进出容器的数据包,实现业务需求。
1)实验环境描述
Calico 依赖 etcd 在不同主机间共享和交换信息,存储 Calico 网络状态。我们将在 node1 192.168.2.110 上运行 etcd。
Calico 网络中的每个主机都需要运行 Calico 组件,提供容器 interface 管理、动态路由、动态 ACL、报告状态等功能。
启动etcd
etcd 安装配置详细方法请参考 flannel 章节.
在node1 运行命令启动etcd:
[root@node1 ~]# etcd -listen-client-urls http://192.168.2.110:2379 -advertise-client-urls http://192.168.2.110:2379
修改 host1 和 host2 的 Docker daemon 配置文件 /etc/systemd/system/docker.service.d/10-machine.conf, 连接 etcd:
[root@host1 ~]# !vim
vim /etc/systemd/system/docker.service.d/10-machine.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver overlay2 --tlsverify --tlscacert \
/etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=generic \
--cluster-store=etcd://192.168.2.110:2379
Environment=
[root@host1 ~]# systemctl daemon-reload
[root@host1 ~]# systemctl restart docker
2)部署 calico
host1 和 host2 下载 calicoctl:
[root@host1 ~]# wget -O /usr/local/bin/calicoctl https://github.com/projectcalico/calicoctl/releases/download/v1.6.5/calicoctl
[root@host1 ~]# chmod +x /usr/local/bin/calicoctl
[root@host1 ~]# calicoctl --version
calicoctl version v1.6.5, build 614fcf12
在 host1 和 host2 上启动 calico:
创建calico配置文件#### 注意格式和缩进
[root@host1 ~]# cat /etc/calico/calicoctl.cfg
apiVersion: v1
kind: calicoApiConfig
metadata:
spec:
datastoreType: "etcdv2"
etcdEndpoints: "http://192.168.2.110:2379"
启动
[root@host1 ~]# calicoctl node run --node-image=quay.io/calico/node:v2.6.12 -c /etc/calico/calicoctl.cfg
Running command to load modules: modprobe -a xt_set ip6_tables
Enabling IPv4 forwarding
Enabling IPv6 forwarding
Increasing conntrack limit
Removing old calico-node container (if running).
Running the following command to start calico-node:
docker run --net=host --privileged --name=calico-node -d --restart=always -e CALICO_LIBNETWORK_ENABLED=true -e ETCD_ENDPOINTS=http://192.168.2.110:2379 -e NODENAME=host1 -e CALICO_NETWORKING_BACKEND=bird -v /var/log/calico:/var/log/calico -v /var/run/calico:/var/run/calico -v /lib/modules:/lib/modules -v /run:/run -v /run/docker/plugins:/run/docker/plugins -v /var/run/docker.sock:/var/run/docker.sock quay.io/calico/node:v2.6.12
Image may take a short time to download if it is not available locally.
Container started, checking progress logs.
2018-12-19 03:09:54.698 [INFO][8] startup.go 173: Early log level set to info
2018-12-19 03:09:54.698 [INFO][8] client.go 202: Loading config from environment
2018-12-19 03:09:54.698 [INFO][8] startup.go 83: Skipping datastore connection test
2018-12-19 03:09:54.704 [INFO][8] startup.go 259: Building new node resource Name="host1"
2018-12-19 03:09:54.704 [INFO][8] startup.go 273: Initialise BGP data
2018-12-19 03:09:54.707 [INFO][8] startup.go 467: Using autodetected IPv4 address on interface ens37: 172.16.1.120/24
2018-12-19 03:09:54.707 [INFO][8] startup.go 338: Node IPv4 changed, will check for conflicts
2018-12-19 03:09:54.710 [INFO][8] etcd.go 430: Error enumerating host directories error=100: Key not found (/calico) [15]
2018-12-19 03:09:54.711 [INFO][8] startup.go 530: No AS number configured on node resource, using global value
2018-12-19 03:09:54.713 [INFO][8] etcd.go 105: Ready flag is now set
2018-12-19 03:09:54.715 [INFO][8] client.go 133: Assigned cluster GUID ClusterGUID="28d6ce342fb54ad69e6edb9d752e16d4"
2018-12-19 03:09:54.733 [INFO][8] startup.go 419: CALICO_IPV4POOL_NAT_OUTGOING is true (defaulted) through environment variable
2018-12-19 03:09:54.733 [INFO][8] startup.go 659: Ensure default IPv4 pool is created. IPIP mode: off
2018-12-19 03:09:54.735 [INFO][8] startup.go 670: Created default IPv4 pool (192.168.0.0/16) with NAT outgoing true. IPIP mode: off
2018-12-19 03:09:54.736 [INFO][8] startup.go 419: FELIX_IPV6SUPPORT is true (defaulted) through environment variable
2018-12-19 03:09:54.736 [INFO][8] startup.go 626: IPv6 supported on this platform: true
2018-12-19 03:09:54.736 [INFO][8] startup.go 419: CALICO_IPV6POOL_NAT_OUTGOING is false (defaulted) through environment variable
2018-12-19 03:09:54.736 [INFO][8] startup.go 659: Ensure default IPv6 pool is created. IPIP mode: off
2018-12-19 03:09:54.738 [INFO][8] startup.go 670: Created default IPv6 pool (fd80:24e2:f998:72d6::/64) with NAT outgoing false. IPIP mode: off
2018-12-19 03:09:54.767 [INFO][8] startup.go 131: Using node name: host1
2018-12-19 03:09:55.081 [INFO][12] client.go 202: Loading config from environment
Starting libnetwork service
Calico node started successfully
启动过程如下:
① 设置主机网络,例如 enable IP forwarding。
② 下载并启动 calico-node 容器,calico 会以容器的形式运行(与 weave 类似)。
③ 连接 etcd。
④ calico 启动成功
查看calico运行状态:
[root@host1 ~]# calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+-------------------+-------+----------+-------------+
| 172.16.1.130 | node-to-node mesh | up | 03:18:14 | Established |
+--------------+-------------------+-------+----------+-------------+
IPv6 BGP status
No IPv6 peers found.
[root@host2 scripts]# calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+-------------------+-------+----------+-------------+
| 172.16.1.120 | node-to-node mesh | up | 03:18:15 | Established |
+--------------+-------------------+-------+----------+-------------+
IPv6 BGP status
No IPv6 peers found.
创建calico网络
在 host1 或 host2 上执行如下命令创建 calico 网络 cal_ent1:
[root@host1 ~]# docker network create --driver calico --ipam-driver calico-ipam cal_net1
d0760b57695c3dbcf5cb69571984f909456502f69c9a13030305905da68fb4dd
--driver calico
指定使用 calico 的 libnetwork CNM driver。
--ipam-driver calico-ipam
指定使用 calico 的 IPAM driver 管理 IP。
calico 为 global 网络,etcd 会将 cal_net 同步到所有主机。
[root@host1 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
963caa36a8d1 bridge bridge local
d0760b57695c cal_net1 calico global
12e0fcdac081 host host local
797eee7fca29 none null local
3a1e0d8a2730 weave weavemesh local
3)calico网络结构
在 host1 中运行容器 bbox1 并连接到 cal_net1:
[root@host1 ~]# docker run --name bbox1 --net cal_net1 -itd busybox
19d88eb8ee187ef9a0e1592b6ae9f59dafa7481d6e4eea9a6e25c8ca30b316b1
查看 bbox1 的网络配置。
[root@host1 ~]# docker exec bbox1 ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
26: cali0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
inet 192.168.119.2/32 brd 192.168.119.2 scope global cali0
valid_lft forever preferred_lft forever
cali0
是 calico interface,分配的 IP 为 192.168.119.2
。cali0 对应 host1 编号 27
的 interface cali08a3cd4c842
。
[root@host1 ~]# ip a
......
27: cali08a3cd4c842@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether c2:3d:a6:92:fe:b8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::c03d:a6ff:fe92:feb8/64 scope link
valid_lft forever preferred_lft forever
host1 将作为 router 负责转发目的地址为 bbox1 的数据包。
[root@host1 ~]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.120 metric 101
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.120 metric 100
blackhole 192.168.119.0/26 proto bird
192.168.119.2 dev cali08a3cd4c842 scope link
所有发送到 bbox1 的数据都会发给 cali08a3cd4c842
,因为 cali08a3cd4c842
与 cali0
是一对 veth pair,bbox1 能够接收到数据。
接下来我们在 host2 中运行容器 bbox2,也连接到 cal_net1:
[root@host2 scripts]# docker run --name bbox2 --net cal_net1 -itd busybox
ac8faffa86318a830397a8030ca136386fec0063d75e050426a08444bfdcbced
[root@host2 scripts]# docker exec bbox2 ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
20: cali0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
inet 192.168.183.66/32 brd 192.168.183.66 scope global cali0
valid_lft forever preferred_lft forever
[root@host2 scripts]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.54.0/24 dev docker0 proto kernel scope link src 10.2.54.1
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.130 metric 101
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.130 metric 100
192.168.119.0/26 via 172.16.1.120 dev ens37 proto bird
blackhole 192.168.183.64/26 proto bird
192.168.183.66 dev calia8c668b6de2 scope link
bbox2的IP为192.168.183.66 host2主机增加了两条路由:
1、目的地址为host1容器subnet192.168.119.0/26的路由
2、目的地址为本地bbox2的192.168.183.66的路由
同样 host1也自动添加了 192.168.183.64/26的路由
[root@host1 ~]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.120 metric 101
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.120 metric 100
blackhole 192.168.119.0/26 proto bird
192.168.119.2 dev cali08a3cd4c842 scope link
192.168.183.64/26 via 172.16.1.130 dev ens37 proto bird
4)calico的默认连通性
测试一下bbox1和bbox2的连通性
[root@host1 ~]# docker exec bbox1 ping -c 3 bbox2
PING bbox2 (192.168.183.66): 56 data bytes
64 bytes from 192.168.183.66: seq=0 ttl=62 time=6.818 ms
64 bytes from 192.168.183.66: seq=1 ttl=62 time=0.879 ms
64 bytes from 192.168.183.66: seq=2 ttl=62 time=0.773 ms
--- bbox2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.773/2.823/6.818 ms
ping 成功,
数据包流程为
1、bbox1的数据包从cal0发出
[root@host1 ~]# docker exec bbox1 ip route
default via 169.254.1.1 dev cali0
169.254.1.1 dev cali0 scope link
2、数据经过 veth pair到达host1,查看路由表数据由ens37 发给host2(192.168.2.130 172.16.1.130 为内网IP地址)
[root@host1 ~]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.19.0/24 dev docker0 proto kernel scope link src 10.2.19.1
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.120 metric 101
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.120 metric 100
blackhole 192.168.119.0/26 proto bird
192.168.119.2 dev cali08a3cd4c842 scope link
192.168.183.64/26 via 172.16.1.130 dev ens37 proto bird
3、host2收到数据包,根据路由表发送给,进而通过veth pair到达bbox2
[root@host2 scripts]# ip route
default via 192.168.2.1 dev ens33 proto static metric 100
10.2.54.0/24 dev docker0 proto kernel scope link src 10.2.54.1
172.16.1.0/24 dev ens37 proto kernel scope link src 172.16.1.130 metric 101
192.168.2.0/24 dev ens33 proto kernel scope link src 192.168.2.130 metric 100
192.168.119.0/26 via 172.16.1.120 dev ens37 proto bird
blackhole 192.168.183.64/26 proto bird
192.168.183.66 dev calia8c668b6de2 scope link
接下来我们看看不同 calico 网络之间的连通性。
创建 cal_net2。
[root@host2 scripts]# docker network create --driver calico --ipam-driver calico-ipam cal_net2
2b7e049df6cd8b0ea5d346d1aa80500a524b1ee14a9c0e9c6faa7b9ef5128e2d
在host1中运行容器bbox3,连接到cal_net2
[root@host1 ~]# docker run --name bbox3 --net cal_net2 -itd busybox
157cf44e2b18e4c2b023805241818f34d444bb9bc0cc122f002e59ec8da8ae6e
[root@host1 ~]# docker exec bbox3 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
28: cali0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
inet 192.168.119.3/32 brd 192.168.119.3 scope global cali0
valid_lft forever preferred_lft forever
calico给bbix分配的ip为 192.168.119.3
验证bbox1和bbox3的连通性
[root@host1 ~]# docker exec bbox3 ping -c 3 192.168.119.2
PING 192.168.119.2 (192.168.119.2): 56 data bytes
--- 192.168.119.2 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
虽然 bbox1 和 bbox3 都位于 host1,而且都在一个 subnet 192.168.119.0/26,但它们属于不同的 calico 网络,默认不能通行。
calico 默认的 policy 规则是:容器只能与同一个 calico 网络中的容器通信。
calico 的每个网络都有一个同名的 profile,profile 中定义了该网络的 policy。我们具体看一下 cal_net1 的 profile:
[root@host1 ~]# calicoctl get profile cal_net1 -o yaml
- apiVersion: v1
kind: profile
metadata:
name: cal_net1
tags:
- cal_net1
spec:
egress:
- action: allow
destination: {}
source: {}
ingress:
- action: allow
destination: {}
source:
tag: cal_net1
1)name: cal_net1 :命名为cal_net1 这就是calico网络cal_net1的prifile
2)- cal_net1: 为 profile 添加一个 tag cal_net1
。注意,这个 tag 虽然也叫 cal_net1
,其实可以随便设置,这跟上面的 name: cal_net1
没有任何关系。此 tag 后面会用到。
3) egress:对从容器发出的数据包进行控制,当前没有任何限制。
4) ingress: 对进入容器的数据包进行限制,当前设置是接收来自 tag cal_net1
的容器,根据第 ① 步设置我们知道,实际上就是只接收本网络的数据包,这也进一步解释了前面的实验结果。
既然这是默认 policy,那就有方法定制 policy,这也是 calico 较其他网络方案最大的特性。
5)定制 calico policy
Calico 能够让用户定义灵活的 policy 规则,精细化控制进出容器的流量,下面我们就来实践一个场景:
- 创建一个新的 calico 网络
cal_web
并部署一个 httpd 容器web1
。 - 定义 policy 允许
cal_net2
中的容器访问web1
的 80 端口。
首先创建cal_web
[root@host1 ~]# docker network create --driver calico --ipam-driver calico-ipam cal_web
741b5fded82ffba3edac7d94ed405e533cfcc63b121bcbed3c892bf0d71cac85
在 host1 中运行容器 web1,连接到 cal_web:
[root@host1 ~]# docker run --name web1 --net cal_web -d httpd
17eccfdf171de1355deef178fd33fd0e3a2cb3ec4fcee7945f8bf949c52c9b3f
[root@host1 ~]# docker exec -it web1 /bin/sh
# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.119.17 17eccfdf171d
[root@host1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
17eccfdf171d httpd "httpd-foreground" 4 minutes ago Up 4 minutes
web1的ip为192.168.119.17
目前 bbox3 还无法访问 web1 的 80 端口。
[root@host1 ~]# docker exec bbox3 wget 192.168.119.17
Connecting to 192.168.119.17 (192.168.119.17:80)
wget: can't connect to remote host (192.168.119.17): Connection timed out
创建 policy 文件 web.yml,内容为:
[root@host1 ~]# vim web.yml
- apiVersion: v1
kind: profile
metadata:
name: cal_web
spec:
ingress:
- action: allow
protocol: tcp
source:
tag: cal_net2
destination:
ports:
- 80
profile 与 cal_web 网络同名,cal_web 的所有容器(web1)都会应用此 profile 中的 policy。
ingress 允许 cal_net2 中的容器(bbox3)访问。
只开放 80 端口。
应用该 policy。
[root@host1 ~]# calicoctl apply -f web.yml
Successfully applied 1 'profile' resource(s)
现在bbox3已经可以访问web1的http服务
不过 ping 还是不行,因为只放开了 80 端口。
[root@host1 ~]# docker exec bbox3 wget 192.168.119.17
Connecting to 192.168.119.17 (192.168.119.17:80)
index.html 100% |********************************| 45 0:00:00 ETA
[root@host1 ~]# docker exec bbox3 ping -c 3 192.168.119.17
PING 192.168.119.17 (192.168.119.17): 56 data bytes
--- 192.168.119.17 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
上面这个例子比较简单,不过已经向我们展示了 calico 强大的 policy 功能。通过 policy,可以动态实现非常复杂的容器访问控制。有关 calico policy 更多的配置,可参看官网文档 http://docs.projectcalico.org/v2.0/reference/calicoctl/resources/policy。
6)定制IP Pool
我们没有特别配置,calico 会为自动为网络分配 subnet,当然我们也可以定制。
首先定义一个 IP Pool
[root@host1 ~]# vim ipPool.yml
- apiVersion: v1
kind: ipPool
metadata:
cidr: 17.2.0.0/16
[root@host1 ~]# calicoctl create -f ipPool.yml
Successfully created 1 'ipPool' resource(s)
用此 IP Pool 创建 calico 网络。
[root@host1 ~]# docker network create --driver calico --ipam-driver calico-ipam --subnet=17.2.0.0/16 my_net
c09c90a736872401a991f2431bcf7275c5ba51e3c1e271b466d00e24d3f924e7
此时运行容器将分配到指定 subnet 中的 IP。
[root@host1 ~]# docker run --net my_net -it busybox
/ # ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
62: cali0@if63: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
inet 17.2.119.0/32 brd 17.2.119.0 scope global cali0
valid_lft forever preferred_lft forever
当然也可以通过 --ip
为容器指定 IP,但必须在 subnet 范围之内。
[root@host1 ~]# docker run --net my_net --ip 17.2.3.11 -it busybox
[root@host1 ~]# docker exec b3c485a2e81a ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
68: cali0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff
inet 17.2.3.11/32 brd 17.2.3.11 scope global cali0
valid_lft forever preferred_lft forever
6、各种跨主机方案对比
我们将从如下几个方面比较,大家可以根据不同场景选择最合适的方案。
网络模型
采用何种网络模型支持 multi-host 网络?
Distributed Store
是否需要 etcd 或 consul 这类分布式 key-value 数据库存储网络信息?
IPMA
如何管理容器网络的 IP?
连通与隔离
提供怎样的网络连通性?支持容器间哪个级别和哪个类型的隔离?
性能
性能比较。
1)网络模型
跨主机网络意味着将不同主机上的容器用同一个虚拟网络连接起来。这个虚拟网络的拓扑结构和实现技术就是网络模型。
Docker overlay 如名称所示,是 overlay 网络,建立主机间 VxLAN 隧道,原始数据包在发送端被封装成 VxLAN 数据包,到达目的后在接收端解包。
Macvlan 网络在二层上通过 VLAN 连接容器,在三层上依赖外部网关连接不同 macvlan。数据包直接发送,不需要封装,属于 underlay 网络。
Flannel 我们讨论了两种 backend:vxlan 和 host-gw。vxlan 与 Docker overlay 类似,属于 overlay 网络。host-gw 将主机作为网关,依赖三层 IP 转发,不需要像 vxlan 那样对包进行封装,属于 underlay 网络。
Weave 是 VxLAN 实现,属于 overlay 网络。
2)Distributed Store
Docker Overlay、Flannel 和 Calico 都需要 etcd 或 consul。Macvlan 是简单的 local 网络,不需要保存和共享网络信息。Weave 自己负责在主机间交换网络配置信息,也不需要 Distributed Store。
3)IPAM
Docker Overlay 网络中所有主机共享同一个 subnet,容器启动时会顺序分配 IP,可以通过 --subnet
定制此 IP 空间。
Macvlan 需要用户自己管理 subnet,为容器分配 IP,不同 subnet 通信依赖外部网关。
Flannel 为每个主机自动分配独立的 subnet,用户只需要指定一个大的 IP 池。不同 subnet 之间的路由信息也由 Flannel 自动生成和配置。
Weave 的默认配置下所有容器使用 10.32.0.0/12 subnet,如果此地址空间与现有 IP 冲突,可以通过 --ipalloc-range
分配特定的 subnet。
Calico 从 IP Pool(可定制)中为每个主机分配自己的 subnet。
4)连通与隔离
同一 Docker Overlay 网络中的容器可以通信,但不同网络之间无法通信,要实现跨网络访问,只有将容器加入多个网络。与外网通信可以通过 docker_gwbridge 网络。
Macvlan 网络的连通或隔离完全取决于二层 VLAN 和三层路由。
不同 Flannel 网络中的容器直接就可以通信,没有提供隔离。与外网通信可以通过 bridge 网络。
Weave 网络默认配置下所有容器在一个大的 subnet 中,可以自由通信,如果要实现隔离,需要为容器指定不同的 subnet 或 IP。与外网通信的方案是将主机加入到 weave 网络,并把主机当作网关。
Calico 默认配置下只允许位于同一网络中的容器之间通信,但通过其强大的 Policy 能够实现几乎任意场景的访问控制。
5)性能
性能测试是一个非常严谨和复杂的工程,这里我们只尝试从技术方案的原理上比较各方案的性能。
最朴素的判断是:Underlay 网络性能优于 Overlay 网络。
Overlay 网络利用隧道技术,将数据包封装到 UDP 中进行传输。因为涉及数据包的封装和解封,存在额外的 CPU 和网络开销。虽然几乎所有 Overlay 网络方案底层都采用 Linux kernel 的 vxlan 模块,这样可以尽量减少开销,但这个开销与 Underlay 网络相比还是存在的。所以 Macvlan、Flannel host-gw、Calico 的性能会优于 Docker overlay、Flannel vxlan 和 Weave。
Overlay 较 Underlay 可以支持更多的二层网段,能更好地利用已有网络,以及有避免物理交换机 MAC 表耗尽等优势,所以在方案选型的时候需要综合考虑。