以下に示すステップバイステップの手順に従い、サーバーレスバックエンドを構築します。各ステップの番号をクリックして、セクションを展開してください。

  • ステップ 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 ゲートウェイ
      • Sts:AssumeRole
    • CloudWatch
      • *

    カスタム IAM ポリシーを作成するには、次の手順を実行します。

     1.IAM コンソールを https://console.aws.amazon.com/iam/ で開きます。

     2.ナビゲーションペインで、[Policies] を選択します。

     3.[Policies] を初めて選択した場合、[Welcome to Managed Policies] ページが表示されます。[Get Started] を選択します。

     4.[Create policy] を選択します。

     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 リージョンに対応する値に置き換えます。値はすべて小文字でなければなりません。AppStream 2.0 の AWS リージョンコード一覧については、AWS General ReferenceAmazon AppStream 2.0 エンドポイント表の [リージョン] 列を参照してください。<AWS-ACCOUNT-ID> を AWS アカウント ID に置き換えます。

     8.完了したら、[Get Started] をクリックします。

     9.[名前] には、新しいポリシーの名前として [examplecorp_eao_policy] と入力します。

     10.[Create policy] を選択します。

  • ステップ 2:IAM サービスにリンクされたロールを作成して、Lambda 関数が AWS サービスを呼び出せるようにする

    Lambda がユーザーに代わって他のサービスのリソースにアクセスできるようにするには、IAM サービスにリンクされたロールが必要です。次の手順を実行して IAM サービスにリンクされたロールを作成し、作成したポリシーをこのロールにアタッチします。

       1.IAM コンソールを https://console.aws.amazon.com/iam/ で開きます。

       2.ナビゲーションペインで [Roles] をクリックしてから [Create role] をクリックします。

       3.[Select type of trusted entity] では、[AWS service] を選択したままにします。

       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.comstates.amazonaws.com、および apigateway.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:6 つの Lambda 関数を作成および設定します。

    以下の手順を実行して 6 つの 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.次の 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