kubeadmによるKubernetes Clusterの作成

kubeadm を利用して、kubernetes clusterをゼロから作成してみます。 kubeadm とは、kubernetes ClusterのBootstrap Toolとなっています。

Kubeadm is a tool built to provide best-practice "fast paths" for creating Kubernetes clusters. It performs the actions necessary to get a minimum viable, secure cluster up and running in a user friendly way. Kubeadm's scope is limited to the local node filesystem and the Kubernetes API, and it is intended to be a composable building block of higher level tools.

GitHub kubeadm

環境情報

作成するkubernetes Cluster構成と、各バージョンは下記となります。

  • Cluster構成

    • Master Node数
      • 1台
    • Worker Node数
      • 1台
  • 各バージョン

    • OS
    • kubernetes
      • 1.20.0-00
    • コンテナ・ランタイム
      • Docker CE 5:19.03.11
    • CNI
      • calico/node:v3.17.1

環境はGCEを利用しました。

kubeadmのインストール

Cluster内の全ノードに対して、必要な事前設定・確認を実施し、kubeadmをインストールします。下記は公式ドキュメント。

Installing kubeadm

MACアドレスとproduct_uuidの確認

各ノードにて、MACアドレスとproduct_uuidが一意であることを確認します。

$ ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc mq state UP group default qlen 1000
    link/ether 42:01:0a:92:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.146.0.2/32 scope global dynamic ens4
       valid_lft 2607sec preferred_lft 2607sec
    inet6 fe80::4001:aff:fe92:2/64 scope link 
       valid_lft forever preferred_lft forever
$
$ sudo cat /sys/class/dmi/id/product_uuid
759f0cc0-24c3-54dd-22d7-ac580e9e0568

スワップの無効化

スワップを無効化しておきます。確認。

$ free -h | grep -i swap
Swap:            0B          0B          0B

br_netfilterモジュールをロードして、iptablesとのフィルタリングを有効化

br_netfileter(bridge network filter) モジュールの役割を理解するために、Dockerネットワークについておさらいします。以下サイトの説明が、非常に簡潔で分かりやすかったです。

広く使われているコンテナ化技術「Docker」を例として、コンテナネットワークの概要を説明する。コンテナは一般的にLinux OS上で起動する。このホストは「コンテナホスト」と呼ばれ、各コンテナはコンテナホスト内の論理的なネットワークに接続されている。

具体的には、コンテナホスト内に論理ブリッジ(Linuxブリッジ)が存在し、各コンテナは、LinuxのNetwork Namespace機能により隔離され、仮想ネットワークインターフェース(veth)でこのブリッジに接続される。これによって、コンテナ内のプロセスから見ると、自分専用のネットワークインターフェースがあるように見えている。

そして、コンテナホスト自身が持つネットワークインターフェースもこのブリッジに接続されているため、コンテナで起動するサービスを外部公開する場合には、コンテナホストに対するアクセスがLinuxカーネルiptablesを使ってNATされる。

<コンテナNWの課題と展望>Kubernetes環境のネットワークの基礎を学ぶ

そして、この br_netfileter(bridge network filter) は、iptables上のルールを論理ブリッジに統合するような働きをするみたいです。

現在の設定を確認。

$ lsmod | grep br_netfilter

モジュールのロード。

$ sudo modprobe br_netfilter

以下コンフィグファイルを設定することで、OS起動時にモジュールをロードし、iptablesとのフィルタリングを有効とするようカーネルパラメーターを設定します。

$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF
$
$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

パラメーターを再読み込みします。

$ sudo sysctl --system

確認。

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

利用するネットワークポートの確認

kubernets cluster内で、利用するポートを、疎通可能としておきます。利用ポート情報は以下。

f:id:goodbyegangster:20210116170817p:plain

Check required ports

Container Runtimeのインストール

コンテナ実行環境として、Docker CEをインストールし、起動させておきます。公式ドキュメントにて言われるまま、作業しています。

Container runtimes - Docker

$ # (Install Docker CE)
$ ## Set up the repository:
$ ### Install packages to allow apt to use a repository over HTTPS
$ sudo apt-get update && sudo apt-get install -y \
    apt-transport-https ca-certificates curl software-properties-common gnupg2
$ 
$ # Add Docker's official GPG key:
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key --keyring /etc/apt/trusted.gpg.d/docker.gpg add -
$ 
$ # Add the Docker apt repository:
$ sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
$ 
$ # Install Docker CE
$ sudo apt-get update && sudo 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)
$ 
$ ## Create /etc/docker
$ sudo mkdir /etc/docker
$ 
$ # Set up the Docker daemon
$ cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
$
$ # Create /etc/systemd/system/docker.service.d
$ sudo mkdir -p /etc/systemd/system/docker.service.d
$ 
$ # Restart Docker
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
$ sudo systemctl enable docker
$ 

正しく起動できていることを確認。

$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2021-01-14 16:40:25 UTC; 46s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 4875 (dockerd)
      Tasks: 10
     Memory: 36.0M
     CGroup: /system.slice/docker.service
             └─4875 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.090713320Z" level=warning msg="Your kernel>
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.090724966Z" level=warning msg="Your kernel>
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.090733013Z" level=warning msg="Your kernel>
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.091795132Z" level=info msg="Loading contai>
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.226503101Z" level=info msg="Default bridge>
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.275624298Z" level=info msg="Loading contai>
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.298577391Z" level=info msg="Docker daemon">
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.298661273Z" level=info msg="Daemon has com>
Jan 14 16:40:25 kubeadm-master systemd[1]: Started Docker Application Container Engine.
Jan 14 16:40:25 kubeadm-master dockerd[4875]: time="2021-01-14T16:40:25.332888590Z" level=info msg="API listen on >

Kubernetes v1.20. 以降にて、コンテナランタイムでDockerを利用することは非推奨となりました。以下、公式のアナウンス資料です。

Don't Panic: Kubernetes and Docker

Kubernetes is deprecating Docker as a container runtime after v1.20.

Docker-produced images will continue to work in your cluster with all runtimes, as they always have.

When Docker runtime support is removed in a future release (currently planned for the 1.22 release in late 2021) of Kubernetes it will no longer be supported and you will need to switch to one of the other compliant container runtimes, like containerd or CRI-O. Just make sure that the runtime you choose supports the docker daemon configurations you currently use (e.g. logging).

とは言うものの、Dockerコンテナイメージは継続して利用できます。Kubernetes Clusterにて利用されるコンテナラインタイムを、containerdやCRI-Oといったランタイムに変更する必要が、将来バージョンであるとの事です。

Kubernetes内のコンテナランタイムの機能としては、コンテナのpullとrunが出来れば良いのに対し、Dockerの機能はtoo muchであり、且つCRI(Container Runtime Interface)にも準拠していないため色々しんどいんだ、みたいなことが書いてありました。

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

kubeadm、kubelet、kubectlをインストールします。下記が各モジュールの説明です。

  • kubeadm
    • Kubernetes Cluster作成のためのBootstrap Tool
  • kubelet
    • 各ノード上で起動される、kube-apiとやり取りするコンポーネント
    • podの起動とかしてくれる
  • kubectl
    • kube-apiとやり取りするCLIツール

公式ドキュメント通りにインストール。なお、各モジュールのバージョンを揃える必要があります。

$ 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=1.20.0-00 kubeadm=1.20.0-00 kubectl=1.20.0-00
$ sudo apt-mark hold kubelet kubeadm kubectl

確認。

$ kubelet --version
Kubernetes v1.20.0
$
$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.0", GitCommit:"af46c47ce925f4c4ad5cc8d1fca46c7b77d13b38", GitTreeState:"clean", BuildDate:"2020-12-08T17:57:36Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}
$
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.0", GitCommit:"af46c47ce925f4c4ad5cc8d1fca46c7b77d13b38", GitTreeState:"clean", BuildDate:"2020-12-08T17:59:43Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}

kubeletの起動

kubeletプロセスの自動起動設定を入れておきます。

$ sudo systemctl daemon-reload
$ sudo systemctl restart kubelet
$ sudo systemctl enable kubelet

ステータスを確認すると正しく起動できていないですが、この後kube-apiを作成すると、正しく起動できるようになります。

$ sudo systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /etc/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: activating (auto-restart) (Result: exit-code) since Thu 2021-01-14 17:15:09 UTC; 7s ago
       Docs: https://kubernetes.io/docs/home/
    Process: 11120 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS >
   Main PID: 11120 (code=exited, status=255/EXCEPTION)

Jan 14 17:15:09 kubeadm-master systemd[1]: kubelet.service: Failed with result 'exit-code'.

コンテナランタイムでDockerを利用している場合には不要ですが、その他ランタイムを利用している場合、kubeletで利用するcgroup driverを明示的に指定する必要があるそうです。

Configure cgroup driver used by kubelet on control-plane node

kubeadm設定

インストールしたkubeadmを利用して、kubernetes clusterの初期セットアップをしていきます。公式ドキュメントはこちら。

Creating a cluster with kubeadm

kubeadm initの実行

マスターノード上で、Cluster初期化処理を行います。以下を実行します。

$ sudo kubeadm init
[init] Using Kubernetes version: v1.20.2
[preflight] Running pre-flight checks
...
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

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/

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

kubeadm join 10.146.0.2:6443 --token m8sf51.jiip1suec225lyai \
    --discovery-token-ca-cert-hash sha256:a70c0361997bbefb6c77f74d9c85d13a6feb830ae52ff1d1b4ebe4c6567e5279

今回の構成ではオプションを何も指定していませんが、作成するClusterの環境により、オプションを付与して実行します。

  • オプションたち
    • -control-plane-endpoint
      • マスターノードが複数構成の場合に利用
      • 複数構成の場合、kube-apiへのアクセスをロードバランスする必要あるため、ロードバランサーのエンドポイントとして利用されるDNS名を指定
    • -pod-network-cidr
      • Cluster内のNodeネットワークアドレス帯とPodネットワークアドレス帯は重複不可となる
      • 利用する各CNIにより、デフォルトのPodネットワークアドレス帯が定められているため、その情報を基にCIDR情報を設定
      • Installing a Pod network add-on
      • ただし、現在kubeadmで推奨されているCNIは Calico のみのようで、Calicoで利用されるデフォルトPodネットワークアドレス帯は 192.168.0.0/16 となる
    • -cri-socket
      • コンテナランタイムでDocker以外を利用する場合、利用するランタイムのUNIX Domain Socketを指定
      • Path to Unix domain socket
    • -apiserver-advertise-address

Initializing your control-plane node

kubeconfigの設定

マスターノード上でkubectlを利用できるよう、kubeconfigの設定をいれておきます。サンプルとなるコンフィグファイルを提供してくれるため、そのファイルをHOMEディレクトリ配下にコピーします。

以下の、 kubeadm init 実行時の出力結果のコマンドを実行。

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

確認。

$ kubectl cluster-info
Kubernetes control plane is running at https://10.146.0.2:6443
KubeDNS is running at https://10.146.0.2:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

CNI(Container Network Interface)の作成

CNI(Container Network Interface)として、 Calico の設定を実施します。CNIとは、Kubernetes Cluster内で、ノード間のコンテナ通信を管理する働きをします。単一ノード内でのコンテナ通信は、上記で引用したように、仮想的なブリッジを作成し、各コンテナに対し仮想的ネットワークインターフェースを付与することで実現しています。kubernetesでは複数ノードにコンテナが分散しているため、ノードを跨いだコンテナ間通信や、コンテナ間でのIPアドレスを重複制御する必要があり、CNIがその機能を提供してくれています。

まず、現在のノード情報を確認します。STATUSはNotReadyとなっています。

$ kubectl get node
NAME             STATUS     ROLES                  AGE     VERSION
kubeadm-master   NotReady   control-plane,master   7m12s   v1.20.0

Calicoを作成するには、公式サイトにて公開されているマニフェストファイルをapplyすることになります。以下、公式の参考マニュアル。

Install Calico networking and network policy for on-premises deployments

実行。利用するPodネットワークアドレスを 192.168.0.0/16 より変更している場合、マニフェストファイル内で定義されているCIDR情報を修正してから実行します。

$ kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
daemonset.apps/calico-node created
serviceaccount/calico-node created
deployment.apps/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
poddisruptionbudget.policy/calico-kube-controllers created

いろいろ作成されますが、結局 calico-node というDaemonSetを作成している、という訳でしょうか。

再度、現在のノード情報を確認すると、STATUSがReadyとなっています。

$ kubectl get node
NAME             STATUS   ROLES                  AGE   VERSION
kubeadm-master   Ready    control-plane,master   34m   v1.20.0

kubeletプロセスを確認すると、正しく起動するようになっています。

$ sudo systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /etc/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Fri 2021-01-15 12:51:58 UTC; 41min ago
       Docs: https://kubernetes.io/docs/home/
   Main PID: 4422 (kubelet)
      Tasks: 15 (limit: 9544)
     Memory: 44.4M
     CGroup: /system.slice/kubelet.service
             └─4422 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc>

マスターノードのtaint削除

デフォルトでは、マスターノード上にはPodをスケジューリングされないようにtaint設定が入っています。

$ kubectl describe node kubeadm-master | grep -i taint
Taints:             node-role.kubernetes.io/master:NoSchedule

今回の構成では、ワーカーノードも単一ノードとなるため、万が一に備えて、マスターノード上のtaint設定を削除しておきます。

$ kubectl taint nodes --all node-role.kubernetes.io/master-
node/kubeadm-master untainted

確認。

$ kubectl describe node kubeadm-master | grep -i taint
Taints:             <none>

ワーカーノードの追加

Clusterにワーカーノードを追加します。

ワーカーノードとして追加したいノードに接続して、 kubeadm init コマンド実行時に表示された以下のコマンドを実行します。

$ sudo kubeadm join 10.146.0.2:6443 --token m8sf51.jiip1suec225lyai \
    --discovery-token-ca-cert-hash sha256:a70c0361997bbefb6c77f74d9c85d13a6feb830ae52ff1d1b4ebe4c6567e5279
[preflight] Running pre-flight checks
...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

ワーカーノード上のkubeletプロセスも、正しく起動するようなっています。

$ sudo systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /etc/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Fri 2021-01-15 13:42:34 UTC; 2min 4s ago
       Docs: https://kubernetes.io/docs/home/
   Main PID: 11357 (kubelet)
      Tasks: 15 (limit: 9544)
     Memory: 44.7M
     CGroup: /system.slice/kubelet.service
             └─11357 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/>

マスターノード上でCluster Nodeの確認。ワーカーノードが参加してくれています。

$ kubectl get node
NAME             STATUS   ROLES                  AGE    VERSION
kubeadm-master   Ready    control-plane,master   53m    v1.20.0
kubeadm-worker   Ready    <none>                 3m2s   v1.20.0

token情報は、24時間で期限切れとなってしまうらしく、以下コマンドにてtokenの再作成を行えます。

$ sudo kubeadm token create --print-join-command
kubeadm join 10.146.0.2:6443 --token ykz2kc.vhrt26ajm4ge854s     --discovery-token-ca-cert-hash sha256:a70c0361997bbefb6c77f74d9c85d13a6feb830ae52ff1d1b4ebe4c6567e5279

確認

Kubernets Clusterを作成できました。適当なPodを動かしてみます。

$ kubectl run sample --image=nginx
pod/sample created
$
$ kubectl get pod
NAME     READY   STATUS    RESTARTS   AGE
sample   1/1     Running   0          10s
$

ちゃんと動きますね。