![[アップデート] Amazon EKS Auto ModeでKubernetes Gateway APIが使えるようになりました](https://devio2024-media.developers.io/image/upload/f_auto,q_auto,w_3840/f_auto,q_auto/v1790870255/eyecatch/lucsav4axjceen0colwy.png)
[アップデート] Amazon EKS Auto ModeでKubernetes Gateway APIが使えるようになりました
はじめに
皆様こんにちは、あかいけです。
EKS Auto ModeでGateway APIを使いたいけど、そのためだけにAWS Load Balancer Controllerを入れて運用したくない…と思ったことはありますか?私はあります。
そして先日、EKS Auto ModeがKubernetes Gateway APIに対応するアップデートがありました。
なんとこのアップデートにより、EKS側の設定を有効化するだけでKubernetes Gateway APIが使えるようになったようです。
というわけで今回は、EKS Auto ModeのクラスターでGateway APIを有効にして、各種機能を試してみた結果をまとめます。
アップデートの概要
これまでAuto ModeのクラスターでGateway APIを使うには、AWS Load Balancer Controllerなどのコントローラーを自分でインストールし、バージョンアップなどの運用も自分で行う必要がありました。
今回のアップデートで、Auto ModeがGateway API用のコントローラーを提供するようになりました。クラスターの設定でGateway APIを有効にすることで、GatewayやHTTPRouteを作成するだけで、Auto ModeがALBやNLBを作成します。
- 主要機能
- HTTPとgRPCのルーティング、Namespaceをまたいだルーティング、重み付けルーティング、ヘッダーベースのルーティング、ACM証明書の自動検出に対応
- 既存のIngressやService(
type: LoadBalancer)と共存できる
- 設定方法
- クラスターの
gatewayApi.crds.managedをtrue(Managed)にすると、AWSがGateway APIのCRDをインストールして管理する - スキームなどのAWS固有の設定は、AWS Load Balancer Controllerと同じ拡張リソース(
LoadBalancerConfigurationなど)で行う
- クラスターの
- コスト
- 追加料金はない(作成されたロードバランサーの料金はかかる)
検証環境
- Kubernetesバージョン: 1.37
- クラスター: マネジメントコンソールのカスタム設定で、EKS Auto Modeを有効にして作成
- クラスターIAMロール: Auto Modeで標準の5つのマネージドポリシーを付与
AmazonEKSClusterPolicy、AmazonEKSComputePolicy、AmazonEKSBlockStoragePolicy、AmazonEKSLoadBalancingPolicy、AmazonEKSNetworkingPolicy
AWS 構成
AWS上の構成は以下のとおりです。

図のサブネットに付けているタグは、EKS Auto Modeがロードバランサーを置くサブネットを選ぶためのものです。
インターネット向けのALBはkubernetes.io/role/elb: 1、内部向けのALBはkubernetes.io/role/internal-elb: 1のタグが付いたサブネットに作られます。
EKSクラスター 構成
次にEKSクラスター内の構成です。

Gatewayは共通インフラ用にgateway-infraのNamespaceに置き、アプリはそれぞれのアプリ用のNamespaceでHTTPRouteを作るイメージです。
またteam-aのNamespaceにだけgateway-access: "true"のラベルを付けて、Gatewayにルートを紐付けられるNamespaceを制限しています。
なおサンプルアプリには、決まった文字列を返すだけのhashicorp/http-echoを使い、team-aにecho-v1とecho-v2、team-bにecho-bを置きます。
試してみた(Gatewayを作成するまで)
まずは、Gateway APIを有効にしたAuto Modeのクラスターを作成し、ALBが作られるまでを確認します。
クラスターを作成する
マネジメントコンソールから、カスタム設定でEKS Auto Modeを有効にしたクラスターを作成します。
Gateway APIの設定は、「Gateway API CRD 管理」の項目で選びます。
アンマネージドがmanaged: false、マネージドがmanaged: trueにあたります。
今回はAWSにCRDを管理してもらうため、マネージドを選びました。

なお、Auto Modeを無効にしている場合は、「Gateway API CRD 管理は、EKS Auto Mode が有効になっている場合にのみご利用いただけます。」と表示され、どちらも選べません。

また、クイック設定(EKS オートモード付き)では、画面に表示される設定値の一覧で「Gateway API CRD 管理」が「アンマネージド」になっていました。
作成時にマネージドを選ぶ項目は見当たらないため、クイック設定で作る場合は作成後にマネージドへ変更する必要がありそうです。

Gateway APIのCRDを確認する
クラスターの作成が完了したら、Gateway APIのCRDがインストールされているかを確認します。
$ kubectl get crd -o custom-columns='NAME:.metadata.name,BUNDLE:.metadata.annotations.gateway\.networking\.k8s\.io/bundle-version,CHANNEL:.metadata.annotations.gateway\.networking\.k8s\.io/channel' | grep -E 'NAME|gateway'
NAME BUNDLE CHANNEL
gatewayclasses.gateway.networking.k8s.io v1.6.0 standard
gateways.gateway.networking.k8s.io v1.6.0 standard
grpcroutes.gateway.networking.k8s.io v1.6.0 standard
httproutes.gateway.networking.k8s.io v1.6.0 standard
listenerruleconfigurations.gateway.k8s.aws <none> <none>
listenersets.gateway.networking.k8s.io v1.6.0 standard
loadbalancerconfigurations.gateway.k8s.aws <none> <none>
referencegrants.gateway.networking.k8s.io v1.6.0 standard
targetgroupconfigurations.gateway.k8s.aws <none> <none>
tcproutes.gateway.networking.k8s.io v1.6.0 standard
tlsroutes.gateway.networking.k8s.io v1.6.0 standard
udproutes.gateway.networking.k8s.io v1.6.0 standard
Gateway APIのCRDと、AWS拡張リソース(gateway.k8s.aws)のCRDがインストールされていました。Gateway APIのCRDは、v1.6.0のstandardチャネルのものでした。
GatewayClassのCRDのアノテーションも見てみます。
$ kubectl get crd gatewayclasses.gateway.networking.k8s.io -o jsonpath='{.metadata.annotations}' | jq .
{
"api-approved.kubernetes.io": "https://github.com/kubernetes-sigs/gateway-api/pull/4530",
"eks.amazonaws.com/managed": "true",
"gateway.networking.k8s.io/bundle-version": "v1.6.0",
"gateway.networking.k8s.io/channel": "standard"
}
eks.amazonaws.com/managed: "true"のアノテーションが付いており、AWSが管理しているCRDだとわかります。
サンプルアプリをデプロイする
次に、Namespaceとサンプルアプリを作成します。
Namespaceはgateway-infra、team-a、team-bの3つで、team-aにだけgateway-access: "true"のラベルを付けます。
apiVersion: v1
kind: Namespace
metadata:
name: gateway-infra
---
apiVersion: v1
kind: Namespace
metadata:
name: team-a
labels:
gateway-access: "true"
---
apiVersion: v1
kind: Namespace
metadata:
name: team-b
アプリはteam-aにecho-v1とecho-v2、team-bにecho-bを作ります。
3つとも同じイメージを使い、返す文字列だけを変えています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-v1
namespace: team-a
spec:
replicas: 1
selector:
matchLabels:
app: echo-v1
template:
metadata:
labels:
app: echo-v1
spec:
containers:
- name: echo
image: hashicorp/http-echo:1.0
args: ["-text=hello from team-a v1"]
ports:
- containerPort: 5678
resources:
requests:
cpu: 50m
memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
name: echo-v1
namespace: team-a
spec:
selector:
app: echo-v1
ports:
- port: 80
targetPort: 5678
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-v2
namespace: team-a
spec:
replicas: 1
selector:
matchLabels:
app: echo-v2
template:
metadata:
labels:
app: echo-v2
spec:
containers:
- name: echo
image: hashicorp/http-echo:1.0
args: ["-text=hello from team-a v2"]
ports:
- containerPort: 5678
resources:
requests:
cpu: 50m
memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
name: echo-v2
namespace: team-a
spec:
selector:
app: echo-v2
ports:
- port: 80
targetPort: 5678
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-b
namespace: team-b
spec:
replicas: 1
selector:
matchLabels:
app: echo-b
template:
metadata:
labels:
app: echo-b
spec:
containers:
- name: echo
image: hashicorp/http-echo:1.0
args: ["-text=hello from team-b"]
ports:
- containerPort: 5678
resources:
requests:
cpu: 50m
memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
name: echo-b
namespace: team-b
spec:
selector:
app: echo-b
ports:
- port: 80
targetPort: 5678
$ kubectl apply -f 00-namespaces.yaml -f 02-apps.yaml
namespace/gateway-infra created
namespace/team-a created
namespace/team-b created
deployment.apps/echo-v1 created
service/echo-v1 created
deployment.apps/echo-v2 created
service/echo-v2 created
deployment.apps/echo-b created
service/echo-b created
デプロイ後に全NamespaceのPodを確認すると、表示されたのはアプリのPodだけでした。
$ kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
team-a echo-v1-d747fdc94-6gxzz 1/1 Running 0 71s
team-a echo-v2-56445bbf74-mvg62 1/1 Running 0 71s
team-b echo-b-76b465cc87-7zvwv 1/1 Running 0 71s
このことからもゲートウェイのコントローラーは、クラスター内のPodとしては動いていないようです。なのでコントローラーのインストールやバージョンアップを、利用者が行う必要はなさそうですね。
GatewayClassとGatewayを作成する
いよいよGateway APIのリソースを作ります。
まずはGatewayClassを作成します。
controllerNameにはALBを作るコントローラーを指定します。
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eks-auto-alb
spec:
controllerName: eks.amazonaws.com/gateway-alb
ACCEPTEDがTrueになればOKです。
$ kubectl apply -f 01-gatewayclass.yaml
gatewayclass.gateway.networking.k8s.io/eks-auto-alb created
$ kubectl get gatewayclass
NAME CONTROLLER ACCEPTED AGE
eks-auto-alb eks.amazonaws.com/gateway-alb True 1s
次にGatewayを作成します。
Gatewayから作られるALBのスキームは、既定ではinternalですが、今回は手元のPCからALBに直接アクセスするため、LoadBalancerConfigurationでscheme: internet-facingを指定します。
LoadBalancerConfigurationはGatewayと同じNamespaceに置き、Gatewayのspec.infrastructure.parametersRefで参照します。
また、allowedRoutesで、gateway-access: "true"のラベルが付いたNamespaceのルートだけを受け付けるようにしています。
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: internet-facing
namespace: gateway-infra
spec:
scheme: internet-facing
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
namespace: gateway-infra
spec:
gatewayClassName: eks-auto-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: internet-facing
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"
数分でPROGRAMMEDがTrueになり、ALBが作成されました。
ALBはinternet-facingのスキームで、パブリックサブネットに作成されています。
$ kubectl apply -f 03-gateway.yaml
loadbalancerconfiguration.gateway.k8s.aws/internet-facing created
gateway.gateway.networking.k8s.io/shared-gateway created
$ kubectl wait --for=condition=Programmed gateway/shared-gateway -n gateway-infra --timeout=300s
gateway.gateway.networking.k8s.io/shared-gateway condition met
$ kubectl get gateway -n gateway-infra
NAME CLASS ADDRESS PROGRAMMED AGE
shared-gateway eks-auto-alb k8s-gatewayi-sharedga-f98c6e5eb2-326408971.ap-northeast-1.elb.amazonaws.com True 4m40s
試してみた(ルーティングとHTTPSの確認)
次に、ここまでで作ったGatewayを使って、アップデート記事で紹介されている機能を試します。
今回は重み付けルーティング、ヘッダーベースのルーティング、Namespaceをまたいだルーティング、ACM証明書の自動検出を試してみます。
なお、試す機能自体はもともとAWS Load Balancer ControllerのGateway API対応で使えていたものです。対応しているフィールドなどの詳細は、AWS Load Balancer Controllerのドキュメントにまとまっています。
重み付けルーティングとヘッダーベースのルーティング
team-aにHTTPRouteを作成します。
ルールは次の2つです。
x-canary: trueヘッダーが付いたリクエストはecho-v2へ送る- それ以外のリクエストは
echo-v1とecho-v2に80:20の割合で振り分ける
Gatewayは別のNamespaceにあるので、parentRefsにnamespaceを指定します。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: team-a
spec:
parentRefs:
- name: shared-gateway
namespace: gateway-infra
rules:
# x-canary: true ヘッダー付きのリクエストはv2へ
- matches:
- headers:
- name: x-canary
type: Exact
value: "true"
backendRefs:
- name: echo-v2
port: 80
# それ以外はv1:v2 = 80:20で振り分け
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: echo-v1
port: 80
weight: 80
- name: echo-v2
port: 80
weight: 20
適用すると、HTTPRouteはAcceptedになりました。
$ kubectl get httproute echo-route -n team-a -o jsonpath='{.status.parents}' | jq -c '.[] | .conditions[] | {type,status,reason,message}'
{"type":"ResolvedRefs","status":"True","reason":"ResolvedRefs","message":""}
{"type":"Accepted","status":"True","reason":"Accepted","message":""}
curlで100回リクエストして、振り分けの割合を見てみます。
$ ADDRESS=$(kubectl get gateway shared-gateway -n gateway-infra -o jsonpath='{.status.addresses[0].value}')
$ for i in $(seq 1 100); do curl -s http://$ADDRESS/; done | sort | uniq -c
77 hello from team-a v1
23 hello from team-a v2
77:23で、指定した80:20に近い割合で振り分けられています。
次に、x-canary: trueヘッダーを付けて20回リクエストします。
$ for i in $(seq 1 20); do curl -s -H 'x-canary: true' http://$ADDRESS/; done | sort | uniq -c
20 hello from team-a v2
20回すべてecho-v2に届きました。
ALBのHTTP:80リスナーのルールを確認すると、HTTPRouteの内容は次のように変換されていました。
| 優先度 | 条件 | アクション |
|---|---|---|
| 1 | パスが/*、かつヘッダーx-canaryがtrue |
echo-v2のターゲットグループへ転送 |
| 2 | パスが/* |
echo-v1(重み80)とecho-v2(重み20)のターゲットグループへ転送 |
| default | なし | 固定レスポンス(404) |
重み付けは、ALBの転送アクションのターゲットグループの重みとして設定されています。
ヘッダー条件だけを書いたルールにも、パスが/*の条件が付いていました。
Gateway APIではmatchesにパスを書かない場合、パスは/の前方一致(PathPrefix)として扱われます。
この既定値が、ALBのルールにも反映されています。
ターゲットグループは、ターゲットタイプがip、ポートがコンテナのポート(5678)で作成されていました。
ALBはServiceのポート(80)を経由せず、PodのIPアドレスの5678番ポートへ直接転送します。
Namespaceをまたいだルーティング
先ほどのteam-aのHTTPRouteは、別のNamespace(gateway-infra)にあるGatewayに紐付けていたので、実はこれ自体がNamespaceをまたいだルーティングになっていました。
念のため、Gatewayが許可していないNamespaceから紐付けようとするとどうなるかも試してみます。
ラベルを付けていないteam-bから、同じGatewayにHTTPRouteを紐付けます。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: echo-route
namespace: team-b
spec:
parentRefs:
- name: shared-gateway
namespace: gateway-infra
rules:
- matches:
- path:
type: PathPrefix
value: /team-b
backendRefs:
- name: echo-b
port: 80
AcceptedがFalse、理由はNotAllowedByListenersとなり、紐付けは拒否されました。
拒否されたのは、team-bのNamespaceにGatewayが求めるラベルがないためです。
$ kubectl get httproute echo-route -n team-b -o jsonpath='{.status.parents}' | jq -c '.[] | .conditions[] | {type,status,reason,message}'
{"type":"ResolvedRefs","status":"True","reason":"ResolvedRefs","message":""}
{"type":"Accepted","status":"False","reason":"NotAllowedByListeners","message":"Listener does not allow route attachment, namespace does not match between listener and route"}
もちろんALBのリスナールールにもteam-bのルールは追加されておらず、/team-bへのリクエストはteam-aの/のルールで処理されました。
(team-aのHTTPRouteは/の前方一致ですべてのパスに一致するため、/team-bへのリクエストもこのルールで処理されています)
$ curl -s http://$ADDRESS/team-b
hello from team-a v2
結論として、ルートを受け付けるNamespaceは、Gatewayを管理する側が決められます。
そのため複数のチームで1つのALBを共有する場合も、許可していないチームが勝手にALBへルールを追加する、といったことはできなそうですね。(各チームがGatewayやNamespaceのラベルを編集できないよう、KubernetesのRBACで権限を絞っている前提ではありますが…)
ACM証明書の自動検出
ACMでgwapi.example.comの証明書を東京リージョンで発行します。
GatewayではHTTPSのリスナーを追加し、hostnameに証明書のドメイン名 gwapi.example.com を指定します。
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared-gateway
namespace: gateway-infra
spec:
gatewayClassName: eks-auto-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: internet-facing
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"
- name: https
protocol: HTTPS
port: 443
hostname: gwapi.example.com
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"
Gateway APIの仕様では、リスナーのtls.certificateRefsでKubernetesのSecretを参照して証明書を指定します。
一方、AWS Load Balancer ControllerのドキュメントではcertificateRefsは未対応とされており、証明書はLoadBalancerConfigurationで指定するか、hostnameをもとに自動検出させます。
今回はtlsの設定を書かずにhostnameだけを指定しましたが、Gatewayはそのまま受け付けられました。
適用後にALBのリスナーを確認すると、HTTPS:443のリスナーが追加され、ACMの証明書が自動で設定されていました。
| リスナー | 証明書 | SSLポリシー |
|---|---|---|
| HTTPS:443 | ACMで発行したgwapi.example.comの証明書 |
ELBSecurityPolicy-2016-08 |
| HTTP:80 | なし | なし |
HTTPS:443のリスナールールには、HTTP:80と同じ条件に加えて、host-headerがgwapi.example.comという条件が付いていました。
Gatewayのリスナーに書いたhostnameは、ALBではリスナールールの条件になります。
Route 53でgwapi.example.comのエイリアスレコードをALBに向けて、HTTPSでアクセスしてみます。
$ curl -sv https://gwapi.example.com/ 2>&1 | grep -E 'SSL connection|subject:|expire date|issuer:|< HTTP/|^hello'
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / [blank] / UNDEF
* subject: CN=gwapi.example.com
* expire date: May 24 23:59:59 2027 GMT
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
< HTTP/2 200
hello from team-a v1
ACMで発行した証明書でHTTPSの通信ができました。
余談:デフォルトのSSLポリシーについて
Gatewayで作成されたALBのHTTPSリスナーには、SSLポリシーとして既定でELBSecurityPolicy-2016-08が設定されていました。
このポリシーはTLS 1.0とTLS 1.1も許可するため、本番環境ではLoadBalancerConfigurationのlistenerConfigurationsでsslPolicyを指定しておくのがよさそうです。
今回は試していませんが、ドキュメントの記載に沿うと次のように書くようです。
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: internet-facing
namespace: gateway-infra
spec:
scheme: internet-facing
listenerConfigurations:
- protocolPort: HTTPS:443
sslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06
さいごに
以上、EKS Auto ModeのKubernetes Gateway API対応の紹介でした。
Auto Modeのクラスターであれば、「Gateway API CRD 管理」をマネージドにするだけでGateway APIが使えるようになります。
コントローラーのインストールやバージョンアップを気にせず、重み付けルーティングやNamespaceをまたいだルーティングを使えるため、なかなか嬉しいのではないでしょうか。
これからEKS Auto ModeでGateway APIを使おうとしている方の参考になれば幸いです。










