diff --git a/k8s/canary.yaml b/k8s/canary.yaml
new file mode 100644
index 00000000..88045150
--- /dev/null
+++ b/k8s/canary.yaml
@@ -0,0 +1,21 @@
+apiVersion: networking.k8s.io/v1beta1
+kind: Ingress
+metadata:
+ name: whatever
+ annotations:
+ traefik.ingress.kubernetes.io/service-weights: |
+ whatever: 90%
+ whatever-new: 10%
+spec:
+ rules:
+ - host: whatever.A.B.C.D.nip.io
+ http:
+ paths:
+ - path: /
+ backend:
+ serviceName: whatever
+ servicePort: 80
+ - path: /
+ backend:
+ serviceName: whatever-new
+ servicePort: 80
diff --git a/slides/k8s/concepts-k8s.md b/slides/k8s/concepts-k8s.md
index 994f7ce8..2d07da54 100644
--- a/slides/k8s/concepts-k8s.md
+++ b/slides/k8s/concepts-k8s.md
@@ -199,6 +199,30 @@ class: extra-details
class: extra-details
+## How many nodes should a cluster have?
+
+- There is no particular constraint
+
+ (no need to have an odd number of nodes for quorum)
+
+- A cluster can have zero node
+
+ (but then it won't be able to start any pods)
+
+- For testing and development, having a single node is fine
+
+- For production, make sure that you have extra capacity
+
+ (so that your workload still fits if you lose a node or a group of nodes)
+
+- Kubernetes is tested with [up to 5000 nodes](https://kubernetes.io/docs/setup/best-practices/cluster-large/)
+
+ (however, running a cluster of that size requires a lot of tuning)
+
+---
+
+class: extra-details
+
## Do we need to run Docker at all?
No!
diff --git a/slides/k8s/ingress.md b/slides/k8s/ingress.md
index 7b12100a..a51c453b 100644
--- a/slides/k8s/ingress.md
+++ b/slides/k8s/ingress.md
@@ -524,3 +524,183 @@ spec:
- This should eventually stabilize
(remember that ingresses are currently `apiVersion: networking.k8s.io/v1beta1`)
+
+---
+
+## A special feature in action
+
+- We're going to see how to implement *canary releases* with Traefik
+
+- This feature is available on multiple ingress controllers
+
+- ... But it is configured very differently on each of them
+
+---
+
+## Canary releases
+
+- A *canary release* (or canary launch or canary deployment) is a release that will process only a small fraction of the workload
+
+- After deploying the canary, we compare its metrics to the normal release
+
+- If the metrics look good, the canary will progressively receive more traffic
+
+ (until it gets 100% and becomes the new normal release)
+
+- If the metrics aren't good, the canary is automatically removed
+
+- When we deploy a bad release, only a tiny fraction of traffic is affected
+
+---
+
+## Various ways to implement canary
+
+- Example 1: canary for a microservice
+
+ - 1% of all requests (sampled randomly) are sent to the canary
+ - the remaining 99% are sent to the normal release
+
+- Example 2: canary for a web app
+
+ - 1% of users are sent to the canary web site
+ - the remaining 99% are sent to the normal release
+
+- Example 3: canary for shipping physical goods
+
+ - 1% of orders are shipped with the canary process
+ - the reamining 99% are shipped with the normal process
+
+- We're going to implement example 1 (per-request routing)
+
+---
+
+## Canary releases with Traefik
+
+- We need to deploy the canary and expose it with a separate service
+
+- Then, in the Ingress resource, we need:
+
+ - multiple `paths` entries (one for each service, canary and normal)
+
+ - an extra annotation indicating the weight of each service
+
+- If we want, we can send requests to more than 2 services
+
+- Let's send requests to our 3 cheesy services!
+
+.exercise[
+
+- Create the resource shown on the next slide
+
+]
+
+---
+
+## The Ingress resource
+
+.small[
+```yaml
+apiVersion: networking.k8s.io/v1beta1
+kind: Ingress
+metadata:
+ name: cheeseplate
+ annotations:
+ traefik.ingress.kubernetes.io/service-weights: |
+ cheddar: 50%
+ wensleydale: 25%
+ stilton: 25%
+spec:
+ rules:
+ - host: cheeseplate.`A.B.C.D`.nip.io
+ http:
+ paths:
+ - path: /
+ backend:
+ serviceName: cheddar
+ servicePort: 80
+ - path: /
+ backend:
+ serviceName: wensledale
+ servicePort: 80
+ - path: /
+ backend:
+ serviceName: stilton
+ servicePort: 80
+```
+]
+
+---
+
+## Testing the canary
+
+- Let's check the percentage of requests going to each service
+
+.exercise[
+
+- Continuously send HTTP requests to the new ingress:
+ ```bash
+ while sleep 0.1; do
+ curl -s http://cheeseplate.A.B.C.D.nip.io/
+ done
+ ```
+
+]
+
+We should see a 50/25/25 request mix.
+
+---
+
+class: extra-details
+
+## Load balancing fairness
+
+Note: if we use odd request ratios, the load balancing algorithm might appear to be broken on a small scale (when sending a small number of requests), but on a large scale (with many requests) it will be fair.
+
+For instance, with a 11%/89% ratio, we can see 79 requests going to the 89%-weighted service, and then requests alternating between the two services; then 79 requests again, etc.
+
+---
+
+class: extra-details
+
+## Other ingress controllers
+
+*Just to illustrate how different things are ...*
+
+- With the NGINX ingress controller:
+
+ - define two ingress ressources
+
+ (specifying rules with the same host+path)
+
+ - add `nginx.ingress.kubernetes.io/canary` annotations on each
+
+
+- With Linkerd2:
+
+ - define two services
+
+ - define an extra service for the weighted aggregate of the two
+
+ - define a TrafficSplit (this is a CRD introduced by the SMI spec)
+
+---
+
+class: extra-details
+
+## We need more than that
+
+What we saw is just one of the multiple building blocks that we need to achieve a canary release.
+
+We also need:
+
+- metrics (latency, performance ...) for our releases
+
+- automation to alter canary weights
+
+ (increase canary weight if metrics look good; decrease otherwise)
+
+- a mechanism to manage the lifecycle of the canary releases
+
+ (create them, promote them, delete them ...)
+
+For inspiration, check [flagger by Weave](https://github.com/weaveworks/flagger).
diff --git a/slides/k8s/versions-k8s.md b/slides/k8s/versions-k8s.md
index 8f28c203..2daf88f8 100644
--- a/slides/k8s/versions-k8s.md
+++ b/slides/k8s/versions-k8s.md
@@ -1,7 +1,7 @@
## Versions installed
-- Kubernetes 1.15.3
-- Docker Engine 19.03.1
+- Kubernetes 1.17.1
+- Docker Engine 19.03.5
- Docker Compose 1.24.1
@@ -23,6 +23,10 @@ class: extra-details
## Kubernetes and Docker compatibility
+- Kubernetes 1.17 validates Docker Engine version [up to 19.03](https://github.com/kubernetes/kubernetes/pull/84476)
+
+ *however ...*
+
- Kubernetes 1.15 validates Docker Engine versions [up to 18.09](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.15.md#dependencies)
(the latest version when Kubernetes 1.14 was released)
@@ -40,5 +44,47 @@ class: extra-details
- "Validates" = continuous integration builds with very extensive (and expensive) testing
- The Docker API is versioned, and offers strong backward-compatibility
+
+ (if a client uses e.g. API v1.25, the Docker Engine will keep behaving the same way)
- (If a client uses e.g. API v1.25, the Docker Engine will keep behaving the same way)
+---
+
+## Kubernetes versioning and cadence
+
+- Kubernetes versions are expressed using *semantic versioning*
+
+ (a Kubernetes version is expressed as MAJOR.MINOR.PATCH)
+
+- There is a new *patch* release whenever needed
+
+ (generally, there is about [2 to 4 weeks](https://github.com/kubernetes/sig-release/blob/master/release-engineering/role-handbooks/patch-release-team.md#release-timing) between patch releases,
+ except when a critical bug or vulnerability is found:
+ in that case, a patch release will follow as fast as possible)
+
+- There is a new *minor* release approximately every 3 months
+
+- At any given time, 3 *minor* releases are maintained
+
+ (in other words, a given *minor* release is maintained about 9 months)
+
+---
+
+## Kubernetes version compatibility
+
+*Should my version of `kubectl` match exactly my cluster version?*
+
+- `kubectl` can be up to one minor version older or newer than the cluster
+
+ (if cluster version is 1.15.X, `kubectl` can be 1.14.Y, 1.15.Y, or 1.16.Y)
+
+- Things *might* work with larger version differences
+
+ (but they will probably fail randomly, so be careful)
+
+- This is an example of an error indicating version compability issues:
+ ```
+ error: SchemaError(io.k8s.api.autoscaling.v2beta1.ExternalMetricStatus):
+ invalid object doesn't have additional properties
+ ```
+
+- Check [the documentation](https://kubernetes.io/docs/setup/release/version-skew-policy/#kubectl) for the whole story about compatibility
diff --git a/slides/markmaker.py b/slides/markmaker.py
index c0eb15b8..aad08012 100755
--- a/slides/markmaker.py
+++ b/slides/markmaker.py
@@ -89,6 +89,15 @@ def flatten(titles):
def generatefromyaml(manifest, filename):
manifest = yaml.safe_load(manifest)
+ if "zip" not in manifest:
+ if manifest["slides"].endswith('/'):
+ manifest["zip"] = manifest["slides"] + "slides.zip"
+ else:
+ manifest["zip"] = manifest["slides"] + "/slides.zip"
+
+ if "html" not in manifest:
+ manifest["html"] = filename + ".html"
+
markdown, titles = processchapter(manifest["chapters"], filename)
logging.debug("Found {} titles.".format(len(titles)))
toc = gentoc(titles)
@@ -117,6 +126,8 @@ def generatefromyaml(manifest, filename):
html = html.replace("@@CHAT@@", manifest["chat"])
html = html.replace("@@GITREPO@@", manifest["gitrepo"])
html = html.replace("@@SLIDES@@", manifest["slides"])
+ html = html.replace("@@ZIP@@", manifest["zip"])
+ html = html.replace("@@HTML@@", manifest["html"])
html = html.replace("@@TITLE@@", manifest["title"].replace("\n", " "))
html = html.replace("@@SLIDENUMBERPREFIX@@", manifest.get("slidenumberprefix", ""))
return html
diff --git a/slides/shared/prereqs.md b/slides/shared/prereqs.md
index 0c66c22c..533a80d4 100644
--- a/slides/shared/prereqs.md
+++ b/slides/shared/prereqs.md
@@ -72,6 +72,12 @@ Misattributed to Benjamin Franklin
- Slides will remain online so you can review them later if needed
+- You can download the slides using that URL:
+
+ @@ZIP@@
+
+ (then open the file `@@HTML@@`)
+
---
class: in-person