GKE + Argo Workflows で構築するセキュアで効率的なMLOpsパイプライン
はじめに
本記事では、Google Kubernetes Engine(GKE)とArgo Workflowsを活用した機械学習パイプラインの実装について、Kubernetes設定の観点から詳しく解説します。
特に以下の技術的なポイントに焦点を当てます:
- Terraformによる Infrastructure as Code
- Workload Identity による安全な認証
- Argo Workflows による ML パイプライン実行
- GKE Spot Instances を活用したコスト最適化
- 永続リソースと一時リソースの分離設計
- RBAC(Role-Based Access Control)の適切な設定
システムアーキテクチャ概要
このシステムは、金融データの分析パイプラインとして以下の構成で実装されています:
リソース分離の設計思想
このアーキテクチャの最大の特徴は、永続リソース(Persistent)と一時リソース(Ephemeral)を完全に分離していることです。
| リソース種別 | 管理場所 | ライフサイクル | 目的 |
|---|---|---|---|
| Persistent | terraform/persistent/ |
長期保持 | データ、認証情報など重要資産 |
| Ephemeral | terraform/ephemeral/ |
実行時のみ | GKEクラスタ、ワークフロー実行環境 |
この設計により、以下のメリットがあります:
- ✅ データを失うことなくクラスタを破棄できる
- ✅ コスト削減(使用時のみGKEクラスタを起動)
- ✅ インフラの再現性が高い
- ✅ 環境の汚染を防ぐ
1. Terraform による永続リソースの管理
1.1 永続リソースの定義
まず、データや認証情報など、絶対に削除してはいけないリソースを定義します。
# GCS Bucket(データストレージ)
resource "google_storage_bucket" "data_bucket" {
name = var.bucket_name
location = "US"
force_destroy = false
uniform_bucket_level_access = true
# 誤削除防止
lifecycle {
prevent_destroy = true
}
}
# BigQuery Dataset(分析用データセット)
resource "google_bigquery_dataset" "dataset" {
dataset_id = var.bq_dataset_name
location = var.region
}
# Service Account(GKEから使用)
resource "google_service_account" "app_sa" {
account_id = "chart-movement-sa"
display_name = "Chart Movement Detection App SA"
}
1.2 IAM 権限の設定
Service Account に必要最小限の権限を付与します。
# BigQuery データ編集権限
resource "google_project_iam_member" "bq_editor" {
project = var.project_id
role = "roles/bigquery.dataEditor"
member = "serviceAccount:${google_service_account.app_sa.email}"
}
# BigQuery ジョブ実行権限
resource "google_project_iam_member" "bq_job_user" {
project = var.project_id
role = "roles/bigquery.jobUser"
member = "serviceAccount:${google_service_account.app_sa.email}"
}
# BigQuery Read Session(大規模データ読み取り用)
resource "google_project_iam_member" "bq_read_session_user" {
project = var.project_id
role = "roles/bigquery.readSessionUser"
member = "serviceAccount:${google_service_account.app_sa.email}"
}
# GCS バケット管理権限
resource "google_storage_bucket_iam_member" "bucket_admin" {
bucket = var.bucket_name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.app_sa.email}"
}
[TIP]
roles/bigquery.readSessionUserは、BigQuery Storage Read API を使用する場合に必要です。大量データを効率的に読み取れます。
1.3 Terraform State の管理
永続リソースの状態は GCS バケットで管理します。
terraform/persistent/backend.tf
terraform {
backend "gcs" {
bucket = "terraform-state-btcusd-analysis"
prefix = "terraform/state-persistent"
}
}
これにより、複数の環境や開発者間で状態を共有できます。
2. GKE クラスタの構成(Ephemeral)
2.1 GKE クラスタの作成
一時リソースとして、実行時のみ起動する GKE クラスタを定義します。
resource "google_container_cluster" "primary" {
name = var.cluster_name
location = var.zone
deletion_protection = false
remove_default_node_pool = true
initial_node_count = 1
# Workload Identity の有効化
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
}
[IMPORTANT]
deletion_protection = falseにより、クラスタを安全に削除できます。これはコスト削減の要です。
2.2 Spot Instances を使用したノードプール
コスト削減のため、GKE Spot Instances(プリエンプティブルVM)を使用します。
resource "google_container_node_pool" "spot_nodes" {
name = "spot-node-pool"
location = var.zone
cluster = google_container_cluster.primary.name
# オートスケーリング設定
autoscaling {
min_node_count = 0 # 未使用時は0台
max_node_count = 5 # 最大5台まで自動拡張
}
node_config {
preemptible = true # Spot Instanceを使用
machine_type = "e2-standard-2" # 2vCPU, 8GB RAM
oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
# Workload Identity の有効化
workload_metadata_config {
mode = "GKE_METADATA"
}
# ノードにラベルを付与(Pod配置制御用)
labels = {
"cloud.google.com/gke-spot" = "true"
}
}
}
Spot Instances のメリット:
- 📉 通常料金の最大91%割引
- 🔄 自動スケーリング(min_node_count = 0)で未使用時のコストゼロ
- ⚡ 短時間のML計算に最適
デメリットと対策:
- ❌ 中断される可能性がある → リトライ戦略で対応(後述)
3. Workload Identity による安全な認証
3.1 Workload Identity とは
Workload Identity は、Kubernetes Service Account と GCP Service Account を紐付ける仕組みです。これにより、Pod から GCP リソースに安全にアクセスできます。
3.2 Kubernetes Service Account の作成
resource "kubernetes_service_account_v1" "workflow_runner" {
metadata {
name = "workflow-runner"
namespace = "argo"
annotations = {
# GCP Service Account との紐付け
"iam.gke.io/gcp-service-account" = data.terraform_remote_state.persistent.outputs.service_account_email
}
}
depends_on = [helm_release.argo_workflows]
}
3.3 IAM Binding の設定
resource "google_service_account_iam_member" "workload_identity_binding" {
service_account_id = "projects/${var.project_id}/serviceAccounts/${data.terraform_remote_state.persistent.outputs.service_account_email}"
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[argo/workflow-runner]"
}
この設定により:
- ✅ JSONキーが不要(セキュリティリスクを削減)
- ✅ 自動ローテーション(トークンは短命)
- ✅ 最小権限の原則を実現
[WARNING]
memberの形式serviceAccount:<PROJECT_ID>.svc.id.goog[<NAMESPACE>/<K8S_SA>]を正確に記述する必要があります。
4. Argo Workflows の構成
4.1 Helm による Argo Workflows のインストール
resource "helm_release" "argo_workflows" {
name = "argo-workflows"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-workflows"
namespace = "argo"
create_namespace = true
version = "0.46.2"
timeout = 1200
# 認証モードの設定
set {
name = "server.extraArgs[0]"
value = "--auth-mode=server"
}
# Controller 用 Spot Instance Toleration
set {
name = "controller.tolerations[0].key"
value = "cloud.google.com/gke-spot"
}
set {
name = "controller.tolerations[0].operator"
value = "Equal"
}
set {
name = "controller.tolerations[0].value"
value = "true"
type = "string"
}
set {
name = "controller.tolerations[0].effect"
value = "NoSchedule"
}
# Server 用 Spot Instance Toleration(同様の設定)
# ...
}
[TIP]
Tolerations を設定することで、Argo の Controller と Server も Spot Instance 上で動作し、さらなるコスト削減が可能です。
4.2 Artifact Repository の設定(GCS)
Argo Workflows では、タスク間でデータを受け渡すために Artifact Repository が必要です。
resource "kubernetes_config_map_v1" "artifact_repositories" {
metadata {
name = "artifact-repositories"
namespace = "argo"
}
data = {
"default-v1" = yamlencode({
gcs = {
bucket = data.terraform_remote_state.persistent.outputs.bucket_name
keyFormat = "artifacts/{{workflow.name}}/{{pod.name}}"
}
})
}
depends_on = [helm_release.argo_workflows]
}
この設定により、ワークフロー間のデータは自動的に GCS に保存されます。
4.3 Workflow 定義
実際の ML パイプラインを定義します。
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: chart-movement-pipeline-
spec:
entrypoint: unified-pipeline
serviceAccountName: workflow-runner
activeDeadlineSeconds: 10800 # 3時間でタイムアウト
# Spot Instance の設定
podGC:
strategy: OnPodSuccess # 成功したPodは削除、失敗したPodは保持
nodeSelector:
cloud.google.com/gke-spot: "true" # Spot Instanceを指定
tolerations:
- key: "cloud.google.com/gke-spot"
operator: "Equal"
value: "true"
effect: "NoSchedule"
templates:
- name: unified-pipeline
dag:
tasks:
- name: ingest-data
template: data-ingestion-container
# Spot Instance 中断時のリトライ戦略
retryStrategy:
limit: "3" # 最大3回リトライ
retryPolicy: "Always" # 常にリトライ
backoff:
duration: "1m" # 初回リトライまで1分待機
factor: "2" # 待機時間を2倍ずつ増加
maxDuration: "10m" # 最大10分まで待機
- name: train-model
dependencies: [ingest-data]
template: genetic-learning-container
arguments:
artifacts:
- name: dataset
from: "{{tasks.ingest-data.outputs.artifacts.dataset}}"
retryStrategy:
limit: "3"
retryPolicy: "Always"
backoff:
duration: "1m"
factor: "2"
maxDuration: "10m"
ポイント解説:
| 設定項目 | 説明 |
|---|---|
serviceAccountName: workflow-runner |
Workload Identity を使用 |
activeDeadlineSeconds: 10800 |
3時間でタイムアウト(コスト暴走を防ぐ) |
nodeSelector |
Spot Instance のみを使用 |
tolerations |
Spot Instance の taint を許容 |
retryStrategy |
Spot 中断時に自動リトライ |
4.4 データ取得タスク
- name: data-ingestion-container
outputs:
artifacts:
- name: dataset
path: /tmp/dataset.pkl # このファイルが自動的にGCSに保存される
container:
image: PLACEHOLDER_IMAGE
imagePullPolicy: IfNotPresent
command: ["uv", "run", "python", "src/data_ingestion.py", "--output-file", "/tmp/dataset.pkl"]
env:
- name: GOOGLE_CLOUD_PROJECT
valueFrom:
secretKeyRef:
name: app-secrets
key: bigquery-project-name
- name: BIGQUERY_DATASET_NAME
valueFrom:
secretKeyRef:
name: app-secrets
key: bigquery-dataset-name
# ...他の環境変数
4.5 モデル学習タスク
- name: genetic-learning-container
inputs:
artifacts:
- name: dataset
path: /tmp/dataset.pkl # 前タスクのArtifactを受け取る
container:
image: PLACEHOLDER_IMAGE
imagePullPolicy: IfNotPresent
command: ["uv", "run", "python", "src/genetic_learning.py", "--input-file", "/tmp/dataset.pkl"]
env:
- name: GOOGLE_CLOUD_PROJECT
valueFrom:
secretKeyRef:
name: app-secrets
key: bigquery-project-name
- name: GCS_BUCKET_NAME
valueFrom:
secretKeyRef:
name: app-secrets
key: gcs-bucket-name
- name: GCS_CHECKPOINT_PATH
value: "data/checkpoints/genetic_state.pkl"
5. RBAC(Role-Based Access Control)の設定
5.1 なぜRBAC設定が必要か
Argo Workflows が正常に動作するには、適切な Kubernetes 権限が必要です。
| 操作 | 必要な権限 | 理由 |
|---|---|---|
| Workflow 実行 |
workflowtaskresults の作成/更新 |
タスク間のデータ受け渡し |
| Pod 監視 |
pods の取得/監視 |
ワークフロー状態の追跡 |
| ログ取得 |
pods/log の取得 |
デバッグ用 |
| Secret 参照 |
secrets の取得 |
環境変数の読み取り |
5.2 Role の定義
resource "kubernetes_role_v1" "workflow_runner" {
metadata {
name = "workflow-runner-role"
namespace = "argo"
}
rule {
api_groups = ["argoproj.io"]
resources = ["workflowtaskresults"]
verbs = ["create", "get", "patch", "delete"]
}
depends_on = [helm_release.argo_workflows]
}
5.3 RoleBinding の設定
resource "kubernetes_role_binding_v1" "workflow_runner" {
metadata {
name = "workflow-runner-binding"
namespace = "argo"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = kubernetes_role_v1.workflow_runner.metadata[0].name
}
subject {
kind = "ServiceAccount"
name = "workflow-runner"
namespace = "argo"
}
depends_on = [kubernetes_service_account_v1.workflow_runner]
}
5.4 追加の RBAC 設定(手動適用)
Argo がすべての機能を使用するには、追加の権限が必要です。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argo-role
namespace: argo
rules:
- apiGroups:
- coordination.k8s.io
resources:
- leases # リーダー選出用
verbs:
- create
- get
- update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- apiGroups:
- argoproj.io
resources:
- workflowtaskresults
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- watch
- patch
- update
- apiGroups:
- ""
resources:
- pods/log
verbs:
- get
適用コマンド:
kubectl apply -f k8s/argo-role-patch.yaml
[NOTE]
この設定は Argo Workflows のデフォルト Role では不足している権限を補完します。
6. Kubernetes Secret の管理
6.1 Terraform による Secret 作成
resource "kubernetes_secret_v1" "app_secrets" {
metadata {
name = "app-secrets"
namespace = "argo"
}
data = {
"bigquery-project-name" = data.terraform_remote_state.persistent.outputs.project_id
"bigquery-dataset-name" = data.terraform_remote_state.persistent.outputs.bq_dataset_name
"bigquery-table-name" = data.terraform_remote_state.persistent.outputs.bq_table_name
"gcs-bucket-name" = data.terraform_remote_state.persistent.outputs.bucket_name
}
depends_on = [helm_release.argo_workflows]
}
Terraform Remote State の活用:
data "terraform_remote_state" "persistent" {
backend = "gcs"
config = {
bucket = var.state_bucket_name
prefix = "terraform/state-persistent"
}
}
これにより、Persistent リソースの情報を Ephemeral リソースから参照できます。
6.2 ローカル開発用の Secret 作成
ローカル環境では、以下のスクリプトで Secret を作成します。
# .env ファイルから Kubernetes 用の .env.k8s を生成
python clean_env.py
# Secret を作成
kubectl create secret generic app-secrets \
--from-env-file=.env.k8s \
-n argo \
--dry-run=client -o yaml | kubectl apply -f -
7. 実際の運用フロー
7.1 初回セットアップ
# 1. 永続リソースのデプロイ(一度だけ実行)
cd terraform/persistent
terraform init
terraform apply
# 2. State Bucket の設定確認
terraform output
7.2 パイプライン実行時
# 1. GKE クラスタの起動
cd terraform/ephemeral
terraform init
terraform apply
# 2. Argo UI へのアクセストークン取得
kubectl get secret argo-ui-admin-token -n argo -o jsonpath='{.data.token}' | base64 -d
# 3. Argo UI にアクセス
kubectl port-forward -n argo svc/argo-workflows-server 2746:2746
# ブラウザで https://localhost:2746 を開く
# 4. ワークフローの実行
argo submit -n argo k8s/workflow.yaml --watch
# 5. ログの確認
kubectl logs -n argo -l workflows.argoproj.io/workflow -f
7.3 実行後のクリーンアップ
# GKE クラスタを破棄してコスト削減
cd terraform/ephemeral
terraform destroy
[IMPORTANT]
terraform destroyでクラスタを削除しても、Persistent リソース(BigQuery、GCS)のデータは保持されます。
8. コスト最適化のまとめ
このアーキテクチャにより、以下のコスト削減を実現しています:
| 施策 | 削減効果 | 詳細 |
|---|---|---|
| Spot Instances | 最大91%削減 | プリエンプティブルVMの利用 |
| オートスケーリング | min=0により未使用時ゼロ | 実行時のみノードを起動 |
| クラスタの完全破棄 | 24時間x30日のコスト削減 | 実行時のみクラスタ起動 |
| リトライ戦略 | Spot中断による再実行コストを最小化 | 効率的なバックオフ |
試算例(東京リージョン):
- 通常のGKE(常時稼働): 約 $150/月
- Spot + 完全破棄: 実行時間が月10時間の場合 約 $5/月
→ 97%のコスト削減
9. セキュリティのベストプラクティス
このシステムで実装しているセキュリティ対策:
✅ 認証・認可
- Workload Identity(JSONキー不要)
- RBAC による最小権限の原則
- Service Account の適切な分離
✅ データ保護
- GCS の Uniform Bucket-Level Access
- BigQuery の IAM 権限制御
- Terraform State の暗号化
✅ 運用
-
prevent_destroyによる誤削除防止 -
activeDeadlineSecondsによるコスト暴走防止 - Terraform による Infrastructure as Code
[CAUTION]
本番環境では、ネットワークポリシーや Pod Security Policy の追加を推奨します。
10. トラブルシューティング
問題1: Workflow が Pending のまま起動しない
原因: RBAC 権限不足
解決策:
kubectl apply -f k8s/argo-role-patch.yaml
問題2: GCS へのアクセスが拒否される
原因: Workload Identity の設定ミス
確認:
# Pod 内から確認
kubectl exec -it <pod-name> -n argo -- gcloud auth list
# 正しい Service Account が表示されるか確認
問題3: Spot Instance が頻繁に中断される
原因: リージョンのキャパシティ不足
解決策:
-
retryStrategyのパラメータを調整 - 複数のゾーンを使用(Zonal → Regional クラスタ)
まとめ
本記事では、GKE と Argo Workflows を活用した MLOps パイプラインの実装について、以下のポイントを解説しました:
-
Terraform による Infrastructure as Code
- Persistent/Ephemeral の分離設計
- Remote State による状態共有
-
Workload Identity による安全な認証
- JSONキー不要のセキュアな設定
- IAM Binding の正確な構成
-
Argo Workflows によるパイプライン実行
- DAG による依存関係の定義
- Artifact Repository による データ受け渡し
-
Spot Instances によるコスト最適化
- 最大97%のコスト削減
- リトライ戦略による信頼性確保
-
RBAC による適切な権限管理
- 最小権限の原則
- Role/RoleBinding の設定
この設計により、セキュアで、コスト効率の高い、再現性のある ML パイプラインを実現できました。