aws cloudformation "NetworkInterfaces" in Autoscaling launchconfig group - amazon-web-services

In aws cloudformation how to add "NetworkInterfaces" in Autoscaling launchconfig group as I want to configure every instance launched and I need "NetworkInterfaces" to be there same AWS::EC2::Instance?

The solution I'm currently using is to ensure that every Instance is launched with an IAM Instance Profile that allows contains the Policy
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ec2:CreateTags",
"ec2:DescribeSubnets",
"ec2:AttachNetworkInterface",
"ec2:CreateNetworkInterface",
"ec2:ModifyNetworkInterfaceAttribute"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
My Cloud Formation Template for creating the AutoScalingGroup and LaunchConfiguration receives Parameters for Subnets and Security Groups to attach to each additional ENI.
"SecondaryNICSubnetIds":{
"Type" : "CommaDelimitedList",
"Description" : "Ensure that the spread of Availability Zones for these Subnets matches the SubnetIds used to create Instances, as when creating a Secondary ENI it must exist in the same AZ as the Instance"
},
"SecondaryNICSecurityGroupIds":{
"Type" : "CommaDelimitedList",
"Description" : "Security Groups to associate to the Secondary ENI"
},
Then the LaunchConfiguration contains a UserData property that
ensures awscli is installed
sets the region to use by parse the instance metadata identity document
gets the instance id from the instance metadata
gets the availability zone for the instance from the instance metadata
from the Subnets that have been passed in find the first that matches my instances AZ by using the awscli describe-subnets call
Creates a Network Interface in my chosen Subnet, and add Security Groups to it using the awscli create-network-interface call
Tag my ENI using the awscli create-tags call
Attach ENI to my Instance using the awscli attach-network-interface call
Modify Attachment to Delete the ENI on Instance Termination using the modify-network-interface-attribute call
"UserData": {
"Fn::Base64" : {
"Fn::Join": [ "\n",
[
"#!/bin/bash -xe",
"sudo apt-get install -y awscli",
"export AWS_DEFAULT_REGION=$(curl -sS http://169.254.169.254/latest/dynamic/instance-identity/document | python -c 'import sys, json; print(json.load(sys.stdin)[\"region\"])')",
"INSTANCE_ID=$(curl -sS http://169.254.169.254/latest/meta-data/instance-id)",
"AZ=$(curl -sS http://169.254.169.254/latest/meta-data/placement/availability-zone)",
"echo Availability Zone: ${AZ}",
{"Fn::Sub":[
"SUBNET_ID=$(aws ec2 describe-subnets --subnet-ids ${SubnetNetIds} --filters Name=availabilityZone,Values=${!AZ} --query 'Subnets[0].SubnetId' --output text)",
{"SubnetNetIds": {"Fn::Join": [" ", {"Ref": "SecondaryNICSubnetIds"} ] }}
]},
"echo Subnet Id: ${SUBNET_ID}",
{"Fn::Sub":[
"ENI_ID=$(aws ec2 create-network-interface --subnet ${!SUBNET_ID} --description 'Secondary ENI' --groups ${SecurityGroups} --query 'NetworkInterface.NetworkInterfaceId' --output text)",
{"SecurityGroups": {"Fn::Join": [" ", {"Ref": "SecondaryNICSecurityGroupIds"}]} }
]},
"echo ENI ID: ${ENI_ID}",
"aws ec2 create-tags --resources ${!ENI_ID} --tags Key=Some,Value=Tag",
"ATTACHMENT_ID=$(aws ec2 attach-network-interface --network-interface-id ${ENI_ID} --instance-id ${INSTANCE_ID} --device-index 1 --output text)",
"echo Attachment ID: ${ATTACHMENT_ID}",
"echo Delete On Termination: $(aws ec2 modify-network-interface-attribute --network-interface-id ${ENI_ID} --attachment AttachmentId=${ATTACHMENT_ID},DeleteOnTermination=true --output text)"
]
]
}
}
If you didn't want to pass the Subnets into the Cloud Formation Template you could attempt to look them up by adding tags to --query in the awscli describe-subnets call, if your infrastructure allows you to identify them this way.

Related

Retrieve elements from two levels using AWS CLI 'describe-instances'

I would like to --query across multiple levels of the aws ec2 describe-instances API. For example, I would like to combine:
aws ec2 describe-instances --query 'Reservations[*].Instances[*].[ImageId, InstanceType, KeyName, State.Name, PublicIpAddress, NetworkInterfaces.Groups.GroupName]' --output json
and fields such as OwnerId which are a level above Instances.
You can include the top-level OwnerId by specifying it prior to Instances[*]:
aws ec2 describe-instances --query 'Reservations[*].[OwnerId,Instances[*].[ImageId, InstanceType, KeyName, State.Name, PublicIpAddress, NetworkInterfaces.Groups.GroupName]]' --output json
However, the fact that there is a one-to-many relationship to the instances means it will be returned at a higher level:
[
[
"123456789012",
[
[
"ami-48d38c2b",
"t2.micro",
"class",
"running",
"54.2.33.44",
null
]
]
],
[
"123456789012",
[
[
"ami-f806349b",
"t2.small",
"class",
"running",
"54.1.22.33",
null
]
]
]
]
By the way, the OwnerId is merely the ID of the AWS Account that owns the resource, which in almost every case would be the same value.

AWS get all private ip address in given vpc

I want to run an ansible role, for all the ip address in the vpc which are running.
How to get all the ip address of running instance in given vpc
Things: I have tired:
aws ec2 describe-instances --filters "Name=vpc-id,
Values="vpc-******"" --query
"Reservations[].Instances[].PrivateIpAddresses[*]" --output text
This is returning null
The name of the parameter is PrivateIpAddress not PrivateIpAddresses as you can see from Json object
[
[
{
"Monitoring": {
"State": "disabled"
},
"PublicDnsName": "xxxx",
"RootDeviceType": "ebs",
"State": {
"Code": 16,
"Name": "running"
},
"EbsOptimized": false,
"LaunchTime": "xxx",
"PublicIpAddress": "xxx",
"PrivateIpAddress": "xxxxx",
"ProductCodes": [
....
so if you run your command as
aws ec2 describe-instances --filters "Name=vpc-id, Values="vpc-cda7c6a8"" --query "Reservations[*].Instances[*].PrivateIpAddress" --output text
you will have your expected result
it's PrivateIPAddress, not Addresses
aws ec2 describe-instances --instance-ids --query Reservations[].Instances[].PrivateIpAddress
Hope this helps

How to assign EIP to Autoscaling Group of VPC in Cloudformation template

I want to assign one of my reserved Elastic IP's(ec2 classic ip) to Autoscaling group in VPC. Using AWS Cli I moved ip to vpc:
$ aws ec2 move-address-to-vpc --public-ip 23.23.23.23
And saw in aws concole, that this IP passed to VPC.
And Assigned in tags of AutoscalingGroup in Cloudformation template in Resources:
"Process": {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"LaunchConfigurationName": {"Ref": "PreprocessorLC"},
"LoadBalancerNames": [{"Ref": "ProcessELB"}],
"VPCZoneIdentifier" : [{ "Fn::Join" : [",", [ { "Ref" : "PublicSubnet1"}, { "Ref" : "PublicSubnet2"} ]]}],
"AvailabilityZones": {"Ref": "AZs"},
"MinSize" : "1",
"MaxSize" : "1",
"HealthCheckGracePeriod": 300,
"Tags" : [
{"Key": "Name", "Value": {"Fn::Join": ["", [{"Ref": "Env"}, "-Process"]]}, "PropagateAtLaunch": true},
{"Key": "WorkersScalingGroup", "Value": {"Fn::Join": ["", ["Offering-", {"Ref": "Env"}, "-Process-Worker"]]}, "PropagateAtLaunch": true},
{"Key": "EIP", "Value": {"Ref": "ProcessIP"}, "PropagateAtLaunch": true},
{"Key": "Environment", "Value": {"Ref": "Env"}, "PropagateAtLaunch": true}
]
}
}
And added value of "ProcessIP" in Parameters:
"ProcessIP":{
"Description": "DEV: 23.23.23.23",
"Type": "String",
"Default": "23.23.23.23",
"AllowedValues": ["23.23.23.23"]
}
And it doesn't worked. Still get random IP.
If someone can tell where I'm wrong or what should to add for make it work?
Thanks!
In my case, I needed to keep a bank of unassigned EIPs and randomly assign them to the EC2 when they boot. That way I always know my servers will be using a specific list of IPs that I can whitelist in other places.
If you create several EIPs named "prod-pool" you can then use this script.
apt install -y jq awscli
ALLOCATION_ID=`aws ec2 describe-addresses --filters="Name=tag:Name,Values=prod-pool" | jq -r '.Addresses[] | "\(.InstanceId) \(.AllocationId)"' | grep null | awk '{print $2}' | xargs shuf -n1 -e`
if [ ! -z $ALLOCATION_ID ]; then
aws ec2 associate-address --instance-id $INSTANCE_ID --allocation-id $ALLOCATION_ID --allow-reassociation
fi
You can attached this policy to your IAM user
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEIPAttachment",
"Effect": "Allow",
"Resource": [
"*"
],
"Action": [
"ec2:AssociateAddress",
"ec2:DisassociateAddress"
]
}
]
}
Here is simple bash script:
#!/bin/sh
# Region in Which instance is running
EC2_REGION='us-east-1'
AWS_ACCESS_KEY='xxxxxxxxxxxx'
AWS_SECRET_ACCESS_KEY='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
#Instance ID captured through Instance meta data
InstanceID=`/usr/bin/curl -s http://169.254.169.254/latest/meta-data/instance-id`
#Elastic IP captured through the EIP instance tag
Elastic_IP=`/opt/aws/apitools/ec2/bin/ec2-describe-tags -O $AWS_ACCESS_KEY -W $AWS_SECRET_ACCESS_KEY --filter resource-id=$InstanceID --filter key='EIP' | cut -f5`
Allocate_ID=`/opt/aws/apitools/ec2/bin/ec2-describe-tags -O $AWS_ACCESS_KEY -W $AWS_SECRET_ACCESS_KEY --filter resource-id=$InstanceID --filter key="AllocationID" | cut -f5`
#Assigning Elastic IP to Instance
aws ec2 associate-address --instance-id $InstanceID --allocation-id $Allocate_ID
You need to explicitly associate the Elastic IP address with your desired EC2 instance. You can do this in a userdata script at launch time, or externally through other scripting or Configuration Management.
PropagateAtLaunch simply propagates tags from the Auto Scaling Group to any instances that are launched as a result of Auto Scaling actions. I'm not aware of any magic that would cause a tagged Elastic IP address to be associated with a launched instance.
See more discussion and examples of launch time scripting with EIPs here.
I created a AWS Lambda function which will automatically bind an Elastic IP address from a pool to instance of an autoscaling group. This alleviates the need to grab an EIP address in the bootscript of the instances. For a complete description check out
https://binx.io/blog/2019/09/02/how-to-dynamically-bind-elastic-ip-addresses-to-an-auto-scaling-group/

aws ec2 describe-addresses won't show some instances

I'm scripting some stuff with aws ec2 describe-addresses, but, for some reason, some instances won't be returned by it.
Example:
$ aws ec2 describe-addresses --filter=Name=instance-id,Values=i-xxxxx
{
"Addresses": []
}
The given instanceId is valid and has addresses, but it just won't be shown by aws cli.
However, for another instances it seems to work just fine:
$ aws ec2 describe-addresses --filter='Name=instance-id,Values=i-yyyyyy'
{
"Addresses": [
{
"PrivateIpAddress": "X.X.X.X",
"InstanceId": "i-yyyyyy",
"NetworkInterfaceOwnerId": "XXXXXXXXXX",
"Domain": "vpc",
"AllocationId": "eipalloc-xxxxxx",
"PublicIp": "Y.Y.Y.Y",
"NetworkInterfaceId": "eni-xxxxxx",
"AssociationId": "eipassoc-xxxxx"
}
]
}
The keys I'm using have EC2FullAccess policy, so, it doesn't seem to be related to security...
What am I doing wrong? Any tips? Is there any limitations of aws cli that I'm not aware of?
I think the reason is you are not listing the instances, actually listing the Elastic IP Addresses. Probably the missing ones are the ones without Elastic IPs.
So for example the command without the filter will list the EIP list
aws ec2 describe-addresses
Output:
{
"Addresses": [
{
"InstanceId": null,
"PublicIp": "198.51.100.0",
"Domain": "standard"
},
{
"PublicIp": "203.0.113.0",
"Domain": "vpc",
"AllocationId": "eipalloc-64d5890a"
}
]
}
Source: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-addresses.html

Is there a way to tag a root volume when initializing from the cloudformation template?

I am creating an instance through the cloud formation script.
The only way I found to attach an OS partition was through "BlockDeviceMappings" property. (I've tried to use "Volumes" property before, but the instance could not be mounted, the system told me that /dev/sda was already mapped and rolled back the instance creation)
Here is the relevant portion of my template:
"Resources" :
{
"Ec2Instance" :
{
"Type" : "AWS::EC2::Instance",
"Properties" :
{
"BlockDeviceMappings" :
[{
"DeviceName" : "/dev/sda",
"Ebs" :
{
"VolumeSize" : { "Ref" : "RootVolumeSize" },
"SnapshotId" :
{ "Fn::FindInMap" : [ "RegionMap",
{ "Ref" : "AWS::Region" }, "RootVolumeSnapshotId" ]
}
}
}],
...
}
}
My question is, how can I tag the Ebs volume, that I am creating here with "BlockDeviceMappings" property? I did not find the obvious solution.
Thanks.
Was able to make it work through an AWS CLI interface, IAM role, and UserData initialization.
Added this to AWS::EC2::Instance:Properties:UserData
{ "Fn::Base64" : { "Fn::Join" : [ "\n", [
"#!/bin/bash",
"set -eux",
"exec > >(tee /tmp/user-data.log | logger -t user-data -s 2>/dev/console) 2>&1",
{ "Fn::Join" : [ "", [
"AWS_STACK_NAME='", { "Ref" : "AWS::StackName" }, "'"
]]},
{ "Fn::Join" : [ "", [
"AWS_ROOT_VOLUME_SNAPSHOT_ID='",
{ "Fn::FindInMap" :
[ "RegionMap", { "Ref" : "AWS::Region" }, "RootVolumeSnapshotId" ]},
"'"
]]},
"AWS_INSTANCE_ID=$( curl http://169.254.169.254/latest/meta-data/instance-id )",
"",
"AWS_HOME=/opt/aws",
"AWS_BIN_DIR=\"${AWS_HOME}/bin\"",
"export EC2_HOME=\"${AWS_HOME}/apitools/ec2\"",
"export JAVA_HOME=/etc/alternatives/jre_1.7.0",
"",
"ROOT_DISK_ID=$(",
" \"${AWS_BIN_DIR}/ec2-describe-volumes\" \\",
" --filter \"attachment.instance-id=${AWS_INSTANCE_ID}\" \\",
" --show-empty-fields \\",
" | grep '^VOLUME' \\",
" | awk '{printf \"%s,%s\\n\", $4, $2}' \\",
" | grep '^${AWS_ROOT_VOLUME_SNAPSHOT_ID}' \\",
" | cut --delimiter=, --fields=2",
" exit ${PIPESTATUS[0]}",
" )",
"\"${AWS_BIN_DIR}/ec2-create-tags \\",
" \"${ROOT_DISK_ID}\" \\",
" --tag \"Name=${AWS_STACK_NAME}-root\"",
""
]]}}
Also have to add a reference to an IAM role that can describe volumes and create tags.
Added this to "Resources" section:
"InstanceProfile" :
{
"Type" : "AWS::IAM::InstanceProfile",
"Properties" :
{
"Path" : "/",
"Roles" : [ "ec2-tag-instance" ]
}
}
Referenced this profile in the Instance resource:
"Ec2Instance" :
{
"Type" : "AWS::EC2::Instance",
"Properties" :
{
...
"IamInstanceProfile" : {"Ref" : "InstanceProfile"},
...
}
}
And in IAM UI create a new Role called ec2-tag-instance, and assign this policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"ec2:CreateTags"
],
"Resource": "*"
}
]
}
This said, would be much nicer if BlockDeviceMappings:Ebs had supported Tags element.
If your CloudFormation stack is tagged and you want your EC2 attached volumes to copy over the tags from the stack you can use the below UserData value.
Fn::Base64: !Sub |
#!/bin/bash -xe
exec > /tmp/part-001.log 2>&1
# --==Tagging Attached Volumes==--
TAGS=$(aws cloudformation describe-stacks --stack-name ${AWS::StackName} --query 'Stacks[0].Tags' --region ${AWS::Region})
EC2_INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
EBS_IDS=$(aws ec2 describe-volumes --filters Name=attachment.instance-id,Values="$EC2_INSTANCE_ID" --region ${AWS::Region} --query 'Volumes[*].[VolumeId]' --out text | tr "\n" " ")
aws ec2 create-tags --resources $EBS_IDS --tags "$TAGS" --region ${AWS::Region}
TAGS=$(echo $TAGS | tr "Key" "key" | tr "Value" "value")
aws ecs tag-resource --resource-arn arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${EcsClusterName} --tags "$TAGS"
Write all stdout and stderr to file for debugging:
`exec > /tmp/part-001.log 2>&1
(requires permission) Get the tags from the stack:
TAGS=$(aws cloudformation describe-stacks --stack-name ${AWS::StackName} --query 'Stacks[0].Tags' --region ${AWS::Region})
Get the EC2 instance id from the metadata endpoint:
EC2_INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
(requires permission) Get the EBS IDS:
EBS_IDS=$(aws ec2 describe-volumes --filters Name=attachment.instance-id,Values="$EC2_INSTANCE_ID" --region ${AWS::Region} --query 'Volumes[*].[VolumeId]' --out text | tr "\n" " ")
(requires permission) Add tags to the EBS volumes:
aws ec2 create-tags --resources $EBS_IDS --tags "$TAGS" --region ${AWS::Region}
Format tags for ECS tagging:
TAGS=$(echo $TAGS | tr "Key" "key" | tr "Value" "value")
(requires permission) Tag the ECS cluster:
aws ecs tag-resource --resource-arn arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${EcsClusterName} --tags "$TAGS"
The policy should look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ec2:DeleteTags",
"ec2:CreateTags",
"ecs:TagResource",
"cloudformation:DescribeStacks"
],
"Resource": "*"
}
]
}
You can use CloudFormation template to create resources like CloudWatch event rule and Lambda. The lambda invocation can get triggered not only when there is instance creation event but when updates happen to instance tags.
This is what I did in my ec2 userdata. I think it's much simpler than above answer.
Key=<Your Tag Name>
Value=<Your Tag Value>
Region=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | grep -oP "(?<=\"region\" : \")[^\"]+")
aws ec2 create-tags --resources $(aws ec2 describe-instances --instance-ids $(curl -s http://169.254.169.254/latest/meta-data/instance-id) --region $Region | grep -oP "(?<=\"VolumeId\": \")[^\"]+") --tags Key=$Key,Value=$Value --region $Region