Gitlab + Jenkins 实现 CICD

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

一 、前期准备

1、部署 Gitlab

点击查看安装教程

2、部署 Jenkins

点击查看安装教程

3、部署 Docker Harbor

点击查看安装教程

4、部署 Kubernetes

点击查看安装教程

5、服务器配置

服务IP角色
master192.168.31.110集群管理节点
node1192.168.31.111集群工作节点
node2192.168.31.112集群工作节点
harbor192.168.31.113镜像仓库
gitlab192.168.31.114代码托管
jenkins192.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 定义的流程来跑自动化构建 。

在这里插入图片描述

5、jenkins 执行权限

(1)如果执行 docker 操作,需要给 jenkins 用户添加 docker 执行权限

1、直接在服务器上安装的 jenkins

# 检查 Jenkins 用户所属组
id jenkins
# 将 Jenkins 用户添加到 docker 用户组
sudo usermod -aG docker jenkins
# 重启 Jenkins 服务
sudo systemctl restart jenkins
# 重启  Docker 服务:
sudo systemctl restart docker

# 以 jenkins 用户身份执行 shell,然后执行 docker ps,可以查看 jenkins 是否有权限执行 docker ps
sudo -u jenkins bash
docker ps

2、通过 docker 安装的 jenkins

# 获取宿主机的 docker 组 GID
getent group docker | cut -d: -f3

# 在宿主机运行以下命令,确认 UID 1000 的用户是否存在:
id -nu 1000  # 应返回 "jenkins" 或其他用户名
# 如果不存在,需先创建用户并指定 UID:
sudo useradd -u 1000 -m jenkins
# 将 UID 1000 的用户加入 docker 组
sudo usermod -aG docker $(id -nu 1000)

# 查看 jenkins 是否有执行 docker ps 的权限
[root@jenkins ~]# docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED              STATUS              PORTS                                                                                          NAMES
9acb30a66cc7   jenkins/jenkins:lts   "/usr/bin/tini -- /u…"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp, 0.0.0.0:50000->50000/tcp, [::]:50000->50000/tcp   jenkins
[root@jenkins ~]# docker exec -it 9acb30a66cc7 bash
jenkins@9acb30a66cc7:/$ docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS         PORTS                                                                                          NAMES
9acb30a66cc7   jenkins/jenkins:lts   "/usr/bin/tini -- /u…"   2 minutes ago   Up 2 minutes   0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp, 0.0.0.0:50000->50000/tcp, [::]:50000->50000/tcp   jenkins

(2)ssh 执行权限(可选)

上文中是通过添加 kubeconfig 文件,作为 jenkins 访问 k8s 的凭据。还有一种方式是通过 ssh 连接,生成公钥,然后将公钥拷贝到目标服务器。

1、直接在服务器上安装的 jenkins

# 在 Jenkins 服务器上以 jenkins 用户生成密钥:
sudo -u jenkins ssh-keygen -t ed25519 -f /var/lib/jenkins/.ssh/id_ed25519
# 查看公钥内容
sudo -u jenkins cat /var/lib/jenkins/.ssh/id_ed25519.pub
# 将公钥拷贝到目标服务器
sudo -u jenkins ssh-copy-id -i /var/lib/jenkins/.ssh/id_ed25519.pub root@192.168.31.110

2、通过 docker 安装的 jenkins

# 1. 在 Jenkins 容器内生成 SSH 密钥,发布到其它服务器的时候可以用 ssh 连接
# 进入 Jenkins 容器
docker exec -it jenkins bash
# 生成 SSH 密钥对
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""  # 无密码
# 2. 将公钥复制到目标服务器
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@target-server

四、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 中查看日志
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值