Docker 容器化技術能有效確保開發環境一致性,但實際操作中,容器生命週期管理不當會破壞一致性。容器映像是容器的基礎,Docker 會先檢查本地倉函式庫是否存在所需映像,若無則會提取或建立。然而,即使遠端倉函式庫的 latest 標籤更新,本地已有的 latest 映像檔並不會自動更新,這可能導致版本不一致。為確保一致性,需定期清理舊容器和映像檔,並養成更新本地映像檔的習慣。清理指令包含 docker stop、docker rm、docker image prune 和 docker system prune 等。此外,使用 Docker Compose 能簡化多容器應用程式的管理,透過定義服務、網路和卷,確保開發和生產環境的一致性。同時,使用特定版本標籤、docker pull 更新映像檔、docker build --no-cache 重建映像檔,以及在 docker-compose 中設定 pull_policy: always 也是確保一致性的有效方法。除了過時映像檔,本地卷差異、不同硬體和複雜的容器組合也可能造成環境不一致,需要特別注意。
容器化技術
使用容器化技術是確保開發環境一致性和可重複性的有效方法。容器將程式碼及其依賴項封裝成一個單元,能夠在任何地方執行。這意味著無論是在本地開發還是在生產環境中執行,程式碼的執行環境都保持一致,從而減少了因環境差異而導致的問題。
容器化技術的優勢
容器化技術因 Docker 的工作而變得流行,包括其產品供應以及對開放容器倡議(Open Container Initiative)的貢獻,該倡議致力於制定容器化的標準。本章將使用 Docker 的範例來說明如何使用容器。
例項解析
假設你正在開發一個執行在 Spark 上的資料轉換任務。如果沒有容器化技術,你可能會在本地安裝 Spark 及其依賴項。然而,當另一個開發者加入並安裝了新版本的 Spark 時,生產環境可能因為某些資料消費者執行的 Spark 查詢所需的依賴項而被固定在特定的 Spark 版本上。這樣一來,開發和生產環境中就存在三個不同版本的依賴項,可能導致程式碼在不同環境中的行為差異,且難以重現和調查差異的根本原因。
曾經有過這樣的案例:開發者在使用某個資料函式庫的新功能時,因為開發環境與生產環境版本不一致,導致開發的功能在生產環境中不可用。最終,這項工作不得不被廢棄。
容器生命週期管理
雖然容器化技術有助於建立一致且可重複的開發流程,但容器生命週期的實際情況可能會在實踐中破壞這種一致性。Docker 容器和映像檔可能會像不請自來的房客一樣,除非強制移除,否則不會離開。實際上,這通常是容器化環境之間不比對的主要原因。
讓我們來看看容器的生命週期。容器是從映像檔建立的。映像檔可以指定為倉函式庫中預建映像檔的連結,或透過指定 Dockerfile 來建立映像檔。在這兩種情況下,Docker 在建立容器時首先會檢查本地倉函式庫中是否存在所需的映像檔。如果不存在,它會提取或建立映像檔,並將其新增到本地倉函式庫。
圖示說明:容器生命週期
此圖示顯示了執行帶有 latest 標籤的 Postgres 映像檔的容器生命週期。
內容解密:
- 此圖示呈現了帶有
latest標籤的 Postgres 映像檔如何隨時間更新,但本地倉函式庫中的映像檔不會自動更新。 - 在 T0 時,建立一個使用
postgres:latest的容器,由於本地沒有對應映像檔,因此會從官方倉函式庫提取。 - 在 T1 之後,即使官方倉函式庫中的
latest標籤更新為新的映像檔,本地已經提取的映像檔不會自動更新。 - 在 T2,容器繼續執行,直到 T3 時被銷毀。
- 圖示強調了即使用
latest標籤,本地實際執行的映像檔版本可能與遠端倉函式庫最新的版本不一致,因為舊映像檔仍保留在本地倉函式庫中。
管理容器生命週期以確保一致性
為瞭解決上述問題,需要定期清理舊的或未使用的 Docker 映像檔和容器,以避免版本不一致的問題。此外,也應該養成定期更新本地映像檔的習慣,以確保開發環境與生產環境的一致性。
程式碼範例:清理 Docker 容器和映像檔
# 列出所有正在執行的容器
docker ps
# 停止並刪除所有正在執行的容器
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
# 刪除未使用的映像檔
docker image prune -af
# 清理未使用的 Docker 資源
docker system prune -af
內容解密:
docker ps:列出所有正在執行的容器,用於檢查目前有哪些容器正在執行。docker stop $(docker ps -aq):停止所有正在執行的容器,docker ps -aq用於取得所有容器的 ID。docker rm $(docker ps -aq):刪除所有停止的容器,同樣使用docker ps -aq取得所有容器的 ID。docker image prune -af:刪除所有未被使用的映像檔,-af表示強制刪除所有未使用的映像檔,無需確認。docker system prune -af:清理未使用的 Docker 資源,包括停止的容器、未使用的網路和未使用的映像檔等,同樣是強制執行無需確認。
使用Docker容器開發的挑戰與最佳實踐
在軟體開發過程中,Docker容器提供了一種方便且一致的環境來佈署和執行應用程式。然而,如果不正確地管理容器和映像檔,就可能會遇到一些問題。
保持容器和映像檔更新
當您使用Docker容器時,很容易忘記映像檔可能已經過時。假設您在T0時間下載了postgres:latest映像檔並建立了一個容器。稍後,在T1時間,您再次執行相同的命令來執行Postgres,因為postgres:latest映像檔已經存在於您的本地倉函式庫中,Docker將根據該映像檔建立一個新的容器。然而,這個映像檔可能已經不是最新的,因為Postgres Docker倉函式庫中的postgres:latest映像檔已經被更新了兩次。
為瞭解決這個問題,您需要養成定期刪除和重新建立映像檔和容器的習慣。就像您可能會將程式函式庫或模組固定到特定版本以防止“最新”版本變更造成意外中斷一樣,使用特定版本的Docker映像檔也可以達到同樣的效果。
最佳實踐:
- 使用
docker pull下載新的映像檔,然後重新建立容器。 - 如果您使用Dockerfile來建立映像檔,請使用
docker build --no-cache來重建映像檔,而不依賴任何現有的層。 - 如果您使用docker-compose,可以在服務定義中指定
pull_policy: always來總是取得最新的映像檔。
檢查映像檔和容器的建立時間戳
如果您不確定您的容器是否正在執行最新的映像檔,可以檢查映像檔和容器的建立時間戳。例如,要檢視本地有哪些Postgres映像檔,可以執行:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
postgres latest f8dd270e5152 8 days ago 376MB
postgres 11.1 5a02f920193b 3 years ago 312MB
要檢視容器正在執行的映像檔,可以先列出容器,使用-a來顯示已停止的容器:
$ docker container ls -a
CONTAINER ID IMAGE CREATED STATUS
2007866a1f6c postgres 48 minutes ago Up 48 minutes
7eea49adaae4 postgres:11.1 2 hours ago Exited (0) 2 hours ago
然後,您可以使用docker inspect來取得有關容器所使用的映像檔的詳細資訊。
其他容器相關問題
除了過時的容器和映像檔之外,還有一些其他原因可能導致“它在我的機器上可以運作”的問題:
本地卷差異
當您掛載本地捲到容器中時,卷中的檔案可能會與容器中的檔案發生衝突。例如,如果您在本地目錄中安裝了Python函式庫,但該函式庫的版本與容器中的版本不同,則可能會導致問題。
不同硬體
如果開發人員使用不同的硬體,例如Linux和Mac,或者Intel和M1晶片的Mac,則可能會遇到不同的Docker問題。
容器組合
當您需要為資料管線開發設定多個服務時,您可能需要使用Docker Compose來組合多個容器。
使用Docker Compose
Docker Compose提供了一種方便的方式來定義和執行多個容器。例如,對於圖5-6所示的流式管線,您可以建立一個Docker Compose檔案,其中包含Kafka、Postgres和資料轉換服務。每個服務條目都會建立一個新的容器。
# transform-docker-compose.yml
version: '3'
services:
kafka:
image: confluentinc/cp-kafka:latest
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
postgres:
image: postgres:latest
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypass
POSTGRES_DB: mydb
transformation:
build: .
depends_on:
- kafka
- postgres
內容解密:
此Docker Compose檔案定義了三個服務:Kafka、Postgres和資料轉換。Kafka服務使用confluentinc/cp-kafka:latest映像檔,並將容器的9092埠對映到主機的9092埠。Postgres服務使用postgres:latest映像檔,並設定了環境變數來建立資料函式庫和使用者。資料轉換服務則是根據當前目錄中的Dockerfile建立的,並依賴於Kafka和Postgres服務。
在Docker Compose中建立有效的本地開發環境
在現代軟體開發中,容器化技術已成為主流。Docker Compose作為一種強大的工具,能夠簡化多容器應用程式的開發、測試和佈署流程。本文將探討如何使用Docker Compose建立有效的本地開發環境,涵蓋服務組態、環境變數管理、分享組態等多個關鍵導向。
Docker Compose基礎組態
首先,讓我們看看一個基本的Docker Compose檔案,它定義了多個服務之間的關係和組態。
services:
kafka:
image: confluentinc/cp-kafka:7.2.1
# ... 其他組態
postgres:
image: postgres:latest
# ... 其他組態
transform:
image: image/repo/transform_image:latest
# ... 其他組態
networks:
migration:
內容解密:
services定義了多個服務,包括kafka、postgres和transform,這些服務共同構成了應用程式的基礎。- 每個服務都指定了所使用的Docker映像,例如
confluentinc/cp-kafka:7.2.1和postgres:latest。 networks部分定義了服務之間的網路連線,確保transform可以發布訊息到kafka,而postgres可以從kafka讀取資料。
原生程式碼測試與生產環境依賴
在本地開發環境中,我們經常需要在生產級別的依賴下測試原生程式碼變更。以下是一個典型的transform容器定義:
transform:
image: container/repo/transform_image:latest
container_name: transform
environment:
KAFKA_TOPIC_READ: migration_data
KAFKA_TOPIC_WRITE: transformed_data
volumes:
- ./code_root/transform:/container_code/path
networks:
migration:
內容解密:
image指定了用於transform服務的Docker映像,這與生產環境中使用的映像相同。environment部分定義了環境變數,例如KAFKA_TOPIC_READ和KAFKA_TOPIC_WRITE,這些變數被Spark作業用來決定讀取和寫入哪些Kafka主題。volumes掛載了本地的轉換程式碼目錄到容器內的指定路徑,從而實作了原生程式碼變更的實時測試。
使用Dockerfile進行開發
如果需要升級或新增函式庫,可以建立一個Dockerfile來新增依賴項到現有的映像中:
FROM container/repo/transform_image:latest
WORKDIR /
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
內容解密:
FROM指令指定了基礎映像,即生產環境中使用的映像。COPY指令將本地的requirements.txt檔案複製到容器內。RUN指令安裝了指定的Python依賴項。
環境變數管理
環境變數的使用鼓勵良好的實踐,例如避免將憑據資訊硬編碼到Compose檔案中。以postgres容器定義為例:
postgres:
image: postgres
container_name: postgres
environment:
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASS}
volumes:
- pg_data:/var/lib/postgresql/data
networks:
- migration
內容解密:
environment部分使用${PG_USER}和${PG_PASS}等環境變數來設定Postgres使用者名稱和密碼。- 這種做法避免了敏感資訊被直接寫入Compose檔案。
分享組態
當有多個服務在不同的Compose檔案中重複使用時,可以將分享組態提取到單獨的檔案中。例如:
# dev-docker-compose.yml
services:
postgres:
image: postgres
environment:
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASS}
volumes:
- pg_data:/var/lib/postgresql/data
networks:
- migration
volumes:
pg_data:
name: postgres
networks:
migration:
內容解密:
- 將分享的Postgres服務組態和網路組態提取到
dev-docker-compose.yml檔案中。 - 這種做法有助於保持不同團隊和專案之間的組態一致性。
共用Docker Compose組態以簡化開發環境
在團隊開發中,共用Docker Compose組態是一種重要的實踐,可以確保團隊成員使用相同的環境設定。以下是一個範例,展示如何共用Compose組態以簡化開發環境。
共用Compose檔案
首先,建立一個共用的Compose檔案dev-compose-file.yml,其中包含多個服務共用的設定:
version: '3'
services:
postgres:
image: postgres
environment:
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASS}
networks:
- migration
api:
environment:
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASS}
POSTGRES_PORT: ${PG_PORT}
POSTGRES_HOST: ${PG_HOST}
networks:
- migration
depends_on:
- postgres
內容解密:
此共用Compose檔案定義了兩個服務:postgres和api。postgres服務使用官方Postgres映像,並設定了環境變數。api服務則依賴於postgres服務,並共用了部分環境變數。
個別服務的Compose檔案
接下來,為不同的團隊建立個別的Compose檔案。例如,api-docker-compose.yml僅包含與API服務相關的設定:
version: '3'
services:
api:
image: container/repo/api_image:dev_tag
container_name: api
volumes:
- ./code_root/api:/container_code/path
內容解密:
此API Compose檔案定義了api服務的映像標籤和容器名稱,並掛載了原生程式碼到容器中。由於共用Compose檔案中已定義了api服務對postgres的依賴,因此無需在此檔案中重複定義。
同樣地,transform-docker-compose.yml包含與轉換服務相關的設定:
version: '3'
services:
kafka:
# kafka服務的設定
transform:
# transform服務的設定
api:
image: container/repo/api_image:latest
container_name: api
內容解密:
此轉換Compose檔案定義了kafka、transform和api服務。其中,api服務使用了不同的映像標籤,並且沒有掛載原生程式碼。
啟動容器
要啟動容器,可以使用以下命令:
docker compose -f transform-docker-compose.yml -f dev-compose-file.yml up
docker compose -f api-docker-compose.yml -f dev-compose-file.yml up
內容解密:
在這些命令中,-f選項用於指定要使用的Compose檔案。最後指定的檔案會覆寫前面檔案中相同的設定。這使得團隊可以根據需要自定義服務的設定。
使用擴充套件欄位共用變數
為了進一步減少重複,可以使用擴充套件欄位共用變數。例如,在dev-docker-compose.yml中:
x-environ: &def-common
environment:
&common-env
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASS}
services:
postgres:
image: postgres
environment:
<<: *common-env
api:
environment:
<<: *common-env
POSTGRES_PORT: ${PG_PORT}
POSTGRES_HOST: ${PG_HOST}
內容解密:
此範例中,x-environ是一個擴充套件欄位,定義了共用的環境變數。postgres和api服務都使用了這些共用變數,從而避免了重複定義。
減少外部依賴
透過使用Docker和共用Compose組態,可以簡化開發環境並減少外部依賴。這有助於降低成本並加快開發速度。圖5-8展示了一個短暫的開發環境設定,其中部分服務執行在本地,部分執行在雲端。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Docker容器生命週期管理與最佳實踐
package "Docker 架構" {
actor "開發者" as dev
package "Docker Engine" {
component [Docker Daemon] as daemon
component [Docker CLI] as cli
component [REST API] as api
}
package "容器運行時" {
component [containerd] as containerd
component [runc] as runc
}
package "儲存" {
database [Images] as images
database [Volumes] as volumes
database [Networks] as networks
}
cloud "Registry" as registry
}
dev --> cli : 命令操作
cli --> api : API 呼叫
api --> daemon : 處理請求
daemon --> containerd : 容器管理
containerd --> runc : 執行容器
daemon --> images : 映像檔管理
daemon --> registry : 拉取/推送
daemon --> volumes : 資料持久化
daemon --> networks : 網路配置
@enduml此圖示展示了開發環境的不同部分如何協同工作。透過減少外部依賴,可以提高開發效率並降低成本。