使用Kubeadm在Ubuntu22引导部署K8s集群

280 阅读10分钟

操作成功度

  • 99.9%

部署耗时

  • 20分钟

环境及组件版本说明

  • 操作系统:Ubuntu 22.04.5 LTS
  • 服务器:两台 4C4G 腾讯云轻量应用服务器
  • 网络:两台服务器在两个账号下,无法内网互联,通过公网互通(仅限学习环境,生产不允许公网组建集群)
  • IP栈:单协议栈 IPv4
  • Kubernetes:v1.33.4(Latest)
  • Containerd:v2.1.4(Latest)
  • Flannel:v0.27.2(Latest)

转载说明

  • 原创内容,请注明出处

1. 环境配置

在所有机器上执行

1.1 技术方案选择说明

  • Why Ubuntu 22.04.5 LTS

    • 最热门的Linux发行版之一
    • 易用性
    • 成熟的社区资源
    • LTS 长期支持版本足够稳定可靠
  • Why Kubeadm

    • K8s 官方推荐的部署方式
    • 成熟的社区资源
    • 上手能感受到部署的细节和集群核心参数
  • Why Containerd

    • K8s 官方力挺
    • 轻量化,节省服务器资源
    • 稳定性
    • 提前适应新的技术方案,避免沉迷自己更加熟悉的技术(docker)
  • Why Flannel

    • 老牌知名网络插件
    • 易用性
    • 轻量化,节省服务器资源
    • 简单经典,适合开源新手参与贡献
      • 如果考虑参与开源贡献,选择经典且正在使用的项目是不错的选择
      • 有必要了解Go云原生组件的开发模式,简单经典的组件易于上手

1.2 云服务器安全组设置

说明

  • 如果是单机部署集群可以跳过这一步
  • 控制平面表中"必要性"仅适用于 单台 控制平面机器的情况,具体根据表中“使用者”衡量

1.2.1 控制面

协议方向端口范围端口描述使用者必要性
TCP入站6443Kubernetes API 服务器所有必选
TCP入站2379-2380etcd 服务器客户端 APIkube-apiserver、etcd
TCP入站10250kubelet API自身、控制平面必选(Metric需要)
TCP入站10259kube-scheduler自身
TCP入站10257kube-controller-manager自身
UDP入站+出站8472VXLANFlannel必选

1.2.2 工作节点

协议方向端口范围端口描述使用者必要性
TCP入站10250kubelet API自身、控制面必选
TCP入站10256kube-proxy自身、负载均衡器必选
TCP入站30000-32767NodePort Services所有声明service时可以指定NodePort端口,安全组只暴露特定的端口即可
UDP入站30000-32767NodePort Services所有声明service时可以指定NodePort端口,安全组只暴露特定的端口即可
UDP入站+出站8472VXLANFlannel必选

1.3 设置 hostname

# 控制平面机器执行
sudo hostnamectl hostname k8s-control

# 工作节点机器执行
sudo hostnamectl hostname k8s-worker-1

1.4 修改 hosts 文件

sudo tee -a /etc/hosts <<EOF
<k8s-control public_ip> k8s-control
<k8s-worker-1 public_ip> k8s-worker-1
EOF

1.5 关闭交换分区与防火墙

sudo swapoff -a # 暂时关闭交换分区
sudo sed -i '/swap/d' /etc/fstab # 保证重启后仍然生效

# 关闭操作系统防火墙,安装时建议,后续熟悉了可以精细化管理
sudo ufw disable

1.6 创建公网IP 的虚拟网卡

注意:如果所有机器之间可以使用内网互通,跳过此步

云服务商一般只会创建内网IP的虚拟网卡,因此在公网互通的环境下,需要我们手动创建公网IP的虚拟网卡, 在 所有 节点机器上执行如下命令:

# 默认网卡新增公网IP
sudo nano /etc/netplan/01-netcfg.yaml

---内容如下

network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      addresses:
        - <公网IP>/24 # 子网掩码255.255.255.0

---  

# 应用配置
sudo netplan apply

# 重启网络服务
sudo systemctl restart systemd-networkd

# 检查 netplan 状态
sudo netplan status

# 检查 systemd-networkd 状态
sudo systemctl status systemd-networkd

1.7 内核配置

# 加载bridge模块
sudo modprobe br_netfilter

# 持久化配置bridge模块
sudo tee /etc/modules-load.d/k8s.conf <<EOF
br_netfilter
EOF

# 设置所需的 sysctl 参数,参数在重新启动后保持不变
sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

# 应用 sysctl 参数而不重新启动
sudo sysctl --system

# 验证配置的值是否为 1
sysctl net.bridge.bridge-nf-call-ip6tables
sysctl net.bridge.bridge-nf-call-iptables
sysctl net.ipv4.ip_forward

1.8 同步所有节点的时间

# 同步时间
sudo apt install -y chrony
sudo systemctl enable --now chrony
timedatectl set-timezone Asia/Shanghai

2. 安装配置 CRI(容器运行时)

在所有机器上执行

k8s v1.24之前可以跳过这一步,因为v1.24之前的 k8s 直接集成了 Docker Engine 的一个组件,名为 dockershim

2.1 cgroup 驱动选择

我们选择将 systemd 作为 cgroup 驱动,原因如下:

  • 从 k8s v1.22 开始,在使用 kubeadm 创建集群时,如果用户没有在 KubeletConfiguration 下设置 cgroupDriver 字段,kubeadm 默认使用 systemd
  • 在 k8s v1.33 中,启用 KubeletCgroupDriverFromCRI 特性门控结合支持 RuntimeConfig CRI RPC 的容器运行时,kubelet 会自动从运行时检测适当的 Cgroup 驱动程序,并忽略 kubelet 配置中的 cgroupDriver 设置

因此我们要在 containerd 安装时,就选择 systemd 作为 Cgroup 驱动

2.2 安装 containerd

参考: github.com/containerd/…

2.2.1 下载 containerd 并安装

从 github.com/containerd/… 下载二进制包,然后执行如下命令安装

sudo tar Cxzvf /usr/local containerd-2.1.4-linux-amd64.tar.gz

2.2.2 下载并指定 systemdcgroup 驱动

sudo mkdir -p /usr/local/lib/systemd/system/

sudo wget -O /usr/local/lib/systemd/system/containerd.service https://raw.githubusercontent.com/containerd/containerd/main/containerd.service

sudo systemctl daemon-reload

sudo systemctl enable --now containerd

2.2.3 下载 runc 并安装

github.com/opencontain… 下载二进制包,然后执行如下命令安装

sudo install -m 755 runc.amd64 /usr/local/sbin/runc

2.2.4 下载 CNI plugins 并安装

github.com/containerne… 下载二进制包,然后执行如下命令安装

sudo mkdir -p /opt/cni/bin

sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.7.1.tgz 

2.2.5 生成 containerd 默认配置

sudo mkdir -p /etc/containerd
sudo touch /etc/containerd/config.toml
sudo bash -c 'containerd config default > /etc/containerd/config.toml'

2.2.6 国内镜像配置

www.cnblogs.com/plain-coder…

编辑 containerd 配置文件

sudo nano /etc/containerd/config.toml


[plugins]
	[plugins.'io.containerd.cri.v1.images']
	...
	[plugins.'io.containerd.cri.v1.images'.pinned_images]
      sandbox = 'registry.aliyuncs.com/google_containers/pause:3.10'
    
	[plugins.'io.containerd.cri.v1.images'.registry]
      config_path = '/etc/containerd/certs.d'
    ...    

配置路由

# docker hub镜像加速
sudo mkdir -p /etc/containerd/certs.d/docker.io
sudo tee /etc/containerd/certs.d/docker.io/hosts.toml << EOF
server = "https://docker.io"

[host."https://dockerproxy.cn"]
  capabilities = ["pull", "resolve"]

[host."https://docker.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
EOF

# registry.k8s.io镜像加速
sudo mkdir -p /etc/containerd/certs.d/registry.k8s.io
sudo tee /etc/containerd/certs.d/registry.k8s.io/hosts.toml << 'EOF'
server = "https://registry.k8s.io"

[host."https://k8s.m.daocloud.io"]
  capabilities = ["pull", "resolve", "push"]
EOF

# docker.elastic.co镜像加速
sudo mkdir -p /etc/containerd/certs.d/docker.elastic.co
sudo tee /etc/containerd/certs.d/docker.elastic.co/hosts.toml << 'EOF'
server = "https://docker.elastic.co"

[host."https://elastic.m.daocloud.io"]
  capabilities = ["pull", "resolve", "push"]
EOF

# gcr.io镜像加速
sudo mkdir -p /etc/containerd/certs.d/gcr.io
sudo tee /etc/containerd/certs.d/gcr.io/hosts.toml << 'EOF'
server = "https://gcr.io"

[host."https://gcr.m.daocloud.io"]
  capabilities = ["pull", "resolve", "push"]
EOF

# ghcr.io镜像加速
sudo mkdir -p /etc/containerd/certs.d/ghcr.io
sudo tee /etc/containerd/certs.d/ghcr.io/hosts.toml << 'EOF'
server = "https://ghcr.io"

[host."https://ghcr.m.daocloud.io"]
  capabilities = ["pull", "resolve", "push"]
EOF

# quay.io镜像加速
sudo mkdir -p /etc/containerd/certs.d/quay.io
sudo tee /etc/containerd/certs.d/quay.io/hosts.toml << 'EOF'
server = "https://quay.io"

[host."https://quay.m.daocloud.io"]
  capabilities = ["pull", "resolve", "push"]
EOF

# 默认镜像加速
sudo mkdir -p /etc/containerd/certs.d/_default
sudo tee /etc/containerd/certs.d/_default/hosts.toml << 'EOF'
[host."mirror.ccs.tencentyun.com"]
  capabilities = ["pull", "resolve"]
EOF

2.2.7 重启 containerd 生效

sudo systemctl restart containerd

3. 安装 kubeadm、kubelet 和 kubectl

在所有机器上执行

以下指令适用于 Kubernetes 1.33.

sudo apt-get update
# apt-transport-https 可能是一个虚拟包(dummy package);如果是的话,你可以跳过安装这个包
sudo apt-get install -y apt-transport-https ca-certificates curl gpg


# 如果 `/etc/apt/keyrings` 目录不存在,则应在 curl 命令之前创建它,请阅读下面的注释。
# sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg


# 此操作会覆盖 /etc/apt/sources.list.d/kubernetes.list 中现存的所有配置。
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list


sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

kubelet 现在每隔几秒就会重启,因为它陷入了一个等待 kubeadm 指令的死循环。

4. 修改 kubelet 参数

在所有机器上执行

sudo nano /etc/default/kubelet

---内容如下

KUBELET_EXTRA_ARGS=--node-ip=<当前机器公网IP>

---

5. 初始化控制平面节点

***控制平面机器上执行

5.1 kubeadm 初始化

如果初始化过程出错,可以执行sudo kubeadm reset -f命令后,重新执行初始化

# 说明: 10.244.0.0/16 是flannel特定要求的,其他组件按实际需求配置

# 节点之间通过内网通信的使用如下命令
sudo kubeadm init --apiserver-advertise-address=0.0.0.0 \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.33.4 \
--pod-network-cidr=10.244.0.0/16 \
--upload-certs \
--v=5

# 节点之间通过公网通信的使用如下命令
sudo kubeadm init \
--apiserver-advertise-address=$(hostname -i) \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.33.4 \
--pod-network-cidr=10.244.0.0/16 \
--upload-certs \
--v=5

成功后,执行如下命令,允许非 root 用户运行 kubectl , 注意:记得复制初始化完成后打印的工作节点 join 命令

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

5.2 集群健康检查

# 检查集群状态
kubectl cluster-info

# 健康检查
curl -k https://localhost:6443/healthz

# 等待所有Pod状态处于Running(除coredns外)
kubectl get pods -A -o wide

5.3 修改kube-apiserver 配置

sudo nano /etc/kubernetes/manifests/kube-apiserver.yaml

# 在 spec.containers.command 下添加`--bind-address`启动参数

--- 内容如下

spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=****** # 你的公网IP
    - --bind-address=0.0.0.0 # 增加这个
      
---

kubelet会监测到文件变动并自动重启API Server Pod以应用新配置

6. 安装网络插件

控制平面机器上执行

# 下载Flannel的Manifest文件
# 如果网络原因阻碍,可以本地下载文件后上传到服务器使用
sudo wget -O kube-flannel.yml https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml


# 修改 kube-flannel.yml

## 第一处修改点
containers:
- args:
  - --public-ip=$(PUBLIC_IP)  # 新增行
  - --iface-regex=10\.2\.\d+\.\d+  # 新增行,正则匹配你所有机器内网IP, 因为每个节点都会有一个flannel的Pod
  - --ip-masq=true
  - --kube-subnet-mgr
  command:
  - /opt/bin/flanneld
    
## 第二处修改点
env:
- name: PUBLIC_IP # 新增环境变量
  valueFrom:
	fieldRef:
	  fieldPath: status.podIP
- name: POD_NAME
  valueFrom:
	fieldRef:
	  fieldPath: metadata.name
	  

# 部署 Flannel
kubectl apply -f kube-flannel.yml

注意:Flannel使用的 VXLAN后端 并不具有对传输数据的加密功能,虽然提供了加密的 IPSec后端 , 但目前还是实验功能,如果你希望获得加密能力,服务器资源也允许的情况下,可以直接使用更高级的CNI 插件,比如 Cilium

查看所有Pod状态如下

image.png

7. 添加工作节点

***工作节点机器上执行 ***

执行第 5.1 步结尾复制的 kubeadm join 命令,类似如下格式

sudo kubeadm join <控制平面公网IP>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>

8. 部署Metrics Server

后续自动扩缩容、监控等都需要

# 如果网络原因阻碍,可以本地下载文件后上传到服务器使用
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

9. 控制平面允许调用Pod (可选)

出于安全原因,K8s集群默认不会在控制平面节点上调度 Pod。 如果你希望能够在 K8s 集群等控制平面节点上调度 Pod,可以执行如下命令(移除污点):

kubectl taint nodes --all node-role.kubernetes.io/control-plane-

10. 恭喜韩仙师

node的 INTERNAL-IP 实际为我们第 4 步指定的机器的公网IP

image.png

11. Troubleshooting

11.1 部署Kubernetes Metrics Server可能会遇到证书问题

11.1.1 Pod状态虽然显示Running,但没有READY

image.png

11.1.2 Pod日志类似如下

E0829 14:05:26.747406       1 scraper.go:149] "Failed to scrape node" err="Get \"https://<控制平面ip>:10250/metrics/resource\": tls: failed to verify certificate: x509: cannot validate certificate for <控制平面ip> because it doesn't contain any IP SANs" node="k8s-control"

E0829 14:05:26.753943       1 scraper.go:149] "Failed to scrape node" err="Get \"https://<工作节点IP>:10250/metrics/resource\": tls: failed to verify certificate: x509: cannot validate certificate for <工作节点IP> because it doesn't contain any IP SANs" node="k8s-worker-1"

10250 端口是 Kubelet API 专用的,由此想到可以修改Kebelet配置

11.1.3 解决方案

a. 修改所有服务器的Kubelet配置

# 开启服务端证书流转
echo "serverTLSBootstrap: true" | sudo tee -a /var/lib/kubelet/config.yaml

# 重新加载systemd配置
sudo systemctl daemon-reload
# 重启Kubelet
sudo systemctl restart kubelet

b. 在控制平面机器批准 CSR

# 检查 CSR
kubectl get csr

可以从 CSR 列表看到状态为 Pending的 CSR, 每个机器对应一条

image.png

# 批准 CSR
kubectl certificate approve <csr-name> # 将 <csr-name> 替换为实际的 CSR 名称

# 再次检查 CSR
kubectl get csr

可以看到CSR已经 Issued 了

image.png

c. 可以重新查看Metrics Server的Pod日志,不再会报错


创作不易,希望大家多多支持,文章持续更新,我们下期见.

程序员白话 | [原创]

点关注不迷路

可以抖音搜索「程序员白话」,大家有任何问题都可以私聊我,知无不言~