diff --git a/k8s/dockercoins.yaml b/k8s/dockercoins.yaml
new file mode 100644
index 00000000..4e754715
--- /dev/null
+++ b/k8s/dockercoins.yaml
@@ -0,0 +1,160 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: hasher
+ name: hasher
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: hasher
+ template:
+ metadata:
+ labels:
+ app: hasher
+ spec:
+ containers:
+ - image: dockercoins/hasher:v0.1
+ name: hasher
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ app: hasher
+ name: hasher
+spec:
+ ports:
+ - port: 80
+ protocol: TCP
+ targetPort: 80
+ selector:
+ app: hasher
+ type: ClusterIP
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: redis
+ name: redis
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: redis
+ template:
+ metadata:
+ labels:
+ app: redis
+ spec:
+ containers:
+ - image: redis
+ name: redis
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ app: redis
+ name: redis
+spec:
+ ports:
+ - port: 6379
+ protocol: TCP
+ targetPort: 6379
+ selector:
+ app: redis
+ type: ClusterIP
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: rng
+ name: rng
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: rng
+ template:
+ metadata:
+ labels:
+ app: rng
+ spec:
+ containers:
+ - image: dockercoins/rng:v0.1
+ name: rng
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ app: rng
+ name: rng
+spec:
+ ports:
+ - port: 80
+ protocol: TCP
+ targetPort: 80
+ selector:
+ app: rng
+ type: ClusterIP
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: webui
+ name: webui
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: webui
+ template:
+ metadata:
+ labels:
+ app: webui
+ spec:
+ containers:
+ - image: dockercoins/webui:v0.1
+ name: webui
+---
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ app: webui
+ name: webui
+spec:
+ ports:
+ - port: 80
+ protocol: TCP
+ targetPort: 80
+ selector:
+ app: webui
+ type: NodePort
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: worker
+ name: worker
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: worker
+ template:
+ metadata:
+ labels:
+ app: worker
+ spec:
+ containers:
+ - image: dockercoins/worker:v0.1
+ name: worker
diff --git a/prepare-local/README.md b/prepare-local/README.md
index 0fb0057f..96ad0ba3 100644
--- a/prepare-local/README.md
+++ b/prepare-local/README.md
@@ -7,9 +7,9 @@ workshop.
## 1. Prerequisites
-
Virtualbox, Vagrant and Ansible
+
- Virtualbox: https://www.virtualbox.org/wiki/Downloads
- Vagrant: https://www.vagrantup.com/downloads.html
@@ -25,7 +25,7 @@ Virtualbox, Vagrant and Ansible
$ git clone --recursive https://github.com/ansible/ansible.git
$ cd ansible
- $ git checkout stable-2.0.0.1
+ $ git checkout stable-{{ getStableVersionFromAnsibleProject }}
$ git submodule update
- source the setup script to make Ansible available on this terminal session:
@@ -38,6 +38,7 @@ Virtualbox, Vagrant and Ansible
## 2. Preparing the environment
+Change into directory that has your Vagrantfile
Run the following commands:
@@ -66,6 +67,14 @@ will reflect inside the instance.
- Depending on the Vagrant version, `sudo apt-get install bsdtar` may be needed
+- If you get an error like "no Vagrant file found" or you have a file but "cannot open base box" when running `vagrant up`,
+chances are good you not in the correct directory.
+Make sure you are in sub directory named "prepare-local". It has all the config files required by ansible, vagrant and virtualbox
+
+- If you are using Python 3.7, running the ansible-playbook provisioning, see an error like "SyntaxError: invalid syntax" and it mentions
+the word "async", you need to upgrade your Ansible version to 2.6 or higher to resolve the keyword conflict.
+https://github.com/ansible/ansible/issues/42105
+
- If you get strange Ansible errors about dependencies, try to check your pip
version with `pip --version`. The current version is 8.1.1. If your pip is
older than this, upgrade it with `sudo pip install --upgrade pip`, restart
diff --git a/prepare-vms/README.md b/prepare-vms/README.md
index c86c4c81..4dbaff56 100644
--- a/prepare-vms/README.md
+++ b/prepare-vms/README.md
@@ -10,15 +10,21 @@ These tools can help you to create VMs on:
- [Docker](https://docs.docker.com/engine/installation/)
- [Docker Compose](https://docs.docker.com/compose/install/)
-- [Parallel SSH](https://code.google.com/archive/p/parallel-ssh/) (on a Mac: `brew install pssh`) - the configuration scripts require this
+- [Parallel SSH](https://code.google.com/archive/p/parallel-ssh/) (on a Mac: `brew install pssh`)
Depending on the infrastructure that you want to use, you also need to install
the Azure CLI, the AWS CLI, or terraform (for OpenStack deployment).
And if you want to generate printable cards:
-- [pyyaml](https://pypi.python.org/pypi/PyYAML) (on a Mac: `brew install pyyaml`)
-- [jinja2](https://pypi.python.org/pypi/Jinja2) (on a Mac: `brew install jinja2`)
+- [pyyaml](https://pypi.python.org/pypi/PyYAML)
+- [jinja2](https://pypi.python.org/pypi/Jinja2)
+
+You can install them with pip (perhaps with `pip install --user`, or even use `virtualenv` if that's your thing).
+
+These require Python 3. If you are on a Mac, see below for specific instructions on setting up
+Python 3 to be the default Python on a Mac. In particular, if you installed `mosh`, Homebrew
+may have changed your default Python to Python 2.
## General Workflow
@@ -256,3 +262,32 @@ If you don't have `wkhtmltopdf` installed, you will get a warning that it is a m
- Don't write to bash history in system() in postprep
- compose, etc version inconsistent (int vs str)
+
+## Making sure Python3 is the default (Mac only)
+
+Check the `/usr/local/bin/python` symlink. It should be pointing to
+`/usr/local/Cellar/python/3`-something. If it isn't, follow these
+instructions.
+
+1) Verify that Python 3 is installed.
+
+```
+ls -la /usr/local/Cellar/Python
+```
+
+You should see one or more versions of Python 3. If you don't,
+install it with `brew install python`.
+
+2) Verify that `python` points to Python3.
+
+```
+ls -la /usr/local/bin/python
+```
+
+If this points to `/usr/local/Cellar/python@2`, then we'll need to change it.
+
+```
+rm /usr/local/bin/python
+ln -s /usr/local/Cellar/Python/xxxx /usr/local/bin/python
+# where xxxx is the most recent Python 3 version you saw above
+```
diff --git a/prepare-vms/lib/commands.sh b/prepare-vms/lib/commands.sh
index cc8b6c60..ade36b6e 100644
--- a/prepare-vms/lib/commands.sh
+++ b/prepare-vms/lib/commands.sh
@@ -127,11 +127,11 @@ _cmd_kubebins() {
set -e
cd /usr/local/bin
if ! [ -x etcd ]; then
- curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz \
+ curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.15/etcd-v3.3.15-linux-amd64.tar.gz \
| sudo tar --strip-components=1 --wildcards -zx '*/etcd' '*/etcdctl'
fi
if ! [ -x hyperkube ]; then
- curl -L https://dl.k8s.io/v1.14.1/kubernetes-server-linux-amd64.tar.gz \
+ curl -L https://dl.k8s.io/v1.16.2/kubernetes-server-linux-amd64.tar.gz \
| sudo tar --strip-components=3 -zx kubernetes/server/bin/hyperkube
fi
if ! [ -x kubelet ]; then
@@ -143,7 +143,7 @@ _cmd_kubebins() {
sudo mkdir -p /opt/cni/bin
cd /opt/cni/bin
if ! [ -x bridge ]; then
- curl -L https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-amd64-v0.7.5.tgz \
+ curl -L https://github.com/containernetworking/plugins/releases/download/v0.7.6/cni-plugins-amd64-v0.7.6.tgz \
| sudo tar -zx
fi
"
diff --git a/slides/index.yaml b/slides/index.yaml
index c98a0d1a..788d3b1f 100644
--- a/slides/index.yaml
+++ b/slides/index.yaml
@@ -5,6 +5,7 @@
speaker: jpetazzo
title: Deploying and scaling applications with Kubernetes
attend: https://conferences.oreilly.com/velocity/vl-eu/public/schedule/detail/79109
+ slides: https://velocity-2019-11.container.training/
- date: 2019-11-13
country: fr
diff --git a/slides/intro-fullday.yml b/slides/intro-fullday.yml
index b137fb59..a0e31be0 100644
--- a/slides/intro-fullday.yml
+++ b/slides/intro-fullday.yml
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
diff --git a/slides/intro-selfpaced.yml b/slides/intro-selfpaced.yml
index 5ebc32c5..14c494b3 100644
--- a/slides/intro-selfpaced.yml
+++ b/slides/intro-selfpaced.yml
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- in-person
diff --git a/slides/k8s/authn-authz.md b/slides/k8s/authn-authz.md
index a2c0c431..57f2c965 100644
--- a/slides/k8s/authn-authz.md
+++ b/slides/k8s/authn-authz.md
@@ -667,17 +667,12 @@ class: extra-details
- For auditing purposes, sometimes we want to know who can perform an action
-- There is a proof-of-concept tool by Aqua Security which does exactly that:
+- There are a few tools to help us with that
- https://github.com/aquasecurity/kubectl-who-can
+ - [kubectl-who-can](https://github.com/aquasecurity/kubectl-who-can) by Aqua Security
-- This is one way to install it:
- ```bash
- docker run --rm -v /usr/local/bin:/go/bin golang \
- go get -v github.com/aquasecurity/kubectl-who-can
- ```
+ - [Review Access (aka Rakkess)](https://github.com/corneliusweig/rakkess)
-- This is one way to use it:
- ```bash
- kubectl-who-can create pods
- ```
+- Both are available as standalone programs, or as plugins for `kubectl`
+
+ (`kubectl` plugins can be installed and managed with `krew`)
diff --git a/slides/k8s/buildshiprun-dockerhub.md b/slides/k8s/buildshiprun-dockerhub.md
index 1a75774f..3d414c49 100644
--- a/slides/k8s/buildshiprun-dockerhub.md
+++ b/slides/k8s/buildshiprun-dockerhub.md
@@ -15,26 +15,3 @@
- `dockercoins/webui:v0.1`
- `dockercoins/worker:v0.1`
-
----
-
-## Setting `$REGISTRY` and `$TAG`
-
-- In the upcoming exercises and labs, we use a couple of environment variables:
-
- - `$REGISTRY` as a prefix to all image names
-
- - `$TAG` as the image version tag
-
-- For example, the worker image is `$REGISTRY/worker:$TAG`
-
-- If you copy-paste the commands in these exercises:
-
- **make sure that you set `$REGISTRY` and `$TAG` first!**
-
-- For example:
- ```bash
- export REGISTRY=dockercoins TAG=v0.1
- ```
-
- (this will expand `$REGISTRY/worker:$TAG` to `dockercoins/worker:v0.1`)
diff --git a/slides/k8s/dmuc.md b/slides/k8s/dmuc.md
index 17f60543..94658c6b 100644
--- a/slides/k8s/dmuc.md
+++ b/slides/k8s/dmuc.md
@@ -481,13 +481,13 @@ docker run alpine echo hello world
.exercise[
-- Create the file `kubeconfig.kubelet` with `kubectl`:
+- Create the file `~/.kube/config` with `kubectl`:
```bash
- kubectl --kubeconfig kubeconfig.kubelet config \
+ kubectl config \
set-cluster localhost --server http://localhost:8080
- kubectl --kubeconfig kubeconfig.kubelet config \
+ kubectl config \
set-context localhost --cluster localhost
- kubectl --kubeconfig kubeconfig.kubelet config \
+ kubectl config \
use-context localhost
```
@@ -495,19 +495,7 @@ docker run alpine echo hello world
---
-## 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
+## Our `~/.kube/config` file
The file that we generated looks like the one below.
@@ -533,9 +521,9 @@ clusters:
.exercise[
-- Start kubelet with that `kubeconfig.kubelet` file:
+- Start kubelet with that kubeconfig file:
```bash
- kubelet --kubeconfig kubeconfig.kubelet
+ kubelet --kubeconfig ~/.kube/config
```
]
diff --git a/slides/k8s/healthchecks-more.md b/slides/k8s/healthchecks-more.md
index b3093c93..a812966c 100644
--- a/slides/k8s/healthchecks-more.md
+++ b/slides/k8s/healthchecks-more.md
@@ -1,41 +1,3 @@
-## Questions to ask before adding healthchecks
-
-- Do we want liveness, readiness, both?
-
- (sometimes, we can use the same check, but with different failure thresholds)
-
-- Do we have existing HTTP endpoints that we can use?
-
-- Do we need to add new endpoints, or perhaps use something else?
-
-- Are our healthchecks likely to use resources and/or slow down the app?
-
-- Do they depend on additional services?
-
- (this can be particularly tricky, see next slide)
-
----
-
-## Healthchecks and dependencies
-
-- A good healthcheck should always indicate the health of the service itself
-
-- It should not be affected by the state of the service's dependencies
-
-- Example: a web server requiring a database connection to operate
-
- (make sure that the healthcheck can report "OK" even if the database is down;
-
- because it won't help us to restart the web server if the issue is with the DB!)
-
-- Example: a microservice calling other microservices
-
-- Example: a worker process
-
- (these will generally require minor code changes to report health)
-
----
-
## Adding healthchecks to an app
- Let's add healthchecks to DockerCoins!
@@ -370,24 +332,4 @@ class: extra-details
(and have gcr.io/pause take care of the reaping)
----
-
-## Healthchecks for worker
-
-- Readiness isn't useful
-
- (because worker isn't a backend for a service)
-
-- Liveness may help us restart a broken worker, but how can we check it?
-
-- Embedding an HTTP server is an option
-
- (but it has a high potential for unwanted side effects and false positives)
-
-- Using a "lease" file can be relatively easy:
-
- - touch a file during each iteration of the main loop
-
- - check the timestamp of that file from an exec probe
-
-- Writing logs (and checking them from the probe) also works
+- Discussion of this in [Video - 10 Ways to Shoot Yourself in the Foot with Kubernetes, #9 Will Surprise You](https://www.youtube.com/watch?v=QKI-JRs2RIE)
diff --git a/slides/k8s/healthchecks.md b/slides/k8s/healthchecks.md
index a72fedbf..8055bfd8 100644
--- a/slides/k8s/healthchecks.md
+++ b/slides/k8s/healthchecks.md
@@ -42,9 +42,11 @@
- internal corruption (causing all requests to error)
-- If the liveness probe fails *N* consecutive times, the container is killed
+- Anything where our incident response would be "just restart/reboot it"
-- *N* is the `failureThreshold` (3 by default)
+.warning[**Do not** use liveness probes for problems that can't be fixed by a restart]
+
+- Otherwise we just restart our pods for no reason, creating useless load
---
@@ -52,7 +54,7 @@
- Indicates if the container is ready to serve traffic
-- If a container becomes "unready" (let's say busy!) it might be ready again soon
+- If a container becomes "unready" it might be ready again soon
- If the readiness probe fails:
@@ -66,19 +68,79 @@
## When to use a readiness probe
-- To indicate temporary failures
+- To indicate failure due to an external cause
- - the application can only service *N* parallel connections
+ - database is down or unreachable
- - the runtime is busy doing garbage collection or initial data load
+ - mandatory auth or other backend service unavailable
-- The container is marked as "not ready" after `failureThreshold` failed attempts
+- To indicate temporary failure or unavailability
- (3 by default)
+ - application can only service *N* parallel connections
-- It is marked again as "ready" after `successThreshold` successful attempts
+ - runtime is busy doing garbage collection or initial data load
- (1 by default)
+- For processes that take a long time to start
+
+ (more on that later)
+
+---
+
+## Dependencies
+
+- If a web server depends on a database to function, and the database is down:
+
+ - the web server's liveness probe should succeed
+
+ - the web server's readiness probe should fail
+
+- Same thing for any hard dependency (without which the container can't work)
+
+.warning[**Do not** fail liveness probes for problems that are external to the container]
+
+---
+
+## Timing and thresholds
+
+- Probes are executed at intervals of `periodSeconds` (default: 10)
+
+- The timeout for a probe is set with `timeoutSeconds` (default: 1)
+
+.warning[If a probe takes longer than that, it is considered as a FAIL]
+
+- A probe is considered successful after `successThreshold` successes (default: 1)
+
+- A probe is considered failing after `failureThreshold` failures (default: 3)
+
+- A probe can have an `initialDelaySeconds` parameter (default: 0)
+
+- Kubernetes will wait that amount of time before running the probe for the first time
+
+ (this is important to avoid killing services that take a long time to start)
+
+---
+
+class: extra-details
+
+## Startup probe
+
+- Kubernetes 1.16 introduces a third type of probe: `startupProbe`
+
+ (it is in `alpha` in Kubernetes 1.16)
+
+- It can be used to indicate "container not ready *yet*"
+
+ - process is still starting
+
+ - loading external data, priming caches
+
+- Before Kubernetes 1.16, we had to use the `initialDelaySeconds` parameter
+
+ (available for both liveness and readiness probes)
+
+- `initialDelaySeconds` is a rigid delay (always wait X before running probes)
+
+- `startupProbe` works better when a container start time can vary a lot
---
@@ -112,10 +174,12 @@
(instead of serving errors or timeouts)
-- Overloaded backends get removed from load balancer rotation
+- Unavailable backends get removed from load balancer rotation
(thus improving response times across the board)
+- If a probe is not defined, it's as if there was an "always successful" probe
+
---
## Example: HTTP probe
@@ -165,14 +229,56 @@ If the Redis process becomes unresponsive, it will be killed.
---
-## Details about liveness and readiness probes
+## Questions to ask before adding healthchecks
-- Probes are executed at intervals of `periodSeconds` (default: 10)
+- Do we want liveness, readiness, both?
-- The timeout for a probe is set with `timeoutSeconds` (default: 1)
+ (sometimes, we can use the same check, but with different failure thresholds)
-- A probe is considered successful after `successThreshold` successes (default: 1)
+- Do we have existing HTTP endpoints that we can use?
-- A probe is considered failing after `failureThreshold` failures (default: 3)
+- Do we need to add new endpoints, or perhaps use something else?
-- If a probe is not defined, it's as if there was an "always successful" probe
+- Are our healthchecks likely to use resources and/or slow down the app?
+
+- Do they depend on additional services?
+
+ (this can be particularly tricky, see next slide)
+
+---
+
+## Healthchecks and dependencies
+
+- Liveness checks should not be influenced by the state of external services
+
+- All checks should reply quickly (by default, less than 1 second)
+
+- Otherwise, they are considered to fail
+
+- This might require to check the health of dependencies asynchronously
+
+ (e.g. if a database or API might be healthy but still take more than
+ 1 second to reply, we should check the status asynchronously and report
+ a cached status)
+
+---
+
+## Healthchecks for workers
+
+(In that context, worker = process that doesn't accept connections)
+
+- Readiness isn't useful
+
+ (because workers aren't backends for a service)
+
+- Liveness may help us restart a broken worker, but how can we check it?
+
+- Embedding an HTTP server is a (potentially expensive) option
+
+- Using a "lease" file can be relatively easy:
+
+ - touch a file during each iteration of the main loop
+
+ - check the timestamp of that file from an exec probe
+
+- Writing logs (and checking them from the probe) also works
diff --git a/slides/k8s/kubectlrun.md b/slides/k8s/kubectlrun.md
index 2c8a9c2b..9b7ae2ec 100644
--- a/slides/k8s/kubectlrun.md
+++ b/slides/k8s/kubectlrun.md
@@ -153,10 +153,7 @@ pod/pingpong-7c8bbcd9bc-6c9qz 1/1 Running 0 10m
kubectl logs deploy/pingpong --tail 1 --follow
```
-
+- Leave that command running, so that we can keep an eye on these logs
]
@@ -186,6 +183,44 @@ We could! But the *deployment* would notice it right away, and scale back to the
---
+## Log streaming
+
+- Let's look again at the output of `kubectl logs`
+
+ (the one we started before scaling up)
+
+- `kubectl logs` shows us one line per second
+
+- We could expect 3 lines per second
+
+ (since we should now have 3 pods running `ping`)
+
+- Let's try to figure out what's happening!
+
+---
+
+## Streaming logs of multiple pods
+
+- What happens if we restart `kubectl logs`?
+
+.exercise[
+
+- Interrupt `kubectl logs` (with Ctrl-C)
+
+- Restart it:
+ ```bash
+ kubectl logs deploy/pingpong --tail 1 --follow
+ ```
+
+]
+
+`kubectl logs` will warn us that multiple pods were found, and that it's showing us only one of them.
+
+Let's leave `kubectl logs` running while we keep exploring.
+
+---
+
+
## Resilience
- The *deployment* `pingpong` watches its *replica set*
@@ -196,20 +231,12 @@ We could! But the *deployment* would notice it right away, and scale back to the
.exercise[
-- In a separate window, list pods, and keep watching them:
+- In a separate window, watch the list of pods:
```bash
- kubectl get pods -w
+ watch kubectl get pods
```
-
-
-- Destroy a pod:
+- Destroy the pod currently shown by `kubectl logs`:
```
kubectl delete pod pingpong-xxxxxxxxxx-yyyyy
```
@@ -217,6 +244,23 @@ We could! But the *deployment* would notice it right away, and scale back to the
---
+## What happened?
+
+- `kubectl delete pod` terminates the pod gracefully
+
+ (sending it the TERM signal and waiting for it to shutdown)
+
+- As soon as the pod is in "Terminating" state, the Replica Set replaces it
+
+- But we can still see the output of the "Terminating" pod in `kubectl logs`
+
+- Until 30 seconds later, when the grace period expires
+
+- The pod is then killed, and `kubectl logs` exits
+
+---
+
+
## What if we wanted something different?
- What if we wanted to start a "one-shot" container that *doesn't* get restarted?
@@ -234,6 +278,72 @@ We could! But the *deployment* would notice it right away, and scale back to the
---
+## Scheduling periodic background work
+
+- A Cron Job is a job that will be executed at specific intervals
+
+ (the name comes from the traditional cronjobs executed by the UNIX crond)
+
+- It requires a *schedule*, represented as five space-separated fields:
+
+ - minute [0,59]
+ - hour [0,23]
+ - day of the month [1,31]
+ - month of the year [1,12]
+ - day of the week ([0,6] with 0=Sunday)
+
+- `*` means "all valid values"; `/N` means "every N"
+
+- Example: `*/3 * * * *` means "every three minutes"
+
+---
+
+## Creating a Cron Job
+
+- Let's create a simple job to be executed every three minutes
+
+- Cron Jobs need to terminate, otherwise they'd run forever
+
+.exercise[
+
+- Create the Cron Job:
+ ```bash
+ kubectl run --schedule="*/3 * * * *" --restart=OnFailure --image=alpine sleep 10
+ ```
+
+- Check the resource that was created:
+ ```bash
+ kubectl get cronjobs
+ ```
+
+]
+
+---
+
+## Cron Jobs in action
+
+- At the specified schedule, the Cron Job will create a Job
+
+- The Job will create a Pod
+
+- The Job will make sure that the Pod completes
+
+ (re-creating another one if it fails, for instance if its node fails)
+
+.exercise[
+
+- Check the Jobs that are created:
+ ```bash
+ kubectl get jobs
+ ```
+
+]
+
+(It will take a few minutes before the first job is scheduled.)
+
+---
+
+
## What about that deprecation warning?
- As we can see from the previous slide, `kubectl run` can do many things
diff --git a/slides/k8s/logs-cli.md b/slides/k8s/logs-cli.md
index 5a9a6c1a..5dda900b 100644
--- a/slides/k8s/logs-cli.md
+++ b/slides/k8s/logs-cli.md
@@ -66,6 +66,8 @@ Exactly what we need!
sudo chmod +x /usr/local/bin/stern
```
+- On OS X, just `brew install stern`
+
---
diff --git a/slides/k8s/multinode.md b/slides/k8s/multinode.md
index af6b6194..829208db 100644
--- a/slides/k8s/multinode.md
+++ b/slides/k8s/multinode.md
@@ -218,6 +218,18 @@ class: extra-details
## What's going on?
+- 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
+
+---
+
+## What do we need to do?
+
- On a normal cluster, kubelet is configured to set up pod networking with CNI plugins
- This requires:
@@ -228,14 +240,6 @@ class: extra-details
- 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
@@ -394,7 +398,7 @@ class: extra-details
- Start kube-proxy:
```bash
- sudo kube-proxy --kubeconfig ~/kubeconfig
+ sudo kube-proxy --kubeconfig ~/.kube/config
```
- Expose our Deployment:
diff --git a/slides/k8s/ourapponkube.md b/slides/k8s/ourapponkube.md
index 50eed1ea..abe48620 100644
--- a/slides/k8s/ourapponkube.md
+++ b/slides/k8s/ourapponkube.md
@@ -11,16 +11,36 @@
- Deploy everything else:
```bash
- set -u
- for SERVICE in hasher rng webui worker; do
- kubectl create deployment $SERVICE --image=$REGISTRY/$SERVICE:$TAG
- done
+ kubectl create deployment hasher --image=dockercoins/hasher:v0.1
+ kubectl create deployment rng --image=dockercoins/rng:v0.1
+ kubectl create deployment webui --image=dockercoins/webui:v0.1
+ kubectl create deployment worker --image=dockercoins/worker:v0.1
```
]
---
+class: extra-details
+
+## Deploying other images
+
+- If we wanted to deploy images from another registry ...
+
+- ... Or with a different tag ...
+
+- ... We could use the following snippet:
+
+```bash
+ REGISTRY=dockercoins
+ TAG=v0.1
+ for SERVICE in hasher rng webui worker; do
+ kubectl create deployment $SERVICE --image=$REGISTRY/$SERVICE:$TAG
+ done
+```
+
+---
+
## Is this working?
- After waiting for the deployment to complete, let's look at the logs!
diff --git a/slides/k8s/rollout.md b/slides/k8s/rollout.md
index 5783c020..41aba22d 100644
--- a/slides/k8s/rollout.md
+++ b/slides/k8s/rollout.md
@@ -61,32 +61,6 @@
---
-## Building a new version of the `worker` service
-
-.warning[
-Only run these commands if you have built and pushed DockerCoins to a local registry.
-
-If you are using images from the Docker Hub (`dockercoins/worker:v0.1`), skip this.
-]
-
-.exercise[
-
-- Go to the `stacks` directory (`~/container.training/stacks`)
-
-- Edit `dockercoins/worker/worker.py`; update the first `sleep` line to sleep 1 second
-
-- Build a new tag and push it to the registry:
- ```bash
- #export REGISTRY=localhost:3xxxx
- export TAG=v0.2
- docker-compose -f dockercoins.yml build
- docker-compose -f dockercoins.yml push
- ```
-
-]
-
----
-
## Rolling out the new `worker` service
.exercise[
@@ -105,7 +79,7 @@ If you are using images from the Docker Hub (`dockercoins/worker:v0.1`), skip th
- Update `worker` either with `kubectl edit`, or by running:
```bash
- kubectl set image deploy worker worker=$REGISTRY/worker:$TAG
+ kubectl set image deploy worker worker=dockercoins/worker:v0.2
```
]
@@ -146,8 +120,7 @@ That rollout should be pretty quick. What shows in the web UI?
- Update `worker` by specifying a non-existent image:
```bash
- export TAG=v0.3
- kubectl set image deploy worker worker=$REGISTRY/worker:$TAG
+ kubectl set image deploy worker worker=dockercoins/worker:v0.3
```
- Check what's going on:
@@ -216,27 +189,14 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
.exercise[
-- Check which port the dashboard is on:
- ```bash
- kubectl -n kube-system get svc socat
- ```
+- Connect to the dashboard that we deployed earlier
+
+- Check that we have failures in Deployments, Pods, and Replica Sets
+
+- Can we see the reason for the failure?
]
-Note the `3xxxx` port.
-
-.exercise[
-
-- Connect to http://oneofournodes:3xxxx/
-
-
-
-]
-
---
-
-- We have failures in Deployments, Pods, and Replica Sets
-
---
## Recovering from a bad rollout
@@ -285,7 +245,7 @@ spec:
spec:
containers:
- name: worker
- image: $REGISTRY/worker:v0.1
+ image: dockercoins/worker:v0.1
strategy:
rollingUpdate:
maxUnavailable: 0
@@ -316,7 +276,7 @@ class: extra-details
spec:
containers:
- name: worker
- image: $REGISTRY/worker:v0.1
+ image: dockercoins/worker:v0.1
strategy:
rollingUpdate:
maxUnavailable: 0
diff --git a/slides/k8s/yamldeploy.md b/slides/k8s/yamldeploy.md
new file mode 100644
index 00000000..f9bf9250
--- /dev/null
+++ b/slides/k8s/yamldeploy.md
@@ -0,0 +1,93 @@
+# Deploying with YAML
+
+- So far, we created resources with the following commands:
+
+ - `kubectl run`
+
+ - `kubectl create deployment`
+
+ - `kubectl expose`
+
+- We can also create resources directly with YAML manifests
+
+---
+
+## `kubectl apply` vs `create`
+
+- `kubectl create -f whatever.yaml`
+
+ - creates resources if they don't exist
+
+ - if resources already exist, don't alter them
+
(and display error message)
+
+- `kubectl apply -f whatever.yaml`
+
+ - creates resources if they don't exist
+
+ - if resources already exist, update them
+
(to match the definition provided by the YAML file)
+
+ - stores the manifest as an *annotation* in the resource
+
+---
+
+## Creating multiple resources
+
+- The manifest can contain multiple resources separated by `---`
+
+```yaml
+ kind: ...
+ apiVersion: ...
+ metadata: ...
+ name: ...
+ ...
+ ---
+ kind: ...
+ apiVersion: ...
+ metadata: ...
+ name: ...
+ ...
+```
+
+---
+
+## Creating multiple resources
+
+- The manifest can also contain a list of resources
+
+```yaml
+ apiVersion: v1
+ kind: List
+ items:
+ - kind: ...
+ apiVersion: ...
+ ...
+ - kind: ...
+ apiVersion: ...
+ ...
+```
+
+---
+
+## Deploying dockercoins with YAML
+
+- We provide a YAML manifest with all the resources for Dockercoins
+
+ (Deployments and Services)
+
+- We can use it if we need to deploy or redeploy Dockercoins
+
+.exercise[
+
+- Deploy or redeploy Dockercoins:
+ ```bash
+ kubectl apply -f ~/container.training/k8s/dockercoins.yaml
+ ```
+
+]
+
+(If we deployed Dockercoins earlier, we will see warning messages,
+because the resources that we created lack the necessary annotation.
+We can safely ignore them.)
+
diff --git a/slides/kadm-fullday.yml b/slides/kadm-fullday.yml
index 18d0a334..9e63d8eb 100644
--- a/slides/kadm-fullday.yml
+++ b/slides/kadm-fullday.yml
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
- static-pods-exercise
diff --git a/slides/kadm-twodays.yml b/slides/kadm-twodays.yml
index 2c5d4c66..ac78d49f 100644
--- a/slides/kadm-twodays.yml
+++ b/slides/kadm-twodays.yml
@@ -11,6 +11,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
diff --git a/slides/kube-fullday.yml b/slides/kube-fullday.yml
index 35574248..47449152 100644
--- a/slides/kube-fullday.yml
+++ b/slides/kube-fullday.yml
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
@@ -23,7 +25,7 @@ chapters:
- shared/prereqs.md
#- shared/webssh.md
- shared/connecting.md
- - k8s/versions-k8s.md
+ #- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
#- shared/hastyconclusions.md
@@ -42,17 +44,18 @@ chapters:
#- k8s/buildshiprun-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
- #- k8s/kubectlproxy.md
- #- k8s/localkubeconfig.md
- #- k8s/accessinternal.md
-
- - k8s/setup-k8s.md
+ - k8s/yamldeploy.md
+ #- k8s/setup-k8s.md
- k8s/dashboard.md
#- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
#- k8s/dryrun.md
+ #- k8s/kubectlproxy.md
+ #- k8s/localkubeconfig.md
+ #- k8s/accessinternal.md
- k8s/rollout.md
#- k8s/healthchecks.md
#- k8s/healthchecks-more.md
diff --git a/slides/kube-halfday.yml b/slides/kube-halfday.yml
index 4a4856b2..1cbc5337 100644
--- a/slides/kube-halfday.yml
+++ b/slides/kube-halfday.yml
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml
index 9a21f941..3cf05c7f 100644
--- a/slides/kube-selfpaced.yml
+++ b/slides/kube-selfpaced.yml
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- in-person
@@ -43,9 +45,7 @@ chapters:
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
-
- - k8s/kubectlproxy.md
- - k8s/localkubeconfig.md
- - k8s/accessinternal.md
+ - k8s/yamldeploy.md
- k8s/setup-k8s.md
- k8s/dashboard.md
#- k8s/kubectlscale.md
@@ -53,6 +53,9 @@ chapters:
- shared/hastyconclusions.md
- k8s/daemonset.md
- k8s/dryrun.md
+ - k8s/kubectlproxy.md
+ - k8s/localkubeconfig.md
+ - k8s/accessinternal.md
-
- k8s/rollout.md
- k8s/healthchecks.md
diff --git a/slides/kube-twodays.yml b/slides/kube-twodays.yml
index eb976900..992c7d68 100644
--- a/slides/kube-twodays.yml
+++ b/slides/kube-twodays.yml
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
@@ -23,7 +25,7 @@ chapters:
- shared/prereqs.md
#- shared/webssh.md
- shared/connecting.md
- - k8s/versions-k8s.md
+ #- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
#- shared/hastyconclusions.md
@@ -43,20 +45,21 @@ chapters:
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
-
- - k8s/kubectlproxy.md
- - k8s/localkubeconfig.md
- - k8s/accessinternal.md
- - k8s/setup-k8s.md
+ - k8s/yamldeploy.md
+ #- k8s/setup-k8s.md
- k8s/dashboard.md
#- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
--
- k8s/dryrun.md
+-
+ #- k8s/kubectlproxy.md
+ - k8s/localkubeconfig.md
+ - k8s/accessinternal.md
- k8s/rollout.md
- k8s/healthchecks.md
- - k8s/healthchecks-more.md
+ #- k8s/healthchecks-more.md
- k8s/record.md
-
- k8s/namespaces.md
diff --git a/slides/markmaker.py b/slides/markmaker.py
index dd50291f..37cbf6bc 100755
--- a/slides/markmaker.py
+++ b/slides/markmaker.py
@@ -80,7 +80,7 @@ def flatten(titles):
def generatefromyaml(manifest, filename):
- manifest = yaml.load(manifest)
+ manifest = yaml.safe_load(manifest)
markdown, titles = processchapter(manifest["chapters"], filename)
logging.debug("Found {} titles.".format(len(titles)))
@@ -111,6 +111,7 @@ def generatefromyaml(manifest, filename):
html = html.replace("@@GITREPO@@", manifest["gitrepo"])
html = html.replace("@@SLIDES@@", manifest["slides"])
html = html.replace("@@TITLE@@", manifest["title"].replace("\n", " "))
+ html = html.replace("@@SLIDENUMBERPREFIX@@", manifest.get("slidenumberprefix", ""))
return html
diff --git a/slides/shared/connecting.md b/slides/shared/connecting.md
index 2e83d996..eb40ec6d 100644
--- a/slides/shared/connecting.md
+++ b/slides/shared/connecting.md
@@ -4,7 +4,12 @@ class: in-person
.exercise[
-- Log into the first VM (`node1`) with your SSH client
+- Log into the first VM (`node1`) with your SSH client:
+ ```bash
+ ssh `user`@`A.B.C.D`
+ ```
+
+ (Replace `user` and `A.B.C.D` with the user and IP address provided to you)
-- Check that you can SSH (without password) to `node2`:
- ```bash
- ssh node2
- ```
-- Type `exit` or `^D` to come back to `node1`
-
-
-
]
+You should see a prompt looking like this:
+```
+[A.B.C.D] (...) user@node1 ~
+$
+```
If anything goes wrong — ask for help!
---
@@ -52,6 +54,20 @@ If anything goes wrong — ask for help!
---
+## For a consistent Kubernetes experience ...
+
+- If you are using your own Kubernetes cluster, you can use [shpod](https://github.com/jpetazzo/shpod)
+
+- `shpod` provides a shell running in a pod on your own cluster
+
+- It comes with many tools pre-installed (helm, stern...)
+
+- These tools are used in many exercises in these slides
+
+- `shpod` also gives you completion and a fancy prompt
+
+---
+
class: self-paced
## Get your own Docker nodes
diff --git a/slides/swarm-fullday.yml b/slides/swarm-fullday.yml
index 8f30665d..279be3d1 100644
--- a/slides/swarm-fullday.yml
+++ b/slides/swarm-fullday.yml
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
- snap
diff --git a/slides/swarm-halfday.yml b/slides/swarm-halfday.yml
index a80a0733..30029a95 100644
--- a/slides/swarm-halfday.yml
+++ b/slides/swarm-halfday.yml
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- self-paced
- snap
diff --git a/slides/swarm-selfpaced.yml b/slides/swarm-selfpaced.yml
index 2510eed5..36e1d353 100644
--- a/slides/swarm-selfpaced.yml
+++ b/slides/swarm-selfpaced.yml
@@ -8,6 +8,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- in-person
- btp-auto
diff --git a/slides/swarm-video.yml b/slides/swarm-video.yml
index 0d361a40..02c1e6b2 100644
--- a/slides/swarm-video.yml
+++ b/slides/swarm-video.yml
@@ -8,6 +8,8 @@ gitrepo: github.com/jpetazzo/container.training
slides: http://container.training/
+#slidenumberprefix: "#SomeHashTag — "
+
exclude:
- in-person
- btp-auto
diff --git a/slides/workshop.html b/slides/workshop.html
index ffb378ce..96f56b04 100644
--- a/slides/workshop.html
+++ b/slides/workshop.html
@@ -28,6 +28,7 @@
var slideshow = remark.create({
ratio: '16:9',
highlightSpans: true,
+ slideNumberFormat: '@@SLIDENUMBERPREFIX@@%current%/%total%',
excludedClasses: [@@EXCLUDE@@]
});