Service
6.1 Service APIs的概要
Kubernetes的Service是为一组功能相同的Pod提供单一不变的endpoint的resource。
Service APIs:给cluster上的container提供endpoint,发现label一致的contianer。
- Service
- ClusterIP
- ExternalIP
- NodePort
- LoadBalancer
- Headless
- ExternalName
- None-Selector
- Ingress(提供L7 loadBalancing)
6.2 Kubernetes cluster的network和Service
由于包含在同一个Pod中的多个container被分配的是相同的IP address。因此,同一个Pod内部contianer之间的通信以发给localhost进行通信。而当某一个Pod的contianer向其他Pod的container进行通信的场合,以Pod的IP address为目的地址进行通信。
Kubernetes cluster的内部网络
当构建kubernetes cluster时,在node里服务Pod的内部网络则被自动创建。
基本上在每个node的构建的是不同的network segment ,而且node之间通过使用VXLAN或者L2 Routing等技术来转发traffic(流量),从而node之间能相互通信。
由于k8s自动构筑了这样的内部网络,Pod之间不需要使用Service也能够进行通信。但是,使用Service有2大优势:
- 发送到Pod的traffic的load balancling (发送到Pod流量的负载均衡)
- Service Discovery (服务发现)和cluster内DNS
6.2.1 发送到Pod的流量的负载均衡
Service提供了把收到的流量负载均衡到多个Pod的功能。例如:由于一个Deployment启动了多个Pod,而每个Pod都被赋予了不同的IP address,因此要实现load balancing的功能就需要每次都查找各个Pod的IP address并设定为转发目的地。
因此,Service提供了作为负载均衡连接口的endpoint。endpoint提供了,供外部load balancer使用的virtual ip address(虚拟ip address)和只能在cluster内部使用的virtual IP address。
sample-deployment.yaml
1 | apiVersion: v1 |
由该Deployment创建的Pod会被自动赋予app: sample-app
的label。
使用特点的jsonpath输出label
1 | # kubectl get pod sample-deployment-dcc7d5c94-m9f7x -o jsonpath='{.metadata.labels}' |
ClusterIP (集群内IP)
Cluster IP是一个虚拟的IP,只能在Cluster内部使用。
Cluster构成了load balancer。该LB提供的endpoint是只能在Cluster内部使用虚拟IP。
Service是根据spec.selector
所定义的Selector的条件来进行流量转发。
例如:下面的ClusterIP是把流量负载均衡到拥有app:sample
标签的Pod上。
sample-clusterip.yaml
1 | apiVersion: v1 |
首先,来确认label为app:sample-app
的Pod的IP address。
1 | # kubectl get pods -l app=sample-app -o custom-columns="NAME:{metadata.name},IP:{status.podIP}" |
接下来,创建Service,并确认Service的详细情报。可以在Endpoints一栏中看到流量转发目的地的IP address和Port是和拥有app: sample-app
标签的Pod的IP address是相同的。而CLUSTER-IP或者IP栏的IP address则是为了实现负载均衡的endpoint的虚拟IP。
1 | # kubectl apply -f sample-clusterip.yaml |
最后来确认,创建的load balancer是否把流量负载均衡到了各个Pod上。
由于本例使用的Service type是ClusterIP,因此endpoint的虚拟IP address只能在cluster内部来访问。所以需要通过在cluster内部再创建一个container来向该endpoint发送HTTP request来进行确认。
创建一个临时的Pod
1 | # kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.100.40.52:8080 |
在执行多次之后通过返回的Pod名来看到对每个Pod都进行了request。
分配多个Port
一个Service可以分配多个Port。例如:对ClusterIP的8080/TCP Port发送的request,负载均衡到Pod的80/TCP Port;而对ClusterIP的8443/TCP Port发送的request,则负载均衡到Pod的443/TCP Port。(由于使用的Deployment的Pod没有监听443Port,因此https-port
不会疏通)
sample-clusterip-multi.yaml
1 | apiVersion: v1 |
通过port的name来设置targetPort
在定义Pod的port时通过给port命名,可以使用port的name来进行Service的targePort的指定。
sample-named-port-pods.yaml
1 |
|
sample-named-port-service.yaml
1 | apiVersion: v1 |
在上面的例子中,由于port80和port81的name都为http,因此在Service的Endpoints的目的IP地址中也都分别登陆了这两个port。
1 | # kubectl describe svc sample-named-port-service |
可以看到ClusterIP的endpoint分别登陆了这两个Pod的ip address:port
6.2.2 cluster内部DNS和service discovery(发现服务)
kube-system namespace下的名为kube-dns
的Pod运行DNS服务,在cluster的其他Pod都被配置成使用其作为dns(k8s通过修改每个container的/etc/resolv.conf
实现)
service discovery: 列举特定条件的对象的member(成员),并判别从名称到endpoint的功能。在k8s层面上来讲就是,列举属于Service的Pod,并返回从Service的名称到endpoint的信息。
service发现是k8s中的一个重要机制,其基本功能为:在cluster内通过service名对service进行访问,即需要完成从service名到ClusterIP的解析。
Service 发现的方法主要有3种:
- 使用环境变量
- 使用DNS A record
- 使用DNS SRV record
在使用DNS的service discovery中,使用的是自动登陆到cluster内部DNS sever的service endpoint。
使用环境变量的service discovery
从Pod内部可以通过环境变量来确认在同一namespace下的service信息。
需要注意的是,当Pod创建之后,伴随创建或者删除的Service而产生变更的环境变量并不会自动登陆到之前已经启动的Pod的环境中。因此,需要重新创建Pod。
使用DNS A record的service discovery
从其他的Pod连接到Service除了使用IP Address还可以使用自动登录的DNS record。由于每当创建Service时,分配给Service的IP Address会产生变化。因此,如果使用DNS的话,就无需在意由于Service 。
对http://sample-clusterip:8080
发送request,
1 | # kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080 |
实际上,登录的正式FQDN是,[Service名称].[Namespace名称].svc.cluster.local
1 | # kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-clusterip.default.svc.cluster.local |
在同一个Namespace下,根据Service名称
来区分;不同的Namespace的情况下,则通过Namespace名来进行区分。
使用 DNS SRV record
6.3 ClusterIP Service
当创建type:ClusterIP
的Service时,会被分配由Internal Network所创建的只能从k8s cluster内部使用的虚拟IP address,因此被称作ClusterIP。而发送到ClusterIP的通信,则会通过运行在各个node上的system component中的kube-proxy来向Pod进行转发。
6.3.1 ClusterIP Service的创建
sample-clusterip.yaml
1 | apiVersion: v1 |
创建方法:指定
type:ClusterIP
。sepc.ports[].port
是ClusterIP用来接受信息的port。spec.ports[].port
是ClusterIP把信息发送目的地Pod中的container的port。
接下来还是使用sample-deployment.yaml
创建Deployment后,并发起向ClusterIP发起HTTP request,会发现发送到ClusterIP的traffic会被转送到各个Pod。
1 | # kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080 |
6.3.2指定ClusterIP的虚拟IP为静态IP
通过指定spec.ClusterIP
字段来手动指定ClusterIP的IP address。
sample-clusterip-vip.yaml
1 | apiVersion: v1 |
对于已经创建的Service来说,之后不能变更ClusterIP。虽然使用kubectl apply
可以更新设定值,但是包括ClusterIP
的值等一部分字段是不能变更的(Immutable)。当想要指定ClusterIP的时候,首先需要删除之后再创建。
6.4 ExternalIP Service
ExternalIP Service是把从特定的k8s Node的IPaddress:Port
接受的信息转发到container的确立外部连通性的Service。(不过,目前更推荐使用NodePort)
sample-externalip.yaml
1 | apiVersion: v1 |
ExternalIP并不需要指定type: ExternalIP
。ExternalIP能够使用的IP address是node的IP address
可以使用以下命令获得node的IP address
1 | # kubectl get nodes -o custom-columns="NAME:{metadata.name},IP:{status.addresses[].address}" |
当创建好ExternalIP Service后,可以使用curl
验证外部连通性。
1 | # curl 61.210.160.120:8080 |
6.5 NodePort Service
NodePort与ExternalIP类似。不同的是,ExternalIP是通过指定的Node的IP address:Port
来接受信息,并转发到container。而NodePort则是接受所有的Node的IP address:Port
的信息并转发到container。严格的来说,NodePort是通过监听0.0.0.0:Port
来Bind所有的IP address。
sample-nodeport.yaml
1 | apiVersion: v1 |
spec.ports[].port
是ClusterIP用来接收信息的Portspec.ports[].targetPort
是转发目的地container的Portspec.ports[].nodePort
是所有k8s Node用来接收信息的Port
由于是在所有k8s Node的IP address上进行监听,spec.ports[].nodePort
所指定的Port要避免冲突。如果
临时创建Pod,确认NodePort Service的名字是否解决
1 | # kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-nodeport.default.svc.cluster.local |
用curl
进行外部连通性确认,多次执行后确认三个Pod的出现频率相同。
1 | # curl -s 61.210.163.59:30080 |
6.5.2 NodePort的注意事项
NodePort的Port的范围应该设定在30000~32767
之间,如果不在这个范围之内,则会报错。
当指定nodePort:8888
时,报错的例子
sample-nodeport-fail.yaml
1 | apiVersion: v1 |
创建产生error
1 | # kubectl apply -f sample-nodeport-fail.yaml |
The range of valid ports is 30000-32767
多个NodePort不能同时使用一个Port
sample-nodeport-fail2.yaml
1 | apiVersion: v1 |
由于之前已经创建了port为30080的nodePort,因此报错
1 | # kubectl apply -f sample-nodeport-fail2.yaml |
排除服务故障
如果无法通过Service访问Pod,应该根据下面的列表进行排查:
- 首先,确保从cluster内连接到Service的ClusterIP,而不是外部。
- 不要通过ping Service的IP address来判断Service是否可以访问。(Serivce的ClusterIP是虚拟IP,是无法ping通的)
- 如果已经定义了就绪探针,请确保它返回成功;否则该pod不会成为Service的一部分。
- 要确认某个container是Service的一部分,请使用
kubectl get endpoints
来检查相应的端点对象。 - 如果尝试通过FQDN或其中一部分来访问Service。(例如,
myservice.mynamespace.svc.cluster.local
或myservice.mynamespace
),但并不起作用,请查看是否可以使用其ClusterIP而不是FQDN来访问Service。 - 检查是否连接到Service公开的port,而不是targetPort(目标container的port)
- 尝试之间连接到pod IP以确认pod正在接受正确端口上的连接。
- 如果使用pod的IP address都无法访问应用,请确保应用不是尽绑定到localhost(本地主机)上