ニフクラでもできる！Kubernetes クラスタ

この記事は [富士通クラウドテクノロジーズ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/fjct) の15日目の記事です。

昨日は [mini_act4](https://qiita.com/mimi_act4) さんの [認定スクラムプロダクトオーナー研修を受けて気づいた大切なこと](http://tech.fjct.fujitsu.com/entry/2017/12/14/000000) でした。
[mini_act4](https://qiita.com/mimi_act4) さんがオーナーをするプロダクトやチームに期待が高まります！

# TL;DR

* ニフクラでも [Kubernetes](https://kubernetes.io/) を使うことができます。（あたりまえ
* [ansible-role-nifcloud](https://github.com/nifcloud/ansible-role-nifcloud) と [kubespray-cli](https://github.com/kubespray/kubespray-cli) を組み合わせて、クラスタのデプロイも簡単にできます。

# はじめに

[Serverlessconf Tokyo](http://tokyo.serverlessconf.io/) や [Kubernetes Meetpup Tokyo #8](https://k8sjp.connpass.com/event/70143/) に参加して、物理サーバーやVMといったインフラを抽象化し、設定ファイルやプログラムといったコードによってシステムを管理・運用するのすげー！と感じて [Kubernetes](https://kubernetes.io/) について色々調べる中で、何はともあれクラスタ作らないとな、と言うことで仕事で触る機会の多いニフクラで簡単に構築できるように試してみました。

[Kubernetes](https://kubernetes.io/) についての概要は MahoTakara さんの [今さら人に聞けない Kubernetes とは？](https://qiita.com/MahoTakara/items/85096f8b2632c802ab22) が分かりやすかったかなと思いました。

[Kubernetes](https://kubernetes.io/) クラスタのデプロイについては albatross さんの [kubespray (kargo-cli) で20分で Kubernetes クラスタを作る](https://qiita.com/albatross/items/7f472a973d0c04041a4d) を参考にさせていただきました。ありがとうございます。
[kubespray](https://github.com/kubernetes-incubator/kubespray) を使うとサーバーさえ用意すればサクッとクラスタを構築できちゃうので、すごい世の中です。

# やること

* サーバーを準備する
    * [ansible-role-nifcloud](https://github.com/nifcloud/ansible-role-nifcloud) を使ってサーバーを構築します。
* クラスタをデプロイする
    * [kubespray-cli](https://github.com/kubespray/kubespray-cli) を使ってクラスタをデプロイします。
    * 私はとりあえず試してみたいというモチベーションなので、運用性とかセキュリティーは一旦無視しちゃいます。
なので諸々のクラスタ設定はデフォルト.....

# 構築するクラスタの情報

![クラスタイメージ](https://camo.githubusercontent.com/894d8ca89be8ffba8239cb80df2685ad36d79fe4/68747470733a2f2f7333322e706f7374696d672e6f72672f387137676e733875742f336e6f6465732e706e67)
※ 引用：https://github.com/kubespray/kubespray-cli

* node:3台
    * OSイメージ：CentOS 7.1
    * インスタンスタイプ：Small2
        * [ニフクラ料金](https://cloud.nifty.com/price/#type-h)
    * ネットワーク：共通グローバルは無し/適当に作った[プライベートLAN](https://cloud.nifty.com/service/plan.htm)
        * 共通グローバルにルーティング（インターネットに出れない環境では試してないです）
            * [ルーター](https://cloud.nifty.com/service/router.htm)とか
    * 内訳
        * master:2台
        * etcd:3台

# ansible-role-nifcloud でサーバー作成

* ansible-playbook のディレクトリ構成は下記のような感じです。

    ```shell:tree
    ├── group_vars
    │   └── all.yml
    ├── roles
    │   └── nifcloud_vm
    │       ├── tasks
    │       │   └── main.yml
    │       └── templates
    │           └── startup.sh
    └── site.yml

    ```

## ansible-playbook のファイルたち

* site.yml
  ansible-playbook コマンドで直接実行するymlファイルです。
  nifcloud.nifcloud という名前の ansible-role-nifcloud を読みこんだり、
  自作の nifcloud_vm ロールを読みこんで実行したりします。
  ちなみに、nifcloud.nifcloud をモジュールとして使うために、site.yml で読み込ませないと動かないのですが、 
  その情報に辿りつけずに数日ハマりました... 

    ```yaml:site.yml
    - name: provision vms
      hosts: all
      connection: local                         # nifcloud.nifcloud はAPIを使うだけなので connection は local です
      roles:
        - nifcloud.nifcloud                     # デプロイ用ロールの前に、nifcloud.nifcloud を読ませるのが必要です。
        - nifcloud_vm                           # 後述
    ```
* group_vars/all.yml
  全ホスト共通の設定です。

    ```yaml:all.yml
    computing:
      region: <YOUR_CLUSTER_REGION>              # クラスタをデプロイするリージョン:jp-east-1 とか
      availability_zone: <YOUR_CLUSTER_ZONE>     # クラスタをデプロイするゾーン:jp-east-14 とか
      instance_type: <INSTANCE_TYPE_NAME>        # サーバータイプ:kubespray-cli は 2GB以上のメモリが必須なので small2 とか
      image_id: <YOUR_IMAGE_IE>                  # イメージID:コントロールパネルのOSイメージメニューを確認 CentOS 7.1 なら 68
      key_name: <YOUR_SSH_KEY_NAME>              # 登録したSSHキーの名前
      security_group: <YOUR_SECURITY_GROUP_NAME> # 適用するファイアウォールの名前
      root_password: <YOUR_ROOT_PASSWORD>        # インスタンスに設定するルートパスワード
    credentials:
      access_key: <YOUR_ACCESS_KEY>              # アクセスキー
      secret_key: <YOUR_SECRET_ACCESS_LEY>       # シークレットキー
    private_network:
      id: <YOUR_PLAN_ID>                         # クラスタをデプロイするネットワークのID:net-XXXXXXXXX みたいなやつ
      address: <YOUR_PLAN_ADDRESS>               # プライベートLANのネットワークアドレス:10.0.0.0/24 とか
      gateway: <YOUR_PLAN_GATEWAY>               # ゲートウェイ: 10.0.0.1 とか
    ```
* roles/nifcloud_vm
サーバーをプロビジョニングするansibleロールです。

    * roles/nifcloud_vm/tasks/main.yml
    [GitHubのサンプル](https://github.com/nifcloud/ansible-role-nifcloud/blob/master/library/documents/nifcloud.md)を参考に作成しました。  

        ```yaml+jinja:main.yml
        - name: Install (Requests) python package
          local_action:
            module: pip
            name: requests
        - name: Start server
          local_action:
            module: nifcloud
            access_key: "{{ credentials.access_key }}"
            secret_access_key: "{{ credentials.secret_key }}"
            endpoint: "computing.{{ computing.region }}.api.cloud.nifty.com"
            instance_id: "{{ inventory_hostname }}"
            state: "running"
            image_id: "{{ computing.image_id }}"
            key_name: "{{ computing.key_name }}"
            instance_type: "{{ computing.instance_type }}"
            availability_zone: "{{ computing.availability_zone }}"
            security_group: "{{ computing.security_group}}"
            accounting_type: "2"
            ip_type: "none"
            startup_script: "roles/nifcloud_vm/templates/startup.sh"
            startup_script_vars:           # 後述のサーバー起動時スクリプトに渡す値
              root_password: "{{ computing.root_password }}"
              ip_addr: "{{ ansible_ssh_host }}/24"
              network_addr: "{{ private_network.address }}"
              gateway: "{{ private_network.gateway }}"
            network_interface:
              - network_id: "{{ private_network.id }}"
                ipAddress: "static"
        ```
    * roles/nifcloud_vm/templates/startup.sh
        * ニフクラの [サーバー起動時スクリプト](https://cloud.nifty.com/service/svscript.htm) の機能を使って諸々設定します
            * インスタンスIDをホスト名に設定
            * root のパスワードを設定（やらなくても良い）
            * プライベートLANのインターフェースに任意のアドレスを設定
        * ysaotome さんの [Gist](https://gist.github.com/ysaotome/378205689c7b6ef0a6bf#file-centos71_64bit_plain_setup_script_for_niftycloud-sh) を参考に作成しました

        ```bash:starup.sh
        #!/bin/sh
        
        (
        #===============================================
        # Settings
        #===============================================
        ##rootのパスワード
        ROOT_PASSWORD='{root_password}'
        #===============================================
        ARC=$(/bin/uname -m)
        SALT=$(/usr/bin/uuidgen| /usr/bin/tr -d '-')
        
        ## hostname変更
        HOSTNAME=$(/usr/bin/vmtoolsd --cmd 'info-get guestinfo.hostname')
        /bin/hostname ${{HOSTNAME}}
        /bin/sed -i.org -e 's/localhost.localdomain/'${{HOSTNAME}}'/' /etc/hostname
        
        ## ROOTパスワード設定
        /usr/sbin/usermod -p $(/usr/bin/perl -e 'print crypt(${{ARGV[0]}}, ${{ARGV[1]}})' ${{ROOT_PASSWORD}} ${{SALT}}) root
        
        ## swap領域 無効化
        /usr/sbin/swapoff `awk '{{print $1}}' /proc/swaps | grep -v 'Filename'`
        /usr/bin/sed -i -e "/swap/s/^/#/g" /etc/fstab
        
        ## network 設定
        IPADDR={ip_addr}               # yaml から渡された値
        CONNECTION="System ens160"     # 注意！：共通グローバルが有効な場合はプライベートLANのコネクションは ens192 になります。
        DISABLE_CONNECTION="System ens192"
        NETWORK_ADDR={network_addr}    # yaml から渡された値
        GATEWAY={gateway}              # yaml から渡された値
        /usr/bin/nmcli connection modify "${{DISABLE_CONNECTION}}" connection.autoconnect no
        /usr/bin/nmcli connection modify "${{CONNECTION}}" ipv4.addresses ${{IPADDR}}
        /usr/bin/nmcli connection modify "${{CONNECTION}}" ipv4.gateway ${{GATEWAY}}
        /usr/bin/nmcli connection modify "${{CONNECTION}}" ipv4.dns ${{GATEWAY}}
        /usr/bin/nmcli connection modify "${{CONNECTION}}" ipv4.method manual
        /usr/bin/nmcli connection down "${{CONNECTION}}"
        /usr/bin/nmcli connection up "${{CONNECTION}}"
        /bin/systemctl restart NetworkManager.service
        /bin/systemctl restart network.service
        ) 2>&1 | /usr/bin/tee /var/log/niftycloud-init.log

        ```

* インベントリファイルは？
    * kubespray-cli prepare コマンドを使うと、 `.kubespray/inventory/inventory.cfg` にansible のインベントリファイルが作成されるため、コレを使います。
        * 例えばこうすると、
     
            ```shell-session
            $ kubespray prepare --nodes \
              node1[ansible_ssh_host=10.0.0.101] \
              node2[ansible_ssh_host=10.0.0.102] \
              node3[ansible_ssh_host=10.0.0.103] 
            ```
          こうなります。
    
            ```ini:.kubespray/inventory/inventory.cfg
            [kube-master]
            node1
            node2
            
            [all]
            node1           ansible_ssh_host=10.0.0.101
            node2           ansible_ssh_host=10.0.0.102
            node3           ansible_ssh_host=10.0.0.103
        
            [k8s-cluster:children]
            kube-node
            kube-master
            
            [kube-node]
            node1
            node2
            node3
            
            [etcd]
            node1
            node2
            node3
            ```

## サーバー作成
インベントリファイルに↑のやつを指定すれば良いので、例えば↓のように実行します。

```shell-session
$ ansible-playbook -i .kubespray/inventory/inventory.cfg site.yml
```

* 実行結果サンプル  

    ```shell-session
    bash-4.2# time ansible-playbook -i .kubespray/inventory/inventory.cfg site.yml
    --diff
    
    PLAY [provision vms]
    ***********************************************************************************************************
    
    TASK [Gathering Facts]
    *********************************************************************************************************
    ok: [node3]
    ok: [node1]
    ok: [node2]
    
    TASK [nifcloud.nifcloud : Gathering facts from localhost]
    **********************************************************************
    ok: [node1 -> localhost]
    
    TASK [nifcloud.nifcloud : Import EPEL GPG Key (for CentOS)]
    ********************************************************************
    ok: [node1 -> localhost]
    
    TASK [nifcloud.nifcloud : Check EPEL repository (for CentOS)]
    ******************************************************************
    ok: [node1 -> localhost]
    
    TASK [nifcloud.nifcloud : Add EPEL repository (for CentOS)]
    ********************************************************************
    skipping: [node1]
    
    TASK [nifcloud.nifcloud : Install python-pip (for CentOS)]
    *********************************************************************
    ok: [node1 -> localhost]
    
    TASK [nifcloud.nifcloud : Install python-pip (for Ubuntu)]
    *********************************************************************
    skipping: [node1]
    
    TASK [nifcloud.nifcloud : Install "Requests" python package]
    *******************************************************************
    ok: [node1 -> localhost]
    
    TASK [nifcloud_vm : Install (Requests) python package]
    *************************************************************************
    ok: [node1 -> localhost]
    ok: [node2 -> localhost]
    ok: [node3 -> localhost]
    
    TASK [nifcloud_vm : Start server]
    **********************************************************************************************
    changed: [node2 -> localhost]
    changed: [node1 -> localhost]
    changed: [node3 -> localhost]
    
    PLAY RECAP
    *********************************************************************************************************************
    node1                      : ok=8    changed=1    unreachable=0    failed=0   
    node2                      : ok=3    changed=1    unreachable=0    failed=0   
    node3                      : ok=3    changed=1    unreachable=0    failed=0   
    
    
    real    2m22.832s
    user    0m21.668s
    sys 0m6.400s
    bash-4.2# 
    ```
    という感じでサーバーが作成されます。(node1, node2, node3)  
    ![Screenshot from 2017-12-15 00-35-46.v01.png](https://qiita-image-store.s3.amazonaws.com/0/49886/18a93be0-9404-d293-4dce-8497925ea7ca.png)


# [kubespray-cli](https://github.com/kubespray/kubespray-cli) で [Kubernetes](https://kubernetes.io/) のデプロイ

サーバーができたら [kubespray-cli](https://github.com/kubespray/kubespray-cli) を実行するだけです。
サーバーにSSH接続ができるホストで下記を実行します。（ `time` で時間をはかってみています。）
`-u` オプションでユーザー、 `-k` オプションでSSHキーを指定しています。
`-n` オプションではネットワークプラグインを指定できますが、今回は [flannel](https://github.com/coreos/flannel) を使ってみました。他にも weave,calico,canal,contiv,cloud が指定できるみたいです。何も指定しないと [calico](https://github.com/projectcalico/calico) になるそうです。

```shell-session
bash-4.2# time kubespray deploy -u root -k .ssh/kubernetes_rsa -n flannel
Identity added: /root/.ssh/kubernetes_rsa (/root/.ssh/kubernetes_rsa)

CHECKING SSH CONNECTIONS *******************************************************************************************************
/usr/bin/ansible --ssh-extra-args -o StrictHostKeyChecking=no -u root -b --become-user=root -m ping all -i /root/.kubespray/inventory/inventory.cfg
node2 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
node3 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
node1 | SUCCESS => {
    "changed": false, 
    "ping": "pong"
}
All hosts are reachable
/usr/bin/ansible-playbook --ssh-extra-args -o StrictHostKeyChecking=no -u root -b --become-user=root -i /root/.kubespray/inventory/inventory.cfg /root/.kubespray/cluster.yml -e kube_network_plugin=flannel
Run kubernetes cluster deployment with the above command ? [Y/n] 
...途中割愛...
PLAY RECAP *********************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0   
node1                      : ok=359  changed=111  unreachable=0    failed=0   
node2                      : ok=316  changed=105  unreachable=0    failed=0   
node3                      : ok=277  changed=85   unreachable=0    failed=0   

Kubernetes deployed successfuly

real	11m15.910s
user	6m40.516s
sys	2m1.336s
bash-4.2# 
```
という感じでクラスタのデプロイが完了します。

# 動作確認

[kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) を使ってクラスタの状態を確認しますが、
その前に [Kubernetes](https://kubernetes.io/) のAPIを利用するための鍵と設定を取得（デプロイしたノードから拝借）します。

```shell-session
bash-4.2# scp -r -i .ssh/kubernetes_rsa root@10.0.0.101:~/.kube .
```

クラスタの状態を確認すると...

```shell-session
bash-4.2# kubectl cluster-info
Kubernetes master is running at https://10.0.0.101:6443
KubeDNS is running at https://10.0.0.101:6443/api/v1/namespaces/kube-system/services/kube-dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
bash-4.2# 
```

どうやら動いているみたいなので、nginx のポッドをデプロイしてみます。

```shell-session
bash-4.2# kubectl run nginx --image=nginx:latest
deployment "nginx" created
bash-4.2# kubectl get pods -o wide
NAME                     READY     STATUS    RESTARTS   AGE       IP            NODE
nginx-5db977d67c-zctf9   1/1       Running   0          43s       10.233.66.4   node3
bash-4.2# 
```

node3 にデプロイされました。  
ポッドにIPが振ってありますが、クラスタ内に閉じたIPなので、ノードのポートにエクスポーズして外からアクセスしてみます。

```shell-session
bash-4.2# kubectl expose deployment nginx --port 80 --type=NodePort
service "nginx" exposed
bash-4.2# kubectl get svc -o wide
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE       SELECTOR
kubernetes   ClusterIP   10.233.0.1      <none>        443/TCP        19m       <none>
nginx        NodePort    10.233.21.221   <none>        80:32251/TCP   3s        run=nginx
bash-4.2# 
```

32551 ポートにマッピングされたのでブラウザでアクセスしてみます。

![Screenshot from 2017-12-15 02-14-28.png](https://qiita-image-store.s3.amazonaws.com/0/49886/47edd16a-1ec8-e1d3-daf1-1dd9b13d6ac0.png)

どのノードにアクセスしても同じ画面が得られるので、ちゃんと node3 にルーティングされているようです。


# まとめ

ニフクラでも [Kubernetes](https://kubernetes.io/) クラスタを作ることが出来ました。
[ansible-role-nifcloud](https://github.com/nifcloud/ansible-role-nifcloud) と [kubespray](https://github.com/kubernetes-incubator/kubespray) を使うことで、かなり簡単にクラスタをデプロイすることが出来ます。
簡単な構成で実験しているので、本番運用に向けた凝った構成にするにはもう少し工夫が必要そうです。
GitHubに[上記の諸々を公開](https://github.com/conhumi/k8s-on-nifcloud) しています。実行環境のコンテナを作る諸々も一緒に置いてありますのでご参考いただければ幸いです。

[Kubernetes](https://kubernetes.io/) の勉強は始まったばかりなので、クラスタを壊したり直したりしながら理解を深めて行きたいと思います。

明日は [clutter](https://qiita.com/clutter) さんです！お楽しみに！

# 参考サイト

* https://qiita.com/albatross/items/7f472a973d0c04041a4d
* https://github.com/kubespray/kubespray-cli
* https://github.com/nifcloud/ansible-role-nifcloud/blob/master/library/documents/nifcloud.md
* https://gist.github.com/ysaotome/378205689c7b6ef0a6bf
* https://kubernetes-v1-4.github.io/docs/user-guide/kubectl/kubectl_expose/
* https://qiita.com/MahoTakara/items/85096f8b2632c802ab22