-
Notifications
You must be signed in to change notification settings - Fork 0
/
dmswarm.py
executable file
·319 lines (247 loc) · 10.8 KB
/
dmswarm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/usr/bin/env python3
## TO DO
## - add support for additional cloud provider drivers
## - add option to scp copy directories or files to the manager such
## as aws credentials
"""
Quickly launch a docker swarm on AWS.
Important
docker-machine creates a default security group that does not have all of
the require ports open for docker swarm. An aws security group with the
following configuration was tested and worked.
Inbound
Type Protocol Port Range Source
Custom TCP Rule TCP 2377 0.0.0.0/0
SSH TCP 22 0.0.0.0/0
Custom UDP Rule UDP 7946 0.0.0.0/0
Custom TCP Rule TCP 2376 0.0.0.0/0
Custom TCP Rule TCP 7946 0.0.0.0/0
Custom UDP Rule UDP 4789 0.0.0.0/0
Outbound
Type Protocol Port Range Source
All traffic All All 0.0.0.0/0
Currently, this program does not have the ability to change an existing
security group. One can specify a security group with above configuration
using the '--amazonec2-security-group AWS_SECURITY_GROUP' arg or create
(or modify) a security group named 'docker-machine'. docker-machine will
create a security group named 'docker-machine' or use an existing group
named 'docker-machine' if the '--amazonec2-security-group AWS_SECURITY_GROUP'
arg is omitted.
usage:
dmwswarm.py create [options] <swarm-name>
options:
--driver DRIVER [default: amazonec2]
--amazonec2-access-key AWS_ACCESS_KEY_ID
--amazonec2-secret-key AWS_SECRET_ACCESS_KEY
--amazonec2-session-token AWS_SESSION_TOKEN
--amazonec2-ami AWS_AMI
--amazonec2-region AWS_DEFAULT_REGION
--amazonec2-vpc-id AWS_VPC_ID
--amazonec2-zone AWS_ZONE
--amazonec2-subnet-id AWS_SUBNET_ID
--amazonec2-security-group AWS_SECURITY_GROUP
--amazonec2-tags AWS_TAGS
--amazonec2-instance-type AWS_INSTANCE_TYPE
--amazonec2-device-name AWS_DEVICE_NAME
--amazonec2-root-size AWS_ROOT_SIZE
--amazonec2-volume-type AWS_VOLUME_TYPE
--amazonec2-iam-instance-profile AWS_INSTANCE_PROFILE
--amazonec2-ssh-user AWS_SSH_USER
--amazonec2-request-spot-instance
--amazonec2-spot-price
--amazonec2-use-private-address
--amazonec2-private-address-only
--amazonec2-monitoring
--amazonec2-use-ebs-optimized-instance
--amazonec2-ssh-keypath AWS_SSH_KEYPATH
--amazonec2-retries
--log-level LOG Set the log level to DEBUG, INFO, WARNING,
CRITICAL, or ERROR [default: DEBUG].
--nodes NODES Number of worker nodes.
--userconfig USERCONFIG Path to user config file with above options
in yml format.
"""
__author__ = 'Sean Landry'
__date__ = '24july2017'
__version__ = '0.1.0'
import subprocess
from docopt import docopt
import json
import yaml
import os
import logging
import sys
class DMSubprocess():
"""
Serves as the base class for all docker-machine subprocess calls.
"""
def __init__(self, log_level = "INFO", action = None, optDict = None,
machine_name = None, sshArgs = None):
"""
Construct a DMSubprocess object.
Args:
log_level (str): set the logging level to DEBUG, INFO, WARNING,
CRITICAL, or ERROR.
optDict (dict): a dictionary of options to be passed to the
docker-machine subprocess call.
action (str): the main docker-machine arg, e.g. create
"""
self.logger = self.init_logger(log_level)
self.options = self.parse_opts(optDict)
self.machine_name = machine_name
self.action = action
self.sshArgs = sshArgs
self.stdout = ''
self.stderr = ''
self.argLst = self.dm_action(action)
def init_logger(self, log_level = "WARNING"):
"""
Initialize the class logger.
Args:
log_level (str): set logging level to DEBUG, INFO, WARNING,
CRITICAL, or ERROR.
Returns:
class_logger (logging obj): python logging object with class name.
"""
numeric_level = getattr(logging, log_level.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % log_level)
class_logger = logging.getLogger(self.__class__.__name__)
class_logger.setLevel(numeric_level)
class_logger.debug('logger initialized')
return class_logger
def dm_action(self, action):
all_actions = {
'ssh':["docker-machine", action, self.machine_name, self.sshArgs],
'create':["docker-machine"] + [action] + self.options + [self.machine_name],
'inspect':["docker-machine", action, self.machine_name]
}
return all_actions[action]
def parse_opts(self, opts = None):
optlst = []
if opts['--userconfig']:
userconfig = os.path.abspath(opts['--userconfig'])
print(userconfig)
with open(userconfig, 'r') as user:
config = yaml.load(user)
for k,v in config.items():
if v != 'None' and (opts['--driver'] in k or 'driver' in k):
optlst.append(k)
optlst.append(v)
for k,v in opts.items():
if k not in optlst and '--' in k and v and (opts['--driver'] in k or 'driver' in k):
optlst.append(k)
optlst.append(v)
self.logger.debug("parse_opts --> " + str(optlst))
return optlst
def private_ip(self, stdout):
result = json.loads(stdout.decode())
return result['Driver']['PrivateIPAddress']
def parse_token(self, stdout):
for line in stdout.decode().split('\n'):
if 'docker swarm join --token' in line:
return line.strip()
def log_streams(self, stdout = False, stderr = True):
self.logger.info("finished subprocess, stream logs:")
if stdout and self.stdout:
self.logger.info("stdout: \n" + self.stdout.decode())
if stderr and self.stderr:
self.logger.info("stderr: \n" + self.stderr.decode())
def run(self, proc_in = None):
"""
Run the subprocess.
Args:
proc_in (boolean): Set to True of subprocess call will be
expecting the stdout PIPE of another
subprocess call.
"""
self.logger.info("running subprocess")
self.logger.info("using args --> " + str(self.argLst))
if proc_in:
proc = subprocess.Popen(self.argLst, stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
stdin = subprocess.PIPE)
self.stdout, self.stderr = proc.communicate(input = proc_in)
else:
proc = subprocess.Popen(self.argLst, stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
self.stdout,self.stderr = proc.communicate()
def main():
"""
Command line arguments.
"""
options = docopt(__doc__)
log_level = options['--log-level']
numeric_level = getattr(logging, log_level.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % log_level)
## setup module logger
moduleLogger = logging.getLogger()
moduleLogger.setLevel(numeric_level)
# create console handler
console = logging.StreamHandler()
console.setLevel(numeric_level)
# create formatter and add it to the handler
console_formatter = logging.Formatter('%(asctime)s %(filename)s %(name)s.%(funcName)s() - %(levelname)s:%(message)s')
console.setFormatter(console_formatter)
moduleLogger.addHandler(console)
if options['create']:
## launch the swarm nodes
if options['--nodes']:
for n in range(int(options['--nodes'])):
if n == 0:
moduleLogger.info("creating " + options['<swarm-name>'] + "-manager")
node = DMSubprocess(
action = 'create',
optDict = options,
machine_name = options['<swarm-name>'] +"-manager",
log_level = log_level)
else:
moduleLogger.info("creating " + options['<swarm-name>'] + "-worker-"
+ str(n))
node = DMSubprocess(
action = 'create',
optDict = options,
machine_name = options['<swarm-name>'] +"-worker-" + str(n),
log_level = log_level)
node.run()
node.log_streams(stdout = True)
## inspect manager node to obtain ip
moduleLogger.info('finding manager ip')
manager = DMSubprocess(
action = 'inspect',
optDict = options,
log_level = log_level,
machine_name = options['<swarm-name>'] + "-manager")
manager.run()
manager.log_streams(stdout = True)
ip = manager.private_ip(manager.stdout)
moduleLogger.debug("manager private ip " + ip)
## initialize manager node
moduleLogger.info('initializing swarm manager')
manager = DMSubprocess(
action = 'ssh',
optDict = options,
log_level = log_level,
machine_name = options['<swarm-name>'] + "-manager",
sshArgs = "sudo docker swarm init --advertise-addr " + ip)
manager.run()
manager.log_streams(stdout = True)
token = manager.parse_token(manager.stdout)
moduleLogger.debug(token)
## add workers to the swarm
for n in range(int(options['--nodes']) - 1):
moduleLogger.info(options['<swarm-name>'] + "-worker-" + str(n+1)
+ " is attempting to join the swarm")
moduleLogger.debug(token)
worker = DMSubprocess(
action = 'ssh',
optDict = options,
log_level = log_level,
machine_name = options['<swarm-name>'] + "-worker-"
+ str(n+1),
sshArgs = "sudo " + token)
worker.run()
worker.log_streams(stdout = True)
if __name__== "__main__":
main()