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 中查看日志