iT邦幫忙

2025 iThome 鐵人賽

DAY 8
2
DevOps

賢者大叔的容器修煉手札系列 第 8

ConfigMap - 讓你的應用配置不再寫死在程式碼裡 ⚙️

  • 分享至 

  • xImage
  •  

賢者大叔的容器修煉手札系列 第 8 篇

ConfigMap - 讓你的應用配置不再寫死在程式碼裡 ⚙️

還記得我們上一章學會了資源管理,讓 Pod 不再餓肚子嗎?現在我們的 Pod 已經能穩定運行了,但是各位,有沒有遇到過這種情況:每次要修改應用程式的配置(比如資料庫連線、API 端點、feature flag),就要重新打包 Docker image?
就像你是一家連鎖餐廳的老闆,每家分店的菜單價格、營業時間都不一樣,如果每次調整都要重新印製所有的 SOP 手冊,是不是很麻煩?ConfigMap 就是要解決這個「配置與程式碼分離」的問題!

今日學習目標 🎯

✅ 理解配置外部化的重要性:學會將配置從程式碼中解耦

✅ 掌握 ConfigMap 的多種使用方式:環境變數、檔案掛載、命令列參數

✅ 實作配置熱更新機制:不重啟容器就能更新配置

✅ 培養配置管理思維:從硬編碼到配置驅動的架構轉變

為什麼需要 ConfigMap?🤔

沒有配置管理的災難現場

想像一下,你是一家跨國企業的 IT 主管,每個國家的系統都需要不同的配置:

# 災難場景:hard code配置 😱
FROM node:16
COPY . /app
WORKDIR /app

# TW 版本
ENV DB_HOST=tw-db.company.com
ENV CURRENCY=TWD
ENV LANGUAGE=zh-TW

# JP 版本
# 如果要部署到日本,就要重新打包... 🔴
# ENV DB_HOST=jp-db.company.com  
# ENV CURRENCY=JPY
# ENV LANGUAGE=ja-JP

這樣會造成什麼問題?
🔴 一個配置,多個 Image:每個環境都要維護不同的 Image
🔴 部署複雜性:無法用同一個 Image 部署到不同環境
🔴 安全風險:敏感資訊(如 API Key)被寫死在 Image 裡
🔴 維護噩夢:修改一個配置就要重新 CI/CD 流程

Docker Compose 時代的解決方案

  1. 環境變數
  2. 掛載 volume
# docker-compose.yml
version: '3'
services:
  web:
    image: my-app:latest
    environment:
      - DB_HOST=${DB_HOST}
      - CURRENCY=${CURRENCY}
    volumes:
      - ./config.json:/app/config.json

這已經比 hard code 好很多,但在 K8s cluster 中還是有限制:

🟡 檔案管理困難:配置檔案散落在各個節點
🟡 版本控制問題:難以追蹤配置變更歷史
🟡 共享困難:多個 Pod 難以共享同一份配置

https://ithelp.ithome.com.tw/upload/images/20250822/20104930YWmDIl4Zt3.png

ConfigMap:Kubernetes 的配置管理專家 🎛️

ConfigMap 就像餐廳的「標準作業程序手冊」

想像你經營一家連鎖餐廳:

  • 📋 SOP 手冊:ConfigMap(標準化的配置)
  • 👨‍🍳 廚師:Pod(執行服務的容器)
  • 🏪 分店:不同的環境(dev/staging/prod)
# 餐廳 SOP 手冊 = ConfigMap
營業時間: "09:00-22:00"
主廚推薦: "牛肉麵"
價格表:
  牛肉麵: 280
  排骨麵: 260
  餛飩麵: 240

每個廚師(Pod)都可以參考同一本手冊(ConfigMap),但根據不同分店(環境)可能有不同的版本。

https://ithelp.ithome.com.tw/upload/images/20250822/20104930W5wW58qz41.png

所以 ConfigMap 本質上是 K8s 中的一個 Key-Value Pair 儲存對象,專門用來存放非敏感的配置資料。

ConfigMap 的四種使用方式 🎭

方式一:環境變數注入 🌍

最常見的使用方式,就像給員工發工作證

  • envFrom
  • env
# app-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:
  # 簡單的 key-value 配置
  DATABASE_HOST: "mysql.default.svc.cluster.local"
  DATABASE_PORT: "3306"
  APP_ENV: "production"
  DEBUG_MODE: "false"
  MAX_CONNECTIONS: "100"
  CACHE_TTL: "3600"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: app
        image: nginx:1.21
        # 方式1:直接引用整個 ConfigMap
        envFrom:
        - configMapRef:
            name: app-config
        # 方式2:選擇性引用特定 key
        env:
        - name: DB_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DATABASE_HOST
        - name: DB_PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DATABASE_PORT
# 創建 ConfigMap
> kubectl apply -f app-config.yaml

# 檢查 ConfigMap 內容
> kubectl get configmap app-config -o yaml

# 檢查 Pod 環境變數
> kubectl exec -it deployment/web-app -- env | grep -E "(DATABASE|APP_ENV)"

方式二:檔案掛載 📁

適合複雜配置檔案,就像給員工一本完整的操作手冊

  • volumeMounts
# nginx-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  # 完整的 nginx 配置檔案
  nginx.conf: |
    events {
        worker_connections 1024;
    }
    http {
        upstream backend {
            server app1.default.svc.cluster.local:8080;
            server app2.default.svc.cluster.local:8080;
        }
        server {
            listen 80;
            location / {
                proxy_pass http://backend;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
            }
            location /health {
                return 200 "OK";
            }
        }
    }
  # 額外的配置檔案
  mime.types: |
    types {
        text/html                             html htm shtml;
        text/css                              css;
        application/javascript                js;
        application/json                      json;
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-proxy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-proxy
  template:
    metadata:
      labels:
        app: nginx-proxy
    spec:
      containers:
      - name: nginx
        image: nginx:1.21
        ports:
        - containerPort: 80
        volumeMounts:
        # 將 ConfigMap 掛載為檔案
        - name: nginx-config-volume
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf  # 只掛載特定檔案
        - name: nginx-config-volume
          mountPath: /etc/nginx/mime.types
          subPath: mime.types
      volumes:
      - name: nginx-config-volume
        configMap:
          name: nginx-config
# 部署 nginx
kubectl apply -f nginx-config.yaml

# 檢查掛載的配置檔案
kubectl exec -it deployment/nginx-proxy -- cat /etc/nginx/nginx.conf

# 檢查 nginx 配置是否正確
kubectl exec -it deployment/nginx-proxy -- nginx -t

方式三:目錄掛載 📂

將整個 ConfigMap 掛載為目錄,適合多檔案配置

  • volumeMounts
# multi-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-configs
data:
  database.yml: |
    production:
      host: mysql.default.svc.cluster.local
      port: 3306
      database: myapp_production
      pool: 20
  redis.yml: |
    production:
      host: redis.default.svc.cluster.local
      port: 6379
      db: 0
  application.properties: |
    server.port=8080
    logging.level.root=INFO
    spring.profiles.active=production
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-app
  template:
    metadata:
      labels:
        app: spring-app
    spec:
      containers:
      - name: app
        image: openjdk:11-jre-slim
        command: ["sleep", "3600"]  # 用於測試
        volumeMounts:
        # 將整個 ConfigMap 掛載為目錄
        - name: config-volume
          mountPath: /app/config
      volumes:
      - name: config-volume
        configMap:
          name: app-configs
# 部署應用
kubectl apply -f multi-config.yaml

# 檢查掛載的目錄結構
kubectl exec -it deployment/spring-app -- ls -la /app/config

# 檢查各個配置檔案
kubectl exec -it deployment/spring-app -- cat /app/config/database.yml

方式四:命令列參數 💻

將 ConfigMap 的值作為容器啟動參數

  • env
# cli-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: cli-config
data:
  log-level: "info"
  port: "8080"
  workers: "4"
  timeout: "30s"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cli-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cli-app
  template:
    metadata:
      labels:
        app: cli-app
    spec:
      containers:
      - name: app
        image: nginx:1.21
        command: ["nginx"]
        args:
        - "-g"
        - "daemon off;"
        - "-c"
        - "/etc/nginx/nginx.conf"
        env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: cli-config
              key: log-level
        - name: PORT
          valueFrom:
            configMapKeyRef:
              name: cli-config
              key: port

https://ithelp.ithome.com.tw/upload/images/20250823/20104930UCtlEdt5Cq.png

ConfigMap 的動態更新機制 🔄

這是 ConfigMap 最強大的功能之一!

實時配置更新實戰

# dynamic-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dynamic-config
data:
  app.properties: |
    # 應用程式配置
    feature.new_ui=false
    feature.payment_v2=false
    cache.ttl=3600
    rate.limit=1000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dynamic-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: dynamic-app
  template:
    metadata:
      labels:
        app: dynamic-app
    spec:
      containers:
      - name: app
        image: nginx:1.21
        volumeMounts:
        - name: config-volume
          mountPath: /app/config
        # 用於監控配置變更的 sidecar
      - name: config-reloader
        image: jimmidyson/configmap-reload:v0.5.0
        args:
        - --volume-dir=/app/config
        - --webhook-url=http://localhost:8080/reload
        volumeMounts:
        - name: config-volume
          mountPath: /app/config
      volumes:
      - name: config-volume
        configMap:
          name: dynamic-config

測試動態更新

# 部署應用
kubectl apply -f dynamic-config.yaml

# 檢查初始配置
kubectl exec -it deployment/dynamic-app -c app -- cat /app/config/app.properties

# 更新 ConfigMap
kubectl patch configmap dynamic-config --patch='
data:
  app.properties: |
    # 應用程式配置 - 已更新
    feature.new_ui=true
    feature.payment_v2=true
    cache.ttl=1800
    rate.limit=2000
'

# 等待幾秒後檢查配置(通常需要 30-60 秒)
sleep 60
kubectl exec -it deployment/dynamic-app -c app -- cat /app/config/app.properties

重要提醒 ⚠️
配置更新的限制:

  1. 環境變數方式:需要重啟 Pod 才能生效
  2. 檔案掛載方式:自動更新,但應用程式需要支援熱重載
  3. 更新延遲:通常需要 30-60 秒才會同步到 Pod
  4. 應用程式支援:需要應用程式主動讀取配置變更

https://ithelp.ithome.com.tw/upload/images/20250823/20104930LBNnt5xchN.png

ConfigMap 的最佳實踐 🏆

  1. 命名規範與標籤管理
# 良好的命名和標籤實踐
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config-v1.2.0  # 包含版本號
  namespace: production
  labels:
    app: myapp
    component: backend
    version: v1.2.0
    environment: production
  annotations:
    description: "MyApp backend configuration for production"
    last-updated: "2024-01-15T10:30:00Z"
    updated-by: "devops-team"
data:
  # 配置內容...
  1. 配置分層管理
# 基礎配置 - 所有環境共用
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-base-config
data:
  app.name: "MyApplication"
  app.version: "1.2.0"
  logging.format: "json"
---
# 環境特定配置 - 生產環境
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-prod-config
data:
  database.host: "prod-mysql.company.com"
  cache.size: "1000"
  debug.enabled: "false"
---
# 功能開關配置 - 可以獨立更新
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-features
data:
  feature.new_ui: "true"
  feature.payment_v2: "false"

ConfigMap 的安全性考慮 🔒

敏感資訊處理

# ❌ 錯誤:將敏感資訊放在 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: bad-config
data:
  database.password: "super-secret-password"  # 這是錯誤的!
  api.key: "sk-1234567890abcdef"              # 這也是錯誤的!

---
# ✅ 正確:敏感資訊使用 Secret
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  database.password: c3VwZXItc2VjcmV0LXBhc3N3b3Jk  # base64 編碼
  api.key: c2stMTIzNDU2Nzg5MGFiY2RlZg==

---
# ConfigMap 只存放非敏感配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: good-config
data:
  database.host: "mysql.default.svc.cluster.local"
  database.port: "3306"
  database.name: "myapp"

ConfigMap 的限制與注意事項 ⚠️

  1. 大小限制
# ConfigMap 的限制
大小限制: 1MB (1,048,576 bytes)
Key 數量: 沒有硬性限制,但建議不超過 100 個
Key 名稱: 必須是有效的 DNS 子域名

# 如果配置太大,考慮分割
apiVersion: v1
kind: ConfigMap
metadata:
  name: large-config-part1
data:
  config.part1: |
    # 第一部分配置...
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: large-config-part2
data:
  config.part2: |
    # 第二部分配置...

  1. 更新延遲
# ConfigMap 更新到 Pod 的延遲時間
最短延遲: 30 秒
最長延遲: 60 秒 + kubelet 同步週期

# 如果需要立即更新,可以重啟 Pod
kubectl rollout restart deployment/myapp
  1. 二進位檔案處理
# ConfigMap 不適合存放二進位檔案
# 如果需要,使用 base64 編碼,但有大小限制

apiVersion: v1
kind: ConfigMap
metadata:
  name: binary-config
binaryData:
  # 使用 binaryData 而不是 data
  logo.png: iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==

實戰演練 Grafana Dashboard Provioning 為例

場景設定

假設你是一家電商公司的 DevOps 工程師,需要為不同環境(dev/staging/prod)部署 Grafana 監控系統。
每個環境都有不同的:

  • 資料源配置(Prometheus、Loki 等)
  • Dashboard 設定
  • 告警通知設定
  • 使用者權限配置

讓我們用 ConfigMap 來優雅地解決這個問題!
以 Dashboard 的 Text Panel 為例,演示如何設定 configMap 掛載到 Grafana Pod,然後更新 text 內容。

.
├── grafana-deployment-only.yaml
├── grafana.ini
├── kind-cluster-config.yaml
├── manage-grafana.sh
├── provisioning
│   └── dashboards
│       ├── dashboard-providers.yaml
│       └── welcome-dashboard.json
└── update-dashboard.sh

provioning 資料夾中的就是 Grafana Provisioning(配置) 的檔案,我們能用Grafana 使用 provisioning 文件進行自動化配置,支持 GitOps。有興趣了解能參考小弟的書OpenTelemetry 入門指南:建立全面可觀測性架構 ch 9

**步驟 1:用 Kind 建立 K8s cluster **

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30300
    hostPort: 30300
    protocol: TCP
  - containerPort: 30301
    hostPort: 30301
    protocol: TCP
  - containerPort: 30302
    hostPort: 30302
    protocol: TCP
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
# 移除之前的cluser
kind delete cluster --name multi-node-cluster

# 建立k8s cluster
kind create cluster --name multi-node-cluster --config kind-cluster-config.yaml

步驟 2:創建 Deployment、 ConfigMap
grafana-deployment-only.yaml
這個文件包含了三個 Kubernetes 資源:

  • Namespace︰創建 monitoring 命名空間用於隔離監控相關資源
  • Deployment︰佈署 grafana,掛載 grafana.iniprovisioning dashboard
    • grafana-config: grafana server 設定檔 (/etc/grafana/grafana.ini)
    • grafana-provisioning-dashboards: dashboard provider 配置
    • grafana-dashboards: dashboard 文件目錄
    • grafana-storage: 數據儲存 (使用 emptyDir)
  • Service︰端口: 3000 (內部) → 30300 (外部)
# grafana-deployment-only.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: monitoring
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: monitoring
  labels:
    app: grafana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      securityContext:
        fsGroup: 472
        runAsUser: 472
        runAsNonRoot: true
      containers:
      - name: grafana
        image: grafana/grafana:12.1.0
        ports:
        - containerPort: 3000
          name: http-grafana
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /robots.txt
            port: 3000
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 30
          successThreshold: 1
          timeoutSeconds: 2
        livenessProbe:
          failureThreshold: 3
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: 3000
          timeoutSeconds: 1
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
          limits:
            cpu: 500m
            memory: 1Gi
        volumeMounts:
        - name: grafana-config
          mountPath: /etc/grafana/grafana.ini
          subPath: grafana.ini
        - name: grafana-provisioning-dashboards
          mountPath: /etc/grafana/provisioning/dashboards/dashboards.yaml
          subPath: dashboard-providers.yaml
        - name: grafana-dashboards
          mountPath: /var/lib/grafana/dashboards
        - name: grafana-storage
          mountPath: /var/lib/grafana
      volumes:
      - name: grafana-config
        configMap:
          name: grafana-config
      - name: grafana-provisioning-dashboards
        configMap:
          name: grafana-provisioning-dashboards
      - name: grafana-dashboards
        configMap:
          name: grafana-dashboards
      - name: grafana-storage
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: monitoring
  labels:
    app: grafana
spec:
  type: NodePort
  ports:
  - port: 3000
    protocol: TCP
    targetPort: 3000
    nodePort: 30300
  selector:
    app: grafana
# 能選擇執行kubectl
kubectl wait --for=condition=available --timeout=300s deployment/grafana -n monitoring
  
# 或者執行 manage-grafana.sh
> ./manage-grafana.sh deploy

[INFO] 🚀 部署 Grafana...
[INFO] 📄 從獨立文件創建 ConfigMaps...
namespace/monitoring created
[INFO] 創建 grafana-config ConfigMap...
configmap/grafana-config created
[INFO] 創建 grafana-provisioning-dashboards ConfigMap...
configmap/grafana-provisioning-dashboards created
[INFO] 創建 grafana-dashboards ConfigMap...
configmap/grafana-dashboards created
[SUCCESS] ✅ 所有 ConfigMaps 創建完成
namespace/monitoring configured
deployment.apps/grafana created
service/grafana created
[INFO] ⏳ 等待 Grafana 啟動...
deployment.apps/grafana condition met
[SUCCESS] ✅ Grafana 部署完成!
🌐 訪問 URL: http://172.18.0.3:30300
👤 用戶名: admin
🔑 密碼: admin123

能透過http://172.18.0.3:30300看到以下畫面
https://ithelp.ithome.com.tw/upload/images/20250823/201049304a1gB9EcwK.png

然後修改welcome-dashboard.json,執行

# 可以選擇自己更新 ConfigMap
kubectl create configmap grafana-dashboards \
      --from-file="$dashboard_file" \
      -n monitoring \
      --dry-run=client -o yaml | kubectl apply -f -

# 或是透過 manage-grafana.sh update-dashboard
./manage-grafana.sh update-dashboard provisioning/dashboards/welcome-dashboard.json

等個約一分鐘後按下F5 refresh 瀏覽器,就能看到以下畫化。
https://ithelp.ithome.com.tw/upload/images/20250823/201049302Wcmghm7eY.png

manage-grafana.sh

#!/bin/bash
# manage-grafana.sh
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }

# 檢查必要文件
check_files() {
  local missing_files=()
  
  if [[ ! -f "grafana.ini" ]]; then
      missing_files+=("grafana.ini")
  fi
  
  if [[ ! -f "provisioning/dashboards/dashboard-providers.yaml" ]]; then
      missing_files+=("provisioning/dashboards/dashboard-providers.yaml")
  fi
  
  if [[ ${#missing_files[@]} -gt 0 ]]; then
      log_error "缺少必要文件:"
      for file in "${missing_files[@]}"; do
          echo "  - $file"
      done
      return 1
  fi
  
  return 0
}

# 創建目錄結構
setup_directories() {
  log_info "📁 創建目錄結構..."
  mkdir -p provisioning/dashboards
  log_success "✅ 目錄結構創建完成"
}

# 從文件創建 ConfigMaps
create_configmaps() {
  log_info "📄 從獨立文件創建 ConfigMaps..."
  
  # 檢查文件
  if ! check_files; then
      return 1
  fi
  
  # 創建 namespace
  kubectl create namespace monitoring --dry-run=client -o yaml | kubectl apply -f -
  
  # 創建 grafana.ini ConfigMap
  log_info "創建 grafana-config ConfigMap..."
  kubectl create configmap grafana-config \
      --from-file=grafana.ini \
      -n monitoring \
      --dry-run=client -o yaml | kubectl apply -f -
  
  # 創建 provisioning ConfigMap
  log_info "創建 grafana-provisioning-dashboards ConfigMap..."
  kubectl create configmap grafana-provisioning-dashboards \
      --from-file=dashboard-providers.yaml=provisioning/dashboards/dashboard-providers.yaml \
      -n monitoring \
      --dry-run=client -o yaml | kubectl apply -f -
  
  # 創建 dashboards ConfigMap
  log_info "創建 grafana-dashboards ConfigMap..."
  if [[ -d "provisioning/dashboards" ]]; then
      # 排除 dashboard-providers.yaml,只包含 .json 文件
      find provisioning/dashboards -name "*.json" -exec \
          kubectl create configmap grafana-dashboards \
              --from-file={} \
              -n monitoring \
              --dry-run=client -o yaml \; | kubectl apply -f -
  else
      # 如果沒有 dashboard 文件,創建空的 ConfigMap
      kubectl create configmap grafana-dashboards \
          -n monitoring \
          --dry-run=client -o yaml | kubectl apply -f -
  fi
  
  log_success "✅ 所有 ConfigMaps 創建完成"
}

# 部署 Grafana
deploy_grafana() {
  log_info "🚀 部署 Grafana..."
  
  # 先創建 ConfigMaps
  create_configmaps || return 1
  
  # 部署 Grafana
  kubectl apply -f grafana-deployment-only.yaml
  
  # 等待部署完成
  log_info "⏳ 等待 Grafana 啟動..."
  kubectl wait --for=condition=available --timeout=300s deployment/grafana -n monitoring
  
  # 獲取訪問資訊
  NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}')
  log_success "✅ Grafana 部署完成!"
  echo "🌐 訪問 URL: http://$NODE_IP:30300"
  echo "👤 用戶名: admin"
  echo "🔑 密碼: admin123"
}

# 更新 grafana.ini (需要重啟)
update_grafana_ini() {
  log_info "🔧 更新 grafana.ini 配置..."
  
  if [[ ! -f "grafana.ini" ]]; then
      log_error "grafana.ini 文件不存在"
      return 1
  fi
  
  # 更新 ConfigMap
  kubectl create configmap grafana-config \
      --from-file=grafana.ini \
      -n monitoring \
      --dry-run=client -o yaml | kubectl apply -f -
  
  # 重啟 Deployment
  log_warning "⚠️  grafana.ini 更新需要重啟 Pod..."
  kubectl rollout restart deployment/grafana -n monitoring
  
  # 等待重啟完成
  kubectl rollout status deployment/grafana -n monitoring
  
  log_success "✅ Grafana 配置更新並重啟完成"
}

# 更新 provisioning 配置
update_provisioning() {
  log_info "⚙️  更新 provisioning 配置..."
  
  if [[ ! -f "provisioning/dashboards/dashboard-providers.yaml" ]]; then
      log_error "dashboard-providers.yaml 文件不存在"
      return 1
  fi
  
  # 更新 ConfigMap
  kubectl create configmap grafana-provisioning-dashboards \
      --from-file=dashboard-providers.yaml=provisioning/dashboards/dashboard-providers.yaml \
      -n monitoring \
      --dry-run=client -o yaml | kubectl apply -f -
  
  log_success "✅ Provisioning 配置更新完成 (Grafana 會自動檢測)"
}

# 更新 Dashboard
update_dashboard() {
  local dashboard_file=$1
  
  if [[ -z "$dashboard_file" ]]; then
      log_error "請指定 dashboard 文件路徑"
      echo "使用方法: $0 update-dashboard <dashboard-file.json>"
      return 1
  fi
  
  if [[ ! -f "$dashboard_file" ]]; then
      log_error "Dashboard 文件不存在: $dashboard_file"
      return 1
  fi
  
  log_info "📊 更新 Dashboard: $dashboard_file"
  
  # 獲取文件名
  filename=$(basename "$dashboard_file")
  
  # 更新 ConfigMap
  kubectl create configmap grafana-dashboards \
      --from-file="$dashboard_file" \
      -n monitoring \
      --dry-run=client -o yaml | kubectl apply -f -
  
  log_success "✅ Dashboard 更新完成,Grafana 會在 10 秒內自動檢測變更"
}

# 批量更新所有 dashboards
update_all_dashboards() {
  log_info "📊 批量更新所有 Dashboard 文件..."
  
  if [[ ! -d "provisioning/dashboards" ]]; then
      log_error "provisioning/dashboards 目錄不存在"
      return 1
  fi
  
  # 查找所有 .json 文件
  json_files=($(find provisioning/dashboards -name "*.json"))
  
  if [[ ${#json_files[@]} -eq 0 ]]; then
      log_warning "沒有找到 .json dashboard 文件"
      return 0
  fi
  
  # 更新 ConfigMap
  kubectl create configmap grafana-dashboards \
      --from-file=provisioning/dashboards/ \
      -n monitoring \
      --dry-run=client -o yaml | kubectl apply -f -
  
  log_success "✅ 所有 Dashboard 更新完成 (共 ${#json_files[@]} 個文件)"
  for file in "${json_files[@]}"; do
      echo "  - $(basename "$file")"
  done
}

# 查看狀態
show_status() {
  log_info "📋 Grafana 狀態資訊"
  
  echo "=== Namespace ==="
  kubectl get namespace monitoring 2>/dev/null || echo "Namespace 'monitoring' 不存在"
  
  echo -e "\n=== ConfigMaps ==="
  kubectl get configmap -n monitoring 2>/dev/null || echo "沒有 ConfigMaps"
  
  echo -e "\n=== Deployments ==="
  kubectl get deployment -n monitoring 2>/dev/null || echo "沒有 Deployments"
  
  echo -e "\n=== Pods ==="
  kubectl get pods -n monitoring 2>/dev/null || echo "沒有 Pods"
  
  echo -e "\n=== Services ==="
  kubectl get svc -n monitoring 2>/dev/null || echo "沒有 Services"
  
  # 訪問資訊
  if kubectl get svc grafana -n monitoring &>/dev/null; then
      NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}' 2>/dev/null)
      echo -e "\n=== 訪問資訊 ==="
      echo "🌐 URL: http://$NODE_IP:30300"
      echo "👤 用戶名: admin"
      echo "🔑 密碼: admin123"
  fi
}

# 查看 ConfigMap 內容
show_configmap() {
  local configmap_name=$1
  
  if [[ -z "$configmap_name" ]]; then
      echo "可用的 ConfigMaps:"
      kubectl get configmap -n monitoring --no-headers | awk '{print "  - " $1}'
      return 0
  fi
  
  log_info "📄 查看 ConfigMap: $configmap_name"
  kubectl get configmap "$configmap_name" -n monitoring -o yaml
}

# 清理資源
cleanup() {
  log_warning "🗑️  清理 Grafana 資源..."
  read -p "確定要刪除所有 Grafana 資源嗎?(y/N): " -n 1 -r
  echo
  if [[ $REPLY =~ ^[Yy]$ ]]; then
      kubectl delete namespace monitoring
      log_success "✅ 清理完成"
  else
      log_info "取消清理操作"
  fi
}

# 主函數
main() {
  case $1 in
      "setup")
          setup_directories
          ;;
      "deploy")
          deploy_grafana
          ;;
      "create-configmaps")
          create_configmaps
          ;;
      "update-ini")
          update_grafana_ini
          ;;
      "update-provisioning")
          update_provisioning
          ;;
      "update-dashboard")
          update_dashboard $2
          ;;
      "update-all-dashboards")
          update_all_dashboards
          ;;
      "status")
          show_status
          ;;
      "show-configmap")
          show_configmap $2
          ;;
      "cleanup")
          cleanup
          ;;
      *)
          echo "Grafana 文件管理腳本"
          echo ""
          echo "使用方法: $0 <command> [options]"
          echo ""
          echo "命令列表:"
          echo "  setup                    - 創建目錄結構"
          echo "  deploy                   - 完整部署 Grafana"
          echo "  create-configmaps        - 從文件創建 ConfigMaps"
          echo "  update-ini               - 更新 grafana.ini (需要重啟)"
          echo "  update-provisioning      - 更新 provisioning 配置"
          echo "  update-dashboard <file>  - 更新指定的 dashboard"
          echo "  update-all-dashboards    - 更新所有 dashboard 文件"
          echo "  status                   - 查看部署狀態"
          echo "  show-configmap [name]    - 查看 ConfigMap 內容"
          echo "  cleanup                  - 清理所有資源"
          echo ""
          echo "文件結構:"
          echo "  grafana.ini"
          echo "  provisioning/dashboards/dashboard-providers.yaml"
          echo "  provisioning/dashboards/*.json"
          echo "  grafana-deployment-only.yaml"
          exit 1
          ;;
  esac
}

main "$@"

總結

ConfigMap 從檔案創建的優勢:

✅ 文件分離 - JSON 和 YAML 分開管理
✅ 版本控制 - 可以用 Git 追蹤變更
✅ 易於編輯 - 使用專門的編輯器
✅ 重複使用 - 同一個 dashboard 可用於多個環境
✅ 團隊協作 - 多人可以同時編輯不同文件

關注使用者體驗

  • 零停機更新:透過 ConfigMap 熱更新減少服務中斷
  • 環境一致性:確保開發、測試、生產環境配置管理一致
  • 快速回滾:配置版本化管理,支援快速回滾

https://ithelp.ithome.com.tw/upload/images/20250823/20104930j46po9JC3w.png


上一篇
Kubernetes 資源管理 - 讓你的 Pod 不再餓肚子
下一篇
Secret 基礎 - 守護你的機密資料
系列文
賢者大叔的容器修煉手札19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言