集群中的服务要想向外提供服务,就不得不提到Service和Ingress。 下面我们就介绍一下两者的区别和关系。
Service
必须了解的一点是 Service 的访问信息在 Kubernetes 集群内是有效的,集群之外是无效的。
Service可以看作是一组提供相同服务的Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。对于Service 的工作原理请参考https://time.geekbang.org/column/article/68636
当需要从集群外部访问k8s里的服务的时候,方式有四种:ClusterIP、NodePort、LoadBalancer、ExternalName 。
下面我们介绍一下这几种方式的区别
一、ClusterIP
通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
我们先看一下最简单的Service定义
apiVersion: v1 kind: Service metadata: name: hostnames spec: selector: app: hostnames ports: - name: default protocol: TCP port: 80 targetPort: 9376
这里我使用了 selector 字段来声明这个 Service 只代理携带了 app=hostnames 标签的 Pod。并且这个 Service 的 80 端口,代理的是 Pod 的 9376 端口。
然后我们再看一下 Deployment 的定义
apiVersion: apps/v1 kind: Deployment metadata: name: hostnames spec: selector: matchLabels: app: hostnames replicas: 3 template: metadata: labels: app: hostnames spec: containers: - name: hostnames image: k8s.gcr.io/serve_hostname ports: - containerPort: 9376 protocol: TCP
我们这里声明的Pod份数是3份,使用的镜像为 k8s.gcr.io/serve_hostname,主要提供输出hostname的功能,如果我们依次访问 curl 10.0.1.175:80 的话,会发现每次响应的内容,这是因为 Service 提供的是 Round Robin 方式的负载均衡。这个10.0.1.175是集群的IP地址,俗称VIP,是 Kubernetes 自动为 Service 分配的。对于这种方式,我们称为:ClusterIP 模式的 Service。
二、NodePort
通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 NodeIP:Port,可以从集群的外部访问一个 NodePort 服务。
apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: type: NodePort ports: - nodePort: 8080 targetPort: 80 protocol: TCP name: http - nodePort: 443 protocol: TCP name: https selector: run: my-nginx
Service描述文件:
spec.type 声明Service的type
spec.selector 字段确定这个Service将要使用哪些Label。在本例中,会管理所有具有 run: my-nginx 标签的Pod。
spec.ports.nodePort 表明此Service将会监听8080端口,并将所有监听到的请求转发给其管理的Pod。
spec.ports.targetPort 表明此Service监听到的8080端口的请求都会被转发给其管理的Pod的80端口。此字段可以省略,省略后其值会被设置为spec.ports.port的值。
如果你未指定 spec.ports.nodePort 的话,则系统会随机选择一个范围为 30000-32767的端口号使用。
这时要访问这个Service的话,只需要通过访问
<任何一台宿主机器的IP>:8080
这样可以访问到某一个被代理Pod的80端口。后端的Pod并不是一定在当前Node上,有可能你访问的Node1:8080,而后端对应的Pod是在Node2上。
可以看到这种方式很好理解,非常类似于平时我们使用docker部署容器应用后,将容器服务端口暴露出来宿主端口就可以了。
三、LoadBalancer
在NodePort基础上,Kubernetes可以请求底层云平台cloud provider 创建一个外部的负载均衡器,并将请求转发到每个Node作为后端,进行服务分发。
该模式需要底层云平台(例如GCE、亚马孙AWS)支持。
--- kind: Service apiVersion: v1 metadata: name: example-service spec: ports: - port: 8765 targetPort: 9376 selector: app: example type: LoadBalancer
创建这个服务后,系统会自动创建一个外部负载均衡器,其端口为8765, 并且把被代理的 地址添加到公有云的负载均衡当中。
四、ExternalName
创建一个dns别名指到service name上,主要是防止service name发生变化,要配合dns插件使用。
通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如 foo.bar.example.com)。
没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。
kind: Service apiVersion: v1 metadata: name: my-service spec: type: ExternalName externalName: my.database.example.com
指定了一个 externalName=my.database.example.com 的字段。而且你应该会注意到,这个 YAML 文件里不需要指定 selector。
这时候,当你通过 Service 的 DNS 名字访问它的时候,比如访问:my-service.default.svc.cluster.local。
那么,Kubernetes 为你返回的就是my.database.example.com。所以说,ExternalName 类型的 Service,其实是在 kube-dns 里为你添加了一条 CNAME 记录。
这时访问 my-service.default.svc.cluster.local 就和访问 my.database.example.com 这个域名是一个效果了。
Ingress
上面我们提到有一个叫作 LoadBalancer 类型的 Service,它会为你在 Cloud Provider(比如:Google Cloud 或者 OpenStack)里创建一个与该 Service 对应的负载均衡服务。但是,相信你也应该能感受到,由于每个 Service 都要有一个负载均衡服务,所以这个做法实际上既浪费成本又高。作为用户,我其实更希望看到 Kubernetes 为我内置一个全局的负载均衡器。然后,通过我访问的 URL,把请求转发给不同的后端 Service。这种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的 Ingress 服务。
Ingress 的功能其实很容易理解:所谓 Ingress 就是 Service 的“Service”,这就是它们两者的关系。
internet
|
[ Ingress ]
--|-----|--
[ Services ]
通过使用 Kubernetes 的 Ingress 来创建一个统一的负载均衡器,从而实现当用户访问不同的域名时,访问后端不同的服务。
假如我现在有这样一个站点:https://cafe.example.com。其中 https://cafe.example.com/coffee,对应的是“咖啡点餐系统”。而 https://cafe.example.com/tea,对应的则是“茶水点餐系统”。这两个系统,分别由名叫 coffee 和 tea 这样两个 Deployment 来提供服务。
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: cafe-ingress spec: tls: - hosts: - cafe.example.com secretName: cafe-secret rules: - host: cafe.example.com http: paths: - path: /tea backend: serviceName: tea-svc servicePort: 80 - path: /coffee backend: serviceName: coffee-svc servicePort: 80
最值得我们关注的,是 rules 字段。在 Kubernetes 里,这个字段叫作:IngressRule。
IngressRule 的 Key,就叫做:host。它必须是一个标准的域名格式(Fully Qualified Domain Name)的字符串,而不能是 IP 地址。
而接下来 IngressRule 规则的定义,则依赖于 path 字段。你可以简单地理解为,这里的每一个 path 都对应一个后端 Service。
所以在我们的例子里,我定义了两个 path,它们分别对应 coffee 和 tea 这两个 Deployment 的 Service(即:coffee-svc 和 tea-svc)。
通过上面的介绍,不难看到所谓 Ingress 对象,其实就是 Kubernetes 项目对“反向代理”的一种抽象。
一个 Ingress 对象的主要内容,实际上就是一个“反向代理”服务(比如:Nginx)的配置文件的描述。而这个代理服务对应的转发规则,就是 IngressRule。
这就是为什么在每条 IngressRule 里,需要有一个 host 字段来作为这条 IngressRule 的入口,然后还需要有一系列 path 字段来声明具体的转发策略。这其实跟 Nginx、HAproxy 等项目的配置文件的写法是一致的。
在实际使用中,我们一般选择一种 Ingress Controller,将期部署在k8s集群中,这样它就会根据我们定义的 Ingress 对象来提供对应的代理功能。
业界常用的各种反向代理项目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。
Nginx Ingress Controller 的示例请参考 https://time.geekbang.org/column/article/69214
推荐参考官方推荐脚本:https://github.com/resouer/kubernetes-ingress/tree/master/examples/complete-example