Need Help. Trying to create Windows EC2 with Java installed using below template. However, I get 403 when artifacts are being copied from my Private S3 Bucket. Role to Access S3 and EC2 instance are created fine. What could be the issue?
Resources:
EC2S3Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: 'sts:AssumeRole'
Principal:
Service: ec2.amazonaws.com
Effect: Allow
Sid: ''
Policies:
- PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- 's3:GetObject'
Resource: "arn:aws:s3:::windowsartifcats"
Effect: Allow
PolicyName: AuthenticatedS3GetObjects
EC2S3InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref EC2S3Role
JavaSeleniumEC2Instance:
Type: "AWS::EC2::Instance"
Properties:
KeyName: "windowskeypair"
ImageId: ami-0229f7666f517b31e
InstanceType: "t2.micro"
SecurityGroups:
- "windows"
IamInstanceProfile: !Ref EC2S3InstanceProfile
UserData:
Fn::Base64: !Join
- ""
- - "<script> cfn-init.exe -v -s "
- !Ref "AWS::StackId"
- " -r JavaSeleniumEC2Instance --region "
- !Ref "AWS::Region"
- "</script>"
Metadata:
AWS::CloudFormation::Authentication:
S3AccessCreds:
Type: "S3"
buckets:
- "windowsartifcats"
roleName:
!Ref EC2S3Role
AWS::CloudFormation::Init:
config:
files:
"c:\\Install-Java-JDK.ps1":
content: !Join
- ''
- - 'Set-Location C:\\;'
- '.\\jdk-11.0.9_windows-x64_bin.exe /s'
"c:\\jdk-11.0.9_windows-x64_bin.exe":
source: "https://windowsartifcats.s3.us-east-1.amazonaws.com/jdk-11.0.9_windows-x64_bin.exe"
"c:\\IEDriverServer.exe":
source: "https://windowsartifcats.s3.us-east-1.amazonaws.com/IEDriverServer.exe"
commands:
"JavaInstall":
command: "powershell.exe -ExecutionPolicy RemoteSigned -Command c:\\Install-Java-JDK.ps1"
waitAfterCompletion: '180'
Logs from C:\cfn\log.cfn-init:
2020-12-21 06:28:15,608 [ERROR] Error encountered during build of config: Failed to retrieve https://windowsartifcats.s3.us-east-1.amazonaws.com/IEDriverServer.exe: HTTP Error 403
Your EC2S3Role is incorrect. The following
Resource: "arn:aws:s3:::windowsartifcats"
refers to bucket only, not its objects. To be able to download objects in your bucket, it should be:
Resource: "arn:aws:s3:::windowsartifcats/*"
Related
I'm trying to write a template that configures the whole ecs fargate server and its code pipeline.
There is no problem in all other configurations, but the image is empty because it is right after creating the ecr in cloudformation, and the create ecs service refers to the empty image and the process does not end.
So I want to push the server image to ecr with code build and then ecs service create to work, but I don't know how.
Could it be possible to trigger code build or code pipeline inside cloudformation?
If not, is there any way to do docker build & push?
Yes it can be done, I used it before to perform jobs like a database restore as part of stack creation (don't ask). What you need:
A custom resource lambda which kicks off the codebuild job. It will receive the codebuild project to start via a property passed to it in the cloudformation resource definition (assuming that the codebuild is determined at deploy time; else feel free to make the lambda know about which codebuild to run in whatever way makes the most sense to you).
The endpoint to call when the custom resource is complete. A custom resource will stay in CREATE_IN_PROGRESS state until the endpoint is called. It's been a while since I've used custom resources so don't remember where it comes from but I think it's found in the event that the custom resource lambda is invoked with.
Your codebuild job needs that endpoint and needs to be able to send a (GET or POST?) request to it, on both success and failure cases (you pass different params signifying success or failure).
So the overall sequence of steps is:
Define/reference the custom resource in your template, passing in whatever properties the lambda needs.
Deploy stack, custom resource lambda is invoked.
The custom resource status goes into CREATE_IN_PROGRESS
Lambda kicks off codebuild, passing in custom resource endpoint as a param or env var, and returns.
Codebuild starts doing its work.
Until the endpoint is invoked, the custom resource will remain as CREATE_IN_PROGRESS, and the stack create/update process will wait for it, even if it takes hours.
When codebuild has finished its work, it uses curl or similar to invoke that endpoint to signal it's complete.
Custom resource status goes to CREATE_COMPLETE (assuming you've invoked the endpoint with params saying it was successful).
Stack creation completes (or moves on to any resources that were dependent on the custom resource).
Yes, it is possible to trigger the Fargate deployment after pushing the image rather than when the CloudFormation template is run.
The trick is to set the DesiredCount property of AWS::ECS::Service to zero:
Service:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref Cluster
DesiredCount: 0
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
SecurityGroups:
- !Ref SecG
Subnets: !Ref Subs
ServiceName: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
TaskDefinition: !Ref TaskDefinition
That said, you can also choose to create a repo with an initial commit that will trigger the build as soon as the template is done executing. This requires you to upload the zipped source code to an S3 bucket and configure the CodeCommit repository like so:
Repo:
Type: AWS::CodeCommit::Repository
Properties:
Code:
BranchName: main
S3:
Bucket: some-bucket
Key: code.zip
RepositoryName: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
RepositoryDescription: Repository
Triggers:
- Name: Trigger
CustomData: The Code Repository
DestinationArn: !Ref Topic
Branches:
- main
Events: [all]
Note that the some-bucket S3 bucket needs to contain the zipped .Dockerfile and any source code without any .git directory included.
You can see my implementation of this system and the rest of the stack below:
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation Stack to Trigger CodeBuild via CodePipeline
Parameters:
SecG:
Description: Single security group
Type: AWS::EC2::SecurityGroup::Id
Subs:
Description: Comma separated subnet IDs
Type: List<AWS::EC2::Subnet::Id>
ImagesFile:
Type: String
Default: images.json
Resources:
ArtifactBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: UseWithCodeDeploy
Value: true
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub 'ssm-${AWS::Region}-${AWS::StackName}'
PolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Action:
- ssm:GetParameters
- secretsmanager:GetSecretValue
Resource: '*'
- PolicyName: !Sub 'logs-${AWS::Region}-${AWS::StackName}'
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
- PolicyName: !Sub 'ecr-${AWS::Region}-${AWS::StackName}'
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:CompleteLayerUpload
- ecr:GetAuthorizationToken
- ecr:InitiateLayerUpload
- ecr:PutImage
- ecr:UploadLayerPart
- lightsail:*
Resource: '*'
- PolicyName: !Sub bkt-${ArtifactBucket}-${AWS::Region}
PolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
- s3:ListBucketVersions
- s3:GetBucketVersioning
Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}
- arn:aws:s3:::some-bucket
- PolicyName: !Sub obj-${ArtifactBucket}-${AWS::Region}
PolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:GetObjectAcl
- s3:PutObjectAcl
- s3:GetObjectTagging
- s3:PutObjectTagging
- s3:GetObjectVersion
- s3:GetObjectVersionAcl
- s3:PutObjectVersionAcl
Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}/*
- arn:aws:s3:::some-bucket/*
CodeDeployServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Sid: '1'
Effect: Allow
Principal:
Service:
- codedeploy.us-east-1.amazonaws.com
- codedeploy.eu-west-1.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS
- arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole
- arn:aws:iam::aws:policy/service-role/AWSCodeDeployRoleForLambda
CodeDeployRolePolicies:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub 'CDPolicy-${AWS::Region}-${AWS::StackName}'
PolicyDocument:
Statement:
- Effect: Allow
Resource:
- '*'
Action:
- ec2:Describe*
- Effect: Allow
Resource:
- '*'
Action:
- autoscaling:CompleteLifecycleAction
- autoscaling:DeleteLifecycleHook
- autoscaling:DescribeLifecycleHooks
- autoscaling:DescribeAutoScalingGroups
- autoscaling:PutLifecycleHook
- autoscaling:RecordLifecycleActionHeartbeat
Roles:
- !Ref CodeDeployServiceRole
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub 'root-${AWS::Region}-${AWS::StackName}'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Resource:
- !Sub 'arn:aws:s3:::${ArtifactBucket}/*'
- !Sub 'arn:aws:s3:::${ArtifactBucket}'
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- Resource: "*"
Effect: Allow
Action:
- ecs:*
- Resource: "*"
Effect: Allow
Action:
- iam:PassRole
Condition:
StringLike:
iam:PassedToService:
- ecs-tasks.amazonaws.com
- Resource: !GetAtt Build.Arn
Effect: Allow
Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
- codebuild:BatchGetBuildBatches
- codebuild:StartBuildBatch
- Resource: !GetAtt Repo.Arn
Effect: Allow
Action:
- codecommit:CancelUploadArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetRepository
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
AmazonCloudWatchEventRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
-
PolicyName: cwe-pipeline-execution
PolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Action: codepipeline:StartPipelineExecution
Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
AmazonCloudWatchEventRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- aws.codecommit
detail-type:
- CodeCommit Repository State Change
resources:
- !GetAtt Repo.Arn
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- main
Targets:
-
Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
RoleArn: !GetAtt AmazonCloudWatchEventRole.Arn
Id: codepipeline-Pipeline
Topic:
Type: AWS::SNS::Topic
Properties:
Subscription:
- Endpoint: user#example.com
Protocol: email
TopicPolicy:
Type: AWS::SNS::TopicPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
-
Sid: AllowPublish
Effect: Allow
Principal:
Service:
- 'codestar-notifications.amazonaws.com'
Action:
- 'SNS:Publish'
Resource:
- !Ref Topic
Topics:
- !Ref Topic
Repo:
Type: AWS::CodeCommit::Repository
Properties:
Code:
BranchName: main
S3:
Bucket: some-bucket
Key: code.zip
RepositoryName: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
RepositoryDescription: Repository
Triggers:
- Name: Trigger
CustomData: The Code Repository
DestinationArn: !Ref Topic
Branches:
- main
Events: [all]
RepoUser:
Type: AWS::IAM::User
Properties:
Path: '/'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSCodeCommitPowerUser
RepoUserKey:
Type: AWS::IAM::AccessKey
Properties:
UserName:
!Ref RepoUser
Registry:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
RepositoryPolicyText:
Version: '2012-10-17'
Statement:
- Sid: AllowPushPull
Effect: Allow
Principal:
AWS:
- !GetAtt CodeDeployServiceRole.Arn
Action:
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:BatchCheckLayerAvailability
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
Build:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: !Sub |
version: 0.2
phases:
pre_build:
commands:
- echo "[`date`] PRE_BUILD"
- echo "Logging in to Amazon ECR..."
- aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$REGION.amazonaws.com
- IMAGE_URI="$ACCOUNT.dkr.ecr.$REGION.amazonaws.com/$REPO:$TAG"
build:
commands:
- echo "[`date`] BUILD"
- echo "Building Docker Image..."
- docker build -t $REPO:$TAG .
- docker tag $REPO:$TAG $IMAGE_URI
post_build:
commands:
- echo "[`date`] POST_BUILD"
- echo "Pushing Docker Image..."
- docker push $IMAGE_URI
- echo Writing image definitions file...
- printf '[{"name":"svc","imageUri":"%s"}]' $IMAGE_URI > $FILE
artifacts:
files: $FILE
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:6.0
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: REGION
Type: PLAINTEXT
Value: !Ref AWS::Region
- Name: ACCOUNT
Type: PLAINTEXT
Value: !Ref AWS::AccountId
- Name: TAG
Type: PLAINTEXT
Value: latest
- Name: REPO
Type: PLAINTEXT
Value: !Ref Registry
- Name: FILE
Type: PLAINTEXT
Value: !Ref ImagesFile
PrivilegedMode: true
Name: !Ref AWS::StackName
ServiceRole: !GetAtt CodeBuildServiceRole.Arn
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: Site
ActionTypeId:
Category: Source
Owner: AWS
Version: '1'
Provider: CodeCommit
Configuration:
RepositoryName: !GetAtt Repo.Name
BranchName: main
PollForSourceChanges: 'false'
InputArtifacts: []
OutputArtifacts:
- Name: SourceArtifact
RunOrder: 1
- Name: Build
Actions:
- Name: Docker
ActionTypeId:
Category: Build
Owner: AWS
Version: '1'
Provider: CodeBuild
Configuration:
ProjectName: !Ref Build
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
RunOrder: 1
- Name: Deploy
Actions:
- Name: Fargate
ActionTypeId:
Category: Deploy
Owner: AWS
Version: '1'
Provider: ECS
Configuration:
ClusterName: !Ref Cluster
FileName: !Ref ImagesFile
ServiceName: !GetAtt Service.Name
InputArtifacts:
- Name: BuildArtifact
RunOrder: 1
Cluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
FargateTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
TaskRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ecs-tasks.amazonaws.com
Action:
- sts:AssumeRole
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
ContainerDefinitions:
-
Name: svc
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Registry}:latest
PortMappings:
- ContainerPort: 8080
Cpu: 256
ExecutionRoleArn: !Ref FargateTaskExecutionRole
Memory: 512
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
RuntimePlatform:
CpuArchitecture: ARM64
OperatingSystemFamily: LINUX
TaskRoleArn: !Ref TaskRole
Service:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref Cluster
DesiredCount: 0
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
SecurityGroups:
- !Ref SecG
Subnets: !Ref Subs
ServiceName: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
TaskDefinition: !Ref TaskDefinition
Outputs:
ArtifactBucketName:
Description: ArtifactBucket S3 Bucket Name
Value: !Ref ArtifactBucket
ArtifactBucketSecureUrl:
Description: ArtifactBucket S3 Bucket Domain Name
Value: !Sub 'https://${ArtifactBucket.DomainName}'
ClusterName:
Value: !Ref Cluster
ServiceName:
Value: !GetAtt Service.Name
RepoUserAccessKey:
Description: S3 User Access Key
Value: !Ref RepoUserKey
RepoUserSecretKey:
Description: S3 User Secret Key
Value: !GetAtt RepoUserKey.SecretAccessKey
BuildArn:
Description: CodeBuild URL
Value: !GetAtt Build.Arn
RepoArn:
Description: CodeCommit Repository ARN
Value: !GetAtt Repo.Arn
RepoName:
Description: CodeCommit Repository NAme
Value: !GetAtt Repo.Name
RepoCloneUrlHttp:
Description: CodeCommit HTTP Clone URL
Value: !GetAtt Repo.CloneUrlHttp
RepoCloneUrlSsh:
Description: CodeCommit SSH Clone URL
Value: !GetAtt Repo.CloneUrlSsh
PipelineUrl:
Description: CodePipeline URL
Value: !Sub https://console.aws.amazon.com/codepipeline/home?region=${AWS::Region}#/view/${Pipeline}
RegistryUri:
Description: ECR Repository URI
Value: !GetAtt Registry.RepositoryUri
TopicArn:
Description: CodeCommit Notification SNS Topic ARN
Value: !Ref Topic
Hope this helps!
I have created the following Script where i am trying to use a custom image from ECR:
Adminrole:
Type: AWS::IAM::Role
Properties:
RoleName: !Join
- "."
- - !Ref "AWS::StackName"
- !Ref "AWS::Region"
- "codebuild"
Path: "/"
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [codebuild.amazonaws.com]
Version: '2012-10-17'
Policies:
- PolicyName: "root"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "*"
Resource: "*"
ProjectTerrafom:
Type: AWS::CodeBuild::Project
Properties:
Name: !Join
- "_"
- - !Ref "AWS::StackName"
- !Ref "AWS::Region"
- "ProjectTerrafom"
Description: Terraform deployment
ServiceRole: !Ref Adminrole
Artifacts:
Type: no_artifacts
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: 111111.cer.ecr.eu-center-1.amazonaws.com/my_terraform
Source:
Location: !Ref "FullPathRepoNameTerraform"
Type: GITHUB_ENTERPRISE
TimeoutInMinutes: 10
Tags:
- Key: Project
Value: "Run Terraform From CodeBuild"
When i run the CodeBuild i get the following error:
BUILD_CONTAINER_UNABLE_TO_PULL_IMAGE: Unable to pull customer's container image.
CannotPullContainerError: Error response from daemon: pull access denied for
111111.cer.ecr.eu-center-1.amazonaws.com/my_terraform, repository does
not exist or may `enter code here`require 'docker login': denied: User: CodeBuild
Is this a permission issue or are we not allowed to use the ECR images for CodeBuild?
i have solved the issue by adding following line:
ImagePullCredentialsType: SERVICE_ROLE
The full part looks like this:
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: 111111.cer.ecr.eu-center-1.amazonaws.com/my_terraform
ImagePullCredentialsType: SERVICE_ROLE
You can use ECR images on code build but you need verification according to that error. So I suggest you crosscheck your docker credentials, confirm login then cross-check image URL
I am trying to build an ECS cluster via CloudFormation. The subnets that the cluster instances will reside in are to be private. Additionally, I have created an image from an EC2 I built, and have verified the SSM agent, ECS agent, and cloud-init are installed and running. I have also added an inbound rule in my security group to allow HTTPS traffic from the subnet/CIDR of the private subnet with the endpoints as well.
I have added the following endpoints to my private subnet:
com.amazonaws.us-west-2.ssm
com.amazonaws.us-west-2.ssmmessages
com.amazonaws.us-west-2.ecs
com.amazonaws.us-west-2.ecs-agent
com.amazonaws.us-west-2.ecs-telemetry
com.amazonaws.us-west-2.cloudformation
Here is my CF template:
Description: >-
A stack for deploying containerized applications onto a cluster of EC2 hosts
using Elastic Container Service. This stack runs containers on hosts that are
in a public VPC subnet, and includes a public facing load balancer to register
the services in.
Parameters:
DesiredCapacity:
Type: Number
Default: '1'
Description: Number of EC2 instances to launch in your ECS cluster.
MaxSize:
Type: Number
Default: '2'
Description: Maximum number of EC2 instances that can be launched in your ECS cluster.
ECSAMI:
Description: AMI ID
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id
InstanceType:
Description: EC2 instance type
Type: String
Default: t2.micro
SecurityGroup:
Description: Select the Security Group to use for the ECS cluster hosts
Type: 'AWS::EC2::SecurityGroup::Id'
Subnets:
Description: Choose which subnets this ECS cluster should be deployed to
Type: 'List<AWS::EC2::Subnet::Id>'
VPC:
Description: Choose which VPC this ECS cluster should be deployed to
Type: 'AWS::EC2::VPC::Id'
Resources:
ECSCluster:
Type: 'AWS::ECS::Cluster'
Properties:
Clustername: change-name
ECSAutoScalingGroup:
Type: 'AWS::AutoScaling::AutoScalingGroup'
Properties:
AvailabilityZones:
- 'us-west-2a'
# VPCZoneIdentifier:
# - '
LaunchConfigurationName: !Ref ContainerInstances
MinSize: '1'
MaxSize: !Ref MaxSize
DesiredCapacity: !Ref DesiredCapacity
CreationPolicy:
ResourceSignal:
Count: 1
Timeout: PT5M
UpdatePolicy:
AutoScalingReplacingUpdate:
WillReplace: 'true'
ContainerInstances:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
ImageId: <custom ami>
SecurityGroups:
- !Ref SecurityGroup
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref EC2InstanceProfile
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash -xe
yum update -y
yum install -y aws-cfn-bootstrap
yum install cloud-init
echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region}
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agent
AutoscalingRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- application-autoscaling.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: service-autoscaling
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'application-autoscaling:*'
- 'cloudwatch:DescribeAlarms'
- 'cloudwatch:PutMetricAlarm'
- 'ecs:DescribeServices'
- 'ecs:UpdateService'
Resource: '*'
EC2InstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
Path: /
Roles:
- !Ref EC2Role
EC2Role:
Type: 'AWS::IAM::Role'
Properties:
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
- 'arn:aws:iam::aws:policy/AmazonECS_FullAccess'
- 'arn:aws:iam::aws:policy/CloudWatchFullAccess'
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: ecs-service
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'ecs:CreateCluster'
- 'ecs:DeregisterContainerInstance'
- 'ecs:DiscoverPollEndpoint'
- 'ecs:Poll'
- 'ecs:RegisterContainerInstance'
- 'ecs:StartTelemetrySession'
- 'ecs:Submit*'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- 'ecr:GetAuthorizationToken'
- 'ecr:BatchGetImage'
- 'ecr:GetDownloadUrlForLayer'
Resource: '*'
ECSRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ecs.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: ecs-service
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'ec2:AttachNetworkInterface'
- 'ec2:CreateNetworkInterface'
- 'ec2:CreateNetworkInterfacePermission'
- 'ec2:DeleteNetworkInterface'
- 'ec2:DeleteNetworkInterfacePermission'
- 'ec2:Describe*'
- 'ec2:DetachNetworkInterface'
- 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer'
- 'elasticloadbalancing:DeregisterTargets'
- 'elasticloadbalancing:Describe*'
- 'elasticloadbalancing:RegisterInstancesWithLoadBalancer'
- 'elasticloadbalancing:RegisterTargets'
Resource: '*'
Outputs:
ClusterName:
Description: The name of the ECS cluster
Value: !Ref ECSCluster
Export:
Name: !Join
- ':'
- - !Ref 'AWS::StackName'
- ClusterName
ECSRole:
Description: The ARN of the ECS role
Value: !GetAtt ECSRole.Arn
Export:
Name: !Join
- ':'
- - !Ref 'AWS::StackName'
- ECSRole
The issue is that at the final stage of creating the AutoScaling role, it hangs and errors out with a failure to receive a successful status code.
Error:
Received 0 SUCCESS signal(s) out of 1. Unable to satisfy 100% MinSuccessfulInstancesPercent requirement
Any help would be greatly appreciated, thank you for your time.
A possible reason could be the following line:
yum install cloud-init
Since you are missing -y, yum probably is waiting for a manual confirmation. The line should be replaced with
yum install -y cloud-init
Also, I'm not sure what is the meaning of:
ImageId: <custom ami>
since you are using SSM AMI parameter. Thus, natural procedure would be to use it:
ImageId: !Ref ECSAMI
There are a lot of options to do that and I don't know which is the best one. I tried in the beginning to do is as following:
ServiceInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !Ref AmiId, !Ref LatestOnescoutAmi ]
InstanceType: !Ref InstanceType
SubnetId: !ImportValue vpc-stack-PublicASubnet
SecurityGroupIds:
- !Ref ServiceSecurityGroup
KeyName: !Ref KeyName
UserData:
'Fn::Base64': !Sub |
#cloud-config
write_files:
- path: /etc/sysconfig/cloudformation
permissions: 0644
owner: root
content: |
STACK_NAME=${AWS::StackName}
AWS_REGION=${AWS::Region}
- path: /etc/datadog-agent/conf.d/mysql.d/conf.yaml
permissions: 0644
owner: dd-agent
content: |
init_config:
instances:
- server: some-db-host
user: some-admin
pass: some-password
port: 3306
tags:
- dbinstanceidentifier:someide
runcmd:
## enable datadog agent
- systemctl start datadog-agent
- systemctl start application.service
but then my /etc/datadog-agent/conf.d/mysql.d/conf.yaml grew and I have around 13 blocks and it's not good to put them hardcoded inside the template. Better to keep the template generic and pass the config file as a parameter.
But, according to this answer here, it's not possible to pass a file or content of file to cloud formation.
The way above is the simplest I see among other two options I can think about.
Store the config in SSM and then get it back at the ec2 start.
Create an Autoscaling and launch group which accepts the file path but it's more complex than I need:
LaunchConfig:
Type: AWS::AutoScaling::LaunchConfiguration
Metadata:
AWS::CloudFormation::Init:
configSets:
service_configuration:
- datadog_setup
datadog_setup:
files:
/etc/datadog-agent/conf.d/mysql.d/conf.yaml:
content: "#file://./config/conf-${Env}.yaml"
mode: "000644"
owner: "root"
group: "root"
commands:
start_datadog:
command: service datadog-agent start
Any idea how to do this in a simple, generic and secure way?
An example given would be appreciated.
Thanks in advance.
How I managed to do that with another way, I created S3 bucket and then created a role to my ec2 instance that can access this s3 bucket and can download files, then in my runcmd section, I downloaded this file.
ServiceInstance:
Type: "AWS::EC2::Instance"
Properties:
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref InstanceProfile
UserData:
'Fn::Base64': !Sub |
#cloud-config
write_files:
- path: /etc/sysconfig/cloudformation
permissions: 0644
owner: root
content: |
STACK_NAME=${AWS::StackName}
AWS_REGION=${AWS::Region}
runcmd:
- aws s3 cp s3://${ArtifactsBucketName}/dd-get-secrets.py /home/ec2-user/dd-get-secrets.py
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles: [!Ref IAMRole]
IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ec2.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: allow-downloading-dd-templates
PolicyDocument:
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:ListBucket
Resource: !Sub "arn:aws:s3:::${ArtifactsBucketName}/*"
How I passed parameters to create json file
DNACInstance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", Production]
InstanceType: t2.medium
SecurityGroupIds:
- !Ref SecurityGroupID
KeyName: !Ref Keypair
SubnetId: !Ref PrivateSubnet1ID
PrivateIpAddress: !Ref PrimaryIP
Tags:
- Key: Name
Value: !Sub '${EnvironmentName}_Production_Instance'
UserData:
'Fn::Base64': !Sub |
#cloud-config
write_files:
- content: |
{
"IPaddress": "${PrimaryIP}",
"SecondaryIPaddress": "${SecondaryIP}",
"netmask": "${Netmask}",
"gateway": "${Gateway}",
"dns_servers": "${Server}",
"https_proxy" : "${HttpProxy}",
"FQDN": "${FQDN}",
"ntp": ["169.254.169.123"]
}
path: /etc/instance.json```
I am writing a Cloudformation template with a single EC2 instance and an EBS volume. I attach the volume later on at some point when the machine is created using Powershell script. It works when I put wildcard '*' in policy statement resource however I want to limit the access to one instance and one ebs volume. With EBS volume it's easy I can just refer it in the template and it is created before the role but with instance the problem is that instance requires the role to be created first but also to be able to create the instance we need to create the role first. What's a good way of resolving this kind of circular dependency?
Here is my template:
Resources:
InstanceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: InstanceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: AttachVolume
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'ec2:AttachVolume'
Resource:
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':instance/*'
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':volume/'
- !Ref DataVolume
InstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
Roles:
- !Ref InstanceRole
InstanceProfileName: InstanceProfile
Instance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !Ref AMI
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref InstanceProfile
KeyName: ec2key
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeType: gp2
DeleteOnTermination: 'true'
VolumeSize: '30'
Tags:
- Key: Name
Value: MyInstance
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref SGId
UserData: !Base64
'Fn::Join':
- ''
- - |
<script>
- 'cfn-init.exe -v -c config -s '
- !Ref 'AWS::StackId'
- ' -r Instance'
- ' --region '
- !Ref 'AWS::Region'
- |+
- |
</script>
DataVolume:
Type: "AWS::EC2::Volume"
Properties:
AvailabilityZone: !GetAtt
- Instance
- AvailabilityZone
Size: "100"
Tags:
- Key: Name
Value: InstanceExtraVolume
In your particular example, you have the following dependency chain: InstanceRole -> DataVolume -> Instance -> InstanceProfile -> InstanceRole
In general, when your Role depends on your Resources and your Resources depend on your Role, this is where the AWS::IAM::Policy resource type is useful. This basically decouples the particular policy on the IAM Role from being resolved at the same time as the IAM Policy itself.
To do this, you would take your InstanceRole and split it into an InstanceRole and an InstanceRolePolicy
Resources:
InstanceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: InstanceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
InstanceRolePolicy:
Type: 'AWS::IAM::Policy'
Properties:
Roles:
- !Ref InstanceRole
PolicyName: AttachVolume
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'ec2:AttachVolume'
Resource:
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':instance/*'
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':volume/'
- !Ref DataVolume
With that, the InstanceRolePolicy depends on the InstanceRole and DataVolume, but the InstanceRole doesn't depend on anything, so the DataVolume -> Instance -> InstanceProfile -> InstanceRole chain can resolve.
One common solution to circular dependencies is to do this is multiple steps: create the stack with minimal resources, then modify the template and update the stack.
So, v1 of your template creates just the basic dependent resource and in v2, you modify the template to add the depending resource and modify the original dependent resource at the same time. Then do a stack update.
Also, see more ideas.