本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
kubectl apply --dry-runで安全なデプロイ
Kubernetes環境でのアプリケーションデプロイは、本番環境への影響を最小限に抑えつつ、確実に行う必要があります。kubectl apply --dry-runコマンドは、実際にリソースを作成または変更することなく、適用される変更内容を事前に確認できるため、この目標を達成するための重要なツールです。本記事では、kubectl apply --dry-runを最大限に活用し、シェルスクリプトのベストプラクティス、systemdによる自動化、および適切な権限管理を組み合わせることで、冪等性のある安全なデプロイパイプラインを構築する方法をDevOpsエンジニアの視点から解説します。
要件と前提
安全なデプロイプロセスを構築するために、以下の要件と前提を設けます。
kubectl apply --dry-runの活用: デプロイ前に変更内容を必ず検証します。--dry-run=client:kubectlがローカルで変更を検証します。基本的な構文チェックには有用ですが、APIサーバー側のAdmission Controllerやリソースの競合は検出できません。--dry-run=server: リクエストをAPIサーバーに送信し、APIサーバー側で検証とシミュレーションを行います。Admission Controllerによる拒否や、既存リソースとの競合など、より実践的な検証が可能です。本記事では、このserver-side dry-runを推奨します。
冪等性(Idempotency): デプロイスクリプトは、何度実行しても同じ結果になるように設計します。
シェルスクリプトの安全性: エラーハンドリング、一時ファイルの管理など、堅牢なスクリプトを作成します。
権限分離と最小権限の原則: デプロイ操作に必要最小限の権限のみを付与し、特権での実行を避けます。
ネットワーク通信の安定性:
curlなどを用いる外部連携においては、再試行メカニズムを組み込みます。自動化と監視:
systemdを用いてデプロイプロセスをスケジュールし、ログを適切に管理します。
実装
デプロイフローの概要(Mermaid)
まず、kubectl apply --dry-runを用いたデプロイの全体的な流れをフローチャートで示します。
graph TD
A["開始"] --> B{"YAMLマニフェスト準備"};
B --> C["kubectl apply -f manifest/ --dry-run=server 実行"];
C --> D{"変更内容の確認"};
D -- 変更あり/問題なし --> E["変更の承認"];
D -- 変更なし/問題あり --> F["デプロイ中断/マニフェスト修正"];
E --> G["kubectl apply -f manifest/ 実行"];
G --> H["デプロイ完了"];
F --> I["終了"];
H --> I;
安全なシェルスクリプトの作成
set -euo pipefail、trap、mktempを用いて、エラー耐性の高いデプロイスクリプトを作成します。
#!/bin/bash
# スクリプトの実行中にエラーが発生した場合、即座に終了
set -euo pipefail
# 一時ディレクトリの作成と、スクリプト終了時の自動削除
TMP_DIR=$(mktemp -d)
trap 'echo "Cleaning up temporary directory: $TMP_DIR"; rm -rf "$TMP_DIR"' EXIT
# --- 設定変数 ---
# kubeconfigのパス (必要に応じて環境変数KUBECONFIGで設定)
KUBECONFIG_PATH="${KUBECONFIG_PATH:-$HOME/.kube/config}"
# 使用するKubernetesコンテキスト
KUBE_CONTEXT="${KUBE_CONTEXT:-my-production-cluster}"
# デプロイ対象のYAMLマニフェストディレクトリ
MANIFEST_DIR="${MANIFEST_DIR:-./kubernetes-manifests}"
# Dry-runの出力ログファイル
DRY_RUN_LOG="$TMP_DIR/dry_run_output.json"
# Applyの出力ログファイル
APPLY_LOG="$TMP_DIR/apply_output.json"
# --- 関数定義 ---
# デプロイステータスを外部サービスに通知する関数
# TLSv1.2、再試行、バックオフ、タイムアウトを設定し、ネットワークエラーに対応
function notify_deployment_status() {
local status="$1" # "success", "failure", "cancelled"
local message="$2"
local webhook_url="https://your-monitoring-system.com/webhook" # 通知先のWebhook URL
echo "INFO: Notifying deployment status: $status - $message"
# curlコマンドでTLSv1.2を使用し、再試行とタイムアウトを設定
curl -sS --fail \
--retry 5 --retry-delay 3 --retry-max-time 30 \
--connect-timeout 5 --max-time 10 \
--tlsv1.2 \
-H "Content-Type: application/json" \
-d "$(jq -n --arg s "$status" --arg m "$message" \
'{"status": $s, "message": $m, "timestamp": "$(date -uIs)"}')" \
"$webhook_url" || echo "WARNING: Failed to send notification to $webhook_url" >&2
}
# Kubernetesマニフェストの適用(dry-runまたは本番適用)
function deploy_kubernetes_manifests() {
local action="$1" # "dry-run" or "apply"
local log_file=""
local kubectl_args=("--kubeconfig" "$KUBECONFIG_PATH" "--context" "$KUBE_CONTEXT")
# マニフェストディレクトリが存在するか確認
if [[ ! -d "$MANIFEST_DIR" ]]; then
echo "ERROR: Manifest directory '$MANIFEST_DIR' does not exist." >&2
notify_deployment_status "failure" "Manifest directory not found."
return 1
fi
echo "INFO: Performing '$action' operation for manifests in '$MANIFEST_DIR'..."
if [[ "$action" == "dry-run" ]]; then
log_file="$DRY_RUN_LOG"
# server-side dry-runを実行し、JSON形式で出力
kubectl "${kubectl_args[@]}" apply -f "$MANIFEST_DIR" --dry-run=server -o json \
> "$log_file" 2>&1
elif [[ "$action" == "apply" ]]; then
log_file="$APPLY_LOG"
# 実際のリソース適用。出力はJSON形式
kubectl "${kubectl_args[@]}" apply -f "$MANIFEST_DIR" -o json \
> "$log_file" 2>&1
else
echo "ERROR: Invalid action '$action' provided to deploy_kubernetes_manifests." >&2
notify_deployment_status "failure" "Invalid deployment action specified."
return 1
fi
if [[ $? -ne 0 ]]; then
echo "ERROR: 'kubectl $action' failed. Check '$log_file' for details." >&2
cat "$log_file" >&2
notify_deployment_status "failure" "'kubectl $action' failed. See logs."
return 1
fi
echo "INFO: 'kubectl $action' completed successfully."
return 0
}
# --- メイン処理 ---
echo "INFO: Starting Kubernetes deployment process at $(date +'%Y-%m-%dT%H:%M:%S%z')"
# 1. サーバーサイドDry-runの実行
if ! deploy_kubernetes_manifests "dry-run"; then
echo "ERROR: Dry-run failed. Exiting deployment process."
exit 1
fi
echo "--- Dry-run Output (first 10 lines) ---"
# jqを用いてdry-runのJSON出力を整形し、人間が読める形式で表示
# 注意: 実際の変更点を確認するには、kubectl diff --server-sideなどのツールと組み合わせることが推奨されます
jq . "$DRY_RUN_LOG" | head -n 10
echo "-----------------------------------"
# 承認ステップ (CI/CDパイプラインでは自動化されることが多い)
read -p "Dry-run was successful. Do you want to proceed with actual apply? (y/N) " -n 1 -r
echo # 改行
if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
echo "INFO: Deployment cancelled by user."
notify_deployment_status "cancelled" "Deployment cancelled after dry-run."
exit 0
fi
# 2. 実際のマニフェスト適用
if ! deploy_kubernetes_manifests "apply"; then
echo "ERROR: Actual apply failed. Exiting deployment process."
exit 1
fi
echo "INFO: Kubernetes deployment completed successfully at $(date +'%Y-%m-%dT%H:%M:%S%z')"
notify_deployment_status "success" "Kubernetes deployment completed."
exit 0
権限分離とroot権限の扱い
kubectlコマンドは、kubeconfigファイルに指定された認証情報と、Kubernetes APIサーバーのRBAC(Role-Based Access Control)に基づいて動作します。デプロイスクリプトを実行するユーザーやサービスアカウントには、デプロイ対象のリソースに対する最小限の権限のみを付与すべきです。
サービスアカウントの利用: CI/CDパイプラインや自動デプロイスクリプトでは、特定のKubernetesサービスアカウントを作成し、そのサービスアカウントに紐付けられたトークンを
kubeconfigに設定して使用します。RBACロールの設計: デプロイ対象のリソース(Deployment, Service, Ingressなど)に対して、
get,list,watch,create,update,patch,deleteなどの動詞(verb)を必要最小限に限定したRoleまたはClusterRoleを定義します。dry-runであっても、APIサーバーは同様の権限チェックを行います。root権限の回避: デプロイスクリプトをrootユーザーで実行することは極力避けるべきです。これにより、意図しないシステム変更やセキュリティ上の脆弱性を防ぎます。通常、kubectlは通常のユーザー権限で十分動作します。
検証
デプロイ後の状態検証は、変更が意図通りに適用されたことを確認するために不可欠です。
kubectl getでのリソース確認: デプロイしたリソースが正しく作成・更新されたかを確認します。kubectl get deployment my-app -o yaml kubectl get service my-app -o json | jq '.spec.ports'
kubectl diff --server-side: 既存のクラスタリソースとローカルのマニフェストの差分を確認し、変更がないことを確認します。これは冪等性の確認にも役立ちます。kubectl diff --server-side -f ./kubernetes-manifests/
Podのログ確認: 新しいPodが起動し、アプリケーションが正常に動作しているかログで確認します。
kubectl logs -l app=my-app
journalctlでのスクリプトログ確認: デプロイスクリプト自体の実行ログをjournalctlで確認します(systemdで実行している場合)。
運用
systemd Unit/Timerによる自動化
定期的なデプロイや特定の時刻にデプロイスクリプトを実行したい場合、systemdのUnitとTimerを組み合わせると便利です。
1. デプロイサービスユニット (/etc/systemd/system/my-k8s-deploy.service)
[Unit] Description=Kubernetes Deployment Service for My-App # サービスが正常に動作するために必要な条件 Requires=network-online.target After=network-online.target [Service] Type=oneshot # 一度実行したら終了するサービス User=k8s-deploy # デプロイ専用のユーザー (最小権限) WorkingDirectory=/opt/my-app-deploy # スクリプトの実行ディレクトリ ExecStart=/opt/my-app-deploy/deploy.sh # 上記で作成したスクリプトのパス Environment="KUBECONFIG_PATH=/home/k8s-deploy/.kube/config" # 必要に応じてkubeconfigパスを設定 Environment="KUBE_CONTEXT=my-production-cluster" StandardOutput=journal # 標準出力をjournalに記録 StandardError=journal # 標準エラー出力をjournalに記録 # エラー時にスクリプトが異常終了した場合の動作 OnFailure=my-app-deploy-failure-handler.service # 失敗時に実行する別のサービス (任意) [Install] WantedBy=multi-user.target
2. デプロイタイマーユニット (/etc/systemd/system/my-k8s-deploy.timer)
例えば、毎日午前3時に実行する場合。
[Unit] Description=Run Kubernetes Deployment Service Daily [Timer] OnCalendar=*-*-* 03:00:00 # 毎日午前3時に実行 (JST基準) Persistent=true # タイマー起動中にシステムが停止した場合、次回起動時にスキップされた実行を試みる [Install] WantedBy=timers.target
3. systemdの設定と起動
# スクリプトを適切な場所に配置し、実行権限を付与 sudo mkdir -p /opt/my-app-deploy sudo cp deploy.sh /opt/my-app-deploy/ sudo chmod +x /opt/my-app-deploy/deploy.sh # デプロイ専用ユーザーを作成し、kubeconfigを設定 sudo useradd -r -s /sbin/nologin k8s-deploy sudo mkdir -p /home/k8s-deploy/.kube # 適切なkubeconfigファイルを /home/k8s-deploy/.kube/config に配置し、k8s-deployユーザーに読み取り権限を付与 # (例: sudo cp /path/to/k8s-deploy-kubeconfig /home/k8s-deploy/.kube/config) sudo chown -R k8s-deploy:k8s-deploy /home/k8s-deploy/.kube # systemdユニットファイルをリロード sudo systemctl daemon-reload # タイマーを有効化し、すぐに起動 sudo systemctl enable --now my-k8s-deploy.timer
4. ログの確認
デプロイ実行ログはjournalctlで確認できます。
journalctl -u my-k8s-deploy.service --since "2024-07-30 00:00:00 JST"
(日付は2024-07-30の例です。実際の実行日に合わせてください。)
CI/CDパイプラインへの組み込み
上記のスクリプトは、GitLab CI/CD、GitHub Actions、JenkinsなどのCI/CDパイプラインに容易に組み込むことができます。dry-runステップをPull Requestのマージ前に実行し、差分をレビュー担当者に提示することで、デプロイプロセスの安全性をさらに高めることが可能です。
トラブルシュート
kubectl apply --dry-runを用いたデプロイプロセスで問題が発生した場合の一般的なトラブルシュート方法です。
Dry-runエラー:
YAML構文エラー:
kubectl applyコマンド自体が失敗し、構文エラーメッセージが出力されます。YAMLファイルを修正してください。RBAC権限不足:
dry-runであっても、APIサーバーはリソースの作成・更新に必要な権限をチェックします。エラーメッセージにforbiddenやunauthorizedといったキーワードがあれば、デプロイ実行ユーザー/サービスアカウントのRBACロールを見直してください。Admission Controllerによる拒否:
ValidatingWebhookConfigurationやMutatingWebhookConfigurationが設定されている場合、dry-runでもそれらのチェックが実行されます。ポリシーに違反する変更は拒否されるため、マニフェストを修正するか、Admission Controllerの設定を確認してください。
Applyエラー:
リソース競合: 既に存在するリソースと名前が衝突したり、異なるControllerが同じリソースを管理しようとしたりする場合があります。
kubectl get <resource-type> <resource-name> -o yamlで現在のクラスタの状態を確認し、マニフェストと照らし合わせてください。APIサーバー接続問題: ネットワークの問題や
kubeconfigの誤設定により、APIサーバーに接続できない場合があります。kubectl cluster-infoやkubectl versionで接続性を確認してください。
ログの確認:
journalctl:systemdで実行している場合、デプロイスクリプト自体の標準出力/エラー出力はjournalctl -u my-k8s-deploy.serviceで確認できます。kubectl describe: デプロイしたDeploymentやPodなどのリソースに問題がある場合、kubectl describe deployment my-appなどでイベントログや詳細情報を確認します。kubectl logs: 新しくデプロイされたアプリケーションPodのログを確認し、アプリケーションレベルでのエラーがないか調査します。
まとめ
kubectl apply --dry-runは、Kubernetes環境における安全で信頼性の高いデプロイを実現するための強力な機能です。特に--dry-run=serverオプションを用いることで、APIサーバー側の厳密な検証をデプロイ前に実施できます。本記事で示したシェルスクリプトのベストプラクティス、systemdによる自動化、そして適切な権限分離と監視を組み合わせることで、冪等性のある堅牢なデプロイパイプラインを構築し、本番環境への予期せぬ変更リスクを大幅に低減できます。これにより、DevOpsプラクティスにおける継続的なデリバリーの品質と信頼性を向上させることが可能です。

