はじめに
電気通信大学情報理工学研究科情報・ネットワーク工学専攻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の機能として追加したいものです。
- namespace mode
- Backendルーティング
- ヘルスチェック
検証は、検証用クラスタ上に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チームの皆様、人事の皆様に大変お世話になりました。インターンを通して技術的な学びだけでなく、今後のキャリア設計において非常に参考になるお話を伺えたり、多くのことを得ることができました。
ありがとうございました!
