Gitlab + Jenkins 实现 CICD

一、配置 Gitlab

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

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

1、新建项目 HelloWorld,地址:http://8.146.228.212/root/helloworld.git
2、点击 管理员 - 设置 - 网络
3、勾选 允许来自 webhooks 和集成对本地网络的请求,然后保存更改
作用:代码推送后可以触发自动化通知
4、选择项目,点击设置,点击 Webhooks
5、配置 Webhooks

URL 和 Secret 令牌从 Jenkins 获取,如何获取下面配置 Jenkins 有截图

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

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

URL 从 Jenkins 获取,如何获取下面配置 Jenkins 有截图

(三)两种方式比较

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

(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

四、部署 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 = "crpi-btptlf3rm0734il2.cn-wulanchabu.personal.cr.aliyuncs.com"  // 私有Docker仓库地址
        IMAGE_NAME = "zhoulinglongqwe/nginx"      // --镜像名称
        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: '93b5afc9-e485-489c-9a46-bf279eea8c46', 
                    url: 'http://192.168.0.28/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: '9141e7fa-676d-47dc-bffc-98ea55f4d8e0',
                    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} --insecure-skip-tls-verify=true"
                          
                            // 2. 执行部署
                            //sh """
                                 
                            //    kubectl set image deployment/${K8S_DEPLOYMENT_NAME} *=${DOCKER_REGISTRY}/${IMAGE_NAME}:${TIMESTAMP} --record  --insecure-skip-tls-verify=true
                            //    kubectl rollout status deployment/${K8S_DEPLOYMENT_NAME} --timeout=5m --insecure-skip-tls-verify=true 
                            //"""
                            sh ''' sed  -e "s/{{TIMESTAMP}}/${TIMESTAMP}/g" helloworld.yaml | kubectl apply  -f - --insecure-skip-tls-verify=true'''
                            
                            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} --insecure-skip-tls-verify=true 
                                kubectl rollout status deployment/${K8S_DEPLOYMENT_NAME} --timeout=3m --insecure-skip-tls-verify=true
                            """
                            
                            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: 1
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
        - name: helloworld
          image: crpi-btptlf3rm0734il2.cn-wulanchabu.personal.cr.aliyuncs.com/zhoulinglongqwe/nginx:{{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
相关推荐
難釋懷1 小时前
Nginx测试工具charles
运维·nginx·php
云飞云共享云桌面1 小时前
东莞制造业研发降本方案:1 台云主机承载 10 人 SolidWorks,钣金操作秒响应
linux·运维·服务器·安全·电脑
Mark White1 小时前
一次 Ubuntu 内核升级翻车的运维记录:从 Kernel Panic 到锁定 6.14 内核
运维·ubuntu
修炼室1 小时前
告别天天变动的随机端口!基于 Tailscale 子网路由(Subnet Router)外网原生直连学院服务器及安装踩坑指南
运维·服务器
sbjdhjd1 小时前
企业级 Tomcat (上):WEB 技术栈 + 架构演进 + 生产级安装部署
linux·运维·云原生·开源·tomcat·云计算·负载均衡
JAMSAN09301 小时前
AI服务器MLCC:从“电子大米”到“算力石油”的价值重估
运维·人工智能·数据分析·智能硬件
华纳云IDC服务商1 小时前
高防服务器清洗流量导致丢包怎么办?
运维·服务器·网络
一直跑1 小时前
codex服务器运用(服务器上不了外网chatgpt)
运维·服务器·chatgpt
草莓熊Lotso1 小时前
【Linux网络】深入理解 TCP 协议(一):报头设计与可靠性基石
linux·运维·服务器·c语言·网络·c++·tcp/ip