├── .gitignore ├── test ├── e2e │ ├── types.go │ ├── testdata │ │ ├── grpcroute-basic.yml │ │ ├── grpcroute-header.yml │ │ ├── httproute-basic.yml │ │ ├── httproute-header.yml │ │ ├── tcproute-basic.yml │ │ ├── tlsroute-basic.yml │ │ ├── single-tcproute-rollout.yml │ │ ├── single-grpcroute-rollout.yml │ │ ├── single-httproute-rollout.yml │ │ ├── single-tlsroute-rollout.yml │ │ ├── single-httproute-label-rollout.yml │ │ ├── single-header-based-grpcroute-rollout.yml │ │ ├── single-header-based-httproute-rollout.yml │ │ ├── single-httproute-filters-rollout.yml │ │ ├── single-grpcroute-filters-rollout.yml │ │ ├── grpcroute-filters.yml │ │ └── httproute-filters.yml │ ├── plugin_test.go │ ├── utils.go │ └── constants.go └── cluster-setup │ ├── cluster-config.yml │ ├── traefik-values.yml │ ├── argo-rollouts-values.yml │ └── sanity-check.sh ├── docs ├── assets │ ├── logo.png │ ├── versions.js │ └── versions.css ├── images │ ├── gateway-api.png │ ├── quick-start │ │ ├── canary-end.png │ │ ├── canary-start.png │ │ └── canary-in-progress.png │ ├── advanced-deployments │ │ ├── downward-api.png │ │ ├── ideal-scenario.png │ │ ├── initial-problem.png │ │ └── pinning-versions.png │ ├── header-based-routing │ │ ├── user-routing.png │ │ ├── using-a-header.png │ │ ├── request-routing.png │ │ └── a-route-with-headers.png │ ├── multiple-routes │ │ └── multiple-routes.png │ └── contributing │ │ └── argo-rollouts-plugin-system-architecture.png ├── requirements.txt ├── features │ ├── grpc.md │ ├── tcp.md │ └── tls.md ├── index.md ├── provider-status.md └── installation.md ├── examples ├── linkerd │ ├── namespace.yaml │ ├── cluster-role.yaml │ ├── cluster-role-binding.yaml │ ├── argo-rollouts-plugin.yaml │ ├── kind-cluster.yaml │ ├── httproute.yaml │ ├── steps.sh │ ├── service.yaml │ ├── setup.sh │ ├── rollout.yaml │ └── README.md ├── linkerd-header-based │ ├── namespace.yaml │ ├── cluster-role.yaml │ ├── kind-cluster.yaml │ ├── cluster-role-binding.yaml │ ├── argo-rollouts-plugin.yaml │ ├── httproute.yaml │ ├── service.yaml │ └── rollout.yaml ├── aws-gateway-api-controller-lattice │ ├── lattice-argrollout.jpg │ ├── prodcatalogsvc.yaml │ ├── gateway.yaml │ ├── configmap-gatewayapicontrollerforargo.yaml │ ├── httproute.yaml │ ├── frontendsvc.yaml │ ├── proddetails-lat-svc.yaml │ ├── ClusterRoleForArgoGatewayAPI.yaml │ ├── proddetail-tgp.yaml │ ├── frontenddep.yaml │ ├── prodcatalogdep.yaml │ └── proddetail-rollout.yaml ├── cilium-header-based │ ├── kind-cluster.yaml │ ├── httproute.yaml │ ├── service.yaml │ └── rollout.yaml ├── kong │ ├── gateway.yml │ ├── gatewayclass.yml │ ├── canary.yml │ ├── stable.yml │ ├── cluster-role.yml │ ├── kong-cluster-role.yml │ ├── kong-cluster-role-binding.yml │ ├── cluster-role-binding.yml │ ├── httproute.yml │ ├── rollout.yml │ └── README.md ├── google-cloud │ ├── gateway.yml │ ├── canary.yml │ ├── stable.yml │ ├── cluster-role.yml │ ├── cluster-role-binding.yml │ ├── httproute.yml │ ├── rollout.yml │ └── README.md ├── cilium │ ├── canary.yml │ ├── stable.yml │ ├── cluster-role.yml │ ├── gateway.yml │ ├── cluster-role-binding.yml │ ├── httproute.yml │ ├── rollout.yml │ └── README.md ├── nginx │ ├── canary.yml │ ├── stable.yml │ ├── gateway.yml │ ├── httproute.yml │ ├── rollout.yml │ └── README.md ├── traefik │ ├── canary.yml │ ├── stable.yml │ ├── gateway.yml │ ├── cluster-role.yml │ ├── cluster-role-binding.yml │ ├── httproute.yml │ └── rollout.yml ├── envoygateway │ ├── cluster-role.yaml │ ├── canary.yaml │ ├── stable.yaml │ ├── cluster-role-binding.yaml │ ├── gateway.yaml │ ├── httproute.yaml │ ├── rollout.yaml │ └── README.md └── google-cloud-experiment │ ├── gateway.yaml │ ├── service.yaml │ ├── httproute.yaml │ ├── rollout.yaml │ └── Readme.md ├── RELEASE_NOTES.md ├── internal ├── defaults │ └── defaults.go └── utils │ ├── types.go │ └── common.go ├── .golangci.yaml ├── .readthedocs.yaml ├── Dockerfile ├── pkg └── plugin │ ├── errors.go │ ├── labels.go │ ├── tcproute.go │ ├── tlsroute.go │ ├── experiment.go │ ├── types.go │ └── experiment_test.go ├── mkdocs.yml ├── main.go ├── README.md ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── ci.yaml │ ├── docker-publish.yaml │ └── release.yaml ├── Makefile └── go.mod /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | dist 4 | .idea -------------------------------------------------------------------------------- /test/e2e/types.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | type contextKey string 4 | -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/images/gateway-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/gateway-api.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.4.2 2 | mkdocs-material==8.5.11 3 | markdown_include==0.8.0 4 | pygments==2.13.0 5 | jinja2==3.1.2 6 | markdown==3.3.7 7 | -------------------------------------------------------------------------------- /docs/images/quick-start/canary-end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/quick-start/canary-end.png -------------------------------------------------------------------------------- /docs/images/quick-start/canary-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/quick-start/canary-start.png -------------------------------------------------------------------------------- /docs/images/quick-start/canary-in-progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/quick-start/canary-in-progress.png -------------------------------------------------------------------------------- /examples/linkerd/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: default 6 | annotations: 7 | linkerd.io/inject: enabled 8 | spec: {} 9 | -------------------------------------------------------------------------------- /docs/images/advanced-deployments/downward-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/advanced-deployments/downward-api.png -------------------------------------------------------------------------------- /docs/images/header-based-routing/user-routing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/header-based-routing/user-routing.png -------------------------------------------------------------------------------- /docs/images/multiple-routes/multiple-routes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/multiple-routes/multiple-routes.png -------------------------------------------------------------------------------- /docs/images/advanced-deployments/ideal-scenario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/advanced-deployments/ideal-scenario.png -------------------------------------------------------------------------------- /docs/images/header-based-routing/using-a-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/header-based-routing/using-a-header.png -------------------------------------------------------------------------------- /examples/linkerd-header-based/namespace.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: default 6 | annotations: 7 | linkerd.io/inject: enabled 8 | spec: {} 9 | -------------------------------------------------------------------------------- /docs/images/advanced-deployments/initial-problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/advanced-deployments/initial-problem.png -------------------------------------------------------------------------------- /docs/images/advanced-deployments/pinning-versions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/advanced-deployments/pinning-versions.png -------------------------------------------------------------------------------- /docs/images/header-based-routing/request-routing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/header-based-routing/request-routing.png -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | - Compiled against Gateway API v1.4 (previously v1.1) 2 | - Now supports the standard CORS filter type in HTTPRoute 3 | - Golang updated to 1.24 and K8s client libraries to 0.34.x 4 | -------------------------------------------------------------------------------- /docs/images/header-based-routing/a-route-with-headers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/header-based-routing/a-route-with-headers.png -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/lattice-argrollout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/examples/aws-gateway-api-controller-lattice/lattice-argrollout.jpg -------------------------------------------------------------------------------- /docs/images/contributing/argo-rollouts-plugin-system-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/HEAD/docs/images/contributing/argo-rollouts-plugin-system-architecture.png -------------------------------------------------------------------------------- /examples/cilium-header-based/kind-cluster.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | name: cilium-header-based 4 | nodes: 5 | - role: control-plane 6 | - role: worker 7 | networking: 8 | disableDefaultCNI: true 9 | -------------------------------------------------------------------------------- /internal/defaults/defaults.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | const ( 4 | ConfigMap = "argo-gatewayapi-configmap" 5 | InProgressLabelKey = "rollouts.argoproj.io/gatewayapi-canary" 6 | InProgressLabelValue = "in-progress" 7 | ) 8 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | modules-download-mode: readonly 3 | linters: 4 | enable: 5 | - vet 6 | - gofmt 7 | - goimports 8 | - unused 9 | - ineffassign 10 | - unconvert 11 | - misspell 12 | disable-all: true 13 | -------------------------------------------------------------------------------- /examples/kong/gateway.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1beta1 2 | kind: Gateway 3 | metadata: 4 | name: kong 5 | spec: 6 | gatewayClassName: kong 7 | listeners: 8 | - name: proxy 9 | port: 80 10 | protocol: HTTP 11 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | formats: all 3 | mkdocs: 4 | configuration: mkdocs.yml 5 | fail_on_warning: false 6 | python: 7 | install: 8 | - requirements: docs/requirements.txt 9 | build: 10 | os: "ubuntu-22.04" 11 | tools: 12 | python: "3.7" -------------------------------------------------------------------------------- /examples/kong/gatewayclass.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1beta1 2 | kind: GatewayClass 3 | metadata: 4 | name: kong 5 | annotations: 6 | konghq.com/gatewayclass-unmanaged: 'true' 7 | 8 | spec: 9 | controllerName: konghq.com/kic-gateway-controller -------------------------------------------------------------------------------- /examples/google-cloud/gateway.yml: -------------------------------------------------------------------------------- 1 | kind: Gateway 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | metadata: 4 | name: internal-http 5 | spec: 6 | gatewayClassName: gke-l7-rilb 7 | listeners: 8 | - name: http 9 | protocol: HTTP 10 | port: 80 11 | -------------------------------------------------------------------------------- /examples/cilium/canary.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-canary-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/cilium/stable.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-stable-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/kong/canary.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-canary-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/kong/stable.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-stable-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/nginx/canary.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-canary-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/nginx/stable.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-stable-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/traefik/canary.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-canary-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/traefik/stable.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-stable-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/google-cloud/canary.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-canary-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/google-cloud/stable.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: argo-rollouts-stable-service 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: http 9 | protocol: TCP 10 | name: http 11 | selector: 12 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/kong/cluster-role.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: gateway-controller-role 5 | namespace: argo-rollouts 6 | rules: 7 | - apiGroups: 8 | - "*" 9 | resources: 10 | - "*" 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /examples/traefik/gateway.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: Gateway 3 | metadata: 4 | name: traefik-gateway 5 | spec: 6 | gatewayClassName: traefik 7 | listeners: 8 | - protocol: HTTP 9 | name: web 10 | port: 8000 # Default endpoint for Helm chart -------------------------------------------------------------------------------- /examples/cilium/cluster-role.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: gateway-controller-role 5 | namespace: argo-rollouts 6 | rules: 7 | - apiGroups: 8 | - "*" 9 | resources: 10 | - "*" 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /examples/traefik/cluster-role.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: gateway-controller-role 5 | namespace: argo-rollouts 6 | rules: 7 | - apiGroups: 8 | - "*" 9 | resources: 10 | - "*" 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /examples/google-cloud/cluster-role.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: gateway-controller-role 5 | namespace: argo-rollouts 6 | rules: 7 | - apiGroups: 8 | - "*" 9 | resources: 10 | - "*" 11 | verbs: 12 | - "*" -------------------------------------------------------------------------------- /examples/envoygateway/cluster-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: gateway-controller-role 6 | namespace: argo-rollouts 7 | rules: 8 | - apiGroups: 9 | - "*" 10 | resources: 11 | - "*" 12 | verbs: 13 | - "*" -------------------------------------------------------------------------------- /examples/linkerd/cluster-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: gateway-controller-role 6 | namespace: argo-rollouts 7 | rules: 8 | - apiGroups: 9 | - "*" 10 | resources: 11 | - "*" 12 | verbs: 13 | - "*" 14 | -------------------------------------------------------------------------------- /examples/kong/kong-cluster-role.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | name: kong-controller-role 6 | namespace: kong 7 | rules: 8 | - apiGroups: 9 | - "*" 10 | resources: 11 | - "*" 12 | verbs: 13 | - "*" -------------------------------------------------------------------------------- /examples/nginx/gateway.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1beta1 2 | kind: Gateway 3 | metadata: 4 | name: argo-rollouts-gateway 5 | spec: 6 | gatewayClassName: nginx 7 | listeners: 8 | - protocol: HTTP 9 | name: web 10 | port: 80 # one of Gateway entrypoint that we created at 1 step -------------------------------------------------------------------------------- /examples/envoygateway/canary.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: argo-rollouts-canary-service 6 | namespace: default 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: http 11 | protocol: TCP 12 | name: http 13 | selector: 14 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/envoygateway/stable.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: argo-rollouts-stable-service 6 | namespace: default 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: http 11 | protocol: TCP 12 | name: http 13 | selector: 14 | app: rollouts-demo -------------------------------------------------------------------------------- /examples/linkerd-header-based/cluster-role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: gateway-controller-role 6 | namespace: argo-rollouts 7 | rules: 8 | - apiGroups: 9 | - "*" 10 | resources: 11 | - "*" 12 | verbs: 13 | - "*" 14 | -------------------------------------------------------------------------------- /examples/cilium/gateway.yml: -------------------------------------------------------------------------------- 1 | kind: Gateway 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | metadata: 4 | name: cilium 5 | spec: 6 | gatewayClassName: cilium 7 | listeners: 8 | - name: http 9 | protocol: HTTP 10 | port: 80 11 | allowedRoutes: 12 | namespaces: 13 | from: All 14 | -------------------------------------------------------------------------------- /test/cluster-setup/cluster-config.yml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | - role: worker 6 | labels: 7 | argo-rollouts-controller: true 8 | extraMounts: 9 | - hostPath: ./dist/ 10 | containerPath: /Volumes/ 11 | - role: worker 12 | - role: worker 13 | -------------------------------------------------------------------------------- /examples/google-cloud-experiment/gateway.yaml: -------------------------------------------------------------------------------- 1 | kind: Gateway 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | metadata: 4 | name: app-gateway 5 | namespace: demo 6 | spec: 7 | gatewayClassName: gke-l7-rilb 8 | listeners: 9 | - name: http 10 | protocol: HTTP 11 | port: 80 12 | allowedRoutes: 13 | namespaces: 14 | from: All 15 | -------------------------------------------------------------------------------- /examples/kong/kong-cluster-role-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: kong-admin 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: kong-controller-role 9 | subjects: 10 | - namespace: kong 11 | kind: ServiceAccount 12 | name: kong-1685611187-kong -------------------------------------------------------------------------------- /examples/linkerd-header-based/kind-cluster.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | name: linkerd-header-based 4 | nodes: 5 | - role: control-plane 6 | kubeadmConfigPatches: 7 | - | 8 | kind: InitConfiguration 9 | nodeRegistration: 10 | kubeletExtraArgs: 11 | node-labels: "ingress-ready=true" 12 | -------------------------------------------------------------------------------- /examples/cilium/cluster-role-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: gateway-admin 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: gateway-controller-role 9 | subjects: 10 | - namespace: argo-rollouts 11 | kind: ServiceAccount 12 | name: argo-rollouts -------------------------------------------------------------------------------- /examples/google-cloud/cluster-role-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: gateway-admin 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: gateway-controller-role 9 | subjects: 10 | - namespace: argo-rollouts 11 | kind: ServiceAccount 12 | name: argo-rollouts -------------------------------------------------------------------------------- /examples/kong/cluster-role-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: gateway-admin-rollouts 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: gateway-controller-role 9 | subjects: 10 | - namespace: argo-rollouts 11 | kind: ServiceAccount 12 | name: argo-rollouts -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/prodcatalogsvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: prodcatalog 5 | namespace: workshop 6 | labels: 7 | app: prodcatalog 8 | spec: 9 | type: ClusterIP 10 | selector: 11 | app: prodcatalog 12 | ports: 13 | - name: http 14 | port: 5000 15 | targetPort: 5000 16 | protocol: TCP 17 | -------------------------------------------------------------------------------- /examples/traefik/cluster-role-binding.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: gateway-admin-rollouts 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: gateway-controller-role 9 | subjects: 10 | - namespace: argo-rollouts 11 | kind: ServiceAccount 12 | name: argo-rollouts -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1beta1 2 | kind: Gateway 3 | metadata: 4 | name: latcansvcnet 5 | annotations: 6 | application-networking.k8s.aws/lattice-vpc-association: "true" 7 | spec: 8 | gatewayClassName: amazon-vpc-lattice 9 | listeners: 10 | - name: http 11 | protocol: HTTP 12 | port: 80 13 | -------------------------------------------------------------------------------- /examples/envoygateway/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: gateway-admin 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: gateway-controller-role 10 | subjects: 11 | - namespace: argo-rollouts 12 | kind: ServiceAccount 13 | name: argo-rollouts -------------------------------------------------------------------------------- /examples/linkerd/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: gateway-admin 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: gateway-controller-role 10 | subjects: 11 | - namespace: argo-rollouts 12 | kind: ServiceAccount 13 | name: argo-rollouts 14 | -------------------------------------------------------------------------------- /examples/linkerd-header-based/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: gateway-admin 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: gateway-controller-role 10 | subjects: 11 | - namespace: argo-rollouts 12 | kind: ServiceAccount 13 | name: argo-rollouts 14 | -------------------------------------------------------------------------------- /examples/traefik/httproute.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: HTTPRoute 3 | metadata: 4 | name: argo-rollouts-http-route 5 | spec: 6 | parentRefs: 7 | - name: traefik-gateway 8 | namespace: default 9 | rules: 10 | - backendRefs: 11 | - name: argo-rollouts-stable-service 12 | port: 80 13 | - name: argo-rollouts-canary-service 14 | port: 80 -------------------------------------------------------------------------------- /test/e2e/testdata/grpcroute-basic.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: GRPCRoute 3 | metadata: 4 | name: grpcroute-basic 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | rules: 10 | - backendRefs: 11 | - name: argo-rollouts-stable-service 12 | port: 80 13 | - name: argo-rollouts-canary-service 14 | port: 80 15 | -------------------------------------------------------------------------------- /test/e2e/testdata/grpcroute-header.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: GRPCRoute 3 | metadata: 4 | name: grpcroute-header 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | rules: 10 | - backendRefs: 11 | - name: argo-rollouts-stable-service 12 | port: 80 13 | - name: argo-rollouts-canary-service 14 | port: 80 15 | -------------------------------------------------------------------------------- /examples/nginx/httproute.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1beta1 2 | kind: HTTPRoute 3 | metadata: 4 | name: argo-rollouts-http-route 5 | spec: 6 | parentRefs: 7 | - name: argo-rollouts-gateway 8 | namespace: default 9 | rules: 10 | - backendRefs: 11 | - name: argo-rollouts-stable-service 12 | port: 80 13 | - name: argo-rollouts-canary-service 14 | port: 80 -------------------------------------------------------------------------------- /test/cluster-setup/traefik-values.yml: -------------------------------------------------------------------------------- 1 | experimental: 2 | kubernetesGateway: 3 | enabled: true 4 | 5 | providers: 6 | kubernetesCRD: 7 | enabled: false 8 | kubernetesIngress: 9 | enabled: false 10 | kubernetesGateway: 11 | enabled: true 12 | experimentalChannel: true 13 | 14 | ingressClass: 15 | enabled: false 16 | 17 | service: 18 | enabled: false 19 | 20 | logs: 21 | general: 22 | level: DEBUG 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM golang:1.24 AS builder 2 | 3 | ENV GO111MODULE=on 4 | ARG TARGETARCH 5 | 6 | WORKDIR /app 7 | 8 | COPY . . 9 | 10 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -ldflags "-s -w" -o rollouts-plugin-trafficrouter-gatewayapi . 11 | 12 | FROM alpine:3.19.0 13 | 14 | ARG TARGETARCH 15 | 16 | USER 999 17 | 18 | COPY --from=builder /app/rollouts-plugin-trafficrouter-gatewayapi /bin/ 19 | -------------------------------------------------------------------------------- /test/e2e/testdata/httproute-basic.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: HTTPRoute 3 | metadata: 4 | name: httproute-basic 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | namespace: default 10 | rules: 11 | - backendRefs: 12 | - name: argo-rollouts-stable-service 13 | port: 80 14 | - name: argo-rollouts-canary-service 15 | port: 80 16 | -------------------------------------------------------------------------------- /test/e2e/testdata/httproute-header.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: HTTPRoute 3 | metadata: 4 | name: httproute-header 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | namespace: default 10 | rules: 11 | - backendRefs: 12 | - name: argo-rollouts-stable-service 13 | port: 80 14 | - name: argo-rollouts-canary-service 15 | port: 80 16 | -------------------------------------------------------------------------------- /examples/linkerd/argo-rollouts-plugin.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: argo-rollouts-config # must be so name 6 | namespace: argo-rollouts # must be in this namespace 7 | data: 8 | trafficRouterPlugins: |- 9 | - name: "argoproj-labs/gatewayAPI" 10 | location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.2.0/gateway-api-plugin-linux-arm64" 11 | -------------------------------------------------------------------------------- /examples/linkerd-header-based/argo-rollouts-plugin.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: argo-rollouts-config # must be so name 6 | namespace: argo-rollouts # must be in this namespace 7 | data: 8 | trafficRouterPlugins: |- 9 | - name: "argoproj-labs/gatewayAPI" 10 | location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.8.0/gatewayapi-plugin-linux-amd64" 11 | -------------------------------------------------------------------------------- /examples/google-cloud/httproute.yml: -------------------------------------------------------------------------------- 1 | kind: HTTPRoute 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | metadata: 4 | name: argo-rollouts-http-route 5 | spec: 6 | parentRefs: 7 | - kind: Gateway 8 | name: internal-http 9 | hostnames: 10 | - "demo.example.com" 11 | rules: 12 | - backendRefs: 13 | - name: argo-rollouts-stable-service 14 | port: 80 15 | - name: argo-rollouts-canary-service 16 | port: 80 17 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/configmap-gatewayapicontrollerforargo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: argo-rollouts-config # must be named like this 5 | namespace: argo-rollouts # must be in this namespace 6 | data: 7 | trafficRouterPlugins: |- 8 | - name: "argoproj-labs/gatewayAPI" 9 | location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.6.0/gatewayapi-plugin-linux-amd64" 10 | 11 | -------------------------------------------------------------------------------- /examples/envoygateway/gateway.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | kind: GatewayClass 4 | metadata: 5 | name: eg 6 | namespace: default 7 | spec: 8 | controllerName: gateway.envoyproxy.io/gatewayclass-controller 9 | --- 10 | apiVersion: gateway.networking.k8s.io/v1beta1 11 | kind: Gateway 12 | metadata: 13 | name: eg 14 | namespace: default 15 | spec: 16 | gatewayClassName: eg 17 | listeners: 18 | - name: http 19 | protocol: HTTP 20 | port: 80 -------------------------------------------------------------------------------- /test/e2e/testdata/tcproute-basic.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1alpha2 2 | kind: TCPRoute 3 | metadata: 4 | name: tcproute-basic 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | sectionName: tcp 10 | namespace: default 11 | kind: Gateway 12 | rules: 13 | - backendRefs: 14 | - name: argo-rollouts-stable-service 15 | port: 80 16 | - name: argo-rollouts-canary-service 17 | port: 80 18 | -------------------------------------------------------------------------------- /examples/linkerd/kind-cluster.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | kubeadmConfigPatches: 6 | - | 7 | kind: InitConfiguration 8 | nodeRegistration: 9 | kubeletExtraArgs: 10 | node-labels: "ingress-ready=true" 11 | extraPortMappings: 12 | - containerPort: 80 13 | hostPort: 80 14 | protocol: TCP 15 | - containerPort: 443 16 | hostPort: 443 17 | protocol: TCP 18 | -------------------------------------------------------------------------------- /internal/utils/types.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | 6 | v1 "k8s.io/client-go/kubernetes/typed/core/v1" 7 | ) 8 | 9 | type CreateConfigMapOptions struct { 10 | Clientset v1.ConfigMapInterface 11 | Ctx context.Context 12 | } 13 | 14 | type UpdateConfigMapOptions struct { 15 | Clientset v1.ConfigMapInterface 16 | Ctx context.Context 17 | ConfigMapKey string 18 | } 19 | 20 | type Task struct { 21 | Action func() error 22 | ReverseAction func() error 23 | } 24 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/httproute.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1beta1 2 | kind: HTTPRoute 3 | metadata: 4 | name: latcan-app 5 | spec: 6 | parentRefs: 7 | - name: latcansvcnet 8 | sectionName: http 9 | rules: 10 | - backendRefs: 11 | - name: proddetail-stable-service 12 | namespace: workshop 13 | kind: Service 14 | port: 3000 15 | - name: proddetail-canary-service 16 | namespace: workshop 17 | kind: Service 18 | port: 3000 19 | -------------------------------------------------------------------------------- /test/e2e/testdata/tlsroute-basic.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1alpha2 2 | kind: TLSRoute 3 | metadata: 4 | name: tlsroute-basic 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | sectionName: tls 10 | namespace: default 11 | kind: Gateway 12 | hostnames: 13 | - "example.com" 14 | rules: 15 | - backendRefs: 16 | - name: argo-rollouts-stable-service 17 | port: 443 18 | - name: argo-rollouts-canary-service 19 | port: 443 20 | -------------------------------------------------------------------------------- /examples/cilium/httproute.yml: -------------------------------------------------------------------------------- 1 | kind: HTTPRoute 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | metadata: 4 | name: argo-rollouts-http-route 5 | spec: 6 | parentRefs: 7 | - kind: Gateway 8 | name: cilium 9 | hostnames: 10 | - "demo.example.com" 11 | rules: 12 | - matches: 13 | - path: 14 | type: PathPrefix 15 | value: / 16 | backendRefs: 17 | - name: argo-rollouts-stable-service 18 | kind: Service 19 | port: 80 20 | - name: argo-rollouts-canary-service 21 | kind: Service 22 | port: 80 23 | -------------------------------------------------------------------------------- /examples/google-cloud-experiment/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: demo-app-canary 6 | namespace: demo 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: http 11 | protocol: TCP 12 | name: http 13 | selector: 14 | app: demo-app 15 | --- 16 | apiVersion: v1 17 | kind: Service 18 | metadata: 19 | name: demo-app-stable 20 | namespace: demo 21 | spec: 22 | ports: 23 | - port: 80 24 | targetPort: http 25 | protocol: TCP 26 | name: http 27 | selector: 28 | app: demo-app 29 | -------------------------------------------------------------------------------- /examples/envoygateway/httproute.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: HTTPRoute 3 | apiVersion: gateway.networking.k8s.io/v1beta1 4 | metadata: 5 | name: argo-rollouts-http-route 6 | namespace: default 7 | spec: 8 | parentRefs: 9 | - name: eg 10 | hostnames: 11 | - "demo.example.com" 12 | rules: 13 | - matches: 14 | - path: 15 | type: PathPrefix 16 | value: / 17 | backendRefs: 18 | - name: argo-rollouts-stable-service 19 | kind: Service 20 | port: 80 21 | - name: argo-rollouts-canary-service 22 | kind: Service 23 | port: 80 -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/frontendsvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend 5 | namespace: workshop 6 | labels: 7 | app: frontend 8 | annotations: 9 | service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip 10 | service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing 11 | spec: 12 | loadBalancerClass: eks.amazonaws.com/nlb 13 | type: LoadBalancer 14 | selector: 15 | app: frontend 16 | ports: 17 | - name: http 18 | port: 80 19 | targetPort: 9000 20 | protocol: TCP 21 | -------------------------------------------------------------------------------- /examples/google-cloud-experiment/httproute.yaml: -------------------------------------------------------------------------------- 1 | kind: HTTPRoute 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | metadata: 4 | name: demo-app-route 5 | namespace: demo 6 | labels: 7 | managed-by: external-dns 8 | spec: 9 | parentRefs: 10 | - kind: Gateway 11 | name: app-gateway 12 | namespace: demo 13 | hostnames: 14 | - "demo.example.com" 15 | rules: 16 | - backendRefs: 17 | - name: demo-app-stable 18 | namespace: demo 19 | port: 80 20 | weight: 100 21 | - name: demo-app-canary 22 | namespace: demo 23 | port: 80 24 | weight: 0 25 | -------------------------------------------------------------------------------- /examples/cilium-header-based/httproute.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | kind: HTTPRoute 4 | metadata: 5 | name: argo-rollouts-http-route 6 | namespace: default 7 | spec: 8 | parentRefs: 9 | - group: "" 10 | name: argo-rollouts-service 11 | kind: Service 12 | port: 80 13 | rules: 14 | - backendRefs: 15 | - name: argo-rollouts-stable-service 16 | group: "" 17 | port: 80 18 | kind: Service 19 | - name: argo-rollouts-canary-service 20 | group: "" 21 | port: 80 22 | kind: Service 23 | -------------------------------------------------------------------------------- /examples/linkerd/httproute.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | kind: HTTPRoute 4 | metadata: 5 | name: argo-rollouts-http-route 6 | namespace: default 7 | spec: 8 | parentRefs: 9 | - group: "core" 10 | name: argo-rollouts-service 11 | kind: Service 12 | port: 80 13 | rules: 14 | - backendRefs: 15 | - name: argo-rollouts-stable-service 16 | group: "core" 17 | port: 80 18 | kind: Service 19 | - name: argo-rollouts-canary-service 20 | group: "core" 21 | port: 80 22 | kind: Service 23 | -------------------------------------------------------------------------------- /examples/kong/httproute.yml: -------------------------------------------------------------------------------- 1 | kind: HTTPRoute 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | metadata: 4 | name: argo-rollouts-http-route 5 | annotations: 6 | konghq.com/strip-path: 'true' 7 | spec: 8 | parentRefs: 9 | - kind: Gateway 10 | name: kong 11 | hostnames: 12 | - "demo.example.com" 13 | rules: 14 | - matches: 15 | - path: 16 | type: PathPrefix 17 | value: / 18 | backendRefs: 19 | - name: argo-rollouts-stable-service 20 | kind: Service 21 | port: 80 22 | - name: argo-rollouts-canary-service 23 | kind: Service 24 | port: 80 25 | -------------------------------------------------------------------------------- /examples/linkerd/steps.sh: -------------------------------------------------------------------------------- 1 | 2 | # watch Route 3 | kubectl -n argo-demo get httproute.gateway.networking.k8s.io/argo-rollouts-http-route -o custom-columns=NAME:.metadata.name,PRIMARY_SERVICE:.spec.rules[0].backendRefs[0].name,PRIMARY_WEIGHT:.spec.rules[0].backendRefs[0].weight,CANARY_SERVICE:.spec.rules[0].backendRefs[1].name,CANARY_WEIGHT:.spec.rules[0].backendRefs[1].weight 4 | 5 | # View traffic 6 | linkerd viz -n argo-demo stat rs --from deploy/slow-cooker 7 | 8 | # View Rollout 9 | kubectl argo rollouts -n argo-demo get rollout rollouts-demo 10 | 11 | watch k argo rollouts -n argo-demo get rollout rollouts-demo 12 | -------------------------------------------------------------------------------- /examples/linkerd-header-based/httproute.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: gateway.networking.k8s.io/v1beta1 3 | kind: HTTPRoute 4 | metadata: 5 | name: argo-rollouts-http-route 6 | namespace: default 7 | spec: 8 | parentRefs: 9 | - group: "core" 10 | name: argo-rollouts-service 11 | kind: Service 12 | port: 80 13 | rules: 14 | - backendRefs: 15 | - name: argo-rollouts-stable-service 16 | group: "core" 17 | port: 80 18 | kind: Service 19 | - name: argo-rollouts-canary-service 20 | group: "core" 21 | port: 80 22 | kind: Service 23 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/proddetails-lat-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: proddetail-stable-service 5 | namespace: workshop 6 | labels: 7 | app: proddetail 8 | spec: 9 | ports: 10 | - name: "http" 11 | port: 3000 12 | targetPort: 3000 13 | selector: 14 | app: proddetail 15 | 16 | --- 17 | 18 | apiVersion: v1 19 | kind: Service 20 | metadata: 21 | name: proddetail-canary-service 22 | namespace: workshop 23 | labels: 24 | app: proddetail 25 | spec: 26 | ports: 27 | - name: "http" 28 | port: 3000 29 | targetPort: 3000 30 | selector: 31 | app: proddetail -------------------------------------------------------------------------------- /examples/linkerd/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: argo-rollouts-service 6 | spec: 7 | ports: 8 | - port: 80 9 | targetPort: http 10 | selector: 11 | app: rollouts-demo 12 | --- 13 | apiVersion: v1 14 | kind: Service 15 | metadata: 16 | name: argo-rollouts-canary-service 17 | spec: 18 | ports: 19 | - port: 80 20 | targetPort: http 21 | selector: 22 | app: rollouts-demo 23 | --- 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: argo-rollouts-stable-service 28 | spec: 29 | ports: 30 | - port: 80 31 | targetPort: http 32 | selector: 33 | app: rollouts-demo 34 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/ClusterRoleForArgoGatewayAPI.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: gateway-controller-role 6 | namespace: argo-rollouts 7 | rules: 8 | - apiGroups: 9 | - "*" 10 | resources: 11 | - "*" 12 | verbs: 13 | - "*" 14 | --- 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: ClusterRoleBinding 17 | metadata: 18 | name: gateway-admin 19 | roleRef: 20 | apiGroup: rbac.authorization.k8s.io 21 | kind: ClusterRole 22 | name: gateway-controller-role 23 | subjects: 24 | - namespace: argo-rollouts 25 | kind: ServiceAccount 26 | name: argo-rollouts 27 | -------------------------------------------------------------------------------- /test/e2e/plugin_test.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/sirupsen/logrus" 9 | "sigs.k8s.io/e2e-framework/pkg/env" 10 | "sigs.k8s.io/e2e-framework/pkg/envconf" 11 | ) 12 | 13 | var ( 14 | global env.Environment 15 | ) 16 | 17 | func TestMain(m *testing.M) { 18 | global = env.New() 19 | os.Exit(global.Run(m)) 20 | } 21 | 22 | func setupEnvironment(ctx context.Context, t *testing.T, config *envconf.Config) context.Context { 23 | logrus.SetLevel(logrus.InfoLevel) 24 | logrus.SetFormatter(&logrus.TextFormatter{ 25 | ForceColors: true, 26 | PadLevelText: true, 27 | FullTimestamp: true, 28 | }) 29 | return ctx 30 | } 31 | -------------------------------------------------------------------------------- /examples/linkerd/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | kind delete cluster &>/dev/null 3 | kind create cluster --config manifests/kind-cluster.yaml 4 | kubectl ns default 5 | 6 | linkerd install --crds | kubectl apply -f - 7 | 8 | linkerd install | kubectl apply -f - && linkerd check 9 | 10 | linkerd viz install | kubectl apply -f - && linkerd check 11 | 12 | kubectl create namespace argo-rollouts 13 | kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml 14 | kubectl apply -k https://github.com/argoproj/argo-rollouts/manifests/crds\?ref\=stable 15 | 16 | kubectl apply -k manifests/ 17 | kubectl rollout restart deploy -n argo-rollouts 18 | 19 | -------------------------------------------------------------------------------- /examples/linkerd-header-based/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: argo-rollouts-service 6 | namespace: default 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: http 11 | --- 12 | apiVersion: v1 13 | kind: Service 14 | metadata: 15 | name: argo-rollouts-canary-service 16 | namespace: default 17 | spec: 18 | ports: 19 | - port: 80 20 | targetPort: http 21 | selector: 22 | app: rollouts-demo 23 | --- 24 | apiVersion: v1 25 | kind: Service 26 | metadata: 27 | name: argo-rollouts-stable-service 28 | namespace: default 29 | spec: 30 | ports: 31 | - port: 80 32 | targetPort: http 33 | selector: 34 | app: rollouts-demo 35 | -------------------------------------------------------------------------------- /examples/cilium-header-based/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: argo-rollouts-service 6 | namespace: default 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: http 11 | selector: 12 | app: dummy # dummy selector needed for Cilium, see https://github.com/cilium/cilium/issues/38415 13 | --- 14 | apiVersion: v1 15 | kind: Service 16 | metadata: 17 | name: argo-rollouts-canary-service 18 | namespace: default 19 | spec: 20 | ports: 21 | - port: 80 22 | targetPort: http 23 | selector: 24 | app: rollouts-demo 25 | --- 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: argo-rollouts-stable-service 30 | namespace: default 31 | spec: 32 | ports: 33 | - port: 80 34 | targetPort: http 35 | selector: 36 | app: rollouts-demo 37 | -------------------------------------------------------------------------------- /test/cluster-setup/argo-rollouts-values.yml: -------------------------------------------------------------------------------- 1 | controller: 2 | nodeSelector: 3 | argo-rollouts-controller: "true" 4 | 5 | replicas: 1 6 | 7 | logging: 8 | level: debug 9 | 10 | volumes: 11 | - name: gatewayapi-plugin 12 | hostPath: 13 | path: /Volumes/ 14 | type: Directory 15 | 16 | volumeMounts: 17 | - mountPath: /argo-rollouts-gatewayapi-plugin/ 18 | name: gatewayapi-plugin 19 | 20 | trafficRouterPlugins: 21 | - name: "argoproj-labs/gatewayAPI" 22 | location: "file:///argo-rollouts-gatewayapi-plugin/gatewayapi-plugin-linux-amd64" 23 | 24 | providerRBAC: 25 | providers: 26 | istio: false 27 | smi: false 28 | ambassador: false 29 | awsLoadBalancerController: false 30 | awsAppMesh: false 31 | apisix: false 32 | contour: false 33 | glooPlatform: false 34 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/proddetail-tgp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: application-networking.k8s.aws/v1alpha1 2 | kind: TargetGroupPolicy 3 | metadata: 4 | name: proddetail-policy 5 | namespace: workshop 6 | spec: 7 | targetRef: 8 | group: "" 9 | kind: Service 10 | name: proddetail-stable-service 11 | protocol: HTTP 12 | protocolVersion: HTTP1 13 | healthCheck: 14 | enabled: true 15 | path: "/catalogdetail" 16 | statusMatch: "200" 17 | --- 18 | apiVersion: application-networking.k8s.aws/v1alpha1 19 | kind: TargetGroupPolicy 20 | metadata: 21 | name: proddetailv2-policy 22 | namespace: workshop 23 | spec: 24 | targetRef: 25 | group: "" 26 | kind: Service 27 | name: proddetail-canary-service 28 | protocol: HTTP 29 | protocolVersion: HTTP1 30 | healthCheck: 31 | enabled: true 32 | path: "/catalogdetail" 33 | statusMatch: "200" -------------------------------------------------------------------------------- /test/e2e/testdata/single-tcproute-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: tcproute-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | tcpRoute: tcproute-basic 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: { } 20 | revisionHistoryLimit: 1 21 | selector: 22 | matchLabels: 23 | app: rollouts-demo 24 | template: 25 | metadata: 26 | labels: 27 | app: rollouts-demo 28 | spec: 29 | containers: 30 | - name: rollouts-demo 31 | image: argoproj/rollouts-demo:red 32 | ports: 33 | - name: http 34 | containerPort: 8080 35 | protocol: TCP 36 | resources: 37 | requests: 38 | memory: 32Mi 39 | cpu: 5m -------------------------------------------------------------------------------- /test/e2e/testdata/single-grpcroute-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: grpcroute-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | grpcRoute: grpcroute-basic 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: { } 20 | revisionHistoryLimit: 1 21 | selector: 22 | matchLabels: 23 | app: rollouts-demo 24 | template: 25 | metadata: 26 | labels: 27 | app: rollouts-demo 28 | spec: 29 | containers: 30 | - name: rollouts-demo 31 | image: argoproj/rollouts-demo:red 32 | ports: 33 | - name: http 34 | containerPort: 8080 35 | protocol: TCP 36 | resources: 37 | requests: 38 | memory: 32Mi 39 | cpu: 5m -------------------------------------------------------------------------------- /test/e2e/testdata/single-httproute-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: httproute-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | httpRoute: httproute-basic 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: {} 20 | revisionHistoryLimit: 1 21 | selector: 22 | matchLabels: 23 | app: rollouts-demo 24 | template: 25 | metadata: 26 | labels: 27 | app: rollouts-demo 28 | spec: 29 | containers: 30 | - name: rollouts-demo 31 | image: argoproj/rollouts-demo:red 32 | ports: 33 | - name: http 34 | containerPort: 8080 35 | protocol: TCP 36 | resources: 37 | requests: 38 | memory: 32Mi 39 | cpu: 5m 40 | -------------------------------------------------------------------------------- /test/e2e/testdata/single-tlsroute-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: tlsroute-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | tlsRoute: tlsroute-basic 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: { } 20 | revisionHistoryLimit: 1 21 | selector: 22 | matchLabels: 23 | app: rollouts-demo 24 | template: 25 | metadata: 26 | labels: 27 | app: rollouts-demo 28 | spec: 29 | containers: 30 | - name: rollouts-demo 31 | image: argoproj/rollouts-demo:red 32 | ports: 33 | - name: https 34 | containerPort: 8080 35 | protocol: TCP 36 | resources: 37 | requests: 38 | memory: 32Mi 39 | cpu: 5m 40 | -------------------------------------------------------------------------------- /test/e2e/testdata/single-httproute-label-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: httproute-label 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | httpRoute: httproute-basic 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: 20 | duration: 10s 21 | revisionHistoryLimit: 1 22 | selector: 23 | matchLabels: 24 | app: rollouts-demo 25 | template: 26 | metadata: 27 | labels: 28 | app: rollouts-demo 29 | spec: 30 | containers: 31 | - name: rollouts-demo 32 | image: argoproj/rollouts-demo:red 33 | ports: 34 | - name: http 35 | containerPort: 8080 36 | protocol: TCP 37 | resources: 38 | requests: 39 | memory: 32Mi 40 | cpu: 5m 41 | -------------------------------------------------------------------------------- /pkg/plugin/errors.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | const ( 4 | GatewayAPIUpdateError = "error updating Gateway API %q: %s" 5 | GatewayAPIManifestError = "No routes configured. At least one of 'httpRoutes', 'grpcRoutes', 'tcpRoutes', 'tlsRoutes', 'httpRoute', 'grpcRoute', 'tcpRoute' or 'tlsRoute' must be set" 6 | HTTPRouteFieldIsEmptyError = "httpRoute field is empty. It has to be set to remove managed routes" 7 | InvalidHeaderMatchTypeError = "invalid header match type" 8 | BackendRefWasNotFoundInHTTPRouteError = "backendRef was not found in httpRoute" 9 | BackendRefWasNotFoundInGRPCRouteError = "backendRef was not found in grpcRoute" 10 | BackendRefWasNotFoundInTCPRouteError = "backendRef was not found in tcpRoute" 11 | BackendRefWasNotFoundInTLSRouteError = "backendRef was not found in tlsRoute" 12 | BackendRefListWasNotFoundInTCPRouteError = "backendRef list was not found in tcpRoute" 13 | BackendRefListWasNotFoundInTLSRouteError = "backendRef list was not found in tlsRoute" 14 | ManagedRouteMapEntryDeleteError = "can't delete key %q from managedRouteMap. The key %q is not in the managedRouteMap" 15 | ) 16 | -------------------------------------------------------------------------------- /test/cluster-setup/sanity-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit immediately if any command fails 4 | set -e 5 | 6 | echo ">>> Sanity checks for e2e tests. If these tests fail, your e2e tests will also fail. <<<" 7 | 8 | echo "Checking e2egateway class traefik with accepted condition=true (up to 5 attempts) ..." 9 | for i in {1..5}; do 10 | if kubectl get gatewayclasses traefik -o jsonpath='{.status.conditions[?(@.type=="Accepted")].status}' | grep -q "True"; then 11 | echo "traefik gatewayclass accepted" 12 | break 13 | fi 14 | if [ "$i" -eq 5 ]; then 15 | echo "gatewayclass traefik not accepted after 5 attempts" 16 | exit 1 17 | fi 18 | echo "gatewayclass not ready yet, retrying ($i/5)..." 19 | sleep 5 20 | done 21 | 22 | echo "Checking e2egateway traefik-gateway with programmed condition=true ..." 23 | kubectl get gateway traefik-gateway -o jsonpath='{.status.conditions[?(@.type=="Programmed")].status}' | grep -q "True" 24 | 25 | echo "Checking e2e canary service..." 26 | kubectl get svc argo-rollouts-canary-service > /dev/null 27 | 28 | echo "Checking e2e stable service..." 29 | kubectl get svc argo-rollouts-stable-service > /dev/null 30 | 31 | echo ">>> Sanity checks finished. Your cluster is now ready for e2e tests. <<<" 32 | 33 | -------------------------------------------------------------------------------- /examples/envoygateway/rollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | namespace: default 6 | spec: 7 | replicas: 3 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service # our created canary service 11 | stableService: argo-rollouts-stable-service # our created stable service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | httpRoute: argo-rollouts-http-route # our created httproute 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: {} 20 | - setWeight: 60 21 | - pause: {} 22 | - setWeight: 100 23 | - pause: {} 24 | revisionHistoryLimit: 2 25 | selector: 26 | matchLabels: 27 | app: rollouts-demo 28 | template: 29 | metadata: 30 | labels: 31 | app: rollouts-demo 32 | spec: 33 | containers: 34 | - name: rollouts-demo 35 | image: kostiscodefresh/summer-of-k8s-app:v1 36 | ports: 37 | - name: http 38 | containerPort: 8080 39 | protocol: TCP 40 | resources: 41 | requests: 42 | memory: 32Mi 43 | cpu: 5m -------------------------------------------------------------------------------- /examples/cilium/rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | namespace: default 6 | spec: 7 | replicas: 10 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service # our created canary service 11 | stableService: argo-rollouts-stable-service # our created stable service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | httpRoute: argo-rollouts-http-route # our created httproute 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: {} 20 | - setWeight: 60 21 | - pause: {} 22 | - setWeight: 100 23 | - pause: {} 24 | revisionHistoryLimit: 2 25 | selector: 26 | matchLabels: 27 | app: rollouts-demo 28 | template: 29 | metadata: 30 | labels: 31 | app: rollouts-demo 32 | spec: 33 | containers: 34 | - name: rollouts-demo 35 | image: kostiscodefresh/summer-of-k8s-app:v1 # change to v2 for next version 36 | ports: 37 | - name: http 38 | containerPort: 8080 39 | protocol: TCP 40 | resources: 41 | requests: 42 | memory: 32Mi 43 | cpu: 5m -------------------------------------------------------------------------------- /examples/google-cloud/rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | namespace: default 6 | spec: 7 | replicas: 10 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service # our created canary service 11 | stableService: argo-rollouts-stable-service # our created stable service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | httpRoute: argo-rollouts-http-route # our created httproute 16 | namespace: default 17 | steps: 18 | - setWeight: 30 19 | - pause: {} 20 | - setWeight: 60 21 | - pause: {} 22 | - setWeight: 100 23 | - pause: {} 24 | revisionHistoryLimit: 2 25 | selector: 26 | matchLabels: 27 | app: rollouts-demo 28 | template: 29 | metadata: 30 | labels: 31 | app: rollouts-demo 32 | spec: 33 | containers: 34 | - name: rollouts-demo 35 | image: kostiscodefresh/summer-of-k8s-app:v1 # change to v2 for next version 36 | ports: 37 | - name: http 38 | containerPort: 8080 39 | protocol: TCP 40 | resources: 41 | requests: 42 | memory: 32Mi 43 | cpu: 5m -------------------------------------------------------------------------------- /examples/kong/rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | namespace: default 6 | spec: 7 | revisionHistoryLimit: 1 8 | replicas: 10 9 | strategy: 10 | canary: 11 | canaryService: argo-rollouts-canary-service # our created canary service 12 | stableService: argo-rollouts-stable-service # our created stable service 13 | trafficRouting: 14 | plugins: 15 | argoproj-labs/gatewayAPI: 16 | httpRoute: argo-rollouts-http-route # our created httproute 17 | namespace: default 18 | steps: 19 | - setWeight: 30 20 | - pause: {} 21 | - setWeight: 60 22 | - pause: {} 23 | - setWeight: 100 24 | - pause: {} 25 | revisionHistoryLimit: 2 26 | selector: 27 | matchLabels: 28 | app: rollouts-demo 29 | template: 30 | metadata: 31 | labels: 32 | app: rollouts-demo 33 | spec: 34 | containers: 35 | - name: rollouts-demo 36 | image: kostiscodefresh/summer-of-k8s-app:v1 # change to v2 for next version 37 | ports: 38 | - name: http 39 | containerPort: 8080 40 | protocol: TCP 41 | resources: 42 | requests: 43 | memory: 32Mi 44 | cpu: 5m -------------------------------------------------------------------------------- /test/e2e/testdata/single-header-based-grpcroute-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: grpcroute-header-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | managedRoutes: 14 | - name: canary-route1 15 | plugins: 16 | argoproj-labs/gatewayAPI: 17 | grpcRoutes: 18 | - name: grpcroute-header 19 | useHeaderRoutes: true 20 | namespace: default 21 | steps: 22 | - setWeight: 30 23 | - setHeaderRoute: 24 | name: canary-route1 25 | match: 26 | - headerName: X-Test 27 | headerValue: 28 | exact: test 29 | - pause: { } 30 | revisionHistoryLimit: 2 31 | selector: 32 | matchLabels: 33 | app: rollouts-demo 34 | template: 35 | metadata: 36 | labels: 37 | app: rollouts-demo 38 | spec: 39 | containers: 40 | - name: rollouts-demo 41 | image: argoproj/rollouts-demo:red 42 | ports: 43 | - name: http 44 | containerPort: 8080 45 | protocol: TCP 46 | -------------------------------------------------------------------------------- /test/e2e/testdata/single-header-based-httproute-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: httproute-header-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | managedRoutes: 14 | - name: canary-route1 15 | plugins: 16 | argoproj-labs/gatewayAPI: 17 | httpRoutes: 18 | - name: httproute-header 19 | useHeaderRoutes: true 20 | namespace: default 21 | steps: 22 | - setWeight: 30 23 | - setHeaderRoute: 24 | name: canary-route1 25 | match: 26 | - headerName: X-Test 27 | headerValue: 28 | exact: test 29 | - pause: { } 30 | revisionHistoryLimit: 2 31 | selector: 32 | matchLabels: 33 | app: rollouts-demo 34 | template: 35 | metadata: 36 | labels: 37 | app: rollouts-demo 38 | spec: 39 | containers: 40 | - name: rollouts-demo 41 | image: argoproj/rollouts-demo:red 42 | ports: 43 | - name: http 44 | containerPort: 8080 45 | protocol: TCP 46 | -------------------------------------------------------------------------------- /test/e2e/testdata/single-httproute-filters-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: httproute-filters-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | managedRoutes: 14 | - name: filter-preservation-route 15 | plugins: 16 | argoproj-labs/gatewayAPI: 17 | httpRoutes: 18 | - name: httproute-filters 19 | useHeaderRoutes: true 20 | namespace: default 21 | steps: 22 | - setWeight: 30 23 | - setHeaderRoute: 24 | name: filter-preservation-route 25 | match: 26 | - headerName: X-Filter-Test 27 | headerValue: 28 | exact: preserve-filters 29 | - pause: { } 30 | revisionHistoryLimit: 2 31 | selector: 32 | matchLabels: 33 | app: rollouts-demo 34 | template: 35 | metadata: 36 | labels: 37 | app: rollouts-demo 38 | spec: 39 | containers: 40 | - name: rollouts-demo 41 | image: argoproj/rollouts-demo:red 42 | ports: 43 | - name: http 44 | containerPort: 8080 45 | protocol: TCP 46 | -------------------------------------------------------------------------------- /examples/nginx/rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | replicas: 5 7 | strategy: 8 | canary: 9 | canaryService: argo-rollouts-canary-service # our created canary service 10 | stableService: argo-rollouts-stable-service # our created stable service 11 | trafficRouting: 12 | plugins: 13 | argoproj-labs/gatewayAPI: 14 | httpRoute: argo-rollouts-http-route # our created httproute 15 | namespace: default # namespace where this rollout resides 16 | steps: 17 | - setWeight: 30 18 | - pause: {} 19 | - setWeight: 40 20 | - pause: { duration: 10 } 21 | - setWeight: 60 22 | - pause: { duration: 10 } 23 | - setWeight: 80 24 | - pause: { duration: 10 } 25 | revisionHistoryLimit: 2 26 | selector: 27 | matchLabels: 28 | app: rollouts-demo 29 | template: 30 | metadata: 31 | labels: 32 | app: rollouts-demo 33 | spec: 34 | containers: 35 | - name: rollouts-demo 36 | image: argoproj/rollouts-demo:red 37 | ports: 38 | - name: http 39 | containerPort: 8080 40 | protocol: TCP 41 | resources: 42 | requests: 43 | memory: 32Mi 44 | cpu: 5m -------------------------------------------------------------------------------- /examples/traefik/rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | replicas: 5 7 | strategy: 8 | canary: 9 | canaryService: argo-rollouts-canary-service # our created canary service 10 | stableService: argo-rollouts-stable-service # our created stable service 11 | trafficRouting: 12 | plugins: 13 | argoproj-labs/gatewayAPI: 14 | httpRoute: argo-rollouts-http-route # our created httproute 15 | namespace: default # namespace where this rollout resides 16 | steps: 17 | - setWeight: 30 18 | - pause: {} 19 | - setWeight: 40 20 | - pause: { duration: 10 } 21 | - setWeight: 60 22 | - pause: { duration: 10 } 23 | - setWeight: 80 24 | - pause: { duration: 10 } 25 | revisionHistoryLimit: 2 26 | selector: 27 | matchLabels: 28 | app: rollouts-demo 29 | template: 30 | metadata: 31 | labels: 32 | app: rollouts-demo 33 | spec: 34 | containers: 35 | - name: rollouts-demo 36 | image: argoproj/rollouts-demo:red 37 | ports: 38 | - name: http 39 | containerPort: 8080 40 | protocol: TCP 41 | resources: 42 | requests: 43 | memory: 32Mi 44 | cpu: 5m -------------------------------------------------------------------------------- /test/e2e/testdata/single-grpcroute-filters-rollout.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: grpcroute-filters-rollout 5 | namespace: default 6 | spec: 7 | replicas: 2 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service 11 | stableService: argo-rollouts-stable-service 12 | trafficRouting: 13 | managedRoutes: 14 | - name: grpc-filter-preservation-route 15 | plugins: 16 | argoproj-labs/gatewayAPI: 17 | grpcRoutes: 18 | - name: grpcroute-filters 19 | useHeaderRoutes: true 20 | namespace: default 21 | steps: 22 | - setWeight: 30 23 | - setHeaderRoute: 24 | name: grpc-filter-preservation-route 25 | match: 26 | - headerName: X-GRPC-Filter-Test 27 | headerValue: 28 | exact: preserve-grpc-filters 29 | - pause: { } 30 | revisionHistoryLimit: 2 31 | selector: 32 | matchLabels: 33 | app: rollouts-demo 34 | template: 35 | metadata: 36 | labels: 37 | app: rollouts-demo 38 | spec: 39 | containers: 40 | - name: rollouts-demo 41 | image: argoproj/rollouts-demo:red 42 | ports: 43 | - name: http 44 | containerPort: 8080 45 | protocol: TCP 46 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | extra: 2 | analytics: 3 | property: G-5Z1VTPDL73 4 | provider: google 5 | extra_css: 6 | - assets/versions.css 7 | extra_javascript: 8 | - assets/versions.js 9 | markdown_extensions: 10 | - codehilite 11 | - admonition 12 | - pymdownx.superfences 13 | - pymdownx.tabbed 14 | - footnotes 15 | - toc: 16 | permalink: true 17 | nav: 18 | - Overview: index.md 19 | - Installation: installation.md 20 | - Providers: provider-status.md 21 | - Quick start: quick-start.md 22 | - Features: 23 | - Advanced Deployments: features/advanced-deployments.md 24 | - Multiple Routes: features/multiple-routes.md 25 | - Header Based Routing: features/header-based-routing.md 26 | - TCP Routing: features/tcp.md 27 | - TLS Routing: features/tls.md 28 | - GRPC Routing: features/grpc.md 29 | 30 | - Contributing: CONTRIBUTING.md 31 | repo_url: https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi 32 | site_name: Argo Rollouts Gateway API plugin 33 | site_url: https://argo-rollouts.readthedocs.io/ 34 | theme: 35 | font: 36 | text: Work Sans 37 | logo: assets/logo.png 38 | name: material 39 | palette: 40 | - primary: teal 41 | scheme: default 42 | toggle: 43 | icon: material/toggle-switch-off-outline 44 | name: Switch to dark mode 45 | - scheme: slate 46 | toggle: 47 | icon: material/toggle-switch 48 | name: Switch to light mode 49 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/frontenddep.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: frontend 5 | namespace: workshop 6 | labels: 7 | app: frontend 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: frontend 13 | strategy: 14 | type: RollingUpdate 15 | rollingUpdate: 16 | maxSurge: 25% 17 | maxUnavailable: 25% 18 | template: 19 | metadata: 20 | labels: 21 | app: frontend 22 | annotations: 23 | prometheus.io/path: /metrics 24 | prometheus.io/port: "9000" 25 | prometheus.io/scrape: "true" 26 | spec: 27 | containers: 28 | - name: frontend 29 | image: nicksrj/frontend_node:1.0 30 | imagePullPolicy: Always 31 | ports: 32 | - containerPort: 9000 33 | name: http 34 | env: 35 | - name: BASE_URL 36 | value: http://prodcatalog.workshop:5000/products/ 37 | - name: AWS_XRAY_DAEMON_ADDRESS 38 | value: xray-service.default:2000 39 | livenessProbe: 40 | httpGet: 41 | path: /ping 42 | port: 9000 43 | initialDelaySeconds: 5 44 | periodSeconds: 5 45 | readinessProbe: 46 | httpGet: 47 | path: /ping 48 | port: 9000 49 | initialDelaySeconds: 5 50 | periodSeconds: 3 51 | -------------------------------------------------------------------------------- /pkg/plugin/labels.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | 6 | "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/internal/defaults" 7 | ) 8 | 9 | func ensureInProgressLabel(obj metav1.Object, desiredWeight int32, config *GatewayAPITrafficRouting) bool { 10 | if obj == nil || config == nil || config.DisableInProgressLabel { 11 | return false 12 | } 13 | 14 | key := config.inProgressLabelKey() 15 | if key == "" { 16 | return false 17 | } 18 | 19 | labels := obj.GetLabels() 20 | if desiredWeight == 0 { 21 | if labels == nil { 22 | return false 23 | } 24 | if _, ok := labels[key]; ok { 25 | delete(labels, key) 26 | obj.SetLabels(labels) 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | value := config.inProgressLabelValue() 33 | if labels == nil { 34 | labels = make(map[string]string) 35 | } 36 | if current, ok := labels[key]; ok && current == value { 37 | return false 38 | } 39 | labels[key] = value 40 | obj.SetLabels(labels) 41 | return true 42 | } 43 | 44 | func (c *GatewayAPITrafficRouting) inProgressLabelKey() string { 45 | if c.InProgressLabelKey != "" { 46 | return c.InProgressLabelKey 47 | } 48 | return defaults.InProgressLabelKey 49 | } 50 | 51 | func (c *GatewayAPITrafficRouting) inProgressLabelValue() string { 52 | if c.InProgressLabelValue != "" { 53 | return c.InProgressLabelValue 54 | } 55 | return defaults.InProgressLabelValue 56 | } 57 | -------------------------------------------------------------------------------- /examples/linkerd/rollout.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: argoproj.io/v1alpha1 3 | kind: Rollout 4 | metadata: 5 | name: rollouts-demo 6 | spec: 7 | replicas: 5 8 | strategy: 9 | canary: 10 | canaryService: argo-rollouts-canary-service # our created canary service 11 | stableService: argo-rollouts-stable-service # our created stable service 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | httpRoute: argo-rollouts-http-route # our created httproute 16 | namespace: default # namespace where this rollout resides 17 | steps: 18 | - setWeight: 30 19 | - pause: { duration: 10 } 20 | - setWeight: 40 21 | - pause: { duration: 10 } 22 | - setWeight: 60 23 | - pause: { duration: 10 } 24 | - setWeight: 80 25 | - pause: { duration: 10 } 26 | revisionHistoryLimit: 2 27 | selector: 28 | matchLabels: 29 | app: rollouts-demo 30 | template: 31 | metadata: 32 | labels: 33 | app: rollouts-demo 34 | spec: 35 | containers: 36 | - name: rollouts-demo 37 | image: argoproj/rollouts-demo:red 38 | ports: 39 | - name: http 40 | containerPort: 8080 41 | protocol: TCP 42 | env: 43 | - name: APP_VERSION 44 | value: "1.0.0" 45 | resources: 46 | requests: 47 | memory: 32Mi 48 | cpu: 5m 49 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/prodcatalogdep.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: prodcatalog 5 | namespace: workshop 6 | labels: 7 | app: prodcatalog 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: prodcatalog 13 | strategy: 14 | type: RollingUpdate 15 | rollingUpdate: 16 | maxSurge: 25% 17 | maxUnavailable: 25% 18 | template: 19 | metadata: 20 | labels: 21 | app: prodcatalog 22 | spec: 23 | containers: 24 | - name: prodcatalog 25 | image: nicksrj/product_catalog:1.0 26 | imagePullPolicy: Always 27 | ports: 28 | - containerPort: 5000 29 | name: http 30 | env: 31 | - name: AGG_APP_URL 32 | value: http://latcan-app-default-xxxxxxxxxxxx.vpc-lattice-svcs.ap-southeast-1.on.aws/catalogDetail 33 | #value: http://proddetail.workshop:3000/catalogDetail 34 | - name: AWS_XRAY_DAEMON_ADDRESS 35 | value: xray-service.default:2000 36 | - name: DATABASE_SERVICE_URL 37 | value: mysql.workshop 38 | livenessProbe: 39 | httpGet: 40 | path: /products/ping 41 | port: 5000 42 | initialDelaySeconds: 5 43 | periodSeconds: 5 44 | readinessProbe: 45 | httpGet: 46 | path: /products/ping 47 | port: 5000 48 | initialDelaySeconds: 5 49 | periodSeconds: 3 50 | -------------------------------------------------------------------------------- /examples/aws-gateway-api-controller-lattice/proddetail-rollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: proddetail 5 | namespace: workshop 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: proddetail 11 | template: 12 | metadata: 13 | labels: 14 | app: proddetail 15 | spec: 16 | containers: 17 | - name: proddetail 18 | image: nicksrj/product_detail:1.0 19 | imagePullPolicy: Always 20 | livenessProbe: 21 | httpGet: 22 | path: /ping 23 | port: 3000 24 | initialDelaySeconds: 0 25 | periodSeconds: 10 26 | timeoutSeconds: 1 27 | failureThreshold: 3 28 | readinessProbe: 29 | httpGet: 30 | path: /ping 31 | port: 3000 32 | successThreshold: 3 33 | ports: 34 | - containerPort: 3000 35 | env: 36 | - name: AWS_XRAY_DAEMON_ADDRESS 37 | value: xray-service.default:2000 38 | strategy: 39 | canary: 40 | canaryService: proddetail-canary-service 41 | stableService: proddetail-stable-service 42 | trafficRouting: 43 | plugins: 44 | argoproj-labs/gatewayAPI: 45 | httpRoute: latcan-app 46 | namespace: default 47 | steps: 48 | - setWeight: 20 49 | - pause: {duration: 2m} 50 | - setWeight: 40 51 | - pause: {duration: 3m} 52 | - setWeight: 60 53 | - pause: {duration: 3m} 54 | - setWeight: 80 55 | - pause: {duration: 3m} 56 | - setWeight: 100 57 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/internal/utils" 7 | "github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/pkg/plugin" 8 | 9 | rolloutsPlugin "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" 10 | goPlugin "github.com/hashicorp/go-plugin" 11 | ) 12 | 13 | // handshakeConfigs are used to just do a basic handshake between 14 | // a plugin and host. If the handshake fails, a user friendly error is shown. 15 | // This prevents users from executing bad plugins or executing a plugin 16 | // directory. It is a UX feature, not a security feature. 17 | var handshakeConfig = goPlugin.HandshakeConfig{ 18 | ProtocolVersion: 1, 19 | MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", 20 | MagicCookieValue: "trafficrouter", 21 | } 22 | 23 | func main() { 24 | // Define and parse flags for your command line options: 25 | kubeClientQPS := flag.Int("kubeClientQPS", 5, "The QPS to use for the Kubernetes client.") 26 | kubeClientBurst := flag.Int("kubeClientBurst", 10, "The Burst to use for the Kubernetes client.") 27 | flag.Parse() 28 | 29 | // Create the plugin implementation, injecting command line options: 30 | rpcPluginImp := &plugin.RpcPlugin{ 31 | CommandLineOpts: plugin.CommandLineOpts{ 32 | KubeClientQPS: float32(*kubeClientQPS), 33 | KubeClientBurst: *kubeClientBurst, 34 | }, 35 | LogCtx: utils.SetupLog(), 36 | } 37 | 38 | pluginMap := map[string]goPlugin.Plugin{ 39 | "RpcTrafficRouterPlugin": &rolloutsPlugin.RpcTrafficRouterPlugin{Impl: rpcPluginImp}, 40 | } 41 | 42 | goPlugin.Serve(&goPlugin.ServeConfig{ 43 | HandshakeConfig: handshakeConfig, 44 | Plugins: pluginMap, 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /examples/cilium-header-based/rollout.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: argoproj.io/v1alpha1 3 | kind: Rollout 4 | metadata: 5 | name: rollouts-demo 6 | namespace: default 7 | spec: 8 | replicas: 5 9 | strategy: 10 | canary: 11 | canaryService: argo-rollouts-canary-service 12 | stableService: argo-rollouts-stable-service 13 | trafficRouting: 14 | plugins: 15 | argoproj-labs/gatewayAPI: 16 | httpRoutes: 17 | - name: argo-rollouts-http-route 18 | useHeaderRoutes: true 19 | namespace: default 20 | managedRoutes: 21 | - name: argo-rollouts 22 | steps: 23 | - pause: {} 24 | - setCanaryScale: 25 | replicas: 1 26 | - setHeaderRoute: 27 | name: argo-rollouts 28 | match: 29 | - headerName: X-Test 30 | headerValue: 31 | exact: test 32 | - pause: {} 33 | - setHeaderRoute: # remove header route 34 | name: argo-rollouts 35 | revisionHistoryLimit: 2 36 | selector: 37 | matchLabels: 38 | app: rollouts-demo 39 | template: 40 | metadata: 41 | labels: 42 | app: rollouts-demo 43 | spec: 44 | containers: 45 | - name: http-echo 46 | image: hashicorp/http-echo:1.0 47 | args: 48 | - "-text=Hello from $(POD_NAME)" 49 | ports: 50 | - name: http 51 | containerPort: 5678 52 | protocol: TCP 53 | env: 54 | - name: APP_VERSION 55 | value: "1.0.0" 56 | - name: POD_NAME 57 | valueFrom: 58 | fieldRef: 59 | fieldPath: metadata.name 60 | resources: 61 | requests: 62 | memory: 32Mi 63 | cpu: 5m 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Code:** 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi)](https://goreportcard.com/report/github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi) 3 | [![Gateway API plugin CI](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/actions/workflows/ci.yaml/badge.svg)](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/actions/workflows/ci.yaml) 4 | 5 | **Social:** 6 | [![Twitter Follow](https://img.shields.io/twitter/follow/argoproj?style=social)](https://twitter.com/argoproj) 7 | [![Slack](https://img.shields.io/badge/slack-argoproj-brightgreen.svg?logo=slack)](https://argoproj.github.io/community/join-slack) 8 | 9 | # Argo Rollouts Gateway API plugin 10 | 11 | This repository is a [plugin](https://argoproj.github.io/argo-rollouts/features/traffic-management/plugins/) of [Argo Rollouts](https://argoproj.github.io/rollouts/) for enabling the use of the Kubernetes Gateway API in Progressive Delivery scenarios. 12 | 13 | See [the main documentation site](https://rollouts-plugin-trafficrouter-gatewayapi.readthedocs.io/en/latest/) for all the details. 14 | 15 | ## Feedback needed 16 | 17 | The gateway API plugin should cover all providers that support it. If you find an issue 18 | please [tell us about it](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/issues) 19 | 20 | If you also want to add an example with your favorite [gateway API provider](https://rollouts-plugin-trafficrouter-gatewayapi.readthedocs.io/en/latest/provider-status/) please open a [Pull Request](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/pulls). 21 | 22 | ## Contact 23 | 24 | You can find us at the `#argo-rollouts` channel [at the CNCF slack](https://argoproj.github.io/community/join-slack). 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/google-cloud-experiment/rollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: demo-app 5 | namespace: demo 6 | spec: 7 | replicas: 3 8 | strategy: 9 | canary: 10 | canaryService: demo-app-canary 11 | stableService: demo-app-stable 12 | trafficRouting: 13 | plugins: 14 | argoproj-labs/gatewayAPI: 15 | httpRoute: demo-app-route 16 | namespace: demo 17 | steps: 18 | - experiment: 19 | duration: 5m 20 | templates: 21 | - name: experiment-baseline 22 | specRef: stable 23 | service: 24 | name: demo-app-exp-baseline 25 | weight: 10 26 | metadata: 27 | labels: 28 | app: demo-app 29 | - name: experiment-canary 30 | specRef: canary 31 | service: 32 | name: demo-app-exp-canary 33 | weight: 15 34 | metadata: 35 | labels: 36 | app: demo-app 37 | - pause: {} # Empty pause means indefinite - will require manual promotion 38 | - setWeight: 30 39 | - pause: { duration: 5m } 40 | - setWeight: 60 41 | - pause: { duration: 5m } 42 | - setWeight: 100 43 | - pause: { duration: 5m } 44 | revisionHistoryLimit: 2 45 | selector: 46 | matchLabels: 47 | app: demo-app 48 | template: 49 | metadata: 50 | labels: 51 | app: demo-app 52 | spec: 53 | containers: 54 | - name: demo-app 55 | image: argoproj/rollouts-demo:blue # change to green for next version 56 | ports: 57 | - name: http 58 | containerPort: 8080 59 | protocol: TCP 60 | resources: 61 | requests: 62 | memory: 64Mi 63 | cpu: 10m 64 | -------------------------------------------------------------------------------- /test/e2e/testdata/grpcroute-filters.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: GRPCRoute 3 | metadata: 4 | name: grpcroute-filters 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | namespace: default 10 | rules: 11 | - matches: 12 | - method: 13 | service: rollouts.demo.Service 14 | filters: 15 | # RequestHeaderModifier - adds, sets, and removes request headers 16 | - type: RequestHeaderModifier 17 | requestHeaderModifier: 18 | set: 19 | - name: X-GRPC-Request-Header 20 | value: grpc-request-value 21 | - name: X-Service-Environment 22 | value: test 23 | add: 24 | - name: X-GRPC-Added-Header 25 | value: grpc-added-value 26 | remove: 27 | - X-Remove-GRPC-Header 28 | # ResponseHeaderModifier - adds, sets, and removes response headers 29 | - type: ResponseHeaderModifier 30 | responseHeaderModifier: 31 | set: 32 | - name: X-GRPC-Response-Header 33 | value: grpc-response-value 34 | - name: X-GRPC-Server 35 | value: gateway-api-grpc-test 36 | add: 37 | - name: X-GRPC-Response-Added 38 | value: grpc-response-added-value 39 | remove: 40 | - grpc-status-details-bin 41 | # RequestMirror - mirrors gRPC requests to another backend 42 | - type: RequestMirror 43 | requestMirror: 44 | backendRef: 45 | name: argo-rollouts-grpc-mirror-service 46 | port: 80 47 | backendRefs: 48 | - name: argo-rollouts-stable-service 49 | port: 80 50 | - name: argo-rollouts-canary-service 51 | port: 80 52 | -------------------------------------------------------------------------------- /examples/linkerd-header-based/rollout.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: argoproj.io/v1alpha1 3 | kind: Rollout 4 | metadata: 5 | name: rollouts-demo 6 | namespace: default 7 | spec: 8 | replicas: 5 9 | strategy: 10 | canary: 11 | canaryService: argo-rollouts-canary-service # our created canary service 12 | stableService: argo-rollouts-stable-service # our created stable service 13 | trafficRouting: 14 | plugins: 15 | argoproj-labs/gatewayAPI: 16 | httpRoutes: 17 | - name: argo-rollouts-http-route 18 | useHeaderRoutes: true 19 | namespace: default 20 | managedRoutes: 21 | - name: argo-rollouts 22 | steps: 23 | - pause: {} 24 | - setCanaryScale: 25 | replicas: 1 26 | - setHeaderRoute: 27 | name: argo-rollouts 28 | match: 29 | - headerName: X-Test 30 | headerValue: 31 | exact: test 32 | - pause: {} 33 | - setHeaderRoute: # remove header route 34 | name: argo-rollouts 35 | revisionHistoryLimit: 2 36 | selector: 37 | matchLabels: 38 | app: rollouts-demo 39 | template: 40 | metadata: 41 | labels: 42 | app: rollouts-demo 43 | spec: 44 | containers: 45 | - name: http-echo 46 | image: hashicorp/http-echo 47 | args: 48 | - "-text=Hello from $(POD_NAME)" 49 | ports: 50 | - name: http 51 | containerPort: 5678 52 | protocol: TCP 53 | env: 54 | - name: APP_VERSION 55 | value: "1.0" 56 | - name: POD_NAME 57 | valueFrom: 58 | fieldRef: 59 | fieldPath: metadata.name 60 | resources: 61 | requests: 62 | memory: 32Mi 63 | cpu: 5m 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | Checklist: 13 | 14 | * [ ] I've included steps to reproduce the bug. 15 | * [ ] I've included the version of argo rollouts. 16 | * [ ] I've included the version of the gateway API plugin 17 | * [ ] I've included the name and version of the gateway API provider I am using 18 | * [ ] I've included my Rollout Spec (anonymized if needed) 19 | 20 | **Describe the bug** 21 | 22 | 23 | 24 | **To Reproduce** 25 | 26 | 27 | 28 | **Expected behavior** 29 | 30 | 31 | 32 | **Screenshots** 33 | 34 | 35 | 36 | **Version** 37 | 38 | 39 | 40 | **Logs** 41 | 42 | ``` 43 | # Paste the logs from the rollout controller 44 | 45 | # Logs for the entire controller: 46 | kubectl logs -n argo-rollouts deployment/argo-rollouts 47 | 48 | # Logs for a specific rollout: 49 | kubectl logs -n argo-rollouts deployment/argo-rollouts | grep rollout= 54 | **Message from the maintainers**: 55 | 56 | Impacted by this bug? Give it a 👍. We prioritize the issues with the most 👍. 57 | -------------------------------------------------------------------------------- /test/e2e/testdata/httproute-filters.yml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1 2 | kind: HTTPRoute 3 | metadata: 4 | name: httproute-filters 5 | namespace: default 6 | spec: 7 | parentRefs: 8 | - name: traefik-gateway 9 | namespace: default 10 | rules: 11 | - matches: 12 | - path: 13 | type: PathPrefix 14 | value: / 15 | filters: 16 | # RequestHeaderModifier - adds, sets, and removes request headers 17 | - type: RequestHeaderModifier 18 | requestHeaderModifier: 19 | set: 20 | - name: X-Custom-Request-Header 21 | value: custom-request-value 22 | - name: X-Environment 23 | value: test 24 | add: 25 | - name: X-Added-Header 26 | value: added-value 27 | remove: 28 | - X-Remove-Me 29 | # ResponseHeaderModifier - adds, sets, and removes response headers 30 | - type: ResponseHeaderModifier 31 | responseHeaderModifier: 32 | set: 33 | - name: X-Custom-Response-Header 34 | value: custom-response-value 35 | - name: X-Server 36 | value: gateway-api-test 37 | add: 38 | - name: X-Response-Added 39 | value: response-added-value 40 | remove: 41 | - Server 42 | # RequestMirror - mirrors requests to another backend for testing/monitoring 43 | - type: RequestMirror 44 | requestMirror: 45 | backendRef: 46 | name: argo-rollouts-mirror-service 47 | port: 80 48 | # URLRewrite - rewrites the request URL path and hostname 49 | - type: URLRewrite 50 | urlRewrite: 51 | path: 52 | type: ReplacePrefixMatch 53 | replacePrefixMatch: /api/v2 54 | hostname: api.example.com 55 | backendRefs: 56 | - name: argo-rollouts-stable-service 57 | port: 80 58 | - name: argo-rollouts-canary-service 59 | port: 80 60 | -------------------------------------------------------------------------------- /docs/assets/versions.js: -------------------------------------------------------------------------------- 1 | const targetNode = document.querySelector('.md-header__inner'); 2 | const observerOptions = { 3 | childList: true, 4 | subtree: true 5 | }; 6 | 7 | const observerCallback = function(mutationsList, observer) { 8 | for (let mutation of mutationsList) { 9 | if (mutation.type === 'childList') { 10 | const titleElement = document.querySelector('.md-header__inner > .md-header__title'); 11 | if (titleElement) { 12 | initializeVersionDropdown(); 13 | observer.disconnect(); 14 | } 15 | } 16 | } 17 | }; 18 | 19 | const observer = new MutationObserver(observerCallback); 20 | observer.observe(targetNode, observerOptions); 21 | 22 | function initializeVersionDropdown() { 23 | const callbackName = 'callback_' + new Date().getTime(); 24 | window[callbackName] = function(response) { 25 | const div = document.createElement('div'); 26 | div.innerHTML = response.html; 27 | document.querySelector(".md-header__inner > .md-header__title").appendChild(div); 28 | const container = div.querySelector('.rst-versions'); 29 | var caret = document.createElement('div'); 30 | caret.innerHTML = ""; 31 | caret.classList.add('dropdown-caret'); 32 | div.querySelector('.rst-current-version').appendChild(caret); 33 | 34 | div.querySelector('.rst-current-version').addEventListener('click', function() { 35 | container.classList.toggle('shift-up'); 36 | }); 37 | }; 38 | 39 | var CSSLink = document.createElement('link'); 40 | CSSLink.rel='stylesheet'; 41 | CSSLink.href = '/assets/versions.css'; 42 | document.getElementsByTagName('head')[0].appendChild(CSSLink); 43 | 44 | var script = document.createElement('script'); 45 | script.src = 'https://rollouts-plugin-trafficrouter-gatewayapi.readthedocs.io/_/api/v2/footer_html/?'+ 46 | 'callback=' + callbackName + '&project=rollouts-plugin-trafficrouter-gatewayapi&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (window['READTHEDOCS_DATA'] || { version: 'latest' }).version; 47 | document.getElementsByTagName('head')[0].appendChild(script); 48 | } -------------------------------------------------------------------------------- /test/e2e/utils.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/sirupsen/logrus" 7 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | "sigs.k8s.io/e2e-framework/klient/k8s" 10 | gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 11 | ) 12 | 13 | func getMatchHTTPRouteFetcher(t *testing.T, targetWeight int32) func(k8s.Object) bool { 14 | return func(obj k8s.Object) bool { 15 | var httpRoute gatewayv1.HTTPRoute 16 | unstructuredHTTPRoute, ok := obj.(*unstructured.Unstructured) 17 | if !ok { 18 | logrus.Error("k8s object type assertion was failed") 19 | t.Error() 20 | return false 21 | } 22 | // logrus.Info("k8s object was type asserted") 23 | err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredHTTPRoute.Object, &httpRoute) 24 | if err != nil { 25 | logrus.Errorf("conversation from unstructured httpRoute %q to the typed httpRoute was failed", unstructuredHTTPRoute.GetName()) 26 | t.Error() 27 | return false 28 | } 29 | // logrus.Infof("unstructured httpRoute %q was converted to the typed httpRoute", httpRoute.GetName()) 30 | return *httpRoute.Spec.Rules[ROLLOUT_ROUTE_RULE_INDEX].BackendRefs[CANARY_BACKEND_REF_INDEX].Weight == targetWeight 31 | } 32 | } 33 | 34 | func getMatchGRPCRouteFetcher(t *testing.T, targetWeight int32) func(k8s.Object) bool { 35 | return func(obj k8s.Object) bool { 36 | var grpcRoute gatewayv1.GRPCRoute 37 | unstructuredGRPCRoute, ok := obj.(*unstructured.Unstructured) 38 | if !ok { 39 | logrus.Error("k8s object type assertion was failed") 40 | t.Error() 41 | return false 42 | } 43 | // logrus.Info("k8s object was type asserted") 44 | err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredGRPCRoute.Object, &grpcRoute) 45 | if err != nil { 46 | logrus.Errorf("conversation from unstructured grpcRoute %q to the typed grpcRoute was failed", unstructuredGRPCRoute.GetName()) 47 | t.Error() 48 | return false 49 | } 50 | // logrus.Infof("Looking for grpcRoute %q to reach weight %d", grpcRoute.GetName(), targetWeight) 51 | return *grpcRoute.Spec.Rules[ROLLOUT_ROUTE_RULE_INDEX].BackendRefs[CANARY_BACKEND_REF_INDEX].Weight == targetWeight 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/features/grpc.md: -------------------------------------------------------------------------------- 1 | # GRPC routes 2 | 3 | To use GRPCRoute: 4 | 5 | 1. Install your traffic provider 6 | 2. Install [GatewayAPI CRD](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api) if your traffic provider doesn't do it by default 7 | 3. Install [Argo Rollouts](https://argoproj.github.io/argo-rollouts/installation/) 8 | 4. Install [Argo Rollouts GatewayAPI plugin](../installation.md) 9 | 5. Create stable and canary services 10 | 6. Create GRPCRoute resource according to the GatewayAPI and your traffic provider documentation 11 | ```yaml 12 | apiVersion: gateway.networking.k8s.io/v1 13 | kind: GRPCRoute 14 | metadata: 15 | name: first-grpcroute 16 | namespace: default 17 | spec: 18 | parentRefs: 19 | - name: traefik-gateway # read documentation of your traffic provider to understand what you need to specify here 20 | rules: 21 | - backendRefs: 22 | - name: argo-rollouts-stable-service # stable service you have created on the 5th step 23 | port: 80 24 | - name: argo-rollouts-canary-service # canary service you have created on the 5th step 25 | port: 80 26 | ``` 27 | 1. Create Rollout resource 28 | ```yaml 29 | apiVersion: argoproj.io/v1alpha1 30 | kind: Rollout 31 | metadata: 32 | name: rollouts-demo 33 | namespace: default 34 | spec: 35 | replicas: 2 36 | strategy: 37 | canary: 38 | canaryService: argo-rollouts-canary-service 39 | stableService: argo-rollouts-stable-service 40 | trafficRouting: 41 | plugins: 42 | argoproj-labs/gatewayAPI: 43 | grpcRoute: first-grpcroute # grpcroute you have created on the 6th step 44 | namespace: default # namespace where your grpcroute is 45 | steps: 46 | - setWeight: 30 47 | - pause: { duration: 2 } 48 | revisionHistoryLimit: 1 49 | selector: 50 | matchLabels: 51 | app: rollouts-demo 52 | template: 53 | metadata: 54 | labels: 55 | app: rollouts-demo 56 | spec: 57 | containers: 58 | - name: rollouts-demo 59 | image: argoproj/rollouts-demo:red 60 | ports: 61 | - name: http 62 | containerPort: 8080 63 | protocol: TCP 64 | resources: 65 | requests: 66 | memory: 32Mi 67 | cpu: 5m 68 | ``` -------------------------------------------------------------------------------- /docs/features/tcp.md: -------------------------------------------------------------------------------- 1 | # TCP Routes 2 | 3 | To use TCPRoute: 4 | 5 | 1. Install your traffic provider 6 | 2. Install [GatewayAPI CRD](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api) if your traffic provider doesn't do it by default 7 | 3. Install [Argo Rollouts](https://argoproj.github.io/argo-rollouts/installation/) 8 | 4. Install [Argo Rollouts GatewayAPI plugin](../installation.md) 9 | 5. Create stable and canary services 10 | 6. Create TCPRoute resource according to the GatewayAPI and your traffic provider documentation 11 | ```yaml 12 | apiVersion: gateway.networking.k8s.io/v1alpha2 13 | kind: TCPRoute 14 | metadata: 15 | name: first-tcproute 16 | namespace: default 17 | spec: 18 | parentRefs: 19 | - name: traefik-gateway # read documentation of your traffic provider to understand what you need to specify here 20 | sectionName: tcp 21 | namespace: default 22 | kind: Gateway 23 | rules: 24 | - backendRefs: 25 | - name: argo-rollouts-stable-service # stable service you have created on the 5th step 26 | port: 80 27 | - name: argo-rollouts-canary-service # canary service you have created on the 5th step 28 | port: 80 29 | ``` 30 | 7. Create Rollout resource 31 | ```yaml 32 | apiVersion: argoproj.io/v1alpha1 33 | kind: Rollout 34 | metadata: 35 | name: rollouts-demo 36 | namespace: default 37 | spec: 38 | replicas: 2 39 | strategy: 40 | canary: 41 | canaryService: argo-rollouts-canary-service 42 | stableService: argo-rollouts-stable-service 43 | trafficRouting: 44 | plugins: 45 | argoproj-labs/gatewayAPI: 46 | tcpRoute: first-tcproute # tcproute you have created on the 6th step 47 | namespace: default # namespace where your tcproute is 48 | steps: 49 | - setWeight: 30 50 | - pause: { duration: 2 } 51 | revisionHistoryLimit: 1 52 | selector: 53 | matchLabels: 54 | app: rollouts-demo 55 | template: 56 | metadata: 57 | labels: 58 | app: rollouts-demo 59 | spec: 60 | containers: 61 | - name: rollouts-demo 62 | image: argoproj/rollouts-demo:red 63 | ports: 64 | - name: http 65 | containerPort: 8080 66 | protocol: TCP 67 | resources: 68 | requests: 69 | memory: 32Mi 70 | cpu: 5m 71 | ``` -------------------------------------------------------------------------------- /docs/features/tls.md: -------------------------------------------------------------------------------- 1 | # TLS Routes 2 | 3 | To use TLSRoute: 4 | 5 | 1. Install your traffic provider 6 | 2. Install [GatewayAPI CRD](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api) if your traffic provider doesn't do it by default 7 | 3. Install [Argo Rollouts](https://argoproj.github.io/argo-rollouts/installation/) 8 | 4. Install [Argo Rollouts GatewayAPI plugin](../installation.md) 9 | 5. Create stable and canary services 10 | 6. Create TLSRoute resource according to the GatewayAPI and your traffic provider documentation 11 | ```yaml 12 | apiVersion: gateway.networking.k8s.io/v1alpha2 13 | kind: TLSRoute 14 | metadata: 15 | name: first-tlsroute 16 | namespace: default 17 | spec: 18 | parentRefs: 19 | - name: traefik-gateway # read documentation of your traffic provider to understand what you need to specify here 20 | sectionName: tls 21 | namespace: default 22 | kind: Gateway 23 | hostnames: 24 | - "example.com" # SNI hostname for TLS traffic routing 25 | rules: 26 | - backendRefs: 27 | - name: argo-rollouts-stable-service # stable service you have created on the 5th step 28 | port: 443 29 | - name: argo-rollouts-canary-service # canary service you have created on the 5th step 30 | port: 443 31 | ``` 32 | 7. Create Rollout resource 33 | ```yaml 34 | apiVersion: argoproj.io/v1alpha1 35 | kind: Rollout 36 | metadata: 37 | name: rollouts-demo 38 | namespace: default 39 | spec: 40 | replicas: 2 41 | strategy: 42 | canary: 43 | canaryService: argo-rollouts-canary-service 44 | stableService: argo-rollouts-stable-service 45 | trafficRouting: 46 | plugins: 47 | argoproj-labs/gatewayAPI: 48 | tlsRoute: first-tlsroute # tlsroute you have created on the 6th step 49 | namespace: default # namespace where your tlsroute is 50 | steps: 51 | - setWeight: 30 52 | - pause: { duration: 2 } 53 | revisionHistoryLimit: 1 54 | selector: 55 | matchLabels: 56 | app: rollouts-demo 57 | template: 58 | metadata: 59 | labels: 60 | app: rollouts-demo 61 | spec: 62 | containers: 63 | - name: rollouts-demo 64 | image: argoproj/rollouts-demo:red 65 | ports: 66 | - name: https 67 | containerPort: 8080 68 | protocol: TCP 69 | resources: 70 | requests: 71 | memory: 32Mi 72 | cpu: 5m 73 | ``` 74 | 75 | ## Traffic Provider Support 76 | 77 | TLSRoute is part of the Gateway API experimental channel. Ensure your traffic provider supports TLSRoute before using it in production. Check the [Gateway API implementations list](https://gateway-api.sigs.k8s.io/implementations/) for TLSRoute support. 78 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Progressive Delivery with the Kubernetes Gateway API 2 | 3 | [Argo Rollouts](https://argoproj.github.io/rollouts/) is a progressive delivery controller for Kubernetes. It supports several advanced deployment methods such as [blue/green](https://argo-rollouts.readthedocs.io/en/stable/features/bluegreen/) and [canaries](https://argo-rollouts.readthedocs.io/en/stable/features/canary/). 4 | 5 | For canary deployments Argo Rollouts can optionally use [a traffic provider](https://argoproj.github.io/argo-rollouts/features/traffic-management/) to split traffic between pods with full control and in a gradual way. 6 | 7 | ![Gateway API with traffic providers](images/gateway-api.png) 8 | 9 | Until recently adding a new traffic provider to Argo Rollouts needed ad-hoc support code. With the adoption of the [Gateway API](https://gateway-api.sigs.k8s.io/), the integration becomes much easier as any traffic provider that implements the API will automatically be supported by Argo Rollouts. 10 | 11 | ## The Kubernetes Gateway API 12 | 13 | The Gateway API is an open source project managed by the [SIG-NETWORK](https://github.com/kubernetes/community/tree/master/sig-network) community. It is a collection of resources that model service networking in Kubernetes. 14 | 15 | See a [list of current projects](https://gateway-api.sigs.k8s.io/implementations/) that support the API. 16 | 17 | ## Prerequisites 18 | 19 | You need the following 20 | 21 | 1. A Kubernetes cluster 22 | 2. An [installation](https://argoproj.github.io/argo-rollouts/installation/) of the Argo Rollouts controller 23 | 3. A traffic provider that [supports the Gateway API](https://gateway-api.sigs.k8s.io/implementations/) 24 | 4. An [installation of the Gateway plugin](installation.md) 25 | 26 | Once everything is ready you need to create [a Rollout resource](https://argoproj.github.io/argo-rollouts/features/specification/) for all workloads that will use the integration. 27 | 28 | ## How to use the Gateway API with Argo Rollouts 29 | 30 | This is the installation process. 31 | 32 | 1. Enable Gateway Provider and create Gateway entrypoint 33 | 1. Create GatewayClass and Gateway resources 34 | 1. Create cluster entrypoint and map it with our Gateway entrypoint 35 | 1. Install Argo Rollouts in your cluster along with the Gateway API plugin 36 | 1. Create an HTTPRoute 37 | 1. Create canary and stable services 38 | 1. Create your Rollout resources 39 | 1. Start a deployment 40 | 41 | The first 3 steps are specific to your provider/implementation of the Gateway API inside the Kubernetes cluster. The rest of the steps are the same regardless of the specific implementation you chose. 42 | 43 | See end-to-end examples for several other implementations 44 | at the [provider status page](provider-status.md) or try our [quick start guide](quick-start.md). 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Gateway API plugin CI 2 | on: 3 | # Run the workflow manually without pushing a commit 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "main" 8 | pull_request: 9 | branches: 10 | - "main" 11 | env: 12 | GOLANG_VERSION: '1.24' 13 | 14 | jobs: 15 | linting: 16 | name: Go code linting 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: ${{ env.GOLANG_VERSION }} 23 | 24 | - name: Checkout code 25 | uses: actions/checkout@v3.1.0 26 | 27 | - name: Run golangci-lint 28 | uses: golangci/golangci-lint-action@v3 29 | with: 30 | args: --verbose --timeout 6m 31 | 32 | unit-tests: 33 | name: Unit tests running 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Set up Go 37 | uses: actions/setup-go@v3 38 | with: 39 | go-version: ${{ env.GOLANG_VERSION }} 40 | 41 | - name: Checkout code 42 | uses: actions/checkout@v3.1.0 43 | 44 | - name: Unit tests running 45 | run: | 46 | make unit-tests 47 | 48 | e2e-tests: 49 | name: E2E tests running 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Set up Go 53 | uses: actions/setup-go@v3 54 | with: 55 | go-version: ${{ env.GOLANG_VERSION }} 56 | 57 | - name: Set up Kind 58 | uses: engineerd/setup-kind@v0.5.0 59 | with: 60 | version: "v0.23.0" 61 | skipClusterCreation: true 62 | 63 | - name: Checkout code 64 | uses: actions/checkout@v3.1.0 65 | 66 | - name: E2E tests running 67 | run: | 68 | make e2e-tests 69 | 70 | e2e-tests-flaky: 71 | name: E2E Flaky tests 72 | runs-on: ubuntu-latest 73 | steps: 74 | - name: Set up Go 75 | uses: actions/setup-go@v3 76 | with: 77 | go-version: ${{ env.GOLANG_VERSION }} 78 | 79 | - name: Set up Kind 80 | uses: engineerd/setup-kind@v0.5.0 81 | with: 82 | version: "v0.23.0" 83 | skipClusterCreation: true 84 | 85 | - name: Checkout code 86 | uses: actions/checkout@v3.1.0 87 | 88 | - name: E2E tests running 89 | run: | 90 | make e2e-tests-flaky 91 | 92 | build: 93 | name: Build creation 94 | runs-on: ubuntu-latest 95 | steps: 96 | - name: Set up Go 97 | uses: actions/setup-go@v3 98 | with: 99 | go-version: ${{ env.GOLANG_VERSION }} 100 | 101 | - name: Checkout code 102 | uses: actions/checkout@v3.1.0 103 | 104 | - name: Build creation 105 | run: | 106 | go build -v 107 | -------------------------------------------------------------------------------- /internal/utils/common.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" 7 | log "github.com/sirupsen/logrus" 8 | v1 "k8s.io/api/core/v1" 9 | kubeErrors "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/clientcmd" 13 | ) 14 | 15 | func GetKubeConfig() (*rest.Config, error) { 16 | loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 17 | // if you want to change the loading rules (which files in which order), you can do so here 18 | configOverrides := &clientcmd.ConfigOverrides{} 19 | // if you want to change override values or bind them to flags, there are methods to help you 20 | kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) 21 | config, err := kubeConfig.ClientConfig() 22 | if err != nil { 23 | return nil, pluginTypes.RpcError{ErrorString: err.Error()} 24 | } 25 | return config, nil 26 | } 27 | 28 | func SetupLog() *log.Entry { 29 | log.SetLevel(log.InfoLevel) 30 | log.SetFormatter( 31 | &log.TextFormatter{ 32 | FullTimestamp: true, 33 | }, 34 | ) 35 | return log.WithFields(log.Fields{"plugin": "trafficrouter"}) 36 | } 37 | 38 | func GetOrCreateConfigMap(name string, options CreateConfigMapOptions) (*v1.ConfigMap, error) { 39 | clientset := options.Clientset 40 | ctx := options.Ctx 41 | configMap, err := clientset.Get(ctx, name, metav1.GetOptions{}) 42 | if err != nil && !kubeErrors.IsNotFound(err) { 43 | return nil, err 44 | } 45 | if err == nil { 46 | return configMap, err 47 | } 48 | configMap.Name = name 49 | configMap, err = clientset.Create(ctx, configMap, metav1.CreateOptions{}) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return configMap, err 54 | } 55 | 56 | func GetConfigMapData(configMap *v1.ConfigMap, configMapKey string, destination any) error { 57 | if configMap.Data != nil && configMap.Data[configMapKey] != "" { 58 | err := json.Unmarshal([]byte(configMap.Data[configMapKey]), &destination) 59 | if err != nil { 60 | return err 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | func UpdateConfigMapData(configMap *v1.ConfigMap, configMapData any, options UpdateConfigMapOptions) error { 67 | clientset := options.Clientset 68 | rawConfigMapData, err := json.Marshal(configMapData) 69 | if err != nil { 70 | return err 71 | } 72 | if configMap.Data == nil { 73 | configMap.Data = make(map[string]string) 74 | } 75 | configMap.Data[options.ConfigMapKey] = string(rawConfigMapData) 76 | _, err = clientset.Update(options.Ctx, configMap, metav1.UpdateOptions{}) 77 | return err 78 | } 79 | 80 | func DoTransaction(logCtx *log.Entry, taskList ...Task) error { 81 | var err, reverseErr error 82 | for index, task := range taskList { 83 | err = task.Action() 84 | if err == nil { 85 | continue 86 | } 87 | logCtx.Error(err.Error()) 88 | for i := index - 1; i > -1; i-- { 89 | reverseErr = taskList[i].ReverseAction() 90 | if reverseErr != nil { 91 | return reverseErr 92 | } 93 | } 94 | return err 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /pkg/plugin/tcproute.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" 8 | pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | func (r *RpcPlugin) setTCPRouteWeight(rollout *v1alpha1.Rollout, desiredWeight int32, gatewayAPIConfig *GatewayAPITrafficRouting) pluginTypes.RpcError { 13 | ctx := context.TODO() 14 | tcpRouteClient := r.TCPRouteClient 15 | if !r.IsTest { 16 | gatewayClientV1alpha2 := r.GatewayAPIClientset.GatewayV1alpha2() 17 | tcpRouteClient = gatewayClientV1alpha2.TCPRoutes(gatewayAPIConfig.Namespace) 18 | } 19 | tcpRoute, err := tcpRouteClient.Get(ctx, gatewayAPIConfig.TCPRoute, metav1.GetOptions{}) 20 | if err != nil { 21 | return pluginTypes.RpcError{ 22 | ErrorString: err.Error(), 23 | } 24 | } 25 | canaryServiceName := rollout.Spec.Strategy.Canary.CanaryService 26 | stableServiceName := rollout.Spec.Strategy.Canary.StableService 27 | routeRuleList := TCPRouteRuleList(tcpRoute.Spec.Rules) 28 | canaryBackendRefs, err := getBackendRefs(canaryServiceName, routeRuleList) 29 | if err != nil { 30 | return pluginTypes.RpcError{ 31 | ErrorString: err.Error(), 32 | } 33 | } 34 | for _, ref := range canaryBackendRefs { 35 | ref.Weight = &desiredWeight 36 | } 37 | stableBackendRefs, err := getBackendRefs(stableServiceName, routeRuleList) 38 | if err != nil { 39 | return pluginTypes.RpcError{ 40 | ErrorString: err.Error(), 41 | } 42 | } 43 | restWeight := 100 - desiredWeight 44 | for _, ref := range stableBackendRefs { 45 | ref.Weight = &restWeight 46 | } 47 | ensureInProgressLabel(tcpRoute, desiredWeight, gatewayAPIConfig) 48 | updatedTCPRoute, err := tcpRouteClient.Update(ctx, tcpRoute, metav1.UpdateOptions{}) 49 | if r.IsTest { 50 | r.UpdatedTCPRouteMock = updatedTCPRoute 51 | } 52 | if err != nil { 53 | return pluginTypes.RpcError{ 54 | ErrorString: err.Error(), 55 | } 56 | } 57 | return pluginTypes.RpcError{} 58 | } 59 | 60 | func (r *TCPRouteRule) Iterator() (GatewayAPIRouteRuleIterator[*TCPBackendRef], bool) { 61 | backendRefList := r.BackendRefs 62 | index := 0 63 | next := func() (*TCPBackendRef, bool) { 64 | if len(backendRefList) == index { 65 | return nil, false 66 | } 67 | backendRef := (*TCPBackendRef)(&backendRefList[index]) 68 | index = index + 1 69 | return backendRef, len(backendRefList) > index 70 | } 71 | return next, len(backendRefList) > index 72 | } 73 | 74 | func (r TCPRouteRuleList) Iterator() (GatewayAPIRouteRuleListIterator[*TCPBackendRef, *TCPRouteRule], bool) { 75 | routeRuleList := r 76 | index := 0 77 | next := func() (*TCPRouteRule, bool) { 78 | if len(routeRuleList) == index { 79 | return nil, false 80 | } 81 | routeRule := (*TCPRouteRule)(&routeRuleList[index]) 82 | index = index + 1 83 | return routeRule, len(routeRuleList) > index 84 | } 85 | return next, len(routeRuleList) > index 86 | } 87 | 88 | func (r TCPRouteRuleList) Error() error { 89 | return errors.New(BackendRefListWasNotFoundInTCPRouteError) 90 | } 91 | 92 | func (r *TCPBackendRef) GetName() string { 93 | return string(r.Name) 94 | } 95 | 96 | func (r TCPRoute) GetName() string { 97 | return r.Name 98 | } 99 | -------------------------------------------------------------------------------- /pkg/plugin/tlsroute.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" 8 | pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | func (r *RpcPlugin) setTLSRouteWeight(rollout *v1alpha1.Rollout, desiredWeight int32, gatewayAPIConfig *GatewayAPITrafficRouting) pluginTypes.RpcError { 13 | ctx := context.TODO() 14 | tlsRouteClient := r.TLSRouteClient 15 | if !r.IsTest { 16 | gatewayClientV1alpha2 := r.GatewayAPIClientset.GatewayV1alpha2() 17 | tlsRouteClient = gatewayClientV1alpha2.TLSRoutes(gatewayAPIConfig.Namespace) 18 | } 19 | tlsRoute, err := tlsRouteClient.Get(ctx, gatewayAPIConfig.TLSRoute, metav1.GetOptions{}) 20 | if err != nil { 21 | return pluginTypes.RpcError{ 22 | ErrorString: err.Error(), 23 | } 24 | } 25 | canaryServiceName := rollout.Spec.Strategy.Canary.CanaryService 26 | stableServiceName := rollout.Spec.Strategy.Canary.StableService 27 | routeRuleList := TLSRouteRuleList(tlsRoute.Spec.Rules) 28 | canaryBackendRefs, err := getBackendRefs(canaryServiceName, routeRuleList) 29 | if err != nil { 30 | return pluginTypes.RpcError{ 31 | ErrorString: err.Error(), 32 | } 33 | } 34 | for _, ref := range canaryBackendRefs { 35 | ref.Weight = &desiredWeight 36 | } 37 | stableBackendRefs, err := getBackendRefs(stableServiceName, routeRuleList) 38 | if err != nil { 39 | return pluginTypes.RpcError{ 40 | ErrorString: err.Error(), 41 | } 42 | } 43 | restWeight := 100 - desiredWeight 44 | for _, ref := range stableBackendRefs { 45 | ref.Weight = &restWeight 46 | } 47 | ensureInProgressLabel(tlsRoute, desiredWeight, gatewayAPIConfig) 48 | updatedTLSRoute, err := tlsRouteClient.Update(ctx, tlsRoute, metav1.UpdateOptions{}) 49 | if r.IsTest { 50 | r.UpdatedTLSRouteMock = updatedTLSRoute 51 | } 52 | if err != nil { 53 | return pluginTypes.RpcError{ 54 | ErrorString: err.Error(), 55 | } 56 | } 57 | return pluginTypes.RpcError{} 58 | } 59 | 60 | func (r *TLSRouteRule) Iterator() (GatewayAPIRouteRuleIterator[*TLSBackendRef], bool) { 61 | backendRefList := r.BackendRefs 62 | index := 0 63 | next := func() (*TLSBackendRef, bool) { 64 | if len(backendRefList) == index { 65 | return nil, false 66 | } 67 | backendRef := (*TLSBackendRef)(&backendRefList[index]) 68 | index = index + 1 69 | return backendRef, len(backendRefList) > index 70 | } 71 | return next, len(backendRefList) > index 72 | } 73 | 74 | func (r TLSRouteRuleList) Iterator() (GatewayAPIRouteRuleListIterator[*TLSBackendRef, *TLSRouteRule], bool) { 75 | routeRuleList := r 76 | index := 0 77 | next := func() (*TLSRouteRule, bool) { 78 | if len(routeRuleList) == index { 79 | return nil, false 80 | } 81 | routeRule := (*TLSRouteRule)(&routeRuleList[index]) 82 | index = index + 1 83 | return routeRule, len(routeRuleList) > index 84 | } 85 | return next, len(routeRuleList) > index 86 | } 87 | 88 | func (r TLSRouteRuleList) Error() error { 89 | return errors.New(BackendRefListWasNotFoundInTLSRouteError) 90 | } 91 | 92 | func (r *TLSBackendRef) GetName() string { 93 | return string(r.Name) 94 | } 95 | 96 | func (r TLSRoute) GetName() string { 97 | return r.Name 98 | } 99 | -------------------------------------------------------------------------------- /docs/provider-status.md: -------------------------------------------------------------------------------- 1 | # Provider Status 2 | 3 | Several Service Mesh and Gateway solutions are already implementing 4 | the Gateway API. You can find a contributed list of known implementations at the [Gateway API website](https://gateway-api.sigs.k8s.io/implementations/). 5 | 6 | All providers should work out of the box with Argo Rollouts and the Gateway plugin. 7 | 8 | !!! warning 9 | Notice that with 0.x implementations of the Gateway API, only the 0.2.0 release of the 10 | plugin will work. Versions from 0.3.0 and upwards need a v1.0+ implementation 11 | to work. Plugin version 0.2.0 uses `v1beta1` resources while 0.3.0 needs `gateway.networking.k8s.io/v1` resources 12 | 13 | For convenience we are including here a list of those actually tested with the plugin along with the related example (if applicable). 14 | 15 | 16 | | Provider | Version | API Version | Plugin | Code | 17 | |------------|------------|-------------| ---------| ---------| 18 | | [Amazon VPC Lattice](https://www.gateway-api-controller.eks.aws.dev/latest//) | 1.1.2 | 1.3.0 | 0.6.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/aws-gateway-api-controller-lattice ) | 19 | | [Cilium](https://cilium.io/) | 1.18.2 | 1.2.0 | 0.8.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/cilium-header-based) | 20 | | [Envoy Gateway](https://gateway.envoyproxy.io/) | 0.5.0 | Unknown | 0.2.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/envoygateway) | 21 | | [Gloo Gateway](https://docs.solo.io/gloo-gateway/v2/) | 2.0.0-beta | 1.0 | 0.2.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/gloo-gateway) | 22 | | [Google Cloud](https://cloud.google.com/kubernetes-engine/docs/concepts/gateway-api) | N/A | 0.7.0 | 0.2.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/google-cloud) | 23 | | [Kong](https://docs.konghq.com/kubernetes-ingress-controller/latest/concepts/gateway-api/) | 2.9.x | 0.7.1 | 0.2.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/kong) | 24 | | [Linkerd](https://linkerd.io/) | edge-25.9.4 | 1.2.1 | 0.8.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/linkerd-header-based) | 25 | | [NGINX Gateway](https://github.com/nginxinc/nginx-gateway-fabric) | Unknown | 0.8.0 | 0.2.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/nginx) | 26 | | [Traefik](https://doc.traefik.io/traefik/providers/kubernetes-gateway/) | 3.1.3 | 1.1 | 0.4.0 | [Example](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/tree/main/examples/traefik) | 27 | 28 | Note that these examples are included just for completeness. You should be able 29 | to use any solution that implements the Gateway API. 30 | 31 | !!! note 32 | We are always looking for more tested implementations. If you have tried the plugin with a provider not listed above please [open a Pull Request](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/pulls) to add it to the list. 33 | -------------------------------------------------------------------------------- /test/e2e/constants.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "time" 5 | 6 | gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 7 | ) 8 | 9 | const ( 10 | // HTTP Route test paths 11 | HTTP_ROUTE_BASIC_PATH = "./testdata/httproute-basic.yml" 12 | HTTP_ROUTE_HEADER_PATH = "./testdata/httproute-header.yml" 13 | HTTP_ROUTE_BASIC_ROLLOUT_PATH = "./testdata/single-httproute-rollout.yml" 14 | HTTP_ROUTE_HEADER_ROLLOUT_PATH = "./testdata/single-header-based-httproute-rollout.yml" 15 | 16 | // GRPC Route test paths 17 | GRPC_ROUTE_BASIC_PATH = "./testdata/grpcroute-basic.yml" 18 | GRPC_ROUTE_HEADER_PATH = "./testdata/grpcroute-header.yml" 19 | GRPC_ROUTE_BASIC_ROLLOUT_PATH = "./testdata/single-grpcroute-rollout.yml" 20 | GRPC_ROUTE_HEADER_ROLLOUT_PATH = "./testdata/single-header-based-grpcroute-rollout.yml" 21 | 22 | // TCP Route test paths 23 | TCP_ROUTE_BASIC_PATH = "./testdata/tcproute-basic.yml" 24 | TCP_ROUTE_BASIC_ROLLOUT_PATH = "./testdata/single-tcproute-rollout.yml" 25 | 26 | // TLS Route test paths 27 | TLS_ROUTE_BASIC_PATH = "./testdata/tlsroute-basic.yml" 28 | TLS_ROUTE_BASIC_ROLLOUT_PATH = "./testdata/single-tlsroute-rollout.yml" 29 | 30 | // HTTP Route filter test paths 31 | HTTP_ROUTE_FILTERS_PATH = "./testdata/httproute-filters.yml" 32 | HTTP_ROUTE_FILTERS_ROLLOUT_PATH = "./testdata/single-httproute-filters-rollout.yml" 33 | 34 | // GRPC Route filter test paths 35 | GRPC_ROUTE_FILTERS_PATH = "./testdata/grpcroute-filters.yml" 36 | GRPC_ROUTE_FILTERS_ROLLOUT_PATH = "./testdata/single-grpcroute-filters-rollout.yml" 37 | 38 | // HTTP Route label test paths 39 | HTTP_ROUTE_LABEL_PATH = "./testdata/httproute-basic.yml" 40 | HTTP_ROUTE_LABEL_ROLLOUT_PATH = "./testdata/single-httproute-label-rollout.yml" 41 | 42 | ROLLOUT_TEMPLATE_CONTAINERS_FIELD = "spec.template.spec.containers" 43 | ROLLOUT_TEMPLATE_FIRST_CONTAINER_FIELD = "spec.template.spec.containers.0" 44 | NEW_IMAGE_FIELD_VALUE = "argoproj/rollouts-demo:green" 45 | 46 | ROLLOUT_ROUTE_RULE_INDEX = 0 47 | FIRST_HEADER_BASED_RULES_LENGTH = 2 48 | HEADER_BASED_RULE_INDEX = 1 49 | LAST_HEADER_BASED_RULES_LENGTH = 1 50 | 51 | HEADER_BASED_MATCH_INDEX = 0 52 | HEADER_BASED_HEADER_INDEX = 0 53 | 54 | CANARY_BACKEND_REF_INDEX = 1 55 | HEADER_BASED_BACKEND_REF_INDEX = 0 56 | 57 | FIRST_CANARY_ROUTE_WEIGHT = 0 58 | LAST_CANARY_ROUTE_WEIGHT = 30 59 | 60 | RESOURCES_MAP_KEY contextKey = "resourcesMap" 61 | 62 | HTTP_ROUTE_KEY = "httpRoute" 63 | GRPC_ROUTE_KEY = "grpcRoute" 64 | TCP_ROUTE_KEY = "tcpRoute" 65 | TLS_ROUTE_KEY = "tlsRoute" 66 | ROLLOUT_KEY = "rollout" 67 | ) 68 | 69 | const ( 70 | SHORT_PERIOD = time.Second 71 | MEDIUM_PERIOD = 30 * time.Second 72 | LONG_PERIOD = 60 * time.Second 73 | ) 74 | 75 | var ( 76 | FIRST_HEADER_BASED_HTTP_ROUTE_VALUE gatewayv1.HTTPHeaderMatch 77 | headerBasedHTTPRouteValueType = gatewayv1.HeaderMatchExact 78 | LAST_HEADER_BASED_HTTP_ROUTE_VALUE = gatewayv1.HTTPHeaderMatch{ 79 | Name: "X-Test", 80 | Type: &headerBasedHTTPRouteValueType, 81 | Value: "test", 82 | } 83 | 84 | FIRST_HEADER_BASED_GRPC_ROUTE_VALUE gatewayv1.GRPCHeaderMatch 85 | headerBasedGRPCRouteValueType = gatewayv1.GRPCHeaderMatchExact 86 | LAST_HEADER_BASED_GRPC_ROUTE_VALUE = gatewayv1.GRPCHeaderMatch{ 87 | Name: "X-Test", 88 | Type: &headerBasedGRPCRouteValueType, 89 | Value: "test", 90 | } 91 | ) 92 | -------------------------------------------------------------------------------- /examples/google-cloud-experiment/Readme.md: -------------------------------------------------------------------------------- 1 | # Argo Rollouts Gateway API Experiment Support 2 | 3 | This feature adds support for conducting experiments with Argo Rollouts using the Kubernetes Gateway API. Experiments allow you to test multiple versions of your application simultaneously with precise control over traffic distribution. 4 | 5 | ## Overview 6 | 7 | When using the Gateway API traffic router with Argo Rollouts, you can now define experiments that: 8 | 9 | - Automatically adjust traffic weights in HTTPRoutes for the additional services created for experiment variants 10 | - Clean up experiment services when experiments complete 11 | 12 | ## How It Works 13 | 14 | The plugin automatically: 15 | 16 | 1. Detects when an experiment is active in a rollout 17 | 2. Adjusts the stable service weight to accommodate experiment traffic 18 | 3. Adds experiment services to the HTTPRoute with appropriate weights 19 | 4. Removes experiment services when the experiment completes 20 | 21 | ## Example Usage 22 | 23 | The included example demonstrates a rollout with an experiment step that tests: 24 | - A baseline variant based on the stable version (10% traffic) 25 | - A canary variant based on the new version (10% traffic) 26 | 27 | During the experiment: 28 | - The stable service receives 80% of traffic (reduced from 100%) 29 | - The canary service continues to receive 0% traffic 30 | - The experiment variants receive their specified weights ( 10% , 10%) 31 | 32 | After the experiment completes, traffic distribution returns to normal with stable receiving 100% until the next step begins. 33 | 34 | ### Sample Manifest 35 | 36 | ```yaml 37 | apiVersion: argoproj.io/v1alpha1 38 | kind: Rollout 39 | metadata: 40 | name: demo-app 41 | namespace: demo 42 | spec: 43 | strategy: 44 | canary: 45 | canaryService: demo-app-canary 46 | stableService: demo-app-stable 47 | trafficRouting: 48 | plugins: 49 | argoproj-labs/gatewayAPI: 50 | httpRoute: demo-app-route 51 | namespace: demo 52 | steps: 53 | - experiment: 54 | duration: 5m 55 | templates: 56 | - name: experiment-baseline 57 | specRef: stable 58 | service: 59 | name: demo-app-exp-baseline 60 | weight: 10 61 | - name: experiment-canary 62 | specRef: canary 63 | service: 64 | name: demo-app-exp-canary 65 | weight: 10 66 | # Remaining steps... 67 | ``` 68 | 69 | ## Implementation Details 70 | 71 | The experiment handler: 72 | 73 | 1. Identifies the matching rule in the HTTPRoute for the rollout 74 | 2. Checks if an experiment is active by examining `rollout.Status.Canary.CurrentExperiment` 75 | 3. For active experiments: 76 | - Sets the stable service weight to 80% 77 | - Adds experiment services from `rollout.Status.Canary.Weights.Additional` 78 | 4. For inactive experiments: 79 | - Removes any experiment services from the HTTPRoute 80 | - Resets the stable service weight to 100% 81 | 82 | ## Requirements 83 | 84 | - Kubernetes cluster with Gateway API CRDs installed 85 | - Argo Rollouts v1.5.0 or newer 86 | - Simple HTTP Gateway (TLS configuration optional) 87 | 88 | ## See Also 89 | 90 | - [Argo Rollouts Documentation](https://argoproj.github.io/argo-rollouts/) 91 | - [Gateway API Documentation](https://gateway-api.sigs.k8s.io/) 92 | - [Experiment Documentation](https://argoproj.github.io/argo-rollouts/features/experiment/) -------------------------------------------------------------------------------- /examples/linkerd/README.md: -------------------------------------------------------------------------------- 1 | # Using Linkerd with Argo Rollouts 2 | 3 | [Linkerd](https://linkerd.io/) is a service mesh for Kubernetes. It makes running services easier and safer by giving you runtime debugging, observability, reliability, and security—all without requiring any changes to your code. 4 | 5 | ## Prerequisites 6 | 7 | A Kubernetes cluster. If you do not have one, you can create one using [kind](https://kind.sigs.k8s.io/), [minikube](https://minikube.sigs.k8s.io/), or any other Kubernetes cluster. This guide will use Kind. 8 | 9 | Linkerd installed in your Kubernetes cluster. 10 | 11 | 12 | ## Step 1 - Create a Kind cluster by running the following command: 13 | 14 | ```shell 15 | kind delete cluster &>/dev/null 16 | kind create cluster --config ./kind-cluster.yaml 17 | ``` 18 | 19 | ## Step 2 - Install Linkerd and Linkerd Viz by running the following commands: 20 | 21 | I will use the Linkerd CLI to install Linkerd in the cluster. You can also install Linkerd using Helm or kubectl. 22 | I tested this guide with Linkerd version 2.13.0 23 | 24 | ```shell 25 | linkerd install --crds | kubectl apply -f - 26 | linkerd install | kubectl apply -f - && linkerd check 27 | linkerd viz install | kubectl apply -f - && linkerd check 28 | ``` 29 | 30 | 31 | ## Step 3 - Install Argo Rollouts and Argo Rollouts plugin to allow Linkerd to manage the traffic: 32 | 33 | ```shell 34 | kubectl create namespace argo-rollouts 35 | kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml 36 | kubectl apply -k https://github.com/argoproj/argo-rollouts/manifests/crds\?ref\=stable 37 | kubectl apply -f argo-rollouts-plugin.yaml 38 | kubectl rollout restart deploy -n argo-rollouts 39 | ``` 40 | 41 | ## Step 4 - Grant Argo Rollouts SA access to the Gateway/Http Route 42 | ```shell 43 | kubectl apply -f cluster-role.yaml 44 | ``` 45 | __Note:__ These permission are very permissive. You should lock them down according to your needs. 46 | 47 | With the following role we allow Argo Rollouts to have Admin access to HTTPRoutes and Gateways. 48 | 49 | ```shell 50 | kubectl apply -f cluster-role-binding.yaml 51 | ``` 52 | ## Step 5 - Create HTTPRoute that defines a traffic split between two services 53 | 54 | Create HTTPRoute and connect to the created Gateway resource 55 | 56 | ```shell 57 | kubectl apply -f httproute.yaml 58 | ``` 59 | ## Step 6 - Create the services required for traffic split 60 | 61 | Create three Services required for canary based rollout stratedy 62 | 63 | ```shell 64 | kubectl apply -f service.yaml 65 | ``` 66 | 67 | ## Step 7 - Create the services required for traffic split 68 | 69 | Add Linkerd annotaions to the namespace where the services are deployed 70 | 71 | ```shell 72 | kubectl apply -f namespace.yaml 73 | ``` 74 | 75 | ## Step 8 - Create an example Rollout 76 | 77 | Deploy a rollout to get the initial version. 78 | ```shell 79 | kubectl apply -f rollout.yaml 80 | ``` 81 | 82 | ## Step 9 - Watch the rollout 83 | ```shell 84 | watch "kubectl -n default get httproute.gateway.networking.k8s.io/argo-rollouts-http-route -o custom-columns=NAME:.metadata.name,PRIMARY_SERVICE:.spec.rules[0].backendRefs[0].name,PRIMARY_WEIGHT:.spec.rules[0].backendRefs[0].weight,CANARY_SERVICE:.spec.rules[0].backendRefs[1].name,CANARY_WEIGHT:.spec.rules[0].backendRefs[1].weight" 85 | ``` 86 | 87 | ## Step 10 - Patch the rollout to see the canary deployment 88 | ```shell 89 | kubectl patch rollout rollouts-demo --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/env/0/value", "value": "1.1.0"}]' 90 | ``` 91 | 92 | 93 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CURRENT_DIR=$(shell pwd) 2 | DIST_DIR=${CURRENT_DIR}/dist 3 | E2E_CLUSTER_NAME=gatewayapi-plugin-e2e 4 | IS_E2E_CLUSTER=$(shell kind get clusters | grep -e "^${E2E_CLUSTER_NAME}$$") 5 | 6 | # Versions of components used in e2e tests 7 | GATEWAY_API_VERSION=v1.4.0 8 | # See more versions at https://artifacthub.io/packages/helm/argo/argo-rollouts 9 | ARGO_ROLLOUTS_HELM_VERSION=2.40.5 # Contains Argo Rollouts 1.8.3 10 | # See more versions at https://artifacthub.io/packages/helm/traefik/traefik 11 | TRAEFIK_HELM_VERSION=37.4.0 # Contains Traefik proxy v3.6.2 12 | 13 | 14 | 15 | CLUSTER_DELETE ?= true 16 | RUN ?= '' 17 | 18 | define add_helm_repo 19 | helm repo add traefik https://traefik.github.io/charts 20 | helm repo add argo https://argoproj.github.io/argo-helm 21 | endef 22 | 23 | define setup_cluster 24 | helm install argo-rollouts argo/argo-rollouts --values ./test/cluster-setup/argo-rollouts-values.yml --version ${ARGO_ROLLOUTS_HELM_VERSION} --wait 25 | helm install traefik traefik/traefik --values ./test/cluster-setup/traefik-values.yml --version ${TRAEFIK_HELM_VERSION} --wait 26 | kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION}/experimental-install.yaml --server-side=true --force-conflicts 27 | endef 28 | 29 | define install_k8s_resources 30 | kubectl apply -f ./examples/traefik/stable.yml 31 | kubectl apply -f ./examples/traefik/canary.yml 32 | endef 33 | 34 | .PHONY: install-dependencies 35 | install-dependencies: 36 | go mod download 37 | 38 | .PHONY: release 39 | release: 40 | make BIN_NAME=gatewayapi-plugin-darwin-amd64 GOOS=darwin GOARCH=amd64 gatewayapi-plugin-build 41 | make BIN_NAME=gatewayapi-plugin-darwin-arm64 GOOS=darwin GOARCH=arm64 gatewayapi-plugin-build 42 | make BIN_NAME=gatewayapi-plugin-linux-amd64 GOOS=linux GOARCH=amd64 gatewayapi-plugin-build 43 | make BIN_NAME=gatewayapi-plugin-linux-arm64 GOOS=linux GOARCH=arm64 gatewayapi-plugin-build 44 | make BIN_NAME=gatewayapi-plugin-windows-amd64.exe GOOS=windows gatewayapi-plugin-build 45 | 46 | .PHONY: gatewayapi-plugin-build 47 | gatewayapi-plugin-build: 48 | CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -v -o ${DIST_DIR}/${BIN_NAME} . 49 | 50 | .PHONY: local-build 51 | local-build: 52 | go build -gcflags=all="-N -l" -o gatewayapi-plugin 53 | 54 | .PHONY: lint 55 | lint: 56 | golangci-lint run --fix 57 | 58 | .PHONY: unit-tests 59 | unit-tests: 60 | go test -v -count=1 ./pkg/... 61 | 62 | .PHONY: setup-e2e-cluster 63 | setup-e2e-cluster: 64 | make BIN_NAME=gatewayapi-plugin-linux-amd64 GOOS=linux GOARCH=amd64 gatewayapi-plugin-build 65 | ifeq (${IS_E2E_CLUSTER},) 66 | kind create cluster --name ${E2E_CLUSTER_NAME} --config ./test/cluster-setup/cluster-config.yml 67 | $(call add_helm_repo) 68 | $(call setup_cluster) 69 | $(call install_k8s_resources) 70 | endif 71 | 72 | .PHONY: e2e-tests 73 | e2e-tests: setup-e2e-cluster run-e2e-tests 74 | ifeq (${CLUSTER_DELETE},true) 75 | make clear-e2e-cluster 76 | endif 77 | 78 | .PHONY: sanity-check-e2e 79 | sanity-check-e2e: 80 | ./test/cluster-setup/sanity-check.sh 81 | 82 | .PHONY: run-e2e-tests 83 | run-e2e-tests: sanity-check-e2e 84 | go test -v -timeout 5m -count=1 -run ${RUN} ./test/e2e/... 85 | 86 | # Flaky tests usually fail with GitHub actions. You should be able to run them locally though. 87 | .PHONY: e2e-tests-flaky 88 | e2e-tests-flaky: setup-e2e-cluster run-e2e-tests-flaky 89 | ifeq (${CLUSTER_DELETE},true) 90 | make clear-e2e-cluster 91 | endif 92 | 93 | .PHONY: run-e2e-tests-flaky 94 | run-e2e-tests-flaky: sanity-check-e2e 95 | go test -tags "flaky" -v -timeout 5m -count=1 -run ${RUN} ./test/e2e/... 96 | 97 | .PHONY: clear-e2e-cluster 98 | clear-e2e-cluster: 99 | kind delete cluster --name ${E2E_CLUSTER_NAME} 100 | 101 | # convenience target to run `mkdocs serve` using a docker container 102 | .PHONY: serve-docs 103 | serve-docs: ## serve docs locally 104 | docker run --rm -it -p 8000:8000 -v ${CURRENT_DIR}:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8000 105 | 106 | 107 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to GitHub packages 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | # Allow creating a container image at any time (even without a tag) 10 | workflow_dispatch: 11 | push: 12 | # Publish semver tags as releases. 13 | tags: [ 'v*.*.*' ] 14 | 15 | env: 16 | # Use docker.io for Docker Hub if empty 17 | REGISTRY: ghcr.io 18 | # github.repository as / 19 | IMAGE_NAME: ${{ github.repository }} 20 | 21 | 22 | jobs: 23 | build: 24 | 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: read 28 | packages: write 29 | # This is used to complete the identity challenge 30 | # with sigstore/fulcio when running outside of PRs. 31 | id-token: write 32 | 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v4 36 | 37 | # Install the cosign tool except on PR 38 | # https://github.com/sigstore/cosign-installer 39 | - name: Install cosign 40 | if: github.event_name != 'pull_request' 41 | uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 42 | with: 43 | cosign-release: 'v2.2.4' 44 | 45 | # Set up BuildKit Docker container builder to be able to build 46 | # multi-platform images and export cache 47 | # https://github.com/docker/setup-buildx-action 48 | - name: Set up Docker Buildx 49 | uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 50 | 51 | # Login against a Docker registry except on PR 52 | # https://github.com/docker/login-action 53 | - name: Log into registry ${{ env.REGISTRY }} 54 | if: github.event_name != 'pull_request' 55 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 56 | with: 57 | registry: ${{ env.REGISTRY }} 58 | username: ${{ github.actor }} 59 | password: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | # Extract metadata (tags, labels) for Docker 62 | # https://github.com/docker/metadata-action 63 | - name: Extract Docker metadata 64 | id: meta 65 | uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 66 | with: 67 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 68 | 69 | # Build and push Docker image with Buildx (don't push on PR) 70 | # https://github.com/docker/build-push-action 71 | - name: Build and push Docker image 72 | id: build-and-push 73 | uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 74 | with: 75 | context: . 76 | push: ${{ github.event_name != 'pull_request' }} 77 | platforms: linux/amd64,linux/arm64 78 | tags: ${{ steps.meta.outputs.tags }} 79 | labels: ${{ steps.meta.outputs.labels }} 80 | cache-from: type=gha 81 | cache-to: type=gha,mode=max 82 | 83 | # Sign the resulting Docker image digest except on PRs. 84 | # This will only write to the public Rekor transparency log when the Docker 85 | # repository is public to avoid leaking data. If you would like to publish 86 | # transparency data even for private images, pass --force to cosign below. 87 | # https://github.com/sigstore/cosign 88 | - name: Sign the published Docker image 89 | if: ${{ github.event_name != 'pull_request' }} 90 | env: 91 | # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable 92 | TAGS: ${{ steps.meta.outputs.tags }} 93 | DIGEST: ${{ steps.build-and-push.outputs.digest }} 94 | # This step uses the identity token to provision an ephemeral certificate 95 | # against the sigstore community Fulcio instance. 96 | run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} 97 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.3 6 | 7 | require ( 8 | github.com/argoproj/argo-rollouts v1.6.6 9 | github.com/go-playground/validator/v10 v10.19.0 10 | github.com/hashicorp/go-plugin v1.6.0 11 | github.com/sirupsen/logrus v1.9.3 12 | github.com/stretchr/testify v1.11.0 13 | k8s.io/client-go v0.34.1 14 | sigs.k8s.io/e2e-framework v0.4.0 15 | sigs.k8s.io/gateway-api v1.4.0 16 | ) 17 | 18 | require ( 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/blang/semver/v4 v4.0.0 // indirect 21 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 24 | github.com/fatih/color v1.18.0 // indirect 25 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 26 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 27 | github.com/go-playground/locales v0.14.1 // indirect 28 | github.com/go-playground/universal-translator v0.18.1 // indirect 29 | github.com/google/gnostic-models v0.7.0 // indirect 30 | github.com/google/uuid v1.6.0 // indirect 31 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect 32 | github.com/hashicorp/go-hclog v1.6.3 // indirect 33 | github.com/hashicorp/yamux v0.1.1 // indirect 34 | github.com/leodido/go-urn v1.4.0 // indirect 35 | github.com/mattn/go-colorable v0.1.13 // indirect 36 | github.com/mattn/go-isatty v0.0.20 // indirect 37 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 38 | github.com/moby/spdystream v0.5.0 // indirect 39 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 40 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 41 | github.com/oklog/run v1.1.0 // indirect 42 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 43 | github.com/prometheus/client_golang v1.23.0 // indirect 44 | github.com/prometheus/client_model v0.6.2 // indirect 45 | github.com/prometheus/common v0.65.0 // indirect 46 | github.com/prometheus/procfs v0.17.0 // indirect 47 | github.com/spf13/pflag v1.0.7 // indirect 48 | github.com/x448/float16 v0.8.4 // indirect 49 | go.opentelemetry.io/otel v1.37.0 // indirect 50 | go.opentelemetry.io/otel/trace v1.37.0 // indirect 51 | go.yaml.in/yaml/v2 v2.4.2 // indirect 52 | go.yaml.in/yaml/v3 v3.0.4 // indirect 53 | golang.org/x/crypto v0.41.0 // indirect 54 | golang.org/x/oauth2 v0.30.0 // indirect 55 | golang.org/x/term v0.34.0 // indirect 56 | golang.org/x/time v0.12.0 // indirect 57 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect 58 | google.golang.org/grpc v1.75.1 // indirect 59 | gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect 60 | k8s.io/component-base v0.34.0 // indirect 61 | sigs.k8s.io/controller-runtime v0.22.1 // indirect 62 | sigs.k8s.io/randfill v1.0.0 // indirect 63 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect 64 | sigs.k8s.io/yaml v1.6.0 // indirect 65 | ) 66 | 67 | require ( 68 | github.com/emicklei/go-restful/v3 v3.13.0 // indirect 69 | github.com/go-logr/logr v1.4.3 // indirect 70 | github.com/go-openapi/jsonpointer v0.21.2 // indirect 71 | github.com/go-openapi/jsonreference v0.21.0 // indirect 72 | github.com/go-openapi/swag v0.23.1 // indirect 73 | github.com/gogo/protobuf v1.3.2 // indirect 74 | github.com/golang/protobuf v1.5.4 // indirect 75 | github.com/josharian/intern v1.0.0 // indirect 76 | github.com/json-iterator/go v1.1.12 // indirect 77 | github.com/mailru/easyjson v0.9.0 // indirect 78 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 79 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect 80 | golang.org/x/net v0.43.0 // indirect 81 | golang.org/x/sys v0.35.0 // indirect 82 | golang.org/x/text v0.28.0 // indirect 83 | google.golang.org/protobuf v1.36.8 // indirect 84 | gopkg.in/inf.v0 v0.9.1 // indirect 85 | gopkg.in/yaml.v3 v3.0.1 // indirect 86 | k8s.io/api v0.34.1 87 | k8s.io/apimachinery v0.34.1 88 | k8s.io/klog/v2 v2.130.1 // indirect 89 | k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect 90 | k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect 91 | sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect 92 | ) 93 | -------------------------------------------------------------------------------- /docs/assets/versions.css: -------------------------------------------------------------------------------- 1 | .md-header__title { 2 | display: flex; 3 | } 4 | 5 | .dropdown-caret { 6 | display: inline-block !important; 7 | position: absolute; 8 | right: 4px; 9 | } 10 | 11 | .fa .fa-caret-down { 12 | display: none !important; 13 | } 14 | 15 | .rst-other-versions { 16 | text-align: right; 17 | } 18 | 19 | .rst-other-versions > dl, .rst-other-versions dt, .rst-other-versions small { 20 | display: none; 21 | } 22 | 23 | .rst-other-versions > dl:first-child { 24 | display: flex !important; 25 | flex-direction: column; 26 | line-height: 0px !important; 27 | } 28 | 29 | .rst-versions.shift-up .rst-other-versions { 30 | display: flex !important; 31 | } 32 | 33 | .rst-versions .rst-other-versions { 34 | display: none; 35 | } 36 | 37 | /* Version Warning */ 38 | div[data-md-component=announce] { 39 | background-color: rgb(248, 243, 236); 40 | position: sticky; 41 | top: 0; 42 | z-index: 2; 43 | } 44 | div[data-md-component=announce]>div#announce-msg{ 45 | color: var(--md-code-hl-number-color); 46 | font-size: .8rem; 47 | text-align: center; 48 | margin: 15px; 49 | } 50 | div[data-md-component=announce]>div#announce-msg>a{ 51 | color: var(--md-typeset-a-color); 52 | text-decoration: underline; 53 | } 54 | 55 | /* from https://assets.readthedocs.org/static/css/badge_only.css, 56 | most styles have to be overriden here */ 57 | .rst-versions{ 58 | position: relative !important; 59 | bottom: 0; 60 | left: 0; 61 | width: 100px !important; 62 | background: hsla(173, 100%, 24%, 1) !important; 63 | font-family: inherit !important; 64 | z-index: 0 !important; 65 | } 66 | .rst-versions a{ 67 | color:#2980B9; 68 | text-decoration:none 69 | } 70 | .rst-versions .rst-badge-small{ 71 | display:none 72 | } 73 | .rst-versions .rst-current-version{ 74 | padding:12px; 75 | background: hsla(173, 100%, 24%, 1) !important; 76 | display:block; 77 | text-align:right; 78 | font-size:90%; 79 | cursor:pointer; 80 | color: white !important; 81 | *zoom:1 82 | } 83 | .rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{ 84 | display:table;content:"" 85 | } 86 | .rst-versions .rst-current-version:after{ 87 | clear:both 88 | } 89 | .rst-versions .rst-current-version .fa{ 90 | color:#fcfcfc 91 | } 92 | .rst-versions .rst-current-version .fa-caret-down{ 93 | display: none; 94 | } 95 | .rst-versions.shift-up .rst-other-versions{ 96 | display:block 97 | } 98 | .rst-versions .rst-other-versions{ 99 | font-size:90%; 100 | padding:12px; 101 | color:gray; 102 | display:none 103 | } 104 | .rst-versions .rst-other-versions hr{ 105 | display: none !important; 106 | height: 0px !important; 107 | border: 0px; 108 | margin: 0px !important; 109 | padding: 0px; 110 | border-top: none !important; 111 | } 112 | .rst-versions .rst-other-versions dd{ 113 | display:inline-block; 114 | margin:0 115 | } 116 | .rst-versions .rst-other-versions dd a{ 117 | display:inline-block; 118 | padding: 1em 0em !important; 119 | color:#fcfcfc; 120 | font-size: .6rem !important; 121 | white-space: nowrap; 122 | text-overflow: ellipsis; 123 | overflow: hidden; 124 | width: 80px; 125 | } 126 | .rst-versions .rst-other-versions dd a:hover{ 127 | font-size: .7rem !important; 128 | font-weight: bold; 129 | } 130 | .rst-versions.rst-badge{ 131 | display: block !important; 132 | width: 100px !important; 133 | bottom: 0px !important; 134 | right: 0px !important; 135 | left:auto; 136 | border:none; 137 | text-align: center !important; 138 | line-height: 0; 139 | } 140 | .rst-versions.rst-badge .icon-book{ 141 | display: none; 142 | } 143 | .rst-versions.rst-badge .fa-book{ 144 | display: none !important; 145 | } 146 | .rst-versions.rst-badge.shift-up .rst-current-version{ 147 | text-align: left !important; 148 | } 149 | .rst-versions.rst-badge.shift-up .rst-current-version .fa-book{ 150 | display: none !important; 151 | } 152 | .rst-versions.rst-badge.shift-up .rst-current-version .icon-book{ 153 | display: none !important; 154 | } 155 | .rst-versions.rst-badge .rst-current-version{ 156 | width: 70px !important; 157 | height: 2.4rem !important; 158 | line-height:2.4rem !important; 159 | padding: 0px 5px !important; 160 | display: inline-block !important; 161 | font-size: .6rem !important; 162 | overflow: hidden !important; 163 | text-overflow: ellipsis !important; 164 | white-space: nowrap !important; 165 | text-align: left !important; 166 | } 167 | @media screen and (max-width: 768px){ 168 | .rst-versions{ 169 | width:85%; 170 | display:none 171 | } 172 | .rst-versions.shift{ 173 | display:block 174 | } 175 | } -------------------------------------------------------------------------------- /examples/nginx/README.md: -------------------------------------------------------------------------------- 1 | # Using NGINX Kubernetes Gateway with Argo Rollouts 2 | 3 | This guide will describe how to use NGINX Kubernetes Gateway as an implementation 4 | for the Gateway API in order to do split traffic with Argo Rollouts. 5 | 6 | Note that Argo Rollouts also [supports NGINX natively](https://argoproj.github.io/argo-rollouts/features/traffic-management/nginx/). 7 | 8 | ## Step 1 - Enable Gateway Provider and create Gateway entrypoint 9 | 10 | Before enabling a Gateway Provider you also need to install NGINX Kubernetes Gateway. Follow the official [installation instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/installing-ngf/helm/). 11 | 12 | This installation will create an `nginx` gateway class that we can use later on. 13 | 14 | 15 | 1. If not already done through the previous installation instructions, register the [Gateway API CRDs](https://gateway-api.sigs.k8s.io/guides/#install-standard-channel) 16 | 17 | ``` 18 | kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.8.0/standard-install.yaml 19 | ``` 20 | 21 | ## Step 2 - Create a Gateway resource and HTTPRoute that defines a traffic split 22 | 23 | 24 | After we deployed the Gateway API provider and Gateway class, we can create a Gateway resource: 25 | 26 | 27 | ```yaml 28 | apiVersion: gateway.networking.k8s.io/v1beta1 29 | kind: Gateway 30 | metadata: 31 | name: argo-rollouts-gateway 32 | spec: 33 | gatewayClassName: nginx 34 | listeners: 35 | - protocol: HTTP 36 | name: web 37 | port: 80 # one of Gateway entrypoint that was created following the official installation instructions 38 | ``` 39 | 40 | Create HTTPRoute and connect to the created Gateway resource: 41 | 42 | ```yaml 43 | apiVersion: gateway.networking.k8s.io/v1beta1 44 | kind: HTTPRoute 45 | metadata: 46 | name: argo-rollouts-http-route 47 | spec: 48 | parentRefs: 49 | - name: argo-rollouts-gateway 50 | rules: 51 | - backendRefs: 52 | - name: argo-rollouts-stable-service 53 | port: 80 54 | - name: argo-rollouts-canary-service 55 | port: 80 56 | ``` 57 | 58 | ## Step 3 - Create canary and stable services for your application 59 | 60 | - Canary service 61 | 62 | ```yaml 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: argo-rollouts-canary-service 67 | spec: 68 | ports: 69 | - port: 80 70 | targetPort: http 71 | protocol: TCP 72 | name: http 73 | selector: 74 | app: rollouts-demo 75 | ``` 76 | 77 | - Stable service 78 | 79 | ```yaml 80 | apiVersion: v1 81 | kind: Service 82 | metadata: 83 | name: argo-rollouts-stable-service 84 | spec: 85 | ports: 86 | - port: 80 87 | targetPort: http 88 | protocol: TCP 89 | name: http 90 | selector: 91 | app: rollouts-demo 92 | ``` 93 | ## Step 4 - Grant argo-rollouts permissions to view and modify Gateway HTTPRoute resources 94 | 95 | The argo-rollouts service account needs the ability to be able to view and modify HTTPRoutes as well as its existing permissions. Edit the `argo-rollouts` cluster role to add the following permissions: 96 | 97 | ```yaml 98 | rules: 99 | - apiGroups: 100 | - gateway.networking.k8s.io 101 | resources: 102 | - httproutes 103 | verbs: 104 | - get 105 | - list 106 | - watch 107 | - update 108 | - patch 109 | ``` 110 | 111 | ## Step 5 - Create argo-rollouts resources 112 | 113 | We can finally create the definition of the application. 114 | 115 | ```yaml 116 | apiVersion: argoproj.io/v1alpha1 117 | kind: Rollout 118 | metadata: 119 | name: rollouts-demo 120 | spec: 121 | replicas: 5 122 | strategy: 123 | canary: 124 | canaryService: argo-rollouts-canary-service # our created canary service 125 | stableService: argo-rollouts-stable-service # our created stable service 126 | trafficRouting: 127 | plugins: 128 | argoproj-labs/gatewayAPI: 129 | httpRoute: argo-rollouts-http-route # our created httproute 130 | namespace: default # namespace where this rollout resides. 131 | steps: 132 | - setWeight: 30 133 | - pause: {} 134 | - setWeight: 40 135 | - pause: { duration: 10 } 136 | - setWeight: 60 137 | - pause: { duration: 10 } 138 | - setWeight: 80 139 | - pause: { duration: 10 } 140 | revisionHistoryLimit: 2 141 | selector: 142 | matchLabels: 143 | app: rollouts-demo 144 | template: 145 | metadata: 146 | labels: 147 | app: rollouts-demo 148 | spec: 149 | containers: 150 | - name: rollouts-demo 151 | image: argoproj/rollouts-demo:red 152 | ports: 153 | - name: http 154 | containerPort: 8080 155 | protocol: TCP 156 | resources: 157 | requests: 158 | memory: 32Mi 159 | cpu: 5m 160 | ``` 161 | 162 | Apply all the yaml files to your cluster 163 | 164 | ## Step 6 - Test the canary 165 | 166 | Perform a deployment like any other Rollout and the Gateway plugin will split the traffic to your canary by instructing NGINX Gateway to proxy via the Gateway API 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Kubernetes installation 2 | 3 | The plugin needs the main Argo Rollouts controller to work. You need to have both installed in order to perform progressive delivery 4 | scenarios using your Kubernetes API Gateway implementation. 5 | 6 | ## Installing the Argo Rollouts controller 7 | 8 | First get the core Argo Rollouts controller in your cluster. 9 | 10 | Follow the [official instructions](https://argo-rollouts.readthedocs.io/en/stable/installation/) to install Argo Rollouts. 11 | 12 | Optionally install the [Argo Rollouts CLI](https://argoproj.github.io/argo-rollouts/features/kubectl-plugin/) in order to control Rollouts from your terminal. 13 | 14 | ## Installing the plugin via HTTP(S) 15 | 16 | To install the plugin create a configmap with the following syntax: 17 | 18 | ```yaml 19 | apiVersion: v1 20 | kind: ConfigMap 21 | metadata: 22 | name: argo-rollouts-config # must be named like this 23 | namespace: argo-rollouts # must be in this namespace 24 | data: 25 | trafficRouterPlugins: |- 26 | - name: "argoproj-labs/gatewayAPI" 27 | location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download//gatewayapi-plugin-" 28 | ``` 29 | 30 | You can find the available versions and architectures at the [Releases page](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases). 31 | 32 | For example for a Linux/x86 cluster save the following in a file of your choosing e.g. `gateway-plugin.yml`. 33 | 34 | ```yaml 35 | apiVersion: v1 36 | kind: ConfigMap 37 | metadata: 38 | name: argo-rollouts-config # must be named like this 39 | namespace: argo-rollouts # must be in this namespace 40 | data: 41 | trafficRouterPlugins: |- 42 | - name: "argoproj-labs/gatewayAPI" 43 | location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.4.0/gatewayapi-plugin-linux-amd64" 44 | ``` 45 | 46 | Deploy this file with `kubectl apply -f gateway-plugin.yml -n argo-rollouts`. You can also use [Argo CD](https://argoproj.github.io/cd/) or any other Kubernetes deployment method that you prefer. 47 | 48 | ## Installing the plugin via init containers 49 | 50 | Use the [Argo Rollouts Helm chart](https://argoproj.github.io/argo-helm/) and change the [default values](https://artifacthub.io/packages/helm/argo/argo-rollouts): 51 | 52 | ```yaml 53 | controller: 54 | initContainers: 55 | - name: copy-gwapi-plugin 56 | image: ghcr.io/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi:v0.5.0 57 | command: ["/bin/sh", "-c"] 58 | args: 59 | - cp /bin/rollouts-plugin-trafficrouter-gatewayapi /plugins 60 | volumeMounts: 61 | - name: gwapi-plugin 62 | mountPath: /plugins 63 | trafficRouterPlugins: 64 | - name: argoproj-labs/gatewayAPI 65 | location: "file:///plugins/rollouts-plugin-trafficrouter-gatewayapi" 66 | volumes: 67 | - name: gwapi-plugin 68 | emptyDir: {} 69 | volumeMounts: 70 | - name: gwapi-plugin 71 | mountPath: /plugins 72 | ``` 73 | 74 | We publish [container images](https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/pkgs/container/rollouts-plugin-trafficrouter-gatewayapi) for both ARM and x86. 75 | 76 | For more installation options see the [Plugin documentation](https://argoproj.github.io/argo-rollouts/features/traffic-management/plugins/) at the main Argo Rollouts site. 77 | 78 | ## Verifying the installation 79 | 80 | Restart the Argo Rollouts controller so that it detects the presence of the plugin. 81 | 82 | ``` 83 | kubectl rollout restart deployment -n argo-rollouts argo-rollouts 84 | ``` 85 | 86 | Then check the controller logs. You should see a line for loading the plugin: 87 | 88 | ``` 89 | time="XXX" level=info msg="Downloading plugin argoproj-labs/gatewayAPI from: https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.4.0/gatewayapi-plugin-linux-amd64" 90 | time="YYY" level=info msg="Download complete, it took 7.792426599s" 91 | ``` 92 | 93 | You are now ready to use the Gateway API in your [Rollout definitions](https://argoproj.github.io/argo-rollouts/features/specification/). See also our [Quick Start Guide](quick-start.md). 94 | 95 | ## Configuration 96 | 97 | The `kubeClientQPS` and `kubeClientBurst` options configure the behavior of the Kubernetes client. These 98 | values may need to be increased if you operate Argo Rollouts in a large cluster. These values can be specified 99 | using the `args` block of the plugin configuration: 100 | 101 | ```yaml 102 | trafficRouterPlugins: |- 103 | - name: "argoproj-labs/gatewayAPI" 104 | location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/vX.X.X/gatewayapi-plugin-linux-amd64" 105 | args: 106 | - "-kubeClientQPS=40" 107 | - "-kubeClientBurst=80" 108 | ``` 109 | 110 | Notice that this setting applies **only** to the plugin process. The main Argo Rollouts controller is not affected (or any other additional plugins you might have already). 111 | -------------------------------------------------------------------------------- /pkg/plugin/experiment.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" 8 | "github.com/sirupsen/logrus" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 12 | gatewayApiClientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" 13 | ) 14 | 15 | func HandleExperiment(ctx context.Context, clientset *kubernetes.Clientset, gatewayClient *gatewayApiClientset.Clientset, logger *logrus.Entry, rollout *v1alpha1.Rollout, httpRoute *gatewayv1.HTTPRoute, additionalDestinations []v1alpha1.WeightDestination) error { 16 | ruleIdx := -1 17 | stableService := rollout.Spec.Strategy.Canary.StableService 18 | canaryService := rollout.Spec.Strategy.Canary.CanaryService 19 | 20 | for i, rule := range httpRoute.Spec.Rules { 21 | if ruleIdx != -1 { 22 | break 23 | } 24 | for _, backendRef := range rule.BackendRefs { 25 | if string(backendRef.Name) == stableService || string(backendRef.Name) == canaryService { 26 | ruleIdx = i 27 | break 28 | } 29 | } 30 | } 31 | 32 | if ruleIdx == -1 { 33 | return fmt.Errorf("no matching rule found for rollout %s", rollout.Name) 34 | } 35 | 36 | isExperimentActive := rollout.Spec.Strategy.Canary != nil && rollout.Status.Canary.CurrentExperiment != "" 37 | 38 | hasExperimentServices := false 39 | for _, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 40 | serviceName := string(backendRef.Name) 41 | if serviceName != stableService && serviceName != canaryService { 42 | hasExperimentServices = true 43 | break 44 | } 45 | } 46 | 47 | if isExperimentActive { 48 | logger.Info(fmt.Sprintf("Found active experiment %s", rollout.Status.Canary.CurrentExperiment)) 49 | 50 | if len(additionalDestinations) == 0 { 51 | logger.Info("No experiment services found in additionalDestinations, skipping experiment service addition") 52 | return nil 53 | } 54 | 55 | // Compute total experiment weight 56 | var totalExperimentWeight int32 57 | for _, dest := range additionalDestinations { 58 | totalExperimentWeight += dest.Weight 59 | } 60 | 61 | // Sanity cap: don't allow overflow 62 | if totalExperimentWeight > 100 { 63 | logger.Warnf("Total experiment weight exceeds 100 (got %d), capping at 100", totalExperimentWeight) 64 | totalExperimentWeight = 100 65 | } 66 | 67 | stableWeight := int32(100) - totalExperimentWeight 68 | 69 | for i, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 70 | if string(backendRef.Name) == stableService { 71 | httpRoute.Spec.Rules[ruleIdx].BackendRefs[i].Weight = &stableWeight 72 | break 73 | } 74 | } 75 | 76 | for _, additionalDest := range additionalDestinations { 77 | serviceName := additionalDest.ServiceName 78 | weight := additionalDest.Weight 79 | 80 | exists := false 81 | for _, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 82 | if string(backendRef.Name) == serviceName { 83 | exists = true 84 | break 85 | } 86 | } 87 | 88 | if !exists { 89 | logger.Info(fmt.Sprintf("Adding experiment service to HTTPRoute: %s with weight %d", serviceName, weight)) 90 | 91 | service, err := clientset.CoreV1().Services(rollout.Namespace).Get(ctx, serviceName, metav1.GetOptions{}) 92 | if err != nil { 93 | logger.Warn(fmt.Sprintf("Failed to get service %s: %v", serviceName, err)) 94 | continue 95 | } 96 | 97 | port := gatewayv1.PortNumber(8080) 98 | portName := "http" 99 | for _, servicePort := range service.Spec.Ports { 100 | if servicePort.Name == portName { 101 | port = servicePort.Port 102 | break 103 | } 104 | } 105 | 106 | if len(service.Spec.Ports) > 0 && port == 8080 { 107 | port = service.Spec.Ports[0].Port 108 | } 109 | 110 | namespace := gatewayv1.Namespace(rollout.Namespace) 111 | httpRoute.Spec.Rules[ruleIdx].BackendRefs = append(httpRoute.Spec.Rules[ruleIdx].BackendRefs, gatewayv1.HTTPBackendRef{ 112 | BackendRef: gatewayv1.BackendRef{ 113 | BackendObjectReference: gatewayv1.BackendObjectReference{ 114 | Name: gatewayv1.ObjectName(serviceName), 115 | Namespace: &namespace, 116 | Port: &port, 117 | }, 118 | Weight: &weight, 119 | }, 120 | }) 121 | } 122 | } 123 | return nil 124 | } 125 | 126 | if !isExperimentActive && hasExperimentServices { 127 | logger.Info("Experiment is no longer active, removing experiment services from HTTPRoute") 128 | 129 | stableWeight := int32(100) 130 | filteredBackendRefs := []gatewayv1.HTTPBackendRef{} 131 | 132 | for _, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 133 | serviceName := string(backendRef.Name) 134 | 135 | if serviceName == stableService { 136 | backendRef.Weight = &stableWeight 137 | filteredBackendRefs = append(filteredBackendRefs, backendRef) 138 | } else if serviceName == canaryService { 139 | zeroWeight := int32(0) 140 | backendRef.Weight = &zeroWeight 141 | filteredBackendRefs = append(filteredBackendRefs, backendRef) 142 | } else { 143 | logger.Info(fmt.Sprintf("Removing experiment service from HTTPRoute: %s", serviceName)) 144 | } 145 | } 146 | 147 | httpRoute.Spec.Rules[ruleIdx].BackendRefs = filteredBackendRefs 148 | logger.Info("Experiment services removed from HTTPRoute") 149 | } 150 | 151 | return nil 152 | } 153 | -------------------------------------------------------------------------------- /examples/google-cloud/README.md: -------------------------------------------------------------------------------- 1 | # Using Google Cloud with Argo Rollouts 2 | 3 | Google cloud has [native support](https://cloud.google.com/kubernetes-engine/docs/concepts/gateway-api) for the Gateway API making the integration with Argo Rollouts a straightforward process. 4 | 5 | ## Step 1 - Create a cluster with Gateway support in Google Cloud 6 | 7 | Follow [the official instructions](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways#internal-gateway) 8 | 9 | The example below is for an internal gateway as it is simple but the integration should work for all Google cloud gateways. 10 | 11 | 12 | You can create a new cluster with gateway support with: 13 | 14 | ```shell 15 | gcloud container clusters create CLUSTER_NAME \ 16 | --gateway-api=standard \ 17 | --cluster-version=VERSION \ 18 | --region=COMPUTE_REGION 19 | ``` 20 | 21 | or update an existing one with: 22 | 23 | ```shell 24 | gcloud container clusters update CLUSTER_NAME \ 25 | --gateway-api=standard \ 26 | --region=COMPUTE_REGION 27 | ``` 28 | 29 | ## Step 2 - Create Google Load balancer with Gateway support 30 | 31 | Then create a proxy subnet as shown in the [instructions](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways#configure_a_proxy-only_subnet) 32 | 33 | ```shell 34 | gcloud compute networks subnets create demo-subnet \ 35 | --purpose=REGIONAL_MANAGED_PROXY \ 36 | --role=ACTIVE \ 37 | --region=us-central1 \ 38 | --network=default \ 39 | --range=10.1.1.0/24 40 | ``` 41 | 42 | Create a gateway and apply it to the cluster with 43 | 44 | ```yaml 45 | kind: Gateway 46 | apiVersion: gateway.networking.k8s.io/v1beta1 47 | metadata: 48 | name: internal-http 49 | spec: 50 | gatewayClassName: gke-l7-rilb 51 | listeners: 52 | - name: http 53 | protocol: HTTP 54 | port: 80 55 | ``` 56 | 57 | Get the IP of the gateway with 58 | 59 | ```shell 60 | kubectl get gateways.gateway.networking.k8s.io internal-http -o=jsonpath="{.status.addresses[0].value}" 61 | ``` 62 | 63 | Note down the IP address for testing the application later. 64 | 65 | ## Step 3 - Give access to Argo Rollouts for the Gateway/Http Route 66 | 67 | 68 | Create Cluster Role resource with needed permissions for Gateway API provider. 69 | 70 | ```yaml 71 | apiVersion: rbac.authorization.k8s.io/v1 72 | kind: ClusterRole 73 | metadata: 74 | name: gateway-controller-role 75 | namespace: argo-rollouts 76 | rules: 77 | - apiGroups: 78 | - "*" 79 | resources: 80 | - "*" 81 | verbs: 82 | - "*" 83 | ``` 84 | 85 | Note that these permission are not very strict. You should lock them down according to your needs. 86 | 87 | With the following role we allow Argo Rollouts to have write access to Http Routes and Gateways. 88 | 89 | ```yaml 90 | apiVersion: rbac.authorization.k8s.io/v1 91 | kind: ClusterRoleBinding 92 | metadata: 93 | name: gateway-admin 94 | roleRef: 95 | apiGroup: rbac.authorization.k8s.io 96 | kind: ClusterRole 97 | name: gateway-controller-role 98 | subjects: 99 | - namespace: argo-rollouts 100 | kind: ServiceAccount 101 | name: argo-rollouts 102 | ``` 103 | 104 | ## Step 4 - Create HTTPRoute that defines a traffic split between two services 105 | 106 | Create HTTPRoute and connect to the created Gateway resource 107 | 108 | ```yaml 109 | kind: HTTPRoute 110 | apiVersion: gateway.networking.k8s.io/v1beta1 111 | metadata: 112 | name: argo-rollouts-http-route 113 | spec: 114 | parentRefs: 115 | - kind: Gateway 116 | name: internal-http 117 | hostnames: 118 | - "demo.example.com" 119 | rules: 120 | - backendRefs: 121 | - name: argo-rollouts-stable-service 122 | port: 80 123 | - name: argo-rollouts-canary-service 124 | port: 80 125 | 126 | ``` 127 | 128 | 129 | - Canary service 130 | 131 | ```yaml 132 | apiVersion: v1 133 | kind: Service 134 | metadata: 135 | name: argo-rollouts-canary-service 136 | spec: 137 | ports: 138 | - port: 80 139 | targetPort: http 140 | protocol: TCP 141 | name: http 142 | selector: 143 | app: rollouts-demo 144 | ``` 145 | 146 | - Stable service 147 | 148 | ```yaml 149 | apiVersion: v1 150 | kind: Service 151 | metadata: 152 | name: argo-rollouts-stable-service 153 | spec: 154 | ports: 155 | - port: 80 156 | targetPort: http 157 | protocol: TCP 158 | name: http 159 | selector: 160 | app: rollouts-demo 161 | ``` 162 | 163 | Apply all the above manifests 164 | 165 | ## Step 5 - Create an example Rollout 166 | 167 | Deploy a rollout to get the initial version 168 | 169 | ```yaml 170 | Here is an example rollout 171 | 172 | apiVersion: argoproj.io/v1alpha1 173 | kind: Rollout 174 | metadata: 175 | name: rollouts-demo 176 | namespace: default 177 | spec: 178 | revisionHistoryLimit: 1 179 | replicas: 10 180 | strategy: 181 | canary: 182 | canaryService: argo-rollouts-canary-service # our created canary service 183 | stableService: argo-rollouts-stable-service # our created stable service 184 | trafficRouting: 185 | plugins: 186 | argoproj-labs/gatewayAPI: 187 | httpRoute: argo-rollouts-http-route # our created httproute 188 | namespace: default 189 | steps: 190 | - setWeight: 30 191 | - pause: {} 192 | - setWeight: 60 193 | - pause: {} 194 | - setWeight: 100 195 | - pause: {} 196 | revisionHistoryLimit: 2 197 | selector: 198 | matchLabels: 199 | app: rollouts-demo 200 | template: 201 | metadata: 202 | labels: 203 | app: rollouts-demo 204 | spec: 205 | containers: 206 | - name: rollouts-demo 207 | image: kostiscodefresh/summer-of-k8s-app:v1 208 | ports: 209 | - name: http 210 | containerPort: 8080 211 | protocol: TCP 212 | resources: 213 | requests: 214 | memory: 32Mi 215 | cpu: 5m 216 | ``` 217 | 218 | Change the manifest to the `v2` tag and while the rollout is progressing you should see 219 | the split traffic by visiting the IP of the gateway (see step 2) 220 | 221 | ```shell 222 | curl -H "host: demo.example.com" /call-me 223 | ``` 224 | 225 | Run the command above multiple times and depending on the canary status you will sometimes see "v1" returned and sometimes "v2" 226 | 227 | 228 | -------------------------------------------------------------------------------- /examples/cilium/README.md: -------------------------------------------------------------------------------- 1 | # Using Cilium Gateway API with Argo Rollouts 2 | 3 | Cilium has [native support]() for the Gateway API making the integration with Argo Rollouts a straightforward process. Read more on eBPF and Cilium [here](https://cilium.io/). 4 | 5 | ## Prerequisites 6 | 7 | - Cilium must be installed and configured with `kubeProxyReplacement=true` 8 | - The CRDs for the Gateway API installed on your cluster: 9 | ``` 10 | kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v0.7.0/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml 11 | kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v0.7.0/config/crd/standard/gateway.networking.k8s.io_gateways.yaml 12 | kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v0.7.0/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml 13 | kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v0.7.0/config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml 14 | kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v0.7.0/config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml 15 | 16 | ``` 17 | - [Cilium Gateway API Controller enabled](https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gateway-api/). This will create a `GatewayClass` object on your behalf. Try running `kubectl get gatewayclass` after enabling the Gateway Controller. 18 | - Similar to Ingress, Gateway API controller creates a service of LoadBalancer type, so your environment will need to support this (for example MetalLB if running locally). 19 | 20 | ## Step 1 - Create your Gateway object 21 | 22 | Create a gateway: 23 | ```yaml 24 | kind: Gateway 25 | apiVersion: gateway.networking.k8s.io/v1beta1 26 | metadata: 27 | name: cilium 28 | spec: 29 | gatewayClassName: cilium 30 | listeners: 31 | - name: http 32 | protocol: HTTP 33 | port: 80 34 | allowedRoutes: 35 | namespaces: 36 | from: All 37 | ``` 38 | 39 | Get the IP of your Gateway 40 | ```shell 41 | kubectl get gateway cilium -o=jsonpath="{.status.addresses[0].value}" 42 | ``` 43 | 44 | ## Step 2 - Give access to Argo Rollouts for the Gateway/Http Route 45 | 46 | 47 | Create Cluster Role resource with needed permissions for Gateway API provider. 48 | 49 | ```yaml 50 | apiVersion: rbac.authorization.k8s.io/v1 51 | kind: ClusterRole 52 | metadata: 53 | name: gateway-controller-role 54 | namespace: argo-rollouts 55 | rules: 56 | - apiGroups: 57 | - "*" 58 | resources: 59 | - "*" 60 | verbs: 61 | - "*" 62 | ``` 63 | Note that these permission are not very strict. You should lock them down according to your needs. 64 | 65 | With the following role we allow Argo Rollouts to have write access to Http Routes and Gateways. 66 | 67 | ```yaml 68 | apiVersion: rbac.authorization.k8s.io/v1 69 | kind: ClusterRoleBinding 70 | metadata: 71 | name: gateway-admin 72 | roleRef: 73 | apiGroup: rbac.authorization.k8s.io 74 | kind: ClusterRole 75 | name: gateway-controller-role 76 | subjects: 77 | - namespace: argo-rollouts 78 | kind: ServiceAccount 79 | name: argo-rollouts 80 | ``` 81 | 82 | Apply both files with `kubectl`. 83 | 84 | ## Step 4 - Create HTTPRoute that defines a traffic split between two services 85 | Create HTTPRoute and connect to the created Gateway resource 86 | 87 | ```yaml 88 | kind: HTTPRoute 89 | apiVersion: gateway.networking.k8s.io/v1beta1 90 | metadata: 91 | name: argo-rollouts-http-route 92 | spec: 93 | parentRefs: 94 | - kind: Gateway 95 | name: cilium 96 | hostnames: 97 | - "demo.example.com" 98 | rules: 99 | - matches: 100 | - path: 101 | type: PathPrefix 102 | value: / 103 | backendRefs: 104 | - name: argo-rollouts-stable-service 105 | kind: Service 106 | port: 80 107 | - name: argo-rollouts-canary-service 108 | kind: Service 109 | port: 80 110 | ``` 111 | - Canary service 112 | 113 | ```yaml 114 | apiVersion: v1 115 | kind: Service 116 | metadata: 117 | name: argo-rollouts-canary-service 118 | spec: 119 | ports: 120 | - port: 80 121 | targetPort: http 122 | protocol: TCP 123 | name: http 124 | selector: 125 | app: rollouts-demo 126 | ``` 127 | - Stable service 128 | 129 | ```yaml 130 | apiVersion: v1 131 | kind: Service 132 | metadata: 133 | name: argo-rollouts-stable-service 134 | spec: 135 | ports: 136 | - port: 80 137 | targetPort: http 138 | protocol: TCP 139 | name: http 140 | selector: 141 | app: rollouts-demo 142 | ``` 143 | 144 | ## Step 5 - Create an example Rollout 145 | 146 | Deploy a rollout to get the initial version. 147 | 148 | Here is an example rollout: 149 | 150 | ```yaml 151 | 152 | apiVersion: argoproj.io/v1alpha1 153 | kind: Rollout 154 | metadata: 155 | name: rollouts-demo 156 | namespace: default 157 | spec: 158 | revisionHistoryLimit: 1 159 | replicas: 10 160 | strategy: 161 | canary: 162 | canaryService: argo-rollouts-canary-service # our created canary service 163 | stableService: argo-rollouts-stable-service # our created stable service 164 | trafficRouting: 165 | plugins: 166 | argoproj-labs/gatewayAPI: 167 | httpRoute: argo-rollouts-http-route # our created httproute 168 | namespace: default 169 | steps: 170 | - setWeight: 30 171 | - pause: {} 172 | - setWeight: 60 173 | - pause: {} 174 | - setWeight: 100 175 | - pause: {} 176 | revisionHistoryLimit: 2 177 | selector: 178 | matchLabels: 179 | app: rollouts-demo 180 | template: 181 | metadata: 182 | labels: 183 | app: rollouts-demo 184 | spec: 185 | containers: 186 | - name: rollouts-demo 187 | image: kostiscodefresh/summer-of-k8s-app:v1 188 | ports: 189 | - name: http 190 | containerPort: 8080 191 | protocol: TCP 192 | resources: 193 | requests: 194 | memory: 32Mi 195 | cpu: 5m 196 | ``` 197 | 198 | Change the manifest to the `v2` tag and while the rollout is progressing you should see 199 | the split traffic by visiting the IP of the gateway (see step 2) 200 | 201 | ```shell 202 | curl -H "host: demo.example.com" /call-me 203 | ``` 204 | Run the command above multiple times and depending on the canary status you will sometimes see "v1" returned and sometimes "v2" 205 | 206 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Create Gateway API plugin release 2 | on: 3 | push: 4 | tags: 5 | - "release-v[0-9]+.[0-9]+.[0-9]+" 6 | - "release-v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" 7 | 8 | env: 9 | GOLANG_VERSION: "1.22" 10 | 11 | jobs: 12 | release-creation: 13 | name: Automatic release creation triggered on ${{ github.ref_name }} 14 | runs-on: ubuntu-latest 15 | env: 16 | # The full name of the tag as supplied by the GitHub event 17 | # refs/tags/release-v0.0.0-rc1 18 | TRIGGER_TAG: ${{ github.ref }} 19 | # Only tag name 20 | TRIGGER_TAG_NAME: ${{ github.ref_name }} 21 | # Whether to create release 22 | IS_DRY_RUN: false 23 | # Whether a draft release should be created, instead of public one 24 | IS_DRAFT_RELEASE: false 25 | # Name of the GitHub user for Git config 26 | GIT_USERNAME: Philipp-Plotnikov 27 | # E-Mail of the GitHub user for Git config 28 | GIT_EMAIL: philipp.plotnikov@icloud.com 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v3 32 | with: 33 | fetch-depth: 0 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Setup Golang 37 | uses: actions/setup-go@v4 38 | with: 39 | go-version: ${{ env.GOLANG_VERSION }} 40 | 41 | - name: Setup Git author information 42 | run: | 43 | set -ue 44 | git config --global user.email "${GIT_EMAIL}" 45 | git config --global user.name "${GIT_USERNAME}" 46 | 47 | - name: Preparing env variables 48 | run: | 49 | set -xue 50 | # Target version must match major.minor.patch and optional -rcX suffix 51 | # where X must be a number. 52 | # The release tag is the source tag, minus the release- prefix 53 | RELEASE_TAG="${TRIGGER_TAG#*release-}" 54 | # Whether this is a pre-release (indicated by -rc suffix) 55 | IS_PRE_RELEASE=false 56 | if echo "${RELEASE_TAG}" | grep -E -- '-rc[0-9]+$'; then 57 | IS_PRE_RELEASE=true 58 | fi 59 | # Ensure that release do not yet exist 60 | if [[ -n $(git tag -l | grep -E -- '^'${RELEASE_TAG}) ]]; then 61 | echo "::error::Release tag ${RELEASE_TAG} already exists in repository. Refusing to continue." 62 | exit 1 63 | fi 64 | # Make the variables available in follow-up steps 65 | echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_ENV 66 | echo "IS_PRE_RELEASE=${IS_PRE_RELEASE}" >> $GITHUB_ENV 67 | 68 | - name: Creating the release tag 69 | run: | 70 | set -ue 71 | if [[ "$IS_DRY_RUN" == "true" ]]; then 72 | echo "IS_DRY_RUN=${IS_DRY_RUN}" 73 | exit 0 74 | fi 75 | echo "Creating release tag ${RELEASE_TAG}" 76 | git tag ${RELEASE_TAG} 77 | git push origin ${RELEASE_TAG} 78 | 79 | - name: Deleting pushed tag 80 | run: | 81 | set -ue 82 | echo "Deleting pushed tag ${TRIGGER_TAG_NAME}" 83 | git tag -d ${TRIGGER_TAG_NAME} 84 | git push -d origin ${TRIGGER_TAG_NAME} 85 | 86 | - name: Release building 87 | run: | 88 | make release 89 | 90 | - name: GitHub release place creation 91 | uses: softprops/action-gh-release@v1 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | id: create_release 95 | with: 96 | tag_name: ${{ env.RELEASE_TAG }} 97 | release_name: ${{ env.RELEASE_TAG }} 98 | draft: ${{ env.IS_DRAFT_RELEASE }} 99 | prerelease: ${{ env.IS_PRE_RELEASE }} 100 | body_path: RELEASE_NOTES.md 101 | 102 | - name: Gatewayapi-plugin-linux-amd64 binary uploading to release assets 103 | uses: actions/upload-release-asset@v1 104 | env: 105 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | with: 107 | upload_url: ${{ steps.create_release.outputs.upload_url }} 108 | asset_path: ./dist/gatewayapi-plugin-linux-amd64 109 | asset_name: gatewayapi-plugin-linux-amd64 110 | asset_content_type: application/octet-stream 111 | if: ${{ env.IS_DRY_RUN != 'true' }} 112 | 113 | - name: Gatewayapi-plugin-linux-arm64 binary uploading to release assets 114 | uses: actions/upload-release-asset@v1 115 | env: 116 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 117 | with: 118 | upload_url: ${{ steps.create_release.outputs.upload_url }} 119 | asset_path: ./dist/gatewayapi-plugin-linux-arm64 120 | asset_name: gatewayapi-plugin-linux-arm64 121 | asset_content_type: application/octet-stream 122 | if: ${{ env.IS_DRY_RUN != 'true' }} 123 | 124 | - name: Gatewayapi-plugin-darwin-amd64 binary uploading to release assets 125 | uses: actions/upload-release-asset@v1 126 | env: 127 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 128 | with: 129 | upload_url: ${{ steps.create_release.outputs.upload_url }} 130 | asset_path: ./dist/gatewayapi-plugin-darwin-amd64 131 | asset_name: gatewayapi-plugin-darwin-amd64 132 | asset_content_type: application/octet-stream 133 | if: ${{ env.IS_DRY_RUN != 'true' }} 134 | 135 | - name: Gatewayapi-plugin-darwin-arm64 binary uploading to release assets 136 | uses: actions/upload-release-asset@v1 137 | env: 138 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 139 | with: 140 | upload_url: ${{ steps.create_release.outputs.upload_url }} 141 | asset_path: ./dist/gatewayapi-plugin-darwin-arm64 142 | asset_name: gatewayapi-plugin-darwin-arm64 143 | asset_content_type: application/octet-stream 144 | if: ${{ env.IS_DRY_RUN != 'true' }} 145 | 146 | - name: Gatewayapi-plugin-windows-amd64 binary uploading to release assets 147 | uses: actions/upload-release-asset@v1 148 | env: 149 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 150 | with: 151 | upload_url: ${{ steps.create_release.outputs.upload_url }} 152 | asset_path: ./dist/gatewayapi-plugin-windows-amd64.exe 153 | asset_name: gatewayapi-plugin-windows-amd64.exe 154 | asset_content_type: application/octet-stream 155 | if: ${{ env.IS_DRY_RUN != 'true' }} 156 | 157 | - name: Create Container image 158 | run: | 159 | echo "Building containers for ${RELEASE_TAG}" 160 | gh workflow run docker-publish.yaml --ref ${RELEASE_TAG} 161 | env: 162 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 163 | -------------------------------------------------------------------------------- /pkg/plugin/types.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/sirupsen/logrus" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/client-go/kubernetes" 9 | v1 "k8s.io/client-go/kubernetes/typed/core/v1" 10 | gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 11 | "sigs.k8s.io/gateway-api/apis/v1alpha2" 12 | gatewayAPIClientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" 13 | gatewayApiClientv1 "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/typed/apis/v1" 14 | gatewayApiClientv1alpha2 "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/typed/apis/v1alpha2" 15 | ) 16 | 17 | type CommandLineOpts struct { 18 | KubeClientQPS float32 19 | KubeClientBurst int 20 | } 21 | 22 | type RpcPlugin struct { 23 | CommandLineOpts CommandLineOpts 24 | HTTPRouteClient gatewayApiClientv1.HTTPRouteInterface 25 | TCPRouteClient gatewayApiClientv1alpha2.TCPRouteInterface 26 | GRPCRouteClient gatewayApiClientv1.GRPCRouteInterface 27 | TLSRouteClient gatewayApiClientv1alpha2.TLSRouteInterface 28 | TestClientset v1.ConfigMapInterface 29 | GatewayAPIClientset *gatewayAPIClientset.Clientset 30 | Clientset *kubernetes.Clientset 31 | UpdatedHTTPRouteMock *gatewayv1.HTTPRoute 32 | UpdatedTCPRouteMock *v1alpha2.TCPRoute 33 | UpdatedGRPCRouteMock *gatewayv1.GRPCRoute 34 | UpdatedTLSRouteMock *v1alpha2.TLSRoute 35 | LogCtx *logrus.Entry 36 | IsTest bool 37 | } 38 | 39 | type GatewayAPITrafficRouting struct { 40 | // HTTPRoute refers to the name of the HTTPRoute used to route traffic to the 41 | // service 42 | HTTPRoute string `json:"httpRoute,omitempty"` 43 | // GRPCRoute refers to the name of the GRPCRoute used to route traffic to the 44 | // service 45 | GRPCRoute string `json:"grpcRoute,omitempty"` 46 | // TCPRoute refers to the name of the TCPRoute used to route traffic to the 47 | // service 48 | TCPRoute string `json:"tcpRoute,omitempty"` 49 | // TLSRoute refers to the name of the TLSRoute used to route traffic to the 50 | // service 51 | TLSRoute string `json:"tlsRoute,omitempty"` 52 | // Namespace refers to the namespace of the specified resource 53 | Namespace string `json:"namespace,omitempty"` 54 | // ConfigMap refers to the config map where plugin stores data about managed routes 55 | ConfigMap string `json:"configMap,omitempty"` 56 | // HTTPRoutes refer to names of HTTPRoute resources used to route traffic to the 57 | // service 58 | HTTPRoutes []HTTPRoute `json:"httpRoutes,omitempty"` 59 | // TCPRoutes refer to names of TCPRoute resources used to route traffic to the 60 | // service 61 | TCPRoutes []TCPRoute `json:"tcpRoutes,omitempty"` 62 | // GRPCRoutes refer to names of GRPCRoute resources used to route traffic to the 63 | // service 64 | GRPCRoutes []GRPCRoute `json:"grpcRoutes,omitempty"` 65 | // TLSRoutes refer to names of TLSRoute resources used to route traffic to the 66 | // service 67 | TLSRoutes []TLSRoute `json:"tlsRoutes,omitempty"` 68 | // HTTPRouteSelector refers to label selector for auto-discovery of HTTPRoutes 69 | HTTPRouteSelector *metav1.LabelSelector `json:"httpRouteSelector,omitempty"` 70 | // GRPCRouteSelector refers to label selector for auto-discovery of GRPCRoutes 71 | GRPCRouteSelector *metav1.LabelSelector `json:"grpcRouteSelector,omitempty"` 72 | // TCPRouteSelector refers to label selector for auto-discovery of TCPRoutes 73 | TCPRouteSelector *metav1.LabelSelector `json:"tcpRouteSelector,omitempty"` 74 | // TLSRouteSelector refers to label selector for auto-discovery of TLSRoutes 75 | TLSRouteSelector *metav1.LabelSelector `json:"tlsRouteSelector,omitempty"` 76 | // DisableInProgressLabel disables the automatic label that marks routes as managed during canary steps 77 | DisableInProgressLabel bool `json:"disableInProgressLabel,omitempty"` 78 | // InProgressLabelKey overrides the label key used while a canary is running 79 | InProgressLabelKey string `json:"inProgressLabelKey,omitempty"` 80 | // InProgressLabelValue overrides the label value used while a canary is running 81 | InProgressLabelValue string `json:"inProgressLabelValue,omitempty"` 82 | // ConfigMapRWMutex refers to the RWMutex that we use to enter to the critical section 83 | // critical section is config map 84 | ConfigMapRWMutex sync.RWMutex 85 | } 86 | 87 | type HTTPRoute struct { 88 | // Name refers to the HTTPRoute name 89 | Name string `json:"name" validate:"required"` 90 | // UseHeaderRoutes defines header routes will be added to this route or not 91 | // during setHeaderRoute step 92 | UseHeaderRoutes bool `json:"useHeaderRoutes,omitempty"` 93 | } 94 | 95 | type TCPRoute struct { 96 | // Name refers to the TCPRoute name 97 | Name string `json:"name" validate:"required"` 98 | // UseHeaderRoutes indicates header routes will be added to this route or not 99 | // during setHeaderRoute step 100 | UseHeaderRoutes bool `json:"useHeaderRoutes"` 101 | } 102 | 103 | type GRPCRoute struct { 104 | // Name refers to the GRPCRoute name 105 | Name string `json:"name" validate:"required"` 106 | // UseHeaderRoutes indicates header routes will be added to this route or not 107 | // during setHeaderRoute step 108 | UseHeaderRoutes bool `json:"useHeaderRoutes"` 109 | } 110 | 111 | type TLSRoute struct { 112 | // Name refers to the TLSRoute name 113 | Name string `json:"name" validate:"required"` 114 | // UseHeaderRoutes indicates header routes will be added to this route or not 115 | // during setHeaderRoute step 116 | UseHeaderRoutes bool `json:"useHeaderRoutes"` 117 | } 118 | 119 | type ManagedRouteMap map[string]map[string]int 120 | 121 | type HTTPRouteRule gatewayv1.HTTPRouteRule 122 | 123 | type GRPCRouteRule gatewayv1.GRPCRouteRule 124 | 125 | type TCPRouteRule v1alpha2.TCPRouteRule 126 | 127 | type TLSRouteRule v1alpha2.TLSRouteRule 128 | 129 | type HTTPRouteRuleList []gatewayv1.HTTPRouteRule 130 | 131 | type GRPCRouteRuleList []gatewayv1.GRPCRouteRule 132 | 133 | type TCPRouteRuleList []v1alpha2.TCPRouteRule 134 | 135 | type TLSRouteRuleList []v1alpha2.TLSRouteRule 136 | 137 | type HTTPBackendRef gatewayv1.HTTPBackendRef 138 | 139 | type GRPCBackendRef gatewayv1.GRPCBackendRef 140 | 141 | type TCPBackendRef gatewayv1.BackendRef 142 | 143 | type TLSBackendRef gatewayv1.BackendRef 144 | 145 | type GatewayAPIRoute interface { 146 | HTTPRoute | GRPCRoute | TCPRoute | TLSRoute 147 | GetName() string 148 | } 149 | 150 | type GatewayAPIRouteRule[T1 GatewayAPIBackendRef] interface { 151 | *HTTPRouteRule | *GRPCRouteRule | *TCPRouteRule | *TLSRouteRule 152 | Iterator() (GatewayAPIRouteRuleIterator[T1], bool) 153 | } 154 | 155 | type GatewayAPIRouteRuleList[T1 GatewayAPIBackendRef, T2 GatewayAPIRouteRule[T1]] interface { 156 | HTTPRouteRuleList | GRPCRouteRuleList | TCPRouteRuleList | TLSRouteRuleList 157 | Iterator() (GatewayAPIRouteRuleListIterator[T1, T2], bool) 158 | Error() error 159 | } 160 | 161 | type GatewayAPIBackendRef interface { 162 | *HTTPBackendRef | *GRPCBackendRef | *TCPBackendRef | *TLSBackendRef 163 | GetName() string 164 | } 165 | 166 | type GatewayAPIRouteRuleListIterator[T1 GatewayAPIBackendRef, T2 GatewayAPIRouteRule[T1]] func() (T2, bool) 167 | 168 | type GatewayAPIRouteRuleIterator[T1 GatewayAPIBackendRef] func() (T1, bool) 169 | -------------------------------------------------------------------------------- /examples/kong/README.md: -------------------------------------------------------------------------------- 1 | # Using Kong Gateway with Argo Rollouts 2 | 3 | Kong Ingress has [native support](https://docs.konghq.com/kubernetes-ingress-controller/latest/concepts/gateway-api/) for the Gateway API making the integration with Argo Rollouts a straightforward process. 4 | 5 | ## Step 0 - Install Argo Rollouts and the API Gateway Plugin 6 | 7 | See [instructions](https://rollouts-plugin-trafficrouter-gatewayapi.readthedocs.io/en/latest/installation/). 8 | 9 | ## Step 1 - Install the Gateway APIs 10 | 11 | Kong does not install the Gateway APIs by default. You need to install them manually 12 | as described in the [instructions](https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api). 13 | 14 | ```shell 15 | kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.7.1/standard-install.yaml 16 | ``` 17 | 18 | It is imperative you install the APIs **before** installing Kong, as the Helm chart detects the APIs and also installs the correct roles for Kong itself to manage Gateway resources. 19 | 20 | ## Step 2 - Deploy Kong Ingress to the cluster 21 | 22 | Follow [the official instructions](https://docs.konghq.com/kubernetes-ingress-controller/2.9.x/deployment/k4k8s/#helm) 23 | 24 | 25 | ```shell 26 | helm repo add kong https://charts.konghq.com 27 | helm repo update 28 | 29 | 30 | # Helm 3 31 | helm install kong/kong --generate-name --set ingressController.installCRDs=false -n kong --create-namespace 32 | 33 | ``` 34 | 35 | Then enable Gateway support by toggling [the respective feature](https://docs.konghq.com/kubernetes-ingress-controller/2.9.x/deployment/install-gateway-apis/): 36 | 37 | ```shell 38 | kubectl set env -n kong deployment/ingress-kong CONTROLLER_FEATURE_GATES="GatewayAlpha=true" -c ingress-controller 39 | kubectl rollout restart -n NAMESPACE deployment DEPLOYMENT_NAME 40 | ``` 41 | 42 | ## Step 3 - Create Gateway and Gateway class 43 | 44 | Now create the GatewayClass object (it needs to be created only once). 45 | 46 | ```yaml 47 | apiVersion: gateway.networking.k8s.io/v1beta1 48 | kind: GatewayClass 49 | metadata: 50 | name: kong 51 | annotations: 52 | konghq.com/gatewayclass-unmanaged: 'true' 53 | 54 | spec: 55 | controllerName: konghq.com/kic-gateway-controller 56 | ``` 57 | 58 | Apply the file with `kubectl` 59 | 60 | Create a gateway: 61 | 62 | ```yaml 63 | apiVersion: gateway.networking.k8s.io/v1beta1 64 | kind: Gateway 65 | metadata: 66 | name: kong 67 | spec: 68 | gatewayClassName: kong 69 | listeners: 70 | - name: proxy 71 | port: 80 72 | protocol: HTTP 73 | 74 | ``` 75 | 76 | Get the IP of the gateway with: 77 | 78 | ```shell 79 | kubectl get gateways.gateway.networking.k8s.io kong -o=jsonpath="{.status.addresses[0].value}" 80 | ``` 81 | 82 | Note down the IP address for testing the application later. 83 | 84 | ## Step 4 - Give access to Argo Rollouts for the Gateway/Http Route 85 | 86 | 87 | Create Cluster Role resource with needed permissions for Gateway API provider. 88 | 89 | ```yaml 90 | apiVersion: rbac.authorization.k8s.io/v1 91 | kind: ClusterRole 92 | metadata: 93 | name: gateway-controller-role 94 | namespace: argo-rollouts 95 | rules: 96 | - apiGroups: 97 | - "*" 98 | resources: 99 | - "*" 100 | verbs: 101 | - "*" 102 | ``` 103 | 104 | Note that these permission are not very strict. You should lock them down according to your needs. 105 | 106 | With the following role we allow Argo Rollouts to have write access to Http Routes and Gateways. 107 | 108 | ```yaml 109 | apiVersion: rbac.authorization.k8s.io/v1 110 | kind: ClusterRoleBinding 111 | metadata: 112 | name: gateway-admin 113 | roleRef: 114 | apiGroup: rbac.authorization.k8s.io 115 | kind: ClusterRole 116 | name: gateway-controller-role 117 | subjects: 118 | - namespace: argo-rollouts 119 | kind: ServiceAccount 120 | name: argo-rollouts 121 | ``` 122 | 123 | Apply both files with `kubectl`. 124 | 125 | ## Step 5 - Create HTTPRoute that defines a traffic split between two services 126 | 127 | Create HTTPRoute and connect to the created Gateway resource 128 | 129 | ```yaml 130 | kind: HTTPRoute 131 | apiVersion: gateway.networking.k8s.io/v1beta1 132 | metadata: 133 | name: argo-rollouts-http-route 134 | annotations: 135 | konghq.com/strip-path: 'true' 136 | spec: 137 | parentRefs: 138 | - kind: Gateway 139 | name: kong 140 | hostnames: 141 | - "demo.example.com" 142 | rules: 143 | - matches: 144 | - path: 145 | type: PathPrefix 146 | value: / 147 | backendRefs: 148 | - name: argo-rollouts-stable-service 149 | kind: Service 150 | port: 80 151 | - name: argo-rollouts-canary-service 152 | kind: Service 153 | port: 80 154 | ``` 155 | 156 | 157 | - Canary service 158 | 159 | ```yaml 160 | apiVersion: v1 161 | kind: Service 162 | metadata: 163 | name: argo-rollouts-canary-service 164 | spec: 165 | ports: 166 | - port: 80 167 | targetPort: http 168 | protocol: TCP 169 | name: http 170 | selector: 171 | app: rollouts-demo 172 | ``` 173 | 174 | - Stable service 175 | 176 | ```yaml 177 | apiVersion: v1 178 | kind: Service 179 | metadata: 180 | name: argo-rollouts-stable-service 181 | spec: 182 | ports: 183 | - port: 80 184 | targetPort: http 185 | protocol: TCP 186 | name: http 187 | selector: 188 | app: rollouts-demo 189 | ``` 190 | 191 | Apply all the above manifests with `kubectl`. 192 | 193 | ## Step 6 - Create an example Rollout 194 | 195 | Deploy a rollout to get the initial version 196 | 197 | ```yaml 198 | Here is an example rollout 199 | 200 | apiVersion: argoproj.io/v1alpha1 201 | kind: Rollout 202 | metadata: 203 | name: rollouts-demo 204 | namespace: default 205 | spec: 206 | revisionHistoryLimit: 1 207 | replicas: 10 208 | strategy: 209 | canary: 210 | canaryService: argo-rollouts-canary-service # our created canary service 211 | stableService: argo-rollouts-stable-service # our created stable service 212 | trafficRouting: 213 | plugins: 214 | argoproj-labs/gatewayAPI: 215 | httpRoute: argo-rollouts-http-route # our created httproute 216 | namespace: default 217 | steps: 218 | - setWeight: 30 219 | - pause: {} 220 | - setWeight: 60 221 | - pause: {} 222 | - setWeight: 100 223 | - pause: {} 224 | revisionHistoryLimit: 2 225 | selector: 226 | matchLabels: 227 | app: rollouts-demo 228 | template: 229 | metadata: 230 | labels: 231 | app: rollouts-demo 232 | spec: 233 | containers: 234 | - name: rollouts-demo 235 | image: kostiscodefresh/summer-of-k8s-app:v1 236 | ports: 237 | - name: http 238 | containerPort: 8080 239 | protocol: TCP 240 | resources: 241 | requests: 242 | memory: 32Mi 243 | cpu: 5m 244 | ``` 245 | 246 | Change the manifest to the `v2` tag and while the rollout is progressing you should see 247 | the split traffic by visiting the IP of the gateway (see step 2) 248 | 249 | ```shell 250 | curl -H "host: demo.example.com" /call-me 251 | ``` 252 | Run the command above multiple times and depending on the canary status you will sometimes see "v1" returned and sometimes "v2" 253 | 254 | -------------------------------------------------------------------------------- /examples/envoygateway/README.md: -------------------------------------------------------------------------------- 1 | # Using Envoy Gateway with Argo Rollouts 2 | 3 | [Envoy Gateway](https://gateway.envoyproxy.io/) is an open source project for managing Envoy Proxy as a standalone or Kubernetes-based application gateway. Gateway API resources are used to dynamically provision and configure the managed Envoy Proxies 4 | 5 | ## Prerequisites 6 | 7 | A Kubernetes cluster. 8 | 9 | __Note:__ Refer to the [Compatibility Matrix](https://gateway.envoyproxy.io/latest/intro/compatibility.html) for supported Kubernetes versions. 10 | 11 | Install the Gateway API CRDs and Envoy Gateway: 12 | 13 | ```shell 14 | helm install eg oci://docker.io/envoyproxy/gateway-helm --version v0.5.0 -n envoy-gateway-system --create-namespace 15 | ``` 16 | 17 | Wait for Envoy Gateway to become available: 18 | 19 | ```shell 20 | kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available 21 | ``` 22 | 23 | ## Step 1 - Create EnvoyGateway GatewayClass and Gateway object 24 | 25 | Create a gateway: 26 | 27 | ```yaml title="gateway.yaml" 28 | --- 29 | apiVersion: gateway.networking.k8s.io/v1beta1 30 | kind: GatewayClass 31 | metadata: 32 | name: eg 33 | namespace: default 34 | spec: 35 | controllerName: gateway.envoyproxy.io/gatewayclass-controller 36 | --- 37 | apiVersion: gateway.networking.k8s.io/v1beta1 38 | kind: Gateway 39 | metadata: 40 | name: eg 41 | namespace: default 42 | spec: 43 | gatewayClassName: eg 44 | listeners: 45 | - name: http 46 | protocol: HTTP 47 | port: 80 48 | ``` 49 | 50 | Apply the file with `kubectl`: 51 | 52 | ```shell 53 | cd examples/envoygateway 54 | kubectl apply -f gateway.yaml 55 | ``` 56 | 57 | Get the IP of your Gateway 58 | 59 | ```shell 60 | export GATEWAY_IP=$(kubectl get gateway eg -o=jsonpath="{.status.addresses[0].value}") 61 | echo $GATEWAY_IP 62 | ``` 63 | 64 | ## Step 2 - Give access to Argo Rollouts for the Gateway/Http Route 65 | 66 | Create Cluster Role resource with needed permissions for Gateway API provider. 67 | 68 | ```yaml title="cluster-role.yaml" 69 | apiVersion: rbac.authorization.k8s.io/v1 70 | kind: ClusterRole 71 | metadata: 72 | name: gateway-controller-role 73 | namespace: argo-rollouts 74 | rules: 75 | - apiGroups: 76 | - "*" 77 | resources: 78 | - "*" 79 | verbs: 80 | - "*" 81 | ``` 82 | 83 | __Note:__ These permission are not very strict. You should lock them down according to your needs. 84 | 85 | With the following role we allow Argo Rollouts to have write access to HTTPRoutes and Gateways. 86 | 87 | ```yaml title="cluster-role-binding.yaml" 88 | apiVersion: rbac.authorization.k8s.io/v1 89 | kind: ClusterRoleBinding 90 | metadata: 91 | name: gateway-admin 92 | roleRef: 93 | apiGroup: rbac.authorization.k8s.io 94 | kind: ClusterRole 95 | name: gateway-controller-role 96 | subjects: 97 | - namespace: argo-rollouts 98 | kind: ServiceAccount 99 | name: argo-rollouts 100 | ``` 101 | 102 | Apply both files with `kubectl`: 103 | 104 | ```shell 105 | kubectl apply -f cluster-role.yaml 106 | kubectl apply -f cluster-role-binding.yaml 107 | ``` 108 | 109 | ## Step 4 - Create HTTPRoute that defines a traffic split between two services 110 | Create HTTPRoute and connect to the created Gateway resource 111 | 112 | ```yaml title="httproute.yaml" 113 | kind: HTTPRoute 114 | apiVersion: gateway.networking.k8s.io/v1beta1 115 | metadata: 116 | name: argo-rollouts-http-route 117 | namespace: default 118 | spec: 119 | parentRefs: 120 | - name: eg 121 | hostnames: 122 | - "demo.example.com" 123 | rules: 124 | - matches: 125 | - path: 126 | type: PathPrefix 127 | value: / 128 | backendRefs: 129 | - name: argo-rollouts-stable-service 130 | kind: Service 131 | port: 80 132 | - name: argo-rollouts-canary-service 133 | kind: Service 134 | port: 80 135 | ``` 136 | 137 | - Stable service 138 | 139 | ```yaml title="stable.yaml" 140 | apiVersion: v1 141 | kind: Service 142 | metadata: 143 | name: argo-rollouts-stable-service 144 | namespace: default 145 | spec: 146 | ports: 147 | - port: 80 148 | targetPort: http 149 | protocol: TCP 150 | name: http 151 | selector: 152 | app: rollouts-demo 153 | ``` 154 | 155 | - Canary service 156 | 157 | ```yaml title="canary.yaml" 158 | apiVersion: v1 159 | kind: Service 160 | metadata: 161 | name: argo-rollouts-canary-service 162 | namespace: default 163 | spec: 164 | ports: 165 | - port: 80 166 | targetPort: http 167 | protocol: TCP 168 | name: http 169 | selector: 170 | app: rollouts-demo 171 | ``` 172 | 173 | Apply the files with `kubectl`: 174 | 175 | ```shell 176 | kubectl apply -f httproute.yaml 177 | kubectl apply -f stable.yaml 178 | kubectl apply -f canary.yaml 179 | ``` 180 | 181 | 182 | ## Step 5 - Create an example Rollout 183 | 184 | Deploy a rollout to get the initial version. 185 | 186 | Here is an example rollout: 187 | 188 | ```yaml title="rollout.yaml" 189 | apiVersion: argoproj.io/v1alpha1 190 | kind: Rollout 191 | metadata: 192 | name: rollouts-demo 193 | namespace: default 194 | spec: 195 | replicas: 3 196 | strategy: 197 | canary: 198 | canaryService: argo-rollouts-canary-service # our created canary service 199 | stableService: argo-rollouts-stable-service # our created stable service 200 | trafficRouting: 201 | plugins: 202 | argoproj-labs/gatewayAPI: 203 | httpRoute: argo-rollouts-http-route # our created httproute 204 | namespace: default 205 | steps: 206 | - setWeight: 30 207 | - pause: {} 208 | - setWeight: 60 209 | - pause: {} 210 | - setWeight: 100 211 | - pause: {} 212 | revisionHistoryLimit: 2 213 | selector: 214 | matchLabels: 215 | app: rollouts-demo 216 | template: 217 | metadata: 218 | labels: 219 | app: rollouts-demo 220 | spec: 221 | containers: 222 | - name: rollouts-demo 223 | image: kostiscodefresh/summer-of-k8s-app:v1 224 | ports: 225 | - name: http 226 | containerPort: 8080 227 | protocol: TCP 228 | resources: 229 | requests: 230 | memory: 32Mi 231 | cpu: 5m 232 | ``` 233 | 234 | Apply the file with `kubectl`: 235 | 236 | ```shell 237 | kubectl apply -f rollout.yaml 238 | ``` 239 | 240 | Check the rollout: 241 | ```shell 242 | export GATEWAY_IP=$(kubectl get gateway eg -o=jsonpath="{.status.addresses[0].value}") 243 | curl -H "host: demo.example.com" $GATEWAY_IP/callme 244 | ``` 245 | 246 | The output should be: 247 | 248 | ```shell 249 |
ver: 1.0 250 |
% 251 | ``` 252 | 253 | Change the manifest to the `v2` tag and while the rollout is progressing you should see 254 | the split traffic by visiting the IP of the gateway (see step 2) 255 | 256 | ```shell 257 | kubectl patch rollout rollouts-demo -n default \ 258 | --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"kostiscodefresh/summer-of-k8s-app:v2"}]' 259 | ``` 260 | 261 | Run the command and depending on the canary status you will sometimes see "v1" returned and sometimes "v2" 262 | ```shell 263 | while true; do curl -H "host: demo.example.com" $GATEWAY_IP/callme; done 264 | ``` 265 | -------------------------------------------------------------------------------- /pkg/plugin/experiment_test.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" 7 | "github.com/stretchr/testify/assert" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 10 | ) 11 | 12 | func TestHandleExperiment_ExperimentStatusChecking(t *testing.T) { 13 | stableService := "stable-svc" 14 | canaryService := "canary-svc" 15 | 16 | rollout := &v1alpha1.Rollout{ 17 | ObjectMeta: metav1.ObjectMeta{ 18 | Name: "rollout-test", 19 | Namespace: "default", 20 | }, 21 | Spec: v1alpha1.RolloutSpec{ 22 | Strategy: v1alpha1.RolloutStrategy{ 23 | Canary: &v1alpha1.CanaryStrategy{ 24 | StableService: stableService, 25 | CanaryService: canaryService, 26 | }, 27 | }, 28 | }, 29 | Status: v1alpha1.RolloutStatus{ 30 | Canary: v1alpha1.CanaryStatus{ 31 | CurrentExperiment: "active-experiment", 32 | }, 33 | }, 34 | } 35 | 36 | stableWeight := int32(100) 37 | canaryWeight := int32(0) 38 | httpRoute := &gatewayv1.HTTPRoute{ 39 | Spec: gatewayv1.HTTPRouteSpec{ 40 | Rules: []gatewayv1.HTTPRouteRule{ 41 | { 42 | BackendRefs: []gatewayv1.HTTPBackendRef{ 43 | { 44 | BackendRef: gatewayv1.BackendRef{ 45 | BackendObjectReference: gatewayv1.BackendObjectReference{ 46 | Name: gatewayv1.ObjectName(stableService), 47 | }, 48 | Weight: &stableWeight, 49 | }, 50 | }, 51 | { 52 | BackendRef: gatewayv1.BackendRef{ 53 | BackendObjectReference: gatewayv1.BackendObjectReference{ 54 | Name: gatewayv1.ObjectName(canaryService), 55 | }, 56 | Weight: &canaryWeight, 57 | }, 58 | }, 59 | }, 60 | }, 61 | }, 62 | }, 63 | } 64 | 65 | isExperimentActive := rollout.Spec.Strategy.Canary != nil && rollout.Status.Canary.CurrentExperiment != "" 66 | assert.True(t, isExperimentActive, "Experiment should be detected as active") 67 | 68 | hasExperimentServices := false 69 | ruleIdx := 0 70 | for _, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 71 | serviceName := string(backendRef.Name) 72 | if serviceName != stableService && serviceName != canaryService { 73 | hasExperimentServices = true 74 | break 75 | } 76 | } 77 | assert.False(t, hasExperimentServices, "HTTPRoute should not have experiment services initially") 78 | 79 | // Test dynamic weight calculation 80 | additionalDestinations := []v1alpha1.WeightDestination{ 81 | { 82 | ServiceName: "exp-svc-1", 83 | Weight: 25, 84 | }, 85 | { 86 | ServiceName: "exp-svc-2", 87 | Weight: 30, 88 | }, 89 | } 90 | 91 | // Calculate total experiment weight 92 | var totalExperimentWeight int32 93 | for _, dest := range additionalDestinations { 94 | totalExperimentWeight += dest.Weight 95 | } 96 | expectedStableWeight := int32(100) - totalExperimentWeight 97 | 98 | // Update stable weight 99 | for i, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 100 | if string(backendRef.Name) == stableService { 101 | httpRoute.Spec.Rules[ruleIdx].BackendRefs[i].Weight = &expectedStableWeight 102 | break 103 | } 104 | } 105 | 106 | assert.Equal(t, int32(45), *httpRoute.Spec.Rules[0].BackendRefs[0].Weight, "Stable weight should be 45% (100% - 55% experiment weight)") 107 | } 108 | 109 | func TestHandleExperiment_RemoveExperimentServices(t *testing.T) { 110 | stableService := "stable-svc" 111 | canaryService := "canary-svc" 112 | experimentSvc := "exp-svc" 113 | 114 | rollout := &v1alpha1.Rollout{ 115 | ObjectMeta: metav1.ObjectMeta{ 116 | Name: "rollout-test", 117 | Namespace: "default", 118 | }, 119 | Spec: v1alpha1.RolloutSpec{ 120 | Strategy: v1alpha1.RolloutStrategy{ 121 | Canary: &v1alpha1.CanaryStrategy{ 122 | StableService: stableService, 123 | CanaryService: canaryService, 124 | }, 125 | }, 126 | }, 127 | Status: v1alpha1.RolloutStatus{ 128 | Canary: v1alpha1.CanaryStatus{ 129 | CurrentExperiment: "", 130 | }, 131 | }, 132 | } 133 | 134 | stableWeight := int32(45) 135 | canaryWeight := int32(0) 136 | experimentWeight := int32(15) 137 | port := gatewayv1.PortNumber(8080) 138 | namespace := gatewayv1.Namespace("default") 139 | 140 | httpRoute := &gatewayv1.HTTPRoute{ 141 | Spec: gatewayv1.HTTPRouteSpec{ 142 | Rules: []gatewayv1.HTTPRouteRule{ 143 | { 144 | BackendRefs: []gatewayv1.HTTPBackendRef{ 145 | { 146 | BackendRef: gatewayv1.BackendRef{ 147 | BackendObjectReference: gatewayv1.BackendObjectReference{ 148 | Name: gatewayv1.ObjectName(stableService), 149 | Port: &port, 150 | }, 151 | Weight: &stableWeight, 152 | }, 153 | }, 154 | { 155 | BackendRef: gatewayv1.BackendRef{ 156 | BackendObjectReference: gatewayv1.BackendObjectReference{ 157 | Name: gatewayv1.ObjectName(canaryService), 158 | Port: &port, 159 | }, 160 | Weight: &canaryWeight, 161 | }, 162 | }, 163 | { 164 | BackendRef: gatewayv1.BackendRef{ 165 | BackendObjectReference: gatewayv1.BackendObjectReference{ 166 | Name: gatewayv1.ObjectName(experimentSvc), 167 | Namespace: &namespace, 168 | Port: &port, 169 | }, 170 | Weight: &experimentWeight, 171 | }, 172 | }, 173 | }, 174 | }, 175 | }, 176 | }, 177 | } 178 | 179 | isExperimentActive := rollout.Spec.Strategy.Canary != nil && rollout.Status.Canary.CurrentExperiment != "" 180 | assert.False(t, isExperimentActive, "Experiment should be detected as inactive") 181 | 182 | hasExperimentServices := false 183 | ruleIdx := 0 184 | for _, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 185 | serviceName := string(backendRef.Name) 186 | if serviceName != stableService && serviceName != canaryService { 187 | hasExperimentServices = true 188 | break 189 | } 190 | } 191 | assert.True(t, hasExperimentServices, "HTTPRoute should have experiment services initially") 192 | 193 | stableWeight = int32(100) 194 | canaryWeight = int32(0) 195 | filteredBackendRefs := []gatewayv1.HTTPBackendRef{} 196 | 197 | for _, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 198 | serviceName := string(backendRef.Name) 199 | 200 | if serviceName == stableService { 201 | backendRef.Weight = &stableWeight 202 | filteredBackendRefs = append(filteredBackendRefs, backendRef) 203 | } else if serviceName == canaryService { 204 | backendRef.Weight = &canaryWeight 205 | filteredBackendRefs = append(filteredBackendRefs, backendRef) 206 | } 207 | } 208 | 209 | httpRoute.Spec.Rules[ruleIdx].BackendRefs = filteredBackendRefs 210 | 211 | assert.Len(t, httpRoute.Spec.Rules[0].BackendRefs, 2, "Should only have stable and canary services after cleanup") 212 | assert.Equal(t, int32(100), *httpRoute.Spec.Rules[0].BackendRefs[0].Weight, "Stable weight should be reset to 100%") 213 | assert.Equal(t, int32(0), *httpRoute.Spec.Rules[0].BackendRefs[1].Weight, "Canary weight should remain at 0%") 214 | 215 | hasExperimentServices = false 216 | for _, backendRef := range httpRoute.Spec.Rules[ruleIdx].BackendRefs { 217 | serviceName := string(backendRef.Name) 218 | if serviceName != stableService && serviceName != canaryService { 219 | hasExperimentServices = true 220 | break 221 | } 222 | } 223 | assert.False(t, hasExperimentServices, "HTTPRoute should not have experiment services after cleanup") 224 | } 225 | --------------------------------------------------------------------------------