在k8s中的Service简介
本文最后更新于:July 23, 2019 pm
简单介绍一下k8s中的Service组件。
1、Service简介
老规矩,我们先来看一下官网对service的描述:
Kubernetes
Pod
是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。 通过ReplicaSets
能够动态地创建和销毁Pod
(例如,需要进行扩缩容,或者执行 滚动升级)。 每个Pod
都会获取它自己的 IP 地址,即使这些 IP 地址不总是稳定可依赖的。 这会导致一个问题:在 Kubernetes 集群中,如果一组Pod
(称为 backend)为其它Pod
(称为 frontend)提供服务,那么那些 frontend 该如何发现,并连接到这组Pod
中的哪些 backend 呢?关于
Service
Kubernetes
Service
定义了这样一种抽象:逻辑上的一组Pod
,一种可以访问它们的策略 —— 通常称为微服务。 这一组Pod
能够被Service
访问到,通常是通过Label Selector
(查看下面了解,为什么可能需要没有 selector 的Service
)实现的。举个例子,考虑一个图片处理 backend,它运行了3个副本。这些副本是可互换的 —— frontend 不需要关心它们调用了哪个 backend 副本。 然而组成这一组 backend 程序的
Pod
实际上可能会发生变化,frontend 客户端不应该也没必要知道,而且也不需要跟踪这一组 backend 的状态。Service
定义的抽象能够解耦这种关联。对 Kubernetes 集群中的应用,Kubernetes 提供了简单的
Endpoints
API,只要Service
中的一组Pod
发生变更,应用程序就会被更新。 对非 Kubernetes 集群中的应用,Kubernetes 提供了基于 VIP 的网桥的方式访问Service
,再由Service
重定向到 backendPod
。
官网的这段话可能有些不好理解,我们用最简单的web服务器软件apache举个例子。一般来说,我们在一台电脑上安装了apache服务之后,它默认监听的是80端口(http),也就是说我们如果需要访问这个apache服务,我们只需要使用浏览器访问这台电脑的IP地址加上80端口号即可,对于其他的应用也是一样,一般都是以主机IP加上应用的监听端口来进行访问。
那么在k8s的集群中,我们都知道应用服务被打包成容器,而容器放在pod
中运行,每个pod
都会被分配到一个IP地址,当然我们还是可以按照上述的方式来访问这些pod
里面的服务,但是,一个应用往往会部署多个pod
(保证可靠性和健壮性),而不同的pod
也会因为需求的不同或者节点宕机等原因被销毁或者新建(对应又是新的IP),所以k8s定义了service
这一层抽象层,用来解决这个问题,将同一应用下的一组pod进行打包,然后通过service
来进行统一访问(主要通过label
来实现),用户只需要访问service
这一层,而不需要在意service
之后的pod
是怎么调度运行的。也就是说,service
会负责把用户的访问请求转发到pod
对应的端口上,或者是说service
会将pod
对应的端口和service
上暴露给用户访问的端口建立映射关系。
2、使用service
接下来我们来尝试着使用一下service。
首先我们创建一个pod,对应的配置文件如下:
1 |
|
需要特别注意的是这里我们使用了一个labels
,使用这个配置文件创建的pod
都会带上一个app=httpd
的labels
,这个是后面创建的service
用来区分pod
的。
这里我们使用curl直接对部署的三个pod进行访问,可以看到都是能够访问成功的。
接下来我们创建一个service,配置文件如下
1 |
|
上述配置将创建一个名称为 “http-service” 的 Service
对象,它会将请求代理到使用 TCP 端口 80,并且具有标签(labels)
"app=httpd"
的 Pod
上。 这个 Service
将被指派一个 IP 地址(通常称为 “Cluster IP”),它会被服务的代理使用(见下面)。 该 Service
的 selector 将会持续评估,处理结果将被 POST 到一个名称为 “http-service”
的 Endpoints
对象上。
需要注意的是, Service
能够将一个接收端口映射到任意的 targetPort
。 默认情况下,targetPort
将被设置为与 port
字段相同的值。
创建成功之后我们可以使用下面的这个命令来查看service
1 |
|
这里我们可以看到这个service
被分配了一个10.96.16.218
的IP地址(这个IP地址称为ClusterIP
),服务对应的端口号是7777
。我们试着使用curl来对其进行访问。
这里可以看到是可以成功访问的。
我们再来看一下详细的情况:
这里可以看到已经和上面的三个pods的80端口建立了映射关系。
3、ipvs和iptables
我们现在已经知道,service
这个抽象层是将这个service
的IP(10.96.16.218
)的7777端口和Endpoints
的pod的80端口建立映射关系来实现这个抽象的。那么这个映射关系的建立,就是由iptables
来实现的,而ipvs
在这里也能实现一样的功能,但是更加强大,我们可以将它简单看作是iptables
的增强版。
3.1 ipvs和iptables的区别
k8s中默认使用的是iptables
,但是ipvs
要更加强大,
ipvs
为大型集群提供了更好的可扩展性和性能ipvs
支持比iptables
更复杂的复制均衡算法(最小负载、最少连接、加权等等)ipvs
支持服务器健康检查和连接重试等功能
ipvs
(IP Virtual Server) 实现了传输层负载均衡,也就是我们常说的4层LAN
交换(基于TCP四层(IP+端口)的负载均衡软件),作为 Linux 内核的一部分。ipvs
运行在主机上,在真实服务器集群前充当负载均衡器。ipvs
可以将基于TCP
和UDP
的服务请求转发到真实服务器上,并使真实服务器的服务在单个 IP 地址上显示为虚拟服务。
ipvs
是在kubernetes v1.8
版本中引进的,小七这里使用的是v1.15
版本,之前配置的时候就是使用了ipvs
来替换iptables
。
3.2 查看ipvs
不管是ipvs
还是iptables
,都是需要root
权限才能查看,因此我们需要在命令前加上sudo
或者是切换到root
用户。
1 |
|
从这里的规则中我们可以看到。ipvs
将对10.96.16.218:7777
的访问转发到下面对应的三个IP地址的80端口上。
接下来我们对这个deployment进行修改,将副本数量从3改到6,再看这个ipvs规则的变化
副本数量增加到了6个,对应的ipvs
规则也完成了更新:
接下来我们删除掉这个service,可以看到ipvs中的规则并没有进行删除
这里我们可以知道,k8s对ipvs的规则可以说是只增不减。
这里留一个坑,后面再开一篇来详细研究一下
ipvs
3.3 使用DNS访问
实际上,k8s集群中内置有dns解析服务,我们可以通过<SERVICE_NAME>.<NAMESPACE_NAME>
来对service进行访问。
v1.11
版本之后,coredns
取 代kube-dns
成为k8s中默认的dns解析服务器。
我们在pod中运行一个shell来实验一下:
1 |
|
busybox
对nslookup
的支持似乎有些问题,不一定能成功查询到dns
,而且并没有内置curl
命令,我们只能用wget
来进行访问。事实上wget
能成功就说明dns
服务是正常的。
然后我们使用它的测试版本,这时候nslookup
命令正常了,wget
命令又不正常了。
1 |
|
这里面的10.96.0.10
地址正是我们的coredns
的地址:
4、外网访问service
4.1 NodePort简介
前面我们已经可以通过clusterIP来访问service了,但是显然这只能局限于在k8s集群内的主机进行,集群外的机器无法对其进行访问,这显然是不行的。我们先看一下官网给出的解决方案:
对一些应用(如 Frontend)的某些部分,可能希望通过外部(Kubernetes 集群外部)IP 地址暴露 Service。
Kubernetes
ServiceTypes
允许指定一个需要的类型的 Service,默认是ClusterIP
类型。
Type
的取值以及行为如下:
ClusterIP
:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的ServiceType
。NodePort
:通过每个 Node 上的 IP 和静态端口(NodePort
)暴露服务。NodePort
服务会路由到ClusterIP
服务,这个ClusterIP
服务会自动创建。通过请求<NodeIP>:<NodePort>
,可以从集群的外部访问一个NodePort
服务。LoadBalancer
:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort
服务和ClusterIP
服务。ExternalName
:通过返回CNAME
和它的值,可以将服务映射到externalName
字段的内容(例如,foo.bar.example.com
)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的kube-dns
才支持。
可以看到,这里我们主要将用到的就是NodePort了。
如果设置 type
的值为 "NodePort"
,Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 Service
。该端口将通过 Service
的 spec.ports[*].nodePort
字段被指定。
如果需要指定的端口号,可以配置 nodePort
的值,系统将分配这个端口,否则调用 API 将会失败(比如,需要关心端口冲突的可能性)。
这可以让开发人员自由地安装他们自己的负载均衡器,并配置 Kubernetes 不能完全支持的环境参数,或者直接暴露一个或多个 Node 的 IP 地址。
需要注意的是,Service 将能够通过 <NodeIP>:spec.ports[*].nodePort
和spec.clusterIp:spec.ports[*].port
而对外可见。
4.2 自动分配端口
接下来我们来修改一下刚刚的service配置文件,先尝试一下让master自动分配一个端口。
然后我们重新应用一下该配置文件,就能看到分配的端口:
我们查看ipvs规则,可以看到本机对应的IP都已经建立了相应的映射转发规则
正好小七用的是虚拟机,使用集群外的电脑用浏览器访问任意一个master节点均可:
4.3 手动指定端口
我们在下面的port选项中添加nodePort: 32333
,然后重新应用配置文件。
使用浏览器尝试访问: