Istio 是一种服务网格,通过一组 CRD 提供了丰富的功能集,用于控制流向 Web 服务的流量。Istio 通过调整 Istio VirtualService 中定义的流量权重来实现此功能。使用 Argo Rollouts 时,用户可以部署包含至少一个 HTTP 路由 的 VirtualService,其中包含两个 HTTP 路由目标:一个路由目标针对金丝雀 ReplicaSet 的 pod,一个路由目标针对稳定 ReplicaSet 的 pod。Istio 提供了两种带权流量分割方法,这两种方法都可以作为 Argo Rollouts 的选项。
- 主机级流量分割
- 子集级流量分割
主机级流量分割
使用 Argo Rollouts 和 Istio 进行流量分割的第一种方法是在两个主机名或 Kubernetes Service 之间进行分割:一个金丝雀 Service 和一个稳定 Service。这种方法类似于所有其他 Argo Rollouts mesh/ingress-controller 集成方式的工作方式(例如 ALB、SMI、Nginx)。使用此方法,用户需要部署以下资源:
- Rollout
- Service(金丝雀)
- Service(稳定)
- VirtualService
Rollout 应定义以下字段:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-example
spec:
...
strategy:
canary:
canaryService: canary-svc # 必需
stableService: stable-svc # 必需
trafficRouting:
istio:
virtualService:
name: rollout-vsvc # 必需
routes:
- primary # 如果 VirtualService 中只有一条路由,则为可选,否则为必需
steps:
- setWeight: 5
- pause:
duration: 10m
VirtualService 必须包含一个 HTTP 路由,其名称在 Rollout 中引用,包含两个路由目标,其 host
值与 Rollout 中引用的 canaryService
和 stableService
匹配。如果 VirtualService 定义在与 rollout 不同的命名空间中,则其名称应为 rollout-vsvc.<vsvc 命名空间名称>
。请注意,Istio 要求所有权重总和为 100,因此初始权重可以是 100% 的稳定和 0% 的金丝雀。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: rollout-vsvc
spec:
gateways:
- istio-rollout-gateway
hosts:
- istio-rollout.dev.argoproj.io
http:
- name: primary # 在 canary.trafficRouting.istio.virtualService.routes 中被引用
route:
- destination:
host: stable-svc # 在 canary.stableService 中被引用
weight: 100
- destination:
host: canary-svc # 在 canary.canaryService 中被引用
weight: 0
最后,应部署金丝雀和稳定的 Service。这些 Service 的选择器将在更新期间由 Rollout 修改,以对齐金丝雀和稳定 ReplicaSet pod。请注意,如果 VirtualService 和目标主机位于不同的命名空间中(例如,VirtualService 和 Rollout 不在同一命名空间中),则应在目标主机中包含命名空间(例如,stable-svc.<namespace>
)。
apiVersion: v1
kind: Service
metadata:
name: canary-svc
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
# 这个选择器将使用 canary ReplicaSet 的pod-template-hash进行更新。例如:rollouts-pod-template-hash: 7bf84f9696
---
apiVersion: v1
kind: Service
metadata:
name: stable-svc
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
# 这个选择器将使用 stable ReplicaSet 的pod-template-hash进行更新。例如:rollouts-pod-template-hash: 123746c88d
在 Rollout 更新的生命周期中,Argo Rollouts 将不断地:
- 修改金丝雀 Service
spec.selector
,以包含金丝雀 ReplicaSet 的rollouts-pod-template-hash
标签 - 修改稳定 Service
spec.selector
,以包含稳定 ReplicaSet 的rollouts-pod-template-hash
标签 - 修改 VirtualService
spec.http[].route[].weight
,以匹配当前所需的金丝雀权重
🔔 注意:Rollout 不会对 VirtualService 或 Istio 网络中的其他字段做出任何假设。如果需要,用户可以为 VirtualService 指定其他配置,例如主要路由或任何其他路由的 URI 重写规则。用户还可以为每个服务创建特定的 DestinationRules。
子集级流量分割
🔔 重要提示:自 v1.0 起可用。
使用 Argo Rollouts 和 Istio 进行流量分割的第二种方法是在两个 Istio DestinationRule Subsets 之间进行分割:一个金丝雀子集和一个稳定子集。在按 DestinationRule 子集进行拆分时,需要用户部署以下资源:
- Rollout
- Service
- VirtualService
- DestinationRule
Rollout 应定义以下字段:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-example
spec:
...
strategy:
canary:
trafficRouting:
istio:
virtualService:
name: rollout-vsvc # 必需
routes:
- primary # 如果 VirtualService 中只有一条路由,则为可选,否则为必需
destinationRule:
name: rollout-destrule # 必需
canarySubsetName: canary # 必需
stableSubsetName: stable # 必需
steps:
- setWeight: 5
- pause:
duration: 10m
应定义一个服务,该服务针对 Rollout pods。请注意,与第一种方法不同,在第一种方法中,流量拆分针对多个 Service,这些 Service 被修改为包含金丝雀/稳定 ReplicaSets 的 rollout-pod-template-hash,而此 Service 不会被 rollout 控制器修改。
apiVersion: v1
kind: Service
metadata:
name: rollout-example
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollout-example
VirtualService 必须包含一个 HTTP 路由,其名称在 Rollout 中引用,包含两个路由目标,其 subset
值与 Rollout 中引用的 canarySubsetName
和 stableSubsetName
匹配。请注意,Istio 要求所有权重总和为 100,因此初始权重可以是 100% 的稳定和 0% 的金丝雀。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: rollout-vsvc
spec:
gateways:
- istio-rollout-gateway
hosts:
- istio-rollout.dev.argoproj.io
http:
- name: primary # 在 canary.trafficRouting.istio.virtualService.routes 中被引用
route:
- destination:
host: rollout-example
subset: stable # 在 canary.trafficRouting.istio.destinationRule.stableSubsetName 中被引用
weight: 100
- destination:
host: rollout-example
subset: canary # 在 canary.trafficRouting.istio.destinationRule.canarySubsetName 中被引用
weight: 0
最后,包含在 Rollout 中引用的金丝雀和稳定子集的 DestinationRule。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: rollout-destrule
spec:
host: rollout-example
subsets:
- name: canary # 在 canary.trafficRouting.istio.destinationRule.canarySubsetName 中引用
labels: # 标签将注入 canary 部署的 pod 模板哈希值
app: rollout-example
- name: stable # 在 canary.trafficRouting.istio.destinationRule.stableSubsetName 中引用
labels: # 标签将注入 stable 部署的 pod 模板哈希值
app: rollout-example
在使用 Istio DestinationRule 的 Rollout 生命周期中,Argo Rollouts 将不断地:
- 修改 VirtualService
spec.http[].route[].weight
,以匹配当前所需的金丝雀权重 - 修改 DestinationRule
spec.subsets[].labels
,以包含金丝雀和稳定 ReplicaSets 的rollouts-pod-template-hash
标签
TCP 流量分割
🔔 重要提示:自 v1.2.2 起可用。
支持拆分 TCP 流量,并要求 Rollout 定义以下字段:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollout-example
spec:
...
strategy:
canary:
canaryService: canary-svc # 必需
stableService: stable-svc # 必需
trafficRouting:
istio:
virtualService:
name: rollout-vsvc # 必需
tcpRoutes:
# 下面的字段是可选的,但如果定义了,必须与 VirtualService 中至少一个 TCP 路由匹配规则完全匹配
- port: 3000 # 仅在你想匹配包含此端口的 VirtualService 中的任何规则时才需要
steps:
- setWeight: 5
- pause:
duration: 10m
VirtualService 必须包含与 Rollout 中引用的匹配端口的 TCP 路由:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: rollout-vsvc
spec:
gateways:
- istio-rollout-gateway
hosts:
- istio-rollout.dev.argoproj.io
tcp:
- match:
- port: 3000
route:
- destination:
host: stable-svc # 在 canary.stableService 中引用
weight: 100
- destination:
host: canary-svc # 在 canary.canaryService 中引用
weight: 0
多集群设置
如果你有 Istio 多集群设置,其中主 Istio 集群不同于运行 Argo Rollout 控制器的集群,则需要执行以下设置:
-
在 Istio 主集群中创建一个
ServiceAccount
。apiVersion: v1 kind: ServiceAccount metadata: name: argo-rollouts-istio-primary namespace: <any-namespace-preferrably-config-namespace>
-
创建一个
ClusterRole
,为 Istio 主集群中的 Rollout 控制器提供访问权限。apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: argo-rollouts-istio-primary rules: - apiGroups: - networking.istio.io resources: - virtualservices - destinationrules verbs: - get - list - watch - update - patch
注意:如果 Argo Rollout 控制器也安装在 Istio 主集群中,则可以重用 argo-rollouts-clusterrole ClusterRole,而无需创建新的 ClusterRole。
-
将
ClusterRole
与 Istio 主集群中的ServiceAccount
进行连接。apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: argo-rollouts-istio-primary roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: argo-rollouts-istio-primary subjects: - kind: ServiceAccount name: argo-rollouts-istio-primary namespace: <namespace-of-the-service-account>
-
现在,使用以下命令为 Rollout 控制器生成一个密钥,以便访问 Istio 主集群。该密钥将应用于运行 Argo Rollout 的集群(即,Istio 远程集群),但将从 Istio 主集群生成。此秘密可以在第 1 步之后立即生成,只需要
ServiceAccount
存在即可。命令的参考。istioctl x create-remote-secret --type remote --name <cluster-name> \ --namespace <namespace-of-the-service-account> \ --service-account <service-account-created-in-step1> \ --context="<ISTIO_PRIMARY_CLUSTER>" | \ kubectl apply -f - --context="<ARGO_ROLLOUT_CLUSTER/ISTIO_REMOTE_CLUSTER>"
-
标记密钥。
kubectl label secret <istio-remote-secret> istio.argoproj.io/primary-cluster="true" -n <namespace-of-the-secret>
方法比较
主机级流量分割与子集级流量分割存在一些优缺点。
DNS 要求
使用主机级分割时,VirtualService 需要不同的 host
值以在两个目标之间进行拆分。但是,使用两个主机值意味着需要使用不同的 DNS 名称(一个用于金丝雀,另一个用于稳定)。对于北南流量,该流量通过 Istio 网关到达服务,使用多个 DNS 名称以访问金丝雀与稳定 pod 可能无关紧要。但是,对于东西向或集群内流量,它会强制微服务通信选择是要命中稳定还是金丝雀 DNS 名称,是否要通过网关,或者是否要为 VirtualServices 添加 DNS 条目。在这种情况下,DestinationRule 子集流量分割将是集群内金丝雀的更好选择。
指标
根据选择主机级分割还是子集级分割,将有不同风格的 prometheus 指标可用。例如,如果使用主机级分割,则金丝雀与稳定的度量将出现在 Istio 服务度量仪表板中:
另一方面,当通过子集进行拆分时,需要使用不同的参数查询 Prometheus,例如工作负载名称:
与 GitOps 集成
之前已经解释了 VirtualService 应该使用初始金丝雀和稳定权重为 0 和 100 进行部署,例如以下示例:
http:
- name: primary
route:
- destination:
host: stable-svc
weight: 100
- destination:
host: canary-svc
weight: 0
这会给 GitOps 练习的用户带来问题。由于 Rollout 将在其步骤中修改这些 VirtualService 权重,因此不幸的是会导致 VirtualService 与 git 版本不同步。此外,如果在 Rollout 处于此状态(在服务之间分流流量)时应用 git 中的 VirtualService,则应用将将权重恢复为 git 中的值(即 100 至 stable,0 至金丝雀)。
在 Argo Rollouts 中实现的一种保护是,它不断地监视管理的 VirtualService 的更改。如果使用 git 中的 VirtualService 进行 kubectl apply
,则 Rollout 控制器会立即检测到更改,并立即将 VirtualService 权重设置回适用于 Rollout 给定步骤的金丝雀权重。但是由于权重的瞬时抖动,应该了解这种行为。
在使用 Argo CD 与 Argo Rollouts 时遵循的一些最佳实践,以防止出现此行为,是利用以下 Argo CD 功能:
-
配置应用程序以忽略 VirtualService 中的差异。例如:
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: guestbook spec: ignoreDifferences: - group: networking.istio.io kind: VirtualService jsonPointers: - /spec/http/0
忽略 VirtualServices 的 HTTP 路由中的差异,防止 gitops 差异在 VirtualService HTTP 路由中对 Argo CD 应用程序的总同步状态产生影响。这增加了一个额外的好处,可以防止触发自动同步操作。
-
将应用程序配置为仅应用 OutOfSync 资源:
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: guestbook spec: syncPolicy: syncOptions: - ApplyOutOfSyncOnly=true
默认情况下,当 Argo CD 同步应用程序时,它会对与应用程序相关的 git 中的所有资源运行
kubectl apply
。ApplyOutOfSyncOnly=true
同步选项指示 Argo CD 跳过已经认为是“同步”的资源应用,仅应用“OutOfSync”的资源。当与ignoreDifferences
功能一起使用时,提供了一种管理 Argo CD 和 Argo Rollouts 之间 VirtualService 所需状态冲突的方法。
Argo CD 还有一个开放问题,它将有助于解决此问题。拟议的解决方案是在资源中引入注释,指示 Argo CD 尊重并保留指定路径中的差异,以允许其他控制器(例如 Argo Rollouts)管理它们。
考虑的替代方案
Rollout 对 Virtual Service 的所有权
早期的设计替代方案是,控制器不修改所引用的 VirtualService,而是 Rollout 控制器将创建、管理和拥有 Virtual Service。虽然这种方法对 GitOps 友好,但它引入了其他问题:
- 为了提供与 Rollout 引用 VirtualService 相同的灵活性,Rollout 需要内联 Istio 规范的大部分内容。网络是 Rollout 的责任范畴之外,使 Rollout 规范变得不必要地复杂。
- 如果 Istio 引入了一项功能,则该功能在 Argo Rollouts 中将不可用,直到在 Argo Rollouts 中实现。
与引用 Virtual Service 相比,这两个问题为用户和 Argo Rollouts 开发人员增加了更多的复杂性。
通过 SMI Adapter for Istio 支持 Istio
SMI 是服务网格接口,它作为服务网格所有常见特性的标准接口。该功能对 GitOps 友好,但原生 Istio 具有 SMI 目前不提供的额外功能。