mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-07-27 00:31:12 +00:00
Merge branch 'master' into 2020-02-enix
This commit is contained in:
160
k8s/dockercoins.yaml
Normal file
160
k8s/dockercoins.yaml
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: hasher
|
||||
name: hasher
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: hasher
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: hasher
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/hasher:v0.1
|
||||
name: hasher
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: hasher
|
||||
name: hasher
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: hasher
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
name: redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- image: redis
|
||||
name: redis
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
name: redis
|
||||
spec:
|
||||
ports:
|
||||
- port: 6379
|
||||
protocol: TCP
|
||||
targetPort: 6379
|
||||
selector:
|
||||
app: redis
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: rng
|
||||
name: rng
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: rng
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: rng
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/rng:v0.1
|
||||
name: rng
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: rng
|
||||
name: rng
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: rng
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: webui
|
||||
name: webui
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: webui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: webui
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/webui:v0.1
|
||||
name: webui
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: webui
|
||||
name: webui
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: webui
|
||||
type: NodePort
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: worker
|
||||
name: worker
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: worker
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: worker
|
||||
spec:
|
||||
containers:
|
||||
- image: dockercoins/worker:v0.1
|
||||
name: worker
|
||||
@@ -7,9 +7,9 @@ workshop.
|
||||
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
Virtualbox, Vagrant and Ansible
|
||||
|
||||
|
||||
- Virtualbox: https://www.virtualbox.org/wiki/Downloads
|
||||
|
||||
- Vagrant: https://www.vagrantup.com/downloads.html
|
||||
@@ -25,7 +25,7 @@ Virtualbox, Vagrant and Ansible
|
||||
|
||||
$ git clone --recursive https://github.com/ansible/ansible.git
|
||||
$ cd ansible
|
||||
$ git checkout stable-2.0.0.1
|
||||
$ git checkout stable-{{ getStableVersionFromAnsibleProject }}
|
||||
$ git submodule update
|
||||
|
||||
- source the setup script to make Ansible available on this terminal session:
|
||||
@@ -38,6 +38,7 @@ Virtualbox, Vagrant and Ansible
|
||||
|
||||
|
||||
## 2. Preparing the environment
|
||||
Change into directory that has your Vagrantfile
|
||||
|
||||
Run the following commands:
|
||||
|
||||
@@ -66,6 +67,14 @@ will reflect inside the instance.
|
||||
|
||||
- Depending on the Vagrant version, `sudo apt-get install bsdtar` may be needed
|
||||
|
||||
- If you get an error like "no Vagrant file found" or you have a file but "cannot open base box" when running `vagrant up`,
|
||||
chances are good you not in the correct directory.
|
||||
Make sure you are in sub directory named "prepare-local". It has all the config files required by ansible, vagrant and virtualbox
|
||||
|
||||
- If you are using Python 3.7, running the ansible-playbook provisioning, see an error like "SyntaxError: invalid syntax" and it mentions
|
||||
the word "async", you need to upgrade your Ansible version to 2.6 or higher to resolve the keyword conflict.
|
||||
https://github.com/ansible/ansible/issues/42105
|
||||
|
||||
- If you get strange Ansible errors about dependencies, try to check your pip
|
||||
version with `pip --version`. The current version is 8.1.1. If your pip is
|
||||
older than this, upgrade it with `sudo pip install --upgrade pip`, restart
|
||||
|
||||
@@ -10,15 +10,21 @@ These tools can help you to create VMs on:
|
||||
|
||||
- [Docker](https://docs.docker.com/engine/installation/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
- [Parallel SSH](https://code.google.com/archive/p/parallel-ssh/) (on a Mac: `brew install pssh`) - the configuration scripts require this
|
||||
- [Parallel SSH](https://code.google.com/archive/p/parallel-ssh/) (on a Mac: `brew install pssh`)
|
||||
|
||||
Depending on the infrastructure that you want to use, you also need to install
|
||||
the Azure CLI, the AWS CLI, or terraform (for OpenStack deployment).
|
||||
|
||||
And if you want to generate printable cards:
|
||||
|
||||
- [pyyaml](https://pypi.python.org/pypi/PyYAML) (on a Mac: `brew install pyyaml`)
|
||||
- [jinja2](https://pypi.python.org/pypi/Jinja2) (on a Mac: `brew install jinja2`)
|
||||
- [pyyaml](https://pypi.python.org/pypi/PyYAML)
|
||||
- [jinja2](https://pypi.python.org/pypi/Jinja2)
|
||||
|
||||
You can install them with pip (perhaps with `pip install --user`, or even use `virtualenv` if that's your thing).
|
||||
|
||||
These require Python 3. If you are on a Mac, see below for specific instructions on setting up
|
||||
Python 3 to be the default Python on a Mac. In particular, if you installed `mosh`, Homebrew
|
||||
may have changed your default Python to Python 2.
|
||||
|
||||
## General Workflow
|
||||
|
||||
@@ -256,3 +262,32 @@ If you don't have `wkhtmltopdf` installed, you will get a warning that it is a m
|
||||
|
||||
- Don't write to bash history in system() in postprep
|
||||
- compose, etc version inconsistent (int vs str)
|
||||
|
||||
## Making sure Python3 is the default (Mac only)
|
||||
|
||||
Check the `/usr/local/bin/python` symlink. It should be pointing to
|
||||
`/usr/local/Cellar/python/3`-something. If it isn't, follow these
|
||||
instructions.
|
||||
|
||||
1) Verify that Python 3 is installed.
|
||||
|
||||
```
|
||||
ls -la /usr/local/Cellar/Python
|
||||
```
|
||||
|
||||
You should see one or more versions of Python 3. If you don't,
|
||||
install it with `brew install python`.
|
||||
|
||||
2) Verify that `python` points to Python3.
|
||||
|
||||
```
|
||||
ls -la /usr/local/bin/python
|
||||
```
|
||||
|
||||
If this points to `/usr/local/Cellar/python@2`, then we'll need to change it.
|
||||
|
||||
```
|
||||
rm /usr/local/bin/python
|
||||
ln -s /usr/local/Cellar/Python/xxxx /usr/local/bin/python
|
||||
# where xxxx is the most recent Python 3 version you saw above
|
||||
```
|
||||
|
||||
@@ -127,11 +127,11 @@ _cmd_kubebins() {
|
||||
set -e
|
||||
cd /usr/local/bin
|
||||
if ! [ -x etcd ]; then
|
||||
curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz \
|
||||
curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.15/etcd-v3.3.15-linux-amd64.tar.gz \
|
||||
| sudo tar --strip-components=1 --wildcards -zx '*/etcd' '*/etcdctl'
|
||||
fi
|
||||
if ! [ -x hyperkube ]; then
|
||||
curl -L https://dl.k8s.io/v1.14.1/kubernetes-server-linux-amd64.tar.gz \
|
||||
curl -L https://dl.k8s.io/v1.16.2/kubernetes-server-linux-amd64.tar.gz \
|
||||
| sudo tar --strip-components=3 -zx kubernetes/server/bin/hyperkube
|
||||
fi
|
||||
if ! [ -x kubelet ]; then
|
||||
@@ -143,7 +143,7 @@ _cmd_kubebins() {
|
||||
sudo mkdir -p /opt/cni/bin
|
||||
cd /opt/cni/bin
|
||||
if ! [ -x bridge ]; then
|
||||
curl -L https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-amd64-v0.7.5.tgz \
|
||||
curl -L https://github.com/containernetworking/plugins/releases/download/v0.7.6/cni-plugins-amd64-v0.7.6.tgz \
|
||||
| sudo tar -zx
|
||||
fi
|
||||
"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
speaker: jpetazzo
|
||||
title: Deploying and scaling applications with Kubernetes
|
||||
attend: https://conferences.oreilly.com/velocity/vl-eu/public/schedule/detail/79109
|
||||
slides: https://velocity-2019-11.container.training/
|
||||
|
||||
- date: 2019-11-13
|
||||
country: fr
|
||||
|
||||
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- in-person
|
||||
|
||||
|
||||
@@ -667,17 +667,12 @@ 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:
|
||||
- There are a few tools to help us with that
|
||||
|
||||
https://github.com/aquasecurity/kubectl-who-can
|
||||
- [kubectl-who-can](https://github.com/aquasecurity/kubectl-who-can) by Aqua Security
|
||||
|
||||
- This is one way to install it:
|
||||
```bash
|
||||
docker run --rm -v /usr/local/bin:/go/bin golang \
|
||||
go get -v github.com/aquasecurity/kubectl-who-can
|
||||
```
|
||||
- [Review Access (aka Rakkess)](https://github.com/corneliusweig/rakkess)
|
||||
|
||||
- This is one way to use it:
|
||||
```bash
|
||||
kubectl-who-can create pods
|
||||
```
|
||||
- Both are available as standalone programs, or as plugins for `kubectl`
|
||||
|
||||
(`kubectl` plugins can be installed and managed with `krew`)
|
||||
|
||||
@@ -15,26 +15,3 @@
|
||||
- `dockercoins/webui:v0.1`
|
||||
|
||||
- `dockercoins/worker:v0.1`
|
||||
|
||||
---
|
||||
|
||||
## Setting `$REGISTRY` and `$TAG`
|
||||
|
||||
- In the upcoming exercises and labs, we use a couple of environment variables:
|
||||
|
||||
- `$REGISTRY` as a prefix to all image names
|
||||
|
||||
- `$TAG` as the image version tag
|
||||
|
||||
- For example, the worker image is `$REGISTRY/worker:$TAG`
|
||||
|
||||
- If you copy-paste the commands in these exercises:
|
||||
|
||||
**make sure that you set `$REGISTRY` and `$TAG` first!**
|
||||
|
||||
- For example:
|
||||
```bash
|
||||
export REGISTRY=dockercoins TAG=v0.1
|
||||
```
|
||||
|
||||
(this will expand `$REGISTRY/worker:$TAG` to `dockercoins/worker:v0.1`)
|
||||
|
||||
@@ -481,13 +481,13 @@ docker run alpine echo hello world
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the file `kubeconfig.kubelet` with `kubectl`:
|
||||
- Create the file `~/.kube/config` with `kubectl`:
|
||||
```bash
|
||||
kubectl --kubeconfig kubeconfig.kubelet config \
|
||||
kubectl config \
|
||||
set-cluster localhost --server http://localhost:8080
|
||||
kubectl --kubeconfig kubeconfig.kubelet config \
|
||||
kubectl config \
|
||||
set-context localhost --cluster localhost
|
||||
kubectl --kubeconfig kubeconfig.kubelet config \
|
||||
kubectl config \
|
||||
use-context localhost
|
||||
```
|
||||
|
||||
@@ -495,19 +495,7 @@ docker run alpine echo hello world
|
||||
|
||||
---
|
||||
|
||||
## All Kubernetes clients can use `kubeconfig`
|
||||
|
||||
- The `kubeconfig.kubelet` file has the same format as e.g. `~/.kubeconfig`
|
||||
|
||||
- All Kubernetes clients can use a similar file
|
||||
|
||||
- The `kubectl config` commands can be used to manipulate these files
|
||||
|
||||
- This highlights that kubelet is a "normal" client of the API server
|
||||
|
||||
---
|
||||
|
||||
## Our `kubeconfig.kubelet` file
|
||||
## Our `~/.kube/config` file
|
||||
|
||||
The file that we generated looks like the one below.
|
||||
|
||||
@@ -533,9 +521,9 @@ clusters:
|
||||
|
||||
.exercise[
|
||||
|
||||
- Start kubelet with that `kubeconfig.kubelet` file:
|
||||
- Start kubelet with that kubeconfig file:
|
||||
```bash
|
||||
kubelet --kubeconfig kubeconfig.kubelet
|
||||
kubelet --kubeconfig ~/.kube/config
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
@@ -1,41 +1,3 @@
|
||||
## 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!
|
||||
@@ -370,24 +332,4 @@ class: extra-details
|
||||
|
||||
(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
|
||||
- Discussion of this in [Video - 10 Ways to Shoot Yourself in the Foot with Kubernetes, #9 Will Surprise You](https://www.youtube.com/watch?v=QKI-JRs2RIE)
|
||||
|
||||
@@ -42,9 +42,11 @@
|
||||
|
||||
- internal corruption (causing all requests to error)
|
||||
|
||||
- If the liveness probe fails *N* consecutive times, the container is killed
|
||||
- Anything where our incident response would be "just restart/reboot it"
|
||||
|
||||
- *N* is the `failureThreshold` (3 by default)
|
||||
.warning[**Do not** use liveness probes for problems that can't be fixed by a restart]
|
||||
|
||||
- Otherwise we just restart our pods for no reason, creating useless load
|
||||
|
||||
---
|
||||
|
||||
@@ -52,7 +54,7 @@
|
||||
|
||||
- Indicates if the container is ready to serve traffic
|
||||
|
||||
- If a container becomes "unready" (let's say busy!) it might be ready again soon
|
||||
- If a container becomes "unready" it might be ready again soon
|
||||
|
||||
- If the readiness probe fails:
|
||||
|
||||
@@ -66,19 +68,79 @@
|
||||
|
||||
## When to use a readiness probe
|
||||
|
||||
- To indicate temporary failures
|
||||
- To indicate failure due to an external cause
|
||||
|
||||
- the application can only service *N* parallel connections
|
||||
- database is down or unreachable
|
||||
|
||||
- the runtime is busy doing garbage collection or initial data load
|
||||
- mandatory auth or other backend service unavailable
|
||||
|
||||
- The container is marked as "not ready" after `failureThreshold` failed attempts
|
||||
- To indicate temporary failure or unavailability
|
||||
|
||||
(3 by default)
|
||||
- application can only service *N* parallel connections
|
||||
|
||||
- It is marked again as "ready" after `successThreshold` successful attempts
|
||||
- runtime is busy doing garbage collection or initial data load
|
||||
|
||||
(1 by default)
|
||||
- For processes that take a long time to start
|
||||
|
||||
(more on that later)
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- If a web server depends on a database to function, and the database is down:
|
||||
|
||||
- the web server's liveness probe should succeed
|
||||
|
||||
- the web server's readiness probe should fail
|
||||
|
||||
- Same thing for any hard dependency (without which the container can't work)
|
||||
|
||||
.warning[**Do not** fail liveness probes for problems that are external to the container]
|
||||
|
||||
---
|
||||
|
||||
## Timing and thresholds
|
||||
|
||||
- Probes are executed at intervals of `periodSeconds` (default: 10)
|
||||
|
||||
- The timeout for a probe is set with `timeoutSeconds` (default: 1)
|
||||
|
||||
.warning[If a probe takes longer than that, it is considered as a FAIL]
|
||||
|
||||
- A probe is considered successful after `successThreshold` successes (default: 1)
|
||||
|
||||
- A probe is considered failing after `failureThreshold` failures (default: 3)
|
||||
|
||||
- A probe can have an `initialDelaySeconds` parameter (default: 0)
|
||||
|
||||
- Kubernetes will wait that amount of time before running the probe for the first time
|
||||
|
||||
(this is important to avoid killing services that take a long time to start)
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Startup probe
|
||||
|
||||
- Kubernetes 1.16 introduces a third type of probe: `startupProbe`
|
||||
|
||||
(it is in `alpha` in Kubernetes 1.16)
|
||||
|
||||
- It can be used to indicate "container not ready *yet*"
|
||||
|
||||
- process is still starting
|
||||
|
||||
- loading external data, priming caches
|
||||
|
||||
- Before Kubernetes 1.16, we had to use the `initialDelaySeconds` parameter
|
||||
|
||||
(available for both liveness and readiness probes)
|
||||
|
||||
- `initialDelaySeconds` is a rigid delay (always wait X before running probes)
|
||||
|
||||
- `startupProbe` works better when a container start time can vary a lot
|
||||
|
||||
---
|
||||
|
||||
@@ -112,10 +174,12 @@
|
||||
|
||||
(instead of serving errors or timeouts)
|
||||
|
||||
- Overloaded backends get removed from load balancer rotation
|
||||
- Unavailable backends get removed from load balancer rotation
|
||||
|
||||
(thus improving response times across the board)
|
||||
|
||||
- If a probe is not defined, it's as if there was an "always successful" probe
|
||||
|
||||
---
|
||||
|
||||
## Example: HTTP probe
|
||||
@@ -165,14 +229,56 @@ If the Redis process becomes unresponsive, it will be killed.
|
||||
|
||||
---
|
||||
|
||||
## Details about liveness and readiness probes
|
||||
## Questions to ask before adding healthchecks
|
||||
|
||||
- Probes are executed at intervals of `periodSeconds` (default: 10)
|
||||
- Do we want liveness, readiness, both?
|
||||
|
||||
- The timeout for a probe is set with `timeoutSeconds` (default: 1)
|
||||
(sometimes, we can use the same check, but with different failure thresholds)
|
||||
|
||||
- A probe is considered successful after `successThreshold` successes (default: 1)
|
||||
- Do we have existing HTTP endpoints that we can use?
|
||||
|
||||
- A probe is considered failing after `failureThreshold` failures (default: 3)
|
||||
- Do we need to add new endpoints, or perhaps use something else?
|
||||
|
||||
- If a probe is not defined, it's as if there was an "always successful" probe
|
||||
- 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
|
||||
|
||||
- Liveness checks should not be influenced by the state of external services
|
||||
|
||||
- All checks should reply quickly (by default, less than 1 second)
|
||||
|
||||
- Otherwise, they are considered to fail
|
||||
|
||||
- This might require to check the health of dependencies asynchronously
|
||||
|
||||
(e.g. if a database or API might be healthy but still take more than
|
||||
1 second to reply, we should check the status asynchronously and report
|
||||
a cached status)
|
||||
|
||||
---
|
||||
|
||||
## Healthchecks for workers
|
||||
|
||||
(In that context, worker = process that doesn't accept connections)
|
||||
|
||||
- Readiness isn't useful
|
||||
|
||||
(because workers aren't backends for a service)
|
||||
|
||||
- Liveness may help us restart a broken worker, but how can we check it?
|
||||
|
||||
- Embedding an HTTP server is a (potentially expensive) option
|
||||
|
||||
- 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
|
||||
|
||||
@@ -153,10 +153,7 @@ pod/pingpong-7c8bbcd9bc-6c9qz 1/1 Running 0 10m
|
||||
kubectl logs deploy/pingpong --tail 1 --follow
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait seq=3```
|
||||
```keys ^C```
|
||||
-->
|
||||
- Leave that command running, so that we can keep an eye on these logs
|
||||
|
||||
]
|
||||
|
||||
@@ -186,6 +183,44 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
---
|
||||
|
||||
## Log streaming
|
||||
|
||||
- Let's look again at the output of `kubectl logs`
|
||||
|
||||
(the one we started before scaling up)
|
||||
|
||||
- `kubectl logs` shows us one line per second
|
||||
|
||||
- We could expect 3 lines per second
|
||||
|
||||
(since we should now have 3 pods running `ping`)
|
||||
|
||||
- Let's try to figure out what's happening!
|
||||
|
||||
---
|
||||
|
||||
## Streaming logs of multiple pods
|
||||
|
||||
- What happens if we restart `kubectl logs`?
|
||||
|
||||
.exercise[
|
||||
|
||||
- Interrupt `kubectl logs` (with Ctrl-C)
|
||||
|
||||
- Restart it:
|
||||
```bash
|
||||
kubectl logs deploy/pingpong --tail 1 --follow
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
`kubectl logs` will warn us that multiple pods were found, and that it's showing us only one of them.
|
||||
|
||||
Let's leave `kubectl logs` running while we keep exploring.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Resilience
|
||||
|
||||
- The *deployment* `pingpong` watches its *replica set*
|
||||
@@ -196,20 +231,12 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
.exercise[
|
||||
|
||||
- In a separate window, list pods, and keep watching them:
|
||||
- In a separate window, watch the list of pods:
|
||||
```bash
|
||||
kubectl get pods -w
|
||||
watch kubectl get pods
|
||||
```
|
||||
|
||||
<!--
|
||||
```wait Running```
|
||||
```keys ^C```
|
||||
```hide kubectl wait deploy pingpong --for condition=available```
|
||||
```keys kubectl delete pod ping```
|
||||
```copypaste pong-..........-.....```
|
||||
-->
|
||||
|
||||
- Destroy a pod:
|
||||
- Destroy the pod currently shown by `kubectl logs`:
|
||||
```
|
||||
kubectl delete pod pingpong-xxxxxxxxxx-yyyyy
|
||||
```
|
||||
@@ -217,6 +244,23 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
---
|
||||
|
||||
## What happened?
|
||||
|
||||
- `kubectl delete pod` terminates the pod gracefully
|
||||
|
||||
(sending it the TERM signal and waiting for it to shutdown)
|
||||
|
||||
- As soon as the pod is in "Terminating" state, the Replica Set replaces it
|
||||
|
||||
- But we can still see the output of the "Terminating" pod in `kubectl logs`
|
||||
|
||||
- Until 30 seconds later, when the grace period expires
|
||||
|
||||
- The pod is then killed, and `kubectl logs` exits
|
||||
|
||||
---
|
||||
|
||||
|
||||
## What if we wanted something different?
|
||||
|
||||
- What if we wanted to start a "one-shot" container that *doesn't* get restarted?
|
||||
@@ -234,6 +278,72 @@ We could! But the *deployment* would notice it right away, and scale back to the
|
||||
|
||||
---
|
||||
|
||||
## Scheduling periodic background work
|
||||
|
||||
- A Cron Job is a job that will be executed at specific intervals
|
||||
|
||||
(the name comes from the traditional cronjobs executed by the UNIX crond)
|
||||
|
||||
- It requires a *schedule*, represented as five space-separated fields:
|
||||
|
||||
- minute [0,59]
|
||||
- hour [0,23]
|
||||
- day of the month [1,31]
|
||||
- month of the year [1,12]
|
||||
- day of the week ([0,6] with 0=Sunday)
|
||||
|
||||
- `*` means "all valid values"; `/N` means "every N"
|
||||
|
||||
- Example: `*/3 * * * *` means "every three minutes"
|
||||
|
||||
---
|
||||
|
||||
## Creating a Cron Job
|
||||
|
||||
- Let's create a simple job to be executed every three minutes
|
||||
|
||||
- Cron Jobs need to terminate, otherwise they'd run forever
|
||||
|
||||
.exercise[
|
||||
|
||||
- Create the Cron Job:
|
||||
```bash
|
||||
kubectl run --schedule="*/3 * * * *" --restart=OnFailure --image=alpine sleep 10
|
||||
```
|
||||
|
||||
- Check the resource that was created:
|
||||
```bash
|
||||
kubectl get cronjobs
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Cron Jobs in action
|
||||
|
||||
- At the specified schedule, the Cron Job will create a Job
|
||||
|
||||
- The Job will create a Pod
|
||||
|
||||
- The Job will make sure that the Pod completes
|
||||
|
||||
(re-creating another one if it fails, for instance if its node fails)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check the Jobs that are created:
|
||||
```bash
|
||||
kubectl get jobs
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(It will take a few minutes before the first job is scheduled.)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## What about that deprecation warning?
|
||||
|
||||
- As we can see from the previous slide, `kubectl run` can do many things
|
||||
|
||||
@@ -66,6 +66,8 @@ Exactly what we need!
|
||||
sudo chmod +x /usr/local/bin/stern
|
||||
```
|
||||
|
||||
- On OS X, just `brew install stern`
|
||||
|
||||
<!-- ##VERSION## -->
|
||||
|
||||
---
|
||||
|
||||
@@ -218,6 +218,18 @@ class: extra-details
|
||||
|
||||
## What's going on?
|
||||
|
||||
- Without the `--network-plugin` flag, kubelet defaults to "no-op" networking
|
||||
|
||||
- It lets the container engine use a default network
|
||||
|
||||
(in that case, we end up with the default Docker bridge)
|
||||
|
||||
- Our pods are running on independent, disconnected, host-local networks
|
||||
|
||||
---
|
||||
|
||||
## What do we need to do?
|
||||
|
||||
- On a normal cluster, kubelet is configured to set up pod networking with CNI plugins
|
||||
|
||||
- This requires:
|
||||
@@ -228,14 +240,6 @@ class: extra-details
|
||||
|
||||
- running kubelet with `--network-plugin=cni`
|
||||
|
||||
- Without the `--network-plugin` flag, kubelet defaults to "no-op" networking
|
||||
|
||||
- It lets the container engine use a default network
|
||||
|
||||
(in that case, we end up with the default Docker bridge)
|
||||
|
||||
- Our pods are running on independent, disconnected, host-local networks
|
||||
|
||||
---
|
||||
|
||||
## Using network plugins
|
||||
@@ -394,7 +398,7 @@ class: extra-details
|
||||
|
||||
- Start kube-proxy:
|
||||
```bash
|
||||
sudo kube-proxy --kubeconfig ~/kubeconfig
|
||||
sudo kube-proxy --kubeconfig ~/.kube/config
|
||||
```
|
||||
|
||||
- Expose our Deployment:
|
||||
|
||||
@@ -11,16 +11,36 @@
|
||||
|
||||
- Deploy everything else:
|
||||
```bash
|
||||
set -u
|
||||
for SERVICE in hasher rng webui worker; do
|
||||
kubectl create deployment $SERVICE --image=$REGISTRY/$SERVICE:$TAG
|
||||
done
|
||||
kubectl create deployment hasher --image=dockercoins/hasher:v0.1
|
||||
kubectl create deployment rng --image=dockercoins/rng:v0.1
|
||||
kubectl create deployment webui --image=dockercoins/webui:v0.1
|
||||
kubectl create deployment worker --image=dockercoins/worker:v0.1
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
class: extra-details
|
||||
|
||||
## Deploying other images
|
||||
|
||||
- If we wanted to deploy images from another registry ...
|
||||
|
||||
- ... Or with a different tag ...
|
||||
|
||||
- ... We could use the following snippet:
|
||||
|
||||
```bash
|
||||
REGISTRY=dockercoins
|
||||
TAG=v0.1
|
||||
for SERVICE in hasher rng webui worker; do
|
||||
kubectl create deployment $SERVICE --image=$REGISTRY/$SERVICE:$TAG
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Is this working?
|
||||
|
||||
- After waiting for the deployment to complete, let's look at the logs!
|
||||
|
||||
@@ -61,32 +61,6 @@
|
||||
|
||||
---
|
||||
|
||||
## Building a new version of the `worker` service
|
||||
|
||||
.warning[
|
||||
Only run these commands if you have built and pushed DockerCoins to a local registry.
|
||||
<br/>
|
||||
If you are using images from the Docker Hub (`dockercoins/worker:v0.1`), skip this.
|
||||
]
|
||||
|
||||
.exercise[
|
||||
|
||||
- Go to the `stacks` directory (`~/container.training/stacks`)
|
||||
|
||||
- Edit `dockercoins/worker/worker.py`; update the first `sleep` line to sleep 1 second
|
||||
|
||||
- Build a new tag and push it to the registry:
|
||||
```bash
|
||||
#export REGISTRY=localhost:3xxxx
|
||||
export TAG=v0.2
|
||||
docker-compose -f dockercoins.yml build
|
||||
docker-compose -f dockercoins.yml push
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Rolling out the new `worker` service
|
||||
|
||||
.exercise[
|
||||
@@ -105,7 +79,7 @@ If you are using images from the Docker Hub (`dockercoins/worker:v0.1`), skip th
|
||||
|
||||
- Update `worker` either with `kubectl edit`, or by running:
|
||||
```bash
|
||||
kubectl set image deploy worker worker=$REGISTRY/worker:$TAG
|
||||
kubectl set image deploy worker worker=dockercoins/worker:v0.2
|
||||
```
|
||||
|
||||
]
|
||||
@@ -146,8 +120,7 @@ That rollout should be pretty quick. What shows in the web UI?
|
||||
|
||||
- Update `worker` by specifying a non-existent image:
|
||||
```bash
|
||||
export TAG=v0.3
|
||||
kubectl set image deploy worker worker=$REGISTRY/worker:$TAG
|
||||
kubectl set image deploy worker worker=dockercoins/worker:v0.3
|
||||
```
|
||||
|
||||
- Check what's going on:
|
||||
@@ -216,27 +189,14 @@ If you didn't deploy the Kubernetes dashboard earlier, just skip this slide.
|
||||
|
||||
.exercise[
|
||||
|
||||
- Check which port the dashboard is on:
|
||||
```bash
|
||||
kubectl -n kube-system get svc socat
|
||||
```
|
||||
- Connect to the dashboard that we deployed earlier
|
||||
|
||||
- Check that we have failures in Deployments, Pods, and Replica Sets
|
||||
|
||||
- Can we see the reason for the failure?
|
||||
|
||||
]
|
||||
|
||||
Note the `3xxxx` port.
|
||||
|
||||
.exercise[
|
||||
|
||||
- Connect to http://oneofournodes:3xxxx/
|
||||
|
||||
<!-- ```open https://node1:3xxxx/``` -->
|
||||
|
||||
]
|
||||
|
||||
--
|
||||
|
||||
- We have failures in Deployments, Pods, and Replica Sets
|
||||
|
||||
---
|
||||
|
||||
## Recovering from a bad rollout
|
||||
@@ -285,7 +245,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: worker
|
||||
image: $REGISTRY/worker:v0.1
|
||||
image: dockercoins/worker:v0.1
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
@@ -316,7 +276,7 @@ class: extra-details
|
||||
spec:
|
||||
containers:
|
||||
- name: worker
|
||||
image: $REGISTRY/worker:v0.1
|
||||
image: dockercoins/worker:v0.1
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
|
||||
93
slides/k8s/yamldeploy.md
Normal file
93
slides/k8s/yamldeploy.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Deploying with YAML
|
||||
|
||||
- So far, we created resources with the following commands:
|
||||
|
||||
- `kubectl run`
|
||||
|
||||
- `kubectl create deployment`
|
||||
|
||||
- `kubectl expose`
|
||||
|
||||
- We can also create resources directly with YAML manifests
|
||||
|
||||
---
|
||||
|
||||
## `kubectl apply` vs `create`
|
||||
|
||||
- `kubectl create -f whatever.yaml`
|
||||
|
||||
- creates resources if they don't exist
|
||||
|
||||
- if resources already exist, don't alter them
|
||||
<br/>(and display error message)
|
||||
|
||||
- `kubectl apply -f whatever.yaml`
|
||||
|
||||
- creates resources if they don't exist
|
||||
|
||||
- if resources already exist, update them
|
||||
<br/>(to match the definition provided by the YAML file)
|
||||
|
||||
- stores the manifest as an *annotation* in the resource
|
||||
|
||||
---
|
||||
|
||||
## Creating multiple resources
|
||||
|
||||
- The manifest can contain multiple resources separated by `---`
|
||||
|
||||
```yaml
|
||||
kind: ...
|
||||
apiVersion: ...
|
||||
metadata: ...
|
||||
name: ...
|
||||
...
|
||||
---
|
||||
kind: ...
|
||||
apiVersion: ...
|
||||
metadata: ...
|
||||
name: ...
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating multiple resources
|
||||
|
||||
- The manifest can also contain a list of resources
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- kind: ...
|
||||
apiVersion: ...
|
||||
...
|
||||
- kind: ...
|
||||
apiVersion: ...
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deploying dockercoins with YAML
|
||||
|
||||
- We provide a YAML manifest with all the resources for Dockercoins
|
||||
|
||||
(Deployments and Services)
|
||||
|
||||
- We can use it if we need to deploy or redeploy Dockercoins
|
||||
|
||||
.exercise[
|
||||
|
||||
- Deploy or redeploy Dockercoins:
|
||||
```bash
|
||||
kubectl apply -f ~/container.training/k8s/dockercoins.yaml
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
(If we deployed Dockercoins earlier, we will see warning messages,
|
||||
because the resources that we created lack the necessary annotation.
|
||||
We can safely ignore them.)
|
||||
|
||||
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
- static-pods-exercise
|
||||
|
||||
@@ -11,6 +11,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
|
||||
@@ -23,7 +25,7 @@ chapters:
|
||||
- shared/prereqs.md
|
||||
#- shared/webssh.md
|
||||
- shared/connecting.md
|
||||
- k8s/versions-k8s.md
|
||||
#- k8s/versions-k8s.md
|
||||
- shared/sampleapp.md
|
||||
#- shared/composescale.md
|
||||
#- shared/hastyconclusions.md
|
||||
@@ -42,17 +44,18 @@ chapters:
|
||||
#- k8s/buildshiprun-selfhosted.md
|
||||
- k8s/buildshiprun-dockerhub.md
|
||||
- k8s/ourapponkube.md
|
||||
#- k8s/kubectlproxy.md
|
||||
#- k8s/localkubeconfig.md
|
||||
#- k8s/accessinternal.md
|
||||
-
|
||||
- k8s/setup-k8s.md
|
||||
- k8s/yamldeploy.md
|
||||
#- k8s/setup-k8s.md
|
||||
- k8s/dashboard.md
|
||||
#- k8s/kubectlscale.md
|
||||
- k8s/scalingdockercoins.md
|
||||
- shared/hastyconclusions.md
|
||||
- k8s/daemonset.md
|
||||
#- k8s/dryrun.md
|
||||
#- k8s/kubectlproxy.md
|
||||
#- k8s/localkubeconfig.md
|
||||
#- k8s/accessinternal.md
|
||||
- k8s/rollout.md
|
||||
#- k8s/healthchecks.md
|
||||
#- k8s/healthchecks-more.md
|
||||
|
||||
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- in-person
|
||||
|
||||
@@ -43,9 +45,7 @@ chapters:
|
||||
- k8s/buildshiprun-dockerhub.md
|
||||
- k8s/ourapponkube.md
|
||||
-
|
||||
- k8s/kubectlproxy.md
|
||||
- k8s/localkubeconfig.md
|
||||
- k8s/accessinternal.md
|
||||
- k8s/yamldeploy.md
|
||||
- k8s/setup-k8s.md
|
||||
- k8s/dashboard.md
|
||||
#- k8s/kubectlscale.md
|
||||
@@ -53,6 +53,9 @@ chapters:
|
||||
- shared/hastyconclusions.md
|
||||
- k8s/daemonset.md
|
||||
- k8s/dryrun.md
|
||||
- k8s/kubectlproxy.md
|
||||
- k8s/localkubeconfig.md
|
||||
- k8s/accessinternal.md
|
||||
-
|
||||
- k8s/rollout.md
|
||||
- k8s/healthchecks.md
|
||||
|
||||
@@ -10,6 +10,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
|
||||
@@ -23,7 +25,7 @@ chapters:
|
||||
- shared/prereqs.md
|
||||
#- shared/webssh.md
|
||||
- shared/connecting.md
|
||||
- k8s/versions-k8s.md
|
||||
#- k8s/versions-k8s.md
|
||||
- shared/sampleapp.md
|
||||
#- shared/composescale.md
|
||||
#- shared/hastyconclusions.md
|
||||
@@ -43,20 +45,21 @@ chapters:
|
||||
- k8s/buildshiprun-dockerhub.md
|
||||
- k8s/ourapponkube.md
|
||||
-
|
||||
- k8s/kubectlproxy.md
|
||||
- k8s/localkubeconfig.md
|
||||
- k8s/accessinternal.md
|
||||
- k8s/setup-k8s.md
|
||||
- k8s/yamldeploy.md
|
||||
#- k8s/setup-k8s.md
|
||||
- k8s/dashboard.md
|
||||
#- k8s/kubectlscale.md
|
||||
- k8s/scalingdockercoins.md
|
||||
- shared/hastyconclusions.md
|
||||
- k8s/daemonset.md
|
||||
-
|
||||
- k8s/dryrun.md
|
||||
-
|
||||
#- k8s/kubectlproxy.md
|
||||
- k8s/localkubeconfig.md
|
||||
- k8s/accessinternal.md
|
||||
- k8s/rollout.md
|
||||
- k8s/healthchecks.md
|
||||
- k8s/healthchecks-more.md
|
||||
#- k8s/healthchecks-more.md
|
||||
- k8s/record.md
|
||||
-
|
||||
- k8s/namespaces.md
|
||||
|
||||
@@ -80,7 +80,7 @@ def flatten(titles):
|
||||
|
||||
|
||||
def generatefromyaml(manifest, filename):
|
||||
manifest = yaml.load(manifest)
|
||||
manifest = yaml.safe_load(manifest)
|
||||
|
||||
markdown, titles = processchapter(manifest["chapters"], filename)
|
||||
logging.debug("Found {} titles.".format(len(titles)))
|
||||
@@ -111,6 +111,7 @@ def generatefromyaml(manifest, filename):
|
||||
html = html.replace("@@GITREPO@@", manifest["gitrepo"])
|
||||
html = html.replace("@@SLIDES@@", manifest["slides"])
|
||||
html = html.replace("@@TITLE@@", manifest["title"].replace("\n", " "))
|
||||
html = html.replace("@@SLIDENUMBERPREFIX@@", manifest.get("slidenumberprefix", ""))
|
||||
return html
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ class: in-person
|
||||
|
||||
.exercise[
|
||||
|
||||
- Log into the first VM (`node1`) with your SSH client
|
||||
- Log into the first VM (`node1`) with your SSH client:
|
||||
```bash
|
||||
ssh `user`@`A.B.C.D`
|
||||
```
|
||||
|
||||
(Replace `user` and `A.B.C.D` with the user and IP address provided to you)
|
||||
|
||||
<!--
|
||||
```bash
|
||||
@@ -18,16 +23,13 @@ done
|
||||
```
|
||||
-->
|
||||
|
||||
- Check that you can SSH (without password) to `node2`:
|
||||
```bash
|
||||
ssh node2
|
||||
```
|
||||
- Type `exit` or `^D` to come back to `node1`
|
||||
|
||||
<!-- ```bash exit``` -->
|
||||
|
||||
]
|
||||
|
||||
You should see a prompt looking like this:
|
||||
```
|
||||
[A.B.C.D] (...) user@node1 ~
|
||||
$
|
||||
```
|
||||
If anything goes wrong — ask for help!
|
||||
|
||||
---
|
||||
@@ -52,6 +54,20 @@ If anything goes wrong — ask for help!
|
||||
|
||||
---
|
||||
|
||||
## For a consistent Kubernetes experience ...
|
||||
|
||||
- If you are using your own Kubernetes cluster, you can use [shpod](https://github.com/jpetazzo/shpod)
|
||||
|
||||
- `shpod` provides a shell running in a pod on your own cluster
|
||||
|
||||
- It comes with many tools pre-installed (helm, stern...)
|
||||
|
||||
- These tools are used in many exercises in these slides
|
||||
|
||||
- `shpod` also gives you completion and a fancy prompt
|
||||
|
||||
---
|
||||
|
||||
class: self-paced
|
||||
|
||||
## Get your own Docker nodes
|
||||
|
||||
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
- snap
|
||||
|
||||
@@ -9,6 +9,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- self-paced
|
||||
- snap
|
||||
|
||||
@@ -8,6 +8,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- in-person
|
||||
- btp-auto
|
||||
|
||||
@@ -8,6 +8,8 @@ gitrepo: github.com/jpetazzo/container.training
|
||||
|
||||
slides: http://container.training/
|
||||
|
||||
#slidenumberprefix: "#SomeHashTag — "
|
||||
|
||||
exclude:
|
||||
- in-person
|
||||
- btp-auto
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
var slideshow = remark.create({
|
||||
ratio: '16:9',
|
||||
highlightSpans: true,
|
||||
slideNumberFormat: '@@SLIDENUMBERPREFIX@@%current%/%total%',
|
||||
excludedClasses: [@@EXCLUDE@@]
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user