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