本文主要谈谈关于主机网络和容器网络的实现原理!node
容器资源限制python
在某些时候咱们不想让容器肆无忌惮的抢占系统资源,因此就会对其作一系列的限制,这些参数可使用蛮力查看到:git
docker container run --help
主要的限制参数包含如下这些:github
--cpu-shares:CPU 使用占比,如一个容器配置 10,一个配置 20,另一个配置 10,那就是资源分配:1:2:1。redis
--memory:限制内存,好比配置 200M,可是若是不配置 swap,则实际内存实际上是 400M。docker
--memory-swap:限制 swap,结合内存限制使用。flask
虚拟机网络名称空间(选择性了解)vim
docker 网络隔离的实现方式其实就是网络名称空间的方式,可是这方面的知识其实对于就算是运维工程师也不必定有详细的了解,因此这部份内容做为选择性了解。简单的对其实现方式说明,而后看看效果是怎样的。服务器
1. 创建两个网络名称空间:网络
# 查看现有的网络名称空间 ip netns ls # 新建两个网络名称空间 ip netns add net-ns-1 ip netns add net-ns-2 # 在两个网络名称空间中分别查看它的网卡信息 ip netns exec net-ns-1 ip a ip netns exec net-ns-2 ip a
结果如图:
能够看到各自拥有一张回环网卡,而且未启动。咱们能够将其启动起来再查看:
# 分别启动网络名称空间中的回环网卡 ip netns exec net-ns-1 ip link set dev lo up ip netns exec net-ns-2 ip link set dev lo up # 查看启动效果 ip netns exec net-ns-1 ip a ip netns exec net-ns-2 ip a
结果如图:
此时状态变成 UNKNOW,这就至关于一个机器,你至少一头差了网线,另一端没有插线的状态。
2. 新建两个虚拟网卡,并把它们绑定到命名空间中:
# 新建两个网卡 ip link add veth-1 type veth peer name veth-2
结果如图:
将网卡分别分配到两个网络名称空间:
# 将网卡分别分配到指定的网络名称空间 ip link set veth-1 netns net-ns-1 ip link set veth-2 netns net-ns-2 # 查看当前虚拟机网卡信息 ip link # 查看指定网络名称空间的网卡信息 ip netns exec net-ns-1 ip link ip netns exec net-ns-2 ip link
结果如图:
能够发现本机中已经不存在这两张网卡了,可是以前创建网络名称空间中能够看到他们。
这样的场景就相似于以前是一根网线的两头,咱们把它拔了下来,一头插到了第一个网络名称空间的网口上,一头插到了另外一个。
3. 给网口配置 IP 测试连通性:
# 配置 IP ip netns exec net-ns-1 ip addr add 192.168.1.1/24 dev veth-1 ip netns exec net-ns-2 ip addr add 192.168.1.2/24 dev veth-2 # 启动网卡 ip netns exec net-ns-1 ip link set dev veth-1 up ip netns exec net-ns-2 ip link set dev veth-2 up # 查看网卡信息 ip netns exec net-ns-1 ip a ip netns exec net-ns-2 ip a
结果以下:
此时在网络名称空间之中测试网络连通性:
ip netns exec net-ns-1 ping 192.168.1.2
结果如图:
能够发现网络名空间之中由于两个网卡至关于链接通的了效果,可是和宿主机自己倒是没法通讯的。
这样就实现了 docker 的明明本机却可以作到网络隔离的效果。固然 docker 并不是单单如此。
容器的网络原理
docker 的网络相较于上文的网络模式多了一个 docker0 网桥,咱们能够将其当作交换机:
并且因为 iptables 的缘由,容器内部还可以上网!
查看容器的网络:
docker network ls
结果如图:
熟悉 VMware 的人就应该很熟悉这些:
bridge:桥接网络,默认就是它,可以独立网段于宿主机自己网络,可是又能和宿主机所在的网络其它主机通讯。
host:主机网络,这意味着容器和宿主机将会共享端口,一样的端口会致使端口冲突。
none:彻底独立,没法通讯。
由于这三种网络的关系,意味着本机通属于一个 bridge 的容器是能够互相通讯的。能够本身创建一个桥接网络和两个容器测试:
docker network create -d bridge my-bridge
结果如图:
开两个窗口,分别新建容器并加入到这个网络:
# 窗口1 docker container run -it --name b3 --network my-bridge busybox /bin/sh # 窗口2 docker container run -it --name b4 --network my-bridge busybox /bin/sh
查看窗口1 IP:
查看窗口2 IP:
窗口1 测试通讯:
窗口1 测试和宿主机通讯:
能够发现可以和其它的宿主机通讯!可是没法和其它宿主机的容器通讯,很简单,由于他们是本身单独的 IP,不说其它,单单是他们两个就多是同样的 IP 地址。
同时,咱们新开窗口查看网卡状况:
能够发现后面三个网卡其实就是相似于一个网络链接,后面两个都至关于一个网线,一头会链接到 br-xxx 上面,一头链接到容器中,因此它们可以通讯。
这个 br-xxx 其实就是咱们建立网络的时候出现的。至于如何和其它宿主机通讯则是经过 iptables 的 nat 实现的。
查看网络详细信息:
docker network inspect my-bridge
结果以下:
能够看到哪些容器属于该网络!
容器网络名称解析
因为容器的 IP 是随机的,因此在不少服务使用的时候咱们不能再依赖容器的的 IP 来期望链接到指定的容器。
固然这并不是不行,而是太麻烦并且不便于管理。那有没有更好的办法?有。
那就是咱们在建立容器时会指定容器的名称,咱们看他经过解析这个名称实现通讯。
这里涉及到一个参数:--link,将一个容器链接到另一个容器。
1. 新建两个容器,而后分别重新窗口进入容器测试网络通讯:
# 建立容器 docker container run --name b1 -d busybox /bin/sh -c "while true;do sleep 3000;done" docker container run --name b2 -d busybox /bin/sh -c "while true;do sleep 3000;done" # 进入容器查看 IP 后测试网络 docker container exec -it b1 /bin/sh
结果如图:
能够发现 ping IP 可以通讯,可是 ping 容器名称不行。
2. 新建第三个容器,使用 link 绑定查看:
# 建立容器 docker container run --name b3 --link b1 -d busybox /bin/sh -c "while true;do sleep 3000;done" # 进入容器 docker container exec -it b3 /bin/sh
测试连通性如图:
能够看到在 b3 中可以经过容器名称 ping 通 b1,可是没法 ping 通 b2。
3. 进入 b1 中测试和 b3 的连通性:
docker container exec -it b1 /bin/sh
结果如图:
没法通,由此能够得出一个结论:--link 在绑定的时候是单向解析的。
这样就解决了咱们想要经过容器名称来实现服务之间的通讯而不是依靠随时均可能改变的 IP 地址。
容器端口映射
将容器内部咱们须要的某个端口映射到宿主机,这算是一种解决办法,由于宿主机的 IP 是固定的。
这里涉及到两个参数:
-p:将宿主机的某个端口映射到容器的某个端口。
-P:将容器的全部端口随机映射到宿主机的随机端口。
1. 为了便于理解,这里仍是以以前的 Flask 项目为例,不过这里咱们改一下代码,让其复杂一点。
mkdir flask-redis-demo cd flask-redis-demo/ vim app.py
内容以下:
from flask import Flask from redis import Redis import os import socket app = Flask(__name__) redis = Redis(host=os.environ.get('REDIS_HOST', "127.0.0.1"), port=6379) @app.route("/") def hello(): redis.incr('hits') return 'Redis hits: %s, Hostname: %s' % (redis.get('hits'), socket.gethostname()) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
其实就是 flask 经过用户传递过来的变量找到 redis 的地址链接上去,而后获取到 redis 的 hits 打印出来。
这就涉及到跨容器通讯!
2. 编写 Dockerfile:
FROM python:2.7 LABEL author="Dylan" mail="1214966109@qq.com" RUN pip install flask && pip install redis COPY app.py /app/ WORKDIR /app/ EXPOSE 5000 CMD ["python", "app.py"]
制做成为镜像:
docker image build -t dylan/flask-redis-demo .
3. 先运行一个 redis 容器:
docker container run -d --name redis-demo redis
结果如图:
4. 此时运行 flask 容器并链接到 redis-demo 容器:
docker container run -d --name flask-redis-demo --link redis-demo -e REDIS_HOST="redis-demo" dylan/flask-redis-demo
这里有个新增的参数:-e 传递变量给容器,结果如图:
此时咱们在容器中访问测试:
docker container exec -it flask-redis-demo curl 127.0.0.1:5000
输入以下:
Redis hits: 1, Hostname: 0a4f4560fa72
能够看到容器内部是正常的,而且 flask 容器能够去 redis 容器中获取值。可是问题仍是在于该地址只能容器内部访问。
5. 新建容器,将端口随机映射出来:
docker container run -d --name flask-redis-demo-1 -P --link redis-demo -e REDIS_HOST="redis-demo" dylan/flask-redis-demo
结果如图:
能够看到宿主机的 1025 端口映射到了容器内部的 5000 端口,此时咱们访问本机 1025 端口测试:
curl 192.168.200.101:1025
结果如图:
6. 新建容器,将容器端口映射到 8000 端口:
docker container run -d --name flask-redis-demo-2 --link redis-demo -e REDIS_HOST="redis-demo" -p 8000:5000 dylan/flask-redis-demo
结果如图:
访问测试:
至此,端口映射完成!
跨主机通讯
虽然端口映射可以解决咱们必定量的问题,可是在面对某些复杂的状况的时候也会很麻烦。好比跨主机通讯。
这就不是 link 可以解决的问题,须要将全部用到的服务的端口都映射到宿主机才能完成操做!
那有没有办法将多个主机的容器作成相似于一个集群的样子?这就是涉及容器的跨主机通讯问题。
这一次咱们会用到两个虚拟机,192.168.200.101 和 102,都安装好 docker。
在实现这个功能以前,咱们须要了解到两个东西:
overlay 网络:至关于在两个机器上面创建通讯隧道,让两个 docker 感受运行在一个机器上。
etcd:基于 zookeeper 的一个共享配置的 KV 存储系统。其中 etcd 地址以下:
https://github.com/etcd-io/etcd/tags
在使用以前须要注意:
1. 服务器的主机名最好具备必定的意义,最简单也要坐到惟一,一样的主机名可能出现 BUG。
2. 确认关闭防火墙这些东西。
3. 能有 epel 源最好。
yum -y install epel-release
我在安装的时候,epel 源中最新的 etcd 版本为:3.3.11-2
1. 在 101 上面安装 etcd 并配置:
yum -y install etcd cd /etc/etcd/ cp etcd.conf etcd.conf_bak vim etcd.conf
内容以下:
#[Member] ETCD_DATA_DIR="/var/lib/etcd/default.etcd" ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380" ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001" ETCD_NAME="docker-node-01" #[Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.200.101:2380" ETCD_ADVERTISE_CLIENT_URLS="http://192.168.200.101:2379,http://192.168.200.101:4001" ETCD_INITIAL_CLUSTER="docker-node-01=http://192.168.200.101:2380,docker-node-02=http://192.168.200.102:2380" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_INITIAL_CLUSTER_STATE="new"
注意红色的地方,名称保持一致。
同理在 102 上面也进行相似的配置:
#[Member] ETCD_DATA_DIR="/var/lib/etcd/default.etcd" ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380" ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001" ETCD_NAME="docker-node-02" #[Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.200.102:2380" ETCD_ADVERTISE_CLIENT_URLS="http://192.168.200.102:2379,http://192.168.200.102:4001" ETCD_INITIAL_CLUSTER="docker-node-01=http://192.168.200.101:2380,docker-node-02=http://192.168.200.102:2380" ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" ETCD_INITIAL_CLUSTER_STATE="new"
注意修改 IP 地址便可。
关键字 | 说明 |
---|---|
ETCD_DATA_DIR | 数据存储目录 |
ETCD_LISTEN_PEER_URLS | 与其余节点通讯时的监听地址列表,通讯协议能够是http、https |
ETCD_LISTEN_CLIENT_URLS | 与客户端通讯时的监听地址列表 |
ETCD_NAME | 节点名称,在也就是后面配置的那个 名字=地址 |
ETCD_INITIAL_ADVERTISE_PEER_URLS | 节点在整个集群中的通讯地址列表,能够理解为能与外部通讯的ip端口 |
ETCD_ADVERTISE_CLIENT_URLS | 告知集群中其余成员本身名下的客户端的地址列表 |
ETCD_INITIAL_CLUSTER | 集群内全部成员的地址,这就是为何称之为静态发现,由于全部成员的地址都必须配置 |
ETCD_INITIAL_CLUSTER_TOKEN | 初始化集群口令,用于标识不一样集群 |
ETCD_INITIAL_CLUSTER_STATE | 初始化集群状态,new表示新建 |
2. 启动服务:
systemctl start etcd systemctl enable etcd # 查看节点 etcdctl member list # 监控检查 etcdctl cluster-health
结果如图:
能够看到集群处于健康状态!
3. 此时修改 101 的 docker 启动配置:
# 中止 docker systemctl stop docker # 修改配置 vim /etc/systemd/system/docker.service
修改内容以下:
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --registry-mirror=https://jxus37ad.mirror.aliyuncs.com -H tcp://0.0.0.0:2375 --cluster-store=etcd://192.168.200.101:2379 --cluster-advertise=192.168.200.101:2375
主要增长了红色部分,而后启动 docker:
systemctl daemon-reload
systemctl start docker
同理 102 也进行修改,注意 IP 地址:
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --registry-mirror=https://jxus37ad.mirror.aliyuncs.com -H tcp://0.0.0.0:2375 --cluster-store=etcd://192.168.200.102:2379 --cluster-advertise=192.168.200.102:2375
一样重启 docker,此时的 docker 进程发生了改变:
ps -ef | grep docker
结果以下:
4. 此时在 101 上建立 overlay 网络:
docker network create -d overlay my-overlay
结果以下:
此时查看 102 网络:
已经同步过来了,若是没有同步成功,能够从新关闭 102 的防火墙,重启 102 的 docker 再看。
5. 此时在 101 和 102 上面分别建立容器而后测试网络的连通性:
# 101 上面建立 docker container run -d --name b1 --network my-overlay busybox /bin/sh -c "while true;do sleep 3000;done" # 102 上面建立 docker container run -d --name b2 --network my-overlay busybox /bin/sh -c "while true;do sleep 3000;done"
注意这里新的参数:--network,指定容器的网络,再也不是默认的 bridge 网络。
docker container exec -it b1 ping b2
直接在 b1 上面 ping b2:
发现已经可以实现正常的通讯了。并且再也不须要咱们指定 link 参数了。
固然 etcd 也有它的缺点,它是静态发现,意味着咱们增长节点就须要增长配置和重启 etcd。