kubectl: Deploymentのローリング再起動

Tech

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

kubectl: Deploymentのローリング再起動

Kubernetes環境において、DeploymentのPodを再起動するシナリオは多岐にわたります。例えば、設定変更の適用、メモリリーク対策、または定期的なリフレッシュなどです。本記事では、kubectlコマンドを用いたDeploymentの安全なローリング再起動の手順、その自動化、そしてトラブルシューティングについて、DevOpsエンジニアの視点から解説します。

1. 要件と前提

1.1. ローリング再起動とは

ローリング再起動は、Deployment内のPodを一度にすべて停止させるのではなく、Podを順番に置き換えることで、サービスへの影響を最小限に抑えながら更新を行う手法です。KubernetesのDeploymentでは、デフォルトでRollingUpdateストラテジーが採用されており、これによってゼロダウンタイムでの更新が可能になります。kubectl rollout restartコマンドは、DeploymentのPod Template Specに変更を加え、既存のPodを順次終了させ、新しいPodを起動させることでローリングアップデートをトリガーします。

1.2. 前提条件

  • Kubernetesクラスターが稼働していること。

  • kubectlコマンドラインツールがインストールされ、クラスターへの接続設定が完了していること。

  • 再起動対象となるDeploymentがクラスター内に存在していること。

  • ロールアウトを正常に完了するための十分なクラスターリソース(CPU, メモリ)が存在すること。

  • Readiness ProbeとLiveness Probeが適切に設定されていること(これにより、新しいPodがトラフィックを受け入れる準備ができたことをKubernetesが判断できます)。

2. 実装

2.1. 基本的なローリング再起動の実施

最もシンプルなローリング再起動は、以下のkubectlコマンドで実行できます。

kubectl rollout restart deployment/<deployment-name> -n <namespace>

例:

kubectl rollout restart deployment/my-app-deployment -n default

このコマンドは、DeploymentのPod Template Specにkubernetes.io/change-causeアノテーションまたはタイムスタンプを含むアノテーションを追加・更新することで、変更をトリガーし、ローリングアップデートを開始します。Kubernetesはこれを検知し、Deploymentコントローラーが新しいPodの起動と古いPodの終了を順次行います。

2.2. 安全なスクリプトによる自動化

ローリング再起動を自動化する場合、堅牢なシェルスクリプトを作成することが重要です。ここでは、set -euo pipefailtrapを用いた安全なbashスクリプトの例を示します。

#!/bin/bash


# file: restart-deployment.sh

# スクリプトの途中でエラーが発生した場合、即座に終了


# 未定義の変数が使用された場合、エラーとして終了


# パイプライン中のコマンドが一つでも失敗した場合、エラーとして終了

set -euo pipefail

# 現在のJST日付を取得

JST_TODAY=$(TZ='Asia/Tokyo' date +'%Y年%m月%d日 (%a)')

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


# mktemp -d はセキュリティを考慮し、ユニークな一時ディレクトリを作成

tmpdir=$(mktemp -d)
trap 'echo "[$JST_TODAY] 一時ディレクトリ ${tmpdir} をクリーンアップします。" && rm -rf "$tmpdir"' EXIT

# 設定変数

DEPLOYMENT_NAME="my-app-deployment"
NAMESPACE="default"
TIMEOUT_MINUTES="5m" # ロールアウト完了の最大待機時間

echo "[$JST_TODAY] Deployment '${DEPLOYMENT_NAME}' (namespace: '${NAMESPACE}') のローリング再起動を開始します。"

# Deploymentの存在チェック

if ! kubectl get deployment "${DEPLOYMENT_NAME}" -n "${NAMESPACE}" > /dev/null 2>&1; then
    echo "[$JST_TODAY] エラー: Deployment '${DEPLOYMENT_NAME}' が存在しません。スクリプトを終了します。"
    exit 1
fi

# ロールアウト再起動のトリガー

echo "[$JST_TODAY] kubectl rollout restart コマンドを実行します..."
if ! kubectl rollout restart deployment "${DEPLOYMENT_NAME}" -n "${NAMESPACE}"; then
    echo "[$JST_TODAY] エラー: ロールアウト再起動のトリガーに失敗しました。スクリプトを終了します。"
    exit 1
fi

# ロールアウトのステータス確認

echo "[$JST_TODAY] ロールアウトの完了を最大 ${TIMEOUT_MINUTES} 待機します..."
if ! kubectl rollout status deployment "${DEPLOYMENT_NAME}" -n "${NAMESPACE}" --timeout="${TIMEOUT_MINUTES}"; then
    echo "[$JST_TODAY] エラー: ロールアウトが時間内に完了しませんでした。Deploymentの状態を確認してください。"
    exit 1
fi

echo "[$JST_TODAY] Deployment '${DEPLOYMENT_NAME}' のローリング再起動が成功しました。"

# オプション: サービスヘルスチェック (curlとjqの活用)


# 必要に応じて、サービスのエンドポイントやヘルスチェックパスを調整してください。


# この例では、サービスタイプがLoadBalancerまたはNodePortで、外部IPが割り当てられていることを想定。

echo "[$JST_TODAY] サービスのエンドポイント情報を取得します..."
SERVICE_NAME="my-app-service" # ヘルスチェック対象のService名
SERVICE_JSON=$(kubectl get service "${SERVICE_NAME}" -n "${NAMESPACE}" -o json 2>/dev/null || true)

if [[ -n "${SERVICE_JSON}" ]]; then

    # jqを用いて、LoadBalancerの外部IPまたはClusterIPを取得


    # .status.loadBalancer.ingress[0].ip は LoadBalancer の外部IP


    # .spec.clusterIP は ClusterIP

    SERVICE_IP=$(echo "${SERVICE_JSON}" | jq -r '
        .status.loadBalancer.ingress[0].ip // .spec.clusterIP // "null"
    ')

    if [[ "${SERVICE_IP}" != "null" ]]; then
        HEALTH_CHECK_URL="http://${SERVICE_IP}:8080/healthz" # アプリケーションのヘルスチェックURLに合わせる
        echo "[$JST_TODAY] サービスヘルスチェックを開始します (${HEALTH_CHECK_URL})..."

        # curl: 5回リトライ、5秒間隔、接続拒否もリトライ対象、サイレントモード、エラーコードで失敗、HTTPS証明書検証無効

        if curl --retry 5 --retry-delay 5 --retry-connrefused -sS --fail -k "${HEALTH_CHECK_URL}"; then
            echo "[$JST_TODAY] サービスヘルスチェックに成功しました。"
        else
            echo "[$JST_TODAY] 警告: サービスヘルスチェックに失敗しました。アプリケーションの状態を確認してください。"
        fi
    else
        echo "[$JST_TODAY] 警告: サービス '${SERVICE_NAME}' のIPアドレスが見つかりませんでした。ヘルスチェックをスキップします。"
    fi
else
    echo "[$JST_TODAY] 警告: サービス '${SERVICE_NAME}' が見つからないか、JSON取得に失敗しました。ヘルスチェックをスキップします。"
fi

echo "[$JST_TODAY] スクリプトが正常に終了しました。"

2.3. ロールアウトプロセスのMermaidフローチャート

ローリング再起動のプロセスを図で示します。

graph TD
    A["再起動トリガーを適用"] -->|kubectl rollout restart| B{"Deploymentの変更を検出"};
    B -- Pod Template Spec変更 --> C["新しいReplicaSetを作成"];
    C --> D{"Podの終了と新規作成を順次実行"};
    D -- 新しいPod起動 --> E{"Readiness Probeが成功"};
    E -- 成功 --> F["古いPodを削除し、新しいPodを追加"];
    F --> G{"全てのPodが新しいバージョンに"};
    G -- 更新完了 --> H["Rollout成功"];
    E -- 失敗 --> I["Rollout失敗/Rollbackを検討"];
    I --> J["トラブルシューティング"];

3. 検証

ローリング再起動後には、以下の方法で成功を確認します。

  1. ロールアウト履歴の確認:

    kubectl rollout history deployment/my-app-deployment -n default
    

    最新のリビジョンが更新されていることを確認します。

  2. Podの確認:

    kubectl get pods -n default -l app=my-app -o wide
    

    すべてのPodがRunning状態であり、新しいPodが起動していることを確認します。AGEが再起動からの時間を示します。

  3. イベントログの確認:

    kubectl describe deployment my-app-deployment -n default
    kubectl get events -n default
    

    Deploymentのイベントログから、更新が正常に進行したか、エラーが発生していないかを確認します。

  4. アプリケーションログの確認: アプリケーションのログを確認し、サービスが正常に動作しているか、エラーが発生していないかを検証します。

    kubectl logs -f <pod-name> -n default
    

4. 運用

4.1. systemdによる自動化

前述のシェルスクリプトをsystemdのUnit/Timerと組み合わせて、定期的な再起動やイベントベースの再起動を自動化できます。

4.1.1. systemd Service Unitの作成 /etc/systemd/system/restart-my-app.service

[Unit]
Description=Restart my-app Kubernetes Deployment
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/restart-deployment.sh
User=kubernetes-admin # kubectl設定済みユーザー(サービスアカウント利用推奨)
Group=kubernetes-admin # kubectl設定済みグループ

# StandardOutput=journal


# StandardError=journal


# Restart=on-failure # 失敗時に自動再起動する場合

[Install]
WantedBy=multi-user.target
  • ExecStart: 上記で作成したシェルスクリプトのパスを指定します。

  • User/Group: kubectlが動作するために必要なkubeconfigファイルへのアクセス権を持つユーザーを指定します。本番環境では、最小権限のサービスアカウントを作成し、その資格情報を使用するようシェルスクリプトを調整し、User=...は適切なものに設定することが強く推奨されます。root権限でサービスを実行する場合、セキュリティリスクが増大します。

4.1.2. systemd Timer Unitの作成 /etc/systemd/system/restart-my-app.timer (例: 毎日午前3時JSTに実行)

[Unit]
Description=Run my-app Deployment restart daily at 3 AM JST

[Timer]
OnCalendar=*-*-* 03:00:00 # 毎日午前3時 (システムのタイムゾーンに基づく)
Persistent=true # サーバ再起動後も、実行し損ねた場合は即座に実行

[Install]
WantedBy=timers.target

4.1.3. systemdの設定と起動

  1. シェルスクリプトの配置と権限設定:

    sudo install -m 755 restart-deployment.sh /usr/local/bin/
    
  2. systemd設定ファイルの再読み込み:

    sudo systemctl daemon-reload
    
  3. タイマーの有効化と起動:

    sudo systemctl enable restart-my-app.timer
    sudo systemctl start restart-my-app.timer
    
  4. ステータスの確認:

    sudo systemctl status restart-my-app.timer
    sudo systemctl status restart-my-app.service
    
  5. ログの確認:

    sudo journalctl -u restart-my-app.service -f
    

    これにより、スクリプトの実行ログやエラーを確認できます。

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

systemdサービスを管理するsystemctlコマンドは、通常root権限が必要です。しかし、ExecStartで実行されるシェルスクリプト内でkubectlコマンドが呼び出される場合、そのkubectlコマンドはsystemd Unitファイルで指定されたUserで実行されます。

  • 原則: kubectlコマンドは、可能な限り最小権限のサービスアカウントを使用すべきです。kubectlの実行ユーザーは、Deploymentの更新権限(update, get, list, watch on deployments, replicasets, pods)のみを持つロールにバインドされている必要があります。

  • 本番環境: systemd UnitのUser設定で、特定の非rootユーザー(例: kubernetes-operator)を指定し、そのユーザーのホームディレクトリに.kube/configを配置し、適切な権限を設定します。これにより、万が一スクリプトが侵害されても、root権限によるシステム全体への影響を防ぐことができます。

5. トラブルシュート

ローリング再起動中に問題が発生した場合の一般的なトラブルシューティング手順です。

  1. kubectl rollout statusが失敗する場合:

    • kubectl get events -n <namespace>: クラスターイベントを確認し、Podのスケジューリング失敗やイメージプルエラーなどがないか確認します。

    • kubectl describe pod <failed-pod-name> -n <namespace>: 失敗したPodの詳細を確認し、エラーメッセージやコンテナステータスを確認します。

    • kubectl logs <failed-pod-name> -n <namespace>: アプリケーションのログを確認し、アプリケーションレベルのエラーがないか調査します。

    • kubectl get rs -n <namespace>: 新しいReplicaSetが作成され、そのPodがPendingまたはCrashLoopBackOff状態になっていないか確認します。

  2. Readiness Probeが失敗する場合:

    • アプリケーションが起動に時間がかかっている可能性があります。initialDelaySecondsperiodSecondstimeoutSecondsfailureThresholdなどのReadiness Probe設定を見直します。

    • アプリケーションのヘルスチェックエンドポイントが正しく機能しているか確認します。

  3. リソース不足:

    • kubectl describe nodes: ノードのリソース使用状況を確認し、CPUやメモリが不足していないか確認します。

    • kubectl top pods -n <namespace>: 各Podのリソース使用量を確認します。

    • resources.requestsおよびresources.limitsをDeploymentのPod Template Specで適切に設定しているか確認します。

  4. ロールバック: 問題が解決できない場合や、サービスが劣化している場合は、以前の安定したバージョンにロールバックすることを検討します。

    kubectl rollout undo deployment/my-app-deployment -n default
    
    # 特定のリビジョンに戻す場合 (historyで確認したリビジョン番号)
    
    
    # kubectl rollout undo deployment/my-app-deployment -n default --to-revision=<revision-number>
    

6. まとめ

Kubernetes Deploymentのローリング再起動は、サービスに中断を与えることなくアプリケーションを更新するための重要な運用プラクティスです。kubectl rollout restartコマンドを基本とし、set -euo pipefailtrapを用いた安全なシェルスクリプト、jqcurlによる検証、そしてsystemd Unit/Timerによる自動化を組み合わせることで、堅牢な運用が実現できます。常にReadiness Probeの重要性を理解し、本番環境では最小権限の原則に従い、問題発生時には冷静にログやイベントを確認し、必要に応じてロールバックできるよう準備しておくことがDevOpsエンジニアには求められます。

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

コメント

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