[CI/CD] GitHub Actions 多專案變數集中管理解決方案:AWS SSM Parameter Store

使用 AWS SSM Parameter Store 實現集中式設定管理,解決多專案 CI/CD 變數分散問題

 

 

 

前言

過去在前幾份工作經驗中,大型專案 / 大型公司在管理變數時,常會遇到幾個問題:

  1. 有 global 的變數需全公司共用
  2. 部門內也會有多個 Project 需共用變數
  3. 單個 Project 也有不同 environment 的變數

 

如果是用那種各 Project 各自設定 variables 的作法,不管是設在 project 內的 config,還是設在 GitHub/Gitlab 的 variables 中,同個變數都有可能在多個不同專案中被重複設定

如果這些專案都只有一個人管理或許沒事,但如果未來共同維護的 RD 越來越多,組別拆分越來越細,一旦要更新 value,有任何一個組別 / Project 沒通知到要改,一上線就會大爆炸了 🚬

 

過去我也曾經為了上述的問題花費不少維護時間,所以我決定開個 side project 進一步研究,陸續做一些嘗試後,最終收斂出一個可行的解決方案 → 使用雲端參數集中管理服務!

 

 

什麼是 AWS SSM Parameter Store

雲端參數集中管理服務有很多種,可以看你用哪一家的雲來做選擇,這裡我使用的是 AWS 的 SSM Parameter Store

SSM Parameter Store 是一個由 AWS 提供的「參數管理服務」,可以用來集中儲存和管理 Application 的 variables 和 secrets

 

使用它有以下幾個好處:

  1. 可集中管理變數,不會四散在各地
  2. 可供多 Project 共用
  3. 支援 AWS KMS 加密,安全性更高
  4. 可透過 IAM 限制參數存取的權限(哪個專案可以存取哪些變數)
  5. 有版本控制,每次變更都會有更改紀錄可以追蹤,要 rollback 也很方便
  6. 費用便宜,如果只是使用 Standard 參數的話甚至是免費的(Advanced 才要收費,採用 1 個參數每個月多少錢的計算方式)

 

接下來會簡單展示一下創建的方式,並沿途做一些知識補充與探討,以及最後也會提供 GitHub Action yaml 給大家使用

開始~~~~~!

 

 

Create Parameter Store (AWS Console)

有好幾種方式(CLI、Terraform、AWS Console)可以創建 Parameter Store

這裡我用 AWS Console 示範

 

搜尋 Systems Manager

我的參數 > 建立參數

 

名稱

可以用斜線做多層的結構,方便之後需限制權限時可以分層管控

( ex: /a-team/a-project/prod/xxurl )

 

方案

根據自身的使用情境選擇

 Standard ( 標準 )Advanced ( 高級 )
數量上限 (每個 AWS account / 每個 region)10,000100,000
單筆容量4 KB8 KB
可用 Parameter Policy
可與其他 AWS 帳戶分享
定價Free每個 Advanced 參數
每月 0.05 USD

費用可能會隨時間異動,最新金額以官方公告為主:集中式營運中心 – AWS Systems Manager 定價 – Amazon Web Services

類型

  • String
    儲存非敏感的純文字資訊,無加密,沒有 KMS 保護
  • StringList
    他會儲存像是這樣的資料結構 → value1,value2,value3
    需要多個值的時候使用
  • SecureString
    儲存敏感資訊,會使用 KMS 加密
    KMS 金鑰 ID 可以使用他預設的

 

KMS 金鑰來源

顧名思義,看你的 Key 是放在目前帳戶還是放在其他帳戶,根據自身情況選擇

 

KMS 金鑰 ID

指定「用哪一把 Key 來加解密資料」,不一定要更改它,可以用 AWS 預設的

更多 AWS KMS Key 詳情可參考:AWS KMS keys - AWS Key Management Service

 

就 Value,如果類型選擇 SecureString,KMS 會自動對這個內容進行加密

 

標籤

如果你有想要為變數分組方便日後管理或設置權限,可以添加標籤,結構是 key / value

 

填好後創建即可 👍

 

 

Parameter Store 介紹 (AWS Console)

新增完的變數可以在列表看到

 

點擊參數本身,可以進到詳情頁

如果是 SecureString,他的值 Default 會是隱藏的

如果你有權限查看,就可以在這裡打開來確認 Value 的內容

 

歷程紀錄中則可以看到每次變更的 Value、更改時間、更改的 User 是誰

值得注意的是,歷程記錄中的標籤欄位,並不等於參數的標籤喔!

他是 版本的別名(alias)

每次異動變數時,AWS 都會新增一個版本,其中版本號是流水號,只會一直累加,如果需要用一個有意義的名稱來指向特定版本,並藉此用來快速切換 & rollback,就可以使用標籤功能

(ex: 程式碼那邊讀取變數時是吃 current,目前線上使用的是最新的版本 2,他被添加了一個標籤「current」,如果我要退版,我只要移除版本 2 的標籤,在版本 1 添加標籤「current」即可,程式碼就不用動)

更多詳情可參考官方:Working with parameter labels in Parameter Store - AWS Systems Manager

 

如果你有新增參數的標籤,就可以在標籤分頁看到

 

 

Parameter Store / Secrets Manager 抉擇

AWS 除了 Parameter Store 可以用來儲存參數以外,還有另一個選擇:Secrets Manager

所以這裡我想比較一下這 2 個服務的差異

 

Secrets Manager 是 AWS「專門」提供用來存放敏感資訊的服務

更多詳情可參考官方文件:什麼是 AWS Secrets Manager? - AWS Secrets Manager

 

看到這裡很多人會產生一個疑惑❓

一般非敏感資訊存 Parameter store 沒問題,但敏感資訊應該存哪裏比較正確?

(1) Parameter store 的 SecureString

(2) Secrets Manager

 

過去在技術論壇中常會看到有人在討論這個議題,其中蠻多人會選擇 (2),原因在於跟 Parameter Store 相比,Secrets Manager 還多了跨帳號共享Auto Rotation功能,並且它的容量更大,能存放更多變數,以功能定位來說用來存放敏感資訊也極為合理

but …. Secrets Manager 最大的缺點就是他比較貴 🤢

 

然而~隨著時代的變化,Parameter Store 缺乏跨帳號共享Auto Rotation的問題也已經不是問題了!

 

Parameter Store 從 2024 年 2 月起開始允許分享(Advanced)參數

 

接著,這裡簡單討論一下Auto Rotation的問題

Secrets Manager 它底層是透過呼叫 Lambda function 來做 Auto Rotation

只要有支援 rotation 的 AWS 服務(ex:RDS),AWS 就會自動幫我們建立對應的 Lambda function,不用自己撰寫 Lambda 邏輯

 

而 Parameter Store 因為預設沒有 Auto Rotation 功能

所以如果要做到 Auto Rotation,就要另外自行開發 Lambda Function,用 EventBridge 去 trigger 定期更改 RDS 的密碼,以及更新 Parameter Store 的值

 

這裡再放個表格整理一下他們的差異細節

 Secrets ManagerParameter Store (Advanced)
定位專門管理「機密資料」通用的資料存放服務,可存放一般資料、機密資料
加密預設強制 KMS 加密SecureString 才可用 KMS 加密
Rotation ( 定期更換機密資料 )❌ 
(但可自己實作)
跨帳號共享
版本管理✅ 內建「版本狀態機制」,AWS 幫你管理 current / previous / pending✅ 只有「版本流水號」,狀態(current / previous / pending)要自己用 label 管
單筆容量64 KB8 KB
數量上限 (每個 AWS account / 每個 region)500,000 個 secrets100,000 個 Advanced
定價每個 Secrets 參數 每月 0.40 USD 每 10,000 次 API 呼叫 0.05 USD每個 Advanced 參數 每月 0.05 USD

溫馨提醒:以上關於容量、費用等數值會隨時間異動,此表僅供參考,最新資訊請以官方公告為主

從上面的表格中可以看到

他們主要是差異在容量、數量大小限制

大部分的功能沒差多少

即使 Parameter Store 沒有內建 Auto Rotation 功能,也依然可以自己架出差不多的自動化架構

若你使用量沒有那麼大,使用 Parameter Store (Advanced) 是相對比較經濟實惠的選擇

那就看你們公司的現有條件適合哪一種囉~

 

 

在 GitHub Actions 存取 Parameter Store

要讓 GitHub Actions 能存取 AWS 服務的話,需要先做身分驗證以取得存取的權限(Create Access Key 或使用 OIDC),這裡主要著重在變數管理,我就不補充存取權限的部分了,不知道的夥伴們再自行爬文,以下假設你已經做好權限的處理~

 

這裡我把它包成一個 GitHub Actions composite action,方便重複使用

 

.github/actions/get-ssm-parameters/action.yml


name: "Get SSM Parameters"
description: "Retrieves parameters from AWS SSM Parameter Store and sets them as environment variables"

inputs:
  ssm_param_path:
    description: "Path to the SSM Parameter Store"
    required: true

outputs:
  env_vars:
    description: "Formatted environment variables string"
    value: ${{ steps.get-ssm.outputs.env_vars }}

runs:
  using: "composite"
  steps:
    - id: get-ssm
      shell: bash
      run: |
        # Retrieve parameters from SSM Parameter Store
        params=$(aws ssm get-parameters-by-path --path "${{ inputs.ssm_param_path }}" --recursive --with-decryption --query "Parameters[*].{Name:Name,Value:Value}" --output text)
        
        # Build the environment variables string
        env_vars=""
        param_count=0
        while read -r name value; do
          if [[ -n "$name" && -n "$value" ]]; then
            var_name=$(echo "$name" | awk -F'/' '{print $NF}') # Extract the last part of the parameter name
            # Mask sensitive values in logs
            echo "::add-mask::$value"
            echo "Found parameter: $var_name"
            if [[ -z "$env_vars" ]]; then
              env_vars="$var_name=$value"
            else
              env_vars="$env_vars,$var_name=$value"
            fi
            param_count=$((param_count + 1))
          fi
        done <<< "$params"
        
        # Output environment variables for use in subsequent steps
        echo "env_vars=$env_vars" >> $GITHUB_OUTPUT
        
        # Debug output
        if [[ -n "$env_vars" ]]; then
          echo "✓ Successfully retrieved $param_count SSM parameters"
        else
          echo "⚠ No SSM parameters found"
        fi
  

 

🟠 input

這個 composite action 接受一個 input 參數 ssm_param_path

這裡要輸入你 Parameter Store 創建的路徑

(ex:/budget-sentinel/prod)

 

🟠 variables 存取方式

yaml 中會去逐一取出這個路徑底下的所有 Parameter Store,並取路徑的最後一段作為變數名稱

(ex:/budget-sentinel/prod/TEST_URL 只取 TEST_URL)

 

使用 GitHub Actions 的 ::add-mask:: workflow command,避免敏感資訊列印在 GitHub Actions Log,加了這個設定後他在 Log 就會被打碼,變成 *******,但不會影響他真正的 value

更多 workflow command 說明可參考:GitHub Actions 的工作流命令 - GitHub 文档

 

🟠 output

接著他會將所有參數以逗號串聯,輸出成 $GITHUB_OUTPUT

(ex:"TEST_URL": "***","TEST_URL_2": "***")

 

使用端 yml


name: Deploy to XXX

env:
  SSM_PARAM_PATH: /budget-sentinel/prod
  
jobs:
  deploy:
    ....
      
    steps:
    ....
      - name: Get SSM Parameters
        id: get-ssm
        uses: ./.github/actions/get-ssm-parameters
        with:
          ssm_param_path: ${{ env.SSM_PARAM_PATH }}

      - name: Deploy
        if: steps.get-ssm.outputs.env_vars != ''
        run: 
          # can use ${{ steps.get-ssm.outputs.env_vars }}
    ....
  

使用端就可以新增一個 step(Get SSM Parameters),呼叫 composite action 以取得逗號分隔的變數們

為此 step 設置 id,讓其他 step 可以用 steps.<id>.outputs.<output名稱> 存取它的輸出值

其他 step 就取 ${{ steps.get-ssm.outputs.env_vars }} 自行加工使用

 

 

這個 yaml 以取某一個路徑底下的所有變數為範例,大家可以根據需求自行微調

因為所有變數都統一集中在 AWS SSM Parameter Store

所以只要有存取權限的 Project 都可以使用那些變數

未來要改某個變數值,只要改一個地方,所有有用到它的 Project 都可以吃到最新的,提高可維護性~

 

 

END