第11章:大结局 - 部署一个完整的微服务应用

124 阅读8分钟

第11章:大结局 - 部署一个完整的微服务应用

恭喜你,走到了我们旅程的最后一站!

在过去的十章里,我们从 Docker 的基础一路走来,跨越了镜像、容器、网络、数据卷的重重关卡,最终登上了 Kubernetes 这艘云原生的巨轮,学会了使用 Deployment、Service、ConfigMap 和 PVC 等核心武器。

现在,是时候将所有这些“武功”融会贯通,完成一次终极实战了。

在本章,我们将把一个经典的微服务应用——一个包含前端(React)、后端(Node.js API)和数据库(PostgreSQL)的投票应用——完整地部署到我们的 Minikube 集群上。

这不仅是对你所学知识的一次全面检验,更是一个可以让你自信地写在简历上的、完整的云原生项目经验。

11.1 项目架构概览

我们的投票应用(Example Voting App)由以下几个部分组成:

  1. Voting App (前端): 一个 Python/Flask 或 .NET/C# 的 Web 应用,提供投票界面(猫 vs. 狗),它会将投票结果发送到 Redis。
  2. Redis (内存数据库): 接收并临时存储投票数据。
  3. Worker (工作进程): 一个 .NET/Java 应用,它会连接到 Redis,获取投票数据,然后将其持久化到 PostgreSQL 数据库中。
  4. DB (数据库): 一个 PostgreSQL 数据库,用于永久存储投票结果。
  5. Result App (结果展示): 一个 Node.js Web 应用,它会从 PostgreSQL 数据库中读取投票结果,并以图表形式实时展示出来。

这是一个非常典型的微服务架构,每个服务各司其职,通过网络进行通信,数据在其中流动。

Voting App Architecture

我们的任务,就是为这个应用中的每一个服务,编写对应的 Kubernetes YAML 配置文件(Deployment 和 Service),并将它们部署到集群中。

11.2 准备工作:获取项目代码和镜像

为了方便大家,官方已经为这个示例应用的所有服务都构建好了 Docker 镜像,并发布在 Docker Hub 上。我们不需要自己去写 Dockerfile 和构建镜像。

你可以在这里找到项目的所有信息:github.com/dockersampl…

我们将要用到的镜像是:

  • dockersamples/examplevotingapp_vote
  • dockersamples/examplevotingapp_worker
  • dockersamples/examplevotingapp_result
  • redis:alpine
  • postgres:15-alpine

11.3 编写 Kubernetes YAML 文件

我们将把所有服务的 K8s 资源定义,写在一个大的 YAML 文件里,用 --- 来分隔每一个资源。创建一个名为 voting-app.yaml 的文件。

1. 数据库 (PostgreSQL)

首先部署我们的有状态服务——数据库。我们需要一个 StatefulSet、一个 PVC 模板来持久化数据,以及一个 Service 来让其他服务可以访问它。

# ----------------- DB Service ----------------- #
apiVersion: v1
kind: Service
metadata:
  name: db
  labels:
    app: db
spec:
  ports:
  - port: 5432
    name: postgres
  selector:
    app: db

---

# ----------------- DB Deployment (StatefulSet) ----------------- #
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: "db"
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_USER
          value: "postgres"
        - name: POSTGRES_PASSWORD
          value: "postgres"
        volumeMounts:
        - name: db-data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: db-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

2. 缓存 (Redis)

Redis 也是一个有状态服务,但在这个例子里,我们把它当做临时缓存,所以简单地用 Deployment 和一个 ClusterIP 类型的 Service 即可。

# ----------------- Redis Service ----------------- #
apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app: redis
spec:
  ports:
  - port: 6379
    name: redis
  selector:
    app: redis

---

# ----------------- Redis Deployment ----------------- #
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:alpine
        ports:
        - containerPort: 6379

3. 工作进程 (Worker)

Worker 是一个无状态服务,它不需要 Service,因为它不接收任何外部请求,只是默默地连接 Redis 和 DB 工作。

# ----------------- Worker Deployment ----------------- #
apiVersion: apps/v1
kind: Deployment
metadata:
  name: worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: worker
  template:
    metadata:
      labels:
        app: worker
    spec:
      containers:
      - name: worker
        image: dockersamples/examplevotingapp_worker

4. 投票应用 (Vote App)

这是我们的前端,需要一个 Deployment 和一个 NodePort 类型的 Service,来让我们可以从外部浏览器访问。

# ----------------- Vote Service ----------------- #
apiVersion: v1
kind: Service
metadata:
  name: vote
  labels:
    app: vote
spec:
  type: NodePort
  ports:
  - port: 80
    name: http
  selector:
    app: vote

---

# ----------------- Vote Deployment ----------------- #
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vote
spec:
  replicas: 2 # 我们可以启动2个副本
  selector:
    matchLabels:
      app: vote
  template:
    metadata:
      labels:
        app: vote
    spec:
      containers:
      - name: vote
        image: dockersamples/examplevotingapp_vote
        ports:
        - containerPort: 80

5. 结果应用 (Result App)

这也是一个前端,同样需要 Deployment 和 NodePort Service。

# ----------------- Result Service ----------------- #
apiVersion: v1
kind: Service
metadata:
  name: result
  labels:
    app: result
spec:
  type: NodePort
  ports:
  - port: 80
    name: http
  selector:
    app: result

---

# ----------------- Result Deployment ----------------- #
apiVersion: apps/v1
kind: Deployment
metadata:
  name: result
spec:
  replicas: 1
  selector:
    matchLabels:
      app: result
  template:
    metadata:
      labels:
        app: result
    spec:
      containers:
      - name: result
        image: dockersamples/examplevotingapp_result
        ports:
        - containerPort: 80

11.4 部署并验证

现在,我们有了一个包含了所有资源定义的 voting-app.yaml 文件。

注意

  • 以上每个配置部分之间需要用 --- 分隔开。
  • 如果遇到镜像拉取问题,也可以手动导入镜像到 Minikube
    # 在宿主机上拉取镜像
    docker pull dockersamples/examplevotingapp_result
    docker pull dockersamples/examplevotingapp_vote
    docker pull dockersamples/examplevotingapp_worker
    
    # 将镜像导入到 Minikube
    minikube image load dockersamples/examplevotingapp_result
    minikube image load dockersamples/examplevotingapp_vote
    minikube image load dockersamples/examplevotingapp_worker
    

完成部署

注:
部署前可以先验证一下配置是否正确,避免部署后出现问题。
如何验证: kubectl apply -f voting-app.yaml --dry-run=client

kubectl apply -f voting-app.yaml

这个命令会一次性创建所有9个资源(4个 Deployment、1个 StatefulSet 和 4个 Service)。

mac@192 resource % kubectl apply -f voting-app.yaml 
service/db created
statefulset.apps/db created
service/redis created
deployment.apps/redis created
deployment.apps/worker created
service/vote created
deployment.apps/vote created
service/result created
deployment.apps/result created

验证状态

# 查看所有 Pods,确保它们都处于 Running 状态
kubectl get pods -w

# 查看所有 Services
kubectl get svc

在 Service 列表中,找到 voteresult 这两个 NodePort 类型的服务,我们后面验证要访问这两个的页面。

mac@192 resource % kubectl delete -f voting-app.yaml
service "db" deleted
service "redis" deleted
service "vote" deleted
service "result" deleted
deployment.apps "result" deleted
mac@192 resource % kubectl apply -f voting-app.yaml         
service/db created
service/redis created
service/vote created
service/result created
deployment.apps/result created
mac@192 resource % kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
result-59cbf4565b-tlbsl   1/1     Running   0          12m
mac@192 resource % kubectl get svc 
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
db           ClusterIP   10.99.90.74     <none>        5432/TCP       12m
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        14m
redis        ClusterIP   10.111.244.10   <none>        6379/TCP       12m
result       NodePort    10.98.46.86     <none>        80:31939/TCP   12m
vote         NodePort    10.107.56.229   <none>        80:32735/TCP   12m
mac@192 resource % 

访问应用

还记得我们前面章节提到的访问 NodePort 服务的方法吗?这里也是一样的。

由于我们使用的是 Minikube,访问 NodePort 服务有几种方式:

方法 1:使用 minikube service 命令(推荐) 分别启动 vote 和 result 服务,会自动打开浏览器并处理端口转发。

# 自动打开浏览器并处理端口转发
minikube service vote
minikube service result

方法 2:使用端口转发(最可靠)

# 将 vote 服务端口转发到本地 8080 端口
kubectl port-forward service/vote 8080:80
# 在浏览器访问 http://localhost:8080

# 将 result 服务端口转发到本地 8081 端口  
kubectl port-forward service/result 8081:80
# 在浏览器访问 http://localhost:8081

方法 3:直接访问(某些环境可行) 使用 minikube ip 获取集群IP,然后访问:

  • 投票页面http://<minikube-ip>:<vote-service-nodeport>
    • 你会看到一个让你在“猫”和“狗”之间投票的界面。随便投几票。
  • 结果页面http://<minikube-ip>:<result-service-nodeport>
    • 你会看到一个实时更新的图表,展示了猫和狗的票数。

📋 适合方法 3 的情况:

  • Linux 系统 + 裸机 Minikube(不是 Docker Desktop)
  • Windows 系统 + Hyper-V 驱动
  • macOS + VirtualBox/Parallels 驱动(不是 Docker Desktop)

❌ 不适合的情况:

  • macOS + Docker Desktop
  • Windows + Docker Desktop

如果访问失败: 如果执行 minikube service vote 出现 SVC_UNREACHABLE 错误,说明对应的 Pod 还没有正常运行。请:

  1. 等待几分钟让 Pod 完全启动
  2. 检查 Pod 状态:kubectl get pods
  3. 查看 Pod 日志:kubectl logs -l app=votekubectl logs -l app=result
  4. 确保所有依赖服务(db, redis)都已正常运行

成功了! 你已经将一个完整的多服务微服务应用,成功地部署到了 Kubernetes 集群上!

chapter_11_01.png

清理现有部署(如果要删除的话):

kubectl delete -f voting-app.yaml

11.5 旅程的终点,也是新的起点

恭喜你,完成了这本小册子的所有学习和实践!

让我们回顾一下这趟不凡的旅程:

  • 我们从 Docker 的基本概念出发,学会了用 Dockerfile 打包应用
  • 我们掌握了容器的生命周期管理,并学会了如何使用数据卷网络
  • 我们用 Docker Compose 实现了本地开发环境的“一键启停”。
  • 我们理解了 Kubernetes 为解决规模化和高可用而生的价值。
  • 我们掌握了 K8s 的核心对象:Pod, Deployment, Service, ConfigMap, PVC
  • 最终,我们亲手将一个完整的微服务应用部署到了 K8s

你现在所掌握的,已经不仅仅是一些工具的用法,而是一整套云原生应用的开发、部署和管理思想。这套思想和技能,将是你未来技术生涯中一笔宝贵的财富。

当然,Kubernetes 的世界远比我们在这本小册子中探索的要广阔得多。还有 Ingress, Helm, Prometheus, Istio 等等更多强大的工具和概念在等着你。

但这本小册子已经为你打下了最坚实的基础,为你推开了通往云原生世界的大门。

前方的路还很长,愿你保持好奇,不断探索。

Happy Hacking!