From 633f3f6e469061b4b83fad3dd0c72536ac0717cf Mon Sep 17 00:00:00 2001 From: Paul Harkink Date: Sat, 28 Feb 2026 15:28:39 +0100 Subject: [PATCH] feat(ex03): MetalLB + Ingress-Nginx + podinfo ingress - apps/networking/metallb.yaml: MetalLB Helm app (chart 0.14.9) - apps/networking/metallb-config.yaml: IPAddressPool + L2Advertisement CRDs - apps/networking/ingress-nginx.yaml: Ingress-Nginx Helm app (chart 4.12.0) - manifests/networking/metallb/: values.yaml + metallb-config.yaml (pool 192.168.56.200-220) - manifests/networking/ingress-nginx/values.yaml: LB IP 192.168.56.200, class nginx - manifests/apps/podinfo/ingress.yaml: podinfo.192.168.56.200.nip.io - docs/03-metallb-ingress.md: Exercise 03 participant guide - manifests/argocd/values.yaml: ArgoCD ingress block commented (enabled in Ex03) --- apps/networking/ingress-nginx.yaml | 28 +++ apps/networking/metallb-config.yaml | 24 +++ apps/networking/metallb.yaml | 29 +++ docs/03-metallb-ingress.md | 166 ++++++++++++++++++ manifests/apps/podinfo/ingress.yaml | 20 +++ .../networking/ingress-nginx/values.yaml | 17 ++ .../networking/metallb/metallb-config.yaml | 20 +++ manifests/networking/metallb/values.yaml | 8 + 8 files changed, 312 insertions(+) create mode 100644 apps/networking/ingress-nginx.yaml create mode 100644 apps/networking/metallb-config.yaml create mode 100644 apps/networking/metallb.yaml create mode 100644 docs/03-metallb-ingress.md create mode 100644 manifests/apps/podinfo/ingress.yaml create mode 100644 manifests/networking/ingress-nginx/values.yaml create mode 100644 manifests/networking/metallb/metallb-config.yaml create mode 100644 manifests/networking/metallb/values.yaml diff --git a/apps/networking/ingress-nginx.yaml b/apps/networking/ingress-nginx.yaml new file mode 100644 index 0000000..502b047 --- /dev/null +++ b/apps/networking/ingress-nginx.yaml @@ -0,0 +1,28 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ingress-nginx + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "3" +spec: + project: workshop + sources: + - repoURL: https://kubernetes.github.io/ingress-nginx + chart: ingress-nginx + targetRevision: "4.12.0" + helm: + valueFiles: + - $values/manifests/networking/ingress-nginx/values.yaml + - repoURL: https://github.com/innspire/ops-demo.git + targetRevision: HEAD + ref: values + destination: + server: https://kubernetes.default.svc + namespace: ingress-nginx + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/networking/metallb-config.yaml b/apps/networking/metallb-config.yaml new file mode 100644 index 0000000..1c8c89f --- /dev/null +++ b/apps/networking/metallb-config.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: metallb-config + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + project: workshop + source: + repoURL: https://github.com/innspire/ops-demo.git + targetRevision: HEAD + path: manifests/networking/metallb + directory: + include: "metallb-config.yaml" + destination: + server: https://kubernetes.default.svc + namespace: metallb-system + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/networking/metallb.yaml b/apps/networking/metallb.yaml new file mode 100644 index 0000000..500ffeb --- /dev/null +++ b/apps/networking/metallb.yaml @@ -0,0 +1,29 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: metallb + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" +spec: + project: workshop + sources: + - repoURL: https://metallb.github.io/metallb + chart: metallb + targetRevision: "0.14.9" + helm: + valueFiles: + - $values/manifests/networking/metallb/values.yaml + - repoURL: https://github.com/innspire/ops-demo.git + targetRevision: HEAD + ref: values + destination: + server: https://kubernetes.default.svc + namespace: metallb-system + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/docs/03-metallb-ingress.md b/docs/03-metallb-ingress.md new file mode 100644 index 0000000..e718ad3 --- /dev/null +++ b/docs/03-metallb-ingress.md @@ -0,0 +1,166 @@ +# Exercise 03 — MetalLB + Ingress-Nginx (LAN exposure) + +**Time**: ~45 min +**Goal**: Expose podinfo and the ArgoCD UI on a real LAN IP — accessible from your laptop's browser without any port-forward. + +--- + +## What you'll learn +- What MetalLB is and why you need it in a bare-metal / local Kubernetes cluster +- How a LoadBalancer service gets a real IP via L2 ARP +- How Ingress-Nginx routes HTTP traffic by hostname +- `nip.io` — a public wildcard DNS service for local development + +--- + +## Background + +In cloud Kubernetes (EKS, GKE, AKS), `type: LoadBalancer` automatically provisions a cloud load balancer with a public IP. On bare metal or local VMs, nothing does that — so pods stay unreachable. + +**MetalLB** fills that gap: it watches for `LoadBalancer` services and assigns IPs from a pool you define. In L2 mode it uses ARP to answer "who has 192.168.56.200?" — so your laptop routes directly to the VM. + +**Ingress-Nginx** is a single LoadBalancer service that MetalLB gives one IP. All your apps share that IP — Nginx routes to the right service based on the `Host:` header. + +**nip.io** is a public DNS wildcard: `anything.192.168.56.200.nip.io` resolves to `192.168.56.200`. No `/etc/hosts` editing needed. + +--- + +## Steps + +### 1. Enable MetalLB + +The ArgoCD Application manifests for MetalLB are already in this repo. The root +App-of-Apps watches the `apps/` directory, which includes `apps/networking/`. +They are already being applied — MetalLB just needs a moment to become healthy. + +Check MetalLB is running: + +```bash +kubectl get pods -n metallb-system +# NAME READY STATUS RESTARTS AGE +# controller-xxx 1/1 Running 0 Xm +# speaker-xxx 1/1 Running 0 Xm +``` + +Check the IP pool is configured: + +```bash +kubectl get ipaddresspool -n metallb-system +# NAME AUTO ASSIGN AVOID BUGGY IPS ADDRESSES +# workshop-pool true false ["192.168.56.200-192.168.56.220"] +``` + +--- + +### 2. Enable Ingress-Nginx + +Similarly, `apps/networking/ingress-nginx.yaml` is already in the repo. Wait for it +to become Synced in ArgoCD, then: + +```bash +kubectl get svc -n ingress-nginx +# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) +# ingress-nginx-controller LoadBalancer 10.43.x.x 192.168.56.200 80:xxx,443:xxx +``` + +The `EXTERNAL-IP` column shows `192.168.56.200`. MetalLB assigned it. + +From your **laptop** (not the VM), verify: + +```bash +curl http://192.168.56.200 +# 404 from Nginx — correct! No ingress rule yet, but Nginx is reachable. +``` + +--- + +### 3. Add a podinfo Ingress + +The Ingress resource is already in `manifests/apps/podinfo/ingress.yaml`. +ArgoCD will sync it automatically. After sync: + +```bash +kubectl get ingress -n podinfo +# NAME CLASS HOSTS ADDRESS PORTS +# podinfo nginx podinfo.192.168.56.200.nip.io 192.168.56.200 80 +``` + +Open from your **laptop browser**: **http://podinfo.192.168.56.200.nip.io** + +You should see the podinfo UI with version 6.6.2. + +--- + +### 4. Enable the ArgoCD ingress + +Now let's expose ArgoCD itself on a nice URL. Open `manifests/argocd/values.yaml` +and find the commented-out ingress block near the `server:` section: + +```yaml + # ── Exercise 03: uncomment this block after Ingress-Nginx is deployed ────── + # ingress: + # enabled: true + # ... +``` + +Uncomment the entire block (remove the `#` characters): + +```yaml + ingress: + enabled: true + ingressClassName: nginx + hostname: argocd.192.168.56.200.nip.io + annotations: + nginx.ingress.kubernetes.io/ssl-passthrough: "false" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +``` + +Commit and push: + +```bash +git add manifests/argocd/values.yaml +git commit -m "feat(ex03): enable ArgoCD ingress" +git push +``` + +ArgoCD will detect the change, upgrade its own Helm release, and create the Ingress. +Within a minute or two: + +```bash +kubectl get ingress -n argocd +# NAME CLASS HOSTS ADDRESS +# argocd-server nginx argocd.192.168.56.200.nip.io 192.168.56.200 +``` + +Open from your laptop: **http://argocd.192.168.56.200.nip.io** + +--- + +## Expected outcome + +| URL | App | +|-----|-----| +| http://podinfo.192.168.56.200.nip.io | podinfo v6.6.2 | +| http://argocd.192.168.56.200.nip.io | ArgoCD UI | + +Both accessible from your laptop without any port-forward. + +--- + +## Troubleshooting + +| Symptom | Fix | +|---------|-----| +| `EXTERNAL-IP` is `` on ingress-nginx svc | MetalLB not ready yet — check `kubectl get pods -n metallb-system` | +| Curl to 192.168.56.200 times out from laptop | VirtualBox host-only adapter not configured; check `VBoxManage list hostonlyifs` | +| `nip.io` doesn't resolve | Temporary DNS issue; try again or use `/etc/hosts` with `192.168.56.200 podinfo.local` | +| ArgoCD ingress gives 502 | Wait for ArgoCD to restart after values change; ArgoCD now runs in insecure (HTTP) mode | + +--- + +## What's next + +In Exercise 04 you'll build a Tekton pipeline that: +1. Validates manifests +2. Bumps the podinfo image tag from `6.6.2` to `6.7.0` in `deployment.yaml` +3. Pushes the commit — and ArgoCD picks it up automatically diff --git a/manifests/apps/podinfo/ingress.yaml b/manifests/apps/podinfo/ingress.yaml new file mode 100644 index 0000000..dc2efee --- /dev/null +++ b/manifests/apps/podinfo/ingress.yaml @@ -0,0 +1,20 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: podinfo + namespace: podinfo + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: nginx + rules: + - host: podinfo.192.168.56.200.nip.io + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: podinfo + port: + name: http diff --git a/manifests/networking/ingress-nginx/values.yaml b/manifests/networking/ingress-nginx/values.yaml new file mode 100644 index 0000000..9ae8582 --- /dev/null +++ b/manifests/networking/ingress-nginx/values.yaml @@ -0,0 +1,17 @@ +# Ingress-Nginx Helm values +# The controller's LoadBalancer service will get 192.168.56.200 from MetalLB. +# All workshop ingresses use IngressClass "nginx". +controller: + ingressClassResource: + name: nginx + default: true + + service: + type: LoadBalancer + # Request a specific IP so docs can reference it reliably + loadBalancerIP: "192.168.56.200" + + resources: + requests: + cpu: 100m + memory: 128Mi diff --git a/manifests/networking/metallb/metallb-config.yaml b/manifests/networking/metallb/metallb-config.yaml new file mode 100644 index 0000000..cf7c298 --- /dev/null +++ b/manifests/networking/metallb/metallb-config.yaml @@ -0,0 +1,20 @@ +# MetalLB L2 configuration +# IP pool: 192.168.56.200–192.168.56.220 (VirtualBox host-only subnet) +# First IP (200) will be claimed by Ingress-Nginx LoadBalancer service. +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: workshop-pool + namespace: metallb-system +spec: + addresses: + - 192.168.56.200-192.168.56.220 +--- +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: workshop-l2 + namespace: metallb-system +spec: + ipAddressPools: + - workshop-pool diff --git a/manifests/networking/metallb/values.yaml b/manifests/networking/metallb/values.yaml new file mode 100644 index 0000000..121aaff --- /dev/null +++ b/manifests/networking/metallb/values.yaml @@ -0,0 +1,8 @@ +# MetalLB Helm values +# No special configuration needed at chart level; +# IP pool is configured via metallb-config.yaml (CRDs). +speaker: + tolerations: + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule