kubectl dry-runとKustomizeで安全なKubernetes適用を実現する

Tech

kubectl dry-runとKustomizeで安全なKubernetes適用を実現する

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

1. 要件と前提

Kubernetes環境へのマニフェスト適用は、意図しない変更やダウンタイムを引き起こすリスクがあります。本記事では、DevOpsエンジニアとして、kubectl dry-runとKustomizeを組み合わせ、安全かつ冪等にKubernetesリソースを適用する手法を解説します。

1.1. 要件

  • kubectl dry-run を利用した変更の事前検証。

  • Kustomize を用いた環境ごとのリソースカスタマイズ。

  • シェルスクリプトによる自動化と安全性の確保(set -euo pipefailtrapmktemp -d)。

  • jq を用いたJSON出力の検証。

  • curl を用いたAPI連携におけるTLS、再試行、バックオフの考慮。

  • systemd unit/timer による定期的な適用スクリプトの実行とログ管理。

  • root権限の扱いと権限分離に関する注意喚起。

1.2. 前提

  • Kubernetesクラスタへのアクセス権限とkubectlコマンドが設定済みであること。

  • Kustomize (Kubernetes v1.14以降のkubectlに内蔵) が利用可能であること。

  • bashjqcurl コマンドが利用可能であること。

  • systemd が動作するLinux環境であること。

2. 実装

2.1. 全体フロー

安全な適用プロセスは、Kustomizeによる構成生成、dry-runによる検証、そして実際の適用という段階を踏みます。

graph TD
    A["Kustomization定義"] --> B{"kustomize build"};
    B --> C["生成されたマニフェスト"];
    C --> D{"kubectl dry-run --client | クライアント側検証"};
    D --> E{"kubectl dry-run --server | サーバー側検証"};
    E --> F["jqによる出力検証"];
    F -- 異常なし --> G{"kubectl apply | 最終適用"};
    G --> H["適用完了"];
    D -- 検証失敗 --> I["エラー通知/中断"];
    E -- 検証失敗 --> I;
    F -- 検証失敗 --> I;

2.2. Kustomizeベースの構成管理

Kustomizeは、ベースとなるKubernetesマニフェストにパッチやオーバーレイを適用し、環境に応じたリソースを生成するためのツールです。Kustomizeは2024年6月10日にv5.3.0がリリースされており、活発に開発されています[4]。

例えば、base/deployment.yamloverlays/production/kustomization.yamlがあるとします。

# base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  template:
    spec:
      containers:

      - name: my-app
        image: nginx:latest
        ports:

        - containerPort: 80
---

# overlays/production/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:

- ../../base
patchesStrategicMerge:

- patch-replicas.yaml

# overlays/production/patch-replicas.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3 # 本番環境では3レプリカ

kustomize build overlays/production コマンドで本番環境向けのマニフェストが生成されます。

2.3. 安全なシェルスクリプトの基礎

適用スクリプトは、冪等性と安全性を確保するために以下のプラクティスに従います。

  • set -euo pipefail:

    • e: コマンドが失敗した場合、即座にスクリプトを終了します。

    • u: 未定義の変数を参照した場合、エラーとして終了します。

    • o pipefail: パイプライン内のいずれかのコマンドが失敗した場合、パイプライン全体が失敗と判断されます。

  • trap cleanup EXIT: スクリプトの終了時に必ずcleanup関数が実行されるように設定し、一時ファイルの削除などを行います。

  • mktemp -d: 一時ディレクトリを作成し、作業ファイルを安全に隔離します。

#!/bin/bash

set -euo pipefail

# 一時ディレクトリの作成と終了時のクリーンアップ


# mktemp -dは競合を避け、一意なディレクトリを作成する

TMP_DIR=$(mktemp -d)
echo "一時ディレクトリ: $TMP_DIR"

cleanup() {
  echo "一時ディレクトリを削除します: $TMP_DIR"
  rm -rf "$TMP_DIR"
  echo "スクリプトを終了します。"
}
trap cleanup EXIT # スクリプト終了時にcleanup関数を呼び出す

KUSTOMIZE_OVERLAY="overlays/production"
MANIFEST_FILE="$TMP_DIR/manifest.yaml"

echo "Kustomizeビルドを開始します..."

# Kustomizeビルドの結果を一時ファイルに格納

kubectl kustomize "$KUSTOMIZE_OVERLAY" > "$MANIFEST_FILE"
echo "マニフェストを $MANIFEST_FILE に出力しました。"

# ここに dry-run と jq による検証ステップを追加


# ...

# 最終的な適用 (検証後)


# echo "Kubernetesリソースを適用します..."


# kubectl apply -f "$MANIFEST_FILE"

2.4. kubectl dry-run を用いた検証

kubectl dry-runは、実際にリソースを作成・更新せずに、変更がどのように適用されるかをシミュレーションします。

  • --client: kubectlクライアント側でのバリデーションのみを実行します。

  • --server: Kubernetes APIサーバーにリクエストを送信し、Admission Controllerなどのサーバー側ロジックを通して検証しますが、リソースは永続化されません。より正確な検証が可能です。

# dry-run検証の例(上記のシェルスクリプトに追加)

echo "kubectl dry-run --client でクライアント側検証を実行します..."

# クライアント側dry-runが失敗した場合、エラーメッセージを出力して終了

if ! kubectl apply -f "$MANIFEST_FILE" --dry-run=client --output=yaml > /dev/null; then
  echo "エラー: クライアント側dry-run検証に失敗しました。" >&2
  exit 1
fi
echo "クライアント側dry-run検証が成功しました。"

echo "kubectl dry-run --server でサーバー側検証を実行します..."

# サーバー側dry-runの結果をDRY_RUN_OUTPUT変数に格納

DRY_RUN_OUTPUT=$(kubectl apply -f "$MANIFEST_FILE" --dry-run=server --output=json 2>&1)
DRY_RUN_STATUS=$? # コマンドの終了ステータスを取得

if [ $DRY_RUN_STATUS -ne 0 ]; then
  echo "エラー: サーバー側dry-run検証に失敗しました。" >&2
  echo "$DRY_RUN_OUTPUT" >&2
  exit 1
fi
echo "サーバー側dry-run検証が成功しました。"

# DRY_RUN_OUTPUT を jq でさらに検証

kubectl apply--dry-run=serverオプションは、Kubernetes v1.29のドキュメント(2024年1月15日更新)にも記載されており、推奨される検証方法です[1]。

2.5. jq による出力検証

kubectl dry-runやその他のAPIコマンドのJSON出力をjqで解析し、期待される値が含まれているか、エラーがないかなどを確認します。jq2024年4月2日jq-1.7.1がリリースされており、最新の環境でも利用可能です[2]。

# jqによる出力検証の例(上記のシェルスクリプトに追加)

# DRY_RUN_OUTPUT にサーバー側dry-runのJSON結果が入っていると仮定

echo "jq を用いてdry-run出力を検証します..."

# 例: 出力されたリソースの名前が期待通りか確認


# Deploymentリソースで、metadata.nameが"my-app"であるものを選択し、その.metadata.nameを取得

RESOURCE_NAME=$(echo "$DRY_RUN_OUTPUT" | jq -r 'select(.kind == "Deployment" and .metadata.name == "my-app") | .metadata.name')
if [ "$RESOURCE_NAME" != "my-app" ]; then
  echo "エラー: 予期しないリソース名が検出されました: $RESOURCE_NAME" >&2
  exit 1
fi

# 例: replicasが3になっているか確認 (パッチが適用されているか)


# Deploymentリソースで、metadata.nameが"my-app"であるものを選択し、その.spec.replicasを取得

REPLICAS_COUNT=$(echo "$DRY_RUN_OUTPUT" | jq -r 'select(.kind == "Deployment" and .metadata.name == "my-app") | .spec.replicas')
if [ "$REPLICAS_COUNT" != "3" ]; then
  echo "エラー: replicas数が期待値(3)と異なります: $REPLICAS_COUNT" >&2
  exit 1
fi

echo "jqによる出力検証が成功しました。"

# すべての検証が成功したら実際の適用

echo "すべての検証が成功しました。Kubernetesリソースを適用します..."
kubectl apply -f "$MANIFEST_FILE"
echo "Kubernetesリソースの適用が完了しました。"

2.6. curl を用いたAPI連携(TLS/再試行/バックオフ)

Kubernetesの適用スクリプト内で外部システム(CI/CDフック、監視システムなど)と連携する場合、curlを安全に利用することが重要です。curl2024年5月15日8.8.0がリリースされており、これらの機能は広くサポートされています[3]。

#!/bin/bash

set -euo pipefail

API_ENDPOINT="https://api.example.com/status"

# 現在時刻をUTCで取得し、ISO 8601形式にフォーマット

PAYLOAD='{"status": "kubernetes_applied", "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"}'

echo "外部APIに適用ステータスを通知します..."
if ! curl -X POST "$API_ENDPOINT" \
          --header "Content-Type: application/json" \
          --data "$PAYLOAD" \
          --silent --show-error \
          --fail-early \
          --tlsv1.2 \
          --retry 5 \
          --retry-delay 5 \
          --retry-max-time 60 \
          --connect-timeout 10 \
          --max-time 300; then
  echo "エラー: 外部APIへの通知に失敗しました。" >&2
  exit 1
fi
echo "外部APIへの通知が成功しました。"
  • --tlsv1.2: TLS 1.2以上のバージョンを強制し、安全な通信を保証します。

  • --retry 5: 失敗した場合、最大5回リトライします。

  • --retry-delay 5: 各リトライの間に5秒間待機します(バックオフ)。

  • --retry-max-time 60: リトライを含むリクエスト全体の最大時間を60秒に制限します。

  • --connect-timeout 10: 接続確立の最大時間を10秒に設定します。

  • --max-time 300: 全操作の最大時間を300秒に設定します。

これらのオプションは、ネットワークの不安定性や一時的なサービス停止に対する堅牢性を高めます。

3. 検証

上記で示したスクリプトは、dry-runおよびjqによる検証ステップで構成されており、エラーが発生した場合は即座に中断するように設計されています。これにより、実際のKubernetesクラスタに変更が加えられる前に問題を発見し、修正することが可能です。手動でスクリプトを実行し、意図した通りの動作とエラーハンドリングが機能することを確認してください。

4. 運用

4.1. systemd unit/timer での定期実行

systemdを使用して、作成したスクリプトを定期的に実行し、Kubernetesクラスタの構成を最新の状態に保つことができます。

4.1.1. 適用スクリプト (/usr/local/bin/apply-k8s-config.sh)

上記のシェルスクリプトを /usr/local/bin/apply-k8s-config.sh として保存し、実行権限を与えます。

#!/bin/bash


# apply-k8s-config.sh

set -euo pipefail

# スクリプト実行者として通常ユーザーを指定することを推奨


# 例えば、systemd unitでUser=myuserを設定する

# 一時ディレクトリを/var/tmp以下に作成し、システム全体で共有されにくいようにする

TMP_DIR=$(mktemp -d --tmpdir=/var/tmp dry-run-k8s-XXXXXX)

# ログファイルはシステムログとは別に管理し、journaldと重複して出力

LOG_DIR="/var/log/k8s-apply"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/apply-$(date +%Y%m%d-%H%M%S).log"

cleanup() {
  echo "$(date +%Y-%m-%dT%H:%M:%SZ): 一時ディレクトリを削除します: $TMP_DIR"
  rm -rf "$TMP_DIR"
  echo "$(date +%Y-%m-%dT%H:%M:%SZ): スクリプト終了。"
}
trap cleanup EXIT

# ログをjournaldに加えて、個別ファイルにも出力(systemdのstdout/stderrはjournaldに行く)


# execコマンドで標準出力と標準エラー出力をリダイレクト

exec >> "$LOG_FILE" 2>&1

echo "$(date +%Y-%m-%dT%H:%M:%SZ): Kubernetes構成適用スクリプトを開始します。"

# Kustomizeのベースディレクトリは環境に合わせて変更

KUSTOMIZE_OVERLAY="/opt/kustomize/overlays/production"
MANIFEST_FILE="$TMP_DIR/manifest.yaml"

echo "$(date +%Y-%m-%dT%H:%M:%SZ): Kustomizeビルドを開始します..."

# Kustomizeビルドが失敗した場合、ログに出力して終了

if ! kubectl kustomize "$KUSTOMIZE_OVERLAY" > "$MANIFEST_FILE"; then
  echo "$(date +%Y-%m-%dT%H:%M:%SZ): エラー: Kustomizeビルドに失敗しました。"
  exit 1
fi
echo "$(date +%Y-%m-%dT%H:%M:%SZ): マニフェストを $MANIFEST_FILE に出力しました。"

echo "$(date +%Y-%m-%dT%H:%M:%SZ): kubectl dry-run --client でクライアント側検証を実行します..."
if ! kubectl apply -f "$MANIFEST_FILE" --dry-run=client --output=yaml > /dev/null; then
  echo "$(date +%Y-%m-%dT%H:%M:%SZ): エラー: クライアント側dry-run検証に失敗しました。"
  exit 1
fi
echo "$(date +%Y-%m-%dT%H:%M:%SZ): クライアント側dry-run検証が成功しました。"

echo "$(date +%Y-%m-%dT%H:%M:%SZ): kubectl dry-run --server でサーバー側検証を実行します..."

# サーバー側dry-runの結果をDRY_RUN_OUTPUT変数に格納

DRY_RUN_OUTPUT=$(kubectl apply -f "$MANIFEST_FILE" --dry-run=server --output=json)
DRY_RUN_STATUS=$?

if [ $DRY_RUN_STATUS -ne 0 ]; then
  echo "$(date +%Y-%m-%dT%H:%M:%SZ): エラー: サーバー側dry-run検証に失敗しました。"
  echo "$DRY_RUN_OUTPUT"
  exit 1
fi
echo "$(date +%Y-%m-%dT%H:%M:%SZ): サーバー側dry-run検証が成功しました。"

echo "$(date +%Y-%m-%dT%H:%M:%SZ): jq を用いてdry-run出力を検証します..."

# 例: replicasが3になっているか確認

REPLICAS_COUNT=$(echo "$DRY_RUN_OUTPUT" | jq -r 'select(.kind == "Deployment" and .metadata.name == "my-app") | .spec.replicas')
if [ "$REPLICAS_COUNT" != "3" ]; then
  echo "$(date +%Y-%m-%dT%H:%M:%SZ): エラー: replicas数が期待値(3)と異なります: $REPLICAS_COUNT"
  exit 1
fi
echo "$(date +%Y-%m-%dT%H:%M:%SZ): jqによる出力検証が成功しました。"

echo "$(date +%Y-%m-%dT%H:%M:%SZ): すべての検証が成功しました。Kubernetesリソースを適用します..."
kubectl apply -f "$MANIFEST_FILE"
echo "$(date +%Y-%m-%dT%H:%M:%SZ): Kubernetesリソースの適用が完了しました。"

exit 0

sudo chmod +x /usr/local/bin/apply-k8s-config.sh を実行)

4.1.2. systemd Unitファイル (/etc/systemd/system/k8s-config-apply.service)

[Unit]
Description=Apply Kubernetes Configuration
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/apply-k8s-config.sh
WorkingDirectory=/opt/kustomize # Kustomizeのベースディレクトリの親を設定
User=k8sadmin # 専用のシステムユーザーを指定
Group=k8sadmin # 専用のシステムグループを指定
Environment="KUBECONFIG=/home/k8sadmin/.kube/config" # kubeconfigのパスをユーザーのホームディレクトリに設定
StandardOutput=journal # 標準出力はjournaldに記録
StandardError=journal # 標準エラーもjournaldに記録

# Restart=on-failure # タイマー実行なので基本的に不要だが、手動実行時の堅牢性を高めるなら設定

4.1.3. systemd Timerファイル (/etc/systemd/system/k8s-config-apply.timer)

[Unit]
Description=Run Kubernetes Configuration Apply Script Daily

[Timer]
OnCalendar=daily # 毎日実行

# OnCalendar=*-*-* 03:00:00 # 毎日午前3時00分00秒に実行する場合

Persistent=true # タイマーが非アクティブな間に発生したイベントを実行(システムの起動時など)
Unit=k8s-config-apply.service # 実行するサービスユニット

[Install]
WantedBy=timers.target

4.1.4. systemdの有効化と起動

# systemd設定をリロード

sudo systemctl daemon-reload

# タイマーを有効化

sudo systemctl enable k8s-config-apply.timer

# タイマーを起動

sudo systemctl start k8s-config-apply.timer

# タイマーのステータスを確認

sudo systemctl status k8s-config-apply.timer

ログはjournalctl -u k8s-config-apply.serviceで確認できます。また、スクリプト内で指定した/var/log/k8s-apply/ディレクトリにも出力されます。

4.2. root権限の扱いと権限分離

root権限でのスクリプト実行は避けるべきです。

  • 専用ユーザーの作成: k8sadminのような専用のシステムユーザーを作成し、そのユーザーにkubectl実行に必要な最小限の権限(kubeconfigファイルへの読み取り権限、Kubernetesクラスタ上でのRBACロール)のみを付与します。

  • Principle of Least Privilege (最小権限の原則): k8sadminユーザーは、systemdサービスファイルで指定されたExecStartコマンドと、そのコマンドがアクセスするファイル(Kustomize定義、kubeconfigなど)にのみアクセス権を持つようにします。

  • kubeconfigの制限: kubeconfigファイルには、k8sadminが操作する名前空間やリソースに限定された権限を持つServiceAccountトークンや証明書を使用します。

  • パスワードや秘密鍵の管理: 環境変数、Secrets管理ツール、あるいはsystemd-credsのような機能を使用して、認証情報を安全に渡します。スクリプト内にハードコードしてはいけません。

これにより、万が一スクリプトに脆弱性があった場合でも、システム全体への影響を最小限に抑えることができます。

5. トラブルシュート

  • スクリプトが実行されない:

    • sudo systemctl status k8s-config-apply.timerでタイマーが有効かつアクティブか確認します。

    • sudo systemctl status k8s-config-apply.serviceでサービスの状態を確認します。

    • sudo journalctl -u k8s-config-apply.serviceでサービスのログを確認し、エラーメッセージがないか確認します。

    • スクリプトファイルのパスや実行権限(chmod +x)が正しいか確認します。

  • kubectlコマンドが失敗する:

    • kubeconfigファイルのパス(Environment="KUBECONFIG=...")が正しいか、そのファイルへの読み取り権限がUserに指定されたユーザーにあるか確認します。

    • Kubernetesクラスタへのネットワーク接続が安定しているか確認します。

    • kubectl get --raw=/healthzなどでAPIサーバーのヘルスチェックを行います。

    • kubectl auth can-i ...で、k8sadminユーザーに十分なRBAC権限があるか確認します。

  • dry-runが失敗するが、手動では成功する:

    • systemd環境と手動実行環境で、環境変数(PATH, KUBECONFIGなど)やカレントディレクトリが異なる可能性があります。systemdユニットファイルで明示的に設定します。

    • jqの構文エラーがないか確認します。

  • jqによる検証が期待通りに動作しない:

    • kubectl dry-run --output=jsonの実際の出力を確認し、jqフィルターが正しくパスをたどっているかテストします。

6. まとめ

kubectl dry-runとKustomizeを核とし、シェルスクリプトのベストプラクティス、jqによる出力検証、curlによる安全なAPI連携、そしてsystemdによる自動化を組み合わせることで、Kubernetesへの安全かつ冪等なリソース適用プロセスをDevOpsエンジニアの視点から解説しました。

重要なのは、変更を適用する前に徹底的に検証を行い、予期せぬ動作を防ぐことです。また、運用においては最小権限の原則を徹底し、セキュリティリスクを最小限に抑えることが不可欠です。これらのプラクティスを導入することで、Kubernetes環境の安定性と信頼性を大幅に向上させることができます。


References: [1] Kubernetes Documentation, “API Concepts – Dry Run,” Kubernetes Project, Last updated 2024年1月15日. Available: https://kubernetes.io/docs/reference/using-api/api-concepts/#dry-run [2] jq Releases, “jq 1.7.1,” jqlang.github.io, Released 2024年4月2日. Available: https://jqlang.github.io/jq/download/ [3] curl Releases, “curl 8.8.0,” curl.se, Released 2024年5月15日. Available: https://curl.se/changes.html#8_8_0 [4] Kustomize, “Official Website,” kustomize.io, Last updated 2024年6月10日. Available: https://kustomize.io/

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

コメント

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