资讯专栏INFORMATION COLUMN

Kubernetes Service详解

baukh789 / 585人阅读

摘要:后端代理之前的文章部署最后的测试部分,创建了一组及服务来验证业务,继续以这个例子来说明集群中已经有如下一组都带有标签,对外暴露端口,访问路径会返回主机名。请求代理转发是一个虚拟的地址,并不是某张网卡的真实地址。

为什么需要service

Kubernetes可以方便的为容器应用提供了一个持续运行且方便扩展的环境,但是,应用最终是要被用户或其他应用访问、调用的。要访问应用pod,就会有以下两个问题:

pod是有生命周期的。它会根据集群的期望状态不断的在创建、删除、更新,所以pod的ip也在不断变化,如何访问到不断变化的pod?

通常一个应用不会单只有一个pod,而是由多个相同功能的pod共同提供服务的。那么对这个应用的访问,如何在多个pod中负载均衡?

service主要就是用来解决这两个问题的。简单来说,它是一个抽象的api对象,用来表示一组提供相同服务的pod及对这组pod的访问方式。

service的实现

service作为一个类似中介的角色,对内,它要能代理访问到不断变换的一组后端Pod;对外,它要能暴露自己给集群内部或外部的其他资源访问。我们分别来看下具体是怎么实现的。

后端代理

之前的文章kubeadm部署最后的测试部分,创建了一组pod及服务来验证业务,继续以这个例子来说明:

集群中已经有如下一组pod:

NAME                     READY   STATUS       IP            NODE     APP
goweb-55c487ccd7-5t2l2   1/1     Running     10.244.1.15   node-1   goweb
goweb-55c487ccd7-cp6l8   1/1     Running     10.244.3.9    node-2   goweb
goweb-55c487ccd7-gcs5x   1/1     Running     10.244.1.17   node-1   goweb
goweb-55c487ccd7-pp6t6   1/1     Running     10.244.3.10   node-2   goweb

pod都带有app:goweb标签,对外暴露8000端口,访问/info路径会返回主机名。

创建service

创建一个servcie有两种方式

命令式

$ kubectl expose deployment goweb --name=gowebsvc --port=80  --target-port=8000  

声明式

# 定义服务配置文件
# svc-goweb.yaml
apiVersion: v1
kind: Service
metadata:
  name: gowebsvc
spec:
  selector:
    app: goweb
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 8000
  type: ClusterIP
# 创建服务
$ kubectl apply -f svc-goweb.yaml

我们来看下配置文件中几个重点字段:

selector指定了app: goweb标签。说明该svc代理所有包含有"app: goweb"的pod

port字段指定了该svc暴露80端口

targetPort指定改svc代理对应pod的8000端口

type定义了svc的类型为ClusterIP,这也是svc的默认类型

通过apply创建服务后,来查看一下服务状态

$ kubectl  get svc gowebsvc  -o wide
NAME       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
gowebsvc   ClusterIP   10.106.202.0           80/TCP    3d   app=goweb

可以看到,Kubernetes自动为服务分配了一个CLUSTER-IP。通过这个访问这个IP的80端口,就可以访问到"app: goweb"这组pod的8000端口,并且可以在这组pod中负载均衡。

[root@master-1 ~]# curl http://10.106.202.0/info
Hostname: goweb-55c487ccd7-gcs5x
[root@master-1 ~]# curl http://10.106.202.0/info
Hostname: goweb-55c487ccd7-cp6l8
[root@master-1 ~]# curl http://10.106.202.0/info
Hostname: goweb-55c487ccd7-pp6t6
请求代理转发

cluster-ip是一个虚拟的ip地址,并不是某张网卡的真实地址。那具体的请求代理转发过程是怎么实现的呢? 答案是iptables。我们来看下iptables中与cluster-ip相关的规则

[root@master-1 ~]# iptables-save | grep 10.106.202.0
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.106.202.0/32 -p tcp -m comment --comment "default/gowebsvc:default cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.106.202.0/32 -p tcp -m comment --comment "default/gowebsvc:default cluster IP" -m tcp --dport 80 -j KUBE-SVC-SEG6BTF25PWEPDFT

可以看到,目的地址为CLUSTER-IP、目的端口为80的数据包,会被转发到KUBE-MARK-MASQ与KUBE-SVC-SEG6BTF25PWEPDFT链上。其中,KUBE-MARK-MASQ链的作用是给数据包打上特定的标记(待验证),重点来看下KUBE-SVC-SEG6BTF25PWEPDFT链:

-A KUBE-SVC-SEG6BTF25PWEPDFT -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-5ZXTVLEM4DKNW7T2
-A KUBE-SVC-SEG6BTF25PWEPDFT -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-EBFXI7VOCPDT2QU5
-A KUBE-SVC-SEG6BTF25PWEPDFT -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-C3PKSXKMO2M43SPF
-A KUBE-SVC-SEG6BTF25PWEPDFT -j KUBE-SEP-2GQCCNJGO65Z5MFS

可以看到,KUBE-SVC-SEG6BTF25PWEPDFT链通过设置--probability,将请求等概率转发到4条链上,查看其中一条转发链:

[root@master-1 ~]# iptables-save | grep  "A KUBE-SEP-5ZXTVLEM4DKNW7T2" 
-A KUBE-SEP-5ZXTVLEM4DKNW7T2 -s 10.244.1.15/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-5ZXTVLEM4DKNW7T2 -p tcp -m tcp -j DNAT --to-destination 10.244.1.15:8000

发现KUBE-SEP-5ZXTVLEM4DKNW7T2这条规则对请求的目的地址作了DNAT到10.244.1.15:8000,这正是goweb组中goweb-55c487ccd7-5t2l2这个pod的ip地址。这样,对svc的CLUSTER-IP的请求,就会通过iptables规则转发到相应的pod。

但是,还有个问题,svc是怎么跟踪pod的ip变化的?
注意到前面的nat规则,第一次转发的链名称是KUBE-SVC-xxx,第二次转发给具体pod的链名称是KUBE-SEP-xxx,这里的SEP实际指的是kubernetes另一个对象endpoint,我们可以通过vkubectl get ep命令来查看:

[root@master-1 ~]# kubectl  get ep gowebsvc
NAME         ENDPOINTS 
gowebsvc     10.244.1.15:8000,10.244.1.17:8000,10.244.3.10:8000 + 1 more...   35d

在svc创建的时候,kube-proxy组件会自动创建同名的endpoint对象,动态地跟踪匹配selector的一组pod当前ip及端口,并生成相应的iptables KUBE-SVC-xxx规则。

请求代理的三种方式

上面说的请求代理转发的方式,是kubernetes目前版本的默认方式,实际上,service的代理方式一共有三种:

Userspace 模式

在这种模式下,kube-proxy为每个服务都打开一个随机的端口,所有访问这个端口的请求都会被转发到服务对应endpoints指定的后端。最后,kube-proxy还会生成一条iptables规则,把访问cluster-ip的请求重定向到上面说的随机端口,最终转发到后端pod。整个过程如下图所示:

Userspace模式的代理转发主要依靠kube-proxy实现,工作在用户态。所以,转发效率不高。较为不推荐用该种模式。

iptables 模式

iptables模式是目前版本的默认服务代理转发模式,上两小节做过详细说明的就是这种模式,来看下请求转发的示意图:

与userspace模式最大的不同点在于,kube-proxy只动态地维护iptables,而转发完全靠iptables实现。由于iptables工作在内核态,不用在用户态与内核态切换,所以相比userspace模式更高效也更可靠。但是每个服务都会生成若干条iptables规则,大型集群iptables规则数会非常多,造成性能下降也不易排查问题。

ipvs 模式

在v1.9版本以后,服务新增了ipvs转发方式。kube-proxy同样只动态跟踪后端endpoints的情况,然后调用netlink接口来生成ipvs规则。通过ipvs来转发请求:

ipvs同样工作在内核态,而且底层转发是依靠hash表实现,所以性能比iptables还要好的多,同步新规则也比iptables快。同时,负载均衡的方式除了简单rr还有多种选择,所以很适合在大型集群使用。而缺点就是带来了额外的配置维护操作。

集群内部服务发现

在集群内部对一个服务的访问,主要有2种方式,环境变量与DNS。

环境变量方式

当一个pod创建时,集群中属于同个namespace下的所有service对象信息都会被作为环境变量添加到pod中。随便找一个pod查看一下:

$ kubectl exec goweb-55c487ccd7-5t2l2 "env" | grep GOWEBSVC
GOWEBSVC_PORT_80_TCP_ADDR=10.106.202.0
GOWEBSVC_SERVICE_PORT=80
GOWEBSVC_SERVICE_PORT_DEFAULT=80
GOWEBSVC_PORT_80_TCP=tcp://10.106.202.0:80
GOWEBSVC_PORT_80_TCP_PROTO=tcp
GOWEBSVC_PORT_80_TCP_PORT=80
GOWEBSVC_PORT=tcp://10.106.202.0:80
GOWEBSVC_SERVICE_HOST=10.106.202.0

可以看到,pod通过{SVCNAME}_SERVICE_HOST/PORT就可以方便的访问到某个服务。这种访问方式简单易用,可以用来快速测试服务。但最大的问题就是,服务必须先于pod创建,后创建的服务是不会添加到现有pod的环境变量中的。

DNS方式

DNS组件是k8s集群的可选组件,它会不停监控k8s API,在有新服务创建时,自动创建相应的DNS记录。。以gowebsvc为例,在服务创建时,会创建一条gowebsvc.default.svc.cluster.local的dns记录指向服务。而且dns记录作用域是整个集群,不局限在namespace。
虽然是可选组件,但DNS生产环境可以说是必备的组件了。这里先简单说明,后面打算专门开篇文章来详细介绍。

集群外部的服务暴露

服务发现解决了集群内部访问pod问题,但很多时候,pod提供的服务也是要对集群外部来暴露访问的,最典型的就是web服务。k8s中的service有多种对外暴露的方式,可以在部署Service时通过ServiceType字段来指定。默认情况下,ServiceType配置是只能内部访问的ClusterIP方式,前面的例子都是这种模式,除此之外,还可以配置成下面三种方式:

NodePort方式:

该方式把服务暴露在每个Node主机IP的特定端口上,同一个服务在所有Node上端口是相同的,并自动生成相应的路由转发到ClusterIP。这样,集群外部通过:就可以访问到对应的服务。举个例子:

## 创建svc,通过Nodeport方式暴露服务
$ kubectl expose deployment goweb --name=gowebsvc-nodeport --port=80  --target-port=8000  --type=NodePort 
## 查看svc,可以看到NodePort随机分配的端口为32538
$ kubectl get svc gowebsvc-nodeport 
NAME                TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
gowebsvc-nodeport   NodePort   10.101.166.252           80:32538/TCP   86s
## 随便访问一个nodeip的32538端口,都可以访问到gowebsvc-nodeport服务对应的pod
$ curl 172.16.201.108:32538/info
Hostname: goweb-55c487ccd7-pp6t6
$ curl 172.16.201.109:32538/info
Hostname: goweb-55c487ccd7-5t2l2
LoadBalance:

LoadBalance方式主要是给公有云服务使用的,通过配置LoadBalance,可以触发公有云创建负载均衡器,并把node节点作为负载的后端节点。每个公有云的配置方式不同,具体可以参考各公有云的相关文档。

ExternalName:

当ServiceType被配置为这种方式时,该服务的目的就不是为了外部访问了,而是为了方便集群内部访问外部资源。举个例子,假如目前集群的pod要访问一组DB资源,而DB是部署在集群外部的物理机,还没有容器化,可以配置这么一个服务:

apiVersion: v1
kind: Service
metadata:
  name: dbserver
  namespace: default
spec:
  type: ExternalName
  externalName: database.abc.com

这样,集群内部的pod通过dbserver.default.svc.cluster.local这个域名访问这个服务时,请求会被cname到database.abc.com来。过后,假如db容器化了,不需要修改业务代码,直接修改service,加上相应selector就可以了。

几种特殊的service

除了上面这些通常的service配置,还有几种特殊情况:

Multi-Port Services

service可以配置不止一个端口,比如官方文档的例子:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  - name: https
    protocol: TCP
    port: 443
    targetPort: 9377

这个service保留了80与443端口,分别对应pod的9376与9377端口。这里需要注意的是,pod的每个端口一定指定name字段(默认是default)。

Headless services

Headless services是指一个服务没有配置了clusterIP=None的服务。这种情况下,kube-proxy不会为这个服务做负载均衡的工作,而是交予DNS完成。具体又分为2种情况:

有配置selector: 这时候,endpoint控制器会为服务生成对应pod的endpoint对象。service对应的DNS返回的是endpoint对应后端的集合。

没有配置selector:这时候,endpoint控制器不会自动为服务生成对应pod的endpoint对象。若服务有配置了externalname,则生成一套cnmae记录,指向externalname。如果没有配置,就需要手动创建一个同名的endpoint对象。dns服务会创建一条A记录指向endpoint对应后端。

External IPs

如果有个非node本地的IP地址,可以通过比如外部负载均衡的vip等方式被路由到任意一台node节点,那就可以通过配置service的externalIPs字段,通过这个IP地址访问到服务。集群以这个IP为目的IP的请求时,会把请求转发到对应服务。参考官方文档的例子:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  externalIPs:
  - 80.11.12.10

这里的80.11.12.10就是一个不由kubernetes维护的公网IP地址,通过80.11.12.10:80就可以访问到服务对应的pod。

简单总结下,service对象实际上解决的就是一个分布式系统的服务发现问题,把相同功能的pod做成一个服务集也能很好的对应微服务的架构。在目前的kubernetes版本中,service还只能实现4层的代理转发,并且要搭配好DNS服务才能真正满足生产环境的需求。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/32978.html

相关文章

  • 【容器云 UK8S】服务发现之ULB 参数说明:用于创建LoadBalancer类型的Service

    摘要:参数说明本文主要描述用于创建类型的时,与相关的说明。为时表示连接保持的时间,单位为秒,取值范围,,表示禁用连接保持,默认为。会话保持方式枚举值为关闭,自动生成,用户自定义,默认为。健康检查方式为时有效,指检查路径。ULB 参数说明本文主要描述用于创建LoadBalancer 类型的Service时,与ULB相关的Annotations说明。备注:目前除了外网 ULB 绑定的 EIP 的带宽值...

    Tecode 评论0 收藏0
  • Kubernetes集群监控详解

    摘要:仪表板是一个附加组件,它能提供集群上运行的资源的概述信息。可以很容易地创建图形,并且把它们合并称仪表板,而这些仪表板由一个强大的身份验证和授权层保护,它们还可以和其他仪表板进行共享而不需要访问服务器本身。 介 绍 Kubernetes在Github上拥有超过4万颗星,7万以上的commits,以及像Google这样的主要贡献者。Kubernetes可以说已经快速地接管了容器生态系统,成...

    A Loity 评论0 收藏0
  • kubernetes安装heapster、influxdb及grafana

    摘要:下载在这里下载修改替换镜像修改添加,同时把由改为。因为的跟中的的冲突了。修改新增的暴露出来,同时添加创建配置修改下数据源的查看数据总结部署详解监控 下载yaml 在这里下载deploy/kube-config/influxdb 修改yaml 替换镜像 gcr.io/google_containers/heapster-grafana:v4.0.2 registry.cn-hangzho...

    waterc 评论0 收藏0
  • Kubernetes之Pod生命周期详解

    摘要:下面通过该文章来简述的基础信息并详述的生命周期。声明周期钩子函数为容器提供了两种生命周期钩子于容器创建完成之后立即运行的钩子程序。向容器指定发起请求,响应码为或者是为成功,否则失败。 简述 Kubernetes 是一种用于在一组主机上运行和协同容器化应用程序的系统,提供应用部署、规划、更新维护的机制。应用运行在 kubernetes 集群之上,实现服务的扩容、缩容,执行滚动更新以及在不...

    高胜山 评论0 收藏0

发表评论

0条评论

baukh789

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<