Skip to content

k8s NodePort访问流程

Posted on:2023年7月26日 at 16:02 (4 min read)

目前有 NodePort、LoadBalancer、Ingress、PortForwarding 这四种模式。NodePort 通过将服务暴露到节点上的 30000-32767 的某个端口来实现外部访问的方式。下面将通过本地 k8s 集群,部署和测试 NodePort 的访问流程。

本地集群配置如下,一个主节点,两个工作节点:

要实现 NodePort,我们只需要创建 type: NodePort 类型的 Service:

apiVersion: v1
kind: Service
metadata:
  name: test-host-svc
spec:
  type: NodePort
  selector:
    app: testhost
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
      nodePort: 30088

在上面例子中声明了 Service 的 80 端口代理 Pod 的 9376 端口,同时我们指定了一个 NodePort,将该 Service 公开到集群中的每个节点上的 30088 端口。我们能看到已经创建了 Service,其 Cluster-IP 为 10.97.80.240:

然后我们声明 Deployment,它直接返回 Pod 的 hostname:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hostnames
spec:
  selector:
    matchLabels:
      app: testhost
  replicas: 3 -- 3副本
  template:
    metadata:
      labels:
        app: testhost
    spec:
      containers:
        - name: hostnames
          image: k8s.gcr.io/serve_hostname
          ports:
            - containerPort: 9376
              protocol: TCP

效果如下:

这时,我们访问http://10.97.80.240 或者任意一台 NodeIp 的 30088 端口如http://192.168.8.210:30088 都能会成功,并且由于有 3 个副本,每次请求访问的 Pod 也会有所不同:

实现详解: 在上面 Service 和 Deployment 的执行过程中,k8s 会在每台 node 上创建类似下面的 iptables 规则,通过 iptables-save 命令可以看到:

k8s 正是利用 iptables 规则做服务转发和网络地址转换(NAT)的配置,iptables 转发规则解析如下:

  1. 将目标 IP 地址为 10.97.80.240、目标端口为 80 的 TCP 流量转发到名为 KUBE-SVC-WUKGG54QWYV6D2GE 的链。这通常是通过 ClusterIP 类型的 Kubernetes Service 将流量转发到后端的 Pod。

    -A KUBE-SERVICES -d 10.97.80.240/32 -p tcp -m comment —comment “default/test-host-svc cluster IP” -m tcp —dport 80 -j KUBE-SVC-WUKGG54QWYV6D2GE

  2. 将不来自 10.244.0.0/16 子网的目标 IP 地址为 10.97.80.240 、目标端口为 80 的 TCP 流量转发到名为 KUBE-MARK-MASQ 的链。这是为了实现网络地址转换(NAT),将流量从集群外部发送到 Service 的 ClusterIP 地址。

    -A KUBE-SVC-WUKGG54QWYV6D2GE ! -s 10.244.0.0/16 -d 10.97.80.240/32 -p tcp -m comment —comment “default/test-host-svc cluster IP” -m tcp —dport 80 -j KUBE-MARK-MASQ

  3. 将一部分流量从名为 KUBE-SVC-WUKGG54QWYV6D2GE 的链随机转发到名为 KUBE-SEP-3ARAAGGSO7FCMWRW 等的链。这通常是通过 Service 和 Endpoint 的匹配来将流量转发到后端的 Pod。这条规则的注释指示将流量从 Service 的 ClusterIP 地址转发到 10.244.1.3x:8080 的 Pod。

    -A KUBE-SVC-WUKGG54QWYV6D2GE -m comment —comment “default/test-host-svc -> 10.244.1.35:9376” -m statistic —mode random —probability 0.33333333349 -j KUBE-SEP-3ARAAGGSO7FCMWRW

    -A KUBE-SVC-WUKGG54QWYV6D2GE -m comment —comment “default/test-host-svc -> 10.244.2.31:9376” -m statistic —mode random —probability 0.50000000000 -j KUBE-SEP-UFUYNCYHFYQIBEKJ

    -A KUBE-SVC-WUKGG54QWYV6D2GE -m comment —comment “default/test-host-svc -> 10.244.2.32:9376” -j KUBE-SEP-PCRKIDGDPMTHWFQY

因此我们能看到 k8s 通过 NodePort 实际的访问流程如下:

client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort

通过上面的解析,可以看出在 k8s 中要在外部访问容器内部服务不是一件容易的事。而实际的访问和传统 web 网络架构也有很大区别。当然 NodePort 一般只做测试用,而它也有不少缺陷,例如:端口冲突、安全风险、复杂的网络配置导致的性能损失等等。