diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index a82cc3533..f3d5ad360 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -646,7 +646,6 @@ referencing services in the [`IngressRoute`](#kind-ingressroute) objects, or rec * services [Weighted Round Robin](#weighted-round-robin) load balancing. * services [mirroring](#mirroring). - #### Server Load Balancing More information in the dedicated server [load balancing](../services/index.md#load-balancing) section. @@ -916,6 +915,154 @@ More information in the dedicated [mirroring](../services/index.md#mirroring-ser Specifying a namespace attribute in this case would not make any sense, and will be ignored (except if the provider is `kubernetescrd`). +#### Stickiness and load-balancing + +As explained in the section about [Sticky sessions](../../services/#sticky-sessions), for stickiness to work all the way, +it must be specified at each load-balancing level. + +For instance, in the example below, there is a first level of load-balancing because there is a (Weighted Round Robin) load-balancing of the two `whoami` services, +and there is a second level because each whoami service is a `replicaset` and is thus handled as a load-balancer of servers. + +??? "Stickiness on two load-balancing levels" + + ```yaml tab="IngressRoute" + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRoute + metadata: + name: ingressroutebar + namespace: default + + spec: + entryPoints: + - web + routes: + - match: Host(`example.com`) && PathPrefix(`/foo`) + kind: Rule + services: + - name: wrr1 + namespace: default + kind: TraefikService + ``` + + ```yaml tab="Weighted Round Robin" + apiVersion: traefik.containo.us/v1alpha1 + kind: TraefikService + metadata: + name: wrr1 + namespace: default + + spec: + weighted: + services: + - name: whoami1 + kind: Service + port: 80 + weight: 1 + sticky: + cookie: + name: lvl2 + - name: whoami2 + kind: Service + weight: 1 + port: 80 + sticky: + cookie: + name: lvl2 + sticky: + cookie: + name: lvl1 + ``` + + ```yaml tab="K8s Service" + apiVersion: v1 + kind: Service + metadata: + name: whoami1 + + spec: + ports: + - protocol: TCP + name: web + port: 80 + selector: + app: whoami1 + + --- + apiVersion: v1 + kind: Service + metadata: + name: whoami2 + + spec: + ports: + - protocol: TCP + name: web + port: 80 + selector: + app: whoami2 + ``` + + ```yaml tab="Deployment (to illustrate replicas)" + kind: Deployment + apiVersion: apps/v1 + metadata: + namespace: default + name: whoami1 + labels: + app: whoami1 + + spec: + replicas: 2 + selector: + matchLabels: + app: whoami1 + template: + metadata: + labels: + app: whoami1 + spec: + containers: + - name: whoami1 + image: containous/whoami + ports: + - name: web + containerPort: 80 + + --- + kind: Deployment + apiVersion: apps/v1 + metadata: + namespace: default + name: whoami2 + labels: + app: whoami2 + + spec: + replicas: 2 + selector: + matchLabels: + app: whoami2 + template: + metadata: + labels: + app: whoami2 + spec: + containers: + - name: whoami2 + image: containous/whoami + ports: + - name: web + containerPort: 80 + ``` + + To keep a session open with the same server, the client would then need to specify the two levels within the cookie for each request, e.g. with curl: + + ```bash + curl -H Host:example.com -b "lvl1=default-whoami1-80; lvl2=http://10.42.0.6:80" http://localhost:8000/foo + ``` + + assuming `10.42.0.6` is the IP address of one of the replicas (a pod then) of the `whoami1` service. + ### Kind `IngressRouteTCP` `IngressRouteTCP` is the CRD implementation of a [Traefik TCP router](../routers/index.md#configuring-tcp-routers). @@ -1192,7 +1339,7 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube port: 8081 weight: 10 ``` - + ### Kind: `TLSOption` `TLSOption` is the CRD implementation of a [Traefik "TLS Option"](../../https/tls.md#tls-options). @@ -1386,6 +1533,7 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0= ``` + ## Further Also see the [full example](../../user-guides/crd-acme/index.md) with Let's Encrypt. diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index 1f8abc936..8211b0cd0 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -167,8 +167,12 @@ For now, only round robin load balancing is supported: #### Sticky sessions -When sticky sessions are enabled, a cookie is set on the initial request to track which server handles the first response. -On subsequent requests, the client is forwarded to the same server. +When sticky sessions are enabled, a cookie is set on the initial request and response to let the client know which server handles the first response. +On subsequent requests, to keep the session alive with the same server, the client should resend the same cookie. + +!!! info "Stickiness on multiple levels" + + When chaining or mixing load-balancers (e.g. a load-balancer of servers is one of the "children" of a load-balancer of services), for stickiness to work all the way, the option needs to be specified at all required levels. Which means the client needs to send a cookie with as many key/value pairs as there are sticky levels. !!! info "Stickiness & Unhealthy Servers" @@ -226,6 +230,80 @@ On subsequent requests, the client is forwarded to the same server. httpOnly: true ``` +??? example "Setting Stickiness on all the required levels -- Using the [File Provider](../../providers/file.md)" + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.wrr1] + [http.services.wrr1.weighted.sticky.cookie] + name = "lvl1" + [[http.services.wrr1.weighted.services]] + name = "whoami1" + weight = 1 + [[http.services.wrr1.weighted.services]] + name = "whoami2" + weight = 1 + + [http.services.whoami1] + [http.services.whoami1.loadBalancer] + [http.services.whoami1.loadBalancer.sticky.cookie] + name = "lvl2" + [[http.services.whoami1.loadBalancer.servers]] + url = "http://127.0.0.1:8081" + [[http.services.whoami1.loadBalancer.servers]] + url = "http://127.0.0.1:8082" + + [http.services.whoami2] + [http.services.whoami2.loadBalancer] + [http.services.whoami2.loadBalancer.sticky.cookie] + name = "lvl2" + [[http.services.whoami2.loadBalancer.servers]] + url = "http://127.0.0.1:8083" + [[http.services.whoami2.loadBalancer.servers]] + url = "http://127.0.0.1:8084" + ``` + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + wrr1: + weighted: + sticky: + cookie: + name: lvl1 + services: + - name: whoami1 + weight: 1 + - name: whoami2 + weight: 1 + + whoami1: + loadBalancer: + sticky: + cookie: + name: lvl2 + servers: + - url: http://127.0.0.1:8081 + - url: http://127.0.0.1:8082 + + whoami2: + loadBalancer: + sticky: + cookie: + name: lvl2 + servers: + - url: http://127.0.0.1:8083 + - url: http://127.0.0.1:8084 + ``` + + To keep a session open with the same server, the client would then need to specify the two levels within the cookie for each request, e.g. with curl: + + ``` + curl -b "lvl1=whoami1; lvl2=http://127.0.0.1:8081" http://localhost:8000 + ``` + #### Health Check Configure health check to remove unhealthy servers from the load balancing rotation. diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index e9d917d2a..8b14a5bd4 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -101,7 +101,7 @@ curl [-k] https://your.example.com/tls ``` ```bash -curl [-k] http://your.example.com:8000/notls +curl http://your.example.com:8000/notls ``` Note that you'll have to use `-k` as long as you're using the staging server of Let's Encrypt, since it is not an authorized certificate authority on systems where it hasn't been manually added. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index a8b6eae22..9cc3face3 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -1,9 +1,6 @@ package v1alpha1 import ( - "errors" - "fmt" - "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -82,30 +79,6 @@ type LoadBalancerSpec struct { Weight *int `json:"weight,omitempty"` } -// IsServersLB reports whether lb is a load-balancer of servers -// (as opposed to a traefik load-balancer of services). -func (lb LoadBalancerSpec) IsServersLB() (bool, error) { - if lb.Name == "" { - return false, errors.New("missing Name field in service") - } - if lb.Kind == "" || lb.Kind == "Service" { - return true, nil - } - if lb.Kind != "TraefikService" { - return false, fmt.Errorf("invalid kind value: %v", lb.Kind) - } - if lb.Port != 0 || - lb.Scheme != "" || - lb.HealthCheck != nil || - lb.Strategy != "" || - lb.PassHostHeader != nil || - lb.ResponseForwarding != nil || - lb.Sticky != nil { - return false, fmt.Errorf("service of kind %v is incompatible with Kubernetes Service related fields", lb.Kind) - } - return false, nil -} - // Service defines an upstream to proxy traffic. type Service struct { LoadBalancerSpec