Продолжаем создавать 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.Instance
— ec2.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)
Готово.