目录

Service 的概念

Service 的类型

四种类型

VIP 和 Service 代理

代理模式的分类

 测试用例

ClusterIP

Headless Service(无头服务)

NodePort

 LoadBalancer(了解即可)

ExternalName


假如说,我们有下面的服务需要部署进入我们的k8s集群中,

k8s headless service 使用 k8s serverless实现_k8s

方法:先创建我们的deployment,通过deployment部署我们的NGINX,它的副本数为1,然后去部署php-fpm,副本数为3,再部署MySQL,通过我们的StatefulSet,对于有状态服务,我们一般通过StatefulSet。

意外情况:假如说,有一天我们的php-fpm中有一个突然挂掉了,那么此时我们的副本数目不满足3个了,此时就会再次创建出一个pod,那么新创建出来的这个pod,它的ip就会变化,此时NGINX里面还是原来的那个ip,新的ip它不认识,此时就会报错。。。这样就引入出我们的SVC概念,根据标签匹配至对应的pod,然后负责去监测pod的信息,会同步它所监测的pod信息,我们的NGINX再去反向代理SVC的话,SVC会自动更新,不需要我们的NGINX做任何的修改。

k8s headless service 使用 k8s serverless实现_svc_02


Service 的概念

Kubernetes  Service  定义了这样一种抽象:一个  Pod  的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。 这一组  Pod  能够被  Service  访问到,通常是通过  Label Selector(如下图的红框)。

k8s headless service 使用 k8s serverless实现_k8s_03

分析:我们的Frontend Deployment创建了三个pod,那么个pod里面有三组不同的标签,上面又定义一个Frontend Service,它所对应的pod标签是当app=webapp,role=frontend的时候,就会匹配上我们的这个SVC,注意,匹配的话,主要满足就行,pod里面那个version=1.0.0不用管。。一旦匹配成功之后,这个pod的信息就会被写入到SVC中,以后我们去访问这个SVC,相当于访问下属的pod,通过轮询的方式访问pod。。并且当我们的pod有死亡的时候,我们新创建出来的pod的信息也会被写入到SVC中去。

Service能够提供负载均衡的能力,但是在使用上有以下限制:

只提供 4 层负载均衡能力(也就是只能基于我们的ip地址和端口进行转发,实现负载均衡),而没有 7 层功能,也就是不能通过我们的主机名和域名的方案去负载均衡(这个可以通过ingress实现七层负载均衡),但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的。

Service 的类型

四种类型

Service 在 K8s 中有以下四种类型:

ClusterIp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP。

NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 : NodePort 来访问该服务。

LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到: NodePort。

ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持。

k8s headless service 使用 k8s serverless实现_k8s_04

分析:SVC服务发现需要上图几个组件的配合,首先由我们的apiserver去监听我们的服务和端口,通过我们的kube-proxy,kube-proxy负责去监听对应的pod,把对应pod的信息写入到iptables规则里去,当我们的客户端想要访问SVC的时候,其实就是访问我们的iptables规则。。这里其实几个概念,客户端访问到我们的节点是通过iptables去实现的,iptables的规则是通过我们的kube-proxy去实现的,apiserver通过kube-proxy去实现监听我们服务的端口和发现。kube-proxy会通过pod的标签去判断这个端点信息是否写入到我们的SVC中的端点信息中去。

VIP 和 Service 代理

在 Kubernetes 集群中,每个 Node 运行一个  kube-proxy  进程。 kube-proxy  负责为  Service  实现了一种VIP(虚拟 IP)的形式,而不是  ExternalName  的形式。 在 Kubernetes v1.0 版本,代理完全在 userspace。在Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。 从 Kubernetes v1.2 起,默认就是iptables 代理。 在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理。

在 Kubernetes 1.14 版本开始默认使用 ipvs 代理

在 Kubernetes v1.0 版本, Service 是 “4层”(TCP/UDP over IP)概念。 在 Kubernetes v1.1 版本,新增了Ingress API(beta 版),用来表示 “7层”(HTTP)服务。。

问:为何不使用 round-robin DNS?

回答:DNS会在很多的客户端中进行缓存。。。我们很多服务用DNS进行域名解析之后,不会对DNS进行清除缓存的操作,也就是我们一旦有它的地址信息之后,不管访问几次,还是原来的地址信息。

代理模式的分类

Ⅰ、userspace 代理模式

k8s headless service 使用 k8s serverless实现_负载均衡_05

分析:我们的客户端访问server的方式是通过iptables(防火墙),再到kube-proxy,然后到server,包括我们的kube-apiserver也会通过kube-proxy去监听所谓的服务的更新以及端点的维护。这样我们的kube-proxy的负载压力很大。

Ⅱ、iptables 代理模式

k8s headless service 使用 k8s serverless实现_负载均衡_06

分析:我们发现,所有的访问直接由我们的iptables(防火墙)去完成,不需要通过kube-proxy去调度一个,这样我们的访问速度就会增加,以及kube-proxy的稳定性会提高和压力会减小。这个的话,除了防火墙的性能没那么高,,其他没什么影响。

Ⅲ、ipvs 代理模式

这种模式,kube-proxy 会监视 Kubernetes Service 对象和 Endpoints ,调用 netlink 接口以相应地创建ipvs 规则并定期与 Kubernetes Service 对象和 Endpoints 对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod。。

与 iptables 类似,ipvs 于netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs 为负载均衡算法提供了更多选项,例如:

rr :轮询调度

lc :最小连接数

dh :目标哈希

sh :源哈希

sed :最短期望延迟

nq : 不排队调度

注意如下说明:

ipvs模式启动成功的前提是宿主机上安装了ipvs相关模块,如果未安装,则会退回到iptables代理模式

k8s headless service 使用 k8s serverless实现_IP_07

k8s headless service 使用 k8s serverless实现_IP_08

k8s headless service 使用 k8s serverless实现_nginx_09

分析:这个模式已经成为标准了,先安装yum install ipvsadm,使用命令:ipvsadm -Ln,这个也是检测我们集群当中有没有开启ipvs模式

k8s headless service 使用 k8s serverless实现_k8s_10

 测试用例

ClusterIP

clusterIP 主要在每个 node 节点使用 iptables/ipvs,将发向 clusterIP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口。。。

k8s headless service 使用 k8s serverless实现_k8s_11

分析:Nginx相当于访问者,WebApp-1/2/3相当于被访问者

为了实现图上的功能,主要需要以下几个组件的协同工作:

1. apiserver 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中。

k8s headless service 使用 k8s serverless实现_负载均衡_12

2. kube-proxy kubernetes的每个节点中都有一个叫做kube-porxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables规则中

3. iptables 使用NAT等技术将virtualIP的流量转至endpoint中

为了演示的效果,我们需要先创建一个deployment,然后去绑定我们的SVC。。。

创建 depl-demo.yaml文件

[root@master1 svc]# ls
depl-demo.yaml  depl-svc.yaml
[root@master1 svc]# cat depl-demo.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      release: stabel
  template:
    metadata:
      labels:
        app: myapp
        release: stabel
        env: test
    spec:
      containers:
      - name: myapp
        image: nginx:alpine
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 8
[root@master1 svc]#

然后给这个deployment绑定了一个SVC

创建 Service 信息

[root@master1 svc]# cat depl-svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: myapp
    release: stabel
  ports:
  - name: http
    port: 80
    targetPort: 80      #后端的pod的真实端口
[root@master1 svc]#

注意:我们创建的SVC跟上面先创建的deployment实现匹配的上,是根据labels,也就是deployment下面的matchLabels的键值对和SVC中的selector对应的上即可绑定成功。。

k8s headless service 使用 k8s serverless实现_负载均衡_13

然后下面就是显示的效果:访问SVC的10.1.96.245的80端口,就相当于去访问后端的那三个ip

k8s headless service 使用 k8s serverless实现_k8s_14

rr:表示轮询

注意:我们在用xxx.yaml文件创建pod的时候,如果不想要这个pod了,可以使用kubectl delete -f xxx.yaml命令删除我们这个yaml文件创建的pod。

在 Cluster 内部中,除了可以通过 Cluster IP 访问 Service,Kubernetes 还提供了更为方便的 DNS 访问,在kubeadm 部署时会默认安装 kube-dns 组件。

coredns 是一个 DNS 服务器。每当有新的 Service 被创建,coredns 会添加该 Service 的 DNS 记录。Cluster 中的 Pod 可以通过 <SERVICE_NAME>.<NAMESPACE_NAME> 访问 Service。

比如可以用 myapp.default 访问 Service myapp。

[root@master1 controller]# kubectl run nginx --image=nginx:alpine
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/nginx created
[root@master1 controller]# 
[root@master1 controller]# 
[root@master1 controller]# 
[root@master1 controller]# kubectl get pod 
NAME                            READY   STATUS    RESTARTS   AGE
myapp-deploy-65ff67dcb6-2q92v   1/1     Running   0          8m24s
myapp-deploy-65ff67dcb6-8pbcg   1/1     Running   0          8m24s
myapp-deploy-65ff67dcb6-x88m4   1/1     Running   0          8m24s
nginx-74d5899f46-ggggl          1/1     Running   0          4s
[root@master1 controller]# kubectl exec -it nginx-74d5899f46-ggggl sh 
/ # curl myapp.default
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
/ #

上面的就是通过我们重新创建一个pod(nginx-74d5899f46-ggggl),然后验证集群内部通过访问<SERVICE_NAME>.<NAMESPACE_NAME> 访问 Service ,可以看到访问成功。

Headless Service(无头服务)

有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 Cluster

IP(spec.clusterIP) 的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。。。

(这类服务的作用主要就是,通过这种方式去解决我们的所谓的hostname和我们的podname的变化问题,即通过它去绑定)

[root@master1 svc]# cat svc-headless.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
  namespace: default
spec:
  selector:
    app: myapp
  clusterIP: "None"
  ports:
  - port: 80
    targetPort: 80
[root@master1 svc]# kubectl get svc 
NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes       ClusterIP   10.1.0.1      <none>        443/TCP   63m
myapp            ClusterIP   10.1.96.245   <none>        80/TCP    20m
myapp-headless   ClusterIP   None          <none>        80/TCP    47s
[root@master1 svc]# kubectl get pod -o wide -n kube-system
NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
coredns-645bfc575f-mpmf8          1/1     Running   0          64m   10.244.0.69      master1   <none>           <none>
coredns-645bfc575f-vlktl          1/1     Running   0          64m   10.244.0.68      master1   <none>           <none>
etcd-master1                      1/1     Running   0          63m   192.168.64.150   master1   <none>           <none>
kube-apiserver-master1            1/1     Running   0          63m   192.168.64.150   master1   <none>           <none>
kube-controller-manager-master1   1/1     Running   0          63m   192.168.64.150   master1   <none>           <none>
kube-flannel-ds-amd64-wn26n       1/1     Running   0          64m   192.168.64.150   master1   <none>           <none>
kube-proxy-8vhd7                  1/1     Running   0          46m   192.168.64.150   master1   <none>           <none>
kube-scheduler-master1            1/1     Running   0          63m   192.168.64.150   master1   <none>           <none>
[root@master1 svc]#

分析:我们如果访问这个无头服务,会有什么情况发生呢?

我们知道,对应SVC一旦创建成功,就会有一个主机名,它就会写入到我们的coredns中去,它的写入格式是:svc的名称+名字空间的名称+集群的域名(默认为svc.cluster.local)

下面的是通过dig命令去解析一下

最后会得到如下:

[root@master1 svc]# dig  -t A myapp-headless.default.svc.cluster.local. @10.244.0.69

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.5 <<>> -t A myapp-headless.default.svc.cluster.local. @10.244.0.69
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13719
;; flags: qr rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp-headless.default.svc.cluster.local. IN A

;; ANSWER SECTION:
myapp-headless.default.svc.cluster.local. 22 IN	A 10.244.0.73
myapp-headless.default.svc.cluster.local. 22 IN	A 10.244.0.71
myapp-headless.default.svc.cluster.local. 22 IN	A 10.244.0.72

;; Query time: 0 msec
;; SERVER: 10.244.0.69#53(10.244.0.69)
;; WHEN: 六 8月 28 15:58:36 CST 2021
;; MSG SIZE  rcvd: 237

[root@master1 svc]#

可以和我的pod对应起来,也就意味着在我们的无头服务中,虽然没有自己的SVC了,但是可以通过访问域名的方案去访问这几个不同的pod上去。

[root@master1 svc]# kubectl get pod -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES
myapp-deploy-65ff67dcb6-27vmt   1/1     Running   0          24m   10.244.0.73   master1   <none>           <none>
myapp-deploy-65ff67dcb6-lgtg8   1/1     Running   0          24m   10.244.0.71   master1   <none>           <none>
myapp-deploy-65ff67dcb6-mcttp   1/1     Running   0          24m   10.244.0.72   master1   <none>           <none>
[root@master1 svc]# 
[root@master1 svc]# kubectl exec -it myapp-deploy-65ff67dcb6-27vmt sh 
/ # ping myapp-headless.default.svc.cluster.local -c 3
PING myapp-headless.default.svc.cluster.local (10.244.0.71): 56 data bytes
64 bytes from 10.244.0.71: seq=0 ttl=64 time=0.070 ms
64 bytes from 10.244.0.71: seq=1 ttl=64 time=0.160 ms
64 bytes from 10.244.0.71: seq=2 ttl=64 time=0.193 ms

--- myapp-headless.default.svc.cluster.local ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.070/0.141/0.193 ms
/ #

关于无头服务更加详细的介绍:https://cloud.tencent.com/developer/article/1820094

NodePort

它的概念就是可以在我们的物理机上暴露一个端口,我们就可以通过物理机的ip+端口的方式去访问至集群内部的服务

nodePort 的原理在于在 node 上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步到给对应的 pod。

[root@master1 svc]# cat svc-nodeport.yaml 
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  type: NodePort
  selector:
    app: myapp
    release: stabel
  ports:
  - name: http
    port: 80
    targetPort: 80

k8s headless service 使用 k8s serverless实现_svc_15

k8s headless service 使用 k8s serverless实现_IP_16

而且我们发现上面的是端口是kube-proxy去监听的

[root@master1 svc]# lsof -i :32435
COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
kube-prox 27288 root   11u  IPv6 243499      0t0  TCP *:32435 (LISTEN)

 LoadBalancer(了解即可)

loadBalancer 和 nodePort 其实是同一种方式。区别在于 loadBalancer 比 nodePort 多了一步,就是可以调用cloud provider (云供应商)去创建 LB 来向节点导流。。。

k8s headless service 使用 k8s serverless实现_svc_17

ExternalName

这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容( 例如:hub.atguigu.com )。ExternalName Service 是 Service 的特例,它没有 selector(也就是没有标签选择),也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

[root@master1 svc]# cat externalname.yaml 
kind: Service
apiVersion: v1
metadata:
  name: my-service-1
  namespace: default
spec:
  type: ExternalName
  externalName: hub.atguigu.com
[root@master1 svc]# kubectl get svc 
NAME             TYPE           CLUSTER-IP    EXTERNAL-IP       PORT(S)        AGE
kubernetes       ClusterIP      10.1.0.1      <none>            443/TCP        93m
my-service-1     ExternalName   <none>        hub.atguigu.com   <none>         98s
myapp            NodePort       10.1.52.170   <none>            80:32435/TCP   14m
myapp-headless   ClusterIP      None          <none>            80/TCP         30m

 

k8s headless service 使用 k8s serverless实现_k8s_18

当查询主机 my-service-1.defalut.svc.cluster.local (SVC_NAME.NAMESPACE.svc.cluster.local )时,集群的DNS 服务将返回一个值hub.atguigu.com 的 CNAME(别名) 记录。访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发 。