Istioを本番システムで利用する場合は、Istioの機能がどのように実現されているかを理解していないと、いざという時トラブルシュートができません。そこで、Istioを勉強するにあたって、IstioのTraffic ManagementがKubernetes上でどのように実現されているかをドキュメントを見たり、kubectlでリバースエンジニアリングしながら調べてみました。
-
想定読者
- IstioがTraffic ManagementをKubernetes上でどのように実現するか理解したい人
- Kubernetesの基礎は理解している
- Service、Podなどの基本的なリソースの説明はしません
-
前提
-
外部からmesh内サービスへのアクセスまでを対象とし、mesh内から外部への通信については触れない
- ServiceEntryについては触れません、いつかもっと理解したら載せるかもです
-
IstioのサンプルアプリIstio / Bookinfo Applicationを題材とする
-
IstioのTraffic Managementを実現するリソースの理解
本記事ではIstioのTraffic にManagementを実現する以下3つのCRD(Custom Resource Definition)について説明をする。
- VirtualService
- DestinationRule
- Gateway
各リソースを用いてBookinfoにおけるIstioのリクエストフローを表現すると、以下のようになる。Kubernetes上で具体的にどのように実現されているかは後述する。あくまでも、リソースを理解するうえでのイメージ図であることに注意。
Gateway
- Istio / Gateway
- mesh外からのリクエスト受付窓口
- exposeするポート、プロトコル、SNIの設定情報を記載する
- GatewayとVirtualServiceの紐付けはVirtualServiceで行う
VirtualService
- Istio / Virtual Service
- mesh上のリクエストのルーティング設定を行う
- DestinationRule(後述)でより細かいルーティング設定ができる
- bookinfoではまずbookinfo VirtualServiceでGatewayへのアクセスをproductpage VirtualServiceへルーティングしている
- 次にproductpage VirtualServiceの設定によりproductpage Podにリクエストがルーティングされる
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
namespace: ""
spec:
gateways:
- bookinfo-gateway
hosts:
- '*'
http:
- match:
- uri:
exact: /productpage
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage # <= 上記でmatchしたリクエストをproductpageにルーティングする
port:
number: 9080
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: productpage
namespace: ""
resourceVersion: ""
spec:
hosts:
- productpage <= productpageへのリクエストを受けるVirtualService
http:
- route:
- destination:
host: productpage <= productpageに
subset: v1 <= subset: v1というDestinationRuleでルーティングを行う
DestinationRule
- Istio / Destination Rule
- VirtualServiceがPodにリクエストをルーティングする際の細かいポリシーなどを設定する
- バージョンごとのルーティングや、負荷分散のポリシーなどを設定できる
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: productpage
namespace: default
spec:
host: productpage <= productpageへ来たリクエストに対する細かいルールを設定
subsets:
- labels:
version: v1
name: v1 <= version: v1のラベルへルーティングするsubsetをv1という名前で定義している
上記productpageは単一バージョンしかないが、reviewsは複数バージョンがあるため、複数のsubsetが定義されている。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
namespace: default
spec:
host: reviews
subsets:
- labels:
version: v1
name: v1
- labels:
version: v2
name: v2
- labels:
version: v3
name: v3
Kubernetes上での実現方法を理解する
IstioのTraffic ManagementがKubernetesでどのように実現されるかを説明する。
(参考:Istio / Traffic Management)
mesh外からのリクエストルーティング
- istio-ingressgateway ServiceからProxyコンテナに入ってきたリクエストは、各Pod内にsidecarとして起動しているProxyコンテナを介してリクエストのやりとりを行う
- ProxyコンテナはIstioのPilotコンポーネントが作成したルーティングルールにしたがってリクエストをルーティングする
istio-ingressgateway Service
- Istio install時にデフォルトでinstallされている
- KubernetesのServiceリソース(type LoadBalancer)で作成されている
- EKSなどで作成すると、ELBが自動的に作成される
- 外部からのリクエストを受け付ける窓口となる
- Gatewayリソース作成時に紐付けをする
- 宛先にはProxyコンテナが指定されている
- 外部からのアクセスはこの仕組みにより最初のProxyコンテナにルーティングされ、ルーティング設定にしたがってmesh上の各Podにルーティングされていく
IstioのCRDどこいった?
お気づきかと思うが、上記の説明ではGatewayやVirtualService、DestinationRuleがでてこない。これらのリソースはPilotが作成するProxyコンテナのルーティングルールの作成に利用されている。それぞれの設定はPilotによりルーティングルールとして全Proxyコンテナに設定される。
Serviceリソースをどこに使ってるんだ?
通信は全てProxyコンテナ間で行われ、各ProxyコンテナはPilotによって作成されたルーティングルールに沿って通信を行う。つまりProxyコンテナは直接はKubernetesのServiceリソースによる名前解決を利用していない。ではどこで利用しているかというと、Pilotがルーティングルールを作成する際に利用している。(参考:Istio / Traffic Management)
実際にServiceの定義を見てみるとselectorには app: reviews
しか指定されていない。
apiVersion: v1
kind: Service
metadata:
labels:
app: reviews
service: reviews
name: reviews
spec:
ports:
- name: http
port: 9080
protocol: TCP
targetPort: 9080
selector:
app: reviews
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
以下のようにDeploymentを確認すると、reviewsの全てのバージョン(V1~V3)に app: reviews
ラベルが貼られている。つまり、reviews Serviceで名前解決をすると全てのバージョン(V1~V3)の宛先が返ってくるはずである。
$ k get deploy -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
details-v1 1 1 1 1 5h details istio/examples-bookinfo-details-v1:1.10.1 app=details,version=v1
productpage-v1 1 1 1 1 5h productpage istio/examples-bookinfo-productpage-v1:1.10.1 app=productpage,version=v1
ratings-v1 1 1 1 1 5h ratings istio/examples-bookinfo-ratings-v1:1.10.1 app=ratings,version=v1
reviews-v1 1 1 1 1 5h reviews istio/examples-bookinfo-reviews-v1:1.10.1 app=reviews,version=v1
reviews-v2 1 1 1 1 5h reviews istio/examples-bookinfo-reviews-v2:1.10.1 app=reviews,version=v2
reviews-v3 1 1 1 1 5h reviews istio/examples-bookinfo-reviews-v3:1.10.1 app=reviews,version=v3
上記のようなServiceの設定を見てもわかるように、Proxyコンテナ自体はServiceリソースを直接は用いておらず、Pilotがルーティングルールを作成するために利用していることがわかる。
(参考)Pod上のAPコードの変更なしでリクエストがProxyを通るようにする仕組み
内容
IstioではAPの変更なしで、PodがProxyコンテナを通すようにすることができる。PodからのリクエストをどのようにProxyコンテナを通すようにするか気になったので少し調べてみた。結論は以下。
- PodからのリクエストはよしなにProxyコンテナを仲介するように設定される
- たぶん自動injectionをtrueにすれば勝手にやってくれる
Podのyamlを見てみるとProxyコンテナがリクエストを奪い取るっぽい設定がされたいる。
以下にproductpageの元のDeploymentのyamlと実際にデプロイされていたpodのyamlを記載する。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: productpage-v1
labels:
app: productpage
version: v1
spec:
replicas: 1
template:
metadata:
labels:
app: productpage
version: v1
spec:
containers:
- name: productpage
image: istio/examples-bookinfo-productpage-v1:1.10.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
$ k get po productpage-v1-5cb458d74f-wnlf8 -o yaml [~/SDropbox/Dropbox/k8s/istio]
apiVersion: v1
kind: Pod
metadata:
annotations:
sidecar.istio.io/status: '{"version":"db2f6e5a0d19dde20f8dc886fe87dbb029dd3d2c3f77bfc11196d156ca5f8069","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs"],"imagePullSecrets":null}'
creationTimestamp: 2019-04-28T00:50:50Z
generateName: productpage-v1-5cb458d74f-
labels:
app: productpage
pod-template-hash: "1760148309"
version: v1
name: productpage-v1-5cb458d74f-wnlf8
namespace: default
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: productpage-v1-5cb458d74f
uid: aaf0a34b-694f-11e9-89ac-067d8b9d0d08
resourceVersion: "4680"
selfLink: /api/v1/namespaces/default/pods/productpage-v1-5cb458d74f-wnlf8
uid: aaf1f726-694f-11e9-89ac-067d8b9d0d08
spec:
containers:
- image: istio/examples-bookinfo-productpage-v1:1.10.1
imagePullPolicy: IfNotPresent
name: productpage
ports:
- containerPort: 9080
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-c5qk5
readOnly: true
- args:
- proxy
- sidecar
- --domain
- $(POD_NAMESPACE).svc.cluster.local
- --configPath
- /etc/istio/proxy
- --binaryPath
- /usr/local/bin/envoy
- --serviceCluster
- productpage.$(POD_NAMESPACE)
- --drainDuration
- 45s
- --parentShutdownDuration
- 1m0s
- --discoveryAddress
- istio-pilot.istio-system:15010
- --zipkinAddress
- zipkin.istio-system:9411
- --connectTimeout
- 10s
- --proxyAdminPort
- "15000"
- --concurrency
- "2"
- --controlPlaneAuthPolicy
- NONE
- --statusPort
- "15020"
- --applicationPorts
- "9080"
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: INSTANCE_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.podIP
- name: ISTIO_META_POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: ISTIO_META_CONFIG_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: ISTIO_META_INTERCEPTION_MODE
value: REDIRECT
- name: ISTIO_METAJSON_LABELS
value: |
{"app":"productpage","pod-template-hash":"1760148309","version":"v1"}
image: gcr.io/istio-release/proxyv2:release-1.1-latest-daily
imagePullPolicy: IfNotPresent
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
readinessProbe:
failureThreshold: 30
httpGet:
path: /healthz/ready
port: 15020
scheme: HTTP
initialDelaySeconds: 1
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
cpu: "2"
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
securityContext:
readOnlyRootFilesystem: true
runAsUser: 1337
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/istio/proxy
name: istio-envoy
- mountPath: /etc/certs/
name: istio-certs
readOnly: true
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-c5qk5
readOnly: true
dnsPolicy: ClusterFirst
initContainers:
- args:
- -p
- "15001"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- "9080"
- -d
- "15020"
image: gcr.io/istio-release/proxy_init:release-1.1-latest-daily
imagePullPolicy: IfNotPresent
name: istio-init
resources:
limits:
cpu: 100m
memory: 50Mi
requests:
cpu: 10m
memory: 10Mi
securityContext:
capabilities:
add:
- NET_ADMIN
runAsNonRoot: false
runAsUser: 0
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
nodeName: ip-192-168-20-207.us-west-2.compute.internal
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- name: default-token-c5qk5
secret:
defaultMode: 420
secretName: default-token-c5qk5
- emptyDir:
medium: Memory
name: istio-envoy
- name: istio-certs
secret:
defaultMode: 420
optional: true
secretName: istio.default
status:
conditions:
- lastProbeTime: null
lastTransitionTime: 2019-04-28T00:50:53Z
status: "True"
type: Initialized
- lastProbeTime: null
lastTransitionTime: 2019-04-28T00:51:03Z
status: "True"
type: Ready
- lastProbeTime: null
lastTransitionTime: null
status: "True"
type: ContainersReady
- lastProbeTime: null
lastTransitionTime: 2019-04-28T00:50:50Z
status: "True"
type: PodScheduled
containerStatuses:
- containerID: docker://ff9bcd670cfdf0ebb511e2a354f275eccdfd87adb0e08a62c3b4ea7634271519
image: gcr.io/istio-release/proxyv2:release-1.1-latest-daily
imageID: docker-pullable://gcr.io/istio-release/proxyv2@sha256:01d50d0390058d52197727499dcad3f0ccc6f65e0cf6a82ddf85bd0083341dfe
lastState: {}
name: istio-proxy
ready: true
restartCount: 0
state:
running:
startedAt: 2019-04-28T00:51:01Z
- containerID: docker://5786b8460526512f321dba0f5317c6a474f319b8c63c2d22ed5e582840af3197
image: istio/examples-bookinfo-productpage-v1:1.10.1
imageID: docker-pullable://istio/examples-bookinfo-productpage-v1@sha256:a427e10277a814b27c066d9ea5f2605b656fc1948714ed09e55c97342c5a721d
lastState: {}
name: productpage
ready: true
restartCount: 0
state:
running:
startedAt: 2019-04-28T00:51:00Z
hostIP: 192.168.20.207
initContainerStatuses:
- containerID: docker://49956b55bed3950f0a4a3c35207994c4ad1925ea82e304a870533db82073825f
image: gcr.io/istio-release/proxy_init:release-1.1-latest-daily
imageID: docker-pullable://gcr.io/istio-release/proxy_init@sha256:30e629a68500f45b73dd7258cf86c6c1c20920bdd5d92eb6a26aacb6d1c54739
lastState: {}
name: istio-init
ready: true
restartCount: 0
state:
terminated:
containerID: docker://49956b55bed3950f0a4a3c35207994c4ad1925ea82e304a870533db82073825f
exitCode: 0
finishedAt: 2019-04-28T00:50:53Z
reason: Completed
startedAt: 2019-04-28T00:50:52Z
phase: Running
podIP: 192.168.3.132
qosClass: Burstable
startTime: 2019-04-28T00:50:50Z
istio-proxyとistio-initが追記されていることがわかりますね。
こいつらがよしなにやってくれているのだろう。
まとめ
IstioのTrafficがどのようにKubernetes上で実現されるかを説明しました。