Raspberry Pi 5を利用しておうちKubernetes(v1.29)を構築する。今回が最後で、kubelet
、kube-proxy
、CoreDNS
のデプロイを行う。
前回はetcd
、kube-apiserver
、kube-controller-manager
、kube-scheduler
のデプロイまで実施した。
hirano00o.hateblo.jp
- kubeletのデプロイ
- kube-proxyのデプロイ
- ルーティング追加
- kubeletのkube-apiserver認証とRBAC設定
- Nodeの動作確認
- CoreDNSのデプロイ
- 最終確認
- オプション補完とエイリアス設定
- 終わりに
CyberAgentのリポジトリ を参考にしつつ、v1.29に合わせて手順やスクリプトを一部改変している。
Hardwayの手順は下記の通り。
No | 項目 | 構築対象 | 備考 |
---|---|---|---|
1 | 証明書の作成 | rs01 | |
2 | Kubeconfigの生成 | rs01 | |
3 | etcdのデプロイ | rs01 | |
4 | kube-apiserverのデプロイ | rs01 | |
5 | kube-controller-managerのデプロイ | rs01 | |
6 | kube-schedulerのデプロイ | rs01 | |
7 | kubeletのデプロイ | rs01 rs02 rs03 |
ここから再開 |
8 | kube-proxyのデプロイ | rs01 rs02 rs03 |
|
9 | ルーティング追加 | rs01 rs02 rs03 |
|
10 | kubeletのkube-apiserver認証とRBAC設定 | rs01 | |
11 | CoreDNSのデプロイ | rs01 |
改めてサブネットやホスト名等を下記に示しておく。
名前 | サブネット |
---|---|
自宅 | 10.105.136.0/22 |
Pod | 10.0.0.0/16 |
ClusterIP用 | 10.10.0.0/24 |
ホスト名 | IPアドレス | Pod用サブネット | ログインユーザ | 備考 |
---|---|---|---|---|
rs01 | 10.105.138.201 | 10.0.1.0/24 | hirano00o | Master, Worker兼用 |
rs02 | 10.105.138.202 | 10.0.2.0/24 | hirano00o | Worker |
rs03 | 10.105.138.203 | 10.0.3.0/24 | hirano00o | Worker |
kubeletのデプロイ
kubelet
はPodSpecsに従ってPodやコンテナの作成、開始、停止などの管理をする。またノードのリソース監視も行う。kube-scheduler
はこのリソース状況を利用し、スケジューリングされてないPodを最適なノードに配置する。
containerd
とrunc
を利用する。Kuberbetes v1.24以降、CNIはkubeletの管理範囲外となった。containerd
でCNIを管理する。
ここからは各ノードで同じ作業を行う。
cd sudo mkdir -p \ /etc/cni/net.d \ /opt/cni/bin \ /var/lib/kubelet \ /var/lib/kubernetes \ /etc/containerd sudo cp -ai ${HOSTNAME}-key.pem ${HOSTNAME}.pem /var/lib/kubelet/ sudo cp -ai ${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig sudo cp -ai ca.pem /var/lib/kubernetes/ # Masterは配置済みなのでスキップ mkdir -p ~/ws/k8s/ && cd $_ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubelet" curl -LO https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.29.0/crictl-v1.29.0-linux-arm64.tar.gz curl -LO https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-arm64-v1.4.0.tgz tar zxf crictl-v1.29.0-linux-arm64.tar.gz sudo tar zxvf cni-plugins-linux-arm64-v1.4.0.tgz -C /opt/cni/bin/ chmod +x crictl kubelet sudo mv crictl kubelet /usr/local/bin/ kubelet --version # Kubernetes v1.29.2 crictl --version # crictl version v1.29.0
containerd
, runc
, kubelet
の設定を行う。
CNIでプラグインを使う場合はv1.0以上が推奨なので 1.0.0 に変更する(1.1以上は未サポート)。 参考: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/
また、/run/systemd/resolve/resolv.conf
が存在しないので /etc/resolv.conf
に変更する。(systemd-resolved
がデフォルトではインストールされていないため)
最新のkubelet
を利用しているため、元の手順からは削除されているフラグ等がある。
--network-plugin
--container-runtime
--image-pull-progress-deadline
--container-runtime-endpoint
→KubeletConfiguration
へ移動、ソケットファイルのパスも変更
またKubeletConfiguration
に下記を追加している。
containerRuntimeEndpoint: "unix:///run/containerd/containerd.sock"
cgroupDriver: systemd
- cgroup v2ならsystemdが推奨とのこと
containerd --version # containerd github.com/containerd/containerd 1.6.20~ds1 1.6.20~ds1-1+b1 runc --version # runc version 1.1.5+ds1 # commit: 1.1.5+ds1-1+deb12u1 # spec: 1.0.2-dev # go: go1.19.8 # libseccomp: 2.5.4 POD_CIDR="10.0.1.0/24" # rs02は 10.0.2.0/24, rs03は 10.0.3.0/24 cat <<EOF | sudo tee /etc/cni/net.d/10-bridge.conf { "cniVersion": "1.0.0", "name": "bridge", "type": "bridge", "bridge": "cnio0", "isGateway": true, "ipMasq": true, "ipam": { "type": "host-local", "ranges": [ [{"subnet": "${POD_CIDR}"}] ], "routes": [{"dst": "0.0.0.0/0"}] } } EOF cat <<EOF | sudo tee /etc/cni/net.d/99-loopback.conf { "cniVersion": "1.0.0", "name": "lo", "type": "loopback" } EOF containerd config default | sudo tee /etc/containerd/config.toml sudo vi /etc/containerd/config.toml # 下記のようにSystemdCgroupをtrueに変更する [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true sudo systemctl restart containerd CLUSTER_DNS_ADDRESS="10.10.0.10" cat <<EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml kind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 authentication: anonymous: enabled: false webhook: enabled: true x509: clientCAFile: "/var/lib/kubernetes/ca.pem" authorization: mode: Webhook clusterDomain: "cluster.local" clusterDNS: - "${CLUSTER_DNS_ADDRESS}" podCIDR: "${POD_CIDR}" resolvConf: "/etc/resolv.conf" runtimeRequestTimeout: "15m" tlsCertFile: "/var/lib/kubelet/${HOSTNAME}.pem" tlsPrivateKeyFile: "/var/lib/kubelet/${HOSTNAME}-key.pem" containerRuntimeEndpoint: "unix:///run/containerd/containerd.sock" cgroupDriver: systemd EOF cat <<EOF | sudo tee /etc/systemd/system/kubelet.service [Unit] Description=Kubernetes Kubelet Documentation=https://github.com/kubernetes/kubernetes After=containerd.service Requires=containerd.service [Service] ExecStart=/usr/local/bin/kubelet \\ --config=/var/lib/kubelet/kubelet-config.yaml \\ --kubeconfig=/var/lib/kubelet/kubeconfig \\ --register-node=true \\ --v=2 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl start kubelet sudo systemctl status kubelet sudo systemctl enable kubelet
kube-proxyのデプロイ
kube-proxy
は各ノードのネットワーク設定を行い、クラスタ内部や外部ネットワークからPodへの通信が可能になる。
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kube-proxy" chmod +x kube-proxy sudo mv kube-proxy /usr/local/bin/ sudo mkdir -p /var/lib/kube-proxy sudo mv ~/kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig POD_NETWORK="10.0.0.0/16" cat <<EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml kind: KubeProxyConfiguration apiVersion: kubeproxy.config.k8s.io/v1alpha1 clientConnection: kubeconfig: "/var/lib/kube-proxy/kubeconfig" mode: "iptables" clusterCIDR: "${POD_NETWORK}" EOF cat <<EOF | sudo tee /etc/systemd/system/kube-proxy.service [Unit] Description=Kubernetes Kube Proxy Documentation=https://github.com/kubernetes/kubernetes [Service] ExecStart=/usr/local/bin/kube-proxy \\ --config=/var/lib/kube-proxy/kube-proxy-config.yaml Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl start kube-proxy sudo systemctl status kube-proxy sudo systemctl enable kube-proxy
ルーティング追加
各NodeからPod用NWに到達させるためにルーティングを追加する。
# Master node(rs01) sudo nmcli connection modify eth0 +ipv4.routes "10.0.2.0/24 10.105.138.202" sudo nmcli connection modify eth0 +ipv4.routes "10.0.3.0/24 10.105.138.203" # Worker node1(rs02) sudo nmcli connection modify eth0 +ipv4.routes "10.0.1.0/24 10.105.138.201" sudo nmcli connection modify eth0 +ipv4.routes "10.0.3.0/24 10.105.138.203" # Worker node2(rs03) sudo nmcli connection modify eth0 +ipv4.routes "10.0.1.0/24 10.105.138.201" sudo nmcli connection modify eth0 +ipv4.routes "10.0.2.0/24 10.105.138.202" sudo reboot # 投入したルーティングが追加されているか確認 ip route
kubeletのkube-apiserver認証とRBAC設定
ここからはMaster nodeのみで行う。kube-apiserver
が各ノードのkubelet
にアクセスできるように設定する。
cd ~/ws/k8s/ cat <<EOF | kubectl apply --kubeconfig kubeconfig/admin.kubeconfig -f - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: "true" labels: kubernetes.io/bootstrapping: rbac-defaults name: system:kube-apiserver-to-kubelet rules: - apiGroups: - "" resources: - nodes/proxy - nodes/stats - nodes/log - nodes/spec - nodes/metrics verbs: - "*" EOF cat <<EOF | kubectl apply --kubeconfig kubeconfig/admin.kubeconfig -f - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:kube-apiserver namespace: "" roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:kube-apiserver-to-kubelet subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: kubernetes EOF
Nodeの動作確認
ノードを認識できているか確認する。
kubectl get node # NAME STATUS ROLES AGE VERSION # rs01 Ready <none> 4m28s v1.29.2 # rs02 Ready <none> 4m28s v1.29.2 # rs03 Ready <none> 4m28s v1.29.2
CoreDNSのデプロイ
クラスタ内部で名前解決を行うために実施する。
cd ~/ws/k8s/ vi coredns-1.11.1.yaml # 下記マニフェスト参照 kubectl apply -f coredns-1.11.1.yaml
coredns-1.11.1.yaml
apiVersion: v1 kind: ServiceAccount metadata: name: coredns namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: kubernetes.io/bootstrapping: rbac-defaults name: system:coredns rules: - apiGroups: - "" resources: - endpoints - services - pods - namespaces verbs: - list - watch - apiGroups: - "" resources: - nodes verbs: - get - apiGroups: - "discovery.k8s.io" resources: - endpointslices verbs: - list - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: "true" labels: kubernetes.io/bootstrapping: rbac-defaults name: system:coredns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:coredns subjects: - kind: ServiceAccount name: coredns namespace: kube-system --- apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance } --- apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/name: "CoreDNS" spec: replicas: 2 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns spec: priorityClassName: system-cluster-critical serviceAccountName: coredns tolerations: - key: "CriticalAddonsOnly" operator: "Exists" nodeSelector: kubernetes.io/os: linux containers: - name: coredns image: coredns/coredns:1.11.1 imagePullPolicy: IfNotPresent resources: limits: memory: 170Mi requests: cpu: 100m memory: 70Mi args: [ "-conf", "/etc/coredns/Corefile" ] volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - all readOnlyRootFilesystem: true livenessProbe: httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 scheme: HTTP dnsPolicy: Default volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefile --- apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system annotations: prometheus.io/port: "9153" prometheus.io/scrape: "true" labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" kubernetes.io/name: "CoreDNS" spec: selector: k8s-app: kube-dns clusterIP: 10.10.0.10 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP - name: metrics port: 9153 protocol: TCP
マニフェストでやっていることは下記の通り。
- kube-system namespaceにcorednsのサービスアカウントを作成
- 権限(
endpointslices
)が足りてないようだったので追加した
- 権限(
- system:corednsのクラスタロールを作成
- corednsのサービスアカウントにsystem:corednsの権限を付与
- DNSの設定
- CoreDNSのデプロイ
- サービスとして公開
- clusterIPは環境に合わせて書き換えること
デプロイしたらDNSが動いているか確認する。動いてなさそうだったらログを確認する(kubectl logs -f -n kube-system <ポッド名>
)。
# STATUSがRunningでもREADYじゃなかったらdescribeでEventを見たり、ログを確認する kubectl get po -n kube-system -o wide # NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES # coredns-5774666f6d-wntvb 1/1 Running 0 3h44m 10.0.3.8 rs03 <none> <none> # coredns-5774666f6d-xj686 1/1 Running 0 3h44m 10.0.2.15 rs02 <none> <none> kubectl describe po -n kube-system $(kubectl get po -n kube-system --output=jsonpath='{.items[0].metadata.name}') # Name: coredns-5774666f6d-wntvb # Namespace: kube-system # Priority: 2000000000 # Priority Class Name: system-cluster-critical # Service Account: coredns # Node: rs03/10.105.138.203 # ... # Events: <none> kubectl logs -f -n kube-system $(kubectl get po -n kube-system --output=jsonpath='{.items[0].metadata.name}') # ...
Podから名前解決できるか確認する。
# 一旦kubernetes.default.svc.cluster.localは解決できてるので良しとする kubectl run test --image busybox:1.36 --restart Never -it --rm -- nslookup kubernetes # If you don't see a command prompt, try pressing enter. # warning: couldn't attach to pod/test, falling back to streaming logs: unable to upgrade connection: container test not found in pod test_default # Server: 10.10.0.10 # Address: 10.10.0.10:53 # # ** server can't find kubernetes.cluster.local: NXDOMAIN # # ** server can't find kubernetes.cluster.local: NXDOMAIN # # ** server can't find kubernetes.svc.cluster.local: NXDOMAIN # # ** server can't find kubernetes.svc.cluster.local: NXDOMAIN # # # Name: kubernetes.default.svc.cluster.local # Address: 10.10.0.1 # # pod "test" deleted # pod default/test terminated (Error) kubectl run test --image busybox:1.36 --restart Never -it --rm -- nslookup google.com # Server: 10.10.0.10 # Address: 10.10.0.10:53 # # Name: google.com # Address: 172.217.175.46 # # Name: google.com # Address: 2404:6800:4004:820::200e # # pod "test" deleted
最終確認
暗号化されたsecretが保存されることを確認する。
kubectl create secret generic kubernetes-the-hard-way \ --from-literal="mykey=mydata" sudo ETCDCTL_API=3 etcdctl get \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/etcd/ca.pem \ --cert=/etc/etcd/kubernetes.pem \ --key=/etc/etcd/kubernetes-key.pem \ /registry/secrets/default/kubernetes-the-hard-way | hexdump -C # 00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret| # 00000010 73 2f 64 65 66 61 75 6c 74 2f 6b 75 62 65 72 6e |s/default/kubern| # 00000020 65 74 65 73 2d 74 68 65 2d 68 61 72 64 2d 77 61 |etes-the-hard-wa| # 00000030 79 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 |y.k8s:enc:aescbc| # 00000040 3a 76 31 3a 6b 65 79 31 3a 06 2d 76 2e 57 e4 c7 |:v1:key1:.-v.W..| # 00000050 84 a6 ee 1f 86 8d 46 af b8 81 98 ec 36 a6 01 89 |......F.....6...| # 00000060 19 58 d5 4d 3a 0c c6 f1 3f 30 0f 7f aa 5d 43 80 |.X.M:...?0...]C.| # 00000070 3a eb fb 97 0e 7c 92 4b 39 21 5b a0 80 12 ea 41 |:....|.K9![....A| # 00000080 98 bb 81 4c 76 d7 e3 9d 2e 98 b5 83 54 b0 c4 b6 |...Lv.......T...| # 00000090 b7 34 85 6f fa 79 16 1f 4d 33 de a8 d0 d9 b4 1a |.4.o.y..M3......| # 000000a0 0a b8 55 d4 d8 39 cb e6 3e ec 9a 34 4d 31 9f a3 |..U..9..>..4M1..| # 000000b0 33 dd ef 4d 20 23 da 17 67 e4 a7 24 f5 39 e8 d3 |3..M #..g..$.9..| # 000000c0 c2 59 32 88 93 60 d1 3e dc ff 47 4f 9a 63 4e b0 |.Y2..`.>..GO.cN.| # 000000d0 fe 8d 27 1c dd 71 de f2 62 43 42 65 cb 88 80 12 |..'..q..bCBe....| # 000000e0 03 c6 56 b1 22 bd 17 d3 7a 12 46 91 10 52 5e 79 |..V."...z.F..R^y| # 000000f0 09 c7 a0 79 6f 80 f4 58 a1 c3 38 5e 75 84 79 b0 |...yo..X..8^u.y.| # 00000100 70 a7 06 32 d0 6a ce 0f 42 ea 27 c7 ce 0a 6d 3f |p..2.j..B.'...m?| # 00000110 19 f2 fd a9 55 87 db 72 1b 06 5b 42 42 b2 cb ae |....U..r..[BB...| # 00000120 14 be 8a e4 24 dc d5 ee 8d cd 50 ff 7a a6 bc 63 |....$.....P.z..c| # 00000130 6d 25 77 2e 0e 87 cf 59 3e 5f e6 4e 61 49 c9 1b |m%w....Y>_.NaI..| # 00000140 ea e6 b0 91 4b 73 16 65 6b 87 7e e4 02 55 6b aa |....Ks.ek.~..Uk.| # 00000150 77 26 e0 90 b2 19 88 c5 75 0a |w&......u.| # 0000015a kubectl delete secret kubernetes-the-hard-way # secret "kubernetes-the-hard-way" deleted
nginxを動かし、いくつか確認する。
kubectl create deployment nginx --image=nginx kubectl get pods -o wide # NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES # nginx-7854ff8877-q5lws 1/1 Running 0 2m7s 10.0.1.13 rs01 <none> <none>
ポートフォワーディングを確認する。
POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}") kubectl port-forward $POD_NAME 8081:80 # Forwarding from 127.0.0.1:8081 -> 80 # Forwarding from [::1]:8081 -> 80 # 別ターミナルから操作 curl --head http://127.0.0.1:8081 # HTTP/1.1 200 OK # Server: nginx/1.25.4 # Date: Tue, 27 Feb 2024 10:38:25 GMT # Content-Type: text/html # Content-Length: 615 # Last-Modified: Wed, 14 Feb 2024 16:03:00 GMT # Connection: keep-alive # ETag: "65cce434-267" # Accept-Ranges: bytes
ログを確認する。
kubectl logs $POD_NAME # ... # 127.0.0.1 - - [27/Feb/2024:10:38:25 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.88.1" "-" # ...
Pod上でコマンドを実行する。
kubectl exec -ti $POD_NAME -- nginx -v # nginx version: nginx/1.25.4
NodePortを通じてアクセスできることを確認する。
kubectl expose deployment nginx --port 80 --type NodePort # service/nginx exposed kubectl get svc # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE # kubernetes ClusterIP 10.10.0.1 <none> 443/TCP 3d21h # nginx NodePort 10.10.0.233 <none> 80:32663/TCP 7s curl -I http://10.105.138.201:32663 # HTTP/1.1 200 OK # Server: nginx/1.25.4 # Date: Tue, 27 Feb 2024 10:45:20 GMT # Content-Type: text/html # Content-Length: 615 # Last-Modified: Wed, 14 Feb 2024 16:03:00 GMT # Connection: keep-alive # ETag: "65cce434-267" # Accept-Ranges: bytes curl -I http://10.105.138.202:32663 # HTTP/1.1 200 OK # Server: nginx/1.25.4 # Date: Tue, 27 Feb 2024 10:45:23 GMT # Content-Type: text/html # Content-Length: 615 # Last-Modified: Wed, 14 Feb 2024 16:03:00 GMT # Connection: keep-alive # ETag: "65cce434-267" # Accept-Ranges: bytes curl -I http://10.105.138.203:32663 # HTTP/1.1 200 OK # Server: nginx/1.25.4 # Date: Tue, 27 Feb 2024 10:45:27 GMT # Content-Type: text/html # Content-Length: 615 # Last-Modified: Wed, 14 Feb 2024 16:03:00 GMT # Connection: keep-alive # ETag: "65cce434-267" # Accept-Ranges: bytes
オプション補完とエイリアス設定
cat <<EOF >> ~/.bashrc source <(kubectl completion bash) alias k='kubectl' complete -o default -F __start_kubectl k EOF source ~/.bashrc
終わりに
Raspberry PiでKubernetesクラスタを構築した。この後は、CNIとkube-proxy
をCiliumに入れ替えてみようと思う。また、外部からデプロイしたアプリケーションにアクセスできるようにするために、cloudflaredを利用するなど、やることは多い。
最終的には、パブリッククラウドの無料枠で動かしているサービスを移行して、すべて自宅でホストできるようにしたい。