0%

Service

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Deployment
metadata:
name: sample-deployment
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx

由该Deployment创建的Pod会被自动赋予app: sample-app的label。

使用特点的jsonpath输出label

1
2
# kubectl get pod sample-deployment-dcc7d5c94-m9f7x -o jsonpath='{.metadata.labels}'
{"app":"sample-app","pod-template-hash":"dcc7d5c94"}

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
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: sample-clusterip
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080 #ClusterIP的endpoint的port
targetPort: 80 #Pod的port
selector:
app: sample-app

首先,来确认label为app:sample-app的Pod的IP address。

1
2
3
4
5
6
# kubectl get pods -l app=sample-app -o custom-columns="NAME:{metadata.name},IP:{status.podIP}"
NAME IP
sample-deployment-dcc7d5c94-5dcfx 10.244.1.18
sample-deployment-dcc7d5c94-88zxp 10.244.2.25
sample-deployment-dcc7d5c94-m9f7x 10.244.1.17

接下来,创建Service,并确认Service的详细情报。可以在Endpoints一栏中看到流量转发目的地的IP address和Port是和拥有app: sample-app标签的Pod的IP address是相同的。而CLUSTER-IP或者IP栏的IP address则是为了实现负载均衡的endpoint的虚拟IP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# kubectl apply -f sample-clusterip.yaml 
service/sample-clusterip created
# 确认Service的情报
# kubectl get service sample-clusterip
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
sample-clusterip ClusterIP 10.100.40.52 <none> 8080/TCP 7m28s
# kubectl describe service sample-clusterip
Name: sample-clusterip
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=sample-app
Type: ClusterIP
IP Families: <none>
IP: 10.100.40.52
IPs: 10.100.40.52
Port: http-port 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.17:80,10.244.1.18:80,10.244.2.25:80
Session Affinity: None
Events: <none>

最后来确认,创建的load balancer是否把流量负载均衡到了各个Pod上。

由于本例使用的Service type是ClusterIP,因此endpoint的虚拟IP address只能在cluster内部来访问。所以需要通过在cluster内部再创建一个container来向该endpoint发送HTTP request来进行确认。

创建一个临时的Pod

1
2
3
4
5
6
7
8
9
10
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.100.40.52:8080
Host=10.100.40.52 Path=/ From=sample-deployment-55b544cd4-g7sfx ClientIP=10.244.2.28 XFF=
pod "testpod" deleted
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.100.40.52:8080
Host=10.100.40.52 Path=/ From=sample-deployment-55b544cd4-g7sfx ClientIP=10.244.2.29 XFF=
pod "testpod" deleted
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.100.40.52:8080
Host=10.100.40.52 Path=/ From=sample-deployment-55b544cd4-lg6wk ClientIP=10.244.2.30 XFF=
pod "testpod" deleted

在执行多次之后通过返回的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Service
metadata:
name: sample-clusterip-multi
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
- name: "https-port"
protocol: "TCP"
port: 8443
targetPort: 443
selector:
app: sample-app

通过port的name来设置targetPort

在定义Pod的port时通过给port命名,可以使用port的name来进行Service的targePort的指定。

sample-named-port-pods.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
---
apiVersion: v1
kind: Pod
metadata:
name: sample-named-port-pod-80
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
ports:
- name: http #给port命名
containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
name: sample-named-port-pod-81
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: amsy810/echo-nginx:v2.0
env:
- name: NGINX_PORT
value: "81"
ports:
- name: http #给port命名
containerPort: 81

sample-named-port-service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: sample-named-port-service
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: http
selector:
app: sample-app

在上面的例子中,由于port80和port81的name都为http,因此在Service的Endpoints的目的IP地址中也都分别登陆了这两个port。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# kubectl describe svc sample-named-port-service
Name: sample-named-port-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=sample-app
Type: ClusterIP
IP Families: <none>
IP: 10.110.38.208
IPs: 10.110.38.208
Port: http-port 8080/TCP
TargetPort: http/TCP
Endpoints: 10.244.2.39:80,10.244.1.21:81
Session Affinity: None
Events: <none>

# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
sample-named-port-pod-80 1/1 Running 0 6m14s 10.244.2.39 node02 <none> <none>
sample-named-port-pod-81 1/1 Running 0 3m8s 10.244.1.21 node01 <none> <none>

可以看到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
2
3
4
5
6
7
8
9
10
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-55b544cd4-vjbj2 ClientIP=10.244.2.46 XFF=
pod "testpod" deleted
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-55b544cd4-tpntb ClientIP=10.244.2.47 XFF=
pod "testpod" deleted
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-55b544cd4-kmqkk ClientIP=10.244.2.48 XFF=
pod "testpod" deleted

实际上,登录的正式FQDN是,[Service名称].[Namespace名称].svc.cluster.local

1
2
3
4
5
6
7
8
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-clusterip.default.svc.cluster.local
...(省略)...
;; QUESTION SECTION:
;sample-clusterip.default.svc.cluster.local. IN A

;; ANSWER SECTION:
sample-clusterip.default.svc.cluster.local. 30 IN A 10.96.59.224
...(省略)...

在同一个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
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: sample-clusterip
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
selector: sample-app
  • 创建方法:指定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
2
3
4
5
6
7
8
9
10
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-55b544cd4-tpntb ClientIP=10.244.2.41 XFF=
pod "testpod" deleted
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-55b544cd4-vjbj2 ClientIP=10.244.2.42 XFF=
pod "testpod" deleted
#多次执行之后....
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-55b544cd4-kmqkk ClientIP=10.244.1.26 XFF=
pod "testpod" deleted

6.3.2指定ClusterIP的虚拟IP为静态IP

通过指定spec.ClusterIP字段来手动指定ClusterIP的IP address。

sample-clusterip-vip.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: sample-clusterip-vip
sepc:
type: ClusterIP
clusterIP: 10.11.253.81
ports:
- name: "http-port"
protocol: "TCP"
prot: 8080
targetPort: 80
selector:
app: sample-app

对于已经创建的Service来说,之后不能变更ClusterIP。虽然使用kubectl apply可以更新设定值,但是包括ClusterIP的值等一部分字段是不能变更的(Immutable)。当想要指定ClusterIP的时候,首先需要删除之后再创建。

6.4 ExternalIP Service

ExternalIP Service是把从特定的k8s Node的IPaddress:Port接受的信息转发到container的确立外部连通性的Service。(不过,目前更推荐使用NodePort

sample-externalip.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
name: sample-externalip
spec:
type: ClusterIP
externalIPs:
- 61.210.160.120 # node的IP address
- 61.210.163.59
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
selector:
app: sample-app

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
2
# curl 61.210.160.120:8080
Host=61.210.160.120 Path=/ From=sample-deployment-55b544cd4-tpntb ClientIP=10.244.1.0 XFF=

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
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadta:
name: sample-nodeport
sepc:
type: NodePort
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
nodePort: 30080
selector:
app: sample-app
  • spec.ports[].port是ClusterIP用来接收信息的Port
  • spec.ports[].targetPort是转发目的地container的Port
  • spec.ports[].nodePort是所有k8s Node用来接收信息的Port

由于是在所有k8s Node的IP address上进行监听,spec.ports[].nodePort所指定的Port要避免冲突。如果

临时创建Pod,确认NodePort Service的名字是否解决

1
2
3
4
5
6
7
8
9
10
# kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-nodeport.default.svc.cluster.local
...(省略)...

;; QUESTION SECTION:
;sample-nodeport.default.svc.cluster.local. IN A

;; ANSWER SECTION:
sample-nodeport.default.svc.cluster.local. 30 IN A 10.97.127.165

...(省略)...

curl进行外部连通性确认,多次执行后确认三个Pod的出现频率相同。

1
2
3
4
5
6
# curl -s 61.210.163.59:30080
Host=61.210.163.59 Path=/ From=sample-deployment-55b544cd4-tpntb ClientIP=10.244.2.1 XFF=
# curl -s 61.210.163.59:30080
Host=61.210.163.59 Path=/ From=sample-deployment-55b544cd4-kmqkk ClientIP=10.244.2.0 XFF=
# curl -s 61.210.163.59:30080
Host=61.210.163.59 Path=/ From=sample-deployment-55b544cd4-vjbj2 ClientIP=10.244.2.0 XFF=

6.5.2 NodePort的注意事项

NodePort的Port的范围应该设定在30000~32767之间,如果不在这个范围之内,则会报错。

当指定nodePort:8888时,报错的例子

sample-nodeport-fail.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: sample-nodeport-fail
spec:
type: NodePort
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
nodePort: 8888
selector:
app: sample-app

创建产生error

1
2
# kubectl apply -f sample-nodeport-fail.yaml 
The Service "sample-nodeport-fail" is invalid: spec.ports[0].nodePort: Invalid value: 8888: provided port is not in the valid range. The range of valid ports is 30000-32767

The range of valid ports is 30000-32767

多个NodePort不能同时使用一个Port

sample-nodeport-fail2.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: sample-nodeport-fail2
spec:
type: NodePort
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
nodePort: 30080
selector:
app: sample-app

由于之前已经创建了port为30080的nodePort,因此报错

1
2
# kubectl apply -f sample-nodeport-fail2.yaml 
The Service "sample-nodeport-fail2" is invalid: spec.ports[0].nodePort: Invalid value: 30080: provided port is already allocated

排除服务故障

如果无法通过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.localmyservice.mynamespace),但并不起作用,请查看是否可以使用其ClusterIP而不是FQDN来访问Service。
  • 检查是否连接到Service公开的port,而不是targetPort(目标container的port)
  • 尝试之间连接到pod IP以确认pod正在接受正确端口上的连接。
  • 如果使用pod的IP address都无法访问应用,请确保应用不是尽绑定到localhost(本地主机)上