Docker Network 及其他組件介紹

本文基於 docker 版本:

Client: Docker Engine - Community Version: 19.03.8

Server: Docker Engine - Community Version: 19.03.8


Cgroups & Namespace

在介紹 Docker 之前,我打算先說一下 linux 的 cgroups 和 namespace

Cgroups 是 Control Groups 的簡稱, 是 linux 內核提供的一個功能。它允許將進程分組,然後可以限制和監控各種資源的使用情況。

<code>➜  ~ cat /proc/cgroups
#subsys_name    hierarchy       num_cgroups     enabled
cpuset  6       2       1
cpu     7       60      1
cpuacct 7       60      1
blkio   4       60      1
memory  10      82      1
devices 5       60      1
freezer 9       2       1
net_cls 12      2       1
perf_event      8       2       1
net_prio        12      2       1
hugetlb 11      2       1
pids    2       68      1
rdma    3       1       1/<code>

通過查看 cgroups 文件,我們可以看到 cgroups 可以管控的系統資源,包括 cpu、memory、io、網絡等等。

也就是說,通過 cgroups,我們可以對一組進程進行內核資源管控。

Namespace 能夠對全局的系統資源,如 pid ,網絡,用戶,主機名等,進行抽象隔離,這使得其中的進程看起來,就好像是有自己的全局資源一樣,也意味著不同 namespace 中的進程,可以擁有一樣的 pid ,端口。一個 namespace 中的進程,其所有的 “全局資源” 是對同 namespace 中的其他進程可見的,但對非 namespace 中的進程不可見。

我們可以查看 /proc/[pid]/ns 目錄:

<code>➜  ~ sudo ls -lh /proc/16310/ns
[sudo] password for dylan:
total 0
lrwxrwxrwx 1 root root 0 Apr 18 02:30 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Apr 18 02:30 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Apr 18 02:30 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Apr 18 02:30 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Apr 18 02:30 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Apr 18 02:30 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Apr 18 02:30 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 18 02:30 uts -> 'uts:[4026531838]'/<code>

同樣能看到,ipc, mnt, net, pid 等公共資源的抽象。

總結來說, namespace 讓你能用什麼, cgroups 讓你能用多少。

Docker 架構

Docker Network 及其他組件介紹

這是一張 Docker 引擎的功能描述圖。通過這張圖,我們能看到 Docker 的基本結構劃分以及包含的功能:

  • cli : docker 提供的命令行交互工具,通過 cli 提供的命令,可以管理網絡(network)、容器(container)、鏡像(image)、數據卷(data volume)
  • rest api : 提供 HTTP API 接口給 cli
  • server :我們常說的 docker daemon 守護進程,這是一個長期運行的進程,會處理來自 cli 的請求
Docker Network 及其他組件介紹

這張 Docker 的基礎架構圖,可以很好的告訴我們它的運行機制:

  1. client 端通過 cli 執行 docker build/pull/run... 等命令,發送給 dockerd(docker daemon , 後簡稱為 dockerd) 。 cli 和 dockerd 之間可以通過 rest api 連接。 支持的連接協議有: linux 中分別為 tcp, unix, fd, windows 中為 tcp, npipe (源碼: daemon/listeners::Init)
  2. dockerd 收到命令後(假如命令為 docker run nginx) , dockerd 一看本地並沒有 nginx 鏡像,就會去 docker registry 拉取 nginx 鏡像(這裡沒有指定 nginx 版本,則會拉取 latest 版本)
  3. 拉取 nginx 鏡像後,則會創建 nginx 容器的運行時。

通過 docker info 命令,可以看到它的詳細配置,其中有一個 Docker Root Dir ,為 docker 所有文件包括 image、container、overlay 等的存儲位置,默認為 /var/lib/docker 。如果要修改,可以通過在 /etc/docker/daemon.json 中增加 "graph":"YOUR_PATH" 然後重啟 dockerd 即可(注意要做好數據備份工作!!!):

<code>{
  "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"],
  "graph": "YOUR_PATH"
}/<code>


Docker 各組件架構

我們以一個簡單的流程,通過串聯 Docker 源碼,來講述 Docker 的整個工作流程。

Dockerfile

Dockerfile , 包括 Docker Cli Command 等內容,會放到下一篇文章。這裡我們先準備一個異常簡單的 Dockerfile, 同時把這個 Dockerfile 命名為 dockerfile.min :

<code>From alpine
CMD echo "Hello From iyuhp"/<code>

docker build

之後進行構建 :

<code>➜  docker docker build . -t test/min:v0.1 -f ./dockerfile.min
Sending build context to Docker daemon  1.448MB
Step 1/2 : From alpine
 ---> a187dde48cd2
Step 2/2 : CMD echo "Hello From iyuhp"
 ---> Running in 392cd9c26109
Removing intermediate container 392cd9c26109
 ---> ae661fc7deae
Successfully built ae661fc7deae
Successfully tagged test/min:v0.1/<code>

在這一步, docker cli 運行命令 docker build args... 命令, 這個命令是怎麼處理的呢?

這裡要注意下,目前 docker cli 還在 這裡

docker cli 使用 spf13/cobra (我們執行 docker --help 時候輸出的一系列說明,就是這個東西搞的),初始化時,會把一系列的命令初始化進去:

Docker Network 及其他組件介紹

我們去看下 NewBuildCommand :

<code>// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
    options := newBuildOptions()
    cmd := &cobra.Command{
        Use:   "build [OPTIONS] PATH | URL | -",
        Short: "Build an image from a Dockerfile",
        Args:  cli.ExactArgs(1),
        RunE: func(cmd *cobra.Command, args []string) error {
            options.context = args[0]
            return runBuild(dockerCli, options)
        },
    }
    // ...
    return cmd
}/<code>

這個方法裡, 當收到 docker build ... 命令時,會執行 RunE 方法 , 然後執行 runBuild 方法:

<code>// cli/command/image/build.go:228
func runBuild(dockerCli command.Cli, options buildOptions) error {
    // 如果設置了 DOCKER_BUILDKIT 環境變量為 1, 則使用 buildkit 構建鏡像
    buildkitEnabled, err := command.BuildKitEnabled(dockerCli.ServerInfo())
    if buildkitEnabled {
        return runBuildBuildKit(dockerCli, options)
    }
    // 否則通過 dockerd 構建鏡像
    response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
    // ...
}
​
// client/image_build.go:20
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
    // ...
    serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
    return types.ImageBuildResponse{
        Body:   serverResp.body,
        OSType: osType,
    }, nil
}/<code>

可以看到,cli 通過 rest api 發送給了 docker daemon。

docker 封裝了 api 層,也就是第一張圖中的 REST API 部分。關於這一部分,可以在 api/server/router 目錄中找到。docker 現在提供兩種構建方式:

  • 基於 docker daemon 的第一代構建技術
  • 基於 bulidkit 的構建技術,不依賴 docker daemon(競品有 google 家的 kaniko 以及 img, img 沒有大廠背書)
  • docker 目前通過 BuilderBuildKit 來區分。在構建時, 加上 BuilderBuildKit=1 docker build ... 則可使用 buildkit:➜ docker DOCKER_BUILDKIT=1 docker build . -t test/min:v0.1 -f ./dockerfile.min
    [+] Building 0.0s (5/5) FINISHED
    => [internal] load build definition from dockerfile.min 0.0s
    => => transferring dockerfile: 41B 0.0s
    => [internal] load .dockerignore 0.0s
    => => transferring context: 2B 0.0s
    => [internal] load metadata for docker.io/library/alpine:latest 0.0s
    => CACHED [1/1] FROM docker.io/library/alpine 0.0s
    => exporting to image 0.0s
    => => exporting layers 0.0s
    => => writing image sha256:13f822e3f1827d48d690e86dd2f30f1690e72f42f947db54b04b63597e0c2952 0.0s
    => => naming to docker.io/test/min:v0.1 0.0s

鏡像構建完成後,通過 docker images 可以找到:

<code>➜  docker docker images
REPOSITORY       TAG                 IMAGE ID            CREATED            SIZE
test/min         v0.1                13f822e3f182        3 weeks ago         5.6MB/<code>

docker run

到這一步, docker build 就執行完畢。現在我們來 docker run 一波, 看看會發生什麼 :

<code>➜  docker docker run 13f822e3f182
Hello From iyuhp/<code>

嗯,輸出了我們希望輸出的內容,說明我們構建的鏡像是 OK 的。

同樣的, docker cli 通過 docker run... 命令,調用 rest api 。不過這個命令需要分兩步執行: docker create 和 docker start... :

<code>// cli/command/container/run.go:96
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
    // create container
    createResponse, err := createContainer(ctx, dockerCli, containerConfig, &opts.createOptions)
    // start container
    if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
        return runStartContainerErr(err)
    }
    // ...
}/<code>

我們先看看 docker create 都做了哪些事情:

<code>// Create creates a new container from the given configuration with a given name.
func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr error) {
    // 1. 獲取鏡像 id
    if opts.params.Config.Image != "" {
        img, err = daemon.imageService.GetImage(opts.params.Config.Image)
    }
    // 2. 與鏡像中的配置合併並驗證
    if err := daemon.mergeAndVerifyConfig(opts.params.Config, img); err != nil {
        return nil, errdefs.InvalidParameter(err)
    }
    // 3. 配置 container 的 log driver,若未配置,則使用 daemon 的 log driver
    if err := daemon.mergeAndVerifyLogConfig(&opts.params.HostConfig.LogConfig); err != nil {
        return nil, errdefs.InvalidParameter(err)
    }
    // 4. 創建容器對象,包含容器配置,網絡,Host,鏡像等信息
    if container, err = daemon.newContainer(opts.params.Name, os, opts.params.Config, opts.params.HostConfig, imgID, opts.managed); err != nil {
        return nil, err
    }
​
    // 5. 增加讀寫層, 鏡像層是隻讀的,增加讀寫層用來運行時的數據交互
    // Set RWLayer for container after mount labels have been set
    rwLayer, err := daemon.imageService.CreateLayer(container, setupInitLayer(daemon.idMapping))
    if err != nil {
        return nil, errdefs.System(err)
    }
    container.RWLayer = rwLayer
    // 6. 創建一些列文件夾,用來保存容器信息, /var/lib/docker/containers/[id] 目錄下
    // checkpoints, hostconfig.json, hostconfig.json
    rootIDs := daemon.idMapping.RootPair()
    if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil {
        return nil, err
    }
    if err := idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err != nil {
        return nil, err
    }
    if err := daemon.setHostConfig(container, opts.params.HostConfig); err != nil {
        return nil, err
    }
    // 7. 註冊到 dockerd 中
    if err := daemon.Register(container); err != nil {
        return nil, err
    }
    stateCtr.set(container.ID, "stopped")
    daemon.LogContainerEvent(container, "create")
    return container, nil
}/<code>

docker create 主要就是完成 container 配置的初始化以及註冊到 dockerd

那麼 docker start 做了什麼呢?

  1. 根據給定的 name(這個 name 可以是 container id,或者 container name ,或者 short container id) 獲取 container 對象
  2. 查看 container 的狀態,如果在 Paused, Running, Dead, RemovalInProgress 狀態,則返回錯誤
  3. 驗證 container 配置文件
  4. 掛在 RWLayer 讀寫層(TODO: 如何掛載)
  5. 初始化網絡,設置 Hostname
  6. 創建容器 spec
  7. 調用 libcontainer runc 創建並運行容器, runc 負責和 linux kernel 打交道,最開始提到的 cgroups 和 namespace ,都是由 runc 來創建的

這裡的部分,本渣目前還沒有理清除,可以參考 這裡 , 以及該作者關於 Docker 的一系列文章

架構圖

Docker Network 及其他組件介紹


Docker Image

鏡像由 layer 構成,每一層 layer 對應 Dockerfile 中一個命令,比如:

<code>From alpine
WORKDIR /iyuhp
ADD flag.txt .
RUN rm flag.txt
CMD echo "Hello From iyuhp"/<code>

當我們執行 docker build . 時, 可以看到輸出:

<code>➜  docker docker build . -t layer-test:v0.1 -f ./dockerfile.layer2
Sending build context to Docker daemon  1.451MB
Step 1/5 : From alpine
 ---> a187dde48cd2
Step 2/5 : WORKDIR /iyuhp
 ---> Running in 2aca1ee61533
Removing intermediate container 2aca1ee61533
 ---> 9a4c07535dd2
Step 3/5 : ADD flag.txt .
 ---> 10360de80e7c
Step 4/5 : RUN rm flag.txt
 ---> Running in 4065ee172091
Removing intermediate container 4065ee172091
 ---> 8a0e76ce41b7
Step 5/5 : CMD echo "Hello From iyuhp"
 ---> Running in c039ecab87dd
Removing intermediate container c039ecab87dd
 ---> 8b7004425461
Successfully built 8b7004425461
Successfully tagged layer-test:v0.1/<code>

構建鏡像時,docker 會對 Dockerfile 文件從上至下一行行執行,每次執行都會構建一層 layer (本質上也是一個 image),構建完畢後,該 layer 則不可更改(readonly)。

docker layer 的實現,基於 Union FS ,即傳說中的聯合文件系統。

如果該層的操作對象在上一層(上面 Dockerfile 的第四步) , docker 只會在該層將這個對象標記為刪除,最後運行這個鏡像的時候,這個對象你無法看到,表現為被刪除。

而當我們 run image 的時候,docker 則會掛在一層 RWLayer,如果只需要讀取底層 layer 的數據時,則直接去讀取,但如果需要對底層數據做修改,則會先將該文件 copy 到 RWLayer ,再做修改,這就是常說的 COW (coyp-on-write) 技術。

Docker Registry

docker registry 是用來存儲 docker image 的鏡像倉庫,類似於 github。通過 docker pull 、docker push 等命令,可以從 registry 拉取鏡像或者將我們本地構建的鏡像推送到 registry。

docker 提供官方的 Docker Hub, 各大雲廠商也都推出了自己的容器鏡像服務。

當然,我們也可以基於 docker 提供的 registry 鏡像,來構建我們自己的 docker registry,就好像我們搭建自己的 gitlab 一樣。

Docker Network

docker 的網絡實現了插件化,意味著,你可以基於 docker network interface 來實現自己的網絡插件,或者使用其他的網絡插件。

docker 內置了以下幾種 network driver:

  • bridge:docker 默認的 network driver
  • host:共享宿主機的網絡
  • overlay:可以實現不同 dockerd 之間的網絡通信
  • macvlan:該模式下,可以為容器分配 mac 地址,使其在網絡上顯示為物理設備(不太清除...)
  • none:該模式下,將禁止網絡連接

這裡以 bridge 模式為例。

默認網絡

我們通過 docker network ls 查看時,會發現 docker 已經幫我們創建了三個類型的網絡:

<code>➜  unionFS docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
4b3d0dc09100        bridge              bridge              local
847894dcaa28        host                host                local
f8c096c91de0        none                null                local/<code>

通過 docker network inspect [id/name] 可以查看該網絡的具體信息(刪除了部分內容):

<code>➜  unionFS docker network inspect 4b3d0dc09100
[
    {
        "Name": "bridge",
        "Id": "4b3d0dc0910017d6bb7823e3415910ff7a7f9175d04cb29808ce627efcf9ad58",
        "Created": "2020-04-18T21:58:05.999876875+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Containers": {
            "8b87032b8281671c043a33202efa022ddaa398bc42719d1fa1c3e1ca3245970f": {
                "Name": "growing-uploader",
                "EndpointID": "227d88337cda67d632f48cf64a1b5e5c6607f560fe0e0dca8b09515d3fe29208",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        }
    }
]/<code>

這裡向我們顯示了該網絡的:

  • Driver: bridge,網橋模式
  • subnet : 16位掩碼
  • gateway : 172.17.0.1
  • containers :當前使用該網絡的容器,這裡能看到該容器的 id,ip 等信息
  • options : 網絡的其他設置,包括掛載的網橋 docker0(該網橋是 docker 在安裝時創建的), 網絡的 mtu 為 1500 等等。這裡說下 mtu,maximum transmission unit, 最大傳輸單元。如果我們的 mtu 設置的不合理,比如設置為 1400 ,那就有可能產生丟包問題

其中, bridge 是 docker 默認的網絡模式以及使用的網絡。當我們去運行容器時,如果未通過 --network 來指定使用的網絡時,則會使用 bridge 網絡。

創建網絡

現在我們嘗試創建一個自己的網絡 ownbridge [1]:

<code>docker network create -d bridge --subnet 172.0.0.3/30 --gateway 172.0.0.1 ownbridge/<code>
  • -d 參數用來指定 network driver ,默認未 bridge
  • --sebnet 通過 cidr 格式指定網段
  • --gateway 指定網關
  • 更多參數可通過 docker network create -h 獲取

現在,我們 build 一個 image ,然後使用上面的網絡啟動:

Dockerfile

<code>FROM busybox
RUN mkdir /html \
        && echo "Hello World" > /html/index.html
EXPOSE 1234
CMD ["httpd",  "-f", "-p", "1234", "-h", "/html"]/<code>

docker build && docker run

  • --network 指定網絡為 ownbridge
  • --rm : 容器停止時刪除容器
  • -p : 將 1234 端口暴露到宿主機, -p 12306:1234 , 則會將容器端口 1234 映射到宿主機 12306 上
  • -d : 後臺運行
<code># build
➜  docker docker build . -t simpelhttpd:v0.1
// ...
Successfully built 1e3ceb976b97
Successfully tagged simpelhttpd:v0.1

# run 通過 --network 指定網絡
➜  docker docker run --rm -p 1234 --network ownbridge -d 1e3ceb976b97
1ad1f5236ab5f95b2bf235f33cff494b43a77b7e414b85b067416ad9715b039c/<code>

Curl

<code># 查看端口 可以看到是 0.0.0.0:32775 -> 1234/tcp 
# 即 docker 將容器內 1234 端口映射到了 32775 上
➜  docker docker ps                

# Curl
➜  docker curl localhost:32775
Hello World/<code>

輸出了 "Hello World" , 就是我們在 Dockerfile 中寫入到 index.html 中的內容

通信

現在我們看看,curl 是怎麼訪問到我們的容器的。在此之前,我們整理下目前的信息:

目標網絡

容器信息:

<code>"Gateway": "172.0.0.1",
"IPAddress": "172.0.0.2"/<code>

它關聯的網橋:

<code>176: br-814aff72d8df:  mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:83:a7:d1:16 brd ff:ff:ff:ff:ff:ff
    inet 172.0.0.1/30 brd 172.0.0.3 scope global br-814aff72d8df
       valid_lft forever preferred_lft forever
    inet6 fe80::42:83ff:fea7:d116/64 scope link
       valid_lft forever preferred_lft forever/<code>

在執行 curl 之前,宿主機經過 icmp + arp ,習得 arp 信息,放入本機的 arp 緩存表中。

訪問前 arp 表:

<code>➜  docker arp
Address                  HWtype  HWaddress           Flags Mask            Iface
_gateway                 ether   ee:ac:ca:ff:ff:ff   C                     eth0/<code>

這個時候,在 arp 表中沒有找到,則會進行廣播 arp 報文,報文結構一般為:

dest mac | source mac | type

type 為 1 時表示 arp 請求, dest mac 為全 f 時(ff.ff.ff.ff.ff.ff) 即進行廣播,具體信息請參考 Ref 部分的引用。

訪問後 arp 表:

<code>➜  docker arp -nvvv
Address                  HWtype  HWaddress           Flags Mask            Iface
172.16.11.253            ether   ee:ae:ca:ff:ff:ff   C                     eth0
172.0.0.2                ether   02:42:ac:00:00:02   C                     br-814aff72d8df/<code>

這裡還要再說一句,在 bridge 模式下,docker 默認會為所有容器開放的端口起一個 docker-proxy 的進程,通過nat + iptables[2] 對該端口進行代理,該功能可以通過配置 userland-proxy 為 false 關閉。

通過 sudo iptables -L 查看:

<code>➜  docker sudo iptables -t nat -nL
Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:32775 to:172.0.0.2:1234/<code>

查看 iptables nat 表,能看到,所有訪問 32775 端口的請求,都被轉發到 172.0.0.2:1234 這兒了。

於是宿主機查詢自己的路由表:

<code>➜  docker route -nvvv
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.16.11.253   0.0.0.0         UG    100    0        0 eth0
172.0.0.0       0.0.0.0         255.255.255.252 U     0      0        0 br-814aff72d8df/<code>

第一條中的 0.0.0.0 意為默認路由,即所有不存在該路由表中的請求,都會由它路由到下一跳 172.16.11.253 。

而我們的 ip 為 172.0.0.2 ,根據路由表規則,會被網卡 br-814aff72d8df 處理。

docker 通過 veth pair [3],與 網卡 br-814aff72d8df 連接,也就是說,上面的請求,最終被髮送給我們此行的目的地。

Network Flow

Docker Network 及其他組件介紹

註釋

[1]

--subnet 172.0.0.1/30 :

Docker Network 及其他組件介紹

我們知道,通常將 ip 地址分為 A 類、B 類、C 類地址,ip 地址可以用一個長 32 位的二進制數表示。這其中又分為網絡地址 + 主機地址。

對於 A 類地址而言,其前八位表示網絡地址,後 24 位表示主機地址。ip 尋址,就是先通過尋找網絡地址,然後再尋找主機地址。

上面的劃分,會導致 ipv4 資源的浪費,於是誕生了 CIDR , 上面的 172.0.0.1/30 就是其表現形式。這是什麼含義呢?

前面的 ip 可以轉化位一個 32 位的二進制數, 30 表示前 32 位為網絡地址,將前 30 位置為 1, 即為它的子網掩碼,這裡為: 255.255.255.252,則主機地址只能是剩下的後兩位表示,也就是總共可以產生 22 = 4 個 ip ,即 172.0.0.[0-3] ,通常,會有一些特殊的 ip 不會被分配,用來作為廣播、網關等作用,所以通常會減去 2,作為實際可用的 ip 數。

如何判斷兩個 ip 是否在同一網段?

  • 對於 A, ip B 與掩碼 A 做與運算,得到的與自己子網一致
  • 對於 B, ip A 與掩碼 B 做與運算,得到的與自己子網一致

假設兩個 ip A : 172.168.0.1/16, ip B : 172.168.3.1/24

於是我們得到:

  • ip A : 172.168.0.1
  • 掩碼 A: 172.168.0.0
  • ip B:172.168.3.1
  • 掩碼 B:172.168.3.0

則 A 運算後,得到子網 ip B & 掩碼 A = 172.168.0.0/16

B 運算後, 得到子網 ip A & 掩碼 B = 172.168.3.0/24

一致,所以他們在同一網段。

如果 A 變為 172.168.3.1/16, B 變成 172.168.0.1/24 , 則不在同一網段了。因為 B 運算後的結果為: 172.168.3.0/24 ,與自己的 172.168.0.0/24 不一致。

[2]

iptables 內置四張 table ,執行順序為 raw > mangle > nat > filter

我們執行 sudo iptables -nL 時, 默認 table 為 filter。

docker 在做 port 映射時, 會分別向 nat 和 filter 兩張表寫入信息。我們在查找的時候,需要查看兩張表的信息。

同理, 我們甚至可以手動修改 iptables ,來達到宿主機內不同容器間的通信。

[3]

如何查看 docker veth pair,這裡提供兩種方式

  1. 先查看容器內 veth pair,然後通過 ip a 查看:# 107a4446c1f5 對應具體的 container id
    ➜ docker docker exec -ti 107a4446c1f5 sh -c 'cat /sys/class/net/eth0/iflink'
    214
    # 這裡看到是 214, 然後通過 ip link 查看
    ➜ docker ip link | grep 214:
    214: veth9ea7c64@if213: ...
    # 所以這裡 veth9ea7c64 就是和上面 container 的 veth pair
  2. 通過 docker inspect + ethtool# 查找 net namespace
    ➜ docker docker inspect --format='{{ .NetworkSettings.SandboxKey}}' 107a4446c1f5
    /var/run/docker/netns/047bec4e1b1c

    # 去容器內部查看 veth
    ➜ docker sudo nsenter --net=/var/run/docker/netns/047bec4e1b1c ethtool -S eth0
    NIC statistics:
    peer_ifindex: 214
    rx_queue_0_xdp_packets: 0
    rx_queue_0_xdp_bytes: 0
    rx_queue_0_xdp_drops: 0

    # 通過 ip link 查看
    ➜ docker ip link | grep 214:
    214: veth9ea7c64@if213: ...

這個 nsenter 的命令對於調試容器貌似挺有用的,需要學習一波。

Reference

Cgroups

Namespace

InfoQ 源碼分析

ARP Wiki


Read More

丟包問題


分享到:


相關文章: