Compare commits

..

105 Commits

Author SHA1 Message Date
Jerome Petazzoni
cacc6cd6d9 Fixes after Budapest edition 2016-05-03 02:38:16 -07:00
Jerome Petazzoni
2a35e4954c Last touch-ups 2016-04-26 15:40:45 -07:00
Jerome Petazzoni
feefd4e013 Update outline + last round of minor fixes 2016-04-26 14:05:20 -07:00
Jerome Petazzoni
8e1827a506 Another round of updates for CRAFT, almost there 2016-04-26 12:53:50 -07:00
Jerome Petazzoni
76689cd431 Updates for CRAFT (bring everything to Compose v2) 2016-04-26 06:20:11 -07:00
Jerome Petazzoni
7448474b92 Update for Berlin workshop 2016-04-21 22:25:00 -07:00
Jérôme Petazzoni
52a2e6f3e6 Bump swarm image version to 1.2; add Consul Compose file 2016-04-21 10:01:52 +00:00
Jerome Petazzoni
2b213a9821 Add reference to MobaXterm 2016-04-20 08:08:04 -07:00
Jerome Petazzoni
3ec61d706e Update versions 2016-04-20 08:07:50 -07:00
Jérôme Petazzoni
4fc9d64737 Add environment logic in autotest 2016-04-17 20:33:31 +00:00
Jérôme Petazzoni
e427c1aa38 Update test harness 2016-04-13 22:23:42 +00:00
Jérôme Petazzoni
169d1085a1 Update slides for automated testing 2016-04-13 22:23:35 +00:00
Jérôme Petazzoni
506c6ea61b Minor change in placeholder for GELF section 2016-04-13 21:49:02 +00:00
Jérôme Petazzoni
654de369ca Add autotest skeleton 2016-04-11 20:11:52 +00:00
Jerome Petazzoni
0e37cb8a93 Tweak printer settings 2016-04-05 11:15:14 -07:00
Jerome Petazzoni
4a081d06ee Merge branch 'master' of github.com:jpetazzo/orchestration-workshop 2016-04-05 11:14:49 -07:00
Jérôme Petazzoni
c21e2ae73c Last fixes for Stockholm 2016-04-05 18:13:07 +00:00
Jérôme Petazzoni
2ca1babd4a Set YAML indentation to two spaces 2016-04-05 11:11:34 +00:00
Jerome Petazzoni
1127ce8fb2 Minor updates about discovery of nodes and backends 2016-04-04 05:47:58 -07:00
Jerome Petazzoni
5662dbef23 Add vimrc + DOCKER_HOST hint in prompt 2016-04-03 13:29:44 -07:00
Jerome Petazzoni
da10562d0e Replace .icon[...warning...] with .warning[]; update hamba description 2016-04-03 06:51:20 -07:00
Jerome Petazzoni
89ca0f9173 Refactor first part for Compose 1.7 2016-04-03 06:40:15 -07:00
Jerome Petazzoni
8fe2b8b392 Merge branch 'master' of github.com:jpetazzo/orchestration-workshop 2016-04-03 06:39:43 -07:00
Jérôme Petazzoni
15f6b7bcd1 Merge pull request #15 from schrodervictor/adds-local-environment
Adds local environment
2016-04-03 12:46:24 +02:00
Victor Schröder
6ba755d869 Adds .gitignore 2016-04-03 00:45:12 +02:00
Victor Schröder
1f350e2fe7 Amends the README 2016-04-03 00:15:17 +02:00
Victor Schröder
58713c2bc9 Adds ssh configuration to allow ssh between nodes without passwords 2016-04-03 00:11:27 +02:00
Victor Schröder
1d43566233 Creates the README file with instructions 2016-04-02 23:49:20 +02:00
Victor Schröder
1c4877164d Creates ansible.cfg file and copies the private-key used to ssh in the VMs 2016-04-02 23:31:14 +02:00
Victor Schröder
d7f9f00fcf Removes some unnecessary files from the home folders 2016-04-02 23:29:56 +02:00
Victor Schröder
62270121a0 Adds task to install tmux in the VMs 2016-04-02 23:29:03 +02:00
Victor Schröder
7d40b5debb Adjusts the /etc/hosts files to make all instances visible by the hostname in the subnet 2016-04-02 23:28:12 +02:00
Victor Schröder
3f2ce588c5 Makes the docker daemons listen to port 55555 2016-04-02 23:26:58 +02:00
Victor Schröder
32607b38a2 Adds installation for docker-compose in the playbook 2016-04-02 23:25:33 +02:00
Victor Schröder
25cde1706e Adds initial provisioning playbook and inventory 2016-04-02 23:21:40 +02:00
Victor Schröder
73f8c9e9ae Adds generic Vagrantfile and vagrant.yml with five nodes 2016-04-02 23:20:58 +02:00
Jérôme Petazzoni
ecb1508410 Merge branch 'master' of github.com:jpetazzo/orchestration-workshop 2016-04-02 01:07:28 +00:00
Jérôme Petazzoni
edffb26c29 Add DNS watcher 2016-04-02 01:07:01 +00:00
Jerome Petazzoni
4e48a1badb Remove pip from dependencies 2016-04-01 14:51:42 -07:00
Jérôme Petazzoni
71e309080a Minor fixes 2016-04-01 23:22:26 +02:00
Jérôme Petazzoni
452b8c5dd3 Install Compose using single binaries instead of pip 2016-04-01 23:21:49 +02:00
Jérôme Petazzoni
884d0507c2 Display errors on stderr 2016-04-01 23:20:59 +02:00
Jérôme Petazzoni
599e344340 Upgrade remark from 0.5.9 to 0.13; switch slide layout to 16:9 2016-03-31 02:05:40 +02:00
Jérôme Petazzoni
7ea512c034 Consistent capitalization of Swarm and web UI 2016-03-30 16:16:41 +02:00
Jérôme Petazzoni
d59330d0ed Remove extraneous output in chaosmonkey 2016-03-29 18:59:54 +00:00
Jérôme Petazzoni
0440d1ea8d Switch hasher to ruby:alpine 2016-03-29 01:03:05 +00:00
Jérôme Petazzoni
a34f262a95 Switch to alpine/slim images when possible 2016-03-28 12:59:06 +00:00
Jérôme Petazzoni
d1f95ddb39 Add function to do custom build of Swarm 2016-03-28 12:58:31 +00:00
Jérôme Petazzoni
4b6b43530d Add chaosmonkey script 2016-03-28 12:58:11 +00:00
Jérôme Petazzoni
d8680366db Add command to retag instances 2016-03-28 13:47:39 +02:00
Jérôme Petazzoni
8240734b6e Fix Docker Machine install from OSX; add RC settings 2016-03-28 13:47:26 +02:00
Jérôme Petazzoni
306805c02e Refactor setting selection mechanism 2016-03-28 00:18:28 +02:00
Jérôme Petazzoni
cdd237f38e Fix instance shutdown on OSX 2016-03-26 11:14:34 +01:00
Jérôme Petazzoni
d5f98110d6 Install mosh 2016-03-26 10:58:26 +01:00
Jérôme Petazzoni
a42bc7fe28 Minor refactoring 2016-03-25 21:12:50 +01:00
AJ Bowen
9e5c7c8216 Complete rewrite of deployment process
The former VM deployment process relied on extra scripts
located in a (private) repo. The new process is standalone.
2016-03-25 13:59:53 +01:00
Jérôme Petazzoni
1be92ea3fa Add details about security upgrades and mention Nautilus 2016-03-24 14:23:19 +01:00
Jérôme Petazzoni
a38919ff5b Minor fixes after Munich workshop 2016-03-24 14:12:00 +01:00
Jérôme Petazzoni
c2180cffad Minor updates for Munich workshop 2016-03-22 00:16:49 +01:00
Jérôme Petazzoni
914c80dba8 Merge pull request #12 from npalm/qcon
Qcon
2016-03-22 00:15:32 +01:00
Jérôme Petazzoni
fa2dc41176 Merge pull request #8 from svx/master
to -> do
2016-03-14 15:16:48 +01:00
Jérôme Petazzoni
f27b99cbc4 Merge pull request #9 from akafred/rng0-to-rng-in-ambassador
Change rng0 -> rng in dockercoins ambassador-file
2016-03-14 15:07:16 +01:00
Palm, Niek
73b997faf7 add sudo so docker can install consulfs 2016-03-11 15:59:16 +00:00
Palm, Niek
948691a7d6 Add missing commands and a hint 2016-03-11 15:25:16 +00:00
akafred
aa7e8bb1f9 Change rng0 -> rng in dockercoins ambassador-file 2016-03-11 10:55:51 +00:00
Jerome Petazzoni
88e558bd08 Last updates for QCON 2016-03-11 00:29:58 -08:00
Jerome Petazzoni
1174845c52 Add v2 load balancing technique 2016-03-09 15:39:23 -08:00
Jerome Petazzoni
8a73d85beb Fixed lcal registry setup thanks to @soulshake 2016-03-09 08:28:02 -08:00
Jerome Petazzoni
bc1bb493d4 First round of update for QCON 2016-03-09 06:37:21 -08:00
Jérôme Petazzoni
cc6543ce1c Add helper to remove stale endpoints 2016-03-05 14:33:37 +00:00
Jérôme Petazzoni
8c62220c8d Add missing EXPOSE in webui/Dockerfile 2016-03-02 12:12:00 +00:00
Jérôme Petazzoni
332eb9cff9 Add script to automate LB setup; add setup/teardown helpers 2016-03-02 12:11:22 +00:00
Jerome Petazzoni
2ad3625687 Add ConsulFS section 2016-03-01 06:30:53 -08:00
Jerome Petazzoni
7e60d482f7 Add cron strategies 2016-03-01 05:37:31 -08:00
Jerome Petazzoni
4d2e62ffee Adjust for QCON schedule; use local registry 2016-03-01 05:21:02 -08:00
Jérôme Petazzoni
b4870b8ed6 Hackish CEPH cluster on Docker 2016-03-01 01:01:54 +00:00
Jérôme Petazzoni
29ae9a10f2 Remove extraneous ambassadors 2016-02-29 22:03:25 +00:00
Jérôme Petazzoni
ec2b098c29 Add parallel_run function and use it to manage ambassadors 2016-02-29 21:24:32 +00:00
Jérôme Petazzoni
240a55a18a Update other scripts to use common.py 2016-02-29 19:08:01 +00:00
Jérôme Petazzoni
362c5e8a65 Add Compose file for self-hosted registry 2016-02-29 18:40:14 +00:00
Jérôme Petazzoni
b47dccdb8d Move scripts to bin directory; abstract Compose V1/V2 formats 2016-02-29 18:17:06 +00:00
sven
dca494c090 Revoke wording change 2016-02-24 20:39:20 +01:00
sven
9286fdec28 Assess -> Access 2016-02-19 11:46:14 +01:00
sven
c31e961e61 to -> do 2016-02-19 11:30:01 +01:00
Jerome Petazzoni
2b88a121ba Add DockerCoins image 2016-02-18 23:42:53 -08:00
Jerome Petazzoni
84a521296d Round of update for Amsterdam workshop 2016-02-18 15:09:58 -08:00
Jerome Petazzoni
86271b6336 Merge branch 'master' of github.com:jpetazzo/orchestration-workshop 2016-02-14 07:11:13 -08:00
Jerome Petazzoni
104e736c45 Add kibana screenshot 2016-02-14 07:10:52 -08:00
Jerome Petazzoni
9dfb421eb1 Last round of updates 2016-02-14 07:10:42 -08:00
Jerome Petazzoni
d69719abc6 Content rehaul before Paris workshop 2016-02-14 06:05:39 -08:00
Jérôme Petazzoni
98bac6713d Add v2 Compose file 2016-02-14 13:19:10 +00:00
Jerome Petazzoni
6c1958e244 Update Compose+Swarm section 2016-02-13 10:56:55 -08:00
Jerome Petazzoni
936a80f86d Conclusions + mention Docker Cloud and UCP 2016-02-13 10:23:10 -08:00
Jerome Petazzoni
2634230a4a Switch to node:4 image to work around AUFS issue 2016-02-12 09:11:00 -08:00
Jerome Petazzoni
4e96a2949c Add Swarm replication and rescheduling sections 2016-02-12 09:10:39 -08:00
Jerome Petazzoni
175d468718 Merge branch 'master' of github.com:jpetazzo/orchestration-workshop 2016-02-09 16:33:25 -08:00
Jerome Petazzoni
eb38b7e2e6 Rehaul logging section 2016-02-09 16:33:16 -08:00
Jérôme Petazzoni
9d5dfc9a40 Add sample Compose file with GELF logging 2016-02-10 00:32:00 +00:00
Jérôme Petazzoni
676aad7edc Revert to Compose v1 for simplicity 2016-02-09 23:39:30 +00:00
Jérôme Petazzoni
43d574a164 Add Compose file for ELK stack 2016-02-09 22:09:48 +00:00
Jerome Petazzoni
661948d162 Update versions, schedule, and twitter button 2016-02-08 07:24:50 -08:00
Jerome Petazzoni
edf3aeb9c4 Fix LISA reference 2016-01-26 10:34:07 -08:00
Jerome Petazzoni
e16c391deb Good to go! 2016-01-22 14:29:37 -08:00
Jérôme Petazzoni
1a486a2f95 Minor fixes 2016-01-21 18:06:30 -08:00
Jerome Petazzoni
962737ffa2 Update for SCALE; add support for multi-host networking 2016-01-21 16:44:34 -08:00
91 changed files with 7252 additions and 1459 deletions

9
.gitignore vendored
View File

@@ -1,5 +1,8 @@
*.pyc
*.swp
*~
ips.txt
ips.html
ips.pdf
prepare-vms/ips.txt
prepare-vms/ips.html
prepare-vms/ips.pdf
prepare-vms/settings.yaml
prepare-vms/tags

View File

@@ -8,6 +8,11 @@ at multiple conferences and events like:
- KCDC, Kansas City (2015, June)
- JDEV, Bordeaux (2015, July)
- OSCON, Portland (2015, July)
- StrangeLoop, Saint Louis (2015, September)
- LISA, Washington D.C. (2015, November)
- SCALE, Pasadena (2016, January)
- Zenika, Paris (2016, February)
- Container Solutions, Amsterdam (2016, February)
## Slides

191
autotest/autotest.py Executable file
View File

@@ -0,0 +1,191 @@
#!/usr/bin/env python
import os
import re
import signal
import subprocess
import time
def print_snippet(snippet):
print(78*'-')
print(snippet)
print(78*'-')
class Snippet(object):
def __init__(self, slide, content):
self.slide = slide
self.content = content
self.actions = []
def __str__(self):
return self.content
class Slide(object):
current_slide = 0
def __init__(self, content):
Slide.current_slide += 1
self.number = Slide.current_slide
# Remove commented-out slides
# (remark.js considers ??? to be the separator for speaker notes)
content = re.split("\n\?\?\?\n", content)[0]
self.content = content
self.snippets = []
exercises = re.findall("\.exercise\[(.*)\]", content, re.DOTALL)
for exercise in exercises:
if "```" in exercise and "<br/>`" in exercise:
print("! Exercise on slide {} has both ``` and <br/>` delimiters, skipping."
.format(self.number))
print_snippet(exercise)
elif "```" in exercise:
for snippet in exercise.split("```")[1::2]:
self.snippets.append(Snippet(self, snippet))
elif "<br/>`" in exercise:
for snippet in re.findall("<br/>`(.*)`", exercise):
self.snippets.append(Snippet(self, snippet))
else:
print(" Exercise on slide {} has neither ``` or <br/>` delimiters, skipping."
.format(self.number))
def __str__(self):
text = self.content
for snippet in self.snippets:
text = text.replace(snippet.content, ansi("7")(snippet.content))
return text
def ansi(code):
return lambda s: "\x1b[{}m{}\x1b[0m".format(code, s)
slides = []
with open("index.html") as f:
content = f.read()
for slide in re.split("\n---?\n", content):
slides.append(Slide(slide))
is_editing_file = False
placeholders = {}
for slide in slides:
for snippet in slide.snippets:
content = snippet.content
# Multi-line snippets should be ```highlightsyntax...
# Single-line snippets will be interpreted as shell commands
if '\n' in content:
highlight, content = content.split('\n', 1)
else:
highlight = "bash"
content = content.strip()
# If the previous snippet was a file fragment, and the current
# snippet is not YAML or EDIT, complain.
if is_editing_file and highlight not in ["yaml", "edit"]:
print("! On slide {}, previous snippet was YAML, so what do what do?"
.format(slide.number))
print_snippet(content)
is_editing_file = False
if highlight == "yaml":
is_editing_file = True
elif highlight == "placeholder":
for line in content.split('\n'):
variable, value = line.split(' ', 1)
placeholders[variable] = value
elif highlight == "bash":
for variable, value in placeholders.items():
quoted = "`{}`".format(variable)
if quoted in content:
content = content.replace(quoted, value)
del placeholders[variable]
if '`' in content:
print("! The following snippet on slide {} contains a backtick:"
.format(slide.number))
print_snippet(content)
continue
print("_ "+content)
snippet.actions.append((highlight, content))
elif highlight == "edit":
print(". "+content)
snippet.actions.append((highlight, content))
elif highlight == "meta":
print("^ "+content)
snippet.actions.append((highlight, content))
else:
print("! Unknown highlight {!r} on slide {}.".format(highlight, slide.number))
if placeholders:
print("! Remaining placeholder values: {}".format(placeholders))
actions = sum([snippet.actions for snippet in sum([slide.snippets for slide in slides], [])], [])
# Strip ^{ ... ^} for now
def strip_curly_braces(actions, in_braces=False):
if actions == []:
return []
elif actions[0] == ("meta", "^{"):
return strip_curly_braces(actions[1:], True)
elif actions[0] == ("meta", "^}"):
return strip_curly_braces(actions[1:], False)
elif in_braces:
return strip_curly_braces(actions[1:], True)
else:
return [actions[0]] + strip_curly_braces(actions[1:], False)
actions = strip_curly_braces(actions)
background = []
cwd = os.path.expanduser("~")
env = {}
for current_action, next_action in zip(actions, actions[1:]+[("bash", "true")]):
if current_action[0] == "meta":
continue
print(ansi(7)(">>> {}".format(current_action[1])))
time.sleep(1)
popen_options = dict(shell=True, cwd=cwd, stdin=subprocess.PIPE, preexec_fn=os.setpgrp)
# The follow hack allows to capture the environment variables set by `docker-machine env`
# FIXME: this doesn't handle `unset` for now
if any([
"eval $(docker-machine env" in current_action[1],
"DOCKER_HOST" in current_action[1],
"COMPOSE_FILE" in current_action[1],
]):
popen_options["stdout"] = subprocess.PIPE
current_action[1] += "\nenv"
proc = subprocess.Popen(current_action[1], **popen_options)
proc.cmd = current_action[1]
if next_action[0] == "meta":
print(">>> {}".format(next_action[1]))
time.sleep(3)
if next_action[1] == "^C":
os.killpg(proc.pid, signal.SIGINT)
proc.wait()
elif next_action[1] == "^Z":
# Let the process run
background.append(proc)
elif next_action[1] == "^D":
proc.communicate()
proc.wait()
else:
print("! Unknown meta action {} after snippet:".format(next_action[1]))
print_snippet(next_action[1])
print(ansi(7)("<<< {}".format(current_action[1])))
else:
proc.wait()
if "stdout" in popen_options:
stdout, stderr = proc.communicate()
for line in stdout.split('\n'):
if line.startswith("DOCKER_"):
variable, value = line.split('=', 1)
env[variable] = value
print("=== {}={}".format(variable, value))
print(ansi(7)("<<< {} >>> {}".format(proc.returncode, current_action[1])))
if proc.returncode != 0:
print("Got non-zero status code; aborting.")
break
if current_action[1].startswith("cd "):
cwd = os.path.expanduser(current_action[1][3:])
for proc in background:
print("Terminating background process:")
print_snippet(proc.cmd)
proc.terminate()
proc.wait()

1
autotest/index.html Symbolic link
View File

@@ -0,0 +1 @@
../www/htdocs/index.html

42
bin/add-load-balancer-v1.py Executable file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python
import os
import sys
import yaml
# arg 1 = service name
# arg 2 = number of instances
service_name = sys.argv[1]
desired_instances = int(sys.argv[2])
compose_file = os.environ["COMPOSE_FILE"]
input_file, output_file = compose_file, compose_file
config = yaml.load(open(input_file))
# The ambassadors need to know the service port to use.
# Those ports must be declared here.
ports = yaml.load(open("ports.yml"))
port = str(ports[service_name])
command_line = port
depends_on = []
for n in range(1, 1+desired_instances):
config["services"]["{}{}".format(service_name, n)] = config["services"][service_name]
command_line += " {}{}:{}".format(service_name, n, port)
depends_on.append("{}{}".format(service_name, n))
config["services"][service_name] = {
"image": "jpetazzo/hamba",
"command": command_line,
"depends_on": depends_on,
}
if "networks" in config["services"]["{}1".format(service_name)]:
config["services"][service_name]["networks"] = config["services"]["{}1".format(service_name)]["networks"]
yaml.safe_dump(config, open(output_file, "w"), default_flow_style=False)

87
bin/add-load-balancer-v2.py Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python
import os
import sys
import yaml
def error(msg):
print("ERROR: {}".format(msg))
exit(1)
# arg 1 = service name
service_name = sys.argv[1]
compose_file = os.environ["COMPOSE_FILE"]
input_file, output_file = compose_file, compose_file
config = yaml.load(open(input_file))
version = config.get("version")
if version != "2":
error("Unsupported $COMPOSE_FILE version: {!r}".format(version))
# The load balancers need to know the service port to use.
# Those ports must be declared here.
ports = yaml.load(open("ports.yml"))
port = str(ports[service_name])
if service_name not in config["services"]:
error("service {} not found in $COMPOSE_FILE"
.format(service_name))
lb_name = "{}-lb".format(service_name)
be_name = "{}-be".format(service_name)
wd_name = "{}-wd".format(service_name)
if lb_name in config["services"]:
error("load balancer {} already exists in $COMPOSE_FILE"
.format(lb_name))
if wd_name in config["services"]:
error("dns watcher {} already exists in $COMPOSE_FILE"
.format(wd_name))
service = config["services"][service_name]
if "networks" in service:
error("service {} has custom networks"
.format(service_name))
# Put the service on its own network.
service["networks"] = {service_name: {"aliases": [ be_name ] } }
# Put a label indicating which load balancer is responsible for this service.
if "labels" not in service:
service["labels"] = {}
service["labels"]["loadbalancer"] = lb_name
# Add the load balancer.
config["services"][lb_name] = {
"image": "jpetazzo/hamba",
"command": "{} {} {}".format(port, be_name, port),
"depends_on": [ service_name ],
"networks": {
"default": {
"aliases": [ service_name ],
},
service_name: None,
},
}
# Add the DNS watcher.
config["services"][wd_name] = {
"image": "jpetazzo/watchdns",
"command": "{} {} {}".format(port, be_name, port),
"volumes_from": [ lb_name ],
"networks": {
service_name: None,
},
}
if "networks" not in config:
config["networks"] = {}
if service_name not in config["networks"]:
config["networks"][service_name] = None
yaml.safe_dump(config, open(output_file, "w"), default_flow_style=False)

61
bin/build-tag-push.py Executable file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python
from common import ComposeFile
import os
import subprocess
import time
registry = os.environ.get("DOCKER_REGISTRY")
if not registry:
print("Please set the DOCKER_REGISTRY variable, e.g.:")
print("export DOCKER_REGISTRY=jpetazzo # use the Docker Hub")
print("export DOCKER_REGISTRY=localhost:5000 # use a local registry")
exit(1)
# Get the name of the current directory.
project_name = os.path.basename(os.path.realpath("."))
# Generate a Docker image tag, using the UNIX timestamp.
# (i.e. number of seconds since January 1st, 1970)
version = str(int(time.time()))
# Execute "docker-compose build" and abort if it fails.
subprocess.check_call(["docker-compose", "-f", "docker-compose.yml", "build"])
# Load the services from the input docker-compose.yml file.
# TODO: run parallel builds.
compose_file = ComposeFile("docker-compose.yml")
# Iterate over all services that have a "build" definition.
# Tag them, and initiate a push in the background.
push_operations = dict()
for service_name, service in compose_file.services.items():
if "build" in service:
compose_image = "{}_{}".format(project_name, service_name)
registry_image = "{}/{}_{}:{}".format(registry, project_name, service_name, version)
# Re-tag the image so that it can be uploaded to the registry.
subprocess.check_call(["docker", "tag", compose_image, registry_image])
# Spawn "docker push" to upload the image.
push_operations[service_name] = subprocess.Popen(["docker", "push", registry_image])
# Replace the "build" definition by an "image" definition,
# using the name of the image on the registry.
del service["build"]
service["image"] = registry_image
# Wait for push operations to complete.
for service_name, popen_object in push_operations.items():
print("Waiting for {} push to complete...".format(service_name))
popen_object.wait()
print("Done.")
# Write the new docker-compose.yml file.
if "COMPOSE_FILE" not in os.environ:
os.environ["COMPOSE_FILE"] = "docker-compose.yml-{}".format(version)
print("Writing to new Compose file:")
else:
print("Writing to provided Compose file:")
print("COMPOSE_FILE={}".format(os.environ["COMPOSE_FILE"]))
compose_file.save()

47
bin/chaosmonkey.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/bin/sh
[ -z "$2" ] && {
echo "Syntax: $0 <host> <command>"
echo "
Command should be:
connect Cancels the effects of 'disconnect'
disconnect Disable all network communication except SSH
reboot Sync disks and immediately reboot (without proper shutdown)
"
exit 1
}
ssh docker@$1 sudo sh <<EOF
_cm_init () {
iptables -L CHAOSMONKEY >/dev/null 2>/dev/null || {
iptables -N CHAOSMONKEY
iptables -I FORWARD -j CHAOSMONKEY
iptables -I INPUT -j CHAOSMONKEY
iptables -I OUTPUT -j CHAOSMONKEY
}
}
_cm_reboot () {
echo "Rebooting..."
echo s > /proc/sysrq-trigger
echo u > /proc/sysrq-trigger
echo b > /proc/sysrq-trigger
}
_cm_disconnect () {
_cm_init
echo "Dropping all network traffic, except SSH..."
iptables -F CHAOSMONKEY
iptables -A CHAOSMONKEY -p tcp --sport 22 -j ACCEPT
iptables -A CHAOSMONKEY -p tcp --dport 22 -j ACCEPT
iptables -A CHAOSMONKEY -j DROP
}
_cm_connect () {
_cm_init
echo "Re-enabling network communication..."
iptables -F CHAOSMONKEY
}
_cm_$2
EOF

76
bin/common.py Executable file
View File

@@ -0,0 +1,76 @@
import os
import subprocess
import sys
import time
import yaml
def COMPOSE_FILE():
if "COMPOSE_FILE" not in os.environ:
print("The $COMPOSE_FILE environment variable is not set. Aborting.")
exit(1)
return os.environ["COMPOSE_FILE"]
class ComposeFile(object):
def __init__(self, filename=None):
if filename is None:
filename = COMPOSE_FILE()
if not os.path.isfile(filename):
print("File {!r} does not exist. Aborting.".format(filename))
exit(1)
self.data = yaml.load(open(filename))
@property
def services(self):
if self.data.get("version") == "2":
return self.data["services"]
else:
return self.data
def save(self, filename=None):
if filename is None:
filename = COMPOSE_FILE()
with open(filename, "w") as f:
yaml.safe_dump(self.data, f, default_flow_style=False)
# Executes a bunch of commands in parallel, but no more than N at a time.
# This allows to execute concurrently a large number of tasks, without
# turning into a fork bomb.
# `parallelism` is the number of tasks to execute simultaneously.
# `commands` is a list of tasks to execute.
# Each task is itself a list, where the first element is a descriptive
# string, and the folloowing elements are the arguments to pass to Popen.
def parallel_run(commands, parallelism):
running = []
# While stuff is running, or we have stuff to run...
while commands or running:
# While there is stuff to run, and room in the pipe...
while commands and len(running)<parallelism:
command = commands.pop(0)
print("START {}".format(command[0]))
popen = subprocess.Popen(
command[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
popen._desc = command[0]
running.append(popen)
must_sleep = True
for popen in running:
status = popen.poll()
if status is not None:
must_sleep = False
running.remove(popen)
if status==0:
print("OK {}".format(popen._desc))
else:
print("ERROR {} [Exit status: {}]"
.format(popen._desc, status))
output = "\n" + popen.communicate()[0].strip()
output = output.replace("\n", "\n| ")
print(output)
else:
print("WAIT ({} running, {} more to run)"
.format(len(running), len(commands)))
if must_sleep:
time.sleep(1)

View File

@@ -1,16 +1,12 @@
#!/usr/bin/env python
from common import parallel_run
import os
import subprocess
import sys
import yaml
compose_file = os.environ.get("COMPOSE_FILE") or sys.argv[1]
stack = yaml.load(open(compose_file))
project_name = os.path.basename(os.path.realpath("."))
# Get all services and backends in our compose application
# Get all services and backends in our compose application.
containers_data = subprocess.check_output([
"docker", "ps",
"--filter", "label=com.docker.compose.project={}".format(project_name),
@@ -19,7 +15,7 @@ containers_data = subprocess.check_output([
'{{ .Ports }}',
])
# Build list of backends
# Build list of backends.
frontend_ports = dict()
backends = dict()
for container in containers_data.split('\n'):
@@ -40,7 +36,7 @@ for container in containers_data.split('\n'):
backends[service_name] = []
backends[service_name].append((backend_addr, backend_port))
# Get all existing ambassadors for this application
# Get all existing ambassadors for this application.
ambassadors_data = subprocess.check_output([
"docker", "ps",
"--filter", "label=ambassador.project={}".format(project_name),
@@ -49,7 +45,8 @@ ambassadors_data = subprocess.check_output([
'{{ .Label "ambassador.bindaddr" }}',
])
# Update ambassadors
# Update ambassadors.
operations = []
for ambassador in ambassadors_data.split('\n'):
if not ambassador:
continue
@@ -59,11 +56,14 @@ for ambassador in ambassadors_data.split('\n'):
bind_address, frontend_ports[service_name],
backends[service_name]))
command = [
ambassador_id,
"docker", "run", "--rm", "--volumes-from", ambassador_id,
"jpetazzo/hamba", "reconfigure",
"{}:{}".format(bind_address, frontend_ports[service_name])
]
for backend_addr, backend_port in backends[service_name]:
command.extend([backend_addr, backend_port])
print command
subprocess.check_call(command)
operations.append(command)
# Execute all commands in parallel.
parallel_run(operations, 10)

View File

@@ -1,23 +1,21 @@
#!/usr/bin/env python
from common import ComposeFile, parallel_run
import os
import subprocess
import sys
import yaml
compose_file = os.environ.get("COMPOSE_FILE") or sys.argv[1]
stack = yaml.load(open(compose_file))
config = ComposeFile()
project_name = os.path.basename(os.path.realpath("."))
# Get all services in our compose application
# Get all services in our compose application.
containers_data = subprocess.check_output([
"docker", "ps",
"--filter", "label=com.docker.compose.project={}".format(project_name),
"--format", '{{ .ID }} {{ .Label "com.docker.compose.service" }}',
])
# Get all existing ambassadors for this application
# Get all existing ambassadors for this application.
ambassadors_data = subprocess.check_output([
"docker", "ps",
"--filter", "label=ambassador.project={}".format(project_name),
@@ -26,7 +24,7 @@ ambassadors_data = subprocess.check_output([
'{{ .Label "ambassador.service" }}',
])
# Build a set of existing ambassadors
# Build a set of existing ambassadors.
ambassadors = dict()
for ambassador in ambassadors_data.split('\n'):
if not ambassador:
@@ -34,21 +32,24 @@ for ambassador in ambassadors_data.split('\n'):
ambassador_id, container_id, linked_service = ambassador.split()
ambassadors[container_id, linked_service] = ambassador_id
# Start the missing ambassadors
operations = []
# Start the missing ambassadors.
for container in containers_data.split('\n'):
if not container:
continue
container_id, service_name = container.split()
extra_hosts = stack[service_name].get("extra_hosts", {})
extra_hosts = config.services[service_name].get("extra_hosts", {})
for linked_service, bind_address in extra_hosts.items():
description = "Ambassador {}/{}/{}".format(
service_name, container_id, linked_service)
ambassador_id = ambassadors.get((container_id, linked_service))
ambassador_id = ambassadors.pop((container_id, linked_service), None)
if ambassador_id:
print("{} already exists: {}".format(description, ambassador_id))
else:
print("{} not found, creating it:".format(description))
subprocess.check_call([
print("{} not found, creating it.".format(description))
operations.append([
description,
"docker", "run", "-d",
"--net", "container:{}".format(container_id),
"--label", "ambassador.project={}".format(project_name),
@@ -58,3 +59,13 @@ for container in containers_data.split('\n'):
"jpetazzo/hamba", "run"
])
# Destroy extraneous ambassadors.
for ambassador_id in ambassadors.values():
print("{} is not useful anymore, destroying it.".format(ambassador_id))
operations.append([
"rm -f {}".format(ambassador_id),
"docker", "rm", "-f", ambassador_id,
])
# Execute all commands in parallel.
parallel_run(operations, 10)

38
bin/link-to-ambassadors.py Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python
from common import ComposeFile
import yaml
config = ComposeFile()
# The ambassadors need to know the service port to use.
# Those ports must be declared here.
ports = yaml.load(open("ports.yml"))
def generate_local_addr():
last_byte = 2
while last_byte<255:
yield "127.127.0.{}".format(last_byte)
last_byte += 1
for service_name, service in config.services.items():
if "links" in service:
for link, local_addr in zip(service["links"], generate_local_addr()):
if link not in ports:
print("Skipping link {} in service {} "
"(no port mapping defined). "
"Your code will probably break."
.format(link, service_name))
continue
if "extra_hosts" not in service:
service["extra_hosts"] = {}
service["extra_hosts"][link] = local_addr
del service["links"]
if "ports" in service:
del service["ports"]
if "volumes" in service:
del service["volumes"]
if service_name in ports:
service["ports"] = [ ports[service_name] ]
config.save()

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python
# FIXME: hardcoded
PORT="80"
import os
import subprocess
project_name = os.path.basename(os.path.realpath("."))
# Get all existing services for this application.
containers_data = subprocess.check_output([
"docker", "ps",
"--filter", "label=com.docker.compose.project={}".format(project_name),
"--format", '{{ .Label "com.docker.compose.service" }} '
'{{ .Label "com.docker.compose.container-number" }} '
'{{ .Label "loadbalancer" }}',
])
load_balancers = dict()
for line in containers_data.split('\n'):
if not line:
continue
service_name, container_number, load_balancer = line.split(' ')
if load_balancer:
if load_balancer not in load_balancers:
load_balancers[load_balancer] = []
load_balancers[load_balancer].append((service_name, int(container_number)))
for load_balancer, backends in load_balancers.items():
# FIXME: iterate on all load balancers
container_name = "{}_{}_1".format(project_name, load_balancer)
command = [
"docker", "run", "--rm",
"--volumes-from", container_name,
"--net", "container:{}".format(container_name),
"jpetazzo/hamba", "reconfigure", PORT,
]
command.extend(
"{}_{}_{}:{}".format(project_name, backend_name, backend_number, PORT)
for (backend_name, backend_number) in sorted(backends)
)
print("Updating configuration for {} with {} backend(s)..."
.format(container_name, len(backends)))
subprocess.check_output(command)

168
bin/setup-all-the-things.sh Executable file
View File

@@ -0,0 +1,168 @@
#!/bin/sh
unset DOCKER_REGISTRY
unset DOCKER_HOST
unset COMPOSE_FILE
SWARM_IMAGE=${SWARM_IMAGE:-swarm:1.2.0}
prepare_1_check_ssh_keys () {
for N in $(seq 1 5); do
ssh node$N true
done
}
prepare_2_compile_swarm () {
cd ~
git clone git://github.com/docker/swarm
cd swarm
[[ -z "$1" ]] && {
echo "Specify which revision to build."
return
}
git checkout "$1" || return
mkdir -p image
docker build -t docker/swarm:$1 .
docker run -i --entrypoint sh docker/swarm:$1 \
-c 'cat $(which swarm)' > image/swarm
chmod +x image/swarm
cat >image/Dockerfile <<EOF
FROM scratch
COPY ./swarm /swarm
ENTRYPOINT ["/swarm", "-debug", "-experimental"]
EOF
docker build -t jpetazzo/swarm:$1 image
docker login
docker push jpetazzo/swarm:$1
docker logout
SWARM_IMAGE=jpetazzo/swarm:$1
}
clean_1_containers () {
for N in $(seq 1 5); do
ssh node$N "docker ps -aq | xargs -r -n1 -P10 docker rm -f"
done
}
clean_2_volumes () {
for N in $(seq 1 5); do
ssh node$N "docker volume ls -q | xargs -r docker volume rm"
done
}
clean_3_images () {
for N in $(seq 1 5); do
ssh node$N "docker images | awk '/dockercoins|jpetazzo/ {print \$1\":\"\$2}' | xargs -r docker rmi -f"
done
}
clean_4_machines () {
rm -rf ~/.docker/machine/
}
clean_all () {
clean_1_containers
clean_2_volumes
clean_3_images
clean_4_machines
}
dm_swarm () {
eval $(docker-machine env node1 --swarm)
}
dm_node1 () {
eval $(docker-machine env node1)
}
setup_1_swarm () {
grep node[12345] /etc/hosts | grep -v ^127 |
while read IPADDR NODENAME; do
docker-machine create --driver generic \
--engine-opt cluster-store=consul://localhost:8500 \
--engine-opt cluster-advertise=eth0:2376 \
--swarm --swarm-master --swarm-image $SWARM_IMAGE \
--swarm-discovery consul://localhost:8500 \
--swarm-opt replication --swarm-opt advertise=$IPADDR:3376 \
--generic-ssh-user docker --generic-ip-address $IPADDR $NODENAME
done
}
setup_2_consul () {
ssh node1 docker run --name consul_node1 \
-d --restart=always --net host \
jpetazzo/consul agent -server -bootstrap
IPADDR=$(ssh node1 ip a ls dev eth0 |
sed -n 's,.*inet \(.*\)/.*,\1,p')
# Start other Consul nodes
for N in 2 3 4 5; do
ssh node$N docker run --name consul_node$N \
-d --restart=always --net host \
jpetazzo/consul agent -server -join $IPADDR
done
}
setup_3_wait () {
# Wait for a Swarm master
dm_swarm
while ! docker ps; do sleep 1; done
# Wait for all nodes to be there
while ! [ "$(docker info | grep "^Nodes:")" = "Nodes: 5" ]; do sleep 1; done
}
setup_4_registry () {
cd ~/orchestration-workshop/registry
dm_swarm
docker-compose up -d
for N in $(seq 2 5); do
docker-compose scale frontend=$N
done
}
setup_5_btp_dockercoins () {
cd ~/orchestration-workshop/dockercoins
dm_node1
export DOCKER_REGISTRY=localhost:5000
cp docker-compose.yml-v2 docker-compose.yml
~/orchestration-workshop/bin/build-tag-push.py | tee /tmp/btp.log
export $(tail -n 1 /tmp/btp.log)
}
setup_6_add_lbs () {
cd ~/orchestration-workshop/dockercoins
~/orchestration-workshop/bin/add-load-balancer-v2.py rng
~/orchestration-workshop/bin/add-load-balancer-v2.py hasher
}
setup_all () {
setup_1_swarm
setup_2_consul
setup_3_wait
setup_4_registry
setup_5_btp_dockercoins
setup_6_add_lbs
dm_swarm
}
force_remove_network () {
dm_swarm
NET="$1"
for CNAME in $(docker network inspect $NET | grep Name | grep -v \"$NET\" | cut -d\" -f4); do
echo $CNAME
docker network disconnect -f $NET $CNAME
done
docker network rm $NET
}
demo_1_compose_up () {
dm_swarm
cd ~/orchestration-workshop/dockercoins
docker-compose up -d
}
grep -qs -- MAGICMARKER "$0" && { # Don't display this line in the function lis
echo "You should source this file, then invoke the following functions:"
grep -- '^[a-z].*{$' "$0" | cut -d" " -f1
}

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env python
import os
import subprocess
import time
import yaml
user_name = os.environ.get("DOCKERHUB_USER")
if not user_name:
print("Please set the DOCKERHUB_USER to your user name, e.g.:")
print("export DOCKERHUB_USER=zoe")
exit(1)
# Get the name of the current directory.
project_name = os.path.basename(os.path.realpath("."))
# Generate a Docker image tag, using the UNIX timestamp.
# (i.e. number of seconds since January 1st, 1970)
version = str(int(time.time()))
input_file = os.environ.get(
"DOCKER_COMPOSE_YML", "docker-compose.yml")
output_file = os.environ.get(
"DOCKER_COMPOSE_YML", "docker-compose.yml-{}".format(version))
if input_file == output_file == "docker-compose.yml":
print("I will not clobber your docker-compose.yml file.")
print("Unset DOCKER_COMPOSE_YML or set it to something else.")
exit(1)
print("Input file: {}".format(input_file))
print("Output file: {}".format(output_file))
# Execute "docker-compose build" and abort if it fails.
subprocess.check_call(["docker-compose", "-f", input_file, "build"])
# Load the services from the input docker-compose.yml file.
# TODO: run parallel builds.
stack = yaml.load(open(input_file))
# Iterate over all services that have a "build" definition.
# Tag them, and initiate a push in the background.
push_operations = dict()
for service_name, service in stack.items():
if "build" in service:
compose_image = "{}_{}".format(project_name, service_name)
hub_image = "{}/{}_{}:{}".format(user_name, project_name, service_name, version)
# Re-tag the image so that it can be uploaded to the Docker Hub.
subprocess.check_call(["docker", "tag", compose_image, hub_image])
# Spawn "docker push" to upload the image.
push_operations[service_name] = subprocess.Popen(["docker", "push", hub_image])
# Replace the "build" definition by an "image" definition,
# using the name of the image on the Docker Hub.
del service["build"]
service["image"] = hub_image
# Wait for push operations to complete.
for service_name, popen_object in push_operations.items():
print("Waiting for {} push to complete...".format(service_name))
popen_object.wait()
print("Done.")
# Write the new docker-compose.yml file.
with open(output_file, "w") as f:
yaml.safe_dump(stack, f, default_flow_style=False)
print("Wrote new compose file.")
print("COMPOSE_FILE={}".format(output_file))

19
ceph/README.md Normal file
View File

@@ -0,0 +1,19 @@
# CEPH on Docker
Note: this doesn't quite work yet.
The OSD containers need to be started twice (the first time, they fail
initializing; second time is a champ).
Also, it looks like you need at least two OSD containers (or the OSD
container should have two disks/directories, whatever).
RadosGw is listening on port 8080.
The `admin` container will create a `docker` user using `radosgw-admin`.
If you run it multiple times, that's OK: further invocations are idempotent.
Last but not least: it looks like AWS CLI uses a new signature format
that doesn't work with RadosGW. After almost two hours trying to figure
out what was wrong, I tried the S3 credentials directly with boto and
it worked immediately (I was able to create a bucket).

53
ceph/docker-compose.yml Normal file
View File

@@ -0,0 +1,53 @@
version: "2"
services:
mon:
image: ceph/daemon
command: mon
environment:
CEPH_PUBLIC_NETWORK: 10.33.0.0/16
MON_IP: 10.33.0.2
osd:
image: ceph/daemon
command: osd_directory
depends_on:
- mon
volumes_from:
- mon
volumes:
- /var/lib/ceph/osd
mds:
image: ceph/daemon
command: mds
environment:
CEPHFS_CREATE: 1
depends_on:
- mon
volumes_from:
- mon
rgw:
image: ceph/daemon
command: rgw
depends_on:
- mon
volumes_from:
- mon
environment:
CEPH_OPTS: --verbose
admin:
image: ceph/daemon
entrypoint: radosgw-admin
depends_on:
- mon
volumes_from:
- mon
command: user create --uid=docker --display-name=docker
networks:
default:
ipam:
driver: default
config:
- subnet: 10.33.0.0/16
gateway: 10.33.0.1

12
consul/docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
version: "2"
services:
bootstrap:
image: jpetazzo/consul
command: agent -server -bootstrap
container_name: bootstrap
server:
image: jpetazzo/consul
command: agent -server -join bootstrap -join server
client:
image: jpetazzo/consul
command: members -rpc-addr server:8400

View File

@@ -1,29 +1,26 @@
rng:
version: "2"
services:
rng:
build: rng
ports:
- "8001:80"
- "8001:80"
hasher:
hasher:
build: hasher
ports:
- "8002:80"
- "8002:80"
webui:
webui:
build: webui
links:
- redis
ports:
- "8000:80"
- "8000:80"
volumes:
- "./webui/files/:/files/"
- "./webui/files/:/files/"
redis:
redis:
image: redis
worker:
worker:
build: worker
links:
- rng
- hasher
- redis

View File

@@ -1,43 +1,35 @@
rng1:
version: "2"
services:
rng1:
build: rng
rng2:
build: rng
rng3:
build: rng
rng2:
build: rng
rng3:
build: rng
rng0:
rng:
image: jpetazzo/hamba
links:
- rng1
- rng2
- rng3
command: 80 rng1 80 rng2 80 rng3 80
command: 80 rng1:80 rng2:80 rng3:80
ports:
- "8001:80"
- "8001:80"
hasher:
hasher:
build: hasher
ports:
- "8002:80"
- "8002:80"
webui:
webui:
build: webui
links:
- redis
ports:
- "8000:80"
#volumes:
# - "./webui/files/:/files/"
- "8000:80"
volumes:
- "./webui/files/:/files/"
redis:
redis:
image: jpetazzo/hamba
command: 6379 AA.BB.CC.DD EEEEE
command: 6379 AA.BB.CC.DD:EEEEE
worker:
worker:
build: worker
links:
- rng0:rng
- hasher:hasher
- redis

View File

@@ -1,43 +0,0 @@
rng1:
build: rng
rng2:
build: rng
rng3:
build: rng
rng0:
image: jpetazzo/hamba
links:
- rng1
- rng2
- rng3
command: 80 rng1 80 rng2 80 rng3 80
ports:
- "8001:80"
hasher:
build: hasher
ports:
- "8002:80"
webui:
build: webui
extra_hosts:
redis: A.B.C.D
ports:
- "8000:80"
#volumes:
# - "./webui/files/:/files/"
#redis:
# image: redis
worker:
build: worker
links:
- rng0:rng
- hasher:hasher
extra_hosts:
redis: A.B.C.D

View File

@@ -0,0 +1,47 @@
version: "2"
services:
rng:
build: rng
ports:
- "8001:80"
logging:
driver: gelf
options:
gelf-address: udp://localhost:12201
hasher:
build: hasher
ports:
- "8002:80"
logging:
driver: gelf
options:
gelf-address: udp://localhost:12201
webui:
build: webui
ports:
- "8000:80"
volumes:
- "./webui/files/:/files/"
logging:
driver: gelf
options:
gelf-address: udp://localhost:12201
redis:
image: redis
logging:
driver: gelf
options:
gelf-address: udp://localhost:12201
worker:
build: worker
logging:
driver: gelf
options:
gelf-address: udp://localhost:12201

View File

@@ -0,0 +1,26 @@
version: "2"
services:
rng:
build: rng
ports:
- "80"
hasher:
build: hasher
ports:
- "8002:80"
webui:
build: webui
ports:
- "8000:80"
volumes:
- "./webui/files/:/files/"
redis:
image: redis
worker:
build: worker

View File

@@ -1,43 +1,38 @@
rng1:
version: "2"
services:
rng1:
build: rng
rng2:
build: rng
rng3:
build: rng
rng2:
build: rng
rng3:
build: rng
rng0:
rng:
image: jpetazzo/hamba
links:
- rng1
- rng2
- rng3
command: 80 rng1 80 rng2 80 rng3 80
command: 80 rng1:80 rng2:80 rng3:80
depends_on:
- rng1
- rng2
- rng3
ports:
- "8001:80"
- "8001:80"
hasher:
hasher:
build: hasher
ports:
- "8002:80"
- "8002:80"
webui:
webui:
build: webui
links:
- redis
ports:
- "8000:80"
- "8000:80"
volumes:
- "./webui/files/:/files/"
- "./webui/files/:/files/"
redis:
redis:
image: redis
worker:
worker:
build: worker
links:
- rng0:rng
- hasher:hasher
- redis

View File

@@ -0,0 +1,20 @@
version: '2'
services:
rng:
build: rng
hasher:
build: hasher
webui:
build: webui
ports:
- "8000:80"
redis:
image: redis
worker:
build: worker

View File

@@ -1,4 +1,5 @@
FROM ruby
FROM ruby:alpine
RUN apk add --update build-base
RUN gem install sinatra
RUN gem install thin
ADD hasher.rb /

View File

@@ -1,4 +1,4 @@
FROM python
FROM python:alpine
RUN pip install Flask
COPY rng.py /
CMD ["python", "rng.py"]

View File

@@ -1,6 +1,7 @@
FROM node
FROM node:4-slim
RUN npm install express
RUN npm install redis
COPY files/ /files/
COPY webui.js /
CMD ["node", "webui.js"]
EXPOSE 80

View File

@@ -2,13 +2,16 @@
<html>
<head>
<title>DockerCoin Miner WebUI</title>
<link rel="stylesheet" type="text/css" href="rickshaw.min.css">
<link rel="stylesheet" type="text/css" href="rickshaw.min.css">
<style>
#graph {
background-color: #eee;
width: 800px;
height: 400px;
}
#tweet {
color: royalblue;
}
</style>
<script src="jquery.js"></script>
<script src="d3.min.js"></script>
@@ -38,13 +41,22 @@ function refresh () {
while (points.length > 0) {
points.pop();
}
var speed;
for (var i=0; i<series.length-1; i++) {
// Compute instantaneous speed
var s1 = series[i];
var s2 = series[i+1];
var speed = (s2.hashes-s1.hashes)/(s2.now-s1.now);
speed = (s2.hashes-s1.hashes)/(s2.now-s1.now);
points.push({ x: s2.now, y: speed });
}
$("#speed").text("~" + speed.toFixed(1) + " hashes/second");
var msg = ("I'm attending the @docker workshop at @scaleconf, "
+ "and my #DockerCoins mining rig is crunching "
+ speed.toFixed(1) + " hashes/second! W00T!");
$("#tweet").attr(
"href",
"https://twitter.com/intent/tweet?text="+encodeURIComponent(msg)
);
if (graph == null) {
graph = new Rickshaw.Graph({
renderer: "area",
@@ -56,7 +68,7 @@ function refresh () {
series: [
{ name: "Coins",
color: "steelblue",
data: points
data: points
}
]
});
@@ -89,5 +101,11 @@ $(function () {
<div id="graph"></div>
<h2>
Current mining speed:
<span id="speed">-</span>
<a id="tweet" href="#">(Tweet this!)</a>
</h2>
</body>
</html>

View File

@@ -1,4 +1,4 @@
FROM python
FROM python:alpine
RUN pip install redis
RUN pip install requests
COPY worker.py /

55
elk/docker-compose.yml Normal file
View File

@@ -0,0 +1,55 @@
version: "2"
services:
elasticsearch:
image: elasticsearch
# If you need to access ES directly, just uncomment those lines.
#ports:
# - "9200:9200"
# - "9300:9300"
logstash:
image: logstash
command: |
-e '
input {
# Default port is 12201/udp
gelf { }
# This generates one test event per minute.
# It is great for debugging, but you might
# want to remove it in production.
heartbeat { }
}
# The following filter is a hack!
# The "de_dot" filter would be better, but it
# is not pre-installed with logstash by default.
filter {
ruby {
code => "
event.to_hash.keys.each { |k| event[ k.gsub('"'.'"','"'_'"') ] = event.remove(k) if k.include?'"'.'"' }
"
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
}
# This will output every message on stdout.
# It is great when testing your setup, but in
# production, it will probably cause problems;
# either by filling up your disks, or worse,
# by creating logging loops! BEWARE!
stdout {
codec => rubydebug
}
}'
ports:
- "12201:12201/udp"
kibana:
image: kibana
ports:
- "5601:5601"
environment:
ELASTICSEARCH_URL: http://elasticsearch:9200

View File

@@ -1,62 +0,0 @@
#!/usr/bin/env python
import os
import sys
import yaml
# You can specify up to 2 parameters:
# - with 0 parameter, we will look for the COMPOSE_FILE env var
# - with 1 parameter, the same file will be used for in and out
# - with 2 parameters, the 1st is the input, the 2nd the output
if len(sys.argv)==1:
if "COMPOSE_FILE" not in os.environ:
print("Please specify 1 or 2 file names, or set COMPOSE_FILE.")
sys.exit(1)
compose_file = os.environ["COMPOSE_FILE"]
if compose_file == "docker-compose.yml":
print("Refusing to operate directly on docker-compose.yml.")
print("Specify it on the command-line if that's what you want.")
sys.exit(1)
input_file, output_file = compose_file, compose_file
elif len(sys.argv)==2:
input_file, output_file = sys.argv[1], sys.argv[1]
elif len(sys.argv)==3:
input_file, output_file = sys.argv[1], sys.argv[2]
else:
print("Too many arguments. Please specify up to 2 file names.")
sys.exit(1)
stack = yaml.load(open(input_file))
# The ambassadors need to know the service port to use.
# Those ports must be declared here.
ports = yaml.load(open("ports.yml"))
def generate_local_addr():
last_byte = 2
while last_byte<255:
yield "127.127.0.{}".format(last_byte)
last_byte += 1
for service_name, service in stack.items():
if "links" in service:
for link, local_addr in zip(service["links"], generate_local_addr()):
if link not in ports:
print("Skipping link {} in service {} "
"(no port mapping defined). "
"Your code will probably break."
.format(link, service_name))
continue
if "extra_hosts" not in service:
service["extra_hosts"] = {}
service["extra_hosts"][link] = local_addr
del service["links"]
if "ports" in service:
del service["ports"]
if "volumes" in service:
del service["volumes"]
if service_name in ports:
service["ports"] = [ ports[service_name] ]
yaml.safe_dump(stack, open(output_file, "w"), default_flow_style=False)

1
prepare-local/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vagrant

85
prepare-local/README.md Normal file
View File

@@ -0,0 +1,85 @@
DOCKER ORCHESTRATION (local environment instructions)
=====================================================
Instead of running this training on a cloud provider, you can simulate the
infrastructure locally. These instructions apply to the **PART ONE** of the
workshop.
## 1. Prerequisites
Virtualbox, Vagrant and Ansible
- Virtualbox: https://www.virtualbox.org/wiki/Downloads
- Vagrant: https://www.vagrantup.com/downloads.html
- Ansible:
- install Ansible's prerequisites:
$ sudo pip install paramiko PyYAML Jinja2 httplib2 six
- clone the Ansible repository and checkout to a stable version
(don't forget the `--recursive` argument when cloning!):
$ git clone --recursive https://github.com/ansible/ansible.git
$ cd ansible
$ git checkout stable-2.0.0.1
$ git submodule update
- source the setup script to make Ansible available on this terminal session:
$ source path/to/your-ansible-clone/hacking/env-setup
- you need to repeat the last step everytime you open a new terminal session
and want to use any Ansible command (but you'll probably only need to run
it once).
## 2. Preparing the environment
Run the following commands:
$ vagrant up
$ ansible-playbook provisioning.yml
And that's it! Now you should be able to ssh on `node1` using:
$ ssh vagrant@10.10.10.10 -i private-key
These are the default IP addresses for the nodes:
10.10.10.10 node1
10.10.10.20 node2
10.10.10.30 node3
10.10.10.40 node4
10.10.10.50 node5
The source code of this repo will be mounted at `~/orchestration-workshop`
(only on the `node1`), so you can edit the code externally and the changes
will reflect inside the instance.
## 3. Possible problems and solutions
- Depending on the Vagrant version, `sudo apt-get install bsdtar` may be needed
- If you get strange Ansible errors about dependencies, try to check your pip
version with `pip --version`. The current version is 8.1.1. If your pip is
older than this, upgrade it with `sudo pip install --upgrade pip`, restart
your terminal session and install the Ansible prerequisites again.
- If the IP's `10.10.10.[10-50]` are already taken in your machine, you can
change them to other values in the `vagrant.yml` and `inventory` files in
this directory. Make sure you pick a set of IP's inside the same subnet.
- If you suspend your computer, the simulated private network may stop to
work. This is a known problem of Virtualbox. To fix it, reload all the VM's
with `vagrant reload`.
- If you get a ssh error saying `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED`
it means that you already had in the past some other host using one of the
IP addresses we use here. To solve this, remove the old entry in your
`known_hosts` file with:
$ ssh-keygen -f "~/.ssh/known_hosts" -R 10.10.10.10 -R 10.10.10.20 -R 10.10.10.30 -R 10.10.10.40 -R 10.10.10.50

78
prepare-local/Vagrantfile vendored Normal file
View File

@@ -0,0 +1,78 @@
# vim: set filetype=ruby:
require 'yaml'
require 'vagrant-vbguest' unless defined? VagrantVbguest::Config
configuration_file = File.expand_path("vagrant.yml", File.dirname(__FILE__))
configuration = YAML.load_file configuration_file
settings = configuration['vagrant']
instances = configuration['instances']
$enable_serial_logging = false
Vagrant.configure('2') do |config|
def check_dependency(plugin_name)
unless Vagrant.has_plugin?(plugin_name)
puts "Vagrant [" + plugin_name + "] is required but is not installed\n" +
"please check if you have the plugin with the following command:\n" +
" $ vagrand plugin list\n" +
"If needed install the plugin:\n" +
" $ vagrant plugin install " + plugin_name + "\n"
abort "Missing [" + plugin_name + "] plugin\n\n"
end
end
check_dependency 'vagrant-vbguest'
config.vm.box = settings['default_box']
config.vm.box_url = settings['default_box_url']
config.ssh.forward_agent = true
config.ssh.insert_key = settings['ssh_insert_key']
config.vm.box_check_update = true
config.vbguest.auto_update = false
instances.each do |instance|
next if instance.has_key? 'deactivated' and instance['deactivated']
config.vm.define instance['hostname'] do |guest|
if instance.has_key? 'box'
guest.vm.box = instance['box']
end
if instance.has_key? 'box_url'
guest.vm.box_url = instance['box_url']
end
if instance.has_key? 'private_ip'
guest.vm.network 'private_network', ip: instance['private_ip']
end
guest.vm.provider 'virtualbox' do |vb|
if instance.has_key? 'cpu_execution_cap'
vb.customize ["modifyvm", :id, "--cpuexecutioncap", instance['cpu_execution_cap'].to_s]
end
vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
if instance.has_key? 'memory'
vb.memory = instance['memory']
end
if instance.has_key? 'cores'
vb.cpus = instance['cores']
end
end
if instance.has_key? 'mounts'
instance['mounts'].each do |mount|
guest.vm.synced_folder mount['host_path'], mount['guest_path'], owner: mount['owner'], group: mount['group']
end
end
end
end
end

10
prepare-local/ansible.cfg Normal file
View File

@@ -0,0 +1,10 @@
[defaults]
nocows = True
inventory = inventory
remote_user = vagrant
private_key_file = private-key
host_key_checking = False
deprecation_warnings = False
[ssh_connection]
ssh_args = -o StrictHostKeyChecking=no

11
prepare-local/inventory Normal file
View File

@@ -0,0 +1,11 @@
node1 ansible_ssh_host=10.10.10.10
node2 ansible_ssh_host=10.10.10.20
node3 ansible_ssh_host=10.10.10.30
node4 ansible_ssh_host=10.10.10.40
node5 ansible_ssh_host=10.10.10.50
[nodes]
node[1:5]
[all:children]
nodes

27
prepare-local/private-key Normal file
View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI
w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP
kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2
hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO
Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW
yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd
ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1
Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf
TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK
iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A
sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf
4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP
cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk
EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN
CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX
3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG
YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj
3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+
dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz
6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC
P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF
llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ
kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH
+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ
NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,142 @@
---
- hosts: nodes
sudo: true
vars_files:
- vagrant.yml
tasks:
- name: clean up the home folder
file:
path: /home/vagrant/{{ item }}
state: absent
with_items:
- base.sh
- chef.sh
- cleanup.sh
- cleanup-virtualbox.sh
- puppetlabs-release-wheezy.deb
- puppet.sh
- ruby.sh
- vagrant.sh
- virtualbox.sh
- zerodisk.sh
- name: installing dependencies
apt:
name: apt-transport-https,ca-certificates,python-pip,tmux
state: present
update_cache: true
- name: fetching docker repo key
apt_key:
keyserver: hkp://p80.pool.sks-keyservers.net:80
id: 58118E89F3A912897C070ADBF76221572C52609D
- name: adding package repos
apt_repository:
repo: "{{ item }}"
state: present
with_items:
- deb http://http.debian.net/debian wheezy-backports main
- deb https://apt.dockerproject.org/repo {{ ansible_lsb.id|lower }}-{{ ansible_lsb.codename }} main
- name: installing docker
apt:
name: docker-engine
state: present
update_cache: true
- name: adding user vagrant to group docker
user:
name: vagrant
groups: docker
append: yes
- name: making docker daemon listen to port 55555
lineinfile:
dest: /etc/default/docker
line: DOCKER_OPTS="--host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:55555"
regexp: '^#?DOCKER_OPTS=.*$'
state: present
register: docker_opts
- name: restarting docker daemon, if needed
service:
name: docker
state: restarted
when: docker_opts is defined and docker_opts.changed
- name: performing pip autoupgrade
pip:
name: pip
state: latest
- name: installing virtualenv
pip:
name: virtualenv
state: latest
- name: creating docker-compose folder
file:
path: /opt/docker-compose
state: directory
register: docker_compose_folder
- name: creating virtualenv for docker-compose
shell: virtualenv /opt/docker-compose
when: docker_compose_folder is defined and docker_compose_folder.changed
- name: installing docker-compose
pip:
name: docker-compose
state: latest
virtualenv: /opt/docker-compose
- name: making the docker-compose command available to user
lineinfile:
dest: .bashrc
line: "alias docker-compose='/opt/docker-compose/bin/docker-compose'"
state: present
regexp: '^alias docker-compose=.*$'
- name: building the /etc/hosts file with all nodes
lineinfile:
dest: /etc/hosts
line: "{{ item.private_ip }} {{ item.hostname }}"
regexp: "^{{ item.private_ip }} {{ item.hostname }}$"
state: present
with_items: instances
- name: copying the ssh key to the nodes
copy:
src: private-key
dest: /home/vagrant/private-key
mode: 0600
group: root
owner: vagrant
- name: copying ssh configuration
copy:
src: ssh-config
dest: /home/vagrant/.ssh/config
mode: 0600
group: root
owner: vagrant
- name: fixing the hostname
hostname:
name: "{{ inventory_hostname }}"
- name: adjusting the /etc/hosts to the new hostname
lineinfile:
dest: /etc/hosts
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
owner: root
group: root
mode: 0644
with_items:
- regexp: '^127\.0\.0\.1'
line: "127.0.0.1 localhost {{ inventory_hostname }}"
- regexp: '^127\.0\.1\.1'
line: "127.0.1.1 {{ inventory_hostname }}"

3
prepare-local/ssh-config Normal file
View File

@@ -0,0 +1,3 @@
Host node*
IdentityFile ~/private-key
StrictHostKeyChecking no

41
prepare-local/vagrant.yml Normal file
View File

@@ -0,0 +1,41 @@
---
vagrant:
default_box: debian-7.2.0
default_box_url: https://dl.dropboxusercontent.com/u/197673519/debian-7.2.0.box
default_box_check_update: true
ssh_insert_key: false
min_memory: 256
min_cores: 1
instances:
- hostname: node1
private_ip: 10.10.10.10
memory: 512
cores: 1
mounts:
- host_path: ../
guest_path: /home/vagrant/orchestration-workshop
owner: vagrant
group: vagrant
- hostname: node2
private_ip: 10.10.10.20
memory: 512
cores: 1
- hostname: node3
private_ip: 10.10.10.30
memory: 512
cores: 1
- hostname: node4
private_ip: 10.10.10.40
memory: 512
cores: 1
- hostname: node5
private_ip: 10.10.10.50
memory: 512
cores: 1

52
prepare-vms/Dockerfile Normal file
View File

@@ -0,0 +1,52 @@
FROM debian:jessie
MAINTAINER AJ Bowen <aj@soulshake.net>
RUN apt-get update
RUN apt-get install -y ca-certificates
RUN apt-get install -y groff
RUN apt-get install -y less
RUN apt-get install -y python python-pip
RUN apt-get install -y python-docutils
RUN apt-get install -y sudo
RUN apt-get install -y \
bsdmainutils \
curl \
jq \
less \
man \
pssh \
ssh
RUN pip install awscli
RUN pip install \
pdfkit \
PyYAML \
termcolor
RUN apt-get install -y wkhtmltopdf
ENV HOME /home/user
RUN useradd --create-home --home-dir $HOME user \
&& mkdir -p $HOME/.config/gandi \
&& chown -R user:user $HOME
RUN echo "user ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# Replace 1000 with your user / group id
#RUN export uid=1000 gid=1000 && \
# mkdir -p /home/user && \
# mkdir -p /etc/sudoers.d && \
# echo "user:x:${uid}:${gid}:user,,,:/home/user:/bin/bash" >> /etc/passwd && \
# echo "user:x:${uid}:" >> /etc/group && \
# echo "user ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/user && \
# chmod 0440 /etc/sudoers.d/user && \
# chown ${uid}:${gid} -R /home/user
WORKDIR $HOME
RUN echo "alias ll='ls -lahF'" >> /home/user/.bashrc
RUN echo "export PATH=$PATH:/home/user/bin" >> /home/user/.bashrc
RUN mkdir -p /home/user/bin
RUN ln -s /home/user/prepare-vms/scripts/trainer-cli /home/user/bin/trainer-cli
USER user

125
prepare-vms/README.md Normal file
View File

@@ -0,0 +1,125 @@
# Trainer tools to prepare VMs for Docker workshops
There are several options for using these tools:
### Clone the repo
$ git clone https://github.com/soulshake/prepare-vms.git
$ cd prepare-vms
$ docker-compose build
$ mkdir $HOME/bin && ln -s `pwd`/trainer $HOME/bin/trainer
### Via the image
$ docker pull soulshake/prepare-vms
### Submodule
This repo can be added as a submodule in the repo of the Docker workshop:
$ git submodule add https://github.com/soulshake/prepare-vms.git
## Setup
### Export needed envvars
Required environment variables:
* `AWS_ACCESS_KEY_ID`
* `AWS_SECRET_ACCESS_KEY`
* `AWS_DEFAULT_REGION`
### Update settings.yaml
If you have more than one workshop:
$ cp settings.yaml settings/YOUR_WORKSHOP_NAME-settings.yaml
$ ln -s settings/YOUR_WORKSHOP_NAME-settings.yaml `pwd`/settings.yaml
Update the `settings.yaml` as needed. This is the file that will be used to generate cards.
## Usage
### Summary
Summary of steps to launch a batch of instances for a workshop:
* Export the environment variables needed by the AWS CLI (see **Requirements** below)
* `trainer start NUMBER_OF_VMS` to create AWS instances
* `trainer deploy TAG` to run `scripts/postprep.rc` via parallel-ssh
* `trainer pull-images TAG` to pre-pull a bunch of Docker images
* `trainer test TAG`
* `trainer cards TAG` to generate a PDF and an HTML file you can print
The `trainer` script can be executed directly.
It will check for the necessary environment variables. Then, if all its dependencies are installed
locally, it will execute `trainer-cli`. If not, it will look for a local Docker image
tagged `soulshake/trainer-tools`. If found, it will run in a container. If not found,
the user will be prompted to either install the missing dependencies or download
the Docker image.
## Detailed usage
### Start some VMs
$ trainer start 10
A few things will happen:
#### Sync of SSH keys
When the `start` command is run, your local RSA SSH public key will be added to your AWS EC2 keychain.
To see which local key will be uploaded, run `ssh-add -l | grep RSA`.
#### Instance + tag creation
10 VMs will be started, with an automatically generated tag (timestamp + your username).
Your SSH key will be added to the `authorized_keys` of the ubuntu user.
#### Creation of ./$TAG/ directory and contents
Following the creation of the VMs, a text file will be created containing a list of their IPs.
This ips.txt file will be created in the $TAG/ directory and a symlink will be placed in the working directory of the script.
If you create new VMs, the symlinked file will be overwritten.
## Deployment
Instances can be deployed manually using the `deploy` command:
$ trainer deploy TAG
The `postprep.rc` file will be copied via parallel-ssh to all of the VMs and executed.
### Pre-pull images
$ trainer pull-images TAG
### Generate cards
$ trainer cards TAG
### List tags
$ trainer list
### List VMs
$ trainer list TAG
This will print a human-friendly list containing some information about each instance.
### Stop and destroy VMs
$ trainer stop TAG
## ToDo
* Don't write to bash history in system() in postprep
* compose, etc version inconsistent (int vs str)

View File

@@ -0,0 +1,38 @@
prepare-vms:
build: .
container_name: prepare-vms
working_dir: /home/user/prepare-vms
volumes:
- $HOME/.aws/:$HOME.aws/
- $HOME/.ssh/:/home/user/.ssh/
- /etc/localtime:/etc/localtime:ro
#- /home:/home
- /tmp/.X11-unix:/tmp/.X11-unix
- $SSH_AUTH_DIRNAME:$SSH_AUTH_DIRNAME
- $SCRIPT_DIR/:/home/user/prepare-vms/
#- $SCRIPT_DIR/:$HOME/prepare-vms/
#- $HOME/trainer-tools/
#- $(dirname $SSH_AUTH_SOCK):$(dirname $SSH_AUTH_SOCK)
#- /etc/passwd:/etc/passwd:ro
#- /etc/group:/etc/group:ro
#- /run/user:/run/user
environment:
HOME: /home/user
#SCRIPT_DIR: /home/aj/git/prepare-vms
SCRIPT_DIR: /home/user/prepare-vms
HOME: /home/user
SSH_AUTH_SOCK: ${SSH_AUTH_SOCK}
SSH_AGENT_PID: ${SSH_AGENT_PID}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
DISPLAY: ${DISPLAY}
USER: ${USER}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION}
AWS_DEFAULT_OUTPUT:
AWS_INSTANCE_TYPE: ${AWS_INSTANCE_TYPE}
AWS_VPC_ID: ${AWS_VPC_ID}
entrypoint: /home/user/prepare-vms/scripts/trainer-cli
#entrypoint: trainer
#AWS_DEFAULT_PROFILE: ${AWS_DEFAULT_PROFILE}
#command: /home/user/prepare-vms/trainer-cli

View File

@@ -1,82 +0,0 @@
#!/usr/bin/env python
SETTINGS_BASIC = dict(
clustersize=1,
pagesize=15,
blurb="<p>Here is the connection information to your very own "
"VM for this intro to Docker workshop. You can connect "
"to the VM using your SSH client.</p>\n"
"<p>Your VM is reachable on the following address:</p>\n",
prettify=lambda x: x,
footer="<p>You can find the last version of the slides on "
"http://lisa.dckr.info/.</p>",
)
SETTINGS_ADVANCED = dict(
clustersize=5,
pagesize=12,
blurb="<p>Here is the connection information to your very own "
"cluster for this orchestration workshop. You can connect "
"to each VM with any SSH client.</p>\n"
"<p>Your machines are:<ul>\n",
prettify=lambda l: [ "node%d: %s"%(i+1, s)
for (i, s) in zip(range(len(l)), l) ],
footer="<p>You can find the last version of the slides on "
"http://lisa.dckr.info/.</p>"
)
SETTINGS = SETTINGS_BASIC
globals().update(SETTINGS)
###############################################################################
ips = list(open("ips.txt"))
assert len(ips)%clustersize == 0
clusters = []
while ips:
cluster = ips[:clustersize]
ips = ips[clustersize:]
clusters.append(cluster)
html = open("ips.html", "w")
html.write("<html><head><style>")
html.write("""
div {
float:left;
border: 1px solid black;
width: 28%;
padding: 4% 2.5% 2.5% 2.5%;
font-size: x-small;
background-image: url("docker-nb.svg");
background-size: 15%;
background-position-x: 50%;
background-repeat: no-repeat;
}
p {
margin: 0.5em 0 0.5em 0;
}
.pagebreak {
page-break-before: always;
clear: both;
display: block;
height: 8px;
}
""")
html.write("</style></head><body>")
for i, cluster in enumerate(clusters):
if i>0 and i%pagesize==0:
html.write('<span class="pagebreak"></span>\n')
html.write("<div>")
html.write(blurb)
for s in prettify(cluster):
html.write("<li>%s</li>\n"%s)
html.write("</ul></p>")
html.write("<p>login=docker password=training</p>\n")
html.write(footer)
html.write("</div>")
html.close()

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -1,43 +0,0 @@
pssh -I tee /tmp/postprep.py <<EOF
#!/usr/bin/env python
import os
import sys
import urllib
clustersize = 5
myaddr = urllib.urlopen("http://myip.enix.org/REMOTE_ADDR").read()
addresses = list(l.strip() for l in sys.stdin)
def makenames(addrs):
return [ "node%s"%(i+1) for i in range(len(addrs)) ]
while addresses:
cluster = addresses[:clustersize]
addresses = addresses[clustersize:]
if myaddr not in cluster:
continue
names = makenames(cluster)
for ipaddr, name in zip(cluster, names):
os.system("grep ^%s /etc/hosts || echo %s %s | sudo tee -a /etc/hosts"
%(ipaddr, ipaddr, name))
if myaddr == cluster[0]:
os.system("[ -f .ssh/id_rsa ] || ssh-keygen -t rsa -f .ssh/id_rsa -P ''")
os.system("sudo apt-get -qy install python-setuptools pssh apache2-utils httping htop unzip")
os.system("sudo easy_install pip")
os.system("sudo pip install docker-compose==1.5.0")
os.system("docker pull swarm:1.0.0")
os.system("docker tag -f swarm:1.0.0 swarm")
os.system("sudo curl -L https://github.com/docker/machine/releases/download/v0.5.0/docker-machine_linux-amd64.zip -o /tmp/docker-machine.zip")
os.system("cd /usr/local/bin ; sudo unzip /tmp/docker-machine.zip")
os.system("sudo chmod +x /usr/local/bin/docker-machine*")
os.system("echo 1000000 | sudo tee /proc/sys/net/nf_conntrack_max")
os.system("""sudo sed -i 's,^DOCKER_OPTS=.*,DOCKER_OPTS="-H unix:///var/run/docker.sock -H tcp://0.0.0.0:55555",' /etc/default/docker""")
os.system("sudo service docker restart")
EOF
pssh -t 300 -I "python /tmp/postprep.py >>/tmp/pp.out 2>>/tmp/pp.err" < ips.txt
pssh "[ -f .ssh/id_rsa ] || scp -o StrictHostKeyChecking=no node1:.ssh/id_rsa* .ssh"
pssh "grep docker@ .ssh/authorized_keys || cat .ssh/id_rsa.pub >> .ssh/authorized_keys"

View File

@@ -1,23 +0,0 @@
pssh () {
HOSTFILES="hosts ips.txt /tmp/hosts"
for HOSTFILE in $HOSTFILES; do
[ -f $HOSTFILE ] && break
done
[ -f $HOSTFILE ] || {
echo "No hostfile found (tried $HOSTFILES)"
return
}
parallel-ssh -h $HOSTFILE -l docker \
-O UserKnownHostsFile=/dev/null -O StrictHostKeyChecking=no \
-O ForwardAgent=yes \
"$@"
}
pcopykey () {
ssh-add -L | pssh --askpass --send-input \
"mkdir -p .ssh; tee .ssh/authorized_keys"
ssh-add -L | pssh --send-input \
"sudo mkdir -p /root/.ssh; sudo tee /root/.ssh/authorized_keys"
}

116
prepare-vms/scripts/aws.sh Executable file
View File

@@ -0,0 +1,116 @@
#!/bin/bash
source scripts/cli.sh
aws_display_tags(){
# Print all "Name" tags in our region with their instance count
echo "[#] [Status] [Tag]" | awk '{ printf " %7s %8s %10s \n", $1, $2, $3}'
aws ec2 describe-instances --filter "Name=tag:Name,Values=[*]" \
--query "Reservations[*].Instances[*].[{Tags:Tags[0].Value,State:State.Name}]" \
| awk '{ printf " %-13s %-10s %-1s\n", $1, $2, $3}' \
| uniq -c \
| sort -k 3
}
aws_display_tokens(){
# Print all tokens in our region with their instance count
echo "[#] [Token] [Tag]" | awk '{ printf " %7s %12s %30s\n", $1, $2, $3}'
# --query 'Volumes[*].{ID:VolumeId,AZ:AvailabilityZone,Size:Size}'
aws ec2 describe-instances --output text \
--query 'Reservations[*].Instances[*].{ClientToken:ClientToken,Tags:Tags[0].Value}' \
| awk '{ printf " %7s %12s %50s\n", $1, $2, $3}' \
| sort \
| uniq -c \
| sort -k 3
}
aws_get_tokens() {
aws ec2 describe-instances --output text \
--query 'Reservations[*].Instances[*].[ClientToken]' \
| sort -u
}
aws_display_instance_statuses_by_tag() {
TAG=$1
need_tag $TAG
IDS=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=$TAG" \
--query "Reservations[*].Instances[*].InstanceId" | tr '\t' ' ' )
aws ec2 describe-instance-status \
--instance-ids $IDS \
--query "InstanceStatuses[*].{ID:InstanceId,InstanceState:InstanceState.Name,InstanceStatus:InstanceStatus.Status,SystemStatus:SystemStatus.Status,Reachability:InstanceStatus.Status}" \
--output table
}
aws_display_instances_by_tag() {
TAG=$1
need_tag $TAG
result=$(aws ec2 describe-instances --output table \
--filter "Name=tag:Name,Values=$TAG" \
--query "Reservations[*].Instances[*].[ \
InstanceId, \
State.Name, \
Tags[0].Value, \
PublicIpAddress, \
InstanceType \
]"
)
if [[ -z $result ]]; then
echo "No instances found with tag $TAG in region $AWS_DEFAULT_REGION."
else
echo "ID State Tags IP Type" \
| awk '{ printf "%9s %12s %15s %20s %14s \n", $1, $2, $3, $4, $5}' # column -t -c 70}
echo "$result"
fi
}
aws_get_instance_ids_by_client_token() {
TOKEN=$1
need_tag $TOKEN
aws ec2 describe-instances --filters "Name=client-token,Values=$TOKEN" \
| grep ^INSTANCE \
| awk '{print $8}'
}
aws_get_instance_ids_by_tag() {
TAG=$1
need_tag $TAG
aws ec2 describe-instances --filters "Name=tag:Name,Values=$TAG" \
| grep ^INSTANCE \
| awk '{print $8}'
}
aws_get_instance_ips_by_tag() {
TAG=$1
need_tag $TAG
aws ec2 describe-instances --filter "Name=tag:Name,Values=$TAG" \
--output text \
--query "Reservations[*].Instances[*].PublicIpAddress" \
| tr "\t" "\n" \
| sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 # sort IPs
}
aws_kill_instances_by_tag() {
TAG=$1
need_tag $TAG
IDS=$(aws_get_instance_ids_by_tag $TAG)
if [ -z "$IDS" ]; then
die "Invalid tag."
fi
echo "Deleting instances with tag $TAG"
aws ec2 terminate-instances --instance-ids $IDS \
| grep ^TERMINATINGINSTANCES
}
aws_tag_instances() {
OLD_TAG_OR_TOKEN=$1
NEW_TAG=$2
IDS=$(aws_get_instance_ids_by_client_token $OLD_TAG_OR_TOKEN)
[[ -n "$IDS" ]] && aws ec2 create-tags --tag Key=Name,Value=$NEW_TAG --resources $IDS >/dev/null
IDS=$(aws_get_instance_ids_by_tag $OLD_TAG_OR_TOKEN)
[[ -n "$IDS" ]] && aws ec2 create-tags --tag Key=Name,Value=$NEW_TAG --resources $IDS >/dev/null
}

39
prepare-vms/scripts/cli.sh Executable file
View File

@@ -0,0 +1,39 @@
die () {
if [ -n "$1" ]; then
>&2 echo -n $(tput setaf 1)
>&2 echo -e "$1"
>&2 echo -n $(tput sgr0)
fi
exit 1
}
need_tag(){
TAG=$1
if [ -z "$TAG" ]; then
echo "Please specify a tag. Here's the list: "
aws_display_tags
die
fi
}
need_token(){
TOKEN=$1
if [ -z "$TOKEN" ]; then
echo "Please specify a token. Here's the list: "
aws_display_tokens
die
fi
}
need_ips_file() {
IPS_FILE=$1
if [ -z "$IPS_FILE" ]; then
echo "IPS_FILE not set."
die
fi
if [ ! -s "$IPS_FILE" ]; then
echo "IPS_FILE $IPS_FILE not found. Please run: trainer ips <TAG>"
die
fi
}

View File

@@ -0,0 +1,15 @@
bold() {
msg=$1
echo "$(tput bold)$1$(tput sgr0)"
}
green() {
msg=$1
echo "$(tput setaf 2)$1$(tput sgr0)"
}
yellow(){
msg=$1
echo "$(tput setaf 3)$1$(tput sgr0)"
}

View File

@@ -0,0 +1,132 @@
#!/bin/bash
# borrowed from https://gist.github.com/kirikaza/6627072
usage() {
cat >&2 <<__
usage: find-ubuntu-ami.sh [ <filter>... ] [ <sorting> ]
where:
<filter> is pair of key and substring to search
-r <region>
-n <name>
-v <version>
-a <arch>
-t <type>
-d <date>
-i <image>
-k <kernel>
<sorting> is on of:
-R by region
-N by name
-V by version
-A by arch
-T by type
-D by date
-I by image
-K by kernel
protip for Docker orchestration workshop admin:
./find-ubuntu-ami.sh -t hvm:ebs -r \$AWS_REGION -v 15.10 -N
__
exit 1
}
args=`getopt hr:n:v:a:t:d:i:k:RNVATDIK $*`
if [ $? != 0 ] ; then
echo >&2
usage
fi
region=
name=
version=
arch=
type=
date=
image=
kernel=
sort=date
set -- $args
for a ; do
case "$a" in
-h) usage ;;
-r) region=$2 ; shift ;;
-n) name=$2 ; shift ;;
-v) version=$2 ; shift ;;
-a) arch=$2 ; shift ;;
-t) type=$2 ; shift ;;
-d) date=$2 ; shift ;;
-i) image=$2 ; shift ;;
-k) kernel=$2 ; shift ;;
-R) sort=region ;;
-N) sort=name ;;
-V) sort=version ;;
-A) sort=arch ;;
-T) sort=type ;;
-D) sort=date ;;
-I) sort=image ;;
-K) sort=kernel ;;
--) shift ; break ;;
*) continue ;;
esac
shift
done
[ $# = 0 ] || usage
fix_json() {
tr -d \\n | sed 's/,]}/]}/'
}
jq_query() { cat <<__
.aaData | map (
{
region: .[0],
name: .[1],
version: .[2],
arch: .[3],
type: .[4],
date: .[5],
image: .[6],
kernel: .[7]
} | select (
(.region | contains("$region")) and
(.name | contains("$name")) and
(.version | contains("$version")) and
(.arch | contains("$arch")) and
(.type | contains("$type")) and
(.date | contains("$date")) and
(.image | contains("$image</a>")) and
(.kernel | contains("$kernel"))
)
) | sort_by(.$sort) | .[] |
"\(.region)|\(.name)|\(.version)|\(.arch)|\(.type)|\(.date)|\(.image)|\(.kernel)"
__
}
trim_quotes() {
sed 's/^"//;s/"$//'
}
escape_spaces() {
sed 's/ /\\\ /g'
}
url=http://cloud-images.ubuntu.com/locator/ec2/releasesTable
{
echo REGION NAME VERSION ARCH TYPE DATE IMAGE KERNEL
curl -s $url | fix_json | jq "`jq_query`" | trim_quotes | escape_spaces | tr \| ' '
} |
while read region name version arch type date image kernel ; do
image=${image%<*}
image=${image#*>}
echo "$region|$name|$version|$arch|$type|$date|$image|$kernel"
done | column -t -s \|

View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python
import os
import sys
import yaml
try:
import pdfkit
except ImportError:
print("WARNING: could not import pdfkit; PDF generation will fali.")
def prettify(l):
l = [ip.strip() for ip in l]
ret = [ "node{}: <code>{}</code>".format(i+1, s) for (i, s) in zip(range(len(l)), l) ]
return ret
# Read settings from settings.yaml
with open(sys.argv[1]) as f:
data = f.read()
SETTINGS = yaml.load(data)
SETTINGS['footer'] = SETTINGS['footer'].format(url=SETTINGS['url'])
globals().update(SETTINGS)
###############################################################################
ips = list(open("ips.txt"))
print("Current settings (as defined in settings.yaml):")
print(" Number of IPs: {}".format(len(ips)))
print(" VMs per cluster: {}".format(clustersize))
print("Background image: {}".format(background_image))
print("---------------------------------------------")
assert len(ips)%clustersize == 0
if clustersize == 1:
blurb = blurb.format(
cluster_or_machine="machine",
this_or_each="this",
machine_is_or_machines_are="machine is",
workshop_name=workshop_short_name,
)
else:
blurb = blurb.format(
cluster_or_machine="cluster",
this_or_each="each",
machine_is_or_machines_are="machines are",
workshop_name=workshop_short_name,
)
clusters = []
while ips:
cluster = ips[:clustersize]
ips = ips[clustersize:]
clusters.append(cluster)
html = open("ips.html", "w")
html.write("<html><head><style>")
head = """
div {{
float:left;
border: 1px dotted black;
width: 27%;
padding: 6% 2.5% 2.5% 2.5%;
font-size: x-small;
background-image: url("{background_image}");
background-size: 13%;
background-position-x: 50%;
background-position-y: 5%;
background-repeat: no-repeat;
}}
p {{
margin: 0.5em 0 0.5em 0;
}}
.pagebreak {{
page-break-before: always;
clear: both;
display: block;
height: 8px;
}}
"""
head = head.format(background_image=SETTINGS['background_image'])
html.write(head)
html.write("</style></head><body>")
for i, cluster in enumerate(clusters):
if i>0 and i%pagesize==0:
html.write('<span class="pagebreak"></span>\n')
html.write("<div>")
html.write(blurb)
for s in prettify(cluster):
html.write("<li>%s</li>\n"%s)
html.write("</ul></p>")
html.write("<p>login: <b><code>{}</code></b> <br>password: <b><code>{}</code></b></p>\n".format(instance_login, instance_password))
html.write(footer)
html.write("</div>")
html.close()
"""
html.write("<div>")
html.write("<p>{}</p>".format(blurb))
for s in prettify(cluster):
html.write("<li>{}</li>".format(s))
html.write("</ul></p>")
html.write("<center>")
html.write("<p>login: <b><code>{}</code></b> &nbsp&nbsp password: <b><code>{}</code></b></p>\n".format(instance_login, instance_password))
html.write("</center>")
html.write(footer)
html.write("</div>")
html.close()
"""
with open('ips.html') as f:
pdfkit.from_file(f, 'ips.pdf')

209
prepare-vms/scripts/postprep.rc Executable file
View File

@@ -0,0 +1,209 @@
pssh -I tee /tmp/settings.yaml < $SETTINGS
pssh sudo easy_install pyyaml
pssh -I tee /tmp/postprep.py <<EOF
#!/usr/bin/env python
import os
import platform
import sys
import time
import urllib
import yaml
#################################
config = yaml.load(open("/tmp/settings.yaml"))
COMPOSE_VERSION = config["compose_version"]
MACHINE_VERSION = config["machine_version"]
SWARM_VERSION = config["swarm_version"]
CLUSTER_SIZE = config["clustersize"]
ENGINE_VERSION = config["engine_version"]
#################################
# This script will be run as ubuntu user, which has root privileges.
# docker commands will require sudo because the ubuntu user has no access to the docker socket.
STEP = 0
START = time.time()
def bold(msg):
return "{} {} {}".format("$(tput smso)", msg, "$(tput rmso)")
def system(cmd):
global STEP
with open("/tmp/pp.status", "a") as f:
t1 = time.time()
f.write(bold("--- RUNNING [step {}] ---> {}...".format(STEP, cmd)))
retcode = os.system(cmd)
if retcode:
retcode = bold(retcode)
t2 = time.time()
td = str(t2-t1)[:5]
f.write("[{}] in {}s\n".format(retcode, td))
STEP += 1
with open("/home/ubuntu/.bash_history", "a") as f:
f.write("{}\n".format(cmd))
# On EC2, the ephemeral disk might be mounted on /mnt.
# If /mnt is a mountpoint, place Docker workspace on it.
system("if mountpoint -q /mnt; then sudo mkdir /mnt/docker && sudo ln -s /mnt/docker /var/lib/docker; fi")
# Put our public IP in /tmp/ipv4
# ipv4_retrieval_endpoint = "http://169.254.169.254/latest/meta-data/public-ipv4"
ipv4_retrieval_endpoint = "http://myip.enix.org/REMOTE_ADDR"
system("curl --silent {} > /tmp/ipv4".format(ipv4_retrieval_endpoint))
ipv4 = open("/tmp/ipv4").read()
# Add a "docker" user with password "training"
system("sudo useradd -d /home/docker -m -s /bin/bash docker")
system("echo docker:training | sudo chpasswd")
# Helper for Docker prompt.
system("""sudo tee /usr/local/bin/docker-prompt <<SQRL
#!/bin/sh
case "\\\$DOCKER_HOST" in
*:3376)
echo swarm
;;
*:2376)
echo \\\$DOCKER_MACHINE_NAME
;;
*:2375)
echo \\\$DOCKER_MACHINE_NAME
;;
*:55555)
echo \\\$DOCKER_MACHINE_NAME
;;
"")
echo local
;;
*)
echo unknown
;;
esac
SQRL""")
system("sudo chmod +x /usr/local/bin/docker-prompt")
# Fancy prompt courtesy of @soulshake.
system("""sudo -u docker tee -a /home/docker/.bashrc <<SQRL
export PS1='\e[1m\e[32m[\h] \e[34m(\\\$(docker-prompt)) \e[35m\u@{}\e[33m \w\e[0m\n$ '
SQRL""".format(ipv4))
# Custom .vimrc
system("""sudo -u docker tee /home/docker/.vimrc <<SQRL
syntax on
set autoindent
set expandtab
set number
set shiftwidth=2
set softtabstop=2
SQRL""")
# add docker user to sudoers and allow password authentication
system("""sudo tee /etc/sudoers.d/docker <<SQRL
docker ALL=(ALL) NOPASSWD:ALL
SQRL""")
system("sudo sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config")
system("sudo service ssh restart")
system("sudo apt-get -q update")
system("sudo apt-get -qy install git jq python-pip")
# increase the size of the conntrack table so we don't blow it up when going crazy with http load testing
system("echo 1000000 | sudo tee /proc/sys/net/nf_conntrack_max")
#######################
### DOCKER INSTALLS ###
#######################
# This will install the latest Docker.
system("curl --silent https://{}/ | grep -v '( set -x; sleep 20 )' | sudo sh".format(ENGINE_VERSION))
# Make sure that the daemon listens on 55555 (for orchestration workshop).
# To test, run: export DOCKER_HOST=tcp://localhost:55555 ; docker ps
# or, run "curl localhost:55555" (it should return 404 not found). If it tells you connection refused, that's a bad sign
system("sudo sed -i 's,-H fd://$,-H fd:// -H tcp://0.0.0.0:55555,' /lib/systemd/system/docker.service")
system("sudo systemctl daemon-reload")
# There seems to be a bug in the systemd scripts; so work around it.
# See https://github.com/docker/docker/issues/18444
# If docker is already running, need to do a restart
system("curl --silent localhost:55555 || sudo systemctl restart docker ") # does this work? if not, next line should cover it
system("sudo systemctl start docker || true")
### Install docker-compose
#system("sudo pip install -U docker-compose=={}".format(COMPOSE_VERSION))
system("sudo curl -sSL -o /usr/local/bin/docker-compose https://github.com/docker/compose/releases/download/{}/docker-compose-{}-{}".format(COMPOSE_VERSION, platform.system(), platform.machine()))
system("sudo chmod +x /usr/local/bin/docker-compose")
### Install docker-machine
system("sudo curl -sSL -o /usr/local/bin/docker-machine https://github.com/docker/machine/releases/download/v{}/docker-machine-{}-{}".format(MACHINE_VERSION, platform.system(), platform.machine()))
system("sudo chmod +x /usr/local/bin/docker-machine")
system("sudo apt-get remove -y --purge dnsmasq-base")
system("sudo apt-get -qy install python-setuptools pssh apache2-utils httping htop unzip mosh")
### Wait for Docker to be up.
### (If we don't do this, Docker will not be responsive during the next step.)
system("while ! sudo -u docker docker version ; do sleep 2; done")
### Install Swarm
system("docker pull swarm:{}".format(SWARM_VERSION))
system("docker tag -f swarm:{} swarm".format(SWARM_VERSION))
### BEGIN CLUSTERING ###
addresses = list(l.strip() for l in sys.stdin)
assert ipv4 in addresses
def makenames(addrs):
return [ "node%s"%(i+1) for i in range(len(addrs)) ]
while addresses:
cluster = addresses[:CLUSTER_SIZE]
addresses = addresses[CLUSTER_SIZE:]
if ipv4 not in cluster:
continue
names = makenames(cluster)
for ipaddr, name in zip(cluster, names):
system("grep ^{} /etc/hosts || echo {} {} | sudo tee -a /etc/hosts"
.format(ipaddr, ipaddr, name))
print(cluster)
mynode = cluster.index(ipv4) + 1
system("echo 'node{}' | sudo -u docker tee /tmp/node".format(mynode))
system("sudo -u docker mkdir -p /home/docker/.ssh")
system("sudo -u docker touch /home/docker/.ssh/authorized_keys")
if ipv4 == cluster[0]:
# If I'm node1 and don't have a private key, generate one (with empty passphrase)
system("sudo -u docker [ -f /home/docker/.ssh/id_rsa ] || sudo -u docker ssh-keygen -t rsa -f /home/docker/.ssh/id_rsa -P ''")
FINISH = time.time()
duration = "Initial deployment took {}s".format(str(FINISH - START)[:5])
system("echo {}".format(duration))
EOF
IPS_FILE=ips.txt
if [ ! -s $IPS_FILE ]; then
echo "ips.txt not found."
exit 1
fi
pssh --timeout 900 --send-input "python /tmp/postprep.py >>/tmp/pp.out 2>>/tmp/pp.err" < $IPS_FILE
# If /home/docker/.ssh/id_rsa doesn't exist, copy it from node1
pssh "sudo -u docker [ -f /home/docker/.ssh/id_rsa ] || ssh -o StrictHostKeyChecking=no node1 sudo -u docker tar -C /home/docker -cvf- .ssh | sudo -u docker tar -C /home/docker -xf-"
# if 'docker@' doesn't appear in /home/docker/.ssh/authorized_keys, copy it there
pssh "grep docker@ /home/docker/.ssh/authorized_keys \
|| cat /home/docker/.ssh/id_rsa.pub \
| sudo -u docker tee -a /home/docker/.ssh/authorized_keys"

22
prepare-vms/scripts/rc Executable file
View File

@@ -0,0 +1,22 @@
# This file can be sourced in order to directly run commands on
# a batch of VMs whose IPs are located in ips.txt of the directory in which
# the command is run.
pssh () {
HOSTFILE="ips.txt"
[ -f $HOSTFILE ] || {
echo "No hostfile found at $HOSTFILE"
return
}
echo "[parallel-ssh] $@"
parallel-ssh -h $HOSTFILE -l ubuntu \
--par 100 \
-O LogLevel=ERROR \
-O UserKnownHostsFile=/dev/null \
-O StrictHostKeyChecking=no \
-O ForwardAgent=yes \
"$@"
}

501
prepare-vms/scripts/trainer-cli Executable file
View File

@@ -0,0 +1,501 @@
#!/bin/bash
# Don't execute this script directly. Use ../trainer instead.
set -e # if we encounter an error, abort
export AWS_DEFAULT_OUTPUT=text
greet() {
hello=$(aws iam get-user --query 'User.UserName')
echo "Greetings, $hello!"
}
deploy_hq(){
TAG=$1
need_tag $TAG
REMOTE_USER=ubuntu
REMOTE_HOST=$(aws_get_instance_ips_by_tag $TAG)
echo "Trying to reach $TAG instances..."
while ! tag_is_reachable $TAG; do
echo -n "."
sleep 2
done
env | grep -i aws > envvars.sh
scp \
-o "UserKnownHostsFile /dev/null" \
-o "StrictHostKeyChecking=no" \
scripts/remote-execution.sh \
envvars.sh \
$REMOTE_USER@$REMOTE_HOST:/tmp/
ssh -A $REMOTE_USER@$REMOTE_HOST "bash /tmp/remote-execution.sh >>/tmp/pre.out 2>>/tmp/pre.err"
ssh -A $REMOTE_USER@$REMOTE_HOST
}
deploy_tag(){
TAG=$1
SETTINGS=$2
need_tag $TAG
link_tag $TAG
count=$(wc -l ips.txt)
# wait until all hosts are reachable before trying to deploy
echo "Trying to reach $TAG instances..."
while ! tag_is_reachable $TAG; do
echo -n "."
sleep 2
done
echo "[[ Deploying tag $TAG ]]"
export SETTINGS
source scripts/postprep.rc
echo "Finished deploying $TAG."
echo "You may want to run one of the following commands:"
echo "trainer pull-images $TAG"
echo "trainer cards $TAG"
}
link_tag() {
TAG=$1
need_tag $TAG
IPS_FILE=tags/$TAG/ips.txt
need_ips_file $IPS_FILE
ln -sf $IPS_FILE ips.txt
}
pull_tag(){
TAG=$1
need_tag $TAG
link_tag $TAG
cards_file=ips.html
if [ ! -s $IPS_FILE ]; then
echo "Nonexistent or empty IPs file $IPS_FILE"
fi
# Pre-pull a bunch of images
pssh --timeout 900 'for I in \
debian:latest \
ubuntu:latest \
fedora:latest \
centos:latest \
postgres \
redis \
training/namer \
nathanleclaire/redisonrails; do
sudo -u docker docker pull $I
done'
echo "Finished pulling images for $TAG"
echo "You may now want to run:"
echo "trainer cards $TAG"
}
wait_until_tag_is_running() {
max_retry=50
TAG=$1
COUNT=$2
i=0
done_count=0
while [[ $done_count -lt $COUNT ]]; do \
let "i += 1"
echo "Waiting: $done_count/$COUNT instances online"
done_count=$(aws ec2 describe-instances \
--filters "Name=instance-state-name,Values=running" \
"Name=tag:Name,Values=$TAG" \
--query "Reservations[*].Instances[*].State.Name" \
| tr "\t" "\n" \
| wc -l)
if [[ $i -gt $max_retry ]]; then
die "Timed out while waiting for instance creation (after $max_retry retries)"
fi
sleep 1
done
}
tag_is_reachable() {
TAG=$1
need_tag $TAG
link_tag $TAG
pssh -t 5 true 2>&1 >/dev/null
}
test_tag(){
ips_file=tags/$TAG/ips.txt
echo "Using random IP in $ips_file to run tests on $TAG"
ip=$(shuf -n 1 $ips_file)
test_vm $ip
echo "Tests complete. You may want to run one of the following commands:"
echo "trainer cards $TAG"
}
test_vm() {
ip=$1
echo "[[ Testing instance with IP $(tput bold)$ip $(tput sgr0) ]]"
user=ubuntu
for cmd in "hostname" \
"whoami" \
"hostname -i" \
"cat /tmp/node" \
"cat /tmp/ipv4" \
"cat /etc/hosts" \
"hostnamectl status" \
"docker version | grep Version -B1" \
"docker-compose version" \
"docker-machine version" \
"docker images" \
"docker ps" \
"which fig" \
"curl --silent localhost:55555" \
"sudo ls -la /mnt/ | grep docker" \
"env" \
"ls -la /home/docker/.ssh"; do
echo "=== $cmd ==="
echo "$cmd" |
ssh -A -q \
-o "UserKnownHostsFile /dev/null" \
-o "StrictHostKeyChecking=no" \
$user@$ip sudo -u docker -i
echo
done
}
make_key_name(){
SHORT_FINGERPRINT=$(ssh-add -l | grep RSA | head -n1 | cut -d " " -f 2 | tr -d : | cut -c 1-8)
echo "${SHORT_FINGERPRINT}-${USER}"
}
sync_keys() {
# make sure ssh-add -l contains "RSA"
ssh-add -l | grep -q RSA ||
die "The output of \`ssh-add -l\` doesn't contain 'RSA'. Start the agent, add your keys?"
AWS_KEY_NAME=$(make_key_name)
echo -n "Syncing keys... "
if ! aws ec2 describe-key-pairs --key-name "$AWS_KEY_NAME" &> /dev/null; then
aws ec2 import-key-pair --key-name $AWS_KEY_NAME \
--public-key-material "$(ssh-add -L \
| grep -i RSA \
| head -n1 \
| cut -d " " -f 1-2)" &> /dev/null
if ! aws ec2 describe-key-pairs --key-name "$AWS_KEY_NAME" &> /dev/null; then
die "Somehow, importing the key didn't work. Make sure that 'ssh-add -l | grep RSA | head -n1' returns an RSA key?"
else
echo "Imported new key $AWS_KEY_NAME."
fi
else
echo "Using existing key $AWS_KEY_NAME."
fi
}
suggest_amis() {
scripts/find-ubuntu-ami.sh -r $AWS_DEFAULT_REGION -a amd64 -v 15.10 -t hvm:ebs -N
}
get_token() {
if [ -z $USER ]; then
export USER=anonymous
fi
date +%Y-%m-%d-%H-%M-$USER
}
get_ami() {
# using find-ubuntu-ami script in `trainer-tools/scripts`:
#AMI=$(./scripts/find-ubuntu-ami.sh -r $AWS_DEFAULT_REGION -a amd64 -v 15.10 -t hvm:ebs -N | grep -v ^REGION | head -1 | awk '{print $7}')
#AMI=$(suggest_amis | grep -v ^REGION | head -1 | awk '{print $7}')
case $AWS_DEFAULT_REGION in
eu-central-1)
AMI=ami-74a4bc18
;;
eu-west-1)
AMI=ami-cda312be
;;
us-west-2)
AMI=ami-495bbd29
;;
us-east-1)
AMI=ami-1711387d
;;
esac
echo $AMI
}
make_cards(){
# Generate cards for a given tag
TAG=$1
SETTINGS_FILE=$2
[[ -z "$SETTINGS_FILE" ]] && {
echo "Please specify the settings file you want to use."
echo "e.g.: settings/orchestration.yaml"
exit 1
}
aws_get_instance_ips_by_tag $TAG > tags/$TAG/ips.txt
# Remove symlinks to old cards
rm -f ips.html ips.pdf
# This will generate two files in the base dir: ips.pdf and ips.html
python scripts/ips-txt-to-html.py $SETTINGS_FILE
for f in ips.html ips.pdf; do
# Remove old versions of cards if they exist
rm -f tags/$TAG/$f
# Move the generated file and replace it with a symlink
mv -f $f tags/$TAG/$f && ln -s tags/$TAG/$f $f
done
echo "Cards created. You may want to run:"
echo "chromium ips.html"
echo "chromium ips.pdf"
}
describe_tag() {
# Display instance details and reachability/status information
TAG=$1
need_tag $TAG
echo "============= Tag: $TAG ============="
aws_display_instances_by_tag $TAG
aws_display_instance_statuses_by_tag $TAG
}
run_cli() {
case "$1" in
ami)
# A wrapper for scripts/find-ubuntu-ami.sh
shift
scripts/find-ubuntu-ami.sh -r $AWS_DEFAULT_REGION $*
echo
echo "Protip:"
echo "trainer ami -a amd64 -v 15.10 -t hvm:ebs -N | grep -v ^REGION | cut -d\" \" -f15"
echo
echo "Suggestions:"
suggest_amis
;;
cards)
TAG=$2
need_tag $TAG
make_cards $TAG $3
;;
deploy)
TAG=$2
need_tag $TAG
if [[ $TAG == *"-hq"* ]]; then
echo "Deploying HQ"
deploy_hq $TAG
else
SETTINGS=$3
if [[ -z "$SETTINGS" ]]; then
echo "Please specify a settings file."
exit 1
fi
if ! [[ -f "$SETTINGS" ]]; then
echo "Settings file $SETTINGS not found."
exit 1
fi
echo "Deploying with settings $SETTINGS."
deploy_tag $TAG $SETTINGS
fi
;;
ids)
TAG=$2
need_tag $TAG
IDS=$(aws_get_instance_ids_by_tag $TAG)
echo "$IDS"
# Just in case we managed to create instances but weren't able to tag them
echo "Lookup by client token $TAG:"
IDS=$(aws_get_instance_ids_by_client_token $TAG)
echo "$IDS"
;;
ips)
TAG=$2
need_tag $TAG
mkdir -p tags/$TAG
aws_get_instance_ips_by_tag $TAG | tee tags/$TAG/ips.txt
link_tag $TAG
;;
list)
# list existing instances in a given batch
# to list batches, see "tags" command
echo "Using region $AWS_DEFAULT_REGION."
TAG=$2
need_tag $TAG
describe_tag $TAG
tag_is_reachable $TAG
echo "You may be interested in running one of the following commands:"
echo "trainer ips $TAG"
echo "trainer deploy $TAG <settings/somefile.yaml>"
;;
opensg)
aws ec2 authorize-security-group-ingress \
--group-name default \
--protocol icmp \
--port -1 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-name default \
--protocol udp \
--port 0-65535 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-name default \
--protocol tcp \
--port 0-65535 \
--cidr 0.0.0.0/0
;;
pull-images)
TAG=$2
need_tag $TAG
pull_tag $TAG
;;
retag)
if [[ -z "$2" ]] || [[ -z "$3" ]]; then
die "Please specify old tag/token, and new tag."
fi
aws_tag_instances $2 $3
;;
shell)
# Get a shell in the container
export PS1="trainer@$AWS_DEFAULT_REGION# "
exec $SHELL
;;
start)
# Create $2 instances
COUNT=$2
if [ -z "$COUNT" ]; then
die "Indicate number of instances to start."
fi
greet # Print our AWS username, to ease the pain of credential-juggling
key_name=$(sync_keys) # Upload our SSH keys to AWS if needed, to be added to each VM's authorized_keys
AMI=$(get_ami) # Retrieve the AWS image ID
TOKEN=$(get_token) # generate a timestamp token for this batch of VMs
if [ ! -z $3 ]; then
# If an extra arg is present, append it to the tag
TOKEN=$TOKEN-$3
fi
echo "-----------------------------------"
echo "Starting $COUNT instances:"
echo " Region: $AWS_DEFAULT_REGION"
echo " Token/tag: $TOKEN"
echo " AMI: $AMI"
AWS_KEY_NAME=$(make_key_name)
result=$(aws ec2 run-instances \
--key-name $AWS_KEY_NAME \
--count $2 \
--instance-type c3.large \
--client-token $TOKEN \
--image-id $AMI)
reservation_id=$(echo "$result" | head -1 | awk '{print $2}' )
echo " Key name: $AWS_KEY_NAME"
echo "Reservation ID: $reservation_id"
echo "-----------------------------------"
# if instance creation succeeded, we should have some IDs
IDS=$(aws_get_instance_ids_by_client_token $TOKEN)
if [ -z "$IDS" ]; then
die "Instance creation failed."
fi
# Tag these new instances with a tag that is the same as the token
TAG=$TOKEN
aws_tag_instances $TOKEN $TAG
wait_until_tag_is_running $TAG $COUNT
echo "[-------------------------------------------------------------------------------------]"
echo " Successfully created $2 instances with tag: $TAG"
echo "[-------------------------------------------------------------------------------------]"
mkdir -p tags/$TAG
IPS=$(aws_get_instance_ips_by_tag $TAG)
echo "$IPS" > tags/$TAG/ips.txt
link_tag $TAG
echo "To deploy or kill these instances, run one of the following:"
echo "trainer deploy $TAG <settings/somefile.yml>"
echo "trainer list $TAG"
;;
status)
greet && echo
max_instances=$(aws ec2 describe-account-attributes \
--attribute-names max-instances \
--query 'AccountAttributes[*][AttributeValues]')
echo "Max instances: $max_instances" && echo
# Print list of AWS EC2 regions, highlighting ours ($AWS_DEFAULT_REGION) in the list
# If our $AWS_DEFAULT_REGION is not valid, the error message will be pretty descriptive:
# Could not connect to the endpoint URL: "https://ec2.foo.amazonaws.com/"
echo "Region:" # $AWS_DEFAULT_REGION."
aws ec2 describe-regions | awk '{print $3}' | grep --color=auto $AWS_DEFAULT_REGION -C50
;;
stop)
TAG=$2
need_tag $TAG
aws_kill_instances_by_tag $TAG
;;
tag)
# add a tag to a batch of VMs
TAG=$2
NEW_TAG_KEY=$3
NEW_TAG_VALUE=$4
need_tag $TAG
need_tag $NEW_TAG_KEY
need_tag $NEW_TAG_VALUE
;;
test)
TAG=$2
need_tag $TAG
test_tag $TAG
;;
*)
echo "
trainer COMMAND [n-instances|tag]
Core commands:
start n Start n instances
list [TAG] If a tag is provided, list its VMs. Otherwise, list tags.
deploy TAG Deploy all instances with a given tag
pull-images TAG Pre-pull docker images. Run only after deploying.
stop TAG Stop and delete instances tagged TAG
Extras:
ips TAG List all IPs of instances with a given tag (updates ips.txt)
ids TAG/TOKEN List all instance IDs with a given tag
shell Get a shell in the trainer container
status TAG Print information about this tag and its VMs
tags List all tags (per-region)
retag TAG/TOKEN TAG Retag instances with a new tag
Beta:
ami Look up Amazon Machine Images
cards Generate cards
opensg Modify AWS security groups
"
;;
esac
}
(
cd $SCRIPT_DIR
source scripts/cli.sh
source scripts/aws.sh
source scripts/rc
source scripts/colors.sh
mkdir -p tags
# TODO: unset empty envvars
run_cli "$@"
)

View File

@@ -0,0 +1,35 @@
# This file is passed by trainer-cli to scripts/ips-txt-to-html.py
workshop_name: Docker fundamentals
workshop_short_name: Docker # appears on VM connection cards
repo: https://github.com/docker/docker-fundamentals
instance_login: docker
instance_password: training
clustersize: 1 # Number of VMs per cluster
pagesize: 15 # Number of cards to print per page
#background_image: https://myapps.developer.ubuntu.com/site_media/appmedia/2014/12/swarm.png
background_image: http://www.yellosoft.us/public/images/docker.png
#background_image: ../media/swarm.png
# To be printed on the cards:
blurb: >
Here is the connection information to your very own
{cluster_or_machine} for this {workshop_name} workshop. You can connect
to {this_or_each} VM with any SSH client.
Your {machine_is_or_machines_are}:
# {url} will be replaced by the script
footer: >
<p>For slides, chat and other useful links, see: </p>
<center>{url}</center>
url: http://container.training/
engine_version: get.docker.com
compose_version: 1.7.0
machine_version: 0.6.0
swarm_version: 1.2.0

View File

@@ -0,0 +1,35 @@
# This file is passed by trainer-cli to scripts/ips-txt-to-html.py
workshop_name: Advanced Docker Orchestration
workshop_short_name: orchestration
repo: https://github.com/jpetazzo/orchestration-workshop
instance_login: docker
instance_password: training
clustersize: 5 # Number of VMs per cluster
pagesize: 12 # Number of cards to print per page
#background_image: https://myapps.developer.ubuntu.com/site_media/appmedia/2014/12/swarm.png
background_image: http://www.yellosoft.us/public/images/docker.png
#background_image: ../media/swarm.png
# To be printed on the cards:
blurb: >
Here is the connection information to your very own
{cluster_or_machine} for this {workshop_name} workshop. You can connect
to {this_or_each} VM with any SSH client.
Your {machine_is_or_machines_are}:
# {url} will be replaced by the script
footer: >
<p>For slides, chat and other useful links, see: </p>
<center>{url}</center>
url: http://container.training/
engine_version: get.docker.com
compose_version: 1.7.0
machine_version: 0.6.0
swarm_version: 1.2.0

View File

@@ -0,0 +1,35 @@
# This file is passed by trainer-cli to scripts/ips-txt-to-html.py
workshop_name: Advanced Docker Orchestration
workshop_short_name: orchestration
repo: https://github.com/jpetazzo/orchestration-workshop
instance_login: docker
instance_password: training
clustersize: 5 # Number of VMs per cluster
pagesize: 12 # Number of cards to print per page
#background_image: https://myapps.developer.ubuntu.com/site_media/appmedia/2014/12/swarm.png
background_image: http://www.yellosoft.us/public/images/docker.png
#background_image: ../media/swarm.png
# To be printed on the cards:
blurb: >
Here is the connection information to your very own
{cluster_or_machine} for this {workshop_name} workshop. You can connect
to {this_or_each} VM with any SSH client.
Your {machine_is_or_machines_are}:
# {url} will be replaced by the script
footer: >
<p>For slides, chat and other useful links, see: </p>
<center>{url}</center>
url: http://container.training/
engine_version: test.docker.com
compose_version: 1.7.0-rc2
machine_version: 0.7.0-rc1
swarm_version: 1.2.0-rc2

81
prepare-vms/trainer Executable file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
TRAINER_IMAGE="soulshake/prepare-vms"
DEPENDENCIES="
aws
ssh
curl
jq
pssh
wkhtmltopdf
man
"
ENVVARS="
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION
SSH_AUTH_SOCK
"
check_envvars() {
STATUS=0
for envvar in $ENVVARS; do
if [ -z "${!envvar}" ]; then
echo "Please set environment variable $envvar."
STATUS=1
unset $envvar
fi
done
return $STATUS
}
check_dependencies() {
STATUS=0
for dependency in $DEPENDENCIES ; do
if ! command -v $dependency >/dev/null; then
echo "Could not find dependency $dependency."
STATUS=1
fi
done
return $STATUS
}
check_ssh_auth_sock() {
if [ -z $SSH_AUTH_SOCK ]; then
echo -n "SSH_AUTH_SOCK envvar not set, so its parent directory can't be "
echo "mounted as a volume in a container."
echo "Try running the command below and trying again:"
echo "eval \$(ssh-agent) && ssh-add"
exit 1
fi
}
check_image() {
docker inspect $TRAINER_IMAGE >/dev/null 2>&1
}
# Get the script's real directory, whether we're being called directly or via a symlink
if [ -L "$0" ]; then
export SCRIPT_DIR=$(dirname $(readlink "$0"))
else
export SCRIPT_DIR=$(dirname "$0")
fi
cd "$SCRIPT_DIR"
check_envvars || exit 1
if check_dependencies; then
scripts/trainer-cli "$@"
elif check_image; then
check_ssh_auth_sock
export SSH_AUTH_DIRNAME=$(dirname $SSH_AUTH_SOCK)
docker-compose -f docker-compose.yml run prepare-vms "$@"
else
echo "Some dependencies are missing, and docker image $TRAINER_IMAGE doesn't exist locally."
echo "Please do one of the following: "
echo "- run \`docker build -t soulshake/prepare-vms .\`"
echo "- run \`docker pull soulshake/prepare-vms\`"
echo "- install missing dependencies"
fi

37
registry/README.md Normal file
View File

@@ -0,0 +1,37 @@
# Docker Registry with Swarm superpowers
To start your registry, just do:
```
docker-compose up -d
```
You can then refer to the registry as `localhost:5000`.
If you are running on Swarm, do the following:
```
docker-compose up -d
docker-compose scale frontend=N
```
... where `N` is the number of nodes in your cluster.
This will make sure that a `frontend` container runs on every node,
so that `localhost:5000` always refers to your registry.
If you scale up your cluster, make sure to re-run `docker-compose scale`
accordingly.
If you supply a too large value for `N`, you will see errors
(since Swarm tries to schedule more frontends than there are
available hosts) but everything will work fine, don't worry.
Note: this will bind port 5000 on the loopoback interface on
all your machines. That port will therefore be unavailable if
you try e.g. `docker run -p 5000:...`.
Note: the registry will only be available from your cluster,
through the loopback interface. If you want to make it available
from outside, remove `127.0.0.1:` from the Compose file.

View File

@@ -0,0 +1,12 @@
version: "2"
services:
backend:
image: registry:2
frontend:
image: jpetazzo/hamba
command: 5000 backend:5000
ports:
- "127.0.0.1:5000:5000"
depends_on:
- backend

View File

@@ -1,6 +1,6 @@
www:
image: nginx
ports:
- "80:80"
- "8080:80"
volumes:
- "./htdocs:/usr/share/nginx/html"

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

BIN
www/htdocs/dockercoins.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

File diff suppressed because it is too large Load Diff

BIN
www/htdocs/kibana.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB