Help us understand the problem. What is going on with this article?

Istio を用いた Blue Green / Canary Deployment その1

More than 3 years have passed since last update.

今回は 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![blue.png](https://qiita-image-store.s3.amazonaws.com/0/3470/e96e853b-1cdd-a648-d60a-2ca90e2ac6de.png)

status: {}

Envoyというproxy が注入されることで Kubernetes にデプロイされた Istio の管理用コンテナと動作して、ターゲットの container の前に、Envoy が動作して、プロキシする。プロキシをかますことで、L7レベルのトラフィックルーティングが可能になる。他にも、タイムアウトの設定や、Fault Injection等も可能になる。こちなみに、このパターンは、Sidecar patternのアーキテクチャのようだ。

istio.png

ルーティングの設定変更

さて、サーバーへのアクセスは、Istio の Ingress 経由で行う。

$ kubectl get ingress
NAME                 HOSTS     ADDRESS         PORTS     AGE
webservice-ingress   *         104.210.39.92   80        6m

といった感じで Istio の ingress が見える。何も設定しなければ、1.0.02.0.0 のどちらが出てくるかはわからない。

blue.png
green.png

ルーティングを例えばこんな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

blue.png
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 はもう少し複雑なルーティングに挑戦してみよう。

TsuyoshiUshio@github
プログラマ。自分の学習用のブログです。内容は会社とは一切関係ありません。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away