0经验小白运维生产 k8s 翻车日记2 - 应用部署失败

最近接到这样一个应用部署需求有点特别,他说他的程序中用到了webscoket,这让没有部署过webscoket应用的小白心里一惊,这咋办?我该说多久可以部署好?部署上线出了问题让我帮助排查我不是只能胡乱抓瞎吗?抱着惶恐的心情开始分析了一下他应用的部署需求,服务是在一个容器中的,但是暴露了3个端口分别用来处理不同的服务:

  • 前端 Web 站点 3000 端口
  • 后端 API 服务 3001 端口
  • 后端 Websocket 聊天服务 3002 端口

让我们尝试部署一下

先写一个部署文件,将这个程序的镜像运行起来。

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
    name: chatapp-deployment
spec:
    replicas: 1
    selector:
        matchLabels:
            app: chatapp
    template:
        metadata:
            labels:
                app: chatapp
        spec:
            containers:
                - name: chatapp
                  image: chatapp

Line 1 指定了 K8S API 版本为 v1, Line 2 定义了一个类型为 Deployment 的资源,并且这个 Deployment 资源的名称为 chatapp-deployment

Line 5 的 spec 描述了这个 deployment 的规范,replicas 为 1,分布式部署的话,就该这个参数就可以,比如写 5,K8S 就会部署 5 个程序,默认情况下这 5 台机器不一定会部署在同一个机器上。

这里有个细节 这个 matchLabelsLine 12labels 有什么区别呢?

yaml 复制代码
  selector:
        matchLabels:
            app: chatapp
            
... ...

           labels:
               app:chatapp

matchLabels 和 labels 可以相同,并且不会产生副作用。将它们设置为相同的值可以确保选择器选择与指定标签匹配的 Pod,并将它们管理在同一个 Deployment 或 ReplicaSet 下。这么说太抽象,举个🌰,比如我们在 Deployment 中申明部署 5 个程序,那 K8S 就会保证只有 5 个 POD,它怎么知道这 1个 Deployment 对应的那几个 POD 呢? 就是通过这个 label!为了避免潜在的问题和混淆,通常建议在定义 Deployment 或 ReplicaSet 的 selector 时,与 Pod 的 metadata.labels 使用相同的标签。

Line 14 描述了 POD 的规范,比如指定了从哪里拉镜像。

在 K8S 上执行这个配置文件后,业务方给的应用就算在 K8S 上跑起来,但是他们没有 IP 还不能访问,下面我们通过 Service 给他们创建 IP。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
    name: chatapp-svc
spec:
    selector:
        app: chatapp
    ports:
        - name: web	
          protocol: TCP
          port: 3001 
          targetPort: 5001 
        - name: api	
          protocol: TCP
          port: 3002
          targetPort: 5002 
        - name: ws
          protocol: TCP
          port: 3003
          targetPort: 5003 

我们将这个容器的 3 个服务端口都暴露出来,在Kubernetes中,targetPort和port是部署service时的两个重要参数。

  • targetPort 是指定Pod中容器的端口号,用于将流量转发到Pod中运行的应用程序。
  • port 是指定Service的端口号,用于将流量引入Service并将其转发到Pod。

简而言之,targetPort 就是开发程序中写的,他们写的多少,那就是多少,开发应该将端口写成配置文件,可以通过配置文件灵活改动端口。 port 就只是个 service 端口,没有含义,当有人请求这个 service,我们将他转发到 实际应用的端口就行(也就是targetPort) ,所以为了心智负担低,这里两个值写一样。

然后再去 K8S 上执行这个 Service 申明后,新建一个 Nginx ingress 做一个反代,将 3 个服务通过一个域名 "串" 起来如图

yaml 复制代码
spec:
  rules:
    - host: chatapp.com
      http:
        paths:
          - backend:
              service:
                name: chatapp-deployment
                port:
                  number: 3001
            path: /
            pathType: Prefix
          - backend:
              service:
                name: chatapp-deployment
                port:
                  number: 3002
            path: /api
            pathType: Prefix
          - backend:
              service:
                name: chatapp-deployment
                port:
                  number: 3003
            path: /wss
            pathType: Prefix

目前我们的部署架构图如下:

然而,当点到聊天页面时,终于,墨菲定律发生了,害怕什么来什么,聊天页面挂掉了!

然后大脑开始飞速运转:

  • 这个前端转发是 OK 的,不然看不到聊天页面
  • 这个后端 API 转发也是 Ok 的,API 接口访问不报错
  • 这个 Websocket 就报上面的错,难道?难道 Nginx ingress 在反代 websocket 需要特殊配置?

快速找到 Nginx ingress 官网看看相关介绍

从这个描述中看,Nginx ingress 反代应该没有什么东西需要配置的,就当它是一个 Nginx 配置就行。

先排查一下 websocket 服务是否正常提供服务, 创建一个 NodePort 将这个服务单独暴露出来,然后通过 Postman 连接试试。

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 3003
      nodePort: 30000 
  selector:
    app: chatapp

用 Postman 单独连接一下它这个 websocket 服务试试呢

显示没问题,websocket 聊天服务器已经连接上了,那奇怪了? 那有没有可能是业务方程序自己的问题呢?报错页面打个断点看看。

看这个报错明显是前端 call 浏览器的 websocket API 在创建连接时候失败了。

这 URL 不对!程序的锅! 这连接连协议头都没带好吗? 自己做了一下实验

错误的:

和业务方的报错一模一样!

正确的:

我去看看他代码和打包文件,收集好证据,知己知彼!

很快就在程序的 .env 发现了问题:

.env 复制代码
api_endpoint='/api'
chat_endpoint=/wss'

这里的 chat_endpoint 地址没填,还是调试时候的,应该加上协议,是一个完整的地址,websocket API 才能创建连接成功,否则连校验都过不去,都不会发起连接。

于是修改为 chat_endpoint=ws://localhost/wss,但是看到这里突然发现之前的部署架构"太多余",我恍然大悟为什么他 3 个程序写在一个容器里了, 这样部署起来简单! 前端打包时配置文件写成 localhost + 对应端口号,部署时候只要将 ingress 的流量转发到前端即可!所以改一下这个配置文件和架构图然后重新打包前端程序。

.env 复制代码
api_endpoint='http://localhost:3002/api'
chat_endpoint=ws://localhost:3003'

同时把 ingress 的转发规则删掉,就是下面这两条

yaml 复制代码
          - backend:
              service:
                name: chatapp-deployment
                port:
                  number: 3002
            path: /api
            pathType: Prefix
          - backend:
              service:
                name: chatapp-deployment
                port:
                  number: 3003
            path: /wss
            pathType: Prefix

最终部署架构图为:

见证奇迹:

看起来这次的部署和 websocket 基本没有关系,但是为了未来更好的故障排查,这里还是巩固一下 websocket 的连接过程:

  1. 客户端发送一个 HTTP 请求到服务器,请求升级到 WebSocket 协议。

    • 也就是说浏览器会发送一个普通的 HTTP GET 请求到服务器,其中包含了一些特殊的头部信息,如 Upgrade 和 Connection,表明客户端希望升级到 WebSocket 协议。
    • 如果客户端没发这个请请求头,说明客户端代码的问题
  2. 服务器收到请求后,会检查是否支持 WebSocket 协议,如果支持,则返回一个 101 Switching Protocols 的响应,表示协议切换成功。

    • 服务器在收到客户端的请求后,会进行协议切换的验证。如果服务器支持 WebSocket 协议,会返回一个 101 Switching Protocols 的响应,表明协议切换成功,可以进行 WebSocket 通信。
    • 如果服务端没返回这个,客户端得到类似升级失败或者把请求当做一个普通 Http 请求处理,那说明是服务端代码问题,没有协商成功 将 http 升级为 ws
  3. 客户端收到响应后,连接成功建立,可以开始进行 WebSocket 通信。

    • 客户端接收到服务器返回的 101 Switching Protocols 的响应后,表示连接已经成功建立。此时,客户端和服务器可以开始进行 WebSocket 通信,并通过 WebSocket 连接发送和接收消息。
    • 这时候浏览器的 network 里我们可以通过过滤 ws标签来查看 message了

同时开发同学为了保障在分布式环境中 websocket 应用不出问题,还应该至少考虑下面 3 种情况:

  • 会话同步:如果需要在多个服务器之间共享会话状态,可以考虑使用会话同步机制。可以使用共享存储或数据库来存储会话状态,并确保多个服务器可以访问和更新该状态。这样可以确保在不同服务器上的 WebSocket 连接都可以访问到相同的会话状态。

  • 消息广播:在分布式环境中,确保 WebSocket 消息可以正确地广播到所有连接的客户端是很重要的。可以使用消息队列或其他分布式通信机制来实现消息广播。当一个服务器接收到消息时,它可以将消息发送到消息队列,然后其他服务器从队列中获取消息并发送到连接的客户端。

  • 容错处理:在分布式环境中,需要处理网络故障、服务器崩溃等异常情况,以确保 WebSocket 连接的可靠性和稳定性。可以使用故障检测和容错机制来监测服务器的状态,并在服务器故障时自动切换到其他可用的服务器。此外,可以实现断线重连机制,以便客户端在连接中断后能够重新建立连接。

如果本文对你有帮助,请点个赞和关注吧❤️,我会持续更新有价值的技术和视野,谢谢!

相关推荐
想用offer打牌3 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX4 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法5 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端