In the migration journey to the cloud, not all apps are stateless immediately. Sometimes, we still need the session affinity or sticky session for the request to come to the same pod replica that was responding to the request before.

Nginx ingress controller

The kubernetes ingress controllers, such as Nginx ingress controller already has these requirement considered and implemented. The ingress controller replies the response with a Set-Cookie header to the first request. The value of the cookie will map to a specific pod replica. When the subsequent request come back again, the client browser will attach the cookie and the ingress controller is therefore able to route the traffic to the same pod replica.

The configuration is normally achieved with annotations. See the following example from https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/ingress.yaml.

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

name: nginx-test

annotations:

nginx.ingress.kubernetes.io/affinity: "cookie"

nginx.ingress.kubernetes.io/session-cookie-name: "route"

nginx.ingress.kubernetes.io/session-cookie-expires: "172800"

nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"



spec:

rules:

- host: stickyingress.example.com

http:

paths:

- backend:

serviceName: http-svc

servicePort: 80

path: /

By setting the annotations in the ingress object, we can make sure the subsequent request will still be served by the same pod.

Traefik ingress controller on K3s

K3s comes by default with the Traefik controller. I will demonstrate the session affinity with it.

Create a sample app

Let's have a toy golang app, whose HTTP handler will just print out the hostname.

package main import (

"fmt"

"net/http"

"os"

) func greet(w http.ResponseWriter, r *http.Request) {

fmt.Fprintf(w, "You are served by host:%s.", os.Getenv("HOSTNAME"))

} func main() {

http.HandleFunc("/", greet)

http.ListenAndServe(":8080", nil)

}

Compile it, build a Docker image and push into docker hub. Attached is the Dockerfile

FROM alpine

RUN mkdir -p /myapp

ADD myapp /myapp

RUN chmod a+rx /myapp/myapp CMD ["/myapp/myapp"]

Deploy into K3s

Nothing special, just export the KUBECONFIG, you have the familiar kubectl the same as K8s.

export KUBECONFIG=~/k3s/k3s.yaml

Apply the following sample Kubernetes object files, including ingress, service, and deployment.

---

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

annotations:

kubernetes.io/ingress.class: traefik

labels:

app: session-affinity

name: session-affinity

spec:

rules:

- host: sess.192.168.64.5.nip.io

http:

paths:

- path: /

backend:

serviceName: session-affinity

servicePort: 8080

---

apiVersion: v1

kind: Service

metadata:

name: session-affinity

labels:

app: session-affinity

annotations:

traefik.ingress.kubernetes.io/affinity: "true"

traefik.ingress.kubernetes.io/session-cookie-name: "sticky"

spec:

type: NodePort

ports:

- port: 8080

targetPort: 8080

protocol: TCP

name: http

selector:

app: session-affinity-demo ---

apiVersion: apps/v1

kind: Deployment

metadata:

name: session-affinity-demo

labels:

app: session-affinity-demo

spec:

replicas: 3

selector:

matchLabels:

app: session-affinity-demo

template:

metadata:

labels:

app: session-affinity-demo

spec:

containers:

- name: session-affinity-demo

image: zhiminwen/session-sticky:v1.0

In the ingress object, we instruct it in the annotations to use the Traefik ingress controller. Note the sticky session in Traefik is defined in the Service object with the annotation, which is different comparing with the Nginx ingress controller. Instead of a random cookie name, we define it as “sticky”.

We have 3 replicas running.

Testing

Launch the browser, go to the expected Ingress host, sess.192.168.64.5.nip.io

You can see the greeting message of the hostname. Note upon the very first response, the Set-Cookie is set in the response header.

Refresh the browser, you will see the request is served by the same pod. This was because of the Cookie which was set by the browser.