云原生核心技术 (10/12): K8s 终极实战:从零部署一个 Spring Boot + MySQL + Redis 应用

大家好,欢迎来到《云原生核心技术》系列最激动人心的第十篇!

在过去的九篇文章里,我们从云原生的基本概念出发,一路过关斩将,掌握了 Docker 的容器化技术,搭建了 K8s 集群,并深入学习了 Pod, Deployment, Service, Ingress, ConfigMap, Secret, PVC 等核心组件。我们就像一位武功高手,已经练熟了所有的基本招式。

今天,是时候将所有招式融会贯通,挑战一次"华山论剑"了。我们将迎来本系列的终极实战 :从零开始,将一个包含 Web 后端 (Spring Boot)、关系型数据库 (MySQL) 和内存缓存 (Redis) 的典型三层应用,完整地部署到 Kubernetes 集群中。

这不仅是对你所学知识的一次全面检验,更是你迈向 K8s 实战专家的关键一步。准备好,让我们开始构建吧!


本文摘要

本文将手把手带你完成以下任务:

  • 目标: 在 K8s 集群中部署一个由 Spring Boot, MySQL, Redis 构成的完整 Web 应用。
  • 技术栈与K8s资源映射:
    • MySQL (数据库): 使用 StatefulSet 保证其稳定的网络标识和有序部署,PersistentVolumeClaim (PVC) 来持久化数据,Secret 存储密码,Service 用于内部访问。
    • Redis (缓存): 使用 Deployment 进行部署,Service 用于内部访问。
    • Spring Boot (应用后端): 使用 Deployment 管理应用实例,ConfigMap 注入数据库和 Redis 的连接配置,Service 暴露应用,最后通过 Ingress 将应用安全地发布到外部世界。
  • 你将获得: 一套完整的、可在你本地集群运行的 Dockerfile 和 K8s YAML 文件,以及一个端到端可验证的成功案例。

第一步:规划蓝图 - 应用架构分析

在动手之前,我们先规划好架构。我们的应用部署在 K8s 中后,其组件间的交互关系如下图所示:

  1. 外部流量 :用户的 HTTP 请求首先到达 K8s 集群的入口------Ingress
  2. 路由与应用 :Ingress 根据域名(例如 sb.app.test)将流量转发给后端的 Spring Boot Service ,Service 再将流量负载均衡到多个 Spring Boot Pod 上。
  3. 内部通信 :Spring Boot 应用需要连接数据库和缓存。它会通过 K8s 的服务发现机制,使用 MySQL ServiceRedis Service 的名称作为主机名进行连接。
  4. 有状态服务
    • MySQLStatefulSet 管理,确保每个 Pod 都有一个独一无二且固定的身份。它的数据通过 PVC 持久化在外部存储上,即使 Pod 重启,数据也不会丢失。
    • Redis 在本例中作为缓存,我们用简单的 Deployment 管理即可。

第二步:准备工作

  1. 一个运行中的 K8s 集群:确保你的 Minikube 或 kind 集群已启动。

  2. 容器化 Spring Boot 应用 :在项目根目录下,有一个 Dockerfile,它使用多阶段构建来优化镜像大小。

    dockerfile 复制代码
    # Dockerfile
    # Stage 1: Build the application
    FROM openjdk:17-jdk-slim as builder
    WORKDIR /app
    COPY . .
    RUN ./mvnw package -DskipTests
    
    # Stage 2: Create the final image
    FROM openjdk:17-jre-slim
    WORKDIR /app
    COPY --from=builder /app/target/*.jar app.jar
    ENTRYPOINT ["java", "-jar", "app.jar"]

    你需要构建并将其推送到你的 Docker Hub 或其他镜像仓库。为方便测试,你也可以直接使用我预先构建好的镜像 yourdockerhub/k8s-demo-app:1.0 (请替换)。


第三步:部署 MySQL (有状态核心)

部署顺序很重要,我们应该先部署依赖的基础服务,比如数据库。

1. 创建 Secret 存储密码

永远不要把密码明文写在 YAML 文件里。我们先创建一个 Secret 来存放 MySQL 的 root 密码。

bash 复制代码
# 在终端执行
kubectl create secret generic mysql-pass --from-literal=password=YourStrongPassword123
2. 创建 Headless Service 和 StatefulSet

MySQL 需要稳定的网络标识,因此我们使用 StatefulSetStatefulSet 需要一个 "Headless Service" (即 clusterIP: None)来控制其 Pod 的网络域。

创建一个 mysql-statefulset.yaml 文件:

yaml 复制代码
# mysql-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  ports:
  - port: 3306
  # 定义为 Headless Service
  clusterIP: None
  selector:
    app: mysql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql-sts
spec:
  serviceName: "mysql-headless"
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          # 从 Secret 中获取密码
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        # 挂载持久化卷
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
  # 定义 PVC 模板,将为每个 Pod 动态创建一个 PVC
  volumeClaimTemplates:
  - metadata:
      name: mysql-persistent-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi

应用它:kubectl apply -f mysql-statefulset.yaml


第四步:部署 Redis

Redis 作为缓存,我们用简单的 DeploymentService 即可。

创建一个 redis-deployment.yaml 文件:

yaml 复制代码
# redis-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis-svc
spec:
  ports:
  - port: 6379
  selector:
    app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:6-alpine
        ports:
        - containerPort: 6379

应用它:kubectl apply -f redis-deployment.yaml


第五步:部署 Spring Boot 应用

现在,万事俱备,只欠东风。我们来部署核心的 Spring Boot 应用。

1. 创建 ConfigMap 注入配置

创建一个 app-configmap.yaml,注意 spring.datasource.urlspring.redis.host 的值,它们直接使用了 K8s Service 的名称,K8s 内置的 DNS 会将它们解析到正确的 Service IP。

yaml 复制代码
# app-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # key 必须是 application.properties,以便覆盖容器内的文件
  application.properties: |
    spring.datasource.url=jdbc:mysql://mysql-headless:3306/db_example?useSSL=false&allowPublicKeyRetrieval=true
    spring.datasource.username=root
    # 注意:密码我们稍后会用环境变量从 Secret 注入
    spring.jpa.hibernate.ddl-auto=update
    spring.redis.host=redis-svc
    spring.redis.port=6379

应用它:kubectl apply -f app-configmap.yaml

2. 创建 Deployment, Service 和 Ingress

创建一个 springboot-app.yaml 文件。这个文件将组装所有部分。

yaml 复制代码
# springboot-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: springboot-app-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: springboot-app
  template:
    metadata:
      labels:
        app: springboot-app
    spec:
      containers:
      - name: springboot-app
        # 请替换成你自己的镜像地址
        image: yourdockerhub/k8s-demo-app:1.0
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_DATASOURCE_PASSWORD
          # 从 Secret 注入数据库密码
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        volumeMounts:
        # 将 ConfigMap 挂载为文件,覆盖默认配置
        - name: app-config-volume
          mountPath: /app/config/application.properties
          # 文件名作为路径的一部分
          subPath: application.properties
      volumes:
      - name: app-config-volume
        configMap:
          name: app-config
---
apiVersion: v1
kind: Service
metadata:
  name: springboot-svc
spec:
  type: NodePort
  selector:
    app: springboot-app
  ports:
  - port: 80
    targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: springboot-ingress
spec:
  rules:
  - host: "sb.app.test"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: springboot-svc
            port:
              number: 80

应用它:kubectl apply -f springboot-app.yaml


第六步:验证成果!

  1. 检查所有组件状态

    bash 复制代码
    kubectl get all,pvc,secret

    确保所有的 Pods 都是 Running 状态,StatefulSetDeploymentREADY 状态是 1/12/2PVCBound 状态。

  2. 配置 hosts :和第八篇一样,编辑你的 /etc/hosts 文件,添加 sb.app.test 的解析。

    复制代码
    # 格式:[Minikube IP] [你的域名]
    $(minikube ip)   sb.app.test
  3. 测试应用 :打开你的终端,使用 curl 测试应用的 API。

    • 添加一个用户到数据库,并缓存到 Redis:

      bash 复制代码
      curl -X POST http://sb.app.test/users -H "Content-Type: application/json" -d '{"name": "Alice", "email": "[email protected]"}'
    • 从 Redis/数据库获取用户:

      bash 复制代码
      curl http://sb.app.test/users/1
      # 第一次请求会从 MySQL 读取并写入 Redis,第二次请求会直接从 Redis 读取

如果你能看到正确的 JSON 返回,那么恭喜你!你已经成功地在 Kubernetes 上部署了一个完整、复杂、有状态的真实应用!


总结与展望

今天,我们打了一场漂亮的硬仗。通过这个终极实战,我们不仅将前面学到的 K8s 知识点串联了起来,更重要的是,我们建立了一套在 K8s 上部署复杂应用的思维模式:

  • 识别状态:分析应用组件,区分无状态(用 Deployment)和有状态(用 StatefulSet)。
  • 分离配置:使用 ConfigMap 和 Secret 将环境配置与应用镜像解耦。
  • 管理数据:利用 PVC/PV 模型为有状态服务提供可靠的数据持久化。
  • 服务发现:利用 K8s Service 的 DNS 机制实现服务间的轻松通信。
  • 暴露流量:通过 Ingress 提供统一、专业的访问入口。

然而,你可能已经注意到了,我们今天的整个过程都是手动 执行 kubectl apply。如果代码频繁更新,每次都这样手动操作,不仅效率低下,而且极易出错。

那么,如何将这个过程自动化,实现只要我们一提交代码,就能自动构建镜像、自动部署到 K8s 呢?

这就是我们系列最后两篇将要探索的 CI/CD(持续集成/持续部署)。在下一篇,我们将首先理解 CI/CD 的核心理念,为最终的自动化实战铺平道路。我们下期见!

相关推荐
955.34 分钟前
k8s从入门到放弃之k3s轻量级
云原生·容器·kubernetes
风象南1 小时前
SpringBoot防重放攻击的5种实现方案
java·spring boot·后端
callJJ2 小时前
从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(1)
java·开发语言·spring boot·后端·spring·restful·ioc di
Elcker9 小时前
Springboot+idea热更新
spring boot·后端·intellij-idea
悟纤11 小时前
Docker 操作容器[SpringBoot之Docker实战系列] - 第538篇
spring boot·docker·容器
一个有女朋友的程序员11 小时前
Spring Boot 整合 Smart-Doc:零注解生成 API 文档,告别 Swagger
java·spring boot·smart-doc
苹果醋312 小时前
AI大模型竞赛升温:百度发布文心大模型4.5和X1
java·运维·spring boot·mysql·nginx
张乔2412 小时前
spring boot项目整合mybatis实现多数据源的配置
java·spring boot·多数据源
美好的事情能不能发生在我身上12 小时前
苍穹外卖Day11代码解析以及深入思考
java·spring boot·后端·spring·架构