GitLab + Jenkins + Docker + Harbor + K8s 集群构建 CI/CD 平台

一、概述

本文介绍如何利用 GitLab、Jenkins、Docker、Harbor 以及 Kubernetes (K8s) 搭建一个完整的 CI/CD 自动化平台,实现从代码提交、自动构建、镜像推送到容器部署的一体化持续集成与持续交付流程。

该平台旨在通过自动化的流水线,提高软件交付效率、减少人工干预、提升系统的可靠性与可扩展性

二、CI/CD 概念

1. CI(持续集成)

持续集成是指开发人员频繁地将代码合并到主分支,并通过自动化构建和测试来验证代码质量的过程。
通过 Jenkins 等工具,系统可以自动完成代码编译、构建镜像等操作,从而:

  • 及时发现并修复集成问题;
  • 保证代码库的稳定性;
  • 提高版本迭代效率。

2. CD(持续交付 / 持续部署)

  • 持续交付:在持续集成的基础上,确保代码在满足上线条件后,可以随时部署到生产环境。
  • 持续部署:在持续交付的基础上,实现代码自动上线到生产环境。

通常使用 Kubernetes (K8s) 实现自动化部署、弹性伸缩与服务高可用。

三、系统架构流程

整个 CI/CD 平台的核心流程如下:

  1. 代码提交与版本管理
    开发人员通过 Git 将代码提交至 GitLab 仓库。项目管理员对代码进行审核与合并。
  2. 自动化构建与集成
    Jenkins 通过 Webhook 自动触发构建任务,从 GitLab 拉取最新代码并使用 Maven 等工具进行编译与打包。
  3. 镜像构建与推送
    构建成功后,Jenkins 使用 Docker 构建应用镜像,并将镜像推送(Push)到 Harbor 私有镜像仓库。
  4. 镜像部署与集群管理
    Kubernetes 集群 从 Harbor 拉取最新镜像,并根据部署编排文件(YAML/Helm)进行自动部署与滚动更新。
    K8s 同时负责服务的负载均衡、弹性伸缩及高可用性管理。

四、环境准备

windows内存32g,不然会很卡,可能卡得做不了

1.环境列表

角色 主机名 IP地址 备注

master master 192.168.163.11 4核4g

node1 node1 192.168.163.12 2核2g

node2 node2 192.168.163.13 2核2g

gitlab,jenkins jenkins 192.168.163.10 4核9g

harbor ubuntu 192.168.163.135 2核4g

软件 版本

linux ubuntu22.04

docker 28.3.3

k8s 1.29.1

harbor 2.13.2

jenkins 2.526

gitlab 15.11

2.前置基础操作(所有主机)

a.修改主机名
hostnamectl set-hostname master   ##其它主机同理
bash
b.关闭防火墙
sudo ufw disable
sudo ufw status
##若输出 `Status: inactive`,说明防火墙已成功关闭
c.关闭 Swap
sudo swapoff -a                                         # 临时关闭
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab      # 永久关闭
sudo swapon --show                                      # 查看是否已关闭
d.安装docker

#安装最新版本的Docker
sudo apt install docker-ce docker-ce-cli containerd.io
#查看Docker版本
sudo docker version
#查看Docker运行状态
sudo systemctl status docker

### 安装Docker 命令补全工具
sudo apt-get install bash-completion
sudo curl -L https://raw.githubusercontent.com/docker/docker-ce/master/components/cli/contrib/completion/bash/docker -o /etc/bash_completion.d/docker.sh
source /etc/bash_completion.d/docker.sh
##配置docker镜像源
vim /etc/docker/daemon.json

{
    "registry-mirrors": [
        "https://docker.m.daocloud.io"
  ]
} 

systemctl restart docker

3.部署k8s集群

3.1基础介绍

a.环境介绍 使用3台机器搭建一个1个master节点,2个node节点的集群 使用的操作系统为ubuntu:22.04 k8s版本为1.29 docker版本为28.3.3 docker-cri版本为0.4.0 角色 ip master 192.168.163.11 node1 192.168.163.12 node2 192.168.163.13 确保每台机器的时间一致,并且主机名不同

3.2修改基础配置

3.2.1检查当前时间和时区
timedatectl
##如果时区不对:
sudo timedatectl set-timezone Asia/Shanghai
3.2.2修改主机名
sudo hostnamectl set-hostname master
sudo hostnamectl set-hostname node1
sudo hostnamectl set-hostname node2

3.2.3修改本地解析文件 (三个都要修改)

vim /etc/hosts

重启系统或者服务使改动生效:

sudo systemctl restart systemd-logind.service

3.2.4关闭swap分区 之前操作过,就不重复操作了

3.2.5**Linux 内核模块 br_netfilter

##加载内核模块
sudo modprobe br_netfilter

##开启 bridge-nf-call-iptables
sudo sysctl -w net.bridge.bridge-nf-call-iptables=1
sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=1

##永久生效
sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system

##验证是否生效
cat /proc/sys/net/bridge/bridge-nf-call-iptables
输出应该是 `1`

3.3安装cri-docker

3.3.1.简介介绍
从 **Kubernetes 1.24** 开始,**kubelet 移除了对 Docker 的直接支持(dockershim)**。  
这意味着:
- 以前 kubelet 可以直接通过 Docker 运行容器;
- 现在必须通过 **一个中间层 (CRI 接口实现)**,即 **cri-dockerd**。
所以要让 Kubernetes 继续使用 Docker,就必须安装 **cri-dockerd**。
3.3.2.下载安装cri-docerk

网址: Release v0.4.0 · Mirantis/cri-dockerd · GitHub

下载后将其解压复制到/home/skcheng下(自己的家目录,或root目录,自己根据情况修改)
tar -zxvf cri-dockerd-0.4.0.amd64.tgz
install -o root -g root -m 0755 /home/skcheng/cri-dockerd/cri-dockerd /usr/bin/cri-dockerd
cd /usr/bin
ls | grep cri

检查是否安装成功
cri-dockerd --version
3.3.3.创建 systemd 服务与 socket 文件

创建文件/etc/systemd/system/cri-docker.service​

[Unit]
Description=CRI Interface for Docker Application Container Engine
Documentation=[https://docs.mirantis.com](https://docs.mirantis.com) After=network-online.target firewalld.service docker.service
Wants=network-online.target
Requires=cri-docker.socket

[Service]
Type=notify
ExecStart=/usr/bin/cri-dockerd --container-runtime-endpoint fd:// --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.9
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60s
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
Delegate=yes
KillMode=process

[Install]
WantedBy=multi-user.target

创建文件/etc/systemd/system/cri-docker.socket

[Unit]
Description=CRI Docker Socket for the API
PartOf=cri-docker.service

[Socket]
ListenStream=%t/cri-dockerd.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
启动cri-docker
systemctl daemon-reload
systemctl enable --now cri-docker.socket

3.4k8s初始化

kubeadm:用于初始化集群 kubelet:在集群中每个节点上用来启动pod和容器 kubectl:与集群通信的命令行工具

3.4.1.使用国内源安装
apt-get update && apt-get install -y apt-transport-https
curl -fsSL https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.29/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.29/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl  # 锁定版本,apt upgrade时不会更新
3.4.2.master节点配置kubeadm

创建文件kubeadm_init.yaml

apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:

- groups:
  - system:bootstrappers:kubeadm:default-node-token
    token: abcdef.0123456789abcdef
    ttl: 24h0m0s
    usages:
  - signing
  - authentication
    kind: InitConfiguration
    localAPIEndpoint:
    advertiseAddress: 192.168.163.11 ##根据自己IP修改 master节点ip
    bindPort: 6443
    nodeRegistration:
    criSocket: unix:///var/run/cri-dockerd.sock
    imagePullPolicy: IfNotPresent
    taints: null

---

apiServer:
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: 1.29.1
networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/12
  podSubnet: 10.244.0.0/16
scheduler: {}
下载镜像
kubeadm config images pull --config ./kubeadm_init.yaml

初始化K8S集群
kubeadm init --config ./kubeadm_init.yaml

复制config文件到指定目录
 mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

3.5配置集群网络

3.5.1.配置最简单的Flannel网络

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

运行命令检查coreDNS pod是否处于Running状态 (我等了四五分钟)

kubectl get pods -A

3.5.2.node节点加入集群
sudo kubeadm join 192.168.163.11:6443 \
  --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:a7fbe0387f422c2c0a95798eafe4298e7379fb135e735023dbf3f0307ed727eb \
  --cri-socket unix:///var/run/cri-dockerd.sock

执行命令查看集群中所有的节点

kubectl get nodes

3.6安装K8S组件

有4个组件需要安装 helm:类似apt那样的包管理工具 dashboard:仪表盘,更直观的查看k8s中的资源情况 nginx ingress:整个集群的出口,相当于最集群前端的nginx meatllb:让服务的LoadBalancer可以绑定局域网ip

3.6.1.helm

GitHub

下载后上传到服务器,解压复制到/usr/bin/目录下

tar xvf helm-v3.17.4-linux-amd64.tar.gz mv linux-amd64/helm /usr/bin/

3.6.2.Dabshboard

7.0之后的版本只能使用helm安装

添加存储库
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/

安装或者升级dashboard
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard

创建服务账户 新建文件dashboard-adminuser.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:

- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
应用到集群
kubectl apply -f dashboard-adminuser.yaml

创建令牌,用于页面登录
kubectl -n kubernetes-dashboard create token admin-user
复制输出的内容,这个就是登录要用到的令牌
3.6.3.ingress

GitHub – kubernetes/ingress-nginx

下载配置文件

wget -O nginx_ingress.yaml https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml

替换其中用到的镜像

提取一下镜像版本
controller_v=`grep image: nginx_ingress.yaml|grep controller|uniq|grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+'`
webhook_v=`grep image: nginx_ingress.yaml|grep webhook|uniq|grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+'`

更改为阿里云镜像

sed -i "s|registry.k8s.io/ingress-nginx/controller:v.*|registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v$(echo $controller_v)|" nginx_ingress.yaml
sed -i "s|registry.k8s.io/ingress-nginx/kube-webhook-certgen:v.*|registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v$(echo $webhook_v)|" nginx_ingress.yaml
应用到集群
kubectl apply -f nginx_ingress.yaml

查看pod启动状态
kubectl get pod -n ingress-nginx

2个状态为Completed,1个状态为Running即为成功,如下

3.6.4.Meatllb
##使用helm进行安装
##添加repo源
helm repo add metallb [MetalLB | metallb](https://metallb.github.io/metallb)
##安装
helm install metallb metallb/metallb

等待pod准备就绪

创建配置meatllb.yaml

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
spec:
  addresses:

- 192.168.163.200-192.168.163.240 

---

apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example

应用到集群

kubectl apply -f meatllb.yaml

4个组件安装完毕

4.Harbor镜像仓库安装

4.1下载harbor离线包

网址: Releases · goharbor/harbor · GitHub

上传文件到/opt/
tar -xzvf harbor-offline-installer-v2.13.2.tgz
cd harbor/

4.2下载docker-compose并执行操作

网址: Releases · docker/compose · GitHub

上传到/opt/harbor
##修改名称
mv docker-compose-linux-x86_64 docker-compose

##给执行权限
chmod +x ./docker-compose

##移动到PATH目录下
mv docker-compose /usr/local/bin

##测试
docker-compose -v
在/opt/harbor目录下
复制出.yml文件
cp harbor.yml.tmpl harbor.yml
修改文件harbor.yml
vim harbor.yml

修改ip,注释https

执行安装脚本

./install.sh

访问 192.168.163.135:80

admin

Harbor12345

修改harbor docker启动参数

vim /lib/systemd/system/docker.service

ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H fd:// --containerd=/run/containerd/containerd.sock

4.3.设置harbor开机启动

vim /etc/systemd/system/harbor.service

[Unit]
Description=Harbor Service
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/harbor
ExecStart=/usr/local/bin/docker-compose -f /opt/harbor/docker-compose.yml up -d
ExecStop=/usr/local/bin/docker-compose -f /opt/harbor/docker-compose.yml down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target

systemctl restart harbor systemctl status harbor systemctl enable harbor

5.Gitlab

5.1拉取镜像

##更新软件源
sudo apt update
##拉取官方社区版 GitLab 镜像
sudo docker pull gitlab/gitlab-ce:latest

5.2创建持久化目录

sudo mkdir -p /srv/gitlab/{config,logs,data}
sudo chown -R 1000:1000 /srv/gitlab
##`/srv/gitlab` 是 GitLab 官方推荐路径;  
##`1000:1000` 是容器中 `git` 用户的 UID/GID,确保读写权限正常。

5.3创建并运行 GitLab 容器

sudo docker run -d \
  --name gitlab \
  --hostname 192.168.163.10 \
  -p 80:80 -p 443:443 -p 2222:22 \
  --restart unless-stopped \
  -v /srv/gitlab/config:/etc/gitlab \
  -v /srv/gitlab/logs:/var/log/gitlab \
  -v /srv/gitlab/data:/var/opt/gitlab \
  gitlab/gitlab-ce:latest

##Web 界面端口(必须为 80,否则 push 时可能出错)

5.4修改配置文件

vim /srv/gitlab/config/gitlab.rb

##修改或添加以下内容:
external_url 'http://192.168.163.10'          # 你的服务器 IP 或域名
gitlab_rails['gitlab_shell_ssh_port'] = 2222   # 与上面 docker 端口对应
##保存并重启容器
sudo docker restart gitlab

http://192.168.163.10/

首次访问会提示你设置 root 用户密码。

如果密码设置的没有满足一定的复杂性,则会报500,需要从新设置

完成密码设置,完成登录

5.5生成密钥

##容器里密钥
##进入gitlab容器
docker exec -it gitlab /bin/sh

##生成密钥
ssh-keygen

##查看密钥
cat ~/.ssh/id_rsa.pub

填写修改信息

exit
#退出容器

6.jenlins安装

6.1镜像拉取

docker pull jenkins/jenkins:latest

6.2### 创建共享卷,修改所属组和用户,和容器里相同

mkdir /jenkins
chown 1000:1000 /jenkins

6.3创建 jenkins 容器

docker run -dit -p 8080:8080 -p 50000:50000 --name jenkins  --privileged=true --restart=always -v /jenkins:/var/jenkins_home jenkins/jenkins:latest
docker ps | grep jenkins

访问jenkins  http://192.168.163.10:8080

#再关闭网页和容器,修改配置
docker stop jenkins

6.4 配置jenkins环境

更换国内清华大学镜像,Jenkins下载插件特别慢,更换国内的清华源的镜像地址会快不少

如果下载失败也可以用其它源,比如华为源,阿里源等问ai或搜索引擎可以找到

6.4.1.修改 Jenkins 插件更新中心地址
##查看当前更新中心配置文件
cat /jenkins/hudson.model.UpdateCenter.xml

##替换官方源为清华镜像源
sed -i 's#updates.jenkins.io/update-center.json#mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json#g' /jenkins/hudson.model.UpdateCenter.xml

##验证修改结果
cat /jenkins/hudson.model.UpdateCenter.xml
6.4.2.替换连接检测地址
##Jenkins 默认使用 `https://www.google.com/` 来检测网络连通性,可以替换为百度。

##安装 jq 工具(用于解析 JSON)
yum install -y jq

##查看当前连接检测地址
cat /jenkins/updates/default.json | jq '.connectionCheckUrl'

##修改为百度检测地址
sed -i 's#https://www.google.com/#https://www.baidu.com/#g' /jenkins/updates/default.json

##查看修改结果
cat /jenkins/updates/default.json | jq '.connectionCheckUrl'
6.4.3.启动 Jenkins 容器并获取管理员初始密码
##启动 Jenkins 容器
docker start jenkins

##查看 Jenkins 初始管理员密码
cat /jenkins/secrets/initialAdminPassword
6.4.4.修改 Docker 服务参数
Jenkins 需要能够通过 TCP 远程访问 Docker 守护进程,因此需要修改 Docker 的启动参数。

vim /lib/systemd/system/docker.service
原来:ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
改后:ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2376 -H fd:// --containerd=/run/containerd/containerd.sock

重载并重启 Docker 服务
systemctl daemon-reload
systemctl restart docker

6.5配置Jenkins 安装插件

6.5.1选择推荐安装

jenkins中文设置

在插件下载处下载两个插件(参考下面的docker插件下载)

6.5.2安装和配置docker插件

依此点击Manage Jenkins->Manage Plugins->AVAILABLE->Search 搜索docker、docker-build-step

  以上docker配置是为了Build / Publish Docker Image时使用,定义了用那个服务器的docker执行docker命令

6.5.3.jenkins 安全设置

后面 gitlab 要和 jenkins 进行联动,所以必须要需要对 jenkins 的安全做一些设置,依次点击 系统管理-全局安全配置-授权策略,勾选”匿名用户具有可读权限”

6.5.4.配置 jenkins容器到其他服务的ssh免密登录
##在jenkins容器所在服务器进入容器
docker exec -it jenkins bash

##生产密钥
ssh-keygen -t rsa

##将公钥复制到jenkins容器所在服务器
ssh-copy-id root@192.168.163.10

##将公钥复制到k8s主节点服务器
ssh-copy-id root@192.168.163.11

#测试ssh免密
ssh root@192.168.163.11 

##退出
exit
6.5.5.CI服务器的docker配置( jenkins:192.168.163.10)

因为我们要在192.168.163.10(CI服务器)上push镜像到192.168.163.135(私仓),所有需要修改CI服务器上的Docker配置。

vim /etc/docker/daemon.json

"insecure-registries": [ "192.168.163.135:80" ], "registry-mirrors": [ "https://registry.docker-cn.com", "https://docker.m.daocloud.io", "https://mirror.ccs.tencentyun.com", "https://hub-mirror.c.163.com" ]

##加载使其生效
systemctl daemon-reload
systemctl restart docker

##登录测试一下
docker login 192.168.163.135
6.5.6.CD服务器的docker配置(k8s集 三台机器都要)

vim /etc/docker/daemon.json

"insecure-registries": [
          "192.168.163.135:80"
  ],
  "registry-mirrors": [
    "https://registry.docker-cn.com",
    "https://docker.m.daocloud.io",
    "https://mirror.ccs.tencentyun.com",
    "https://hub-mirror.c.163.com"
  ]
##加载使其生效
systemctl daemon-reload
systemctl restart docker

##登录测试一下
docker login 192.168.163.135

五、项目实践

1.添加前后端代码

由于个人原因,代码将在之后添加上,这里我大致说一下我的项目代码怎么来的。

我们可以在淘宝购买一个java或其它语言的项目,但是一定要前后端分离的,10元左右。

购买之后我们先在windows是部署出来,能够在win上运行之后,我们使用idea(java)修改代码,主要是修改连接处的代码,比如后端的数据库连接,大部分修改代码都是localhost和数据库名字账号密码,搜索localhost再修改这部分代码为之后的数据库虚拟机IP就可以了。

前端修改多一点,都是大致思路是一样的,搜索localhost,把localhost部分修改为后续k8s主机IP就可以了,但是有时候也有特殊情况,有前后端的基础知识不容易出错,实在不知道的,就可以借助ai,搜索localhost把这个文件都复制给ai,告诉他,我准备在k8s中部署这个项目,这里的localhost部分要怎么修改?同时还要注意k8s端口问题,要提前规划好nodeport端口,我这里是31072,原来项目的后端端口是8888.

这里是比较容易出错的,同时也是后面的一切的基础,所以这里细心一点

这里用一个图例帮助理解

下面举几个例子:

1.1.后端代码修改:

src/main/resources/application.yml

注意我这里只改了数据库mysql,redis的IP,原本的8888端口没有修改,是作为pod端口,而31072是作外部连接端口

把原来写死的 localhost 改成了 虚拟机 IP(192.168.163.11),并保证账号、密码、端口一致

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 原来写法
      url: jdbc:mysql://localhost:3306/db_bs?serverTimezone=GMT%2b8&useSSL=false
      username: root
      password: 123456
修改后
url: jdbc:mysql://192.168.163.11:3306/db_bs?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=true
username: skc
password: 123456

1.2.前端代码修改:

第一种修改直接修改端口IP

src/store/index.js

改前

state: { baseApi: "http://localhost:8888", }

改后

state: { baseApi: "http://192.168.163.11:31072", // 后端 NodePort }

第二种修改为相对路径

改动前(节选)

import request from '../utils/request';
...
request.post("http://localhost:8888/role").then(res => {
  // 处理角色逻辑
  ...
});

改动后(节选)

import request from '../utils/request';
...
request.post("/role").then(res => {
  // 处理角色逻辑
  ...
});

这里为什么有两种方法,这里其实是代码的问题,买来的代码可能没有那么规范,这里的相对路径,如果规范写的话,本来一开始就不会有IP存在,一开始就应该是相对路径,所以代码质量也有影响到我们修改

1.3.上传代码到虚拟机

##上传前端代码到虚拟机root目录下:
scp -r /d/ddkc/web root@192.168.163.11:/root/

##上传后端代码:
scp -r /d/ddkc/server root@192.168.163.11:/root/

##修改文件权限
chmod -R 755 /root/web /root/server

2.k8s主机的准备

2.1准备镜像仓库凭据(Harbor)

在 K8s 集群中创建拉取镜像的 Secret(记住替换用户名/密码)

kubectl create secret docker-registry harbor-creds  
--docker-server=192.168.163.135  
--docker-username=admin  
--docker-password='<Harbor密码>'  
--docker-email='you@example.com'  
-n default

后续 Deployment 里通过 imagePullSecrets: [{name: harbor-creds}] 使用

后续有很多镜像拉取,我选择的方式是先依次拉取镜像,再上传到harbor上,后续使用直接在harbor上拉取

以mysql8为例

##拉取镜像
docker pull mysql:8
docker pull redis:7-alpine
docker pull maven:3.8.6-openjdk-8
docker pull openjdk:8
docker pull node:16
docker pull nginx:latest
##打标签
docker tag mysql:8 192.168.163.135:80/library/mysql:8

##上传镜像
docker push 192.168.163.135:80/library/mysql:8

##拉取测试
docker pull 192.168.163.135:80/library/mysql:8

2.2.在k8s主机上部署数据库redis mysql

vim /opt/db/docker-compose.yml

services:                               # 定义要运行的一组服务
  mysql:
    image: 192.168.163.135/library/mysql:8
                                        # 使用你 Harbor 私库里的 MySQL 8 镜像
    container_name: mysql8              # 容器名字(可选,便于定位)
    environment:
      MYSQL_ROOT_PASSWORD: "123456"     # 初始化 root 密码(首次启动时生效)
      MYSQL_DATABASE: "db_bs"           # 首次启动自动创建数据库 db_bs(可选)
      MYSQL_USER: "skc"                 # 额外创建一个业务用户(可选)
      MYSQL_PASSWORD: "123456"          # 上面业务用户的密码(可选)
      TZ: "Asia/Shanghai"               # 容器时区,影响日志/时间函数
    ports:
      - "3306:3306"                     # 暴露到宿主机 3306 端口(外部可以直连)
    volumes:
      # 数据持久化目录(必须存在且有写权限),存放 ibdata/redo/binlog 等
      - /opt/db/mysql-data:/var/lib/mysql

      # 额外 MySQL 配置目录(把自定义 *.cnf 放到 /opt/db/mysql-conf/)
      # 例如 /opt/db/mysql-conf/my.cnf 会被自动加载
      - /opt/db/mysql-conf:/etc/mysql/conf.d

      # 初始化脚本目录:仅“首次初始化数据目录时”会执行
      # 放 .sql / .sql.gz / .sh:可建库建表、插入基础数据等
      - /opt/db/initdb:/docker-entrypoint-initdb.d
    restart: always                     # 容器意外退出后自动重启(随 Docker 启动)
                                        # 注意:如果配置错误导致起不来,会无限重启

  redis:
    image: 192.168.163.135/library/redis:7-alpine
                                        # 你的 Harbor 私库里的 Redis 7(Alpine 变体)
    container_name: redis               # 容器名字
    command: ["redis-server","--appendonly","yes"]
                                        # 开启 AOF 持久化(写安全性高于 RDB)
    ports:
      - "6379:6379"                     # 暴露到宿主机 6379 端口
    volumes:
      - /opt/db/redis-data:/data        # Redis 持久化目录(AOF/RDB 存在这里)
    environment:
      TZ: "Asia/Shanghai"               # 时区(影响日志时间戳)
    restart: always                     # 异常退出自动重启

同时初始化数据,把.sql文件放到/opt/db/init 里

这里会有一点问题,我们把之前在windows的数据库连接到Navicat,重新导出应该新的.sql文件,再放入/opt/db/init应该就没有问题了

cd /opt/db && docker compose up -d

3.构建并推送后端镜像

我们之前拉取了两个目录server 和 web,这里分别是后端和前端的意思,之后的文件创建应该都在这两目录里,前端文件就在前端里,后端同理

3.1.创建maven镜像源文件

vim mvn-settings.xml

##mvn-settings.xml 创建放到dockerfile同目录下

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
          https://maven.apache.org/xsd/settings-1.0.0.xsd">
  <mirrors>
    <!-- 阿里云公共仓库 -->
    <mirror>
      <id>aliyunmaven</id>
      <mirrorOf>*</mirrorOf>
      <name>Aliyun Maven</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror>
    <!-- 备用:华为云(需要时可打开) -->
    <!--
    <mirror>
      <id>huaweicloud</id>
      <mirrorOf>*</mirrorOf>
      <name>Huawei Cloud Maven</name>
      <url>https://repo.huaweicloud.com/repository/maven/</url>
    </mirror>
    -->
  </mirrors>
</settings>

3.2创建后端Dockerfile文件

vim Dockerfile

使用多阶段构建(容器内 Maven 打包,容器内运行):

server/Dockerfile

阶段1:用 Harbor 的 Maven+JDK8 打包
FROM 192.168.163.135/library/maven:3.8.6-openjdk-8 AS build
WORKDIR /src

可选:企业内网可准备 mvn-settings.xml 指向镜像源
COPY mvn-settings.xml /root/.m2/settings.xml

COPY pom.xml ./
RUN --mount=type=cache,target=/root/.m2/repository mvn -B -q -DskipTests dependency:go-offline
##拷贝pom.xml /root/.m2/repository 是容器目录(如果在虚拟机运行则是虚拟机目录)
##cache是声明是可缓存目录
COPY src ./src
RUN --mount=type=cache,target=/root/.m2/repository mvn -B -DskipTests package
##拷贝src源码 到容器./src目录 其他同上

阶段2:仅运行 JRE
FROM 192.168.163.135/library/openjdk:8
WORKDIR /app
COPY --from=build /src/target/*.jar /app/app.jar
##将打包好的镜像复制到 /app/app.jar 取名为app.jar
ENV JAVA_OPTS="-Xms256m -Xmx512m"
EXPOSE 8888
ENTRYPOINT ["sh","-c","java $JAVA_OPTS -jar /app/app.jar"]
构建并推送:
cd server
docker build -t 192.168.163.135/library/onlinemall-backend:1.0.1 .
docker push 192.168.163.135/library/onlinemall-backend:1.0.1

4.构建并推送前端镜像

4.1.创建nginx.conf配置文件(支持 SPA 刷新与缓存优化)

vim web/nginx.conf


# ---------------- 基础配置 ----------------
user  nginx;                       # 指定 Nginx 运行的用户(容器里一般就是 nginx)
worker_processes  auto;            # 自动根据 CPU 核心数启动 worker 进程,提升并发性能

events {
  worker_connections 1024;         # 每个 worker 允许同时打开的最大连接数(1024 个)
}

http {
  include       /etc/nginx/mime.types;   # 引入 MIME 类型映射表(比如 .html、.css、.js)
  default_type  application/octet-stream;# 默认 MIME 类型(二进制流)

  sendfile      on;                # 开启高效文件传输模式(零拷贝)
  tcp_nopush    on;                # 优化 TCP 数据包传输,减少网络开销
  keepalive_timeout  65;           # 长连接保持时间 65 秒,提升用户体验

  # ---------------- 压缩配置 ----------------
  gzip on;                         # 开启 gzip 压缩
  gzip_types text/plain application/javascript application/json text/css text/xml image/svg+xml;
                                   # 指定对哪些类型文件启用压缩,减小传输体积

  # ---------------- 虚拟主机配置 ----------------
  server {
    listen 80;                     # 监听 80 端口(HTTP 默认端口)
    server_name  _;                # 匹配所有域名(“_” 表示兜底规则)

    root /usr/share/nginx/html;    # 网站根目录(前端构建好的 dist/ 会复制到这里)
    index index.html;              # 默认首页文件

    # 1) 前端路由:处理 SPA 刷新/跳转 404 问题
    location / {
      try_files $uri $uri/ /index.html;
      # 含义:
      #   先检查请求的 URI 是否有对应的静态文件 ($uri)
      #   再检查是否是目录 ($uri/)
      #   如果都没有,就回退到 index.html(前端路由交给 Vue/React 处理)
      #
      # 举例:
      #   http://site/user/list -> 没有 user/list 文件
      #   Nginx 回退 index.html -> Vue Router 负责渲染对应页面
    }

    # 2) 静态资源缓存:为 js/css/img 等文件设置缓存
    location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
      expires 30d;                  # 设置缓存时间 30 天,减少重复请求
      access_log off;               # 静态资源不写 access 日志,减少磁盘 IO
      try_files $uri =404;          # 确保文件存在,否则返回 404
    }

    # 3) index.html 不缓存:避免发布新版本后还看到旧页面
    location = /index.html {
      add_header Cache-Control "no-cache, no-store, must-revalidate";
      # no-cache: 强制客户端向服务器验证缓存
      # no-store: 不允许缓存任何响应
      # must-revalidate: 缓存过期必须重新请求服务器
    }

    # 4) 健康检查接口(可选)
    location = /healthz {
      return 200 'ok';              # 返回 HTTP 200 状态码,内容是 "ok"
      add_header Content-Type text/plain;
      # 用于 K8s readinessProbe/livenessProbe 健康检查
      # 比如 Deployment 配置里探测路径 /healthz
    }
  }
}

4.2创建前端Dockerfile文件

web/Dockerfile

阶段1:Harbor 的 node:16 构建
FROM 192.168.163.135/library/node:16 AS build
WORKDIR /app #/app 容器目录 被当做工作目录
COPY package*.json ./ #复制虚拟机package*.json文件到容器当前目录
RUN npm install #安装依赖
COPY . . #复制虚拟机所有目录到容器app目录 因为之前app被设置为工作目录
RUN npm run build #打包 把源码编译为静态文件放到dist/目录下(还是容器目录)

阶段2:Harbor 的 nginx 托管静态资源
FROM 192.168.163.135/library/nginx:latest
COPY nginx.conf /etc/nginx/conf.d/default.conf #覆盖默认配置
COPY --from=build /app/dist/ /usr/share/nginx/html/  
##/app/dist 上个阶段的文件目录 /usr/share/nginx/html/ 这个阶段的目录 都是在容器内
EXPOSE 80
构建并推送:
cd web 
docker build -t 192.168.163.135/library/onlinemall-frontend:1.0.2 . 
docker push 192.168.163.135/library/onlinemall-frontend:1.0.2

5.准备持久化存储 (NFS,用于文件/头像上传)

5.1在 master 上搭 NFS Server

apt -y install nfs-kernel-server
mkdir -p /srv/nfs/onlinemall/{file,avatar}
chown -R nobody:nogroup /srv/nfs/onlinemall
chmod -R 0777 /srv/nfs/onlinemall

cat >> /etc/exports <<'EOF'
/srv/nfs/onlinemall *(rw,sync,no_subtree_check,no_root_squash)
EOF

exportfs -ra
systemctl restart nfs-kernel-server

5.2在各工作节点安装客户端并测试

apt -y install nfs-common
mkdir -p /mnt/test-nfs
mount -t nfs 192.168.163.11:/srv/nfs/onlinemall /mnt/test-nfs
ls -al /mnt/test-nfs
umount /mnt/test-nfs

5.3创建 PV / PVCyaml

server/pv-pvc-nfs.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
 name: onlinemall-pv
spec:
 capacity:
 storage: 50Gi
 accessModes: [ ReadWriteMany ]
 persistentVolumeReclaimPolicy: Retain
 nfs:
 server: 192.168.163.11
 path: /srv/nfs/onlinemall

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: onlinemall-pvc
spec:
 accessModes: [ ReadWriteMany ]
 resources:
 requests:
 storage: 50Gi
 volumeName: onlinemall-pv
kubectl apply -f pv-pvc-nfs.yaml
kubectl get pv,pvc
##看到 PVC 状态为 Bound 即正常

6.在k8s部署后端

server/backend.yml

---------------- Deployment:后端应用 ----------------

apiVersion: apps/v1              # API 版本,Deployment 属于 apps/v1
kind: Deployment                 # 资源类型:Deployment(无状态应用部署)
metadata:
  name: backend                  # Deployment 名字叫 backend
spec:
  replicas: 1                     # 副本数量=1,只运行一个 Pod
  selector:                       # Deployment 如何选择它要管理的 Pod
    matchLabels:                  # 匹配规则
      app: backend                # 匹配标签 app=backend 的 Pod
  template:                       # Pod 模板(Deployment 会用它来创建 Pod)
    metadata:
      labels:
        app: backend              # 给 Pod 打上标签 app=backend,方便 Service 选中
    spec:
      imagePullSecrets:           # 指定镜像拉取密钥(用于私有 Harbor 仓库)
        - name: harbor-creds      # Secret 名字 harbor-creds(存储登录 Harbor 的用户名密码)
      # 安全上下文配置
      securityContext:
        fsGroup: 0                # 给容器进程一个附加组 ID(0=root 组),
                                  # 常用于挂载 NFS 卷时避免权限问题
      containers:                 # 容器列表(一个 Pod 可以运行多个容器)
        - name: backend           # 容器名字叫 backend
          image: 192.168.163.135/library/onlinemall-backend:1.0.1
                                  # 使用 Harbor 上的后端镜像 onlinemall-backend:1.0.1
          ports:
            - containerPort: 8888 # 容器内部监听的端口(Spring Boot 通常是 8888)
          volumeMounts:           # 挂载持久存储卷(PVC)
            - name: uploads
              mountPath: /app/file
              subPath: file       # 把 PVC 中的 file 子目录挂到容器的 /app/file
            - name: uploads
              mountPath: /app/avatar
              subPath: avatar     # 把 PVC 中的 avatar 子目录挂到容器的 /app/avatar
      volumes:                    # 定义卷(Pod 使用的存储)
        - name: uploads
          persistentVolumeClaim:
            claimName: onlinemall-pvc
                                  # 使用 PVC:onlinemall-pvc
                                  # PVC 再去绑定 PV(通常是 NFS),保证数据持久化

---

---------------- Service:暴露后端应用 ----------------

apiVersion: v1                   # API 版本,Service 属于 core/v1
kind: Service                    # 资源类型:Service
metadata:
  name: backend                  # Service 名字叫 backend
spec:
  type: NodePort                 # Service 类型:NodePort,把服务暴露到每个节点的固定端口
  selector:
    app: backend                 # 选择标签 app=backend 的 Pod(即上面 Deployment 创建的 Pod)
  ports:
    - port: 8888                 # Service 在集群内部的逻辑端口(集群内访问用)
      targetPort: 8888           # Pod 容器实际监听的端口(必须和 containerPort 对应)
      nodePort: 31072            # 节点对外暴露的端口(集群外访问用)
                                  # 外部访问方式:http://<任意节点IP>:31072
                                  # 例如:http://192.168.163.11:31072
部署并检查:

kubectl apply -f backend.yml
kubectl get pods -l app=backend -o wide
kubectl get svc backend
kubectl get endpoints backend
kubectl logs -f deploy/backend

7.在 K8s 部署前端

web/frontend.yml

---------------- Deployment:前端应用 ----------------

apiVersion: apps/v1              # API 版本,Deployment 属于 apps/v1
kind: Deployment                 # 资源类型:Deployment(无状态应用部署)
metadata:
  name: frontend                 # Deployment 名字叫 frontend
spec:
  replicas: 2                    # 副本数量=2,表示只运行2个 Pod
  selector:                       # Deployment 如何选择它要管理的 Pod
    matchLabels:                  # 匹配规则
      app: frontend               # 必须匹配标签 app=frontend 的 Pod
  template:                       # Pod 模板(Deployment 根据它创建 Pod)
    metadata:
      labels:
        app: frontend             # 给 Pod 打上标签 app=frontend,方便 Service 选中
    spec:
      imagePullSecrets:           # 指定镜像拉取密钥(私有仓库)
        - name: harbor-creds      # Secret 名字是 harbor-creds,用于拉取 Harbor 镜像
      containers:                 # 容器列表(一个 Pod 可以运行多个容器)
        - name: frontend          # 容器的名字叫 frontend
          image: 192.168.163.135/library/onlinemall-frontend:1.0.3
                                  # 容器镜像,从 Harbor 仓库拉取 onlinemall-frontend:1.0.3
          ports:
            - containerPort: 80   # 容器内部监听端口,Nginx 默认是 80

---

---------------- Service:暴露前端应用 ----------------

apiVersion: v1                   # API 版本,Service 属于 core/v1
kind: Service                    # 资源类型:Service
metadata:
  name: frontend                 # Service 名字叫 frontend
spec:
  type: NodePort                 # Service 类型:NodePort,把服务暴露到每个节点的固定端口
  selector:
    app: frontend                # 选择标签 app=frontend 的 Pod(即上面 Deployment 创建的 Pod)
  ports:
    - port: 80                   # Service 在集群内部暴露的端口(集群内访问用)
      targetPort: 80             # Pod 容器实际监听的端口(必须和 containerPort 对应)
      nodePort: 30081            # 节点对外暴露的端口(集群外访问用)
                                  # 外部访问方式:http://<任意节点IP>:30081
                                  # 例如:http://192.168.163.11:30081

部署并访问:

kubectl apply -f frontend.yml kubectl get svc frontend

浏览器打开:http://192.168.163.11:30081

网站可以正常运行即成功

8.gitlab创建项目,配置cicd

8.1配置master 到gitlab ssh免密

8.2gitlab创建项目上传目录

创建两个仓库web 和 server

以web为例:

cd /web
git init
git add .
git commit -m "init web"
git branch -M main
git remote add origin ssh://git@192.168.163.10:222/root/web.git
git push -u origin main

8.3jenkins创建流水线并编写脚本

创建两个流水线 web 和 server

以web为例:

前端脚本

pipeline {
  agent any                               // 在任意可用的 Jenkins 节点上跑本流水线(非远程构建/部署机)

  environment {
    // =============== 代码仓库 ===============
    GIT_URL    = 'http://192.168.163.10/root/web.git'   // 前端项目 GitLab 仓库地址

    // =============== Harbor 镜像仓库 ===============
    HARBOR     = '192.168.163.135'       // Harbor 注册表地址(无协议)
    PROJECT    = 'library'               // Harbor 项目命名空间
    IMAGE_NAME = 'onlinemall-frontend'   // 镜像名
    REPO       = "${HARBOR}/${PROJECT}/${IMAGE_NAME}"   // 完整 repo 前缀(不含 tag)

    // =============== 远程构建机(docker build 发生在这里) ===============
    BUILD_HOST = '192.168.163.10'        // 远程构建机 IP(有 Docker 能力)
    BUILD_USER = 'root'                   // SSH 登录用户名(需要 docker 权限)

    // =============== 远程部署机(kubectl 在这台机子上执行) ===============
    DEPLOY_HOST = '192.168.163.11'       // 远程部署机 IP(已配置好 kubectl 上下文)
    DEPLOY_USER = 'root'                  // SSH 登录用户名

    // =============== Kubernetes 资源定位 ===============
    NAMESPACE = 'default'                // 要更新的命名空间
    DEPLOY    = 'frontend'               // 要更新的 Deployment 名
    CONTAINER = 'frontend'               // Deployment 内的容器名(set image 用)

  }

  // ====== 全局流水线选项 ======
  options {
    timestamps()                         // 控制台日志带时间戳
    disableConcurrentBuilds()            // 禁止同一 Job 并发构建(避免互相覆盖)
    buildDiscarder(logRotator(numToKeepStr: '20')) // 只保留最近 20 次构建记录/制品
  }

  stages {

    stage('Checkout') {                  // 1) 拉取源码
      steps {
        // 用凭据拉 Git(Jenkins 系统凭据里要有 'gitlab-creds')
        git url: env.GIT_URL, branch: 'main', credentialsId: 'gitlab-creds'
      }
    }

    stage('Set Vars') {                  // 2) 生成镜像 tag(含短 SHA + 构建号)
      steps {
        script {
          def shortSha = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
          env.TAG   = "${shortSha}-b${env.BUILD_NUMBER}" // 如:a1b2c3d-b42
          env.IMAGE = "${env.REPO}:${env.TAG}"           // 完整镜像:H/P/N:tag
          echo "TAG=${env.TAG}"
          echo "IMAGE=${env.IMAGE}"
        }
      }
    }

    stage('Build & Push on Remote (via SSH)') {   // 3) 通过 SSH 在“构建机”上 build & push
      steps {
        // Harbor 登录凭据(Jenkins 系统凭据里要有 'harbor-creds':用户名+密码)
        withCredentials([usernamePassword(credentialsId: 'harbor-creds',
                                          usernameVariable: 'HUSER',
                                          passwordVariable: 'HPASS')]) {
          sh '''
            set -e
            # 3.1 远程 Docker 登录 Harbor(密码通过管道传递,避免明文)
            ssh -o StrictHostKeyChecking=no ${BUILD_USER}@${BUILD_HOST} \
              "echo '${HPASS}' | docker login ${HARBOR} -u '${HUSER}' --password-stdin"

            # 3.2 打包当前工作区并通过管道传给远端;在远端执行 docker build -
            #     -t 打两个 tag:唯一 tag 和 latest;随后 push 两个 tag
            tar -C "$PWD" -cz . \
            | ssh -o StrictHostKeyChecking=no ${BUILD_USER}@${BUILD_HOST} "
                docker build -t '${IMAGE}' -t '${REPO}:latest' - && \
                docker push '${IMAGE}' && \
                docker push '${REPO}:latest'
              "
          '''
        }
      }
    }

    stage('Deploy (remote kubectl)') {   // 4) 远程在“部署机”上用 kubectl 滚动更新
      steps {
        sh '''
          set -e
          ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} \
            "kubectl -n ${NAMESPACE} set image deployment/${DEPLOY} ${CONTAINER}=${IMAGE} --record && \
             kubectl -n ${NAMESPACE} rollout status deployment/${DEPLOY} --timeout=180s"
          # --record 会在 deployment annotation 记录本次 set image(方便审计/回滚)
          # rollout status 等待滚动更新完成,180s 超时可按镜像体积/副本数调整
        '''
      }
    }

    stage('Smoke Test') {                // 5) 冒烟测试(简单可用性检查)
      steps {
        // 访问前端健康检查,非 2xx 会导致阶段失败
        sh 'curl -sf http://192.168.163.11:30081/healthz'
        // 若你前端没有 /healthz,可改为首页 200 检查:
        // sh 'curl -sSfI http://192.168.163.11:30081/ | grep "200 OK"'
      }
    }

  }

  post {
    success {
      echo '✅ 前端发布成功'
    }
    failure {                           // 任一阶段失败,采集部署机侧的诊断信息
      echo '❌ 前端发布失败,采集诊断'
      sh '''
        set +e
        ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} \
          "kubectl -n ${NAMESPACE} describe deploy ${DEPLOY} || true; \
           kubectl -n ${NAMESPACE} get pods -l app=${DEPLOY} -o wide || true; \
           kubectl -n ${NAMESPACE} get events --sort-by=.lastTimestamp | tail -n 80 || true"
      '''
    }
    always {
      cleanWs()                         // 无论成功失败都清理 Jenkins 工作区
    }
  }
}

后端脚本

pipeline {
  agent any                               // 在任意可用的 Jenkins 节点上执行本流水线

  options { timestamps() }                // 控制台输出带时间戳,便于排查

  environment {
    REGISTRY   = '192.168.163.135'        // Harbor 注册表地址(不含协议)
    REPO       = 'library/onlinemall-backend' // Harbor 项目/仓库路径
    BUILD_HOST = '192.168.163.10'         // 远程“构建机”:执行 docker build/push 的主机
    KCTL_HOST  = '192.168.163.11'         // 远程“部署机”:执行 kubectl 的主机
    NAMESPACE  = 'default'                // 目标命名空间
    DEPLOY     = 'backend'                // 目标 Deployment 名称;也被当作容器名使用(见 set image)
    SVC_PORT   = '31072'                  // 后端 NodePort(冒烟测试要访问它)
  }

  stages {

    stage('Checkout') {                   // 1) 拉取后端代码
      steps {
        git branch: 'main',               // 拉取 main 分支(若仓库是 master,请改成 master)
            url: 'http://192.168.163.10/root/server.git', // GitLab 仓库地址
            credentialsId: 'gitlab-creds' // Jenkins 凭据:能访问 GitLab 的帐号/Token
      }
    }

    stage('Set Vars') {                   // 2) 生成镜像 TAG 与完整镜像名
      steps {
        script {
          // 取当前提交的短 SHA,作为版本标识的一部分
          def shortSha = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
          env.TAG   = "${shortSha}-b${env.BUILD_NUMBER}"    // 形如 a1b2c3d-b42
          env.IMAGE = "${REGISTRY}/${REPO}:${TAG}"          // 完整镜像名:REGISTRY/REPO:TAG
          echo "TAG=${env.TAG}"
          echo "IMAGE=${env.IMAGE}"
        }
      }
    }

    stage('Build & Push on Remote (via SSH)') { // 3) 通过 SSH 在构建机上 build & push 镜像
      steps {
        // 读取 Harbor 的用户名/密码到环境变量 HUSER/HPASS
        withCredentials([usernamePassword(credentialsId: 'harbor-creds', usernameVariable: 'HUSER', passwordVariable: 'HPASS')]) {
          sh '''
            set -eu
            # 3.1 在构建机上 docker login(用 --password-stdin 避免明文出现在命令行)
            ssh -o StrictHostKeyChecking=no root@${BUILD_HOST} "echo '${HPASS}' | docker login ${REGISTRY} -u '${HUSER}' --password-stdin"

            # 3.2 将 Jenkins 工作区打包,经由管道送到构建机的 docker build -
            #     同时打两个标签:唯一 TAG 与 latest;随后推送两者
            tar -C "${WORKSPACE}" -cz . | ssh -o StrictHostKeyChecking=no root@${BUILD_HOST} "
              set -e
              docker build -t '${IMAGE}' -t '${REGISTRY}/${REPO}:latest' - && \
              docker push '${IMAGE}' && \
              docker push '${REGISTRY}/${REPO}:latest'
            "
          '''
        }
      }
    }

    // (按你的要求已移除镜像校验阶段)

    stage('Deploy (remote kubectl)') {    // 4) 通过 SSH 在部署机上 kubectl 滚动更新
      steps {
        sh '''
          set -eu
          ssh -o StrictHostKeyChecking=no root@${KCTL_HOST} "
            # 将 Deployment 中名为 \${DEPLOY} 的容器镜像,更新为刚构建好的 \${IMAGE}
            # 这里容器名 == backend(与 DEPLOY 一致);若实际容器名不同,请改右侧键名
            kubectl -n ${NAMESPACE} set image deployment/${DEPLOY} ${DEPLOY}='${IMAGE}' --record=false && \
            # 等待滚动发布完成(超时 240s 可按镜像大小/副本数调整)
            kubectl -n ${NAMESPACE} rollout status deployment/${DEPLOY} --timeout=240s
          "
        '''
      }
    }

    stage('Smoke Test (tolerant)') {      // 5) 冒烟测试(容错:200 通过;401 且带登录提示也放行)
      steps {
        sh '''
          set +e
          URL="http://${KCTL_HOST}:${SVC_PORT}/actuator/health"  # Spring Boot 健康检查;若不是此路径请改
          code=$(curl -s -o /tmp/resp.txt -w "%{http_code}" "$URL")
          echo "HTTP_CODE=$code"
          head -c 300 /tmp/resp.txt || true; echo

          if [ "$code" = "200" ]; then
            echo "Smoke OK: 200"
            exit 0
          fi

          # 如果网关/鉴权在健康检查 URL 上拦截,可能返回 401;带关键字则容忍通过
          if [ "$code" = "401" ] && grep -Eqi 'token|未登录|失效' /tmp/resp.txt; then
            echo "Smoke Tolerated: 401 with auth guard text"
            exit 0
          fi

          echo "Smoke WARN: 非预期状态,但不阻塞发布"
          exit 0
        '''
      }
    }

  }

  post {
    always {
      cleanWs()                           // 无论成功失败都清理 Jenkins 工作区
    }
    failure {                             // 任一阶段失败:收集部署侧诊断信息
      echo '❌ 后端发布失败,采集诊断'
      sh '''
        set +e
        ssh -o StrictHostKeyChecking=no root@${KCTL_HOST} "
          kubectl -n ${NAMESPACE} describe deploy/${DEPLOY} || true;         # 查看 Deployment 详细与事件
          kubectl -n ${NAMESPACE} get pods -l app=${DEPLOY} -o wide || true; # 列出相关 Pod
          kubectl -n ${NAMESPACE} get events --sort-by=.lastTimestamp | tail -n 80 || true  # 近期事件
        "
      '''
    }
  }
}

点击测试构建

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇