Compare commits

..

34 Commits

Author SHA1 Message Date
Jerome Petazzoni
a72148d51a fix-redirects.sh: adding forced redirect 2020-04-07 16:58:03 -05:00
Jerome Petazzoni
2d2246db4e Fix FIXME :) 2019-05-28 09:49:17 -05:00
Jerome Petazzoni
5872100101 Merge branch 'master' into wwrk-2019-05 2019-05-28 05:43:22 -05:00
Jerome Petazzoni
8b98058f22 Add note about Helm first deploy fail 2019-05-27 15:51:57 -05:00
Jerome Petazzoni
33f5a6b2ed merge 2019-05-26 14:17:03 -05:00
Jerome Petazzoni
56f2083a2b Fix Ingress section 2019-05-26 14:16:25 -05:00
Jerome Petazzoni
48bd2a98bd merge 2019-05-25 21:45:06 -05:00
Jerome Petazzoni
918fa2091d Merge branch 'master' into wwrk-2019-05 2019-05-25 21:43:27 -05:00
Jerome Petazzoni
e56ab48070 Fixup title 2019-05-25 21:18:09 -05:00
Jerome Petazzoni
50ad11a697 Merge branch 'master' into wwrk-2019-05 2019-05-25 21:14:21 -05:00
Jerome Petazzoni
5447b187ac Update final slides 2019-05-25 20:51:53 -05:00
Jerome Petazzoni
d2a91c27c1 Break down each day into 4 parts 2019-05-25 20:44:50 -05:00
Jerome Petazzoni
a8605a9316 Adapt ingress section to wek8s 2019-05-25 20:31:06 -05:00
Jerome Petazzoni
25e2f8eca8 merge 2019-05-25 19:44:59 -05:00
Jerome Petazzoni
a6bd6a94e8 Add extended chapter on Helm in wek8s context 2019-05-25 17:21:38 -05:00
Jerome Petazzoni
8650209381 Merge branch 'master' into wwrk-2019-05 2019-05-25 13:56:32 -05:00
Jerome Petazzoni
b0aeac555d Add a short blurb about wek8s and security 2019-05-24 22:13:52 -05:00
Jerome Petazzoni
f3b9340528 Add note about Slack channel 2019-05-24 22:02:11 -05:00
Jerome Petazzoni
927484bcbc Merge branch 'master' into wwrk-2019-05 2019-05-24 21:40:32 -05:00
Jerome Petazzoni
8d0c568f5a Merge branch 'master' into wwrk-2019-05 2019-05-24 20:28:46 -05:00
Jerome Petazzoni
53c466e6ed Fix AWS role name 2019-05-24 20:21:03 -05:00
Jerome Petazzoni
9b130861ea Add #connecting-to-wek8s anchor 2019-05-24 19:49:39 -05:00
Jerome Petazzoni
b28ed0bbfc Merge branch 'master' into wwrk-2019-05 2019-05-24 19:43:26 -05:00
Jerome Petazzoni
8672a11c3b Add wek8s basic info + show how to connect 2019-05-24 18:12:45 -05:00
Jerome Petazzoni
65647d5882 Merge branch 'master' into wwrk-2019-05 2019-05-24 16:21:17 -05:00
Jerome Petazzoni
1bc7415c54 Improve transition between Docker and Kubernetes section 2019-05-24 16:12:26 -05:00
Jerome Petazzoni
2fdede72f1 Merge branch 'master' into wwrk-2019-05 2019-05-24 15:44:05 -05:00
Jerome Petazzoni
70c91b121c Add Slack URL 2019-05-24 15:37:41 -05:00
Jerome Petazzoni
a7833a75b4 Setup redirect 2019-05-24 12:49:44 -05:00
Jerome Petazzoni
31e23477d6 Prepare cards and scripts 2019-05-24 12:12:54 -05:00
Jerome Petazzoni
4ba9d5e82e Merge branch 'master' into wwrk-2019-05 2019-05-23 23:15:44 -05:00
Jerome Petazzoni
7ddda3456c Remove Kustomize (we'll put more emphasis on Helm) 2019-05-23 22:39:04 -05:00
Jerome Petazzoni
747f7a07d4 Merge branch 'master' into wwrk-2019-05 2019-05-23 22:35:35 -05:00
Jerome Petazzoni
faf7e1af42 WWRK NYC 2019-05-23 17:36:19 -05:00
100 changed files with 2394 additions and 2695 deletions

View File

@@ -1,21 +0,0 @@
apiVersion: enterprises.upmc.com/v1
kind: ElasticsearchCluster
metadata:
name: es
spec:
kibana:
image: docker.elastic.co/kibana/kibana-oss:6.1.3
image-pull-policy: Always
cerebro:
image: upmcenterprises/cerebro:0.7.2
image-pull-policy: Always
elastic-search-image: upmcenterprises/docker-elasticsearch-kubernetes:6.1.3_0
image-pull-policy: Always
client-node-replicas: 2
master-node-replicas: 3
data-node-replicas: 3
network-host: 0.0.0.0
use-ssl: false
data-volume-size: 10Gi
java-options: "-Xms512m -Xmx512m"

View File

@@ -1,94 +0,0 @@
# This is mirrored from https://github.com/upmc-enterprises/elasticsearch-operator/blob/master/example/controller.yaml but using the elasticsearch-operator namespace instead of operator
---
apiVersion: v1
kind: Namespace
metadata:
name: elasticsearch-operator
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: elasticsearch-operator
namespace: elasticsearch-operator
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: elasticsearch-operator
rules:
- apiGroups: ["extensions"]
resources: ["deployments", "replicasets", "daemonsets"]
verbs: ["create", "get", "update", "delete", "list"]
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["create", "get", "update", "delete", "list"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "create", "delete", "deletecollection"]
- apiGroups: [""]
resources: ["persistentvolumes", "persistentvolumeclaims", "services", "secrets", "configmaps"]
verbs: ["create", "get", "update", "delete", "list"]
- apiGroups: ["batch"]
resources: ["cronjobs", "jobs"]
verbs: ["create", "get", "deletecollection", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["list", "get", "watch"]
- apiGroups: ["apps"]
resources: ["statefulsets", "deployments"]
verbs: ["*"]
- apiGroups: ["enterprises.upmc.com"]
resources: ["elasticsearchclusters"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: elasticsearch-operator
namespace: elasticsearch-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: elasticsearch-operator
subjects:
- kind: ServiceAccount
name: elasticsearch-operator
namespace: elasticsearch-operator
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: elasticsearch-operator
namespace: elasticsearch-operator
spec:
replicas: 1
template:
metadata:
labels:
name: elasticsearch-operator
spec:
containers:
- name: operator
image: upmcenterprises/elasticsearch-operator:0.2.0
imagePullPolicy: Always
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 8000
name: http
livenessProbe:
httpGet:
path: /live
port: 8000
initialDelaySeconds: 10
timeoutSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 10
timeoutSeconds: 5
serviceAccount: elasticsearch-operator

View File

@@ -1,167 +0,0 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: kube-system
labels:
k8s-app: filebeat
data:
filebeat.yml: |-
filebeat.config:
inputs:
# Mounted `filebeat-inputs` configmap:
path: ${path.config}/inputs.d/*.yml
# Reload inputs configs as they change:
reload.enabled: false
modules:
path: ${path.config}/modules.d/*.yml
# Reload module configs as they change:
reload.enabled: false
# To enable hints based autodiscover, remove `filebeat.config.inputs` configuration and uncomment this:
#filebeat.autodiscover:
# providers:
# - type: kubernetes
# hints.enabled: true
processors:
- add_cloud_metadata:
cloud.id: ${ELASTIC_CLOUD_ID}
cloud.auth: ${ELASTIC_CLOUD_AUTH}
output.elasticsearch:
hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
username: ${ELASTICSEARCH_USERNAME}
password: ${ELASTICSEARCH_PASSWORD}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-inputs
namespace: kube-system
labels:
k8s-app: filebeat
data:
kubernetes.yml: |-
- type: docker
containers.ids:
- "*"
processors:
- add_kubernetes_metadata:
in_cluster: true
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
spec:
template:
metadata:
labels:
k8s-app: filebeat
spec:
serviceAccountName: filebeat
terminationGracePeriodSeconds: 30
containers:
- name: filebeat
image: docker.elastic.co/beats/filebeat-oss:7.0.1
args: [
"-c", "/etc/filebeat.yml",
"-e",
]
env:
- name: ELASTICSEARCH_HOST
value: elasticsearch-es.default.svc.cluster.local
- name: ELASTICSEARCH_PORT
value: "9200"
- name: ELASTICSEARCH_USERNAME
value: elastic
- name: ELASTICSEARCH_PASSWORD
value: changeme
- name: ELASTIC_CLOUD_ID
value:
- name: ELASTIC_CLOUD_AUTH
value:
securityContext:
runAsUser: 0
# If using Red Hat OpenShift uncomment this:
#privileged: true
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
volumeMounts:
- name: config
mountPath: /etc/filebeat.yml
readOnly: true
subPath: filebeat.yml
- name: inputs
mountPath: /usr/share/filebeat/inputs.d
readOnly: true
- name: data
mountPath: /usr/share/filebeat/data
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: config
configMap:
defaultMode: 0600
name: filebeat-config
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: inputs
configMap:
defaultMode: 0600
name: filebeat-inputs
# data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart
- name: data
hostPath:
path: /var/lib/filebeat-data
type: DirectoryOrCreate
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: kube-system
roleRef:
kind: ClusterRole
name: filebeat
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: filebeat
labels:
k8s-app: filebeat
rules:
- apiGroups: [""] # "" indicates the core API group
resources:
- namespaces
- pods
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
---

View File

@@ -1,110 +0,0 @@
# This is a local copy of:
# https://github.com/rancher/local-path-provisioner/blob/master/deploy/local-path-storage.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: local-path-storage
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: local-path-provisioner-role
namespace: local-path-storage
rules:
- apiGroups: [""]
resources: ["nodes", "persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["endpoints", "persistentvolumes", "pods"]
verbs: ["*"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: local-path-provisioner-bind
namespace: local-path-storage
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: local-path-provisioner-role
subjects:
- kind: ServiceAccount
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: local-path-provisioner
namespace: local-path-storage
spec:
replicas: 1
selector:
matchLabels:
app: local-path-provisioner
template:
metadata:
labels:
app: local-path-provisioner
spec:
serviceAccountName: local-path-provisioner-service-account
containers:
- name: local-path-provisioner
image: rancher/local-path-provisioner:v0.0.8
imagePullPolicy: Always
command:
- local-path-provisioner
- --debug
- start
- --config
- /etc/config/config.json
volumeMounts:
- name: config-volume
mountPath: /etc/config/
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumes:
- name: config-volume
configMap:
name: local-path-config
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: local-path-storage
data:
config.json: |-
{
"nodePathMap":[
{
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/opt/local-path-provisioner"]
}
]
}

21
k8s/malicious-pod.yaml Normal file
View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Pod
metadata:
name: malicious
spec:
volumes:
- name: slash
hostPath:
path: /
containers:
- image: alpine
name: alpine
securityContext:
privileged: true
command:
- sleep
- "1000000000"
volumeMounts:
- name: slash
mountPath: /hostfs
restartPolicy: Never

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

@@ -1,28 +0,0 @@
# Number of VMs per cluster
clustersize: 1
# The hostname of each node will be clusterprefix + a number
clusterprefix: dmuc
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.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

@@ -1,28 +0,0 @@
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: kubenet
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.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

@@ -1,28 +0,0 @@
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: kuberouter
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.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

@@ -1,28 +0,0 @@
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: test
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.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

@@ -7,7 +7,7 @@ clustersize: 1
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

@@ -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

@@ -1,30 +0,0 @@
# customize your cluster size, your cards template, and the versions
# Number of VMs per cluster
clustersize: 5
# The hostname of each node will be clusterprefix + a number
clusterprefix: node
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: Letter
# 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: test
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.18.0
machine_version: 0.13.0
# Password used to connect with the "docker user"
docker_user_password: training

View File

@@ -1,31 +0,0 @@
# 3 nodes for k8s 101 workshops
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: node
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: Letter
# 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

@@ -1,30 +0,0 @@
# This file is passed by trainer-cli to scripts/ips-txt-to-html.py
# Number of VMs per cluster
clustersize: 3
# The hostname of each node will be clusterprefix + a number
clusterprefix: node
# Jinja2 template to use to generate ready-to-cut cards
cards_template: cards.html
# Use "Letter" in the US, and "A4" everywhere else
paper_size: Letter
# 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.22.0
machine_version: 0.15.0
# Password used to connect with the "docker user"
docker_user_password: training

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

@@ -1,218 +0,0 @@
{# 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 -%}
{%- 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;
{% 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;
}
/*
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 %}
<div>
<p>{{ intro }}</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>
<tr><td class="logpass">{{ docker_user_password }}</td></tr>
</table>
</p>
<p>
{{ listhead }}
<table>
{% for node in cluster %}
<tr>
<td>{{ clusterprefix }}{{ loop.index }}:</td>
<td>{{ node }}</td>
</tr>
{% endfor %}
</table>
</p>
<p>
{{ slides_are_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,113 @@
{# Feel free to customize or override anything in there! #}
{%- set url = "http://wwrk-2019-05.container.training/" -%}
{%- set pagesize = 9 -%}
{%- set workshop_name = "training session" -%}
{%- if clustersize == 1 -%}
{%- set cluster_or_machine = "Docker 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 cluster_or_machine = "Kubernetes 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>
{% endfor %}
</body>
</html>

View File

@@ -1,4 +1,7 @@
FROM alpine:3.9
RUN apk add --no-cache entr py-pip git
FROM alpine
RUN apk update
RUN apk add entr
RUN apk add py-pip
RUN apk add git
COPY requirements.txt .
RUN pip install -r requirements.txt

View File

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

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

@@ -150,7 +150,7 @@ Different deployments will use different underlying technologies.
* Ad-hoc deployments can use a master-less discovery protocol
like avahi to register and discover services.
* It is also possible to do one-shot reconfiguration of the
ambassadors. It is slightly less dynamic but has far fewer
ambassadors. It is slightly less dynamic but has much less
requirements.
* Ambassadors can be used in addition to, or instead of, overlay networks.

View File

@@ -98,13 +98,13 @@ COPY prometheus.conf /etc
* Allows arbitrary customization and complex configuration files.
* Requires writing a configuration file. (Obviously!)
* Requires to write a configuration file. (Obviously!)
* Requires building an image to start the service.
* Requires to build an image to start the service.
* Requires rebuilding the image to reconfigure the service.
* Requires to rebuild the image to reconfigure the service.
* Requires rebuilding the image to upgrade the service.
* Requires to rebuild the image to upgrade the service.
* Configured images can be stored in registries.
@@ -132,11 +132,11 @@ docker run -v appconfig:/etc/appconfig myapp
* Allows arbitrary customization and complex configuration files.
* Requires creating a volume for each different configuration.
* Requires to create a volume for each different configuration.
* Services with identical configurations can use the same volume.
* Doesn't require building / rebuilding an image when upgrading / reconfiguring.
* Doesn't require to build / rebuild an image when upgrading / reconfiguring.
* Configuration can be generated or edited through another container.
@@ -198,4 +198,4 @@ E.g.:
- read the secret on stdin when the service starts,
- pass the secret using an API endpoint.
- pass the secret using an API endpoint.

View File

@@ -257,7 +257,7 @@ $ docker kill 068 57ad
The `stop` and `kill` commands can take multiple container IDs.
Those containers will be terminated immediately (without
the 10-second delay).
the 10 seconds delay).
Let's check that our containers don't show up anymore:

View File

@@ -222,16 +222,16 @@ CMD ["hello world"]
Let's build it:
```bash
$ docker build -t myfiglet .
$ docker build -t figlet .
...
Successfully built 6e0b6a048a07
Successfully tagged myfiglet:latest
Successfully tagged figlet:latest
```
Run it without parameters:
```bash
$ docker run myfiglet
$ docker run figlet
_ _ _ _
| | | | | | | | |
| | _ | | | | __ __ ,_ | | __|
@@ -246,7 +246,7 @@ $ docker run myfiglet
Now let's pass extra arguments to the image.
```bash
$ docker run myfiglet hola mundo
$ docker run figlet hola mundo
_ _
| | | | |
| | __ | | __, _ _ _ _ _ __| __
@@ -262,13 +262,13 @@ We overrode `CMD` but still used `ENTRYPOINT`.
What if we want to run a shell in our container?
We cannot just do `docker run myfiglet bash` because
We cannot just do `docker run figlet bash` because
that would just tell figlet to display the word "bash."
We use the `--entrypoint` parameter:
```bash
$ docker run -it --entrypoint bash myfiglet
$ docker run -it --entrypoint bash figlet
root@6027e44e2955:/#
```

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.
---
@@ -164,7 +164,7 @@ We're not aware of anyone using it directly (i.e. outside of Kubernetes).
* Run each container in a lightweight virtual machine.
* Requires running on bare metal *or* with nested virtualization.
* Requires to run on bare metal *or* with nested virtualization.
---

View File

@@ -474,7 +474,7 @@ When creating a network, extra options can be provided.
* `--ip-range` (in CIDR notation) indicates the subnet to allocate from.
* `--aux-address` allows specifying a list of reserved addresses (which won't be allocated to containers).
* `--aux-address` allows to specify a list of reserved addresses (which won't be allocated to containers).
---
@@ -556,7 +556,7 @@ General idea:
* So far, we have specified which network to use when starting the container.
* The Docker Engine also allows connecting and disconnecting while the container is running.
* The Docker Engine also allows to connect and disconnect while the container runs.
* This feature is exposed through the Docker API, and through two Docker CLI commands:

View File

@@ -259,7 +259,7 @@ bash: figlet: command not found
* work on project
* when done, shut down VM
* when done, shutdown VM
* next time we need to work on project, restart VM as we left it
@@ -281,7 +281,7 @@ bash: figlet: command not found
* work on project
* when done, shut down container
* when done, shutdown container
* next time we need to work on project, start a new container

View File

@@ -156,7 +156,7 @@ Option 3:
* Use a *volume* to mount local files into the container
* Make changes locally
* Changes are reflected in the container
* Changes are reflected into the container
---
@@ -176,7 +176,7 @@ $ docker run -d -v $(pwd):/src -P namer
* `namer` is the name of the image we will run.
* We don't specify a command to run because it is already set in the Dockerfile via `CMD`.
* We don't specify a command to run because it is already set in the Dockerfile.
Note: on Windows, replace `$(pwd)` with `%cd%` (or `${pwd}` if you use PowerShell).
@@ -192,7 +192,7 @@ The flag structure is:
[host-path]:[container-path]:[rw|ro]
```
* `[host-path]` and `[container-path]` are created if they don't exist.
* If `[host-path]` or `[container-path]` doesn't exist it is created.
* You can control the write status of the volume with the `ro` and
`rw` options.
@@ -255,13 +255,13 @@ color: red;
* Volumes are *not* copying or synchronizing files between the host and the container.
* Volumes are *bind mounts*: a kernel mechanism associating one path with another.
* Volumes are *bind mounts*: a kernel mechanism associating a path to another.
* Bind mounts are *kind of* similar to symbolic links, but at a very different level.
* Changes made on the host or on the container will be visible on the other side.
(Under the hood, it's the same file anyway.)
(Since under the hood, it's the same file on both anyway.)
---
@@ -273,7 +273,7 @@ by Chad Fowler, where he explains the concept of immutable infrastructure.)*
--
* Let's majorly mess up our container.
* Let's mess up majorly with our container.
(Remove files or whatever.)
@@ -319,7 +319,7 @@ and *canary deployments*.
<br/>
Use the `-v` flag to mount our source code inside the container.
3. Edit the source code outside the container, using familiar tools.
3. Edit the source code outside the containers, using regular tools.
<br/>
(vim, emacs, textmate...)

View File

@@ -86,13 +86,13 @@ class: extra-details, deep-dive
- the `unshare()` system call.
- The Linux tool `unshare` allows doing that from a shell.
- The Linux tool `unshare` allows to do that from a shell.
- A new process can re-use none / all / some of the namespaces of its parent.
- It is possible to "enter" a namespace with the `setns()` system call.
- The Linux tool `nsenter` allows doing that from a shell.
- The Linux tool `nsenter` allows to do that from a shell.
---
@@ -138,11 +138,11 @@ class: extra-details, deep-dive
- gethostname / sethostname
- Allows setting a custom hostname for a container.
- Allows to set a custom hostname for a container.
- That's (mostly) it!
- Also allows setting the NIS domain.
- Also allows to set the NIS domain.
(If you don't know what a NIS domain is, you don't have to worry about it!)
@@ -392,13 +392,13 @@ class: extra-details
- Processes can have their own root fs (à la chroot).
- Processes can also have "private" mounts. This allows:
- Processes can also have "private" mounts. This allows to:
- isolating `/tmp` (per user, per service...)
- isolate `/tmp` (per user, per service...)
- masking `/proc`, `/sys` (for processes that don't need them)
- mask `/proc`, `/sys` (for processes that don't need them)
- mounting remote filesystems or sensitive data,
- mount remote filesystems or sensitive data,
<br/>but make it visible only for allowed processes
- Mounts can be totally private, or shared.
@@ -570,7 +570,7 @@ Check `man 2 unshare` and `man pid_namespaces` if you want more details.
## User namespace
- Allows mapping UID/GID; e.g.:
- Allows to map UID/GID; e.g.:
- UID 0→1999 in container C1 is mapped to UID 10000→11999 on host
- UID 0→1999 in container C2 is mapped to UID 12000→13999 on host
@@ -947,7 +947,7 @@ Killed
(i.e., "this group of process used X seconds of CPU0 and Y seconds of CPU1".)
- Allows setting relative weights used by the scheduler.
- Allows to set relative weights used by the scheduler.
---
@@ -1101,9 +1101,9 @@ See `man capabilities` for the full list and details.
- Original seccomp only allows `read()`, `write()`, `exit()`, `sigreturn()`.
- The seccomp-bpf extension allows specifying custom filters with BPF rules.
- The seccomp-bpf extension allows to specify custom filters with BPF rules.
- This allows filtering by syscall, and by parameter.
- This allows to filter by syscall, and by parameter.
- BPF code can perform arbitrarily complex checks, quickly, and safely.

View File

@@ -119,7 +119,7 @@ Now, how are things for our IAAS provider?
- Solution: *migrate* VMs and shutdown empty servers
(e.g. combine two hypervisors with 40% load into 80%+0%,
<br/>and shut down the one at 0%)
<br/>and shutdown the one at 0%)
---
@@ -127,7 +127,7 @@ Now, how are things for our IAAS provider?
How do we implement this?
- Shut down empty hosts (but keep some spare capacity)
- Shutdown empty hosts (but keep some spare capacity)
- Start hosts again when capacity gets low
@@ -175,7 +175,7 @@ In practice, these goals often conflict.
- 16 GB RAM, 8 cores, 1 TB disk
- Each week, your team requests:
- Each week, your team asks:
- one VM with X RAM, Y CPU, Z disk

View File

@@ -72,7 +72,7 @@
- For memory usage, the mechanism is part of the *cgroup* subsystem.
- This subsystem allows limiting the memory for a process or a group of processes.
- This subsystem allows to limit the memory for a process or a group of processes.
- A container engine leverages these mechanisms to limit memory for a container.

View File

@@ -45,13 +45,13 @@ individual Docker VM.*
- The Docker Engine is a daemon (a service running in the background).
- This daemon manages containers, the same way that a hypervisor manages VMs.
- This daemon manages containers, the same way that an hypervisor manages VMs.
- We interact with the Docker Engine by using the Docker CLI.
- The Docker CLI and the Docker Engine communicate through an API.
- There are many other programs and client libraries which use that API.
- There are many other programs, and many client libraries, to use that API.
---

View File

@@ -33,13 +33,13 @@ Docker volumes can be used to achieve many things, including:
* Sharing a *single file* between the host and a container.
* Using remote storage and custom storage with *volume drivers*.
* Using remote storage and custom storage with "volume drivers".
---
## Volumes are special directories in a container
Volumes can be declared in two different ways:
Volumes can be declared in two different ways.
* Within a `Dockerfile`, with a `VOLUME` instruction.
@@ -163,7 +163,7 @@ Volumes are not anchored to a specific path.
* Volumes are used with the `-v` option.
* When a host path does not contain a `/`, it is considered a volume name.
* When a host path does not contain a /, it is considered to be a volume name.
Let's start a web server using the two previous volumes.
@@ -189,7 +189,7 @@ $ curl localhost:1234
* In this example, we will run a text editor in the other container.
(But this could be an FTP server, a WebDAV server, a Git receiver...)
(But this could be a FTP server, a WebDAV server, a Git receiver...)
Let's start another container using the `webapps` volume.

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,15 +1,13 @@
title: |
Containers,
Docker,
Kubernetes
(Partie 1)
Introduction
to Containers
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
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/
slides: http://container.training/
exclude:
- self-paced
@@ -20,43 +18,46 @@ chapters:
- containers/intro.md
- shared/about-slides.md
- shared/toc.md
# DAY 1
- - containers/Docker_Overview.md
#- containers/Docker_History.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/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/Publishing_To_Docker_Hub.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/Connecting_Containers_With_Links.md
- containers/Ambassadors.md
- - containers/Local_Development_Workflow.md
- containers/Windows_Containers.md
- containers/Working_With_Volumes.md
- containers/Compose_For_Dev_Stacks.md
- containers/Exercise_Composefile.md
- - containers/Advanced_Dockerfiles.md
- - containers/Docker_Machine.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
- - containers/Namespaces_Cgroups.md
- containers/Copy_On_Write.md
#- containers/Containers_From_Scratch.md
- - containers/Container_Engines.md
#- containers/Ecosystem.md
- containers/Orchestration_Overview.md
- shared/thankyou.md
- containers/links.md

View File

@@ -0,0 +1,63 @@
title: |
Introduction
to Containers
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://container.training/
exclude:
- in-person
chapters:
- shared/title.md
# - shared/logistics.md
- containers/intro.md
- shared/about-slides.md
- shared/toc.md
- - containers/Docker_Overview.md
- containers/Docker_History.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/Multi_Stage_Builds.md
- containers/Publishing_To_Docker_Hub.md
- containers/Dockerfile_Tips.md
- containers/Exercise_Dockerfile_Advanced.md
- - containers/Naming_And_Inspecting.md
- containers/Labels.md
- containers/Getting_Inside.md
- - containers/Container_Networking_Basics.md
- containers/Network_Drivers.md
- containers/Container_Network_Model.md
#- containers/Connecting_Containers_With_Links.md
- containers/Ambassadors.md
- - containers/Local_Development_Workflow.md
- containers/Windows_Containers.md
- containers/Working_With_Volumes.md
- containers/Compose_For_Dev_Stacks.md
- containers/Exercise_Composefile.md
- containers/Docker_Machine.md
- - containers/Advanced_Dockerfiles.md
- containers/Application_Configuration.md
- containers/Logging.md
- containers/Resource_Limits.md
- - containers/Namespaces_Cgroups.md
- containers/Copy_On_Write.md
#- containers/Containers_From_Scratch.md
- - containers/Container_Engines.md
- containers/Ecosystem.md
- containers/Orchestration_Overview.md
- shared/thankyou.md
- containers/links.md

View File

@@ -356,9 +356,9 @@ We demonstrated *update* and *watch* semantics.
- we create a Deployment object
- the Deployment controller notices it, and creates a ReplicaSet
- the Deployment controller notices it, creates a ReplicaSet
- the ReplicaSet controller notices the ReplicaSet, and creates a Pod
- the ReplicaSet controller notices it, creates a Pod
---

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

@@ -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)
---
@@ -83,7 +83,7 @@ The list includes the following providers:
## Audience questions
- What kind of clouds are you using/planning to use?
- What kind of clouds are you using / planning to use?
- What kind of details would you like to see in this section?
@@ -105,7 +105,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 +123,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

@@ -26,7 +26,7 @@
The reference plugins are available [here].
Look in each plugin's directory for its documentation.
Look into each plugin's directory for its documentation.
[here]: https://github.com/containernetworking/plugins/tree/master/plugins
@@ -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
@@ -100,7 +98,7 @@ class: extra-details
- CNI_NETNS: path to network namespace file
- CNI_IFNAME: what the network interface should be named
- CNI_IFNAME: how the network interface should be named
- The network configuration must be provided to the plugin on stdin
@@ -190,16 +188,12 @@ 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
---
## Logging into the new cluster
.exercise[
@@ -227,7 +221,7 @@ class: extra-details
- It is similar to the one we used with the `kubenet` cluster
- The API server is started with `--allow-privileged`
(because we will start kube-router in privileged pods)
- The controller manager is started with extra flags too:
@@ -260,7 +254,7 @@ class: extra-details
---
## The kube-router DaemonSet
## The kube-router DaemonSet
- In the same directory, there is a `kuberouter.yaml` file
@@ -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
@@ -603,13 +599,13 @@ done
## Updating kube-router configuration
- We need to pass two command-line flags to the kube-router process
- We need to add two command-line flags to the kube-router process
.exercise[
- Edit the `kuberouter.yaml` file
- Add the following flags to the kube-router arguments:
- Add the following flags to the kube-router arguments,:
```
- "--peer-router-ips=`X.X.X.X`"
- "--peer-router-asns=64512"

View File

@@ -177,7 +177,7 @@ class: extra-details
- In that case, there is no "master node"
*For this reason, it is more accurate to say "control plane" rather than "master."*
*For this reason, it is more accurate to say "control plane" rather than "master".*
---

View File

@@ -22,7 +22,7 @@
- There are many ways to pass configuration to code running in a container:
- baking it into a custom image
- baking it in a custom image
- command-line arguments
@@ -125,7 +125,7 @@
- We can also use a mechanism called the *downward API*
- The downward API allows exposing pod or container information
- The downward API allows to expose pod or container information
- either through special files (we won't show that for now)
@@ -436,7 +436,7 @@ We should see connections served by Google, and others served by IBM.
- We are going to store the port number in a configmap
- Then we will expose that configmap as a container environment variable
- Then we will expose that configmap to a container environment variable
---

View File

@@ -34,7 +34,7 @@
```bash
while read kind name; do
kubectl get -o yaml $kind $name > dockercoins/templates/$name-$kind.yaml
kubectl get -o yaml --export $kind $name > dockercoins/templates/$name-$kind.yaml
done <<EOF
deployment worker
deployment hasher

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
--
@@ -371,7 +371,7 @@ But ... why do these pods (in particular, the *new* ones) have this `app=rng` la
- Bottom line: if we remove our `app=rng` label ...
... The pod "disappears" for its parent, which re-creates another pod to replace it
... The pod "diseappears" for its parent, which re-creates another pod to replace it
---

View File

@@ -153,7 +153,5 @@ The dashboard will then ask you which authentication you want to use.
--
- It introduces new failure modes
(for instance, if you try to apply YAML from a link that's no longer valid)
- It introduces new failure modes (like if you try to apply yaml from a link that's no longer valid)

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
@@ -584,7 +584,7 @@ Our pod is still `Pending`. 🤔
Which is normal: it needs to be *scheduled*.
(i.e., something needs to decide which node it should go on.)
(i.e., something needs to decide on which node it should go.)
---
@@ -658,7 +658,7 @@ class: extra-details
- This is actually how the scheduler works!
- It watches pods, makes scheduling decisions, and creates Binding objects
- It watches pods, takes scheduling decisions, creates Binding objects
---
@@ -686,7 +686,7 @@ We should see the `Welcome to nginx!` page.
## Exposing our Deployment
- We can now create a Service associated with this Deployment
- We can now create a Service associated to this Deployment
.exercise[
@@ -711,11 +711,11 @@ This won't work. We need kube-proxy to enable internal communication.
## Starting kube-proxy
- kube-proxy also needs to connect to the API server
- kube-proxy also needs to connect to API server
- It can work with the `--master` flag
(although that will be deprecated in the future)
(even though that will be deprecated in the future)
.exercise[
@@ -832,6 +832,6 @@ class: extra-details
- By default, the API server expects to be running directly on the nodes
(it could be as a bare process, or in a container/pod using the host network)
(it could be as a bare process, or in a container/pod using host network)
- ... And it expects to be listening on port 6443 with TLS

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

@@ -1,393 +0,0 @@
## Questions to ask before adding healthchecks
- Do we want liveness, readiness, both?
(sometimes, we can use the same check, but with different failure thresholds)
- Do we have existing HTTP endpoints that we can use?
- Do we need to add new endpoints, or perhaps use something else?
- Are our healthchecks likely to use resources and/or slow down the app?
- Do they depend on additional services?
(this can be particularly tricky, see next slide)
---
## Healthchecks and dependencies
- A good healthcheck should always indicate the health of the service itself
- It should not be affected by the state of the service's dependencies
- Example: a web server requiring a database connection to operate
(make sure that the healthcheck can report "OK" even if the database is down;
<br/>
because it won't help us to restart the web server if the issue is with the DB!)
- Example: a microservice calling other microservices
- Example: a worker process
(these will generally require minor code changes to report health)
---
## Adding healthchecks to an app
- Let's add healthchecks to DockerCoins!
- We will examine the questions of the previous slide
- Then we will review each component individually to add healthchecks
---
## Liveness, readiness, or both?
- To answer that question, we need to see the app run for a while
- Do we get temporary, recoverable glitches?
→ then use readiness
- Or do we get hard lock-ups requiring a restart?
→ then use liveness
- In the case of DockerCoins, we don't know yet!
- Let's pick liveness
---
## Do we have HTTP endpoints that we can use?
- Each of the 3 web services (hasher, rng, webui) has a trivial route on `/`
- These routes:
- don't seem to perform anything complex or expensive
- don't seem to call other services
- Perfect!
(See next slides for individual details)
---
- [hasher.rb](https://github.com/jpetazzo/container.training/blob/master/dockercoins/hasher/hasher.rb)
```ruby
get '/' do
"HASHER running on #{Socket.gethostname}\n"
end
```
- [rng.py](https://github.com/jpetazzo/container.training/blob/master/dockercoins/rng/rng.py)
```python
@app.route("/")
def index():
return "RNG running on {}\n".format(hostname)
```
- [webui.js](https://github.com/jpetazzo/container.training/blob/master/dockercoins/webui/webui.js)
```javascript
app.get('/', function (req, res) {
res.redirect('/index.html');
});
```
---
## Running DockerCoins
- We will run DockerCoins in a new, separate namespace
- We will use a set of YAML manifests and pre-built images
- We will add our new liveness probe to the YAML of the `rng` DaemonSet
- Then, we will deploy the application
---
## Creating a new namespace
- This will make sure that we don't collide / conflict with previous exercises
.exercise[
- Create the yellow namespace:
```bash
kubectl create namespace yellow
```
- Switch to that namespace:
```bash
kns yellow
```
]
---
## Retrieving DockerCoins manifests
- All the manifests that we need are on a convenient repository:
https://github.com/jpetazzo/kubercoins
.exercise[
- Clone that repository:
```bash
cd ~
git clone https://github.com/jpetazzo/kubercoins
```
- Change directory to the repository:
```bash
cd kubercoins
```
]
---
## A simple HTTP liveness probe
This is what our liveness probe should look like:
```yaml
containers:
- name: ...
image: ...
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 5
```
This will give 30 seconds to the service to start. (Way more than necessary!)
<br/>
It will run the probe every 5 seconds.
<br/>
It will use the default timeout (1 second).
<br/>
It will use the default failure threshold (3 failed attempts = dead).
<br/>
It will use the default success threshold (1 successful attempt = alive).
---
## Adding the liveness probe
- Let's add the liveness probe, then deploy DockerCoins
.exercise[
- Edit `rng-daemonset.yaml` and add the liveness probe
```bash
vim rng-daemonset.yaml
```
- Load the YAML for all the resources of DockerCoins:
```bash
kubectl apply -f .
```
]
---
## Testing the liveness probe
- The rng service needs 100ms to process a request
(because it is single-threaded and sleeps 0.1s in each request)
- The probe timeout is set to 1 second
- If we send more than 10 requests per second per backend, it will break
- Let's generate traffic and see what happens!
.exercise[
- Get the ClusterIP address of the rng service:
```bash
kubectl get svc rng
```
]
---
## Monitoring the rng service
- Each command below will show us what's happening on a different level
.exercise[
- In one window, monitor cluster events:
```bash
kubectl get events -w
```
- In another window, monitor the response time of rng:
```bash
httping `<ClusterIP>`
```
- In another window, monitor pods status:
```bash
kubectl get pods -w
```
]
---
## Generating traffic
- Let's use `ab` to send concurrent requests to rng
.exercise[
- In yet another window, generate traffic:
```bash
ab -c 10 -n 1000 http://`<ClusterIP>`/1
```
- Experiment with higher values of `-c` and see what happens
]
- The `-c` parameter indicates the number of concurrent requests
- The final `/1` is important to generate actual traffic
(otherwise we would use the ping endpoint, which doesn't sleep 0.1s per request)
---
## Discussion
- Above a given threshold, the liveness probe starts failing
(about 10 concurrent requests per backend should be plenty enough)
- When the liveness probe fails 3 times in a row, the container is restarted
- During the restart, there is *less* capacity available
- ... Meaning that the other backends are likely to timeout as well
- ... Eventually causing all backends to be restarted
- ... And each fresh backend gets restarted, too
- This goes on until the load goes down, or we add capacity
*This wouldn't be a good healthcheck in a real application!*
---
## Better healthchecks
- 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
(but it would still be an imperfect solution)
- A possible combination:
- readiness check with a short timeout / low failure threshold
- liveness check with a longer timeout / higher failure treshold
---
## Healthchecks for redis
- A liveness probe is enough
(it's not useful to remove a backend from rotation when it's the only one)
- We could use an exec probe running `redis-cli ping`
---
class: extra-details
## Exec probes and zombies
- When using exec probes, we should make sure that we have a *zombie reaper*
🤔🧐🧟 Wait, what?
- When a process terminates, its parent must call `wait()`/`waitpid()`
(this is how the parent process retrieves the child's exit status)
- In the meantime, the process is in *zombie* state
(the process state will show as `Z` in `ps`, `top` ...)
- 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
- OK, but how does that affect us?
---
class: extra-details
## PID 1 in containers
- On ordinary systems, PID 1 (`/sbin/init`) has logic to reap processes
- In containers, PID 1 is typically our application process
(e.g. Apache, the JVM, NGINX, Redis ...)
- These *do not* take care of reaping orphans
- If we use exec probes, we need to add a process reaper
- We can add [tini](https://github.com/krallin/tini) to our images
- Or [share the PID namespace between containers of a pod](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/)
(and have gcr.io/pause take care of the reaping)
---
## Healthchecks for worker
- Readiness isn't useful
(because worker isn't a backend for a service)
- Liveness may help us restart a broken worker, but how can we check it?
- Embedding an HTTP server is an option
(but it has a high potential for unwanted side effects and false positives)
- Using a "lease" file can be relatively easy:
- touch a file during each iteration of the main loop
- check the timestamp of that file from an exec probe
- Writing logs (and checking them from the probe) also works

View File

@@ -1,4 +1,4 @@
# Healthchecks
# Healthchecks (extra material)
- Kubernetes provides two kinds of healthchecks: liveness and readiness
@@ -108,7 +108,7 @@
(as opposed to merely started)
- Containers in a broken state get killed and restarted
- Containers in a broken state gets killed and restarted
(instead of serving errors or timeouts)

View File

@@ -158,7 +158,7 @@ Where do these `--set` options come from?
]
The chart's metadata includes a URL to the project's home page.
The chart's metadata includes an URL to the project's home page.
(Sometimes it conveniently points to the documentation for the chart.)

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)
---
@@ -34,7 +34,7 @@
`TargetNumOfPods = ceil(sum(CurrentPodsCPUUtilization) / Target)`
- It scales the related object up/down to this target number of pods
- It scales up/down the related object to this target number of pods
---
@@ -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

@@ -88,7 +88,7 @@
- the control loop watches over ingress resources, and configures the LB accordingly
- Step 2: set up DNS
- Step 2: setup DNS
- associate DNS entries with the load balancer address
@@ -126,7 +126,7 @@
- We could use pods specifying `hostPort: 80`
... but with most CNI plugins, this [doesn't work or requires additional setup](https://github.com/kubernetes/kubernetes/issues/23920)
... but with most CNI plugins, this [doesn't work or require additional setup](https://github.com/kubernetes/kubernetes/issues/23920)
- We could use a `NodePort` service
@@ -142,7 +142,7 @@
(sometimes called sandbox or network sandbox)
- An IP address is assigned to the pod
- An IP address is associated to the pod
- This IP address is routed/connected to the cluster network
@@ -239,7 +239,7 @@ class: extra-details
- an error condition on the node
<br/>
(for instance: "disk full," do not start new pods here!)
(for instance: "disk full", do not start new pods here!)
- The `effect` can be:
@@ -501,11 +501,11 @@ spec:
(as long as it has access to the cluster subnet)
- This allows the use of external (hardware, physical machines...) load balancers
- This allows to use external (hardware, physical machines...) load balancers
- Annotations can encode special features
(rate-limiting, A/B testing, session stickiness, etc.)
(rate-limiting, A/B testing, session stickiness, etc.)
---

View File

@@ -81,7 +81,7 @@ Under the hood: `kube-proxy` is using a userland proxy and a bunch of `iptables`
.exercise[
- In another window, watch the pods (to see when they are created):
- In another window, watch the pods (to see when they will be created):
```bash
kubectl get pods -w
```

View File

@@ -132,7 +132,7 @@ class: extra-details
- short (e.g. `no`, `svc`, `deploy`)
- Some resources do not have a short name
- Some resources do not have a short names
- `Endpoints` only have a plural form
@@ -466,4 +466,4 @@ class: extra-details
- For more details, see [KEP-0009] or the [node controller documentation]
[KEP-0009]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/0009-node-heartbeat.md
[node controller documentation]: https://kubernetes.io/docs/concepts/architecture/nodes/#node-controller
[node controller documentation]: https://kubernetes.io/docs/concepts/architecture/nodes/#node-controller

View File

@@ -77,9 +77,9 @@ If we wanted to talk to the API, we would need to:
- This is a great tool to learn and experiment with the Kubernetes API
- ... And for serious uses as well (suitable for one-shot scripts)
- ... And for serious usages as well (suitable for one-shot scripts)
- For unattended use, it's better to create a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
- For unattended use, it is better to create a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
---

View File

@@ -54,7 +54,7 @@
(15 are listed in the Kubernetes documentation)
- Pods have level 3 (IP) connectivity, but *services* are level 4 (TCP or UDP)
- Pods have level 3 (IP) connectivity, but *services* are level 4
(Services map to a single UDP or TCP port; no port ranges or arbitrary IP packets)

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

@@ -70,7 +70,7 @@
- We need to run `ship init` in a new directory
- `ship init` requires a URL to a remote repository containing Kubernetes YAML
- `ship init` requires an URL to a remote repository containing Kubernetes YAML
- It will clone that repository and start a web UI

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

@@ -32,30 +32,6 @@
---
## If we have a dynamic provisioner ...
- The labs in this section assume that we *do not* have a dynamic provisioner
- If we do have one, we need to disable it
.exercise[
- Check if we have a dynamic provisioner:
```bash
kubectl get storageclass
```
- If the output contains a line with `(default)`, run this command:
```bash
kubectl annotate sc storageclass.kubernetes.io/is-default-class- --all
```
- Check again that it is no longer marked as `(default)`
]
---
## Work in a separate namespace
- To avoid conflicts with existing resources, let's create and use a new namespace
@@ -74,7 +50,7 @@
]
.warning[Make sure to call that namespace `orange`: it is hardcoded in the YAML files.]
.warning[Make sure to call that namespace `orange`, because that name is hardcoded in the YAML files.]
---

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`
@@ -75,9 +75,9 @@ Platform:"linux/amd64"}
---
## Preserving the existing `~/.kube/config`
## Moving away the existing `~/.kube/config`
- If you already have a `~/.kube/config` file, rename it
- If you already have a `~/.kube/config` file, move it away
(we are going to overwrite it in the following slides!)
@@ -192,4 +192,4 @@ class: extra-details
]
We can now utilize the cluster exactly as we did before, except that it's remote.
We can now utilize the cluster exactly as we did before, ignoring that it's remote.

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

@@ -8,7 +8,7 @@
- That would require updating the code
- There has to be a better way!
- There as to be a better way!
--
@@ -26,7 +26,7 @@
- We cannot have two resources *of the same kind* with the same name
(but it's OK to have an `rng` service, an `rng` deployment, and an `rng` daemon set)
(but it's OK to have a `rng` service, a `rng` deployment, and a `rng` daemon set)
--

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

@@ -1,356 +0,0 @@
## What does it take to write an operator?
- Writing a quick-and-dirty operator, or a POC/MVP, is easy
- Writing a robust operator is hard
- We will describe the general idea
- We will identify some of the associated challenges
- We will list a few tools that can help us
---
## Top-down vs. bottom-up
- Both approaches are possible
- Let's see what they entail, and their respective pros and cons
---
## Top-down approach
- Start with high-level design (see next slide)
- Pros:
- can yield cleaner design that will be more robust
- Cons:
- must be able to anticipate all the events that might happen
- design will be better only to the extend of what we anticipated
- hard to anticipate if we don't have production experience
---
## High-level design
- What are we solving?
(e.g.: geographic databases backed by PostGIS with Redis caches)
- What are our use-cases, stories?
(e.g.: adding/resizing caches and read replicas; load balancing queries)
- What kind of outage do we want to address?
(e.g.: loss of individual node, pod, volume)
- What are our *non-features*, the things we don't want to address?
(e.g.: loss of datacenter/zone; differentiating between read and write queries;
<br/>
cache invalidation; upgrading to newer major versions of Redis, PostGIS, PostgreSQL)
---
## Low-level design
- What Custom Resource Definitions do we need?
(one, many?)
- How will we store configuration information?
(part of the CRD spec fields, annotations, other?)
- Do we need to store state? If so, where?
- state that is small and doesn't change much can be stored via the Kubernetes API
<br/>
(e.g.: leader information, configuration, credentials)
- things that are big and/or change a lot should go elsewhere
<br/>
(e.g.: metrics, bigger configuration file like GeoIP)
---
class: extra-details
## What can we store via the Kubernetes API?
- The API server stores most Kubernetes resources in etcd
- Etcd is designed for reliability, not for performance
- If our storage needs exceed what etcd can offer, we need to use something else:
- either directly
- or by extending the API server
<br/>(for instance by using the agregation layer, like [metrics server](https://github.com/kubernetes-incubator/metrics-server) does)
---
## Bottom-up approach
- Start with existing Kubernetes resources (Deployment, Stateful Set...)
- Run the system in production
- Add scripts, automation, to facilitate day-to-day operations
- Turn the scripts into an operator
- Pros: simpler to get started; reflects actual use-cases
- Cons: can result in convoluted designs requiring extensive refactor
---
## General idea
- Our operator will watch its CRDs *and associated resources*
- Drawing state diagrams and finite state automata helps a lot
- It's OK if some transitions lead to a big catch-all "human intervention"
- Over time, we will learn about new failure modes and add to these diagrams
- It's OK to start with CRD creation / deletion and prevent any modification
(that's the easy POC/MVP we were talking about)
- *Presentation* and *validation* will help our users
(more on that later)
---
## Challenges
- Reacting to infrastructure disruption can seem hard at first
- Kubernetes gives us a lot of primitives to help:
- Pods and Persistent Volumes will *eventually* recover
- Stateful Sets give us easy ways to "add N copies" of a thing
- The real challenges come with configuration changes
(i.e., what to do when our users update our CRDs)
- Keep in mind that [some] of the [largest] cloud [outages] haven't been caused by [natural catastrophes], or even code bugs, but by configuration changes
[some]: https://www.datacenterdynamics.com/news/gcp-outage-mainone-leaked-google-cloudflare-ip-addresses-china-telecom/
[largest]: https://aws.amazon.com/message/41926/
[outages]: https://aws.amazon.com/message/65648/
[natural catastrophes]: https://www.datacenterknowledge.com/amazon/aws-says-it-s-never-seen-whole-data-center-go-down
---
## Configuration changes
- It is helpful to analyze and understand how Kubernetes controllers work:
- watch resource for modifications
- compare desired state (CRD) and current state
- issue actions to converge state
- Configuration changes will probably require *another* state diagram or FSA
- Again, it's OK to have transitions labeled as "unsupported"
(i.e. reject some modifications because we can't execute them)
---
## Tools
- CoreOS / RedHat Operator Framework
[GitHub](https://github.com/operator-framework)
|
[Blog](https://developers.redhat.com/blog/2018/12/18/introduction-to-the-kubernetes-operator-framework/)
|
[Intro talk](https://www.youtube.com/watch?v=8k_ayO1VRXE)
|
[Deep dive talk](https://www.youtube.com/watch?v=fu7ecA2rXmc)
- Zalando Kubernetes Operator Pythonic Framework (KOPF)
[GitHub](https://github.com/zalando-incubator/kopf)
|
[Docs](https://kopf.readthedocs.io/)
|
[Step-by-step tutorial](https://kopf.readthedocs.io/en/stable/walkthrough/problem/)
- Mesosphere Kubernetes Universal Declarative Operator (KUDO)
[GitHub](https://github.com/kudobuilder/kudo)
|
[Blog](https://mesosphere.com/blog/announcing-maestro-a-declarative-no-code-approach-to-kubernetes-day-2-operators/)
|
[Docs](https://kudo.dev/)
|
[Zookeeper example](https://github.com/kudobuilder/frameworks/tree/master/repo/stable/zookeeper)
---
## Validation
- By default, a CRD is "free form"
(we can put pretty much anything we want in it)
- When creating a CRD, we can provide an OpenAPI v3 schema
([Example](https://github.com/amaizfinance/redis-operator/blob/master/deploy/crds/k8s_v1alpha1_redis_crd.yaml#L34))
- The API server will then validate resources created/edited with this schema
- If we need a stronger validation, we can use a Validating Admission Webhook:
- run an [admission webhook server](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#write-an-admission-webhook-server) to receive validation requests
- register the webhook by creating a [ValidatingWebhookConfiguration](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#configure-admission-webhooks-on-the-fly)
- each time the API server receives a request matching the configuration,
<br/>the request is sent to our server for validation
---
## Presentation
- By default, `kubectl get mycustomresource` won't display much information
(just the name and age of each resource)
- When creating a CRD, we can specify additional columns to print
([Example](https://github.com/amaizfinance/redis-operator/blob/master/deploy/crds/k8s_v1alpha1_redis_crd.yaml#L6),
[Docs](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#additional-printer-columns))
- By default, `kubectl describe mycustomresource` will also be generic
- `kubectl describe` can show events related to our custom resources
(for that, we need to create Event resources, and fill the `involvedObject` field)
- For scalable resources, we can define a `scale` sub-resource
- This will enable the use of `kubectl scale` and other scaling-related operations
---
## About scaling
- It is possible to use the HPA (Horizontal Pod Autoscaler) with CRDs
- But it is not always desirable
- The HPA works very well for homogenous, stateless workloads
- For other workloads, your mileage may vary
- Some systems can scale across multiple dimensions
(for instance: increase number of replicas, or number of shards?)
- If autoscaling is desired, the operator will have to take complex decisions
(example: Zalando's Elasticsearch Operator ([Video](https://www.youtube.com/watch?v=lprE0J0kAq0)))
---
## Versioning
- As our operator evolves over time, we may have to change the CRD
(add, remove, change fields)
- Like every other resource in Kubernetes, [custom resources are versioned](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definition-versioning/
)
- When creating a CRD, we need to specify a *list* of versions
- Versions can be marked as `stored` and/or `served`
---
## Stored version
- Exactly one version has to be marked as the `stored` version
- As the name implies, it is the one that will be stored in etcd
- Resources in storage are never converted automatically
(we need to read and re-write them ourselves)
- Yes, this means that we can have different versions in etcd at any time
- Our code needs to handle all the versions that still exist in storage
---
## Served versions
- By default, the Kubernetes API will serve resources "as-is"
(using their stored version)
- It will assume that all versions are compatible storage-wise
(i.e. that the spec and fields are compatible between versions)
- We can provide [conversion webhooks](https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definition-versioning/#webhook-conversion) to "translate" requests
(the alternative is to upgrade all stored resources and stop serving old versions)
---
## Operator reliability
- Remember that the operator itself must be resilient
(e.g.: the node running it can fail)
- Our operator must be able to restart and recover gracefully
- Do not store state locally
(unless we can reconstruct that state when we restart)
- As indicated earlier, we can use the Kubernetes API to store data:
- in the custom resources themselves
- in other resources' annotations
---
## Beyond CRDs
- CRDs cannot use custom storage (e.g. for time series data)
- CRDs cannot support arbitrary subresources (like logs or exec for Pods)
- CRDs cannot support protobuf (for faster, more efficient communication)
- If we need these things, we can use the [aggregation layer](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/) instead
- The aggregation layer proxies all requests below a specific path to another server
(this is used e.g. by the metrics server)
- [This documentation page](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#choosing-a-method-for-adding-custom-resources) compares the features of CRDs and API aggregation

View File

@@ -1,389 +0,0 @@
# Operators
- Operators are one of the many ways to extend Kubernetes
- We will define operators
- We will see how they work
- We will install a specific operator (for ElasticSearch)
- We will use it to provision an ElasticSearch cluster
---
## What are operators?
*An operator represents **human operational knowledge in software,**
<br/>
to reliably manage an application.
— [CoreOS](https://coreos.com/blog/introducing-operators.html)*
Examples:
- Deploying and configuring replication with MySQL, PostgreSQL ...
- Setting up Elasticsearch, Kafka, RabbitMQ, Zookeeper ...
- Reacting to failures when intervention is needed
- Scaling up and down these systems
---
## What are they made from?
- Operators combine two things:
- Custom Resource Definitions
- controller code watching the corresponding resources and acting upon them
- A given operator can define one or multiple CRDs
- The controller code (control loop) typically runs within the cluster
(running as a Deployment with 1 replica is a common scenario)
- But it could also run elsewhere
(nothing mandates that the code run on the cluster, as long as it has API access)
---
## Why use operators?
- Kubernetes gives us Deployments, StatefulSets, Services ...
- These mechanisms give us building blocks to deploy applications
- They work great for services that are made of *N* identical containers
(like stateless ones)
- They also work great for some stateful applications like Consul, etcd ...
(with the help of highly persistent volumes)
- They're not enough for complex services:
- where different containers have different roles
- where extra steps have to be taken when scaling or replacing containers
---
## Use-cases for operators
- Systems with primary/secondary replication
Examples: MariaDB, MySQL, PostgreSQL, Redis ...
- Systems where different groups of nodes have different roles
Examples: ElasticSearch, MongoDB ...
- Systems with complex dependencies (that are themselves managed with operators)
Examples: Flink or Kafka, which both depend on Zookeeper
---
## More use-cases
- Representing and managing external resources
(Example: [AWS Service Operator](https://operatorhub.io/operator/alpha/aws-service-operator.v0.0.1))
- Managing complex cluster add-ons
(Example: [Istio operator](https://operatorhub.io/operator/beta/istio-operator.0.1.6))
- Deploying and managing our applications' lifecycles
(more on that later)
---
## How operators work
- An operator creates one or more CRDs
(i.e., it creates new "Kinds" of resources on our cluster)
- The operator also runs a *controller* that will watch its resources
- Each time we create/update/delete a resource, the controller is notified
(we could write our own cheap controller with `kubectl get --watch`)
---
## One operator in action
- We will install the UPMC Enterprises ElasticSearch operator
- This operator requires PersistentVolumes
- We will install Rancher's [local path storage provisioner](https://github.com/rancher/local-path-provisioner) to automatically create these
- Then, we will create an ElasticSearch resource
- The operator will detect that resource and provision the cluster
---
## Installing a Persistent Volume provisioner
(This step can be skipped if you already have a dynamic volume provisioner.)
- This provisioner creates Persistent Volumes backed by `hostPath`
(local directories on our nodes)
- It doesn't require anything special ...
- ... But losing a node = losing the volumes on that node!
.exercise[
- Install the local path storage provisioner:
```bash
kubectl apply -f ~/container.training/k8s/local-path-storage.yaml
```
]
---
## Making sure we have a default StorageClass
- The ElasticSearch operator will create StatefulSets
- These StatefulSets will instantiate PersistentVolumeClaims
- These PVCs need to be explicitly associated with a StorageClass
- Or we need to tag a StorageClass to be used as the default one
.exercise[
- List StorageClasses:
```bash
kubectl get storageclasses
```
]
We should see the `local-path` StorageClass.
---
## Setting a default StorageClass
- This is done by adding an annotation to the StorageClass:
`storageclass.kubernetes.io/is-default-class: true`
.exercise[
- Tag the StorageClass so that it's the default one:
```bash
kubectl annotate storageclass local-path \
storageclass.kubernetes.io/is-default-class=true
```
- Check the result:
```bash
kubectl get storageclasses
```
]
Now, the StorageClass should have `(default)` next to its name.
---
## Install the ElasticSearch operator
- The operator needs:
- a Deployment for its controller
- a ServiceAccount, ClusterRole, ClusterRoleBinding for permissions
- a Namespace
- We have grouped all the definitions for these resources in a YAML file
.exercise[
- Install the operator:
```bash
kubectl apply -f ~/container.training/k8s/elasticsearch-operator.yaml
```
]
---
## Wait for the operator to be ready
- Some operators require to create their CRDs separately
- This operator will create its CRD itself
(i.e. the CRD is not listed in the YAML that we applied earlier)
.exercise[
- Wait until the `elasticsearchclusters` CRD shows up:
```bash
kubectl get crds
```
]
---
## Create an ElasticSearch resource
- We can now create a resource with `kind: ElasticsearchCluster`
- The YAML for that resource will specify all the desired parameters:
- how many nodes do we want of each type (client, master, data)
- image to use
- add-ons (kibana, cerebro, ...)
- whether to use TLS or not
- etc.
.exercise[
- Create our ElasticSearch cluster:
```bash
kubectl apply -f ~/container.training/k8s/elasticsearch-cluster.yaml
```
]
---
## Operator in action
- Over the next minutes, the operator will create:
- StatefulSets (one for master nodes, one for data nodes)
- Deployments (for client nodes; and for add-ons like cerebro and kibana)
- Services (for all these pods)
.exercise[
- Wait for all the StatefulSets to be fully up and running:
```bash
kubectl get statefulsets -w
```
]
---
## Connecting to our cluster
- Since connecting directly to the ElasticSearch API is a bit raw,
<br/>we'll connect to the cerebro frontend instead
.exercise[
- Edit the cerebro service to change its type from ClusterIP to NodePort:
```bash
kubectl patch svc cerebro-es -p "spec: { type: NodePort }"
```
- Retrieve the NodePort that was allocated:
```bash
kubectl get svc cerebreo-es
```
- Connect to that port with a browser
]
---
## (Bonus) Setup filebeat
- Let's send some data to our brand new ElasticSearch cluster!
- We'll deploy a filebeat DaemonSet to collect node logs
.exercise[
- Deploy filebeat:
```bash
kubectl apply -f ~/container.training/k8s/filebeat.yaml
```
]
We should see at least one index being created in cerebro.
---
## (Bonus) Access log data with kibana
- Let's expose kibana (by making kibana-es a NodePort too)
- Then access kibana
- We'll need to configure kibana indexes
---
## Deploying our apps with operators
- It is very simple to deploy with `kubectl run` / `kubectl expose`
- We can unlock more features by writing YAML and using `kubectl apply`
- Kustomize or Helm let us deploy in multiple environments
(and adjust/tweak parameters in each environment)
- We can also use an operator to deploy our application
---
## Pros and cons of deploying with operators
- The app definition and configuration is persisted in the Kubernetes API
- Multiple instances of the app can be manipulated with `kubectl get`
- We can add labels, annotations to the app instances
- Our controller can execute custom code for any lifecycle event
- However, we need to write this controller
- We need to be careful about changes
(what happens when the resource `spec` is updated?)
---
## Operators are not magic
- Look at the ElasticSearch resource definition
(`~/container.training/k8s/elasticsearch-cluster.yaml`)
- What should happen if we flip the `use-tls` flag? Twice?
- What should happen if we remove / re-add the kibana or cerebro sections?
- What should happen if we change the number of nodes?
- What if we want different images or parameters for the different nodes?
*Operators can be very powerful, iff we know exactly the scenarios that they can handle.*

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:
@@ -485,7 +586,7 @@ class: extra-details
- Our cluster is currently broken
(we can't create pods in namespaces kube-system, default, ...)
(we can't create pods in kube-system, default, ...)
- We need to either:

View File

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

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[
@@ -340,7 +340,7 @@ container_cpu_usage_seconds_total
- that it's the total used since the container creation
- Since it's a "total," it is an increasing quantity
- Since it's a "total", it is an increasing quantity
(we need to compute the derivative if we want e.g. CPU % over time)
@@ -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 ...
---
@@ -495,7 +495,7 @@ class: extra-details
## Querying labels
- What if we want to get metrics for containers belonging to a pod tagged `worker`?
- What if we want to get metrics for containers belong to pod tagged `worker`?
- The cAdvisor exporter does not give us Kubernetes labels
@@ -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"
---
@@ -392,7 +392,7 @@ These quotas will apply to the namespace where the ResourceQuota is created.
count/roles.rbac.authorization.k8s.io: 10
```
(The `count/` syntax allows limiting arbitrary objects, including CRDs.)
(The `count/` syntax allows to limit arbitrary objects, including CRDs.)
---
@@ -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

@@ -5,9 +5,9 @@
- new pods are created
- old pods are terminated
- ... all at the same time
- if something goes wrong, ¯\\\_(ツ)\_/¯
---
@@ -28,7 +28,7 @@
- there will therefore be up to `maxUnavailable`+`maxSurge` pods being updated
- We have the possibility of rolling back to the previous version
- We have the possibility to rollback to the previous version
<br/>(if the update fails or is unsatisfactory in any way)
---
@@ -212,7 +212,7 @@ class: extra-details
## Checking the dashboard during the bad rollout
If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
If you haven't deployed the Kubernetes dashboard earlier, just skip this slide.
.exercise[
@@ -255,7 +255,7 @@ Note the `3xxxx` port.
```
-->
- Cancel the deployment and wait for the dust to settle:
- Cancel the deployment and wait for the dust to settle down:
```bash
kubectl rollout undo deploy worker
kubectl rollout status deploy worker

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,12 +57,12 @@
- 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
(As produced by `kubectl get pod my-little-pod -o yaml`)
(As produced by `kubectl get pod my-little-pod -o yaml --export`)
---
@@ -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 restart 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,13 +23,13 @@ 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)
- Kubernetes 1.13 only validates Docker Engine versions [up to 18.06](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.13.md#external-dependencies)
- Is it a problem if I use Kubernetes with a "too recent" Docker Engine?
- Is this a problem if I use Kubernetes with a "too recent" Docker Engine?
--

View File

@@ -28,11 +28,11 @@ class: extra-details
<br/>
but it refers to Docker 1.7, which was released in 2015!)
- Docker volumes allow us to share data between containers running on the same host
- Docker volumes allow to share data between containers running on the same host
- Kubernetes volumes allow us to share data between containers in the same pod
- Both Docker and Kubernetes volumes enable access to storage systems
- Both Docker and Kubernetes volumes allow us access to storage systems
- Kubernetes volumes are also used to expose configuration and secrets
@@ -60,7 +60,7 @@ class: extra-details
- correspond to concrete volumes (e.g. on a SAN, EBS, etc.)
- cannot be associated with a Pod directly; but through a Persistent Volume Claim
- cannot be associated to a Pod directly; but through a Persistent Volume Claim
- won't be discussed further in this section

77
slides/kube-fullday.yml Normal file
View File

@@ -0,0 +1,77 @@
title: |
Deploying and Scaling Microservices
with Kubernetes
#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
- - shared/prereqs.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
#- shared/hastyconclusions.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-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
#- k8s/kubectlproxy.md
#- k8s/localkubeconfig.md
#- k8s/accessinternal.md
- k8s/dashboard.md
#- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
- - k8s/rollout.md
- k8s/namespaces.md
#- k8s/kustomize.md
#- k8s/helm.md
#- k8s/create-chart.md
#- k8s/healthchecks.md
- k8s/logs-cli.md
- k8s/logs-centralized.md
#- k8s/netpol.md
#- k8s/authn-authz.md
#- k8s/csr-api.md
#- k8s/podsecuritypolicy.md
#- k8s/ingress.md
#- k8s/gitworkflows.md
- k8s/prometheus.md
#- k8s/volumes.md
#- k8s/build-with-docker.md
#- k8s/build-with-kaniko.md
#- k8s/configuration.md
#- k8s/owners-and-dependents.md
#- k8s/extending-api.md
#- k8s/statefulsets.md
#- k8s/local-persistent-volumes.md
#- k8s/portworx.md
#- k8s/staticpods.md
- - k8s/whatsnext.md
- k8s/links.md
- shared/thankyou.md

77
slides/kube-selfpaced.yml Normal file
View File

@@ -0,0 +1,77 @@
title: |
Deploying and Scaling Microservices
with Docker and Kubernetes
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://container.training/
exclude:
- in-person
chapters:
- shared/title.md
#- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
- shared/composescale.md
- shared/hastyconclusions.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-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
- k8s/kubectlproxy.md
- k8s/localkubeconfig.md
- k8s/accessinternal.md
- k8s/dashboard.md
- - k8s/kubectlscale.md
# - k8s/scalingdockercoins.md
# - shared/hastyconclusions.md
- k8s/daemonset.md
- k8s/rollout.md
- k8s/namespaces.md
- - k8s/kustomize.md
- k8s/helm.md
- k8s/create-chart.md
- k8s/healthchecks.md
- k8s/logs-cli.md
- k8s/logs-centralized.md
- - k8s/netpol.md
- k8s/authn-authz.md
- k8s/csr-api.md
- k8s/podsecuritypolicy.md
- - k8s/ingress.md
- k8s/gitworkflows.md
- k8s/prometheus.md
- - k8s/volumes.md
- k8s/build-with-docker.md
- k8s/build-with-kaniko.md
- k8s/configuration.md
- - k8s/owners-and-dependents.md
- k8s/extending-api.md
- - k8s/statefulsets.md
- k8s/local-persistent-volumes.md
- k8s/portworx.md
- k8s/staticpods.md
- - k8s/whatsnext.md
- k8s/links.md
- shared/thankyou.md

View File

@@ -1,73 +1,77 @@
title: |
Containers,
Docker,
Kubernetes
(Partie 2)
Deploying and Scaling Microservices
with Kubernetes
#chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
chat: "In person!"
gitrepo: github.com/jpetazzo/container.training
slides: http://alfun-2019-06.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 3
- - shared/prereqs.md
- shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
#- shared/hastyconclusions.md
- shared/composedown.md
- k8s/kubectlget.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/kubectlexpose.md
- - k8s/shippingimages.md
#- k8s/buildshiprun-selfhosted.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/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- - k8s/daemonset.md
- k8s/rollout.md
- k8s/namespaces.md
- k8s/kustomize.md
#- k8s/helm.md
#- k8s/create-chart.md
- - k8s/healthchecks.md
- k8s/logs-cli.md
- k8s/logs-centralized.md
- k8s/prometheus.md
- - k8s/authn-authz.md
- k8s/netpol.md
#- k8s/netpol.md
- k8s/authn-authz.md
- k8s/csr-api.md
- k8s/podsecuritypolicy.md
- - k8s/ingress.md
- k8s/statefulsets.md
- k8s/local-persistent-volumes.md
#- k8s/gitworkflows.md
- k8s/prometheus.md
- - k8s/volumes.md
#- k8s/build-with-docker.md
#- k8s/build-with-kaniko.md
- k8s/configuration.md
#- k8s/owners-and-dependents.md
#- k8s/extending-api.md
- - k8s/resource-limits.md
- k8s/metrics-server.md
- k8s/cluster-sizing.md
- k8s/horizontal-pod-autoscaler.md
- - k8s/statefulsets.md
- k8s/local-persistent-volumes.md
- k8s/portworx.md
#- k8s/staticpods.md
- - k8s/whatsnext.md
- k8s/links.md
- shared/thankyou.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 3 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

@@ -14,7 +14,7 @@ done
```
```bash
### FIXME find a way to reset the cluster, maybe?
## FIXME find a way to reset the cluster, maybe?
```
-->
@@ -84,14 +84,14 @@ You will need a Docker ID to use Play-With-Docker.
- Unless instructed, **all commands must be run from the first VM, `node1`**
- We will only check out/copy the code on `node1`
- We will only checkout/copy the code on `node1`
- During normal operations, we do not need access to the other nodes
- If we had to troubleshoot issues, we would use a combination of:
- SSH (to access system logs, daemon status...)
- Docker API (to check running containers and container engine status)
---

View File

@@ -50,6 +50,8 @@ Misattributed to Benjamin Franklin
- Go to @@SLIDES@@ to view these slides
- Join the chat room: @@CHAT@@
<!-- ```open @@SLIDES@@``` -->
]
@@ -88,7 +90,7 @@ class: in-person
## Why don't we run containers locally?
- Installing this stuff can be hard on some machines
- Installing that stuff can be hard on some machines
(32 bits CPU or OS... Laptops without administrator access... etc.)

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)
---
@@ -134,7 +134,7 @@ 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 do not hard-code FQDN in the code, either
- We just connect to a service name, and container-magic does the rest
@@ -173,7 +173,7 @@ class: extra-details
- Compose file version 2+ makes each container reachable through its service name
- Compose file version 1 required "links" sections to accomplish this
- Compose file version 1 did require "links" sections
- Network aliases are automatically namespaced

View File

@@ -8,8 +8,8 @@ class: title, self-paced
class: title, in-person
@@TITLE@@<br/></br>
@@TITLE@@
.footnote[
**Slides: @@SLIDES@@**
]
**Slides
[](https://www.youtube.com/watch?v=h16zyxiwDLY)
@@SLIDES@@**

234
slides/wek8s/connecting.md Normal file
View File

@@ -0,0 +1,234 @@
name: connecting-to-wek8s
## Connecting to wek8s
- Let's see what it entails to connect to one of our wek8s clusters
- We need an account on https://we.okta.com/
(with access to "Dev AWS" environment)
- We need an account on https://quay.io/
(with access to images
[wework/okta-aws](https://quay.io/repository/wework/okta-aws)
and
[wework/wek8s-tools](https://quay.io/repository/wework/wek8s-tools))
- We will obtain AWS credentials through Okta
- Then, we will use these AWS credentials to obtain Kubernetes credentials
(because the wek8s cluster we will connect to is using AWS EKS under the hood)
.warning[These instructions are up-to-date as of May 2019, but may change in the future.]
---
## Pulling okta-aws and wek8s-tools images
- If we are already logged into quay.io, we can skip that step
<br/>
(the images will be pulled automatically when we need them)
- ... But this makes it easier to troubleshoot registry issues
<br/>
(if we get an error *now*, we know where it's coming from)
.exercise[
- Log into quay.io:
```bash
docker login quay.io
```
- Pull both images:
```bash
docker pull quay.io/wework/okta-aws
docker pull quay.io/wework/wek8s-tools:0.3.2
```
]
---
## Obtaining AWS credentials
- We will use okta-aws to obtain our AWS credentials
- For convenience, we will use a pre-built okta-aws container
.warning[If we already have credentials in `~/.aws`, this may overwrite them!]
.exercise[
- Run okta-aws to obtain AWS credentials and store them in `~/.aws`:
```bash
docker run -it --rm -v ~/.aws:/package/.aws quay.io/wework/okta-aws
```
- Select `Dev` environment at the first prompt
- Enter Okta email, password, and MFA code
]
---
## Verifying account and role
The last lines of output of okta-aws will confirm which account we logged into.
For the `Dev` account, this should look like this:
```
Account: 681484253316
Role: AWS-Tech-User
Profile: saml
```
... And a few files have been updated in `~/.aws`, including `~/.aws/credentials`.
Q: How did the container update `~/.aws` on our machine?
A: Because we mounted that directory into the container with `-v`.
---
## Running wek8s-tools
- Two more steps are necessary to obtain Kubernetes cluster credentials
- For simplicity, we are going to use a "Swiss Army Knife" image, wek8s-tools
- This image contains tools to obtain the Kubernetes credentials + many others
(including kubectl, helm, ...)
.exercise[
- Start a container using the the wek8s-tools image:
```bash
docker run --rm -v ~/.aws:/root/.aws -it quay.io/wework/wek8s-tools:0.3.2 sh
```
]
*We are using the `-v` option again, to mount our fresh AWS credentials into this container.*
---
## Generating kubeconfig
- The next step is to generate a kubeconfig file with:
- the address of the wek8s cluster we want to use
- instructions to use the AWS IAM authenticator plugin
- This is done with the `deploy_helper` binary
.exercise[
- Generate the kubeconfig file:
```bash
deploy_helper fetch_reqs --env wek8s-phoenix --namespace k8s-training
```
]
We now have a `~kube/config` file (in the container).
---
## Using the cluster
- Let's get a shell on this cluster!
.exercise[
- Run a one-time Pod with an Alpine container:
```bash
kubectl -n k8s-training run --restart=Never --rm -it test-$RANDOM --image=alpine
```
- Find out the node's IP address:
```bash
apk add curl
curl https://canihazip.com/s
```
- Exit when done
]
---
## Using local tools
.warning[Do not run the commands in this slide! This is not an exercise ☺]
- What if we wanted to use our local tools, instead of the wek8s-tools image?
- First, we would need to install the AWS IAM authenticator plugin
(see [AWS EKS documentation](https://docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html) for instructions)
- Then, we would need to get the kubeconfig file:
```bash
docker run --rm -v ~/.aws:/root/.aws -v ~/.kube-wek8s:/root/.kube \
quay.io/wework/wek8s-tools:0.3.2 \
deploy_helper fetch_reqs --env wek8s-phoenix --namespace k8s-training
```
- This would generate the file `~/.kube-wek8s/config`
---
## Permission issues
.warning[Do not run the commands in this slide! This is not an exercise ☺]
- If you use Docker Desktop (on Windows or macOS), you should be set
- Otherwise (on Linux or Docker Toolbox) you will need to fix permissions:
```bash
chown -R $USER ~/.kube-wek8s
```
---
## Connecting to wek8s with local tools
.warning[Do not run the commands in this slide! This is not an exercise ☺]
- We would need to tell kubectl (and other tools) to use the file we generated:
```bash
export KUBECONFIG=~/.kube-wek8s/config
```
- Then we could do some simple commands to test the connection:
```bash
kubectl get version
kubectl get svc -n default kubernetes
```
---
## Deploying DockerCoins on wek8s
.warning[Do not run the commands in this slide! This is not an exercise ☺]
- We could deploy DockerCoins like this:
```bash
git clone https://github.com/jpetazzo/kubercoins
kubectl -n k8s-training apply -f kubercoins
```
- To access the web UI, we would need an Ingress
(more on that later)
- Rather than applying YAML directly, we would use Helm Charts
(more on that later)

667
slides/wek8s/helm.md Normal file
View File

@@ -0,0 +1,667 @@
# Managing stacks with Helm
- We created our first resources with `kubectl run`, `kubectl expose` ...
- We have also created resources by loading YAML files with `kubectl apply -f`
- For larger stacks, managing thousands of lines of YAML is unreasonable
- These YAML bundles need to be customized with variable parameters
(E.g.: number of replicas, image version to use ...)
- It would be nice to have an organized, versioned collection of bundles
- It would be nice to be able to upgrade/rollback these bundles carefully
- [Helm](https://helm.sh/) is an open source project offering all these things!
---
## Helm concepts
- `helm` is a CLI tool
- `tiller` is its companion server-side component
- A "chart" is an archive containing templatized YAML bundles
- Charts are versioned
- Charts can be stored on private or public repositories
---
## Helm 2 / Helm 3
- Helm 3.0.0-alpha.1 was released May 15th, 2019
- Helm 2 is still the stable version (and will be for a while)
- Helm 3 removes Tiller (which simplifies permission management)
- There are many other smaller changes
(see [Helm release changelog](https://github.com/helm/helm/releases/tag/v3.0.0-alpha.1) for the full list!)
---
## Installing Helm
- If the `helm` CLI is not installed in your environment, install it
.exercise[
- Check if `helm` is installed:
```bash
helm
```
- If it's not installed, run the following command:
```bash
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
```
]
---
## Installing Tiller
- Tiller is composed of a *service* and a *deployment* in the `kube-system` namespace
- They can be managed (installed, upgraded...) with the `helm` CLI
.exercise[
- Deploy Tiller:
```bash
helm init
```
]
If Tiller was already installed, don't worry: this won't break it.
At the end of the install process, you will see:
```
Happy Helming!
```
---
## Fix account permissions
- Helm permission model requires us to tweak permissions
- In a more realistic deployment, you might create per-user or per-team
service accounts, roles, and role bindings
.exercise[
- Grant `cluster-admin` role to `kube-system:default` service account:
```bash
kubectl create clusterrolebinding add-on-cluster-admin \
--clusterrole=cluster-admin --serviceaccount=kube-system:default
```
]
(Defining the exact roles and permissions on your cluster requires
a deeper knowledge of Kubernetes' RBAC model. The command above is
fine for personal and development clusters.)
---
## Repositories
- A repository is a remote server hosting a number of charts
(any HTTP server can be a chart repository)
- Repositories are identified by a local name
- We can add as many repositories as we need
.exercise[
- List the repositories currently available:
```bash
helm repo list
```
]
When we install Helm, it automatically configures a repository called `stable`.
<br/>
(Think of it like "Debian stable", for instance.)
---
## View available charts
- We can view available charts with `helm search` (and an optional keyword)
.exercise[
- View all available charts:
```bash
helm search
```
- View charts related to `prometheus`:
```bash
helm search prometheus
```
]
---
## Viewing installed charts
- Helm keeps track of what we've installed
.exercise[
- List installed Helm charts:
```bash
helm list
```
]
---
## Adding the WeWork repository
- The generic syntax is `helm repo add <nickname> <url>`
- We have a number of charts in Artifactory
- Since Artifactory is password-protected, we need to add `--username`
.exercise[
- Add the WeWork repository:
```bash
helm repo add wework https://wework.jfrog.io/wework/helm/ --username=`jdoe`
```
- When prompted, provide your password
]
---
## Looking at the WeWork repository
- Let's have a look at the charts in this repository
.exercise[
- Search the repository name:
```bash
helm search wework
```
]
---
## What's next?
- We could *install an existing application that has already been packaged*:
`helm install wework/moonbase-climate-control`
(sorry folks, that one doesn't exist [yet](https://disruption.medium.com/))
- We could *create a chart from scratch*:
`helm create my-wonderful-new-app`
(this creates a directory named `my-wonderful-new-app` with a barebones chart)
- We could do something in between: *install an app using a generic chart*
(let's do that!)
---
## The wek8s generic service chart
- There is ~~an app~~ a chart for that!
.exercise[
- Look for `generic service`:
```bash
helm search generic service
```
]
- The one that we want is `wework/wek8s-generic-service`
---
## Inspecting a chart
- Before installing a chart, we can check its description, README, etc.
.exercise[
- Look at all the available information:
```bash
helm inspect wework/wek8s-generic-service
```
(that's way too much information!)
- Look at the chart's description:
```bash
helm inspect chart wework/wek8s-generic-service
```
]
---
## Using the wek8s generic chart
- We are going to download the chart's `values.yaml`
(a file showing all the possible parameters for that chart)
- We are going to set the parameters we need, and discard the ones we don't
- Then we will install DockerCoins using that chart
---
## Dumping the chart's values
- Let's download the chart's `values.yaml`
- Then we will edit it to suit our needs
.exercise[
- Dump the chart's values to a YAML file:
```bash
helm inspect values wework/wek8s-generic-service > values-rng.yaml
```
]
---
## Editing the chart's values
Edit `values-rng.yaml` and keep only this:
```yaml
appName: rng
replicaCount: 1
image:
repository: dockercoins/rng
tag: v0.1
service:
enabled: true
ports:
- port: 80
containerPort: 80
```
---
## Deploying the chart
- We can now install a *release* of the generic service chart using these values
- We will do that in a separate namespace (to avoid colliding with other resources)
.exercise[
- Switch to the `happyhelming` namespace:
```bash
kubectl config set-context --current --namespace=happyhelming
```
- Install the `rng` release:
```bash
helm install wework/wek8s-generic-service --name=rng --values=values-rng.yaml
```
]
Note: Helm will automatically create the namespace if it doesn't exist.
---
## Testing what we did
- If we were directly on the cluster, we could curl the service's ClusterIP
- But we're *not* on the cluster, so we will use `kubectl port-forward`
.exercise[
- Create a port forwarding to access port 80 of Deployment `rng`:
```bash
kubectl port-forward deploy/rng 1234:80 &
```
- Confirm that RNG is running correctly:
```bash
curl localhost:1234
```
- Terminate the port forwarder:
```bash
kill %1
```
]
---
## Deploying the other services
- We need to create the values files for the other services:
- `values-hasher.yaml` → almost identical (just change name and image)
- `values-webui.yaml` → same
- `values-redis.yaml` → same, but adjust port number
- `values-worker.yaml` → same, but we can even remove the `service` part
- Then create all these services, using these YAML files
---
# Exercise — deploying an app with the wek8s Generic chart
.exercise[
- Create the 4 YAML files mentioned previously
- Install 4 Helm releases (one for each YAML file)
- What do we see in the logs of the worker?
]
---
## Troubleshooting
- We should see errors like this:
```
Error -2 connecting to redis:6379. Name does not resolve.
```
- Why?
--
- Hint: `kubectl get services`
--
- Our services are named `redis-service`, `rng-service`, etc.
- Our code connects to `redis`, `rng`, etc.
- We need to drop the extra `-service`
---
## Editing a chart
- To edit a chart, we can push a new version to the repository
- But there is a much simpler and faster way
- We can use Helm to download the chart locally, make changes, apply them
- This also works when creating / developing a chart
(we don't need to push it to the repository to try it out)
---
## Before diving in ...
.warning[Before editing or forking a generic chart like this one ...]
- Have a conversation with the authors of the chart
- Perhaps they can suggest other options, or adapt the chart
- We will edit the chart here, as a learning experience
- It may or may not be the right course of action in the general case!
---
## Download the chart
.exercise[
- Fetch the generic service chart to have a local, editable copy:
```bash
helm fetch wework/wek8s-generic-service --untar
```
- This creates the directory `wek8s-generic-service`
- Have a look!
]
---
## Chart structure
Here is the structure of the directory containing our chart:
```
$ tree wek8s-generic-service/
wek8s-generic-service/
├── Chart.yaml
├── migrations
│ └── ...
├── README.md
├── templates
│ ├── _appContainer.yaml
│ ├── configmaps.yaml
│ ├── ... more YAML ...
│ ├── ... also, some .tpl files ...
│ ├── NOTES.txt
│ ├── ... more more YAML ...
│ └── ... and more more .tpl files
└── values.yaml
```
---
## Explanations
- `Chart.yaml` → chart short descriptipon and metadata
- `README.md` → longer description
- `values.yaml` → the file we downloaded earlier
- `templates/` → files in this directory will be *rendered* when the chart is installed
- after rendering, each file is treated as a Kubernetes resource YAML file
- ... except the ones starting with underscore (these will contain templates)
- ... and except `NOTES.txt`, which is shown at the end of the deployment
Note: file extension doesn't really matter; the leading underscore does.
---
## Templates details
- Helm uses an extension of the Go template package
- This means that the files in `templates/` will be peppered with `{{ ... }}`
- For instance, this is an excerpt of `wek8s-generic-service/templates/service.yaml`:
```yaml
metadata:
name: {{ .Values.appName }}-service
labels:
app: {{ .Values.appName }}
```
- `{{ .Values.appName }}` will be replaced by the `appName` field from the values YAML
- For more details about the templating system, see the [Helm docs](https://helm.sh/docs/chart_template_guide/)
---
## Editing the templates
- Let's remove the trailing `-service` in the service definition
- Then, we will roll out that change
.exercise[
- Edit the file `wek8s-generic-service/templates/service.yaml`
- Remove the `-service` suffix
- Roll out the change to the `redis` release:
```bash
helm upgrade redis wek8s-generic-service
```
]
- We used `upgrade` instead of `install` this time
- We didn't need to pass again the YAML file with the values
---
## Viewing our changes
- Normally, we "fixed" the `redis` service
- The `worker` should now be able to contact `redis`
.exercise[
- Check the logs of the `worker`:
```bash
kubectl logs deploy/worker --tail 10 --follow
```
]
- Alright, now we need to fix `rng`, `hasher`, and `webui` the same way
---
## Fixing the other services
- We don't need to download the chart or edit it again
- We can use the same chart for the other services
.exercise[
- Upgrade `rng`, `hasher`, and `webui` with the updated chart
- Confirm that the `worker` works correctly
(it should say, "X units of work done ...")
]
---
## Extra steps
(If time permits ...)
.exercise[
- Setup a `port-forward` to view the web UI
- Scale the `worker` by updating the `replicaCount`
]
---
## Exposing a web application
- How do we expose the web UI with a proper URL?
- We will need to use an *Ingress*
- More on that later!
---
## If we wanted to submit our changes
- The source of the wek8s-generic-chart is in the following GitHub repository:
https://github.com/WeConnect/WeK8s-charts
(along with many other charts)
---
class: extra-details
## Good to know ...
- If we don't specify `--name` when running `helm install`, a name is generated
(like `wisfhul-elephant` or `nihilist-alligator`)
- If we want to install-or-upgrade, we can use `helm upgrade --install`:
`helm upgrade <name> <chart> --install --values=...`
- If we only want to set a few values, we can use `--set`, for instance:
`helm upgrade redis wke8s-generic-chart --values=... --set=replicaCount=5`
(we can use `--set` multiple times if needed)
.warning[If we specify `--set` without `--values`, it erases all the other values!]
---
class: extra-details
## If the first deployment fails
- If the first deployment of a release fails, it will be in an inconsistent state
- Further attempts to `helm install` or `helm upgrade` will fail
- To fix the problem, two solutions:
- `helm delete --purge` that release
- `helm upgrade --force` that release
- This only applies to the first deployment
(i.e., Helm knows how to recover if a subsequent deployment fails)

342
slides/wek8s/ingress.md Normal file
View File

@@ -0,0 +1,342 @@
# Exposing HTTP services with Ingress resources
- *Services* give us a way to access a pod or a set of pods
- Services can be exposed to the outside world:
- with type `NodePort` (on a port >30000)
- with type `LoadBalancer` (allocating an external load balancer)
- What about HTTP services?
- how can we expose `webui`, `rng`, `hasher`?
- the Kubernetes dashboard?
- a new version of `webui`?
---
## Exposing HTTP services
- If we use `NodePort` services, clients have to specify port numbers
(i.e. http://xxxxx:31234 instead of just http://xxxxx)
- `LoadBalancer` services are nice, but:
- they are not available in all environments
- they often carry an additional cost (e.g. they provision an ELB)
- they require one extra step for DNS integration
<br/>
(waiting for the `LoadBalancer` to be provisioned; then adding it to DNS)
---
## Ingress resources
- Kubernetes API resource (`kubectl get ingress`/`ingresses`/`ing`)
- Designed to expose HTTP services
- Basic features:
- load balancing
- SSL termination
- name-based virtual hosting
- Can also route to different services depending on:
- URI path (e.g. `/api``api-service`, `/static``assets-service`)
- Client headers, including cookies (for A/B testing, canary deployment...)
- and more!
---
## Principle of operation
- Step 1: deploy an *ingress controller*
- ingress controller = load balancer + control loop
- the control loop watches over ingress resources, and configures the LB accordingly
- Step 2: setup DNS
- associate DNS entries with the load balancer address
- Step 3: create *ingress resources*
- the ingress controller picks up these resources and configures the LB
- Step 4: profit!
---
## Ingress in action
- We will deploy the Traefik ingress controller
- this is an arbitrary choice
- maybe motivated by the fact that Traefik releases are named after cheeses
- For DNS, we will use [nip.io](http://nip.io/)
- `*.1.2.3.4.nip.io` resolves to `1.2.3.4`
- We will create ingress resources for various HTTP services
---
## Running Traefik on our cluster
- We provide a YAML file (`k8s/traefik.yaml`) which is essentially the sum of:
- [Traefik's Daemon Set resources](https://github.com/containous/traefik/blob/v1.7/examples/k8s/traefik-ds.yaml) (patched with `hostNetwork` and tolerations)
- [Traefik's RBAC rules](https://github.com/containous/traefik/blob/v1.7/examples/k8s/traefik-rbac.yaml) allowing it to watch necessary API objects
.exercise[
- Apply the YAML:
```bash
kubectl apply -f ~/container.training/k8s/traefik.yaml
```
]
---
## Checking that Traefik runs correctly
- If Traefik started correctly, we now have a web server listening on each node
.exercise[
- Check that Traefik is serving 80/tcp:
```bash
curl localhost
```
]
We should get a `404 page not found` error.
This is normal: we haven't provided any ingress rule yet.
---
## Setting up DNS
- To make our lives easier, we will use [nip.io](http://nip.io)
- Check out `http://webui.A.B.C.D.nip.io`
(replacing A.B.C.D with the IP address of `node1`)
- We should get the same `404 page not found` error
(meaning that our DNS is "set up properly", so to speak!)
---
## Traefik web UI
- Traefik provides a web dashboard
- With the current install method, it's listening on port 8080
.exercise[
- Go to `http://node1:8080` (replacing `node1` with its IP address)
<!-- ```open http://node1:8080``` -->
]
---
## Ingress with the wek8s generic chart
- The wek8s generic chart that we used earlier can generate an Ingress for us
- All we have to do is add a few lines to the YAML file (`values-webui.yaml`)
- ... And update the Helm release `webui` to use these new values
.exercise[
- Add the following snippet to the `values-webui.yaml` file:
```yaml
ingress:
enabled: true
hosts:
- webui.`A.B.C.D`.nip.io
```
(Where `A.B.C.D` is the IP address of node1, that we used earlier)
]
---
## Update the Helm release
- Now, we need to use these new values
- We will use `helm upgrade` to run the templates and apply them to the cluster
.exercise[
- Update the Helm release:
```bash
helm upgrade webui wek8s-generic-service --values=values-webui.yaml
```
]
We should see an Ingress resource appear in the output.
---
## Something's wrong ...
- In the Traefik web UI, at this point, we may see an error
(the backend is highlighted in red)
- What's happening?
- Let's try and find out!
---
## Inspecting the Ingress
- Let's look at the Ingress generated by the generic service chart
.exercise[
- Dump the YAML for the Ingress:
```bash
kubectl get ingress webui-ingress -o yaml
```
]
- Can you see the problem?
--
- It still refers to `webui-service` instead of `webui`!
---
## Fixing the Ingress
- We need to edit the chart (again)
.exercise[
- Find the file defining the Ingress resource
- Make the necessary changes
- Upgrade the `webui` release with the new chart
]
---
## Access the service
- Go back to the browser tab where we were loading webui.A.B.C.D.nip.io
- Hit reload ...
- ... And we should we the web UI for DockerCoins!
---
## Creating an Ingress by hand
If we need to, here is a minimal host-based ingress resource:
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: webui
spec:
rules:
- host: webui.`A.B.C.D`.nip.io
http:
paths:
- path: /
backend:
serviceName: webui
servicePort: 80
```
---
## Using multiple ingress controllers
- You can have multiple ingress controllers active simultaneously
(e.g. Traefik and NGINX)
- You can even have multiple instances of the same controller
(e.g. one for internal, another for external traffic)
- The `kubernetes.io/ingress.class` annotation can be used to tell which one to use
- It's OK if multiple ingress controllers configure the same resource
(it just means that the service will be accessible through multiple paths)
---
## Using Ingress on wek8s
- Each wek8s cluster has a wildcard domain mapped to it
- For instance, phoenix has `*.phoenix.dev.wwrk.co`
- To be reachable from outside, we must use the ingress class `nginx-external`
- This is done through an annotation, like this:
```yaml
ingress:
enabled: true
hosts:
- webui.phoenix.dev.wwrk.co
annotations:
kubernetes.io/ingress.class: nginx-external
```
---
## Extra goodies
- The wek8s generic service chart can also provide:
- TLS
- HTTP Basic Auth
- GRPC with HTTP/2
- For more details, we can look at:
- the `values.yaml` file
- the chart's README
- the file `templates/ingress.yaml` in the chart

19
slides/wek8s/security.md Normal file
View File

@@ -0,0 +1,19 @@
## Security in the context of wek8s
- The wek8s dev clusters have permissive policies
(so that we can easily experiment and try things)
- This means that we need to be particularly careful about unknown sources
- Check the provenance of images, YAML bundles, Helm Charts, etc.:
- does it come from the website / documentation / repository of a trusted vendor?
- is it maintained; how often does it get updates?
- For images:
- is the source (Dockerfile or otherwise) available?
- are they checked by an automated vulnerability scanner?

View File

@@ -0,0 +1,3 @@
## Get back to node1
- From now on, it is recommended to log back into `node1`

View File

@@ -0,0 +1,15 @@
## Differences with wek8s
- We have `cluster-admin` (=`root`) privileges on our clusters
(we can read/write everything)
- This is typical when working on "personal" clusters
(used by a single person, or a very small team)
- But *not* when working on production clusters
- On wek8s clusters, we can't access most *global* resources
(resources that don't belong to namespaces; e.g. nodes)

110
slides/wek8s/whatsnext.md Normal file
View File

@@ -0,0 +1,110 @@
# Next steps
*Alright, how do I get started and containerize my apps?*
--
Suggested containerization checklist:
.checklist[
- write a Dockerfile for one service in one app
- write Dockerfiles for the other (buildable) services
- write a Compose file for that whole app
- make sure that devs are empowered to run the app in containers
- set up automated builds of container images from the code repo
- set up a CI pipeline using these container images
- set up a CD pipeline (for staging/QA) using these images
]
And *then* it is time to look at orchestration!
---
## Local workflow
- Make sure that you have a local Kubernetes cluster
(Docker Desktop, Minikube, microk8s ...)
- Use that cluster early and often
- Regularly try to deploy on a "real" cluster
---
## Isolation
- We did *not* talk about Role-Based Access Control (RBAC)
- We did *not* talk about Network Policies
- We did *not* talk about Pod Security Policies
- We did *not* talk about resource limits, Limit Ranges, Resource Quotas
- You don't need these features when getting started
(your friendly s19e team is here for that)
---
## Stateful services (databases etc.)
- As a first step, it is wiser to keep stateful services *outside* of the cluster
- Exposing them to pods can be done with multiple solutions:
- `ExternalName` services
<br/>
(`redis.blue.svc.cluster.local` will be a `CNAME` record)
- `ClusterIP` services with explicit `Endpoints`
<br/>
(instead of letting Kubernetes generate the endpoints from a selector)
- Ambassador services
<br/>
(application-level proxies that can provide credentials injection and more)
---
## Stateful services (second take)
- If we want to host stateful services on Kubernetes, we can use:
- a storage provider
- persistent volumes, persistent volume claims
- stateful sets
- Good questions to ask:
- what's the *operational cost* of running this service ourselves?
- what do we gain by deploying this stateful service on Kubernetes?
- Relevant sections:
[Volumes](kube-selfpaced.yml.html#toc-volumes)
|
[Stateful Sets](kube-selfpaced.yml.html#toc-stateful-sets)
|
[Persistent Volumes](kube-selfpaced.yml.html#toc-highly-available-persistent-volumes)
- Excellent [blog post](http://www.databasesoup.com/2018/07/should-i-run-postgres-on-kubernetes.html) tackling the question: “Should I run Postgres on Kubernetes?”
---
## Developer experience
*We've put this last, but it's pretty important!*
- How do you on-board a new developer?
- What do they need to install to get a dev stack?
- How does a code change make it from dev to prod?
- How does someone add a component to a stack?
*Mind the gap!*

116
slides/wwrk.yml Normal file
View File

@@ -0,0 +1,116 @@
title: |
Containers,
Docker,
Kubernetes,
WeK8S
chat: "Slack (channel [#k8s-training-may2019](https://wework.slack.com/messages/GJN9CBZLH/))"
gitrepo: github.com/jpetazzo/container.training
slides: http://wwrk-2019-05.container.training/
exclude:
- self-paced
chapters:
- shared/title.md
- logistics.md
- k8s/intro.md
- shared/about-slides.md
- shared/toc.md
- - containers/Training_Environment.md
- containers/First_Containers.md
- containers/Background_Containers.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/Multi_Stage_Builds.md
- containers/Dockerfile_Tips.md
- containers/Exercise_Dockerfile_Advanced.md
- containers/Naming_And_Inspecting.md
- containers/Getting_Inside.md
- - 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/Application_Configuration.md
- containers/Orchestration_Overview.md
- |
# From Docker to Kubernetes
- We are now going to run a demo app made of multiple containers
- We will start by running it on one node, with Compose
- Then we will deploy that application on a Kubernetes cluster
- We will identify performance bottlenecks and scale out that app
(and learn Kubernetes in the process)
---
## Our new environment
- Since a 1-node cluster isn't fun, we will switch to a new environment!
- This environment is a 4-node Kubernetes cluster
- Also, from now on, demos and labs are identified with these gray boxes
.exercise[
- You should run this command:
```bash
echo Hello world
```
]
- 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
- - wek8s/visibility.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
- - k8s/rollout.md
- k8s/setup-k8s.md
- wek8s/connecting.md
- k8s/logs-cli.md
- k8s/dashboard.md
- wek8s/security.md
- - k8s/localkubeconfig.md
- k8s/accessinternal.md
- wek8s/helm.md
- wek8s/ingress.md
- wek8s/switchback.md
- - k8s/prometheus.md
- k8s/volumes.md
- k8s/configuration.md
- k8s/healthchecks.md
- - wek8s/whatsnext.md
- k8s/links.md
- shared/thankyou.md