K8S Service
Pod的IP地址是在Pod启动后才被分配,在启动前并不知道Pod的IP地址。 应用往往都是由多个运行相同镜像的一组Pod组成,逐个访问Pod也变得不现实。Kubernetes中的Service对象就是用来解决上述Pod访问问题的。Service有一个固定IP地址,Service将访问它的流量转发给Pod,具体转发给哪些Pod通过Label来选择,而且Service可以给这些Pod做负载均衡。
创建 Deployment
创建后台Pod首先创建一个3副本的Deployment,即3个Pod,且Pod上带有标签“app: nginx”,具体如下所示。
1 | apiVersion: apps/v1 |
创建 Service
创建一个名为“nginx”的Service,通过selector选择到标签“app:nginx”的Pod,目标Pod的端口为80,Service对外暴露的端口为8080。
访问服务只需要通过“服务名称:对外暴露的端口”接口,对应本例即“nginx:8080”。这样,在其他Pod中,只需要通过“nginx:8080”就可以访问到“nginx”关联的Pod。
1 | apiVersion: v1 |
将上面Service的定义保存到nginx-svc.yaml文件中,使用kubectl创建这个Service。
1 | $ kubectl create -f nginx-svc.yaml |
可以看到Service有个Cluster IP,这个IP是固定不变的,除非Service被删除,所以您也可以使用ClusterIP在集群内部访问Service。
下面创建一个Pod并进入容器,使用ClusterIP访问Pod,可以看到能直接返回内容。
1 | $ kubectl run -i --tty --image nginx:alpine test --rm /bin/sh |
使用ServiceName访问Service
通过DNS进行域名解析后,可以使用“ServiceName:Port”访问Service,这也是Kubernetes中最常用的一种使用方式。向K8s 内部 DNS查询Service的名称获得Service的IP地址。
访问时通过 ServiceName.namespace.svc.cluster.local
访问,其中nginx为 Service的名称,namespace 为命名空间名称,svc.cluster.local为域名后缀,在实际使用中,在同一个命名空间下可以省略 namespace.svc.cluster.local,直接使用ServiceName即可。
例如上面创建的名为nginx的Service,直接通过“nginx:8080”就可以访问到Service,进而访问后台Pod。
使用ServiceName的方式有个主要的优点就是可以在开发应用程序时可以将ServiceName写在程序中,这样无需感知具体Service的IP地址。
Service是如何做到服务发现的
前面说到有了Service后,无论Pod如何变化,Service都能够发现到Pod。
如果调用kubectl describe命令查看Service的信息,您会看下如下信息。
1 | $ kubectl describe svc nginx |
可以看到一个Endpoints,Endpoints同样也是Kubernetes的一种资源对象,可以查询得到。Kubernetes正是通过Endpoints监控到Pod的IP,从而让Service能够发现Pod。
1 | $ kubectl get endpoints |
实际上Service相关的事情都由节点上的kube-proxy处理。在Service创建时Kubernetes会分配IP给Service,同时通过API Server通知所有kube-proxy有新Service创建了,kube-proxy收到通知后通过iptables记录Service和IP/端口对的关系,从而让Service在节点上可以被查询到。
Service的类型与使用场景
Service的类型除了ClusterIP还有NodePort、LoadBalancer和None,这几种类型的Service有着不同的用途。
- ClusterIP:用于在集群内部互相访问的场景,通过ClusterIP访问Service。
- NodePort:用于从集群外部访问的场景,通过节点上的端口访问Service,详细介绍请参见NodePort类型的Service。
- LoadBalancer:用于从集群外部访问的场景,其实是NodePort的扩展,通过一个特定的LoadBalancer访问Service,这个LoadBalancer将请求转发到节点的NodePort
- None:用于Pod间的互相发现,这种类型的Service又叫Headless Service
NodePort类型的Service
NodePort类型的Service可以让Kubemetes集群每个节点上保留一个相同的端口, 外部访问连接首先访问节点IP:Port,然后将这些连接转发给服务对应的Pod。如下图所示。
1 | apiVersion: v1 |
创建并查看,可以看到PORT这一列为8080:30120/TCP,说明Service的8080端口是映射到节点的30120端口。
1 | $ kubectl create -f nodeport.yaml |
LoadBalancer类型的Service
LoadBalancer类型的Service其实是NodePort类型Service的扩展,通过一个特定的LoadBalancer访问Service,这个LoadBalancer将请求转发到节点的NodePort。
1 | apiVersion: v1 |
Headless Service
Service解决了Pod的内外部访问问题,但还有下面这些问题没解决。
- 同时访问所有Pod
- 一个Service内部的Pod互相访问
Headless Service正是解决这个问题的,Headless Service不会创建ClusterIP,并且查询会返回所有Pod的DNS记录,这样就可查询到所有Pod的IP地址。
StatefulSet中StatefulSet正是使用Headless Service解决Pod间互相访问的问题。
1 | apiVersion: v1 |
执行如下命令创建Headless Service。
1 | # kubectl create -f headless.yaml |
创建一个Pod来查询DNS,可以看到能返回所有Pod的记录,这就解决了访问所有Pod的问题了。
1 | $ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=Never --rm /bin/sh |