KubernetesのGateway APIが'23/10にGAとなり、v1.0としてリリースされた。
ベンダーでもサポートの動きがあり、例えば'24/2にリリースされたVMwareのTanzu Kubernretes Gridのv2.5からGateway APIがサポートされるようになった。
今後Gateway APIを使う場面も増えてくると思われるので、どういうリソースかを改めて整理してみる。
また、Mac上でK8sクラスタを構築し、サクっと動作確認してみる。
Gateway APIとは
Kubernetesクラスタ内のサービスを外部に公開する場合、一般的にはIngress
が利用されると思う。
ただ、Ingress
では以下のような課題があった。
- インフラ管理者、クラスタ管理者、アプリ開発者の間での責任分界点が不明瞭
- L7ロードバランサのみ提供され、L4が足りていない
- トラフィックの細かなルーティング管理がIngress Controller独自の拡張機能(annotation利用や独自リソースによる拡張)でしか提供されず、環境依存が発生する
Gateway APIではIngress
の標準機能では提供出来ていなかった以下のような機能が提供される。
責任分界点の明確化
Gateway SIGの中で、どのリソースを誰が責任を持つかが明確化された。
(Gatway SIGのIntroductionより引用)
これにより、以下のような分担が標準ロールとして定義された。
ロール | 役割 |
---|---|
インフラ管理者 |
Gateway リソースを管理するためのGatewayClass (IngressClass みたいなもので、コントローラを指定)を提供・管理 |
クラスタ管理者 |
GatewayClass に紐づくロードバランサ(Gateway リソース)を提供・管理 |
アプリケーション開発者 | ロードバランサとアプリケーションを紐づけるためのルーティング(HTTPRoute 、TLSRoute など)を提供・管理 |
様々な種類のルーティングの提供
Ingress
ではL7のみだったが、Gateway APIでは以下のルーティング用のリソースが提供される。
HTTPRoute
TLSRoute
GRPCRoute
TCPRoute
UDPRoute
なお、'24/3時点ではHTTPRoute
以外は正式GAされていなかったと思うので、利用する際にサポート等が必要な場合は要確認である。
(例えばTKG2.5だとHTTPRouteのみサポート)
細かなルーティング制御の実現
今まではIngress
内のannotationsで設定したり、Servie Mesh製品を入れて実現していたような細かなルーティングが設定可能である。
詳細はHTTPRoute
のRulesを見ると分かるが、ヘッダーでフィルタリングしたり、カナリアリリースやタイムアウトをパスや条件ごとに個別に設定することが出来るようになった。
以下はカナリアリリースの例であり、spec.rules[].backendRefs[].weight
で重み付けを行っている事が分かる。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: foo-route
labels:
gateway: prod-web-gw
spec:
hostnames:
- foo.example.com
rules:
- backendRefs:
- name: foo-v1
port: 8080
weight: 90
- name: foo-v2
port: 8080
weight: 10
Gateway APIが提供するリソース
APIの種類については公式リファレンスで確認できるが、ここでは代表的な3つのリソース(GatewayClass
、Gateway
、HTTPRoute
)を掘り下げる。
GatewayClass
GatewayClass
はIngressClass
と同様にGateway
リソースを作るための型みたいなもので、基本的にはソフトウェアの中に梱包されていて利用者がManifestを編集することはほとんどないと思われる。
クラスタリソースであり、全Namespaceで利用できる。
Manifestの例は以下となる。
kind: GatewayClass
metadata:
name: cluster-gateway
spec:
controllerName: "example.net/gateway-controller"
Gateway Controllerによってはspec.paramtersRef
などを使ってパラメータをコントローラに渡すこともあるが、利用者はこのリソースはあまり意識しないので書き方は知らなくても問題ないかと思われる。
Gateway
クラスタ管理者はどういうパケットをクラスタ内に流すのを許可するか、をGateway
リソースを使って設定・管理する。
Ingress
でいうとエンドポイントのプロトコルやポートを管理するようなリソースで、AWSのSecurityGroupの設定に近いかもしれない。
以下は実際のGateway
リソースの例である。
spec:
gatewayClassName: kong
listeners:
- allowedRoutes:
namespaces:
from: Same
name: http
port: 80
protocol: HTTP
- name: proxy-ssl
port: 443
protocol: HTTPS
- name: proxy-tcp-9901
port: 9901
protocol: TCP
- name: proxy-udp-9902
port: 9902
protocol: UDP
gatewayClassName
はingressClassName
のGateway版でGatewayClass
を指定する。
lisnters
でホスト名、ポート、TLS設定等を定義する。
また例には載せていないが、spec.addresses
でGateway
がListenするアドレスも指定する事が出来る。
詳細な仕様はこちら。
HTTPRoute
HTTPRoute
ではルーティング時の振る舞いを定義する事が出来る。
ここでパスごとの重み付け(B/Gやカナリアリリースで使用)、タイムアウト、ヘッダの書き換えなど柔軟なリクエスト処理が行えるようになる。
タイムアウトの例
以下ではtimeouts.request
でクライアントに対する応答時間のタイムアウト値を設定し、timeouts.backendRequest
でGatewayからバックエンドへのタイムアウト値を設定している。
spec:
parentRefs:
- name: example-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /timeout
timeouts:
request: 10s
backendRequest: 2s
backendRefs:
- name: timeout-svc
port: 8080
ヘッダ書き換えの例
以下ではリクエストヘッダにmy-header: foo
を追加している。
spec:
hostnames:
- my.filter.com
rules:
- filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: my-header
value: foo
Gateway API利用時のサービスへのアクセスの流れ
クライアントからPodが提供するサービスへアクセスするまでの流れは以下となる。
(Request flowより引用)
- クライアントがURLにリクエストを投げる
- DNSリゾルバがURLに対応する
Gateway
に割り当てられたIPを引っ張り出す - クライアントは
Gateway
のIPにリクエストを送信する("Host:"ヘッダでURLアクセス名も渡す) -
Gateway
側で対応するHTTPRoute
を確認し、HTTPRoute
の処理に移る -
HTTPRoute
でService
に転送する。この際ヘッダによるフィルタリングやヘッダの加工などリクエストの修正をしたり、流すパスを変えたりする事が出来る。 -
Service
経由でPod
が提供するサービスに到達する
ローカルPCでGateway APIを利用する
ここではMacにMicroK8sでKubernetes環境を作成し、そこにGateway APIを入れて動作確認する。
Kubernetes環境の構築
microk8sのインストール手順に従って構築・起動する。
brew install ubuntu/microk8s/microk8s
microk8s install
multipass
をセットアップするか?みたいな質問が出るのでy
を押して併せてセットアップする。
また途中でsudo
で動くためにパスワードを聞かれるので、それも素直に入力する。
インストールが終わったらMetalLBを使ってtype: LoadBalancer
が使えるようにする。
multipass
が使っているネットワークをNodeのIPから確認する。
microk8s kubectl get node microk8s-vm -o jsonpath={.status.addresses[0].address}
ここでは以下の結果となった。
192.168.64.3
なので、192.168.64.xxxであれば使えそうなので、ここをMetalLBが割り当てるレンジとしてMetalLBをデプロイする。
microk8s enable metallb:192.168.64.201-192.168.64.210
動作確認する。
microk8s kubectl run nginx --image nginx --port 80
microk8s kubectl expose pod nginx --port 80 --type LoadBalancer
NGINX_IP=$(microk8s kubectl get svc nginx -o jsonpath={.status.loadBalancer.ingress[0].ip})
curl $NGINX_IP
curlで以下が取得できればOK。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
:(省略)
また、これ以降の作業はMicroK8s以外のKubernetes環境と手順を共通化させるため、microk8s
コマンドにエイリアスを貼ってkubectl
とhelm
をmicrok8s
を意識せずに叩けるようにする。
alias 'kubectl=microk8s kubectl'
alias 'helm=microk8s helm'
Gateway APIの導入:インフラ管理者作業
Gateway APIを利用するためにはGateway Controllerが必要となる。
これはKubernetes標準では提供されておらず、各種ベンダから提供されている。
Gateway Controllerの一覧はこちら。
どれを使ってもそれほど手順は変わらないと思うが、ここではどこのインフラでも使えそうなKong Ingress Controllerで試す。
最初にGateway APIで提供されるリソース定義をインストールする。
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
導入すると以下のリソースが見えるようになる。
$ kubectl api-resources |grep gateway
gatewayclasses gc gateway.networking.k8s.io/v1 false GatewayClass
gateways gtw gateway.networking.k8s.io/v1 true Gateway
httproutes gateway.networking.k8s.io/v1 true HTTPRoute
referencegrants refgrant gateway.networking.k8s.io/v1beta1 true ReferenceGrant
次にGateway Controllerを導入する。
helm repo add kong https://charts.konghq.com
helm repo update
helm install kong kong/ingress -n kong --create-namespace
しばらくすると、以下のような感じでGateway Controllerを含む各種コンポーネントが立ち上がる。
$ kubectl get all -n kong
NAME READY STATUS RESTARTS AGE
pod/kong-controller-55b777f995-b7zsg 1/1 Running 0 37s
pod/kong-gateway-868d48556c-m9vdf 1/1 Running 0 37s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kong-gateway-admin ClusterIP None <none> 8444/TCP 38s
service/kong-controller-validation-webhook ClusterIP 10.152.183.107 <none> 443/TCP 38s
service/kong-gateway-proxy LoadBalancer 10.152.183.96 192.168.64.202 80:31493/TCP,443:32243/TCP 38s
service/kong-gateway-manager NodePort 10.152.183.34 <none> 8002:32171/TCP,8445:32729/TCP 38s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kong-controller 1/1 1 1 38s
deployment.apps/kong-gateway 1/1 1 1 38s
NAME DESIRED CURRENT READY AGE
replicaset.apps/kong-controller-55b777f995 1 1 1 37s
replicaset.apps/kong-gateway-868d48556c 1 1 1 37s
GatewayClass
を作成する。公式サイトの記述をそのまま適用する。
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: kong
annotations:
konghq.com/gatewayclass-unmanaged: 'true'
spec:
controllerName: konghq.com/kic-gateway-controller
EOF
なお、annotationの記述の意味はこちらによるとGateway
リソースをコントローラに監視させるためのKong Ingress Controller固有の設定となるため、他のGateway Controllerだと変わる。
他のGatway Controllerを使う場合、基本的にGatewayClass
は各種ベンダから作成方法の指示があるので、そちらの指示に従って作成する。
以上でインフラ管理者作業は終了となり、クラスタ管理者に引き渡す。
クラスタへのインバウンドルーティングの設定(Gateway
リソースの作成):クラスタ管理者作業
ここではクラスタ管理者として、HTTPのみクラスタ内に流すことを許可すると想定して、以下のGateway
リソースを作成する。
kubectl create ns demo
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: demo
spec:
gatewayClassName: kong
listeners:
- name: http
port: 80
protocol: HTTP
EOF
作成するとADDRESS
にMetalLBによって払い出されたIPが表示される。
$ kubectl get gateway -n demo
NAME CLASS ADDRESS PROGRAMMED AGE
gateway kong 192.168.64.202 True 18m
このIPを使ってアプリケーション開発者はアプリケーションとGatewayをつなぐ。
アプリケーションへのルーティングの設定(HTTPRoute
リソースの作成):アプリケーション開発者作業
ここではサンプルとして、クラスタ内のnginx Podとクラスタ外のhttpbin.orgにService
経由でアクセス出来るようにし、HTTPRoute
を使ってそれらに分散してアクセスできるかを試す。
最初にサンプルとなるnginxのPod
を立て、Service
を作成する。
kubectl run nginx --image nginx --port 80 -n demo
kubectl expose pod nginx --port 80 -n demo
次に外部サービスにつなぐためのExternalNameなService
を作成する。
kubectl create svc externalname httpbin --external-name httpbin.org -n demo
サービスにアクセスするためのホスト名を定義する。DNSサーバがある人は先程のGatewayのアドレスを向いたドメインを用意する。
ここではDNSサーバがなかったので、フリーのワイルドカードDNSサービスのnip.ioを使って代用する。
PROXY_IP=$(kubectl get gateway gateway -n demo -o jsonpath={.status.addresses[].value})
export HOSTNAME="my-service.$(sed "s/\./-/g" <<< $PROXY_IP).nip.io"
${HOSTNAME}/service
にアクセスすると、先ほどの2つのサービスに分散するようなHTTPRoute
を作成する。
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: route
namespace: demo
annotations:
konghq.com/strip-path: 'true'
spec:
hostnames:
- $HOSTNAME
parentRefs:
- name: gateway
rules:
- matches:
- path:
type: PathPrefix
value: /service
backendRefs:
- name: nginx
kind: Service
port: 80
weight: 50
- name: httpbin
kind: Service
port: 80
weight: 50
EOF
ちなみにExternalName
ではポート番号は指定しないが、HTTPRoute
作成時は必須となっているのでとりあえず80を入れておく。
問題なければ以下のようにHTTPRoute
が作成される。
$ kubectl get httproute -n demo
NAME HOSTNAMES AGE
route ["my-service.192-168-64-202.nip.io"] 8m4s
アクセスしてみる。
curl -s my-service.192-168-64-202.nip.io/service
何度か実行すると、nginxとhttpbin.orgのそれぞれにアクセスする。
これにより、weight
が適切に働き分散していることが確認できる。
所感
自分の経験としてIngress
やServiceMesh製品を使ってルーティングの設定をする場合、製品や環境個々の設定方法を知る必要があり、環境が変わるたびにキャッチアップするのが大変だった。
Gateway APIがあればこの辺の環境差異が容易に吸収できそうなので、Gateway APIが普及すれば少し楽が出来そうな気がする。
また、ServiceMesh製品を入れないと難しかった機能も提供されており、構成がシンプルかつサポート利用時の問い合わせルートを簡素化出来るのもありがたい。
その他機能以外にはなるが、責任分界点をコミュニティ側で用意してくれたのもいい感じだと思う。
Kubernetes環境では責任分界点で揉めることも多いので、揉めそうな時はデファクトとして提示できればと思う。