容器佈署的時代來臨

在當今軟體開發領域中,Docker容器技術已經從實驗性工具演進為企業級解決方案。許多人對Docker保持觀望態度,認為生產環境的應用案例相對稀少,但實際上包括Spotify、Yelp、百度等大型企業都已經在關鍵業務系統中使用容器技術。即使在開發與測試階段使用Docker,團隊也能獲得顯著的生產力提升。

生產環境中佈署容器的常見模式是在虛擬機器上執行容器。這種作法雖然增加了系統負擔,降低了擴充效率,但主要是出於安全考量。容器本身提供的隔離保證相對較弱,若沒有虛擬機器層的保護,一個容器可能影響到同主機上的其他容器。目前Google Container Engine(GKE)與Amazon EC2 Container Service(ECS)等主流解決方案內部仍採用虛擬機器架構,僅有Joyent的Triton與Giant Swarm直接在裸機上執行容器。

本文將透過實際案例展示如何在各種雲端環境與專業Docker託管服務上佈署網頁應用程式,同時探討生產環境中執行容器的關鍵技術與最佳實踐。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "開發環境" {
  [Docker Desktop]
  [本地容器]
}

package "測試環境" {
  [Docker Machine]
  [雲端測試主機]
}

package "生產環境" {
  [容器協調平台]
  [負載平衡器]
  [監控系統]
}

[開發環境] --> [測試環境] : 映像推送
[測試環境] --> [生產環境] : 驗證通過

@enduml

Docker Machine:雲端資源快速設定

Docker Machine是快速設定新資源並執行容器的實用工具。它能夠建立伺服器、安裝Docker引擎,並設定本地Docker客戶端連線到遠端主機。Machine內建支援主要雲端平台驅動程式,包含AWS、Google Compute Engine、Microsoft Azure與Digital Ocean,同時也支援VMware與VirtualBox等虛擬化平台。

安裝與初始設定

若您透過Docker Toolbox安裝Docker,Machine應該已經可用。否則需要從GitHub下載二進位檔案並放置於系統路徑中。安裝完成後,可以使用以下命令檢視現有主機:

docker-machine ls

輸出可能顯示預設的boot2docker虛擬機器或空白列表。接下來我們將在Digital Ocean雲端平台上建立Docker主機。

在Digital Ocean建立Docker主機

首先需要在Digital Ocean註冊帳號並生成個人存取權杖。準備就緒後執行以下命令:

docker-machine create --driver digitalocean \
  --digitalocean-access-token YOUR_TOKEN \
  identihost-do

這個命令會在Digital Ocean上建立虛擬機器並自動安裝Docker。請注意雲端資源會產生費用,完成實驗後應該移除機器以避免持續計費。

連線遠端Docker主機

建立雲端主機後,需要設定本地Docker客戶端連線到遠端環境:

docker-machine env identihost-do
eval "$(docker-machine env identihost-do)"

執行評估命令後,所有Docker指令都將在雲端主機執行。透過docker info可以確認連線狀態,輸出會顯示遠端主機的作業系統與硬體資訊。

容器應用的雲端佈署實戰

使用Docker Compose佈署服務

我們將佈署一個包含多個容器的identidock應用。首先建立docker-compose.yml設定檔:

identidock:
  image: amouat/identidock:1.0
  ports:
    - "5000:5000"
    - "9000:9000"
  environment:
    ENV: DEV
  links:
    - dnmonster
    - redis
dnmonster:
  image: amouat/dnmonster:1.0
redis:
  image: redis:3

這個設定定義了三個服務,透過links建立容器間的連線。在連線到雲端主機的狀態下執行:

docker-compose up -d
curl $(docker-machine ip identihost-do):5000

應用程式成功啟動後,可以透過瀏覽器存取雲端主機IP檢視服務。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

actor 使用者
participant "Nginx代理" as Proxy
participant "Identidock應用" as App
database "Redis快取" as Redis
participant "DNMonster服務" as DNS

使用者 -> Proxy : HTTP請求
Proxy -> App : 轉發請求
App -> Redis : 檢查快取
Redis --> App : 快取資料
App -> DNS : 生成頭像
DNS --> App : 頭像影像
App --> Proxy : 回應資料
Proxy --> 使用者 : HTTP回應

@enduml

引入反向代理提升架構

直接暴露應用連線埠存在安全隱憂,生產環境應該使用反向代理。建立identiproxy目錄並準備Nginx設定:

建立Dockerfile:

FROM nginx:1.7
COPY default.conf /etc/nginx/conf.d/default.conf

建立default.conf:

server {
  listen 80;
  server_name YOUR_HOST_IP;
  
  location / {
    proxy_pass http://identidock:9090;
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_redirect off;
    proxy_buffering off;
    proxy_set_header Host YOUR_HOST_IP;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

在雲端主機上建構代理映像:

docker build -t identiproxy:0.1 .

生產環境設定最佳化

建立prod.yml設定檔,整合代理服務:

proxy:
  image: identiproxy:0.1
  links:
    - identidock
  ports:
    - "80:80"
identidock:
  image: amouat/identidock:1.0
  links:
    - dnmonster
    - redis
  environment:
    ENV: PROD
dnmonster:
  image: amouat/dnmonster:1.0
redis:
  image: redis:3

注意identidock不再直接暴露連線埠,所有外部流量都透過代理處理。環境變數設為PROD啟動生產版伺服器。

docker-compose stop
docker-compose -f prod.yml up -d
curl $(docker-machine ip identihost-do)

進階佈署策略

使用環境變數動態設定

硬編碼設定值降低了靈活性,應該使用環境變數。修改default.conf使用佔位符:

server {
  listen 80;
  server_name {{NGINX_HOST}};
  
  location / {
    proxy_pass {{NGINX_PROXY}};
    proxy_set_header Host {{NGINX_HOST}};
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

建立entrypoint.sh指令碼處理變數替換:

#!/bin/bash
set -e
sed -i "s|{{NGINX_HOST}}|$NGINX_HOST|;s|{{NGINX_PROXY}}|$NGINX_PROXY|" \
  /etc/nginx/conf.d/default.conf
exec "$@"

更新Dockerfile:

FROM nginx:1.7
COPY default.conf /etc/nginx/conf.d/default.conf
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

在prod.yml中設定環境變數:

proxy:
  image: proxy:1.0
  environment:
    - NGINX_HOST=YOUR_HOST_IP
    - NGINX_PROXY=http://identidock:9090

Shell指令碼佈署方案

雖然Compose提供便利性,但傳統shell指令碼在某些場景仍有價值。以下範例展示使用Docker命令佈署:

#!/bin/bash
set -e
echo "啟動identidock系統"
docker run -d --restart=always --name redis redis:3
docker run -d --restart=always --name dnmonster amouat/dnmonster:1.0
docker run -d --restart=always \
  --link dnmonster:dnmonster \
  --link redis:redis \
  -e ENV=PROD \
  --name identidock amouat/identidock:1.0
docker run -d --restart=always \
  --name proxy \
  --link identidock:identidock \
  -p 80:80 \
  -e NGINX_HOST=YOUR_HOST_IP \
  -e NGINX_PROXY=http://identidock:9090 \
  amouat/proxy:1.0
echo "系統啟動完成"

這個指令碼基本上將Compose設定轉換為等效的Docker命令。使用restart=always確保容器異常終止時自動重啟。

systemd整合與服務管理

systemd可以提供更精細的容器生命週期管理。建立Redis服務單元檔案:

[Unit]
Description=Redis容器服務
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop redis
ExecStartPre=-/usr/bin/docker rm redis
ExecStartPre=/usr/bin/docker pull redis
ExecStart=/usr/bin/docker run --rm --name redis redis

[Install]
WantedBy=multi-user.target

對於依賴其他服務的容器,設定會更複雜:

[Unit]
Description=Identidock應用容器
After=docker.service
Requires=docker.service
After=identidock.redis.service
Requires=identidock.redis.service
After=identidock.dnmonster.service
Requires=identidock.dnmonster.service

[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/docker stop identidock
ExecStartPre=-/usr/bin/docker rm identidock
ExecStartPre=/usr/bin/docker pull amouat/identidock
ExecStart=/usr/bin/docker run --name identidock \
  --link dnmonster:dnmonster \
  --link redis:redis \
  -e ENV=PROD \
  amouat/identidock

使用systemd的優勢包括依賴管理、自動重啟與統一的服務管理介面。但也需要注意不要在docker run中使用restart=always,因為這會與systemd的重啟機制衝突。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

state "系統初始化" as Init
state "Docker服務啟動" as Docker
state "Redis容器啟動" as Redis
state "DNMonster容器啟動" as DNS
state "Identidock容器啟動" as App
state "Proxy容器啟動" as Proxy
state "服務就緒" as Ready

[*] --> Init
Init --> Docker
Docker --> Redis
Docker --> DNS
Redis --> App
DNS --> App
App --> Proxy
Proxy --> Ready
Ready --> [*]

@enduml

設定管理工具整合

Ansible自動化佈署

Ansible提供簡單的容器管理方式,無需在目標主機安裝代理程式。建立hosts檔案:

[identidock]
YOUR_HOST_IP

建立identidock playbook:

---
- hosts: identidock
  sudo: yes
  tasks:
  - name: 安裝pip
    apt: pkg=python-pip
  - name: 安裝docker-py
    pip: name=docker-py
  - name: Redis容器
    docker:
      name: redis
      image: redis:3
      pull: always
      state: reloaded
      restart_policy: always
  - name: DNMonster容器
    docker:
      name: dnmonster
      image: amouat/dnmonster:1.0
      pull: always
      state: reloaded
      restart_policy: always
  - name: Identidock容器
    docker:
      name: identidock
      image: amouat/identidock:1.0
      pull: always
      state: reloaded
      links:
        - "dnmonster:dnmonster"
        - "redis:redis"
      env:
        ENV: PROD
      restart_policy: always
  - name: Proxy容器
    docker:
      name: proxy
      image: amouat/proxy:1.0
      pull: always
      state: reloaded
      links:
        - "identidock:identidock"
      ports:
        - "80:80"
      env:
        NGINX_HOST: YOUR_HOST_IP
        NGINX_PROXY: http://identidock:9090
      restart_policy: always

執行playbook:

docker run -it \
  -v ${HOME}/.ssh:/root/.ssh:ro \
  -v $(pwd)/identidock.yml:/ansible/identidock.yml \
  -v $(pwd)/hosts:/etc/ansible/hosts \
  --rm=true generik/ansible ansible-playbook identidock.yml

Docker儲存驅動選擇策略

儲存驅動的選擇直接影響容器效能與穩定性。以下是主要驅動的特性分析。

AUFS驅動

AUFS是Docker早期的預設選擇,在Ubuntu與Debian系統上仍被廣泛使用。它實作檔案級別的寫時複製(CoW)機制,穩定性高且經過長時間驗證。對小檔案處理效率高,但大檔案的小範圍修改會導致整個檔案被複製到容器層。適合處理大量小型檔案的應用。

Overlay2驅動

Overlay與AUFS類似,但已合併到Linux核心3.18版本中。效能略優於AUFS,需要較新核心版本支援。從長期來看,Overlay2很可能成為Docker的主要儲存驅動。對於支援最新核心的系統,這是推薦選擇。

BTRFS驅動

BTRFS採用區塊級別的寫時複製,專注於容錯與大檔案支援。具有內建快照與校驗和功能,支援非常大的檔案尺寸。除非組織已有BTRFS使用經驗或需要特定功能,否則不建議選擇。適合需要處理TB級別大檔案的科學資料處理應用。

Device Mapper驅動

Device Mapper是Red Hat系統的預設驅動,使用區塊級別寫時複製。預設從100GB稀疏檔案分配薄池,建立的容器預設可擴充至100GB。這是最複雜的驅動,也是問題來源的常見點。如果可能建議使用替代方案,必須使用時需要調整引數並將儲存移至實體裝置。

VFS驅動

VFS是預設Linux虛擬檔案系統,不實作寫時複製機制。啟動容器時需要完整複製映像,大幅增加啟動時間與磁碟空間需求。只有在遇到其他驅動問題且不介意效能損失時才考慮使用。適合少量長期執行容器的環境。

切換儲存驅動

切換驅動相對簡單,假設已安裝必要依賴。重啟Docker守護程式並傳遞storage-driver引數:

docker daemon -s overlay

若需要使用特定檔案系統,設定graph引數:

docker daemon -s btrfs -g /mnt/btrfs_partition

永久變更需要編輯Docker服務設定檔。在Ubuntu 14.04上編輯/etc/default/docker中的DOCKER_OPTS變數。

切換驅動後無法存取舊容器與映像。要遷移映像,先儲存為TAR檔案再載入新檔案系統:

docker save -o /tmp/debian.tar debian:wheezy
sudo stop docker
docker daemon -s vfs
docker load -i /tmp/debian.tar
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "儲存驅動選擇" {
  [AUFS] as A
  [Overlay2] as O
  [BTRFS] as B
  [Device Mapper] as D
  [VFS] as V
}

note right of A
  穩定可靠
  適合小檔案
end note

note right of O
  效能最佳
  未來主流
end note

note right of B
  大檔案處理
  需要專業知識
end note

note right of D
  Red Hat預設
  設定複雜
end note

note right of V
  簡單但慢
  測試專用
end note

@enduml

專業容器託管平台

Joyent Triton

Triton最特殊之處在於不使用虛擬機器,直接在SmartOS管理程式上執行容器。這帶來顯著效能優勢,允許按容器進行資源設定。Triton實作Docker遠端API,與普通Docker客戶端完全相容。

設定Triton帳戶後,可以使用標準Docker命令:

docker info

輸出會顯示SmartDataCenter作業系統,確認連線到Triton環境。

使用Compose佈署應用,建立triton.yml:

proxy:
  image: amouat/proxy:1.0
  links:
    - identidock
  ports:
    - "80:80"
  environment:
    - NGINX_HOST=www.identidock.com
    - NGINX_PROXY=http://identidock:9090
  mem_limit: "128M"

identidock:
  image: amouat/identidock:1.0
  links:
    - dnmonster
    - redis
  environment:
    ENV: PROD
  mem_limit: "128M"

dnmonster:
  image: amouat/dnmonster:1.0
  mem_limit: "128M"

redis:
  image: redis
  mem_limit: "128M"

啟動應用:

docker-compose -f triton.yml up -d
docker inspect -f {{.NetworkSettings.IPAddress}} triton_proxy_1
curl PROXY_IP

Triton會自動為發布連線埠的容器分配公開可存取IP。完成後記得停止並移除容器,避免持續計費。

Google Container Engine

GKE建立在Kubernetes協調系統之上,提供自動複製與負載平衡等企業級功能。佈署應用需要瞭解Kubernetes基本概念並建立特定設定檔案。作為額外工作的回報,可以獲得高用性與自動擴充能力。

Kubernetes適合需要保證執行時間的服務,即使對於中小型應用也能提供價值。但要注意這會將系統繫結到Kubernetes模型,增加平台遷移難度。

Amazon ECS

ECS幫助在Amazon EC2基礎設施上執行容器,提供Web介面與API管理底層叢集。在每個節點上執行容器代理,與ECS服務通訊負責容器生命週期。

ECS核心概念包括:

叢集是容器執行環境,由多個EC2執行個體組成。任務定義描述應用程式的容器集合。任務是任務定義的例項,代表正在執行的容器。服務確保指定數量的任務持續執行。

建立任務定義範例:

{
  "family": "identidock",
  "containerDefinitions": [
    {
      "name": "proxy",
      "image": "amouat/proxy:1.0",
      "cpu": 100,
      "memory": 100,
      "essential": true,
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 80
        }
      ],
      "links": ["identidock"]
    },
    {
      "name": "identidock",
      "image": "amouat/identidock:1.0",
      "cpu": 100,
      "memory": 100,
      "links": ["dnmonster", "redis"],
      "essential": true
    },
    {
      "name": "dnmonster",
      "image": "amouat/dnmonster:1.0",
      "cpu": 100,
      "memory": 100,
      "essential": true
    },
    {
      "name": "redis",
      "image": "redis:3",
      "cpu": 100,
      "memory": 100,
      "essential": false
    }
  ]
}

ECS能夠輕鬆佈署數百或數千個容器,提供強大擴充能力與靈活的排程策略,並與AWS服務無縫整合。

持久化資料管理策略

大型資料函式庫處理

對於大量資料儲存,容器並未改變現有解決方案。由於資料量大,容器實際上被繫結到主機,限制了可移植性優勢。若關注效能,可以使用net=host與privileged模式確保容器效率接近主機。也可以考慮使用Amazon RDS等託管服務。

小型服務資料儲存

對於設定檔案與中等量資料,標準卷可能受限於主機繫結,使容器擴充與遷移變得困難。建議考慮以下方案:

將資料移至獨立的鍵值儲存或資料函式庫。使用Flocker管理資料卷,利用ZFS檔案系統功能支援資料隨容器遷移。在實施微服務時,盡可能保持容器無狀態以簡化系統架構。

機密管理最佳實踐

敏感資料如密碼與API金鑰需要安全地與容器分享。以下是幾種方法及其優缺點。

儲存在映像中

絕對不要這樣做。雖然最簡單,但任何能存取映像的人都能取得機密。即使刪除,機密仍存在於之前的層中,映像被意外分享時機密就暴露了。

透過環境變數傳遞

方法簡單:

docker run -d -e API_TOKEN=my_secret_token myimage

這是十二因素應用程式推薦方式,但有嚴重缺點。環境變數對所有子程式、docker inspect與連結容器可見,常被記錄用於除錯而洩漏,且無法刪除。

透過卷傳遞

稍好方案:

docker run -d -v $(pwd)/secret-file:/secret-file:ro myimage

可以建立設定環境變數的指令碼:

cat /secret/env.sh
export DB_PASSWORD=s3cr3t
source /secret/env.sh >> run_my_app.sh

避免將變數暴露給docker inspect或連結容器,但需將機密儲存在檔案中,容易被誤加入版本控制。

使用鍵值儲存

最佳解決方案是使用專用鍵值儲存,在容器執行時檢索機密。主要方案包括:

HashiCorp的Vault可在多種後端加密儲存機密,具有CLI與API。Square的KeyWhiz將機密加密儲存在記憶體,透過REST API與命令列介面提供存取。Crypt在etcd或Consul中加密儲存值。

使用秘密管理系統的優勢包括:

為機密設定租約,特定時間後自動過期。在安全警示時立即鎖定存取。使用一次性權杖,使用後立即撤銷。考慮使用專用卷外掛,將機密從儲存系統直接掛載為容器內檔案。

日誌管理與監控

Docker原生日誌功能

Docker預設記錄所有傳送到STDOUT與STDERR的內容,可以使用docker logs命令檢索:

docker run --name logtest debian sh -c 'echo "stdout"; echo "stderr" >&2'
docker logs logtest
docker logs -t logtest

對於執行中的容器,使用f引數即時串流日誌:

docker run -d --name streamtest debian \
  sh -c 'while true; do echo "tick"; sleep 1; done;'
docker logs -f streamtest

Docker提供多種日誌驅動選項:

json-file是預設日誌方式。syslog將日誌傳送到syslog。journald使用systemd日誌驅動。gelf使用Graylog擴充日誌格式。fluentd將日誌轉發到fluentd。none關閉日誌功能。

ELK堆疊整合

ELK堆疊由Elasticsearch、Logstash與Kibana組成,提供完整的日誌管理解決方案。

Elasticsearch是具有近即時搜尋功能的文字搜尋引擎,設計為可輕鬆跨節點擴充。Logstash用於收集、解析與轉換日誌,可從多個來源取得資料並傳送到指定目的地。Kibana提供視覺化與探索Elasticsearch資料的豐富圖表功能。

設定Logspout收集容器日誌,建立prod-with-logging.yml:

proxy:
  image: amouat/proxy:1.0
  links:
    - identidock
  ports:
    - "80:80"
  environment:
    - NGINX_HOST=YOUR_HOST_IP
    - NGINX_PROXY=http://identidock:9090

identidock:
  image: amouat/identidock:1.0
  links:
    - dnmonster
    - redis
  environment:
    ENV: PROD

dnmonster:
  image: amouat/dnmonster

redis:
  image: redis

logspout:
  image: amouat/logspout-logstash
  volumes:
    - /var/run/docker.sock:/tmp/docker.sock
  links:
    - logstash
  command: logstash://logstash:5000

logstash:
  image: logstash:1.5
  volumes:
    - ./logstash.conf:/etc/logstash.conf
  environment:
    LOGSPOUT: ignore
  links:
    - elasticsearch
  command: -f /etc/logstash.conf

elasticsearch:
  image: elasticsearch:1.7
  environment:
    LOGSPOUT: ignore

kibana:
  image: kibana:4
  environment:
    LOGSPOUT: ignore
    ELASTICSEARCH_URL: http://elasticsearch:9200
  links:
    - elasticsearch
  ports:
    - "5601:5601"

建立logstash.conf設定:

input {
  tcp {
    port => 5000
    codec => json
  }
  udp {
    port => 5000
    codec => json
  }
}

filter {
  if [docker.image] =~ /^amouat\/proxy.*/ {
    mutate { replace => { type => "nginx" } }
    grok {
      match => { "message" => "%{COMBINEDAPACHELOG}" }
    }
  }
}

output {
  elasticsearch { host => "elasticsearch" }
}

啟動完整堆疊後,可以在瀏覽器中開啟Kibana檢視與分析日誌。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

component "容器群組" as Containers
component "Logspout" as Logspout
component "Logstash" as Logstash
database "Elasticsearch" as ES
component "Kibana" as Kibana
actor "管理員" as Admin

Containers --> Logspout : 日誌串流
Logspout --> Logstash : 轉發日誌
Logstash --> ES : 儲存與索引
ES --> Kibana : 查詢資料
Kibana --> Admin : 視覺化介面

@enduml

日誌儲存與旋轉

無論使用什麼日誌驅動,都需要規劃儲存與保留策略。使用logrotate管理日誌檔案增長,建立/etc/logrotate.d/docker設定:

/var/lib/docker/containers/*/*.log {
    daily
    rotate 3
    compress
    delaycompress
    missingok
    copytruncate
}

對於需要長期儲存的關鍵日誌,建議將日誌轉發到PostgreSQL等穩健資料函式庫。不要僅依賴Elasticsearch等索引解決方案作為永久儲存,因為它們沒有成熟資料函式庫的保證。

容器監控方案

Docker提供docker stats命令檢視資源使用即時流:

docker stats logging_logspout_1

要取得所有執行容器的統計:

docker stats $(docker inspect -f {{.Name}} $(docker ps -q))

cAdvisor監控工具

cAdvisor來自Google,提供主機上執行容器的資源使用與效能指標圖形概覽:

docker run -d \
  --name cadvisor \
  -v /:/rootfs:ro \
  -v /var/run:/var/run:rw \
  -v /sys:/sys:ro \
  -v /var/lib/docker/:/var/lib/docker:ro \
  -p 8080:8080 \
  google/cadvisor:latest

在瀏覽器中開啟http://localhost:8080檢視監控資料。cAdvisor透過REST API提供資料,也可以匯出到InfluxDB等時間序列資料函式庫。

Prometheus叢集監控

Prometheus是來自SoundCloud的開放原始碼監控解決方案,專為支援大型微服務架構設計。它採用根據提取的模型,應用程式需要自行暴露指標。

建立prometheus.conf設定:

global:
  scrape_interval: 1m
  scrape_timeout: 10s
  evaluation_interval: 1m
scrape_configs:
  - job_name: prometheus
    scheme: http
    target_groups:
      - targets:
        - 'cadvisor:8080'
        - 'localhost:9090'

啟動Prometheus:

docker run -d --name prometheus -p 9090:9090 \
  -v $(pwd)/prometheus.conf:/prometheus.conf \
  --link cadvisor:cadvisor \
  prom/prometheus -config.file=/prometheus.conf

在Graph標籤頁可以開始調查Prometheus內部資料,使用查詢語言過濾與分析指標。

服務探索架構

服務探索是自動為服務客戶端提供適當服務例項連線資訊的過程。在分散式系統中,服務例項會隨時間增減,問題變得複雜。

大使容器模式

大使容器是連線跨主機容器的簡單方式。這些代理容器替代真實服務,將流量轉發給實際服務。

在etcd-1上設定Redis與大使:

docker run -d --name real-redis redis:3
docker run -d --name real-redis-ambassador \
  -p 6379:6379 \
  --link real-redis:real-redis \
  amouat/ambassador

在identidock-host上設定大使:

docker run -d --name redis_ambassador --expose 6379 \
  -e REDIS_PORT_6379_TCP=tcp://REDIS_HOST_IP:6379 \
  amouat/ambassador

啟動應用並連結到大使:

docker run -d --link redis_ambassador:redis amouat/identidock:1.0

大使容器提供關注點分離,允許生產環境網路架構與開發架構不同而無需更改程式碼。

etcd分散式鍵值儲存

etcd是分散式鍵值儲存,實作Raft共識演算法,旨在既高效又容錯。建議最小叢集規模為3,提供容錯能力。

建立etcd叢集:

docker-machine create -d virtualbox etcd-1
docker-machine create -d virtualbox etcd-2

HOSTA=$(docker-machine ip etcd-1)
HOSTB=$(docker-machine ip etcd-2)

eval $(docker-machine env etcd-1)
docker run -d -p 2379:2379 -p 2380:2380 -p 4001:4001 \
  --name etcd quay.io/coreos/etcd \
  -name etcd-1 \
  -initial-advertise-peer-urls http://${HOSTA}:2380 \
  -listen-peer-urls http://0.0.0.0:2380 \
  -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
  -advertise-client-urls http://${HOSTA}:2379 \
  -initial-cluster-token etcd-cluster-1 \
  -initial-cluster etcd-1=http://${HOSTA}:2380,etcd-2=http://${HOSTB}:2380 \
  -initial-cluster-state new

驗證叢集狀態:

curl -s http://$HOSTA:2379/v2/members | jq '.'

新增資料到etcd:

curl -s http://$HOSTA:2379/v2/keys/service_name \
  -XPUT -d value="service_address" | jq '.'

檢索資料:

curl -s http://$HOSTA:2379/v2/keys/service_name | jq '.'

SkyDNS服務探索

SkyDNS提供根據DNS的服務探索,建立在etcd之上。首先新增設定到etcd:

curl -XPUT http://${HOSTA}:2379/v2/keys/skydns/config \
  -d value='{"dns_addr":"0.0.0.0:53", "domain":"identidock.local."}'

啟動SkyDNS:

docker run -d -e ETCD_MACHINES="http://${HOSTA}:2379,http://${HOSTB}:2379" \
  --name dns skynetservices/skydns:2.5.2a

註冊服務:

docker run -d -p 6379:6379 --name redis redis:3
curl -XPUT http://${HOSTA}:2379/v2/keys/skydns/local/identidock/redis \
  -d value='{"host":"'$HOSTB'","port":6379}'

測試DNS解析:

docker run --dns DNS_IP -it redis:3 bash
ping redis.identidock.local

Consul服務探索

Consul是HashiCorp提供的解決方案,除了鍵值儲存外還提供高階健康檢查功能與DNS伺服器。

建立Consul叢集:

docker-machine create -d virtualbox consul-1
docker-machine create -d virtualbox consul-2

HOSTA=$(docker-machine ip consul-1)
HOSTB=$(docker-machine ip consul-2)

docker run -d --name consul -h consul-1 \
  -p 8300:8300 -p 8301:8301 -p 8301:8301/udp \
  -p 8302:8302/udp -p 8400:8400 -p 8500:8500 \
  -p 172.17.42.1:53:8600/udp \
  gliderlabs/consul agent -data-dir /data -server \
  -client 0.0.0.0 \
  -advertise $HOSTA -bootstrap-expect 2

驗證叢集:

docker exec consul consul members

註冊服務:

curl -XPUT http://$HOSTA:8500/v1/agent/service/register \
  -d '{"name": "redis", "address":"'$HOSTB'","port": 6379}'

新增健康檢查:

curl -XPUT http://$HOSTA:8500/v1/agent/service/register \
  -d '{"name": "dnmonster", "address":"'$DNM_IP'","port": 8080,
  "check": {"http": "http://'$DNM_IP':8080/monster/foo",
  "interval": "10s"}}'

檢查服務狀態:

curl -s $HOSTA:8500/v1/health/checks/dnmonster | jq '.[].Status'
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "服務探索架構" {
  [應用程式容器] as App
  [服務註冊] as Register
  [服務目錄] as Directory
  [健康檢查] as Health
  [DNS伺服器] as DNS
}

App -> Register : 註冊服務
Register -> Directory : 更新目錄
Health -> Directory : 狀態更新
App -> DNS : 查詢服務
DNS -> Directory : 檢索資訊
Directory --> DNS : 服務位址
DNS --> App : 回傳結果

@enduml

容器網路解決方案

Docker網路模式

Docker提供四種基本網路模式:

橋接模式(bridge)是預設選項,透過docker0橋接器連線容器。在開發中運作良好,但生產環境中管道設定開銷較大。

主機模式(host)使用net=host讓容器分享主機網路名稱空間,消除橋接開銷但需要協調埠號。

容器模式(container)使用另一個容器的網路名稱空間,適合需要分享網路堆疊的場景。

無網路模式(none)完全關閉容器網路,適合不需要網路的容器。

新Docker網路架構

Docker網路正在經歷重大改造。新架構引入網路與服務兩個頂層物件,允許網路與容器分開管理。

列出網路:

docker network ls

建立自定義網路:

docker network create mynet

將容器連線到網路:

docker run -d --net=mynet --name web nginx
docker run -d --net=mynet --name db redis

容器可以透過名稱直接通訊,無需連結。這種方式提供更靈活的網路隔離與服務發現。

持續佈署策略

持續交付將持續整合延伸到生產環境,工程師可以透過按鈕將修改佈署。持續佈署更進一步,自動將透過測試的修改推播到佈署環境。

無停機更新技術

藍綠佈署準備完全相同的環境,確認無誤後將流量從原環境切換過去。階梯式佈署逐步將流量從舊版本轉移到新版本。

這些技術通常使用內部工具實作,Kubernetes等框架提供內建解決方案。

@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

state "藍色環境運作中" as Blue
state "啟動綠色環境" as Green
state "測試綠色環境" as Test
state "切換流量到綠色" as Switch
state "關閉藍色環境" as Close

[*] --> Blue
Blue --> Green : 佈署新版本
Green --> Test : 驗證功能
Test --> Switch : 測試通過
Test --> Blue : 測試失敗
Switch --> Close : 流量切換完成
Close --> [*]

@enduml

結論

Docker容器佈署從開發到生產環境涉及眾多技術與決策。本文探討了容器協調工具、儲存驅動選擇、網路設定、服務探索、日誌管理與監控策略等關鍵主題。

透過Docker Machine可以快速設定雲端資源,使用Compose簡化多容器應用管理。生產環境需要考慮反向代理、環境變數設定、系統服務整合等進階議題。儲存驅動的選擇直接影響效能,應根據工作負載特性權衡。

服務探索方面,從簡單的大使容器到etcd、Consul等專業解決方案,提供不同程度的功能與複雜度。網路設定包含橋接、主機、容器等模式,新Docker網路架構提供更靈活的管理方式。

日誌管理使用ELK堆疊可以建立強大的集中式系統,監控方面cAdvisor與Prometheus提供豐富的指標收集與視覺化功能。持久化資料與機密管理需要特別注意,選擇適當的解決方案確保安全與可靠性。

容器技術持續快速發展,但核心原理保持穩定。理解這些基礎概念與工具,將幫助您設計更健壯、更靈活的容器化應用架構。無論選擇哪種技術棧,持續關注最佳實踐並根據應用需求調整策略,才能充分發揮容器技術的優勢。

從本地開發到雲端生產,Docker容器提供一致的執行環境與靈活的佈署選項。透過適當的工具與流程,可以建立高效、可靠與易於維護的容器化系統,為現代軟體開發帶來顯著價值。