🦅

Kubernetes The Hard Wayにトライする

2024/12/15に公開

Kuberenetes

Kubernetesとは、複数のコンピュータでコンテナをいい感じに動かしてくれるものです。Kubernetesの説明はいろんなサイトに書いてあるため、そちらを参照してください。公式サイトも参考になります。
https://kubernetes.io/docs/concepts/overview/

Kuberentes The Hard Way

Kubernetes The Hard Wayとは、kubeadmやkubesplayのような、クラスタ構築ツールに頼らず、コンテナランタイムや各コンポーネントを自分でインストールして、設定をし、Kubernetesクラスタを作成するという取り組みです。
本記事は、作業メモとして書いているため、実際に取り組む場合は、GitHubを参照してください。
https://github.com/kelseyhightower/kubernetes-the-hard-way

環境

今回は、研究室にあった3台のミニPCを使って、進めていきます。
それぞれの機器のスペックは以下の通りです。

ノード ホスト名 種類 CPU メモリ ストレージ OS カーネル
マスター NucBox7 NucBox7 4コア 16GB 512GB Ubuntu 24.04 6.8.0-49-generic
ワーカー bmax04 BMAX 2コア 6GB 64GB Ubuntu 24.04 6.8.0-49-generic
ワーカー bmax05 BMAX 2コア 6GB 64GB Ubuntu 24.04 6.8.0-49-generic

01: Prerequisites

必要スペックを満たしていることを確認します。

02: Set Up The Administration Host

クラスタを作成するホスト(=私のPC)はApple SiliconのMacBookを使用します。

まず、必要なコンポーネントをダウンロードします。
download.txtを参考に必要なコンポーネントを理解します。
Kubernetesで開発が進められているプロジェクトのバイナリはDownload Kubernetesに書いてあります。etcd,containerd, runc,cri-tool,cni-pluginsは独立したプロジェクトであるため、GitHubからバイナリを取得します。
MacBookはArmチップですが、Kubernetesのマスターノード、ワーカーノードはx86_64なので、amd64のものをダウンロードします。

$ wget https://dl.k8s.io/v1.31.3/bin/linux/amd64/kubectl
$ wget https://dl.k8s.io/v1.31.3/bin/linux/amd64/kube-apiserver
$ wget https://dl.k8s.io/v1.31.3/bin/linux/amd64/kube-controller-manager
$ wget https://dl.k8s.io/v1.31.3/bin/linux/amd64/kube-scheduler
$ wget https://dl.k8s.io/v1.31.3/bin/linux/amd64/kube-proxy
$ wget https://github.com/etcd-io/etcd/releases/download/v3.5.17/etcd-v3.5.17-linux-amd64.tar.gz
$ wget https://dl.k8s.io/v1.31.3/bin/linux/amd64/kubelet
$ wget https://github.com/containerd/containerd/releases/download/v2.0.0/containerd-2.0.0-linux-amd64.tar.gz
$ wget https://github.com/opencontainers/runc/releases/download/v1.2.2/runc.amd64
$ wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.31.1/crictl-v1.31.1-linux-amd64.tar.gz
$ wget https://github.com/containernetworking/plugins/releases/download/v1.6.1/cni-plugins-linux-amd64-v1.6.1.tgz

Macにもkubectlをインストールします。簡単なのでHomebrewで入れています。

$ brew install kubectl

03: Provisioning Compute Resources

Macからマスターノード・ワーカーノードにアクセスできるように、sshの設定を行います。
また、以下の記述をMac・マスターノード・ワーカーノードの /etc/hosts に追加します。

10.70.70.27 NucBox7.kubernetes.local NucBox7
10.70.70.225 bmax04.kubernetes.local bmax04
10.70.70.226 bmax05.kubernetes.local bmax05

04: Provisioning a CA and Generating TLS Certificates

Kubernetesコンポーネントに必要なTLS証明書を生成します。まず、自己署名によるCA証明書を作成し、そのCA証明書を使用して各コンポーネントのTLS証明書を署名します。
証明書については、以下のサイトが参考になります。
https://dev.classmethod.jp/articles/eetann-learn-mtls/

まず、CA(Certificate Authorities)の設定ファイルと認証局の秘密鍵とルート証明書を作成します。CSR(Certificate Signing Request)は、365日で申請しています。すでにca.confが用意されているため、一部書き換えて使います。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/ca.conf
$ vim ca.conf

$ openssl genrsa -out ca.key 4096

$ openssl req -x509 -new -sha512 -noenc \
    -key ca.key -days 365 \
    -config ca.conf \
    -out ca.crt
ca.confの書き換えた箇所
49,50c49,50
< [node-0]
< distinguished_name = node-0_distinguished_name
---
> [bmax04]
> distinguished_name = bmax04_distinguished_name
52c52
< req_extensions = node-0_req_extensions
---
> req_extensions = bmax04_req_extensions
54c54
< [node-0_req_extensions]
---
> [bmax04_req_extensions]
59,60c59,60
< nsComment = "Node-0 Certificate"
< subjectAltName = DNS:node-0, IP:127.0.0.1
---
> nsComment = "Bmax04 Certificate"
> subjectAltName = DNS:bmax04, IP:127.0.0.1
63,64c63,64
< [node-0_distinguished_name]
< CN = system:node:node-0
---
> [bmax04_distinguished_name]
> CN = system:node:bmax04
70,71c70,71
< [node-1]
< distinguished_name = node-1_distinguished_name
---
> [bmax05]
> distinguished_name = bmax05_distinguished_name
73c73
< req_extensions = node-1_req_extensions
---
> req_extensions = bmax05_req_extensions
75c75
< [node-1_req_extensions]
---
> [bmax05_req_extensions]
80,81c80,81
< nsComment = "Node-1 Certificate"
< subjectAltName = DNS:node-1, IP:127.0.0.1
---
> nsComment = "Bmax05 Certificate"
> subjectAltName = DNS:bmax05, IP:127.0.0.1
84,85c84,85
< [node-1_distinguished_name]
< CN = system:node:node-1
---
> [bmax05_distinguished_name]
> CN = system:node:bmax05
186c186
< DNS.5 = server.kubernetes.local
---
> DNS.5 = NucBox7.kubernetes.local

次に、kube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxyおよびadminユーザのを作成します。

$ certs=("admin" "bmax04" "bmax05" "kube-proxy" "kube-scheduler" "kube-controller-manager" "kube-api-server" "service-accounts")
$ echo ${certs[@]}
admin bmax04 bmax05 kube-proxy kube-scheduler kube-controller-manager kube-api-server service-accounts
$ for i in ${certs[*]}; do
  openssl genrsa -out "${i}.key" 4096 # 秘密鍵の作成

  openssl req -new -key "${i}.key" -sha256 \ # 証明書署名要求の作成
    -config "ca.conf" -section ${i} \
    -out "${i}.csr"
  
  openssl x509 -req -days 365 -in "${i}.csr" \ # 証明書の作成
    -copy_extensions copyall \
    -sha256 -CA "ca.crt" \
    -CAkey "ca.key" \
    -CAcreateserial \
    -out "${i}.crt"
done

ここまでで、各コンポーネントの秘密鍵、証明書署名要求、署名付きTLS証明書が作成できました。

最後に、各サーバに秘密鍵や証明書を配布します。
しかし、 /var/lib/ にファイルを配置するため、root権限が必要になります。そのため、ワーカーノードの設定を変更して、rootユーザでssh接続ができるようにします。
※ 設定が終わったら、rootユーザでのsshの許可は消すことを忘れないようにします。

$ sudo vim /etc/ssh/sshd_config
  #PermitRootLogin prohibit-password
  PermitRootLogin yes # 追記
$ sudo systemctl reload ssh.service
$ sudo passwd root # デフォルトではrootユーザのパスワードがないため設定
$ ssh root@bmax04 # rootでsshできるか確認

root権限でsshアクセスができるようになったため、ワーカーノードに秘密鍵や証明書を配布します。
/var/lib/ にkubeletディレクトリを作成し、{ホスト名}.crtをkubelet.crtとして、{ホスト名}.keyをkubelet.keyとして保存します。

$ for host in bmax04 bmax05; do
  ssh root@$host mkdir /var/lib/kubelet/
  scp ca.crt root@$host:/var/lib/kubelet/
  scp $host.crt \
    root@$host:/var/lib/kubelet/kubelet.crt
  scp $host.key \
    root@$host:/var/lib/kubelet/kubelet.key
done

マスターノードにも秘密鍵と証明書を配布します。

$ scp \
  ca.key ca.crt \
  kube-api-server.key kube-api-server.crt \
  service-accounts.key service-accounts.crt \
  kobayashi-s@NucBox7:~/

05: Generating Kubernetes Configuration Files for Authentication

kubeconfigを作成します。
まずは、ワーカーノードのkubeletが使用するConfigファイルを作成します。クラスター名は"lab-cluster"にし、APIサーバのドメインを"NucBox7.kubernetes.local"に変更して実行します。これもMacで行います。

$ for host in bmax04 bmax05; do
  kubectl config set-cluster lab-cluster \
    --certificate-authority=ca.crt \
    --embed-certs=true \
    --server=https://NucBox7.kubernetes.local:6443 \
    --kubeconfig=${host}.kubeconfig

  kubectl config set-credentials system:node:${host} \
    --client-certificate=${host}.crt \
    --client-key=${host}.key \
    --embed-certs=true \
    --kubeconfig=${host}.kubeconfig

  kubectl config set-context default \
    --cluster=lab-cluster \
    --user=system:node:${host} \
    --kubeconfig=${host}.kubeconfig
  kubectl config use-context default \
    --kubeconfig=${host}.kubeconfig
done

kube-proxyが使用するConfigファイルを作成します。

$ kubectl config set-cluster lab-cluster \
    --certificate-authority=ca.crt \
    --embed-certs=true \
    --server=https://NucBox7.kubernetes.local:6443 \
    --kubeconfig=kube-proxy.kubeconfig

$ kubectl config set-credentials system:kube-proxy \
    --client-certificate=kube-proxy.crt \
    --client-key=kube-proxy.key \
    --embed-certs=true \
    --kubeconfig=kube-proxy.kubeconfig

$ kubectl config set-context default \
    --cluster=lab-cluster \
    --user=system:kube-proxy \
    --kubeconfig=kube-proxy.kubeconfig

$ kubectl config use-context default \
    --kubeconfig=kube-proxy.kubeconfig

kube-controller-managerが使用するConfigファイルを作成します。

$ kubectl config set-cluster lab-cluster \
    --certificate-authority=ca.crt \
    --embed-certs=true \
    --server=https://NucBox7.kubernetes.local:6443 \
    --kubeconfig=kube-controller-manager.kubeconfig

$ kubectl config set-credentials system:kube-controller-manager \
    --client-certificate=kube-controller-manager.crt \
    --client-key=kube-controller-manager.key \
    --embed-certs=true \
    --kubeconfig=kube-controller-manager.kubeconfig

$ kubectl config set-context default \
    --cluster=lab-cluster \
    --user=system:kube-controller-manager \
    --kubeconfig=kube-controller-manager.kubeconfig

$ kubectl config use-context default \
    --kubeconfig=kube-controller-manager.kubeconfig

kube-schedulerが使用するConfigファイルを作成します。

$ kubectl config set-cluster lab-cluster \
    --certificate-authority=ca.crt \
    --embed-certs=true \
    --server=https://NucBox7.kubernetes.local:6443 \
    --kubeconfig=kube-scheduler.kubeconfig

$ kubectl config set-credentials system:kube-scheduler \
    --client-certificate=kube-scheduler.crt \
    --client-key=kube-scheduler.key \
    --embed-certs=true \
    --kubeconfig=kube-scheduler.kubeconfig

$ kubectl config set-context default \
    --cluster=lab-cluster \
    --user=system:kube-scheduler \
    --kubeconfig=kube-scheduler.kubeconfig

$ kubectl config use-context default \
    --kubeconfig=kube-scheduler.kubeconfig

adminユーザが使用するConfigファイルを作成します。

$ kubectl config set-cluster lab-cluster \
    --certificate-authority=ca.crt \
    --embed-certs=true \
    --server=https://127.0.0.1:6443 \
    --kubeconfig=admin.kubeconfig

$ kubectl config set-credentials admin \
    --client-certificate=admin.crt \
    --client-key=admin.key \
    --embed-certs=true \
    --kubeconfig=admin.kubeconfig

$ kubectl config set-context default \
    --cluster=lab-cluster \
    --user=admin \
    --kubeconfig=admin.kubeconfig

$ kubectl config use-context default \
    --kubeconfig=admin.kubeconfig

作成したkubeletkube-proxyのConfigファイルをワーカーノードに配布します。

$ for host in bmax04 bmax05; do
  ssh root@$host mkdir /var/lib/kube-proxy/
  scp kube-proxy.kubeconfig \
    root@$host:/var/lib/kube-proxy/kubeconfig
  scp ${host}.kubeconfig \
    root@$host:/var/lib/kubelet/kubeconfig
done

また、admin,kube-controller-manager,kube-schedulerのConfigファイルをマスターノードに配布します。

$ scp admin.kubeconfig \
  kube-controller-manager.kubeconfig \
  kube-scheduler.kubeconfig \
  kobayashi-s@NucBox7:~/

06: Generating the Data Encryption Config and Key

Kubernetesはクラスタデータを暗号化する機能をサポートしており、この機能を使うために暗号化のための鍵を作成します。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/configs/encryption-config.yaml
$ export ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
$ envsubst < encryption-config.yaml > encryption-config-after.yaml
$ mv encryption-config-after.yaml encryption-config.yaml # 上書き

暗号鍵を含むyamlファイルをマスターノードに配布します。

$ scp encryption-config.yaml kobayashi-s@NucBox7:~/

07: Bootstrapping the etcd Cluster

Kuberentesコンポーネントはステートレスであるため、クラスタ情報はetcdに保存します。
まずは、02: Set Up The Administration Hostでダウンロードしたetcdの圧縮ファイルをマスターノードにコピーします。

$ scp etcd-v3.5.17-linux-amd64.tar.gz kobayashi-s@NucBox7:~/

ここからは、マスターノードの中で操作をします。先ほどコピーしたetcdの圧縮ファイルを解凍して、バイナリを /usr/local/bin/ に配置し、 /etc/etcd に証明書や鍵を配置します。

$ ssh kobayashi-s@NucBox7
$ tar -xvf etcd-v3.5.17-linux-amd64.tar.gz
$ sudo mv etcd-v3.5.17-linux-amd64/etcd* /usr/local/bin/
$ sudo mkdir /etc/etcd /var/lib/etcd
$ sudo chmod 700 /var/lib/etcd
$ sudo cp ca.crt kube-api-server.key kube-api-server.crt /etc/etcd/

etcd.serviceが用意されていますが、x86用に一箇所変更します。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/units/etcd.service

$ vim etcd.service
  Environment="ETCD_UNSUPPORTED_ARCH=amd64" # arm64 → amd64
$ sudo mv etcd.service /etc/systemd/system/

etcdのserviceファイルが用意できたら、systemdで起動します。

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now etcd

$ systemctl status etcd # 確認
$ etcdctl member list # etcdクラスタのメンバー表示

08.Bootstrapping the Kubernetes Control Plane

コントロールプレーンを構築していきます。
まずは、マスターノードに必要なバイナリを転送します。

$ scp kube-apiserver kube-controller-manager \
    kube-scheduler kubectl \
    kobayashi-s@NucBox7:~/

これ以降の操作は、マスターノードにssh接続をしてから行います。

$ ssh kobayashi-s@NucBox7

serviceファイルと必要なyamlファイルをダウンロードします。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/units/kube-apiserver.service
$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/units/kube-controller-manager.service
$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/units/kube-scheduler.service

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/configs/kube-scheduler.yaml
$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/configs/kube-apiserver-to-kubelet.yaml

kube-apiserver,kube-controller-manager,kube-scheduler,kubectlのバイナリに実行権限を付与し、 /usr/local/bin/ に移動します。

$ sudo mkdir -p /etc/kubernetes/config
$ chmod +x kube-apiserver kube-controller-manager kube-scheduler kubectl
$ sudo mv kube-apiserver kube-controller-manager kube-scheduler kubectl /usr/local/bin/

kube-apiserverに関連する設定ファイルを移動します。

$ sudo mkdir -p /var/lib/kubernetes/
$ sudo mv ca.crt ca.key kube-api-server.key kube-api-server.crt \
    service-accounts.key service-accounts.crt encryption-config.yaml \
    /var/lib/kubernetes
$ sudo mv kube-apiserver.service /etc/systemd/system/kube-apiserver.service

kube-controller-managerに関連する設定ファイルを移動します。

$ sudo mv kube-controller-manager.kubeconfig /var/lib/kubernetes
$ sudo mv kube-controller-manager.service /etc/systemd/system/

kube-schedulerに関連する設定ファイルを移動します。

$ sudo mv kube-scheduler.kubeconfig /var/lib/kubernetes/
$ sudo mv kube-scheduler.yaml /etc/kubernetes/config/
$ sudo mv kube-scheduler.service /etc/systemd/system/

マスターノードでkube-apiserver,kube-controller-manager,kube-schedulerを起動します。マスターノードでkubectlコマンドを実行し、cluster-infoの出力結果からコントロールプレーンが構築できたことを確認します。

$ sudo systemctl enable --now kube-apiserver kube-controller-manager kube-scheduler
$ kubectl cluster-info --kubeconfig admin.kubeconfig
  Kubernetes control plane is running at https://127.0.0.1:6443

kube-apiserverが各ワーカーノードのkubeletにアクセスできるようにRBAC権限を付与します。
kube-apiserver-to-kubelet.yamlをデプロイし、ClusterRoleを作成して、ClusterRoleBindingでsystem:kube-apiserverに権限を割り当てます。

$ kubectl apply -f kube-apiserver-to-kubelet.yaml --kubeconfig admin.kubeconfig

09: Bootstrapping the Kubernetes Worker Nodes

ワーカーノードの構築をしていきます。
まずは、ワーカーノードに必要なバイナリを転送します。

$ for host in bmax04 bmax05; do
    scp runc.amd64 crictl-v1.31.1-linux-amd64.tar.gz \
    cni-plugins-linux-amd64-v1.6.1.tgz containerd-2.0.0-linux-amd64.tar.gz \
    kubectl kubelet kube-proxy \
    kobayashi-s@${host}:~/
done

これ以降の操作は、ワーカーノードにssh接続をしてから行います。記事内では、bmax04に対してしか行っていませんが、bmax05に対しても同じことを行います。

$ ssh kobayashi-s@bmax04

まず、10-briedge.yamlkubelet-config.yamlを取得して、SUBNETの箇所をPodネットワークに変更します。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/configs/10-bridge.conf
$ sed -i "s/SUBNET/10.200.0.0\/24/g" 10-bridge.conf

$ wget https://raw.githubusercontent.com/kelseyhightower/kuber
netes-the-hard-way/refs/heads/master/configs/kubelet-config.yaml
$ sed -i "s/SUBNET/10.200.0.0\/24/g" kubelet-config.yaml 

次に必要なツールをインストールします。

$ sudo apt update
$ sudo apt -y install socat conntrack ipset

swapが有効だとkubeletがコンテナの起動に失敗するため、swapを無効にします。

$ swapon --show
$ sudo swapoff -a

ディレクトリを作成し、バイナリに実行権限を付与して、適切な場所に配置していきます。

$ sudo mkdir -p \
  /etc/cni/net.d \
  /opt/cni/bin \
  /var/lib/kubelet \
  /var/lib/kube-proxy \
  /var/lib/kubernetes \
  /var/run/kubernetes

$ tar -xvf crictl-v1.31.1-linux-amd64.tar.gz
$ mkdir -p containerd
$ tar -xvf containerd-2.0.0-linux-amd64.tar.gz -C containerd/
$ sudo tar -xvf cni-plugins-linux-amd64-v1.6.1.tgz -C /opt/cni/bin/
$ mv runc.amd64 runc
$ chmod +x crictl kubectl kube-proxy kubelet runc

$ sudo mv crictl kubectl kube-proxy kubelet runc /usr/local/bin/
$ sudo mv containerd/bin/* /bin/

$ sudo apt -y remove socat conntrack ipset

CNIの設定をします。99-loopback.confをダウンロードして、/etc/cni/net.d/に配置します。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/configs/99-loopback.conf
$ sudo mv 10-bridge.conf 99-loopback.conf /etc/cni/net.d/

containerdの設定をします。
containerd-config.tomlcontainerd.serviceをダウンロードして、適切な場所に配置していきます。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/configs/containerd-config.toml
$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/units/containerd.service

$ sudo mkdir -p /etc/containerd/
$ sudo mv containerd-config.toml /etc/containerd/config.toml
$ sudo mv containerd.service /etc/systemd/system/

kubeletの設定をします。
kubelet.serviceをダウンロードして、適切な場所に配置します。

$ sudo mv kubelet-config.yaml /var/lib/kubelet/
$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/units/kubelet.service
$ sudo mv kubelet.service /etc/systemd/system/

kube-proxyの設定をします。
kube-proxy-config.yamlkube-proxy.serviceをダウンロードして、適切な場所に配置します。

$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/configs/kube-proxy-config.yaml
$ sudo mv kube-proxy-config.yaml /var/lib/kube-proxy/
$ wget https://raw.githubusercontent.com/kelseyhightower/kubernetes-the-hard-way/refs/heads/master/units/kube-proxy.service
$ sudo mv kube-proxy.service /etc/systemd/system/

containerd,kubelet,kube-proxyを起動します。

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now containerd kubelet kube-proxy

10: Configuring kubectl for Remote Access

Macでkubectlを実行するために必要なkubeconfigを用意します。

$ kubectl config set-cluster lab-cluster \
    --certificate-authority=ca.crt \
    --embed-certs=true \
    --server=https://NucBox7.kubernetes.local:6443

$ kubectl config set-credentials admin \
    --client-certificate=admin.crt \
    --client-key=admin.key

$ kubectl config set-context lab-cluster \
    --cluster=lab-cluster \
    --user=admin

Macでkubectlが使用できるようになりました。

$ kubectl version
  Client Version: v1.31.3
  Kustomize Version: v5.4.2
  Server Version: v1.31.3

11: Provisioning Pod Network Routes

Podを作成するとノードのPodCIDRからIPアドレスを割り当てられますが、異なるノードのPodとは通信できません。これは、異なるPodCIDRを認識していないために、ノード上では、デフォルトルートの方にトラフィックが向かってしまうためです。そのため、別のノードのPodCIDRに対する経路をノードに追加します。

# マスターノード
$ ssh root@NucBox7 "ip route add 10.200.1.0/24 via 10.70.70.226"
$ ssh root@NucBox7 "ip route add 10.200.0.0/24 via 10.70.70.225"

# ワーカーノード
$ ssh root@bmax04 "ip route add 10.200.1.0/24 via 10.70.70.226"
$ ssh root@bmax05 "ip route add 10.200.0.0/24 via 10.70.70.225"

ここまでで、Kubernetesクラスタを構築することができました。

12: Smoke Test

検証を行っていきます。
まずは、Secretが暗号化されていることを確認します。etcdctlでSecretを見てみると、AES-CBC方式で暗号化されていることが確認できました。

$ kubectl create secret generic test-secret \
  --from-literal="mykey=mydata"

$ ssh root@NucBox7 "etcdctl get /registry/secrets/default/test-secret | hexdump -C"

次に、Deploymentリソースを確認します。ノードを跨いだPod間通信も検証したいため、レプリカ数を2にして作成しました。厳密には、Node SelectorやNode Affinityを使う必要がありますが、2つのPodだけの場合、2つのノードにそれぞれPodが作られるため、今回は使っていません。
Podが作られているのを確認したのちに、ポートフォワードして、Podにアクセスします。

$ kubectl create deployment nginx --image=nginx:latest --replicas=2
$ kubectl get pods -o wide # Podの確認
  NAME                  READY STATUS  IP         NODE
  nginx-c95765fd4-5b7sx 1/1   Running 10.200.1.4 bmax05 
  nginx-c95765fd4-wtk2h 1/1   Running 10.200.0.4 bmax04

$ kubectl port-forward nginx-c95765fd4-wtk2h 8080:80
$ curl localhost:8080 # 別ターミナルで実行

$ kubectl logs nginx-c95765fd4-wtk2h

$ kubectl exec -it nginx-c95765fd4-wtk2h -- /bin/bash
  root@nginx-c95765fd4-wtk2h:/# apt update && apt install iproute2 iputils-ping -y
  root@nginx-c95765fd4-wtk2h:/# ping 10.200.1.4 # Pod間の疎通確認

最後に、Serviceリソースを確認します。NodePortタイプのServiceを作成し、アクセスします。

$ kubectl expose deployment nginx --port 80 --type NodePort

$ kubectl get svc
  nginx NodePort 80:30108/TCP
$ curl http://bmax04:30108

13: Cleaning Up

運用時の課題にも書いていますが、このクラスタは消すことにしました。そのため、バイナリや設定ファイルを削除して、構築前の状態に戻します。

# マスターノード
$ sudo systemctl stop kube-apiserver kube-controller-manager kube-scheduler etcd
$ sudo rm /etc/systemd/system/kube-apiserver.service \
  /etc/systemd/system/kube-controller-manager.service \
  /etc/systemd/system/kube-scheduler.service \
  /etc/systemd/system/etcd.service
$ sudo rm /usr/local/bin/kube-apiserver \
  /usr/local/bin/kube-controller-manager \
  /usr/local/bin/kube-scheduler \
  /usr/local/bin/etcd
$ sudo systemctl daemon-reload

$ sudo rm -rf /var/lib/kubernetes
$ sudo rm -rf /etc/kubernetes/config
$ sudo rm -rf /etc/etcd
$ sudo rm -rf /var/lib/etcd

# ワーカーノード
$ sudo systemctl stop containerd kubelet kube-proxy
$ sudo rm /etc/systemd/system/containerd.service \
  /etc/systemd/system/kubelet.service \
  /etc/systemd/system/kube-proxy.service
$ sudo rm /bin/containerd \
  /usr/local/bin/kubelet \
  /usr/local/bin/kube-proxy
$ sudo systemctl daemon-reload

$ sudo rm -rf /var/lib/kubelet
$ sudo rm -rf /var/lib/kube-proxy
$ sudo rm -rf /var/lib/containerd
$ sudo rm -rf /var/lib/kubernetes
$ sudo rm -rf /var/run/kubernetes
$ sudo rm -rf /etc/cni/net.d
$ sudo rm -rf /opt/cni/bin

$ sudo rm /usr/local/bin/runc \
  /usr/local/bin/kubectl \
  /usr/local/bin/crictl

# Mac
$ kubectl config delete-cluster lab-cluster
$ kubectl config unset users.admin
$ kubectl config delete-context lab-cluster
$ kubectl config view # 消されているか確認

つまったところ

いくつか詰まった点を記録しておきます。基本的には、各コンポーネントのログを確認し、原因を分析します。特に、journalctlコマンドを使用して多くのログを確認しました。ログを見ただけでは原因や解決方法がわからない場合でも、LLMにそのままログを入力すると、意外と解決策や次にどのログをみるべきかを教えてくれます。

kube-api-serverにアクセスできない

ca.confで書いてることを理解できておらず、kubeletやkubectlからkube-api-serverにTLS通信をしようとしても失敗しました。ca.confの[kube-api-server_alt_names]の変更を忘れており、SAN(Subject Alternative Name)の設定ミスでクライアント側でのサーバー検証でエラーが発生していました。ca.confを適切に変更することで解決しました。

kubeletが起動しない

kubeletのバイナリの所有者が一般ユーザであると起動に失敗します。root権限が必要とのことだったので、kubeletの所有者をrootに変更することで解決しました。

片方のワーカーノードがNot Ready

まず、詳細情報を確認し、cni pluginが初期化されていないことがわかりました。次に、ワーカーノードでjournalctlでcontainerdのログを見てみると、No cni config templateとなっており、crictlでコンテナランタイムの情報を見てみると、"lastCNILoadStatus"でパースエラーになっていました。そのため、 /etc/cni/net.d/10-bridge.conf を修正して、kubelet,kube-proxy,containerdをリスタートして解決しました。

$ kubectl descrive node bmax04
  Ready False KubeletNotReady container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized

$ ssh root@bmax04 "journalctl -u containerd -r | head"
  bmax04 containerd: msg="No cni config template is specified, wait for other system components to drop the config."

$ ssh root@bmax04 crictl info
  "lastCNILoadStatus": "cni config load failed: failed to load CNI config file /etc/cni/net.d/10-bridge.conf: error parsing configuration: invalid character ':' after array element: invalid cni config: failed to load cni config"

$ sudo vim /etc/cni/net.d/10-bridge.conf # 修正
$ sudo systemctl restart kubelet kube-proxy containerd # デーモンの再起動

ワーカーノードを再起動するとkubeletが動かない

systemctlでkubeletをenableにしているにもかかわらず、ノードを再起動するとkubeletがうまく起動せずに停止しました。journalctlでログを見てみると、swapが問題であることが確認できました。swapoffコマンドでswapを削除していたために、再起動するとswapが作られていたのです。そのため、再起動したらswapoffでswapを削除してから、kubeletをリスタートするか、/etc/fstabのswap.imgを削除することで解決しました。

$ systemctl status kubelet

$ sudo swapoff -a
$ sudo systemctl restart kubelet

Podから外部に通信できない

Podの中で、apt updateを実行しても外に繋がりませんでした。Pod内で、 /etc/resolf.conf を見てみるとnameserverがおかしなIPアドレスになっていました。このIPアドレスはkubeletのconfigファイルに書いているIPアドレスであったため、ワーカーノードで /var/lib/kubelet/kubelet-config.yamlclusterDNSをノードが使用しているDNSサーバのIPアドレス(私の環境では10.70.70.254)に変更して解決しました。

root@nginx-c95765fd4-dnn2l:/# cat /etc/resolv.conf
  nameserver 10.32.0.10

$ ssh kobayashi-s@bmax04
$ vim /var/lib/kubelet/kubelet-config.yaml
  clusterDNS:
    - "10.70.70.254"
$ sudo systemctl restart kubelet

運用時の課題

当初、このクラスタは研究室で運用することを想定して構築を進めていました。しかし、kubernetes-the-hard-wayを進めていく中で、アップデートやノードのスケール時にかかるコストが高いことに気づきました。特に、APTのようなパッケージ管理ツールを使用せず、バイナリを手動でアップデートするのは非常に手間がかかります。また、ワーカーノードを増やす際には、証明書の発行やノード側のルーティングテーブルの手動設定が必要になることも課題でした。
さらに、kube-controller-managerkube-api-serversystemdで起動しているため、kubectlでこれらを管理することができません。kubectlで管理するには、これらのコンポーネントをStatic Podとして起動する必要があります。
これらの理由から、kubeadm を使用してクラスタを再構築することに決めました。

感想

Kubernetesクラスタをゼロから構築してみたことで、少しですがKubernetesの理解が深まったように感じます。また、構築中にいくつかトラブルが発生しましたが、そのたびにログを確認し、原因を特定して解決するという貴重な経験を積むことができました。
Kubernetes構築ツールが内部でどのような処理を行っているのかを知りたい方には、一度手動で構築してみることをおすすめします。

Discussion