How can I tag a root volume from an instance created by CloudFormation?

Lesedauer: 9 Minute
0

I want to tag the root volume of my Amazon Elastic Compute Cloud (Amazon EC2) instances that are created through AWS CloudFormation.

Short description

The tag property of the EC2 instance resource doesn't extend to the volumes that are created through CloudFormation. Tagging can restrict the control that you have over your instances. Tagging helps you manage the costs of specific resources and restrict AWS Identity and Access Management (IAM) policies. Tagging also helps you exert similar control over other resources.

Bootstrapping with CloudFormation allows you to tag the Amazon Elastic Block Store (Amazon EBS) root volume of your instance. The bootstrapping method is done through the UserData property of the AWS::EC2::Instance resource. To perform bootstrapping, use AWS Command Line Interface (AWS CLI) commands or standard Windows PowerShell commands after creating your instance.

Note: If you receive errors when running AWS CLI commands, make sure that you’re using the most recent AWS CLI version.

Resolution

Create an instance with a CloudFormation template

1.    Open the CloudFormation console.

2.    Choose Create Stack, and then choose Design template.

3.    In the code editor, on the Parameters tab, choose Template.

4.    For Choose template language, choose YAML.

5.    Copy either of the following JSON or YAML templates, and then paste that copied template into your code editor.

JSON template:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AWS CloudFormation Sample Template Tagging Root Volumes of EC2 Instances: This template shows you how to automatically tag the root volume of the EC2 instances that are created through the AWS CloudFormation template. This is done through the UserData property of the AWS::EC2::Instance resource. **WARNING** This template creates two Amazon EC2 instances and an IAM role. You will be billed for the AWS resources used if you create a stack from this template.",
  "Parameters": {
    "KeyName": {
      "Type": "AWS::EC2::KeyPair::KeyName",
      "Description": "Name of an existing EC2 KeyPair to enable SSH access to the ECS instances."
    },
    "InstanceType": {
      "Description": "EC2 instance type",
      "Type": "String",
      "Default": "t2.micro",
      "AllowedValues": [
        "t2.micro",
        "t2.small",
        "t2.medium",
        "t2.large",
        "m3.medium",
        "m3.large",
        "m3.xlarge",
        "m3.2xlarge",
        "m4.large",
        "m4.xlarge",
        "m4.2xlarge",
        "m4.4xlarge",
        "m4.10xlarge",
        "c4.large",
        "c4.xlarge",
        "c4.2xlarge",
        "c4.4xlarge",
        "c4.8xlarge",
        "c3.large",
        "c3.xlarge",
        "c3.2xlarge",
        "c3.4xlarge",
        "c3.8xlarge",
        "r3.large",
        "r3.xlarge",
        "r3.2xlarge",
        "r3.4xlarge",
        "r3.8xlarge",
        "i2.xlarge",
        "i2.2xlarge",
        "i2.4xlarge",
        "i2.8xlarge"
      ],
      "ConstraintDescription": "Please choose a valid instance type."
    },
    "InstanceAZ": {
      "Description": "EC2 AZ.",
      "Type": "AWS::EC2::AvailabilityZone::Name",
      "ConstraintDescription": "Must be the name of an Availability Zone."
    },
    "WindowsAMIID": {
      "Description": "The Latest Windows 2016 AMI taken from the public Systems Manager Parameter Store",
      "Type": "AWS::SSM::Parameter::Value<String>",
      "Default": "/aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base"
    },
    "LinuxAMIID": {
      "Description": "The Latest Amazon Linux 2 AMI taken from the public Systems Manager Parameter Store",
      "Type": "AWS::SSM::Parameter::Value<String>",
      "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
    }
  },
  "Resources": {
    "WindowsInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": {
          "Ref": "WindowsAMIID"
        },
        "InstanceType": {
          "Ref": "InstanceType"
        },
        "AvailabilityZone": {
          "Ref": "InstanceAZ"
        },
        "IamInstanceProfile": {
          "Ref": "InstanceProfile"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "<powershell>\n",
                "try {\n",
                "$AWS_AVAIL_ZONE=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/placement/availability-zone' -UseBasicParsing).Content\n ",
                "$AWS_REGION=$AWS_AVAIL_ZONE.Substring(0,$AWS_AVAIL_ZONE.length-1)\n ",
                "$AWS_INSTANCE_ID=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/instance-id' -UseBasicParsing).Content\n ",
                "$ROOT_VOLUME_IDS=((Get-EC2Instance -Region $AWS_REGION -InstanceId $AWS_INSTANCE_ID).Instances.BlockDeviceMappings | where-object DeviceName -match '/dev/sda1').Ebs.VolumeId\n ",
                "$tag = New-Object Amazon.EC2.Model.Tag\n ",
                "$tag.key = \"MyRootTag\"\n ",
                "$tag.value = \"MyRootVolumesValue\"\n ",
                "New-EC2Tag -Resource $ROOT_VOLUME_IDS -Region $AWS_REGION -Tag $tag\n",
                "}\n",
                "catch {\n",
                "Write-Output $PSItem\n",
                "}\n",
                "</powershell>\n"
              ]
            ]
          }
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Ref": "AWS::StackName"
            }
          }
        ],
        "BlockDeviceMappings": [
          {
            "DeviceName": "/dev/sdm",
            "Ebs": {
              "VolumeType": "io1",
              "Iops": "200",
              "DeleteOnTermination": "true",
              "VolumeSize": "10"
            }
          }
        ]
      }
    },
    "LinuxInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": {
          "Ref": "LinuxAMIID"
        },
        "InstanceType": {
          "Ref": "InstanceType"
        },
        "AvailabilityZone": {
          "Ref": "InstanceAZ"
        },
        "IamInstanceProfile": {
          "Ref": "InstanceProfile"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/sh\n",
                "AWS_AVAIL_ZONE=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone)\n",
                "AWS_REGION=${AWS_AVAIL_ZONE::-1}\n",
                "AWS_INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)\n",
                "ROOT_VOLUME_IDS=$(aws ec2 describe-instances --region $AWS_REGION --instance-id $AWS_INSTANCE_ID --output text --query Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId)\n",
                "aws ec2 create-tags --resources $ROOT_VOLUME_IDS --region $AWS_REGION --tags Key=MyRootTag,Value=MyRootVolumesValue\n"
              ]
            ]
          }
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Ref": "AWS::StackName"
            }
          }
        ],
        "BlockDeviceMappings": [
          {
            "DeviceName": "/dev/sdm",
            "Ebs": {
              "VolumeType": "io1",
              "Iops": "200",
              "DeleteOnTermination": "true",
              "VolumeSize": "10"
            }
          }
        ]
      }
    },
    "InstanceRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "ec2.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyName": "taginstancepolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "ec2:Describe*"
                  ],
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "ec2:CreateTags"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:volume/*"
                    },
                    {
                      "Fn::Sub": "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*"
                    }
                  ]
                }
              ]
            }
          }
        ]
      }
    },
    "InstanceProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          {
            "Ref": "InstanceRole"
          }
        ]
      }
    }
  }
}

YAML template:

AWSTemplateFormatVersion: 2010-09-09
Description: >-
  AWS CloudFormation Sample Template Tagging Root Volumes of EC2 Instances: This
  template shows you how to automatically tag the root volume of the EC2
  instances that are created through the AWS CloudFormation template. This is
  done through the UserData property of the AWS::EC2::Instance resource.
  **WARNING** This template creates two Amazon EC2 instances and an IAM role.
  You will be billed for the AWS resources used if you create a stack from this
  template.
Parameters:
  KeyName:
    Type: 'AWS::EC2::KeyPair::KeyName'
    Description: Name of an existing EC2 KeyPair to enable SSH access to the ECS instances.
  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
      - t2.large
      - m3.medium
      - m3.large
      - m3.xlarge
      - m3.2xlarge
      - m4.large
      - m4.xlarge
      - m4.2xlarge
      - m4.4xlarge
      - m4.10xlarge
      - c4.large
      - c4.xlarge
      - c4.2xlarge
      - c4.4xlarge
      - c4.8xlarge
      - c3.large
      - c3.xlarge
      - c3.2xlarge
      - c3.4xlarge
      - c3.8xlarge
      - r3.large
      - r3.xlarge
      - r3.2xlarge
      - r3.4xlarge
      - r3.8xlarge
      - i2.xlarge
      - i2.2xlarge
      - i2.4xlarge
      - i2.8xlarge
    ConstraintDescription: Please choose a valid instance type.
  InstanceAZ:
    Description: EC2 AZ.
    Type: 'AWS::EC2::AvailabilityZone::Name'
    ConstraintDescription: Must be the name of an Availability Zone.
  WindowsAMIID:
    Description: >-
      The Latest Windows 2016 AMI taken from the public Systems Manager
      Parameter Store
    Type: 'AWS::SSM::Parameter::Value<String>'
    Default: /aws/service/ami-windows-latest/Windows_Server-2016-English-Full-Base
  LinuxAMIID:
    Description: >-
      The Latest Amazon Linux 2 AMI taken from the public Systems Manager
      Parameter Store
    Type: 'AWS::SSM::Parameter::Value<String>'
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Resources:
  WindowsInstance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref WindowsAMIID
      InstanceType: !Ref InstanceType
      AvailabilityZone: !Ref InstanceAZ
      IamInstanceProfile: !Ref InstanceProfile
      KeyName: !Ref KeyName
      UserData: !Base64 
        'Fn::Join':
          - ''
          - - |
              <powershell>
            - |
              try {
            - >-
              $AWS_AVAIL_ZONE=(Invoke-WebRequest -Uri
              'http://169.254.169.254/latest/meta-data/placement/availability-zone'
              -UseBasicParsing).Content
               
            - |-
              $AWS_REGION=$AWS_AVAIL_ZONE.Substring(0,$AWS_AVAIL_ZONE.length-1)
               
            - >-
              $AWS_INSTANCE_ID=(Invoke-WebRequest -Uri
              'http://169.254.169.254/latest/meta-data/instance-id'
              -UseBasicParsing).Content
               
            - >-
              $ROOT_VOLUME_IDS=((Get-EC2Instance -Region $AWS_REGION -InstanceId
              $AWS_INSTANCE_ID).Instances.BlockDeviceMappings | where-object
              DeviceName -match '/dev/sda1').Ebs.VolumeId
               
            - |-
              $tag = New-Object Amazon.EC2.Model.Tag
               
            - |-
              $tag.key = "MyRootTag"
               
            - |-
              $tag.value = "MyRootVolumesValue"
               
            - >
              New-EC2Tag -Resource $ROOT_VOLUME_IDS -Region $AWS_REGION -Tag
              $tag
            - |
              }
            - |
              catch {
            - |
              Write-Output $PSItem
            - |
              }
            - |
              </powershell>
      Tags:
        - Key: Name
          Value: !Ref 'AWS::StackName'
      BlockDeviceMappings:
        - DeviceName: /dev/sdm
          Ebs:
            VolumeType: io1
            Iops: '200'
            DeleteOnTermination: 'true'
            VolumeSize: '10'
  LinuxInstance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !Ref LinuxAMIID
      InstanceType: !Ref InstanceType
      AvailabilityZone: !Ref InstanceAZ
      IamInstanceProfile: !Ref InstanceProfile
      KeyName: !Ref KeyName
      UserData: !Base64 
        'Fn::Join':
          - ''
          - - |
              #!/bin/sh
            - >
              AWS_AVAIL_ZONE=$(curl
              http://169.254.169.254/latest/meta-data/placement/availability-zone)
            - |
              AWS_REGION=${AWS_AVAIL_ZONE::-1}
            - >
              AWS_INSTANCE_ID=$(curl
              http://169.254.169.254/latest/meta-data/instance-id)
            - >
              ROOT_VOLUME_IDS=$(aws ec2 describe-instances --region $AWS_REGION
              --instance-id $AWS_INSTANCE_ID --output text --query
              Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId)
            - >
              aws ec2 create-tags --resources $ROOT_VOLUME_IDS --region
              $AWS_REGION --tags Key=MyRootTag,Value=MyRootVolumesValue
      Tags:
        - Key: Name
          Value: !Ref 'AWS::StackName'
      BlockDeviceMappings:
        - DeviceName: /dev/sdm
          Ebs:
            VolumeType: io1
            Iops: '200'
            DeleteOnTermination: 'true'
            VolumeSize: '10'
  InstanceRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: taginstancepolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'ec2:Describe*'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'ec2:CreateTags'
                Resource:
                  - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:volume/*'
                  - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*'
  InstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: /
      Roles:
        - !Ref InstanceRole

6.    In the UserData section of the template, update --tags Key=Name,Value=newAMI to match your requirements for a Linux instance. For a Windows instance, update $tag.key="MyRootTag" and $tag.value="MyRootVolumesValue". See the following UserData section examples for Linux and Windows.

Linux example:

#Linux UserData
  UserData:
     Fn::Base64: !Sub |
      #!/bin/bash
      AWS_AVAIL_ZONE=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone)
      AWS_REGION="`echo \"$AWS_AVAIL_ZONE\" | sed 's/[a-z]$//'`"
      AWS_INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
      ROOT_VOLUME_IDS=$(aws ec2 describe-instances --region $AWS_REGION --instance-id $AWS_INSTANCE_ID --output text --query Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId)
      aws ec2 create-tags --resources $ROOT_VOLUME_IDS --region $AWS_REGION --tags Key=MyRootTag,Value=MyRootVolumesValue

Windows example:

#Windows UserData with standard Powershell commands (no AWS CLI installed)
    UserData: 
    Fn::Base64: !Sub |
      <powershell>
      try {  
      $AWS_AVAIL_ZONE=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/placement/availability-zone' -UseBasicParsing).Content
      $AWS_REGION=$AWS_AVAIL_ZONE.Substring(0,$AWS_AVAIL_ZONE.length-1)
      $AWS_INSTANCE_ID=(Invoke-WebRequest -Uri 'http://169.254.169.254/latest/meta-data/instance-id' -UseBasicParsing).Content
      $ROOT_VOLUME_IDS=((Get-EC2Instance -Region $AWS_REGION -InstanceId $AWS_INSTANCE_ID).Instances.BlockDeviceMappings | where-object DeviceName -match '/dev/sda1').Ebs.VolumeId
      $tag = New-Object Amazon.EC2.Model.Tag
      $tag.key = "MyRootTag"
      $tag.value = "MyRootVolumesValue"
      New-EC2Tag -Resource $ROOT_VOLUME_IDS -Region $AWS_REGION -Tag $tag
      }
      catch {
      Write-Output $PSItem
      }
      </powershell>

Important: To use the AWS CLI commands with UserData, you must install the AWS CLI within the Amazon Machine Image (AMI) of your EC2 instances. The AWS CLI is installed by default on all Amazon Linux AMIs. You must also attach an instance profile to your EC2 instances. The instance profile includes the permissions to call the ec2:DescribeInstances and ec2:CreateTags APIs only on EC2 volumes and instances within the AWS Region and account.

7.    Choose the Create stack icon.

8.    For Stack name, enter a name for your stack.

9.    In the Parameters section, enter the appropriate information based on the needs of your environment, including your instance type, EC2 key pair, and AMI.

10.    Choose Next.

11.    In the Options section, enter the appropriate information for your stack, and then choose Next.

12.    To enable the CloudFormation stack to create an IAM resource, select the "I acknowledge that AWS CloudFormation might create IAM resources" check box.

13.    Choose Create.

Tag the root volume of the instance

1.    Open the Amazon EC2 console.

2.    In the navigation pane, in the Elastic Block Store section, choose Volumes.

3.    In the Filter field, enter the tag that you set in the CloudFormation stack to confirm that the volume was tagged.


AWS OFFICIAL
AWS OFFICIALAktualisiert vor 2 Jahren