インターネットへのアウトバウンド通信が可能な仮想マシン(EC2)を作成する

SCSKの石原です。

最近、iPaaSの研修を受ける機会があり仮想マシンが複数台必要となったため、AWSにて研修用環境を用意しました。

簡単な構成ですが、NW周りの設定なども必要なため、1から作るとなると少し時間がかかるかと思います。

研修や検証などで、仮想マシンが欲しいときに当記事を活用いただけたらと思います。

研修用環境アーキテクチャ

下記の図の構成をCloudFormationを利用して作成します。この構成のポイントは下記の通りです。

  • CFnテンプレートは、共用リソースと個別リソース(1ユーザ1サブネット想定)で分割しています。
  • 仮想マシン(EC2)からはNatGWを介してアウトバウンド通信/S3エンドポイントへの通信/同一サブネット内の通信のみ許可されます。
  • S3へのアクセスは、コスト最適化のためNatGWではなくS3エンドポイントを利用しています。
  • 研修用環境を想定していますので、冗長化は全く考慮しておりません。

 

構築リソース

2つのCFnテンプレートを利用して構築します。

  • 共通リソース
  • 個別リソース

また、下記のテンプレートではS3の構成は実施しません。下記の記事などを参考に作成頂ければと思います。

S3 Intelligent-Tieringに自動的に移行されるバケットを作成する [AWS CloudFormation テンプレート付き]
今回はS3のコストを最適化してくれるストレージクラス「Intelligent-Tiering」に、自動的に移行してくれるバケットをCloudFormationでサクッと作れるテンプレートを用意しました。よりコストの安い「Archive Access tier 」と「Deep Archive Access tier」を利用するパターンも用意しています。ぜひご活用ください。

共有リソース

まずは共通リソース(VPCやNatGW・ルーティングなど)を作成します。

研修用途ですので、EC2に設定するロールに「AmazonS3FullAccess」をアタッチしていますが、必要に応じて権限を変更ください。

AWSTemplateFormatVersion: "2010-09-09"
Description: "techharmony-traning-nw Template"
Parameters:
  ProjectID:
    Type: String
    MinLength: 3
    MaxLength: 15
    Default: "th-traning"
    AllowedValues:
      - "th-traning"

Resources:
# ================================
# VPC
# ================================
  SANDBOXVPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "192.168.0.0/16"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: "default"
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-vpc"

# ================================
# Subnet
# ================================
  SubnetNW1a:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Sub "${AWS::Region}a"
      CidrBlock: "192.168.0.0/24"
      VpcId: !Ref SANDBOXVPC
      MapPublicIpOnLaunch: false
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-nw-public-1a"

# ================================
# ACL
# ================================
  NetworkAclNW:
    Type: "AWS::EC2::NetworkAcl"
    Properties:
      VpcId: !Ref SANDBOXVPC
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-nw-acl"
  
  NetworkAclEC2:
    Type: "AWS::EC2::NetworkAcl"
    Properties:
      VpcId: !Ref SANDBOXVPC
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-ec2-acl"

# # ACL association
  NetworkAclAssociationNW1a:
    Type: "AWS::EC2::SubnetNetworkAclAssociation"
    Properties:
      SubnetId: !Ref SubnetNW1a
      NetworkAclId: !Ref NetworkAclNW

# # ACL NW Egress
  NetworkAclEntryNWEg100:
    Type: "AWS::EC2::NetworkAclEntry"
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: true
      NetworkAclId: !Ref NetworkAclNW
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100

# # ACL NW Ingress
  NetworkAclEntryNWIg100:
    Type: "AWS::EC2::NetworkAclEntry"
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: false
      NetworkAclId: !Ref NetworkAclNW
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100

# # ACL EC2 Egress
  NetworkAclEntryEC2Eg100:
    Type: "AWS::EC2::NetworkAclEntry"
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: true
      NetworkAclId: !Ref NetworkAclEC2
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100

# # ACL EC2 Ingress
  NetworkAclEntryEC2Ig100:
    Type: "AWS::EC2::NetworkAclEntry"
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: false
      NetworkAclId: !Ref NetworkAclEC2
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100

# ================================
# Route Table
# ================================
  RouteTableNW:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref SANDBOXVPC
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-nw-public-rt"
  
  RouteTableEC2:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref SANDBOXVPC
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-ec2-private-rt"

# route table association
  RouteTableAssociationNW1a:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: !Ref RouteTableNW
      SubnetId: !Ref SubnetNW1a

# ================================
# NAT Gateway
# ================================
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: stack
        Value: "sandbox"

  EIPNATGW:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIPNATGW.AllocationId
      SubnetId: !Ref SubnetNW1a

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref SANDBOXVPC

  OutBoundOnlyRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NatGateway
      RouteTableId: !Ref RouteTableEC2

  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref RouteTableNW

# ================================
# Endpoint(Gateway)
# ================================
  s3Endpoint:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      VpcEndpointType: "Gateway"
      VpcId: !Ref SANDBOXVPC
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal: "*"
            Action: "*"
            Resource: "*"
      RouteTableIds:
        - !Ref RouteTableEC2
      PrivateDnsEnabled: false

# ================================
# IAM
# ================================
  EC2IAMRole:
    Type: "AWS::IAM::Role"
    Properties:
      Path: "/"
      RoleName: !Sub "${ProjectID}-ec2-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Description: "Allows EC2 S3 and SSMCore"
  
  EC2IAMInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: /
      Roles:
        - !Ref EC2IAMRole

Outputs:
  ExportParamSANDBOXVPCID:
    Description: SANDBOXVPCID
    Value: !Ref SANDBOXVPC
    Export:
      Name: !Sub ${ProjectID}-SANDBOXVPC
  
  ExportParamNetworkAclEC2:
    Description: NetworkAclEC2
    Value: !Ref NetworkAclEC2
    Export:
      Name: !Sub ${ProjectID}-NetworkAclEC2

  ExportParamRouteTableEC2:
    Description: RouteTableEC2
    Value: !Ref RouteTableEC2
    Export:
      Name: !Sub ${ProjectID}-RouteTableEC2

  ExportParamEC2IAMInstanceProfile:
    Description: EC2IAMInstanceProfile
    Value: !Ref EC2IAMInstanceProfile
    Export:
      Name: !Sub ${ProjectID}-EC2IAMInstanceProfile

個別リソース

共通リソースに仮想マシン(EC2)を2台デプロイするCFnテンプレートになります。

前提条件として、下記があります。

  • InstanceImage」についてCFnテンプレートにamiIDをハードコーディングするか、パラメータストアに保存しておく必要があります。
  • EC2KeyPair」について、事前に作成したキーペアをご入力ください。
  • ProjectID」について、共通テンプレートで変更した場合は、こちらのテンプレートも修正ください。

また、共通リソースをクロススタック参照しています。テンプレートを修正して利用される場合はご注意ください。

AWSTemplateFormatVersion: "2010-09-09"
Description: "techharmony-traning-ec2 Template"
Parameters:
  ProjectID:
    Type: String
    MinLength: 3
    MaxLength: 15
    Default: "th-traning"
    AllowedValues:
      - "th-traning"

  # Must be unique
  userID:
    Type: String
    MinLength: 3
    MaxLength: 15

  # 192.168.0.0/16
  SubnetCidrBlock:
    Type: String
    Default: "192.168.1.0/24"
    AllowedValues:
      - "192.168.1.0/24"
      - "192.168.2.0/24"
      - "192.168.3.0/24"
      - "192.168.4.0/24"
      - "192.168.5.0/24"
      - "192.168.6.0/24"
      - "192.168.7.0/24"
      - "192.168.8.0/24"
      - "192.168.9.0/24"
      - "192.168.10.0/24"

  # Need to be created
  EC2KeyPair:
    Type: String

  # Registration to the parameter store is required
  InstanceImage:
    Type: AWS::SSM::Parameter::Value
    Default: /techharmony/sandbox/ami/windowsserver2019

  SelectInstanceType:
    Type: String
    Default: t3.micro
    AllowedValues:
      - t3.micro
      - t3.medium
      - m5.large
      - m5.xlarge
      - m5.2xlarge
      - m5.4xlarge

Resources:
# ================================
# Subnet
# ================================
  SubnetEC21a:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Sub "${AWS::Region}a"
      CidrBlock: !Ref SubnetCidrBlock
      VpcId: {'Fn::ImportValue': !Sub '${ProjectID}-SANDBOXVPC'}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-${userID}-private-1a"

# ================================
# ACL Association
# ================================
  NetworkAclAssociationEC21a:
    Type: "AWS::EC2::SubnetNetworkAclAssociation"
    Properties:
      SubnetId: !Ref SubnetEC21a
      NetworkAclId: {'Fn::ImportValue': !Sub '${ProjectID}-NetworkAclEC2'} 

# ================================
# Route Association
# ================================
  RouteTableAssociationEC21a:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      RouteTableId: {'Fn::ImportValue': !Sub '${ProjectID}-RouteTableEC2'}
      SubnetId: !Ref SubnetEC21a

# ================================
# EC2
# ================================
  # Instance No 01
  EC2Instance01:
    Type: AWS::EC2::Instance
    Properties: 
      AvailabilityZone: !Sub "${AWS::Region}a"
      DisableApiTermination: false
      EbsOptimized: false
      IamInstanceProfile: {'Fn::ImportValue': !Sub '${ProjectID}-EC2IAMInstanceProfile'}
      ImageId: !Ref InstanceImage
      InstanceInitiatedShutdownBehavior: "stop"
      InstanceType: !Ref SelectInstanceType
      KeyName: !Ref EC2KeyPair
      Monitoring: false
      SecurityGroupIds: 
        - !Ref EC2SecurityGroup
      SubnetId: !Ref SubnetEC21a
      Tags: 
        - Key: "Name"
          Value: !Sub "${ProjectID}-${userID}-ec2-01"
      Tenancy: default

  # Instance No 02
  EC2Instance02:
    Type: AWS::EC2::Instance
    Properties: 
      AvailabilityZone: !Sub "${AWS::Region}a"
      DisableApiTermination: false
      EbsOptimized: false
      IamInstanceProfile: {'Fn::ImportValue': !Sub '${ProjectID}-EC2IAMInstanceProfile'}
      ImageId: !Ref InstanceImage
      InstanceInitiatedShutdownBehavior: "stop"
      InstanceType: !Ref SelectInstanceType
      KeyName: !Ref EC2KeyPair
      Monitoring: false
      SecurityGroupIds: 
        - !Ref EC2SecurityGroup
      SubnetId: !Ref SubnetEC21a
      Tags: 
        - Key: "Name"
          Value: !Sub "${ProjectID}-${userID}-ec2-02"
      Tenancy: default

  EC2SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: !Sub "${ProjectID}-${userID}-ec2-sg"
      GroupName: !Sub "${ProjectID}-${userID}-ec2-sg"
      VpcId: {'Fn::ImportValue': !Sub '${ProjectID}-SANDBOXVPC'}
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectID}-${userID}-ec2-sg"

  SecurityGroupEC2Ingress01:
    Type: AWS::EC2::SecurityGroupIngress
    Properties: 
      CidrIp: !Ref SubnetCidrBlock
      FromPort: 0
      GroupId: !Ref EC2SecurityGroup
      IpProtocol: -1
      ToPort: 65535

Outputs:
  InstanceIPAddress01:
    Description: EC2 01 Instance IP address
    Value: !GetAtt EC2Instance01.PrivateIp

  InstanceIPAddress02:
    Description: EC2 02 Instance IP address
    Value: !GetAtt EC2Instance02.PrivateIp

 

終わりに

管理者が環境を払い出す仕掛けにするのであれば、このままCFnテンプレートでスタックを作成いただけます。

セルフサービスで社内のユーザに活用してもらうのであれば、「AWS Service Catalog」に登録しておくのも良いかと思います。

また、性能の高いインスタンスの放置にはお気を付けください。

なにかしらお役にたてていれば幸いです。

タイトルとURLをコピーしました