CI/CD 实践指南
1.概念和背景
IT团队内部为了提升开发效率,减少不断发布耗费的时间,我们会将流程固化下来,这样避免每次发布都需要专人,去操作计算机,打包,编译,运行等。
这样就有了Devops,CI CD这些概念,那首先说一下Devops和CICD的关系?
DevOps是一种以协作和自动化为基础的方法论,旨在促进开发和运维团队之间的合作,提高软件交付的速度和质量。而CICD则是一种实践方法,通过自动化的流程来保证软件开发中的持续集成、测试和部署。在采用DevOps方法论的组织中,CICD是一个核心的实践方法。通过持续集成和持续交付,团队能够频繁地将代码集成到主干分支,并自动化地构建、测试和部署软件,从而快速响应用户需求,减少发布周期,提高软件质量和可靠性。CICD是实现DevOps的方法之一。通过自动化工具和流程,CICD支持DevOps的实践,确保软件开发过程中的高质量和交付的及时性2。持续集成(CI)是在源代码变更后自动检测、拉取、构建和进行单元测试的过程,而持续交付(CD)则是在完成CI后,将已验证的代码发布到生产环境。

DevOps(Development和Operations的组合词)是一组过程、方法与系统的统称,用于促进开发、技术运营和质量保障(QA)部门之间的沟通、协作与整合。
--可以把DevOps看作开发、技术运营和质量保障(QA)三者的交集。

CI/CD 的核心概念是持续集成、持续交付和持续部署。CI/CD 可让持续自动化和持续监控贯穿于应用的整个生命周期(从集成和测试阶段,到交付和部署)。这些关联的事务通常被统称为“CI/CD 管道”,由开发和运维团队以敏捷方式协同支持。
--CI持续集成(Continuous Integration),可以帮助开发人员更加频繁地(有时甚至每天)将代码更改合并到共享分支或“主干”中。一旦开发人员对应用所做的更改被合并,系统就会通过自动构建应用并运行不同级别的自动化测试,来验证这些更改,确保这些更改没有对应用造成破坏。
--CD 持续交付(Continuous Delivery),完成 CI 中构建及单元测试和集成测试的自动化流程后,持续交付可自动将已验证的代码发布到存储库。为了实现高效的持续交付流程,务必要确保 CI 已内置于开发管道。持续交付的目标是拥有一个可随时部署到生产环境的代码库。
--CD 持续部署(Continuous Deployment),持续部署可以自动将应用发布到生产环境,持续部署意味着开发人员对应用的更改在编写后的几分钟内就能生效(假设它通过了自动化测试)。
总而言之,所有这些 CI/CD 的关联步骤都有助于降低应用的部署风险,因此更便于以小件的方式(而非一次性)发布对应用的更改。不过,由于还需要编写自动化测试以适应 CI/CD 管道中的各种测试和发布阶段,因此前期投资还是会很大。另外,针对现在较流行的敏捷开发,需要高频词发布,Devops比较适用。简言之,我们使用CI/CD来高频且可预测地交付更高质量的软件。
(PS:我们还应该确保从开始到结束都将安全性考虑到开发过程中。这通常被称为DevSecOps。)
2.我们的技术栈?
讲讲我们的实现,目前我们使用基于springcloud的微服务架构,运行环境是K8S,CI工具:Jenkins,CD工具:Argo CD
Argo CD流程:

Argo CD 的工作流程如下:
创建应用程序: 用户通过 Argo CD 的命令行界面或 Web 界面创建应用程序。在创建应用程序时,用户需要指定应用程序的名称、Git 存储库的 URL、分支或标签以及路径等信息。
GitOps 同步: Argo CD 定期轮询配置的 Git 存储库,检测应用程序配置文件的变更。一旦发现变更,它会触发同步过程。
应用程序同步: Argo CD 从 Git 存储库中获取应用程序的声明性配置文件(例如 YAML 文件),并将其与当前的实际状态进行比较。
状态比较: Argo CD 使用 Kubernetes API 与集群进行通信,获取当前部署的应用程序的实际状态。然后,它将实际状态与声明性配置文件中定义的期望状态进行比较。
状态同步: 如果实际状态与期望状态不一致,Argo CD 将自动采取必要的操作来调整实际状态,使其与期望状态保持一致。这可能涉及创建、更新或删除 Kubernetes 资源。
应用程序健康检查: Argo CD 监测应用程序的健康状态,以确保部署成功。它可以通过检查容器的就绪状态、服务的可用性和自定义的健康检查指标来确定应用程序是否正常运行。
持续同步: Argo CD 会定期轮询 Git 存储库,以确保应用程序的状态与声明性配置文件保持同步。如果发现配置文件有更新,它将触发新一轮的同步过程,以将实际状态调整为期望状态。
可视化界面和监控: Argo CD 提供了一个直观的 Web 界面,用于查看和管理应用程序的状态。用户可以在界面上查看应用程序的拓扑图、部署历史、健康状态和同步状态。此外,Argo CD 还提供了监控和警报功能,以帮助用户监测应用程序的性能和可用性。
3.如何做?
3.1.创建代码库
在项目中创建 devops-ci 和 devops-cd 两个代码仓库 。devops-ci : 用于放 Jenkins 脚本,以及 ci 相关的所有文件 ;devops-cd : 用于放 k8s 部署脚本,ArgoCD 会读取其中的 yaml。所有环境的部署脚本都放在 master 分支下,通过目录结构隔开。
3.2.CI脚本
目录结构为(三个环境):
pipeline
├─dev
│ declartion.jenkinsfile
│ gateway.jenkinsfile
├─qa
│ declartion.jenkinsfile
│ gateway.jenkinsfile
└─test
举例,一个微服务的构建脚本:
def project_name = "app" def service_name = "order" def dev_repo_url = "http://***/kra-mobileapp/order.git" def branch = "master" def target_env = "dev" def docker_register_url = "docker-registry.***.net"//内部nexues镜像仓库 def git_credential_id = "{git_credential_id}" // 请修改为正确的 credential_id def node_name = "{node_name}" // 请修改为对应的 node_name @Library('uea-shared-libraries') _ def ueaCommonUtils = new com.tool.UeaCommonUtils(); pipeline { agent { node { label node_name } } stages { stage("Get code") { steps { timeout(time:10, unit:"MINUTES") { script { ueaCommonUtils.checkout(git_credential_id, dev_repo_url, branch) } } } } stage("Maven package") { steps { timeout(time:30, unit:"MINUTES") { script { dir('source') { ueaCommonUtils.mvnPackage() } } } } } stage("Maven build image") { steps { timeout(time:30, unit:"MINUTES") { script { dir('source') { ueaCommonUtils.buildDockerImage() } } } } } stage('Push image') { steps { script { timeout(time:10, unit:"MINUTES") { ueaCommonUtils.pushImage(project_name, service_name, docker_register_url) } } } } stage('Get DevOps CD') { steps { script { timeout(time:10, unit:"MINUTES") { script { ueaCommonUtils.checkoutCD(git_credential_id, cd_url) } } } } } stage('Update CD Image tag') { steps { script { timeout(time:10, unit:"MINUTES") { script { ueaCommonUtils.updateCDImageTag(project_name, service_name, docker_register_url, target_env) } } } } } } post { always { 2.2.2. gateway 服务构建脚本 script{ currentBuild.description = "Build Node: ${node_name}" } } success { script{ currentBuild.description += "\n 构建成功!" } } failure { script{ currentBuild.description += "\n 构建失败!" } } aborted { script{ currentBuild.description += "\n 构建取消!" } } } }
ps:shared-libraries
package com.tool def checkout(credentials_id, repo_url, branch, targetDir='source', wipeWorkspace=true) { def gitExtensions = [ [ $class: 'RelativeTargetDirectory', relativeTargetDir: targetDir ] ] if(wipeWorkspace) { gitExtensions.add([ $class: 'WipeWorkspace' ]) } timeout(time:10, unit:"MINUTES") { checkout( poll: false, scm: scmGit( branches: [[name: branch]], extensions: gitExtensions, userRemoteConfigs: [[ credentialsId: credentials_id, url: repo_url ]] ) ) } } def mvnPackage(){ sh """ mvn clean package """ } def buildDockerImage(enableTest=false){ if(enableTest) { sh """ mvn -Dprod clean verify -DskipTests -U jib:dockerBuild """ } else { sh """ mvn -Dprod clean test -U jib:dockerBuild """ } } def pushImage(project_name, service_name, docker_register_url){ sh """ docker tag ${project_name}-${service_name}:latest ${docker_register_url}/${project_name}/${service_name}:${build_id} sleep 1 docker push ${docker_register_url}/${project_name}/${service_name}:${build_id} """ } def checkoutCD(credentials_id, repo_url) { checkout(credentials_id, repo_url, 'master', 'devOpsCD', true) } def updateCDImageTag(project_name, service_name, docker_register_url, target_env){ sh """ cd devOpsCD/manifests/overlays/${target_env} kustomize edit set image ${service_name}-image=${docker_register_url}/${project_name}/${service_name}:${build_id} git config --global user.name "Bot UEA DevOps" git config --global user.email "**@**.com" git checkout master git commit -am "ci(UEA auto commit): update image tag to ${build_id}" git push origin master """ }
微服务中POM.xml需要调整打包方式:
<from> <image>docker-registry.***.net/adoptopenjdk:11-jre-hotspot</image> </from> <to> <image>app-order:latest</image> </to>
3.3.Jenkins pipeline
选择“文件夹”,然后创建item

选择“pipeline script from SCM”

script path填写仓库中jenkins file 的路径:

3.4.CD 脚本
CD脚本放在上面介绍的devops-cd目录中
目录结构参考:

service中按照微服务模块分,不同环境放在overlays下,如:dev/prod,通过kustomization.yaml 的 namePrefix 和 namespace来确定项目名和环境名(开发dev,生产prod)
kustomization.yaml参考
# 文件路径 app/overlays/dev/kustomization.yaml resources: - ./config/central-config-configmap.yaml - ./config/common-configmap.yaml - ./network/gateway.yaml - ./network/virtual-service.yaml - ../../base namePrefix: app-order-dev- namespace: app-order-dev commonAnnotations: note: development environment # REDIS # RABBITMQ # DB # Registry #- DB_GATEWAY_URL=jdbc:oracle:thin:@***:1521:DEV secretGenerator: - literals: - SPRING_REDIS_PASSWORD=** - SPRING_RABBITMQ_USERNAME=admin - SPRING_RABBITMQ_PASSWORD=** - DB_GATEWAY_URL=jdbc:postgresql://***/app_order_dev_gateway?currentSchema=app_order_dev_gateway - DB_GATEWAY_USERNAME=app_order_dev_gateway - DB_GATEWAY_PASSWORD=dev_gateway - DB_CONFIGURATION_URL=jdbc:postgresql://***/app_order_dev_configuration?currentSchema=app_order_dev_configuration - DB_CONFIGURATION_USERNAME=app_order_dev_configuration - DB_CONFIGURATION_PASSWORD=dev_configuration - JHIPSTER_REGISTRY_PASSWORD=admin name: common-secret apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: configuration-image newName: docker-registry.bullchina.net/order/configuration newTag: "5" - name: gateway-image newName: docker-registry.bullchina.net/order/gateway newTag: "11"
微服务配置参考:
apiVersion: apps/v1 kind: Deployment metadata: name: configuration spec: replicas: 1 selector: matchLabels: app: configuration template: metadata: labels: app: configuration spec: nodeSelector: "kubernetes.io/os": linux containers: - name: configuration image: configuration-image env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED value: "false" - name: JHIPSTER_CACHE_REDIS_CLUSTER value: "false" - name: EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE valueFrom: configMapKeyRef: name: common-configmap key: EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE - name: SPRING_CLOUD_CONFIG_URI valueFrom: configMapKeyRef: name: common-configmap key: SPRING_CLOUD_CONFIG_URI - name: SPRING_REDIS_HOST valueFrom: configMapKeyRef: name: common-configmap key: SPRING_REDIS_HOST - name: SPRING_REDIS_PORT valueFrom: configMapKeyRef: name: common-configmap key: SPRING_REDIS_PORT - name: SPRING_RABBITMQ_HOST valueFrom: configMapKeyRef: name: common-configmap key: SPRING_RABBITMQ_HOST - name: SPRING_RABBITMQ_PORT valueFrom: configMapKeyRef: name: common-configmap key: SPRING_RABBITMQ_PORT - name: JHIPSTER_REGISTRY_PASSWORD valueFrom: secretKeyRef: name: common-secret key: JHIPSTER_REGISTRY_PASSWORD - name: SPRING_REDIS_PASSWORD valueFrom: secretKeyRef: name: common-secret key: SPRING_REDIS_PASSWORD - name: SPRING_RABBITMQ_USERNAME valueFrom: secretKeyRef: name: common-secret key: SPRING_RABBITMQ_USERNAME - name: SPRING_RABBITMQ_PASSWORD valueFrom: secretKeyRef: name: common-secret key: SPRING_RABBITMQ_PASSWORD - name: SPRING_DATASOURCE_URL valueFrom: secretKeyRef: name: common-secret key: DB_CONFIGURATION_URL - name: SPRING_DATASOURCE_JDBC_URL valueFrom: secretKeyRef: name: common-secret key: DB_CONFIGURATION_URL - name: SPRING_DATASOURCE_USERNAME valueFrom: secretKeyRef: name: common-secret key: DB_CONFIGURATION_USERNAME - name: SPRING_DATASOURCE_PASSWORD valueFrom: secretKeyRef: name: common-secret key: DB_CONFIGURATION_PASSWORD resources: requests: cpu: 10m memory: 128Mi limits: cpu: "1" memory: 2Gi ports: - containerPort: 8082 name: http startupProbe: httpGet: path: /healthcheck port: 8082 initialDelaySeconds: 20 periodSeconds: 5 failureThreshold: 120 readinessProbe: httpGet: path: /healthcheck port: 8082 periodSeconds: 5 failureThreshold: 30 livenessProbe: httpGet: path: /healthcheck port: 8082 periodSeconds: 5 failureThreshold: 30 apiVersion: v1 kind: Service metadata: name: configuration spec: ports: - port: 8082 targetPort: 8082 name: http selector: app: configuration
3.5.Argo CD配置
3.5.1.创建命名空间
# case 项目名称,env 部署环境名称 kubectl create namespace app-{case}-{env} kubectl label namespace app-{case}-{env} istio-injection=enabled
3.5.2.创建项目

连接CD脚本仓库:



3.5.3.授权仓库和集群给项目



3.5.4创建APP



到此,完成CI/CD的配置,
3.5.5.访问域名配置
脚本位置:manifests/overlays/dev/network/gateway.yaml,virtual-service.yaml
apiVersion: networking.istio.io/v1 kind: VirtualService metadata: name: virtual-service spec: gateways: - app-order-dev-gateway hosts: - app-order-dev.k8s.local http: - match: - uri: prefix: / route: - destination: host: app-order-dev-gateway port: number: 8080
apiVersion: networking.istio.io/v1 kind: Gateway metadata: name: gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - app-order-dev.k8s.local
PS:需要将域名与IP映射加入到主机host文件,即可使用上面virtual-service.yaml中配置的hosts地址进行访问。


浙公网安备 33010602011771号