作為一個開發者,當你從事一項服務時,你會面臨一個工作環境的問題。當我說到工作環境時,我想到的不是IDE、堆疊、作業系統、庫等等。我想的是我們的服務所處的環境。
這些天,我們的服務通常被裝在一些容器裡,放在某種分散式系統中。大多數容器和其他移動部件都由Kubernetes、Nomad和類似的協調系統控制。
無論它們有多大區別,它們都控制著容器化服務。他們做的不僅僅是控制容器;至少,我們關心的是他們擁有容器。
問題
所以,我正在為一些服務開發一個功能。當我檢查程式碼時,我意識到該服務有外部依賴性:其他服務,一些儲存(資料庫),訊息系統(Kafka)。
在理想的世界裡,我不會太在意。我會新增一個API端點和一些DTOs,並對外部服務進行一些呼叫。為了確保一切正常,我會新增一些單元測試。
後來,當我完成程式碼時,我會把它推到工作環境中,以測試服務與其他元件的整合。而一切都很順利。
在現實世界中,並不是這樣的。當你使用新做的功能時,會存在一些bug、錯誤的假設和新發現的約束。
這裡的痛苦是等待程式碼被編譯和部署。我做了一系列的小改動,一個接一個。每次修改後,我都會把程式碼推送到工作環境中。然後你就進入了一個提交、推送、構建和測試的迴圈,如果通過CI來完成,就會非常慢。
想法: 複製所需的依賴關係
簡單。複製所需的依賴關係。
但是,由於你的依賴性有依賴性,你必須複製這些依賴性,然後這些可以有依賴性,等等。這是否意味著複製整個系統?不,那是不聰明的。我們需要弄清楚所需依賴關係的最小集合。我們的想法是隻複製一組最小的依賴關係。
好的方面是,複製不需要像在工作環境中那樣精確。它可以用更少的記憶體和CPU工作。例如,假設工作環境中的Postgres資料庫版本為14.1.1。在這種情況下,你將使用相同的Postgres版本,但更輕(記憶體和CPU更少)。儲存在副本中的資料將是原始資料的一個片段。
如何做到這一點?
由於有了容器,你可以快速地執行任何程式,而不需要混亂的安裝。如果你不相信我,試著在你的本地機器上安裝Postgres資料庫,然後用Docker做同樣的事情(執行容器化的Postgres)。然後比較一下經驗,特別是如果你需要執行同一個服務的幾個例項,但不同的版本。
所以,容器。我認為,docker compose是這項工作的完美選擇。
例子
讓我描述一下情況:
- 我的目標服務,名為app
- 依賴於名為next的服務
- app的依賴性為Postgres DB
- next服務的MariaDB的依賴關係
- Kafka的依賴關係
讓我快速描述一下配置。
服務app和next
這兩個服務共享一個程式碼庫。在容器啟動之前,必須先構建一個映象。映象是通過兩步docker構建的:
FROM golang:latest as builder WORKDIR /app COPY . /app/ RUN go mod tidy RUN go build -o app FROM golang:buster WORKDIR /app COPY --from=builder /app/app /app/ ENTRYPOINT [ "/app/app" ]
但配置是不同的。服務app使用來自檔案的配置,而next服務從環境變數中獲得配置。同時,服務app從Kafka寫和讀,所以它需要等待broker服務。
在另一個方面,next服務只有一個依賴:mariaDB。
服務app:
app: container_name: echo build: . environment: KAFKA_BROKER: "broker:29092" ports: - 9999:9999 volumes: - ./configs:/app/configs depends_on: - liquibase_pg - broker restart: always
指令:
build: .
它告訴Docker,映象需要先被構建。構建環境是當前目錄。
接下來,指示Docker將一個卷從本地FS掛載到容器FS:
volumes: - ./configs:/app/configs
並等待依賴性:
depends_on: - liquibase_pg - broker
其中liquibase_pg是一個一次性服務,在pg服務啟動後啟動,以便用Liquibase建立DB模式。只有當模式被建立後,app服務才能啟動。
next服務:
next: container_name: beta build: . ports: - 8888:8888 depends_on: - liquibase_maria environment: DB_HOST: maria DB_PORT: 3306 DB_USER: docker DB_PASSWORD: password DB_NAME: docker DB_TYPE: MARIA APP_PORT: 8888 TARGET: "http://echo:9999" ERROR_RATE: 10 DELAY: 3000 restart: always
next服務是等待Liquibase在MariaDB中建立模式。
其他服務
其他服務代表依賴性: Postgres DB、MariaDB、Kafka broker和Liquibase。有趣的是,設定所有這些依賴關係是非常容易的。如果你在官方文件或DockerHub上搜尋它們,你會發現關於設定它們的說明。
最重要的是要設定正確的服務啟動順序。例如,Kafka代理應該在任何使用它的服務之前啟動。DB的情況也是如此。
關於名稱
有一件事需要注意:服務名稱和容器名稱。它們可以是不同的。但如果它們是相同的,那就更好了。這將使你的生活更容易。在我的例子中,它們是不同的。原因是要明確這種區別。
在使用docker compose命令時,你會使用服務名稱,因為你會與服務進行互動。另一方面,容器只看到容器。這意味著你將使用容器名稱來稱呼另一個容器。例如,在我的例子中,app服務容器是echo。它週期性地呼叫另一個容器。為了稱呼那個容器,我必須使用容器名:
TARGET="http://beta:8888"
原因是Docker使它的網路,而這些容器是通過名字來解決的。看看服務next環境變數:
environment: DB_HOST: maria DB_PORT: 3306 DB_USER: docker DB_PASSWORD: password DB_NAME: docker DB_TYPE: MARIA APP_PORT: 8888 TARGET: "http://echo:9999" ERROR_RATE: 10 DELAY: 3000
當一切都設定好後,你可以用一個命令啟動整個系統:
docker compose up -d
重建應用程式、日誌和停止工作
你設定了這一切。開始對你的功能進行工作,在某個時刻,你想看看你做了什麼。現在你必須重建你的app:
docker compose -f <compose file> up --detach --build <service name>
使用預設的docker-compose檔名,你可以省略帶有檔名的部分:compose.yml。
要獲得日誌:
docker compose logs -f <container name>
要停止整個系統:
docker compose down
要停止並刪除所有卷:
docker compose down -v
總結
我希望你喜歡這個想法。它可以做得更好,但這是一個好的開始。我相信你可以改進它。如果你有任何問題,請隨時提出。
評論留言