Having CloudFormation wait for the user data - bash

I have a cloudformation stack which creates a EC2 instance and install something in it using UserData. Cloudformation immediately reports CREATE_COMPLETE upon creation of the EC2 instance based on RedHat. But at this point, the instance is not really usable since the userdata takes about 40 min to finish. I read through documentation and even tried cfn-signal but I could not successfully execute it.
Can someone tell me how exactly it has to be done?
EC2Instance:
Type: AWS::EC2::Instance
Properties:
CreditSpecification:
CPUCredits: standard
IamInstanceProfile:
Fn::ImportValue:
!Sub ${InstanceProfileStackName}-instanceProfile
ImageId: !Ref ImageId
InstanceInitiatedShutdownBehavior: stop
InstanceType: !Ref InstanceType
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref DefaultSecurityGroup
- !Ref WebSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash
set -e
yum update -y
The above is the truncated part of my Cloudformation template.
UPDATE
I have the script which has the following line
source scl_source enable rh-python36
The default of my instance is python2.7 but I had to install my pip packages with python3.6. I am not sure if that was making the cfn-signal fail.
The script is going till the final step and seems to fail there. I am creating a recordset from the EC2 IP but Cloudformation still thinks the EC2 instance is not done and waiting till the timeout.
Screenshot of the instance snapshot
Log file end is as follows
Also my log file is named /var/log/cloud-init.log. There was no cloud-init-output.log in that directory.

You need two components:
CreationPolicy so that CFN waits for a SUCCESS signal from the instance.
cfn-signal helper script to perform the signalling action.
Thus your template could be modified as follows for Redhat 8:
EC2Instance:
Type: AWS::EC2::Instance
CreationPolicy: # <--- creation policy with timeout of 5 minutes
ResourceSignal:
Timeout: PT5M
Properties:
CreditSpecification:
CPUCredits: standard
IamInstanceProfile:
Fn::ImportValue:
!Sub ${InstanceProfileStackName}-instanceProfile
ImageId: !Ref ImageId
InstanceInitiatedShutdownBehavior: stop
InstanceType: !Ref InstanceType
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref DefaultSecurityGroup
- !Ref WebSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash -x
yum update -y
yum -y install python2-pip
pip2 install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz
python2 /usr/bin/cfn-signal -e $? \
--stack ${AWS::StackName} \
--resource EC2Instance \
--region ${AWS::Region}
For debugging, as the user data may error out, have to login to the instance and check /var/log/cloud-init-output.log file

I could recreate your error and fixed here. Here is the corrected template. I added to the answer from Marcin
EC2Instance:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal:
Timeout: PT5M # Specify the time here
Properties:
CreditSpecification:
CPUCredits: standard
IamInstanceProfile:
Fn::ImportValue:
!Sub ${InstanceProfileStackName}-instanceProfile
ImageId: !Ref ImageId
InstanceInitiatedShutdownBehavior: stop
InstanceType: !Ref InstanceType
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref DefaultSecurityGroup
- !Ref WebSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash -ex
yum update -y
source scl_source enable rh-python36
<Your additional commands>
cfn-signal -e $? --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}
You might want to counter check the indentation before trying.

Related

Reducing over 30 seconds cold start on AWS API Gateway + Lambda

I've been facing an extremely slow cold start on Lambda Functions deployed in Docker containers together with an API Gateway.
Tech Stack:
FastAPI
Mangum (https://mangum.io/)
API Gateway
AWS Lambda
To do the deployment, I've been using AWS SAM with the following template file:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
demo
Resources:
AppFunction:
Type: AWS::Serverless::Function
Properties:
Timeout: 118
MemorySize: 3008
CodeUri: app/
PackageType: Image
Events:
ApiEvent:
Properties:
RestApiId:
Ref: FastapiExampleGateway
Path: /{proxy+}
Method: ANY
Auth:
ApiKeyRequired: true
Type: Api
Metadata:
Dockerfile: Dockerfile
DockerContext: .
FastapiExampleGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
OpenApiVersion: '3.0.0'
# Timeout: 30
Auth:
ApiKeyRequired: true
UsagePlan:
CreateUsagePlan: PER_API
UsagePlanName: GatewayAuthorization
Outputs:
Api:
Description: "API Gateway endpoint URL for Prod stage for App function"
Value: !Sub "https://${FastapiExampleGateway}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
The lambda is relatively light, with the following requirements installed:
jsonschema==4.16.0
numpy==1.23.3
pandas==1.5.0
pandas-gbq==0.17.8
fastapi==0.87.0
uvicorn==0.19.0
PyYAML==6.0
SQLAlchemy==1.4.41
pymongo==4.3.2
google-api-core==2.10.1
google-auth==2.11.0
google-auth-oauthlib==0.5.3
google-cloud-bigquery==3.3.2
google-cloud-bigquery-storage==2.16.0
google-cloud-core==2.3.2
google-crc32c==1.5.0
google-resumable-media==2.3.3
googleapis-common-protos==1.56.4
mangum==0.11.0
And the Dockerfile I'm using for the deployment is:
FROM public.ecr.aws/lambda/python:3.9
WORKDIR /code
RUN pip install pip --upgrade
COPY ./api/requirements.txt /code/api/requirements.txt
RUN pip install --no-cache-dir -r /code/api/requirements.txt
COPY ./api /code/api
EXPOSE 7777
CMD ["api.main.handler"]
ENV PYTHONPATH "${PYTHONPATH}:/code/"
Leading to a 250mb image.
On the first Lambda pull, I'm seeing
which looks like it's a very long start before the actual lambda execution. It reaches the point where API gateway times out due to the maximum 30 second response!
Local tests using sam local start-api work fine.
I've tried increasing the lambda function RAM to higher values.
Not sure if this a problem with Mangum (wrapper for FastAPI)?

Cannot add lambda layer via GUI or programmatically, but works via cloud formation. Failed to unzip archive: Zip file contains invalid files/folders;

I'm following this excellent article: https://github.com/vittorio-nardone/selenium-chromium-lambda/
End to end the example works correctly - I just want to re-use the layers that are created in my own function.
Whatever method I use to try and add the layer fails. Manually using GUI,Boto3 in python or the AWS CLI, although it is working on the function setup up by the cloud formation script.
aws lambda update-function-configuration --function-name='test_headless' --layers='arn:aws:lambda:eu-west-1:366134052888:layer:SeleniumChromiumLayer:1'
An error occurred (InvalidParameterValueException) when calling the UpdateFunctionConfiguration operation: Failed to unzip archive: Zip file contains invalid files/folders;
Clearly I'm missing something here:
Partial Extract from cloud formation script:
ScreenshotFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.7
Description: Function to take a screenshot of a website.
Handler: src/lambda_function.lambda_handler
Role:
Fn::GetAtt: [ "ScreenshotFunctionRole", "Arn" ]
Environment:
Variables:
PYTHONPATH: "/var/task/src:/opt/python"
PATH: "/opt/bin:/opt/bin/lib"
URL:
Ref: WebSite
BUCKET:
Ref: BucketName
DESTPATH:
Ref: ScreenshotsFolder
Timeout: 60
MemorySize: 2048
Code:
S3Bucket:
Ref: BucketName
S3Key:
Fn::Sub: '${SourceFolder}/ScreenshotFunction.zip'
Layers:
- Ref: SeleniumChromiumLayer
SeleniumChromiumLayer:
Type: AWS::Lambda::LayerVersion
Properties:
CompatibleRuntimes:
- python3.7
- python3.6
Content:
S3Bucket:
Ref: BucketName
S3Key:
Fn::Sub: '${SourceFolder}/SeleniumChromiumLayer.zip'
Description: Selenium and Chromium Layer for Python3.6
How is it that the contents of the zip used can be OK to add via cloudformation but not in any other manner?
Seems there was some corruption on a function - add the layer to another function worked successfully

Creating RDS cloudformation with route 53

I am having trouble with aws cloud formation. I need to create cloudformation that will install and configure RDS with RHEL and mariadb with route 53 and master user. I started first with basic config.yaml but i am getting an error with vpc, it says
No default VPC for this user (Service: AmazonEC2; Status Code: 400;
Error Code: VPCIdNotSpecified; Request ID:
407bd74c-9b85-4cce-b5a7-b816fe7aea15)
My config.yaml is this
Resources:
Ec2Instance1:
Type: 'AWS::EC2::Instance'
Properties:
SecurityGroups:
- !Ref InstanceSecurityGroup
KeyName: adivir
ImageId: ami-07dfba995513840b5
AvailabilityZone: eu-central-1
InstanceType: t2.micro
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
yum install -y httpd
yum install -y git
yum install -y php php-mysql
git clone https://github.com/demoglot/php.git /var/www/html
systemctl restart httpd
systemctl enable httpd
Ec2Instance2:
Type: 'AWS::EC2::Instance'
Properties:
SecurityGroups:
- !Ref InstanceSecurityGroup
KeyName: adivir
ImageId: ami-07dfba995513840b5
AvailabilityZone: eu-central-1
InstanceType: t2.micro
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
yum install -y httpd
yum install git -y
git clone https://github.com/demoglot/php.git /var/www/html
systemctl restart httpd
systemctl enable httpd
InstanceSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable SSH access
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '2256'
ToPort: '2256'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
ElasticLoadBalancer:
Type: 'AWS::ElasticLoadBalancing::LoadBalancer'
Properties:
AvailabilityZones:
- eu-central-1
- eu-central-1b
Listeners:
- InstancePort: '80'
LoadBalancerPort: '80'
Protocol: HTTP
HealthCheck:
Target: 'HTTP:80/'
HealthyThreshold: '3'
UnhealthyThreshold: '5'
Interval: '30'
Timeout: '5'
Instances :
- !Ref Ec2Instance1
- !Ref Ec2Instance2
DBSECURITYGROUP:
Type: 'AWS::RDS::DBSecurityGroup'
Properties:
GroupDescription: Security Group for RDS private access
DBSecurityGroupIngress:
- CIDRIP: 0.0.0.0/0
MyDB:
Type: 'AWS::RDS::DBInstance'
Properties:
DBName: kk
AllocatedStorage: '20'
DBInstanceClass: db.t2.micro
Engine: MariaDB
EngineVersion: '10.1.31'
MasterUsername: admin
MasterUserPassword: admin123
DBSecurityGroups:
- !Ref DBSECURITYGROUP
Tags:
- Key: name
Value: kk
DeletionPolicy: Snapshot
What i need to do in order to resolve vpc error and have RDS create successfully and how and where to add route 53 creation in yaml file? Also database neds to be connected to java app athat is on other instance. What do i need to share with person making app in order for him to connect to database? Also, is it possible to have one shell script that will run cloudformations in order, create stacks and then exit so that not each team member needs to run his own cloud formation? Thank you
Solution to this problem and why it occurs have been documented and explained in the resent AWS blog:
How do I resolve the CloudFormer error “No default VPC found for this user” in AWS CloudFormation?
Basically, the solution is to create new default vpc.
p.s.
I also agree with #mokugo-devops. You ask too many sub-questions which limits the focus and precision of your main question and issue you have reported.

Parse user/metadata/runtime data from cloudformation into EC2

I am trying to setup my windows 2016 instances in cloudformation stack and It requires some inputs from user that is taken in parameter section to be transferred into the instance and the software inside would require those parameters. Till now i only found that i can send it by parsing it inside a powershell script while saving the data into a text file. Is there anyother method? or more efficient method to do this???
See Bootstrapping AWS CloudFormation Windows Stacks.
In short, you typically use AWS::CloudFormation::Init metadata and cfn-init in UserData to bootstrap and initialize your instances.
Here is an AWS CloudFormation template that can take a parameter from the CloudFormation stack and put that information onto the Windows instance:
AWSTemplateFormatVersion: 2010-09-09
Parameters:
Username:
Type: String
Description: Ask for a username, will be placed on the instance
WindowsAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: /aws/service/ami-windows-latest/Windows_Server-2019-English-Full-Base
KeyName:
Type: String
Description: Keyname for the keypair to use when launching EC2 instances
Resources:
AppInstance:
Type: AWS::EC2::Instance
Properties:
KeyName: !Ref KeyName
ImageId: !Ref WindowsAmiId
InstanceType: t2.large
UserData:
Fn::Base64: !Sub |
<powershell>
Set-Content -Path 'C:\username.txt' -Value '${Username}'
</powershell>
The result is a file in C:\username.txt that contains the Username parameter that was provided when the stack was launched.
Alternatively, you could call describe-stack to retrieve the parameters.

AWS Cloudformation - Installing packages with cfn-init

I have created an EC2 instance via cloudformation and I am trying to get it to install postgres on the instance directly via cloudformation. However, when I SSH into my instance and try to run psql via the command line I keep getting:
bash: psql: command not found
I have tried doing it manually, installing postgres with the below command and it works fine.
sudo yum install postgresql postgresql-server postgresql-devel postgresql-contrib postgresql-docs
Could it be that it is because I'm just updating the stack and thus the ec2 instance rather than creating a new one?
Below is a snippet from the cloudformation template. Everything works when I update the template but it seems that postgres still isn't installed...
DbWrapper:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
postgresql: []
postgresql-server: []
postgresql-devel: []
postgresql-contrib: []
postgresql-docs: []
Properties:
ImageId: ami-f976839e #AMI aws linux 2
InstanceType: t2.micro
AvailabilityZone: eu-west-2a
SecurityGroupIds:
- !Ref Ec2SecurityGroup
SubnetId: !Ref SubnetA
KeyName: !Ref KeyPairName
UserData:
Fn::Base64:
!Join [ "", [
"#!/bin/bash -xe\n",
"sudo yum update\n",
"sudo yum install -y aws-cfn-bootstrap\n", #download aws helper scripts
"sudo /opt/aws/bin/cfn-init -v ", #use cfn-init to install packages in cloudformation init
!Sub "--stack ${AWS::StackName} ",
"--resource DbWrapper ",
"--configsets Install ",
!Sub "--region ${AWS::Region} ",
"\n" ] ]
If anyone is encountering the same problem, the solution was indeed that you need to delete the instance and recreate from scratch. Just updating the stack won't work.
A bit late here (I found this searching for another issue), but you can re-run your CF Launch Config with this piece from your snippet:
UserData:
Fn::Base64:
!Join [ "", [
"#!/bin/bash -xe\n",
"sudo yum update\n",
"sudo yum install -y aws-cfn-bootstrap\n", #download aws helper scripts
"sudo /opt/aws/bin/cfn-init -v ", #use cfn-init to install packages in cloudformation init
!Sub "--stack ${AWS::StackName} ",
"--resource DbWrapper ",
"--configsets Install ",
!Sub "--region ${AWS::Region} ",
"\n" ] ]
the /opt/aws/bin/cfn-init command is what's running the metadata config from the launch config you've specified, which is where your packages are defined.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-init.html
The reason deleting the instance and recreating it works is that it re-runs the UserData piece from the EC2 section above.
This is related to you calling cfn-init with --configsets value that you do not have defined. You would need to add the configSets section below to your metadata section:
Metadata:
AWS::CloudFormation::Init:
configSets:
Install:
- "config"
config:
packages:
yum:
postgresql: []
postgresql-server: []
postgresql-devel: []
postgresql-contrib: []
postgresql-docs: []
Otherwise take out --configset from your cfn-init original call.
References:
cfn-init
AWS::CloudFormation::Init

Resources