使用NGINX Plus对Kubernetes服务进行负载均衡

NGINX | 2015年11月17日

Kubernetes 是由 Google 开发的开源系统,用于在集群中运行和管理基于容器化微服务的应用。Kubernetes 用户通常需要让其在 Kubernetes 中创建的服务可从 Kubernetes 集群外部访问。

虽然Kubernetes提供了用于公开服务的内置解决方案(详见下文的“使用内置解决方案公开Kubernetes服务”),但这些解决方案仅支持第4层负载均衡或轮询HTTP负载均衡。

本文介绍如何使用NGINX Plus作为高级第7层负载均衡解决方案,将Kubernetes服务公开到互联网,无论Kubernetes运行在云端还是自有基础设施上。

本文假设您已基本了解Kubernetes(Pod、服务、复制控制器和标签),并且已有一个正在运行的Kubernetes集群。要进一步了解Kubernetes,请参阅官方的《Kubernetes用户指南》。

使用内置解决方案公开Kubernetes服务

Kubernetes提供了多种公开服务的方式。其中,NodePort和LoadBalancer分别对应一种特定的服务类型。第3种方式Ingress API从Kubernetes 1.1版本开始以Beta版形式提供。

NodePort

将服务类型指定为NodePort后,即可通过每个Kubernetes节点上的同一端口访问该服务。要将服务公开到互联网,请在一个或多个节点上开放该端口。为实现高可用性,可以开放多个节点,并使用基于DNS的负载均衡在这些节点之间分配流量;也可以将这些节点置于所选的负载均衡器之后。

当入站流量通过该端口到达某个节点时,流量会在该服务的各个Pod之间进行负载均衡。每个节点上运行的Kubernetes网络代理(kube-proxy)仅支持TCP/UDP负载均衡。

LoadBalancer

将服务类型指定为LoadBalancer后,系统会分配一个云负载均衡器,将传入流量分发到该服务的各个Pod。

LoadBalancer解决方案仅受部分云提供商和Google Container Engine支持;如果在自有基础设施上运行Kubernetes,则无法使用该解决方案。此外,即使云负载均衡器具备会话持久性或请求映射等高级功能,Kubernetes也只允许配置轮询TCP负载均衡。

Ingress API

创建Ingress资源后,可以通过自定义URL将服务公开到互联网(例如,服务A使用URL /foo,服务B使用URL /bar),并使用多个虚拟主机名(例如,一组服务使用foo.example.com,另一组服务使用bar.example.com)。Ingress控制器会读取Ingress资源并设置外部负载均衡器。

Ingress控制器不属于标准Kubernetes部署的一部分:需要选择最符合需求的控制器或自行实现一个,并将其添加到Kubernetes集群中。预计很快会出现许多控制器实现,但目前唯一可用的实现是Google Compute Engine HTTP负载均衡器控制器,且仅适用于在Google Compute Engine或Google Container Engine上运行Kubernetes的情况。即使实际的负载均衡器支持高级功能,Ingress API也仅支持轮询HTTP负载均衡。

截至本文撰写时,Ingress API和Google Compute Engine HTTP Load Balancer控制器均处于测试阶段。

更新:适用于NGINX和NGINX Plus的NGINX Ingress Controller现已在我们的GitHub repository中提供。有关产品详情,请参阅NGINX Ingress Controller。

尽管上述解决方案设置简单且开箱即用,但并不提供任何高级功能,尤其是与第 7 层负载均衡相关的功能。

使用NGINX Plus公开Kubernetes服务

[编辑注:本节已更新为介绍NGINX Plus API。该API取代了原先在此讨论的独立动态配置模块,后者现已弃用。]

要将NGINX Plus与Kubernetes集成,需要确保NGINX Plus配置与Kubernetes保持同步,以反映Kubernetes服务的变化,例如添加或删除Pod。使用NGINX Open Source时,需要手动修改NGINX配置文件并重新加载配置。使用NGINX Plus时,可通过以下两种方式动态更新配置:

  • 使用API:此方法使用NGINX Plus API在NGINX Plus配置中添加和删除Kubernetes Pod条目,并使用Kubernetes API获取这些Pod的IP地址。此方法需要编写一些代码,本文不作深入讨论。有关详细信息,请观看Kelsey Hightower的网络研讨会借助NGINX Plus将Kubernetes扩展到边缘。他在其中探讨了这些API,并创建了一个使用这些API的应用程序。
  • 通过重新解析DNS名称,此方法只需按照下一节所述,对NGINX Plus进行一次正确配置。

使用基于DNS的重新配置

假设您已有一个正在运行的Kubernetes集群,以及一台可使用kubectl实用程序管理该集群的主机;有关说明,请参阅适用于相应集群类型的Kubernetes入门指南。此外,还需要构建NGINX Plus Docker镜像;有关说明,请参阅我们博客中的使用Docker部署NGINX和NGINX Plus。

下面概述了我们将执行的操作:

  1. 配置NGINX Plus Pod,以公开并对我们将在第2步中创建的服务进行负载均衡。
  2. 创建一个简单的Web应用程序作为服务。
  3. 扩展或缩减服务,并观察NGINX Plus如何自动重新配置。

注意:我们使用在Kubernetes 1.0.6上运行的Google Compute Engine和本地Vagrant环境测试了本博客中介绍的解决方案。以下内容使用的是本地Vagrant环境。

在命令中,可能因Kubernetes设置而异的值以斜体显示。

配置NGINX Plus Pod

我们将NGINX Plus部署在一个Kubernetes Pod中,该Pod位于可从互联网访问的节点上。此Pod由我们同时配置的复制控制器创建。Kubernetes专用的NGINX Plus配置文件存放在NGINX Plus Pod与节点共享的文件夹中,便于维护。

选择用于托管NGINX Plus Pod的节点

要指定运行NGINX Plus Pod的节点,请为该节点添加标签。运行以下命令可获取所有节点的列表:

$ kubectl get nodesNAME         LABELS                              STATUS
10.245.1.3   Kubernetes.io/hostname=10.245.1.3   Ready
10.245.1.4   Kubernetes.io/hostname=10.245.1.4   Ready
10.245.1.5   Kubernetes.io/hostname=10.245.1.5   Ready

选择第一个节点,并运行以下命令为其添加标签:

$ kubectl label node 10.245.1.3 role=nginxplus

为NGINX Plus Pod配置复制控制器

我们并非直接创建NGINX Plus Pod,而是通过复制控制器创建。我们在名为nginxplus-rc.yaml的Kubernetes声明文件中配置NGINX Plus Pod的复制控制器。

  • 我们将replicas的数量设置为1,这意味着Kubernetes会确保始终有一个NGINX Plus Pod处于运行状态:如果该Pod发生故障,Kubernetes会创建一个新Pod来替代它。
  • 在nodeSelector字段中,我们指定将NGINX Plus Pod创建在带有以下标签的节点上:role: nginxplus。
  • NGINX Plus容器公开了80和8080两个端口,我们将它们分别映射到节点上的80和8080端口。
  • 我们的NGINX Plus容器还共享节点上的/etc/nginx/conf.d文件夹。正如下文“配置NGINX Plus”中进一步说明的那样,共享该文件夹后,无需重新构建容器镜像即可重新配置NGINX Plus。
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginxplus-rc
spec:
  replicas: 1
  selector:
    app: nginxplus
  template:
    metadata:
      labels:
        app: nginxplus
    spec:
      nodeSelector:
        role: nginxplus
      containers:
      - name: nginxplus
        imagePullPolicy: IfNotPresent
        image: nginxplus
        ports:
          - name: http
            containerPort: 80
            hostPort: 80
          - name: http-alt
            containerPort: 8080
            hostPort: 8080
        volumeMounts:
          - mountPath: "/etc/nginx/conf.d"
            name: etc-nginx-confd
      volumes:
        - hostPath:
            path: "/etc/nginx/conf.d"
          name: etc-nginx-confd

使节点能够使用NGINX Plus Docker镜像

如上所述,我们已经构建了一个NGINX Plus Docker镜像。现在,需要让该镜像可供节点使用。为简单起见,我们不使用私有Docker仓库,而是直接将镜像手动加载到节点上。

在构建Docker镜像的主机上,运行以下命令将镜像保存到文件中:

$ docker save -o nginxplus.tar nginxplus

将nginxplus.tar传输到节点,然后在该节点上运行以下命令,从文件中加载镜像:

$ docker load -i nginxplus.tar

配置NGINX Plus

在NGINX Plus容器的/etc/nginx文件夹中,我们保留了NGINX Plus软件包随附的默认主配置文件nginx.conf。默认文件中的include指令会从/etc/nginx/conf.d文件夹中读取其他配置文件。按照NGINX Plus复制控制器的声明文件(nginxplus-rc.yaml)中的指定设置,我们在NGINX Plus节点与容器之间共享/etc/nginx/conf.d文件夹。通过共享,可以直接修改该文件夹中(节点上)存储的配置文件,而无需重新构建NGINX Plus Docker镜像。如果直接在容器中创建该文件夹,则每次修改都必须重新构建镜像。我们将Kubernetes专用配置文件(backend.conf)放在共享文件夹中。

首先,在节点上创建/etc/nginx/conf.d文件夹。

$ sudo mkdir -p /etc/nginx/conf.d

然后,在该目录中创建backend.conf文件,并添加以下指令:

  • resolver:定义NGINX Plus用于定期重新解析域名的DNS服务器。该域名用于标识上游服务器(位于server指令中,该指令包含在upstream块内,将在下一项中介绍)。我们使用域名kube-dns.kube-system.svc.cluster.local标识此DNS服务器。valid参数指示NGINX Plus每5秒发送一次重新解析请求。(请注意,此指令的解析过程与上游服务器的解析过程不同:此域名仅在NGINX启动或重新加载时解析,NGINX Plus使用/etc/resolv.conf文件中定义的一个或多个系统DNS服务器进行解析。)
  • upstream:创建一个名为backend的上游组,用于包含提供我们所公开Kubernetes服务的服务器。我们不逐一列出服务器,而是在一条server指令中使用完全限定主机名来标识这些服务器。resolve参数指示NGINX Plus根据resolver指令中指定的设置,在运行时重新解析主机名。由于Kubernetes DNS和NGINX Plus(R10及更高版本)均支持DNS服务(SRV)记录,因此NGINX Plus可以通过DNS获取上游服务器的端口号。我们添加service参数,使NGINX Plus请求SRV记录,并为服务公开的端口指定名称(_http)和协议(_tcp)。这些值在下文“webapp-svc.yaml”文件中进行了声明,详见“为服务创建复制控制器”。有关使用DNS进行服务发现的更多信息,请参阅我们博客中的“使用DNS通过NGINX和NGINX Plus进行服务发现”。
  • server(两次),定义两个虚拟服务器:
    • 第一个服务器侦听端口 80,并在运行服务实例的Pod之间对以下路径的传入请求进行负载均衡:/webapp(我们的服务)。我们还设置了主动运行状况检查。
    • 第二台服务器侦听端口 8080。我们在此设置对实时活动监控 NGINX Plus。稍后,我们将使用该功能检查 NGINX Plus 是否已正确重新配置。[编辑注:第二台服务器的配置已更新为使用 NGINX Plus API。该 API 取代了原先使用的独立状态模块,后者现已弃用。]
resolver kube-dns.kube-system.svc.cluster.local valid=5s;

upstream backend {
    zone upstream-backend 64k;
    server webapp-svc.default.svc.cluster.local service=_http._tcp resolve;
}

server {
    listen 80;
    status_zone backend-servers;

    location /webapp {
        proxy_pass http://backend;
        health_check;
    }
}

server {
    listen 8080;
    root /usr/share/nginx/html;

    location = /dashboard.html { }

    location = / {
        return 302 /dashboard.html;
    }

    location /api {
        api write=on;
    }
}

创建复制控制器

现在,可以运行以下命令来创建复制控制器:

$ kubectl create -f nginxplus-rc.yaml

要验证NGINX Plus Pod是否已创建,请运行:

$ kubectl get pods
NAME                 READY     STATUS    RESTARTS   AGE
nginxplus-rc-0ts5t   1/1       Running   0          17s

我们在本地Vagrant环境中运行Kubernetes,因此已知节点的外部IP地址为10.245.1.3,本示例的后续部分将使用该地址。如果您在云提供商的环境中运行Kubernetes,可以通过运行以下命令获取节点的外部IP地址:

$ kubectl get nodes node-name -o json | grep -i externalIP -A 1                "type": "ExternalIP",
                "address": XXX.XXX.XXX.XXX

如果在云环境中运行,请务必设置防火墙规则,以允许NGINX Plus节点接收入站流量。请参阅云服务提供商的文档。

可以通过查看NGINX Plus实时活动监控仪表板,确认NGINX Plus Pod已启动并正常运行。该仪表板可通过节点外部IP地址的8080端口访问(在本例中为http://10.245.1.3:8080/dashboard.html)。不过,此时仪表板中不会显示该服务的任何服务器,因为我们尚未创建该服务。

创建Kubernetes服务之前的NGINX Plus实时活动监控仪表板

创建简单的Kubernetes服务

现在可以创建Kubernetes服务了。该服务由两台Web服务器组成,每台服务器都会提供一个网页,其中包含其运行所在容器的相关信息。

为服务创建复制控制器

首先创建一个副本控制器,以便Kubernetes确保指定数量的Web服务器副本(Pod)始终在集群中运行。以下是声明文件(webapp-rc.yaml):

apiVersion: v1kind: ReplicationController
metadata:
  name: webapp-rc
spec:
  replicas: 2
  selector:
    app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: hello
        image: nginxdemos/hello 
        ports:
        - containerPort: 80

我们的控制器由两台Web服务器组成。我们声明了一个控制器,其中包含仅运行一个容器的Pod,并开放80端口。系统将从Docker Hub拉取nginxdemos/hello镜像。

要创建复制控制器,请运行以下命令:

$ kubectl create -f webapp-rc.yaml

要检查Pod是否已创建,可以运行以下命令。使用标签选择器app=webapp,仅获取上一步中由复制控制器创建的Pod:

$ kubectl get pods -l app=webapp
NAME             READY     STATUS    RESTARTS   AGE
webapp-rc-544f1   1/1       Running   0          2m
webapp-rc-uk6pm   1/1       Running   0          2m

创建服务

接下来,为复制控制器创建的Pod创建一个服务。使用以下文件(webapp-service.yaml)声明该服务:

apiVersion: v1kind: Service
metadata:
  name: webapp-svc
spec:
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  selector:
    app: webapp

在此,我们通过将无头服务的ClusterIP字段设置为None,声明一种特殊的A记录(即Pod的IP地址)。

我们还声明了NGINX Plus用于连接Pod的端口。除了指定端口号和目标端口号外,还指定了名称(http)和协议(TCP)。我们在NGINX Plus配置文件中使用这些值,并指示NGINX Plus通过DNS使用SRV记录获取Pod的端口号。

通过将selector字段设置为app: webapp,我们声明哪些Pod属于该服务,即由NGINX复制控制器创建的Pod(在webapp-rc.yaml中定义)。

运行以下命令以创建服务:

$ kubectl create -f webapp-service.yaml

现在,刷新仪表板页面并点击右上角的“Upstreams”选项卡,即可看到刚刚添加的两台服务器。

创建Kubernetes服务后的NGINX Plus实时活动监控仪表板

还可以检查NGINX Plus是否正在该服务的各个Pod之间对流量进行负载均衡。如果是,在浏览器中访问http://10.245.1.3/webapp/时,页面会显示Web服务器所在容器的信息,例如主机名和IP 地址。

多次刷新此页面并查看状态仪表板,即可看到请求如何分配到两台上游服务器。

扩展Kubernetes服务

[编辑注:本节已更新为使用NGINX Plus API。该API取代了原先使用的独立状态模块,后者现已弃用。]

现在,为服务再添加2个Pod,并确保NGINX Plus配置再次自动更新。运行以下命令扩展复制控制器,将Pod数量更改为4个:

$ kubectl scale rc webapp-rc --replicas=4
scaled

要检查NGINX Plus是否已重新配置,我们仍可查看仪表板,但这次改用NGINX Plus API。运行以下命令,其中10.245.1.3是NGINX Plus节点的外部IP地址,3是NGINX Plus API的版本。为使JSON输出格式整齐,将其通过管道传递给jq。

$ curl -s 10.245.1.3:8080/api/3/http/upstreams/backend/servers | jq{
  "peers": [
    {
      "id": 1,
      "server": "10.0.0.1:80",
      "backup": false,
      "weight": 1,
      "state": "unhealthy",
      "active": 0,
      "requests": 1,
      "responses": {
        "1xx": 0,
        "2xx": 0,
        "3xx": 0,
        "4xx": 0,
        "5xx": 0,
        "total": 0
      },
      "sent": 0,
      "received": 0,
      "fails": 0,
      "unavail": 0,
      "health_checks": {
        "checks": 1,
        "fails": 1,
        "unhealthy": 1,
        "last_passed": false
      },
      "downtime": 33965,
      "downstart": 1445378182275,
      "selected": 1445378131000
    },
    {
      "id": 2,
      "server": "10.246.1.6:80",
      ...
    },
    {
      "id": 3,
      "server": "10.246.3.2:80",
       ...
    {
      "id": 4,
      "server": "10.0.0.2:80",
      ...
    }
  ],
  "keepalive": 0
}

JSON输出中的peers数组正好包含4个元素,每个Web服务器对应一个元素。

现在,将Pod数量从4个减少到1个,然后再次检查NGINX Plus状态:

$ kubectl scale rc webapp-rc --replicas=1scaled

$ curl -s 10.245.1.3:8080/api/3/http/upstreams/backend/servers | jq

现在,JSON输出中的peers数组仅包含一个元素(输出与上一个示例命令中ID为1的对等节点相同)。

NGINX Plus现已启动并正常运行,我们可以开始利用其高级功能,例如会话持久性、SSL/TLS终止、请求路由、高级监控以及更多功能。

总结

NGINX Plus提供即时重新配置选项,可轻松与Kubernetes集成:既可以通过API以编程方式实现,也可以完全通过DNS实现。使用NGINX Plus将Kubernetes服务公开到互联网,可提供当前Kubernetes内置负载均衡解决方案所不具备的多项功能。

要了解NGINX Plus如何与Kubernetes协同工作,立即开始30天免费试用,或联系我们讨论您的使用场景。


分享

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。