Kubernetes v1.13の構築をkubeadmで行う(Centos7, AWS)

# はじめに
オンプレ/VM/IaaS上でKubernetesを構築する方法は複数ありますが、公式のKubernetesではkubeadmを推奨しています。2018年12月の初めに[Kubernetes1.13がリリースされました](https://kubernetes.io/blog/2018/12/03/kubernetes-1-13-release-announcement/)が、変更点の1つにkubeadmがGAになったことが挙げられます。今回は個人的な検証用のKubernetesの環境を構築した際にkubeadmを利用して構築したので、その手順を紹介したいと思います。

## kubeadmとは
Kubernetesを簡便に構築するツールで、必要最小限の設定を行います。例えばKubernetes Dashboardなどはインストールされません。[公式ページはこちら](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/)。

## 構築環境
今回は以下の環境で構築を行いました。

* EC2: t2.medium × 2 (master, node1つずつ)
* OS: centos7

## 手順
以下手順はすべてrootで実施しています。
### 下準備 (master, node共通)
* yum update -y
* vi /etc/hosts
  * master, node両方の記載をします。

```/etc/hosts
<masterのプライベートIPアドレス> k8s-master
<nodeのプライベートIPアドレス> k8s-node-1
```

* ホスト名の変更
  * hostnamectl set-hostname --static k8s-master
  * --static: /etc/hostnameに記載されるホスト名を変更
* vi /etc/cloud/cloud.cfg
  * [再起動後も新しいホスト名が維持されるよう、以下の記載を追加します。](https://aws.amazon.com/jp/premiumsupport/knowledge-center/linux-static-hostname-rhel7-centos7/)

```/etc/cloud/cloud.cfg
preserve_hostname: true
```

* swapの無効化
  * swapoff -a
  * /etc/fstabにswapの項目がある場合はそちらも削除します。今回は特に変更しません。
* SELinuxの無効化
  * setenforce 0
  * /etc/selinux/configの編集

```
SELINUX=disabled
```

* firewall
  * firewallが有効の場合は変更が必要ですが、今回は初めから有効でないので特に変更はしません。

### docker-ce インストール
[公式ページを参考にしながら](https://docs.docker.com/install/linux/docker-ce/centos/#install-docker-ce-1)実行します。

* 必要なパッケージのインストール

```
yum install -y yum-utils device-mapper-persistent-data lvm2
```

* リポジトリの追加

```
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
```

* dockerインストール
  * [公式ページからdockerバージョンの依存性を確認し](https://kubernetes.io/docs/setup/release/notes/#external-dependencies)、別記事も参考にしつつ以下のバージョンをインストールしました。
コマンド中のオプションをつけると、[インストール中のconflictが発生せずうまくインストールできる](https://qiita.com/penguin_dream/items/23f539ab112962aa93e8)ようです。

```
yum install -y --setopt=obsoletes=0 docker-ce-17.03.2.ce
```

* docker起動・自動起動設定

```
systemctl enable docker && systemctl start docker
```

* docker起動の確認

```
[root@k8s-master ~]# docker version
Client:
 Version:      17.03.2-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   f5ec1e2
 Built:        Tue Jun 27 02:21:36 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.2-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   f5ec1e2
 Built:        Tue Jun 27 02:21:36 2017
 OS/Arch:      linux/amd64
 Experimental: false
```

### kubeadmインストール (master, node共通)
* yumリポジトリの追加

```
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
```

* kubeadm・kubelet・kubectlインストール

```
yum install -y kubelet kubeadm kubectl
```

インストール中のログを見るとバージョンが1.13になっているのがわかります。

```
Dependencies Resolved

=======================================================================================================================================
 Package                            Arch                       Version                            Repository                      Size
=======================================================================================================================================
Installing:
 kubeadm                            x86_64                     1.13.1-0                           kubernetes                     7.9 M
 kubectl                            x86_64                     1.13.1-0                           kubernetes                     8.5 M
 kubelet                            x86_64                     1.13.1-0                           kubernetes                      21 M
Installing for dependencies:
 cri-tools                          x86_64                     1.12.0-0                           kubernetes                     4.2 M
 ebtables                           x86_64                     2.0.10-16.el7                      base                           123 k
 kubernetes-cni                     x86_64                     0.6.0-0                            kubernetes                     8.6 M
 socat                              x86_64                     1.7.3.2-2.el7                      base                           290 k

Transaction Summary
=======================================================================================================================================
Install  3 Packages (+4 Dependent packages)

```

* kubelet起動・自動起動設定

```
systemctl enable kubelet && systemctl start kubelet
```

* kubelet起動確認
  * ここではうまく起動していませんが、kubeadmコマンド実行時にkubelet起動に必要な設定を行ってくれるため、ここでは無視して次に進みます。

```
[root@k8s-master ~]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /etc/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: activating (auto-restart) (Result: exit-code) since Mon 2018-12-24 03:07:01 UTC; 5s ago
     Docs: https://kubernetes.io/docs/
  Process: 13362 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (code=exited, status=255)
 Main PID: 13362 (code=exited, status=255)

Dec 24 03:07:01 k8s-master systemd[1]: Unit kubelet.service entered failed state.
Dec 24 03:07:01 k8s-master systemd[1]: kubelet.service failed.
```

### kubeadm init (master)
[公式ページはこちらから](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/)

* dockerとkubernetesとのcgroup driverの確認
  * [kubeletのデフォルト設定であるcgroupfsの場合は変更する必要はない](https://kubernetes.io/docs/setup/independent/install-kubeadm/#configure-cgroup-driver-used-by-kubelet-on-master-node)
ため、今回は特に変更しません。

```
[root@k8s-master ~]# docker info | grep -i cgroup
Cgroup Driver: cgroupfs
```

* kubeadm起動
  * 今回はCalicoを使用しますので、[Calicoの使用条件に合わせて](https://docs.projectcalico.org/v2.0/getting-started/kubernetes/installation/hosted/kubeadm/)CIDRを指定します。

```
kubeadm init --pod-network-cidr=192.168.0.0/16
```

* kubeadm init時に何をしているか
  * 上記コマンド実行時に出てくるログを見てみます。

```
[root@k8s-master ~]# kubeadm init --pod-network-cidr=192.168.0.0/16
[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 <k8s-masterのプライベートIPアドレス>]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-master localhost] and IPs [<k8s-masterのプライベートIPアドレス> 127.0.0.1 ::1]
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-master localhost] and IPs [<k8s-masterのプライベートIPアドレス> 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 20.501682 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-master" as an annotation
[mark-control-plane] Marking the node k8s-master as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node k8s-master as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: figfuf.3uvfrsxnowkrf5ed
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!
```

  * preflight: 必要なdockerイメージのインストールを行います。
  * kubelet-start: kubelet起動に必要なコンフィグファイルの更新を行います。
  * certs: Kubernetesの動作に必要な各コンポーネントの証明書を発行します。
  * kubeconfig: 各コンポーネントのコンフィグファイルの更新を行います。
  * control-plane: apiserverなどのcontrol planeに必要なmanifestファイルの作成を行います。
  * etcd: etcd用のmanifestファイルを作成します。
  * wait-control-plane: control planeが立ち上がるのを待ちます。
  * apiclient: すべてのcontrol planeの動作を確認します。
  * uploadconfig: kubeadm-configと言うConfigMapファイルで使用したコンフィグ情報を保存します。
  * kubelet: 先ほど保存した情報を用いて、kubernetes 1.13用のConfigMapを作成します。
  * patchnode: CRI(Container Runtime Interface)のsocket情報をk8s-master用に更新します。
  * mark-control-plane: k8s-masterにcontrol plane用のlabelを添付し、control planeとして認識するようにします。
  * bootstrap-token: RBAC用のtokenを発行します。
  * bootstraptoken: 作成したtokenを用いてRBACルールを適用します。
  * addons: Kubernetesに必要なaddonであるCoreDNSとkube-proxyをアプライします。

以上、これだけのことをkubeadmはやってくれているんですね。もっと具体的に、何をやっているかについては、[公式ページに説明があります](https://kubernetes.io/docs/reference/setup-tools/kubeadm/implementation-details/#core-design-principles)。

[追記]
kubeadm init時にオプションを追加することでセットアップ時の設定を変更できるようです。[公式ページはこちらになります](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init-phase/)。本番環境への適用などを考える際には参考になりそうです。

* kubeadm init後の設定
  * kubeadm init実行後に出てくる出力をもとに設定します。

```
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
```

### Calicoデプロイ
* 条件確認
  * [公式ページにある通り](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/#network-plugin-requirements)の条件を満たすか確認します。

```
[root@k8s-master ~]# sysctl -n net.bridge.bridge-nf-call-iptables
1
[root@k8s-master ~]#
```

* Calicoデプロイ
  * 今回は[やや古いバージョンのCalicoをデプロイしました](https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/calico#installing-with-the-kubernetes-api-datastore50-nodes-or-less)が、2018年12月28日時点での最新版である[v3.4ではやや手順が変わっています](https://docs.projectcalico.org/v3.4/getting-started/kubernetes/installation/calico#installing-with-the-kubernetes-api-datastore50-nodes-or-less)。

```
kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
```

* kubectl動作確認

```
[root@k8s-master ~]# kubectl get nodes
NAME         STATUS   ROLES    AGE   VERSION
k8s-master   Ready    master   4m    v1.13.1
[root@k8s-master ~]#
```

### kubeadm join (node)
[公式ページはこちらから](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/)

* kubeadm join実行
  * kubeadm init時に出力されるコマンドをnode側で実行します。

```
kubeadm join <k8s-masterプライベートIPアドレス:ポート番号> --token <token値> sha256:<sha256値>
```

* kubeadm join時に何をしているか
  * 先ほどと同様に出力ログを確認します。

```
[preflight] Running pre-flight checks
	[WARNING Service-Kubelet]: kubelet service is not enabled, please run 'systemctl enable kubelet.service'
[discovery] Trying to connect to API Server "<k8s-masterプライベートIPアドレス:ポート番号>"
[discovery] Created cluster-info discovery client, requesting info from "https://<k8s-masterプライベートIPアドレス:ポート番号>"
[discovery] Requesting info from "https://<k8s-masterプライベートIPアドレス:ポート番号>" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "<k8s-masterプライベートIPアドレス:ポート番号>"
[discovery] Successfully established connection with API Server "<k8s-masterプライベートIPアドレス:ポート番号>"
[join] Reading configuration from the cluster...
[join] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.13" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "k8s-node-1" as an annotation

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.
```
  * preflight: 事前確認をします。ここでkubeletが起動していないと出ますが、しばらく後で実行してくれます。
  * discovery: k8s-masterを探して接続を確立します。
  * join: コンフィグ情報を取得してjoinします。
  * kubelet: kubelet用のConfigMapを作成します。
  * kubelet-start: kubeletを起動します。
  * tlsbootstrap: [TLS Bootstrap](https://qiita.com/oke-py/items/cf83a174c5e95c9cbe0e)の起動を待ちます。
  * patchnode: CRI socketを更新します。

### 動作確認
* kubectl実行(master)
  * kubectlで追加したk8s-node-1が認識されたか確認します。

```
[root@k8s-master ~]# kubectl get nodes
NAME         STATUS   ROLES    AGE     VERSION
k8s-master   Ready    master   5m59s   v1.13.1
k8s-node-1   Ready    <none>   57s     v1.13.1
[root@k8s-master ~]# 
```

## 参考リンク
https://qiita.com/sotoiwa/items/f35a0b818f43aa5867e9#os%E6%9B%B4%E6%96%B0