跳至內容

我如何使用 AWS CloudFormation,在具有 AWS PrivateLink 端點的 Amazon EC2 上自動部署 DataSync 代理程式?

4 分的閱讀內容
0

我想使用 AWS CloudFormation 在具有 AWS PrivateLink 端點的私有子網路中,於 Amazon Elastic Compute Cloud (Amazon EC2) 上建立 AWS DataSync 代理程式。

解決方法

使用 AWS CloudFormation 自動建立並啟用透過 AWS PrivateLink 端點,在 Amazon EC2 執行的 DataSync 代理程式。

使用 YAML 範本,其中包含使用 Lambda 函式來擷取 DataSync 代理程式啟用金鑰的自訂資源。複製以下 YAML 程式碼,然後將其儲存為您的 AWS CloudFormation 範本:

AWSTemplateFormatVersion: '2010-09-09'  
Description: >-  
  This CloudFormation template deploys an EC2 instance with the AWS DataSync  
  agent AMI.  

Metadata:  
  AWS::CloudFormation::Interface:  
    ParameterGroups:  
    - Label:  
        default: Deploy DataSync agent on Amazon EC2 via PrivateLink VPC endpoint  
      Parameters:  
      - InstanceType  
      - ImageId  
      - SubnetId  
      - VpcId  
    ParameterLabels:  
      InstanceType:  
        default: Agent EC2 Instance Size  
      SubnetId:  
        default: Select your Subnet  
      VpcId:  
        default: Select your VPC Contains Above Subnet  
      ImageId:  
        default: Agent Image (Keep default - Do NOT change)  

Parameters:  
  InstanceType:  
    Description: Instance type for the DataSync agent EC2 instance  
    Type: String  
    Default: m5.2xlarge  
    AllowedValues:  
      - m5.2xlarge  
      - m5.4xlarge  
    ConstraintDescription: must be a valid EC2 instance type.  

  ImageId:  
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>  
    Default: /aws/service/datasync/ami  

  SubnetId:  
    Type: AWS::EC2::Subnet::Id  
    Description: The subnet where to deploy the instance  

  VpcId:   
    Type: AWS::EC2::VPC::Id  
    Description: Select the VPC containing the subnet selected above.  

Resources:  
  NewKeyPair:  
    Type: 'AWS::EC2::KeyPair'  
    Properties:  
      KeyName: !Sub "ssh-key-cfn-stack-${AWS::StackName}"  

  DataSyncAgentInstance:  
    Type: AWS::EC2::Instance  
    Properties:  
      ImageId: !Ref ImageId  
      InstanceType: !Ref InstanceType  
      SubnetId: !Ref SubnetId  
      SecurityGroupIds:  
        - !Ref DataSyncAgentSecurityGroup  
      KeyName: !Ref NewKeyPair  
      Tags:  
        - Key: "Name"  
          Value: !Ref 'AWS::StackName'  

  DataSyncAgentSecurityGroup:  
    Type: AWS::EC2::SecurityGroup  
    Properties:  
      GroupDescription: Enable SSH access and DataSync agent communication  
      SecurityGroupIngress:  
        - IpProtocol: tcp  
          FromPort: 80  
          ToPort: 80  
          CidrIp: 0.0.0.0/0  
      VpcId: !Ref VpcId  

  DsInterfaceEndpoint:  
    Type: 'AWS::EC2::VPCEndpoint'  
    Properties:  
      VpcEndpointType: 'Interface'  
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.datasync'  
      VpcId: !Ref VpcId  
      SubnetIds:   
        - !Ref SubnetId  
      SecurityGroupIds:  
        - !Ref DsVpceSecurityGroup  

  DsVpceSecurityGroup:  
    Type: 'AWS::EC2::SecurityGroup'  
    Properties:  
      GroupDescription: 'Allow control traffic from agent'  
      VpcId: !Ref VpcId  
      SecurityGroupIngress:  
        - IpProtocol: tcp  
          FromPort: 443  
          ToPort: 443  
          CidrIp: !Sub "${DataSyncAgentInstance.PrivateIp}/32"  
        - IpProtocol: tcp  
          FromPort: 1024  
          ToPort: 1064  
          CidrIp: !Sub "${DataSyncAgentInstance.PrivateIp}/32"  
        - IpProtocol: tcp  
          FromPort: 22  
          ToPort: 22  
          CidrIp: !Sub "${DataSyncAgentInstance.PrivateIp}/32"  

  LambdaActivator:  
      Type: AWS::Lambda::Function  
      DependsOn: DsInterfaceEndpoint  
      Properties:  
        FunctionName: !Ref 'AWS::StackName'  
        Description: 'Lambda function to get the activation key'  
        VpcConfig:   
          SecurityGroupIds:  
            - !Ref DataSyncAgentSecurityGroup  
          SubnetIds:  
            - !Ref SubnetId  
        Environment:  
          Variables:  
            DsDnsEntryList: !Select [1, !Split [':', !Select [0, !GetAtt DsInterfaceEndpoint.DnsEntries]]]  
        Code:  
          ZipFile: !Sub |  
            import boto3, json, urllib.request, socket, time, os  
            import cfnresponse  

            print('Loading function')  
            def handler(event, context):  
                print('EVENT:')  
                print(event)  
                responseData = {}  
                try:  
                    if event['RequestType'] == "Create":  
                        agentIp = "${DataSyncAgentInstance.PrivateIp}"  
                        awsRegion = "${AWS::Region}"  
                        VpceDnsName = os.environ['DsDnsEntryList']  
                        VpceIp = socket.gethostbyname(VpceDnsName)  

                        print("Agent IP: " + agentIp)  
                        print("AWS Region: " + awsRegion)  
                        print("VPC Endpoint DNS Name: " + VpceDnsName)  
                        print("VPC IP: " + VpceIp)  

                        activateUrl = "http://" + agentIp + "/?gatewayType=SYNC&activationRegion=" + awsRegion + "&privateLinkEndpoint=" + VpceIp + "&endpointType=PRIVATE_LINK&no_redirect"  
                        print(activateUrl)  

                        time.sleep(30)  
                        print("Sending URL for Getting activation key")  
                        res = urllib.request.urlopen(urllib.request.Request(  
                            url=activateUrl,  
                            method='GET'),  
                            timeout=600)  

                        print("HTTP Response :" + str(res.status) + " " + res.reason)  
                        activationKey = res.read().decode('utf-8')  

                        responseData['Data'] = activationKey  

                    elif event['RequestType'] == "Delete":  
                        print("Lambda is being deleted by the CloudFormation stack!!")  

                    else:  
                        print("Should not perform actions!!")  

                    cfnresponse.send(event, context, cfnresponse.SUCCESS,   
                            responseData, 'cfn-customresource-id')  

                    return {  
                      'body': json.dumps('Execution completed!')  
                    }  

                except Exception as e:  
                    print(e)  
                    cfnresponse.send(event, context, cfnresponse.FAILED,   
                            responseData, 'cfn-customresource-id')  
                    raise e  

        Handler: index.handler  
        Role: !GetAtt FunctionIamRole.Arn  
        Runtime: python3.12  
        Timeout: 300  

  ActivatorInvoker:  
    DependsOn:  
      - LambdaActivator  
      - DataSyncAgentInstance  
    Type: AWS::CloudFormation::CustomResource  
    Properties:  
      ServiceToken: !GetAtt LambdaActivator.Arn  

  FunctionIamRole:  
    Type: AWS::IAM::Role  
    Properties:  
      AssumeRolePolicyDocument:  
        Version: '2012-10-17'  
        Statement:  
          - Effect: Allow  
            Principal:  
              Service:  
                - lambda.amazonaws.com  
            Action:  
              - 'sts:AssumeRole'  
      ManagedPolicyArns:  
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"  
      Policies:   
        - PolicyName: !Sub "ec2-permissions-lambda-${AWS::StackName}"  
          PolicyDocument:  
            Version: '2012-10-17'  
            Statement:  
              - Effect: Allow  
                Action:  
                  - ec2:CreateNetworkInterface  
                  - ec2:DeleteNetworkInterface  
                  - ec2:DescribeNetworkInterfaces  
                Resource: "*"  

  ActiveAgent:  
    Type: AWS::DataSync::Agent  
    Properties:  
      ActivationKey: !GetAtt ActivatorInvoker.Data  
      AgentName: !Sub "datasync-agent-created-from-cfn-${AWS::StackName}"  
      VpcEndpointId: !GetAtt DsInterfaceEndpoint.Id  
      SecurityGroupArns:  
        - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/${DsVpceSecurityGroup}"  
      SubnetArns:  
        - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:subnet/${SubnetId}"  

Outputs:  
  KeyPairId:  
    Description: KeyPairId of the newly created SSH Key to connect the EC2 (you can download the key under Parameter Store, a capability of AWS Systems Manager)  
    Value: !GetAtt NewKeyPair.KeyPairId  

  PrivateIp:  
    Description: PrivateIp of the newly created EC2 instance  
    Value: !GetAtt DataSyncAgentInstance.PrivateIp  

  ActivationKey:  
    Description: Agent Activation Key  
    Value: !GetAtt ActivatorInvoker.Data

使用 AWS CloudFormation 主控台來 建立 AWS CloudFormation 堆疊。在 Create stack (建立堆疊) 頁面上,於 Prerequisite - Prepare template (先決條件 - 準備範本) 中選擇 Choose an existing template (選擇現有範本)。在 Specify template (指定範本) 下,選擇 Upload a template file (上傳範本檔案),然後選取您儲存的範本。

**注意:**在 Parameters (參數) 區段中,您指定的私有子網路必須能透過 Amazon Virtual Private Cloud (VPC) 閘道端點存取 Amazon Simple Storage Service (Amazon S3) 服務。

AWS 官方已更新 3 個月前