IBM Containers (Kubernetes)の上でOpenWhiskを構成してみる

ちょっと間を空けての投稿です。<br>
この間[Bluemixのユーザー会](https://bmxug.connpass.com/event/56114/)でOpenWhiskを話しましたが、その続きを・・・と思いつつも、最近どうしてもIBM Containers(Kubernetes)と仲良くならないといけない状況に陥ってしまったので、せっかくなのでOpenWhiskと合わせて楽しみましょうということで、Kubernetes上でOpenWhisk環境を構成してみます。

Mac OS v10.12.5で以下試しています。お使いの環境によっては手順が若干変わると思いますのでご注意ください。

# IBM Containers (Kubernetes)とは
IBM Bluemixで最近利用可能になった、新しいCaaS (Container as a Service)の環境です。詳しくは以下を参考にすると良いと思います。

- [bluemix kubernetes clusterを試してみる](http://qiita.com/ssakaigawa/items/c6a1ea7dcbf62289e3fe)
- [IBM Bluemix Container Serviceで自前のdocker imageを稼働させるまで](http://qiita.com/hkitahara/items/693479bcc9b909875795)

今回はすでにKubernetes Cluster環境があるものとして話を進めます。
環境はマルチテナントの無償のLite Clusterで十分です。シングルテナントが良いならStandardなClusterで環境を事前に用意しておきます。

# OpenWhiskデプロイ関連のコードを用意する
OpenwhiskをIBM Bluemix上のKubernetesにデプロイするための手順がIBMから公開されています。
(1) [OpenWhisk on Kubernetes leveraging Bluemix Container Service](https://github.com/IBM/OpenWhisk-on-Kubernetes)
・・・が、実態は以下のリポジトリーのコードが導入コードだったりします。
(2) [Github - OpenWhisk Deployment for Kubernetes](https://github.com/apache/incubator-openwhisk-deploy-kube) 

導入にあたっては両方あると良いので、とりあえず「\$HOME/workspace 」(\$HOMEはターミナルのホーム・ディレクトリー)にgit cloneでそれぞれのコードを引っ張ってきます。

```bash
$git clone https://github.com/IBM/OpenWhisk-on-Kubernetes.git
Cloning into 'OpenWhisk-on-Kubernetes'...
remote: Counting objects: 222, done.
remote: Total 222 (delta 0), reused 0 (delta 0), pack-reused 222
Receiving objects: 100% (222/222), 716.35 KiB | 134.00 KiB/s, done.
Resolving deltas: 100% (123/123), done.
```

```bash
$git clone https://github.com/apache/incubator-openwhisk-deploy-kube.git
Cloning into 'incubator-openwhisk-deploy-kube'...
remote: Counting objects: 212, done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 212 (delta 2), reused 22 (delta 2), pack-reused 180
Receiving objects: 100% (212/212), 66.87 KiB | 54.00 KiB/s, done.
Resolving deltas: 100% (58/58), done.
```

あとは後々前提条件にもなってしまうので、OpenWhiskのソースコードを手元に落としておきます。今回は上記のソースコードと同様、「\$HOME/workspace」に落としておきます。

```bash
$git clone --depth=1 https://github.com/apache/incubator-openwhisk.git openwhisk
:
```
OpenWhiskの操作コマンドであるwskコマンドは「\$HOME/workspace/openwhisk/bin」に配置し、パスを通しておくのが良いです。wskコマンドのダウンロード先は以下のリンクからダウンロードしてください。
https://openwhisk.ng.bluemix.net/cli/go/download/

# クラスターにアクセスする
IBM Containersでmyclusterという名前のクラスターがすでに作成されている前提で、bluemixコマンドでloginした後、クラスターの構成をダウンロードします。

```
$bluemix cs cluster-config mycluster
Downloading cluster config for mycluster
OK
mycluster の構成は正常にダウンロードされました。 環境変数をエクスポートして Kubernetes の使用を開始してください。

export KUBECONFIG=/Users/hoge/.bluemix/plugins/container-service/clusters/mycluster/kube-config-dal10-mycluster.yml
```
正常に構成情報をダウンロードできたら、環境変数KUBECONFIGをOSに合わせてターミナル上で設定し、以下のコマンドでアクセスができているかを確認します。Server Versionが確認できれば、まずは問題ないでしょう。

```
$kubectl version
Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.6", GitCommit:"7fa1c1756d8bc963f1a389f4a6937dc71f08ada2", GitTreeState:"clean", BuildDate:"2017-06-16T18:34:20Z", GoVersion:"go1.7.6", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"5+", GitVersion:"v1.5.6-4+abe34653415733", GitCommit:"abe346534157336e6bd5a70702756cff19d43a49", GitTreeState:"clean", BuildDate:"2017-05-18T16:52:50Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}
```
# 名前空間を作成する
Kubernetes上で、名前空間(namespace)を作成します。名前空間は擬似的なクラスターで、ポッドの参照範囲を分けたい場合などに定義します。名前空間はデフォルトのまま「openwhisk」とします。

```bash
# incubator-openwhisk-deploy-kubeに移動
$cd $HOME/incubator-openwhisk-deploy-kube

# 名前空間を作成するためのリソース・ファイルを確認
$cat configure/openwhisk_kube_namespace.yml 
kind: Namespace
apiVersion: v1
metadata:
  name: openwhisk
  labels:
    name: openwhisk

# 名前空間の作成
$kubectl apply -f configure/openwhisk_kube_namespace.yml
namespace "openwhisk" created
```

# 適切なパーミッションを与える
OpenWhiskをIBM ContainersのKubernetes上で構成するためには、適切な権限を与える必要があります。パーミッションの定義を以下で実施します。

```bash
# OpenWhisk-on-Kubernetesに移動
$cd $HOME/OpenWhisk-on-Kubernetes

# パーミッションを与えるためのリソース・ファイルを確認
$cat permission.yaml
apiVersion: rbac.authorization.k8s.io/v1alpha1
kind: ClusterRoleBinding
metadata:
  name: openwhisk:admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: default
  namespace: openwhisk

# パーミッションの作成
$kubectl create -f permission.yaml
clusterrolebinding "openwhisk:admin" created
```

# OpenWhiskを構成する
あとは導入するだけです、やったね、簡単!!・・・と言いたいところなのですが、そのまま[README](https://github.com/apache/incubator-openwhisk-deploy-kube) の通りに与えられた設定では、アクションを実行するためのInvokerが構成できないため、進めません。<br>

>2017/07/14 追記
>Issueで確認したところ、仕様チェンジをしようとしていて、invokerは別途導入するためのリソース・ファイルが用意され、個別に導入するようになったとのことです。リポジトリーの更新はまだみたいなので、更新次第以下の構成手順の説明を更新します。

ではどうするか、ですが、自分で必要なイメージを作ることになります。それも[README](https://github.com/apache/incubator-openwhisk-deploy-kube)に記載がありますので、指示に従って構成すれば良いので迷うことはないと思います。DockerHubのアカウントを持っていることが前提ですので、持っていない方は取得しましょう。

incubator-openwhisk-deploy-kubeに移動し、スクリプト「docker/build.sh」を叩くだけなのですが、Mac OSの場合sedのオプションが異なるため、確実にエラーになってしまいます。

```bash
$cat docker/build.sh 
#!/usr/bin/env bash
:
# build nginx
pushd $SCRIPTDIR/nginx
 mkdir -p blackbox
 pushd blackbox
   # copy docker sdk to dockerSkeleton in scratch space
   cp $OPENWHISK_DIR/sdk/docker/buildAndPush.sh .
   cp $OPENWHISK_DIR/sdk/docker/Dockerfile .
   cp $OPENWHISK_DIR/sdk/docker/example.c .
   cp $OPENWHISK_DIR/sdk/docker/README.md .

   # rename base image in Dockerfile (この下の行が問題)
   sed -i "s|FROM dockerskeleton|FROM openwhisk/dockerskeleton|g" Dockerfile
:
```
回避策として、gnu-sedをHomebrewで導入してsedのエイリアスとしてgsedを登録するか、スクリプトをsedではなくgsedに書き換えてしまうのが手っ取り早いかと思います。

```
$brew install gnu-sed
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (caskroom/cask).
No changes to formulae.

==> Downloading https://homebrew.bintray.com/bottles/gnu-sed-4.4.sierra.bottle.tar.gz
:
🍺  /usr/local/Cellar/gnu-sed/4.4: 12 files, 491KB
```
回避策を講じたら、早速ビルド処理を行います。「docker/build <Docker username> <(optional) openwhisk dir>」で実行するとのことですが、第１引数はDockerHubのアカウント名、第２引数はOpenWhiskのソース・リポジトリのパスを指定すれば良いです。特に第２引数は「\$HOME/workspace/openwhisk」としておけば指定する必要がないです。

```
$./docker/build.sh ra1nmaker
+ '[' -z ra1nmaker ']'
+ OPENWHISK_DIR=
+ '[' -z '' ']'
+ cat
  Second argument should be location of the OpenWhisk repo on the local
:
f5cfc06b640d: Layer already exists 
9669d6b73383: Layer already exists 
v1.6.2-dev: digest: sha256:0f98ed7d0be15dea93d47ef8e410f112dc464c4deb097d53ef94f061446ab9f0 size: 2620
+ popd
~/workspace/incubator-openwhisk-deploy-kube
```
特にエラーがなければOKです。DockerHubにログインすると、whisk_configという名前のイメージができていることが確認できます。このイメージはOpenWhiskを構成するために使用するイメージです。

![スクリーンショット 2017-07-12 21.37.54.png](https://qiita-image-store.s3.amazonaws.com/0/101771/9d8eb6c2-ad8f-25c6-4ea7-3466ed8b1098.png)

用意ができたら、OpenWhiskを構成するためのリソース・ファイルを開き、imageの項目を作成したイメージに置き換えます。

```
$cat configure/configure_whisk.yml
---
apiVersion: batch/v1
kind: Job
metadata:
  name: configure-openwhisk
  namespace: openwhisk
  labels:
    name: configure-openwhisk
spec:
  completions: 1
  template:
    metadata:
      labels:
        name: config
    spec:
      restartPolicy: Never
      containers:
      - name: configure-openwhisk
        image: ra1nmaker/whisk_config:v1.5.6
        imagePullPolicy: Always
        command: [ "/incubator-openwhisk-deploy-kube/configure/configure.sh" ]
```

編集したら、実際にOpenWhiskを導入します。

```bash
# OpenWhiskの導入
$kubectl apply -f configure/configure_whisk.yml
job "configure-openwhisk" created
```

実行すると、Kubernetes上にポッドが作成され、OpenWhiskの構成タスクが走ります。ジョブの状況を確認したい場合は、以下の方法でログをtailすると良いと思います。

```
# 構成ジョブ用のポッド名を確認
$kubectl -n openwhisk get pods
NAME                        READY     STATUS              RESTARTS   AGE
configure-openwhisk-s520q   0/1       ContainerCreating   0          30s

# ポッドがレディーな状態になったのを確認してから、ログをtail
$kubectl -n openwhisk logs -f configure-openwhisk-s520q
:
Wednesday 12 July 2017  02:16:30 +0000 (0:00:00.040)       0:03:22.966 ******** 
=============================================================================== 
invoker : wait until Invoker is up and running ------------------------- 82.59s
consul : wait until the Consul Server/Agent in this host is up and running -- 40.93s
controller : wait until the Controller in this host is up and running -- 12.35s
Gathering Facts --------------------------------------------------------- 8.03s
Gathering Facts --------------------------------------------------------- 7.74s
Gathering Facts --------------------------------------------------------- 7.73s
Gathering Facts --------------------------------------------------------- 7.69s
Gathering Facts --------------------------------------------------------- 7.68s
kafka : create the active-ack and health topic -------------------------- 5.72s
kafka : create the invoker topics --------------------------------------- 2.84s
consul : fill consul kv ------------------------------------------------- 2.46s
kafka : wait until the Zookeeper in this host is up and running --------- 1.26s
kafka : get kafka pods -------------------------------------------------- 1.16s
consul : create configmap ----------------------------------------------- 1.12s
consul : create consul deployment --------------------------------------- 1.10s
invoker : create invoker deployment ------------------------------------- 1.10s
kafka : get zookeeper pods ---------------------------------------------- 1.10s
kafka : create zookeeper deployment ------------------------------------- 1.09s
kafka : create kafka deployment ----------------------------------------- 1.09s
controller : create controller deployment ------------------------------- 1.09s
+ popd
/
```
ジョブが実行終了すると、ポッドは停止します。停止したらポッドの一覧を確認すると、OpenWhiskを構成しているコンポーネントのポッドが稼働していることが確認できます!!

```
$kubectl -n openwhisk get pods --show-all=true
NAME                          READY     STATUS      RESTARTS   AGE
configure-openwhisk-s520q     0/1       Completed   0          7m
consul-57995027-gtkfr         2/2       Running     0          5m
controller-3250411552-jxnrd   1/1       Running     0          4m
couchdb-109298327-0h6c6       1/1       Running     0          6m
invoker-0                     1/1       Running     0          4m
kafka-1060962555-62sx5        1/1       Running     0          4m
zookeeper-1304892743-w5vwj    1/1       Running     0          4m
```

# エンドポイントを構成する
後の手順は[README](https://github.com/apache/incubator-openwhisk-deploy-kube)に沿えば十分ですが、以下備忘録的に残しておきます。

```
# nginxの構成ディレクトリーに移動する
$ cd $HOME/incubator-openwhisk-deploy-kube/kubernetes/nginx

# オレオレ証明書を作成する (本当はここでちゃんとした証明書を発行したいところ)
$./certs.sh localhost
+ '[' -z localhost ']'
+ mkdir -p certs
+ openssl req -x509 -newkey rsa:2048 -keyout certs/key.pem -out certs/cert.pem -nodes -subj /CN=localhost -days 365
Generating a 2048 bit RSA private key
................+++
....................................................+++
writing new private key to 'certs/key.pem'

# nginxの構成ファイルをコンフィグ・マップに登録
$ kubectl -n openwhisk create configmap nginx --from-file=nginx.conf
configmap "nginx" created

# 証明書をシークレットに登録
$ kubectl -n openwhisk create secret tls nginx --cert=certs/cert.pem --key=certs/key.pem
secret "nginx" created

# Nginxの構成のためのリソース・ファイルを編集する (イメージの部分を置き換える)
$cat nginx.yml
:
      containers:
      - name: nginx
        imagePullPolicy: Always
        image: ra1nmaker/whisk_nginx
:

# Nginxの構成
$kubectl apply -f nginx.yml
service "nginx" created
deployment "nginx" created

# 結果の確認
$kubectl -n openwhisk get pods --show-all=true
NAME                          READY     STATUS      RESTARTS   AGE
configure-openwhisk-s520q     0/1       Completed   0          14m
consul-57995027-gtkfr         2/2       Running     0          12m
controller-3250411552-jxnrd   1/1       Running     0          11m
couchdb-109298327-0h6c6       1/1       Running     0          13m
invoker-0                     1/1       Running     0          10m
kafka-1060962555-62sx5        1/1       Running     0          11m
nginx-599024668-lg718         1/1       Running     0          1m
zookeeper-1304892743-w5vwj    1/1       Running     0          11m
```

# OpenWhiskにアクセスする
Nginxを用意することでOpenWhiskに対しアクセスするためのエンドポイントが定義されました。外部からはNodePortと呼ばれるサービスでアクセスできるようにしているので、どのIPアドレス、ポートでアクセスするかをコマンドで確認し、アクセスしていきます。

```
# ノードのIPアドレスを確認
$kubectl get nodes
NAME         STATUS    AGE       VERSION
xx.xx.xx.xx   Ready     17d       v1.5.6-4+abe34653415733

# 確認したIPアドレスを環境変数WSK_IPADDRにセット
$export WSK_IPADDR=xx.xx.xx.xx

# NodePortで公開されているポート番号を環境変数WSK_PORTに埋め込む
$export WSK_PORT=$(kubectl -n openwhisk describe service nginx | grep https-api | grep NodePort| awk '{print $3}' | cut -d'/' -f1)

# プロパティーをセット
# 認証情報はREADME通り「789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP」を使用
$wsk property set --auth 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP --apihost https://$WSK_IPADDR:$WSK_PORT
ok: whisk auth set. Run 'wsk property get --auth' to see the new value.
ok: whisk API host set to https://xx.xx.xx.xx:31800
```
これでコマンドラインのセットアップは終了です。あとは必要なパッケージをインポートするだけ。

# wskにシステム・パッケージをインポートする
これもお決まり通り普通に導入するだけです。

```
# カタログのソースコードをダウンロード
cd $HOME/workspace
git clone https://github.com/apache/incubator-openwhisk-catalog

# 導入スクリプトがあるディレクトリに移動
cd $workspace/incubator-openwhisk-catalog/packages

# パッケージの導入
# 認証情報はREADME通り「789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP」を使用
$ ./installCatalog.sh 789c46b1-71f6-4ed5-8c54-816aa4f8c502:abczO3xZCLrMN6v2BKK1dXYFpXlPkccOFqm12CdAsMgRU4VrNZ9lyGVCGuMDGIwP https://$WSK_IPADDR:$WSK_PORT

Installing OpenWhisk packages
Installing package installCombinators.sh with pid 32554
Installing package installGit.sh with pid 32555
Installing package installSlack.sh with pid 32556
Installing package installSystem.sh with pid 32557
Installing package installWatson.sh with pid 32558
Installing package installWeather.sh with pid 32559
Installing package installWebSocket.sh with pid 32560
Installing action combinator package.
Installing Git package.
Creating package combinators with pid 32603
Installing whisk.system entities.
Creating package github with pid 32604
Installing Watson package.
Installing Slack package.
Creating package utils with pid 32605
Creating package watson-translator with pid 32606
Creating package slack with pid 32607
Creating package samples with pid 32608
Installing WebSocket package.
Creating package watson-speechToText with pid 32609
Creating package websocket with pid 32610
Installing Weather package.
Creating package watson-textToSpeech with pid 32611
Creating package weather with pid 32612
ok: updated package github
ok: updated package watson-textToSpeech
ok: updated package watson-translator
ok: updated package websocket
ok: updated package watson-speechToText
ok: updated package weather
ok: updated package slack
ok: updated package utils
ok: updated package combinators
ok: updated package samples
32604 finished with status 0
32606 finished with status 0
32609 finished with status 0
32611 finished with status 0
32610 finished with status 0
32607 finished with status 0
32612 finished with status 0
32605 finished with status 0
Installing slack/post with pid 32613
Installing websocket/send with pid 32614
Installing github/webhook with pid 32615
32608 finished with status 0
32603 finished with status 0
Installing weather/forecast with pid 32616
Installing watson-speechToText/speechToText with pid 32617
Installing utils/echo with pid 32618
Installing combinators/eca with pid 32619
Installing utils/cat with pid 32620
Installing watson-translator/translator with pid 32621
Installing combinators/forwarder with pid 32622
Installing utils/smash with pid 32623
Installing watson-translator/languageId with pid 32624
Installing combinators/retry with pid 32625
Installing utils/split with pid 32626
Installing watson-textToSpeech/textToSpeech with pid 32627
Installing combinators/trycatch with pid 32628
Installing utils/sort with pid 32629
Installing utils/head with pid 32630
Installing utils/date with pid 32631
Installing utils/namespace with pid 32632
Installing utils/hosturl with pid 32633
Installing samples/helloWorld with pid 32634
Installing samples/greeting with pid 32635
Installing samples/wordCount with pid 32636
Installing samples/curl with pid 32637
ok: updated action utils/smash
ok: updated action combinators/eca
32619 finished with status 0
ok: updated action slack/post
32613 finished with status 0
Slack package ERRORS = 0
ok: updated action combinators/trycatch
ok: updated action utils/sort
ok: updated action samples/wordCount
ok: updated action utils/echo
32618 finished with status 0
ok: updated action samples/helloWorld
ok: updated action samples/greeting
ok: updated action utils/split
ok: updated action utils/namespace
ok: updated action combinators/retry
ok: updated action combinators/forwarder
ok: updated action samples/curl
ok: updated action github/webhook
ok: updated action weather/forecast
32622 finished with status 0
32625 finished with status 0
32628 finished with status 0
combinator package ERRORS = 0
32554 finished with status 0
32615 finished with status 0
Git package ERRORS = 0
32555 finished with status 0
32556 finished with status 0
32616 finished with status 0
Weather package ERRORS = 0
ok: updated action utils/hosturl
ok: updated action websocket/send
32614 finished with status 0
WebSocket package ERRORS = 0
ok: updated action watson-textToSpeech/textToSpeech
ok: updated action utils/head
ok: updated action watson-speechToText/speechToText
32617 finished with status 0
ok: updated action utils/cat
32620 finished with status 0
32623 finished with status 0
32626 finished with status 0
32629 finished with status 0
32630 finished with status 0
ok: updated action watson-translator/languageId
ok: updated action utils/date
32631 finished with status 0
32632 finished with status 0
32633 finished with status 0
32634 finished with status 0
32635 finished with status 0
32636 finished with status 0
32637 finished with status 0
whisk.system entities ERRORS = 0
32557 finished with status 0
ok: updated action watson-translator/translator
32621 finished with status 0
32624 finished with status 0
32627 finished with status 0
Watson package ERRORS = 0
32558 finished with status 0
32559 finished with status 0
32560 finished with status 0
open catalog ERRORS = 0

# 導入できたかの確認
$wsk -i package list
packages
/whisk.system/watson-translator                                        shared
/whisk.system/weather                                                  shared
/whisk.system/utils                                                    shared
/whisk.system/watson-textToSpeech                                      shared
/whisk.system/github                                                   shared
/whisk.system/slack                                                    shared
/whisk.system/combinators                                              shared
/whisk.system/websocket                                                shared
/whisk.system/watson-speechToText                                      shared
/whisk.system/samples                                                  shared
```
以上でセットアップは終わりです。

# OpenWhiskをテストする
あとは適当にアクションを実行してみます。

```
$wsk -i action invoke /whisk.system/utils/echo -p message hello --blocking --result
{
    "message": "hello"
}
```
特にエラーとならず実行できていれば完了です!!

# まとめ
今回はIBM Containers(Kubernetes)上にMy OpenWhisk環境をセットアップする手順をなぞってみました。現時点ではイメージを自作しないとうまく進めませんが、近いうち修正されると思いますので、それを待ってから導入を試してみるでも良いかもしれません。

個人的にはAPI GatewayがMy OpenWhisk環境には入っていないので、引き続きこのKubernetes Cluster上のOpenWhiskに追加で導入していこうかなと思っています。