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

feat(ec2/asg): CloudFormation-init support #8788

Closed
wants to merge 53 commits into from
Closed

Conversation

rix0rrr
Copy link
Contributor

@rix0rrr rix0rrr commented Jun 29, 2020

Add CloudFormation-init support.

The CloudFormation-init metadata is encapsulated in a CloudFormationInit object, and using it automatically renders the UserData to apply it and send a signal to the appropriate CloudFormation resource and adds the permissions required to use cfn-init, cfn-signal and any S3 files/assets to the instance role.

On an Instance, using CloudFormation-init automatically adds a ResourceSignal with a default timeout to the instance.

On an AutoScalingGroup, using CloudFormation-init requires that you also specify a Signal configuration to indicate how much of the fleet you want to be up and running before continuing deployment.

Also on AutoScalingGroup, the half-baked understanding of resource signals and update policies has been replaced with a stronger, DRY object modeling.

Fixes #777


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@rix0rrr rix0rrr marked this pull request as draft June 29, 2020 15:43
@rix0rrr rix0rrr changed the title feat(ec2/asg): CloudFormation-Init support feat(ec2/asg): CloudFormation-init support Jun 29, 2020
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Jun 29, 2020
packages/@aws-cdk/aws-ec2/lib/cfn-init.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-ec2/lib/cfn-init.ts Outdated Show resolved Hide resolved
*
* You must escape the string appropriately.
*/
public static shellCommand(shellCommand: string, options: InitCommandOptions = {}): InitCommand {
Copy link
Contributor

Choose a reason for hiding this comment

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

~ Should we stick with the from* convention for the factories? So fromShellCommand here?

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, that's a good question. It would be more consistent with the rest.

/**
* Configuration to apply to an EC2 instance on startup
*/
export abstract class CloudFormationInit {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thoughts on CfnInit (and CfnInitElement, etc.) throughout? Or does that imply auto-generated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Somewhat. It definitely implies something that's operating at the template level. I'm in doubt whether this is that.

In any case, I don't hate it. Generally not too happy with our names atm.

packages/@aws-cdk/aws-ec2/lib/cfn-init.ts Outdated Show resolved Hide resolved
/**
* Map a user name to a user id
*/
public static fromName(userName: string, uid: number, groups?: string[], homeDir?: string): InitUser {
Copy link
Contributor

Choose a reason for hiding this comment

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

Both this and the group are a little awkward as the factory methods, although it's mostly in the naming. I do generally agree with keeping all factory methods though for consistency. Only alternative I can think of is "fromAttributes" or "fromProperties" and then having an InitUserOptions/Attributes/Props struct with the name/uid/groups/etc.

/**
* Download from a URL at instance startup time
*/
public static fromUrl(targetFileName: string, url: string, options: InitFileOptions = {}): InitFile {
Copy link
Contributor

Choose a reason for hiding this comment

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

How would this be extended to support auth? A new fromUrlWithAuth, or creating a InitFileUrlOptions that extends InitFileOptions and includes the auth params?

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 thinking just a new factory.

/**
* A package to be installed during cfn-init time
*/
export class InitPackage extends InitElement {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this one of those examples where we'd want to provide an extension point for when a new repo type is added? Something like:

public static custom(repositoryName: string, packageName: string, ...versions: string[]): InitPackage

I'm thinking of use cases like https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-cognito/lib/user-pool-client.ts#L96-L133; for example, if CloudFormation starts supporting npm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not opposed to it.

@rix0rrr rix0rrr marked this pull request as ready for review July 7, 2020 13:50
@njlynch njlynch requested a review from a team July 7, 2020 16:09
Copy link
Contributor

@iliapolo iliapolo left a comment

Choose a reason for hiding this comment

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

Lots of code, I'm just gonna drop some comments incrementally...

instances in the AutoScalingGroup. You can write files to it, run commands,
start services, etc. See the documentation of
[AWS::CloudFormation::Init](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html)
and the documentation of CDK's `aws-ec2` library for more information.
Copy link
Contributor

Choose a reason for hiding this comment

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

A direct link to the appropriate section would be nice :)

import * as fs from 'fs';

/**
* Defines whether this Init template will is being rendered for Windows or Linux-based systems.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Defines whether this Init template will is being rendered for Windows or Linux-based systems.
* Defines whether this Init template is being rendered for Windows or Linux-based systems.

* creation and updates. The UserData script needs to invoke `cfn-signal`
* with a success or failure code after it is done setting up the instance.
*/
public waitForResourceSignal(timeout: Duration) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this public? Does it make sense to call this outside the context of applyCloudFormationInit?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the use case there would be a customer adding/applying their own user-data that called cfn-signal independent of cfn-init.

Copy link
Contributor

Choose a reason for hiding this comment

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

Did we design for this scenario? Not sure its worth exposing this as a sort of addendum feature. Do we want to commit to this API? for example, maybe its better to accept a count argument so that users don'r have to split their timeout calculation and invoke this multiple times.

Copy link
Contributor

Choose a reason for hiding this comment

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

TBH, @rix0rrr was the main architect of this side. I could certainly see the argument that this (and applyCloudFormationInit) start out their lives as private, and we expose them publicly after we have some more feedback.

* - Add commands to the instance UserData to run `cfn-init` and `cfn-signal`.
* - Update the instance's CreationPolicy to wait for the `cfn-signal` commands.
*/
public applyCloudFormationInit(init: CloudFormationInit, options: ApplyCloudFormationInitOptions = {}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this overrides a previously configured init, it kinda feels like it should only happen at instance creation time. Any specific reason its exposed post creation as well? Also, I might be missing something here, but wouldn't calling this twice actually produce a single cfn-signal call, while CloudFormation will be waiting for 2 signals per the creation policy?

Side node: If we do keep this, maybe just instance.init()?

Copy link
Contributor

@nija-at nija-at left a comment

Choose a reason for hiding this comment

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

Sorry to be a bummer, but this PR is hard to review effectively. It's massive!
How can this be broken up into reviewable chunks?

@njlynch
Copy link
Contributor

njlynch commented Jul 14, 2020

Sorry to be a bummer, but this PR is hard to review effectively. It's massive!
How can this be broken up into reviewable chunks?

@nija-at - Definitely agreed. Initially, since we paired on the whole thing and had reviewed each others' code, we were going to declare it as already reviewed; we later both agreed having a third set of eyes -- primarily for the naming and public APIs -- made sense. With that in mind, do you think it's reasonable to look at the API surface area and be able to do an effective review, or is this going to need to be split somehow to get through?

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 4966d40
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

njlynch added a commit that referenced this pull request Jul 14, 2020
This is a prerequisite of #8788, where it will be used to add CfnInit metadata.
njlynch added a commit that referenced this pull request Jul 14, 2020
This is a prerequisite of #8788, where it will be used to add CfnInit metadata.
njlynch added a commit that referenced this pull request Jul 14, 2020
> NOTE: This is a reduced version of #8788, which is the full
CloudFormation-init support. This has been reduced down to only support
instances (not ASGs), and to only support the InitCommand and InitService init
elements, rather than the full set. This is to reduce the PR size and encourage
a more thorough review. A follow-up review will add the remainder of the
elements and auto-scaling group support.

Add CloudFormation-init support.

The CloudFormation-init metadata is encapsulated in a CloudFormationInit object,
and using it automatically renders the UserData to apply it and send a signal to
the appropriate CloudFormation resource and adds the permissions required to use
cfn-init, cfn-signal and any S3 files/assets to the instance role.

On an Instance, using CloudFormation-init automatically adds a ResourceSignal
with a default timeout to the instance.
@njlynch
Copy link
Contributor

njlynch commented Jul 14, 2020

NOTE: At @nija-at 's request, I have halved the size of this PR by breaking out a few supporting changes into separate PRs, and posting a PR with just two Init Elements and only instance (not ASG) support. #9065 is about half the size of this PR and should be a bit more manageable to review. Keeping this PR open as-is for now, as it contains all the comments and history; once #9065 is approved, I'll update this PR with just the second half of the functionality.

mergify bot pushed a commit that referenced this pull request Jul 15, 2020
Exposes a mechanism to add metadata to CloudFormation resources by key name. This can be used for internal usage like setting the CDK metadata path, or for other metadata like CfnInit use cases.

This is a prerequisite of #8788, where it will be used to add CfnInit metadata.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
curtiseppel pushed a commit to curtiseppel/aws-cdk that referenced this pull request Aug 11, 2020
Exposes a mechanism to add metadata to CloudFormation resources by key name. This can be used for internal usage like setting the CDK metadata path, or for other metadata like CfnInit use cases.

This is a prerequisite of aws#8788, where it will be used to add CfnInit metadata.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
mergify bot pushed a commit that referenced this pull request Aug 13, 2020
> NOTE: This is a reduced version of #8788, which is the full
CloudFormation-init support. This has been reduced down to only support
instances (not ASGs), and to only support the InitCommand and InitService init
elements, rather than the full set. This is to reduce the PR size and encourage
a more thorough review. A follow-up review will add the remainder of the
elements and auto-scaling group support.

Add CloudFormation-init support.

The CloudFormation-init metadata is encapsulated in a CloudFormationInit object,
and using it automatically renders the UserData to apply it and send a signal to
the appropriate CloudFormation resource and adds the permissions required to use
cfn-init, cfn-signal and any S3 files/assets to the instance role.

On an Instance, using CloudFormation-init automatically adds a ResourceSignal
with a default timeout to the instance.

Note this currently also includes the same changes as #9063, as this relies on it. #9063 can be independently shipped.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
njlynch added a commit that referenced this pull request Aug 13, 2020
…nd groups

Cloudformation Init (cfn-init) support was introduced in #9065 with a minimal
set of support for various init element types. This PR is a continuation of
that support (based on the original #8788), and adds the remaining init element
types: files, packages, sources, users, and groups.

With this PR, CloudFormation init support for EC2 instances is complete. A final
PR will be submitted to extend this support to auto-scaling groups (again, based
on the original work done in #8788).
mergify bot pushed a commit that referenced this pull request Aug 17, 2020
… groups (#9664)

Cloudformation Init (cfn-init) support was introduced in #9065 with a minimal
set of support for various init element types. This PR is a continuation of
that support (based on the original #8788), and adds the remaining init element
types: files, packages, sources, users, and groups.

With this PR, CloudFormation init support for EC2 instances is complete. A final
PR will be submitted to extend this support to auto-scaling groups (again, based
on the original work done in #8788).

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@njlynch
Copy link
Contributor

njlynch commented Aug 24, 2020

Closing this out in favor of the other PRs that have since been opened. #9674 remains as the last piece of this to be merged.

@njlynch njlynch closed this Aug 24, 2020
@rix0rrr rix0rrr deleted the huijbers/cfn-init branch October 23, 2020 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for AWS::CloudFormation::Init
5 participants