K8s 集群部署微服务 - yaml 版本(三)

K8s 集群部署微服务 - yaml 版本(三)


K8s 集群环境搭建 - yaml 版本(一)
K8s 集群部署中间件 - yaml 版本(二)
K8s 集群部署微服务 - yaml 版本(三)

文章目录

  • [K8s 集群部署微服务 - yaml 版本(三)](#K8s 集群部署微服务 - yaml 版本(三))
  • 一、制作镜像
    • [1. package 生成可直接运行的 jar包](#1. package 生成可直接运行的 jar包)
    • [2. 制作镜像](#2. 制作镜像)
    • [3. 验证镜像](#3. 验证镜像)
    • [4. 上传镜像](#4. 上传镜像)
  • 二、授权服务部署
    • [1. Service](#1. Service)
    • [2. Deployment](#2. Deployment)
  • 三、业务服务部署
    • [1. Service](#1. Service)
    • [2. PVC](#2. PVC)
    • [3. Deployment](#3. Deployment)
  • 四、网关服务部署
    • [1. Service](#1. Service)
    • [2. PVC](#2. PVC)
    • [3. Deployment](#3. Deployment)
    • [4. Ingress](#4. Ingress)
    • [5. 验证](#5. 验证)
  • 五、前端项目部署
    • [1. 制作镜像](#1. 制作镜像)
    • [2. 部署服务](#2. 部署服务)
    • 3.验证
  • 疑问与总结
  • [为什么 Deployment 需要单独创建PVC,而不是像 StatefulSet 一样定义volumeClaimTemplates?](#为什么 Deployment 需要单独创建PVC,而不是像 StatefulSet 一样定义volumeClaimTemplates?)
  • [部署 Deployment 是如何关联上 Nacos 配置中心的配置文件?](#部署 Deployment 是如何关联上 Nacos 配置中心的配置文件?)
  • 结尾

一、制作镜像

1. package 生成可直接运行的 jar包

  • 使用 k8s 部署服务之前,需要将我们的服务制作成镜像,以便在后续使用该镜像启动服务。

  • 制作镜像需要做一下几步:

    • 将我们需要运行的微服务通过 maven 生成 可直接运行的 jar 包。在该微服务的 pom 文件中需要加上 springboot 打包插件,通过该插件则无需手动配置类路径、依赖包,只需通过 mvn package 生成可执行 Jar 包,直接用 java -jar 启动,实现「一键部署」,没有该插件 jar 是无法直接运行的,会报错。注:加载需要打包的微服务pom中,而不是 parent 中。

      java 复制代码
      <build>
              <plugins>
                  <!-- 核心:Spring Boot打包插件,负责生成主清单 -->
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                      <version>2.6.13</version> <!-- 如2.7.10 -->
                      <executions>
                          <execution>
                              <goals>
                                  <goal>repackage</goal> <!-- 关键:重新打包为可执行Jar -->
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
              </plugins>
          </build>
  • 通过 maven package 生成 jar包。如果该微服务依赖了其他的公共服务,需要先将依赖的服务先执行 maven install,从而把包上传至本地仓库,供本地项目使用,不然会打包失败,无法找到依赖的项目。

    • mvn package:「本地打包」------ 生成可执行 / 依赖包,仅存本地项目。
    • mvn install:「本地仓库安装」------ 把包上传到本地仓库,供所有本地项目引用。

2. 制作镜像

  • 生成了 jar 包之后,如果我们需要将包上传至服务器,可通过如下面命令上传。scp [源文件] user@[目标ip]:[目录],输入用户登录密码即可。

    java 复制代码
     scp .\mortal-authority-1.0-SNAPSHOT.jar root@14.103.138.45:/opt/mortal/jar
  • 编写 dockerfile 来制作镜像。

    java 复制代码
    # 基础镜像:和你的 JDK 版本匹配(JDK 8 用 8-jre-slim,轻量且安全)
    FROM 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
    
    # 工作目录(容器内目录,统一路径)
    WORKDIR /opt/mortal/app
    
    # 复制授权服务的 Jar 包到容器(注意:Jar 包名称要和 target/ 下的一致!)
    # 如果你想简化 Jar 包名称,可在复制时重命名为 app.jar(推荐,避免版本号变更导致 Dockerfile 修改)
    # [当前系统相对路径] [容器内绝对路径]
    COPY jar/mortal-authority-1.0-SNAPSHOT.jar /opt/mortal/app/mortal-authority.jar
    
    # 给非 root 用户授权(避免启动时权限不足)
    RUN chown -R appuser:appgroup /opt/mortal/app
    
    # 切换到非 root 用户运行
    USER 1001
    
    # 暴露服务端口(和授权服务的 server.port 一致,比如 2001)
    EXPOSE 2001
    
    # 启动命令(exec 格式,支持优雅停机)
    ENTRYPOINT ["java", "-jar", "mortal-authority.jar"]
  • 通过下面命令制作镜像:

    java 复制代码
    docker build -f dockerfile/mortal-authority-service.Dockerfile -t mortal.harbor.com/mortal-system/mortal-authority:1.0-SNAPSHOT .
    • dockerfile/mortal-authority-service.Dockerfile:我们需要使用的 dockerfile 文件。
    • mortal.harbor.com/mortal-system/mortal-authority:1.0-SNAPSHOT:制作成的镜像名:版本号。
    • . :构建上下文 Context。告诉 Docker 「构建镜像时,可以访问哪些文件 / 目录」------ Docker 构建时,会把「构建上下文」下的所有文件递归打包,发送给 Docker 守护进程(daemon),供 Dockerfile 中的 COPY、ADD 等指令使用。与 dockerfile 文件中的指令:COPY jar/mortal-authority-1.0-SNAPSHOT.jar /opt/mortal/app/mortal-authority.jar。这里的 target/xxx.jar 路径,就是相对于「构建上下文(.)」的 ------ 如果构建上下文不是 target 所在的目录,COPY 会失败!
  • 查看镜像:

    java 复制代码
    docker images

3. 验证镜像

  • 成功制作成镜像后需要先验证下镜像是否可用,防止到 k8s 部署时出错时,方便问题排查。运行镜像:

    java 复制代码
    docker run -d -p 2001:2001 --name auth-tes mortal.harbor.com/mortal-system/mortal-authority:1.0-SNAPSHOT
  • 如果运行没问题则继续后续操作。

4. 上传镜像

  • 将我们的镜像上传至镜像仓库:

    java 复制代码
    docker push mortal.harbor.com/mortal-system/mortal-authority:1.0-SNAPSHOT
  • 至此镜像制作完了,方便后续引用。

二、授权服务部署

  • 首先需要确定部署一个微服务需要的资源有哪些:
    • Deployment:定义微服务部署规则(镜像地址、副本数、资源限制、健康检查、配置注入、日志存储挂载)。必须。
    • Service(ClusterIP):暴露微服务为 K8s 内部服务,供集群内其他服务(或网关)访问。必须。
    • PersistentVolumeClaim(PVC):挂载 NFS 存储用于微服务日志持久化(若不需要日志持久化,可省略)。非必须。
    • 以及多个微服务共用一个 Ingress,实现外部访问入口。由于我们需要部署 网关微服务,所以我们会将 Ingress 的请求转发给网关服务,由网关实现更细粒度的路由、鉴权、限流等功能,结构更清晰。

1. Service

  • 提供给集群内部访问的入口。
java 复制代码
# Service配置(暴露微服务,供内部集群访问)
apiVersion: v1
kind: Service
metadata:
  name: author-service
  namespace: mortal-system
spec:
  selector:
    app: author-service
  type: ClusterIP  # 集群内部访问,如需外部访问可改为NodePort或LoadBalancer
  ports:
  - port: 2001  # Service端口
    targetPort: 2001  # 容器端口(与Deployment中containerPort一致)
  • 查看部署的 service:kubectl get svc -n [namespace]

2. Deployment

  • 授权服务
java 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: author-service  # 部署名称
  namespace: mortal-system  # 建议创建专属命名空间,也可改为你的现有命名空间
  labels:
    app: author-service
spec:
  replicas: 3  # 副本数,可根据需求调整
  selector:
    matchLabels:
      app: author-service
  template:
    metadata:
      labels:
        app: author-service
    spec:
      containers:
      - name: author-service
        # 镜像地址(harbor中的目标镜像)
        image: mortal.harbor.com/mortal-system/mortal-authority:1.0-SNAPSHOT
        imagePullPolicy: IfNotPresent  # 镜像拉取策略:本地无则拉取
        # 容器端口(对应微服务的启动端口,如8080、9999等,需与服务配置一致)
        ports:
        - containerPort: 2001
        # 环境变量配置(连接nacos、mysql,根据你的实际配置修改值)
        env:
        # 1. 微服务名称(对应 Nacos DataID 前缀)
        - name: SPRING_APPLICATION_NAME
          value: "mortal-authority-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: "db0788b5-43d5-4f7c-abf4-a9aa8308d272"
        # 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: /mortal-authority/actuator/health
            port: 2001
          initialDelaySeconds: 60  # 启动后延迟60秒检查
          periodSeconds: 10  # 每10秒检查一次
        readinessProbe:
          httpGet:
            path: /mortal-authority/actuator/health
            port: 2001
          initialDelaySeconds: 30
          periodSeconds: 5
        # 挂载动态PVC(与volumeClaimTemplates名称对应)
        volumeMounts:
        - name: author-log-volume  # 必须与volumeClaimTemplates.name一致
          mountPath: /app/logs  # 容器内持久化目录
      volumes:  # 新增volumes字段引用PVC
      - name: author-log-volume
        persistentVolumeClaim:
          claimName: author-log-pvc  # 对应上面创建的PVC名称
  • 需要注意 就绪探针 的配置

    java 复制代码
    		# 健康检查(可选,建议配置)
            livenessProbe:
              httpGet:
                path: /mortal-authority/actuator/health
                port: 2001
              initialDelaySeconds: 60  # 启动后延迟60秒检查
              periodSeconds: 10  # 每10秒检查一次
            readinessProbe:
              httpGet:
                path: /mortal-authority/actuator/health
                port: 2001
              initialDelaySeconds: 30
              periodSeconds: 5
  • path 为 :[项目配置文件中 server.servlet.context-path] / actuator/health,否则会出现就绪探针失败的问题:context deadline exceeded。

    • 就绪探针失败的 Pod 会被 K8s 从 Service 的负载均衡池中 "剔除"------ 不会接收任何请求(包括集群内其他微服务的调用、外部通过 Ingress 的访问),直到探针检测成功(Pod 就绪)。
  • 查看部署的服务:

  • 虽然是 running ,但也要查看日志,因为 就绪探针失败,也是 running。如下就是失败了:

  • 所以需要使用命令检查下:

    java 复制代码
    kubectl describe pod [podName] -n [namespace]

三、业务服务部署

1. Service

  • 提供向集群内提供服务的 Service:

    java 复制代码
    # Service配置(暴露微服务,供内部集群访问)
    apiVersion: v1
    kind: Service
    metadata:
      name: system-service
      namespace: mortal-system
    spec:
      selector:
        app: system-service
      type: ClusterIP  # 集群内部访问,如需外部访问可改为NodePort或LoadBalancer
      ports:
      - port: 8001  # Service端口
        targetPort: 8001  # 容器端口(与Deployment中containerPort一致)

2. PVC

  • 申请存储空间,用来存储日志。

    java 复制代码
    # author-pvc.yaml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: system-log-pvc
      namespace: mortal-system
    spec:
      accessModes: [ "ReadWriteMany" ]
      storageClassName: "nfs-sc"
      resources:
        requests:
          storage: 2Gi

3. Deployment

  • 部署服务,我这里是部署的系统管理服务:
java 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: system-service  # 部署名称
  namespace: mortal-system  # 建议创建专属命名空间,也可改为你的现有命名空间
  labels:
    app: system-service
spec:
  replicas: 3  # 副本数,可根据需求调整
  selector:
    matchLabels:
      app: system-service
  template:
    metadata:
      labels:
        app: system-service
    spec:
      containers:
      - name: system-service
        # 镜像地址(harbor中的目标镜像)
        image: mortal.harbor.com/mortal-system/mortal-service-system:1.0-SNAPSHOT
        imagePullPolicy: IfNotPresent  # 镜像拉取策略:本地无则拉取
        # 容器端口(对应微服务的启动端口,如8080、9999等,需与服务配置一致)
        ports:
        - containerPort: 8001
        # 环境变量配置(连接nacos、mysql,根据你的实际配置修改值)
        env:
        # 1. 微服务名称(对应 Nacos DataID 前缀)
        - name: SPRING_APPLICATION_NAME
          value: "mortal-service-system"
        # 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: "db0788b5-43d5-4f7c-abf4-a9aa8308d272"
        # 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: /mortal-service-system/actuator/health
            port: 8001
          initialDelaySeconds: 60  # 启动后延迟60秒检查
          periodSeconds: 10  # 每10秒检查一次
        readinessProbe:
          httpGet:
            path: /mortal-service-system/actuator/health
            port: 8001
          initialDelaySeconds: 30
          periodSeconds: 5
        # 挂载动态PVC(与volumeClaimTemplates名称对应)
        volumeMounts:
        - name: author-log-volume  # 必须与volumeClaimTemplates.name一致
          mountPath: /app/logs  # 容器内持久化目录
      volumes:  # 新增volumes字段引用PVC
      - name: author-log-volume
        persistentVolumeClaim:
          claimName: author-log-pvc  # 对应上面创建的PVC名称

四、网关服务部署

1. Service

  • 提供给集群内部访问的入口。

    java 复制代码
    # Service配置(暴露微服务,供内部集群访问)
    apiVersion: v1
    kind: Service
    metadata:
      name: gateway-service
      namespace: mortal-system
    spec:
      selector:
        app: gateway-service
      type: ClusterIP  # 集群内部访问,如需外部访问可改为NodePort或LoadBalancer
      ports:
      - port: 1888  # Service端口
        targetPort: 1888  # 容器端口(与Deployment中containerPort一致)
  • name 为:gateway-service

2. PVC

  • 创建网关微服务的 pv 和 pvc:

    java 复制代码
    # 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

3. Deployment

  • 部署 gateway 网关微服务,其中的配置文件由 nacos 配置中心管理,并在 Deployment 中指定配置文件。SPRING_APPLICATION_NAME - SPRING_PROFILES_ACTIVE.yaml。并将服务注册进 nacos 注册中心。
  • Deployment 代码如下:

    java 复制代码
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: gateway-service  # 部署名称
      namespace: mortal-system  # 建议创建专属命名空间,也可改为你的现有命名空间
      labels:
        app: gateway-service
    spec:
      replicas: 3  # 副本数,可根据需求调整
      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: IfNotPresent  # 镜像拉取策略:本地无则拉取
            # 容器端口(对应微服务的启动端口,如8080、9999等,需与服务配置一致)
            ports:
            - containerPort: 1888
            # 环境变量配置(连接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: "db0788b5-43d5-4f7c-abf4-a9aa8308d272"
            # 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
                port: 1888
              initialDelaySeconds: 60  # 启动后延迟60秒检查
              periodSeconds: 10  # 每10秒检查一次
            readinessProbe:
              httpGet:
                path: /actuator/health
                port: 1888
              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名称
  • 部署成功后:

4. Ingress

  • 我们需要网关服务能够对外提供服务,并且由网关服务负责进行转发。
java 复制代码
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: 1888
    # 域名规则(可选,保留)
    - host: gateway.mortal.com
      http:
        paths:
          - path: /mortal-gateway/(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: gateway-service
                port:
                  number: 1888

5. 验证

  • 首先查看 ingress 向外提供的端口号,可以使用 k8s 集群中任意节点的 ip + 端口来访问:

  • 可以发现 http 请求端口是 31360 映射 80。我们这里用的是 http 请求,所以可以用任意节点的 ip + 31360/ xxx 来访问ingress 向外提供的服务。 (443:31689 对应的是https 请求)。

  • 使用 postman 发起服务注册服务: 请求流程是: postman -> ingress -> gateway-service -> gateway 服务 (转发) -> authority-servie -> authority 服务。

五、前端项目部署

  • 我的前端项目为 vue3 + typeScript。

  • 使用 k8s 部署前端项目的流程是:

    1. 编写 front.Dockerfile 文件:构建镜像。
    2. 编写 front.dockerignore,用于 Docker 构建镜像时需要排除的文件 / 目录,减小镜像体积。
    3. 上传 项目源码:用于构建镜像。
    4. 编写 front-service.yaml:向k8s集群内提供服务。
    5. 编写 front-k8s.yaml:服务部署。
    6. 编写 front-ingress.yaml:向集群外提供服务。
    7. 编写 nginx.conf : 解决 Vue Router History 模式的「刷新 404」问题,优化静态资源缓存,减轻 K8s 集群压力。
  • 我上传后文件结构如下:

    front/ # 你执行 docker build 的目录(当前目录)

    ├── dockerfile/

    │ └── front.Dockerfile # 你的 Dockerfile

    │ └── nginx.conf

    ├── mortal # 项目源码

    │ └── package.json

    │ └── package-lock.json

    └── .dockerignore # 注意不要排除 package.json

  • 文件上传后需要制作镜像了

1. 制作镜像

  • front.Dockerfile

    java 复制代码
    # ==================== 阶段1:构建 Vue 项目(Node 环境)====================
    FROM node:18-alpine AS builder
    
    # 设置工作目录(root 用户创建,有权限)
    WORKDIR /app
    
    # 1. 复制依赖文件(root 用户,无权限问题)
    COPY mortal/package.json mortal/package-lock.json ./
    
    # 2. 安装依赖(--unsafe-perm 尝试赋予权限,基础保障)
    RUN npm ci --unsafe-perm
    
    # 3. 复制项目所有核心文件(root 用户,无权限问题)
    COPY mortal/ ./
    
    # 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 nginx:alpine
    
    # 复制 dist 产物到 Nginx 静态目录(已验证正确)
    COPY --from=builder /app/dist /usr/share/nginx/html
    
    # 启用自定义 Nginx 配置(关键!解决路由 404)
    COPY dockerfile/nginx.conf /etc/nginx/conf.d/default.conf
    
    # 暴露端口(可选,仅声明)
    EXPOSE 80
    
    # 启动 Nginx
    CMD ["nginx", "-g", "daemon off;"]
  • front.dockerignore:

    java 复制代码
    # 依赖目录
    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
  • nginx.conf:

    java 复制代码
    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";
        }
    }
  • 在 front 目录下执行制作镜像命令:

    java 复制代码
    docker build -f dockerfile/front.Dockerfile -t mortal.harbor.com/mortal-system/mortal-front:1.0-SNAPSHOT .
  • 查看镜像:

  • 镜像制作完之后,先不急后续流程,先验证镜像是否可用,使用镜像部署容器:

    java 复制代码
    docker run -d -p 8888:80 --name mortal-front-test mortal.harbor.com/mortal-system/mortal-front:1.0-SNAPSHOT
  • 如果镜像可正常访问服务,则继续后续流程:

  • 上传进行至 harbor:

    java 复制代码
    docker push mortal.harbor.com/mortal-system/mortal-front:1.0-SNAPSHOT
  • 后续部署服务需要使用我们制作的镜像。

2. 部署服务

  • 部署 Service 向集群内提供服务:front-service.yaml

    java 复制代码
    ---
    # 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  # 对应容器端口
  • 使用我们上传的镜像进行服务部署:front-k8s.yaml

    java 复制代码
    # frontend-prod-deploy.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mortal-frontend
      namespace: mortal-system
    spec:
      replicas: 3  # 生产环境建议 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
            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
  • 部署 ingress,向集群外提供服务,让我们能够访问页面:

    java 复制代码
    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
  • 至此前端服务就部署完毕了,如果使用域名访问,需要修改 ingress,配置域名访问规则。

3.验证

  • 通过前面已经部署的 ingress-controller 知道,通过端口 31360 -> 80 映射,我们访问 任意节点ip + 端口 + / xxx 来访问前端页面:
  • 发现可以正常访问,部署完成。

疑问与总结

为什么 Deployment 需要单独创建PVC,而不是像 StatefulSet 一样定义volumeClaimTemplates?

  • 首先 volumeClaimTemplates 是 StatefulSet(有状态服务) 的专属字段。为 StatefulSet 管理每一个 pod 都会单独创建一个独立的、与 pod 生命周期绑定的 pvc 以及对应的 pv。每个 Pod 挂载自己的专属存储,数据互不干扰,且 Pod 重建(即使调度到其他节点)仍能绑定到原来的 PVC。

  • Deployment(无状态服务)并不需要 "专属存储",所有副本共享同一套数据(存在 MySQL/MinIO),本地仅存储临时文件、日志(可选):

    • 日志可通过 NFS 共享存储挂载(所有副本写日志到同一个 NFS 目录,或按 Pod 名分目录),无需每个副本专属 PVC。
    • 临时文件不需要持久化,Pod 销毁后可丢失,用 emptyDir 即可。如果给 Deployment 加 volumeClaimTemplates,会导致每个副本有独立存储,但这些存储里的数据毫无意义(因为服务无状态),反而浪费资源。
  • 对比:Deployment vs StatefulSet 存储设计差异:

    特性 Deployment(无状态) StatefulSet(有状态)
    存储需求 共享存储(日志)或无持久化 每个实例专属存储(核心数据)
    存储绑定关系 所有副本挂载同一个 PVC 每个副本绑定独立 PVC(由模板生成)
    实例身份 无身份(名称随机) 有固定身份(名称有序:xxx-0、xxx-1)
    数据独立性 无(数据存在共享存储) 有(每个实例数据独立)
    是否支持 volumeClaimTemplates 不支持(设计目标冲突) 支持(核心特性)

部署 Deployment 是如何关联上 Nacos 配置中心的配置文件?

  • Nacos 配置文件的 DataID 默认拼接规则是: s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name−{spring.profiles.active}.yaml,对应 Deployment 配置如下:(file-extension 默认为 yaml)

    • nacos 对应配置:

结尾

  • 至此使用命令行部署yaml文件的方式来部署项目差不多就结束了,其中有些安全要求会更高,需要添加一些额外安全配置,比如部署 Nacos 的时候可以开启安全模式,比如一些用户名密码统一放到 Secret 中进行管理,并且不使用明文的方式,不过大致的流程就是这样的。总的来看,可以发现如下痛点:
  • 每一次部署,无论是中间件,还是微服务,都需要编写大量的 yaml 文件,这些yaml 文件难以管理。
  • 每次服务部署,扩容,缩容,查看日志,都需要手动敲命令行,非常繁琐。
  • 每次服务更新都要重新 package,重新上传jar包,重新制作镜像并上传 harbor,重新 部署 deployment 从而更新其中的镜像,这个过程,可想而知...
  • 带着这些痛点,继续探索。
相关推荐
皮糖小王子3 小时前
Docker打开本地镜像
运维·docker·容器
❀͜͡傀儡师3 小时前
docker 部署Flink和传统部署
docker·容器·flink
r***d8653 小时前
GitHub星标15万+的Docker项目,使用指南
docker·容器·github
回家路上绕了弯4 小时前
单体架构拆微服务:从评估到落地的全流程指南
后端·微服务
努力发光的程序员5 小时前
互联网大厂Java面试:从Spring Boot到大数据处理的实战场景问题解析
spring boot·微服务·云原生·java面试·大数据处理·技术解析·互联网求职
袅沫6 小时前
微服务如何进行远程调用其他服务
java·微服务·架构
笨蛋不要掉眼泪7 小时前
Docker概念入门与初步安装
docker·微服务·容器
也许是_9 小时前
架构的取舍之道:在微服务的“混乱”中建立秩序
微服务·云原生·架构
-大头.10 小时前
JVM框架实战指南:Spring到微服务
jvm·spring·微服务