今回は Istioを用いて、Blue Green Deployment と Canary の実施方法を試してみた。
特に Canary に関しては、Vampという素晴らしいツールが DC/OS には存在するが、Kubernetes の方はalpha だし、決定版の Canaryの方法は無かった。しかし、サービスメッシュつまり、マイクロサービスの「隙間」を埋めるためのツールとして、Istioがリリースされた。おそらくこれがデファクトになっていくので、とても注目だ。
前提条件
前提として、次の環境をすでに構築していると想定している。私はAzure Container Service と Azure Container Registry で環境を作成したが、他のクラウドサービスでも同じように動作すると思う。
- Azure Container Service (Kubernetes 1.6.6)
- Azure Container Registry
- Istio (0.1.6)
Istio はすでにインストールされている前提である。ちなみに、Azure の場合はこの手順でばっちり動作する。Deploying Istio on Azure Container Service
ちなみに、上記の手順でのインストールには、helm が必要なのでインストールしておくこと。
kubernetes は、 kubectl がダウンロード済み、config ファイルも設定済みであること。Azure の場合はこちらを参照のこと。 ちなみに、クラスターのSSHのPublic Key に パスワード付きの Private Key を指定した場合、az acs kubernetes get-credentials
では、config ファイルは取得できない。マスターノードにログインして、.kube/config
からローカルに取ってくること。
サンプルアプリケーション
最もシンプルに検証するために、index.html と、最低限のDockerfile を書いておく。
version 1.0.0 用 index.html
<html>
<head>
<title>Blue Version 1.0.0</title>
</head>
<body bgcolor="#0000FF">
<H1>This is Blue Version 1.0.0</H1>
</body>
</html>
version 2.0.0 用 index.html
<html>
<head>
<title>Green Version 2.0.0</title>
</head>
<body bgcolor="#00FF00">
<H1>This is Green Version 2.0.0</H1>
</body>
</html>
Dockerfile
FROM nginx
ADD index.html /usr/share/nginx/html
それぞれにディレクトリを掘って、index.html と、Dockerfile の対をおいておく。
1.0.0
2.0.0
それぞれ次のようなコマンドでDocker image を作っておく。kube16.azurecr.io
は、私のテスト用 Azure Container Registry のアドレスで、ログインに必要な情報はすべて ポータルの setting > accesss key
から取得できる。
docker login docker login -u USERNAME_HERE -p PASSWORD_HERE kube16.azurecr.io
docker build -t kube16.azurecr.io/websample:1.0.0 -t kube16.azurecr.io/websample:latest .
それぞれを、ACR (Azure Container Registry) に push しておく。
まず、Secret を作成するコマンドを実行する。
kubectl create secret docker-registry acrsecret --docker-server=kube16.azurecr.io --docker-username=USERNAME_HERE --docker-password=PASSWORD_HERE --docker-email=YOURE_E_MAIL_HERE
このコマンドで、Kubernetes に ACR にアクセスための Secret が作成される。ただ、毎回手でたたくのは嫌だと思うので、次のコマンドをたたいてから、
kubectl get secret kb16acrsecret --output=yaml
すると、下記のような情報が返ってくる
apiVersion: v1
data:
.dockercfg: ey...............................=
kind: Secret
metadata:
creationTimestamp: 2017-07-09T14:17:27Z
name: kb16acrsecret
namespace: default
resourceVersion: "420393"
selfLink: /api/v1/namespaces/default/secrets/acrsecret
uid: 56048e53-64b1-11e7-bdb4-000d3a30f128
type: kubernetes.io/dockercfg
ここで、ポイントとなるのが、.dockercfg: の値である。これは、ACRに接続するための、情報が、base64 エンコードされたものだ。
これをもとに、secret.yaml を作っていつでも再生成できるようにしておく。ポイントは上記で取得した、.dockercfgの値をとっておくこと。
apiVersion: v1
kind: Secret
metadata:
name: kb16acrsecret
type: kubernetes.io/dockercfg
data:
.dockercfg: ey......................=
さて、これで準備環境。最後に、2 つのバージョンを持った、WebService というサービスをデプロイしてみる。
WebService.yaml
apiVersion: v1
kind: Service
metadata:
name: web-service
labels:
app: web-service
spec:
selector:
app: web-service
ports:
- port: 80
name: http
targetPort: 80
type: LoadBalancer
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: web-deployment-v1
spec:
replicas: 1
template:
metadata:
labels:
app: web-service
version: 1.0.0
spec:
containers:
- name: web-service
image: kube16.azurecr.io/websample:1.0.0
ports:
- containerPort: 80
imagePullSecrets:
- name: kb16acrsecret
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: web-deployment-v2
spec:
replicas: 1
template:
metadata:
labels:
app: web-service
version: 2.0.0
spec:
containers:
- name: web-service
image: kube16.azurecr.io/websample:2.0.0
ports:
- containerPort: 80
imagePullSecrets:
- name: kb16acrsecret
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: webservice-ingress
annotations:
kubernetes.io/ingress.class: istio
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: web-service
servicePort: 80
このような結果が返ってくる。ここで、Web Service の EXTERNAL-IP にブラウザで、アクセスしてみる。このままの yaml では、v1.0.0 もしくは、v2.0.0 のどちらかにルーティングされる。
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
details 10.0.94.56 <none> 9080/TCP 2d
istio-egress 10.0.53.207 <none> 80/TCP 3d
istio-ingress 10.0.229.66 123.45.67.89 80:32656/TCP,443:31504/TCP 3d
kubernetes 10.0.0.1 <none> 443/TCP 3d
productpage 10.0.19.13 <none> 9080/TCP 2d
prometheus 10.0.103.100 <none> 9090/TCP 3d
quiet-lambkin-istio-grafana 10.0.83.157 <none> 3000/TCP 3d
quiet-lambkin-istio-mixer 10.0.40.41 <none> 9091/TCP,9094/TCP,42422/TCP 3d
quiet-lambkin-istio-pilot 10.0.185.10 <none> 8080/TCP,8081/TCP 3d
quiet-lambkin-istio-servicegraph 10.0.149.187 <none> 8088/TCP 3d
quiet-lambkin-istio-zipkin 10.0.135.63 <none> 9411/TCP 3d
ratings 10.0.161.28 <none> 9080/TCP 2d
reviews 10.0.137.87 <none> 9080/TCP 2d
web-service 10.0.76.169 123.45.67.10 80:31228/TCP 32m
istio を Injection する
さて、この至って普通の アプリケーションに対して、Istio化してみよう。
kubectl delete -f webservice.yaml
kubectl apply -f <(istioctl kube-inject -f webservice.yaml)
これは、istioctl が、通常の yaml に対して、istioの envoy と呼ばれるプロキシを注入するためである。こんな感じで、既存のyamlファイルを活用できる。
試しにどうなるか見てみよう。
istioctl kube-inject -f webservice.yaml
annotation として, init-container が指定されている。init-container は、通常の app container が動く前に実行されるコンテナだ。また、自分で指定した、コンテナ以外に、proxy-debug というコンテナが注入されているのがわかる。
apiVersion: apps/v1beta1
kind: Deployment
metadata:
creationTimestamp: null
name: web-deployment-v1
spec:
replicas: 1
strategy: {}
template:
metadata:
annotations:
alpha.istio.io/sidecar: injected
alpha.istio.io/version: jenkins@ubuntu-16-04-build-12ac793f80be71-0.1.6-dab2033
pod.beta.kubernetes.io/init-containers: '[{"args":["-p","15001","-u","1337"],"image":"docker.io/istio/init:0.1","imagePullPolicy":"Always","name":"init","securityContext":{"capabilities":{"add":["NET_ADMIN"]}}},{"args":["-c","sysctl
-w kernel.core_pattern=/tmp/core.%e.%p.%t \u0026\u0026 ulimit -c unlimited"],"command":["/bin/sh"],"image":"alpine","imagePullPolicy":"Always","name":"enable-core-dump","securityContext":{"privileged":true}}]'
creationTimestamp: null
labels:
app: web-service
version: 1.0.0
spec:
containers:
- image: kube16.azurecr.io/websample:1.0.0
name: web-service
ports:
- containerPort: 80
resources: {}
- args:
- proxy
- sidecar
- -v
- "2"
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
image: docker.io/istio/proxy_debug:0.1
imagePullPolicy: Always
name: proxy
resources: {}
securityContext:
runAsUser: 1337
volumeMounts:
- mountPath: /etc/certs
name: istio-certs
readOnly: true
imagePullSecrets:
- name: kb16acrsecret
volumes:
- name: istio-certs
secret:
secretName: istio.default
status: {}
Envoyというproxy が注入されることで Kubernetes にデプロイされた Istio の管理用コンテナと動作して、ターゲットの container の前に、Envoy が動作して、プロキシする。プロキシをかますことで、L7レベルのトラフィックルーティングが可能になる。他にも、タイムアウトの設定や、Fault Injection等も可能になる。こちなみに、このパターンは、Sidecar patternのアーキテクチャのようだ。
ルーティングの設定変更
さて、サーバーへのアクセスは、Istio の Ingress 経由で行う。
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
webservice-ingress * 104.210.39.92 80 6m
といった感じで Istio の ingress が見える。何も設定しなければ、1.0.0
と 2.0.0
のどちらが出てくるかはわからない。
ルーティングを例えばこんなyamlで指定すると、必ず 1.0.0 にルーティングされる
type: route-rule
name: web-service-default
namespace: default
spec:
destination: web-service.default.svc.cluster.local
precedence: 1
route:
- tags:
version: 1.0.0
weight: 100
実行してみよう。
istioctl create -f all-v1.yaml --configAPIService=quiet-lambkin-istio-pilot:8081
istioctl コマンドで、ルールを適用すると、1.0.0
のみにルーティングされた。
ちなみに、--configAPIService がついているのは、普通にインストールすると大丈夫だが、helmでインストールすると、pilot と呼ばれるIstioの管理サービスの名前がデフォルト(istio-pilot:8081)からかわるので、指定しないといけないから。これらの値は、kubectl get svc
で見つけることができる。
はまったところ
ハマりどころとしては、Ingress の設定部分。
上記の webservice.yaml の抜粋だが、Istio の Ingress はすべてのサービスで共有するので、当初 path: /web
と設定していた。すると、pod へのリクエストも、/web
でルーティングされるので、404が出るという現象があった。バックエンドには、/でルーティングしてほしいものだが、やる方法があるかは今後の宿題。
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: webservice-ingress
annotations:
kubernetes.io/ingress.class: istio
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: web-service
servicePort: 80
というわけで、ルーティングの切り替えまでは実施できた。次は Canary はもう少し複雑なルーティングに挑戦してみよう。