cloud-mu 2.0.4 → 2.1.0beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"],
|