Docker 多租戶資源隔離策略

Docker Multi-Tenancy Resource Isolation Strategy

多租戶架構概念

多租戶(Multi-Tenancy)架構是一種軟體架構模式,允許單一應用程式實例同時服務多個租戶(客戶或組織),同時確保各租戶之間的資料和資源完全隔離。在容器化環境中,Docker 提供了多種機制來實現安全且高效的多租戶隔離。

多租戶架構的核心需求

在設計 Docker 多租戶架構時,需要考慮以下關鍵面向:

  • 資源隔離:確保租戶之間的 CPU、記憶體、I/O 等資源互不干擾
  • 安全隔離:防止租戶之間的未授權存取
  • 網路隔離:確保租戶網路流量的獨立性
  • 儲存隔離:保障租戶資料的私密性和完整性
  • 效能可預測性:確保每個租戶獲得穩定的效能表現

多租戶部署模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌─────────────────────────────────────────────────────────┐
│                     Docker Host                          │
├───────────────────┬───────────────────┬─────────────────┤
│    Tenant A       │    Tenant B       │    Tenant C     │
│  ┌─────────────┐  │  ┌─────────────┐  │  ┌───────────┐  │
│  │ Container 1 │  │  │ Container 1 │  │  │Container 1│  │
│  │ Container 2 │  │  │ Container 2 │  │  │Container 2│  │
│  └─────────────┘  │  └─────────────┘  │  └───────────┘  │
│  Network: net-a   │  Network: net-b   │  Network: net-c │
│  Volume: vol-a    │  Volume: vol-b    │  Volume: vol-c  │
└───────────────────┴───────────────────┴─────────────────┘

Cgroups 資源限制

Control Groups(Cgroups)是 Linux 核心的功能,用於限制、記錄和隔離行程群組的資源使用。Docker 原生支援 Cgroups,可以精確控制容器的資源配額。

CPU 資源限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 限制容器使用 50% 的 CPU 資源
docker run -d --name tenant-a-app \
    --cpus="0.5" \
    nginx

# 設定 CPU 權重(相對權重,預設為 1024)
docker run -d --name tenant-a-app \
    --cpu-shares=512 \
    nginx

# 將容器綁定到特定 CPU 核心
docker run -d --name tenant-a-app \
    --cpuset-cpus="0,1" \
    nginx

# 設定 CPU 週期限制(更精細的控制)
docker run -d --name tenant-a-app \
    --cpu-period=100000 \
    --cpu-quota=50000 \
    nginx

記憶體資源限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 限制容器最大記憶體使用量
docker run -d --name tenant-a-app \
    --memory="512m" \
    nginx

# 設定記憶體加上 swap 的總限制
docker run -d --name tenant-a-app \
    --memory="512m" \
    --memory-swap="1g" \
    nginx

# 設定記憶體保留量(軟性限制)
docker run -d --name tenant-a-app \
    --memory="512m" \
    --memory-reservation="256m" \
    nginx

# 禁用 OOM Killer(不建議在生產環境使用)
docker run -d --name tenant-a-app \
    --memory="512m" \
    --oom-kill-disable \
    nginx

I/O 資源限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 限制區塊裝置讀取速率(每秒 10MB)
docker run -d --name tenant-a-app \
    --device-read-bps /dev/sda:10mb \
    nginx

# 限制區塊裝置寫入速率
docker run -d --name tenant-a-app \
    --device-write-bps /dev/sda:10mb \
    nginx

# 限制每秒讀取操作次數
docker run -d --name tenant-a-app \
    --device-read-iops /dev/sda:1000 \
    nginx

# 設定區塊 I/O 權重(100-1000,預設 500)
docker run -d --name tenant-a-app \
    --blkio-weight=300 \
    nginx

查看 Cgroups 設定

1
2
3
4
5
6
7
8
9
# 查看容器的資源使用統計
docker stats tenant-a-app

# 查看容器的詳細資源限制設定
docker inspect tenant-a-app --format='{{json .HostConfig}}' | jq

# 直接查看 cgroups 檔案系統(cgroups v2)
cat /sys/fs/cgroup/docker/<container_id>/cpu.max
cat /sys/fs/cgroup/docker/<container_id>/memory.max

Namespace 隔離

Linux Namespace 是實現容器隔離的核心技術,Docker 使用多種 Namespace 來確保容器之間的隔離。

Namespace 類型

Namespace隔離內容說明
PID行程 ID每個容器有獨立的行程樹
NET網路獨立的網路堆疊、IP 位址、路由表
MNT掛載點獨立的檔案系統掛載點
UTS主機名稱獨立的主機名稱和網域名稱
IPC行程間通訊獨立的 System V IPC 和 POSIX 訊息佇列
USER使用者獨立的使用者和群組 ID
CGROUPCgroup 根目錄獨立的 cgroup 階層視圖

使用 User Namespace

User Namespace 可以將容器內的 root 使用者映射到主機上的非特權使用者,大幅提升安全性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 啟用 Docker daemon 的 User Namespace 支援
# 編輯 /etc/docker/daemon.json
{
    "userns-remap": "default"
}

# 重新啟動 Docker
sudo systemctl restart docker

# 驗證 User Namespace 配置
cat /etc/subuid
cat /etc/subgid

自訂 User Namespace 映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 建立專用的使用者和群組
sudo useradd -r -s /bin/false dockremap

# 設定 UID/GID 映射範圍
echo "dockremap:100000:65536" | sudo tee -a /etc/subuid
echo "dockremap:100000:65536" | sudo tee -a /etc/subgid

# 更新 Docker daemon 配置
{
    "userns-remap": "dockremap"
}

PID Namespace 隔離

1
2
3
4
5
6
7
8
9
# 預設情況下,容器有獨立的 PID namespace
docker run -d --name isolated-app nginx

# 在容器內查看行程
docker exec isolated-app ps aux
# PID 1 是容器的主行程

# 共享主機的 PID namespace(不建議用於多租戶)
docker run -d --pid=host --name shared-pid-app nginx

網路隔離策略

網路隔離是多租戶架構中最關鍵的安全層之一。Docker 提供了多種網路驅動程式和配置選項來實現網路隔離。

建立隔離的租戶網路

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 為每個租戶建立獨立的 bridge 網路
docker network create --driver bridge \
    --subnet=172.20.0.0/24 \
    --ip-range=172.20.0.0/25 \
    --gateway=172.20.0.1 \
    tenant-a-network

docker network create --driver bridge \
    --subnet=172.21.0.0/24 \
    --ip-range=172.21.0.0/25 \
    --gateway=172.21.0.1 \
    tenant-b-network

# 將容器連接到對應的租戶網路
docker run -d --name tenant-a-app \
    --network tenant-a-network \
    nginx

docker run -d --name tenant-b-app \
    --network tenant-b-network \
    nginx

使用內部網路

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 建立內部網路(無法存取外部網路)
docker network create --driver bridge \
    --internal \
    --subnet=172.22.0.0/24 \
    tenant-internal-network

# 容器只能與同網路的其他容器通訊
docker run -d --name internal-app \
    --network tenant-internal-network \
    nginx

網路加密(Overlay 網路)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 初始化 Docker Swarm
docker swarm init

# 建立加密的 overlay 網路
docker network create --driver overlay \
    --opt encrypted \
    --subnet=10.10.0.0/24 \
    secure-tenant-network

# 在 swarm 服務中使用加密網路
docker service create --name secure-app \
    --network secure-tenant-network \
    nginx

使用 iptables 進行進階網路隔離

1
2
3
4
5
6
7
8
9
# 查看 Docker 建立的 iptables 規則
sudo iptables -L -n -v

# 阻止特定租戶網路之間的通訊
sudo iptables -I DOCKER-USER -s 172.20.0.0/24 -d 172.21.0.0/24 -j DROP
sudo iptables -I DOCKER-USER -s 172.21.0.0/24 -d 172.20.0.0/24 -j DROP

# 限制容器對外部網路的存取
sudo iptables -I DOCKER-USER -s 172.20.0.0/24 ! -d 172.20.0.0/24 -j DROP

網路頻寬限制

1
2
3
4
5
6
7
# 使用 tc (traffic control) 限制網路頻寬
# 首先進入容器的網路 namespace
CONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' tenant-a-app)
sudo nsenter -t $CONTAINER_PID -n tc qdisc add dev eth0 root tbf \
    rate 10mbit \
    burst 10kb \
    latency 70ms

儲存隔離與配額

儲存隔離確保租戶的資料安全且互不干擾,同時透過配額機制防止單一租戶耗盡儲存空間。

使用 Docker Volume 進行隔離

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 為每個租戶建立獨立的 volume
docker volume create tenant-a-data
docker volume create tenant-b-data

# 使用 volume 啟動容器
docker run -d --name tenant-a-db \
    -v tenant-a-data:/var/lib/mysql \
    mysql:8.0

# 查看 volume 詳細資訊
docker volume inspect tenant-a-data

設定儲存空間配額

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 使用 overlay2 驅動程式的儲存限制
docker run -d --name tenant-a-app \
    --storage-opt size=10G \
    nginx

# 注意:需要 overlay2 驅動程式配合 xfs 檔案系統
# 編輯 /etc/docker/daemon.json
{
    "storage-driver": "overlay2",
    "storage-opts": [
        "overlay2.size=10G"
    ]
}

使用 LVM 進行儲存隔離

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 建立 LVM thin pool
sudo lvcreate --size 100G --thin --thinpool docker-pool vg-docker

# 配置 Docker 使用 devicemapper 驅動程式
{
    "storage-driver": "devicemapper",
    "storage-opts": [
        "dm.thinpooldev=/dev/mapper/vg--docker-docker--pool",
        "dm.basesize=10G"
    ]
}

建立隔離的 tmpfs 掛載

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 使用 tmpfs 提供臨時儲存(記憶體內)
docker run -d --name tenant-a-app \
    --tmpfs /tmp:size=100m,mode=1777 \
    nginx

# 多個 tmpfs 掛載點
docker run -d --name tenant-a-app \
    --tmpfs /tmp:size=100m \
    --tmpfs /run:size=50m \
    nginx

唯讀檔案系統

1
2
3
4
5
6
# 以唯讀模式執行容器
docker run -d --name secure-app \
    --read-only \
    --tmpfs /tmp:size=50m \
    --tmpfs /run:size=25m \
    nginx

安全性強化

多租戶環境需要額外的安全措施來防止租戶之間的攻擊和資料洩露。

移除不必要的 Capabilities

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 移除所有 capabilities,僅保留必要的
docker run -d --name secure-app \
    --cap-drop=ALL \
    --cap-add=NET_BIND_SERVICE \
    --cap-add=CHOWN \
    nginx

# 常見的安全 capabilities 設定
docker run -d --name secure-app \
    --cap-drop=ALL \
    --cap-add=SETUID \
    --cap-add=SETGID \
    nginx

使用 Seccomp 設定檔

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// secure-profile.json
{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": [
        "SCMP_ARCH_X86_64"
    ],
    "syscalls": [
        {
            "names": [
                "accept",
                "accept4",
                "bind",
                "clone",
                "close",
                "connect",
                "dup",
                "dup2",
                "execve",
                "exit",
                "exit_group",
                "fcntl",
                "fstat",
                "futex",
                "getpid",
                "listen",
                "mmap",
                "mprotect",
                "munmap",
                "open",
                "openat",
                "read",
                "recvfrom",
                "rt_sigaction",
                "rt_sigprocmask",
                "sendto",
                "set_tid_address",
                "socket",
                "stat",
                "write"
            ],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}
1
2
3
4
# 套用自訂 seccomp 設定檔
docker run -d --name secure-app \
    --security-opt seccomp=/path/to/secure-profile.json \
    nginx

使用 AppArmor 設定檔

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 查看可用的 AppArmor 設定檔
docker run --rm --security-opt apparmor=docker-default nginx

# 載入自訂 AppArmor 設定檔
sudo apparmor_parser -r /etc/apparmor.d/docker-tenant-profile

# 使用自訂設定檔啟動容器
docker run -d --name tenant-a-app \
    --security-opt apparmor=docker-tenant-profile \
    nginx

禁止權限提升

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 禁止容器內的權限提升
docker run -d --name secure-app \
    --security-opt=no-new-privileges:true \
    nginx

# 結合其他安全選項
docker run -d --name secure-app \
    --security-opt=no-new-privileges:true \
    --cap-drop=ALL \
    --read-only \
    nginx

使用非 root 使用者執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Dockerfile 中指定非 root 使用者
FROM nginx:alpine

# 建立應用程式使用者
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -D appuser

# 設定適當的檔案權限
RUN chown -R appuser:appgroup /var/cache/nginx /var/log/nginx

# 切換到非 root 使用者
USER appuser

EXPOSE 8080
1
2
3
4
# 執行時指定使用者
docker run -d --name secure-app \
    --user 1001:1001 \
    nginx

Docker Compose 多租戶範例

以下是一個完整的多租戶 Docker Compose 配置範例,展示如何整合各種隔離策略。

多租戶基礎架構配置

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# docker-compose.multi-tenant.yml
version: '3.8'

# 定義多租戶網路
networks:
  tenant-a-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24
          gateway: 172.20.0.1
    driver_opts:
      com.docker.network.bridge.enable_icc: "true"
      com.docker.network.bridge.enable_ip_masquerade: "true"
    internal: false

  tenant-b-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/24
          gateway: 172.21.0.1
    internal: false

  shared-services:
    driver: bridge
    internal: true

# 定義租戶專用 volumes
volumes:
  tenant-a-db-data:
    driver: local
  tenant-a-app-data:
    driver: local
  tenant-b-db-data:
    driver: local
  tenant-b-app-data:
    driver: local

services:
  # ========== Tenant A Services ==========
  tenant-a-web:
    image: nginx:alpine
    container_name: tenant-a-web
    networks:
      - tenant-a-network
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
        reservations:
          cpus: '0.25'
          memory: 128M
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
      - CHOWN
      - SETUID
      - SETGID
    read_only: true
    tmpfs:
      - /tmp:size=50m,mode=1777
      - /var/cache/nginx:size=50m
      - /var/run:size=10m
    volumes:
      - tenant-a-app-data:/usr/share/nginx/html:ro
    ports:
      - "8080:80"
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost/"]
      interval: 30s
      timeout: 10s
      retries: 3

  tenant-a-db:
    image: mysql:8.0
    container_name: tenant-a-db
    networks:
      - tenant-a-network
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
    security_opt:
      - no-new-privileges:true
    volumes:
      - tenant-a-db-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/tenant_a_db_root_password
      MYSQL_DATABASE: tenant_a_db
      MYSQL_USER: tenant_a_user
      MYSQL_PASSWORD_FILE: /run/secrets/tenant_a_db_password
    secrets:
      - tenant_a_db_root_password
      - tenant_a_db_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 30s
      timeout: 10s
      retries: 5

  tenant-a-app:
    image: my-app:latest
    container_name: tenant-a-app
    networks:
      - tenant-a-network
    depends_on:
      tenant-a-db:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: '0.75'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    user: "1001:1001"
    environment:
      - DATABASE_HOST=tenant-a-db
      - DATABASE_NAME=tenant_a_db
      - DATABASE_USER=tenant_a_user
    secrets:
      - tenant_a_db_password

  # ========== Tenant B Services ==========
  tenant-b-web:
    image: nginx:alpine
    container_name: tenant-b-web
    networks:
      - tenant-b-network
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
        reservations:
          cpus: '0.25'
          memory: 128M
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
      - CHOWN
      - SETUID
      - SETGID
    read_only: true
    tmpfs:
      - /tmp:size=50m,mode=1777
      - /var/cache/nginx:size=50m
      - /var/run:size=10m
    volumes:
      - tenant-b-app-data:/usr/share/nginx/html:ro
    ports:
      - "8081:80"
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost/"]
      interval: 30s
      timeout: 10s
      retries: 3

  tenant-b-db:
    image: mysql:8.0
    container_name: tenant-b-db
    networks:
      - tenant-b-network
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
    security_opt:
      - no-new-privileges:true
    volumes:
      - tenant-b-db-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/tenant_b_db_root_password
      MYSQL_DATABASE: tenant_b_db
      MYSQL_USER: tenant_b_user
      MYSQL_PASSWORD_FILE: /run/secrets/tenant_b_db_password
    secrets:
      - tenant_b_db_root_password
      - tenant_b_db_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 30s
      timeout: 10s
      retries: 5

# 定義 secrets
secrets:
  tenant_a_db_root_password:
    file: ./secrets/tenant_a_db_root_password.txt
  tenant_a_db_password:
    file: ./secrets/tenant_a_db_password.txt
  tenant_b_db_root_password:
    file: ./secrets/tenant_b_db_root_password.txt
  tenant_b_db_password:
    file: ./secrets/tenant_b_db_password.txt

部署腳本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# deploy-multi-tenant.sh

# 建立 secrets 目錄
mkdir -p secrets

# 產生隨機密碼
openssl rand -base64 32 > secrets/tenant_a_db_root_password.txt
openssl rand -base64 32 > secrets/tenant_a_db_password.txt
openssl rand -base64 32 > secrets/tenant_b_db_root_password.txt
openssl rand -base64 32 > secrets/tenant_b_db_password.txt

# 設定適當的檔案權限
chmod 600 secrets/*.txt

# 部署多租戶環境
docker compose -f docker-compose.multi-tenant.yml up -d

# 驗證部署狀態
docker compose -f docker-compose.multi-tenant.yml ps

# 檢查網路隔離
echo "Testing network isolation..."
docker exec tenant-a-web ping -c 1 tenant-b-web 2>&1 && \
    echo "WARNING: Network isolation failed!" || \
    echo "OK: Network isolation working correctly"

監控與資源計量

監控和計量是多租戶環境中確保公平資源分配和問題診斷的關鍵。

使用 Docker Stats 監控

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 即時監控所有容器的資源使用
docker stats

# 監控特定租戶的容器
docker stats tenant-a-web tenant-a-db tenant-a-app

# 格式化輸出
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"

# 取得一次性的 stats 輸出(非串流模式)
docker stats --no-stream --format "{{.Name}}: CPU={{.CPUPerc}}, MEM={{.MemUsage}}"

使用 cAdvisor 進行監控

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# docker-compose.monitoring.yml
version: '3.8'

services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    container_name: cadvisor
    privileged: true
    ports:
      - "8090:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    devices:
      - /dev/kmsg
    restart: unless-stopped

使用 Prometheus 收集指標

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']
    metric_relabel_configs:
      # 根據容器名稱添加租戶標籤
      - source_labels: [container_label_com_docker_compose_project]
        regex: 'tenant-(.+)'
        target_label: tenant
        replacement: '$1'

  - job_name: 'docker'
    static_configs:
      - targets: ['host.docker.internal:9323']

自訂資源計量腳本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/bash
# tenant-resource-report.sh

# 產生租戶資源使用報告
generate_tenant_report() {
    local tenant=$1
    local containers=$(docker ps --filter "name=${tenant}" --format "{{.Names}}")

    echo "========================================"
    echo "Tenant: ${tenant}"
    echo "Report Time: $(date)"
    echo "========================================"

    for container in $containers; do
        echo ""
        echo "Container: ${container}"
        echo "----------------------------------------"

        # 取得 CPU 和記憶體使用
        stats=$(docker stats --no-stream --format "{{.CPUPerc}},{{.MemUsage}},{{.NetIO}},{{.BlockIO}}" $container)

        IFS=',' read -r cpu mem net block <<< "$stats"

        echo "CPU Usage: ${cpu}"
        echo "Memory Usage: ${mem}"
        echo "Network I/O: ${net}"
        echo "Block I/O: ${block}"

        # 取得容器運行時間
        uptime=$(docker inspect --format='{{.State.StartedAt}}' $container)
        echo "Started At: ${uptime}"
    done
}

# 計算租戶總資源使用
calculate_tenant_totals() {
    local tenant=$1

    # 使用 docker stats API 取得精確數值
    docker stats --no-stream --format '{{.Name}},{{.CPUPerc}},{{.MemPerc}}' | \
        grep "^${tenant}" | \
        awk -F',' '
        BEGIN { cpu_total=0; mem_total=0; count=0 }
        {
            gsub(/%/, "", $2);
            gsub(/%/, "", $3);
            cpu_total += $2;
            mem_total += $3;
            count++
        }
        END {
            printf "Total Containers: %d\n", count
            printf "Total CPU Usage: %.2f%%\n", cpu_total
            printf "Avg Memory Usage: %.2f%%\n", mem_total/count
        }'
}

# 產生所有租戶的報告
for tenant in tenant-a tenant-b; do
    generate_tenant_report $tenant
    calculate_tenant_totals $tenant
    echo ""
done

Grafana Dashboard 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
  "dashboard": {
    "title": "Docker Multi-Tenant Resource Dashboard",
    "panels": [
      {
        "title": "CPU Usage by Tenant",
        "type": "graph",
        "targets": [
          {
            "expr": "sum(rate(container_cpu_usage_seconds_total{name=~\"tenant-.*\"}[5m])) by (tenant) * 100",
            "legendFormat": "{{tenant}}"
          }
        ]
      },
      {
        "title": "Memory Usage by Tenant",
        "type": "graph",
        "targets": [
          {
            "expr": "sum(container_memory_usage_bytes{name=~\"tenant-.*\"}) by (tenant)",
            "legendFormat": "{{tenant}}"
          }
        ]
      },
      {
        "title": "Network Traffic by Tenant",
        "type": "graph",
        "targets": [
          {
            "expr": "sum(rate(container_network_receive_bytes_total{name=~\"tenant-.*\"}[5m])) by (tenant)",
            "legendFormat": "{{tenant}} - RX"
          },
          {
            "expr": "sum(rate(container_network_transmit_bytes_total{name=~\"tenant-.*\"}[5m])) by (tenant)",
            "legendFormat": "{{tenant}} - TX"
          }
        ]
      },
      {
        "title": "Disk I/O by Tenant",
        "type": "graph",
        "targets": [
          {
            "expr": "sum(rate(container_fs_reads_bytes_total{name=~\"tenant-.*\"}[5m])) by (tenant)",
            "legendFormat": "{{tenant}} - Read"
          },
          {
            "expr": "sum(rate(container_fs_writes_bytes_total{name=~\"tenant-.*\"}[5m])) by (tenant)",
            "legendFormat": "{{tenant}} - Write"
          }
        ]
      }
    ]
  }
}

資源配額告警

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# alertmanager-rules.yml
groups:
  - name: tenant-resource-alerts
    rules:
      - alert: TenantHighCPUUsage
        expr: sum(rate(container_cpu_usage_seconds_total{name=~"tenant-.*"}[5m])) by (tenant) > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage for tenant {{ $labels.tenant }}"
          description: "Tenant {{ $labels.tenant }} CPU usage is above 80% for more than 5 minutes"

      - alert: TenantMemoryExceeded
        expr: sum(container_memory_usage_bytes{name=~"tenant-.*"}) by (tenant) / sum(container_spec_memory_limit_bytes{name=~"tenant-.*"}) by (tenant) > 0.9
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Memory limit exceeded for tenant {{ $labels.tenant }}"
          description: "Tenant {{ $labels.tenant }} is using more than 90% of allocated memory"

      - alert: TenantNetworkAnomalous
        expr: sum(rate(container_network_transmit_bytes_total{name=~"tenant-.*"}[5m])) by (tenant) > 100000000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Unusual network activity for tenant {{ $labels.tenant }}"
          description: "Tenant {{ $labels.tenant }} has unusual outbound network traffic"

總結

Docker 多租戶資源隔離是建構安全、可靠的共享基礎設施的關鍵。透過本文介紹的各種技術和策略,您可以:

  1. Cgroups:精確控制 CPU、記憶體和 I/O 資源配額
  2. Namespace:實現行程、網路和使用者的完全隔離
  3. 網路隔離:建立租戶專屬的網路環境並控制流量
  4. 儲存隔離:保護租戶資料並設定儲存配額
  5. 安全強化:透過 capabilities、seccomp 和 AppArmor 減少攻擊面
  6. 監控計量:追蹤資源使用並實現公平的成本分攤

實施多租戶隔離時,應根據實際需求在安全性、效能和複雜度之間取得平衡。對於高度敏感的工作負載,可考慮使用 gVisor 或 Kata Containers 等強化隔離方案,甚至採用每個租戶獨立主機的架構。

透過持續監控和定期審查隔離策略,您可以確保多租戶環境的長期安全和穩定運行。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy