在專案中,我們很多時候會需要透過程式碼來操作某項服務——比如操作 Google Map 或本文的 Docker——而不是使用相對直覺的 CLI 或更方便的 GUI,畢竟它們都是給人類使用的。

遇到這樣的需求,腦中第一個浮現的通常是「這服務有沒有官方 API 可以呼叫?」,再進一步想,如果有提供我們 SDK 就更好了!

本文將介紹 Python Docker SDK 的基本使用方式,大致集中在初始化連線容器操作部分,主要參考自 SDK 官方文件,並會加上一些個人的實作經驗與小提醒。

一方面是記錄,同時也可以讓剛接觸 Docker SDK 的人看一下常用對應 CLI 指令的套件使用方式。也許你的程式中只需要簡單 Docker 的應用,相信本文可以減少你摸索文件的時間,類似文件中的「getting started」。

好,讓我們開始。


安裝 Docker SDK for Python

我們都知道,SDK 從某個角度來說就是對 API 的封裝,以方便開發者使用,所以 SDK 中的各項函式、類別當然也是一種 API。

而我們平常講的 API,通常指的是 Web API,單純使用 Web API,任何語言都可以透過 HTTP 輕鬆存取,實現互動與溝通。

但 SDK 需要配合相對應的語言,因為它本質上就是「給特定語言的 API 懶人包」,從這個頁面可以看出,Docker SDK 官方支援的語言總共有兩種:

  • Go
  • Python

其餘語言則是社群開發的非官方版本,而這裡我們選用的是 Python,直接從 PyPI 安裝即可:

1
pip install docker

初始化 Docker client

使用 Python SQL driver 類的套件我們都需要先建立一個「連線」,比如:

1
2
3
4
5
6
import pymysql
connection = pymysql.connect(
host='localhost',
user='user',
password='passwd',
database='db')

同理,要操作 Docker Engine 也需要先建立一個「客戶端連線」,畢竟 Docker 是典型的 client/server 架構,我們平常使用的 Docker CLI 就是一個 Docker client,而 server 端則是 Docker Engine,又稱為 Docker daemon。

1
2
import docker
client = docker.from_env()

絕大部分的 Docker 操作,都是從這個 client 開始。初始化 client 連線非常簡單,只需要from_env()一行解決,不過這裡有一個需要注意的點,那就是 Docker daemon 所在的連線位址——DOCKER_HOST環境變數。

事實上,from_env()自動使用相關環境變數,來取得 Docker daemon 的連線位址。如果沒有設定,那麼預設會連線到本機上的位址,通常是unix://var/run/docker.sock

和 Docker CLI 相同,from_env()會使用這三個環境變數:

  • DOCKER_HOST
  • DOCKER_TLS_VERIFY
  • DOCKER_CERT_PATH

通常我們只需要關注第一個——DOCKER_HOST——即可。

DOCKER_HOST 環境變數

平常我們在本機安裝 Docker,實際上是同時安裝了 Docker CLI 和 Docker Engine,啟動 Docker Engine 在本機執行一個 process ,即為 Docker daemon。

注意:一般提到 Docker host,通常指的是 Docker network 中的 host network,和這裡的DOCKER_HOST環境變數所代表的意義並不相同。

因為 Docker client 和 Docker Engine 都安裝在同一台主機上,此時DOCKER_HOST位址通常是像unix://var/run/docker.socktcp://127.0.0.1:2375這類的本機路徑,使用from_env()可以輕鬆建立 client 連線。

當然,Docker Engine 必須先啟動。

Remote Docker daemon

但如果想要連線的 Docker daemon 是位於遠端主機,透過from_env()建立連線時,就要先設定DOCKER_HOST這個環境變數,以取代 SDK 提供的連線預設值。換句話說,在連線 localhost 時,這個變數可以不設定,而在連線遠端主機時,就必須要有

最簡單的方式如下:

1
2
3
4
5
import os
import docker

os.environ["DOCKER_HOST"] = 'tcp://22.22.22.22:2375' # ip 只是舉例
client = docker.from_env()

但最好還是直接在.env之類的環境設定檔案把環境變數值設定好,並在 app 中取得。在程式碼中明示任何帳號、密碼、內部主機位址都不妥,應盡量避免

如果遠端主機要求使用 SSL 連線,則連線位址要設定在DOCKER_TLS_VERIFY

from_env()是 Docker SDK 提供建立連線的捷徑,如果想使用更完整的參數,則要直接呼叫DockerClient類別來建立連線物件(實例),如下:

1
2
import docker
client = docker.DockerClient(base_url='unix://var/run/docker.sock')

這部分請直接參考文件,大部分情況使用from_env()應該已經足夠。

Docker socket 權限問題

即使是透過本機上的 socket 連接本機的 Docker daemon,程式也可能遇到連線問題,通常是程式 app 的執行權限不符合主機上 Docker socket 的要求,會出現類似下面的錯誤訊息:

1
Error while fetching server API version: ('Connection aborted.', PermissionError(13, 'Permission denied'))

此時可能要修改var/run/docker.sock檔案或 app 本身的權限,而這樣做是否安全,需具體考量,這裡只是點出這個潛在問題。

容器操作

Docker 最常見使用的就是容器操作,我們來大概了解一下。

建立並啟動容器:containers.run

先看個例子:

1
client.containers.run('alpine', command='echo hello world', ports={'2222/tcp': 3333})

方式是使用 SDK 中containers模組的run方法(method),再看一下文件run的使用說明:

run(imagecommand=None**kwargs)

Run a container. By default, it will wait for the container to finish and return its logs, similar to docker run.

簡單來說,執行run的結果,即是依據給定的參數,來建立相對應的Container物件,和你使用 CLI 執行docker container run指令很類似。

從文件中可知run方法具有兩個位置參數:imagecommandimage呼叫時一定不可省略,而command有參數預設值None,呼叫時可以省略,位置參數的特色是可以直接依順序賦值調用,不需使用參數名稱,如下:

1
client.containers.run('alpine', 'echo hello world')

從這上述兩個例子可以輕易看出,這裡的參數就相當於我們使用 CLI 時的 flag(也是參數啦)!很容易理解與轉換,換句話說,本段開頭的第一個例子可以理解為下面的 CLI 指令:

1
docker container run -p 2222:3333 alpine echo hello world

containers模組的角色相當於 CLI 中的docker container,了解怎麼建立與啟動容器後,剩下就是容器的取得與操作問題了。

建立時即取得容器物件:detach=True

程式成功執行containers.run方法後,容器就會建立及啟用,但若想要直接取得該容器物件以進行後續操作,則需要在呼叫run方法時加上detach=True參數,類似於 CLI 中的-d

1
2
>>> client.containers.run('alpine', detach=True)
<Container '45e6d2de7c54'>

雖然都是 detach 的意思,但 CLI 使用-d通常是為了不要讓容器的 process 佔用前景,而這裡是程式執行,沒有前景佔用問題,detach=True的主要意義在於取得容器。

取得容器並存為變數:

1
container = client.containers.run('alpine', detach=True)

取得容器物件通常是想要進一步存取該容器的相關資訊,比如容器 id、logs 等:

1
2
container_id = container.id
logs = container.logs

容器物件的所有屬性及方法,請參考文件

取得已存在的容器物件:containers.get

上一段講的是建立的時候順便取得容器物件,而如果要取得「已存在」的容器,不論容器是否執行中,都可以透過容器 id 或容器名稱取得——使用get方法:

1
2
3
# 無論 id 或名稱格式皆為「字串」
container = client.containers.get('<容器id>')
container = client.containers.get('<容器名稱>')

這裡的容器 id 和使用 CLI 一樣,可以只取前面幾位即可,比如只取'45e6',只要沒有重複的容器 id 就能成功取得,反之則會出 error。不過這畢竟是程式執行,謹慎見起,建議還是不要取太少位,但可以不必是全部。

常用容器操作例示

取得特定容器物件後,才能對它進行操作,這裡就稍為演示一下幾個常用的操作即可。

刪除容器

1
2
container.remove()
container.remove(force=True)

相當於docker container rmdocker container rm -f

啟動容器

1
container.start()

相當於docker container start

停止容器

1
container.stop()

相當於docker container stop


小結

可以看出,只要熟悉 CLI 操作指令,使用 SDK 也會很容易上手。可以想見,平常我們透過 CLI 操作 Docker ,其實也是內部 SDK 幫你把指令轉換為程式碼再與 Docker daemon 進行溝通。

更多操作請參考文件,本文僅稍作演示,希望讓第一次使用的人在心中有個輪廓。