Skip to content

Commit 014c13a

Browse files
authored
feat(ec2): CloudFormation-init support (#9065)
> 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*
1 parent a48e95a commit 014c13a

12 files changed

+1726
-27
lines changed

packages/@aws-cdk/aws-ec2/README.md

+82-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
## Amazon EC2 Construct Library
2+
23
<!--BEGIN STABILITY BANNER-->
34
---
45

@@ -48,10 +49,9 @@ distinguishes three different subnet types:
4849
connected to from other instances in the same VPC. A default VPC configuration
4950
will not include isolated subnets,
5051

51-
5252
A default VPC configuration will create public and **private** subnets. However, if
53-
`natGateways:0` **and** `subnetConfiguration` is undefined, default VPC configuration
54-
will create public and **isolated** subnets. See [*Advanced Subnet Configuration*](#advanced-subnet-configuration)
53+
`natGateways:0` **and** `subnetConfiguration` is undefined, default VPC configuration
54+
will create public and **isolated** subnets. See [*Advanced Subnet Configuration*](#advanced-subnet-configuration)
5555
below for information on how to change the default subnet configuration.
5656

5757
Constructs using the VPC will "launch instances" (or more accurately, create
@@ -68,7 +68,6 @@ created by setting the `natGateways` property to a lower value (the default
6868
is one NAT gateway per availability zone). Be aware that this may have
6969
availability implications for your application.
7070

71-
7271
[Read more about
7372
subnets](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html).
7473

@@ -280,9 +279,9 @@ const igwId = vpc.internetGatewayId;
280279

281280
For a VPC with only `ISOLATED` subnets, this value will be undefined.
282281

283-
This is only supported for VPC's created in the stack - currently you're
282+
This is only supported for VPC's created in the stack - currently you're
284283
unable to get the ID for imported VPC's. To do that you'd have to specifically
285-
look up the Internet Gateway by name, which would require knowing the name
284+
look up the Internet Gateway by name, which would require knowing the name
286285
beforehand.
287286

288287
This can be useful for configuring routing using a combination of gateways:
@@ -501,7 +500,9 @@ examples of things you might want to use:
501500
> [Runtime Context](https://docs.aws.amazon.com/cdk/latest/guide/context.html) in the CDK
502501
> developer guide.
503502
504-
## VPN connections to a VPC
503+
## Special VPC configurations
504+
505+
### VPN connections to a VPC
505506

506507
Create your VPC with VPN connections by specifying the `vpnConnections` props (keys are construct `id`s):
507508

@@ -531,6 +532,7 @@ const vpc = new ec2.Vpc(this, 'MyVpc', {
531532
```
532533

533534
VPN connections can then be added:
535+
534536
```ts fixture=with-vpc
535537
vpc.addVpnConnection('Dynamic', {
536538
ip: '1.2.3.4'
@@ -554,14 +556,15 @@ const vpnConnection = vpc.addVpnConnection('Dynamic', {
554556
const state = vpnConnection.metricTunnelState();
555557
```
556558

557-
## VPC endpoints
559+
### VPC endpoints
560+
558561
A VPC endpoint enables you to privately connect your VPC to supported AWS services and VPC endpoint services powered by PrivateLink without requiring an internet gateway, NAT device, VPN connection, or AWS Direct Connect connection. Instances in your VPC do not require public IP addresses to communicate with resources in the service. Traffic between your VPC and the other service does not leave the Amazon network.
559562

560563
Endpoints are virtual devices. They are horizontally scaled, redundant, and highly available VPC components that allow communication between instances in your VPC and services without imposing availability risks or bandwidth constraints on your network traffic.
561564

562565
[example of setting up VPC endpoints](test/integ.vpc-endpoint.lit.ts)
563566

564-
By default, CDK will place a VPC endpoint in one subnet per AZ. If you wish to override the AZs CDK places the VPC endpoint in,
567+
By default, CDK will place a VPC endpoint in one subnet per AZ. If you wish to override the AZs CDK places the VPC endpoint in,
565568
use the `subnets` parameter as follows:
566569

567570
```ts
@@ -591,7 +594,8 @@ new InterfaceVpcEndpoint(stack, 'VPC Endpoint', {
591594
});
592595
```
593596

594-
### Security groups for interface VPC endpoints
597+
#### Security groups for interface VPC endpoints
598+
595599
By default, interface VPC endpoints create a new security group and traffic is **not**
596600
automatically allowed from the VPC CIDR.
597601

@@ -603,7 +607,8 @@ myEndpoint.connections.allowDefaultPortFromAnyIpv4();
603607

604608
Alternatively, existing security groups can be used by specifying the `securityGroups` prop.
605609

606-
## VPC endpoint services
610+
### VPC endpoint services
611+
607612
A VPC endpoint service enables you to expose a Network Load Balancer(s) as a provider service to consumers, who connect to your service over a VPC endpoint. You can restrict access to your service via whitelisted principals (anything that extends ArnPrincipal), and require that new connections be manually accepted.
608613

609614
```ts
@@ -614,17 +619,69 @@ new VpcEndpointService(this, 'EndpointService', {
614619
});
615620
```
616621

617-
## Bastion Hosts
622+
## Instances
623+
624+
You can use the `Instance` class to start up a single EC2 instance. For production setups, we recommend
625+
you use an `AutoScalingGroup` from the `aws-autoscaling` module instead, as AutoScalingGroups will take
626+
care of restarting your instance if it ever fails.
627+
628+
### Configuring Instances using CloudFormation Init (cfn-init)
629+
630+
CloudFormation Init allows you to configure your instances by writing files to them, installing software
631+
packages, starting services and running arbitrary commands. By default, if any of the instance setup
632+
commands throw an error, the deployment will fail and roll back to the previously known good state.
633+
The following documentation also applies to `AutoScalingGroup`s.
634+
635+
For the full set of capabilities of this system, see the documentation for
636+
[`AWS::CloudFormation::Init`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html).
637+
Here is an example of applying some configuration to an instance:
638+
639+
```ts
640+
new ec2.Instance(this, 'Instance', {
641+
init: ec2.CloudFormationInit.fromElements(
642+
ec2.InitCommand.shellCommand('/bin/true'),
643+
),
644+
initOptions: {
645+
// Optional, which configsets to activate (['default'] by default)
646+
configSets: ['default'],
647+
648+
// Optional, how long the installation is expected to take (5 minutes by default)
649+
timeout: Duration.minutes(30),
650+
},
651+
});
652+
```
653+
654+
You can have services restarted after the init process has made changes to the system.
655+
To do that, instantiate an `InitServiceRestartHandle` and pass it to the config elements
656+
that need to trigger the restart and the service itself. For example, the following
657+
config installs nginx through a custom script, and then
658+
restarts nginx so that it picks up the new config and files:
659+
660+
```ts
661+
const handle = new ec2.InitServiceRestartHandle();
662+
663+
ec2.CloudFormationInit.fromElements(
664+
ec2.InitCommand.shellCommand('/usr/bin/custom-nginx-install.sh', { serviceRestartHandles: [handle] }),
665+
ec2.InitService.enable('nginx', {
666+
serviceRestartHandle: handle,
667+
})
668+
);
669+
```
670+
671+
### Bastion Hosts
672+
618673
A bastion host functions as an instance used to access servers and resources in a VPC without open up the complete VPC on a network level.
619674
You can use bastion hosts using a standard SSH connection targetting port 22 on the host. As an alternative, you can connect the SSH connection
620675
feature of AWS Systems Manager Session Manager, which does not need an opened security group. (https://aws.amazon.com/about-aws/whats-new/2019/07/session-manager-launches-tunneling-support-for-ssh-and-scp/)
621676

622677
A default bastion host for use via SSM can be configured like:
678+
623679
```ts fixture=with-vpc
624680
const host = new ec2.BastionHostLinux(this, 'BastionHost', { vpc });
625681
```
626682

627683
If you want to connect from the internet using SSH, you need to place the host into a public subnet. You can then configure allowed source hosts.
684+
628685
```ts fixture=with-vpc
629686
const host = new ec2.BastionHostLinux(this, 'BastionHost', {
630687
vpc,
@@ -637,6 +694,7 @@ As there are no SSH public keys deployed on this machine, you need to use [EC2 I
637694
with the command `aws ec2-instance-connect send-ssh-public-key` to provide your SSH public key.
638695

639696
EBS volume for the bastion host can be encrypted like:
697+
640698
```ts
641699
const host = new ec2.BastionHostLinux(stack, 'BastionHost', {
642700
vpc,
@@ -649,7 +707,7 @@ EBS volume for the bastion host can be encrypted like:
649707
});
650708
```
651709

652-
## Block Devices
710+
### Block Devices
653711

654712
To add EBS block device mappings, specify the `blockDeviceMappings` property. The follow example sets the EBS-backed
655713
root device (`/dev/sda1`) size to 50 GiB, and adds another EBS-backed device mapped to `/dev/sdm` that is 100 GiB in
@@ -672,7 +730,7 @@ new ec2.Instance(this, 'Instance', {
672730

673731
```
674732

675-
## Volumes
733+
### Volumes
676734

677735
Whereas a `BlockDeviceVolume` is an EBS volume that is created and destroyed as part of the creation and destruction of a specific instance. A `Volume` is for when you want an EBS volume separate from any particular instance. A `Volume` is an EBS block device that can be attached to, or detached from, any instance at any time. Some types of `Volume`s can also be attached to multiple instances at the same time to allow you to have shared storage between those instances.
678736

@@ -696,7 +754,7 @@ const volume = new ec2.Volume(this, 'Volume', {
696754
volume.grantAttachVolume(role, [instance]);
697755
```
698756

699-
### Instances Attaching Volumes to Themselves
757+
#### Instances Attaching Volumes to Themselves
700758

701759
If you need to grant an instance the ability to attach/detach an EBS volume to/from itself, then using `grantAttachVolume` and `grantDetachVolume` as outlined above
702760
will lead to an unresolvable circular reference between the instance role and the instance. In this case, use `grantAttachVolumeByResourceTag` and `grantDetachVolumeByResourceTag` as follows:
@@ -713,7 +771,7 @@ const attachGrant = volume.grantAttachVolumeByResourceTag(instance.grantPrincipa
713771
const detachGrant = volume.grantDetachVolumeByResourceTag(instance.grantPrincipal, [instance]);
714772
```
715773

716-
### Attaching Volumes
774+
#### Attaching Volumes
717775

718776
The Amazon EC2 documentation for
719777
[Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AmazonEBS.html) and
@@ -741,7 +799,8 @@ instance.userData.addCommands(
741799
```
742800

743801
## VPC Flow Logs
744-
VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. Flow log data can be published to Amazon CloudWatch Logs and Amazon S3. After you've created a flow log, you can retrieve and view its data in the chosen destination. (https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html).
802+
803+
VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. Flow log data can be published to Amazon CloudWatch Logs and Amazon S3. After you've created a flow log, you can retrieve and view its data in the chosen destination. (<https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html>).
745804

746805
By default a flow log will be created with CloudWatch Logs as the destination.
747806

@@ -752,6 +811,7 @@ new ec2.FlowLog(this, 'FlowLog', {
752811
resourceType: ec2.FlowLogResourceType.fromVpc(vpc)
753812
})
754813
```
814+
755815
Or you can add a Flow Log to a VPC by using the addFlowLog method like this:
756816

757817
```ts
@@ -781,6 +841,7 @@ the log group. In the case of an S3 destination, it will create the S3 bucket.
781841
If you want to customize any of the destination resources you can provide your own as part of the `destination`.
782842

783843
*CloudWatch Logs*
844+
784845
```ts
785846
const logGroup = new logs.LogGroup(this, 'MyCustomLogGroup');
786847

@@ -795,6 +856,7 @@ new ec2.FlowLog(this, 'FlowLog', {
795856
```
796857

797858
*S3*
859+
798860
```ts
799861

800862
const bucket = new s3.Bucket(this, 'MyCustomBucket');
@@ -806,10 +868,12 @@ new ec2.FlowLog(this, 'FlowLog', {
806868
```
807869

808870
## User Data
871+
809872
User data enables you to run a script when your instances start up. In order to configure these scripts you can add commands directly to the script
810873
or you can use the UserData's convenience functions to aid in the creation of your script.
811874

812875
A user data could be configured to run a script found in an asset through the following:
876+
813877
```ts
814878
const asset = new Asset(this, 'Asset', {path: path.join(__dirname, 'configure.sh')});
815879
const instance = new ec2.Instance(this, 'Instance', {
@@ -846,4 +910,4 @@ const subnet = Subnet.fromSubnetAttributes(this, 'SubnetFromAttributes', {
846910

847911
// Supply only subnet id
848912
const subnet = Subnet.fromSubnetId(this, 'SubnetFromId', 's-1234');
849-
```
913+
```

0 commit comments

Comments
 (0)