kubernetes - kubeadm でオンプレ環境準備

# 背景
Kubernetes 完全ガイドで勉強するために kubeadm でオンプレ環境を用意します。
環境は以下の通りです。

・CentOS7.9 - masternode / master1
・CentOS7.9 - workernode / worker1
※ ノードは必要になれば追加します

# kubeadm のインストール
手順は[公式サイト](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)に則ります。

#### 前提条件
CentOS7 を満たしていれば良さそうです。

#### 事前準備
iptables がブリッジを通過するトラフィックを処理できるようにする

```bash
[root@master1 ~]# cat <<EOF > /etc/sysctl.d/k8s.conf
> net.bridge.bridge-nf-call-ip6tables = 1
> net.bridge.bridge-nf-call-iptables = 1
> EOF
[root@master1 ~]# sysctl --system
* Applying /usr/lib/sysctl.d/00-system.conf ...
* Applying /usr/lib/sysctl.d/10-default-yama-scope.conf ...
kernel.yama.ptrace_scope = 0
* Applying /usr/lib/sysctl.d/50-default.conf ...
kernel.sysrq = 16
kernel.core_uses_pid = 1
kernel.kptr_restrict = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.promote_secondaries = 1
net.ipv4.conf.all.promote_secondaries = 1
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
* Applying /etc/sysctl.d/99-sysctl.conf ...
* Applying /etc/sysctl.d/k8s.conf ...
* Applying /etc/sysctl.conf ...
*
```
カーネルパラメータを変更したようですね。

#### iptables が nftables バックエンドを使用しないようにする
Cent7 は対応不要項目ですが、"RHEL 8 はレガシーモードへの切り替えをサポートしていないため現在の kubeadm パッケージとは互換性がありません" とのことです。
Cent8 で kubeadm 使えないので注意が必要ですね。

#### 必要ポートの確認
検証環境なので必要に応じてポート開放 or firewalld 無効化で行きます。

#### コンテナランタイムのインストール
Kubernetes は複数のコンテナランタイムをサポートしています。
Docker・containerd・CRI-O などです。親しみがある Docker を採用します。

ここで[Docker公式サイト](https://docs.docker.com/engine/install/centos/)を基に docker-ce をインストールします。

```bash
[root@master1 ~]# yum install -y yum-utils
// yum-utils インストール

[root@master1 ~]# yum-config-manager \
>     --add-repo \
>     https://download.docker.com/linux/centos/docker-ce.repo
// レポジトリ追加
```

```bash
[root@master1 ~]# yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
// docker-ce 等インストール

[root@master1 ~]# systemctl start docker
// サービス起動
[root@master1 ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
// サービス自動起動有効化

[root@master1 ~]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
// docker コマンド実行
```

#### kubeadm・kubelet・kubectl のインストール
kubernetes レポジトリを追加します。

```bash
[root@master1 ~]# 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
[root@master1 ~]# ls -la /etc/yum.repos.d/
合計 60
--snip--
-rw-r--r--.  1 root root  263  6月 29 12:53 kubernetes.repo // kubernetes.repo 追加
```

SElinux を permissive モードに変更します。
```bash
[root@master1 ~]# setenforce 0
[root@master1 ~]# sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
[root@master1 ~]# getenforce
Permissive
```

kubelet 等をインストールします。
```bash
[root@master1 ~]# yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
 * base: ftp.riken.jp
 * extras: ftp.riken.jp
 * updates: ftp.riken.jp
kubernetes/signature                          |  844 B     00:00     
https://packages.cloud.google.com/yum/doc/yum-key.gpg から鍵を取得中
です。
Importing GPG key 0x13EDEF05:
 Userid     : "Rapture Automatic Signing Key (cloud-rapture-signing-key-2022-03-07-08_01_01.pub)"
 Fingerprint: a362 b822 f6de dc65 2817 ea46 b53d c80d 13ed ef05
 From       : https://packages.cloud.google.com/yum/doc/yum-key.gpg
https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg から鍵
を取得中です。
kubernetes/signature                          | 1.4 kB     00:00 !!! 
https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64/repodata/repomd.xml: [Errno -1] repomd.xml signature could not be verified for kubernetes
// 取得した鍵がマッチしない的なことを言っていますね。


他のミラーを試します。
--snip--
```

gpgcheck を無効化すると行けるようなので無効化します。
```bash
[root@master1 ~]# cat /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
```

再度 yum コマンドで kubelet 等をインストールします。
次に kubelet を起動します。

```bash
[root@master1 ~]# systemctl enable --now kubelet
Created symlink from /etc/systemd/system/multi-user.target.wants/kubelet.service to /usr/lib/systemd/system/kubelet.service.

# systemctl --help
--now            Start or stop unit in addition to enabling or disabling it
// --now オプションはサービス有効/無効 + 起動/停止なんですね
```

# kubeadm を使用したクラスターの作成
kubeadm init <args> でコントロールプレーンノードをセットアップします。
まずは kubeadm init だけで実行してみる。

```bash
[root@master1 ~]# kubeadm init
[init] Using Kubernetes version: v1.24.2
[preflight] Running pre-flight checks
        [WARNING Firewalld]: firewalld is active, please ensure ports [6443 10250] are open or your cluster may not function correctly
        [WARNING Swap]: swap is enabled; production deployments should disable swap unless testing the NodeSwap feature gate of the kubelet
        [WARNING Hostname]: hostname "master1.example.local" could not be reached
        [WARNING Hostname]: hostname "master1.example.local": lookup master1.example.local on 8.8.8.8:53: no such host
error execution phase preflight: [preflight] Some fatal errors occurred:
        [ERROR CRI]: container runtime is not running: output: E0629 13:11:53.138685    1851 remote_runtime.go:925] "Status from runtime service failed" err="rpc error: code = Unimplemented desc = unknown service runtime.v1alpha2.RuntimeService"
time="2022-06-29T13:11:53+09:00" level=fatal msg="getting status of runtime: 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
```

気持ちいいくらいにエラーが出ます。一つずつ対処します。
まずは、firewalld に必要なポートを開けます。

```bash
[root@master1 ~]# firewall-cmd --add-port=6443/tcp --zone=public --permanent && \
> firewall-cmd --add-port=10250/tcp --zone=public --permanent && \
> firewall-cmd --reload
success
success
success
```

次に、swap を無効化します。

[root@master1 ~]# cat /etc/fstab 

```bash
[root@master1 ~]# swapoff -a // 一時的な無効化

[root@master1 ~]# cat /etc/fstab 
#
# /etc/fstab
# Created by anaconda on Wed Jun 29 10:06:31 2022
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/centos-root /                       xfs     defaults        0 0
UUID=438009b8-90bf-45b4-9dae-46a9010b234e /boot                   xfs     defaults        0 0
#/dev/mapper/centos-swap swap                    swap    defaults        0 0 // 先頭 # 追加で恒久的な無効化
```

次に、ERROR CRI の対応ですが、systemctl statu docker で docker は Active です。
下記ファイルを削除すると行けるようなので削除します。

```bash
[root@master1 ~]# rm -rf /etc/containerd/config.toml

[root@master1 ~]# systemctl restart containerd // containerd を再起動しないと config.toml 削除手順が反映されないようです
```

Warning は名前解決系なので、ここで再度 kubeadm init を実行します。

```bash
[root@master1 ~]# kubeadm init

Then you can join any number of worker nodes by running the following on each as root:
// 最終行に workernode 追加用のコマンドが表示されれば OK です
```

kubectl コマンドでコントロールプレーンの状態を確認します。

```bash
[root@master1 ~]# kubectl get nodes
The connection to the server localhost:8080 was refused - did you specify the right host or port?
```

環境変数に KUBECONFIG を追加する。

```bash
[root@master1 ~]# export KUBECONFIG=/etc/kubernetes/admin.conf
[root@master1 ~]# vi ~/.bash_profile 

[root@master1 ~]# kubectl get nodes
NAME                    STATUS     ROLES           AGE   VERSION
master1.example.local   NotReady   control-plane   10m   v1.24.2 // workernode追加前は NotReady ステータスで問題ない
```

続いて workernodeを追加します。(事前に kubeadm・kubelet・kubeclt はインストール済みです)

```bash
[root@worker1 ~]# kubeadm join 
--snip---
[ERROR CRI]: container runtime is not running: output: E0629 13:35:17.280545    2936 remote_runtime.go:925] "Status from runtime service failed" err="rpc error: code = Unimplemented desc = unknown service runtime.v1alpha2.RuntimeService"
// workernode も綺麗にエラーが出ますね。master 同様に swap 無効化及び config.toml 削除+containerd 再起動をします

エラー対処後、再度 kubeadm join を実行します。

```bash
[root@worker1 ~]# kubeadm join 
-- snip --
This node has joined the cluster:
// このメッセージが表示されれば成功です

master 側で join 結果を確認します。

```bash
[root@master1 ~]# kubectl get nodes
NAME                    STATUS     ROLES           AGE    VERSION
master1.example.local   NotReady   control-plane   107m   v1.24.2
worker1.example.local   NotReady   <none>          89m    v1.24.2
// 期待値は Ready でした。
// あとで判明したのですが、coredns が pending だったので master も NotReady だったようです
```

各 node の状態を確認します。

```bash
[root@master1 ~]# kubectl describe node |grep  KubeletNotReady
  Ready            False   Wed, 29 Jun 2022 15:09:59 +0900   Wed, 29 Jun 2022 13:22:45 +0900   KubeletNotReady              container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized
  Ready            False   Wed, 29 Jun 2022 15:08:04 +0900   Wed, 29 Jun 2022 13:40:53 +0900   KubeletNotReady              container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized
// "cni plugin not initialized" と言っていますね。cni セットアップまで NotReady が正しいようです
```

cni をセットアップします。いくつかあるようですが、今回は flannel を採用します。
[flannel githubページ](https://github.com/flannel-io/flannel)を参照してインストールします。

```bash
[root@master1 ~]# kubectl apply -f https://raw.githubusercontent.com/flannel-Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created
// Manifest を apply するだけです
```

kubectl get pods で状態を確認します。

```bash
[root@master1 k8s]# kubectl get pods --all-namespaces
NAMESPACE     NAME                                            READY   STATUS    RESTARTS   AGE
kube-system   coredns-6d4b75cb6d-w6rxt                        1/1     Running   0          8m35s
kube-system   coredns-6d4b75cb6d-x4n2p                        1/1     Running   0          8m35s
kube-system   etcd-master1.example.local                      1/1     Running   5          8m50s
kube-system   kube-apiserver-master1.example.local            1/1     Running   5          8m50s
kube-system   kube-controller-manager-master1.example.local   1/1     Running   0          8m52s
kube-system   kube-flannel-ds-7bpfg                           1/1     Running   0          7m11s
kube-system   kube-flannel-ds-fc6wh                           1/1     Running   0          7m11s
kube-system   kube-proxy-2czrg                                1/1     Running   0          8m36s
kube-system   kube-proxy-7tpvl                                1/1     Running   0          8m30s
kube-system   kube-scheduler-master1.example.local            1/1     Running   5          8m50s
```

ここまでで k8s + flannel 構成が出来ました。
沢山検証ブログがあって助かりました。

# おわりに
検証環境が出来たので、kubernetes 完全ガイドに沿って手を動かしていきたいと思います。