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

歡醉發表於2021-10-18

  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     "https://docker.mirrors.ustc.edu.cn",
 6     "http://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叢集部署關鍵知識總結  地址 https://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 替換。

相關文章