KubeadmとCalicoでKubernetes環境を構築してみた

# はじめに

今回は，ESXi上にUbuntuのVMを建ててKubernetes (K8s) 環境の構築をしてみました．
クラスター構築ツールにはkubeadm，CNIプラグインにはCalico，CRIにはDockerを利用しました．
詳細は下記のリストを参照してください．

- マシンスペック:
    - CPU: 2 vCPU
    - RAM: 2 GB
    - OS: Ubuntu 20.04.2 LTS
- Docker: 20.10.7
- kubelet: 1.21.2
- kubeadm: 1.21.2
- kubectl: 1.21.2 
- calicoctl: 3.19.1

K8sのNodeになる各マシンにUbuntuのインストールまでが完了している状態から開始します．この時点で，すべてのマシンに対してDDNSによってドメイン名が割り当てられています．

基本的には，[Kubernetesの公式ドキュメント](https://kubernetes.io/ja/docs/home/)に従って進めていきます．

# kubeadm, kubelet, kubectl のインストール

kubeadmを導入しないことには，K8sクラスターの構築は始まりません．[kubeadmのインストール](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)のページのとおりにインストールを進めます．

## 要件

「始まる前に」の節にホストマシンの満たすべき要件が記されています．ドキュメントでは確認方法がほどんど省かれているので細かめに書いておきます．

### OS

今のところ，OS（というよりディストリビューション）は下記リストのいずれかである必要があるようです．

- Ubuntu 16.04+
- Debian 9+
- CentOS 7
- Red Hat Enterprise Linux (RHEL) 7
- Fedora 25+
- HypriotOS v1.0.1+
- Container Linux (tested with 1800.6.0)

Ubuntuであれば，

```bash
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
```

のようにして確認できます．

### CPU・メモリ

CPUのコア数は， `/proc/cpuinfo` にコア毎の情報が書かれているので，それを利用して `processor` の項目がいくつあるかを数えることで求められます．
メモリ容量は `free` コマンドで確認できます．

```bash
$ fgrep 'processor' /proc/cpuinfo | wc -l
2

$ free -m
              total        used        free      shared  buff/cache   available
Mem:           1987         508         258           2        1221        1305
Swap:             0           0           0
```

### マシン間の疎通確認

pingをうてばよかろうなのだ．xxxには疎通確認したいマシンのhostnameが入ります．

```bash
$ ping xxx
PING xxx (192.168.100.88) 56(84) bytes of data.
64 bytes from 192.168.100.88 (192.168.100.88): icmp_seq=1 ttl=64 time=0.166 ms
64 bytes from 192.168.100.88 (192.168.100.88): icmp_seq=2 ttl=64 time=0.227 ms
64 bytes from 192.168.100.88 (192.168.100.88): icmp_seq=3 ttl=64 time=0.170 ms
64 bytes from 192.168.100.88 (192.168.100.88): icmp_seq=4 ttl=64 time=0.165 ms
^C
--- xxx ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3043ms
rtt min/avg/max/mdev = 0.165/0.182/0.227/0.026 ms
```

### ユニークなhostname，MACアドレス，product_uuid

hostnameは `hostname` コマンド，MACアドレスは，`ip link` コマンド，product_uuid は `sudo cat /sys/class/dmi/id/product_uuid`コマンドで確認できます．

自分は，ローカルのエディタに張り付けて目視で重複のないことを確認しました．マシンの数が多くなれば別の方法を考えたほうが良いかもしれません．

### ポート開放

今回はファイアウォールがOFFなので省略✌．

### SwapがOFF

デフォルトだとONになっていると思います．`swapoff -a`コマンドでOFFにします．サーバ再起動後もSwapをOFF状態にしておくために，`/etc/fstab` の`swap.img`の行をコメントアウトしておきます．

## それ以降

これ以降のセットアップは，全部のコマンドが貼り付けられたシェルスクリプトを用意して，管理者権限で実行すると楽なのでおすすめです．
ひとまず，1つのマシンでだけ１つづつ進めていって動作の確認をしておきましょう．

iptablesの設定をして，Dockerのセットアップ，kube*のインストールをするまでの全部入りの雑スクリプト（ただのコピペ）を置いておきます．

```bash
# iptables関連の設定
modprobe br_netfilter
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
sudo apt-get install -y iptables arptables ebtables
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy

# Dockerのインストール
apt-get update && apt-get install -y \
  apt-transport-https ca-certificates curl software-properties-common gnupg2
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"
apt-get update && apt-get install -y \
containerd.io=1.2.13-2 \
docker-ce=5:19.03.11~3-0~ubuntu-$(lsb_release -cs) \
docker-ce-cli=5:19.03.11~3-0~ubuntu-$(lsb_release -cs)
cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
systemctl daemon-reload
systemctl restart docker
systemctl enable docker

# kubelet kubeadm kubectl のインストール
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
```

# kubeadmを利用したクラスターの作成とCalicoのインストール

[K8sのクラスター作成のドキュメント](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/)と，[Calicoのドキュメント](https://docs.projectcalico.org/getting-started/kubernetes/quickstart)の両方を参照しながら進めます．

kubeadmはもうインストールされているので，あとは

1. コントロールプレーンノードの初期化
1. ワーカーノードの追加

の手順を踏めばよいです．いずれも実行するコマンド自体は多くありません．

### コントロールプレーンノードの初期化

`kubeadm init`コマンドを利用しますが，その際に利用するCNIプラグイン次第でオプション引数をかえる必要があるようです．
Calicoのチュートリアルの方を参照すると，

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

で初期化をしているので，これに倣ってコントロールプレーンノードの初期化をします．

最後に`kubeadm join 192.168.100.81:6443 --token 5b6zo4.cqje07byosk6wjmb --discovery-token-ca-cert-hash sha256:686401e05de6d479482d8a7b5176d6d28c78a783702e849d1d54507317849e6a` のようなコマンドが表示されるので，メモしておきましょう．ワーカーノードをクラスターに追加させる際に利用するコマンドです．

無事完了したら，kubectl がroot以外でも利用できるように `~/.kube`ディレクトリ以下を作成します．

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

### CNIプラグイン（Calico）のインストール

Calicoのインストールは，インターネット上で公開されているyamlをコントロールプレーンノードの`kubectl create`コマンドに渡すだけです．

```bash 
kubectl create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml
kubectl create -f https://docs.projectcalico.org/manifests/custom-resources.yaml
```

### ワーカーノードの追加

先ほど`kubeadm init`した際に発行された，`kubeadm join`コマンドを各ワーカーノードで実行すれば完了です．

完了したら，コントロールプレーンで`kubectl get nodes`コマンドを実行して，ワーカーノードの情報を確認しましょう．
STATUSの項目がReadyになっていれば，これでKubernetesクラスターの完成です．

下の例は，作成後しばらくしてから再度実行したものなので，AGEがすこし大きくなっています．

```bash
$ kubectl get nodes
NAME       STATUS   ROLES                  AGE     VERSION
hoge0   Ready    control-plane,master   7d13h   v1.21.2
hoge1   Ready    <none>                 7d13h   v1.21.2
hoge2   Ready    <none>                 7d13h   v1.21.2
hoge3   Ready    <none>                 5d7h    v1.21.3
hoge4   Ready    <none>                 5d7h    v1.21.3
hoge5   Ready    <none>                 5d7h    v1.21.3
```