注:本教程是 Microservices June 2022 微服务之月项目第三单元“Kubernetes 高级部署策略”的配套实验手册,您可点此报名参与这一免费线上教学项目以获取更多学习资源。
- 第一单元:通过自动扩展减少 Kubernetes 延迟
- 第二单元:通过速率限制保护 Kubernetes API
- 第三单元:通过灰度部署改善正常运行时间和弹性(本文)
- 第四单元:保护 Kubernetes 应用免遭 SQL 注入攻击
您的企业已经成功在 Kubernetes 中交付应用,现在是时候部署 v2 版 backend 服务了。但是,对于流量中断(又称停机)以及 v2 可能存在的不稳定性,人们也有着一些合理的担忧。作为 Kubernetes 工程师,您需要找到一种方法来确保在几乎不影响客户的情况下测试和部署 v2。
您可以使用流量分割技术“灰度部署”来实施渐进的、可控的迁移,这种部署模式支持安全、敏捷地测试新特性或新版本的稳定性。您的用例涉及在两个 Kubernetes service 之间移动的流量,因此您可以选择使用简单好用、效果可靠的 NGINX Service Mesh。您将 10% 的流量发送到 v2,其余 90% 仍将被路由到 v1。看到稳定性良好,于是您逐步将越来越多的流量发送到 v2,直至全部转移完毕!问题就这样解决了!
实验和教程概述
本文是“Microservices June 2022 微服务之月”第三单元“Kubernetes 高级部署策略”的实验配套文档,但您也可以在自己的环境中将其当作教程使用(您可以从我们的 GitHub 仓库中获取示例)。
为了完成实验,您需要一台具有以下配置的电脑:
- 至少 2 个 CPU
- 2GB 可用内存
- 20GB 可用磁盘空间
- 互联网连接
- 容器或虚拟机管理器,例如 Docker、HyperKit、Hyper-V、KVM、Parallels、Podman、VirtualBox 或 VMware Fusion/Workstation
- 装有 minikube
- 装有 Helm
注意:本文提到的 minikube 需在可以启动浏览器窗口的台式/笔记本电脑上运行。如果您所处的环境无法做到这一点,那么您需要解决如何通过浏览器访问服务的问题。
为了充分利用实验和教程,我们建议您在开始之前:
- 观看直播回放
- 阅读相关的博客文章和其他学习资源
- 观看答疑课视频回放
本教程使用了以下技术:
- NGINX Service Mesh
- Helm
- Jaeger
- minikube
- 两个示例应用
本教程涉及三个挑战:
挑战 1:部署集群和 NGINX Service Mesh
部署一个 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 教程的命令不同?
- 您需要一些额外的配置来启用 Service Account Token Volume Projection(服务帐户令牌卷投影)——这是使用 NGINX Service Mesh 的必要条件。
- 启用此功能后,kubelet 将把同一个服务帐户安装到每个 pod 中。
- 此后,pod 就会在集群中获得唯一标识,kubelet 则负责定期轮换令牌。
- 如欲了解更多信息,请阅读《使用 Kubernetes 身份在微服务之间进行身份验证》。
部署 NGINX Service Mesh
NGINX Service Mesh 由 F5 NGINX 维护,使用 NGINX Plus 作为 sidecar。虽然 NGINX Plus 是商用产品,但您可以通过 NGINX Service Mesh 免费使用这一组件。
我们有两种安装选择:
Helm 是最简单、最快捷的方法,因此本教程选择了使用 Helm。
- 下载并安装 NGINX Service Mesh:
- 确认 NGINX Service Mesh pod 已经部署成功,如
STATUS
列中的值Running
所示。
helm repo add nginx-stable https://helm.nginx.com/stable
helm repo update
helm install nsm nginx-stable/nginx-service-mesh --namespace nginx-mesh --create-namespace --wait
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 协同工作。
挑战 2:部署两个应用(Frontend 和 Backend)
应用部署由两个微服务组成:
frontend
:为访问者提供用户界面的 Web 应用。它先解构复杂的请求,然后向众多 backend 应用发送调用。backend-v1
:一个通过 Kubernetes API 为frontend
提供数据的业务逻辑应用。
安装 Backend-v1 应用
- 使用您选择的文本编辑器,创建一个名为 1-backend-v1.yaml 的 YAML 文件,该文件应包含以下内容:
- 部署
backend-v1
: - 确认
backend-v1
pod 和 service 已经部署成功,如STATUS
列中的值Running
所示。
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
$ kubectl apply -f 1-backend-v1.yaml
configmap/backend-v1 created
deployment.apps/backend-v1 created
service/backend-svc created
$ 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 内有两个容器?”
- NGINX Service Mesh 会将一个 sidecar 代理注入到您的 pod 中。
- 该 sidecar 容器会拦截进出您 pod 的所有流量。
- 收集到的所有数据被用于指标监测,但是您也可以使用此代理来决定流量的去向。
部署 Frontend 应用
- 创建一个名为 2-frontend.yaml 的 YAML 文件,该文件应包含以下内容:请注意,pod 使用 cURL 每秒向 backend service (
backend-svc
) 签发一个请求。 - 部署
frontend
: - 确认 frontend pod 已经部署成功,如
STATUS
列中的值 Running 所示。请注意,每个应用同样也有两个 pod,因为它们是 NGINX Service Mesh 的一部分。
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" ]
$ kubectl apply -f 2-frontend.yaml
deployment.apps/frontend created
$ 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"}
使用 Jaeger 检查依赖关系图
更有趣的是,随两个应用一起部署的 NGINX Service Mesh sidecar 会随着流量的流动收集指标。您可以通过 Jaeger 使用这些数据生成架构的依赖关系图。
- 使用 minikube service jaeger 在浏览器中打开 Jaeger 仪表盘。
- 点击“System Architecture(系统架构)”选项卡,您将看到一个非常简单的架构(将鼠标悬停在图上即可显示标签)。DAG 选项卡提供了一个放大的视图。想象一下,如果 frontend 访问数十个甚至数百个 backend service,这个图将是多么的精彩!
添加 Backend-v2
您现在需要部署第二个 backend 应用 backend-v2
,它是用来服务 frontend
的。正如版本号所示,backend-v2
是 backend-v1
的新版本。
- 创建一个名为 3-backend-v2.yaml 的 YAML 文件(包含以下内容)并注意以下两点:
- 当前部署如何与先前的部署共享 app:
backend
标签。 - 由于 service 选择器为 app:
backend
,流量应该均匀分布在backend-v1
和backend-v2
之间。 - 部署 backend-v2:
- 确认
backend-v2
pod 和 services 已经部署成功,如STATUS
列中的值Running
所示。
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
版本。
$ 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
返回到 Jaeger 选项卡,查看 NGINX Service Mesh 能否正确映射两个 backend
版本。
- Jaeger 可能需要几分钟才能识别添加了第二个
backend
。 - 如果一分钟后没有结果显现,请先继续下一个挑战,稍后再回头查看依赖关系图。
挑战 3:使用 NGINX Service Mesh 实施灰度部署
按照本教程的节奏,现在您已经部署了两个版本的 backend
:v1 和 v2。虽然您可以立即将所有流量转移到 v2,但在将生产流量托付给新版本之前,您最好先测试一下稳定性。在这种情况下,使用灰度部署再合适不过了。
什么是灰度部署?
《如何通过高级流量管理提高 Kubernetes 的弹性》一文曾详细地探讨过,灰度部署是一种流量分割技术,为测试新特性或新版本的稳定性提供了一种安全、敏捷的方法。在常规的灰度部署中,企业会先让绝大多数(比如 99%)用户使用稳定版,并且只将一小部分用户(剩余的 1%)转移到新版本。如果新版本出现问题(例如崩溃或向客户端返回错误),您可以立即将测试用户转移回稳定版。如果新版本顺利运行,您可以一次性或渐进、可控地(后者更为常见)将用户从稳定版迁移到新版本。
下图描述了使用 Ingress controller 分割流量的灰度部署。
Service 之间的灰度部署
在本教程中,您将设置流量分割,让 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 进行流量分割
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 Plus 代理(称为 sidecar)组成,这些代理负责卸载网格中所有应用所需的功能,并实施灰度部署等部署模式。
- 控制平面:负责管理数据平面。sidecar 能够路由应用流量并提供其他数据平面服务,控制平面则可以将 sidecar 注入到 pod 中并执行管理任务,例如更新 mTLS 证书并将其推送到合适的 sidecar 等。
创建灰度部署
NGINX Service Mesh 控制平面可以使用 Kubernetes 自定义资源定义 (CRD) 进行控制。它使用 Kubernetes service 来检索 pod 的 IP 地址和端口列表。然后,它会结合来自 CRD 的指令,通知 sidecar 直接将流量路由到 pod。
- 使用您选择的文本编辑器,创建一个名为 5-split.yaml 的 YAML 文件,在其中使用
TrafficSplit
CRD 定义流量分割。 backend-svc
是面向所有 pod 的 service。backend-v1
是从backend-v1
部署中选择 pod 的 service。backend-v2
是从backend-v2
部署中选择 pod 的 service。- 在实施流量分割之前,您必须先创建缺失的 service。创建一个名为 4-services.yaml 的 YAML 文件,该文件应包含以下内容:
- 添加您的 service:
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(到目前为止您只创建了一个):
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. - 再次观察日志。现在,您应该看到 90% 的流量被传输到 v1,如 5-split.yaml 所定义的那样。
$ 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"}
转移到 V2
在实际应用中,我们很少会先进行 90/10 的流量分割,然后立即将所有流量移至新版本。相反,最好的做法是递增转移流量。例如:0%、5%、10%、25%、50% 和 100%。为了演示实施递增转移的简单性,您可以将权重更改为 20/80,然后再改为 0/100。
- 编辑 5-split.yaml,让
backend-v1
获得 20% 的流量、backend-v2
获得剩余 80% 的流量。 - 应用更改:
- 观察日志,查看实际变化:
- 要完成转移,请编辑 5-split.yaml,让
backend-v1
获得 0% 的流量、backend-v2
获得 100% 的流量。 - 应用更改:
- 观察日志,查看实际变化。所有响应都已转移到 backend-v2,这意味着您的递增转移已全部完成!
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"}
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。