Compare commits

..

3 Commits
kube ... dc17eu

Author SHA1 Message Date
Jerome Petazzoni
60ce3882e8 fix-redirects.sh: adding forced redirect 2020-04-07 16:48:29 -05:00
Jérôme Petazzoni
3c6706ad03 Map site root to dc17eu workshop slides 2017-11-05 09:03:29 -08:00
Jérôme Petazzoni
bee3e763a9 Prepare dc17eu branch 2017-11-05 08:58:58 -08:00
127 changed files with 2097 additions and 2802 deletions

2
.gitignore vendored
View File

@@ -7,4 +7,4 @@ prepare-vms/ips.pdf
prepare-vms/settings.yaml
prepare-vms/tags
slides/*.yml.html
slides/nextstep
autotest/nextstep

View File

@@ -39,16 +39,14 @@ your own tutorials.
All these materials have been gathered in a single repository
because they have a few things in common:
- some [common slides](slides/common/) that are re-used
(and updated) identically between different decks;
- a [build system](slides/) generating HTML slides from
Markdown source files;
- a [semi-automated test harness](slides/autotest.py) to check
that the exercises and examples provided work properly;
- a [PhantomJS script](slides/slidechecker.js) to check
that the slides look good and don't have formatting issues;
- some [common slides](slides/common/) that are re-used
(and updated) identically between different decks;
- [deployment scripts](prepare-vms/) to start training
VMs in bulk;
- a [semi-automated test harness](autotest/) to check
that the exercises and examples provided work properly;
- a fancy pipeline powered by
[Netlify](https://www.netlify.com/) and continuously
deploying `master` to http://container.training/.
@@ -76,6 +74,9 @@ a few other contributors. It is actively maintained.
## Repository structure
- [autotest](autotest/)
- Semi-automated testing system to check that all the exercises
in the slides work properly.
- [bin](bin/)
- A few helper scripts that you can safely ignore for now.
- [dockercoins](dockercoins/)

View File

@@ -1,21 +1,16 @@
#!/usr/bin/env python
# coding: utf-8
import click
import uuid
import logging
import os
import random
import re
import subprocess
import sys
import time
import uuid
logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))
logging.basicConfig(level=logging.DEBUG)
interactive = True
verify_status = False
simulate_type = True
TIMEOUT = 60 # 1 minute
@@ -78,9 +73,9 @@ def ansi(code):
return lambda s: "\x1b[{}m{}\x1b[0m".format(code, s)
def wait_for_string(s, timeout=TIMEOUT):
def wait_for_string(s):
logging.debug("Waiting for string: {}".format(s))
deadline = time.time() + timeout
deadline = time.time() + TIMEOUT
while time.time() < deadline:
output = capture_pane()
if s in output:
@@ -96,17 +91,13 @@ def wait_for_prompt():
output = capture_pane()
# If we are not at the bottom of the screen, there will be a bunch of extra \n's
output = output.rstrip('\n')
if output.endswith("\n$"):
return
if output.endswith("\n/ #"):
if output[-2:] == "\n$":
return
time.sleep(1)
raise Exception("Timed out while waiting for prompt!")
def check_exit_status():
if not verify_status:
return
token = uuid.uuid4().hex
data = "echo {} $?\n".format(token)
logging.debug("Sending {!r} to get exit status.".format(data))
@@ -126,23 +117,6 @@ def check_exit_status():
# Otherwise just return peacefully.
def setup_tmux_and_ssh():
if subprocess.call(["tmux", "has-session"]):
logging.info("Couldn't connect to tmux. A new tmux session will be created.")
subprocess.check_call(["tmux", "new-session", "-d"])
wait_for_string("$")
send_keys("cd ../prepare-vms\n")
send_keys("ssh docker@$(head -n1 ips.txt)\n")
wait_for_string("password:")
send_keys("training\n")
wait_for_prompt()
else:
logging.info("Found tmux session. Trying to acquire shell prompt.")
wait_for_prompt()
logging.info("Successfully connected to test cluster in tmux session.")
slides = []
content = open(sys.argv[1]).read()
for slide in re.split("\n---?\n", content):
@@ -163,22 +137,12 @@ for slide in slides:
def send_keys(data):
if simulate_type and data[0] != '^':
for key in data:
if key == ";":
key = "\\;"
subprocess.check_call(["tmux", "send-keys", key])
time.sleep(0.1*random.random())
else:
subprocess.check_call(["tmux", "send-keys", data])
subprocess.check_call(["tmux", "send-keys", data])
def capture_pane():
return subprocess.check_output(["tmux", "capture-pane", "-p"])
setup_tmux_and_ssh()
try:
i = int(open("nextstep").read())
logging.info("Loaded next step ({}) from file.".format(i))
@@ -186,6 +150,8 @@ except Exception as e:
logging.warning("Could not read nextstep file ({}), initializing to 0.".format(e))
i = 0
interactive = True
while i < len(actions):
with open("nextstep", "w") as f:
f.write(str(i))
@@ -199,14 +165,10 @@ while i < len(actions):
print(hrule())
if interactive:
print("[{}/{}] Shall we execute that snippet above?".format(i, len(actions)))
print("y/⏎/→ Execute snippet")
print("s Skip snippet")
print("g Go to a specific snippet")
print("q Quit")
print("c Continue non-interactively until next error")
command = click.getchar()
print("(ENTER to execute, 'c' to continue until next error, N to jump to step #N)")
command = raw_input("> ")
else:
command = "y"
command = ""
# For now, remove the `highlighted` sections
# (Make sure to use $() in shell snippets!)
@@ -214,16 +176,12 @@ while i < len(actions):
logging.info("Stripping ` from snippet.")
data = data.replace('`', '')
if command == "s":
i += 1
elif command == "g":
i = click.prompt("Enter snippet number", type=int)
elif command == "q":
break
elif command == "c":
if command == "c":
# continue until next timeout
interactive = False
elif command in ("y", "\r", " ", "\x1b[C"):
elif command.isdigit():
i = int(command)
elif command == "":
logging.info("Running with method {}: {}".format(method, data))
if method == "keys":
send_keys(data)
@@ -241,8 +199,6 @@ while i < len(actions):
_, _, next_method, next_data = actions[i+1]
if next_method == "wait":
wait_for_string(next_data)
elif next_method == "longwait":
wait_for_string(next_data, 10*TIMEOUT)
else:
wait_for_prompt()
# Verify return code FIXME should be optional
@@ -260,19 +216,13 @@ while i < len(actions):
# FIXME: we should factor out the "bash" method
wait_for_prompt()
check_exit_status()
elif method == "open":
# Cheap way to get node1's IP address
screen = capture_pane()
ipaddr = re.findall("^\[(.*)\]", screen, re.MULTILINE)[-1]
url = data.replace("/node1", "/{}".format(ipaddr))
# This should probably be adapted to run on different OS
subprocess.check_call(["open", url])
else:
logging.warning("Unknown method {}: {!r}".format(method, data))
i += 1
else:
logging.warning("Unknown command {}.".format(command))
i += 1
logging.warning("Unknown command {}, skipping to next step.".format(command))
# Reset slide counter
with open("nextstep", "w") as f:

View File

@@ -20,5 +20,5 @@ paper_margin: 0.2in
engine_version: test
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.17.1
machine_version: 0.13.0
compose_version: 1.16.1
machine_version: 0.12.0

View File

@@ -20,5 +20,5 @@ paper_margin: 0.2in
engine_version: test
# These correspond to the version numbers visible on their respective GitHub release pages
compose_version: 1.17.1
machine_version: 0.13.0
compose_version: 1.16.1
machine_version: 0.12.0

View File

@@ -1 +1 @@
/ /kube-halfday.yml.html 200!
/ /dockercon.yml.html 200!

View File

@@ -1,17 +0,0 @@
#!/usr/bin/env python
import logging
import os
import subprocess
import sys
logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO"))
filename = sys.argv[1]
logging.info("Checking file {}...".format(filename))
text = subprocess.check_output(["./slidechecker.js", filename])
html = open(filename).read()
html = html.replace("</textarea>", "\n---\n```\n{}\n```\n</textarea>".format(text))
open(filename, "w").write(html)

View File

@@ -2,16 +2,11 @@
case "$1" in
once)
for YAML in *.yml; do
./markmaker.py $YAML > $YAML.html || {
./markmaker.py < $YAML > $YAML.html || {
rm $YAML.html
break
}
done
if [ -n "$SLIDECHECKER" ]; then
for YAML in *.yml; do
./appendcheck.py $YAML.html
done
fi
;;
forever)

View File

@@ -1,63 +0,0 @@
# Declarative vs imperative
- Our container orchestrator puts a very strong emphasis on being *declarative*
- Declarative:
*I would like a cup of tea.*
- Imperative:
*Boil some water. Pour it in a teapot. Add tea leaves. Steep for a while. Serve in cup.*
--
- Declarative seems simpler at first ...
--
- ... As long as you know how to brew tea
---
## Declarative vs imperative
- What declarative would really be:
*I want a cup of tea, obtained by pouring an infusion¹ of tea leaves in a cup.*
--
*¹An infusion is obtained by letting the object steep a few minutes in hot² water.*
--
*²Hot liquid is obtained by pouring it in an appropriate container³ and setting it on a stove.*
--
*³Ah, finally, containers! Something we know about. Let's get to work, shall we?*
--
.footnote[Did you know there was an [ISO standard](https://en.wikipedia.org/wiki/ISO_3103)
specifying how to brew tea?]
---
## Declarative vs imperative
- Imperative systems:
- simpler
- if a task is interrupted, we have to restart from scratch
- Declarative systems:
- if a task is interrupted (or if we show up to the party half-way through),
we can figure out what's missing and do only what's necessary
- we need to be able to *observe* the system
- ... and compute a "diff" between *what we have* and *what we want*

View File

@@ -1,19 +0,0 @@
## Using Play-With-Docker
- Open a new browser tab to [www.play-with-docker.com](http://www.play-with-docker.com/)
- Confirm that you're not a robot
- Click on "ADD NEW INSTANCE": congratulations, you have your first Docker node!
- When you will need more nodes, just click on "ADD NEW INSTANCE" again
- Note the countdown in the corner; when it expires, your instances are destroyed
- If you give your URL to somebody else, they can access your nodes too
<br/>
(You can use that for pair programming, or to get help from a mentor)
- Loving it? Not loving it? Tell it to the wonderful authors,
[@marcosnils](https://twitter.com/marcosnils) &
[@xetorthio](https://twitter.com/xetorthio)!

View File

@@ -1,16 +1,16 @@
# Our sample application
- Visit the GitHub repository with all the materials of this workshop:
<br/>https://github.com/jpetazzo/container.training
<br/>https://github.com/jpetazzo/orchestration-workshop
- The application is in the [dockercoins](
https://github.com/jpetazzo/container.training/tree/master/dockercoins)
https://github.com/jpetazzo/orchestration-workshop/tree/master/dockercoins)
subdirectory
- Let's look at the general layout of the source code:
there is a Compose file [docker-compose.yml](
https://github.com/jpetazzo/container.training/blob/master/dockercoins/docker-compose.yml) ...
https://github.com/jpetazzo/orchestration-workshop/blob/master/dockercoins/docker-compose.yml) ...
... and 4 other services, each in its own directory:
@@ -59,32 +59,25 @@ class: extra-details
## Example in `worker/worker.py`
```python
redis = Redis("`redis`")
def get_random_bytes():
r = requests.get("http://`rng`/32")
return r.content
def hash_bytes(data):
r = requests.post("http://`hasher`/",
data=data,
headers={"Content-Type": "application/octet-stream"})
```
(Full source code available [here](
https://github.com/jpetazzo/container.training/blob/8279a3bce9398f7c1a53bdd95187c53eda4e6435/dockercoins/worker/worker.py#L17
))
![Service discovery](images/service-discovery.png)
---
## What's this application?
--
---
- It is a DockerCoin miner! .emoji[💰🐳📦🚢]
class: pic
![DockerCoins logo](images/dockercoins.png)
(DockerCoins 2016 logo courtesy of [@XtlCnslt](https://twitter.com/xtlcnslt) and [@ndeloof](https://twitter.com/ndeloof). Thanks!)
---
## What's this application?
- It is a DockerCoin miner! 💰🐳📦🚢
--
@@ -116,15 +109,15 @@ https://github.com/jpetazzo/container.training/blob/8279a3bce9398f7c1a53bdd95187
<!--
```bash
if [ -d container.training ]; then
mv container.training container.training.$$
if [ -d orchestration-workshop ]; then
mv orchestration-workshop orchestration-workshop.$$
fi
```
-->
- Clone the repository on `node1`:
```bash
git clone git://github.com/jpetazzo/container.training
git clone git://github.com/jpetazzo/orchestration-workshop
```
]
@@ -141,7 +134,7 @@ Without further ado, let's start our application.
- Go to the `dockercoins` directory, in the cloned repo:
```bash
cd ~/container.training/dockercoins
cd ~/orchestration-workshop/dockercoins
```
- Use Compose to build and run all containers:
@@ -150,7 +143,7 @@ Without further ado, let's start our application.
```
<!--
```longwait units of work done```
```wait units of work done```
```keys ^C```
-->
@@ -270,31 +263,11 @@ class: extra-details
]
A drawing area should show up, and after a few seconds, a blue
graph will appear.
You should see a speed of approximately 4 hashes/second.
---
class: self-paced, extra-details
## If the graph doesn't load
If you just see a `Page not found` error, it might be because your
Docker Engine is running on a different machine. This can be the case if:
- you are using the Docker Toolbox
- you are using a VM (local or remote) created with Docker Machine
- you are controlling a remote Docker Engine
When you run DockerCoins in development mode, the web UI static files
are mapped to the container using a volume. Alas, volumes can only
work on a local environment, or when using Docker4Mac or Docker4Windows.
How to fix this?
Edit `dockercoins.yml` and comment out the `volumes` section, and try again.
More precisely: 4 hashes/second, with regular dips down to zero.
<br/>This is because Jérôme is incapable of writing good frontend code.
<br/>Don't ask. Seriously, don't ask. This is embarrassing.
---
@@ -302,43 +275,19 @@ class: extra-details
## Why does the speed seem irregular?
- It *looks like* the speed is approximately 4 hashes/second
- Or more precisely: 4 hashes/second, with regular dips down to zero
- Why?
--
class: extra-details
- The app actually has a constant, steady speed: 3.33 hashes/second
<br/>
(which corresponds to 1 hash every 0.3 seconds, for *reasons*)
- Yes, and?
---
class: extra-details
## The reason why this graph is *not awesome*
- The worker doesn't update the counter after every loop, but up to once per second
- The speed is computed by the browser, checking the counter about once per second
- Between two consecutive updates, the counter will increase either by 4, or by 0
- The perceived speed will therefore be 4 - 4 - 4 - 0 - 4 - 4 - 0 etc.
- The perceived speed will therefore be 4 - 4 - 4 - 0 - 4 - 4 - etc.
- What can we conclude from this?
--
class: extra-details
- Jérôme is clearly incapable of writing good frontend code
*We told you to not ask!!!*
---

View File

@@ -1,26 +0,0 @@
class: title, self-paced
Thank you!
---
class: title, in-person
That's all folks! <br/> Questions?
![end](images/end.jpg)
---
# Links and resources
- [Docker Community Slack](https://community.docker.com/registrations/groups/4316)
- [Docker Community Forums](https://forums.docker.com/)
- [Docker Hub](https://hub.docker.com)
- [Docker Blog](http://blog.docker.com/)
- [Docker documentation](http://docs.docker.com/)
- [Docker on StackOverflow](https://stackoverflow.com/questions/tagged/docker)
- [Docker on Twitter](http://twitter.com/docker)
- [Play With Docker Hands-On Labs](http://training.play-with-docker.com/)
.footnote[These slides (and future updates) are on → http://container.training/]

View File

@@ -1,15 +0,0 @@
class: title, self-paced
@@TITLE@@
.nav[*Self-paced version*]
---
class: title, in-person
Docker + Kubernetes = ❤️<br/></br>
.footnote[
**Slides: http://kube.container.training/**
]

View File

@@ -1,4 +0,0 @@
@@TOC@@

182
slides/dockercon.yml Normal file
View File

@@ -0,0 +1,182 @@
chat: "[Slack](https://dockercommunity.slack.com/messages/C7ET1GY4Q)"
exclude:
- self-paced
- snap
- auto-btp
- benchmarking
- elk-manual
- prom-manual
title: "Swarm: from Zero to Hero (DC17EU)"
chapters:
- |
class: title
.small[
Swarm: from Zero to Hero
.small[.small[
**Be kind to the WiFi!**
*Use the 5G network*
<br/>
*Don't use your hotspot*
<br/>
*Don't stream videos from YouTube, Netflix, etc.
<br/>(if you're bored, watch local content instead)*
Also: share the power outlets
<br/>
*(with limited power comes limited responsibility?)*
<br/>
*(or something?)*
Thank you!
]
]
]
---
## Intros
<!--
- Hello! We are
AJ ([@s0ulshake](https://twitter.com/s0ulshake))
&
Jérôme ([@jpetazzo](https://twitter.com/jpetazzo))
-->
- Hello! We are Jérôme, Lee, Nicholas, and Scott
<!--
I am
Jérôme ([@jpetazzo](https://twitter.com/jpetazzo))
-->
--
- This is our collective Docker knowledge:
![Bell Curve](images/bell-curve.jpg)
---
## "From zero to hero"
--
- It rhymes, but it's a pretty bad title, to be honest
--
- None of you is a "zero"
--
- None of us is a "hero"
--
- None of us should even try to be a hero
--
*The hero syndrome is a phenomenon affecting people who seek heroism or recognition,
usually by creating a desperate situation which they can resolve.
This can include unlawful acts, such as arson.
The phenomenon has been noted to affect civil servants,
such as firefighters, nurses, police officers, and security guards.*
(Wikipedia page on [hero syndrome](https://en.wikipedia.org/wiki/Hero_syndrome))
---
## Agenda
.small[
- 09:00-09:10 Hello!
- 09:10-10:30 Part 1
- 10:30-11:00 coffee break
- 11:00-12:30 Part 2
- 12:30-13:30 lunch break
- 13:30-15:00 Part 3
- 15:00-15:30 coffee break
- 15:30-17:00 Part 4
- 17:00-18:00 Afterhours and Q&A
]
<!--
- The tutorial will run from 9:00am to 12:20pm
- This will be fast-paced, but DON'T PANIC!
- There will be a coffee break at 10:30am
<br/>
(please remind me if I forget about it!)
-->
- All the content is publicly available (slides, code samples, scripts)
Upstream URL: https://github.com/jpetazzo/orchestration-workshop
- Feel free to interrupt for questions at any time
- Live feedback, questions, help on [Gitter](chat)
http://container.training/chat
- swarm/intro.md
- |
@@TOC@@
- - swarm/prereqs.md
- swarm/versions.md
- |
class: title
All right!
<br/>
We're all set.
<br/>
Let's do this.
- common/sampleapp.md
- swarm/swarmkit.md
- swarm/creatingswarm.md
- swarm/morenodes.md
- - swarm/firstservice.md
- swarm/ourapponswarm.md
- swarm/updatingservices.md
- swarm/healthchecks.md
- - swarm/operatingswarm.md
- swarm/netshoot.md
- swarm/ipsec.md
- swarm/swarmtools.md
- swarm/security.md
- swarm/secrets.md
- swarm/encryptionatrest.md
- swarm/leastprivilege.md
- swarm/apiscope.md
- - swarm/logging.md
- swarm/metrics.md
- swarm/stateful.md
- swarm/extratips.md
- swarm/end.md
- |
class: title
That's all folks! <br/> Questions?
.small[.small[
Jérôme ([@jpetazzo](https://twitter.com/jpetazzo)) — [@docker](https://twitter.com/docker)
]]
<!--
Tiffany ([@tiffanyfayj](https://twitter.com/tiffanyfayj))
AJ ([@s0ulshake](https://twitter.com/s0ulshake))
-->

View File

@@ -1,2 +0,0 @@
#!/bin/sh
grep --color=auto -P -n "[^\x00-\x80]" */*.md

View File

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

View File

Before

Width:  |  Height:  |  Size: 927 KiB

After

Width:  |  Height:  |  Size: 927 KiB

View File

Before

Width:  |  Height:  |  Size: 595 KiB

After

Width:  |  Height:  |  Size: 595 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

View File

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 350 KiB

View File

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View File

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,162 +0,0 @@
<html>
<head>
<title>Container Training</title>
<style type="text/css">
body {
background-image: url("images/container-background.jpg");
max-width: 1024px;
margin: 0 auto;
}
table {
font-size: 20px;
font-family: sans-serif;
background: white;
width: 100%;
height: 100%;
padding: 20px;
}
.header {
font-size: 300%;
font-weight: bold;
}
.title {
font-size: 150%;
font-weight: bold;
}
td {
padding: 1px;
height: 1em;
}
td.spacer {
height: unset;
}
td.footer {
padding-top: 80px;
height: 100px;
}
td.title {
border-bottom: thick solid black;
padding-bottom: 2px;
padding-top: 20px;
}
a {
text-decoration: none;
}
a:hover {
background: yellow;
}
a.attend:after {
content: "📅 attend";
}
a.slides:after {
content: "📚 slides";
}
a.chat:after {
content: "💬 chat";
}
a.video:after {
content: "📺 video";
}
</style>
</head>
<body>
<div class="main">
<table>
<tr><td class="header" colspan="4">Container Training</td></tr>
<tr><td class="title" colspan="4">Coming soon at a conference near you</td></tr>
<tr>
<td>Kubernetes enablement at Docker</td>
<td><a class="slides" href="http://kube.container.training/" /></td>
<td><a class="chat" href="https://docker.slack.com/messages/C83M572J2" /></td>
</tr>
<!--
<td><a class="attend" href="https://qconsf.com/sf2017/workshop/orchestrating-microservices-docker-swarm" /></td>
-->
<!--
<tr>
<td>Nothing for now (stay tuned...)</td>
</tr>
-->
<tr><td class="title" colspan="4">Past workshops</td></tr>
<tr>
<td>QCON SF: Orchestrating Microservices with Docker Swarm</td>
<td><a class="slides" href="http://qconsf2017swarm.container.training/" /></td>
</tr>
<tr>
<td>QCON SF: Introduction to Docker and Containers</td>
<td><a class="slides" href="http://qconsf2017intro.container.training/" /></td>
</tr>
<tr>
<td>LISA17 M7: Getting Started with Docker and Containers</td>
<td><a class="slides" href="http://lisa17m7.container.training/" /></td>
</tr>
<tr>
<td>LISA17 T9: Build, Ship, and Run Microservices on a Docker Swarm Cluster</td>
<td><a class="slides" href="http://lisa17t9.container.training/" /></td>
</tr>
<tr>
<td>Deploying and scaling microservices with Docker and Kubernetes</td>
<td><a class="slides" href="http://osseu17.container.training/" /></td>
<td><a class="video" href="https://www.youtube.com/playlist?list=PLBAFXs0YjviLrsyydCzxWrIP_1-wkcSHS" /></td>
</tr>
<tr>
<td>DockerCon Workshop: from Zero to Hero (full day, B3 M1-2)</td>
<td><a class="slides" href="http://dc17eu.container.training/" /></td>
</tr>
<tr>
<td>DockerCon Workshop: Orchestration for Advanced Users (afternoon, B4 M5-6)</td>
<td><a class="slides" href="https://www.bretfisher.com/dockercon17eu/" /></td>
</tr>
<tr>
<td>LISA16 T1: Deploying and Scaling Applications with Docker Swarm</td>
<td><a class="slides" href="http://lisa16t1.container.training/" /></td>
<td><a class="video" href="https://www.youtube.com/playlist?list=PLBAFXs0YjviIDDhr8vIwCN1wkyNGXjbbc" /></td>
</tr>
<tr>
<td>PyCon2016: Introduction to Docker and containers</td>
<td><a class="slides" href="https://us.pycon.org/2016/site_media/media/tutorial_handouts/DockerSlides.pdf" /></td>
<td><a class="video" href="https://www.youtube.com/watch?v=ZVaRK10HBjo" /></td>
</tr>
<tr><td class="title" colspan="4">Self-paced tutorials</td></tr>
<tr>
<td>Introduction to Docker and Containers</td>
<td><a class="slides" href="intro-fullday.yml.html" /></td>
</tr>
<tr>
<td>Container Orchestration with Docker and Swarm</td>
<td><a class="slides" href="swarm-selfpaced.yml.html" /></td>
</tr>
<tr>
<td>Deploying and Scaling Microservices with Docker and Kubernetes</td>
<td><a class="slides" href="kube-halfday.yml.html" /></td>
</tr>
<tr><td class="spacer"></td></tr>
<tr>
<td class="footer">
Maintained by Jérôme Petazzoni (<a href="https://twitter.com/jpetazzo">@jpetazzo</a>)
</td>
</tr>
</table>
</div>
</body>
</html>

View File

@@ -1,15 +0,0 @@
https://gallant-turing-d0d520.netlify.com/containers/Container-Ship-Freighter-Navigation-Elbe-Romance-1782991.jpg
https://gallant-turing-d0d520.netlify.com/containers/ShippingContainerSFBay.jpg
https://gallant-turing-d0d520.netlify.com/containers/aerial-view-of-containers.jpg
https://gallant-turing-d0d520.netlify.com/containers/blue-containers.jpg
https://gallant-turing-d0d520.netlify.com/containers/chinook-helicopter-container.jpg
https://gallant-turing-d0d520.netlify.com/containers/container-cranes.jpg
https://gallant-turing-d0d520.netlify.com/containers/container-housing.jpg
https://gallant-turing-d0d520.netlify.com/containers/containers-by-the-water.jpg
https://gallant-turing-d0d520.netlify.com/containers/distillery-containers.jpg
https://gallant-turing-d0d520.netlify.com/containers/lots-of-containers.jpg
https://gallant-turing-d0d520.netlify.com/containers/plastic-containers.JPG
https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-1.jpg
https://gallant-turing-d0d520.netlify.com/containers/train-of-containers-2.jpg
https://gallant-turing-d0d520.netlify.com/containers/two-containers-on-a-truck.jpg
https://gallant-turing-d0d520.netlify.com/containers/wall-of-containers.jpeg

View File

@@ -1,42 +0,0 @@
title: |
Introduction
to Docker and
Containers
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
exclude:
- self-paced
chapters:
- common/title.md
- logistics.md
- common/intro.md
- common/toc.md
- - intro/Docker_Overview.md
#- intro/Docker_History.md
- intro/Training_Environment.md
- intro/Installing_Docker.md
- intro/First_Containers.md
- intro/Background_Containers.md
- intro/Start_And_Attach.md
- - intro/Initial_Images.md
- intro/Building_Images_Interactively.md
- intro/Building_Images_With_Dockerfiles.md
- intro/Cmd_And_Entrypoint.md
- intro/Copying_Files_During_Build.md
- intro/Multi_Stage_Builds.md
- intro/Publishing_To_Docker_Hub.md
- intro/Dockerfile_Tips.md
- - intro/Naming_And_Inspecting.md
- intro/Container_Networking_Basics.md
- intro/Network_Drivers.md
- intro/Container_Network_Model.md
#- intro/Connecting_Containers_With_Links.md
- intro/Ambassadors.md
- - intro/Local_Development_Workflow.md
- intro/Working_With_Volumes.md
- intro/Compose_For_Dev_Stacks.md
- intro/Advanced_Dockerfiles.md
- common/thankyou.md

View File

@@ -1,42 +0,0 @@
title: |
Introduction
to Docker and
Containers
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
exclude:
- in-person
chapters:
- common/title.md
# - common/logistics.md
- common/intro.md
- common/toc.md
- - intro/Docker_Overview.md
#- intro/Docker_History.md
- intro/Training_Environment.md
- intro/Installing_Docker.md
- intro/First_Containers.md
- intro/Background_Containers.md
- intro/Start_And_Attach.md
- - intro/Initial_Images.md
- intro/Building_Images_Interactively.md
- intro/Building_Images_With_Dockerfiles.md
- intro/Cmd_And_Entrypoint.md
- intro/Copying_Files_During_Build.md
- intro/Multi_Stage_Builds.md
- intro/Publishing_To_Docker_Hub.md
- intro/Dockerfile_Tips.md
- - intro/Naming_And_Inspecting.md
- intro/Container_Networking_Basics.md
- intro/Network_Drivers.md
- intro/Container_Network_Model.md
#- intro/Connecting_Containers_With_Links.md
- intro/Ambassadors.md
- - intro/Local_Development_Workflow.md
- intro/Working_With_Volumes.md
- intro/Compose_For_Dev_Stacks.md
- intro/Advanced_Dockerfiles.md
- common/thankyou.md

View File

@@ -1,6 +1,9 @@
class: title
# Advanced Dockerfiles
![construction](images/title-advanced-dockerfiles.jpg)
![construction](images/construction.jpg)
---

View File

@@ -3,7 +3,7 @@ class: title
# Ambassadors
![Two serious-looking persons shaking hands](images/title-ambassador.jpg)
![](images/ambassador.jpg)
---
@@ -40,7 +40,7 @@ ambassador containers.
---
![ambassador](images/ambassador-diagram.png)
![ambassador](images/diagram.png)
---

View File

@@ -1,9 +1,9 @@
class: title
# Background containers
# Background Containers
![Background containers](images/title-background-containers.jpg)
![Background Containers](images/background-containers.jpg)
---

View File

@@ -1,4 +1,4 @@
# Building images interactively
# Building Images Interactively
In this section, we will create our first container image.
@@ -16,21 +16,27 @@ We will:
---
## The plan
## Building Images Interactively
1. Create a container (with `docker run`) using our base distro of choice.
As we have seen, the images on the Docker Hub are sometimes very basic.
2. Run a bunch of commands to install and set up our software in the container.
How do we want to construct our own images?
3. (Optionally) review changes in the container with `docker diff`.
As an example, we will build an image that has `figlet`.
4. Turn the container into a new image with `docker commit`.
First, we will do it manually with `docker commit`.
5. (Optionally) add tags to the image with `docker tag`.
Then, in an upcoming chapter, we will use a `Dockerfile` and `docker build`.
---
## Setting up our container
## Building from a base
Our base will be the `ubuntu` image.
---
## Create a new container and make some changes
Start an Ubuntu container:
@@ -101,7 +107,7 @@ As explained before:
---
## Commit our changes into a new image
## Commit and run your image
The `docker commit` command will create a new layer with those changes,
and a new image using this new layer.
@@ -113,13 +119,7 @@ $ docker commit <yourContainerId>
The output of the `docker commit` command will be the ID for your newly created image.
We can use it as an argument to `docker run`.
---
## Testing our new image
Let's run this image:
We can run this image:
```bash
$ docker run -it <newImageId>
@@ -131,8 +131,6 @@ root@fcfb62f0bfde:/# figlet hello
|_| |_|\___|_|_|\___/
```
It works! .emoji[🎉]
---
## Tagging images

View File

@@ -3,7 +3,7 @@ class: title
# Building Docker images with a Dockerfile
![Construction site with containers](images/title-building-docker-images-with-a-dockerfile.jpg)
![construction](images/construction.jpg)
---
@@ -188,7 +188,7 @@ root@91f3c974c9a1:/# figlet hello
```
Yay! .emoji[🎉]
Yay! 🎉
---

View File

@@ -1,7 +1,7 @@
class: title
# `CMD` and `ENTRYPOINT`
# CMD and ENTRYPOINT
![Container entry doors](images/entrypoint.jpg)
@@ -141,7 +141,7 @@ Why did we use JSON syntax for our `ENTRYPOINT`?
* When CMD or ENTRYPOINT use string syntax, they get wrapped in `sh -c`.
* To avoid this wrapping, we can use JSON syntax.
* To avoid this wrapping, you must use JSON syntax.
What if we used `ENTRYPOINT` with string syntax?
@@ -178,6 +178,8 @@ $ docker run figlet salut
\/ \_/|_/|__/ \_/|_/|_/
```
Great success!
---
## Using `CMD` and `ENTRYPOINT` together
@@ -225,8 +227,9 @@ $ docker build -t figlet .
Successfully built 6e0b6a048a07
```
Run it without parameters:
And run it:
.small[
```bash
$ docker run figlet
_ _ _ _
@@ -234,15 +237,7 @@ $ docker run figlet
| | _ | | | | __ __ ,_ | | __|
|/ \ |/ |/ |/ / \_ | | |_/ \_/ | |/ / |
| |_/|__/|__/|__/\__/ \/ \/ \__/ |_/|__/\_/|_/
```
---
## Overriding the image default parameters
Now let's pass extra arguments to the image.
```bash
$ docker run figlet hola mundo
_ _
| | | | |
@@ -250,8 +245,7 @@ $ docker run figlet hola mundo
|/ \ / \_|/ / | / |/ |/ | | | / |/ | / | / \_
| |_/\__/ |__/\_/|_/ | | |_/ \_/|_/ | |_/\_/|_/\__/
```
We overrode `CMD` but still used `ENTRYPOINT`.
]
---

View File

@@ -1,4 +1,5 @@
# Compose for development stacks
# Compose For Development Stacks
Dockerfiles are great to build container images.
@@ -112,7 +113,6 @@ them.
Here is the file used in the demo:
.small[
```yaml
version: "2"
@@ -131,7 +131,6 @@ services:
redis:
image: redis
```
]
---

View File

@@ -1,9 +1,9 @@
class: title
# Connecting containers with links
# Connecting Containers With Links
![graph](images/title-connecting-containers-with-links.gif)
![graph](images/graph.gif)
---

View File

@@ -3,7 +3,7 @@ class: title
# The Container Network Model
![A denser graph network](images/title-the-container-network-model.jpg)
![A denser graph network](images/complex-network.jpg)
---
@@ -126,16 +126,13 @@ $ docker run -d --name es --net dev elasticsearch:2
Now, create another container on this network.
.small[
```bash
$ docker run -ti --net dev alpine sh
root@0ecccdfa45ef:/#
```
]
From this new container, we can resolve and ping the other one, using its assigned name:
.small[
```bash
/ # ping es
PING es (172.18.0.2) 56(84) bytes of data.
@@ -148,7 +145,6 @@ PING es (172.18.0.2) 56(84) bytes of data.
rtt min/avg/max/mdev = 0.114/0.149/0.221/0.052 ms
root@0ecccdfa45ef:/#
```
]
---
@@ -159,7 +155,6 @@ class: extra-details
In Docker Engine 1.9, name resolution is implemented with `/etc/hosts`, and
updating it each time containers are added/removed.
.small[
```bash
[root@0ecccdfa45ef /]# cat /etc/hosts
172.18.0.3 0ecccdfa45ef
@@ -172,7 +167,6 @@ ff02::2 ip6-allrouters
172.18.0.2 es
172.18.0.2 es.dev
```
]
In Docker Engine 1.10, this has been replaced by a dynamic resolver.
@@ -180,7 +174,7 @@ In Docker Engine 1.10, this has been replaced by a dynamic resolver.
---
# Service discovery with containers
## Connecting multiple containers together
* Let's try to run an application that requires two containers.
@@ -216,7 +210,9 @@ $ docker ps -l
* If we connect to the application now, we will see an error page:
.small[
![Trainingwheels error](images/trainingwheels-error.png)
]
* This is because the Redis service is not running.
* This container tries to resolve the name `redis`.
@@ -245,7 +241,9 @@ $ docker run --net dev --name redis -d redis
* If we connect to the application now, we should see that the app is working correctly:
.small[
![Trainingwheels OK](images/trainingwheels-ok.png)
]
* When the app tries to resolve `redis`, instead of getting a DNS error, it gets the IP address of our Redis container.
@@ -364,7 +362,6 @@ Each ElasticSearch instance has a name (generated when it is started). This name
Try the following command a few times:
.small[
```bash
$ docker run --rm --net dev centos curl -s es:9200
{
@@ -372,11 +369,9 @@ $ docker run --rm --net dev centos curl -s es:9200
...
}
```
]
Then try it a few times by replacing `--net dev` with `--net prod`:
.small[
```bash
$ docker run --rm --net prod centos curl -s es:9200
{
@@ -384,7 +379,6 @@ $ docker run --rm --net prod centos curl -s es:9200
...
}
```
]
---
@@ -492,7 +486,7 @@ Very short instructions:
- `docker network create mynet --driver overlay`
- `docker service create --network mynet myimage`
See http://jpetazzo.github.io/container.training for all the deets about clustering!
See http://jpetazzo.github.io/orchestration-workshop for all the deets about clustering!
---

View File

@@ -1,9 +1,9 @@
class: title
# Container networking basics
# Container Networking Basics
![A dense graph network](images/title-container-networking-basics.jpg)
![A dense graph network](images/network.jpg)
---
@@ -69,7 +69,7 @@ But first, let's make sure that everything works properly.
Point your browser to the IP address of your Docker host, on the port
shown by `docker ps` for container port 80.
![Screenshot](images/welcome-to-nginx.png)
![Screenshot](images/web.png)
---
@@ -189,6 +189,87 @@ $ ping <ipAddress>
---
## The different network drivers
A container can use one of the following drivers:
* `bridge` (default)
* `none`
* `host`
* `container`
The driver is selected with `docker run --net ...`.
The different drivers are explained with more details on the following slides.
---
## The default bridge
* By default, the container gets a virtual `eth0` interface.
<br/>(In addition to its own private `lo` loopback interface.)
* That interface is provided by a `veth` pair.
* It is connected to the Docker bridge.
<br/>(Named `docker0` by default; configurable with `--bridge`.)
* Addresses are allocated on a private, internal subnet.
<br/>(Docker uses 172.17.0.0/16 by default; configurable with `--bip`.)
* Outbound traffic goes through an iptables MASQUERADE rule.
* Inbound traffic goes through an iptables DNAT rule.
* The container can have its own routes, iptables rules, etc.
---
## The null driver
* Container is started with `docker run --net none ...`
* It only gets the `lo` loopback interface. No `eth0`.
* It can't send or receive network traffic.
* Useful for isolated/untrusted workloads.
---
## The host driver
* Container is started with `docker run --net host ...`
* It sees (and can access) the network interfaces of the host.
* It can bind any address, any port (for ill and for good).
* Network traffic doesn't have to go through NAT, bridge, or veth.
* Performance = native!
Use cases:
* Performance sensitive applications (VOIP, gaming, streaming...)
* Peer discovery (e.g. Erlang port mapper, Raft, Serf...)
---
## The container driver
* Container is started with `docker run --net container:id ...`
* It re-uses the network stack of another container.
* It shares with this other container the same interfaces, IP address(es), routes, iptables rules, etc.
* Those containers can communicate over their `lo` interface.
<br/>(i.e. one can bind to 127.0.0.1 and the others can connect to it.)
---
## Section summary
We've learned how to:

View File

@@ -3,7 +3,7 @@ class: title
# Copying files during the build
![Monks copying books](images/title-copying-files-during-build.jpg)
![Monks copying books](images/copy.jpg)
---

View File

@@ -0,0 +1,32 @@
class: title
# Course Conclusion
![end](images/end.jpg)
---
## Questions & Next Steps
A bunch of useful links:
* Docker homepage - http://www.docker.com/
* Docker Hub - https://hub.docker.com
* Docker blog - http://blog.docker.com/
* Docker documentation - http://docs.docker.com/
* Docker code on GitHub - https://github.com/docker/docker
* Docker mailing list - [https://groups.google.com/forum/#!forum/docker-user
* Docker on IRC: irc.freenode.net and channels `#docker` and `#docker-dev`
* Docker on Twitter - http://twitter.com/docker
* Get Docker help on Stack Overflow - http://stackoverflow.com/search?q=docker
* Play With Docker Hands-On Labs - http://training.play-with-docker.com/
These slides are at: http://container.training/
---
class: title
Thank You!
.small[http://container.training/]

View File

@@ -20,7 +20,7 @@ class: pic
## The VPS age (until 2007-2008)
![lightcont](images/containers-as-lightweight-vms.png)
![lightcont](images/lightcont.png)
---

View File

@@ -0,0 +1,35 @@
# Publishing images to the Docker Hub
We have built our first images.
If we were so inclined, we could share those images through the Docker Hub.
We won't do it since we don't want to force everyone to create a Docker Hub account (although it's free, yay!) but the steps would be:
* have an account on the Docker Hub
* tag our image accordingly (i.e. `username/imagename`)
* `docker push username/imagename`
Anybody can now `docker run username/imagename` from any Docker host.
Images can be set to be private as well.
---
## The goodness of automated builds
* You can link a Docker Hub repository with a GitHub or BitBucket repository
* Each push to GitHub or BitBucket will trigger a build on Docker Hub
* If the build succeeds, the new image is available on Docker Hub
* You can map tags and branches between source and container images
* If you work with public repositories, this is free
* Corollary: this gives you a very simple way to get free, basic CI
(With the technique presented earlier)

View File

@@ -58,7 +58,7 @@ class: pic
## The deployment problem
![problem](images/shipping-software-problem.png)
![problem](images/problem.png)
---
@@ -66,7 +66,7 @@ class: pic
## The matrix from hell
![matrix](images/shipping-matrix-from-hell.png)
![matrix](images/matrix.png)
---
@@ -74,7 +74,7 @@ class: pic
## The parallel with the shipping indsutry
![history](images/shipping-industry-problem.png)
![history](images/shippingindustry.png)
---
@@ -82,7 +82,7 @@ class: pic
## Intermodal shipping containers
![shipping](images/shipping-industry-solution.png)
![shipping](images/shipping.png)
---
@@ -90,7 +90,7 @@ class: pic
## A new shipping ecosystem
![shipeco](images/shipping-indsutry-results.png)
![shipeco](images/shipeco.png)
---
@@ -98,7 +98,7 @@ class: pic
## A shipping container system for applications
![shipapp](images/shipping-software-solution.png)
![shipapp](images/appcont.png)
---
@@ -106,29 +106,17 @@ class: pic
## Eliminate the matrix from hell
![elimatrix](images/shipping-matrix-solved.png)
![elimatrix](images/elimatrix.png)
---
## Results
* [Dev-to-prod reduced from 9 months to 15 minutes (ING)](
https://www.docker.com/sites/default/files/CS_ING_01.25.2015_1.pdf)
* Dev-to-prod reduced from 9 months to 15 minutes (ING)
* [Continuous integration job time reduced by more than 60% (BBC)](
https://www.docker.com/sites/default/files/CS_BBCNews_01.25.2015_1.pdf)
* Continuous integration job time reduced by more than 60% (BBC)
* [Deploy 100 times a day instead of once a week (GILT)](
https://www.docker.com/sites/default/files/CS_Gilt%20Groupe_03.18.2015_0.pdf)
* [70% infrastructure consolidation (MetLife)](
https://www.docker.com/customers/metlife-transforms-customer-experience-legacy-and-microservices-mashup)
* [60% infrastructure consolidation (Intesa Sanpaolo)](
https://blog.docker.com/2017/11/intesa-sanpaolo-builds-resilient-foundation-banking-docker-enterprise-edition/)
* [14x application density; 60% of legacy datacenter migrated in 4 months (GE Appliances)](
https://www.docker.com/customers/ge-uses-docker-enable-self-service-their-developers)
* Dev-to-prod reduced from weeks to minutes (GILT)
* etc.

View File

@@ -1,9 +1,9 @@
class: title
# Our first containers
# Our First Containers
![Colorful plastic tubs](images/title-our-first-containers.jpg)
![Plastic Containers](images/firstcontainers.jpg)
---
@@ -51,13 +51,10 @@ root@04c0bb0a6c07:/#
```
* This is a brand new container.
* It runs a bare-bones, no-frills `ubuntu` system.
* `-it` is shorthand for `-i -t`.
* `-i` tells Docker to connect us to the container's stdin.
* `-t` tells Docker that we want a pseudo-terminal.
---
@@ -75,6 +72,22 @@ Alright, we need to install it.
---
## An observation
Let's check how many packages are installed here.
```bash
root@04c0bb0a6c07:/# dpkg -l | wc -l
189
```
* `dpkg -l` lists the packages installed in our container
* `wc -l` counts them
* If you have a Debian or Ubuntu machine, you can run the same command
and compare the results.
---
## Install a package in our container
We want `figlet`, so let's install it:
@@ -91,12 +104,6 @@ Reading package lists... Done
One minute later, `figlet` is installed!
---
## Try to run our freshly installed program
The `figlet` program takes a message as parameter.
```bash
root@04c0bb0a6c07:/# figlet hello
_ _ _
@@ -106,30 +113,11 @@ root@04c0bb0a6c07:/# figlet hello
|_| |_|\___|_|_|\___/
```
Beautiful! .emoji[😍]
---
## Counting packages in the container
## Exiting our container
Let's check how many packages are installed there.
```bash
root@04c0bb0a6c07:/# dpkg -l | wc -l
190
```
* `dpkg -l` lists the packages installed in our container
* `wc -l` counts them
How many packages do we have on our host?
---
## Counting packages on the host
Exit the container by logging out of the shell, like you would usually do.
Just exit the shell, like you would usually do.
(E.g. with `^D` or `exit`)
@@ -137,36 +125,10 @@ Exit the container by logging out of the shell, like you would usually do.
root@04c0bb0a6c07:/# exit
```
Now, try to:
* run `dpkg -l | wc -l`. How many packages are installed?
* run `figlet`. Does that work?
---
## Host and containers are independent things
* We ran an `ubuntu` container on an `ubuntu` host.
* But they have different, independent packages.
* Installing something on the host doesn't expose it to the container.
* And vice-versa.
* We can run *any container* on *any host*.
---
## Where's our container?
* Our container is now in a *stopped* state.
* It still exists on disk, but all compute resources have been freed up.
* We will see later how to get back to that container.
---
## Starting another container

View File

@@ -1,9 +1,9 @@
class: title
# Understanding Docker images
# Understanding Docker Images
![image](images/title-understanding-docker-images.png)
![image](images/image.png)
---

View File

@@ -1,8 +1,10 @@
class: title
# Installing Docker
# Install Docker
![install](images/title-installing-docker.jpg)
![install](images/install.jpg)
---

View File

@@ -1,9 +1,9 @@
class: title
# Local development workflow with Docker
# Local Development Workflow with Docker
![Construction site](images/title-local-development-workflow-with-docker.jpg)
![construction](images/construction.jpg)
---
@@ -17,7 +17,7 @@ At the end of this section, you will be able to:
---
## Containerized local development environments
## Using a Docker container for local development
We want to solve the following issues:
@@ -31,25 +31,28 @@ By using Docker containers, we will get a consistent development environment.
---
## Working on the "namer" application
## Our "namer" application
* We have to work on some application whose code is at:
* The code is available on https://github.com/jpetazzo/namer.
https://github.com/jpetazzo/namer.
* The image jpetazzo/namer is automatically built by the Docker Hub.
* What is it? We don't know yet!
* Let's download the code.
Let's run it with:
```bash
$ git clone https://github.com/jpetazzo/namer
$ docker run -dP jpetazzo/namer
```
Check the port number with `docker ps` and open the application.
---
## Looking at the code
## Let's look at the code
Let's download our application's source code.
```bash
$ git clone https://github.com/jpetazzo/namer
$ cd namer
$ ls -1
company_name_generator.rb
@@ -59,13 +62,11 @@ Dockerfile
Gemfile
```
--
Aha, a `Gemfile`! This is Ruby. Probably. We know this. Maybe?
---
## Looking at the `Dockerfile`
## Where's my code?
According to the Dockerfile, the code is copied into `/src` :
```dockerfile
FROM ruby
@@ -79,85 +80,9 @@ CMD ["rackup", "--host", "0.0.0.0"]
EXPOSE 9292
```
* This application is using a base `ruby` image.
* The code is copied in `/src`.
* Dependencies are installed with `bundler`.
* The application is started with `rackup`.
* It is listening on port 9292.
We want to make changes *inside the container* without rebuilding it each time.
---
## Building and running the "namer" application
* Let's build the application with the `Dockerfile`!
--
```bash
$ docker build -t namer .
```
--
* Then run it. *We need to expose its ports.*
--
```bash
$ docker run -dP namer
```
--
* Check on which port the container is listening.
--
```bash
$ docker ps -l
```
---
## Connecting to our application
* Point our browser to our Docker node, on the port allocated to the container.
--
* Hit "reload" a few times.
--
* This is an enterprise-class, carrier-grade, ISO-compliant company name generator!
(With 50% more bullshit than the average competition!)
(Wait, was that 50% more, or 50% less? *Anyway!*)
![web application 1](images/webapp-in-blue.png)
---
## Making changes to the code
Option 1:
* Edit the code locally
* Rebuild the image
* Re-run the container
Option 2:
* Enter the container (with `docker exec`)
* Install an editor
* Make changes from within the container
Option 3:
* Use a *volume* to mount local files into the container
* Make changes locally
* Changes are reflected into the container
For that, we will use a *volume*.
---
@@ -166,16 +91,16 @@ Option 3:
We will tell Docker to map the current directory to `/src` in the container.
```bash
$ docker run -d -v $(pwd):/src -P namer
$ docker run -d -v $(pwd):/src -p 80:9292 jpetazzo/namer
```
* `-d`: the container should run in detached mode (in the background).
* `-v`: the following host directory should be mounted inside the container.
* `-P`: publish all the ports exposed by this image.
* `-p`: connections to port 80 on the host should be routed to port 9292 in the container.
* `namer` is the name of the image we will run.
* `jpetazzo/namer` is the name of the image we will run.
* We don't specify a command to run because is is already set in the Dockerfile.
@@ -183,15 +108,14 @@ $ docker run -d -v $(pwd):/src -P namer
## Mounting volumes inside containers
The `-v` flag mounts a directory from your host into your Docker container.
The flag structure is:
The `-v` flag mounts a directory from your host into your Docker
container. The flag structure is:
```bash
[host-path]:[container-path]:[rw|ro]
```
* If `[host-path]` or `[container-path]` doesn't exist it is created.
* If [host-path] or [container-path] doesn't exist it is created.
* You can control the write status of the volume with the `ro` and
`rw` options.
@@ -204,15 +128,27 @@ There will be a full chapter about volumes!
## Testing the development container
* Check the port used by our new container.
Now let us see if our new container is running.
```bash
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
045885b68bc5 namer rackup 3 seconds ago Up ... 0.0.0.0:32770->9292/tcp ...
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
045885b68bc5 trai... rackup 3 seconds ago Up ... 0.0.0.0:80->9292/tcp ...
```
* Open the application in your web browser.
---
## Viewing our application
Now let's browse to our web application on:
```bash
http://<yourHostIP>:80
```
We can see our company naming application.
![web application 1](images/webapp1.png)
---
@@ -238,121 +174,53 @@ color: red;
---
## Viewing our changes
## Refreshing our application
* Reload the application in our browser.
Now let's refresh our browser:
--
```bash
http://<yourHostIP>:80
```
* The color should have changed.
We can see the updated color of our company naming application.
![web application 2](images/webapp-in-red.png)
![web application 2](images/webapp2.png)
---
## Understanding volumes
## Improving the workflow with Compose
* Volumes are *not* copying or synchronizing files between the host and the container.
* You can also start the container with the following command:
* Volumes are *bind mounts*: a kernel mechanism associating a path to another.
```bash
$ docker-compose up -d
```
* Bind mounts are *kind of* similar to symbolic links, but at a very different level.
* This works thanks to the Compose file, `docker-compose.yml`:
* Changes made on the host or on the container will be visible on the other side.
(Since under the hood, it's the same file on both anyway.)
```yaml
www:
build: .
volumes:
- .:/src
ports:
- 80:9292
```
---
## Trash your servers and burn your code
## Why Compose?
*(This is the title of a
[2013 blog post](http://chadfowler.com/2013/06/23/immutable-deployments.html)
by Chad Fowler, where he explains the concept of immutable infrastructure.)*
* Specifying all those "docker run" parameters is tedious.
--
* And error-prone.
* Let's mess up majorly with our container.
* We can "encode" those parameters in a "Compose file."
(Remove files or whatever.)
* Now, how can we fix this?
--
* Our old container (with the blue version of the code) is still running.
* See on which port it is exposed:
```bash
docker ps
```
* Point our browser to it to confirm that it still works fine.
---
## Immutable infrastructure in a nutshell
* Instead of *updating* a server, we deploy a new one.
* This might be challenging with classical servers, but it's trivial with containers.
* In fact, with Docker, the most logical workflow is to build a new image and run it.
* If something goes wrong with the new image, we can always restart the old one.
* We can even keep both versions running side by side.
If this pattern sounds interesting, you might want to read about *blue/green deployment*
and *canary deployments*.
---
## Improving the workflow
The workflow that we showed is nice, but it requires us to:
* keep track of all the `docker run` flags required to run the container,
* inspect the `Dockerfile` to know which path(s) to mount,
* write scripts to hide that complexity.
There has to be a better way!
---
## Docker Compose to the rescue
* Docker Compose allows us to "encode" `docker run` parameters in a YAML file.
* Here is the `docker-compose.yml` file that we can use for our "namer" app:
```yaml
www:
build: .
volumes:
- .:/src
ports:
- 80:9292
```
* Try it:
```bash
$ docker-compose up -d
```
---
## Working with Docker Compose
* When you see a `docker-compose.yml` file, you can use `docker-compose up`.
* It can build images and run them with the required parameters.
* When you see a `docker-compose.yml` file, you know that you can use `docker-compose up`.
* Compose can also deal with complex, multi-container apps.
(More on this later!)
<br/>(More on this later.)
---

View File

@@ -49,7 +49,7 @@
---
## Multi-stage builds for our C program
## Implementing multi-stage builds for our C program
We will change our Dockerfile to:
@@ -65,7 +65,7 @@ The resulting Dockerfile is on the next slide.
---
## Multi-stage build `Dockerfile`
## Revised Dockerfile implementing multi-stage build
Here is the final Dockerfile:
@@ -89,7 +89,7 @@ docker run hellomultistage
---
## Comparing single/multi-stage build image sizes
## Comparing single-stage and multi-stage image sizes
List our images with `docker images`, and check the size of:

View File

@@ -3,7 +3,7 @@ class: title
# Naming and inspecting containers
![Markings on container door](images/title-naming-and-inspecting-containers.jpg)
![Markings on container door](images/containermarkings.jpg)
---
@@ -85,8 +85,16 @@ The `docker inspect` command will output a very detailed JSON map.
```bash
$ docker inspect <containerID>
[{
...
(many pages of JSON here)
"AppArmorProfile": "",
"Args": [],
"Config": {
"AttachStderr": true,
"AttachStdin": false,
"AttachStdout": true,
"Cmd": [
"bash"
],
"CpuShares": 0,
...
```

View File

@@ -1,84 +0,0 @@
# Container network drivers
The Docker Engine supports many different network drivers.
The built-in drivers include:
* `bridge` (default)
* `none`
* `host`
* `container`
The driver is selected with `docker run --net ...`.
The different drivers are explained with more details on the following slides.
---
## The default bridge
* By default, the container gets a virtual `eth0` interface.
<br/>(In addition to its own private `lo` loopback interface.)
* That interface is provided by a `veth` pair.
* It is connected to the Docker bridge.
<br/>(Named `docker0` by default; configurable with `--bridge`.)
* Addresses are allocated on a private, internal subnet.
<br/>(Docker uses 172.17.0.0/16 by default; configurable with `--bip`.)
* Outbound traffic goes through an iptables MASQUERADE rule.
* Inbound traffic goes through an iptables DNAT rule.
* The container can have its own routes, iptables rules, etc.
---
## The null driver
* Container is started with `docker run --net none ...`
* It only gets the `lo` loopback interface. No `eth0`.
* It can't send or receive network traffic.
* Useful for isolated/untrusted workloads.
---
## The host driver
* Container is started with `docker run --net host ...`
* It sees (and can access) the network interfaces of the host.
* It can bind any address, any port (for ill and for good).
* Network traffic doesn't have to go through NAT, bridge, or veth.
* Performance = native!
Use cases:
* Performance sensitive applications (VOIP, gaming, streaming...)
* Peer discovery (e.g. Erlang port mapper, Raft, Serf...)
---
## The container driver
* Container is started with `docker run --net container:id ...`
* It re-uses the network stack of another container.
* It shares with this other container the same interfaces, IP address(es), routes, iptables rules, etc.
* Those containers can communicate over their `lo` interface.
<br/>(i.e. one can bind to 127.0.0.1 and the others can connect to it.)

View File

@@ -1,102 +0,0 @@
# Publishing images to the Docker Hub
We have built our first images.
We can now publish it to the Docker Hub!
*You don't have to do the exercises in this section,
because they require an account on the Docker Hub, and we
don't want to force anyone to create one.*
*Note, however, that creating an account on the Docker Hub
is free (and doesn't require a credit card), and hosting
public images is free as well.*
---
## Logging into our Docker Hub account
* This can be done from the Docker CLI:
```bash
docker login
```
.warning[When running Docker4Mac, Docker4Windows, or
Docker on a Linux workstation, it can (and will when
possible) integrate with your system's keyring to
store your credentials securely. However, on most Linux
servers, it will store your credentials in `~/.docker/config`.]
---
## Image tags and registry addresses
* Docker images tags are like Git tags and branches.
* They are like *bookmarks* pointing at a specific image ID.
* Tagging an image doesn't *rename* an image: it adds another tag.
* When pushing an image to a registry, the registry address is in the tag.
Example: `registry.example.net:5000/image`
* What about Docker Hub images?
--
* `jpetazzo/clock` is, in fact, `index.docker.io/jpetazzo/clock`
* `ubuntu` is, in fact, `library/ubuntu`, i.e. `index.docker.io/library/ubuntu`
---
## Tagging an image to push it on the Hub
* Let's tag our `figlet` image (or any other to our liking):
```bash
docker tag figlet jpetazzo/figlet
```
* And push it to the Hub:
```bash
docker push jpetazzo/figlet
```
* That's it!
--
* Anybody can now `docker run jpetazzo/figlet` anywhere.
---
## The goodness of automated builds
* You can link a Docker Hub repository with a GitHub or BitBucket repository
* Each push to GitHub or BitBucket will trigger a build on Docker Hub
* If the build succeeds, the new image is available on Docker Hub
* You can map tags and branches between source and container images
* If you work with public repositories, this is free
---
class: extra-details
## Setting up an automated build
* We need a Dockerized repository!
* Let's go to https://github.com/jpetazzo/trainingwheels and fork it.
* Go to the Docker Hub (https://hub.docker.com/).
* Select "Create" in the top-right bar, and select "Create Automated Build."
* Connect your Docker Hub account to your GitHub account.
* Select your user and the repository that we just forked.
* Create.
* Then go to "Build Settings."
* Put `/www` in "Dockerfile Location" (or whichever directory the Dockerfile is in).
* Click "Trigger" to build the repository immediately (without waiting for a git push).
* Subsequent builds will happen automatically, thanks to GitHub hooks.

View File

@@ -2,7 +2,7 @@ class: title
# Our training environment
![SSH terminal](images/title-our-training-environment.jpg)
![SSH terminal](images/ssh.jpg)
---

View File

@@ -1,9 +1,9 @@
class: title
# Working with volumes
# Working with Volumes
![volume](images/title-working-with-volumes.jpg)
![volume](images/volume.jpg)
---
@@ -19,7 +19,7 @@ At the end of this section, you will be able to:
---
## Working with volumes
## Working with Volumes
Docker volumes can be used to achieve many things, including:
@@ -95,7 +95,7 @@ We will see an example in the following slides.
class: extra-details
## Sharing app server logs with another container
## Sharing web application logs with another container
Let's start a Tomcat container:
@@ -311,9 +311,9 @@ QUIT
---
## Volumes lifecycle
## What happens when you remove containers with volumes?
* When you remove a container, its volumes are kept around.
* Volumes are kept around.
* You can list them with `docker volume ls`.
@@ -371,9 +371,9 @@ $ docker inspect <yourContainerID>
---
## Sharing a single file
## Sharing a single file between the host and a container
The same `-v` flag can be used to share a single file (instead of a directory).
The same `-v` flag can be used to share a single file.
One of the most interesting examples is to share the Docker control socket.
@@ -381,11 +381,8 @@ One of the most interesting examples is to share the Docker control socket.
$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker sh
```
From that container, you can now run `docker` commands communicating with
the Docker Engine running on the host. Try `docker ps`!
.warning[Since that container has access to the Docker socket, it
has root-like access to the host.]
Warning: when using such mounts, the container gains root-like access to the host.
It can potentially do bad things.
---

View File

@@ -1,32 +0,0 @@
title: |
Docker + Kubernetes = <3
chat: "[Slack](https://docker.slack.com/messages/C83M572J2)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
exclude:
- self-paced
chapters:
- common/title.md
- logistics.md
- common/intro.md
- common/toc.md
- - common/prereqs.md
- kube/versions-k8s.md
- common/sampleapp.md
- - kube/concepts-k8s.md
- common/declarative.md
- kube/declarative.md
- kube/kubenet.md
- kube/kubectlget.md
- kube/setup-k8s.md
- kube/kubectlrun.md
- - kube/kubectlexpose.md
- kube/ourapponkube.md
- kube/dashboard.md
- - kube/kubectlscale.md
- kube/daemonset.md
- kube/rollout.md
- kube/whatsnext.md
- common/thankyou.md

View File

@@ -1,33 +0,0 @@
title: |
Deploying and Scaling Microservices
with Docker and Kubernetes
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
exclude:
- in-person
chapters:
- common/title.md
#- logistics.md
- common/intro.md
- common/toc.md
- - common/prereqs.md
- kube/versions-k8s.md
- common/sampleapp.md
- - kube/concepts-k8s.md
- common/declarative.md
- kube/declarative.md
- kube/kubenet.md
- kube/kubectlget.md
- kube/setup-k8s.md
- kube/kubectlrun.md
- - kube/kubectlexpose.md
- kube/ourapponkube.md
- kube/dashboard.md
- - kube/kubectlscale.md
- kube/daemonset.md
- kube/rollout.md
- kube/whatsnext.md
- common/thankyou.md

View File

@@ -208,6 +208,89 @@ Yes!
class: pic
![Node, pod, container](images/k8s-arch3-thanks-weave.png)
![Node, pod, container](images/thanks-weave.png)
(Diagram courtesy of Weave Works, used with permission.)
---
# Declarative vs imperative
- Kubernetes puts a very strong emphasis on being *declarative*
- Declarative:
*I would like a cup of tea.*
- Imperative:
*Boil some water. Pour it in a teapot. Add tea leaves. Steep for a while. Serve in cup.*
--
- Declarative seems simpler at first ...
--
- ... As long as you know how to brew tea
---
## Declarative vs imperative
- What declarative would really be:
*I want a cup of tea, obtained by pouring an infusion¹ of tea leaves in a cup.*
--
*¹An infusion is obtained by letting the object steep a few minutes in hot² water.*
--
*²Hot liquid is obtained by pouring it in an appropriate container³ and setting it on a stove.*
--
*³Ah, finally, containers! Something we know about. Let's get to work, shall we?*
--
.footnote[Did you know there was an [ISO standard](https://en.wikipedia.org/wiki/ISO_3103)
specifying how to brew tea?]
---
## Declarative vs imperative
- Imperative systems:
- simpler
- if a task is interrupted, we have to restart from scratch
- Declarative systems:
- if a task is interrupted (or if we show up to the party half-way through),
we can figure out what's missing and do only what's necessary
- we need to be able to *observe* the system
- ... and compute a "diff" between *what we have* and *what we want*
---
## Declarative vs imperative in Kubernetes
- Virtually everything we create in Kubernetes is created from a *spec*
- Watch for the `spec` fields in the YAML files later!
- The *spec* describes *how we want the thing to be*
- Kubernetes will *reconcile* the current state with the spec
<br/>(technically, this is done by a number of *controllers*)
- When we want to change some resource, we update the *spec*
- Kubernetes will then *converge* that resource

View File

@@ -336,7 +336,7 @@ Of course, option 2 offers more learning opportunities. Right?
---
## We've put resources in your resources
## We've put resources in your resources all the way down
- Reminder: a daemon set is a resource that creates more resources!

View File

@@ -1,14 +0,0 @@
## Declarative vs imperative in Kubernetes
- Virtually everything we create in Kubernetes is created from a *spec*
- Watch for the `spec` fields in the YAML files later!
- The *spec* describes *how we want the thing to be*
- Kubernetes will *reconcile* the current state with the spec
<br/>(technically, this is done by a number of *controllers*)
- When we want to change some resource, we update the *spec*
- Kubernetes will then *converge* that resource

15
slides/kube/intro-ks.md Normal file
View File

@@ -0,0 +1,15 @@
## About these slides
- Your one-stop shop to awesomeness:
http://container.training/
- The content that you're viewing right now is in a public GitHub repository:
https://github.com/jpetazzo/orchestration-workshop
- Typos? Mistakes? Questions? Feel free to hover over the bottom of the slide ...
--
.footnote[👇 Try it! The source file will be shown and you can view it on GitHub and fork and edit it.]

View File

@@ -42,7 +42,7 @@
---
## Obtaining machine-readable output
## From human-readable to machine-readable output
- `kubectl get` can output JSON, YAML, or be directly formatted

View File

@@ -55,7 +55,7 @@ We should see the following things:
---
## What are these different things?
## Deployments, replica sets, and replication controllers
- A *deployment* is a high-level construct
@@ -236,13 +236,14 @@ Unfortunately, `--follow` cannot (yet) be used to stream the logs from multiple
class: title
Meanwhile,
.small[
Meanwhile, at the Google NOC ...
.small[
Why the hell
<br/>
at the Google NOC ...
are we getting 1000 packets per second
<br/>
<br/>
.small[“Why the hell]
<br/>
.small[are we getting 1000 packets per second]
<br/>
.small[of ICMP ECHO traffic from EC2 ?!?”]
of ICMP ECHO traffic from EC2 ?!?
]
]

View File

@@ -40,7 +40,7 @@
---
## Kubernetes network model: the less good
## Kubernetes network model: the bad and the ugly
- Everything can reach everything

View File

@@ -179,7 +179,7 @@ The curl command should now output:
- Go to the `stacks` directory:
```bash
cd ~/container.training/stacks
cd ~/orchestration-workshop/stacks
```
- Build and push the images:

View File

@@ -1,22 +1,37 @@
# Pre-requirements
- Be comfortable with the UNIX command line
- Computer with internet connection and a web browser
- navigating directories
- For instructor-led workshops: an SSH client to connect to remote machines
- editing files
- on Linux, OS X, FreeBSD... you are probably all set
- a little bit of bash-fu (environment variables, loops)
- on Windows, get [putty](http://www.putty.org/),
Microsoft [Win32 OpenSSH](https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH),
[Git BASH](https://git-for-windows.github.io/), or
[MobaXterm](http://mobaxterm.mobatek.net/)
- Some Docker knowledge
- A tiny little bit of Docker knowledge
- `docker run`, `docker ps`, `docker build`
(that's totally OK if you're not a Docker expert!)
- ideally, you know how to write a Dockerfile and build it
<br/>
(even if it's a `FROM` line and a couple of `RUN` commands)
---
- It's totally OK if you are not a Docker expert!
class: in-person, extra-details
## Nice-to-haves
- [Mosh](https://mosh.org/) instead of SSH, if your internet connection tends to lose packets
<br/>(available with `(apt|yum|brew) install mosh`; then connect with `mosh user@host`)
- [GitHub](https://github.com/join) account
<br/>(if you want to fork the repo)
- [Slack](https://community.docker.com/registrations/groups/4316) account
<br/>(to join the conversation after the workshop)
- [Docker Hub](https://hub.docker.com) account
<br/>(it's one way to distribute images on your cluster)
---
@@ -38,7 +53,7 @@ class: extra-details
- The whole workshop is hands-on
- We are going to build, ship, and run containers!
- We will see Docker and Kubernetes in action
- You are invited to reproduce all the demos
@@ -47,25 +62,25 @@ class: extra-details
.exercise[
- This is the stuff you're supposed to do!
- Go to [container.training](http://container.training/) to view these slides
- Join the chat room on @@CHAT@@
<!-- ```open http://container.training/``` -->
]
---
class: in-person
## Where are we going to run our containers?
---
class: in-person, pic
class: pic, in-person
![You get five VMs](images/you-get-five-vms.jpg)
<!--
```bash
kubectl get all -o name | grep -v services/kubernetes | xargs -n1 kubectl delete
```
-->
---
class: in-person
@@ -73,82 +88,22 @@ class: in-person
## You get five VMs
- Each person gets 5 private VMs (not shared with anybody else)
- They'll remain up for the duration of the workshop
- Kubernetes has been deployed and pre-configured on these machines
- They'll remain up until the day after the tutorial
- You should have a little card with login+password+IP addresses
- You can automatically SSH from one VM to another
- The nodes have aliases: `node1`, `node2`, etc.
---
class: in-person
## Why don't we run containers locally?
- Installing that stuff can be hard on some machines
(32 bits CPU or OS... Laptops without administrator access... etc.)
- *"The whole team downloaded all these container images from the WiFi!
<br/>... and it went great!"* (Literally no-one ever)
- All you need is a computer (or even a phone or tablet!), with:
- an internet connection
- a web browser
- an SSH client
---
class: in-person
## SSH clients
- On Linux, OS X, FreeBSD... you are probably all set
- On Windows, get one of these:
- [putty](http://www.putty.org/)
- Microsoft [Win32 OpenSSH](https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH)
- [Git BASH](https://git-for-windows.github.io/)
- [MobaXterm](http://mobaxterm.mobatek.net/)
- On Android, [JuiceSSH](https://juicessh.com/)
([Play Store](https://play.google.com/store/apps/details?id=com.sonelli.juicessh))
works pretty well
- Nice-to-have: [Mosh](https://mosh.org/) instead of SSH, if your internet connection tends to lose packets
<br/>(available with `(apt|yum|brew) install mosh`; then connect with `mosh user@host`)
---
class: in-person
## Connecting to our lab environment
.exercise[
- Log into the first VM (`node1`) with SSH or MOSH
<!--
```bash
for N in $(seq 1 5); do
ssh -o StrictHostKeyChecking=no node$N true
done
```
```bash
if which kubectl; then
kubectl get all -o name | grep -v services/kubernetes | xargs -n1 kubectl delete
fi
```
-->
- Log into the first VM (`node1`) with SSH or MOSH
- Check that you can SSH (without password) to `node2`:
```bash
ssh node2
@@ -159,34 +114,10 @@ fi
]
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://medium.com/@marcosnils/introducing-pwk-play-with-k8s-159fcfeb787b)
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://github.com/jpetazzo/container.training/tree/master/prepare-vms))
Bigger setup effort; ideal for group training
---
## 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`

View File

@@ -51,7 +51,7 @@
- Go to the `stack` directory:
```bash
cd ~/container.training/stacks
cd ~/orchestration-workshop/stacks
```
- Edit `dockercoins/worker/worker.py`, update the `sleep` line to sleep 1 second
@@ -68,7 +68,7 @@
---
## Rolling out the new `worker` service
## Rolling out the new version of the `worker` service
.exercise[

View File

@@ -1,8 +1,8 @@
## Brand new versions!
- Kubernetes 1.8
- Docker Engine 17.11
- Docker Compose 1.17
- Docker Engine 17.10
- Docker Compose 1.16
.exercise[

View File

@@ -1,19 +0,0 @@
## Logistics
- Hi!
- This will run from 9am to 1:30pm
- We'll have a coffee break around 10:30am
- Food will be served (to be confirmed)
- Feel free (please do) interrupt me to ask questions
- There is a Slack channel for questions: #kube
- Chapters are separated by pictures of containers
- The slides are targeting a wide audience
(i.e. people who might not have the Docker expertise that you have)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python
# transforms a YAML manifest into a HTML workshop file
import glob
@@ -7,7 +7,7 @@ import os
import re
import string
import subprocess
import sys
import sys
import yaml
@@ -26,14 +26,6 @@ def anchor(title):
return "toc-" + title
def interstitials_generator():
images = [url.strip() for url in open("interstitials.txt") if url.strip()]
while True:
for image in images:
yield image
interstitials = interstitials_generator()
def insertslide(markdown, title):
title_position = markdown.find("\n# {}\n".format(title))
slide_position = markdown.rfind("\n---\n", 0, title_position+1)
@@ -41,37 +33,18 @@ def insertslide(markdown, title):
before = markdown[:slide_position]
toclink = "toc-chapter-{}".format(title2path[title][0])
_titles_ = [""] + all_titles + [""]
currentindex = _titles_.index(title)
previouslink = anchor(_titles_[currentindex-1])
nextlink = anchor(_titles_[currentindex+1])
interstitial = interstitials.next()
extra_slide = """
---
class: pic
.interstitial[![Image separating from the next chapter]({interstitial})]
---
name: {anchor}
class: title
{title}
.nav[
[Previous section](#{previouslink})
|
[Back to table of contents](#{toclink})
|
[Next section](#{nextlink})
]
.nav[[Back to table of contents](#{toclink})]
.debug[(automatically generated title slide)]
""".format(anchor=anchor(title), interstitial=interstitial, title=title, toclink=toclink, previouslink=previouslink, nextlink=nextlink)
""".format(anchor=anchor(title), title=title, toclink=title2chapter[title])
after = markdown[slide_position:]
return before + extra_slide + after
@@ -85,10 +58,10 @@ def flatten(titles):
yield title
def generatefromyaml(manifest, filename):
def generatefromyaml(manifest):
manifest = yaml.load(manifest)
markdown, titles = processchapter(manifest["chapters"], filename)
markdown, titles = processchapter(manifest["chapters"], "(inline)")
logging.debug("Found {} titles.".format(len(titles)))
toc = gentoc(titles)
markdown = markdown.replace("@@TOC@@", toc)
@@ -101,51 +74,38 @@ def generatefromyaml(manifest, filename):
logging.warning("'exclude' is empty.")
exclude = ",".join('"{}"'.format(c) for c in exclude)
# Insert build info. This is super hackish.
markdown = markdown.replace(
".debug[",
".debug[\n```\n{}\n```\n\nThese slides have been built from commit: {}\n\n".format(dirtyfiles, commit),
1)
markdown = markdown.replace("@@TITLE@@", manifest["title"].replace("\n", "<br/>"))
html = open("workshop.html").read()
html = html.replace("@@MARKDOWN@@", markdown)
html = html.replace("@@EXCLUDE@@", exclude)
html = html.replace("@@CHAT@@", manifest["chat"])
html = html.replace("@@TITLE@@", manifest["title"].replace("\n", " "))
html = html.replace("@@TITLE@@", manifest["title"])
return html
# Maps a section title (the string just after "^# ") to its position
# in the table of content (as a (chapter,part,subpart,...) tuple).
title2path = {}
path2title = {}
all_titles = []
title2chapter = {}
# "tree" is a list of titles, potentially nested.
def gentoc(tree, path=()):
if not tree:
def gentoc(titles, depth=0, chapter=0):
if not titles:
return ""
if isinstance(tree, str):
title = tree
title2path[title] = path
path2title[path] = title
all_titles.append(title)
logging.debug("Path {} Title {}".format(path, title))
return "- [{}](#{})".format(title, anchor(title))
if isinstance(tree, list):
if len(path) == 0:
return "\n---\n".join(gentoc(subtree, path+(i+1,)) for (i,subtree) in enumerate(tree))
elif len(path) == 1:
chapterslide = "name: toc-chapter-{n}\n\n## Chapter {n}\n\n".format(n=path[0])
for (i,subtree) in enumerate(tree):
chapterslide += gentoc(subtree, path+(i+1,)) + "\n\n"
chapterslide += ".debug[(auto-generated TOC)]"
return chapterslide
if isinstance(titles, str):
title2chapter[titles] = "toc-chapter-1"
logging.debug("Chapter {} Title {}".format(chapter, titles))
return " "*(depth-2) + "- [{}](#{})\n".format(titles, anchor(titles))
if isinstance(titles, list):
if depth==0:
sep = "\n\n.debug[(auto-generated TOC)]\n---\n\n"
head = ""
tail = ""
elif depth==1:
sep = "\n"
head = "name: toc-chapter-{}\n\n## Chapter {}\n\n".format(chapter, chapter)
tail = ""
else:
return "\n\n".join(gentoc(subtree, path+(i+1,)) for (i,subtree) in enumerate(tree))
sep = "\n"
head = ""
tail = ""
return head + sep.join(gentoc(t, depth+1, c+1) for (c,t) in enumerate(titles)) + tail
# Arguments:
@@ -186,8 +146,8 @@ try:
if "BRANCH" in os.environ:
branch = os.environ["BRANCH"]
else:
branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
branch = branch.strip()
branch = subprocess.check_output(["git", "status", "--short", "--branch"])
branch = branch[3:].split("...")[0]
base = subprocess.check_output(["git", "rev-parse", "--show-prefix"])
base = base.strip().strip("/")
urltemplate = ("{repo}/tree/{branch}/{base}/{filename}"
@@ -195,16 +155,6 @@ try:
except:
logging.exception("Could not generate repository URL; generating local URLs instead.")
urltemplate = "file://{pwd}/{filename}".format(pwd=os.environ["PWD"], filename="{}")
try:
commit = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
except:
logging.exception("Could not figure out HEAD commit.")
commit = "??????"
try:
dirtyfiles = subprocess.check_output(["git", "status", "--porcelain"])
except:
logging.exception("Could not figure out repository cleanliness.")
dirtyfiles = "?? git status --porcelain failed"
def makelink(filename):
if os.path.isfile(filename):
@@ -213,15 +163,6 @@ def makelink(filename):
else:
return filename
if len(sys.argv) != 2:
logging.error("This program takes one and only one argument: the YAML file to process.")
else:
filename = sys.argv[1]
if filename == "-":
filename = "<stdin>"
manifest = sys.stdin
else:
manifest = open(filename)
logging.info("Processing {}...".format(filename))
sys.stdout.write(generatefromyaml(manifest, filename))
logging.info("Processed {}.".format(filename))
sys.stdout.write(generatefromyaml(sys.stdin))
logging.info("Done")

View File

@@ -1,11 +0,0 @@
#!/bin/sh
if ! [ -f "$1" ]; then
echo "File $1 not found, aborting."
exit 1
fi
if [ -f "$2" ]; then
echo "File $2 already exists, aborting."
exit 1
fi
git mv "$1" "$2"
sed -i "" "s,$1,$2," */*.md

View File

@@ -9,63 +9,13 @@ page.onResourceError = function(resourceError) {
console.log('ResourceError: ' + resourceError.url);
}
page.onConsoleMessage = function(msg) {
//console.log('Console: ' +msg);
}
console.log('DEBUG Loading: ' + url);
console.log('Loading: ' + url);
page.open(url, function(status) {
console.log('DEBUG Loaded: ' + url + '(' + status + ')');
/* analyze will be an object with:
*
* titles
* A dict with all the titles that are too high
* (i.e. because they have been broken across multiple
* lines because they are too long)
*
* slides
* A dict with the slides that are too high
*
* n_slides
* Number of slides found
*/
var analyze = page.evaluate(function() {
var ret = {}, i, n = slideshow.getSlideCount();
ret = [];
for (i=1; i<=n; i++) {
console.log('DEBUG Current slide: ' + i + '/' + n);
var visible_slide = document.getElementsByClassName('remark-visible')[0];
var debug = visible_slide.getElementsByClassName('debug');
if (debug.length==0) {
debug = '?';
}
else {
debug = debug[0].textContent;
}
var slide_desc = 'Slide ' + i + '/' + n + ' (' + debug + ')';
['h1', 'h2'].forEach(function(tag) {
var titles = visible_slide.getElementsByTagName(tag);
console.log('DEBUG Found ' + titles.length + ' titles with tag ' + tag);
titles.forEach(function(t) {
if (t.clientHeight>60) {
ret.push(slide_desc + ' has a long title: ' + t.textContent);
}
});
});
var scaler = visible_slide.getElementsByClassName('remark-slide-scaler')[0];
var slide = scaler.getElementsByClassName('remark-slide')[0];
if (slide.clientHeight > scaler.clientHeight) {
ret.push(slide_desc + ' is too long');
}
slideshow.gotoNextSlide();
}
ret.push('Deck has ' + n + ' slides');
return ret;
console.log('Loaded: ' + url + '(' + status + ')');
var slides = page.evaluate(function() {
return document.getElementsByClassName('remark-slide-container');
});
analyze.forEach(function(msg) {
console.log(msg);
});
console.log('DEBUG Done: ' + url + '(' + status + ')');
console.log('Number of slides: ' + slides.length);
console.log('Done: ' + url + '(' + status + ')');
phantom.exit();
});

View File

@@ -1,53 +0,0 @@
title: |
Container Orchestration
with Docker and Swarm
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
exclude:
- self-paced
- snap
- btp-auto
- benchmarking
- elk-manual
- prom-manual
chapters:
- common/title.md
- logistics.md
- common/intro.md
- common/toc.md
- - common/prereqs.md
- swarm/versions.md
- common/sampleapp.md
- swarm/swarmkit.md
- common/declarative.md
- swarm/swarmmode.md
- swarm/creatingswarm.md
#- swarm/machine.md
- swarm/morenodes.md
- - swarm/firstservice.md
- swarm/ourapponswarm.md
- swarm/hostingregistry.md
- swarm/testingregistry.md
- swarm/btp-manual.md
- swarm/swarmready.md
- swarm/compose2swarm.md
- swarm/updatingservices.md
#- swarm/rollingupdates.md
- swarm/healthchecks.md
- - swarm/operatingswarm.md
- swarm/netshoot.md
- swarm/ipsec.md
- swarm/swarmtools.md
- swarm/security.md
- swarm/secrets.md
- swarm/encryptionatrest.md
- swarm/leastprivilege.md
- swarm/apiscope.md
- - swarm/logging.md
- swarm/metrics.md
- swarm/stateful.md
- swarm/extratips.md
- common/thankyou.md

View File

@@ -1,53 +0,0 @@
title: |
Container Orchestration
with Docker and Swarm
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
#chat: "[Gitter](https://gitter.im/jpetazzo/workshop-yyyymmdd-city)"
exclude:
- self-paced
- snap
- btp-manual
- benchmarking
- elk-manual
- prom-manual
chapters:
- common/title.md
- logistics.md
- common/intro.md
- common/toc.md
- - common/prereqs.md
- swarm/versions.md
- common/sampleapp.md
- swarm/swarmkit.md
- common/declarative.md
- swarm/swarmmode.md
- swarm/creatingswarm.md
#- swarm/machine.md
- swarm/morenodes.md
- - swarm/firstservice.md
- swarm/ourapponswarm.md
#- swarm/hostingregistry.md
#- swarm/testingregistry.md
#- swarm/btp-manual.md
#- swarm/swarmready.md
- swarm/compose2swarm.md
- swarm/updatingservices.md
#- swarm/rollingupdates.md
#- swarm/healthchecks.md
- - swarm/operatingswarm.md
#- swarm/netshoot.md
#- swarm/ipsec.md
#- swarm/swarmtools.md
- swarm/security.md
#- swarm/secrets.md
#- swarm/encryptionatrest.md
- swarm/leastprivilege.md
- swarm/apiscope.md
- swarm/logging.md
- swarm/metrics.md
#- swarm/stateful.md
#- swarm/extratips.md
- common/thankyou.md

View File

@@ -1,62 +0,0 @@
title: |
Container Orchestration
with Docker and Swarm
chat: "[Slack](https://dockercommunity.slack.com/messages/C7GKACWDV)"
exclude:
- in-person
- btp-auto
chapters:
- common/title.md
#- common/logistics.md
- common/intro.md
- common/toc.md
- - common/prereqs.md
- swarm/versions.md
- |
name: part-1
class: title, self-paced
Part 1
- common/sampleapp.md
- swarm/swarmkit.md
- common/declarative.md
- swarm/swarmmode.md
- swarm/creatingswarm.md
- swarm/machine.md
- swarm/morenodes.md
- - swarm/firstservice.md
- swarm/ourapponswarm.md
- swarm/hostingregistry.md
- swarm/testingregistry.md
- swarm/btp-manual.md
- swarm/swarmready.md
- swarm/compose2swarm.md
- |
name: part-2
class: title, self-paced
Part 2
- - swarm/operatingswarm.md
- swarm/netshoot.md
- swarm/swarmnbt.md
- swarm/ipsec.md
- swarm/updatingservices.md
- swarm/rollingupdates.md
- swarm/healthchecks.md
- swarm/nodeinfo.md
- swarm/swarmtools.md
- - swarm/security.md
- swarm/secrets.md
- swarm/encryptionatrest.md
- swarm/leastprivilege.md
- swarm/apiscope.md
- swarm/logging.md
- swarm/metrics.md
- swarm/stateful.md
- swarm/extratips.md
- common/thankyou.md

View File

@@ -1,282 +0,0 @@
## Build, tag, and push our container images
- Compose has named our images `dockercoins_XXX` for each service
- We need to retag them (to `127.0.0.1:5000/XXX:v1`) and push them
.exercise[
- Set `REGISTRY` and `TAG` environment variables to use our local registry
- And run this little for loop:
```bash
cd ~/container.training/dockercoins
REGISTRY=127.0.0.1:5000 TAG=v1
for SERVICE in hasher rng webui worker; do
docker tag dockercoins_$SERVICE $REGISTRY/$SERVICE:$TAG
docker push $REGISTRY/$SERVICE
done
```
]
---
## Overlay networks
- SwarmKit integrates with overlay networks
- Networks are created with `docker network create`
- Make sure to specify that you want an *overlay* network
<br/>(otherwise you will get a local *bridge* network by default)
.exercise[
- Create an overlay network for our application:
```bash
docker network create --driver overlay dockercoins
```
]
---
## Viewing existing networks
- Let's confirm that our network was created
.exercise[
- List existing networks:
```bash
docker network ls
```
]
---
## Can you spot the differences?
The networks `dockercoins` and `ingress` are different from the other ones.
Can you see how?
--
- They are using a different kind of ID, reflecting the fact that they
are SwarmKit objects instead of "classic" Docker Engine objects.
- Their *scope* is `swarm` instead of `local`.
- They are using the overlay driver.
---
class: extra-details
## Caveats
.warning[In Docker 1.12, you cannot join an overlay network with `docker run --net ...`.]
Starting with version 1.13, you can, if the network was created with the `--attachable` flag.
*Why is that?*
Placing a container on a network requires allocating an IP address for this container.
The allocation must be done by a manager node (worker nodes cannot update Raft data).
As a result, `docker run --net ...` requires collaboration with manager nodes.
It alters the code path for `docker run`, so it is allowed only under strict circumstances.
---
## Run the application
- First, create the `redis` service; that one is using a Docker Hub image
.exercise[
- Create the `redis` service:
```bash
docker service create --network dockercoins --name redis redis
```
]
---
## Run the other services
- Then, start the other services one by one
- We will use the images pushed previously
.exercise[
- Start the other services:
```bash
REGISTRY=127.0.0.1:5000
TAG=v1
for SERVICE in hasher rng webui worker; do
docker service create --network dockercoins --detach=true \
--name $SERVICE $REGISTRY/$SERVICE:$TAG
done
```
]
---
## Expose our application web UI
- We need to connect to the `webui` service, but it is not publishing any port
- Let's reconfigure it to publish a port
.exercise[
- Update `webui` so that we can connect to it from outside:
```bash
docker service update webui --publish-add 8000:80 --detach=false
```
]
Note: to "de-publish" a port, you would have to specify the container port.
</br>(i.e. in that case, `--publish-rm 80`)
---
## What happens when we modify a service?
- Let's find out what happened to our `webui` service
.exercise[
- Look at the tasks and containers associated to `webui`:
```bash
docker service ps webui
```
]
--
The first version of the service (the one that was not exposed) has been shutdown.
It has been replaced by the new version, with port 80 accessible from outside.
(This will be discussed with more details in the section about stateful services.)
---
## Connect to the web UI
- The web UI is now available on port 8000, *on all the nodes of the cluster*
.exercise[
- If you're using Play-With-Docker, just click on the `(8000)` badge
- Otherwise, point your browser to any node, on port 8000
]
---
## Scaling the application
- We can change scaling parameters with `docker update` as well
- We will do the equivalent of `docker-compose scale`
.exercise[
- Bring up more workers:
```bash
docker service update worker --replicas 10 --detach=false
```
- Check the result in the web UI
]
You should see the performance peaking at 10 hashes/s (like before).
---
# Global scheduling
- We want to utilize as best as we can the entropy generators
on our nodes
- We want to run exactly one `rng` instance per node
- SwarmKit has a special scheduling mode for that, let's use it
- We cannot enable/disable global scheduling on an existing service
- We have to destroy and re-create the `rng` service
---
## Scaling the `rng` service
.exercise[
- Remove the existing `rng` service:
```bash
docker service rm rng
```
- Re-create the `rng` service with *global scheduling*:
```bash
docker service create --name rng --network dockercoins --mode global \
--detach=false $REGISTRY/rng:$TAG
```
- Look at the result in the web UI
]
---
class: extra-details
## Why do we have to re-create the service?
- State reconciliation is handled by a *controller*
- The controller knows how to "converge" a scaled service spec to another
- It doesn't know how to "transform" a scaled service into a global one
<br/>
(or vice versa)
- This might change in the future (after all, it was possible in 1.12 RC!)
- As of Docker Engine 17.05, other parameters requiring to `rm`/`create` the service are:
- service name
- hostname
- network
---
## Removing everything
- Before moving on, let's get a clean slate
.exercise[
- Remove *all* the services:
```bash
docker service ls -q | xargs docker service rm
```
]

View File

@@ -1,373 +0,0 @@
class: btp-manual
## Integration with Compose
- We saw how to manually build, tag, and push images to a registry
- But ...
--
class: btp-manual
*"I'm so glad that my deployment relies on ten nautic miles of Shell scripts"*
*(No-one, ever)*
--
class: btp-manual
- Let's see how we can streamline this process!
---
# Integration with Compose
- Compose is great for local development
- It can also be used to manage image lifecycle
(i.e. build images and push them to a registry)
- Compose files *v2* are great for local development
- Compose files *v3* can also be used for production deployments!
---
## Compose file version 3
(New in Docker Engine 1.13)
- Almost identical to version 2
- Can be directly used by a Swarm cluster through `docker stack ...` commands
- Introduces a `deploy` section to pass Swarm-specific parameters
- Resource limits are moved to this `deploy` section
- See [here](https://github.com/aanand/docker.github.io/blob/8524552f99e5b58452fcb1403e1c273385988b71/compose/compose-file.md#upgrading) for the complete list of changes
- Supersedes *Distributed Application Bundles*
(JSON payload describing an application; could be generated from a Compose file)
---
## Our first stack
We need a registry to move images around.
Without a stack file, it would be deployed with the following command:
```bash
docker service create --publish 5000:5000 registry:2
```
Now, we are going to deploy it with the following stack file:
```yaml
version: "3"
services:
registry:
image: registry:2
ports:
- "5000:5000"
```
---
## Checking our stack files
- All the stack files that we will use are in the `stacks` directory
.exercise[
- Go to the `stacks` directory:
```bash
cd ~/container.training/stacks
```
- Check `registry.yml`:
```bash
cat registry.yml
```
]
---
## Deploying our first stack
- All stack manipulation commands start with `docker stack`
- Under the hood, they map to `docker service` commands
- Stacks have a *name* (which also serves as a namespace)
- Stacks are specified with the aforementioned Compose file format version 3
.exercise[
- Deploy our local registry:
```bash
docker stack deploy registry --compose-file registry.yml
```
]
---
## Inspecting stacks
- `docker stack ps` shows the detailed state of all services of a stack
.exercise[
- Check that our registry is running correctly:
```bash
docker stack ps registry
```
- Confirm that we get the same output with the following command:
```bash
docker service ps registry_registry
```
]
---
class: btp-manual
## Specifics of stack deployment
Our registry is not *exactly* identical to the one deployed with `docker service create`!
- Each stack gets its own overlay network
- Services of the task are connected to this network
<br/>(unless specified differently in the Compose file)
- Services get network aliases matching their name in the Compose file
<br/>(just like when Compose brings up an app specified in a v2 file)
- Services are explicitly named `<stack_name>_<service_name>`
- Services and tasks also get an internal label indicating which stack they belong to
---
class: btp-auto
## Testing our local registry
- Connecting to port 5000 *on any node of the cluster* routes us to the registry
- Therefore, we can use `localhost:5000` or `127.0.0.1:5000` as our registry
.exercise[
- Issue the following API request to the registry:
```bash
curl 127.0.0.1:5000/v2/_catalog
```
]
It should return:
```json
{"repositories":[]}
```
If that doesn't work, retry a few times; perhaps the container is still starting.
---
class: btp-auto
## Pushing an image to our local registry
- We can retag a small image, and push it to the registry
.exercise[
- Make sure we have the busybox image, and retag it:
```bash
docker pull busybox
docker tag busybox 127.0.0.1:5000/busybox
```
- Push it:
```bash
docker push 127.0.0.1:5000/busybox
```
]
---
class: btp-auto
## Checking what's on our local registry
- The registry API has endpoints to query what's there
.exercise[
- Ensure that our busybox image is now in the local registry:
```bash
curl http://127.0.0.1:5000/v2/_catalog
```
]
The curl command should now output:
```json
"repositories":["busybox"]}
```
---
## Building and pushing stack services
- When using Compose file version 2 and above, you can specify *both* `build` and `image`
- When both keys are present:
- Compose does "business as usual" (uses `build`)
- but the resulting image is named as indicated by the `image` key
<br/>
(instead of `<projectname>_<servicename>:latest`)
- it can be pushed to a registry with `docker-compose push`
- Example:
```yaml
webfront:
build: www
image: myregistry.company.net:5000/webfront
```
---
## Using Compose to build and push images
.exercise[
- Try it:
```bash
docker-compose -f dockercoins.yml build
docker-compose -f dockercoins.yml push
```
]
Let's have a look at the `dockercoins.yml` file while this is building and pushing.
---
```yaml
version: "3"
services:
rng:
build: dockercoins/rng
image: ${REGISTRY-127.0.0.1:5000}/rng:${TAG-latest}
deploy:
mode: global
...
redis:
image: redis
...
worker:
build: dockercoins/worker
image: ${REGISTRY-127.0.0.1:5000}/worker:${TAG-latest}
...
deploy:
replicas: 10
```
---
## Deploying the application
- Now that the images are on the registry, we can deploy our application stack
.exercise[
- Create the application stack:
```bash
docker stack deploy dockercoins --compose-file dockercoins.yml
```
]
We can now connect to any of our nodes on port 8000, and we will see the familiar hashing speed graph.
---
## Maintaining multiple environments
There are many ways to handle variations between environments.
- Compose loads `docker-compose.yml` and (if it exists) `docker-compose.override.yml`
- Compose can load alternate file(s) by setting the `-f` flag or the `COMPOSE_FILE` environment variable
- Compose files can *extend* other Compose files, selectively including services:
```yaml
web:
extends:
file: common-services.yml
service: webapp
```
See [this documentation page](https://docs.docker.com/compose/extends/) for more details about these techniques.
---
class: extra-details
## Good to know ...
- Compose file version 3 adds the `deploy` section
- Further versions (3.1, ...) add more features (secrets, configs ...)
- You can re-run `docker stack deploy` to update a stack
- You can make manual changes with `docker service update` ...
- ... But they will be wiped out each time you `docker stack deploy`
(That's the intended behavior, when one thinks about it!)
- `extends` doesn't work with `docker stack deploy`
(But you can use `docker-compose config` to "flatten" your configuration)
---
## Summary
- We've seen how to set up a Swarm
- We've used it to host our own registry
- We've built our app container images
- We've used the registry to host those images
- We've deployed and scaled our application
- We've seen how to use Compose to streamline deployments
- Awesome job, team!

View File

@@ -179,7 +179,7 @@ ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
--
- Don't panic, we can easily see it again .emoji[😏]
- Don't panic, we can easily see it again 😏
---
@@ -226,7 +226,7 @@ class: extra-details
docker node ls
```
<!-- Ignore errors: .dummy[```wait not a swarm manager```] -->
```wait```
- This is because the node that we added is currently a *worker*
- Only *managers* can accept Swarm-specific commands
@@ -239,9 +239,10 @@ class: extra-details
.exercise[
- Switch back to `node1` (with `exit`, `Ctrl-D` ...)
<!-- ```keys ^D``` -->
- Switch back to `node1`:
```keys
^D
```
- View the cluster from `node1`, which is a manager:
```bash
@@ -257,6 +258,7 @@ ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
ehb0...4fvx node2 Ready Active
```
---
class: under-the-hood

Some files were not shown because too many files have changed in this diff Show More