博客 / 詳情

返回

鏈路追蹤--使用nginx-ingress-controller記錄後端pod真實ip

前言

常見的nginx做反向代理架構,nginx轉發到後端的多台服務

                 ┌─────────┐
                 │backend-1│
                 └─────────┘

                 ┌─────────┐
┌─────┐          │backend-2│
│nginx│  ────►   └─────────┘
└─────┘
                     ...

                 ┌─────────┐
                 │backend-n│
                 └─────────┘

在傳統的部署,如直接部署在nginx上,或者部署在docker中,nginx轉發到後面的的backend是固定在nginx的配置文件中的,這也很方便排查問題節點在哪兒:直接看access_log中的upstream_addr即可

該部署的優點非常明顯,簡單高效易維護,每一條鏈路,每一個狀態都非常清晰。但是缺點也很明顯,擴縮容艱難,一旦需要擴容backend,就非常麻煩了,需要首先部署backend,修改nginx轉發配置,最後重啓nginx

為了提高擴縮容效率,將該架構搬到k8s中

                                        ┌─────────┐
                                        │backend-1│
                                        └─────────┘
                                        ┌─────────┐
┌─────┐       ┌───────────────┐         │backend-2│
│nginx│ ────► │backend-service│ ────►   └─────────┘
└─────┘       └───────────────┘            ...
                                        ┌─────────┐
                                        │backend-n│
                                        └─────────┘

通過k8s-service的能力,自動做服務發現,每當上/下線一個backend,就會動態發現backend的個數,從此之後,擴縮容就會變得非常簡單

但是新的問題來了,問題出現時,比如某個backend出現問題,導致從nginx的日誌出現了502,在access_log中顯示的upstream_addr並不是後端backend的地址,而是backend-service的地址,無法立刻知道到底是哪個backend 出問題

問題出現了,如何跟蹤一條request,能夠明確知道它進入了哪一個pod,就是本文需要探索的內容

nginx-ingress

如果架構遷移還在方案驗證階段,那麼恭喜,這個方法可以一勞永逸的解決nginx的問題,那就是利用nginx-ingress-controller來作為入口

安裝

這個就不班門弄斧了,直接祭出官網

  • github倉庫地址
  • 安裝腳本地址

這一步需要注意的點不多,就是鏡像的問題,鏡像有可能拉不下來,至於怎麼解決,大家可以看我之前的文章,關於如何拉鏡像

安裝完成之後

▶ kubectl -n ingress-nginx get pod
NAME                                        READY   STATUS    RESTARTS       AGE
ingress-nginx-controller-78f7f8bd46-tqjm4   1/1     Running   0              1m

nginx-ingress-controller本質和其他的nginx沒有什麼區別,只不過有一些額外的功能來輔助nginx提供更好的服務,這個一會再討論

backend後端服務

接着創建一個backend服務,使用最簡單的python tornado作為web容器

test.py

from tornado.ioloop import IOLoop
import tornado.httpserver as httpserver
import tornado.web
import os

class TestFlow(tornado.web.RequestHandler):
    def get(self):
        ret = 'i am backend in {}'.format(os.environ['HOSTNAME'])
        self.write(ret)

    def post(self, *args, **kwargs):
        print(self.request.body.decode('utf-8'))


def applications():
    urls = []
    urls.append([r'/', TestFlow])
    urls.append([r'/test', TestFlow])
    return tornado.web.Application(urls)

def main():
    app = applications()
    server = httpserver.HTTPServer(app)
    server.bind(10000, '0.0.0.0')
    server.start(1)
    IOLoop.current().start()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt as e:
        IOLoop.current().stop()
    finally:
        IOLoop.current().close()

將其打包成鏡像

Dockerfile

FROM python:3.11-alpine

WORKDIR /opt
RUN pip3 install tornado  -i https://mirrors.aliyun.com/pypi/simple/
ADD test.py /opt/test.py

CMD ["python3", "test.py"]

docker build . -t backend-service:v1

製作完成之後將其放入k8s,並且使用一個service作為訪問入口

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - image: backend-service:v1
        imagePullPolicy: Never
        name: backend
        ports:
        - containerPort: 10000
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: default
spec:
  ports:
  - port: 10000
    protocol: TCP
    targetPort: 10000
  selector:
    app: backend
  type: ClusterIP

NAME                          READY   STATUS    RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
backend-6d4cdd4c68-mqzgj      1/1     Running   0          2m      10.244.0.40   wilson   <none>           <none>
backend-6d4cdd4c68-qjp9m      1/1     Running   0          2m      10.244.0.60   wilson   <none>           <none>

大功告成

創建ingress

繼續創建ingress,ingress其實就是nginx轉發到後端服務的配置

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-test-ingress
  namespace: default
spec:
  rules:
  - host: wilsonchai.com
    http:
      paths:
      - backend:
          service:
            name: backend-service
            port:
              number: 10000
        path: /
        pathType: Prefix

測試結果

折騰了半天,先捋一下整個的路徑,我們創建了nginx-ingress-controller,它是整個系統的入口,其次創建了後端服務backend,請求的終點是該服務,還創建了一系列的service以及ingress,他們的關係如圖所示

watermarked-nginx-ingress_1

  • 請求通過nodePort進來進入nginx-ingress。
    ▶ kubectl -n ingress-nginx get svc
    NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
    ingress-nginx-controller             LoadBalancer   10.98.224.124   <pending>     80:30296/TCP,443:31592/TCP   30m
    
  • nginx-ingress檢查ingress規則,將請求轉發至backend-service
    ▶ kubectl  get svc backend-service
    NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE
    backend-service   ClusterIP   10.105.148.194   <none>        10000/TCP   30m
    
  • backend-service隨機轉發該流量進入一個pod
    ▶ kubectl  get pod -owide
    NAME                          READY   STATUS    RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
    backend-6d4cdd4c68-mqzgj      1/1     Running   0          30m     10.244.0.40   wilson   <none>           <none>
    backend-6d4cdd4c68-qjp9m      1/1     Running   0          30m     10.244.0.60   wilson   <none>           <none>
    

訪問集羣: curl -H "host: wilsonchai.com" 127.0.0.1:30296

▶ curl -H "host: wilsonchai.com" 127.0.0.1:30296
i am backend in backend-6d4cdd4c68-qjp9m

請求順利到達了backend,再檢查一下nginx-ingress的日誌

10.244.0.1 - - [28/Nov/2025:09:28:45 +0000] "GET / HTTP/1.1" 200 40 "-" "curl/7.81.0" 78 0.001 [default-backend-service-10000] [] 10.244.0.60:10000 40 0.001 200 1896b48d60ef31861478e713d65c9660

upstream_addr上面,顯示的就是後端pod的ip: 10.244.0.60

至此,終於解決了開篇提出的問題,如果有request報錯502、503等,可以精準的定位到時哪個pod出現了問題

小結

Nginx Ingress Controller 能夠直接記錄後端 Pod IP,是因為它在轉發流量時,繞過了 Service 的 Cluster IP,直接與後端 Pod 建立連接,Nginx Ingress Controller 並不是一個普通的客户端,而是一個特殊的 K8s Controller

  • Service 與 Endpoints: 當創建一個 Service 時,K8s 會自動創建一個相應的 Endpoints 對象(或 EndpointSlice)。這個 Endpoints 對象會實時記錄所有與 Service 標籤匹配的 後端 Pod 的實際 IP 地址和端口
  • Controller 實時同步: Nginx Ingress Controller 會持續監聽 K8s API Server 中所有 Service 對應的 Endpoints 對象的變化(即 Pod 的創建、刪除、就緒狀態改變)

檢驗一下,再次訪問: curl -H "host: wilsonchai.com" 127.0.0.1:30296

登錄到nginx-ingress-controller容器,使用netstat檢查連接狀態

ingress-nginx-controller-78f7f8bd46-tqjm4:/etc/nginx$ netstat -anpt | grep ESTABLISH
tcp        0      0 10.244.0.16:36074       10.244.0.40:10000       ESTABLISHED -

果然直接與後端的pod建立了連接

後記

盲目的搬進k8s真的是完全錯誤的,解決了新的問題,又帶來更多不可控的問題,所以架構遷移真的需要做好調研:新架構能夠滿足需求,並且新架構的缺點也能夠克服或者忍受,方可行動

有位兄弟説了,如果沒有使用nginx-ingress-controller,而是直接把nginx直接搬遷到k8s,那怎麼辦?並且該文只是解決了nginx訪問後端的問題,如果是服務之間的訪問,又該怎麼辦呢?並且這是http協議,如果是其他協議, 比如gRPC,又該怎麼辦呢?

大家稍安勿躁,本文只是一個引子,後面會把這些問題統統説清楚,敬請期待

聯繫我

  • 聯繫我,做深入的交流


至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.