kubesphere devops系统基于jenkins构建CI/CD工作流,可以方便快捷地构建、发布应用到k8s。本文以jeesite为例,说明devops流水线的构建过程。

创建前的准备

Kubesphere开启devops

部署Gitlab

部署Harbor

部署SonarQube

部署mysql

创建凭证:harbor-jeesite(帐户凭证),kubeconfig(kubeconfig),sonar(秘密文本),gitlab-id(帐户凭证)

流水线构建

进入自己的企业空间,点击devops工程,创建“jeesite”。

创建devops工程

进入jeesite devops工程,创建jeesite-pipeline流水线,点击“编辑Jenkinsfile”,将下列代码修改后输入。

Jenkinsfile
 
    pipeline {
        agent {
            node {
            label 'maven'
            }
        }
        stages {
            stage('git-clone') {
            agent none
            steps {
                sh 'git config --global http.sslverify false'
                git(url: 'https://your-gitlab-url/your-group-name/jeesite4', credentialsId: 'gitlab-id', changelog: true, poll: false, branch: 'master')
            }
            }
            stage('maven-test') {
            agent none
            steps {
                container('maven') {
                sh 'mvn -version'
                }
            }
            }
            stage('maven-deploy') {
            agent none
            steps {
                container('maven') {
                sh '''cd web/bin/
        sh package.sh
        # mvn -Dmaven.test.skip=true -Dfile.encoding=UTF-8 -DsourceEncoding=UTF-8 clean install -U -f web/pom.xml'''
                }
            }
            }
            stage('code-check') {
            agent none
            steps {
                container('maven') {
                withCredentials([string(credentialsId : 'sonar' ,variable : 'SONAR_TOKEN' ,)]) {
                    withSonarQubeEnv('sonar') {
                    sh '''cd web
        mvn sonar:sonar -Dsonar.projectKey=jeesite  -Dsonar.host.url=your-sonarqube-url -Dsonar.login=$SONAR_TOKEN'''
                    }
                }
                timeout(unit: 'HOURS', activity: true, time: 1) {
                    waitForQualityGate 'true'
                }
                }
            }
            }
            stage('build & push') {
            agent none
            steps {
                script {
                env.COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()
                env.BUILD_FLAG="${COMMIT_ID}"
                }
                container('maven') {
                sh 'docker build -f Dockerfile -t $REGISTRY/$HARBOR_NAMESPACE/$IMAGE_APP_NAME:$BUILD_FLAG .'
                // 'robot$test+jeesite'中的test为HARBOR_NAMESPACE,jeesite为自己创建的机器人帐户名字
                sh 'echo $HARBOR_CREDENTIAL_PSW | docker login $REGISTRY -u \'robot$test+jeesite\' --password-stdin' 
                sh 'docker push $REGISTRY/$HARBOR_NAMESPACE/$IMAGE_APP_NAME:$BUILD_FLAG'
                }
            }
            }
            stage('deploy2k8s') {
            agent none
            steps {
                kubernetesDeploy(enableConfigSubstitution: true, deleteResource: false, kubeconfigId: 'kubeconfig', configs: 'deploy/jeesite.yaml')
            }
            }
        }
        environment {
            IMAGE_APP_NAME = 'jeesite'
            REGISTRY = 'your-harbor-domain'
            HARBOR_NAMESPACE = 'test'
            HARBOR_CREDENTIAL = credentials('harbor-jeesite')
        }
    }
  

jeesite源码clone到自己的gitlab,进入web/src/main/resources/config/application.yml,修改jdbc配置。创建Dockerfile文件以打包镜像,创建deploy/jeesite.yaml文件以部署项目到k8s。

gitlab添加文件

Dockerfile
 
    FROM openjdk:8-jdk-alpine
    COPY  ./web/target/web.war /usr/local/web.war
    RUN mkdir /usr/local/web \
        && unzip -n /usr/local/web.war -d /usr/local/web/ \
        && rm -rf /usr/local/web.war
    WORKDIR /usr/local/web/WEB-INF/
    ENTRYPOINT ["sh", "./startup.sh"]
  
jeesite.yaml
 
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: jeesite
      namespace: jeesite
    spec:
      replicas: 1
      revisionHistoryLimit: 3
      selector:
        matchLabels:
          app: jeesite
      template:
        metadata:
          labels:
            app: jeesite
        spec:
        #   volumes:
        #     - name: volume-config
        #       configMap:
        #         name: jeesite-config
        #         items:
        #           - key: application.yml
        #             path: application.yml
        #         defaultMode: 0644
        containers:
            - image: $REGISTRY/$HARBOR_NAMESPACE/$IMAGE_APP_NAME:$BUILD_FLAG
              name: jeesite
              imagePullPolicy: Always  
              ports:
                - containerPort: 80
                  name: http
                - containerPort: 443
                  name: https
            #   volumeMounts:
            #     - name: volume-config
            #       readOnly: true
            #       mountPath: /usr/local/web/WEB-INF/classes/config/application.yml
            #       subPath: application.yml
          imagePullSecrets:
            - name: harbor-sec 
          restartPolicy: Always   
    ---
    apiVersion: v1
    kind: Service # 若无域名,可自行修改为nodeport方式
    metadata:
      name: jeesite
      namespace: jeesite
    spec:
      ports:
      - port: 80
        targetPort: 8980
      selector:
        app: jeesite
    ---
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress # 若无域名,则取消ingress
    metadata:
      name: jeesite
      namespace: jeesite
      annotations:
        kubernetes.io/ingress.class: nginx
    spec:
      tls:
      - hosts:
        - your-jeesite-domain
        secretName: jeesite-crt-secret
      rules:
      - host: your-jeesite-domain
        http:
          paths:
          - backend:
              serviceName: jeesite
              servicePort: 80
  

若harbor项目访问级别为私有,需要在k8s你所要部署jeesite的命名空间中添加密钥,这对应到jeesite.yaml中Deployment的template.imagePullSecrets字段。

对应命名空间创建密钥

若想将application.yml通过configmap挂载,可以将jeesite.yaml中的注释取消,但会产生如下报错:

1
2
3
4
5
6
7
8
9
10
10-28 03:52:04.564 ERROR [com.jeesite.common.config.Global       ] - Update classpath:config/application.yml shiro.rememberMe.secretKey failure.

java.io.IOException: File '/usr/local/web/WEB-INF/classes/config/application.yml' cannot be written to
at org.apache.commons.io.FileUtils.openOutputStream(FileUtils.java:2177)
at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:3096)
at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:3133)
at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:3115)
at com.jeesite.common.config.Global.goto(kda:245)
at com.jeesite.common.config.Global.updateProperty(kda:391)
...

流水线的构建已大致完成,先不要着急运行,jeesite需要初始化数据库。

  • 通过kubectl exec命令或在kubesphere中进入mysql容器,在/etc/mysql/my.cnf中添加如下配置:
1
2
[mysqld]
sql_mode="ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
  • mysql -uroot -p 进入mysql后,创建用户和授权:
1
2
3
4
5
6
set global read_only=0;
set global optimizer_switch='derived_merge=off';
create user 'jeesite'@'%' identified by 'jeesite';
create database jeesite DEFAULT CHARSET 'utf8' COLLATE 'utf8_general_ci';
grant all privileges on jeesite.* to 'jeesite'@'%' identified by 'jeesite';
flush privileges;
  • 由于执行init-data.sh脚本需要源代码以及配置maven和java环境,因此本步骤在云ide code-server中完成。git clone下项目源码后,修改web/src/main/resources/config/application.yml中的jdbc配置,然后cd到jeesite4/web/bin目录下执行sh init-date.sh

code-server初始化数据库

数据库初始化完毕后,若上述所有配置得当,则可以运行流水线了。若流水线运行成功,且jeesite容器正常运行,则可以通过域名(或nodeport)对其进行访问。访问成功页面如下。

账号system密码admin

使用Webhook触发流水线

  • jenkins配置

30180端口打开jenkins页面(默认账号密码:admin / P@88w0rd),进入系统管理-插件管理,点击可选插件,搜索并安装下列未装插件:Git Plugin、SSH Plugin、URL Trigger Plugin、Gerrit Trigger、Gitlab Hook Plugin、Gitlab Merge Request Builder、GitLab Plugin。然后重启jenkins(your-host:30180/restart)。待重启完毕,进入jeesite对应的流水线,点击配置-构建触发器,选中Build when a change is pushed to GitLab…,复制红框中的链接。
勾选触发器选项

点击高级-Generate,生成secret token,复制token,然后保存设置。

  • gitlab配置

点击扳手图标进入admin area,再点击settings-network,确保outbound requests下两选项为选中状态。

outbound requests

进入jeesite4项目,点击settings-webhooks,在URL和Secret Token中输入上步复制的对应值。若复制过来的URL不是ip形式,则将jenkins.devops.kubesphere.local部分替换为节点ip。添加完成后,点击teset-push events,可模拟一次push events事件。若顺利,页面会出现Hook executed successfully: HTTP 200消息,刷新kubesphere jeesite流水线页面,有新的活动运行。

为测试实际效果,在gitlab中修改web/src/main/resources/config/application.yml,将productName设置为JeeSite,提交修改后,流水线自动运行。待部署完毕,访问jeesite,发现“Demo”已消失。

productName更改生效