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。
下面概述了我们将执行的操作:
- 配置NGINX Plus Pod,以公开并对我们将在第2步中创建的服务进行负载均衡。
- 创建一个简单的Web应用程序作为服务。
- 扩展或缩减服务,并观察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(两次),定义两个虚拟服务器:
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服务
现在可以创建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”选项卡,即可看到刚刚添加的两台服务器。

还可以检查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 Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。
