🚥

LinkerdのGateway API実装(HTTPRoute)を触ってみる

2023/12/15に公開

この記事は Kubernetes Advent Calendar 2023 の 15日目(シリーズ2)の記事です。


Gateway APIとは

https://gateway-api.sigs.k8s.io/

Gateway API は、Ingress API のより表現力豊かな次世代バージョンに似ていると考えることができます。

「表現力豊か」であることによってIngressの課題を克服することを目指しています。

  • クラスターへ関与する役割(インフラ構築・クラスター運用・アプリケーション開発)ごとに関心事を分離できていない
  • 固有の実装にアノテーションが多用されて互換性が低い

Ingressの進化版であるため元々はnorth/south(クラスター外から内へ、内から外へ)トラフィックの管理を目的としていましたが、サービスメッシュユーザーからの関心の高まりによってeast/west(クラスター内のワークロード間)トラフィックにも利用できるようにする仕様策定が進んでいます。


Kubernetes Advent Calendar 2023 の 1日目の記事にて、Gateway APIの最新動向について総括されています。

https://amsy810.hateblo.jp/entry/2023/12/01/090000


今回はLinkerdによる実装を通じて、Gateway APIのeast/westユースケースを体験してみます。
下記リポジトリを手元にcloneして同じ内容を追体験することもできます。

https://github.com/mikutas/linkerd-installation

Linkerdすこし紹介

LinkerdはKubernetes向けサービスメッシュのひとつです。
この分野で有名なプロダクトといえばIstioですが、LinkerdはIstioと同じくCNCFのプロジェクトで、Istioより2年早くサービスメッシュの中で最初にgraduated projectに認定されています。

https://www.cncf.io/announcements/2021/07/28/cloud-native-computing-foundation-announces-linkerd-graduation/

https://www.cncf.io/announcements/2023/07/12/cloud-native-computing-foundation-reaffirms-istio-maturity-with-project-graduation/

Linkerdは設計原則に表される通り「シンプルであること」を追及しているのが特徴です。

https://linkerd.io/design-principles/

  • シンプルにせよ。Linkerdは認知的オーバーヘッドが低く、運用がシンプルである必要があります。運用者は最小限の魔法で、そのコンポーネントが明確であり、その動作が理解可能かつ予測可能であると思える必要があります。
  • リソース要件を最小化せよ。Linkerdは特にデータプレーン層(Podに注入されるサイドカープロキシ)において、パフォーマンスとリソースのコストを可能な限り最小限に抑える必要があります。
  • ただ機能せよ。Linkerdは既存のアプリケーションを壊してはならず、また、使い始めたり単純なことを実行したりするために複雑な構成※を必要とするべきではありません。

セットアップ

事前準備としてLinkerdがインストールされたローカルクラスターを用意します。
Linkerd CLIに加えて、以下のツールを使います。

全部いっぺんにインストールする

aquaがインストールされていれば、リポジトリのルートでaqua iを実行すればすべてのツールがインストールできます。

クラスター作成

just create-cluster

k3dで作ったクラスターにはtraefik Ingressコントローラーがついてくるため、Ingressオブジェクトを作ることでPodにリクエストを送るときkubectl port-forwardしなくて済みます。
Ingressにはlocalhost:54321でリクエストできるようになっています。

CRDインストール

LinkerdはCRDの取り扱いについて、Helmのベストプラクティスで推奨される二通りの方法のうちCRDのチャートを分離する方法を取っています。

just apply linkerd-crds

Linkerdインストール

just ca
just issuer
just apply linkerd-control-plane

Viz extensionインストール

just apply linkerd-viz

HTTPRouteリソース

https://gateway-api.sigs.k8s.io/concepts/gamma/

east/westユースケースにおいてはクラスターへの入口を表現するGatewayGatewayClassリソースは利用されないため、主にServiceへのルーティングを定義するXRouteリソースが主役になります。

XRouteというリソースがあるのではなく、HTTPRoute / TLSRoute / TCPRoute / UDPRoute があります。

この中でLinkerdが実装しているHTTPRouteを触ってみます。

https://gateway-api.sigs.k8s.io/api-types/httproute/

https://linkerd.io/2.14/features/httproute/

Linkerdをインストールすると、2種類のAPIグループでHTTPRoute CRDが使えるようになります。

  • gateway.networking.k8s.io
  • policy.linkerd.io

前者はGateway APIのアップストリーム準拠、後者はGateway Enhancement Proposals(GEPs)は出ているが、まだ仕様が固まっていない機能が先んじて盛り込まれた実験版になります。

LinkerdのHTTPRouteを触ってみる

チュートリアルに沿って試していきます。
過程で作成するリソースや実行するコマンドについて、より簡単に繰り返し試せるように、多少読み替えつつlinkerd-installationリポジトリに納めています。

以下、このリポジトリを使ってセットアップした前提で進めていきます。

Fault injectionをやってみる

https://linkerd.io/2.14/tasks/fault-injection/

なにができるのか?

HTTPRouteリソースを使用してトラフィックの一部を特定のバックエンドにリダイレクトすることで、アプリケーションに障害を簡単に注入できます。

サンプルアプリケーションのデプロイ

https://github.com/BuoyantIO/booksapp

以下の構成をもつサンプルを利用し、booksバックエンドへのアクセスに障害を注入してみます。

books-app-topology

just booksapp

authorsサービスはデフォルトで一定の失敗が発生するように設定されています(FAILURE_RATE環境変数)が、これを常に成功するように変更します。

        env:
        - name: DATABASE_URL
          value: sqlite3:db/db.sqlite3
        - name: BOOKS_SITE
          value: http://books:7002
        - name: FAILURE_RATE
          value: "0.5"

kubectl patchで環境変数を削除することで常に成功するようになります。

kubectl -n booksapp patch deploy authors \
  --type='json' \
  -p='[{"op":"remove", "path":"/spec/template/spec/containers/0/env/2"}]'

patchしてしばらくするとsuccess rateが100%になることを確認できます。

linkerd viz -n booksapp stat deploy
NAME      MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
authors      1/1   100.00%   4.3rps           3ms          42ms          82ms          8
books        1/1   100.00%   5.7rps           6ms          70ms          94ms          8
traffic      1/1   100.00%   0.3rps           1ms           1ms           1ms          2
webapp       3/3   100.00%   5.9rps          21ms          64ms          93ms         10

ダッシュボードでも同様に確認できます(linkerd-viz.local:54321でアクセスできます)。
books-app-success

障害を再現するバックエンドの用意

500を常に返すよう設定されたNGINXをデプロイします。

kubectl apply -f booksapp/failure.yaml

https://github.com/mikutas/linkerd-installation/blob/6b8666934e729fcad5b8cb1237d1a7222fe9444c/booksapp/failure.yaml#L7-L16

HTTPRouteの作成

kubectl apply -f booksapp/fault-injection-httproute.yaml

books行きのリクエストのうち、10%のリクエストを500を返すNGINXに送るように定義されています。

https://github.com/mikutas/linkerd-installation/blob/6b8666934e729fcad5b8cb1237d1a7222fe9444c/booksapp/fault-injection-httproute.yaml#L12-L19

エラーの確認

webappから発されたリクエストの成功率を確認してみると以下のようになります。

linkerd viz stat -n booksapp deploy --from deploy/webapp
NAME             MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
authors             1/1    95.58%   4.2rps           5ms          45ms          81ms          3
books               1/1   100.00%   5.9rps           8ms          77ms          95ms          6
error-injector      1/1     0.00%   0.6rps           1ms           2ms           2ms          3
books 100%?

コマンドの出力はwebappから受け取ったリクエストに対して各Deployment自身が返したレスポンスの成功率なので、

  • authors: 裏のbooksに送ったリクエストが一部NGINXに飛ばされるため100%にならない
    • booksにリクエストを送る経路はauthorsからだけではないため90%より高い
  • books: ここまで届いたリクエストには正常にレスポンスが返されるため100%
  • error-injector: 常に500を返しているため0%

経路が二股に分かれますが結局はwebappから出発するため、webappの成功率を確認すると約90%になっています。

linkerd viz stat -n booksapp deploy/webapp
NAME     MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
webapp      3/3    90.07%   8.9rps          19ms          77ms          95ms         10

backendRefsとweightの他の使い道

Fault injection以外のシナリオとして、backendRefsbooksの異なるバージョンを定義すればcanary releaseになります。

タイムアウトを設定してみる

https://linkerd.io/2.14/tasks/configuring-timeouts/#using-httproutes

なにができるのか?

Serviceの親を持つアウトバウンドHTTPRoutesに対して、GEP-1742で指定されているタイムアウトをサポートします。

サンプルアプリケーションのデプロイ

ドキュメントには一気通貫の完全なチュートリアルがありませんが、booksappを使って試せます。
Fault Injectionのときと同じにkubectl patchでauthorsサービスが常に成功する状態を作り、成功率100%であることを確認します。

linkerd viz -n booksapp stat deploy
NAME             MESHED   SUCCESS      RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
authors             1/1   100.00%   6.7rps           3ms          33ms          45ms          6
books               1/1   100.00%   8.8rps           5ms          69ms          94ms          7
error-injector      1/1   100.00%   0.3rps           1ms           1ms           1ms          2
traffic             1/1   100.00%   0.3rps           1ms           1ms           1ms          2
webapp              3/3   100.00%   8.8rps          20ms          73ms          95ms         11

HTTPRouteの作成

authorsへのアクセスにタイムアウトを設けてみます。

kubectl apply booksapp/timeouts-httproute.yaml

https://github.com/mikutas/linkerd-installation/blob/f73a550a202024aeaf3356dd8c210c595fa0bfe9/booksapp/timeouts-httproute.yaml#L7-L20

requestbackendRequestの違いは以下の通りです。

  • request
    • プロキシがリクエストを受信したときに開始され、成功した応答がクライアントに送信されたときに終了します。

      • =自分(この場合authors)がリクエストを受信してから自分のクライアントに返事するまで
  • backendRequest
    • リクエストがバックエンドにディスパッチされたときに開始され、そのバックエンドから応答を受信したときに終了します。

      • =自分のバックエンド(この場合books)にリクエストしてから自分が返事をもらうまで

webappからauthorsへのリクエストが一部504で返ってくるようになるため、webappの成功率が下がることが確認できます。

linkerd viz -n booksapp stat deploy
NAME             MESHED   SUCCESS       RPS   LATENCY_P50   LATENCY_P95   LATENCY_P99   TCP_CONN
authors             1/1   100.00%    0.4rps           1ms           9ms          10ms          6
books               1/1   100.00%    0.4rps           1ms           4ms           4ms          5
error-injector      1/1   100.00%    0.3rps           1ms           2ms           2ms          2
traffic             1/1   100.00%    0.3rps           1ms           2ms           2ms          2
webapp              3/3    11.41%   10.5rps           5ms          10ms          87ms         11

Dynamic request routingをやってみる

https://linkerd.io/2.14/tasks/configuring-dynamic-request-routing/

なにができるのか?

リクエストヘッダーの内容に基づいてHTTPトラフィックをルーティングできます。これは、A/B テストやその他のトラフィック管理戦略の実行に役立ちます。

「その他のトラフィック管理戦略」の一つはcanary releaseで、weightによる無差別な振り分けと違い、狙ったリクエストを流し分けることができます。

https://github.com/stefanprodan/podinfo

ここではpodinfoアプリケーションを利用して1つのフロントエンドと2つのバックエンドが存在する環境を作り、バックエンドへのリクエストをHTTPRouteによって送り分けます。

サンプルアプリケーションのデプロイ

just apply backend-a
just apply backend-b
just apply frontend

backend aとbackend bはリクエストが届いたとき自分の名前を答えるように設定されています。

https://github.com/mikutas/linkerd-installation/blob/afc2e83e1bb56f1d98de6b9a3e76767186a47608/podinfo/helmfile.yaml#L12-L13
https://github.com/mikutas/linkerd-installation/blob/afc2e83e1bb56f1d98de6b9a3e76767186a47608/podinfo/helmfile.yaml#L26-L27

frontendはbackend-a-podinfo Serviceにリクエストを送るように設定されています。

https://github.com/mikutas/linkerd-installation/blob/afc2e83e1bb56f1d98de6b9a3e76767186a47608/podinfo/helmfile.yaml#L35

リクエストを送ってみる

この段階ではbackend bにリクエストが届くことはありません。

curl -sX POST podinfo.local:54321/echo | grep -o 'PODINFO_UI_MESSAGE=. backend'
PODINFO_UI_MESSAGE=A backend

HTTPRouteの作成

kubectl apply -f podinfo/httproute.yaml

https://github.com/mikutas/linkerd-installation/blob/afc2e83e1bb56f1d98de6b9a3e76767186a47608/podinfo/httproute.yaml#L6-L22

HTTPRouteにより、backend-a-podinfo Service行きのリクエストに分岐を追加しています。
x-request-idヘッダーの値がalternativeである場合、リクエストはbackend-a-podinfoではなくbackend-b-podinfoに送られるようになります。

リクエストを送ってみる

さっきと同じリクエストは引き続きbackend aに届きます。

curl -sX POST podinfo.local:54321/echo | grep -o 'PODINFO_UI_MESSAGE=. backend'
PODINFO_UI_MESSAGE=A backend

x-request-id: alternativeヘッダーを付けたリクエストはbackend bに届きます。

curl -sX POST -H 'x-request-id: alternative' \
podinfo.local:54321/echo | grep -o 'PODINFO_UI_MESSAGE=. backend'
PODINFO_UI_MESSAGE=B backend

x-request-idヘッダーがあっても値が違うとbackend aに届きます。

curl -sX POST -H 'x-request-id: foo' \
podinfo.local:54321/echo | grep -o 'PODINFO_UI_MESSAGE=. backend'
PODINFO_UI_MESSAGE=A backend

Authorization policyを定義してみる

https://linkerd.io/2.14/tasks/configuring-per-route-policy/

なにができるのか?

Serviceレベルで認可を強制するだけでなく、個々のHTTPルートに対してより詳細な認可ポリシーを構成することもできます。

今回はHTTPRoute以外のカスタムリソースServer / AuthorizationPolicy / NetworkAuthentication / MeshTLSAuthenticationも登場します。

サンプルアプリケーションのデプロイ

booksappをデプロイし、kubectl patchauthorsFAILURE_RATE環境変数を消しておきます。

アプリの構成をおさらいすると、authorsサービスに対してwebappbooksはいずれもクライアントの立場になります。

books-app-topology

ただしauthorsに送るリクエストの内容はそれぞれ異なり、
booksサービスはGETリクエストを/authors/:id.jsonにのみ送信する必要があります。
一方webappサービスは、さらにDELETEリクエストとPUTリクエストを/authorsに送信し、POSTリクエストを/authors.jsonに送信することもあります。

今はまだサービス間の通信に何のポリシーも課していないため、authorsに対して未認証のすべてのリクエストとprobeのためのリクエストが許可されています。

linkerd viz authz -n booksapp deploy/authors
ROUTE    SERVER                       AUTHORIZATION                UNAUTHORIZED  SUCCESS     RPS  LATENCY_P50  LATENCY_P95  LATENCY_P99
default  default:all-unauthenticated  default/all-unauthenticated        0.0rps  100.00%  6.8rps          3ms         42ms         66ms
probe    default:all-unauthenticated  default/probe                      0.0rps  100.00%  0.3rps          1ms          1ms          1ms

Serverの作成

ServerはselectorにマッチしたPodを指すという点でServiceに似たリソースです。基本的に単体で使うものではなく、HTTPRouteやAuthorizationPolicyから参照されます。

authorsのPodを指すServerを作ります。

kubectl apply -f booksapp/server.yaml

https://github.com/mikutas/linkerd-installation/blob/3f866a494df7ae6516538c8ac3afde71c2263bae/booksapp/server.yaml#L1-L11

Serverが存在すると、明示的に許可するまで対象Podへのトラフィックは拒否されるため、authorsにはリクエストできない状態になりました。

linkerd viz authz -n booksapp deploy/authors
ROUTE    SERVER                       AUTHORIZATION                UNAUTHORIZED  SUCCESS     RPS  LATENCY_P50  LATENCY_P95  LATENCY_P99
default  authors-server                                                  5.1rps    0.00%  0.0rps          0ms          0ms          0ms
probe    authors-server               default/probe                      0.0rps  100.00%  0.1rps          3ms          3ms          3ms
default  default:all-unauthenticated  default/all-unauthenticated        0.0rps  100.00%  0.1rps          1ms          1ms          1ms
probe    default:all-unauthenticated  default/probe                      0.0rps  100.00%  0.1rps          1ms          1ms          1ms

ブラウザでアクセスしてみてもInternal Server Errorが表示されます。

500

ここからルート別に通信を許可するポリシーを作成していきます。

HTTPRouteの作成

authorsの特定パスへのGETリクストとマッチするHTTPRouteを作ります。

kubectl apply -f booksapp/authors-get-route.yaml

https://github.com/mikutas/linkerd-installation/blob/22a5d46a38699190f358a61fcb0ce06bbc624a53/booksapp/authors-get-route.yaml#L6-L19

AuthorizationPolicy / MeshTLSAuthenticationの作成

ここまでに定義したServerとルートについて通信を許可するポリシーを作成します。
requiredAuthenticationRefsで指定した条件を満たす場合、HTTPRouteで定義した経路でauthorsにリクエストを送ることが許可されます。

kubectl apply -f booksapp/authors-get-policy.yaml

https://github.com/mikutas/linkerd-installation/blob/22a5d46a38699190f358a61fcb0ce06bbc624a53/booksapp/authors-get-policy.yaml#L2-L15

https://github.com/mikutas/linkerd-installation/blob/22a5d46a38699190f358a61fcb0ce06bbc624a53/booksapp/authors-get-policy.yaml#L17-L25

ここでの条件はServiceAccountがbookswebappであることです。

authorsのreadiness probeを通す

authorsのメインコンテナにはreadiness probeが定義されていますが、現在は通らなくなっています(明示的な許可がないため)。

    readinessProbe:
      failureThreshold: 3
      httpGet:
        path: /ping
        port: 7002
        scheme: HTTP

これを通すためのルートとポリシーを作ります。
まずprobeとマッチするHTTPRouteを定義し、

https://github.com/mikutas/linkerd-installation/blob/6dd0904a9c63c35bfe738c5524c1bb9ac249e5bc/booksapp/probe-route-policy.yaml#L7-L16

AuthorizationPolicyでこのルートを許可します。
https://github.com/mikutas/linkerd-installation/blob/6dd0904a9c63c35bfe738c5524c1bb9ac249e5bc/booksapp/probe-route-policy.yaml#L28-L41

許可の条件をNetworkAuthenticationで定義します。
Probeの送信元はメッシュされていないkubeletのため、MeshTLSAuthenticationではなくNetworkAuthenticationを使い、ローカルネットワークからの通信を許可します。
https://github.com/mikutas/linkerd-installation/blob/6dd0904a9c63c35bfe738c5524c1bb9ac249e5bc/booksapp/probe-route-policy.yaml#L18-L26

kubectl apply -f booksapp/probe-route-policy.yaml

ここまでくるとトップページは正常に見えるようになります。

books-top

しかし、著者の削除ボタンを押すとNot foundが表示されます。

delete-author
not-found

一部パスへのGETを許可するポリシーしかまだ作成していないため、著者の作成や削除といった操作はエラーを引き起こします。

webapp->authors 変更操作の許可

booksappが正常に機能できるように最後のルートとポリシーを定義します。

kubectl apply -f booksapp/authors-modify-route.yaml

authors-serverへのDELETE / PUT / POSTリクエストにマッチさせています。

https://github.com/mikutas/linkerd-installation/blob/c16915ae341aa5d8da2c7d76d45cce97efe4b0df/booksapp/authors-modify-route.yaml#L8-L24

kubectl apply -f booksapp/authors-modify-policy.yaml

booksからauthorsへはDELETE / PUT / POSTを行わないため、今回のAuthorizationPolicyではwebapp ServiceAccountのみを許可します。

https://github.com/mikutas/linkerd-installation/blob/f9da2a8adbf509c1dc765001a75bc482da9d3fcb/booksapp/authors-modify-policy.yaml#L2-L15

https://github.com/mikutas/linkerd-installation/blob/f9da2a8adbf509c1dc765001a75bc482da9d3fcb/booksapp/authors-modify-policy.yaml#L17-L24

著者を追加してみると、成功するようになりました。

create-author

created-author

Discussion