diff --git a/k8s/traefik-v1.yaml b/k8s/traefik-v1.yaml
deleted file mode 100644
index 1bb8d619..00000000
--- a/k8s/traefik-v1.yaml
+++ /dev/null
@@ -1,87 +0,0 @@
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
- name: traefik-ingress-controller
- namespace: kube-system
----
-kind: DaemonSet
-apiVersion: apps/v1
-metadata:
- name: traefik-ingress-controller
- namespace: kube-system
- labels:
- k8s-app: traefik-ingress-lb
-spec:
- selector:
- matchLabels:
- k8s-app: traefik-ingress-lb
- template:
- metadata:
- labels:
- k8s-app: traefik-ingress-lb
- name: traefik-ingress-lb
- spec:
- tolerations:
- - effect: NoSchedule
- operator: Exists
- hostNetwork: true
- serviceAccountName: traefik-ingress-controller
- terminationGracePeriodSeconds: 60
- containers:
- - image: traefik:1.7
- name: traefik-ingress-lb
- ports:
- - name: http
- containerPort: 80
- hostPort: 80
- - name: admin
- containerPort: 8080
- hostPort: 8080
- securityContext:
- capabilities:
- drop:
- - ALL
- add:
- - NET_BIND_SERVICE
- args:
- - --api
- - --kubernetes
- - --logLevel=INFO
----
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
- name: traefik-ingress-controller
-rules:
- - apiGroups:
- - ""
- resources:
- - services
- - endpoints
- - secrets
- verbs:
- - get
- - list
- - watch
- - apiGroups:
- - extensions
- resources:
- - ingresses
- verbs:
- - get
- - list
- - watch
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
- name: traefik-ingress-controller
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: traefik-ingress-controller
-subjects:
-- kind: ServiceAccount
- name: traefik-ingress-controller
- namespace: kube-system
diff --git a/k8s/traefik-v2.yaml b/k8s/traefik-v2.yaml
deleted file mode 100644
index e6d113db..00000000
--- a/k8s/traefik-v2.yaml
+++ /dev/null
@@ -1,114 +0,0 @@
----
-apiVersion: v1
-kind: Namespace
-metadata:
- name: traefik
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
- name: traefik
- namespace: traefik
----
-kind: DaemonSet
-apiVersion: apps/v1
-metadata:
- name: traefik
- namespace: traefik
- labels:
- app: traefik
-spec:
- selector:
- matchLabels:
- app: traefik
- template:
- metadata:
- labels:
- app: traefik
- name: traefik
- spec:
- tolerations:
- - effect: NoSchedule
- operator: Exists
- # If, for some reason, our CNI plugin doesn't support hostPort,
- # we can enable hostNetwork instead. That should work everywhere
- # but it doesn't provide the same isolation.
- #hostNetwork: true
- serviceAccountName: traefik
- terminationGracePeriodSeconds: 60
- containers:
- - image: traefik:v2.10
- name: traefik
- ports:
- - name: http
- containerPort: 80
- hostPort: 80
- - name: https
- containerPort: 443
- hostPort: 443
- - name: admin
- containerPort: 8080
- hostPort: 8080
- securityContext:
- capabilities:
- drop:
- - ALL
- add:
- - NET_BIND_SERVICE
- args:
- - --accesslog
- - --api
- - --api.insecure
- - --log.level=INFO
- - --metrics.prometheus
- - --providers.kubernetesingress
- - --entrypoints.http.Address=:80
- - --entrypoints.https.Address=:443
- - --entrypoints.https.http.tls.certResolver=default
----
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
- name: traefik
-rules:
- - apiGroups:
- - ""
- resources:
- - services
- - endpoints
- - secrets
- verbs:
- - get
- - list
- - watch
- - apiGroups:
- - networking.k8s.io
- resources:
- - ingresses
- - ingressclasses
- verbs:
- - get
- - list
- - watch
----
-kind: ClusterRoleBinding
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
- name: traefik
-roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: traefik
-subjects:
-- kind: ServiceAccount
- name: traefik
- namespace: traefik
----
-kind: IngressClass
-apiVersion: networking.k8s.io/v1
-metadata:
- name: traefik
- annotations:
- ingressclass.kubernetes.io/is-default-class: "true"
-spec:
- controller: traefik.io/ingress-controller
diff --git a/k8s/traefik.yaml b/k8s/traefik.yaml
deleted file mode 120000
index 8ac2c242..00000000
--- a/k8s/traefik.yaml
+++ /dev/null
@@ -1 +0,0 @@
-traefik-v2.yaml
\ No newline at end of file
diff --git a/k8s/traefik.yaml b/k8s/traefik.yaml
new file mode 100644
index 00000000..d89174b5
--- /dev/null
+++ b/k8s/traefik.yaml
@@ -0,0 +1,123 @@
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: traefik
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: traefik
+ namespace: traefik
+---
+kind: DaemonSet
+apiVersion: apps/v1
+metadata:
+ name: traefik
+ namespace: traefik
+ labels:
+ app: traefik
+spec:
+ selector:
+ matchLabels:
+ app: traefik
+ template:
+ metadata:
+ labels:
+ app: traefik
+ name: traefik
+ spec:
+ tolerations:
+ - effect: NoSchedule
+ operator: Exists
+ # If, for some reason, our CNI plugin doesn't support hostPort,
+ # we can enable hostNetwork instead. That should work everywhere
+ # but it doesn't provide the same isolation.
+ #hostNetwork: true
+ serviceAccountName: traefik
+ terminationGracePeriodSeconds: 60
+ containers:
+ - image: traefik:v3.5
+ name: traefik
+ ports:
+ - name: http
+ containerPort: 80
+ hostPort: 80
+ - name: https
+ containerPort: 443
+ hostPort: 443
+ - name: admin
+ containerPort: 8080
+ hostPort: 8080
+ securityContext:
+ capabilities:
+ drop:
+ - ALL
+ add:
+ - NET_BIND_SERVICE
+ args:
+ - --accesslog
+ - --api
+ - --api.insecure
+ - --entrypoints.http.Address=:80
+ - --entrypoints.https.Address=:443
+ - --global.sendAnonymousUsage=true
+ - --log.level=INFO
+ - --metrics.prometheus
+ - --providers.kubernetesingress
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: traefik
+rules:
+ - apiGroups:
+ - ""
+ resources:
+ - services
+ - endpoints
+ - secrets
+ - nodes
+ verbs:
+ - get
+ - list
+ - watch
+ - apiGroups:
+ - networking.k8s.io
+ resources:
+ - ingresses
+ - ingressclasses
+ verbs:
+ - get
+ - list
+ - watch
+ - apiGroups:
+ - discovery.k8s.io
+ resources:
+ - endpointslices
+ verbs:
+ - get
+ - list
+ - watch
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: traefik
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: traefik
+subjects:
+- kind: ServiceAccount
+ name: traefik
+ namespace: traefik
+---
+kind: IngressClass
+apiVersion: networking.k8s.io/v1
+metadata:
+ name: traefik
+ annotations:
+ ingressclass.kubernetes.io/is-default-class: "true"
+spec:
+ controller: traefik.io/ingress-controller
diff --git a/slides/k8s/ingress-setup.md b/slides/k8s/ingress-setup.md
new file mode 100644
index 00000000..a48ddad4
--- /dev/null
+++ b/slides/k8s/ingress-setup.md
@@ -0,0 +1,512 @@
+# Setting up an Ingress Controller
+
+- We're going to install Traefik as our Ingress Controller
+
+ - arbitrary choice; but it has a nice dashboard, which is helpful when troubleshooting
+
+ - also, Traefik releases are named after tasty cheeses :)
+
+- We're going to install it using the official Helm chart
+
+ - also an arbitrary choice; but a fairly straightforward one
+
+ - Helm charts can easily fit in other tools (like Flux, ArgoCD, Terraform/OpenTofu...)
+
+- There can be some differences depending on how we want to handle inbound traffic
+
+- Let's review the different possibilities!
+
+---
+
+## Scenario 1: `LoadBalancer` Service
+
+- This is the default option for most Ingress Controllers
+
+ (i.e. what you get if you install their Helm charts without further configuration)
+
+- It requires a cluster where `LoadBalancer` Services are available
+
+ - most cloud-based, managed clusters support `LoadBalancer` Services
+
+ - on-premises clusters can also support them with e.g. [MetalLB] or [kube-vip])
+
+- The Ingress Controller runs with a Deployment
+
+ (typically scaled to at least two replicas, to ensure high availability)
+
+- It is exposed with a `LoadBalancer` Service
+
+---
+
+## Scenario 2: `hostPort`
+
+- This is a good fallback option when `LoadBalancer` Services aren't available
+
+- It typically requires extra configuration steps or options when installing the controller
+
+- It requires a cluster where at least some Nodes have public IP addresses
+
+- The Ingress Controller runs with a DaemonSet
+
+ (potentially with a `nodeSelector` to restrict it to a specific set of nodes)
+
+- The Ingress Controller Pods are exposed by using one (or multiple) `hostPort`
+
+- `hostPort` creates a direct port mapping on the Node, for instance:
+
+ *port 80 on the Node → port 8080 in the Pod*
+
+- It can also create a shorter, faster path to the application Pods
+
+---
+
+## Scenario 3: `hostNetwork`
+
+- This is similar to `hostPort`
+
+ (but a bit less secure)
+
+- Ingress controller Pods run with `hostNetwork: true`
+
+- This lets the Pods use the network stack of the Node that they're running on
+
+- When the Ingress Controller binds to port 80, it means "port 80 on the Node"
+
+- The Ingress Controller must be given permissions to bind to ports below 1024
+
+ (it must either run as root, or leverage `NET_BIND_SERVICE` capability accordingly)
+
+- The Ingress Controller can potentially bind to any port on the Node
+
+ (this might not be desirable!)
+
+---
+
+## Scenario 4: `externalIPs`
+
+- Heads up, this is a rather exotic scenario, but here we go!
+
+- It's possible to [manually assign `externalIPs` to a Service][externalips]
+
+ (including `ClusterIP` services)
+
+- When TCP connections (or UDP packets) destined to an `externalIP` reach a Node...
+
+ ...the Node will forward these connections or packets to the relevant Pods
+
+- This requires manual management of a pool of available `externalIPs`
+
+- It also requires some network engineering so that the traffic reaches the Nodes
+
+ (in other words: just setting `externalIPs` on Services won't be enough!)
+
+- This is how some controllers like [MetalLB] or [kube-vip] operate
+
+[externalips]: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips
+
+---
+
+## Scenario 5: local dev clusters
+
+- Local dev clusters are typically not reachable from the outside world
+
+- They rarely have `LoadBalancer` Services
+
+ (when they do, they use local addresses, and are sometimes limited to a single one)
+
+- Their Nodes might not be directly reachable
+
+ (making the `hostPort` and `hostNetwork` strategies impractical)
+
+- In some cases, it's possible to map a port on our machine to a port on the dev cluster
+
+ (e.g. [KinD has an `extraPortMappings` option][kind-extraportmappings])
+
+- We can also use `kubectl port-forward` to test our local Ingress Controller
+
+[kind-extraportmappings]: https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings
+
+---
+
+class: extra-details
+
+## Local clusters details
+
+- When using Docker-based clusters on Linux:
+
+ *connect directly to the node's IP address (172.X.Y.Z)*
+
+- When using Docker-based clusters with Docker Desktop:
+
+ *set up port mapping (then connect to localhost:XYZ)*
+
+- Generic scenario:
+
+ *run `kubectl port-forward 8888:80` to the Ingress Controller*
+
+ *(and then connect to `http://localhost:8888`)*
+
+---
+
+class: extra-details
+
+## Why not a `NodePort` Service?
+
+- Node ports are typically in the 30000-32767 range
+
+- Web site users don't want to specify port numbers
+
+ (e.g. "connect to https://blahblah.whatever:31550")
+
+- Our Ingress Controller needs to actually be exposed on port 80
+
+ (and 443 if we want to handle HTTPS)
+
+---
+
+## Installing Traefik with a `LoadBalancer`
+
+- We're going to use the official Helm chart
+
+ (https://artifacthub.io/packages/helm/traefik/traefik)
+
+- Its default configuration values should work out of the box
+
+ (as long as our cluster supports `LoadBalancer` Services!)
+
+.lab[
+
+- Install the Traefik Helm chart:
+ ```bash
+ helm upgrade --install --repo https://traefik.github.io/charts \
+ traefik traefik --namespace traefik --create-namespace \
+ --version 37.4.0
+ ```
+
+]
+
+- That's it; now let's send it a test request!
+
+---
+
+## Retrieving the Ingress Controller address
+
+- Our Ingress Controller uses a `LoadBalancer` Service
+
+- We want to obtain that Service's `EXTERNAL-IP`
+
+.lab[
+
+- Retrieve the `EXTERNAL-IP` that has been allocated to the Service:
+ ```bash
+ kubectl get services --namespace=traefik
+ ```
+
+- Send a test request; it should show `404 not found`:
+ ```bash
+ curl http://``
+ ```
+
+]
+
+- Note: that `EXTERNAL-IP` might be `` for a little while before showing up
+
+ (in that case, just try again a few seconds later)
+
+---
+
+class: extra-details
+
+## Scripting it
+
+- If we want to include these steps in a script, here's what we can do!
+
+.lab[
+
+- Use `kubectl wait` to wait until a specific field exists in the resource:
+ ```bash
+ kubectl wait service traefik --namespace=traefik \
+ --for=jsonpath=.status.loadBalancer.ingress
+ ```
+
+- Then extract the IP address:
+ ```bash
+ kubectl get service traefik --namespace=traefik \
+ -o jsonpath={.status.loadBalancer.ingress[0].ip}
+ ```
+
+]
+
+- Note: on some providers like AWS, you might have to use `.hostname` instead of `.ip`
+
+- Note: there might be multiple addresses; the command above returns only the first one
+
+---
+
+class: extra-details
+
+## Make it production-ready
+
+- To improve the availability of our Ingress Controller:
+
+ - configure at least 2 replicas (in case of Node outage)
+
+ - add a `podAntiAffinity` (to make sure Pods are not all in the same place)
+
+ - add a PodDisruptionBudget (to handle planned maintenance, e.g. cluster ugprades)
+
+ - set resource requests and limits for CPU and RAM
+
+- To monitor our Ingress Controller:
+
+ - collect the metrics exposed by Traefik (e.g. with Prometheus)
+
+ - set up some alerting (e.g. with [stakater/IngressMonitorController])
+
+[stakater/IngressMonitorController]: https://github.com/stakater/IngressMonitorController
+
+---
+
+## Installing Traefik with a `DaemonSet` + `hostPort`
+
+- The plan is to run one Traefik Pod on each Node of the cluster
+
+- For that, we need a `DaemonSet` instead of a `Deployment`
+
+- Instead of a `LoadBalancer` Service, we'll use a `hostPort`
+
+ (actually, multiple `hostPort`; at least one for HTTP and one for HTTPS)
+
+- Let's see how to do that with the Traefik Helm chart!
+
+- We'll be looking at the chart's [default values] and [values schema]
+
+[default values]: https://artifacthub.io/packages/helm/traefik/traefik?modal=values
+[values schema]: https://artifacthub.io/packages/helm/traefik/traefik?modal=values-schema
+
+---
+
+## Switching to a `DaemonSet`
+
+- In the chart's [default values], looking for the string `DaemonSet` gives us this:
+ ```yaml
+ deployment:
+ # -- Enable deployment
+ enabled: true
+ # -- Deployment or `DaemonSet`
+ kind: Deployment
+ ```
+
+- This means we need to set `deployment.kind=DaemonSet`!
+
+---
+
+## Using `hostPort`
+
+- In the chart's [default values], we find 3 references mentioning `hostPort`:
+ .small[
+ ```yaml
+ports:
+ traefik:
+ port: 8080
+ # -- Use hostPort if set.
+ `hostPort`: # @schema type:[integer, null]; minimum:0
+ ...
+ web:
+ ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint ...
+ # asDefault: true
+ port: 8000
+ # `hostPort`: 8000
+ ...
+ websecure:
+ ## -- Enable this entrypoint as a default entrypoint. When a service doesn't explicitly set an entrypoint ...
+ # asDefault: true
+ port: 8443
+ `hostPort`: # @schema type:[integer, null]; minimum:0
+ ```
+ ]
+
+- This deserves a small explanation about the Traefik concept of "entrypoint"!
+
+---
+
+## Traefik "entrypoints"
+
+- An entrypoint in Traefik is basically an open port
+
+- Common Traefik configurations will have 3 entrypoints (3 open ports):
+
+ - `web` for HTTP traffic
+
+ - `websecure` for HTTPS traffic
+
+ - `traefik` for Traefik dashboard and API
+
+- We'll set `ports.web.hostPort=80` and `ports.websecure.hostPort=443`
+
+⚠️ Traefik entrypoints are totally unrelated to `ENTRYPOINT` in Dockerfiles!
+
+---
+
+## Traefik Service
+
+- By default, the Helm chart creates a `LoadBalancer` Service
+
+- We don't need that anymore, so we can either:
+
+ - disable it altogether (`service.enabled=false`)
+
+ - switch it to a `ClusterIP` service (`service.type=ClusterIP`)
+
+- Either option is fine!
+
+---
+
+## Putting it all together
+
+- We're going to use a bunch of `--set` flags with all the options that we gathered
+
+- We could also put them in a YAML file and use `--values`
+
+.lab[
+
+- Install Traefik with all our options:
+ ```bash
+ helm upgrade --install --repo https://traefik.github.io/charts \
+ traefik traefik --namespace traefik --create-namespace \
+ --set deployment.kind=DaemonSet \
+ --set ports.web.hostPort=80 \
+ --set ports.websecure.hostPort=443 \
+ --set service.type=ClusterIP \
+ --version 37.4.0
+ ```
+
+]
+
+---
+
+## Testing our Ingress Controller
+
+- We should be able to connect to *any* Node of the cluster, on port 80
+
+.lab[
+
+- Send a test request:
+ ```bash
+ curl http://` 139m
+ ```
+
+- It's also possible to have an IngressClass without a working Ingress Controller
+
+ (e.g. if the controller is broken, or has been partially uninstalled...)
+
+---
+
+## A taxonomy of Ingress Controllers
+
+- Some Ingress Controllers are based on existing load balancers
(HAProxy, NGINX...)
@@ -73,7 +102,56 @@ A few use-cases:
(Contour, Traefik...)
-- Note: there is no "default" or "official" ingress controller!
+- Some are proprietary to a specific hardware or cloud vendor
+
+ (GKE Ingress, AWS ALB Ingress)
+
+- Note: there is no "default" or "official" Ingress Controller!
+
+---
+
+class: extra-details
+
+## Details about these proprietary controllers
+
+- GKE has "[GKE Ingress]", a custom Ingress Controller
+
+ (enabled by default but [does not use IngressClass][gke-ingressclass])
+
+- EKS has "AWS ALB Ingress Controller" as well
+
+ (not enabled by default, requires extra setup)
+
+- They leverage cloud-specific HTTP load balancers
+
+ (GCP HTTP LB, AWS ALB)
+
+- They typically carry a cost *per ingress resource*
+
+[GKE Ingress]: https://cloud.google.com/kubernetes-engine/docs/concepts/ingress
+[gke-ingressclass]: https://docs.cloud.google.com/kubernetes-engine/docs/concepts/ingress#deprecated_annotation
+
+---
+
+class: extra-details
+
+## Single or multiple LoadBalancer
+
+- Most Ingress Controllers will create a LoadBalancer Service
+
+ (and will receive all HTTP/HTTPS traffic through it)
+
+- We need to point our DNS entries to the IP address of that LB
+
+- Some rare Ingress Controllers will allocate one LB per ingress resource
+
+ (example: the GKE Ingress and ALB Ingress mentioned previously)
+
+- This leads to increased costs
+
+- Note that it's possible to have multiple "rules" per ingress resource
+
+ (this will reduce costs but may be less convenient to manage)
---
@@ -103,11 +181,14 @@ A few use-cases:
- etc.
+*Supporting these features in a standard, vendor-independent way, is
+one of the goals of the Gateway API. (More on that at the end of this section!)*
+
---
## Principle of operation
-- Step 1: deploy an *ingress controller*
+- Step 1: deploy an *Ingress Controller*
(one-time setup; typically done by cluster admin)
@@ -115,146 +196,78 @@ A few use-cases:
- maps a domain and/or path to a Kubernetes Service
- - the controller watches ingress resources and sets up a LB
+ - the controller watches Ingress resources and sets up a LB
- Step 3: set up DNS (optional)
- associate DNS entries with the load balancer address
----
+ - this can be automated with [ExternalDNS]
-class: extra-details
-
-## Special cases
-
-- GKE has "[GKE Ingress]", a custom ingress controller
-
- (enabled by default)
-
-- EKS has "AWS ALB Ingress Controller" as well
-
- (not enabled by default, requires extra setup)
-
-- They leverage cloud-specific HTTP load balancers
-
- (GCP HTTP LB, AWS ALB)
-
-- They typically a cost *per ingress resource*
-
-[GKE Ingress]: https://cloud.google.com/kubernetes-engine/docs/concepts/ingress
-
----
-
-class: extra-details
-
-## Single or multiple LoadBalancer
-
-- Most ingress controllers will create a LoadBalancer Service
-
- (and will receive all HTTP/HTTPS traffic through it)
-
-- We need to point our DNS entries to the IP address of that LB
-
-- Some rare ingress controllers will allocate one LB per ingress resource
-
- (example: the GKE Ingress and ALB Ingress mentioned previously)
-
-- This leads to increased costs
-
-- Note that it's possible to have multiple "rules" per ingress resource
-
- (this will reduce costs but may be less convenient to manage)
+[ExternalDNS]: https://github.com/kubernetes-sigs/external-dns
---
## Ingress in action
-- We will deploy the Traefik ingress controller
+- We're going to deploy an Ingress Controller
- - this is an arbitrary choice
+ (unless our cluster already has one that we can use)
- - maybe motivated by the fact that Traefik releases are named after cheeses
+- Then, we will create ingress resources for various HTTP services
-- We will create ingress resources for various HTTP services
+- We'll demonstrate DNS integration as well
-- For DNS, we can use [nip.io](http://nip.io/)
+- If you don't have a domain name for this part, you can use [nip.io]
- - `*.1.2.3.4.nip.io` resolves to `1.2.3.4`
+ (`*.1.2.3.4.nip.io` resolves to `1.2.3.4`)
---
-## Classic ingress controller setup
+## Deploying the Ingress Controller
-- Ingress controller runs with a Deployment
+- Many variations are possible, depending on:
- (with at least 2 replicas for redundancy)
+ - which Ingress Controller we pick
-- It is exposed with a `LoadBalancer` Service
+ - whether `LoadBalancer` Services are available or not
-- Typical for cloud-based clusters
+ - the deployment tool we want to use (Helm, plain YAML...)
-- Also common when running or on-premises with [MetalLB] or [kube-vip]
+- If you're attending a live class, we're going to take a shortcut
-[MetalLB]: https://metallb.org/
-[kube-vip]: https://kube-vip.io/
+ (with a ready-to-use manifest optimized for the clusters we use in class)
+
+- Otherwise, check the section dedicated to Ingress Controller setup first
---
-## Alternate ingress controller setup
+## If you're attending a live class...
-- Ingress controller runs with a DaemonSet
+- Each student is assigned a pre-configured cluster
- (on bigger clusters, this can be coupled with a `nodeSelector`)
+ (sometimes, multiple clusters, to demonstrate different scenarios)
-- It is exposed with `externalIPs`, `hostPort`, or `hostNetwork`
+- We have prepared a YAML manifest that will take care of setting up Traefik for you
-- Typical for on-premises clusters
+.lab[
- (where at least a set of nodes have a stable IP and high availability)
+- Install Traefik on your cluster:
+ ```bash
+ kubectl apply -f ~/container.training/k8s/traefik.yml
+ ```
+
+]
+
+- Note: this YAML manifest is only suitable for live class clusters!
---
class: extra-details
-## Why not a `NodePort` Service?
+## What's about this manifest?
-- Node ports are typically in the 30000-32767 range
-
-- Web site users don't want to specify port numbers
-
- (e.g. "connect to https://blahblah.whatever:31550")
-
-- Our ingress controller needs to actually be exposed on port 80
-
- (and 443 if we want to handle HTTPS)
-
----
-
-class: extra-details
-
-## Local clusters
-
-- When running a local cluster, some extra steps might be necessary
-
-- When using Docker-based clusters on Linux:
-
- *connect directly to the node's IP address (172.X.Y.Z)*
-
-- When using Docker-based clusters with Docker Desktop:
-
- *set up port mapping (then connect to localhost:XYZ)*
-
-- Generic scenario:
-
- *run `kubectl port-forward 8888:80` to the ingress controller*
-
- *(and then connect to `http://localhost:8888`)*
-
----
-
-## Trying it out with Traefik
-
-- We are going to run Traefik with a DaemonSet
+- It runs Traefik with a DaemonSet
(there will be one instance of Traefik on every node of the cluster)
@@ -262,322 +275,215 @@ class: extra-details
- This means that we will be able to connect to any node of the cluster on port 80
----
+- It also includes a *toleration* to make sure Traefik runs on every Node
-## Running Traefik
-
-- The [Traefik documentation][traefikdoc] recommends to use a Helm chart
-
-- For simplicity, we're going to use a custom YAML manifest
-
-- Our manifest will:
-
- - use a Daemon Set so that each node can accept connections
-
- - enable `hostPort: 80`
-
- - add a *toleration* so that Traefik also runs on all nodes
-
-- We could do the same with the official [Helm chart][traefikchart]
-
-[traefikdoc]: https://doc.traefik.io/traefik/getting-started/install-traefik/#use-the-helm-chart
-[traefikchart]: https://artifacthub.io/packages/helm/traefik/traefik
+ (including the control plane Node)
---
-class: extra-details
-
-## Taints and tolerations
-
-- A *taint* is an attribute added to a node
-
-- It prevents pods from running on the node
-
-- ... Unless they have a matching *toleration*
-
-- When deploying with `kubeadm`:
-
- - a taint is placed on the node dedicated to the control plane
-
- - the pods running the control plane have a matching toleration
-
----
-
-class: extra-details
-
-## Checking taints on our nodes
-
-.lab[
-
-- Check our nodes specs:
- ```bash
- kubectl get node node1 -o json | jq .spec
- kubectl get node node2 -o json | jq .spec
- ```
-
-]
-
-We should see a result only for `node1` (the one with the control plane):
-
-```json
- "taints": [
- {
- "effect": "NoSchedule",
- "key": "node-role.kubernetes.io/master"
- }
- ]
-```
-
----
-
-class: extra-details
-
-## Understanding a taint
-
-- The `key` can be interpreted as:
-
- - a reservation for a special set of pods
-
- (here, this means "this node is reserved for the control plane")
-
- - an error condition on the node
-
- (for instance: "disk full," do not start new pods here!)
-
-- The `effect` can be:
-
- - `NoSchedule` (don't run new pods here)
-
- - `PreferNoSchedule` (try not to run new pods here)
-
- - `NoExecute` (don't run new pods and evict running pods)
-
----
-
-class: extra-details
-
-## Checking tolerations on the control plane
-
-.lab[
-
-- Check tolerations for CoreDNS:
- ```bash
- kubectl -n kube-system get deployments coredns -o json |
- jq .spec.template.spec.tolerations
- ```
-
-]
-
-The result should include:
-```json
- {
- "effect": "NoSchedule",
- "key": "node-role.kubernetes.io/master"
- }
-```
-
-It means: "bypass the exact taint that we saw earlier on `node1`."
-
----
-
-class: extra-details
-
-## Special tolerations
-
-.lab[
-
-- Check tolerations on `kube-proxy`:
- ```bash
- kubectl -n kube-system get ds kube-proxy -o json |
- jq .spec.template.spec.tolerations
- ```
-
-]
-
-The result should include:
-```json
- {
- "operator": "Exists"
- }
-```
-
-This one is a special case that means "ignore all taints and run anyway."
-
----
-
-## Running Traefik on our cluster
-
-- We provide a YAML file (@@LINK[k8s/traefik.yaml]) which contains:
-
- - a `traefik` Namespace
-
- - a `traefik` DaemonSet in that Namespace
-
- - RBAC rules allowing Traefik to watch the necessary API objects
-
-.lab[
-
-- Apply the YAML:
- ```bash
- kubectl apply -f ~/container.training/k8s/traefik.yaml
- ```
-
-]
-
----
-
-## Checking that Traefik runs correctly
-
-- If Traefik started correctly, we now have a web server listening on each node
-
-.lab[
-
-- Check that Traefik is serving 80/tcp:
- ```bash
- curl localhost
- ```
-
-]
-
-We should get a `404 page not found` error.
-
-This is normal: we haven't provided any ingress rule yet.
-
----
-
-## Traefik web UI
-
-- Traefik provides a web dashboard
-
-- With the current install method, it's listening on port 8080
-
-.lab[
-
-- Go to `http://node1:8080` (replacing `node1` with its IP address)
-
-
-
-]
-
----
-
-## Setting up routing ingress rules
+## Creating Ingress resources
- We are going to use the `jpetazzo/color` image
- This image contains a simple static HTTP server on port 80
-- We will run 3 deployments (`red`, `green`, `blue`)
+- We will run a Deployment, e.g. `blue`
-- We will create 3 services (one for each deployment)
+- We will expose that Deployment with a Service
-- Then we will create 3 ingress rules (one for each service)
-
-- We will route requests to `/red`, `/green`, `/blue`
+- And create an Ingress for that Service
---
-## Running colorful web servers
+## Deploying the `blue` app
+
+- Nothing special here; we're just creating a Deployment and a Service
.lab[
-- Run all three deployments:
+- Create the Deployment:
```bash
- kubectl create deployment red --image=jpetazzo/color
- kubectl create deployment green --image=jpetazzo/color
- kubectl create deployment blue --image=jpetazzo/color
+ kubectl create deployment blue --image=jpetazzo/color
```
-- Create a service for each of them:
+- Expose it with a Service:
```bash
- kubectl expose deployment red --port=80
- kubectl expose deployment green --port=80
- kubectl expose deployment blue --port=80
+ kubectl expose deployment blue --port=80
```
]
---
-## Creating ingress resources
+## Creating Ingress resources
-- Since Kubernetes 1.19, we can use `kubectl create ingress`
+- There is a convenient helper command, `kubectl create ingress`
- (if you're running an older version of Kubernetes, **you must upgrade**)
+ (available since Kubernetes 1.19; before that, the only way was to use YAML manifests)
+
+- An Ingress resource can contain one or multiple "rules"
.lab[
-- Create the three ingress resources:
+- Create an Ingress with a single rule:
```bash
- kubectl create ingress red --rule=/red=red:80
- kubectl create ingress green --rule=/green=green:80
- kubectl create ingress blue --rule=/blue=blue:80
+ kubectl create ingress blue --rule=/blue=blue:80
```
-
]
+`/blue` = HTTP path that the Ingress should use
+
+`blue:80` = Service name + port where requests should be sent
+
---
-## Testing
+## Testing our new Ingress
-- We should now be able to access `localhost/red`, `localhost/green`, etc.
+- We need to know to which IP address to connect
-.lab[
+- If you're attending a live class; of if you installed your Ingress Controller with a DaemonSet and `hostPort` or `hostNetwork`:
-- Check that these routes work correctly:
- ```bash
- curl http://localhost/red
- curl http://localhost/green
- curl http://localhost/blue
- ```
+ *use the IP address of any of the nodes of your cluster*
-]
+- If you installed your Ingress Controller with a `LoadBalancer` Service:
+
+ *use the EXTERNAL-IP of the Service*
+
+- If you're using a local dev cluster:
+
+ *it depends; we suggest `kubectl port-forward` and then use `localhost`*
---
-## Accessing other URIs
+## Testing our new Ingress
-- What happens if we try to access e.g. `/blue/hello`?
+- Connect to `http:///blue`
+
+- We should see a reply from the `blue` Deployment
+
+---
+
+## Using domain names
+
+- With Ingress, we can use what is often called "name-based virtual hosting"
+
+- This lets us host multiple web apps on a single IP address
+
+- All we need is to used a different domain name for each web app
+
+ (e.g.: `blue.example.com`, `green.example.com`, `red.example.com`...)
+
+- We could use a real domain name, or, for simplicity, [nip.io]
+
+- In the next steps, we'll assume that our Ingress controller uses IP address `A.B.C.D`
+
+ (make sure to substitute accordingly!)
+
+---
+
+## Before creating the Ingress
+
+- We will make the `blue` Deployment available at the URL http://blue.A.B.C.D.nip.io
.lab[
-- Retrieve the `ClusterIP` of Service `blue`:
+- Let's check what happens if we connect to that address right now:
```bash
- BLUE=$(kubectl get svc blue -o jsonpath={.spec.clusterIP})
- ```
-
-- Check that the `blue` app serves `/hello`:
- ```bash
- curl $BLUE/hello
- ```
-
-- See what happens if we try to access it through the Ingress:
- ```bash
- curl http://localhost/blue/hello
+ curl http://blue.A.B.C.D.nip.io
```
]
+- If we're using Traefik, it will give us a very terse `404 not found` error
+
+ (that's expected!)
+
+---
+
+## Creating the Ingress
+
+- This will be very similar to the Ingress that we created earlier
+
+- But we're going to add a domain name in the rule
+
+.lab[
+
+- Create the Ingress:
+ ```bash
+ kubectl create ingress blue-with-domain --rule=blue.A.B.C.D.nip.io/=blue:80
+ ```
+
+- Test it:
+ ```bash
+ curl http://blue.A.B.C.D.nip.io
+ ```
+
+]
+
+- We should see a response from the `blue` Deployment
+
---
## Exact or prefix matches
-- By default, ingress rules are *exact* matches
+- By default, Ingress rules are *exact* matches
- (the request is routed only if the URI is exactly `/blue`)
+ (the request is routed only for the specified URL)
-- We can also ask a *prefix* match by adding a `*` to the rule
+.lab[
+
+- Confirm that only `/` routes to the `blue` app:
+ ```bash
+ curl http://blue.A.B.C.D.nip.io # works
+ curl http://blue.A.B.C.D.nip.io/hello # does not work
+ ```
+
+]
+
+- How do we change that?
+
+---
+
+## Specifying a prefix match
+
+- If a rule ends with `*`, it will be interpreted as a prefix match
.lab[
- Create a prefix match rule for the `blue` service:
```bash
- kubectl create ingress bluestar --rule=/blue*=blue:80
+ kubectl create ingress blue-with-prefix --rule=blue.A.B.C.D.nip.io/*=blue:80
```
- Check that it works:
```bash
- curl http://localhost/blue/hello
+ curl http://blue.A.B.C.D.nip.io/hello
+ ```
+
+]
+
+---
+
+## What do Ingress manifests look like?
+
+- Let's have a look at the manifests generated by `kubectl create ingress`!
+
+- We'll use `-o yaml` to display the YAML generated by `kubectl`
+
+- And `--dry-run=client` to instruct `kubectl` to skip resource creation
+
+.lab[
+
+- Generate and display a few manifests:
+ ```bash
+ kubectl create ingress -o yaml --dry-run=client \
+ exact-route --rule=/blue=blue:80
+
+ kubectl create ingress -o yaml --dry-run=client \
+ with-a-domain --rule=blue.test/=blue:80
+
+ kubectl create ingress -o yaml --dry-run=client \
+ now-with-a-prefix --rule=blue.test/*=blue:80
```
]
@@ -586,78 +492,18 @@ This is normal: we haven't provided any ingress rule yet.
## Multiple rules per Ingress resource
-- It is also possible to have multiple rules in a single resource
+- It is also possible to have multiple rules in a single Ingress resource
+
+- Let's see what that looks like, too!
.lab[
-- Create an Ingress resource with multiple rules:
+- Show the manifest for an Ingress resource with multiple rules:
```bash
- kubectl create ingress rgb \
- --rule=/red*=red:80 \
- --rule=/green*=green:80 \
- --rule=/blue*=blue:80
- ```
-
-- Check that everything still works after deleting individual rules
-
-]
-
----
-
-## Using domain-based routing
-
-- In the previous examples, we didn't use domain names
-
- (we routed solely based on the URI of the request)
-
-- We are now going to show how to use domain-based routing
-
-- We are going to assume that we have a domain name
-
- (for instance: `cloudnative.tld`)
-
-- That domain name should be set up so that a few subdomains point to the ingress
-
- (for instance, `blue.cloudnative.tld`, `green.cloudnative.tld`...)
-
-- For simplicity or flexibility, we can also use a wildcard record
-
----
-
-## Setting up DNS
-
-- To make our lives easier, we will use [nip.io](http://nip.io)
-
-- Check out `http://red.A.B.C.D.nip.io`
-
- (replacing A.B.C.D with the IP address of `node1`)
-
-- We should get the same `404 page not found` error
-
- (meaning that our DNS is "set up properly", so to speak!)
-
----
-
-## Setting up name-based Ingress
-
-.lab[
-
-- Set the `$IPADDR` variable to our ingress controller address:
- ```bash
- IPADDR=`A.B.C.D`
- ```
-
-- Create our Ingress resource:
- ```bash
- kubectl create ingress rgb-with-domain \
- --rule=red.$IPADDR.nip.io/*=red:80 \
- --rule=green.$IPADDR.nip.io/*=green:80 \
- --rule=blue.$IPADDR.nip.io/*=blue:80
- ```
-
-- Test it out:
- ```bash
- curl http://red.$IPADDR.nip.io/hello
+ kubectl create ingress -o yaml --dry-run=client rgb \
+ --rule=/red*=red:80 \
+ --rule=/green*=green:80 \
+ --rule=/blue*=blue:80
```
]
@@ -666,9 +512,9 @@ This is normal: we haven't provided any ingress rule yet.
class: extra-details
-## Using multiple ingress controllers
+## Using multiple Ingress Controllers
-- You can have multiple ingress controllers active simultaneously
+- You can have multiple Ingress Controllers active simultaneously
(e.g. Traefik and NGINX)
@@ -676,7 +522,7 @@ class: extra-details
(e.g. one for internal, another for external traffic)
-- To indicate which ingress controller should be used by a given Ingress resouce:
+- To indicate which Ingress Controller should be used by a given Ingress resouce:
- before Kubernetes 1.18, use the `kubernetes.io/ingress.class` annotation
@@ -690,7 +536,11 @@ class: extra-details
- A lot of things have been left out of the Ingress v1 spec
- (routing requests according to weight, cookies, across namespaces...)
+ (e.g.: routing requests according to weight, cookies, across namespaces...)
+
+- Most Ingress Controllers have vendor-specific ways to address these shortcomings
+
+- But since they're vendor-specific, migrations become more complex
- Example: stripping path prefixes
@@ -702,6 +552,60 @@ class: extra-details
---
+## A word about Ingress NGINX
+
+- There are two Ingress Controllers based on NGINX (both open source)
+
+- [F5 NGINX Ingress Controller][f5-nginx] aka "NGINX Ingress" ([GitHub repo][f5-nginx-repo], [docs][f5-nginx-docs])
+
+ - developed and maintained by F5 (company that acquired NGINX in 2019)
+
+ - supports vendor-specific CRDs like [VirtualServer and VirtualServerRoute][f5-nginx-crds]
+
+- Ingress NGINX Controller aka "Ingress NGINX" ([GitHub repo][k8s-nginx-repo], [docs][k8s-nginx-docs])
+
+ - one of the earliest Kubernetes Ingress Controllers
+
+ - developed by the community
+
+ - **no longer under active development; maintenance will stop in March 2026**
+ (check the [announcement][k8s-nginx-announcement])
+
+[f5-nginx]: https://docs.nginx.com/nginx-ingress-controller/
+[f5-nginx-docs]: https://docs.nginx.com/nginx-ingress-controller
+[f5-nginx-repo]: https://github.com/nginx/kubernetes-ingress
+[f5-nginx-crds]: https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/
+[k8s-nginx-docs]: https://kubernetes.github.io/ingress-nginx/
+[k8s-nginx-repo]: https://github.com/kubernetes/ingress-nginx
+[k8s-nginx-announcement]: https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/
+
+---
+
+## A word about software sustainability
+
+- From the Ingress NGINX retirement announcement:
+
+ *Despite the project’s popularity among users, Ingress NGINX has always struggled with insufficient or barely-sufficient maintainership. For years, the project has had only one or two people doing development work, on their own time, after work hours and on weekends.*
+
+--
+
+- If your production, mission-critical workloads depend on open source software:
+
+ *what happens if the maintainers throw the towel?*
+
+--
+
+- If your production, mission-critical workloads depend on commercial software:
+
+ *what happens if the the company behind it goes out of business?*
+
+ *what happens if they drastically change their business model or [pricing][vmware1] [structure][vmware2]?*
+
+[vmware1]: https://www.theregister.com/2025/05/22/euro_cloud_body_ecco_says_broadcom_licensing_unfair/
+[vmware2]: https://www.ciodive.com/news/att-broadcom-vmware-price-hikes-court-battle/728603/
+
+---
+
## Ingress in the future
- The [Gateway API SIG](https://gateway-api.sigs.k8s.io/) is probably be the future of Ingress
@@ -720,5 +624,7 @@ class: extra-details
???
+[nip.io]: http://nip.io
+
:EN:- The Ingress resource
:FR:- La ressource *ingress*
diff --git a/slides/kube-adv.yml b/slides/kube-adv.yml
index feb0b40f..8ac89ca2 100644
--- a/slides/kube-adv.yml
+++ b/slides/kube-adv.yml
@@ -67,6 +67,7 @@ content:
- k8s/crd.md
- #6
- k8s/ingress-tls.md
+ #- k8s/ingress-setup.md
#- k8s/ingress-advanced.md
#- k8s/ingress-canary.md
- k8s/gateway-api.md
diff --git a/slides/kube-fullday.yml b/slides/kube-fullday.yml
index 7b587911..913500d5 100644
--- a/slides/kube-fullday.yml
+++ b/slides/kube-fullday.yml
@@ -65,6 +65,7 @@ content:
- k8s/rollout.md
- k8s/healthchecks.md
- k8s/ingress.md
+ #- k8s/ingress-setup.md
#- k8s/gateway-api.md
#- k8s/volumes.md
- k8s/configuration.md
diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml
index 6f199ddc..a6d28a21 100644
--- a/slides/kube-selfpaced.yml
+++ b/slides/kube-selfpaced.yml
@@ -82,6 +82,7 @@ content:
- k8s/kubectlproxy.md
-
- k8s/ingress.md
+ - k8s/ingress-setup.md
- k8s/ingress-advanced.md
#- k8s/ingress-canary.md
- k8s/ingress-tls.md
diff --git a/slides/kube-twodays.yml b/slides/kube-twodays.yml
index ced725da..a4af3e69 100644
--- a/slides/kube-twodays.yml
+++ b/slides/kube-twodays.yml
@@ -80,6 +80,7 @@ content:
-
- k8s/namespaces.md
- k8s/ingress.md
+ #- k8s/ingress-setup.md
#- k8s/ingress-advanced.md
#- k8s/ingress-canary.md
#- k8s/ingress-tls.md