構造化データ(JSON/YAML)の相互変換による設定ファイル自動生成の堅牢化

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

構造化データ(JSON/YAML)の相互変換による設定ファイル自動生成の堅牢化

【導入と前提】

JSONソースからYAML形式の設定ファイルを安全に自動生成し、環境差分を吸収するSRE向けパイプラインの構築を自動化します。

  • OS: GNU/Linux (Ubuntu/RHEL/Debian)

  • 必須ツール:

    • jq: JSON処理(v1.6以上推奨)

    • yq: YAML処理(mikefarah/yq v4以上)

    • curl: リモートソース取得

【処理フローと設計】

graph TD
    A["Remote JSON Source"] -->|curl --retry| B("Local Temporary File")
    B -->|jq validation| C{"Syntax Check"}
    C -->|Invalid| D["Error Log & Alert"]
    C -->|Valid| E["yq Transformation"]
    E -->|Merge Env Vars| F["Final YAML Config"]
    F -->|Atomic Write| G["Service Restart/Reload"]
  1. ソース取得: リトライ処理を含む curl で外部APIまたはリポジトリからJSONを取得。

  2. 検証: jq を用いて、変換前にスキーマやシンタックスが正しいかを確認。

  3. 変換・マージ: yq でJSONをYAMLへ変換し、環境固有の変数(.env 等)と結合。

  4. 反映: アトミックなファイル更新を行い、整合性を担保。

【実装:堅牢な自動化スクリプト】

#!/usr/bin/env bash

# --- 設定とエラーハンドリング ---

set -euo pipefail

# -e: エラーが発生した時点でスクリプトを終了


# -u: 未定義の変数を使用しようとしたらエラー


# -o pipefail: パイプラインの途中のエラーを無視せず終了

# 一時ファイル管理

TMP_JSON=$(mktemp /tmp/config_XXXXXX.json)
TMP_YAML=$(mktemp /tmp/config_XXXXXX.yaml)
DEST_CONFIG="/etc/myapp/config.yaml"

# 終了時に必ず一時ファイルを削除

trap 'rm -f "$TMP_JSON" "$TMP_YAML"' EXIT

echo "[INFO] 設定取得を開始します..."

# 1. JSONソースの取得 (GitHub APIを例に)


# -s: 進捗非表示, -L: リダイレクト追従, --retry: 失敗時のリトライ回数

SOURCE_URL="https://api.example.com/v1/config"
if ! curl -sL --retry 3 --connect-timeout 5 "$SOURCE_URL" -o "$TMP_JSON"; then
    echo "[ERROR] ソースの取得に失敗しました。" >&2
    exit 1
fi

# 2. jqによる検証


# -e: 結果がnullまたはfalseの場合に終了ステータスを1にする

if ! jq -e . "$TMP_JSON" > /dev/null; then
    echo "[ERROR] 無効なJSONフォーマットです。" >&2
    exit 1
fi

# 3. yqによるJSONからYAMLへの変換と加工


# eval: 式を実行, -P: 読みやすく整形(Pretty print)

echo "[INFO] JSONをYAMLに変換中..."
yq eval -P "$TMP_JSON" > "$TMP_YAML"

# 4. 特定の環境変数をYAMLに注入 (例: DBホストの書き換え)


# env(DB_HOST) でシェル変数を参照

export DB_HOST=${DB_HOST:-"localhost"}
yq eval -i '.database.host = env(DB_HOST)' "$TMP_YAML"

# 5. アトミックなファイル更新 (上書きエラー防止)


# installコマンドは権限設定も同時に行えるため推奨

echo "[INFO] 設定ファイルを反映します: $DEST_CONFIG"
sudo install -m 644 -o root -g root "$TMP_YAML" "$DEST_CONFIG"

echo "[SUCCESS] 自動生成が完了しました。"

systemdによる定期実行の自動化

このスクリプトを30分ごとに実行するためのユニット定義例です。

/etc/systemd/system/config-sync.service

[Unit]
Description=Sync Remote Config to Local YAML
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/sync-config.sh
User=root

# 必要に応じて環境変数をロード

EnvironmentFile=/etc/default/myapp-sync

/etc/systemd/system/config-sync.timer

[Unit]
Description=Run config-sync every 30 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=30min

[Install]
WantedBy=timers.target

【検証と運用】

正常系の確認

設定ファイルが意図した構造になっているか、yq を使って再度確認します。

# 生成されたYAMLの内容をデバッグ表示

yq eval '.' /etc/myapp/config.yaml

# systemdタイマーの稼働状況確認

systemctl list-timers config-sync.timer

異常系の確認(ログ確認)

スクリプトが失敗した場合、journalctl でエラー箇所を特定します。

journalctl -u config-sync.service -n 50 --no-pager

【トラブルシューティングと落とし穴】

  1. 権限問題 (Sudo):

    • install コマンドで /etc 配下に書き込む際、非ルートユーザーで実行していると失敗します。systemd ユニットで User=root を指定するか、書き込み先ディレクトリの権限を適切に調整してください。
  2. 一時ファイルの肥大化:

    • trap 処理が漏れると /tmp にゴミが残ります。スクリプト冒頭での trap '...' EXIT は必須です。
  3. yq のバージョン差異:

    • Python製の yq (kislyuk/yq) と Go製の yq (mikefarah/yq) ではコマンド引数が全く異なります。本稿では主流の Go版(v4以降)を前提としています。

【まとめ】

運用の冪等性を維持するための3つのポイント:

  1. 検証の強制: 変換前に jq で構造チェックを行い、不正なデータで既存設定を上書きさせない。

  2. アトミック更新: cp ではなく installmv を用いて、ファイル書き込み中の不完全な状態をサービスに読み込ませない。

  3. 状態の外部化: 環境依存のパラメータはスクリプト内にハードコードせず、yqenv() 機能を活用して環境変数から注入する。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました