云原生下的底层系统平台保姆级教程_八kubernetes服务发现之service4种类型

7.5 service 4 种类型演示范例:

7.5.1 cluster ip 范例演示:

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

注意:由于我们在底层采用的方案不一样所以它的代理方式也可能不同

上图中我们得 nginx 相当于一个客户端,然后 webapp-1、webapp-2、webapp-3 相当于我们的客户端,也就是说 nginx 是我们的访问者,后端的各种 webapp 是被访问者,我们会发现后端被访问者有很多个,所以中间我们通过创建 SVC 的方式将这个服务进行维稳,也就意味着这个 cluster IP 会去绑定至三个后端不同的 web-app pod 之上。那如果我们的 nginx 访问至这个 SVC 的 cluster ip 加 port 上的时候相当于就是代理至后端不同的 pod 上,并且使用的是 RR 轮询算法。

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

apiserver 用户通过 kubectl 命令向 apiserver 发送创建 service 的命令,apiserver 接收到请求后将数据存储至 etcd 中,注意:apiserver 并没有直接操作完成

kube-proxy 、 kubernetes 的每个节点上都有一个假作 kube-proxy 的进程,这个进程负责感知 service、pod 的变化,并将变化的信息写入到本地的 iptables 规则中。也就是我们有这么一个流程:apiserver 先将信息写到 etcd 里,然后 kube-proxy 再去监控检测 etcd 里发生的变化,然后 kube-proxy 得到变化以后就会写入到我们的 iptables 规则,但这里 iptables 规则说的是本地的,因为每一个节点都会有自己对应的 kube-proxy 的进程存在。当然这里写入到 iptables 也不太合理,这得看我们本地使用的是什么代理

iptables 使用 NAT 等技术将 virtualip(虚拟ip)得流量转至 endpoint(也就是后端服务得真实信息) 中

为了演示我们的实验效果我们先创建了一个 deployment ,然后再给这个 deployment 去绑定了一个 SVC

7.5.1.1 创建 deployment

yaml 文件详解:

apiVersion: apps/v1 #api 主版本使用的是 appsv1kind: Deployment#类型为 deploymentmetadata: #元数据信息name: myapp-deploy#这个 deployment 控制器叫做 myapp-deploynamespace: default#使用的名称空间是我们的 defaultspec: #详细信息描述replicas: 3 #当前的副本数目为 3selector: #选择器  matchLabels:#匹配标签    app: myapp#匹配条件 app = myapp    release: stabel #release = stabel 即可template: #创建这个 deployment 的模板  metadata: #元数据信息    labels: #标签信息      app: myapp#第一个标签为 app=myapp      release: stabel #第二个标签为 release=stabel      env: test #第三个标签为 env=test  spec: #详细信息描述    containers: #容器信息    - name: myapp #容器名称为 myapp      image: nginx#使用的镜像是 nginx      imagePullPolicy: IfNotPresent #镜像下载策略为 ifnotpresent 如果有 nginx 镜像就不用下载      ports:#端口信息      - name: http#释放的端口名称为 http        containerPort: 80 #端口是 80

1、编写我们的 svc-deployment.yaml 文件

[20:30:20 root@k8s-master-1 ~]#vim svc-deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:name: myapp-deploynamespace: defaultspec:replicas: 3selector:  matchLabels:    app: myapp    release: stabeltemplate:  metadata:    labels:      app: myapp      release: stabel      env: test  spec:    containers:    - name: myapp      image: nginx      imagePullPolicy: IfNotPresent      ports:      - name: http        containerPort: 80

2、通过kubectl apply创建该 deployment

[20:30:19 root@k8s-master-1 ~]#kubectl apply -f svc-deployment.yaml kubectl apply -f: #使用声明式的方式通过 yaml 文件创建 deploymentsvc-deployment.yaml:#需要创建 deployment 的 yaml 文件

3、kubectl get pod查看这三个 deployment 的 pod 已经在运行了。

[20:32:01 root@k8s-master-1 ~]#kubectl get pod NAME                           READY   STATUS  RESTARTS   AGEmyapp-deploy-5954fc89f-58kz9   1/1     Running   0        113smyapp-deploy-5954fc89f-5xrlt   1/1     Running   0        113smyapp-deploy-5954fc89f-lpxw2   1/1     Running   0        113s

那我现在知道了这么多的容器在运行,我们要想访问的话就必须要借助到一个 SVC 不然的话访问比较费劲。因为这么多的 pod 每一个 pod 都有自己的地址,并且 pod 死亡之后它的 ip 就会发生改变,所以为了进行一个可靠的访问我们就需要借助到我们的 svc。

7.5.1.2 创建 service (SVC) 信息

SVC yaml 模板解释:

apiVersion: v1 #api 版本为 v1kind: Service #kind 类型 servicemetadata: #元数据信息name: myapp #SVC 名称为 myappnamespace: default #使用的是 default 名称空间,因为 SVC 依然是我们名称空间的资源spec: #详细描述type: ClusterIP #使用的 SVC 类型是我们的 cluster IPselector: #标签选择器,下面匹配条件一定要与我们的 deployment yaml 文件中的 selector 相同  app: myapp #第一个标签 匹配条件是 app=myapp  release: stabel #第二个标签 匹配条件是 release=stabelports: #端口暴露- name: http #名称为 http  port: 80 #宿主机暴露端口是 80  targetPort: 80 #容器目标端口 80

1、编写一个 SVC.yaml 文件

[20:48:16 root@k8s-master-1 ~]#vim mysvc.yamlapiVersion: v1kind: Servicemetadata:name: myappnamespace: defaultspec:type: ClusterIPselector:  app: myapp  release: stabelports: - name: http  port: 80  targetPort: 80

2、通过kubectl apply创建 SVC

[21:00:05 root@k8s-master-1 ~]#kubectl apply -f mysvc.yaml

3、通过kubectl get svc查看我们的 SVC 已经创建出来了,并且访问端口是我们的 80,一就意味着我们访问这个 SVC 的地址,他就会通过 ipvs 的代理模块到了后端那几台标签相同的 pod 之上。

[08:24:29 root@master-1 ~]#kubectl get svcNAME         TYPE      CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGEkubernetes   ClusterIP   172.30.0.1    <none>        443/TCP   9hmyapp      ClusterIP   172.30.172.57   <none>        80/TCP  9h

4、通过 master 这个节点访问这个 SVC 的 ip 是没有问题的

其实对于我们的 SVC 来说这种类型只是我们的 cluster IP SVC 的一个标准类型,也就是通过我们这里的 ipvs 或者是 iptables 模块进行所谓的负载均衡。去实现我们代理的方案这是没有问题的。但其实 SVC 的 cluster IP 类型还有另一种叫做 headless service(无头服务)

7.5.1.3 cluster IP 之 headless service (无头服务)

也就意味着对于我们的 headless service 来说这里有个特殊点就是:有时不需要或不想要负载均衡,以及单独的 service IP 。遇到这种情况,可以通过指定 cluster IP(spec.cluster IP)的值为 none 来创建 headless service(无头服务)。这类的 service 并不会分配 cluster IP ,kube-proxy 不会处理他们,而且平台也不会为它们进行负载均衡和路由。

那这里主要有什么特点呢?主要是想通过这种方式去解决我们的所谓的 portname 和 hostname 的变化问题,也就是通过 headless service 进行绑定。

7.5.1.3.1 通过 yaml 文件创建一个 无头服务

yaml 文件详解:

apiVersion: v1# api主接口使用的是 v1kind: Service # kind 类型是 servicemetadata: # 元数据信息name: myapp-headless# 这个 service 名称为 myapp-headlessnamespace: default# 使用的是默认名称空间spec: # 详细描述selector: # 选择器标签  app: myapp# 标签匹配 app=myappclusterIP: "None" # cluster IP 使用的是 none 空 IPports: # 暴露端口声明- port: 80# 宿主机端口为 80  targetPort: 80# 容器端口为 80

1、编写headless.yaml文件

[08:44:21 root@master-1 ~]#vim headless.yamlapiVersion: v1kind: Servicemetadata:name: myapp-headlessnamespace: defaultspec:selector:  app: myappclusterIP: "None"ports:- port: 80  targetPort: 80

2、通过 kubectl apply 创建 SVC

[08:45:56 root@master-1 ~]#kubectl apply -f headless.yaml kubectl apply -f: #声明式创建headless.yaml : #使用该 yaml 文件创建

3、通过kubectl get svc 查看当前的 svc 就多了我们的 myapp-headless 并且 clusterIP 为 none

[08:46:02 root@master-1 ~]#kubectl get svcmyapp-headless   ClusterIP   None          <none>        80/TCP  48s

4、那如果我现在访问这个服务会发什么情况,对于 SVC 一旦创建成功呢会被写入到 coredns 中去,因为我们的 svc 创建会有一个主机名所以就会被写入到 coredns 。他写入的格式就是,svc 名称 . 使用名称空间的名称 . 加上集群的域名 . 最后通过 coredns 进行解析

1)首先我们先获取当前 dns 地址信息,这里我们的 coredns 地址式 10.10.0.4 和 10.10.0.5 这两个选择其中任意一个即可

首先我们先获取当前 dns 地址信息[08:51:48 root@master-1 ~]#kubectl get pod -n kube-system -o wide

5、通过 dig命令进行解析

[08:55:49 root@master-1 ~]#dig -t -A myapp-headless.default.svc.linux38.local. @10.10.0.5dig -t -A:#就是我们进行一个 A 记录的解析 [email protected]:#这是我们 A 记录的名称

6、在通过kubectl get pod -o wide进行查看 pod 的详细信息就会发现每个我们的 svc 管理的每个 pod IP 和我们查看到 DNS 的IP 完全相同。也就意味着在我们在我们无头服务中虽然没有自己的 SVC 了,但是可以通过访问域名的方案依然可以访问至这几个不同的 pod 上去,这个就是我们无头服务的含义

[08:59:48 root@master-1 ~]#kubectl get pod -o wideNAME                           READY   STATUS  RESTARTS   AGE   IP        NODE     NOMINATED NODE   READINESS GATESmyapp-deploy-5954fc89f-jlswb   1/1     Running   0        10h   10.10.1.5   node-1   <none>           <none>myapp-deploy-5954fc89f-t2qhm   1/1     Running   0        10h   10.10.2.4   node-2   <none>           <none>myapp-deploy-5954fc89f-zxpbp   1/1     Running   0        10h   10.10.1.6   node-1   <none>           <none>

记住无头服务也是我们的一种 cluster IP

7.5.2 NodePort 类型演示范例:

nodeport 的原理在于 node 物理机上开启了一个端口,将向该端口的流量导入到kube-proxy,然后由 kube-proxy 进一步到给对应的 pod  。别人可以通过 node 物理机的 ip 加暴露的随机端口方式访问至集群内部的这么一个服务, nodeport 也是我们默认类型里的一个能够将内部服务暴露给外部的这么一种方式

yaml 文件详解:

apiVersion: v1# api 主版本为 v1kind: Service # kind 类型为 servicemetadata: # 元数据信息name: myapp # service 名称为 myappnamespace: default# 使用的式默认名称空间spec: # 详细信息描述type: NodePort# type 类型为 nodeportselector: # 标签选择器  app: myapp# 标签一 app=myapp  release: stabel # 标签二 release=stabelports:# 暴露端口信息- name: http# 端口名称 http  port: 80# 容器端口  targetPort: 80# pod 暴露端口 80

1、编写MYAPP-NODEPORT.YAML文件

[09:23:29 root@master-1 ~]#vim mysvc-nodeport.yaml apiVersion: v1kind: Servicemetadata:name: myappnamespace: defaultspec:type: NodePortselector:  app: myapp  release: stabelports:- name: http  port: 80  targetPort: 80

2、通过kubectl apply 创建 svc

[09:24:20 root@master-1 ~]#kubectl apply -f mysvc-nodeport.yaml kubectl apply -f: #声明式创建 podmysvc-nodeport.yaml : #创建 pod 的 yaml 文件

3、通过kubectl get svc 查看我们的 service 已经被创建,并且暴露在宿主机上的随机端口是 32043

[09:25:12 root@master-1 ~]#kubectl get svcNAME             TYPE      CLUSTER-IP    EXTERNAL-IP   PORT(S)      AGEkubernetes       ClusterIP   172.30.0.1    <none>        443/TCP      10hmyapp          NodePort    172.30.172.57   <none>        80:32043/TCP   10hmyapp-headless   ClusterIP   None          <none>        80/TCP         39m

4、通过 kubectl get pod 查看我们的 pod 已经被创建,并且我们会发现一组 pod 是可以对应到多个不同的 svc 的,只要 svc 的 Label 标签和我们 pod 的 label 标签一致是可以被关联到的。也就是这里的对应关系式 N 对 M,多对多的。

[09:25:20 root@master-1 ~]#kubectl get podNAME                           READY   STATUS  RESTARTS   AGEmyapp-deploy-5954fc89f-jlswb   1/1     Running   0        10hmyapp-deploy-5954fc89f-t2qhm   1/1     Running   0        10hmyapp-deploy-5954fc89f-zxpbp   1/1     Running   0        10h

5、通过浏览器访问,我们的访问方式就是宿主机 IP 加上它随机暴露出来的端口。

:32043/

访问的是我们 master 节点的 ip 加上刚才暴露出来的随机端口 32043

这个就是我们 nodeport 的含义,可以将我们内部的服务暴露给外部用户访问。只需要访问到所在的 node IP 加上这里的随机端口即可并且所有的集群 node 都会开启这个端口

我们访问后端 node 节点的 ip 加上这个随机端口也是可以对 nginx 进行访问的。

6、使用 netstat 命令查看,会发现这个随机端口是由我们的 kube-proxy 开启的,并且 kubernetes 集群中每一个节点都有这个端点信息

#master 1 上查看该随机端口[09:26:56 root@master-1 ~]#netstat -anpt | grep 32043tcp6       0      0 :::32043              :::*                  LISTEN      19603/kube-proxy # node 1 节点上查看该随机端口也是由 kube-proxy 组件开启[22:57:58 root@node-1 ~]#netstat -anpt | grep 32043tcp6       0      0 :::32043              :::*                  LISTEN      13824/kube-proxy# node 2 节点上也是由 kube0-proxy 开启[22:57:58 root@node-2 ~]#netstat -anpt | grep 32043tcp6       0      0 :::32043              :::*                  LISTEN      13083/kube-proxy

查询流程:

在 iptables 上是由 KUBE-NODEPORTS 这个链去实现的

[09:41:26 root@master-1 ~]#iptables -t nat -nvL | grep KUBE-NODEPORTS

总结:通过我们的 kube-proxy 跟我们的 iptables 或者是 ipvs 的接口程序进行交互,创建出来对应的规则以后再进行负载均衡。

7.5.3 load balancer 类型演示范例:

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

也就是在这种模式下,也会暴露一个 nodeport ,并且会自动到云供应商哪去新建出来一个负载均衡调度层,客户端只需要访问到新建出来的负载调度层以后,这个负载均衡调度层会自动将流量导入到 kubernetes 节点对应的 nodeport 之上,当然这个服务需要花钱。原因是这里的负载均衡层是由供应商给我提供的叫做 LAAS,这个是收费服务,并且是根据我们使用的流量去进行收费的。

7.5.4 ExternalName 类型演示范例:

这种类型的 service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如:hub.zhangguayuan.com)。ExternalName service 是 service 的特例,他没有 selector(标签选择器),也没有定义任何的端口和 Endpoint(端点)。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。也就是我以前给大家说过的,引入外部流量至我们的服务内部

yaml 文件详解:

kind: Service # kind 类型为 serviceapiVersion: v1# api 主版本为 v1metadata: # 元数据信息name: my-service-1# svc 名称为 my-service-1namespace: default# 使用的是默认名称空间spec: # 详细信息描述type: ExternalName# SVC 类型为 externalnameexternalName: my.database.example.com # externalname 名称是 my.database.example.com,只要用户访问我们的这个服务就会被重定向到 my.database.example.com 这个域名去做了一个跳转的动作

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

1、编写svc-externalname.yaml 文件

[09:41:55 root@master-1 ~]#vim svc-externalname.yaml kind: ServiceapiVersion: v1metadata:name: my-service-1namespace: defaultspec:type: ExternalNameexternalName: my.database.example.com

2、kubectl apply创建 svc

[10:10:52 root@master-1 ~]#kubectl apply -f svc-externalname.yaml kubectl apply -f: #声明式创建 pod svc-externalname.yaml:#被创建的 yaml 文件

3、通过kubectl get svc查看就会发现它的外部链接是我们在 yaml 文件写的 my.database.example.com 当然我们可以把这个域名改成 ip 地址也是没有问题的,并且 type 类型为 externalname。

[10:11:23 root@master-1 ~]#kubectl get svcNAME             TYPE           CLUSTER-IP    EXTERNAL-IP               PORT(S)      AGEkubernetes       ClusterIP      172.30.0.1    <none>                    443/TCP      11hmy-service-1     ExternalName   <none>        my.database.example.com   <none>         47s

4、通过 dig 解析 coredns ip ,就会发现他其实是一个 CNAME 并且绑定在了 my.database.example.com 上,其实可以理解为它就是做了一个 DNS 的别名操作,externalname 的含义具体的功能就是想把外部的流量、外部的服务引入至集群内部

[10:12:10 root@master-1 ~]#dig -t -A my-service-1.default.svc.linux38.local. @10.10.0.5

7.5.5 总结:

以上就是我们所有的 SVC (服务发现)的创建,上面给大家讲了 SVC 的相关的一些原理包括它的配置方案,我们现在得知了一个问题就是对于我们传统的 SVC 来说它仅仅支持我们的 4 层代理。那如果要是遇到我们的 7 层的话就没有办法去实现,也就是如果我们遇到 7 层这么一种代理方式,那可能我们就需要一些别的手段帮我们去攻破。所以我们就得往下看。