ジョブカン事業部のアドベントカレンダー20日目です。
ジョブカン採用管理で開発を担当しています、@maeda3net です。
自宅でKubernetes(以下、k8s)環境を構築してみた記事になります。
基本的には公式の手順に則る形にはなりますが、私の求めた構成に合わせていくつかの変更を加えています。
つまずいた点なども記載しているので、これからk8sを構築しようとしている方の参考になれば幸いです。
背景
ジョブカン採用管理では基盤としてk8sを利用しています。
日々の運用において決まった手順の実行などを行なっていますが、私自身k8sへの深い理解があるわけではなく即応性にかけるなと感じることが多々ありました。
気軽に試せる環境があれば、もっとk8sについて理解を深められるのではないかと思い立ち、手始めにk8s環境を構築してみることにしました。
いざ構築
構築にあたり、利用するマシンと想定する構成は下記の通りです。
1コントロールプレーン、1ワーカーノード構成を目指します。
マシン | 役割 | OS |
---|---|---|
Lenovo Thinkpad E595 | コントロールプレーン | Ubuntu 24.04.1(Noble Numbat) |
Raspberry Pi 5 | ワーカーノード | Debian 12(Bookworm) |
172.22.4.x
なネットワークはインターネットと疎通可能です。
制約の都合上、このネットワークではIPを固定するのが困難なため、別途ノード間通信用のLANとして 192.168.2.0/24
のLANを構築しました。
また、k8sのPodネットワークとしては 10.244.0.0/16
としています。
手順
基本的にk8s公式ページの手順に則り進めます。
大雑把な流れとしては下記の通りとなります。
- containerd(CRI)のセットアップ
- kubeadmのインストール
- クラスタのセットアップ
- calico(CNI)のセットアップ
containerdのセットアップ
まずはCRIとしてcontainerdをセットアップします。
コンテナランタイム共通設定
overlayfsのモジュールやPod間通信のフィルタリングに必要なbr_netfilterを有効化し、設定をします。
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
$ sudo modprobe overlay
$ sudo modprobe br_netfilter
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
$ sudo sysctl --system
インストール
下記手順より、apt-get
を使った方法で進めます。
この場合、Dockerのaptリポジトリを使うことになりますが、最終的にインストールするのは必要な containerd.io
のみインストールします。
$ sudo apt install -y containerd.io
設定
次にcontainerdの設定を進めます。
disabled_plugins
に cri
が含まれないようにします。
今回は disabled_plugins
ごとコメントアウトしてしまいます。
また、runc
が systemd cgroup
ドライバを使うように設定をします。
# disabled_plugins = ["cri"]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
上記設定が完了したらcontainerdをを再起動して設定を反映します。
$ sudo systemctl restart containerd
kubeadmのインストール
公式の手順に則り、aptパッケージとしてインストールします。
最後に不用意に更新されないために apt hold
してバージョンを固定します。
$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https ca-certificates curl gpg
$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
また、swapも無効化しておきます。
$ sudo swapoff -a
$ sudo sed -i '/ swap / s/^/$/' /etc/fstab
ここまでの手順はk8sのノード共通で行います。
クラスタの作成
ここからはノードごとの設定となります。
コントロールプレーンの初期化
Podネットワークとして 10.244.0.0/16
、コントロールプレーンのAPIサーバーへのadvertise addressは 192.168.2.10
とします。
$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --cri-socket=/run/containerd/containerd.sock --apiserver-advertise-address=192.168.2.10
当初は --cri-socket
オプションをつけていなかったのですが、これがないとPodの展開時などに /var/run/containerd/containerd.sock
を参照しようとするも存在せず、うまく動作しないといった事象に遭遇しました。
そのためinit時の引数で予め設定するようにしました。
上記初期化コマンドが正常終了すると、クラスタ設定をホームディレクトリにコピーするよう指示があるため従います。
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
また、初期化コマンド正常終了時に下記のようなコマンドが出力されますが、ワーカーノードのjoinに必要なのでメモります。
kubeadm join 192.168.2.10:6443 --token SOME_TOKEN_HERE \
--discovery-token-ca-cert-hash sha256:[SOME_SHA256_HERE]
CNIのセットアップ
クイックスタートの手順に従いcalicoをセットアップします。
基本的に手順の通り進めているため、URL直指定でのリソース作成をしていますが、中身の確認は事前にしています。
- オペレータのセットアップ
$ kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/tigera-operator.yaml
- 必要なカスタムリソースのセットアップ
ここではPodネットワークのCIDR設定が入っており、今回は10.244.0.0/16
で構成しているため、URL直指定での実行ではなく、ダウンロードの上編集して適用しています。custom-resource.yamlの一部抜粋$ wget https://raw.githubusercontent.com/projectcalico/calico/v3.29.0/manifests/custom-resources.yaml
リソースの作成をし、立ち上がりを確認します。apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - name: default-ipv4-ippool blockSize: 26 cidr: 10.244.0.0/16 ...
$ kubectl create -f custom-resources.yaml $ watch kubectl get pods -n calico-system
立ち上がらないcalico
ここでcalicoのPodのうち1つについて起動が一向に完了しませんでした。Podをdescribeしてみると下記エラーが出ていました。
Warning NetworkNotReady 3m19s (x25 over 4m7s) kubelet network is not ready: container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized
下記を参考にcontainerdを再起動してしばらく経つと当該Podの起動に成功しました。
ワーカーの作成
コントロールプレーン作成時に出力されたコマンドを実行して設定完了です。
kubeadm join 192.168.2.10:6443 --token SOME_TOKEN_HERE \
--discovery-token-ca-cert-hash sha256:[SOME_SHA256_HERE]
立ち上がらないkube-proxy
join時にkube-proxyがContainerCreatingで止まってしまっていました。
詳細を見てみると /run/systemd/resolve/resolv.conf
が解決されておらず立ち上がらないようでした。
$ kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-7c65d6cfc9-2l5dl 1/1 Running 0 14m
coredns-7c65d6cfc9-qjs8h 1/1 Running 0 14m
etcd-k8s-master-01 1/1 Running 0 14m
kube-apiserver-k8s-master-01 1/1 Running 0 14m
kube-controller-manager-k8s-master-01 1/1 Running 0 14m
kube-proxy-j8llm 0/1 ContainerCreating 0 3m34s
kube-proxy-nq4xs 1/1 Running 0 14m
kube-scheduler-k8s-master-01 1/1 Running 0 14m
$ kubectl describe po kube-proxy-j8llm -n kube-system
Name: kube-proxy-j8llm
Namespace: kube-system
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m53s default-scheduler Successfully assigned kube-system/kube-proxy-j8llm to k8s-worker-01
Warning FailedCreatePodSandBox 8s (x18 over 3m53s) kubelet Failed to create pod sandbox: open /run/systemd/resolve/resolv.conf: no such file or directory
こちらは下記を参考にkubeletの設定で /etc/resolv.conf
を使用するようにし、kubeletを再起動することで解消しました。
# /var/lib/kubelet/config.yaml
- resolvConf: /run/systemd/resolve/resolv.conf
+ resolvConf: /etc/resolv.conf
以下は修正後のログです。
正常にイメージのプルができるようになっていることが確認できます。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 9m28s default-scheduler Successfully assigned kube-system/kube-proxy-j8llm to k8s-worker-01
Warning FailedCreatePodSandBox 3m52s (x26 over 9m28s) kubelet Failed to create pod sandbox: open /run/systemd/resolve/resolv.conf: no such file or directory
Normal Pulling 7s kubelet Pulling image "registry.k8s.io/kube-proxy:v1.31.3"
INTERNAL IPの調整
kubectl get nodes -o wide
を実行すると、INTERNAL-IPがいずれも 172.22.4.x
を指していました。
192.168.2.0/24
でないとIPの固定化は行なっていないので困ります。
調べてみると、どうやら KUBELET_CONFIG_ARGS
に --node-ip
オプションを追加することで指定できるようなので、オプションの追加をしました。
ノードごとに systemctl edit kubelet
でkubeletのサービスファイルの当該環境変数にオプションを追加してkubeletを再起動して完了です。
# /etc/systemd/system/kubelet.service.d/override.conf
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
-Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
+Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml --node-ip=192.168.2.10"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/default/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
いざHello World
Podが展開できるようになったはずなので、Hello Worldで動作を確認します。
$ kubectl run hello-world --image hello-world --restart=Never
pod/hello-world created
$ kubectl get po
NAME READY STATUS RESTARTS AGE
hello-world 1/1 Running 0 55s
$ kubectl logs pod/hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm64v8)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
無事、Hello Worldコンテナの出力が確認できました。
これで構築完了です。
まとめ
当初想定より構築に手間取りつつも、先人の知恵を借りつつ一通り動作する環境を整えることができました。
実は途中で手順を飛ばしてエラーで連携できなかったり、動的IP環境のままで進めてノード間の通信ができなくなりリセットするなどしていましたが、それらの経験も含めてk8sのベースとなる構成要素について理解が深まりました。
構築はまだまだ入り口に過ぎませんが、今回作成したクラスタを用いて運用をしてみたり異常な状況を作ってトラブルシュートしてみたりして知見を貯めていこうと思います。
お知らせ
DONUTSでは新卒中途問わず積極的に採用活動を行っています。
我々ジョブカン事業部も、一緒に働くエンジニアを募集しています。