前情提要
整篇文章会非常的长…可以选择性阅读,另外,这篇文章是自己学习使用的,用于生产,还请三思和斟酌
- 静态 pod 的部署方式和二进制部署的方式是差不多的,区别在于 master 组件的管理方式是
kubectl
还是systemctl
- 有 kubeadm 工具,为什么还要用静态 pod 的方式部署?
- kubeadm init 节点一旦灾难性异常了,整个 k8s 集群都难逃一死(
我曾经干活干迷瞪了,要 reset 一个 worker 节点,不小心在 kubeadm init 节点执行 kubeadm reset 命令,整个集群都废了,好在是测试环境,pod 服务都有持久化和相关的 yaml 文件兜底,直接重建 k8s 集群了
)
- kubeadm init 节点一旦灾难性异常了,整个 k8s 集群都难逃一死(
- 有二进制部署的方式,为什么还要用静态 pod 的方式部署?
- 统一采集 pod 日志也能同时采集 master 组件的日志,不需要额外的处理
- pod 的 request 和 limit 颗粒度更小,能更好的调整 master 组件的资源使用限制
相关概念
The differences between Docker, containerd, CRI-O and runc
- 此图概述了 Docker、Kubernetes、CRI、OCI、containerd 和 runc 如何在这个生态系统中协同工作
OCI
**Open Container Initiative (OCI):**一组容器标准,用于描述镜像格式、运行时和分发的规范
- OCI 是为容器世界创建一些标准的首批工作之一
- 它由 Docker 和其他公司于 2015 年成立
- OCI 得到了许多科技公司的支持,并维护着容器映像格式以及容器应如何运行的规范
CRI
**Container Runtime Interface (CRI) in Kubernetes:**是 Kubernetes 用来控制创建和管理容器的不同运行时的接口
- CRI 使 Kubernetes 更容易使用不同的容器运行时
- Kubernetes 项目不需要单独为每个运行时添加支持,而是描述了 Kubernetes 将如何与任何运行时交互
- 只要给定的容器运行时实现 CRI API,运行时就可以按照自己的喜好创建和启动容器
containerd 和 CRI-O 都实现了 CRI 规范,都可以用来当作容器运行时
Docker
- 容器化就是从 Docker 开始的,对很多人来说 “Docker” 这个名字本身就是 “容器” 的代名词
- 它是管理容器的最流行的工具
- 现在被命名为 Docker Engine
- docker 命令实际上调用了一些较低级别的工具来完成繁重的工作
docker-cli
:这是一个命令行工具,它是用来完成 docker 命令的pull
,build
,run
,exec
等进行交互。containerd
:这是高级别的容器运行时,一个管理和运行容器的守护进程。它推送和拉取镜像,管理存储和网络,并监督容器的运行。runc
:这是低级别的容器运行时(实际创建和运行容器的东西)。它包括 libcontainer,一个用于创建容器的基于 Go 的本地实现。
dockershim
由于 Docker 比 Kubernetes 更早,没有实现 CRI,这就是 dockershim 存在的原因,它支持将 Docker 被硬编码到 Kubernetes 中
- 随着容器化成为行业标准,Kubernetes 项目增加了对额外运行时的支持,比如通过 Container Runtime Interface (CRI) 容器运行时接口来支持运行容器
- dockershim 将会从 Kubernetes 1.24 中完全移除
- 所有支持 OCI 规范的镜像,containerd 和 CRI-O 都可以运行
containerd
containerd 是来自 Docker 的高级容器运行时
- containerd 诞生于原始 Docker 项目的一部分,之后被捐赠给云原生计算基金会(CNCF)
- 在内部,Docker Engine 使用 containerd,所以安装 docker 的时候会同时安装 containerd
CRI-O
CRI-O 是一个轻量级的容器运行时
- 它诞生于 Red Hat、IBM、Intel、SUSE 和其他公司
- 专注于 Kubernetes 的需求,提供一个精简的容器运行时
- 由 Kubernetes 使用的
kubelet
来管理和调用,直接通过 Kubernetes 的 CRI 接口与容器交互
runc
一个轻量级的容器运行时,负责创建和运行容器实例,是实现 OCI 接口的最低级别的容器运行时
- 为容器提供所有低级功能,并与现有的低级 Linux 功能(如命名空间和控制组)交互,使用这些功能来创建和运行容器进程
关系总结
runc
: 容器运行时的低层实现,负责直接启动和运行容器。containerd
: 容器运行时管理工具,负责容器的生命周期管理,通常通过runc
来启动容器CRI-O
: 为 Kubernetes 提供的容器运行时实现Docker
: 一个容器平台,使用containerd
作为容器运行时,包含了容器构建、管理和运行的完整工具链
开始部署
环境介绍
使用 podman 是因为比 docker 更轻量,没有 deamon 守护进程,只是一个命令文件,自身不占用系统资源,配置私有仓库什么的,也不存在重启服务,和 docker 一样的语法
组件 | 版本 |
---|---|
OS | openEuler 22.03 (LTS-SP4) |
podman | 3.4.4 |
k8s | 1.32.0 |
cri-o | 1.23.2 |
etcd | 3.5.16-0 |
pause | 3.10 |
calico | v3.26.0 |
coredns | v1.11.3 |
haproxy | 2.9.8 |
keepalived | 2.3.1 |
机器 ip 和对应的服务
IP | HOSTNAME | SERVICE/ROLE |
---|---|---|
192.168.182.129 | k8s-master-01 | k8s-master+keepalived+haproxy |
192.168.182.130 | k8s-master-02 | k8s-master+keepalived+haproxy |
192.168.182.131 | k8s-master-03 | k8s-master+keepalived+haproxy |
192.168.182.132 | k8s-worker-04 | k8s-worker |
192.168.182.133 | k8s-worker-05 | k8s-worker |
192.168.182.200 | / | VIP |
系统初始化相关
- 如果是虚拟机还没就绪,可以先启动一台机器,执行完初始化后,直接克隆机器更方便快捷
- 如果机器已经就绪了,下面的初始化操作,每个机器都需要执行
- 下面的操作省略了
静态 ip
和时间同步
的操作,大家自己操作一下
关闭防火墙
systemctl disable firewalld --now
关闭 selinux
setenforce 0
sed -i '/SELINUX/s/enforcing/disabled/g' /etc/selinux/config
关闭 swap
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
开启内核模块
# 针对于 kube-proxy 使用 ipvs 模式的
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
# 常规要开启的
modprobe nf_conntrack
modprobe br_netfilter
modprobe overlay
开启模块自动加载服务
cat > /etc/modules-load.d/k8s-modules.conf <<EOF
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
br_netfilter
overlay
EOF
设置为开机自启
systemctl enable systemd-modules-load && systemctl restart systemd-modules-load
sysctl 内核参数调整
cat <<EOF > /etc/sysctl.d/kubernetes.conf
# 开启数据包转发功能(实现vxlan)
net.ipv4.ip_forward=1
# iptables对bridge的数据进行处理
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.bridge.bridge-nf-call-arptables=1
# 不允许将TIME-WAIT sockets重新用于新的TCP连接
net.ipv4.tcp_tw_reuse=0
# socket监听(listen)的backlog上限
net.core.somaxconn=32768
# 最大跟踪连接数,默认 nf_conntrack_buckets * 4
net.netfilter.nf_conntrack_max=1000000
# 禁止使用 swap 空间,只有当系统 OOM 时才允许使用它
vm.swappiness=0
# 计算当前的内存映射文件数。
vm.max_map_count=655360
# 内核可分配的最大文件数
fs.file-max=6553600
# 持久连接
net.ipv4.tcp_keepalive_time=600
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=10
EOF
立即生效
sysctl -p /etc/sysctl.d/kubernetes.conf
特殊操作:
/etc/sysctl.conf
文件里面的net.ipv4.ip_forward=0
会覆盖掉上面子配置文件里面的值
- 猜测是因为
/etc/openEuler_security/security.conf
文件里面配置了301@m@/etc/sysctl.conf@net.ipv4.ip_forward=@0
,但是不确定修改后会影响哪些,还是打算直接修改/etc/sysctl.conf
,这样就不会导致机器重启,kubelet 起不来的情况
sed -i 's|^net.ipv4.ip_forward.*|net.ipv4.ip_forward=1|g' /etc/sysctl.conf
sysctl -p
清空 iptables 规则
iptables -F && \
iptables -X && \
iptables -F -t nat && \
iptables -X -t nat && \
iptables -P FORWARD ACCEPT
安装各种依赖和工具
yum install -y vim wget curl tar net-tools jq bash-completion tree bind-utils telnet unzip nc
修改 .bashrc 文件
具体参考我之前的博客:关于 openeuler 22.03-LTS-SP4 scp 失败问题的记录,主要影响的是 scp 命令,具体的,看大家自己选择
安装 kubectl 和 kubelet
k8s 官方也没有 openeuler 的源,但是可以直接使用
kubernetes-el7
的源来安装,下面是配置kubernetes-el7
源
- 由于 Kubernetes 官方变更了仓库的存储路径以及使用方式,如果需要使用 1.28 及以上版本,请使用新版配置方法进行配置:https://developer.aliyun.com/mirror/kubernetes
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.32/rpm/repodata/repomd.xml.key
EOF
后面会用 kubeadm 来生成 kubeconfig 证书
- 新版本的 k8s 仓库,安装 kubeadm 不再自动安装 kubelet 和 kubectl 了,这里需要都装一下
- 新版本的 k8s 仓库,安装 kubeadm 也不再自动安装
conntrack-tools
了,kube-proxy 依赖conntrack
yum install -y kubeadm-1.32.0 kubelet-1.32.0 kubectl-1.32.0 conntrack-tools
利用 kubeadm 提前拉取镜像
kubeadm config images pull --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers --kubernetes-version 1.32.0
安装 CRI-O
- openeuler 的 EPOL 里面自带 cri-o,直接 yum 安装即可
- crictl 可以在后期用来查看容器状态,日志等操作
yum install -y cri-o crictl
配置 cri-o
默认配置文件是
/etc/crio/crio.conf
[crio.image]
pause_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.10"
# 指定 cgroup 是 systemd,虽然参数默认是 systemd,这里再加一下也不影响
[crio.runtime]
cgroup_manager = "systemd"
# 默认配置文件里面是 true
[crio.metrics]
enable_metrics = false
启动 cri-o 并配置开机自启
systemctl enable crio.service --now
配置 crictl
检查
/etc/crictl.yaml
里面的内容
runtime-endpoint: "unix:///var/run/crio/crio.sock"
安装 podman
yum install -y podman
修改配置文件
/etc/containers/registries.conf
- 让不带仓库的镜像自动指定
docker.io
这个仓库前缀,podman 不指定镜像仓库的时候,本地显示是localhost/
unqualified-search-registries = ["docker.io"]
重启 crio,让 crio 重新加载
/etc/containers/registries.conf
systemctl restart crio.service
到这一步,就可以克隆机器了
配置变量
创建目录
mkdir -p /appdata/k8s_deploy
配置变量,避免一些目录名称或者 ip 和我不一样,后面还要一个个检查和修改,后面全部使用变量来替代,尽可能通过 cv 就可以完成部署
cat > /appdata/k8s_deploy/k8s.env <<EOF
# 部署文件的存放目录
deploy_home="/appdata/k8s_deploy"
# k8s master 节点
k8s_master_nodes='
192.168.182.129
192.168.182.130
192.168.182.131
'
# k8s worker 节点
k8s_worker_nodes='
192.168.182.132
192.168.182.133
'
# 所有节点
all_nodes="
\${k8s_master_nodes}
\${k8s_worker_nodes}
"
# 临时的证书目录
cert_dir=\${deploy_home}/certs
# 定义 k8s 目录
k8s_home=/etc/kubernetes
# k8s 证书目录
k8s_cert=\${k8s_home}/certs
# k8s kubeconfig 目录
kubeconfig_dir=\${k8s_home}/kubeconfig
# k8s 静态 pod 目录
manifest_dir=\${k8s_home}/manifests
# k8s 静态 pod 配置文件目录
pod_config=\${k8s_home}/static_pod_config
# k8s 高可用域名,自己定义
k8s_vip_url=apiserver.ha.icu
# k8s 高可用 vip 地址
k8s_vip_ip=192.168.182.200
# haproxy 端口
k8s_vip_port=8443
# 宿主机网卡名称
interface_name=ens33
# k8s svc 的 ip 段
svc_ip_range=10.96.0.1
# apiserver 中 svc 的网段配置,上下两个变量必须是同一个网段的
svc_cluster_ip_range=10.96.0.0/12
# coredns 的 ip 地址
cluster_dns=10.96.0.10
# k8s pod 的网段
cluster_cidr=172.22.0.0/16
EOF
将 source 命令加入到
~/.bashrc
目录下面,防止终端断开后,变量为空
cat >> ~/.bashrc <<EOF
source /appdata/k8s_deploy/k8s.env
EOF
source ~/.bashrc
证书制作
创建证书目录
for i in ${k8s_master_nodes};do ssh ${i} "mkdir -p ${k8s_cert}";done
mkdir -p ${deploy_home}/{
templates,certs}
ca 证书
- 注:kubeadm 在创建证书的时候有三个 ca 证书,我这边就一个 ca 通用了,没啥影响
生成一个 2048 位的 ca.key 文件
openssl genrsa -out ${cert_dir}/ca.key 2048
在 ca.key 文件的基础上,生成 ca.crt 文件(用参数
-days
设置证书有效期,单位:天)
openssl req -x509 -new -nodes \
-key ${cert_dir}/ca.key \
-subj "/CN=kubernetes" \
-addext "keyUsage=critical,digitalSignature,keyEncipherment,keyCertSign" \
-addext "subjectAltName=DNS:kubernetes" \
-days 36525 \
-out ${cert_dir}/ca.crt
检查证书到期时间
openssl x509 -text -in ${cert_dir}/ca.crt | grep -A 2 Validity
分发 ca 证书到所有 master 节点
for i in ${k8s_master_nodes};do scp ${cert_dir}/{
ca.crt,ca.key} ${i}:${k8s_cert}/;done
apiserver 相关证书
kube-apiserver.crt
生成一个 2048 位的 kube-apiserver.key 文件
cert_name='kube-apiserver'
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048
证书配置文件模板
cat > ${deploy_home}/templates/server-csr.cnf.tmp <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
CN = kubernetes
[ req_ext ]
subjectAltName = @alt_names
[ v3_ext ]
basicConstraints=critical, CA:FALSE
keyUsage=critical, digitalSignature, keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName = @alt_names
[ alt_names ]
EOF
生成证书配置文件
sed "s|CN =.*|CN = ${cert_name}|g" ${deploy_home}/templates/server-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf
idx_num=1;
for dnsurl in ${k8s_master_nodes} ${k8s_vip_ip} ${k8s_vip_url} kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local;
do
echo "DNS.${idx_num} = ${dnsurl}" >> ${cert_dir}/${cert_name}-csr.cnf;
let idx_num++;
done
idx_num=1;
for hostip in ${k8s_master_nodes} ${k8s_vip_ip} ${svc_ip_range};
do
echo "IP.${idx_num} = ${hostip}" >> ${cert_dir}/${cert_name}-csr.cnf;
let idx_num++;
done
生成 kube-apiserver.csr 证书
openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-config ${cert_dir}/${cert_name}-csr.cnf
使用 csr 证书和 ca 证书生成 apiserver.crt 证书
openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
kube-apiserver-kubelet-client.crt
生成一个 2048 位的 kube-apiserver-kubelet-client.key 文件
cert_name='kube-apiserver-kubelet-client'
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048
证书配置文件模板
cat > ${deploy_home}/templates/client-csr.cnf.tmp <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[ dn ]
[ v3_ext ]
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth
EOF
生成 kube-apiserver-kubelet-client.csr 证书
cat ${deploy_home}/templates/client-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf
openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-subj "/O=system:masters/CN=${cert_name}" \
-config ${cert_dir}/${cert_name}-csr.cnf
使用 csr 证书和 ca 证书生成 kube-apiserver-kubelet-client.crt 证书
openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
front-proxy-client.crt
- 这个证书是 apiserver 的代理身份验证和身份识别相关的,比如 metrics-server 等代理的部署会依赖
生成一个 2048 位的 front-proxy-client.key 文件
cert_name=front-proxy-client
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048
生成 front-proxy-client.csr 证书
cat ${deploy_home}/templates/client-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf
openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-subj "/CN=${cert_name}" \
-config ${cert_dir}/${cert_name}-csr.cnf
使用 csr 证书和 ca 证书生成 front-proxy-client.crt 证书
openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
kube-apiserver-etcd-client.crt
生成一个 2048 位的 kube-apiserver-etcd-client.key 文件
cert_name=kube-apiserver-etcd-client
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048
生成 kube-apiserver-etcd-client.csr 证书
cat ${deploy_home}/templates/client-csr.cnf.tmp > ${cert_dir}/${cert_name}-csr.cnf
openssl req -new -key ${cert_dir}/${cert_name}.key \
-out ${cert_dir}/${cert_name}.csr \
-subj "/O=system:masters/CN=${cert_name}" \
-config ${cert_dir}/${cert_name}-csr.cnf
使用 csr 证书和 ca 证书生成 kube-apiserver-etcd-client.crt 证书
openssl x509 -req -sha256 -in ${cert_dir}/${cert_name}.csr \
-CA ${cert_dir}/ca.crt \
-CAkey ${cert_dir}/ca.key \
-CAcreateserial \
-out ${cert_dir}/${cert_name}.crt \
-days 36525 \
-extensions v3_ext \
-extfile ${cert_dir}/${cert_name}-csr.cnf
sa.pub
生成 sa.key 私钥
openssl genpkey -algorithm RSA -out ${cert_dir}/sa.key -pkeyopt rsa_keygen_bits:2048
生成 sa.pub 公钥文件
openssl rsa -pubout -in ${cert_dir}/sa.key -out ${cert_dir}/sa.pub
验证证书链
返回 ok 说明证书链没问题
for i in kube-apiserver kube-apiserver-etcd-client kube-apiserver-kubelet-client front-proxy-client;do openssl verify -CAfile ${cert_dir}/ca.crt ${cert_dir}/${i}.crt;done
分发 apiserver 相关证书到 master 节点
for i in ${k8s_master_nodes};do scp ${cert_dir}/{
kube-apiserver*.crt,kube-apiserver*.key,front-proxy-client.crt,front-proxy-client.key,sa.key,sa.pub} ${i}:${k8s_cert}/;done
etcd 相关证书
- 注:kubeadm 在创建证书的时候,etcd 会创建 server,peer 和 ca 三个证书,ca 已经通用了,剩下的两个证书也可以通用,就不重复创建了
生成一个 2048 位的 kube-etcd.key 文件
cert_name='kube-etcd'
openssl genrsa -out ${cert_dir}/${cert_name}.key 2048
证书配置文件模板
cat > ${deploy_home}/templates/serverAndClient-csr.cnf.tmp <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
CN = kubernetes
[ req_ext ]
subjectAltName = @alt_names
[ v3_ext ]
basicConstraints=critical, CA:FALSE
keyUsage=critical, digitalSignature, keyEncipherment
extendedKeyUsage=serverAuth, clientAuth
subjectAltName = @alt_names
[ alt_names ]
EOF
生成证书配置文件
for i in ${k8s_master_nodes};do sed "s|CN =.*|CN = ${i}|g" ${deploy_home}/templates/serverAndClient-csr.cnf.tmp > ${cert_dir}/${cert_name}-${i}-csr.cnf;done
idx_num=1;
for i in ${k8s_master_nodes};
do
for dnsurl in ${k8s_master_nodes} localhost;
do
echo "DNS.${idx_num} = ${dnsurl}" >> ${cert_dir}/${cert_name}-${i}-csr.cnf;
let idx_num&#