Docker容器網絡詳解
從范圍上分:
單機網絡:none,host, bridge
跨主機網絡:overlay,macvlan,flannel等
從生成方式分:
原生網絡:none,host, bridge
自定義網絡:
使用docker原生實現的驅動自定義的網絡:bridge(自定義),overlay,macvlan,
使用第三方驅動實現的自定義網絡:flannel等
在學習網絡的時候肯定遇到過關於CNM這個概念,所以首先,我們一起學習下CNM&libnetwork
CNM&libnetwork
libnetwork是Docker團隊將Docker的網絡功能從Docker的核心代碼中分離出來形成的一個單獨的庫,libnetwork通過插件的形式為Docker提供網絡功能。基於代碼層面再升華一下,可以將docker的網絡抽象出一個模型來,就叫CNM(Container Networking Model),該模型包含三大塊:
- Sandbox:容器的網絡棧,包含interface,路由表,DNS設置等,可以看做就是linux network類型的namespace本身,該有的網絡方面的東西都要有,另外還包含一些用於連接各種網絡的endpoint
- Endpoint : 用來將sandbox接入到network中。典型的實現是Veth pair技術(Veth pair是Linux固有的,是一個成對的接口,用來做連接用)
- Network : 具體的網絡實現,比如是brige,VLAN等,同樣它包含了很多endpoint(那一頭)
一句話:sandbox代表容器,network代表容器外的網絡驅動形成的網絡,endpoint連接了二者
另外,CMN還提供了2個可插拔的接口,讓用戶可以自己實現驅動然后接入該接口,支持驅動有兩類:網絡驅動和IPAM驅動,看看這倆類驅動干什么的?
-
Network Drivers: 即真正的網絡實現,可以為Docker Engine或其他類型的集群網絡同時提供多種驅動,但是每一個具體的網絡只能實例化一個網絡驅動。細分為本地網絡驅動和遠端網絡驅動:
- 本地網絡驅動:對應前面說到的原生網絡
- 遠端網絡驅動:對應前面說的自定義網絡
-
IPAM Drivers — 構建docker網絡的時候,每個docker容器如果不手動指定的話是會被分配ip地址的,這個分配的任務就是由該驅動完成的,同樣的,Docker Engine還是給我們提供了缺省的實現。
整個的原理模型圖如下,參見官網:
參考:https://success.docker.com/article/networking
(一定要好好看看這篇文章,我英文不行看了整整2天,很有收獲)
好了,收,開始真正進入docker網絡的學習,我們挑2個代表性的網絡一起研究下
單機網絡---brige類型的網絡
原理如下圖(摘自https://success.docker.com/article/networking):
接下來聽我慢慢道來,我們先按照步驟走一遍,然后再細摳里面的原理
【實操】:在主機上起兩個docker容器,使用缺省網絡即bridge網絡,容器要使用有操作系統的鏡像,要不不方便驗證
1)進入任一個容器內
sh-4.2# ip addr
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
inet 172.17.0.3/16 scope global eth0
附:容器中可能缺少諸多命令,可以在啟動后安裝如下工具:
yum install net-tools
yum install iputils
yum install iproute *
2)在宿主機上查看接口信息:
[root@centos network-scripts]# ip addr 4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default inet 172.17.0.1/16 scope global docker0 14: vetha470484@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 16: veth25dfcae@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
3) 在host上查看docker缺省會創建的三個網絡
[root@centos ~]# docker network ls NETWORK ID NAME DRIVER SCOPE 451a2ff68c71 bridge bridge local 7bd661f0c17f host host local 6c9bb2d42d95 none null local
4)再看下支撐網絡背后的驅動,即這個叫“bridge”的bridge類型網絡使用的驅動:一個名叫docker0的bridge(網橋)。網橋上掛兩個interface:veth25dfcae, vetha470484
[root@centos network-scripts]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242dee689ea no veth25dfcae vetha470484
【解析】:
看容器(Sandbox), 接口的number是13的那個,他名字是eth0, 然后他@if14,這個就是endpoint,那么這個if14是誰?
看主機,有個網橋叫做docker0,有兩個interface 他們的master是docker0,並且這兩個interface的number分別是14,16,並且分別@if13和@if15,是的,if13正是容器中的接口,同理if14也是另一個容器中的接口,也就是說在host上的veth接口(NO.14)和容器中的eth接口(NO.13)正是一對veth pair,至此Endpoint作為容器和nework的連接的任務達成了。而docker0正是名叫bridge的Network的驅動。
最后,看一下路由吧
容器1:
sh-4.2# ip route default via 172.17.0.1 dev eth0 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3
表示:目的是172.17的流量交給eth0出去,然后交給網關172.17.0.1,也就是docker0
宿主機:
[root@centos ~]# ip route default via 192.168.12.2 dev ens33 proto dhcp metric 100 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 192.168.12.0/24 dev ens33 proto kernel scope link src 192.168.12.132 metric 100
表示:目的是172.17的流量從docker0出去,
缺省的交給ens33接口給網關192.168.12.2(因為我的宿主機是個虛擬機,所以還是個小網ip),也就是說如果訪問的是同網段(如加入同一網絡的其他容器)則交給網橋docker0內部轉發,否則走向世界
另外:詳細的可以 看一下bridge網絡,可以網絡中有兩個容器,ip,mac都有

插播:bridge的原理
在容器技術中,bridge扮演了一個非常重要的角色,懂得bridge的原理可以很好的定位網絡問題,這里就不展開討論,在我的[爬坑]系列有講述,你只需要記住:不要把bridge想復雜,bridge是一個橋作為master,可以往橋上掛很多類型及個數的interface接口,當橋上有一個接口接收到數據后,只要不是給橋所在的宿主機本身,則橋會內部轉發,數據會從其余接口同步冒出來
【驗證】:
1,容器連接外網--OK
sh-4.2# ping www.baidu.com PING www.a.shifen.com (115.239.210.27) 56(84) bytes of data. 64 bytes from 115.239.210.27 (115.239.210.27): icmp_seq=1 ttl=127 time=5.38 ms
注:這里面要說明一下,從容器發外網的egress流量之所以能順利得到應答,是因為它在出去的時候經過了iptables的NAT表,就是這里:
# iptables -t nat -L ... Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 anywhere MASQUERADE all -- 172.18.0.0/16 anywhere
這里如果對iptables的原理不是很清楚的,推薦這位大牛的博客:
http://www.zsythink.net/archives/1199/
2,容器連接另一個容器--OK
sh-4.2# ping 172.17.0.2 -c 2 PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data. 64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=95.9 ms
3,容器如果想要被外網可以訪問的到,需要在起容器的時候指定--publish/-p,即在host上找一個port與容器映射,這個host將代表容器對外打交道,這里就不展開了,網上資料很多,上面貼出的鏈接中也有講解。
單機網絡---overlay類型的網絡
上一個章節我們沒有細說容器和linux的namespace之間的關系,因為在overlay類型的網絡中,大量使用了linux的namespace技術,所以我們再展開講。
overlay這個詞不是docker家的,一直以來就有overlay類型的網絡,意/譯為"覆蓋"型網絡,其中VxLan技術就可以認為是一種overlay類型網絡的實現,而在docker的overlay網絡,使用的正是Vxlan技術。所以我們再跑偏一下,講講VxLAN
插播3:VxLAN
Virtual Extensible LAN,虛擬化可擴展局域網,基於IP網絡,采用 MAC in UDP封裝形式的二層VPN技術,一個簡單拓撲和報文結構圖來感性認識下
幾個關鍵名詞:
VNI:VXLAN Network Identifier,即虛擬出來的網絡id
VTEP:VxLAN隧道的EndPoint,代表隧道的始端或者終端實體。負責對VxLAN報文進行封裝/解封裝,包括ARP請求報文和數據報文。VTEP可由硬件設備或軟件去實現。
結合報文格式解讀一下:學過TCP/IP的我們都知道,協議棧可以分成5層,從最上層的應用出來的數據會經過協議棧一層層封裝:應用層--->傳輸層TCP/UDP頭--->三層(網絡層)IP頭--->二層(鏈路層)MAC頭-->物理層,從物理網卡出去。
VxLAN是其在封裝到二層的時候,由VTEP進行再一次的封裝叫做VxLAN封裝,即又增加了UDP頭->IP頭->MAC頭,所以叫"MAC in UDP"。這就非常適用於容器的應用場景,一方面滿足了網絡隔離的要求(比如多租戶等),另一方面VNI是個24-bit的值,即支持2^24個vxlan網絡,遠高於vlan。這樣的好處是用一張圖就能看出來:
水鬼子:可以暫時這么理解,VTEP就是宿主機上的一個組件。它的ip就是宿主機的ip,負責和其他宿主機連通形成隧道。這樣宿主機上的VM就可以利用該隧道和其他host上的VM溝通了,然后還並不感知該隧道的存在。也許這么說並不嚴謹,但是有助於對overlay網絡的理解。
另外,為了學習vxlan網絡以及容器對vxlan網絡的應用,我做了大量的實驗,遇到了很多很多的坑,需要的可以參考:.[爬坑系列]之VXLan網絡實現
oK,再“收”,回到docker 網絡
前情提要:
我們也可以直接使用docker 提供的overlay驅動創建overlay網絡,然后創建容器加入到該網絡,但如果是Docker Engine 1.12之前,還需要一個k-v類型的存儲介質。Docker Engine 1.12之后的由於集成了一個叫做“網絡控制平面(control plane)”的功能,則不需要額外的存儲介質了。在這里為了更好的理解,我們選擇前者,大致的步驟如下:
【操作】:
1,安裝啟動etcd,安裝方法
2,在一個host上創建overlay類型的網絡,並創建容器使用該網絡
3,在另一個容器上也可以看到該網絡,然后也加入這個網絡
root@master ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
731d1b63b387 ov_net2 overlay global
分別在兩個節點上創建兩個docker 容器
master:
docker run -ti -d --network=ov_net2 --name=centos21 centos:wxy /bin/sh
minion:
docker run -d --name nginx --network=ov_net2 nginx22
注:更詳細的步驟網上很多,另外想要看我自己搭建的過程中遇到的坑等,請參見【爬坑系列】之docker的overlay網絡配置
這里我只想展示overlay網絡的深層實現,包括和namespace,vxlan的關系等
【解析】:
1)看一下namespace以及配置
ln -s /var/run/docker/netns/ /var/run/netns
//一個容器創建完了,就多了兩個namespace
[root@master ~]# ip netns
a740da7c2043 (id: 9)
1-731d1b63b3 (id: 8)
第一個namespace:其實就是docker容器本身,它有兩個接口,都是veth pair類型,對應的兄弟接口分別位於另一個namespace和系統namespace
root@master ~]# ip netns exec a740da7c2043 ip addr 44: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default link/ether 02:42:0a:00:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.0.1.2/24 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:aff:fe00:102/64 scope link valid_lft forever preferred_lft forever 46: eth1@if47: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet 172.18.0.4/16 scope global eth1 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe12:4/64 scope link valid_lft forever preferred_lft forever
第二個namespace:他的作用是專門用來做vxlan轉發的,核心是一個bro橋,橋上有兩個接口,一個與容器(第一個ns )相連,另一個vxlan1接口充當 vxlan網絡的vtep
[root@master ~]# ip netns exec 1-731d1b63b3 ip addr 2: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default link/ether 12:54:57:62:92:74 brd ff:ff:ff:ff:ff:ff inet 10.0.1.1/24 scope global br0 valid_lft forever preferred_lft forever inet6 fe80::842a:a1ff:fec8:da3b/64 scope link valid_lft forever preferred_lft forever 43: vxlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UNKNOWN group default link/ether 12:54:57:62:92:74 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::1054:57ff:fe62:9274/64 scope link valid_lft forever preferred_lft forever 45: veth2@if44: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master br0 state UP group default link/ether ae:a1:58:8c:c2:0a brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::aca1:58ff:fe8c:c20a/64 scope link valid_lft forever preferred_lft forever
結論:docker的overlay網絡 = 2namespace + vxlan
kubernetes之pod網絡詳解
一,kubernetes網絡概述
我們說的k8s網絡就是集群網絡,因為k8s是用於集群的,單機網絡沒什么意義。一個集群的網絡,主要涉及一下4個方面的通信
1,pod中的容器之間的通信:
這個在前面的章節我們已經說了,pod中容器都是共享網絡空間的,所以就如一台機器上的應用一般,使用localhost:port就可以通信,也當然需要容器們自己保證大家別端口沖突了。
2,pod-to-pod的通信
首先k8s給每一個pod都分配ip了,具體他們之間怎么通信取決於具體的網絡實施,這個不是k8s做的,但是k8s做了要求,也叫網絡模型,具體要求如下:
1)一個節點上的pod可以和所有節點上的所有pod通信,且不需要NAT
2)節點上的agent(比如系統daemons:kubelet)可以和該節點上的所有pod通信
3)如果pod是運行與host的網絡中,則要求pod同樣可以和節點上的其他pod通信
3,pod和service的通信,這個詳見service的定義
4,外網和serice的通信,同樣相近service的定義
注:3,4的實現就是service的實現,建議仔仔細細研究官方文檔就可以搞清楚了,有時間我會將這部分也整理下分享出來。那部分總結一句話就是:iptables的運用
另外,在進行k8s網絡驗證之前,需要進行k8s本地環境安裝,為了更好的研究我走了一條非常復雜的安裝之路:本地編譯源碼生成二進制,在打包鏡像,最后通過三種途徑安裝了master節點和node節點:yum自動安裝,二進制安裝,鏡像安裝,想起來都是淚,想參考的見【爬坑系列】之kubernetes環境搭建:二進制安裝與鏡像安裝
二,詳解pod內的通信
1,一個例子模擬pod的通信
模擬k8的行為,首先創建pause容器,然后創建centos容器和nginx容器都加入到pause容器的網絡中,然后在centos容器中訪問localhost:80,於是訪問到了nginx容器。具體如下:
docker run -d --name pause docker.io/ist0ne/pause-amd64
docker run -d --name nginx --net=container:pause --ipc=container:pause --pid=container:pause nginx
docker run -d -ti --name centos --net=container:pause --ipc=container:pause --pid=container:pause centos /bin/sh
sh-4.2# curl localhost:80 ---訪問nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
2,實際的k8s的:
創建一個有倆容器的pod,一個是centos操作系統,一個是nginx,在centos中訪問localhost:80可以訪問到nginx服務
//創建pod的yaml文件,該pod中有兩個容器
# vi centos-nginx.yaml apiVersion: v1 kind: Pod metadata: name: myapp-2c labels: app: centos spec: containers: - name: centos-container image: centos:wxy command: ["/bin/sh"] args: ["-c", "while true; do echo hello; sleep 10; done"] - name: nginx-container image: nginx
//登錄pod默認登錄到centos-container這個容器,剛好
# kubectl exec -ti myapp-2c /bin/sh Defaulting container name to centos-container. Use 'kubectl describe pod/myapp-2c -n default' to see all of the containers in this pod. sh-4.2# curl localhost:80 <!DOCTYPE html> ... <h1>Welcome to nginx!</h1>
//看看容器的情況:一個pause容器領着領養倆業務容器
# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES edf722104aa6 nginx "nginx -g 'daemon ..." 27 minutes ago Up 27 minutes k8s_nginx-container.570a1191_myapp-2c_default_0461d9cd-8370-11e9-a770-5254006bdf04_702066eb 6e5e2c96bd90 centos:wxy "/bin/sh -c 'while..." 27 minutes ago Up 27 minutes k8s_centos-container.e8c7257b_myapp-2c_default_0461d9cd-8370-11e9-a770-5254006bdf04_d950aeda 044e4c09f0e9 docker.io/ist0ne/pause-amd64 "/pause" 27 minutes ago Up 27 minutes k8s_POD.68b7dd1e_myapp-2c_default_0461d9cd-8370-11e9-a770-5254006bdf04_00db96a2
二:詳解pod之間的通信
這個是k8s集群網絡的核心,前面說了,取決於具體的網絡實施。
水鬼子:如果你剛接觸k8s,不知道你是否會對這里感到迷惑,第三方實現是什么鬼?pod之間的通信不是k8s自己做的么?難道要額外安裝啥軟件?
是的需要額外安裝,在這里我們就以etcd + flunnel為例來講解。
概述:
集群中的各個node,首先一定是連通的,然后基於底層網絡之上的flannel其實就是一個overlay網絡。說到overlay,如果你看了前面Docker網絡的章節,一定大概知道是怎么回事了。flannel網絡有多種實現方式,目前支持的有:udp(默認),host-gw,VxLAN(是不是很熟悉?)。實際上pod之間的網絡要比docker的還簡單。
原理詳解:
1,為什么需要etcd
etcd是一個key-value形式的存儲系統,可以往里寫各種類型的數據。我們需要做的就是首先約定好一個key,以這個key向etcd中寫入網絡配置參數;然后在安裝flannel的時候配置FLANNEL_ETCD_PREFIX參數為此key。
k8s集群中的每個節點上都有安裝flanneld作為網絡代理,它通過讀取etcd知道當前的網絡配置信息,為pod分配ip后,再將當前的網絡狀況比如本節點分得的subnet和對應的public ip等信息再寫入etcd中。這種網絡管理模式就是所謂的network planes,用我的話說,大家也沒什么上下級關系,都是處在同一平級,所以叫"網絡平面"。
2,網絡拓撲和轉發原理
所謂flannel的安裝,最主要是在每個節點上安裝flanneld,這個"代理"一樣的角色會做什么呢?
1)首先去etcd那里讀取配置。根據配置為自己分配一個subnet,並將其與public ip(在安裝flannel時指定的ip,如果沒有指定則缺省是host上的eth0的地址)的對應關系再存入etcd中,這樣其他節點上的flannel也就共享這些信息了。
2)創建虛擬網卡flannel0並分配一個該子網段ip,還會添加一條路由:所有到其他pod的數據都從flanel0出去;
3))將網段信息寫入/run/flannel/subnet.env中,這是一個用於刷新Docker環境變量相關的的文件:/run/flannel/docker。所以這時候重啟一下Docker,docker0就有了一個該子網段的ip。
[坑]這里要注意,我不知道別的版本,反正我目前安裝的版本,如果flannel重啟並重新給自己分配了subnet,那么此時一定記得重啟Docker,否則docker0的ip和flannel0的ip不一致,進而導致數據無法正確轉發。
最后,數據如何轉發的呢?數據從容器出來,經過veth pair到達網橋docker0,根據路由再轉發給flannel0,進而數據由flannel的backend(udp或者host-gw或者vxlan)轉發給目的pod所在的backend上,最后由目的host上的backend上送至目的pod的容器中。具體如下:
//etcd中存放着flannel網絡配置信息:地址空間,缺省backend類型
#etcdctl get /k8s/network/config
{"Network": "10.0.0.0/16"}
//flannel啟動后,它會偷偷改變Docker的環境變量
#vi /usr/lib/systemd/system/flanneld.service
...
ExecStartPost=/usr/libexec/flannel/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
...
//於是docker在啟動的時候就有了約束,其中bip決定了網橋docker0的IP
# systemctl status docker.service
...
/usr/bin/dockerd-current --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current ... --bip=10.0.42.1/24 --ip-masq=true --mtu=1472
...
//集群中兩個節點,分別給自己分配了子網,也都存在了etcd中,包括到達子網絡的public ip
# etcdctl ls /k8s/network/subnets /k8s/network/subnets/10.0.58.0-24 /k8s/network/subnets/10.0.42.0-24 # etcdctl get /k8s/network/subnets/10.0.42.0-24 {"PublicIP":"188.x.x.113"}
//veth pair把數據容器里導出來到達網橋docker0,再經過路由表到達flannel0,最后交給backend
# ip addr
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue state UP group default
link/ether 02:42:ad:d6:96:b7 brd ff:ff:ff:ff:ff:ff
inet 10.0.42.1/24 scope global docker0
valid_lft forever preferred_lft forever
...
36: flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1472 qdisc pfifo_fast state UNKNOWN group default qlen 500
link/none
inet 10.0.42.0/16 scope global flannel0
valid_lft forever preferred_lft forever
40: veth9b5a76f@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue master docker0 state UP group default
link/ether da:e4:3e:d3:cb:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 3
# ip route
default via 172.21.0.1 dev eth0
10.0.0.0/16 dev flannel0 proto kernel scope link src 10.0.42.0
10.0.42.0/24 dev docker0 proto kernel scope link src 10.0.42.1
3,詳解flannel的三種backend
參考博客:
http://www.360doc.com/content/16/0629/16/17572791_571683078.shtml
我覺得他講的很明了了
實操
1,創建l兩個pod,為了讓他們能夠分別調度到兩個節點上,我還為node打了標簽,並為pod配置了nodeSelector,例如
# cat centos.yaml apiVersion: v1 kind: Pod metadata: name: myapp-centos labels: app: centos spec: containers: - name: nginx-container image: centos:wxy command: ["/bin/sh"] args: ["-c", "while true; do echo hello; sleep 10; done"] nodeSelector: node: master
[root@master ~]# kubectl get pods NAME READY STATUS RESTARTS AGE myapp-centos 1/1 Running 0 16h myapp-nginx 1/1 Running 0 83s
2,驗證連通性:在一個pod里訪問另一個pod
1)看一下nginx的ip
# kubectl get pods myapp-nginx -oyaml |grep podIP
podIP: 10.0.58.3
2)在centos中訪問一下nginx
[root@master ~]# kubectl exec -ti myapp-centos /bin/sh
sh-4.2# curl http://10.0.58.3:80
...
<h1>Welcome to nginx!</h1>