k8s service+kube-proxy+endpoint+标签+通信过程规则解释
1.2 Service的类型1.3 kube-proxy和service的关系1.3.1 kube-proxy实际作用1.3.2 代理模型User space模型(已淘汰)iptables模式ipvs模式1.3.3 kube-proxy和service的工作过程2.3 标签选择器2.3.1 解释及规则2.3.1 等值标签选择器用法2.3.2 集合标签选择器用法三、service类型示例3.1 自定
k8s service+kube-proxy+endpoint+标签
一、Service资源
service是名称空间级别的资源,并且service创建时会自动创建Endpoint(端点)资源,因为service自己并不会去直接匹配后端pod资源,而是由endpoint匹配,匹配过程是由endpoint控制器完成的。只有pod的就绪探针就绪之后,endpoint才会去匹配
1.1 参数解释
apiVersion: v1
kind: Service
metadata:
name: …
namespace: …
labels:
key1: value1
key2: value2
spec:
type <string> # Service类型,默认为ClusterIP
selector <map[string]string> # 等值类型的标签选择器,内含“与”逻辑
publishNotReadyAddresses 表示是否将没有就绪的 Pod 关联到SVC上。默认情况是 false,只有就绪状态的 Pod 的地址才会关联到SVC上
ports: # Service的端口对象列表
- name <string> # 端口名称
protocol <string> # 协议,目前仅支持TCP、UDP和SCTP,默认为TCP
port <integer> # Service的端口号
targetPort <string> # 后端目标进程的端口号或名称,名称需由Pod规范定义
nodePort <integer> # 节点端口号,仅适用于NodePort和LoadBalancer类型
clusterIP <string> # Service的集群IP,建议由系统自动分配,也可自己指定
externalTrafficPolicy <string> # 外部流量策略处理方式,Local表示由当前节点处理,Cluster表示向集群范围调度,外部访问时生效
loadBalancerIP <string> # 外部负载均衡器使用的IP地址,仅适用于LoadBlancer
externalName <string> # 外部服务名称,该名称将作为Service的DNS CNAME值
1.ClusterIP:建议由K8S动态指定一个; 也支持用户手动明确指定;
2.ServicePort:被映射进Pod上的应用程序监听的端口; 而且如果后端Pod有多个端口,并且每个端口都想通过SErvice暴露的话,每个都要单独定义。
3.最终接收请求的是PodIP和containerPort;
1.2 Service的类型
- ClusterIP:通过集群内部IP地址暴露服务,但该地址仅在集群内部可见、可达,它无法被集群外部的客户端访问;默认类型;
- NodePort:NodePort是ClusterIP的增强类型,它会于ClusterIP的功能之外,在每个节点上使用一个相同的端口号将外部流量引入到该Service上来。端口范围:30000-32767
- LoadBalancer:LB是NodePort的增强类型,要借助于底层IaaS云服务上的LBaaS产品来按需管理LoadBalancer。
- ExternalName:借助集群上KubeDNS来实现,服务的名称会被解析为一个CNAME记录,而CNAME名称会被DNS解析为集群外部的服务的IP地址; 这种Service既不会有ClusterIP,也不会有NodePort;
1.3 kube-proxy和service的关系
1.3.1 kube-proxy实际作用
kube-proxy实际作用是Service Controller位于各节点上的agent
主要是注册监听着api-server中的所有的service数据对象,任何的service数据在api-server中添加,每个节点上的kube-proxy都会将service数据象加载到本地节点上
1.3.2 代理模型
- User space模型(已淘汰)
Pod访问Service时,iptables会利用自身规则拦截这条请求报文,并将这条请求报文重定向给kube-proxy,然后kube-proxy再去转发到内核中的iptables,然后被调度到后端pod中
- iptables模式
此模式会根据service数据对象生成大量的iptables规则,在service很多的情况下建议使用ipvs模式
Pod访问Service时,iptables会拦截这条请求报文,并根据自身规则将请求报文转发出去,其中kube-proxy只负责监听api-server中的service数据对象,并将新生成的service数据转化为iptables拦截规则和调度规则,详情见:本文章的1.3.3 kube-proxy和service的工作过程
- ipvs模式
1.3.3 kube-proxy和service的工作过程
- service是定义在api-server中的一个数据对象
- 每个节点的kube-proxy会注册监听着api-server中的所有service数据对象,任何的service数据在api-server中添加,每个节点上的kube-proxy都会将service数据象加载到本地节点上
- kube-proxy将加载到本地节点上的service数据转换成本地节点上的iptables或ipvs规则(拦截和调度规则)
- 当nodeX节点中的某一个client pod 想要访问server pod,请求报文就会发往内核空间,进而被iptables或ipvs规则所捕获
- 当内核空间中的iptables或ipvs规则捕获到请求报文之后,会根据规则将请求调度到server pod中(至于会调度到那个pod,这个是调度规则来管理的,如轮询等)
下图是演示图
二、标签
2.1 什么是标签
- 标签中的键名称通常由“键前缀”和“键名”组成,其格式形如“KEY_PREFIX/KEY_NAME”,键前缀为可选部分。键名至多能使用63个字符,支持字母、数字、连接号(-)、下划线(_)、点号(.)等字符,且只能以字母或数字开头。而键前缀必须为DNS子域名格式,且不能超过253个字符。省略键前缀时,键将被视为用户的私有数据。那些由Kubernetes系统组件或第三方组件自动为用户资源添加的键必须使用键前缀,kubernetes.io/和k8s.io/前缀预留给了kubernetes的核心组件使用,例如Node对象上常用的kubernetes.io/os、kubernetes.io/arch和kubernetes.io/hostname等。
- 标签的键值必须不能多于63个字符,它要么为空,要么是以字母或数字开头及结尾,且中间仅使用了字母、数字、连接号(-)、下划线(_)或点号(.)等字符的数据。
2.2 标签分类
版本标签:"release" : "stable","release" : "canary","release" : "beta"。
环境标签:"environment" : "dev","environment" : "qa","environment" : "prod"。
应用标签:"app" : "ui","app" : "as","app" : "pc","app" : "sc"。
架构层级标签:"tier" : "frontend","tier" : "backend", "tier" : "cache"。
分区标签:"partition" : "customerA","partition" : "customerB"。
品控级别标签:"track" : "daily","track" : "weekly"。
2.3 标签选择器
2.3.1 解释及规则
标签选择器用于表达标签的查询条件或选择标准,Kubernetes API目前支持两个选择器:基于等值关系(equality-based)的标签选项器以及基于集合关系(set-based)的标签选择器。同时指定多个选择器时需要以逗号将其分隔,各选择器之间遵循“与”逻辑,即必须要满足所有条件,而且空值的选择器将不选择任何对象。
标签选择器可以基于等值关系的标签选择器的可用操作符有=、==和!=三种,其中前两个意义相同,都表示“等值”关系,最后一个表示“不等”。例如env=dev和env!=prod都是基于等值关系的选择器,而tier in (frontend,backend)则是基于集合关系的选择器。
2.3.1 等值标签选择器用法
1.过滤出键名为app,键值为demoapp的pod
kubectl get pods -l app=demoapp -n dev
或kubectl get pods -l app==demoapp -n dev 与上一条命令一个意思
或kubectl get pods -l app==demoapp -n dev --show-labels 可显示标签
2.过滤多个标签的pod,这里只有都含有这两个标签的pod才会被过滤出来
kubectl get pods -l 'app=demoapp,env=dev' -n dev
2.3.2 集合标签选择器用法
KEY in (VALUE1,VALUE2,…) :指定的键名的值存在于给定的列表中即满足条件;
KEY notin (VALUE1,VALUE2,…) :指定的键名的值不存在于给定列表中即满足条件;
KEY:所有存在此键名标签的资源;
!KEY:所有不存在此键名标签的资源
1.过滤出键名为app的pod,不需要管键值是什么
kubectl get pods -l app -n dev
2.过滤出键名不为app的pod,没有标签的也会被过滤出来
kubectl get pods -l '!app' -n dev #因为!在shell命令行中有意义,所以加上了引号
3.过滤出键名为app,键值为emoapp,nginx,mysql,php的pod
kubectl get pods -l 'app in (demoapp,nginx,mysql,php)' -n dev
4.过滤出键名为app,键值不为emoapp,nginx,mysql,php的pod
kubectl get pods -l 'app notin (demoapp,nginx,mysql,php)' -n dev
三、service类型示例
3.1 自定义service-clusterip
可以自己随便定义IP
1. 编写yaml文件
vim services-clusterip-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp
labels:
app: demoapp
env: dev
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: demoapp
template:
metadata:
labels:
app: demoapp
spec:
containers:
- name: demoapp
image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
name: demoapp-svc
namespace: dev
spec:
clusterIP: 10.97.72.1
selector:
app: demoapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
2. 部署
[root@master01 yaml]# kubectl apply -f services-demoapp.yaml
deployment.apps/demoapp created
service/demoapp-svc created
3. 查看demoapp-svc的IP及后端pod
[root@master01 yaml]# kubectl describe svc demoapp-svc -n dev
Name: demoapp-svc
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=demoapp
Type: ClusterIP
IP: 10.97.72.1
Port: http 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.5:80,10.244.2.8:80,10.244.2.9:80
Session Affinity: None
Events: <none>
4. 访问测试
#发现是轮询访问的后端pod
[root@master01 yaml]# curl 10.97.72.1
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-88k2l, ServerIP: 10.244.2.8!
[root@master01 yaml]# curl 10.97.72.1
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-jdsrm, ServerIP: 10.244.1.5!
[root@master01 yaml]# curl 10.97.72.1
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-85665, ServerIP: 10.244.2.9!
3.2 service-nodeport-demo
集群内部访问过程: Client -> ClusterIP:ServicePort -> PodIP:targetPort
集群外部访问过程: Client -> NodeIP:NodePort -> PodIP:targetPort
1. 编写yaml文件
vim services-nodeport-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp
labels:
app: demoapp
env: dev
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: demoapp
template:
metadata:
labels:
app: demoapp
spec:
containers:
- name: demoapp
image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
name: demoapp-nodeport-svc
namespace: dev
spec:
type: NodePort
selector:
app: demoapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 31398 #如果不指定会自动生成
2. 部署
[root@master01 yaml]# kubectl apply -f services-nodeport-demo.yaml
deployment.apps/demoapp created
service/demoapp-nodeport-svc created
3. 查看
[root@master01 yaml]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp-nodeport-svc NodePort 10.105.212.237 <none> 80:31398/TCP 8s
[root@master01 yaml]# kubectl describe svc demoapp-nodeport-svc -n dev
Name: demoapp-nodeport-svc
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=demoapp
Type: NodePort
IP: 10.105.212.237
Port: http 80/TCP
TargetPort: 80/TCP
NodePort: http 31398/TCP
Endpoints: 10.244.1.10:80,10.244.2.15:80,10.244.2.16:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
4. 使用clusterIP访问测试
[root@master01 yaml]# curl 10.105.212.237
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-dqd9f, ServerIP: 10.244.1.10!
[root@master01 yaml]# curl 10.105.212.237
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-j6z99, ServerIP: 10.244.2.15!
[root@master01 yaml]# curl 10.105.212.237
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-t6lkh, ServerIP: 10.244.2.16!
5. 使用宿主机IP和端口访问,任何一个节点的宿主机IP都可以访问到
[root@master01 yaml]# curl 192.168.8.10:31398
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-j6z99, ServerIP: 10.244.2.15!
[root@master01 yaml]# curl 192.168.8.10:31398
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-t6lkh, ServerIP: 10.244.2.16!
[root@master01 yaml]# curl 192.168.8.10:31398
iKubernetes demoapp v1.0 !! ClientIP: 10.244.0.0, ServerName: demoapp-5f7d8f9847-dqd9f, ServerIP: 10.244.1.10!
3.3 services-loadbalancer-demo
如果IaaS云服务上不支持loadbalancer,就算创建了service也会被集群定义为nodeport类型的service
1. 编写yaml文件
vim services-loadbalancer-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp
labels:
app: demoapp
env: dev
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: demoapp
template:
metadata:
labels:
app: demoapp
spec:
containers:
- name: demoapp
image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
name: demoapp-loadbalancer-svc
namespace: dev
spec:
type: LoadBalancer
selector:
app: demoapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
#loadBalancerIP: 1.2.3.4 不可以随便定义,取决于底层IaaS云服务上的LBaaS产品支不支持
2. 查看
这里EXTERNAL-IP(外部IP)字段会显示pending,因为我的测试机不支持外部IP,所以还是只能通过nodeport访问
[root@master01 yaml]# kubectl get svc -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
demoapp-loadbalancer-svc LoadBalancer 10.97.40.143 <pending> 80:30237/TCP 3s app=demoapp
3.4 外部IP地址service-externalip-demo
当没有IaaS云服务支持的时候,可以自己定义外部IP,也可以利用VIP,来保证
外部访问过程:域名->externalip->service->pod
1. 编写yaml文件
vim services-externalip-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp
labels:
app: demoapp
env: dev
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: demoapp
template:
metadata:
labels:
app: demoapp
spec:
containers:
- name: demoapp
image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
name: demoapp-externalip-svc
namespace: dev
spec:
type: ClusterIP #或NodePort
selector:
app: demoapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
externalIPs:
- 172.29.9.26 #此IP可以是节点上VIP,外部可用过这个IP的service访问到后端pod
四、endpoint资源
4.1 endpoint解释
service是名称空间级别的资源,并且service创建时会自动创建Endpoint(端点)资源,因为service自己并不会去直接匹配后端pod资源,而是由endpoint匹配,匹配过程是由endpoint控制器完成的。只有pod的就绪探针就绪之后,endpoint才会去匹配
4.2 参数解释
apiVersion: v1
kind: Endpoint
metadata: # 对象元数据
name:
namespace:
subsets: # 端点对象的列表
- addresses: # 处于“就绪”状态的端点地址对象列表
- hostname <string> # 端点主机名
ip <string> # 端点的IP地址,必选字段
nodeName <string> # 节点主机名
targetRef: # 提供了该端点的对象引用
apiVersion <string> # 被引用对象所属的API群组及版本
kind <string> # 被引用对象的资源类型,多为Pod
name <string> # 对象名称
namespace <string> # 对象所属的名称究竟
fieldPath <string> # 被引用的对象的字段,在未引用整个对象时使用,常用于仅引用
# 指定Pod对象中的单容器,例如spec.containers[1]
uid <string> # 对象的标识符;
notReadyAddresses: # 处于“未就绪”状态的端点地址对象列表,格式与address相同
ports: # 端口对象列表
- name <string> # 端口名称;
port <integer> # 端口号,必选字段;
protocol <string> # 协议类型,仅支持UDP、TCP和SCTP,默认为TCP;
appProtocol <string> # 应用层协议;
4.3 示例
4.3.1 利用readiness测试endpoint
此次目的:1.测试是否创建service就会自动创建endpoint资源
2.利用readiness(就绪探针)模拟后端pod宕机,查看endpoint资源是否会将宕机的pod下线
- 编写services-readiness.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: demoapp-with-readiness
template:
metadata:
creationTimestamp: null
labels:
app: demoapp-with-readiness
spec:
containers:
- image: ikubernetes/demoapp:v1.0
name: demoapp
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
path: '/readyz'
port: 80
initialDelaySeconds: 15
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: services-readiness-demo
namespace: dev
spec:
selector:
app: demoapp-with-readiness
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
- 查看部署状态
[root@master01 yaml]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
demoapp-677db795b4-9zf5t 1/1 Running 0 63s
demoapp-677db795b4-wdf7g 1/1 Running 0 63s
demoapp-677db795b4-wn76j 1/1 Running 0 63s
[root@master01 yaml]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
services-readiness-demo ClusterIP 10.109.208.211 <none> 80/TCP 66s
#这里看到确实有一个endpoint的资源而且和svc重名,因为endpoint资源和svc资源就是以名字来进行关联的
[root@master01 yaml]# kubectl get endpoints -n dev
NAME ENDPOINTS AGE
services-readiness-demo 10.244.1.15:80,10.244.2.23:80,10.244.2.24:80 68s
#可以看到以下A
[root@master01 yaml]# kubectl describe endpoints services-readiness-demo -n dev
Name: services-readiness-demo
Namespace: dev
Labels: <none>
Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2022-06-17T07:02:49Z
Subsets:
Addresses: 10.244.1.15,10.244.2.23,10.244.2.24
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
http 80 TCP
Events: <none>
- 访问测试
正常是可以访问200的,并且现在endpoint资源里后端pod还是三个
[root@master01 yaml]# curl 10.109.208.211:80/readyz
OK
[root@master01 yaml]# curl -I 10.109.208.211:80/readyz
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 2
Server: Werkzeug/1.0.0 Python/3.8.2
Date: Fri, 17 Jun 2022 07:25:57 GMT
修改某一个pod的readyz参数,就会导致访问出现5xx,之后readiness探针就会检测到pod宕机,endpoint就会下线宕机的pod
curl -X POST -d "readyz=fail" 10.244.2.23/readyz
[root@master01 yaml]# curl 10.244.2.23/readyz
fail
[root@master01 yaml]# curl -I 10.244.2.23/readyz
HTTP/1.0 507 INSUFFICIENT STORAGE
Content-Type: text/html; charset=utf-8
Content-Length: 4
Server: Werkzeug/1.0.0 Python/3.8.2
Date: Fri, 17 Jun 2022 07:32:23 GMT
查看endpoint详细信息,会看到NotReadAddresses字段中已经有刚刚宕机pod了
[root@master01 yaml]# kubectl describe endpoints services-readiness-demo -n dev
Name: services-readiness-demo
Namespace: dev
Labels: <none>
Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2022-06-17T07:32:28Z
Subsets:
Addresses: 10.244.1.15,10.244.2.24
NotReadyAddresses: 10.244.2.23
Ports:
Name Port Protocol
---- ---- --------
http 80 TCP
Events: <none>
可以看到READY字段中有一个pod的状态已经是0了
[root@master01 yaml]# kubectl get pod -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
demoapp-677db795b4-9zf5t 1/1 Running 0 32m 10.244.2.24 node02 <none> <none>
demoapp-677db795b4-wdf7g 1/1 Running 0 32m 10.244.1.15 node01 <none> <none>
demoapp-677db795b4-wn76j 0/1 Running 0 32m 10.244.2.23 node02 <none> <none>
4.3.2 利用endpoint代理外部mysql(externalName的示例)
- 编写mysql-endpoints-demo.yaml
这里Endpoints和Service不需要通过labels来进行关联,只需要通过metadata.name字段关联即可(保持一致)
vim mysql-endpoints-demo.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: mysql-external
namespace: default
subsets:
- addresses:
- ip: 172.29.9.51 #这里的地址可以是外部mysql的读写分离器的IP这样就能保证数据一致性了
- ip: 172.29.9.52
ports:
- name: mysql
port: 3306
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: mysql-external
namespace: default
spec:
type: ClusterIP
ports:
- name: mysql
port: 3306
targetPort: 3306
protocol: TCP
- 部署之后
部署之后,任何pod都可以通过service来访问数据库了
五、iptables模式下的service
详情见本文章的 1.3
5.1 k8s在iptables中的规则解释
- KUBE-SERVICES:包含所有ClusterIP类型的Service的流量匹配规则,由PREROUTING和OUTPUT两个内置链直接调用;每个Service对象包含两条规则定义,对于所有发往该Service(目标IP为Service_IP且目标端口为Service_Port)的请求报文,前一条用于为那些非源自Pod网络(! -s 10.244.0.0/16)中请求报文借助于KUBE-MARQ-MASK自定义链中的规则打上特有的防火墙标记,后一条负责将所有报文转至专用的以KUBE-SVC为名称前缀的自定义链,后缀是Service信息hash值。
- KUBE-MARK-MASQ:专用目的自定义链,所有转至该自定义链的报文都将被置入特有的防火墙标记(0x4000)以便于将特定的类型的报文定义为单独的分类,目的在将该类报文转发到目标端点之前由POSTROUTING规则链进行源地址转换。
- KUBE-SVC-<HASH>:定义一个服务的流量调度规则,它通过随机调度算法(RANDOM)将请求分发给该Service的所有后端端点,每个后端端点定义在以KUBE-SEP为前缀名称的自定链上,后缀是端点信息的hash值。
- KUBE-SEP-<HASH>:定义一个端点相关的流量处理规则,它通常包含两条规则,前一条用于为那些源自该端点自身(-s ep_ip)的请求流量调用自定义链KUBE-MARQ-MASK打上特有的防火墙标记,后一条负责将发往该端点的所有流量进行目标IP地址和端口转换,新目标为该端点的IP和端口(-j DNAT --to-destination ep_ip:ep_port)。
- KUBE-POSTROUTING:专用的自定义链,由内置链POSTROUTING无条件调用,负责将拥有特有防火墙标记0x4000的请求报文进行源地址转换(Target为实现地址伪装的MASQUERADE),新的源地址为报文离开协议栈时流经接口的主IP(primary ip)地址。
5.2 Pod-Pod通信过程和非Pod网络-Pod的通信过程
iptables代理模式下的ClusterIP,每个Service在每个节点上(由kube-proxy负责生成)都会生成相应的iptables规则
下图是流量从pod或者k8s集群上的独立的docker容器或者节点上的应用进程中流出并流向目的地的过程
iptables规则中出站流量必须经过OUTPUT->POSTROUTING,这里表示的是k8s集群中出站流量的过程
以下的序号都表示请求报文的流经路径
(1)表示从NodeX节点上的一个独立的容器中流出的请求报文(这里独立的容器表示的是docker自己的网络172.17.0.0/16)
(2)表示从NodeX节点上的某一个Pod中流出请求报文
(3)表示从NodeX节点上的应用进程中流出的请求报文
这里规则间的序号都是规则的调用,以下说的将报文发给某一条规则,只是为了更好的捋清楚关系而已。
- 请求由k8s集群中的Pod或者k8s集群节点上的应用进程出站的过程
(集群内部通信,Pod-Pod间通信过程,因为Pod-Pod间通信需要经过Service,所以需要经过这些规则)
- (2)号请求从Pod中流出进入内核空间,并被iptables出站规则捕获,并转发给KUBE-SERVICES
- KUBE-SERVICES中的第一条规则会判断是否是从Pod网络中出站的报文,发现(2)请求是,然后匹配第二条规则,发现请求(2)的目标地址和端口是另一个Service的,之后将这条请求转发给KUBE-SVC-<HASH>
- 请求(2)到达KUBE-SVC-<HASH>之后,根据流量调度规则(这里的规则可以使用命令iptables -t nat -S KUBE-SVC-VJUP3FCP3I4BT7K3看到,其中-S 后边的这个是查看的iptables -t nat -S KUBE-SERVICES | grep svc_name 得到的),将请求调度给后端的Pod,(2)请求从这里的规则得知要调度给具体的Pod_IP之后,请求就会发往KUBE-SEP-<HASH>
- 请求(2)到达KUBE-SEP-<HASH>之后,先匹配第一条规则,如果发现源地址是自己发出请求时的Pod_IP,就需要经由序号(9)发送给KUBE-MARQ-MASK打标签(进行源地址转换)转换之后就会返回给KUBE-SEP-<HASH>,并进行第二条规则匹配进行目标地址转换,将目标IP和端口换成第3步中获取到的地址,然后发送给POSTROUTING;如果发现源地址不是自己发出请求时的Pod_IP,就会进行第二条规则匹配进行目标地址转换,将目标IP和端口换成第3步中获取到的地址,然后发送给POSTROUTING
- 请求到达POSTROUTING之后,又会转发给KUBE-POSTROUTING并匹配规则,将拥有特有防火墙标记0x4000的请求报文做源地址转换(源地址转换为当前节点的PodCIDR的IP,因为每个node的Pod网段都不一样,但是范围都在10.244.0.0/16这个大网段内,所以源地址会转换成当前节点所在的10.244.1.0/24这个网段中的IP),然后将转换之后的请求报文经接口发走,然后发送给Pod。
- 请求由k8s集群中非Pod网络出站的过程(这里举例是docker网络总运行的容器)
- (1)号请求从一个独立的docker网络中的容器内部发出,进入内核空间,并被iptables出站规则捕获,并转发给KUBE-SERVICES
- KUBE-SERVICES的第一条规则会判断是否是从Pod网络中出站的报文,发现(1)请求不是,就会将报文经由(6)发给KUBE-MARQ-MASK打标签,并将打好标签的报文返回给KUBE-SERVICES,并进行第二条规则的匹配,发现(1)报文目的地址是k8s集群中service的地址和端口,就将报文发给KUBE-SVC-<HASH>规则。
- 报文到达KUBE-SVC-<HASH>规则之后,根据流量调度规则(这里的规则可以使用命令iptables -t nat -S KUBE-SVC-VJUP3FCP3I4BT7K3看到,其中-S 后边的这个是查看的iptables -t nat -S KUBE-SERVICES | grep svc_name 得到的),将请求调度给后端的Pod,(1)请求从这里的规则得知要调度给具体的Pod_IP之后,请求就会发往KUBE-SEP-<HASH>规则
- 请求(1)到达KUBE-SEP-<HASH>之后,先匹配第一条规则,如果发现源地址并不是集群中的Pod网络,就需要经由序号(9)发送给KUBE-MARQ-MASK打标签之后,就会返回给KUBE-SEP-<HASH>,并进行第二条规则匹配进行目标地址转换,将目标IP和端口换成第3步中获取到的地址,然后发送给POSTROUTING
- 请求到达POSTROUTING之后,又会转发给KUBE-POSTROUTING并匹配规则,将拥有特有防火墙标记0x4000的请求报文标记做源地址转换(源地址转换为当前节点的PodCIDR的IP,因为每个node的Pod网段都不一样,但是范围都在10.244.0.0/16这个大网段内,所以源地址会转换成当前节点所在的10.244.1.0/24这个网段中的IP),然后将转换之后的请求报文经接口发走,发给目标Pod。
5.3 外部-Pod通信
iptables代理模式下的NodePort,外部流量从NodePort流入有两种类型,一个是从NodeX的NodePort流入,而访问的Pod_IP在另一台节点上;一个是一个是从NodeX的NodePort流入,而访问的Pod_IP在本节点。
- 下图是第一种类型从NodeX的NodePort流入,流入另一台节点上的Pod_IP
- 外部请求从(1)进入PREROUTING(通过iptables -t nat -S PREROUTING命令查看下一步规则)
- KUBE-SERVICES规则判断目标地址是否是本地节点IP地址(因为请求是从nodeX流入的,所以目标地址应该是nodeX的IP),之后调用KUBE-NODEPORTS
- KUBE-NODEPORTS第一条规则判断请求的端口是否是NodePort,然后调用KUBE-MARK-MASQ规则打标签
- KUBE-MARK-MASQ规则打好标签之后,开始KUBE-SERVICES第二条规则,匹配第二条规则,判断目标端口,调用KUBE-SVC-<HASH>
- 请求(1)到达KUBE-SVC-<HASH>之后,根据流量调度规则(这里的规则可以使用命令iptables -t nat -S KUBE-SVC-VJUP3FCP3I4BT7K3看到,其中-S 后边的这个是查看的iptables -t nat -S KUBE-NODEPORTS | grep svc_name 得到的),将请求调度给后端的Pod,(1)请求从这里的规则得知要调度给具体的Pod_IP之后,请求就会发往KUBE-SEP-<HASH>
- KUBE-SEP-<HASH>第一条规则匹配源地址是否和上一步所获取到的目标Pod_IP一样,如果一样就会调用KUBE-MARK-MASQ规则,再次打标签,然后进行第二条规则匹配,第二条规则对报文做目标地址转换(目标地址转换成目标Pod_IP),然后调用POSTROUTING
- POSTROUTING调用KUBE-POSTROUTING并匹配规则,将拥有特有防火墙标记0x4000的请求报文标记做源地址转换(源地址转换为当前节点的PodCIDR的IP,因为每个node的Pod网段都不一样,但是范围都在10.244.0.0/16这个大网段内,所以源地址会转换成当前节点所在的10.244.1.0/24这个网段中的IP),然后将转换之后的请求报文经接口发走,发给另一个节点的目标Pod。
- 报文从目标Pod返回时,会再次经过NodeX并做源地址转换(源地址转换成NodeX的节点IP),然后返回给用户。
- 下图是第二种类型从NodeX的NodePort流入,流入本节点上的Pod_IP
5.5 externalIP-Pod通信
六、ipvs模式下的service
模式类型见本文章的 1.3
6.1 特点作用
ipvs会在每个节点上创建一个名为kube-ipvs0的虚拟接口,并将集群所有Service对象的ClusterIP和ExternalIP都配置在该接口; kube-proxy为每个service生成一个虚拟服务器(Virtual Server)的定义。
ipvs类型:默认是nat; 仅需要借助于极少量的iptables规则完成源地址转换等功能。
ipvs支持更多的调度算法wrr等
6.2 修改为ipvs模式
因为ipvs规则是由kube-proxy生成的,所以需要修改kube-proxy的配置
- 查看kube-proxy配置文件
1. 修改
kubectl edit cm kube-proxy -n kube-system
解释:
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: "" #调度算法,默认是轮询(rr)
strictARP: false
syncPeriod: 0s
tcpFinTimeout: 0s
tcpTimeout: 0s
udpTimeout: 0s
修改data字段中的mode字段,默认是空(默认就是iptables模式),改为ipvs
mode: "ipvs"
修改完之后保存退出即可,之后会自动应用的
2. 手动删除kube-proxy
如果不自动应用,可以手动删除以下kube-proxy,因为删除之后,会重新创建,但是需要注意一个一个删除,等第一个起来在删除第二个,否则集群就不能访问了。
[root@master01 ~]# kubectl get pod -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-6c76c8bb89-pskcq 1/1 Running 71 103d 10.244.0.39 master01 <none> <none>
coredns-6c76c8bb89-tk2m6 1/1 Running 71 103d 10.244.0.38 master01 <none> <none>
etcd-master01 1/1 Running 18 103d 192.168.8.10 master01 <none> <none>
kube-apiserver-master01 1/1 Running 22 103d 192.168.8.10 master01 <none> <none>
kube-controller-manager-master01 1/1 Running 32 103d 192.168.8.10 master01 <none> <none>
kube-flannel-ds-f8g89 1/1 Running 19 103d 192.168.8.30 node02 <none> <none>
kube-flannel-ds-fv62j 1/1 Running 16 103d 192.168.8.20 node01 <none> <none>
kube-flannel-ds-kjqnb 1/1 Running 24 103d 192.168.8.10 master01 <none> <none>
kube-proxy-cb4t9 1/1 Running 15 103d 192.168.8.20 node01 <none> <none>
kube-proxy-kv8mj 1/1 Running 18 103d 192.168.8.30 node02 <none> <none>
kube-proxy-n622g 1/1 Running 18 103d 192.168.8.10 master01 <none> <none>
kube-scheduler-master01 1/1 Running 31 103d 192.168.8.10 master01 <none> <none>
3. 删除操作
kubectl delete pods kube-proxy-cb4t9 -n kube-system
4. 查看网卡是否出现kube-ipvs0,就会发现每个service的IP都会在这个网卡上配置
ip a
5. 最好重启一下kubelet,不重启应该也没关系,如果生产环境在运行这,最好不要重启
6.3 使用ipvsadm查看生成的规则
yum -y install ipvsadm
会看到svc的ip和后端pod_ip
[root@master01 kubernetes]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.1:443 rr
-> 192.168.8.10:6443 Masq 1 0 0
TCP 10.96.0.10:53 rr
-> 10.244.0.38:53 Masq 1 0 0
-> 10.244.0.39:53 Masq 1 0 0
TCP 10.96.0.10:9153 rr
-> 10.244.0.38:9153 Masq 1 0 0
-> 10.244.0.39:9153 Masq 1 0 0
TCP 10.109.208.211:80 rr
-> 10.244.1.17:80 Masq 1 0 0
-> 10.244.2.25:80 Masq 1 0 0
-> 10.244.2.26:80 Masq 1 0 0
UDP 10.96.0.10:53 rr
-> 10.244.0.38:53 Masq 1 0 0
-> 10.244.0.39:53 Masq 1 0 0
七、特殊类型的service
7.1 Headless Service
Headless Service
因为k8s中有DNS可以将service_name的名称解析为cluster_ip,这种类型的service就是为了没有cluster_ip的service_name所存在的,并且这种的service可以被解析为后端的pod_ip不需要cluster_ip,这service都称为无头服务,一般都是有状态服务
stateful(有状态):每个个体都具有一定程度的独特性,由其存储的状态决定;
stateless(无状态)
无头服务的DNS解析记录:
(正向解析记录):<a>-<b>-<c>-<d>.<service>.<ns>.svc.<zone> A PodIP
(反向解析记录):<d>.<c>.<b>.<a>.in-addr.arpa IN PTR <hostname>.<service>.<ns>.svc.<zone>
例:
- 编写无头服务svc
vim demoapp-headless-svc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp01
labels:
app: demoapp01
env: dev
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: demoapp01
template:
metadata:
labels:
app: demoapp01
spec:
containers:
- name: demoapp
image: ikubernetes/demoapp:v1.0
---
kind: Service
apiVersion: v1
metadata:
name: demoapp-headless-svc
namespace: dev
spec:
clusterIP: None #这里需要把集群ip置为空
selector:
app: demoapp01
ports:
- port: 80
targetPort: 80
name: http
- 查看svc和后端pod
可以看到并没有集群ip
[root@master01 yaml]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp-headless-svc ClusterIP None <none> 80/TCP 4s
查看ep后端pod_ip,可以看到已经代理到后端pod了
[root@master01 yaml]# kubectl describe ep demoapp-headless-svc -n dev
Name: demoapp-headless-svc
Namespace: dev
Labels: service.kubernetes.io/headless=
Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2022-06-18T12:23:37Z
Subsets:
Addresses: 10.244.1.19,10.244.1.20,10.244.2.29
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
http 80 TCP
Events: <none>
- 进入某个pod内的容器内测试解析结果
kubectl exec demoapp01-5bd6cd658d-8kzll -it /bin/sh -n dev
正向解析,可以看到解析刚刚创建的无头svc就会得到后端pod的IP
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup -query=A demoapp-headless-svc
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: demoapp-headless-svc.dev.svc.cluster.local
Address: 10.244.2.29
Name: demoapp-headless-svc.dev.svc.cluster.local
Address: 10.244.1.19
Name: demoapp-headless-svc.dev.svc.cluster.local
Address: 10.244.1.20
反向解析,找刚刚部署的pod_ip进行反向解析会看到以下结果
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup -query=PTR 10.244.1.19
Server: 10.96.0.10
Address: 10.96.0.10#53
19.1.244.10.in-addr.arpa name = 10-244-1-19.demoapp-headless-svc.dev.svc.cluster.local.
7.2 ExternalName(外部服务名称)
svc创建的时候svc_name需要对应一个域名,svc_name -> CNAME, 对应是外部服务的名称,该服务要能在外部DNS服务中被解析;
例:
- yaml文件
kind: Service
apiVersion: v1
metadata:
name: externalname-redis-svc
namespace: dev
spec:
type: ExternalName
externalName: redis.ik8s.io #这个是外部dns可以解析的地址
ports:
- protocol: TCP
port: 6379
targetPort: 6379
nodePort: 0
selector: {} #定义为空,不需要标签选择器,可以去掉
- 查看
这种没有后端pod的svc是不会创建endpoint资源的
[root@master01 yaml]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
externalname-redis-svc ExternalName <none> redis.ik8s.io 6379/TCP 10s
[root@master01 yaml]# kubectl describe svc externalname-redis-svc -n dev
Name: externalname-redis-svc
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ExternalName
IP:
External Name: redis.ik8s.io
Port: <unset> 6379/TCP
TargetPort: 6379/TCP
Endpoints: <none>
Session Affinity: None
Events: <none>
- 解析测试
进入任何一个pod容器内
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup -query=CNAME externalname-redis-svc
Server: 10.96.0.10
Address: 10.96.0.10#53
externalname-redis-svc.dev.svc.cluster.local canonical name = redis.ik8s.io.
#这里的正向解析是k8s中coreDNS将externalname-redis-svc解析为redis.ik8s.io.
#之后当k8s在试图解析redis.ik8s.io.这个域名时,发现coreDNS中没有这条记录,所以就会使用节点上的DNS,所以就解析出1.2.3.4
[root@demoapp01-5bd6cd658d-8kzll /]# nslookup externalname-redis-svc
Server: 10.96.0.10
Address: 10.96.0.10#53
externalname-redis-svc.dev.svc.cluster.local canonical name = redis.ik8s.io.
Name: redis.ik8s.io
Address: 1.2.3.4
更多推荐
所有评论(0)