kubeadm / containerd で Kubernetes

# はじめに
昨年 CKA を取得するべく Kubernetes を勉強し無事取得することが出来ました。
体験記は以下に記事を書いているのでご参考までに。
> [CKA受験体験記(2020/03)](https://qiita.com/t_ume/items/1b72b3af5046716bf472)

※改定前の受験内容なので、最新の情報はホームページをご確認ください。
> [Certified Kubernetes Administrator (CKA)](https://www.cncf.io/certification/cka/)

受験範囲には ```kubeadm``` の使い方も入っていました。
```kubeadm``` は Kubernetes を構築するためのツールで、シングルノードから複数ノードのクラスタまで構築することが出来ます。
受験当時は関連する問題があったものの ```kubeadm``` の知識が薄かったため解くことが出来ず・・・
今回は復習も込めて ```kubeadm``` を使ってシングルノードのクラスタを構築しようと思います。

```kubeadm``` の動きについては Y氏の解説記事が分かりやすいので是非。
> [kubeadmって何やってるの？公式ページをちゃんと読む](https://qiita.com/FY0323/items/6a3b3270888c96ba13d3)

# 環境情報
今回はローカル PC に VirtualBox / Vagrant で構築します。

**ホストマシン**
OS：Windows10 Pro
VirtualBox：6.1.16
Vagrant：2.2.14

**ゲストマシン**
OS：Ubuntu 18.04.5 LTS
containerd：1.4.3
kubeadm：v1.20.2
Kubernetes：v1.20.2
kubectl：v1.20.2

|Role|hostname|enp0s3（NAT）|enp0s8（Bridge）|
|:---|:---|:---|:---|
|ControlPlane|cp01|10.0.2.15|192.168.10.61|
|Worker|wk01|10.0.2.15|192.168.10.71|

下記に今回使った ```Vagrantfile``` を載せておきます。ご参考までに。
※NIC1（enp0s3）：NAT、NIC2（enp0s8）：ブリッジアダプターで設定しています。
※ ```kubectl exec``` で支障がでるため、起動スクリプトでデフォルトゲートウェイをブリッジアダプター側（192.168.10.1）に変更しています。
> [Vagrant上のkubernetesでWorker Nodeのコンテナにアタッチできない問題について]( https://qiita.com/ishii1648/items/a50e945000269a951033)

```ruby:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.define "cp01" do |server|
    server.vm.box = "ubuntu/bionic64"
    server.vm.hostname = "cp01"
    server.vm.network "public_network", ip: "192.168.10.61"    
    server.vm.synced_folder ".", "/vagrant", disabled: true
    server.vm.provider "virtualbox" do |vb|
      vb.customize [
        "modifyvm", :id,
        "--memory", "4096",
        "--cpus", "2"
      ]
    end
    server.vm.provision "shell", run: "always", inline: <<-SHELL
sudo ip route del default via 10.0.2.2
sudo ip route add default via 192.168.10.1
    SHELL
  end

  config.vm.define "wk01" do |server|
    server.vm.box = "ubuntu/bionic64"
    server.vm.hostname = "wk01"
    server.vm.network "public_network", ip: "192.168.10.71"    
    server.vm.synced_folder ".", "/vagrant", disabled: true
    server.vm.provider "virtualbox" do |vb|
      vb.customize [
        "modifyvm", :id,
        "--memory", "8192",
        "--cpus", "2"
      ]
    end
    server.vm.provision "shell", run: "always", inline: <<-SHELL
sudo ip route del default via 10.0.2.2
sudo ip route add default via 192.168.10.1
    SHELL
  end
end
```

# containerd インストール
コンテナ実行環境として ```containerd``` をインストールしていきます。
まずは前準備から。
※cp01 / wk01 両方で実行します。
※root ユーザで実行しています。

```console3
_/_/_/ カーネルモジュールのロード _/_/_/
# modprobe br_netfilter
# modprobe overlay

_/_/_/ カーネルモジュールの確認 _/_/_/
# lsmod | grep -e br_netfilter -e overlay
overlay                77824  0
br_netfilter           24576  0
bridge                155648  1 br_netfilter

_/_/_/ 再起動後にも有効にするため設定ファイルを作成 _/_/_/
# cat >/etc/modules-load.d/containerd.conf <<EOF
br_netfilter
overlay
EOF

_/_/_/ カーネルパラメータの設定 _/_/_/
# cat >/etc/sysctl.d/99-containerd.conf <<EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

_/_/_/ カーネルパラメータ適用 _/_/_/
# sysctl --system
・・・
* Applying /etc/sysctl.d/99-containerd.conf ...
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
・・・

_/_/_/ 適用の確認 _/_/_/
# sysctl -a | grep -e bridge-nf-call-ip -e ip_forward
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
・・・
```

準備ができたので ```containerd``` をインストールしていきます。

```
_/_/_/ https 越しのリポジトリ利用のための準備 _/_/_/
# apt-get update && \
  apt-get install -y apt-transport-https ca-certificates curl software-properties-common

_/_/_/ Docker 公式の GPG 鍵の追加 _/_/_/
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

_/_/_/ Docker のリポジトリ追加 _/_/_/
# add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"

_/_/_/ containerd のインストール _/_/_/
# apt-get update && apt-get install -y containerd.io

_/_/_/ containerd の状態確認 _/_/_/
# systemctl status containerd -n0
● containerd.service - containerd container runtime
   Loaded: loaded (/lib/systemd/system/containerd.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2021-02-05 16:26:30 UTC; 13min ago
     Docs: https://containerd.io
  Process: 30327 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
 Main PID: 30328 (containerd)
    Tasks: 11
   CGroup: /system.slice/containerd.service
           mq30328 /usr/bin/containerd
```

# kubeadm 準備
```kubeadm``` で Kuberenetes をインストールする準備を行います。
```kubeadm```、```kubectl```、```kubelet``` をインストールします。
※cp01 / wk01 両方で実行します。
※ root ユーザで実行しています。

```
_/_/_/ google の GPG 鍵の追加 _/_/_/
# curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

_/_/_/ Kubernetes レポジトリの追加 _/_/_/
# cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

_/_/_/ 各パッケージのインストール _/_/_/
# apt-get update && sudo apt-get install -y kubelet kubeadm kubectl

_/_/_/ kubelet の状態確認 _/_/_/
# systemctl status kubelet -n0
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/kubelet.service.d
           mq10-kubeadm.conf
   Active: activating (auto-restart) (Result: exit-code) since Fri 2021-02-05 16:49:51 UTC; 6s ago
     Docs: https://kubernetes.io/docs/home/
  Process: 31978 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (cod
 Main PID: 31978 (code=exited, status=255)

_/_/_/ kubectl バージョン確認 _/_/_/
# kubectl version --client
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:28:09Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}

_/_/_/ kubeadm バージョン確認 _/_/_/
# kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:25:59Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}
```

# kubeadm 実行
準備ができたので早速 ```kubeadm``` を使って Kubernetes の ControlPlane からインストールしていきます。

```
_/_/_/ kubeadm で kubernetes インストール _/_/_/
### --pod-network-cidr：Pod に割り振られる IP アドレスレンジ
### ⇒ デフォルトだと LAN と被ったので指定
### --apiserver-advertise-address：API サーバーが待ち受けする IP アドレス
### ⇒ Vagrant デフォルトだと NAT の NIC で動作不良起こしそうだったので、ブリッジの NIC を指定

# kubeadm init \
--pod-network-cidr 172.16.0.0/16 \
--apiserver-advertise-address 192.168.10.61

[init] Using Kubernetes version: v1.20.2
[preflight] Running pre-flight checks
error execution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR CRI]: container runtime is not running: output: time="2021-02-05T16:54:10Z" level=fatal msg="getting status of runtime failed: rpc error: code = Unimplemented desc = unknown service runtime.v1alpha2.RuntimeService"
, error: exit status 1
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
To see the stack trace of this error execute with --v=5 or higher
```

エラーが・・・出た際は以下で対応みたいです。
> 参考：https://github.com/containerd/containerd/issues/4581

```
_/_/_/ containerd の 設定ファイルを削除して再起動 _/_/_/
# rm /etc/containerd/config.toml
# systemctl restart containerd
```

kubeadm を再実行します。

```
_/_/_/ kubeadm で kubernetes インストール _/_/_/
# kubeadm init \
--pod-network-cidr 172.16.0.0/16 \
--apiserver-advertise-address 192.168.10.61

[init] Using Kubernetes version: v1.20.2
[preflight] Running pre-flight checks
・・・
[certs] Using certificateDir folder "/etc/kubernetes/pki"
・・・
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
・・・
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
・・・
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
・・・
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
・・・
[apiclient] All control plane components are healthy after 18.501499 seconds
・・・
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.10.61:6443 --token xxxx \
    --discovery-token-ca-cert-hash sha256:xxxx
```

ControlPlane がインストール出来ました。
出力にもあるように、```kubectl``` の設定ファイルを配置して、接続確認を行います。

```
_/_/_/　設定ファイル配置 _/_/_/
# mkdir -p $HOME/.kube
# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

_/_/_/ クラスタへ接続確認 _/_/_/
# kubectl get node
NAME   STATUS     ROLES                  AGE   VERSION
cp01   NotReady   control-plane,master   17m   v1.20.2
```

続いて Pod ネットワークプラグインをインストールします。
今回は Project Calico をインストールしていきます。

```
# kubectl apply -f https://docs.projectcalico.org/v3.17/manifests/calico.yaml
・・・
daemonset.apps/calico-node created
serviceaccount/calico-node created
deployment.apps/calico-kube-controllers created
・・・

# kubectl get node
NAME   STATUS   ROLES                  AGE   VERSION
cp01   Ready    control-plane,master   18m   v1.20.2
```

最後に Worker ノードを追加していきます。
※```kubeadm``` 実行時に ControlPlane と同じエラーが出力された場合は、上述の対応が必要です。
※オプション（--token/--discovery-token-ca-cert-hash）は ControlPlane 導入時に出力されているので、そちらをコピーします。
※今回は ControlPlane 導入時の出力コマンドに「--apiserver-advertise-address」で Worker の IP アドレスを追加しています。

```
_/_/_/ worker ノードの追加 _/_/_/
# kubeadm join 192.168.10.61:6443 \
--token xxxx \
--discovery-token-ca-cert-hash sha256:xxxx \
--apiserver-advertise-address 192.168.10.71

[preflight] Running pre-flight checks
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[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] Starting the kubelet
・・・

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

_/_/_/ クラスタのノード確認 _/_/_/
# kubectl get node
NAME   STATUS   ROLES                  AGE   VERSION
cp01   Ready    control-plane,master   26m   v1.20.2
wk01   Ready    <none>                 90s   v1.20.2
```

# 動作確認
コンテナをデプロイして動作確認を行います。

```
_/_/_/ Pod 作成 _/_/_/
# kubectl run nginx --image=nginx --port=80
pod/nginx created

# kubectl get pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP             NODE   NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          15s   172.16.74.65   wk01   <none>           <none>

_/_/_/ Pod 内でバージョン確認 _/_/_/
# kubectl exec nginx -- nginx -v
nginx version: nginx/1.19.6

_/_/_/ Service（NodePort）を公開 _/_/_/
# kubectl expose pod nginx --type=NodePort
service/nginx exposed

_/_/_/ Worker の IP、Service の NodePort を取得して Pod にアクセス _/_/_/
# IP=`kubectl get node wk01 -o=jsonpath='{.status.addresses[?(@.type == "InternalIP")].address}'`
# PORT=`kubectl get svc nginx -o yaml -o=jsonpath='{.spec.ports[0].nodePort}'`
# curl -I http://$IP:$PORT/
HTTP/1.1 200 OK
Server: nginx/1.19.6
Date: Sat, 06 Feb 2021 22:18:04 GMT
Content-Type: text/html
・・・
```

# まとめ
久しぶりに ```kubeadm``` を利用してみましたが、簡単にクラスタを構築できるので改めて便利だと思いました。
また、今まで ```docker``` で構築していたため、```containerd``` に少し抵抗があったのですが、意外とすんなり構築できてよかったです。
この調子でクラスタの構築も試してみようと思います。

参考
> ◆ CRIのインストール
> https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/
> ◆ kubeadmのインストール
> https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
> ◆ kubeadmを使用したシングルコントロールプレーンクラスターの構築
> https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/