先日、4月19日に開催されたJapan Container Days v18.04にて「Kubernetes x PaaS – コンテナアプリケーションのNoOpsへの挑戦」というタイトルでセッションを担当させていただいた。その名の通りメインがKubernetesで、KubernetesアプリケーションにおいてNoOps(運用レス)を目指すためのにどういった工夫ができるのか、どういったものを活用していけばよいのか、という内容です。このブログではJapan Container Daysでの発表に使用したスライドの共有とセッションに中のサンプルやデモについて補足させていただく。

Session Slides

Kubernetes x PaaS – コンテナアプリケーションの NoOpsへの挑戦 from Yoichi Kawasaki

補足情報

1. Open Service Broker for AzureでAzure Database for MySQLの利用

スライドでお見せした実際のファイルを使ってAzure Database for MySQLのサービスインスタンス作成、バインディング、そして実際のアプリケーションからの利用までの流れを紹介させていただく。

Open Service Broker for AzureプロジェクトのGithubにあるサンプルファイルmysql-instance.yamlmysql-binding.yamlを使ってそれぞれServiceInstanceとServiceBindingを作成する `

# Provisioning the database, basic50 plan ...
$ kubectl create -f mysql-instance.yaml

# Wait until ServiceInstance named example-mysql-instance get ready 'Status => Ready',
# then execute the following to create a binding for this new database,
$ kubectl create -f mysql-binding.yaml

これでexample-mysql-secretという名前のシークレットオブジェクトが作成され、そこにアプリケーションがAzure Database for MySQLインスタンスとの接続必要な情報が格納される。ここではMySQLに接続して単純な処理をするだけのPythonアプリでテストする。アプリをデプロイメントのために下記YAML (mysql-deploy.yaml)を用意したが注目すべきはYAMLの中でMySQLの接続必要な情報をシークレットexample-mysql-secretから取得している点。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: sample-osba-mysql
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sample-osba-mysql
    spec:
      containers:
      - name: osba-mysql-demo
        image: yoichikawasaki/sample-osba-mysql:0.0.1
        ports:
        - containerPort: 80
        env:
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              key: username
              name: example-mysql-secret
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              key: password
              name: example-mysql-secret
        - name: MYSQL_HOST
          valueFrom:
            secretKeyRef:
              key: host
              name: example-mysql-secret
        - name: MYSQL_DATABASE
          valueFrom:
            secretKeyRef:
              key: database
              name: example-mysql-secret

また、アプリケーションの中ではYAMLで指定された環境変数からMySQL接続に必要な情報を取得している。以下、サンプルアプリのコード(sample-osba-mysql.py)。

import MySQLdb as db
import os

# MySQL configurations
MYSQL_USER = os.environ['MYSQL_USER']
MYSQL_PASSWORD = os.environ['MYSQL_PASSWORD']
MYSQL_HOST = os.environ['MYSQL_HOST']
MYSQL_DATABASE = os.environ['MYSQL_DATABASE']

print(MYSQL_USER)
print(MYSQL_PASSWORD)
print(MYSQL_HOST)
print(MYSQL_DATABASE)

def main():
    conn = db.connect(
            user=MYSQL_USER,
            passwd=MYSQL_PASSWORD,
            host=MYSQL_HOST,
            db=MYSQL_DATABASE
        )
    c = conn.cursor()

    sql = 'drop table if exists test'
    c.execute(sql)

    sql = 'create table test (id int, content varchar(32))'
    c.execute(sql)

    sql = 'show tables'
    c.execute(sql)
    print('===== table list =====')
    print(c.fetchone())

    # insert records
    sql = 'insert into test values (%s, %s)'
    c.execute(sql, (1, 'hoge'))

    datas = [
        (2, 'foo'),
        (3, 'bar')
    ]
    c.executemany(sql, datas)

    # select records
    sql = 'select * from test'
    c.execute(sql)
    print('===== Records =====')
    for row in c.fetchall():
        print('Id:', row[0], 'Content:', row[1])

    conn.commit()
    c.close()
    conn.close()


if __name__ == '__main__':
    main()

それでは次のようにアプリケーションをデプロイする

$ kubectl create -f mysql-deploy.yaml

全ての過程が問題なければ、デプロイ後のPodのログをみると、下記のような出力が確認できるはずだ。これはアプリケーションがMySQLとの接続して処理が行われたことを表す。ここではsternを使ってログを参照している。

$ stern <アプリケーションのPod名>

+ sample-osba-mysql-684ccd679f-nl252 › osba-mysql-demo
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo o36e6ivtni@8ae3fca5-9b5c-471b-99d8-0eeb567c6acb
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo sE8UFm5cN4tgIeNF
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo 8ae3fca5-9b5c-471b-99d8-0eeb567c6acb.mysql.database.azure.com
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo v8i0xxlerz
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo ===== table list =====
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo ('test',)
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo ===== Records =====
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo Id: 1 Content: hoge
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo Id: 2 Content: foo
sample-osba-mysql-684ccd679f-nl252 osba-mysql-demo Id: 3 Content: bar

2. デモ - Virtual Kubelet + Azure Container Instances

3. デモ - Traffic Routing with Istio

セッション中に時間が足りなくてチラ見せ程度しかお見せできなかったサービスメッシュにIstio(Istio-0.5.0)を使ったトラフィックルーティングデモ。せっかくなのでここで全ての手順を紹介させていただく。アプリな単純なコンテナイメージのタグ名を表示するだけのFlaskアプリを利用している

必要ファイル一覧

まずはV1とV2アプリケーションのデプロイメント。ここではアプリケーションのManifestに対してistioctlのkube-injectでIstioの設定が組み込まれたManifestを元にデプロイメントしている。

## ver1.0
$ kubectl apply -f <(istioctl kube-inject --debug -f myversion-v1.yaml)
## ver2.0
$ kubectl apply -f <(istioctl kube-inject --debug -f myversion-v2.yaml)

上記のデプロイメントで作られたIngressコントローラーのIP、PORTを取得を次のように取得する。サンプルで使用しているFlaskアプリで指定しているエンドポイントが/versionであるからアプリケーションのエンドポイントはGATEWAY_IP:GATEWAY_PORT/versionがとなる。

GATEWAY_IP=$(kubectl get po -n istio-system -l \
        istio=ingress -n istio-system \
        -o 'jsonpath={.items[0].status.hostIP}')

GATEWAY_PORT=$(kubectl get svc istio-ingress \
        -n istio-system -n istio-system \
        -o 'jsonpath={.spec.ports[0].nodePort}')

GATEWAY_URL=$GATEWAY_IP:$GATEWAY_PORT
echo $GATEWAY_URL

まずは、Istioに全てのトラフィックをv1アプリにルーティングするように設定する

$ istioctl create -f route-default-v1.yaml

次のように連続でアクセスして全てのトラフィックがV1にルーティングされていることを確認

$ while true; do curl http://${GATEWAY_URL}/version; sleep 1; done

I am v1.111111111
I am v1.111111111
I am v1.111111111
I am v1.111111111

続いて、Istioにさきほどの100% V1の設定を削除して、新しく90%のトラフィックをv1アプリに 10%をV2アプリにルーティングするように設定する

$ istioctl delete routerule myversion-default-v1 -n default
$ istioctl create -f route-canary.yaml

前と同様に連続でアクセスして90%のトラフィックはV1に、10%はV2にルーティングされていることを確認

$ while true; do curl http://${GATEWAY_URL}/version; sleep 1; done

I am v1.111111111
I am v1.111111111
I am v1.111111111
I am v2.222222222  ... たまにV2
I am v1.111111111

最後に、Istioに特定の条件でのみ100%のトラフィックをV2アプリ向けるように設定する。下記設定ファイルroute-conditional-v2.yamlに記述されているように、ここではHTTPヘッダのuserの値が"yoichi"であることを特定の条件とする。

---
apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: myversion-conditional-v2
spec:
  destination:
    name: myversion
  precedence: 2
  match:
    request:
      headers:
        user:
          regex: "yoichi"
  route:
  - labels:
      version: v2.0

Istioに特定条件用のルーティング設定を追加

$ istioctl create -f route-conditional-v2.yaml

次のようにヘッダーに"user: yoichi"の条件を追加して連続でアクセスして全てのトラフィックがV2にルーティングされていることを確認

$ while true; do curl -H "user: yoichi" http://${GATEWAY_URL}/version; sleep 1; done

I am v2.222222222
I am v2.222222222
I am v2.222222222
I am v2.222222222

以上、補足情報でした。