はじめに

電気通信大学情報理工学研究科情報・ネットワーク工学専攻1年の平地浩一と申します。

2025年10月1日から2025年12月26日までの約3ヶ月間、CIUのCALBチームにてインターンさせていただきました。

今回は、Cycloudの中でもCycloud Application Load Balancer(CALB)というロードバランサー改良のタスクを担当し、内部で使われているミドルウェアをIstioからEnvoy Gatewayへ移行するための検証と、簡単なコントローラ実装まで行いました。

本記事ではインターン中に取り組んだCALBにおけるEnvoyGatewayへの移行の検証とコントローラ開発について紹介します。

 

背景

CALBはCycloud Load Balancingの一部であるL7ロードバランサーです。CALBは、KubernetesのGateway APIと互換性を持っています。

基本的な機能として、Cycloud 上で構築しているサービスをインターネット上に公開することができます。また特徴として、Identity-Aware Proxy(IAP)の機能を提供しており、サービスを安全にインターネットに公開できるセキュリティ機能を備えています。

 

Gateway APIとは?

CALBはKubernetesのGateway APIと呼ばれる仕様に互換性を持って設計されています。今まではKubernetesで外部にサービスを公開するリソースとして、Ingressが一般的に使われてきました。しかし、IngressではリソースのSpecが管理者とアプリケーション開発者の関心領域を両方含む構造になっているということが課題でした。また、Ingressに存在しない仕様はannotationを用いてProvider独自の命令を記述するような手法が取られており、運用上の課題も残されていました。

こうしたIngressの課題から、新たに設計し直されたものがGateway APIです。Gateway APIでは関心領域ごとにリソースを分離し、さらに拡張性を向上させたものがGateway APIです。

Gateway APIはGateway、HTTPRouteといった主要なリソースと、それを実際に提供するProviderによって成り立ちます。まず、Gatewayリソースがリソースを処理するロードバランサーのインスタンスの定義です。GatewayはIngressのIngress Classと同じくGatewayClassを持ち、Gatewayを管理するコントローラを選択します。そして、HTTP RouteがGatewayリスナーからバックエンドのネットワークまでのルールを定義し、図1のようにルーティングされます。

図1 Gateway APIにおけるトラフィックの流れ    *Kubernetes.ioのdocsより引用

 

Gateway API図2のようにはロール思考に沿ったリソース構造で作られています。GatewayClassをAWSやGCPなどのプラットフォーム提供者が管理し、クラスター管理者がGatewayを管理、そしてアプリケーション開発者がHTTPRouteといったリソースを管理するといった仕様になっています。

図2 Gateway APIおけるそれぞれのロール *Kubernetes.ioのdocsより引用

 

IstioからEnvoy Gatewayへの移行

現行のCALBでは、過去の経緯からデータプレーンとしてIstioを採用しています。しかしながら、CALBを開発していく上でいくつかの課題があり今回データプレーンをIstioからEnvoy Gatewayへの移行を検討することになりました。

IstioとEnvoy GatewayはどちらもGateway APIに準拠したProviderで、内部的にはどちらもEnvoyを利用しています。Controller側で、Envoyの設定を生成してそれをxDS (x Discovery Service)という仕組みを利用してコントローラから各Envoy Proxyに配信しています。

IstioとEnvoy Gatewayの主な違いをまとめると以下の通りです。

 

項目 Istio Envoy Gateway
主な用途 サービスメッシュ (East-West中心) API Gateway / Ingress (North-South中心)
独自リソース VirtualService、DestinationRule等 基本はGateway API +

 一部の拡張用CRD

 

IstioはGateway APIが普及する以前から存在する歴史のあるミドルウェアであり、多種多様な独自のAPIを持っています。主にEast-Westと呼ばれるKubernetesクラスタ内部のトラフィックを扱うことにフォーカスしています。そのため、CALBのようなクラスタの内部と外部を繋ぐようなNorth-Southの用途で利用しようとするとIstio ではまだサポートされていない機能などもあり、CALB のサービス機能を拡充する上で実装が困難といった課題がありました。

Envoy Gatewayは、Gateway API準拠で作られたミドルウェアで、いわゆるNorth-Southといったような用途を想定して開発されています。そこで、CALBのDataPlaneを置き換えることによって、今までIstioで実現できなかった機能の実現や構成の単純化ができないかということで、移行の検証をするという流れになりました。

Envoy Gatewayの検証

CALBのDataPlaneとして十分な機能を持っているのかを確認するため、インターンの最初ではEnvoy Gatewayの検証を行うこととなりました。

細かい部分も多く検証したところはありますが、主に検証を行ったのは以下の3点です。これらの中で上の2つは既にCALBで提供されている機能でEnvoy Gatewayでも同じく提供する必要がある機能です。最後のヘルスチェックは現在のIstioで対応していないもののCALBの機能として追加したいものです。

  1. namespace mode
  2. Backendルーティング
  3. ヘルスチェック

検証は、検証用クラスタ上にEnvoy Gatewayをhelmで導入して公式のドキュメント(https://www.envoyproxy.io/docs/envoy/latest)を参考にして実装を行いました。

namespace mode

現在のCALBではDataplane上に複数のテナントが存在しており、それぞれのテナントをnamespaceで隔離しています。Envoy Gatewayで同じことを実現するためには、envoy proxyのリソースをそれぞれのnamespaceに配置してあげる必要があります。

しかしながら、Envoy Gatewayではデフォルトでデータプレーンのリソースをenvoy-gateway-systemというnamespaceに配置します。そこで、Gateway Namespace Modeというモードを有効化し、Gatewayのnamepsaceにリソースをデプロイしてくれるようにしました。

設定はEnvoy GatewayのConfigで以下のように設定を追加してあげると動作しました。また、今回検証した限りだとGateway Namespace Modeの有効化することによる不都合は特にありませんでした。

config:
  envoyGateway:
    provider:
      type: Kubernetes
      kubernetes:
        deploy:
          type: GatewayNamespace

Backendルーティング

CALBでは、CycloudBackendというリソースでルーティング先のFQDNやServiceを指定します。Gateway API標準にはこうした外部にルーティングをするためのAPIはありません。しかし、Envoy GatewyではBackendというリソースがあり、これを用いることで非常に簡単に実現することができました。

Backendルーティングをするためには、以下のようにEnvoyの設定を追加しておく必要があります。

config:
  envoyGateway:
    extensionApis:
      enableBackend: true

この設定をした上で以下のようにリソースを作ると、Backend Routingが機能します。

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway-a
  namespace: team-a
spec:
  gatewayClassName: eg
  listeners:
    - allowedRoutes:
        namespaces:
          from: Same
      name: http
      port: 8080
      protocol: HTTP
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: Backend
metadata:
  name: cycloud-test
  namespace: team-a
spec:
  endpoints:
    - ip:
        address: 192.168.0.1
        port: 8080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: backend
  namespace: team-a
spec:
  hostnames:
    - "www.example.com"
  parentRefs:
    - name: gateway-a
  rules:
    - backendRefs:
        - group: gateway.envoyproxy.io
          kind: Backend
          name: cycloud-test
          weight: 1
      matches:
        - path:
            type: PathPrefix
            value: /

ヘルスチェック

Envoy GatewayではActive HealtheckとPassive Healthcheckの2つに対応しています。2つの違いは簡単にいうと以下の通りです。

  • Active Healthcheck
    ロードバランサ自身が定期的にエンドポイントを叩いてルーティング先の正常性を確認する
  • Passive Healthcheck
    実際にトラフィックをルーティングし、正常でない応答が帰ってきたら別のリソースに切り替えを行う

細かい部分は省きますが、以下のようなBackendTrafficPolicyというリソースをHTTPRouteなどに対して割り当ててあげることによってHelalth Checkを実現できました。

apiVersion: gateway.envoyproxy.io/v1alpha1

kind: BackendTrafficPolicy
metadata:
  name: passive-health-check
  namespace: team-b
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: ha-example 
  healthCheck:
    passive:
      baseEjectionTime: 10s
      interval: 2s
      maxEjectionPercent: 100
      consecutive5XxErrors: 1 
      consecutiveGatewayErrors: 0
      consecutiveLocalOriginFailures: 1
      splitExternalLocalOriginErrors: false
    active:
      type: HTTP # ヘルスチェックのタイプを指定
      timeout: 1s
      interval: 2s
      unhealthyThreshold: 3
      healthyThreshold: 1
      http:
        path: /healthz # ヘルスチェック用のパス
        method: GET
        expectedStatuses: [200]

実際にActive Healthcheckをしている状態だと、以下のような形でトラフィックの切り替えを確認することができます。
以下の例では、HTTP Routeにルーティングしたいactiveとpassiveという2つのserviceが紐づけられています。初期状態では、activeの方にルーティングされています。

# for i in {1..10}; do curl --verbose --header "Host: www.example.com" http://localhost:5000/test 2>/dev/null | jq .pod; done
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"
"active-858c6d4d7c-cs8zm"

ここで、activeのdeploymentのreplicaを0にして、Podを削除するとactiveに接続できなくなるためpassiveに移動します。

# for i in {1..10}; do curl --verbose --header "Host: www.example.com" http://localhost:5000/test 2>/dev/null | jq .pod; done
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"
"passive-65f7cd47f9-vfv9d"

その他

その他に細かいところでは、IAPやProxy Protocolに関する検証やメトリクスの収集方法に関する検証を行いました。

 

ここまでの結果から、Envoy GatewayはCALBのデータプレーンとして十分な機能を持っており、それぞれの機能が正常に動作するということが検証できました。

Controllerの実装

インターンの中での目標はEnvoy Gatewayが実際に利用できるかの検証までがメインだったのですが、少し時間があったので最後に軽くControllerの実装にも挑戦することになりました。

CALBのクラスタ構成は図2のような形になっており、ManagementとData Planeの2台のクラスタから構成されています。Managementクラスタは、APIからのリクエストを受け付けてCycloudのリソースを定義して管理します。Dataplane Controllerでは、作成されたCycloudのリソースに対して、実際のEnvoy Gatewayのリソースを作成します。

こうした実装になっている理由としては、DataPlaneで使うProviderを容易に切り替えられるようにするためです。それぞれのProvider独自のリソースをWrapして、ユーザは内部のDataPlaneを意識することなく使えるようにするためこのような設計となっています。

今回は、最小構成として以下のようなCALBのリソースに対して対応するEnvoy Gatewayのリソースを作成してくれるようなControllerを開発しました。

---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: test
  namespace: loadbalancing-test-project
spec:
  gatewayClassName: application
  listeners:
  - allowedRoutes:
      namespaces:
        from: All
    hostname: example.com
    name: http
    port: 80
    protocol: HTTP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: test
  namespace: loadbalancing-test-project
spec:
  parentRefs:
  - name: test
  hostnames:
  - example.com
  rules:
  - backendRefs:
    - name: test
      port: 80
---
apiVersion: loadbalancing.cycloud.jp/v1
kind: CycloudBackend
metadata:
  name: test
  namespace: loadbalancing-test-project
spec:
  endpoints:
  - address: 192.168.0.1
  ports:
  - appProtocol: http
    name: http
    port: 80
    protocol: TCP
    targetPort: 80
  targetCyrn: cyrn:loadbalancing:xxx:xxx:kubernetesmanagedbackends/test

Gateway APIに存在するリソースはCALBでも同じリソースを利用しており、Gateway APIに存在しないBackendはCycloudBackendとしてCRDを定義しています。

CycloudBackendは、設定を見るとわかるようにLBが転送する先のエンドポイントの情報を設定するリソースになっています。

 今回は既存のIstioの実装からEnvoyGatewayに対応するようにControllerを開発しました。KubernetesのController開発は初めてだったので最初は挙動の理解に時間がかかりましたが、トレーナーの方から丁寧に説明していただき実装まで終わらせることができました。

今回実装したContrllerは非常にシンプルで、CALBのIAPやTLSなどの機能はなく単純にHTTPで外部にトラフィックを転送する部分だけを開発しました。

 以下は作成したコントローラをデプロイした際の様子です。

# kubectl get Gateway
NAME         CLASS         ADDRESS                                                                                      PROGRAMMED   AGE
test         application   example.com   True         16h
test-4wpkx   eg            192.168.0.1                                                                               True         15h
---
# kubectl get HTTPRoute
NAME         HOSTNAMES         AGE
test         ["example.com"]   12h
test-86pn6   ["example.com"]   12h
---
# kubectl get CycloudBackend
NAME   AGE
test   16h
# kubectl get Backend
NAME         STATUS     AGE
test-hgpzf   Accepted   16h

Gateway、HTTPRoute、Backendで実際にCALBのリソースに対して、対応するリソースがControllerによって作成されAcceptedになっていることが確認できました。

実際にリクエストを送ってみると、設定したIPアドレスに転送されて結果が返ってくることが確認できました。

# curl --header "Host: example.com" http://192.168.0.1/get
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "example.com", 
    "User-Agent": "curl/8.7.1", 
    "X-Envoy-External-Address": "127.0.0.1"
  }, 
  "origin": "10.0.0.1", 
  "url": "http://example.com/get"
}

まとめ

 今回の検証を通じて、CALBのDataPlaneとしてIstioからEnvoyGatewayに移行に向けて検証を行いました。また、実際に簡易的なコントローラの実装を行いました。これにより今までのIstioでは実現できなかった新しい機能をCALBに導入することが可能となるとともに、構成がシンプルになることにより保守性が上がることが期待されます。

 今後はコントローラの実装を進めていき、EnvoyGatewayへの移行が行われていく予定です。

終わりに

 3ヶ月のインターンを通して、今まで使う側として使っていたクラウド内部がどのような仕組みや設計で構築されているのかというのを学ぶことができました。

また、CyberAgentではサービス開発者が自由にインフラ基盤を選択でき、その中の1つとしてCycloudがあるという特殊な環境で、いかにして既存サービスからの学習コストを下げるのかといった設計や考え方は他の会社にはないもので非常に勉強になりました。

 今回のトレーナーをしてくださった木村さんはEnvoy Gatewayのメンテナーもされており、今回の検証中に困ったところを詳しく説明していただきデバッグ方法の知見や、EnvoyGateway / Envoyの挙動の理解が深まりました。

 最後にサポートしていただいたトレーナーの木村さん、CIUチームの皆様、人事の皆様に大変お世話になりました。インターンを通して技術的な学びだけでなく、今後のキャリア設計において非常に参考になるお話を伺えたり、多くのことを得ることができました。
ありがとうございました!

アバター画像
インターンシップ参加者による記事です。