From ef70ed8006372a483bf758f0ac02395a8101a7c8 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Thu, 4 Apr 2019 09:33:04 -0500 Subject: [PATCH 01/51] Pre-requirements + Architecture sections --- slides/k8s/architecture.md | 364 ++++++++++++++++++++++++++++++++++++ slides/k8s/prereqs-admin.md | 63 +++++++ 2 files changed, 427 insertions(+) create mode 100644 slides/k8s/architecture.md create mode 100644 slides/k8s/prereqs-admin.md diff --git a/slides/k8s/architecture.md b/slides/k8s/architecture.md new file mode 100644 index 00000000..3efc8712 --- /dev/null +++ b/slides/k8s/architecture.md @@ -0,0 +1,364 @@ +# Kubernetes architecture + +We can arbitrarily split Kubernetes in two parts: + +- the *nodes*, a set of machines that run our containerized workloads; + +- the *control plane*, a set of processes implementing the Kubernetes APIs. + +Kubernetes also relies on underlying infrastructure: + +- servers, network connectivity (obviously!), + +- optional components like storage systems, load balancers ... + +--- + +## Control plane location + +The control plane can run: + +- in containers, on the same nodes that run other application workloads + + (example: Minikube; 1 node runs everything) + +- on a dedicated node + + (example: a cluster installed with kubeadm) + +- on a dedicated set of nodes + + (example: Kubernetes The Hard Way; kops) + +- outside of the cluster + + (example: most managed clusters like AKS, EKS, GKE) + +--- + +class: pic + +![Kubernetes architecture diagram: control plane and nodes](images/k8s-arch2.png) + +--- + +## What runs on a node + +- Our containerized workloads + +- A container engine like Docker, CRI-O, container... + + (in theory, the choice doesn't matter, as the engine is abstracted by Kubernetes) + +- kubelet: an agent connecting the node to the cluster + + (it connects to the API server, registers the node, receives instructions) + +- kube-proxy: a component used for internal cluster communication + + (note that this is *not* an overlay network or a CNI plugin!) + +--- + +## What's in the control plane + +- Everything is stored in an etcd + + (it's the only stateful component) + +- Everyone communicates exclusively through the API server: + + - we (users) interact with the cluster through the API server + + - the nodes register and get their instructions through the API server + + - the other control plane components also register to the API server + +- API server is the only component that reads/writes from/to etcd + +--- + +## Communication protocols: API server + +- The API server exposes a REST API + + (except for some calls, e.g. to attach interactively to a container) + +- Almost all requests and responses are JSON following a strict format + +- For performance, the requests and responses can also be done over protobuf + + (see this [design proposal](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/protobuf.md) for details) + +- In practice, protobuf is used for all internal communication + + (between control plane components, and with kubelet) + +--- + +## Communication protocols: on the nodes + +The kubelet agent uses a number of special-purpose protocols and interfaces, including: + +- CRI (Container Runtime Interface) + + - used for communication with the container engine + - abstracts the differences between container engines + - based on gRPC+protobuf + +- [CNI (Container Network Interface)](https://github.com/containernetworking/cni/blob/master/SPEC.md) + + - used for communication with network plugins + - network plugins are implemented as executable programs invoked by kubelet + - network plugins provide IPAM + - network plugins set up network interfaces in pods + +--- + +class: pic + +![Kubernetes architecture diagram: communication between components](images/k8s-arch4-thanks-luxas.png) + +--- + +# The Kubernetes API + +[ +*The Kubernetes API server is a "dumb server" which offers storage, versioning, validation, update, and watch semantics on API resources.* +]( +https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/protobuf.md#proposal-and-motivation +) + +([Clayton Coleman](https://twitter.com/smarterclayton), Kubernetes Architect and Maintainer) + +What does that mean? + +--- + +## The Kubernetes API is declarative + +- We cannot tell the API, "run a pod" + +- We can tell the API, "here is the definition for pod X" + +- The API server will store that definition (in etcd) + +- *Controllers* will then wake up and create a pod matching the definition + +--- + +## The core features of the Kubernetes API + +- We can create, read, update, and delete objects + +- We can also *watch* objects + + (be notified when an object changes, or when an object of a given type is created) + +- Objects are strongly typed + +- Types are *validated* and *versioned* + +- Storage and watch operations are provided by etcd + + (note: the [k3s](https://k3s.io/) project allows to use sqlite instead of etcd) + +--- + +## Create + +- Let's create a simple object + +.exercise[ + +- Create a namespace with the following command: + ```bash + kubectl create -f- < + (example: this [demo scheduler](https://github.com/kelseyhightower/scheduler) uses the cost of nodes, stored in node annotations) + +- A pod might stay in `Pending` state for a long time: + + - if the cluster is full + + - if the pod has special constraints that can't be met + + - if the scheduler is not running (!) diff --git a/slides/k8s/prereqs-admin.md b/slides/k8s/prereqs-admin.md new file mode 100644 index 00000000..e3f77154 --- /dev/null +++ b/slides/k8s/prereqs-admin.md @@ -0,0 +1,63 @@ +# Pre-requirements + +- Kubernetes concepts + + (pods, deployments, services, labels, selectors) + +- Hands-on experience working with containers + + (building images, running them; doesn't matter how exactly) + +- Familiar with the UNIX command-line + + (navigating directories, editing files, using `kubectl`) + +--- + +## Labs and exercises + +- We are going to build and breaks multiple clusters + +- Everyone will get their own private environment(s) + +- You are invited to reproduce all the demos (but you don't have to) + +- All hands-on sections are clearly identified, like the gray rectangle below + +.exercise[ + +- This is the stuff you're supposed to do! + +- Go to @@SLIDES@@ to view these slides + +- Join the chat room: @@CHAT@@ + + + +] + +--- + +## Private environments + +- Each person gets their own private set of VMs + +- Each person should have a printed card with connection information + +- We will connect to these VMs with SSH + + (if you don't have an SSH client, install one **now!**) + +--- + +## Doing or re-doing this on your own? + +- We are using basic cloud VMs with Ubuntu LTS + +- The Kubernetes packages have been installed + + (from official repos) + +- We disabled IP address checks + + (to allow pod IP addresses to be carried by the cloud network) From d1609f07255e2ab8890497a54bd1d55672eeb96b Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Thu, 4 Apr 2019 12:58:35 -0500 Subject: [PATCH 02/51] Add Dessine-Moi Un Cluster --- slides/k8s/dmuc.md | 837 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 837 insertions(+) create mode 100644 slides/k8s/dmuc.md diff --git a/slides/k8s/dmuc.md b/slides/k8s/dmuc.md new file mode 100644 index 00000000..f16e9b4a --- /dev/null +++ b/slides/k8s/dmuc.md @@ -0,0 +1,837 @@ +# Building our own cluster + +- Let's build our own cluster! + + *Perfection is attained not when there is nothing left to add, but when there is nothing left to take away. (Antoine de Saint-Exupery)* + +- Our goal is to build a minimal cluster allowing us to: + + - create a Deployment (with `kubectl run` or `kubectl create deployment`) + - expose it with a Service + - connect to that service + + +- "Minimal" here means: + + - smaller number of components + - smaller number of command-line flags + - smaller number of configuration files + +--- + +## Non-goals + +- For now, we don't care about security + +- For now, we don't care about scalability + +- For now, we don't care about high availability + +- All we care about is *simplicity* + +--- + +## Our environment + +- We will use the machine indicated as `dmuc` + + (this stands for "Dessine Moi Un Cluster" or "Draw Me A Sheep", +
in hommage to Saint-Exupery's "The Little Prince") + +- This machine: + + - runs Ubuntu LTS + + - has Kubernetes, Docker, and etcd binaries installed + + - but nothing is running + +--- + +## Checking our environment + +- Let's make sure we have everything we need first + +.exercise[ + +- Log into the `dmuc` machine + +- Get root: + ```bash + sudo -i + ``` + +- Check available versions: + ```bash + etcd -version + kube-apiserver --version + dockerd --version + ``` + +] + +--- + +## The plan + +1. Start API server + +2. Interact with it (create Deployment and Service) + +3. See what's broken + +4. Fix it and go back to step 2 until it works! + +--- + +## Dealing with multiple processes + +- We are going to start many processes + +- Depending on what you're comfortable with, you can: + + - open multiple windows and multiple SSH connections + + - use a terminal multiplexer like screen or tmux + + - put processes in the background with `&` +
(warning: log output might get confusing to read!) + +--- + +## Starting API server + +.exercise[ + +- Try to start the API server: + ```bash + kube-apiserver + # It will fail with --etcd-servers must be specified + ``` + +] + +Since the API server stores everything in etcd, +it cannot start without it. + +--- + +## Starting etcd + +.exercise[ + +- Try to start etcd: + ```bash + etcd + ``` + +] + +Success! + +Note the last line of output: +``` +serving insecure client requests on 127.0.0.1:2379, this is strongly discouraged! +``` + +*Sure, that's discouraged. But thanks for telling us the address!* + +--- + +## Starting API server (for real) + +- Try again, passing the `--etcd-servers` argument + +- That argument should be a comma-separated list of URLs + +.exercise[ + +- Start API server: + ```bash + kube-apiserver --etcd-servers http://127.0.0.1:2379 + ``` + +] + +Success! + +--- + +## Interacting with API server + +- Let's try a few "classic" commands + +.exercise[ + +- List nodes: + ```bash + kubectl get nodes + ``` + +- List services: + ```bash + kubectl get services + ``` + +] + +So far, so good. + +Note: the API server automatically created the `kubernetes` service entry. + +--- + +class: extra-details + +## What about `kubeconfig`? + +- We didn't need to create a `kubeconfig` file + +- By default, the API server is listening on `localhost:8080` + + (without requiring authentication) + +- By default, `kubectl` connects to `localhost:8080` + + (without providing authentication) + +--- + +## Creating a Deployment + +- Let's run a web server! + +.exercise[ + +- Create a Deployment with NGINX: + ```bash + kubectl create deployment web --image=nginx + ``` + +] + +Success? + +--- + +## Checking our Deployment status + +.exercise[ + +- Look at pods, deployments, etc.: + ```bash + kubectl get all + ``` + +] + +Our Deployment is in a bad shape: +``` +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/web 0/1 0 0 2m26s +``` + +And, there is no ReplicaSet, and no Pod. + +--- + +## What's going on? + +- We stored the definition of our Deployment in etcd + + (through the API server) + +- But there is no *controller* to do the rest of the work + +- We need to start the *controller manager* + +--- + +## Starting the controller manager + +.exercise[ + +- Try to start the controller manager: + ```bash + kube-controller-manager + ``` + +] + +The final error message is: +``` +invalid configuration: no configuration has been provided +``` + +But the logs include another useful piece of information: +``` +Neither --kubeconfig nor --master was specified. +Using the inClusterConfig. This might not work. +``` + +--- + +## Reminder: everyone talks to API server + +- The controller manager needs to connect to the API server + +- It *does not* have a convenient `localhost:8080` default + +- We can pass the connection information in two ways: + + - `--master` and a host:port combination (easy) + + - `--kubeconfig` and a `kubeconfig` file + +- For simplicity, we'll use the first option + +--- + +## Starting the controller manager (for real) + +.exercise[ + +- Start the controller manager: + ```bash + kube-controller-manager --master http://localhost:8080 + ``` + +] + +Success! + +--- + +## Checking our Deployment status + +.exercise[ + +- Check all our resources again: + ```bash + kubectl get all + ``` + +] + +We now have a ReplicaSet. + +But we still don't have a Pod. + +--- + +## What's going on? + +In the controller manager logs, we should see something like this: +``` +E0404 15:46:25.753376 22847 replica_set.go:450] Sync "default/web-5bc9bd5b8d" +failed with `No API token found for service account "default"`, retry after the +token is automatically created and added to the service account +``` + +- The service account `default` was automatically added to our Deployment + + (and to its pods) + +- The service account `default` exists + +- But it doesn't have an associated token + + (the token is a secret; creating it requires signature; therefore a CA) + +--- + +## Solving the missing token issue + +There are many ways to solve that issue. + +We are going to list a few (to get an idea of what's happening behind the scenes). + +Of course, we don't need to perform *all* the solutions mentioned here. + +--- + +## Option 1: disable service accounts + +- Restart the API server with + `--disable-admission-plugins=ServiceAccount` + +- The API server will no longer add a service account automatically + +- Our pods will be created without a service account + +--- + +## Option 2: do not mount the (missing) token + +- Add `automountServiceAccountToken: false` to the Deployment spec + + *or* + +- Add `automountServiceAccountToken: false` to the default ServiceAccount + +- The ReplicaSet controller will no longer create pods referencing the (missing) token + +.exercise[ + +- Programmatically change the `default` ServiceAccount: + ```bash + kubectl patch sa default -p "automountServiceAccountToken: false" + ``` + +] + +--- + +## Option 3: set up service accounts properly + +- This is the most complex option! + +- Generate a key pair + +- Pass the private key to the controller manager + + (to generate and sign tokens) + +- Pass the public key to the API server + + (to verify these tokens) + +--- + +## Continuing without service account token + +- Once we patch the default service account, the ReplicaSet can create a Pod + +.exercise[ + +- Check that we now have a pod: + ```bash + kubectl get all + ``` + +] + +Note: we might have to wait a bit for the ReplicaSet controller to retry. + +If we're impatient, we can restart the controller manager. + +--- + +## What's next? + +- Our pod exists, but it is in `Pending` state + +- Remember, we don't have a node so far + + (`kubectl get nodes` shows an empty list) + +- We need to: + + - start a container engine + + - start kubelet + +--- + +## Starting a container engine + +- We're going to use Docker (because it's the default option) + +.exercise[ + +- Start the Docker Engine: + ```bash + dockerd + ``` + +] + +Success! + +Feel free to check that it actually works with e.g.: +```bash +docker run alpine echo hello world +``` + +--- + +## Starting kubelet + +- If we start kubelet without arguments, it *will* start + +- But it will not join the cluster! + +- It will start in *standalone* mode + +- Just like with the controler manager, we need to tell kubelet where is the API server + +- Alas, kubelet doesn't have a simple `--master` option + +- We have to use `--kubeconfig` + +- We need to write a `kubeconfig` file for kubelet + +--- + +## Writing a kubeconfig file + +- We can copy/paste a bunch of YAML + +- Or we can generate the file with `kubectl` + +.exercise[ + +- Create the file `kubeconfig.kubelet` with `kubectl`: + ```bash + kubectl --kubeconfig kubeconfig.kubelet config \ + set-cluster localhost --server http://localhost:8080 + kubectl --kubeconfig kubeconfig.kubelet config \ + set-context localhost --cluster localhost + kubectl --kubeconfig kubeconfig.kubelet config \ + use-context localhost + ``` + +] + +--- + +## All Kubernetes clients can use `kubeconfig` + +- The `kubeconfig.kubelet` file has the same format as e.g. `~/.kubeconfig` + +- All Kubernetes clients can use a similar file + +- The `kubectl config` commands can be used to manipulate these files + +- This highlights that kubelet is a "normal" client of the API server + +--- + +## Our `kubeconfig.kubelet` file + +The file that we generated looks like the one below. + +That one has been slightly simplified (removing extraneous fields), but it is still valid. + +```yaml +apiVersion: v1 +kind: Config +current-context: localhost +contexts: +- name: localhost + context: + cluster: localhost +clusters: +- name: localhost + cluster: + server: http://localhost:8080 +``` + +--- + +## Starting kubelet + +.exercise[ + +- Start kubelet with that `kubeconfig.kubelet` file: + ```bash + kubelet --kubeconfig kubelet.kubeconfig + ``` + +] + +Success! + +--- + +## Looking at our 1-node cluster + +- Let's check that our node registered correctly + +.exercise[ + +- List the nodes in our cluster: + ```bash + kubectl get nodes + ``` + +] + +Our node should show up. + +Its name will be its hostname (it should be `dmuc`). + +--- + +## Are we there yet? + +- Let's check if our pod is running + +.exercise[ + +- List all resources: + ```bash + kubectl get all + ``` + +] + +-- + +Our pod is still `Pending`. 🤔 + +-- + +Which is normal: it needs to be *scheduled*. + +(i.e., something needs to decide on which node it should go.) + +--- + +## Scheduling our pod + +- Why do we need a scheduling decision, since we have only one node? + +- The node might be full, unavailable; the pod might have constraints ... + +- The easiest way to schedule our pod is to start the scheduler + + (we could also schedule it manually) + +--- + +## Starting the scheduler + +- The scheduler also needs to know how to connect to the API server + +- Just like for controller manager, we can use `--kubeconfig` or `--master` + +.exercise[ + +- Start the scheduler: + ```bash + kube-scheduler --master http://localhost:8080 + ``` + +] + +- Our pod should now start correctly + +--- + +## Checking the status of our pod + +- Our pod will go through a short `ContainerCreating` phase + +- Then it will be `Running` + +.exercise[ + +- Check pod status: + ```bash + kubectl get pods + ``` + +] + +Success! + +--- + +class: extra-details + +## Scheduling a pod manually + +- We can schedule a pod in `Pending` state by creating a Binding, e.g.: + ```bash + kubectl create -f- < Date: Thu, 4 Apr 2019 13:21:26 -0500 Subject: [PATCH 03/51] Do not abort if a file can't be loaded; just report it and continue --- slides/markmaker.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/slides/markmaker.py b/slides/markmaker.py index a01fbe02..dd50291f 100755 --- a/slides/markmaker.py +++ b/slides/markmaker.py @@ -14,12 +14,6 @@ import yaml logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO")) -class InvalidChapter(ValueError): - - def __init__(self, chapter): - ValueError.__init__(self, "Invalid chapter: {!r}".format(chapter)) - - def anchor(title): title = title.lower().replace(' ', '-') title = ''.join(c for c in title if c in string.ascii_letters+'-') @@ -175,7 +169,8 @@ def processchapter(chapter, filename): markdown = "\n---\n".join(c[0] for c in chapters) titles = [t for (m,t) in chapters if t] return (markdown, titles) - raise InvalidChapter(chapter) + logging.warning("Invalid chapter: {}".format(chapter)) + return "```\nInvalid chapter: {}\n```\n".format(chapter), [] # Try to figure out the URL of the repo on GitHub. # This is used to generate "edit me on GitHub"-style links. From d5fd297c2d52336b32959a419fb17e3977f0bfb4 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Thu, 4 Apr 2019 13:38:24 -0500 Subject: [PATCH 04/51] Add YAML manifest for 1-day admin training --- slides/kube-admin-one.yml | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 slides/kube-admin-one.yml diff --git a/slides/kube-admin-one.yml b/slides/kube-admin-one.yml new file mode 100644 index 00000000..6c3dea9d --- /dev/null +++ b/slides/kube-admin-one.yml @@ -0,0 +1,44 @@ +title: | + Kubernetes + for Admins and Ops + +#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)" +#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)" +chat: "In person!" + +gitrepo: github.com/jpetazzo/container.training + +slides: http://container.training/ + +exclude: +- self-paced + +chapters: +- shared/title.md +- logistics.md +- k8s/intro.md +- shared/about-slides.md +- shared/toc.md +- - k8s/prereqs-admin.md + - k8s/architecture.md + - k8s/dmuc.md +- - k8s/multinode.md + - k8s/cni.md + - k8s/apilb.md + - k8s/kuberouter.md + - k8s/interco.md + #FIXME: check le talk de Laurent Corbes pour voir s'il y a d'autres choses utiles à mentionner + #BONUS: intégration CoreDNS pour résoudre les noms des clusters des voisins +- - k8s/setup-managed.md + - k8s/setup-selfhosted.md + - k8s/cluster-upgrade.md + - k8s/cluster-configuration.md + - k8s/cluster-backup.md + - k8s/cloud-controller-manager.md + - k8s/bootstrap.md +- - k8s/resource-limits.md + - k8s/metrics-server.md + - k8s/cluster-sizing.md +- - k8s/lastwords-admin.md + - k8s/links.md + - shared/thankyou.md From a4b23e3f02c0b8094bd7a2e8f4641bc9441cf4ac Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Fri, 5 Apr 2019 09:13:27 -0500 Subject: [PATCH 05/51] Add kubenet lab --- slides/k8s/multinode.md | 513 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100644 slides/k8s/multinode.md diff --git a/slides/k8s/multinode.md b/slides/k8s/multinode.md new file mode 100644 index 00000000..65f66d7a --- /dev/null +++ b/slides/k8s/multinode.md @@ -0,0 +1,513 @@ +# Adding nodes to the cluster + +- So far, our cluster has only 1 node + +- Let's see what it takes to add more nodes + +- We are going to use another set of machines: `kubenet` + +--- + +## The environment + +- We have 3 identical machines: `kubenet1`, `kubenet2`, `kubenet3` + +- The Docker Engine is installed (and running) on these machines + +- The Kubernetes packages are installed, but nothing is running + +- We will use `kubenet1` to run the control plane + +--- + +## The plan + +- Start the control plane on `kubenet1` + +- Join the 3 nodes to the cluster + +- Deploy and scale a simple web server + +.exercise[ + +- Log into `kubenet1` + +] + +--- + +## Running the control plane + +- We will use a Compose file to start the control plane components + +.exercise[ + +- Clone the repository containing the workshop materials: + ```bash + git clone https://@@GITREPO@@ + ``` + +- Go to the `compose/simple-k8s-control-plane` directory: + ```bash + cd orchestration-workshop/compose/simple-k8s-control-plane + ``` + +- Start the control plane: + ```bash + docker-compose up + ``` + +] + +--- + +## Checking the control plane status + +- Before moving on, verify that the control plane works + +.exercise[ + +- Show control plane components statuses: + ```bash + kubectl get componentstatuses + kubectl get cs + ``` + +- Show the (empty) list of nodes: + ```bash + kubectl get nodes + ``` + +] + +--- + +class: extra-details + +## Differences with `dmuc` + +- Our new control plane listens on `0.0.0.0` instead of the default `127.0.0.1` + +- The ServiceAccount admission plugin is disabled + +--- + +## Joining the nodes + +- We need to generate a `kubeconfig` file for kubelet + +- This time, we need to put the IP address of `kubenet1` + + (instead of `localhost` or `127.0.0.1`) + +.exercise[ + +- Generate the `kubeconfig` file: + ```bash + kubectl --kubeconfig ~/kubeconfig config \ + set-cluster kubenet --server http://`X.X.X.X`:8080 + kubectl --kubeconfig ~/kubeconfig config \ + set-context kubenet --cluster kubenet + kubectl --kubeconfig ~/kubeconfig config\ + use-context kubenet + ``` + +] + +--- + +## Distributing the `kubeconfig` file + +- We need that `kubeconfig` file on the other nodes, too + +.exercise[ + +- Copy `kubeconfig` to the other nodes: + ```bash + for N in 2 3; do + scp ~/kubeconfig kubenet$N: + done + ``` + +] + +--- + +## Starting kubelet + +- Reminder: kubelet needs to run as root; don't forget `sudo`! + +.exercise[ + +- Join the first node: + ```bash + sudo kubelet --kubeconfig ~/kubeconfig + ``` + +- Open more terminals and join the other nodes: + ```bash + ssh kubenet2 sudo kubelet --kubeconfig ~/kubeconfig + ssh kubenet3 sudo kubelet --kubeconfig ~/kubeconfig + ``` + +] + +--- + +## Checking cluster status + +- We should now see all 3 nodes + +- At first, their `STATUS` will be `NotReady` + +- They will move to `Ready` state after approximately 10 seconds + +.exercise[ + +- Check the list of nodes: + ```bash + kubectl get nodes + ``` + +] + +--- + +## Deploy a web server + +- Let's create a Deployment and scale it + + (so that we have multiple pods on multiple nodes) + +.exercise[ + +- Create a Deployment running NGINX: + ```bash + kubectl create deployment web --image=nginx + ``` + +- Scale it: + ```bash + kubectl scale deployment web --replicas=5 + ``` + +] + +--- + +## Check our pods + +- The pods will be scheduled to the nodes + +- The nodes will pull the `nginx` image, and start the pods + +- What are the IP addresses of our pods? + +.exercise[ + +- Check the IP addresses of our pods + ```bash + kubectl get pods -o wide + ``` + +] + +-- + +🤔 Something's not right ... Some pods have the same IP address! + +--- + +## What's going on? + +- On a normal cluster, kubelet is configured to set up pod networking with CNI plugins + +- This requires: + + - installing CNI plugins + + - writing CNI configuration files + + - running kubelet with `--network-plugin=cni` + +- Without the `--network-plugin` flag, kubelet defaults to "no-op" networking + +- It lets the container engine use a default network + + (in that case, we end up with the default Docker bridge) + +- Our pods are running on independent, disconnected, host-local networks + +--- + +## Using network plugins + +- We need to set up a better network + +- Before diving into CNI, we will use the `kubenet` plugin + +- This plugin creates a `cbr0` bridge and connects the containers to that bridge + +- This plugin allocates IP addresses from a range: + + - either specified to kubelet (e.g. with `--pod-cidr`) + + - or stored in the node's `spec.podCIDR` field + +.footnote[See [here] for more details about this `kubenet` plugin.] + +[here]: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/#kubenet + +--- + +## What `kubenet` does and *does not* do + +- It allocates IP addresses to pods *locally* + + (each node has its own local subnet) + +- It connects the pods to a *local* bridge + + (pods on the same node can communicate together; not with other nodes) + +- It doesn't set up routing or tunneling + + (we get pods on separated networks; we need to connect them somehow) + +- It doesn't allocate subnets to nodes + + (this can be done manually, or by the controller manager) + +--- + +## Setting up routing or tunneling + +- *On each node*, we will add routes to the other nodes' pod network + +- Of course, this is not convenient or scalable! + +- We will see better techniques to do this; but for now, hang on! + +--- + +## Allocating subnets to nodes + +- There are multiple options: + + - passing the subnet to kubelet with the `--pod-cidr` flag + + - manually setting `spec.podCIDR` on each node + + - allocating node CIDRs automatically with the controller manager + +- The last option would be implemented by adding these flags to controller manager: + ``` + --allocate-node-cidrs=true --cluster-cidr= + ``` + +--- + +class: extra-details + +## The pod CIDR field is not mandatory + +- `kubenet` needs the pod CIDR, but other plugins don't need it + + (e.g. because they allocate addresses in multiple pools, or a single big one) + +- The pod CIDR field may eventually be deprecated and replaced by an annotation + + (see [kubernetes/kubernetes#57130](https://github.com/kubernetes/kubernetes/issues/57130)) + +--- + +## Restarting kubelet wih pod CIDR + +- We need to stop and restart all our kubelets + +- We will add the `--pod-cidr` flag + +- We all have a "cluster number" (let's call that `C`) + +- We will use pod CIDR `10.C.N.0/24` (where `N` is the node number: 1, 2, 3) + +.exercise[ + +- Stop all the kubelets (Ctrl-C is fine) + +- Restart them all, adding `--pod-cidr 10.C.N.0/24` + +] + +--- + +## What happens to our pods? + +- When we stop (or kill) kubelet, the containers keep running + +- When kubelet starts again, it detects the containers + +.exercise[ + +- Check that our pods are still here: + ```bash + kubectl get pods -o wide + ``` + +] + +🤔 But our pods still use local IP addresses! + +--- + +## Recreating the pods + +- The IP address of a pod cannot change + +- kubelet doesn't automatically kill/restart containers will "invalid" addresses +
+ (in fact, from kubelet's point of view, there is no such thing as an "invalid" address) + +- We must delete our pods and recreate them + +.exercise[ + +- Delete all the pods, and let the ReplicaSet recreate them: + ```bash + kubectl delete pods --all + ``` + +- Wait for the pods to be up again: + ```bash + kubectl get pods -o wide -w + ``` + +] + +--- + +## Adding kube-proxy + +- Let's start kube-proxy to provide internal load balancing + +- Then see if we can create a Service and use it to contact our pods + +.exercise[ + +- Start kube-proxy: + ```bash + sudo kube-proxy --kubeconfig ~/kubeconfig + ``` + +- Expose our Deployment: + ```bash + kubectl expose deployment web --port=80 + ``` + +] + +--- + +## Test internal load balancing + +.exercise[ + +- Retrieve the ClusterIP address: + ```bash + kubectl get svc web + ``` + +- Send a few requests to the ClusterIP address (with `curl`) + +] + +-- + +Sometimes it works, sometimes it doesn't. Why? + +--- + +## Routing traffic + +- Our pods have new, distinct, IP addresses + +- But they are on host-local, isolated networks + +- If we try to ping a pod on a different node, it won't work + +- kube-proxy merely rewrites the destination IP address + +- But we need that IP address to be reachable in the first place + +- How do we fix this? + + (hint: check the title of this slide!) + +--- + +## Important warning + +- The technique that we are about to use doesn't work everywhere + +- It only works if: + + - all the nodes are directly connected to each other (at layer 2) + + - the underlying network allows the IP addresses of our pods + +- If we are on physical machines connected by a switch: OK + +- If we are on virtual machines in a public cloud: NOT OK + + - on AWS, we need to disable "source and destination checks" on our instances + + - on OpenStack, we need to disable "port security" on our network ports + +--- + +## Routing basics + +- We need to tell to *each* node: + + "The subnet 10.C.N.0/24 is located on node N" (for all values of N) + +- This is how we add a route on Linux: + ```bash + ip route add 10.C.N.0/24 via W.X.Y.Z + ``` + + (where `W.X.Y.Z` is the internal IP address of node N) + +- We can see the internal IP addresses of our nodes with: + ```bash + kubectl get nodes -o wide + ``` +--- + +## Setting up routing + +.exercise[ + +- Create all the routes on all the nodes + +- Check that you can ping all the pods from one of the nodes + +- Check that you can `curl` the ClusterIP of the Service successfully + +] + +--- + +## What's next? + +- We did a lot of manual operations: + + - allocating subnets to nodes + + - adding command-line flags to kubelet + + - updating the routing tables on our nodes + +- We want to automate all these steps + +- We want something that works on all networks From 2b2d7c554439e4de7d30e87992edddd857e0323d Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sat, 6 Apr 2019 12:00:59 -0500 Subject: [PATCH 06/51] Add CNI section (first part; still needs federation) --- slides/k8s/cni.md | 492 ++++++++++++++++++++++++++++++++++++++++ slides/k8s/multinode.md | 4 +- 2 files changed, 494 insertions(+), 2 deletions(-) create mode 100644 slides/k8s/cni.md diff --git a/slides/k8s/cni.md b/slides/k8s/cni.md new file mode 100644 index 00000000..40ef9ebc --- /dev/null +++ b/slides/k8s/cni.md @@ -0,0 +1,492 @@ +# The Container Network Interface + +- Allows to decouple network configuration from Kubernetes + +- Implemented by *plugins* + +- Plugins are executables that will be invoked by kubelet + +- Plugins are responsible for: + + - allocating IP addresses for containers + + - configuring the network for containers + +- Plugins can be combined and chained when it makes sense + +--- + +## Combining plugins + +- Interface could be created by e.g. `vlan` or `bridge` plugin + +- IP address could be allocated by e.g. `dhcp` or `host-local` plugin + +- Interface parameters (MTU, sysctls) could be tweaked by the `tuning` plugin + +The reference plugins are available [here]. + +Look into each plugin's directory for its documentation. + +[here]: https://github.com/containernetworking/plugins/tree/master/plugins + +--- + +## How does kubelet know which plugins to use? + +- The plugin (or list of plugins) is set in the CNI configuration + +- The CNI configuration is a *single file* in `/etc/cni/net.d` + +- If there are multiple files in that directory, the first one is used + + (in lexicographic order) + +- That path can be changed with the `--cni-conf-dir` flag of kubelet + +--- + +## CNI configuration in practice + +- When we set up the "pod network" (like Calico, Weave...) it ships a CNI configuration + + (and sometimes, custom CNI plugins) + +- Very often, that configuration (and plugins) is installed automatically + + (by a DaemonSet featuring an initContainer with hostPath volumes) + +- Examples: + + - Calico [CNI config](https://github.com/projectcalico/calico/blob/1372b56e3bfebe2b9c9cbf8105d6a14764f44159/v2.6/getting-started/kubernetes/installation/hosted/calico.yaml#L25) + and [volume](https://github.com/projectcalico/calico/blob/1372b56e3bfebe2b9c9cbf8105d6a14764f44159/v2.6/getting-started/kubernetes/installation/hosted/calico.yaml#L219) + + - kube-router [CNI config](https://github.com/cloudnativelabs/kube-router/blob/c2f893f64fd60cf6d2b6d3fee7191266c0fc0fe5/daemonset/generic-kuberouter.yaml#L10) + and [volume](https://github.com/cloudnativelabs/kube-router/blob/c2f893f64fd60cf6d2b6d3fee7191266c0fc0fe5/daemonset/generic-kuberouter.yaml#L73) + +--- + +## Conf vs conflist + +- There are two slightly different configuration formats + +- Basic configuration format: + + - holds configuration for a single plugin + - typically has a `.conf` name suffix + - has a `type` string field in the top-most structure + - [examples](https://github.com/containernetworking/cni/blob/master/SPEC.md#example-configurations) + +- Configuration list format: + + - can hold configuration for multiple (chained) plugins + - typically has a `.conflist` name suffix + - has a `plugins` list field in the top-most structure + - [examples](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration-lists) + +--- + +class: extra-details + +## How plugins are invoked + +- Parameters are given through environment variables, including: + + - CNI_COMMAND: desired operation (ADD, DEL, CHECK, or VERSION) + + - CNI_CONTAINERID: container ID + + - CNI_NETNS: path to network namespace file + + - CNI_IFNAME: how the network interface should be named + +- The network configuration must be provided to the plugin on stdin + + (this avoids race conditions that could happen by passing a file path) + +--- + +## In practice: kube-router + +- We are going to set up a new cluster + +- For this new cluster, we will use kube-router + +- kube-router will provide the "pod network" + + (connectivity with pods) + +- kube-router will also provide internal service connectivity + + (replacing kube-proxy) + +--- + +## How kube-router works + +- Very simple architecture + +- Does not introduce new CNI plugins + + (uses the `bridge` plugin, with `host-local` for IPAM) + +- Pod traffic is routed between nodes + + (no tunnel, no new protocol) + +- Internal service connectivity is implemented with IPVS + +- Can provide pod network and/or internal service connectivity + +- kube-router daemon runs on every node + +--- + +## What kube-router does + +- Connect to the API server + +- Obtain the local node's `podCIDR` + +- Inject it into the CNI configuration file + + (we'll use `/etc/cni/net.d/10-kuberouter.conflist`) + +- Obtain the addresses of all nodes + +- Establish a *full mesh* BGP peering with the other nodes + +- Exchange routes over BGP + +--- + +## What's BGP? + +- BGP (Border Gateway Protocol) is the protocol used between internet routers + +- [It scales pretty well](https://www.cidr-report.org/as2.0/) + (more than 400k aggregated routes on internet) + +- It is spoken by many hardware routers from many vendors + +- It also has many software implementations (Quagga, Bird, FRR...) + +- The network folks may or may not love it; but at least they know it + +- It also used by Calico (another popular network system for Kubernetes) + +- Using BGP allows us to interconnect our "pod network" with other systems + +--- + +## The plan + +- We'll work in a new cluster (named `kuberouter`) + +- We will run a simple control plane (like before) + +- ... But this time, the controller manager with allocate `podCIDR` subnets + +- We will start kube-router with a DaemonSet + +- This DaemonSet will start one instance of kube-router on each node + +--- + +## Logging into the new cluster + +.exercise[ + +- Log into node `kuberouter1` + +- Clone the workshop repository: + ```bash + git clone https://@@GITREPO@@ + ``` + +- Move to this directory: + ```bash + cd container.training/compose/kube-router-k8s-control-plane + ``` + +] + +--- + +## Our control plane + +- We will use a Compose file to start the control plane + +- It is similar to the one we used with the `kubenet` cluster + +- The API server is started with `--allow-privileged` + + (because we will start kube-router in privileged pods) + +- The controller manager is started with extra flags too: + + `--allocate-node-cidrs` and `--cluster-cidr` + +- We need to edit the Compose file to set the Cluster CIDR + +--- + +## Starting the control plane + +- Our cluster CIDR will be `10.C.0.0/16` + + (where `C` is our cluster number) + +.exercise[ + +- Edit the Compose file to set the Cluster CIDR: + ```bash + vim docker-compose.yaml + ``` + +- Start the control plane: + ```bash + docker-compose up + ``` + +] + +--- + +## The kube-router DaemonSet + +- In the same directory, there is a `kuberouter.yaml` file + +- It contains the definition for a DaemonSet and a ConfigMap + +- Before we load it, we also need to edit it + +- We need to indicate the address of the API server + + (because kube-router needs to connect to it to retrieve node information) + +--- + +## Creating the DaemonSet + +- The address of the API server will be `http://A.B.C.D:8080` + + (where `A.B.C.D` is the address of `kuberouter1`, running the control plane) + +.exercise[ + +- Edit the YAML file to set the API server address: + ```bash + vim kuberouter.yaml + ``` + +- Create the DaemonSet: + ```bash + kubectl create -f kuberouter.yaml + ``` + +] + +Note: the DaemonSet won't create any pod (yet) since there are no nodes (yet). + +--- + +## Generating the kubeconfig for kubelet + +- This is similar to what we did for the `kubenet` cluster + +.exercise[ + +- Generate the kubeconfig file (replacing `X.X.X.X` with the address of `kuberouter1`): + ```bash + kubectl --kubeconfig ~/kubeconfig config \ + set-cluster kubenet --server http://`X.X.X.X`:8080 + kubectl --kubeconfig ~/kubeconfig config \ + set-context kubenet --cluster kubenet + kubectl --kubeconfig ~/kubeconfig config\ + use-context kubenet + ``` + +] + +--- + +## Distributing kubeconfig + +- We need to copy that kubeconfig file to the other nodes + +.exercise[ + +- Copy `kubeconfig` to the other nodes: + ```bash + for N in 2 3; do + scp ~/kubeconfig kubenet$N: + done + ``` + +] + +--- + +## Starting kubelet + +- We don't need the `--pod-cidr` option anymore + + (the controller manager will allocate these automatically) + +- We need to pass `--network-plugin=cni` + +.exercise[ + +- Join the first node: + ```bash + sudo kubelet --kubeconfig ~/kubeconfig --network-plugin=cni + ``` + +- Open more terminals and join the other nodes: + ```bash + ssh kubenet2 sudo kubelet --kubeconfig ~/kubeconfig --network-plugin=cni + ssh kubenet3 sudo kubelet --kubeconfig ~/kubeconfig --network-plugin=cni + ``` + +] + +--- + +## Setting up a test + +- Let's create a Deployment and expose it with a Service + +.exercise[ + +- Create a Deployment running a web server: + ```bash + kubectl create deployment web --image=jpetazzo/httpenv + ``` + +- Scale it so that it spans multiple nodes: + ```bash + kubectl scale deployment web --replicas=5 + ``` + +- Expose it with a Service: + ```bash + kubectl expose deployment web --port=8888 + ``` + +] + +--- + +## Checking that everything works + +.exercise[ + +- Get the ClusterIP address for the service: + ```bash + kubectl get svc web + ``` + +- Send a few requests there: + ```bash + curl `X.X.X.X`:8888 + ``` + +] + +Note that if you send multiple requests, they are load-balanced in a round robin manner. + +This shows that we are using IPVS (vs. iptables, which picked random endpoints). + +--- + +## Troubleshooting + +- What if we need to check that everything is working properly? + +.exercise[ + +- Check the IP addresses of our pods: + ```bash + kubectl get pods -o wide + ``` + +- Check our routing table: + ```bash + route -n + ip route + ``` + +] + +We should see the local pod CIDR connected to `kube-bridge`, and the other nodes' pod CIDRs having individual routes, with each node being the gateway. + +--- + +## More troubleshooting + +- Of course, we can also look at the output of the kube-router pods + +.exercise[ + +- Try to show the logs of a kube-router pod: + ```bash + kubectl -n kube-system logs ds/kube-router + ``` + +] + +We get an error message including: +``` +dial tcp: lookup kuberouterX on 127.0.0.11:53: no such host +``` + +What is this about? + +--- + +## Internal name resolution + +- When we do `kubectl logs`, the API server needs to connect to kubelet + + (also for e.g. `kubectl exec`) + +- By default, looks up the kubelet's provided node name in DNS + + (e.g. `kuberouter1`) + +- We can change that by setting a flag on the API server: + + `--kubelet-preferred-address-types=InternalIP` + +--- + +## Another way to check the logs + +- We can also ask the logs directly to the container engine + +- First, get the container ID, with `docker ps` or like this: + ```bash + CID=$(docker ps + --filter label=io.kubernetes.pod.namespace=kube-system + --filter label=io.kubernetes.container.name=kube-router) + ``` + +- Then view the logs: + ```bash + docker logs $CID + ``` + +--- + +## What's next? + +- We assigned different Cluster CIDRs to each cluster + +- This allows us to connect our clusters together + +- We will leverage kube-router BGP abilities for that + +- We will *peer* each kube-router instance with a *route reflector* + +- As a result, we will be able to ping each other's pods diff --git a/slides/k8s/multinode.md b/slides/k8s/multinode.md index 65f66d7a..096e92eb 100644 --- a/slides/k8s/multinode.md +++ b/slides/k8s/multinode.md @@ -325,7 +325,7 @@ class: extra-details - We need to stop and restart all our kubelets -- We will add the `--pod-cidr` flag +- We will add the `--network-plugin` and `--pod-cidr` flags - We all have a "cluster number" (let's call that `C`) @@ -335,7 +335,7 @@ class: extra-details - Stop all the kubelets (Ctrl-C is fine) -- Restart them all, adding `--pod-cidr 10.C.N.0/24` +- Restart them all, adding `--network-plugin=kubenet --pod-cidr 10.C.N.0/24` ] From 637c46e37257befe35d4e244ed7eaf79bdb90247 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sun, 7 Apr 2019 11:22:46 -0500 Subject: [PATCH 07/51] Add cluster interconnection with a route reflector --- slides/k8s/cni.md | 141 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/slides/k8s/cni.md b/slides/k8s/cni.md index 40ef9ebc..3736757d 100644 --- a/slides/k8s/cni.md +++ b/slides/k8s/cni.md @@ -479,7 +479,7 @@ What is this about? --- -## What's next? +# Interconnecting clusters - We assigned different Cluster CIDRs to each cluster @@ -490,3 +490,142 @@ What is this about? - We will *peer* each kube-router instance with a *route reflector* - As a result, we will be able to ping each other's pods + +--- + +## Disclaimers + +- There are many methods to interconnect clusters + +- Depending on your network implementation, you will use different methods + +- The method shown here only works for nodes with direct layer 2 connection + +- We will often need to use tunnels or other network techniques + +--- + +## The plan + +- Someone will start the *route reflector* + + (typically, that will be the person presenting these slides!) + +- We will update our kube-router configuration + +- We will add a *peering* with the route reflector + + (instructing kube-router to connect to it and exchange route information) + +- We should see the routes to other clusters on our nodes + + (in the output of e.g. `route -n` or `ip route show`) + +- We should be able to ping pods of other nodes + +--- + +## Starting the route reflector + +- Only do this if you are doing this on your own + +- There is a Compose file in the `compose/frr-route-reflector` directory + +- Before continuing, make sure that you have the IP address of the route reflector + +--- + +## Configuring kube-router + +- This can be done in two ways: + + - with command-line flags to the `kube-router` process + + - with annotations to Node objects + +- We will use the command-line flags + + (because it will automatically propagate to all nodes) + +.footnote[Note: with Calico, this is achieved by creating a BGPPeer CRD.] + +--- + +## Updating kube-router configuration + +- We need to add two command-line flags to the kube-router process + +.exercise[ + +- Edit the `kuberouter.yaml` file + +- Add the following flags to the kube-router arguments,: + ``` + - "--peer-router-ips=`X.X.X.X`" + - "--peer-router-asns=64512" + ``` + (Replace `X.X.X.X` with the route reflector address) + +- Update the DaemonSet definition: + ```bash + kubectl apply -f kuberouter.yaml + ``` + +] + +--- + +## Restarting kube-router + +- The DaemonSet will not restart the pods automatically + +- For simplicity, we will delete the pods + + (they will be recreated with the updated definition) + +.exercise[ + +- Delete all the kube-router pods: + ```bash + kubectl delete pods -n kube-system -l k8s-app=kube-router + ``` + +] + +--- + +## Checking peering status + +- We can see informative messages in the output of kube-router: + ``` + time="2019-04-07T15:53:56Z" level=info msg="Peer Up" Key=X.X.X.X State=BGP_FSM_OPENCONFIRM Topic=Peer + ``` + +- We should see the routes of the other clusters show up + +- For debugging purposes, the reflector also exports a route to 1.0.0.2/32 + +- That route will show up like this: + ``` + 1.0.0.2 172.31.X.Y 255.255.255.255 UGH 0 0 0 eth0 + ``` + +- We should be able to ping the pods of other clusters! + +--- + +## If we wanted to do more ... + +- kube-router can also export ClusterIP addresses + + (by adding the flag `--advertise-cluster-ip`) + +- They are exported individually (as /32) + +- This would allow us to easily access other clusters' services + + (without having to resolve the individual addresses of pods) + +- Even better if it's combined with DNS integration + + (to facilitate name → ClusterIP resolution) From c44449399a1e1a29a84d16b26742b5c761e6e6d7 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Mon, 8 Apr 2019 04:10:28 -0500 Subject: [PATCH 08/51] Add API load balancer --- slides/k8s/apilb.md | 89 +++++++++++++++++++++++++++++++++++++++ slides/kube-admin-one.yml | 2 - 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 slides/k8s/apilb.md diff --git a/slides/k8s/apilb.md b/slides/k8s/apilb.md new file mode 100644 index 00000000..bdec57a8 --- /dev/null +++ b/slides/k8s/apilb.md @@ -0,0 +1,89 @@ +# API server availability + +- When we set up a node, we need the address of the API server: + + - for kubelet + + - for kube-proxy + + - sometimes for the pod network system (like kube-router) + +- How do we ensure the availability of that endpoint? + + (what if the node running the API server goes down?) + +--- + +## Option 1: external load balancer + +- Set up an external load balancer + +- Point kubelet (and other components) to that load balancer + +- Put the node(s) running the API server behind that load balancer + +- Update the load balancer if/when an API server node needs to be replaced + +- On cloud infrastructures, some mechanisms provide automation for this + + (e.g. on AWS, an Elastic Load Balancer + Auto Scaling Group) + +- [Example in Kubernetes The Hard Way](https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/08-bootstrapping-kubernetes-controllers.md#the-kubernetes-frontend-load-balancer) + +--- + +## Option 2: local load balancer + +- Set up a load balancer (like NGINX, HAProxy...) on *each* node + +- Configure that load balancer to send traffic to the API server node(s) + +- Point kubelet (and other components) to `localhost` + +- Update the load balancer configuration when API server nodes are updated + +--- + +## Updating the local load balancer config + +- Distribute the updated configuration (push) + +- Or regularly check for updates (pull) + +- The latter requires an external, highly available store + + (it could be an object store, an HTTP server, or even DNS...) + +- Updates can be facilitated by a DaemonSet + + (but remember that it can't be used when installing a new node!) + +--- + +## Option 3: DNS records + +- Put all the API server nodes behind a round-robin DNS + +- Point kubelet (and other components) to that name + +- Update the records when needed + +- Note: this option is not officially supported + + (but since kubelet supports reconnection anyway, it *should* work) + +--- + +## Option 4: .................... + +- Many managed clusters expose a high-availability API endpoint + + (and you don't have to worry about it) + +- You can also use HA mechanisms that you're familiar with + + (e.g. virtual IPs) + +- Tunnels are also fine + + (e.g. k3s uses a tunnel to allow each node to contact the API server) diff --git a/slides/kube-admin-one.yml b/slides/kube-admin-one.yml index 6c3dea9d..9ae83d17 100644 --- a/slides/kube-admin-one.yml +++ b/slides/kube-admin-one.yml @@ -25,8 +25,6 @@ chapters: - - k8s/multinode.md - k8s/cni.md - k8s/apilb.md - - k8s/kuberouter.md - - k8s/interco.md #FIXME: check le talk de Laurent Corbes pour voir s'il y a d'autres choses utiles à mentionner #BONUS: intégration CoreDNS pour résoudre les noms des clusters des voisins - - k8s/setup-managed.md From ff4219ab5ddbebcd77a2a5935be0aae293b0bcf2 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Mon, 8 Apr 2019 06:15:23 -0500 Subject: [PATCH 09/51] Add managed installation options --- slides/k8s/setup-managed.md | 235 ++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 slides/k8s/setup-managed.md diff --git a/slides/k8s/setup-managed.md b/slides/k8s/setup-managed.md new file mode 100644 index 00000000..7097319b --- /dev/null +++ b/slides/k8s/setup-managed.md @@ -0,0 +1,235 @@ +# Installing a managed cluster + +*"The easiest way to install Kubernetes is to get someone +else to do it for you." +
+([Jérôme Petazzoni](https://twitter.com/jpetazzo))* + +- Let's see a few options to install managed clusters! + +- All the options mentioned here require an account +with a cloud provider + +- ... And a credit card + +--- + +## EKS (the hard way) + +- [Read the doc](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html) + +- Create service roles, VPCs, and a bunch of other oddities + +- Try to figure out why it doesn't work + +- Start over, following an [official AWS blog post](https://aws.amazon.com/blogs/aws/amazon-eks-now-generally-available/) + +- Try to find the missing Cloud Formation template + +-- + +.footnote[(╯°□°)╯︵ ┻━┻] + +--- + +## EKS (the easy way) + +- Install `eksctl` + +- Set the usual environment variables + + ([AWS_DEFAULT_REGION](https://docs.aws.amazon.com/general/latest/gr/rande.html#eks_region), AWS_ACCESS_KEY, AWS_SECRET_ACCESS_KEY) + +- Create the cluster: + ```bash + eksctl create cluster + ``` + +- Wait 15-20 minutes (yes, it's sloooooooooooooooooow) + +- Add cluster add-ons + + (by default, it doesn't come with metrics-server, logging, etc.) + +--- + +## EKS (cleanup) + +- Delete the cluster: + ```bash + eksctl delete cluster + ``` + +- If you need to find the name of the cluster: + ```bash + eksctl get clusters + ``` + +--- + +## GKE (initial setup) + +- Install `gcloud` + +- Login: + ```bash + gcloud auth init + ``` + +- Create a "project": + ```bash + gcloud projects create my-gke-project + gcloud config set project my-gke-project + ``` + +- Pick a [region](https://cloud.google.com/compute/docs/regions-zones/) + + (example: `europe-west1`, `us-west1`, ...) + +--- + +## GKE (create cluster) + +- Create the cluster: + ```bash + gcloud container clusters create my-gke-cluster --region us-west1 --num-nodes=2 + ``` + + (without `--num-nodes` you might exhaust your IP address quota!) + +- The first time you try to create a cluster in a given project, you get an error + + - you need to enable the Kubernetes Engine API + - the error message gives you a link + - follow the link and enable the API (and billing) +
(it's just a couple of clicks and it's instantaneous) + +- Wait a couple of minutes (yes, it's faaaaaaaaast) + +- The cluster comes with many add-ons + +--- + +## GKE (cleanup) + +- List clusters (if you forgot its name): + ```bash + gcloud container clusters list + ``` + +- Delete the cluster: + ```bash + gcloud container clusters delete my-gke-cluster --region us-west1 + ``` + +- Delete the project (optional): + ```bash + gcloud projects delete my-gke-project + ``` + +--- + +## AKS (initial setup) + +- Install the Azure CLI + +- Login: + ```bash + az login + ``` + +- Select a [region](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=kubernetes-service®ions=all +) + +- Create a "resource group": + ```bash + az group create --name my-aks-group --location westeurope + ``` + +--- + +## AKS (create cluster) + +- Create the cluster: + ```bash + az aks create --resource-group my-aks-group --name my-aks-cluster + ``` + +- Wait 15 minutes (sometimes longer) + +- Add credentials to `kubeconfig`: + ```bash + az aks get-credentials --resource-group my-aks-group --name my-aks-cluster + ``` + +- The cluster has a lot of goodies pre-installed + +--- + +## AKS (cleanup) + +- Delete the cluster: + ```bash + az aks delete --resource-group my-aks-group --name my-aks-cluster + ``` + +- Delete the resource group: + ```bash + az group delete --resource-group aks-test + ``` + +- Note: delete actions can take a long time too! + + (10 minutes is typical) + +--- + +## Digital Ocean (initial setup) + +- Install `doctl` + +- Generate API token (in web console) + +- Set up the CLI authentication: + ```bash + doctl auth init + ``` + (It will ask you for the API token) + +- Check the list of regions and pick one: + ```bash + doctl compute region list + ``` + (If you don't specify the region later, it will use `nyc1`) + +--- + +## Digital Ocean (create cluster) + +- Create the cluster: + ```bash + doctl kubernetes cluster create my-do-cluster [--region xxx1] + ``` + +- Wait 5 minutes + +- Update `kubeconfig`: + ```bash + kubectl config use-context do-xxx1-my-do-cluster + ``` + +- The cluster comes with some goodies (like Cilium) but no metrics server + +--- + +## Digital Ocean (cleanup) + +- List clusters (if you forgot its name): + ```bash + doctl kubernetes cluster list + ``` + +- Delete the cluster: + ```bash + doctl kubernetes cluster delete my-do-cluster + ``` From 6636f92cf5fc6866698a77b059d778fdf7f6875a Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Mon, 8 Apr 2019 06:38:13 -0500 Subject: [PATCH 10/51] Add a few more managed options --- slides/k8s/setup-managed.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/slides/k8s/setup-managed.md b/slides/k8s/setup-managed.md index 7097319b..4c8418bf 100644 --- a/slides/k8s/setup-managed.md +++ b/slides/k8s/setup-managed.md @@ -233,3 +233,17 @@ with a cloud provider ```bash doctl kubernetes cluster delete my-do-cluster ``` + +--- + +## More options + +- Alibaba Cloud + +- [IBM Cloud](https://console.bluemix.net/docs/containers/cs_cli_install.html#cs_cli_install) + +- OVH + +- Scaleway (private beta) + +- ... From 82c26c2f1944d97ccb9cea1cc1ad9000fb07752f Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Mon, 8 Apr 2019 06:39:07 -0500 Subject: [PATCH 11/51] Oops (thanks @rdegez for catching that one) --- slides/k8s/dmuc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/dmuc.md b/slides/k8s/dmuc.md index f16e9b4a..f5452032 100644 --- a/slides/k8s/dmuc.md +++ b/slides/k8s/dmuc.md @@ -794,7 +794,7 @@ class: extra-details This is a `DNAT` rule to rewrite the destination address of the connection to our pod. -This is how kube-router works! +This is how kube-proxy works! --- From 2d3ddc570e9f181a88d342696451226cd918e1ce Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Mon, 8 Apr 2019 06:56:06 -0500 Subject: [PATCH 12/51] Add mention to kube-router special shell (thanks @rdegez) --- slides/k8s/cni.md | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/slides/k8s/cni.md b/slides/k8s/cni.md index 3736757d..7021fea8 100644 --- a/slides/k8s/cni.md +++ b/slides/k8s/cni.md @@ -425,7 +425,21 @@ We should see the local pod CIDR connected to `kube-bridge`, and the other nodes ## More troubleshooting -- Of course, we can also look at the output of the kube-router pods +- We can also look at the output of the kube-router pods + + (with `kubectl logs`) + +- kube-router also comes with a special shell that gives lots of useful info + + (we can access it with `kubectl exec`) + +- But with the current setup of the cluster, these options may not work! + +- Why? + +--- + +## Trying `kubectl logs` / `kubectl exec` .exercise[ @@ -434,26 +448,31 @@ We should see the local pod CIDR connected to `kube-bridge`, and the other nodes kubectl -n kube-system logs ds/kube-router ``` +- Or to exec into one of the kube-router pods: + ```bash + kubectl -n kube-system exec kuber-router-xxxxx bash + ``` + ] -We get an error message including: +These commands will give an error message that includes: ``` dial tcp: lookup kuberouterX on 127.0.0.11:53: no such host ``` -What is this about? +What does that mean? --- ## Internal name resolution -- When we do `kubectl logs`, the API server needs to connect to kubelet +- To execute these commands, the API server needs to connect to kubelet - (also for e.g. `kubectl exec`) +- By default, it creates a connection using the kubelet's name -- By default, looks up the kubelet's provided node name in DNS + (e.g. `http://kuberouter1:...`) - (e.g. `kuberouter1`) +- This requires our nodes names to be in DNS - We can change that by setting a flag on the API server: From 287f6e1cdf997de687dc3587de6e5df2d5e1f7a2 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Mon, 8 Apr 2019 12:21:04 -0500 Subject: [PATCH 13/51] Reword a few BGP things (Thanks Benji) --- slides/k8s/cni.md | 5 +++-- slides/k8s/setup-managed.md | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/slides/k8s/cni.md b/slides/k8s/cni.md index 7021fea8..16ddfeba 100644 --- a/slides/k8s/cni.md +++ b/slides/k8s/cni.md @@ -164,14 +164,15 @@ class: extra-details - BGP (Border Gateway Protocol) is the protocol used between internet routers -- [It scales pretty well](https://www.cidr-report.org/as2.0/) +- [It scales](https://www.cidr-report.org/as2.0/) + [pretty well](https://www.cidr-report.org/cgi-bin/plota?file=%2fvar%2fdata%2fbgp%2fas2.0%2fbgp-active%2etxt&descr=Active%20BGP%20entries%20%28FIB%29&ylabel=Active%20BGP%20entries%20%28FIB%29&with=step) (more than 400k aggregated routes on internet) - It is spoken by many hardware routers from many vendors - It also has many software implementations (Quagga, Bird, FRR...) -- The network folks may or may not love it; but at least they know it +- Experimented network folks generally know it (and appreciate it) - It also used by Calico (another popular network system for Kubernetes) diff --git a/slides/k8s/setup-managed.md b/slides/k8s/setup-managed.md index 4c8418bf..74d94d36 100644 --- a/slides/k8s/setup-managed.md +++ b/slides/k8s/setup-managed.md @@ -7,6 +7,10 @@ else to do it for you." - Let's see a few options to install managed clusters! +- This is not an exhaustive list + + (the goal is to show the actual steps to get started) + - All the options mentioned here require an account with a cloud provider From 9cc422f78297ce622675fa64b535a2c5556ec393 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Tue, 9 Apr 2019 03:32:14 -0500 Subject: [PATCH 14/51] Add distributions & installers --- slides/k8s/setup-selfhosted.md | 108 +++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 slides/k8s/setup-selfhosted.md diff --git a/slides/k8s/setup-selfhosted.md b/slides/k8s/setup-selfhosted.md new file mode 100644 index 00000000..d2302524 --- /dev/null +++ b/slides/k8s/setup-selfhosted.md @@ -0,0 +1,108 @@ +# Kubernetes distributions and installers + +- There are [countless](https://kubernetes.io/docs/setup/pick-right-solution/) distributions available + +- We can't review them all + +- We're just going to explore a few options + +--- + +## kops + +- Deploys Kubernetes using cloud infrastructure + + (supports AWS, GCE, Digital Ocean ...) + +- Leverages special cloud features when possible + + (e.g. Auto Scaling Groups ...) + +--- + +## kubeadm + +- Provisions Kubernetes nodes on top of existing machines + +- `kubeadm init` to provision a single-node control plane + +- `kubeadm join` to join a node to the cluster + +- Supports HA control plane [with some extra steps](https://kubernetes.io/docs/setup/independent/high-availability/) + +--- + +## Kubespray + +- Based on Ansible + +- Works on bare metal and cloud infrastructure + + (good for hybrid deployments) + +- The expert says: ultra flexible; slow; complex + +--- + +## RKE (Rancher Kubernetes Engine) + +- Opinionated installer with low requirements + +- Requires a set of machines with Docker + SSH access + +- Supports highly available etcd and control plane + +- The expert says: fast; maintenance can be tricky + +--- + +## Terraform + kubeadm + +- Sometimes it is necessary to build a custom solution + +- Example use case: + + - deploying Kubernetes on OpenStack + + - ... with highly available control plane + + - ... and Cloud Controller Manager integration + +- Solution: Terraform + kubeadm (kubeadm driven by remote-exec) + + - [GitHub repository](https://github.com/enix/terraform-openstack-kubernetes) + + - [Blog post (in French)](https://enix.io/fr/blog/deployer-kubernetes-1-13-sur-openstack-grace-a-terraform/) + +--- + +## And many more ... + +- Docker Enterprise Edition + +- Pivotal Container Service (PKS) + +- Tectonic by CoreOS + +- etc. + +--- + +## Bottom line + +- Each distribution / installer has pros and cons + +- Before picking one, we should sort our priorities: + + - cloud, on-premises, hybrid? + + - integration with existing network/storage architecture or equipment? + + - are we storing very sensitive data, like finance, health, military? + + - how many clusters are we deploying (and maintaining): 2, 10, 50? + + - which team will be responsible for deployment and maintenance? +
(do they need training?) + + - etc. From 0d551f682e28cc3e54bf1463b98fb07fbf828297 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Tue, 9 Apr 2019 09:42:28 -0500 Subject: [PATCH 15/51] Add chapter about cluster upgrades + static pods --- slides/k8s/cluster-upgrade.md | 305 ++++++++++++++++++++++++++++++++++ slides/k8s/staticpods.md | 4 + slides/kube-admin-one.yml | 3 +- 3 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 slides/k8s/cluster-upgrade.md diff --git a/slides/k8s/cluster-upgrade.md b/slides/k8s/cluster-upgrade.md new file mode 100644 index 00000000..6a40083f --- /dev/null +++ b/slides/k8s/cluster-upgrade.md @@ -0,0 +1,305 @@ +# Upgrading clusters + +- It's *recommended* to run consistent versions across a cluster + + (mostly to have feature parity and latest security updates) + +- It's not *mandatory* + + (otherwise, cluster upgrades would be a nightmare!) + +- Components can be upgraded one at a time without problems + +--- + +## Checking what we're running + +- It's easy to check the version for the API server + +.exercise[ + +- Check the version of kubectl and of the API server: + ```bash + kubectl version + ``` + +] + +- In a HA setup with multiple API servers, they can have different versions + +- Running the command above multiple times can return different values + +--- + +## Node versions + +- It's also easy to check the version of kubelet + +.exercise[ + +- Check node versions (includes kubelet, kernel, container engine): + ```bash + kubectl get nodes -o wide + ``` + +] + +- Different nodes can run different kubelet versions + +- Different nodes can run different kernel versions + +- Different nodes can run different container engines + +--- + +## Control plane versions + +- If the control plane is self-hosted (running in pods), we can check it + +.exercise[ + +- Show image versions for all pods in `kube-system` namespace: + ```bash + kubectl --namespace=kube-system get pods -o json \ + | jq -r ' + .items[] + | [.spec.nodeName, .metadata.name] + + + (.spec.containers[].image | split(":")) + | @tsv + ' \ + | column -t + ``` + +] + +--- + +## What version are we running anyway? + +- When I say, "I'm Kubernetes 1.11", is that the version of: + + - kubectl + + - API server + + - kubelet + + - controller manager + + - something else? + +--- + +## Other versions that are important + +- etcd + +- kube-dns or CoreDNS + +- CNI plugin(s) + +- Network controller, network policy controller + +- Linux kernel + +--- + +## General guidelines + +- To update a component, use whatever was used to install it + +- If it's a distro package, update that distro package + +- If it's a container or pod, update that container or pod + +- If you used configuration management, update with that + +--- + +## Know where your binaries come from + +- Sometimes, we need to upgrade *quickly* + + (when a vulnerability is announced and patched) + +- If we are using an installer, we should: + + - make sure it's using upstream packages + + - or make sure that whatever packages it uses are current + + - make sure we can tell it to pin specific component versions + +--- + +## In practice + +- We are going to update a few cluster components + +- We will change the kubelet version on one node + +- We will change the version of the API server + +- We will work with cluster `test` (nodes `test1`, `test2`, `test3`) + +--- + +## Updating kubelet + +- These nodes have been installed using the official Kubernetes packages + +- We can therefore use `apt` or `apt-get` + +.exercise[ + +- Log into node `test3` + +- View available versions for package `kubelet`: + ```bash + apt show kubelet -a | grep ^Version + ``` + +- Upgrade kubelet: + ```bash + apt install kubelet=1.14.1-00 + ``` + +] + +--- + +## Checking what we've done + +.exercise[ + +- Log into node `test1` + +- Check node versions: + ```bash + kubectl get nodes -o wide + ``` + +- Create a deployment and scale it to make sure that the node still works + +] + +--- + +## Updating the API server + +- This cluster has been deployed with kubeadm + +- The control plane runs in *static pods* + +- These pods are started automatically by kubelet + + (even when kubelet can't contact the API server) + +- They are defined in YAML files in `/etc/kubernetes/manifests` + + (this path is set by a kubelet command-line flag) + +- kubelet automatically updates the pods when the files are changed + +--- + +## Changing the API server version + +- We will edit the YAML file to use a different image version + +.exercise[ + +- Log into node `test1` + +- Check API server version: + ```bash + kubectl version + ``` + +- Edit the API server pod manifest: + ```bash + sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml + ``` + +- Look for the `image:` line, and update it to e.g. `v1.14.0` + +] + +--- + +## Checking what we've done + +- The API server will be shortly unavailable while kubelet restarts it + +.exercise[ + +- Check the API server version: + ```bash + kubectl version + ``` + +] + +--- + +## Updating the whole control plane + +- As an example, we'll use kubeadm to upgrade the entire control plane + + (note: this is possible only because the cluster was installed with kubeadm) + +.exercise[ + +- Check what will be upgraded: + ```bash + sudo kubeadm upgrade plan + ``` + + (Note: kubeadm is confused by our manual upgrade of the API server. +
It thinks the cluster is running 1.14.0!) + + + +- Perform the upgrade: + ```bash + sudo kubeadm upgrade apply v1.14.1 + ``` + +] + +--- + +## Updating kubelets + +- After updating the control plane, we need to update each kubelet + +- This requires to run a special command on each node, to download the config + + (this config is generated by kubeadm) + +.exercise[ + +- Download the configuration on each node, and upgrade kubelet: + ```bash + for N in 1 2 3; do + ssh node$N sudo kubeadm upgrade node config --kubelet-version v1.14.1 + ssh node $N sudo apt install kubelet=1.14.1-00 + done + ``` +] + +--- + +## Checking what we've done + +- All our nodes should now be updated to version 1.14.1 + +.exercise[ + +- Check nodes versions: + ```bash + kubectl get nodes -o wide + ``` + +] diff --git a/slides/k8s/staticpods.md b/slides/k8s/staticpods.md index ae9be452..e979cc2b 100644 --- a/slides/k8s/staticpods.md +++ b/slides/k8s/staticpods.md @@ -192,6 +192,8 @@ We should see YAML files corresponding to the pods of the control plane. --- +class: static-pods-exercise + ## Running a static pod - We are going to add a pod manifest to the directory, and kubelet will run it @@ -214,6 +216,8 @@ The output should include a pod named `hello-node1`. --- +class: static-pods-exercise + ## Remarks In the manifest, the pod was named `hello`. diff --git a/slides/kube-admin-one.yml b/slides/kube-admin-one.yml index 9ae83d17..4dac2298 100644 --- a/slides/kube-admin-one.yml +++ b/slides/kube-admin-one.yml @@ -12,6 +12,7 @@ slides: http://container.training/ exclude: - self-paced +- static-pods-exercise chapters: - shared/title.md @@ -30,7 +31,7 @@ chapters: - - k8s/setup-managed.md - k8s/setup-selfhosted.md - k8s/cluster-upgrade.md - - k8s/cluster-configuration.md + - k8s/staticpods.md - k8s/cluster-backup.md - k8s/cloud-controller-manager.md - k8s/bootstrap.md From 4784a41a37c14b72913cbd3859090962992bb965 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Tue, 9 Apr 2019 13:58:46 -0500 Subject: [PATCH 16/51] Add chapter about backups --- slides/k8s/cluster-backup.md | 301 +++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 slides/k8s/cluster-backup.md diff --git a/slides/k8s/cluster-backup.md b/slides/k8s/cluster-backup.md new file mode 100644 index 00000000..283ee7cf --- /dev/null +++ b/slides/k8s/cluster-backup.md @@ -0,0 +1,301 @@ +# Backing up clusters + +- Backups can have multiple purposes: + + - disaster recovery (servers or storage are destroyed or unreachable) + + - error recovery (human or process has altered or corrupted data) + + - cloning environments (for testing, validation ...) + +- Let's see the strategies and tools available with Kubernetes! + +--- + +## Important + +- Kubernetes helps us with disaster recovery + + (it gives us replication primitives) + +- Kubernetes helps us to clone / replicate environments + + (all resources can be described with manifests) + +- Kubernetes *does not* help us with error recovery + +- This is the job of the storage layer + +- We still need to do e.g. database backups + + (with snapshots, or tools like mysqldump, pgdump, etc.) + +--- + +## In a perfect world ... + +- The deployment of our Kubernetes clusters is automated + + (recreating a cluster takes less than a minute of human time) + +- All the resources (Deployments, Services...) on our clusters are under version control + + (never use `kubectl run`; always apply YAML files coming from a repository) + +- Stateful components are either: + + - stored on systems with regular snapshots + + - backed up regularly to an external, durable storage + + - outside of Kubernetes + +--- + +## Kubernetes cluster deployment + +- If our deployment system isn't fully automated, it should at least be documented + +- Litmus test: how long does it take to deploy a cluster ... + + - for a senior engineer? + + - for a new hire? + +- Does it require external intervention? + + (e.g. provisioning servers, signing TLS certs ...) + +--- + +## Plan B + +- Full machine backups of the control plane can help + +- If the control plane is in pods (or containers), pay attention to storage drivers + + (if the backup mechanism is not container-aware, the backups can take way more resources than they should, or even be unusable!) + +- If the previous sentence worries you: + + **automate the deployment of your clusters!** + +--- + +## Managing our Kubernetes resources + +- Ideal scenario: + + - never create a resource directly on a cluster + + - push to a code repository + + - a special branch (`production` or even `master`) gets automatically deployed + +- Some folks call this "GitOps" + + (it's the logical evolution of configuration management and infrastructure as code) + +--- + +## GitOps in theory + +- What do we keep in version control? + +- For very simple scenarios: source code, Dockerfiles, scripts + +- For real applications: add resources (as YAML files) + +- For applications deployed multiple times: Helm, Kustomize ... + + (staging and production count as "multiple times") + +--- + +## GitOps tooling + +- Various tools exist (Weave Flux, GitKube...) + +- These tools are still very young + +- You still need to write YAML for all your resources + +- There is no tool to: + + - list *all* resources in a namespace + + - get resource YAML in a canonical form + + - diff YAML descriptions with current state + +--- + +## GitOps in practice + +- Start describing your resources with YAML + +- Leverage a tool like Kustomize or Helm + +- Make sure that you can easily deploy to a new namespace + + (or even better: to a new cluster) + +- When tooling matures, you will be ready + +--- + +## Plan B + +- What if we can't describe everything with YAML? + +- What if we manually create resources and forget to commit them to source control? + +- What about global resources, that don't live in a namespace? + +- How can we be sure that we saved *everything*? + +--- + +## Backing up etcd + +- All objects are saved in etcd + +- etcd data should be relatively small + + (and therefore, quick and easy to back up) + +- Two options to back up etcd: + + - snapshot the data directory + + - use `etcdctl snapshot` + +--- + +## Making an etcd snapshot + +- The basic command is simple: + ```bash + etcdctl snapshot save + ``` + +- But we also need to specify: + + - an environment variable to specify that we want etcdctl v3 + + - the address of the server to back up + + - the path to the key, certificate, and CA certificate +
(if our etcd uses TLS certificates) + +--- + +## Snapshotting etcd on kubeadm + +- The following command will work on clusters deployed with kubeadm + + (and maybe others) + +- It should be run on a master node + +```bash +docker run --rm --net host -v $PWD:/vol \ + -v /etc/kubernetes/pki/etcd:/etc/kubernetes/pki/etcd:ro \ + -e ETCDCTL_API=3 k8s.gcr.io/etcd:3.3.10 \ + etcdctl --endpoints=https://[127.0.0.1]:2379 \ + --cacert=/etc/kubernetes/pki/etcd/ca.crt \ + --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \ + --key=/etc/kubernetes/pki/etcd/healthcheck-client.key \ + snapshot save /vol/snapshot +``` + +- It will create a file named `snapshot` in the current directory + +--- + +## How can we remember all these flags? + +- Look at the static pod manifest for etcd + + (in `/etc/kubernetes/manifests`) + +- The healthcheck probe is calling `etcdctl` with all the right flags + 😉👍✌️ + +- Exercise: write the YAML for a batch job to perform the backup + +--- + +## Restoring an etcd snapshot + +- ~~Execute exactly the same command, but replacing `save` with `restore`~~ + + (Believe it or not, doing that will *not* do anything useful!) + +- The `restore` command does *not* load a snapshot into a running etcd server + +- The `restore` command creates a new data directory from the snapshot + + (it's an offline operation; it doesn't interact with an etcd server) + +--- + +## When using kubeadm + +1. Create a new data directory from the snapshot: + ```bash + sudo rm -rf /var/lib/etcd + docker run --rm -v /var/lib:/var/lib -v $PWD:/vol \ + -e ETCDCTL_API=3 k8s.gcr.io/etcd:3.3.10 \ + etcdctl snapshot restore /vol/snapshot --data-dir=/var/lib/etcd + ``` + +2. Provision the control plane, using that data directory: + ```bash + sudo kubeadm init \ + --ignore-preflight-errors=DirAvailable--var-lib-etcd + ``` + +3. Rejoin the other nodes + +--- + +## The fine print + +- This only saves etcd state + +- It **does not** save persistent volumes and local node data + +- Some critical components (like the pod network) might need to be reset + +- As a result, our pods might have to be recreated, too + +- If we have proper liveness checks, this should happen automatically + +--- + +## More information + +- [Kubernetes documentation](https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/#built-in-snapshot) about etcd backups + +- [etcd documentation](https://coreos.com/etcd/docs/latest/op-guide/recovery.html#snapshotting-the-keyspace) about snapshots and restore + +- [A good blog post by elastisys](https://elastisys.com/2018/12/10/backup-kubernetes-how-and-why/) explaining how to restore a snapshot + +- [Another good blog post by consol labs](https://labs.consol.de/kubernetes/2018/05/25/kubeadm-backup.html) on the same topic + +--- + +## Stateful services + +- It's totally fine to keep your production databases outside of Kubernetes + + *Especially if you have only one database server!* + +- Feel free to put development and staging databases on Kubernetes + + (as long as they don't hold important data) + +- Using Kubernetes for stateful services makes sense if you have *many* + + (because then you can leverage Kubernetes automation) From aa6b74efcb64056ba0eb538a3a2e80720ccb5f8e Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Wed, 10 Apr 2019 03:15:33 -0500 Subject: [PATCH 17/51] Add Cloud Controller Manager --- slides/k8s/cloud-controller-manager.md | 144 +++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 slides/k8s/cloud-controller-manager.md diff --git a/slides/k8s/cloud-controller-manager.md b/slides/k8s/cloud-controller-manager.md new file mode 100644 index 00000000..dd48e7d0 --- /dev/null +++ b/slides/k8s/cloud-controller-manager.md @@ -0,0 +1,144 @@ +# The Cloud Controller Manager + +- Kubernetes has many features that are cloud-specific + + (e.g. providing cloud load balancers when a Service of type LoadBalancer is created) + +- These features were initially implemented in API server and controller manager + +- Since Kubernetes 1.6, these features are available through a separated process: + + the *Cloud Controller Manager* + +- The CCM is optional, but if we run in a cloud, we probably want it! + +--- + +## Cloud Controller Manager duties + +- Creating and updating cloud load balancers + +- Configuring routing tables in the cloud network (specific to GCE) + +- Updating node labels to indicate region, zone, instance type ... + +- Obtain node name, internal and external addresses from cloud metadata service + +- Deleting nodes from Kubernetes when they're deleted in the cloud + +- Managing *some* volumes (e.g. ELBs, AzureDisks ...) + + (Eventually, volumes will be managed by the CSI) + +--- + +## In-tree vs. out-of-tree + +- A number of cloud providers are supported "in-tree" + + (in the main kubernetes/kubernetes repository on GitHub) + +- More cloud providers are supported "out-of-tree" + + (with code in different repositories) + +- There is an [ongoing effort](https://github.com/kubernetes/kubernetes/tree/master/pkg/cloudprovider) to move everything to out-of-tree providers + +--- + +## In-tree providers + +The following providers are actively maintained: + +- Amazon Web Services +- Azure +- Google Compute Engine +- IBM Cloud +- OpenStack +- VMware vSphere + +These ones are less actively maintained: + +- Apache CloudStack +- oVirt +- VMware Photon + +--- + +## Out-of-tree providers + +The list includes the following providers: + +- DigitalOcean + +- keepalived (not exactly a cloud; provides VIPs for load balancers) + +- Linode + +- Oracle Cloud Infrastructure + +(And possibly others; there is no central registry for these.) + +--- + +## Audience questions + +- What kind of clouds are you using / planning to use? + +- What kind of details would you like to see in this section? + +- Would you appreciate to get details on clouds that you don't / won't use? + +--- + +## Cloud Controller Manager in practice + +- Write a configuration file + + (typically `/etc/kubernetes/cloud.conf`) + +- Run the CCM process + + (on self-hosted clusters, this can be a DaemonSet selecting the control plane nodes) + +- Start kubelet with `--cloud-provider=external` + +- When using managed clusters, this is done automatically + +- There is very little documentation to write the configuration file + + (except for OpenStack) + +--- + +## Bootstrapping challenges + +- When a node joins the cluster, it needs to obtain a signed TLS certificate + +- That certificate must contain the node's addresses + +- These addresses are provided by the Cloud Controller Manager + + (at least the external address) + +- To get these addresses, the node needs to communicate with the control plane + +- ... Which means joining the cluster + +(The problem didn't occur when cloud-specific code was running in kubelet: kubelet could obtain the required information directly from the cloud provider's metadata service.) + +--- + +## More information about CCM + +- CCM configuration and operation is highly specific to each cloud provider + + (which is why this section remains very generic) + +- The Kubernetes documentation has *some* information: + + - [architecture and diagrams](https://kubernetes.io/docs/concepts/architecture/cloud-controller/) + + - [configuration](https://kubernetes.io/docs/concepts/cluster-administration/cloud-providers/) (mainly for OpenStack) + + - [deployment](https://kubernetes.io/docs/tasks/administer-cluster/running-cloud-controller/) From 945586d975758b3c8eec7c637c4391e0ca339fe7 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Wed, 10 Apr 2019 03:16:32 -0500 Subject: [PATCH 18/51] Add container engine version reminder (thanks @rdegez) --- slides/k8s/cluster-upgrade.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slides/k8s/cluster-upgrade.md b/slides/k8s/cluster-upgrade.md index 6a40083f..fcf89145 100644 --- a/slides/k8s/cluster-upgrade.md +++ b/slides/k8s/cluster-upgrade.md @@ -101,6 +101,8 @@ - Network controller, network policy controller +- Container engine + - Linux kernel --- From cd1dafd9e5d4cf3fd866fa9b38c323077e3a58bb Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Wed, 10 Apr 2019 03:53:39 -0500 Subject: [PATCH 19/51] Improve backup section (thanks @rdegez & @naps) --- slides/k8s/cluster-backup.md | 51 ++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/slides/k8s/cluster-backup.md b/slides/k8s/cluster-backup.md index 283ee7cf..7efdf7c7 100644 --- a/slides/k8s/cluster-backup.md +++ b/slides/k8s/cluster-backup.md @@ -24,11 +24,13 @@ - Kubernetes *does not* help us with error recovery -- This is the job of the storage layer +- We still need to backup / snapshot our data: -- We still need to do e.g. database backups + - with database backups (mysqldump, pgdump, etc.) - (with snapshots, or tools like mysqldump, pgdump, etc.) + - and/or snapshots at the storage layer + + - and/or traditional full disk backups --- @@ -196,7 +198,7 @@ (and maybe others) -- It should be run on a master node +- It should be executed on a master node ```bash docker run --rm --net host -v $PWD:/vol \ @@ -238,6 +240,10 @@ docker run --rm --net host -v $PWD:/vol \ (it's an offline operation; it doesn't interact with an etcd server) +- It will create a new data directory in a temporary container + + (leaving the running etcd node untouched) + --- ## When using kubeadm @@ -274,7 +280,7 @@ docker run --rm --net host -v $PWD:/vol \ --- -## More information +## More information about etcd backups - [Kubernetes documentation](https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/#built-in-snapshot) about etcd backups @@ -286,6 +292,25 @@ docker run --rm --net host -v $PWD:/vol \ --- +## Don't forget ... + +- Also back up the TLS information + + (at the very least: CA key and cert; API server key and cert) + +- With clusters provisioned by kubeadm, this is in `/etc/kubernetes/pki` + +- If you don't: + + - you will still be able to restore etcd state and bring everyting back up + + - you will need to redistribute user certificates + +.warning[**TLS information is highly sensitive! +
Anyone who has it has full access to your cluster!**] + +--- + ## Stateful services - It's totally fine to keep your production databases outside of Kubernetes @@ -299,3 +324,19 @@ docker run --rm --net host -v $PWD:/vol \ - Using Kubernetes for stateful services makes sense if you have *many* (because then you can leverage Kubernetes automation) + +--- + +## Snapshotting persistent volumes + +- Option 1: snapshot volumes out of band + + (with the API/CLI/GUI of our SAN/cloud/...) + +- Option 2: storage system integration + + (e.g. [Portworx](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/create-snapshots/) can [create snapshots through annotations](https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/create-snapshots/snaps-annotations/#taking-periodic-snapshots-on-a-running-pod)) + +- Option 3: [snapshots through Kubernetes API](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/) + + (now in alpha for a few storage providers: GCE, OpenSDS, Ceph, Portworx) From d929f5f84c682aae8f01739d32e4b9a2ff224269 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Wed, 10 Apr 2019 04:07:28 -0500 Subject: [PATCH 20/51] Add more backup tools --- slides/k8s/cluster-backup.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/slides/k8s/cluster-backup.md b/slides/k8s/cluster-backup.md index 7efdf7c7..6fd09e59 100644 --- a/slides/k8s/cluster-backup.md +++ b/slides/k8s/cluster-backup.md @@ -340,3 +340,23 @@ docker run --rm --net host -v $PWD:/vol \ - Option 3: [snapshots through Kubernetes API](https://kubernetes.io/blog/2018/10/09/introducing-volume-snapshot-alpha-for-kubernetes/) (now in alpha for a few storage providers: GCE, OpenSDS, Ceph, Portworx) + +--- + +## More backup tools + +- [Stash](https://appscode.com/products/stash/) + + back up Kubernetes persistent volumes + +- [ReShifter](https://github.com/mhausenblas/reshifter) + + cluster state management + +- ~~Heptio Ark~~ [Velero](https://github.com/heptio/velero) + + full cluster backup + +- [kube-backup](https://github.com/pieterlange/kube-backup) + + simple scripts to save resource YAML to a git repository From 038563b5eab8b8324718c32cecc62c93018d6bd2 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Wed, 10 Apr 2019 06:49:29 -0500 Subject: [PATCH 21/51] Add TLS bootstrap --- slides/k8s/bootstrap.md | 259 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 slides/k8s/bootstrap.md diff --git a/slides/k8s/bootstrap.md b/slides/k8s/bootstrap.md new file mode 100644 index 00000000..70989ea0 --- /dev/null +++ b/slides/k8s/bootstrap.md @@ -0,0 +1,259 @@ +# TLS bootstrap + +- kubelet needs TLS keys and certificates to communicate with the control plane + +- How do we generate this information? + +- How do we make it available to kubelet? + +--- + +## Option 1: push + +- When we want to provision a node: + + - generate its keys, certificate, and sign centrally + + - push the files to the node + +- OK for "traditional", on-premises deployments + +- Not OK for cloud deployments with auto-scaling + +--- + +## Option 2: poll + push + +- Discover nodes when they are created + + (e.g. with cloud API) + +- When we detect a new node, push TLS material to the node + + (like in option 1) + +- It works, but: + + - discovery code is specific to each provider + + - relies heavily on the cloud provider API + + - doesn't work on-premises + + - doesn't scale + +--- + +## Option 3: bootstrap tokens + CSR API + +- Since Kubernetes 1.4, the Kubernetes API supports CSR + + (Certificate Signing Requests) + +- This is similar to the protocol used to obtain e.g. HTTPS certifiates: + + - subject (here, kubelet) generates TLS keys and CSR + + - subject submits CSR to CA + + - CA validates (or not) the CSR + + - CA sends back signed certificate to subject + +- This is combined with *bootstrap tokens* + +--- + +## Bootstrap tokens + +- A [bootstrap token](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/) is an API access token + + - it is a Secret with type `bootstrap.kubernetes.io/token` + + - it is 6 public characters (ID) + 16 secret characters +
(example: `whd3pq.d1ushuf6ccisjacu`) + + - it gives access to groups `system:bootstrap:` and `system:bootstrappers` + + - additional groups can be specified in the Secret + +--- + +## Bootstrap tokens with kubeadm + +- kubeadm automatically creates a bootstrap token + + (it is shown at the end of `kubeadm init`) + +- That token adds the group `system:bootstrappers:kubeadm:default-node-token` + +- kubeadm also creates a ClusterRoleBinding `kubeadm:kubelet-bootstrap` +
binding `...:default-node-token` to ClusterRole `system:node-bootstrapper` + +- That ClusterRole gives create/get/list/watch permissions on the CSR API + +--- + +## Bootstrap tokens in practice + +- Let's list our bootstrap tokens on a cluster created with kubeadm + +.exercise[ + +- Log into node `test1` + +- View bootstrap tokens: + ```bash + sudo kubeadm token list + ``` + +] + +- Tokens are short-lived + +- We can create new tokens with `kubeadm` if necessary + +--- + +class: extra-details + +## Retrieving bootstrap tokens with kubectl + +- Bootstrap tokens are Secrets with type `bootstrap.kubernetes.io/token` + +- Token ID and secret are in data fields `token-id` and `token-secret` + +- In Secrets, data fields are encoded with Base64 + +- This "very simple" command will show us the tokens: + +``` +kubectl -n kube-system get secrets -o json | + jq -r '.items[] + | select(.type=="bootstrap.kubernetes.io/token") + | ( .data["token-id"] + "Lg==" + .data["token-secret"] + "Cg==") + ' | base64 -d +``` + +(On recent versions of `jq`, you can simplify by using filter `@base64d`.) + +--- + +class: extra-details + +## Using a bootstrap token + +- The token we need to use has the form `abcdef.1234567890abcdef` + +.exercise[ + +- Check that it is accepted by the API server: + ```bash + curl -k -H "Authorization: Bearer abcdef.1234567890abcdef" + ``` + +- We should see that we are *authenticated* but not *authorized*: + ``` + User \"system:bootstrap:abcdef\" cannot get path \"/\"" + ``` + +- Check that we can access the CSR API: + ```bash + curl -k -H "Authorization: Bearer abcdef.1234567890abcdef" \ + https://10.96.0.1/apis/certificates.k8s.io/v1beta1/certificatesigningrequests + ``` + +] + +--- + +## The cluster-info ConfigMap + +- Before we can talk to the API, we need: + + - the API server address (obviously!) + + - the cluster CA certificate + +- That information is stored in a public ConfigMap + +.exercise[ + +- Retrieve that ConfigMap: + ```bash + curl -k https://10.96.0.1/api/v1/namespaces/kube-public/configmaps/cluster-info + ``` + +] + +*Extracting the kubeconfig file is left as an exercise for the reader.* + +--- + +class: extra-details + +## Signature of the config-map + +- You might have noticed a few `jws-kubeconfig-...` fields + +- These are config-map signatures + + (so that the client can protect against MITM attacks) + +- These are JWS signatures using HMAC-SHA256 + + (see [here](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/#configmap-signing) for more details) + +--- + +## Putting it all together + +This is the TLS bootstrap mechanism, step by step. + +- The node uses the cluster-info ConfigMap to get the cluster CA certificate + +- The node generates its keys and CSR + +- Using the bootstrap token, the node creates a CertificateSigningRequest object + +- The node watches the CSR object + +- The CSR object is accepted (automatically or by an admin) + +- The node gets notified, and retrives the certificate + +- The node can now join the cluster + +--- + +## Bottom line + +- If you paid attention, we still need a way to: + + - either safely get the bootstrap token to the nodes + + - or disable auto-approval and manually approve the nodes when they join + +- The goal of the TLS bootstrap mechanism is *not* to solve this + + (in terms of information knowledge, it's fundamentally impossible!) + +- But it reduces the differences between environments, infrastructures, providers ... + +- It gives a mechanism that is easier to use, and flexible enough, for most scenarios + +--- + +## More information + +- As always, the Kubernetes documentation has extra details: + + - [TLS management](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/) + + - [Authenticating with bootstrap tokens](https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/) + + - [TLS bootstrapping](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet-tls-bootstrapping/) + + - [kubeadm token](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-token/) command + + - [kubeadm join](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/) command (has details about [the join workflow](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/#join-workflow)) From ded5fbdcd4b8cc650f1c4c088e0ca5c5d2cf24ca Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Fri, 12 Apr 2019 12:53:45 -0500 Subject: [PATCH 22/51] Add chapter about resource limits --- slides/k8s/resource-limits.md | 518 ++++++++++++++++++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 slides/k8s/resource-limits.md diff --git a/slides/k8s/resource-limits.md b/slides/k8s/resource-limits.md new file mode 100644 index 00000000..673d3d6d --- /dev/null +++ b/slides/k8s/resource-limits.md @@ -0,0 +1,518 @@ +# Resource Limits + +- We can attach resource indications to our pods + + (or rather: to the *containers* in our pods) + +- We can specify *limits* and/or *requests* + +- We can specify quantities of CPU and/or memory + +--- + +## CPU vs memory + +- CPU is a *compressible resource* + + (it can be preempted immediately without adverse effect) + +- Memory is an *incompressible resource* + + (it needs to be swapped out to be reclaimed; and this is costly) + +- As a result, exceeding limits will have different consequences for CPU and memory + +--- + +## Exceeding CPU limits + +- CPU can be reclaimed instantaneously + + (in fact, it is preempted hundreds of times per second, at each context switch) + +- If a container uses too much CPU, it can be throttled + + (it will be scheduled less often) + +- The processes in that container will run slower + + (or rather: they will not run faster) + +--- + +## Exceeding memory limits + +- Memory needs to be swapped out before being reclaimed + +- "Swapping" means writing memory pages to disk, which is very slow + +- On a classic system, a process that swaps can get 1000x slower + + (because disk I/O is 1000x slower than memory I/O) + +- Exceeding the memory limit (even by a small amount) can reduce performance *a lot* + +- Kubernetes *does not support swap* (more on that later!) + +- Exceeding the memory limit will cause the container to be killed + + + +--- + +## Limits vs requests + +- Limits are "hard limits" (they can't be exceeded) + + - a container exceeding its memory limit is killed + + - a container exceeding its CPU limit is throttled + +- Requests are used for scheduling purposes + + - a container using *less* than what it requested will never be killed or throttled + + - the scheduler uses the requested sizes to determine placement + + - the resources requested by all pods on a node will never exceed the node size + +--- + +## Pod quality of service + +Each pod is assigned a QoS class (visible in `status.qosClass`). + +- If limits = requests: + + - as long as the container uses less than the limit, it won't be affected + + - if all containers in a pod have *(limits=requests)*, QoS is "Guaranteed" + +- If requests < limits: + + - as long as the container uses less than the request, it won't be affected + + - otherwise, it might be killed / evicted if the node gets overloaded + + - if at least one container has *(requests<limits)*, QoS is "Burstable" + +- If a pod doesn't have any request nor limit, QoS is "BestEffort" + +--- + +## Quality of service impact + +- When a node is overloaded, BestEffort pods are killed first + +- Then, Burstable pods that exceed their limits + +- Burstable and Guaranteed pods below their limits are never killed + + (except if their node fails) + +- If we only use Guaranteed pods, no pod should ever be killed + + (as long as they stay within their limits) + +(Pod QoS is also explained in [this page](https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/) of the Kubernetes documentation and in [this blog post](Explanation of quality of service +https://medium.com/google-cloud/quality-of-service-class-qos-in-kubernetes-bb76a89eb2c6).) + +--- + +## Where is my swap? + +- The semantics of memory and swap limits on Linux cgroups are complex + +- In particular, it's not possible to disable swap for a cgroup + + (the closest option is to [reduce "swappiness"](https://unix.stackexchange.com/questions/77939/turning-off-swapping-for-only-one-process-with-cgroups)) + +- The architects of Kubernetes wanted to ensure that Guaranteed pods never swap + +- The only solution was to disable swap entirely + +--- + +## Alternative point of view + +- Swap enables paging¹ of anonymous² memory + +- Even when swap is disabled, Linux will still page memory for: + + - executables, libraries + + - mapped files + +- Disabling swap *will reduce performance and available resources* + +- For a good time, read [kubernetes/kubernetes#53533](https://github.com/kubernetes/kubernetes/issues/53533) + +- Also read this [excellent blog post about swap](https://jvns.ca/blog/2017/02/17/mystery-swap/) + +¹Paging: reading/writing memory pages from/to disk to reclaim physical memory + +²Anonymous memory: memory that is not backed by files or blocks + +--- + +## Enabling swap anyway + +- If you don't care that pods are swapping, you can enable swap + +- You will need to add the flag `--fail-swap-on=false` to kubelet + + (otherwise, it won't start!) + +--- + +## Specifying resources + +- Resource requests are expressed at the *container* level + +- CPU is expressed in "virtual CPUs" + + (corresponding to the virtual CPUs offered by some cloud providers) + +- CPU can be expressed with a decimal value, or even a "milli" suffix + + (so 100m = 0.1) + +- Memory is expressed in bytes + +- Memory can be expressed with k, M, G, T, ki, Mi, Gi, Ti suffixes + + (corresponding to 10^3, 10^6, 10^9, 10^12, 2^10, 2^20, 2^30, 2^40) + +--- + +## Specifying resources in practice + +This is what the spec of a Pod with resources will look like: + +```yaml +containers: +- name: httpenv + image: jpetazzo/httpenv + resources: + limits: + memory: "100Mi" + cpu: "100m" + requests: + memory: "100Mi" + cpu: "10m" +``` + +This set of resources makes sure that this service won't be killed (as long as it stays below 100 MB of RAM), but allows its CPU usage to be throttled if necessary. + +--- + +## Default values + +- If we specify a limit without a request: + + the request is set to the limit + +- If we specify a request without a limit: + + there will be no limit + + (which means that the limit will be the size of the node) + +- If we don't specify anything: + + the request is zero and the limit is the size of the node + +*Unless there are default values defined for our namespace!* + +--- + +## We need default resource values + +- If we do not set resource values at all: + + - the limit is "the size of the node" + + - the request is zero + +- This is generally *not* what we want + + - a container without a limit can use up all the resources of a node + + - if the request is zero, the scheduler can't make a smart placement decision + +- To address this, we can set default values for resources + +- This is done with a LimitRange object + +--- + +# Defining min, max, and default resources + +- We can create LimitRange objects to indicate any combination of: + + - min and/or max resources allowed per pod + + - default resource *limits* + + - default resource *requests* + + - maximal burst ratio (*limit/request*) + +- LimitRange objects are namespaced + +- They apply to their namespace only + +--- + +## LimitRange example + +```yaml +apiVersion: v1 +kind: LimitRange +metadata: + name: my-very-detailed-limitrange +spec: + limits: + - type: Container + min: + cpu: "100m" + max: + cpu: "2000m" + memory: "1Gi" + default: + cpu: "500m" + memory: "250Mi" + defaultRequest: + cpu: "500m" +``` + +--- + +## Example explanation + +The YAML on the previous slide shows an example LimitRange object specifying very detailed limits on CPU usage, +and providing defaults on RAM usage. + +Note the `type: Container` line: in the future, +it might also be possible to specify limits +per Pod, but it's not [officially documented yet](https://github.com/kubernetes/website/issues/9585). + +--- + +## LimitRange details + +- LimitRange restrictions are enforced only when a Pod is created + + (they don't apply retroactively) + +- They don't prevent creation of e.g. an invalid Deployment or DaemonSet + + (but the pods will not be created as long as the LimitRange is in effect) + +- If there are multiple LimitRange, they all apply together + + (which means that it's possible to specify conflicting LimitRanges, +
preventing any Pod from being created) + +- If a LimitRange specifies a `max` for a resource but no `default`, +
that `max` value becomes the `default` limit too + +--- + +# Namespace quotas + +- We can also set quotas per namespace + +- Quotas apply to the total usage in a namespace + + (e.g. total CPU limits of all pods in a given namespace) + +- Quotas can apply to resource limits and/or requests + + (like the CPU and memory limits that we saw earlire) + +- Quotas can also apply to other resources: + + - "extended" resources (like GPUs) + + - storage size + + - number of objects (number of pods, services...) + +--- + +## Creating a quota for a namespace + +- Quotas are enforced by creating a ResourceQuotas object + +- ResourceQuota objects are namespaced, and apply to their namespace only + +- We can have multiple ResourceQuota objects in the same namespace + +- The most restrictive values are used + +--- + +## Limiting total CPU/memory usage + +- The following YAML specifies an upper bound for *limits* and *requests*: + ```yaml + apiVersion: v1 + kind: ResourceQuota + metadata: + name: a-little-bit-of-compute + spec: + hard: + requests.cpu: "10" + requests.memory: 10Gi + limits.cpu: "20" + limits.memory: 20Gi + ``` + +These quotas will apply to the namespace where the ResourceQuota is created. + +--- + +## Limiting number of objects + +- The following YAML specifies how many objects of specific types can be created: + ```yaml + apiVersion: v1 + kind: ResourceQuota + metadata: + name: quota-for-objects + spec: + hard: + pods: 100 + services: 10 + secrets: 10 + configmaps: 10 + persistentvolumeclaims: 20 + services.nodeports: 0 + services.loadbalancers: 0 + count/roles.rbac.authorization.k8s.io: 10 + ``` + +(The `count/` syntax allows to limit arbitrary objects, including CRDs.) + +--- + +## YAML vs CLI + +- Quotas can be created with a YAML definition + +- ... Or with the `kubectl create quota` command + +- Example: + ```bash + kubectl create quota sparta --hard=pods=300,limits.memory=300Gi + ``` + +- With both YAML and CLI form, the values are always under the `hard` section + + (there is no `soft` quota) + +--- + +## Viewing current usage + +When a ResourceQuota is created, we can see how much of it is used: + +``` +kubectl describe resourcequota my-resource-quota + +Name: my-resource-quota +Namespace: default +Resource Used Hard +-------- ---- ---- +pods 12 100 +services 1 5 +services.loadbalancers 0 0 +services.nodeports 0 0 +``` + +--- + +## Advanced quotas and PriorityClass + +- Since Kubernetes 1.12, it is possible to create PriorityClass objects + +- Pods can be assigned a PriorityClass + +- Quotas can be linked to a PriorityClass + +- This allows to reserve resources to pods within a namespace + +- For more details, check [this documentation page](https://kubernetes.io/docs/concepts/policy/resource-quotas/#resource-quota-per-priorityclass) + +--- + +# Limiting resources in practice + +- We have at least three mechanisms: + + - requests and limits per Pod + + - LimitRange per namespace + + - ResourceQuota per namespace + +- Let's see a simple recommendation to get started with resource limits + +--- + +## Set a LimitRange + +- In each namespace, create a LimitRange object + +- Set a small default CPU request and CPU limit + + (e.g. "100m") + +- Set a default memory request and limit depending on your most common workload + + - for Java, Ruby: start with "1G" + + - for Go, Python, PHP, Node: start with "250M" + +- Set upper bounds slightly below your expected node size + + (80-90% of your node size, with at least a 500M memory buffer) + +--- + +## Set a ResourceQuota + +- In each namespace, create a ResourceQuota object + +- Set generous CPU and memory limits + + (e.g. half the cluster size if the cluster hosts multiple apps) + +- Set generous objects limits + + - these limits should not be here to constrain your users + + - they should catch a runaway process creating many resources + + - example: a custom controller creating many pods + +--- + +## Observe, refine, iterate + +- Observe the resource usage of your pods + + (we will see how in the next chapter) + +- Adjust individual pod limits + +- If you see trends: adjust the LimitRange + + (rather that adjusting every individual set of pod limits) + +- Observe the resource usage of your namespaces + + (with `kubectl describe resourcequota ...`) + +- Rinse and repeat regularly From f40b8a1bfa410bba17df7f3e5e99f1ad5c841c3a Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Fri, 12 Apr 2019 17:58:14 -0500 Subject: [PATCH 23/51] Add short section about metrics server --- slides/k8s/metrics-server.md | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 slides/k8s/metrics-server.md diff --git a/slides/k8s/metrics-server.md b/slides/k8s/metrics-server.md new file mode 100644 index 00000000..c08b00d7 --- /dev/null +++ b/slides/k8s/metrics-server.md @@ -0,0 +1,66 @@ +# Checking pod and node resource usage + +- Since Kubernetes 1.8, metrics are collected by the [core metrics pipeline](https://v1-13.docs.kubernetes.io/docs/tasks/debug-application-cluster/core-metrics-pipeline/) + +- The core metrics pipeline is: + + - optional (Kubernetes can function without it) + + - necessary for some features (like the Horizontal Pod Autoscaler) + + - exposed through the Kubernetes API using the [aggregation layer](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/) + + - usually implemented by the "metrics server" + +--- + +## How to know if the metrics server is running? + +- The easiest way to know is to run `kubectl top` + +.exercise[ + +- Check if the core metrics pipeline is available: + ```bash + kubectl top nodes + ``` + +] + +If it shows our nodes and their CPU and memory load, we're good! + +--- + +## Installing metrics server + +- The metrics server doesn't have any particular requirement + + (it doesn't need persistence, as it doesn't *store* metrics) + +- It has its own repository, [kubernetes-incubator/metrics-server](https://github.com/kubernetes-incubator/metrics-server]) + +- The repository comes with [YAML files for deployment](https://github.com/kubernetes-incubator/metrics-server/tree/master/deploy/1.8%2B) + +- These files may not work on some clusters + + (e.g. if your node names are not in DNS) + +- The container.training repository has a [metrics-server.yaml](https://github.com/jpetazzo/container.training/blob/master/k8s/metrics-server.yaml#L90) file to help with that + + (we can `kubectl apply -f` that file if needed) + +--- + +## Showing container resource usage + +- Once metrics server is running, we can check container resource usage + +.exercise[ + +- Show resource usage accross all containers: + ```bash + kuebectl top pods --containers --all-namespaces + ``` +] + +- We can also use selectors (`-l app=...`) From df185c88a579fd68b118617ea5f7988d912cc3ad Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sat, 13 Apr 2019 04:30:22 -0500 Subject: [PATCH 24/51] Add shell snippet generating route commands --- slides/k8s/cni.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/slides/k8s/cni.md b/slides/k8s/cni.md index 16ddfeba..319e39d5 100644 --- a/slides/k8s/cni.md +++ b/slides/k8s/cni.md @@ -499,6 +499,32 @@ What does that mean? --- +class: extra-details + +## Other ways to distribute routing tables + +- We don't need kube-router and BGP to distribute routes + +- The list of nodes (and associated `podCIDR` subnets) is available through the API + +- This shell snippet generates the commands to add all required routes on a node: + +```bash +NODES=$(kubectl get nodes -o name | cut -d/ -f2) +for DESTNODE in $NODES; do + if [ "$DESTNODE" != "$HOSTNAME" ]; then + echo $(kubectl get node $DESTNODE -o go-template=" + route add -net {{.spec.podCIDR}} gw {{(index .status.addresses 0).address}}") + fi +done +``` + +- This could be useful for embedded platforms with very limited resources + + (or lab environments for learning purposes) + +--- + # Interconnecting clusters - We assigned different Cluster CIDRs to each cluster From 2dc634e1f51eae1f3741acddaf3bd9e96ff3f613 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sat, 13 Apr 2019 05:25:14 -0500 Subject: [PATCH 25/51] Add cluster sizing chapter --- slides/k8s/cluster-sizing.md | 167 +++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 slides/k8s/cluster-sizing.md diff --git a/slides/k8s/cluster-sizing.md b/slides/k8s/cluster-sizing.md new file mode 100644 index 00000000..d2263cfd --- /dev/null +++ b/slides/k8s/cluster-sizing.md @@ -0,0 +1,167 @@ +# Cluster sizing + +- What happens when the cluster gets full? + +- How can we scale up the cluster? + +- Can we do it automatically? + +- What are other methods to address capacity planning? + +--- + +## When are we out of resources? + +- kubelet monitors node resources: + + - memory + + - node disk usage (typically the root filesystem of the node) + + - image disk usage (where container images and RW layers are stored) + +- For each resource, we can provide two thresholds: + + - a hard threshold (if it's met, it provokes immediate action) + + - a soft threshold (provokes action only after a grace period) + +- Resource thresholds and grace periods are configurable + + (by passing kubelet command-line flags) + +--- + +## What happens then? + +- If disk usage is too high: + + - kubelet will try to remove terminated pods + + - then, it will try to *evict* pods + +- If memory usage is too high: + + - it will try to evict pods + +- The node is marked as "under pressure" + +- This temporarily prevents new pods from being scheduled on the node + +--- + +## Which pods get evicted? + +- kubelet looks at the pods QoS and PriorityClass + +- First, pods with BestEffort QoS are considered + +- Then, pods with Burstable QoS exceeding their *requests* + + (but only if the exceeding resource is the one that is low on the node) + +- Finally, pods with Guaranteed QoS, and Burstable pods within their requests + +- Within each group, pods are sorted by PriorityClass + +- If there are pods with the same PriorityClass, they are sorted by usage excess + + (i.e. the pods whose usage exceeds their requests the most are evicted first) + +--- + +class: extra-details + +## Eviction of Guaranteed pods + +- *Normally*, pods with Guaranteed QoS should not be evicted + +- A chunk of resources is reserved for node processes (like kubelet) + +- It is expected that these processes won't use more than this reservation + +- If they do use more resources anyway, all bets are off! + +- If this happens, kubelet must evict Guaranteed pods to preserve node stability + + (or Burstable pods that are still within their requested usage) + +--- + +## What happens to evicted pods? + +- The pod is terminated + +- It is marked as `Failed` at the API level + +- If the pod was created by a controller, the controller will recreate it + +- The pod will be recreated on another node, *if there are resources available!* + +- For more details about the eviction process, see: + + - [this documentation page](https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/) about resource pressure and pod eviction, + + - [this other documentation page](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/) about pod priority and preemption. + +--- + +## What if there are no resources available? + +- Sometimes, a pod cannot be scheduled anywhere: + + - all the nodes are under pressure, + + - or the pod requests more resources than are available + +- The pod then remains in `Pending` state until the situation improves + +--- + +## Cluster scaling + +- One way to improve the situation is to add new nodes + +- This can be done automatically with the [Cluster Autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler) + +- The autoscaler will automatically scale up: + + - if there are pods that failed to be scheduled + +- The autoscaler will automatically scale down: + + - if nodes have a low utilization for an extended period of time + +--- + +## Restrictions, gotchas ... + +- The Cluster Autoscaler only supports a few cloud infrastructures + + (see [here](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler/cloudprovider) for a list) + +- The Cluster Autoscaler cannot scale down nodes that have pods using: + + - local storage + + - affinity/anti-affinity rules preventing them from being rescheduled + + - a restrictive PodDisruptionBudget + +--- + +## Other way to do capacity planning + +- "Running Kubernetes without nodes" + +- Systems like Virtual Kubelet or Kiyot can run pods using on-demand resources + + - Virtual Kubelet can leverage e.g. ACI or Fargate to run pods + + - Kiyot runs pods in ad-hoc EC2 instances (1 instance per pod) + +- Economic advantage (no wasted capacity) + +- Security advantage (stronger isolation between pods) + +Check [this blog post](http://jpetazzo.github.io/2019/02/13/running-kubernetes-without-nodes-with-kiyot/) for more details. From 1e77f57434d33bc9c376d2b28f5058ede41fed1b Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sat, 13 Apr 2019 11:45:08 -0500 Subject: [PATCH 26/51] Add course conclusion --- slides/k8s/lastwords-admin.md | 210 ++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 slides/k8s/lastwords-admin.md diff --git a/slides/k8s/lastwords-admin.md b/slides/k8s/lastwords-admin.md new file mode 100644 index 00000000..5bd12285 --- /dev/null +++ b/slides/k8s/lastwords-admin.md @@ -0,0 +1,210 @@ +# What's next? + +- Congratulations! + +- We learned a lot about Kubernetes, its internals, its advanced concepts + +-- + +- That was just the easy part + +- The hard challenges will revolve around *culture* and *people* + +-- + +- ... What does that mean? + +--- + +## Running an app involves many steps + +- Write the app + +- Tests, QA ... + +- Ship *something* (more on that later) + +- Provision resources (e.g. VMs, clusters) + +- Deploy the *something* on the resources + +- Manage, maintain, monitor the resources + +- Manage, maintain, monitor the app + +- And much more + +--- + +## Who does what? + +- The old "devs vs ops" division has changed + +- In some organizations, "ops" are now called "SRE" or "platform" teams + + (and they have very different sets of skills) + +- Do you know which team is responsible for each item on the list on the previous page? + +- Acknowledge that a lot of tasks are outsourced + + (e.g. if we add "buy / rack / provision machines" in that list) + +--- + +## What do we ship? + +- Some organizations embrace "you build it, you run it" + +- When "build" and "run" are owned by different teams, where's the line? + +- What does the "build" team ship to the "run" team? + +- Let's see a few options, and what they imply + +--- + +## Shipping code + +- Team "build" ships code + + (hopefully in a repository, identified by a commit hash) + +- Team "run" containerizes that code + +✔️ no extra work for developers + +❌ very little advantage of using containers + +--- + +## Shipping container images + +- Team "build" ships container images + + (hopefully built automatically from a source repository) + +- Team "run" uses theses images to create e.g. Kubernetes resources + +✔️ universal artefact (support all languages uniformly) + +✔️ easy to start a single component (good for monoliths) + +❌ complex applications will require a lot of extra work + +❌ adding/removing components in the stack also requires extra work + +❌ complex applications will run very differently between dev and prod + +--- + +## Shipping Compose files + +(Or another kind of dev-centric manifest) + +- Team "build" ships a manifest that works on a single node + + (as well as images, or ways to build them) + +- Team "run" adapts that manifest to work on a cluster + +✔️ all teams can start the stack in a reliable, deterministic manner + +❌ adding/removing components still requires *some* work (but less than before) + +❌ there will be *some* differences between dev and prod + +--- + +## Shipping Kubernetes manifests + +- Team "build" ships ready-to-run manifests + + (YAML, Helm Charts, Kustomize ...) + +- Team "run" adjusts some parameters and monitors the application + +✔️ parity between dev and prod environments + +✔️ "run" team can focus on SLAs, SLOs, and overall quality + +❌ requires *a lot* of extra work (and new skills) from the "build" team + +❌ Kubernetes is not a very convenient development platform (at least, not yet) + +--- + +## What's the right answer? + +- It depends on our teams + + - existing skills (do they know how to do it?) + + - availability (do they have the time to do it?) + + - potential skills (can they learn to do it?) + +- It depends on our culture + + - owning "run" often implies being on call + + - do we reward on-call duty without encouraging hero syndrome? + + - do we give resources (time, money) to people to learn? + +--- + +class: extra-details + +## Tools to develop on Kubernetes + +*If we decide to make Kubernetes the primary development platform, here +are a few tools that can help us.* + +- Docker Desktop + +- Draft + +- Minikube + +- Skaffold + +- Tilt + +- ... + +--- + +## Where do we run? + +- Managed vs. self-hosted + +- Cloud vs. on-premises + +- If cloud: public vs. private + +- Which vendor / distribution to pick? + +- Which versions / features to enable? + +--- + +## Some guidelines + +- Start small + +- Outsource what we don't know + +- Start simple, and stay simple as long as possible + + (try to stay away from complex features that we don't need) + +- Automate + + (regularly check that we can successfully redeploy by following scripts) + +- Transfer knowledge + + (make sure everyone is on the same page / same level) + +- Iterate! From 5a4adb700a92ef58530f9f2fd1c4d6c6a0056997 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sun, 14 Apr 2019 13:58:02 -0500 Subject: [PATCH 27/51] Tweaks (thanks @rdegez!) --- slides/k8s/cni.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/slides/k8s/cni.md b/slides/k8s/cni.md index 319e39d5..66cb1fe7 100644 --- a/slides/k8s/cni.md +++ b/slides/k8s/cni.md @@ -164,9 +164,9 @@ class: extra-details - BGP (Border Gateway Protocol) is the protocol used between internet routers -- [It scales](https://www.cidr-report.org/as2.0/) - [pretty well](https://www.cidr-report.org/cgi-bin/plota?file=%2fvar%2fdata%2fbgp%2fas2.0%2fbgp-active%2etxt&descr=Active%20BGP%20entries%20%28FIB%29&ylabel=Active%20BGP%20entries%20%28FIB%29&with=step) - (more than 400k aggregated routes on internet) +- It [scales](https://www.cidr-report.org/as2.0/) + pretty [well](https://www.cidr-report.org/cgi-bin/plota?file=%2fvar%2fdata%2fbgp%2fas2.0%2fbgp-active%2etxt&descr=Active%20BGP%20entries%20%28FIB%29&ylabel=Active%20BGP%20entries%20%28FIB%29&with=step) + (it is used to announce the 700k CIDR prefixes of internet) - It is spoken by many hardware routers from many vendors @@ -623,9 +623,11 @@ done ## Restarting kube-router -- The DaemonSet will not restart the pods automatically +- The DaemonSet will not update the pods automatically -- For simplicity, we will delete the pods + (it is using the default `updateStrategy`, which is `OnDelete`) + +- We will therefore delete the pods (they will be recreated with the updated definition) @@ -638,6 +640,10 @@ done ] +Note: the other `updateStrategy` for a DaemonSet is RollingUpdate. +
+For critical services, we might want to control precisely the update process. + --- ## Checking peering status From 0e7c05757fe4d92e00ffda76f20184ff3938b2a7 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Fri, 19 Apr 2019 14:43:40 -0500 Subject: [PATCH 28/51] add k3s link Unless k3s is front-of-mind when you're on this slide, I suspect attendees might benefit from a link here? --- slides/k8s/apilb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/apilb.md b/slides/k8s/apilb.md index bdec57a8..2de80383 100644 --- a/slides/k8s/apilb.md +++ b/slides/k8s/apilb.md @@ -86,4 +86,4 @@ - Tunnels are also fine - (e.g. k3s uses a tunnel to allow each node to contact the API server) + (e.g. [k3s](https://k3s.io/) uses a tunnel to allow each node to contact the API server) From 50710539afa01ee93690495dbc4cd80335523e05 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Fri, 19 Apr 2019 14:50:50 -0500 Subject: [PATCH 29/51] Update architecture.md Slight grammatical adjustments. If you wanted to say "an etcd instance" that works, but "an etcd" doesn't parse correctly. And for "allows to use" we have to say who's allowed - "one" or "us" or "you". --- slides/k8s/architecture.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slides/k8s/architecture.md b/slides/k8s/architecture.md index 3efc8712..701a3415 100644 --- a/slides/k8s/architecture.md +++ b/slides/k8s/architecture.md @@ -62,7 +62,7 @@ class: pic ## What's in the control plane -- Everything is stored in an etcd +- Everything is stored in etcd (it's the only stateful component) @@ -161,7 +161,7 @@ What does that mean? - Storage and watch operations are provided by etcd - (note: the [k3s](https://k3s.io/) project allows to use sqlite instead of etcd) + (note: the [k3s](https://k3s.io/) project allows us to use sqlite instead of etcd) --- From e2528191cd473888f6a72c9e9917609bcb9cce91 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Fri, 19 Apr 2019 14:56:58 -0500 Subject: [PATCH 30/51] Update bootstrap.md typo fix --- slides/k8s/bootstrap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/bootstrap.md b/slides/k8s/bootstrap.md index 70989ea0..8d9483e3 100644 --- a/slides/k8s/bootstrap.md +++ b/slides/k8s/bootstrap.md @@ -50,7 +50,7 @@ (Certificate Signing Requests) -- This is similar to the protocol used to obtain e.g. HTTPS certifiates: +- This is similar to the protocol used to obtain e.g. HTTPS certificates: - subject (here, kubelet) generates TLS keys and CSR From 4c89d48a0b0471c716baabc383de7c2d56f8c0a7 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Fri, 19 Apr 2019 15:11:51 -0500 Subject: [PATCH 31/51] Update cluster-backup.md typo fix --- slides/k8s/cluster-backup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/cluster-backup.md b/slides/k8s/cluster-backup.md index 6fd09e59..eb34a58c 100644 --- a/slides/k8s/cluster-backup.md +++ b/slides/k8s/cluster-backup.md @@ -302,7 +302,7 @@ docker run --rm --net host -v $PWD:/vol \ - If you don't: - - you will still be able to restore etcd state and bring everyting back up + - you will still be able to restore etcd state and bring everything back up - you will need to redistribute user certificates From 020cfeb0ad60df50e4d738933103c642e95d88e9 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 10:41:17 -0500 Subject: [PATCH 32/51] Update cni.md Grammatical clarifications. --- slides/k8s/cni.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/slides/k8s/cni.md b/slides/k8s/cni.md index 66cb1fe7..e4749234 100644 --- a/slides/k8s/cni.md +++ b/slides/k8s/cni.md @@ -1,6 +1,6 @@ # The Container Network Interface -- Allows to decouple network configuration from Kubernetes +- Allows us to decouple network configuration from Kubernetes - Implemented by *plugins* @@ -166,13 +166,13 @@ class: extra-details - It [scales](https://www.cidr-report.org/as2.0/) pretty [well](https://www.cidr-report.org/cgi-bin/plota?file=%2fvar%2fdata%2fbgp%2fas2.0%2fbgp-active%2etxt&descr=Active%20BGP%20entries%20%28FIB%29&ylabel=Active%20BGP%20entries%20%28FIB%29&with=step) - (it is used to announce the 700k CIDR prefixes of internet) + (it is used to announce the 700k CIDR prefixes of the internet) - It is spoken by many hardware routers from many vendors - It also has many software implementations (Quagga, Bird, FRR...) -- Experimented network folks generally know it (and appreciate it) +- Experienced network folks generally know it (and appreciate it) - It also used by Calico (another popular network system for Kubernetes) @@ -186,7 +186,7 @@ class: extra-details - We will run a simple control plane (like before) -- ... But this time, the controller manager with allocate `podCIDR` subnets +- ... But this time, the controller manager will allocate `podCIDR` subnets - We will start kube-router with a DaemonSet @@ -288,7 +288,7 @@ class: extra-details ] -Note: the DaemonSet won't create any pod (yet) since there are no nodes (yet). +Note: the DaemonSet won't create any pods (yet) since there are no nodes (yet). --- @@ -449,7 +449,7 @@ We should see the local pod CIDR connected to `kube-bridge`, and the other nodes kubectl -n kube-system logs ds/kube-router ``` -- Or to exec into one of the kube-router pods: +- Or try to exec into one of the kube-router pods: ```bash kubectl -n kube-system exec kuber-router-xxxxx bash ``` @@ -642,7 +642,7 @@ done Note: the other `updateStrategy` for a DaemonSet is RollingUpdate.
-For critical services, we might want to control precisely the update process. +For critical services, we might want to precisely control the update process. --- From c761ce9436d3f644da8544fea08cf6117143cad0 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 10:49:29 -0500 Subject: [PATCH 33/51] Update dmuc.md typo fixes --- slides/k8s/dmuc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slides/k8s/dmuc.md b/slides/k8s/dmuc.md index f5452032..06c594b7 100644 --- a/slides/k8s/dmuc.md +++ b/slides/k8s/dmuc.md @@ -36,7 +36,7 @@ - We will use the machine indicated as `dmuc` (this stands for "Dessine Moi Un Cluster" or "Draw Me A Sheep", -
in hommage to Saint-Exupery's "The Little Prince") +
in homage to Saint-Exupery's "The Little Prince") - This machine: @@ -463,7 +463,7 @@ docker run alpine echo hello world - It will start in *standalone* mode -- Just like with the controler manager, we need to tell kubelet where is the API server +- Just like with the controller manager, we need to tell kubelet where the API server is - Alas, kubelet doesn't have a simple `--master` option From 95b05d8a2305ba4cde4af616ee0731b7e8398801 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 10:54:26 -0500 Subject: [PATCH 34/51] Update metrics-server.md --- slides/k8s/metrics-server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/metrics-server.md b/slides/k8s/metrics-server.md index c08b00d7..7cdb7ffa 100644 --- a/slides/k8s/metrics-server.md +++ b/slides/k8s/metrics-server.md @@ -57,7 +57,7 @@ If it shows our nodes and their CPU and memory load, we're good! .exercise[ -- Show resource usage accross all containers: +- Show resource usage across all containers: ```bash kuebectl top pods --containers --all-namespaces ``` From dd5a66704c404391754cb60463d900d744f7a560 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 11:18:17 -0500 Subject: [PATCH 35/51] Update setup-selfhosted.md --- slides/k8s/setup-selfhosted.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/setup-selfhosted.md b/slides/k8s/setup-selfhosted.md index d2302524..45bd1d81 100644 --- a/slides/k8s/setup-selfhosted.md +++ b/slides/k8s/setup-selfhosted.md @@ -92,7 +92,7 @@ - Each distribution / installer has pros and cons -- Before picking one, we should sort our priorities: +- Before picking one, we should sort out our priorities: - cloud, on-premises, hybrid? From 603baa096602e82a67b47122721b5c8f6b797e2c Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 12:25:29 -0500 Subject: [PATCH 36/51] Update resource-limits.md Suggested rewordings for clarity - but I am not going to merge it myself, as I don't want to accidentally change meaning. --- slides/k8s/resource-limits.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slides/k8s/resource-limits.md b/slides/k8s/resource-limits.md index 673d3d6d..578f6608 100644 --- a/slides/k8s/resource-limits.md +++ b/slides/k8s/resource-limits.md @@ -309,7 +309,7 @@ per Pod, but it's not [officially documented yet](https://github.com/kubernetes/ (but the pods will not be created as long as the LimitRange is in effect) -- If there are multiple LimitRange, they all apply together +- If there are multiple LimitRange restrictions, they all apply together (which means that it's possible to specify conflicting LimitRanges,
preventing any Pod from being created) @@ -329,7 +329,7 @@ per Pod, but it's not [officially documented yet](https://github.com/kubernetes/ - Quotas can apply to resource limits and/or requests - (like the CPU and memory limits that we saw earlire) + (like the CPU and memory limits that we saw earlier) - Quotas can also apply to other resources: @@ -343,7 +343,7 @@ per Pod, but it's not [officially documented yet](https://github.com/kubernetes/ ## Creating a quota for a namespace -- Quotas are enforced by creating a ResourceQuotas object +- Quotas are enforced by creating a ResourceQuota object - ResourceQuota objects are namespaced, and apply to their namespace only @@ -441,7 +441,7 @@ services.nodeports 0 0 - Quotas can be linked to a PriorityClass -- This allows to reserve resources to pods within a namespace +- This allows us to reserve resources for pods within a namespace - For more details, check [this documentation page](https://kubernetes.io/docs/concepts/policy/resource-quotas/#resource-quota-per-priorityclass) From 627c3361a108934fadd14222b44825f0d3fffa4b Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 12:29:33 -0500 Subject: [PATCH 37/51] Update prereqs-admin.md typo fix --- slides/k8s/prereqs-admin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/prereqs-admin.md b/slides/k8s/prereqs-admin.md index e3f77154..9a249abe 100644 --- a/slides/k8s/prereqs-admin.md +++ b/slides/k8s/prereqs-admin.md @@ -16,7 +16,7 @@ ## Labs and exercises -- We are going to build and breaks multiple clusters +- We are going to build and break multiple clusters - Everyone will get their own private environment(s) From fea69f62d60adb9fe27bda97da11625574b124d9 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 12:34:40 -0500 Subject: [PATCH 38/51] Update multinode.md Clarifications and rewordings --- slides/k8s/multinode.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/slides/k8s/multinode.md b/slides/k8s/multinode.md index 096e92eb..0e1b7255 100644 --- a/slides/k8s/multinode.md +++ b/slides/k8s/multinode.md @@ -84,7 +84,7 @@ class: extra-details -## Differences with `dmuc` +## Differences from `dmuc` - Our new control plane listens on `0.0.0.0` instead of the default `127.0.0.1` @@ -144,7 +144,7 @@ class: extra-details sudo kubelet --kubeconfig ~/kubeconfig ``` -- Open more terminals and join the other nodes: +- Open more terminals and join the other nodes to the cluster: ```bash ssh kubenet2 sudo kubelet --kubeconfig ~/kubeconfig ssh kubenet3 sudo kubelet --kubeconfig ~/kubeconfig @@ -364,7 +364,7 @@ class: extra-details - The IP address of a pod cannot change -- kubelet doesn't automatically kill/restart containers will "invalid" addresses +- kubelet doesn't automatically kill/restart containers with "invalid" addresses
(in fact, from kubelet's point of view, there is no such thing as an "invalid" address) @@ -429,7 +429,7 @@ Sometimes it works, sometimes it doesn't. Why? ## Routing traffic -- Our pods have new, distinct, IP addresses +- Our pods have new, distinct IP addresses - But they are on host-local, isolated networks @@ -467,7 +467,7 @@ Sometimes it works, sometimes it doesn't. Why? ## Routing basics -- We need to tell to *each* node: +- We need to tell *each* node: "The subnet 10.C.N.0/24 is located on node N" (for all values of N) From b92da2cf9f68d29634f1aee59023f227cad96651 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 12:37:37 -0500 Subject: [PATCH 39/51] Update metrics-server.md Small details --- slides/k8s/metrics-server.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slides/k8s/metrics-server.md b/slides/k8s/metrics-server.md index 7cdb7ffa..666234b7 100644 --- a/slides/k8s/metrics-server.md +++ b/slides/k8s/metrics-server.md @@ -33,7 +33,7 @@ If it shows our nodes and their CPU and memory load, we're good! ## Installing metrics server -- The metrics server doesn't have any particular requirement +- The metrics server doesn't have any particular requirements (it doesn't need persistence, as it doesn't *store* metrics) @@ -53,7 +53,7 @@ If it shows our nodes and their CPU and memory load, we're good! ## Showing container resource usage -- Once metrics server is running, we can check container resource usage +- Once the metrics server is running, we can check container resource usage .exercise[ From f272df9aae7706615d6b370d575977528a7a07da Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 13:06:10 -0500 Subject: [PATCH 40/51] Update dmuc.md typo fixes --- slides/k8s/dmuc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slides/k8s/dmuc.md b/slides/k8s/dmuc.md index 06c594b7..b4b2dc46 100644 --- a/slides/k8s/dmuc.md +++ b/slides/k8s/dmuc.md @@ -734,7 +734,7 @@ This won't work. We need kube-proxy to enable internal communication. .exercise[ -- Check again the Service's ClusterIP, and retry connecting: +- Check the Service's ClusterIP again, and retry connecting: ```bash kubectl get service web curl http://`X.X.X.X` @@ -761,7 +761,7 @@ class: extra-details iptables -t nat -L OUTPUT ``` -- Traffic is sent to `KUBE-SERVICES`, check that too: +- Traffic is sent to `KUBE-SERVICES`; check that too: ```bash iptables -t nat -L KUBE-SERVICES ``` From aa55a5b870416cfd49c2c654bb34230e8e74084e Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 13:09:42 -0500 Subject: [PATCH 41/51] Update multinode.md Typo fixes --- slides/k8s/multinode.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slides/k8s/multinode.md b/slides/k8s/multinode.md index 096e92eb..aea39a1f 100644 --- a/slides/k8s/multinode.md +++ b/slides/k8s/multinode.md @@ -49,7 +49,7 @@ - Go to the `compose/simple-k8s-control-plane` directory: ```bash - cd orchestration-workshop/compose/simple-k8s-control-plane + cd container.training/compose/simple-k8s-control-plane ``` - Start the control plane: @@ -67,7 +67,7 @@ .exercise[ -- Show control plane components statuses: +- Show control plane component statuses: ```bash kubectl get componentstatuses kubectl get cs From 3f40cc25a2f31e54a02d64e683deee1be9837e86 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 13:24:40 -0500 Subject: [PATCH 42/51] Update setup-managed.md Need to escape the `&` or the URL gets changed to an incorrect one. --- slides/k8s/setup-managed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/setup-managed.md b/slides/k8s/setup-managed.md index 74d94d36..935840a3 100644 --- a/slides/k8s/setup-managed.md +++ b/slides/k8s/setup-managed.md @@ -142,7 +142,7 @@ with a cloud provider az login ``` -- Select a [region](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=kubernetes-service®ions=all +- Select a [region](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=kubernetes-service\®ions=all ) - Create a "resource group": From dbcb4371d4e9e14c39686da1b922df3bb1575411 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 15:33:08 -0500 Subject: [PATCH 43/51] Update cloud-controller-manager.md Wording fixes. --- slides/k8s/cloud-controller-manager.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slides/k8s/cloud-controller-manager.md b/slides/k8s/cloud-controller-manager.md index dd48e7d0..d0975278 100644 --- a/slides/k8s/cloud-controller-manager.md +++ b/slides/k8s/cloud-controller-manager.md @@ -6,7 +6,7 @@ - These features were initially implemented in API server and controller manager -- Since Kubernetes 1.6, these features are available through a separated process: +- Since Kubernetes 1.6, these features are available through a separate process: the *Cloud Controller Manager* @@ -87,7 +87,7 @@ The list includes the following providers: - What kind of details would you like to see in this section? -- Would you appreciate to get details on clouds that you don't / won't use? +- Would you appreciate details on clouds that you don't / won't use? --- From fada4e8ae7ce388a785dff415a224f2b5dc64431 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 15:36:24 -0500 Subject: [PATCH 44/51] Update bootstrap.md Typo fix --- slides/k8s/bootstrap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/bootstrap.md b/slides/k8s/bootstrap.md index 8d9483e3..b8531dc3 100644 --- a/slides/k8s/bootstrap.md +++ b/slides/k8s/bootstrap.md @@ -220,7 +220,7 @@ This is the TLS bootstrap mechanism, step by step. - The CSR object is accepted (automatically or by an admin) -- The node gets notified, and retrives the certificate +- The node gets notified, and retrieves the certificate - The node can now join the cluster From 6d761b4dccef78817125207a455e1a7ffc30826f Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 15:39:22 -0500 Subject: [PATCH 45/51] Fixing broken link This link was malformed. --- slides/k8s/resource-limits.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/slides/k8s/resource-limits.md b/slides/k8s/resource-limits.md index 673d3d6d..412a9775 100644 --- a/slides/k8s/resource-limits.md +++ b/slides/k8s/resource-limits.md @@ -114,8 +114,7 @@ Each pod is assigned a QoS class (visible in `status.qosClass`). (as long as they stay within their limits) -(Pod QoS is also explained in [this page](https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/) of the Kubernetes documentation and in [this blog post](Explanation of quality of service -https://medium.com/google-cloud/quality-of-service-class-qos-in-kubernetes-bb76a89eb2c6).) +(Pod QoS is also explained in [this page](https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/) of the Kubernetes documentation and in [this explanation of quality of service](https://medium.com/google-cloud/quality-of-service-class-qos-in-kubernetes-bb76a89eb2c6).) --- From 9296b375f37ef02ce80fc4f313f194a48171bd5c Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 15:47:09 -0500 Subject: [PATCH 46/51] Update resource-limits.md --- slides/k8s/resource-limits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/resource-limits.md b/slides/k8s/resource-limits.md index 578f6608..d008e172 100644 --- a/slides/k8s/resource-limits.md +++ b/slides/k8s/resource-limits.md @@ -509,7 +509,7 @@ services.nodeports 0 0 - If you see trends: adjust the LimitRange - (rather that adjusting every individual set of pod limits) + (rather than adjusting every individual set of pod limits) - Observe the resource usage of your namespaces From f5d523d3c8bf0019bb89093a293dde5f1a55a903 Mon Sep 17 00:00:00 2001 From: Bridget Kromhout Date: Sat, 20 Apr 2019 15:54:21 -0500 Subject: [PATCH 47/51] Update cluster-sizing.md Suggested clarification and link --- slides/k8s/cluster-sizing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slides/k8s/cluster-sizing.md b/slides/k8s/cluster-sizing.md index d2263cfd..b5b11e56 100644 --- a/slides/k8s/cluster-sizing.md +++ b/slides/k8s/cluster-sizing.md @@ -52,7 +52,7 @@ ## Which pods get evicted? -- kubelet looks at the pods QoS and PriorityClass +- kubelet looks at the pods' QoS and PriorityClass - First, pods with BestEffort QoS are considered @@ -154,7 +154,7 @@ class: extra-details - "Running Kubernetes without nodes" -- Systems like Virtual Kubelet or Kiyot can run pods using on-demand resources +- Systems like [Virtual Kubelet](https://virtual-kubelet.io/) or Kiyot can run pods using on-demand resources - Virtual Kubelet can leverage e.g. ACI or Fargate to run pods From fba198d4d7760f2e0d26312c341bddea24dbf9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Sat, 20 Apr 2019 17:42:13 -0500 Subject: [PATCH 48/51] Update resource-limits.md --- slides/k8s/resource-limits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/resource-limits.md b/slides/k8s/resource-limits.md index 412a9775..18a1f088 100644 --- a/slides/k8s/resource-limits.md +++ b/slides/k8s/resource-limits.md @@ -114,7 +114,7 @@ Each pod is assigned a QoS class (visible in `status.qosClass`). (as long as they stay within their limits) -(Pod QoS is also explained in [this page](https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/) of the Kubernetes documentation and in [this explanation of quality of service](https://medium.com/google-cloud/quality-of-service-class-qos-in-kubernetes-bb76a89eb2c6).) +(Pod QoS is also explained in [this page](https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/) of the Kubernetes documentation and in [this blog post](https://medium.com/google-cloud/quality-of-service-class-qos-in-kubernetes-bb76a89eb2c6).) --- From 3d001b05857845a09af5b877f93bc59af1c67043 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sun, 21 Apr 2019 06:05:09 -0500 Subject: [PATCH 49/51] 'shortly unavailable' means 'unavailable soon', not 'briefly unavailable' --- slides/k8s/cluster-upgrade.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slides/k8s/cluster-upgrade.md b/slides/k8s/cluster-upgrade.md index fcf89145..4fa08593 100644 --- a/slides/k8s/cluster-upgrade.md +++ b/slides/k8s/cluster-upgrade.md @@ -232,7 +232,7 @@ ## Checking what we've done -- The API server will be shortly unavailable while kubelet restarts it +- The API server will be briefly unavailable while kubelet restarts it .exercise[ From 2fe4644225bb97840751263d92fa2225b17439f6 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sun, 21 Apr 2019 08:24:00 -0500 Subject: [PATCH 50/51] Tweaks/fixes addressing @bridgetkromhout's feedback <3 --- slides/k8s/prereqs-admin.md | 14 +++++++++++--- slides/k8s/setup-managed.md | 8 ++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/slides/k8s/prereqs-admin.md b/slides/k8s/prereqs-admin.md index 9a249abe..7e9716d0 100644 --- a/slides/k8s/prereqs-admin.md +++ b/slides/k8s/prereqs-admin.md @@ -54,10 +54,18 @@ - We are using basic cloud VMs with Ubuntu LTS -- The Kubernetes packages have been installed +- Kubernetes [packages] or [binaries] have been installed - (from official repos) + (depending on what we want to accomplish in the lab) - We disabled IP address checks - (to allow pod IP addresses to be carried by the cloud network) + - we want to route pod traffic directly between nodes + + - most cloud providers will treat pod IP addresses as invalid + + - ... and filter them out; so we disable that filter + +[packages]: https://kubernetes.io/docs/setup/independent/install-kubeadm/#installing-kubeadm-kubelet-and-kubectl + +[binaries]: https://kubernetes.io/docs/setup/release/notes/#server-binaries diff --git a/slides/k8s/setup-managed.md b/slides/k8s/setup-managed.md index 935840a3..ccc1b084 100644 --- a/slides/k8s/setup-managed.md +++ b/slides/k8s/setup-managed.md @@ -159,7 +159,7 @@ with a cloud provider az aks create --resource-group my-aks-group --name my-aks-cluster ``` -- Wait 15 minutes (sometimes longer) +- Wait about 5-10 minutes - Add credentials to `kubeconfig`: ```bash @@ -179,12 +179,12 @@ with a cloud provider - Delete the resource group: ```bash - az group delete --resource-group aks-test + az group delete --resource-group my-aks-group ``` -- Note: delete actions can take a long time too! +- Note: delete actions can take a while too! - (10 minutes is typical) + (5-10 minutes as well) --- From 1af958488e106266848f5f7dad787943c6d1e33a Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Sun, 21 Apr 2019 08:30:39 -0500 Subject: [PATCH 51/51] =?UTF-8?q?More=20fixes=20thanks=20to=20@bridgetkrom?= =?UTF-8?q?hout=20excellent=20feedback=20and=20advice=20=E2=99=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- slides/k8s/architecture.md | 4 ++-- slides/k8s/cluster-upgrade.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/slides/k8s/architecture.md b/slides/k8s/architecture.md index 701a3415..ecd28284 100644 --- a/slides/k8s/architecture.md +++ b/slides/k8s/architecture.md @@ -46,7 +46,7 @@ class: pic - Our containerized workloads -- A container engine like Docker, CRI-O, container... +- A container engine like Docker, CRI-O, containerd... (in theory, the choice doesn't matter, as the engine is abstracted by Kubernetes) @@ -72,7 +72,7 @@ class: pic - the nodes register and get their instructions through the API server - - the other control plane components also register to the API server + - the other control plane components also register with the API server - API server is the only component that reads/writes from/to etcd diff --git a/slides/k8s/cluster-upgrade.md b/slides/k8s/cluster-upgrade.md index 4fa08593..a599f58d 100644 --- a/slides/k8s/cluster-upgrade.md +++ b/slides/k8s/cluster-upgrade.md @@ -77,7 +77,7 @@ ## What version are we running anyway? -- When I say, "I'm Kubernetes 1.11", is that the version of: +- When I say, "I'm running Kubernetes 1.11", is that the version of: - kubectl