Route groups¶
Route groups are an alternative to the Kubernetes Ingress format for defining ingress rules. They allow to define Skipper routing in Kubernetes, while providing a straightforward way to configure the routing features supported by Skipper and not defined by the generic Ingress.
Skipper as Kubernetes Ingress controller¶
Skipper is an extensible HTTP router with rich route matching, and request flow and traffic shaping capabilities. Through its integration with Kubernetes, it can be used in the role of an ingress controller for forwarding incoming external requests to the right services in a cluster. Kubernetes provides the Ingress specification to define the rules by which an ingress controller should handle the incoming traffic. The specification is simple and generic, but doesn’t offer a straightforward way to benefit from Skipper’s rich HTTP related functionality.
RouteGroups¶
A RouteGroup is a custom Kubernetes resource definition. It provides a way to define the ingress routing for Kubernetes services. It allows route matching based on any HTTP request attributes, and provides a clean way for the request flow augmentation and traffic shaping. It supports higher level features like gradual traffic switching, A/B testing, and more.
Example:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-routes
spec:
backends:
- name: variant-a
type: service
serviceName: service-a
servicePort: 80
- name: variant-b
type: service
serviceName: service-b
servicePort: 80
defaultBackends:
- backendName: variant-b
routes:
- pathSubtree: /
filters:
- responseCookie("canary", "A")
predicates:
- Traffic(.1)
backends:
- backendName: variant-a
- pathSubtree: /
filters:
- responseCookie("canary", "B")
- pathSubtree: /
predicates:
- Cookie("canary", "A")
backends:
- backendName: variant-a
- pathSubtree: /
predicates:
- Cookie("canary", "B")
(See a more detailed explanation of the above example further down in this document.)
Links:
Requirements¶
Installation¶
The definition file of the CRD can be found as part of Skipper’s source code, at:
To install it manually in a cluster, assuming the current directory is the root of Skipper’s source, call this command:
kubectl apply -f dataclients/kubernetes/deploy/apply/routegroups_crd.yaml
This will install a namespaced resource definition, providing the RouteGroup kind:
- full name: routegroups.zalando.org
- resource group: zalando.org/v1
- resource names: routegroup, routegroups, rg, rgs
- kind: RouteGroup
The route groups, once any is defined, can be displayed then via kubectl as:
kubectl get rgs
The API URL of the routegroup resources will be:
https://kubernetes-api-hostname/apis/zalando.org/v1/routegroups
Usage¶
The absolute minimal route group configuration for a Kubernetes service (my-service) looks as follows:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
spec:
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
routes:
- pathSubtree: /
backends:
- backendName: my-backend
This is equivalent to the ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
defaultBackend:
service:
name: my-service
port:
number: 80
Notice that the route group contains a list of actual backends, and the defined service backend is then referenced as the default backend. This structure plays a role in supporting scenarios like A/B testing and gradual traffic switching, explained below. The backend definition also has a type field, whose values can be service, lb, network, shunt, loopback or dynamic. More details on that below.
Creating, updating and deleting route groups happens the same way as with ingress objects. E.g, manually applying a route group definition:
kubectl apply -f my-route-group.yaml
Hosts¶
Hosts contain hostnames that are used to match the requests handled by a given route group. They are also used to update the required DNS entries and load balancer configuration if the cluster is set up that way.
Note that it is also possible to use any Skipper predicate in the routes of a route group, with the Host predicate included, but the hostnames defined that way will not serve as input for the DNS configuration.
Backends¶
RouteGroups support different backends. The most typical backend type is the ‘service’, and it works the same way as in case of ingress definitions.
In a RouteGroup, there can be multiple backends and they are listed on the top level of the route group spec, and are referenced from the actual routes or as default backends.
type=service¶
This backend resolves to a Kubernetes service. It works the same way as in case of Ingress definitions. Skipper
resolves the Services to the available Endpoints belonging to the Service, and generates load balanced routes
using them. (This basically means that under the hood, a service
backend becomes an lb
backend.)
type=lb¶
This backend provides load balancing between multiple network endpoints. Keep in mind that the service type backend automatically generates load balanced routes for the service endpoints, so this backend type typically doesn’t need to be used for services.
type=network¶
This backend type results in routes that proxy incoming requests to the defined network address, regardless of the Kubernetes semantics, and allows URLs that point somewhere else, potentially outside of the cluster, too.
type=shunt, type=loopback, type=dynamic¶
These backend types allow advanced routing setups. Please check the reference manual for more details.
Default Backends¶
A default backend is a reference to one of the defined backends. When a route doesn’t specify which backend(s) to use, the ones referenced in the default backends will be used.
In case there are no individual routes at all in the route group, a default set of routes (one or more) will be generated and will proxy the incoming traffic to the default backends.
The reason, why multiple backends can be referenced as default, is that this makes it easy to execute gradual traffic switching between different versions, even more than two, of the same application. See more.
Routes¶
Routes define where to and how the incoming requests will be proxied. The predicates, including the path, pathSubtree, pathRegexp and methods fields, and any free-form predicate listed under the predicates field, control which requests are matched by a route, the filters can apply changes to the forwarded requests and the returned responses, and the backend refs, if defined, override the default backends, where the requests will be proxied to. If a route group doesn’t contain any explicit routes, but it contains default backends, a default set of routes will be generated for the route group.
Important to bear in mind about the path fields, that the plain ‘path’ means exact path match, while ‘pathSubtree’ behaves as a path prefix, and so it is more similar to the path in the Ingress specification.
See also:
Gradual traffic switching¶
The weighted backend references allow to split the traffic of a single route and send it to different backends with the ratio defined by the weights of the backend references. E.g:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-routes
spec:
hosts:
- api.example.org
backends:
- name: api-svc-v1
type: service
serviceName: api-service-v1
servicePort: 80
- name: api-svc-v2
type: service
serviceName: foo-service-v2
servicePort: 80
routes:
- pathSubtree: /api
backends:
- backendName: api-svc-v1
weight: 80
- backendName: api-svc-v2
weight: 20
In case of the above example, 80% of the requests is sent to api-service-v1 and the rest is sent to api-service-v2.
Since this type of weighted traffic switching can be used in combination with the Traffic predicate, it is possible to control the routing of a long running A/B test, while still executing gradual traffic switching independently to deploy a new version of the variants, maybe to deploy a fix only to one variant. E.g:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-routes
spec:
hosts:
- api.example.org
backends:
- name: variant-a
type: service
serviceName: service-a
servicePort: 80
- name: variant-b
type: service
serviceName: service-b-v1
servicePort: 80
- name: variant-b-v2
type: service
serviceName: service-b-v2
servicePort: 80
defaultBackends:
- backendName: variant-b
weight: 80
- backendName: variant-b-v2
weight: 20
routes:
- filters:
- responseCookie("canary", "A")
predicates:
- Traffic(.1)
backends:
- backendName: variant-a
- filters:
- responseCookie("canary", "B")
- predicates:
- Cookie("canary", "A")
backends:
- backendName: variant-a
- predicates:
- Cookie("canary", "B")
See also:
Mapping from Ingress to RouteGroups¶
RouteGroups are one-way compatible with Ingress, meaning that every Ingress specification can be expressed in the RouteGroup format, as well. In the following, we describe the mapping from Ingress fields to RouteGroup fields.
Ingress with default backend¶
Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
defaultBackend:
service:
name: my-service
port:
number: 80
RouteGroup:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
spec:
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
defaultBackends:
- backendName: my-backend
Ingress with path rule¶
Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: api.example.org
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
RouteGroup:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
spec:
hosts:
- api.example.org
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
routes:
- pathSubtree: /api
Ingress with multiple hosts¶
Ingress (we need to define two rules):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: api.example.org
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
- host: legacy-name.example.org
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
RouteGroup (we just define an additional host):
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
spec:
hosts:
- api.example.org
- legacy-name.example.org
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
routes:
- pathSubtree: /api
Ingress with multiple hosts, and different routing¶
For those cases when using multiple hostnames in the same ingress with different rules, we need to apply a small workaround for the equivalent route group spec. Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- host: api.example.org
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
- host: legacy-name.example.org
http:
paths:
- path: /application
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
RouteGroup (we need to use additional host predicates):
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
spec:
hosts:
- api.example.org
- legacy-name.example.org
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
routes:
- pathSubtree: /api
predicates:
- Host("api.example.org")
- pathSubtree: /application
predicates:
- Host("legacy-name.example.org")
The RouteGroups allow multiple hostnames for each route group, but by default, their union is used during routing. If we want to distinguish between them, then we need to use an additional Host predicate in the routes. Importantly, only the hostnames listed under the hosts field serve as input for the DNS and LB configuration.
Mapping Skipper Ingress extensions to RouteGroups¶
Skipper accepts a set of annotations in Ingress objects that give access to certain Skipper features that would not be possible with the native fields of the Ingress spec, e.g. improved path handling or rate limiting. These annotations can be expressed now natively in the RouteGroups.
zalando.org/backend-weights¶
Backend weights are now part of the backend references, and they can be controlled for multiple backend sets within the same route group. See Gradual traffic switching.
zalando.org/skipper-filter and zalando.org/skipper-predicate¶
Filters and predicates are now part of the route objects, and different set of filters or predicates can be set for different routes.
zalando.org/skipper-routes¶
“Custom routes” in a route group are unnecessary, because every route can be configured with predicates, filters and backends without limitations. E.g where an ingress annotation’s metadata may look like this:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
zalando.org/skipper-routes: |
Method("OPTIONS") -> status(200) -> <shunt>
spec:
backend:
service:
name: my-service
port:
number: 80
the equivalent RouteGroup would look like this:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
spec:
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
- name: options200
type: shunt
defaultBackends:
- backendName: my-backend
routes:
- pathSubtree: /
- pathSubtree: /
methods: OPTIONS
filters:
- status(200)
backends:
- backendName: options200
zalando.org/ratelimit¶
The ratelimiting can be defined on the route level among the filters, in the same format as in this annotation.
zalando.org/skipper-ingress-redirect and zalando.org/skipper-ingress-redirect-code¶
Skipper ingress provides global HTTPS redirect, but it allows individual ingresses to override the global settings: enabling/disabling it and changing the default redirect code. With route groups, this override can be achieved by simply defining an additional route, with the same matching rules, and therefore the override can be controlled eventually on a route basis. E.g:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
spec:
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
- name: redirectShunt
type: shunt
defaultBackends:
- backendName: my-backend
routes:
- pathSubtree: /
- pathSubtree: /
predicates:
- Header("X-Forwarded-Proto", "http")
filters:
- redirectTo(302, "https:")
backends:
- backendName: redirectShunt
zalando.org/skipper-loadbalancer¶
Skipper Ingress doesn’t use the ClusterIP of the Service for forwarding the traffic to, but sends it directly to the Endpoints represented by the Service, and balances the load between them with the round-robin algorithm. The algorithm choice can be overridden by this annotation. In case of the RouteGroups, the algorithm is simply an attribute of the backend definition, and it can be set individually for each backend. E.g:
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
algorithm: consistentHash
See also:
zalando.org/skipper-ingress-path-mode¶
The route objects support the different path lookup modes, by using the path, pathSubtree or the pathRegexp field. See also the route matching explained for the internals. The mapping is as follows:
Ingress pathType: | RouteGroup: |
---|---|
Exact and /foo |
path: /foo |
Prefix and /foo |
pathSubtree: /foo |
Ingress (pathType: ImplementationSpecific ): |
RouteGroup: |
---|---|
kubernetes-ingress and /foo |
pathRegexp: ^/foo |
path-regexp and /foo |
pathRegexp: /foo |
path-prefix and /foo |
pathSubtree: /foo |
kubernetes-ingress and /foo$ |
path: /foo |
Multiple skipper deployments¶
If you want to split for example internal
and public
traffic, it
might be a good choice to split your RouteGroups. Skipper has
the flag --kubernetes-routegroup-class=<string>
to only select RouteGroup
objects that have the annotation zalando.org/routegroup.class
set to
<string>
. Skipper will only create routes for RouteGroup objects with
it’s annotation or RouteGroup objects that do not have this annotation. The
default class is skipper
, if not set.
Example RouteGroup:
apiVersion: zalando.org/v1
kind: RouteGroup
metadata:
name: my-route-group
annotations:
zalando.org/routegroup.class: internal
spec:
backends:
- name: my-backend
type: service
serviceName: my-service
servicePort: 80
defaultBackends:
- backendName: my-service