GKE Gateway Controller でサービスネットワーキング
7 月に職場が変わり従事しているプロダクトの下回りで使っている GKE と仲良くなりたいと思う、今日この頃です。Google 主催のハンズオンなどに参加する中で Kubernetes でサービスを外部公開する際の API として Gateway API なるものが知り、Ingress に変わって Gateway が使われるケースも増えてくるだろうと話を聞きました。
また、Gateway API でデプロイしたリソースを Google Cloud の世界に紐づける GKE Gateway Controller なるものがあり、2021 年 5 月に Preview されて、2022 年 11 月に GA されています。GA 以降も定期的に機能が追加されているようです。
今回はこのあたりをまとめようと思います。既出の内容も多いところですが、マニフェストのサンプルを備忘録的に残しておきたいので、GKE × Gateway API の参考になれば幸いです!
Overview
Gateway API と GKE Gateway Controller によるサービス外部公開の検証
- Gateway API を利用した Cloud Load Balancing のプロビジョニングによるサービスの外部公開を実現することができました
- Gateway API の中の HTTPRoute リソースで細かいトラフィック管理が可能でした
- Gateway リソースと Namespace リソースで特定の設定をすることで Namespace をまたいだルーティングも可能でした
所感
Service
⇒ NEG
, HTTPRoute
⇒ URL Map(Routing Rule) & Backend Service
, Gateway
⇒ Cloud Load Balacing & Frontend
という対応関係が存在する点や Kubernetes のリソースをデプロイ後にある程度時間が経ってからでないと Google Cloud 側にリソースが作成されない点を理解できるまでは使いづらかったですが、これらを理解した上だと特にルーティングの部分にフォーカスして細かく扱える点が良いと感じました。
あとは Namespace
を超えてルーティング可能になるので、サービスごとの Namespace
の切り分け・管理がよりしやすくなると感じました。 Google Cloud Next で発表があった GKE Enterprise Edition
も Fleet
に登録されたクラスタを Namespace
単位で横串にチームで管理できるようになったりと、 Namespace
単位での管理が流れとしてありそうです。
キーワード
- GKE Gateway Controller(公式ドキュメント)
- Gateway API
Gateway API と GKE Gateway Controller
サービスを外部公開するための Gateway API を利用して定義した Kubernetes のリソースを GKE Gateway Controller が Google Cloud の世界に紐づけてくれるようなイメージをしました。
公式ドキュメントには下記のように記載されています。
GKE Gateway Controller は、Google が Cloud Load Balancing に実装した Gateway API です。GKE Ingress Controller と同様に、Gateway Controller は Gateway API リソースの Kubernetes API を監視し、Cloud Load Balancing リソースを調整して、Gateway リソースで指定されたネットワーク動作を実装します。
Google Cloud 側から監視しているということで、下記の図では Google Cloud から Kubernetes リソースに矢印が向いているんですかね。
こちらの記事が基本情報をまとめてくれているので、本記事では定義方法と実際の挙動をまとめていきたいと思います。
サンプルとして Nginx をデプロイ
トラフィック分割やリダイレクトなどトラフィック管理やルーティングを試したいのでサンプルとして Nginx を GKE にデプロイします。GKE のプロビジョニングについてはこちらをご参考ください。
nginx
|- gateway.yaml
|- httproute.yaml
|- httproute2.yaml
|- nginx-v1.yaml
|- nginx-v2.yaml
|- nginx-v2-2.yaml
|- v1
| |- Dockerfile
| |- default.conf
| |- index.html
|- v2
| |- Dockerfile
| |- default.conf
| |- index.html
Nginx イメージの格納
2 つのバージョンの Nginx を作成します。 共通で /test/
で簡単な html を表示させ、バージョンで異なる内容を表示します。また、バージョン 2 のみ /test2/
で簡単な html を表示させます。
v1/Dockerfile
FROM 'nginx:latest'
RUN mkdir /usr/share/nginx/html/test
COPY ./index.html /usr/share/nginx/html/test
COPY ./default.conf /etc/nginx/conf.d
RUN service nginx start
v1/default.conf
server {
listen 80 default_server;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location = /test {
root /usr/share/nginx/html;
}
}
v1/index.html
<h1>Hello World!</h1>
v2/Dockerfile
FROM 'nginx:latest'
RUN mkdir /usr/share/nginx/html/test
COPY ./index.html /usr/share/nginx/html/test
RUN mkdir /usr/share/nginx/html/test2
COPY ./index.html /usr/share/nginx/html/test2
COPY ./default.conf /etc/nginx/conf.d
RUN service nginx start
v2/default.conf
server {
listen 80 default_server;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location = /test {
root /usr/share/nginx/html;
}
location = /test2 {
root /usr/share/nginx/html;
}
}
v2/index.html
<h1>Hello World! v2</h1>
Artifact Registry の作成から各種イメージの格納までを行います。
# Artifact Registry の作成
gcloud artifacts repositories create sample --location=asia-northeast1 --repository-format=docker
# Artifact Registry の認証
gcloud auth configure-docker asia-northeast1-docker.pkg.dev
# v1 のビルドおよび格納
cd nginx/v1
docker build -f Dockerfile -t asia-northeast1-docker.pkg.dev/sample-project/sample/nginx:v1 .
docker push asia-northeast1-docker.pkg.dev/sample-project/sample/nginx:v1
# v2 のビルドおよび格納
cd ../v2
docker build -f Dockerfile -t asia-northeast1-docker.pkg.dev/sample-project/sample/nginx:v2 .
docker push asia-northeast1-docker.pkg.dev/sample-project/sample/nginx:v2
Nginx のデプロイ
両バージョンともほぼ同一の内容となるので v1 のものだけ載せます。
nginx-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-v1
namespace: sample
labels:
apps: nginx-v1
spec:
replicas: 1
selector:
matchLabels:
app: nginx-v1
template:
metadata:
labels:
app: nginx-v1
spec:
containers:
- name: sample-nginx
image: asia-northeast1-docker.pkg.dev/sample-project/sample/nginx:v1
imagePullPolicy: Always
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v1
namespace: sample
annotations:
cloud.google.com/app-protocols: '{"my-http-port":"HTTP"}'
cloud.google.com/neg: '{"exposed_ports": {"80":{}}}'
spec:
selector:
app: nginx-v1
type: ClusterIP
ports:
- name: my-http-port
port: 80
targetPort: 80
# deployment と service の作成
kubectl apply -f nginx-v1.yaml
# service の情報を取得
kubectl describe svc nginx-v1 -n sample
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 47s sc-gateway-controller sample/nginx-v1
Normal UPDATE 47s sc-gateway-controller sample/nginx-v1
Normal Create 39s neg-controller Created NEG "k8s1-194efb7a-sample-nginx-v1-80-2693315c" for sample/nginx-v1-k8s1-194efb7a-sample-nginx-v1-80-2693315c-my-http-port/80-80-GCE_VM_IP_PORT-L7 in "asia-northeast1-a".
Normal Attach 36s neg-controller Attach 1 network endpoint(s) (NEG "k8s1-194efb7a-sample-nginx-v1-80-2693315c" in zone "asia-northeast1-a")
Service の作成とともに Google Cloud の世界に Network Endpoint Group を作成します。この NEG によって Kubernetes の世界と Google Cloud の世界がつながることできて、コンテナネイティブの負荷分散が実現できるようです。
こちらの Service では下記がブラウザに表示されます。(現時点ではブラウザからはアクセスできません)
v2 も同様にデプロイするとこんな感じになります。
これらを Gateway API を利用して外部に公開していきます。
GatewayClass
GatewayClass は kind で Gateway を指定したマニフェストで記述します。gke
や asm
が Prefix であるもので Cloud Load Balancing の種別を指定することが可能です。(参考)
今回はグローバル外部アプリケーションロードバランサを利用したいので gke-l7-global-external-managed
を選択します。
Gateway API を利用するためにはクラスターの更新が必要となります。
# クラスターの更新
gcloud container clusters update [クラスター名] \
--gateway-api=standard \
--location=[リージョン or ゾーン]
# 利用可能な GatewayClass を確認
kubectl get gatewayclass
Gateway
Gateway では Cloud Load Bancing の名称や構成要素の 1 つであるフロントエンドを指定します。GatewayClass Name に上記の種別を記述します。
外部ロードバランサということで外部 IP が必要となります。 sample-ip
という名前かつグローバルで作成します。
gateway.yaml
apiVersion: v1
kind: Namespace
metadata:
name: sample
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: sample-gateway
namespace: sample
spec:
gatewayClassName: gke-l7-global-external-managed
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
addresses:
- type: NamedAddress
value: sample-ip
# 外部 IP の予約
gcloud compute addresses create sample-ip --global --project=sample-project
# GKE の Config を取得
gcloud container clusters get-credentials sample-cluster --zone asia-northeast1 --project sample-project
# namespace と gateway をデプロイ
kubectl apply -f gateway.yaml
# gateway の情報を確認
kubectl describe gateway sample-gateway -n sample
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 2m5s sc-gateway-controller sample/sample-gateway
Normal UPDATE 53s (x3 over 2m5s) sc-gateway-controller sample/sample-gateway
Normal SYNC 20s (x2 over 53s) sc-gateway-controller SYNC on sample/sample-gateway was a success
describe の Events の結果で SYNC が正常に完了すると Cloud Load Balancing が作成されます。出力からも GKE Gateway Controller が仕事してくれている感じがしますね。
Cloud Load Balancing の名前は gkegwxxxx-[Namespace名]-[Gateway名]
-xxxx のようになります。
フロントエンド
アタッチされている外部 IP も先ほど払い出したものと一致しています。
当然ですが、ルーティングルールは存在していません。該当の IP にアクセスすると fault filter abort
と表示されるかと思います。
HTTPRoute
HTTPRoute ではルーティングルールとバックエンドサービスを追加していきます。HTTPRoute では特に HTTPRouteRule
の種類が多く、細かいトラフィック管理ができそうです。
ここから 5 つのルーティングパターンを紹介します。
1. シンプルルーティング
一番オーソドックスなルーティングはこちらです。冒頭でも説明しましたが http://[外部IP]/test/
で指定した html が返ってくるようにしています。
httproute.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: nginx-httproute
namespace: sample
spec:
parentRefs:
- kind: Gateway
name: sample-gateway
namespace: sample
# hostnames:
# - "*"
rules:
- backendRefs:
- name: nginx-v1
port: 80
# httproute のデプロイ
kubectl apply -f httproute.yaml
# httproute の情報を確認
kubectl describe httproute nginx-httproute -n sample
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 4m12s sc-gateway-controller sample/nginx-httproute
Normal SYNC 2m33s (x2 over 3m6s) sc-gateway-controller Bind of HTTPRoute "sample/nginx-httproute" to ParentRef {Group: "gateway.networking.k8s.io",
Kind: "Gateway",
Namespace: "sample",
Name: "sample-gateway",
SectionName: nil,
Port: nil} was a success
SYNC が正常に完了するとブラウザからアクセス可能になります。
ルーティングルール
コンソール画面のルーティングルールから url-maps
を確認できます。
また、gcloud
コマンドでの確認は下記になります。
# url-map の一覧を取得
gcloud compute url-maps list
# 該当の url-map の詳細を取得
gcloud compute url-maps describe [url-map 名]
バックエンド
コンソール画面のバックエンドには最初にデプロイした Service
である nginx-v1
が紐づいていることが確認できます。実態としては Service
作成時に Google Cloud の世界に作成された NEG
がバックエンドとして登録されていそうです。
2. パスマッチによるルーティング
次にパスによってルーティング先を変更してみます。 /test/
のルーティングは残しつつ、 /test2/
で Nginx-v2
にルーティングされるように設定します。
httproute.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: nginx-httproute
namespace: sample
spec:
parentRefs:
- kind: Gateway
name: sample-gateway
namespace: sample
# hostnames:
# - "*"
rules:
- backendRefs:
- name: nginx-v1
port: 80
- matches:
- path:
value: /test2/
backendRefs:
- name: nginx-v2
port: 80
# httproute のデプロイ
kubectl apply -f httproute.yaml
ルーティングルール
コンソール画面から確認すると matchRules
が増えています。紐づいているバックエンドサービスが Nginx-v2
になっています。
バックエンド
こちらも想定通りバックエンドサービスとして Nginx-v2
が追加されていて、NEG
が紐づいています。
3. リダイレクト
リダイレクトも簡単に実装できます。/redirect/
は /test/
にリダイレクトされるようにします。 httproute.yaml
に修正します。
httproute.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: nginx-httproute
namespace: sample
spec:
parentRefs:
- kind: Gateway
name: sample-gateway
namespace: sample
# hostnames:
# - "*"
rules:
- backendRefs:
- name: nginx-v1
port: 80
- matches:
- path:
value: /redirect/
filters:
- type: RequestRedirect
requestRedirect:
path:
type: ReplaceFullPath
replaceFullPath: /test/
# httproute のデプロイ
kubectl apply -f httproute.yaml
http://[外部 IP]/redirect/
と入力すると http://[外部 IP]/test/
にリダイレクトされます。
ルーティングルール
コンソール画面から確認すると urlRedirect
という項目が増えています。優先度を指定しないとデプロイした最新の routeRules
が高くなっていくようです。
4. トラフィック分割
トラフィック分割も簡単に実装できます。/test/
にアクセスすると、 nginx-v1
と nginx-v2
に 1:1 の比率でルーティングされるように設定します。
httproute.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: nginx-httproute
namespace: sample
spec:
parentRefs:
- kind: Gateway
name: sample-gateway
namespace: sample
# hostnames:
# - "*"
rules:
- matches:
- path:
value: /test/
backendRefs:
- name: nginx-v1
port: 80
weight: 50
- name: nginx-v2
port: 80
weight: 50
# httproute のデプロイ
kubectl apply -f httproute.yaml
みてわかる通りですが、weight
の部分でルーティングされる比率を制御しています。何回かリロードすると無印と v2 が大体交互に表示されるかと思います。
ルーティングルール
コンソール画面から確認すると matchRules
の中に routeAction: weightBackendServices
で weight
が 50 ずつになっています。
5. Cross-Namespace ルーティング
Ingress
では単一の Namespace へのルーティングだけだったのが、Gateway
と HTTPRoute
では Namespace
またぎのルーティングが可能になります。
新しく sample2
という Namespace
を作成します。
Gateway
に関する変更点としては、 shared-gateway-access
を true
にするのがポイントです。このように設定することで、metadata.labels.shared-gateway-access
が true
になっている Namespace
のみ HTTPRoute
が Gateway
にアタッチに可能になります。
gateway.yaml
apiVersion: v1
kind: Namespace
metadata:
name: sample
labels:
shared-gateway-access: "true"
---
apiVersion: v1
kind: Namespace
metadata:
name: sample2
labels:
shared-gateway-access: "true"
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: sample-gateway
namespace: sample
spec:
gatewayClassName: gke-l7-global-external-managed
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"
addresses:
- type: NamedAddress
value: sample-ip
こちらの sample2
に Nginx-v2
をデプロイします。
nginx-v2-2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-v2
namespace: sample2
labels:
apps: nginx-v2
spec:
replicas: 1
selector:
matchLabels:
app: nginx-v2
template:
metadata:
labels:
app: nginx-v2
spec:
containers:
- name: sample-nginx
image: asia-northeast1-docker.pkg.dev/sample-project/sample/nginx:v2
imagePullPolicy: Always
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v2
namespace: sample2
annotations:
cloud.google.com/app-protocols: '{"my-http-port":"HTTP"}'
cloud.google.com/neg: '{"exposed_ports": {"80":{}}}'
spec:
selector:
app: nginx-v2
type: ClusterIP
ports:
- name: my-http-port
port: 80
targetPort: 80
トラフィック分割で利用した httproute.yaml
はデプロイしたままで、 sample2
の Namespace
にデプロイする httproute2.yaml
を作成します。
httproute2.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: nginx-httproute
namespace: sample2
spec:
parentRefs:
- kind: Gateway
name: sample-gateway
namespace: sample
# hostnames:
# - "*"
rules:
- matches:
- path:
value: /test2/
backendRefs:
- name: nginx-v2
port: 80
# sample2 の Namespace を作成
# sample と sample2 に shared-gateway-accesss のラベルを付与
kubectl apply -f gateway.yaml
# sample2 に Service を作成
kubectl apply -f nginx-v2-2.yaml
# sampl2 に HTTPRoute を作成
kubectl apply -f httproute2.yaml
Namespace
をまたいで /test2/
で sample2
の Nginx-v2
にアクセス可能となりました!
ルーティングルール
コンソール画面から確認すると、特に Namespace
に関する情報はありませんでした。結局は NEG
がどのように紐づいているかの話なので、 Google Cloud の世界には関係がないようです。
まとめ
GKE Gateway Controller を介して Gateway API によってサービスを外部公開することができました。
外部公開した際のルーティングは細かく実装することができました。
- シンプルなルーティング
- パスマッチによるルーティング
- ルーティングにおけるトラフィック分割
- Namespace をまたいだルーティング
Gateway API によって Namespace 単位でのサービス管理・公開がより促進されるように感じました。少しだけ GKE と仲良くなることができました。
参考記事
さいごに
AWS と Google Cloud で構築したデータ基盤の開発・運用に携わっているデータエンジニアです。5 年くらい携わっていて、この業務がきっかけで Google Cloud が好きになりました。
現在は React のフロントエンジニアとして修行中です。
X では Google Cloud 関連の情報を発信をしています。
Discussion