使用Jenkins实现镜像的CI/CD

若您期望实现从源代码自动构建、推送镜像到最终部署应用的一体化自动化操作。您可以使用Jenkins实现镜像的CI/CD,只要您在GitLab中提交源代码,容器镜像会自动使用源代码构建镜像,容器服务会自动拉取镜像部署应用,并自动发送事件通知到钉钉群。

前提条件

使用Jenkins实现镜像的CI

只要您在GitLab中提交源代码,容器镜像会自动使用源代码构建镜像,然后您可以对镜像进行漏洞扫描,镜像扫描完成后,将自动发送事件通知到钉钉群。

  1. 在GitLab中创建项目。

    1. 登录GitLab。

    2. 在顶部导航栏中,选择Projects > Your projects

    3. Projects页面单击右上角的New Project,单击Create blank project

    4. Create blank project页面设置Project nameProject URLProject slug,设置Visibility LevelPrivate,然后单击Create project

      创建Project

    5. 使用以下内容,在本地创建Dockerfilepom.xmlDemoApplication.javaHelloController.java文件。

      • Dockerfile

        FROM registry.cn-hangzhou.aliyuncs.com/public-toolbox/maven:3.8.3-openjdk-8-aliyun AS build
        COPY src /home/app/src
        COPY pom.xml /home/app
        RUN ["/usr/local/bin/mvn-entrypoint.sh","mvn","-f","/home/app/pom.xml","clean","package","-Dmaven.test.skip=true"]
        
        FROM registry.cn-hangzhou.aliyuncs.com/public-toolbox/openjdk:8-jdk-alpine
        COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo-0.0.1-SNAPSHOT.jar
        EXPOSE 8080
        ENTRYPOINT ["java","-jar","/usr/local/lib/demo-0.0.1-SNAPSHOT.jar"]
      • pom.xml

        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.6.1</version>
                <relativePath/> <!-- lookup parent from repository -->
            </parent>
            <groupId>com.example</groupId>
            <artifactId>demo</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <name>demo</name>
            <description>Demo project for Spring Boot</description>
            <properties>
                <java.version>1.8</java.version>
            </properties>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-freemarker</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
        
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <optional>true</optional>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                </dependency>
                <dependency>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-core</artifactId>
                    <version>2.13.2</version>
                </dependency>
            </dependencies>
        
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <configuration>
                            <excludes>
                                <exclude>
                                    <groupId>org.projectlombok</groupId>
                                    <artifactId>lombok</artifactId>
                                </exclude>
                            </excludes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        
        </project>
      • DemoApplication.java

        package com.example.demo;
        
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        
        import java.util.TimeZone;
        
        @SpringBootApplication
        public class DemoApplication {
        
            public static void main(String[] args) {
                TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
                SpringApplication.run(DemoApplication.class, args);
            }
        
        }
      • HelloController.java

        package com.example.demo;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RestController;
        
        import javax.servlet.http.HttpServletRequest;
        import java.text.SimpleDateFormat;
        import java.util.Date;
        
        @RestController
        @Slf4j
        public class HelloController {
        
            @RequestMapping({"/hello", "/"})
            public String hello(HttpServletRequest request) {
                return "Hello World at " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            }
        }
    6. 执行以下命令,上传构建文件至GitLab。

      cd java-web  #进入构建文件所在目录。
      git remote set-url origin http://8.218.20*.***/shoppingmall/java-web.git
      git push origin master
      * [new branch]      master -> master
  2. 在Jenkins配置构建镜像的流水线。

    1. 在Jenkins配置GitLab的SSH Key。

      1. 登录Jenkins。

      2. 在Jenkins左侧导航栏单击系统管理

      3. 安全区域单击Manage Credentials

      4. Stores scoped to Jenkins区域单击存储列下的Jenkins,然后单击全局凭据

      5. 在左侧导航栏单击添加凭据

      6. 设置类型为SSH Username with private key,然后输入描述Username,选中Enter directly,然后单击确定

        全局凭据页面会自动生成凭据ID,保存该凭据ID。

    2. 创建流水线。

      1. 在Jenkins左侧导航栏单击新建任务

      2. 输入任务名称,选中流水线,单击确定

      3. 单击构建触发器页签,选中Build when a change is pushed to GitLab,然后选中Push Events

        Build when a change is pushed to GitLab右侧记录Webhook的地址。

      4. 单击高级,单击Secret token右下角的Generate

        Jenkins会生成一个Secret token,保存该Secret token。

      5. 单击流水线页签,根据实际情况修改以下模板,然后将内容复制到文本框中,然后单击保存

        def git_auth_id = "6d5a2c06-f0a7-43c8-9b79-37b8c266****"  #凭据ID
        def git_branch_name = "master" #分支名
        def git_url = "git@172.16.1*.***:shoppingmall/java-web.git"   #GitLab仓库地址
        def acr_url = "s*****-devsecops-registry.cn-hongkong.cr.aliyuncs.com"  #镜像仓库地址
        def acr_username = "acr_test_*****@test.aliyunid.com"   #容器镜像用户名
        def acr_password = "HelloWorld2021"   #容器镜像密码
        def acr_namespace = "ns"    #命名空间
        def acr_repo_name = "test"   #镜像仓库名称
        def tag_version = "0.0.1"   #镜像版本
        node {
        
            stage('checkout git repo') {
                checkout([$class: 'GitSCM', branches: [[name: "*/${git_branch_name}"]], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth_id}", url: "${git_url}"]]])
            }
            stage('build image') {
                sh "sudo docker build -t java-web:${tag_version} ."
                sh "sudo docker tag java-web:${tag_version} ${acr_url}/${acr_namespace}/${acr_repo_name}:${tag_version}"
            }
            stage('push image') {
                sh "sudo docker login --username=${acr_username} --password=${acr_password} ${acr_url}"
                sh "sudo docker push ${acr_url}/${acr_namespace}/${acr_repo_name}:${tag_version}"
            }
        }
  3. 添加Webhook地址到GitLab中。

    1. 登录GitLab。

    2. Projects页面单击上文创建的Project名称。

    3. 在左侧导航栏选择Settings > Webhooks,输入Webhook地址和Secret token,取消选中Enable SSL verification,然后单击Add webhooks

  4. 创建事件通知。

    1. 登录容器镜像服务控制台

    2. 在顶部菜单栏,选择所需地域。

    3. 在左侧导航栏,选择实例列表

    4. 实例列表页面单击目标企业版实例。

    5. 在实例详情页面左侧导航栏选择实例管理 > 事件通知

    6. 事件规则页签下单击创建规则

    7. 事件范围配置向导中设置规则名称事件类型镜像扫描完成,选中扫描完成,设置生效范围命名空间,选择命名空间为ns,然后单击下一步

    8. 事件通知配置向导中设置通知方式钉钉,输入钉钉的Webhook地址和加签密钥,然后单击保存

  5. 触发镜像构建。

    执行以下命令,修改HelloController.java文件,提交文件到GitLab中,从而触发镜像构建。

    vim java/com/example/demo/HelloController.java #根据实际情况在本地修改HelloController.java文件
    git add . && git commit -m 'commit' && git push origin #提交文件到GitLab

    稍等一段时间,在企业版实例详情页面左侧导航栏选择仓库管理 > 镜像仓库,在右侧页面单击目标仓库test,在仓库管理页面左侧导航栏单击镜像版本,可以看到镜像版本页面生成一个镜像。

  6. 设置漏洞扫描。

    1. 在镜像版本页面单击目标镜像右侧操作下的安全扫描

    2. 安全扫描页面,单击立即扫描

      待镜像扫描完成后,您可以在钉钉群里收到通知。

使用Jenkins实现镜像的CD

只要您在GitLab中提交源代码,容器镜像会自动用源代码构建镜像,镜像构建成功后,自动触发执行交付链,待交付链运行完成后,将自动发送HTTP请求至Jenkins,然后触发ACK的Deployment重新拉取镜像部署应用。

  1. 创建应用。

    1. 登录容器服务管理控制台

    2. 在控制台左侧导航栏,单击集群

    3. 集群列表页面,单击目标集群名称或者目标集群右侧操作列下的详情

    4. 在集群管理页左侧导航栏,选择工作负载 > 无状态

    5. 无状态页面单击右上角的使用YAML创建资源

    6. 创建页面顶部选择命名空间,设置示例模板自定义,然后复制以下内容到模板中,然后单击创建

      说明

      没有设置免密拉取镜像,您需要在容器镜像服务中开启公网访问,并设置镜像为公开镜像。具体操作,请参见配置公网的访问控制

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        creationTimestamp: null
        labels:
          app: demo
        name: demo
      spec:
        replicas: 3
        minReadySeconds: 5
        progressDeadlineSeconds: 60
        revisionHistoryLimit: 5
        selector:
          matchLabels:
            app: demo
        strategy:
          rollingUpdate:
            maxUnavailable: 1
          type: RollingUpdate
        template:
          metadata:
            annotations:
              prometheus.io/port: "9797"
              prometheus.io/scrape: "true"
            creationTimestamp: null
            labels:
              app: demo
          spec:
            containers:
            - image: s*****-devsecops-registry.cn-hongkong.cr.aliyuncs.com/ns/test:0.0.1 
              imagePullPolicy: Always
              name: demo
              ports:
              - containerPort: 8080
                name: http
                protocol: TCP
              readinessProbe:
                initialDelaySeconds: 5
                tcpSocket:
                  port: 8080
                timeoutSeconds: 5
              resources:
                limits:
                  cpu: "2"
                  memory: 512Mi
                requests:
                  cpu: 100m
                  memory: 64Mi
      status: {}
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: demo-svc
      spec:
        selector:
          app: demo
        ports:
          - protocol: TCP
            port: 80
            targetPort: 8080
      ---
      apiVersion: extensions/v1beta1
      kind: Ingress
      metadata:
        name: demo
        labels:
          app: demo
      spec:
        rules:
          - host: app.demo.example.com
            http:
              paths:
                - backend:
                    serviceName: demo-svc
                    servicePort: 80
      ---
                                      
    7. 无状态页面单击目标应用demo,单击访问方式页签。

      访问方式页签下获取外部端点地址。

    8. 在本地hosts绑定以下内容。

      <外部端点地址> app.demo.example.com
    9. 在浏览器中输入app.demo.example.com

      应用看到以上页面,说明应用部署成功。

  2. 创建触发器。

    1. 在无状态页面单击目标应用demo的名称。

    2. 在应用详情页面单击触发器页签,单击创建触发器

    3. 创建触发器对话框设置触发器行为重新部署,然后单击确定

      触发器页签下获取触发器链接。

  3. 创建流水线。

    1. 登录Jenkins。

    2. 在左侧导航栏单击新建任务

    3. 输入任务名称,单击流水线

    4. 单击构建触发器页签,选中Generic Webhook Trigger

      Generic Webhook Trigger下可以获取HTTP请求触发地址为JENKINS_URL/generic-webhook-trigger/invoke,本文设置Generic Webhook tokenhelloworld2021,因此Webhook地址为JENKINS_URL/generic-webhook-trigger/invoke?token=helloworld2021

    5. 单击流水线页签,根据实际情况修改以下模板中的Generic Webhook token和触发器链接,然后将内容复制到文本框中,然后单击保存

      pipeline {
        agent any
        triggers {
          GenericTrigger(
           genericVariables: [
            [key: 'InstanceId', value: '$.data.InstanceId'],   
            [key: 'RepoNamespaceName', value: '$.data.RepoNamespaceName'],
            [key: 'RepoName', value: '$.data.RepoName'],
            [key: 'Tag', value: '$.data.Tag']
           ],
           causeString: 'Triggered on $ref',
           token: 'helloworld2021',   #Generic Webhook token,请根据实际情况替换。
           tokenCredentialId: '',
           printContributedVariables: true,
           printPostContent: true,
           silentResponse: false,
           regexpFilterText: '$ref'
          )
        }
        stages {
          stage('Some step') {
            steps {
              sh "echo 'will print post content'"
              sh "echo $InstanceId"
              sh "echo $RepoNamespaceName"
              sh "echo $RepoName"
              sh "echo $Tag"
              sh "echo 'redeploy to ACK or you can deoloy to other platforms use before message'"
              sh "curl 'https://cs.console.aliyun.com/hook/trigger?token=g****'  #触发器链接,请根据实际情况替换。
              sh "echo 'done'"
            }
          }
        }
      }
  4. 创建交付链。具体操作。请参见创建交付链

  5. 创建事件规则。

    1. 在企业版实例管理页面左侧导航栏中选择实例管理 > 事件通知

    2. 事件规则页签下单击创建规则

    3. 事件范围配置向导中设置规则名称事件类型交付链处理完成,选中成功,设置生效范围命名空间,选择命名空间为ns,然后单击下一步

    4. 事件通知配置向导中设置通知方式HTTP,输入步骤3获取的Webhook地址,然后单击保存

  6. 执行以下命令,修改HelloController.java文件,提交代码到GitLab中,从而触发镜像构建。

    vim java/com/example/demo/HelloController.java #根据实际情况在本地修改HelloController.java文件。
    git add . && git commit -m 'add update' && git push origin   #提交文件到GitLab中。

    镜像构建成功后,触发执行交付链,交付链执行完毕后,触发ACK的Deployment重新拉取镜像部署应用。

  7. 执行以下命令,验证应用是否重新部署。

    curl app.demo.example.com

    可以看到输出内容发生了变化, 说明应用重新部署成功。