KubernetesでvClusterをデプロイする方法:開発チーム向けの低コストな仮想クラスタ構築

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

背景と導入理由

木曜日の午前2時14分、PagerDutyのアラート音で叩き起こされました。ステージング環境のAmazon EKSの請求額が、わずか1スプリントで3,200ドルから9,800ドル超へと跳ね上がっていたのです。さらに悪いことに、ジュニアエンジニアが誤って実行したHelmチャートによって、マルチテナントクラスタ全体で共有されていたcert-managerのCustomResourceDefinition(CRD)が削除されてしまいました。瞬時に3つのフィーチャーブランチがダウンし、統合パイプラインは停止、Slackは障害通知で埋め尽くされました。

プラットフォームチームはここで厳しいトレードオフに直面します。20人の開発者それぞれに専用のクラウドクラスタを提供すると、EKSコントロールプレーンの費用だけで月額およそ1,500ドル(1クラスタあたり月73ドル)かかり、さらにアイドル状態のノードによる無駄も発生します。逆に、共有Kubernetesネームスペースに閉じ込めると、クラスタスコープのリソースやカスタムオペレーター、アドミッションウェブフックが必要になった瞬間に破綻します。

ネームスペースが提供できるのは、せいぜい「ソフトテナンシー」に過ぎません。名前の分離やクォータの適用は可能ですが、全員が同じAPIサーバー、CRDレジストリ、グローバルなRBACを共有しています。通常のネームスペースでIstioサービスメッシュのアップグレードをテストしたり、ArgoCDオペレーターを検証しようとすれば、すぐに権限の壁に突き当たることになります。

そこで登場するのが、Loft Labsが開発したvClusterです。仮想クラスタとは、既存のホストクラスタの標準的なネームスペース内で動作する、完全認定されたKubernetesコントロールプレーンです。

これは独自のAPIサーバーとデータストア(デフォルトはSQLite、またはetcd)をプロビジョニングし、通常は軽量なK3sをベースにしています。エンジニアがPodをスケジュールすると、vClusterはランタイムリソース(Pod、Service、PVC)のみを下位のホストノードに同期します。カスタムCRD、ネームスペース、クラスタロールは仮想APIサーバー内に隔離されたまま保持されます。

このアーキテクチャを採用することで、本番外環境のコンピュートコストを通常60%〜70%削減できると同時に、「ノイジーネイバー(うるさい隣人)」によるリスクを完全に解消できます。

インストール

クラウドプロバイダーでマネージドコントロールプレーンを立ち上げるには10〜20分かかります。対照的に、vClusterは約15秒で初期化されます。さっそくCLIをインストールし、最初の分離環境を構成してみましょう。

Linuxでは公式バイナリを取得するか、macOSではHomebrewを使用します(brew install loft-sh/tap/vcluster):

# vCluster CLIバイナリをダウンロードしてインストール(Linux amd64)
curl -L -o vcluster "https://github.com/loft-sh/vcluster/releases/latest/download/vcluster-linux-amd64"
chmod +x vcluster
sudo mv vcluster /usr/local/bin/vcluster

# バイナリの実行確認
vcluster --version

ホストコンテキストが、ネームスペースレベルの権限を持つターゲットクラスタを指していることを確認します:

# 現在のアクティブなホストコンテキストとノードを確認
kubectl config current-context
kubectl get nodes

次に、ホストのteam-alphaネームスペース内にdev-alphaという名前の仮想クラスタを起動します。CLIがネームスペースの作成、コントロールプレーンのデプロイ、ローカルkubeconfigのルーティングを自動的に処理します:

# 仮想クラスタをプロビジョニングし、アクティブなコンテキストを切り替え
vcluster create dev-alpha -n team-alpha --connect

内部的には、この1つのコマンドで次の4つのステップが実行されます:

  1. ターゲットとなるホストネームスペース(team-alpha)が存在しない場合は作成する。
  2. K3s APIサーバー、コントローラーマネージャー、syncerを含む軽量なStatefulSetを起動する。
  3. 新しいコンテキスト(vcluster_dev-alpha_team-alpha_...)をローカルの~/.kube/configに直接追加する。
  4. バックグラウンドでポートフォワードを確立し、ローカルのkubectlコマンドを仮想APIサーバーに直接ルーティングする。

設定

ローカルでの検証であればデフォルト設定で十分です。しかし、本番レベルのステージング環境では、values.yamlファイルを通じて明確なリソース境界と決定論的な同期ルールを宣言する必要があります。

同期ルール、コントロールプレーンのリソース使用量、認証情報の自動エクスポートを管理するために、以下のHelm設定を保存します:

# vcluster-values.yaml
sync:
  nodes:
    enabled: false # ホストノード情報の露出を防ぐためノードをエミュレート
  persistentvolumes:
    enabled: true
  ingresses:
    enabled: true
  services:
    enabled: true

# メモリ消費を最小限に抑えるため軽量なK3sを使用
controlPlane:
  distro:
    k3s:
      enabled: true
      image:
        tag: "v1.30.2-k3s1"

# コントロールプレーンのリソース消費を最小限に抑える(アイドル時RAM約250MB)
resources:
  limits:
    cpu: "1000m"
    memory: "1024Mi"
  requests:
    cpu: "200m"
    memory: "256Mi"

# CI/CDランナー向けに分離されたkubeconfigを自動出力
exportKubeConfig:
  declare:
    path: "./kubeconfig-alpha.yaml"

これらの宣言的設定を使用して仮想クラスタをデプロイまたは更新します:

vcluster create dev-alpha \
  --namespace team-alpha \
  --values vcluster-values.yaml \
  --connect=false

GitLab CI、GitHub Actions、Argoワークフローの自動化では、対話型セッションを必要とせずにスタンドアロンのアクセスキーをエクスポートできます:

# 分離されたクライアント認証情報を専用ファイルに抽出
vcluster connect dev-alpha -n team-alpha --update-current-context=false --kube-config ./dev-alpha.kubeconfig

# 分離された仮想クラスタに直接クエリを実行
kubectl --kubeconfig=./dev-alpha.kubeconfig get namespaces

この環境内で作業するエンジニアは、無制限の管理者権限を持つことができます。隣接するワークロードを危険にさらしたり、物理ホスト上でのcluster-admin権限を必要とすることなく、Knativeのインストール、Prometheusオペレーターのテスト、クラスタロールのバインドなどが可能です。

検証とモニタリング

仮想クラスタを検証するには、変換レイヤー(トランスレーション層)の理解が不可欠です。仮想的な表現が実際の物理コンピュートにどのようにマッピングされているかを確認する必要があります。

仮想コンテキストに接続し、軽量なテスト用デプロイメントを立ち上げます:

# dev-alpha仮想コンテキストに切り替え
vcluster connect dev-alpha -n team-alpha

# カスタム仮想ネームスペース内にテスト用デプロイメントを立ち上げ
kubectl create namespace frontend
kubectl create deployment web --image=nginx:alpine -n frontend --replicas=2

# 仮想ワークロードのステータスを確認
kubectl get pods -n frontend

仮想のfrontendネームスペース内で2つのPodが正常に稼働していることが確認できます。次に、ホストコンテキストに切り替えて、実際の実行状態を確認します:

# ホストクラスタのコンテキストに戻る
kubectl config use-context your-host-cluster-context

# 変換された物理Podを確認
kubectl get pods -n team-alpha

ホストクラスタにはfrontendネームスペースは存在しません。代わりに、dev-alpha-0 StatefulSetと並んでweb-x-frontend-x-dev-alphaという名前のPodが実行されているのが確認できます。syncerがメタデータの衝突を防ぎながら、自動的に名前を変換し、コンテナをホストノードのネットワークにバインドします。

変換のボトルネックや拒否されたリソースをトラブルシューティングするには、syncerコンテナのログを直接確認(tail)します:

# 変換エンジンのログをリアルタイム追跡(tail)
kubectl logs -n team-alpha dev-alpha-0 -c syncer --tail=50 -f

Grafanaダッシュボードで以下の3つの重要なシグナルを追跡してください:

  • Syncerのエラー急増: エラー数の増加は、ホストレベルのPodSecurityポリシーやアドミッションウェブフックが変換されたPodをブロックしていることを示します。
  • ホストネームスペースのクォータ: ホストネームスペースteam-alphaには、必ず標準のResourceQuotaおよびLimitRangeルールを適用してください。これにより、暴走したテストスクリプトによってノードのメモリやCPUが枯渇するのを防ぎます。
  • データストアのレイテンシ: dev-alpha-0にアタッチされたPersistentVolumeのディスクI/Oメトリクスを追跡します。特にSQLiteへの同時書き込みが集中するテストスイートを実行する際は重要です。

機能検証が完了したら、破棄に必要なコマンドは1つだけです:

vcluster delete dev-alpha -n team-alpha

ホストネームスペースは即座にクリーンアップされ、孤立したCRDが残ることもなく、テスト終了と同時にクラウド費用の発生も停止します。

Share: