cloud-mu 2.0.4 → 2.1.0beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +6 -0
- data/ansible/roles/geerlingguy.firewall/LICENSE +20 -0
- data/ansible/roles/geerlingguy.firewall/README.md +93 -0
- data/ansible/roles/geerlingguy.firewall/defaults/main.yml +19 -0
- data/ansible/roles/geerlingguy.firewall/handlers/main.yml +3 -0
- data/ansible/roles/geerlingguy.firewall/meta/main.yml +26 -0
- data/ansible/roles/geerlingguy.firewall/molecule/default/molecule.yml +40 -0
- data/ansible/roles/geerlingguy.firewall/molecule/default/playbook.yml +17 -0
- data/ansible/roles/geerlingguy.firewall/molecule/default/tests/test_default.py +14 -0
- data/ansible/roles/geerlingguy.firewall/molecule/default/yaml-lint.yml +6 -0
- data/ansible/roles/geerlingguy.firewall/tasks/disable-other-firewalls.yml +66 -0
- data/ansible/roles/geerlingguy.firewall/tasks/main.yml +44 -0
- data/ansible/roles/geerlingguy.firewall/templates/firewall.bash.j2 +136 -0
- data/ansible/roles/geerlingguy.firewall/templates/firewall.init.j2 +52 -0
- data/ansible/roles/geerlingguy.firewall/templates/firewall.unit.j2 +12 -0
- data/bin/mu-ansible-secret +114 -0
- data/bin/mu-aws-setup +74 -21
- data/bin/mu-node-manage +22 -12
- data/bin/mu-self-update +11 -4
- data/cloud-mu.gemspec +3 -3
- data/cookbooks/firewall/metadata.json +1 -1
- data/cookbooks/firewall/recipes/default.rb +4 -0
- data/cookbooks/mu-master/recipes/default.rb +0 -3
- data/cookbooks/mu-master/recipes/init.rb +15 -9
- data/cookbooks/mu-master/templates/default/mu.rc.erb +1 -1
- data/cookbooks/mu-master/templates/default/web_app.conf.erb +0 -4
- data/cookbooks/mu-php54/metadata.rb +2 -2
- data/cookbooks/mu-php54/recipes/default.rb +1 -3
- data/cookbooks/mu-tools/recipes/eks.rb +25 -2
- data/cookbooks/mu-tools/recipes/nrpe.rb +6 -1
- data/cookbooks/mu-tools/recipes/set_mu_hostname.rb +8 -0
- data/cookbooks/mu-tools/templates/default/etc_hosts.erb +1 -1
- data/cookbooks/mu-tools/templates/default/kubeconfig.erb +2 -2
- data/cookbooks/mu-tools/templates/default/kubelet-config.json.erb +35 -0
- data/extras/clean-stock-amis +10 -4
- data/extras/list-stock-amis +64 -0
- data/extras/python_rpm/build.sh +21 -0
- data/extras/python_rpm/muthon.spec +68 -0
- data/install/README.md +5 -2
- data/install/user-dot-murc.erb +1 -1
- data/modules/mu.rb +52 -8
- data/modules/mu/clouds/aws.rb +1 -1
- data/modules/mu/clouds/aws/container_cluster.rb +1071 -47
- data/modules/mu/clouds/aws/firewall_rule.rb +45 -19
- data/modules/mu/clouds/aws/log.rb +3 -2
- data/modules/mu/clouds/aws/role.rb +18 -2
- data/modules/mu/clouds/aws/server.rb +11 -5
- data/modules/mu/clouds/aws/server_pool.rb +20 -24
- data/modules/mu/clouds/aws/userdata/linux.erb +1 -1
- data/modules/mu/clouds/aws/vpc.rb +9 -0
- data/modules/mu/clouds/google/server.rb +2 -0
- data/modules/mu/config.rb +3 -3
- data/modules/mu/config/container_cluster.rb +1 -1
- data/modules/mu/config/firewall_rule.rb +4 -0
- data/modules/mu/config/role.rb +29 -0
- data/modules/mu/config/server.rb +9 -4
- data/modules/mu/groomer.rb +14 -3
- data/modules/mu/groomers/ansible.rb +553 -0
- data/modules/mu/groomers/chef.rb +0 -5
- data/modules/mu/mommacat.rb +18 -3
- data/modules/scratchpad.erb +1 -1
- data/requirements.txt +5 -0
- metadata +39 -16
data/install/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Cloudamatic Mu Master Installation
|
2
|
-
|
2
|
+
There are two paths to creating a Mu Master.
|
3
3
|
|
4
|
-
|
4
|
+
- **Typical Installation**: The simplest and recommended path is to use our CloudFormation script to configure an appropriate Virtual Private Cloud and master with all features enabled, including both a command line and Jenkins GUI user interface.
|
5
|
+
- **Custom Installation:** If you prefer, you can also create your own VPC and manually provision a Mu Master. This gives you more control over the shape of the master VPC and individual settings
|
6
|
+
|
7
|
+
For detailed instructions on both installation techniques see [our Wiki Installation page](https://github.com/cloudamatic/mu/wiki/Install-Home)
|
5
8
|
For mu master usage instructions see [our Wiki usage page](https://github.com/cloudamatic/mu/wiki/Usage)
|
data/install/user-dot-murc.erb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
export MU_DATADIR="<%= home %>/.mu/var"
|
2
2
|
export MU_CHEF_CACHE="<%= home %>/.chef"
|
3
|
-
export PATH="<%= installdir %>/bin:/usr/local/ruby-current/bin:${PATH}:/opt/opscode/embedded/bin"
|
3
|
+
export PATH="<%= installdir %>/bin:/usr/local/ruby-current/bin:/usr/local/python-current/bin:${PATH}:/opt/opscode/embedded/bin"
|
4
4
|
|
5
5
|
if [ ! -f "<%= home %>/.first_chef_upload" -a "`tty`" != "not a tty" ];then
|
6
6
|
touch "<%= home %>/.first_chef_upload"
|
data/modules/mu.rb
CHANGED
@@ -214,11 +214,12 @@ module MU
|
|
214
214
|
@myDataDir = File.expand_path(ENV['MU_DATADIR']) if ENV.has_key?("MU_DATADIR")
|
215
215
|
@myDataDir = @@mainDataDir if @myDataDir.nil?
|
216
216
|
# Mu's deployment metadata directory.
|
217
|
-
def self.dataDir
|
218
|
-
if
|
217
|
+
def self.dataDir(for_user = MU.mu_user)
|
218
|
+
if for_user.nil? or for_user.empty? or for_user == "mu" or for_user == "root"
|
219
219
|
return @myDataDir
|
220
220
|
else
|
221
|
-
|
221
|
+
for_user ||= MU.mu_user
|
222
|
+
basepath = Etc.getpwnam(for_user).dir+"/.mu"
|
222
223
|
Dir.mkdir(basepath, 0755) if !Dir.exists?(basepath)
|
223
224
|
Dir.mkdir(basepath+"/var", 0755) if !Dir.exists?(basepath+"/var")
|
224
225
|
return basepath+"/var"
|
@@ -426,7 +427,7 @@ module MU
|
|
426
427
|
# XXX these guys to move into mu/groomer
|
427
428
|
# List of known/supported grooming agents (configuration management tools)
|
428
429
|
def self.supportedGroomers
|
429
|
-
["Chef"]
|
430
|
+
["Chef", "Ansible"]
|
430
431
|
end
|
431
432
|
|
432
433
|
MU.supportedGroomers.each { |groomer|
|
@@ -626,10 +627,38 @@ module MU
|
|
626
627
|
true
|
627
628
|
end
|
628
629
|
|
630
|
+
# Given a hash, or an array that might contain a hash, change all of the keys
|
631
|
+
# to symbols. Useful for formatting option parameters to some APIs.
|
632
|
+
def self.strToSym(obj)
|
633
|
+
if obj.is_a?(Hash)
|
634
|
+
newhash = {}
|
635
|
+
obj.each_pair { |k, v|
|
636
|
+
if v.is_a?(Hash) or v.is_a?(Array)
|
637
|
+
newhash[k.to_sym] = MU.strToSym(v)
|
638
|
+
else
|
639
|
+
newhash[k.to_sym] = v
|
640
|
+
end
|
641
|
+
}
|
642
|
+
newhash
|
643
|
+
elsif obj.is_a?(Array)
|
644
|
+
newarr = []
|
645
|
+
obj.each { |v|
|
646
|
+
if v.is_a?(Hash) or v.is_a?(Array)
|
647
|
+
newarr << MU.strToSym(v)
|
648
|
+
else
|
649
|
+
newarr << v
|
650
|
+
end
|
651
|
+
}
|
652
|
+
newarr
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
|
629
657
|
# Recursively turn a Ruby OpenStruct into a Hash
|
630
658
|
# @param struct [OpenStruct]
|
659
|
+
# @param stringify_keys [Boolean]
|
631
660
|
# @return [Hash]
|
632
|
-
def self.structToHash(struct)
|
661
|
+
def self.structToHash(struct, stringify_keys: false)
|
633
662
|
google_struct = false
|
634
663
|
begin
|
635
664
|
google_struct = struct.class.ancestors.include?(::Google::Apis::Core::Hashable)
|
@@ -646,18 +675,33 @@ module MU
|
|
646
675
|
google_struct or aws_struct
|
647
676
|
|
648
677
|
hash = struct.to_h
|
678
|
+
if stringify_keys
|
679
|
+
newhash = {}
|
680
|
+
hash.each_pair { |k, v|
|
681
|
+
newhash[k.to_s] = v
|
682
|
+
}
|
683
|
+
hash = newhash
|
684
|
+
end
|
685
|
+
|
649
686
|
hash.each_pair { |key, value|
|
650
|
-
hash[key] = self.structToHash(value)
|
687
|
+
hash[key] = self.structToHash(value, stringify_keys: stringify_keys)
|
651
688
|
}
|
652
689
|
return hash
|
653
690
|
elsif struct.is_a?(Hash)
|
691
|
+
if stringify_keys
|
692
|
+
newhash = {}
|
693
|
+
struct.each_pair { |k, v|
|
694
|
+
newhash[k.to_s] = v
|
695
|
+
}
|
696
|
+
struct = newhash
|
697
|
+
end
|
654
698
|
struct.each_pair { |key, value|
|
655
|
-
struct[key] = self.structToHash(value)
|
699
|
+
struct[key] = self.structToHash(value, stringify_keys: stringify_keys)
|
656
700
|
}
|
657
701
|
return struct
|
658
702
|
elsif struct.is_a?(Array)
|
659
703
|
struct.map! { |elt|
|
660
|
-
self.structToHash(elt)
|
704
|
+
self.structToHash(elt, stringify_keys: stringify_keys)
|
661
705
|
}
|
662
706
|
else
|
663
707
|
return struct
|
data/modules/mu/clouds/aws.rb
CHANGED
@@ -1239,7 +1239,7 @@ module MU
|
|
1239
1239
|
retval = @api.method(method_sym).call
|
1240
1240
|
end
|
1241
1241
|
return retval
|
1242
|
-
rescue Aws::EC2::Errors::InternalError, Aws::EC2::Errors::RequestLimitExceeded, Aws::EC2::Errors::Unavailable, Aws::Route53::Errors::Throttling, Aws::ElasticLoadBalancing::Errors::HttpFailureException, Aws::EC2::Errors::Http503Error, Aws::AutoScaling::Errors::Http503Error, Aws::AutoScaling::Errors::InternalFailure, Aws::AutoScaling::Errors::ServiceUnavailable, Aws::Route53::Errors::ServiceUnavailable, Aws::ElasticLoadBalancing::Errors::Throttling, Aws::RDS::Errors::ClientUnavailable, Aws::Waiters::Errors::UnexpectedError, Aws::ElasticLoadBalancing::Errors::ServiceUnavailable, Aws::ElasticLoadBalancingV2::Errors::Throttling, Seahorse::Client::NetworkingError, Aws::IAM::Errors::Throttling, Aws::EFS::Errors::ThrottlingException, Aws::Pricing::Errors::ThrottlingException, Aws::APIGateway::Errors::TooManyRequestsException => e
|
1242
|
+
rescue Aws::EC2::Errors::InternalError, Aws::EC2::Errors::RequestLimitExceeded, Aws::EC2::Errors::Unavailable, Aws::Route53::Errors::Throttling, Aws::ElasticLoadBalancing::Errors::HttpFailureException, Aws::EC2::Errors::Http503Error, Aws::AutoScaling::Errors::Http503Error, Aws::AutoScaling::Errors::InternalFailure, Aws::AutoScaling::Errors::ServiceUnavailable, Aws::Route53::Errors::ServiceUnavailable, Aws::ElasticLoadBalancing::Errors::Throttling, Aws::RDS::Errors::ClientUnavailable, Aws::Waiters::Errors::UnexpectedError, Aws::ElasticLoadBalancing::Errors::ServiceUnavailable, Aws::ElasticLoadBalancingV2::Errors::Throttling, Seahorse::Client::NetworkingError, Aws::IAM::Errors::Throttling, Aws::EFS::Errors::ThrottlingException, Aws::Pricing::Errors::ThrottlingException, Aws::APIGateway::Errors::TooManyRequestsException, Aws::ECS::Errors::ThrottlingException => e
|
1243
1243
|
if e.class.name == "Seahorse::Client::NetworkingError" and e.message.match(/Name or service not known/)
|
1244
1244
|
MU.log e.inspect, MU::ERR
|
1245
1245
|
raise e
|
@@ -61,16 +61,28 @@ module MU
|
|
61
61
|
|
62
62
|
resp = nil
|
63
63
|
begin
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
subnet_ids: subnet_ids
|
64
|
+
params = {
|
65
|
+
:name => @mu_name,
|
66
|
+
:version => @config['kubernetes']['version'],
|
67
|
+
:role_arn => role_arn,
|
68
|
+
:resources_vpc_config => {
|
69
|
+
:security_group_ids => security_groups,
|
70
|
+
:subnet_ids => subnet_ids
|
72
71
|
}
|
73
|
-
|
72
|
+
}
|
73
|
+
if @config['logging'] and @config['logging'].size > 0
|
74
|
+
params[:logging] = {
|
75
|
+
:cluster_logging => [
|
76
|
+
{
|
77
|
+
:types => @config['logging'],
|
78
|
+
:enabled => true
|
79
|
+
}
|
80
|
+
]
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
MU.log "Creating EKS cluster #{@mu_name}", details: params
|
85
|
+
resp = MU::Cloud::AWS.eks(region: @config['region'], credentials: @config['credentials']).create_cluster(params)
|
74
86
|
rescue Aws::EKS::Errors::UnsupportedAvailabilityZoneException => e
|
75
87
|
# this isn't the dumbest thing we've ever done, but it's up there
|
76
88
|
if e.message.match(/because (#{Regexp.quote(@config['region'])}[a-z]), the targeted availability zone, does not currently have sufficient capacity/)
|
@@ -130,6 +142,7 @@ module MU
|
|
130
142
|
MU::Cloud::AWS.ecs(region: @config['region'], credentials: @config['credentials']).create_cluster(
|
131
143
|
cluster_name: @mu_name
|
132
144
|
)
|
145
|
+
|
133
146
|
end
|
134
147
|
@cloud_id = @mu_name
|
135
148
|
end
|
@@ -181,14 +194,14 @@ module MU
|
|
181
194
|
}
|
182
195
|
|
183
196
|
authmap_cmd = %Q{/opt/mu/bin/kubectl --kubeconfig "#{kube_conf}" apply -f "#{eks_auth}"}
|
184
|
-
MU.log "Configuring Kubernetes <=> IAM mapping for worker nodes", details: authmap_cmd
|
197
|
+
MU.log "Configuring Kubernetes <=> IAM mapping for worker nodes", MU::NOTICE, details: authmap_cmd
|
185
198
|
# maybe guard this mess
|
186
199
|
%x{#{authmap_cmd}}
|
187
200
|
|
188
201
|
# and this one
|
189
202
|
admin_user_cmd = %Q{/opt/mu/bin/kubectl --kubeconfig "#{kube_conf}" apply -f "#{MU.myRoot}/extras/admin-user.yaml"}
|
190
203
|
admin_role_cmd = %Q{/opt/mu/bin/kubectl --kubeconfig "#{kube_conf}" apply -f "#{MU.myRoot}/extras/admin-role-binding.yaml"}
|
191
|
-
MU.log "Configuring Kubernetes admin-user and role", details: admin_user_cmd+"\n"+admin_role_cmd
|
204
|
+
MU.log "Configuring Kubernetes admin-user and role", MU::NOTICE, details: admin_user_cmd+"\n"+admin_role_cmd
|
192
205
|
%x{#{admin_user_cmd}}
|
193
206
|
%x{#{admin_role_cmd}}
|
194
207
|
|
@@ -213,8 +226,8 @@ module MU
|
|
213
226
|
}
|
214
227
|
end
|
215
228
|
|
216
|
-
MU.log %Q{How to interact with your Kubernetes cluster\nkubectl --kubeconfig "#{kube_conf}" get all\nkubectl --kubeconfig "#{kube_conf}" create -f some_k8s_deploy.yml}, MU::SUMMARY
|
217
|
-
|
229
|
+
MU.log %Q{How to interact with your Kubernetes cluster\nkubectl --kubeconfig "#{kube_conf}" get all\nkubectl --kubeconfig "#{kube_conf}" create -f some_k8s_deploy.yml\nkubectl --kubeconfig "#{kube_conf}" get nodes}, MU::SUMMARY
|
230
|
+
elsif @config['flavor'] != "Fargate"
|
218
231
|
resp = MU::Cloud::AWS.ecs(region: @config['region'], credentials: @config['credentials']).list_container_instances({
|
219
232
|
cluster: @mu_name
|
220
233
|
})
|
@@ -281,7 +294,323 @@ module MU
|
|
281
294
|
}
|
282
295
|
}
|
283
296
|
end
|
284
|
-
|
297
|
+
|
298
|
+
if @config['flavor'] != "EKS" and @config['containers']
|
299
|
+
|
300
|
+
security_groups = []
|
301
|
+
if @dependencies.has_key?("firewall_rule")
|
302
|
+
@dependencies['firewall_rule'].values.each { |sg|
|
303
|
+
security_groups << sg.cloud_id
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
tasks_registered = 0
|
308
|
+
retries = 0
|
309
|
+
svc_resp = begin
|
310
|
+
MU::Cloud::AWS.ecs(region: @config['region'], credentials: @config['credentials']).list_services(
|
311
|
+
cluster: arn
|
312
|
+
)
|
313
|
+
rescue Aws::ECS::Errors::ClusterNotFoundException => e
|
314
|
+
if retries < 10
|
315
|
+
sleep 5
|
316
|
+
retries += 1
|
317
|
+
retry
|
318
|
+
else
|
319
|
+
raise e
|
320
|
+
end
|
321
|
+
end
|
322
|
+
existing_svcs = svc_resp.service_arns.map { |s|
|
323
|
+
s.gsub(/.*?:service\/(.*)/, '\1')
|
324
|
+
}
|
325
|
+
|
326
|
+
# Reorganize things so that we have services and task definitions
|
327
|
+
# mapped to the set of containers they must contain
|
328
|
+
tasks = {}
|
329
|
+
created_generic_loggroup = false
|
330
|
+
|
331
|
+
@config['containers'].each { |c|
|
332
|
+
service_name = c['service'] ? @mu_name+"-"+c['service'].upcase : @mu_name+"-"+c['name'].upcase
|
333
|
+
tasks[service_name] ||= []
|
334
|
+
tasks[service_name] << c
|
335
|
+
}
|
336
|
+
|
337
|
+
tasks.each_pair { |service_name, containers|
|
338
|
+
launch_type = @config['flavor'] == "ECS" ? "EC2" : "FARGATE"
|
339
|
+
cpu_total = 0
|
340
|
+
mem_total = 0
|
341
|
+
role_arn = nil
|
342
|
+
|
343
|
+
container_definitions = containers.map { |c|
|
344
|
+
cpu_total += c['cpu']
|
345
|
+
mem_total += c['memory']
|
346
|
+
|
347
|
+
if c["role"] and !role_arn
|
348
|
+
found = MU::MommaCat.findStray(
|
349
|
+
@config['cloud'],
|
350
|
+
"role",
|
351
|
+
cloud_id: c["role"]["id"],
|
352
|
+
name: c["role"]["name"],
|
353
|
+
deploy_id: c["role"]["deploy_id"] || @deploy.deploy_id,
|
354
|
+
dummy_ok: false
|
355
|
+
)
|
356
|
+
if found
|
357
|
+
found = found.first
|
358
|
+
if found and found.cloudobj
|
359
|
+
role_arn = found.cloudobj.arn
|
360
|
+
end
|
361
|
+
else
|
362
|
+
raise MuError, "Unable to find execution role from #{c["role"]}"
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
params = {
|
367
|
+
name: @mu_name+"-"+c['name'].upcase,
|
368
|
+
image: c['image'],
|
369
|
+
memory: c['memory'],
|
370
|
+
cpu: c['cpu']
|
371
|
+
}
|
372
|
+
if !@config['vpc']
|
373
|
+
c['hostname'] ||= @mu_name+"-"+c['name'].upcase
|
374
|
+
end
|
375
|
+
[:essential, :hostname, :start_timeout, :stop_timeout, :user, :working_directory, :disable_networking, :privileged, :readonly_root_filesystem, :interactive, :pseudo_terminal, :links, :entry_point, :command, :dns_servers, :dns_search_domains, :docker_security_options, :port_mappings, :repository_credentials, :mount_points, :environment, :volumes_from, :secrets, :depends_on, :extra_hosts, :docker_labels, :ulimits, :system_controls, :health_check, :resource_requirements].each { |param|
|
376
|
+
if c.has_key?(param.to_s)
|
377
|
+
params[param] = if !c[param.to_s].nil? and (c[param.to_s].is_a?(Hash) or c[param.to_s].is_a?(Array))
|
378
|
+
MU.strToSym(c[param.to_s])
|
379
|
+
else
|
380
|
+
c[param.to_s]
|
381
|
+
end
|
382
|
+
end
|
383
|
+
}
|
384
|
+
if @config['vpc']
|
385
|
+
[:hostname, :dns_servers, :dns_search_domains, :links].each { |param|
|
386
|
+
if params[param]
|
387
|
+
MU.log "Container parameter #{param.to_s} not supported in VPC clusters, ignoring", MU::WARN
|
388
|
+
params.delete(param)
|
389
|
+
end
|
390
|
+
}
|
391
|
+
end
|
392
|
+
if @config['flavor'] == "Fargate"
|
393
|
+
[:privileged, :docker_security_options].each { |param|
|
394
|
+
if params[param]
|
395
|
+
MU.log "Container parameter #{param.to_s} not supported in Fargate clusters, ignoring", MU::WARN
|
396
|
+
params.delete(param)
|
397
|
+
end
|
398
|
+
}
|
399
|
+
end
|
400
|
+
if c['log_configuration']
|
401
|
+
log_obj = @deploy.findLitterMate(name: c['log_configuration']['options']['awslogs-group'], type: "logs")
|
402
|
+
if log_obj
|
403
|
+
c['log_configuration']['options']['awslogs-group'] = log_obj.mu_name
|
404
|
+
end
|
405
|
+
params[:log_configuration] = MU.strToSym(c['log_configuration'])
|
406
|
+
end
|
407
|
+
params
|
408
|
+
}
|
409
|
+
|
410
|
+
cpu_total = 2 if cpu_total == 0
|
411
|
+
mem_total = 2 if mem_total == 0
|
412
|
+
|
413
|
+
task_params = {
|
414
|
+
family: @deploy.deploy_id,
|
415
|
+
container_definitions: container_definitions,
|
416
|
+
requires_compatibilities: [launch_type]
|
417
|
+
}
|
418
|
+
|
419
|
+
if @config['volumes']
|
420
|
+
task_params[:volumes] = []
|
421
|
+
@config['volumes'].each { |v|
|
422
|
+
vol = { :name => v['name'] }
|
423
|
+
if v['type'] == "host"
|
424
|
+
vol[:host] = {}
|
425
|
+
if v['host_volume_source_path']
|
426
|
+
vol[:host][:source_path] = v['host_volume_source_path']
|
427
|
+
end
|
428
|
+
elsif v['type'] == "docker"
|
429
|
+
vol[:docker_volume_configuration] = MU.strToSym(v['docker_volume_configuration'])
|
430
|
+
else
|
431
|
+
raise MuError, "Invalid volume type '#{v['type']}' specified in ContainerCluster '#{@mu_name}'"
|
432
|
+
end
|
433
|
+
task_params[:volumes] << vol
|
434
|
+
}
|
435
|
+
end
|
436
|
+
|
437
|
+
if role_arn
|
438
|
+
task_params[:execution_role_arn] = role_arn
|
439
|
+
task_params[:task_role_arn] = role_arn
|
440
|
+
end
|
441
|
+
if @config['flavor'] == "Fargate"
|
442
|
+
task_params[:network_mode] = "awsvpc"
|
443
|
+
task_params[:cpu] = cpu_total.to_i.to_s
|
444
|
+
task_params[:memory] = mem_total.to_i.to_s
|
445
|
+
end
|
446
|
+
|
447
|
+
tasks_registered += 1
|
448
|
+
MU.log "Registering task definition #{service_name} with #{container_definitions.size.to_s} containers"
|
449
|
+
|
450
|
+
# XXX this helpfully keeps revisions, but let's compare anyway and avoid cluttering with identical ones
|
451
|
+
resp = MU::Cloud::AWS.ecs(region: @config['region'], credentials: @config['credentials']).register_task_definition(task_params)
|
452
|
+
|
453
|
+
task_def = resp.task_definition.task_definition_arn
|
454
|
+
|
455
|
+
service_params = {
|
456
|
+
:cluster => @mu_name,
|
457
|
+
:desired_count => @config['instance_count'], # XXX this makes no sense
|
458
|
+
:service_name => service_name,
|
459
|
+
:launch_type => launch_type,
|
460
|
+
:task_definition => task_def
|
461
|
+
}
|
462
|
+
if @config['vpc']
|
463
|
+
subnet_ids = []
|
464
|
+
all_public = true
|
465
|
+
subnet_names = @config['vpc']['subnets'].map { |s| s.values.first }
|
466
|
+
@vpc.subnets.each { |subnet_obj|
|
467
|
+
next if !subnet_names.include?(subnet_obj.config['name'])
|
468
|
+
subnet_ids << subnet_obj.cloud_id
|
469
|
+
all_public = false if subnet_obj.private?
|
470
|
+
}
|
471
|
+
service_params[:network_configuration] = {
|
472
|
+
:awsvpc_configuration => {
|
473
|
+
:subnets => subnet_ids,
|
474
|
+
:security_groups => security_groups,
|
475
|
+
:assign_public_ip => all_public ? "ENABLED" : "DISABLED"
|
476
|
+
}
|
477
|
+
}
|
478
|
+
end
|
479
|
+
|
480
|
+
if !existing_svcs.include?(service_name)
|
481
|
+
MU.log "Creating Service #{service_name}"
|
482
|
+
|
483
|
+
resp = MU::Cloud::AWS.ecs(region: @config['region'], credentials: @config['credentials']).create_service(service_params)
|
484
|
+
else
|
485
|
+
service_params[:service] = service_params[:service_name].dup
|
486
|
+
service_params.delete(:service_name)
|
487
|
+
service_params.delete(:launch_type)
|
488
|
+
MU.log "Updating Service #{service_name}", MU::NOTICE, details: service_params
|
489
|
+
|
490
|
+
resp = MU::Cloud::AWS.ecs(region: @config['region'], credentials: @config['credentials']).update_service(service_params)
|
491
|
+
end
|
492
|
+
existing_svcs << service_name
|
493
|
+
}
|
494
|
+
|
495
|
+
max_retries = 10
|
496
|
+
retries = 0
|
497
|
+
if tasks_registered > 0
|
498
|
+
retry_me = false
|
499
|
+
begin
|
500
|
+
retry_me = !MU::Cloud::AWS::ContainerCluster.tasksRunning?(@mu_name, log: (retries > 0), region: @config['region'], credentials: @config['credentials'])
|
501
|
+
retries += 1
|
502
|
+
sleep 15 if retry_me
|
503
|
+
end while retry_me and retries < max_retries
|
504
|
+
tasks = nil
|
505
|
+
|
506
|
+
if retry_me
|
507
|
+
MU.log "Not all tasks successfully launched in cluster #{@mu_name}", MU::WARN
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
end
|
512
|
+
|
513
|
+
end
|
514
|
+
|
515
|
+
# Returns true if all tasks in the given ECS/Fargate cluster are in the
|
516
|
+
# RUNNING state.
|
517
|
+
# @param cluster [String]: The cluster to check
|
518
|
+
# @param log [Boolean]: Output the state of each task to Mu's logger facility
|
519
|
+
# @param region [String]
|
520
|
+
# @param credentials [String]
|
521
|
+
# @return [Boolean]
|
522
|
+
def self.tasksRunning?(cluster, log: true, region: MU.myRegion, credentials: nil)
|
523
|
+
services = MU::Cloud::AWS.ecs(region: region, credentials: credentials).list_services(
|
524
|
+
cluster: cluster
|
525
|
+
).service_arns.map { |s| s.sub(/.*?:service\/([^\/:]+?)$/, '\1') }
|
526
|
+
|
527
|
+
tasks_defined = []
|
528
|
+
|
529
|
+
begin
|
530
|
+
listme = services.slice!(0, (services.length >= 10 ? 10 : services.length))
|
531
|
+
if services.size > 0
|
532
|
+
tasks_defined.concat(
|
533
|
+
tasks = MU::Cloud::AWS.ecs(region: region, credentials: credentials).describe_services(
|
534
|
+
cluster: cluster,
|
535
|
+
services: listme
|
536
|
+
).services.map { |s| s.task_definition }
|
537
|
+
)
|
538
|
+
end
|
539
|
+
end while services.size > 0
|
540
|
+
|
541
|
+
containers = {}
|
542
|
+
|
543
|
+
tasks_defined.each { |t|
|
544
|
+
taskdef = MU::Cloud::AWS.ecs(region: region, credentials: credentials).describe_task_definition(
|
545
|
+
task_definition: t.sub(/^.*?:task-definition\/([^\/:]+)$/, '\1')
|
546
|
+
)
|
547
|
+
taskdef.task_definition.container_definitions.each { |c|
|
548
|
+
containers[c.name] = {}
|
549
|
+
}
|
550
|
+
}
|
551
|
+
|
552
|
+
tasks = MU::Cloud::AWS.ecs(region: region, credentials: credentials).list_tasks(
|
553
|
+
cluster: cluster,
|
554
|
+
desired_status: "RUNNING"
|
555
|
+
).task_arns
|
556
|
+
|
557
|
+
tasks.concat(MU::Cloud::AWS.ecs(region: region, credentials: credentials).list_tasks(
|
558
|
+
cluster: cluster,
|
559
|
+
desired_status: "STOPPED"
|
560
|
+
).task_arns)
|
561
|
+
|
562
|
+
begin
|
563
|
+
sample = tasks.slice!(0, (tasks.length >= 100 ? 100 : tasks.length))
|
564
|
+
break if sample.size == 0
|
565
|
+
task_ids = sample.map { |task_arn|
|
566
|
+
task_arn.sub(/^.*?:task\/([a-f0-9\-]+)$/, '\1')
|
567
|
+
}
|
568
|
+
|
569
|
+
MU::Cloud::AWS.ecs(region: region, credentials: credentials).describe_tasks(
|
570
|
+
cluster: cluster,
|
571
|
+
tasks: task_ids
|
572
|
+
).tasks.each { |t|
|
573
|
+
task_name = t.task_definition_arn.sub(/^.*?:task-definition\/([^\/:]+)$/, '\1')
|
574
|
+
t.containers.each { |c|
|
575
|
+
containers[c.name] ||= {}
|
576
|
+
containers[c.name][t.desired_status] ||= {
|
577
|
+
"reasons" => []
|
578
|
+
}
|
579
|
+
[t.stopped_reason, c.reason].each { |r|
|
580
|
+
next if r.nil?
|
581
|
+
containers[c.name][t.desired_status]["reasons"] << r
|
582
|
+
}
|
583
|
+
containers[c.name][t.desired_status]["reasons"].uniq!
|
584
|
+
if !containers[c.name][t.desired_status]['time'] or
|
585
|
+
t.created_at > containers[c.name][t.desired_status]['time']
|
586
|
+
MU.log c.name, MU::NOTICE, details: t
|
587
|
+
containers[c.name][t.desired_status] = {
|
588
|
+
"time" => t.created_at,
|
589
|
+
"status" => c.last_status,
|
590
|
+
"reasons" => containers[c.name][t.desired_status]["reasons"]
|
591
|
+
}
|
592
|
+
end
|
593
|
+
}
|
594
|
+
}
|
595
|
+
end while tasks.size > 0
|
596
|
+
|
597
|
+
to_return = true
|
598
|
+
containers.each_pair { |name, states|
|
599
|
+
if !states["RUNNING"] or states["RUNNING"]["status"] != "RUNNING"
|
600
|
+
to_return = false
|
601
|
+
if states["STOPPED"] and states["STOPPED"]["status"]
|
602
|
+
MU.log "Container #{name} has failures", MU::WARN, details: states["STOPPED"] if log
|
603
|
+
elsif states["RUNNING"] and states["RUNNING"]["status"]
|
604
|
+
MU.log "Container #{name} not currently running", MU::NOTICE, details: states["RUNNING"] if log
|
605
|
+
else
|
606
|
+
MU.log "Container #{name} in unknown state", MU::WARN, details: states["STOPPED"] if log
|
607
|
+
end
|
608
|
+
else
|
609
|
+
MU.log "Container #{name} running", details: states["RUNNING"] if log
|
610
|
+
end
|
611
|
+
}
|
612
|
+
|
613
|
+
to_return
|
285
614
|
end
|
286
615
|
|
287
616
|
# Return the cloud layer descriptor for this EKS/ECS/Fargate cluster
|
@@ -337,7 +666,19 @@ module MU
|
|
337
666
|
elsif flavor == "EKS"
|
338
667
|
# XXX this is absurd, but these don't appear to be available from an API anywhere
|
339
668
|
# Here's their Packer build, should just convert to Chef: https://github.com/awslabs/amazon-eks-ami
|
340
|
-
amis = {
|
669
|
+
amis = {
|
670
|
+
"us-east-1" => "ami-0abcb9f9190e867ab",
|
671
|
+
"us-east-2" => "ami-04ea7cb66af82ae4a",
|
672
|
+
"us-west-2" => "ami-0923e4b35a30a5f53",
|
673
|
+
"eu-west-1" => "ami-08716b70cac884aaa",
|
674
|
+
"eu-west-2" => "ami-0c7388116d474ee10",
|
675
|
+
"eu-west-3" => "ami-0560aea042fec8b12",
|
676
|
+
"ap-northeast-1" => "ami-0bfedee6a7845c26d",
|
677
|
+
"ap-northeast-2" => "ami-0a904348b703e620c",
|
678
|
+
"ap-south-1" => "ami-09c3eb35bb3be46a4",
|
679
|
+
"ap-southeast-1" => "ami-07b922b9b94d9a6d2",
|
680
|
+
"ap-southeast-2" => "ami-0f0121e9e64ebd3dc"
|
681
|
+
}
|
341
682
|
return amis[region]
|
342
683
|
end
|
343
684
|
nil
|
@@ -381,6 +722,24 @@ module MU
|
|
381
722
|
resp.cluster_arns.each { |arn|
|
382
723
|
if arn.match(/:cluster\/(#{MU.deploy_id}[^:]+)$/)
|
383
724
|
cluster = Regexp.last_match[1]
|
725
|
+
|
726
|
+
svc_resp = MU::Cloud::AWS.ecs(region: region, credentials: credentials).list_services(
|
727
|
+
cluster: arn
|
728
|
+
)
|
729
|
+
if svc_resp and svc_resp.service_arns
|
730
|
+
svc_resp.service_arns.each { |svc_arn|
|
731
|
+
svc_name = svc_arn.gsub(/.*?:service\/(.*)/, '\1')
|
732
|
+
MU.log "Deleting Service #{svc_name} from ECS Cluster #{cluster}"
|
733
|
+
if !noop
|
734
|
+
MU::Cloud::AWS.ecs(region: region, credentials: credentials).delete_service(
|
735
|
+
cluster: arn,
|
736
|
+
service: svc_name,
|
737
|
+
force: true # man forget scaling up and down if we're just deleting the cluster
|
738
|
+
)
|
739
|
+
end
|
740
|
+
}
|
741
|
+
end
|
742
|
+
|
384
743
|
instances = MU::Cloud::AWS.ecs(credentials: credentials, region: region).list_container_instances({
|
385
744
|
cluster: cluster
|
386
745
|
})
|
@@ -400,13 +759,33 @@ module MU
|
|
400
759
|
MU.log "Deleting ECS Cluster #{cluster}"
|
401
760
|
if !noop
|
402
761
|
# TODO de-register container instances
|
762
|
+
begin
|
403
763
|
deletion = MU::Cloud::AWS.ecs(credentials: credentials, region: region).delete_cluster(
|
404
764
|
cluster: cluster
|
405
765
|
)
|
766
|
+
rescue Aws::ECS::Errors::ClusterContainsTasksException => e
|
767
|
+
sleep 5
|
768
|
+
retry
|
769
|
+
end
|
406
770
|
end
|
407
771
|
end
|
408
772
|
}
|
409
773
|
end
|
774
|
+
|
775
|
+
tasks = MU::Cloud::AWS.ecs(region: region, credentials: credentials).list_task_definitions(
|
776
|
+
family_prefix: MU.deploy_id
|
777
|
+
)
|
778
|
+
if tasks and tasks.task_definition_arns
|
779
|
+
tasks.task_definition_arns.each { |arn|
|
780
|
+
MU.log "Deregistering Fargate task definition #{arn}"
|
781
|
+
if !noop
|
782
|
+
MU::Cloud::AWS.ecs(region: region, credentials: credentials).deregister_task_definition(
|
783
|
+
task_definition: arn
|
784
|
+
)
|
785
|
+
end
|
786
|
+
}
|
787
|
+
end
|
788
|
+
|
410
789
|
return if !MU::Cloud::AWS::ContainerCluster.EKSRegions.include?(region)
|
411
790
|
|
412
791
|
|
@@ -491,19 +870,553 @@ module MU
|
|
491
870
|
"enum" => ["ECS", "EKS", "Fargate"],
|
492
871
|
"default" => "ECS"
|
493
872
|
},
|
873
|
+
"kubernetes" => {
|
874
|
+
"default" => { "version" => "1.11" }
|
875
|
+
},
|
494
876
|
"platform" => {
|
495
|
-
"description" => "The platform to choose for worker nodes. Will default to Amazon Linux for ECS, CentOS 7 for everything else",
|
877
|
+
"description" => "The platform to choose for worker nodes. Will default to Amazon Linux for ECS, CentOS 7 for everything else. Only valid for EKS and ECS flavors.",
|
496
878
|
"default" => "centos7"
|
497
879
|
},
|
498
880
|
"ami_id" => {
|
499
881
|
"type" => "string",
|
500
|
-
"description" => "The Amazon EC2 AMI on which to base this cluster's container hosts. Will use the default appropriate for the platform, if not specified."
|
882
|
+
"description" => "The Amazon EC2 AMI on which to base this cluster's container hosts. Will use the default appropriate for the platform, if not specified. Only valid for EKS and ECS flavors."
|
501
883
|
},
|
502
884
|
"run_list" => {
|
503
885
|
"type" => "array",
|
504
886
|
"items" => {
|
505
887
|
"type" => "string",
|
506
|
-
"description" => "An extra Chef run list entry, e.g. role[rolename] or recipe[recipename]s, to be run on worker nodes."
|
888
|
+
"description" => "An extra Chef run list entry, e.g. role[rolename] or recipe[recipename]s, to be run on worker nodes. Only valid for EKS and ECS flavors."
|
889
|
+
}
|
890
|
+
},
|
891
|
+
"ingress_rules" => {
|
892
|
+
"type" => "array",
|
893
|
+
"items" => MU::Config::FirewallRule.ruleschema,
|
894
|
+
"default" => [
|
895
|
+
{
|
896
|
+
"egress" => true,
|
897
|
+
"port" => 443,
|
898
|
+
"hosts" => [ "0.0.0.0/0" ]
|
899
|
+
}
|
900
|
+
]
|
901
|
+
},
|
902
|
+
"logging" => {
|
903
|
+
"type" => "array",
|
904
|
+
"default" => ["authenticator", "api"],
|
905
|
+
"items" => {
|
906
|
+
"type" => "string",
|
907
|
+
"description" => "Cluster CloudWatch logs to enable for EKS clusters.",
|
908
|
+
"enum" => ["api", "audit", "authenticator", "controllerManager", "scheduler"]
|
909
|
+
}
|
910
|
+
},
|
911
|
+
"volumes" => {
|
912
|
+
"type" => "array",
|
913
|
+
"items" => {
|
914
|
+
"description" => "Define one or more volumes which can then be referenced by the +mount_points+ parameter inside +containers+. +docker+ volumes are not valid for Fargate clusters. See also https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_data_volumes.html",
|
915
|
+
"type" => "object",
|
916
|
+
"required" => ["name", "type"],
|
917
|
+
"properties" => {
|
918
|
+
"name" => {
|
919
|
+
"type" => "string",
|
920
|
+
"description" => "Name this volume so it can be referenced by containers."
|
921
|
+
},
|
922
|
+
"type" => {
|
923
|
+
"type" => "string",
|
924
|
+
"enum" => ["docker", "host"]
|
925
|
+
},
|
926
|
+
"docker_volume_configuration" => {
|
927
|
+
"type" => "object",
|
928
|
+
"default" => {
|
929
|
+
"autoprovision" => true,
|
930
|
+
"driver" => "local"
|
931
|
+
},
|
932
|
+
"description" => "This parameter is specified when you are using +docker+ volumes. Docker volumes are only supported when you are using the EC2 launch type. To use bind mounts, specify a +host+ volume instead.",
|
933
|
+
"properties" => {
|
934
|
+
"autoprovision" => {
|
935
|
+
"type" => "boolean",
|
936
|
+
"description" => "Create the Docker volume if it does not already exist.",
|
937
|
+
"default" => true
|
938
|
+
},
|
939
|
+
"driver" => {
|
940
|
+
"type" => "string",
|
941
|
+
"description" => "The Docker volume driver to use. Note that Windows containers can only use the +local+ driver. This parameter maps to +Driver+ in the Create a volume section of the Docker Remote API and the +xxdriver+ option to docker volume create."
|
942
|
+
},
|
943
|
+
"labels" => {
|
944
|
+
"description" => "Custom metadata to add to your Docker volume.",
|
945
|
+
"type" => "object"
|
946
|
+
},
|
947
|
+
"driver_opts" => {
|
948
|
+
"description" => "A map of Docker driver-specific options passed through. This parameter maps to +DriverOpts+ in the Create a volume section of the Docker Remote API and the +xxopt+ option to docker volume create .",
|
949
|
+
"type" => "object"
|
950
|
+
},
|
951
|
+
}
|
952
|
+
},
|
953
|
+
"host_volume_source_path" => {
|
954
|
+
"type" => "string",
|
955
|
+
"description" => "If specified, and the +type+ of this volume is +host+, data will be stored in the container host in this location and will persist after containers associated with it stop running."
|
956
|
+
}
|
957
|
+
}
|
958
|
+
}
|
959
|
+
},
|
960
|
+
"containers" => {
|
961
|
+
"type" => "array",
|
962
|
+
"items" => {
|
963
|
+
"type" => "object",
|
964
|
+
"description" => "A container image to run on this cluster.",
|
965
|
+
"required" => ["name", "image"],
|
966
|
+
"properties" => {
|
967
|
+
"name" => {
|
968
|
+
"type" => "string",
|
969
|
+
"description" => "The name of a container. If you are linking multiple containers together in a task definition, the name of one container can be entered in the +links+ of another container to connect the containers. This parameter maps to +name+ in the Create a container section of the Docker Remote API and the +--name+ option to docker run."
|
970
|
+
},
|
971
|
+
"service" => {
|
972
|
+
"type" => "string",
|
973
|
+
"description" => "The Service of which this container will be a component. Default behavior, if unspecified, is to create a service with the name of this container definition and assume they map 1:1."
|
974
|
+
},
|
975
|
+
"image" => {
|
976
|
+
"type" => "string",
|
977
|
+
"description" => "A Docker image to run, as a shorthand name for a public Dockerhub image or a full URL to a private container repository (+repository-url/image:tag+ or <tt>repository-url/image@digest</tt>). See +repository_credentials+ to specify authentication for a container repository.",
|
978
|
+
},
|
979
|
+
"cpu" => {
|
980
|
+
"type" => "integer",
|
981
|
+
"default" => 256,
|
982
|
+
"description" => "CPU to allocate for this container/task. This parameter maps to +CpuShares+ in the Create a container section of the Docker Remote API and the +--cpu-shares+ option to docker run. Not all +cpu+ and +memory+ combinations are valid, particularly when using Fargate, see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html"
|
983
|
+
},
|
984
|
+
"memory" => {
|
985
|
+
"type" => "integer",
|
986
|
+
"default" => 512,
|
987
|
+
"description" => "Hard limit of memory to allocate for this container/task. Not all +cpu+ and +memory+ combinations are valid, particularly when using Fargate, see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html"
|
988
|
+
},
|
989
|
+
"memory_reservation" => {
|
990
|
+
"type" => "integer",
|
991
|
+
"default" => 512,
|
992
|
+
"description" => "Soft limit of memory to allocate for this container/task. This parameter maps to +MemoryReservation+ in the Create a container section of the Docker Remote API and the +--memory-reservation+ option to docker run."
|
993
|
+
},
|
994
|
+
"role" => MU::Config::Role.reference,
|
995
|
+
"essential" => {
|
996
|
+
"type" => "boolean",
|
997
|
+
"description" => "Flag this container as essential or non-essential to its parent task. If the container fails and is marked essential, the parent task will also be marked as failed.",
|
998
|
+
"default" => true
|
999
|
+
},
|
1000
|
+
"hostname" => {
|
1001
|
+
"type" => "string",
|
1002
|
+
"description" => "Set this container's local hostname. If not specified, will inherit the name of the parent task. Not valid for Fargate clusters. This parameter maps to +Hostname+ in the Create a container section of the Docker Remote API and the +--hostname+ option to docker run."
|
1003
|
+
},
|
1004
|
+
"user" => {
|
1005
|
+
"type" => "string",
|
1006
|
+
"description" => "The system-level user to use when executing commands inside this container"
|
1007
|
+
},
|
1008
|
+
"working_directory" => {
|
1009
|
+
"type" => "string",
|
1010
|
+
"description" => "The working directory in which to run commands inside the container."
|
1011
|
+
},
|
1012
|
+
"disable_networking" => {
|
1013
|
+
"type" => "boolean",
|
1014
|
+
"description" => "This parameter maps to +NetworkDisabled+ in the Create a container section of the Docker Remote API."
|
1015
|
+
},
|
1016
|
+
"privileged" => {
|
1017
|
+
"type" => "boolean",
|
1018
|
+
"description" => "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user). This parameter maps to +Privileged+ in the Create a container section of the Docker Remote API and the +--privileged+ option to docker run. Not valid for Fargate clusters."
|
1019
|
+
},
|
1020
|
+
"readonly_root_filesystem" => {
|
1021
|
+
"type" => "boolean",
|
1022
|
+
"description" => "This parameter maps to +ReadonlyRootfs+ in the Create a container section of the Docker Remote API and the +--read-only+ option to docker run."
|
1023
|
+
},
|
1024
|
+
"interactive" => {
|
1025
|
+
"type" => "boolean",
|
1026
|
+
"description" => "When this parameter is +true+, this allows you to deploy containerized applications that require +stdin+ or a +tty+ to be allocated. This parameter maps to +OpenStdin+ in the Create a container section of the Docker Remote API and the +--interactive+ option to docker run."
|
1027
|
+
},
|
1028
|
+
"pseudo_terminal" => {
|
1029
|
+
"type" => "boolean",
|
1030
|
+
"description" => "When this parameter is true, a TTY is allocated. This parameter maps to +Tty+ in the Create a container section of the Docker Remote API and the +--tty+ option to docker run."
|
1031
|
+
},
|
1032
|
+
"start_timeout" => {
|
1033
|
+
"type" => "integer",
|
1034
|
+
"description" => "Time duration to wait before giving up on containers which have been specified with +depends_on+ for this one."
|
1035
|
+
},
|
1036
|
+
"stop_timeout" => {
|
1037
|
+
"type" => "integer",
|
1038
|
+
"description" => "Time duration to wait before the container is forcefully killed if it doesn't exit normally on its own."
|
1039
|
+
},
|
1040
|
+
"links" => {
|
1041
|
+
"type" => "array",
|
1042
|
+
"items" => {
|
1043
|
+
"description" => "The +link+ parameter allows containers to communicate with each other without the need for port mappings. Only supported if the network mode of a task definition is set to +bridge+. The +name:internalName+ construct is analogous to +name:alias+ in Docker links.",
|
1044
|
+
"type" => "string"
|
1045
|
+
}
|
1046
|
+
},
|
1047
|
+
"entry_point" => {
|
1048
|
+
"type" => "array",
|
1049
|
+
"items" => {
|
1050
|
+
"type" => "string",
|
1051
|
+
"description" => "The entry point that is passed to the container. This parameter maps to +Entrypoint+ in the Create a container section of the Docker Remote API and the +--entrypoint+ option to docker run."
|
1052
|
+
}
|
1053
|
+
},
|
1054
|
+
"command" => {
|
1055
|
+
"type" => "array",
|
1056
|
+
"items" => {
|
1057
|
+
"type" => "string",
|
1058
|
+
"description" => "This parameter maps to +Cmd+ in the Create a container section of the Docker Remote API and the +COMMAND+ parameter to docker run."
|
1059
|
+
}
|
1060
|
+
},
|
1061
|
+
"dns_servers" => {
|
1062
|
+
"type" => "array",
|
1063
|
+
"items" => {
|
1064
|
+
"type" => "string",
|
1065
|
+
"description" => "A list of DNS servers that are presented to the container. This parameter maps to +Dns+ in the Create a container section of the Docker Remote API and the +--dns+ option to docker run."
|
1066
|
+
}
|
1067
|
+
},
|
1068
|
+
"dns_search_domains" => {
|
1069
|
+
"type" => "array",
|
1070
|
+
"items" => {
|
1071
|
+
"type" => "string",
|
1072
|
+
"description" => "A list of DNS search domains that are presented to the container. This parameter maps to +DnsSearch+ in the Create a container section of the Docker Remote API and the +--dns-search+ option to docker run."
|
1073
|
+
}
|
1074
|
+
},
|
1075
|
+
"linux_parameters" => {
|
1076
|
+
"type" => "object",
|
1077
|
+
"description" => "Linux-specific options that are applied to the container, such as Linux KernelCapabilities.",
|
1078
|
+
"properties" => {
|
1079
|
+
"init_process_enabled" => {
|
1080
|
+
"type" => "boolean",
|
1081
|
+
"description" => "Run an +init+ process inside the container that forwards signals and reaps processes. This parameter maps to the +--init+ option to docker run."
|
1082
|
+
},
|
1083
|
+
"shared_memory_size" => {
|
1084
|
+
"type" => "integer",
|
1085
|
+
"description" => "The value for the size (in MiB) of the +/dev/shm+ volume. This parameter maps to the +--shm-size+ option to docker run. Not valid for Fargate clusters."
|
1086
|
+
},
|
1087
|
+
"capabilities" => {
|
1088
|
+
"type" => "object",
|
1089
|
+
"description" => "The Linux capabilities for the container that are added to or dropped from the default configuration provided by Docker.",
|
1090
|
+
"properties" => {
|
1091
|
+
"add" => {
|
1092
|
+
"type" => "array",
|
1093
|
+
"items" => {
|
1094
|
+
"type" => "string",
|
1095
|
+
"description" => "This parameter maps to +CapAdd+ in the Create a container section of the Docker Remote API and the +--cap-add+ option to docker run. Not valid for Fargate clusters.",
|
1096
|
+
"enum" => ["ALL", "AUDIT_CONTROL", "AUDIT_WRITE", "BLOCK_SUSPEND", "CHOWN", "DAC_OVERRIDE", "DAC_READ_SEARCH", "FOWNER", "FSETID", "IPC_LOCK", "IPC_OWNER", "KILL", "LEASE", "LINUX_IMMUTABLE", "MAC_ADMIN", "MAC_OVERRIDE", "MKNOD", "NET_ADMIN", "NET_BIND_SERVICE", "NET_BROADCAST", "NET_RAW", "SETFCAP", "SETGID", "SETPCAP", "SETUID", "SYS_ADMIN", "SYS_BOOT", "SYS_CHROOT", "SYS_MODULE", "SYS_NICE", "SYS_PACCT", "SYS_PTRACE", "SYS_RAWIO", "SYS_RESOURCE", "SYS_TIME", "SYS_TTY_CONFIG", "SYSLOG", "WAKE_ALARM"]
|
1097
|
+
}
|
1098
|
+
},
|
1099
|
+
"drop" => {
|
1100
|
+
"type" => "array",
|
1101
|
+
"items" => {
|
1102
|
+
"type" => "string",
|
1103
|
+
"description" => "This parameter maps to +CapDrop+ in the Create a container section of the Docker Remote API and the +--cap-drop+ option to docker run.",
|
1104
|
+
"enum" => ["ALL", "AUDIT_CONTROL", "AUDIT_WRITE", "BLOCK_SUSPEND", "CHOWN", "DAC_OVERRIDE", "DAC_READ_SEARCH", "FOWNER", "FSETID", "IPC_LOCK", "IPC_OWNER", "KILL", "LEASE", "LINUX_IMMUTABLE", "MAC_ADMIN", "MAC_OVERRIDE", "MKNOD", "NET_ADMIN", "NET_BIND_SERVICE", "NET_BROADCAST", "NET_RAW", "SETFCAP", "SETGID", "SETPCAP", "SETUID", "SYS_ADMIN", "SYS_BOOT", "SYS_CHROOT", "SYS_MODULE", "SYS_NICE", "SYS_PACCT", "SYS_PTRACE", "SYS_RAWIO", "SYS_RESOURCE", "SYS_TIME", "SYS_TTY_CONFIG", "SYSLOG", "WAKE_ALARM"]
|
1105
|
+
}
|
1106
|
+
}
|
1107
|
+
}
|
1108
|
+
},
|
1109
|
+
"devices" => {
|
1110
|
+
"type" => "array",
|
1111
|
+
"items" => {
|
1112
|
+
"type" => "object",
|
1113
|
+
"description" => "Host devices to expose to the container.",
|
1114
|
+
"properties" => {
|
1115
|
+
"host_path" => {
|
1116
|
+
"type" => "string",
|
1117
|
+
"description" => "The path for the device on the host container instance."
|
1118
|
+
},
|
1119
|
+
"container_path" => {
|
1120
|
+
"type" => "string",
|
1121
|
+
"description" => "The path inside the container at which to expose the host device."
|
1122
|
+
},
|
1123
|
+
"permissions" => {
|
1124
|
+
"type" => "array",
|
1125
|
+
"items" => {
|
1126
|
+
"description" => "The explicit permissions to provide to the container for the device. By default, the container has permissions for +read+, +write+, and +mknod+ for the device.",
|
1127
|
+
"type" => "string"
|
1128
|
+
}
|
1129
|
+
}
|
1130
|
+
}
|
1131
|
+
}
|
1132
|
+
},
|
1133
|
+
"tmpfs" => {
|
1134
|
+
"type" => "array",
|
1135
|
+
"items" => {
|
1136
|
+
"type" => "object",
|
1137
|
+
"description" => "A tmpfs device to expost to the container. This parameter maps to the +--tmpfs+ option to docker run. Not valid for Fargate clusters.",
|
1138
|
+
"properties" => {
|
1139
|
+
"container_path" => {
|
1140
|
+
"type" => "string",
|
1141
|
+
"description" => "The absolute file path where the tmpfs volume is to be mounted."
|
1142
|
+
},
|
1143
|
+
"size" => {
|
1144
|
+
"type" => "integer",
|
1145
|
+
"description" => "The size (in MiB) of the tmpfs volume."
|
1146
|
+
},
|
1147
|
+
"mount_options" => {
|
1148
|
+
"type" => "array",
|
1149
|
+
"items" => {
|
1150
|
+
"description" => "tmpfs volume mount options",
|
1151
|
+
"type" => "string",
|
1152
|
+
"enum" => ["defaults", "ro", "rw", "suid", "nosuid", "dev", "nodev", "exec", "noexec", "sync", "async", "dirsync", "remount", "mand", "nomand", "atime", "noatime", "diratime", "nodiratime", "bind", "rbind", "unbindable", "runbindable", "private", "rprivate", "shared", "rshared", "slave", "rslave", "relatime", "norelatime", "strictatime", "nostrictatime", "mode", "uid", "gid", "nr_inodes", "nr_blocks", "mpol"]
|
1153
|
+
}
|
1154
|
+
}
|
1155
|
+
}
|
1156
|
+
}
|
1157
|
+
}
|
1158
|
+
}
|
1159
|
+
},
|
1160
|
+
"docker_labels" => {
|
1161
|
+
"type" => "object",
|
1162
|
+
"description" => "A key/value map of labels to add to the container. This parameter maps to +Labels+ in the Create a container section of the Docker Remote API and the +--label+ option to docker run."
|
1163
|
+
},
|
1164
|
+
"docker_security_options" => {
|
1165
|
+
"type" => "array",
|
1166
|
+
"items" => {
|
1167
|
+
"type" => "string",
|
1168
|
+
"description" => "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field is not valid for containers in tasks using the Fargate launch type. This parameter maps to +SecurityOpt+ in the Create a container section of the Docker Remote API and the +--security-opt+ option to docker run."
|
1169
|
+
}
|
1170
|
+
},
|
1171
|
+
"health_check" => {
|
1172
|
+
"type" => "object",
|
1173
|
+
"required" => ["command"],
|
1174
|
+
"description" => "The health check command and associated configuration parameters for the container. This parameter maps to +HealthCheck+ in the Create a container section of the Docker Remote API and the +HEALTHCHECK+ parameter of docker run.",
|
1175
|
+
"properties" => {
|
1176
|
+
"command" => {
|
1177
|
+
"type" => "array",
|
1178
|
+
"items" => {
|
1179
|
+
"type" => "string",
|
1180
|
+
"description" => "A string array representing the command that the container runs to determine if it is healthy."
|
1181
|
+
}
|
1182
|
+
},
|
1183
|
+
"interval" => {
|
1184
|
+
"type" => "integer",
|
1185
|
+
"description" => "The time period in seconds between each health check execution."
|
1186
|
+
},
|
1187
|
+
"timeout" => {
|
1188
|
+
"type" => "integer",
|
1189
|
+
"description" => "The time period in seconds to wait for a health check to succeed before it is considered a failure."
|
1190
|
+
},
|
1191
|
+
"retries" => {
|
1192
|
+
"type" => "integer",
|
1193
|
+
"description" => "The number of times to retry a failed health check before the container is considered unhealthy."
|
1194
|
+
},
|
1195
|
+
"start_period" => {
|
1196
|
+
"type" => "integer",
|
1197
|
+
"description" => "The optional grace period within which to provide containers time to bootstrap before failed health checks count towards the maximum number of retries."
|
1198
|
+
}
|
1199
|
+
}
|
1200
|
+
},
|
1201
|
+
"environment" => {
|
1202
|
+
"type" => "array",
|
1203
|
+
"items" => {
|
1204
|
+
"type" => "object",
|
1205
|
+
"description" => "The environment variables to pass to a container. This parameter maps to +Env+ in the Create a container section of the Docker Remote API and the +--env+ option to docker run.",
|
1206
|
+
"properties" => {
|
1207
|
+
"name" => {
|
1208
|
+
"type" => "string"
|
1209
|
+
},
|
1210
|
+
"value" => {
|
1211
|
+
"type" => "string"
|
1212
|
+
}
|
1213
|
+
}
|
1214
|
+
}
|
1215
|
+
},
|
1216
|
+
"resource_requirements" => {
|
1217
|
+
"type" => "array",
|
1218
|
+
"items" => {
|
1219
|
+
"type" => "object",
|
1220
|
+
"description" => "Special requirements for this container. As of this writing, +GPU+ is the only valid option.",
|
1221
|
+
"required" => ["type", "value"],
|
1222
|
+
"properties" => {
|
1223
|
+
"type" => {
|
1224
|
+
"type" => "string",
|
1225
|
+
"enum" => ["GPU"],
|
1226
|
+
"description" => "Special requirements for this container. As of this writing, +GPU+ is the only valid option."
|
1227
|
+
},
|
1228
|
+
"value" => {
|
1229
|
+
"type" => "string",
|
1230
|
+
"description" => "The number of physical GPUs the Amazon ECS container agent will reserve for the container."
|
1231
|
+
}
|
1232
|
+
}
|
1233
|
+
}
|
1234
|
+
},
|
1235
|
+
"system_controls" => {
|
1236
|
+
"type" => "array",
|
1237
|
+
"items" => {
|
1238
|
+
"type" => "object",
|
1239
|
+
"description" => "A list of namespaced kernel parameters to set in the container. This parameter maps to +Sysctls+ in the Create a container section of the Docker Remote API and the +--sysctl+ option to docker run.",
|
1240
|
+
"properties" => {
|
1241
|
+
"namespace" => {
|
1242
|
+
"type" => "string",
|
1243
|
+
"description" => "The namespaced kernel parameter for which to set a +value+."
|
1244
|
+
},
|
1245
|
+
"value" => {
|
1246
|
+
"type" => "string",
|
1247
|
+
"description" => "The value for the namespaced kernel parameter specified in +namespace+."
|
1248
|
+
}
|
1249
|
+
}
|
1250
|
+
}
|
1251
|
+
},
|
1252
|
+
"ulimits" => {
|
1253
|
+
"type" => "array",
|
1254
|
+
"items" => {
|
1255
|
+
"type" => "object",
|
1256
|
+
"description" => "This parameter maps to +Ulimits+ in the Create a container section of the Docker Remote API and the +--ulimit+ option to docker run.",
|
1257
|
+
"required" => ["name", "soft_limit", "hard_limit"],
|
1258
|
+
"properties" => {
|
1259
|
+
"name" => {
|
1260
|
+
"type" => "string",
|
1261
|
+
"description" => "The ulimit parameter to set.",
|
1262
|
+
"enum" => ["core", "cpu", "data", "fsize", "locks", "memlock", "msgqueue", "nice", "nofile", "nproc", "rss", "rtprio", "rttime", "sigpending", "stack"]
|
1263
|
+
},
|
1264
|
+
"soft_limit" => {
|
1265
|
+
"type" => "integer",
|
1266
|
+
"description" => "The soft limit for the ulimit type."
|
1267
|
+
},
|
1268
|
+
"hard_limit" => {
|
1269
|
+
"type" => "integer",
|
1270
|
+
"description" => "The hard limit for the ulimit type."
|
1271
|
+
},
|
1272
|
+
}
|
1273
|
+
}
|
1274
|
+
},
|
1275
|
+
"extra_hosts" => {
|
1276
|
+
"type" => "array",
|
1277
|
+
"items" => {
|
1278
|
+
"type" => "object",
|
1279
|
+
"description" => "A list of hostnames and IP address mappings to append to the +/etc/hosts+ file on the container. This parameter maps to ExtraHosts in the +Create+ a container section of the Docker Remote API and the +--add-host+ option to docker run.",
|
1280
|
+
"required" => ["hostname", "ip_address"],
|
1281
|
+
"properties" => {
|
1282
|
+
"hostname" => {
|
1283
|
+
"type" => "string"
|
1284
|
+
},
|
1285
|
+
"ip_address" => {
|
1286
|
+
"type" => "string"
|
1287
|
+
}
|
1288
|
+
}
|
1289
|
+
}
|
1290
|
+
},
|
1291
|
+
"secrets" => {
|
1292
|
+
"type" => "array",
|
1293
|
+
"items" => {
|
1294
|
+
"type" => "object",
|
1295
|
+
"description" => "See https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html",
|
1296
|
+
"required" => ["name", "value_from"],
|
1297
|
+
"properties" => {
|
1298
|
+
"name" => {
|
1299
|
+
"type" => "string",
|
1300
|
+
"description" => "The value to set as the environment variable on the container."
|
1301
|
+
},
|
1302
|
+
"value_from" => {
|
1303
|
+
"type" => "string",
|
1304
|
+
"description" => "The secret to expose to the container."
|
1305
|
+
}
|
1306
|
+
}
|
1307
|
+
}
|
1308
|
+
},
|
1309
|
+
"depends_on" => {
|
1310
|
+
"type" => "array",
|
1311
|
+
"items" => {
|
1312
|
+
"type" => "object",
|
1313
|
+
"required" => ["container_name", "condition"],
|
1314
|
+
"description" => "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed.",
|
1315
|
+
"properties" => {
|
1316
|
+
"container_name" => {
|
1317
|
+
"type" => "string"
|
1318
|
+
},
|
1319
|
+
"condition" => {
|
1320
|
+
"type" => "string",
|
1321
|
+
"enum" => ["START", "COMPLETE", "SUCCESS", "HEALTHY"]
|
1322
|
+
}
|
1323
|
+
}
|
1324
|
+
}
|
1325
|
+
},
|
1326
|
+
"mount_points" => {
|
1327
|
+
"type" => "array",
|
1328
|
+
"items" => {
|
1329
|
+
"type" => "object",
|
1330
|
+
"description" => "The mount points for data volumes in your container. This parameter maps to +Volumes+ in the Create a container section of the Docker Remote API and the +--volume+ option to docker run.",
|
1331
|
+
"properties" => {
|
1332
|
+
"source_volume" => {
|
1333
|
+
"type" => "string",
|
1334
|
+
"description" => "The name of the +volume+ to mount, defined under the +volumes+ section of our parent +container_cluster+ (if the volume is not defined, an ephemeral bind host volume will be allocated)."
|
1335
|
+
},
|
1336
|
+
"container_path" => {
|
1337
|
+
"type" => "string",
|
1338
|
+
"description" => "The container-side path where this volume must be mounted"
|
1339
|
+
},
|
1340
|
+
"read_only" => {
|
1341
|
+
"type" => "boolean",
|
1342
|
+
"default" => false,
|
1343
|
+
"description" => "Mount the volume read-only"
|
1344
|
+
}
|
1345
|
+
}
|
1346
|
+
}
|
1347
|
+
},
|
1348
|
+
"volumes_from" => {
|
1349
|
+
"type" => "array",
|
1350
|
+
"items" => {
|
1351
|
+
"type" => "object",
|
1352
|
+
"description" => "Data volumes to mount from another container. This parameter maps to +VolumesFrom+ in the Create a container section of the Docker Remote API and the +--volumes-from+ option to docker run.",
|
1353
|
+
"properties" => {
|
1354
|
+
"source_container" => {
|
1355
|
+
"type" => "string",
|
1356
|
+
"description" => "The name of another container within the same task definition from which to mount volumes."
|
1357
|
+
},
|
1358
|
+
"read_only" => {
|
1359
|
+
"type" => "boolean",
|
1360
|
+
"default" => false,
|
1361
|
+
"description" => "If this value is +true+, the container has read-only access to the volume."
|
1362
|
+
}
|
1363
|
+
}
|
1364
|
+
}
|
1365
|
+
},
|
1366
|
+
"repository_credentials" => {
|
1367
|
+
"type" => "object",
|
1368
|
+
"description" => "The Amazon Resource Name (ARN) of a secret containing the private repository credentials.",
|
1369
|
+
"properties" => {
|
1370
|
+
"credentials_parameter" => {
|
1371
|
+
"type" => "string",
|
1372
|
+
# XXX KMS? Secrets Manager? This documentation is vague.
|
1373
|
+
"description" => "The Amazon Resource Name (ARN) of a secret containing the private repository credentials."
|
1374
|
+
}
|
1375
|
+
}
|
1376
|
+
},
|
1377
|
+
"port_mappings" => {
|
1378
|
+
"type" => "array",
|
1379
|
+
"items" => {
|
1380
|
+
"description" => "Mappings of ports between the container instance and the host instance. This parameter maps to +PortBindings+ in the Create a container section of the Docker Remote API and the +--publish+ option to docker run.",
|
1381
|
+
"type" => "object",
|
1382
|
+
"properties" => {
|
1383
|
+
"container_port" => {
|
1384
|
+
"type" => "integer",
|
1385
|
+
"description" => "The port number on the container that is bound to the user-specified or automatically assigned host port."
|
1386
|
+
},
|
1387
|
+
"host_port" => {
|
1388
|
+
"type" => "integer",
|
1389
|
+
"description" => "The port number on the container instance to reserve for your container. This should not be specified for Fargate clusters, nor for ECS clusters deployed into VPCs."
|
1390
|
+
},
|
1391
|
+
"protocol" => {
|
1392
|
+
"type" => "string",
|
1393
|
+
"description" => "The protocol used for the port mapping.",
|
1394
|
+
"enum" => ["tcp", "udp"],
|
1395
|
+
"default" => "tcp"
|
1396
|
+
},
|
1397
|
+
}
|
1398
|
+
}
|
1399
|
+
},
|
1400
|
+
"log_configuration" => {
|
1401
|
+
"type" => "object",
|
1402
|
+
"description" => "Where to send container logs. If not specified, Mu will create a CloudWatch Logs output channel. See also: https://docs.aws.amazon.com/sdkforruby/api/Aws/ECS/Types/ContainerDefinition.html#log_configuration-instance_method",
|
1403
|
+
"default" => {
|
1404
|
+
"log_driver" => "awslogs"
|
1405
|
+
},
|
1406
|
+
"required" => ["log_driver"],
|
1407
|
+
"properties" => {
|
1408
|
+
"log_driver" => {
|
1409
|
+
"type" => "string",
|
1410
|
+
"description" => "Type of logging facility to use for container logs.",
|
1411
|
+
"enum" => ["json-file", "syslog", "journald", "gelf", "fluentd", "awslogs", "splunk"]
|
1412
|
+
},
|
1413
|
+
"options" => {
|
1414
|
+
"type" => "object",
|
1415
|
+
"description" => "Per-driver configuration options. See also: https://docs.aws.amazon.com/sdkforruby/api/Aws/ECS/Types/ContainerDefinition.html#log_configuration-instance_method"
|
1416
|
+
}
|
1417
|
+
}
|
1418
|
+
}
|
1419
|
+
}
|
507
1420
|
}
|
508
1421
|
}
|
509
1422
|
}
|
@@ -520,7 +1433,6 @@ module MU
|
|
520
1433
|
cluster['size'] = MU::Cloud::AWS::Server.validateInstanceType(cluster["instance_type"], cluster["region"])
|
521
1434
|
ok = false if cluster['size'].nil?
|
522
1435
|
|
523
|
-
|
524
1436
|
if cluster["flavor"] == "ECS" and cluster["kubernetes"] and !MU::Cloud::AWS.isGovCloud?(cluster["region"])
|
525
1437
|
cluster["flavor"] = "EKS"
|
526
1438
|
MU.log "Setting flavor of ContainerCluster '#{cluster['name']}' to EKS ('kubernetes' stanza was specified)", MU::NOTICE
|
@@ -531,8 +1443,101 @@ module MU
|
|
531
1443
|
ok = false
|
532
1444
|
end
|
533
1445
|
|
534
|
-
if
|
535
|
-
|
1446
|
+
if cluster["volumes"]
|
1447
|
+
cluster["volumes"].each { |v|
|
1448
|
+
if v["type"] == "docker"
|
1449
|
+
if cluster["flavor"] == "Fargate"
|
1450
|
+
MU.log "ContainerCluster #{cluster['name']}: Docker volumes are not supported in Fargate clusters (volume '#{v['name']}' is not valid)", MU::ERR
|
1451
|
+
ok = false
|
1452
|
+
end
|
1453
|
+
end
|
1454
|
+
}
|
1455
|
+
end
|
1456
|
+
|
1457
|
+
if cluster["flavor"] != "EKS" and cluster["containers"]
|
1458
|
+
created_generic_loggroup = false
|
1459
|
+
cluster['containers'].each { |c|
|
1460
|
+
if c['log_configuration'] and
|
1461
|
+
c['log_configuration']['log_driver'] == "awslogs" and
|
1462
|
+
(!c['log_configuration']['options'] or !c['log_configuration']['options']['awslogs-group'])
|
1463
|
+
|
1464
|
+
logname = cluster["name"]+"-svclogs"
|
1465
|
+
rolename = cluster["name"]+"-logrole"
|
1466
|
+
c['log_configuration']['options'] ||= {}
|
1467
|
+
c['log_configuration']['options']['awslogs-group'] = logname
|
1468
|
+
c['log_configuration']['options']['awslogs-region'] = cluster["region"]
|
1469
|
+
c['log_configuration']['options']['awslogs-stream-prefix'] ||= c['name']
|
1470
|
+
if c['mount_points']
|
1471
|
+
cluster['volumes'] ||= []
|
1472
|
+
volnames = cluster['volumes'].map { |v| v['name'] }
|
1473
|
+
c['mount_points'].each { |m|
|
1474
|
+
if !volnames.include?(m['source_volume'])
|
1475
|
+
cluster['volumes'] << {
|
1476
|
+
"name" => m['source_volume'],
|
1477
|
+
"type" => "host"
|
1478
|
+
}
|
1479
|
+
end
|
1480
|
+
}
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
if !created_generic_loggroup
|
1484
|
+
cluster["dependencies"] << { "type" => "log", "name" => logname }
|
1485
|
+
logdesc = {
|
1486
|
+
"name" => logname,
|
1487
|
+
"region" => cluster["region"],
|
1488
|
+
"cloud" => cluster["cloud"]
|
1489
|
+
}
|
1490
|
+
configurator.insertKitten(logdesc, "logs")
|
1491
|
+
|
1492
|
+
if !c['role']
|
1493
|
+
roledesc = {
|
1494
|
+
"name" => rolename,
|
1495
|
+
"cloud" => cluster["cloud"],
|
1496
|
+
"can_assume" => [
|
1497
|
+
{
|
1498
|
+
"entity_id" => "ecs-tasks.amazonaws.com",
|
1499
|
+
"entity_type" => "service"
|
1500
|
+
}
|
1501
|
+
],
|
1502
|
+
"policies" => [
|
1503
|
+
{
|
1504
|
+
"name" => "ECSTaskLogPerms",
|
1505
|
+
"permissions" => [
|
1506
|
+
"logs:CreateLogStream",
|
1507
|
+
"logs:DescribeLogGroups",
|
1508
|
+
"logs:DescribeLogStreams",
|
1509
|
+
"logs:PutLogEvents"
|
1510
|
+
],
|
1511
|
+
"import" => [
|
1512
|
+
""
|
1513
|
+
],
|
1514
|
+
"targets" => [
|
1515
|
+
{
|
1516
|
+
"type" => "log",
|
1517
|
+
"identifier" => logname
|
1518
|
+
}
|
1519
|
+
]
|
1520
|
+
}
|
1521
|
+
],
|
1522
|
+
"dependencies" => [{ "type" => "log", "name" => logname }]
|
1523
|
+
}
|
1524
|
+
configurator.insertKitten(roledesc, "roles")
|
1525
|
+
|
1526
|
+
cluster["dependencies"] << {
|
1527
|
+
"type" => "role",
|
1528
|
+
"name" => rolename
|
1529
|
+
}
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
created_generic_loggroup = true
|
1533
|
+
end
|
1534
|
+
c['role'] ||= { 'name' => rolename }
|
1535
|
+
end
|
1536
|
+
}
|
1537
|
+
end
|
1538
|
+
|
1539
|
+
if MU::Cloud::AWS.isGovCloud?(cluster["region"]) and cluster["flavor"] == "EKS"
|
1540
|
+
MU.log "AWS GovCloud does not support #{cluster["flavor"]} yet", MU::ERR
|
536
1541
|
ok = false
|
537
1542
|
end
|
538
1543
|
|
@@ -563,6 +1568,46 @@ module MU
|
|
563
1568
|
end
|
564
1569
|
end
|
565
1570
|
|
1571
|
+
if cluster["flavor"] == "Fargate" and !cluster['vpc']
|
1572
|
+
if MU.myVPC
|
1573
|
+
cluster["vpc"] = {
|
1574
|
+
"vpc_id" => MU.myVPC,
|
1575
|
+
"subnet_pref" => "all_private"
|
1576
|
+
}
|
1577
|
+
MU.log "Fargate cluster #{cluster['name']} did not specify a VPC, inserting into private subnets of #{MU.myVPC}", MU::NOTICE
|
1578
|
+
else
|
1579
|
+
MU.log "Fargate cluster #{cluster['name']} must specify a VPC", MU::ERR
|
1580
|
+
ok = false
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
end
|
1584
|
+
|
1585
|
+
cluster['ingress_rules'] ||= []
|
1586
|
+
if cluster['flavor'] == "ECS"
|
1587
|
+
cluster['ingress_rules'] << {
|
1588
|
+
"sgs" => ["server_pool#{cluster['name']}workers"],
|
1589
|
+
"port" => 443
|
1590
|
+
}
|
1591
|
+
end
|
1592
|
+
fwname = "container_cluster#{cluster['name']}"
|
1593
|
+
|
1594
|
+
acl = {
|
1595
|
+
"name" => fwname,
|
1596
|
+
"credentials" => cluster["credentials"],
|
1597
|
+
"rules" => cluster['ingress_rules'],
|
1598
|
+
"region" => cluster['region'],
|
1599
|
+
"optional_tags" => cluster['optional_tags']
|
1600
|
+
}
|
1601
|
+
acl["tags"] = cluster['tags'] if cluster['tags'] && !cluster['tags'].empty?
|
1602
|
+
acl["vpc"] = cluster['vpc'].dup if cluster['vpc']
|
1603
|
+
|
1604
|
+
ok = false if !configurator.insertKitten(acl, "firewall_rules")
|
1605
|
+
cluster["add_firewall_rules"] = [] if cluster["add_firewall_rules"].nil?
|
1606
|
+
cluster["add_firewall_rules"] << {"rule_name" => fwname}
|
1607
|
+
cluster["dependencies"] << {
|
1608
|
+
"name" => fwname,
|
1609
|
+
"type" => "firewall_rule",
|
1610
|
+
}
|
566
1611
|
|
567
1612
|
if ["ECS", "EKS"].include?(cluster["flavor"])
|
568
1613
|
|
@@ -574,6 +1619,7 @@ module MU
|
|
574
1619
|
"max_size" => cluster["instance_count"],
|
575
1620
|
"wait_for_nodes" => cluster["instance_count"],
|
576
1621
|
"ssh_user" => cluster["host_ssh_user"],
|
1622
|
+
"role_strip_path" => true,
|
577
1623
|
"basis" => {
|
578
1624
|
"launch_config" => {
|
579
1625
|
"name" => cluster["name"]+"workers",
|
@@ -594,7 +1640,8 @@ module MU
|
|
594
1640
|
worker_pool["vpc"] = cluster["vpc"].dup
|
595
1641
|
worker_pool["vpc"]["subnet_pref"] = cluster["instance_subnet_pref"]
|
596
1642
|
worker_pool["vpc"].delete("subnets")
|
597
|
-
|
1643
|
+
end
|
1644
|
+
|
598
1645
|
if cluster["host_image"]
|
599
1646
|
worker_pool["basis"]["launch_config"]["image_id"] = cluster["host_image"]
|
600
1647
|
end
|
@@ -628,32 +1675,9 @@ module MU
|
|
628
1675
|
"name" => cluster["name"]+"workers",
|
629
1676
|
"type" => "server_pool",
|
630
1677
|
}
|
631
|
-
|
632
|
-
cluster['ingress_rules'] ||= []
|
633
|
-
cluster['ingress_rules'] << {
|
634
|
-
"sgs" => ["server_pool#{cluster['name']}workers"],
|
635
|
-
"port" => 443
|
636
|
-
}
|
637
|
-
fwname = "container_cluster#{cluster['name']}"
|
638
|
-
|
639
|
-
acl = {
|
640
|
-
"name" => fwname,
|
641
|
-
"credentials" => cluster["credentials"],
|
642
|
-
"rules" => cluster['ingress_rules'],
|
643
|
-
"region" => cluster['region'],
|
644
|
-
"optional_tags" => cluster['optional_tags']
|
645
|
-
}
|
646
|
-
acl["tags"] = cluster['tags'] if cluster['tags'] && !cluster['tags'].empty?
|
647
|
-
acl["vpc"] = cluster['vpc'].dup if cluster['vpc']
|
648
|
-
|
649
|
-
ok = false if !configurator.insertKitten(acl, "firewall_rules")
|
650
|
-
cluster["add_firewall_rules"] = [] if cluster["add_firewall_rules"].nil?
|
651
|
-
cluster["add_firewall_rules"] << {"rule_name" => fwname}
|
652
|
-
cluster["dependencies"] << {
|
653
|
-
"name" => fwname,
|
654
|
-
"type" => "firewall_rule",
|
655
|
-
}
|
1678
|
+
end
|
656
1679
|
|
1680
|
+
if cluster["flavor"] == "EKS"
|
657
1681
|
role = {
|
658
1682
|
"name" => cluster["name"]+"controlplane",
|
659
1683
|
"credentials" => cluster["credentials"],
|