V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
eephee
V2EX  ›  Kubernetes

请教一个在 k8s 里面使用 nginx 代理 headless service 的问题

  •  
  •   eephee · 30 天前 · 1847 次点击

    背景

    如题,我目前在 k8s 里面部署了一个 nginx 服务( Deployment )和一个后端服务 backend ( StatefulSet ),backend 创建了三个副本:backend-0, backend-1, backend-2 。

    nginx 服务目前充当 API 网关的角色,其核心配置如下:

    upstream backend {
        hash $request_uri;
        server backend-0.backend-headless.default.svc;
        server backend-1.backend-headless.default.svc;
        server backend-2.backend-headless.default.svc;
    }
    
    
    server {
       ...
       location /api/ {
           proxy_pass backend;
       }
    }
    

    配置说明:nginx 反代 backend 的三个 headless service ,并且使用一致性哈希以达到 对于特定的 URL 的请求,固定转发到唯一的副本。


    问题

    目前我面临的问题如下:

    1. 当 backend 更新或重启后,nginx 反代会出错,报错 504 (记不太清了应该是 504 )

    这是因为 backend 重启之后,Pod 变了,其 headless service 域名对应的 IP 也变了。但是 nginx 默认只会在启动时解析一遍域名,无法做到动态解析(动态解析功能要商业版 nginx 才有),因此 backend 重启之后 nginx 无法与 backend 建立连接,所以报错。

    我在网上搜了一圈,我目前是在用这个第三方模块 + 自己编译 nginx ,但是并不喜欢这个做法,而且该项目兼容的 nginx 版本较低。

    我在想有没有其他更好的办法,再不行就换 tengine ,但是 tengine 我还没用过也许可以试一试

    1. 无法灵活地增减 backend 的副本数

    目前 backend 是三个副本,假如我要增加到 5 个副本,我需要手动修改 nginx 配置里面的 upstream server 。虽然我目前是通过环境变量来配置 upstream server 的,但是仍然无法避免需要手动修改环境变量的麻烦。

    如果要解决这个问题,似乎只能自己开发和部署一个专门更新配置的小服务,除此之外还有更方便的解决办法吗?

    另外想咨询一下有人知道 Kong 能否在上面两个场景中派上用场吗?我打算抽空也去调研一下 Kong

    40 条回复    2024-12-05 09:25:12 +08:00
    XiLingHost
        1
    XiLingHost  
       30 天前
    有试过直接用 ingress 而非自己部署 nginx 服务进行服务的暴露吗
    rrfeng
        2
    rrfeng  
       30 天前
    watch service 写 upstream 然后 reload nginx ,这是最简单的方法。
    eephee
        3
    eephee  
    OP
       30 天前
    @XiLingHost

    是因为我们有一些 rewrite/hash 之类的需求,所以得用 nginx 来做。

    至于为什么不用 ingress-nginx ,是因为我们部署在华为云上面,就用了华为云的集群自带的 cce ingress controller 了,而 cce 除了路由好像没有提供更多的功能。
    dropdatabase
        4
    dropdatabase  
       30 天前
    1.加就绪探针
    2. backend StatefulSet 加一个 service ,proxy_pass 直接配置这个 service 。
    3.用 headless service 的用意是?
    eephee
        5
    eephee  
    OP
       30 天前
    @dropdatabase
    > 用 headless service 的用意是?

    我们有一个需求,就是 “对于特定的 URL 的请求,需要固定转发到唯一的副本”,如果只用一个 service 的话,就没法达到这个目的
    eephee
        6
    eephee  
    OP
       30 天前
    @rrfeng 嗯嗯,我也在考虑这个方法,就是感觉有点太耦合了
    defunct9
        7
    defunct9  
       30 天前
    nginx-dynamic-upstream ,后端服务加 sidecar ,重启第一步就通知 nginx 去刷一下配置。
    eephee
        8
    eephee  
    OP
       30 天前
    @defunct9 感谢 ssh 哥,我也再考虑考虑这个做法
    winglight2016
        9
    winglight2016  
       30 天前
    @dropdatabase #4 我们也是用类似的方式,还使用了 pre-stop 去调用内部接口触发 spring 的退出。

    根据 url 分发到特定 pod ,这个需求很奇怪,可以使用 gateway 做转发规则或者 nacos 的服务发现。
    hejw19970413
        10
    hejw19970413  
       30 天前
    如果你是单个服务一对一的配置 nginx 的推荐你用 watch 去重启 nginx ,如果是一对多的情况下不建议这么干,因为 nginx 频繁重启会有问题。目前对于 k8s 来说最好的代理是 envoy ,支持动态配置,只不过就是对接起来有点困难,但是简单的用是可以的。
    NoobPhper
        11
    NoobPhper  
       30 天前
    等下 你们的 ingress 是什么, 按理说 这个不用再 套一层的
    eephee
        12
    eephee  
    OP
       30 天前
    @winglight2016

    > 可以使用 gateway 做转发规则或者 nacos 的服务发现

    我们就是拿 nginx 做 api gateway 的,所以这一层转发就打算在 nginx 这里做
    eephee
        13
    eephee  
    OP
       30 天前
    @hejw19970413 不瞒你说,我们有 3 个类似 backend 这样的 StatefulSet 服务,而且有 3 个 nginx 这样的服务。也就是说 3x3=9 的场景...
    dropdatabase
        14
    dropdatabase  
       30 天前
    @winglight2016 细说根据 url 分发到特定 pod ??

    在流量接入层配置按 URL 转发就好了吧
    eephee
        15
    eephee  
    OP
       30 天前
    @NoobPhper 我们是用的华为云集群的 cce ingress controller ,然后集群内部再用 nginx 做请求分发到各个后端服务
    defunct9
        16
    defunct9  
       30 天前
    噢,也可以用 openresty 做分发器,lua 读取 redis 的配置往后分发,我们就是这样搞的灰度
    ser3w
        17
    ser3w  
       30 天前
    @eephee 最简单的方法 改为变量类型的 proxy_pass
    resolver <coredns svc ip> valid=5 ipv6=off;

    set $wx_upstream "";
    set $wx_host "";
    location / {
    proxy_pass $wx_upstream;
    }
    nothingLeft
        18
    nothingLeft  
       30 天前
    我不明白,你都用 k8s 了,为什么还用 nginx 的 upstream
    defunct9
        19
    defunct9  
       30 天前
    ser3w 是正解
    justdoit123
        20
    justdoit123  
       30 天前
    楼主可以再深入描述一下,业务的细节,这样其他人可以给更好的建议。

    另外,想请教一下 "一致性哈希以达到 对于特定的 URL 的请求,固定转发到唯一的副本。" 这个需求,在扩容或缩容之后,如何保证之前的请求,依然分流到之前的副本?
    eephee
        21
    eephee  
    OP
       30 天前
    @ser3w 是的这个我也有查到,但是这个只针对单个 url 生效(即 wx_upstream 是一个 url ),无法对 upstream 生效,根据 nginx 的说法的话 https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
    eephee
        22
    eephee  
    OP
       30 天前
    @justdoit123

    > 业务的细节,这样其他人可以给更好的建议

    这个说来话长了,关键词是 协同编辑、websocket 之类的。最开始制定技术方案的时候定下来的这个需求。

    > 如何保证之前的请求,依然分流到之前的副本

    我们目前也在考虑这块问题
    kennylam777
        23
    kennylam777  
       30 天前
    反正都 hardcode 的 nginx config, 即是 3 個副本是固定數量的。

    1 的 504 問題很簡單, 三個副本獨立各自有 ClusterIP 的 service 即可解決問題, ClusterIP 是固定 IP 不會跟隨 Pod IP 變動。


    2 的 hash 問題, 我是用 Istio 解決的, Istio 有自己的 resolving 機制不跟隨 k8s services 做法, 它會自行更新 Pod IP 比較有彈性。
    winglight2016
        24
    winglight2016  
       30 天前
    @eephee 大部分网关都提供 url 对下游服务器的配置,kong 还提供 route by header 功能,我们一般都是用 service 作下游服务,lz 这种指定 pod 方式比较少见,但是依然可以通过 ip 的方式配置。


    @eephee 自己配 nginx 有点难以维护,用现成的网关服务还能通过 API 动态配置
    ser3w
        25
    ser3w  
       30 天前
    @eephee 要是需要 url 亲和性的话我建议你上网关 apisix 之类的,nginx 不好维护, 但如果你把 有状态的 3 副本应用调整为 3 个单副本的 deployment + 3 个 svc 就可以实现你这种,不过有点麻烦了
    justdoit123
        26
    justdoit123  
       30 天前
    @eephee 按你的描述,感觉应该先解决这个分布式有状态扩缩容问题。 然后你这个问题可能就不是什么问题。
    kennylam777
        27
    kennylam777  
       30 天前
    @ser3w 3 個 service 的方法就是我說過的 1, 但問題還是 2 的 load balancing 。

    其實我自己有這種 hash 指定 backend 場景, 解決方法也很簡單, 沒有用多個 service 這麼麻煩, Istio 會參考 service 的配置但不觸及 ClusterIP, 這個我研究過。

    直接上 Istio, EnvoyFilter 用 lua 加一個"x-hash-key"的 HTTP header, 然後在 DestinationRule.spec.trafficPolicy.loadBalancer.consistentHash.httpHeaderName 設成"x-hash-key"就好

    ChatGPT 就能給出代碼細節。
    buffzty
        28
    buffzty  
       30 天前
    我有个服务跟你场景一样, 长链接服务器有 2-n 个,动态变化
    我的做法 一个 ingress+ 多个 service + 一个 statusSet
    比如 a.com/1/ 路由到 a-svc-01 a.com/$n/ 路由到 a-svc-$n
    a-svc-01 指向 a-pod-01, a-svc-$n 指向 a-pod-$n

    我觉得 前面不用放 nginx,也不用 headless service. 你这是把简单问题复杂化了.

    动态更新也很简单,pod 根据 hpa 动态调整, svc ingress 预生成 n 个,不用人为干预
    smallparking
        29
    smallparking  
       30 天前 via Android
    那个 我们公司有个开原的 kic 不确定是不是满足你的需求,可以看看 https://gitee.com/njet-rd/open-njet-kic
    bay1
        30
    bay1  
       30 天前
    我们感觉跟 28 楼差不多,也是一个 StatefulSet 对应 多个 svc ,svc 端口号和 pod index 联动,联动部分是通过 helm 部署直接自动填充的。就可以针对多个 svc 做路由 分发
    nivalxer
        31
    nivalxer  
       30 天前   ❤️ 1
    @eephee 楼主的有状态服务是为了根据$request_uri 进行 hash 做会话亲和,还是有其他特殊业务?

    如果只是会话亲和的话,推荐用 27 楼的方法上 Istio ,也能解决后续扩容缩容的问题。

    同华为云 CCE ,早期在用华为云的 cce ingress ,后面陆续换成 Istio gateway ,配合 EnvoyFilter ,实现了 WebSocket 前期握手( signalr )和后续链接的会话亲和。
    li24361
        32
    li24361  
       29 天前
    楼主还是用传统架构的思维去思考的,k8s 不是这么用的,它本身就带了服务编排。
    最简单的就是使用 service ,service 自己会对应 deployment ,无论增减 pod 都可以自动负载均衡
    复杂的可以参考 27 、31 使用 istio 或者安装自己的 lb
    wenche
        33
    wenche  
       29 天前
    根本原因是:proxy_pass 中如果配置的是域名地址,Nginx 只有在 start / restart / reload 时,才会连接一次域名服务器解析域名,缓存解析的结果,后续则不会根据解析结果的 TTL 进行自动更新。增加一个 svc 就好了
    oaa
        34
    oaa  
       29 天前
    你需要一个 ingress-controller
    https://github.com/alauda/alb
    Serino
        35
    Serino  
       29 天前
    我就是因为 nginx 的 header 搞不定,换了 caddy
    feedcode
        36
    feedcode  
       29 天前
    你要做的是 watch endpointslice, 然后去更新 uppstream, 这不就是 nginx ingress controller 吗,加上 annotations 就是你现在实现的效果
    nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri"

    对于这个 “至于为什么不用 ingress-nginx ,是因为我们部署在华为云上面,就用了华为云的集群自带的 cce ingress controller 了,而 cce 除了路由好像没有提供更多的功能。” 看来不了解 ingress controller, k8s 里可以安装多个的啊,谁告诉你只能用一个? 你用 ingressClass 区分就好了
    hejw19970413
        37
    hejw19970413  
       29 天前
    @eephee 那你这种服务变动还好,不是很大,如果不频繁重启 pod 的情况下,对现有的架构不做重大的调整,那就 watch 动态变更 nginx 。还有一种方式不知道你可以不,就是根据具体需求建立不同的 service ,然后 watch pod, 不要自动的添加 endpoint 中 IP ,而是要手动更新 endpoint IP ,这样你不用频繁重启 nginx,因为 nginx 中的地址都是 service ip, 通过 service IP 就能动态找到 POD IP 了。这种情况就是针对你这种很少的服务可以,因为无论多少都是固定的。
    n0bug
        38
    n0bug  
       29 天前
    用 envoy 替换 nginx 就行,我做过类似的场景,当时 PoC 就发现 nginx 不好做
    yijiangchengming
        39
    yijiangchengming  
       27 天前
    nginx ingress 支持按 url 分流啊
    Alliot
        40
    Alliot  
       16 天前
    关于原生的 nginx 动态加载 upstream 可以参考这篇博客中的做法: https://www.iots.vip/post/nginx-proxy-pass-aws-alb-504-issue
    使用变量引入 upstream 这样就解决了 upstream 在更新后 504 的问题了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1801 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 16:21 · PVG 00:21 · LAX 08:21 · JFK 11:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.