Istio 1.21 で "Support for all CNIs in ambient mode" になったので挙動を確認してみる

3月13日に Istio 1.21.0 のリリースがアナウンスされました。リリースノートの中でも "Support for all CNIs in ambient mode" が気になったので少し調べてみました。

"Support for all CNIs in ambient mode" とは?

"Support for all CNIs in ambient mode" は文字通り Ambient Mode がすべての CNI でサポートしたようです。つまり元々何か問題があって、それを解決した実装がこのバージョンから入ったことを意味します。リリースノートにもリンクされていた下記の記事を読むと技術的な背景などが書いてあります。

istio.io

何が問題だったのか

Istio Ambient Mode では Pod と ztunnel 間の Traffic Redirection を設定します。Istio 1.21.0 より前では Pod の起動時に istio-cni が Pod が起動している Node の Network namespace に Traffic Redirection を設定します。istio-cni はメインとなる CNI (e.g. Calico, Cilium) を拡張するものとなります。つまりメインの CNI と istio-cni は同じ network namespace の設定を変更するためコンフリクトが起きるケースがあったようです。その結果意図しないトラフィックの中断や Network Policy が動かないなどの問題が起きていたようです。

解決策

解決策として実装されたのが Traffic Redirection を node の network namespace ではなく、pod の network namespace に設定するというものでした。これは Sidecar と似たアプローチになります。違いとしては Sidecar の時は istio-proxy とアプリケーションが同じ Pod 内にあるため pod の network namespace を使用した traffic redirection が機能しました。しかし Ambient mode では ztunnel が Pod の外側にあります。そこで Istio では Socket を介すことで別の network namespace を参照する Linux socket API の機能を使用したようです。これにより Pod の network namespace からは ztunnel が見えますが、 node の network namespace からは見えないのでメインの CNI との競合が解消されたようです。

より技術的な詳細を知りたい方は記事中盤の Technical deep dive of in-Pod traffic redirection を読んでください。

動作確認

今回のポイントは Pod の network namespace への traffic redirection の設定ということが分かったので実際に見てみました。

確認に使用した環境は下記です。

今回は ambient mode を namespace で有効にする前と後の下記の結果の違いを確認しました。

これらは node 内から nsenter を使って確認していきます。 nsenter の使用に関しては下記の記事をご覧ください。

hatappi.blog

Ambient Mode 有効前

TCP でのポート待ち受け状態を確認します。下記の結果からわかるように Ruby のプロセスが 3000 port で待ち受けていることが分かります。

$ nsenter -t 12783 -n -- ss -tlpn
State                     Recv-Q                    Send-Q                                         Local Address:Port                                         Peer Address:Port                    Process
LISTEN                    0                         1024                                                 0.0.0.0:3000                                              0.0.0.0:*                        users:(("ruby",pid=12783,fd=7))

次は iptables を確認してみます。どのテーブルにおいても設定されているルールはありませんでした。

# filter table
$ nsenter -t 12783 -n -- iptables -vL -t filter
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

# nat table
$ nsenter -t 12783 -n -- iptables -vL -t nat
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

# mangle table
$ nsenter -t 12783 -n -- iptables -vL -t mangle
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

# raw table
$ nsenter -t 12783 -n -- iptables -vL -t raw
Chain PREROUTING (policy ACCEPT 745 packets, 365K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 734 packets, 341K bytes)
 pkts bytes target     prot opt in     out     source               destination

Ambient Mode 有効後

Ambient を有効にするために kubectl label namespace [namespace] istio.io/dataplane-mode=ambient で対象の namespace にラベルを付与します。Ambient mode は有効にした後の Pod の再起動は必要ありません。

ss の結果は下記になりました。さきほどの ruby process に加えて ztunnel のプロセスが4つ追加されました。

$ nsenter -t 12783 -n -- ss -tlpn
State                  Recv-Q                 Send-Q                                 Local Address:Port                                  Peer Address:Port                Process
LISTEN                 0                      128                                        127.0.0.1:15080                                      0.0.0.0:*                    users:(("ztunnel",pid=20510,fd=60))
LISTEN                 0                      1024                                         0.0.0.0:3000                                       0.0.0.0:*                    users:(("ruby",pid=12783,fd=7))
LISTEN                 0                      128                                                *:15001                                            *:*                    users:(("ztunnel",pid=20510,fd=47))
LISTEN                 0                      128                                                *:15006                                            *:*                    users:(("ztunnel",pid=20510,fd=29))
LISTEN                 0                      128                                                *:15008                                            *:*                    users:(("ztunnel",pid=20510,fd=28))

続いて iptables を確認します。各テーブルをみると新しいルールがいくつか追加されています。さらに ISTIO_* の新しいチェーンが追加されていることが分かります。

# filter table
$ nsenter -t 12783 -n -- iptables -vL -t filter
Chain INPUT (policy ACCEPT 528 packets, 42888 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 441 packets, 79061 bytes)
 pkts bytes target     prot opt in     out     source               destination

# nat table
$ nsenter -t 12783 -n -- iptables -vL -t nat
Chain PREROUTING (policy ACCEPT 64 packets, 3840 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain INPUT (policy ACCEPT 64 packets, 3840 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 29 packets, 2313 bytes)
 pkts bytes target     prot opt in     out     source               destination
   62  5434 ISTIO_OUTPUT  all  --  any    any     anywhere             anywhere

Chain POSTROUTING (policy ACCEPT 62 packets, 5434 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain ISTIO_OUTPUT (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  any    any     anywhere             169.254.7.127        tcp
    0     0 ACCEPT     tcp  --  any    any     anywhere             anywhere             mark match 0x111/0xfff
   20  1200 ACCEPT     all  --  any    lo      anywhere            !localhost
   13  1921 REDIRECT   tcp  --  any    any     anywhere            !localhost            mark match ! 0x539/0xfff redir ports 15001

# mangle table
$ nsenter -t 12783 -n -- iptables -vL -t mangle
Chain PREROUTING (policy ACCEPT 55 packets, 10545 bytes)
 pkts bytes target     prot opt in     out     source               destination
 4412 3243K ISTIO_PRERT  all  --  any    any     anywhere             anywhere

Chain INPUT (policy ACCEPT 4412 packets, 3243K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 4545 packets, 2983K bytes)
 pkts bytes target     prot opt in     out     source               destination
 4545 2983K ISTIO_OUTPUT  all  --  any    any     anywhere             anywhere

Chain POSTROUTING (policy ACCEPT 4545 packets, 2983K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain ISTIO_OUTPUT (1 references)
 pkts bytes target     prot opt in     out     source               destination
 2707 2046K CONNMARK   all  --  any    any     anywhere             anywhere             connmark match  0x111/0xfff CONNMARK restore

Chain ISTIO_PRERT (1 references)
 pkts bytes target     prot opt in     out     source               destination
   33  1880 CONNMARK   all  --  any    any     anywhere             anywhere             mark match 0x539/0xfff CONNMARK xset 0x111/0xfff
  282 20864 ACCEPT     tcp  --  any    any     169.254.7.127        anywhere             tcp
 1538 1720K ACCEPT     tcp  --  lo     any     anywhere            !localhost
    0     0 TPROXY     tcp  --  any    any     anywhere             anywhere             tcp dpt:15008 mark match ! 0x539/0xfff TPROXY redirect 0.0.0.0:15008 mark 0x111/0xfff
 2514 1490K ACCEPT     tcp  --  any    any     anywhere             anywhere             ctstate RELATED,ESTABLISHED
   23  1356 TPROXY     tcp  --  any    any     anywhere            !localhost            mark match ! 0x539/0xfff TPROXY redirect 0.0.0.0:15006 mark 0x111/0xfff

# raw table
$ nsenter -t 12783 -n -- iptables -vL -t raw
Chain PREROUTING (policy ACCEPT 5448 packets, 3633K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 5520 packets, 3368K bytes)
 pkts bytes target     prot opt in     out     source               destination

Rails アプリケーションは inbound と outbound なリクエストがあるので、順番に見ていきます。

inbound

Rails は 3000 port で plaintext なリクエストを受け付けています。ドキュメントによれば plaintext なリクエストは 15006 で listen している ztunnel に対して TPROXY を使用してパケットがリダイレクトされるようです。

iptables を見てみると mangle table 内にある ISTIO_PRERT チェーンが重要そうです。このチェーンは PREROUTING チェーンで全パケットのターゲットとして設定されています。 ISTIO_PRERT の中でも下記のルールが TPROXY redirect 0.0.0.0:15006 によってパケットを 15006 で Listen している ztunnel へと TPROXY を使用してリダイレクトしています。

   23  1356 TPROXY     tcp  --  any    any     anywhere            !localhost            mark match ! 0x539/0xfff TPROXY redirect 0.0.0.0:15006 mark 0x111/0xfff

outbound

ドキュメント を見るとパケットは 15001 で listen している ztunnel に対してリダイレクトされるようです。

iptables を見てみると nat table 内にある ISTIO_OUTPUT チェーンが重要そうです。このチェーンは OUTPUT チェーンで全パケットのターゲットとして設定されています。ISTIO_OUTPUT の中でも下記のルールが redir ports 15001 によってパケットを 15001 で Listen している ztunnel へとリダイレクトしています。

   13  1921 REDIRECT   tcp  --  any    any     anywhere            !localhost            mark match ! 0x539/0xfff redir ports 15001

まとめ

今回は Istio 1.21 で ambient mode がすべての CNI でサポートされてたのでその中身をブログを読み実際の設定を少し確認してみました。 Ambient mode は現状 Beta ですが、GA になるのが待ち遠しいですね!