K8s 集群部署微服务 - DevOps(二)
但是一些系统服务,是所有命名空间可以共享的,在新建的企业空间无法修改,也无法看到这些服务,如需修改,需要再默认企业空间中进行修改配置。在部署服务之前,需要新建企业空间,不能在默认的企业空间进行部署,因为默认的企业空间是不能使用 DevOps 流水线的,想要 DevOps,必须要在新建的企业空间上进行。安装的过程中需要拉取 jenkins 镜像,由于拉取比较慢,可能回到导致 UI 界面提示安装失败了
K8s 集群部署微服务 - DevOps(二)
| K8s 集群环境搭建 - yaml 版本(一) |
|---|
| K8s 集群部署中间件 - yaml 版本(二) |
| K8s 集群部署微服务 - yaml 版本(三) |
| K8s 集群部署微服务 - DevOps(一) |
| K8s 集群部署微服务 - DevOps(二) |
文章目录
一、安装 DevOps
-
根据官方文档,需要创建新的企业空间,默认的企业空间不支持 DevOps。

-
创建之后添加我们的集群进该企业空间:

-
在扩展程中心安装 DevOps,点击安装即可。
-
安装的过程中需要绑定 pvc 和 pv,所需要我们的k8s有默认的 stroageClass。
-
安装的过程中需要拉取 jenkins 镜像,由于拉取比较慢,可能回到导致 UI 界面提示安装失败了,但其实还在拉取的过程中,需要拉取成功后,进行卸载 DevOps,在重新安装 DevOps。
-
如果出现错误: Internal error occurred: failed calling webhook “reverseproxies.extensions.kubesphere.io”,执行如下命令即可:
kubectl delete validatingwebhookconfiguration extensions.kubesphere.io
-
-
安装成功后:

-
进去我们自定义的企业空间发现有 DevOps 功能了,到这就安装完成了。

二、编写后端流水线
-
在部署服务之前,需要新建企业空间,不能在默认的企业空间进行部署,因为默认的企业空间是不能使用 DevOps 流水线的,想要 DevOps,必须要在新建的企业空间上进行。在 KubeSphere中,企业空间比命名空间更高层次的隔离单位。隔离关系如下:
├── 企业空间A(workspace-a) │ ├── 项目1(namespace-a1) │ │ ├── MySQL服务 │ │ └── 其他资源 │ └── 项目2(namespace-a2) └── 企业空间B(workspace-b) # 👈 新创建的企业空间 ├── 项目1(namespace-b1) # 这里看不到企业空间A的MySQL └── 项目2(namespace-b2)在这里插入代码片 -
在新建的企业空间中,kubeSphere 上是看不到其他企业空间的任何服务的。但是一些系统服务,是所有命名空间可以共享的,在新建的企业空间无法修改,也无法看到这些服务,如需修改,需要再默认企业空间中进行修改配置。比如上篇文章中配置的服务:ks-apiserver 和 ks-controller-manager。
-
在新建的企业空间中,创建流水线:DevOps -> 流水线 -> 点击创建:

-
创建流水线之后,需要进行编辑,我这里的流水线分为以下几步:
- 从 gitee 拉取代码;
- 将拉取的代码进行项目编译;
- 编译好之后构建镜像;
- 推送进行到已经部署好的 harbor;
- 使用镜像部署服务;
-
当然你还可以添加额外的步骤,比如单元测试、质量检测等。
1. 拉取代码
-
我用的 gitee 作为代码仓库。首先
开通我们的项目支持 svn 拉取代码,因为我们创建的流水线,默认就是使用的 svn 拉取代码,用 https 会报错。
-
在 kubeSphere 中创建 gitee 的凭证:用户名密码。用于从 gitee 拉取代码的凭证。

-
创建代码仓库,添加代码地址,记得填写 svn 的地址,还要关联上我们上面创建的凭证。

-
接下来编辑我们流水线的第一步:拉取代码
- 代理选择:none
- 任务第一步,关联上我们上面创建的代码仓库拉取代码。
- 任务第二步:添加 shell 脚本,在拉取代码结束后打印出 “拉取代码成功”。
- 任务第三步:添加了第二个 shell 脚本,打印我们拉取的项目的信息。

-
编辑好第一步之后,先点击运行,测试下第一步是否有问题:

-
成功运行了第一步,效果如下:

-
日志如下:

2. 项目编译
-
项目进行编译的时候,maven 会下载项目依赖。但在 kubeSphere 中,默认是没有配置镜像地址的,所以需要配置依赖地址。
-
在 【集群管理】中,选择我们需要修改的集群,在菜单 【配置】- 【配置字典】- 命名空间【kubesphere-devops-worker】,找到服务名:ks-devops-agent,修改改配置,添加镜像地址:

-
项目编译是在容器中进行的,我的 kubeSphere 版本 v4.2.0-community,提供的容器有 base 和 jnlp,其中 base 就是提供了 java 环境的容器。但是使用的是 jdk-21,而我需要部署的项目使用的 jdk 1.8。所以需要添加新的容器。
-
【集群】- 选择我们的集群 - 【配置】 - 【配置字段】 - 命名空间【kubesphere-devops-system】- 配置名【jenkins-casc-config】:

-
在 jenkins_user.yaml: templates 下添加我们需要的容器:base-jdk8
- name: "base-jdk8" image: "swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/kubesphere/builder-maven:v4.1.4-podman" command: "cat" args: "" ttyEnabled: true privileged: false resourceRequestCpu: "100m" resourceLimitCpu: "4000m" resourceRequestMemory: "100Mi" resourceLimitMemory: "8192Mi"
-
该容器来自渡渡鸟的容器镜像站,如果需要其他版本的 jdk,也可搜索满足需求的容器。当然也可以自己制作容器,放入私有的镜像仓库中。
-
需要注意的是,我们需要知道我们使用的镜像的maven环境变量,知道maven的setting文件在容器的位置。然后将该文件挂载到上面我们配置的 ks-devops-agent 上,从而让 ks-devops-agent 的 mirrors 仓库生效。需要再 jenkins_user.yaml 下配置挂载:
- name: "base-jdk8" resources: requests: ephemeral-storage: "1Gi" limits: ephemeral-storage: "10Gi" volumeMounts: - name: config-volume mountPath: /apache-maven-3.5.3/conf/settings.xml subPath: settings.xml
-
如下是容器 base-jdk8 的maven环境变量,对应上面的配置:

-
编辑流水线:指定容器名为:base-jdk8,并添加命令:mvn clean package

-
运行测试成功:

3. 构建镜像
-
在项目中编写 Dockerfile 文件,并推送到仓库中,流水线需要用该 Dockerfile 进行构建镜像。项目结构如下:与 src 齐平

-
dockerfile 内容如下:
# 基础镜像:和你的 JDK 版本匹配(JDK 8 用 8-jre-slim,轻量且安全) FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/openjdk:8-jre-slim # 避免中文乱码 ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 # 创建非 root 用户(安全最佳实践,避免容器用 root 运行) RUN groupadd --system --gid 1001 appgroup && \ useradd --system --uid 1001 --gid 1001 appuser # 创建 Nacos 配置目录(新增) RUN mkdir -p /home/appuser/nacos/config && \ mkdir -p /home/appuser/nacos/naming && \ chown -R appuser:appgroup /home/appuser # 工作目录(容器内目录,统一路径) WORKDIR /opt/mortal/app # 复制授权服务的 Jar 包到容器(注意:Jar 包名称要和 target/ 下的一致!) # 如果你想简化 Jar 包名称,可在复制时重命名为 app.jar(推荐,避免版本号变更导致 Dockerfile 修改) # [当前系统相对路径] [容器内绝对路径] COPY mortal-gateway/target/mortal-gateway-1.0-SNAPSHOT.jar /opt/mortal/app/mortal-gateway.jar # 给非 root 用户授权(避免启动时权限不足) RUN chown -R appuser:appgroup /opt/mortal/app # 切换到非 root 用户运行 USER 1001 # 暴露服务端口(和授权服务的 server.port 一致,比如 2001) EXPOSE 1888 # 启动命令(exec 格式,支持优雅停机) ENTRYPOINT ["java", "-jar", "mortal-gateway.jar"]- 需要注意我们选择的 jdk 版本必须和 运行容器的主机架构一直才行,不然会报错。
- mortal-gateway/target/mortal-gateway-1.0-SNAPSHOT.jar:该路径需要根据你编译后的 jar 位置来决定的。
特别解释下:这里为什么要创建 Nacos 配置目录呢,因为我这里k8s在部署项目时,会加载 nacos 的配置中心的配置,加载后 k8s 会生成缓存,就是在创建的该目录下进行缓存,如果没有该目录,会报错:no such directory
-
将 Dockerfile 文件推送到远程仓库,在我们运行流水线的第一步会将他拉去下来。

-
接下来就是使用 docker build 进行打包了。但是前提是需要观察我们使用的 base-jdk8 镜像中是否安装了docker-cli,是否启动了 docker 守护进程,如果没有启动docker 守护进程,是无法使用docker build 的。通过观察发现,使用了 podman 代替了 docker:

-
podman 对比 docker:
- 无守护进程:Podman 不需要运行 root 权限的守护进程。
- rootless 容器:可以在普通用户权限下运行容器,更安全。
- 没有特权提升:减少了安全风险。
-
所以我们需要使用 podman build 代替 docker build。使用 podman 还需要解决下权限问题,不然会报错如下:

-
在 jenkins_user.yaml 添加如下三处配置:
envVars: - envVar: key: "USER" value: "root" - envVar: key: "HOME" value: "/root" --- securityContext: runAsUser: 0 runAsGroup: 0 allowPrivilegeEscalation: true --- securityContext: fsGroup: 0 -
效果如下:


-
配置修改后大概两分钟后生效,接下来编写构建镜像流水线:
BASE_VERSION="1.0" # BUILD_NUMBER 是 KubeSphere/Jenkins 内置变量,每次构建自动+1 IMAGE_TAG="${BASE_VERSION}-build${BUILD_NUMBER}" # 制作镜像 podman build --tls-verify=false -f mortal-gateway/Dockerfile -t mortal.harbor.com/mortal-system/mortal-gateway:${IMAGE_TAG} . -
注意:这里每次构建镜像名称都需要进行更新,和版本号一样,不能构建的镜像名称一成不变,因为如果这样,后续在部署服务时,由于你的部署文件yaml毫无变化,是不会触发pod重启的。我这里用了 jenkins 的内置变量,后续在推送进行和拉取镜像同样用该值。该值不同的流水线之间不共享,每次触发流水线则值+ 1。
-
测试运行成功:

-
其他的服务也是按照上面的步骤来:在流水线中点击【添加并行阶段】即可:并行可以加快构建速度

-
添加其他服务:

- 测试成功:

-
其他优化:
- 用于 Dockerfile 中的 jdk镜像,上传至私有库 harbor中,这样可以加快拉取速度。该方式之前叙说过多次,便不再赘述。
- 每次任务的构建都是通过 Agent ,每次构建创建的 pod 都是临时的,构建完成后就被销毁,下次构建又是全新的 Pod,所以本地永远没有缓存。导致每次构建又要重新拉取 jdk 镜像。
-
查看日志如下,每次都是 copying,没有使用到缓存,服务多的情况下,每个服务都需要 copying,非常慢和浪费资源:

-
修改 jenkins-casc-config ,添加镜像路径的挂载:
- hostPathVolume: hostPath: "/var/data/jenkins_podman_cache" mountPath: "/var/lib/containers"
-
其中 hostPath 是宿主机的路径,montPah 是 容器 base-jdk8 的容器路径。
-
hostPath:该路径宿主机如果没有的话,需要提前创建好:
sudo mkdir -p /var/data/jenkins_podman_cache sudo chmod 777 /var/data/jenkins_podman_cache -
montPah:该值需要确定我们使用的容器中,镜像的路径地址,比如我用的base-jdk8 信息如下:

-
-
配置好之后再次启动流水线测试效果如下:发现成功使用了缓存,构建速度行得到了大大的提升。从 17.35min 提升到了 58.43 s,速度提升巨大。

4. 推送镜像
-
推送镜像需要如下4步:
-
指定容器,依旧是我们前面使用的 base-jdk8。
-
添加 harbor 凭证:harbor 的用户名和密码。
-
使用 harbor 凭证进行登录。
podman login --tls-verify=false -u $HARBOR_USER -p $HARBOR_PASSWD mortal.harbor.com -
推送镜像:
BASE_VERSION="1.0" # BUILD_NUMBER 是 KubeSphere/Jenkins 内置变量,每次构建自动+1 IMAGE_TAG="${BASE_VERSION}-build${BUILD_NUMBER}" podman push --tls-verify=false mortal.harbor.com/mortal-system/mortal-gateway:${IMAGE_TAG}
-
-
如上边成功推送了一个服务的镜像,其他服务的进行,点击【添加并行阶段】,和上述进行一样的设置,修改下镜像名称即可。
5. 部署服务
-
部署服务分为以下几步:
- 创建流水线访问 k8s 集群的凭证。
- 添加该凭证。
- 执行部署服务的脚本。
-
创建凭证:选择类型为 kubeconfig,会自动带出内容,点击确定创建。

-
在流水线里添加步骤,然后添加我们刚刚创建的凭证,变量起名为:
KUBECONFIG_CONTENT,该变量下一步执行脚本需要用到。
-
执行服务。
BASE_VERSION="1.0" # BUILD_NUMBER 是 KubeSphere/Jenkins 内置变量,每次构建自动+1 IMAGE_TAG="${BASE_VERSION}-build${BUILD_NUMBER}" # 替换 deploy.yaml 中的镜像名 sed -i "s/1.0-SNAPSHOT/${IMAGE_TAG}/g" mortal-gateway/deploy/deploy.yaml mkdir -p ~/.kube echo "$KUBECONFIG_CONTENT" > ~/.kube/config envsubst < mortal-gateway/deploy/deploy.yaml | kubectl apply -f -- 由于我们deploy.yaml 部署文件中的镜像名称是固定的:1.0-SNAPSHOT,所以需要进行下替换:sed -i “s/1.0-SNAPSHOT/${IMAGE_TAG}/g” mortal-gateway/deploy/deploy.yaml
- KUBECONFIG_CONTENT 为上面添加凭证的变量名。
- mortal-gateway/deploy/deploy.yaml:为我们服务的 deploy.yaml 的位置。如果不清楚,可以先打印 ls 看下目录结构。
-
我们的服务:deploy 位置

-
deploy.yaml 内容如下,集合了 deployment,service,pvc,ingress 等
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway-service # 部署名称
namespace: mortal-system # 建议创建专属命名空间,也可改为你的现有命名空间
labels:
app: gateway-service
spec:
replicas: 1 # 副本数,可根据需求调整
selector:
matchLabels:
app: gateway-service
template:
metadata:
labels:
app: gateway-service
spec:
containers:
- name: gateway-service
# 镜像地址(harbor中的目标镜像)
image: mortal.harbor.com/mortal-system/mortal-gateway:1.0-SNAPSHOT
imagePullPolicy: Always # 镜像拉取策略:本地无则拉取
# 容器端口(对应微服务的启动端口,如8080、9999等,需与服务配置一致)
ports:
- containerPort: 8888
# 环境变量配置(连接nacos、mysql,根据你的实际配置修改值)
env:
# 1. 微服务名称(对应 Nacos DataID 前缀)
- name: SPRING_APPLICATION_NAME
value: "mortal-gateway-service"
# 2. 激活 prod 环境
- name: SPRING_PROFILES_ACTIVE
value: "prod" # 激活prod环境配置
# 3. Nacos 配置中心地址 + 命名空间
- name: SPRING_CLOUD_NACOS_CONFIG_SERVER-ADDR
value: "nacos-headless:8848"
- name: SPRING_CLOUD_NACOS_CONFIG_NAMESPACE
value: "aa104d54-9e4b-470b-9662-31ab4aedefb4"
- name: SPRING_CLOUD_NACOS_CONFIG_FILE-EXTENSION
value: "yaml"
# 4. Nacos 注册中心地址 + 命名空间(和配置中心一致)
- name: SPRING_CLOUD_NACOS_DISCOVERY_SERVER-ADDR
value: "nacos-headless:8848"
- name: SPRING_DATASOURCE_URL
value: "jdbc:mysql://mysql-headless:3306/mortal?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true" # mysql地址(k8s中mysql的service名称)
- name: SPRING_DATASOURCE_USERNAME
value: "root" # mysql用户名
- name: SPRING_DATASOURCE_PASSWORD
value: "root" # mysql密码
# 资源限制(根据集群资源调整)
resources:
limits:
cpu: "1"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"
# 健康检查(可选,建议配置)
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8888
initialDelaySeconds: 60 # 启动后延迟60秒检查
periodSeconds: 10 # 每10秒检查一次
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8888
initialDelaySeconds: 30
periodSeconds: 5
# 挂载动态PVC(与volumeClaimTemplates名称对应)
volumeMounts:
- name: gateway-log-volume # 必须与volumeClaimTemplates.name一致
mountPath: /app/logs # 容器内持久化目录
volumes: # 新增volumes字段引用PVC
- name: gateway-log-volume
persistentVolumeClaim:
claimName: gateway-log-pvc # 对应上面创建的PVC名称
- name: nacos-snapshot-dir
emptyDir: { }
---
# Service配置(暴露微服务,供内部集群访问)
apiVersion: v1
kind: Service
metadata:
name: gateway-service
namespace: mortal-system
spec:
selector:
app: gateway-service
type: ClusterIP # 集群内部访问,如需外部访问可改为NodePort或LoadBalancer
ports:
- port: 8888 # Service端口
targetPort: 8888 # 容器端口(与Deployment中containerPort一致)
---
# gateway-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gateway-log-pvc
namespace: mortal-system
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "nfs-sc"
resources:
requests:
storage: 2Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gateway-ingress
namespace: mortal-system
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
# 1. 禁用 HTTPS 重定向(核心)
nginx.ingress.kubernetes.io/ssl-redirect: "false"
# 2. 禁用强制 SSL 重定向(覆盖全局)
nginx.ingress.kubernetes.io/force-ssl-redirect: "false"
# 3. 禁用永久重定向(针对 308 状态码)
nginx.ingress.kubernetes.io/permanent-redirect-code: "307"
# 4. 明确指定后端服务用 HTTP 协议(避免默认 HTTPS 转发)
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
# 5. 其他原有注解保留
nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
spec:
ingressClassName: nginx
rules:
# 无域名规则(直接用 IP 访问)
- http:
paths:
- path: /mortal-gateway/(.*)
pathType: ImplementationSpecific
backend:
service:
name: gateway-service
port:
number: 8888
# 域名规则(可选,保留)
- host: gateway.mortal.com
http:
paths:
- path: /mortal-gateway/(.*)
pathType: ImplementationSpecific
backend:
service:
name: gateway-service
port:
number: 8888
-
其中环境变量配置 env对应如下:

server: port: 8888 servlet: context-path: /mortal-gateway spring: application: name: mortal-gateway-service cloud: nacos: discovery: enabled: true #如果不想使用 Nacos 进行服务注册与发现,设置为false即可 server-addr: 14.103.138.45:30848 namespace: aa104d54-9e4b-470b-9662-31ab4aedefb4 username: ${NACOS_USERNAME:nacos} password: ${NACOS_PASSWORD:nacos} metadata: management: context-path: ${server.servlet.context-path}/actuator gateway: # 网关全局跨域配置 globalcors: cors-configurations: '[/**]': allowedOrigins: "*" allowedMethods: "*" allowedHeaders: "*" allowCredentials: true # 解决options请求被拦截的问题 add-to-simple-url-handler-mapping: true # 这个地方独立配置,是网关的数据,代码 GatewayConfig.java 中读取被监听 nacos: gateway: route: config: data-id: mortal-gateway-router group: prod # Spring Boot Actuator 健康检查配置(核心,对应K8s的liveness/readiness探针) management: endpoints: web: exposure: include: health # 暴露health端点(如需更多如info、metrics可追加,用逗号分隔) base-path: /actuator # Actuator基础路径,对应K8s探针路径的/actuator endpoint: health: enabled: true # 启用健康检查端点 show-details: always # 显示健康检查详情(生产环境可改为when_authorized) health: livenessState: enabled: true # 适配K8s存活探针(Spring Boot 2.3+推荐) readinessState: enabled: true # 适配K8s就绪探针(Spring Boot 2.3+推荐) -
其他微服务添加并行阶段,重复按上述操作即可。
三、编写前端流水线
- 有了前面部署 后端流水线的经验,部署前端流水线就快了很多了。首先创建 流水线,选择 node.js 模板。
1. 拉取代码
-
添加 前段代码仓库地址,并添加仓库凭证,如下:

2. 项目编译
-
这次我们使用 kubeSphere 自带的容器 base,来进行后续操作。
-
选择容器为 base,执行命令:npm install 进行编译。

3. 构建镜像
-
和前面 base-jdk8 一样需要修改 jenkins_user.yaml 的 base 容器的配置:防止权限问题。
privileged: true envVars: - envVar: key: "USER" value: "root" - envVar: key: "HOME" value: "/root"
-
像之前我们用 yaml 文件,用命令行发布前端项目一样,我们需要文件有:
- Dockerfile
- nginx.conf
- .dockerignore
- 以及要部署的 deployment,service,ingress 整合到 deploy.yaml 中,结构如下:

-
各个文件代码如下:
-
Dockerfile:其中 node 和 nginx 镜像我是改为了私有镜像库中拉取,加快了构建速度。
# ==================== 阶段1:构建 Vue 项目(Node 环境)==================== FROM mortal.harbor.com/mortal-system/node:18-alpine AS builder # 设置工作目录(root 用户创建,有权限) WORKDIR /app # 1. 复制依赖文件(root 用户,无权限问题) COPY package.json package-lock.json ./ # 2. 安装依赖(--unsafe-perm 尝试赋予权限,基础保障) RUN npm ci --unsafe-perm # 3. 复制项目所有核心文件(root 用户,无权限问题) COPY ../mortal-front/ ./ # 4. 关键修复:手动给 node_modules/.bin 下所有命令赋予执行权限 # 直接暴力解决 vue-tsc、vite 等命令的 Permission denied 问题 RUN chmod +x -R node_modules/.bin/ # 5. 构建 Vue 项目(此时命令已有权限,可正常执行) RUN npm run build # ==================== 阶段2:部署静态资源(Nginx 环境)==================== FROM mortal.harbor.com/mortal-system/nginx:alpine # 复制 dist 产物到 Nginx 静态目录(已验证正确) COPY --from=builder /app/dist /usr/share/nginx/html # 启用自定义 Nginx 配置(关键!解决路由 404) COPY nginx.conf /etc/nginx/conf.d/default.conf # 暴露端口(可选,仅声明) EXPOSE 80 # 启动 Nginx CMD ["nginx", "-g", "daemon off;"] -
nginx.conf
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # 核心:所有路由请求转发到 index.html,支持 Vue Router History 模式 location / { try_files $uri $uri/ /index.html; add_header Cache-Control "no-cache"; } # 静态资源缓存(可选,优化性能) location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|svgz|woff|woff2|ttf|otf)$ { expires 7d; add_header Cache-Control "public, max-age=604800"; } } -
.dockerignore
# 依赖目录 node_modules .npm .yarn # 构建产物(本地构建的,Docker 内会重新构建) dist dist-ssr # 日志和缓存 logs *.log .vite .eslintcache .prettiercache # 本地配置 .env.local .env.development.local .env.production.local .git .gitignore README.md -
deploy.yaml
# frontend-prod-deploy.yaml apiVersion: apps/v1 kind: Deployment metadata: name: mortal-frontend namespace: mortal-system spec: replicas: 1 # 生产环境建议 2-3 个副本,保证高可用 selector: matchLabels: app: mortal-frontend template: metadata: labels: app: mortal-frontend spec: # 私有 Harbor 需配置镜像拉取密钥(和之前一致) imagePullSecrets: - name: harbor-secret containers: - name: mortal-frontend image: mortal.harbor.com/mortal-system/mortal-front:1.0-SNAPSHOT imagePullPolicy: Always ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi livenessProbe: httpGet: path: / port: 80 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5 --- # 1. ClusterIP Service(仅集群内访问,实现 Pod 负载均衡) apiVersion: v1 kind: Service metadata: name: mortal-frontend-svc namespace: mortal-system spec: type: ClusterIP # 类型改为 ClusterIP(默认类型,可不写) selector: app: mortal-frontend ports: - port: 80 # Service 内部端口(集群内其他服务访问用:http://mortal-frontend-svc:80) targetPort: 80 # 对应容器端口 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: mortal-frontend-ingress namespace: mortal-system annotations: nginx.ingress.kubernetes.io/use-regex: "true" # 1. 禁用 HTTPS 重定向(核心) nginx.ingress.kubernetes.io/ssl-redirect: "false" # 2. 禁用强制 SSL 重定向(覆盖全局) nginx.ingress.kubernetes.io/force-ssl-redirect: "false" # 3. 禁用永久重定向(针对 308 状态码) nginx.ingress.kubernetes.io/permanent-redirect-code: "307" # 4. 明确指定后端服务用 HTTP 协议(避免默认 HTTPS 转发) nginx.ingress.kubernetes.io/backend-protocol: "HTTP" # 5. 其他原有注解保留 nginx.ingress.kubernetes.io/proxy-connect-timeout: "60" nginx.ingress.kubernetes.io/proxy-read-timeout: "60" nginx.ingress.kubernetes.io/proxy-send-timeout: "60" spec: ingressClassName: nginx rules: # 无域名规则(直接用 IP 访问) - http: paths: - path: /(.*) pathType: ImplementationSpecific backend: service: name: mortal-frontend-svc port: number: 80
-
-
流水线中执行构建镜像命令:
BASE_VERSION="1.0" # BUILD_NUMBER 是 KubeSphere/Jenkins 内置变量,每次构建自动+1 IMAGE_TAG="${BASE_VERSION}-build${BUILD_NUMBER}" # 制作镜像 podman build --tls-verify=false -f Dockerfile -t mortal.harbor.com/mortal-system/mortal-front:${IMAGE_TAG} .
4. 推送镜像
-
和后端推送进行一样,添加凭证 -> 登陆 harbor -> 推送镜像:
# 登录 harbor podman login --tls-verify=false -u $HARBOR_USER -p $HARBOR_PASSWD mortal.harbor.com BASE_VERSION="1.0" # BUILD_NUMBER 是 KubeSphere/Jenkins 内置变量,每次构建自动+1 IMAGE_TAG="${BASE_VERSION}-build${BUILD_NUMBER}" # 推送镜像 podman push --tls-verify=false mortal.harbor.com/mortal-system/mortal-front:${IMAGE_TAG}
5. 部署服务
-
执行我们的 deploy.yaml 来发布项目,和后端一样,添加凭证 -> 执行脚本:代码上没有任何变化
BASE_VERSION="1.0" # BUILD_NUMBER 是 KubeSphere/Jenkins 内置变量,每次构建自动+1 IMAGE_TAG="${BASE_VERSION}-build${BUILD_NUMBER}" # 替换 deploy.yaml 中的镜像名 sed -i "s/1.0-SNAPSHOT/${IMAGE_TAG}/g" deploy/deploy.yaml mkdir -p ~/.kube echo "$KUBECONFIG_CONTENT" > ~/.kube/config envsubst < deploy/deploy.yaml | kubectl apply -f -
-
测试成功,并且能够成功访问前端项目:


其他问题总结
-
网关服务和其他服务的启动方式不同,网关服务采用的是 netty 启动的,而 其他服务采用的是 tomcat 启动。为什么呢?
- 网关是微服务的入口,需要处理高并发、高流量的请求转发,Netty 在这方面有显著优势:
- NIO 多路复用:Netty 基于 Java NIO 实现,通过 Selector 实现单线程管理多个连接,相比 Servlet 容器的 “一个请求一个线程” 模型,能极大减少线程创建和销毁的开销,在高并发下内存占用更低、响应更快;
- 零拷贝(Zero-Copy):Netty 支持操作系统级别的零拷贝(如 FileRegion、Direct Buffer),在网关转发请求 / 响应数据时,无需频繁拷贝数据到用户态,大幅提升 IO 效率;
- 零拷贝(Zero-Copy):Netty 支持操作系统级别的零拷贝(如 FileRegion、Direct Buffer),在网关转发请求 / 响应数据时,无需频繁拷贝数据到用户态,大幅提升 IO 效率;
- 网关是微服务的入口,需要处理高并发、高流量的请求转发,Netty 在这方面有显著优势:
-
tomcat 和 netty 不同的服务器启动方式在 k8s 部署过程中会带来什么样的影响呢?
- 在 pod 就绪探针健康监测请求路径上不同:
- Tomcat 服务(Servlet 容器):探针路径会受 server.servlet.context-path 配置影响,完整路径是 {context-path}/actuator/health;
- Gateway(Netty):不受 server.servlet.context-path 控制,探针路径固定为 /actuator/health(即使配置了该参数也无效)
- 为何如此呢?
- Tomcat(Servlet 容器):server.servlet.context-path 是 Servlet 规范的核心配置,作用于所有基于 Servlet 的请求,包括:业务接口(如 /api/user → 配置 context-path 后变为 /myapp/api/user);Actuator 健康检查接口(默认 /actuator/health → 变为 /myapp/actuator/health)。这是因为 Tomcat 是 Servlet 容器,所有请求都会经过 Servlet 上下文的路径匹配,context-path 是全局前缀。
- Gateway(Netty):Spring Cloud Gateway 基于 Netty + WebFlux,不遵循 Servlet 规范,server.servlet.context-path 对它完全无效:Gateway 的路径匹配基于自身的路由规则(spring.cloud.gateway.routes);Actuator 接口的路径由 management.endpoints.web.base-path 控制(默认 /actuator),且不受 context-path 影响;即使你配置了 server.servlet.context-path=/gateway,Gateway 的健康探针路径依然是 /actuator/health,而非 /gateway/actuator/health。
- 在 pod 就绪探针健康监测请求路径上不同:
-
在部署服务过程中,我们定义了本地bootstrap.yaml 、application.yaml 、以及 nacos 配置中心的 yaml。我们希望 服务启动后能够加载 nacos 配置中心的配置,替换掉本地的配置。如何做呢?以及他们的关系是什么样的呢?
- 首先要清楚 bootstrap.yaml 和 application.yaml 优先级关系:
- 执行顺序:bootstrap.yml(或bootstrap.properties)会先于application.yml(或application.properties)被加载。这是因为它属于引导上下文(Bootstrap Context),用于初始化应用启动所需的关键配置(如连接配置中心的参数)。
- 优先级与覆盖关系:在 springboot (2.3.x)即以前的版本中:bootstrap.yaml 的配置优先级最高,不会被覆盖。
- 优先级与覆盖关系:在 springboot (2.4.x、2.5.x、2.6.x 以及 Spring Boot 2.3.x + Spring Cloud Hoxton.SR 组合版本中)即以后的版本中:bootstrap.yml中的配置会优先加载,但application.yml中同名配置项会覆盖bootstrap.yml的值。这是因为主应用上下文(Application Context)是引导上下文的子上下文,子上下文的配置优先级更高。同时 nacos 配置中心的配置也会覆盖本地的配置。
- Spring Boot 4.0+ 版本中 bootstrap 正式退场。
- 而我的项目是 Spring Boot 2.3.x + Spring Cloud Hoxton.SR 组合版本 、所以 nacos 配置中心的配置有覆盖本地的配置。
- 但不是 bootstrap 所有的配置都会被覆盖:详情可查看此处
- 最佳实践是在bootstrap 中配置加载nacos配置的地址,以及 spring.profiles.active 属性。其他配置在 application中进行配置。
- 首先要清楚 bootstrap.yaml 和 application.yaml 优先级关系:
小结
- 至此便可通过流水线实现一键 CI/CD ,解决了前面我们使用命令行来部署服务的繁琐,并且可通过页面点击来实现服务的扩容与缩容,通过页面便可查看日志信息。
- kubeSphere 流水线 也可设置触发机制,比如每次仓库更新,自动触发流水线。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)