⒈引用

  在Kubernetes中,pod通常需要对来自集群内部的其他pod或来自集群外部的客户端的HTTP请求做出响应。pod需要一种寻找其他pod的方法来使用其他pod提供的服务,不像在没有Kubernetes的世界,系统管理员需要在配置文件中明确指出服务的精确的IP地址或者主机名,这种方式在Kubernetes中并不适用,因为
    ·pod是短暂的一—它们随时会启动或者关闭,无论是为了给其他pod提供空间而从节点中被移除,或者是减少了pod的数量,又或者是因为节点异常。
    ·Kubernetes在pod启动前会给已经调度到节点上的pod分配IP地址一—因此客户端不能提前知道提供服务pod的IP地址。
    ·水平伸缩意味着多个pod可能会提供相同的服务一—每个pod都有自己的IP地址,客户端无须关心后端提供服务pod的数量,以及各自对应的IP地址。它们无须记录每个pod的IP地址。相反,所有的pod可以通过一个单一的IP地址进行访问。
  为了解决上述问题,Kubernetes提供了一种资源类型——服务(service)。

⒉介绍

  Kubenetes服务是一种为一组功能相同的pod提供单一不变的接入点的资源。当服务存在时,它的IP地址和端口不会改变。客户端通过IP地址和端口号建立连接,这些连接会被路由到提供该服务的任意一个pod上。通过这种方式,客户端不需要知道每个单独的提供服务的pod的地址,这样这些pod就可以在集群中随时被创建或移除。

⒊创建服务

  1.通过kubectl expose创建服务

  创建服务的最简单的方法是通过kubectl expose,利用expose命令和pod选择器来创建服务资源,从而通过单个的IP和端口来访问所有的pod。

  2.通过YAML描述文件来创建服务



apiVersion: v1
kind: Service
metadata:
  name: coreqi
spec:
  ports:
  - port: 80  #该服务将在80端口接收请求
    targetPort: 8080  #服务将把请求转发到pod的8080端口
  selector:
    app: coreqi  #具有app=coreqi标签的pod都属于该服务



  创建以上描述文件后使用kubectl命令创建服务



kubectl create -f coreqi-svc.yaml



⒋获取所有服务(当面命名空间下)



kubectl get svc



⒌测试服务

  从内部集群测试服务可以通过以下几种方法向服务发送请求:
    ·显而易见的方法是创建一个pod,它将请求发送到服务的集群IP并记录响应。可以通过查看pod日志检查服务的响应。
    ·使用ssh远程登录到其中一个Kubernetes节点上,然后使用curl命令。
    ·可以通过kubectl exec命令在一个已经存在的pod中执行curl命令。

  以第三种方式为例,可以使用kubectl exec命令远程地在一个已经存在的pod容器上执行任何命令。这样就可以很方便地了解pod的内容、状态及环境。用kubectl get pod命令列出所有的pod,并且选择其中一个作为exec命令的执行目标。



kubectl exec {podName} --curl -s http://10.111.249.153



 ⒍服务的会话亲和性

  如果向一个服务多次执行同样的命令,每次调用都应该在不同的pod上执行。因为服务代理通常将每个连接随机指向选中的后端pod中的一个,即使连接来自于同一个客户端。
  如果希望特定客户端产生的所有请求每次都指向同一个pod,可以设置服务的 sessionAffinity属性为 ClientIP(而不是none,none是默认值)。这种方式将会使服务代理将来自同一个 客户端 IP的所有请求转发至同一个pod上。

  Kubernetes仅仅支持两种形式的会话亲和性服务:None和 ClientIP,你或许惊讶竟然不支持基于 cookie的会话亲和性的选项,但是你要了解 Kubernetes服务不是在HTTP层面上工作。服务处理TP和UDP包,并不关心其中的载荷内容。因为 cookie HTTP是HTTP协议中的一部分,服务并不知道它们,这就解释了为什么会话亲和性不能基于 cookie。



apiVersion: v1
kind: Service
metadata:
  name: coreqi
spec:
  ports:
  - port: 80  #该服务将在80端口接收请求
    targetPort: 8080  #服务将把请求转发到pod的8080端口
  selector:
    app: coreqi  #具有app=coreqi标签的pod都属于该服务
  sessionAffinity: ClientIP #会话亲和性,默认值值为None【随机指向pod】,设置为ClientIP则特定客户端所产生的所有请求每次都会指向同一个pod,这种方式将会使服务代理将来自同一个client IP的所有请求转发至同一个pod上。



⒎单个服务暴露多个端口

  创建的服务可以暴露一个端口,也可以暴露多个端口。比如,你的pod监听两个端口,比如HTTP监听8080端口、 ,HTTP HTTPS,监听8443端口,可以使用一个服务从端口80和443转发至pod端口8080和8443。在这种情况下,无须创建两个不同的服务。通过一个集群IP,使用一个服务就可以将多个端口全部暴露出来。
  注意:在创建一个有多个端口的服务的时候必须给每个端口指定名字。



apiVersion: v1
kind: Service
metadata:
  name: coreqi
spec:
  ports:
  - name: http
    port: 80  #该服务将在80端口接收请求
    targetPort: 8080  #服务将把请求转发到pod的8080端口
  - name: https
    port: 443
    targetPort: 8443  
  selector:
    app: coreqi  #具有app=coreqi标签的pod都属于该服务
  sessionAffinity: ClientIP #会话亲和性,默认值值为None【随机指向pod】,设置为ClientIP则特定客户端所产生的所有请求每次都会指向同一个pod,这种方式将会使服务代理将来自同一个client IP的所有请求转发至同一个pod上。



  注意:服务的标签选择器应用于整个服务,不能对每个端口做单独的配置。如果不同的pod有不同的端口映射关系,需要创建两个服务。

⒏为端口号命名(起别名) 



#在pod的定义中指定port名称
kind: Pod
spec: 
  containers:
  - name: coreqi
    ports:
    - name: http
      containerPort: 8080
    - name: https
      containerPort: 8443



#在Service的定义中引用
apiVersion: v1
kind: Service
spec:
  ports:
  - name: http
    port: 80 
    targetPort: http  
  - name: https
    port: 443
    targetPort: https



  采用命名端口最大的好处就是即使更换端口号也无须更改服务 spec,你的pod现在对http服务用的是8080,但是假设过段时间你决定将端口更换为80呢?
  如果你采用了命名的端口,仅仅需要做的就是改变pod中spec描述的端口号(当然你的端口号的名称没有改变)。在你的pod向新端口更新时,根据pod收到的连接(8080端口在旧的pod上、80端口在新的pod上),用户连接将会转发到对应的端口号上。

⒐服务发现

   通过创建服务,现在就可以通过一个单一稳定的IP地址访问到pod。在服务整个生命周期内这个地址保持不变。在服务后面的pod可能删除重建,它们的IP地址可能改变,数量也会增减,但是始终可以通过服务的单一不变的IP地址访问到这些pod。

  但客户端pod如何知道服务的IP和端口?是否需要先创建服务,然后手动查找其IP地址并将IP传递给客户端pod的配置选项?当然不是。Kubernetes为客户端提供了发现服务的IP和端口的方式。

  1.通过环境变量发现服务

  在pod开始运行的时候,Kubernetes会初始化一系列的环境变量指向现在存在的服务。如果你创建的服务早于客户端pod的创建,则哭护短pod上的进程可以根据环境变量获得服务的IP地址和端口号。但是如果服务的创建晚于哭护短pod的创建,那么关于这个服务的环境变量并没有设置,这个问题也需要解决。

  查看客户端pod的环境变量



kubectl exec {podName} env



  列表中将显示集群中所有服务的环境变量(在客户端pod创建之前所有服务的环境变量),其中环境变量使用HOST保存了该服务的IP地址,使用PORT保存了服务的端口。

  注意:环境变量保存的服务名称中的横杠被转换为下画线,并且当服务名称用作环境变量名称中的前缀时,所有的字母都是大写的。

  2.通过DNS发现服务 

  默认情况下每一个命名空间下都有一个称作kube-dns的pod,就像名字的暗示,这个pod运行DNS服务,集群中的其他pod都被配置为使用其作为dns(Kubernetes通过修改每个容器的/etc/resolv.conf文件实现)。集群中的其他pod所运行的DNS查询都会被该DNS服务器响应,该服务器知道系统中运行的所有服务。
  注意:集群中的pod是否使用内部的DNS服务器是根据pod中spec的dnsPolicy属性来决定的。
  每个服务从内部DNS服务器中获得一个DNS条目,客户端的pod在知道服务名称的情况下可以通过全限定域名(FQDN)来访问,而不仅仅只是通过环境变量。

  通过FQDN连接服务
  集群中的其他pod可以通过打开以下FQDN连接来访问服务:



serviceName.default.svc.cluster.local



  其中serviceName对应于服务名称,default表示当前服务所在的命名空间,而svc.cluster.local是在所有集群本地服务名称中使用的可配置集群域后缀。
  注意:客户端仍然必须知道服务的端口号。如果服务使用标准端口号(例如,HTTP的80端口或Postgres的5432端口),这样是没问题的。如果并不是标准端口,客户端可以从环境变量中获取端口号。
  如果其他pod和服务在同一个命名空间下,可以省略svc.cluster.local后缀,甚至命名空间。因此可以使用serviceName来指代服务。这简单到不可思议。

  在pod容器中运行shell
  可以通过kubectl exec命令在一个pod容器上运行bash(或者其他形式的shell)。通过这种方式,可以随意浏览容器,而无须为每个要运行的命令执行kubectl exec。
  注意:shell的二进制可执行文件必须在容器镜像中可用才能使用。
  为了正常地使用shell,kubectl exec命令需要添加-it选项



进入容器内部运行bash shell
kubectl exec-it {podName} bash
通过任意一种方式访问服务
1.curl http://serviceName.default.svc.cluster.local
2.curl http://serviceName.default
3.curl http://serviceName



  在请求的URL中,可以将服务的名称作为主机名来访问服务。因为每个pod容器在DNS解析器配置中都默认配置了可以将命名空间和svc.cluster.local后缀省略掉。通过以下命令查看一下pod容器中的/etc/resilv.conf文件就明白了。



cat /etc/resolv.conf



  

  无法ping 通服务IP的原因
  当我们使用curl这个服务时,这个服务是工作的,但是却ping不通。这是因为服务的集群IP是一个虚拟IP,并且只有在与服务端口结合时才有意义。

 

⒑连接集群外部的服务

  如果我们希望不要让Kubernetes服务将请求重定向到集群中的pod,而是让它重定向到外部IP和端口。这样做的话可以充分利用服务的负载平衡和服务发现。在集群中运行的客户端pod可以像连接到内部服务一样连接到外部服务。

  1.介绍服务endpoint

  在讲述如何做到这一点之前,先阐述一下服务。服务并不是和pod直接相连的。相反,有一种资源介于两者之间—-它就是Endpoint资源。如果之前在服务上运行过kubectl describe命令。



kubectl describe svc {serviceName}



  则展示服务的详细列表内将会出现Selector、Endpoints属性等等,其中Selector描述了服务用于创建endpoint列表的pod标签选择器。而Endpoints则描述了服务的endpoint的pod的IP和端口列表。

  Endpoint 资源就是暴露一个服务所指向的的IP地址和端口的列表,Endpoint资源和其他Kubernetes 资源一样,所以可以使用kubectl info来获取它的基本信息。



kubectl get endpoints {serviceName}



  尽管在服务中定义了pod选择器,但在将请求转发时不会直接使用它。相反,选择器用于构建IP和端口列表,然后存储在Endpoint资源中。当客户端连接到服务时,服务代理会选择这些IP和端口列表中的一个,并将传入的连接重定向到选中的服务器。

  2.手动配置服务的 endpoint

  或许你已经意识到一点,只有将服务的endpoint与服务解耦后,才可以分别手动配置和更新它们。只有创建了不包含pod选择器的服务,Kubernetes才不会创建Endpoint资源(毕竟,缺少选择器,服务就不知道应该包括哪些pod)。这样就需要手动创建Endpoint 资源来指定该服务的endpoint列表。
  要使用手动的方式配置服务的endpoint,需要分别创建服务和Endpoint资源。
  一、首先创建一个没有选择器的服务YAML描述文件。



#在该服务的描述文件中并没有定义pod选择器
apiVersion: v1
kind: Service
metadata:
  name: coreqi-service  #注意服务的名称必须和Endpoint对象的名称相匹配
spec:
  ports:
    port: 80



 创建以上描述文件后使用kubectl命令创建服务



kubectl create -f coreqi-svc.yaml



  二、为没有选择器的服务创建 Endpoint 资源

  Endpoint是一个单独的资源并不是服务的一个属性。由于创建的资源中并不包含选择器,相关的Endpoints资源并没有自动创建,所以必须手动创建。



apiVersion: v1
kind: Endpoints
metadata:
  name: coreqi-service  #Endpoint的名称必须和服务的名称相匹配
subsets:
  - addresses:  #服务将请求重定向到endpoint的ip地址
    - ip: 11.11.11.11
    - ip: 22.22.22.22
    ports:  #endpoint的目标端口
    - port: 80



  创建以上描述文件后使用kubectl命令创建

  Endpoint对象需要与服务具有相同的名称,并包含该服务的目标IP地址和端口列表。服务和Endpoint资源都发布到服务器后服务就可以像具有pod 选择器那样的服务正常使用。在服务创建之后所创建的容器将包含该服务的环境变量,并且与其IP:port对的所有连接都将在服务端点之间进行负载均衡。

  如果稍后决定将外部服务迁移到Kubernetes中运行的pod,可以为服务添加选择器,从而对Endpoint进行自动管理。反过来也是一样的一—将选择器从服务中移除,Kubernetes将停止更新Endpoints。这意味着服务的IP地址可以保持不变,同时服务的实际实现却发生了改变。

  3.为外部服务创建别名

  除了手动配置服务的Endpoint来代替公开外部服务方法,有一种更简单的方法,就是通过其完全限定域名(FQDN)访问外部服务
  创建 ExternalName类型的服务
  要创建一个具有别名的外部服务的服务时,要将创建服务资源的一个type字段设置为ExternalName。例如,设想一下在api.coreqi.cn上有公共可用的API,可以定义一个指向它的服务,如下面的代码清单所示。



apiVersion: v1
kind: Service
metadata:
  name: coreqi-service
spec:
  type: ExternalName  #type被设置成ExternalName
  externalName: api.coreqi.cn #实际服务的完全限定域名
  ports:
    port: 80



  服务创建完成后,pod可以通过coreqi-service.default.svc.cluster.local域名(甚至是coreqi-service)连接到外部服务,而不是使用服务的实际FQDN。这隐藏了实际的服务名称及其使用该服务的pod的位置,如果以后要将其指向不同的服务,只需简单地修改服务定义externalName属性,或者将类型重新变回ClusterIP并为服务创建Endpoint--无论是手动创建,还是对服务上指定标签选择器使其自动创建。
  ExternalName服务仅在DNS级别实施一—为服务创建了简单的CNAME DNS记录。因此,连接到服务的客户端将直接连接到外部服务,完全绕过服务代理。出于这个原因,这些类型的服务甚至不会获得集群IP。
  注意:CNAME记录指向完全限定的域名而不是数字IP地址。

⒒将服务暴露给外部客户端

  到目前为止,只讨论了集群内服务如何被pod使用;但是,还需要向外部公开某些服务。例如前端web服务器,以便外部客户端可以访问它们。
  有几种方式可以在外部访问服务:

  一、将服务的类型设置成NodePort--每个集群节点都会在节点上打开一个端口,对于NodePort服务,每个集群节点在节点本身(因此得名叫NodePort)上打开一个端口,并将在该端口上接收到的流量重定向到基础服务。该服务仅在内部集群IP和端口上才可访问,但也可通过所有节点上的专用端口访问。
  二、将服务的类型设置成LoadBalance,NodePort类型的一种扩展--这使得服务可以通过一个专用的负载均衡器来访问,这是由Kubernetes中正在运行的云基础设施提供的。负载均衡器将流量重定向到跨所有节点的节点端口。客户端通过负载均衡器的IP连接到服务。
  三、创建一个Ingress资源,这是一个完全不同的机制,通过一个IP地址公开多个服务——它运行在HTTP层(网络协议第7层)上,因此可以提供比工作在第4层的服务更多的功能。

 

  1.使用NodePort类型的服务  

  将一组pod公开给外部客户端的第一种方法是创建一个服务并将其类型设置为NodePort。通过创建NodePort服务,可以让Kubernetes在其所有节点上保留一个端口(所有节点上都使用相同的端口号),并将传入的连接转发给作为服务部分的pod。
  这与常规服务类似(它们的实际类型是ClusterIP),但是不仅可以通过服务的内部集群IP访问NodePort服务,还可以通过任何节点的IP和预留节点端口访问NodePort服务。
  当尝试与NodePort服务交互时,意义更加重大。

  创建NodePort 类型的服务
  现在将创建一个NodePort服务,以查看如何使用它。下面的代码清单显示了服务的YAML。



apiVersion: v1
kind: Service
metadata:
  name: coreqi-service
spec:
  type: NodePort  #type被设置成NodePort
  ports:
  - port: 80  
    targetPort: 8080
    nodePort: 30123 #通过集群节点的30123端口可以访问该服务
  selector:
    app: coreqi



  将类型设置为NodePort并指定该服务应该绑定到的所有集群节点的节点端口。指定端口不是强制性的。如果忽略它,Kubernetes将选择一个随机端口。
  注意当在GKE中创建服务时,kubectl将会打印出一个关于必须配置防火墙规则的警告。
  查看NodePort类型的服务
  查看该服务的基础信息: