ClusterHATを使って世界最小Kubernetesクラスタを構築する - インストール編その1

#はじめに
まずはClusterHATを接続してRaspberry Pi Zero3枚を含んだ豆腐クラスタを構築した[サーバー構築編](https://qiita.com/ShinHashitani/items/24fe88a012e01dfc28d0)、そしてKelsey Hightowerさんの[Kubernetes The Hard Way](https://github.com/kelseyhightower/kubernetes-the-hard-way)を[豆腐クラスタ上に構築する第一弾(TLS証明書編)](https://qiita.com/ShinHashitani/items/d91599399422b7b03cb2)に続き、いよいよ今回は各種サーバーコンポーネントをインストールする。

前回同様に原則Kelsay Hightowerさんのチュートリアルを順を追って進めていくが、本家がGCP上に6つのVMを用意して構築する手順となっている為、その手順と差が出る箇所は特に重点的に記載していく。前回は第6章まで終わったので、今回は第7章から始める。

#環境
[前回の投稿](https://qiita.com/ShinHashitani/items/d91599399422b7b03cb2#%E7%92%B0%E5%A2%83)に記載。

最終的に各種バイナリならびに設定ファイルはコントローラーの以下に配置される：

|     Folder          | File                            |
|:--------------------|:--------------------------------|
| /etc/etcd           | ca.pem                          |
|                     | kubernetes-key.pem              |
|                     | kubernetes.pem                  |
| /etc/systemd/system | etcd.service                    |
|                     | kube-apiserver.service          |
|                     | kube-controller-manager.service |
|                     | kube-sccheduler.service         |
| /usr/local/bin      | etcd                            |
|                     | etcdctl                         |
|                     | kube-apiserver                  |
|                     | kube-controller-manager         |
|                     | kube-schedule                   |
|                     | kubectl                         |
| /var/lib/kubernetes | ca-key.pem                      |
|                     | ca.pem                          |
|                     | encryption-config.yaml          |
|                     | kubernetes-key.pem              |
|                     | kubernetes.pem                  |


#7. [Bootstrapping the etcd Cluster](https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/07-bootstrapping-etcd.md)
まずはクラスタの各種状態を保存するetcdをインストールする。チュートリアルでは3つのコントローラーぞれぞれにetcdをインストールしクラスタ化しているが、豆腐クラスタでは母艦であるPi3 Bのみをコントローラーとしており、今回はこのコントローラーのみにインストールする。（ノードにもインストールしてクラスタ化しても一向に問題ない。）

ここがさらに大事。Raspberry 3BのCPUは64ビットだがRaspbian自体は32ビットである。チュートリアルでは64ビット版のバイナリをインストール手順となっているのでここを32ビットに変更する必要がある。そして残念ながらetcdは[Goの32ビットに関する問題の為サポートしない](https://github.com/coreos/etcd/blob/master/Documentation/op-guide/supported-platform.md#32-bit-and-other-unsupported-systems)とあるので自分でビルドする必要がある。

etcdのソースからのインストールについては[kubernetesをRaspberry pi 3にインストールする(その2)](http://wasure-memo.h-tsk.com/2017/05/kubernetesraspberry-pi-32.html)に大変お世話になった。感謝。

```bash:Controller
#Goのインストール
$ wget https://storage.googleapis.com/golang/go1.10.1.linux-armv6l.tar.gz
$ tar zxf go1.10.1.linux-armv6l.tar.gz
$ sudo mv go /usr/local
$ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc
# Gitのインストール
$ sudo apt-get install git
# etcdのインストール
$ export GOOS=linux
$ export GOARCH=arm
$ export GOARM=7
$ git clone --branch v3.3.4 https://github.com/coreos/etcd.git
$ cd etcd
$ ./build
$ sudo mv bin/* /usr/local/bin
$ sudo mkdir -p /etc/etcd /var/lib/etcd
# 第5章で生成した証明書とキーをコピーしたフォルダに移動してから実行
$ sudo cp ca.pem kubernetes-key.pem kubernetes.pem /etc/etcd/
```

次にetcd用の.serviceファイルを作成。ホスト名を取得してからクラスタ内のインスタンス名として定義する。

```bash:Controller
# INTERNAL_IPは直接コントローラーのIPを設定
$ INTERNAL_IP="192.168.1.100"
# ホスト名を取得。etcdクラスタ内で一意となる名前であれば良。
$ ETCD_NAME=$(hostname -s)
# etcd.serviceファイルの生成
$ cat > etcd.service <<EOF
[Unit]
Description=etcd
Documentation=https://github.com/coreos
After=network.target

[Service]
Type=notify
Restart=always
RestartSec=5s
LimitNOFILE=40000
TimeoutStartSec=0
Environment="ETCD_UNSUPPORTED_ARCH=arm"
ExecStart=/usr/local/bin/etcd \\
  --name ${ETCD_NAME} \\
  --cert-file=/etc/etcd/kubernetes.pem \\
  --key-file=/etc/etcd/kubernetes-key.pem \\
  --peer-cert-file=/etc/etcd/kubernetes.pem \\
  --peer-key-file=/etc/etcd/kubernetes-key.pem \\
  --trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-trusted-ca-file=/etc/etcd/ca.pem \\
  --peer-client-cert-auth \\
  --client-cert-auth \\
  --initial-advertise-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-peer-urls https://${INTERNAL_IP}:2380 \\
  --listen-client-urls https://${INTERNAL_IP}:2379,http://127.0.0.1:2379 \\
  --advertise-client-urls https://${INTERNAL_IP}:2379 \\
  --initial-cluster-token etcd-cluster-0 \\
  --initial-cluster controller=https://${INTERNAL_IP}:2380 \\
  --initial-cluster-state new \\
  --data-dir=/var/lib/etcd

[Install]
WantedBy=multi-user.target
EOF
```

本来etcdはもっぱらクラスタとして複数インスタンスを登録して運用するものなのだが、今回はまず1インスタンスのみで動くかどうか検証する。具体的にはinitial-clusterのパラメータに1インスタンス（つまりコントローラー）の定義のみを登録している。クラスタを組む場合にはここに列挙すれば良い。

もう一つ大事なのはオリジナルは無いEnvironment="ETCD_UNSUPPORTED_ARCH=arm"というパラメータ。etcdはサポートされていないプラットフォーム上で稼働させる場合、例えそのプラットフォーム向けにコンパイルされたものでもワーニングを出力してプロセスを終了させてしまう。（WARNINGと出るがこれはエラーと同じ扱い。）このETCD_UNSUPPORTED_ARCHを明示的に指定することでワーニングを回避出来る。

最後にサービスファイルを所定の場所に移動しスタートさせる。

```bash:Controller
$ sudo mv etcd.service /etc/systemd/system/
$ sudo systemctl daemon-reload
$ sudo systemctl enable etcd
Created symlink /etc/systemd/system/multi-user.target.wants/etcd.service → /etc/systemd/system/etcd.service.
$ sudo systemctl start etcd
# 稼働確認
$ ETCDCTL_API=3 etcdctl member list
371464a192041f5c, started, controller, https://192.168.1.100:2380, https://192.168.1.100:2379
```

これで完了。
#8. [Bootstrapping the Kubernetes Control Plane](https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/08-bootstrapping-kubernetes-controllers.md)
いよいよ佳境に入ってきた。
###各種サービスのインストール
etcdとは異なりkubernetesの場合はARM用のバイナリが用意されているのでこれらを取得する。（ちなみにetcdと異なりビルドにはmakeが必要）またそれを踏まえてチュートリアルのバイナリ取得箇所を少し訂正する。

まず各サービスのバイナリを取得しコントローラーにインストールする。

```bash:Controller
# ARM版バイナリを取得
$ wget -q --show-progress --https-only --timestamping \
  "https://storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/linux/arm/kube-apiserver" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/linux/arm/kube-controller-manager" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/linux/arm/kube-scheduler" \
  "https://storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/linux/arm/kubectl"
# インストール
$ chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
$ sudo mkdir -p /var/lib/kubernetes/
$ sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/
# TLS証明書&キー及び設定ファイルの設置
$ sudo mkdir -p /var/lib/kubernetes/
# 第5章及び6章で生成した証明書、キー、設定ファイルをコピーしたフォルダに移動してから実行
$ sudo mv ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem encryption-config.yaml /var/lib/kubernetes/
```

次にAPI-Serverの.serviceファイルを生成

```bash:Controller
$ cat > kube-apiserver.service <<EOF
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
  --admission-control=Initializers,NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
  --advertise-address=192.168.1.100 \\
  --allow-privileged=true \\
  --apiserver-count=1 \\
  --audit-log-maxage=30 \\
  --audit-log-maxbackup=3 \\
  --audit-log-maxsize=100 \\
  --audit-log-path=/var/log/audit.log \\
  --authorization-mode=Node,RBAC \\
  --bind-address=0.0.0.0 \\
  --client-ca-file=/var/lib/kubernetes/ca.pem \\
  --enable-swagger-ui=true \\
  --etcd-cafile=/var/lib/kubernetes/ca.pem \\
  --etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
  --etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
  --etcd-servers=https://192.168.1.100:2379 \\
  --event-ttl=1h \\
  --experimental-encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \\
  --insecure-bind-address=127.0.0.1 \\
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
  --kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
  --kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
  --kubelet-https=true \\
  --runtime-config=api/all \\
  --service-account-key-file=/var/lib/kubernetes/ca-key.pem \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --service-node-port-range=30000-32767 \\
  --tls-ca-file=/var/lib/kubernetes/ca.pem \\
  --tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
  --tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
```

Control Managerの.serviceファイルを生成。

```bash:Controller
$ cat > kube-controller-manager.service <<EOF
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \\
  --address=0.0.0.0 \\
  --cluster-cidr=10.200.0.0/16 \\
  --cluster-name=kubernetes \\
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
  --leader-elect=true \\
  --master=http://127.0.0.1:8080 \\
  --root-ca-file=/var/lib/kubernetes/ca.pem \\
  --service-account-private-key-file=/var/lib/kubernetes/ca-key.pem \\
  --service-cluster-ip-range=10.32.0.0/24 \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
```

次にSchedulerの.serviceファイルを生成。

```bash:Controller
$ cat > kube-scheduler.service <<EOF
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-scheduler \\
  --leader-elect=true \\
  --master=http://127.0.0.1:8080 \\
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
```

これら全てのサービスを起動する。

```bash:Controller
$ sudo mv kube-apiserver.service kube-scheduler.service kube-controller-manager.service /etc/systemd/system/
$ sudo systemctl daemon-reload
$ sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler
$ sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler
$ kubectl get componentstatuses
NAME                 STATUS    MESSAGE             ERROR
scheduler            Healthy   ok
controller-manager   Healthy   ok
etcd-0               Healthy   {"health":"true"}
```

ヘルスチェックが通れば完了。
###API-ServerからKubeletへの認証
API-Serverから各Podを管理する為にはそれぞれのノードに配置されたKubeletへのアクセスが必要となる。まずはKubeletにアクセスする為のClusterRoleを定義する

```bash:Controller
#以下をコントローラーにて実行
$ cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:kube-apiserver-to-kubelet
rules:
  - apiGroups:
      - ""
    resources:
      - nodes/proxy
      - nodes/stats
      - nodes/log
      - nodes/spec
      - nodes/metrics
    verbs:
      - "*"
EOF
#以下の結果が返ってくれば成功
clusterrole "system:kube-apiserver-to-kubelet" created
```

このClusterRoleを実際にAPI-ServerがKubeletにアクセスする際に使用するユーザーと紐づける必要がある。このユーザーとは[TLS認証編で記載した証明書発行のセクション](https://qiita.com/ShinHashitani/items/d91599399422b7b03cb2#tls%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%A8%E3%82%AD%E3%83%BC%E3%81%AE%E7%94%9F%E6%88%90)にあるように、API-ServerのCNとして指定したkubernetesである。これはClusterRoleBindingの生成という方法で行う。

```bash:Controller
#以下をコントローラーにて実行
$ cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: system:kube-apiserver
  namespace: ""
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:kube-apiserver-to-kubelet
subjects:
  - apiGroup: rbac.authorization.k8s.io
    kind: User
    name: kubernetes
EOF
#以下の結果が返ってくれば成功
clusterrolebinding "system:kube-apiserver" created
```

#おわりに
etcdのインストールで少々躓いたが、それ以降は比較的順調に進んだ。（元のマニュアルの品質が高い）また、Kubernetesに限らずLinuxのsystemdのサービス定義にまで説明してくれているので、こういった事に疎い人間には大変参考になる。

次回はノード側の各種サービスをインストールする。