AWS: Lambda – копирование тегов EC2 на EBS, часть 2 – создание Lambda-функции

Автор: | 11/10/2021
 

Продолжаем создавать Lambda-функцию для копирования тегов EC2 на EBS.

В первой части – AWS: Lambda – копирование тегов EC2 на EBS, часть 1 – Python и boto3, мы написали Python-скрипт, который умеет получать список всех EC2 в регионе, и потом для каждого ЕС2 получает список всех его EBS, на которые копирует теги плюс добавляет один новый.

В этой части мы создадим AWS Lambda-функцию, которая будет тригеррится через AWS CloudWatch Event.

Создание AWS Lambda-функции

Переходим в AWS Lambda, создаём новую функцию, выбираем “Author from scratch“, Runtime – Python 3.8:

В Execution role оставляем “Create a new role” – потом к этой роли добавим нужные IAM Policy.

Вставляем код функции, в конце, в вызове lambda_hanlder() вместо 0, 0 из предыдущего поста – указываем передачу event, context:

Что бы увидеть содержимое context, из которого мы будем получать EC2 ID – добавим вывод его содержимого в лог.

Добавляем import json и print("CONTEXT: " + json.dumps(event)):

Кроме того, в вызове ec2 = boto3.resource() убираем аутентификацию через ключи, оставляем только тип ресурса ec2:

Не забываем нажать Deploy, что бы изменения кода загрузить в Lambda.

Добавление триггера Amazon EventBridge (CloudWatch Events)

Далее, нам надо эту функцию как-то запускать при появлении новых ЕС2 в регионе.

Используем Amazon EventBridge, он же CloudWatch Events. Переходим в CloudWatch Events > Rules, кликаем Create rule:

В Event Pattern выбираем Service Name == EC2, в Event Type выбираем “EC2 Instance State-change Notification“, а в Specific state(s) указываем running:

Справа в Add target выбираем Lambda function, которую создали выше:

Внизу в Show sample event(s) видим пример данных, которые мы и будем парсить, что бы получить EC2 ID:

Сохраняем правило:

Проверяем, что в самой Lambda-функции появился триггер:

Проверяем, как это всё будет работать.

В CloudWatch Rules открываем мониторинг созданного правила:

Запускаем новый инстанс, например через скейлинг AutoScale-группы, ждём его создания:

Видим, что CloudWatch Rule затриггерился:

Проверяем логи Lambda:

AWS Lambda: UnauthorizedOperation

В логах нам интересны две записи.

Первая – содержимое event, которое мы выводим через print("CONTEXT: " + json.dumps(event)):

Второе – ошибка “ClientError: An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: You are not authorized to perform this operation”:

С ошибкой всё ясно – сейчас наша Lambda-функция использует IAM-роль, созданную при создании функции, и которой не хватает прав на выполнение действий с EC2:

Переходим в AWS IAM, находим роль, кликаем Attach policies:

Создаём политику:

Описываем разрешения:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DeleteTags",
                "ec2:CreateTags",
                "ec2:DescribeVolumes"
            ],
            "Resource": "*"
        }
    ]
}

Сохраняем её:

Возвращаемся к роли, добавляем политику:

Повторяем запуск ЕС2, и ещё раз проверяем логи функции:

Ура! “It works!” (c)

Далее, нам осталось обновить саму функцию – получать из event значение instance-id.

Парсинг Lambda event

Пример event мы видели в Cloudwatch, и знаем, что он представляет собой python-dictionary с несколькими ключами:

{
    "version": "0",
    "id": "a99597e5-90a5-5ac3-3aba-da3ecd51452a",
    "detail-type": "EC2 Instance State-change Notification",
    "source": "aws.ec2",
    "account": "534***385",
    "time": "2021-10-11T09:02:09Z",
    "region": "eu-west-3",
    "resources": [
        "arn:aws:ec2:eu-west-3:534***385:instance/i-0cc24729109ba61e5"
    ],
    "detail": {
        "instance-id": "i-0cc24729109ba61e5",
        "state": "running"
    }
}

Из которого мы хотим получить detail.instance-id.

Добавляем новую переменную instance_id, значение которой получаем из event["detail"]["instance-id"], а потом используя этот ID – создаём новый объект класса ec2.Instanceec2.Instance(instance_id):

Вместо запуска ЕС2 вручную – можем создать тестовый евент, в который передадим данные.

Создаём евент:

Добвляем данные евента, которые мы будем получать от CloudWatch:

{
    "version": "0",
    "id": "a99597e5-90a5-5ac3-3aba-da3ecd51452a",
    "detail-type": "EC2 Instance State-change Notification",
    "source": "aws.ec2",
    "account": "534***385",
    "time": "2021-10-11T09:02:09Z",
    "region": "eu-west-3",
    "resources": [
        "arn:aws:ec2:eu-west-3:534***385:instance/i-0cc24729109ba61e5"
    ],
    "detail": {
        "instance-id": "i-0cc24729109ba61e5",
        "state": "running"
    }
}

Запускаем ещё раз тест:

Работает.

И запускаем создание нового ЕС2, что бы проверить всю схему.

Триггерим AutoScaling:

Запущен инстанс i-051f2e1f1bc8d332b, проверяем логи Lambda:

Находим EBS этого инстанса:

И проверяем его теги:

И код функции целиком:

#!/usr/bin/env python

import os
import json
import boto3


def lambda_handler(event, context):

    ec2 = boto3.resource('ec2')
    instance_id = event["detail"]["instance-id"]
    instance = ec2.Instance(instance_id)

    print("[DEBUG] EC2\n\t\tID:  " + str(instance))
    print("\tEBS")

    for vol in instance.volumes.all():

        vol_id = str(vol)
        print("VOLUME: " + str(vol))

        device_id = "ec2.vol.Device('" + str(vol.attachments[0]['Device']) + "')"
        print("\t\tID:  " + vol_id + "\n\t\tDev: " + device_id)

        role_tag = vol.create_tags(Tags=set_role_tag(vol))
        ec2_tags = vol.create_tags(Tags=copy_ec2_tags(instance))
        print("\t\tTags set:\n\t\t\t" + str(role_tag) + "\n\t\t\t" + str(ec2_tags) + "\n")


def is_pvc(vol): 

    try:
        for tag in vol.tags:
            if tag['Key'] == 'kubernetes.io/created-for/pvc/name':
                return True
                break
    except TypeError:
            return False


def set_role_tag(vol):
    
    device = vol.attachments[0]['Device']
    tags_list = []
    values = {}
        
    if is_pvc(vol):
        values['Key'] = "Role"
        values['Value'] = "PvcDisk"
        tags_list.append(values)
    elif device == "/dev/xvda":
        values['Key'] = "Role"
        values['Value'] = "RootDisk"
        tags_list.append(values)
    else:   
        values['Key'] = "Role"
        values['Value'] = "DataDisk"
        tags_list.append(values)

    return tags_list

    
def copy_ec2_tags(instance):
            
    tags_list = []
    values = {} 
        
    for instance_tag in instance.tags:

        if instance_tag['Key'] == 'Env':
            tags_list.append(instance_tag)
        elif instance_tag['Key'] == 'Tier':
            tags_list.append(instance_tag)
        elif instance_tag['Key'] == 'DataClass':
            tags_list.append(instance_tag)
        elif instance_tag['Key'] == 'JiraTicket':
            tags_list.append(instance_tag)

    return tags_list


if __name__ == "__main__":
    lambda_handler(event, context)

Готово.