IstioのEnvoyへの中継方法を理解したい

# Istioのiptablesを理解したい
HUIT Advent Calendar 2023の14日目の記事です。
Istioを触ってて気になったことを書き残していきます。



## 初めに
IstioのEnvoyをサイドカーとして挿入する構造はわかりやすいですが、 目的地のアドレスを入力するだけで自動的にEnvoyを通す仕組みが気になり調べてみました。

## Version
`Istio: 1.20.1`

## Istioとは
雑に説明すると、Kubernetesで扱えるサービスメッシュ実装の１つ。
ここら辺はわかりやすい記事が沢山あるので貼っておきます。

[新卒でもわかる！Istioとは？ | SIOS Tech. Lab](https://tech-lab.sios.jp/archives/35957)

## Istioの構造

大まかにコントロールプレーンとデータプレーンの２つに分かれています。
コントロールプレーンではポリシーやプロキシ設定の管理を。
データプレーンでは各PodにEnvoyをプロキシーとして配置、サービス間の通信を制御する構成です。

![Istio Image](https://camo.qiitausercontent.com/071618620ee974394ea6e96afeb7c613ccea5fcf/68747470733a2f2f697374696f2e696f2f6c61746573742f646f63732f6f70732f6465706c6f796d656e742f6172636869746563747572652f617263682e737667)

## 全ての通信をEnvoyに通すために
アプリケーションからはProxyの存在を意識することなく、目的地のアドレスを指定して通信することができます。
ここで、その状態でProxyを通すにはどうしているのか気になりました。

調べてみると、Istioではサービスからの通信を、iptablesを用いて制御しているらしいです。
これはPod毎に指定されており、INPUT, OUTPUTそれぞれにおいてEnvoyに一度リダイレクトするように指定されています。


## iptablesの設定を確認する
iptablesの設定を見ていきたいです。
手元のkindで簡単なnginxサーバーを構築してIstioをインストールしてみました。


Pod内のコンテナはネットワーク名前空間を共有しているので、iptablesやport,deviceなどは共有されているみたいです。（初めはコンテナが２つあるので、iptablesが別々に設定されるものかと思ってた）

下の実行結果から、nginxとenvoyのプロセスが同一のネットワーク名前空間に属していることがわかります。

```bash:network namespace
# docker execでノード内に入り、lsnsコマンドで確認 （4026533805は名前空間のid)

$ lsns 4026533805
  PID  PPID USER  COMMAND
 1122  1102 65535 /pause
 1252  1102 root  nginx: master process nginx -g daemon off;
 1297  1252 _rpc  |-nginx: worker process
 1298  1252 _rpc  |-nginx: worker process
 1299  1252 _rpc  |-nginx: worker process
 1300  1252 _rpc  |-nginx: worker process
 1301  1252 _rpc  |-nginx: worker process
 1302  1252 _rpc  |-nginx: worker process
 1304  1252 _rpc  |-nginx: worker process
 1305  1252 _rpc  `-nginx: worker process
 1314  1102 1337  /usr/local/bin/pilot-agent proxy sidecar --domain default.svc.cluster.local --proxyLogLevel=warning --proxyComponentLogLevel=misc:error --log_output_level=default:info
 1338  1314 1337  `-/usr/local/bin/envoy -c etc/istio/proxy/envoy-rev.json
```




それでnginxのコンテナに入り、iptablesコマンドで確認したものは以下になります。
```bash:iptables     
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
ISTIO_INBOUND  tcp  --  anywhere             anywhere            

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
ISTIO_OUTPUT  tcp  --  anywhere             anywhere            

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain ISTIO_INBOUND (1 references)
target     prot opt source               destination         
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15008
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15090
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15021
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15020
ISTIO_IN_REDIRECT  tcp  --  anywhere             anywhere            

Chain ISTIO_IN_REDIRECT (3 references)
target     prot opt source               destination         
REDIRECT   tcp  --  anywhere             anywhere             redir ports 15006

Chain ISTIO_OUTPUT (1 references)
target     prot opt source               destination         
RETURN     all  --  127.0.0.6            anywhere            
ISTIO_IN_REDIRECT  tcp  --  anywhere            !localhost            tcp dpt:!15008 owner UID match 1337
RETURN     all  --  anywhere             anywhere             ! owner UID match 1337
RETURN     all  --  anywhere             anywhere             owner UID match 1337
ISTIO_IN_REDIRECT  tcp  --  anywhere            !localhost            tcp dpt:!15008 owner GID match 1337
RETURN     all  --  anywhere             anywhere             ! owner GID match 1337
RETURN     all  --  anywhere             anywhere             owner GID match 1337
RETURN     all  --  anywhere             localhost           
ISTIO_REDIRECT  all  --  anywhere             anywhere            

Chain ISTIO_REDIRECT (1 references)
target     prot opt source               destination         
REDIRECT   tcp  --  anywhere             anywhere             redir ports 15001
```

:::note info
- UIDの1337はEnovyを実行しているユーザー
- Envoyの重要なポート番号
    - 15001: Envoy outbound
    - 15006: Envoy inbound 
:::


他のポート番号は公式ドキュメントを参照
https://istio.io/latest/docs/ops/deployment/requirements/


これだけでは理解が難しいので、入力と出力の２種類を分けて詳しくみていきます。

## Outbound

アプリケーションからの出力では、`Servcie -> Envoy`と`Envoy -> Service`の２通りに分けて考えることができます。

### Service  -> Envoy
![IstioOutboundToEnvoy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453151/70b519bb-6578-b5ba-52de-64471434107c.png)

このルートの目的は、アプリケーションからのリクエストをEnvoyの15001ポートにリダイレクトすることになります。

ルーティングテーブルを追うと、
`OUTPUT` -> `ISTIO_OUTPUT` -> `ISTIO_REDIRECT` -> `REDIRECT(15001)`
となっていました。

基本的な全てのリクエストは`ISTIO_OUTPUT`の一番下のルールに引っ掛かり、`ISTIO_REDIRECT`チェーンでport:`15001`にリダイレクトしています。

ここでNATでパケットを書き換えることなく、リダイレクトすることで目的地のIPアドレスをそのままにするのがポイントになっていそう。



### Envoy -> Service
![IstioOutboundEnvoyToService.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453151/d8104230-a4ec-234f-8129-8b1e567eac52.png)

このルートの目的は、Envoyから目的地にリクエストを投げることです。
ルーティングテーブルを追うと、
`OUTPUT` ->`ISTIO_OUTPUT` -> `RETURN` -> `POSTROUTING`
となっています。
UIDがEnvoyの1337からのリクエストなので、`ISTIO_OUTPUT`の上から４番目のルールにヒットし`RETURN`されます。
この仕組みでリクエストがループすることを防いでいます。


## Inbound

外部ネットワークからの入力でも同様に、`Servcie -> Envoy`と`Envoy -> Service`の２通りに分けて考えることができます。

### Service -> Envoy
![IstioInboundToEnvoy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453151/a5604802-d339-fccb-422a-dc2c3dc7de6b.png)
このルートの目的は、外部からの通信をEnvoyの15006ポートにリダイレクトすることです。
`PREROUTING` -> `ISTIO_INBOUND` -> `ISTIO_IN_REDIRECT` -> `REDIRECT(15006)`
Istioの管理用ポートが予約されており、それは150~となります。それ以外のポートはISTIO_INBOUNDチェーン一番下の`ISTIO_IN_REDIRECT`にヒットするので、15006にリダイレクトされるようです。

### Envoy -> Service
![IstioEnvoyToPod.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1453151/a38f17be-01df-19cf-9bbd-befa48c0782a.png)
このルートの目的は、Envoyから目的地にリクエストを投げることです。
ルーティングテーブルを追うと、
`OUTPUT` ->`ISTIO_OUTPUT` -> `RETURN` -> `POSTROUTING`
inboundの時はEnvoyが送信元IPアドレスを`127.0.0.6`に書き換えるので、一番上のルールに引っ掛かりRETURNされます。


## 最後に
コンテナの通信をあれこれ調べるの楽しい！
初心者が頑張って調べた程度なので、間違ってる箇所があれば教えてください〜。



## 参考
- [iptables から理解する Istio 1.10 から変更された Inbound Forwarding](https://engineering.mercari.com/blog/entry/20211021-istio1-10-inbound-fowarding/)
- [istio-proxyがどのように通信を仲介しているかを知る](https://zenn.dev/tayusa/articles/aa54bbff3d0d2d)