【IBM Cloud k8s検証メモ】アプリをHTTPSとドメイン名でアクセスする



今回の目標は、Cloud Foundry アプリと同様に、Bluemix k8sでロードバランサーによる負荷分散された高可用性環境にコンテナを配置して、Bluemixで自動付与されたドメイン名へHTTPSでアクセスできる様にすることです。


![スクリーンショット 2017-09-11 10.01.59.png](https://qiita-image-store.s3.amazonaws.com/0/42829/c6c7419a-e91c-fe36-a98c-d1616b7e6f6b.png "スクリーンショット 2017-09-11 10.01.59.png")



## 前提条件
k8sで、HTTPSを利用できる様にするためには、有償の標準クラスタを利用する必要あります。 このため、フリーアカウントでは利用できません。また、Bluemix Infrastrucre と連携ができない Bluemix アカウントでは利用できませんので、ご了承願います。

* k8s標準クラスタを作るために、クレジットカード登録または、IBM契約のあるBluemixのアカウント
* Vagrant + Virtual Box が動作するPC環境 (Windows PC, Mac, Linux PCのいづれか)
* インターネット接続 10Mbps以上



## ローカル環境の構築

Cloud Foundry の場合には、コードをアップロードするだけで良かったのですが、k8sの場合は、ビルド済みのコンテナを利用します。 したがって、アプリを開発する場合には、Bluemix CLIコマンドやプラグイン以外に、Dockerのビルド環境が必要になります。


* Bluemix環境と連携させるために bxコマンド、および プラグイン
* k8sのマスターノードと連携するために、kubectl コマンド、 
* アプリケーションをコンテナ化するために Docker環境
* プログラム言語の開発環境

Vagrant + Virtual Box を利用して開発環境を作っている方向けに、PHP,Ruby,Python,Node のバージョン管理、git, Bluemix CLI, kubectl などコマンドと前提パッケージを予めインストールした仮想サーバーを作るためのVagrantfile を作成してあります。
https://github.com/takara9/bluemix-dev



## k8sクラスタの作成

ここからの操作は、前述のVagrantfileを利用して作成した仮想サーバー上での操作です。 それから、bx login を完了した状態から始めます。

k8sの標準クラスタを構築していきます。 この操作は、Bluemix の ウェブ画面からも同様の事が出来るのですが、この記事では、将来自動化しやすい様に、すべて、CLIで進めていきます。

最初に、どこのデータセンターにk8sのクラスタを構築するかを決めておきます。 [現在k8sが利用出来るデータセンター](https://console.bluemix.net/docs/containers/container_index.html#clusters)を参照して決めると良いと思います。 近々、東京データセンターでも k8sクラスタが利用できる様になりますので、乞うご期待です。　

この資料では、Bluemix Infrastructure の ダラス 第12データセンター DAL12を選択して進めていきます。 まず、コンテナのホストになるサーバーを決めて、Nameの欄の表記をメモしておきます。　

~~~
$ bx cs machine-types dal12
OK
Name         Cores   Memory   Network Speed   OS             Storage   Server Type
u1c.2x4      2       4GB      1000Mbps        UBUNTU_16_64   100GB     virtual
b1c.4x16     4       16GB     1000Mbps        UBUNTU_16_64   100GB     virtual
b1c.16x64    16      64GB     1000Mbps        UBUNTU_16_64   100GB     virtual
b1c.32x128   32      128GB    1000Mbps        UBUNTU_16_64   100GB     virtual
b1c.56x242   56      242GB    1000Mbps        UBUNTU_16_64   100GB     virtual
~~~

ここでは、以前にDAL12を利用した事があり、VLANが割り当てられていれば表示されます。 

~~~
$ bx cs vlans dal12
OK
ID        Name   Number   Type      Router
2114477          1441     private   bcr01a.dal12
2114475          1286     public    fcr01a.dal12
~~~


スタンダード・クラスタを作成します。 --name で指定する名前や --workers　で指定するノードの数、 --machine-type で指定される サーバーのスペックなどは、目的に合わせて変更をお願いします。 また、 --public-vlan, --private-vlan の IDは省略すれば、自動割り当てとなります。

~~~
$ bx cs cluster-create --name mycluster3 --location dal12 --machine-type u1c.2x4 --private-vlan 2114477 --public-vlan 2114475 --workers 3
Creating cluster...
OK
~~~

2017年9月現在の実装状況では、DAL12で3ノードを指定した場合、上記コマンドを投入してから約30分でクラスタが完成しました。 

~~~
$ bx cs clusters
OK
Name         ID                                 State    Created                    Workers   Datacenter
mycluster3   dc74ec9f2d9242ee824a2af3da894d31   normal   2017-09-09T15:11:17+0000   3         dal12
~~~


今回作成したクラスタの詳細を確認しておきます。 Ingress subdomein と Ingress secret は、後ほど利用しますのでメモしておきます。

~~~
$ bx cs cluster-get mycluster3
Retrieving cluster mycluster3...
OK

Name:                   mycluster3
ID:                     dc74ec9f2d9242ee824a2af3da894d31
State:                  normal
Created:                2017-09-09T15:11:17+0000
Datacenter:             dal12
Master URL:             https://169.47.70.10:21530
Ingress subdomain:      mycluster3.us-south.containers.mybluemix.net
Ingress secret:         mycluster3
Workers:                3
Log Space:              7e209692-4e0c-40b2-a7a6-6abb874dba8d

Addons
Name                   Enabled
customer-storage-pod   true
storage-watcher-pod    true
basic-ingress          true
~~~

初期の頃に、k8sのクラスタを構築した場合、 Addonsが動作していない事がありました。 これらが Addons Enabked が false になっていた場合、関連する機能が働きません。　もし、false があれば、チケットに問い合わせるか、または、削除して再作成するのが良いです。 

以上で、クラスタの準備ができましたので、次にアプリの開発へ触れていきます。


## Node.js アプリの開発

この記事では、コンテナでアプリを開発する事が主眼ではないので、最も簡単と思う方法で、簡単なアプリを作ります。 

開発用のディレクトリを作成して、そこへ移動します。

~~~~
mkdir dev_app
cd dev_app
~~~~

Node.js の Expressフレームワークのコード生成ツールをインストールします。

~~~
$ npm install express-generator -g
~~~

次のコマンドで、テンプレートアプリを作成します。

~~~
$ express --view=pug myapp
~~~

ローカル環境でのテストの為に、生成したアプリのディレクトリへ移動します。

~~~
$ cd myapp
~~~

アプリが実行に必要なモジュールをインストールします。

~~~
$ npm install
~~~

デバッグモードで起動してテストします。

~~~
$ DEBUG=myapp:* npm start
~~~

別のターミナル・ウィンドを立ち上げて、もう一つ、vagrant ssh でログインして、curlコマンドでアクセスして、下記のHTMLのレスポンスがあれば、アプリの準備完了です。

~~~
$ curl http://localhost:3000/
<!DOCTYPE html><html><head><title>Express</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>Express</h1><p>Welcome to Express</p></body></html>
~~~



## アプリのコンテナ化

作成したアプリのディレクトリから、コンテナイメージを作成します。 　この為に、ディレクトリを一つ上がって、dev_appに移動します。

~~~
$ cd ..
~~~

コンテナを作成する為に、Dockerfileの名前で、カレントディレクトリに次の内容のファイルを作成します。

~~~file:Dockerfile
FROM ubuntu
RUN apt-get update
RUN apt-get install curl -y
RUN curl -sL https://deb.nodesource.com/setup_6.x | /bin/bash -
RUN apt-get install nodejs -y
ADD ./myapp /app
ENV NODE_ENV development
EXPOSE 3000
CMD ["node", "/app/bin/www"]
~~~

Dockerfileの内容について、以下に列挙します。　詳しい説明説明は、[Docker reference](https://docs.docker.com/engine/reference/builder/)にあります。
* FROM ベースになるOSを指定するもので、https://hub.docker.com/_/ubuntu/ のlatest バージョンになります 
* RUN 初回だけ実行するコマンドです。 nodeの環境などをインストールしています
* ADD アプリのディレクトリ ./myapp を コンテナのファイルシステム上の/app へコピーします
* ENV 環境変数設定
* EXPOSE 外部へ開くポート番号
* CMD コンテナ起動時に毎回実行するコマンド


次のコマンドで、アプリケーションのディレクトリから、Dockerコンテナを作成します。

~~~
$ docker build -t express-app . 
~~~

ビルドされたコンテナは、Dockerのローカル・リポジトリに登録されます。 

~~~
$ docker images
REPOSITORY                                       TAG                 IMAGE ID            CREATED             SIZE
express-app                                      latest              94ffae43dcae        11 seconds ago      265MB
~~~

ローカル・リポジトリのコンテナを起動します。 -p は外部に公開するポートで、外部公開ポート番号:コンテナのEXPOSEで指定したポート番号となります。 例えば、-p 4000:3000 とすれば、EXPOSE で指定したポート番号を 4000番に対応させて外部からアクセスできる様になります。 また -t は TTYからアクセスできる様にします。 もし、-t を忘れると Ctrl + Cで止める事ができなくなります。

~~~
$ docker run -t -p 3000:3000 express-app
~~~

上記と別のウィンドから、curl コマンドでアクセスして、コンテナの動作を確認します。 以下のHTMLが表示されていれば、コンテナの動作は予定通りとなります。

~~~
$ curl http://localhost:3000/
<!DOCTYPE html><html><head><title>Express</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>Express</h1><p>Welcome to Express</p></body></html>
~~~



## レジストリへの登録
k8sで利用できる様にする為に、ローカルリポジトリに保存されている コンテナのイメージを Bluemix の レジストリサービスへ転送します。
この中で、registry.ng.bluemix.net までがリポジトリとなり、その後の takara の部分が、ネームスペースと呼ばれる利用者が作成する部分になります。 このネームスペースを作成する為には、bx cr namespace-add を実行します。 詳しくは、bx cr --help を参照ください。

~~~
$ docker tag express-app registry.ng.bluemix.net/takara/express-app
~~~

ローカルから、Bluemixへプッシュします。

~~~
$ docker push registry.ng.bluemix.net/takara/express-app
~~~

登録したイメージが存在するか確認します。 もし無ければ手順やネームスペースが存在しているかを確認します。

~~~
$ bx cr images
~~~

これで k8s のクラスタ環境へアプリを登録する準備ができました。 次に、k8s へデプロイしていきます。


## k8sでの実行 （シングルでテスト）

まずは、コマンドラインだけで出来る方法で、k8sでコンテナを実行します。

~~~
$ kubectl run express-app --image=registry.ng.bluemix.net/takara/express-app
~~~

k8sのポッド、デプロイメント、サービス、レプリカセットなどの情報を確認します。 次の例では、AVAILABLE が 1となっており、動作している事が読み取れます。

~~~
$ kubectl get all
NAME                              READY     STATUS    RESTARTS   AGE
po/express-app-2177018659-tqhzf   1/1       Running   0          38s

NAME             CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
svc/kubernetes   10.10.10.1   <none>        443/TCP   1h

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/express-app   1         1         1            1           38s

NAME                        DESIRED   CURRENT   READY     AGE
rs/express-app-2177018659   1         1         1         38s
~~~



## コンテナを外部へテスト公開　（分散クラスタ）

これだけでは、外部へ公開されないので、--type NodePortで公開します。 この方法は、クラスタとして公開するのではなく、コンテナが実行されているワーカー（ノード）のポートで、公開するというものです。


~~~
$ kubectl expose deployment express-app --port 3000 --type NodePort
~~~

expose を実行した後に、再び状態を確認すると svc/express-app が増えており、EXTERNAL-IPが <nodes> となり、 PORT(S)が、3000:31980/TCP となっています。 これは、ワーカー(ノード）の Public IP で参照可能であり、 TCP3000番を31980で公開している事が読み取れます。

~~~
$ kubectl get all
NAME                              READY     STATUS    RESTARTS   AGE
po/express-app-2177018659-tqhzf   1/1       Running   0          2m

NAME              CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
svc/express-app   10.10.10.217   <nodes>       3000:31980/TCP   6s
svc/kubernetes    10.10.10.1     <none>        443/TCP          1h

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/express-app   1         1         1            1           2m

NAME                        DESIRED   CURRENT   READY     AGE
rs/express-app-2177018659   1         1         1         2m
~~~

そこで、ワーカーのPublic IPアドレスを次のコマンドで確認します。

~~~
vagrant@vagrant-ubuntu-trusty-64:~/k8s$ bx cs workers mycluster3
~~~


curl コマンドで、public IP　と ポート番号を指定して、HTMLが表示されると、シングルでの確認は完了です。 この例では、ワーカーノードが３つあり、その何れかで稼働しています。 応答がなければ、IPアドレスを変えて試してみましょう。

~~~
$ curl http://xxx.xxx.211.236:31980/
<!DOCTYPE html><html><head><title>Express</title><link rel="stylesheet" href="/stylesheets/style.css"></head><body><h1>Express</h1><p>Welcome to Express</p></body></html>
~~~



## デプロイしたコンテナやサービスの削除

コンテナ単体の動作確認が完了したので、削除しておきます。 削除には以下２つが、必要です。

~~~
$ kubectl delete deployment express-app
~~~

~~~
$ kubectl delete service express-app
~~~



## HTTPSとドメイン名でアクセスできる様にデプロイ

アプリを３つのワーカ（ノード）に展開して、暗号化可能なロードバランサーを実行するには、次の様にYAMLファイルを作成して設定を適用します。


~~~file:publish.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: express-app
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: express-app
    spec:
      containers:
      - name: express-container
        image: registry.ng.bluemix.net/takara/express-app
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: express-svc
spec:
  type: NodePort
  selector:
    app: express-app
  ports:
  - protocol: TCP
    port: 3000
    nodePort: 31514
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: express-ingress
spec:
  tls:
  - hosts:
    - mycluster3.us-south.containers.mybluemix.net
    secretName: mycluster3
  rules:
  - host: mycluster3.us-south.containers.mybluemix.net
    http:
      paths:
      - path: /
        backend:
          serviceName: express-svc
          servicePort: 3000
~~~

最初に構成した３ノードのk8sクラスタに対して、このYAMLファイルで構築する構成を図にすると、次の様な内容になります。  
![スクリーンショット 2017-09-11 15.46.11.png](https://qiita-image-store.s3.amazonaws.com/0/42829/2aab7262-3b7a-7704-14af-fa7e894db3d0.png "スクリーンショット 2017-09-11 15.46.11.png")


このYAMLファイルの説明は、[TLS 終端ありで IBM 提供ドメインを使用する](https://console.bluemix.net/docs/containers/cs_apps.html#ibm_domain_cert)を参照してください。

kubectl、コマンドで前述のYAMLファイルを適用します。

~~~
$ kubectl create -f publish.yml
deployment "express-app" created
service "express-svc" created
ingress "express-ingress" created
~~~


問題が無いか確認します。

~~~
$ kubectl get all
NAME                              READY     STATUS    RESTARTS   AGE
po/express-app-1882574706-pfhjv   1/1       Running   0          8s
po/express-app-1882574706-pqmf2   1/1       Running   0          8s
po/express-app-1882574706-xlgr4   1/1       Running   0          8s

NAME              CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
svc/express-svc   10.10.10.57   <nodes>       3000:31514/TCP   7s
svc/kubernetes    10.10.10.1    <none>        443/TCP          1h

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/express-app   3         3         3            3           8s

NAME                        DESIRED   CURRENT   READY     AGE
rs/express-app-1882574706   3         3         3         8s
~~~


ingressの動作を確認してみます。 ADDRESSが表示されないですが、Bluemixでは、これで正常な動作です。

~~~
$ kubectl get ing
NAME              HOSTS                                          ADDRESS   PORTS     AGE
express-ingress   mycluster3.us-south.containers.mybluemix.net             80, 443   1d
~~~


それでは、https://mycluster3.us-south.containers.mybluemix.net/ でアクセスした結果が、次のスクリーン・ショットになります。
HTTPSとドメイン名でアクセスできる事がわかります。


![スクリーンショット 2017-09-10 1.48.52.png](https://qiita-image-store.s3.amazonaws.com/0/42829/2a4503d4-b2d4-d407-e3e7-7e4cd547399f.png "スクリーンショット 2017-09-10 1.48.52.png")




## トラブル・シューティング

今年の４月初めにオープンしたk8sの環境ですが、9月に入って、安定的に利用できる様になった様に思います。 トラブルの中で、とても困ったケースについて、症状と対策を記載しておきます。


### Ingress のドメイン名でアクセスしても、タイムアウトになる

暗号化と負荷分散を受け持つ Ingress を設定しているのですが、

~~~
$ kubectl get ing
NAME              HOSTS                                                ADDRESS   PORTS     AGE
express-ingress   mycluster-241320.us-south.containers.mybluemix.net             80, 443   15s
~~~

curl コマンドで、アクセスしても、応答がありません。

~~~
$ curl https://mycluster-241320.us-south.containers.mybluemix.net/
curl: (7) Failed to connect to mycluster-241320.us-south.containers.mybluemix.net port 443: Operation timed out
~~~


k8sの中を調べても、異常が見つかりません。

~~~
$ kubectl get all
NAME                              READY     STATUS    RESTARTS   AGE
po/express-app-1065668149-14x76   1/1       Running   0          21s
po/express-app-1065668149-pzxjq   1/1       Running   0          21s
po/express-app-1065668149-sccx3   1/1       Running   0          21s

NAME              CLUSTER-IP     EXTERNAL-IP     PORT(S)          AGE
svc/express-lb    10.10.10.216   169.48.212.10   80:31520/TCP     20s
svc/express-svc   10.10.10.86    <nodes>         3000:31514/TCP   20s
svc/kubernetes    10.10.10.1     <none>          443/TCP          16d

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/express-app   3         3         3            3           21s

NAME                        DESIRED   CURRENT   READY     AGE
rs/express-app-1065668149   3         3         3         21s
~~~


Bluemix CLIで クラスタ状態を確認すると、Addonsの部分の Enabled が全て falseになっていました。
kubectl で操作できない為、本クラスタを破棄して、再作成した処、正常に動作する様になりました。

~~~
$ bx cs cluster-get mycluster
Retrieving cluster mycluster...
OK

Name:                   mycluster
ID:                     852830a347cc4280b8985489fb883cbf
State:                  normal
Created:                2017-08-24T06:46:39+0000
Datacenter:             dal12
Master URL:             https://169.47.70.10:25428
Ingress subdomain:      mycluster-241320.us-south.containers.mybluemix.net
Ingress secret:         mycluster-241320
Workers:                3
Log Space:              db39e8a1-5cb6-4fcc-83d2-3e8f0c8c259a

Addons
Name                   Enabled
storage-watcher-pod    false
basic-ingress          false
customer-storage-pod   false
~~~





# 参考資料 
(1) GitHub bluemix-dev https://github.com/takara9/bluemix-dev
(2) Ingress を使用してインターネットにアプリを公開する  https://console.bluemix.net/docs/containers/cs_planning.html#cs_ingress
(3) IBM Bluemix Container Service での Kubernetes クラスターとデプロイメントのセキュリティー設定 https://console.bluemix.net/docs/containers/cs_security.html#cs_security_network_policies
(4) TLS 終端ありで IBM 提供ドメインを使用する https://console.bluemix.net/docs/containers/cs_apps.html#cs_apps_public_ingress
(5) Kubernetes and IBM Bluemix: How to deploy, manage, and secure your container-based workloads – Part 3 https://www.ibm.com/blogs/bluemix/2017/06/kubernetes-and-bluemix-container-based-workloads-part3/