在云上自建Kubernetes集群

安装背景

出于对于kubernetes集群的配置灵活性的需求,很多朋友有自建Kubernetes集群的诉求,在此记录一下我在阿里云上购买ECS并从头开始来完成kubernetes集群的过程;为节省成本集群中的节点采用了规格为2c4G的突发性能型ECS,操作系统是Anolis OS 8.8 RHCK 64位;使用kubeadm这个工具来进行安装,主要流程可抽象成两个大步骤:

  1. 初始化节点:通过rpm包管理工具安装kubectl、kubelet、kubeadm 这几个组件;安装containerd,并完成相关配置;
  2. 初始化集群:注意kubeadm的配置文件,生成证书等配置,建立高可用控制面,并将worker节点加入集群中;

初始化节点

安装kubectl、kubeadm、kubelet 组件

考虑到网络联通性问题,需要加入rpm包的国内本地化配置源,首先是kubernetes的源:添加rpm源的配置如下所示,清注意关闭gpgcheck

cat << EOF |tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
# 建议设定版本,不声明版本则安装具体的最新版;
yum install kubectl kubeadm kubelet   

安装 containerd

contaienrd 配置

参考 https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/#containerd 来进行containerd的安装

containerd 国内本地化源配置

 wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

先验证contianerd包在源中存在,然后执行如下命令安装相关 containerd 运行时:

  yum install containerd.io
  systemctl enable containerd
  systemctl start containerd

修改 containerd 中的 sandbox image配置

修改 containerd 配置文件 config.toml文件中的 sandbox image配置:registry.aliyuncs.com/google_containers/pause:3.9 ,此修改是因为默认的镜像地址为gcr,在国内无法正常访问。

# 生成对应containerd的配置文件
containerd config default > /etc/containerd/config.toml  

# 保存此配置文件后通过 命令重启生效配置
systemctl restart containerd 

注意:

  • 查看containerd当前生效配置:containerd config dump > 2.toml;
  • 如果containerd的工作状态不符合预期,可以通过 journalctl -xeu containerd 命令来查看containerd日志 ;

添加 crictl配置

相关配置文件为 /etc/crictl.yaml

cat << EOF |tee /etc/crictl.yaml
# these first two endpoint setting is where you configure crictl to containerd
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 3
debug: true
EOF

此时执行 crictl images 可以正常输出内容;

安装 并配置 cni 插件

在 cni 界面 https://github.com/containernetworking/plugins/releases ,找到 v1.3.0 或者v1.0.0 版本,通过wget命令下载此包。

# 通过命令创建cni的对应目录
mkdir -p /opt/cni/bin
# 通过如下相关命令将包解压至相关目录
tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.3.0.tgz

本地cni配置,注意版本号以及相关ip子网地址段:

cat << EOF | tee /etc/cni/net.d/10-containerd-net.conflist
{
 "cniVersion": "1.0.0",
 "name": "containerd-net",
 "plugins": [
   {
     "type": "bridge",
     "bridge": "cni0",
     "isGateway": true,
     "ipMasq": true,
     "promiscMode": true,
     "ipam": {
       "type": "host-local",
       "ranges": [
         [{
           "subnet": "10.14.0.0/24"
         }]
       ],
       "routes": [
         { "dst": "0.0.0.0/0" },
         { "dst": "::/0" }
       ]
     }
   },
   {
     "type": "portmap",
     "capabilities": {"portMappings": true},
     "externalSetMarkChain": "KUBE-MARK-MASQ"
   }
 ]
}
EOF

注意此时只解决了本节点上容器通信的问题,并未解决在节点间的容器通信问题,跨节点通信需要通过类似 flannel、calico 这样的方案来完成。

测试:通过crictl 成功run起来一个本地容器,参考crictl 相关文档;

内核参数配置修改

内核参数设置(如果不做如下操作,将会无法通过 kubeadm init的 precheck )

# 修改域名配置 
hostnamectl set-hostname k8s-1

# 安装tc 
yum install iproute-tc

# sysctl: cannot stat /proc/sys/net/bridge/bridge-nf-call-iptables: No such file or directory 如果遇到相关错误,请执行 modprobe 命令

 modprobe bridge
 modprobe br_netfilter

 echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf
 echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf
 echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf
 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
 echo "vm.swappiness = 0" >> /etc/sysctl.conf

sysctl 使相关配置生效
sysctl -p /etc/sysctl.conf

集群初始化–拉起控制面

控制面(control plane)采用三台服务器,etcd与api server同机部署的架构;通过ssh登录一台预先规划好的master服务器,通过kubeadm 来完成节点初始化工作,注意给出config配置文件需要使用国内阿里云的镜像仓库;–apiserver-advertise-address 为此master服务器的内网地址;另外控制面的证书如果需要上传到集群请在 kubeadm init 执行时加上 –upload-certs 这个参数。 一般来说 kubeadm的init命令如下所示:

kubeadm init \
  --apiserver-advertise-address=192.168.0.121 \
  --image-repository registry.aliyuncs.com/google\_containers \
  --kubernetes-version v1.28.2 \
  --service-cidr=10.1.0.0/16 \
  --pod-network-cidr=10.244.0.0/16
  

但是为了更多配置项,我们选择配置文件的方式来进行init,具体命令:

kubeadm init --config kubeadm-config.yaml

kubeadm 配置文件内容如下,certSANs配置中将三台master的hostname及ip地址都写入,此外还加入了api server对应LB的域名及ip地址;注意此处采用的是将etcd实例与api server 实例部署在同一node上的架构,如果是生产环境部署建议将etcd集群拆分部署在其他服务器上,如果是etcd拆分部署的情况,kubeadm配置文件的内容还会有所不同,具体请参照附录中的文件链接:

apiServer:
  certSANs:
  - api.k8s.local
  - api.lixin.com
  - master3105
  - master12
  - master079
  - 10.0.3.105
  - 10.0.1.2
  - 10.0.0.79
  - 10.0.4.212
  extraArgs:
    authorization-mode: Node,RBAC
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kube-lixin
controllerManager: {}
dns: {}
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: v1.28.0
controlPlaneEndpoint: "10.0.4.212:6443"
networking:
  dnsDomain: cluster.local
  podSubnet: 10.244.0.0/16
  serviceSubnet: 10.96.0.0/12
scheduler: {}

请注意观察在kubeadm init 执行过程中的日志输出,错误日志会在此处给出详细信息,如果日志不够细致的话还可以加入 –v=10 参数并执行;

在其他两台控制面机器上完成节点初始化(本文第一部分)后,如果在kubeadm init时没有选择加上 –upload-certs 参数,则需要将相关kubernetes证书在控制面机器之间进行拷贝:

scp /etc/kubernetes/pki/etcd/ca.key root@10.0.1.2:/etc/kubernetes/pki/etcd                                                                            
scp /etc/kubernetes/pki/etcd/ca.crt root@10.0.0.79:/etc/kubernetes/pki/etcd    
scp /etc/kubernetes/pki/front-proxy-ca.key root@10.0.0.79:/etc/kubernetes/pki 
scp /etc/kubernetes/pki/front-proxy-ca.crt root@10.0.0.79:/etc/kubernetes/pki
scp /etc/kubernetes/pki/sa.pub root@10.0.0.79:/etc/kubernetes/pki
scp /etc/kubernetes/pki/sa.key root@10.0.0.79:/etc/kubernetes/pki   
scp /etc/kubernetes/pki/ca.key root@10.0.0.79:/etc/kubernetes/pki 
scp /etc/kubernetes/pki/ca.crt root@10.0.0.79:/etc/kubernetes/pki

通过如下命令将此节点作为一个master加入集群,注意参数 –control-plane 这里说明是作为master进入的:

 kubeadm join 10.0.3.105:6443 --token svy3br.1hcjfw953q23ypq3 --discovery-token-ca-cert-hash sha256:032c441fac7307da1867fab25ab77f480318c3ca68fb67977cd8011c3a4aab88 --control-plane

高可用集群控制面的两个问题:

  1. 如果证书没有选择上传集群,则需要提前手动完成证书在master实例节点之间的copy;
  2. 需要申请一个SLB将流量转发到多个api server 实例上,在 controlPlaneEndpoint 上体现了这个SLB的对应配置。

加入worker节点

此刻可以说是完成了控制面的搭建;通过如下命令将worker节点加入集群,注意worker节点并不需要提前scp证书,并且不需要 control-plane 参数;如果执行时日志中有 token 过期相关错误,需要登录到最初的master节点上,通过kubeadm token create创建一个新的token,并在–token 处使用新的token。

  kubeadm join 10.0.3.105:6443 --token svy3br.1hcjfw953q23ypq3 --discovery-token-ca-cert-hash sha256:032c441fac7307da1867fab25ab77f480318c3ca68fb67977cd8011c3a4aab88 --v=10

master节点的添加及一些注意事项

如果需要新增控制面节点机器,需要在kubeadm配置文件中加入相关ip及dns配置,并通过如下kubeadm init phase命令重新生成相关的api server证书;

  # 新加入控制面机器,如果证书中没有包括相关机器的hostname及ip,需要重新生成对应的api server证书 
  kubeadm init phase certs apiserver --config kubeadm.yaml 

  # 重新生成cluster-info:
  kubeadm init phase bootstrap-token

此时已完成自建kubernetes集群的初步搭建,对于集群中跨node的通信需求,推荐安装Calico来解决;

参考资料

安装过程中参考了如下文档中的描述:

kubeadm join命令中的 discovery-token-ca-cert-hash 对应的值可以通过如下命令获取:

openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
   openssl dgst -sha256 -hex | sed 's/^.* //'

证书相关一些命令备忘:


# 查看证书内容:
openssl x509 -in apiserver.crt -text -noout

# 将新master的IP地址更新入证书的另一种做法:

openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \
    -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=kube-apiserver" \
    -keyout apiserver.key \
    -out apiserver.crt \
    -extensions SAN -config <(echo "[req]"; echo distinguished_name=req; echo "[SAN]"; echo subjectAltName=IP:Load_Balancer_IP1,IP:Load_Balancer_IP2)

openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \
    -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=kube-apiserver" \
    -keyout apiserver.key \
    -out apiserver.crt \
    -extensions SAN -config <(echo "[req]"; echo distinguished_name=req; echo "[SAN]"; echo subjectAltName=DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:master3105,IP:10.96.0.1,IP:10.0.3.105,IP:10.0.4.212)

最后修改于 2023-11-14