本文将介绍国内网络环境下,如何在 debian12 系统,使用 Kubeadm 搭建基于 containerd 容器运行、包含 Master 和 Worker 两节点的、目前(2024.08.12)最新最新版本(v1.30.3)的 kubernetes 集群。

主机规划

角色 主机名 IP 版本 配置
Master Node k8s-master 192.168.0.210 debian12 2C/4G/30G
Worker Node 1 k8s-worker01 192.168.0.211 debian12 2C/4G/30G

软件版本

软件名称 版本 备注
PVE v7.4-18
KVM debian12 PVE 虚拟主机使用 debian12 cloud qcow2 镜像
containerd v1.6.20 官方公布从 1.24 版本开始移除 dockershim
kubernetes v1.30.3 docker 可 v1.23.6,其它推荐官方仍在维护的版本
calico v3.28.1 注意版本需与 k8s 版本匹配,查看支持的 k8s 版本
dashboard v2.7.0 官方从 v7.0.0 开始,仅支持 Helm 方式安装

主机设置

所有节点机器都执行。

设置主机名

设置主机名

# master 和 所有 woker 节点都执行
hostnamectl set-hostname "k8s-master"      # Run on master node
hostnamectl set-hostname "k8s-worker01"    # Run on 1st worker node

修改 /etc/hosts 映射关系

192.168.0.210   k8s-master
192.168.0.211   k8s-worker01

关闭防火墙

# Master node
sudo ufw allow 6443/tcp
sudo ufw allow 2379/tcp
sudo ufw allow 2380/tcp
sudo ufw allow 10250/tcp
sudo ufw allow 10251/tcp
sudo ufw allow 10252/tcp
sudo ufw allow 10255/tcp
sudo ufw reload
# Worker nodes
sudo ufw allow 10250/tcp
sudo ufw allow 30000:32767/tcp
sudo ufw reload
# 或者直接关闭防火墙
sudo ufw disable

关闭 swap 内存

# 临时关闭
swapoff -a
# 以“#”符号注释已久关闭
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 查看状态
free -m

设置内核参数

# 开机加载 overlay、br_netfilter 模块
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf 
overlay 
br_netfilter
EOF
modprobe overlay 
modprobe br_netfilter
# 确认模块加载
lsmod | grep -E 'overlay|br_netfilter'

# 将桥接的 IPv4 流量传递到 iptables 的链
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1 
net.bridge.bridge-nf-call-ip6tables = 1 
EOF
# 使修改生效
sysctl --system

同步时区

apt-get install ntp
ntpdate time.windows.com
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

sudo apt-get install ntp
sudo apt-get install ntpdate
sudo ntpdate ntp.ubuntu.com

配置免密登录

# https://www.cnblogs.com/binliubiao/p/14823221.html
# https://lework.github.io/2019/06/30/k8s-ha-bin-install/
# 在 k8s-master 操作
apt install sshpass -y
ssh-keygen -t rsa -P '' -f /root/.ssh/id_rsa
for NODE in k8s-master k8s-worker01 k8s-worker02; do
  echo "--- $NODE ---"
  sshpass -p 123456 ssh-copy-id -o "StrictHostKeyChecking no" -i /root/.ssh/id_rsa.pub ${NODE}
  ssh ${NODE} "hostnamectl set-hostname ${NODE}"
done
# 其中 123456 是服务器的密码

安装 containerd run time

所有节点机器都执行。

k8s 官方公布从 1.24 版本开始移除 dockershim

containerd 实现了 kubernetes 的 Container Runtime Interface (CRI) 接口,提供容器运行时核心功能,如镜像管理、容器管理等,相比 dockerd 更加简单、健壮和可移植。

apt update
apt install containerd -y
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null 2>&1
# 配置 containerd 用 systemdcgroup 启动
# 配置与 kubeadm init 指定一样的镜像源
sed -i "s/SystemdCgroup = false/SystemdCgroup = true/g" /etc/containerd/config.toml
sed -i "s/sandbox_image = \"registry\.k8s\.io\/pause:3\.6\"/sandbox_image = \"registry\.aliyuncs\.com\/google_containers\/pause:3\.9\"/g" /etc/containerd/config.toml
# 重启服务
systemctl enable containerd
systemctl restart containerd
systemctl status containerd
# 查看版本
containerd --version
# containerd github.com/containerd/containerd 1.6.20~ds1 1.6.20~ds1-1+b1

安装 kubernetes 工具

所有节点机器都执行。

安装 kubeadm、kubelet、kubectl 工具

  • kubectl: 用以控制集群的客户端工具
  • kubeadm: 用以构建一个 k8s 集群的官方工具
  • kubelet: 工作在集群的每个节点,负责容器的一些行为如启动
# 添加源和 GPG 证书
apt install gpg -y
# 非 v1.30.3,其它版本更改对应的版本号
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
# 安装
apt update
apt install kubelet kubeadm kubectl -y
# apt-mark 用于将软件包标记/取消标记为自动安装。 hold 选项用于将软件包标记为保留,以防止软件包被自动安装、升级或删除。
apt-mark hold kubelet kubeadm kubectl
# 非 v1.30.3,其它版本更改对应的版本号
# apt-get remove --purge kubelet kubeadm kubectl
# apt search kubelet && apt search kubeadm && apt search kubectl
# apt install kubelet=1.30.3-1.1 kubeadm=1.30.3-1.1 kubectl=1.30.3-1.1
# apt install kubelet=1.30.3* kubeadm=1.30.3* kubectl=1.30.3*
# 检查版本
kubeadm version
# kubeadm version: &version.Info{Major:"1", Minor:"30", GitVersion:"v1.30.3", GitCommit:"6fc0a69044f1ac4c13841ec4391224a2df241460", GitTreeState:"clean", BuildDate:"2024-07-16T23:53:15Z", GoVersion:"go1.22.5", Compiler:"gc", Platform:"linux/amd64"}
kubelet --version
# Kubernetes v1.30.3
kubectl version
# Client Version: v1.30.3
# Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
# Server Version: v1.30.3

初始化节点

拉取镜像在所有节点机器都执行,然后依机器的情况,分别执行【初始化 master 节点】或者【初始化 worker 节点】二选一。

# 提前拉取镜像
kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers
# 查看镜像
crictl images

初始化 master 节点

使用 kubeadm 初始化 master 节点或首节点。

  • 不要在非 master 节点上运行 kubeadm init,如果在非 master 节点上运行了 kubeadm init,它会尝试初始化该节点为一个新的控制平面节点,导致该节点不能正常加入现有集群。
  • 如果要将一个非 master 节点加入到现有的 Kubernetes 集群中并,成为一个 worker 节点,在 master 节点上生成kubeadm join 命令, 并在非 master 节点(worker 节点)上运行即可。
  • --image-repository 指定 docker 镜像的仓库地址,用于下载 kubernetes 组件所需的容器镜像。因为 k8s 的很多镜像都在外网无法访问,所以这里使用了阿里云容器镜像地址。注意,即使提前拉取了镜像,这里也要指定相同的仓库,否则还是会拉取官方镜像如果访问不了导致拉取失败。
  • --ignore-preflight-errors=NumCpu如果你的服务器不足 2 核 cpu 可以添加,来忽略错误。
  • 如果你在 init 后因发生任何异常导致初始化终止了,可以使用 kubeadm reset -f 强制重置之后再重新进行初始化,注意该命令会将此 master 完全重置。
  • 命令 kubeam init 完整参数见官方文档 https://kubernetes.io/zh-cn/docs/reference/setup-tools/kubeadm/kubeadm-init/
# 初始化 kubeadm
kubeadm init --image-repository registry.aliyuncs.com/google_containers
# 报错查看日志
systemctl status kubelet
journalctl -xeu kubelet
# # 初始化 kubeadm,方法二,使用 config 模式
# https://www.cnblogs.com/wswind/p/14972730.html
# cat /var/lib/kubelet/config.yaml
# kubeadm config print init-defaults > kubeadm.yaml
# 修改节点名
# sed -i "s/name: node/name: k8s-master/g" kubeadm.yaml
# 修改镜像仓库
# sed -i "s/imageRepository: registry\.k8s\.io/imageRepository: registry\.cn-hangzhou\.aliyuncs\.com\/google_containers/g" kubeadm.yaml
# sed -i "s/advertiseAddress: 1\.2\.3\.4/advertiseAddress: 192\.168\.0\.212/g" kubeadm.yaml
# 把 kubelet 的 cgroup 驱动配置为 systemd
# tee -a kubeadm.yaml <<EOF
# EOF
# cat <<EOF >> kubeadm.yaml
# ---
# # Added by How,for kubelet systemd cgroup
# apiVersion: kubelet.config.k8s.io/v1beta1
# kind: KubeletConfiguration
# cgroupDriver: systemd
# EOF
# kubeadm init --config=kubeadm.yaml
# 成功后按照提示执行
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
export KUBECONFIG=/etc/kubernetes/admin.conf
# 查看已加入的节点,k8s-master 节点执行
kubectl get nodes
# 查看集群状态,k8s-master 节点执行
kubectl get cs

如果错过初始化 Kubernetes 集群时输出的用于将其它节点加入集群的 kubeadm join 命令或者需要再次查看它。

可以在 master 节点通过以下方式来获取

添加 master 节点的命令获取方式

# 获取新的 certificate-key
kubeadm init phase upload-certs --upload-certs
# 生成添加 master 节点的命令
kubeadm token create --print-join-command --certificate-key <your-new-certificate-key>

添加 worker 节点的命令获取方式

# 添加 worker 节点的命令获取方式
kubeadm token create --print-join-command

初始化 worker 节点

依照上文的 【添加 worker 节点的命令获取方式】 章节,在 master 节点上获取到添加非 master(worker 节点)节点的 kubeadm join 命令执行。

kubeadm join 192.168.0.210:6443 --token 80rfzl.wfa1lardiybcwl03 --discovery-token-ca-cert-hash sha256:d33d29d7afc98aa08a7134e3ba0aaa1a25498a25cd250f9871d7c9c659a78164 
# 配置 kubectl 命令工具
# https://github.com/chaseSpace/k8s-tutorial-cn/blob/main/install_by_kubeadm/install.md#55-%E5%9C%A8%E6%99%AE%E9%80%9A%E8%8A%82%E7%82%B9%E6%89%A7%E8%A1%8Ckubectl
# 普通节点执行 kubectl get nodes 提示错误:memcache.go:265] couldn't get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
mkdir ~/.kube && cp /etc/kubernetes/kubelet.conf  ~/.kube/config
# 执行完成后,可以尝试使用如下两条命令测试 kubectl 是否可用
# 查看已加入的节点,k8s-worker01 节点执行
kubectl get nodes
# 从主节点运行以下命令来检查节点状态,k8s-master 节点执行
kubectl get cs

现在有了 master 和 node 节点,但是所有节点状态都是 NotReady,这是因为没有 CNI 网络插件。

安装 CNI 网络插件

安装 CNI 网络插件在所有节点机器执行。

Kubernetes 需要网络插件 (Container Network Interface: CNI) 来提供集群内部和集群外部的网络通信。

常用的 k8s 网络插件有 Flannel、Calico、Canal、Weave-net 等。

Calico 性能更强,Flannel 更简单方便。

这里选择 Calico,注意版本需要匹配 k8s 版本,否则无法应用,查看支持的 k8s 版本

# flannel 的安装参考,但是 CNI插件部分可以结合看 “CNI插件,kubelet组件在启动时,在命令行选项 --network-plugin=cni 来选择CNI插件。它会自动搜索 --cni-bin-dir (default /opt/cni/bin)指定目录下的网络插件,并使用 --cni-conf-dir (default /etc/cni/net.d) 目录下配置文件设置每个Pod的网络。CNI配置文件引用的插件必须--cni-bin-dir目录中。新版k8s 不需要单独安装CNI, calico自带有cni插件 —— https://www.cnblogs.com/binliubiao/p/14823221.html”
# 在 Kubernetes 1.24 之前,CNI 插件也可以由 kubelet 使用命令行参数 cni-bin-dir 和 network-plugin 管理。Kubernetes 1.24 移除了这些命令行参数, CNI 的管理不再是 kubelet 的工作。—— https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/
# https://www.jianshu.com/p/fde6c8a90706
# k8s集群中的pod通信依赖于网络插件我使用的是flannel,应用前可先浏览配置。注意master初始化集群时的--pod-network-cidr参数需要与 kube-flannel.yml 中的 net-conf.json:Network 保持一致,
# https://www.cnblogs.com/wswind/p/14972730.html
# https://github.com/chaseSpace/k8s-tutorial-cn/blob/main/install_by_kubeadm/install.md#54-%E5%AE%89%E8%A3%85%E7%AC%AC%E4%B8%89%E6%96%B9%E7%BD%91%E7%BB%9C%E6%8F%92%E4%BB%B6
# https://blog.csdn.net/u010978399/article/details/136850644
# https://www.linuxtechi.com/install-kubernetes-cluster-on-debian/
wget --no-check-certificate https://raw.gitmirror.com/projectcalico/calico/v3.28.1/manifests/calico.yaml
kubectl delete -f calico.yaml
kubectl apply -f calico.yaml
# 查看 pod 状态
kubectl get pods -n kube-system --watch
# 报错看日志( Init:ImagePullBackOff )
kubectl describe pod calico-node-hv4ss -n kube-system
# 查看 pod 部署的所在 node
kubectl get pods -n kube-system -o wide
# 手动拉取镜像(在所有节点都执行)
export http_proxy=http://192.168.0.104:10809 && export https_proxy=http://192.168.0.104:10809 && export no_proxy="localhost, 127.0.0.1, ::1"
ctr -n k8s.io image pull docker.io/calico/cni:v3.28.1
ctr -n k8s.io image pull docker.io/calico/node:v3.28.1
ctr -n k8s.io image pull docker.io/calico/kube-controllers:v3.28.1
# 删除 Calico pod,让其重启
kubectl get pods -n kube-system | grep calico-node-hv4ss | awk '{print$1}'| xargs kubectl delete -n kube-system pods
kubectl describe po calico-node-hv4ss -n kube-system
# 防火墙中允许 Calico 端口,在所有节点上运行 ufw 命令
sudo ufw allow 179/tcp
sudo ufw allow 4789/udp
sudo ufw allow 51820/udp
sudo ufw allow 51821/udp
sudo ufw reload
# 验证 Calico pod 的状态,运行
kubectl get pods -n kube-system
# 查看 nodes 状态
kubectl get nodes

再次检查节点状态,确认主节点和工作节点处于就绪(Ready)状态。现在,集群已准备好处理工作负载。

验证集群

通过在集群中部署 nginx 服务来验证集群是否正常工作。

# https://github.com/chaseSpace/k8s-tutorial-cn/blob/main/install_by_kubeadm/install.md#6-%E9%AA%8C%E8%AF%81%E9%9B%86%E7%BE%A4
# https://www.linuxtechi.com/install-kubernetes-cluster-on-debian/
# 创建
kubectl create deployment nginx-test-deployment --image=nginx:1.27-alpine --replicas 2
# 通过 NodePort 类型的 Service 来暴露 Pod
kubectl expose deployment nginx-test-deployment --name=nginx-test-service --type NodePort --port 80 --target-port 80
# 查看 pod 及服务信息
kubectl get pod,svc
kubectl describe svc nginx-test-service
# root@k8s-master:~/k8s/blog-in-kubernetes# kubectl get pod,svc
# NAME                                         READY   STATUS    RESTARTS      AGE
# pod/nginx-test-deployment-6876b4697f-68wnt   1/1     Running   0             13s
# pod/nginx-test-deployment-6876b4697f-dp66k   1/1     Running   0             13s
# 
# NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
# service/nginx-test-service   NodePort    10.102.158.74   <none>        80:31493/TCP   6s
# 它将容器 80 端口映射到【所有节点】的一个随机端口(这里是 31493 )。 我们通过访问节点端口来测试在所有集群机器上的 pod 的连通性。
# 在 k8s-master(192.168.0.210)上执行
curl http://192.168.0.210:31493
curl http://192.168.0.211:31493
# k8s-master shell 打不开,局域网其它机器(192.168.0.104)浏览器可以打开
curl http://k8s-worker01:31493
# 删除部署
kubectl delete deployment nginx-test-deployment
kubectl delete svc nginx-test-service

安装 Dashboard

Kubernetes Dashboard 是 Kubernetes 开发的基于 Web 的面板,用户可以用它部署容器化的应用、监控应用的状态、执行故障排查任务以及管理 Kubernetes 各种资源。

从 7.0.0 版开始仅支持基于 Helm 的安装。这里用的 v2.7.0 版本,使用 yaml 文件的安装。

# https://github.com/kubernetes/dashboard/blob/v2.7.0/docs/user/access-control/creating-sample-user.md
# https://www.jianshu.com/p/fde6c8a90706
# https://zhuanlan.zhihu.com/p/91731765
# 下载 recommended.yaml 文件
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
# 改为 NodePort 访问
# ...
# spec:
#   ports:
#     - port: 443
#       targetPort: 8443
#       name: https                      # 新加 name
#       nodePort: 32001                  # 新加 nodePort
#   type: NodePort                       # 新加 nodePort
#   selector:
# ...
# 安装
kubectl apply -f dashboard-recommended.yaml
kubectl delete -f dashboard-recommended.yaml
# 查看 pod 名称
kubectl get pods --namespace=kubernetes-dashboard -o wide
# 报错看日志
kubectl describe pod dashboard-metrics-scraper-795895d745-8fvbg --namespace=kubernetes-dashboard
# 手动拉取镜像(在所有节点都执行)
export http_proxy=http://192.168.0.104:10809 && export https_proxy=http://192.168.0.104:10809 && export no_proxy="localhost, 127.0.0.1, ::1"
ctr -n k8s.io image pull docker.io/kubernetesui/metrics-scraper:v1.0.8
ctr -n k8s.io image pull docker.io/kubernetesui/dashboard:v2.7.0
# 创建用户
# 创建文件 dashboard-adminuser.yaml
# apiVersion: v1
# kind: ServiceAccount
# metadata:
#   name: admin-user
#   namespace: kubernetes-dashboard
# ---
# apiVersion: rbac.authorization.k8s.io/v1
# kind: ClusterRoleBinding
# metadata:
#   name: admin-user
# roleRef:
#   apiGroup: rbac.authorization.k8s.io
#   kind: ClusterRole
#   name: cluster-admin
# subjects:
# - kind: ServiceAccount
#   name: admin-user
#   namespace: kubernetes-dashboard
# ---
# # 长期 token
# apiVersion: v1
# kind: Secret
# metadata:
#   name: admin-user
#   namespace: kubernetes-dashboard
#   annotations:
#     kubernetes.io/service-account.name: "admin-user"
# type: kubernetes.io/service-account-token
# 执行
kubectl apply -f dashboard-adminuser.yaml
# 临时 token
kubectl -n kubernetes-dashboard create token admin-user
# 获取长期 token
kubectl get secret admin-user -n kubernetes-dashboard -o jsonpath={".data.token"} | base64 -d
# eyJhbGciOiJSUzI1NiIsImtpZCI6IkxEbzRCOEZxQ1ZrQnBHd1VkdjRsZzRPN1cxc1lNci1FQUozOEpMaTdyOFkifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1ZGQ3YjFhNi1iMWNiLTQ2NmItYjIzMC1mNGIyMWFkOTlhNDYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.ScCyX4_4DCSzYyX-lQK0LvR6MKJ5u0I3hsSs69Wy4ntSWu0hZBc0JUP1sA3Ku-5B-hdozUQVvxRz2kREcgL8aRmc57TrnzoatdJVfxYxXWbbsLcfUxpeTZHsfM9FpclIYb6ORRQSg3gdhVKcpiam5Bn7lpdF_zqWiBT1pvUw0NaBulHEGZHzyNSTg56EXmHIWLnrXveUlti_-Wv-esIRa0B9195_eKx-TYjLtYwyK2W1sMnuopbP0iYyPUm1FQQEnEWQydVxiGv3beXt7MfMJGxIRX-2aeqLsOumvxnl8Q5YL_9ulmnZAwC-Zp89SmnAEVik3VCMXPJfhecNyn719g

访问填写上步骤生成的 token

https://192.168.0.210:32001/#/workloads?namespace=default

至此,整个 k8s 集群已经搭建完毕。