持续集成CI/CD之CD的完整版最佳实践

网友投稿 272 2022-09-10

持续集成CI/CD之CD的完整版最佳实践

上一章:​​持续集成CI&CD之CI的完整版最佳实践​​

CI&CD解读

概述

CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。作为一个面向开发和运营团队的解决方案,CI/CD 主要针对在集成新代码时所引发的问题(亦称:"集成地狱")。

具体而言,CI/CD 可让持续自动化和持续监控贯穿于应用的整个生命周期(从集成和测试阶段,到交付和部署)。这些关联的事务通常被统称为"CI/CD 管道",由开发和运维团队以敏捷方式协同支持。

CI 是什么?CI 和 CD 有什么区别?

缩略词 CI / CD 具有几个不同的含义。CI/CD 中的"CI"始终指持续集成,它属于开发人员的自动化流程。成功的 CI 意味着应用代码的新更改会定期构建、测试并合并到共享存储库中。该解决方案可以解决在一次开发中有太多应用分支,从而导致相互冲突的问题。

CI/CD 中的"CD"指的是持续交付和/或持续部署,这些相关概念有时会交叉使用。两者都事关管道后续阶段的自动化,但它们有时也会单独使用,用于说明自动化程度。

持续*交付*通常是指开发人员对应用的更改会自动进行错误测试并上传到存储库(如 GitHub 或容器注册表),然后由运维团队将其部署到实时生产环境中。这旨在解决开发和运维团队之间可见性及沟通较差的问题。因此,持续交付的目的就是确保尽可能减少部署新代码时所需的工作量。

持续*部署*(另一种"CD")指的是自动将开发人员的更改从存储库发布到生产环境,以供客户使用。它主要为了解决因手动流程降低应用交付速度,从而使运维团队超负荷的问题。持续部署以持续交付的优势为根基,实现了管道后续阶段的自动化。

CI/CD 既可能仅指持续集成和持续交付构成的关联环节,也可以指持续集成、持续交付和持续部署这三项构成的关联环节。更为复杂的是,有时"持续交付"也包含了持续部署流程。

归根结底,我们没必要纠结于这些语义,您只需记得 CI/CD 其实就是一个流程(通常形象地表述为管道),用于实现应用开发中的高度持续自动化和持续监控。因案例而异,该术语的具体含义取决于 CI/CD 管道的自动化程度。许多企业最开始先添加 CI,然后逐步实现交付和部署的自动化(例如作为云原生应用的一部分)。

CI 持续集成(Continuous Integration)

现代应用开发的目标是让多位开发人员同时处理同一应用的不同功能。但是,如果企业安排在一天内将所有分支源代码合并在一起(称为"合并日"),最终可能造成工作繁琐、耗时,而且需要手动完成。这是因为当一位独立工作的开发人员对应用进行更改时,有可能会与其他开发人员同时进行的更改发生冲突。如果每个开发人员都自定义自己的本地集成开发环境(IDE),而不是让团队就一个基于云的 IDE 达成一致,那么就会让问题更加雪上加霜。

持续集成(CI)可以帮助开发人员更加频繁地(有时甚至每天)将代码更改合并到共享分支或"主干"中。一旦开发人员对应用所做的更改被合并,系统就会通过自动构建应用并运行不同级别的自动化测试(通常是单元测试和集成测试)来验证这些更改,确保这些更改没有对应用造成破坏。这意味着测试内容涵盖了从类和函数到构成整个应用的不同模块。如果自动化测试发现新代码和现有代码之间存在冲突,CI 可以更加轻松地快速修复这些错误。

CD 持续交付(Continuous Delivery)

完成 CI 中构建及单元测试和集成测试的自动化流程后,持续交付可自动将已验证的代码发布到存储库。为了实现高效的持续交付流程,务必要确保 CI 已内置于开发管道。持续交付的目标是拥有一个可随时部署到生产环境的代码库。

在持续交付中,每个阶段(从代码更改的合并,到生产就绪型构建版本的交付)都涉及测试自动化和代码发布自动化。在流程结束时,运维团队可以快速、轻松地将应用部署到生产环境中。

CD 持续部署(Continuous Deployment)

对于一个成熟的 CI/CD 管道来说,最后的阶段是持续部署。作为持续交付——自动将生产就绪型构建版本发布到代码存储库——的延伸,持续部署可以自动将应用发布到生产环境。由于在生产之前的管道阶段没有手动门控,因此持续部署在很大程度上都得依赖精心设计的测试自动化。

实际上,持续部署意味着开发人员对应用的更改在编写后的几分钟内就能生效(假设它通过了自动化测试)。这更加便于持续接收和整合用户反馈。总而言之,所有这些 CI/CD 的关联步骤都有助于降低应用的部署风险,因此更便于以小件的方式(而非一次性)发布对应用的更改。不过,由于还需要编写自动化测试以适应 CI/CD 管道中的各种测试和发布阶段,因此前期投资还是会很大。

CI&CD集成流程

架构流程设计

(1)制品库制作;(2)提交部署应用与版本;(3)选择应用部署;(4)查看应用情况;

gitlab-ci构建制品库

制作制品库,使用了gitlab自带的CI/CD,代码编译与镜像制作都使用容器环境构建。

java制品库模板

本模板所有编译都采用容器环境,适用于父子结构的java工程;工程结构如下

父工程|--- 子模块1  # java子模块|------ docker文件夹 # 存放部署文件|--------- Dockerfile|--------- Jenkinsfile|--------- k8s.yaml|------ src...|--- 子模块2|------ docker文件夹|--------- Dockerfile|--------- Jenkinsfile|--------- k8s.yaml|------ src...|--- .gitlab-ci.yml

运行CI之前,需要在代码库组的CI/CD参数中需要设置​​CI_REGISTRY​​​:docker仓库地址;​​CI_REGISTRY_USER​​​:docker仓库账号; ​​CI_REGISTRY_PASSWORD​​​:docker仓库密码;​​CI_NAME_SPACE​​​:镜像的仓库路径[项目名称空间];​​CI_MAVEN_REPO​​​:maven本地仓库路径;​​CI_SETTING​​文件类型maven的setting.xml

参数说明

​​MODULES​​:是子模块列表以“,”隔开

​​​MODULE_PREFIX​​:用于更正子模块名称不带前缀情况。

# 参考文档:​​CI_REGISTRY_PASSWORD​​​:docker仓库密码;​​CI_NAME_SPACE​​:镜像的仓库路径[项目名称空间];

# 参考文档:https://docs.gitlab.com/ee/ci/yaml/index.html# 需要配置如下参数:# CI_REGISTRY:docker仓库地址,  CI_REGISTRY_USER:docker仓库账号,  CI_REGISTRY_PASSWORD:docker仓库密码,# CI_NAME_SPACE:镜像的仓库路径[项目所属空间], 若$CI_PROJECT_NAME命名不规范,则手动修改variables:  IMAGE: $CI_REGISTRY/$CI_NAME_SPACE/$CI_PROJECT_NAME  # 全局缓存jar文件cache: &global_cache  paths:    - node_modules/    - dist/  when: 'on_success'stages:  - vue-build  - docker-buildjob_build_tag_vue:  image: node:12  only:    - tags  stage: vue-build  # 配置仅仅上传全局node缓存  cache:    <<: *global_cache    policy: push  script:    - npm -v && node -v && npm config set registry https://registry.npm.taobao.org && npm install && npm run build job_build_tag_docker:  image: docker:latest  only:    - tags  stage: docker-build  services:    - docker:dind  # 配置仅仅下载全局缓存  cache:    <<: *global_cache    policy: pull  before_script:    - docker login $CI_REGISTRY --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD    - | # 定义应用版本号      app_version=$CI_COMMIT_TAG      if [ -z "$app_version" ]; then        app_version=$CI_COMMIT_SHORT_SHA      fi      export app_version  script:    - echo "开始打包docker镜像"    - rm -rf docker/dist && mv dist docker/    - docker build -t $IMAGE:$app_version docker    - docker push $IMAGE:$app_version    - docker rmi $IMAGE:$app_version

运行结果如图

应用部署列表与版本跟踪

因为需要发布到生产环境,需要跟踪发布的过程,本实践采用git来跟踪脚本。

本jenkins部署脚本时将成品的docker镜像,通过jenkins直接发布到k8s环境测试或者生产环境。使用git地址存储编写的jenkinsfile脚本,利用git与jenkins自身的功能完成发版的可追溯、可回滚等等。

|--- yaml  #存放k8s部署的yaml文件模块|--- version-app.sh #定义部署应用的列表,版本等等|--- Jenkinsfile #定义jenkins部署脚本

部署yaml文件

模板部分参数占位符说明: ​​__NAME_SPACE__​​: 部署空间占位符;

​​__DOMAIN_NAME__​​:部署应用名称占位符;

​​__REPLICAS_NUM__​​:部署实例数量占位符;

​​__DOCKER_IMAGE__​​:docker镜像地址占位符;

java部署模板k8s-java.yaml

apiVersion: v1kind: Namespacemetadata:  name: __NAME_SPACE__  labels:    name: __NAME_SPACE__---apiVersion: v1kind: Servicemetadata:  name: __DOMAIN_NAME__  namespace: __NAME_SPACE__spec:  ports:  - name: app-port    port: __CONTAINER_PORT__    targetPort: __CONTAINER_PORT__    protocol: TCP  # 当开启管理端口时,外部应用需要访问时打开此注释  # - name: manage-port  #   port: __MANAGE_PORT__  #   targetPort: __MANAGE_PORT__  #   protocol: TCP  selector:    app: __DOMAIN_NAME__---apiVersion: apps/v1kind: Deploymentmetadata:  name: __DOMAIN_NAME__  namespace: __NAME_SPACE__spec:  selector:    matchLabels:      app: __DOMAIN_NAME__  replicas: __REPLICAS_NUM__  template:    metadata:      labels:        app: __DOMAIN_NAME__    spec:      initContainers:      - name: init-agent-sidecar        image: ming19871211/skywalking-agent:__SKYWALKING_VESION__        command:        - 'sh'        - '-c'        - 'set -ex;cp -r /skywalking/agent/* /usr/skywalking/agent;'        volumeMounts:        - name: agent          mountPath: /usr/skywalking/agent      containers:      - name: __DOMAIN_NAME__        image: __DOCKER_IMAGE__        imagePullPolicy: IfNotPresent #本地存在就不到远程拉取镜像        env: #环境变量设置        - name: TZ          value: Asia/Shanghai        - name: NACOS_NAMESPACE          value: __NACOS_NAMESPACE__        - name: DEPLOY_ENV  # 兼容性配置,后续版本建议删除          value: __NACOS_NAMESPACE__        - name: NACOS_GROUP          value: __NACOS_GROUP__        - name: JAVA_OPTS          value: "__JAVA_OPTS__"        - name: CONTAINER_PORT          value: "__CONTAINER_PORT__"        - name: MANAGE_PORT          value: "__MANAGE_PORT__"        - name: NACOS_URL          value: __NACOS_URL__        - name: DOMAIN_NAME          value: __DOMAIN_NAME__.__NAME_SPACE__        # - name: DOMAIN_NAME        #   valueFrom:        #     fieldRef:        #       apiVersion: v1        #       fieldPath: status.podIP        envFrom:        - secretRef:            name: __NACOS_AUTH__        resources: #资源限制          requests:            memory: "128Mi"            cpu: "200m" #最低需要 0.1个cpu          limits:            memory: "__LIMIT_MEMORY__Mi"            cpu: "2000m"        ports:        - containerPort: __CONTAINER_PORT__          name: app-port          protocol: TCP        - containerPort: __MANAGE_PORT__          name: manage-port          protocol: TCP        readinessProbe: #就绪探针          httpGet:          #  path: __APP_MANAGE_PATH__/actuator/health/readiness          #  port: __MANAGE_PORT__          tcpSocket:            port: __CONTAINER_PORT__          initialDelaySeconds: 60          periodSeconds: 15          timeoutSeconds: 5        livenessProbe: #健康检查        #  httpGet:        #    path: __APP_MANAGE_PATH__/actuator/health/liveness        #    port: __MANAGE_PORT__          tcpSocket:            port: __CONTAINER_PORT__          initialDelaySeconds: 60          periodSeconds: 15          timeoutSeconds: 5        volumeMounts:        - name: time-config          mountPath: /etc/localtime          readOnly: true        - name: agent          mountPath: /usr/skywalking/agent        # 增加挂载        # - name: [PVC_NAME_ALIAS] # pod挂载别称        #   mountPath: [POD_MOUNT_PATH] # pods内挂载路径        #   subPath: [PVC_MOUNT_SUBPATH] #  pvc挂载盘中的子路径,注释这一项标识挂载根路径下      imagePullSecrets:      - name: __DOCKER_REGISTRY_SECRET__      nodeSelector:        isTest: "true"      volumes:      - name: time-config        hostPath:          path: /etc/localtime      - name: agent        emptyDir: {}      # 增加挂载      # - name: [PVC_NAME_ALIAS] # pod挂载别称      #   persistentVolumeClaim:      #     claimName: [PVC_NAME] # 对应指定的pvc名称

vue部署模板k8s-vue.yaml

apiVersion: v1kind: Namespacemetadata:  name: __NAME_SPACE__  labels:    name: __NAME_SPACE__---apiVersion: v1kind: Servicemetadata:  name: __DOMAIN_NAME__  namespace: __NAME_SPACE__spec:  ports:  - name: app-port    port: 80    targetPort: 80    protocol: TCP  selector:    app: __DOMAIN_NAME__---apiVersion: apps/v1  kind: Deploymentmetadata:  name: __DOMAIN_NAME__  namespace: __NAME_SPACE__spec:  selector:    matchLabels:      app: __DOMAIN_NAME__  replicas: __REPLICAS_NUM__   template:     metadata:      labels:        app: __DOMAIN_NAME__    spec:      initContainers:      - name: init-env-sidecar        image: busybox:latest        command: [ "sh", "-c"]        args:        - set -ex;          CONFIG_FILE=${CONFIG_FILE:-"vue-comm.properties"};          SYS_GLOBAL_CONFIG=${SYS_GLOBAL_CONFIG:-"sys-global-config.properties"};          wget --post-data="username=${NACOS_USR}&password=${NACOS_PWD}" -S "${NACOS_URL}/nacos/v1/auth/users/login" -O login-token;          access_token=$(grep  -Eo '"accessToken":"([^"]*)"' login-token |awk -F \":\" '{print $2}');          access_token=${access_token/\"/};          rm -f /init-env/env.conf;          rm -f /init-env/env-sys.conf;          wget "${NACOS_URL}/nacos/v1/cs/configs?dataId=${CONFIG_FILE}&group=${NACOS_GROUP}&tenant=${NACOS_NAMESPACE}&accessToken=$access_token" -O  /init-env/env.conf;          wget "${NACOS_URL}/nacos/v1/cs/configs?dataId=${SYS_GLOBAL_CONFIG}&group=${NACOS_GROUP}&tenant=${NACOS_NAMESPACE}&accessToken=$access_token" -O  /init-env/env-sys.conf || return 0;          if [  $? -eq 0 -a -f "/init-env/env-sys.conf" ]; then            echo -e  "\n" >>  /init-env/env.conf;            cat /init-env/env-sys.conf >> /init-env/env.conf;          fi        env: #环境变量设置        - name: NACOS_NAMESPACE          value: __NACOS_NAMESPACE__        - name: NACOS_GROUP          value: __NACOS_GROUP__        - name: SYS_GLOBAL_CONFIG          value: __SYS_GLOBAL_CONFIG__        - name: CONFIG_FILE          value: __CONFIG_FILE__        - name: NACOS_URL          value: __NACOS_URL__        envFrom:        - secretRef:            name: __NACOS_AUTH__        volumeMounts:        - name: init-env          mountPath: /init-env/      containers:      - name: __DOMAIN_NAME__        image: __DOCKER_IMAGE__        imagePullPolicy: IfNotPresent #本地存在就不到远程拉取镜像        env: #环境变量设置        - name: TZ          value: Asia/Shanghai        - name: DOMAIN_NAME          value: __DOMAIN_NAME__.__NAME_SPACE__        resources: #资源限制          requests:            memory: "128Mi"            cpu: "200m" #最低需要 0.1个cpu          limits:            memory: "__LIMIT_MEMORY__Mi"            cpu: "2000m"        ports:        - containerPort: 80        readinessProbe: #就绪探针        #  httpGet:        #    path: /index.html        #    port: 80          tcpSocket:            port: 80          initialDelaySeconds: 30          periodSeconds: 15          timeoutSeconds: 5        livenessProbe: #健康检查        #  httpGet:        #    path: /index.html        #    port: 80          tcpSocket:            port: 80          initialDelaySeconds: 30          periodSeconds: 15          timeoutSeconds: 5        volumeMounts:        - name: time-config          mountPath: /etc/localtime          readOnly: true        - name: init-env          mountPath: /init-env/      imagePullSecrets:      - name: __DOCKER_REGISTRY_SECRET__      nodeSelector:        isTest: "true"      volumes:      - name: time-config        hostPath:          path: /etc/localtime      - name: init-env        emptyDir: {}

版本定义文件

version-app.sh定义部署应用的列表、版本、实例、内存使用、应用类型等等 可以操作以下案例定义

#合同应用-后端export SOEASY_CLM="alpha1.0"export SOEASY_CLM_AI="$SOEASY_CLM"export SOEASY_CLM_ARCHIVE="$SOEASY_CLM"export SOEASY_CLM_CONTRACT="$SOEASY_CLM"export SOEASY_CLM_DATA="$SOEASY_CLM"export SOEASY_CLM_FILE="$SOEASY_CLM"export SOEASY_CLM_FORM="$SOEASY_CLM"export SOEASY_CLM_LOG="$SOEASY_CLM"export SOEASY_CLM_MESSAGE="$SOEASY_CLM"export SOEASY_CLM_SEARCH="$SOEASY_CLM"export SOEASY_CLM_WORKFLOW="$SOEASY_CLM"#合同-前端export SOEASY_CLM_UI="alpha1.0"export SOEASY_FRONTEND_ADMIN="alpha1.0"#部署应用参数定义,可选参数以:为分隔符,可选参数可以为空-表示此位置的值为默认值#eg:[应用名]:[版本号]:[可选:实例数量,默认1]:[可选:工程类型,默认java]:[可选:内存限制大小默认单位M]:[可选:挂载路径]#内存限制 java默认:2048 vue默认:1024#挂载路径 挂载pvc名称|容器应用内部地址 ,注意此参数暂无实现,等待有需求再实现APP_SERVERS=""""soeasy-clm-ai-server:$SOEASY_CLM_AI""soeasy-clm-archive-server:$SOEASY_CLM_ARCHIVE""soeasy-clm-contract-server:$SOEASY_CLM_CONTRACT""soeasy-clm-data-server:$SOEASY_CLM_DATA""soeasy-clm-file-server:$SOEASY_CLM_FILE""soeasy-clm-form-server:$SOEASY_CLM_FORM""soeasy-clm-log-server:$SOEASY_CLM_LOG""soeasy-clm-message-server:$SOEASY_CLM_MESSAGE""soeasy-clm-search-server:$SOEASY_CLM_SEARCH""soeasy-clm-workflow-server:$SOEASY_CLM_WORKFLOW""soeasy-clm-ui:$SOEASY_CLM_UI::vue""soeasy-frontend-admin:$SOEASY_FRONTEND_ADMIN::vue""""echo $APP_SERVERS

Jenkins部署文件

本文件以部署到k8s为例,只有一个步骤。

pipeline {    /*    ## 系统全局配置 ##    1.配置docker仓库相关变量 系统环境变量:DOCKER_REGISTRY_ADDR [docker仓库地址]    credentials-[Username with password]类型:jenkins-docker-registry-creds[docker仓库账号/docker仓库密码]    2.配置nacos相关变量 定义账号密码对应下面的NACOS_URL    credentials-[Username with password]类型:jenkins-nacos-cd-creds[nacos登录账号/nacos登录密码]    3.配置k8s相关变量 credentials-[Secret file]类型: jenkins-k8s-cd-config[k8s集群访问凭证文件]    ## k8s部署环境要求 ##    在部署节点上增加isTest=true的标签       nodeSelector:        isTest: "true"    ## 项目任务配置 ##    配置环境变量:SKYWALKING_BACKEND_SERVICE SKYWALKING_VERSION=7.0.0 IS_SKYWALKING=true    编写语法参考:-->选择流水线-->确定丢弃旧的构建-->保持构建的天数:[7]-->保持构建的最大个数:[7]选择参数化构建-->添加active choices parameter -->名称:[project] -->Groovy Script

return ["合同应用","合同应用-前端"]

选择参数化构建-->添加Active Choices Reactive parameters -->名称:[modules] -->-->Choice Type: [Check Boxes]-->Referenced parameters:[project]

Script Groovy Script:

clm_servers = ["soeasy-clm-ai-server","soeasy-clm-archive-server","soeasy-clm-contract-server",               "soeasy-clm-data-server","soeasy-clm-file-server","soeasy-clm-form-server","soeasy-clm-log-server",               "soeasy-clm-message-server","soeasy-clm-search-server","soeasy-clm-workflow-server"]clm_vue_servers = ["soeasy-clm-ui","soeasy-frontend-admin"]if (project.equals("合同应用")) {    return clm_servers}else if (project.equals("合同应用-前端")) {    return clm_servers}else{    return []}

如图

流水线-->定义:[pipeline script from SCM] -->SCM:[git]-->Repository URL:[工程的git地址]-->Credentials:[访问git的账号密码] --> 指定分支(为空时代表any): --> 脚本路径:[Jenkinsfile文件路径,Jenkinsfile]

注意:新建任务前,需要将Jenkinsfile文件注释的前3点,在jenkins系统中配置好。

jenkins版本发布

具体步骤如下

部署环境运维操作

上述版本发布后,查看应用的具体情况需要使用运维管理工具进行查看,以k8s为例,我们选择rancher进行应用查看等

查看部署的应用

查看日志

重启应用

进入容器

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Flannel Vxlan 跨节点通信
下一篇:持续集成CI/CD之配置管理最佳实践
相关文章

 发表评论

暂时没有评论,来抢沙发吧~