Kubernetes上にCrossplaneをデプロイ:GitOpsによる統合クラウドコントロールプレーンの構築

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

アプローチの比較:従来のIaCパイプライン vs. Kubernetesネイティブなコントロールプレーン

6か月前、私たちのプラットフォームエンジニアリングチームは、従来のインフラストラクチャワークフローで限界に直面しました。多くの現場と同様に、私たちもJenkinsとGitHub Actionsで管理されたTerraformに依存していました。アプリケーションマニフェストはGitで管理され、Argo CDを介してKubernetesクラスターに同期されていましたが、S3バケット、RDSデータベース、IAMロールといったクラウドの依存関係は、分離されたCIジョブに取り残されたままでした。

イベント駆動型のCIパイプラインによるインフラ管理は、破綻するまでは機能します。しかし、継続的なコントロールループを稼働させることは、まったく異なる運用モデルです:

  • 実行モデル: Terraformは特定時点(ポイントインタイム)の実行に依存しています。パイプラインはプルリクエストのマージ時にterraform planおよびapplyを実行します。もしエンジニアがその10分後にAWSコンソールでセキュリティグループを緩和した場合、そのドリフト(設定の乖離)は次回のデプロイまで気付かれないまま放置されます。一方、Crossplaneは継続的に動作します。Kubernetesのカスタムリソース定義(CRD)とコントローラーをベースに構築されており、60秒ごとにクラウドプロバイダーのAPIをポーリングして、稼働中のインフラをGitで宣言された状態へと自動的に引き戻します。
  • APIおよびアクセスの統一性: 従来の構成では、開発者はKubernetesマニフェストと並行して、HCL構文、クラウドCLI、AWS IAMロールを使い分ける必要がありました。CrossplaneはすべてをKubernetes APIの標準規格のもとに統合します。Amazon RDSインスタンスやS3バケットも、ネイティブなDeploymentやServiceとまったく同様に扱えます。
  • GitOpsとの統合: Argo CDやFluxを介してTerraformを実行するには、扱いづらいラッパースクリプトやtf-controllerのようなランナーポッドが必要です。Crossplaneはそのレイヤーを完全に排除します。クラウドリソースが標準的なKubernetesマニフェストとなるため、既存のPRレビュープロセスを通じて、GitOpsエンジンがクラウドアセットの監視、差分検出、同期を行えます。
  • シークレット管理: Terraformの出力をポッドに連携させるには、多くの場合、Vault同期ジョブ、AWS Secrets Managerオペレーター、bashスクリプトといったカスタムのグルー(接着剤)コードが必要でした。Crossplaneはこれを直接処理します。エンドポイント、ユーザー名、生成された認証情報をネイティブなKubernetesのSecretオブジェクトへ自動的に書き出し、ポッドから即座に利用できる状態にします。

本番環境で6か月運用して分かったメリットとデメリット

インフラをKubernetesへ移行したことで、35人のエンジニアリングチーム全体においてデータベースのプロビジョニング時間が45分から3分未満に短縮されました。開発者はチケットを発行する代わりに、わずか15行のプルリクエストを投げるだけでインフラをリクエストできるようになりました。それでもなお、Kubernetesをインフラのコントロールプレーンに変革することには固有のトレードオフが伴います。ここでは、6か月の本番トラフィック運用から得られた知見を紹介します。

メリット

  • 自動化されたドリフト修正: Crossplaneは1分ごとにクラウドAPIに問い合わせを行います。先日のインシデント時、あるエンジニアがAWSコンソール上でRDSパラメータグループを手動で調整しましたが、Crossplaneは60秒以内にその乖離を検知し、Gitのベースライン状態へとロールバックしました。
  • カスタムセルフサービスAPI: 複合リソース定義(Composite Resource Definitions: XRD)により、シニアエンジニアは複雑なトポロジーをクリーンな抽象化層としてパッケージ化できます。私たちはVPC、プライベートサブネット、セキュリティグループ、AuroraクラスターをAppDatabase CRDへとまとめました。プロダクトチームは低レベルのネットワーキングに触れることなく、ストレージ容量とエンジンバージョンを設定するだけで済みます。
  • 統一されたRBACとポリシーガード: 12のAWSアカウントにわたって個別に権限を管理する代わりに、セキュリティチームはKubernetes RBACでアクセスを定義しています。Kyvernoポリシーが提出されたインフラ定義を検証し、CrossplaneがAWSを呼び出す前に、暗号化されていないストレージやパブリックエンドポイントをブロックします。

課題・デメリット

  • メモリフットプリントが大きい: Upjetベースのプロバイダーは、何百ものクラウドAPIに対応する包括的なスキーマを読み込みます。当初、完全なprovider-awsスイートをデプロイした際、そのコントローラーは3.4 GBものRAMを消費しました。スコープを絞ったプロバイダーを採用することで負荷は軽減されますが、依然としてコントローラーには専用のノードキャパシティが必要です。
  • トラブルシューティングの学習曲線が急峻: IAMの設定ミスによってプロビジョニングが失敗した際、ターミナルにわかりやすい差分(diff)は出力されません。エラーはkubectl describeのイベント、リソースのステータスブロック、またはプロバイダーコントローラーのログの中に隠れています。Terraformの明瞭なplan出力に慣れ親しんだ開発者が適応するには時間がかかります。
  • ネームスペース削除の危険性: kubectl delete namespaceを実行すると、そのネームスペース内のすべての管理対象リソースに対して削除がカスケード(連鎖)します。保護策を講じていない場合、CrossplaneはAWS上の対応する本番データベースやストレージバケットまで削除してしまいます。重要なリソースにはdeletionPolicy: Orphanを設定し、アドミッションWEBフックでそれを強制する必要があります。

本番環境向けの推奨構成

Crossplaneコントローラーをお客様のワークロードと同じ場所で稼働させると、ノイジーネイバー問題が発生し、セキュリティ上の影響範囲(ブラストレイディウス)が広がってしまいます。以下の4つの本番向けガイドラインに従ってください:

  1. 専用の管理クラスター: Crossplaneは、分離された軽量なコントロールクラスターで運用します(私たちは3台のt4g.largeノードを使用しています)。このクラスターがCrossplane、Argo CD、シークレットオペレーターをホストし、セキュリティが確保された単一のハブからステージング環境および本番環境アカウントのクラウドリソースをオーケストレーションします。
  2. きめ細かくスコープを絞ったプロバイダー: モノリシックなプロバイダーパッケージは避けましょう。provider-aws-s3やprovider-aws-rdsのような対象を絞ったモジュールをデプロイしたことで、クラスターのCRD数は900以上から68へと減少し、APIサーバーのメモリ使用量を約60%削減できました。
  3. ワークロードアイデンティティによる認証: クラスター内のSecretに長期的なクラウド認証情報をマウントしてはいけません。AWS IAM Roles for Service Accounts (IRSA) やGCP Workload Identityを使用してください。ポッドは短命のOIDCトークンを一時的なクラウドアクセス権限と交換し、自動的にローテーションされます。
  4. Orphan削除ポリシーの徹底: 本番データベース、VPC、ストレージバケットにはspec.deletionPolicy: Orphanを設定します。万が一誰かが誤ってKubernetesマニフェストを削除しても、手動で対応するまでAWS内の基盤となるクラウドリソースは維持されます。

実践ガイド:Crossplaneによるクラウドリソースのプロビジョニング

このチュートリアルでは、Kubernetesクラスター上にCrossplaneをセットアップし、IRSAを使用してモジュール化されたAWS S3プロバイダーを設定した上で、GitOpsを介してS3バケットをプロビジョニングします。

ステップ1:HelmによるCrossplaneのインストール

公式のCrossplaneチャートリポジトリを追加し、専用のネームスペースにコアコントローラーをインストールします:

helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update

helm install crossplane crossplane-stable/crossplane \
  --namespace crossplane-system \
  --create-namespace \
  --set args='{"--enable-composition-functions=true"}'

コアコントローラーとRBACマネージャーの両方のポッドがRunningステータスになっていることを確認します:

kubectl get pods -n crossplane-system

ステップ2:スコープ限定版AWS S3プロバイダーのインストール

不要なAWSサービスをメモリに読み込むことなくバケットを管理するために、モジュール化されたS3プロバイダーをデプロイします:

cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws-s3
spec:
  package: xpkg.upbound.io/upbound/provider-aws-s3:v1.3.0
EOF

INSTALLEDとHEALTHYの両方の列にTrueが表示されるまで待ちます:

kubectl get provider provider-aws-s3 -w

ステップ3:ProviderConfigによるクラウド認証の設定

IRSA経由でプロバイダーのServiceAccountにAWS IAMロールARNをアノテーションしたら、ポッド認証を有効にするためのProviderConfigを作成します:

cat <<EOF | kubectl apply -f -
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: aws-provider-config
spec:
  credentials:
    source: InjectedIdentity
EOF

ステップ4:Managed Resourceを介したS3バケットのプロビジョニング

S3バケットをネイティブなKubernetesマニフェストとして宣言します:

cat <<EOF | kubectl apply -f -
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
  name: app-analytics-production-data-2026
  labels:
    environment: production
    managed-by: crossplane
spec:
  forProvider:
    region: us-east-1
  deletionPolicy: Orphan
  providerConfigRef:
    name: aws-provider-config
EOF

ステップ5:同期とドリフト検出の検証

kubectlを使用して管理リソースのステータスを確認します:

kubectl get bucket app-analytics-production-data-2026

出力結果で、2つの重要な健全性指標を確認します:

  • READY: AWS側でのバケットのプロビジョニングが完了するとTrueになります。
  • SYNCED: Crossplaneの調整(reconciliation)ループがリモートのAWSバケットとYAML宣言を正常に同期させるとTrueになります。

詳細な調整ログの確認やIAM権限エラーの調査を行うには、以下を実行します:

kubectl describe bucket app-analytics-production-data-2026

ドリフトの自動修正を実際にテストしてみましょう。AWSコンソールにログインし、手動でタグを追加するかバケットポリシーを変更してみてください。60秒以内にCrossplaneがドリフトを検知し、マニフェストで宣言されたパラメータへと復元します。これで、クラウドインフラもアプリケーションのデプロイとまったく同じGitOpsライフサイクルに乗せることができました。

Share: