多環境佈署策略在軟體開發流程中至關重要,能有效提升系統穩定性和可靠性,但也伴隨著環境耦合、成本增加等挑戰。本文將探討這些挑戰,並提供降低成本的策略,例如自動擴充套件設定、資源分配最佳化、生命週期管理等,同時也強調安全性與成本效益之間的平衡。此外,建立一致且可重複的開發環境,特別是在微服務架構中,也相當關鍵。短暫環境、排程設定環境、本地開發與容器化技術都能有效提升開發效率。然而,容器生命週期管理和硬體差異等問題也需要仔細考量。Docker 和 Docker Compose 工具則能協助解決這些問題,提供一致的開發環境,並簡化多容器應用程式的管理。透過共用 Docker Compose 組態,更能減少冗餘設定,提升團隊協作效率,確保開發環境的一致性。

多環境佈署的挑戰與成本考量

在軟體開發與資料處理的過程中,多環境佈署是一項常見的實踐,旨在確保系統的穩定性與可靠性。然而,這種做法也帶來了諸多挑戰與成本問題。

環境耦合的風險

當生產(PROD)資料被同步到測試(TEST)環境時,這兩個環境之間就形成了耦合。這種情況下,測試環境的運作可能會干擾到生產環境的資料,導致無法正常執行測試流程。曾經有一次,我們的團隊因為這個問題而停止了在測試環境中執行完整的資料管道測試。結果,我們只能在開發(DEV)環境中使用小規模的樣本資料集進行測試。這種做法使得我們在面對實際生產環境中的大規模資料時,難以及時發現和解決潛在的問題。

環境設計的重要性

經歷了上述挑戰後,我們深刻體會到環境設計的重要性。一個良好的環境設計應該能夠支援完整的管道測試和分階段發布,同時兼顧不同資料層級的需求。雖然理論上沒有固定的環境數量,但實際上需要根據團隊的具體需求和技術能力進行調整。

靈活的環境組態

有經驗的維運工程師曾指出,如果不需要額外溝通來協調環境的使用,那麼很可能意味著環境設定不夠理想。這是因為多個環境會帶來資源消耗和工程師的時間成本。例如,在某些情況下,可以考慮合併開發和測試環境,以減少資源浪費。

成本考量

建立和維護多個資料管道環境需要相當的成本。首先,每個環境都需要具備完整的資源,包括雲端儲存、資料函式庫和計算資源等。這些資源不僅需要付費使用,還需要投入工程師的時間進行管理和維護。

資料複製的成本

在我們的案例中,由於安全規定,我們需要在測試環境中建立生產資料的副本,這意味著需要儲存和管理大量資料。這不僅增加了儲存成本,也需要額外的流程來同步和管理這些資料。

降低成本的策略

為了降低這些成本,可以採用一些策略:

  1. 自動擴充套件設定:在非生產環境中使用自動擴充套件設定,以減少閒置時的資源消耗。
  2. 資源分配最佳化:根據實際需求為不同環境分配適當的資源,避免資源浪費。
  3. 生命週期管理:實施適當的資料生命週期管理策略,例如定期刪除過期的測試資料,以減少儲存成本。

安全性與成本效益

在我們的案例中,由於安全限制,我們不得不建立生產資料的副本,這大大增加了成本。如果能夠透過雲端儲存桶的存取策略來提供只讀許可權,就可以避免額外的複製成本。

環境可用性

值得注意的是,環境並不需要一直保持可用狀態。例如,透過基礎設施即程式碼(IaC)的實踐,可以動態建立和銷毀沙盒環境,以供開發人員進行測試。

動態環境建立

這種做法不僅可以節省資源,還能提高開發效率和靈活性。透過這種方式,可以根據實際需求動態地建立和組態環境,以滿足不同的測試和開發需求。

有效開發環境的建立

在軟體開發過程中,建立一致且可重複的開發環境至關重要,特別是在微服務架構中。這不僅能提高開發效率,還能減少錯誤和溝通不良的問題。

短暫環境的優勢

使用短暫環境(ephemeral environments)可以降低成本,因為這些環境只在需要時存在,並且會定期被銷毀。這種方法還能測試災難應對準備度,因為它能定期演練基礎設施程式碼。

考慮環境的複雜性和規模

對於分析平台來說,短暫環境可以在大約15分鐘內準備就緒,因為它們需要上線的雲端資源數量有限,並且不需要進行資料函式庫還原。然而,如果需要還原大型資料函式庫,則可能需要數小時的準備時間。長時間的啟動並不意味著不應使用短暫環境,只是需要在評估使用案例時考慮啟動和拆除時間。

根據排程設定環境

還可以透過根據排程設定環境來節省成本。例如,分析平台使用Hive呈現資料,並將雲端儲存作為後端資料檔案。為了存取這些資料,需要執行叢集供分析使用者使用,但他們通常只在24小時中的10小時內工作。我們透過設定叢集在第一個查詢發出時啟動,並在特定時間自動終止來降低成本。

本地開發

在開發團隊中工作時,擁有一致且可重複的開發實踐有助於快速推進並減少錯誤和溝通不良的問題。在微服務環境中,這一點尤為重要,因為管道與不同個人或團隊開發的多種服務互動。

檔案記錄和自動化溝通

一種保持團隊同步的方法是記錄開發方法並透過自動化溝通機制來強化它。例如,如果在開發過程中需要連線到資料函式庫、API或雲端服務,請在團隊中分享執行此操作的方法。對於雲端,這可以包括建立憑證,例如使用Google Auth組態AWS憑證。

容器化

使用容器是確保一致且可重複開發環境的一種方法。容器將程式碼和依賴項封裝到一個單一的包中,可以在任何地方執行。這意味著無論是在本地開發還是在生產環境中執行,程式碼都執行在相同的環境中,從而有助於緩解由於開發程式碼的方式與佈署時的行為之間的差異所產生的問題。

容器生命週期

雖然使用容器有助於建立一致且可重複的開發過程,但容器生命週期的現實可能會在實踐中破壞這一宣告。Docker容器和映像檔可能像壞掉的房客一樣,一旦邀請它們進入,就不會離開,直到你強迫它們離開。

# 使用官方的Postgres映像作為基礎映像
FROM postgres:latest

# 設定環境變數
ENV POSTGRES_DB=mydb
ENV POSTGRES_USER=myuser
ENV POSTGRES_PASSWORD=mypassword

# 初始化資料函式庫
COPY init.sql /docker-entrypoint-initdb.d/

內容解密:

此Dockerfile用於建立一個Postgres資料函式庫容器。首先,它使用官方的Postgres映像作為基礎映像。接著,它設定了幾個環境變數,包括資料函式庫名稱、使用者名稱和密碼。最後,它將一個SQL檔案init.sql複製到/docker-entrypoint-initdb.d/目錄下,用於初始化資料函式庫。

-- init.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);

INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com');

內容解密:

此SQL檔案用於初始化Postgres資料函式庫。它建立了一個名為users的表格,包含idnameemail欄位。接著,它向users表格中插入了一條記錄。

容器生命週期

圖表翻譯: 此圖表展示了容器的生命週期。首先,從開始到建立容器。接著,檢查所需的映像是否存在。如果存在,則使用本地映像;如果不存在,則從倉函式庫提取映像。提取或使用本地映像後,執行容器。容器執行後,可以繼續執行,直到被銷毀,最終結束。

使用 Docker 容器時的常見問題與解決方案

在使用 Docker 容器進行本地開發時,開發者可能會遇到一些常見的問題,例如容器與映像檔(image)過時、本地卷(volume)差異、硬體差異等。這些問題可能會導致「在我的機器上可以運作」(it works on my machine)的情況發生。

容器與映像檔過時

當你使用 docker run 命令執行一個容器時,Docker 會根據指定的映像檔名稱下載映像檔並建立容器。然而,如果映像檔已經存在於本地倉函式庫中,Docker 就會直接使用本地的映像檔,而不會檢查遠端倉函式庫是否有更新的版本。

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
postgres     latest    f8dd270e5152   8 days ago    376MB
postgres     11.1      5a02f920193b   3 years ago   312MB

內容解密:

  • docker images 命令用於列出本地倉函式庫中的映像檔。
  • REPOSITORYTAG 列分別顯示映像檔的名稱和標籤。
  • IMAGE ID 是映像檔的唯一識別碼。
  • CREATED 列顯示映像檔的建立時間。
  • SIZE 列顯示映像檔的大小。

為了避免使用過時的容器和映像檔,開發者應該養成定期刪除和重新建立容器和映像檔的習慣。可以使用 docker pull 命令下載最新的映像檔,然後重新建立容器。如果使用 Dockerfile 建立映像檔,可以使用 docker build --no-cache 命令重新建立映像檔而不使用快取。

本地卷差異

當你將本地目錄掛載到容器中時,容器會使用本地目錄中的檔案和設定。如果本地目錄中有不正確或過時的檔案,就可能會導致容器中的應用程式出現問題。

本地卷掛載示意圖

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 多環境佈署挑戰與成本最佳實踐

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

圖表翻譯: 此圖示展示了本地目錄掛載到容器的過程。本地目錄中的檔案會被掛載到容器中,容器中的應用程式會使用這些檔案。

硬體差異

當開發團隊使用不同的硬體平台(如 Linux 和 Mac)時,Docker 容器可能會出現相容性問題。這是因為不同的硬體平台可能需要不同的 Docker 設定和依賴項。

為瞭解決這個問題,可以使用 Docker Compose 的 platform 屬性來指定容器的平台。此外,也可以考慮使用雲端開發環境(如 Google Cloud Workstations)來提供一個統一的開發環境,從而減少硬體差異帶來的問題。

容器組合

當需要建立多個服務的資料管道時,可以使用 Docker Compose 來管理多個容器的建立和組態。Docker Compose 檔案可以定義多個服務,每個服務對應一個容器。

Docker Compose 檔案範例

version: '3'
services:
  kafka:
    image: confluentinc/cp-kafka:latest
    ports:
      - "9092:9092"
  postgres:
    image: postgres:latest
    environment:
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword

內容解密:

  • version 指定 Docker Compose 檔案的版本。
  • services 定義多個服務,每個服務對應一個容器。
  • image 指定服務使用的映像檔。
  • ports 指定容器與主機之間的連線埠對映。
  • environment 指定容器的環境變數。

Docker Compose 可以簡化多個容器的管理和組態,從而提高開發效率和降低出錯率。

Docker Compose 在本地開發中的應用與最佳實踐

在現代軟體開發中,Docker Compose 已成為管理多容器應用程式的標準工具。本文將探討 Docker Compose 在本地開發環境中的應用,並提供最佳實踐的具體例項。

網路組態與服務間通訊

networks:
  migration:

在 Docker Compose 中,網路組態定義了哪些服務可以相互通訊。在上述範例中,所有服務都連線到 migration 網路,確保 transform 可以發布訊息到 kafka,而 postgres 可以從 kafka 讀取資料。

內容解密:

  1. 網路定義的重要性:明確定義網路可以控制服務間的通訊,增強安全性。
  2. migration 網路的作用:所有相關服務都加入此網路,實作服務發現和通訊。
  3. 避免預設網路的侷限性:自定義網路提供更細粒度的控制。

生產級映像與原生程式碼掛載

transform:
  image: container/repo/transform_image:latest
  environment:
    KAFKA_TOPIC_READ: migration_data
    KAFKA_TOPIC_WRITE: transformed_data
  volumes:
    - ./code_root/transform:/container_code/path

此組態使用生產級別的映像,同時掛載原生程式碼,實作快速迭代開發。

內容解密:

  1. 使用生產映像的好處:確保開發環境與生產環境的一致性。
  2. 環境變數的作用:組態 Spark 作業讀寫的 Kafka 主題。
  3. 程式碼掛載的優勢:無需重建映像即可測試原生程式碼變更。
  4. 掛載路徑的對應:本地路徑 ./code_root/transform 對應容器內路徑 /container_code/path

管理依賴與環境變數

postgres:
  image: postgres
  environment:
    POSTGRES_USER: ${PG_USER}
    POSTGRES_PASSWORD: ${PG_PASS}
  volumes:
    - pg_data:/var/lib/postgresql/data

此範例展示瞭如何使用環境變陣列態 Postgres 服務,並掛載資料卷持久化資料。

內容解密:

  1. 環境變數的安全性優勢:避免將敏感資訊硬編碼在 Compose 檔案中。
  2. 使用 .env 檔案的好處:集中管理環境變數,保持本地環境的整潔。
  3. 資料持久化的實作:透過掛載 pg_data 卷實作資料持久化。

共用組態的最佳實踐

當多個 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:

transform-docker-compose.yml

services:
  kafka:
    image: confluentinc/cp-kafka:7.2.1
    ...
  transform:
    ...

api-docker-compose.yml

services:
  postgres:
    ...
  api:
    ...

內容解密:

  1. 共用組態的重要性:減少重複組態,避免不同步問題。
  2. dev-docker-compose.yml 的作用:定義共用的 Postgres 服務和網路。
  3. 具體應用的靈活性:各個服務可以根據需要擴充套件或修改共用組態。

共用Docker Compose組態以簡化開發環境

在團隊開發過程中,共用Docker Compose組態可以大幅簡化開發環境的設定和管理。本文將介紹如何透過共用Compose檔案和使用擴充套件欄位來減少冗餘組態,並提高開發效率。

共用Compose檔案

為了方便不同團隊成員使用相同的環境組態,可以建立一個共用的Compose檔案。例如,建立一個名為dev-compose-file.yml的檔案,包含共用的環境變數和服務組態:

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

程式碼解析:

此共用組態檔案定義了postgresapi服務,並設定了相關的環境變數。api服務依賴於postgres服務,確保資料函式庫服務在API服務啟動前已經準備就緒。

針對特定團隊的Compose檔案

不同團隊可以根據自己的需求,建立簡化的Compose檔案,並與共用檔案結合使用。例如,API團隊可以建立api-docker-compose.yml

services:
  api:
    image: container/repo/api_image:dev_tag
    container_name: api
    volumes:
      - ./code_root/api:/container_code/path

程式碼解析:

此組態檔案定義了API服務使用的映象和掛載原生程式碼的卷,方便開發人員進行本地開發和除錯。

轉換團隊可以建立transform-docker-compose.yml

services:
  kafka:
    # kafka服務組態...
  transform:
    # transform服務組態...
  api:
    image: container/repo/api_image:latest
    container_name: api

程式碼解析:

此組態檔案定義了Kafka、Transform和API服務。注意,這裡的API服務使用的是latest標籤的映象,且沒有掛載原生程式碼。

啟動容器

使用以下命令啟動容器:

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

命令解析:

這些命令結合了特定團隊的Compose檔案和共用的dev-compose-file.yml,按照指定的順序載入組態,確保後載入的檔案中的組態覆寫前面的組態。

使用擴充套件欄位分享變數

在共用的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的擴充套件欄位,使用錨點&def-common和別名*common-env來分享環境變數。這樣可以避免重複定義相同的變數,提高組態的可維護性。

減少外部依賴

除了簡化本地開發環境的組態外,減少與外部依賴的互動也可以提高開發效率和降低成本。圖5-8展示了一個短暫的開發環境設定,其中部分服務執行在本地,部分執行在雲端。

圖表翻譯:

此圖展示了開發環境的架構,包括本地執行的排程器和雲端執行的叢集。資料集被複制到雲端儲存,並透過雲佇列提交作業訊息。作業完成後,結果被寫入雲端儲存,並支援Hive metastore。

透過簡化開發環境組態和減少外部依賴,可以顯著提高團隊的開發效率和生產力。共用Docker Compose組態和使用擴充套件欄位是實作這一目標的有效方法。