1 介绍服务
Kubernetes服务是一种为一组功能相同的pod提供单一不变的接入点的资源。当服务存在时,它的IP地址和端口不会改变。客户端通过IP地址和端口号建立连接,这些连接会被路由到提供该服务的任意一个pod上。通过这种方式,客户端不需要知道每个单独的提供服务的pod的地址,这样这些pod就可以在集群中随时被创建或移除。
1.1 创建服务
- 通过
kubectl expose
创建服务 - 通过YAML描述文件来创建服务
代码清单 服务的定义:kubia-svc.yaml
apiVersion:v1
kind:Service
metadata:
name:kubia
spec:
ports:
-port:80 #该服务的可用端口
targetPort:8080 #服务将连接转发到的容器端口
selector: #具有app=kubia标签的pod
app:kubia #都属于该服务
-
接下来通过使用
kubectl create
发布文件来创建服务 -
检测新的服务
$ kubectl get svc
-
从内部集群测试服务
- 显而易见的方法是创建一个pod,它将请求发送到服务的集群IP并记录响应。可以通过查看pod日志检查服务的响应。
- 使用ssh远程登录到其中一个Kubernetes节点上,然后使用
curl
命令。
-
可以通过
kubectl exec
命令在一个已经存在的pod中执行curl
命令。 -
在运行的容器中远程执行命令
$ kubectl exec kubia-7nog1 --curl -s http://10.111.249.153
-
配置服务上的会话亲和性
设置服务的
sessionAffinity
属性为ClientIP
(而不是None,None是默认值)
apiversion:v1
kind:Service
spec:
sessionAffinity:ClientIP
...
这种方式将会使服务代理将来自同一个client IP的所有请求转发至同一个pod上。
Kubernetes仅仅支持两种形式的会话亲和性服务:None和ClientIP。不支持基于cookie的会话亲和性的选项,这是因为Kubernetes服务不是在HTTP层面上工作。服务处理TCP和UDP包,并不关心其中的载荷内容。因为cookie是HTTP协议中的一部分,服务并不知道它们,这就解释了为什么会话亲和性不能基于cookie。
-
同一个服务暴露多个端口
创建的服务可以暴露一个端口,也可以暴露多个端口。比如,你的pod监听两个端口,比如HTTP监听8080端口、HTTPS监听8443端口,可以使用一个服务从端口80和443转发至pod端口8080和8443。在这种情况下,无须创建两个不同的服务。通过一个集群IP,使用一个服务就可以将多个端口全部暴露出来。
apiVersion:v1 kind:Service metadata: name:kubia spec: ports: - name:http port:80 #pod的8080端口 targetPort:8080 #映射成80端口 - name:https port:443 #pod的8443端口 targetPort:8443 #映射成443端口 selector: #标签选择器适用于 app:kubia #整个服务
1.2 服务发现
- 通过环境变量发现服务
- 通过DNS发现服务
2 连接集群外部的服务
希望通过Kubernetes服务特性暴露外部服务的情况。不要让服务将连接重定向到集群中的pod,而是让它重定向到外部IP和端口。这样做可以充分利用服务负载平衡和服务发现。
2.1 服务endpoint
Endpoint 资源就是暴露一个服务的IP地址和端口的列表,Endpoint资源和其他Kubernetes资源一样,所以可以使用kubectl info
来获取它的基本信息。
$ kubectl get endpoints kubia
2.2 手动配置服务的endpoint
如果创建了不包含pod选择器的服务,Kubernetes将不会创建Endpoint资源(毕竞,缺少选择器,将不会知道服务中包含哪些pod)。这样就需要手动创建Endpoint资源来指定该服务的endpoint列表。
要使用手动配置endpoint的方式创建服务,需要创建服务和Endpoint资源。
-
创建没有选择器的服务
首先为服务创建一个YAML文件
代码清单 不含pod选择器的服务:external-service.yaml
apiVersion:v1 kind:Service metadata: name:external-service #服务的名字必须和Endpoint对象的名字相匹配 spec: ports: #服务中没有定义选择器 -port:80
-
为没有选择器的服务创建 Endpoint 资源
代码清单 手动创建 Endpoint 资源:external-service-endpoints.yaml
apiVersion:v1 kind:Endpoints metadata: name:external-service #Endpoint的名称必须和服务的名称相匹配(见前一个代码清单) subsets: -addresses: -ip:11.11.11.11 #服务将连接重定向 -ip:22.22.22.22 #到endpoint的IP地址 ports: -port:80 #endpoint的目标端口
Endpoint对象需要与服务具有相同的名称,并包含该服务的目标IP地址和端口列表。服务和Endpoint资源都发布到服务器后,这样服务就可以像具有pod选择器那样的服务正常使用。在服务创建后创建的容器将包含服务的环境变量,并且与其
IP:port
对的所有连接都将在服务端点之间进行负载均衡。
2.3 为服务创建别名
除了手动配置服务的Endpoint来代替公开外部服务方法,有一种更简单的方法,就是通过其完全限定域名(FQDN)访问外部服务
-
创建
ExternalName
类型的服务 要创建一个具有别名的外部服务的服务时,要将创建服务资源的一个type字段设置为
ExternalName
。例如,设想一下在someapi.somecompany.com
上有公共可用的API,可以定义一个指向它的服务,如下面的代码清单所示。代码清单 ExternalName类型的服务:extemal-service-extemalname.yaml
apiVersion:v1 kind:Service metadata: name:external-service spec: type:ExternalName #代码的type被设置成ExternalName externalName:someapi.somecompany.com #实际服务的完全限定域名 ports: -port:80
3 将服务暴露给外部客户端
有几种方式可以在外部访问服务:
- 将服务的类型设置成
NodePort
- 将服务的类型设置成
LoadBalance
(NodePort
类型的一种扩展) - 创建一个Ingress资源,这是一个完全不同的机制,通过一个IP地址公开多个服务一—它运行在HTTP层(网络协议第7层)上。
3.1 使用NodePort
类型的服务
创建 NodePort类型的服务
代码清单 NodePort 服务定义kubia-svc-nodeport.yaml
apiVersion:v1
kind:Service
metadata:
name:kubia-nodeport
spec:
type:NodePort #为NodePort设置服务类型
ports:
-port:80 #服务集群IP的端口号
targetPort:8080 #背后pod的目标端口号
nodePort:30123 #通过集群节点的30123端口可以访问该服务
selector:
app:kubia
查看NodePort类型的服务
$ kubectl get svc kubia-nodeport
更改防火墙规则,让外部客户端访问我们的NodePort
服务(谷歌云平台)
$ gcloud compute firewall -rules create kubia-svc-rule --allow=tcp:30123
3.2 通过负载均衡器将服务暴露出来
创建 LoadBalance
服务
代码清单 Load Balancer类型的服务:kubia-svc-loadbalancer.yaml
apiversion:v1
kind:Service
metadata:
name:kubia-loadbalancer
spec:
type:LoadBalancer #该服务从Kubernetes集群的基础架构获取负载平衡器
ports:
-port:80
targetPort:8080
selector:
app:kubia
通过负载均衡器连接服务
创建服务后,云基础架构需要一段时间才能创建负载均衡器并将其IP地址写入服务对象。一旦这样做了,IP地址将被列为服务的外部IP地址:
$ kubectl get svc kubia-loadbalancer
假设负载均衡器的IP地址(EXTERNAL-IP)为130.211.53.173
,现在可以通过该IP地址访问该服务:
$ curl http://130.211.53.173
4 pod就绪后发出信号
4.1 就绪指针
像存活探针一样,就绪探针有三种类型:
- Exec探针,执行进程的地方。容器的状态由进程的退出状态代码确定。
- HTTP GET探针,向容器发送HTTP GET请求,通过响应的HTTP状态代码判断容器是否准备好。
- TCP socket探针,它打开一个TCP连接到容器的指定端口。如果连接已建立,则认为容器已准备就绪。
4.2 向pod 添加就绪指针
通过修改Replication Controller
的pod模板来为现有的pod添加就绪探针。
向pod template添加就绪探针
可以通过kubectl edit
命令来向已存在的ReplicationController
中的pod模板添加探针。
$ kubectl edit rc kubia
当在文本编辑器中打开ReplicationController
的YAML
时,在pod模板中查找容器规格,并将以下就绪探针定义添加到spec.template.spec.containers
下的第一个容器。YAML
看起来应该就像下面的代码清单。
代码清单 RC创建带有就绪探针的pod:kubia-rc-readinessprobe.yaml
apiVersion:v1
kind:ReplicationController
...
spec:
...
template:
...
spec:
containers:
- name:kubia
image:luksa/kubia
readinessProbe: #
xec: #pod中的每个容器都
command: #会有一个就绪探针
- ls #
- /var/ready #
...
5 使用headless服务来发现独立的pod
5.1 创建 headless服务
将服务spec
中的clusterIP
字段设置为None
会使服务成为headless
服务,因为Kubernetes
不会为其分配集群IP,客户端可通过该IP将其连接到支持它的pod。
现在将创建一个名为kubia-headless
的headless
服务。以下代码清单显示了它的定义。
代码清单 一个headless 服务:kubia-svc-headless.yaml
apiVersion:v1
kind:Service
metadata:
name:kubia-headless
spec:
clusterIP:None #这使得服务成为headless的
ports:
- port:80
targetPort:8080
selector:
app:kubia
在使用kubectl create
创建服务之后,可以通过kubectl get
和kubectl describe
来查看服务,你会发现它没有集群IP,并且它的后端包含与pod 选择器匹配的(部分)pod。“部分”是因为pod包含就绪探针,所以只有准备就绪的pod会被列出作为服务的后端文件来确保至少有两个pod报告已准备就绪。
5.2 通过DNS发现pod
不通过YAML文件运行pod:
$ kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command--sleep infinity
诀窍在--generator=run-pod/v1
选项中,该选项让kubectl直接创建pod,而不需要通过ReplicationController
之类的资源来创建。
使用新创建的pod执行DNS查找:
$ kubectl exec dnsutils nslookup kubia-headless
5.3 发现所有的pod—包括未就绪的pod
只有准备就绪的pod能够作为服务的后端。但有时希望即使pod没有准备就绪,服务发现机制也能够发现所有匹配服务标签选择器的pod。
幸运的是,不必通过查询KubernetesAPI
服务器,可以使用DNS查找机制来查找那些未准备好的pod。要告诉Kubernetes
无论pod的准备状态如何,希望将所有pod添加到服务中。必须将以下注解添加到服务中:
kind:Service
metadata:
annotations:
service.alpha.kubernetes.io/tolerate-unready-endpoints:"true"
6 排除服务故障
如果无法通过服务访问pod,应该根据下面的列表进行排查:
- 首先,确保从集群内连接到服务的集群
IP
,而不是从外部。 - 不要通过
ping
服务IP
来判断服务是否可访问(请记住,服务的集群IP
是虚拟IP
,是无法ping
通的)。 - 如果已经定义了就绪探针,请确保它返回成功;否则该
pod
不会成为服务的一部分。 - 要确认某个容器是服务的一部分,请使用
kubectl get endpoints
来检查相应的端点对象。 - 如果尝试通过
FQDN
或其中一部分来访问服务(例如,myservice.mynamespace.svc.cluster.local
或myservice.mynamespace
),但并不起作用,请查看是否可以使用其集群IP
而不是FQDN
来访问服务。 - 检查是否连接到服务公开的端口,而不是目标端口。
- 尝试直接连接到
podIP
以确认pod
正在接收正确端口上的连接。 - 如果甚至无法通过
pod
的IP
访问应用,请确保应用不是仅绑定到本地主机。
7 小结
- 在一个固定的
IP
地址和端口下暴露匹配到某个标签选择器的多个pod
- 服务在集群内默认是可访问的,通过将服务的类型设置为
NodePort
或LoadBalancer
,使得服务也可以从集群外部访问 - 让
pod
能够通过查找环境变量发现服务的IP
地址和端口 - 允许通过创建服务资源而不指定选择器来发现驻留在集群外部的服务并与之通信,方法是创建关联的
Endpoint
资原 - 为具有
ExternalName
服务类型的外部服务提供DNS CNAME
别名 - 通过单个
Ingress
公开多个HTTP
服务(使用单个IP
) - 使用
pod
容器的就绪探针来确定是否应该将pod
包含在服务endpoints
内 - 通过创建
headless
服务让DNS
发现pod IP
- 通过
kubectl exec
在pod
容器中执行命令 - 通过
kubectl apply
命令修改Kubernetes
资源 - 使用
kubectl run --generator=run-pod/v1
运行临时的pod