なぜ書いているのか?
Istioをドキュメントを読んだり、動かしてみたりしていました。
しかし、タイトルに記載したSidecarリソースについてはいまいち理解できていませんでした。
個人的に抱えているこのモヤモヤをなくすためにIstioのSidecarリソースについて理解したことを書いていこうと思います。
IstioのSidecarリソースはいったい何者なのか
Istioの1.1にリリースされたカスタムリソースです。
ドキュメントには下記のように記載されています。
Sidecar describes the configuration of the sidecar proxy that mediates inbound and outbound communication to the workload instance it is attached to.
The Sidecar configuration provides a way to fine tune the set of ports, protocols that the proxy will accept when forwarding traffic to and from the workload. In addition, it is possible to restrict the set of services that the proxy can reach when forwarding outbound traffic from workload instances.
https://istio.io/latest/docs/reference/config/networking/sidecar/より抜粋
- Fine-tune the set of ports and protocols that an Envoy proxy accepts.
- Limit the set of services that the Envoy proxy can reach.
You can specify that you want a sidecar configuration to apply to all workloads in a particular namespace, or choose specific workloads using a workloadSelector.
https://istio.io/latest/docs/concepts/traffic-management/#sidecarsより抜粋
デフォルトの状態でアプリケーションをデプロイするとistio proxyはメッシュ内のすべてのワークロードと通信できるように設定を持ちます。
このSidecarリソースは、istio proxyが持つ通信の設定を調整したりすることができるリソースです。
例えば、ある特定のNamespaceにデプロイされているPodはそのNamespace内の他のPodとしか通信を行わないのに
デフォルトのままだと他のNamespaceと通信できるようにistio proxyに設定が注入されてしまいます。
セキュリティ面や後述するistio proxyのリソースの面でもSidecarリソースを使うことによって通信できる範囲を絞ることができます。
このあたりは実際にデプロイされているistio proxyの設定を確認したほうが見たほうがよさそうです。
しかし、設定をどう見るかについはEnvoyを少し知る必要があったので、少し整理してみたいと思います。
Envoyをざっくり理解する
ここではEnvoyが何者なのかは割愛しますが、istio proxyの設定を見るためにEnvoyで出てくる用語などについて書きます。
主な用語
downstream:Envoyに接続する。
Envoyをプロキシとしたサイドカーアプリケーションがデプロイされている場合
リクエストを送るクライアントがダウンストリームにあたる。
レスポンスを返すサイドカーアプリケーションがダウンストリームにあたる。
upstream : Envoyが受け取ったリクエストを転送する先を指す。
Envoyをプロキシとしたサイドカーアプリケーションがデプロイされている場合
Envoyがクラアントから受け取ったリクエストを転送する先であるサイドカーアプリケーションがアップストリームにあたる。
Envoyがサイドカーアプリケーションから受け取ったレスポンスを転送する先であるクライアントがアップストリームにあたる。
listener : Envoyがダウンストリームからの接続を受け入れるためのIPやポートなどの情報。
cluster : Envoyがアップストリームへ接続するため(転送するため)のエンドポイントのグループ
endpoint : クラスタが持つエンドポイントグループの1つ。クラスタとは親子関係になる。
router: Envoyが受け取ったリクエストをどのクラスタを選択してアップストリームに転送するかを決める経路情報。
HTTPヘッダで転送先を切り替えたり、リクエストのリトライの回数を制御したり、
パーセンテージをベースにしたトラフィックの分割、優先順位ベースのルーティング、、、などルートで設定できることは多岐にわたります。
Envoyがリクエストを受け取る情報はリスナーで管理され、そのリクエストを転送するための情報がクラスタで管理されている形になります。
routerによってリスナーとクラスタが紐づき、ダウンストリームからアップストリームへと転送できるようになります。
参考 :
https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request
EnvoyのxDSについて
Envoyは静的にも、動的にも設定を適用することができますが、ここではIstioが採用している形式に合わせて動的な設定にフォーカスします。
Envoyはファイルシステムによる設定変更やREST APIや、gRPCなど様々な手段で設定を変更することができますが、
特にREST APIやgRPCによる設定変更では、Envoyが管理サーバーに問い合わせて動的に設定を受け取り更新します。
この設定を提供するサービスとAPIをまとめてxDSと呼ばれています。
xには用語で説明したclusterやlistenerがあてはまります。
LDS(Listener Discovery Service) : Listenerの設定を受け取るためのサービス
CDS(Cluster Discovery Servide) : Clusterの設定を受け取るためのサービス
EDS(Endpoint Discovery Service) : Endpointの設定を受け取るためのサービス
RDS(Route Discovery Service) : Routeの設定を受け取るためのサービス
(ADSやSDSは省略しています。)
設定を提供するAPIのサーバーをコントロールプレーンと呼び、問い合せるEnvoyプロキシをデータプレーンと呼びます。
Istioのコントロールプレーン内のPilotというコンポーネントがまさにIstio Proxyの設定を提供していますね。
Istio Proxyの設定を見てみる
Envoyについてざっくりと確認したため実際にIstio Proxyにはどんな設定されているか見てみたいと思います。
例として
Namespace foo
と bar
にそれぞれdeployment(Pod1台)をapplyしてみます。
(それぞれのNamespaceにデプロイされたPod間での内部通信する要件はないとします。)
Namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: foo
labels:
istio-injection: enabled
---
apiVersion: v1
kind: Namespace
metadata:
name: bar
labels:
istio-injection: enabled
Deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-app
namespace: foo
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- name: test-app
image: test-app
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-app
namespace: bar
spec:
replicas: 1
selector:
matchLabels:
app: test-app
template:
metadata:
labels:
app: test-app
spec:
containers:
- name: test-app
image: test-app
ports:
- containerPort: 80
Service.yaml
apiVersion: v1
kind: Service
metadata:
name: foo
namespace: foo
spec:
selector:
app: test-app
ports:
- port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: bar
namespace: bar
spec:
selector:
app: test-app
ports:
- port: 80
targetPort: 80
これでIngressの設定次第などでは、Port 80で通信できるような構成になりました。
NamespaceとDeploymentをapplyしたので、istio-proxyの設定を確認してみたいと思います。
Istioにはデバッグ目的も含めproxyのステータスを確認するコマンドが用意されています。
% istioctl proxy-status
NAME CDS LDS EDS RDS ISTIOD VERSION
istio-ingressgateway-7576658c9b-gqrgh.istio-system SYNCED SYNCED SYNCED NOT SENT istiod-c85d85ddd-6nfmg 1.8.1
test-app-9dfd8f5-58t4d.foo SYNCED SYNCED SYNCED SYNCED istiod-c85d85ddd-6nfmg 1.8.1
test-app-9dfd8f5-x9m2q.bar SYNCED SYNCED SYNCED SYNCED istiod-c85d85ddd-6nfmg 1.8.1
前述のEnvoyのxDSに沿ってそれぞれのステータスが記載されています。
test-appのistio-proxyはどのサービスディスカバリでも SYNCED
になっているためistio-proxyは正常にinjectされているようです。
ちなみにステータスの状態は3種類存在します。
SYNCED : Istiodが最後に送った情報をistio-proxyが受け取り、確認応答を返した状態
NOT SENT : 送信するべき設定がなく、Istiodが何も送ってないことを示す状態
STALE : Istiodが送った設定の確認応答が返ってきていない状態
istio-proxyが正常に動いていることを確認することができたため、各設定も確認していこうと思います。
Listener
% istioctl proxy-config listener test-app-9dfd8f5-58t4d.foo
ADDRESS PORT MATCH DESTINATION
10.68.0.10 53 ALL Cluster: outbound|53||kube-dns.kube-system.svc.cluster.local
0.0.0.0 80 Trans: raw_buffer; App: HTTP Route: 80
0.0.0.0 80 ALL PassthroughCluster
10.68.13.187 80 Trans: raw_buffer; App: HTTP Route: bar.bar.svc.cluster.local:80
10.68.13.187 80 ALL Cluster: outbound|80||bar.bar.svc.cluster.local
10.68.4.198 80 Trans: raw_buffer; App: HTTP Route: foo.foo.svc.cluster.local:80
10.68.4.198 80 ALL Cluster: outbound|80||foo.foo.svc.cluster.local
10.68.0.1 443 ALL Cluster: outbound|443||kubernetes.default.svc.cluster.local
10.68.0.60 443 ALL Cluster: outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.68.10.133 443 ALL Cluster: outbound|443||istiod.istio-system.svc.cluster.local
10.68.11.98 443 Trans: raw_buffer; App: HTTP Route: metrics-server.kube-system.svc.cluster.local:443
10.68.11.98 443 ALL Cluster: outbound|443||metrics-server.kube-system.svc.cluster.local
0.0.0.0 15001 ALL PassthroughCluster
0.0.0.0 15001 Addr: *:15001 Non-HTTP/Non-TCP
0.0.0.0 15006 Addr: *:15006 Non-HTTP/Non-TCP
0.0.0.0 15006 Trans: tls; App: TCP TLS; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: raw_buffer; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: tls; App: HTTP TLS; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: raw_buffer; App: HTTP; Addr: 0.0.0.0/0 InboundPassthroughClusterIpv4
0.0.0.0 15006 Trans: tls; App: istio-http/1.0,istio-http/1.1,istio-h2; Addr: *:80 Cluster: inbound|80||
0.0.0.0 15006 Trans: raw_buffer; App: HTTP; Addr: *:80 Cluster: inbound|80||
0.0.0.0 15006 Trans: tls; App: TCP TLS; Addr: *:80 Cluster: inbound|80||
0.0.0.0 15006 Trans: raw_buffer; Addr: *:80 Cluster: inbound|80||
0.0.0.0 15006 Trans: tls; Addr: *:80 Cluster: inbound|80||
0.0.0.0 15010 Trans: raw_buffer; App: HTTP Route: 15010
0.0.0.0 15010 ALL PassthroughCluster
10.68.0.60 15012 ALL Cluster: outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.68.10.133 15012 ALL Cluster: outbound|15012||istiod.istio-system.svc.cluster.local
0.0.0.0 15014 Trans: raw_buffer; App: HTTP Route: 15014
0.0.0.0 15014 ALL PassthroughCluster
0.0.0.0 15021 ALL Inline Route: /healthz/ready*
10.68.0.60 15021 Trans: raw_buffer; App: HTTP Route: istio-ingressgateway.istio-system.svc.cluster.local:15021
10.68.0.60 15021 ALL Cluster: outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
0.0.0.0 15090 ALL Inline Route: /stats/prometheus*
10.68.0.60 15443 ALL Cluster: outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
routes
% istioctl proxy-config routes test-app-9dfd8f5-58t4d.foo
NOTE: This output only contains routes loaded via RDS.
NAME DOMAINS MATCH VIRTUAL SERVICE
80 bar.bar /*
80 default-http-backend.kube-system /*
80 foo /*
80 istio-ingressgateway.istio-system /*
15010 istiod.istio-system /*
15014 istiod.istio-system /*
istio-ingressgateway.istio-system.svc.cluster.local:15021 istio-ingressgateway.istio-system /*
metrics-server.kube-system.svc.cluster.local:443 metrics-server.kube-system /*
foo.foo.svc.cluster.local:80 foo /*
bar.bar.svc.cluster.local:80 bar.bar /*
* /stats/prometheus*
InboundPassthroughClusterIpv4 * /*
InboundPassthroughClusterIpv4 * /*
inbound|80|| * /*
* /healthz/ready*
inbound|80|| * /*
cluster
% istioctl proxy-config cluster test-app-9dfd8f5-58t4d.foo
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
80 - inbound STATIC
BlackHoleCluster - - - STATIC
InboundPassthroughClusterIpv4 - - - ORIGINAL_DST
PassthroughCluster - - - ORIGINAL_DST
agent - - - STATIC
bar.bar.svc.cluster.local 80 - outbound EDS
default-http-backend.kube-system.svc.cluster.local 80 - outbound EDS
foo.foo.svc.cluster.local 80 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 80 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 443 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 15012 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 15021 - outbound EDS
istio-ingressgateway.istio-system.svc.cluster.local 15443 - outbound EDS
istiod.istio-system.svc.cluster.local 443 - outbound EDS
istiod.istio-system.svc.cluster.local 15010 - outbound EDS
istiod.istio-system.svc.cluster.local 15012 - outbound EDS
istiod.istio-system.svc.cluster.local 15014 - outbound EDS
kube-dns.kube-system.svc.cluster.local 53 - outbound EDS
kubernetes.default.svc.cluster.local 443 - outbound EDS
metrics-server.kube-system.svc.cluster.local 443 - outbound EDS
prometheus_stats - - - STATIC
sds-grpc - - - STATIC
xds-grpc - - - STATIC
zipkin - - - STRICT_DNS
endpoints
% istioctl proxy-config endpoints test-app-9dfd8f5-58t4d.foo
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.64.0.10:80 HEALTHY OK outbound|80||foo.foo.svc.cluster.local
10.64.0.4:53 HEALTHY OK outbound|53||kube-dns.kube-system.svc.cluster.local
10.64.0.8:443 HEALTHY OK outbound|443||metrics-server.kube-system.svc.cluster.local
10.64.0.9:8080 HEALTHY OK outbound|80||default-http-backend.kube-system.svc.cluster.local
10.64.1.3:53 HEALTHY OK outbound|53||kube-dns.kube-system.svc.cluster.local
10.64.1.4:8080 HEALTHY OK outbound|80||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:8443 HEALTHY OK outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15012 HEALTHY OK outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15021 HEALTHY OK outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15443 HEALTHY OK outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.2.2:15010 HEALTHY OK outbound|15010||istiod.istio-system.svc.cluster.local
10.64.2.2:15012 HEALTHY OK outbound|15012||istiod.istio-system.svc.cluster.local
10.64.2.2:15014 HEALTHY OK outbound|15014||istiod.istio-system.svc.cluster.local
10.64.2.2:15017 HEALTHY OK outbound|443||istiod.istio-system.svc.cluster.local
10.64.2.3:80 HEALTHY OK outbound|80||bar.bar.svc.cluster.local
127.0.0.1:80 HEALTHY OK inbound|80||
127.0.0.1:15000 HEALTHY OK prometheus_stats
127.0.0.1:15020 HEALTHY OK agent
35.189.153.189:443 HEALTHY OK outbound|443||kubernetes.default.svc.cluster.local
unix://./etc/istio/proxy/SDS HEALTHY OK sds-grpc
unix://./etc/istio/proxy/XDS HEALTHY OK xds-grpc
どの設定もたくさんあります。
ちょっと気になるのがfooのPodはbarのPodとは通信する予定はないのに、通信できるような設定があります。
冒頭に記載しているようにデフォルトではサービスメッシュ内のすべてのワークロードを通信できるような設定が適用されてしまいます。
本来であればbarのPodとは疎通できなくても問題ないため上記のbarに関する設定は消したいところです。
消すためにはSidecarリソースを使用します。
やっと本題であるSidecarリソースを使っていきたいと思います。
Sidecarリソースを使ってみる
ドキュメントを参考にSidecarリソースをapplyしてみようと思います。
今回はメッシュ内の、istio-systemと疎通できるような設定にしてみます。
Sidecar.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: foo
namespace: foo
spec:
workloadSelector:
labels:
app: test-app
ingress:
- port:
number: 80
protocol: HTTP
defaultEndpoint: 127.0.0.1:80
egress:
- hosts:
- "istio-system/*"
---
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: bar
namespace: bar
spec:
workloadSelector:
labels:
app: test-app
ingress:
- port:
number: 80
protocol: HTTP
defaultEndpoint: 127.0.0.1:80
egress:
- hosts:
- "istio-system/*"
適用後のendpointsの設定を確認してみます。
% istioctl proxy-config endpoints test-app-9dfd8f5-58t4d.foo
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.64.1.4:8080 HEALTHY OK outbound|80||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:8443 HEALTHY OK outbound|443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15012 HEALTHY OK outbound|15012||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15021 HEALTHY OK outbound|15021||istio-ingressgateway.istio-system.svc.cluster.local
10.64.1.4:15443 HEALTHY OK outbound|15443||istio-ingressgateway.istio-system.svc.cluster.local
10.64.2.2:15010 HEALTHY OK outbound|15010||istiod.istio-system.svc.cluster.local
10.64.2.2:15012 HEALTHY OK outbound|15012||istiod.istio-system.svc.cluster.local
10.64.2.2:15014 HEALTHY OK outbound|15014||istiod.istio-system.svc.cluster.local
10.64.2.2:15017 HEALTHY OK outbound|443||istiod.istio-system.svc.cluster.local
127.0.0.1:80 HEALTHY OK inbound|80||
127.0.0.1:15000 HEALTHY OK prometheus_stats
127.0.0.1:15020 HEALTHY OK agent
unix://./etc/istio/proxy/SDS HEALTHY OK sds-grpc
unix://./etc/istio/proxy/XDS HEALTHY OK xds-grpc
先ほどまで存在していたbarへの通信の設定がなくなりました。
Sidecarリソースを使うことで設定を必要以上に持たなくて済みました。
Sidecarリソースをもう少し理解したい
先ほどのSidecarリソースを使ってみた例ではいまいち恩恵を受けることができているのか、いないのかという印象は薄かったかもしれません。
しかし、istio-proxyのリソース使用量などパフォーマンスの側面で見るとSidecarリソースを使用することはとても大切になるようです。
大規模なシステムでistio-proxyが必要以上に設定を持ってしまうとCPUやメモリの消費も増えてしまいます。
issueなどにもVitualServiceリソースが増加するごとにメモリの使用量が増加した事例などがありました。
自身もどれくらいの負荷の増減が発生するのか検証してみました。
今回はVirtualServiceやリクエストによる負荷ではなく、単純にSidecarリソースを使用せずにNamespaceとPodを増やしてみてリソースの使用量に差異が見られるのか検証してみました。
Pod数を初回6台くらいから徐々に増やしていき最終的に120台まで増やしてみました。120台まで増やした後にSidecarリソースを適用してみました。
以下がその際のメモリの使用量の推移です。
全体として見ると増減は見られますが、5MB~10MBほどの増減でした。
時刻で見ると19:00頃から少しずつ使用量が増加しています。
19:00頃の Podの台数は50~60台ほどでした。そこから120台まで徐々に増加していったことが確認できました。
2:00前くらいから使用量が減少していますが、このときにSidecarリソースを適用しました。
一応メモリ使用量の変化を確認することができましたが、増減の値が小さかったり、、と少し検証内容は不十分だったのでは?と考えています。
絶えずリクエストを送ったり、VirtualServiceを使用して、サーキットブレーカーや割合によるリクエスト制御など様々な機能を利用した複雑なProxy設定を行った上でメトリクスを確認したら、より違った効果が見られたかもしれません。
Podを増やしていく上で、追加されたPodのistio-proxy、既に存在しているPodのistio-proxyどちらにも設定の更新が発生するためControl PlaneのIstiodにも負荷がかかってしまうということもわかりました。このあたりはPodの立ち上がりに影響が出てしまうかもしれないため別途検証してみたほうがよさそうです。
最後に
今回はなんとなく、Sidecarリソースを理解することができましたが、パフォーマンスに関する領域についてはもう少し確認したほうがいいことや、不十分なことが多く見られました。
また、時間を取って今回以上の検証ができればと思います。
もしくは、とても詳しい方がいたら教えてほしいですし、より詳しい記事があったら教えてほしいです。
参考
https://github.com/istio/istio/issues/19925
https://banzaicloud.com/blog/istio-sidecar/
Everything We Learned Running Istio In Production — Part 2 | by Craig Huber | HelloTech
envoyproxy - Reducing memory usage by ISTIO side car - Stack Overflow
Istio / Announcing Istio 1.1
https://linkerd.io/2020/12/03/why-linkerd-doesnt-use-envoy/
Potential memory leak on istio-proxy sidecar · Issue #29505 · istio/istio · GitHub
https://istio.io/latest/docs/concepts/traffic-management/#sidecars
High memory utilization of istio-proxy proportional to number of services · Issue #7912 · istio/istio · GitHub
https://tech.bigbasket.com/bigbaskets-experience-with-istio/
High memory usage with long HTTP/2 connections (GRPC) · Issue #9891 · envoyproxy/envoy · GitHub
Sidecar uses a large amount of memory · Issue #9367 · istio/istio · GitHub
https://github.com/istio/istio.io/issues/7700
https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request