Garden.ioによるKubernetesインナーループ開発の最適化:高速ビルド、テスト、スマートなリモートキャッシュ

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

複雑なマイクロサービスにおけるKubernetesインナーループのボトルネック

Kubernetes上で直接マイクロサービスを開発するのは、非常に時間がかかることで知られています。例えば、認証コントローラーのコードをたった2行変更したとしましょう。そこからDockerのビルドを待ち、混雑したVPN経由で1.2GBのイメージをプッシュし、Helmチャートを適用して、Podのロールアウトが完了するまでに8分も待たされることになります。このようなフィードバックの遅延は、開発者の集中力を削ぎます。コードの編集から変更内容の確認までの短い反復サイクルである「インナーループ」が、完全に停止してしまうのです。

SkaffoldやTiltはローカルワークフローを大幅に改善しましたが、大規模なエンジニアリングチームでは依然としてスケーリングの壁に直面します。アプリケーションが15〜20個ものマイクロサービスで構成されるようになると、フラットなビルドパイプラインでは対応しきれません。

サービスCに対する統合テストを実行する前に、サービスAがサービスBのデータベースマイグレーションを必要とするような場合、手動のスクリプトによる依存関係の管理はすぐに破綻します。また、重複作業も大きなボトルネックの1つです。5人のエンジニアが同じベースレイヤーのアップデートを取得したり、共有ライブラリを修正したりすると、全員のワークステーションでまったく同じコードがゼロから再ビルド・再テストされてしまいます。

Garden.ioは、スタック全体を有向非巡回グラフ(DAG: Directed Acyclic Graph)として扱い、クラスタ全体で共有されるビルド&テストキャッシュを活用することで、この問題を解決します。

コアコンセプト:Garden.ioによるマイクロサービススタックのモデル化

Gardenはリポジトリをサイロ化された個別の存在として扱うのをやめ、ビルド、デプロイ、テストスイートを以下の3つのコア要素に体系化します。

1. スタックグラフ(DAG)

すべてのサービス、データベースマイグレーション、コンテナビルド、統合テストスイートは、依存関係グラフ内の個別ノードとして実行されます。Gardenは実行パスをオンザフライで計算します。認証サービスのコードを変更した場合、Gardenはそのサービスと直接の依存関係にあるものだけを再ビルドします。請求サービスや通知ワーカーといった無関係なモジュールには一切手を加えません。

2. 暗号論的リモートキャッシュ

Gardenはソースファイル、環境変数、依存関係、設定ツリーのハッシュ値を計算します。生成されたハッシュがチームのリモートクラスタキャッシュやS3バケット内のエントリと一致した場合、Gardenはそのステップを完全にスキップします。mainブランチでチームメンバーがすでにauth-service:2.4.0イメージをビルドし、すべてのテストを通過させていれば、手元のマシンはコンパイラを一度も実行することなく、200ミリ秒未満でその結果を取得します。

3. クラスタ内統合テスト

本番環境の挙動と乖離しやすい脆弱なAPIモックやローカルのSQLite代用品を使う必要はもうありません。Gardenはリモートの開発用ネームスペース内に直接エフェメラルなテストランナーを起動します。実際のPostgreSQL 15インスタンスやRedisクラスタといった本物の依存関係に対してテストが実行されるため、コードがプルリクエストに到達する前にリグレッションバグを検出できます。

実践的なセットアップ:PostgreSQLとクラスタ内テストを備えたNode.js API

ここでは、Node.js Express API、PostgreSQLデータベース、自動統合テストランナーで構成される実用的な3層構造のワークフローを設定してみましょう。

ステップ 1: Garden CLIのインストール

開発用ワークステーションにGarden CLIをインストールします。

# Homebrew経由でのmacOSおよびLinuxへのインストール
brew install garden-io/garden/garden

# または公式インストールスクリプトを使用
curl -sL https://get.garden.io/install.sh | bash

# インストールされたバージョンの確認
garden version

ステップ 2: プロジェクト設定の定義 (project.garden.yml)

ルート設定ファイルを作成し、環境、クラスタプロバイダー、対象ネームスペースを登録します。

kind: Project
apiVersion: garden.io/v1
name: shop-microservices
defaultEnvironment: dev
environments:
  - name: dev
    production: false
providers:
  - name: local-kubernetes
    environments: [dev]
    context: minikube # またはリモートのEKS / GKE開発用コンテキスト
    defaultNamespace: garden-dev

ステップ 3: バックエンドのビルド、デプロイ、ホットリロードの設定 (backend/garden.yml)

backend/garden.yml内で、コンテナビルド、ランタイムマニフェスト、および単方向ファイル同期を定義します。

kind: Build
apiVersion: garden.io/v1
name: backend-image
type: container
spec:
  build:
    timeout: 300

---
kind: Deploy
apiVersion: garden.io/v1
name: backend
type: kubernetes
dependencies:
  - build.backend-image
  - deploy.postgres
spec:
  manifests:
    - apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: backend
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: backend
        template:
          metadata:
            labels:
              app: backend
          spec:
            containers:
              - name: api
                image: ${actions.build.backend-image.outputs.deployment-image-id}
                ports:
                  - containerPort: 3000
                env:
                  - name: DB_HOST
                    value: postgres
                  - name: PORT
                    value: "3000"
  sync:
    paths:
      - source: src/
        target: /app/src
        mode: one-way
        containerName: api

ステップ 4: クラスタ内テストスイートの設定 (tests/garden.yml)

バックエンドと同じKubernetesネームスペース内で実行される統合テストアクションを定義します。

kind: Test
apiVersion: garden.io/v1
name: backend-integration-tests
type: container
dependencies:
  - deploy.backend
spec:
  image: ${actions.build.backend-image.outputs.deployment-image-id}
  args: ["npm", "run", "test:integration"]
  env:
    API_URL: "http://backend.garden-dev.svc.cluster.local:3000"

ステップ 5: インタラクティブな開発ループの開始

ホットリロードを有効にしてインタラクティブな開発セッションを開始します。

garden dev --hot=backend

起動時、Garden CLIは次の5つのステージを実行します。

  1. ソースファイルをスキャンし、暗号論的依存関係ハッシュを生成します。
  2. キャッシュが一致した場合、リモートレジストリからビルド済みコンテナレイヤーを取得します。
  3. データベースのマニフェストを適用し、PostgreSQLが正常に稼働していることを検証します。
  4. バックエンドPodをデプロイし、双方向ファイル同期ウォッチャーを接続します。
  5. クラスタ内で、稼働中のエンドポイントに対して統合テストスイートを実行します。

ここで、backend/src/controllers/user.tsのルートを更新して保存してみてください。Gardenは通常の5分かかるコンテナ再ビルドサイクルをスキップし、変更されたファイルをわずか1.5秒で実行中のPodに直接ストリーミングして、即座にbackend-integration-testsをトリガーします。

SkaffoldとGarden.io:アーキテクチャの比較

機能 Skaffold Garden.io
依存関係のモデリング アーティファクトごとの線形シーケンス サービス間の依存関係解決を備えた完全なDAG
チームでのリモートキャッシュ ローカルのDockerデーモン / 標準のレイヤーキャッシュ ビルドとテストのためのクラスタ全体の暗号論的結果キャッシュ
クラスタ内テスト カスタムのデプロイ前/後フックが必要 DAGに直接統合されたファーストクラスのTestアクション
マルチリポジトリのサポート 外部ソースの参照機能が限定的 ネイティブなリモートソース参照およびサブプロジェクトのインポート

実際のワークフローへのGarden.ioの導入

Gardenを標準として採用することで、オンボーディングがシンプルになります。新しく参加した開発者がローカル環境のスクリプトのデバッグに最初の3日間を費やすことはもうありません。garden devを実行するだけで、数分でアプリケーションスタック全体がセットアップされます。

CI/CDパイプラインも同様のメリットを享受できます。garden testやgarden deployといったコマンドは、ローカルPCとGitHub Actionsランナーの双方で同じDAG定義を使用するため、設定の乖離(Configuration Drift)が発生しません。マイクロサービスのビルド時間が10分を超えているのであれば、共有リモートキャッシュを備えたDAG対応ツールへ移行することで、フィードバック時間を数秒にまで短縮できます。

Share: