IBM Cloud Container Serviceでk8sからwatson language translatorへ連携してみた

IBM Cloud Container Service で k8sからwatsonへ連携してみた
このエントリは、[Kubernetes Advent Calendar 2017](https://qiita.com/advent-calendar/2017/kubernetes) 20日目の記事です。
(6日遅れでした・・・)

# はじめに
IBM Cloudのk8sプラットフォームは、IBM Cloud Container Serviceという名称で提供されています。
マネージドなk8sプラットフォームとしてはどこのk8sプラットフォームと遜色ありませんが、

以下がIBM Cloudでk8sを使う最大魅力です。

1. IBM Bluemix時代からの資産 CloudFoundry環境アプリとの連携
2. IBM Watsonとの連携でコグニティブ環境をコンテナアプリから利用できる

# IBM Cloud Container Serviceを使ってみる
IBM Cloud のpay-as-you-goアカウントではクレジッドカードの登録でk8sクラスタのworkernodeが無料で利用できますので、これをきっかけに利用される方が増えるといいなと考えています。
IBM Cloudにはクレジッドカードなしで登録できるライトアカウントが存在しますが、執筆時点ではk8sクラスタが適用外となっているためご注意ください。

# 事前準備
1. IBM Cloud pay-as-you-goアカウントの取得
2. k8s クラスタ(IBM Cloud Container Service)のデプロイ<BR>
   今回はクラスタ名"mycluster"としてデプロイしました。<BR>
   ライトプラン　クラスタ（無料）を利用
3. watoson language translatorのデプロイ<BR>
   今回はサービス名"langtrans-test"としてデプロイしました。<BR>
   Liteプラン (1,000,000文字まで無料)を利用<BR>
4. bx(bluemix)コマンドの導入
5. kubectlの導入

# 実際にやってみる
今回ライトプランのkubernetesクラスタはUS-Southにデプロイされていた旧来のものを使用します。
ライトプランの場合地域を選んでデプロイすることができないので、デプロイされた地域をConsole上で確認してから、bxコマンドでログインします。
また、k8sクラスタの動作する地域とCloudFoundryのネームスペースのある地域が同じである必要があるので、注意してください。

#### ログイン

```bash
$ bx login -a api.ng.bluemix.net
API エンドポイント: api.ng.bluemix.net
```

### クラスタの確認
```bash
$ bx target --cf
$ bx cs clusters
OK
Name    ID                                 State    Created          Workers   Datacenter   Version   
mycluster   fbf0dff***53432***41e3a596997b6a   normal   4 weeks ago   1         dal13        1.8.4_1502  
```

#### kubectl用にクラスタ構成ファイルの読み込み
```bash
$ bx cs cluster-config mycluster
コマンド実行結果に出てくる "export KUBECONFIG="をコピーして実行する
$ export KUBECONFIG=/Users/sakaigawa/.bluemix/plugins/container-service/clusters/mycluster/kube-config-dal13-mycluster.yml
```

## Watson Service との連携
watson language translatorを今回のサンプルとして使用します。
文字通り言語翻訳の機能を持っています。

プロジェクトとして"langtrans"という名前でデプロイしたところ、
サービス名は個別にランダム文字が割り当てられたので(12/25時点)、念のため確認コマンドで確認します。

#### バインド可能なサービス一覧
```bash
$ bx service list
'cf services' を起動しています...

[your IBMid] として組織 [your IBMid] / スペース pumpkinheads 内のサービスを取得しています...
OK

名前                                 サービス                 プラン       バインド済みアプリ   最後の操作
langtrans-language-t-1514215788955   language_translator      lite                              create は成功しました
```

※もっと出てきます。

#### サービスをバインドしてみる
では実際にバインド可能なサービス名をコピーして、k8sクラスタへ割り当ててみましょう
今回は`langtrans-language-t-1514215788955`をサービスとしてクラスタへバインドします。

```bash
$ bx cs cluster-service-bind mycluster default [service name]
Binding service instance to namespace...
OK
Namespace:	default
Secret name:	binding-langtrans-language-t-1514215788955
```

## アプリケーションの準備
ここでは、DockerfileとアプリケーションのPHPファイルを準備します。
Dockerfileはお好みでドウゾ（centos+phpを入れてイメージ作る習慣がついてしまったのですいません、長いですが）

#### Dockerfileの作成
```Dockerfile:Dockerfile
#centos7のイメージを取得
FROM centos:centos7

#Dockerfile作成者
MAINTAINER Shoichiro Sakaigawa sakaigawa@pumpkinheds.jp

#タイムゾーンの設定
RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

#yumによる必要パッケージのインストール
RUN yum -y install httpd unzip git

# Vulnerability Advisor : Fix PASS_MAX_DAYS, PASS_MIN_DAYS and PASS_MIN_LEN, common-password
RUN mv -f /etc/login.defs /etc/login.defs.orig
RUN sed 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' /etc/login.defs.orig > /etc/login.defs
RUN grep -q '^PASS_MIN_DAYS' /etc/login.defs && sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' /etc/login.defs || echo 'PASS_MIN_DAYS 1\n' >> /etc/login.defs
RUN grep -q '^PASS_MIN_LEN' /etc/login.defs && sed -i 's/^PASS_MIN_LEN.*/PASS_MIN_LEN 8/' /etc/login.defs || echo 'PASS_MIN_LEN 9\n' >> /etc/login.defs
RUN grep -q '^password.*required' /etc/pam.d/common-password && sed -i 's/^password.*required.*/password    required            pam_permit.so minlen=9/' /etc/pam.d/common-password || echo 'password    required            pam_permit.so minlen=9' >> /etc/pam.d/common-password



#yumリポジトリの追加
RUN yum -y localinstall http://ftp.iij.ad.jp/pub/linux/fedora/epel/epel-release-latest-7.noarch.rpm
RUN yum -y localinstall http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

#yumによるphp7.0インストール
RUN yum install -y --enablerepo=remi --enablerepo=remi-php70 php php-common php-mbstring php-gd php-xml php-xmlrpc php-devel php-cli php-pdo php-mysql php-odbc php-pear php-mcrypt php-pecl-apc

#tmpディレクトリに移動
WORKDIR /tmp/

ADD app /tmp/app

#ディレクトリのリネーム
RUN mv app/* /var/www/html

#ディレクトリの権限変更
RUN chown -R apache:apache /var/www/html/

#httpd.confの変更
RUN sed -i -e 's#AllowOverride None#AllowOverride All#g' /etc/httpd/conf/httpd.conf

#公開ポート
EXPOSE 80

#httpdの実行
CMD ["httpd", "-D", "FOREGROUND"]
```

#### PHPファイルの作成
今回はWatsonとの連携を見てもらうためだけを目的としたので、シンプルにJSONデータを投げて返ってきたものをDUMPして出力します。

後ほどdeploy.yamlで`WATSON_TL`というシステム変数としてコンテナにサービスバインドした情報を提供します。

```php:watson-tl.php
<pre>
<?php
    echo date('l F d, Y G:i:s').PHP_EOL;

    $watson_json_env = getenv('WATSON_TL');
    $watson_json_decoded = json_decode($watson_json_env,true);
    $url=$watson_json_decoded["url"]."/v2/translate";
    $values = array(
        'source' => 'en',
        'target' => 'ja',
        'text' => "Hello, I'm watson language translator. this page now display by IBM Cloud Container Service"
    );
    $params = json_encode($values);
    
    var_dump($values);

    $ch = curl_init();
    $options = array(
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_AUTOREFERER => true,
    );
    curl_setopt($ch,CURLOPT_URL, $url);
    curl_setopt($ch,CURLOPT_HEADER, false);
    curl_setopt($ch,CURLOPT_USERPWD, $watson_json_decoded['username']. ":" . $watson_json_decoded['password']);
    curl_setopt($ch,CURLOPT_POST, true);
    curl_setopt($ch,CURLOPT_POSTFIELDS, $params);
    curl_setopt($ch,CURLOPT_VERBOSE, true);
    curl_setopt($ch,CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
    curl_setopt_array($ch, $options);
    $response=curl_exec($ch);
    $info=curl_getinfo($ch);
    curl_close($ch);
    
    var_dump($response);

?>
</pre>
```

#### docker プライベートレポジトリへの接続
Docker-Registoryのプライベート版であるibm cloudのdocker registryへログインし、
名前空間の追加を行い、名前空間に合わせてdocker imageをタグ付きでビルドします。

```bash
$ bx cr login
$ bx cr namespace-add sakaigawa
名前空間「sakaigawa」を追加しています...

名前空間「sakaigawa」は正常に追加されました

OK
```

#### コンテナのビルド
```bash
$ docker build -t registry.ng.bluemix.net/sakaigawa/hellotrans .
```

#### ビルドしたコンテナをdocker registryにpushする

```bash
$ docker push registry.ng.bluemix.net/sakaigawa/hellotrans
$ bx cr image-list
イメージをリストしています...

リポジトリー                                         名前空間   タグ     ダイジェスト   作成            サイズ   脆弱性の状況   
registry.ng.bluemix.net/sakaigawa/hellotrans   sakaigawa   latest   aa4868a89061   15 minutes ago   234 MB   OK  

OK
```

####  Deploy / Serviceの設定を定義する・コミットする
今回はやってみたをメインにするので内容を細かく書きませんが、
Podは１個、ポートはNodePortでロードバランスせずに公開します。

このdeploy.ymlのキモの1つは、image指定の直下に`env`として`WATSON_TL`を登録したことです。
`WATSON_TL`は任意の名称で、システム変数としてコンテナに引き継がれます。
先程割り当てた際に表示されたservice bind nameを貼り付けて紐付けます。

```yaml:deploy.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: hellotrans-app
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: hellotrans-app
    spec:
      containers:
      - name: hellotrans-app
        image: registry.ng.bluemix.net/sakaigawa/hellotrans
        env:
        - name: WATSON_TL
          valueFrom:
            secretKeyRef:
              name: binding-langtrans-language-t-1514215788955
              key: binding
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: hellotrans-service
  labels:
    app: hellotrans-service
spec:
  type: NodePort
  ports:
    - port: 80
  selector:
    app: hellotrans-app
```

kubectlでPodのデプロイとServiceのデプロイを実施する。

```bash
$ ~/kubectl create -f deploy.yml 
deployment "hellotrans-app" created
service "hellotrans-service" created 
```
### サーバアドレスを確認する

bx cs workers コマンドでk8s workernode の外部IPを見つけます。
今回はworker1台のライトプランですので複数台のkubernetesクラスタの場合はPodが動作するworkerを特定してからの作業が必要です。
コマンド結果のPublic IPをメモします。

```bash
$ bx cs workers ssk8s
OK
ID                                                 Public IP        Private IP       Machine Type   State    Status   Version   
kube-dal13-cr64ad7*****aa4dd3bd68ebd8ff8e6005-w1   169.60.151.246   10.186.127.202   free           normal   Ready    1.8.4_1502
```

### kubectl get svcで外部公開のポート番号を確認する

今回のサンプルでは、コンテナ側が80で公開されており、外部側は：の右側30666であることがわかりました。

```bash
$ ~/kubectl get svc
NAME                 CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
hellotrans-service   10.10.10.150   <nodes>       80:30666/TCP   1h
kubernetes           10.10.10.1     <none>        443/TCP        3h
```

### 結果
以下アドレスを参考に上記のノードIP、ノードポートを確認してアクセスしてみてください。

`http://クラスタIP:ポート番号/watson-tl.php`

以下の様にArrayで投入した結果が翻訳されて返却されてきました。

```php:結果サンプル
Tuesday December 25, 2017 23:25:55
array(3) {
  ["source"]=>
  string(2) "en"
  ["target"]=>
  string(2) "ja"
  ["text"]=>
  string(91) "Hello, I'm watson language translator. this page now display by IBM Cloud Container Service"
}
string(130) "こんにちは、私はwatson言語変換プログラムです。 このページは、IBM Cloud Service Containerによる表示"
```

# まとめ
Watsonサービスは１４０を超えるサービスが日々進化しています。
このLanguage Translatorもカテゴリや学習をさせることで進化した翻訳プラットフォームが手に入るのではないでしょうか。
アイデア次第では、フォーム入力を利用した同時通訳だったり、クラウドからの通知文を翻訳したり、OSSドキュメンテーションの自動翻訳だったり夢は広がります。