Kubernetes(K8s)Service提供了一种机制,使得集群内的应用程序可以从集群外部进行访问。通过使用不同类型的Service(如NodePort、LoadBalancer和Ingress),Kubernetes实现了不同级别的集群外访问。NodePort为每个节点分配了一个静态端口,使得外部流量可以通过节点访问服务。LoadBalancer通过云服务商提供的负载均衡器将流量分配到集群中的服务。Ingress则提供了更高级的路由功能,允许基于域名和路径将流量转发到不同的服务。K8s Service为集群外部用户提供了可靠且灵活的访问方式,使得应用程序可以无缝地与外部系统进行交互。
NodePort类型Service如何定义?
nodePort->servicePort->podPort
apiVersion: v1
kind: Service
metadata:
name: service-nodeport
namespace: dev
spec:
selector:
app: nginx-pod
type: NodePort # service类型
ports:
- port: 80
nodePort: 30002 # 指定绑定的node的端口(默认的取值范围是:30000-32767), 如果不指定,会默认分配
targetPort: 80
nodeport和clusterip的service端口展示对比
nodeport:
[root@master k8sYamlForCSDN]# kubectl get svc service-nodeport -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service-nodeport NodePort 10.99.95.186 <none> 80:30002/TCP 11s
clusterIp:
[root@master k8sYamlForCSDN]# kubectl get svc -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-clusterip ClusterIP 10.97.97.97 <none> 80/TCP 17s app=nginx-pod
NodePort类型Service实现原理?
NodePort 模式也就非常容易理解了。显然,kube-proxy 要做的,就是在每台宿主机上生成这样一条 iptables 规则
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- nodePort: 8080
targetPort: 80
protocol: TCP
name: http
selector:
run: my-nginx
Iptables规则:nodePort(8080)->service
- 控制面:
- 添加Iptables规则:让nodeport转给svc
- -A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx: nodePort" -m tcp --dport 8080 -j KUBE-SVC-67RL4FN6JRUPOJYM
- 表示:访问nodeport80端口,则跳转给service规则:KUBE-SVC-67RL4FN6JRUPOJYM
- 所以接下来的流程,就跟 ClusterIP 模式完全一样了
- 添加Iptables规则:NodePort 方式下,Kubernetes 会在 IP 包离开宿主机发往目的 Pod 时,对这个 IP 包做一次 SNAT 操作
- -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
- 只对service转发的做SNAT, IP 包是否有一个“0x4000”的“标志”,认为是service转发的
- IP 包的源地址替换成了这台宿主机上的 CNI 网桥地址,或者宿主机本身的 IP 地址(如果 CNI 网桥不存在的话)
- 数据面:
- 请求进入node节点机器,被Iptable拦截,如果是访问指定nodeport端口,则转给service
- service如果将请求转给其他node上的pod,会对该请求包做SNAT
NodePort类型Service,如何拿到client真实源IP?
- 问题:
- node2暴露nodeport,请求转发到node1上的pod时候,会做SNAT,源IP改成node2的ip
- 但有些场景,我们的应用需要拿到client真实来源IP,如何做?
- 解决方案:
- 将 Service 的 spec.externalTrafficPolicy 字段设置为 local(默认值是cluster)
- 原理:
- ocal表示service只把请求转给当前节点的pod(即不存在SNAT),这样就可以拿到client真实来源IP
- 缺点:
- 转到node2上没有pod,则直接报错
LoadBalance类型Service如何定义?
---
kind: Service
apiVersion: v1
metadata:
name: example-service
spec:
ports:
- port: 8765
targetPort: 9376
selector:
app: example
type: LoadBalancer
在公有云提供的 Kubernetes 服务里,都使用了一个叫作 CloudProvider 的转接层,来跟公有云本身的 API 进行对接
LoadBalance类型Service实现原理?
Loadbalancer 的实现方面各个厂家的方案不尽一致,从大类来做下对比:
- 控制面实现:
- 实现方式一:
- External Cloud Provider 直接与 Kubernetes 集群对接,监听 Kubernetes 资源对象变更事件:这种方式要求 Cloud Provider 本身支持与 Kubernetes 环境对接,兼容性和灵活性上略差;
- 实现方式二:
- 使用 Operator 在集群内监听 Kubernetes 事件:Kubernetes 原生建议的对接方式,集群内 Operator 可以理解为 Kubernetes 与外部负载均衡器的桥梁,可以做 API 转换等工作,灵活性很强。
- 数据面实现:
- 实现方式一:
- 在集群内部署 Pod 实现负载均衡服务:将 LB 数据面直接放在 Pod 中运行,这样的好处是 LB Pod 到业务 Pod 流量路径短,理论上也支持 ClusterIP 类型服务的实现。但缺点是复杂,需要考虑 Kubernetes 兼容性、网络如何暴露、资源争夺等诸多问题;
- 实现方式二:
- 外部负载均衡器+NodePort:一种云厂商使用较多的方式,这种实现可以理解为 NodePort 的优化,但继承了 NodePort 的诸多缺点;
- 实现方式三:
- 外部负载均衡器直连 Pod:外部负载均衡器直接通过 Pod IP 访问 Pod,中间不会经过 NodePort,这种实现性能更优,而且流量路径更简化,便于排错,Avi 通常使用这种方式