Kubernetes集群实践(一)使用Kubeadm创建一个K8s集群

本文主要介绍如何使用一个kubeadm创建一个K8s集群。

关键词:docker,k8s

准备工作

首先确保你至少有两台机器,这里准备了四台机器:一台master节点,三台node节点,主机名分别为node1、node2、node3。

master节点配置了管理node集群的脚本,分别是批量执行命令的xcall脚本和批量拷贝文件的xsync脚本。

安装xcall和xsync脚本的机器需要先配置免密码登录,并安装rsync服务

xcall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

# 获取控制台指令

cmd=$*

# 判断指令是否为空
if [ ! -n "$cmd" ]
then
echo "command can not be null !"
exit
fi

# 获取当前登录用户
user=`whoami`

# 在从机执行指令,这里需要根据你具体的集群情况配置,host与具体主机名一致,同上
for host in node1 node2 node3
do
echo =============== $host ===============
echo "--> excute command \"$cmd\""
ssh $host $cmd
done

echo "excute successfully !"

xsync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/bash

# 获取输出参数,如果没有参数则直接返回
pcount=$#
if [ $pcount -eq 0 ]
then
echo "no parameter find !";
exit;
fi

# 获取传输文件名
p1=$1
filename=`basename $p1`
echo "load file $p1 success !"

# 获取文件的绝对路径
pdir=`cd -P $(dirname $p1); pwd`
echo "file path is $pdir"

# 获取当前用户(如果想使用root用户权限拷贝文件,在命令后加入-root参数即可)
user=$2
case "$user" in
"-root")
user="root";;
"")
user=`whoami`;;
*)
echo "illegal parameter $user"

esac

echo $user
# 拷贝文件到从机(这里注意主机的host需要根据你的实际情况配置,要与你具体的主机名对应)
for host in node1 node2 node3
do
echo "================$host================="
ssh $host "mkdir -p $pdir"
rsync -av $pdir/$filename $host:$pdir/$filename
done

echo "complate !"

设置主机公钥确认

SSH 公钥检查是一个重要的安全机制,可以防范中间人劫持等黑客攻击。但是在特定情况下,严格的 SSH 公钥检查会破坏一些依赖 SSH 协议的自动化任务,就需要一种手段能够绕过 SSH 的公钥检查

编辑/etc/ssh/ssh_config文件,修改StrictHostKeyChecking=no.

  1. StrictHostKeyChecking=no 最不安全的级别,当然也没有那么多烦人的提示了,相对安全的内网测试时建议使用。如果连接server的key在本地不存在,那么就自动添加到文件中(默认是known_hosts),并且给出一个警告。
  2. StrictHostKeyChecking=ask 默认的级别,就是出现刚才的提示了。如果连接和key不匹配,给出提示,并拒绝登录。
  3. StrictHostKeyChecking=yes 最安全的级别,如果连接与key不匹配,就拒绝连接,不会提示详细信息。

将两个脚本所在的目录添加到环境变量

1
echo "export PATH=$PATH:~/.bin" >> ~/.bashrc

检查脚本是否正常运行

安装kubeadm

开始之前

你需要准备:

  • 一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux 发行版以及一些不提供包管理器的发行版提供通用的指令
  • 每台机器 2 GB 或更多的 RAM (如果少于这个数字将会影响你应用的运行内存)
  • 2 CPU 核或更多
  • 集群中的所有机器的网络彼此均能相互连接(公网和内网都可以)
  • 节点之中不可以有重复的主机名、MAC 地址或 product_uuid。请参见这里了解更多详细信息。
  • 开启机器上的某些端口。请参见这里 了解更多详细信息。
  • 禁用交换分区。为了保证 kubelet 正常工作,你 必须 禁用交换分区。
禁用交换分区

临时关闭

1
swapoff -a

永久关闭

1
vim /etc/fstab

注释掉最后一行的swap

确保每个节点上 MAC 地址和 product_uuid 的唯一性

这里直接使用xcall脚本执行命令。

一般来讲,硬件设备会拥有唯一的地址,但是有些虚拟机的地址可能会重复。 Kubernetes 使用这些值来唯一确定集群中的节点。 如果这些值在每个节点上不唯一,可能会导致安装失败

检查mac地址

1
ip link

检查product_uuid

1
sudo cat /sys/class/dmi/id/product_uuid

检查网络适配器

如果有一个以上的网络适配器,同时K8s通过默认路由不可达,此时需要先添加默认IP路由规则。

这里使用的是同一局域网的虚拟机,默认符合规则。

允许 iptables 检查桥接流量

检查br_netfilter模块是否被加载

1
lsmod | grep br_netfilter

这里没有加载,需要设置加载

显式加载

1
sudo modprobe br_netfilter

为了让Linux开机时就加载,需要修改sysctl 配置

1
2
3
4
5
6
7
8
9
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system

安装IPVS所需要的模块

ipvs网络模式比iptables具有更优秀的性能

1
2
3
4
5
6
7
cat <<EOF | sudo tee /etc/modules-load.d/k3s.conf
ip_vs
ip_vs_lc
ip_vs_rr
ip_vs_wrr
ip_vs_sh
EOF

实时加载模块

1
2
3
4
5
sudo modprobe ip_vs
sudo modprobe ip_vs_lc
sudo modprobe ip_vs_rr
sudo modprobe ip_vs_wrr
sudo modprobe ip_vs_sh

安装查看ipvs表软件包

1
sudo apt install ipvsadm -y

服务起来以后,可以执行以下命令查看ipvs规则

1
ipvsadm -ln

检查所需端口

为了和K8s组件通信,需要检查特定端口

具体请查看:https://kubernetes.io/zh/docs/reference/ports-and-protocols/

安装容器运行时

设置运行时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 设置必需的 sysctl 参数,这些参数在重新启动后仍然存在。
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

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

K8s支持以下运行时

运行时 域套接字
Docker Engine /var/run/dockershim.sock
containerd /run/containerd/containerd.sock
CRI-O /var/run/crio/crio.sock

由于在1.24之后的版本不在支持Dockershim ,因此不推荐使用Docker Engine作为K8s容器运行时,这里使用containerd

方法1:使用docker-ce 镜像源

后期还可以通过安装docker-cli实现docker的一系列操作,如打包镜像、使用docker-compose运行容器等。

  • 按照安装docker的方法,安装docker需要的apt源;

  • 执行以下命令

    1
    sudo apt-get update && sudo apt-get install containerd.io

配置运行时

1
2
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

重新启动 containerd

1
sudo systemctl restart containerd

方法2:直接使用nerdctl打包好的containerd运行时和一系列插件

nerdctl是containerd项目下的和docker cli兼容的命令行。现在已经支持docker-cli的大部分功能。甚至可以直接将nerdctl设置为docker,这样就可以无缝使用docker和docker compose的命令啦。

1
2
alias docker=`sudo nerdctl`
alias docker-compose=`sudo nerdctl compose`

项目地址:https://github.com/containerd/nerdctl

直接下载Full的版本

下载完毕以后执行安装命令,以0.20.0版本为例

1
sudo tar Cxzvvf /usr/local nerdctl-full-0.20.0-linux-amd64.tar.gz

设置自动启动

1
2
3
sudo systemctl enable buildkit.service --now
sudo systemctl enable containerd.service --now
sudo systemctl enable stargz-snapshotter.service --now
配置stargz-snapshotter

Fast container image distribution plugin with lazy pulling,项目地址:https://github.com/containerd/stargz-snapshotter

修改/etc/containerd/config.toml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version = 2

# Plug stargz snapshotter into containerd
# Containerd recognizes stargz snapshotter through specified socket address.
# The specified address below is the default which stargz snapshotter listen to.
[proxy_plugins]
[proxy_plugins.stargz]
type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"

# Use stargz snapshotter through CRI
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "stargz"
disable_snapshot_annotations = false
设置Docker Hub镜像源

默认containerd在Docker Hub拉取镜像,这样速度很慢

/etc/containerd创建mirrors.d文件夹

编辑hosts.toml,这里使用daocloud的源

1
2
3
4
server = "https://docker.io"

[host."http://f1361db2.m.daocloud.io"]
capabilities = ["pull", "resolve"]
使用 systemd cgroup 驱动程序

结合 runc 使用 systemd cgroup 驱动,在 /etc/containerd/config.toml 中设置

1
2
3
4
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true

如果您应用此更改,请确保再次重新启动 containerd:

1
sudo systemctl restart containerd

注意:此项更改并不能使用crictl infoconfig dumpnerdctl info查看,可以通过systemctl status containerd查看是否存在/opt/dev/bin/containerd-shim-runc-v2来判断是否生效。

https://kubernetes.io/zh/docs/setup/production-environment/container-runtimes/#containerd

https://github.com/containerd/containerd/issues/4900#issuecomment-756085464

安装 kubeadm、kubelet 和 kubectl

这里使用基于Debian的发行版

  1. 更新 apt包索引并安装使用 Kubernetes apt 仓库所需要的包
1
2
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
  1. 安装仓库密钥,这里使用阿里云的源
1
2
3
4
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add - 
cat << EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF

官方源:

1
2
3
4
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - 
cat << EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
  1. 更新apt包索引,安装 kubelet、kubeadm 和 kubectl,并锁定其版本
1
2
3
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

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

配置容器运行时 cgroup 驱动

由于 kubeadm 把 kubelet 视为一个系统服务来管理,所以对基于 kubeadm 的安装, 推荐使用 systemd 驱动,不推荐 cgroupfs 驱动。

一个示例:

1
2
3
4
5
6
7
8
9
10
11
# kubeadm-config.yaml
kind: ClusterConfiguration
apiVersion: kubeadm.k8s.io/v1beta3
kubernetesVersion: v1.23.6
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
networking:
podSubnet: "10.244.0.0/16" # --pod-network-cidr # 网络插件地址
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd

启动控制节点

启用控制平面前应先启动containerd服务

1
sudo systemctl enable containerd --now

修改containerd.sock权限,不使用sudo也可以拉取镜像

1
sudo chown -R wf09:wf09 /run/containerd/

运行master节点之前可以先拉取本地镜像

1
kubeadm config images pull --config kubeadm-config.yaml

在master节点运行以下命令,同时传递上文中说到的kubeadm-config.yaml

1
kubeadm init --config kubeadm-config.yaml

可以使用以下命令重新打印token和加入命令

1
kubeadm token create --print-join-command

删除控制节点

如果在启动后控制平面以后出现错误,必须先删除控制平面

更详细的可以参考卸载集群

1
sudo kubeadm reset

控制节点启动成功

如果使用非root用户运行kubectl,需要运行以下命令

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

如果使用root用户,则可以运行

1
export KUBECONFIG=/etc/kubernetes/admin.conf

还需要记录一下token,后面的节点加入集群需要这个

1
2
kubeadm join 192.168.15.201:6443 --token 57hnhv.sy8wb7c1gsrzbotp \
--discovery-token-ca-cert-hash sha256:15cb23455b3cb846261b535dcbbf73ea36e3f089cf4b8b36623bf36b461b522d

安装 Pod 网络附加组件

必须部署一个基于Pod网络插件的容器网络接口,以便的Pod可以相互通信

安装之前需要先设置网络插件地址,在kubeadm-config.yaml添加以下配置

1
2
networking:
podSubnet: "10.244.0.0/16" # --pod-network-cidr # 网络插件地址

这里使用flannel

1
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

检查节点状态

1
kubectl get nodes

当STATUS状态为Ready时说明集群已经启动成功了。

设置Master为可调度节点

默认情况下,Master 不参与 Pod 调度,也就是说不会在 Master 节点上部署其他非系统 Pod。可以使用以下命令调整这个策略

1
2
3
4
5
6
7
# 允许 Master 部署 Pod
kubectl taint nodes master node-role.kubernetes.io/control-plane- --overwrite

# 禁止 Master 部署 Pod
kubectl taint nodes master node-role.kubernetes.io/control-plane=:NoSchedule --overwrite

# 其中 master 是操作的节点名称

启动工作节点

准备工作首先也需要完成安装kubeadm的步骤。

剩下的步骤就比较简单了,找到上文中成功启动K8s控制节点中的token,在工作节点执行

1
2
sudo kubeadm join 192.168.15.201:6443 --token 57hnhv.sy8wb7c1gsrzbotp \
--discovery-token-ca-cert-hash sha256:15cb23455b3cb846261b535dcbbf73ea36e3f089cf4b8b36623bf36b461b522d

如果有类似的回显说明已经加入成功了。

如果忘记master节点的token可以在master节点上执行命令重新创建一个token

1
sudo kubeadm token create --print-join-command

集群启动成功

Kubernetes排错

[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory “/etc/kubernetes/manifests”. This can take up to 4m0s

查看kubelet日志

1
journalctl -xeu kubelet

出现频率较高的就是sandbox,应该和这个镜像有关

查看containerd默认配置

1
cat /etc/containerd/config.toml

发现默认配置是k8s.gcr.io域的镜像,国内拉取不到。

上文中已经拉取了阿里云的镜像,执行以下命令可以查看镜像的拉取地址

1
crictl images

如果报错应该是权限问题,containerd 的创建的unix socket文件权限是root,可以试试加上sudo

解决方案

k8s.gcr.io/pause镜像更改为本地拉取的阿里云镜像即可。

加入节点时,提示"getting status of runtime: rpc error: code = Unimplemented desc = unknown service runtime.v1alpha2.RuntimeService"

解决方案

检查/etc/containerd/config.toml文件,将SystemdCgroup设置为true

重启containerd服务

1
sudo systemctl restart containerd

failed to set bridge addr: "cni0" already has an IP address different from 10.244.1.1/24

在集群出现异常,需要重置集群的时候可能会出现此问题。如果是第一次安装则不会,因为此时是集群第一次安装cni网络组件,不会出现地址冲突的问题。

解决方案
1
2
ifconfig cni0 down    
ip link delete cni0

FATA[0000] failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error running hook #0: error running hook: exit status 1, stdout: , stderr: time="2022-05-24T02:29:02Z" level=fatal msg="failed to call cni.Setup: plugin type="bridge" failed (add): incompatible CNI versions; config is "1.0.0", plugin supports ["0.1.0" "0.2.0" "0.3.0" "0.3.1" "0.4.0"]"

未安装CNI组件导致容器运行失败

解决方案

直接使用nerdctl打包好的containerd运行时和一系列插件即可,参考上文安装kubeadm——安装容器运行时——方法2

May 24 06:17:48 aws-jp-4G kubelet[184529]: E0524 06:17:48.193006 184529 kubelet.go:2419] "Error getting node" err="node "aws-jp-4g" not found"

一般是因为API Server的IP没有绑定成功,导致无法访问到对应的API Server端口,从而kubelet认为节点没有注册成功。

解决方案

重新初始化集群,从新注册网络即可。

  • https://github.com/flannel-io/flannel

  • https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network

  • https://github.com/containerd/containerd/blob/main/docs/cri/registry.md

删除Terminating的Pod

在执行删除命令时应该使用一下两个flag

1
kubectl delete pod <PODNAME> --grace-period=0 --force --namespace <NAMESPACE>

如果已经执行了删除命令而pod没有被立即删除,使用以下脚本删除Terminating的Pod

1
2
3
4
5
kubectl get pods --all-namespaces | grep Terminating | while read line; do
pod_name=$(echo $line | awk '{print $2}' ) \
name_space=$(echo $line | awk '{print $1}' ); \
kubectl delete pods $pod_name -n $name_space --grace-period=0 --force
done