在現代軟體開發中,容器化技術已成為不可或缺的一部分。PeerSpace 公司利用 Docker 和 systemd 建立了一套高效的 Web 服務佈署和管理系統,實作了零停機佈署、簡化組態管理和提高系統穩定性。他們將所有服務容器化,並使用 systemd 管理容器的生命週期,透過環境變數檔案區分叢集設定、服務組態和敏感資訊,確保不同環境的一致性。佈署流程則透過指令碼自動化執行組態更新、容器映像下載、systemd 重新載入和服務重啟,同時結合日誌聚合和監控工具,提升系統的可觀測性。

利用 systemd 管理服務

在 PeerSpace 的系統架構中,每個服務都由 systemd 進行管理。Systemd 是一種服務管理工具,其設計理念源自於 macOS 的 launchd。它使用簡單的資料檔案(稱為單元檔案,unit files)來定義每個服務的生命週期,而非像傳統的服務管理工具那樣使用 shell 指令碼進行管理。

systemd 的基本架構與組態

PeerSpace 的服務僅依賴 Docker 程式作為其執行時的唯一依賴。Systemd 的依賴管理功能僅用於確保 Docker 正在執行,而不涉及服務啟動順序的管理。這些服務被設計為可以按任意順序啟動。

每個服務由以下部分組成:

  1. 容器映像檔(Container Image):定義了服務的執行環境。
  2. systemd 單元檔案(Systemd Unit File):定義了服務如何被 systemd 管理。
  3. 特定於該容器的環境變數檔案(Environment Variable File):儲存了特定於該服務的組態。
  4. 分享的環境變數檔案(Shared Environment Variable Files):包含全域性的組態引數。

systemd 單元檔案結構

所有的 systemd 單元檔案遵循相同的結構。首先,它們會在服務啟動前載入一系列環境變數檔案:

EnvironmentFile=/usr/etc/service-locations.env
EnvironmentFile=/usr/etc/service-config.env
EnvironmentFile=/usr/etc/cluster.env
EnvironmentFile=/usr/etc/secrets.env
EnvironmentFile=/usr/etc/%n.env

內容解密:

  • 這段組態確保每個服務都會載入一組通用的環境變數檔案,包括 service-locations.envservice-config.envcluster.envsecrets.env
  • 此外,還會載入一個特定於該服務的環境變數檔案,檔案名稱與單元檔案的名稱相同(例如,docker-search.service.env)。

接下來,單元檔案中包含了一些指令,以確保容器在啟動前被正確清理:

ExecStartPre=-/bin/docker kill %n
ExecStartPre=-/bin/docker rm -f %n

內容解密:

  • ExecStartPre 指令用於在服務啟動前執行特定的命令。
  • 這裡使用了 Docker 命令來殺死並移除同名的容器(如果存在)。%n 會被替換為單元檔案的全名。
  • 在 Docker 命令前加上 - 表示即使命令失敗也不會影響服務的啟動。

啟動容器

主要的 ExecStart 指令定義瞭如何啟動容器:

ExecStart=/bin/docker \
run \
-p "${APP_PORT}:${APP_PORT}" \
-e "APP_PORT=${APP_PORT}" \
-e "SERVICE_C_HOST=${SERVICE_C_HOST}" \
-e "SERVICE_D_HOST=${SERVICE_D_HOST}" \
-e "SERVICE_M_HOST=${SERVICE_M_HOST}" \
--add-host docker01:${DOCKER01_IP} \
--add-host docker02:${DOCKER02_IP} \
--volume /usr/local/docker-data/%n/db:/data/data \
--volume /usr/local/docker-data/%n/logs:/data/logs \
--name %n \
${IMAGE_NAME}:${IMAGE_TAG}

內容解密:

  1. 使用 EnvironmentFile 載入的環境變數來組態容器,例如透過 -p 匯出的埠號。
  2. 使用 --add-host 將叢集中其他主機的位址新增到容器的 /etc/hosts 檔案中。
  3. 將日誌和資料目錄對映到主機上的特定路徑,以便檢查是否有未預期的寫入操作。
  4. 容器的映像檔名稱和版本來自於 /usr/etc/%n.env 檔案中定義的環境變數。

停止容器與生命週期管理

最後,單元檔案中還定義瞭如何停止容器以及其他生命週期相關的組態:

ExecStop=-/bin/docker stop %n
Restart=on-failure
RestartSec=1s
TimeoutStartSec=120
TimeoutStopSec=30

內容解密:

  • ExecStop 指令定義了停止容器的方式。
  • Restart=on-failure 表示當容器異常離開時,systemd 會自動重啟它。
  • RestartSec=1s 設定重啟的間隔時間為 1 秒。
  • TimeoutStartSecTimeoutStopSec 分別設定了啟動和停止操作的超時時間。

叢集範圍內的組態管理

PeerSpace 將叢集的組態分成兩種型別:環境變數檔案和 systemd 單元檔案。環境變數檔案根據其變更頻率和敏感性被進一步劃分為不同的類別:

  • service-locations.env:包含叢集中所有服務的主機名稱,通常在不同叢集之間保持一致。
  • service-config.env:包含與服務本身相關的組態,理想情況下在執行相同版本服務的不同叢集之間保持一致。
  • secrets.env:包含敏感資訊,如金鑰等,在每個叢集上都不同。
  • cluster.env:包含叢集特有的組態,如資料函式庫字首、是否為測試或生產環境、外部位址等。

範例:cluster.env 與 service-locations.env

以下是 cluster.env 的範例內容:

CLUSTER_ID=alpha
CLUSTER_TYPE="test"
DOCKER01_IP=x.x.x.226
DOCKER02_IP=x.x.x.144
EXTERNAL_ADDRESS=https://somethingorother.com
LOG_STORE_HOST=x.x.x.201
LOG_STORE_PORT=9200
MONGODB_PREFIX=alpha
MONGODB_HOST_01=x.x.x.177
MONGODB_HOST_02=x.x.x.299
MONGODB_REPLICA_SET_ID=rs001

以下是 service-locations.env 的範例內容:

SERVICE_A_HOST=docker01
SERVICE_B_HOST=docker03
CLIENTLOG_HOST=docker02
SERVICE_D_HOST=docker01
...
SERVICE_Y_HOST=docker03
SERVICE_Z_HOST=docker01

圖表說明:

  graph LR;
    A[cluster.env] -->|包含叢集特有組態|> B[叢集組態];
    C[service-locations.env] -->|包含服務主機名稱|> B;
    D[secrets.env] -->|包含敏感資訊|> B;
    E[service-config.env] -->|包含服務相關組態|> B;
    B -->|被 systemd 單元檔案參照|> F[服務啟動];

圖表翻譯: 此圖示展示了不同的環境變數檔案如何被用於組態叢集和服務。它們最終被 systemd 單元檔案參照,以正確地啟動和管理服務。

容器佈署與管理的最佳實踐:PeerSpace 的經驗分享

在現代化的軟體開發與佈署流程中,容器技術已經成為不可或缺的一環。PeerSpace,一家專注於創新技術的公司,透過採用 Docker 容器技術,建立了一套極簡且高效的佈署與管理系統。本文將探討 PeerSpace 如何利用 Docker 實作開發、測試和生產環境的一致性,並分享他們在容器佈署與管理方面的寶貴經驗。

組態管理的重要性

PeerSpace 的系統架構圍繞著組態管理展開。他們將所有的組態資訊儲存在 Git 倉函式庫中,這不僅確保了組態的可追蹤性,還大大簡化了故障排查的過程。這種做法的核心優勢在於:

  1. 版本控制:所有的組態變更都透過 Git 進行版本控制,使得每一次變更都有跡可循。
  2. 一致性:透過統一的組態管理,確保了開發、測試和生產環境之間的一致性。
  3. 可回溯性:當出現問題時,可以快速定位到具體的組態變更,進行有針對性的修復。

組態管理的技術實作

PeerSpace 的組態管理依賴於以下幾個關鍵技術要素:

  • 環境變數:透過環境變數來控制服務的行為,使得同一套程式碼可以在不同的環境中靈活執行。
-e "SERVICE_D_HOST=${SERVICE_D_HOST}"

這條命令展示瞭如何透過環境變數傳遞主機資訊給容器。

  • 主機資訊注入:利用 --add-host 引數,將 Docker 主機的 IP 位址注入到容器中,實作容器與主機之間的網路連通。
--add-host docker01:${DOCKER01_IP}

這種方式使得容器可以透過主機名稱直接存取對應的主機。

佈署流程的最佳實踐

PeerSpace 的佈署流程分為三個主要步驟:

  1. 組態變更:首先在 Git 倉函式庫中進行組態變更,並提交更新。
  2. 組態推播:透過指令碼將更新後的組態推播到各個主機的暫存區。
  3. 佈署生效:在各個主機上執行佈署指令碼,將新的組態生效。

佈署流程的詳細步驟

在每次佈署過程中,PeerSpace 會執行以下具體操作:

  1. 複製組態檔案:將暫存區的組態檔案複製到最終位置,包括 systemd 單元檔案、分享組態檔案、服務特定組態檔案以及解密後的金鑰檔案。
  2. 下載映象:根據服務組態檔案中的定義,下載必要的 Docker 映象。
  3. 重新載入 systemd 組態:確保 systemd 能夠識別最新的單元檔案。
  4. 重啟服務:重啟對應的 systemd 單元,以使新的組態生效。
# 示例指令碼片段
for service in ${SERVICES_LIST}; do
    # 複製組態檔案
    cp /staging/${service}/config/* /etc/${service}/
    # 下載映象
    docker pull ${REGISTRY}/${service}:${VERSION}
    # 過載 systemd 組態
    systemctl daemon-reload
    # 重啟服務
    systemctl restart ${service}
done

內容解密:

此指令碼片段展示了 PeerSpace 如何自動化佈署流程。首先,遍歷需要更新的服務列表;然後,將組態檔案從暫存區複製到目標位置;接著,下載指定的 Docker 雡像;之後,過載 systemd 組態,以確保最新的單元檔案被識別;最後,重啟對應的服務,以使新的組態生效。這一流程保證了佈署的自動化和一致性。

開發與生產環境的差異管理

PeerSpace 對開發環境和生產環境採取了不同的管理策略:

  • 開發環境:使用 latest 標籤的 Docker 映象,便於快速迭代和測試。
  • 生產環境:使用帶有版本號的標籤,確保生產環境的穩定性和可追溯性。

版本管理的最佳實踐

在生產環境中,PeerSpace 採用了嚴格的版本管理機制:

  1. 釋出指令碼:執行釋出指令碼,對 Git 倉函式庫進行標籤化,並構建和推播帶有版本號的 Docker 映象。
  2. 更新組態:更新服務的環境變數檔案,參照新的映象標籤。
  3. 推播組態:將新的組態推播到生產主機。
  4. 執行佈署:在生產主機上執行佈署指令碼,完成新版本的佈署。

輔助服務的支援

PeerSpace 使用了一系列輔助服務來支援其核心業務,包括:

  • 日誌聚合:透過 Fluentd + Kibana 的組合,實作日誌的收集和分析。同時,利用 docker-gen 動態生成 Fluentd 的組態檔案,確保每個容器的日誌都能被正確收集。
  • 監控服務:採用 Datadog 作為監控服務,提供效能指標、API 使用情況以及業務事件的監控。Datadog 的強大標籤功能,使得資料的多維度分析成為可能。

日誌聚合與監控的技術細節

PeerSpace 的日誌聚合系統根據以下技術堆疊構建:

  1. Fluentd:作為日誌收集代理,負責從各個容器中收集日誌。
  2. Kibana:提供日誌的視覺化介面,方便開發人員進行日誌分析。
  3. docker-gen:自動生成 Fluentd 的組態檔案,確保新啟動的容器能夠被納入日誌收集範圍。
# docker-compose.yml 示例
version: '3'
services:
  fluentd:
    image: fluent/fluentd:v1.12-debian-1
    volumes:
      - ./fluentd.conf:/fluentd/etc/fluent.conf
    ports:
      - "24224:24224"

內容解密:

docker-compose.yml 檔案定義了一個 Fluentd 服務,用於收集日誌。透過掛載自定義的 fluentd.conf 組態檔案,Fluentd 能夠根據 PeerSpace 的需求進行靈活組態。同時,開放 24224 連線埠,用於接收來自其他容器的日誌資料。這種方式實作了日誌收集的標準化和自動化。

  1. 零停機佈署:透過引入反向代理等技術,實作服務的零停機佈署,提升使用者經驗。
  2. 高層級組態抽象:開發更高層級的組態描述語言,自動生成組態檔案,並計算需要重啟的容器,降低人工干預的需求。
  3. 叢集管理工具:評估採用 Mesos 或 Kubernetes,以進一步簡化叢集管理和提升系統的可擴充套件性。

透過不斷最佳化和創新,PeerSpace 不僅提升了自身的技術能力,也為同行業的其他企業提供了寶貴的參考經驗。隨著容器技術和相關工具的不斷發展,相信會有更多企業能夠從中受益,實作更高效、更穩定的軟體開發和佈署流程。

使用Docker實作零停機佈署的Web環境實戰

在現代化的軟體開發與佈署流程中,Docker已經成為眾多企業的首選方案。許多成功的企業透過Docker實作了高效且穩定的生產環境佈署。本文將探討一個真實的生產環境案例,展示如何利用Docker實作零停機佈署、集中化日誌管理、監控以及JVM效能分析等功能。

環境概述

該Web環境執行在Amazon Web Services(AWS)上,使用Ubuntu作業系統,並透過Docker容器化技術佈署其CRM網頁應用程式。選擇Docker的主要原因在於它能夠提供零停機佈署、版本隔離以及快速回復等功能。整個環境透過bash指令碼進行協調和管理,無需依賴複雜的容器協調工具如Apache Mesos或Kubernetes。

單機佈署詳解

在單台AWS伺服器上,執行著四個Docker容器。這些容器之間透過Docker橋接網路進行通訊,並將多個埠暴露給主機,以實作HTTP服務和JVM監控。此外,該環境還使用了Amazon ELB(Elastic Load Balancer)進行負載平衡和健康檢查。所有容器的日誌都被儲存到主機上,以便與現有的日誌解決方案(如SumoLogic)整合。

協調與佈署

協調是Docker生產環境中的關鍵環節,主要包含兩個部分:

  1. 準備Docker主機:使用Chef組態管理工具安裝Docker並組態必要的操作工具,如監控和日誌代理。

    • 伺服器啟動後,Chef會自動安裝Docker、組態監控代理(如Datadog)、設定日誌代理(如SumoLogic)等。
    • 最後,Chef會建立一個bash指令碼,用於管理和佈署容器。
  2. 佈署容器:透過一個定時執行的bash指令碼來管理和佈署容器。

    • 該指令碼會檢查容器是否正在執行,如果沒有,則佈署hipache和redis容器,並將它們連結起來。
    • 提取最新的Web伺服器映像並啟動新的容器。
    • 在將新容器加入負載平衡器之前,進行健康檢查。
    • 更新負載平衡器的組態,以包含新容器的資訊。
    • 保留舊容器以便需要時進行回復。

程式碼範例:佈署指令碼

#!/bin/bash

# 檢查hipache容器是否正在執行
STATE=$(docker inspect hipache | jq ".[0].State.Running")
if [[ "$STATE" != "true" ]]; then
    set +e
    docker rm hipache > /dev/null 2>&1
    set -e
    mkdir -p /logs/hipache/
    docker run -p 80:80 -p 6379:6379 --name hipache -v /logs/hipache:/logs -d repo.com/hipache
    echo "$(date +"%Y-%m-%d %H:%M:%S %Z") lpush frontend:* default"
    sleep 5
    (echo -en "lpush frontend:* default\r\n"; sleep 1) | nc localhost 6379
fi

# 提取最新的映像
IMAGE_ID=$(docker images | grep ${IMAGE_NAME} | grep $REMOTE_VERSION | head -n 1 | awk '{print $3}')
if [ -z $IMAGE_ID ]; then
    docker pull $DOCKER_IMAGE_NAME
fi

# 啟動新的Web應用容器
echo "$(date +"%Y-%m-%d %H:%M:%S %Z") launching $DOCKER_IMAGE_NAME, logging to $LOG_DIR"
mkdir -p $LOG_DIR
NEW_WEBAPP_ID="abcdefghijklmnopqrstuvwxyz"
MAX_TIMEOUT=5
set +e
until [ $MAX_TIMEOUT -le 0 ] || NEW_WEBAPP_ID=$(docker run -P -h $(hostname) --link hipache:hipache $(dockerParameters $BRANCH) -d -v $LOG_DIR:/logs $DOCKER_IMAGE_NAME); do
    echo -n "."
    sleep 1
    let MAX_TIMEOUT-=1
done
set -e

# 檢查新容器是否啟動成功
NEW_WEBAPP_IP_ADDR=$(docker inspect $NEW_WEBAPP_ID | jq '.[0].NetworkSettings.IPAddress' -r)
if [ -z "$NEW_WEBAPP_IP_ADDR" -o "$NEW_WEBAPP_IP_ADDR" = "null" ]; then
    echo "$(date +"%Y-%m-%d %H:%M:%S %Z") no new webapp ip, failed to start"
    # 傳送佈署失敗通知
fi

內容解密:

此指令碼首先檢查hipache容器是否正在執行。如果不是,則移除舊容器並啟動一個新的hipache容器,同時設定必要的組態。接著,它會提取最新的Web應用映像,並啟動一個新的容器,將其日誌掛載到主機上。最後,指令碼會檢查新容器的IP位址,以確認它是否成功啟動。如果啟動失敗,則會傳送通知。

隨著技術的不斷發展,未來可以考慮採用Kubernetes等更先進的容器協調工具,以進一步簡化容器的佈署和管理,提高系統的可擴充套件性和彈性。此外,還可以探索更多的監控和日誌分析工具,以提升系統的可觀測性和故障排查能力。