资讯动态

Ingress NGINX Controller 金丝雀发布(Canary)完整指南:基于注解的灰度路由实现与实战部署

发布时间:2026/9/14 1:36:43 来源:尧图企业网站定制
Ingress NGINX Controller 金丝雀发布Canary完整指南基于注解的灰度路由实现与实战部署【免费下载链接】ingress-nginxIngress NGINX Controller for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/in/ingress-nginx导读本文围绕 Ingress NGINX Controller 的 Canary金丝雀路由能力讲解如何通过 Ingress 注解将一部分请求按权重、请求头或 Cookie 规则引流到新版本服务实现生产环境下的灰度发布。读完本文你将掌握从主/金丝雀 Deployment 与 Ingress 搭建、权重与条件式分流配置到请求验证与源码级分流原理的完整实战方案。什么是 Canary 路由在生产环境中发布新版本时直接全量切换往往伴随着不可控风险。金丝雀发布Canary Release的核心思路是让一小部分真实流量先访问新版本在确认新版本稳定后再逐步扩大流量比例最终完成全量切换。Ingress NGINX Controller 通过在Ingress 资源上设置特定注解来支持金丝雀路由无需改动 Deployment 或 Service 本身只需为同一个 Host 额外创建一个标记为 canary 的 Ingress控制器便会将主 Ingress 与新版本 Ingress 合并并按规则对流量进行分流。官方示例位于仓库 docs/examples/canary/README.md本文即以此示例为主线展开。从源码实现看金丝雀的核心机制是备选后端Alternative Backends合并控制器在处理 Ingress 时会将标记为 canary 的 Ingress 先搁置再通过mergeAlternativeBackends将金丝雀后端合并进主后端见 internal/ingress/controller/controller.go#L917-L927最终由 NGINX 依据流量整形策略TrafficShapingPolicy决定请求去向。第一步创建主版本 Deployment 与 Service先创建主版本应用及其 Service它是正式流量的默认目的地。示例中使用官方测试镜像registry.k8s.io/ingress-nginx/e2e-test-echo:v1.2.9该镜像会回显请求来源 Pod 的主机名便于后续验证流量分流效果echo --- # Deployment apiVersion: apps/v1 kind: Deployment metadata: name: production labels: app: production spec: replicas: 1 selector: matchLabels: app: production template: metadata: labels: app: production spec: containers: - name: production image: registry.k8s.io/ingress-nginx/e2e-test-echo:v1.2.9sha256:9920d084b452b38ee663005a455aa7ed12c15afa512741ea9596e206a189bdf0 ports: - containerPort: 80 env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP --- # Service apiVersion: v1 kind: Service metadata: name: production labels: app: production spec: ports: - port: 80 targetPort: 80 protocol: TCP name: http selector: app: production | kubectl apply -f -注意 Service 通过selector: app: production关联主版本 Pod端口 80 对容器端口 80。第二步创建金丝雀版本 Deployment 与 Service金丝雀版本新版本的 Deployment 与 Service 结构与主版本完全一致仅资源名称与标签不同本例使用canary。它可以承载新版本代码承接被分流过来的部分请求echo --- # Deployment apiVersion: apps/v1 kind: Deployment metadata: name: canary labels: app: canary spec: replicas: 1 selector: matchLabels: app: canary template: metadata: labels: app: canary spec: containers: - name: canary image: registry.k8s.io/ingress-nginx/e2e-test-echo:v1.2.9sha256:9920d084b452b38ee663005a455aa7ed12c15afa512741ea9596e206a189bdf0 ports: - containerPort: 80 env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP --- # Service apiVersion: v1 kind: Service metadata: name: canary labels: app: canary spec: ports: - port: 80 targetPort: 80 protocol: TCP name: http selector: app: canary | kubectl apply -f -第三步创建指向主版本的 Ingress接下来通过 Ingress 暴露主版本应用。这一步创建的 Ingress不携带任何 canary 相关注解它是金丝雀规则合并的目标主后端echo --- # Ingress apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: production annotations: spec: ingressClassName: nginx rules: - host: echo.prod.mydomain.com http: paths: - pathType: Prefix path: / backend: service: name: production port: number: 80 | kubectl apply -f -这里通过ingressClassName: nginx关联 Ingress NGINX ControllerHost 为echo.prod.mydomain.com路径前缀/全部路由到production服务。第四步创建指向金丝雀版本的 Ingress这是整个金丝雀发布的核心配置。为同一 Host 再创建一个携带 canary 注解的 Ingress控制器会将其作为主 Ingress 的替代后端合并。创建时务必注意以下几点Host 必须与主 Ingress 完全一致都是echo.prod.mydomain.com否则无法匹配合并nginx.ingress.kubernetes.io/canary: true注解是必需的它将该 Ingress 标记为金丝雀。若缺失此注解两个同 Host 的 Ingress 会发生冲突Ingress 冲突检测会将其视为规则重叠nginx.ingress.kubernetes.io/canary-weight: 50控制分流权重此处表示请求有 50% 的概率命中金丝雀版本其余 50% 仍流向主版本。echo --- # Ingress apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: canary annotations: nginx.ingress.kubernetes.io/canary: \true\ nginx.ingress.kubernetes.io/canary-weight: \50\ spec: ingressClassName: nginx rules: - host: echo.prod.mydomain.com http: paths: - pathType: Prefix path: / backend: service: name: canary port: number: 80 | kubectl apply -f -注解校验未启用却配置权重会怎样从源码 internal/ingress/annotations/canary/main.go 可以看到canary 注解的解析逻辑非常明确canary注解缺失或非法时Enabled默认置为falsecanary-weight缺失或非法时默认0canary-weight-total默认100若canary未启用Enabledfalse但同时配置了canary-weight 0、canary-by-header、canary-by-header-value、canary-by-header-pattern或canary-by-cookie中的任意一项解析会返回NewInvalidAnnotationConfiguration错误configured but not enabled。单元测试 internal/ingress/annotations/canary/main_test.go 中的TestAnnotations用例逐一验证了这些行为例如{canary disabled and weight, false, 20, , , true}即断言未启用 canary 却配置权重时解析必须报错。因此先设置canary: true再配置分流规则是正确姿势。第五步验证分流效果应用以上资源后使用curl --resolve将域名解析到 Ingress Controller 的 IP将INGRESS_CONTROLLER_IP替换为你的控制器实际地址发起连续请求for i in $(seq 1 10); do curl -s --resolve echo.prod.mydomain.com:80:$INGRESS_CONTROLLER_IP echo.prod.mydomain.com | grep Hostname; done由于e2e-test-echo镜像会回显处理请求的 Pod 主机名你会看到类似下面的输出说明主版本与金丝雀版本各承接了约一半请求Hostname: production-5c5f65d859-phqzc Hostname: canary-6697778457-zkfjf Hostname: canary-6697778457-zkfjf Hostname: production-5c5f65d859-phqzc Hostname: canary-6697778457-zkfjf Hostname: production-5c5f65d859-phqzc Hostname: production-5c5f65d859-phqzc Hostname: production-5c5f65d859-phqzc Hostname: canary-6697778457-zkfjf Hostname: production-5c5f65d859-phqzc10 次请求中canary出现 4 次、production出现 6 次接近 50/50 的权重比例符合canary-weight: 50的预期权重分流是随机的短样本存在波动属正常现象。验证通过后即可逐步调高权重完成灰度放量或移除金丝雀 Ingress 完成回滚。金丝雀注解全解析除了示例中用到的canary与canary-weight控制器还支持基于请求头、Cookie 的条件式分流。所有 canary 注解的官方说明见 docs/user-guide/nginx-configuration/annotations.md#canary注解常量定义于 internal/ingress/annotations/canary/main.go#L28-L36。注解取值作用说明nginx.ingress.kubernetes.io/canarytrue/false将该 Ingress 标记为金丝雀规则是其他 canary 注解生效的前提nginx.ingress.kubernetes.io/canary-by-header字符串指定用于触发分流到金丝雀的请求头名称。请求头值为always时强制路由到金丝雀为never时绝不路由到金丝雀其他值则忽略该头按优先级继续与其余规则比较nginx.ingress.kubernetes.io/canary-by-header-value字符串与canary-by-header配合使用自定义命中金丝雀的请求头值替代硬编码的always。未定义canary-by-header时不生效nginx.ingress.kubernetes.io/canary-by-header-patternPCRE 正则与canary-by-header-value行为类似但使用正则匹配。设置了canary-by-header-value时本注解被忽略正则处理出错时视为不匹配nginx.ingress.kubernetes.io/canary-by-cookie字符串指定用于触发分流到金丝雀的 Cookie 名称。Cookie 值为always时路由到金丝雀never时绝不路由其他值忽略并按优先级继续比较nginx.ingress.kubernetes.io/canary-weight整数0 weight-total随机请求被路由到金丝雀服务的百分比权重。0表示无请求进入金丝雀等于weight-total表示全部请求进入金丝雀nginx.ingress.kubernetes.io/canary-weight-total整数流量总权重默认100。例如设置canary-weight: 10且canary-weight-total: 1000时金丝雀承接 1% 流量优先级与匹配顺序金丝雀规则按如下优先级依次评估先命中的规则生效canary-by-header - canary-by-cookie - canary-weight即先检查请求头规则含 header-value / header-pattern再检查 Cookie 规则最后才回退到权重随机分流。这一设计允许你做指定测试用户通过 Header/Cookie优先走新版本其余流量按权重灰度的精细化发布。与 session affinity 的协同当金丝雀 Ingress 同时启用了会话粘性session affinity时注解nginx.ingress.kubernetes.io/affinity-canary-behavior决定其行为sticky默认被分流到金丝雀的用户后续请求继续命中金丝雀保证会话一致性legacy恢复历史行为忽略会话粘性对金丝雀的影响。从源码 internal/ingress/controller/controller.go#L1597-L1599 可以看到在合并备选后端时若CanaryBehavior不是legacy主后端的SessionAffinity配置会被深拷贝DeepCopyInto到金丝雀后端上从而让两种实现路径行为一致。其他注解的继承规则当 Ingress 被标记为 canary 后其上的其他非 canary 注解会被忽略自动继承主 Ingress 的对应配置仅有以下例外nginx.ingress.kubernetes.io/load-balancenginx.ingress.kubernetes.io/upstream-hash-by与 session affinity 相关的注解这意味着金丝雀 Ingress 只需关心如何分流而重写规则、超时、限流等行为均与主 Ingress 保持一致避免配置漂移。源码视角金丝雀流量整形与后端合并原理TrafficShapingPolicy流量的抽象层控制器解析完 canary 注解后会将结果封装为ingress.TrafficShapingPolicy见 internal/ingress/controller/controller.go#L1911-L1921包含权重、总权重、请求头、请求头值、请求头正则与 Cookie 六个字段随后挂载到对应 upstream 上// newTrafficShapingPolicy creates new ingress.TrafficShapingPolicy instance using canary configuration func newTrafficShapingPolicy(cfg *canary.Config) ingress.TrafficShapingPolicy { return ingress.TrafficShapingPolicy{ Weight: cfg.Weight, WeightTotal: cfg.WeightTotal, Header: cfg.Header, HeaderValue: cfg.HeaderValue, HeaderPattern: cfg.HeaderPattern, Cookie: cfg.Cookie, } }该策略最终在 internal/ingress/controller/nginx.go#L970 被写入 NGINX 配置模板TrafficShapingPolicy: backend.TrafficShapingPolicy由 NGINX 在请求处理时依据此策略完成实际分流。可以推断Weight/WeightTotal驱动随机按比例分流而Header/Cookie字段驱动条件匹配分流与注解文档的描述一一对应。后端合并的三个前置条件金丝雀后端不会盲目合并进主后端canMergeBackendinternal/ingress/controller/controller.go#L1578-L1580要求同时满足备选后端存在且主后端与备选后端名称不同防止金丝雀合并进自身主后端不是默认 upstreamdefUpstreamName主后端必须存在对应的 serverNoServer为假。合并时备选后端名称会被追加到主后端的AlternativeBackends列表若目标已存在则跳过幂等。此外只有存在至少一个非 canary Ingress 时才执行合并nonCanaryIngressExists见 controller.go#L1570-L1572这保证了金丝雀不会落单。冲突检测与已知限制控制器在路径冲突检测时同样感知 canary 状态controller.go#L1850-L1860只有两个 Ingress都标记为 canary 时才会判定为重叠冲突而 canary Ingress 与主 Ingress 的同 Host 同路径则被允许共存——这正是金丝雀机制能够成立的前提。同时需要了解官方文档明确指出的已知限制每条 Ingress 规则每个 Host path 组合最多只能应用一个 canary Ingress。如果需要同时灰度多个版本应当串行调整权重而非叠加多个金丝雀 Ingress。实战建议从低权重起步建议先设置canary-weight: 5配合canary-weight-total: 100观察日志、指标与错误率稳定后再逐步上调善用请求头灰度canary-by-header适合内部测试——QA 团队携带特定请求头如canary: always即可稳定命中新版本不受权重随机性影响验证会话一致性若应用依赖会话注意affinity-canary-behavior默认sticky会保持金丝雀用户会话连续升级前需评估两种行为对业务的影响回滚即删除移除金丝雀 Ingresskubectl delete ingress canary即可瞬间恢复 100% 主版本流量无需重建资源。延伸阅读金丝雀注解完整文档docs/user-guide/nginx-configuration/annotations.md另见注解速查表 annotations.md#L43-L49注解解析器实现internal/ingress/annotations/canary/main.go 与单元测试 internal/ingress/annotations/canary/main_test.go后端合并与流量整形internal/ingress/controller/controller.go合并逻辑见 L917-L927、L1569-L1619、L1911-L1921会话亲和示例docs/examples/affinity/cookie/README.md【免费下载链接】ingress-nginxIngress NGINX Controller for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/in/ingress-nginx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价