前言在 Kubernetes 集群中Pod 是最小的部署单元但 Pod 的生命周期是短暂的——当 Pod 发生故障或被重新调度时其 IP 地址也会随之改变。这就带来一个问题如果一组 Pod 需要对外提供稳定服务例如 HTTP 服务客户端如何能始终访问到正确的 Pod 呢Kubernetes 给出的标准答案就是Service。Service 是一种抽象对象它定义了一组 Pod 的逻辑集合以及访问该集合的策略。Service 拥有自己固定的 Cluster IP客户端只需访问这个 IP而无需关心后端 Pod 的 IP 变化。本文将从 Service 的基本概念入手通过实际演示和 iptables 底层分析深入理解 Service 的工作原理并展示如何在集群内部和外部访问 Service。一、Service 概念与作用Kubernetes 中的 Service 从逻辑上代表了一组 Pod。具体哪些 Pod 属于该 Service由 Service 定义中的selector标签选择器来决定。Service 拥有一个固定不变的 IP 地址Cluster IP客户端只需访问这个 IPKubernetes 负责建立和维护 Service 与后端 Pod 的映射关系。无论后端 Pod 如何增删或 IP 如何变化对客户端都不会产生任何影响。Service 提供了以下核心能力稳定的访问入口Cluster IP负载均衡将请求分发到多个后端 Pod服务发现通过 DNS 或环境变量二、创建 Service 并演示用途2.1 创建一个 Deployment首先我们创建一个 Deployment它负责管理三个 httpdApachePod并使用标签run: httpdservice标记它们。http-service.yml内容apiVersion: apps/v1 kind: Deployment metadata: name: httpservice-deployment spec: replicas: 3 selector: matchLabels: run: httpdservice template: metadata: labels: run: httpdservice spec: containers: - name: httpdservice image: httpd ports: - containerPort: 80执行以下命令创建 Deployment[rootmaster service]# kubectl apply -f http-service.yml2.2 集群内部直接访问 Pod每个 Pod 会被分配一个集群内部可访问的 IP例如 10.244.1.28但该 IP 仅能在 Kubernetes 集群中的节点和容器内访问[rootmaster service]# curl 10.244.1.28 htmlbodyh1It works!/h1/body/html2.3 创建 Service接下来创建一个 Service将流量从 Service 的8080端口转发到 Pod 的80端口并通过selector匹配刚才的 Pod。service.yml内容apiVersion: v1 kind: Service metadata: name: service-httpd spec: selector: run: httpdservice ports: - protocol: TCP port: 8080 targetPort: 80创建 Service[rootmaster service]# kubectl apply -f service.yml [rootmaster service]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 none 443/TCP 10d service-httpd ClusterIP 10.101.20.80 none 8080/TCP 175m现在可以通过 Service 的 Cluster IP 访问后端服务[rootmaster service]# curl 10.101.20.80:8080 htmlbodyh1It works!/h1/body/html2.4 查看 Service 与 Pod 的对应关系[rootmaster service]# kubectl describe svc service-httpd Name: service-httpd Namespace: default Labels: none Annotations: Selector: runhttpdservice Type: ClusterIP IP: 10.101.20.80 Port: unset 8080/TCP TargetPort: 80/TCP Endpoints: 10.244.1.27:80,10.244.1.28:80,10.244.2.21:80 Session Affinity: None Events: none从输出可以看到该 Service 的 Endpoints 包含了三个 Pod 的 IP 和端口。三、Service IP 内部实现原理分析Pod 的 IP 是直接配置在容器网络中的那么 Service 的 Cluster IP 是如何生效的流量又是如何转发到后端 Pod 的呢答案是iptables。Service 的 Cluster IP 是一个虚拟 IP由每个节点上的 iptables 规则管理。我们可以通过iptables-save命令查看规则。3.1 查看 Cluster IP 相关的 iptables 规则[rootmaster service]# iptables-save | grep 10.101.20.80 -A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.101.20.80/32 -p tcp -m comment --comment default/service-httpd: cluster IP -m tcp --dport 8080 -j KUBE-MARK-MASQ -A KUBE-SERVICES -d 10.101.20.80/32 -p tcp -m comment --comment default/service-httpd: cluster IP -m tcp --dport 8080 -j KUBE-SVC-5FEAUJNCXGRSJFN5解释第一条规则对于源 IP 不是集群 CIDR10.244.0.0/16的请求打上 MASQ 标记用于 SNAT。第二条规则目标 IP 为 10.101.20.80、端口 8080 的请求跳转到自定义链KUBE-SVC-5FEAUJNCXGRSJFN5。3.2 查看负载均衡链[rootmaster service]# iptables-save | grep KUBE-SVC-5FEAUJNCXGRSJFN5 -A KUBE-SVC-5FEAUJNCXGRSJFN5 -m comment --comment default/service-httpd: -m statistic --mode random --probability 0.33333333333333 -j KUBE-SEP-KIILMGZB3LCHX6UB -A KUBE-SVC-5FEAUJNCXGRSJFN5 -m comment --comment default/service-httpd: -m statistic --mode random --probability 0.50000000000000 -j KUBE-SEP-JPXQHLXQSDHS2NX -A KUBE-SVC-5FEAUJNCXGRSJFN5 -m comment --comment default/service-httpd: -j KUBE-SEP-5H6K0BYUZ646ISHJ每条规则按概率将流量跳转到不同的KUBE-SEP-*链实现了类似轮询的负载均衡。3.3 查看最终的 DNAT 规则[rootmaster service]# iptables-save | grep KUBE-SEP-KIILMGZB3LCHX6UB -A KUBE-SEP-KIILMGZB3LCHX6UB -s 10.244.1.27/32 -j KUBE-MARK-MASQ -A KUBE-SEP-KIILMGZB3LCHX6UB -p tcp -j DNAT --to-destination 10.244.1.27:80其他KUBE-SEP-*链类似分别指向 10.244.1.28:80 和 10.244.2.21:80。结论iptables 将访问 Service Cluster IP 的流量随机转发到后端 Pod实现负载均衡。每个节点上的 iptables 规则完全一致因此整个集群中任意节点都可以通过 Service 的 Cluster IP 访问。四、通过 DNS 访问 Service除了通过 Cluster IP 访问Kubernetes 还内置了 DNS 服务CoreDNS为 Service 提供域名解析。4.1 查看 CoreDNS 部署[rootmaster service]# kubectl get deployment --namespacekube-system NAME READY UP-TO-DATE AVAILABLE AGE coredns 2/2 2 2 10d每当有新 Service 创建CoreDNS 会自动添加一条 DNS 记录格式为SERVICE_NAME.NAMESPACE_NAME.svc.cluster.local。4.2 使用 busybox 测试 DNS启动一个临时 busybox 容器[rootmaster service]# kubectl run busybox --rm -it --imagebusybox /bin/sh在容器内部通过 Service 名称访问# wget service-httpd.default:8080 Connecting to service-httpd.default:8080 (10.101.20.80:8080) index.html 100% |******************************| 45 0:00:00 ETA # cat index.html htmlbodyh1It works!/h1/body/html注意这里使用service-httpd.default由于当前 Pod 与 Service 同属default命名空间可简写为service-httpd:8080。跨命名空间访问时必须使用全限定名。五、如何通过外网访问 Service前面介绍的 ClusterIP 类型仅支持集群内部访问。为了让外部客户端访问 ServiceKubernetes 提供了多种 Service 类型ClusterIP默认仅集群内部可访问。NodePort在每个节点上开放一个静态端口外部可通过NodeIP:NodePort访问。LoadBalancer利用云厂商的负载均衡器对外暴露服务。下面重点演示NodePort的实现。5.1 修改 Service 为 NodePort 类型编辑service.yml添加type: NodePort并可指定nodePort可选若不指定则自动分配 30000-32767 范围内的端口apiVersion: v1 kind: Service metadata: name: service-httpd spec: type: NodePort selector: run: httpdservice ports: - protocol: TCP nodePort: 32000 # 可选手动指定节点端口 port: 8080 targetPort: 80重新应用配置[rootmaster service]# kubectl apply -f service.yml [rootmaster service]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 none 443/TCP 10d service-httpd NodePort 10.101.20.80 none 8080:30271/TCP 5h7m可以看到Service 的 TYPE 变为 NodePortPORT(S) 显示为8080:30271/TCP其中30271是节点上随机监听的端口若手动指定则为 32000。5.2 通过节点 IP 访问服务现在任意集群节点的 IP 加上该端口都可以访问服务[rootmaster service]# curl 172.16.213.221:30271 htmlbodyh1It works!/h1/body/html [rootmaster service]# curl 172.16.213.222:30271 htmlbodyh1It works!/h1/body/html [rootmaster service]# curl 172.16.213.223:30271 htmlbodyh1It works!/h1/body/html六、外网访问 Service 的映射机制NodePort 的实现同样依赖 iptables。当 Service 类型切换为 NodePort 后每个节点上会新增关于该 NodePort 的规则。6.1 查看 NodePort 对应的 iptables 规则[rootmaster service]# iptables-save | grep 30271 -A KUBE-NODEPORTS -p tcp -m comment --comment default/service-httpd: -m tcp --dport 30271 -j KUBE-MARK-MASQ -A KUBE-NODEPORTS -p tcp -m comment --comment default/service-httpd: -m tcp --dport 30271 -j KUBE-SVC-5FEAUJNCXGRSJFN5解释访问当前节点 30271 端口的请求会被跳转到KUBE-SVC-5FEAUJNCXGRSJFN5链。6.2 负载均衡链仍然复用[rootmaster service]# iptables-save | grep KUBE-SVC-5FEAUJNCXGRSJFN5 : KUBE-SVC-5FEAUJNCXGRSJFN5 - [0:0] -A KUBE-NODEPORTS -p tcp -m comment --comment default/service-httpd: -m tcp --dport 30271 -j KUBE-SVC-5FEAUJNCXGRSJFN5 -A KUBE-SERVICES -d 10.101.20.80/32 -p tcp -m comment --comment default/service-httpd: cluster IP -m tcp --dport 8080 -j KUBE-SVC-5FEAUJNCXGRSJFN5 ...以下为相同的概率转发规则可以看到无论是通过 ClusterIP:8080 还是通过 NodeIP:30271 进来的流量最终都汇聚到同一个负载均衡链KUBE-SVC-5FEAUJNCXGRSJFN5然后随机转发到后端 Pod。结论NodePort 本质是在每个节点上监听一个端口并将到达该端口的流量引入与 ClusterIP 相同的 iptables 负载均衡链路从而实现外网访问。结尾本文从 Service 的基本概念出发通过实际操作演示了如何创建 ClusterIP 和 NodePort 类型的 Service并深入 iptables 层面分析了 Service 的流量转发与负载均衡原理。同时我们还介绍了集群内部通过 DNS 名称访问 Service 的方法。理解 Service 的实现机制有助于更好地排错和设计 Kubernetes 集群的网络架构。在实际生产环境中还可以结合 Ingress、LoadBalancer 等更高级的资源对外暴露服务。希望本文能帮助你掌握 Kubernetes Service 的核心知识。