服务Service和负载均衡

Kubernetes中为了实现服务实例间的负载均衡和不同服务间的服务发现,创造了Serivce对象,同时又为从集群外部访问集群创建了Ingress对象。

一. Service

一种将在一组pod上运行的应用程序公开为网络服务的抽象方法。

使用Kubernetes,您不需要修改应用程序,就可以使用不熟悉的服务发现机制。Kubernetes为Pod提供了自己的IP地址和一组Pod的单个DNS名称,并且可以在它们之间实现负载平衡。

1.1 动机(Motivation)

Kubernetes Pods 是有生命周期的。他们可以被创建,而且销毁不会再启动。 如果您使用 Deployment 来运行您的应用程序,则它可以动态创建和销毁 Pod。

每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。

这导致了一个问题: 如果一组 Pod(称为“后端”)为群集内的其他 Pod(称为“前端”)提供功能,那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用工作量的后端部分?

答案就是使用Service

1.2 Service 资源

Kubernetes Service 定义了这样一种抽象:逻辑上的一组 Pod,一种可以访问它们的策略,通常称为微服务。 这一组 Pod 能够被 Service 访问到,通常是通过 selector (也可能需要没有 selector 的 Service)实现的。

举个例子,考虑一个图片处理 backend,它运行了3个副本。这些副本是可互换的。frontend 不需要关心它们调用了哪个 backend 副本。 然而组成这一组 backend 程序的 Pod 实际上可能会发生变化,frontend 客户端不应该也没必要知道,而且也不需要跟踪这一组 backend 的状态。 Service 定义的抽象能够解耦这种关联。

云原生服务发现

如果您想要在应用程序中使用 Kubernetes 接口进行服务发现,则可以查询 API server 的 endpoint 资源,只要服务中的Pod集合发生更改,端点就会更新。

对于非本机应用程序,Kubernetes提供了在应用程序和后端Pod之间放置网络端口或负载均衡器的方法。

1.2 定义 Service

一个 Service 在 Kubernetes 中是一个 REST 对象,和 Pod 类似。 像所有的 REST 对象一样, Service 定义可以基于 POST 方式,请求 API server 创建新的实例。

例如,假定有一组 Pod,它们对外暴露了 9376 端口,同时还被打上 app=MyApp 标签。

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

上述配置创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 “app=MyApp” 的 Pod 上。 Kubernetes 为该服务分配一个 IP 地址(有时称为 “集群IP” ),该 IP 地址由服务代理使用。服务选择器的控制器不断扫描与其选择器匹配的 Pod,然后将所有更新发布到也称为 “my-service” 的Endpoint对象。

注意:需要注意的是,Service能够将一个接收port映射到任意的targetPort。 默认情况下,targetPort将被设置为与port字段相同的值。

Pods中的端口定义有名称,您可以在服务的targetPort属性中引用这些名称。即使服务中混合了使用单个配置名称的pod,并且通过不同的端口号提供相同的网络协议,这种方法仍然有效。这为部署和改进服务提供了很大的灵活性。例如,您可以更改Pods在下一个版本的后端软件中公开的端口号,而不破坏客户端。

服务的默认协议是TCP。 还可以使用任何其他受支持的协议。

由于许多服务需要公开多个端口,因此 Kubernetes 在服务对象上支持多个端口定义。 每个端口定义可以具有相同的 protocol,也可以具有不同的协议。

1.3 没有selector的Service

服务最常见的是抽象化对Kubernetes Pod的访问,但是它们也可以抽象化其他种类的后端。

  • 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
  • 希望服务指向另一个命名空间中或其它集群中的服务。
  • 您正在将工作负载迁移到Kubernetes。在评估该方法时,您仅在Kubernetes中运行一部分后端。

在任何这些场景中,都能够定义没有selector的Service。实例:

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

由于此服务没有选择器,因此不会自动创建相应的Endpoint对象。您可以通过手动添加Endpoint对象,将服务手动映射到运行该服务的网络地址和端口:

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

注意:端点IP地址不能是其他Kubernetes服务的群集IP,因为kube-proxy不支持将虚拟IP作为目标。

访问没有selector的Service,与有selector的Service的原理相同。请求将被路由到用户定义的Endpoint,YAML中为: 192.0.2.42:9376 (TCP)。

1.4 VIP 和 Service代理

在Kubernetes集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为 Service实现了一种VIP(虚拟 IP)的形式。

User space 代理模式 (User space proxy mode)

这种模式,kube-proxy 会监视 Kubernetes master对Service对象和Endpoints对象的添加和移除。 对每个Service,它会在本地Node上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被代理到Service的backend Pods 中的某个上面(如 Endpoints 所报告的一样)。使用哪个backend Pod,是 kube-proxy基于SessionAffinity来确定的。

最后,它安装iptables规则,捕获到达该Service的clusterIP(是虚拟 IP)和Port的请求,并重定向到代理端口,代理端口再代理请求到backend Pod。

默认情况下,用户空间模式下的kube-proxy通过循环算法选择后端。

默认的策略是,通过round-robin 算法来选择backend Pod。

iptables 代理模式(iptables proxy mode)

这种模式,kube-proxy会监视Kubernetes控制节点对Service对象和Endpoints对象的添加和移除。对每个Service,它会安装iptables规则,从而捕获到达该Service的clusterIP和端口的请求,进而将请求重定向到Service的一组 backend中的某个上面。对于每个Endpoints对象,它也会安装iptables规则,这个规则会选择一个backend组合。

默认的策略是,kube-proxy在iptables模式下随机选择一个backend。

使用iptables处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理,而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。

如果kube-proxy在iptables模式下运行,并且所选的第一个Pod没有响应,则连接失败。 这与用户空间模式不同:在这种情况下,kube-proxy将检测到与第一个Pod的连接已失败,并会自动使用其他后端Pod重试。

您可以使用Pod readiness探测器验证后端Pod可以正常工作,以便iptables 模式下的kube-proxy仅看到测试正常的后端。这样做意味着您避免将流量通过kube-proxy发送到已知已失败的Pod。

IPVS 代理模式(IPVS proxy mode)

在ipvs模式下,kube-proxy监视Kubernetes服务和端点,调用netlink接口相应地创建IPVS规则,并定期将IPVS规则与Kubernetes服务和端点同步。该控制循环可确保IPVS状态与所需状态匹配。访问服务时,IPVS将流量定向到后端Pod之一。

IPVS代理模式基于类似于iptables模式的netfilter挂钩函数,但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与iptables模式下的kube-proxy相比,IPVS模式下的kube-proxy重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。与其他代理模式相比,IPVS模式还支持更高的网络流量吞吐量。

IPVS提供了更多选项来平衡后端Pod的流量。 这些是:

  • rr: round-robin
  • lc: least connection (smallest number of open connections)
  • dh: destination hashing
  • sh: source hashing
  • sed: shortest expected delay
  • nq: never queue

注意

  • 要在IPVS模式下运行kube-proxy,必须在启动kube-proxy之前使IPVS Linux在节点上可用。
  • 当kube-proxy以IPVS代理模式启动时,它将验证IPVS内核模块是否可用。 如果未检测到IPVS内核模块,则kube-proxy将退回到以iptables代理模式运行。

在这些代理模型中,绑定到服务IP的流量:在客户端不了解Kubernetes或服务或Pod的任何信息的情况下,将Port代理到适当的后端。如果要确保每次都将来自特定客户端的连接传递到同一Pod,则可以通过将 service.spec.sessionAffinity设置为 “ClientIP” (默认值是 “None”),来基于客户端的IP地址选择会话关联。

您还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 来设置最大会话停留时间。(默认值为10800秒,即3小时)。

1.5 多端口 Service

对于某些服务,您需要公开多个端口。Kubernetes允许您在Service对象上配置多个端口定义。为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。例如:

apiVersion: v1
kind: Service
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名称一样,端口名称只能包含小写字母数字字符 和“-”。端口名称还必须以字母数字字符开头和结尾。

例如,名称123-abc和web有效,但是123_abc和-web无效。

1.6 选择自己的IP地址

在 Service 创建的请求中,可以通过设置spec.clusterIP 字段来指定自己的集群IP地址。比如,希望替换一个已经已存在的DNS条目,或者遗留系统已经配置了一个固定的IP且很难重新配置。

用户选择的IP地址必须合法,并且这个IP地址在service-cluster-ip-range CIDR 范围内,这对API Server来说是通过一个标识来指定的。如果IP地址不合法,API Server 会返回HTTP状态码422,表示值不合法。

1.7 服务发现(Discovering services)

Kubernetes支持2种基本的服务发现模式:环境变量和DNS。

环境变量

当Pod运行在Node上,kubelet会为每个活跃的Service添加一组环境变量。 它同时支持Docker links兼容变量(查看 makeLinkVariables)、简单的 {SVCNAME}_SERVICE_HOST和{SVCNAME}_SERVICE_PORT变量,这里Service的名称需大写,横线被转换成下划线。

举个例子,一个名称为"redis-master"的Service暴露了TCP端口6379,同时给它分配了Cluster IP地址10.0.0.11,这个Service生成了如下环境变量:

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时,并且您正在使用环境变量方法将端口和群集IP发布到客户端Pod时,必须在客户端Pod出现之前创建服务。否则,这些客户端Pod将不会设定其环境变量。

如果仅使用DNS查找服务的群集IP,则无需担心此设定问题。

DNS

可以(几乎总是应该)使用附加组件为Kubernetes集群设置DNS服务。

支持群集的DNS服务器(例如CoreDNS)监视Kubernetes API中的新服务,并为每个服务创建一组DNS记录。如果在整个群集中都启用了DNS,则所有Pod都应该能够通过其DNS名称自动解析服务。

例如,如果您在Kubernetes命名空间"my-ns"中有一个名为"my-service" 的服务,则控制平面和DNS服务共同为"my-service.my-ns"创建DNS记录。“my-ns"命名空间中的Pod应该能够通过简单地对my-service进行名称查找来找到它( “my-service.my-ns” 也可以工作)。

其他命名空间中的Pod必须将名称限定为my-service.my-ns。这些名称将解析为为服务分配的群集IP。

Kubernetes还支持命名端口的DNS SRV(服务)记录。如果"my-service.my-ns” 服务具有名为"http"的端口,且协议设置为TCP,则可以对_http._tcp.my-service.my-ns执行DNS SRV查询查询以发现该"http"的端口号以及IP地址。

Kubernetes DNS服务器是唯一的一种能够访问ExternalName类型的Service 的方式。

1.8 Headless Services

有时不需要或不想要负载均衡,以及单独的Service IP。 遇到这种情况,可以通过指定Cluster IP(spec.clusterIP)的值为"None"来创建Headless Service。

您可以使用headless Service与其他服务发现机制进行接口,而不必与Kubernetes的实现捆绑在一起。

headless Service并不会分配Cluster IP,kube-proxy不会处理它们,而且平台也不会为它们进行负载均衡和路由。 DNS如何实现自动配置,依赖于Service是否定义了selector。

配置Selector

对定义了selector的Headless Service,Endpoint控制器在API中创建了Endpoints记录,并且修改DNS配置返回A记录(地址),通过这个地址直接到达Service的后端Pod上。

不配置Selector

对没有定义selector的Headless Service,Endpoint控制器不会创建Endpoints记录。

1.9 发布服务(Publishing Services)

如何将服务暴露到集群外部,被集群外部的地址访问到呢?

Kubernetes ServiceTypes允许指定一个你需要的服务类型来完成以上需求,默认是ClusterIP类型。

  • ClusterIP:通过集群的内部IP暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
  • NodePort:通过每个Node上的IP和静态端口(NodePort)暴露服务。NodePort服务会路由到ClusterIP服务,这个ClusterIP服务会自动创建。通过请求 :,可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort服务和ClusterIP 服务。
  • ExternalName:通过返回CNAME和它的值,可以将服务映射到externalName字段的内容(例如,foo.bar.example.com )。没有任何类型代理被创建。

注意: 您需要CoreDNS 1.7或更高版本才能使用ExternalName类型。

也可以使用Ingress来暴露自己的服务。Ingress不是服务类型,但它充当集群的入口点。它可以将路由规则整合到一个资源中,因为它可以在同一IP地址下公开多个服务。

NodePort 类型

如果将type字段设置为NodePort,则Kubernetes控制平面将在--service-node-port-range标志指定的范围内分配端口(默认值:30000-32767)。 每个节点将那个端口(每个节点上的相同端口号)代理到您的服务中。 您的服务在其.spec.ports[*].nodePort字段中要求分配的端口。

从Kubernetes v1.10开始,可以指定特定的IP代理端口,可以将kube-proxy 中的--nodeport-addresses标志设置为特定的IP块。该标志采用逗号分隔的IP块列表(例如10.0.0.0/8、192.0.2.0/25)来指定kube-proxy认为是此节点本地的IP地址范围。

如果需要特定的端口号,则可以在nodePort字段中指定一个值。需要注意端口冲突,还必须使用有效的端口号,该端口号在配置用于NodePort的范围内。

LoadBalancer 类型

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

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

来自外部负载均衡器的流量将直接打到backend Pod上,不过实际它们是如何工作的,这要依赖于云提供商。

在这些情况下,将根据用户设置的loadBalancerIP来创建负载均衡器。某些云提供商允许设置loadBalancerIP。如果没有设置loadBalancerIP,将会给负载均衡器指派一个临时IP。如果设置了loadBalancerIP,但云提供商并不支持这种特性,那么设置的loadBalancerIP值将会被忽略掉。

二. Pod 与 Service 的DNS

Kubernetes DNS在群集上调度DNS Pod和服务,并配置kubelet以告知各个容器使用DNS服务的IP来解析DNS名称。

2.1 怎样获取DNS名字?

在集群中定义的每个Service(包括DNS服务器自身)都会被指派一个DNS名称。默认,一个客户端Pod的DNS搜索列表将包含该Pod自己的Namespace和集群默认域。

假设在Kubernetes集群的Namespace bar中,定义了一个Service foo。 运行在Namespace bar中的一个Pod,可以简单地通过DNS查询foo来找到该Service。 运行在Namespace quux中的一个Pod可以通过DNS查询foo.bar找到该 Service。

2.2 Services

A 记录

“正常” Service(除了 Headless Service)会以 my-svc.my-namespace.svc.cluster-domain.example 这种名字的形式被指派一个DNS A记录。这会解析成该Service的Cluster IP。

“Headless” Service(没有Cluster IP)也会以my-svc.my-namespace.svc.cluster-domain.example这种名字的形式被指派一个DNS A记录。不像正常Service,它会解析成该Service选择的一组Pod 的IP。希望客户端能够使用这一组IP,否则就使用标准的round-robin策略从这一组IP中进行选择。

SRV 记录

命名端口需要创建SRV记录,这些端口是正常Service或Headless Services的一部分。对每个命名端口,SRV记录具有_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example这种形式。对普通Service,这会被解析成端口号和CNAME:my-svc.my-namespace.svc.cluster-domain.example。 对Headless Service,这会被解析成多个结果,Service对应的每个backend Pod各一个,包含auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example这种形式Pod的端口号和CNAME。

Pods

Pod的hostname和subdomain字段

当前,创建Pod后,它的主机名是该Pod的metadata.name值。

PodSpec有一个可选的hostname字段,可以用来指定Pod的主机名。当这个字段被设置时,它将优先于Pod的名字成为该Pod的主机名。举个例子,给定一个hostname设置为 “my-host”的Pod,该Pod的主机名将被设置为“my-host“。

PodSpec还有一个可选subdomain字段,可以用来指定Pod的子域名。举个例子,一个Pod的hostname设置为“foo”,subdomain设置为“bar”,在namespace “my-namespace” 中对应的完全限定域名(FQDN)为“foo.bar.my-namespace.svc.cluster-domain.example”。

apiVersion: v1
kind: Service
metadata:
  name: default-subdomain
spec:
  selector:
    name: busybox
  clusterIP: None
  ports:
  - name: foo # Actually, no port is needed.
    port: 1234
    targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox1
  labels:
    name: busybox
spec:
  hostname: busybox-1
  subdomain: default-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox2
  labels:
    name: busybox
spec:
  hostname: busybox-2
  subdomain: default-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox

如果Headless Service与Pod在同一个Namespace中,它们具有相同的子域名,集群的KubeDNS服务器也会为该Pod的完整合法主机名返回A记录。例如,在同一个Namespace中,给定一个主机名为“busybox-1”的Pod,子域名设置为“default-subdomain”,名称为“default-subdomain”的Headless Service,Pod 将看到自己的FQDN为“busybox-1.default-subdomain.my-namespace.svc.cluster.local”。DNS会为那个名字提供一个A记录,指向该Pod的IP。“busybox1”和“busybox2” 这两个Pod分别具有它们自己的A记录。

端点对象可以为任何端点地址及其IP指定hostname。

Pod 的 DNS 策略

DNS策略可以基于每个pod设置。目前,Kubernetes支持以下特定于pod的DNS策略。这些策略在Pod规范的dnsPolicy字段中指定。

  • Default:Pod从Pod运行所在的节点继承名称解析配置。
  • ClusterFirst:任何与配置的群集域后缀不匹配的DNS查询(例如“ www.kubernetes.io”)都将转发到从节点继承的上游名称服务器。
  • ClusterFirstWithHostNet:对于与hostNetwork一起运行的Pod,您应明确设置其DNS策略为ClusterFirstWithHostNet
  • None:它允许Pod忽略Kubernetes环境中的DNS设置。应该使用Pod Spec中的dnsConfig字段提供所有DNS设置。

注意:“Default” 不是默认的DNS策略。如果未明确指定dnsPolicy,则使用“ClusterFirst”。

下面的示例显示了一个Pod,其DNS策略设置为ClusterFirstWithHostNet,因为它已将hostNetwork设置为true

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Always
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet

Pod 的 DNS 配置

Pod的DNS配置可让用户对Pod的DNS设置进行更多控制。

dnsConfig字段是可选的,它可以与任何dnsPolicy设置一起使用。 但是,当Pod的dnsPolicy设置为None时,必须指定dnsConfig字段。

用户可以在dnsConfig字段中指定以下属性:

  • nameservers: 用于指定Pod的DNS服务器的IP地址列表。最多可以指定3个IP地址。当Pod的dnsPolicy设置为None时,列表必须至少包含一个IP地址,否则此属性是可选的。列出的服务器将合并到从指定的DNS策略生成的基本名称服务器,并删除重复的地址。
  • searches: 用于在Pod中查找主机名的DNS搜索域的列表。此属性是可选的。指定后,提供的列表将合并到根据所选DNS策略生成的基本搜索域名中。重复的域名将被删除。Kubernetes最多允许6个搜索域。
  • options: 对象的可选列表,其中每个对象可能具有name属性(必需)和value属性(可选)。此属性中的内容将合并到从指定的DNS策略生成的选项。重复的条目将被删除。

以下是具有自定义DNS设置的Pod示例:

##service/networking/custom-dns.yaml 

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: dns-example
spec:
  containers:
    - name: test
      image: nginx
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 1.2.3.4
    searches:
      - ns1.svc.cluster-domain.example
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

创建上面的Pod后,容器test会在其/etc/resolv.conf文件中获取以下内容:

nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0

三. Ingress

Ingress是对集群中服务的外部访问进行管理的API对象,典型的访问方式是HTTP。

Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机。

3.1 术语(Terminology)

  • 节点(Node):Kubernetes集群中其中一台工作机器,是集群的一部分。
  • 集群(Cluster):运行由Kubernetes管理的容器化应用程序的一组节点。在大多数常见的Kubernetes部署中,集群中的节点不是公共internet的一部分。
  • 边缘路由器(Edge router):在集群中强制性执行防火墙策略的路由器(router)。可以是由云提供商管理的网关,也可以是物理硬件。
  • 集群网络(Cluster network):一组逻辑或物理的链接,根据Kubernetes 网络模型在集群内实现通信。
  • 服务(Service):Kubernetes Service使用标签选择器(selectors)标识的一组Pod。除非另有说明,否则假定服务只具有在集群网络中可路由的虚拟IP。

3.2 Ingress 是什么?

Ingress 公开了从集群外部到集群内services的HTTP和HTTPS路由。流量路由由Ingress资源上定义的规则控制。

    internet
        |
   [ Ingress ]
   --|-----|--
   [ Services ]

可以将Ingress配置为服务提供外部可访问的URL,负载均衡流量,终止SSL/TLS,并提供基于名称的虚拟主机。Ingress控制器通常负责通过负载平衡器来实现入口,尽管它也可以配置边缘路由器或其他前端以帮助处理流量。

Ingress不会公开任意端口或协议。将HTTP和HTTPS以外的服务公开给Internet时,通常使用以下类型的服务Service.Type=NodePort或者Service.Type=LoadBalancer。

3.3 Ingress 控制器(Ingress Controllers)

为了让Ingress资源工作,集群必须有一个正在运行的Ingress控制器。

与作为kube-controller-manager可执行文件的一部分运行的其他类型的控制器不同,Ingress控制器不是随集群自动启动的。您可以选择最适合您的集群的ingress控制器实现。

Kubernetes作为一个项目,目前支持和维护GCEnginx控制器。

下载ingress

[root@k8s-master ~]# wget -c https://github.com/kubernetes/ingress-nginx/archive/master.zip
[root@k8s-master ~]# unzip ingress-nginx-master.zip

安装

首先要更改镜像仓库位置,国外的不容易拉取
[root@k8s-master ~]# cd ingress-nginx-master/deploy/static/
[root@k8s-master static]# vim mandatory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
<-省略部分内容->
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: nginx-ingress-controller
          ##修改这里镜像的位置,改成阿里的仓库位置
          image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.30.0
          args:
进行安装
[root@k8s-master ~]# kubectl apply -f ingress-nginx-master/deploy/static/mandatory.yaml 
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created

##可以监控其安装过程
[root@k8s-master ~]# kubectl get pods --all-namespaces -l app.kubernetes.io/name=ingress-nginx --watch
NAMESPACE       NAME                                        READY   STATUS             RESTARTS   AGE
ingress-nginx   nginx-ingress-controller-7b86f6f9fc-tk5tt   0/1     ImagePullBackOff   0          2m14s

##安装成功后
[root@k8s-master ~]# kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-7b86f6f9fc-tk5tt   1/1     Running   0          8m21s

3.4 Ingress 资源(The Ingress Resource)

一个最小的Ingress资源示例:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          serviceName: test
          servicePort: 80

与所有其他Kubernetes资源一样,Ingress需要使用apiVersion、kind和metadata字段。Ingress经常使用注解(annotations)来配置一些选项,具体取决于Ingress控制器,例如rewrite-target annotation。不同的Ingress控制器支持不同的注解(annotations)。

Ingress规范具有配置负载均衡器或者代理服务器所需的所有信息。最重要的是,它包含与所有传入请求匹配的规则列表。Ingress资源仅支持用于定向HTTP流量的规则。

Ingress 规则(Ingress rules)

每个 HTTP 规则都包含以下信息:

  • 可选host:在此示例中,未指定host,因此该规则适用于通过指定IP地址的所有入站HTTP通信。如果提供了主机(例如foo.bar.com),则规则适用于该主机。
  • 路径列表(paths):(例如,/testpath),每个路径都有一个由serviceNameservicePort定义的关联后端。在负载均衡器将流量定向到引用的服务之前,主机和路径都必须匹配传入请求的内容。
  • 后端(backend):是服务文档中所述的服务和端口名称的组合。与规则的主机和路径匹配的对Ingress的HTTP(和HTTPS)请求将发送到列出的后。

通常在Ingress控制器中配置默认后端,以服务任何不符合规范中路径的请求。

默认后端(Default Backend)

没有规则的Ingress将所有流量发送到单个默认后端。默认后端通常是在Ingress控制器里的配置选项,而不在Ingress资源中指定。

如果没有主机或路径与Ingress对象中的HTTP请求匹配,则流量将路由到您的默认后端。

3.5 Ingress 类型(Types of Ingress)

单服务 Ingress(Single Service Ingress)

现有的Kubernetes允许您暴露单个Service,您也可以通过指定无规则的默认后端来对Ingress进行此操作。

##service/networking/ingress.yaml 

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-ingress
spec:
  backend:
    serviceName: testsvc
    servicePort: 80

如果使用kubectl apply -f创建它,则应该能够查看刚刚添加的Ingress的状态:

kubectl get ingress test-ingress
##类似输出
NAME           HOSTS     ADDRESS           PORTS     AGE
test-ingress   *         107.178.254.228   80        59s

##其中107.178.254.228是由Ingress控制器分配以满足该Ingress的IP。
##Ingress 控制器和负载均衡器可能需要一两分钟才能分配IP地址。
##在此之前,您通常会看到地址为 <pending>

简单扇形分叉(Simple fanout)

基于所请求的HTTP URI将流量从单个IP地址路由到多个服务。入口允许您将负载平衡器的数量保持在最小值。例如:

foo.bar.com -> 178.91.123.132 -> / foo    service1:4200
                                 / bar    service2:8080

需要一个Ingress,例如:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: simple-fanout-example
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: service1
          servicePort: 4200
      - path: /bar
        backend:
          serviceName: service2
          servicePort: 8080

使用kubectl apply -f创建后:

kubectl describe ingress simple-fanout-example

##类似输出:
Name:             simple-fanout-example
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:4200 (10.8.0.90:4200)
               /bar   service2:8080 (10.8.0.91:8080)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     22s                loadbalancer-controller  default/test

Ingress控制器将提供实现特定的负载均衡器来满足Ingress,只要Service(s1,s2) 存在。成功创建后,会在地址栏看到负载均衡器的地址。

基于名称的虚拟主机(Name based virtual hosting)

基于名称的虚拟主机支持将HTTP流量路由到同一IP地址上的多个主机名。

foo.bar.com --|                 |-> foo.bar.com service1:80
              | 178.91.123.132  |
bar.foo.com --|                 |-> bar.foo.com service2:80

以下Ingress让后台负载均衡器基于主机header路由请求。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: service1
          servicePort: 80
  - host: bar.foo.com
    http:
      paths:
      - backend:
          serviceName: service2
          servicePort: 80

如果您创建的Ingress资源没有规则中定义的任何主机名,则可以匹配到您Ingress控制器IP地址的任何网络流量,而无需基于名称的虚拟主机。

例如,以下Ingress资源会将first.bar.com请求的流量路由到service1,将second.foo.com请求的流量路由到service2,而没有在请求中定义主机名的IP地址的流量路由到service3。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: first.bar.com
    http:
      paths:
      - backend:
          serviceName: service1
          servicePort: 80
  - host: second.foo.com
    http:
      paths:
      - backend:
          serviceName: service2
          servicePort: 80
  - http:
      paths:
      - backend:
          serviceName: service3
          servicePort: 80

TLS

可以通过指定包含TLS私钥和证书的Secret来加密Ingress。目前,Ingress 只支持单个TLS端口443,并假设TLS终结于此。

如果Ingress中的TLS配置部分指定了不同的主机,那么它们将根据通过SNI TLS扩展指定的主机名(如果Ingress控制器支持SNI)在同一端口上进行复用。 TLS Secret必须包含名为tls.crt和tls.key的密钥,这些密钥包含用于TLS的证书和私钥,例如:

apiVersion: v1
kind: Secret
metadata:
  name: testsecret-tls
  namespace: default
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
type: kubernetes.io/tls

Ingress中引用此Secret将会告诉Ingress 控制器使用TLS加密从客户端到负载均衡器的通道。您需要确保创建的TLS secret包含域名为sslexample.foo.com的CN证书。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  tls:
  - hosts:
    - sslexample.foo.com
    secretName: testsecret-tls
  rules:
    - host: sslexample.foo.com
      http:
        paths:
        - path: /
          backend:
            serviceName: service1
            servicePort: 80

注意:各种Ingress控制器所支持的TLS功能之间存在差异。

3.6 更新 Ingress

要更新现有的Ingress以添加新的Host,可以通过编辑资源来对其进行更新:

# kubectl describe ingress test

##类似如下输出:
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:80 (10.8.0.90:80)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     35s                loadbalancer-controller  default/test

执行以下命令:

#kubectl edit ingress test

##弹出具有YAML格式的现有配置的编辑器,修改它来增加新的主机:
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: service1
          servicePort: 80
        path: /foo
  - host: bar.baz.com
    http:
      paths:
      - backend:
          serviceName: service2
          servicePort: 80
        path: /foo
..

保存更改后,kubectl将更新API服务器中的资源,该资源将告诉Ingress控制器重新配置负载均衡器。

#kubectl describe ingress test

##得到类似如下输出:
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:80 (10.8.0.90:80)
  bar.baz.com
               /foo   service2:80 (10.8.0.91:80)
Annotations:
  nginx.ingress.kubernetes.io/rewrite-target:  /
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     45s                loadbalancer-controller  default/test

也可以通过kubectl replace -f命令调用修改后的Ingress yaml文件来获得同样的结果。

四. Ingress案例

4.1 安装Ingress控制器 ingress-nginx

步骤略:前面已经安装过,确保其已经运行

[root@k8s-master ~]# kubectl get pods -n ingress-nginx -o wide
NAME                                        READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
nginx-ingress-controller-7b86f6f9fc-tk5tt   1/1     Running   1          22h   10.244.2.19   k8s-node2   <none>           <none>

4.2 创建匹配nginx-ingress Pod的Service

ingress-nginx控制器实际上就是一个Nginx Pod,里面就是一个nginx反向代理服务器,来做为负载平衡器的功能。因为其只是一个Pod,所以在集群外面访问不方便,需要创建一个service,以便可以方便访问。

[root@k8s-master ~]# cd ingress-nginx-master/deploy/static/provider/baremetal/
[root@k8s-master baremetal]# ls
service-nodeport.yaml

修改service-nodeport.yaml文件,固定其Nodeport。

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 30080   ##添加的固定端口号
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      nodePort: 30443  ##添加的固定端口号
      protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

创建这个service

[root@k8s-master k8s-yaml]# kubectl apply -f service-nodeport.yaml 
service/ingress-nginx created
[root@k8s-master k8s-yaml]# kubectl get svc -n ingress-nginx
NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   10.1.154.111   <none>        80:30080/TCP,443:30443/TCP   26s

现在如果访问这个service的话,因为还没有后端服务,所以会报404错误

[root@k8s-master k8s-yaml]# curl http://192.168.154.220:30080
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
[root@k8s-master k8s-yaml]# curl -k https://192.168.154.220:30443
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>

4.3 创建一个Nginx后端服务

apiVersion: v1
kind: Service
metadata:
  name: my-service
  labels:
    app: my-service
spec:
  selector:
    app: my-service
  ports:
  - name: http
    port: 80
    targetPort: 80
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: my-service
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
    spec:
      containers:
      - name: my-nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

执行这个yaml文件

[root@k8s-master ingress]# kubectl apply -f my-service.yaml 
service/my-service created
daemonset.apps/my-service created
[root@k8s-master ingress]# kubectl get svc 
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.1.0.1      <none>        443/TCP   20d
my-service   ClusterIP   10.1.251.21   <none>        80/TCP    14s

4.4 创建ingress资源

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-test
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: www.shengzhe.com   ##访问的域名
    http:
      paths:
      - path: /app1
        backend:
          serviceName: my-service
          servicePort: 80

执行yaml文件

[root@k8s-master ingress]# kubectl apply -f ingress-test.yaml 
ingress.networking.k8s.io/ingress-test created
[root@k8s-master ingress]# kubectl get ingress
NAME           HOSTS              ADDRESS        PORTS   AGE
ingress-test   www.shengzhe.com   10.1.154.111   80      19s

4.5 访问服务

[root@k8s-master ~]# curl -H "Host: www.shengzhe.com" http://192.168.154.220:30080/app1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

客户端使用浏览器访问: