Рубрики
Без рубрики

Загрузить файл на S3 -> Запустите экземпляр EC2

Как настроить триггер S3 для запуска экземпляра EC2 через функцию лямбда. Теги с AWS, Python, DevOps, Serverless.

Сценарий

Пользователь хочет запустить рабочую нагрузку обработки данных (например, ML) на экземпляре EC2. Данные, которые будут обработаны в этой рабочей нагрузке, представляет собой файл CSV, хранящийся в ведре S3.

В настоящее время пользователь должен вручную раскрутить экземпляр EC2 (с помощью SCRIPT CLIPE MANIFIOR MANCESIONS I .

Разве это не было бы здорово, если это может быть автоматизировано? Таким образом, что создание файла в ведре S3 (из любого источника) автоматически включает соответствующий экземпляр EC2 для обработки файла? И создание выходного файла в том же веке также также будет автоматически прекращать все актуальные экземпляры (например, Tagged) EC2?

Требования

Используйте случай 1.

Когда CSV загружен в входы «Справочник» в данном ведре S3, экземпляром обработки данных EC2 «) (т. Е. Tagged с {« Целью »:« Обработка данных »} ) должна быть запущена, но только если« обработка данных » Тежный экземпляр еще не работает. Один экземпляр за время достаточно для обработки рабочих нагрузок.

Используйте Case 2.

Когда новый файл создан в Выходы «Справочник» в одном ведре S3, он означает, что рабочая нагрузка завершила обработку, и все «обработка данных» – теперь должны быть идентифицированы и прекращены.

Как объяснено на Настройка уведомлений о событиях Amazon S3 S3 может отправлять уведомления на:

  • Создание объекта
  • Удаление объекта
  • Восстановление объекта
  • События репликации
  • Rrs сбои

S3 может опубликовать эти события до 3 возможных направлений:

  • SNS.
  • SQS.
  • Лямбда

Мы хотим S3, чтобы нажать Создание объекта Уведомления непосредственно к функции лямбда, которая будет иметь необходимую логику для обработки этих событий и определять дальнейшие действия.

Вот как это сделать с помощью Python3 и BOTO3 в простой ламбдах.

AWS Console: Настройка ведра S3

Перейти к консоли AWS S3. Перейдите в ведро, которое вы хотите использовать (или создать новый с настройками по умолчанию) для запуска вашей функции Lambda.

Создание трех «папок» в этом ведре («папки/каталоги» в S3 на самом деле на самом деле «ключевые префиксы» или пути), как так:

Для чего эти настройки?

  • config : Удерживать различные настройки для функции лямбда
  • входы : Загруженный здесь файл CSV, запускает рабочую нагрузку ML на экземпляре EC2
  • Выходы : Любой файл здесь указывает на завершение рабочей нагрузки ML и должен привести к тому, что любая работающая обработка данных (I.E. специально помечена) EC2 для завершения

Папка конфигурации

Нам нужно предоставить информацию о запуске EC2 в функцию лямбда. «Запустить информацию», мы имеем в виду, тип экземпляра, тип AMI-ID, группы безопасности, теги, SSH Keypair, пользовательские данные и т. Д.

Один из способов сделать это – это сложный код все это в сам код лямбда, но это никогда не является хорошей идеей. Всегда лучше использовать конфигурацию и один способ сделать это, сохраняя его в самом ведре S3, как так:

Сохраните это как EC2-Launch-Config.json в config папка:

{
    "ami": "ami-0123b531fc646552f",
    "region": "ap-south-1",
    "instance_type": "t2.nano",
    "ssh_key_name": "ssh-connect",
    "security_group_ids": [
        "sg-08b6b31110601e924"
    ],
    "filter_tag_key": "Purpose",
    "filter_tag_value": "data-processing",
    "set_new_instance_tags": [
        {
            "Key": "Purpose",
            "Value": "data-processing"
        },
        {
            "Key": "Name",
            "Value": "ML-runner"
        }
    ]
}

Пармы довольно явно объясняют, и вы можете настроить их, как вам нужно.

"filter_tag_key": «Цель» : "filter_tag_value": «обработка данных» -> Это тег, который будет использоваться для идентификации (I.E. Filter) уже работает обработка данных EC2 экземпляры.

Вы заметите, что пользовательские данные не является частью вышеуказанного json Config. Это читается из отдельного файла под названием пользовательские данные , просто так что легче писать и поддерживать:

#!/bin/bash
apt-get update -y
apt-get install -y apache2
systemctl start apache2
systemctl enable apache2.service
echo "Congrats, your setup is a success!" > /var/www/html/index.html

Вышеуказанное пользовательские данные Скрипт установит веб-сервер Apache2 и записывает поздравительное сообщение, которое будет подано в публичном IP-адресе экземпляра.

Логика функции лямбда

Функция лямбда должна:

  • Получите входящее уведомление о создании объекта S3 JSON Object
  • Разбейте имя ведра S3 и имя ключа объекта S3 из JSON
  • Потяните в конфигурацию запуска EC2 JSON, ранее хранятся в S3
  • Если клавиша объекта S3 (I.E. «Справочник») совпадает с ^ входы/ , проверьте, нужно ли начать новый экземпляр EC2 и если это так, запустите один
  • Если клавиша объекта S3 (I.E. «Справочник») совпадает с ^ Выходы/ Завершите любую бегущую меченные экземпляры

AWS Console: Новая настройка функции лямбда

Перейдите в консоль aws Lambda и нажмите на Создать функцию кнопка.

Выберите Автор с нуля , введите имя функции и выберите Python 3.8 как время выполнения.

Выберите Создать новую роль с базовыми разрешениями лямбда в Выберите или создайте роль выполнения падать.

Нажмите на Создать функцию кнопка.

Это приведет вас к вкладке «Конфигурация».

Нажмите на Добавить триггер кнопка:

Настройка триггера S3 с рассматриваемого ведра, вроде:

  • Выберите ваше ведро
  • Выберите интересующие вас события ( Все объекты создают события В этом случае)
  • Уйти Префикс и Суффикс пусто, как мы позаботимся о префиксах ( входы и Выходы Ведные пути) в нашей функции
  • Выберите Включить триггер

Как говорится у дна скриншота:

Лямбда добавит необходимые разрешения для Amazon S3, чтобы вызвать вашу функцию лямбда с этого триггера. Поэтому нам не нужно идти в S3, чтобы настроить уведомления отдельно.

  • Нажмите на Добавить Кнопка для сохранения этой конфигурации триггера.

Вернуться на основной вкладку LAMBDA Designer, вы заметите, что S3 теперь связан с нашей недавно созданной функцией Lambda:

Нажмите на имя функции лямбда, чтобы открыть Функциональный код . Редактор под панелью дизайнера.

Лямбда код функции

Скопируйте следующий код функции лямбда в Функциональный код . Редактор, затем нажмите на Сохранить кнопка на верхнем праве.

import boto3
import json
import base64
from urllib.parse import unquote_plus


BUCKET_NAME = "YOUR_S3_BUCKET_NAME"
CONFIG_FILE_KEY = "config/ec2-launch-config.json"
USER_DATA_FILE_KEY = "config/user-data"
BUCKET_INPUT_DIR = "inputs"
BUCKET_OUTPUT_DIR = "outputs"


def launch_instance(EC2, config, user_data):
    tag_specs = [{}]
    tag_specs[0]['ResourceType'] = 'instance'
    tag_specs[0]['Tags'] = config['set_new_instance_tags']

    ec2_response = EC2.run_instances(
        ImageId=config['ami'],  # ami-0123b531fc646552f
        InstanceType=config['instance_type'],   # t2.nano
        KeyName=config['ssh_key_name'],  # ambar-default
        MinCount=1,
        MaxCount=1,
        SecurityGroupIds=config['security_group_ids'],  # sg-08b6b31110601e924
        TagSpecifications=tag_specs,
        # UserData=base64.b64encode(user_data).decode("ascii")
        UserData=user_data
    )

    new_instance_resp = ec2_response['Instances'][0]
    instance_id = new_instance_resp['InstanceId']
    # print(f"[DEBUG] Full ec2 instance response data for '{instance_id}': {new_instance_resp}")

    return (instance_id, new_instance_resp)



def lambda_handler(raw_event, context):
    print(f"Received raw event: {raw_event}")
    # event = raw_event['Records']

    for record in raw_event['Records']:
        bucket = record['s3']['bucket']['name']
        print(f"Triggering S3 Bucket: {bucket}")
        key = unquote_plus(record['s3']['object']['key'])
        print(f"Triggering key in S3: {key}")

        # get config from config file stored in S3
        S3 = boto3.client('s3')
        result = S3.get_object(Bucket=BUCKET_NAME, Key=CONFIG_FILE_KEY)
        ec2_config = json.loads(result["Body"].read().decode())
        print(f"Config from S3: {ec2_config}")

        ec2_filters = [
            {
                'Name': f"tag:{ec2_config['filter_tag_key']}",
                'Values':[ ec2_config['filter_tag_value'] ]
            }
        ]

        EC2 = boto3.client('ec2', region_name=ec2_config['region'])

        # launch new EC2 instance if necessary
        if bucket == BUCKET_NAME and key.startswith(f"{BUCKET_INPUT_DIR}/"):
            print("[INFO] Describing EC2 instances with target tags...")
            resp = EC2.describe_instances(Filters=ec2_filters)
            # print(f"[DEBUG] describe_instances response: {resp}")

            if resp["Reservations"] is not []:    # at least one instance with target tags was found
                for reservation in resp["Reservations"] :
                    for instance in reservation["Instances"]:
                        print(f"[INFO] Found '{instance['State']['Name']}' instance '{ instance['InstanceId'] }'"
                            f" having target tags: {instance['Tags']} ")

                        if instance['State']['Code'] == 16: # instance has target tags AND also is in running state
                            print(f"[INFO] instance '{ instance['InstanceId'] }' is already running: so not launching any more instances")
                            return {
                                "newInstanceLaunched": False,
                                "old-instanceId": instance['InstanceId'],
                                "new-instanceId": ""
                            }

            print("[INFO] Could not find even a single running instance matching the desired tag, launching a new one")

            # retrieve EC2 user-data for launch
            result = S3.get_object(Bucket=BUCKET_NAME, Key=USER_DATA_FILE_KEY)
            user_data = result["Body"].read()
            print(f"UserData from S3: {user_data}")

            result = launch_instance(EC2, ec2_config, user_data)
            print(f"[INFO] LAUNCHED EC2 instance-id '{result[0]}'")
            # print(f"[DEBUG] EC2 launch_resp:\n {result[1]}")
            return {
                "newInstanceLaunched": True,
                "old-instanceId": "",
                "new-instanceId": result[0]
            }

        # terminate all tagged EC2 instances
        if bucket == BUCKET_NAME and key.startswith(f"{BUCKET_OUTPUT_DIR}/"):
            print("[INFO] Describing EC2 instances with target tags...")
            resp = EC2.describe_instances(Filters=ec2_filters)
            # print(f"[DEBUG] describe_instances response: {resp}")
            terminated_instance_ids = []

            if resp["Reservations"] is not []:    # at least one instance with target tags was found
                for reservation in resp["Reservations"] :
                    for instance in reservation["Instances"]:
                        print(f"[INFO] Found '{instance['State']['Name']}' instance '{ instance['InstanceId'] }'"
                            f" having target tags: {instance['Tags']} ")

                        if instance['State']['Code'] == 16: # instance has target tags AND also is in running state
                            print(f"[INFO] instance '{ instance['InstanceId'] }' is running: terminating it")
                            terminated_instance_ids.append(instance['InstanceId'])
                            boto3.resource('ec2').Instance(instance['InstanceId']).terminate()

            return {
                "terminated-instance-ids:": terminated_instance_ids
            }

Лямбда исполнение IAM роль

Наша функция лямбда не будет работать только. Он нуждается в доступе S3 для чтения на конфиге и нуждается в разрешениях для описания, запуска и завершения экземпляров EC2.

Прокрутите вниз на странице конфигурации лямбда к Роль исполнения и нажмите на Смотреть роль связь:

Нажмите на AwslAbmdabasic ... Ссылка для редактирования политики функции Lambda:

Нажмите {} JSON , Редактировать политику а потом JSON Отказ

Теперь добавьте следующие JSON на существующие Заявление Раздел

        {
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "*"
        },
        {
            "Action": [
                "ec2:RunInstances",
                "ec2:CreateTags",
                "ec2:ReportInstanceStatus",
                "ec2:DescribeInstanceStatus",
                "ec2:DescribeInstances",
                "ec2:TerminateInstances"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }

Нажмите Обзор политики и Сохранить изменения Отказ

Наша настройка окончательно завершается. Мы готовы проверить это!

Тестовый случай 1: Запуск нового экземпляра

Загрузите файл из консоли S3 в входы папка. Если вы использовали то же самое конфигурацию, что и выше, это сделало бы функцию лямбда, которая, в свою очередь, запустила бы новую T2.nano экземпляр с Цель: обработка данных Тег на это.

Если вы поместите публичный IP-адрес экземпляра в браузер (после того, как дали ему минуту или около того, чтобы загрузить и разогревать), вы также должны увидеть тестовое сообщение, обслуживаемое вам: что указывает на то, что пользовательские данные действительно действительно выступил успешно при загрузке.

Тестовый случай 2: другой экземпляр не должен запускать

Пока есть хотя бы один Цель: обработка данных Помеченный экземпляр работает, другой не должен появляться. Давайте загрузим еще один файл на входы папка. И действительно фактическое поведение соответствует ожиданиям.

Если мы убьем уже запущенный экземпляр, а тогда Загрузить другой файл на входы Папка, она запустит новый экземпляр.

Тестовый случай 3: условие завершения экземпляра

Загрузите файл в Выходы папка. Это заставит функцию лямбда в завершение любых уже запущенных экземпляров, которые помечены Цель: обработка данных Отказ

Бонус: объект событий S3 для проверки вашей функции лямбда

S3-объект – уведомление о создании (для проверки функции лямбда)

{
  "Records": [
    {
      "eventVersion": "2.1",
      "eventSource": "aws:s3",
      "awsRegion": "ap-south-1",
      "eventTime": "2019-09-03T19:37:27.192Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "AWS:AIDAINPONIXQXHT3IKHL2"
      },
      "requestParameters": {
        "sourceIPAddress": "205.255.255.255"
      },
      "responseElements": {
        "x-amz-request-id": "D82B88E5F771F645",
        "x-amz-id-2": 
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "828aa6fc-f7b5-4305-8584-487c791949c1",
        "bucket": {
          "name": "YOUR_S3_BUCKET_NAME",
          "ownerIdentity": {
            "principalId": "A3I5XTEXAMAI3E"
          },
          "arn": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df"
        },
        "object": {
          "key": "b21b84d653bb07b05b1e6b33684dc11b",
          "size": 1305107,
          "eTag": "b21b84d653bb07b05b1e6b33684dc11b",
          "sequencer": "0C0F6F405D6ED209E1"
        }
      }
    }
  ]
}

Это все, люди!

Оригинал: “https://dev.to/nonbeing/upload-file-to-s3-launch-ec2-instance-7m9”