kubernetes-service-5
Service在k8s平台之上,pod是有生命周期的,所以为了能够给对应的客户端提供固定的访问端点, 因此我们在客户端与服务pod之间添加了一个固定的中间层(service), 而service名称解析强依赖于k8s的DNS服务(CoreDns, 1.10之前为kube-dns), 部署完k8s之后必须要部署一个DNS服务用于解析名称。k8s 在每个节点之上有一个工作组件(kube-proxy)
Service
在k8s平台之上,pod是有生命周期的,所以为了能够给对应的客户端提供固定的访问端点, 因此我们在客户端与服务pod之间添加了一个固定的中间层(service), 而service名称解析强依赖于k8s的DNS服务(CoreDns, 1.10之前为kube-dns), 部署完k8s之后必须要部署一个DNS服务用于解析名称。
- k8s 在每个节点之上有一个工作组件(kube-proxy)
- kube-proxy始终监视着api server有关server资源的变动信息,其随时会连到api server上获取任何一个与serviece资源相关的资源变动状态通过k8s当中固有的一种请求方式(watch)来实现
- 一旦有service资源的内容发生变动 (CURD),kube-proxy 都会将其转换为当前节点之上的能够实现的service资源调度
- 包括将用户请求调度到后端的特定pod资源之上的规则,如 iptables或ipvs, 规则取决于service的实现方式
后端选择-endporint
Pod资源在Pod控制器的控制下,可能随时会重启或重构,而pod被终止之后在创建的pod与终止之前的Pod没有任何关系,从某种意义上来说这是不叫重建的,而这种新创建出来的pod只能叫是原先Pod的替代者而已,所以Pod资源存在生命周期,且不可重建;
Pod控制器是可以实现规模伸缩的,通过scale命令或直接修改deployment控制器的副本数据,就可以动态调整Pod副本数量, 而一旦pod数据增加或减少, 也会导致Pod在访问时的可用及不可用状态变动;
这种动态性会使得客户端在访问Pod时带来困扰,时有时无,所以必要时我们需要增加一个中间层service,该中间层是固定的,只要不删除或人为修改;
service本身通过select选择器,关联拥有相匹配的Pod对象,而service其实也不会直接关联pod,甚至说Service压根就不是关联pod的, 而是关联到端点Endpoint, Endpoint引用了Pod,Endpoing就是IP+端口;
比如说指定了某一个Pod的IP+端口,这就是一个Pod的Endpoint,因此Service引用的Endpoint,而Endpoint引用了Pod,因此这个时候客户端向Service请求时,Service能够将其代理并调度至后端不同的Endpoint从而把流量转发给这个Endpoint后端引用的IP地址和端口;
Endpoint后端也不一定是Pod,在一些特定的场景中,我们数据库可能并没有运行在Pod中,而是运行在k8s集群之外的主机上,那么这个时候,我们可以针对于这个数据库做一个端口,让Pod里面的服务能直接通过这个Endpoint访问集群外部的数据库,这就是为什么集群中加一个中间层的原因,因为被引用的不一定是Pod,也有可能是集群之外其他地方的服务,这也是为什么Service后端是Endpoint的这个中间层,而不是直接的Pod的原因;
资源挑选
Service做为一个标准的k8s资源,对于它的CURD操作,依旧需要借助于ApiServer进行,但是对于k8s集群上的Service来讲,它应该是被转换在每一个集群节点上的iptables或ipvs规则;
因此以集群中某一个节点为例,该节点上的Pod做为一个客户端,当它试图访问Service时,这个Service的IP地址一定存在于这个Client节点所在内核netfilter框架iptables或ipvs规则上,所以一旦该Pod对外发出网络请求,如果目标的Service一定会被当前节点上的iptabels或ipvs匹配,而这个规则可能是一个DNAT,也可能是一个简单的转发或者代理规则,一般而言不会使用DNAT逻辑,因为当Pod与Pod之间可直接到达,所以说只要一些特定的场景下才会给它做DNAT;
当我们访问Service时, 假如Service服务后端有N个Pod, 那么此时 Service会基于挑选算法(调度算法), 从众多被访问的服务端Pod中挑选一个Pod出来,并将挑选出的Endpoint对应地址,返回给客户端,或者说将流量转发过去,此时就能使得客户端与目标后端Pod直接进行通信了,此时架构 client --> service --> pod,
Service后端有可能是当前集群中的一个,也可能是集群之外服务器提供的服务,而不管如何,Service都会将流量代理到指定的节点上去,如果只是当前节点上的其它Pod,那么Service做的就只是一个转发而已,假设后端有N个Pod,DNAT虽然有调度功能,但DNAT支持的调度算法也是有限的,我们想要实现更强大的调度算法应该使用ipvs, 所以此时这里的规则可能是iptables或ipvs;
kube-proxy
Service随时有可能会被创建、修改、删除,那也就意味着我们每一个节点上的iptables或ipvs规则随时有可能会发生变化,那这个变动如何及时的反映到每个节点上呢,整个k8s集群的每一个节点都会运行一个守护进程,如果使用kubeadm去部署,它就会运行为一个Pod,就是我们的kube-proxy,它会在每一个节点上运行为一个Pod,它是一个DaemonSet控制器所控制的Pod,因此每个节点上都会有一个,并且k8s集群使用kubeadm部署它基至会容忍主节点的污点, 所以在主节点上也会运行一个;
kube-proxy做为ApiServer的客户端,做为集群中每一个节点的守护进程,它会监视所有Service资源的变动并告知ApiServer,一旦ApiServer上的某一个Service资源发生变动,ApiServer会立即通知到每一个监视这个Service的所有kube-proxy,而后kube-proxy转换为iptables或Ipvs规则,所以支持Service实现的重要组件就是Kube-proxy进程,这个进程监听所有Service资源变动,实时将其转换为节点上的规则
kube-proxy --> apiserver
kube-proxy 每个节点上的守护进程 监控 seriver 发生变动提交给api server --> 保存etcd保存 然后 api server通告所有节点上的kube-proxy , 然后在每个节点上修改对应的iptables|ipvs规则链
service网络功能
如果k8s要想能够向客户端提供网络功能,需要依赖第三方解决方案(CNI:容器网络插入标准), 在kuberentes中有三类ip地址( node network、 pod network、 cluster network ( 虚拟IP地址 ) )
service实现方式
user space
用户请求到达service以后,由service先把它转为本地监听在某个套接字上的用户空间的kube-proxy, 由kube-proxy处理, 处理完之后再转给service ip, 最终代理至其它service相关连的各种pod,实现调度
这种用户空间实现的效率很低, 调度请求需要先到内核空间,然后回到主机上的用户空间,由kube-proxy封装代理完之后在回到内核空间,然后在由Iptables规则分发
iptables
客户端IP请求时直接请求service的ip, 这个请求报文被本地内核中的service iptabels规则所截取,进而直接调度给相关连的service pod , 然后由直接工作在内核空间由iptables规则直接负责调取,iptables与ipvs差不多 iptables是通过端口转发方式,而ipvs是通过均衡负载方式 vip转发,
service的iptables规则
数据 --> 用户请求的 service 的ip地址
网卡 --> 到达节点的ip地址, 被内核截取
内核 --> 到达内核0, 这里由内核 iptables规则调取分发
用户空间 --> 分发之后到达用户空间的Pod, 返回数据
ipvs
1.11版之后默认就是ipvs, 如果没有被激活它会降级为iptabels
service资源
节点访问
ClusterIp: 无法接入集群外部边界流量,如果想接入外部流量需要使用Nodeport
Nodeport: client --> NodeIP: NodePort --> ClusterIp:ServicePort --> PodIP:containerPort
loadBalancer: 负载均衡器类型
ExternelName: 当pod需要访问集群外部的服务时,extenrel会将集群外服务映射到集群内部中,使用FQDN记录集群地址 把外部名称映射为内部名称,
# 获取 service资源
]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# 集群内的各种pod需要与k8s集群的apiserver联系时都是通过它,切勿删除
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
属性
spec.clusterIP: # 动态分配的集群地址, 不指定就是自动获取
spec.ports : # 将主机某个端口与后端容器的端口建立连接关系
spec.selector # 关连到哪些pod资源上
# 只支持等值关系,也没有matchLabels
spec.type:
ClusterIP: # 集群IP地址,谨用于集群内通信
NodePort : # 与集群外地址联系
LoadBalancer:# 创建软负载均衡器时使用,自动触发在外部创建一个负载均衡器
ExternalName:# 把集群外部的服务引用到集群内部使用
spec.ports
name: # 指明 port 名称 (别名)
nodePort: # 指定节点上的端口 service上的端口,只有使用NodePort才有效,在集群外部访问使用
port: # 指定对外提供服务的端口, expose暴露的端口
targetPort: # 指定容器上的端口, pod上面的端口
# 节点端口无需指定,节点类型只有是nodeport时节点端口才有用
ExternalName:在集群中建立一个service服务,而该service的服务端点不是本地的pod而是服务器外的服务,客户端访问service时,由service通过层级转换请求到外部服务中, 外部服务先响应给node ip, 再由node ip转交给 service, 最后由service交给pod client, 从而让集群内的pod访问集群外的服务。
sesssionAffinity: None: 不定义就是负载均衡的效果 , clientIp 将来自同一个客户端的IP发往同一个pod
ClusterIP
资源记录: SVC_NAME.NS_NAME.DOMAIN.LTD.
集群默认后缀: svc.cluster.local.如不修改就是 redis.default.svc.cluster.local.
apiVersion: v1
kind: Service # serevice类型
metadata:
name: redis # dns解析的名称
labels:
app: redis
release: base01
spec:
ports:
- name: redis-port
port: 6379
targetPort: 6379
selector:
app: my-redis
release: base01
type: ClusterIP
查看资源
数据关连关系: service --> endpoints --> 后端pod
]# kubectl describe svc redis
Name: redis
Namespace: default
Labels: app=redis
release=base01
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"redis","release":"base01"},"name":"redis","namespace":"d...
Selector: app=my-redis,release=base01
Type: ClusterIP
IP: 10.102.43.240
Port: redis-port 6379/TCP
TargetPort: 6379/TCP
Endpoints: 10.244.1.68:6379
~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis ClusterIP 10.102.43.240 <none> 6379/TCP 1m31s
# 随便进一个pod,然后解析该地址
~]# kubectl exec -it my-deploy-5fbb68fb8c-qj57l /bin/sh
Address 1: 10.102.43.240 redis.default.svc.cluster.local
# 如果使用clusterIp在节点中访问只能访问本地的pod,而不能访问其它节点的pod
nodePort
NodePort是指,对于一个Service来说,它该有的ClusterIP还是有的,但除此之外,我们会在每一个节点上生成一个iptables或ipvs规则,它允许集群外部客户端去访问节点的IP地址和端口,这个节点端口是通过iptables规则进行操作的,比如说节点的80端口映射到clusterIP的80端口,然后由ClusterIP转发到Endpoint继而转发给Pod,所以外部主机通过访问节点的IP和端口就能直接访问到Pod里面的服务了,由于Service的iptables或者ipvs规则是配置的每一个节点上的,所以每一个节点的同一个端口都为NodePort提供服务;
nodePort: 需要确保每一个节点端口没有被占用, 如果不指定它会动态分配
端口转换说明: nodeport–> service port --> pod port
NodePort访问路径: clinet --> nodeIp: NodePort --> ClusterIp:ServicePort --> PodIp:ContainerPort
]# cat nginx_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
service: nginx
spec:
type: NodePort
ports:
- name: nginx
port: 80
targetPort: 80
nodePort: 30001 # 端口范围:30000-32767, 如果不指定则由系统随机指定
selector:
app: nginx
release: stable
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
labels:
app: my-nginx
release: stable
spec:
replicas: 2
selector:
matchLabels:
app: nginx
release: stable
template:
metadata:
name: my-nginx
namespace: default
labels:
app: nginx
release: stable
spec:
containers:
- name: my-nginx
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 3
httpGet:
port: http
path: index.html
查看资源
]# kubectl describe svc nginx
Type: NodePort
Port: nginx 80/TCP
TargetPort: 80/TCP
NodePort: nginx 30001/TCP
Endpoints: 10.244.1.73:80,10.244.2.64:80
]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.104.195.113 <none> 80:30001/TCP 2s
]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
my-nginx 2/2 2 2 3m44s
检测资源
]# while true;do curl http://192.168.9.30:30001/hostname.html;sleep 2; done
my-nginx-64f758c945-vprjx # 自动负载均衡
my-nginx-64f758c945-spvnc
# 通过直接编辑修改控制器,将版本升级成v2
]# kubectl edit deployments.apps my-nginx
# 升级方式2
]# kubectl set image deployment my-nginx my-nginx=ikubernetes/myapp:v1
# 再次检查版本就升级成v2了
]# while true;do curl http://192.168.9.30:30001;sleep 2; done
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
LoadBalancer
LoadBalancer是kubernetes为NodePort类型引入自动管理的外部负载均衡器,它会向底层cloud provider的API发送请求,由其按需创建,用户也可以手动提供负载均衡器,但它不再属于LoadBalancer类型;
ExternalName
ExternalName即为外部名称,它将集群外部Service引入集群内供各Pod客户端使用,我们在创建一个Service时,不指定标签选择器,不选Pod,而是直接指明类型为ExternalName,并externalName定义一个域名,这个域名能够被我们的DNS服务器解析为外部的IP地址,而这个外部的IP地址就是外部提供服务的对应的端点,那这个时候,客户端对此服务的访问就都被转给外部的服务了。这种方式是使用名称的方式来引用的;
Service标签选择器关联Pod的主要作用在于,它能够自动的为关联到的所有Pod创建一个Endpoint资源,那对于ExternalName类型来说,没有标签选择器了,它就不会自动创建Pod,那因此我们可以手动创建一个endpoint,把它的IP地址和端口指向集群之外的主机的IP地址和端口,然后再把这个endpoint作为ExternalName这个Service的引用放,因此所有发送给这个Service的请求都会被转发到手动创建的这个endpoint上面来,从而就调度到了集群外部的主机了;
无头services
解析service时,既不指点cluster_ip也不让它随机分配, 本地DNS解析时会自动解析给后端的pod IP,像这种类型的service就叫无头services, 客户端访问的就直接是pod, 无头service必须要指定 clusterIP: “None”, 无头服务只能是cluster_ip, 给它解析成CNAME直接配置然后通过DNS访问
创建yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
labels:
service: nginx
spec:
# 定义集群地址为None, 无头service只能是clusterIp
clusterIP: None
ports:
- name: nginx
port: 80
targetPort: 80
selector:
app: nginx
release: stable
]# kubectl apply -f nginx_nohead.yaml
service/nginx-svc created
查看资源
]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc ClusterIP None <none> 80/TCP 9s
]# kubectl describe svc nginx-svc
Name: nginx-svc
Selector: app=nginx,release=stable
Type: ClusterIP
Port: nginx 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.75:80,10.244.2.66:80
# 通过dns解析查看 yum -y install bind-utils
# 解析A记录, 名称.集群默认后缀 @解析DNS地址
]# dig -t A nginx-svc.default.svc.cluster.local @10.96.0.10
nginx-svc.default.svc.cluster.local. 30 IN A 10.244.2.66
nginx-svc.default.svc.cluster.local. 30 IN A 10.244.1.75
# 查看本机的DNS服务地址
]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 20d
.0.10
nginx-svc.default.svc.cluster.local. 30 IN A 10.244.2.66
nginx-svc.default.svc.cluster.local. 30 IN A 10.244.1.75
查看本机的DNS服务地址
]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 53/UDP,53/TCP,9153/TCP 20d
更多推荐
所有评论(0)