【脱コンソールポチポチ】多環境AWS Parameter StoreをYAML×GitHub Actionsで一元管理する仕組みを作った

この記事は KINTOテクノロジーズアドベントカレンダー2025 の10日目の記事です🎅🎄
目次
はじめに
この記事の要約
作ったもの
特徴
前提条件
運用上の工夫
アーキテクチャ
実装のポイント
使い方
導入効果
まとめ
はじめに
こんにちは。KINTOテクノロジーズの共通サービス開発グループのエンジニア、宮下です。
AWSなどを使う現在の開発環境は、簡単に増やしたり減らしたりできる反面、環境の数が増えていきがちです。
我々の開発環境も、dev、dev2、stg、stg2...stg5、ins、prodのように、とても多くなってしまっています。
そのため、AWS Systems Manager Parameter Storeで管理する値も、環境数 × パラメータ数の組み合わせでどんどん増えていき、ケアレスミスが多発していました。
例えば以下のようなミスです。
- devのParameter Storeは最新化したけれど、stgのパラメータを更新し忘れていた
- KEY, VALUEは正しいけれど、タグを入れ忘れていた
- デプロイに失敗するので調査したら、新規追加のパラメータを登録し忘れていたのが原因だった
- devからstgへ手作業でコピペする際、環境ごとに変えるべき値をそのままコピペしてしまった
また、現在のParameter Storeの値がどうなっているかも分かりづらく、ブラウザでいちいち各環境に入って確認するのが面倒で、つい後回しになりがちでした。その結果、よりケアレスミスが起きる悪循環に陥っていました。
そこで、「ローカルのYAMLファイルに各環境のパラメータを集約し、そのファイルとAWSを同期する」という方針で自動化の仕組みを作りました。今回はそのアイデアを紹介します。
この記事の要約
- 10環境以上 × 50以上のパラメータ のAWS Parameter Store管理が煩雑すぎたので自動化した
- YAMLで全パラメータを可視化 + GitHub Actionsでワンクリック同期
- 更新漏れやケアレスミスをゼロにし、面倒くさい機械的なコピペ作業を無くした
作ったもの
以下の3つを組み合わせてこの仕組みを構築しました:
- YAMLファイル - 全環境のParameter Storeの値を一元管理
- Pythonスクリプト - YAMLとAWS間の同期処理
- GitHub Actions - ワンクリックで全環境に反映
特徴
1. YAML可視化
従来は各環境のParameter Storeの値を確認するために、AWSコンソールに各環境ごとにアカウントを切り替えて何度もログインする必要がありました。
今は、リポジトリにある1つのYAMLファイルを開けば、全環境の全パラメータが一覧できます。
parameters:
# 環境ごとに値が異なるパラメータ
- key: api/endpoint
description: "APIエンドポイント"
environment_values:
dev: "https://dev-api.example.com"
stg: "https://stg-api.example.com"
prod: "https://prod-api.example.com"
# 全環境で共通の値
- key: app/timeout
description: "タイムアウト設定(秒)"
default_value: "30"
# 機密情報(SecureString)
# 値はGitHub Secretsから環境変数経由で取得(例: SSM__DB__PASSWORD)
- key: db/password
description: "データベースパスワード"
type: "SecureString"
メリット:
- どの環境でどの値を使っているか一目瞭然
- Git管理できるので変更履歴も追える
- PRレビューで値の間違いを事前に防げる
2. ワンクリック同期
GitHub Actionsのワークフローを手動実行するだけで、全環境のParameter Storeが自動的にYAMLの内容と同期されます。
マトリクス・ストラテジーにより、複数環境(dev, stg, prod)が並列で実行されるため、環境が増えても実行時間はほぼ変わりません。
AWSコンソールで環境を切り替えながらポチポチする必要がなくなりました。
前提条件
この仕組みは以下の前提で動作します。
- AWS CLIがGitHub Actionsランナー上で利用できること
- Parameter Storeへの
ssm:GetParametersByPath,ssm:PutParameter,ssm:AddTagsToResource権限があること - GitHub ActionsからAWSにアクセスできること(Access Keyもしくは OIDCなど)
- Python 3.11 +
pyyamlが利用できること
運用上の工夫
自動化で便利になる一方、誤操作のリスクも考慮が必要です。我々のチームでは以下の工夫をしています。
SecureStringの操作権限を絞る
GitHub Secretsを編集できる人を限定し、機密情報を扱える人を最小限にしています。
本番環境への反映はワークフローを分けて承認制に
本番環境のParameter Store更新時は、本番環境専用のワークフローを使い、ワークフローの中でSlackで承認ステップを挟む運用にしています。承認依頼時には更新対象のパラメータ一覧がSlackに表示されるため、「何が変わるのか」を確認してから承認できます。これにより、うっかり本番を更新してしまう事故を防いでいます。
アーキテクチャ
実装のポイント
ディレクトリ構成
$ tree .github
.github
├── aws-params.yml
├── scripts
│ ├── aws_param_common.py
│ └── update_aws_params.py
└── workflows
└── sync-parameters.yml
YAML設計
全環境のパラメータを .github/aws-params.yml に集約しています(YAMLの例は「特徴」セクションを参照)。
SecureStringの扱い
DBのパスワードなどの機密情報をYAMLにベタ書きするのはセキュリティ上NGです。
そこで、「YAMLにはキーの定義のみ」「実体(値)はGitHub Secrets」 という役割分担を行いました。
Pythonスクリプト側で、YAMLの定義を見て type: SecureString ならば、対応する環境変数を読みに行く設計にしています。
命名規則:
YAMLのkey: db/password
→ 環境変数名: SSM__DB__PASSWORD
環境ごとにSecureStringの値を分ける
DBパスワードなどは環境ごとに異なる値を使うことが多いです。GitHub Actionsの
Environments 機能を使えば、環境ごとに異なるSecretsを設定できます。
設定手順:
- GitHubリポジトリの Settings → Environments で環境を作成(
dev,stg,prod) - 各環境のSecretsに
SSM__DB__PASSWORDなどを登録(値は環境ごとに異なる) - ワークフローで
environment: ${{ matrix.env }}を指定
これにより、DEV環境ではDEV用のDBパスワード、STG環境ではSTG用のDBパスワードが自動的に使われます。


補足:Parameter Store vs Secrets Manager
「機密情報ならSecrets Managerでは?」と思う方もいるかもしれません。使い分けの目安は以下の通りです:
| Parameter Store (SecureString) | Secrets Manager | |
|---|---|---|
| 料金 | 標準パラメータは無料 | $0.40/シークレット/月 |
| ローテーション | 手動 | 自動ローテーション可能 |
| 向いているケース | APIキーなど更新頻度が低いもの | DBパスワードの自動ローテーションが必要な場合 |
多くのケースではParameter Store(SecureString)で十分で、Secrets Managerは「RDSパスワードの自動ローテーション」が必要な場合に検討してください。
Pythonスクリプト構成
aws_param_common.py - 共通機能
#!/usr/bin/env python3
"""AWS Parameter Store 共通処理"""
import os
import sys
import json
import subprocess
from typing import Dict, Any, Tuple
import yaml
def get_env_name() -> str:
"""環境名を取得"""
env = os.environ.get("ENV_NAME")
if not env:
print("エラー: ENV_NAME 環境変数が設定されていません")
sys.exit(1)
return env
def get_prefix(env: str) -> str:
"""環境に応じたプレフィックスを返す"""
return f"/{env}/app/config/"
def load_yaml_config() -> Tuple[Dict[str, Any], set]:
"""YAMLファイルを読み込む"""
yaml_path = os.path.join(os.path.dirname(__file__), "..", "aws-params.yml")
with open(yaml_path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
yaml_keys = {param["key"] for param in config.get("parameters", [])}
return config, yaml_keys
def get_existing_params(env: str) -> Dict[str, Dict[str, Any]]:
"""AWS SSMから既存のパラメータを取得(ページネーション対応)"""
prefix = get_prefix(env)
existing_params = {}
next_token = None
while True:
cmd = [
"aws", "ssm", "get-parameters-by-path",
"--path", prefix,
"--recursive",
"--with-decryption",
"--output", "json"
]
if next_token:
cmd.extend(["--next-token", next_token])
try:
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
data = json.loads(result.stdout)
params_data = data.get("Parameters", [])
except subprocess.CalledProcessError as e:
print(f"警告: パラメータの取得に失敗しました: {e.stderr}")
return {}
for param in params_data:
key = param["Name"].replace(prefix, "")
existing_params[key] = {
"value": param["Value"],
"type": param["Type"],
"version": param.get("Version", 1)
}
next_token = data.get("NextToken")
if not next_token:
break
return existing_params
def get_param_value(param: Dict[str, Any], env: str) -> str | None:
"""パラメータの値を取得(SecureStringは環境変数、それ以外はYAMLの値を使用)"""
# SecureStringの場合は環境変数から取得
if param.get("type") == "SecureString":
env_var_name = "SSM__" + param["key"].upper().replace("/", "__")
value = os.environ.get(env_var_name)
if not value:
print(f"警告: SecureString {param['key']} の環境変数 {env_var_name} が未設定")
return None
return value
# 環境固有の値
env_values = param.get("environment_values", {})
if env in env_values:
return str(env_values[env])
# デフォルト値
if "default_value" in param:
return str(param["default_value"])
return None
def validate_param(param: Dict[str, Any], env: str) -> Tuple[bool, str, Dict[str, Any] | None]:
"""パラメータのバリデーション"""
key = param.get("key")
if not key:
return False, "keyが定義されていません", None
value = get_param_value(param, env)
if value is None:
return False, f"{key}: 環境 {env} の値が定義されていません", None
param_info = {
"key": key,
"value": value,
"type": param.get("type", "String"),
"description": param.get("description", "")
}
return True, "", param_info
def update_parameter(param_info: Dict[str, Any], env: str) -> bool:
"""パラメータを更新"""
prefix = get_prefix(env)
full_name = prefix + param_info["key"]
cmd = [
"aws", "ssm", "put-parameter",
"--name", full_name,
"--value", param_info["value"],
"--type", param_info["type"],
"--overwrite"
]
if param_info.get("description"):
cmd.extend(["--description", param_info["description"]])
try:
subprocess.run(cmd, check=True, capture_output=True, text=True)
add_tags(full_name, env) # タグを追加
return True
except subprocess.CalledProcessError as e:
print(f"エラー: {param_info['key']} の更新に失敗: {e.stderr}")
return False
def add_tags(parameter_name: str, env: str) -> bool:
"""パラメータにタグを追加"""
cmd = [
"aws", "ssm", "add-tags-to-resource",
"--resource-type", "Parameter",
"--resource-id", parameter_name,
"--tags",
f"Key=Environment,Value={env}",
"Key=SID,Value=backend-api"
]
try:
subprocess.run(cmd, check=True, capture_output=True, text=True)
return True
except subprocess.CalledProcessError as e:
print(f"警告: タグの追加に失敗: {e.stderr}")
return False
ポイント:
get_existing_params: ページネーション対応で50件以上のパラメータも取得可能get_param_value: SecureStringは環境変数から、通常パラメータはYAMLから値を取得update_parameter: パラメータ更新後にadd_tagsを呼び出してタグを付与
タグについて
パラメータ作成時に、自動でタグを付与します。タグはAWSコンソールでの検索やコスト管理に便利なだけでなく、システムによってはタグがないとパラメータを読み込めない場合もあります。
| タグ | 値 | 説明 |
|---|---|---|
Environment |
dev, stg, prod |
実行時の環境名が自動で入る |
SID |
backend-api |
サービス識別子(自分のサービス名に置き換えて使用) |
update_aws_params.py - 更新スクリプト
#!/usr/bin/env python3
"""AWS Parameter Store 更新スクリプト"""
import sys
import aws_param_common as common
def update_parameters():
"""パラメータを更新し、結果をレポートする"""
env = common.get_env_name()
print(f"=== 環境: {env} ===")
print(f"プレフィックス: {common.get_prefix(env)}")
print()
config, yaml_keys = common.load_yaml_config()
existing_params = common.get_existing_params(env)
print(f"既存パラメータ数: {len(existing_params)}")
print()
updated_params = []
skipped_params = []
failed_params = []
for param in config.get("parameters", []):
is_valid, error_msg, param_info = common.validate_param(param, env)
if not is_valid:
print(f"[スキップ] {error_msg}")
continue
param_key = param_info["key"]
value = param_info["value"]
# 既存の値と比較
if param_key in existing_params:
if existing_params[param_key]["value"] == value:
print(f"[スキップ] {param_key}: 値に変更なし")
skipped_params.append(param_key)
continue
print(f"[更新] {param_key}: 値を更新します")
else:
print(f"[新規] {param_key}: 新規パラメータを追加します")
# パラメータを更新
success = common.update_parameter(param_info, env)
if success:
updated_params.append(param_key)
print(f" ✓ 完了")
else:
failed_params.append(param_key)
print(f" ✗ 失敗")
# 結果サマリー
print()
print("=== 結果サマリー ===")
print(f"更新: {len(updated_params)} 件")
print(f"スキップ(変更なし): {len(skipped_params)} 件")
print(f"失敗: {len(failed_params)} 件")
if failed_params:
print()
print("失敗したパラメータ:")
for key in failed_params:
print(f" - {key}")
sys.exit(1)
print()
print("✓ 正常終了")
if __name__ == "__main__":
update_parameters()
ポイント:
- 値が変わっていないパラメータはスキップ(無駄な更新を防ぐ)
- 更新結果を統計情報として出力
- 失敗時は終了コード1で終了
GitHub Actionsワークフロー
name: Sync AWS Parameter Store
on:
workflow_dispatch: # 手動実行
jobs:
sync-parameters:
runs-on: ubuntu-latest
strategy:
matrix:
env: [dev, stg, prod]
environment: ${{ matrix.env }} # 環境ごとのSecretsを使用
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install pyyaml
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Sync Parameters
env:
ENV_NAME: ${{ matrix.env }}
# SecureString用(環境ごとのGitHub Secretsから取得)
SSM__DB__PASSWORD: ${{ secrets.SSM__DB__PASSWORD }}
SSM__API__SECRET_KEY: ${{ secrets.SSM__API__SECRET_KEY }}
run: |
cd .github/scripts
python update_aws_params.py
ポイント:
strategy.matrixで複数環境を並列実行environment: ${{ matrix.env }}で環境ごとのSecretsを使用(devとprodで異なるDBパスワードなど)- SecureStringの値は環境変数経由でスクリプトに渡す
- 値に変更がないパラメータは自動的にスキップされる
使い方
1. パラメータの追加・変更
.github/aws-params.yml を編集してPRを出すだけです。
parameters:
# 新しいパラメータを追加
- key: feature/enable_payment_v2
description: "新決済システムの有効化"
environment_values:
dev: "true"
stg: "false"
prod: "false"
2. 全環境への反映
- GitHub Actionsページを開く
Sync AWS Parameter Storeを選択Run workflowボタンをクリック- 全環境に並列で反映される
3. SecureStringパラメータの追加
- YAMLに定義を追加:
- key: payment/api_key
description: "決済APIキー"
type: "SecureString"
- GitHub Environmentsに値を登録:
Settings → Environments → 各環境(dev, stg, prod)のSecretsに登録
Secret名: SSM__PAYMENT__API_KEY
値: (環境ごとに異なる実際の値)
- ワークフローファイルの環境変数セクションに追加:
env:
SSM__PAYMENT__API_KEY: ${{ secrets.SSM__PAYMENT__API_KEY }}
4. 実演
-
GitHub Actions画面から今回作ったアクションを選んで、起動します。

-
アクションが正常終了したことを確認します。各環境が並列で動作した事がわかります。

-
AWSのパラメータストア画面を開いて確認してみます。パラメータが登録されています。成功です。

導入効果
具体的な効果
- 作業時間: 環境数 × 5分 → ワンクリック(10環境なら50分削減)
- 更新漏れ: 月数回発生 → ゼロ
- 確認作業: AWSコンソールを開く → YAMLを見るだけ
まとめ
クラウド時代の「環境が増えすぎ問題」は、多くの現場で直面している課題だと思います。
今回紹介したアイデアのポイントは:
- YAML可視化 - 全環境のパラメータを1ファイルで管理
- ワンクリック同期 - GitHub Actionsで自動反映
- SecureString対応 - 機密情報も安全に管理
特別な技術は使っておらず、GitHub Actions + Python + AWS CLI だけで実現できます。
Parameter Storeの管理で困っている方、環境が増えて運用が大変になっている方の参考になれば幸いです。
最後までお読みいただき、ありがとうございました🙇♂️
関連記事 | Related Posts

【脱コンソールポチポチ】多環境AWS Parameter StoreをYAML×GitHub Actionsで一元管理する仕組みを作った

Achieving Auto Provisioning in ECS Environments

Getting Started with Minimal CI/CD: Streamlining EOL and SBOM Management

Reduce Leftover PRs! GitHub Actions Workflow Recommendations
How We Reduced AWS Costs by 65%—and What We Discovered Beyond That

Efforts to Improve Deploy Traceability to Multiple Environments Utilizing GitHub and JIRA
We are hiring!
【クラウドエンジニア】Cloud Infrastructure G/東京・大阪・福岡
KINTO Tech BlogWantedlyストーリーCloud InfrastructureグループについてAWSを主としたクラウドインフラの設計、構築、運用を主に担当しています。
【クラウドプラットフォームエンジニア】プラットフォームG/東京・大阪・福岡
プラットフォームグループについてAWS を中心とするインフラ上で稼働するアプリケーション運用改善のサポートを担当しています。
