はじめに
今回は、サービスメッシュを提供する Istio
を利用して kubernetes 上カナリアリリースの実験をしてみたいと思います。
Istio やそれに付随する Envoy の詳しい内容は @seikoudoku2000 さんの「Envoy (Envoy proxy)、Istio とは?」を参考にしてみてください。
実験
それでは実際に実験してみます。
Istio のインストールはすでに完了しているものとします。
なお、今回テストする環境は下記環境となります。
- OS
- CentOS Linux release 7.6.1810 (Core)
- kubernetes
- v1.13.1
- master:1台
- node:3台
- Istio
- v1.0.5
今回試すこと
今回は下記のような流れを試してみたいと思います。
- nginx を利用した Web サービス(今回は静的コンテンツを表示するのみ)を
version 1
としてサービスリリース - 静的コンテンツの中身を変更したバージョンを
version 2
としてカナリアリリース-
version 1
に 50% /version 2
に 50% 振り分けされるように設定をします。
-
なお、今回は kubernetes のクラスタ内部からのみアクセスをする想定で各種設定をしていきます。
(NodePort や LoadBalancer のを利用する外部公開は行わない)
手順
Web サービス(Version 1)のリリース
では、最初に nginx を利用した Web サービスを作成していきます。
①. nginx の image を展開する Deployment を 作成
apiVersion: apps/v1
kind: Deployment
metadata:
name: istio-deployment-v1
spec:
replicas: 1
selector:
matchLabels:
istio-test-version: v1
template:
metadata:
labels:
istio-test-app: istio-test-app
istio-test-version: v1
spec:
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
この Deployment では、 istio-test-version: v1
にマッチした POD を管理します。
istio-test-app: istio-test-app
については Service にて利用するため事前に設定しておきます。
しかし、このマニュフェストファイルをそのまま apply しても Istio で提供されるサービスメッシュ配下では管理されません。
というのも、最初にご紹介させていただいた @seikoudoku2000 さんの「Envoy (Envoy proxy)、Istio とは?」を参照するとわかりますが、Envoy と呼ばれる Proxy を各 pod にサイドカーとして提供することでサービスメッシュとしての各種コントロールを行います。
Envoy の提供方法は複数ある(指定した namespace 内で作成される pod にはすべて Envoy を提供する設定をする / 既存のマニュフェストファイルに Envoy の定義を追加して apply する)のですが、今回は後者の方法で実施します。
②. istio-deployment-v1.yaml
に Envoy の定義を追加
$ istioctl kube-inject -f istio-deployment-v1.yaml > istio-deployment-v1-injection.yaml
上記コマンドを実行することで下記ファイルが出力されます。
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
name: istio-deployment-v1
spec:
replicas: 1
selector:
matchLabels:
istio-test-version: v1
strategy: {}
template:
metadata:
annotations:
sidecar.istio.io/status: '{"version":"50128f63e7b050c58e1cdce95b577358054109ad2aff4bc4995158c06924a43b","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs"],"imagePullSecrets":null}'
creationTimestamp: null
labels:
istio-test-app: istio-test-app
istio-test-version: v1
spec:
containers:
- image: nginx:latest
name: nginx-container
ports:
- containerPort: 80
resources: {}
- args:
- proxy
- sidecar
- --configPath
- /etc/istio/proxy
- --binaryPath
- /usr/local/bin/envoy
- --serviceCluster
- istio-proxy
- --drainDuration
- 45s
- --parentShutdownDuration
- 1m0s
- --discoveryAddress
- istio-pilot.istio-system:15007
- --discoveryRefreshDelay
- 1s
- --zipkinAddress
- zipkin.istio-system:9411
- --connectTimeout
- 10s
- --proxyAdminPort
- "15000"
- --controlPlaneAuthPolicy
- NONE
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: INSTANCE_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: ISTIO_META_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: ISTIO_META_INTERCEPTION_MODE
value: REDIRECT
- name: ISTIO_METAJSON_LABELS
value: |
{"istio-test-app":"istio-test-app","istio-test-version":"v1"}
image: docker.io/istio/proxyv2:1.0.5
imagePullPolicy: IfNotPresent
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
resources:
requests:
cpu: 10m
securityContext:
readOnlyRootFilesystem: true
runAsUser: 1337
volumeMounts:
- mountPath: /etc/istio/proxy
name: istio-envoy
- mountPath: /etc/certs/
name: istio-certs
readOnly: true
initContainers:
- args:
- -p
- "15001"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- "80"
- -d
- ""
image: docker.io/istio/proxy_init:1.0.5
imagePullPolicy: IfNotPresent
name: istio-init
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumes:
- emptyDir:
medium: Memory
name: istio-envoy
- name: istio-certs
secret:
optional: true
secretName: istio.default
status: {}
---
③. deployment を apply する
$ kubectl apply -f istio-deployment-v1-injection.yaml
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
istio-deployment-v1 1/1 1 1 47h
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-8bd954dbb-czq6b 2/2 Running 0 2d23h
apply された pod について READY 2/2
となっていることが確認できます。
これは、上記マニュフェストの通り、Proxy のコンテナがサイドカーとして含まれているためとなります。
また、version 1
ということで下記 index.html
を pod 内に配置します。
$ cat html/v1/index.html
version1 server
$ kubectl cp v1/index.html istio-deployment-v1-7c8c9f47fb-p9fvm:/usr/share/nginx/html
④. Service を apply する
apiVersion: v1
kind: Service
metadata:
name: istio-service
spec:
type: ClusterIP
ports:
- name: "http"
protocol: "TCP"
port: 80
targetPort: 80
selector:
istio-test-app: istio-test-app
今回は type: ClusterIP
を提供します。
なお、先程説明した通り istio-test-app: istio-test-app
に当てはまる pod に振り分けを行います。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-service ClusterIP 10.105.126.251 <none> 80/TCP 44h
⑤. Service のトラフィック制御ルールを定義
予め、作成した Service に対してトラフィック制御ルールを定義しておきます。
必要となる kind
は下記 2 つとなります。
- VirtualService
- リクエストに対するルーティング制御設定
- DestinationRule
- ルーティング後にチェックされるポリシー設定
また、クラスタ外部からのアクセスに対してトラフィック制御するには Gateway
という kind
を定義する必要がありますが、今回はクラスタ内部からのアクセスをテストするため、今回は省略させていただきます。
詳しい内容は、調べてみてください。
まずは、 DestinationRule
を定義、apply します。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: istio-destinationrule
spec:
host: istio-service
trafficPolicy:
loadBalancer:
simple: RANDOM
subsets:
- name: v1
labels:
istio-test-version: v1
$ kubectl apply -f istio-destinationrule.yaml
$ kubectl get destinationrule
NAME AGE
istio-destinationrule 1d
spec.subsets
リソースで定義されている内容が振り分け先の pod 情報となります。
istio-test-version: v1
に合致する pod に振り分けされます。
イメージ的に世代が増えたら、このリソースに対して各世代の情報を記載していくイメージになると思います。
また、spec.trafficPolicy
にて負荷分散方式を指定しております。(デフォルトは RoundRobin のようです。)
次に、VirtualService
を定義、apply します。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-virtualservice
spec:
hosts:
- istio-service
http:
- route:
- destination:
host: istio-service
subset: v1
$ kubectl apply -f istio-virtualservice.yaml
$ kubectl get virtualservice
NAME AGE
istio-virtualservice 1d
spec.hosts
リソースで定義されている istio-service
という ホスト名(FQDN でも可)に対する通信に対する設定となります。
spec.http.route.destination
リソースが定義されているのがルーティング先のルールとなります。
これが、先程定義した DestinationRule
の spec.subnets
に対応しています。
⑥. 実際にアクセスしてみる
適当に作成した pod 内部から、作成した Service に対してアクセスしてみます。
$ curl http://istio-service/
version1 server
...(複数回実行)...
version1 server
Web サービス(Version 2)のリリース
次に、静的コンテンツの中身を変更したものを version 2
としてデプロイしていきます。
①. nginx の image を展開する Deployment を 作成
apiVersion: apps/v1
kind: Deployment
metadata:
name: istio-deployment-v2
spec:
replicas: 1
selector:
matchLabels:
istio-test-version: v2
template:
metadata:
labels:
istio-test-app: istio-test-app
istio-test-version: v2
spec:
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
マニュフェストとしては istio-test-version
label が v2
に変更している部分程度です。
②. istio-deployment-v1.yaml
に Envoy の定義を追加
$ kubectl apply -f istio-deployment-v2-injection.yaml
$ cat istio-deployment-v2-injection.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
name: istio-deployment-v2
spec:
replicas: 1
selector:
matchLabels:
istio-test-version: v2
strategy: {}
template:
metadata:
annotations:
sidecar.istio.io/status: '{"version":"50128f63e7b050c58e1cdce95b577358054109ad2aff4bc4995158c06924a43b","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs"],"imagePullSecrets":null}'
creationTimestamp: null
labels:
istio-test-app: istio-test-app
istio-test-version: v2
spec:
containers:
- image: nginx:latest
name: nginx-container
ports:
- containerPort: 80
resources: {}
- args:
- proxy
- sidecar
- --configPath
- /etc/istio/proxy
- --binaryPath
- /usr/local/bin/envoy
- --serviceCluster
- istio-proxy
- --drainDuration
- 45s
- --parentShutdownDuration
- 1m0s
- --discoveryAddress
- istio-pilot.istio-system:15007
- --discoveryRefreshDelay
- 1s
- --zipkinAddress
- zipkin.istio-system:9411
- --connectTimeout
- 10s
- --proxyAdminPort
- "15000"
- --controlPlaneAuthPolicy
- NONE
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: INSTANCE_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: ISTIO_META_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: ISTIO_META_INTERCEPTION_MODE
value: REDIRECT
- name: ISTIO_METAJSON_LABELS
value: |
{"istio-test-app":"istio-test-app","istio-test-version":"v2"}
image: docker.io/istio/proxyv2:1.0.5
imagePullPolicy: IfNotPresent
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
resources:
requests:
cpu: 10m
securityContext:
readOnlyRootFilesystem: true
runAsUser: 1337
volumeMounts:
- mountPath: /etc/istio/proxy
name: istio-envoy
- mountPath: /etc/certs/
name: istio-certs
readOnly: true
initContainers:
- args:
- -p
- "15001"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- "80"
- -d
- ""
image: docker.io/istio/proxy_init:1.0.5
imagePullPolicy: IfNotPresent
name: istio-init
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumes:
- emptyDir:
medium: Memory
name: istio-envoy
- name: istio-certs
secret:
optional: true
secretName: istio.default
status: {}
---
③. deployment を apply する
$ kubectl apply -f istio-deployment-v2-injection.yaml
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
istio-deployment-v1 1/1 1 1 2d
istio-deployment-v2 1/1 1 1 2d
これで、カナリアリリースの準備が整ったので肝心のルーティング制御をしていきましょう。
④. Service のトラフィック制御ルールを変更
今回は、version 1
のサービスに対して 50%
/ version 1
のサービスに対して 50%
の割合で振り分けます。
まずは、DestinationRule
の定義を変更します。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: istio-destinationrule
spec:
host: istio-service
trafficPolicy:
loadBalancer:
simple: RANDOM
subsets:
- name: v1
labels:
istio-test-version: v1
- name: v2
labels:
istio-test-version: v2
新たに spec.subsets
リソースに name: v2
を追加しました。
こちらの内容で apply し直します。
$ kubectl apply -f istio-destinationrule.yaml
$ kubectl get destinationrule
NAME AGE
istio-destinationrule 1d
続いて、VirtualService
の定義を変更します。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-virtualservice
spec:
hosts:
- istio-service
http:
- route:
- destination:
host: istio-service
subset: v1
weight: 50
- destination:
host: istio-service
subset: v2
weight: 50
spec.http.route.detination
リソースを追加し、それぞれの destination
に対して weight: 50
を追加してあげました。
こちらの内容で、apply し直します。
これにより、ルーティングの分散が開始されます。
$ kubectl apply -f istio-virtualservice.yaml
$ kubectl get virtualservice
NAME AGE
istio-virtualservice 1d
⑤. アクセステスト
適当に作成した pod 内部から、作成した Service に対して再度アクセスしてみます。
$ curl http://istio-service/
...(複数回アクセス)...
version1 server
version2 server
version1 server
version2 server
version1 server
version1 server
version2 server
version2 server
version1 server
version1 server
version2 server
version1 server
version1 server
version1 server
version1 server
version1 server
version1 server
version1 server
若干偏ってる気がしますが、アクセスが分散されました。
weight
をの値を変更してあげればこれで自由に重み付けが変更できます!
気づき
今回、curl を実行している pod も Envoy
がサイドカーとしてデプロイされている pod となります。
しかし、Envoy
がデプロイされていない pod からアクセスを行ってもルーティングルールは無視されてしまいます。
これは、Istio
のアーキテクチャとして Envoy
がルーティング制御の役割を担っている(制御ルール等を担っているのは別ですが)ため、Envoy
で Proxy されないとそもそもルーティング制御が行われないためです。
では、クラスタ外からの通信はどうやって Istio
配下に落とし込むか?というとそれは前述した Gateway
によって制御されるのだと思います。
おわりに
Istio
を使えばマイクロサービスの管理/提供がとても簡単になると思います。
まだまだ、触り始めたばかりではありますが今後も色々試していきたいと思います。