Compare commits

..

120 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
Jérôme Petazzoni
076d471a1d Add LICENSE (closes #5) 2015-12-26 20:22:46 +01:00
Jerome Petazzoni
ee2f8ef02e Fixes after LISA 2015-11-29 14:13:52 -08:00
Jerome Petazzoni
bc40a77b2b Update deployment script for Docker Machine 2015-11-23 08:44:13 -08:00
Jerome Petazzoni
f532c99c68 Merge branch 'master' of github.com:jpetazzo/orchestration-workshop 2015-11-10 04:01:00 -08:00
Jerome Petazzoni
9cb8e60e26 Changes for LISA 2015-11-10 04:00:54 -08:00
Jérôme Petazzoni
49d25f19da Merge pull request #4 from clkao/master
Stop docker from complaining about ambiguous volume mapping
2015-10-15 08:41:46 -07:00
Chia-liang Kao
1c170bedb7 Stop docker from complaining about ambiguous volume mapping 2015-10-15 13:31:52 +08:00
Jerome Petazzoni
33d29248bb Allow to use COMPOSE_FILE env var in scripts 2015-10-05 08:50:48 -07:00
Jerome Petazzoni
ecc5a3eafe Fix permissions 2015-10-04 20:33:17 -07:00
Jerome Petazzoni
65c96296cb Add YAML fixup helper 2015-10-04 19:50:15 -07:00
Jerome Petazzoni
49c17df2fd Change YAML output to work around go-gypsy 2015-10-04 13:36:42 -07:00
Jerome Petazzoni
10b3a9c4e0 Style labels 2015-10-03 16:13:42 -07:00
Jerome Petazzoni
eb864d6719 Fix typos and add some extra content 2015-09-28 11:01:07 -07:00
Jerome Petazzoni
b2fc2827f2 Big update for LISA 2015-09-27 22:10:32 -07:00
Jerome Petazzoni
8e5af7b964 Add slides URL to printed cards 2015-09-27 12:58:13 -07:00
94 changed files with 7617 additions and 1356 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

13
LICENSE Normal file
View File

@@ -0,0 +1,13 @@
Copyright 2015 Jérôme Petazzoni
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

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,15 +1,12 @@
#!/usr/bin/env python
from common import parallel_run
import os
import subprocess
import sys
import yaml
stack = yaml.load(open(sys.argv[1]))
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),
@@ -18,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'):
@@ -39,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),
@@ -48,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
@@ -58,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,22 +1,21 @@
#!/usr/bin/env python
from common import ComposeFile, parallel_run
import os
import subprocess
import sys
import yaml
stack = yaml.load(open(sys.argv[1]))
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),
@@ -25,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:
@@ -33,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),
@@ -57,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)

16
bin/fixup-yaml.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
# Some tools will choke on the YAML files generated by PyYAML;
# in particular on a section like this one:
#
# service:
# ports:
# - 8000:5000
#
# This script adds two spaces in front of the dash in those files.
# Warning: it is a hack, and probably won't work on some YAML files.
[ -f "$COMPOSE_FILE" ] || {
echo "Cannot find COMPOSE_FILE"
exit 1
}
sed -i 's/^ -/ -/' $COMPOSE_FILE

View File

@@ -1,15 +1,9 @@
#!/usr/bin/env python
import sys
from common import ComposeFile
import yaml
# You can specify 1 or 2 parameters:
# - 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
input_file = sys.argv[1]
output_file = sys.argv[-1]
stack = yaml.load(open(input_file))
config = ComposeFile()
# The ambassadors need to know the service port to use.
# Those ports must be declared here.
@@ -21,7 +15,7 @@ def generate_local_addr():
yield "127.127.0.{}".format(last_byte)
last_byte += 1
for service_name, service in stack.items():
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:
@@ -41,5 +35,4 @@ for service_name, service in stack.items():
if service_name in ports:
service["ports"] = [ ports[service_name] ]
yaml.safe_dump(stack, open(output_file, "w"))
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,68 +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)
print("Wrote new 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>
@@ -25,7 +28,6 @@ var points = []
var graph = null;
function refresh () {
var content = $("#content");
$.ajax({ url: "json" }).done(function (data) {
series.push(data);
while (series.length < 250) {
@@ -39,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",
@@ -57,7 +68,7 @@ function refresh () {
series: [
{ name: "Coins",
color: "steelblue",
data: points
data: points
}
]
});
@@ -70,12 +81,17 @@ function refresh () {
yAxis.render();
} else {
graph.update();
$("text").css({
"font-size": "15px",
"font-weight": "normal",
"opacity": 0.5,
});
}
});
}
$(function () {
setInterval(refresh, 2000);
setInterval(refresh, 1000);
});
</script>
</head>
@@ -84,7 +100,12 @@ $(function () {
<h1>DockerCoin Miner WebUI</h1>
<div id="graph"></div>
<div id="content">Current speed: N/A</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

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,74 +0,0 @@
#!/usr/bin/env python
SETTINGS_BASIC = dict(
clustersize=1,
pagesize=12,
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,
)
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 your 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) ],
)
SETTINGS = SETTINGS_ADVANCED
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: 25%;
padding: 4%;
font-size: x-small;
background-image: url("docker-nb.svg");
background-size: 15%;
background-position-x: 50%;
background-repeat: no-repeat;
}
.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("</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,42 +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")
os.system("sudo easy_install pip")
os.system("sudo pip install docker-compose==1.4.1")
os.system("docker pull swarm:0.4.0")
os.system("docker tag -f swarm:0.4.0 swarm")
os.system("sudo curl -L https://github.com/docker/machine/releases/download/v0.4.1/docker-machine_linux-amd64 -o /usr/local/bin/docker-machine")
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"
- "./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

BIN
www/htdocs/delay-hasher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
www/htdocs/delay-rng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 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