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访问集群外的服务。

sesssionAffinityNone: 不定义就是负载均衡的效果 , 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


Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐