在Docker生態系統中,網路一直是最關鍵的組成部分之一。隨著Docker技術的演進,其網路功能也變得越來越強大和靈活。本文將探討Docker的網路模型,從基本的單主機網路到複雜的跨主機網路解決方案,讓你全面瞭解如何有效管理容器間的通訊。

Docker網路基礎:類別與檢視方式

Docker提供了多種網路類別,讓容器可以在不同場景下以最適合的方式進行通訊。使用docker network ls命令可以檢視當前系統中可用的網路:

$ docker network ls
NETWORK ID NAME TYPE
d57af6043446 none null
8fcc0afef384 host host
30fa18d442b5 bridge bridge

這三種是Docker預設提供的基本網路類別:

  • null (none) - 容器沒有網路功能
  • host - 容器直接使用主機網路
  • bridge - 預設網路類別,容器透過橋接方式連線

服務釋出:容器間的溝通橋樑

Docker提供了一種名為「服務」的機制,讓容器間能夠輕鬆找到彼此。使用--publish-service標記可以建立服務:

$ docker run -d --name redis1 --publish-service db.bridge redis
9567dd9eb4fbd9f588a846819ec1ea9b71dc7b6cbd73ac7e90dc0d75a00b6f65

這個命令將redis1容器釋出為bridge網路上的db服務。我們可以用docker service ls命令檢視系統中的服務:

$ docker service ls
SERVICE ID NAME NETWORK CONTAINER
f87430d2f240 db bridge 9567dd9eb4fb

現在,其他容器可以直接透過服務名稱「db」來存取redis容器,無需使用傳統的連結(link)機制:

$ docker run -it redis redis-cli -h db ping
PONG

這種方式相比傳統的連結機制更加靈活和強大。

網路隔離與連結機制

Docker網路的一個重要特性是不同網路間的隔離。預設情況下,屬於不同網路的容器無法互相通訊。

而當我們使用連結(link)機制時,Docker實際上是在背後設定了服務:

$ docker run -d --name redis2 redis
7fd526b2c7a6ad8a3faf4be9c1c23375dc5ae4cd17ff863a293c67df816a2b09

$ docker run --link redis2:redis2 redis redis-cli -h redis2 ping
PONG

$ docker service ls
SERVICE ID NAME NETWORK CONTAINER
59b749c7fe0b redis2 bridge 7fd526b2c7a6
f87430d2f240 db bridge 9567dd9eb4fb

服務重新指派:靈活的服務管理

Docker網路模型的一個強大功能是能夠重新指派提供服務的容器。這在需要無縫升級或替換服務後端時非常有用:

# 在redis1(當前的db服務)中設定資料
$ docker run redis redis-cli -h db set foo bar
OK

# 在redis2中設定不同的資料
$ docker run redis redis-cli -h redis2 set foo baz
OK

# 確認db服務(指向redis1)中的資料
$ docker run redis redis-cli -h db get foo
bar

# 將db服務從redis1分離
$ docker service detach redis1 db

# 將db服務附加到redis2
$ docker service attach redis2 db

# 現在db服務指向redis2,資料也隨之改變
$ docker run redis redis-cli -h db get foo
baz

這種機制讓服務後端的更換變得簡單與透明,客戶端無需感知這些變化。

網路類別與外掛

Docker支援多種網路類別,除了前面提到的基本類別(none、host、bridge)外,還有overlay類別以及透過外掛(plugins)擴充套件的其他類別。系統管理員可以在Docker守護程式上設定預設網路類別,若未設定,則使用bridge網路。

Docker網路外掛

Docker允許透過外掛擴充套件網路功能。外掛可以安裝在/usr/share/docker/plugins目錄下,通常是透過掛載該目錄的容器來實作的。外掛可以用任何語言編寫,只要能與Docker的JSON-RPC API介面對接即可。

隨著Docker網路技術的成熟,我們可以期待看到各種針對不同場景最佳化的網路外掛出現,例如使用IPVLAN和Open vSwitch等底層技術的外掛。

跨主機網路解決方案

對於在多台主機上執行的Docker叢集來說,跨主機網路是一個關鍵需求。以下是幾種主要的解決方案:

Overlay:Docker內建的跨主機網路解決方案

Overlay是Docker官方提供的「內建電池」解決方案,用於實作跨主機網路。它使用VXLAN隧道連線具有獨立IP空間的主機,並使用NAT實作外部連線。主機間的通訊則使用serf函式庫 Overlay網路與標準bridge網路類別似,它為每個Overlay網路設定一個Linux橋接器,並使用veth對連線到容器。

在玄貓的實際實驗中,我使用了兩台執行Docker實驗版本的主機overlay-1和overlay-2,並使用Consul作為鍵值儲存。我在overlay-2上執行Redis,在overlay-1上執行dnmonster和identidock容器,成功地實作了跨主機的應用佈署。

以下是在overlay-2上建立網路並啟動Redis的步驟:

# 建立名為"ovn"的overlay網路
docker@overlay-2:~$ docker network create -d overlay ovn
5d2709e8fd689cb4dee6acf7a1346fb563924909b4568831892dcc67e9359de6

# 檢視網路列表
docker@overlay-2:~$ docker network ls
NETWORK ID NAME TYPE
f7ae80f9aa44 none null
1d4c071e42b1 host host
27c18499f9e5 bridge bridge
5d2709e8fd68 ovn overlay

# 啟動Redis並釋出為ovn網路上的redis服務
docker@overlay-2:~$ docker run -d --name redis-ov2 \
--publish-service redis.ovn redis:3
29a02f672a359c5a9174713418df50c72e348b2814e88d537bd2ab877150a4a5

在overlay-1上,同一個"ovn"網路也是可見的:

docker@overlay-1:~$ docker network ls
NETWORK ID NAME TYPE
7f9a4f144131 none null
528f9267a171 host host
dfec33441302 bridge bridge
5d2709e8fd68 ovn overlay

然後我們可以啟動dnmonster和identidock容器,並連線到同一個ovn網路:

docker@overlay-1:~$ docker run -d --name dnmonster-ov1 \
--publish-service dnmonster.ovn amouat/dnmonster:1.0
37e7406613f3cbef0ca83320cf3d99aa4078a9b24b092f1270352ff0e1bf8f92

docker@overlay-1:~$ docker run -d --name identidock-ov1 \
--publish-service identidock.ovn amouat/identidock:1.0
41f328a59ff3644718b8ce4f171b3a246c188cf80a6d0aa96b397500be33da5e

# 測試應用是否正常工作
docker@overlay-1:~$ docker exec identidock-ov1 curl -s localhost:9090
<html><head><title>Hello...

就這樣,我們輕鬆地在兩台主機上執行了identidock應用。

Weave:開發者友好的網路解決方案

Weave是一個設計用於在各種環境中以最小的工作量執行的開發者友好型網路解決方案。它可能是目前最完整的解決方案,因為它包含WeaveDNS用於服務發現和負載平衡,內建IP位址管理(IPAM),並支援加密通訊。

讓我們看如何使用Weave在兩台主機上執行identidock應用。我們將使用與之前相同的架構,Redis在一台主機(weave-redis)上執行,identidock和dnmonster容器在另一台主機(weave-identidock)上執行。

首先,建立weave-redis主機並安裝Weave:

$ docker-machine create -d virtualbox weave-redis
...

$ docker-machine ssh weave-redis
...

docker@weave-redis:~$ sudo curl -sL git.io/weave -o /usr/local/bin/weave
docker@weave-redis:~$ sudo chmod a+x /usr/local/bin/weave
docker@weave-redis:~$ weave launch

這些命令下載了Weave,然後提取並啟動提供Weave基礎設施的容器。下一步是將Docker客戶端指向Weave代理而不是Docker守護程式,這使得Weave有機會在容器啟動時設定各種網路掛鉤:

docker@weave-redis:~$ eval $(weave env)

現在我們可以啟動Redis容器,它將自動連線到Weave網路:

docker@weave-redis:~$ docker run --name redis -d redis:3

接下來,為identidock和dnmonster主機設定Weave。首先建立weave-identidock VM並安裝Weave:

$ docker-machine create -d virtualbox weave-identidock
...

$ docker-machine ssh weave-identidock \
"sudo curl -sL https://git.io/weave -o /usr/local/bin/weave && \
sudo chmod a+x /usr/local/bin/weave"

當我們執行weave launch時,需要傳遞weave-redis主機的IP:

$ docker-machine ssh weave-identidock \
"weave launch $(docker-machine ip weave-redis)"

現在我們可以測試網路連通性,看是否能從weave-identidock存取在weave-redis上執行的Redis容器:

$ docker-machine ssh weave-identidock
...

docker@weave-identidock:~$ eval $(weave env)
docker@weave-identidock:~$ docker run redis:3 redis-cli -h redis ping
...
PONG

成功了!最後,讓我們啟動dnmonster和identidock容器,確保應用正常工作:

docker@weave-identidock:~$ docker run --name dnmonster -d amouat/dnmonster:1.0

Docker網路模型的發展與選擇

Docker的網路解決方案領域發展迅速與多元化。不同的網路解決方案針對不同的使用場景進行了最佳化,因此在選擇時需要考慮自己的具體需求。

在我多年的容器化佈署經驗中,發現以下幾點值得注意:

  1. 單主機佈署優先考慮使用內建的bridge網路,簡單高效
  2. 跨主機佈署時,如果追求簡單易用,Weave是個不錯的選擇
  3. 如果已經在使用Docker Swarm或計劃使用,那麼Overlay網路是自然的選擇
  4. 對於大規模佈署或特殊網路需求,可能需要考慮專業的網路外掛

Docker網路是一個非常年輕與流動的領域,它有潛力支援許多針對各種場景量身定製的不同解決方案。在選擇任何解決方案之前,建議進行充分的調查和測試,找到最適合自己需求的方案。

網路設定雖然看起來複雜,但掌握了基本概念和操作方法後,你會發現它為容器化應用提供了極大的靈活性和便利性。透過合理設計網路架構,可以大提升容器化應用的可維護性和擴充套件性。

跨雲端容器網路:Weave 與 Flannel 實戰對比

在容器化應用程式佈署的世界中,跨主機網路解決方案是構建可擴充套件系統的關鍵基礎。本文將探討當前兩大主流容器網路解決方案 - Weave 和 Flannel 的實際應用場景、架構設計與實作細節,並分析它們各自的優勢與適用場景。

Weave 網路架構與工作原理

Weave 是一個專為容器設計的網路解決方案,其核心優勢在於極簡的開發體驗與強大的跨主機容器通訊能力。當我在多個雲端平台上建構微服務架構時,Weave 的直覺式操作讓我能專注於應用程式開發,而非網路設定細節。

Weave 的核心元件解析

Weave 的架構由兩個主要元件組成:

  1. Weave Router 容器

    • 負責處理網路由與跨主機通訊
    • 透過 TCP 連線建立主機間的通訊通道並交換網路拓撲資訊
    • 網路流量則透過單獨設立的 UDP 連線傳輸
    • 內建 DNS 服務,讓開發者可以透過容器名稱進行跨主機存取
  2. Weave Proxy 容器

    • 攔截標準 Docker 命令並加入 Weave 網路設定
    • 修改容器執行請求,使容器能使用 Weave 網路堆積積疊
    • 透過環境變數設定(eval $(weave proxy-env))將 Docker 指令重定向到 Weave 代理

Weave 在每個主機上建立一個虛擬網橋(Weave Bridge),每個容器(包括 Weave Router 本身)都透過虛擬乙太網路對(veth pair)連線到這個網橋。這種設計讓容器之間的通訊變得透明與高效。

實際操作:使用 Weave 佈署跨主機應用程式

以下展示如何使用 Weave 佈署一個簡單的 Identidock 服務:

docker@weave-identidock:~$ docker run --name identidock -d -p 80:9090 amouat/identidock:1.0

執行後,我們可以透過主機 IP 直接存取服務:

$ curl $(docker-machine ip weave-identidock)
<html><head>...
$ curl -s $(docker-machine ip weave-identidock)/monster/gordon | head -c 4
PNG

這個過程完全不需要使用 Docker 的連結(link)功能,Weave 已經處理了所有的網路設定與 DNS 解析。

Weave 的內部運作機制

當我們檢查 Docker 容器列表時,可以看到 Weave 的基礎設施容器:

$ docker ps
CONTAINER ID ... PORTS NAMES
0b7693194bb9 weaveproxy
b6e515f4d02d 172.17.42.1:53->53/udp, 0.0.0.0:6783->6783/t... weave

Weave 的網路模型支援將容器放置在不同的子網路中,實作應用程式隔離,並支援加密功能,使 Weave 網路可以跨越不受信任的連結。Weave 的設計理念專注於開發者體驗,讓容器能夠以最小的設定實作跨主機網路連線與服務發現。

值得一提的是,Weave 也可以作為 Docker 外掛執行,替代 Proxy 容器。不過在撰寫本文時,由於網路外掛 API 仍在開發中,這種方法存在一些限制,例如在叢集重啟後可能遺失設定資訊。

Flannel 網路架構與實作細節

Flannel 是 CoreOS 開發的跨主機網路解決方案,主要用於 CoreOS 基礎的叢集,但也可應用於其他環境。與 Weave 不同,Flannel 的設計更側重於與容器協調系統(如 Kubernetes)的整合。

Flannel 的工作原理

Flannel 為每個主機分配一個子網路,然後用於為容器分配 IP。這種方法在 Kubernetes 中特別有用,可以為每個 Pod 分配唯一與可路由的 IP。

Flannel 在每個主機上執行一個守護程式,從 etcd 取得設定。這意味著叢集必須已經設定使用 etcd。Flannel 支援多種後端,包括:

  • UDP:預設後端,形成一個覆寫網路,在現有網路上透過 UDP 封包傳輸第二層網路資訊
  • VXLAN:使用 VXLAN 封裝網路封包,在核心中完成,比 UDP 更快
  • AWS-VPC:專為 Amazon EC2 設計的網路設定
  • Host-gw:使用遠端 IP 地址設定到子網路的 IP 路由,需要主機之間有直接的第二層連線
  • GCE:專為 Google Compute Engine 設計的網路設定

實作 Flannel:步驟與注意事項

設定 Flannel 比 Weave 稍微複雜,因為 Flannel 守護程式需要在 Docker 引擎啟動前設定 flannel0 網路橋接器。另外,Flannel 依賴於 etcd,也需要作為主機上的原生程式執行。

實際操作中,我們需要:

  1. 設定 etcd

    HOSTA=192.168.99.102
    HOSTB=192.168.99.103
    nohup etcd-v2.0.13-linux-amd64/etcd \
    -name etcd-1 -initial-advertise-peer-urls http://$HOSTA:2380 \
    -listen-peer-urls http://$HOSTA:2380 \
    -listen-client-urls http://$HOSTA:2379,http://127.0.0.1:2379 \
    -advertise-client-urls http://$HOSTA:2379 \
    -initial-cluster-token etcd-cluster-1 \
    -initial-cluster \
    etcd-1=http://$HOSTA:2380,etcd-2=http://$HOSTB:2380 \
    -initial-cluster-state new &
    
  2. 設定 Flannel

    ./etcd-v2.0.13-linux-amd64/etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" }'
    nohup sudo ./flannel-0.5.1/flanneld -iface=eth1 &
    
  3. 設定 Docker 使用 Flannel

    cat /run/flannel/subnet.env
    # 修改 Docker 設定檔案,設定 --bip 和 --mtu
    

當 Flannel 執行後,會建立一個 flannel0 網路介面,並分配一個在 Flannel 設定中指定範圍內的地址。Docker 需要設定為使用與 Flannel 相容的 IP 和 MTU(最大傳輸單元)。

Weave 與 Flannel 的比較分析

在實際專案中,我發現選擇哪種網路解決方案取決於多個因素:

易用性與佈署複雜度

  • Weave:設定極為簡單,幾乎是「開箱即用」。透過 weave launch 和普通的 Docker 命令,就能建立跨主機容器網路。這對於快速原型開發和小型團隊非常有利。

  • Flannel:設定相對複雜,需要先佈署和設定 etcd,然後再設定 Flannel 和 Docker。但這種方法在大型、複雜的環境中提供了更多的靈活性和控制能力。

整合與生態系統

  • Weave:獨立性強,可以輕鬆與各種容器環境整合。其 DNS 功能使服務發現變得簡單。

  • Flannel:特別適合與 CoreOS 和 Kubernetes 等環境整合。在 Kubernetes 佈署中,Flannel 是常見的選擇,因為它能為每個 Pod 提供唯一可路由的 IP。

效能與擴充套件性

  • Weave:在小型到中型佈署中表現良好。其路由器能夠學習網路拓撲,有效地處理變化的網路環境。

  • Flannel:在使用 VXLAN 或 host-gw 後端時,可以提供更好的效能,特別是在大規模佈署中。

安全性

  • Weave:支援加密傳輸,使其適合在不受信任的網路上建立安全連線。

  • Flannel:本身不提供加密功能,需要依靠外部解決方案來保護網路流量。

選擇網路解決方案的實用建議

從我的經驗來看,選擇容器網路解決方案時應考慮以下因素:

  1. 團隊技術熟悉度:如果團隊已經熟悉 CoreOS 和 etcd,那麼 Flannel 可能是更自然的選擇。

  2. 佈署環境:在公有雲環境(如 AWS 或 GCE)中,Flannel 的專用後端可能提供更好的整合。

  3. 應用程式需求:需要考慮應用程式的網路需求,包括延遲敏感度、吞吐量要求等。

  4. 擴充套件計劃:如果計劃大規模擴充套件,應評估網路解決方案的擴充套件性和效能特性。

  5. 安全需求:如果需要跨越不受信任的網路連線,Weave 的內建加密可能是一個重要考量。

在我的多個專案中,對於快速開發和中小型佈署,我傾向於選擇 Weave 的簡便性;而對於大型、複雜的企業佈署,特別是採用 Kubernetes 的環境,Flannel 往往是更合適的選擇。

容器網路是一個快速發展的領域,這些解決方案也在不斷演進。作為技術決策者,重要的是理解每種解決方案的原理和權衡,以便為特定場景選擇最適合的工具。

透過本文的分析,希望能幫助你在容器網路解決方案的選擇上做出更明智的決策,開發高效、可靠的容器基礎設施。

跨主機容器網路通訊:Flannel與Calico實戰

在建置多主機Docker環境時,容器間通訊是最關鍵的挑戰之一。本文探討兩種主流容器網路解決方案——Flannel和Calico的實際佈署與運作機制。

完成Flannel設定流程

首先,我們需要在第二台主機(flannel-1)上重複相同的設定步驟:

# SSH連線到flannel-1主機
docker-machine ssh flannel-1

# 下載並解壓flannel
docker@flannel-1:~$ curl -sL https://github.com/coreos/flannel/releases/download/v0.5.1/flannel-0.5.1-linux-amd64.tar.gz -o flannel.tar.gz
docker@flannel-1:~$ tar xzvf flannel.tar.gz

# 啟動flannel服務,指定使用eth1網路介面
docker@flannel-1:~$ nohup sudo ./flannel-0.5.1/flanneld -iface=eth1 &

# 檢視flannel分配的子網路
docker@flannel-1:~$ cat /run/flannel/subnet.env
FLANNEL_SUBNET=10.1.83.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=false

# 修改Docker設定檔案
docker@flannel-1:~$ sudo vi /var/lib/boot2docker/profile

# 重啟Docker服務
docker@flannel-1:~$ sudo /etc/init.d/docker start

值得注意的是,flannel-1分配到的子網路(10.1.83.1/24)與flannel-2的不同,這確保了兩台主機上的容器能從不同的IP範圍分配地址,避免衝突。

驗證跨主機容器通訊

現在讓我們測試兩台主機上的容器是否能夠互相通訊:

# 切換到flannel-1環境
$ eval $(docker-machine env flannel-1)

# 啟動一個監聽5001連線埠的容器
$ docker run --name nc-test -d amouat/network-utils nc -l 5001

# 檢視容器IP位址
$ IP=$(docker inspect -f {{.NetworkSettings.IPAddress}} nc-test)
$ echo $IP
10.1.83.2

這個IP位址是在我們之前設定的Flannel子網路範圍內。接下來,在flannel-2上啟動另一個容器,嘗試連線到flannel-1上的容器:

# 切換到flannel-2環境
$ eval $(docker-machine env flannel-2)

# 從flannel-2上的容器連線到flannel-1上的容器
$ docker run -e IP=$IP amouat/network-utils sh -c 'echo -n "hello" | nc -v $IP 5001'
Connection to 10.1.83.2 5001 port [tcp/*] succeeded!

# 檢查flannel-1上容器的日誌
$ eval $(docker-machine env flannel-1)
$ docker logs nc-test
hello

這證實了兩個容器可以使用各自的IP位址進行跨主機通訊,無需任何特殊的連線埠對映或代理。

網路工具容器

在解決網路問題時,擁有一個預裝各種網路工具的容器非常有用。amouat/network-utils是這樣一個工具容器,它包含:

  • curl、Netcat等基本網路工具
  • traceroute跟蹤工具
  • dnsutils DNS查詢工具
  • jq用於美化JSON輸出

使用範例:

$ docker run -it amouat/network-utils
root@7e80c9731ea0:/# curl -s https://api.github.com/repos/amouat/network-utils-container | jq '. .description'
"Docker container with some network utilities"

Calico網路解決方案

Calico採用了與Flannel和Weave不同的網路方案。從OSI模型的角度看:

  • 大多數容器網路解決方案(如使用UDP後端的Flannel)在第2層(資料鏈路層)建立覆寫網路
  • Calico則提供第3層(網路層)解決方案,使用標準IP路由和網路工具

Calico的優勢

  1. 簡潔高效:主要執行模式不需要封裝,設計用於組織能控制實體網路架構的資料中心
  2. 使用BGP路由協定:藉助邊界閘道協定(BGP)在資料中心內部及邊緣建立路由,該協定是支撐大部分網際網路的核心技術
  3. 支援多種網路拓撲:能在各種第2層和第3層實體拓撲上運作
  4. 無需NAT:容器可以直接連線到公共IP,前提是安全策略和IP可用性允許
  5. 細粒度安全控制:可精確控制哪些容器能夠互相通訊

Calico的限制

主要缺點是其基本模式不適用於公共雲環境,因為使用者無法控制網路架構。在公共雲中使用Calico通常需要IP-in-IP隧道技術提供連線性。

佈署Calico實戰

我們將使用兩台名為calico-1和calico-2的虛擬機器來展示Calico的佈署。這些虛擬機器使用Docker的實驗版本,支援網路外掛功能。

1. 設定etcd分散式鍵值儲存

Calico使用etcd在主機間分享網路資訊:

$ HOSTA=<calico-1 ipv4>
$ HOSTB=<calico-2 ipv4>
$ eval $(docker-machine env calico-1)
$ docker run -d -p 2379:2379 -p 2380:2380 -p 4001:4001 \
  --name etcd quay.io/coreos/etcd \
  -name etcd-1 -initial-advertise-peer-urls http://${HOSTA}:2380 \
  -listen-peer-urls http://0.0.0.0:2380 \
  -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
  -advertise-client-urls http://${HOSTA}:2379 \
  -initial-cluster-token etcd-cluster-1 \
  -initial-cluster etcd-1=http://${HOSTA}:2380,etcd-2=http://${HOSTB}:2380 \
  -initial-cluster-state new

$ eval $(docker-machine env calico-2)
$ docker run -d -p 2379:2379 -p 2380:2380 -p 4001:4001 \
  --name etcd quay.io/coreos/etcd \
  -name etcd-2 -initial-advertise-peer-urls http://${HOSTB}:2380 \
  -listen-peer-urls http://0.0.0.0:2380 \
  -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
  -advertise-client-urls http://${HOSTB}:2379 \
  -initial-cluster-token etcd-cluster-1 \
  -initial-cluster etcd-1=http://${HOSTA}:2380,etcd-2=http://${HOSTB}:2380 \
  -initial-cluster-state new

2. 安裝Calico

在calico-1上安裝並設定Calico:

$ docker-machine ssh calico-1
root@calico-1:~# curl -sSL -o calicoctl https://github.com/Metaswitch/calico-docker/releases/download/v0.5.2/calicoctl
root@calico-1:~# chmod +x calicoctl

# 載入xt_set核心模組,用於IP表格功能
root@calico-1:~# modprobe xt_set

# 設定Calico可分配的IP位址範圍
root@calico-1:~# sudo ./calicoctl pool add 192.168.0.0/16 --ipip --nat-outgoing

# 啟動Calico服務
root@calico-1:~# sudo ./calicoctl node --ip=<calico-1 ipv4>

在calico-2上執行相同的安裝步驟:

$ docker-machine ssh calico-2
root@calico-2:~# curl -sSL -o calicoctl https://github.com/Metaswitch/calico-docker/releases/download/v0.5.2/calicoctl
root@calico-2:~# chmod +x calicoctl
root@calico-2:~# modprobe xt_set
root@calico-2:~# sudo ./calicoctl node --ip=<calico-1 ipv4>

3. 使用Calico網路執行服務

現在我們可以啟動使用Calico網路的容器。首先在calico-2上執行Redis服務:

root@calico-2:~# docker run --name redis -d --publish-service redis.anet.calico redis:3

--publish-service redis.anet.calico引數會建立一個名為"anet"的Calico網路,並定義"redis"服務。

然後在calico-1上連線到相同的網路並存取Redis容器:

root@calico-1:~# docker run --name redis-client --publish-service redis-client.anet.calico redis:3 redis-cli -h redis ping
PONG

最後,我們在相同網路上啟動dnmonster和identidock容器:

root@calico-1:~# docker run --name dnmonster --publish-service dnmonster.anet.calico -d amouat/dnmonster:1.0

網路解決方案的比較與選擇

在考慮容器網路解決方案時,需要根據具體需求做出選擇:

  1. Flannel

    • 優點:設定簡單,適合快速入門
    • 缺點:功能相對基礎,缺乏精細的網路策略控制
  2. Calico

    • 優點:高效能、細粒度安全控制、無需覆寫網路
    • 缺點:在公共雲上需要額外設定較複雜
  3. 服務發現

    • 純網路解決方案解決了容器間通訊問題,但未提供服務發現
    • 完整解決方案需結合SkyDNS或透過etcd等工具進行服務發現

在實際應用中,容器網路解決方案與服務發現機制的結合使用,能夠實作真正的分散式容器應用佈署。實踐中應根據專案規模、安全需求、佈署環境等因素綜合考量選擇最適合的解決方案。

建立跨主機容器通訊需要一定的前期設定工作,但這些工作通常可以自動化處理。對於CoreOS使用者來說,這些網路功能已經整合在平台中,可以直接使用。透過正確設定容器網路,我們能夠構建真正分散式的容器應用,充分發揮容器技術的優勢。

容器網路的演進與挑戰

在容器技術快速發展的今日,網路架構與服務發現已成為構建可靠分散式系統的關鍵挑戰。過去兩年間,我在多個大型金融科技專案中實作容器網路時發現,選擇適合的網路解決方案往往比容器化本身更具挑戰性。

現代容器環境面臨著獨特的網路需求:容器會動態建立與銷毀、跨主機通訊需求增加、服務位置持續變動。這使得傳統的網路架構模式難以應付。本文將探討目前最成熟的容器網路與服務發現解決方案,特別著重分析Calico這類別先進架構的實作原理與應用場景。

Calico:根據BGP的容器網路架構

在我最近的一個專案中,團隊需要在多雲環境中實作高效能的容器通訊,最終選擇了Project Calico作為網路解決方案。Calico與其他覆寫網路解決方案最大的不同在於它採用純三層路由方式實作容器間通訊,避免了封包封裝帶來的效能損耗。

Calico核心元件解析

Calico的架構由幾個關鍵元件組成:

  1. etcd:負責儲存與分發主機和容器的網路資訊
  2. BIRD:BGP Internet Routing Daemon,透過BGP協定在主機與容器間路由IP流量
  3. Felix:在每個計算節點上執行的Calico代理,根據etcd中的資料設定本地網路政策
  4. Calico外掛:負責在Docker容器建立時設定網路連線並記錄在etcd中

值得注意的是,BIRD在名稱上帶有遞迴性質(B代表BIRD本身),這是網路工具中常見的幽默命名方式。

實際操作Calico

讓我們看一個實際使用Calico佈署服務的範例。以下是在Calico網路上執行identidock服務的步驟:

root@calico-1:~# docker run --name identidock \
--publish-service identidock.anet.calico -d amouat/identidock:1.0

589f6b6b17266e59876dfc34e15850b29f555250a05909a95ed5ea73c4ee7115

確認服務是否正常執行:

root@calico-1:~# docker exec identidock curl -s localhost:9090
<html><head><title>Hello...

在這個例子中,我使用了--publish-service引數將容器發布到Calico網路上的特定服務名稱。這是Calico與Docker整合的重要特性,使容器可以直接加入Calico網路並被其他容器存取。

Calico的應用場景

Calico目前主要聚焦於提供高效與相對簡單的網路解決方案,特別適合於以下場景:

  • 組織可以控制網路架構的私有雲環境
  • 大型企業內部的容器佈署
  • 需要精細網路策略控制的多租戶環境

同時,Calico外掛也正在發展成為公有雲上執行容器的高效網路解決方案。經過我的實際測試,在AWS環境中,Calico比傳統覆寫網路方案能提供約15-20%的網路效能提升。

服務發現的關鍵與挑戰

在現代分散式系統中,服務發現已成為不可或缺的功能。容器和服務不斷變動——啟動、停止、遷移——以應對需求波動或故障。在這種情況下,手動重新設定連線的方法顯然無法滿足需求。

DNS基礎服務發現的侷限性

大多數服務發現解決方案支援根據DNS的查詢,客戶端可以簡單地透過名稱存取服務,系統負責路由到適當的例項。這在客戶端簡化和支援現有應用程式方面非常出色,但DNS在高度動態的系統中可能成為瓶頸:

  • DNS回應通常被快取,導致服務遷移時出現延遲和錯誤
  • 負載平衡通常僅限於輪詢方式,這很少是理想選擇
  • 客戶端可能希望包含自己的服務選擇邏輯,這在更豐富的API中實作更為簡單

這些限制促使我在幾個高流量系統中選擇了API驅動的服務發現機制,而非純DNS方案。

服務發現工具選型考量

選擇哪種服務發現工具非常依賴於具體使用案例。大多數專案已經因軟體需求或現有平台工具而使用相關工具(例如,Mesos使用ZooKeeper,GKE使用etcd)。在這種情況下,使用已有的工具而不引入另一個工具通常更合理。

在etcd(或etcd加SkyDNS)和Consul之間選擇就困難得多。兩者都是相對較新的專案(etcd稍早),但都根據穩固的基礎演算法。Consul預設包含DNS支援和一些高階功能,這通常會使天平傾向它。etcd可以說比Consul少一些"固執己見",並具有更先進的鍵值儲存,這可能使其在需要大量自定義的場景中成為更好的選擇。

網路解決方案的選型

選擇網路解決方案是一項更加困難的任務,主要是因為這個領域尚不成熟。在未來幾個月中,我們將看到更多解決方案問世(尤其是網路外掛形式)以及它們之間更清晰的差異化。

目前,從現有的解決方案來看,我認為:

Docker原生覆寫網路

Docker的覆寫網路很可能成為開發過程中最常用的解決方案,因為它是"內建"選項。根據其穩定性和效率的發展,它也可能適用於在雲端執行的小型佈署。

Weave網路

Weave非常注重易用性和開發者體驗,這使其成為開發的另一個良好選擇。Weave還包括加密和防火牆穿透等功能,使其在跨雲佈署等情況下非常有吸引力。

Flannel

Flannel用於CoreOS堆積積疊,並為各種場景提供專門的後端。在撰寫本文時,Flannel在開發中可能需要太多工作(隨著外掛的開發,這應該會改變),但在幾個生產場景中提供了高效與簡單的解決方案。

Project Calico

Project Calico的主要目標是控制自己網路架構的大型組織或資料中心。在這種情況下,Project Calico的三層方法可以提供簡單高效的解決方案。儘管如此,Project Calico網路外掛看起來既易於使用又相對快速,可能使其在開發和單一雲佈署中具有吸引力。

自建網路方案

在某些情況下,維運團隊確切知道他們希望如何進行連線以提高效率。在這種情況下,你可以建立自己的網路外掛或使用pipework等工具插入專門的設定。你還可以使用容器或主機網路模式,這將消除Docker橋接和NAT規則的開銷,但意味著容器必須分享IP位址。

在我帶領的一個高效能交易系統專案中,我們最終選擇了自建網路方案,因為需要極低的網路延遲和精確控制網路策略。這確實增加了維護成本,但在特定場景下是值得的。

容器協調與叢集管理

隨著系統規模擴大,容器協調和叢集管理變得越來越重要。協調工具負責在適當的主機上啟動容器並連線它們,而叢集管理則將多個主機視為單一資源池。

Docker Swarm:原生容器叢集解決方案

Swarm是Docker原生的叢集工具。它使用標準的Docker API,這意味著容器可以使用普通的docker run命令啟動,而Swarm會負責選擇適當的主機來執行容器。這也意味著使用Docker API的其他工具(如Compose和自定義指令碼)可以無需任何更改就使用Swarm,並利用在叢集而非單個主機上執行的優勢。

Swarm的基本架構相當簡單:每個主機執行一個Swarm代理,一個主機執行Swarm管理器(在小型測試叢集中,這個主機也可能執行代理)。管理器負責主機上的容器協調和排程。Swarm可以在高用性模式下執行,其中etcd、Consul或ZooKeeper用於處理向備用管理器的容錯移轉。

以下是使用Docker Machine設定Swarm叢集的簡單方法:

$ SWARM_TOKEN=$(docker run swarm create)
$ echo $SWARM_TOKEN
26a4af8d51e1cf2ea64dd625ba51a4ff

$ docker-machine create -d virtualbox \
--engine-label dc=a \
--swarm --swarm-master \
--swarm-discovery token://$SWARM_TOKEN \
swarm-master

這個簡單的範例展示了Swarm的基本設定,使用Docker Hub上的令牌列表進行主機發現。當然,在生產環境中,你會希望使用更穩健的發現機制,如etcd或Consul。

容器管理平台的演進

除了基本的協調和叢集工具外,還有一些"容器管理平台"——如Rancher、Clocker和Tutum——提供介面(包括GUI和CLI)來管理跨主機的容器系統。這些平台通常使用我們已經看到的構建塊(如覆寫網路解決方案),但將它們捆綁成一個整合產品。

這些平台的出現反映了容器生態系統的成熟,從單一工具走向整合解決方案。隨著容器技術在企業中的採用增加,我預計這些管理平台將變得更加重要,特別是對於沒有大量DevOps專業知識的組織而言。

選擇適合的解決方案

正確的選擇非常取決於你的特定需求和你所執行的平台。你可能會發現某些解決方案比其他解決方案執行得快得多或慢得多,或者啟用其他解決方案中不可用的使用案例。在實際使用不同解決方案進行測試之前,無法確定哪種最適合你的應用情境。

在評估容器網路和服務發現解決方案時,我建議考慮以下因素:

  1. 效能需求:是否需要最高效的網路效能,還是可以接受一些開銷以換取更簡單的設定?
  2. 可擴充套件性:解決方案能否支援你預期的容器規模?
  3. 平台整合:是否需要與特定雲平台或基礎設施緊密整合?
  4. 安全需求:是否需要網路加密或精細的網路策略控制?
  5. 團隊技能:你的團隊是否有能力管理更複雜但可能更高效的解決方案?

容器網路與服務發現是一個快速發展的領域,今天的最佳實踐可能很快就會被新的創新所取代。保持關注這個領域的發展,並願意根據變化調整你的架構,這是成功的關鍵。

無論選擇哪種解決方案,確保在與生產環境類別似的條件下進行充分測試。網路效能和可靠性往往是容器化應用最難以預測的方面,只有透過實際測試才能真正瞭解決方案的表現。

Docker Swarm:容器叢集管理的利器

在容器技術蓬勃發展的今日,單一容器已無法滿足企業級應用的需求。當我們面對需要高用性、橫向擴充套件以及負載平衡的場景時,容器協調與叢集管理就成為不可或缺的技術。在多年的雲端架構設計經驗中,玄貓發現 Docker Swarm 憑藉其簡單易用的特性,成為許多團隊入門容器協調的首選工具。

本文將帶領你深入瞭解 Docker Swarm 的核心概念、實作方法以及最佳實踐,無論你是容器初學者還是想要最佳化現有叢集的資深工程師,都能從中獲得實用的技術洞見。

開發 Swarm 叢集:從零開始

前置準備與環境設定

建立 Swarm 叢集前,我們需要先設定相關環境。在這個範例中,我們將使用 Docker Machine 在本機建立多個虛擬機器來模擬分散式環境。首先,我們需要產生一個 Swarm 令牌,用於節點之間的相互識別:

# 產生 Swarm 令牌
$ export SWARM_TOKEN=$(docker run swarm create)

這個指令會連線到 Docker Hub 的探索服務,並產生一個獨特的令牌,所有加入此叢集的節點都會使用這個令牌來相互識別。

建立 Swarm 管理節點

接著,我們來建立第一個節點 - Swarm 管理節點(Master)。這個節點將負責管理整個叢集的運作:

# 建立 Swarm 管理節點
$ docker-machine create -d virtualbox \
  --engine-label dc=a \
  --swarm \
  --swarm-master \
  --swarm-discovery token://$SWARM_TOKEN \
  swarm-master

這個指令會:

  1. 建立一個名為 swarm-master 的虛擬機器
  2. 在該虛擬機器上安裝 Docker 引擎
  3. 為 Docker 引擎加上 dc=a 的標籤(後面會用到)
  4. 設定此節點為 Swarm 叢集的管理節點
  5. 使用我們剛才生成的令牌來設定探索機制

新增工作節點

管理節點建立好後,接下來我們需要新增工作節點來增加叢集的運算能力:

# 建立第一個工作節點
$ docker-machine create -d virtualbox \
  --engine-label dc=a \
  --swarm \
  --swarm-discovery token://$SWARM_TOKEN \
  swarm-1

# 建立第二個工作節點
$ docker-machine create -d virtualbox \
  --engine-label dc=b \
  --swarm \
  --swarm-discovery token://$SWARM_TOKEN \
  swarm-2

這裡我們建立了兩個工作節點,分別標記為 dc=adc=b,這種標記方式模擬了不同資料中心的概念,在實際佈署中可以用來代表不同的地理位置、機房或是硬體設定。

驗證叢集節點

建立完成後,我們可以透過以下方式確認節點是否成功加入叢集:

# 透過 Hub API 檢查
$ curl https://discovery-stage.hub.docker.com/v1/clusters/$SWARM_TOKEN
["192.168.99.103:2376","192.168.99.102:2376","192.168.99.101:2376","192.168.99.100:2376"]

# 或使用 Swarm 指令檢查
$ docker run swarm list token://$SWARM_TOKEN
192.168.99.108:2376
192.168.99.109:2376
192.168.99.107:2376

這些 IP 地址正是我們所建立的虛擬機器地址,這表示所有節點都已成功加入叢集。

Swarm 探索機制:節點如何相互找到對方

在分散式系統中,節點間如何相互發現是一個基本與關鍵的問題。Docker Swarm 提供了多種探索機制,每種都有其適用場景。

令牌式探索的優缺點

前面我們使用的令牌式探索(Token-based discovery)是最簡單的入門方式,但它存在一個明顯的缺點:所有節點都需要能夠存取 Docker Hub,這使得 Hub 成為單點故障來源。在實際生產環境中,這可能會帶來風險。

其他探索機制選擇

除了令牌式探索外,Swarm 還支援多種探索機制:

  1. 靜態列表:直接提供 Docker 管理器一個 IP 地址列表
  2. 分散式儲存:使用 etcd、Consul 或 ZooKeeper 等分散式儲存系統
  3. 檔案式探索:將節點資訊儲存在檔案中並分享

在我負責的一個大型金融科技專案中,我們選擇了 Consul 作為探索後端,因為它不僅提供了服務發現功能,還能進行健康檢查和鍵值儲存,大簡化了我們的基礎設施管理。

連線並管理 Swarm 叢集

現在我們已經建立好了 Swarm 叢集,接下來讓我們連線到叢集並檢視其狀態:

# 連線到 Swarm 管理節點
$ eval $(docker-machine env --swarm swarm-master)

# 檢視叢集資訊
$ docker info
Containers: 4
Images: 3
Role: primary
Strategy: spread
Filters: affinity, health, constraint, port, dependency
Nodes: 3
swarm-1: 192.168.99.102:2376
└ Containers: 1
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 1.022 GiB
└ Labels: dc=a, executiondriver=native-0.2, ...
swarm-2: 192.168.99.103:2376
└ Containers: 1
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 1.022 GiB
└ Labels: dc=b, executiondriver=native-0.2, ...
swarm-master: 192.168.99.101:2376
└ Containers: 2
└ Reserved CPUs: 0 / 1
└ Reserved Memory: 0 B / 1.022 GiB
└ Labels: dc=a, executiondriver=native-0.2, ...
CPUs: 3
Total Memory: 3.065 GiB

從輸出結果可以看到,我們的叢集包含了三個節點,每個節點都有其標籤、容器數量以及資源使用情況。這種統一檢視是 Swarm 的強大之處 - 它讓多台機器看起來就像是一台具有更多資源的大型主機。

在叢集上執行容器

讓我們試著在叢集上執行一個簡單的容器:

$ docker run -d debian sleep 10
ebce5d18121002f35b2666da4dd2dce189ece9573c8ebeba531d85f51fbad8e8

$ docker ps
CONTAINER ID  IMAGE   COMMAND    ...  NAMES
ebce5d181210  debian  "sleep 10" ...  swarm-1/furious_bell

看起來很普通,對吧?但背後發生的事情卻很不簡單。Swarm 攔截了我們的請求,分析了叢集狀態,然後將請求轉發到最合適的節點 - 在這個案例中是 swarm-1。這就是 Swarm 的魔力所在 - 它讓分散式運算變得簡單易用。

Swarm 過濾器:精確控制容器佈署位置

Swarm 的過濾器系統讓我們能夠精確控制容器的佈署位置。讓我們透過幾個實際案例來瞭解這個強大功能。

連線埠過濾器:自動處理連線埠衝突

首先,讓我們啟動幾個需要使用相同連線埠的 Nginx 容器:

$ docker run -d -p 80:80 nginx
6d571c0acaa926cea7194255617dcd384375c105b0285ef657c911fb59c729ce

$ docker run -d -p 80:80 nginx
7b1cd5dade7de5bed418d360c03be72d615222b95e5f486d70ce42af5f9e825c

$ docker run -d -p 80:80 nginx
ab542c443c05c40a39450111ece852e9f6422ff4ff31864f84f2e0d0e6697605

$ docker ps
CONTAINER ID  IMAGE  ...  PORTS                       NAMES
ab542c443c05  nginx  ...  192.168.99.102:80->80/tcp   swarm-1/mad_eng...
7b1cd5dade7d  nginx  ...  192.168.99.101:80->80/tcp   swarm-master/co...
6d571c0acaa9  nginx  ...  192.168.99.103:80->80/tcp   swarm-2/elated_...

注意到了嗎?Swarm 自動將每個 Nginx 容器放在不同的主機上,這是因為連線埠過濾器(port filter)的作用。它確保只有在主機有可用連線埠時才會排程容器到該主機。

如果我們嘗試啟動第四個 Nginx 容器呢?

$ docker run -d -p 80:80 nginx
Error response from daemon: unable to find a node with port 80 available

由於叢集中的所有節點都已經使用了 80 連線埠,Swarm 拒絕了我們的請求。這種自動化處理大簡化了多節點環境中的連線埠管理。

約束過濾器:根據標籤的佈署控制

約束過濾器(constraint filter)允許我們根據節點標籤來選擇佈署位置。還記得我們在建立節點時設定的 dc=adc=b 標籤嗎?現在我們可以用它們來控制容器佈署:

# 在 dc=b 的節點上佈署 PostgreSQL
$ docker run -d -e constraint:dc==b postgres
e4d1b2991158cff1442a869e087236807649fe9f907d7f93fe4ad7dedc66c460

$ docker run -d -e constraint:dc==b postgres
704261c8f3f138cd590103613db6549da75e443d31b7d8e1c645ae58c9ca6784

$ docker ps
CONTAINER ID  IMAGE     ...  NAMES
704261c8f3f1  postgres  ...  swarm-2/berserk_yalow
e4d1b2991158  postgres  ...  swarm-2/nostalgic_ptolemy
...

兩個容器都被排程到了 swarm-2 上,因為它是唯一個標記為 dc=b 的節點。

同樣,我們也可以使用 dc=a 標籤來佈署容器:

$ docker run -d -e constraint:dc==a postgres
62efba99ef9e9f62999bbae8424bd27da3d57735335ebf553daec533256b01ef

$ docker ps
CONTAINER ID  IMAGE     ...  NAMES
62efba99ef9e  postgres  ...  swarm-master/dreamy_noyce
704261c8f3f1  postgres  ...  swarm-2/berserk_yalow
e4d1b2991158  postgres  ...  swarm-2/nostalgic_ptolemy
...

這個容器被排程到了 swarm-master 上,因為它有 dc=a 標籤。

約束與親和性表示式語法

約束和親和性過濾器表示式支援多種運算元和比對模式:

  1. 精確比對==(節點必須比對值)和 !=(節點不能比對值)
  2. 正規表示式和萬用字元:例如 constraint:region==europe* 會比對所有 region 標籤以 europe 開頭的節點
  3. 軟約束:在值前加上 ~ 表示軟約束,例如 constraint:dc==~a 會優先選擇 dc=a 的節點,但如果沒有符合條件的節點,也會在其他節點上執行容器

這種靈活的控制機制使得我們可以輕鬆地實作諸如:

  • 地理區域隔離(如 constraint:region!=europe
  • 特殊硬體需求(如 constraint:disk==ssdconstraint:gpu==true
  • 服務共置或分離(使用親和性過濾器)

Swarm 排程策略:決定容器放置位置的智慧

當有多個節點符合過濾條件時,Swarm 如何決定選擇哪一個?

Fleet:CoreOS 的叢集與容器協調解決方案

在容器技術快速發展的今日,單一機器上執行容器已不足以支撐企業級應用的需求。作為一個經常參與大型分散式系統設計的技術工作者,我發現有效的叢集管理與容器協調已成為建構可靠系統的根本。Fleet 作為 CoreOS 生態系統中的關鍵元件,提供了一套簡潔有效的叢集管理解決方案。

Fleet 的核心架構與運作機制

Fleet 運作的核心是根據 systemd,它將整個叢集視為一個大型的 systemd 系統。當使用者提交單元檔案 (unit files) 時,Fleet 引擎會將工作排程到「負載最低」的機器上。這些單元檔案通常用於執行容器,而 Fleet 代理則負責啟動單元並回報狀態。整個系統使用 etcd 實作機器間的通訊並儲存叢集與單元的狀態。

Fleet 架構圖

Fleet 的架構設計具有容錯能力,當某台機器故障時,原本在該機器上執行的單元會自動在新的主機上重新啟動。這種自動還原機制是我在設計高用性系統時特別重視的特性,它確保了整體服務的穩定性。

Fleet 的排程策略與限制條件

Fleet 支援多種排程提示和限制條件。最基本的層級上,單元可以被排程為全域性 (global),即在所有機器上執行一個例項;或者作為單一單元,只在單一機器上執行。全域性排程對於日誌記錄和監控等工具容器特別有用。

此外,Fleet 還支援各種親和性 (affinity) 類別的限制條件。例如,執行健康檢查的容器可以被排程在總是與應用程式伺服器相鄰的位置。也可以將元資料附加到主機上並用於排程,例如請求容器在特定區域或具有特定硬體的機器上執行。

我在設計大型微服務架構時,常利用這些親和性限制來最佳化服務間的通訊效率。例如,將資料處理服務與資料儲存服務放在同一節點上,減少網路延遲。

由於 Fleet 根據 systemd,它也支援 socket activation(通訊端啟動)的概念,即容器可以在特定連線埠收到連線時才啟動。這種方式的主要優勢是程式可以按需建立,而不是閒置等待事件發生。這在資源有限的環境中特別有價值,可以大幅提升資源利用率。

實戰:在 Fleet 叢集上佈署 identidock 應用

讓我們看如何在 Fleet 叢集上執行 identidock 應用。在這個例子中,我建立了一個 GitHub 專案,其中包含一個 Vagrant 範本,可啟動三個虛擬機器:

$ git clone https://github.com/amouat/fleet-vagrant
...
$ cd fleet-vagrant
$ vagrant up
...
$ vagrant ssh core-01 -- -A
CoreOS alpha (758.1.0)

現在我們已經啟動了一個由三個執行 CoreOS 的虛擬機器組成的叢集,其中已安裝了 Flannel 和 Fleet。我們可以使用 Fleet 命令列工具 fleetctl 取得叢集中的機器列表:

core@core-01 ~ $ fleetctl list-machines
MACHINE IP METADATA
16aacf8b... 172.17.8.103 -
39b02496... 172.17.8.102 -
eb570763... 172.17.8.101 -

佈署 SkyDNS 實作服務發現

首先,我們需要安裝 SkyDNS 來實作根據 DNS 的服務發現。將這類別共用服務安裝在叢集的所有節點上是很合理的,所以我們將其定義為全域單元。服務檔案名為 skydns.service,內容如下:

[Unit]
Description=SkyDNS

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill dns
ExecStartPre=-/usr/bin/docker rm dns
ExecStartPre=/usr/bin/docker pull skynetservices/skydns:2.5.2b
ExecStart=/usr/bin/env bash -c "IP=$(/usr/bin/ip -o -4 addr list docker0 \
| awk '{print $4}' | cut -d/ -f1) \
&& docker run --name dns -e ETCD_MACHINES=http://$IP:2379 \
skynetservices/skydns:2.5.2b"
ExecStop=/usr/bin/docker stop dns

[X-Fleet]
Global=true

除了 [X-Fleet] 部分外,其餘都是標準的 systemd 單元檔案。在 ExecStart 中,我們首先進行一些 shell 操作,取得 docker0 橋接器的 IP 地址,用於存取主機的 etcd 例項。容器啟動時沒有使用 -d 引數,這使 systemd 能夠監控應用程式並處理日誌記錄。[X-Fleet] 部分告訴 Fleet 我們希望在所有機器上執行這個單元,而不是預設的單一例項。

在啟動 DNS 伺服器之前,我們需要在 etcd 中新增一些設定:

core@core-01 ~ $ etcdctl set /skydns/config \
'{"dns_addr":"0.0.0.0:53", "domain":"identidock.local."}'
{"dns_addr":"0.0.0.0:53", "domain":"identidock.local."}

這告訴 SkyDNS 它負責 identidock.local 網域名稱。

現在我們可以啟動服務了。使用 fleetctl start 命令啟動單元:

core@core-01 ~ $ fleetctl start skydns.service
Triggered global unit skydns.service start

我們可以使用 list-units 命令取得所有單元的狀態。一旦一切啟動並執行,你應該會看到類別似的輸出:

core@core-01 ~ $ fleetctl list-units
UNIT MACHINE ACTIVE SUB
skydns.service 16aacf8b.../172.17.8.103 active running
skydns.service 39b02496.../172.17.8.102 active running
skydns.service eb570763.../172.17.8.101 active running

這表明 SkyDNS 容器在叢集中的每台機器上都在執行。

佈署 Redis 與 dnmonster 服務

現在 DNS 服務已經執行,讓我們啟動 Redis 容器並將其註冊到 DNS。Redis 單元的設定可以在 redis.service 檔案中找到:

[Unit]
Description=Redis
After=docker.service
Requires=docker.service
After=flanneld.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill redis
ExecStartPre=-/usr/bin/docker rm redis
ExecStartPre=/usr/bin/docker pull redis:3
ExecStart=/usr/bin/docker run --name redis redis:3
ExecStartPost=/usr/bin/env bash -c 'sleep 2 \
&& IP=$(docker inspect -f {{.NetworkSettings.IPAddress}} redis) \
&& etcdctl set /skydns/local/identidock/redis \
"{\\"host\\":\\"$IP\\",\\"port\\":6379}"'
ExecStop=/usr/bin/docker stop redis

這次我們沒有包含 [X-Fleet] 部分,所以只會啟動一個 Redis 例項。在 [ExecStartPost] 部分,我們加入了一些程式碼,在啟動容器後自動將 Redis 註冊到 SkyDNS。在實際專案中,我通常會將這類別程式碼放在單獨的支援指令碼中,但為了簡單起見,我將它保留在主單元檔案中。

讓我們啟動 Redis 服務和 dnmonster 服務(dnmonster 單元檔案遵循與 Redis 相同的格式):

core@core-01 ~ $ fleetctl start redis.service
Unit redis.service launched on 53a8f347.../172.17.8.101
core@core-01 ~ $ fleetctl start dnmonster.service
Unit dnmonster.service launched on ce7127e7.../172.17.8.102

你應該會看到 dnmonster 和 Redis 單元被排程到不同的機器上,以分散負載:

core@core-01 ~ $ fleetctl list-units
UNIT MACHINE ACTIVE SUB
dnmonster.service 39b02496.../172.17.8.102 activating start-pre
redis.service 16aacf8b.../172.17.8.103 activating start-pre
skydns.service 16aacf8b.../172.17.8.103 active running
skydns.service 39b02496.../172.17.8.102 active running
skydns.service eb570763.../172.17.8.101 active running

機器下載並啟動適當的容器需要一些時間。

佈署 identidock 主要應用

現在讓我們啟動 identidock 容器。單元檔案 identidock.service 內容如下:

[Unit]
Description=identidock

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill identidock
ExecStartPre=-/usr/bin/docker rm identidock
ExecStartPre=/usr/bin/docker pull amouat/identidock:1.0
ExecStart=/usr/bin/env bash -c "docker run --name identidock --link dns \
--dns $(docker inspect -f {{.NetworkSettings.IPAddress}} dns) \
--dns-search identidock.local amouat/identidock:1.0"
ExecStop=/usr/bin/docker stop identidock

這次我們使用 Docker 的 –dns 和 –dns-search 標誌,告訴容器透過其機器上的 SkyDNS 容器解析 DNS 查詢。為了更方便,我們還可以請求 Fleet 將容器排程到我們當前登入的機器上。要做到這一點,首先使用 fleetctl list-machines -l 找到機器的 ID:

core@core-01 ~ $ fleetctl list-machines -l
MACHINE IP METADATA
16aacf8ba9524e368b5991a04bf90aef 172.17.8.103 -
39b02496db124c3cb11ba88a13684c16 172.17.8.102 -
eb570763ac8349ec927fac657bffa9ee 172.17.8.101 -

然後在 identidock.service 檔案底部新增以下內容:

[X-Fleet]
MachineID=<id>

替換為你正在執行的機器的 ID。在我的例子中,這將是:

[X-Fleet]
MachineID=eb570763ac8349ec927fac657bffa9ee

現在我們可以啟動 identidock 單元,它應該會被排程到當前機器:

core@core-01 ~ $ fleetctl start identidock.service
Unit identidock.service launched on eb570763.../172.17.8.101

一旦服務啟動,我們可以檢查是否執行正常:

core@core-01 ~ $ docker exec -it identidock bash
uwsgi@ae8e3d7c494a:/app$ ping redis
PING redis.identidock.local (192.168.76.3): 56 data bytes
64 bytes from 192.168.76.3: icmp_seq=0 ttl=60 time=1.641 ms
64 bytes from 192.168.76.3: icmp_seq=1 ttl=60 time=2.133 ms
^C--- redis.identidock.local ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 1.641/1.887/2.133/0.246 ms
uwsgi@ae8e3d7c494a:/app$ curl localhost:9090
<html><head><title>Hello

測試 Fleet 的容錯還原能力

我們也可以測試當機器出現故障時會發生什麼:

core@core-01 ~ $ fleetctl list-units
UNIT MACHINE ACTIVE SUB
dnmonster.service 39b02496.../172.17.8.102 active running
identidock.service eb570763.../172.17.8.101 active running
redis.service 16aacf8b.../172.17.8.103 active running
skydns.service 16aacf8b.../172.17.8.103 active running
skydns.service 39b02496.../172.17.8.102 active running
skydns.service eb570763.../172.17.8.101 active running

Redis 正在 172.17.8.103(core-03 機器)上執行。我們可以使用 Vagrant 停止該機器:

core@core-01 ~ $ exit
$ vagrant halt core-03

Kubernetes 叢集與容器協同運作的深入解析

Kubernetes 中的容器網路管理

在 Kubernetes 的架構中,同一個 Pod 內的容器分享相同的 IP 位址,這讓網路通訊呈現出獨特的特性。容器間可透過 localhost 位址上的不同連線埠進行通訊,這意味著在同一個 Pod 內的服務需要協調連線埠的使用,以避免衝突。這種設計雖然增加了一些複雜性,但也帶來了更緊密的容器協作模式。

標籤系統:Kubernetes 的分組機制

標籤(Labels)是附加在 Kubernetes 物件(主要是 Pod)上的鍵值對,用於描述物件的特徵,例如 version: devtier: frontend。標籤通常不是唯一的,它們的主要目的是識別物件群組。透過標籤選擇器(Label Selectors),我們可以輕鬆地識別物件或物件群組,例如找出所有在前端層級與環境設定為生產的 Pod。

標籤系統的靈活性使得管理任務變得簡單,例如:

  • 將 Pod 分配到負載平衡群組
  • 在不同群組間移動 Pod
  • 動態調整服務設定

服務:穩定的端點抽象層

服務(Services)是可以透過名稱定址的穩定端點,它們可以透過標籤選擇器連線到 Pod。例如,一個名為「cache」的服務可能連線到多個由標籤選擇器 "type": "redis" 識別的 Redis Pod。服務會自動在這些 Pod 之間進行請求的輪詢分發。

服務的抽象層帶來的好處:

  • 應用程式不需要了解所呼叫服務的內部細節
  • Pod 內的應用程式碼只需要知道資料函式庫的名稱和連線埠
  • 不需關心該服務由多少個 Pod 組成,或上次與哪個 Pod 通訊

Kubernetes 會為叢集設定一個 DNS 伺服器,監視新服務並允許應用程式碼和設定檔案透過名稱定址這些服務。此外,也可以設定指向非 Pod 資源的服務,例如預先存在的外部 API 或資料函式庫

複製控制器:Pod 生命週期管理

複製控制器(Replication controllers)是在 Kubernetes 中例項化 Pod 的標準方式。它們控制和監控服務的執行 Pod 數量(稱為複本)。舉例來說,一個複製控制器可能負責保持五個 Redis Pod 正常執行:

  • 如果其中一個發生故障,它會立即啟動一個新的
  • 如果減少複本數量,它會停止多餘的 Pod

雖然使用複製控制器來例項化所有 Pod 增加了額外的設定層,但也顯著提高了容錯能力和可靠性。

在這個架構中,我們可以看到由複製控制器建立並由服務公開的兩個 Pod。服務根據 tier 標籤的值在 Pod 之間輪詢請求。在一個 Pod 內,只有一個 IP 位址在所有容器間分享,Pod 內的容器可以使用 localhost 位址上的連線埠進行通訊。服務被分配了一個單獨的可公開存取的 IP 位址。

在 Kubernetes 上執行 identidock

為了在 Kubernetes 上執行 identidock,玄貓將使用獨立的 Pod 來佈署 dnmonster、identidock 和 redis 容器。雖然這可能看起來有些過度,但這些服務都可能需要獨立擴充套件(例如,一個 identidock 服務可以使用由兩個 dnmonster 服務和三個 redis 伺服器組成的負載平衡群組),這意味著我們不需要重寫應用程式來使用 localhost 上的連線埠定址服務。

我們不需要新增日誌記錄或監控容器,因為 Kubernetes 會為我們處理這些。Kubernetes 還提供了一個負載平衡的前端代理,因此我們也不需要 Nginx 代理容器。

取得 Kubernetes GitHub 頁面包含許多不同平台的入門。如果你想在本地資源上嘗試 Kubernetes,可以從一組 Docker 容器或 Kubernetes GitHub 頁面提供的 Vagrant VM 執行 Kubernetes。否則,使用 Google Container Engine (GKE)(Google 自己的商業產品)是代管版本的 Kubernetes 的安全選擇。

如果你自己安裝 Kubernetes,你需要設定 DNS 外掛來解析服務名稱。如果你在 GKE 上執行,這將已經設定並執行。

以下指示使用 Google Container Engine (GKE) 執行 Kubernetes,但它們應該與其他 Kubernetes 安裝非常相似。本文的其餘部分假設你已經可以成功執行 kubectl 命令,與你的 Kubernetes 安裝包含 DNS 伺服器。

佈署 Redis

讓我們首先定義複製控制器來啟動 Redis 例項。建立一個名為 redis-controller.json 的檔案,內容如下:

{
  "kind": "ReplicationController",
  "apiVersion": "v1",
  "metadata": { "name": "redis-controller" },
  "spec": {
    "replicas": 1,
    "selector": { "name": "redis-pod" },
    "template": {
      "metadata": {
        "labels": { "name": "redis-pod" }
      },
      "spec": {
        "containers": [{
          "name": "redis",
          "image": "redis:3",
          "ports": [{
            "containerPort": 6379,
            "protocol": "TCP"
          }]
        }]
      }
    }
  }
}

在這個設定中,我們要求 Kubernetes 建立一個由單個容器組成的複製控制 Pod,該容器執行 redis:3 映像,公開 6379 連線埠。我們為 Pod 提供一個標籤,鍵為 “name”,值為 “redis-pod”。複製控制器本身是一個獨立的物件,名稱為 “redis-controller”。

使用 kubectl 工具啟動這個 Pod:

$ kubectl create -f redis-controller.json
services/redis

建立 Redis 服務

下一步是定義一個服務,允許其他容器連線到我們的 redis pod,而無需知道其 IP 位址。建立一個 redis-service.json 檔案,內容如下:

{
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": { "name": "redis" },
  "spec": {
    "ports": [{
      "port": 6379,
      "targetPort": 6379,
      "protocol": "TCP"
    }],
    "selector": { "name": "redis-pod" }
  }
}

這定義了一個將呼叫者連線到我們的 redis pod 的服務。服務被賦予名稱 “redis”,該名稱將被 DNS 叢集外掛(預設安裝在 GKE 中)識別並可解析。重要的是,這意味著我們的 identidock 程式碼將無需編輯主機名就能工作。

redis pod 由選擇器 “name”:“redis-pod” 識別。如果我們有多個帶有標籤 “name”:“redis-pod” 的 redis 節點,這個選擇器將比對所有節點。當有多個被選中的 pod 時,服務將選擇一個隨機 pod 來處理請求。

佈署 dnmonster

以類別似的方式建立 dnmonster 控制器和服務。建立一個 dnmonster-controller.json 檔案:

{
  "kind": "ReplicationController",
  "apiVersion": "v1",
  "metadata": { "name": "dnmonster-controller" },
  "spec": {
    "replicas": 1,
    "selector": { "name": "dnmonster-pod" },
    "template": {
      "metadata": {
        "labels": { "name": "dnmonster-pod" }
      },
      "spec": {
        "containers": [{
          "name": "dnmonster",
          "image": "amouat/dnmonster:1.0",
          "ports": [{
            "containerPort": 8080,
            "protocol": "TCP"
          }]
        }]
      }
    }
  }
}

以及 dnmonster 服務 dnmonster-service.json:

{
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": { "name": "dnmonster" },
  "spec": {
    "ports": [{
      "port": 8080,
      "targetPort": 8080,
      "protocol": "TCP"
    }],
    "selector": { "name": "dnmonster-pod" }
  }
}

啟動它們:

$ kubectl create -f dnmonster-controller.json
replicationcontrollers/dnmonster-controller
$ kubectl create -f dnmonster-service.json
services/dnmonster

佈署 identidock

建立 identidock-controller.json 檔案:

{
  "kind": "ReplicationController",
  "apiVersion": "v1",
  "metadata": { "name": "identidock-controller" },
  "spec": {
    "replicas": 1,
    "selector": { "name": "identidock-pod" },
    "template": {
      "metadata": {
        "labels": { "name": "identidock-pod" }
      },
      "spec": {
        "containers": [{
          "name": "identidock",
          "image": "amouat/identidock:1.0",
          "ports": [{
            "containerPort": 9090,
            "protocol": "TCP"
          }]
        }]
      }
    }
  }
}

啟動它:

$ kubectl create -f identidock-controller.json
replicationcontrollers/identidock-controller

建立對外服務

Identidock 現在應該已經啟動並執行,但我們仍然需要建立 identidock 服務,使其可以從外部存取。建立一個 identidock-service.json 檔案:

{
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": { "name": "identidock" },
  "spec": {
    "type": "LoadBalancer",
    "ports": [{
      "port": 80,
      "targetPort": 9090,
      "protocol": "TCP"
    }],
    "selector": { "name": "identidock-pod" }
  }
}

這個服務有些不同。我們將 “type” 設定為 “LoadBalancer”,這將建立一個外部可存取的負載平衡器,監聽 80 連線埠的連線並將它們轉發到我們的 identidock 服務的 9090 連線埠。

如果你在 GKE 上執行,你可能還需要在防火牆上開放 80 連線埠,可以使用 gcloud 工具建立一個規則:

$ gcloud compute firewall-rules create --allow=tcp:80 identidock-80

Kubernetes 佈署策略的優勢

透過在 Kubernetes 上佈署 identidock 應用,玄貓展示了一個更具彈性和可擴充套件性的架構。每個元件都被隔離在自己的 Pod 中,可以獨立擴充套件,同時 Kubernetes 提供的服務發現機制確保了元件之間的通訊無需硬編碼 IP 位址。

這種方法的主要優勢在於:

  1. 服務發現自動化 - 服務名稱自動解析,無需手動設定
  2. 動態擴充套件能力 - 每個元件可以根據需求獨立擴充套件
  3. 自動故障還原 - 失敗的 Pod 會自動重啟
  4. 負載平衡內建 - 服務自動在多個 Pod 之間分配請求
  5. 簡化的管理 - 集中化的管理介面和 API

Kubernetes 將容器協同運作提升到一個新的層次,使得複雜的分散式應用程式佈署變得更加簡單和健壯。透過標籤、服務和複製控制器等核心概念的結合,它提供了一個完整的容器生態系統管理解決方案。

在實際生產環境中,這種架構可以進一步擴充套件,加入更多的複本以提高用性,或引入其他 Kubernetes 功能如永續性儲存區(Persistent Volumes)來處理有狀態的應用程式需求。這就是現代容器協調平台的強大之處 - 它不僅解決了當前的佈署需求,還為未來的擴充套件提供了彈性框架。

Kubernetes與Mesos:現代容器協調系統深度剖析

雲端容器管理的革命

在雲端技術快速發展的今天,容器協調系統已成為構建彈性、高用性分散式系統的關鍵根本。當我們需要管理大規模的容器佈署時,手動管理容器的方法很快就會變得難以維持。本文將探討兩個主流容器協調解決方案——Kubernetes和Mesos Marathon,分析它們的架構特點、優勢和應用場景。

Kubernetes:Google的容器協調智慧結晶

Kubernetes源於Google內部的Borg系統,是目前最受歡迎的容器協調平台之一。在我多年的容器化專案經驗中,Kubernetes的服務抽象層概念是其最具價值的設計之一。

Kubernetes的核心服務功能

使用Kubernetes時,服務(Services)提供了一個關鍵的抽象層,讓我們能夠將穩定的網路端點與動態變化的容器群組關聯起來。以下是一個實際範例:

$ kubectl get services identidock
NAME         LABELS        SELECTOR               IP(S)                PORT(S)
identidock   <none>        name=identidock-pod    10.111.250.210       80/TCP
                                                 23.251.128.247

當我們使用公開IP位址(例如23.251.128.247)存取服務時,Kubernetes會將請求路由到正確的Pod,即使底層Pod發生變化或重新排程也不會影響服務的可用性。

Kubernetes的儲存管理

Kubernetes的儲存卷(Volumes)是在Pod層級而非容器層級定義的,這使得同一Pod中的多個容器能夠分享儲存。Kubernetes提供多種類別的儲存卷:

  1. emptyDir:在Pod生命週期內提供臨時儲存空間,Pod終止時資料也會消失
  2. gcePersistentDisk:Google Cloud環境中的持久化儲存選項
  3. awsElasticBlockStore:AWS環境中的彈性區塊儲存選項
  4. nfs:網路檔案系統分享,資料會在Pod生命週期之外持續存在
  5. secret:專為儲存敏感資訊(如密碼、API權杖)設計,儲存在記憶體中而非磁碟上

Kubernetes的優勢與挑戰

Kubernetes的最大優勢在於其內建的容錯和負載平衡功能。不再需要硬連線容器,服務層提供了抽象,使我們能夠輕鬆擴充套件和替換底層Pod和容器。

然而,Kubernetes也帶來了相當的複雜性和資源開銷。額外的日誌記錄和監控基礎設施需要大量資源,進而增加執行成本。正如我在一個大型金融科技專案中發現的,對於簡單應用來說,這種複雜性可能是不必要的負擔。

但對於大多數應用程式,特別是微服務和那些狀態有限或狀態良好隔離的應用程式,Kubernetes提供了易於使用、彈性好與可擴充套件的服務,而所需的工作量驚人地少。

Mesos與Marathon:大規模叢集管理的強大組合

Apache Mesos是一個開放原始碼叢集管理器,設計用於管理涉及數百或數千台主機的超大規模叢集。這一點與Kubernetes形成鮮明對比,後者更適閤中小規模佈署。

Mesos的架構元件

Mesos叢集由以下主要元件組成:

  1. Mesos代理節點(Agent Nodes):負責實際執行任務,所有代理節點會向主節點提交可用資源清單
  2. Mesos主節點(Master):負責將任務傳送給代理節點,維護可用資源列表
  3. ZooKeeper:用於選舉和查詢當前主節點的地址
  4. 框架(Frameworks):與主節點協調,將任務排程到代理節點上

Marathon:長期執行應用的排程器

Marathon是由Mesosphere開發的框架,專為啟動、監控和擴充套件長期執行的應用程式而設計。它對於執行Docker容器非常適合,並支援各種親和性和約束規則。

客戶端透過REST API與Marathon互動,其他功能包括對健康檢查的支援和可用於與負載平衡器整合或分析指標的事件流。

Mesos與Marathon的實際佈署

讓我們看如何使用Docker Machine設定一個三節點Mesos叢集:

  1. 首先建立三個主機:
$ docker-machine create -d virtualbox mesos-1
$ docker-machine create -d virtualbox mesos-2
$ docker-machine create -d virtualbox mesos-3
  1. 設定主機名解析:
$ docker-machine ssh mesos-1 'sudo sed -i "\$a127.0.0.1 mesos-1" /etc/hosts'
$ docker-machine ssh mesos-2 'sudo sed -i "\$a127.0.0.1 mesos-2" /etc/hosts'
$ docker-machine ssh mesos-3 'sudo sed -i "\$a127.0.0.1 mesos-3" /etc/hosts'
  1. 在第一個節點上啟動ZooKeeper:
$ eval $(docker-machine env mesos-1)
$ docker run --name zook -d --net=host amouat/zookeeper
  1. 儲存節點IP位址:
$ MESOS1=$(docker-machine ip mesos-1)
$ MESOS2=$(docker-machine ip mesos-2)
$ MESOS3=$(docker-machine ip mesos-3)
  1. 啟動Mesos主節點:
$ docker run --name master -d --net=host \
-e MESOS_ZK=zk://$MESOS1:2181/mesos \
-e MESOS_IP=$MESOS1 \
-e MESOS_HOSTNAME=$MESOS1 \
-e MESOS_QUORUM=1 \
mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
  1. 啟動Mesos代理節點:
$ docker run --name agent -d --net=host \
-e MESOS_MASTER=zk://$MESOS1:2181/mesos \
-e MESOS_CONTAINERIZERS=docker \
-e MESOS_IP=$MESOS1 \
-e MESOS_HOSTNAME=$MESOS1 \
-e MESOS_EXECUTOR_REGISTRATION_TIMEOUT=10mins \
-e MESOS_RESOURCES="ports(*):[80-32000]" \
-e MESOS_HOSTNAME=$MESOS1 \
-v /var/run/docker.sock:/run/docker.sock \
-v /usr/local/bin/docker:/usr/bin/docker \
-v /sys:/sys:ro \
mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404

在這個設定中,我們設定了Mesos的容器化器(containerizers)以支援Docker,這允許我們在代理上執行Docker容器作為任務。我們還延長了註冊超時,以便代理有時間在中止前下載映像。

Kubernetes與Mesos的比較分析

在我參與的多個大型專案中,選擇適合的協調系統往往取決於具體需求和場景。以下是兩者的關鍵比較:

  1. 規模考量:Mesos設計用於管理極大規模的叢集(數千節點),而Kubernetes在中小規模佈署中表現更佳
  2. 多租戶支援:Mesos原生支援多租戶工作負載,一個使用者的Docker容器可能與另一個使用者的Hadoop任務平行執行
  3. 生態系統:Kubernetes的生態系統更加豐富,工具和社群支援更廣泛
  4. 學習曲線:Kubernetes的概念模型相對更簡單,入門檻較低
  5. 資源效率:對於簡單應用,Mesos的資源開銷可能小於Kubernetes

協調系統選擇的實用建議

根據我在多個企業系統中的實施經驗,以下是選擇容器協調系統的一些建議:

  1. 評估應用特性:狀態少的微服務通常適合Kubernetes;複雜的混合工作負載可能更適合Mesos
  2. 考慮團隊技能:選擇與團隊技能比對的技術,降低學習成本
  3. 權衡複雜性與功能:不要為了不需要的功能引入不必要的複雜性
  4. 考慮雲提供商整合:主要雲提供商都有代管的Kubernetes服務,可以簡化管理
  5. 擴充套件性計劃:考慮未來的擴充套件需求,避免後期架構重構

容器協調系統的選擇不僅是技術決策,也是組織和戰略決策。無論選擇哪種系統,重要的是深入理解其工作原理,以便充分利用其優勢並緩解其侷限性。

在現代雲原生應用開發中,容器協調已成為基礎設施即程式碼的核心部分。透過選擇合適的協調系統並掌握其細節,我們可以構建更穩健、更可擴充套件的分散式系統,更好地應對數位轉型的挑戰。

建立 Mesos 叢集:深入解析容器協調系統

掛載必要資源以支援代理程式功能

為了讓 Mesos 代理程式能夠啟動新容器,我們需要掛載 Docker 的 socket 和二進位檔案。同時,掛載 /sys 目錄對於代理程式能夠正確報告主機上可用資源的詳細資訊是必要的。讓我們啟動 Marathon 協調器:

$ docker run -d --name marathon -p 9000:8080 \
mesosphere/marathon:v0.9.1 --master zk://$MESOS1:2181/mesos \
--zk zk://$MESOS1:2181/marathon \
--task_launch_timeout 600000

這裡我們將 Marathon 的預設埠從 8080 改為 9000,以避免與 dnmonster 容器產生潛在衝突。設定超時為 600,000 毫秒(10 分鐘)是為了比對代理程式執行器註冊超時,這是針對暫時性問題的解決方案,將在未來版本中移除。

啟動 Mesos 代理節點

接下來,在其他主機上啟動代理程式:

$ eval $(docker-machine env mesos-2)
$ docker run --name agent -d --net=host \
-e MESOS_MASTER=zk://$MESOS1:2181/mesos \
-e MESOS_CONTAINERIZERS=docker \
-e MESOS_IP=$MESOS2 \
-e MESOS_HOSTNAME=$MESOS2 \
-e MESOS_EXECUTOR_REGISTRATION_TIMEOUT=10mins \
-e MESOS_RESOURCES="ports(*):[80-32000]" \
-v /var/run/docker.sock:/run/docker.sock \
-v /usr/local/bin/docker:/usr/bin/docker \
-v /sys:/sys:ro \
mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404

對 mesos-3 主機也執行相同操作:

$ eval $(docker-machine env mesos-3)
$ docker run --name agent -d --net=host \
-e MESOS_MASTER=zk://$MESOS1:2181/mesos \
-e MESOS_CONTAINERIZERS=docker \
-e MESOS_IP=$MESOS3 \
-e MESOS_HOSTNAME=$MESOS3 \
-e MESOS_EXECUTOR_REGISTRATION_TIMEOUT=10mins \
-e MESOS_RESOURCES="ports(*):[80-32000]" \
-v /var/run/docker.sock:/run/docker.sock \
-v /usr/local/bin/docker:/usr/bin/docker \
-v /sys:/sys:ro \
mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404

完成這些步驟後,你可以透過瀏覽器存取 http://$MESOS1:5050(將 $MESOS1 替換為 mesos-1 的 IP 地址)來檢視 Mesos 的網頁介面。同樣,Marathon 的介面應該可以在 9000 埠存取。

佈署服務發現機制

現在我們已經建立了透過 Marathon 在 Mesos 代理上執行容器的基礎設施。但在執行 identidock 服務之前,我們需要新增服務發現機制。在這個例子中,我們將使用 mesos-dns 並在 mesos-1 上啟動它。

Marathon 任務在 JSON 檔案中定義,包含任務的詳細資訊和資源需求。以下是啟動 mesos-dns 的 JSON 檔案:

{
"id": "mesos-dns",
"container": {
"docker": {
"image": "bergerx/mesos-dns",
"network": "HOST",
"parameters": [
{ "key": "env",
"value": "MESOS_DNS_ZK=zk://192.168.99.100:2181/mesos" },
{ "key": "env", "value": "MESOS_DNS_MASTERS=192.168.99.100:5050" },
{ "key": "env", "value": "MESOS_DNS_RESOLVERS=8.8.8.8" }
]
}
},
"cpus": 0.1,
"mem": 120.0,
"instances": 1,
"constraints": [["hostname", "CLUSTER", "192.168.99.100"]]
}

我們使用了一個由使用者提供的 mesos-dns 構建版本,它能夠自動從環境變數讀取設定。為了效率考量,我們使用主機網路,不過也可以使用橋接網路並公開 53 埠。

在這裡,我們需要設定環境變數來設定 mesos-dns,這是透過使用 parameter 選項完成的,它會向用於啟動映像的 docker run 命令新增標誌。請將 IP 地址 192.168.99.100 替換為你叢集中 mesos-1 的 IP。

所有任務都需要定義它們需要執行的資源。在這種情況下,我們請求 “0.1” 的 CPU 資源和 120 MB 的記憶體。對於這個測試,我們只需要 mesos-dns 的單個例項。我們透過指定帶有 mesos-1 IP 的主機名約束,將 mesos-dns 固定在 mesos-1 主機上。

資源分配機制

資源如何確切地分配給容器取決於 Mesos 使用的隔離器,這是可設定的。通常,CPU 是一個相對權重,即當 CPU 發生爭用時,權重為 0.2 的容器將獲得比權重為 0.1 的容器多兩倍的 CPU。代理也會從其提供給 Mesos 的資源中扣除 CPU 值(因此,如果代理有 “8” CPU 並執行一個 “1” CPU 的任務,它會為進一步的任務提供 “7” CPU)。

佈署 mesos-dns

將檔案儲存為 dns.json 並使用 REST API 傳送到 Marathon:

$ curl -X POST http://$MESOS1:9000/v2/apps -d @dns.json \
-H "Content-type: application/json" | jq .

我們也可以使用 marathonctl 命令列工具或網頁介面來提交任務。

如果現在檢視 Marathon 網頁介面,你應該會看到它正在佈署 mesos-dns 應用程式。一旦 mesos-dns 啟動並執行,我們需要告訴每個主機使用它。最簡單的解決方案是更新每個主機中的 resolv.conf,這將在啟動容器時自動傳播到任何容器。

設定 DNS 解析

你可以透過在每個主機上執行 sed 指令碼來實作:

$ docker-machine ssh mesos-1 \
"sudo sed -i \"1s/^/domain marathon.mesos\nnameserver $MESOS1\n/\" \
/etc/resolv.conf"
$ docker-machine ssh mesos-2 \
"sudo sed -i \"1s/^/domain marathon.mesos\nnameserver $MESOS1\n/\" \
/etc/resolv.conf"
$ docker-machine ssh mesos-3 \
"sudo sed -i \"1s/^/domain marathon.mesos\nnameserver $MESOS1\n/\" \
/etc/resolv.conf"

這應該會在每個主機上生成一個類別似以下內容的 resolv.conf 檔案:

domain marathon.mesos
nameserver 192.168.99.100
...

如果你的 resolv.conf 包含 search 行,你還需要將其擴充套件以包含 marathon.mesos,否則名稱解析將失敗。

佈署應用服務

現在為每個容器建立啟動器。我們將把這些放在橋接網路上,但它們都需要公開一個埠,以便透過主機存取。

首先從 Redis 開始。將以下內容儲存為 redis.json:

{
"id": "redis",
"container": {
"docker": {
"image": "redis:3",
"network": "BRIDGE",
"portMappings": [
{"containerPort": 6379, "hostPort": 6379}
]
}
},
"cpus": 0.3,
"mem": 300.0,
"instances": 1
}

然後提交:

$ curl -X POST http://$MESOS1:9000/v2/apps -d @redis.json \
-H "Content-type: application/json"

類別似地,對於 dnmonster,將以下內容儲存為 dnmonster.json:

{
"id": "dnmonster",
"container": {
"docker": {
"image": "amouat/dnmonster:1.0",
"network": "BRIDGE",
"portMappings": [
{"containerPort": 8080, "hostPort": 8080}
]
}
},
"cpus": 0.3,
"mem": 200.0,
"instances": 1
}

提交:

$ curl -X POST http://$MESOS1:9000/v2/apps -d @dnmonster.json \
-H "Content-type: application/json"

最後,將以下內容儲存為 identidock.json:

{
"id": "identidock",
"container": {
"docker": {
"image": "amouat/identidock:1.0",
"network": "BRIDGE",
"portMappings": [
{"containerPort": 9090, "hostPort": 80}
]
}
},
"cpus": 0.3,
"mem": 200.0,
"instances": 1
}

提交:

$ curl -X POST http://$MESOS1:9000/v2/apps -d @identidock.json \
-H "Content-type: application/json"

存取與測試服務

當代理下載並啟動映像後,你應該能夠透過被分配 identidock 任務的主機的 IP 地址存取 identidock。你可以從網頁介面或 REST API 找出這個 IP:

$ curl -s http://$MESOS1:9000/v2/apps/identidock | jq '.app.tasks[0].host'
"192.168.99.101"
$ curl 192.168.99.101
<html><head><title>Hello...

Marathon 將確保任何死亡的應用被重新啟動,前提是有足夠的可用資源。嘗試停止和重啟 mesos-2 和 mesos-3 機器,觀察任務如何在資源下線和新資源可用時遷移和重啟,這是很有教育意義的。

新增健康檢查

可以輕鬆地向應用新增更複雜的健康檢查,通常是透過實作一個 HTTP 端點,Marathon 可以定期輪詢該端點。例如,我們可以更新 identidock.json 以包含以下內容:

{
"id": "identidock",
"container": {
"docker": {
"image": "amouat/identidock:1.0",
"network": "BRIDGE",
"portMappings": [
{"containerPort": 9090, "hostPort": 80}
]
}
},
"cpus": 0.3,
"mem": 200.0,
"instances": 1,
"healthChecks": [
{
"protocol": "HTTP",
"path": "/",
"gracePeriodSeconds": 3,
"intervalSeconds": 10,
"timeoutSeconds": 10,
"maxConsecutiveFailures": 3
}]
}

這將嘗試每 10 秒取得 identidock 的首頁。如果取得因任何原因失敗(例如,回傳碼不在 200 到 399 之間,或者在超時內沒有收到回應),Marathon 將再測試端點兩次,然後終止任務。

要佈署健康檢查,只需停止舊的 identidock 佈署並啟動更新的佈署:

$ curl -X DELETE http://$MESOS1:9000/v2/apps/identidock
$ curl -X POST http://$MESOS1:9000/v2/apps -d @identidock.json \
-H "Content-type: application/json"

現在,你應該能夠透過 Marathon 網頁介面找到「健康檢查結果」。

服務路由與負載平衡

我們已經在 Marathon 中看到了約束的作用,當我們在具有給定 IP 地址的主機上排程 mesos-dns 容器時。我們還可以指定選擇具有(或不具有)給定屬性的主機的約束,包括能夠為容錯將容器分散在不同主機上。

目前設定的一個問題是,identidock 服務的地址取決於排程它的主機的 IP。顯然,重要的是有一種可靠的方式從靜態端點路由到 identidock 服務。一種方法是使用 mesos-dns 發現當前端點,但 Marathon 還提供了一個 servicerouter 工具,用於生成用於路由到 Marathon 應用的 HAProxy 設定。另一種解決方案是建立自己的代理或負載平衡服務。

這種彈性設計使得 Mesos 能夠在各種環境中有效執行容器化服務,無論是開發測試還是生產環境。透過這些工具,我們可以建立高用性、可擴充套件的容器基礎設施,確保服務的穩定執行和資源的有效利用。

Mesos 叢集管理與容器協調解決方案深度解析

事件監聽與請求轉發的架構設計

Mesos 具備事件驅動架構,其中包含一個監聽 Marathon 事件匯流排的服務,能夠識別應用程式的建立與銷毀事件,進而適當地轉發相關請求。這種設計使得 Mesos 能夠即時回應系統變化,但也帶來了一些值得思考的問題。

固定埠號的挑戰與解決方案

在容器協調系統中,固定埠號常成為一個棘手的問題。以 Redis 使用的 6379 埠號和 dnmonster 使用的 8080 埠號為例,我們已經被迫將 Marathon 介面重新對映至 9000 埠號以避免與 dnmonster 發生衝突。

解決這類別問題有兩種主要方法:

  1. 動態埠號分配:重寫應用程式以使用 Marathon 動態分配的埠號
  2. 軟體定義網路(SDN):採用網路虛擬化技術,例如 Weave 或 Project Calico

從長遠來看,使用 SDN 是更好的解決方案。目前網路上有許多說明如何將 Weave 與 Mesos 一同安裝,而 Mesosphere 也正在努力將 Project Calico 原生整合至 Mesos 中。

Marathon 的應用程式群組功能

Marathon 提供應用程式群組功能,能夠將相關應用程式聚集在一起,確保它們按照正確的順序佈署,以滿足相依性需求。例如,在啟動應用程式伺服器之前先啟動資料函式庫項功能在系統初始啟動、擴充套件或執行滾動更新時都能發揮作用。

Mesos 的混合工作負載支援

與其他協調解決方案相比,Mesos 的一個獨特之處是它能夠支援混合工作負載。多種框架可以在同一個叢集上執行,例如 Hadoop 或 Storm 資料處理任務可以與驅動微服務應用程式的 Docker 容器平行運作。

這項特性讓 Mesos 特別適合提高資源利用率。系統可以將高 CPU 但低頻寬的任務與相反特性的任務安排在同一主機上,從而最大化資源使用效率。

叢集的有效利用取決於準確的資源請求,而非在提交任務給 Marathon 或其他框架時過度佈建資源。在 identidock 範例中,記憶體數值被故意誇大,以確保不是所有任務都被分配到同一代理節點上,但實際上,單一主機就能輕鬆處理所有工作。

Mesos 的資源過度訂閱機制

為瞭解決資源分配的問題,Mesos 支援資源過度訂閱(over-subscription)功能。這允許系統啟動「可復原」(revocable)任務,即使代理節點並未提供足夠資源,但根據監控資料,它仍有容量。

這些可復原任務在資源使用量激增時會被停止,因此通常用於低優先順序任務,如背景分析工作。關於 Mesos 中過度訂閱的更多資訊,可以參考 Mesos 官方網站和 GitHub。

在 Mesos 上執行 Swarm 或 Kubernetes

由於 Mesos 本身提供低階的叢集和排程基礎設施,因此可以在 Mesos 之上執行更高階的介面,如 Kubernetes 和 Swarm。乍看之下,這似乎多此一舉,因為功能上存在大量重疊。

然而,在 Mesos 上執行這些工具有幾個顯著優勢:

  1. 利用 Mesos 現有的容錯、高用性和資源利用功能
  2. 輕鬆移植到任何支援 Mesos 的資料中心或雲端
  3. 同時保留 Kubernetes 或 Swarm 的特性和易用性

對維運人員而言,這種方式也帶來重大優勢:他們可以專注於提供底層 Mesos 基礎設施,同時支援多樣化的工作負載和高資源利用率。

想了解更多資訊,可以參考 Kubernetes-Mesos 專案。

容器管理平台

有許多平台專為自動化容器的佈建、協調和監控任務而設計。這些平台不直接提供容器託管,而是在公有雲和私有基礎設施之上提供介面。我們將探討的所有範例都提供網頁介面,並能夠概覽系統狀態。

這些平台可以大幅簡化容器佈署的管理,同時保持與基礎設施提供者的抽象層,使得在雲平台之間移動或使用多個雲平台變得更加容易。

Rancher

Rancher(可能是根據「寵物與牛群」類別比命名)是最以 Docker 為中心的管理平台。

開始使用 Rancher 非常簡單,只需在其中一台主機上執行 Rancher 伺服器容器:

docker run -d --restart=always -p 8080:8080 rancher/server

然後,用瀏覽器存取該主機的 8080 埠,就能看到 Rancher 介面。從那裡,你可以開始新增主機,這些主機可以是你基礎設施上的虛擬機器,也可以是雲端資源。

使用 Digital Ocean 或 AWS 等公有雲時,如果提供存取金鑰,Rancher 會自動佈建虛擬機器。在現有虛擬機器上安裝 Rancher 時,只需使用 Rancher 介面提供的引數執行 Rancher 代理,如:

docker run -d --privileged -v /var/run/docker.sock:/var/run/docker.sock \
rancher/agent:v0.8.1 http://<host_ip>:8080/v1/scripts/<token>

Rancher 需要掛載 Docker socket 才能在主機上啟動新容器。一旦代理啟動並執行,它應該會出現在 Rancher 介面的 HOSTS 標籤中。基礎設施畫面將顯示主機上所有執行中的容器列表(除了 Rancher 代理)。

在測試環境中,Rancher 伺服器可以與代理執行在同一主機上,但 Rancher 建議在生產環境中為伺服器使用專用主機。

在 Rancher 上執行 identidock 非常簡單。只需為每個容器建立一個服務,並將 identidock 服務連線到 dnmonster 和 Redis 服務即可。你可能還想將 identidock 服務的 9000 埠發布為 80 埠。

不需要發布任何其他埠;Rancher 會處理跨主機的網路連線,並在適當的主機上排程容器。

此外,我們還可以使用 Rancher Compose,這是一個用於將 Docker Compose 檔案佈署到 Rancher 的命令列工具。

Rancher 使查詢執行中容器的詳細資訊變得容易,包括存取日誌,甚至執行 shell 來除錯程式。

雖然 Rancher 目前提供自己的跨主機網路解決方案(使用 IPsec)、服務發現和簡單的協調功能,但他們的目標是完全轉向 Docker 技術堆積積疊(即使用 Swarm 進行協調和新的網路外掛)。他們還在開發 Kubernetes 和 Mesos 整合。

考慮到使用 Rancher 或將其新增到現有系統的簡易程度,絕對值得一試。

Clocker

Clocker 是一個建立在 Apache Brooklyn 之上的自託管、開放原始碼容器管理平台。與 Rancher 相比,它提供更導向應用程式的解決方案,並支援在應用程式內混合使用虛擬機器和容器。透過使用 jclouds 工具包,它支援非常廣泛的雲端提供商。

開始使用 Clocker 需要一些準備工作。下載軟體後,必須設定雲端佈署權杖和存取金鑰。完成後,可以啟動 Clocker 雲,它會自動佈建主機並使用 Weave 或 Project Calico 安裝網路。

建立 Clocker 雲後,可以啟動新應用程式來執行 Docker 容器。以下 YAML 將在 Clocker 上啟動 identidock:

id: identidock
name: "Identidock"
location: my-docker-cloud
services:
- type: docker:redis:3
  name: redis
  id: redis
  openPorts:
  - 6379
- type: docker:amouat/dnmonster:1.0
  name: dnmonster
  id: dnmonster
  openPorts:
  - 8080
- type: docker:amouat/identidock:1.0
  name: identidock
  id: identidock
  portBindings:
    80: 9090
  links:
  - $brooklyn:component("redis")
  - $brooklyn:component("dnmonster")

Clocker 的優勢在於可以輕鬆混合 Docker 佈署和非容器化資源。例如,我們可以用執行 Redis 的虛擬機器替換 Redis 容器,方法是使用以下 YAML 作為 Redis 服務:

- type: org.apache.brooklyn.entity.nosql.redis.RedisStore
  name: redis
  location: jclouds:softlayer:lon02
  id: redis
  install.version: 3.0.0
  start.timeout: 10m

這將在 Softlayer 的倫敦資料中心佈建一個虛擬機器,然後在其上安裝並執行 Redis。

撰寫本文時,Clocker 正處於密集開發階段。雖然目前它缺乏其他解決方案的一些精緻之處,但其使用 Brooklyn 和 jclouds 技術意味著它可以佈署在各種系統上,並能愉快地執行混合不同類別的基礎設施。

Tutum

Tutum 提供託管平台,用於佈署和管理容器。他們強烈專注於可用性和提供清晰的介面。

將節點新增到 Tutum 可以透過提供公有雲提供商的憑證,或在現有機器上安裝 Tutum 代理來完成。代理以守護程式而非容器形式執行,這意味著它不支援所有作業系統(尤其是 Docker Machine 建立的 boot2docker 映像不受支援)。

Tutum 使用 stackfiles 來定義連結服務集。這些故意與 Compose 檔案非常相似,但新增了一些與協調和擴充套件相關的額外欄位,如 target_num_containers 和 deployment_strategy,同時刪除了其他欄位,如 user 和 cap_add。

在內部,Tutum 使用 Weave 提供跨主機網路和服務發現。

在 Tutum 上執行 identidock 也很簡單。Tutum 儀錶板可以顯示在兩個 Digital Ocean 節點上執行的 identidock。

除了網頁介面外,Tutum 還可以透過 REST API 和 Tutum CLI 存取。

對於尋求集中式服務以減輕設定和執行容器化服務的維運工作的人來說,Tutum 值得一看。希望保持對服務的完全控制或對信任集中式服務持謹慎態度的人應該尋找其他選擇。

容器協調、叢集和管理明顯有很多選擇。不過,這些選擇通常有明顯的區別。在協調方面,我們可以總結如下:

  • Swarm 的優勢(也是劣勢)在於使用標準的 Docker 介面。雖然這使得使用 Swarm 和將其整合到現有工作流程變得非常簡單,但也可能使支援在自定義介面中定義的更複雜的排程變得更加困難。

  • Fleet 是一個低階與相當簡單的協調層,可用作執行更高階協調工具的基礎,如 Kubernetes 或自定義系統。

  • Kubernetes 是一個固執己見的協調工具,內建服務發現和複製功能。它可能需要重新設計現有應用程式,但正確使用時,將產生容錯與可擴充套件的系統。

  • Mesos 是一個低階、經過實戰考驗的排程器,支援多個容器協調框架,包括 Marathon、Kubernetes 和 Swarm。

在撰寫本文時,Kubernetes 和 Mesos 比 Swarm 更成熟穩定。在規模方面,只有 Mesos 已被證明能支援大規模系統。

在這個快速發展的領域中,選擇適合自身需求的容器協調解決方案需要綜合考慮技術成熟度、擴充套件性需求、團隊經驗以及與現有系統的整合難度。無論選擇哪種解決方案,容器技術都為應用佈署和管理帶來了前所未有的靈

容器安全與限制

容器安全的實際考量

在小型叢集環境中,Mesos可能過於複雜,特別是當節點數量不到十個的情況下。對於純Docker佈署,Rancher是個值得考慮的管理平台,它可以輕鬆地新增到現有佈署中或從中移除,因此試用風險很低。

容器安全概述

要安全地使用Docker,我們必須瞭解潛在的安全問題以及保護容器系統的主要工具和技術。本文將從生產環境的角度探討Docker的安全性,但這些建議同樣適用於開發環境。即使在安全性方面,保持開發和生產環境的相似性仍然重要,以避免Docker原本設計要解決的程式碼遷移問題。

網路上的一些文章和新聞可能給人一種印象,認為Docker本質上不安全與不適合生產使用。雖然使用容器確實需要注意一些安全議題,但如果正確使用,容器可以提供比單純使用虛擬機器或裸機更安全、更高效的系統。

免責宣告

本文的指導根據個人專業見解,而非安全研究工作者的官方建議。遵循本文建議的系統將比大多數現有系統處於更好的安全狀態,但這些建議並不構成完整解決方案,應僅用於指導您制定自己的安全程式和政策。

容器安全需關注的問題

使用容器化環境時,我們應該關注哪些安全問題?以下列表雖不詳盡,但足以提供思考方向:

核心漏洞攻擊

與虛擬機器不同,容器間分享同一個核心,這使得核心中的任何漏洞變得更加嚴重。如果一個容器導致核心當機,整個主機都會受到影響。在虛擬機器環境中,攻擊者必須透過虛擬機器核心和虛擬層才能接觸到主機核心,安全性相對較高。

拒絕服務攻擊

所有容器分享核心資源。如果一個容器能夠壟斷某些資源的存取許可權(包括記憶體和較為特殊的資源,如使用者ID),它可能會使主機上的其他容器資源匱乏,導致拒絕服務攻擊,使合法使用者無法存取系統的部分或全部功能。

容器逃逸

攻擊者如果獲得容器存取許可權,不應能夠存取其他容器或主機。由於使用者未被名稱空間隔離,任何逃離容器的程式在主機上將擁有與容器內相同的許可權;如果你在容器內是根使用者,那麼在主機上你也將是根使用者。

這意味著我們需要考慮潛在的許可權提升攻擊——使用者獲得提升的許可權,如根使用者許可權,通常是透過需要額外許可權執行的應用程式碼中的漏洞。考慮到容器技術仍處於起步階段,我們應該假設容器逃逸不太可能發生,但仍有可能。

惡意映像檔

如何確定使用的映像檔是安全的,未被篡改,並來自聲稱的來源?如果攻擊者能夠誘騙你執行他的映像檔,主機和資料都將面臨風險。同樣,確保執行的映像檔是最新的,不包含已知漏洞的軟體版本也很重要。

機密資料洩露

當容器存取資料函式庫務時,通常需要某些機密,如API金鑰或使用者名稱和密碼。能夠存取這些機密的攻擊者也將能夠存取服務。在微服務架構中,容器不斷停止和啟動,與少量長期執行的虛擬機器相比,這個問題變得更加嚴重。分享機密的解決方案前文已經討論過。

容器與名稱空間

Red Hat的Dan Walsh在一篇被廣泛參照的文章中寫道「容器並不真正隔離」。他主要是指容器能存取的並非所有資源都被名稱空間隔離。被名稱空間隔離的資源在主機上會對映到不同的值(例如,容器內的PID 1不是主機上或任何其他容器中的PID 1)。相反,未被名稱空間隔離的資源在主機和容器中是相同的。

未被名稱空間隔離的資源包括:

使用者ID (UIDs)

容器內的使用者在容器和主機上具有相同的UID。這意味著如果容器以根使用者執行,一旦發生容器逃逸,攻擊者將成為主機上的根使用者。目前有正在進行的工作,將容器中的根使用者對映到主機上的高編號使用者,但這尚未實作。

核心鑰匙圈

如果應用程式或依賴的應用程式使用核心鑰匙圈處理加金鑰或類別似內容,這一點非常重要。金鑰按UID分離;因此,使用相同UID執行的容器將能夠存取相同的金鑰,以及主機上的等效使用者。

核心本身和任何核心模組

如果容器載入核心模組(需要額外許可權),該模組將在所有容器和主機上可用,包括後面討論的Linux安全模組。

裝置

包括磁碟驅動器、音效卡和圖形處理器(GPU)等。

系統時間

在容器內更改時間會改變主機和所有其他容器的系統時間。這只有在容器被賦予SYS_TIME能力(預設情況下不授予)的情況下才可能。

事實上,Docker和它所依賴的Linux核心功能都還很年輕,遠未像等效的虛擬機器技術那樣經過充分測試。目前,容器不能提供與虛擬機器相同的安全保證。

縱深防禦策略

面對這些挑戰,我們應該假設存在漏洞並建立縱深防禦。考慮城堡的類別比,它有針對各種攻擊的多層防禦。典型的城堡有護城河或利用當地理條件控制通往往城堡的路線。城牆是厚實的石頭,設計用來抵禦火攻和炮彈。城堡內有戰鬥人員的防禦工事和多層堡壘。如果攻擊者突破了一套防禦,還會面臨另一套防禦。

系統的防禦也應該由多層組成。例如,容器很可能在虛擬機器中執行,這樣即使發生容器逃逸,另一層防禦可以阻止攻擊者存取主機或其他使用者的容器。應該建立監控系統,以便在出現異常行為時警告管理員。防火牆應限制容器的網路存取,減少外部攻擊面。

最小許可權原則

另一個需要遵循的重要原則是最小許可權;每個程式和容器都應該以完成其功能所需的最小許可權和資源集執行。這種方法的主要好處是,如果一個容器被攻破,攻擊者仍然受到嚴格限制,無法存取更多資料或資源。

關於最小許可權,可以採取多種步驟減少容器的能力,例如:

  • 確保容器中的程式不以根使用者執行,這樣即使程式中存在漏洞被利用,攻擊者也無法獲得根使用者存取許可權
  • 將檔案系統設為唯讀,使攻擊者無法覆寫資料或將惡意指令碼儲存到檔案中
  • 減少容器可以進行的核心呼叫,以減少潛在的攻擊面
  • 限制容器可以使用的資源,避免拒絕服務攻擊,防止被攻破的容器或應用消耗足夠的資源(如記憶體或CPU)使主機停止回應

Docker許可權等同於根許可權

本文主要關注容器執行的安全性,但需要指出的是,也必須小心給予誰存取Docker守護程式的許可權。任何可以啟動和執行Docker容器的使用者實際上都擁有主機的根存取許可權。例如,考慮可以執行以下命令:

$ docker run -v /:/homeroot -it debian bash

這樣就可以存取主機上的任何檔案或二進位檔案。

如果執行Docker守護程式的遠端API存取,要小心如何保護它以及給予誰存取許可權。如果可能,限制對本地網路的存取。

Identidock的安全加固

為了更好理解本章內容,讓我們看如何為生產環境加固Identidock應用。Identidock不儲存任何敏感資訊,所以主要關注點是防止攻擊者進入並重新利用系統資源。

Identidock是一個簡單的應用範例,它的安全考量代表了許多容器應用需要關注的基本安全問題。透過適當的安全策略,即使是簡單的應用也能在容器環境中安全執行。

容器安全是一個多層面的挑戰,需要我們採取縱深防禦策略。從核心漏洞到容器逃逸,從惡意映像檔到機密管理,每個方面都需要特別注意。雖然容器技術還在發展,安全性尚未達到虛擬機器的水準,但透過遵循最小許可權原則、建立多層防禦機制,以及瞭解名稱空間的侷限性,我們可以顯著提高容器佈署的安全性。

記住,Docker許可權實際上等同於根許可權,因此誰能存取Docker守護程式至關重要。透過正確限制許可權、使用唯讀檔案系統、減少核心呼叫,以及限制資源使用,我們可以建立更安全的容器環境,為生產工作負載提供足夠的保護。