waypoint を付けたら 503 が出た
目標
k3sの上の本物のアンビエントメッシュで、ztunnelが強制するL4と、waypointが強制するL7を分けて体験し、waypointを付けるときに一緒に直す必要があるものを、リクエストとログで確認します。
なぜ重要なのか
アンビエントはサイドカーをなくして軽くなりますが、ポリシーとルーティングが2つの層(ztunnel・waypoint)に分かれます。どの層が何を見るのかを知らないと、HTTPルールが黙って抜け落ち、waypointを付けた瞬間に正常だったトラフィックが途切れ、Pod IPに来るリクエストがL7ポリシーを迂回します。3つともマニフェストだけでは見えないので、本物のメッシュで体験して初めて身に付きます。
ステップ
kubectl apply -f /opt/fixtures/istlab/ambient-app.yamlで材料を載せ、Podの準備ができるまで待ってから、ネームスペースshopにistio.io/dataplane-mode=ambientラベルを付けてください(Podを作り直しません)。その後、/root/istlab-ambient/01-enroll.txtにweb_containers=(web-v1のPodのコンテナ名をカンマ区切りで)とprotocol=(istioctl ztunnel-config workloadsでshopのPodのPROTOCOL欄)の2行を書いてください。- clientから
http://web/を1回呼び、その接続を記録したztunnelのアクセスログの行(メッセージがconnection completeで、src.identityがclient、dst.identityがwebの行)を1つ、そのまま/root/istlab-ambient/02-ztunnel.logに保存してください。 /root/istlab-ambient/web-l4.yamlにAuthorizationPolicyのweb-l4(ネームスペースshop、セレクターapp: web、ALLOW、プリンシパルcluster.local/ns/shop/sa/client)を書いて適用してください。適用後、clientとoutsideのstrangerから、それぞれhttp://web.shop/を呼び、/root/istlab-ambient/03-l4.txtにclient=(ステータスコード)とstranger_exit=(strangerのcurlの終了コード)の2行を書いてください。/root/istlab-ambient/l7-wrong.yamlにAuthorizationPolicyのweb-get-only-l4(セレクターapp: web、ALLOW、プリンシパルclient、メソッドGET)を書いて適用し、そのポリシーのstatus.conditionsのZtunnelAccepted条件のreasonを/root/istlab-ambient/04-l7.txtにstatus_reason=の形で、clientのPOSTリクエストのステータスコードをpost_code=の形で書いてください。記録した後、このポリシーを削除します。istioctl waypoint apply -n shop --enroll-namespace --waitでshopにwaypointを付けてください。その直後のclientのhttp://web/のステータスコードを/root/istlab-ambient/05-waypoint.txtにcode_after_waypoint=の形で書き、原因を直してください。/root/istlab-ambient/web-l4.yamlの許可するプリンシパルを、waypointの身元cluster.local/ns/shop/sa/waypointだけに変えて、もう一度適用します。直した後、clientのhttp://web/は200、clientがweb-v1のPod IPで直接呼ぶと、接続が切れる必要があります。/root/istlab-ambient/web-get-only.yamlにAuthorizationPolicyのweb-get-onlyを書いて適用してください。targetRefsでサービスweb(group""、kindService)を指し、ALLOW、プリンシパルcluster.local/ns/shop/sa/client、メソッドGETにします。適用後、clientのGETは200、POSTは403である必要があります。/root/istlab-ambient/httproute.yamlにHTTPRouteのwebを書いて適用してください。親はサービスweb(group""、kindService、ポート80)、ルールは2つです。ヘッダーx-canary: yesならweb-v2、それ以外はweb-v1にします。適用後、clientのx-canary: yesのリクエストはv2、ヘッダーのないリクエストは常にv1である必要があります。/root/istlab-ambient/08-report.mdに6行を書き、その下に学んだことを4行以上書いてください。6行は、protocol=(ステップ1)、l7_without_waypoint=(ステップ4でGETルールが強制されていればenforced、抜け落ちていればignored)、broke_with_waypoint=(ステップ5のcode_after_waypoint)、l4_allowed_principal=(現在web-l4が許可しているプリンシパル)、post_via_waypoint=(現在のclientのPOSTのステータスコード)、direct_pod_ip=(現在clientがPod IPで直接呼ぶとallowedまたはrejected)です。
参考
- このVMは準備に2–4分かかります。ambientプロファイル(istiod・istio-cni・ztunnel)が
values.global.platform=k3sでインストールされています。 - アンビエントのPodにはサイドカーがないので、
kubectl -n shop exec client -- curl …のようにコンテナを指定しなくても構いません。 - ztunnelが知っているワークロード・サービスは
istioctl ztunnel-config workloads・istioctl ztunnel-config servicesで、ztunnelのログはkubectl -n istio-system logs ds/ztunnelで、waypointのログはkubectl -n shop logs deploy/waypointで見ます。 - ポリシーを変えた直後は、ztunnel・waypointに行き渡るまで数秒かかります。
- よくある失敗: HTTP条件(メソッド・パス)をセレクターのポリシーに書くケースです。waypointのポリシーは
targetRefsで付けます。
ラベル1つでメッシュに入れる: 再起動なしで
kubectl apply -f /opt/fixtures/istlab/ambient-app.yamlで材料を載せ、Podの準備ができるまで待ってから、ネームスペースshopにistio.io/dataplane-mode=ambientラベルを付けてください(Podを作り直しません)。その後、/root/istlab-ambient/01-enroll.txtにweb_containers=(web-v1のPodのコンテナ名をカンマ区切りで)とprotocol=(istioctl ztunnel-config workloadsでshopのPodのPROTOCOL欄)の2行を書いてください。
アンビエントでは、istio-cniのノードエージェントがPodのネットワークネームスペースの中にリダイレクト規則を仕込み、ノードごとに1つあるztunnelがそのトラフィックを受けます。サイドカーを注入しないので、Podを作り直す必要がありません。組み込まれたPodにはアノテーションambient.istio.io/redirection: enabledが付き、ztunnelが知っているワークロード一覧で、プロトコルがHBONEに変わります。メッシュ外のstrangerはTCPのままです。
ztunnelが見た2つの身元
clientからhttp://web/を1回呼び、その接続を記録したztunnelのアクセスログの行(メッセージがconnection completeで、src.identityがclient、dst.identityがwebの行)を1つ、そのまま/root/istlab-ambient/02-ztunnel.logに保存してください。
アンビエントのmTLSは、ztunnel同士がHBONE(HTTP CONNECTの上のmTLSトンネル、ポート15008)で張ります。ztunnelはL4プロキシなので、リクエストのパスやメソッドは知りませんが、両側のワークロードのSPIFFEの身元は知っています。ログはkubectl -n istio-system logs ds/ztunnelで見て、1つの接続が送信側と受信側で1行ずつ残ることがあります。dst.hbone_addrが付いた行が、HBONEで入った接続です。
ztunnelが強制するL4ポリシー
/root/istlab-ambient/web-l4.yamlにAuthorizationPolicyのweb-l4(ネームスペースshop、セレクターapp: web、ALLOW、プリンシパルcluster.local/ns/shop/sa/client)を書いて適用してください。適用後、clientとoutsideのstrangerから、それぞれhttp://web.shop/を呼び、/root/istlab-ambient/03-l4.txtにclient=(ステータスコード)とstranger_exit=(strangerのcurlの終了コード)の2行を書いてください。
セレクターで付けたポリシーは、そのPodのztunnelが強制します。ztunnelは接続を受けた瞬間に送信元の身元を見て許可を決めるので、拒否はHTTP 403ではなく接続の切断(curlの終了コード56)として現れます。メッシュ外のstrangerは身元がなく、どのプリンシパルのルールにも一致しません。終了コードは…; echo $?で見ます。
waypointなしで書いたHTTPルールはどうなるか
/root/istlab-ambient/l7-wrong.yamlにAuthorizationPolicyのweb-get-only-l4(セレクターapp: web、ALLOW、プリンシパルclient、メソッドGET)を書いて適用し、そのポリシーのstatus.conditionsのZtunnelAccepted条件のreasonを/root/istlab-ambient/04-l7.txtにstatus_reason=の形で、clientのPOSTリクエストのステータスコードをpost_code=の形で書いてください。記録した後、このポリシーを削除します。
ztunnelはHTTPを解釈しません。メソッド・パスのような条件が入ったルールをセレクターのポリシーで与えると、istiodはそのルールを除いてztunnelに送り、ポリシーのステータスにその事実を書いておきます。ALLOWポリシー同士は「1つでも一致すれば許可」なので、すでにあるweb-l4がclientを許可していれば、POSTも通ります。GETだけを許可すると書いたのにPOSTが通るのです。ステータスはkubectl -n shop get authorizationpolicy web-get-only-l4 -o yamlで見ます。
waypointを付けたら、正常だったリクエストが503になる
istioctl waypoint apply -n shop --enroll-namespace --waitでshopにwaypointを付けてください。その直後のclientのhttp://web/のステータスコードを/root/istlab-ambient/05-waypoint.txtにcode_after_waypoint=の形で書き、原因を直してください。/root/istlab-ambient/web-l4.yamlの許可するプリンシパルを、waypointの身元cluster.local/ns/shop/sa/waypointだけに変えて、もう一度適用します。直した後、clientのhttp://web/は200、clientがweb-v1のPod IPで直接呼ぶと、接続が切れる必要があります。
waypointができると、サービスへ向かうリクエストは、clientのztunnel → waypoint → webのztunnelの順に進みます。そのため、webのztunnelが見る送信元は、clientではなくwaypointです。clientだけを許可していたL4ポリシーがその接続を切り、waypointは503を返します(waypointのログのtunnel_response:401)。waypointの身元だけを許可すれば、サービスへ来る道は開き、waypointを通らない道(Pod IPで直接)は閉じます。サービス用のwaypointは、Pod IPで来たリクエストを通さないので、このように閉じないと、ステップ7のL7ポリシーを迂回されます。
HTTPルールはwaypointにかける
/root/istlab-ambient/web-get-only.yamlにAuthorizationPolicyのweb-get-onlyを書いて適用してください。targetRefsでサービスweb(group ""、kind Service)を指し、ALLOW、プリンシパルcluster.local/ns/shop/sa/client、メソッドGETにします。適用後、clientのGETは200、POSTは403である必要があります。
waypointはEnvoyなのでHTTPを解釈します。ポリシーをwaypointに付ける方法は、セレクターではなくtargetRefsです。サービスを指すと、そのサービスへ向かうリクエストを処理するwaypointが強制します。waypointで見える送信元はclient自身なので、プリンシパルはclientにします。拒否は今度は接続の切断ではなく、HTTP 403(RBAC: access denied)です。
waypointがルーティングも担当する: HTTPRoute
/root/istlab-ambient/httproute.yamlにHTTPRouteのwebを書いて適用してください。親はサービスweb(group ""、kind Service、ポート80)、ルールは2つです。ヘッダーx-canary: yesならweb-v2、それ以外はweb-v1にします。適用後、clientのx-canary: yesのリクエストはv2、ヘッダーのないリクエストは常にv1である必要があります。
アンビエントでは、メッシュ内のルーティングもGateway APIで書きます。親がGatewayではなくサービスである点が、Ingressと違います(GAMMA)。このルートを実際に処理するのはwebのwaypointなので、waypointがなければルートは適用されません。付いたかどうかは、HTTPRouteのステータスのResolvedWaypoints条件で見ます。
L4とL7を誰が担当するかを整理する
/root/istlab-ambient/08-report.mdに6行を書き、その下に学んだことを4行以上書いてください。6行は、protocol=(ステップ1)、l7_without_waypoint=(ステップ4でGETルールが強制されていればenforced、抜け落ちていればignored)、broke_with_waypoint=(ステップ5のcode_after_waypoint)、l4_allowed_principal=(現在web-l4が許可しているプリンシパル)、post_via_waypoint=(現在のclientのPOSTのステータスコード)、direct_pod_ip=(現在clientがPod IPで直接呼ぶとallowedまたはrejected)です。
前のステップの記録と、現在の状態を一緒に見てください。説明の行には、「ztunnelとwaypointが、それぞれ何を見て何を強制するか」と「waypointを付けるときに、L4ポリシーをなぜ一緒に直す必要があるか」を書いておいてください。