参考文档

APIs 和 CLIs 的设计文档,概念定义以及引用

编辑这个页面

Services

Kubernetes 的Pod的寿命是有限的。它们出生然后死亡,它们不会复活。ReplicationController是特别用来动态的创建和销毁Pods(如:动态伸缩或者执行rolling updates中动态的创建和销毁pod)。尽管每个Pod有自己的IP地址,随着时间变化随着时间推移即使这些IP地址也不能被认为是可靠的。这带来一个问题:如果一些Pod的集合(让我们称之为backends)为集群中的其他的Pod提供了一些功能(让我们称它们为frontends),这些frontends应该如何找到并一直知道哪些backends在这样的集合中呢?

欢迎进入Service的世界。

一个Kubernetes的Service是一种抽象,它定义了一组Pods的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。一个Service的目标Pod集合通常是由Label Selector 来决定的(下面有讲一个没有选择器的Service 有什么用处)。

举个例子,想象一个处理图片的后端运行了三个副本。这些副本都是可以替代的 - 前端不关心它们使用的是哪一个后端。尽管实际组成后端集合的Pod可能会变化,前端的客户端却不需要知道这个变化,也不需要自己有一个列表来记录这些后端服务。Service抽象能让你达到这种解耦。

对于那些Kubernetes原生的应用,Kubernetes提供了一个简单的Endpoints API,会在Service中的Pod集合发生改变的时候更新。对于非Kubernetes原生的应用,Kubernetes为Service提供了一种基于虚拟IP的桥接方式使其重定向到后端的Pods。

定义一个Service

Kubernetes中的Service是一个REST对象,这点与Pod类似。正如所有的REST对象一样,向apiserver POST一个Service的定义就能创建一个新的实例。例如,假设你有一组Pods,每一个Pod都开放了9376端口,并且都有一个"app=MyApp"的标签。

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "MyApp"
        },
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376
            }
        ]
    }
}

这个定义会创建一个新的Service对象,名字为”my-service”,它指向所有带有”app=MyApp”标签的Pod上面的9376端口。这个Service同时也会被分配一个IP地址(有时被称作”cluster ip”),它会被服务的代理所使用(见下面)。这个Service的选择器,会不断的对Pod进行筛选,并将结果POST到名字同样为“my-service”的Endpoints对象。

注意一个Service能将一个来源的端口映射到任意的targetPort。默认情况下,targetPort会被设置成与port字段一样的值。可能更有意思的地方在于,targetPort可以是一个字符串,能引用一个后端Pod中定义的端口名。实际指派给该名称的端口号在每一个Pod中可能会不同。这为部署和更新你的Service提供了很大的灵活性。例如,你可以在你的后端的下一个版本中更改开放的端口,而无需导致客户出现故障。

Kubernetes的Service支持TCPUDP协议。默认是TCP

没有选择器的Service

Service一般是用来对Kubernetes Pod的访问进行抽象,但是也可以用来对其他类型的后端进行抽象,如:

如果你碰到这些场景之一,那么你可以定义一个没有选择器的Service

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "ports": [
            {
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376
            }
        ]
    }
}

因为没有选择器,相应的Endpoints对象不会被创建。你可以手动将service映射到自己特定的endpoint。

{
    "kind": "Endpoints",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "subsets": [
        {
            "addresses": [
                { "ip": "1.2.3.4" }
            ],
            "ports": [
                { "port": 9376 }
            ]
        }
    ]
}

注意:Endpoint的IP不能是本地回环接口 (127.0.0.0/8),本地链路地址(169.254.0.0/16)或者本地链路多播地址((224.0.0.0/24)。

不用选择器来访问一个Service的表现就像它有一个选择器一样。流量会被路由到用户定义的endpoint(在这个例子是:1.2.3.4:9376)。

虚拟IP和服务代理

每一个Kubernetes集群中的node都运行了一个kube-proxy Every node in a Kubernetes cluster runs a kube-proxy. This application is responsible for implementing a form of virtual IP for Services. In Kubernetes v1.0 the proxy was purely in userspace. In Kubernetes v1.1 an iptables proxy was added, but was not the default operating mode. Since Kubernetes v1.2, the iptables proxy is the default.

As of Kubernetes v1.0, Services are a “layer 3” (TCP/UDP over IP) construct. In Kubernetes v1.1 the Ingress API was added (beta) to represent “layer 7” (HTTP) services.

Proxy-mode: userspace

In this mode, kube-proxy watches the Kubernetes master for the addition and removal of Service and Endpoints objects. For each Service it opens a port (randomly chosen) on the local node. Any connections to this “proxy port” will be proxied to one of the Service’s backend Pods (as reported in Endpoints). Which backend Pod to use is decided based on the SessionAffinity of the Service. Lastly, it installs iptables rules which capture traffic to the Service’s clusterIP (which is virtual) and Port and redirects that traffic to the proxy port which proxies the backend Pod.

The net result is that any traffic bound for the Service’s IP:Port is proxied to an appropriate backend without the clients knowing anything about Kubernetes or Services or Pods.

By default, the choice of backend is round robin. Client-IP based session affinity can be selected by setting service.spec.sessionAffinity to "ClientIP" (the default is "None").

Services overview diagram for userspace proxy

Proxy-mode: iptables

In this mode, kube-proxy watches the Kubernetes master for the addition and removal of Service and Endpoints objects. For each Service it installs iptables rules which capture traffic to the Service’s clusterIP (which is virtual) and Port and redirects that traffic to one of the Service’s backend sets. For each Endpoints object it installs iptables rules which select a backend Pod.

By default, the choice of backend is random. Client-IP based session affinity can be selected by setting service.spec.sessionAffinity to "ClientIP" (the default is "None").

As with the userspace proxy, the net result is that any traffic bound for the Service’s IP:Port is proxied to an appropriate backend without the clients knowing anything about Kubernetes or Services or Pods. This should be faster and more reliable than the userspace proxy. However, unlike the userspace proxier, the iptables proxier cannot automatically retry another Pod if the one it initially selects does not respond, so it depends on having working readiness probes.

Services overview diagram for iptables proxy

Multi-Port Services

Many Services need to expose more than one port. For this case, Kubernetes supports multiple port definitions on a Service object. When using multiple ports you must give all of your ports names, so that endpoints can be disambiguated. For example:

{
    "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
            }
        ]
    }
}

选择自己的IP地址

你可以在Service创建的请求中指定自己的集群IP地址。做法就是设置spec.clusterIP字段。例如,如果你已经有了一个DNS条目,并且想替换它,或者一个已经配置到了一个特定IP地址的老系统,并且难以重新配置。用户选择的IP地址必须是一个可用的IP地址,并且必须在API Server的service-cluster-ip-range启动参数所指定的CIDR范围内。如果设置了一个非法的IP地址,API Server将会返回422 HTTP状态码来指明这是一个无效值。

为什么不使用轮询式DNS?

一个经常不时冒出来的问题是,为什么我选择用虚拟IP而不是使用标准的轮询式DNS做法。这有一些原因:

我们试着让用户不要做会伤害自己的事情。尽管如此,如果有足够的人想要这个特性,我们可能会实现它作为一个备选方案。

发现服务

Kubernetes支持两种主要的发现Service的模式 - 环境变量和DNS。

环境变量

当一个Pod运行在一个Node上的时候,kubelet会为每一个活跃的Service添加一组环境变量。它同时支持Docker links compatible变量和简单的如{SVCNAME}_SERVICE_HOST{SVCNAME}_SERVICE_PORT一样的变量,在后边的这些变量名中服务的名称是大写的,然后横线(-)会被转换成下划线(_)。

例如,服务"redis-master"开放了6379的TCP端口,并且被分配了10.0.0.11的集群IP地址,那么生成的变量如下:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

这确实隐含了一个对顺序的要求 - 任何一个Pod想要访问的Service必须在Pod自身被创建之前被创建好,否则环境变量无法被设置到这个Pod中去。DNS没有这种限制。

DNS

一个可选的(也是我们强烈建议的)cluster add-on是一个DNS服务器。DNS会监控Kubernetes的新的Service并且会为其创建一组DNS记录。如果DNS在整个集群中都被启用的话,那所有的Pod应该能自动对Service进行命名解析。

例如,如果你有一个叫做my-serviceService,其位于"my-ns"Namespace下,那么对于"my-service.my-ns"就会有一个DNS的记录被创建。位于名为"my-ns" Namespace下的Pod可以通过简单地使用"my-service"进行查找。而位于其他NamespacesPod必须把查找名称指定为"my-service.my-ns"。这些命名查找的结果是一个集群的IP地址。

对于命名的端口,Kubernetes也支持DNS SRV (service)记录。如果名为"my-service.my-ns"Service 有一个协议为TCP的名叫"http"的端口,你可以对"_http._tcp.my-service.my-ns"做一次DNS SRV查询来发现”http”的端口号。

Headless services

有时你不需要一个单独的服务IP地址,也不需要做负载均衡。在这种情况下,你可以创建一个”headless”的Service,只需要把集群IP(spec.clusterIP)指定为"None"即可。

对于这种类型的Service,没有集群IP地址的分配。也不会有DNS的配置来为一个Service名称返回多个A记录,这些记录会直接指向支撑这个ServicePod。另外,kube-proxy不会处理这些service,并且平台不会为它们做负载均衡或者代理。endpoints controller仍然会在API中创建Endpoints的记录。

这个选项让开发者可以减少对Kubernetes系统的耦合度,在他们想要的时候,能让他们自由决定如何用自己的方式去发现这些服务。应用仍然使用一个自注册的模式,并且其它服务发现的系统的适配器可以轻易的基于这个API被构建出来。

For such Services a cluster IP is not allocated, the kube proxy does not handle these services, and there is no load balancing or proxying done by the platform for them. How DNS is automatically configured depends on if the service has selectors or not.

With selectors

For headless services that define selectors, the endpoints controller creates Endpoints records in the API, and modifies the DNS configuration to return A records (addresses) which point directly to the Pods backing the Service.

Without selectors

For headless services that do not define selectors, the endpoints controller does not create Endpoints records. However, the DNS system looks for and configures A records for any Endpoints that share a name with the service. ««<

发布 services - service的类型

Kubernetes的ServiceTypes能让你指定你想要哪一种服务。默认的和基础的是ClusterIP,这会开放一个服务可以在集群内部进行连接。NodePortLoadBalancer是两种会将服务开放给外部网络的类型。

ServiceType字段的合法值是:

在使用一个集群内部IP地址和在NodePort上开放一个Service的基础上,还可以向云提供者申请一个负载均衡器,将流量转发到已经以NodePort形式开发的Service上。

注意尽管NodePort可以是TCP或者UDP的,对于Kubernetes 1.0来说,LoadBalancer还支持TCP。

NodePort类型

如果你把type字段设置为"NodePort",Kubernetes的master就会从由启动参数配置的范围(默认是:30000-32767)中分配一个端口,然后每一个Node都会将这个端口(在每一个Node上相同的端口)代理到你的Service。这个端口会被写入你的Service的spec.ports[*].nodePort字段中。

如果你想要一个特定的端口号,你可以在nodePort字段中指定一个值,确保系统能为你分配这个端口,否则API请求将会失败(例如你需要自己处理可能出现的端口冲突)。你指定的值必须在节点端口配置的范围内。

这给了开发者了设置他们自己的负载均衡器的自由,配置那些没有被Kubernetes完全支持的云环境,或者甚至可以直接开放一个或者多个节点的IP。

注意这种Service可以同时以<NodeIP>:spec.ports[*].nodePortspec.clusterIp:spec.ports[*].port的形式访问。

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,但是云提供商不支持这个特性,这个字段会被忽略。

SSL support on AWS

For partial SSL support on clusters running on AWS, starting with 1.3 two annotations can be added to a LoadBalancer service:

    "metadata": {
        "name": "my-service",
        "annotations": {
            "service.beta.kubernetes.io/aws-load-balancer-ssl-cert": "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
        }
    },

The first specifies which certificate to use. It can be either a certificate from a third party issuer that was uploaded to IAM or one created within AWS Certificate Manager.

    "metadata": {
        "name": "my-service",
        "annotations": {
            "service.beta.kubernetes.io/aws-load-balancer-backend-protocol=": "(https|http|ssl|tcp)"
        }
    },

The second annotation specificies which protocol a pod speaks. For HTTPS and SSL, the ELB will expect the pod to authenticate itself over the encrypted connection.

HTTP and HTTPS will select layer 7 proxying: the ELB will terminate the connection with the user, parse headers and inject the X-Forwarded-For header with the user’s IP address (pods will only see the IP address of the ELB at the other end of its connection) when forwarding requests.

TCP and SSL will select layer 4 proxying: the ELB will forward traffic without modifying the headers.

外部IP

如果有外部的IP路由到一个或者更多的集群节点,Kubernetes的服务可以在这些externalIPs(外部端口)上开放出来。那些通过外部IP(作为目的IP)进入到集群的的流量,且是在服务的端口上,将会被路由到服务的一个endpoint上。externalIPs不是由Kubernetes管理的,这个责任属于集群的管理者。

在ServiceSpec(服务的定义)中,externalIPs可以伴随任意的ServiceTypes指定。在下面的例子中,my-service可以被客户端通过80.11.12.10:80(externalIP:port)访问到。

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "my-service"
    },
    "spec": {
        "selector": {
            "app": "MyApp"
        },
        "ports": [
            {
                "name": "http",
                "protocol": "TCP",
                "port": 80,
                "targetPort": 9376
            }
        ],
        "externalIPs" : [
            "80.11.12.10"
        ]
    }
}

缺点

我们认为使用iptables和userspace来处理虚拟IP能应付小或者中等的规模,但是可能对于那些有成千的服务的非常大的集群来说可能不行。更多细节请参见the original design proposal for portals

使用kube-proxy会混淆访问一个Service的包的来源IP。这让一些类型的防火墙规则无法实现。

负载均衡器只支持TCP,不支持UDP。

Type字段被设计成一种嵌套的功能 - 每一个层级都会像前一个进行添加。不是所有的云提供商都这样严格要求(如Google Compute Engine不需要分配NodePort就能让LoadBalancer工作,但是AWS需要),但对于目前的API来说是必需的。

未来的工作

在未来我们可以展望代理策略会更加细致而不只是简单的轮询式的负载均衡,例如选主式的或者分片式的。我们也能预见一些Service将会有真正的负载均衡器,这样VIP仅仅是将数据包运送到此。

有一个提案 提出取消用户空间的代理,然后全部用iptables取而代之。这样性能会更好,而且也会消除来源IP不明的问题,尽管这较之随意的用户空间代码来说灵活性下降了。

我们想添加L7(HTTP)的Service作为作为首选的支持。

我们想有更多灵活的Service入口模式,包含目前的ClusterIPNodePortLoadBalancer和更多其他模式。

虚拟IP残酷的细节

之前的信息对于很多只想使用Service的人来说应该足够了。然而,背后还有很多细节值得理解。

防止冲突

Kubernetes的一个主要哲学是用户不应该被暴露在可能因为非自身原因而导致行为失败的环境中。这这种情形之下,我们要说说网络的端口 - 用户不应该选择一个可能会与其他用户冲突的端口号。这是一个隔离性的错误。

为了让用户为自己的Service选择一个端口,我们必须确保任意两个服务的端口不会发生冲突。我们通过给每一个Service分配自己的IP地址来完成这一点。

为了保证每一个Service都能获得一个唯一的IP,一个内部的分配器会自动更新一个etcd中的全局分配表(map)。这个map对象必须存在于注册表中才能让sercvie获得IP,否则创建就会失败并且提示不能分配IP。一个后台控制器负责创建这个map(从老版本的Kubernetes在内存中保存的方式迁移)同时检查因为管理员的干预而出现的无效的分配,并且清理任何已经分配的且没有任何Service正在使用的IP。

IPs and VIPs

Unlike Pod IP addresses, which actually route to a fixed destination, Service IPs are not actually answered by a single host. Instead, we use iptables (packet processing logic in Linux) to define virtual IP addresses which are transparently redirected as needed. When clients connect to the VIP, their traffic is automatically transported to an appropriate endpoint. The environment variables and DNS for Services are actually populated in terms of the Service’s VIP and port.

We support two proxy modes - userspace and iptables, which operate slightly differently.

Userspace

As an example, consider the image processing application described above. When the backend Service is created, the Kubernetes master assigns a virtual IP address, for example 10.0.0.1. Assuming the Service port is 1234, the Service is observed by all of the kube-proxy instances in the cluster. When a proxy sees a new Service, it opens a new random port, establishes an iptables redirect from the VIP to this new port, and starts accepting connections on it.

When a client connects to the VIP the iptables rule kicks in, and redirects the packets to the Service proxy’s own port. The Service proxy chooses a backend, and starts proxying traffic from the client to the backend.

This means that Service owners can choose any port they want without risk of collision. Clients can simply connect to an IP and port, without being aware of which Pods they are actually accessing.

Iptables

Again, consider the image processing application described above. When the backend Service is created, the Kubernetes master assigns a virtual IP address, for example 10.0.0.1. Assuming the Service port is 1234, the Service is observed by all of the kube-proxy instances in the cluster. When a proxy sees a new Service, it installs a series of iptables rules which redirect from the VIP to per-Service rules. The per-Service rules link to per-Endpoint rules which redirect (Destination NAT) to the backends.

When a client connects to the VIP the iptables rule kicks in. A backend is chosen (either based on session affinity or randomly) and packets are redirected to the backend. Unlike the userspace proxy, packets are never copied to userspace, the kube-proxy does not have to be running for the VIP to work, and the client IP is not altered.

This same basic flow executes when traffic comes in through a node-port or through a load-balancer, though in those cases the client IP does get altered.

API对象

在Kubernetes中的REST API中,Service是一个顶层的资源。更多细节可以参见Service API object

For More Information

Read Service Operations.

Analytics