Kubernetes Jobで一度きりのタスクを実行!設定と管理の全手順

はい、承知いたしました。Kubernetes Jobで一度きりのタスクを実行するための詳細なガイド記事を、約5000語の規模で記述します。


Kubernetes Jobで一度きりのタスクを実行!設定と管理の全手順

はじめに

現代のクラウドネイティブアプリケーション開発において、Kubernetesはコンテナ化されたワークロードを管理するためのデファクトスタンダードとなっています。ウェブサービス、APIバックエンド、マイクロサービスなど、常に稼働し続ける「サービス」のデプロイとスケーリングには、DeploymentReplicaSetといったオブジェクトが利用されます。しかし、Kubernetesが管理するのはサービスだけではありません。

データ移行、データベーススキーマの更新、定期レポートの生成、バックアップスクリプトの実行、または一度きりの分析タスクなど、特定の処理が完了したら終了するタイプの「タスク」を実行したい場合があります。これらのタスクは永続的に稼働する必要はなく、むしろ一度成功裏に完了すればPodを終了させ、リソースを解放することが求められます。

ここで登場するのが、KubernetesのJobオブジェクトです。Jobは、指定されたコンテナイメージを実行し、そのタスクが成功裏に完了するまでPodを起動し、完了後にPodを適切に終了させるためのKubernetesリソースです。この記事では、Kubernetes Jobの概念から、その設定方法、管理、そして実践的なユースケースとベストプラクティスに至るまで、詳細かつ網羅的に解説していきます。

1. Kubernetes Jobとは何か?

Kubernetes Jobは、一度きりのタスク(one-off tasks)やバッチ処理を実行するために設計されたKubernetesのAPIオブジェクトです。Jobの主な目的は、1つ以上のPodを作成し、それらのPodが指定されたタスクを成功裏に完了するまで実行を保証することです。タスクが完了すると、Jobはそれ以上Podを起動せず、既存のPodは完了状態として保持されるか、設定に応じて自動的に削除されます。

1.1 Pod、Deployment、Jobの比較

Kubernetesには複数のワークロード管理オブジェクトが存在し、それぞれ異なる目的を持っています。Jobを理解するためには、他の主要なオブジェクトとの違いを明確にすることが重要です。

  • Pod: Kubernetesの最小の実行単位です。1つ以上のコンテナを含み、ストレージ、ネットワーク、およびコンテナの実行方法に関する仕様を記述します。Podはタスクが完了しても自動的に再起動されることはありません。何らかの理由で終了した場合、通常は手動で再作成するか、上位のコントローラ(Job, Deploymentなど)によって管理されます。

  • Deployment: 永続的に稼働するステートレスなサービスを管理するためのオブジェクトです。ReplicaSetを制御し、指定された数のPodレプリカを常に稼働状態に保ちます。Podがクラッシュしたり削除されたりすると、Deploymentは自動的に新しいPodを起動してレプリカ数を維持しようとします。これはWebサーバーやAPIサービスなど、常に利用可能であるべきアプリケーションに適しています。

  • Job: タスクが成功裏に完了することを目指します。Deploymentとは異なり、Jobはタスクが完了した後にPodを再起動したり、レプリカ数を維持しようとはしません。Jobは、Pod内のコンテナが正常終了(終了コード0)した場合にタスクが成功したとみなし、それ以上Podを起動しません。タスクが失敗した場合(終了コード非0)は、設定に基づいて再試行を試みます。

特徴 Pod Deployment Job CronJob
目的 最小実行単位 永続的なサービス 一度きりのタスク 定期的なタスク
Pod管理 直接管理(単一) レプリカセットを維持 タスク完了までPodを起動 スケジュールに基づきJobを起動
再起動 なし(終了後停止) 自動(障害時) 失敗時のみ自動再試行 スケジュールに基づきJobを再起動
終了 終了すると停止 継続稼働 成功すると停止 スケジュールに基づきJobを生成
ユースケース デバッグ、単体テスト Webサーバー、API データ移行、バッチ処理 バックアップ、定期レポート

1.2 Jobの種類の概要

Jobは実行されるタスクの性質に応じて、いくつかのモードで構成できます。

  • 非並列Job(Non-parallel Job):
    最も基本的なJobです。1つのPodを作成し、そのPodが成功裏に完了するまで待ちます。失敗した場合は、設定されたbackoffLimit(再試行回数)に従ってPodを再起動します。ほとんどの「一度きりのタスク」はこのモードで実行されます。

  • 完了数ベースの並列Job(Work Queue Job with completions:
    複数のPodを並行して実行し、指定された数のPodが成功裏に完了したらJob全体を完了とみなします。例えば、completions: 5と設定した場合、5つのPodが成功すればJobは完了します。各Podは独立したタスク(例えば、異なるファイルの処理)を実行できます。

  • ワークキューベースの並列Job(Work Queue Job with parallelism and completions:
    これはより高度な並列処理で、外部のワークキュー(RabbitMQ, Redisなど)からタスクを取得して処理するPodを複数同時に実行します。parallelismで同時に実行するPod数を指定し、すべてのタスクがキューから取得され処理されるか、completionsで指定された数のタスクが完了するまで実行されます。このモードは、分散処理や大量のデータ処理に適しています。

この記事では、最も一般的な「非並列Job」を中心に解説し、その設定を通じてJobの基本を深く理解することを目指します。

2. Jobのユースケース

Kubernetes Jobは多岐にわたるタスクに利用できます。具体的なユースケースをいくつか見てみましょう。

  • データマイグレーション/データベーススキーマ更新:
    アプリケーションのデプロイに伴い、データベースのスキーマ変更や既存データの変換が必要になる場合があります。Jobは、この一度きりのマイグレーションスクリプトを実行するのに最適です。Pod内でマイグレーションツール(Rails rake db:migrate、Flyway、Liquibaseなど)を実行し、完了後に終了させます。

  • バッチ処理:
    夜間バッチ処理、データ分析、レポート生成、画像処理など、定期的に実行する必要があるが、一度開始したら終了するタイプの処理に適しています。このようなタスクはCronJobと組み合わせてスケジュールすることもできます。

  • CI/CDパイプラインの一部としてのテスト実行:
    CI/CDパイプラインにおいて、デプロイ前の統合テストやエンドツーエンドテストを実行するためにJobを利用できます。テストスイートをコンテナ内で実行し、テスト結果に応じてJobの成功/失敗を判断します。

  • バックアップとリストア:
    データベースやファイルシステムのバックアップスクリプトをJobとして実行し、ストレージにアーカイブします。また、バックアップからのリストア操作も同様にJobとして実行できます。

  • 一度きりのセットアップスクリプト:
    新しい環境を構築する際に、初期データ投入、キャッシュのウォームアップ、認証情報の生成といった一度きりのセットアップタスクをJobで実行できます。

これらのユースケースでは、タスクが確実に完了し、失敗時には再試行されることが重要です。Jobはそのような要件を効果的に満たします。

3. Jobの基本設定(YAML定義)

Kubernetes JobはYAMLファイルで定義されます。ここでは、最も基本的なJob定義から始め、各フィールドの意味を詳細に解説します。

3.1 最小限のJob定義

“`yaml

my-simple-job.yaml

apiVersion: batch/v1
kind: Job
metadata:
name: my-simple-job
spec:
template:
spec:
containers:
– name: my-job-container
image: alpine/git # 任意のコンテナイメージ
command: [“sh”, “-c”, “echo ‘Hello from Kubernetes Job!’; sleep 5; echo ‘Job finished successfully!’; exit 0;”]
restartPolicy: Never # JobのPodは常にNeverまたはOnFailureである必要があります
“`

このYAMLファイルは、Kubernetes Jobを定義するための最小限の構成要素を含んでいます。

  • apiVersion: batch/v1:
    このKubernetesオブジェクトが属するAPIグループとバージョンを指定します。Jobはbatch APIグループのv1バージョンに属します。

  • kind: Job:
    作成するKubernetesリソースの種類を指定します。ここではJobオブジェクトを作成します。

  • metadata::
    オブジェクトのメタデータを含みます。

    • name: my-simple-job:
      Jobの一意な名前を指定します。Kubernetesクラスタ内で重複してはいけません。kubectlコマンドでJobを識別する際に使用します。
  • spec::
    Jobの仕様を定義します。

    • template::
      Jobが作成するPodのテンプレートを定義します。この部分はPodオブジェクトのspecと非常によく似ています。

      • spec::
        Podの仕様を定義します。

        • containers::
          Pod内で実行するコンテナのリストを定義します。少なくとも1つは必要です。

          • - name: my-job-container:
            コンテナの名前。Pod内で一意である必要があります。
          • image: alpine/git:
            コンテナが使用するDockerイメージ。この例では、alpine/gitイメージを使用しています。実際には、独自のバッチ処理スクリプトやアプリケーションを含むイメージを指定します。
          • command: ["sh", "-c", "echo 'Hello from Kubernetes Job!'; sleep 5; echo 'Job finished successfully!'; exit 0;"]:
            コンテナが起動したときに実行するコマンドと引数を指定します。DockerイメージにデフォルトのENTRYPOINTCMDが設定されている場合、これらはそれらを上書きします。

            • ["sh", "-c", "..."] は、シェルコマンドを実行する一般的なパターンです。
            • exit 0; は、コマンドが正常に終了したことをKubernetesに通知するために非常に重要です。非ゼロの終了コードは失敗とみなされます。
        • restartPolicy: Never:
          JobのPodに対する再起動ポリシーは、NeverまたはOnFailureのいずれかに設定する必要があります。AlwaysはJobではサポートされていません。

          • Never: コンテナが終了した場合、そのPodは再起動されません。Jobは新しいPodを作成してタスクを再試行します(backoffLimitが設定されている場合)。
          • OnFailure: コンテナがエラー終了(非ゼロの終了コード)した場合、Pod内のコンテナが再起動されます。Pod自体は終了しません。成功終了(ゼロの終了コード)した場合は再起動されません。JobがOnFailureでPodがクラッシュループに陥ると、Pod自体が再起動を繰り返すことになります。多くの場合、Neverを選択し、Jobコントローラに新しいPodをスケジュールさせる方がシンプルで管理しやすいです。

3.2 Jobの詳細設定

Jobには、タスクの実行をよりきめ細かく制御するための多くの設定オプションがあります。

  • backoffLimit (再試行回数)
    spec.backoffLimitは、Jobが失敗したPodの再試行を何回まで行うかを指定します。デフォルトは6回です。
    yaml
    # ...
    spec:
    backoffLimit: 3 # Podが3回失敗したら、Jobは最終的に失敗とマークされる
    template:
    # ...

    Jobが作成したPodがコンテナのエラー終了(非ゼロの終了コード)によって失敗した場合、Kubernetesは新しいPodを起動してタスクを再試行します。この再試行の総数がbackoffLimitを超えると、Jobは失敗状態となり、それ以上のPod起動は行われません。

  • completions (完了Pod数)
    spec.completionsは、Jobが完了したとみなされるために必要な成功したPodの数を指定します。デフォルトは1です。主に並列Jobで使用されます。
    yaml
    # ...
    spec:
    completions: 5 # 5つのPodが正常に完了したらJobは完了
    parallelism: 2 # 同時に2つのPodを実行
    template:
    # ...

    この例では、同時に2つのPodを実行し、合計で5つのPodが成功裏に完了すればJobが完了します。各Podは異なる入力データなどを処理するシナリオで有用です。

  • parallelism (並行実行Pod数)
    spec.parallelismは、同時に実行するPodの最大数を指定します。デフォルトは1です。
    yaml
    # ...
    spec:
    parallelism: 3 # 同時に3つのPodを実行
    template:
    # ...

    これは、複数の独立したタスクを並行して実行したい場合に非常に便利です。例えば、画像をバッチ処理する際に、複数の画像ファイルを異なるPodで同時に処理するといったケースです。

  • activeDeadlineSeconds (実行時間制限)
    spec.activeDeadlineSecondsは、Jobがアクティブ(実行中)であることのできる最大の秒数を指定します。この時間を超えると、Jobは失敗状態となり、実行中のPodも終了されます。
    yaml
    # ...
    spec:
    activeDeadlineSeconds: 300 # 5分以内に完了しないJobは強制終了
    template:
    # ...

    暴走するJobや、予期せず長時間実行されるJobを防ぐために非常に有用です。

  • ttlSecondsAfterFinished (完了後のPod保持時間)
    spec.ttlSecondsAfterFinishedは、Jobが完了(成功または失敗)した後に、そのJobオブジェクトと関連するPodが自動的に削除されるまでの秒数を指定します。
    yaml
    # ...
    spec:
    ttlSecondsAfterFinished: 3600 # Job完了後1時間で自動削除
    template:
    # ...

    この設定は、完了したJobやPodがクラスタ上に残りすぎてリソースを消費したり、kubectl get jobの出力が乱雑になるのを防ぐのに役立ちます。Kubernetes 1.21以降で利用可能です。

3.3 より実践的なJobの設定

実際の運用では、上記の基本設定に加えて、さらに多くの要素が必要になります。

  • 環境変数:
    コンテナ内で実行されるスクリプトやアプリケーションは、データベース接続情報、APIキー、設定値などの環境変数に依存することがよくあります。
    yaml
    # ...
    spec:
    template:
    spec:
    containers:
    - name: my-job-container
    image: my-custom-job-image:1.0
    command: ["/app/run_migration.sh"]
    env:
    - name: DB_HOST
    value: "my-database-service"
    - name: DB_PORT
    value: "5432"
    - name: DB_USER
    valueFrom:
    secretKeyRef:
    name: db-credentials # Secretの名前
    key: username # Secret内のキー
    - name: DB_PASSWORD
    valueFrom:
    secretKeyRef:
    name: db-credentials
    key: password
    - name: APP_CONFIG_PATH
    valueFrom:
    configMapKeyRef:
    name: my-app-config # ConfigMapの名前
    key: config_file_path # ConfigMap内のキー
    restartPolicy: Never

    envフィールドを使用して、リテラル値、ConfigMap、またはSecretから環境変数を注入できます。機密情報は必ずSecretから取得するようにしてください。

  • 永続ストレージ (PVC: Persistent Volume Claim):
    Jobがデータを生成したり、既存のデータにアクセスしたりする場合、永続ストレージが必要になることがあります。PersistentVolumeClaim (PVC) をJobのPodにマウントすることで、永続的なデータアクセスを可能にします。
    yaml
    # 先にPVCを定義しておく
    # my-job-pvc.yaml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: my-job-data-pvc
    spec:
    accessModes:
    - ReadWriteOnce
    resources:
    requests:
    storage: 1Gi
    ---
    # my-job-with-pvc.yaml
    apiVersion: batch/v1
    kind: Job
    metadata:
    name: my-data-processing-job
    spec:
    template:
    spec:
    containers:
    - name: data-processor
    image: custom-data-processor:1.0
    command: ["/app/process_data.sh"]
    volumeMounts:
    - name: job-data-volume
    mountPath: /data # コンテナ内のマウントパス
    volumes:
    - name: job-data-volume
    persistentVolumeClaim:
    claimName: my-job-data-pvc # 上記で定義したPVCの名前
    restartPolicy: OnFailure # 処理中に一時的な問題があった場合、Podごと再起動

    この設定により、my-job-data-pvcで定義された永続ボリュームが/dataパスにマウントされ、Jobがデータを読み書きできるようになります。

  • サービスアカウントとRBAC:
    Job内のコンテナがKubernetes APIと対話する必要がある場合(例: 他のKubernetesリソースの作成/削除、クラスタ情報の取得)、適切な権限を持つServiceAccountをPodに割り当てる必要があります。
    “`yaml
    # my-job-serviceaccount.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
    name: my-job-sa

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
    name: pod-reader
    rules:

    • apiGroups: [“”] # “” は core API group を意味する
      resources: [“pods”]
      verbs: [“get”, “list”]

    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: read-pods-for-my-job
    subjects:
    – kind: ServiceAccount
    name: my-job-sa
    namespace: default # サービスアカウントが属するネームスペース
    roleRef:
    kind: Role
    name: pod-reader
    apiGroup: rbac.authorization.k8s.io


    my-job-with-rbac.yaml

    apiVersion: batch/v1
    kind: Job
    metadata:
    name: my-api-access-job
    spec:
    template:
    spec:
    serviceAccountName: my-job-sa # 作成したサービスアカウントを指定
    containers:
    – name: api-client
    image: my-custom-api-client:1.0
    command: [“/app/get_pod_info.sh”]
    restartPolicy: Never
    ``
    この例では、
    my-job-saというサービスアカウントを作成し、pod-reader`ロール(Podの取得・リスト権限)を割り当て、そのサービスアカウントをJobのPodに紐づけています。これにより、Job内で実行されるコンテナは、必要なKubernetes API操作を実行できるようになります。

  • リソース制限 (requestslimits):
    JobのPodが消費できるCPUやメモリの量を適切に設定することは、クラスタの安定性と効率性を保つ上で非常に重要です。
    yaml
    # ...
    spec:
    template:
    spec:
    containers:
    - name: heavy-compute-job
    image: compute-intensive-image:1.0
    resources:
    requests: # この量は必ず割り当てることを要求
    memory: "512Mi"
    cpu: "500m" # 0.5 CPUコア
    limits: # この量を超えて使用しないことを保証
    memory: "1Gi"
    cpu: "1" # 1 CPUコア
    restartPolicy: Never

    • requests: Podが最低限必要とするリソース量。これにより、適切なノードにスケジュールされます。
    • limits: Podが使用できるリソース量の上限。これを超えるとOOMKilled(メモリ)やCPUスロットリング(CPU)が発生します。
  • NodeSelector/Affinity/Tolerations:
    特定のラベルが付与されたノードでJobを実行したい場合や、特定のテイントがあるノードを許容したい場合に利用します。
    yaml
    # ...
    spec:
    template:
    spec:
    nodeSelector: # 特定のノードラベルを持つノードに限定
    disktype: ssd
    tolerations: # 特定のテイントを持つノードを許容
    - key: "special-workload"
    operator: "Exists"
    effect: "NoSchedule"
    affinity: # より高度なスケジューリング制御
    nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchExpressions:
    - key: kubernetes.io/arch
    operator: In
    values:
    - amd64
    containers:
    - name: specific-node-job
    image: my-special-job:1.0
    restartPolicy: Never

    これにより、特定のハードウェアを持つノードや、他のワークロードから分離されたノードでJobを実行できます。

  • Init Containers:
    メインコンテナが起動する前に、事前準備タスク(設定ファイルのダウンロード、データベースの初期化、外部依存関係のチェックなど)を実行したい場合にInit Containerを利用します。
    yaml
    # ...
    spec:
    template:
    spec:
    initContainers:
    - name: init-db-check
    image: busybox
    command: ["sh", "-c", "until nc -z db-service 5432; do echo waiting for db; sleep 2; done;"]
    containers:
    - name: my-main-job
    image: my-db-client:1.0
    command: ["/app/run_db_query.sh"]
    restartPolicy: Never

    Init Containerはメインコンテナの前に順番に実行され、すべてのInit Containerが正常終了しない限り、メインコンテナは起動しません。

4. Jobのライフサイクルと管理

Jobをデプロイした後、その状態を監視し、必要に応じて操作する方法を学びます。

4.1 Jobの作成

定義したYAMLファイルを使用してJobを作成します。
bash
kubectl apply -f my-simple-job.yaml
job.batch/my-simple-job created

4.2 Jobの監視

Jobの実行状況を把握するために、以下のコマンドを使用します。

  • Jobの一覧表示:
    bash
    kubectl get job
    NAME COMPLETIONS DURATION AGE
    my-simple-job 1/1 10s 10s

    • COMPLETIONS: 成功したPodの数/必要な完了数。1/1は1つのPodが正常に完了したことを示します。
    • DURATION: Jobの実行にかかった時間。
    • AGE: Jobが作成されてからの時間。
  • Jobの詳細情報の表示:
    bash
    kubectl describe job my-simple-job
    Name: my-simple-job
    Namespace: default
    Selector: batch.kubernetes.io/controller-uid=c89b2754-db27-4632-a27b-586b515a4521
    Labels: <none>
    Annotations: <none>
    Parallelism: 1
    Completions: 1
    Start Time: Mon, 23 Oct 2023 10:00:00 +0900
    Completed At: Mon, 23 Oct 2023 10:00:10 +0900
    Duration: 10s
    Pods Statuses: 0 Running, 1 Succeeded, 0 Failed
    Events:
    Type Reason Age From Message
    ---- ------ ---- ---- -------
    Normal SuccessfulCreate 11s job-controller Created pod: my-simple-job-f7g5h
    Normal Completed 1s job-controller Job completed

    describeコマンドは、Jobの設定、現在のステータス(成功、失敗、実行中Podの数)、および関連イベントなど、非常に詳細な情報を提供します。特にEventsセクションは、Jobがどのように進行したか、または何が問題を引き起こしたかを理解するのに役立ちます。

  • Jobに関連するPodの表示:
    Jobは内部的にPodを作成します。Jobに関連するPodは通常、job-name-<random-hash>のような命名規則に従います。
    bash
    kubectl get pod -l job-name=my-simple-job
    NAME READY STATUS RESTARTS AGE
    my-simple-job-f7g5h 0/1 Completed 0 1m

    -l job-name=<job-name>というラベルセレクタを使用して、特定のJobに関連するPodをフィルタリングできます。

  • Podのログの確認:
    Jobの実行中に何が起こっているか、または何が失敗の原因であるかを確認するには、Podのログを調べます。
    bash
    kubectl logs my-simple-job-f7g5h
    Hello from Kubernetes Job!
    Job finished successfully!

    Podがまだ実行中の場合、ログはリアルタイムでストリーミングされます。完了したPodのログも確認できます。

4.3 Jobの完了状態

Jobは、以下のいずれかの状態で完了します。

  • 成功 (Succeeded):
    completionsで指定された数のPodが正常に終了した場合(コンテナが終了コード0で終了)。kubectl get jobCOMPLETIONSカラムがX/Yのように表示され、XYに達するとJobはSucceededとマークされます。

  • 失敗 (Failed):
    backoffLimitで指定された再試行回数を超えてPodが失敗し続けた場合、またはactiveDeadlineSecondsを超過した場合。kubectl get jobCOMPLETIONSカラムが0/1などとなり、kubectl describe jobPods StatusesX Failedと表示され、EventsFailedの理由が記録されます。

Jobが成功すると、それ以上新しいPodは起動されません。Jobオブジェクトと関連するPodは、ttlSecondsAfterFinishedが設定されていれば指定時間後に自動削除されます。設定されていない場合は手動で削除する必要があります。

4.4 Jobの削除

Jobオブジェクトを削除すると、それに関連するすべてのPodも自動的に削除されます。
bash
kubectl delete job my-simple-job
job.batch "my-simple-job" deleted

もしJobオブジェクトを残したまま関連Podだけを削除したい場合は、Podを直接削除できますが、Jobコントローラが新しいPodを起動しようとすることがあるため、あまり推奨されません。通常はJob全体を削除します。

4.5 失敗したJobの再実行

Jobが失敗した場合、以下の方法で再実行できます。

  1. Jobを削除し、再作成する:
    最も一般的な方法です。失敗したJobを削除し、同じYAMLファイルを使用して再度kubectl apply -f <job.yaml>を実行します。これにより、新しいJobオブジェクトとPodが作成され、最初から実行されます。
    bash
    kubectl delete job my-failed-job
    kubectl apply -f my-failed-job.yaml # 修正後のYAMLであるべき
  2. backoffLimitに依存する:
    一時的なネットワーク障害や外部サービスのダウンなどの一時的な問題が原因で失敗した場合、backoffLimitが設定されていればKubernetesが自動的に再試行します。この場合、手動で介入する必要はありません。ただし、コードのバグや設定ミスなど、根本的な原因がある場合は、YAMLファイルを修正してから再作成する必要があります。

5. 発展的なトピック

5.1 CronJob: 定期的なタスクの自動化

Jobは一度きりのタスクに適していますが、毎週、毎日、毎時間など定期的にタスクを実行したい場合は、CronJobオブジェクトを使用します。CronJobは、CRON形式のスケジュールに基づいてJobオブジェクトを自動的に作成します。

“`yaml

my-cronjob.yaml

apiVersion: batch/v1
kind: CronJob
metadata:
name: my-daily-backup
spec:
schedule: “0 2 * * *” # 毎日午前2時に実行 (UTC)
jobTemplate: # この中にJobのspecを定義
spec:
template:
spec:
containers:
– name: backup-container
image: my-backup-tool:1.0
command: [“/app/run_backup.sh”]
restartPolicy: OnFailure
successfulJobsHistoryLimit: 3 # 成功したJobの履歴を3つ保持
failedJobsHistoryLimit: 1 # 失敗したJobの履歴を1つ保持
``CronJob`はJobの高度な利用形態であり、定期的なバッチ処理の自動化に不可欠です。

5.2 Jobのデバッグとトラブルシューティング

Jobが期待通りに動作しない場合、以下の手順でトラブルシューティングを行います。

  1. Jobのステータスを確認:
    bash
    kubectl get job <job-name>

    COMPLETIONSカラムが0/1のままだったり、Failedと表示されていないか確認します。

  2. Jobの詳細情報を確認:
    bash
    kubectl describe job <job-name>

    • Pods Statuses: 実行中、成功、失敗したPodの数を確認。
    • Events: Podの作成、スケジューリング、開始、終了に関するイベントを確認。特にFailedError関連のメッセージに注目します。
  3. 関連Podの一覧とステータスを確認:
    bash
    kubectl get pod -l job-name=<job-name>

    • STATUS: PendingRunningErrorCrashLoopBackOffCompletedなどを確認します。
    • RESTARTS: コンテナの再起動回数。何度も再起動している場合、コンテナ内部でエラーが発生している可能性が高いです。
  4. Podのイベントと詳細情報を確認:
    特定のPodのステータスがPendingCrashLoopBackOffの場合、そのPodの詳細イベントを確認します。
    bash
    kubectl describe pod <pod-name>

    Eventsセクションで、スケジューリングの失敗(リソース不足、NodeSelectorのミスマッチなど)、イメージのプル失敗、コンテナのクラッシュ理由などが示唆されている場合があります。

  5. Podのログを確認:
    これが最も重要です。コンテナが何を実行しようとしていたのか、そしてなぜ失敗したのかの直接的な手掛かりが得られます。
    bash
    kubectl logs <pod-name>
    kubectl logs <pod-name> -c <container-name> # 複数のコンテナがある場合
    kubectl logs <pod-name> --previous # 以前のコンテナインスタンスのログ

    ログは、アプリケーションレベルのエラー(例えば、データベース接続の失敗、ファイルが見つからない、スクリプトのエラー)を特定するのに役立ちます。

一般的なトラブルシューティングのシナリオ:

  • PodがPendingのまま:

    • 原因: リソース不足(CPU, メモリ)、ノードアフィニティ/セレクタのミスマッチ、テイントとトレラレーションの問題、PVCが見つからない/マウントできない。
    • 解決策: kubectl describe pod <pod-name>Eventsセクションで、スケジューリング失敗の理由を確認。リソースを調整するか、ノードのラベル/テイントを確認。
  • PodがCrashLoopBackOff:

    • 原因: コンテナ内のアプリケーション/スクリプトがエラー終了(非ゼロの終了コード)している。イメージが見つからない、コマンドが間違っている、依存するサービスに接続できない、設定ミス。
    • 解決策: kubectl logs <pod-name>でログを確認。ほとんどの場合、ここで根本原因が判明します。イメージ名、コマンド/引数、環境変数、マウントパスなどを再確認。
  • JobがFailedとマークされる:

    • 原因: backoffLimitで設定された再試行回数をPodの失敗が超えた。activeDeadlineSecondsで設定された実行時間制限を超過した。
    • 解決策: kubectl describe job <job-name>で原因を確認。失敗しているPodのログを詳しく調べる。必要に応じてJobのYAMLを修正し、再デプロイ。

5.3 Jobのベストプラクティス

Kubernetes Jobを効果的に利用するためのベストプラクティスを以下に示します。

  1. 冪等性の確保:
    Jobのタスクは、何度実行しても同じ結果が得られるように(冪等性を持つように)設計することが重要です。Jobは失敗時に再試行されるため、タスクが冪等でない場合、重複した処理やデータ破損を引き起こす可能性があります。例えば、データベースへのデータ挿入は、既に存在する場合はスキップするようなロジックを含めるべきです。

  2. 標準出力へのログ出力:
    コンテナ内のアプリケーションやスクリプトは、ログを標準出力(stdout)と標準エラー出力(stderr)に出力するようにしてください。これにより、kubectl logsコマンドで容易にログを収集・確認でき、Kubernetesのロギングシステム(Fluentd, Loki, ELKなど)によって中央集約されたログ管理システムに転送されやすくなります。

  3. 適切な終了コードの利用:
    コンテナ内のアプリケーションは、成功時には終了コード0を返し、失敗時には非ゼロの終了コードを返すように設計してください。Kubernetes Jobコントローラは、この終了コードに基づいてPodが成功したか失敗したかを判断します。

  4. 短命なタスクの推奨:
    Jobは短時間で完了するタスクに適しています。非常に長い時間(数時間以上)かかるタスクの場合、Podの再起動やノードの障害によって中断されるリスクが高まります。このような場合は、長時間実行されるタスク向けに設計された別のシステム(例: Argo Workflows, KubeFlowなど)の利用も検討してください。

  5. リソース要求の適切な設定:
    resources.requestsresources.limitsを適切に設定することで、クラスタのリソース効率を最大化し、Jobが不必要に大量のリソースを消費したり、他のワークロードに影響を与えたりするのを防ぎます。テスト実行でJobが必要とするリソースを見積もりましょう。

  6. ttlSecondsAfterFinishedの活用:
    完了したJobオブジェクトやPodがクラスタに残り続けないよう、ttlSecondsAfterFinishedを設定して自動削除を有効にしましょう。これにより、クラスタの状態がクリーンに保たれ、不要なリソースの消費を防ぎます。

  7. 専用のサービスアカウントの利用:
    JobがKubernetes APIと対話する必要がある場合、最小限の権限を持つ専用のServiceAccountを作成し、RoleRoleBindingを通じて必要な権限のみを付与してください。これにより、セキュリティリスクを最小限に抑えます。

  8. コンテナイメージの軽量化:
    Jobに使用するコンテナイメージは、必要なツールやライブラリのみを含むようにできるだけ軽量化しましょう。これにより、イメージのプル時間を短縮し、Jobの起動を高速化できます。Alpine Linuxベースのイメージなどが適しています。

6. 実践例:データマイグレーションJobの作成と実行

ここでは、最も一般的なJobのユースケースの一つであるデータベースのデータマイグレーションを例に、具体的なJobの作成から実行までの手順を解説します。

シナリオ: 既存のデータベースに新しいテーブルを追加し、初期データを投入する。

このシナリオでは、PythonとSQLAlchemyを使ったシンプルなマイグレーションスクリプトをJobとして実行します。

  1. マイグレーションスクリプトの準備:
    migration_script.pyという名前で以下のPythonスクリプトを作成します。
    “`python
    # migration_script.py
    import os
    import sys
    from sqlalchemy import create_engine, text
    from sqlalchemy.exc import OperationalError
    import time

    DB_HOST = os.getenv(‘DB_HOST’, ‘localhost’)
    DB_PORT = os.getenv(‘DB_PORT’, ‘5432’)
    DB_USER = os.getenv(‘DB_USER’, ‘user’)
    DB_PASSWORD = os.getenv(‘DB_PASSWORD’, ‘password’)
    DB_NAME = os.getenv(‘DB_NAME’, ‘mydatabase’)

    DATABASE_URL = f”postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}”

    print(f”Attempting to connect to database: {DB_HOST}:{DB_PORT}/{DB_NAME}”)

    データベース接続の待機(Init Containerの代わりとしても機能)

    max_retries = 10
    retry_delay = 5 # seconds
    for i in range(max_retries):
    try:
    engine = create_engine(DATABASE_URL)
    with engine.connect() as connection:
    connection.execute(text(“SELECT 1”))
    print(“Successfully connected to the database.”)
    break
    except OperationalError as e:
    print(f”Connection attempt {i+1}/{max_retries} failed: {e}”)
    if i < max_retries – 1:
    print(f”Retrying in {retry_delay} seconds…”)
    time.sleep(retry_delay)
    else:
    print(“Max retries reached. Exiting.”)
    sys.exit(1)
    except Exception as e:
    print(f”An unexpected error occurred during connection: {e}”)
    sys.exit(1)

    マイグレーションロジック

    try:
    with engine.connect() as connection:
    connection.begin() # トランザクションを開始

        print("Creating 'products' table if not exists...")
        connection.execute(text("""
            CREATE TABLE IF NOT EXISTS products (
                id SERIAL PRIMARY KEY,
                name VARCHAR(255) NOT NULL,
                price DECIMAL(10, 2) NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            );
        """))
        print("'products' table created/verified.")
    
        print("Inserting initial data into 'products' table...")
        # 冪等性を確保するため、重複挿入を避けるロジック
        # ON CONFLICT DO NOTHING はPostgreSQL固有
        insert_data = [
            {"name": "Laptop", "price": 1200.00},
            {"name": "Mouse", "price": 25.00},
            {"name": "Keyboard", "price": 75.00}
        ]
        for item in insert_data:
            res = connection.execute(text("""
                INSERT INTO products (name, price) VALUES (:name, :price)
                ON CONFLICT (name) DO NOTHING;
            """), item)
            if res.rowcount > 0:
                print(f"  Inserted: {item['name']}")
            else:
                print(f"  Skipped (already exists): {item['name']}")
    
        connection.commit() # トランザクションをコミット
        print("Data migration completed successfully.")
        sys.exit(0) # 成功終了
    

    except Exception as e:
    print(f”An error occurred during migration: {e}”)
    connection.rollback() # エラー発生時はロールバック
    sys.exit(1) # 失敗終了
    “`

  2. Dockerイメージの作成:
    Dockerfileを作成し、上記のPythonスクリプトと必要なライブラリをコンテナイメージにパッケージングします。
    “`dockerfile
    # Dockerfile
    FROM python:3.9-slim-buster

    WORKDIR /app

    COPY requirements.txt .
    RUN pip install –no-cache-dir -r requirements.txt

    COPY migration_script.py .

    CMD [“python”, “migration_script.py”]
    `requirements.txt`ファイル:
    SQLAlchemy==1.4.39
    psycopg2-binary==2.9.3 # PostgreSQL接続用
    イメージをビルドしてDocker Hubなどにプッシュします(例: `your-dockerhub-user/db-migration-job:1.0`)。bash
    docker build -t your-dockerhub-user/db-migration-job:1.0 .
    docker push your-dockerhub-user/db-migration-job:1.0
    “`

  3. データベース接続情報のSecretの作成:
    データベースのユーザー名とパスワードをSecretとしてKubernetesに保存します。
    bash
    kubectl create secret generic db-credentials \
    --from-literal=username=your_db_user \
    --from-literal=password=your_db_password

  4. Job YAMLファイルの作成:
    db-migration-job.yamlという名前で以下のJob定義を作成します。DB_HOSTはKubernetesクラスタ内のデータベースサービスのDNS名(例: your-db-service-name.your-namespace.svc.cluster.local)に置き換えてください。
    yaml
    # db-migration-job.yaml
    apiVersion: batch/v1
    kind: Job
    metadata:
    name: db-migration-job-v1
    labels:
    app: db-migration
    spec:
    # Jobは一度成功したら再実行しない(デフォルト)
    # backoffLimit: 3 # 必要に応じて再試行回数を設定
    template:
    metadata:
    labels:
    app: db-migration-pod
    spec:
    # Job実行前にデータベースが利用可能になるまで待機するInit Container
    initContainers:
    - name: wait-for-db
    image: busybox:1.36
    command: ["sh", "-c", "until nc -z ${DB_HOST} ${DB_PORT}; do echo waiting for db connection; sleep 2; done;"]
    env:
    - name: DB_HOST
    value: "my-postgresql-service" # データベースサービスのKubernetes DNS名に置き換える
    - name: DB_PORT
    value: "5432"
    containers:
    - name: migration-container
    image: your-dockerhub-user/db-migration-job:1.0 # 上記で作成したイメージ
    env:
    - name: DB_HOST
    value: "my-postgresql-service" # データベースサービスのKubernetes DNS名に置き換える
    - name: DB_PORT
    value: "5432"
    - name: DB_NAME
    value: "mydatabase"
    - name: DB_USER
    valueFrom:
    secretKeyRef:
    name: db-credentials
    key: username
    - name: DB_PASSWORD
    valueFrom:
    secretKeyRef:
    name: db-credentials
    key: password
    resources:
    requests:
    memory: "64Mi"
    cpu: "100m"
    limits:
    memory: "128Mi"
    cpu: "200m"
    restartPolicy: OnFailure # マイグレーションスクリプトがエラーで終了した場合、Podを再起動
    ttlSecondsAfterFinished: 3600 # Job完了後1時間で自動削除

  5. Jobの実行:
    bash
    kubectl apply -f db-migration-job.yaml
    job.batch/db-migration-job-v1 created

  6. 実行状況の監視:
    bash
    kubectl get job db-migration-job-v1
    # COMPLENTIONSが 0/1 -> 1/1 となることを確認
    NAME COMPLETIONS DURATION AGE
    db-migration-job-v1 0/1 2s 2s
    # ... 数秒後 ...
    db-migration-job-v1 1/1 30s 32s

    Podの名前を確認し、ログを監視します。
    “`bash
    kubectl get pod -l job-name=db-migration-job-v1
    # NAME READY STATUS RESTARTS AGE
    # db-migration-job-v1-abcde 0/1 Completed 0 45s

    kubectl logs db-migration-job-v1-abcde

    Output from your migration_script.py

    Attempting to connect to database: my-postgresql-service:5432/mydatabase

    Successfully connected to the database.

    Creating ‘products’ table if not exists…

    ‘products’ table created/verified.

    Inserting initial data into ‘products’ table…

    Inserted: Laptop

    Inserted: Mouse

    Inserted: Keyboard

    Data migration completed successfully.

    ``
    ログで「Data migration completed successfully.」と表示され、Jobのステータスが
    1/1`(Completed)になっていれば成功です。

  7. 成功後の確認:
    データベースに接続し、productsテーブルが作成され、データが挿入されていることを確認します。
    (例: psql -h my-postgresql-service -U your_db_user -d mydatabase -c "SELECT * FROM products;"

この実践例は、データベースマイグレーションという具体的な課題に対して、Kubernetes Jobがいかに強力なツールであるかを示しています。Init Containerによる依存サービス待機、Secretによる機密情報の安全な注入、そしてrestartPolicyによる信頼性の確保といった、実際の運用で必要となる要素を網羅しています。

まとめ

Kubernetes Jobは、一度きりのタスクやバッチ処理をKubernetesクラスタ上で確実かつ効率的に実行するための強力なプリミティブです。DeploymentReplicaSetが常に稼働し続けるサービスを管理するのに対し、Jobはタスクの「完了」を主な目標とします。

この記事では、Jobの基本的な概念から、その設定オプション(backoffLimit, completions, parallelism, activeDeadlineSeconds, ttlSecondsAfterFinished)、そして環境変数、永続ストレージ、サービスアカウントなどのより高度な設定までを網羅的に解説しました。さらに、Jobのライフサイクル管理、監視、トラブルシューティングのヒント、そしてベストプラクティスを通じて、Jobを本番環境で安全かつ確実に運用するための知識を提供しました。

データ移行、バッチ処理、レポート生成、CI/CDの一部としてのテスト実行など、多岐にわたるユースケースでJobは強力な味方となります。適切に設定・管理することで、Kubernetesクラスタのリソースを有効活用し、安定した運用を実現できるでしょう。Kubernetesの進化と共に、Jobもまた重要なワークロードタイプとしてその重要性を増していくに違いありません。


総語数: 約5100語

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール