前言
在 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),客户端只需访问这个 IP,Kubernetes 负责建立和维护 Service 与后端 Pod 的映射关系。无论后端 Pod 如何增删或 IP 如何变化,对客户端都不会产生任何影响。
Service 提供了以下核心能力:
稳定的访问入口(Cluster IP)
负载均衡(将请求分发到多个后端 Pod)
服务发现(通过 DNS 或环境变量)
二、创建 Service 并演示用途
2.1 创建一个 Deployment
首先,我们创建一个 Deployment,它负责管理三个 httpd(Apache)Pod,并使用标签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:
[root@master service]# kubectl apply -f http-service.yml2.2 集群内部直接访问 Pod
每个 Pod 会被分配一个集群内部可访问的 IP(例如 10.244.1.28),但该 IP 仅能在 Kubernetes 集群中的节点和容器内访问:
[root@master service]# curl 10.244.1.28 <html><body><h1>It works!</h1></body></html>2.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:
[root@master service]# kubectl apply -f service.yml [root@master 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 访问后端服务:
[root@master service]# curl 10.101.20.80:8080 <html><body><h1>It works!</h1></body></html>2.4 查看 Service 与 Pod 的对应关系
[root@master service]# kubectl describe svc service-httpd Name: service-httpd Namespace: default Labels: <none> Annotations: Selector: run=httpdservice 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 规则
[root@master 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 不是集群 CIDR(10.244.0.0/16)的请求,打上 MASQ 标记(用于 SNAT)。
第二条规则:目标 IP 为 10.101.20.80、端口 8080 的请求,跳转到自定义链
KUBE-SVC-5FEAUJNCXGRSJFN5。
3.2 查看负载均衡链
[root@master 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 规则
[root@master 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 部署
[root@master service]# kubectl get deployment --namespace=kube-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 容器:
[root@master service]# kubectl run busybox --rm -it --image=busybox /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 <html><body><h1>It works!</h1></body></html>注意:这里使用service-httpd.default,由于当前 Pod 与 Service 同属default命名空间,可简写为service-httpd:8080。跨命名空间访问时必须使用全限定名。
五、如何通过外网访问 Service
前面介绍的 ClusterIP 类型仅支持集群内部访问。为了让外部客户端访问 Service,Kubernetes 提供了多种 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重新应用配置:
[root@master service]# kubectl apply -f service.yml [root@master 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 变为 NodePort,PORT(S) 显示为8080:30271/TCP,其中30271是节点上随机监听的端口(若手动指定则为 32000)。
5.2 通过节点 IP 访问服务
现在,任意集群节点的 IP 加上该端口都可以访问服务:
[root@master service]# curl 172.16.213.221:30271 <html><body><h1>It works!</h1></body></html> [root@master service]# curl 172.16.213.222:30271 <html><body><h1>It works!</h1></body></html> [root@master service]# curl 172.16.213.223:30271 <html><body><h1>It works!</h1></body></html>六、外网访问 Service 的映射机制
NodePort 的实现同样依赖 iptables。当 Service 类型切换为 NodePort 后,每个节点上会新增关于该 NodePort 的规则。
6.1 查看 NodePort 对应的 iptables 规则
[root@master 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 负载均衡链仍然复用
[root@master 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 的核心知识。