大家好,欢迎来到《云原生核心技术》系列最激动人心的第十篇!
在过去的九篇文章里,我们从云原生的基本概念出发,一路过关斩将,掌握了 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
将应用安全地发布到外部世界。
- MySQL (数据库): 使用
- 你将获得: 一套完整的、可在你本地集群运行的 Dockerfile 和 K8s YAML 文件,以及一个端到端可验证的成功案例。
第一步:规划蓝图 - 应用架构分析
在动手之前,我们先规划好架构。我们的应用部署在 K8s 中后,其组件间的交互关系如下图所示:
- 外部流量 :用户的 HTTP 请求首先到达 K8s 集群的入口------Ingress。
- 路由与应用 :Ingress 根据域名(例如
sb.app.test
)将流量转发给后端的 Spring Boot Service ,Service 再将流量负载均衡到多个 Spring Boot Pod 上。 - 内部通信 :Spring Boot 应用需要连接数据库和缓存。它会通过 K8s 的服务发现机制,使用 MySQL Service 和 Redis Service 的名称作为主机名进行连接。
- 有状态服务 :
- MySQL 由
StatefulSet
管理,确保每个 Pod 都有一个独一无二且固定的身份。它的数据通过PVC
持久化在外部存储上,即使 Pod 重启,数据也不会丢失。 - Redis 在本例中作为缓存,我们用简单的
Deployment
管理即可。
- MySQL 由
第二步:准备工作
-
一个运行中的 K8s 集群:确保你的 Minikube 或 kind 集群已启动。
-
容器化 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 需要稳定的网络标识,因此我们使用 StatefulSet
。StatefulSet
需要一个 "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 作为缓存,我们用简单的 Deployment
和 Service
即可。
创建一个 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.url
和 spring.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
第六步:验证成果!
-
检查所有组件状态:
bashkubectl get all,pvc,secret
确保所有的 Pods 都是
Running
状态,StatefulSet
和Deployment
的READY
状态是1/1
或2/2
,PVC
是Bound
状态。 -
配置 hosts :和第八篇一样,编辑你的
/etc/hosts
文件,添加sb.app.test
的解析。# 格式:[Minikube IP] [你的域名] $(minikube ip) sb.app.test
-
测试应用 :打开你的终端,使用
curl
测试应用的 API。-
添加一个用户到数据库,并缓存到 Redis:
bashcurl -X POST http://sb.app.test/users -H "Content-Type: application/json" -d '{"name": "Alice", "email": "[email protected]"}'
-
从 Redis/数据库获取用户:
bashcurl 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 的核心理念,为最终的自动化实战铺平道路。我们下期见!