在雲端運算時代,應用程式的交付方式正在經歷根本性的變革。傳統的虛擬機器雖然提供了良好的隔離性,但其重量級的特性導致資源利用率低下、啟動速度緩慢。容器技術的出現改變了這個格局,它提供了輕量級的應用封裝方式,在保證隔離性的同時大幅提升了資源效率與部署速度。Docker 作為容器技術的代表,已經成為現代應用開發與部署的標準工具。
容器化技術與微服務架構的結合,為複雜應用系統的構建提供了理想的技術基礎。微服務將單體應用拆解為一組小型、自治的服務,每個服務運行在獨立的容器中,透過標準化的 API 進行通訊。這種架構模式帶來了顯著的優勢,包含獨立部署、技術多樣性、彈性擴展與故障隔離。然而,容器的管理與編排也帶來了新的挑戰,如何在數百甚至數千個容器間協調資源、管理網路、處理故障,這些都需要專業的編排工具。
在台灣的軟體產業環境中,從新創公司到大型企業,容器化技術的採用率正在快速提升。電子商務平台使用容器實現快速迭代與彈性擴展,金融科技公司透過容器化確保開發與生產環境的一致性,物聯網應用利用容器在邊緣設備上部署服務。Kubernetes 已經成為容器編排的事實標準,而 Docker Compose、Helm、Istio 等工具則構成了完整的容器生態系統。
本文將從實踐的角度,系統性地探討容器化微服務的完整技術棧。我們將深入分析 Docker 容器技術的原理與應用,展示微服務架構在容器環境中的設計模式,探討 Kubernetes 編排平台的核心能力,並透過實戰案例說明從開發到生產的完整部署流程。
Docker 容器技術基礎
Docker 透過容器技術實現了應用程式的輕量級封裝與隔離。與虛擬機器不同,容器共享主機作業系統的核心,但在處理程序、檔案系統、網路等層面保持隔離。這種設計讓容器的啟動速度達到秒級,資源佔用遠低於虛擬機器。
@startuml
!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 "容器化應用架構" {
package "應用層" {
component "Web 應用\n(Nginx/Apache)" as web
component "後端服務\n(Node.js/Python)" as backend
component "資料庫\n(PostgreSQL/MongoDB)" as db
}
package "容器層" {
component "Web 容器" as web_container
component "API 容器" as api_container
component "DB 容器" as db_container
}
package "Docker 引擎" {
component "容器運行時\n(containerd)" as runtime
component "映像管理" as image_mgr
component "網路管理" as network_mgr
component "存儲管理" as storage_mgr
}
component "主機作業系統\n(Linux Kernel)" as os
component "基礎設施\n(實體/虛擬機器)" as infra
}
web --> web_container
backend --> api_container
db --> db_container
web_container --> runtime
api_container --> runtime
db_container --> runtime
runtime --> image_mgr
runtime --> network_mgr
runtime --> storage_mgr
image_mgr --> os
network_mgr --> os
storage_mgr --> os
os --> infra
note right of web_container
輕量級隔離
快速啟動
資源共享
end note
note bottom of runtime
容器生命週期管理
資源限制與配額
安全隔離機制
end note
@endumlDocker 映像是容器的基礎,它是一個唯讀的模板,包含了應用程式及其所有依賴。映像採用分層架構,每一層都是前一層的增量修改。這種設計讓映像的構建、分發與存儲都更加高效。基礎映像通常包含作業系統的核心檔案,應用映像則在基礎映像上添加應用程式碼、配置檔案與依賴函式庫。
#!/usr/bin/env python3
"""
Docker 容器管理工具
提供 Docker 容器的建立、管理與監控功能
"""
import subprocess
import json
from typing import List, Dict, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class ContainerInfo:
"""容器資訊"""
id: str
name: str
image: str
status: str
ports: Dict[str, str]
created: str
class DockerManager:
"""Docker 容器管理器"""
def __init__(self):
self._verify_docker_installed()
def _verify_docker_installed(self) -> bool:
"""驗證 Docker 是否已安裝"""
try:
result = subprocess.run(
['docker', '--version'],
capture_output=True,
text=True,
check=True
)
print(f"Docker 版本: {result.stdout.strip()}")
return True
except (subprocess.CalledProcessError, FileNotFoundError):
print("錯誤: Docker 未安裝或無法執行")
return False
def pull_image(self, image_name: str, tag: str = 'latest') -> bool:
"""
拉取 Docker 映像
Args:
image_name: 映像名稱
tag: 映像標籤
Returns:
是否成功
"""
full_image = f"{image_name}:{tag}"
print(f"拉取映像: {full_image}")
try:
subprocess.run(
['docker', 'pull', full_image],
check=True
)
print(f"映像 {full_image} 拉取成功")
return True
except subprocess.CalledProcessError as e:
print(f"映像拉取失敗: {e}")
return False
def list_images(self) -> List[Dict]:
"""
列出本地映像
Returns:
映像列表
"""
try:
result = subprocess.run(
['docker', 'images', '--format', '{{json .}}'],
capture_output=True,
text=True,
check=True
)
images = []
for line in result.stdout.strip().split('\n'):
if line:
image_data = json.loads(line)
images.append({
'repository': image_data.get('Repository'),
'tag': image_data.get('Tag'),
'image_id': image_data.get('ID'),
'created': image_data.get('CreatedAt'),
'size': image_data.get('Size')
})
return images
except subprocess.CalledProcessError as e:
print(f"獲取映像列表失敗: {e}")
return []
def create_container(
self,
image: str,
name: Optional[str] = None,
ports: Optional[Dict[str, str]] = None,
env: Optional[Dict[str, str]] = None,
volumes: Optional[Dict[str, str]] = None,
detach: bool = True
) -> Optional[str]:
"""
建立並啟動容器
Args:
image: 映像名稱
name: 容器名稱
ports: 埠映射 {'8080': '80'}
env: 環境變數
volumes: 卷掛載 {'/host/path': '/container/path'}
detach: 是否後台運行
Returns:
容器 ID
"""
cmd = ['docker', 'run']
if detach:
cmd.append('-d')
if name:
cmd.extend(['--name', name])
if ports:
for host_port, container_port in ports.items():
cmd.extend(['-p', f"{host_port}:{container_port}"])
if env:
for key, value in env.items():
cmd.extend(['-e', f"{key}={value}"])
if volumes:
for host_path, container_path in volumes.items():
cmd.extend(['-v', f"{host_path}:{container_path}"])
cmd.append(image)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
container_id = result.stdout.strip()
print(f"容器已建立: {container_id[:12]}")
return container_id
except subprocess.CalledProcessError as e:
print(f"容器建立失敗: {e}")
return None
def list_containers(self, all_containers: bool = False) -> List[ContainerInfo]:
"""
列出容器
Args:
all_containers: 是否包含已停止的容器
Returns:
容器資訊列表
"""
cmd = ['docker', 'ps', '--format', '{{json .}}']
if all_containers:
cmd.append('-a')
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
containers = []
for line in result.stdout.strip().split('\n'):
if line:
data = json.loads(line)
# 解析埠映射
ports = {}
ports_str = data.get('Ports', '')
if ports_str:
# 簡化的埠解析
for port_mapping in ports_str.split(','):
if '->' in port_mapping:
parts = port_mapping.split('->')
if len(parts) == 2:
host_port = parts[0].strip()
container_port = parts[1].strip()
ports[container_port] = host_port
container = ContainerInfo(
id=data.get('ID', ''),
name=data.get('Names', ''),
image=data.get('Image', ''),
status=data.get('Status', ''),
ports=ports,
created=data.get('CreatedAt', '')
)
containers.append(container)
return containers
except subprocess.CalledProcessError as e:
print(f"獲取容器列表失敗: {e}")
return []
def stop_container(self, container_id: str) -> bool:
"""
停止容器
Args:
container_id: 容器 ID 或名稱
Returns:
是否成功
"""
try:
subprocess.run(
['docker', 'stop', container_id],
check=True
)
print(f"容器 {container_id} 已停止")
return True
except subprocess.CalledProcessError as e:
print(f"停止容器失敗: {e}")
return False
def remove_container(self, container_id: str, force: bool = False) -> bool:
"""
刪除容器
Args:
container_id: 容器 ID 或名稱
force: 是否強制刪除
Returns:
是否成功
"""
cmd = ['docker', 'rm']
if force:
cmd.append('-f')
cmd.append(container_id)
try:
subprocess.run(cmd, check=True)
print(f"容器 {container_id} 已刪除")
return True
except subprocess.CalledProcessError as e:
print(f"刪除容器失敗: {e}")
return False
def get_container_logs(
self,
container_id: str,
tail: int = 100,
follow: bool = False
) -> str:
"""
獲取容器日誌
Args:
container_id: 容器 ID 或名稱
tail: 顯示最後幾行
follow: 是否持續追蹤
Returns:
日誌內容
"""
cmd = ['docker', 'logs']
if tail:
cmd.extend(['--tail', str(tail)])
if follow:
cmd.append('-f')
cmd.append(container_id)
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"獲取日誌失敗: {e}")
return ""
def get_container_stats(self, container_id: str) -> Dict:
"""
獲取容器資源使用統計
Args:
container_id: 容器 ID 或名稱
Returns:
統計資訊
"""
try:
result = subprocess.run(
['docker', 'stats', '--no-stream', '--format', '{{json .}}', container_id],
capture_output=True,
text=True,
check=True
)
if result.stdout:
stats = json.loads(result.stdout.strip())
return {
'cpu_percent': stats.get('CPUPerc', '0%'),
'memory_usage': stats.get('MemUsage', '0B / 0B'),
'memory_percent': stats.get('MemPerc', '0%'),
'network_io': stats.get('NetIO', '0B / 0B'),
'block_io': stats.get('BlockIO', '0B / 0B')
}
return {}
except subprocess.CalledProcessError as e:
print(f"獲取統計資訊失敗: {e}")
return {}
def create_network(
self,
network_name: str,
driver: str = 'bridge',
subnet: Optional[str] = None
) -> bool:
"""
建立 Docker 網路
Args:
network_name: 網路名稱
driver: 網路驅動 (bridge/overlay/host)
subnet: 子網路 (例如: 172.18.0.0/16)
Returns:
是否成功
"""
cmd = ['docker', 'network', 'create', '--driver', driver]
if subnet:
cmd.extend(['--subnet', subnet])
cmd.append(network_name)
try:
subprocess.run(cmd, check=True)
print(f"網路 {network_name} 已建立")
return True
except subprocess.CalledProcessError as e:
print(f"建立網路失敗: {e}")
return False
def connect_to_network(
self,
container_id: str,
network_name: str
) -> bool:
"""
將容器連接到網路
Args:
container_id: 容器 ID 或名稱
network_name: 網路名稱
Returns:
是否成功
"""
try:
subprocess.run(
['docker', 'network', 'connect', network_name, container_id],
check=True
)
print(f"容器 {container_id} 已連接到網路 {network_name}")
return True
except subprocess.CalledProcessError as e:
print(f"連接網路失敗: {e}")
return False
# 使用範例
if __name__ == "__main__":
print("=== Docker 容器管理示範 ===\n")
# 初始化管理器
docker_mgr = DockerManager()
# 列出本地映像
print("[本地映像列表]\n")
images = docker_mgr.list_images()
if images:
for img in images[:5]:
print(f"{img['repository']:30s} {img['tag']:15s} {img['size']:15s}")
else:
print("無映像")
# 拉取映像(示範)
print("\n\n[拉取 Nginx 映像]\n")
print("執行: docker pull nginx:alpine")
# 建立網路
print("\n\n[建立自訂網路]\n")
docker_mgr.create_network(
'my_app_network',
driver='bridge',
subnet='172.18.0.0/16'
)
# 建立容器(示範)
print("\n\n[建立 Nginx 容器]\n")
print("執行: docker run -d --name my_nginx -p 8080:80 nginx:alpine")
# 列出運行中的容器
print("\n\n[運行中的容器]\n")
containers = docker_mgr.list_containers()
if containers:
for container in containers:
print(f"ID: {container.id[:12]}")
print(f"名稱: {container.name}")
print(f"映像: {container.image}")
print(f"狀態: {container.status}")
if container.ports:
print(f"埠: {container.ports}")
print()
else:
print("無運行中的容器")
這個 Docker 管理工具展示了容器的完整生命週期管理。在實務應用中,通常會搭配 Docker Compose 進行多容器應用的編排,或使用 Kubernetes 進行大規模容器集群的管理。
Docker Compose 多容器編排
當應用系統包含多個容器時,手動管理每個容器的啟動、網路連接與配置會變得繁瑣且容易出錯。Docker Compose 提供了聲明式的配置方式,透過 YAML 檔案定義整個應用的容器組成、網路與卷配置,一條命令即可啟動完整的應用環境。
# docker-compose.yml 完整範例
version: '3.8'
services:
# Web 前端服務
web:
image: nginx:alpine
container_name: helpdesk_web
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./static:/usr/share/nginx/html:ro
- ./ssl:/etc/nginx/ssl:ro
networks:
- frontend
depends_on:
- api
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 3s
retries: 3
# API 後端服務
api:
build:
context: ./api
dockerfile: Dockerfile
container_name: helpdesk_api
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/helpdesk
- REDIS_URL=redis://cache:6379
- JWT_SECRET=${JWT_SECRET}
- API_KEY=${API_KEY}
volumes:
- ./api:/app
- api_logs:/var/log/api
networks:
- frontend
- backend
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
# PostgreSQL 資料庫
db:
image: postgres:14-alpine
container_name: helpdesk_db
environment:
- POSTGRES_DB=helpdesk
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- db_data:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis 快取服務
cache:
image: redis:7-alpine
container_name: helpdesk_cache
command: redis-server --appendonly yes
volumes:
- cache_data:/data
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
volumes:
db_data:
driver: local
cache_data:
driver: local
api_logs:
driver: local
這個 Docker Compose 配置展示了一個典型的 Web 應用架構。前端 Nginx 服務對外提供 HTTP/HTTPS 存取,後端 API 服務處理業務邏輯,PostgreSQL 存儲持久化資料,Redis 提供快取支援。透過網路隔離,資料庫與快取服務僅能被後端 API 存取,增強了安全性。
Kubernetes 容器編排平台
Kubernetes 是雲端原生時代的容器編排標準,它抽象了底層基礎設施的複雜性,提供了聲明式的 API 來管理容器化應用。在 Kubernetes 的世界中,Pod 是最小的部署單元,通常包含一個或多個緊密耦合的容器。Service 提供穩定的網路端點,Deployment 管理 Pod 的生命週期與滾動更新。
@startuml
!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 "Kubernetes 集群架構" {
package "控制平面" {
component "API Server\n統一入口" as apiserver
component "Scheduler\n調度器" as scheduler
component "Controller Manager\n控制器管理" as controller
component "etcd\n配置存儲" as etcd
}
package "工作節點 1" {
component "Kubelet\n節點代理" as kubelet1
component "Kube-proxy\n網路代理" as proxy1
package "Pod 1" {
component "容器 A" as c1a
component "容器 B" as c1b
}
}
package "工作節點 2" {
component "Kubelet" as kubelet2
component "Kube-proxy" as proxy2
package "Pod 2" {
component "容器 C" as c2a
}
}
}
cloud "外部流量" as external
external --> apiserver
apiserver --> etcd
apiserver --> scheduler
apiserver --> controller
scheduler --> kubelet1
scheduler --> kubelet2
controller --> kubelet1
controller --> kubelet2
kubelet1 --> c1a
kubelet1 --> c1b
kubelet2 --> c2a
note right of apiserver
REST API 端點
認證與授權
請求驗證
end note
note bottom of scheduler
Pod 調度決策
資源分配
節點選擇
end note
@endumlKubernetes 的強大之處在於其自我修復能力。當容器崩潰時,Kubelet 會自動重啟容器。當節點故障時,Controller Manager 會在其他健康節點上重新調度 Pod。這種內建的韌性機制大幅降低了維運負擔。
總結
容器化技術與微服務架構的結合,為現代應用系統的構建提供了強大的技術基礎。Docker 簡化了應用的封裝與分發,Kubernetes 實現了大規模容器的自動化管理。在台灣的軟體產業環境中,掌握容器技術已成為雲端原生開發的必備技能。
然而,容器化並非適用於所有場景。對於小型應用或傳統系統,容器化帶來的複雜性可能超過其收益。組織需要評估自身的技術能力、團隊規模與業務需求,選擇適合的技術路線。容器安全、網路性能、存儲管理等問題也需要持續關注。