iT邦幫忙

2025 iThome 鐵人賽

DAY 18
1
DevOps

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

DevSpace Profiles - 環境切換的魔法師 🎭

  • 分享至 

  • xImage
  •  

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

DevSpace Profiles - 環境切換的魔法師 🎭

還記得昨天我們學會了 Dependencies 編排微服務嗎?現在你的應用已經能夠優雅地處理服務間的依賴關係了。

但是,作為一個經驗豐富的開發者,你一定遇過這樣的場景:
"咦?這個功能在開發環境運行得好好的,怎麼到了測試環境就出問題了?"
"我想在本機測試一下運營環境的配置,但又不想影響正在開發的版本..."
"能不能讓同一套程式碼,在不同環境下使用不同的設定?"
"或者是嘗試佈署成高可用架構,或者有包含監控功能的系統架構來進行系統行為的測試。"

就像一個變色龍 🦎 能夠根據環境改變自己的顏色一樣,DevSpace Profiles 就是讓你的應用能夠在不同環境間自由切換的魔法師!

✅ 核心學習目標

✅ 理解 Profiles 四大機制 - 一套配置,多種環境的魔法

✅ 掌握 Activation 自動激活 - 智慧判斷環境自動切換

✅ 學會 Patches/Merge/Replace - 精準修改配置的三大絕技

✅ 實戰環境切換 - 本機輕鬆切換到其他環境測試

🤔 為什麼需要 Profiles?

傳統做法的痛點

想像你是一家連鎖餐廳的主廚 👨‍🍳,你有一道招牌菜,但在不同分店需要做些調整:

台北店:口味偏清淡,不加辣椒
台中店:口味適中,微辣
高雄店:口味偏重,要加很多辣椒

傳統做法是什麼?寫三份不同的食譜!但這樣會有什麼問題?
https://ithelp.ithome.com.tw/upload/images/20250902/20104930A2jg2Hy3Sg.png

看到了嗎?左邊傳統做法就像是為每家分店都寫一份完整的食譜,90% 的內容都是重複的!

而 DevSpace Profiles 就是圖片右邊,像是寫一份基礎食譜,然後為每家分店只記錄差異:

  • 基礎食譜:所有共通的步驟和材料
  • 台北店 Profile:「不加辣椒」
  • 台中店 Profile:「加一點辣椒」
  • 高雄店 Profile:「加很多辣椒」

🎭 Profiles 四大機制

DevSpace Profiles 提供四種強大的機制,讓你能夠靈活地管理不同環境的配置:

🎯 1. Activation(啟用機制)

自動根據條件啟用 Profile

vars:
 ENV:
   default: development

profiles:
 - name: production
   activation:
     - vars:
         ENV: production
   patches:
     - op: replace
       path: images.backend.image
       value: john/prodbackend

在這個例子中:

當 DevSpace 變數 ENV 的值為 production 時,production profile 會被啟用
可以使用 --var ENV=production 來覆蓋預設值。

使用正則表達式
當環境變數 ENV 匹配正則表達式 prod-\d+ 時,profile 會被啟用

profiles:
  - name: production
    activation:
      - env:
          ENV: "prod-\\d+"
    patches:
      - op: replace
        path: images.backend.image
        value: john/prodbackend

組合使用 vars 和 env,全部匹配(AND 邏輯)

profiles:
  - name: production
    activation:
      - env:
          CI: "true"
        vars:
          ENV: "development"
    patches:
      - op: replace
        path: images.backend.image
        value: john/devbackend

在同一個 activation 中指定多個條件時,所有條件都必須匹配才能啟用 profile。

任一匹配(OR邏輯)

profiles:
  - name: production
    activation:
      - env:
          CI: "true"
      - vars:
          ENV: "development"
    patches:
      - op: replace
        path: images.backend.image
        value: john/devbackend

使用多個 activation 項目時,任何一個匹配就會啟用 profile。

注意看這裡的 activation 是陣列

🔧 2. Patches(精準修改)- 外科手術般的精準操作

概念比喻:就像醫生做外科手術 🏥,需要在特定位置進行精準的操作。

profiles:
  - name: production
    patches:
      - op: <operation>    # 操作類型
        path: <json_path>  # 目標路径
        value: <new_value> # 新值(某些操作不需要)

三種核心操作

🎯 add - 新增操作

# 基礎配置
deployments:
  backend:
    image: myapp:latest
    replicas: 1

# Profile 使用 add 操作
profiles:
  - name: production
    patches:
      - op: add
        path: deployments.backend.env
        value:
          - name: NODE_ENV
            value: production
      - op: add
        path: deployments.backend.resources
        value:
          limits:
            memory: "1Gi"
            cpu: "500m"

透過執行devspace print -p production來看結果結果:

version: v2beta1
name: demo
deployments:
    backend:
        helm:
            chart:
                name: ./chart
            values:
                env: # 新增
                    - name: NODE_ENV
                      value: production
                image:
                    repository: myapp
                    tag: latest
                replicaCount: 1
                resources: # 新增
                    limits:
                        cpu: 500m
                        memory: 1Gi

🔄 replace - 替換操作

# 基礎配置
version: v2beta1
name: demo

deployments:
  backend:
    helm:
      chart:
        name: ./chart
      values:
        image:
          repository: myapp
          tag: latest
        replicaCount: 1
        
profiles:
  - name: production
    patches:
      - op: replace
        path: deployments.backend.helm.values.image.tag
        value: prod
      - op: replace
        path: deployments.backend.helm.values.replicaCount
        value: 3

透過執行devspace print -p production來看結果結果:

version: v2beta1
name: demo
deployments:
    backend:
        helm:
            chart:
                name: ./chart
            values:
                image:
                    repository: myapp
                    tag: prod  # 被替換
                replicaCount: 3 # 被替換

remove - 刪除操作

version: v2beta1
name: demo

images:
  backend:
    image: john/devbackend
  backend-debugger:
    image: john/debugger
# 基礎配置
deployments:
  backend:
    helm:
      values:
        containers:
        - image: john/devbackend
        - image: john/debugger
# Profile 使用 remove 操作
profiles:
- name: production
  patches:
  - op: remove
    path: deployments.backend.helm.values.containers.image=john/debugger

結果

version: v2beta1
name: demo
images:
    backend:
        image: john/devbackend
    backend-debugger:
        image: john/debugger
deployments:
    backend:
        helm:
            values:
                containers:
                    - image: john/devbackend

🔄🔄 3. Merge(智慧合併)- 溫和的融合大師

概念比喻:就像調雞尾酒 🍹,將新的配料溫和地融入原有的基酒中,不會破壞原有的味道。

基本特性︰
📊 深度合併:自動處理嵌套結構
🔗 保留原值:只覆蓋指定的部分
🧠 智慧判斷:物件合併,陣列替換

🌟 物件合併

version: v2beta1
name: demo

# 基礎配置 - 使用 kubectl
deployments:
  backend:
    kubectl:
      manifests:
        - deployment.yaml
        - service.yaml

# Profile 使用 merge
profiles:
  - name: production
    merge:
      deployments:
        backend:
          kubectl:
            manifests:
              - deployment-prod.yaml  # 營運環境專用的 manifest
              - service.yaml

透過執行devspace print -p production來看結果結果:

version: v2beta1
name: demo
deployments:
    backend:
        kubectl:
            manifests:
                - deployment-prod.yaml
                - service.yaml

📋 陣列完整替換

version: v2beta1
name: demo

images:
  backend:
    image: john/devbackend
  backend-debugger:
    image: john/debugger
deployments:
  backend:
    helm:
      values:
        containers:
        - image: john/devbackend
        - image: john/debugger
profiles:
- name: production
  merge:
    images:
      # Change the backend image
      backend:
        image: john/prodbackend
      # Delete the backend-debugger image
      backend-debugger: null
    # Override deployments
    deployments:
      backend:
        helm:
           values:
             containers:
             - image: john/prodbackend

來看結果

version: v2beta1
name: demo
images:
    backend:
        image: john/prodbackend
deployments:
    backend:
        helm:
            values:
                containers:
                    - image: john/prodbackend


特點:

🧠 智慧合併:自動處理嵌套結構
🔗 保留原值:只覆蓋指定的部分
📝 語法簡潔:比 Patches 更直觀


### 🔄 4. Replace(完整替換)
完全替換整個配置區塊
```yaml
profiles:
  - name: development
    replace:
      deployments:
        backend:
          image: myapp:dev
          replicas: 1
          env:
            - name: NODE_ENV
              value: development

特點:

🎯 完全控制:替換整個配置區塊
🚀 簡單直接:適合差異較大的環境
⚠️ 注意:會完全覆蓋原有配置

🚀 4. Replace(完整替換)- 推倒重建的建築師

概念比喻:就像重新裝潢房間 🏠,把整個房間清空,然後按照新的設計重新佈置。
基本特性
🎯 完全控制:替換整個配置區塊
🚀 簡單直接:適合差異較大的環境

🏗️ 完整替換部署配置

version: v2beta1
name: demo

images:
  backend:
    image: john/devbackend
  backend-debugger:
    image: john/debugger
deployments:
  app-backend:
    helm:
      values:
        containers:
        - image: john/devbackend
        - image: john/debugger
profiles:
- name: production
  replace:
    images:
      backend:
        image: john/prodbackend

看結果

version: v2beta1
name: demo
images:
    backend:
        image: john/prodbackend
deployments:
    app-backend:
        helm:
            values:
                containers:
                    - image: john/devbackend
                    - image: john/debugger

組合!!

version: v2beta1

vars:
  ENV:
    default: development
  IMAGE_TAG:
    default: latest
  REPLICAS:
    default: "1"

# 基礎 image 配置
images:
  backend:
    image: mycompany/backend
    dockerfile: ./backend/Dockerfile
    tags:
      - ${IMAGE_TAG}
  frontend:
    image: mycompany/frontend  
    dockerfile: ./frontend/Dockerfile
    tags:
      - ${IMAGE_TAG}

# 基礎部署配置(開發環境)
deployments:
  backend:
    helm:
      chart:
        name: ./charts/backend
      values:
        image:
          repository: mycompany/backend
          tag: ${IMAGE_TAG}
        replicaCount: ${REPLICAS}
        service:
          type: ClusterIP
          port: 3000
        env:
          - name: NODE_ENV
            value: development
          - name: LOG_LEVEL
            value: debug
          - name: DB_HOST
            value: localhost
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"

  frontend:
    helm:
      chart:
        name: ./charts/frontend
      values:
        image:
          repository: mycompany/frontend
          tag: ${IMAGE_TAG}
        replicaCount: 1
        service:
          type: ClusterIP
          port: 80
        env:
          - name: API_URL
            value: http://backend:3000

# 開發工具配置
dev:
  backend:
    imageSelector: mycompany/backend:${IMAGE_TAG}
    ports:
      - port: "3000:3000"
    sync:
      - path: ./backend:/app
        excludePaths:
          - node_modules/
    terminal:
      command: ./devspace_start.sh


# 🎭 Profiles 配置

profiles:

  - name: production
    activation:
      - vars:
          ENV: production
      - env:
          CI: "true"
    
    replace:
      deployments:
        backend:
          helm:
            chart:
              name: ./charts/backend
            values:
              image:
                repository: mycompany/backend
                tag: prod-${IMAGE_TAG}
              replicaCount: 5  # 高可用性
              service:
                type: LoadBalancer
                port: 3000
              env:
                - name: NODE_ENV
                  value: production
                - name: LOG_LEVEL
                  value: error
                - name: DB_HOST
                  value: prod-db-cluster.example.com
                - name: REDIS_URL
                  value: redis://prod-redis-cluster:6379
              resources:
                requests:
                  memory: "1Gi"
                  cpu: "500m"
                limits:
                  memory: "2Gi"
                  cpu: "1000m"
        
        frontend:
          helm:
            chart:
              name: ./charts/frontend
            values:
              image:
                repository: mycompany/frontend
                tag: prod-${IMAGE_TAG}
              replicaCount: 3
              service:
                type: LoadBalancer
                port: 80
              env:
                - name: API_URL
                  value: https://api.example.com


    merge:
      deployments:
        backend:
          helm:
            values:
              labels:
                environment: production
                monitoring: enabled
                team: backend
              env:
                - name: NODE_ENV
                  value: production
                - name: LOG_LEVEL  
                  value: error
                - name: DB_HOST
                  value: prod-db-cluster.example.com
                - name: REDIS_URL
                  value: redis://prod-redis-cluster:6379
                - name: METRICS_ENABLED      # 新增
                  value: "true"
                - name: TRACING_ENABLED      # 新增
                  value: "true"
                - name: SECURITY_HEADERS     # 新增
                  value: "true"
              livenessProbe:
                httpGet:
                  path: /health
                  port: 3000
                initialDelaySeconds: 60
                periodSeconds: 30
              readinessProbe:
                httpGet:
                  path: /ready
                  port: 3000
                initialDelaySeconds: 10
                periodSeconds: 5
              securityContext:
                runAsNonRoot: true
                runAsUser: 1000
                readOnlyRootFilesystem: true
        
        frontend:
          helm:
            values:
              labels:
                environment: production
                monitoring: enabled
                team: frontend
              env:
                - name: API_URL
                  value: https://api.example.com
                - name: ANALYTICS_ENABLED    # 新增
                  value: "true"
                - name: CDN_URL              # 新增
                  value: https://cdn.example.com

    patches:
      - op: add
        path: deployments.backend.helm.values.annotations
        value:
          prometheus.io/scrape: "true"
          prometheus.io/port: "3000"
          prometheus.io/path: "/metrics"
      
      - op: add
        path: deployments.frontend.helm.values.annotations
        value:
          prometheus.io/scrape: "true"
          prometheus.io/port: "80"
          prometheus.io/path: "/metrics"
      
      - op: replace
        path: deployments.backend.helm.values.resources.limits.memory
        value: "3Gi"
      
      - op: add
        path: deployments.backend.helm.values.nodeSelector
        value:
          node-type: "compute-optimized"
      - op: add
        path: deployments.backend.helm.values.affinity
        value:
          podAntiAffinity:
            preferredDuringSchedulingIgnoredDuringExecution:
              - weight: 100
                podAffinityTerm:
                  labelSelector:
                    matchExpressions:
                      - key: app
                        operator: In
                        values:
                          - backend
                  topologyKey: kubernetes.io/hostname

執行 devspace print -p production --var ENV=production
透過 ENV 啟用 production profile

version: v2beta1
name: demo
images:
    backend:
        image: mycompany/backend
        tags:
            - latest
        dockerfile: ./backend/Dockerfile
    frontend:
        image: mycompany/frontend
        tags:
            - latest
        dockerfile: ./frontend/Dockerfile
deployments:
    backend:
        helm:
            chart:
                name: ./charts/backend
            values:
                affinity:
                    podAntiAffinity:
                        preferredDuringSchedulingIgnoredDuringExecution:
                            - podAffinityTerm:
                                labelSelector:
                                    matchExpressions:
                                        - key: app
                                          operator: In
                                          values:
                                            - backend
                                topologyKey: kubernetes.io/hostname
                              weight: 100
                annotations:
                    prometheus.io/path: /metrics
                    prometheus.io/port: "3000"
                    prometheus.io/scrape: "true"
                env:
                    - name: NODE_ENV
                      value: production
                    - name: LOG_LEVEL
                      value: error
                    - name: DB_HOST
                      value: prod-db-cluster.example.com
                    - name: REDIS_URL
                      value: redis://prod-redis-cluster:6379
                    - name: METRICS_ENABLED
                      value: "true"
                    - name: TRACING_ENABLED
                      value: "true"
                    - name: SECURITY_HEADERS
                      value: "true"
                image:
                    repository: mycompany/backend
                    tag: prod-latest
                labels:
                    environment: production
                    monitoring: enabled
                    team: backend
                livenessProbe:
                    httpGet:
                        path: /health
                        port: 3000
                    initialDelaySeconds: 60
                    periodSeconds: 30
                nodeSelector:
                    node-type: compute-optimized
                readinessProbe:
                    httpGet:
                        path: /ready
                        port: 3000
                    initialDelaySeconds: 10
                    periodSeconds: 5
                replicaCount: 5
                resources:
                    limits:
                        cpu: 1000m
                        memory: 3Gi
                    requests:
                        cpu: 500m
                        memory: 1Gi
                securityContext:
                    readOnlyRootFilesystem: true
                    runAsNonRoot: true
                    runAsUser: 1000
                service:
                    port: 3000
                    type: LoadBalancer
    frontend:
        helm:
            chart:
                name: ./charts/frontend
            values:
                annotations:
                    prometheus.io/path: /metrics
                    prometheus.io/port: "80"
                    prometheus.io/scrape: "true"
                env:
                    - name: API_URL
                      value: https://api.example.com
                    - name: ANALYTICS_ENABLED
                      value: "true"
                    - name: CDN_URL
                      value: https://cdn.example.com
                image:
                    repository: mycompany/frontend
                    tag: prod-latest
                labels:
                    environment: production
                    monitoring: enabled
                    team: frontend
                replicaCount: 3
                service:
                    port: 80
                    type: LoadBalancer
dev:
    backend:
        imageSelector: mycompany/backend:latest
        sync:
            - path: ./backend:/app
              excludePaths:
                - node_modules/
        terminal:
            command: ./devspace_start.sh
        ports:
            - port: 3000:3000

如果有多個 profile
能透過 devspace list profiles 簡易的了解有什麼 profile 能啟用。

總結

Profiles 真的是很香!以前切換環境需要手動修改,現在根據變數和環境自動啟用對應 Profile。
還能夠使用 devspace print 預覽最終配置,避免部署意外。最重要的是能夠從開發環境逐步驗證到營運環境。
https://ithelp.ithome.com.tw/upload/images/20250902/20104930bAFgp0BBXW.png


上一篇
DevSpace Dependencies:微服務編排的藝術 🎭
下一篇
VsCode 整合 DevSpace 進行 debug !
系列文
賢者大叔的容器修煉手札20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言