学習用Kubernetes環境を作る1 Ubuntu 20.04 LTS+kubeadm+containerd+CalicoなKubernetes環境をHyper-V上にTailscaleの固定IPで構築

## はじめに

**注意:今回作る環境は,初心者が学習用に作成したものです.**
**決して本番環境等,外部に公開するような重要な環境ではそのままの手順通りで行わないで下さい.**
**また,insecureな設定やベストプラクティスから外れた運用をしている箇所があります.**

学習用Kubernetes環境を作るの第1回です.

Part 1 [学習用Kubernetes環境を作る1 Ubuntu 20.04 LTS+kubeadm+containerd+CalicoなKubernetes環境をHyper-V上にTailscaleの固定IPで構築](https://qiita.com/Hyper-W/items/3b1dd5523eed42786fe8)

Part 2 [学習用Kubernetes環境を作る2 動的PVが使えない環境でKubernetes管理WebUIのPortainer環境を作る/データ永続化/NodePortについて](https://qiita.com/Hyper-W/items/e3b81da2bfc1a82c3c3f)

Part 3 [学習用Kubernetes環境を作る3 Kubernetes上にKubernetes用プライベートDockerレジストリHarbor環境を作る/httpなDockerレジストリをDocker・containerdで使う](https://qiita.com/Hyper-W/items/5b36a03eae172e53109f)

今回は,Hyper-V上にインストールしたUbuntu 20.04 LTS(CUI環境)上に,Kubernetesのシングルノードを作成する.

Kubernetes環境の構築ツールとしてkubeadmを,CRI(Container Runtime Interface)としてDockerのコア部分であるcontainerdを,CNI(Container Network Interface)を採用したKubernetes環境を構築する.

また,KubernetesのパッケージマネージャーとしてHelmを利用する.

KubernetesノードのNICやIPはP2PなWireguardを利用したVPNであるTailscaleの固定IPを利用して構築する(Hyper-V外部からは,TailscaleのIPアドレスやmagicDNSを利用して接続してください).

## 構成

物理PC

- Intel Core i5-8400
- RAM 8GB (少ないので本当は最低でも16GBは欲しい)
- Windows 10 Pro(21H1)

仮想マシン

- CPU 6コア割り当て
- RAM 5GB(起動メモリ,最大8GB)
- ネットワークDefault Switch(Tailscaleの固定IPやmagicDNSで構築)
- Ubuntu 20.04 LTS(GUIは無効)

```bash
lscpu
アーキテクチャ:                      x86_64
CPU 操作モード:                      32-bit, 64-bit
バイト順序:                          Little Endian
Address sizes:                       39 bits physical, 48 bits virtual
CPU:                                 6
オンラインになっている CPU のリスト: 0-5
コアあたりのスレッド数:              1
ソケットあたりのコア数:              6
ソケット数:                          1
NUMA ノード数:                       1
ベンダー ID:                         GenuineIntel
CPU ファミリー:                      6
モデル:                              158
モデル名:                            Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
ステッピング:                        10
CPU MHz:                             2808.009
BogoMIPS:                            5616.01
ハイパーバイザのベンダー:            Microsoft
仮想化タイプ:                        完全仮想化
L1d キャッシュ:                      192 KiB
L1i キャッシュ:                      192 KiB
L2 キャッシュ:                       1.5 MiB
L3 キャッシュ:                       9 MiB
NUMA ノード 0 CPU:                   0-5
Vulnerability Itlb multihit:         KVM: Mitigation: VMX unsupported
Vulnerability L1tf:                  Mitigation; PTE Inversion
Vulnerability Mds:                   Mitigation; Clear CPU buffers; SMT Host state unknown
Vulnerability Meltdown:              Mitigation; PTI
Vulnerability Spec store bypass:     Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Vulnerability Spectre v1:            Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2:            Mitigation; Full generic retpoline, IBPB conditional, IBRS_FW, STIBP disabled, RSB
                                     filling
Vulnerability Srbds:                 Unknown: Dependent on hypervisor status
Vulnerability Tsx async abort:       Not affected
フラグ:                              fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx
                                      fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopol
                                     ogy cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt aes xsave av
                                     x f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti ssbd ibrs ibp
                                     b stibp fsgsbase bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap clflushopt xsaveo
                                     pt xsavec xgetbv1 xsaves md_clear flush_l1d arch_capabilities
```

```bash
cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 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
```

```bash
uname -r
5.8.0-1040-azure
```

## OSのアップグレード/curlのインストールを行う

まず,以下のコマンドのように,環境のアップグレード及び,(一応)再起動を行う.

```bash
sudo apt update

sudo apt -y upgrade

sudo apt -y autoremove

sudo apt -y autoclean

sudo reboot
```

必要なファイルをダウンロードするため,curlをインストールする.

```bash
sudo apt install -y curl
```

## Tailscaleのセットアップ

Tailscaleのセットアップを行う.

Tailscaleをインストールし,magicDNSを有効化し,認証キーを使い認証し,PodのIPとCLUSTER-IPのサブネットを公開します.

### Tailscaleをインストール

Tailscaleのインストールを行う.

以下のURLを参考にする.

https://tailscale.com/kb/1039/install-ubuntu-2004/

以下のようなコマンドでTailscaleをaptのリポジトリに追加し,インストールします.

```bash
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | sudo apt-key add -

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | sudo tee /etc/apt/sources.list.d/tailscale.list

sudo apt update

sudo apt install -y tailscale
```

### Tailscaleアカウントを作成

以下のURLから,Get Startedを押し,アカウントを作成します.

https://tailscale.com/

アカウントは,Google,Microsoft,Githubのアカウントのいずれかから作成出来ます.

### magicDNSを有効化

以下のURLからmagicDNSを有効化します.

https://login.tailscale.com/admin/dns

DNSサーバーには,8.8.8.8(Google Public DNS)等,好きなDNSサーバーを指定して有効化して下さい.

### 認証キーを生成

以下のURLから,One-off keyを選択し,Generate One-off keyを押し,認証キーを生成します.

https://login.tailscale.com/admin/settings/authkeys

生成したら,生成された認証キーの横のCopyをクリックし,クリップボードに保存します(認証するまでなくさないで下さい).

クリップボードにコピー出来たことを確認したら,Doneをクリックします.

One-off keyなので認証すれば,[Tailscaleの認証キーの管理画面](https://login.tailscale.com/admin/settings/authkeys)から消えますが,キーを生成し直す場合等,対象のキーのRevokeを押して削除する事もできます.

### Tailscaleを起動

以下のURLを参考に,サブネットをTailscaleに転送出来るようにします.

https://tailscale.com/kb/1104/enable-ip-forwarding/

以下のようなコマンドを実行します.

```bash
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf

echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf

sudo sysctl -p /etc/sysctl.conf
```

以下のコマンドで,生成された認証キーを利用し,Tailscaleを起動・認証し,公開するサブネットを設定します.

```bash
sudo tailscale up --authkey 認証キー --advertise-routes=10.96.0.0/12,192.168.0.0/16
```

192.168.0.0/16は,今回採用したCNIのCalicoデフォルトのPodのIPのサブネットで,10.96.0.0/12は,デフォルトのCLUSTER-IPのサブネットです.

### Tailscaleの管理画面でサーバーのサブネットを公開するように設定

以下のURLから,Tailscaleを起動したサーバーをクリックします.

https://login.tailscale.com/admin/machines

SubnetsにあるReviewボタンを押し,公開したサブネットをEnableにして有効化します.

これで,サーバーに繋ぐクライアントにもTailscaleをインストール・ログインすれば,Tailscaleを起動している間は,サーバーのホスト名(とTailscaleの固定IP)でサーバーに接続したり,PodのIPや,CLUSTER-IPで直接接続出来るようになります.

## Kubernetesの環境構築

Tailscaleのセットアップが終われば,Kubernetesの環境を構築します.

### containerd(CRI)をインストール

今回は,コンテナランタイムとして,Docker Engineのコア部分であるContainerdを利用する.

多くのkubeadmを利用したKubernetesの環境構築記事では,Dockerをインストールし,kubeadmのデフォルトCRIがDockerだからか,CRIとしてDockerを利用しているが,現在ではKubernetesのCRIとしてDockerを利用することは**非推奨**となっているので,これを使う.

今回は,Canonicalが管理しているUbuntuのデフォルトのリポジトリにあるcontainerdを利用する.

以下のコマンドのようにしてインストールする.

同じ環境でDockerを既に利用している/利用する場合は,既にインストールされているはずなので,この手順は不要です.

また,最新版のcontainerdやDockerを利用したい場合,または,x64とArm64のマルチアーキテクチャイメージを,この環境のDockerを利用してビルドしたいと考えてる場合,マルチアーキテクチャイメージをビルドするためのbuildxがバンドルされている[Docker公式のインストール手順](https://docs.docker.com/engine/install/ubuntu/)に従い,docker-ceをインストールすることをおすすめする(Canonical版はbuilxのバイナリーを手動で直接配置する必要があります).

尚,どちらにせよcontainerdはインストールされるので,以降の手順は同一です.

```bash
sudo apt install -y containerd
```

次に,containerdをKubernetesで利用できるように設定する.

```bash
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF

sudo modprobe overlay

sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system

sudo mkdir -p /etc/containerd

sudo containerd config default | sudo tee /etc/containerd/config.toml
```

設定が完了すれば,Systemdを利用して,Containerdを自動起動出来るようにし,containerdを起動する.

```bash
sudo systemctl enable containerd

sudo systemctl start containerd
```

### Swapを無効

kubeletの正常な稼働のためには,swapを無効化する必要があります.

以下の様なコマンドを利用し,swapを一時的に無効にする.

```bash
sudo swapoff -a
```

これでは,再起動後,swapが有効になってしまうのでfstabを編集する.

nanoで編集するためのコマンドは以下の通りです.

```bash
sudo nano /etc/fstab
```

/etc/fstabを開き,swapという文字が含まれる行の行頭に#を入れ,以下のようにコメントアウトすれば良い.

```bash
#/swapfile                                 none            swap    sw              0       0
```

行頭は/swapfileではなく,UUIDで始まる場合もありますが,swapと書かれている行をコメントアウトすれば良い.

### kubeadmやkubectl等,Kubernetesのコマンド,Helmをインストール

まずは,リポジトリを追加する.

```bash
# add repo kubernetes commands
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"

# add repo helm
curl https://helm.baltorepo.com/organization/signing.asc | sudo apt-key add -

sudo apt install -y apt-transport-https

echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
```

リポジトリを更新し,各コマンドをインストールする.

```bash
sudo apt update

sudo apt install -y kubeadm kubelet kubectl helm
```

### Kubernetesをセットアップ

今回は,固定IPはTailscaleのものを利用する.
以下の様なコマンドでTailscaleに割り当てられた固定IPアドレスを確認する.

```bash
ip a show dev tailscale0
```

このIPアドレスを利用して以下の様にしてKubernetes環境を作成する.

```bash
sudo kubeadm init --control-plane-endpoint=Tailscaleの固定IP:6443  --apiserver-advertise-address=Tailscaleの固定IP --pod-network-cidr=192.168.0.0/16 --cri-socket=/run/containerd/containerd.sock
```

今回はCNIとしてCalicoを利用するため,--pod-network-cidr=192.168.0.0/16としている.

また,今回はシングルノードであるため触れませんが,init時に出てくるjoinコマンドを利用すれば,(Tailscaleでつながるマシンからであれば)クラスタを構築出来る.

init時に,表示されたようにkubectlコマンドを利用出来るようにするために以下のコマンドで設定ファイルを移動させる.

```bash
mkdir -p $HOME/.kube

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

sudo chown $(id -u):$(id -g) $HOME/.kube/config
```

リモートで,このKubernetes環境を管理したい場合,kubectlコマンドを,そのマシンにインストールした後,上記と同じように,ホームディレクトリ(Windowsなら%USERPROFILE%)以下に.kubeというフォルダを作り,上記のconfigファイルをコピーすれば,対象マシンにTailscaleで接続可能であれば,管理出来るようになる.

その場合,Kubernetesのエンドポイントポート(6443)を以下の様なコマンドで許可しておく必要があります.

```bash
sudo ufw allow 6443
```

また,ローカルに戻り,rootでも実行出来るようにするためには,rootの.bashrcに以下の様なコマンドで追記する(再構築した場合手動で,削除していない場合,このコマンドの再実行は不要).

```bash
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" | sudo tee -a /root/.bashrc
```

TailscaleのIPを利用するようにkubeletの設定を変更する.

nanoで編集するためのコマンドは以下の通りです.

```bash
sudo nano /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
```

nanoで開いた後,以下のように変更する.

```bash
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml --node-ip=Tailescaleの固定IP"
```

以下の様なコマンドでサービスファイルの変更を適用し,サービスを再起動させる.

```bash
sudo systemctl daemon-reload

sudo systemctl restart kubelet
```

### Calico(CNI)をインストール

今回は,コンテナネットワークとして,Calicoを利用する.

以下の様なコマンドでCalicoをインストールすることが可能です.

```bash
helm repo add projectcalico https://docs.projectcalico.org/charts #add Helm repo Calico

helm install calico projectcalico/tigera-operator #Helm install Calico
```

### Podをデプロイ出来るようにスケジューリングの設定を変更

デフォルト状態では,taintにより,Podのデプロイが無効になっているので,デプロイ出来るようにする.

以下の様なコマンドを複数回実行して,

```bash
kubectl taint nodes --all node-role.kubernetes.io/master-
```

以下の様なエラーが出るようになれば良い.

```bash
error: taint "node-role.kubernetes.io/master" not found
```

### schedulerのステータスがUnhealthyなのを解消

このままの状態だと,schedulerのステータスがUnhealthyなことが以下のコマンドでわかる.

```bash
kubectl get cs scheduler
```

そのため,設定を変更する.

nanoで編集する場合は,以下のコマンドです.

```bash
sudo nano /etc/kubernetes/manifests/kube-scheduler.yaml
```

\- --port=0となっている行を,コメントアウトまたは,削除する.

そして,以下のようなコマンドでkubeletを再起動する.

```bash
sudo systemctl restart kubelet
```

### metrics-serverをインストールして,リソースを監視

まずは,Helmにリポジトリを追加する.

```bash
helm repo add bitnami https://charts.bitnami.com/bitnami
```

以下のコマンドでインストールする.

```bash
helm install -n kube-system metrics-server bitnami/metrics-server --set apiService.create=true \
  --set hostNetwork=true --set extraArgs.metric-resolution=15s \
  --set extraArgs.kubelet-insecure-tls=true --set extraArgs.kubelet-preferred-address-types=InternalIP
```

以下のようなコマンドで,

```bash
kubectl -n kube-system get deployment metrics-server
```

以下のように,動作していることを確認し,

```bash
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
metrics-server   1/1     1            1           2d4h
```

以下のようなコマンドで,

```bash
kubectl top node
```

以下のように,ノードのリソース消費が表示されれば問題ない.

```bash
NAME             CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
ノード名(手順どおりならホスト名小文字)   707m         11%    2827Mi          58%
```

### kubectl editで起動するエディターを設定

環境変数KUBE_EDITORに,指定したいエディター名を入れれば良い.

毎回同じエディターで起動したいのなら,以下のようなコマンドを実行すれば良い.

```bash
echo "export KUBE_EDITOR=nano" >> ~/.bashrc

echo "export KUBE_EDITOR=nano" | sudo tee -a /root/.bashrc

source ~/.bashrc
```

これは,エディターとしてnanoを選んだ場合の例です.

rootで実行する可能性がない場合,2つ目のコマンドは不要です.

VSCodeで起動する場合(Remote-SSH含む),=nanoを,='code --wait'に変更して下さい.

1つ目のコマンドの場合,以下のようにします.

```bash
echo "export KUBE_EDITOR='code --wait'" >> ~/.bashrc
```

起動オプションとして,--waitを指定しなかった場合,開かずに終了してしまいます.

VSCodeは,基本的にrootで起動出来ないようになっているし,rootで起動すべきではないので,nano等のエディターを使って下さい.

### Kubernetesがしっかり設定されているかを確認

以下の様なコマンドを実行し,

```bash
kubectl get node -o wide
```

以下の様な出力結果になっていれば良い.

```bash
NAME             STATUS   ROLES                  AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
ノード名(手順どおりならホスト名小文字)   Ready    control-plane,master   2d3h   v1.22.1   Tailscaleの固定IP   <none>        Ubuntu 20.04.3 LTS   5.8.0-1040-azure   containerd://1.5.2
```

特に,設定したTailscaleの固定IPアドレスになっていること,コンテナランタイムがcontainerdになっていることは重要です(dockerdになっていないか).

ここまで確認すれば,(Windowsホストごと)一度再起動する.

しっかり,設定できていれば,仮想マシンを再起動してもswapは無効化されているのでkubeletは問題なく動作するはずだし,Windowsを再起動してDefault Switchで割り当てられたIPアドレスが変化しても問題なく,利用出来るようになっているはずです.

上記のコマンドをもう一度実行して特に問題がなければ良い.

もし,エラーが出た場合は,まずswapを確認して

```bash
sudo swapoff -a
```

で,もう一度無効にして,しばらくして再度上記コマンドを実行して,特に問題なければ,swapの無効化ができていないので[swapを無効](#swapを無効)をもう一度確認する.

それでも,解決しない場合は,TailscaleのIPアドレスの問題であるため,[この手順で](#kubernetes環境をリセット)環境をリセットし,再度,[Kubernetesをセットアップ](#kubernetesをセットアップ)からやり直してください.

## 利用していないイメージを削除

コンテナがアップグレードされると,旧バージョンのイメージが残ってしまう.

以下のコマンドで利用していないイメージを全て削除することが出来る.

```bash
sudo crictl rmi --prune
```

## Kubernetes環境をアップグレード

まずは,KubernetesのコマンドやCRIをアップグレードするために,以下のコマンドを実行.

```bash
sudo apt update

sudo apt -y upgrade

sudo apt -y autoremove

sudo apt -y autoclean
```

次にPodを退避する.

```bash
kubectl drain ノード名(手順どおりならホスト名小文字)
```

エラーが出た場合は,その対象のPodを一旦削除し,再度実行する.

アップグレード出来るバージョンを確認する.

```bash
sudo kubeadm upgrade plan
```

アップグレード出来るバージョンを確認したら,そのバージョンを指定してアップグレードする.

指定するバージョンが,v1.22.1の場合,以下のようなコマンドとなる.

```bash
sudo kubeadm upgrade apply v1.22.1
```

退避させたPodを元に戻す.

```bash
kubectl uncordon ノード名(手順どおりならホスト名小文字)
```

もし,退避時にエラーが出ていて一旦Podを削除した場合は,再度デプロイし直す.

## Kubernetes環境をリセット

構築に失敗した場合,途中で誤った場合はリセットする必要がある場合があります.

その場合,以下の様なコマンドを実行し,

```bash
sudo kubeadm reset
```

以下の様に再起動すれば良い.

```bash
sudo reboot
```

再起動後,以下の様なコマンドで,cniのネットワークが削除されているかを確認する.

```bash
ip a
```

上記のコマンドで,caliから始まるネットワークとtunl0@NONEのネットワークが削除されていれば,正常にリセットされている.

上記を実行後[Kubernetesをセットアップ](#kubernetesをセットアップ)からやり直すと良い.

## 感想

途中(helmの手順を追加した時)まで,出来るだけプレーンな環境で,理想的な環境を作りたいと考えていたので,kubeadmとcontainerdを採用したが,おかげで手順が多くなってしまった(第2回以降も).

単純にKubernetes環境が欲しいだけなら,素直にminikubeやmicrok8s,Docker Desktop等を使ったほうが多分楽です(metrics-serverとかはそのままで動かなかったものとかも多い).

後,Kubernetesのバックエンドをcontainerdにしたら,Dockerで使っていた時より,なぜか重い気がするのと,Ubuntuのシャットダウンや再起動の時間がかなり長くなってしまった.

## Next

[次](https://qiita.com/Hyper-W/items/e3b81da2bfc1a82c3c3f)