k8s网络概述-Service集群网络
# Service
Service
是集群重要的核心服务之一,主要是为pod分配固定的、基于iptables(ipvs)
的访问入口。
# 一、概述
当我们创建一个service对象时,会同步的创建endpoint
对象,endpoint
是用来做容器转发,将一组容器与service关联,然后由kube-proxy
组件进行路由转发。
# 二、 service创建
# serve_dep.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: serve-demo
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: mirrorgooglecontainers/serve_hostname
ports:
- containerPort: 9376
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: serve-svc
spec:
selector:
app: nginx
ports:
- name: tcp-80
protocol: TCP
port: 80
targetPort: 9376
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- 当service创建后,我们可以查看下相关资源信息
# 查看pod状态
# ## 可以看到三个pod 有一个处于未就绪状态
[root@k8s-master01 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
serve-demo-5c4f45c7fc-psr42 1/1 Running 0 82m 172.41.56.197 k8s-node01 <none> <none>
serve-demo-5c4f45c7fc-psr42 0/1 ContainerCreating 0 82m <none> k8s-node03 <none> <none>
serve-demo-5c4f45c7fc-v2rlx 1/1 Running 0 82m 172.41.70.132 k8s-node02 <none> <none>
# service对象
[root@k8s-master01 demo]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 60d
serve-svc ClusterIP 10.96.201.52 <none> 80/TCP 4m7s
# endpoint
# ## endpoint里面的两个IP就是上面已经就绪的pod IP
[root@k8s-master01 demo]# kubectl get endpoints serve-svc
NAME ENDPOINTS AGE
serve-svc 172.41.10.193:9376,172.41.56.197:9376,172.41.70.132:9376 3m17s
# 访问service ip查看
# ## 该pod是k8s官方提供的debug镜像,一个返回hostname的web server
[root@k8s-master01 demo]# kubectl get svc serve-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
serve-svc ClusterIP 10.96.201.52 <none> 80/TCP 3m3s
[root@k8s-master01 demo]# curl 10.96.201.52
serve-demo-5c4f45c7fc-v2rlx
[root@k8s-master01 demo]# curl 10.96.201.52
serve-demo-5c4f45c7fc-psr42
[root@k8s-master01 demo]# curl 10.96.201.52
serve-demo-5c4f45c7fc-gtvzm
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
注意
Endpoints
列表里面的pod是处于Running
以及状态是就绪行性的才会在该列表里面,如果pod
出现故障,则k8s会自动把该pod从列表中移除。
- service与pod之间关系
# 三、Service工作机制
通过上面 例子,我们知道了service
对于选择后端pod的方式是通过借助endpoint
实现。其实对于创建service
对象来说,不仅是生产endpoint
对象,当kube-proxy
组件对象监听到集群中创建了service
对象后,会根据service
对象关联的pod
,在对应的pod
宿主机创建一系列的iptables
规则,来进行service
到pod
的流量转发管理。
# 3.1 service负载均衡
- k8s从1.11之后,默认使用
ipvs
代理模式进行流量管理机相关规则创建,当创建service
对象时,kube-proxy
首先会在service
关联的pod
所在宿主机创建一个虚拟网卡kube-ipvs0
,并把service IP
分配给改地址。
- 然后
kube-proxy
通过linux
的ipvs
模块,为该IP添加三个ipvs虚拟主机,采用轮询的方式进行访问.
注意
不过需要注意的有两点:
1)IPVS
模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,ipvs依然使用iptables进行管理。
2)kube-proxy
创建的service IP(ClusterIP)
是一个虚拟IP,该IP在整个集群中根本不存在,当然也就无法通过IP协议栈无法路由,底层underlay设备更无法感知这个IP的存在,因此service IP
只能是单主机(Host Only)
作用域可见,这个IP在其他节点以及集群外均无法访问,Kubernetes
为了实现在集群所有的节点都能够访问Service,kube-proxy默认会在所有的Node节点都创建这个VIP并且实现负载。
# 3.2 service 工作机制过程
# 四、服务发现DNS
k8s集群默认使用coredns
提供集群中内部域名解析。
# 4.1 coredns服务
[root@k8s-master01 ~]# kubectl get pods -n kube-system |grep coredns
coredns-5bbd96d687-8gfw7 1/1 Running 1 (8d ago) 62d
coredns-5bbd96d687-s6kk4 1/1 Running 1 (8d ago) 62d
# service
[root@k8s-master01 ~]# kubectl get svc -n kube-system -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 62d k8s-app=kube-dns
2
3
4
5
6
7
8
# 4.2 pod默认dns
# 我们随便进入一个容器
# ## 可以看到pod指定的dns地址就是coredns的service地址
[root@k8s-master01 demo]# kubectl exec -it busybox-7dcdb7fd4b-2rzgt -- /bin/sh
/ # cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
2
3
4
5
6
7
# 4.3 DNS创建规则
对于coredns,当集群中有svc创建时,k8s会为该svc以及svc下关联的pod创建DNS记录
# 4.3.1 POD
- pod资源创建
# pod资源
[root@k8s-master01 demo]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
alpine-78c7f9955c-ngzqf 1/1 Running 0 64m 172.41.10.197 k8s-node03 <none> <none>
nginx-deployment-7695fcb5b5-j8chs 1/1 Running 0 46m 172.41.10.200 k8s-node03 <none> <none>
serve-demo-5c4f45c7fc-gtvzm 1/1 Running 0 44h 172.41.10.193 k8s-node03 <none> <none>
serve-demo-5c4f45c7fc-psr42 1/1 Running 0 44h 172.41.56.197 k8s-node01 <none> <none>
serve-demo-5c4f45c7fc-v2rlx 1/1 Running 0 44h 172.41.70.132 k8s-node02 <none> <none>
# pod对应svc
[root@k8s-master01 demo]# kubectl get svc nginx-svc -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx-svc ClusterIP 10.101.112.166 <none> 80/TCP 46m app=nginx-v1
2
3
4
5
6
7
8
9
10
11
12
13
- dns记录
这里使用nslook工具进行域名解析的查询
pod 域名格式:
${POD_IP}.${service_name}.${namespace_name}.svc.cluster.local.
# 从上面看 nginx对应的pod IP为172.41.10.200,dns svc IP为10.96.0.10
# ## 可以看到pod的域名解析是以172-41-10-200.nginx-svc.default.svc.cluster.local.
[root@k8s-master01 demo]# nslookup 172.41.10.200 10.96.0.10
200.10.41.172.in-addr.arpa name = 172-41-10-200.nginx-svc.default.svc.cluster.local.
2
3
4
# 4.3.2 SERVICE
上面我们看到nginx对应的svc地址为10.101.112.166
[root@k8s-masternslookup 10.101.112.166 10.96.0.10
166.112.101.10.in-addr.arpa name = nginx-svc.default.svc.cluster.local.
2
注意
1)因为pod IP及名称在集群中会随着pod的销毁重建而改变,所以一般情况下,无需关注pod在集群中对应的解析记录,只有在一些特殊场景需要点对点直接直接访问pod IP时,可以使用pod域名。
2)对于Service资源对象创建时生成的IP,虽然service不会像pod那样经常销毁重建,但是也不会说一直不变,所以最好的方式就是直接使用service对应的域名进行访问链接。
3)通过Service域名访问时,分为以下两个方式:
同命名空间: 可以直接使用${service_name}进行访问,无需拼接全域名,这种解析速度最快,例如: nginx-svc
非同命名空间: 可以直接使用${service_name}.${namespace_name}进行访问,例如: nginx-svc.default
# 4.4 POD的DNS策略
DNS 策略可以逐个 Pod 来设定,通过dnsPolicy
字段设置,支持的字段:
- Default: 继承pod所在宿主机dns解析配置。
- ClusterFirst:k8s默认配置,先从集群coreDNS进行域名解析,解析不到的则通过coreDNS内部自定义的配置
/etc/resolv.conf
进行解析。 - ClusterFirstWithHostNet: 主要用于以
hostNetwork
方式运行的 Pod,如果这些pod想要使用K8S集群内的DNS服务,则可以配置为这个字段. - None:忽略 Kubernetes 环境中的 DNS 设置,使用pod的
dnsConfig
字段进行解析。
# 4.5 DNS解析流程
在集群中,kubelet会为每一个pod创建一个/etc/resolv.conf
文件,DNS解析会按照文件内容进行查询:
[root@k8s-master01 demo]# kubectl exec -it nginx-deployment-7695fcb5b5-j8chs -- /bin/bash
root@nginx-deployment-7695fcb5b5-j8chs:/# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
2
3
4
5
说明
- search: 搜索的域,默认情况下会从该pod所属的namespace开始逐级补充。
- nameserver: 集群中的DNS服务器IP,默认就是coredns的ClusterIP。
- options: 是否触发上的search,k8s集群中默认ndots为5,一般系统默认为1
# 4.5.1 cluster.local 取值
cluster.local 其实是根据coredns中的定义
# 在/etc/resolv.conf文件中 search搜索的域 后面都是以cluster.local结尾,这里对应的是coredns中的配置
# ## coredns的configmap
[root@k8s-master01 demo]# kubectl get configmaps -n kube-system coredns -oyaml
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
metadata:
creationTimestamp: "2023-01-04T09:12:11Z"
name: coredns
namespace: kube-system
resourceVersion: "229"
uid: e718af62-1cf3-4c8a-9d98-c5506cd98b94
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 4.5.2 options ndots
上面我们看到在pod中的dns文件/etc/resolv.conf
,下面针对search
与ndots
探究下DNS的解析流程。
search与ndots
在介绍ndots域search之前,这里需要引入一个概念FQDN
,FQDN
是指完整域名,一般来说,域名最终以.
结束表示是FQDN
,例如baidu.com.
是FQDN
,但baidu.com
不是。如果域名是FQDN则直接走DNS服务,否则就需要用到search
和nodts
了。
ndots
简单来说,就是请求一个域名的时候,域名中点的个数
,如果域名中点的个数大于ndots
,则会直接按照域名进行解析;如果小于ndots,则会按照search中指定的域,依次添加后缀进行解析,直到可以支持获取域名的地址,如果添加后缀都解析不到,则会按照原域名进行解析。
示例-小于ndots
- 外部域名
请求域名: www.baidu.com
请求域名不是完整域名且点数小于5,则会根据搜索域进行如下4次解析:
www.baidu.com.default.svc.cluster.local
www.baidu.com.svc.cluster.local
www.baidu.com.cluster.local
www.baidu.com.
# 抓包数据 tcpdump -nt -i eth0 port 53(ping www.baidu.com)
IP 172.41.10.200.36537 > 10.96.0.10.53: 60071+ A? www.baidu.com.default.svc.cluster.local. (57)
IP 172.41.10.200.36537 > 10.96.0.10.53: 30392+ AAAA? www.baidu.com.default.svc.cluster.local. (57)
IP 10.96.0.10.53 > 172.41.10.200.36537: 60071 NXDomain*- 0/1/0 (150)
IP 10.96.0.10.53 > 172.41.10.200.36537: 30392 NXDomain*- 0/1/0 (150)
IP 172.41.10.200.37517 > 10.96.0.10.53: 55016+ A? www.baidu.com.svc.cluster.local. (49)
IP 172.41.10.200.37517 > 10.96.0.10.53: 12017+ AAAA? www.baidu.com.svc.cluster.local. (49)
IP 10.96.0.10.53 > 172.41.10.200.37517: 12017 NXDomain*- 0/1/0 (142)
IP 10.96.0.10.53 > 172.41.10.200.37517: 55016 NXDomain*- 0/1/0 (142)
IP 172.41.10.200.48853 > 10.96.0.10.53: 61152+ A? www.baidu.com.cluster.local. (45)
IP 172.41.10.200.48853 > 10.96.0.10.53: 60648+ AAAA? www.baidu.com.cluster.local. (45)
IP 10.96.0.10.53 > 172.41.10.200.48853: 60648 NXDomain*- 0/1/0 (138)
IP 10.96.0.10.53 > 172.41.10.200.48853: 61152 NXDomain*- 0/1/0 (138)
IP 172.41.10.200.40127 > 10.96.0.10.53: 22685+ A? www.baidu.com. (31)
IP 172.41.10.200.40127 > 10.96.0.10.53: 54947+ AAAA? www.baidu.com. (31)
IP 10.96.0.10.53 > 172.41.10.200.40127: 22685 3/0/0 CNAME www.a.shifen.com., A 112.80.248.76, A 112.80.248.75 (138)
IP 10.96.0.10.53 > 172.41.10.200.40127: 54947 1/1/0 CNAME www.a.shifen.com. (164)
IP 172.41.10.200.60493 > 10.96.0.10.53: 10365+ PTR? 76.248.80.112.in-addr.arpa. (44)
IP 10.96.0.10.53 > 172.41.10.200.60493: 10365 NXDomain 0/1/0 (130)
请求域名: www.baidu.com.
请求域名点数小于5,但是是完整域名,则会进行如下一次解析:
www.baidu.com.
# 抓包数据 tcpdump -nt -i eth0 port 53(ping www.baidu.com.)
IP 172.41.10.200.51292 > 10.96.0.10.53: 12976+ A? www.baidu.com. (31)
IP 10.96.0.10.53 > 172.41.10.200.51292: 12976* 3/0/0 CNAME www.a.shifen.com., A 112.80.248.76, A 112.80.248.75 (138)
IP 172.41.10.200.51292 > 10.96.0.10.53: 19645+ AAAA? www.baidu.com. (31)
IP 10.96.0.10.53 > 172.41.10.200.51292: 19645* 1/1/0 CNAME www.a.shifen.com. (164)
IP 172.41.10.200.35968 > 10.96.0.10.53: 14896+ PTR? 76.248.80.112.in-addr.arpa. (44)
IP 10.96.0.10.53 > 172.41.10.200.35968: 14896 NXDomain* 0/1/0 (130)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 内部service域名
# 同命名空间访问(default)
请求域名: nginx-svc
请求域名点数小于5,则会进行如下一次解析:
nginx-svc.default.svc.cluster.local.
# 抓包数据tcpdump -nt -i eth0 port 53(nslookup nginx-svc)
IP 172.41.10.200.33807 > 10.96.0.10.53: 22119+ A? nginx-svc.default.svc.cluster.local. (53)
IP 10.96.0.10.53 > 172.41.10.200.33807: 22119*- 1/0/0 A 10.101.112.166 (104)
IP 172.41.10.200.44417 > 10.96.0.10.53: 47328+ AAAA? nginx-svc.default.svc.cluster.local. (53)
IP 10.96.0.10.53 > 172.41.10.200.44417: 47328*- 0/1/0 (146)
# 不同命名空间访问(default-->monitoring)
# ## 根据搜索域规则,不同命名空间必须加上namespace,否则无法支持解析
请求域名: prometheus-k8s.monitoring
请求域名点数小于5,则会进行如下2次解析:
prometheus-k8s.monitoring.default.svc.cluster.local.
prometheus-k8s.monitoring.svc.cluster.local.
# 抓包数据 tcpdump -nt -i eth0 port 53(nslookup prometheus-k8s.monitoring)
IP 172.41.10.200.44501 > 10.96.0.10.53: 25510+ A? prometheus-k8s.monitoring.default.svc.cluster.local. (69)
IP 10.96.0.10.53 > 172.41.10.200.44501: 25510 NXDomain*- 0/1/0 (162)
IP 172.41.10.200.44017 > 10.96.0.10.53: 2945+ A? prometheus-k8s.monitoring.svc.cluster.local. (61)
IP 10.96.0.10.53 > 172.41.10.200.44017: 2945*- 1/0/0 A 10.99.54.68 (120)
IP 172.41.10.200.59528 > 10.96.0.10.53: 63858+ AAAA? prometheus-k8s.monitoring.svc.cluster.local. (61)
IP 10.96.0.10.53 > 172.41.10.200.59528: 63858*- 0/1/0 (154)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
示例-大于ndots
- 外部域名
请求域名: sls.doc.dc.wiki.tbchip.com
请求域名不是完整域名点数等于5,则会根据搜索域进行如下1次解析:
sls.doc.dc.wiki.tbchip.com.
# 抓包数据tcpdump -nt -i eth0 port 53(nslookup sls.doc.dc.wiki.tbchip.com)
IP 172.41.10.200.52474 > 10.96.0.10.53: 61735+ A? sls.doc.dc.wiki.tbchip.com. (44)
IP 10.96.0.10.53 > 172.41.10.200.52474: 61735 1/0/0 A 39.103.233.101 (86)
IP 172.41.10.200.60240 > 10.96.0.10.53: 45547+ AAAA? sls.doc.dc.wiki.tbchip.com. (44)
IP 10.96.0.10.53 > 172.41.10.200.60240: 45547 0/1/0 (129)
2
3
4
5
6
7
8
9
10
- 内部域名
请求域名: prometheus-k8s.monitoring.svc.cluster.local.
请求域名点数等于5,则会进行如下一次解析:
# 抓包数据 tcpdump -nt -i eth0 port 53(nslookup prometheus-k8s.monitoring.svc.cluster.local.)
IP 172.41.10.200.36073 > 10.96.0.10.53: 9472+ A? prometheus-k8s.monitoring.svc.cluster.local. (61)
IP 10.96.0.10.53 > 172.41.10.200.36073: 9472*- 1/0/0 A 10.99.54.68 (120)
IP 172.41.10.200.52819 > 10.96.0.10.53: 18400+ AAAA? prometheus-k8s.monitoring.svc.cluster.local. (61)
IP 10.96.0.10.53 > 172.41.10.200.52819: 18400*- 0/1/0 (154)
2
3
4
5
6
7
# 4.5.3 总结
通过以上测试级search搜索域我们可以得出以下结论:
- 同命名空间: 如果是内部域名,直接访问
service
即可请求到对应的服务,并且解析性能最好。 - 不同命名空间,如果是内部域名:
- 如果域名中点数小于5,则使用${service_name}.${name_space}访问,解析性能最好,会进行2次查询。
- 如果域名中点数大于5,则会直接解析并返回结果,不会再对搜索域进行拼接。
- 对于外部域名:
- 如果是全域名(FQDN),则无论是否域名中点数大于等于5,都会直接解析,不会再拼接搜索域
- 如果是非全域名,域名点数大于等于5,直接解析;域名中点数小于5,触发search搜索域拼接,依次进行解析。
因此,在k8s中,如果应用需要访问外部域名,则最好使用域名的FQDN
,这样保证可以直接解析