Service:为短暂的 Pod 提供稳定的网络
给 Pod 一个稳定的地址,在集群内外暴露它们,并限制谁可以与谁通信。
每个 Pod 都有自己的 IP 地址,但 Pod 来来去去,它们的地址也随之而去。Service 在一组不断变化的 Pod 前面提供一个稳定的地址。本指南基于上一篇指南中的 web Deployment,涵盖 Service、在集群之外暴露它们、Ingress,以及网络策略。
问题:Pod 的 IP 会变
kubectl get pods -l app=web -o wide
kubectl delete pod POD_NAME
kubectl get pods -l app=web -o wide
替代的 Pod 有不同的 IP,而且可能在不同的节点上。任何硬编码了旧地址的东西现在都坏了,而对于来来去去的副本,手动跟踪地址是不可能的。
创建 ClusterIP Service
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
selector 使用与 Deployment 中相同的标签,选出支撑该 Service 的 Pod。port 是 Service 监听的端口,targetPort 是它转发到的 Pod 上的端口,应当与容器的端口一致。因为没有设置 type,这是一个 ClusterIP Service:只能从集群内部访问,通过一个稳定的虚拟 IP。
kubectl apply -f service.yaml
kubectl get service web
kubectl get endpointslices -l kubernetes.io/service-name=web
EndpointSlice 列出当前位于该 Service 后面的 Pod IP。扩容或缩容 Deployment,它会自动更新,而 Service 自己的地址从不改变。(较旧的集群和教程用 kubectl get endpoints web 获取同样的信息。较新的版本弃用了该 API,转而使用 EndpointSlice。)
在集群内部,Service 还会获得一个 DNS 名称:在同一命名空间中是 web,或从任何地方是 web.NAMESPACE.svc.cluster.local。从一个一次性 Pod 测试它:
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web
你应该会看到 nginx 的欢迎页面。在每个节点上,kube-proxy 维护着将连接分散到 Service 就绪 Pod 之间的规则。在常见的 iptables 模式下,这实际上是随机的,而不是严格的轮询,并且未通过就绪检查的 Pod 会被从轮转中取出。
在集群之外暴露 Service
还有另外两种 Service 类型,每一种都建立在前一种之上:
- NodePort 还会在每个节点上开放一个端口(30000–32767)。任何能访问某个节点 IP 的东西,都可以通过该端口访问 Service。
- LoadBalancer 还会向环境请求一个指向这些节点端口的外部负载均衡器。在云服务商上这会自动发生。在裸金属上,你需要 MetalLB 之类的东西。
apiVersion: v1
kind: Service
metadata:
name: web-public
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080
省略 nodePort,Kubernetes 会挑选一个空闲的。在使用 Docker 驱动的 minikube 上,节点的 IP 通常无法从你的笔记本电脑直接访问,所以让 minikube 为你打开它:
minikube service web-public --url
对于 minikube 上的 LoadBalancer Service,请在另一个终端中运行 minikube tunnel。
用 Ingress 路由 HTTP
每个 Service 一个 LoadBalancer 开销很大,而且只处理原始流量。Ingress 让你通过单一入口,按主机和路径把 HTTP(S) 流量路由到许多 Service,并在那里终止 TLS。Ingress 只描述规则。它需要集群中运行的 ingress 控制器来执行它们(在 minikube 上,minikube addons enable ingress)。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: web.example.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
对 web.example.local 的请求现在会前往 web Service。该主机的证书可以用 cert-manager 之类的工具自动化。
用 NetworkPolicy 限制流量
默认情况下,每个 Pod 都可以与其他每个 Pod 通信。NetworkPolicy 选中一些 Pod,并列出允许的流量。一旦某个 Pod 被任何策略选中用于 ingress,所有未被显式允许的流量都会被拒绝。先拒绝命名空间中的所有入站流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
空的 podSelector 选中命名空间中的每个 Pod。然后只允许你需要的内容,例如允许带有 role: client 标签的 Pod 访问 web Pod:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-client-to-web
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: client
ports:
- protocol: TCP
port: 80
网络策略是命名空间级别的,并且只有在集群的网络插件支持它们时才会被执行,例如 Calico 或 Cilium。一些最小的本地配置会接受这些对象但悄悄地忽略它们,所以请测试被拒绝的连接确实被阻止了。