【IBM Cloud k8s検証メモ】コンテナとBluemixのWatsonサービス等と連携させる方法


Bluemixには130を超えるサービスが提供されており、コードは接続情報を実行環境から受け取って利用できる特徴があります。 Bluemix Kubernetes上で動作するコンテナでも、Bluemix CloudFondry下で提供する Watson Conversation や Cloudantの接続情報を取得することができます。 CFからk8sコンテナへの接続情報の受け渡しは、bx cs コマンドとkubectlコマンドの連携によって実施ます。


## 接続イメージ

次の図に示す様に、Bluemix k8sで動作するコンテナが、実行環境からサービスの接続情報を読み取る場合、接続情報をネームスペース全体へ提供されます。つまり、同じネームスペースにデプロイされるコンテナは、接続情報を読み取れるということを示しています。 

![スクリーンショット 2017-09-29 20.11.18.png](https://qiita-image-store.s3.amazonaws.com/0/42829/715b1972-5e01-8b40-4fa0-4551507149f4.png "スクリーンショット 2017-09-29 20.11.18.png")

このため、本番(Production)や開発(Development)ごとにNamespaceを作成しておき、サービスのインスタンスも環境ごとに作成して、それぞれのネームスペースへ接続するのが望ましいでしょう。 こうすることで、開発テスト環境で完成したコンテナは、そのまま、コードを変更することなく、本番環境にデプロイして、本番用のサービスのインスタンスに接続される様になるため、作業工数を軽減し、接続先間違いによる事故を防止することができます。

![スクリーンショット 2017-09-29 20.11.33.png](https://qiita-image-store.s3.amazonaws.com/0/42829/3235e4c4-d7b0-736c-4bf2-8f6784f8979f.png "スクリーンショット 2017-09-29 20.11.33.png")


<font color="red">接続情報は、k8s Secrets 機能[2]によってPod上のコンテナへ提供されます。提供方法には２通りあり、ボリュームとしてマウントする方法、環境変数にセットする方法があります。 </font>



## Bluemixのサービスの準備

検証のために、Bluemixコンソール ウェブ画面、または、次の様にコマンドラインから、サービスを作成します。

~~~
$ bx service create cloudantNoSQLDB Lite cloudant
~~~

サービスのリストを出力して確認しておきます。 今回利用するのは、name欄の cloudant と Conversation-k7 を k8sとバインドします。

~~~
$ bx service list
Invoking 'cf services'...

Getting services in org takara@jp.ibm.com / space dev as takara@jp.ibm.com...
OK

name                           service                  plan       bound apps   last operation
availability-monitoring-auto   AvailabilityMonitoring   Lite       tkr-php      create succeeded
cloudant                       cloudantNoSQLDB          Lite                    create succeeded
Conversation-k7                conversation             free                    create succeeded
Log Analysis-e7                ibmLogAnalysis           standard                create succeeded
~~~


## k8sネームスペースとCFサービスのバインド

BluemixのCloudFoundryで提供されるサービスと Bluemix k8sクラスタのネームスペースを結びつける作業（バインド）を進めていきます。

k8sのクラスタとサービスをバインドするためのコマンドの書式は以下の通りです。 
`bx cs cluster-service-bind [k8sクラスタ名] [k8sネームスペース名] [cfサービス・インスタンス名]` 

次のコマンドで、Cloudantのサービス・インスタンスとk8sクラスタ mycluster2 の ネームスペース "default" をバインドします。 

~~~
$ bx cs cluster-service-bind mycluster2 default cloudant
OK
Namespace:	default
Secret name:	binding-cloudant
~~~

同様に Watson Conversation をバインドします。

~~~
$ bx cs cluster-service-bind mycluster2 default Conversation-k7
OK
Namespace:	default
Secret name:	binding-conversation-k7
~~~

ここで表示された `Secret name`の値を利用しますので、コピペなどで再利用できる様にしておきます。

~~~
$ kubectl get secrets --namespace=default
NAME                      TYPE                                  DATA      AGE
binding-cloudant          Opaque                                1         32m
binding-conversation-k7   Opaque                                1         4m
bluemix-default-secret    kubernetes.io/dockercfg               1         2d
default-token-96rj2       kubernetes.io/service-account-token   3         2d
mycluster2-658797         Opaque                                2         2d
~~~

k8s の secret は、次の方法で、バインドされたネームスペースを確認できます。

~~~
$ kubectl get secret binding-cloudant -o json
{
    "apiVersion": "v1",
    "data": {
        "binding": "eyJ1c2VybmFtZSI6IjEwNzA2ZTJhLWYwOTktNDI5YS1hOTAyLTUyYTg2MWI0M2Y2Yy1ibHVlbWl4IiwicGFzc3dvcmQiOiI1ZmQ1Yzg3OTUxNjY2MzQ3MmRmYWM0MTNkOWQ5MjQxZjIyNmUwOTJmYWU****yMTI5ZjZmODc2ZTE5OTdlNTM1IiwiaG9zdCI6IjEwNzA2ZTJhLWYwOTktNDI5YS1hOTAyLTUyYTg2MWI0M2Y2Yy1ibHVlbWl4LmNsb3VkYW50LmNvbSIsInBvcnQiOjQ0MywidXJsIjoiaHR0cHM6Ly8xMDcwNmUyYS1mMDk5LTQyOWEtYTkwMi01MmE4NjFiNDNmNmMtYmx1ZW1peDo1ZmQ1Yzg3OTUxNjY2MzQ3MmRmYWM0MTNkOWQ5MjQxZjIyNmUwOTJmYWUzN2YyMTI5ZjZmODc2ZTE5OTdlNTM1QDEwNzA2ZTJhLWYwOTktNDI5YS1hOTAyLTUyYTg2MWI0M2Y2Yy1ibHVlbWl4LmNsb3VkYW50LmNvbSJ9"
    },
    "kind": "Secret",
    "metadata": {
        "annotations": {
            "service-instance-id": "054d2879-535f-4299-****-e5654456906b",
            "service-key-id": "e81dcbb6-****-4536-816d-82c9f5a42e22"
        },
        "creationTimestamp": "2017-09-29T07:10:10Z",
        "name": "binding-cloudant",
        "namespace": "default",
        "resourceVersion": "64134",
        "selfLink": "/api/v1/namespaces/default/secrets/binding-cloudant",
        "uid": "3b17255b-a4e5-11e7-b416-365be28344d2"
    },
    "type": "Opaque"
}
~~~

# 接続情報の提供方法

接続情報をコンテナに届けるには、k8sのデプロイメントのYAMLファイルに記述する必要があります。 環境変数とボリュームのそれぞれの提供方法により、YAMLファイルの記述方法が異なりますから、順番に紹介していきます。


## 環境変数として接続情報提供

次は、環境変数で提供する場合のYAMLファイルの例です。

~~~file:x01-service.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: service-bind-env
  namespace: default
spec:
  selector:
    matchLabels:
      app: service-bind-env
  replicas: 1
  template:
    metadata:
      labels:
        app: service-bind-env
    spec:
      containers:
      - name: service-bind-env
        image: php:7-fpm
        env:
        - name: VCAP_CLOUDANT            <-- 環境変数名
          valueFrom:
            secretKeyRef:
              name: binding-cloudant     <-- bx cs cluster-service-bind で返されたシークレット名
              key: binding               <-- 固定文字列
        - name: VCAP_CONVERSATION            <-- 環境変数名
          valueFrom:
            secretKeyRef:
              name: binding-conversation-k7  <-- bx cs cluster-service-bind で返されたシークレット名
              key: binding
~~~


結果を確認するため、このYAMLファイルを利用して、`kubectl apply -f x01-service.yml' を実行してPODを作成します。


~~~
$ kubectl get po
NAME                                READY     STATUS    RESTARTS   AGE
<--- 省略 --->
service-bind-env-1720912246-x3ghz    1/1       Running   0          40m
~~~

コンテナに入って、環境変数を確認します。

~~~
$ kubeclt exec -it service-bind-env-1720912246-x3ghz bash
~~~

コンテナに入ったら、`apt-get udate` と `apt-get python -y` を実行しておきます。環境変数を指定してJSON形式で表示してみます。

次は、Cloudantのケースです。 CFアプリで得られる環境変数と比較すると、credentials の中身だけが提供されているので、環境変数を読み取るコードは書き換える必要があります。

~~~
root@service-bind-env-1720912246-x3ghz:/var/www/html# echo -n $VCAP_CLOUDANT |python -m json.tool
{
    "host": "10706e2a-f099-429a-a902-52a861b43f6c-bluemix.cloudant.com",
    "password": "********",
    "port": 443,
    "url": "https://10706e2a-f099-429a-a902-52a861b43f6c-bluemix:********@10706e2a-f099-429a-a902-52a861b43f6c-bluemix.cloudant.com",
    "username": "10706e2a-f099-429a-a902-52a861b43f6c-bluemix"
}
~~~

こちらは、Watson Conversationの例です。 サービスによって違いがありますが、同じ様に得られます。 CFアプリの場合には、VCAP変数にJSONの配列として、バインドされたすべての接続情報が取れたのですが、k8s環境では、それぞれ環境変数を取る必要があります。

~~~
root@service-bind-env-1720912246-x3ghz:/var/www/html# echo -n $VCAP_CONVERSATION |python -m json.tool
{
    "password": "********",
    "url": "https://gateway.watsonplatform.net/conversation/api",
    "username": "21dd48a7-bb76-4087-a208-8d7d779e57e8"
}
~~~


## ボリュームとして接続情報を提供

次は、k8sシークレットをボリュームとしてマウントして取得する方法のYAMLファイルです。

~~~file:x02-service.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: service-bind-vol
  namespace: default
spec:
  selector:
    matchLabels:
      app: service-bind-vol
  replicas: 1
  template:
    metadata:
      labels:
        app: service-bind-vol
    spec:
      containers:
      - name: service-bind-vol
        image: php:7-fpm
        volumeMounts:
        - mountPath: /bluemix/cloudant           <-- コンテナでのマウントポイント
          name: cloudant                         <-- ボリューム名
        - mountPath: /bluemix/conversation       <-- コンテナでのマウントポイント
          name: conversation                     <-- ボリューム名
      volumes:
      - name: cloudant                 <-- 上記に対応するボリューム名
        secret:
          defaultMode: 444
          secretName: binding-cloudant   <--- bx cs cluster-service-bind で返されたシークレット名
      - name: conversation              <-- 上記に対応するボリューム名
        secret:
          defaultMode: 444
          secretName: binding-conversation-k7      <--- bx cs cluster-service-bind で返されたシークレット名 
~~~

こちらも、前述同様に、デプロイを実行して、コンテナへログインします。

~~~
$ kubectl exec -it service-bind-vol-200860301-t2gpr bash
root@service-bind-vol-200860301-t2gpr:/var/www/html# df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          97G  5.5G   92G   6% /
tmpfs           2.0G     0  2.0G   0% /dev
tmpfs           2.0G     0  2.0G   0% /sys/fs/cgroup
tmpfs           2.0G  4.0K  2.0G   1% /bluemix/cloudant
tmpfs           2.0G  4.0K  2.0G   1% /bluemix/conversation
/dev/xvda2       97G  5.5G   92G   6% /etc/hosts
shm              64M     0   64M   0% /dev/shm
tmpfs           2.0G   12K  2.0G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs           2.0G     0  2.0G   0% /sys/firmware
~~~

上記で、YAMLファイルに設定した /bluemixの下のマウントポイントが確認できます。 

前述同様に、pythonを導入してJSON形式を読みやすく表示してみます。

~~~
root@service-bind-vol-200860301-t2gpr:/var/www/html# python -m json.tool /bluemix/cloudant/binding 
{
    "host": "10706e2a-f099-429a-a902-52a861b43f6c-bluemix.cloudant.com",
    "password": "********",
    "port": 443,
    "url": "https://10706e2a-f099-429a-a902-52a861b43f6c-bluemix:*********@10706e2a-f099-429a-a902-52a861b43f6c-bluemix.cloudant.com",
    "username": "10706e2a-f099-429a-a902-52a861b43f6c-bluemix"
}
~~~

Watson Conversation も同じ様に読み取れます。

~~~
root@service-bind-vol-200860301-t2gpr:/var/www/html# python -m json.tool /bluemix/conversation/binding 
{
    "password": "*******",
    "url": "https://gateway.watsonplatform.net/conversation/api",
    "username": "21dd48a7-bb76-4087-a208-8d7d779e57e8"
}
~~~




# 参考資料
[1] IBM Bluemix Container Service のクラスターに Bluemix サービスを追加する https://console.bluemix.net/docs/containers/cs_apps.html?pos=2#cs_apps_service
[2] Secrets https://kubernetes.io/docs/concepts/configuration/secret/