目前有 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 转发规则解析如下:
- 将目标 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
- 将不来自 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
- 将一部分流量从名为 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 一般只做测试用,而它也有不少缺陷,例如:端口冲突、安全风险、复杂的网络配置导致的性能损失等等。