.. _arm_k8s_deploy:
========================
部署ARM架构Kubernetes
========================
部署环境
===========
树莓派4操作系统采用Ubuntu 20.04(Focal Fossa),提供了64位ARM操作系统,可以非常完美运行AArch64容器镜像,这样可以避免很多32位镜像的软件问题。通过树莓派实现微型Kubernetes集群,可以充分演练新的云原生技术。
.. note::
从技术上来说 ``AArch64`` 和 ``ARM64`` 是同一种架构,是64位系统。ARM和x86的指令集不同,所以不能在x86服务器上运行ARM65镜像,反之也不行。
准备工作
==========
- 准备3个(或更多) :ref:`raspberry_pi` ,我采用了:
- 1台 2G 规格 :ref:`pi_4` :用于管控 ``pi-master1`` ( 操作系统采用 :ref:`ubuntu64bit_pi` )
- 2台 8G 规格 :ref:`pi_4` :用于工作节点 ``pi-worker1`` 和 ``pi-worker2`` ( 操作系统采用 :ref:`ubuntu64bit_pi` )
- 1台 4G 规格 :ref:`pi_400` : 用于工作节点 ``kali`` ( 操作系统采用 :ref:`install_kali_pi` )
在构建Kubernetes集群之前,主要需要解决树莓派访问TF卡性能低下的问题,采用 :ref:`usb_boot_ubuntu_pi_4` 可以极大提高树莓派存储IO性能。
- 我也使用了一台 :ref:`jetson_nano` 设备作为GPU工作节点
- 为了测试和验证Kubernetes混合不同架构,在ARM集群中添加一台 :ref:`thinkpad_x220` 运行 :ref:`arch_linux` 作为模拟X86异构Kubernetes工作节点
安装和配置Docker
==================
我使用 :ref:`ubuntu64bit_pi` ,使用的Ubuntu 20.04 提供了非常新的Docker版本,v19.03,可以直接通过 ``apt`` 命令安装::
sudo apt install -y docker.io
设置systemd管理cgroups
--------------------------
安装完docker之后,需要做一些配置确保激活 cgroups (Control Groups)。cgroups是内核用用限制和隔离资源,可以让Kubernetes更好地管理容器运行时使用地资源,并且通过隔离容器来增加安全性。
- 执行 ``docker info`` 检查::
# Check `docker info`
# Some output omitted
$ sudo docker info
(...)
Cgroup Driver: cgroups
Cgroup Version: 1
(...)
WARNING: No memory limit support
WARNING: No swap limit support
WARNING: No kernel memory limit support
WARNING: No kernel memory TCP limit support
WARNING: No oom kill disable support
.. note::
请注意默认使用 ``Cgroup Version: 1`` ,目前最新版本 :ref:`docker_cgroup_v2` ,可以提供精细的io隔离功能
这里显示 cgroups 驱动需要修改成 :ref:`systemd` 作为 cgroups 管理器,并且确保只使用一个cgroup manager。所以修改或者创建 ``/etc/docker/daemon.json`` 如下::
$ sudo cat > /etc/docker/daemon.json <`_ 详细功能请参考 :ref:`docker_20.10`
激活cgroups limit支持
-------------------------
上述 ``docker info`` 输出中显示了cgroups limit没有激活,需要修改内核来激活这些选项。对于树莓派4,需要在 ``/boot/firmware/cmdline.txt`` 文件中添加以下配置::
cgroup_enable=cpuset
cgroup_enable=memory
cgroup_memory=1
swapaccount=1
确保将上述配置添加到 ``cmdline.txt`` 文件到末尾,可以通过以下 ``sed`` 命令完成::
sudo sed -i '$ s/$/ cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1/' /boot/firmware/cmdline.txt
接下来重启一次系统,就会看到 ``docker info`` 输出显示 ``cgroups driver`` 是 ``systemd`` 并且有关 cgroup limits 的警告消失了。
允许iptables查看bridged流量
-----------------------------
Kubernetes需要使用iptables来配置查看bridged网络流量,可以通过以下命令修改 ``sysctl`` 配置::
cat <
kube-system coredns-f9fd979d6-hbqx9 0/1 Pending 0 2d10h
kube-system etcd-pi-master1 1/1 Running 0 11h 192.168.6.11 pi-master1
kube-system kube-apiserver-pi-master1 1/1 Running 0 11h 192.168.6.11 pi-master1
kube-system kube-controller-manager-pi-master1 1/1 Running 1 2d10h 192.168.6.11 pi-master1
kube-system kube-proxy-525kd 1/1 Running 1 2d10h 192.168.6.11 pi-master1
kube-system kube-scheduler-pi-master1 1/1 Running 1 2d10h 192.168.6.11 pi-master1
- 不过检查集群apiserver访问正常::
kubectl cluster-info
显示输出::
Kubernetes master is running at https://192.168.6.11:6443
KubeDNS is running at https://192.168.6.11:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
排查解决node NotReady
----------------------
- 检查 ``pending`` 的pod原因
.. code-block:: bash
kubectl -n kube-system describe pods coredns-f9fd979d6-gd94x
可以看到是由于调度不成功导致
.. code-block:: console
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 58s (x215 over 5h21m) default-scheduler 0/1 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate.
- 检查节点 NotReady 的原因::
kubectl describe nodes pi-master1
输出显示::
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
MemoryPressure False Wed, 02 Dec 2020 22:59:43 +0800 Sun, 29 Nov 2020 23:53:52 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Wed, 02 Dec 2020 22:59:43 +0800 Sun, 29 Nov 2020 23:53:52 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False Wed, 02 Dec 2020 22:59:43 +0800 Sun, 29 Nov 2020 23:53:52 +0800 KubeletHasSufficientPID kubelet has sufficient PID available
Ready False Wed, 02 Dec 2020 22:59:43 +0800 Sun, 29 Nov 2020 23:53:52 +0800 KubeletNotReady runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
可以看到 ``KubeletNotReady`` 的原因是 ``runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized`` ,也就是说,我们指定使用 flannel网络的CNI没有就绪,导致docker的runtime network不能工作。
重建集群实践
--------------
我在 :ref:`delete_kubeadm_cluster` 之后重新创建集群,吸取了双网卡对集群初始化的配置要求,所以命令改为::
sudo kubeadm init --token=${TOKEN} --kubernetes-version=v1.22.0 --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address 192.168.6.11
输出信息:
.. literalinclude:: arm_kubeadm_init_again.output
:linenos:
.. note::
这里输出信息中有::
...
[WARNING SystemVerification]: missing optional cgroups: hugetlb
这是在 :ref:`cgroup_v1` 中支持的 :ref:`cgroup_v1_hugetlb`
安装CNI插件
-------------
CNI插件处理pod网络的配置和清理,这里使用最简单的flannel CNI插件,只需要下载和 ``kubeclt apply`` Flannel YAML就可以安装好::
# Download the Flannel YAML data and apply it
# (output omitted)
#$ curl -sSL https://raw.githubusercontent.com/coreos/flannel/v0.12.0/Documentation/kube-flannel.yml | kubectl apply -f -
# 从Kubernetes v1.17+可以使用以下命令
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
- 果然,正确安装了 Flannel 网络插件之后再检查节点状态就恢复 ``Ready`` ::
kubectl get nodes
已经恢复正常状态::
NAME STATUS ROLES AGE VERSION
pi-master1 Ready master 2d23h v1.19.4
- 同时检查pod状态::
kubectl get pods --all-namespaces -o wide
可以看到 coredns 也恢复正航运行::
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-f9fd979d6-gd94x 1/1 Running 0 2d23h 10.244.0.3 pi-master1
kube-system coredns-f9fd979d6-hbqx9 1/1 Running 0 2d23h 10.244.0.2 pi-master1
kube-system etcd-pi-master1 1/1 Running 1 23h 192.168.6.11 pi-master1
kube-system kube-apiserver-pi-master1 1/1 Running 1 23h 192.168.6.11 pi-master1
kube-system kube-controller-manager-pi-master1 1/1 Running 2 2d23h 192.168.6.11 pi-master1
kube-system kube-flannel-ds-arm64-5c2kf 1/1 Running 0 3m21s 192.168.6.11 pi-master1
kube-system kube-proxy-525kd 1/1 Running 2 2d23h 192.168.6.11 pi-master1
kube-system kube-scheduler-pi-master1 1/1 Running 2 2d23h 192.168.6.11 pi-master1
将计算节点加入到集群
=====================
在完成了CNI add-on部署之后,就可以向集群增加计算节点
- 登陆到工作节点,例如 ``pi-worker1`` 上使用命令 ``kubeadm join`` ::
kubeadm join 192.168.6.11:6443 --token \
--discovery-token-ca-cert-hash sha256:
这里出现了报错::
[preflight] Running pre-flight checks
[WARNING Service-Docker]: docker service is not enabled, please run 'systemctl enable docker.service'
[WARNING SystemVerification]: missing optional cgroups: hugetlb
error execution phase preflight: couldn't validate the identity of the API Server: could not find a JWS signature in the cluster-info ConfigMap for token ID "8pile8"
To see the stack trace of this error execute with --v=5 or higher
这个问题参考 `Kubernetes: unable to join a remote master node `_ 原因是token已经过期或者已经移除,所以可以通过以下方法重新创建并提供命令::
kubeadm token create --print-join-command
输出可以看到::
W1203 11:50:33.907625 484892 kubelet.go:200] cannot automatically set CgroupDriver when starting the Kubelet: cannot execute 'docker info -f {{.CgroupDriver}}': exit status 2
W1203 11:50:33.918553 484892 configset.go:348] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
kubeadm join 192.168.6.11:6443 --token --discovery-token-ca-cert-hash sha256:
.. note::
默认生成的token都有一个有效期,所以导致上述token过期无法使用的问题。
可以通过以下命令生成一个无期限的token(但是存在安全风险)::
kubeadm token create --ttl 1
查看token的方法如下::
kubeadm token list
然后根据token重新生成证书摘要(即hash)::
openssl x509 -pubkey -in /etc/kubenetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
这样就能拼接出一个添加节点的join命令::
kubeadm join 192.168.6.11:6443 --token --discovery-token-ca-cert-hash sha256:
- 根据提示重新在 ``pi-worker1`` 上执行节点添加::
kubeadm join 192.168.6.11:6443 --token --discovery-token-ca-cert-hash sha256:
输出信息::
[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 -oyaml'
[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.
- 等待一会,在管控节点上检查::
kubectl get nodes -o wide
输出信息如下::
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
pi-master1 Ready master 3d12h v1.19.4 192.168.6.11 Ubuntu 20.04.1 LTS 5.4.0-1022-raspi docker://19.3.8
pi-worker1 Ready 2m10s v1.19.4 192.168.6.15 Ubuntu 20.04.1 LTS 5.4.0-1022-raspi docker://19.3.8
jetson节点(GPU)
----------------
我在 :ref:`arm_k8s` 说明了我部署ARM架构的设备中还有一个 :ref:`jetson_nano` 设备,用来实现验证GPU容器在Kubernetes的部署,并学习 :ref:`machine_learning` 。
jetson nano使用的Ubuntu 18.04定制版本L4T默认已经安装了Docker 19.03版本,满足了运行kubernetes要求,不过,也同样需要做Cgroup Driver调整:
- 通过 ``docker info`` 检查显示
.. literalinclude:: jetson_docker_info.output
:linenos:
- 注意,Jetson Nano的Docker激活了 ``nvidia`` runtime,所以默认的 ``daemon.json`` 配置如下
.. literalinclude:: jetson_daemon.json_default
:linenos:
修订成:
.. literalinclude:: jetson_daemon.json_systemd
:linenos:
然后重启docker服务::
systemctl restart docker
并通过 ``docker info`` 验证确保 ``Cgroup Driver: systemd`` 。
- 上述 ``docker info`` 中显示有2个WARNING::
WARNING: No blkio weight support
WARNING: No blkio weight_device support
.. note::
最初Ubuntu 20/18 提供的docker版本(19.x)都没有出现上述WARNING,但是最近升级docker到 ``20.10.2`` 出现。原因是Docker 从 Docker Engine 20.10开始,支持 :ref:`cgroup_v2` 。 :ref:`docker_cgroup_v2` 提供了更好的io隔离。如果非生产环境,可以暂时忽略上述警告。
- Jetson的L4T系统内核 ``sysctl`` 配置默认已经启动允许iptables查看bridge流量::
sysctl -a | grep net.bridge.bridge-nf-call-ip
可以看到::
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
- 添加Kubernetes repo::
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat < /sys/class/zram-control/hot_remove;done
# 禁用启动swap
systemctl disable nvzramconfig.service
.. note::
Jetson的定制L4T操作系统使用了 :ref:`zram` 来构建swap,详细参考 :ref:`jetson_swap` 。
- 在管控服务器获取当前添加节点命令::
kubeadm token create --print-join-command
- 回到Jetson服务器节点执行添加节点命令::
kubeadm join 192.168.6.11:6443 --token \
--discovery-token-ca-cert-hash sha256:
.. note::
随着 ``kubeadm`` 软件版本不断升级,新安装的worker节点的版本可能高于原先构建 :ref:`kubernetes` 集群,这导致无法加入集群,需要按照 :ref:`kubeadm_upgrade_k8s` 。
- 完成以后执行命令检查节点::
kubectl get nodes
我们会看到如下输出::
NAME STATUS ROLES AGE VERSION
jetson Ready 2m47s v1.19.4
pi-master1 Ready master 5d21h v1.19.4
pi-worker1 Ready 2d9h v1.19.4
pi-worker2 Ready 2d9h v1.19.4
.. note::
注意如果worker主机有多个网卡接口,kubelet执行 ``kubeadm join`` 命令时候有可能注册采用了默认有路由的网卡接口,也可能使用和apiserver指定IP所在相同网段的IP。这点让我很疑惑,例如上述注册worker节点,3个树莓派注册的 ``INTERNAL-IP`` 是正确的内网IP地址 ``192.168.6.x`` ,但是 ``jetson`` 就注册成了外网无线网卡上的IP地址 ``192.168.0.x`` 。
这个问题修订,请参考 :ref:`set_k8s_worker_internal_ip` 明确配置worker的 ``INTERNAL-IP`` 避免出现混乱。
kali linux节点(kali)
-----------------------
:ref:`kali_linux` 也是基于 :ref:`ubuntu_linux` 的操作系统,所以安装和管理Kubernetes非常相似。不过,需要注意的是,默认安装::
apt install docker.io
然后执行 ``docker info`` 可以看到已经启用了 ``systemd`` 和 :ref:`cgroup_v2` ::
...
Cgroup Driver: systemd
Cgroup Version: 2
...
WARNING: No memory limit support
WARNING: No swap limit support
WARNING: Support for cgroup v2 is experimental
不过默认的docker配置是无法安装Kubernetes的,会提示报错:
.. literalinclude:: arm_k8s_deploy/kail_kubeadm_init.output
:linenos:
可以看到,必须要设置 ``CGROUPS_MEMORY`` ,所以也如前设置:
- 修订 :ref:`kali_linux` for Raspberry Pi的配置文件 ``/boot/cmdline.txt`` (原先只有一行配置,在配置行最后添加)::
... cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 swapaccount=1
- 然后重启系统,再检查 ``docker info`` 后,只在最后提示::
...
WARNING: Support for cgroup v2 is experimental
- 然后重新开始将节点加入集群
kali linux节点添加失败排查
~~~~~~~~~~~~~~~~~~~~~~~~~~~
在解决了kali linux节点的docker版本配置问题后,就可以执行 ``kubeadm join`` 指令了,但是发现添加的节点始终是 ``NotReady`` ,检查pod创建::
kubectl -n kube-system get pods
可以看到kali linux节点上容器没有正确启动::
kube-flannel-ds-pkhch 0/1 Init:0/1 0 42m 30.73.167.10 kali
kube-proxy-6jt64 0/1 ContainerCreating 0 42m 30.73.167.10 kali
- 检查pod::
kubectl -n kube-system describe pods kube-flannel-ds-pkhch
可以看到报错原因::
Warning FailedCreatePodSandBox 2m29s (x186 over 42m) kubelet Failed to create pod sandbox: open /run/systemd/resolve/resolv.conf: no such file or directory
- 检查kali linux节点,发现确实没有这个文件::
# ls /run/systemd/resolve/resolv.conf
ls: cannot access '/run/systemd/resolve/resolv.conf': No such file or directory
对比正常节点,则有这个文件,这说明kali linux默认没有激活 :ref:`systemd_resolved` ::
systemctl enable systemd-resolved
systemctl start systemd-resolved
然后再次检查就可以看到文件::
# ls /run/systemd/resolve/resolv.conf
/run/systemd/resolve/resolv.conf
- 等待kali linux节点上kube-system namespace对应的pod创建成功,就可以看到该worker节点正常Ready了
arch linux节点(zcloud)
-----------------------
- 安装docker::
panman -Sy docker
- 启动docker::
systemctl start docker
systemctl enable docker
- 检查 ``docker info`` 输出信息,可以看到 ``Cgroup Driver: cgroupsf`` 不是 ``systemd`` ,所以修订成 :ref:`systemd` 作为 cgroups 管理器,并且确保只使用一个cgroup manager。修改或者创建 ``/etc/docker/daemon.json`` 如下::
$ sudo cat > /etc/docker/daemon.json <`_
- 安装软件包::
pacmsn -Syu && pacman -Sy kubelet kubeadm kubectl
- 关闭swap
- 在管控服务器获取当前添加节点命令::
kubeadm token create --print-join-command
- 回到Jetson服务器节点执行添加节点命令::
kubeadm join 192.168.6.11:6443 --token \
--discovery-token-ca-cert-hash sha256:
.. note::
如果遇到节点一直 ``NotReady`` ,请在节点上执行检查kubelet日志命令::
journalctl -xeu --no-pager kubelet
我遇到的问题是无法下载镜像,提示错误::
Mar 23 00:25:15 zcloud kubelet[3682]: E0323 00:25:15.851566 3682 pod_workers.go:191] Error syncing pod 062199e4-6351-4ff9-9e55-91db9ac8884f ("kube-proxy-jms58_kube-system(062199e4-6351-4ff9-9e55-91db9ac8884f)"), skipping: failed to "CreatePodSandbox" for "kube-proxy-jms58_kube-system(062199e4-6351-4ff9-9e55-91db9ac8884f)" with CreatePodSandboxError: "CreatePodSandbox for pod \"kube-proxy-jms58_kube-system(062199e4-6351-4ff9-9e55-91db9ac8884f)\" failed: rpc error: code = Unknown desc = failed pulling image \"k8s.gcr.io/pause:3.2\": Error response from daemon: Get \"https://k8s.gcr.io/v2/\": dial tcp: lookup k8s.gcr.io: no such host"
这个问题是因为我启动主机时候未连接无线网络,导致没有默认路由可以访问internet,此时启动的dnsmasq无法解析地址。虽然后面用手工脚本命令启动了wifi,但是dnsmasq依然无法提供本地主机域名解析。我是通过重启dnsmasq解决的,你的情况可能和我不同。不过,观察 kubelet 日志是一个比较好的排查问题方法。
再次添加zcloud节点(arch linux)
-------------------------------
我在重建了Kubernetes集群之后,发现当前google提供的kubernetes软件版本,也就是我构建的管控平面,已经是最新的 ``v1.22.0`` ,但是在 zcloud 节点使用的Arch Linux,则提供的是 ``v1.21.3`` 。
- 尝试执行节点添加::
kubeadm join 192.168.6.11:6443 --token \
--discovery-token-ca-cert-hash sha256:
出现报错::
...
error execution phase preflight: unable to fetch the kubeadm-config ConfigMap: failed to decode cluster configuration data: no kind "ClusterConfiguration" is registered for version "kubeadm.k8s.io/v1beta3" in scheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme/scheme.go:31"
To see the stack trace of this error execute with --v=5 or higher
看来低版本节点无法加入高版本集群(兼容性实在太差了)
- 检查 `arch linux kubeadm package `_ 可以看到当前:
- ``1.21.3-1`` 为社区版本(正式)
- ``1.22.0-1`` 为社区测试版本
- 修订 ``/etc/pacman.conf`` 配置文件,激活 ``commuity-testing`` 仓库::
[community-testing]
Include = /etc/pacman.d/mirrorlist
- 然后执行指定版本安装::
# 同步 community-testing 仓库信息
pacman -Sy
# 安装指定版本
pacman -S kubelet=1.22.0-1 kubeadm=1.22.0-1 kubectl=1.22.0-1
- 锁定版本是修改 ``/etc/pacman.conf`` ::
IgnorePkg = kubelet kubeadm kubectl
这样就不会升级上述版本。
- 再次修订 ``/etc/pacman.conf`` 将 ``community-testing`` 仓库注释掉,避免其他软件版本升级到测试版本。
最终结果
==========
- 最终节点如下::
kubectl get nodes -o wide
显示如下::
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
jetson Ready 2d1h v1.22.0 30.73.165.36 Ubuntu 18.04.5 LTS 4.9.201-tegra docker://20.10.7
kali Ready 63m v1.22.0 30.73.167.10 Kali GNU/Linux Rolling 5.4.83-Re4son-v8l+ docker://20.10.5+dfsg1
pi-master1 Ready control-plane,master 2d2h v1.22.0 192.168.6.11 Ubuntu 20.04.2 LTS 5.4.0-1041-raspi docker://20.10.7
pi-worker1 Ready 2d1h v1.22.0 192.168.6.15 Ubuntu 20.04.2 LTS 5.4.0-1041-raspi docker://20.10.7
pi-worker2 Ready 2d1h v1.22.0 192.168.6.16 Ubuntu 20.04.2 LTS 5.4.0-1041-raspi docker://20.10.7
zcloud Ready 21m v1.22.0 192.168.6.200 Arch Linux 5.13.9-arch1-1 docker://20.10.8
.. note::
注意到有两个节点 ``jetson`` 和 ``kali`` 节点 ``INTERNAL-IP`` 采用了无线网卡上的IP地址,需要修订
- 现在,我们终于可以拥有一个混合架构的Kubernetes集群::
kubectl get nodes --show-labels
可以看到 ARM 节点的标签有 ``kubernetes.io/arch=arm64`` ,而 X86 节点标签有 ``kubernetes.io/arch=amd64`` ::
NAME STATUS ROLES AGE VERSION LABELS
jetson Ready 2d1h v1.22.0 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=jetson,kubernetes.io/os=linux
kali Ready 61m v1.22.0 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=kali,kubernetes.io/os=linux
pi-master1 Ready control-plane,master 2d2h v1.22.0 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=pi-master1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
pi-worker1 Ready 2d1h v1.22.0 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=pi-worker1,kubernetes.io/os=linux
pi-worker2 Ready 2d1h v1.22.0 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=pi-worker2,kubernetes.io/os=linux
zcloud Ready 20m v1.22.0 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=zcloud,kubernetes.io/os=linux
后面我们部署应用,所使用的镜像需要区分不同架构。我将实践异构应用部署。
验证集群
===========
现在一个基于ARM的Kubernetes集群已经部署完成,可以运行pods,创建deployments和jobs。
为了验证集群的正确运行,可以执行验证步骤:
- 创建新的namespace
- 创建deployment
- 创建serice
- 验证运行在deployment中的pods能够正确响应
.. note::
这里使用的验证镜像是从RED HAT维护的 quay.io 镜像仓库提供的,你也可以使用其他镜像仓库,例如Docker Hub提供的镜像进行验证。
- 创建一个名为 ``kube-verify`` 的namespace::
kubectl create namespace kube-verify
提示信息::
namespace/kube-verify created
- 检查namespace::
kubectl get namespaces
显示输出::
NAME STATUS AGE
default Active 5d21h
kube-node-lease Active 5d21h
kube-public Active 5d21h
kube-system Active 5d21h
kube-verify Active 36s
- 创建一个deployment用于这个新的namespace:
.. literalinclude:: arm_k8s_deploy/create_deployment.sh
:language: bash
:linenos:
此时提示信息::
deployment.apps/kube-verify created
上述deployment配置了3个副本 ``replicas: 3`` pods,每个pod都运行了 ``quay.io/clcollins/kube-verify:01`` 镜像。
- 检查在 ``kube-verify`` 中的所有资源::
kubectl get all -n kube-verify
输出显示::
NAME READY STATUS RESTARTS AGE
pod/kube-verify-69dd569645-nvnhl 1/1 Running 0 2m8s
pod/kube-verify-69dd569645-s5qb5 1/1 Running 0 2m8s
pod/kube-verify-69dd569645-v9zxt 1/1 Running 0 2m8s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kube-verify 3/3 3 3 2m8s
NAME DESIRED CURRENT READY AGE
replicaset.apps/kube-verify-69dd569645 3 3 3 2m8s
可以看到有一个新的deployment ``deployment.apps/kube-verify`` ,这个deployment配置 ``replicas: 3`` 的请求下有3个pods被创建。
- 创建服务来输出 Nginx 应用,这个操作是一个单一入口 ``single endpoint`` :
.. literalinclude:: arm_k8s_deploy/create_service.sh
:language: bash
:linenos:
提示::
service/kube-verify created
- 现在服务已经创建,我们可以检查新服务的IP地址::
kubectl get -n kube-verify service/kube-verify
显示输出::
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-verify ClusterIP 10.107.123.31 80/TCP 90s
你可以看到 ``kube-verify`` 服务被设置到一个 ClusterIP ``10.107.123.31`` 上,但是这个IP地址是集群内部使用的,所以你可以在集群的任何node节点上访问,但是在集群外部就不能访问。
- 选择一个集群节点,执行以下命令验证deployment的容器是正常工作的::
curl 10.107.123.31
你会看到一个完整输出的html文件内容。
访问服务
============
到这里,你已经完整部署和验证了Kubernetes on Raspberry Pi集群,只是当前在集群外部还不能访问到集群pod提供的服务。
你可以有多种方法:
- 输出部署( ``expose deployments`` )通过简单的负载均衡方式(指定 ``external-ip``)将服务映射输出到集群外部
- 部署 :ref:`ingress` 来管理外部访问集群的服务,例如, :ref:`nginx_ingress` 对外提供服务
这里我们采用最简单的方法,使用 ``expose deployments`` 方法输出服务:
- 检查 ``kube-verify`` namespace的服务::
kubectl get services -n kube-verify
输出可以看到当前服务有内部 ``cluster-ip`` 但是没有 ``external-ip`` ::
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-verify ClusterIP 10.107.123.31 80/TCP 19h
- 我们的woker工作节点如下::
kubectl get nodes -o wide
显示::
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
jetson Ready 19h v1.19.4 192.168.6.10 Ubuntu 18.04.5 LTS 4.9.140-tegra docker://19.3.6
pi-master1 Ready master 6d17h v1.19.4 192.168.6.11 Ubuntu 20.04.1 LTS 5.4.0-1022-raspi docker://19.3.8
pi-worker1 Ready 3d5h v1.19.4 192.168.6.15 Ubuntu 20.04.1 LTS 5.4.0-1022-raspi docker://19.3.8
pi-worker2 Ready 3d5h v1.19.4 192.168.6.16 Ubuntu 20.04.1 LTS 5.4.0-1022-raspi docker://19.3.8
问题来了,我们需要把服务输出到外部无线网卡上,该如何实现?实际上 ``pi-worker1`` 上无线网卡上IP地址 192.168.0.81:
- 无线网卡的外网地址是动态的,假设我直接输出到这个无线网卡IP地址,则下次主机重启如果获取到不同IP地址,则有可能和局域网其他IP地址冲突
- 我考虑到解决方法是采用一个自己控制的IP地址段作为演示,然后需要访问的客户机也绑定同一个网段,这样只要连接到相同的无线AP上,就可以访问服务
当前我简化这个配置,暂时先使用有线网络网段 ``192.168.6.x``
- 检查当前service::
kubectl get service -n kube-verify
可以看到::
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-verify ClusterIP 10.107.123.31 80/TCP 26h
- 删除掉这个service::
kubectl delete service kube-verify -n kube-verify
- 然后重建一个负载均衡类型的输出::
kubectl expose deployments kube-verify -n kube-verify --port=80 --protocol=TCP --target-port=8080 \
--name=kube-verify --external-ip=192.168.6.10 --type=LoadBalancer
提示信息::
service/kube-verify exposed
.. note::
注意,这里负载均衡类型的输出 ``target-ports`` 和之前 service 是一样的,只不过多了一个 ``EXTERNAL-IP`` 对外
- 现在我们检查 ``kubectl get service -n kube-verify`` 显示::
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-verify LoadBalancer 10.96.31.186 192.168.6.10 80:30586/TCP 61s
奇怪,这里 ``--target-port=8080`` 怎么没有生效么?怎么显示 ``PORT(S)`` 是 ``80:30586/TCP`` ,但是直接对该IP地址访问 ``curl http://192.168.6.10`` 是能够正常获取到页面输出的。
现在我们得到的验证环境::
kubectl get all -n kube-verify
输出显示::
NAME READY STATUS RESTARTS AGE
kube-verify-69dd569645-q9hzc 1/1 Running 0 33h
kube-verify-69dd569645-s5qb5 1/1 Running 0 2d12h
kube-verify-69dd569645-v9zxt 1/1 Running 0 2d12h
ubuntu@pi-master1:~$ kubectl get all -n kube-verify
NAME READY STATUS RESTARTS AGE
pod/kube-verify-69dd569645-q9hzc 1/1 Running 0 33h
pod/kube-verify-69dd569645-s5qb5 1/1 Running 0 2d12h
pod/kube-verify-69dd569645-v9zxt 1/1 Running 0 2d12h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-verify LoadBalancer 10.96.31.186 192.168.6.10 80:30586/TCP 32h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kube-verify 3/3 3 3 2d12h
NAME DESIRED CURRENT READY AGE
replicaset.apps/kube-verify-69dd569645 3 3 3 2d12h
参考
=====
- `Build a Kubernetes cluster with the Raspberry Pi `_ - 完整的部署指南,作者 Chris Collins 撰写了一系列在Raspberry Pi上构建homelab的文章,可以实现基于K8s的NFS服务;另外,如果需要通过Kubernetes对外提供HTTP路由和反向代理(ingress),可以参考 `Try this Kubernetes HTTP router and reverse proxy `_
- `解决k8s执行kubeadm join遇到could not find a JWS signature的问题 `_