返回文章列表

無狀態應用程式Docker設計與實作

本文探討無狀態應用程式在Docker Swarm環境中的設計與實作,以Sonyflake分散式ID生成服務為例,說明如何利用Docker

Web 開發 系統設計

在分散式系統中,無狀態應用程式設計至關重要,它讓服務能夠水平擴充套件並提升可靠性。本文以Sonyflake ID生成器為例,展示如何在Docker Swarm叢集中佈署無狀態服務,並探討Docker網路模式的應用,涵蓋bridge、overlay等模式,以及連線埠繫結和DNS解析等關鍵技術。同時,文章也提供實用的網路除錯技巧,例如使用netstat、strace等工具,並推薦Grafana、Netdata、Prometheus等監控工具,幫助開發者快速診斷和解決網路問題,確保服務的穩定執行。

無狀態應用程式的設計與實作

在現代的雲端運算和容器化技術中,無狀態應用程式(Stateless Application)是一種重要的設計模式。這種模式允許應用程式在不依賴本地狀態的情況下執行,從而提高可擴充套件性、可靠性和容錯能力。本文將探討無狀態應用程式的設計原則,並以Sonyflake為例,展示如何在Docker Swarm環境中實作無狀態服務。

無狀態應用程式的優勢

無狀態應用程式的主要優勢在於其可擴充套件性和可靠性。由於應用程式不依賴本地狀態,因此可以輕易地在多個容器或節點之間進行擴充套件和負載平衡。這使得應用程式能夠更好地應對高流量和大規模的使用者需求。

Sonyflake:一個無狀態ID生成器的例子

Sonyflake是一個由Sony開發的64位元ID生成器,類別似於Twitter的Snowflake。它透過結合當前時間戳、序列號和機器ID來生成唯一的ID。由於Sonyflake是無狀態的,因此可以輕易地在Docker Swarm環境中進行擴充套件和佈署。

Sonyflake的工作原理

Sonyflake使用以下元件來生成ID:

  • 39位元的時間戳(以10毫秒為單位)
  • 8位元的序列號
  • 16位元的機器ID

機器ID是根據容器的網路介面生成的,這確保了在同一網路中的容器具有唯一的機器ID。

在Docker Swarm中佈署Sonyflake

要在Docker Swarm中佈署Sonyflake,可以使用以下命令:

# docker service create \
--replicas 5 \
--network party-swarm \
--update-parallelism 5 \
--name sonyflake \
-p 80:80 titpetric/sonyflake

這個命令建立了一個名為sonyflake的服務,具有5個副本,並將其釋出到埠80。

#### 內容解密:

此命令用於在Docker Swarm中建立一個新的服務。--replicas 5 指定了服務的副本數量為5,--network party-swarm 指定了服務所使用的網路,--update-parallelism 5 指定了更新服務時的平行度,--name sonyflake 指定了服務的名稱,-p 80:80 將主機的80埠對映到容器的80埠,titpetric/sonyflake 是Sonyflake服務的Docker映像。

測試Sonyflake服務

可以使用curl命令來測試Sonyflake服務:

# curl -s 127.0.0.1
{"id":134164003092955146,"machine-id":10,"msb":0,"sequence":0,"time":7996797746}

#### 內容解密:

此命令用於測試Sonyflake服務是否正常執行。curl -s 127.0.0.1 向本地主機的80埠傳送一個HTTP請求,並傳回Sonyflake生成的ID。

效能測試

可以使用wrk工具來測試Sonyflake服務的效能:

# docker run --net=party-swarm --rm williamyeh/wrk -t 6 -c 30 http://sonyflake

#### 內容解密:

此命令用於測試Sonyflake服務的效能。docker run --net=party-swarm --rm williamyeh/wrkparty-swarm網路中執行一個臨時的wrk容器,-t 6 -c 30 http://sonyflake 指定了測試的執行緒數、連線數和目標URL。

負載平衡

在預設的VIP(Virtual-IP)模式下,Docker Swarm會將請求均勻地分配到不同的容器中。但是,當使用keep-alive連線時,負載可能會變得不均勻。在這種情況下,可以使用DNS輪詢(DNS Round-Robin)來實作更智慧的負載平衡。

# docker service create \
--replicas 5 \
--network party-swarm \
--endpoint-mode dnsrr \
--name sonyflake \
-p 80:80 titpetric/sonyflake

#### 內容解密:

此命令用於建立一個使用DNS輪詢模式的Sonyflake服務。--endpoint-mode dnsrr 指定了服務的端點模式為DNS輪詢。

Docker 網路模式與 Port Binding 實務解析

Docker 的網路功能提供了多種模式,讓容器能夠以不同的方式與外界通訊。這些模式包括 none、bridge、host 和 overlay 等網路驅動程式,能夠滿足各種不同的應用場景需求。

None 網路模式:完全隔離的容器網路

在 none 網路模式下,容器不會被分配任何網路介面,這意味著容器內的應用程式無法存取網路,也無法對外提供任何服務。這種模式適合用於執行不受信任的程式碼,或者是某些不需要網路存取的 CLI 應用程式,例如影片處理任務。

程式碼範例:驗證 none 網路模式

docker run --rm -it --network=none alpine:3.5 ip addr

內容解密:

  1. docker run:啟動一個新的容器。
  2. --rm:容器停止後自動刪除。
  3. -it:互動模式,允許使用者與容器內的 shell 互動。
  4. --network=none:指定容器使用 none 網路模式。
  5. alpine:3.5:使用的 Docker 映像檔。
  6. ip addr:顯示容器的網路介面資訊。

輸出結果顯示,只有 lo(loopback)介面可用,證明容器沒有外部網路存取能力。

Bridge 網路模式:預設的單機容器網路

bridge 網路模式是 Docker 預設的網路模式,適合單一主機上的容器互通。在此模式下,容器可以透過 IP 位址互相連線,但預設無法透過 DNS 解析彼此的主機名稱。

程式碼範例:驗證 bridge 網路模式下的容器互連

# 啟動兩個容器
docker run -itd --name sh1 alpine:3.5 sh
docker run -itd --name sh2 alpine:3.5 sh

# 檢視容器 IP 位址
docker exec -it sh1 ip addr | grep global
docker exec -it sh2 ip addr | grep global

# 使用 IP 位址進行 ping 測試
docker exec -it sh2 ping 172.17.0.3

內容解密:

  1. docker run -itd:以 detached 模式啟動互動式容器。
  2. --name:為容器指定名稱。
  3. docker exec:在執行中的容器內執行命令。
  4. ip addr | grep global:過濾出容器的全球 IP 位址。
  5. ping:測試容器間的網路連通性。

自訂 Bridge 網路:增強的容器 DNS 解析

自訂 bridge 網路允許容器透過主機名稱互相存取,這是預設 bridge 網路所不具備的功能。

程式碼範例:建立自訂 bridge 網路並測試 DNS 解析

# 建立自訂 bridge 網路
docker network create -d bridge --subnet 172.25.0.0/24 party

# 在自訂網路上啟動容器
docker run -itd --name sh1 --network party alpine:3.5 sh
docker run -itd --name sh2 --network party alpine:3.5 sh

# 測試 DNS 解析
docker exec -it sh2 ping sh1

內容解密:

  1. docker network create:建立新的 Docker 網路。
  2. -d bridge:指定網路驅動程式為 bridge。
  3. --subnet:定義網路的子網路範圍。
  4. --network party:將容器連線到自訂的 party 網路。

Overlay 網路模式:跨主機容器通訊

overlay 網路驅動程式讓 Docker Swarm 中的容器能夠跨多個主機進行通訊。建立 overlay 網路時,需要在 manager 節點上執行,並可透過 --attachable 引數允許非 Swarm 服務的容器連線。

程式碼範例:建立 overlay 網路

docker network create \
  --driver overlay \
  --subnet 10.0.0.0/20 \
  --attachable \
  party-swarm

內容解密:

  1. --driver overlay:指定使用 overlay 網路驅動程式。
  2. --subnet 10.0.0.0/20:定義一個較大的 IP 範圍(4096 個 IP 位址)。
  3. --attachable:允許非 Swarm 服務的容器連線到該網路。

VII. 連線埠繫結 - 透過連線埠繫結匯出服務

在 Docker Swarm 中,當我們建立服務並將其連線到自定義的網路時,容器之間可以互相通訊,而無需將服務連線埠暴露給外部網路。讓我們探討 Docker 的連線埠繫結機制,以及如何安全地將服務暴露給外部存取。

Docker 網路架構

首先,我們在兩個不同的 Swarm 節點(swarm2 和 swarm3)上啟動了兩個 Alpine 容器,分別命名為 sh1 和 sh2,並將它們連線到名為 party-swarm 的網路。

# ssh swarm2 docker run -itd --name sh1 --network party-swarm alpine:3.5 sh
6367bce89b0e255214456f4063c29b21bd8e573f86dc1c15ee0d5c6ce7114b99

# ssh swarm3 docker run -itd --name sh2 --network party-swarm alpine:3.5 sh
d84267c6b277216d0a4363fc2a731d56af5b480b028a6816d68941eda13c3d41

內容解密:

  1. 我們使用 docker run 命令在 swarm2 和 swarm3 上分別啟動了兩個容器 sh1 和 sh2。
  2. --name 引數為容器命名,而 --network party-swarm 將容器連線到 party-swarm 網路。
  3. alpine:3.5 是使用的 Docker 映像檔版本,而 sh 是容器啟動後執行的命令。

接著,我們使用 nslookup 命令檢查了容器 sh1 和 sh2 的 DNS 解析結果,確認它們在 party-swarm 網路中能夠被正確解析。

# docker run -it --rm --network party-swarm alpine:3.5 nslookup sh1
Name: sh1
Address 1: 10.0.0.8 sh1.party-swarm

# docker run -it --rm --network party-swarm alpine:3.5 nslookup sh2
Name: sh2
Address 1: 10.0.0.9 sh2.party-swarm

內容解密:

  1. 我們啟動了臨時的 Alpine 容器,並將其連線到 party-swarm 網路,以執行 nslookup 命令。
  2. nslookup 命令用於查詢容器 sh1 和 sh2 的 IP 位址,結果顯示它們的 IP 位址分別為 10.0.0.8 和 10.0.0.9。

此外,每個容器實際上是連線到兩個網路:party-swarm 網路和另一個系統網路 docker_gwbridge。這個額外的網路是 Docker Swarm 自動建立的,用於提供叢集外部的連線。

# ssh swarm2 docker exec sh1 ip addr | grep global
inet 10.0.0.8/20 scope global eth0
inet 172.18.0.5/16 scope global eth1

內容解密:

  1. 我們使用 docker exec 命令進入容器 sh1,並執行 ip addr 命令檢視其網路介面組態。
  2. 結果顯示容器有兩個網路介面:eth0 連線到 party-swarm 網路(IP 位址為 10.0.0.8),而 eth1 連線到 docker_gwbridge 網路(IP 位址為 172.18.0.5)。

主機網路模式

當我們以主機網路模式執行服務時,容器可以直接存取主機的網路堆積疊,這意味著容器可以控制主機網路介面的建立、修改和刪除。這種模式對於某些特定的應用程式(如 VPN 客戶端或網路監控工具)非常有用。

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 主機網路模式

rectangle "存取" as node1
rectangle "控制" as node2

node1 --> node2

@enduml

此圖示說明瞭容器在主機網路模式下與主機網路堆積疊的關係。

內容解密:

  1. 圖表展示了容器如何直接存取主機的網路堆積疊。
  2. 這種模式下,容器具備對主機網路介面的控制權。

然而,這種模式也帶來了安全風險,因為容器的網路隔離被關閉,任何在容器內監聽的連線埠都會被公開暴露。

暴露服務連線埠

建議的做法是將容器連線到自定義的橋接或覆寫網路,並根據需要使用 -p 選項暴露個別連線埠。

-p=[] : Publish a container's port or a range of ports to the host

格式如下:

ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort

例如,若應用程式監聽在連線埠 3000,但我們想對外暴露連線埠 80,可以使用 -p 80:3000

內容解密:

  1. -p 選項用於將容器的連線埠對映到主機的連線埠。
  2. 可以指定單一連線埠或連線埠範圍進行對映。
  3. 這種方式使得應用程式可以靈活地暴露服務,而無需修改其預設監聽連線埠。

對於遵循 Twelve-Factor App(12FA)原則的應用程式,應該從環境變數中讀取監聽的連線埠,但可以提供一個合理的預設值。在生產環境中,通常需要使用反向代理伺服器來監聽標準的 HTTP 連線埠(80 或 443),並將流量轉發到後端服務。

總之,Docker 的連線埠繫結機制提供了靈活的方式來暴露服務,同時也需要注意安全性和遵循最佳實踐。

除錯 Docker 網路連線問題

在除錯網路連線問題時,通常會檢查幾個關鍵點。當遇到問題時,我會遵循特定的步驟來排查。

使用 netstat 檢查服務埠

在 Debian 系統中,可以使用 net-tools 套件中的 netstat 工具來檢查服務是否正在監聽特定的埠。例如,若要檢視 Docker 應用程式繫結的內部服務埠,可以執行以下命令:

docker exec -it apt-cacher netstat -pan

輸出範例:

Active Internet connections (servers and established)
Proto Local Address           Foreign Address         State       PID/Program name
tcp   127.0.0.11:37391        0.0.0.0:*               LISTEN      -
tcp   0.0.0.0:3142            0.0.0.0:*               LISTEN      7/apt-cacher-ng
tcp6  :::3142                 :::*                    LISTEN      7/apt-cacher-ng
udp   127.0.0.11:32837        0.0.0.0:*                           -

內容解密:

  • docker exec -it apt-cacher netstat -pan:在名為 apt-cacher 的 Docker 容器中執行 netstat -pan 命令,以顯示所有監聽和已建立的連線。
  • Proto:顯示協定型別(TCP 或 UDP)。
  • Local Address:顯示本地地址和埠。
  • Foreign Address:顯示遠端地址和埠。
  • State:顯示連線的狀態,如 LISTEN 表示正在監聽。
  • PID/Program name:顯示相關程式的 PID 和名稱。

若要僅檢視正在監聽的埠,可以附加 | grep LISTEN 到命令中。在上述範例中,apt-cacher-ng 正在 IPv4 和 IPv6 的 3142 埠上監聽。

Docker 內部 DNS 解析

Docker 的內部 DNS 服務執行在 127.0.0.11。可以透過以下命令確認:

docker exec -it apt-cacher cat /etc/resolv.conf

輸出範例:

nameserver 127.0.0.11
options ndots:0

內容解密:

  • nameserver 127.0.0.11:指定 Docker 內部 DNS 伺服器的地址。
  • options ndots:0:設定 DNS 解析的相關選項。

除錯工具介紹

Linux 中有多種工具可用於除錯網路連線和程式執行,包括:

  • telnetnetcat:用於開啟到特定 IP 或主機的原始連線。
  • curlwget:用於傳送 HTTP 請求。
  • netcatss:用於列出開啟的連線和監聽的通訊端。
  • lsof:用於列出程式開啟的檔案。
  • strace:用於列印程式的系統呼叫軌跡。

使用 strace 除錯網路延遲問題

曾經遇到過連線到 Docker 中執行的資料函式庫例項時出現延遲的問題。使用 strace 可以捕捉到系統呼叫的詳細資訊,幫助定位問題。

telnet db1 3306 | grep socket

輸出範例中包含:

socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3

內容解密:

  • socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3:建立了一個 UDP 通訊端,用於 DNS 解析。

進一步分析發現,問題是由於 Docker 容器內的 /etc/resolv.conf 檔案中殘留了過時的 DNS 組態,導致了 DNS 解析延遲。重啟 Docker 服務後,問題得到解決。

重啟 Docker 服務以解決 DNS 解析問題

重啟 Docker 服務可以更新容器內的 DNS 組態:

service docker restart

網路監控工具推薦

為了預防類別似問題,設定網路監控是非常重要的。推薦使用以下工具:

  • Grafana:用於資料視覺化和監控。
  • Netdata:提供實時系統和應用監控。
  • Prometheus:用於系統和服務監控及報警。

這些工具可以幫助及時發現和解決網路連線問題。