来自:指月 https://www.lixueduan.com
原文:https://www.lixueduan.com/posts/kubernetes/05-service-access/
本文主要介绍了 Service 的几种访问方式,包括ClusterIP、NodePort、LoadBalancer、ExternalName等。
所谓 Service,其实就是 Kubernetes 为 Pod 分配的、固定的、基于 iptables(或者 IPVS)的访问入口。而这些访问入口代理的 Pod 信息,则来自于 Etcd,由 kube-proxy 通过控制循环来维护。
Service 的四种访问方式如下:
NodePort
)暴露服务。 NodePort
服务会路由到自动创建的 ClusterIP
服务。 通过请求 <节点 IP>:<节点端口>
,你可以从集群的外部访问一个 NodePort
服务。NodePort
服务和 ClusterIP
服务上。CNAME
和对应值,可以将服务映射到 externalName
字段的内容(例如,foo.bar.example.com
)。 无需创建任何类型代理。其中 ClusterIP 为默认方式,只能集群内部访问。NodePort、LoadBalancer 则是向外暴露服务的同时将流量路由到 ClusterIP服务。ExternalName 则是CNAME方式进行服务映射。
ClusterIP
也是 Service 的默认访问方式。
根据是否生成 ClusterIP 又可分为普通 Service 和 Headless Service 两类:
固定虚拟IP
(Cluster IP),实现集群内的访问,为最常见的方式。NodePort 也是比较常见的一种访问方式。
YAML 定义如下:
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- port: 8080
targetPort: 80
protocol: TCP
name: http
#nodePort: 31703
- port: 443
protocol: TCP
name: https
#nodePort: 31704
selector:
app: nginx
在这个 Service 的定义里,我们声明它的类型是,type=NodePort。然后,我在 ports 字段里声明了 Service 的 8080 端口代理 Pod 的 80 端口,Service 的 443 端口代理 Pod 的 443 端口。
如果你不显式地声明 nodePort 字段,Kubernetes 就会为你分配随机的可用端口来设置代理。这个端口的范围默认是 30000-32767,你可以通过 kube-apiserver 的–service-node-port-range 参数来修改它。
查看一下自动分配的 NodePort
[root@ks ~]# kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx NodePort 10.96.107.207 <none> 8080:31589/TCP,443:30134/TCP 19m
可以看到,虽然是 NodePort 类型,但还是默认创建了 ClusterIP。
:
来访问
:
方式来访问
NodePort 模式也就非常容易理解,kube-proxy 要做的,就是在每台宿主机上生成这样一条 iptables 规则:
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx: nodePort" -m tcp --dport 8080 -j KUBE-SVC-67RL4FN6JRUPOJYM
KUBE-SVC-67RL4FN6JRUPOJYM其实就是一组随机模式的 iptables 规则。所以接下来的流程,就跟 ClusterIP 模式完全一样了
要注意的是,在 NodePort 方式下,Kubernetes 会在 IP 包离开宿主机发往目的 Pod 时,对这个 IP 包做一次 SNAT 操作,如下所示:
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
这里的原理其实很简单,如下所示:
client
\ ^
\ \
v \
node 1 <--- node 2
| ^ SNAT
| | --->
v |
endpoint
当一个外部的 client 通过 node 2 的地址访问一个 Service 的时候,node 2 上的负载均衡规则,就可能把这个 IP 包转发给一个在 node 1 上的 Pod。这里没有任何问题。
而当 node 1 上的这个 Pod 处理完请求之后,它就会按照这个 IP 包的源地址发出回复。
可是,如果没有做 SNAT 操作的话,这时候,被转发来的 IP 包的源地址就是 client 的 IP 地址。所以此时,Pod 就会直接将回复发给client。对于 client 来说,它的请求明明发给了 node 2,收到的回复却来自 node 1,这个 client 很可能会报错。
所以,在上图中,当 IP 包离开 node 2 之后,它的源 IP 地址就会被 SNAT 改成 node 2 的 CNI 网桥地址或者 node 2 自己的地址。这样,Pod 在处理完成之后就会先回复给 node 2(而不是 client),然后再由 node 2 发送给 client。
当然,这也就意味着这个 Pod 只知道该 IP 包来自于 node 2,而不是外部的 client。对于 Pod 需要明确知道所有请求来源的场景来说,这是不可以的。
所以这时候,你就可以将 Service 的 spec.externalTrafficPolicy 字段设置为 local,这就保证了所有 Pod 通过 Service 收到请求之后,一定可以看到真正的、外部 client 的源地址。
而这个机制的实现原理也非常简单:这时候,一台宿主机上的 iptables 规则,会设置为只将 IP 包转发给运行在这台宿主机上的 Pod。所以这时候,Pod 就可以直接使用源地址将回复包发出,不需要事先进行 SNAT 了。这个流程,如下所示:
client
^ / \
/ / \
/ v X
node 1 node 2
^ |
| |
| v
endpoint
当然,这也就意味着如果在一台宿主机上,没有任何一个被代理的 Pod 存在,比如上图中的 node 2,那么你使用 node 2 的 IP 地址访问这个 Service,就是无效的。此时,你的请求会直接被 DROP 掉。
从外部访问 Service 的第二种方式,适用于公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service。
---
kind: Service
apiVersion: v1
metadata:
name: example-service
spec:
ports:
- port: 8765
targetPort: 9376
selector:
app: example
type: LoadBalancer
在公有云提供的 Kubernetes 服务里,都使用了一个叫作 CloudProvider 的转接层,来跟公有云本身的 API 进行对接。
所以,在上述 LoadBalancer 类型的 Service 被提交后,Kubernetes 就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端。
而第三种方式,是 Kubernetes 在 1.7 之后支持的一个新特性,叫作 ExternalName。举个例子:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
type: ExternalName
externalName: my.database.example.com
在上述 Service 的 YAML 文件中,我指定了一个 externalName=my.database.example.com 的字段。而且你应该会注意到,这个 YAML 文件里不需要指定 selector。
这时候,当你通过 Service 的 DNS 名字访问它的时候,比如访问:my-service.default.svc.cluster.local。那么,Kubernetes 为你返回的就是my.database.example.com。
所以说,ExternalName 类型的 Service,其实是在 kube-dns 里为你添加了一条 CNAME 记录。
这时,访问 my-service.default.svc.cluster.local 就和访问 my.database.example.com 这个域名是一个效果了。
此外,Kubernetes 的 Service 还允许你为 Service 分配公有 IP 地址,比如下面这个例子:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
在上述 Service 中,我为它指定的 externalIPs=80.11.12.10,那么此时,你就可以通过访问 80.11.12.10:80 访问到被代理的 Pod 了。
在理解了 Kubernetes Service 机制的工作原理之后,很多与 Service 相关的问题,其实都可以通过分析 Service 在宿主机上对应的 iptables 规则(或者 IPVS 配置)得到解决。
你需要区分到底是 Service 本身的配置问题,还是集群的 DNS 出了问题
一个行之有效的方法,就是检查 Kubernetes 自己的 Master 节点的 Service DNS 是否正常:
# 在一个Pod里执行
$ nslookup kubernetes.default
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: kubernetes.default
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local
如果上面访问 kubernetes.default 返回的值都有问题,那你就需要检查 kube-dns 的运行状态和日志了。否则的话,你应该去检查自己的 Service 定义是不是有问题。
此时首先应该检查的是这个 Service 是否有 Endpoints:
$ kubectl get endpoints hostnames
NAME ENDPOINTS
hostnames 10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376
而如果 Endpoints 正常,那么你就需要确认 kube-proxy 是否在正确运行。在我们通过 kubeadm 部署的集群里,你应该看到 kube-proxy 输出的日志如下所示:
$ kubectl logs <kube-proxy-pod-name>
I1027 22:14:53.995134 5063 server.go:200] Running in resource-only container "/kube-proxy"
I1027 22:14:53.998163 5063 server.go:247] Using iptables Proxier.
I1027 22:14:53.999055 5063 server.go:255] Tearing down userspace rules. Errors here are acceptable.
I1027 22:14:54.038140 5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
I1027 22:14:54.038164 5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
I1027 22:14:54.038209 5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
I1027 22:14:54.038238 5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
I1027 22:14:54.040048 5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
I1027 22:14:54.040154 5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
I1027 22:14:54.040223 5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP
如果 kube-proxy 一切正常,你就应该仔细查看宿主机上的 iptables 了
一个 iptables 模式的 Service 对应的规则,它们包括:
所谓 Service,其实就是 Kubernetes 为 Pod 分配的、固定的、基于 iptables(或者 IPVS)的访问入口。而这些访问入口代理的 Pod 信息,则来自于 Etcd,由 kube-proxy 通过控制循环来维护。
https://kubernetes.io/docs/concepts/services-networking/service/
https://draveness.me/kubernetes-service/
深入剖析Kubernetes