はじめに

はじめまして。CyberAgent group Infrastructure Unit(CIU)に所属している木村洸太です。Cycloud の KaaS 基盤である AKE(Astro Container Engine)や LBaaS 基盤である CLB (Cycloud Load Balancing) の開発運用を担当しています。

今回は、Cycloud の新基盤の全貌に迫る連載シリーズの第6回として、新リージョンで開発した 内製 Network Load Balancer を中心にお届けします。

本記事では、CLB で API として統一採用している Gateway API の選定理由を紹介しながら、新リージョン LB 基盤として技術的挑戦である「LB 筐体の N+1 構成に伴うスケジューリング機構」に焦点を当てます。NW 編で紹介した LB 筐体の N+1 構成により、ユーザーの LB 設定を適した LB 筐体に同期するための機構が必要になりました。どのように実現したのか、その設計の裏側を深掘りします。

CLB (Cycloud Load Balancing) の概要

CLB は、Cycloud の LBaaS として開発しているマネージドロードバランサーサービスです。新リージョンでは、L7 (HTTP / HTTPS) をバランシングする Application Load Balancer と L4 (TCP / UDP) をバランシングする Network Load Balancer を提供しています。

Application Load Balancer  では IAP(Identity-Aware Proxy)の機構を実装しており、社内サービスをグローバル公開する際にアクセス制御機能により、安全にインターネット上に公開することができます。また ACME HTTP-01 を利用して TLS 証明書の発行管理を Application Load Balancer に委譲することができる機能も提供しています。

Gateway API 採用の背景

CLB では L4・L7 共通でサービス API として、Gateway API を採用しています。

Gateway API は、Kubernetes における L4 および L7 ルーティングに特化した公式Kubernetes プロジェクトで、Kubernetes Ingress プロジェクトであった課題を解決するため、当初より拡張性やロール志向に基づいたリソース設計になっている特徴があります。

GatewayAPI におけるロール志向デザイン (公式ドキュメントより引用)

 

GatewayClass はプラットフォーム提供者が管理するリソースで、ロードバランサーのタイプを表現するリソースです。Gateway はクラスタ管理者が管理するリソースで、ロードバランサーの入口となるエンドポイントを表現するリソースです。HTTPRoute はアプリケーション開発者が管理するリソースで、ロードバランサーのトラフィックをどのようにアプリケーションまでルーティングするのかを表現するリソースです。

CLB では、以下の3点の理由から Gateway API をサービス API として採用しました。

1点目は、L4・L7 ロードバランサーで共通した API をユーザーに提供できる点です。上述の通り、GatewayClass を用いることで CLB として提供するロードバランサーの種別を表現することができ、ユーザーは Gateway 設定の GatewayClass を変えるだけで他の設定を変えることなく異なるロードバランサーを払い出すことができます。

2点目は、CLB がサービスを構成するための技術として異なる技術を採用しても、ユーザー API に影響なく行える点です。Gateway API は L4 および L7 ルーティングの共通規約をベンダーニュートラルに策定することを意識されており、特定の技術に関わる API 仕様は入らないように気をつけています。そのため、ユーザーと CLB のインターフェースとなるサービス API で GatewayAPI のみに依存することで、ユーザー影響が最小限になるように設計しています。

3点目は、サイバーエージェントではコンテナ・Kubernetes の採用が盛んな点です。Cycloud では KaaS 基盤である AKE も提供しているため、Kubernetes との親和性が高い点や Kubernetes クラスタ上から容易に利用できる環境を整えることで、Cycloud プラットフォームとしての利便性を高め、ユーザーにより良いサービス体験を提供できるように心がけています。

Network Load Balancer

ここからは新リージョンで開発した Network Load Balancer にフォーカスして紹介していきます。

まずユーザーの利用イメージとして、上記で説明した GatewayAPI リソースを作成するリクエストを送ることで LB 設定を行います。その結果、レスポンスとして複数の IP を紐付けることのできるドメインが返ってきて、このドメインにユーザードメインを紐づけることで疎通が可能になります。

このように単一アドレスではなくドメインを返却することで、ユーザーが可用性を担保したい場合は複数 IP を紐づけるように指定ができたり、LB 筐体の障害時にはドメインから特定 IP を抜くことで安定した LB 基盤を提供することができます。

Gateway API には、このような設定を表現する field が存在しないため、CycloudGatewayPolicy というリソースを定義して対応しています。
このリソースは GatewayAPI の PolicyAttachment という概念に沿って定義しています。

https://gateway-api.sigs.k8s.io/geps/gep-713/ 

PolicyAttachment は、従来の Ingress における annotation に独自ベンダーの追加設定を指定することを改善するために、特定リソースに対して追加設定を付与したい場合に利用することが想定されています。
そのため、以下のように追加設定を表現する field と設定を適用する対象を指定する (.spec.targetRefs ) field を持つ構造になっています。

apiVersion: loadbalancing.cycloud.jp/v1
kind: CycloudGatewayPolicy
metadata:
  name: example
spec:
  default:
    network:
      replicas: 2
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: target-gw

N+1 スケジューリング機構

ここからは新リージョンの技術的挑戦であった LB スケジューリング機構に関して紹介します。

前提として、NW 編でも紹介されていますが NW の技術的挑戦として、LB 筐体の N+1 構成を採用しています。この構成では従来の active-standby のように筐体間の依存関係が事前に決定しているものではなく、状況により LB 筐体の役割が動的に変化していきます。

standby 機は全ての active 機の設定を同期している必要があるため、LB 筐体の役割の変更によって、筐体ごとに動的に設定を投入したり抜いたりする必要があります。

また active の LB 筐体の中で特定の筐体に偏って LB 設定 が同期されてしまうと、リソース効率が悪いことや、トラフィックが集中することで LB 筐体障害に繋がる可能性が高まります。

このような背景から、ユーザーがリクエストしてきた LB 設定を最適な筐体に同期するためのスケジューリング機構がソフトウェアとして必要であり、CLB team の技術的挑戦として開発してきました。

Kubernetes CRD で実現する L4LB プラットフォーム

上記のスケジューリング機構を含む L4LB プラットフォームを実現するにあたって、Kubernetes クラスタ上で CRD (Custom Resource Definitions) を定義し、カスタムコントローラーを実装することで実現しています。

CIU では Kubernetes クラスタ上でのサービス開発の経験が豊富なメンバーが揃っていることや以下の利点から、Kubernetes を活用してサービスを構築することが多いです。

  • CRD による独自の要件を投影したリソースの宣言的表現
  • カスタムコントローラによるあるべき状態への収束
  • 豊富な Kubernetes エコシステムとの連携

L4LB プラットフォームの全体像は以下になります。

ユーザーのリクエストによって作成されるリソースは、 LB 設定を表現する Gateway や TCPRoute などがあり、その LB 設定を適した LB 筐体に同期するために Cycloud 管理者が作成する DataplaneTrafficPolicy や Dataplane、DataplaneTarget などの CRD を定義しています。

このリソース構造に基づき、以下の責務でコンポーネントを分けています。Kubernetes のカスタムコントローラは非同期で処理が実行されるため、冪等性を担保して実装する必要があります。1つの CRDに対してコントローラの責務を持たせすぎるとバグが生じやすいため、各 CRD で関心領域を絞ることで安定した処理を実現しています。

  • ユーザーの入力を検証し指定の DataplaneTarget と紐付けて GatewayInstance を管理するコンポーネント (Gateway reconciler)
  • 指定された情報に基づき対象の LB 筐体に設定を同期するコンポーネント (GatewayInstance reconciler)
  • 全ての LB 筐体から対象の筐体をグルーピングし管理するコンポーネント (DataplaneTarget reconciler)

このように、LB 筐体に設定を入れるコンポーネント、対象の LB 筐体を選定するコンポーネントを明確に分け、それぞれの依存関係のみを確立することで、どこかのコンポーネントのロジックを大きく変更しても影響範囲が少なくなるように設計しています。

次にそれぞれのコンポーネントに関して、簡単に紹介していきます。

DataplaneTarget reconciler

環境の全ての筐体から LB 設定の同期先の対象を選定する役割を持ちます。

CycloudNetworkDataplaneTarget という CRD を定義しており、このリソースごとに処理が実行されます。このリソースには、事前に NW チームと調整した識別子のラベルを持たせることで対象を選定します。

apiVersion: trafficcontrol.network.loadbalancing.cycloud.jp/v1
kind: CycloudNetworkDataplaneTarget
metadata:
  name: target1
spec:
  targetLabel: dataplane1
status:
  targetRefs:
  - kind: CycloudNetworkDataplane
    name: lb00
  - kind: CycloudNetworkDataplane
    name: lb01

実際の LB筐体と 1対1 の関係をもち、Kubernetes クラスタ上で LB 筐体を表現する CycloudNetworkDataplane という CRD も定義しており、このリソースを List することで全ての LB 筐体に対して識別子のラベルを持つか確認しています。

apiVersion: trafficcontrol.network.loadbalancing.cycloud.jp/v1
kind: CycloudNetworkDataplane
metadata:
  name: lb01
spec:
  endpoint: https://10.0.0.1

ここで意図的に CRD に LB 筐体の active や standby といった概念は取り入れずに実現する方式を採用しました。LB 筐体の状態管理を行う責務は NW チームが持つ形になっているため、もし CRD に状態を表す概念を入れてしまうと NW チームのメンバーが Kubernetes クラスタを頻繁に操作する必要が生じてしまいます。責任の境界を明確に分けて、LB 筐体の設定を変えるだけで NW チームが LB 筐体の状態を表現できるためにラベルという識別子を持たせています。

例えば dataplane1 という識別子が active 筐体の1台目を表し、dataplane2 という識別子が active 筐体の2台目を表す取り決めをしていた場合は、以下のように LB 筐体にラベルをふることで論理的に active・standby を表現できるようにしています。

LB01 : dataplane1
LB02 : dataplane2
LB03 : dataplane1,dataplane2 (LB01とLB02 の設定を保持する)

Gateway reconciler

ユーザーのリクエストの値を検証し、実際に LB 筐体に同期する処理の単位である GatewayInstance を管理する役割をもちます。GatewayInstance を作成する際に、指定の CycloudNetworkDataplaneTarget と紐づけています。

ユーザーがロードバランサーを冗長構成にしたい場合は、複数の GatewayInstance を作成し、それぞれが異なる CycloudNetworkDataplaneTarget と紐づくように管理することで冗長性を担保しています。

各 Gateway がどの Target を対象として紐づけることができるかを決めるために、事前に以下の Policy CRD を作成することで、Target の紐付け処理を実現しています。

apiVersion: trafficcontrol.network.loadbalancing.cycloud.jp/v1
kind: ClusterCycloudNetworkDataplaneTrafficPolicy
metadata:
  name: network-internal-default
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: GatewayClass
    name: network-internal
  trafficConfig:
    dataplaneTargetRefs:
    - kind: CycloudNetworkDataplaneTarget
      name: target01
    - kind: CycloudNetworkDataplaneTarget
      name: target02

GatewayInstance reconciler

GatewayInstance の設定をもとに実際に LB 筐体に設定を同期する役割をもちます。

以下のように CycloudNetworkDataplaneTarget への参照を保持していることで、CycloudNetworkDataplaneTarget の status から対象の Dataplane を特定し、特定の筐体に設定を同期しています。

apiVersion: network.loadbalancing.cycloud.jp/v1
kind: GatewayInstance
metadata:
  name: example
spec:
  ...
  dataplaneTargetRef:
    kind: CycloudNetworkDataplaneTarget
    name: dp00

おわりに

本稿では、設計の背景などを中心に内製で開発している LB 基盤に関して紹介してきました。

Kubernetes のエコシステムや CRD をフル活用することで、安定した LB 基盤を実現するとともにユーザーが利用しやすいサービスの実現を目指して奮闘しています。

今回は割愛した Application Load Balancer のデータプレーン選定の内容も過去の Developers Blog にありますので、興味がある方はぜひそちらの記事もご覧ください。

https://developers.cyberagent.co.jp/blog/archives/62184/ 

次回予告

次回は「 KaaS 編」と題して CIU の KaaS基盤である AKE(Astro Container Engine)に関して新リージョン対応についてお届けしていきますので、お楽しみに!

連載記事一覧

本シリーズでは、各チームが新基盤構築における技術的挑戦を公開しています。他のレイヤーの知見については、ぜひ連載の他の記事も併せてご覧ください。

 

Cycloud 新基盤の全貌 第一回 新リージョン基盤の全体像

Cycloud 新基盤の全貌 第二回 HW 編

Cycloud 新基盤の全貌 第三回 NW 編

Cycloud 新基盤の全貌 第四回 IaaS 編

Cycloud 新基盤の全貌 第五回 ComputeController 編

Cycloud 新基盤の全貌 第六回 LB 編

Cycloud 新基盤の全貌 第七回 KaaS 編

Cycloud 新基盤の全貌 第八回 FrontEnd 編

 

各レイヤの Cycloud の新基盤に関して、より詳細な仕様や設計思想を知りたい方は、上記各チームの Developers Blog 記事をぜひご一読ください。