清理AWS上未使用的ENIS
#aws #python #云 #terraform

在您的子网中用完了IP地址,这是大多数团队如今面临的真实问题。大多数时候,这些IP是保留但未使用的!

此博客将解决未使用的弹性网络接口的问题,以及如何释放我们的IP地址池中的其他服务。

该解决方案将由每天触发的lambda函数和一个CloudWatch警报,以提醒我们我们的lambda生成的任何错误。

首先,我将浏览解决方案以及如何使用AWS控制台创建它,然后我将执行相同的解决方案,但使用基础架构为代码(Terraform)。


创建lambda

在浏览代码之前,让我们设置一些配置参数:

1-最重要的是超时,请确保它超过1分钟(这取决于您的工作负载)

2-内存:200 MB应该足够。

3-无需将lambda放入VPC。

使用Python的Lambda代码:
首先,导入python的AWS SDK(boto3):

ps: 要了解有关AWS SDK的更多信息,请查看this link

import boto3

client = boto3.client('ec2')

下一步:具有特定标签的导入子网。

key = type
值=私有

有多种方法。我的方式是:
1-根据标签描述所有资源
2-在资源类型和标签(键和值)上添加过滤器

# Get Subnets that have a Specific Tag.
tags = client.describe_tags(
    Filters = [
        {
        'Name' : 'resource-type',
        'Values' : [
            'subnet'
            ]
        },
        {
        'Name' : 'tag:type',
        'Values': [
            'private'
            ]
        } 
    ]
)

这种方法的格式输出:

{
   "Tags":[
      {
         "Key":"type",
         "ResourceId":"subnet-062715a13f1fffa54",
         "ResourceType":"subnet",
         "Value":"private"
      },
      {
         "Key":"type",
         "ResourceId":"subnet-0ee66ce86ffe0c073",
         "ResourceType":"subnet",
         "Value":"private"
      }
   ],
   "ResponseMetadata":{}
}

接下来,通过解析数据来获取结果字典中的子网ID。示例:

# Get Subnets that have a Specific Tag.
list_subnets = []
i = 0
while i < len(tags['Tags']):
    list_subnets.append(tags['Tags'][i]['ResourceId'])
    i = i+1

现在我们拥有子网ID,下一步是检索所有网络接口并删除它们。

要将我们的结果缩小到只需要的结果,需要添加过滤器。那是:
1-从特定子网获取eNIS
过滤器
2-滤波器要获得未使用的ENI(可用

请注意,在值中,您需要将其检索到
之前的子网ID

eni = client.describe_network_interfaces(
    Filters=[
        {  
        'Name': 'subnet-id',
        'Values': [
            subnetid,
            ]       
        },
        {
        'Name': 'status',
        'Values': [
            'available'
            ]
        },
    ]
)
i = 0
while i < len(eni["NetworkInterfaces"]):
    network_interface = client.NetworkInterface(eni["NetworkInterfaces"][i]['NetworkInterfaceId'])
    network_interface.delete()
    i = i+1

对于处理程序,我们将不得不致电并同步以前的两个功能,以使lambda正常工作。


# Delete Available Network Interfaces in Specific Subnets
def lambda_handler(event, context): 
    list_subnet = get_tagged_subnets()
    i = 0
    while i < len(list_subnet):
        delete_available_eni(list_subnet[i])
        i = i+1


    return {
        "statusCode": 200,
    }

lambda的角色

特定的许可lambda必须必须正常运行。

遵循最低特权原则的IAM政策是:

{
    "Statement": [
        {
            "Action": [
                "ec2:DescribeTags",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DeleteNetworkInterface"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Sid": "1"
        }
    ],
    "Version": "2012-10-17"
}

lambda触发器

使用Amazon EventBridge调用Lambda分为两个步骤:
1-创建一个规则,该规则每次都会触发
2-将lambda分配为规则

的目标

SNS的任何错误

如果lambda发生任何生成的错误,则必须收到电子邮件解决错误。

需要3个AWS服务:

  1. SNS主题
  2. SNS订阅
  3. CloudWatch警报

创建SNS主题非常简单。
选择标准一个,命名,然后将其余作为默认值。

对于SNS订阅,这甚至更容易!
选择您要订阅的主题和协议并添加您的电子邮件!

对于CloudWatch警报,下面的屏幕截图说明了如何设置它们:

Alarm1

Alarm2

Alarm3

基础架构作为代码

受益于一致性,速度和减少人为错误。让我们使用Terraform部署我们的基础架构:

lambda函数:

ps: 您的python代码 main.py.py 将在与您的Terraform项目同一目录中的SRC目录下。

module "lambda_clean_eni" {
  source = "terraform-aws-modules/lambda/aws"

  function_name = format("clean_eni")
  description   = "Delete Unused Available ENIs in Subnets that contains EKS Clusters"
  handler       = "main.lambda_handler"
  runtime       = "python3.9"
  publish       = true
  role_name     = "Lambda-Clean-ENI"

  memory_size = 200
  timeout     = 600

  attach_cloudwatch_logs_policy = true

  attach_policy_jsons    = true
  number_of_policy_jsons = 1
  policy_jsons = [
    data.aws_iam_policy_document.clean_eni.json,
  ]

  source_path = "${path.module}/src"
  hash_extra  = filesha256("${path.module}/src/main.py")


  allowed_triggers = {
    EveryHourRule = {
      principal  = "events.amazonaws.com"
      source_arn = aws_cloudwatch_event_rule.clean_eni.arn
    }
  }

  attach_network_policy = true
}

lambda iam角色策略

data "aws_iam_policy_document" "clean_eni" {
  statement {
    sid = "1"
    actions = [
      "ec2:DeleteNetworkInterface",
      "ec2:DescribeNetworkInterfaces",
      "ec2:DescribeTags",
    ]
    effect    = "Allow"
    resources = ["*"]
  }
}

eventbridge:

resource "aws_cloudwatch_event_rule" "clean_eni" {
  name                = "Clean-Eni-Lambda-Rule"
  description         = "Fires once everyday"
  schedule_expression = "rate(1 day)"
}

resource "aws_cloudwatch_event_target" "clean_eni" {
  rule = aws_cloudwatch_event_rule.clean_eni.name
  arn  = module.lambda_clean_eni.lambda_function_arn
}

CloudWatch警报:

module "alarm_lambda_clean_eni" {
  source  = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarm"

  create_metric_alarm = true

  alarm_name                = "Lambda-clean-eni-error"
  alarm_description         = "Lambda error rate is too high"
  comparison_operator       = "GreaterThanOrEqualToThreshold"
  insufficient_data_actions = []
  evaluation_periods        = 1
  threshold                 = 1
  alarm_actions             = [aws_sns_topic.alarm_error.arn]

  metric_query = [{
    id          = "1"
    return_data = true
    label       = "Error Count"
    metric = [{
      namespace   = "AWS/Lambda"
      metric_name = "Errors"
      period      = 60
      stat        = "Sum"
      unit        = "Count"
      dimensions = {
        FunctionName = module.lambda_clean_eni.lambda_function_name
      }
    }]
  }]
}

sns主题和订阅:

resource "aws_sns_topic" "alarm-error" {
  name = "alarm-error"
}

resource "aws_sns_topic_subscription" "alarm-error-sub" {
  topic_arn = aws_sns_topic.alarm-error.arn
  protocol = "email"
  endpoint = "your@email.com"  
}

摘要使用此解决方案,我们能够成功减轻子网中的IP地址的问题。

旁注:您可以根据自己喜欢自定义过滤器,因此请随时探索如何使它们适合您的环境!