아래의 단계별 지침에 따라 서버리스 백엔드를 구축합니다. 각 단계 번호를 클릭하면 해당 섹션이 펼쳐집니다.

  • 1단계. 사용자 지정 IAM 역할에 대한 IAM 정책을 만듭니다.

    웹 사이트에 대한 백엔드 요청을 처리하기 위한 권한을 부여하려면 다음 AWS 서비스에 대한 권한을 부여하는 사용자 지정 IAM 정책을 생성합니다.

    • 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. https://console.aws.amazon.com/iam/에서 IAM 콘솔을 엽니다.

     2. 탐색 창에서 [Policies]를 선택합니다.

     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. 애플리케이션 온보딩 포털을 배포할 AWS 리전에 해당하는 값으로 <REGION-CODE>를 바꿉니다. 값은 모두 소문자여야 합니다. AppStream 2.0의 AWS 리전 코드 목록은 AWS 일반 참조에서 Amazon AppStream 2.0 엔드포인트리전 열을 참조하십시오. <AWS-ACCOUNT-ID>를 사용자의 AWS 계정 ID로 바꿉니다.

     8. 완료하면 [정책 검토]를 선택합니다.

     9. [이름]에 새 이름의 정책을 examplecorp_eao_policy로 지정합니다.

     10. [정책 생성]을 선택합니다.

  • 2단계. Lambda 함수가 AWS 서비스를 호출할 수 있는 IAM 서비스에 연결된 역할을 생성합니다.

    Lambda에는 IAM 서비스에 연결된 역할이 있어야 서비스가 사용자를 대신하여 다른 서비스의 리소스에 액세스할 수 있습니다. 다음 단계를 완료하여 IAM 서비스에 연결된 역할을 생성하고 생성한 정책을 이 역할에 연결합니다.

       1. https://console.aws.amazon.com/iam/에서 IAM 콘솔을 엽니다.

       2. 탐색 창의 [역할] 아래에서 [역할 생성]을 선택합니다.

       3. [신뢰할 수 있는 유형의 개체 선택]에서 [AWS 서비스]를 선택합니다.

       4. [Lambda]를 선택한 후 [다음: 권한]을 선택합니다.

       5. [정책 필터링] 검색 상자에 examplecorp_eao_policy를 입력합니다. 해당 정책이 목록에 표시되면, 정책 이름 옆에 있는 확인란을 선택합니다.

       6. [다음: 태그]를 선택합니다. 정책에 태그를 지정할 수 있지만 태그는 필수가 아닙니다.

       7. [다음: 검토]를 선택합니다.

       8. 역할 이름에서 examplecorp_eao_role을 입력합니다.

       9. [역할 생성]을 선택합니다.

       10. 검색 상자에 examplecorp_eao_role을 입력합니다. 해당 역할이 목록에 표시되면 역할의 이름을 선택합니다.

       11. [요약] 페이지의 상단에는 [역할 ARN]이 표시되어 있습니다. 이 ARN을 기록해 둡니다. 이 ARN은 프로젝트에서 나중에 필요합니다.

       12. [신뢰 관계] 탭을 선택하고 [신뢰 관계 편집]을 선택합니다.

       13. [정책 문서] 아래에서, lambda.amazonaws.com, states.amazonaws.comapigateway.amazonaws.com 서비스 사용자에 대한 sts:AssumeRole 작업이 포함되도록 신뢰 관계 정책을 구성합니다. 형식은 다음과 같이 표시되어야 합니다.

    {
      "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 함수 6개를 생성 및 구성합니다.

    다음 단계를 따라 Lambda 함수 6개를 생성합니다.

       1. https://console.aws.amazon.com/lambda/에서 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. 다음 각 Lambda 함수에서 2~8단계를 반복합니다.
           참고: 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>를 검색한 후 SES를 구성한 AWS 리전에 해당하는 코드로 리전 코드를 바꾸십시오. 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