容器化之路Docker網路核心知識小結,理清楚了嗎?

  Docker網路是容器化中最難理解的一點也是整個容器化中最容易出問題又難以排查的地方,加上使用Kubernets後大部分人即使是專業運維如果沒有紮實的網路知識也很難定位容器網路問題,因此這裡就容器網路單獨拿出來理一理。

  先了解一下Docker的一點基礎架構知識,Docker 技術架構圖:

 

  Docker是不能直接在 Windows 平台上運行的,只支援 linux 系統,因為Docker 依賴 linux kernel 三項最基本的技術。

  1. Namespaces 充當隔離的第一級,是對 Docker 容器進行隔離,讓容器擁有獨立的 hostname,ip,pid,同時確保一個容器中運行一個進程而且不能看到或影響容器外的其它進程 。
  2. Cgroups 是容器對使用的宿主機資源進行核算並限制的關鍵功能,比如 CPU, 記憶體, 磁碟等。
  3. Union FS 主要是對鏡像也就是 image 這一塊作支援,採用 copy-on-write 技術,讓大家可以共用某一層,對於某些差異層的話就可以在差異的記憶體存儲,
  4. Libcontainer 是一個庫,是對上面這三項技術做一個封裝。
  5. Docker engine 用來控制容器 container 的運行,以及鏡像文件的拉取。

  Docker的工作原理:每個容器都在自己的命名空間中運行,但使用與所有其他容器完全相同的內核。發生隔離是因為內核知道分配給進程的命名空間,並且在API調用期間確保進程只能訪問其自己的命名空間中的資源。

Docker部署關鍵配置

daemon.json文件

指定私有倉庫地址insecure-registries,否則拉取鏡像出現問題:

 1 {
 2   "data-root": "/docker/data",
 3   "exec-opts": ["native.cgroupdriver=cgroupfs"],
 4   "registry-mirrors": [
 5     "//docker.mirrors.ustc.edu.cn",
 6     "//hub-mirror.c.163.com"
 7   ], 
 8   "hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"],
 9   "insecure-registries": ["192.168.0.23:5000"],
10   "max-concurrent-downloads": 10,
11   "live-restore": false,
12   "log-driver": "json-file",
13   "log-level": "warn",
14   "log-opts": {
15     "max-size": "50m",
16     "max-file": "1"
17     },
18   "storage-driver": "overlay2"
19 }

1.指定data-root 配置容器數據地址,在伺服器中單獨規劃磁碟空間,避免佔用系統空間

2.指定hosts,放開2375對外介面

3.Docker使用storage driver(存儲驅動程式)來管理image和container的數據,要使用overlayfs,要確保系統的內核版本大於等於3.18,overlay要比aufs和device mapper快一點,OverlayFS僅有兩層,鏡像中的每一層對應/var/lib/docker/overlay中的一個文件夾,文件夾以該層的UUID命名。然後使用硬連接將下面層的文件引用到上層。這在一定程度上節省了磁碟空間。

4.指定文件驅動native.cgroupdriver=cgroupfs控制的資源主要包括CPU、記憶體、block I/O、網路頻寬等,也可以指定為systemd,這裡要注意的是後續布署k8s時要與k8s設置的文件驅動操持一致,否時會報錯:

failed to create kubelet: misconfiguration: kubelet cgroup driver: “cgroupfs” is different from docker cgroup driver: “systemd”

需要修改kubelet.service Environment中添加–cgroup-driver=cgroupfs或systemd

docker.service文件

[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io

[Service]
Environment="PATH=/docker/bin:/bin:/sbin:/usr/bin:/usr/sbin"
ExecStart=/docker/bin/dockerd
ExecStartPost=/sbin/iptables -I FORWARD -s 0.0.0.0/0 -j ACCEPT
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=5
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target

  docker 從 1.13 版本開始,將`iptables` 的`filter` 表的`FORWARD` 鏈的默認策略設置為`DROP`,從而導致 ping 其它 Node 上的 Pod IP 失敗,因此必須在 `filter` 表的`FORWARD` 鏈增加一條默認允許規則 `iptables -I FORWARD -s 0.0.0.0/0 -j ACCEPT`

  通過了解以上docker基礎框架後排查網路問題思路會更清晰。

Docker容器的網路模型

  Docker容器網路的原始模型主要有三種:Bridge(橋接)、Host(主機)及Container(容器)

  Docker默認使用Bridge+NAT的通訊模型,Bridge模型藉助於虛擬網橋設備為容器建立網路連接,Docker守護進程首次啟動時,它會在當前節點上創建一個名為docker0的橋設備,並默認配置其使用172.17.0.0/16網路,此主機上啟動的Docker容器會連接到這個虛擬網橋上。

  當容器啟動時在主機上創建一對虛擬網卡veth pair設備,Docker將veth pair設備的一端放在新創建的容器中,並命名為eth0(容器的網卡),另一端放在主機中,以vethxxx這樣類似的名字命名,並將這個網路設備加入到docker0網橋中,從docker0子網中分配一個IP給容器使用,並設置docker0的IP地址為容器的默認網關,這樣同一個host的容器之間就可以通過docker0通訊了,可以通過brctl show命令查看。

 

 

容器與外部網路間的通訊

  為了解決容器訪問外部網路,docker引入NAT,通過iptables規則控制,網橋 docker0 通過 iptables 中的配置與宿主機器上的網卡相連,所有符合條件的請求都會通過 iptables 轉發到 docker0 並由網橋分發給對應的機器。創建MASQUERADE規則:

查看nat表 

# iptables -t nat -S

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

數據包流程

   這條規則將所有從容器發出的、目的地址為Host外部網路的包的IP都修改成Host的IP,並由Host發送出去。

外部網路訪問容器

  Docker容器是通過dnat映射或docker-proxy服務對外提供訪問,如指定埠映射:docker run -p 9001:9000。

  使用docker run -p時,docker實際是在iptables做了DNAT規則,實現埠轉發功能,為容器分配一個 IP 地址,同時向 iptables 中追加一條新的規則。

  可以使用iptables -t nat -vnL查看。

-A PREROUTING -m addrtype –dst-type LOCAL -j DOCKER

-A DOCKER ! -i docker0 -p tcp -m tcp –dport 9001 -j DNAT –to-destination 172.17.0.2:9000

  外部訪問外部伺服器訪問10.3.20.87:9001

匹配到DNAT規則,訪問到容器

-A PREROUTING -m addrtype –dst-type LOCAL -j DOCKER

-A DOCKER ! -i docker0 -p tcp -m tcp –dport 9001 -j DNAT –to-destination 172.17.0.2:9000

  本機訪問127.0.0.1:9001 沒有匹配到任何iptable,走docker-proxy

  另外容器要訪問外部網路需要宿主機進行轉發,要在宿主機中打開轉發設置:

#sysctl net.ipv4.ip_forward

net.ipv4.ip_forward = 1

為0說明沒有開啟,需要手動打開。

  
  安裝Docker時,它會自動創建三個網路。
#docker network ls
NETWORK ID          NAME                DRIVER
7fca4eb8c647        bridge              bridge
9f904ee27bf5        none                null
cf03ee007fb4        host                host
  bridge網路代表docker0所有Docker安裝中存在的網路,默認將容器連接到此網路。可以使用該–network標誌來指定容器應連接到哪些網路。
  使用host模式,這個容器將不會獲得一個獨立的Network Namespace,而是和宿主機共用一個Network Namespace。容器將不會虛擬出自己的網卡,配置自己的IP等,而是使用宿主機的IP和埠。但是,容器的其他方面,如文件系統、進程列表等還是和宿主機隔離的。
 
  創建自定義網橋時可指定網段圈定容器ip範圍避免衝突,如:
docker network create -d macvlan \
–subnet=172.16.86.0/24 \
–gateway=172.16.86.1  \
-o parent=eth0 pub_net
 

Docker跨主機容器間網路通訊

  Docker默認的網路環境下,單台主機上的Docker容器可以通過docker0網橋直接通訊,而不同主機上的Docker容器之間只能通過在主機上做埠映射進行通訊,

  如果能讓Docker容器之間直接使用自己的IP地址進行通訊,會解決很多問題。按實現原理可分別直接路由方式、橋接方式(如pipework)、Overlay隧道方式(如flannel、ovs+gre)等。
 
  一般選用Overlay隧道方式,核心是通過Linux網橋與vxlan隧道實現跨主機劃分子網。
  K8S網路模型CNI插件主流使用Flannel ,功能是讓集群中的不同節點主機重新規劃IP地址的使用規則,使得不同節點上的容器能夠獲得”同屬一個內網”且”不重複的”IP地址,並讓屬於不同節點上的容器能夠直接通過內網IP通訊。flannel提供hostgw實現,避免vxlan實現的udp封裝開銷,估計是目前最高效的;
 
  Flannel實質上是運行在一個網上的網(應用層網路),並不依靠ip地址來傳遞消息,而是採用一種映射機制,把ip地址和identifiers做映射來資源定位。也就是將TCP數據包裝在另一種網路包裡面進行路由轉發和通訊,目前已經支援UDP、VxLAN、AWS VPC和GCE路由等數據轉發方式。
   
  原理是每個主機配置一個ip段和子網個數。例如,可以配置一個覆蓋網路使用 10.100.0.0/16段,每個主機/24個子網。因此主機a可以接受10.100.5.0/24,主機B可以接受10.100.18.0/24的包。
 
  Flannel使用etcd來維護分配的子網到實際的ip地址之間的映射。對於數據路徑,flannel 使用udp來封裝ip數據報,轉發到遠程主機。選擇UDP作為轉發協議是因為他能穿透防火牆。例如,AWS Classic無法轉發IPoIP or GRE 網路包,是因為它的安全組僅僅支援TCP/UDP/ICMP。
   
  Flannel使用etcd存儲配置數據和子網分配資訊。Flannel啟動之後,後台進程首先檢索配置和正在使用的子網列表,然後選擇一個可用的子網,然後嘗試去註冊它。
 
  etcd也存儲這個每個主機對應的ip。Flannel使用etcd的watch機制監視/coreos.com/network/subnets下面所有元素的變化資訊,並且根據它來維護一個路由表。為了提高性能,Flannel優化了Universal TAP/TUN設備,對TUN和UDP之間的ip分片做了代理。
 
  默認的節點間數據通訊方式是UDP轉發.在Flannel的GitHub頁面有如下的一張原理圖:

   具體的介紹可參考 之前的文章 Kubernetes集群部署關鍵知識總結  地址 //www.cnblogs.com/zhangs1986/p/10749721.html

  注意:flannel 使用 vxlan 技術為各節點創建一個可以互通的 Pod 網路,使用的埠為 UDP 8472,需要開放該埠。

本文所用到Docker版本為19.03.15

 

Kubernetes 1.20 版本開始將棄用 Docker

kubelet目前推薦方式是直連containerd。

 

  被去掉的部分是刪除 dockershim(Dockershim 作用:把外部收到的請求轉化成 Docker Daemon 能聽懂的請求,讓 Docker Daemon 執行創建、刪除等容器操作。)

   可以用Containerd 或 Podman 替換。