如何對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 ps
與 docker 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
會取得所有Running
與Shutdown
狀態容器資訊- 多利用一個
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 內部運作的欄位名稱了
沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。