code-server と ALB で AWS Cloud9 代替の研修用 IDE を提供する – 実装編1 VPC

こんにちは、広野です。

AWS Cloud9 は研修用途では非常に使い勝手が良かったのですが、AWS が新規アカウントへの提供を終了してしまいました。今回は私が試みた代替ソリューションの実装編です。本記事は VPC を対象にします。特別な設計はないので、別件でも使用可能だと思います。

 

アーキテクチャ

アーキテクチャ概要編で、以下の図を紹介しておりました。

  • code-server サーバを配置する VPC です。何の変哲もない一般的なサブネット構成にしています。
  • NAT Gateway は課金節約のため、1 つのパブリックサブネットにしか配置していません。
  • code-server サーバは 1 ユーザーあたり 1 台を割り当てます。図では 1 つしかありませんが、人数分作成される想定です。ALB はユーザー全体で共用します。
  • code-server のログインパスワードはインストール時に設定しますが、AWS Secrets Manager で生成したものを使用します。
  • ALB には HTTPS アクセスさせるため、図には書いていませんが独自ドメインを使用します。そのための SSL 証明書はそのリージョンの AWS Certificate Manager で作成されていることが前提になります。
  • ALB から EC2 インスタンスへの通信は 80 番ポート (HTTP) を使用します。
  • ALB はインターネットに公開されますが、研修用途を想定して会社のソース IP アドレスからのみアクセスできるようにセキュリティグループを設定しています。

 

既存 VPC を利用する場合

以下の条件であれば、既存 VPC を使用することが可能です。本記事の内容は飛ばすことができます。

  • パブリックサブネットが 2 つある。(ALB 用)
  • プライベートサブネットからインターネットにアクセスできる。(NAT Gateway がある) ソフトウェアのインストールや、AWS Systems Manager, AWS Secrets Manager のエンドポイントへのアクセスに使用する。

 

AWS CloudFormation テンプレートの構成

テンプレートは 3 つに分けています。

  • VPC ←今回はここ
  • Application Load Balancer
  • Amazon EC2 インスタンス

このうち、VPC と Application Load Balancer はユーザー共用になるので 1 回のみ実行します。Amazon EC2 インスタンスはユーザーごとに実行が必要です。

AWS Secrets Manager のシークレットはユーザーごとに作成するので、Amazon EC2 インスタンスと同時に実装します。

テンプレートの範囲を図にすると、以下のようになります。ここに書かれているリソースが作成されます。

 

AWS CloudFormation テンプレート

AWS CloudFormation テンプレートです。AWS Secrets Manager は、続編記事のテンプレートに入ります。

AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates A VPC, Subnets, an Internet Gateway, a single NAT Gateway in AZ A and Routings.

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  SystemName:
    Type: String
    Description: System name. use lower case only. (e.g. example)
    Default: example
    MaxLength: 10
    MinLength: 1

  SubName:
    Type: String
    Description: System sub name. use lower case only. (e.g. prod or dev)
    Default: dev
    MaxLength: 10
    MinLength: 1

  VPCCIDR:
    Type: String
    Description: IP Address range for your VPC (16 bit mask)
    Default: 192.168.0.0/16
    MaxLength: 14
    MinLength: 10
    AllowedPattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){2}0\.0/16$

  PublicSubnetACIDR:
    Type: String
    Description: IP Address range for your public subnet A (24 bit mask)
    Default: 192.168.1.0/24
    MaxLength: 16
    MinLength: 10
    AllowedPattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}0/24$

  PublicSubnetCCIDR:
    Type: String
    Description: IP Address range for your public subnet C (24 bit mask)
    Default: 192.168.2.0/24
    MaxLength: 16
    MinLength: 10
    AllowedPattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}0/24$

  PrivateSubnetACIDR:
    Type: String
    Description: IP Address range for your private subnet A (24 bit mask)
    Default: 192.168.101.0/24
    MaxLength: 16
    MinLength: 10
    AllowedPattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}0/24$

  PrivateSubnetCCIDR:
    Type: String
    Description: IP Address range for your private subnet C (24 bit mask)
    Default: 192.168.102.0/24
    MaxLength: 16
    MinLength: 10
    AllowedPattern: ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}0/24$

Resources:
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub vpc-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub igw-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
#  Subnet
# ------------------------------------------------------------#
# Public SubnetA Create
  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref PublicSubnetACIDR
      MapPublicIpOnLaunch: true
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub public-a-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# Public SubnetC Create
  PublicSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref PublicSubnetCCIDR
      MapPublicIpOnLaunch: true
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub public-c-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# Private SubnetA Create
  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref PrivateSubnetACIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub private-a-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# Private SubnetC Create
  PrivateSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref PrivateSubnetCCIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub private-c-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# ------------------------------------------------------------#
#  RouteTable
# ------------------------------------------------------------#
# Public RouteTable A Create
  PublicRouteTableA:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub rtb-public-a-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# Public RouteTable C Create
  PublicRouteTableC:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub rtb-public-c-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# Private RouteTable A Create
  PrivateRouteTableA:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub rtb-private-a-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# Private RouteTable C Create
  PrivateRouteTableC:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub rtb-private-c-${SystemName}-${SubName}
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}

# ------------------------------------------------------------#
# Routing to Internet
# ------------------------------------------------------------#
# PublicRouteA Create
  PublicRouteA:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTableA
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

# PublicRouteC Create
  PublicRouteC:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTableC
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PrivateRouteA:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTableA
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGatewayA

  PrivateRouteC:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTableC
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGatewayA

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------#
# PublicRouteTable Associate SubnetA
  PublicSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTableA

# PublicRouteTable Associate SubnetC
  PublicSubnetCRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref PublicRouteTableC
                
# PrivateRouteTable Associate SubnetA
  PrivateSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref PrivateRouteTableA

# PrivateRouteTable Associate SubnetC
  PrivateSubnetCRouteTableAssociation: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetC
      RouteTableId: !Ref PrivateRouteTableC

# ------------------------------------------------------------#
# Elastic IP address for NAT Gateway
# ------------------------------------------------------------#
  EipNatGatewayA:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
        - Key: Name
          Value: !Sub eip-ngw-a-${SystemName}-${SubName}

# ------------------------------------------------------------#
# NAT Gateway
# ------------------------------------------------------------#
  NatGatewayA:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EipNatGatewayA.AllocationId
      ConnectivityType: public
      MaxDrainDurationSeconds: 350
      SubnetId: !Ref PublicSubnetA
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
        - Key: Name
          Value: !Sub ngw-a-${SystemName}-${SubName}

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# VPC
  VpcId:
    Value: !GetAtt VPC.VpcId

 

続編記事

続編記事が出来次第、この章を更新します。

 

まとめ

いかがでしたでしょうか。

今回は実装編 1 VPC でした。一般的な内容なので得るものは少ないと思いましたが、絶対に必要だったので紹介しておきました。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャを駆使して社内クラウド人材育成アプリとコンテンツづくりに勤しんでいます。React で SPA を書き始めたら快適すぎて、他の言語には戻れなくなりました。サーバーレス & React 仲間を増やしたいです。AWS は好きですが、それよりもバックエンド構築を簡単にしてくれたことに対する感謝の気持ちの方が強いです。
取得資格:AWS 認定は15資格、IT サービスマネージャ、ITIL v3 Expert 等
2020 - 2024 Japan AWS Top Engineer 受賞
2022 - 2024 AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS Amplify / AWS AppSync / Amazon Cognito / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AWSクラウド
シェアする
タイトルとURLをコピーしました