小伙伴们大家好呀!断更了近一个月,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-provisioner
archiveOnDelete: "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-provisioner
labels:
app: nfs-jenkins-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-jenkins-client-provisioner
template:
metadata:
labels:
app: nfs-jenkins-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-jenkins-client-provisioner
image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
# class.yaml 中的 provisioner保持一致
value: xixi.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 172.24.12.240
- name: NFS_PATH
value: /data/nfs/jenkins
volumes:
- name: nfs-client-root
nfs:
server: 172.24.12.240
path: /data/nfs/jenkins
- nfs的地址和目录路径必改,其他的可以保持原样
- rbac.yaml文件
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: 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: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: 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 deployed
namespace: 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 deployed
namespace: kube-system
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: 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-client
accessModes:
- ReadWriteMany
resources:
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-pvc
namespace: devops-tools
spec:
storageClassName: nfs-jenkins-client
accessModes:
- ReadWriteOnce
resources:
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-jenkins
namespace: devops-tools
spec:
replicas: 1
selector:
matchLabels:
app: xixi-jenkins-server
template:
metadata:
labels:
app: xixi-jenkins-server
spec:
securityContext:
fsGroup: 1000
runAsUser: 1000
serviceAccountName: jenkins-admin
containers:
- name: xixi-jenkins
image: jenkins/jenkins:2.452.2-jdk17
imagePullPolicy: IfNotPresent
resources:
limits:
memory: "2Gi"
cpu: "1000m"
requests:
memory: "500Mi"
cpu: "500m"
ports:
- name: httpport
containerPort: 8080
- name: jnlpport
containerPort: 50000
livenessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 125
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 120
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumeMounts:
- name: xixi-jenkins-data
mountPath: /var/jenkins_home
volumes:
- name: xixi-jenkins-data
persistentVolumeClaim:
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-service
namespace: devops-tools
annotations:
prometheus.io/scrape: 'true'
prometheus.io/path: /
prometheus.io/port: '8080'
spec:
selector:
app: xixi-jenkins-server
type: NodePort
ports:
- name: http
port: 8080
targetPort: 8080
nodePort: 32001
- name: channel
port: 50000
targetPort: 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 block
git 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持续集成从入门到精通》(需要公众号推的,阿里的)