# Simplify IP Utilization Monitoring in Subnets with Serverless Automation

I want to begin with saying that Amazon Q developer and AWS Infrastructure Composer helped me to design this solution in a matter of minutes.

Amazon Q: [https://aws.amazon.com/q/](https://aws.amazon.com/q/) AWS Infrastructure Composer: [https://aws.amazon.com/infrastructure-composer/](https://aws.amazon.com/infrastructure-composer/)

## Problem:

Let's discuss the problem I'm attempting to tackle. IP exhaustion, which occurs when given subnets run out of IPs, is a problem that may arise if you are using Amazon EKS and your workload is growing.

Unless you have [IPAM](https://docs.aws.amazon.com/vpc/latest/ipam/what-it-is-ipam.html), AWS CloudWatch metrics do not support them at the time I am writing this blog. Monitoring your available IP addresses in subnets without the use of IPAM is what I'm attempting to accomplish here.

## Solution:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926377526/2c777f53-1d59-424c-9b4a-e6ca9292230b.png align="center")

AWS Services involved in this solution:

* AWS Lambda
    
* Event Bridge Scheduler
    
* Amazon CloudWatch Metrics
    
* Amazon CloudWatch Alarm
    
* Amazon SNS
    

### Lambda Function

I was able to create this in a matter of minutes with the help of Amazon Q Developer, however, I obviously needed to make a few little adjustments. This is very beneficial if you understand the basics and what you are doing. Instead of configuring AWS services blindly, I recommend everyone to better understand AWS services.

### Full Python Script here:

```python
import boto3
import os
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    vpc_id = os.environ['VPC_ID']
    subnet_ids = os.environ['SUBNET_IDS'].split(',')
    namespace = os.environ['NAMESPACE']

    ec2 = boto3.client('ec2')
    cloudwatch = boto3.client('cloudwatch')

    try:
        response = ec2.describe_subnets(
            Filters=[
                {'Name': 'vpc-id', 'Values': [vpc_id]},
                {'Name': 'subnet-id', 'Values': subnet_ids}
            ]
        )

        for subnet in response['Subnets']:
            subnet_id = subnet['SubnetId']
            available_ip_count = subnet['AvailableIpAddressCount']
            cidr_block = subnet['CidrBlock']
            total_ip_count = 2 ** (32 - int(cidr_block.split('/')[1])) - 5  # Subtract 5 for reserved IPs

            subnet_name = subnet_id  # Default to subnet ID if no name tag
            for tag in subnet.get('Tags', []):
                if tag['Key'] == 'Name':
                    subnet_name = tag['Value']
                    break

            utilization_percentage = ((total_ip_count - available_ip_count) / total_ip_count) * 100

            # Send metrics to CloudWatch
            cloudwatch.put_metric_data(
                Namespace=namespace,
                MetricData=[
                    {
                        'MetricName': 'AvailableIPAddresses',
                        'Dimensions': [
                            {'Name': 'SubnetName', 'Value': subnet_name},
                            {'Name': 'SubnetId', 'Value': subnet_id}
                        ],
                        'Value': available_ip_count,
                        'Unit': 'Count'
                    },
                    {
                        'MetricName': 'IPUtilizationPercentage',
                        'Dimensions': [
                            {'Name': 'SubnetName', 'Value': subnet_name},
                            {'Name': 'SubnetId', 'Value': subnet_id}
                        ],
                        'Value': utilization_percentage,
                        'Unit': 'Percent'
                    }
                ]
            )

            print(f"Metrics sent for Subnet: {subnet_name} (ID: {subnet_id})")

    except ClientError as e:
        print(f"An error occurred: {e}")
        return {
            'statusCode': 500,
            'body': str(e)
        }

    return {
        'statusCode': 200,
        'body': 'Subnet monitoring completed'
    }
```

### Get IP address utilization:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926410281/115af832-8336-4e3a-b08a-25dc93e6d569.png align="center")

### Send metrics to CloudWatch:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926429262/aaa159eb-d32f-4051-a420-69934af18d27.png align="center")

## Use AWS Infrastructure Composer to design the infrastructure.

This further enables you design your infrastructure visually, generate Infrastructure as Code and deploy it using AWS SAM (AWS Serverless Application Model) [https://aws.amazon.com/serverless/sam/](https://aws.amazon.com/serverless/sam/).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926728136/90449649-c623-49fa-8400-2fd6e1d42d8b.png align="center")

## How to Deploy

### Prerequisites

* AWS CLI installed and configured with appropriate permissions
    
* AWS Toolkit for Visual Studio Code installed and configured
    
* AWS SAM CLI installed
    

### Deployment Steps

#### Repository for entire code and instructions on how to deploy: [https://github.com/awsfanboy/aws-subnet-ip-address-utilization-monitor](https://github.com/awsfanboy/aws-subnet-ip-address-utilization-monitor)

* Modify the `template.yaml` file to adjust default parameter values or add/remove resources as needed. eg: VPC ID, Subnet Name, Subnet ID, CloudWatch Metric Namespace.
    
* (Optional) Update the `lambda_`[`function.py`](http://function.py) file in the src directory.
    
* Build the SAM application: `sam build`
    
* Deploy the SAM application: `sam deploy --guided`
    
* This will start an interactive deployment process. You'll be prompted to provide values for the parameters defined in the template. You can accept the default values or provide your own.
    
* During the deployment, you'll be asked to confirm the creation of IAM roles and the changes to be applied. Review and confirm these.
    
* SAM will output the ARNs of the created Lambda function and SNS topic once the deployment is complete.
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926758774/7196628b-33fe-4674-a49e-1b49617e1f70.png align="center")

### Parameters

```apache
    VpcId: The ID of the VPC to monitor
    SubnetIds: Comma-separated list of subnet IDs to monitor
    SubnetName1: Name of the first subnet
    SubnetName2: Name of the second subnet
    CWMetericNamespace: The CloudWatch metric namespace
    AlertEmail: Email address to receive alerts
```

### Resources Created

* Lambda function for monitoring subnets
    
* EventBridge rule to trigger the Lambda function every minute
    
* SNS topic for sending alerts
    
* CloudWatch alarms for each monitored subnet
    

### Customization

* To monitor more than two subnets, duplicate the `SubnetUtilizationAlarm` resource in the template and adjust the `SubnetIds` parameter.
    
* Modify the Lambda function code in `src/lambda_`[`function.py`](http://function.py) to implement your specific monitoring logic.
    
* Adjust the alarm thresholds and evaluation periods in the `SubnetUtilizationAlarm` resources as needed.
    

### Cleanup

* To remove all resources created by this stack: `sam delete`
    
* Follow the prompts to confirm the deletion of resources.
    

## Demo

I have an Amazon EKS cluster running a deployment with 6 replicas. Worker nodes are running on 2 Subnets. IP address utilization is looking good.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926805652/94c0ee4b-00ae-4565-a987-c0a9cb439ff1.png align="center")

The alarm state is OK.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926857053/f88a9b9c-02ae-44be-b3ce-cb580620f0c6.png align="center")

Okay! let's increase the number of replicas from `6` to `600`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926885209/a873024f-fb95-48c5-b17f-76dfc0d40694.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736926902905/69bc6b5d-b49d-4ea0-be06-050dc696f22f.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927031848/c5be081d-9244-46ad-8154-453b062176de.png align="center")

Let's check metrics from the CloudWatch and ooops! now we can see that IP utilization is high.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927049056/1f89864e-747f-4cd1-a01e-ac8596f389d9.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927069588/07e5d03c-9fa8-4259-837a-2a31e4928278.png align="center")

Now, let's check the Alarms in the CloudWatch. Now the state changed from `OK` to `ALARM` state.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927093800/e77e8ea9-ff9e-4d70-b2d1-84d555698de5.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927126619/74bc8843-687e-4be9-9a3b-cb85df8d89b7.png align="center")

Let's check my emails

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927145898/6455160c-95dd-4a5e-bbc4-304913c932f7.png align="center")

I can see there are 2 emails in my inbox.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927194740/802a556f-6888-4afb-b3f0-19cf0c3ed760.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927221546/6a4368ea-f5e8-41ad-b04e-d2c124ea8057.png align="center")

# Cost

I calculated the cost using [calculator.aws](http://calculator.aws), and it appears to be not bad though.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927236461/9edde7c4-8286-4dc8-91b6-da923f4be150.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1736927258083/083e94a8-be20-485f-a3a8-87943a6b9111.png align="center")

# What Next?

These notifications can be sent to Slack, PagerDuty, and other platforms.

# Conclusion

I hope my automation will help someone who doesn't want to use IPAM to monitor IP address utilization in subnets, and I truly wish we could access these metrics straight from CloudWatch.

If you have any suggestions for improvement or if you would like to use anything you currently have in a different way, please feel free to share.
