KubernetesでKnativeをデプロイする:ゼロスケール可能なサーバーレスインフラを構築する

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

アイドル状態のクラスターに潜む隠れたコスト

数年前、私は金曜日の午後にKubernetesクラスターの監査を行っていましたが、メトリクスを見るたびに罪悪感に似たものを感じていました。私たちは40以上のマイクロサービスを運用していましたが、Slack通知のWebhookや週次レポートジェネレーターなど、そのほとんどは99%の時間、何もしていなかったのです。実質的に、アイドル状態のコンテナを維持するために、メモリとCPU予約を24時間365日消費し続け、月額600ドルを支払っていました。

この「アイドルPod症候群」は、静かな予算の天敵です。標準的なKubernetesのセットアップでは、リクエストに即座に応答できるよう、通常はサービスごとに少なくとも1つのレプリカを維持します。50の小規模なサービスがあれば、トラフィックがまったくない午前3時であっても, 50個のPodがスペースを占有し続けます。DevOpsチームはやがて、パフォーマンスではなく、単なるキャパシティ(容量)に対して料金を支払っているという壁に突き当たります。

従来のKubernetesオートスケーリングが不十分な理由

標準のHorizontal Pod Autoscaler(HPA)の使用を検討するかもしれませんが、サーバーレスのワークフローにとっては苦戦を強いられることになります。HPAはCPUやメモリの使用率などのメトリクスに基づいてオートスケーリングします。Podがアイドル状態であっても、生存し続けるためには通常64MBや128MBといったベースラインのメモリ予約が必要です。決定的なのは、バニラのHPAはレプリカをゼロにスケールダウンできないことです。なぜなら、空のエンドポイントに新しいリクエストが届いたときに、サービスを「起こす」方法がないからです。

この制限は、Kubernetesのネットワーキングの仕組みそのものに組み込まれています。標準的なServiceは、既存のPodのIPを指す内部ロードバランサーです。Podが存在しない場合、接続は単純に503エラーで失敗します. Kubernetesには、コンテナを立ち上げる間にリクエストをバッファリングするネイティブな「待合室」がありません。このギャップを埋めるのが、まさにKnativeです。

サーバーレスの選択肢を比較する

私が最初にこの問題に取り組んだとき、3つの主要なサーバーレスの選択肢を検討しました:

  • Cloud Functions (AWS Lambda, GCF): これらは見事にゼロにスケールしますが、ベンダーロックインのリスクが非常に大きいです。100個の関数をAWSからAzureに移すのは週末で終わるような作業ではなく、数ヶ月かかる移行の悪夢となります。
  • KEDAを用いたHPA: KEDAを使用してminReplicas: 0を設定できますが、カスタムメトリクスやイベントトリガーの管理により、設定のオーバーヘッドが大幅に増加します。プラットフォームを活用するというより、プラットフォームと戦っているように感じることがよくあります。
  • Knative: これは既存のクラスターの上にリクエスト駆動型の抽象化レイヤーを追加します。トラフィックを傍受することで「Scale-to-Zero」ロジックを自動的に処理するため、コンテナネイティブなチームにとって最もバランスの取れた選択肢となります。

Knativeという解決策:無駄のないパフォーマンス

Knativeは、Dockerの柔軟性を損なうことなくサーバーレス体験を実現する、最も信頼できる方法であることがわかりました。これを開発環境に導入した後、クラスターのオーバーヘッドを約40%削減できました。あらゆるアプリを標準イメージとしてパッケージ化しながら、関数の効率で実行できるようになります。さっそく動かしてみましょう。

進めるには、動作するKubernetesクラスターとkubectlへのアクセス権が必要です。

ステップ 1: Knative Serving CRDのインストール

まず、Knativeが独自のサービスタイプを管理できるようにするカスタムリソース定義(CRD)を適用します。

kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.12.0/serving-crds.yaml

ステップ 2: コアサービングコンポーネントのデプロイ

次に、実際のサーバーレスロジックとPodのライフサイクル管理を担当するコントローラーをインストールします。

kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.12.0/serving-core.yaml

ステップ 3: Kourierによるネットワークの簡素化

Knativeにはトラフィックをルーティングするためのゲートウェイが必要です。Istioは業界標準ですが、小規模なプロジェクトには重すぎることがよくあります。Kourierは、余計なサイドカーなしで仕事をこなす軽量な代替手段です。

kubectl apply -f https://github.com/knative/net-kourier/releases/download/knative-v1.12.0/kourier.yaml

# KourierをデフォルトのIngressとして設定
kubectl patch configmap/config-network \
  --namespace knative-serving \
  --type merge \
  --patch '{"data":{"ingress-class":"kourier.ingress.networking.knative.dev"}}'

ステップ 4: 自動DNSの設定

テストのために、sslip.ioを使用して自動的にDNSを処理します。これにより、新しいサービスをデプロイするたびに/etc/hostsファイルを編集する手間が省けます。

kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.12.0/serving-default-domain.yaml

ゼロにスケールするサービスのデプロイ

いよいよ本番です。KnativeのServiceオブジェクトを使用してWebアプリケーションをデプロイします。これは標準のK8s Serviceとは異なり、デプロイ、ルーティング、スケーリングを1つのリソースに統合したものです。

hello-knative.yamlを作成します:

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: hello-world
  namespace: default
spec:
  template:
    spec:
      containers:
        - image: gcr.io/knative-samples/helloworld-go
          env:
            - name: TARGET
              value: "Knativeエキスパート"
          resources:
            limits:
              cpu: "200m"
              memory: "128Mi"

コマンド1つでデプロイします:

kubectl apply -f hello-knative.yaml

Scale-to-Zeroの魔法を体験する

デプロイが完了したら、クラスターの様子を観察してください:

kubectl get pods -w

初期設定を処理するために、すぐにPodが立ち上がります。そのまま60〜90秒ほど待ってください。PodがTerminating(停止中)になり、やがて消えるのが確認できるはずです。これでサービスはゼロにスケールされ、ワーカーノード上のCPUとRAMの消費量は正確にゼロになりました。

サービスを呼び出すには、URLを取得します:

kubectl get ksvc hello-world

そのURLに対してcurlリクエストを送信してみてください。通常1.5秒から2.5秒ほどの短い遅延が発生します。これが「コールドスタート」です。この間に、KnativeのActivatorが届いたリクエストをバッファリングし、Autoscalerに新しいPodを起動するよう通知します。コンテナが正常な状態になると、リクエストが解放され処理されます。

本番環境での経験から得たTips

Knativeを本番環境で運用して学んだのは、すべてのワークロードにおいてScale-to-Zeroが常に正解とは限らないということです。私の調整方法は以下の通りです:

  • リクエストベースのスケーリング: CPUに基づいてスケーリングする代わりに、同時実行数制限を使用します。autoscaling.knative.dev/target: "10"を追加すると、同時リクエストが11件に達した瞬間にKnativeがPodを追加します。これはWebトラフィックに対してはるかにレスポンシブです。
  • コールドスタートの解消: 2秒の遅延が許容されない優先度の高いAPIでは、autoscaling.knative.dev/min-scale: "1"を設定します。簡素化されたKnativeのデプロイモデルを維持しつつ、サービスを常にウォームな状態に保てます。
  • 厳格なリソース制限: サーバーレスだからといってリソースが無制限というわけではありません。1つのバグのある関数がクラスター全体の予算を食いつぶさないよう、常に制限(limits)を定義してください。

まとめ

Knativeへの切り替えは、私たちのインフラを最適化する上で最も効果的な方法の1一つでした。HPAの閾値を管理するという精神的な負担が解消され、開発者はコードのリリースに集中できるようになります。Scale-to-Zeroを実装することで、クラスターは、本当の仕事があるときだけ稼働するダイナミックなエンジンへと進化します。効率的でポータブル、そしてまさに「ただ動く」のです。

Share: