Skip to content
Go back

Tìm hiểu về Network trong Docker Swarm

Published:  at  07:29 PM

Table of contents

Open Table of contents

Chuẩn bị

Chuẩn bị ít nhất 03 server cài đặt sẵn Docker. Có thể sử dụng Ansible playbook để cài đặt Docker đồng thời cho các server hoặc cài đặt Docker trên từng server với command sau:

curl -sSL <https://get.docker.io> | bash

Chú ý: Docker Swarm cần mở một số protocols và ports dưới đây để giao tiếp giữa các server:

Kiểm tra tường lửa của server xem có bị chặn các protocols và ports trên hay không bằng command:

iptables-save

Nếu bị chặn, hãy mở protocols và ports trên bằng command:

iptables -I INPUT -p tcp -m tcp --dport 2377 -j ACCEPT
iptables -I INPUT -p tcp -m tcp --dport 7946 -j ACCEPT
iptables -I INPUT -p udp -m udp --dport 7946 -j ACCEPT
iptables -I INPUT -p udp -m udp --dport 4789 -j ACCEPT

Thiết lập cụm Docker Swarm

Kiến trúc triển khai sẽ bao gồm 01 manager node và 02 worker node:

  1. SSH vào manager node và chạy command dưới đây để khởi tạo swarm cluster:
docker swarm init
  1. Sau khi init sẽ có một ghi chú được in ra màn hình bao gồm token dùng để join các work node vào cụm. SSH lần lượt vào worker-1 và worker-2 thực hiện command như hướng dẫn ở ghi chú trên để join vào cụm:
docker swarm join --token <TOKEN> \\
  --advertise-addr <IP-ADDRESS-OF-WORKER-1> \\
  <IP-ADDRESS-OF-MANAGER>:2377
  1. Sau khi join lần lượt worker-1 và worker-2, có thể kiểm tra danh sách các node khả dụng trên manager node bằng command:
docker node ls
ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
3ahbvxay68yn43lojz7j8uqdg *   server-1   Ready     Active         Leader           20.10.7
xjlbrbhzfr2b2do6eosod7rt2     server-2   Ready     Active                          20.10.7
1860sgfoof3ujljs8f1zfk7h3     server-3   Ready     Active                          20.10.7

Tìm hiểu về Docker Swarm Network

Triển khai Nginx service với Docker Swarm

Trên manager node, tạo overlay network nginx-net và triển khai Nginx service kết nối tới nginx-net. Nginx service sẽ publish port 8080 ra bên ngoài map với port 80 bên trong Nginx container:

docker network create -d overlay nginx-net
docker service create \\
  --name my-nginx \\
  --publish target=80,published=8080 \\
  --replicas=2 \\
  --network nginx-net \\
  thuongnn1997/nginx:debug

Kiểm tra Nginx service đang chạy trên server nào bằng command:

docker service ps my-nginx
ID             NAME         IMAGE                      NODE       DESIRED STATE   CURRENT STATE            ERROR     PORTS
opdskafvlrcl   my-nginx.1   thuongnn1997/nginx:debug   server-1   Running         Running 15 minutes ago
6gtue1osr897   my-nginx.2   thuongnn1997/nginx:debug   server-2   Running         Running 15 minutes ago

Để phân biệt nội dung trả về từ Nginx service, sử dụng docker exec vào từng Nginx container và sửa nội dung index.html như sau:

docker exec -it my-nginx.1.opdskafvlrclhc38t0tcw5uup /bin/bash
echo "Welcome to nginx-1 from server-1" > /usr/share/nginx/html/index.html

docker exec -it my-nginx.2.6gtue1osr897u5i8fafdtoc3u /bin/bash
echo "Welcome to nginx-2 from server-2" > /usr/share/nginx/html/index.html

Kiểm tra

Thực hiện curl command nhiều lần vào một địa chỉ IP bất kỳ trong 03 server với port 8080 như ví dụ dưới đây:

curl 171.244.143.222:8080
# Welcome to nginx-2 from server-2
curl 171.244.143.222:8080
# Welcome to nginx-1 from server-1
curl 171.244.143.222:8080
# Welcome to nginx-2 from server-2

Liệt kê tất cả các loại Docker network trên server bằng command dưới đây:

docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
10864fa65d57   bridge            bridge    local
404e32f8d992   docker_gwbridge   bridge    local
4efb973544ed   host              host      local
rasrehzw9jfe   ingress           overlay   swarm
jtlbkwkmjz9w   nginx-net         overlay   swarm
fe2c98e1bb90   none              null      local

Theo lý thuyết, đối với Docker Swarm Network sẽ có các loại network có vai trò khác nhau dưới đây:

Ingress Network hoạt động như thế nào?

Như chúng ta đã biết thì ingress là một loại network đặc biệt đóng vai trò như Load Balancer giúp cho các request từ client được forward đến replica container nằm bất cứ đâu trên các node. Khi triển khai dịch vụ với tham số --publish target=80,published=8080, các replica container sẽ được tự động join vào ingress network. Vậy chúng hoạt động như thế nào?

Chúng ta đã biết 02 replica container của Nginx service đang chạy trên server 1 và 2, thử kiểm tra docker inspect Nginx container trên server-1:

//root@server-1: docker inspect my-nginx.1.opdskafvlrclhc38t0tcw5uup
...
"NetworkSettings": {
    "Networks": {
      "ingress": {
          "IPAMConfig": {
              "IPv4Address": "10.0.0.6"
          },
          "Links": null,
          "Aliases": [
              "b5af8738eb30"
          ],
          "NetworkID": "rasrehzw9jfeuj97ivx65nj5h",
          "EndpointID": "3cf548a835fb3829d9fcb7f71259bad7554483a12d829a53880e5126974b1e36",
          "Gateway": "",
          "IPAddress": "10.0.0.6",
          "IPPrefixLen": 24,
          "IPv6Gateway": "",
          "GlobalIPv6Address": "",
          "GlobalIPv6PrefixLen": 0,
          "MacAddress": "02:42:0a:00:00:06",
          "DriverOpts": null
      },
      "nginx-net": {
          "IPAMConfig": {
              "IPv4Address": "10.0.1.3"
          },
          "Links": null,
          "Aliases": [
              "b5af8738eb30"
          ],
          "NetworkID": "jtlbkwkmjz9wzrhcym45ivrxh",
          "EndpointID": "513595f853f714fb62c90093272c8ec66ac55fcaa904ccb9143bee15ec194a71",
          "Gateway": "",
          "IPAddress": "10.0.1.3",
          "IPPrefixLen": 24,
          "IPv6Gateway": "",
          "GlobalIPv6Address": "",
          "GlobalIPv6PrefixLen": 0,
          "MacAddress": "02:42:0a:00:01:03",
          "DriverOpts": null
      }
    }
}
...

Bỏ qua nginx-net network, chúng ta sẽ tìm hiểu về internal overlay network ở phần sau. Thấy được container my-nginx.1.opdskafvlrclhc38t0tcw5uup chạy trên server-1 đang được gắn với ingress network có địa chỉ IP là 10.0.0.6. Chúng ta tiếp tục kiểm tra docker network inspect ingress:

//root@server-1: docker network inspect ingress
...
"IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
        {
            "Subnet": "10.0.0.0/24",
            "Gateway": "10.0.0.1"
        }
    ]
},
...
"Containers": {
    "b5af8738eb30228c00423e6917d121e25c69222378c22839ea0fcc420a136f8f": {
        "Name": "my-nginx.1.opdskafvlrclhc38t0tcw5uup",
        "EndpointID": "3cf548a835fb3829d9fcb7f71259bad7554483a12d829a53880e5126974b1e36",
        "MacAddress": "02:42:0a:00:00:06",
        "IPv4Address": "10.0.0.6/24",
        "IPv6Address": ""
    },
    "ingress-sbox": {
        "Name": "ingress-endpoint",
        "EndpointID": "8e376885ef5c9be0bc56dae845dec3e5c55e1cc34df8964eec5a5db925acc71a",
        "MacAddress": "02:42:0a:00:00:02",
        "IPv4Address": "10.0.0.2/24",
        "IPv6Address": ""
    }
},
...

Có thể thấy ingress network thuộc dải IP 10.0.0.0/24 và hiện tại có 2 docker container đang được gắn với network này. my-nginx.1.opdskafvlrclhc38t0tcw5uup container hiển nhiên được gắn với ingress network vì chúng ta đã publish port với my-nginx service, vậy ingress-sbox container kia từ đâu ra?

ingress-sbox là một hidden container được tạo ra mặc định (check trên cả 03 server đều thấy container này mặc dù docker ps không thấy chúng đâu cả), kiểm tra tiếp ta sẽ thấy ingress-sbox cũng được gắn với docker_gwbridge network:

//root@server-1: docker network inspect docker_gwbridge
...
"IPAM": {
    "Driver": "default",
    "Options": null,
    "Config": [
        {
            "Subnet": "172.19.0.0/16",
            "Gateway": "172.19.0.1"
        }
    ]
},
...
"Containers": {
    "b5af8738eb30228c00423e6917d121e25c69222378c22839ea0fcc420a136f8f": {
        "Name": "gateway_c3db260d3ae0",
        "EndpointID": "11333a766b84c9a85e032d62c75895c23216c8a1115445118af593e8a50d24fa",
        "MacAddress": "02:42:ac:13:00:03",
        "IPv4Address": "172.19.0.3/16",
        "IPv6Address": ""
    },
    "ingress-sbox": {
        "Name": "gateway_ingress-sbox",
        "EndpointID": "6a9a17ae6df0ae39eac203b257fed6db70a4708e76f810d48fcbaa5cd766f662",
        "MacAddress": "02:42:ac:13:00:02",
        "IPv4Address": "172.19.0.2/16",
        "IPv6Address": ""
    }
},
...

Tổng hợp lại các thông tin về ingress network, docker_gwbridge network và nginx container trên cả 03 server ta sẽ có diagram như sau:

Dựa vào diagram thấy được request từ client vào bất kỳ server nào trong 03 server trên đều được iptables forward vào IP (thuộc dải docker_gwbridge network) của ingress-sbox container:

Vậy làm sao từ ingress-sbox có thể lựa chọn được service đích (nginx-1 hoặc nginx-2) và thực hiện LB tới chúng như thế nào?

ingress-sbox container được dùng để làm gì?

...
"Endpoint": {
    "Spec": {
        "Mode": "vip",
        "Ports": [
            {
                "Protocol": "tcp",
                "TargetPort": 80,
                "PublishedPort": 8080,
                "PublishMode": "ingress"
            }
        ]
    },
    "Ports": [
        {
            "Protocol": "tcp",
            "TargetPort": 80,
            "PublishedPort": 8080,
            "PublishMode": "ingress"
        }
    ],
    "VirtualIPs": [
        {
            "NetworkID": "rasrehzw9jfeuj97ivx65nj5h",
            "Addr": "10.0.0.5/24"
        },
        {
            "NetworkID": "jtlbkwkmjz9wzrhcym45ivrxh",
            "Addr": "10.0.1.2/24"
        }
    ]
}
...

IPVS hoạt động ra sao trong Docker Swarm?

Bởi vì ingress-sbox là hidden container nên chúng ta không thể exec vào trong để kiểm tra nó được, sử dụng nsenter để switch namespace ra ngoài máy host như sau:

sudo nsenter --net=/run/docker/netns/ingress_sbox

### Có thể back trở lại namespace như ban đầu bằng command:
sudo nsenter --net=/run/docker/netns/default

Tiếp tục chạy command dưới đây để xem thông tin iptables rules trong ingress-sbox namespace (Chú ý đến POSTROUTING Chain):

iptables-save
# Generated by iptables-save v1.6.1 on Wed Jul 21 21:09:34 2021
*mangle
:PREROUTING ACCEPT [1153:100216]
:INPUT ACCEPT [637:48400]
:FORWARD ACCEPT [516:51816]
:OUTPUT ACCEPT [637:48400]
:POSTROUTING ACCEPT [1153:100216]
-A PREROUTING -p tcp -m tcp --dport 8080 -j MARK --set-xmark 0x100/0xffffffff
**-A INPUT -d 10.0.0.5/32 -j MARK --set-xmark 0x100/0xffffffff**
COMMIT
# Completed on Wed Jul 21 21:09:34 2021
# Generated by iptables-save v1.6.1 on Wed Jul 21 21:09:34 2021
*filter
:INPUT ACCEPT [1432:115850]
:FORWARD ACCEPT [938:91965]
:OUTPUT ACCEPT [1432:115850]
COMMIT
# Completed on Wed Jul 21 21:09:34 2021
# Generated by iptables-save v1.6.1 on Wed Jul 21 21:09:34 2021
*nat
:PREROUTING ACCEPT [145:7276]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:DOCKER_OUTPUT - [0:0]
:DOCKER_POSTROUTING - [0:0]
-A OUTPUT -d 127.0.0.11/32 -j DOCKER_OUTPUT
-A POSTROUTING -d 127.0.0.11/32 -j DOCKER_POSTROUTING
**-A POSTROUTING -d 10.0.0.0/24 -m ipvs --ipvs -j SNAT --to-source 10.0.0.4**
-A DOCKER_OUTPUT -d 127.0.0.11/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 127.0.0.11:33731
-A DOCKER_OUTPUT -d 127.0.0.11/32 -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.11:39025
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p tcp -m tcp --sport 33731 -j SNAT --to-source :53
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 39025 -j SNAT --to-source :53
COMMIT
# Completed on Wed Jul 21 21:09:34 2021

Trong mangle table của iptables sẽ đánh dấu (MARK) các gói tin có Destination IP là 10.0.0.5 với 0x100, mà 10.0.0.5 chính là địa chỉ VIP đã đề cập ở mục trên. Vậy các gói tin được đánh dấu với 0x100 sẽ đi đâu về đâu? Thực hiện kiểm tra tiếp sử dụng ipvsadm (tool được dùng để cấu hình IPVS) với command ipvsadm -L -n --stats để xem thông tin cấu hình hiện tại:

ipvsadm -L -n --stats
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port               Conns   InPkts  OutPkts  InBytes OutBytes
  -> RemoteAddress:Port
FWM  256                               266     1175      945    90915    92590
  -> 10.0.0.6:0                        133      552      447    43073    43507
  -> 10.0.0.7:0                        133      623      498    47842    49083

Đến đây thì quá rõ luôn rồi 😀 những gói tin được đánh dấu với MARK (0x100 convert to decimal is 256) sẽ được cân bằng tải tới 02 địa chỉ IP là 10.0.0.610.0.0.7 Xem lại diagram phía trên thì thấy rằng 02 địa chỉ này chính là địa chỉ của Nginx container chạy trên server-1 và server-2.

Để ý thêm trong rule NAT của iptables (POSTROUTING Chain) sẽ có thêm đoạn các gói packets sẽ được NAT lại Source IP là 10.0.0.4, mà 10.0.0.4 lại chính là địa chỉ IP của ingress-sbox trên ingress network. Việc này đảm bảo sau khi packets khi gửi đến dịch vụ đích thì sẽ phản hồi lại cho ingress-sbox (đại diện cho server-2)rồi từ đó phản hồi lại cho client (đọc thêm về Load Balancing Layer 4 sử dụng cơ chế NAT)

Internal Overlay Network hoạt động như thế nào?

Đối với Internal Overlay Network thì cơ chế routing mesh (load balancer) không khác gì so với phần Ingress Network đã giải thích phía trên. Để hình dung rõ hơn về giao tiếp nội bộ giữa các service chúng ta triển khai thêm backend service như sau:

docker service create \\
  --name backend \\
  --replicas=1 \\
  --network nginx-net \\
  thuongnn1997/nginx:debug

Triển khai thêm backend service cũng được join với nginx-net network đã tạo ở ví dụ trước. Thực hiện docker inspect xem các thông tin, ta cũng có diagram của các service giao tiếp với nginx-net network như sau:

Thực hiện docker exec vào container của backend service trên server-3, vì backend service và my-nginx service đều dùng chung nginx-net network nên đứng từ backend container ta có thể kết nối được tới các container của my-nginx service như sau:

docker exec -it backend.1.zchpr6x6hvr6tofqlpl2vob3z /bin/bash

curl my-nginx
# Welcome to nginx-2 from server-2
curl my-nginx
# Welcome to nginx-1 from server-1
curl my-nginx
# Welcome to nginx-2 from server-2

Request từ backend container sẽ được LB tới các container của my-nginx service (nội dung trả về từ cả nginx-1nginx-2 sau mỗi lần request). Bản chất mỗi request từ backend container đều được đẩy tới địa chỉ VIP của my-nginx service (10.0.1.2), kiểm tra lại địa chỉ IP được resolve từ domain my-nginx sẽ thấy:

nslookup my-nginx
Server:         127.0.0.11
Address:        127.0.0.11#53

Non-authoritative answer:
Name:   my-nginx
Address: 10.0.1.2

Địa chỉ IP được resolve từ my-nginx không phải là của container nginx-1 (10.0.1.3) hay nginx-2 (10.0.1.4) mà chính là địa chỉ VIP, tương tự như cách hoạt động của IPVS như đã tìm hiểu ở phần Ingress Network. Chúng ta lại tiếp sử dụng nsenter tool để switch sang namespace của lb-nginx-net trên server-3 thấy được như sau:

Bypass the routing mesh (IVPS)

DNS Round Robin hoạt động như thế nào?

Triển khai một dịch vụ khác sử dụng DNSRR mode bằng command dưới đây:

docker network create -d overlay demo-net
docker service create \\
  --name user-api \\
  --publish target=80,published=8081,mode=host \\
  --replicas=3 \\
  --endpoint-mode=dnsrr \\
  --network demo-net \\
  thuongnn1997/nginx:debug

Triển khai user-api service trên overlay network là demo-net, tham số --endpoint-mode để là dnsrrmode=host trong tham số publish (bắt buộc khi triển khai với DNSRR mode).

Sau khi triển khai xong, hãy thử docker exec vào một container bất kỳ đã được triển khai trong cụm Swarm. Truy cập vào dịch vụ thông qua domain user-api ta vẫn nhận được kết quả giống với VIP mode (không có gì khác biệt ở đây):

docker exec -it user-api.1.o6cu6rp3jqje8ay0drvgqnchi /bin/bash
curl user-api
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Tuy nhiên hãy thử kiểm tra địa chỉ IP được resolve từ domain user-api như sau:

nslookup user-api
Server:         127.0.0.11
Address:        127.0.0.11#53

Non-authoritative answer:
Name:   user-api
Address: 10.0.3.4
Name:   user-api
Address: 10.0.3.3
Name:   user-api
Address: 10.0.3.2

Điểm khác biệt chính là ở đây! nếu như với VIP mode sẽ chỉ resolve ra một địa chỉ duy nhất là địa chỉ VIP thì với DNSRR mode sẽ trả về luôn danh sách các địa chỉ IP của replica container.

Vậy khi nào thì sử dụng DNS Round Robin?

Trong thực tế khi chạy hệ thống trên production, chúng ta vẫn cần một External IP cấu hình domain để client có thể kết nối vào hệ thống.

Từ vấn đề trên trong một số hệ thống có thể cân nhắc bỏ swarm load balancer đi để đạt được thời gian truy vấn tối ưu hơn, lúc này cấu hình DNSRR cho LB sẽ được sử dụng. Tuy nhiên sử dụng mode này chúng ta sẽ có những đánh đổi dưới đây:

Có 02 cách để chạy nhiều service task hơn số lượng nodes là:

Tài liệu tham khảo


Suggest Changes

Previous Post
[Google Cloud] Resource Hierarchy, Roles and Identities
Next Post
Kubernetes Overview Diagrams