服务定义

服务注册与发现

Kubernetes ServiceTypes允许指定一个需要的类型的Service,默认是ClusterIP类型。Service有三种类型:

  • ClusterIP:默认类型,自动分配一个仅Cluster内部可以访问的虚拟IP
  • NodePort:在ClusterIP基础上为Service在每台机器上绑定一个端口,这样就可以通过 <NodeIP>:NodePort 来访问该服务。
  • LoadBalancer:在NodePort的基础上,借助Cloud Provider创建一个外部的负载均衡器,并将请求转发到 <NodeIP>:NodePort
  • ExternalName:通过返回CNAME和它的值,可以将服务映射到externalName字段的内容(例如,foo.bar.example.com。没有任何类型代理被创建,这只有Kubernetes 1.7或更高版本的kube-dns才支持。

Service定义

一个ServiceKubernetes中是一个REST对象,和Pod类似。像所有的REST对象一样,Service定义可以基于POST方式,请求Api Server创建新的实例。典型的Service定义方式如下:

kind: Service
apiVersion: v1
metadata:
  name: hostname-service
spec:
  # Expose the service on a static port on each node
  # so that we can access the service from outside the cluster
  type: NodePort

  # When the node receives a request on the static port (30163)
  # "select pods with the label 'app' set to 'echo-hostname'"
  # and forward the request to one of them
  selector:
    app: echo-hostname

  ports:
    # Three types of ports for a service
    # nodePort - a static port assigned on each the node
    # port - port exposed internally in the cluster
    # targetPort - the container port to send requests to
    - nodePort: 30163
      port: 8080
      targetPort: 80

这里需要对于Port的定义进行简要说明:

  • port表示Service暴露在Cluster IP上的端口,<cluster ip>:port 是提供给集群内部客户访问Service的入口。
  • nodePortK8s提供给集群外部客户访问Service入口的一种方式(另一种方式是LoadBalancer,所以,<nodeIP>:nodePort 是提供给集群外部客户访问Service的入口。
  • targetPortPod上的端口,从portnodePort上到来的数据最终经过kube-proxy流入到后端PodtargetPort上进入容器。

总的来说,portnodePort都是Service的端口,前者暴露给集群内客户访问服务,后者暴露给集群外客户访问服务。从这两个端口到来的数据都需要经过反向代理kube-proxy流入后端PodtargetPod,从而到达Pod上的容器内。默认情况下,targetPort将被设置为与port字段相同的值;targetPort可以是一个字符串,引用了后端Pod的一个端口的名称。但是,实际指派给该端口名称的端口号,在每个后端Pod中可能并不相同。对于部署和设计Service,这种方式会提供更大的灵活性;例如,可以在后端 软件下一个版本中,修改Pod暴露的端口,并不会中断客户端的调用。

多端口Service

很多Service需要暴露多个端口。对于这种情况,Kubernetes支持在Service对象中定义多个端口。当使用多个端口时,必须给出所有端口的名称,这样Endpoint就不会产生歧义,例如:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377

应用选择

Kubernetes Service能够支持TCPUDP协议,默认TCP协议,其通过标签来选取服务后端,一般配合Replication Controller或者Deployment来保证后端容器的正常运行。这一组Pod能够被Service访问到,通常是通过Label Selector实现的。另外,也可以将已有的服务以Service的形式加入到Kubernetes集群中来,只需要在创建Service的时候不指定Label selector,而是在Service创建好后手动为其添加endpoint。对Kubernetes集群中的应用,Kubernetes提供了简单的Endpoints API,只要Service中的一组Pod发生变更,应用程序就会被更新。对非Kubernetes集群中的应用,Kubernetes提供了基于VIP的网桥的方式访问Service,再由Service重定向到后端Pod

NodePort类型

如果设置type的值为NodePortKubernetes master将从给定的配置范围内(默认:30000-32767)分配端口,每个Node将从该端口(每个Node上的同一端口)代理到Service。该端口将通过Servicespec.ports[*].nodePort 字段被指定。如果需要指定的端口号,可以配置nodePort的值,系统将分配这个端口,否则调用API将会失败(比如,需要关心端口冲突的可能性。这可以让开发人员自由地安装他们自己的负载均衡器,并配置Kubernetes不能完全支持的环境参数,或者直接暴露一个或多个NodeIP地址。需要注意的是,Service将能够通过 spec.ports[*].nodePortspec.clusterIp:spec.ports[*].port 而对外可见。

运行以下命令,使用NodePort服务导出pod

$ kubectl expose pods nginx --type=NodePort --port=80

service/nginx exposed

$ kubectl get svc

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        29m
nginx        NodePort    10.101.42.230   <none>        80:31858/TCP   5s

$ kubectl delete svc nginx

service "nginx" deleted

现在我们要创建一个NodePort服务,但我们要创建一个包含其定义的yaml清单。

vim primeiro-service-nodeport.yaml
apiVersion : v1
kind : Service
metadata :
   labels :
     run : nginx
  name : nginx-nodeport
  namespace : default
spec :
   externalTrafficPolicy : Cluster
  ports :
  - nodePort : 31111
    port : 80
    protocol : TCP
    targetPort : 80
  selector :
     run : nginx
  sessionAffinity : None
  type : NodePort

然后创建服务:

$ kubectl create -f primeiro-service-nodeport.yaml

service/nginx-nodeport created

$ kubectl get services

NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1      <none>        443/TCP        30m
nginx-nodeport   NodePort    10.102.91.81   <none>        80:31111/TCP   7s

$ kubectl describe service nginx

Name:                     nginx-nodeport
Namespace:                default
Labels:                   run=nginx
Annotations:              <none>
Selector:                 run=nginx
Type:                     NodePort
IP:                       10.102.91.81
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  31111/TCP
Endpoints:                10.46.0.1:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

$ kubectl delete -f primeiro-service-nodeport.yaml

service "nginx-nodeport" deleted

LoadBalancer类型

使用支持外部负载均衡器的云提供商的服务,设置type的值为 “LoadBalancer”,将为Service提供负载均衡器。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过Servicestatus.loadBalancer字段被发布出去。

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
      nodePort: 30061
  clusterIP: 10.0.171.239
  loadBalancerIP: 78.11.24.19
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
      - ip: 146.148.47.155

来自外部负载均衡器的流量将直接打到后端Pod上,不过实际它们是如何工作的,这要依赖于云提供商。在这些情况下,将根据用户设置的loadBalancerIP来创建负载均衡器。某些云提供商允许设置loadBalancerIP。如果没有设置loadBalancerIP,将会给负载均衡器指派一个临时IP。如果设置了loadBalancerIP,但云提供商并不支持这种特性,那么设置的loadBalancerIP值将会被忽略掉。

其他类型的服务后端

Service抽象了该如何访问Kubernetes Pod,但也能够抽象其它类型的后端,例如:

  • 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
  • 希望服务指向另一个Namespace中或其它集群中的服务。
  • 正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的后端。

在任何这些场景中,都能够定义没有selectorService

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

由于这个Service没有selector,就不会创建相关的Endpoints对象。可以手动将Service映射到指定的Endpoints

kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376

注意,Endpoint IP地址不能是loopback(127.0.0.0/8、link-local(169.254.0.0/16、或者link-local多播(224.0.0.0/24。访问没有selectorService,与有selectorService的原理相同。请求将被路由到用户定义的Endpoint(该示例中为1.2.3.4:9376ExternalName ServiceService的特例,它没有selector,也没有定义任何的端口和Endpoint。相反地,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

当查询主机my-service.prod.svc.CLUSTER时,集群的DNS服务将返回一个值为my.database.example.comCNAME记录。访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在DNS层,而且不会进行代理或转发。如果后续决定要将数据库迁移到Kubernetes集群中,可以启动对应的Pod,增加合适的SelectorEndpoint,修改Servicetype