ラズパイkubernetes構築メモ

構築手順はたくさん紹介されていますが、
やってみるといろいろ調べながら進める必要があったのでまとめようと思います。

raspberrypi_k8s_01.jpeg

というわけで物理構築完了。
インフラエンジニアにあるまじきケーブルマネジメントですね!
本番環境で本気出す。

物理構成については省略します。
他のサイト様を参考にしてください。

以降の論理構築手順は主にこちら を参考にさせていただきました。

OSインストール

今回はUbuntu Server 20.04 LTS 64bitで構築しました。
Raspberry Pi Imagerでさくっと書き込みます。

raspberrypi_k8s_02.png

起動しましたらIPを固定します。

ubuntu@ubuntu:~$ sudo vi /etc/netplan/99_config.yaml
ubuntu@ubuntu:~$ cat /etc/netplan/99_config.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      dhcp4: false
      dhcp6: false
      addresses: [192.168.10.10/24]
      gateway4: 192.168.10.1
      nameservers:
        addresses: [192.168.10.1]
ubuntu@ubuntu:~$ sudo netplan apply
ubuntu@ubuntu:~$ sudo hostnamectl set-hostname k8s1
ubuntu@ubuntu:~$ sudo reboot
ubuntu@k8s1:~$ sudo apt update && sudo apt -y upgrade
ubuntu@k8s1:~$ cat << _EOF_ | sudo tee -a /etc/hosts
192.168.10.10  k8s1
192.168.10.11  k8s2
192.168.10.12  k8s3
_EOF_

ラズパイ3台を上記のように設定して、以下のようなネットワーク構成にしました。

raspberrypi_k8s_03.png

containerdインストール

もたもたしてたらDockerランタイムが非推奨になったのでcontainerdで構築してみます。
手順は こちら を参考に進めます。
3台ともインストールしてください。

ubuntu@k8s1:~$ cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
> overlay
> br_netfilter
> EOF
overlay
br_netfilter
ubuntu@k8s1:~$ sudo modprobe overlay
ubuntu@k8s1:~$ sudo modprobe br_netfilter
ubuntu@k8s1:~$ 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
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
ubuntu@k8s1:~$ sudo sysctl --system
* Applying /etc/sysctl.d/10-console-messages.conf ...
kernel.printk = 4 4 1 7
* Applying /etc/sysctl.d/10-ipv6-privacy.conf ...
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
* Applying /etc/sysctl.d/10-kernel-hardening.conf ...
kernel.kptr_restrict = 1
* Applying /etc/sysctl.d/10-link-restrictions.conf ...
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
* Applying /etc/sysctl.d/10-magic-sysrq.conf ...
kernel.sysrq = 176
* Applying /etc/sysctl.d/10-network-security.conf ...
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
* Applying /etc/sysctl.d/10-ptrace.conf ...
kernel.yama.ptrace_scope = 1
* Applying /etc/sysctl.d/10-zeropage.conf ...
vm.mmap_min_addr = 32768
* Applying /usr/lib/sysctl.d/50-default.conf ...
net.ipv4.conf.default.promote_secondaries = 1
sysctl: setting key "net.ipv4.conf.all.promote_secondaries": Invalid argument
net.ipv4.ping_group_range = 0 2147483647
net.core.default_qdisc = fq_codel
fs.protected_regular = 1
fs.protected_fifos = 1
* Applying /usr/lib/sysctl.d/50-pid-max.conf ...
kernel.pid_max = 4194304
* Applying /etc/sysctl.d/99-cloudimg-ipv6.conf ...
net.ipv6.conf.all.use_tempaddr = 0
net.ipv6.conf.default.use_tempaddr = 0
* Applying /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
* Applying /etc/sysctl.d/99-sysctl.conf ...
* Applying /usr/lib/sysctl.d/protect-links.conf ...
fs.protected_fifos = 1
fs.protected_hardlinks = 1
fs.protected_regular = 2
fs.protected_symlinks = 1
* Applying /etc/sysctl.conf ...

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

ubuntu@k8s1:~$ sudo apt-get update && sudo apt-get install -y containerd

バージョンはこちらになりました。

ubuntu@k8s1:~$ containerd -v
containerd github.com/containerd/containerd 1.3.3-0ubuntu2.2 

containerdの設定をします。

ubuntu@k8s1:~$ sudo mkdir -p /etc/containerd
ubuntu@k8s1:~$ sudo containerd config default | sudo tee /etc/containerd/config.toml
ubuntu@k8s1:~$ sudo systemctl restart containerd

kubeadmインストール

こちら を参考に進めます。
こちらも3台ともインストールしてください。

ubuntu@k8s1:~$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
> br_netfilter
> EOF
br_netfilter
ubuntu@k8s1:~$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
> net.bridge.bridge-nf-call-ip6tables = 1
> net.bridge.bridge-nf-call-iptables = 1
> EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
ubuntu@k8s1:~$ sudo sysctl --system

インストールします。

ubuntu@k8s1:~$ sudo apt-get update && sudo apt-get install -y apt-transport-https curl
ubuntu@k8s1:~$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
OK
ubuntu@k8s1:~$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
> deb https://apt.kubernetes.io/ kubernetes-xenial main
> EOF
deb https://apt.kubernetes.io/ kubernetes-xenial main
ubuntu@k8s1:~$ sudo apt-get update
ubuntu@k8s1:~$ sudo apt-get install -y kubelet kubeadm kubectl
ubuntu@k8s1:~$ sudo apt-mark hold kubelet kubeadm kubectl
kubelet set on hold.
kubeadm set on hold.
kubectl set on hold.

バージョンはこちらになりました。

ubuntu@k8s1:~$ kubelet --version
Kubernetes v1.20.2
ubuntu@k8s1:~$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:25:59Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/arm64"}
ubuntu@k8s1:~$ kubectl version
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:28:09Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/arm64"}
The connection to the server localhost:8080 was refused - did you specify the right host or port?

kubernetesクラスタ構築

コントロールプレーンノードを初期化します。
本環境ではk8s1で実行します。

ubuntu@k8s1:~$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --control-plane-endpoint=k8s1 --apiserver-cert-extra-sans=k8s1

~(省略)~

CGROUPS_HUGETLB: missing
  [WARNING SystemVerification]: missing optional cgroups: hugetlb
error execution phase preflight: [preflight] Some fatal errors occurred:
  [ERROR CRI]: container runtime is not running: output: time="2021-01-27T15:32:56Z" level=fatal msg="getting status of runtime failed: rpc error: code = Unimplemented desc = unknown service runtime.v1alpha2.RuntimeService"
, error: exit status 1
  [ERROR SystemVerification]: missing required cgroups: memory

エラーになりました。
調べたらcgroupのmemoryを有効化する必要があるらしいです。
こちら を参考にさせていただきました。
3台とも実施してください。

ubuntu@k8s1:~$ sudo vi /boot/firmware/cmdline.txt
ubuntu@k8s1:~$ cat /boot/firmware/cmdline.txt
net.ifnames=0 dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=LABEL=writable rootfstype=ext4 elevator=deadline rootwait fixrtc cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
ubuntu@k8s1:~$ sudo reboot

ではもう一度やってみます。

ubuntu@k8s1:~$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --control-plane-endpoint=k8s1 --apiserver-cert-extra-sans=k8s1

~(省略)~

Your Kubernetes control-plane has initialized successfully!

  kubeadm join k8s1:6443 --token p66b3o.jpxsfo4cig6oqufh \
    --discovery-token-ca-cert-hash sha256:17c9e27331822aea1326e8678724ba3e907cd6d1679eaf8d5ee662dd3ab6eb99 \
    --control-plane 

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

今度はうまく行きました。
認証情報の設定をしておきます。

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

では残りの2台を参加させます。
上記のように、実行するkubeadm joinコマンドが表示されるのでそれを実行します。

ubuntu@k8s2:~$ sudo kubeadm join k8s1:6443 --token p66b3o.jpxsfo4cig6oqufh --discovery-token-ca-cert-hash sha256:17c9e27331822aea1326e8678724ba3e907cd6d1679eaf8d5ee662dd3ab6eb99 
[preflight] Running pre-flight checks
	[WARNING SystemVerification]: missing optional cgroups: hugetlb
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

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.

確認してみます。
無事参加できてますね。

ubuntu@k8s1:~$ kubectl get nodes
NAME   STATUS     ROLES                  AGE   VERSION
k8s1   NotReady   control-plane,master   21h   v1.20.2
k8s2   NotReady   <none>                 85s   v1.20.2
k8s3   NotReady   <none>                 29s   v1.20.2

しかしステータスがNotReadyですね。
Flannelを入れました。

ubuntu@k8s1:~$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created

少し待つと無事Readyになりました。

ubuntu@k8s1:~$ kubectl get nodes
NAME   STATUS   ROLES                  AGE   VERSION
k8s1   Ready    control-plane,master   21h   v1.20.2
k8s2   Ready    <none>                 12m   v1.20.2
k8s3   Ready    <none>                 11m   v1.20.2

これで無事クラスタ構築完了です。

MetalLBインストール

クラスタは構築できましたがKubernetesにはロードバランサーが標準装備されてないため、
このままだと外部からPodにアクセスできません。

試しにLoadBalancerを作ってみても、
以下のようにEXTERNAL-IPがpendingのまま進まないと思います。

ubuntu@k8s1:~$ kubectl get services
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1        <none>        443/TCP        21h
nginx        LoadBalancer   10.108.212.205   <pending>     80:30455/TCP   2m1s

自分の環境に合わせて実装しないといけないそうですが、
よくわからないのでとりあえずMetalLBを実装してみたらうまく行きました。
手順はこちら を参考に進めます。

ubuntu@k8s1:~$ kubectl get configmap kube-proxy -n kube-system -o yaml | \
> sed -e "s/strictARP: false/strictARP: true/" | \
> kubectl diff -f - -n kube-system
diff -u -N /tmp/LIVE-749915535/v1.ConfigMap.kube-system.kube-proxy /tmp/MERGED-443163554/v1.ConfigMap.kube-system.kube-proxy
--- /tmp/LIVE-749915535/v1.ConfigMap.kube-system.kube-proxy 2021-01-28 13:24:07.530118613 +0000
+++ /tmp/MERGED-443163554/v1.ConfigMap.kube-system.kube-proxy 2021-01-28 13:24:07.546117893 +0000
@@ -30,7 +30,7 @@
       excludeCIDRs: null
       minSyncPeriod: 0s
       scheduler: ""
-      strictARP: false
+      strictARP: true
       syncPeriod: 0s
       tcpFinTimeout: 0s
       tcpTimeout: 0s
@@ -79,7 +79,6 @@
     fieldsV1:
       f:data:
         .: {}
-        f:config.conf: {}
         f:kubeconfig.conf: {}
       f:metadata:
         f:annotations:
@@ -91,6 +90,14 @@
     manager: kubeadm
     operation: Update
     time: "2021-01-27T15:40:26Z"
+  - apiVersion: v1
+    fieldsType: FieldsV1
+    fieldsV1:
+      f:data:
+        f:config.conf: {}
+    manager: kubectl-client-side-apply
+    operation: Update
+    time: "2021-01-28T13:24:07Z"
   name: kube-proxy
   namespace: kube-system
   resourceVersion: "262"
ubuntu@k8s1:~$ kubectl get configmap kube-proxy -n kube-system -o yaml | \
> sed -e "s/strictARP: false/strictARP: true/" | \
> kubectl apply -f - -n kube-system
Warning: resource configmaps/kube-proxy is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
configmap/kube-proxy configured

マニフェストでインストールします。

ubuntu@k8s1:~$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.5/manifests/namespace.yaml
namespace/metallb-system created
ubuntu@k8s1:~$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.5/manifests/metallb.yaml
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
ubuntu@k8s1:~$ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
secret/memberlist created

設定はこちら を参考にさせていただきました。
L2モードで設定します。

ubuntu@k8s1:~$ vi l2.yml
ubuntu@k8s1:~$ cat l2.yml 
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 192.168.10.240/28
ubuntu@k8s1:~$ kubectl apply -f l2.yml
configmap/config created

nginxを起動してみる

やっと動かせます。

ubuntu@k8s1:~$ kubectl create deployment nginx-deployment --image=nginx:latest
deployment.apps/nginx-deployment created
ubuntu@k8s1:~$ kubectl get pods
NAME                                READY   STATUS        RESTARTS   AGE
nginx-deployment-59fc6cb7f9-x58pf   1/1     Running       0          17s
ubuntu@k8s1:~$ kubectl expose deployment/nginx-deployment --type LoadBalancer --port 80 --name nginx-service
service/nginx-service exposed
ubuntu@k8s1:~$ kubectl get services
NAME            TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
kubernetes      ClusterIP      10.96.0.1        <none>           443/TCP        22h
nginx-service   LoadBalancer   10.101.104.190   192.168.10.240   80:31195/TCP   14s

無事EXTERNAL-IPが付与されました。
ブラウザでもアクセスすることができたので良さそうです。

raspberrypi_k8s_04.png

やっと手元に試せる環境ができました。
これからいろいろ試して勉強しようと思います。