一、配置 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