目录
为了提高命令行使用效率,建议先安装ohmyzsh。此外,本教程演示安装的K8s版本为 v1.27.0 ,你也可以选择其他版本,但最好是官方仍在维护的版本。
在生产环境中,你不必自行安装维护K8s集群基础组件,可以使用云厂商提供的K8s服务,如腾讯云 TKE和阿里云 ACK,国外有AWS EKS、Google GKE、Azure AKS等。
10.0.2.2 k8s-master
10.0.2.3 k8s-node1
两台机,最低配置如下:
- cpu: 2c+
- mem: 2g+
- disk: 20g+
- network: 同属一个子网
在实战中,master节点配置通常是中高配置(如4c8g,8c16g),虽然k8s不会调度pod到master上运行,但由于Master是整个 Kubernetes 集群的核心部分,负责协调、管理和调度所有工作负载。并且Master节点上运行着各种关键组件(如 etcd、kube-apiserver、kube-controller-manager 和 kube-scheduler),这些组件都需要处理大量的网络流量和计算任务。
容器运行时是指用于直接对镜像和容器执行基础操作(比如拉取/删除镜像和对容器的创建(使用镜像)/查询/修改/获取/删除等操作)的软件。
最开始的K8s版本只支持Docker作为容器运行时,但为了更好与底层容器技术解耦(同时也是为了兼容其他容器技术),K8s 在v1.5.0就引入了容器运行时接口(CRI)。CRI是K8s与第三方容器运行时通信接口的标准化抽象,它定义了容器运行时必须实现的一组标准接口, 包括前面所说的针对镜像和容器的各项基础操作。
K8s通过CRI支持多个容器运行时,包括Docker、containerd、CRI-O、qemu等, 更多请查看CNCF旗下的容器运行时列表。
后来之所以K8s要宣称放弃Docker(在K8s v1.20)而选择container作为默认容器运行时,是因为Docker并不只是一个容器软件,而是一个完整的技术堆栈,它包含了许多除了容器软件基础功能以外的东西,这些东西不是K8s所需要的,而且增加K8s调度容器的性能开销。 由于Docker本身并不支持CRI(在提出CRI的时候),所以K8s代码库(自v1.5.0起)中实现了一个叫做docker-shim的CRI兼容层来作为中间垫片连接了Docker与K8s。 K8s官方表示,一直以来,维护docker-shim都是一件非常费力不讨好的事情,它使K8s的维护变得复杂,所以当CRI稳定之后,K8s立即在代码库中添加了docker-shim即将废弃的提示(v1.20), 如果使用Docker作为运行时,在kubelet启动时打印一个警告日志。最终在K8s v1.24中删除了docker-shim相关的代码。
如果在K8s v1.20及以后版本依然使用Docker作为容器运行时,需要安装配置一个叫做cri-dockerd的组件(作用类似docker-shim),它是一个轻量级的守护进程,用于将Docker请求转换为CRI请求。
关于containerd,其实它就是Docker内部的容器运行时,只是Docker将containerd封装了一层,并且添加了其他有助于用户使用Docker的多项功能。 K8s废弃了docker-shim以后,Docker公司也声明了会和Mirantis公司继续维护docker-shim(作为一个Docker内的一个组件)。
然而,K8s的这一系列操作不仅仅是针对容器运行时,还包括容器网络层、容器存储层都制定了相应的接口规范,分别叫做CNI和CSI。此外, 还有一个叫OCI(Open Container Initiative)的组织,它标准化了容器工具和技术之间的许多接口,包括容器映像打包(OCI image-spec)和运行容器(OCI runtime-spec)的标准规范,这就让来自不同厂商开发的容器产品(镜像/运行时)能够更好的协同工作。 CRI也是建立在这些底层规范的基础上,为管理容器提供了一个端到端的标准。
Runtime | Path to Unix domain socket |
---|---|
containerd | unix:///var/run/containerd/containerd.sock |
CRI-O | unix:///var/run/crio/crio.sock |
Docker Engine (using cri-dockerd) | unix:///var/run/cri-dockerd.sock |
这里只列出了常见的容器运行时及对应的socket端点,对于其他容器运行时,你会在它们的安装文档中看到对应的端点路径。
containerd对CRI的支持最开始也是单独的一个项目,叫做cri(但对外叫
cri-containerd
),后来被集成到containerd中。
kubernetes 1.24.x及以后版本默认CRI为containerd。安装containerd时自带的命令行工具是ctr
,我们可以使用ctr
来直接管理containerd中的镜像或容器资源(包括由K8s间接管理的)。
由K8s间接管理的镜像和容器资源都存放在containerd中名为
k8s.io
的命名空间下,例如你可以(在安装完集群后)通过ctr -n k8s.io c ls
查看K8s在当前节点调度的容器列表。
而K8s提供的基于CRI的命令行工具则是crictl
,会在下一节中安装K8s基础组件时自动安装。例如你可以通过 crictl ps
查看K8s在当前节点调度的容器列表,使用crictl -h
查看使用帮助。
在所有机器上运行以下命令:
# - 安装依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# - 设置源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install containerd -y
containerd --version
# 创建或修改配置,参考下面的文字说明
# vi /etc/containerd/config.toml
systemctl enable containerd # 开机启动
systemctl daemon-reload
systemctl restart containerd
systemctl status containerd
对于/etc/containerd/config.toml
文件,我们需要修改其中关于镜像源部分的配置,以实现部分镜像仓库源的镜像下载加速。修改的位置关键字为:registry.mirrors
, sandbox_image
。
你也可以使用仓库中的 containerd.config.toml 进行覆盖(先备份现有的)。
即 kubeadm、kubelet 和 kubectl。
在centos上安装(所有节点):
# 设置阿里云为源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
# centos 安装各组件
# -- 你也可以仅在一个节点安装kubectl,用于管理集群
sudo yum install -y wget lsof net-tools jq \
kubelet-1.27.0 kubeadm-1.27.0 kubectl-1.27.0 --disableexcludes=kubernetes
# 开机启动,且立即启动
sudo systemctl enable --now kubelet
# 检查版本
kubeadm version
kubelet --version
kubectl version
# 配置容器运行时,以便后续通过crictl管理 集群内的容器和镜像
crictl config runtime-endpoint unix:///var/run/containerd/containerd.sock
如果是ubuntu系统(供参考):
apt-get update && apt-get install -y apt-transport-https
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
# 设置阿里源
cat <<EOF > /etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet=1.27.0-00 kubeadm=1.27.0-00 kubectl=1.27.0-00
# 查看软件仓库包含哪些版本 apt-cache madison kubelet
# 删除 apt-get remove -y kubelet kubeadm kubectl
注意更新节点时间(部署的Pod资源会使用节点的时间):
yum install -y ntpdate
ntpdate -u pool.ntp.org
在 Kubernetes 集群中,为了确保系统资源的一致性和协同工作,kubelet 和容器运行时的配置需要匹配。其中一个关键的配置项是 cgroup 驱动。kubelet 是 Kubernetes 集群中的节点代理,负责与容器运行时通信,而 cgroup 驱动则决定了 kubelet 如何在底层 Linux 系统上组织和控制容器的资源。
这里分为两个步骤:
- 配置容器运行时 cgroup 驱动
- 配置 kubelet 的 cgroup 驱动
对于第一步,本文编写时安装的containerd(K8s使用的容器运行时)默认使用systemd
作为croup驱动,所以无需配置。
而第二步,从K8s v1.22起,kubeadm也默认使用systemd
作为 kubelet 的cgroupDriver。
本节只做必要的步骤说明,由于演示安装的是v1.27.0版本,并不需要执行配置操作。如果想要了解更多细节, 可以参考官方文档。
下面的命令需要在所有机器上执行。
设置hosts
# 建议主机ip与教程一致
cat <<EOF >> /etc/hosts
10.0.2.2 k8s-master
10.0.2.3 k8s-node1
EOF
设置每台机器的hostname
# 在master节点执行
hostnamectl set-hostname k8s-master
# 在node1节点执行
hostnamectl set-hostname k8s-node1
logout后再登录可见。
# 关闭swap:
swapoff -a # 临时关闭
sed -ri 's/.*swap.*/#&/' /etc/fstab #永久关闭
# 关闭selinux
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# 关闭防火墙
iptables -F
iptables -X
systemctl stop firewalld.service
systemctl disable firewalld
设置sysctl
cat > /etc/sysctl.conf << EOF
vm.swappiness=0
vm.overcommit_memory=1
vm.panic_on_oom=0
fs.inotify.max_user_watches=89100
EOF
sysctl -p # 生效
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1
EOF
sysctl --system # 生效
# 加载内核模块
modprobe br_netfilter # 网络桥接模块
modprobe overlay # 联合文件系统模块
lsmod | grep -e br_netfilter -e overlay
# 提前拉取需要的image
kubeadm config images pull --image-repository registry.aliyuncs.com/google_containers
# 查看拉取的镜像
$ crictl images
IMAGE TAG IMAGE ID SIZE
registry.aliyuncs.com/google_containers/coredns v1.9.3 5185b96f0becf 14.8MB
registry.aliyuncs.com/google_containers/etcd 3.5.6-0 fce326961ae2d 103MB
registry.aliyuncs.com/google_containers/kube-apiserver v1.27.0 48f6f02f2e904 35.1MB
registry.aliyuncs.com/google_containers/kube-controller-manager v1.27.0 2fdc9124e4ab3 31.9MB
registry.aliyuncs.com/google_containers/kube-proxy v1.27.0 b2d7e01cd611a 20.5MB
registry.aliyuncs.com/google_containers/kube-scheduler v1.27.0 62a4b43588914 16.2MB
registry.aliyuncs.com/google_containers/pause 3.8 4873874c08efc 311kB
registry.cn-hangzhou.aliyuncs.com/google_containers/pause 3.6 6270bb605e12e 302kB
# 初始化集群
# --apiserver-advertise-address 指定 Kubernetes API Server 的宣告地址,可以不设置让其自动检测
# 其他节点和客户端将使用此地址连接到 API Server
# --image-repository 指定了 Docker 镜像的仓库地址,用于下载 Kubernetes 组件所需的容器镜像。在这里,使用了阿里云容器镜像地址,可以加速镜像的下载。
# 注意:即使提取拉取了镜像,这里也要指定相同的仓库,否则还是会拉取官方镜像
# --service-cidr 指定 Kubernetes 集群中 Service 的 IP 地址范围,Service IP 地址将分配给 Kubernetes Service,以允许它们在集群内通信
# --pod-network-cidr 指定 Kubernetes 集群中 Pod 网络的 IP 地址范围。Pod IP 地址将分配给容器化的应用程序 Pod,以便它们可以相互通信。
$ kubeadm init \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.27.0 \
--service-cidr=20.1.0.0/16 \
--pod-network-cidr=20.2.0.0/16
[init] Using Kubernetes version: v1.27.0
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
... 省略
k8s-cluster-init.log 是一个k8s集群初始化日志实例。
注意暂存日志输出的最后部分:
...
kubeadm join 10.0.2.2:6443 --token 4iwa6j.ejrsfqm26jpcshz2 \
--discovery-token-ca-cert-hash sha256:f8fa90012cd3bcb34f3198b5b6184dc45104534f998ee601ed97c39f2efa8b05
这是一条普通节点加入集群的命令。其中包含一个临时token和证书hash,如果忘记或想要查询,通过以下方式查看:
# 1. 查看token
$ kubeadm token list
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
4iwa6j.ejrsfqm26jpcshz2 23h 2023-11-13T08:32:40Z authentication,signing The default bootstrap token generated by 'kubeadm init'. system:bootstrappers:kubeadm:default-node-token
# 2. 查看cert-hash
$ openssl x509 -in /etc/kubernetes/pki/ca.crt -pubkey -noout | openssl pkey -pubin -outform DER | openssl dgst -sha256
(stdin)= f8fa90012cd3bcb34f3198b5b6184dc45104534f998ee601ed97c39f2efa8b05
token默认有效期24h。在过期后还想要加入集群的话,我们需要手动创建一个新token:
# cert-hash一般不会改变
$ kubeadm token create --print-join-command
kubeadm join 10.0.2.2:6443 --token eczspu.kjrxrem8xv5x7oqm --discovery-token-ca-cert-hash sha256:f8fa90012cd3bcb34f3198b5b6184dc45104534f998ee601ed97c39f2efa8b05
$ kubeadm token list
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
4iwa6j.ejrsfqm26jpcshz2 23h 2023-11-14T08:25:28Z authentication,signing <none> system:bootstrappers:kubeadm:default-node-token
eczspu.kjrxrem8xv5x7oqm 23h 2023-11-14T08:32:40Z authentication,signing The default bootstrap token generated by 'kubeadm init'. system:bootstrappers:kubeadm:default-node-token
以便用户可以使用 kubectl 工具与 Kubernetes 集群进行通信,下面的操作只需要在master节点执行一次。
若是root用户,执行:
echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> /etc/profile
source /etc/profile
不是root用户,执行:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
查看节点状态:
[root@k8s-master calico]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master NotReady control-plane 7m14s v1.27.0
[root@k8s-master calico]# kubectl cluster-info
Kubernetes control plane is running at https://10.0.2.2:6443
CoreDNS is running at https://10.0.2.2:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
这里由于还未安装pod网络插件,所以是NotReady,后面步骤解决。
# 在node1上执行
# 注意使用初始化集群时输出的命令,确认token和cert-hash正确
$ kubeadm join 10.0.2.2:6443 --token ihde1u.chb9igowre1btgpt --discovery-token-ca-cert-hash sha256:fcbe96b444325ab7c854feeae7014097b6840329a608415b08c3af8e8e513573
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[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.
然后在master上查看节点状态:
[root@k8s-master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master NotReady control-plane 3m48s v1.27.0
k8s-node1 NotReady <none> 6s v1.27.0
下节解决节点状态是NotReady
的问题。
Kubernetes 需要网络插件(Container Network Interface: CNI)来提供集群内部和集群外部的网络通信。以下是一些常用的 k8s 网络插件:
- Flannel:Flannel 是最常用的 k8s 网络插件之一,它使用了虚拟网络技术来实现容器之间的通信,支持多种网络后端,如 VXLAN、UDP 和 Host-GW。
- Calico:Calico 是一种基于 BGP 的网络插件,它使用路由表来路由容器之间的流量,支持多种网络拓扑结构,并提供了安全性和网络策略功能。
- Canal:Canal 是一个组合了 Flannel 和 Calico 的网络插件,它使用 Flannel 来提供容器之间的通信,同时使用 Calico 来提供网络策略和安全性功能。
- Weave Net:Weave Net 是一种轻量级的网络插件,它使用虚拟网络技术来为容器提供 IP 地址,并支持多种网络后端,如 VXLAN、UDP 和 TCP/IP,同时还提供了网络策略和安全性功能。
- Cilium:Cilium 是一种基于 eBPF (Extended Berkeley Packet Filter) 技术的网络插件,它使用 Linux 内核的动态插件来提供网络功能,如路由、负载均衡、安全性和网络策略等。
- Contiv:Contiv 是一种基于 SDN 技术的网络插件,它提供了多种网络功能,如虚拟网络、网络隔离、负载均衡和安全策略等。
- Antrea:Antrea 是一种基于 OVS (Open vSwitch) 技术的网络插件,它提供了容器之间的通信、网络策略和安全性等功能,还支持多种网络拓扑结构。
更多插件列表查看 官方文档 。 这里选择calico,安装步骤如下:
mkdir -p ~/k8s/calico && cd ~/k8s/calico
# 注意calico版本需要匹配k8s版本,否则无法应用
wget --no-check-certificate https://raw.gitmirror.com/projectcalico/calico/v3.26.1/manifests/calico.yaml
#!!!
# 修改calico.yaml,在 CALICO_IPV4POOL_CIDR 的位置,修改value为pod网段:20.2.0.0/16 (与前面的--pod-network-cidr参数一致)
# 应用配置文件
# - 这将自动在Kubernetes集群中创建所有必需的资源,包括DaemonSet、Deployment和Service等
kubectl apply -f calico.yaml
# 观察calico 的几个 pod是否 running,这可能需要几分钟
$ kubectl get pods -n kube-system -w |grep calico
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-74cfc9ffcc-85ng7 0/1 Pending 0 17s
calico-node-bsqtv 0/1 Init:ErrImagePull 0 17s
calico-node-xjwt8 0/1 Init:ErrImagePull 0 17s
...
# 观察到calico镜像拉取失败,查看pod日志
kubectl describe pod -n kube-system calico-node-bsqtv
# 从输出中可观察到是拉取 docker.io/calico/cni:v3.26.1 镜像失败,改为手动拉取(在所有节点都执行)
ctr -n k8s.io image pull docker.io/calico/cni:v3.26.1 &&
ctr -n k8s.io image pull docker.io/calico/node:v3.26.1 &&
ctr -n k8s.io image pull docker.io/calico/kube-controllers:v3.26.1
# 检查
$ ctr image ls
# 删除calico pod,让其重启
kk delete pod -l k8s-app=calico-node -n kube-system
kk delete pod -l k8s-app=calico-kube-controllers -n kube-system
# 观察pod状态
kk get pods -A --watch
# ok后,重启一下网络(笔者出现集群正常后,无法连通外网,重启后可以)
service network restart
当需要重置网络时,在master节点删除calico全部资源:kubectl delete -f calico.yaml
,然后在所有节点执行:
rm -rf /etc/cni/net.d && service kubelet restart
安装calicoctl
(可选),方便观察calico的各种信息和状态:
# 第1种安装方式(推荐)
curl -o /usr/local/bin/calicoctl -O -L "https://hub.gitmirror.com/https://github.com/projectcalico/calico/releases/download/v3.26.1/calicoctl-linux-amd64"
chmod +x /usr/local/bin/calicoctl
# calicoctl 常用命令
calicoctl node status
calicoctl get nodes
# 第2种安装方式
curl -o /usr/local/bin/kubectl-calico -O -L "https://hub.gitmirror.com/https://github.com/projectcalico/calico/releases/download/v3.26.1/calicoctl-linux-amd64"
chmod +x /usr/local/bin/kubectl-calico
kubectl calico -h
# 检查Calico的状态
[root@k8s-master calico]# kubectl calico node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+-------------------+-------+----------+-------------+
| 10.0.2.3 | node-to-node mesh | up | 13:26:06 | Established |
+--------------+-------------------+-------+----------+-------------+
IPv6 BGP status
No IPv6 peers found.
# 列出Kubernetes集群中所有节点的状态,包括它们的名称、IP地址和状态等
[root@k8s-master calico]# kubectl calico get nodes
NAME
k8s-master
k8s-node1
现在再次查看集群状态,一切OK。
[root@k8s-master ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 64m v1.27.0
k8s-node1 Ready <none> 61m v1.27.0
默认情况下,我们只能在master上运行kubectl命令,如果在普通节点执行会得到以下错误提示:
[root@k8s-node1 ~]# kubectl get nodes
The connection to the server localhost:8080 was refused - did you specify the right host or port?
kubectl命令默认连接本地的8080端口,需要修改配置文件,指向master的6443端口。当然,除了连接地址外还需要证书完成认证。 这里可以直接将master节点的配置文件拷贝到普通节点:
# 拷贝配置文件到node1,输入节点密码
[root@k8s-master ~]# scp /etc/kubernetes/admin.conf root@k8s-node1:/etc/kubernetes/
# 在节点配置环境变量后即可使用
[root@k8s-node1 ~]# echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> /etc/profile
[root@k8s-node1 ~]# source /etc/profile
[root@k8s-node1 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 17h v1.27.0
k8s-node1 Ready <none> 16h v1.27.0
但是,在实际环境中,我们通常不需要做这个操作。因为普通节点相对master节点只是一种临时资源,可能会以后某个时间点退出集群。
而且/etc/kubernetes/admin.conf
是一个包含证书密钥的敏感文件,不应该存在于普通节点上。
后面如果想要彻底删除集群,在所有节点执行:
kubeadm reset -f # 重置集群 -f 强制执行
rm -rf /var/lib/kubelet # 删除核心组件目录
rm -rf /etc/kubernetes # 删除集群配置
rm -rf /etc/cni/net.d/ # 删除容器网络配置
rm -rf /var/log/pods && rm -rf /var/log/containers # 删除pod和容器日志
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X # 删除 iptables 规则
service kubelet restart
# 镜像一般保留,查看当前节点已下载的镜像命令如下
crictl images
# 快速删除节点上的全部镜像
# rm -rf /var/lib/containerd/*
# 然后可能需要重启节点才能再次加入集群
reboot
这一节通过在集群中快速部署nginx服务来验证集群是否正常工作。
在master上执行下面的命令:
# 创建pod
kubectl create deployment nginx --image=nginx
# 添加nginx service,设置映射端口
# 如果是临时测试:kubectl port-forward deployment nginx 3000:3000
kubectl expose deployment nginx --port=80 --type=NodePort
# 查看pod,svc状态
$ kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/nginx-76d6c9b8c-g5lrr 1/1 Running 0 7m12s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 20.1.0.1 <none> 443/TCP 94m
service/nginx NodePort 20.1.255.52 <none> 80:30447/TCP 7m
上面通过NodePort类型的Service来暴露了Pod,它将容器80端口映射到所有节点的一个随机端口(这里是30447)。 然后我们可以通过访问节点端口来测试在所有集群机器上的pod连通性:
# 在master上执行
$ curl http://10.0.2.2:30447
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
$ curl http://10.0.2.3:30447
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
删除部署
kubectl delete deployment nginx
kubectl delete svc nginx
至此,使用kubeadm搭建集群结束。但是还有一些进阶话题需要讨论,比如k8s镜像清理、日志存储等,参考下一篇文档。
镜像下载慢会导致节点一直停留在NotReady
状态,可以通过手动拉取的方式解决:
# awk去重
$ cat calico.yaml|grep image: |awk '!seen[$0]++'
image: docker.io/calico/cni:v3.26.1
image: docker.io/calico/node:v3.26.1
image: docker.io/calico/kube-controllers:v3.26.1
# 一次性手动拉取上面的三个镜像(需要在所有节点执行)
$ grep -oP 'image:\s*\K[^[:space:]]+' calico.yaml |awk '!seen[$0]++' | xargs -n 1 ctr image pull
# 等价于
ctr image pull docker.io/calico/cni:v3.26.1
ctr image pull docker.io/calico/node:v3.26.1
ctr image pull docker.io/calico/kube-controllers:v3.26.1
因为之前安装containerd时在其配置文件中添加了国内源,所以这里直接使用ctr手动拉取位于docker.io
的镜像的速度会很快。
在后续测试过程中,你也可以使用这个方式来解决国外镜像下载慢的问题。对于生产环境,通常是使用本地镜像仓库,一般不会有这个问题。
k8s默认使用
crictl pull <image-name>
命令拉取镜像,但crictl读取不到containerd设置的国内源,所以才会慢。
每个新创建的Pod都需要calico分配IP,如果calico无法分配IP,就会导致Pod启动异常:
$ kk describe po hellok8s-go-http-999f66c56-4k72x
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreatePodSandBox 3d6h kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "603ebf87036af6c05e6bf26c82403b404cc9763b5d20ab89cd08286969899348": plugin type="calico" failed (add): error getting ClusterInformation: connection is unauthorized: Unauthorized
这里的connection is unauthorized: Unauthorized
其实是calico的日志,根本原因是calico用来查询集群信息的ServiceAccount
Token过期了。
calico使用的token存储在/etc/cni/net.d/calico-kubeconfig
,通过cat可以查看。这个token的有效期只有24h,
但不知为何calico没有自动续期导致Pod无法正常创建和删除(对应分配和释放IP操作)。
一个快速解决的办法是删除calico-node
Pod,这样它在重建calico-node
Pod后会生成新的token:
$ kk delete po -l k8s-app=calico-node -A
pod "calico-node-v94sd" deleted
pod "calico-node-xzxbd" deleted
再次观察Pod状态就会正常了。
默认情况下kubernetes集群各个组件的证书有效期是一年,这可以通过以下命令查看:
$ kubeadm certs check-expiration
[check-expiration] Reading configuration from the cluster...
[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Nov 15, 2024 08:33 UTC 317d ca no
apiserver Nov 15, 2024 08:33 UTC 317d ca no
apiserver-etcd-client Nov 15, 2024 08:33 UTC 317d etcd-ca no
apiserver-kubelet-client Nov 15, 2024 08:33 UTC 317d ca no
controller-manager.conf Nov 15, 2024 08:33 UTC 317d ca no
etcd-healthcheck-client Nov 15, 2024 08:33 UTC 317d etcd-ca no
etcd-peer Nov 15, 2024 08:33 UTC 317d etcd-ca no
etcd-server Nov 15, 2024 08:33 UTC 317d etcd-ca no
front-proxy-client Nov 15, 2024 08:33 UTC 317d front-proxy-ca no
scheduler.conf Nov 15, 2024 08:33 UTC 317d ca no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Nov 13, 2033 08:33 UTC 9y no
etcd-ca Nov 13, 2033 08:33 UTC 9y no
front-proxy-ca Nov 13, 2033 08:33 UTC 9y no
当证书过期后,执行kubectl命令会得到证书过期的提示,导致无法管理集群。通过以下命令进行证书更新:
# 首先备份旧证书
$ cp -r /etc/kubernetes/ /tmp/backup/
$ ls /tmp/backup
admin.conf controller-manager.conf kubelet.conf manifests pki scheduler.conf
# 对单个组件证书续期(一年)
$ kubeadm certs renew apiserver
[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
certificate for serving the Kubernetes API renewed
$ kubeadm certs check-expiration |grep apiserver
apiserver Jan 01, 2025 16:17 UTC 364d ca no
apiserver-etcd-client Nov 15, 2024 08:33 UTC 317d etcd-ca no
apiserver-kubelet-client Nov 15, 2024 08:33 UTC 317d ca no
# 或者直接对全部组件证书续期
$ kubeadm certs renew all
[renew] Reading configuration from the cluster...
[renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed
Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager, kube-scheduler and etcd, so that they can use the new certificates.
# 重启kubelet
systemctl restart kubelet
# 如果不是root用户
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
flannel也是一个可以用于 Kubernetes 的 overlay 网络提供者。可以用来替换calico, 但它的缺陷是不支持Kubernetes的NetworkPolicy,请慎重考虑。
在一个运行稳定的Kubernetes集群中更换网络插件是一个非常风险的操作,最好是安装集群时确认好安装哪一种网络插件。
下面是安装步骤:
wget --no-check-certificate https://hub.gitmirror.com/https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
# 修改Pod网段:搜索文件内:net-conf.json
# net-conf.json: |
# {
# "Network": "10.244.0.0/16", => 20.2.0.0/16
# "Backend": {
# "Type": "vxlan"
# }
# }
kubectl apply -f kube-flannel.yml
可以手动拉取镜像以提高效率:
$ cat kube-flannel.yml|grep image:
image: docker.io/flannel/flannel:v0.23.0
image: docker.io/flannel/flannel-cni-plugin:v1.2.0
image: docker.io/flannel/flannel:v0.23.0
images=$(grep 'image:' kube-flannel.yml | awk '{print $2}')
for image in $images; do
ctr image pull $image
done
安装成功后,能看到flannel部署的两个Pod以及集群内置的coredns Pod都处于Running状态:
$ kk get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-flannel kube-flannel-ds-672rx 1/1 Running 0 43s
kube-flannel kube-flannel-ds-v4hzd 1/1 Running 0 43s
kube-system coredns-c676cc86f-8vpk4 1/1 Running 0 4m36s
kube-system coredns-c676cc86f-fzzjp 1/1 Running 0 4m36s
...
在测试环境中,你可以通过修改本机时间来检查flannel是否像calico那样存在token过期的问题:
# 改为1天后的时间,甚至是10天后,半年后都可
# - 但注意k8s组件证书默认有效期一年,如果改为一年后,kubectl命令会无法正常执行
$ date -s '2023-11-18 19:00:00'
$ kk apply -f ../practice/deployment.yaml
deployment.apps/hellok8s-go-http created
# pod能够running,就说明不存在token过期问题
$ kk get po
NAMESPACE NAME READY STATUS RESTARTS AGE
default hellok8s-go-http-999f66c56-7fdst 1/1 Running 0 1s
default hellok8s-go-http-999f66c56-j5dhx 1/1 Running 0 1s
症状
➜ xx git:(main) ✗ kk get nodes
E1127 10:34:46.376506 80176 memcache.go:265] couldn't get current server API group list: Get "https://192.168.1.10:6443/api?timeout=32s": dial tcp 192.168.1.10:6443: connect: connection refused
...
The connection to the server 192.168.1.10:6443 was refused - did you specify the right host or port?
几乎所有k8s命令都会提示这个错误,这首先是因为K8s的 API Server 宕机了。下面检查 API Server 的日志:
# crictl 是CRI容器运行时接口的命令行工具
➜ xx git:(main) ✗ crictl ps -a
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
efb7225909997 6f707f569b572 2 minutes ago Exited kube-apiserver 1629 fd11a466c5da6 kube-apiserver-k8s-master
cb50e990ca244 86b6af7dd652c 4 minutes ago Exited etcd 1671 76cea9f6fba09 etcd-k8s-master
b7ec55d7c5778 f73f1b39c3fe8 11 days ago Running kube-scheduler 18 0ab488cc9134f kube-scheduler-k8s-master
bd49ad6e09286 95fe52ed44570 11 days ago Running kube-controller-manager 19 d18d0db5e48e9 kube-controller-manager-k8s-master
c7d49da9a929c f73f1b39c3fe8 2 weeks ago Exited kube-scheduler 17 b79e3528a25c6 kube-scheduler-k8s-master
21414c85dd2f4 95fe52ed44570 2 weeks ago Exited kube-controller-manager 18 146661b811d27 kube-controller-manager-k8s-master
# 查看API Server容器的日志
➜ xxx git:(main) ✗ crictl logs 8eec80bf1a3d7
I1127 02:26:17.324123 1 server.go:551] external host was not specified, using 192.168.1.10
I1127 02:26:17.324870 1 server.go:165] Version: v1.27.0
I1127 02:26:17.324891 1 server.go:167] "Golang settings" GOGC="" GOMAXPROCS="" GOTRACEBACK=""
I1127 02:26:17.534744 1 shared_informer.go:311] Waiting for caches to sync for node_authorizer
I1127 02:26:17.543324 1 plugins.go:158] Loaded 12 mutating admission controller(s) successfully in the following order: NamespaceLifecycle,LimitRanger,ServiceAccount,NodeRestriction,TaintNodesByCondition,Priority,DefaultTolerationSeconds,DefaultStorageClass,StorageObjectInUseProtection,RuntimeClass,DefaultIngressClass,MutatingAdmissionWebhook.
I1127 02:26:17.543338 1 plugins.go:161] Loaded 13 validating admission controller(s) successfully in the following order: LimitRanger,ServiceAccount,PodSecurity,Priority,PersistentVolumeClaimResize,RuntimeClass,CertificateApproval,CertificateSigning,ClusterTrustBundleAttest,CertificateSubjectRestriction,ValidatingAdmissionPolicy,ValidatingAdmissionWebhook,ResourceQuota.
W1127 02:26:17.547556 1 logging.go:59] [core] [Channel #1 SubChannel #2] grpc: addrConn.createTransport failed to connect to {
"Addr": "127.0.0.1:2379",
"ServerName": "127.0.0.1",
"Attributes": null,
"BalancerAttributes": null,
"Type": 0,
"Metadata": null
}. Err: connection error: desc = "transport: Error while dialing dial tcp 127.0.0.1:2379: connect: connection refused"
...
API Server 的日志提示了 etcd 服务器(2379端口)连接失败,说明etcd服务异常,从以上容器状态列表中可见,etcd容器也退出了,现在查看它的日志:
➜ xxx git:(main) ✗ crictl logs 671f3a4d55ae5
{"level":"info","ts":"2024-11-27T02:28:01.319Z","caller":"embed/etcd.go:306","msg":"starting an etcd server","etcd-version":"3.5.7","git-sha":"215b53cf3","go-version":"go1.17.13","go-os":"linux","go-arch":"amd64","max-cpu-set":2,"max-cpu-available":2,"member-initialized":true,"name":"k8s-master","data-dir":"/var/lib/etcd","wal-dir":"","wal-dir-dedicated":"","member-dir":"/var/lib/etcd/member","force-new-cluster":false,"heartbeat-interval":"100ms","election-timeout":"1s","initial-election-tick-advance":true,"snapshot-count":10000,"max-wals":5,"max-snapshots":5,"snapshot-catchup-entries":5000,"initial-advertise-peer-urls":["https://192.168.1.10:2380"],"listen-peer-urls":["https://192.168.1.10:2380"],"advertise-client-urls":["https://192.168.1.10:2379"],"listen-client-urls":["https://127.0.0.1:2379","https://192.168.1.10:2379"],"listen-metrics-urls":["http://127.0.0.1:2381"],"cors":["*"],"host-whitelist":["*"],"initial-cluster":"","initial-cluster-state":"new","initial-cluster-token":"","quota-backend-bytes":2147483648,"max-request-bytes":1572864,"max-concurrent-streams":4294967295,"pre-vote":true,"initial-corrupt-check":true,"corrupt-check-time-interval":"0s","compact-check-time-enabled":false,"compact-check-time-interval":"1m0s","auto-compaction-mode":"periodic","auto-compaction-retention":"0s","auto-compaction-interval":"0s","discovery-url":"","discovery-proxy":"","downgrade-check-interval":"5s"}
panic: freepages: failed to get all reachable pages (page 236: multiple references)
...
通过搜索引擎查询上述关键错误信息可知,这可能是因为断电导致的,在测试环境中需要重建集群;若是自行维护的生产环境, 这需要从备份中恢复etcd数据(所以若没有专业的K8s运维人员,请不要在生产环境中自行搭建k8s集群)。