注:本教程是 Microservices June 2022 微服务之月项目第三单元“Kubernetes 高级部署策略”的配套实验手册,您可点此报名参与这一免费线上教学项目以获取更多学习资源。
您的企业已经成功在 Kubernetes 中交付应用,现在是时候部署 v2 版 backend 服务了。但是,对于流量中断(又称停机)以及 v2 可能存在的不稳定性,人们也有着一些合理的担忧。作为 Kubernetes 工程师,您需要找到一种方法来确保在几乎不影响客户的情况下测试和部署 v2。
您可以使用流量分割技术“灰度部署”来实施渐进的、可控的迁移,这种部署模式支持安全、敏捷地测试新特性或新版本的稳定性。您的用例涉及在两个 Kubernetes service 之间移动的流量,因此您可以选择使用简单好用、效果可靠的 NGINX Service Mesh。您将 10% 的流量发送到 v2,其余 90% 仍将被路由到 v1。看到稳定性良好,于是您逐步将越来越多的流量发送到 v2,直至全部转移完毕!问题就这样解决了!
本文是“Microservices June 2022 微服务之月”第三单元“Kubernetes 高级部署策略”的实验配套文档,但您也可以在自己的环境中将其当作教程使用(您可以从我们的 GitHub 仓库中获取示例)。
为了完成实验,您需要一台具有以下配置的电脑:
注意:本文提到的 minikube 需在可以启动浏览器窗口的台式/笔记本电脑上运行。如果您所处的环境无法做到这一点,那么您需要解决如何通过浏览器访问服务的问题。
为了充分利用实验和教程,我们建议您在开始之前:
本教程使用了以下技术:
本教程涉及三个挑战:
部署一个 minikube 集群。几秒钟后将出现一条确认部署成功的消息。
$ minikube start \ --extra-config=apiserver.service-account-signing-key-file=/var/lib/minikube/certs/sa.key \
--extra-config=apiserver.service-account-key-file=/var/lib/minikube/certs/sa.pub \
--extra-config=apiserver.service-account-issuer=kubernetes/serviceaccount \
--extra-config=apiserver.service-account-api-audiences=api
Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
您是否注意到此 minikube 命令看起来与其他 Microservices March 教程的命令不同?
NGINX Service Mesh 由 F5 NGINX 维护,使用 NGINX Plus 作为 sidecar。虽然 NGINX Plus 是商用产品,但您可以通过 NGINX Service Mesh 免费使用这一组件。
我们有两种安装选择:
Helm 是最简单、最快捷的方法,因此本教程选择了使用 Helm。
helm repo add nginx-stable https://helm.nginx.com/stablehelm repo update
helm install nsm nginx-stable/nginx-service-mesh --namespace nginx-mesh --create-namespace --wait
STATUS
列中的值 Running
所示。kubectl get pods --namespace nginx-mesh NAME READY STATUS
grafana-7c6c88b959-62r72 1/1 Running
jaeger-86b56bf686-gdjd8 1/1 Running
nats-server-6d7b6779fb-j8qbw 2/2 Running
nginx-mesh-api-7864df964-669s2 1/1 Running
nginx-mesh-metrics-559b6b7869-pr4pz 1/1 Running
prometheus-8d5fb5879-8xlnf 1/1 Running
spire-agent-9m95d 1/1 Running
spire-server-0 2/2 Running
所有 pod 的部署可能共需要 1.5 分钟。除了 NGINX Service Mesh pod 外,还有 Grafana、Jaeger、NATS、Prometheus 和 Spire 的 pod。请查阅文档,了解这些工具如何与 NGINX Service Mesh 协同工作。
应用部署由两个微服务组成:
frontend
:为访问者提供用户界面的 Web 应用。它先解构复杂的请求,然后向众多 backend 应用发送调用。backend-v1
:一个通过 Kubernetes API 为 frontend
提供数据的业务逻辑应用。apiVersion: v1
kind: ConfigMap
metadata:
name: backend-v1
data:
nginx.conf: |-
events {}
http {
server {
listen 80;
location / {
return 200 '{"name":"backend","version":"1"}';
}
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-v1
spec:
replicas: 1
selector:
matchLabels:
app: backend
version: "1"
template:
metadata:
labels:
app: backend
version: "1"
annotations:
spec:
containers:
- name: backend-v1
image: "nginx"
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx
name: nginx-config
volumes:
- name: nginx-config
configMap:
name: backend-v1
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
labels:
app: backend
spec:
ports:
- port: 80
targetPort: 80
selector:
app: backend
backend-v1
: $ kubectl apply -f 1-backend-v1.yaml configmap/backend-v1 created
deployment.apps/backend-v1 created
service/backend-svc created
backend-v1
pod 和 service 已经部署成功,如 STATUS
列中的值 Running
所示。$ kubectl get pods,services NAME READY STATUS
pod/backend-v1-745597b6f9-hvqht 2/2 Running
NAME TYPE CLUSTER-IP PORT(S)
service/backend-svc ClusterIP 10.102.173.77 80/TCP
service/kubernetes ClusterIP 10.96.0.1 443/TCP
您可能会纳闷:“为什么 backend-v1-745597b6f9-hvqht
的一个 pod 内有两个容器?”
backend-svc
) 签发一个请求。apiVersion: apps/v1 kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: curlimages/curl:7.72.0
command: [ "/bin/sh", "-c", "--" ]
args: [ "sleep 10; while true; do curl -s http://backend-svc/; sleep 1 && echo ' '; done" ]
frontend
: $ kubectl apply -f 2-frontend.yaml deployment.apps/frontend created
STATUS
列中的值 Running 所示。请注意,每个应用同样也有两个 pod,因为它们是 NGINX Service Mesh 的一部分。$ kubectl get pods NAME READY STATUS RESTARTS
backend-v1-5cdbf9586-s47kx 2/2 Running 0
frontend-6c64d7446-mmgpv 2/2 Running 0
接下来,您需要检查日志,确认流量是否从 frontend 流向了 backend-v1
。日志检索命令要求您使用下列格式进行整合:
kubectl logs -c frontend <insert the full pod id displayed in your Terminal>
您可以在上一步 (frontend-6c64d7446-mmgpv
) 中获得完整的 pod ID,该 ID 在您的部署环境中具有唯一性。提交命令之后,日志应报告所有流量都被路由到 backend-v1
,这在意料之中,因为它是您唯一的 backend。
$ kubectl logs -c frontend frontend-6c64d7446-mmgpv
{"name":"backend","version":"1"}
{"name":"backend","version":"1"}
{"name":"backend","version":"1"}
{"name":"backend","version":"1"}
{"name":"backend","version":"1"}
{"name":"backend","version":"1"}
更有趣的是,随两个应用一起部署的 NGINX Service Mesh sidecar 会随着流量的流动收集指标。您可以通过 Jaeger 使用这些数据生成架构的依赖关系图。
您现在需要部署第二个 backend 应用 backend-v2
,它是用来服务 frontend
的。正如版本号所示,backend-v2
是 backend-v1
的新版本。
backend
标签。backend
,流量应该均匀分布在 backend-v1
和 backend-v2
之间。apiVersion: v1 kind: ConfigMap
metadata:
name: backend-v2
data:
nginx.conf: |-
events {}
http {
server {
listen 80;
location / {
return 200 '{"name":"backend","version":"2"}';
}
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-v2
spec:
replicas: 1
selector:
matchLabels:
app: backend
version: "2"
template:
metadata:
labels:
app: backend
version: "2"
annotations:
spec:
containers:
- name: backend-v2
image: "nginx"
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx
name: nginx-config
volumes:
- name: nginx-config
configMap:
name: backend-v2
$ kubectl apply -f 3-backend-v2.yaml configmap/backend-v2 created
deployment.apps/backend-v2 created
backend-v2
pod 和 services 已经部署成功,如 STATUS
列中的值 Running
所示。使用与上文相同的命令检查日志。您现在应该可以看到响应均匀地来自于两个 backend
版本。
$ kubectl logs -c frontend frontend-6c64d7446-mmgpv
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
{"name":"backend","version":"1"}
返回到 Jaeger 选项卡,查看 NGINX Service Mesh 能否正确映射两个 backend
版本。
backend
。
按照本教程的节奏,现在您已经部署了两个版本的 backend
:v1 和 v2。虽然您可以立即将所有流量转移到 v2,但在将生产流量托付给新版本之前,您最好先测试一下稳定性。在这种情况下,使用灰度部署再合适不过了。
《如何通过高级流量管理提高 Kubernetes 的弹性》一文曾详细地探讨过,灰度部署是一种流量分割技术,为测试新特性或新版本的稳定性提供了一种安全、敏捷的方法。在常规的灰度部署中,企业会先让绝大多数(比如 99%)用户使用稳定版,并且只将一小部分用户(剩余的 1%)转移到新版本。如果新版本出现问题(例如崩溃或向客户端返回错误),您可以立即将测试用户转移回稳定版。如果新版本顺利运行,您可以一次性或渐进、可控地(后者更为常见)将用户从稳定版迁移到新版本。
下图描述了使用 Ingress controller 分割流量的灰度部署。
在本教程中,您将设置流量分割,让 90% 的流量流向 backend-v1
,其余 10% 则流向 backend-v2
。
Ingress controller 可以分割从客户端流向 Kubernetes service 的流量,但不能分割 service 之间的流量。实施这种类型的灰度部署有两种选择:
frontend
pod 的代理向 backend-v1
发送 9 个请求(共 10 个)。但试想一下,如果您的 frontend
有几十个副本呢?您真的想手动更新所有这些代理吗?当然不是了!这不仅容易出错,而且还耗费时间。选项 2:聪明方法
可观测性和可控性是使用 service mesh(服务网格)的首要理由。除此还有其他更多好处。service mesh 也是在 service 之间实施流量分割的理想工具,因为您可以为网格服务的所有 frontend 副本应用统一的策略!
NGINX Service Mesh 能够实施 Service Mesh Interface (SMI) — SMI 是一个规范,定义了在 Kubernetes 上运行的 service mesh 的标准接口,具有 TrafficSplit
、 TrafficTarget
和 HTTPRouteGroup
等类型化资源。借助这些标准的 Kubernetes 配置,NGINX Service Mesh 和NGINX SMI 扩展程序可简化流量分割策略(如灰度部署)的部署,同时最大限度地减少对生产流量的中断。
下图摘自《API 网关 vs. Ingress Controller vs. Service Mesh,该怎么选?》一文,您可以从中看出 NGINX Service Mesh 是如何使用基于 HTTP/S 标准的条件路由在 service 之间实施灰度部署的。
与所有其他网格一样,NGINX Service Mesh 的架构也有一个数据平面和控制平面。由于 NGINX Service Mesh 利用 NGINX Plus 支持数据平面,它能够执行高级部署方案。
NGINX Service Mesh 控制平面可以使用 Kubernetes 自定义资源定义 (CRD) 进行控制。它使用 Kubernetes service 来检索 pod 的 IP 地址和端口列表。然后,它会结合来自 CRD 的指令,通知 sidecar 直接将流量路由到 pod。
TrafficSplit
CRD 定义流量分割。apiVersion: split.smi-spec.io/v1alpha3 kind: TrafficSplit
metadata:
name: backend-ts
spec:
service: backend-svc
backends:
- service: backend-v1
weight: 90
- service: backend-v2
weight: 10
请注意 CRD 中定义了三个 service(到目前为止您只创建了一个):
backend-svc
是面向所有 pod 的 service。backend-v1
是从 backend-v1
部署中选择 pod 的 service。backend-v2
是从 backend-v2
部署中选择 pod 的 service。apiVersion: v1 kind: Service
metadata:
name: backend-v1
labels:
app: backend
version: "1"
spec:
ports:
- port: 80
targetPort: 80
selector:
app: backend
version: "1"
---
apiVersion: v1
kind: Service
metadata:
name: backend-v2
labels:
app: backend
version: "2"
spec:
ports:
- port: 80
targetPort: 80
selector:
app: backend
version: "2"
$ kubectl apply -f 4-services.yaml service/backend-v1 created
service/backend-v2 created
在实施流量分割之前,请先检查日志,查看流量在没有 TrafficSplit
CRD 的情况下是如何流动的。您应该可以看到流量在 v1和 v2 之间均匀分配。
$ kubectl logs -c frontend frontend-6c64d7446-mmgpv
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
TrafficSplit
CRD. $ kubectl apply -f 5-split.yaml trafficsplit.split.smi-spec.io/backend-ts created
$ kubectl logs -c frontend frontend-6c64d7446-mmgpv
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
{"name":"backend","version":"1"}
{"name":"backend","version":"1"}
{"name":"backend","version":"1"}
在实际应用中,我们很少会先进行 90/10 的流量分割,然后立即将所有流量移至新版本。相反,最好的做法是递增转移流量。例如:0%、5%、10%、25%、50% 和 100%。为了演示实施递增转移的简单性,您可以将权重更改为 20/80,然后再改为 0/100。
backend-v1
获得 20% 的流量、backend-v2
获得剩余 80% 的流量。 apiVersion: split.smi-spec.io/v1alpha3 kind: TrafficSplit
metadata:
name: backend-ts
spec:
service: backend-svc
backends:
- service: backend-v1
weight: 20
- service: backend-v2
weight: 80
$ kubectl apply -f 5-split.yaml trafficsplit.split.smi-spec.io/backend-ts configured
$ kubectl logs -c frontend frontend-6c64d7446-mmgpv
{"name":"backend","version":"2"}
{"name":"backend","version":"1"}
{"name":"backend","version":"2"}
{"name":"backend","version":"2"}
{"name":"backend","version":"2"}
backend-v1
获得 0% 的流量、backend-v2
获得 100% 的流量。apiVersion: split.smi-spec.io/v1alpha3 kind: TrafficSplit
metadata:
name: backend-ts
spec:
service: backend-svc
backends:
- service: backend-v1
weight: 0
- service: backend-v2
weight: 100
$ kubectl apply -f 5-split.yaml trafficsplit.split.smi-spec.io/backend-ts configured
$ kubectl logs -c frontend frontend-6c64d7446-mmgpv
{"name":"backend","version":"2"}
{"name":"backend","version":"2"}
{"name":"backend","version":"2"}
{"name":"backend","version":"2"}
{"name":"backend","version":"2"}
如欲继续深入了解和本实验相关的知识技能,您可以加入到 Microservices June 2022 微服务之月项目中来,并继续浏览本项目第三单元“Kubernetes 高级部署策略”的其他学习资源。
NGINX Service Mesh 完全免费。您可以使用 Helm(本教程使用的方法)或通过 F5 Downloads下载 NGINX Service Mesh。
"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."