Featured image of post Ubuntu+kubeadm+containerd+CalicoでシングルmasterのKubernetesクラスタをつくる

Ubuntu+kubeadm+containerd+CalicoでシングルmasterのKubernetesクラスタをつくる

練習用クラスタをつくりたかった

やったことのまとめ

クラスタ操作の練習用にGKEみたいなマネージドではない自前のKubernetesクラスタがほしくて、
かつ運用の練習としてはkindMinikubeみたいなやつだと物足りない。
とはいえKubernetes The Hard Wayするほどじゃないんだよな…みたいな気分になったので、
おさらいも兼ねてkubeadmでクラスタを立てることにした。
基本的にCreating a cluster with kubeadmをなぞっているだけ。

  • GCPでVMとネットワークを作成した
  • kubeadmを使ってKubernetesクラスタをつくった
  • コンテナランタイムにはcontainerd, ネットワークアドオンはCalicoを使用した
  • あまり長くつかっているとお金がかかるのでおかたづけした

つかうもの

  • macOS Big Sur 11.2.3
  • Google Cloud SDK 360.0.0
    • GCP操作に使用
  • Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-1020-gcp x86_64)
    • node用のVMとして使用
  • containerd 1.4.11
    • コンテナランタイム
  • Calico v3.20.2
    • ネットワークアドオン
  • Kubernetes v1.22.2

やったこと

ネットワークとVMの準備(GCP)

gcloudのセットアップはmacOS用のクイックスタートで終わっている前提。

VPCネットワークの作成

まずはnode同士をつなぐためのネットワークを設定する。
GCPではVPCネットワークを使う。

# Macで実行

# VPCネットワークの作成
# サブネットのIPレンジは10.240.0.0/24(最大256node使用可能)にする
gcloud compute networks create kubernetes-vpc-network --subnet-mode=custom
gcloud compute networks subnets create kubernetes-vpc-network-subnet --range=10.240.0.0/24 --network=kubernetes-vpc-network

# ネットワークのIPレンジ内からネットワーク上の任意のnodeへの任意のプロトコルでの接続を許可(kubernetes-vpc-network-allow-internal)
# 任意の送信元からネットワーク上の任意のnodeへの特定ポートへの特定プロトコルでの接続を許可(kubernetes-vpc-network-allow-external)
# tcp:22はnodeへのSSHに, tcp:6443はkube-apiserverへのリクエストに, icmpはpingでの監視に使用
gcloud compute firewall-rules create kubernetes-vpc-network-allow-internal --network=kubernetes-vpc-network --allow all --source-ranges=10.240.0.0/24
gcloud compute firewall-rules create kubernetes-vpc-network-allow-external --network kubernetes-vpc-network --allow tcp:22,tcp:6443,icmp

VMインスタンスの作成

つぎにクラスタのnodeとして使うVMインスタンスmaster用とworker用にそれぞれ1台ずつ建てる。
Creating a cluster with kubeadmによるとmasterは最低でも2GiB RAM, 2CPUsがあるといいらしいのでマシンタイプはそれを満たすように気をつける。
OSの起動imageはUbuntu 20.04 LTSを使用した。

# Macで実行

# master(e2-medium),worker(e2-small)用インスタンスの作成
gcloud compute instances create kubernetes-master --machine-type=e2-medium --image-family=ubuntu-2004-lts --image-project=ubuntu-os-cloud --subnet=kubernetes-vpc-network-subnet
gcloud compute instances create kubernetes-worker --machine-type=e2-small --image-family=ubuntu-2004-lts --image-project=ubuntu-os-cloud --subnet=kubernetes-vpc-network-subnet

最終的に以下のようになっていればOK。

$ gcloud compute networks list --filter="name=kubernetes-vpc-network"
NAME                    SUBNET_MODE  BGP_ROUTING_MODE  IPV4_RANGE  GATEWAY_IPV4
kubernetes-vpc-network  CUSTOM       REGIONAL

$ gcloud compute networks subnets list --filter="name=kubernetes-vpc-network-subnet"
NAME                           REGION       NETWORK                 RANGE          STACK_TYPE  IPV6_ACCESS_TYPE  IPV6_CIDR_RANGE  EXTERNAL_IPV6_CIDR_RANGE
kubernetes-vpc-network-subnet  us-central1  kubernetes-vpc-network  10.240.0.0/24  IPV4_ONLY

$ gcloud compute firewall-rules list --filter="network=kubernetes-vpc-network"
NAME                                   NETWORK                 DIRECTION  PRIORITY  ALLOW                 DENY  DISABLED
kubernetes-vpc-network-allow-external  kubernetes-vpc-network  INGRESS    1000      tcp:22,tcp:6443,icmp        False
kubernetes-vpc-network-allow-internal  kubernetes-vpc-network  INGRESS    1000      all                         False

$ gcloud compute instances list
NAME               ZONE           MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP    STATUS
kubernetes-master  us-central1-a  e2-medium                  10.240.0.4   xx.xx.xx.xxx   RUNNING
kubernetes-worker  us-central1-a  e2-small                   10.240.0.5   yy.yyy.yyy.yy  RUNNING

nodeの準備

ネットワークとnodeVMが準備できたので、いよいよクラスタを構築していく。

# SSHするときは以下のコマンドを使う
gcloud compute ssh kubernetes-master
gcloud compute ssh kubernetes-worker

コンテナランタイム(containerd)のインストール

masterworkerで共にPod(コンテナ)を動かすために必要なコンテナランタイムをインストールする。
コンテナランタイムにはいくつか候補があるが、今回はcontainerdを使う。
Ubuntuでのインストール手順はInstall Docker Engine on Ubuntuを参考にする。

# master/worker両方でそれぞれ実行する

# VM起動時にkernelモジュール(overlay, br_netfilter)を読み込むための設定
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
# kernelモジュール(overlay, br_netfilter)の手動読み込み
sudo modprobe overlay && sudo modprobe br_netfilter
# kernelパラメータを変更(永続化)する設定
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
# kernelパラメータの適用
sudo sysctl --system

# (ないと思うが)古いpackageを削除
sudo apt-get remove docker docker-engine docker.io containerd runc
# aptのpackage indexの更新とHTTPSでリポジトリを使うために必要なpackageのインストール
sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
# Docker公式のGPG鍵追加 
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Dockerのstableリポジトリ追加
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# containerdのインストール
sudo apt-get update && sudo apt-get install -y containerd.io
# containerdの設定ファイル作成(96行目付近にsystemdのcgroupドライバーを使う設定を入れる)
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo vim /etc/containerd/config.toml
# containerdの再起動
sudo systemctl restart containerd

containerdを動かすcgroupsystemdになる/etc/containerd/config.tomlの設定はUsing the systemd cgroup driverに従った。

      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          runtime_type = "io.containerd.runc.v2"
          runtime_engine = ""
          runtime_root = ""
          privileged_without_host_devices = false
          base_runtime_spec = ""
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            SystemdCgroup = true

systemdcontainerdが動いていることを確認できればOK。

kubernetes-worker:~$ systemctl is-active containerd
active

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

コンテナランタイムがインストールできたら、次はInstalling kubeadm, kubelet and kubectlに従ってKubernetesクラスタに必要なコンポーネントをmasterworker両方にインストールする。

# master/worker両方でそれぞれ実行する

# Google CloudのGPG鍵追加
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
# Kubernetesリポジトリ追加
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
# kubelet,kubeadm,kubectlのインストールとバージョン固定
sudo apt-get update && sudo apt-get install -y kubelet kubeadm kubectl && sudo apt-mark hold kubelet kubeadm kubectl

masterの起動

コンテナランタイムとkubeadmの準備ができたらまずはmaster node (control-plane)を作成する。
今回はネットワークアドオンにCalicoを使うため、kubeadm initのオプションを付与する。

# masterでのみ実行する

# master nodeのセットアップ
sudo kubeadm init --pod-network-cidr=192.168.0.0/16

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

# kubectlの準備
source <(kubectl completion bash)
alias k=kubectl
complete -F __start_kubectl k

これでmaster nodeが作成できた…と思いきや、
この時点ではまだネットワークアドオンが入っていないためPod間の通信ができず、CoreDNSが立ち上がらない。

kubernetes-master:~$ k get pods -A
NAMESPACE     NAME                                        READY   STATUS    RESTARTS   AGE
kube-system   coredns-78fcd69978-dzwrb                    0/1     Pending   0          3m25s
kube-system   coredns-78fcd69978-ffpvj                    0/1     Pending   0          3m25s
kube-system   etcd-kubernetes-master                      1/1     Running   0          3m33s
kube-system   kube-apiserver-kubernetes-master            1/1     Running   0          3m33s
kube-system   kube-controller-manager-kubernetes-master   1/1     Running   0          3m33s
kube-system   kube-proxy-zk7mn                            1/1     Running   0          3m25s
kube-system   kube-scheduler-kubernetes-master            1/1     Running   0          3m33s

ネットワークアドオン(Calico)のインストール

kubeadm initが終わったので、
つぎにネットワークアドオンとしてCalicoをインストールする。

# masterでのみ実行する

# Calicoのインストール
k create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml
k create -f https://docs.projectcalico.org/manifests/custom-resources.yaml

Calicoに必要なPodの作成には時間がかかるのですこし待つ。
次のようになっていればOK。
CoreDNSも立ち上がっている。

kubernetes-master:~$ k get pods -A
NAMESPACE          NAME                                        READY   STATUS    RESTARTS   AGE
calico-apiserver   calico-apiserver-6dd4bc68c6-lslcj           1/1     Running   0          12s
calico-system      calico-kube-controllers-767ddd5576-64dj8    1/1     Running   0          80s
calico-system      calico-node-2fx6b                           1/1     Running   0          80s
calico-system      calico-typha-5bf9887dd7-hrtwj               1/1     Running   0          80s
kube-system        coredns-78fcd69978-dzwrb                    1/1     Running   0          12m
kube-system        coredns-78fcd69978-ffpvj                    1/1     Running   0          12m
kube-system        etcd-kubernetes-master                      1/1     Running   0          12m
kube-system        kube-apiserver-kubernetes-master            1/1     Running   0          12m
kube-system        kube-controller-manager-kubernetes-master   1/1     Running   0          12m
kube-system        kube-proxy-zk7mn                            1/1     Running   0          12m
kube-system        kube-scheduler-kubernetes-master            1/1     Running   0          12m
tigera-operator    tigera-operator-59f4845b57-qgq6c            1/1     Running   0          111s

workerの起動

master1台のKubernetesクラスタができたので、
次にworkerをこのクラスタに追加する。

nodeの追加に必要なトークンとCA証明書のハッシュ値はmaster側で取得する。

# masterでのみ実行する

# 新規tokenの作成
kubernetes-master:~$ kubeadm token create
<token>

# CA証明書ハッシュの取得
kubernetes-master:~$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
<hash>

# master nodeのホストとIPを取得
kubernetes-master:~$ k cluster-info | grep control
Kubernetes control plane is running at https://<control-plane-host>:<control-plane-port>

masterで取得した情報を使ってworkerkubeadm joinしてクラスタに追加する。

# workerでのみ実行する
sudo kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>

This node has joined the clusterと表示されればworkerがクラスタに追加できている。

master側で確認するとworkerが認識されていて、各種DaemonSetPodもちゃんとworkerに配置されている。

kubernetes-master:~$ k get nodes -o wide
NAME                STATUS   ROLES                  AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION    CONTAINER-RUNTIME
kubernetes-master   Ready    control-plane,master   25m   v1.22.2   10.240.0.4    <none>        Ubuntu 20.04.3 LTS   5.11.0-1020-gcp   containerd://1.4.11
kubernetes-worker   Ready    <none>                 91s   v1.22.2   10.240.0.5    <none>        Ubuntu 20.04.3 LTS   5.11.0-1020-gcp   containerd://1.4.11

kubernetes-master:~$ k get all -A -o wide
NAMESPACE          NAME                                            READY   STATUS    RESTARTS        AGE    IP              NODE                NOMINATED NODE   READINESS GATES
calico-apiserver   pod/calico-apiserver-6dd4bc68c6-lslcj           1/1     Running   0               19m    192.168.237.5   kubernetes-master   <none>           <none>
calico-system      pod/calico-kube-controllers-767ddd5576-64dj8    1/1     Running   0               20m    192.168.237.2   kubernetes-master   <none>           <none>
calico-system      pod/calico-node-2fx6b                           1/1     Running   0               20m    10.240.0.4      kubernetes-master   <none>           <none>
calico-system      pod/calico-node-vkmhc                           1/1     Running   0               7m7s   10.240.0.5      kubernetes-worker   <none>           <none>
calico-system      pod/calico-typha-5bf9887dd7-hrtwj               1/1     Running   0               20m    10.240.0.4      kubernetes-master   <none>           <none>
calico-system      pod/calico-typha-5bf9887dd7-v8plr               1/1     Running   1 (6m33s ago)   7m4s   10.240.0.5      kubernetes-worker   <none>           <none>
kube-system        pod/coredns-78fcd69978-dzwrb                    1/1     Running   0               31m    192.168.237.1   kubernetes-master   <none>           <none>
kube-system        pod/coredns-78fcd69978-ffpvj                    1/1     Running   0               31m    192.168.237.3   kubernetes-master   <none>           <none>
kube-system        pod/etcd-kubernetes-master                      1/1     Running   0               31m    10.240.0.4      kubernetes-master   <none>           <none>
kube-system        pod/kube-apiserver-kubernetes-master            1/1     Running   0               31m    10.240.0.4      kubernetes-master   <none>           <none>
kube-system        pod/kube-controller-manager-kubernetes-master   1/1     Running   0               31m    10.240.0.4      kubernetes-master   <none>           <none>
kube-system        pod/kube-proxy-xj444                            1/1     Running   0               7m7s   10.240.0.5      kubernetes-worker   <none>           <none>
kube-system        pod/kube-proxy-zk7mn                            1/1     Running   0               31m    10.240.0.4      kubernetes-master   <none>           <none>
kube-system        pod/kube-scheduler-kubernetes-master            1/1     Running   0               31m    10.240.0.4      kubernetes-master   <none>           <none>
tigera-operator    pod/tigera-operator-59f4845b57-qgq6c            1/1     Running   0               20m    10.240.0.4      kubernetes-master   <none>           <none>

NAMESPACE          NAME                                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE   SELECTOR
calico-apiserver   service/calico-api                        ClusterIP   10.110.66.91     <none>        443/TCP                  19m   apiserver=true
calico-system      service/calico-kube-controllers-metrics   ClusterIP   10.106.106.192   <none>        9094/TCP                 19m   k8s-app=calico-kube-controllers
calico-system      service/calico-typha                      ClusterIP   10.110.34.28     <none>        5473/TCP                 20m   k8s-app=calico-typha
default            service/kubernetes                        ClusterIP   10.96.0.1        <none>        443/TCP                  31m   <none>
kube-system        service/kube-dns                          ClusterIP   10.96.0.10       <none>        53/UDP,53/TCP,9153/TCP   31m   k8s-app=kube-dns

NAMESPACE       NAME                         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE   CONTAINERS    IMAGES                          SELECTOR
calico-system   daemonset.apps/calico-node   2         2         2       2            2           kubernetes.io/os=linux   20m   calico-node   docker.io/calico/node:v3.20.2   k8s-app=calico-node
kube-system     daemonset.apps/kube-proxy    2         2         2       2            2           kubernetes.io/os=linux   31m   kube-proxy    k8s.gcr.io/kube-proxy:v1.22.2   k8s-app=kube-proxy

NAMESPACE          NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS                IMAGES                                      SELECTOR
calico-apiserver   deployment.apps/calico-apiserver          1/1     1            1           19m   calico-apiserver          docker.io/calico/apiserver:v3.20.2          apiserver=true
calico-system      deployment.apps/calico-kube-controllers   1/1     1            1           20m   calico-kube-controllers   docker.io/calico/kube-controllers:v3.20.2   k8s-app=calico-kube-controllers
calico-system      deployment.apps/calico-typha              2/2     2            2           20m   calico-typha              docker.io/calico/typha:v3.20.2              k8s-app=calico-typha
kube-system        deployment.apps/coredns                   2/2     2            2           31m   coredns                   k8s.gcr.io/coredns/coredns:v1.8.4           k8s-app=kube-dns
tigera-operator    deployment.apps/tigera-operator           1/1     1            1           20m   tigera-operator           quay.io/tigera/operator:v1.20.4             name=tigera-operator

NAMESPACE          NAME                                                 DESIRED   CURRENT   READY   AGE   CONTAINERS                IMAGES                                      SELECTOR
calico-apiserver   replicaset.apps/calico-apiserver-6dd4bc68c6          1         1         1       19m   calico-apiserver          docker.io/calico/apiserver:v3.20.2          apiserver=true,pod-template-hash=6dd4bc68c6
calico-apiserver   replicaset.apps/calico-apiserver-788fc95f55          0         0         0       19m   calico-apiserver          docker.io/calico/apiserver:v3.20.2          apiserver=true,pod-template-hash=788fc95f55
calico-system      replicaset.apps/calico-kube-controllers-767ddd5576   1         1         1       20m   calico-kube-controllers   docker.io/calico/kube-controllers:v3.20.2   k8s-app=calico-kube-controllers,pod-template-hash=767ddd5576
calico-system      replicaset.apps/calico-typha-5bf9887dd7              2         2         2       20m   calico-typha              docker.io/calico/typha:v3.20.2              k8s-app=calico-typha,pod-template-hash=5bf9887dd7
kube-system        replicaset.apps/coredns-78fcd69978                   2         2         2       31m   coredns                   k8s.gcr.io/coredns/coredns:v1.8.4           k8s-app=kube-dns,pod-template-hash=78fcd69978
tigera-operator    replicaset.apps/tigera-operator-59f4845b57           1         1         1       20m   tigera-operator           quay.io/tigera/operator:v1.20.4             name=tigera-operator,pod-template-hash=59f4845b57

これで1master-1workerKubernetesクラスタをつくることができた。

後はVMのリソースが尽きない限り何でも自由にやりたい放題できる。

おかたづけ

GCPで建てたVMは今回masterに使用したe2-mediumだけでも1ヶ月で約3000円くらいかかってしまう。

おかねなくなる

お金に余裕があればいいんだけど、練習用のクラスタなので使わなくなったらさっさと片付けるべき。
(昔デカめのKubernetesクラスタを放置してたら無料枠$300が1ヶ月で吹き飛んた)

VMをそのまま吹き飛ばしても良いのだが、練習としてkubeadmを使ったお掃除をしてみる。

# masterでのみ実行する

# workerのPodを退去させる
kubectl drain kubernetes-worker --delete-local-data --force --ignore-daemonsets
# workerでのみ実行する

# kubadm joinによって設定されたものを設定前に戻す
sudo kubeadm reset
sudo rm -rf /etc/cni/net.d
# masterでのみ実行する

# workerを削除する
kubectl delete node kubernetes-worker

# kubeadm initによって設定されたものを設定前に戻す
sudo kubeadm reset
sudo rm -rf /etc/cni/net.d

あとは粛々とKubernetesコンポーネントとコンテナランタイムを削除していく。

# master/worker両方でそれぞれ実行する

# Kubernetesコンポーネントの削除
sudo apt-get purge -y --allow-change-held-packages kubelet kubeadm kubectl
sudo apt-get purge -y --allow-change-held-packages containerd.io

最後にgcloudで作ったリソースを削除する。

# Macで実行

# GCPリソースの削除(いきなりこれをやってもOK)
gcloud compute instances delete kubernetes-master kubernetes-worker
gcloud compute firewall-rules delete kubernetes-vpc-network-allow-internal kubernetes-vpc-network-allow-external
gcloud compute networks subnets delete kubernetes-vpc-network-subnet
gcloud compute networks delete kubernetes-vpc-network

これで綺麗さっぱりおかたづけできたのでお財布のダメージも少ないはず。

おわり

Creating a cluster with kubeadmをなぞって練習用のKubernetesクラスタをつくった。
基本的にはドキュメントさえ見ればできるんだけど、参照するドキュメントが何枚もあったりしてたまに混乱するので具体的な手順を1枚にできてよかった。

おまけ

くつろぐねこ