🏭️ Refactor content about Ingress Controllers

The section about Ingress has been both simplified (separating
the content about taints and tolerations) and made somewhat
deeper, to make it more compatible with both live classes and
recorded videos.

A new section about setting up Ingress Controllers has been
added.
This commit is contained in:
Jérôme Petazzoni
2025-12-14 19:19:16 -06:00
parent a44701960c
commit 942f20812b
9 changed files with 970 additions and 627 deletions
-87
View File
@@ -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
-114
View File
@@ -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
-1
View File
@@ -1 +0,0 @@
traefik-v2.yaml
+123
View File
@@ -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
+512
View File
@@ -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*
<br/>
*(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://`<EXTERNAL-IP>`
```
]
- Note: that `EXTERNAL-IP` might be `<Pending>` 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://`<node address/`
```
]
- We should see `404 not found`
---
class: extra-details
## Control plane nodes
- When running Kubernetes on-premises, it's typical to have "control plane nodes"
- These nodes are dedicated to the control plane Pods, and won't run normal workloads
- If you have such a cluster (e.g. deployed with `kubeadm` on multiple nodes):
- get the list of nodes (`kubectl get nodes`)
- check where Traefik Pods are running (`kubectl get pods --namespace=traefik`)
- You should see that Traefik is not running on control plane nodes!
---
class: extra-details
## Running Traefik on the control plane
- If we want to do that, we need to provide a *toleration*
- That toleration needs to match the *taint* on the control plane nodes
- To review the taints on our nodes, we can use one of these commands:
```bash
kubectl get nodes -o custom-columns=NAME:metadata.name,TAINTS:spec.taints
kubectl get nodes -o json | jq '.items[] | [.metadata.name, .spec.taints]'
```
- Then, to place the proper toleration on Traefik pods:
```bash
--set tolerations[0].key=node-role.kubernetes.io/control-plane
--set tolerations[0].effect=NoSchedule
```
- Note: as we keep adding options, writing a values YAML file becomes more convenient!
---
## What about local dev clusters?
- Follow the instructions for "normal" clusters (with a `LoadBalancer` service)
- Once Traefik is up and running, set up a port-forward:
```bash
kubectl port-forward --namespace=traefik service/traefik 8888:80
```
- Connect to http://localhost:8888
- You should see a `404 not found` served by Traefik
- Whenever you'll need the "IP address of the Ingress Controller", use `localhost:8888`
(you'll need to specify that port number)
- With some clusters (e.g. KinD) it's also possible to set up local port mappings
to avoid specifying the port number; but the port-forward method should work everywhere
---
## The Traefik dashboard
- Accessing the Traefik dashboard requires multiple steps
- First, the dashboard feature needs to be enabled in Traefik
*the Helm chart does this automatically by default*
- Next, there needs to be a "route" inside Traefik to expose the dashboard
*this can be done by setting `ingressRoute.dashboard.enabled=true`*
- Finally, we need to connect to the correct entrypoint
*by default, that will be the internal entrypoint on port 8080, on `/dashboard`*
---
## Accessing the Traefik dashboard
- Redeploy Traefik, adding `--set ingressRoute.dashboard.enabled=true`
- Then use port-forward to access the internal `traefik` entrypoint:
```bash
kubectl port-forward --namespace=traefik deployment/traefik 1234:8080
kubectl port-forward --namespace=traefik daemonset/traefik 1234:8080
```
(use the appropriate command depending on how you're running Traefik)
- Connect to http://localhost:1234/dashboard/ (with the trailing slash!)
- You should see the Traefik dashboard!
- Note: it's only available on the internal port, but there is no authentication by default!
(you might want to add authentication or e.g. set up a NetworkPolicy to secure it)
???
[MetalLB]: https://metallb.org/
[kube-vip]: https://kube-vip.io/
:EN:- Setting up an Ingress Controller
:FR:- Mise en place d'un Ingress Controller
+331 -425
View File
@@ -46,7 +46,7 @@ A few use-cases:
- Ingress
- requires an ingress controller
- requires an Ingress Controller
- can implement TLS transparently for the app
- only supports HTTP
- can do content-based routing (e.g. per URI)
@@ -61,11 +61,40 @@ A few use-cases:
- Designed to expose HTTP services
- Requires an *ingress controller*
- Requires an *Ingress Controller*
(otherwise, resources can be created, but nothing happens)
- Some ingress controllers are based on existing load balancers
- Some Kubernetes distributions automatically install an Ingress Controller
(or they give that option as an easy "on/off" switch at install time)
- It's relatively rare, though, because Ingress Controllers aren't "one size fits all"
---
## Checking if we have an Ingress Controller
- A modern Ingress Controller will create an IngressClass resource
- We can check simply by running `kubectl get ingressclasses`
- Example:
```shell
$ kubectl get ingressclasses
NAME CONTROLLER PARAMETERS AGE
traefik traefik.io/ingress-controller <none> 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*
<br/>
*(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
<br/>
(here, this means "this node is reserved for the control plane")
- an error condition on the node
<br/>
(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)
<!-- ```open http://node1:8080``` -->
]
---
## 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://<IP address>/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 projects 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*
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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