miketwais

work up

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 构建取消!"
}
}
}
}
View Code

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
    """
}
View Code

微服务中POM.xml需要调整打包方式:

<from>
<image>docker-registry.***.net/adoptopenjdk:11-jre-hotspot</image>
</from>
<to>
<image>app-order:latest</image>
</to>
View Code

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"
View Code

微服务配置参考:

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
View Code

3.5.Argo CD配置

3.5.1.创建命名空间

# case 项目名称,env 部署环境名称
kubectl create namespace app-{case}-{env}
kubectl label namespace app-{case}-{env} istio-injection=enabled
View Code

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
View Code
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
View Code

PS:需要将域名与IP映射加入到主机host文件,即可使用上面virtual-service.yaml中配置的hosts地址进行访问。

 

posted @ 2024-11-26 10:39  MasonZhang  阅读(99)  评论(0)    收藏  举报