K8s部署高可用Jenkins

小伙伴们大家好呀!断更了近一个月,XiXi去学习了一下K8sJenkins的相关技术。学习内容有些庞杂,近一个月的时间里我只学会了一些皮毛,更多的内容还需要后面不断学习,不断积累。最主要的是云主机真得很贵,为了写这篇文章,我花了近200块😭
491599167.jpg

回到正题,这里我分享的成果是:在K8s上部署一套Jenkins环境,可以基于Pod实现动态的Jenkins-Slave的扩展,下面记录着我的整个搭建过程。

环境介绍

  • K8s1.28.2集群
  • 一台可以连接外网的云主机:用于下载一些Docker镜像

准备NFS制备器

在创建Jenkins之前,我们需要先在K8s集群上创建一个NFS制备器。创建NFS制备器后,我们只需要声明PVC,NFS制备器便会自动地基于NFS文件系统创建相应的PV。

NFS文件系统搭建

我们需要先搭建起一个NFS文件系统,搭建流程相当简单

  1. 每台主机上执行安装
# 安装 nfs
yum install nfs-utils -y
# 启动 nfs
systemctl start nfs-server
# 查看 nfs 版本
cat /proc/fs/nfsd/versions
  1. 选择某一台主机创建共享目录

这里我选择了内网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
  1. 其他机器可以挂载共享目录
mkdir -p /data/nfs/jenkins
# 注意执行mount时的目录,不要是挂载目录
mount -t nfs 172.24.12.240:/data/nfs/jenkins /data/nfs/jenkins
  1. 最终效果

最终,可以实现,在任意一台主机的 /data/nfs/jenkins中操作,都会同步到其他主机上

image.png

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文件
image.png

步骤二:修改deploy目录中的yaml的内容
注:如果制备器想创建在default命名空间,配置文件中namespace的相关内容不用改

image.png

  • class.yaml:用于创建StorageClass
  • deployment.yaml:用于创建Deployment
  • rbac.yaml:创建ServiceAccount和绑定角色
  1. 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文件中用到)

  1. 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的地址目录路径必改,其他的可以保持原样
  1. 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

image.png
image.png
image.png

步骤四:测试

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

image.png
image.png

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

image.png

步骤二:创建命名空间

kubectl create namespace devops-tools

步骤三:执行serviceAccount.yaml

kubectl apply -f serviceAccount.yaml
kubectl get sa -n devops-tools

image.png

步骤四:创建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

image.png
image.png

步骤五:修改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

image.png

步骤六:暴露服务(这里直接用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

image.png

步骤六:获取秘钥,登录Jenkins

kubectl logs xixi-jenkins-c879b6b69-6s5pq -n devops-tools

image.png
访问:ip:32001(32001就是步骤六中service.yaml文件中指定暴露的端口)
image.png
输入pod日志打印的秘钥,安装插件,创建用户的过程就不贴图了,最后要设置一个Jenkins_URL需要注意下,需要设置成下图方式(如果设置错了,后面也可以改,不用担心)
image.png

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

image.png

测试一个简单的FreeStyle任务

  1. 配置Gitee仓库

image.png

  1. shell命令打印“hello world”

image.png

  1. 执行构建,查看结果

image.png

Jenkins-Slave部署

部署步骤

  1. Jenkins中安装K8s插件

image.png

  1. Jenkins中配置云服务

image.png

  1. 新建云

image.png
image.png

  1. 配置K8s地址

image.png

这里什么都不填也可以

  1. 配置Jenkins地址

image.png

http://svc名称.命名空间:8080/

  1. 添加一个Pod标签,并点击保存

image.png

  1. 再次进入配置的云

image.png

  1. 进入PodTemplate,新增Pod模版

image.png

  1. 配置PodTemplate并保存

image.png

PodTemplate后面还会更改

  1. 构建Jenkins任务测试

image.png

限制项目运行节点,填写在PodTemplate中标签列表的内容(见9

image.png
image.png

  1. 查看任务执行结果

image.png
image.png

  1. 查看Jenkins-Slave的Pod
kubectl get pods -n devops-tools
kubectl exec -it jnlp-q90n6 /bin/sh

image.png
image.png

自定义构建环境的镜像

Jenkins-Slave部署完成后,发现Pod中默认启动的容器jnlp,只能满足我们拉取代码的任务,我们要通过Maven构建项目,并且我们项目是JDK8的,需要一个JDK8的环境,因此,我们可以构建一个自己的用于代码打包的镜像,然后添加到Jenkins-Slave的Pod中
image.png

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中

  1. 配置PodTemplate,添加容器

image.png

选择添加容器

image.png

输入自己构建环境的镜像名称:版本号。配置完成后,保存退出

  1. 构建一个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(‘容器名称’),**可以切换到指定容器中执行

  1. 执行构建

image.png
image.png

可以看到已经可以执行mvn了

  1. 查看Pod情况

image.png

kubectl get pod jnlp-l20bs -o=jsonpath='{.spec.containers[*].name}' -n devops-tools

image.png

可以看到此时Pod已经有两个容器了

  • jnlp:是默认启动的容器,里面运行Jenkins-Slave的agent程序
  • xixi-build:是我们自定义的构建环境容器

甚至我们可以继续向Pod中添加容器,比如可以将Docker放入

  1. jnlp容器xixi-build容器有共享目录

image.png
image.png

可以看到两个容器中的**/home/jenkins/agent**是共享的。具体原因是,在Pod模版中配置了Empty Dir
image.pngimage.png

修改PodTemplate,添加Docker容器

添加了自己的容器后,我们再向Pod中添加一个Docker容器,用于执行一些Docker命令

  1. 添加Docker容器

image.png
image.png

这里需要将宿主机的**/var/run/docker.sock**挂载进去

  1. 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"
                }
            }
        }

    }
}

  1. 结果

image.png

  1. 查看Pod情况

image.png
image.png

目录也是通过EmptyDir方式,实现Pod内3个容器jnlp、xixi-build、xixi-docker中目录共享

Jenkins-Slave创建的Pod原理图

image.png

部署一个微服务项目

基于前面的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

image.png
image.png

项目中的Nacos地址配置:nacos-headless.default:8848

修改PodTemplate,挂载kubectl

image.png

继续修改Jenkins中的Pod模板,增添kubectl挂载。
Maven仓库可以也挂载出来,最好是声明个PVC,这里我就直接挂载到HostPath上

Jenkins-配置K8s凭证

image.png

将K8s集群Master节点的/root/.kube/config上传这里

Jenkins-安装K8s CLI插件

image.png

安装此插件后,可以通过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脚本

image.png

引入了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"
                     }
                  }
               }
            }
        }
    }
}

成果展示

image.png
image.png
image.png

通过网关访问服务
curl 10.102.58.6:18000/order/getOrder

image.png

可以为Gateway配置一个Ingress,这里就不演示了。

总结

好了,兄弟们,这就是XiXi最近探索的内容,中间过程相当曲折,镜像下载问题就把整个人整崩溃了,还好在水群的时候以前同学给了方案,阿里云租台美国主机,通过docker save 和 docker load -i的方式下载镜像。
如果大家想要我文档中一些配置的yaml和项目源码,可以私信,我尽量给到大家。

参考资料

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值