小伙伴们大家好呀!断更了近一个月,XiXi去学习了一下K8s和Jenkins的相关技术。学习内容有些庞杂,近一个月的时间里我只学会了一些皮毛,更多的内容还需要后面不断学习,不断积累。最主要的是云主机真得很贵,为了写这篇文章,我花了近200块😭
回到正题,这里我分享的成果是:在K8s上部署一套Jenkins环境,可以基于Pod实现动态的Jenkins-Slave的扩展,下面记录着我的整个搭建过程。
环境介绍
- K8s1.28.2集群
- 一台可以连接外网的云主机:用于下载一些Docker镜像
准备NFS制备器
在创建Jenkins之前,我们需要先在K8s集群上创建一个NFS制备器。创建NFS制备器后,我们只需要声明PVC,NFS制备器便会自动地基于NFS文件系统创建相应的PV。
NFS文件系统搭建
我们需要先搭建起一个NFS文件系统,搭建流程相当简单
- 每台主机上执行安装
# 安装 nfs
yum install nfs-utils -y
# 启动 nfs
systemctl start nfs-server
# 查看 nfs 版本
cat /proc/fs/nfsd/versions
- 选择某一台主机创建共享目录
这里我选择了内网ip:172.24.12.240的一台云主机
# 创建共享目录
mkdir -p /data/nfs/jenkins
# 设置共享目录 export
vim /etc/exports
/data/nfs/jenkins *(rw,sync,no_subtree_check,no_root_squash)
# 重新加载
exportfs -f
systemctl reload nfs-server
# 查看
showmount -e 172.24.12.240
- 其他机器可以挂载共享目录
mkdir -p /data/nfs/jenkins
# 注意执行mount时的目录,不要是挂载目录
mount -t nfs 172.24.12.240:/data/nfs/jenkins /data/nfs/jenkins
- 最终效果
最终,可以实现,在任意一台主机的 /data/nfs/jenkins中操作,都会同步到其他主机上
NFS制备器
参考官方GitHub:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
官方为我们提供了3种NFS制备器的创建方式:
- Helm
- Kustomize
- 手动方式
下面我采取手动的方式给大家做个演示
步骤一:克隆官方项目
# 不管用什么方式大家将项目克隆下来就好
git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git
主要是需要使用项目中deploy文件夹中的yaml文件
步骤二:修改deploy目录中的yaml的内容
注:如果制备器想创建在default命名空间,配置文件中namespace的相关内容不用改
- class.yaml:用于创建StorageClass
- deployment.yaml:用于创建Deployment
- rbac.yaml:创建ServiceAccount和绑定角色
- class.yaml文件
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: nfs-jenkins-client
provisioner: xixi.io/nfs-subdir-external-provisionerarchiveOnDelete: "false"
name:nfs-jenkins-client(可以保持原样)
provisioner: xixi.io/nfs-subdir-external-provisioner(可以保持原样,在下面的deployment.yaml文件中用到)
- deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: nfs-jenkins-client-provisionerlabels:app: nfs-jenkins-client-provisioner# replace with namespace where provisioner is deployednamespace: kube-system
spec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-jenkins-client-provisionertemplate:metadata:labels:app: nfs-jenkins-client-provisionerspec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-jenkins-client-provisionerimage: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2volumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAME# class.yaml 中的 provisioner保持一致value: xixi.io/nfs-subdir-external-provisioner- name: NFS_SERVERvalue: 172.24.12.240- name: NFS_PATHvalue: /data/nfs/jenkinsvolumes:- name: nfs-client-rootnfs:server: 172.24.12.240path: /data/nfs/jenkins
- nfs的地址和目录路径必改,其他的可以保持原样
- rbac.yaml文件
apiVersion: v1
kind: ServiceAccount
metadata:name: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: nfs-client-provisioner-runner
rules:- apiGroups: [""]resources: ["nodes"]verbs: ["get", "list", "watch"]- apiGroups: [""]resources: ["persistentvolumes"]verbs: ["get", "list", "watch", "create", "delete"]- apiGroups: [""]resources: ["persistentvolumeclaims"]verbs: ["get", "list", "watch", "update"]- apiGroups: ["storage.k8s.io"]resources: ["storageclasses"]verbs: ["get", "list", "watch"]- apiGroups: [""]resources: ["events"]verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: run-nfs-client-provisioner
subjects:- kind: ServiceAccountname: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: kube-system
roleRef:kind: ClusterRolename: nfs-client-provisioner-runnerapiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: leader-locking-nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: kube-system
rules:- apiGroups: [""]resources: ["endpoints"]verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: leader-locking-nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: kube-system
subjects:- kind: ServiceAccountname: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: kube-system
roleRef:kind: Rolename: leader-locking-nfs-client-provisionerapiGroup: rbac.authorization.k8s.io
步骤三:执行创建
kubectl apply -f ./rbac.yaml
kubectl apply -f ./class.yaml
kubectl apply -f ./deployment.yaml## 验证
kubectl get storageclass
kubectl get deploy -n kube-system
kubectl get sa -n kube-system | grep nfs
步骤四:测试
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: test-jenkins-claim
spec:storageClassName: nfs-jenkins-clientaccessModes:- ReadWriteManyresources:requests:storage: 1Mi
kubectl apply -f test-jenkins-claim.yaml
kubectl get pv,pvc
Jenkins-Master部署
GitHub供参考的Yaml:https://github.com/scriptcamp/kubernetes-jenkins
GitHub供参考的搭建流程:https://devopscube.com/setup-jenkins-on-kubernetes-cluster/
官方搭建流程:https://www.jenkins.io/doc/book/installing/kubernetes/
K8s上搭建Jenkins,官方提供的yaml比较老,里面的Jenkins镜像很老,所以得改。
步骤一:克隆官方的yaml
git clone https://github.com/scriptcamp/kubernetes-jenkins
步骤二:创建命名空间
kubectl create namespace devops-tools
步骤三:执行serviceAccount.yaml
kubectl apply -f serviceAccount.yaml
kubectl get sa -n devops-tools
步骤四:创建pvc(官方的volume.yaml就不用了)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: xixi-jenkins-pvcnamespace: devops-tools
spec:storageClassName: nfs-jenkins-clientaccessModes:- ReadWriteOnceresources:requests:storage: 10Gi
kubectl apply -f xixi-jenkins-pvc.yaml
kubectl get pv,pvc -n devops-tools
步骤五:修改deployment.yaml并执行
apiVersion: apps/v1
kind: Deployment
metadata:name: xixi-jenkinsnamespace: devops-tools
spec:replicas: 1selector:matchLabels:app: xixi-jenkins-servertemplate:metadata:labels:app: xixi-jenkins-serverspec:securityContext:fsGroup: 1000 runAsUser: 1000serviceAccountName: jenkins-admincontainers:- name: xixi-jenkinsimage: jenkins/jenkins:2.452.2-jdk17imagePullPolicy: IfNotPresentresources:limits:memory: "2Gi"cpu: "1000m"requests:memory: "500Mi"cpu: "500m"ports:- name: httpportcontainerPort: 8080- name: jnlpportcontainerPort: 50000livenessProbe:httpGet:path: "/login"port: 8080initialDelaySeconds: 125periodSeconds: 10timeoutSeconds: 5failureThreshold: 5readinessProbe:httpGet:path: "/login"port: 8080initialDelaySeconds: 120periodSeconds: 10timeoutSeconds: 5failureThreshold: 3volumeMounts:- name: xixi-jenkins-datamountPath: /var/jenkins_home volumes:- name: xixi-jenkins-datapersistentVolumeClaim:claimName: xixi-jenkins-pvc
我这边把探针的时间放的久了一点,等待Jenkins启动,大家可以自己调整
kubectl apply -f deployment.yaml
kubectl get pods -n devops-tools -o wide
步骤六:暴露服务(这里直接用Service的NodePort方式)
apiVersion: v1
kind: Service
metadata:name: xixi-jenkins-servicenamespace: devops-toolsannotations:prometheus.io/scrape: 'true'prometheus.io/path: /prometheus.io/port: '8080'
spec:selector: app: xixi-jenkins-servertype: NodePort ports:- name: httpport: 8080targetPort: 8080nodePort: 32001- name: channelport: 50000targetPort: 50000
kubectl apply -f service.yaml
kubectl get svc -n devops-tools
步骤六:获取秘钥,登录Jenkins
kubectl logs xixi-jenkins-c879b6b69-6s5pq -n devops-tools
访问:ip:32001(32001就是步骤六中service.yaml文件中指定暴露的端口)
输入pod日志打印的秘钥,安装插件,创建用户的过程就不贴图了,最后要设置一个Jenkins_URL需要注意下,需要设置成下图方式(如果设置错了,后面也可以改,不用担心)
Jenkins-Master测试
官方Jenkins镜像提供了OpenJDK17的环境、Git环境,但是没有安装Maven,大家如果有需要可以自己重新构建Jenkins镜像、或者Jenkins的UI界面的工具配置中可以选择自动安装Maven,再或者可以在Pod中配置另一个容器(容器配置好构建环境)用于构建代码。
这里我先简单展示下默认的情况
kubectl get pods -n devops-tools
kubectl exec -it xixi-jenkins-c879b6b69-6s5pq -n devops-tools /bin/bash
测试一个简单的FreeStyle任务
- 配置Gitee仓库
- shell命令打印“hello world”
- 执行构建,查看结果
Jenkins-Slave部署
部署步骤
- Jenkins中安装K8s插件
- Jenkins中配置云服务
- 新建云
- 配置K8s地址
这里什么都不填也可以
- 配置Jenkins地址
http://svc名称.命名空间:8080/
- 添加一个Pod标签,并点击保存
- 再次进入配置的云
- 进入PodTemplate,新增Pod模版
- 配置PodTemplate并保存
PodTemplate后面还会更改
- 构建Jenkins任务测试
限制项目运行节点,填写在PodTemplate中标签列表的内容(见9)
- 查看任务执行结果
- 查看Jenkins-Slave的Pod
kubectl get pods -n devops-tools
kubectl exec -it jnlp-q90n6 /bin/sh
自定义构建环境的镜像
Jenkins-Slave部署完成后,发现Pod中默认启动的容器jnlp,只能满足我们拉取代码的任务,我们要通过Maven构建项目,并且我们项目是JDK8的,需要一个JDK8的环境,因此,我们可以构建一个自己的用于代码打包的镜像,然后添加到Jenkins-Slave的Pod中
FROM centos:7
ADD ./apache-maven-3.9.0-bin.tar.gz /usr/local/
RUN yum -y update \&& yum -y install vim \&& yum -y install git \&& yum -y install java-1.8.0-openjdk-devel.x86_64
WORKDIR /usr/local/
ENV MAVEN_HOME=/usr/local/apache-maven-3.9.0
ENV PATH=$MAVEN_HOME/bin:$PATH
CMD ["/bin/bash","-c","while true; do echo hello world; sleep 1; done"]
解释下Dockerfile
- maven的tar包中,我已经将settings.xml的中央仓库镜像换成了阿里云
- 通过yum的方式安装了OpenJDK8和git
docker build -t my-env-build:2.0 .
修改PodTemplate,添加自定义构建环境容器
接下来我们需要将自己的镜像,也启动个容器放在Jenkins-Slave的Pod中
- 配置PodTemplate,添加容器
选择添加容器
输入自己构建环境的镜像名称:版本号。配置完成后,保存退出
- 构建一个pipeline项目
// Uses Declarative syntax to run commands inside a container.
pipeline {agent {label 'xixi-jnlp'}stages {stage('拉取代码') {steps {container('xixi-build') {// some blockgit credentialsId: '7e0d3d6a-2fd8-4f9e-b170-5dc351e3dc92', url: 'https://gitee.com/gao_xi/k8s-devops'sh "mvn clean install"}}}}
}
Pipeline脚本中,通过**container(‘容器名称’),**可以切换到指定容器中执行
- 执行构建
可以看到已经可以执行mvn了
- 查看Pod情况
kubectl get pod jnlp-l20bs -o=jsonpath='{.spec.containers[*].name}' -n devops-tools
可以看到此时Pod已经有两个容器了
- jnlp:是默认启动的容器,里面运行Jenkins-Slave的agent程序
- xixi-build:是我们自定义的构建环境容器
甚至我们可以继续向Pod中添加容器,比如可以将Docker放入
- jnlp容器和xixi-build容器有共享目录
可以看到两个容器中的**/home/jenkins/agent**是共享的。具体原因是,在Pod模版中配置了Empty Dir
修改PodTemplate,添加Docker容器
添加了自己的容器后,我们再向Pod中添加一个Docker容器,用于执行一些Docker命令
- 添加Docker容器
这里需要将宿主机的**/var/run/docker.sock**挂载进去
- pipeline脚本
// Uses Declarative syntax to run commands inside a container.
pipeline {agent {label 'xixi-jnlp'}stages {stage('拉取代码') {steps {container('xixi-build') {sh "echo hello world"}container('xixi-docker') {sh "docker ps"}}}}
}
- 结果
- 查看Pod情况
目录也是通过EmptyDir方式,实现Pod内3个容器jnlp、xixi-build、xixi-docker中目录共享
Jenkins-Slave创建的Pod原理图
部署一个微服务项目
基于前面的Pod模板,里面有自己构建的一套JDK8+Maven+Git环境、Docker环境(分别在xixi-build容器中和xixi-docker容器中),在构建项目前,我们在K8s中部署一套Nacos,并且Jenkins中再配置一些内容。
Nacos部署
这里由于仅仅是为了演示,我使用的是官方的quick-start
git clone https://github.com/nacos-group/nacos-k8s.git
cd nacos-k8s
sh quick-startup.sh
项目中的Nacos地址配置:
nacos-headless.default:8848
修改PodTemplate,挂载kubectl
继续修改Jenkins中的Pod模板,增添kubectl挂载。
Maven仓库可以也挂载出来,最好是声明个PVC,这里我就直接挂载到HostPath上
Jenkins-配置K8s凭证
将K8s集群Master节点的
/root/.kube/config
上传这里
Jenkins-安装K8s CLI插件
安装此插件后,可以通过Pipeline,执行kubectl命令
pipeline {agent {label 'xixi-jnlp'}stages {stage('测试kubectl'){steps{withKubeConfig(caCertificate: '', clusterName: '', contextName: '', credentialsId: 'k8s-config', namespace: 'default', restrictKubeConfigAccess: false, serverUrl: '') {sh "kubectl get pods"}}}}
}
项目结构和Pipeline脚本
引入了dockerfile-maven-plugin插件,自动为我们打包镜像
<plugin><groupId>com.spotify</groupId><artifactId>dockerfile-maven-plugin</artifactId><version>1.3.6</version><configuration><repository>${project.artifactId}</repository><buildArgs><JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE></buildArgs></configuration>
</plugin>
pipeline
pipeline {agent {label 'xixi-jnlp'}environment {harbor_url = "registry.cn-hangzhou.aliyuncs.com"harbor_namespace = "aliyun_gx"gateway_project_name = "xixi-mall-gateway"}parameters {extendedChoice multiSelectDelimiter: ',', name: 'selectedProjects', quoteValue: false, saveJSONParameterToFile: false, type: 'PT_CHECKBOX', value: 'mall-order-service,mall-product-service,xixi-mall-gateway', visibleItemCount: 3}stages {stage('环境准备'){steps{script{selectedProjects = selectedProjects.split(',')}}}stage('拉取代码'){steps{checkout scmGit(branches: [[name: '*/k8s-devops']], extensions: [], userRemoteConfigs: [[credentialsId: '7e0d3d6a-2fd8-4f9e-b170-5dc351e3dc92', url: 'https://gitee.com/gao_xi/xixi-mall-devops.git']])}}stage('构建整体'){steps{container('xixi-build'){sh "mvn clean install"}}}stage('构建项目镜像'){steps{script{for(int i=0;i<selectedProjects.size();i++){def currentProject = selectedProjects[i];echo "项目名称: ${currentProject}"if( gateway_project_name == currentProject){echo "发布网关"container("xixi-build"){sh "mvn -f ${currentProject} dockerfile:build"}} else {container("xixi-build"){sh "mvn -f xixi-mall-service/${currentProject} dockerfile:build"}}//上传镜像container("xixi-docker"){sh "docker tag ${currentProject}:latest ${harbor_url}/${harbor_namespace}/${currentProject}:latest"withCredentials([usernamePassword(credentialsId: 'aliyun-image-repo', passwordVariable: 'password', usernameVariable: 'username')]) {sh "docker login -u ${username} -p ${password} ${harbor_url}"sh "docker push ${harbor_url}/${harbor_namespace}/${currentProject}:latest"sh "docker rm -f ${currentProject}:latest"sh "docker rm -f ${harbor_url}/${harbor_namespace}/${currentProject}:latest"}}}}}}stage('项目发布'){steps{script{for(int i=0;i<selectedProjects.size();i++){def currentProject = selectedProjects[i];echo "发布项目: ${currentProject}"withKubeConfig(caCertificate: '', clusterName: '', contextName: '', credentialsId: 'k8s-config', namespace: 'default', restrictKubeConfigAccess: false, serverUrl: '') {sh "kubectl apply -f deploy/${currentProject}-deploy.yaml"}}}}}}
}
成果展示
通过网关访问服务
curl 10.102.58.6:18000/order/getOrder
可以为Gateway配置一个Ingress,这里就不演示了。
总结
好了,兄弟们,这就是XiXi最近探索的内容,中间过程相当曲折,镜像下载问题就把整个人整崩溃了,还好在水群的时候以前同学给了方案,阿里云租台美国主机,通过docker save 和 docker load -i
的方式下载镜像。
如果大家想要我文档中一些配置的yaml和项目源码,可以私信,我尽量给到大家。
参考资料
- 【完整版Kubernetes(K8S)全套入门+微服务实战项目,带你一站式深入掌握K8S核心能力】 https://www.bilibili.com/video/BV1MT411x7GH/?share_source=copy_web&vd_source=48905e7be046ec712a8d80b099294b80
- 《Jenkins持续集成从入门到精通》(需要公众号推的,阿里的)