Jenkins + Rancher + K8s 实践教程

Ubuntu环境下K8s + Jenkins + Rancher2.6.x流水线部署实践。

Rancher参考文档

https://docs.rancher.cn

京东云SNAT访问公网(跳过)

https://docs.jdcloud.com/cn/virtual-machines/linux-system-configuration-snat
$ sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward = 1/g' /etc/sysctl.conf
$ iptables -A FORWARD -d 192.168.16.0/24 -j ACCEPT
$ sysctl -p
$ iptables -t nat -I POSTROUTING -s 192.168.0.0/16 -j SNAT --to-source 192.168.16.3

为内网主机单独创建一个路由表,下一跳选择SNAT主机
云主机的安全组入站规则放行了ICMP协议后才能被ping
SNAT主机的安全组入站规则,放行全部端口,源ip填写:192.168.0.0/16

检查/etc/hosts

修改hostname并立即生效
$ hostname -F /etc/hostname
将hostname配到/etc/hosts中
$ hostname
192.168.0.3 JD1
192.168.16.3 JD2
192.168.0.234 HW1
192.168.16.3 rancher.renlm.cn

安装 Docker

Jenkins打包镜像需要映射/var/run/docker.sock,注意对应的节点需要安装Docker,否则报错如下:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

https://docs.ranchermanager.rancher.io/zh/getting-started/installation-and-upgrade/installation-requirements/install-docker
$ curl https://releases.rancher.com/install-docker/20.10.sh | sh
	# 阿里云,获取加速地址并配置
	# https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
	$ sudo mkdir -p /etc/docker
	$ sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [ "https://***.mirror.aliyuncs.com" ],
  "log-driver": "json-file",
  "log-opts": { "max-size": "500m", "max-file": "3" },
  "features": { "buildkit" : true }
}
EOF
	$ sudo systemctl daemon-reload
	$ sudo systemctl enable docker
	$ sudo systemctl restart docker

镜像安装K3s

需要注意版本支持
https://www.suse.com/suse-rancher/support-matrix/all-supported-versions/rancher-v2-6-9/
https://docs.rancher.cn/docs/k3s/installation/ha/_index/
https://github.com/k3s-io/k3s/releases/

master1节点
$ curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_VERSION=v1.24.6+k3s1 K3S_TOKEN=SECRET sh -s - server --cluster-init
master2节点
$ curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_VERSION=v1.24.6+k3s1 K3S_TOKEN=SECRET sh -s - server --server https://master1:6443
agent节点
$ curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_VERSION=v1.24.6+k3s1 K3S_TOKEN=SECRET sh -s - agent --server https://master1:6443

离线安装K3s

https://docs.rancher.cn/docs/k3s/installation/airgap/_index
手动上传K3s二进制文件及k3s.sh
vi查看k3s.sh编码格式(set ff)
vi修改k3s.sh编码格式(set ff=unix)
然后wq保存

$ chmod +x ./k3s.sh
$ mv k3s /usr/local/bin/
$ chmod +x /usr/local/bin/k3s

master1节点
$ K3S_TOKEN=SECRET INSTALL_K3S_SKIP_DOWNLOAD=true INSTALL_K3S_EXEC='server --cluster-init' ./k3s.sh
master2节点
$ K3S_TOKEN=SECRET INSTALL_K3S_SKIP_DOWNLOAD=true INSTALL_K3S_EXEC='server --server https://master1:6443' ./k3s.sh
agent节点
$ K3S_TOKEN=SECRET INSTALL_K3S_SKIP_DOWNLOAD=true K3S_URL=https://master1:6443 ./k3s.sh

验证K3s

$ kubectl get nodes

$ kubectl version --output=json

环境变量KUBECONFIG

https://docs.ranchermanager.rancher.io/zh/how-to-guides/new-user-guides/kubernetes-cluster-setup/k3s-for-rancher
$ cp /etc/rancher/k3s/k3s.yaml /etc/rancher/k3s/KUBECONFIG.yaml
$ sed -i 's/127.0.0.1:6443/192.168.16.3:6443/g' /etc/rancher/k3s/KUBECONFIG.yaml
$ sed -i '$a export KUBECONFIG=/etc/rancher/k3s/KUBECONFIG.yaml' ~/.bashrc
$ source ~/.bashrc

安装 Helm

Helm版本支持策略
https://helm.sh/zh/docs/topics/version_skew/
https://github.com/helm/helm/releases/

手动上传文件,下载较慢
tar -zxvf helm-v3.10.3-linux-amd64.tar.gz
mv linux-amd64/helm /usr/local/bin/helm
$ helm version

安装 cert-manager

# 如果你手动安装了CRD,而不是在 Helm 安装命令中添加了 `--set installCRDs=true` 选项,你应该在升级 Helm Chart 之前升级 CRD 资源。
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.7.1/cert-manager.crds.yaml

# 添加 Jetstack Helm 仓库
helm repo add jetstack https://charts.jetstack.io

# 更新本地 Helm Chart 仓库缓存
helm repo update

# 安装 cert-manager Helm Chart
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.7.1

安装完 cert-manager 后,你可以通过检查 cert-manager 命名空间中正在运行的 Pod 来验证它是否已正确部署

kubectl get pods --namespace cert-manager

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-5c6866597-zw7kh               1/1     Running   0          2m
cert-manager-cainjector-577f6d9fd7-tr77l   1/1     Running   0          2m
cert-manager-webhook-787858fcdb-nlzsq      1/1     Running   0          2m

安装 Rancher

添加 Helm Chart 仓库
$ helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
$ helm search repo rancher-stable/rancher

注意放行80和443端口的访问
注意服务器的入网带宽,需要下载一些软件包,网速太慢会导致deadline错误
RKE Kubernetes 集群默认使用 NGINX Ingress,而 K3s Kubernetes 集群默认使用 Traefik Ingress
http01下ingress的class值在不同环境有差异(RKE:nginx,K3s:traefik)
# 安装最新稳定版
$ helm install rancher rancher-stable/rancher \
    --namespace cattle-system --create-namespace \
    --set hostname=rancher.renlm.cn \
    --set bootstrapPassword="Pwd123654" \
    --set ingress.tls.source=letsEncrypt \
    --set letsEncrypt.email=renlm@21cn.com \
    --set letsEncrypt.ingress.class=traefik
# 安装指定版本
$ helm fetch rancher-stable/rancher --version=v2.6.9
$ helm install rancher ./rancher-2.6.9.tgz \
    --namespace cattle-system --create-namespace \
    --set hostname=rancher.renlm.cn \
    --set bootstrapPassword="Pwd123654" \
    --set ingress.tls.source=letsEncrypt \
    --set letsEncrypt.email=renlm@21cn.com \
    --set letsEncrypt.ingress.class=traefik

验证 Rancher Server 是否部署成功

$ kubectl -n cattle-system rollout status deploy/rancher
Waiting for deployment "rancher" rollout to finish: 0 of 3 updated replicas are available...
deployment "rancher" successfully rolled out
$ kubectl -n cattle-system get deploy rancher
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
rancher   3         3         3            3           3m
卸载Rancher
$ helm uninstall rancher -n cattle-system

Nfs的安装与配置

https://renlm.cn/tech/Nfs-install.html

$ kubectl get pv
$ kubectl get pvc
$ kubectl get storageclass
mkdir -p /nfs_share/mysql
mkdir -p /nfs_share/postgres
mkdir -p /nfs_share/redis
mkdir -p /nfs_share/rabbitmq
集群的每个节点必须安装nfs-common,否则挂载时会FailedMount
$ apt-get update
$ apt-get -y install nfs-common

新建持久化存储卷
$ kubectl apply -f https://renlm.gitee.io/download/chart/nfs-pv.yaml

删除Terminating状态的pvc
$ kubectl get pvc -A
$ kubectl patch pvc -n renlm mysql-pv-claim -p '{"metadata":{"finalizers":null}}'

自动证书签发

https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources
注意放行80和443端口的访问
RKE Kubernetes 集群默认使用 NGINX Ingress,而 K3s Kubernetes 集群默认使用 Traefik Ingress
http01下ingress的class值在不同环境有差异(RKE:nginx,K3s:traefik)
$ cat <<EOF > letsencrypt.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: letsencrypt
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-issuer
  namespace: letsencrypt
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: renlm@21cn.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-issuer
    # Enable the HTTP-01 challenge provider
    solvers:
    # An empty 'selector' means that this solver matches all domains
    - selector: {}
      http01:
        ingress:
          class: traefik
EOF
创建资源
$ kubectl apply -f letsencrypt.yaml
$ kubectl apply -f https://renlm.gitee.io/download/chart/crt-renlm.yaml
$ kubectl describe ingress --all-namespaces
$ kubectl describe challenges --all-namespaces

检查新创建证书的状态,您可能需要等待几秒钟,然后cert-manager才能处理证书请求
$ kubectl get clusterissuer
$ kubectl describe certificate -n renlm
$ kubectl get secret -n renlm

获取证书内容
$ kubectl -n renlm get secrets renlm -o yaml
$ echo {data} | base64 -d
 
清理资源
$ wget https://renlm.gitee.io/download/chart/crt-renlm.yaml
$ kubectl delete -f crt-renlm.yaml

安装 Harbor(跳过,阿里云私有镜像仓库提供有限数量的支持)

签发域名证书
$ kubectl apply -f https://renlm.gitee.io/download/chart/crt-harbor.yaml
$ kubectl describe certificate -n harbor

管理员账号admin
注意使用证书的时候,证书和服务必须在相同namespace下才能生效
https://github.com/goharbor/harbor-helm/blob/master/README.md
$ helm repo add harbor https://helm.goharbor.io
$ helm upgrade --install harbor harbor/harbor \
      --namespace harbor --create-namespace \
      --set expose.tls.enabled=true \
      --set expose.tls.certSource=secret \
      --set expose.tls.secret.secretName=tls-harbor \
      --set expose.tls.secret.notarySecretName=tls-harbor \
      --set expose.ingress.hosts.core=harbor.renlm.cn \
      --set expose.ingress.hosts.notary=notary.renlm.cn \
      --set externalURL=https://harbor.renlm.cn \
      --set harborAdminPassword="Pwd123654"
      
查看与卸载
$ kubectl -n harbor get pods
$ kubectl -n harbor get ingress
$ kubectl -n kube-system get svc
$ helm uninstall harbor -n harbor

私有镜像仓库配置(跳过)

https://docs.ranchermanager.rancher.io/zh/pages-for-subheaders/air-gapped-helm-cli-install
https://docs.k3s.io/installation/private-registry

停止并删除全部容器
$ docker stop $(docker ps -q) & docker rm $(docker ps -aq)

删除所有镜像
$ docker rmi -f $(docker images -qa)

连接自签名的私有仓库需要指定自签名证书,SSL证书颁发机构签名的仓库则不需要。
$ vi /etc/rancher/k3s/registries.yaml
mirrors:
  docker.io:
    endpoint:
      - "https://harbor.renlm.cn"
configs:
  "harbor.renlm.cn":
    auth:
      username: admin
      password: Pwd123654

部署服务

创建私有镜像拉取密文
同一命名空间下使用
$ kubectl create namespace renlm
$ kubectl -n renlm create secret docker-registry harbor \
  	--docker-server=harbor.renlm.cn \
  	--docker-username=admin \
  	--docker-password=Pwd123654

查看密文信息
$ kubectl -n renlm get secret harbor --output="jsonpath={.data.\.dockerconfigjson}" | base64 -d

打开Git Bash,创建Github的SSH keys
$ clip < ~/.ssh/id_rsa.pub
$ clip < ~/.ssh/id_rsa

添加fleet仓库,等待部署成功
$ kubectl get pods -A
$ kubectl -n renlm describe pod mysql-0
$ kubectl -n renlm logs -f --tail=100 mysql-0

开启Ingress注解配置

allow-snippet-annotations(默认false)

https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
$ kubectl get cm -A | grep ingress
$ kubectl edit cm -n kube-system rke2-ingress-nginx-controller

禁用ssl-redirect

30x缓存会默认记录在一级主域名下,对二级域名进行单个设置意义不大。

全局禁用
$ kubectl edit cm -n kube-system rke2-ingress-nginx-controller
apiVersion: v1
data:
  allow-snippet-annotations: "true"
  ssl-redirect: "false"

集成Jenkins流水线

Rancher中查看workload
Rancher中创建Rancher API 密钥
集群中新建项目,将服务部署的命名空间移动到项目中
浏览器打开链接,搜索deployments,https://rancher.renlm.cn/v3/projects

Jenkins中代码仓库连接报错,更改配置项
No ECDSA host key is known for github.com and you have requested strict checking.
Host key verification failed
Dashboard>系统管理>全局安全配置>Git Host Key Verification Configuration

Jenkins中安装Docker Pipeline插件
Jenkins中全局工具配置添加Maven插件
Jenkins中安装插件Redeploy Rancher2.x Workload Plugin
https://plugins.jenkins.io/redeploy-rancher2-workload/
https://www.jenkins.io/doc/pipeline/steps/redeploy-rancher2-workload/#rancherredeploy-redeploy-rancher2-x-workload

Jenkins中添加Github访问的全局凭据:Github
添加Gitee访问的全局凭据:Gitee
添加私有镜像库的全局凭据:Aliyuncs
添加Rancher API访问的全局凭据:Rancher,类型选择Rancher2.x API Keys
Dashboard>系统管理>凭据>系统>全局凭据 (unrestricted)

服务器配置低,可修改Jenkins配置以提高shell脚本执行时间,https://jenkins.renlm.cn/script
System.setProperty("org.jenkinsci.plugins.durabletask.BourneShellScript.HEARTBEAT_CHECK_INTERVAL", "86400");

编写Jenkinsfile文件,创建Jenkins流水线任务

Jenkinsfile
tools {
    maven 'maven-3.6.3'
}

注意观察Declarative: Tool Install阶段的日志
Use a tool from a predefined Tool Installation -- maven-3.6.3

Fleet仓库更新失败

ErrApplied(1) [Bundle middleware-renlm: another operation (install/upgrade/rollback) is in progress]

回滚应用版本后在进行更新
helm -n renlm history {部署应用名称}
$ helm -n renlm history middleware-renlm
$ helm -n renlm rollback middleware-renlm {版本号}

解决爬虫服务Chrome驱动僵尸进程的问题

https://github.com/krallin/tini
Dockerfile添加tini
ENV TINI_VERSION v0.19.0
ADD https://renlm.gitee.io/download/tini/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]

跨云集群

由于不在一个内网,集群节点通讯默认走内网ip,因此需要借助WireGuard将不同的VPC网络环境打通。
实践参考:WireGuard异地组网

MTU 设置

https://projectcalico.docs.tigera.io/networking/mtu

最大传输单元MTU(Maximum Transmission Unit,MTU),是指网络能够传输的最大数据包大小,以字节为单位。  
MTU的大小决定了发送端一次能够发送报文的最大字节数。  
如果MTU超过了接收端所能够承受的最大值,或者是超过了发送路径上途经的某台设备所能够承受的最大值,就会造成报文分片甚至丢弃,加重网络传输的负担。  
如果太小,那实际传送的数据量就会过小,影响传输效率。  
注意保持MTU的一致,否则跨节点访问在数据包超过网卡MTU的上限时,会大量丢包导致连接超时。
以MTU最小的那个节点为准,做相应的减值。
IPv4 – 20 Bytes
IPv6 – 40 Bytes
UDP – 8 Bytes
TCP – 20 Bytes
WireGuard – 32 Bytes
ICMP – 8 Bytes
PPPoE – 8 Bytes
查看网卡MTU
$ ip a

例如:服务器MTU=1450,Calico MTU with VXLAN (IPv6),MTU=1450-70=1380
例如:服务器MTU=1500,Calico MTU with VXLAN (IPv6),MTU=1500-70=1430

如果使用WireGuard的udp隧道进行跨云组网,就需要以WireGuard的虚拟网卡(wg0)的MTU为基点进行计算
在WireGuard不手动设置MTU的默认情况下,其MTU会根据端点地址或系统默认路由自动确定
跨云集群节点通讯将会使用wg0虚拟网卡进行,两边的MTU如果不一致,这将引发大量丢包
如下,我们以一个跨京东云和华为云的K8s集群为例,JD2部署Rancher管理平台,JD1、HW1、HW2为K8s集群的三个节点
京东云VPC下
JD1节点作为与华为云VPC通信的网关
	JD1 eth0 MTU = 1450
	JD2 eth0 MTU = 1500
	
	# 未做设置的情况下,集群节点自动生成
	# 默认Calico MTU with WireGuard (IPv4)
	JD1 Calico MTU = 1400
	JD2 Calico MTU = 1400
	
	JD1 wg0 MTU = 1370
	
华为云VPC下
HW1节点作为与京东云VPC通信的网关
	HW1 eth0 MTU = 1500
	HW2 eth0 MTU = 1500
	
	# 未做设置的情况下,集群节点自动生成
	# 默认Calico MTU with WireGuard (IPv4)
	HW1 Calico MTU = 1450
	HW2 Calico MTU = 1450
	
	HW1 wg0 MTU = 1420
如上图所示的集群,在HW2部署一个MySQL数据库,NodePort=30306
通过JD1的公网ip或域名用DBeaver工具连接,简单操作都正常,但是对于数据量大的操作就一直超时
通过HW1的公网ip或域名用DBeaver工具连接,所有操作都正常,无任何超时或连接重置问题
排查问题的时候开始判断是udp隧道大量丢包,尝试使用了多种方式进行规避和优化
TCP伪装和加速,甚至使用WebSocket转发udp流量,始终无法解决

方向对了,但是问题的根源没找对,其根本的原因,有两点
一是WireGuard的udp隧道两端的MTU不一致,跨VPC通信大量丢包
二是K8s集群网络插件(Calico)的MTU与WireGuard不匹配,内部网关通信大量丢包

修改WireGuard的MTU值,以最小的那个节点为准,或者直接修改JD1 eth0 MTU为1500
此处我们不修改网卡MTU,以配置WireGuard进行案例说明
统一修改JD1和HW1网关通信wg0的MTU值为1370,如下
京东云VPC下
JD1节点作为与华为云VPC通信的网关
	JD1 eth0 MTU = 1450
	JD2 eth0 MTU = 1500
	
	# 未做设置的情况下,集群节点自动生成
	# 默认Calico MTU with WireGuard (IPv4)
	JD1 Calico MTU = 1400
	JD2 Calico MTU = 1400
	
	JD1 wg0 MTU = 1370
	
华为云VPC下
HW1节点作为与京东云VPC通信的网关
	HW1 eth0 MTU = 1500
	HW2 eth0 MTU = 1500
	
	# 未做设置的情况下,集群节点自动生成
	# 默认Calico MTU with WireGuard (IPv4)
	HW1 Calico MTU = 1450
	HW2 Calico MTU = 1450
	
	HW1 wg0 MTU = 1370
此时,我们发现,JD1、HW1、HW2三节点的Calico MTU仍然比WireGuard大,内部网关通信仍然会有问题
使用推荐配置Calico MTU with VXLAN (IPv4),MTU=1370-50=1320,更改集群MTU参数
京东云VPC下
JD1节点作为与华为云VPC通信的网关
	JD1 eth0 MTU = 1450
	JD2 eth0 MTU = 1500
	
	# 计算后手动指定的最佳配置
	JD1 Calico MTU = 1320
	JD2 Calico MTU = 1320
	
	JD1 wg0 MTU = 1370
	
华为云VPC下
HW1节点作为与京东云VPC通信的网关
	HW1 eth0 MTU = 1500
	HW2 eth0 MTU = 1500
	
	# 计算后手动指定的最佳配置
	HW1 Calico MTU = 1320
	HW2 Calico MTU = 1320
	
	HW1 wg0 MTU = 1370
调整后,使用JD1、HW1的公网ip或域名,代理访问HW2节点上的MySQL数据库,大数据量的操作正常,没有超时现象了
实际情况需要灵活调整,尽量在满足数据转发的情况使用最大的MTU值以提高网络通信效率
尤其是在使用多种网络协议进行包装时,尤其要注意MTU的控制
例如,在外层使用wstunnel转发WireGuard流量时,MTU就需要在上边基础上再减20

在集群构建时应统一规划网络插件和MTU设置,消除差异化,就能很好的避免这些问题

# MTU 探测(Linux)
# JD1、HW1作为网关的MTU 1370 - IPv4 20 - ICMP 8 = 1342
$ ping -s 1342 -M do {目标IP或域名}

创建集群

在附加配置的 [ Calico 配置 ] 中找到installation.calicoNetwork,添加mtu设置
installation:
  calicoNetwork:
    mtu: 1320

命令修改

kubectl patch installation.operator.tigera.io default --type merge -p '{"spec":{"calicoNetwork":{"mtu":1320}}}'