mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-07-22 14:26:27 +00:00
Merge branch 'master' into helm-3
This commit is contained in:
21
k8s/canary.yaml
Normal file
21
k8s/canary.yaml
Normal file
@@ -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
|
||||
@@ -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!
|
||||
|
||||
@@ -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
|
||||
<br/>
|
||||
(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).
|
||||
|
||||
@@ -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
|
||||
|
||||
<!-- ##VERSION## -->
|
||||
@@ -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)
|
||||
<br/>
|
||||
(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
|
||||
<br/>
|
||||
(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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user