Compare commits

..

22 Commits

Author SHA1 Message Date
Jerome Petazzoni
b00f91c2ec fix-redirects.sh: adding forced redirect 2020-04-07 16:58:08 -05:00
Jerome Petazzoni
e0ccfb1a71 Merge remote-tracking branch 'origin/master' into wwrk-2019-06 2019-06-03 22:39:17 -05:00
Jerome Petazzoni
6e6f829973 Update Slack link 2019-06-02 23:10:48 -05:00
Jerome Petazzoni
ea7e213911 Merge branch 'master' into wwrk-2019-06 2019-06-02 19:39:23 -05:00
Jerome Petazzoni
950f6050a4 Merge branch 'master' into wwrk-2019-06 2019-06-02 10:23:41 -05:00
Jerome Petazzoni
4e97aec074 Merge branch 'wwrk-2019-06' of github.com:jpetazzo/container.training into wwrk-2019-06 2019-06-02 10:23:27 -05:00
Jerome Petazzoni
c8b4358a2b Tweaks 2019-06-02 10:23:11 -05:00
Jerome Petazzoni
787c983889 Merge branch 'wwrk-2019-06' of github.com:jpetazzo/container.training into wwrk-2019-06 2019-06-02 09:56:24 -05:00
Jerome Petazzoni
20c4f65d09 Merge branch 'control-plane-auth' into wwrk-2019-06 2019-06-02 09:56:12 -05:00
Jerome Petazzoni
42ed6fc56a Tweaks 2019-06-02 09:55:50 -05:00
Jerome Petazzoni
3e8a299f2e Merge branch 'master' into wwrk-2019-06 2019-06-01 20:03:15 -05:00
Jerome Petazzoni
3b4cf44634 Merge branch 'healthchecks-advanced' into wwrk-2019-06 2019-06-01 19:31:36 -05:00
Jerome Petazzoni
a9be1d867d Merge branch 'master' into wwrk-2019-06 2019-06-01 18:18:18 -05:00
Jerome Petazzoni
2d7a5a9a39 merge oidc 2019-06-01 18:06:32 -05:00
Jerome Petazzoni
59c2ff1911 Add chapter about OpenID Connect tokens
Includes a simplified demo using Google OAuth Playground,
as well as numerous examples aiming at piercing the veil
to explain JWT, JWS, and associated protocols and algos.
2019-06-01 17:58:15 -05:00
Jerome Petazzoni
470b1bb1ed Add slides URL; temporary Slack URL; rename YAML file 2019-06-01 11:30:42 -05:00
Jerome Petazzoni
adba4cae9e Adjust transition to advanced content 2019-05-31 21:34:02 -05:00
Jerome Petazzoni
28f6ff1412 merge operators 2019-05-31 21:27:28 -05:00
Jerome Petazzoni
7128cb976b merge advanced healthchecks 2019-05-31 21:26:09 -05:00
Jerome Petazzoni
05c6ee5a13 merge control plane auth 2019-05-31 21:24:25 -05:00
Jerome Petazzoni
4c1b32f32b Assemble 4-day course 2019-05-31 21:23:57 -05:00
Jerome Petazzoni
f4ef2bd6d4 Add control plane auth info 2019-05-27 15:39:12 -05:00
60 changed files with 1720 additions and 884 deletions

View File

@@ -31,7 +31,6 @@ infra_start() {
die "I could not find which AMI to use in this region. Try another region?"
fi
AWS_KEY_NAME=$(make_key_name)
AWS_INSTANCE_TYPE=${AWS_INSTANCE_TYPE-t3a.medium}
sep "Starting instances"
info " Count: $COUNT"
@@ -39,11 +38,10 @@ infra_start() {
info " Token/tag: $TAG"
info " AMI: $AMI"
info " Key name: $AWS_KEY_NAME"
info " Instance type: $AWS_INSTANCE_TYPE"
result=$(aws ec2 run-instances \
--key-name $AWS_KEY_NAME \
--count $COUNT \
--instance-type $AWS_INSTANCE_TYPE \
--instance-type ${AWS_INSTANCE_TYPE-t2.medium} \
--client-token $TAG \
--block-device-mapping 'DeviceName=/dev/sda1,Ebs={VolumeSize=20}' \
--image-id $AMI)
@@ -99,7 +97,7 @@ infra_disableaddrchecks() {
}
wait_until_tag_is_running() {
max_retry=100
max_retry=50
i=0
done_count=0
while [[ $done_count -lt $COUNT ]]; do

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
import os
import sys
import yaml

View File

@@ -5,7 +5,7 @@ clustersize: 1
clusterprefix: dmuc
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
cards_template: admin.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: A4

View File

@@ -5,7 +5,7 @@ clustersize: 3
clusterprefix: kubenet
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
cards_template: admin.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: A4

View File

@@ -5,7 +5,7 @@ clustersize: 3
clusterprefix: kuberouter
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
cards_template: admin.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: A4

View File

@@ -5,7 +5,7 @@ clustersize: 3
clusterprefix: test
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
cards_template: admin.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: A4

View File

@@ -0,0 +1,29 @@
# Number of VMs per cluster
clustersize: 1
# The hostname of each node will be clusterprefix + a number
clusterprefix: node
# Jinja2 template to use to generate ready-to-cut cards
cards_template: enix.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: A4
# Feel free to reduce this if your printer can handle it
paper_margin: 0.2in
# Note: paper_size and paper_margin only apply to PDF generated with pdfkit.
# If you print (or generate a PDF) using ips.html, they will be ignored.
# (The equivalent parameters must be set from the browser's print dialog.)
# This can be "test" or "stable"
engine_version: stable
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.21.1
machine_version: 0.14.0
# Password used to connect with the "docker user"
docker_user_password: training

View File

@@ -5,7 +5,7 @@ clustersize: 4
clusterprefix: node
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
cards_template: jerome.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: Letter

View File

@@ -7,7 +7,7 @@ clustersize: 3
clusterprefix: node
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
cards_template: kube101.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: Letter

View File

@@ -1,20 +1,15 @@
#!/bin/sh
set -e
export AWS_INSTANCE_TYPE=t3a.small
INFRA=infra/aws-us-west-2
INFRA=infra/aws-eu-west-3
STUDENTS=2
PREFIX=$(date +%Y-%m-%d-%H-%M)
SETTINGS=admin-dmuc
TAG=$PREFIX-$SETTINGS
TAG=admin-dmuc
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--settings settings/$TAG.yaml \
--count $STUDENTS
./workshopctl deploy $TAG
@@ -22,45 +17,37 @@ TAG=$PREFIX-$SETTINGS
./workshopctl kubebins $TAG
./workshopctl cards $TAG
SETTINGS=admin-kubenet
TAG=$PREFIX-$SETTINGS
TAG=admin-kubenet
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--settings settings/$TAG.yaml \
--count $((3*$STUDENTS))
./workshopctl disableaddrchecks $TAG
./workshopctl deploy $TAG
./workshopctl kubebins $TAG
./workshopctl disableaddrchecks $TAG
./workshopctl cards $TAG
SETTINGS=admin-kuberouter
TAG=$PREFIX-$SETTINGS
TAG=admin-kuberouter
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--settings settings/$TAG.yaml \
--count $((3*$STUDENTS))
./workshopctl disableaddrchecks $TAG
./workshopctl deploy $TAG
./workshopctl kubebins $TAG
./workshopctl disableaddrchecks $TAG
./workshopctl cards $TAG
#INFRA=infra/aws-us-west-1
export AWS_INSTANCE_TYPE=t3a.medium
SETTINGS=admin-test
TAG=$PREFIX-$SETTINGS
TAG=admin-test
./workshopctl start \
--tag $TAG \
--infra $INFRA \
--settings settings/$SETTINGS.yaml \
--settings settings/$TAG.yaml \
--count $((3*$STUDENTS))
./workshopctl deploy $TAG
./workshopctl kube $TAG 1.13.5
./workshopctl cards $TAG

View File

@@ -0,0 +1,124 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://FIXME.container.training" -%}
{%- set pagesize = 9 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine virtuelle" -%}
{%- set this_or_each = "cette" -%}
{%- set plural = "" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "Kubernetes workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "chaque" -%}
{%- set plural = "s" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_kube -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
@import url('https://fonts.googleapis.com/css?family=Slabo+27px');
body, table {
margin: 0;
padding: 0;
line-height: 1em;
font-size: 15px;
font-family: 'Slabo 27px';
}
table {
border-spacing: 0;
margin-top: 0.4em;
margin-bottom: 0.4em;
border-left: 0.8em double grey;
padding-left: 0.4em;
}
div {
float: left;
border: 1px dotted black;
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
width: 30%;
padding-left: 1.5%;
padding-right: 1.5%;
}
p {
margin: 0.4em 0 0.4em 0;
}
img {
height: 4em;
float: right;
margin-right: -0.3em;
}
img.enix {
height: 4.0em;
margin-top: 0.4em;
}
img.kube {
height: 4.2em;
margin-top: 1.7em;
}
.logpass {
font-family: monospace;
font-weight: bold;
}
.pagebreak {
page-break-after: always;
clear: both;
display: block;
height: 8px;
}
</style></head>
<body>
{% for cluster in clusters %}
{% if loop.index0>0 and loop.index0%pagesize==0 %}
<span class="pagebreak"></span>
{% endif %}
<div>
<p>
Voici les informations permettant de se connecter à un
des environnements utilisés pour cette formation.
Vous pouvez vous connecter à {{ this_or_each }} machine
virtuelle avec n'importe quel client SSH.
</p>
<p>
<img class="enix" src="https://enix.io/static/img/logos/logo-domain-cropped.png" />
<table>
<tr><td>cluster:</td></tr>
<tr><td class="logpass">{{ clusterprefix }}</td></tr>
<tr><td>identifiant:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>mot de passe:</td></tr>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
Adresse{{ plural }} IP :
<!--<img class="kube" src="{{ image_src }}" />-->
<table>
{% for node in cluster %}
<tr><td>{{ clusterprefix }}{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>Le support de formation est à l'adresse suivante :
<center>{{ url }}</center>
</p>
</div>
{% endfor %}
</body>
</html>

View File

@@ -1,88 +1,29 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://FIXME.container.training/" -%}
{%- set pagesize = 9 -%}
{%- set lang = "en" -%}
{%- set event = "training session" -%}
{%- set backside = False -%}
{%- set image = "kube" -%}
{%- set clusternumber = 100 -%}
{%- set image_src = {
"docker": "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png",
"swarm": "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png",
"kube": "https://avatars1.githubusercontent.com/u/13629408",
"enix": "https://enix.io/static/img/logos/logo-domain-cropped.png",
}[image] -%}
{%- if lang == "en" and clustersize == 1 -%}
{%- set intro -%}
Here is the connection information to your very own
machine for this {{ event }}.
You can connect to this VM with any SSH client.
{%- endset -%}
{%- set listhead -%}
Your machine is:
{%- endset -%}
{%- endif -%}
{%- if lang == "en" and clustersize != 1 -%}
{%- set intro -%}
Here is the connection information to your very own
cluster for this {{ event }}.
You can connect to each VM with any SSH client.
{%- endset -%}
{%- set listhead -%}
Your machines are:
{%- endset -%}
{%- endif -%}
{%- if lang == "fr" and clustersize == 1 -%}
{%- set intro -%}
Voici les informations permettant de se connecter à votre
machine pour cette formation.
Vous pouvez vous connecter à cette machine virtuelle
avec n'importe quel client SSH.
{%- endset -%}
{%- set listhead -%}
Adresse IP:
{%- endset -%}
{%- endif -%}
{%- if lang == "en" and clusterprefix != "node" -%}
{%- set intro -%}
Here is the connection information for the
<strong>{{ clusterprefix }}</strong> environment.
{%- endset -%}
{%- endif -%}
{%- if lang == "fr" and clustersize != 1 -%}
{%- set intro -%}
Voici les informations permettant de se connecter à votre
cluster pour cette formation.
Vous pouvez vous connecter à chaque machine virtuelle
avec n'importe quel client SSH.
{%- endset -%}
{%- set listhead -%}
Adresses IP:
{%- endset -%}
{%- endif -%}
{%- if lang == "en" -%}
{%- set slides_are_at -%}
You can find the slides at:
{%- endset -%}
{%- endif -%}
{%- if lang == "fr" -%}
{%- set slides_are_at -%}
Le support de formation est à l'adresse suivante :
{%- endset -%}
{%- set url = "http://container.training/" -%}
{%- set pagesize = 12 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine" -%}
{%- set this_or_each = "this" -%}
{%- set machine_is_or_machines_are = "machine is" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "orchestration workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "each" -%}
{%- set machine_is_or_machines_are = "machines are" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_swarm -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
@import url('https://fonts.googleapis.com/css?family=Slabo+27px');
body, table {
margin: 0;
padding: 0;
line-height: 1em;
font-size: 15px;
font-family: 'Slabo 27px';
font-size: 14px;
}
table {
@@ -96,54 +37,24 @@ table {
div {
float: left;
border: 1px dotted black;
{% if backside %}
height: 31%;
{% endif %}
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
/*
width: 21.5%;
padding-left: 1.5%;
padding-right: 1.5%;
*/
/**/
width: 30%;
padding-left: 1.5%;
padding-right: 1.5%;
/**/
}
p {
margin: 0.4em 0 0.4em 0;
}
div.back {
border: 1px dotted white;
}
div.back p {
margin: 0.5em 1em 0 1em;
}
img {
height: 4em;
float: right;
margin-right: -0.2em;
margin-right: -0.4em;
}
/*
img.enix {
height: 4.0em;
margin-top: 0.4em;
}
img.kube {
height: 4.2em;
margin-top: 1.7em;
}
*/
.logpass {
font-family: monospace;
font-weight: bold;
@@ -158,15 +69,19 @@ img.kube {
</style></head>
<body>
{% for cluster in clusters %}
{% if loop.index0>0 and loop.index0%pagesize==0 %}
<span class="pagebreak"></span>
{% endif %}
<div>
<p>{{ intro }}</p>
<p>
Here is the connection information to your very own
{{ cluster_or_machine }} for this {{ workshop_name }}.
You can connect to {{ this_or_each }} VM with any SSH client.
</p>
<p>
<img src="{{ image_src }}" />
<table>
{% if clusternumber != None %}
<tr><td>cluster:</td></tr>
<tr><td class="logpass">{{ clusternumber + loop.index }}</td></tr>
{% endif %}
<tr><td>login:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>password:</td></tr>
@@ -175,44 +90,17 @@ img.kube {
</p>
<p>
{{ listhead }}
Your {{ machine_is_or_machines_are }}:
<table>
{% for node in cluster %}
<tr>
<td>{{ clusterprefix }}{{ loop.index }}:</td>
<td>{{ node }}</td>
</tr>
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>
{{ slides_are_at }}
<p>You can find the slides at:
<center>{{ url }}</center>
</p>
</div>
{% if loop.index%pagesize==0 or loop.last %}
<span class="pagebreak"></span>
{% if backside %}
{% for x in range(pagesize) %}
<div class="back">
<br/>
<p>You got this at the workshop
"Getting Started With Kubernetes and Container Orchestration"
during QCON London (March 2019).</p>
<p>If you liked that workshop,
I can train your team or organization
on Docker, container, and Kubernetes,
with curriculums of 1 to 5 days.
</p>
<p>Interested? Contact me at:</p>
<p>jerome.petazzoni@gmail.com</p>
<p>Thank you!</p>
</div>
{% endfor %}
<span class="pagebreak"></span>
{% endif %}
{% endif %}
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,121 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://FIXME.container.training" -%}
{%- set pagesize = 9 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine virtuelle" -%}
{%- set this_or_each = "cette" -%}
{%- set plural = "" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "Kubernetes workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "chaque" -%}
{%- set plural = "s" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_kube -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
@import url('https://fonts.googleapis.com/css?family=Slabo+27px');
body, table {
margin: 0;
padding: 0;
line-height: 1em;
font-size: 15px;
font-family: 'Slabo 27px';
}
table {
border-spacing: 0;
margin-top: 0.4em;
margin-bottom: 0.4em;
border-left: 0.8em double grey;
padding-left: 0.4em;
}
div {
float: left;
border: 1px dotted black;
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
width: 30%;
padding-left: 1.5%;
padding-right: 1.5%;
}
p {
margin: 0.4em 0 0.4em 0;
}
img {
height: 4em;
float: right;
margin-right: -0.3em;
}
img.enix {
height: 4.0em;
margin-top: 0.4em;
}
img.kube {
height: 4.2em;
margin-top: 1.7em;
}
.logpass {
font-family: monospace;
font-weight: bold;
}
.pagebreak {
page-break-after: always;
clear: both;
display: block;
height: 8px;
}
</style></head>
<body>
{% for cluster in clusters %}
{% if loop.index0>0 and loop.index0%pagesize==0 %}
<span class="pagebreak"></span>
{% endif %}
<div>
<p>
Voici les informations permettant de se connecter à votre
{{ cluster_or_machine }} pour cette formation.
Vous pouvez vous connecter à {{ this_or_each }} machine virtuelle
avec n'importe quel client SSH.
</p>
<p>
<img class="enix" src="https://enix.io/static/img/logos/logo-domain-cropped.png" />
<table>
<tr><td>identifiant:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>mot de passe:</td></tr>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
Adresse{{ plural }} IP :
<!--<img class="kube" src="{{ image_src }}" />-->
<table>
{% for node in cluster %}
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>Le support de formation est à l'adresse suivante :
<center>{{ url }}</center>
</p>
</div>
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,134 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://qconuk2019.container.training/" -%}
{%- set pagesize = 9 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine" -%}
{%- set this_or_each = "this" -%}
{%- set machine_is_or_machines_are = "machine is" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "Kubernetes workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "each" -%}
{%- set machine_is_or_machines_are = "machines are" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_kube -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
@import url('https://fonts.googleapis.com/css?family=Slabo+27px');
body, table {
margin: 0;
padding: 0;
line-height: 1.0em;
font-size: 15px;
font-family: 'Slabo 27px';
}
table {
border-spacing: 0;
margin-top: 0.4em;
margin-bottom: 0.4em;
border-left: 0.8em double grey;
padding-left: 0.4em;
}
div {
float: left;
border: 1px dotted black;
height: 31%;
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
width: 30%;
padding-left: 1.5%;
padding-right: 1.5%;
}
div.back {
border: 1px dotted white;
}
div.back p {
margin: 0.5em 1em 0 1em;
}
p {
margin: 0.4em 0 0.8em 0;
}
img {
height: 5em;
float: right;
margin-right: 1em;
}
.logpass {
font-family: monospace;
font-weight: bold;
}
.pagebreak {
page-break-after: always;
clear: both;
display: block;
height: 8px;
}
</style></head>
<body>
{% for cluster in clusters %}
<div>
<p>
Here is the connection information to your very own
{{ cluster_or_machine }} for this {{ workshop_name }}.
You can connect to {{ this_or_each }} VM with any SSH client.
</p>
<p>
<img src="{{ image_src }}" />
<table>
<tr><td>login:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>password:</td></tr>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
Your {{ machine_is_or_machines_are }}:
<table>
{% for node in cluster %}
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>You can find the slides at:
<center>{{ url }}</center>
</p>
</div>
{% if loop.index%pagesize==0 or loop.last %}
<span class="pagebreak"></span>
{% for x in range(pagesize) %}
<div class="back">
<br/>
<p>You got this at the workshop
"Getting Started With Kubernetes and Container Orchestration"
during QCON London (March 2019).</p>
<p>If you liked that workshop,
I can train your team or organization
on Docker, container, and Kubernetes,
with curriculums of 1 to 5 days.
</p>
<p>Interested? Contact me at:</p>
<p>jerome.petazzoni@gmail.com</p>
<p>Thank you!</p>
</div>
{% endfor %}
<span class="pagebreak"></span>
{% endif %}
{% endfor %}
</body>
</html>

View File

@@ -0,0 +1,106 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://container.training/" -%}
{%- set pagesize = 12 -%}
{%- if clustersize == 1 -%}
{%- set workshop_name = "Docker workshop" -%}
{%- set cluster_or_machine = "machine" -%}
{%- set this_or_each = "this" -%}
{%- set machine_is_or_machines_are = "machine is" -%}
{%- set image_src = "https://s3-us-west-2.amazonaws.com/www.breadware.com/integrations/docker.png" -%}
{%- else -%}
{%- set workshop_name = "Kubernetes workshop" -%}
{%- set cluster_or_machine = "cluster" -%}
{%- set this_or_each = "each" -%}
{%- set machine_is_or_machines_are = "machines are" -%}
{%- set image_src_swarm = "https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png" -%}
{%- set image_src_kube = "https://avatars1.githubusercontent.com/u/13629408" -%}
{%- set image_src = image_src_kube -%}
{%- endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><style>
body, table {
margin: 0;
padding: 0;
line-height: 1em;
font-size: 14px;
}
table {
border-spacing: 0;
margin-top: 0.4em;
margin-bottom: 0.4em;
border-left: 0.8em double grey;
padding-left: 0.4em;
}
div {
float: left;
border: 1px dotted black;
padding-top: 1%;
padding-bottom: 1%;
/* columns * (width+left+right) < 100% */
width: 21.5%;
padding-left: 1.5%;
padding-right: 1.5%;
}
p {
margin: 0.4em 0 0.4em 0;
}
img {
height: 4em;
float: right;
margin-right: -0.4em;
}
.logpass {
font-family: monospace;
font-weight: bold;
}
.pagebreak {
page-break-after: always;
clear: both;
display: block;
height: 8px;
}
</style></head>
<body>
{% for cluster in clusters %}
{% if loop.index0>0 and loop.index0%pagesize==0 %}
<span class="pagebreak"></span>
{% endif %}
<div>
<p>
Here is the connection information to your very own
{{ cluster_or_machine }} for this {{ workshop_name }}.
You can connect to {{ this_or_each }} VM with any SSH client.
</p>
<p>
<img src="{{ image_src }}" />
<table>
<tr><td>login:</td></tr>
<tr><td class="logpass">docker</td></tr>
<tr><td>password:</td></tr>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
Your {{ machine_is_or_machines_are }}:
<table>
{% for node in cluster %}
<tr><td>node{{ loop.index }}:</td><td>{{ node }}</td></tr>
{% endfor %}
</table>
</p>
<p>You can find the slides at:
<center>{{ url }}</center>
</p>
</div>
{% endfor %}
</body>
</html>

View File

@@ -2,4 +2,4 @@
#/ /kube-halfday.yml.html 200
#/ /kube-fullday.yml.html 200
#/ /kube-twodays.yml.html 200
/ /alfun.html 200!
/ /kadm-fourdays.yml.html 200!

View File

@@ -1,62 +0,0 @@
title: |
Containers,
Docker,
Kubernetes
(Partie 1)
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
gitrepo: github.com/jpetazzo/container.training
slides: http://alfun-2019-06.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- logistics.md
- containers/intro.md
- shared/about-slides.md
- shared/toc.md
# DAY 1
- - containers/Docker_Overview.md
- containers/Training_Environment.md
- containers/Installing_Docker.md
- containers/First_Containers.md
- containers/Background_Containers.md
- - containers/Start_And_Attach.md
- containers/Initial_Images.md
- containers/Building_Images_Interactively.md
- containers/Building_Images_With_Dockerfiles.md
- containers/Cmd_And_Entrypoint.md
- - containers/Copying_Files_During_Build.md
- containers/Exercise_Dockerfile_Basic.md
- containers/Publishing_To_Docker_Hub.md
- containers/Multi_Stage_Builds.md
- containers/Dockerfile_Tips.md
- containers/Exercise_Dockerfile_Advanced.md
- - containers/Naming_And_Inspecting.md
- containers/Labels.md
- containers/Getting_Inside.md
- containers/Resource_Limits.md
# DAY 2
- - containers/Container_Networking_Basics.md
- containers/Network_Drivers.md
- containers/Container_Network_Model.md
- containers/Ambassadors.md
- - containers/Local_Development_Workflow.md
- containers/Working_With_Volumes.md
- containers/Compose_For_Dev_Stacks.md
- containers/Exercise_Composefile.md
- - containers/Advanced_Dockerfiles.md
- containers/Application_Configuration.md
- containers/Logging.md
- containers/Container_Engines.md
- containers/Windows_Containers.md
- - containers/Orchestration_Overview.md
- k8s/concepts-k8s.md
- shared/declarative.md
- k8s/declarative.md
- k8s/kubenet.md

View File

@@ -1,73 +0,0 @@
title: |
Containers,
Docker,
Kubernetes
(Partie 2)
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
gitrepo: github.com/jpetazzo/container.training
slides: http://alfun-2019-06.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- shared/toc.md
# DAY 3
- - shared/prereqs.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
- shared/composedown.md
- k8s/kubectlget.md
- k8s/kubectlrun.md
- k8s/deploymentslideshow.md
- - k8s/kubectlexpose.md
- k8s/shippingimages.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
- - k8s/namespaces.md
- |
# Exercise — from Compose to Kubernetes
Let's run the wordsmith app on Kubernetes!
The code is at: https://github.com/jpetazzo/wordsmith
- k8s/kustomize.md
- k8s/helm.md
#- k8s/create-chart.md
- k8s/rollout.md
- - k8s/healthchecks.md
#- k8s/healthchecks-more.md
- k8s/kubectlproxy.md
- k8s/localkubeconfig.md
- k8s/accessinternal.md
- k8s/dashboard.md
- k8s/setup-k8s.md
# DAY 4
- - k8s/volumes.md
- k8s/configuration.md
- k8s/logs-cli.md
- k8s/logs-centralized.md
- k8s/prometheus.md
- - k8s/authn-authz.md
- k8s/netpol.md
- k8s/podsecuritypolicy.md
- - k8s/ingress.md
- k8s/statefulsets.md
- k8s/local-persistent-volumes.md
#- k8s/extending-api.md
- - k8s/resource-limits.md
- k8s/metrics-server.md
- k8s/cluster-sizing.md
- k8s/horizontal-pod-autoscaler.md
- - k8s/whatsnext.md
- k8s/links.md
- shared/thankyou.md

View File

@@ -1,22 +0,0 @@
title: |
Containers,
Docker,
Kubernetes
(Extras)
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
gitrepo: github.com/jpetazzo/container.training
slides: http://alfun-2019-06.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- shared/toc.md
- - containers/Namespaces_Cgroups.md
- containers/Copy_On_Write.md

View File

@@ -1,5 +0,0 @@
<p><a href="alfun-1.yml.html">Lundi / Mardi</a></p>
<p><a href="alfun-2.yml.html">Jeudi / Vendredi</a></p>
<p><a href="alfun-3.yml.html">Extra slides (container internals)</a></p>

View File

@@ -86,7 +86,7 @@ like Windows, macOS, Solaris, FreeBSD ...
* No notion of image (container filesystems have to be managed manually).
* Networking has to be set up manually.
* Networking has to be setup manually.
---
@@ -112,7 +112,7 @@ like Windows, macOS, Solaris, FreeBSD ...
* Strong emphasis on security (through privilege separation).
* Networking has to be set up separately (e.g. through CNI plugins).
* Networking has to be setup separately (e.g. through CNI plugins).
* Partial image management (pull, but no push).
@@ -152,7 +152,7 @@ We're not aware of anyone using it directly (i.e. outside of Kubernetes).
* Basic image support (tar archives and raw disk images).
* Network has to be set up manually.
* Network has to be setup manually.
---

View File

@@ -1,11 +1,3 @@
- date: [2019-11-04, 2019-11-05]
country: de
city: Berlin
event: Velocity
speaker: jpetazzo
title: Deploying and scaling applications with Kubernetes
attend: https://conferences.oreilly.com/velocity/vl-eu/public/schedule/detail/79109
- date: 2019-11-13
country: fr
city: Marseille
@@ -39,7 +31,6 @@
title: Kubernetes for administrators and operators
speaker: jpetazzo
attend: https://conferences.oreilly.com/velocity/vl-ca/public/schedule/detail/75313
slides: https://kadm-2019-06.container.training/
- date: 2019-05-01
country: us

View File

@@ -1,4 +1,4 @@
# Kubernetes architecture
# Kubernetes architecture deep dive
We can arbitrarily split Kubernetes in two parts:

View File

@@ -22,7 +22,7 @@
- When the API server receives a request, it tries to authenticate it
(it examines headers, certificates... anything available)
(it examines headers, certificates ... anything available)
- Many authentication methods are available and can be used simultaneously
@@ -34,7 +34,7 @@
- the user ID
- a list of groups
- The API server doesn't interpret these; that'll be the job of *authorizers*
- The API server doesn't interpret these; it'll be the job of *authorizers*
---
@@ -50,7 +50,7 @@
- [HTTP basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication)
(carrying user and password in an HTTP header)
(carrying user and password in a HTTP header)
- Authentication proxy
@@ -88,7 +88,7 @@
(i.e. they are not stored in etcd or anywhere else)
- Users can be created (and added to groups) independently of the API
- Users can be created (and given membership to groups) independently of the API
- The Kubernetes API can be set up to use your custom CA to validate client certs
@@ -193,7 +193,7 @@ class: extra-details
(the kind that you can view with `kubectl get secrets`)
- Service accounts are generally used to grant permissions to applications, services...
- Service accounts are generally used to grant permissions to applications, services ...
(as opposed to humans)
@@ -217,7 +217,7 @@ class: extra-details
.exercise[
- The resource name is `serviceaccount` or `sa` for short:
- The resource name is `serviceaccount` or `sa` in short:
```bash
kubectl get sa
```
@@ -309,7 +309,7 @@ class: extra-details
- The API "sees" us as a different user
- But neither user has any rights, so we can't do nothin'
- But neither user has any right, so we can't do nothin'
- Let's change that!
@@ -339,9 +339,9 @@ class: extra-details
- A rule is a combination of:
- [verbs](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb) like create, get, list, update, delete...
- [verbs](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb) like create, get, list, update, delete ...
- resources (as in "API resource," like pods, nodes, services...)
- resources (as in "API resource", like pods, nodes, services ...)
- resource names (to specify e.g. one specific pod instead of all pods)
@@ -375,13 +375,13 @@ class: extra-details
- We can also define API resources ClusterRole and ClusterRoleBinding
- These are a superset, allowing us to:
- These are a superset, allowing to:
- specify actions on cluster-wide objects (like nodes)
- operate across all namespaces
- We can create Role and RoleBinding resources within a namespace
- We can create Role and RoleBinding resources within a namespaces
- ClusterRole and ClusterRoleBinding resources are global
@@ -389,13 +389,13 @@ class: extra-details
## Pods and service accounts
- A pod can be associated with a service account
- A pod can be associated to a service account
- by default, it is associated with the `default` service account
- by default, it is associated to the `default` service account
- as we saw earlier, this service account has no permissions anyway
- as we've seen earlier, this service account has no permission anyway
- The associated token is exposed to the pod's filesystem
- The associated token is exposed into the pod's filesystem
(in `/var/run/secrets/kubernetes.io/serviceaccount/token`)
@@ -460,7 +460,7 @@ class: extra-details
]
It's important to note a couple of details in these flags...
It's important to note a couple of details in these flags ...
---
@@ -493,13 +493,13 @@ It's important to note a couple of details in these flags...
- again, the command would have worked fine (no error)
- ...but our API requests would have been denied later
- ... but our API requests would have been denied later
- What's about the `default:` prefix?
- that's the namespace of the service account
- yes, it could be inferred from context, but... `kubectl` requires it
- yes, it could be inferred from context, but ... `kubectl` requires it
---
@@ -590,7 +590,7 @@ class: extra-details
*In many situations, these roles will be all you need.*
*You can also customize them!*
*You can also customize them if needed!*
---
@@ -652,7 +652,7 @@ class: extra-details
kubectl describe clusterrolebinding cluster-admin
```
- This binding associates `system:masters` with the cluster role `cluster-admin`
- This binding associates `system:masters` to the cluster role `cluster-admin`
- And the `cluster-admin` is, basically, `root`:
```bash
@@ -667,7 +667,7 @@ 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:
- Here is a proof-of-concept tool by Aqua Security, doing exactly that:
https://github.com/aquasecurity/kubectl-who-can

View File

@@ -1,4 +1,4 @@
# TLS bootstrap
# TLS bootstrap (extra material)
- kubelet needs TLS keys and certificates to communicate with the control plane

View File

@@ -20,15 +20,15 @@
- Configuring routing tables in the cloud network (specific to GCE)
- Updating node labels to indicate region, zone, instance type...
- 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...)
- Managing *some* volumes (e.g. ELBs, AzureDisks ...)
(Eventually, volumes will be managed by the Container Storage Interface)
(Eventually, volumes will be managed by the CSI)
---
@@ -81,16 +81,6 @@ The list includes the following providers:
---
## 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 details on clouds that you don't / won't use?
---
## Cloud Controller Manager in practice
- Write a configuration file
@@ -105,7 +95,7 @@ The list includes the following providers:
- When using managed clusters, this is done automatically
- There is very little documentation on writing the configuration file
- There is very little documentation to write the configuration file
(except for OpenStack)
@@ -123,7 +113,7 @@ The list includes the following providers:
- To get these addresses, the node needs to communicate with the control plane
- ...Which means joining the cluster
- ... 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.)

View File

@@ -6,7 +6,7 @@
- error recovery (human or process has altered or corrupted data)
- cloning environments (for testing, validation...)
- cloning environments (for testing, validation ...)
- Let's see the strategies and tools available with Kubernetes!
@@ -18,13 +18,13 @@
(it gives us replication primitives)
- Kubernetes helps us clone / replicate environments
- Kubernetes helps us to clone / replicate environments
(all resources can be described with manifests)
- Kubernetes *does not* help us with error recovery
- We still need to back up/snapshot our data:
- We still need to backup / snapshot our data:
- with database backups (mysqldump, pgdump, etc.)
@@ -58,7 +58,7 @@
- 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...
- Litmus test: how long does it take to deploy a cluster ...
- for a senior engineer?
@@ -66,7 +66,7 @@
- Does it require external intervention?
(e.g. provisioning servers, signing TLS certs...)
(e.g. provisioning servers, signing TLS certs ...)
---
@@ -108,7 +108,7 @@
- For real applications: add resources (as YAML files)
- For applications deployed multiple times: Helm, Kustomize...
- For applications deployed multiple times: Helm, Kustomize ...
(staging and production count as "multiple times")

View File

@@ -287,8 +287,8 @@
- Download the configuration on each node, and upgrade kubelet:
```bash
for N in 1 2 3; do
ssh test$N sudo kubeadm upgrade node config --kubelet-version v1.14.2
ssh test$N sudo apt install kubelet=1.14.2-00
ssh node$N sudo kubeadm upgrade node config --kubelet-version v1.14.2
ssh node $N sudo apt install kubelet=1.14.2-00
done
```
]

View File

@@ -66,8 +66,6 @@ Look in each plugin's directory for its documentation.
---
class: extra-details
## Conf vs conflist
- There are two slightly different configuration formats
@@ -190,13 +188,9 @@ class: extra-details
- ... But this time, the controller manager will allocate `podCIDR` subnets
(so that we don't have to manually assign subnets to individual nodes)
- We will start kube-router with a DaemonSet
- We will create a DaemonSet for kube-router
- We will join nodes to the cluster
- The DaemonSet will automatically start a kube-router pod on each node
- This DaemonSet will start one instance of kube-router on each node
---
@@ -278,7 +272,7 @@ class: extra-details
- The address of the API server will be `http://A.B.C.D:8080`
(where `A.B.C.D` is the public address of `kuberouter1`, running the control plane)
(where `A.B.C.D` is the address of `kuberouter1`, running the control plane)
.exercise[
@@ -306,10 +300,12 @@ Note: the DaemonSet won't create any pods (yet) since there are no nodes (yet).
- Generate the kubeconfig file (replacing `X.X.X.X` with the address of `kuberouter1`):
```bash
kubectl config set-cluster cni --server http://`X.X.X.X`:8080
kubectl config set-context cni --cluster cni
kubectl config use-context cni
cp ~/.kube/config ~/kubeconfig
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
```
]
@@ -455,7 +451,7 @@ We should see the local pod CIDR connected to `kube-bridge`, and the other nodes
- Or try to exec into one of the kube-router pods:
```bash
kubectl -n kube-system exec kube-router-xxxxx bash
kubectl -n kube-system exec kuber-router-xxxxx bash
```
]
@@ -491,8 +487,8 @@ What does that mean?
- First, get the container ID, with `docker ps` or like this:
```bash
CID=$(docker ps -q \
--filter label=io.kubernetes.pod.namespace=kube-system \
CID=$(docker ps
--filter label=io.kubernetes.pod.namespace=kube-system
--filter label=io.kubernetes.container.name=kube-router)
```
@@ -577,7 +573,7 @@ done
## Starting the route reflector
- Only do this slide if you are doing this on your own
- Only do this if you are doing this on your own
- There is a Compose file in the `compose/frr-route-reflector` directory

View File

@@ -0,0 +1,265 @@
# Securing the control plane
- Many components accept connections (and requests) from others:
- API server
- etcd
- kubelet
- We must secure these connections:
- to deny unauthorized requests
- to prevent eavesdropping secrets, tokens, and other sensitive information
- Disabling authentication and/or authorization is **strongly discouraged**
(but it's possible to do it, e.g. for learning / troubleshooting purposes)
---
## Authentication and authorization
- Authentication (checking "who you are") is done with mutual TLS
(both the client and the server need to hold a valid certificate)
- Authorization (checking "what you can do") is done in different ways
- the API server implements a sophisticated permission logic (with RBAC)
- some services will defer authorization to the API server (through webhooks)
- some services require a certificate signed by a particular CA / sub-CA
---
## In practice
- We will review the various communication channels in the control plane
- We will describe how they are secured
- When TLS certificates are used, we will indicate:
- which CA signs them
- what their subject (CN) should be, when applicable
- We will indicate how to configure security (client- and server-side)
---
## etcd peers
- Replication and coordination of etcd happens on a dedicated port
(typically port 2380; the default port for normal client connections is 2379)
- Authentication uses TLS certificates with a separate sub-CA
(otherwise, anyone with a Kubernetes client certificate could access etcd!)
- The etcd command line flags involved are:
`--peer-client-cert-auth=true` to activate it
`--peer-cert-file`, `--peer-key-file`, `--peer-trusted-ca-file`
---
## etcd clients
- The only¹ thing that connects to etcd is the API server
- Authentication uses TLS certificates with a separate sub-CA
(for the same reasons as for etcd inter-peer authentication)
- The etcd command line flags involved are:
`--client-cert-auth=true` to activate it
`--trusted-ca-file`, `--cert-file`, `--key-file`
- The API server command line flags involved are:
`--etcd-cafile`, `--etcd-certfile`, `--etcd-keyfile`
.footnote[¹Technically, there is also the etcd healthcheck. Let's ignore it for now.]
---
## API server clients
- The API server has a sophisticated authentication and authorization system
- For connections coming from other components of the control plane:
- authentication uses certificates (trusting the certificates' subject or CN)
- authorization uses whatever mechanism is enabled (most oftentimes, RBAC)
- The relevant API server flags are:
`--client-ca-file`, `--tls-cert-file`, `--tls-private-key-file`
- Each component connecting to the API server takes a `--kubeconfig` flag
(to specify a kubeconfig file containing the CA cert, client key, and client cert)
- Yes, that kubeconfig file follows the same format as our `~/.kube/config` file!
---
## Kubelet and API server
- Communication between kubelet and API server can be established both ways
- Kubelet → API server:
- kubelet registers itself ("hi, I'm node42, do you have work for me?")
- connection is kept open and re-established if it breaks
- that's how the kubelet knows which pods to start/stop
- API server → kubelet:
- used to retrieve logs, exec, attach to containers
---
## Kubelet → API server
- Kubelet is started with `--kubeconfig` with API server information
- The client certificate of the kubelet will typically have:
`CN=system:node:<nodename>` and groups `O=system:nodes`
- Nothing special on the API server side
(it will authenticate like any other client)
---
## API server → kubelet
- Kubelet is started with the flag `--client-ca-file`
(typically using the same CA as the API server)
- API server will use a dedicated key pair when contacting kubelet
(specified with `--kubelet-client-certificate` and `--kubelet-client-key`)
- Authorization uses webhooks
(enabled with `--authorization-mode=Webhook` on kubelet)
- The webhook server is the API server itself
(the kubelet sends back a request to the API server to ask, "can this person do that?")
---
## Scheduler
- The scheduler connects to the API server like an ordinary client
- The certificate of the scheduler will have `CN=system:kube-scheduler`
---
## Controller manager
- The controller manager is also a normal client to the API server
- Its certificate will have `CN=system:kube-controller-manager`
- If we use the CSR API, the controller manager needs the CA cert and key
(passed with flags `--cluster-signing-cert-file` and `--cluster-signing-key-file`)
- We usually want the controller manager to generate tokens for service accounts
- These tokens deserve some details (on the next slide!)
---
## Service account tokens
- Each time we create a service account, the controller manager generates a token
- These tokens are JWT tokens, signed with a particular key
- These tokens are used for authentication with the API server
(and therefore, the API server needs to be able to verify their integrity)
- This uses another keypair:
- the private key (used for signature) is passed to the controller manager
<br/>(using flags `--service-account-private-key-file` and `--root-ca-file`)
- the public key (used for verification) is passed to the API server
<br/>(using flag `--service-account-key-file`)
---
## kube-proxy
- kube-proxy is "yet another API server client"
- In many clusters, it runs as a Daemon Set
- In that case, it will have its own Service Account and associated permissions
- It will authenticate using the token of that Service Account
---
## Webhooks
- We mentioned webhooks earlier; how does that really work?
- The Kubernetes API has special resource types to check permissions
- One of them is SubjectAccessReview
- To check if a particular user can do a particular action on a particular resource:
- we prepare a SubjectAccessReview object
- we send that object to the API server
- the API server responds with allow/deny (and optional explanations)
- Using webhooks for authorization = sending SAR to authorize each request
---
## Subject Access Review
Here is an example showing how to check if `jean.doe` can `get` some `pods` in `kube-system`:
```bash
kubectl -v9 create -f- <<EOF
apiVersion: authorization.k8s.io/v1beta1
kind: SubjectAccessReview
spec:
user: jean.doe
group:
- foo
- bar
resourceAttributes:
#group: blah.k8s.io
namespace: kube-system
resource: pods
verb: get
#name: web-xyz1234567-pqr89
EOF
```

View File

@@ -46,7 +46,7 @@
(and vice versa)
- If I use someone's public key to encrypt/decrypt their messages,
- If I use someone's public key to encrypt / decrypt their messages,
<br/>
I can be certain that I am talking to them / they are talking to me
@@ -58,11 +58,11 @@
This is what I do if I want to obtain a certificate.
1. Create public and private keys.
1. Create public and private key.
2. Create a Certificate Signing Request (CSR).
(The CSR contains the identity that I claim and a public key.)
(The CSR contains the identity that I claim and an expiration date.)
3. Send that CSR to the Certificate Authority (CA).
@@ -84,7 +84,7 @@ The CA (or anyone else) never needs to know my private key.
(= upload a CSR to the Kubernetes API)
- Then, using the Kubernetes API, we can approve/deny the request
- Then, using the Kubernetes API, we can approve / deny the request
- If we approve the request, the Kubernetes API generates a certificate
@@ -122,7 +122,7 @@ The CA (or anyone else) never needs to know my private key.
- Users can then retrieve their certificate from their CSR object
- ...And use that certificate for subsequent interactions
- ... And use that certificate for subsequent interactions
---
@@ -231,7 +231,7 @@ For a user named `jean.doe`, we will have:
- Let's use OpenSSL; it's not the best one, but it's installed everywhere
(many people prefer cfssl, easyrsa, or other tools; that's fine too!)
.exercise[
- Generate the key and certificate signing request:
@@ -244,7 +244,7 @@ For a user named `jean.doe`, we will have:
The command above generates:
- a 2048-bit RSA key, without encryption, stored in key.pem
- a 2048-bit RSA key, without DES encryption, stored in key.pem
- a CSR for the name `jean.doe` in group `devs`
---
@@ -345,7 +345,7 @@ The command above generates:
kctx -
```
- Retrieve the updated CSR object and extract the certificate:
- Retrieve the certificate from the CSR:
```bash
kubectl get csr users:jean.doe \
-o jsonpath={.status.certificate} \
@@ -387,7 +387,7 @@ The command above generates:
## What's missing?
We have just shown, step by step, a method to issue short-lived certificates for users.
We shown, step by step, a method to issue short-lived certificates for users.
To be usable in real environments, we would need to add:
@@ -417,7 +417,7 @@ To be usable in real environments, we would need to add:
- This provides enhanced security:
- the long-term credentials can use long passphrases, 2FA, HSM...
- the long-term credentials can use long passphrases, 2FA, HSM ...
- the short-term credentials are more convenient to use

View File

@@ -38,7 +38,7 @@
<!-- ##VERSION## -->
- Unfortunately, as of Kubernetes 1.15, the CLI cannot create daemon sets
- Unfortunately, as of Kubernetes 1.14, the CLI cannot create daemon sets
--

View File

@@ -175,7 +175,7 @@ Success!
]
We should get `No resources found.` and the `kubernetes` service, respectively.
So far, so good.
Note: the API server automatically created the `kubernetes` service entry.
@@ -225,7 +225,7 @@ Success?
]
Our Deployment is in bad shape:
Our Deployment is in a bad shape:
```
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web 0/1 0 0 2m26s

View File

@@ -117,7 +117,7 @@ Examples:
## Admission controllers
- When a Pod is created, it is associated with a ServiceAccount
- When a Pod is created, it is associated to a ServiceAccount
(even if we did not specify one explicitly)
@@ -163,7 +163,7 @@ class: pic
- These webhooks can be *validating* or *mutating*
- Webhooks can be set up dynamically (without restarting the API server)
- Webhooks can be setup dynamically (without restarting the API server)
- To setup a dynamic admission webhook, we create a special resource:
@@ -171,7 +171,7 @@ class: pic
- These resources are created and managed like other resources
(i.e. `kubectl create`, `kubectl get`...)
(i.e. `kubectl create`, `kubectl get` ...)
---

View File

@@ -304,9 +304,9 @@ It will use the default success threshold (1 successful attempt = alive).
- We need to make sure that the healthcheck doesn't trip when
performance degrades due to external pressure
- Using a readiness check would have fewer effects
- Using a readiness check would have lesser effects
(but it would still be an imperfect solution)
(but it still would be an imperfect solution)
- A possible combination:
@@ -344,7 +344,7 @@ class: extra-details
- When a process is killed, its children are *orphaned* and attached to PID 1
- PID 1 has the responsibility of *reaping* these processes when they terminate
- PID 1 has the responsibility if *reaping* these processes when they terminate
- OK, but how does that affect us?
@@ -378,11 +378,11 @@ class: extra-details
(because worker isn't a backend for a service)
- Liveness may help us restart a broken worker, but how can we check it?
- Liveness may help us to 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)
(but it has a high potential for unwanted side-effects and false positives)
- Using a "lease" file can be relatively easy:

View File

@@ -6,15 +6,15 @@
- Horizontal scaling = changing the number of replicas
(adding/removing pods)
(adding / removing pods)
- Vertical scaling = changing the size of individual replicas
(increasing/reducing CPU and RAM per pod)
(increasing / reducing CPU and RAM per pod)
- Cluster scaling = changing the size of the cluster
(adding/removing nodes)
(adding / removing nodes)
---
@@ -50,9 +50,9 @@
- The latter actually makes a lot of sense:
- if a Pod doesn't have a CPU request, it might be using 10% of CPU...
- if a Pod doesn't have a CPU request, it might be using 10% of CPU ...
- ...but only because there is no CPU time available!
- ... but only because there is no CPU time available!
- this makes sure that we won't add pods to nodes that are already resource-starved
@@ -238,7 +238,7 @@ This can also be set with `--cpu-percent=`.
- Kubernetes doesn't implement any of these API groups
- Using these metrics requires [registering additional APIs](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-metrics-apis)
- Using these metrics requires to [register additional APIs](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-metrics-apis)
- The metrics provided by metrics server are standard; everything else is custom

View File

@@ -1,244 +0,0 @@
# Deploying a sample application
- We will connect to our new Kubernetes cluster
- We will deploy a sample application, "DockerCoins"
- That app features multiple micro-services and a web UI
---
## Connecting to our Kubernetes cluster
- Our cluster has multiple nodes named `node1`, `node2`, etc.
- We will do everything from `node1`
- We have SSH access to the other nodes, but won't need it
(but we can use it for debugging, troubleshooting, etc.)
.exercise[
- Log into `node1`
- Check that all nodes are `Ready`:
```bash
kubectl get nodes
```
]
---
## Cloning some repos
- We will need two repositories:
- the first one has the "DockerCoins" demo app
- the second one has these slides, some scripts, more manifests ...
.exercise[
- Clone the kubercoins repository on `node1`:
```bash
git clone https://github.com/jpetazzo/kubercoins
```
- Clone the container.training repository as well:
```bash
git clone https://@@GITREPO@@
```
]
---
## Running the application
Without further ado, let's start this application!
.exercise[
- Apply all the manifests from the kubercoins repository:
```bash
kubectl apply -f kubercoins/
```
]
---
## What's this application?
--
- It is a DockerCoin miner! .emoji[💰🐳📦🚢]
--
- No, you can't buy coffee with DockerCoins
--
- How DockerCoins works:
- generate a few random bytes
- hash these bytes
- increment a counter (to keep track of speed)
- repeat forever!
--
- DockerCoins is *not* a cryptocurrency
(the only common points are "randomness", "hashing", and "coins" in the name)
---
## DockerCoins in the microservices era
- DockerCoins is made of 5 services:
- `rng` = web service generating random bytes
- `hasher` = web service computing hash of POSTed data
- `worker` = background process calling `rng` and `hasher`
- `webui` = web interface to watch progress
- `redis` = data store (holds a counter updated by `worker`)
- These 5 services are visible in the application's Compose file,
[docker-compose.yml](
https://@@GITREPO@@/blob/master/dockercoins/docker-compose.yml)
---
## How DockerCoins works
- `worker` invokes web service `rng` to generate random bytes
- `worker` invokes web service `hasher` to hash these bytes
- `worker` does this in an infinite loop
- every second, `worker` updates `redis` to indicate how many loops were done
- `webui` queries `redis`, and computes and exposes "hashing speed" in our browser
*(See diagram on next slide!)*
---
class: pic
![Diagram showing the 5 containers of the applications](images/dockercoins-diagram.svg)
---
## Service discovery in container-land
How does each service find out the address of the other ones?
--
- We do not hard-code IP addresses in the code
- We do not hard-code FQDNs in the code, either
- We just connect to a service name, and container-magic does the rest
(And by container-magic, we mean "a crafty, dynamic, embedded DNS server")
---
## Example in `worker/worker.py`
```python
redis = Redis("`redis`")
def get_random_bytes():
r = requests.get("http://`rng`/32")
return r.content
def hash_bytes(data):
r = requests.post("http://`hasher`/",
data=data,
headers={"Content-Type": "application/octet-stream"})
```
(Full source code available [here](
https://@@GITREPO@@/blob/8279a3bce9398f7c1a53bdd95187c53eda4e6435/dockercoins/worker/worker.py#L17
))
---
## Show me the code!
- You can check the GitHub repository with all the materials of this workshop:
<br/>https://@@GITREPO@@
- The application is in the [dockercoins](
https://@@GITREPO@@/tree/master/dockercoins)
subdirectory
- The Compose file ([docker-compose.yml](
https://@@GITREPO@@/blob/master/dockercoins/docker-compose.yml))
lists all 5 services
- `redis` is using an official image from the Docker Hub
- `hasher`, `rng`, `worker`, `webui` are each built from a Dockerfile
- Each service's Dockerfile and source code is in its own directory
(`hasher` is in the [hasher](https://@@GITREPO@@/blob/master/dockercoins/hasher/) directory,
`rng` is in the [rng](https://@@GITREPO@@/blob/master/dockercoins/rng/)
directory, etc.)
---
## Our application at work
- We can check the logs of our application's pods
.exercise[
- Check the logs of the various components:
```bash
kubectl logs deploy/worker
kubectl logs deploy/hasher
```
]
---
## Connecting to the web UI
- "Logs are exciting and fun!" (No-one, ever)
- The `webui` container exposes a web dashboard; let's view it
.exercise[
- Check the NodePort allocated to the web UI:
```bash
kubectl get svc webui
```
- Open that in a web browser
]
A drawing area should show up, and after a few seconds, a blue
graph will appear.

View File

@@ -48,7 +48,7 @@
- Acknowledge that a lot of tasks are outsourced
(e.g. if we add "buy/rack/provision machines" in that list)
(e.g. if we add "buy / rack / provision machines" in that list)
---
@@ -122,7 +122,7 @@
(YAML, Helm charts, Kustomize ...)
- Team "run" adjusts some parameters and monitors the application
- Team "run" adjusts some parameters and monitors the application
✔️ parity between dev and prod environments
@@ -150,7 +150,7 @@
- do we reward on-call duty without encouraging hero syndrome?
- do we give people resources (time, money) to learn?
- do we give resources (time, money) to people to learn?
---
@@ -183,9 +183,9 @@ are a few tools that can help us.*
- If cloud: public vs. private
- Which vendor/distribution to pick?
- Which vendor / distribution to pick?
- Which versions/features to enable?
- Which versions / features to enable?
---
@@ -205,6 +205,6 @@ are a few tools that can help us.*
- Transfer knowledge
(make sure everyone is on the same page/level)
(make sure everyone is on the same page / same level)
- Iterate!

View File

@@ -34,11 +34,11 @@
- Download the `kubectl` binary from one of these links:
[Linux](https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/linux/amd64/kubectl)
[Linux](https://storage.googleapis.com/kubernetes-release/release/v1.14.2/bin/linux/amd64/kubectl)
|
[macOS](https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/darwin/amd64/kubectl)
[macOS](https://storage.googleapis.com/kubernetes-release/release/v1.14.2/bin/darwin/amd64/kubectl)
|
[Windows](https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/windows/amd64/kubectl.exe)
[Windows](https://storage.googleapis.com/kubernetes-release/release/v1.14.2/bin/windows/amd64/kubectl.exe)
- On Linux and macOS, make the binary executable with `chmod +x kubectl`

View File

@@ -73,12 +73,12 @@ and a few roles and role bindings (to give fluentd the required permissions).
- Fluentd runs on each node (thanks to a daemon set)
- It bind-mounts `/var/log/containers` from the host (to access these files)
- It binds-mounts `/var/log/containers` from the host (to access these files)
- It continuously scans this directory for new files; reads them; parses them
- Each log line becomes a JSON object, fully annotated with extra information:
<br/>container id, pod name, Kubernetes labels...
<br/>container id, pod name, Kubernetes labels ...
- These JSON objects are stored in ElasticSearch

View File

@@ -1,6 +1,6 @@
# Accessing logs from the CLI
- The `kubectl logs` command has limitations:
- The `kubectl logs` commands has limitations:
- it cannot stream logs from multiple pods at a time
@@ -12,7 +12,7 @@
## Doing it manually
- We *could* (if we were so inclined) write a program or script that would:
- We *could* (if we were so inclined), write a program or script that would:
- take a selector as an argument
@@ -72,11 +72,11 @@ Exactly what we need!
## Using Stern
- There are two ways to specify the pods whose logs we want to see:
- There are two ways to specify the pods for which we want to see the logs:
- `-l` followed by a selector expression (like with many `kubectl` commands)
- with a "pod query," i.e. a regex used to match pod names
- with a "pod query", i.e. a regex used to match pod names
- These two ways can be combined if necessary

View File

@@ -96,7 +96,7 @@ class: extra-details
- We need to generate a `kubeconfig` file for kubelet
- This time, we need to put the public IP address of `kubenet1`
- This time, we need to put the IP address of `kubenet1`
(instead of `localhost` or `127.0.0.1`)
@@ -104,10 +104,12 @@ class: extra-details
- Generate the `kubeconfig` file:
```bash
kubectl config set-cluster kubenet --server http://`X.X.X.X`:8080
kubectl config set-context kubenet --cluster kubenet
kubectl config use-context kubenet
cp ~/.kube/config ~/kubeconfig
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
```
]
@@ -195,7 +197,7 @@ class: extra-details
## Check our pods
- The pods will be scheduled on the nodes
- The pods will be scheduled to the nodes
- The nodes will pull the `nginx` image, and start the pods
@@ -325,7 +327,7 @@ class: extra-details
- We will add the `--network-plugin` and `--pod-cidr` flags
- We all have a "cluster number" (let's call that `C`) printed on your VM info card
- 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)
@@ -480,23 +482,6 @@ Sometimes it works, sometimes it doesn't. Why?
```bash
kubectl get nodes -o wide
```
---
## Firewalling
- By default, Docker prevents containers from using arbitrary IP addresses
(by setting up iptables rules)
- We need to allow our containers to use our pod CIDR
- For simplicity, we will insert a blanket iptables rule allowing all traffic:
`iptables -I FORWARD -j ACCEPT`
- This has to be done on every node
---
## Setting up routing
@@ -505,8 +490,6 @@ Sometimes it works, sometimes it doesn't. Why?
- Create all the routes on all the nodes
- Insert the iptables rule allowing traffic
- Check that you can ping all the pods from one of the nodes
- Check that you can `curl` the ClusterIP of the Service successfully

View File

@@ -307,7 +307,7 @@ This policy selects all pods in the current namespace.
It allows traffic only from pods in the current namespace.
(An empty `podSelector` means "all pods.")
(An empty `podSelector` means "all pods".)
```yaml
kind: NetworkPolicy
@@ -329,7 +329,7 @@ This policy selects all pods with label `app=webui`.
It allows traffic from any source.
(An empty `from` field means "all sources.")
(An empty `from` fields means "all sources".)
```yaml
kind: NetworkPolicy
@@ -412,7 +412,7 @@ troubleshoot easily, without having to poke holes in our firewall.
- If we block access to the control plane, we might disrupt legitimate code
- ...Without necessarily improving security
- ... Without necessarily improving security
---

View File

@@ -0,0 +1,377 @@
# OpenID Connect
- The Kubernetes API server can perform authentication with OpenID connect
- This requires an *OpenID provider*
(external authorization server using the OAuth 2.0 protocol)
- We can use a third-party provider (e.g. Google) or run our own (e.g. Dex)
- We are going to give an overview of the protocol
- We will show it in action (in a simplified scenario)
---
## Workflow overview
- We want to access our resources (a Kubernetes cluster)
- We authenticate with the OpenID provider
- we can do this directly (e.g. by going to https://accounts.google.com)
- or maybe a kubectl plugin can open a browser page on our behalf
- After authenticating us, the OpenID provider gives us:
- an *id token* (a short-lived signed JSON Web Token, see next slide)
- a *refresh token* (to renew the previous one when needed)
- We can now issue requests to the Kubernetes API with the *id token*
- The API server will verify that token's content to authenticate us
---
## JSON Web Tokens
- A JSON Web Token (JWT) has three parts:
- a header specifying algorithms and token type
- a payload (indicating who issued the token, for whom, which purposes...)
- a signature generated by the issuer (the issuer = the OpenID provider)
- Anyone can verify a JWT without contacting the issuer
(except to obtain the issuer's public key)
- Pro tip: we can inspect a JWT with https://jwt.io/
---
## How the Kubernetes API uses JWT
- Server side
- enable OIDC authentication
- indicate which issuer (provider) should be allowed
- indicate which audience (or "client id") should be allowed
- if necessary, map or prefix user and group names
- Client side
- obtain JWT as described earlier
- pass JWT as authentication token
- renew JWT when needed (using the refresh token)
---
## Demo time!
- We will use [Google Accounts](https://accounts.google.com) as our OpenID provider
- We will use the [Google OAuth Playground](https://developers.google.com/oauthplayground) as the "audience" or "client id"
- We will obtain a JWT through Google Accounts and the OAuth Playground
- We will enable OIDC in the Kubernetes API server
- We will use the JWT to authenticate
.footnote[If you can't or won't use a Google Account, you can try to adapt to another provider.]
---
## Checking the API server logs
- The API server logs will be particularly useful in this section
(they will indicate e.g. why a specific token is rejected)
- Let's keep an eye on the API server output!
.exercise[
- Tail the logs of the API server:
```bash
kubectl logs kube-apiserver-node1 --follow --namespace=kube-system
```
]
---
## Authenticate with the OpenID provider
- We will use the Google OAuth Playground for convenience
- In a real scenario, we would need our own OAuth client instead of the playground
(even if we were still using Google as the OpenID provider)
.exercise[
- Open the Google OAuth Playground:
```
https://developers.google.com/oauthplayground/
```
- Enter our own custom scope in the text field:
```
https://www.googleapis.com/auth/userinfo.email
```
- Click on "Authorize APIs" and allow the playground to access our email address
]
---
## Obtain our JSON Web Token
- The previous step gave us an "authorization code"
- We will use it to obtain tokens
.exercise[
- Click on "Exchange authorization code for tokens"
]
- The JWT is the very long `id_token` that shows up on the right hand side
(it is a base64-encoded JSON object, and should therefore start with `eyJ`)
---
## Using our JSON Web Token
- We need to create a context (in kubeconfig) for our token
(if we just add the token or use `kubectl --token`, our certificate will still be used)
.exercise[
- Create a new authentication section in kubeconfig:
```bash
kubectl config set-credentials myjwt --token=eyJ...
```
- Try to use it:
```bash
kubectl --user=myjwt get nodes
```
]
We should get an `Unauthorized` response, since we haven't enabled OpenID Connect in the API server yet. We should also see `invalid bearer token` in the API server log output.
---
## Enabling OpenID Connect
- We need to add a few flags to the API server configuration
- These two are mandatory:
`--oidc-issuer-url` → URL of the OpenID provider
`--oidc-client-id` → app requesting the authentication
<br/>(in our case, that's the ID for the Google OAuth Playground)
- This one is optional:
`--oidc-username-claim` → which field should be used as user name
<br/>(we will use the user's email address instead of an opaque ID)
- See the [API server documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server
) for more details about all available flags
---
## Updating the API server configuration
- The instructions below will work for clusters deployed with kubeadm
(or where the control plane is deployed in static pods)
- If your cluster is different, you will need to adapt them
.exercise[
- Edit `/etc/kubernetes/manifests/kube-apiserver.yaml`
- Add the following lines to the list of command-line flags:
```yaml
- --oidc-issuer-url=https://accounts.google.com
- --oidc-client-id=407408718192.apps.googleusercontent.com
- --oidc-username-claim=email
```
]
---
## Restarting the API server
- The kubelet monitors the files in `/etc/kubernetes/manifests`
- When we save the pod manifest, kubelet will restart the corresponding pod
(using the updated command line flags)
.exercise[
- After making the changes described on the previous slide, save the file
- Issue a simple command (like `kubectl version`) until the API server is back up
(it might take between a few seconds and one minute for the API server to restart)
- Restart the `kubectl logs` command to view the logs of the API server
]
---
## Using our JSON Web Token
- Now that the API server is set up to recognize our token, try again!
.exercise[
- Try an API command with our token:
```bash
kubectl --user=myjwt get nodes
kubectl --user=myjwt get pods
```
]
We should see a message like:
```
Error from server (Forbidden): nodes is forbidden: User "jean.doe@gmail.com"
cannot list resource "nodes" in API group "" at the cluster scope
```
→ We were successfully *authenticated*, but not *authorized*.
---
## Authorizing our user
- As an extra step, let's grant read access to our user
- We will use the pre-defined ClusterRole `view`
.exercise[
- Create a ClusterRoleBinding allowing us read access to the cluster:
```bash
kubectl create clusterrolebinding i-can-view \
--user=`jean.doe@gmail.com` --clusterrole=view
```
- Confirm that we can now list pods with our token:
```bash
kubectl --user=myjwt get pods
```
]
---
## From demo to production
.warning[This was a very simplified demo! In a real deployment...]
- We wouldn't use the Google OAuth Playground
- We *probably* wouldn't even use Google at all
(it doesn't seem to provide a way to include groups!)
- Some popular alternatives:
- [Dex](https://github.com/dexidp/dex),
[Keycloak](https://www.keycloak.org/)
(self-hosted)
- [Okta](https://developer.okta.com/docs/how-to/creating-token-with-groups-claim/#step-five-decode-the-jwt-to-verify)
(SaaS)
- We would use a helper (like the [kubelogin](https://github.com/int128/kubelogin) plugin) to automatically obtain tokens
---
class: extra-details
## Service Account tokens
- The tokens used by Service Accounts are JWT tokens as well
- They are signed and verified using a special service account key pair
.exercise[
- Extract the token of a service account in the current namespace:
```bash
kubectl get secrets -o jsonpath={..token} | base64 -d
```
- Copy-paste the token to a verification service like https://jwt.io
- Notice that it says "Invalid Signature"
]
---
class: extra-details
## Verifying Service Account tokens
- JSON Web Tokens embed the URL of the "issuer" (=OpenID provider)
- The issuer provides its public key through a well-known discovery endpoint
(similar to https://accounts.google.com/.well-known/openid-configuration)
- There is no such endpoint for the Service Account key pair
- But we can provide the public key ourselves for verification
---
class: extra-details
## Verifying a Service Account token
- On clusters provisioned with kubeadm, the Service Account key pair is:
`/etc/kubernetes/pki/sa.key` (used by the controller manager to generate tokens)
`/etc/kubernetes/pki/sa.pub` (used by the API server to validate the same tokens)
.exercise[
- Display the public key used to sign Service Account tokens:
```bash
sudo cat /etc/kubernetes/pki/sa.pub
```
- Copy-paste the key in the "verify signature" area on https://jwt.io
- It should now say "Signature Verified"
]

View File

@@ -1,4 +1,4 @@
## What does it take to write an operator?
## Operator design (extra material)
- Writing a quick-and-dirty operator, or a POC/MVP, is easy
@@ -86,7 +86,7 @@ class: extra-details
## What can we store via the Kubernetes API?
- The API server stores most Kubernetes resources in etcd
- The API server stores most Kubernetes resources into etcd
- Etcd is designed for reliability, not for performance

View File

@@ -1,4 +1,4 @@
# Owners and dependents
# Owners and dependents (extra material)
- Some objects are created by other objects

View File

@@ -8,18 +8,12 @@
- Then we will explain how to avoid this with PodSecurityPolicies
- We will enable PodSecurityPolicies on our cluster
- We will create a couple of policies (restricted and permissive)
- Finally we will see how to use them to improve security on our cluster
- We will illustrate this by creating a non-privileged user limited to a namespace
---
## Setting up a namespace
- For simplicity, let's work in a separate namespace
- Let's create a new namespace called "green"
.exercise[
@@ -38,9 +32,168 @@
---
## Using limited credentials
- When a namespace is created, a `default` ServiceAccount is added
- By default, this ServiceAccount doesn't have any access rights
- We will use this ServiceAccount as our non-privileged user
- We will obtain this ServiceAccount's token and add it to a context
- Then we will give basic access rights to this ServiceAccount
---
## Obtaining the ServiceAccount's token
- The token is stored in a Secret
- The Secret is listed in the ServiceAccount
.exercise[
- Obtain the name of the Secret from the ServiceAccount::
```bash
SECRET=$(kubectl get sa default -o jsonpath={.secrets[0].name})
```
- Extract the token from the Secret object:
```bash
TOKEN=$(kubectl get secrets $SECRET -o jsonpath={.data.token}
| base64 -d)
```
]
---
class: extra-details
## Inspecting a Kubernetes token
- Kubernetes tokens are JSON Web Tokens
(as defined by [RFC 7519](https://tools.ietf.org/html/rfc7519))
- We can view their content (and even verify them) easily
.exercise[
- Display the token that we obtained:
```bash
echo $TOKEN
```
- Copy paste the token in the verification form on https://jwt.io
]
---
## Authenticating using the ServiceAccount token
- Let's create a new *context* accessing our cluster with that token
.exercise[
- First, add the token credentials to our kubeconfig file:
```bash
kubectl config set-credentials green --token=$TOKEN
```
- Then, create a new context using these credentials:
```bash
kubectl config set-context green --user=green --cluster=kubernetes
```
- Check the results:
```bash
kubectl config get-contexts
```
]
---
## Using the new context
- Normally, this context doesn't let us access *anything* (yet)
.exercise[
- Change to the new context with one of these two commands:
```bash
kctx green
kubectl config use-context green
```
- Also change to the green namespace in that context:
```bash
kns green
```
- Confirm that we don't have access to anything:
```bash
kubectl get all
```
]
---
## Giving basic access rights
- Let's bind the ClusterRole `edit` to our ServiceAccount
- To allow access only to the namespace, we use a RoleBinding
(instead of a ClusterRoleBinding, which would give global access)
.exercise[
- Switch back to `cluster-admin`:
```bash
kctx -
```
- Create the Role Binding:
```bash
kubectl create rolebinding green --clusterrole=edit --serviceaccount=green:default
```
]
---
## Verifying access rights
- Let's switch back to the `green` context and check that we have rights
.exercise[
- Switch back to `green`:
```bash
kctx green
```
- Check our permissions:
```bash
kubectl get all
```
]
We should see an empty list.
(Better than a series of permission errors!)
---
## Creating a basic Deployment
- Just to check that everything works correctly, deploy NGINX
- Just to demonstrate that everything works correctly, deploy NGINX
.exercise[
@@ -49,7 +202,7 @@
kubectl create deployment web --image=nginx
```
- Confirm that the Deployment, ReplicaSet, and Pod exist, and that the Pod is running:
- Confirm that the Deployment, ReplicaSet, and Pod exist, and Pod is running:
```bash
kubectl get all
```
@@ -163,7 +316,7 @@
- If we create a Pod directly, it can use a PSP to which *we* have access
- If the Pod is created by e.g. a ReplicaSet or DaemonSet, it's different:
- the ReplicaSet / DaemonSet controllers don't have access to *our* policies
- therefore, we need to give access to the PSP to the Pod's ServiceAccount
@@ -178,7 +331,7 @@
- Then we will create a couple of PodSecurityPolicies
- ...And associated ClusterRoles (giving `use` access to the policies)
- ... And associated ClusterRoles (giving `use` access to the policies)
- Then we will create RoleBindings to grant these roles to ServiceAccounts
@@ -212,7 +365,7 @@
- Have a look at the static pods:
```bash
ls -l /etc/kubernetes/manifests
ls -l /etc/kubernetes/manifest
```
- Edit the one corresponding to the API server:
@@ -236,7 +389,7 @@
- Add `PodSecurityPolicy`
It should read: `--enable-admission-plugins=NodeRestriction,PodSecurityPolicy`
(It should read `--enable-admission-plugins=NodeRestriction,PodSecurityPolicy`)
- Save, quit
@@ -321,65 +474,12 @@ We can get hints at what's happening by looking at the ReplicaSet and Events.
---
## Check that we can create Pods again
- We haven't bound the policy to any user yet
- But `cluster-admin` can implicitly `use` all policies
.exercise[
- Check that we can now create a Pod directly:
```bash
kubectl run testpsp3 --image=nginx --restart=Never
```
- Create a Deployment as well:
```bash
kubectl run testpsp4 --image=nginx
```
- Confirm that the Deployment is *not* creating any Pods:
```bash
kubectl get all
```
]
---
## What's going on?
- We can create Pods directly (thanks to our root-like permissions)
- The Pods corresponding to a Deployment are created by the ReplicaSet controller
- The ReplicaSet controller does *not* have root-like permissions
- We need to either:
- grant permissions to the ReplicaSet controller
*or*
- grant permissions to our Pods' ServiceAccount
- The first option would allow *anyone* to create pods
- The second option will allow us to scope the permissions better
---
## Binding the restricted policy
- Let's bind the role `psp:restricted` to ServiceAccount `green:default`
(aka the default ServiceAccount in the green Namespace)
- This will allow Pod creation in the green Namespace
(because these Pods will be using that ServiceAccount automatically)
.exercise[
- Create the following RoleBinding:
@@ -395,17 +495,18 @@ We can get hints at what's happening by looking at the ReplicaSet and Events.
## Trying it out
- The Deployments that we created earlier will *eventually* recover
(the ReplicaSet controller will retry to create Pods once in a while)
- If we create a new Deployment now, it should work immediately
- Let's switch to the `green` context, and try to create resources
.exercise[
- Switch to the `green` context:
```bash
kctx green
```
- Create a simple Deployment:
```bash
kubectl create deployment testpsp5 --image=nginx
kubectl create deployment web --image=nginx
```
- Look at the Pods that have been created:

View File

@@ -1,50 +1,8 @@
# Pre-requirements
# Additional lab environments
- Kubernetes concepts
- We will now use new sets of VMs
(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 break 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
<!-- ```open @@SLIDES@@``` -->
]
---
## 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!**)
- Look out for new individual card with connection information!
---

View File

@@ -20,7 +20,7 @@
- We don't endorse Prometheus more or less than any other system
- It's relatively well integrated within the cloud-native ecosystem
- It's relatively well integrated within the Cloud Native ecosystem
- It can be self-hosted (this is useful for tutorials like this)
@@ -182,7 +182,7 @@ We need to:
- Run the *node exporter* on each node (with a Daemon Set)
- Set up a Service Account so that Prometheus can query the Kubernetes API
- Setup a Service Account so that Prometheus can query the Kubernetes API
- Configure the Prometheus server
@@ -250,7 +250,7 @@ class: extra-details
## Explaining all the Helm flags
- `helm upgrade prometheus` → upgrade release "prometheus" to the latest version...
- `helm upgrade prometheus` → upgrade release "prometheus" to the latest version ...
(a "release" is a unique name given to an app deployed with Helm)
@@ -288,7 +288,7 @@ class: extra-details
## Querying some metrics
- This is easy... if you are familiar with PromQL
- This is easy ... if you are familiar with PromQL
.exercise[
@@ -433,9 +433,9 @@ class: extra-details
- I/O activity (disk, network), per operation or volume
- Physical/hardware (when applicable): temperature, fan speed...
- Physical/hardware (when applicable): temperature, fan speed ...
- ...and much more!
- ... and much more!
---
@@ -448,7 +448,7 @@ class: extra-details
- RAM breakdown will be different
- active vs inactive memory
- some memory is *shared* between containers, and specially accounted for
- some memory is *shared* between containers, and accounted specially
- I/O activity is also harder to track
@@ -467,11 +467,11 @@ class: extra-details
- Arbitrary metrics related to your application and business
- System performance: request latency, error rate...
- System performance: request latency, error rate ...
- Volume information: number of rows in database, message queue size...
- Volume information: number of rows in database, message queue size ...
- Business data: inventory, items sold, revenue...
- Business data: inventory, items sold, revenue ...
---
@@ -541,8 +541,8 @@ class: extra-details
- That person can set up queries and dashboards for the rest of the team
- It's a little bit like knowing how to optimize SQL queries, Dockerfiles...
- It's a little bit likeknowing how to optimize SQL queries, Dockerfiles ...
Don't panic if you don't know these tools!
...But make sure at least one person in your team is on it 💯
... But make sure at least one person in your team is on it 💯

View File

@@ -86,17 +86,17 @@ Each pod is assigned a QoS class (visible in `status.qosClass`).
- 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 considered "Guaranteed"
- if all containers in a pod have *(limits=requests)*, QoS is "Guaranteed"
- If requests &lt; 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
- otherwise, it might be killed / evicted if the node gets overloaded
- if at least one container has *(requests&lt;limits)*, QoS is considered "Burstable"
- if at least one container has *(requests&lt;limits)*, QoS is "Burstable"
- If a pod doesn't have any request nor limit, QoS is considered "BestEffort"
- If a pod doesn't have any request nor limit, QoS is "BestEffort"
---
@@ -400,7 +400,7 @@ These quotas will apply to the namespace where the ResourceQuota is created.
- Quotas can be created with a YAML definition
- ...Or with the `kubectl create quota` command
- ... Or with the `kubectl create quota` command
- Example:
```bash

View File

@@ -90,4 +90,4 @@
- For a longer list, check the Kubernetes documentation:
<br/>
it has a great guide to [pick the right solution](https://kubernetes.io/docs/setup/#production-environment) to set up Kubernetes.
it has a great guide to [pick the right solution](https://kubernetes.io/docs/setup/pick-right-solution/) to set up Kubernetes.

View File

@@ -20,7 +20,7 @@ with a cloud provider
## EKS (the hard way)
- [Read the doc](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html)
- [Read the doc](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html)
- Create service roles, VPCs, and a bunch of other oddities
@@ -69,8 +69,6 @@ with a cloud provider
eksctl get clusters
```
.footnote[Note: the AWS documentation has been updated and now includes [eksctl instructions](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html).]
---
## GKE (initial setup)

View File

@@ -18,7 +18,7 @@
## A possible approach
- Since each component of the control plane can be replicated...
- Since each component of the control plane can be replicated ...
- We could set up the control plane outside of the cluster
@@ -39,9 +39,9 @@
- Worst case scenario, we might need to:
- set up a new control plane (outside of the cluster)
- restore a backup from the old control plane
- move the new control plane to the cluster (again)
- This doesn't sound like a great experience
@@ -57,7 +57,7 @@
- The kubelet can also get a list of *static pods* from:
- a directory containing one (or multiple) *manifests*, and/or
- a URL (serving a *manifest*)
- These "manifests" are basically YAML definitions
@@ -100,11 +100,11 @@
## Static pods vs normal pods
- The API only gives us read-only access to static pods
- The API only gives us a read-only access to static pods
- We can `kubectl delete` a static pod...
- We can `kubectl delete` a static pod ...
...But the kubelet will re-mirror it immediately
... But the kubelet will re-mirror it immediately
- Static pods can be selected just like other pods

View File

@@ -1,6 +1,6 @@
## Versions installed
- Kubernetes 1.15.0
- Kubernetes 1.14.2
- Docker Engine 18.09.6
- Docker Compose 1.21.1
@@ -23,7 +23,7 @@ class: extra-details
## Kubernetes and Docker compatibility
- Kubernetes 1.15 validates Docker Engine versions [up to 18.09](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.15.md#dependencies)
- Kubernetes 1.14 validates Docker Engine versions [up to 18.09](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.14.md#external-dependencies)
<br/>
(the latest version when Kubernetes 1.14 was released)

96
slides/kadm-fourdays.yml Normal file
View File

@@ -0,0 +1,96 @@
title: |
Kubernetes
Administrator
Training
chat: "[Slack](https://wework.slack.com/messages/CK6PD2EUF/)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
#chat: "In person!"
gitrepo: github.com/jpetazzo/container.training
slides: http://wwrk-2019-06.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/toc.md
# DAY 1
- - shared/prereqs.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
- shared/composedown.md
- k8s/concepts-k8s.md
- shared/declarative.md
- k8s/declarative.md
- k8s/kubenet.md
- - k8s/kubectlget.md
- k8s/setup-k8s.md
- k8s/kubectlrun.md
- k8s/deploymentslideshow.md
- - k8s/kubectlexpose.md
- k8s/shippingimages.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
- - k8s/rollout.md
- k8s/healthchecks.md
- k8s/healthchecks-more.md
- k8s/owners-and-dependents.md
# DAY 2
- - k8s/kubectlproxy.md
- k8s/localkubeconfig.md
- k8s/accessinternal.md
- k8s/dashboard.md
- k8s/ingress.md
- - k8s/volumes.md
- k8s/configuration.md
- k8s/logs-cli.md
- k8s/logs-centralized.md
- - k8s/namespaces.md
- k8s/kustomize.md
- k8s/helm.md
#- k8s/create-chart.md
- k8s/prometheus.md
- - k8s/resource-limits.md
- k8s/metrics-server.md
- k8s/cluster-sizing.md
- k8s/horizontal-pod-autoscaler.md
# DAY 3
- - k8s/prereqs-admin.md
- k8s/architecture.md
- k8s/dmuc.md
- - k8s/multinode.md
- k8s/cni.md
- - k8s/setup-managed.md
- k8s/setup-selfhosted.md
- k8s/staticpods.md
- - k8s/apilb.md
- k8s/cluster-upgrade.md
- k8s/cluster-backup.md
- k8s/cloud-controller-manager.md
#DAY 4
- - k8s/authn-authz.md
- k8s/csr-api.md
- k8s/openid-connect.md
- - k8s/control-plane-auth.md
- k8s/bootstrap.md
- k8s/netpol.md
- k8s/podsecuritypolicy.md
- - k8s/extending-api.md
- k8s/operators.md
- k8s/operators-design.md
- - k8s/statefulsets.md
- k8s/local-persistent-volumes.md
- k8s/portworx.md
- - k8s/lastwords-admin.md
- k8s/links.md
- shared/thankyou.md

68
slides/kadm-twoday.yml Normal file
View File

@@ -0,0 +1,68 @@
title: |
Kubernetes
for administrators
and operators
#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
# DAY 1
- - k8s/prereqs-admin.md
- k8s/architecture.md
- k8s/deploymentslideshow.md
- k8s/dmuc.md
- - k8s/multinode.md
- k8s/cni.md
- - k8s/apilb.md
- k8s/setup-managed.md
- k8s/setup-selfhosted.md
- k8s/cluster-upgrade.md
- k8s/staticpods.md
- - k8s/cluster-backup.md
- k8s/cloud-controller-manager.md
- k8s/healthchecks.md
- k8s/healthchecks-more.md
# DAY 2
- - k8s/logs-cli.md
- k8s/logs-centralized.md
- k8s/authn-authz.md
- k8s/csr-api.md
- - k8s/openid-connect.md
- k8s/control-plane-auth.md
###- k8s/bootstrap.md
- k8s/netpol.md
- k8s/podsecuritypolicy.md
- - k8s/resource-limits.md
- k8s/metrics-server.md
- k8s/cluster-sizing.md
- k8s/horizontal-pod-autoscaler.md
- - k8s/prometheus.md
- k8s/extending-api.md
- k8s/operators.md
###- k8s/operators-design.md
# CONCLUSION
- - k8s/lastwords-admin.md
- k8s/links.md
- shared/thankyou.md
- |
# (All content after this slide is bonus material)
# EXTRA
- - k8s/volumes.md
- k8s/configuration.md
- k8s/statefulsets.md
- k8s/local-persistent-volumes.md
- k8s/portworx.md

View File

@@ -1,11 +1,35 @@
## Intros
- Hello! I', Jérôme ([@jpetazzo](https://twitter.com/jpetazzo), Enix SAS)
- Hello! We are:
- The training will run from 9am to 6pm
- .emoji[👷🏻‍♀️] AJ ([@s0ulshake](https://twitter.com/s0ulshake), Travis CI)
- There will be a lunch break (and coffee breaks!)
- .emoji[🐳] Jérôme ([@jpetazzo](https://twitter.com/jpetazzo), Tiny Shell Script LLC)
- Feel free to interrupt for questions at any time
- *Especially when you see full screen container pictures!*
- Live feedback, questions, help: @@CHAT@@
(Let's make sure right now that we all are on that channel!)
---
## Logistics
Training schedule for the 4 days:
|||
|-------------------|--------------------|
| 9:00am | Start of training
| 10:30am → 11:00am | Break
| 12:30pm → 1:30pm | Lunch
| 3:00pm → 3:30pm | Break
| 5:00pm | End of training
- Lunch will be catered
- During the breaks, the instructors will be available for Q&A
- Make sure to hydrate / caffeinate / stretch out your limbs :)

View File

@@ -50,6 +50,8 @@ Misattributed to Benjamin Franklin
- Go to @@SLIDES@@ to view these slides
- Join the chat room: @@CHAT@@
<!-- ```open @@SLIDES@@``` -->
]

View File

@@ -80,7 +80,7 @@ and displays aggregated logs.
- DockerCoins is *not* a cryptocurrency
(the only common points are "randomness," "hashing," and "coins" in the name)
(the only common points are "randomness", "hashing", and "coins" in the name)
---

View File

@@ -11,5 +11,11 @@ class: title, in-person
@@TITLE@@<br/></br>
.footnote[
**Be kind to the WiFi!**<br/>
<!-- *Use the 5G network.* -->
*Don't use your hotspot.*<br/>
*Don't stream videos or download big files during the workshop[.](https://www.youtube.com/watch?v=h16zyxiwDLY)*<br/>
*Thank you!*
**Slides: @@SLIDES@@**
]