Automatically tag EC2 instances with OS, and install Cloudwatch Agent based on OS via Crossplane

So, in this post, we are repeating what we did in the last post, and further builds on it, but, via Crossplane!

It seems that Crossplane is still somewhat early in its adoption curve at the time of writing (July 2024), because examples of deploying AWS services like Lambda, Eventbridge, and Systems Manager are not widely available. The Crossplane documentation is lacking examples, and in too many cases, just wrong.

One such example is highlighted below. Crossplane does not allow passing parameters as shown below. In the Crossplane template seen below, you’ll notice that you either have to actually hard code these variables, or, use Composite Resource Definitions (XRDs), but, following the example as is simply would not work.

In other cases, it’s simply not clear what format the input is expected in. Figuring this out basically required trial and error until I got it right:

The right format required was:

Here is the full code. Please pay attention to the comments for successful implementation:

# Note System Manager has to be enabled with the EC2-Default-SSM-AD-Role role. Go to Systems Manager > Fleet Manager > Configure Default Host Management Configuration, and then change the IAM role to EC2-Default-SSM-AD-Role

# Also note that EC2 instances must be associated with IAM Role EC2-Default-SSM-AD-Role
# If this role doesn't already exist in your environment, then you can create it by adding these built in AWS managed policies: AmazonSSMDirectoryServiceAccess, AmazonSSMManagedInstanceCore, CloudWatchAgentServerPolicy

# Permissions to allow EventBridge service to invoke a Lambda Function. 
---
apiVersion: lambda.aws.upbound.io/v1beta1
kind: Permission
metadata:
  name: alloweventbridgetoinvokefunction
spec:
  forProvider:
    action: lambda:InvokeFunction
    functionNameRef:
      name: autotagec2os
    principal: events.amazonaws.com
    region: ca-central-1
    statementId: AllowExecutionFromEventBridge

# Lambda function to automatically tag EC2 instances with monitoring:disk-memory:linux or monitoring:disk-memory:windows tag
---
apiVersion: lambda.aws.upbound.io/v1beta1
kind: Function
metadata:
  annotations:
    #meta.upbound.io/example-id: lambda/v1beta1/function
    uptest.upbound.io/timeout: "3600"
  labels:
    testing.upbound.io/example-name: autotagec2os
  name: autotagec2os
spec:
  forProvider:
    handler: lambda_function.lambda_handler
    packageType: Zip
    region: ca-central-1
    roleSelector:
      matchLabels:
        testing.upbound.io/example-name: autotagec2os-role
    runtime: python3.12
    s3Bucket: salman-temp-delete-after-aug-2024
    s3Key: autoTagEC2OS.zip
    timeout: 60

# The autoTagEC2OS.zip has a single file named lambda_fucntion.py. Below are the contents of that file:
#import boto3
#def lambda_handler(event, context):
#    ec2 = boto3.client('ec2')
#    instances = ec2.describe_instances()
#    for reservation in instances['Reservations']:
#        for instance in reservation['Instances']:
#            os_tag = 'unknown'
#           for tag in instance.get('Tags', []):
#                if tag['Key'] == 'os':
#                    os_tag = tag['Value']
#                    break
#            if os_tag == 'unknown':
#                # Determine the OS and tag the instance
#                platform = instance.get('Platform', 'linux')
#                if platform == 'windows':
#                    os_value = 'windows'
#                else:
#                    os_value = 'linux'
#                
#                ec2.create_tags(
#                    Resources=[instance['InstanceId']],
#                    Tags=[{'Key': 'monitoring:disk-memory', 'Value': os_value}]
#                )

# This defines the Eventbridge target to be the lambda function created above. Note, the account ID and region have to be changed manually.
---
apiVersion: cloudwatchevents.aws.upbound.io/v1beta1
kind: Target
metadata:
  annotations:
    upjet.upbound.io/manual-intervention: This resource needs arn of Topic.
  name: autotagec2os-lambdatarget
spec:
  forProvider:
    #arn: arn:aws:lambda:${data.aws_region}:${data.aws_account_id}:function:autotagec2os
    arn: arn:aws:lambda:ca-central-1:[YOUR-ACCOUNT-NUMBER]:function:autotagec2os
    eventBusName: default
    region: ca-central-1
    ruleSelector:
      matchLabels:
        testing.upbound.io/example-name: autotagec2os-eventrule
    targetId: autotagec2os-lambdatarget


# This is the EventBridge rule that defines when the event gets triggered, in this case, whenever an EC2 instance gets created by watching the EC2 instance state change. 
# Technically, just watching the "pending" should be enough, but, "runnning" state has been also added as a good measure
# There may be room to improve this Event Rule, by watching some other EC2 parameter. There may however be no need to change this as this works well enough.
---
apiVersion: cloudwatchevents.aws.upbound.io/v1beta1
kind: Rule
metadata:
  name: autotagec2os-eventrule
  labels:
    testing.upbound.io/example-name: autotagec2os-eventrule
spec:
  forProvider:
    eventBusName: default
    eventPattern: |
      { "source": ["aws.ec2"],  "detail-type": ["EC2 Instance State-change Notification"],  "detail": {    "state": ["running", "pending"]  }}   
    region: ca-central-1

# Associations appear under the "State Manager" section of the Systems Manager. 
# This association installs CloudWatch Agent on the instances with tag os:linux or os:windows (which is added automatically using Lambda above)
# Note, the instances must also have the IAM Role EC2-Default-SSM-AD-Role, otherwise they won't have SSM permissions
# Systems Manager is idempotent, so any instances that already have Cloudwatch Agent won't get it reinstalled

---
apiVersion: ssm.aws.upbound.io/v1beta1
kind: Association
metadata:
  labels:
    testing.upbound.io/example-name: install-cloudwatch-daily
  name: install-cloudwatch-daily
spec:
  forProvider:
    associationName: install-cloudwatch-daily
    #document name below
    name: AWS-ConfigureAWSPackage
    nameRef:
      name: AWS-ConfigureAWSPackage
      policy:
        resolution: Optional
    region: ca-central-1
    targets:
      - key: tag:monitoring:disk-memory
        values: ["linux", "windows"]
    scheduleExpression: "rate(30 minutes)"
    parameters:
      action: 
        "Install"
      name: 
        "AmazonCloudWatchAgent"
      additionalArguments:
        "{}"
      installationType:
        "Uninstall and reinstall"

# This association configures the Windows Cloudwatch Agent using the config file in Systems Manager Parameter store named amazoncloudwatchagent-windows
# This template includes the creation of these Parameter further below in the document
---
apiVersion: ssm.aws.upbound.io/v1beta1
kind: Association
metadata:
  labels:
    testing.upbound.io/example-name: configure-cloudwatchagent-windows-daily
  name: configure-cloudwatchagent-windows-daily
spec:
  forProvider:
    associationName: configure-cloudwatchagent-windows-daily
    #document name below
    name: AmazonCloudWatch-ManageAgent
    nameRef:
      name: AmazonCloudWatch-ManageAgent
      policy:
        resolution: Optional
    region: ca-central-1
    targets:
      - key: tag:monitoring:disk-memory
        values: ["windows"]
    scheduleExpression: "rate(30 minutes)"
    parameters:
      action: 
        "configure"
      optionalConfigurationLocation: 
        "amazoncloudwatchagent-windows"
      optionalRestart:
        "yes"
      mode:
        "ec2"
      optionalConfigurationSource:
        "ssm"


# This association configures the Linux Cloudwatch Agent using the config file in Systems Manager Parameter store named amazoncloudwatchagent-linux
# This template includes the creation of these Parameter further below in the document
---
apiVersion: ssm.aws.upbound.io/v1beta1
kind: Association
metadata:
  labels:
    testing.upbound.io/example-name: configure-cloudwatchagent-linux-daily
  name: configure-cloudwatchagent-linux-daily
spec:
  forProvider:
    associationName: configure-cloudwatchagent-linux-daily
    #document name below
    name: AmazonCloudWatch-ManageAgent
    nameRef:
      name: AmazonCloudWatch-ManageAgent
      policy:
        resolution: Optional
    region: ca-central-1
    targets:
      - key: tag:monitoring:disk-memory
        values: ["linux"]
    scheduleExpression: "rate(30 minutes)"
    parameters:
      action: 
        "configure"
      optionalConfigurationLocation: 
        "amazoncloudwatchagent-linux"
      optionalRestart:
        "yes"
      mode:
        "ec2"
      optionalConfigurationSource:
        "ssm"


# This is the Systems Manager Parameter (basically a long string/text file) used to configure Windows Cloudwatch Agent
---
apiVersion: ssm.aws.upbound.io/v1beta1
kind: Parameter
metadata:
  labels:
    testing.upbound.io/example-name: amazoncloudwatchagent-windows
  name: amazoncloudwatchagent-windows
spec:
  forProvider:
    region: ca-central-1
    type: String
    insecureValue: |
      {
        "metrics": {
                "aggregation_dimensions": [
                        [
                                "InstanceId"
                        ]
                ],
                "append_dimensions": {
                        "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
                        "ImageId": "${aws:ImageId}",
                        "InstanceId": "${aws:InstanceId}",
                        "InstanceType": "${aws:InstanceType}"
                },
                "metrics_collected": {
                        "LogicalDisk": {
                                "measurement": [
                                        "% Free Space"
                                ],
                                "metrics_collection_interval": 60,
                                "resources": [
                                        "*"
                                ]
                        },
                        "Memory": {
                                "measurement": [
                                        "% Committed Bytes In Use"
                                ],
                                "metrics_collection_interval": 60
                        }
                }
        }
      }

# This is the Systems Manager Parameter (basically a long string/text file) used to configure Linux Cloudwatch Agent
---
apiVersion: ssm.aws.upbound.io/v1beta1
kind: Parameter
metadata:
  labels:
    testing.upbound.io/example-name: amazoncloudwatchagent-linux
  name: amazoncloudwatchagent-linux
spec:
  forProvider:
    region: ca-central-1
    type: String
    insecureValue: |
      {
        "agent": {
                "metrics_collection_interval": 60,
                "run_as_user": "cwagent"
        },
        "metrics": {
                "aggregation_dimensions": [
                        [
                                "InstanceId"
                        ]
                ],
                "append_dimensions": {
                        "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
                        "ImageId": "${aws:ImageId}",
                        "InstanceId": "${aws:InstanceId}",
                        "InstanceType": "${aws:InstanceType}"
                },
                "metrics_collected": {
                        "disk": {
                                "measurement": [
                                        "used_percent"
                                ],
                                "metrics_collection_interval": 60,
                                "resources": [
                                        "*"
                                ]
                        },
                        "mem": {
                                "measurement": [
                                        "mem_used_percent"
                                ],
                                "metrics_collection_interval": 60
                        }
                }
        }
      }

# These are the permissions required by the Lambda function to be able to tag EC2 instances
---
apiVersion: iam.aws.upbound.io/v1beta1
kind: Role
metadata:
  annotations:
    meta.upbound.io/example-id: iam/v1beta1/role
  labels:
    testing.upbound.io/example-name: autotagec2os-role
  name: autotagec2os-role
spec:
  forProvider:
    assumeRolePolicy: |
      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
              "Service": ["lambda.amazonaws.com", "events.amazonaws.com"]
            },
            "Action": "sts:AssumeRole"
          }
        ]
      }
    inlinePolicy:
      - name: describeec2andaddtag-policy
        policy: |
          {
              "Version": "2012-10-17",
              "Statement": [
                  {
                      "Sid": "allowec2tagging",
                      "Effect": "Allow",
                      "Action": "ec2:CreateTags",
                      "Resource": [
                          "arn:aws:ec2:*:*:instance/*",
                          "arn:aws:ec2:*:*:volume/*"
                      ]
                  },
                  {
                      "Sid": "describeec2details",
                      "Effect": "Allow",
                      "Action": [
                          "ec2:DescribeInstances",
                          "ec2:DescribeVolumes"
                      ],
                      "Resource": "*"
                  },
                  {
                      "Effect": "Allow",
                      "Action": "logs:CreateLogGroup",
                      "Resource": "arn:aws:logs:ca-central-1:[YOUR-ACCOUNT-NUMBER]:*"
                  },
                  {
                      "Effect": "Allow",
                      "Action": [
                          "logs:CreateLogStream",
                          "logs:PutLogEvents"
                      ],
                      "Resource": [
                          "arn:aws:logs:ca-central-1:[YOUR-ACCOUNT-NUMBER]:log-group:/aws/lambda/autoTagEC2OS:*"
                      ]
                  }
                  
              ]
          }

Leave a comment

Blog at WordPress.com.

Up ↑