原文作者:Kate Osborn of F5
原文链接:如何简化 Kubernetes 出入向流量管理
转载来源:NGINX 开源社区
NGINX 唯一中文官方社区 ,尽在 nginx.org.cn
编者注:本文引用相关资源的外链和视频较多,如欲获得更多参考资源,请点击此处前往 NGINX 中文官网查看该博客文章。
Kubernetes 出入向的流量管理可以通过服务网格和 Ingress Controller 两个功能来实现,这两个功能通常是分开进行配置的,这样 Kubernetes 环境的管理就会变得非常复杂。
这样的配置不仅会增加延迟,同时还会增加配置错误的概率,进而阻碍适当的流量路由,甚至导致出现安全漏洞(例如攻击者获得过多的应用访问权限)和不好的使用体验(例如客户无法访问其授权访问的应用)。另外分开配置也会消耗更多的配置时间,出现错误的时候还需要更多的时间来处理。
NGINX Plus 同时集成了 NGINX Ingress Controller 和 NGINX Service Mesh,也就是将服务网格和 Ingress
Controller 两个功能在一个组件上完成,通过这个集成功能可以同时控制出入向的 mTLS 流量,这样不仅可以避免分开配置引起的问题,也会节省时间。
下面通过几个章节进行说明:
前提条件
使用 NGINX Service Mesh 部署 NGINX Ingress Controller
使用标准 Kubernetes Ingress 资源发布应用
使用 NGINX VirtualServer 资源发布应用
使用 NGINX Ingress Controller 配置安全出向路由
前提条件
在开始演示之前,我们需要具备以下前提条件
注:本演示不适用于 NGINX 开源版 NGINX Ingress Controller。为方便阅读,我们在后文把基于 NGINX Plus 的 NGINX Ingress Controller 简称为“NGINX Ingress Controller”。
由于我们在第 1 步中设置了 strict
策略,sidecar 会拒绝来自网格外客户端的 bookinfo
应用请求。为了解决这个问题,我们需要在演示中设置端口转发,具体命令如下:
> kubectl port-forward svc/product-page 9080
Forwarding from 127.0.0.1:9080 -> 9080
Forwarding from [::1]:9080 -> 9080
Handling connection for 9080
复制代码
当我们试图访问该应用时,会得到状态码 503
,因为我们的本地设备不在服务网格中:
使用 NGINX Service Mesh 部署 NGINX Ingress Controller
在发布应用的第一阶段,我们需要部署一个 NGINX Ingress Controller 实例。
NGINX 为此提供了 Deployment 和 Daemonset 的 manifest 文件。本演示中使用的是 Deployment manifest,文件为:nginx-plus-ingress.yaml。
它包括了下面的 annotations,配置这个 annotations 的 NGINX Ingress Controller 会同时实现入向流量和出向流量的管理:
nsm.nginx.com/enable-ingress: "true"
nsm.nginx.com/enable-egress: "true"
查看在 GitHub 托管的nginx-plus-ingress.yaml
复制代码
该 manifest 直接集成了 NGINX Ingress Controller 和 Spire,Spire 是 NGINX Service Mesh 的证书颁发机构 (CA),因此 NGINX Service Mesh sidecar 不需要注入 NGINX Ingress Controller。
NGINX Ingress Controller 将直接从 Spire CA 获取证书和秘钥,用于网格中 pod 的 mTLS。该 manifest 指定了 Spire 的代理地址:
args:
- -spire-agent-address=/run/spire/sockets/agent.sock
查看在 GitHub 托管的nginx-plus-ingress.yaml
复制代码
并将 Spire 代理的 UNIX 套接字安装到 NGINX Ingress Controller pod:
volumes:
- hostPath:
path: /run/spire/sockets
type: DirectoryOrCreate
name: spire-agent-socket
查看在 GitHub 托管的nginx-plus-ingress.yaml
复制代码
关于 manifest,最后需要注意的一点是 -enable-internal-routes
CLI 参数,该参数用于出向路由服务:
args:
- -enable-internal-routes
查看在 GitHub 托管的nginx-plus-ingress.yaml
复制代码
在开始演示之前,我们通过运行 kubectl
apply
-f
nginx-plus-ingress.yaml
命令来安装 NGINX Ingress
Controller,然后检查一下 nginx-ingress
命名空间中的部署情况。
如下面输出结果中的 READY
列所示,NGINX Ingress Controller pod 中只有一个容器,是因为我们还没有为其注入 NGINX Service Mesh sidecar。
此外,我们还部署了 LoadBalancer
类型的服务,以便将 NGINX Ingress Controller 的外部 IP 地址(此处为
35.233.133.188)发布到集群外部。后续我们将访问位于该 IP 地址的 bookinfo
示例应用。
> kubectl get pods --namespace=nginx-ingress
NAME READY STATUS RESTARTS AGE
pod/nginx-ingress-867f954b8f0fzdrm 1/1 Running 0 3d3h
NAME TYPE CLUSTER-IP EXTERNAL-IP ...
service-nginx-ingress LoadBalancer 10.31.245.207 35.233.133.188 ...
... PORT(S) AGE
... 80:31469/TCP,443:32481/TCP 4d2h
...
复制代码
使用标准 Kubernetes Ingress 资源发布应用
现在,我们使用 bookinfo-ingress.yaml 中定义的标准 Kubernetes Ingress 资源在网格中发布 bookinfo
应用。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: bookinfo-ingress
spec:
ingressClassName: nginx # use only with k8s version >= 1.18.0
tls:
- hosts:
- bookinfo.example.com
secretName: bookinfo-secret
rules:
- host: bookinfo.example.com
http:
paths:
- path: /
backend:
serviceName: productpage
servicePort: 9080
查看在 GitHub 托管的bookinfo-ingress.yaml
复制代码
在 bookinfo-ingress.yaml 的第 10 行引用了 Kubernetes Secret,这个配置提供了路由规则,该规则将对
bookinfo.example.com 的请求转发给 productpage
服务(该配置在第 11–18 行)。
该 Kubernetes Secret 在 bookinfo-secret.yaml 中进行了定义:
apiVersion: v1
kind: Secret
metadata:
name: bookinfo-secret
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMl
查看在 GitHub 托管的bookinfo-ingress.yaml
复制代码
我们运行以下命令,可以加载密钥和证书(本演示中为自签名证书):
> kubectl apply -f bookinfo-secret.yaml
secret/bookinfo-secret unchanged
复制代码
然后我们激活 Ingress 资源:
> kubectl apply -f bookinfo-ingress.yaml
ingress.networking.k8s.io/bookinfo-ingress deleted
复制代码
并验证 Ingress Controller 是否添加了 Ingress 资源中定义的路由,输出结果的最后一行的 event 就说明已经确认了这个信息:
> kubectl describe ingress bookinfo-ingress
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 5s nginx-ingress-controller Configuration for ...
...default/bookinfo-ingress was added or updated
复制代码
在本演示中,使用浏览器通过访问 https://bookinfo.example.com/ 来查看 bookinfo
应用(在本机的
/etc/hosts 文件中,已经添加了域名和 IP 的映射,这里的域名为 bookinfo.example.com,IP 为 35.233.133.188)。
页面中“Book Reviews”部分的信息会根据对 bookinfo.yaml(下载)中定义的三个 reviews
服务版本的依次请求而发生周期性改变。
接下来,我们开始检查进入集群的流量。首先运行 generate-traffic.sh 脚本,然后通过访问 NGINX Ingress
Controller 发布的外网 IP 地址来请求productpage
服务,再运行 nginx-meshctl
top
命令来监控流量:
> nginxmesh-ctl top deploy/productpage-v1
Deployment Direction Resource Success Rate P99 P90
P50 ...
productpage-v1
To details-v1 100.00% 3ms 3ms 2ms
To reviews-v2 100.00% 99ms 90ms 20ms
To reviews-v3 100.00% 99ms 85ms 18ms
To reviews-v1 100.00% 20ms 17ms 9ms
From nginx-ingress 100.00% 192ms 120ms 38ms
... NumRequests
... 14
... 5
... 5
... 12
复制代码
使用标准 NGINX VirtualServer 资源发布应用
下面我们来展示另一种发布应用的方法,即使用 NGINX VirtualServer 资源发布应用。它是一种自定义 NGINX
Ingress Controller 资源,支持更复杂的流量处理,例如流量分割和基于内容的路由。
首先,我们删除标准 Ingress 资源:
> kubectl delete -f bookinfo-ingress.yaml
ingress.networking.k8s.io "bookinfo-ingress" deleted
复制代码
通过 bookinfo-vs.yaml 文件使用 Secret 配置 MTLS(第 7-8 行),这个 Secret 与 bookinfo-ingress.yaml 中的 Secret 相同。
在 bookinfo-vs.yaml 的第 9–12 行的配置将 productpage
服务定义为上游服务,第 13–24 行的配置将
bookinfo.example.com 的所有 GET
请求发送到上游。对于除 GET
以外的 HTTP 方法,它将返回状态代码 405
。
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: bookinfo-vs
spec:
host: bookinfo.example.com
tls:
secret: bookinfo-secret
upstreams:
- name: product
service: productpage
port: 9080
routes:
- path: /
matches:
- conditions:
- variable: $request_method
value: GET
action:
pass: product
action:
return:
code: 405
body: "Method not allowed\n"
查看在 GitHub 托管的 bookinfo-vs.yaml
复制代码
下面来创建 NGINX VirtualServer 资源:
> kubectl apply -f bookinfo-vs.yaml
virtualserver.kubernetes.nginx.org/bookinfo-vs created
复制代码
然后执行与 Ingress 资源相同的步骤,即通过运行 kubectl
describe
命令来确认应用已经部署正常,然后通过浏览器访问该应用。还可以通过看该应用是否拒绝 POST 方法来验证应用的正常运行,具体操作如下:
> curl -k -X POST https://bookinfo.example.com/
Method not allowed
复制代码
使用 NGINX Ingress Controller 配置安全出向路由
现在我们展示如何通过 NGINX Ingress Controller 处理出向流量。
我们在 bash.yaml 中定义了一个简单的 bash
pod ,并将其部署在 default 的命名空间中,然后在该命名空间中进行查看。 如下方输出结果中的 READY
列所示,我们已向其注入了 NGINX Service Mesh sidecar。
> kubectl get all
NAME READY STATUS RESTARTS AGE
pod/bash-6ccb678958-zsgm7 2/2 Running 0 77s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.31.240.1 <none> 443/TCP 4d2h
...
复制代码
在一些其他案例中,您可能想要从 pod 中请求出向服务,请求的服务不属于 NGINX Service Mesh 提供的对象。这些部署的服务示例位于:
在本演示中,我们确定了示例的最终状态。我们在 legacy
命名空间(不受 NGINX Service Mesh 控制,并禁用
NGINX Service Mesh sidecar 自动注入功能)中部署了一个应用,并只有一个 pod 运行。
> kubectl get all --namespaces=legacy
NAME READY STATUS RESTARTS AGE
pod/target-5f7bcb96c6-km9lz 1/1 Running 0 27m
NAME TYPE CLUSTER-IP EXTERNAL-IP
PORT(S) AGE
service/target-svc ClusterIP 10.31.245.213 <none> 80/TCP,443/TCP 27m
...
复制代码
由于我们为 NGINX Service Mesh 配置了一个 strict
mTLS 策略,因此,我们无法直接从 bash
pod 向目标服务发送请求,原因是他们之间没有互相进行身份验证。如果我们坚持进行访问的话,我们会得到状态代码 503
(如下图所示):
> kubectl exec -it bash-6ccb678958-zsgm7 -c bash -- curl target-svc.legacy
curl: (56) Recv failure: connection reset by peer
503command terminated with exit code 56
复制代码
解决方案是 让 bash
pod 发送出向流量时,该流量通过 NGINX Ingress Controller 来发送,操作方式是取消
bash.yaml 第 14-15 行的 annotations:
#annotations:
#config.nsm.nginx.com/default-egress-allowed: "true" # uncomment to route egress
traffic to NGINX Ingress Controller
查看在 GitHub 托管的 bash.yaml
复制代码
然后应用新配置:
> kubectl apply -f bash.yaml
deployment.apps/bash configured
复制代码
并验证新的 bash
pod 是否已正常启动:
> kubectl get pods
NAME READY STATUS RESTARTS AGE
bash-678c8b4579-7sfml 2/2 Running 0 6s
bash-6ccb678958-zsgm7 2/2 Terminating 0 3m28s
复制代码
现在,我们运行和前面一样的 kubectl
exec
命令,使得 bash
pod 向目标服务发送请求,这样会得到状态代码
404
,而不是 503
。这表明 bash
已经成功将请求发送到 NGINX Ingress Controller,但由于没有定义路由,所以不知道将它转发到哪里。
我们在 legacy-route.yaml 中使用以下 Ingress 资源创建所需的路由。第 7 行配置的 internal-route
,表示目标服务不会发布给互联网,而只发布给 NGINX Service Mesh 中的工作负载。
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: target-internal-route
namespace: legacy
annotations:
nsm.nginx.com/internal-route: "true"
spec:
ingressClassName: nginx # use only with k8s version >= 1.18.0
tls:
rules:
- host: target-svc.legacy
https:
paths:
- path: /
backend:
serviceName: target-svc
servicePort: 80
查看在 GitHub 托管的legacy-route.yaml
复制代码
通过下列命令激活新资源并确认 NGINX Ingress Controller 已添加相应的路由:
> kubectl apply -f legacy-route.yaml
ingress.networking.k8s.io/target-internal-route created
> kubectl describe ingress target-internal-route -n legacy
...
Events:
复制代码
现在,当我们运行 kubectl
exec
命令时,就可以访问目标服务了:
{"req": {"method": "GET"
"url": "/",
"host": "target-svc.legacy",
"remoteAddr": "10.28.2.76:56086"}}
复制代码
通过 NGINX Ingress Controller 路由出向流量的优势是,您可以准确控制从集群内部访问的外部服务(仅指定义了路由的外部服务)。
最后一项演示内容是如何监控出向流量。通过运行 kubectl
exec
命令来发送几个请求,然后运行以下命令:
> nginxmesh-ctl top deploy/nginx-ingress -n nginx-ingress
Deployment Direction Resource Success Rate P99 P90 P50 NumRequests
nginx-ingress
To target 100.00% 1ms 1ms 1ms 9
From bash 100.00% 0ms 0ms 0ms 9
复制代码
拒绝延迟集成 NGINX Service Mesh 与 NGINX Ingress Controller
虽然许多服务网格都提供出入向网关选项,但 NGINX 还具有低延迟优势。大多数服务网格都需要向 Ingress
Controller 注入 sidecar,因此流量需要额外一跳才能到达目的应用。虽然延迟很小,但这额外一跳会影响用户体验,进而导致客户流失。
NGINX Service Mesh 不会增加这样的延迟,因为它不需要将 sidecar 注入到 NGINX Ingress Controller。通过直接集成 Spire(服务网格的证书授权中心),NGINX Ingress Controller 可以成为 NGINX Service Mesh 的一部分。
NGINX Ingress Controller 只从 Spire 代理获取证书和秘钥,并使用它们参与 MTLS 与网状 pod 的证书交换。
Kubernetes NGINX Ingress Controller 有两个版本:NGINX 开源版和 NGINX Plus 版。
如要使用本文所述的使用 NGINX Service Mesh 部署 NGINX Ingress Controller,您必须使用 NGINX Plus 版(支持 30 天免费试用)。
NGINX Service Mesh 完全免费,您可立即下载并在 10 分钟内完成部署!有关使用方法,请查看我们的文档,并通过 GitHub 告诉我们您的使用体验。
NGINX 唯一中文官方社区 ,尽在 nginx.org.cn
更多 NGINX 相关的技术干货、互动问答、系列课程、活动资源: 开源社区官网 | 微信公众号
评论