Чтобы создать внутреннюю часть для бессерверного приложения, следуйте пошаговой инструкции, приведенной ниже. Щелчком по номеру шага можно раскрывать соответствующие разделы.

  • Шаг 1. Создание политики IAM для настраиваемой роли IAM.

    Чтобы предоставить разрешения на обработку внутренних запросов для вашего веб-сайта, вы создаете настраиваемую политику IAM, которая предоставляет разрешения для следующих сервисов AWS:

    • Step Functions
      • StartExecution (stateMachine:examplecorp_eao)
      • Sts:AssumeRole
    • Lambda
      • function:examplecorp_eao_createfleet
      • function:examplecorp_eao_createimagebuilder
      • function:examplecorp_eao_getimagename
      • function:examplecorp_eao_getstatus
      • function:examplecorp_eao_sendstreamingurl
      • function:examplecorp_eao_stopresources
      • Sts:AssumeRole
    • AppStream 2.0
      • AssociateFleet (Fleet/*)
      • CreateFleet (Fleet/*)
      • CreateImageBuilder (Image-Builder/*)
      • CreateImageBuilderStreamingURL (Image-Builder/*)
      • CreateStack (Stack/*)
      • CreateStreamingURL (Fleet/*)
      • DescribeFleets (Fleet/*)
      • DescribeImageBuilders (Image-Builder/*)
      • DescribeImages (Image/*)
      • StartFleet (Fleet/*)
      • StartImageBuilder (Image-Builder/*)
      • StopFleet (Fleet/*)
      • StopImageBuilder (Image-Builder/*)
    • SES
      • SendEmail
    • API Gateway
      • Sts:AssumeRole
    • CloudWatch
      • *

    Выполните следующие шаги, чтобы создать новую настраиваемую политику IAM.

     1. Откройте консоль IAM по адресу https://console.aws.amazon.com/iam/.

     2. В области навигации выберите раздел Политики.

     3. Если раздел Политики открывается впервые, вы попадете на страницу Добро пожаловать в управляемые политики. Выберите пункт Начать работу.

     4. Выберите пункт Создать политику.

     5. Перейдите на вкладку JSON.

     6. Скопируйте и вставьте следующую политику JSON в поле документа политики.

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": [
                    "lambda:InvokeFunction",
                    "appstream:DescribeImages",
                    "appstream:StartFleet",
                    "appstream:StopImageBuilder",
                    "appstream:CreateStack",
                    "appstream:CreateImageBuilderStreamingURL",
                    "appstream:AssociateFleet",
                    "appstream:DescribeImageBuilders",
                    "appstream:StopFleet",
                    "appstream:CreateImageBuilder",
                    "appstream:CreateFleet",
                    "appstream:DescribeFleets",
                    "states:StartExecution",
                    "appstream:StartImageBuilder",
                    "appstream:CreateStreamingURL"
                ],
                "Resource": [
                    "arn:aws:appstream:*:*:fleet/*",
                    "arn:aws:appstream:*:*:image/*",
                    "arn:aws:appstream:*:*:stack/*",
                    "arn:aws:appstream:*:*:image-builder/*",
                    "arn:aws:states:<REGION-CODE>:<AWS-ACCOUNT-ID>:stateMachine:examplecorp_eao",
                    "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_sendstreamingurl",
                    "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_createimagebuilder",
                    "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_getstatus",
                    "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_getimagename",
                    "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_createfleet",
                    "arn:aws:lambda:<REGION-CODE>:<AWS-ACCOUNT-ID>:function:examplecorp_eao_stopresources"
                ]
            },
            {
                "Sid": "VisualEditor1",
                "Effect": "Allow",
                "Action": [
                    "ses:SendEmail",
                    "logs:CreateLogStream",
                    "cloudwatch:*",
                    "logs:CreateLogGroup",
                    "logs:PutLogEvents"
                ],
                "Resource": "*"
            }
        ]
    }
    

     7. Замените заполнитель <REGION-CODE> значением, соответствующим региону AWS, в котором вы развертываете портал для внедрения приложения. Вводите значение в нижнем регистре. Список кодов регионов AWS для сервиса AppStream 2.0 содержится в столбце Регион таблицы URL-адреса Amazon AppStream 2.0 в Общем справочнике по AWS. Замените заполнитель <AWS-ACCOUNT-ID> своим идентификатором аккаунта AWS.

     8. После этого выберите пункт Проверить политику.

     9. В поле Имя введите следующее имя новой политики: examplecorp_eao_policy.

     10. Выберите пункт Создать политику.

  • Шаг 2. Создайте роль IAM, связанную с сервисом, которая позволит функциям Lambda вызывать сервисы AWS.

    Для Lambda требуется связанная с сервисом роль IAM, чтобы сервис мог получать доступ к ресурсам других сервисов от вашего имени. Выполните следующие шаги, чтобы создать роль IAM, связанную с сервисом, и подключить созданную политику к этой роли.

       1. Откройте консоль IAM по адресу https://console.aws.amazon.com/iam/.

       2. В области навигации в разделе Роли выберите запись Создать роль.

       3. В разделе Выбор типа доверенного объекта не снимайте флажок рядом с пунктом Сервис AWS.

       4. Выберите пункт Lambda и нажмите кнопку Далее: разрешения.

       5. В поле поиска Политики фильтрации введите examplecorp_eao_policy. Когда политика появится в списке, установите флажок рядом с ее именем.

       6. Нажмите кнопку Далее: теги. Для политики можно задать тег, но это не обязательно.

       7. Нажмите кнопку Далее: обзор.

       8. В поле «Имя роли» введите examplecorp_eao_role.

       9. Выберите Создать роль.

       10. В поле поиска введите examplecorp_eao_role. Когда роль появится в списке, выберите имя роли.

       11. В верхней части страницы Информация появится запись ARN роли. Сохраните его себе. Оно потребуется далее в проекте.

       12. Перейдите на вкладку Доверительные отношения и выберите раздел Редактирование доверительных отношений.

       13. В разделе Документ политики настройте политику доверительных отношений, включив в нее действие sts:AssumeRole для субъектов-служб сервисов lambda.amazonaws.com, states.amazonaws.com и apigateway.amazonaws.com. Формат должен соответствовать указанному далее.

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "",
          "Effect": "Allow",
          "Principal": {
            "Service": [
              "lambda.amazonaws.com",
              “states.amazonaws.com”,
              "apigateway.amazonaws.com"
            ]
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }

    14. После этого нажмите кнопку Обновить политику доверия, чтобы сохранить изменения.  

  • Шаг 3. Создание и настройка шести функций Lambda.

    Выполните следующие шаги, чтобы создать шесть функций Lambda.

       1. Откройте консоль Lambda по адресу https://console.aws.amazon.com/lambda/.

       2. Выполните одно из следующих действий.

          • Если у вас нет созданных функций Lambda, отобразится страница «Начало работы». На странице Начало работы нажмите кнопку Создать функцию.

          • Если у вас есть созданная функция Lambda, нажмите кнопку Создать функцию в правом верхнем углу страницы Функции.

       3. На странице Создание функции не снимайте флажок рядом с пунктом Создать с нуля.

       4. На вкладке Основная информация выполните указанные ниже действия.

          • В поле Имя введите examplecorp_eao_createimagebuilder.

          • Для параметра Среда выполнения выберите значение Python 3.7.

       5. В разделе Разрешения щелкните пиктограмму рядом с пунктом Выбор или создание роли выполнения. Выполните указанные ниже действия.

          • Для параметра Роль выполнения выберите значение Использовать существующую роль.

          • Для параметра Существующая роль выберите из списка значение examplecorp_eao_role.

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

       7. В разделе Код функции на вкладке lambda_function отображается код заполнителя. Удалите код заполнителя, затем скопируйте и вставьте следующий код на вкладку:

    from __future__ import print_function
    import json
    import boto3
    appstream = boto3.client('appstream')
    
    def lambda_handler(event, context):
        # Log the received event
        print("Received event: " + json.dumps(event, indent=2))
        # Get variables from web form
        strIBName = event['ib_name']
        strIBARN = event['ib_arn']
        strInstType = event['instance_type']
        strSubnetId = event['subnet1id']
        strSecurityGroups = event['securitygroups']
        strUserEmail = event['email']
        strUserName = event['user_name']
        strUserEmail = strUserEmail.strip()
        strUserName= strUserName.strip()  
    
        try:
            response = appstream.create_image_builder(
                Name=strIBName,
                ImageArn=strIBARN,
                VpcConfig={
                    'SubnetIds':[
                        strSubnetId
                        ],
                    'SecurityGroupIds':[
                        strSecurityGroups
                        ]
                },
                InstanceType=strInstType)
            print("Started: " + strIBName)
            return 1
        except Exception as e:
            print(e)
            message = 'Error creating image builder'
            print(message)
    

       8. Сохраните функцию. Она создает элемент построения образа AppStream 2.0.

       9. Выполните шаги 2–8 для каждой из следующих функций Lambda.
           Примечание. В шаге 7 вставьте код для соответствующей функции Lambda.

    Функция Lambda 2. Получение статуса элемента построения образа и группы инстансов AppStream 2.0

    После завершения создания функции присвойте ей имя examplecorp_eao_getstatus.

    from __future__ import print_function
    
    import json
    import boto3
    
    print('Loading function')
    appstream = boto3.client('appstream')
    
    def Status_to_Return(received_state):
        switcher = {
        "PENDING": 0,
        "RUNNING": 1,
        "SNAPSHOTTING": 2,
        "STOPPED": 3,
        "STOPPING": 4,
        "STARTING": 5  
        }
        return switcher.get(received_state, -1)
    
    def lambda_handler(event, context):
        # Log the received event
        print("Received event: " + json.dumps(event, indent=2))
        # Get names to query against
        strIBName = event['ib_name']
        strFleetName = event['fleet_name']
        strType = event['type']
        try:
            #Fleet status check
            if strType == 2:
                print ("Type is Fleet")
                AS2state = appstream.describe_fleets(Names=[strFleetName])
                Status = AS2state['Fleets'][0]['State']
                print ("State: " + Status)
            #Image Builder status check
            elif strType == 1:
                print ("Type is ImageBuilder")
                AS2state = appstream.describe_image_builders(Names=[strIBName])
                Status = AS2state['ImageBuilders'][0]['State']
                print ("State: " + Status)
            return Status_to_Return(Status)
        except Exception as e:
            print(e)
            print("Error getting status")
            return -1
    

    Функция Lambda 3. Отправка URL‑адреса для потоковой передачи

    После завершения создания функции присвойте ей имя examplecorp_eao_sendstreamingurl.

    Примечание. Прежде чем сохранить эту функцию, найдите заполнитель <REGION-CODE> и замените его кодом региона AWS, в котором вы настроили сервер SES. Затем найдите значение Source='noreply@example.com' и замените его адресом электронной почты, подтвержденным в сервисе SES в предыдущем модуле 

    from __future__ import print_function
    
    import json
    import boto3
    
    print('Loading function')
    appstream = boto3.client('appstream')
    ses = boto3.client('ses',region_name=<REGION-CODE>)
    
    def lambda_handler(event, context):
        # Log the received event
        print("Received event: " + json.dumps(event, indent=2))
        # Get resource names
        strUserName = event['user_name']
        strUserName = strUserName.strip()
        strEmail = event['email']
        strEmail = strEmail.strip()
        strIBName = event['ib_name']
        strFleetName = event['fleet_name']
        strStackName = event['stack_name']
        strType = event['type']
    
        try:
            #Create Fleet Streaming URL
            if strType == 2:
                print ("Type is Fleet")
                mailBody = "Your fleet is now created and ready for verification. Please use the connect link below to connect to the instance."
                url = appstream.create_streaming_url(
                    StackName=strStackName,
                    FleetName=strFleetName,
                    UserId=strUserName,
                    Validity=604800
                )            
            #Create Image Builder Streaming URL
            elif strType == 1:
                print ("Type is ImageBuilder")
                mailBody = "If this is your first time installing your application with AppStream2.0, you can check the getting started guide below for assistance. Otherwise, please click the connect link below to get started."
                url = appstream.create_image_builder_streaming_url(
                    Name=strIBName,
                    Validity=604800
                )
            strStreamingURL = url['StreamingURL']
            print("Streaming URL: " + strStreamingURL)
            #Send Streaming URL in E-mail via SES
            ses.send_email(
                Source='noreply@example.com',
                Destination={
                    'ToAddresses': [strEmail],
                            },
                Message={
                    'Body': {
                    'Html': {
                    'Charset': 'UTF-8',
                    'Data': '<html><head><title>Example Corp</title></head><body bgcolor="#e0e0e0">' + strUserName + ',<p>Thank you for trying out the Example Corp <strong>Enterprise Application Onboarding</strong> demo. ' + mailBody + '</p><p><a href=" ' + strStreamingURL + ' ">Connect</a></p><a href="https://d1.awsstatic.com/product-marketing/AppStream2.0/Amazon%20AppStream%202.0%20Getting%20Started%20Guide%20April%202018.pdf">Getting Started Guide</a></body></html>',
                    },
                    },
                    'Subject': {
                    'Charset': 'UTF-8',
                    'Data': 'Example Corp Enterprise Application Onboarding for ' + strUserName                    
                    },
                }
            )
            return 1
    
        except Exception as e:
            print(e)
            print("Error getting streaming URL")
            return -1
    

    Функция Lambda 4. Получение имени образа AppStream 2.0

    После завершения создания функции присвойте ей имя examplecorp_eao_getimagename.

    from __future__ import print_function
    
    import json
    import boto3
    import datetime
    
    print('Loading function')
    appstream = boto3.client('appstream')
    
    def lambda_handler(event, context):
        # Log the received event
        print("Received event: " + json.dumps(event, indent=2))
        # Get names to query against
        strIBName = event['ib_name']
        try:
            ImagesCMD = appstream.describe_images(Type='PRIVATE')
            Images = ImagesCMD['Images']
            for Image in Images:
                if Image.get('ImageBuilderName','') == strIBName:
                    ImageTime = Image['CreatedTime']
                    now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
                    howold = now - ImageTime
                    HowOldSec = howold.total_seconds()
                    if HowOldSec < 3600:
                        imagename = Image['Name']
                        return (imagename)
        except Exception as e:
            print(e)
            print("Error getting Image Name")
            return -1
    

    Функция Lambda 5. Создание группы инстансов и стека AppStream и установление связи между ними

    После завершения создания функции присвойте ей имя examplecorp_eao_createfleet.

    from __future__ import print_function
    import json
    import boto3
    print('Loading function')
    appstream = boto3.client('appstream')
    
    def lambda_handler(event, context):
        # Log the received event
        print("Received event: " + json.dumps(event, indent=2))
        strFleetName = event['fleet_name']
        strStackName = event['stack_name']
        strImageName = event['imagename']
        strInstType = event['instance_type']
        strSubnet1Id = event['subnet1id']
        strSubnet2Id = event['subnet2id']
        strSecurityGroups = event['securitygroups']
    
        try:
            #Create AppStream fleet
            fleet = appstream.create_fleet(
                Name=strFleetName,
                ImageName=strImageName,
                ComputeCapacity={
                    'DesiredInstances': 1
                },            
                VpcConfig={
                    'SubnetIds':[
                        strSubnet1Id,
                        strSubnet2Id
                        ],
                    'SecurityGroupIds':[
                        strSecurityGroups
                        ]
                },
                InstanceType=strInstType)
            #Create AppStream stack
            stack = appstream.create_stack(
                Name=strStackName,
                StorageConnectors=[
                    {
                        'ConnectorType': 'HOMEFOLDERS'
                    },
                ],
                UserSettings=[
                    {
                        'Action': 'CLIPBOARD_COPY_FROM_LOCAL_DEVICE',
                        'Permission': 'ENABLED'
                    },
                ],
                ApplicationSettings={
                    'Enabled': False
                }
            )
            #Associate Stack to Fleet
            associate = appstream.associate_fleet(
                FleetName=strFleetName,
                StackName=strStackName
            )
            startfleet = appstream.start_fleet(
                Name=strFleetName
            )
            return 2
        except Exception as e:
            print(e)
            print("Error creating fleet")
            return -1
    

    Функция Lambda 6. Прекращение работы группы инстансов и элемента построения образа AppStream 2.0

    После завершения создания функции присвойте ей имя examplecorp_eao_stopresources.

    from __future__ import print_function
    
    import json
    import boto3
    
    print('Loading function')
    appstream = boto3.client('appstream')
    
    def Status_to_Return(received_state):
        switcher = {
        "PENDING": 0,
        "RUNNING": 1,
        "SNAPSHOTTING": 2,
        "STOPPED": 3,
        "STOPPING": 4
        }
        return switcher.get(received_state, -1)
    
    def lambda_handler(event, context):
        # Log the received event
        print("Received event: " + json.dumps(event, indent=2))
        # Get names to query against
        strIBName = event['ib_name']
        strFleetName = event['fleet_name']
        strType = event['type']
        try:
            #Fleet status check
            if strType == 2:
                print ("Type is Fleet")
                AS2state = appstream.describe_fleets(Names=[strFleetName])
                Status = AS2state['Fleets'][0]['State']
                State = Status_to_Return(Status)
                if State < 3:            
                    print (strFleetName + " is " + Status + " attempting to stop")
                    AS2stop = appstream.stop_fleet(Name=strFleetName)
                print ("State: " + Status)
            #Image Builder status check
            elif strType == 1:
                print ("Type is ImageBuilder")
                AS2state = appstream.describe_image_builders(Names=[strIBName])
                Status = response['ImageBuilders'][0]['State']
                State = Status_to_Return(Status)
                if State < 3:
                    print (strIBName + " is " + Status + " attempting to stop")
                    AS2stop = appstream.stop_image_builder(Names=[strIBName])
                print ("State: " + Status)
            return Status_to_Return(Status)
        except Exception as e:
            print(e)
            print("Error stopping services")
            return -1