Kubernetes Ingress Path Override

Suppose we have an old website and page by page rewriting it to something new

  • We have some project demo.mac-blog.org.ua
  • It has few endpoints:
    • /
    • /about/
    • /about/company/
    • /contacts/

Lets deploy old.yml (simple deployment, nothing special)

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: old
data:
  index.html: "HOME OLD\n"
  about.html: "ABOUT OLD\n"
  company.html: "ABOUT COMPANY OLD\n"
  contacts.html: "CONTACTS OLD\n"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: old
  labels:
    app: old
spec:
  selector:
    matchLabels:
      app: old
  template:
    metadata:
      labels:
        app: old
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - name: old
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
            memory: 500Mi
          requests:
            cpu: 50m
            memory: 100Mi
        volumeMounts:
          - name: old-home
            mountPath: /usr/share/nginx/html
          - name: old-about
            mountPath: /usr/share/nginx/html/about
          - name: old-about-company
            mountPath: /usr/share/nginx/html/about/company
          - name: old-contacts
            mountPath: /usr/share/nginx/html/contacts
      volumes:
        - name: old-home
          configMap:
            name: old
            items:
              - key: index.html
                path: index.html
        - name: old-about
          configMap:
            name: old
            items:
              - key: about.html
                path: index.html
        - name: old-about-company
          configMap:
            name: old
            items:
              - key: company.html
                path: index.html
        - name: old-contacts
          configMap:
            name: old
            items:
              - key: contacts.html
                path: index.html
---
apiVersion: v1
kind: Service
metadata:
  name: old
spec:
  type: ClusterIP
  selector:
    app: old
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: old
spec:
  ingressClassName: external
  rules:
  - host: demo.mac-blog.org.ua
    http:
      paths:
      - backend:
          service:
            name: old
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific

And check it inside our cluster

kubectl apply -f old.yml

kubectl get po -l app=old

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/
# HOME OLD

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/about/
# ABOUT OLD

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/about/company/
# ABOUT COMPANY OLD

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/contacts/
# CONTACTS OLD

Ingress allows us to override upstream services by url prefix

Lets imagine that our new version of web site has only "about" page ready

Deploy new.yml (once again nothing fancy, usual deployment, BUT, it has the same domain as in old.yml, and path is changed from / to /about)

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: new
data:
  about.html: "ABOUT NEW\n"
  company.html: "ABOUT COMPANY NEW\n"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: new
  labels:
    app: new
spec:
  selector:
    matchLabels:
      app: new
  template:
    metadata:
      labels:
        app: new
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - name: new
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
            memory: 500Mi
          requests:
            cpu: 50m
            memory: 100Mi
        volumeMounts:
          - name: new-about
            mountPath: /usr/share/nginx/html/about
          - name: new-about-company
            mountPath: /usr/share/nginx/html/about/company
      volumes:
        - name: new-about
          configMap:
            name: new
            items:
              - key: about.html
                path: index.html
        - name: new-about-company
          configMap:
            name: new
            items:
              - key: company.html
                path: index.html
---
apiVersion: v1
kind: Service
metadata:
  name: new
spec:
  type: ClusterIP
  selector:
    app: new
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: new
spec:
  ingressClassName: external
  rules:
  # POI: DNS is the same
  - host: demo.mac-blog.org.ua
    http:
      paths:
      - backend:
          service:
            name: new
            port:
              number: 80
        # POI: but path is different
        path: /about
        pathType: ImplementationSpecific

kubectl apply -f new.yml

kubectl get po -l app=new

kubectl get ing | grep -E "old|new"

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/
# HOME OLD <- still served by old service

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/about/
# ABOUT NEW <- served by new service

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/about/company/
# ABOUT COMPANY NEW <- served by new service

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/contacts/
# CONTACTS OLD <- still served by old service

If we do not want to serve all about sub routes, but only about page, for that we may change path type to exact, more details about this setting can be found here https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types

Lets deploy next variation new2.yml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: new
data:
  about.html: "ABOUT NEW\n"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: new
  labels:
    app: new
spec:
  selector:
    matchLabels:
      app: new
  template:
    metadata:
      labels:
        app: new
    spec:
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - name: new
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
            memory: 500Mi
          requests:
            cpu: 50m
            memory: 100Mi
        volumeMounts:
          - name: new-about
            mountPath: /usr/share/nginx/html/about
      volumes:
        - name: new-about
          configMap:
            name: new
            items:
              - key: about.html
                path: index.html
---
apiVersion: v1
kind: Service
metadata:
  name: new
spec:
  type: ClusterIP
  selector:
    app: new
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: new
spec:
  ingressClassName: external
  rules:
  # POI: DNS is the same
  - host: demo.mac-blog.org.ua
    http:
      paths:
      - backend:
          service:
            name: new
            port:
              number: 80
        # POI: but path is different
        path: /about/
        # POI: changed from "ImplementationSpecific" to "Exact"
        pathType: Exact

kubectl apply -f new.yml

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/
# HOME OLD <- still served by old service, ok

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/about/
# ABOUT NEW <- served by new service, what we want

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/about/company/
# ABOUT COMPANY OLD <- what we wanted, only concrete endpoint is served by new service

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/contacts/
# CONTACTS OLD <- still served by old service

Note that if we delete this new ingress, requests will still work and will be served by old implementation

kubectl delete ing,svc,deployment new

curl --resolve demo.mac-blog.org.ua:80:20.13.179.68 http://demo.mac-blog.org.ua/about/
# ABOUT OLD <- expected, we have removed new.yml which did override that

To cleanup everything

kubectl delete ing,svc,deployment old

Ingress to AWS S3

Here is how we can create something similar to AWS S3 static site with help of Ingress, but even more, we are going to serve single file on main domain

# Note: our service points to another domain instead of pods
---
apiVersion: v1
kind: Service
metadata:
  name: s3
spec:
  type: ExternalName
  externalName: s3.eu-central-1.amazonaws.com
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: s3
  annotations:
    # POI: rewrite target, can use regex, simplified for demo
    nginx.ingress.kubernetes.io/rewrite-target: /static.mac-blog.org.ua/sitemap.xml
    # POI: optional, because of host S3 works we override host as well
    nginx.ingress.kubernetes.io/upstream-vhost: s3.eu-central-1.amazonaws.com
spec:
  ingressClassName: external
  rules:
  # POI: main website
  - host: mac-blog.org.ua
    http:
      paths:
      - backend:
          service:
            name: s3
            port:
              number: 80
        # POI: we want to serve only this path
        path: /sitemap.xml
        pathType: ImplementationSpecific

Ingress to external service

Also we may configure everything in a such a way so it does not matter if for example our old site is running outside Kubernetes

ingress external ip

With all such approaches Ingress may become single entry point for everything which has its benefits, especially after tuning its compression