安いクラウド環境で RancherOS / Kubernetes を使って勉強用クラスタを作る

# はじめに

RancherOS, Kubernetes を一から構築して勉強用のクラスタを作ってみようと思い立ちました。

今回、安いクラウド環境に RancherOS と Ubuntu をインストールし、その上で Rancher をインストールして、勉強用の Kubernetes クラスタを作ってみます。

## 構成

※ etcd, Control Plane の冗長性はありません。あくまで勉強用のクラウドです。

- サーバ1
    - クラウドサービス: Amazon EC2 t2.micro (vCPU 1, Mem 1GB, SSD 30GB)
    - 役割: クラスタマスターとコントロールパネルUI
    - OS: RancherOS v1.1.0
    - サービス: Rancher, Rancher Agent v2.1.1 (etcd, Control Plane)
    - 値段: 無料(1年間の無料利用枠内)
- サーバ2
    - クラウドサービス: Google Compute Engine micro (共有 vCPU 1, Mem 0.6GB, SSD 30GB)
    - 役割: Docker コンテナ実行ノード
    - OS: Ubuntu 16.04 LTS
        - ※ Container-Optimized OS は swap を作成する方法が見当たらず断念
        - ※ CoreOS で swap を作成できるか不明ですが使いたい Docker CE 17.03.2 が標準ではなく Docker CE 18 だったので優位性を感じず未使用
    - サービス: Rancher Agent v2.1.1 (worker)
    - 値段: $5.10/月
        - micro インスタンスは Always Free
        - SSD は $5.10/月
            - メモリ不足をスワップで補うため、全体のIOを上げたく SSD を購入しました

※ Rancher Agent 2.1.1 は Kubernetes v1.11.3 が対応する最新安定版で、Kubernetest 1.11.3 は Docker CE 17.03.X が対応する最新安定板です。([参考](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.11.md#external-dependencies))

# Rancher にまつわる言葉遣い

`RancherOS` と `Rancher` は別ものです。
本記事内では次の認識で記載しています。

- `RancherOS` : Rancher Lab 製の OS。OS のシステム管理プロセス(udev, syslog) が Docker Container で動作する。
- `Rancher` : コンテナオーケストレーション環境を構築・ステータス監視・構成変更等の管理を行うツール
    - Rancher は `Rancher Server` と `Rancher Agent` の 2 種類で構成される
- `Rancher Server` : Rancher を操作する UI の提供と RancherAgent との連携を行うコンテナ
- `Rancher Agent` : Rancher Server と連携してクラスタノードを構築するコンテナ

# RancherOS とは

RancherOS とは OS のシステム管理を行う udev や syslog 等のプロセスが Docker コンテナとなっている OS です。RancherOS に搭載されているのは Docker を実行するために必要な機能のみであるため、他の OS に比べて非常に小さいサイズ(v1.4.2 の rancheros.iso が 86MB)となっています。

RancherOS をインストールする方法は、クラウドサービスでインスタンスを作成する場合は OS イメージがあればそれを選択することになり、オンプレミス環境でインストールする場合やクラウドサービスが ISO から OS をインストールできる場合は、ISO イメージからインストールすることになります。

2018/11/18 現在、次のクラウドサービスで RancherOS をインストールする方法が公式ドキュメントに記載されています。

- Amazon EC2
- Google Compute Engine
- DigitalOcean
- Azure

RancherOS には 2 種類の Docker デーモンが動作しています。1 つは ntpd、syslog、console 等のシステムプロセスを担うコンテナを管理する `system-docker` デーモンです。従来の OS における systemd の役割を置き換えるものです。
もう 1 つはその他のユーザにより起動するコンテナを動作させる `docker` デーモンです。

```sh:system-dockerのコンテナ一覧(RancherOS-HVM-v1-1-0)
$ sudo ros os version
v1.1.0
$ sudo system-docker ps -a
CONTAINER ID        IMAGE                              COMMAND                  CREATED             STATUS                         PORTS               NAMES
d565294231d6        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Exited (0) About an hour ago                       preload-user-images
fb5e2f3c0137        rancher/os-docker:17.03.2          "ros user-docker"        About an hour ago   Up About an hour                                   docker
291c631040fb        rancher/os-console:v1.1.0          "/usr/bin/ros entrypo"   About an hour ago   Up About an hour                                   console
94f22723cd55        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Exited (0) About an hour ago                       cloud-init-execute
3b82a5224259        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Up About an hour                                   ntp
10657b207cbb        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Up About an hour                                   network
4a4fed142136        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Up About an hour                                   udev
d4bc85de8126        rancher/container-crontab:v0.1.0   "container-crontab"      About an hour ago   Up About an hour                                   system-cron
26ab39422144        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Created                                            all-volumes
07c1b8f2487b        rancher/os-logrotate:v1.1.0        "/usr/bin/entrypoint."   About an hour ago   Exited (0) 48 minutes ago                          logrotate
435cada5ddc6        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Exited (0) About an hour ago                       udev-cold
3356393417af        rancher/os-acpid:v1.1.0            "/usr/bin/ros entrypo"   About an hour ago   Up About an hour                                   acpid
1ffc1c23c7fd        rancher/os-syslog:v1.1.0           "/usr/bin/entrypoint."   About an hour ago   Up About an hour                                   syslog
dd187cd1b980        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Created                                            system-volumes
8cb4c59b0f8d        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Created                                            user-volumes
2b8f39367fc4        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Created                                            container-data-volumes
5c598d68e4c3        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Created                                            media-volumes
8cac3f0573af        rancher/os-base:v1.1.0             "/usr/bin/ros entrypo"   About an hour ago   Created
```

# RancherOS のインストール

Amazon EC2 の無料枠であるインスタンスタイプ t2.micro を使う前提です。

1. AWS コンソールから EC2 画面を開く
2. インスタンスの作成ボタンを押す
3. AMI 選択画面で「AWS Marketplace」から "rancher" で検索し「RancherOS - HVM」を選択する (2018/11/18 現在 v1.1.0)
4. 料金に関する情報を確認して問題なければ Continue を押す
5. インスタンスタイプを t2.micro を選択し、以後各ページの「次の手順: XXX」を選択して次のとおり詳細設定が問題ないか確認しつつ設定を行う
    - インスタンスの詳細設定 (default のまま)
    - ストレージの追加 (汎用SSD(gp2) をサイズ 30GB にする)
    - タグの追加 (適宜)
    - セキュリティグループの設定(次のポートを許可する)
        - TCP 22 (SSH)
        - TCP 8080, 8443 (Rancher UI)
        - TCP 2376,2379,2380,9099,10250, UDP 8472 (etcd Node)
        - TCP 80,443,2376,6443,9099,10250,10254,30000-32767, UDP 8472,30000-32767 (ControlPlane Node)

以上でインスタンスの作成が完了し、RancherOS がインストールされた状態で起動します。
尚、RancherOS の default user は `rancher` です。SSH 接続する際は、ユーザ名 `rancher` で、インスタンス作成時に設定した秘密鍵とパスワードを使ってログインしましょう。

次に RancherOS の設定を進めていきます。

## RancherOS の設定

RancherOS を設定する方法は 2 種類あります。

* cloud-config ファイルにより RancherOS 初回起動時に設定を行う
* `ros config` コマンドにより手動で設定変更する
    * 初回起動時以後に設定変更する場合に推奨される方法
    * 設定変更した後は再起動が必要
    * `ros config set <key> <value>`

オンプレミス環境で RancherOS をインストールする場合は cloud-config ファイルを設定する方法が紹介されておりますが、Amazon ECS でインスタンスを作成した場合は UI から設定できるため ros config を使うことにします。

```bash:TimeZoneの設定
$ sudo ros config set rancher.services.console.environment.TZ JST-9
```

今回、スワップを作成し iptables コマンドで必要なポートを上げられるよう設定します。
また、Rancher Node 内通信を許可する FireWall 設定を行います。

```bash:RancherOS起動時にswapを追加してFirewallを設定する
$ sudo ros config merge
runcmd:
- sudo dd if=/dev/zero of=/swapfile bs=64M count=64
- sudo chmod 600 /swapfile
- sudo mkswap /swapfile
- sudo swapon /swapfile
- iptables -N rancher-node-inbound
- iptables -A rancher-node-inbound -m multiport -p tcp --dport 80,443 -j ACCEPT
- iptables -N etcd-node-inbound
- iptables -A etcd-node-inbound -m multiport -p tcp --dport 2376,2379,2380,9099,10250 -j ACCEPT
- iptables -A etcd-node-inbound -p udp --dport 8472 -j ACCEPT
- iptables -N controlplane-node-inbound
- iptables -A controlplane-node-inbound -m multiport -p tcp --dport 80,443,2376,6443,9099,10250,10254,30000:32767 -j ACCEPT
- iptables -A controlplane-node-inbound -m multiport -p udp --dport 8472,30000:32767 -j ACCEPT
- iptables -N worker-node-inbound
- iptables -A worker-node-inbound -m multiport -p tcp --dport 80,443,2376,9099,10250,10254,30000:32767 -j ACCEPT
- iptables -A worker-node-inbound -m multiport -p udp --dport 8472,30000:32767 -j ACCEPT
- iptables -N worker-internal
- iptables -A worker-internal -m multiport --source 10.0.0.0/8 -j ACCEPT
- iptables -A worker-internal -m multiport --source 172.16.0.0/12 -j ACCEPT
- iptables -A worker-internal -m multiport --source 192.168.0.0/16 -j ACCEPT
- iptables -A worker-internal -m multiport --source <Worker Node IP> -j ACCEPT
- iptables -A worker-internal -m multiport --source <Worker Node IP> -j ACCEPT
- iptables -I INPUT 1 -j rancher-node-inbound
- iptables -I INPUT 1 -j etcd-node-inbound
- iptables -I INPUT 1 -j controlplane-node-inbound
- iptables -I INPUT 1 -j worker-node-inbound
- iptables -I INPUT 1 -j worker-internal
```

これで RancherOS の設定は完了です。一応再起動して設定が反映されることを確認するとよいと思います。(スワップファイルを作成するため起動に時間がかかります)

## Rancher をインストールする

次に Rancher をインストールします。
Rancher はオーケストレーション環境を管理構築するツールです。

Rancher v1.6 までは Cattle, Kubernetes, Docker Swarm に対応していましたが v2.0 以降では Kubernetes を前提としており、その特徴が変わっていますので目的に応じて適宜選択してください。

Docker 及び Kubernetes によるコンテナオーケストレーション環境は、デファクトスタンダードと言える環境ですので、今回は Rancher v2.0 以降をインストールすることにします。

https://rancher.com/docs/rancher/v2.x/en/quick-start-guide/deployment/quickstart-manual-setup/#2-install-rancher

```bash:Rancherをインストールする(RancherUIのポート番号は8080,8443を使うようカスタマイズ)
$ sudo docker run -d --name rancher --restart=unless-stopped \
-p 8080:80 -p 8443:443 rancher/rancher
```
※ ポート番号 8080, 8443 を使用するのは Rancher と Rancher Agent を1つのノードで同居させる際にポート番号がかぶらないようにするためです。([参考](https://rancher.com/docs/rancher/v2.x/en/installation/single-node/#running-rancher-rancher-and-rancher-rancher-agent-on-the-same-node))

以上で、Rancher のインストール完了です。簡単ですね。
docker logs rancher 等でログを確認し、特に問題が無ければ起動完了です。

(etcd はレスポンスが低い環境では動作せず `etcdserver: avoid queries with large range/delete range!` を含むメッセージが出力されることがあります [参考](https://github.com/kubernetes/kubernetes/issues/43363))

```
W | etcdserver: apply entries took too long [5.722403199s for 1 entries]
W | etcdserver: avoid queries with large range/delete range!
```

http://ec2-xxx.amazonaws.com:8443/ にアクセスすると Rancher UI が表示されます。
表示されたら admin アカウントを作成し、サーバURLを設定(変更不要)してください。

|アカウント作成画面|サーバURL設定画面|
| --- | --- |
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/5a5bdc4b-4b18-3a00-77a6-290f6ec6aea3.png)|![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/e0e48c87-5ea6-f39d-e0cd-afdedbc07f25.png)|

## Rancher Agent をインストールする(サーバ1側)

次に Rancher Agent をインストールします。
Rancher Agent はクラスタノードを登録するエージェントです。
Rancher で管理する全ノードにインストールする必要があります。

Rancher Agent をインストールする方法は、対象のノードで適切なオプションを指定して rancher/rancher-agent を `docker run` で起動するだけです。
そして適切なオプションは Rancher UI のクラスタ設定画面に表示されるため、それをコピーしてペーストする流れとなります。

今回、Rancher と同じホストに Rancher Agent もインストールすることにします。

1. Rancher UI を開き「Add Cluster」ボタンを押す
2. Add Cluster 画面で次の値を設定する
    - 「CUSTOM」を選択する
    - Cluster Name: (適宜)
    - Member Roles: (適宜) ※本記事ではDefaultのままにします
    - Cluster Options: (適宜) ※本記事ではDefaultのままにします
3. Add Cluster: ranchertest(※自身で付けた名前) 画面で次の設定をする
    - Node Options: etcd, Control Plane
    - `Run this command on one or more existing machines already running a supported version of Docker.` の下に表示されているコマンドをコピーする
    - AWS EC2 の<font color="red">外部IPアドレス</font>が適切に登録されるよう `--address awspublic` を追加する ([参考](https://rancher.com/docs/rancher/v2.x/en/admin-settings/agent-options/#dynamic-ip-address-options))
4. サーバ1でペーストする

本記事で設定した際、次のコマンドとなりました。(アドレス、トークンは伏せてます)

```bash:RancherAgentのインストール(etc,ControlPlaneをインストールする)
$ sudo docker run -d --privileged --restart=unless-stopped --net=host \
-v /etc/kubernetes:/etc/kubernetes \
-v /var/run:/var/run rancher/rancher-agent:v2.1.1 \
--server https://ec2-xxx.amazonaws.com:8443 \
--token hogehoge \
--ca-checksum hogehoge \
--etcd --controlplane \
--address awspublic --internal-address awspublic
```

後は Rancher と RancherAgent が適宜ノードが追加されたことを発見してクラスタの構築を行いますのでしばらく待ちましょう。
インストールが完了したら Rancher UI 上でノードが追加され、State が `Active` になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/6b1685f1-a1c0-177b-6299-bb10c6d388d8.png)


## Rancher Agent をインストールする(サーバ2側)

続いてサーバ2側にも Rancher Agent をインストールしていきます。

次の条件でインスタンスを作成し、VPC でファイアウォールの設定を行います。

- リージョン: us-west1 (オレゴン)
- ゾーン: us-west1-b
- マシンタイプ: micro (共有vCPU x1)
- ブートディスク: Ubuntu 16.04 LTS
- ブートディスクの種類: 標準の永続ディスク 30GB
- セキュリティ: SSH 認証鍵を登録しておく

インスタンスが作成されたら SSH ログインしてサーバ 1 で Rancher Agent をインストールした時と同様に Rancher UI を操作(Edit Cluster)して表示される Rancher Agent のインストールコマンドを実行します。

ここで 2 つ注意点があります。

1. サーバ2側ではコンテナを実行する役割を持たせるため `Node Options` で `Worker` を指定する
2. GCE の<font color="red">外部IPアドレス</font>が適切に登録されるよう `--address gceexternal` を追加する ([参考](https://rancher.com/docs/rancher/v2.x/en/admin-settings/agent-options/#dynamic-ip-address-options))

本記事で設定した際、次のコマンドとなりました。(アドレス、トークンは伏せてます)

```bash:WorkerとしてRancherAgentを登録するコマンド例
$ sudo docker run -d --privileged --restart=unless-stopped --net=host \
-v /etc/kubernetes:/etc/kubernetes \
-v /var/run:/var/run rancher/rancher-agent:v2.1.1 \
--server https://ec2-xxx.amazonaws.com:8443 \
--token hogehoge \
--ca-checksum hogehoge \
--worker \
--address gceexternal --internal-address gceexternal
```

Rancher UI のノード一覧画面に worker が追加されたらインストール完了です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/fd44c678-ed56-2a94-d1f1-a5b062ef28ad.png)


# アプリケーションをデプロイする

試しに Jenkins をデプロイしてみましょう。
但し、ボリュームを作成していないのでコンテナがダウンしたらデータが消えますので、ちゃんと使いたい場合は Persistent Volume を作りましょう。

1. 作成したクラスタ(例では `ranchertest`)の Default を選択する
2. Workloads 画面から Deploy ボタンを押す
3. 次のように Deployment を設定する  
![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/05718be8-bbd9-5ab8-7d73-7f1c50604161.png)
4. Launch ボタンを押す

Deployment が Running になったら終わりです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/30aa0419-e72f-b0ae-b8c5-29efe3f40560.png)

デプロイされた IP とポートにアクセスしたら Jenkins のインストール画面が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/0b6495d4-2a42-382d-9e66-f6fad4b65e8b.png)

# クラスタに kubectl コマンドでアクセスする

## kubectl コマンドを導入する

先の Jenkins インストール画面を見て気が付いたと思いますが、コンテナのファイルシステムへアクセスする方法が現状では用意されていません。

Kubernetes では CLI で操作するための kubectl コマンドが用意されています。
ここでは kubectl コマンドを使ってコンテナへアクセスする方法を紹介します。
[参考](https://rancher.com/docs/rancher/v2.x/en/k8s-in-rancher/kubectl/)

kubectl は複数のクラスタ環境を取り扱うことが出来ます。
クラスタ環境は context として表され、current context を設定することで操作する対象のクラスタを切り替えつつ操作します。

Rancher UI から context を取得するには、Cluster 画面右上の「Kubeconfig File」ボタンを押して表示される指示に従って、 `~/.kube/config` を設定します。
※既にファイルが存在する場合は `clusters`, `users`, `contexts` の項目を YAML ファイルとしてマージします。(current-context の項目はマージ不要)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/692950b7-cd00-1090-3e8e-412a0c48eabb.png)

設定が完了したら `kubectl config get-contexts` でクラスタが見られることを確認し、見られるようになったら `kubectl config use-context ${cluster_name}` でクラスタのコンテキストを切り替えます。

`kubectl get deployments` で Jenkins が表示されれば OK です。

```bash
$ kubectl get deployments
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
jenkins   1         1         1            1           40m
```

## コンテナのファイルシステムにアクセスする

今回 `kubectl exec` を使ってインストールに必要なシークレットを読み込むことにします。

コンテナは Pod として動作しているため exec で指定する対象は kubectl get pods で取得した Pod 名です。

`$ kubectl exec jenkins-6874684fd9-zzmjv -it cat /var/jenkins_home/secrets/initialAdminPassword`

シークレットが得られたら Jenkins のインストールを進めましょう。

# 終わりに

クラスタ環境の構築し、試しにアプリケーションをデプロイする方法と、デプロイしたコンテナへアクセスする方法を紹介しました。

テスト環境でありクラスタを壊してもよいという考えで運用し、その中で勉強するとよいかなと思います。

# おまけ

## Persistent Volume(PV) を用意する

NFS サーバを用意した後、NFS クライアント用の Pod と PVC が使えるように StorageClass を登録します。

用意するために必要なことは [stable/nfs-client-provisioner](https://github.com/helm/charts/tree/master/stable/nfs-client-provisioner) を helm install するだけです。

```bash
$ helm version
version.BuildInfo{Version:"v3.1.0", GitCommit:"b29d20baf09943e134c2fa5e1e1cab3bf93315fa", GitTreeState:"clean", GoVersion:"go1.13.7"}

$ helm install \
--namespace default \
--set storageClass.defaultClass=true \
--set nfs.server=<IP address/NFS Server> \
--set nfs.path=</exported/path> \
nfs-client-provisioner stable/nfs-client-provisione
```

## Rancher をアップデートする

[公式ドキュメント](https://rancher.com/docs/rancher/v2.x/en/upgrades/upgrades/single-node/) に従って作業を行えばデータを維持してアップデートできます。

作業の流れは次のとおりです。詳細なコマンドは公式ドキュメントを参照してください。

1. 古い `rancher/rancher` コンテナを停止する
2. 古い `rancher/rancher` コンテナの全ボリュームをもつ rancher-data コンテナを作成する
3. 新しい `rancher/rancher` イメージを pull する
4. 新しい `rancher/rancher` イメージを起動する (rancher-data container のボリュームを使う)
5. 新しい Rancher にログインしてアップデートされたことを確認する
6. 古い `rancher/rancher` コンテナを削除する

#  Let's Encrypt(LE) 証明書は使えなかった

残念ながら AWS EC2 がデフォルトで割り当てるグローバルIPには LE 証明書は発行できません。([参考](https://community.letsencrypt.org/t/policy-forbids-issuing-for-name-on-amazon-ec2-domain/12692))

```
Error creating new order :: Policy forbids issuing for name
```

# 課題

- kubectl コマンドを実行した際、Rancher Server から Rancher Agent に対してプライベートIPを使った通信が行われているようで、コンテナのファイルシステムを操作する `exec`, `cp` 等のコマンドが下記メッセージが表示されて失敗する。

```
kubectl  Error from server: error dialing backend: dial tcp  i/o timeout
```

今回のように Rancher Server - Rancher Agent 間はグローバルIPアドレスで通信する場合に、どのように設定すればよいのか不明。(`--internal-address` に `gceexternal` と設定しても効果が無かった)

さくらの VPS や Digital Ocean のようにグローバルIPアドレスがノードに割り当てられている環境では正常に動作する。又は、[こちら(前編)](https://capsulecloud.io/blog/aws/4647)[(後編)](https://capsulecloud.io/blog/aws/4609) を参考にして VPN を貼ってプライベートIPで通信できるようにする必要があると思う。

※[参考](https://forums.rancher.com/t/cross-host-intercontainer-communication-trouble/426/22)
> More like they need public IP addresses or a VPN between networks. Just switching the IP subnet won’t help. Each host needs to be able to communicate (on UDP ports 500 and 4500, but you can just consider ping for now) with the registered IP (displayed in the host box in the UI) for every other host for the full overlay network to work.

> Right now you have 2 disparate networks, so only hosts in the same network will be able to communicate with each other. It is possible to use host labels to schedule containers such that the ones that need to talk to each other all live in the same network.

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

## network plugin is not ready: cni config uninitialized #48798

Issue [network plugin is not ready: cni config uninitialized #48798](https://github.com/kubernetes/kubernetes/issues/48798)

## etcd3 log messages "apply entries took too long" #43363

Issue [etcd3 log messages "apply entries took too long" #43363](https://github.com/kubernetes/kubernetes/issues/43363)