From 9cd812de757e06a8e2fb5972a0949bd6e5fb5509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Sat, 13 May 2023 12:06:47 +0200 Subject: [PATCH] =?UTF-8?q?=E2=8F=AB=20Update=20ingress=20chapter=20and=20?= =?UTF-8?q?manifest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/traefik-v2.yaml | 50 +++--- slides/k8s/ingress.md | 367 +++++++++++++++++++++++------------------- 2 files changed, 225 insertions(+), 192 deletions(-) diff --git a/k8s/traefik-v2.yaml b/k8s/traefik-v2.yaml index 9fb6aeeb..e6d113db 100644 --- a/k8s/traefik-v2.yaml +++ b/k8s/traefik-v2.yaml @@ -1,36 +1,44 @@ --- apiVersion: v1 +kind: Namespace +metadata: + name: traefik +--- +apiVersion: v1 kind: ServiceAccount metadata: - name: traefik-ingress-controller - namespace: kube-system + name: traefik + namespace: traefik --- kind: DaemonSet apiVersion: apps/v1 metadata: - name: traefik-ingress-controller - namespace: kube-system + name: traefik + namespace: traefik labels: - k8s-app: traefik-ingress-lb + app: traefik spec: selector: matchLabels: - k8s-app: traefik-ingress-lb + app: traefik template: metadata: labels: - k8s-app: traefik-ingress-lb - name: traefik-ingress-lb + app: traefik + name: traefik spec: tolerations: - effect: NoSchedule operator: Exists - hostNetwork: true - serviceAccountName: traefik-ingress-controller + # 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.5 - name: traefik-ingress-lb + - image: traefik:v2.10 + name: traefik ports: - name: http containerPort: 80 @@ -61,7 +69,7 @@ spec: kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: traefik-ingress-controller + name: traefik rules: - apiGroups: - "" @@ -73,14 +81,6 @@ rules: - get - list - watch - - apiGroups: - - extensions - resources: - - ingresses - verbs: - - get - - list - - watch - apiGroups: - networking.k8s.io resources: @@ -94,15 +94,15 @@ rules: kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: traefik-ingress-controller + name: traefik roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: traefik-ingress-controller + name: traefik subjects: - kind: ServiceAccount - name: traefik-ingress-controller - namespace: kube-system + name: traefik + namespace: traefik --- kind: IngressClass apiVersion: networking.k8s.io/v1 diff --git a/slides/k8s/ingress.md b/slides/k8s/ingress.md index 8e24f83b..b4270ea6 100644 --- a/slides/k8s/ingress.md +++ b/slides/k8s/ingress.md @@ -28,7 +28,7 @@ A few use-cases: - Cost optimization - (because individual `LoadBalancer` services typically cost money) + (using `LoadBalancer` services for everything would be expensive) - Automatic handling of TLS certificates @@ -109,7 +109,7 @@ A few use-cases: - Step 1: deploy an *ingress controller* - (one-time setup) + (one-time setup; typically done by cluster admin) - Step 2: create *Ingress resources* @@ -117,7 +117,7 @@ A few use-cases: - the controller watches ingress resources and sets up a LB -- Step 3: set up DNS +- Step 3: set up DNS (optional) - associate DNS entries with the load balancer address @@ -175,15 +175,50 @@ class: extra-details - maybe motivated by the fact that Traefik releases are named after cheeses -- For DNS, we will use [nip.io](http://nip.io/) +- We will create ingress resources for various HTTP services + +- For DNS, we can use [nip.io](http://nip.io/) - `*.1.2.3.4.nip.io` resolves to `1.2.3.4` -- We will create ingress resources for various HTTP services +--- + +## Classic ingress controller setup + +- Ingress controller runs with a Deployment + + (with at least 2 replicas for redundancy) + +- It is exposed with a `LoadBalancer` Service + +- Typical for cloud-based clusters + +- Also common when running or on-premises with [MetalLB] or [kube-vip] + +[MetalLB]: https://metallb.org/ +[kube-vip]: https://kube-vip.io/ --- -## Accepting connections on port 80 (and 443) +## Alternate ingress controller setup + +- Ingress controller runs with a DaemonSet + + (on bigger clusters, this can be coupled with a `nodeSelector`) + +- It is exposed with `externalIPs`, `hostPort`, or `hostNetwork` + +- Typical for on-premises clusters + + (where at least a set of nodes have a stable IP and high availability) + +--- + +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 @@ -193,94 +228,40 @@ class: extra-details (and 443 if we want to handle HTTPS) -- Let's see how we can achieve that! +--- + +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`)* --- -## Various ways to expose something on port 80 +## Trying it out with Traefik -- Service with `type: LoadBalancer` +- We are going to run Traefik with a DaemonSet - *costs a little bit of money; not always available* + (there will be one instance of Traefik on every node of the cluster) -- Service with one (or multiple) `ExternalIP` - - *requires public nodes; limited by number of nodes* - -- Service with `hostPort` or `hostNetwork` - - *same limitations as `ExternalIP`; even harder to manage* - ---- - -## Deploying pods listening on port 80 - -- We are going to run Traefik in Pods with `hostNetwork: true` - - (so that our load balancer can use the "real" port 80 of our nodes) - -- Traefik Pods will be created by a DaemonSet - - (so that we get one instance of Traefik on every node of the cluster) +- The Pods will use `hostPort: 80` - This means that we will be able to connect to any node of the cluster on port 80 -.warning[This is not typical of a production setup!] - ---- - -## Doing it in production - -- When running "on cloud", the easiest option is a `LoadBalancer` service - -- When running "on prem", it depends: - - - [MetalLB] is a good option if a pool of public IP addresses is available - - - otherwise, using `externalIPs` on a few nodes (2-3 for redundancy) - -- Many variations/optimizations are possible depending on our exact scenario! - -[MetalLB]: https://metallb.org/ - ---- - -class: extra-details - -## Without `hostNetwork` - -- Normally, each pod gets its own *network namespace* - - (sometimes called sandbox or network sandbox) - -- An IP address is assigned to the pod - -- This IP address is routed/connected to the cluster network - -- All containers of that pod are sharing that network namespace - - (and therefore using the same IP address) - ---- - -class: extra-details - -## With `hostNetwork: true` - -- No network namespace gets created - -- The pod is using the network namespace of the host - -- It "sees" (and can use) the interfaces (and IP addresses) of the host - -- The pod can receive outside traffic directly, on any port - -- Downside: with most network plugins, network policies won't work for that pod - - - most network policies work at the IP address level - - - filtering that pod = filtering traffic from the node - --- ## Running Traefik @@ -293,7 +274,7 @@ class: extra-details - use a Daemon Set so that each node can accept connections - - enable `hostNetwork` + - enable `hostPort: 80` - add a *toleration* so that Traefik also runs on all nodes @@ -426,11 +407,13 @@ This one is a special case that means "ignore all taints and run anyway." ## Running Traefik on our cluster -- We provide a YAML file (`k8s/traefik.yaml`) which is essentially the sum of: +- We provide a YAML file (@@LINK[k8s/traefik.yaml]) which contains: - - [Traefik's Daemon Set resources](https://github.com/containous/traefik/blob/v1.7/examples/k8s/traefik-ds.yaml) (patched with `hostNetwork` and tolerations) + - a `traefik` Namespace - - [Traefik's RBAC rules](https://github.com/containous/traefik/blob/v1.7/examples/k8s/traefik-rbac.yaml) allowing it to watch necessary API objects + - a `traefik` DaemonSet in that Namespace + + - RBAC rules allowing Traefik to watch the necessary API objects .lab[ @@ -462,20 +445,6 @@ This is normal: we haven't provided any ingress rule yet. --- -## 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!) - ---- - ## Traefik web UI - Traefik provides a web dashboard @@ -492,7 +461,7 @@ This is normal: we haven't provided any ingress rule yet. --- -## Setting up host-based routing ingress rules +## Setting up routing ingress rules - We are going to use the `jpetazzo/color` image @@ -504,7 +473,7 @@ This is normal: we haven't provided any ingress rule yet. - Then we will create 3 ingress rules (one for each service) -- We will route `.A.B.C.D.nip.io` to the corresponding deployment +- We will route requests to `/red`, `/green`, `/blue` --- @@ -534,100 +503,164 @@ This is normal: we haven't provided any ingress rule yet. - Since Kubernetes 1.19, we can use `kubectl create ingress` + (if you're running an older version of Kubernetes, **you must upgrade**) + +.lab[ + +- Create the three ingress resources: ```bash - kubectl create ingress red \ - --rule=red.`A.B.C.D`.nip.io/*=red:80 + kubectl create ingress red --rule=/red=red:80 + kubectl create ingress green --rule=/green=green:80 + kubectl create ingress blue --rule=/blue=blue:80 ``` -- We can specify multiple rules per resource +] +--- + +## Testing + +- We should now be able to access `localhost/red`, `localhost/green`, etc. + +.lab[ + +- Check that these routes work correctly: + ```bash + curl http://localhost/red + curl http://localhost/green + curl http://localhost/blue + ``` + +] + +--- + +## Accessing other URIs + +- What happens if we try to access e.g. `/blue/hello`? + +.lab[ + +- Retrieve the `ClusterIP` of Service `blue`: + ```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 + ``` + +] + +--- + +## Exact or prefix matches + +- By default, ingress rules are *exact* matches + + (the request is routed only if the URI is exactly `/blue`) + +- We can also ask a *prefix* match by adding a `*` to the rule + +.lab[ + +- Create a prefix match rule for the `blue` service: + ```bash + kubectl create ingress bluestar --rule=/blue*:blue:80 + ``` + +- Check that it works: + ```bash + curl http://localhost/blue/hello + ``` + +] + +--- + +## Multiple rules per Ingress resource + +- It is also possible to have multiple rules in a single resource + +.lab[ + +- Create an Ingress resource with multiple rules: ```bash kubectl create ingress rgb \ - --rule=red.`A.B.C.D`.nip.io/*=red:80 \ - --rule=green.`A.B.C.D`.nip.io/*=green:80 \ - --rule=blue.`A.B.C.D`.nip.io/*=blue:80 + --rule=/red*=red:80 \ + --rule=/green*=green:80 \ + --rule=/blue*=blue:80 ``` ---- +- Check that everything still works after deleting individual rules -## Pay attention to the `*`! - -- The `*` is important: - - ``` - --rule=red.A.B.C.D.nip.io/`*`=red:80 - ``` - -- It means "all URIs below that path" - -- Without the `*`, it means "only that exact path" - - (if we omit it, requests for e.g. `red.A.B.C.D.nip.io/hello` will 404) +] --- -## Before Kubernetes 1.19 +## Using domain-based routing -- Before Kubernetes 1.19: +- In the previous examples, we didn't use domain names - - `kubectl create ingress` wasn't available + (we routed solely based on the URI of the request) - - `apiVersion: networking.k8s.io/v1` wasn't supported +- We are now going to show how to use domain-based routing -- It was necessary to use YAML, and `apiVersion: networking.k8s.io/v1beta1` +- We are going to assume that we have a domain name - (see example on next slide) + (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 --- -## YAML for old ingress resources +## Setting up DNS -Here is a minimal host-based ingress resource: +- To make our lives easier, we will use [nip.io](http://nip.io) -```yaml -apiVersion: networking.k8s.io/v1beta1 -kind: Ingress -metadata: - name: red -spec: - rules: - - host: red.`A.B.C.D`.nip.io - http: - paths: - - path: / - backend: - serviceName: red - servicePort: 80 +- 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!) --- -## YAML for new ingress resources +## Setting up name-based Ingress -- Starting with Kubernetes 1.19, `networking.k8s.io/v1` is available - -- And we can use `kubectl create ingress` 🎉 - -- We can see "modern" YAML with `-o yaml --dry-run=client`: +.lab[ +- Set the `$IPADDR` variable to our ingress controller address: ```bash - kubectl create ingress red -o yaml --dry-run=client \ - --rule=red.`A.B.C.D`.nip.io/*=red:80 - + 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 + ``` -## Creating ingress resources +- Test it out: + ```bash + curl http://red.$IPADDR.nip.io/hello + ``` -- Create the ingress resources with `kubectl create ingress` - - (or use the YAML manifests if using Kubernetes 1.18 or older) - -- Make sure to update the hostnames! - -- Check that you can connect to the exposed web apps +] --- @@ -677,7 +710,7 @@ class: extra-details GatewayClass, Gateway, HTTPRoute, TCPRoute... -- It is still in alpha stage +- It is now in beta (since v0.5.0, released in 2022) ???