如何對Docker Swarm叢集特定服務所有複本容器執行命令

如何對Docker Swarm叢集特定服務所有複本容器執行命令

Docker Swarm 的服務容器專案有個特別需求,再特定情況下需要對特定服務的複本(replicas)容器下達特定指令。這在單純的容器環境非常簡單,透過 docker exec CLI 就能簡單解決。

docker exec {container} ping localhost -n 1
docker exec -it {container} cmd
docker exec -it {container} powershell

不論是簡單測試指令,或是要進入容器內除錯或看日誌,docker exec 算是非常入門與經常使用的 Docker CLI,就不多介紹了。不過,當我們把容器部署至 Docker Swarm 叢集之後,由於是跨伺服器多個複本容器,最呆的做法就是登入每一台 Worker 去一個一個容器去輸入 docker exec 來執行所需指令,這種工作不給他自動化一下實在說不過去。

Docker 本身支援遠端呼叫(-H),所以跨伺服器問題算簡單就能處理。

docker -H worker-1 exec lab_lab.1.ql57e12vm1vlhxxkxmigmd7uh ping localhost -n 1
docker -H {Host} exec {Name}.{Id} ping localhost -n 1

測試之後,先把需要的指令格式確認下來,從上面可以看到變化的部分有「主機名稱 Host服務名稱 Name服務ID」這三個值,接下想辦法轉換撰寫自動化腳本。很幸運的,很快速找到 Docker CLI 兩個指令可以提供我們所需的內容,分別是 docker service psdocker stack ps,輸出內容幾乎一樣。

C:\>docker service ps lab_lab
C:\>docker stack ps lab

ID                  NAME                IMAGE           NODE            DESIRED STATE       CURRENT STATE         ERROR               PORTS
s8li28msfd95        lab_lab.1         ?.azurecr.io/...   worker-3        Running             Running 3 hours ago            
5ijpx2w5yx40        lab_lab.2         ?.azurecr.io/...   worker-2        Running             Running 2 hours ago            
ql57e12vm1vl        lab_lab.3         ?.azurecr.io/...   worker-1        Running             Running 2 hours ago

不過,我們要怎麼取得 STDOUT 的內容並轉換成參數值來使用呢?當然是有請 Windows 裡腳本界P哥 PowerShell 哥上場。

$stacks = docker stack ps lab

$stacks 會被轉換為字串陣列格式,但它存的是一行一行的字串內容。也就是說抬頭一整行$stacks[0] 的值,下面的值就是 $stacks[1]$stacks[2]$stacks[3]

PS C:\> $stacks[0]
ID                  NAME                IMAGE             NODE           DESIRED STATE       CURRENT STATE          ERROR               PORTS
PS C:\> $stacks[1]
s8li28msfd95        lab_lab.1          ?.azurecr.io/...   worker-3        Running             Running 3 hours ago

本想在 PowerShell 裡去分割與組合這些字串,想著想著,想到 PowerShell 本身提供了許多 Convert 的 cmdlet,來試試看這些 Convert 的效果。

PS C:\> $stacks = ConvertFrom-Csv $(docker stack ps lab)
PS C:\> $stacks[0]

ID                  NAME                IMAGE           NODE           DESIRED STATE       CURRENT STATE          ERROR             PORTS
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
s8li28msfd95        lab_lab.1         ?.azurecr.io/...  worker-3        Running             Running 19 hours ago       ...


PS C:\> $stacks[2]

ID                  NAME                IMAGE           NODE           DESIRED STATE       CURRENT STATE          ERROR             PORTS
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ql57e12vm1vl        lab_lab.3         ?.azurecr.io/...   worker-1        Running             Running 19 hours ago       ...

馬上感受到不一樣。但內部的資料結構還是有問題,造成我們無法順利取值。就在苦腦時,靈光一閃想起一個 Golang template,這個文件看過沒用過技巧可以拿來格式化 Docker 的輸出訊息,大部分的 Docker CLI 只要支援 --format 的參數使用 Golang template。

PS C:\> $stacks = ConvertFrom-Csv $(docker stack ps --format "{{.ID}},{{.Name}},{{.Node}}" --no-trunc lab)
PS C:\> $stacks

s8li28msfd95puo1bxzcxmn6g lab_lab.1   worker-3
------------------------- ----------- ------------
5ijpx2w5yx40cwmauez3kvzjr lab_lab.2   worker-2
ql57e12vm1vlhxxkxmigmd7uh lab_lab.3   worker-1

很明顯,我們還少了抬頭(headers),文件裡的 table 實在讓我猜好久,Golang template 文件也沒有寫清楚,還好最後被我猜出來了:

PS C:\> $stacks = ConvertFrom-Csv $(docker stack ps --format "table{{.ID}},{{.Name}},{{.Node}}" --no-trunc lab)
PS C:\> $stacks

ID                        NAME        NODE
--                        ----        ----
s8li28msfd95puo1bxzcxmn6g lab_lab.1   worker-3
5ijpx2w5yx40cwmauez3kvzjr lab_lab.2   worker-2
ql57e12vm1vlhxxkxmigmd7uh lab_lab.3   worker-1


PS C:\> $stacks[0].ID
s8li28msfd95puo1bxzcxmn6g

終於把 STDOUT 轉換為 CSV 所需格式,然後透過 ConvertFrom-Csv 的幫助轉換成合法 CSV 物件,後續取值後的處理就很簡單了,讓我們完成整段腳本:

$stacks = ConvertFrom-Csv $(docker stack ps --format "table{{.ID}},{{.Name}},{{.Node}}" --no-trunc lab)

foreach ($item in $stacks) {
    Write-Host "Run command: docker -H $($item.Node) exec $($item.Name).$($item.ID) ping localhost -n 1"
    $command = "docker -H $($item.Node) exec $($item.Name).$($item.ID) ping localhost -n 1"
    Invoke-Expression $command
}

透過這支 PowreShell Script,我們把 STDOUT 訊息文字轉換為 PowerShell 物件,成為物件之後的操作相信對大家就易如反掌。使用一個簡單 foreach 就能對 Docker Swarm 叢集內容特定服務所有複本容器執行所需要指令。

在 CLI 的世界,還好能認識 PowerShell - P哥,沒有它我還真不知怎麼處理這個需求。


Update: docker service script (2019/09/18)

在跨主機(docker -H host)使用 docker stack發現會有問題,用 docker service 改寫:

$stacks = ConvertFrom-Csv $(docker -H HostName service ps --format "table{{.ID}},{{.Name}},{{.Node}},{{.DesiredState}}" --no-trunc lab)

foreach ($item in $stacks) {
    if ($item.'Desired State' -eq "Running") {
        Write-Host "Run command: docker -H $($item.Node) exec $($item.Name).$($item.ID) ping localhost -n 1"
        $command = "docker -H $($item.Node) exec $($item.Name).$($item.ID) ping localhost -n 1"
        Invoke-Expression $command
    }
}

有幾點小差異:

  • 轉換 CSV 物件時多取得 DESIRED STATE 欄位值
  • docker service 會取得所有 RunningShutdown 狀態容器資訊
  • 多利用一個 if 判斷,僅狀態為 Running 容器執行指令

這裡在取得 DESIRED STATE 有碰到一些些麻煩,{{.DesiredState}} 這個值我原本猜不出來怎麼取得,例如,在 docker service 有二個欄位是含空格的:

ID    NAME    IMAGE    NODE    DESIRED STATE    CURRENT STATE    ERROR    PORTS

研究 --format 學到可以將輸出改為 JSON 格式輸出:

docker service ps --format='{{json .}}' lab
{"CurrentState":"Running 18 hours ago","DesiredState":"Running","Error":"","ID":"aaa","Image":"?.azurecr.io/...","Name":"lab.1","Node":"worker-1","Ports":""}
{"CurrentState":"Shutdown 18 hours ago","DesiredState":"Shutdown","Error":"","ID":"bbb","Image":"?.azurecr.io/...","Name":"lab.1","Node":"worker-1","Ports":""}
...

這樣就能取得實際在 Docker 內部運作的欄位名稱了

沒有留言:

張貼留言

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。