Gitlab + Jenkins 实现 CICD

发布于:2025-06-10 ⋅ 阅读:(13) ⋅ 点赞:(0)

CICD 是持续集成(Continuous Integration, CI)和持续交付/部署(Continuous Delivery/Deployment, CD)的缩写,是现代软件开发中的一种自动化流程实践。下面介绍 Web 项目如何在代码提交到 Gitlab 后,自动发布到 Kubernetes 集群中。

一 、前期准备

1、部署 Gitlab

点击查看安装教程

2、部署 Jenkins

点击查看安装教程

3、部署 Docker Harbor

点击查看安装教程

4、部署 Kubernetes

点击查看安装教程

5、服务器配置

服务 IP 角色
master 192.168.31.110 集群管理节点
node1 192.168.31.111 集群工作节点
node2 192.168.31.112 集群工作节点
harbor 192.168.31.113 镜像仓库
gitlab 192.168.31.114 代码托管
jenkins 192.168.31.115 自动化构建与部署

二、配置 Gitlab

Gitlab 中主要是配置 webhooks,作用是接收到代码推送后触发 Jenkins 任务。有两种方式,下面是配置过程截图:

(一)方式一:使用 Webhooks 触发 Jenkins(传统方式)

1、新建项目 HelloWorld,地址:http://192.168.31.114/root/helloworld.git

在这里插入图片描述

2、点击 管理员 - 设置 - 网络

在这里插入图片描述

3、勾选 允许来自 webhooks 和集成对本地网络的请求,然后保存更改

作用:代码推送后可以触发自动化通知

在这里插入图片描述

4、选择项目,点击设置,点击 Webhooks

在这里插入图片描述

5、配置 Webhooks

URL 和 Secret 令牌从 Jenkins 获取,如何获取下面配置 Jenkins 有截图
在这里插入图片描述

(二)方式二:使用 GitLab 的 Jenkins 集成(推荐方式)

1、新建项目,在项目设置中点击集成,然后添加 Jenkins 集成模块

在这里插入图片描述

2、Jenkins 集成模块配置

URL 从 Jenkins 获取,如何获取下面配置 Jenkins 有截图
在这里插入图片描述

(三)两种方式比较

特性 Webhooks 手动配置 GitLab Jenkins 插件
配置难度 较高 简单
灵活性 中等
维护成本
支持的功能 完全自定义 包括 MR、PR、状态反馈等
是否需要插件 是(如 Generic Webhook Trigger) 是(GitLab Plugin)
安全性 需手动配置 Secret 支持 Token 验证
多项目支持 需手动逐一配置 可集中管理
GitLab 回调支持 需要自己开发 内置支持

GitLab 提供的 Jenkins 集成模块本质上也是基于 Webhook 实现的,但封装更易用和安全。但使用 Webhooks 触发 Jenkins 的传统方式依然会经常使用,尤其适用于:自建的服务,第三方不支持 GitLab 内置集成的系统,需要高度定制化的场景。只是 GitLab 建议你在“有内置集成可用”的情况下,优先使用集成,因为它们更可靠、更易于维护。

三、配置 Jenkins

1、添加全局凭据

在这里插入图片描述

2、新建任务

在这里插入图片描述

3、Triggers(触发器)配置

需要安装插件 Gitlab,在系统管理 - 插件管理 - Available plugins 中搜索并安装

(1)勾选 “Build when a change is pushed to GitLab” 配置项,目的是实现 GitLab 与 Jenkins 之间的自动化触发关联

在这里插入图片描述

(2)点开高级 - 选择 Filter branches by name,作用是让你能精确控制哪些 Git 分支的代码变更可以触发当前 Jenkins 任务(这里是 “helloworld” 任务 )的构建 。

在这里插入图片描述

4、流水线配置

(1)关联 GitLab 代码仓库

需要在 Jenkins 服务器(192.168.31.115)上安装 Git

dnf install git -y

在这里插入图片描述

(2)设置 Jenkins 从 Git 仓库的 main 分支拉取代码,并用仓库里的 Jenkinsfile 定义的流程来跑自动化构建 。

在这里插入图片描述

四、DockerHarbor,新建项目 helloworld

在这里插入图片描述

五、部署 web 项目

1、web 项目内容

(1)index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebView Test</title>
</head>
<body>
    <h1>Hello World!</h1>
</body>
</html>
(2)Dockerfile 文件
# 使用官方的 Nginx 镜像作为基础镜像
FROM nginx:latest

# 将当前目录下的所有文件复制到 Nginx 容器的默认网页根目录(/usr/share/nginx/html)
COPY . /usr/share/nginx/html

# 暴露 Nginx 服务的默认端口 80
EXPOSE 80
(3)Jenkinsfile 文件
pipeline {
    agent any // 在任何可用代理上运行此流水线

    environment {
        // Docker 镜像配置
        DOCKER_REGISTRY = "harbor.yiyang.com:443"  // 私有Docker仓库地址
        IMAGE_NAME = "helloworld/helloworld"      // 镜像名称
        TIMESTAMP = "${new Date().format('yyyyMMdd_HHmmss', TimeZone.getTimeZone('Asia/Shanghai'))}"  // 带时区的时间戳
        IMAGE_TAG_LATEST = "latest"              // 最新标签
        
        // Kubernetes 配置
        K8S_DEPLOYMENT_NAME = "helloworld-deployment"  // 必须与 helloworld.yaml 中的 Deployment 名称一致
        K8S_CONFIG_FILE = "helloworld.yaml"            // Kubernetes部署文件
    }

    stages {
        // 代码检出
        stage('Checkout') {
            steps {
                git credentialsId: '9ba151c2-08ec-4e25-a38c-4b322c40e2bf', 
                    url: 'http://192.168.31.114/root/helloworld.git', 
                    branch: 'main'
            }
        }

        // 构建Docker镜像
        stage('Build Docker Image') {
            steps {
                sh """
                    docker build --pull --no-cache --network none \
                        -t ${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP} .
                    docker tag ${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP} \
                        ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG_LATEST}
                """
            }
        }
        
        // 登录Docker仓库
        stage('Login to Docker Harbor') {
            steps {
                withCredentials([usernamePassword(
                    credentialsId: '61e24bcf-39b1-42b3-8815-080502c30a53',
                    usernameVariable: 'DOCKER_USER',
                    passwordVariable: 'DOCKER_PASSWORD'
                )]) {
                    sh '''
                        echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USER}" --password-stdin ${DOCKER_REGISTRY}
                    '''
                }
            }
        }
        
        // 推送Docker镜像
        stage('Push Docker Image') {
            steps {
                sh """
                    docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP}
                    docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG_LATEST}
                """
            }
        }
        
        // 部署到Kubernetes
        stage('Deploy to Kubernetes') {
            steps {
                script {
                    withCredentials([file(credentialsId: '34fffd5d-e5f4-465d-b3ff-205929444c95', variable: 'KUBECONFIG')]) {
                        try {
                            // 1. 预检查
                            sh "kubectl apply --dry-run=client -f ${K8S_CONFIG_FILE}"
                            
                            // 2. 执行部署
                            sh """
                                kubectl set image deployment/${K8S_DEPLOYMENT_NAME} *=${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP} --record
                                kubectl rollout status deployment/${K8S_DEPLOYMENT_NAME} --timeout=5m
                            """
                            
                            echo "Deployment succeeded for image: ${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP}"
                        } catch (err) {
                            // 3. 失败时自动回滚
                            echo "Deployment failed! Error: ${err}"
                            echo "Initiating rollback..."
                            
                            sh """
                                kubectl rollout undo deployment/${K8S_DEPLOYMENT_NAME}
                                kubectl rollout status deployment/${K8S_DEPLOYMENT_NAME} --timeout=3m
                            """
                            
                            error "Deployment failed and was rolled back. Original error: ${err}"
                        }
                    }
                }
            }
        }
    }

    // 后置处理
    post {
        // 无论成功失败都执行的步骤
        always {
            // 镜像清理
             sh "docker rmi -f ${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP} || true"
            // 可选:清理悬空镜像
            sh "docker image prune -f || true"
        }
        // 仅当流水线成功时执行的步骤
        success {
            echo "Pipeline succeeded! Image: ${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP}"
        }
        // 仅当流水线失败时执行的步骤
        failure {
            echo "Pipeline failed. Check logs for details."
        }
    }
}
(4)helloworld.yaml
---   
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
        - name: helloworld
          image: cicd.ddzhixu.com:443/helloworld/helloworld:{{TIMESTAMP}}
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          imagePullPolicy: IfNotPresent
          env:
            - name: ENVIRONMENT
              value: "production"
      imagePullSecrets:
        - name: cicd  // Docker Harbor 镜像仓库认证的 Secret 名称

---
# Service
apiVersion: v1
kind: Service
metadata:
  name: helloworld-service
spec:
  type: NodePort
  selector:
    app: helloworld
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30001
      
---
# Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: helloworld-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: helloworld-deployment
  minReplicas: 1
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70

2、测试

将 web 项目上传到 Gitlab 仓库之后,会触发 Jenkins 执行 web 项目下 Jenkinsfile 中定义的一系列任务 ,然后浏览器访问 http://192.168.31.111:30001 或 http://192.168.31.112:30001

Jenkins 执行成功或者失败,可以在 Jenkins 中查看日志
在这里插入图片描述