Ingress

Un LoadBalancer usa un indirizzo pubblico dal pool di indirizzi per ogni servizio configurato.

In un LoadBalancer implementato localmente gli indirizzi del pool possono essere insufficienti. Se il LoadBalancer è fornito dal Cloud Provider, ogni sua istanza ha un costo.

Ingress espone servizi multipli tramite un unico Load Balancer.

Ingress01

Ha due componenti:

  • Ingress controller
  • Ingress object

Molte installazioni di cluster Kubernetes, come Kind, non forniscono un Ingress Controller di serie, quindi va installato.

Per l'uso di un cluster Kubernetes di uno specifico Cloud Controller, leggere la documentazione appropriata.

Ingress su Kind

Kind supporta tre Ingress Controllers:

  • Contour
  • Ingress Kong
  • Ingress Nginx

Useremo qui Ingress Nginx-

Modifica del Cluster Kind

Occorre modificare la costruzione del cluster.

Cancellare il cluster corrente con la nostra procedura shell:

cd
./teardown,sh

Creiamo una nuova procedura shell di setup del cluster chiamata ingress.sh:

vim ingerss.sh
#!/bin/sh
set -o errexit

# crea il contenitore del registry se non esiste
reg_name='kind-registry'
reg_port='5000'
if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then
  docker run \
    -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \
    -v $HOME/.docker/registry:/var/lib/registry registry:2
fi

# crea un cluster con il registry locale abilitato in containerd
cat <<EOF | kind create cluster --image kindest/node:v1.24.0 --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
    endpoint = ["http://${reg_name}:5000"]
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
  extraMounts:
    - hostPath: /data
      containerPath: /data
- role: worker
  extraMounts:
    - hostPath: /data
      containerPath: /data
- role: worker
  extraMounts:
    - hostPath: /data
      containerPath: /data
EOF

# connette il registry alla rete del cluster se non connesso
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then
  docker network connect "kind" "${reg_name}"
fi

# Documenta il local registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: local-registry-hosting
  namespace: kube-public
data:
  localRegistryHosting.v1: |
    host: "localhost:${reg_port}"
    help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF

La sezione aggiunta, dopo la linea - role: control-plane è:

  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP

Rendiamola eseguibile:

chmod +x ingress.sh

Copiamo la procedura di setup in una nuova:

cp setup.sh setup-ingress.sh

Modifichiamo la nuova procedura setup-ingress.sh sostituendo le linee:

echo "~/std.sh"
~/std.sh

con

echo "~/ingress.sh"
~/ingress.sh

Lanciamo il nuovo cluster che supporta Ingress:

./setup-ingress.sh

Deployment di Ingress

Copiamo dalla rete il Deployment del controller di Ingress e poniamolo nella directory ~/scripts:

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
mv deploy.yaml ~/scripts/ingress-deploy.yml

Eseguiamo il deployment e attendiamo che sia pronto:

kubectl apply -f ~/scripts/ingress-deploy.yml
echo Waiting up to 120s for nginx-ingress pod ...
kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=120s

Test di Ingress

Prepariamo un Manifest di test:

vim ~/scripts/nginx-test.yml
kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  labels:
    app: foo
spec:
  containers:
  - command:
    - /agnhost
    - netexec
    - --http-port
    - "8080"
    image: registry.k8s.io/e2e-test-images/agnhost:2.39
    name: foo-app
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
spec:
  selector:
    app: foo
  ports:
  # Default port used by the image
  - port: 8080
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  labels:
    app: bar
spec:
  containers:
  - command:
    - /agnhost
    - netexec
    - --http-port
    - "8080"
    image: registry.k8s.io/e2e-test-images/agnhost:2.39
    name: bar-app
---
kind: Service
apiVersion: v1
metadata:
  name: bar-service
spec:
  selector:
    app: bar
  ports:
  # Default port used by the image
  - port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - http:
      paths:
      - pathType: ImplementationSpecific
        path: /foo(/|$)(.*)
        backend:
          service:
            name: foo-service
            port:
              number: 8080
      - pathType: ImplementationSpecific
        path: /bar(/|$)(.*)
        backend:
          service:
            name: bar-service
            port:
              number: 8080

Lo sottomettiamo:

kubectl apply -f ~/scripts/nginx-test.yml

E attendiamo che i pod vengano creati.

Connessioni di prova:

curl localhost/foo/hostname
foo-app
curl localhost/bar/hostname
bar-app

Al termine cancelliamo dal manifest:

kubectl delete -f ~/scripts/nginx-test.yml

Altro Esempio

Il seguente esempio è preso dal libro The Kubernetes Book di Nigel Poulton.

Abbiamo un Manifest con due pod e due servizi:

mkdir -p ~/scripts/ig
vim ~/scripts/ig/app.yml
apiVersion: v1
kind: Service
metadata:
  name: svc-shield
spec:
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    env: shield
---
apiVersion: v1
kind: Service
metadata:
  name: svc-hydra
spec:
  type: ClusterIP
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    env: hydra
---
apiVersion: v1
kind: Pod
metadata:
  name: shield
  labels:
    env: shield
spec:
  containers:
  - image: nigelpoulton/k8sbook:shield-ingress
    name: shield-ctr
    ports:
    - containerPort: 8080
    imagePullPolicy: Always
---
apiVersion: v1
kind: Pod
metadata:
  name: hydra
  labels:
    env: hydra
spec:
  containers:
  - image: nigelpoulton/k8sbook:hydra-ingress
    name: hydra-ctr
    ports:
    - containerPort: 8080
    imagePullPolicy: Always

Sottomettiamo il manifest:

kubectl apply -f ~/scripts/ig/app.yml

Abbiamo due possibilitè per Ingress:

  • Host Based Routing - il routing al servizio avviene sulla base dello host di destinazione
  • Path Based Routing - il routing al servizio avviene sulla base del Path nella URL

Nell'esercizio la situazione è:

  • Host-based: shield.mcu.com >> svc-shield
  • Host-based: hydra.mcu.com >> svc-hydra
  • Path-based: mcu.com/shield >> svc-shield
  • Path-based: mcu.com/hydra >> svc-hydra

Ingroute

L'oggetto Ingress viene specificato dal manifest:

vim ~/scripts/ig/ig-all.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mcu-all
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: shield.mcu.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-shield
            port:
              number: 8080
  - host: hydra.mcu.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-hydra
            port:
              number: 8080
  - host: mcu.com
    http:
      paths:
      - path: /shield
        pathType: Prefix
        backend:
          service:
            name: svc-shield
            port:
              number: 8080
      - path: /hydra
        pathType: Prefix
        backend:
          service:
            name: svc-hydra
            port:
              number: 8080

Note

L'annotazione nginx.ingress.kubernetes.io/rewrite-target: / e' indispensabile per permettere a Ingress il reindirizzamento della richiesta.

E' un esempio di Annotation interpretata da un Controller specifico.

La specifica ingressClassName: nginx non è indispensabile se sul nostro cluster abbiamo solo lo Nginx Ingress Controller.

Qualora vi fossero più Ingress controller dovremmo configurare una classe per ciascuno, per esempio col Manifest:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: igc-nginx
spec:
  controller: nginx.org/ingress-controller

Sottoponiamo il manifest:

kubectl apply -f ~/scripts/ig/ig-all.yml

Ispezione degli oggetti Ingress:

kubectl get ing

Descrizione di un oggetto Ingress:

kubectl describe ing mcu-all

Notare la sezione Regole:

Rules:
  Host            Path  Backends
  ----            ----  --------
  shield.mcu.com  
                  /   svc-shield:8080 (10.244.1.10:8080)
  hydra.mcu.com   
                  /   svc-hydra:8080 (10.244.2.5:8080)
  mcu.com         
                  /shield   svc-shield:8080 (10.244.1.10:8080)
                  /hydra    svc-hydra:8080 (10.244.2.5:8080)

Notare anche il campo Address: localhost

Modificare il file /etc/hosts aggiungendo le linee:

127.0.0.1 shield.mcu.com
127.0.0.1 hydra.mcu.com
127.0.0.1 mcu.com

Aprire un browser e testare i seguenti URL:

  • shield.mcu.com
  • hydra.mcu.com
  • mcu.com/shield
  • mcu.com/hydra

Al termine ripulire:

kubectl delete -f ~/scripts/ig/ig-all.yml
kubectl delete -f ~/scripts/ig/app.yml

Rimettere anche a posto /etc/hosts.