Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service and task definition setup #16

Merged
merged 32 commits into from
Oct 1, 2016
Merged

Conversation

apurvis
Copy link
Contributor

@apurvis apurvis commented Sep 26, 2016

https://lumoslabs.atlassian.net/browse/DE-11289

this is a proposal based on the script code in https://github.com/lumoslabs/events/pull/103 for how to add the option of setting up services and the initial task_definition from a broadside config that i was playing with a little; figure it might be easier to discuss having it in front of you

@matl33t @rfroetscher @albertrdixon

container_def = new_task_def[:container_definitions].select { |c| c[:name] == family }.first
container_def[:environment] = @deploy_config.env_vars
container_def[:image] = image_tag
container_def[:command] = @deploy_config.command
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

container_def was never being used; not sure if that was a bug or just leftover cruft lying around.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is actually modifying object data within new_task_def, which is used on L149 to generate a task definition revision. Admittedly not very clearly though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see it now... seems like the create_task_definition method stuff could all just live in update_task actually?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, create_task_definition wouldn't be needed - update_task basically grabs the latest task definition through create_new_task_revision, deletes some unneeded fields, injects in configurations into the container relevant to broadside, then makes the api call to generate a new revision from that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah so update_task could just create the first task definition if there wasn't one (and if @task_definition_config it was configured appropriately)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference between @command and @deploy_config.command?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@command is an instance variable belonging to https://github.com/lumoslabs/broadside/blob/master/lib/broadside/configuration/deploy.rb#L45

@deploy_config belongs to this class, basically a copy of the configuration object above (https://github.com/lumoslabs/broadside/blob/master/lib/broadside/deploy.rb#L8). This was done so that an instance of a deploy would not corrupt data in the original configuration object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i still don't really understand it (and a few other fields) is duplicated between the two objects? AFAICT the @command passed to AWS is always and every time based on the command in the broadside config?

resp = ecs_client.describe_services({cluster: config.ecs.cluster, services: [family]})
unless resp.failures.empty?
exception "Could not find ECS service '#{family}' in cluster '#{config.ecs.cluster}' !"
def all_results(method, key, args = {})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm a fan

@@ -22,14 +22,18 @@ class DeployConfig < ConfigStruct
:env_vars,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's actually a spec for this file, mind making some additions there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was actually hoping to add a lot of specs with the stub_responses part of the Aws gem but i wanted to get some buy in that this approach basically makes sense before doing that (hence the WIP in the title)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heh, that's a lot of work - i wouldn't worry about speccing ecs.rb for now, let's talk about that separately

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's really hard to jump into a project and change critical code without specs! i would feel like 1000% more confident about this PR if running rspec actually confirmed the right calls were made to AWS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you know that Aws::Client already provides stubs, right? https://docs.aws.amazon.com/sdkforruby/api/Aws/ClientStubs.html

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very true. i'll welcome whatever specs you want to supplement this PR with

unless resp.failures.empty?
exception "Could not find ECS service '#{family}' in cluster '#{config.ecs.cluster}' !"
def all_results(method, key, args = {})
page = ecs.send(method, args)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public_send, please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one of these days i'll remember that public_send exists before you remind me

@@ -326,6 +338,10 @@ def create_new_task_revision
task_def
end

def service_exists?
!ecs_client.describe_services({ cluster: config.ecs.cluster, services: [family] }).failures.empty?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the AWS services don't really return enumerables, they return weird Seahorse data structures with a .failures section.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just saying failures.any? is clearer than !failures.empty?. is that not possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i see; yeah that makes more sense.

name: name,
command: @command,
cpu: 1,
essential: true,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i almost want these values to be in a default constant that get merged with the name and command stuff

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought about that. you can't have a "constant" really because a name, command, family are all dynamic. so i thought about making building the config a separate method but really having a separate method is almost the same as having a method with one line call that builds the aws definition hash.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, you could:

DEFAULT_CONTAINER_DEFINITION = {
  cpu: 1,
  essential: true,
  memory: 1000
}

...

ecs.register_task_definition(
  {
    container_definitions: [
      {
        name: name,
        command: @command
      }.merge(DEFAULT_CONTAINER_DEFINITION)
    ],
    family: name
  }.deep_merge(options)
)

but not sure that's much clearer

@rfroetscher
Copy link

looks pretty solid in general

DE-11289 #time 30m
DE-11289 #time 15m
DE-11289 #time 10m
DE-11289 #time 15m
DE-11289 #time 30m
DE-11289 #time 5m
DE-11289 #time 20m
DE-11289 #time 10m
DE-11289 #time 45m
DE-11289 #time 5m
@apurvis
Copy link
Contributor Author

apurvis commented Sep 27, 2016

ok @matl33t:

  1. the new method is called bootstrap. i didn't yet implement the stuff in the actual broadside commands because you said you could help with getting it runnable
  2. there's a couple of specs now that actually stub against AWS stubs. i'm not going to fill out all the specs for all the existing functionality but i do think that would be incredibly valuable.
  3. the big blocker in terms of getting specs to cover the whole deploy is figuring out how to spec the Waiter response.

look it over carefully but i think this should be basically ready.

DE-11289 #time 5m
@apurvis
Copy link
Contributor Author

apurvis commented Sep 27, 2016

@matl33t the easiest thing to do might be for you to just make a new branch off this one and add the bootstrap command on a PR merged into this branch; then we can merge it all at once into v1.1

also travis is having issues because there's no configured key set for AWS. not sure what the preferred approach is there but probably setting an env variable would do the trick.

)

TARGET_ATTRIBUTE_VALIDATIONS = {
scale: ->(target_attribute) { validate_types([Fixnum], target_attribute) },
env_file: ->(target_attribute) { validate_types([String], target_attribute) },
command: ->(target_attribute) { validate_types([Array, NilClass], target_attribute) },
predeploy_commands: ->(target_attribute) { validate_predeploy(target_attribute) }
predeploy_commands: ->(target_attribute) { validate_predeploy(target_attribute) },
service_config: ->(target_attribute) { validate_types([Hash, NilClass], target_attribute) },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

service and task_definition are ECS constructs, so maybe best placed into the ecs_config file instead

Copy link
Contributor Author

@apurvis apurvis Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except that different targets in a file could/will have different service and task configs... if it's in the ECSConfig won't they have to be unified?

Copy link
Contributor Author

@apurvis apurvis Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be honest i don't really see much point to the EcsConfig class at all and sort of think all that stuff belongs in the targets.

right now it would be impossible to have a single broadside config file that can deploy to two clusters, as well has having this random extra special config class instead of just a key/value in the targets... and for what?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

originally intended to support things outside of ECS, but whatever. let's just go with this and ditch that config file. This will have to be a major version bump since that will be a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't want to be refactoring at the same time as i'm add features but let's add it to the list of things to be refactored

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm just going to leave it for now... the breaking change is too much at the moment / i don't feel like repairing configs in a ton of projects.

@@ -92,7 +92,7 @@ def image_tag
end

def gen_ssh_cmd(ip, options = { tty: false })
opts = @deploy_config.ssh
opts = @deploy_config.ssh || {}
cmd = 'ssh -o StrictHostKeyChecking=no'
cmd << ' -t -t' if options[:tty]
cmd << " -i #{opts[:keyfile]}" if opts[:keyfile]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bugfix: if opts[:keyfile] fails unless opts is an array

[email protected] and others added 3 commits September 27, 2016 23:46
* Debug

* debug

* debug

* debug

* Fix bug with container_definition merge

* debug

* task_definition must come first

* Simplify
@apurvis
Copy link
Contributor Author

apurvis commented Sep 28, 2016

i added the command and i tested this - deploys still work, so i don't think i broke anything. i was also able to run deploy status.

i fully tested bundle exec broadside bootstrap --target test_target with a config and it seemed to work.

scale: 1,
command: ['java', '-cp', '*:.', 'path.to.MyClass'],
env_file: '.env.production',
service: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the one thing that kind of sucks here is that if you want to just use the defaults to bootstrap, you still have to specify service: {} and task_definition: {} (without the keys, and without their values being a hash, you won't be able to trigger a bootstrap).

maybe bootstrap should just use the defaults if the user doesn't specify any extra config and not check for the existence of service: and task_definition?

not the end of the world just kind of blah

@apurvis
Copy link
Contributor Author

apurvis commented Sep 29, 2016

Just FYI this feature is done and ready to be reviewed. we're about to start churning out a lot more eventS ETLs quickly so i'd like to lineup the release of this feature in the reasonably but not urgently near future.

@rfroetscher
Copy link

this sounds fine to me but @matl33t has the final word

👍

@apurvis
Copy link
Contributor Author

apurvis commented Sep 29, 2016

i was just able to use the original script version of this to deploy a new service in about 2 minutes from scratch, which was awesome compared to the old way.

@@ -166,6 +171,8 @@ on_error do |exception|
error exception.message, "\nRun your last command with --help for more information."
false
else
pp exception.message
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding the env var GLI_DEBUG=true will automatically print out the stacktrace, so don't need this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to know; i was lost trying to see the stacktrace.

return 'predeploy_commands must be an array' unless target_attribute.is_a?(Array)

target_attribute.each do |command|
return "predeploy_command '#{command}' must be an array" unless command.is_a?(Array)
messages = target_attribute.select { |cmd| !cmd.is_a?(Array) }.map do |command|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about .reject { |cmd| cmd.is_a?(Array) ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

@@ -6,33 +7,58 @@

module Broadside
class EcsDeploy < Deploy

DEFAULT_DESIRED_COUNT = 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts about putting these constants into the deployconfig class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's cool with me if it makes sense to have it there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait you mean in EcsConfig? the constants are referenced here but that sort of makes sense... not sure it's easier to read if it's in another file

end
end
end

def bootstrap
unless get_latest_task_def_id
# TODO right now this creates a useless first revision and then update_task_revision will create a 2nd one
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm fine with this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it's minor; maybe it's not a TODO it's just a comment

@@ -1,3 +1,3 @@
module Broadside
VERSION = '1.0.3'
VERSION = '1.1.0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll handle the version bump in a separate PR (need to update changelog and whatnot)

@matl33t
Copy link
Contributor

matl33t commented Sep 29, 2016

just a couple minor comments and i think we are ready to 🚢 !

@apurvis
Copy link
Contributor Author

apurvis commented Sep 29, 2016

i tested all the commands except the beacon deploy... bootstrap works. @matl33t feel free to merge and release if you feel good about the long deploy

@apurvis
Copy link
Contributor Author

apurvis commented Sep 30, 2016

i just used this to actually bootstrap and deploy a new eventS ETL; worked great.

@apurvis apurvis merged commit 8665f42 into master Oct 1, 2016
@apurvis apurvis deleted the service_and_task_definitions branch October 19, 2016 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants