Kubernetesクラスタの構築方法

Kubernetesクラスタとは、Kubernetesを利用して構築された複数のノードから成るシステムのことです。

Kubernetesクラスタを利用することで、複数のコンテナを効率的かつ安定的に管理することができます。

Kubernetesクラスタには、MasterノードとWorkerノードがあります。

Masterノードは、クラスタ全体の管理を担当し、Workerノードは、コンテナの実行を担当します。


今回は実験的にKubernetesクラスタを構築するために、Hyper-Vで仮想環境を使用したなんちゃっKubernetesクラスタを構築します。

構成としては、Masterノード1個・Workerノード2個の構成とします。


作業の流れとしては、下記の流れで作業していきます。


  1. Hyper-Vによる仮想環境の作成
  2. 全ノードの共通設定
  3. Masterノードの構築
  4. Workerノードの構築



構築の際に参考にしたものを記載します。


kubernetes.io




それでははじめていきましょう。




1. Hyper-Vによる仮想環境の作成

複数のサーバを自前で用意するのは大変なので、Hyper-Vを使って仮想環境を複数作成し、仮想環境同士で通信をさせることによってクラスタ環境を構築します。

そのための準備としてHyper-Vを使用してUbuntu環境を用意します。

ここからローカル側のOSのことをホストOS、仮想環境側のOSのことをゲストOSとして、説明を進めていきます、


1. Ubuntu環境の構築

Ubuntu環境を構築するために簡単に作成するためにHyper-Vの「クイック作成」を利用します。


今回は「Ubuntu 20.04 LTS」を使用します。

Ubuntu 20.04 LTS」を選択して、「仮想マシンの作成」をクリックします。


あとは流れにそってインストールするだけで仮想環境が構築できます。

仮想マシン名は右クリックから変更できるので、識別しやすいような名前に変更します。



2. 必要なソフトウェアのインストール

必要なソフトウェアをインストールします。

左上ので「Activity」から「Terminal」を検索して、Terminalを起動します。

下記のコマンドで必要なソフトウェアをインストールします。

$ sudo apt-get update
$ sudo apt-get upgrade -y
$ sudo apt-get install -y openssh-server net-tools


これでローカルや別のゲストOSからSSH接続ができるようになります。


3. IPアドレスの固定化

ゲストOSは再起動のたびにIPアドレスが変わってしまうので、IPアドレスを固定化します。

こちらのサイトの手順を参考に、IPアドレスを固定化します。

https://murachan-km.com/hyper-v-static-ip-address-external-network-connection/


それぞれのIPアドレスの設定は下記のようにしました。

サーバ アドレス ネットマスク ゲートウェイ
master-01 192.168.1.1 255.255.255.0 192.168.1.254
worker-01 192.168.1.2 255.255.255.0 192.168.1.254
worker-02 192.168.1.3 255.255.255.0 192.168.1.254


以上の手順でMasterノード用1個とWorkerノード用2個の計3回繰り返します。



2. 全ノードの共通設定

1. コンテナランタイムのインストール

クラスタ内の各ノードがPodを実行できるようにするためコンテナランタイムをインストールします。

今回はコンテナランタイムとして、Dockerを使用します。


1. IPv4フォワーディングの有効化

IPv4フォワーディングを有効化し、iptablesからブリッジされたトラフィックをみえるようにします。

$ 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


下記のコマンドを実行して、br_netfilteroverlayモジュールが読み込まれていることを確認します。

$ lsmod | grep br_netfilter
br_netfilter           28672  0
bridge                176128  1 br_netfilter
   
$ lsmod | grep overlay
overlay               118784  0


下記のコマンドを実行して、net.bridge.bridges-nf-call-iptablesnet.bridge.bridge-nf-call-ip6tablesnet.ipv4.ip_forwardカーネルパラメーターが1に設定されていることを確認します。

$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1


2. cgroupドライバーのセットアップ

kubeletと基盤となるコンテナランタイムは、コンテナリソース管理を実施し、CPU/メモリーの要求や制限などのリソースを設定するために、cgroupとインタフェースする必要がある。

cgroupとインターフェースするためには、kubeletおよびコンテナランタイムはcgroupドライバーを使用する必要があります。

利用可能なcgroupドライバーとして、cgroupsとsystemdがあります。


基本的にはデフォルトのcgroupsで良いと思いますが、systemdがinitシステムである場合や、cgroup v2を使用している場合は、systemdを使用します。

  • systemdがinitであるかの確認方法
$ ps -p 1 -o comm=
systemd


  • cgroupsバージョンの確認方法
$ stat -fc %T /sys/fs/cgroup/
tmpfs

cgroup v2では、cgroup2fsと出力されます。

cgroup v1では、tmpfsと出力されます。

今の環境ではそのままcgroupが使えるようなので、特に設定等は変更せずに行きたいと思います。


3. Dockerのインストール

コンテナにはDockerを使用します。

DockerとKubernetesを統合するために、cri-dockerdアダプタを使用します。

まずは古いバージョンをアンインストールします。

$ sudo apt-get remove docker docker-engine docker.io containerd runc


必要なソフトウェアをインストールします。

$ sudo apt-get install -y ca-certificates curl gnupg


Dockerの公式GPGキーを追加します。

$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg


リポジトリのセットアップを行います。

$ echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null


インデックスを更新します。

$ sudo apt-get update


Dockerをインストールします。

$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin


Dockerのインストール確認をします。

正常に実行できたらDockerのインストールに成功しています。

$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:4e83453afed1b4fa1a3500525091dbfca6ce1e66903fd4c01ff015dbcb1ba33e
Status: Downloaded newer image for hello-world:latest

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.
    (amd64)
 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/


Dockerをsudoをつけずに実行できるように変更します。

# ユーザをdockerグループに所属させる
$ sudo gpasswd -a $USER docker

# dockerデーモンを再起動する
$ sudo systemctl restart docker

# exitして再度ログインする
$ exit


cri-dockerdのインストールします。

下記のリポジトリの手順に従って実行します。

https://github.com/Mirantis/cri-dockerd

※Docker Engineはインストールしているので、下記の手順を実施するだけで問題ないです。

To install, on a Linux system that uses systemd, and already has Docker Engine installed

# Run these commands as root
###Install GO###
$ wget https://storage.googleapis.com/golang/getgo/installer_linux
$ chmod +x ./installer_linux
$ ./installer_linux
$ source ~/.bash_profile

$ cd cri-dockerd
$ mkdir bin
$ go build -o bin/cri-dockerd
$ mkdir -p /usr/local/bin
$ sudo install -o root -g root -m 0755 bin/cri-dockerd /usr/local/bin/cri-dockerd
$ sudo cp -a packaging/systemd/* /etc/systemd/system
$ sudo sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable cri-docker.service
$ sudo systemctl enable --now cri-docker.socket



2. kubeadm・kubelet・kubectlのインストール

Kubernetesクラスタを構築する方法には、いくつかの方法がありますが、ここでは、kubeadmを用いてKubernetesクラスタの構築します。

kubeadmは、Kubernetesクラスタを構築するためのツールの一つであり、Kubernetesの公式ドキュメントでも推奨されている方法です。

下記のパッケージをインストールします。

kubeadmkubeletkubectlは管理しないため、kubeadmにインストールするKubernetesコントロールプレーンのバージョンと一致させる必要があります。

1, 必要なパッケージのインストール

$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https ca-certificates curl


2, Google Cloudの公開鍵をダウンロード

$ sudo curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpg


3, Kubernetesのaptリポジトリを追加

$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list


4, aptパッケージの更新とパッケージのインストール

$ sudo apt-get update
$ sudo apt-get install -y kubelet=1.25.9-00 kubeadm=1.25.9-00 kubectl=1.25.9-00
$ sudo apt-mark hold kubelet kubeadm kubectl


5, swapの無効化

kubeletが正常に動作するためには、swapが必ずオフでなければなりません。

# 確認方法
$ sudo swapon --show


swapが無効化されている場合は、何も出力されません。

もし、有効化されている場合は、/dev/〇〇と出力されます。


有効化されている場合は、下記のコマンドで無効化します。

$ sudo swapoff -a


以上の手順でMasterノード用1個とWorkerノード用2個の計3回繰り返します。



3. Masterノードの構築

Masterノードを構築します。

下記コマンドで構築することができます。

$ sudo kubeadm init --kubernetes-version 1.25.9 --pod-network-cidr=10.244.0.0/16 --control-plane-endpoint=192.168.1.1 --cri-socket=unix:///var/run/cri-dockerd.sock

~~ (省略) ~~

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:

  kubeadm join 192.168.1.1:6443 --token 1244uy.wgtftojxyr0alz3s \
        --discovery-token-ca-cert-hash sha256:b26828a4171ebb57a039e9aeaaf5bcbccdd3b3ea7b2929264aede4bae0e38890 \
        --control-plane

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.1:6443 --token 1244uy.wgtftojxyr0alz3s \
        --discovery-token-ca-cert-hash sha256:b26828a4171ebb57a039e9aeaaf5bcbccdd3b3ea7b2929264aede4bae0e38890


問題なく設定ができたら、成功のメッセージが出ます。

メッセージに表示されているように、通常ユーザでも使用できるように設定をします。

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


ここでDockerで起動したコンテナに付与されるIPアドレスはホストの外から疎通性のないInternal IPになります。

この状態だとノードをまたいでコンテナ同士が通信することはできません。

そこでFannelを使用してノード間をつなぐネットワークに仮想的なトンネルを構成することで、Kubernetesクラスタ内のPod通信を実現します。

Manifestファイルを適用するだけで実現することができます。

$ kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml


これでMasterノードの構築が完了です。



4. Workerノードの構築

Workerノードの構築は、Masterノードでkubeamd initで出力された内容を適用します。

$ sudo kubeadm join 192.168.1.1:6443 --token 1244uy.wgtftojxyr0alz3s --discovery-token-ca-cert-hash sha256:b26828a4171ebb57a039e9aeaaf5bcbccdd3b3ea7b2929264aede4bae0e38890 --cri-socket=unix:///var/run/cri-dockerd.sock


これを2個のWorkerノードに適用します。

これでWorkerノードの構築が完了です。



5. 確認

1. ノードの確認

MasterノードとWorkerノードがそれぞれ構築ができたら、Nodeが適切に設定されているかを確認します。

Masterノード上からノードを確認することができます。

下記のように出力されていれば構築がうまくいっています。

$ kubectl get nodes
NAME        STATUS   ROLES           AGE   VERSION
master-01   Ready    control-plane   58m   v1.25.9
worker-01   Ready    worker          53m   v1.25.9
worker-02   Ready    worker          53m   v1.25.9

※STATUSがReadyになるまでは少し時間がかかります。


もしROLES<none>になっている場合は、次のコマンドで設定できます。

$ kubectl get nodes
NAME        STATUS   ROLES           AGE   VERSION
master-01   Ready    control-plane   58m   v1.25.9
worker-01   Ready    <none>          53m   v1.25.9
worker-02   Ready    <none>          53m   v1.25.9

$ kubectl label node worker-01 node-role.kubernetes.io/worker=worker
$ kubectl label node worker-02 node-role.kubernetes.io/worker=worker


2. 動作確認

実際にPodを作成してみて、Workerノード上で動くか確認します。

下記のManifestファイルを作成します。

# pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-pod
spec:
  containers:
  - name: nginx-container
    image: nginx:1.16


Manifestファイルを適用して、Podがどこのノードで動いているを確認します。

$ kubectl apply -f pod.yaml
pod/sample-pod created

$ get pods -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
sample-pod   1/1     Running   0          9s    10.244.1.3   worker-01   <none>           <none>


NODEのところを確認すると、worker-01ノードで稼働していることが確認できました。



6. まとめ

以上でKubernetesクラスタの構築が完了しました。

MasterノードとWorkerノードはkubeadmがうまくやってくれたので楽だったのですが、dockerkubeadmを入れるのが少し大変だったかなという印象です。

この記事が何かの参考になれば幸いです。