こんにちは、広野です。
AWS Cloud9 は研修用途では非常に使い勝手が良かったのですが、AWS が新規アカウントへの提供を終了してしまいました。今回は私が試みた代替ソリューションの実装編です。本記事は Application Load Balancer とその周辺設定を対象にします。デプロイ先の 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 を作成する場合
前回記事を参考に、作成してください。
既存 VPC を使用する場合
以下の条件であれば、既存 VPC を使用することが可能です。
- パブリックサブネットが 2 つある。(ALB 用)
- プライベートサブネットからインターネットにアクセスできる。(NAT Gateway がある) ソフトウェアのインストールや、AWS Systems Manager, AWS Secrets Manager のエンドポイントへのアクセスに使用する。
AWS CloudFormation テンプレートの構成
テンプレートは 3 つに分けています。
- VPC
- Application Load Balancer ← 今回はここ
- Amazon EC2 インスタンス
このうち、VPC と ALB はユーザー共用になるので 1 回のみ実行します。Amazon EC2 インスタンスはユーザーごとに実行が必要です。
作成する ALB は本体 1 つとリスナーのみ作成します。ターゲットグループやリスナールールはぶらさがる Amazon EC2 インスタンスごとに作成しますので、続編記事で紹介します。
ALB にはターゲットグループからの HTTP レスポンスヘッダーをオーバーライドする機能があります。今回の構成ではそれを使用して若干セキュリティを高めています。
Amazon EC2 インスタンスの共通設定となる起動テンプレートとセキュリティグループもここで共通設定として 1 回だけ作成します。
テンプレートの範囲を図にすると、以下のようになります。ここに書かれているリソースが作成されます。
AWS CloudFormation テンプレート
AWS CloudFormation テンプレートです。AWS Secrets Manager は、続編記事のテンプレートに入ります。
Amazon EC2 の起動テンプレートは、インスタンスの共通設定としてボリュームサイズを可変にしています。とりあえず 10 GB あれば余裕はありますが、インストールするモジュール等により変更は必要です。インスタンスタイプは t3.micro, t3.small からの 2 択にしていますが、必要に応じて変更してください。
その他細かいことはインラインのコメントで補足します。
AWSTemplateFormatVersion: "2010-09-09" Description: The CloudFormation template that creates a S3 bucket for logging, an EC2 Security Group, a Launch template and an ALB for common code-server configurations. # ------------------------------------------------------------# # 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 VpcId: Type: AWS::EC2::VPC::Id Description: Choose a existing VPC ID you deploy the EC2 instance in. AlbSubnet1: Type: AWS::EC2::Subnet::Id Description: Choose an existing Public Subnet ID you deploy the Application Load Balancer in. AlbSubnet2: Type: AWS::EC2::Subnet::Id Description: Choose an existing Public Subnet ID you deploy the Application Load Balancer in. InstanceSubnet: Type: AWS::EC2::Subnet::Id Description: Choose an existing Private Subnet ID you deploy the EC2 instance in. LogRetentionDays: Type: Number Description: The retention period (days) for entire log data. Enter an integer between 35 to 540. Default: 400 MaxValue: 540 MinValue: 35 InstanceType: Type: String Description: EC2 instance type for code-server. Default: t3.small AllowedValues: - t3.micro - t3.small InstanceVolumeSize: Type: Number Description: The volume size in GB Default: 10 # ALB を HTTPS アクセス可能とするため、独自ドメインを使用します。 DomainName: Type: String Description: Domain name for URL. xxxxx.xxx Default: example.com MaxLength: 40 MinLength: 5 # ドメインの SSL 証明書を ACM で登録していることが前提です。証明書の ARN を記入します。 CertificateArn: Type: String Description: ACM certificate ARN. Default: arn:aws:acm:ap-northeast-1:xxxxxxxxxxxx:certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx MaxLength: 128 MinLength: 10 # ALB へのアクセスを許可するソース IPv4 サブネットを指定します。 AllowedSubnet: Type: String Description: Allowed source IPv4 subnet and subnet mask. (e.g. xxx.xxx.xxx.xxx/32) Default: xxx.xxx.xxx.xxx/32 MaxLength: 18 MinLength: 9 Resources: # ------------------------------------------------------------# # S3 # ------------------------------------------------------------# S3BucketLogs: Type: AWS::S3::Bucket Properties: BucketName: !Sub ${SystemName}-${SubName}-code-server-logs LifecycleConfiguration: Rules: - Id: AutoDelete Status: Enabled ExpirationInDays: !Ref LogRetentionDays OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} # for ap-northeast-1 region S3BucketPolicyLogs: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref S3BucketLogs PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: # 東京リージョン以外で使用するときは、以下のアカウント番号を変更する必要があります。AWS 指定のものです。 AWS: "arn:aws:iam::582318560864:root" Action: s3:PutObject Resource: !Sub "${S3BucketLogs.Arn}/*" DependsOn: - S3BucketLogs # ------------------------------------------------------------# # EC2 Launch template # ------------------------------------------------------------# EC2LaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: !Sub code-server-${SystemName}-${SubName} LaunchTemplateData: InstanceType: !Ref InstanceType # AMI は Amazon Linux 2023 の最新版を使用するようになっています。 ImageId: >- {{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}} BlockDeviceMappings: - Ebs: VolumeSize: !Ref InstanceVolumeSize VolumeType: gp3 DeleteOnTermination: true Encrypted: true DeviceName: /dev/xvda NetworkInterfaces: - SubnetId: !Ref InstanceSubnet Groups: - !Ref Ec2SecurityGroup DeviceIndex: 0 AssociatePublicIpAddress: false MetadataOptions: HttpTokens: required Monitoring: Enabled: true TagSpecifications: - ResourceType: volume Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} DependsOn: - Ec2SecurityGroup # ------------------------------------------------------------# # EC2 Security Group # ------------------------------------------------------------# Ec2SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref VpcId GroupDescription: Allow code-server access via ALB only SecurityGroupIngress: - Description: Allow HTTP from ALB FromPort: 80 IpProtocol: tcp ToPort: 80 SourceSecurityGroupId: !GetAtt AlbSecurityGroup.GroupId SecurityGroupEgress: - Description: Allow all outbound traffic IpProtocol: -1 CidrIp: 0.0.0.0/0 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} - Key: Name Value: !Sub SG-code-server-${SystemName}-${SubName} DependsOn: - AlbSecurityGroup # ------------------------------------------------------------# # ALB Security Group # ------------------------------------------------------------# AlbSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref VpcId GroupDescription: Allow access via HTTPS. SecurityGroupIngress: - CidrIp: !Ref AllowedSubnet FromPort: 443 IpProtocol: tcp ToPort: 443 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} - Key: Name Value: !Sub SG-alb-${SystemName}-${SubName} # ------------------------------------------------------------# # ALB # ------------------------------------------------------------# ApplicationLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub alb-code-server-${SystemName}-${SubName} Type: application IpAddressType: ipv4 LoadBalancerAttributes: - Key: deletion_protection.enabled Value: false - Key: access_logs.s3.enabled Value: true - Key: access_logs.s3.bucket Value: !Ref S3BucketLogs - Key: access_logs.s3.prefix Value: albAccessLogs - Key: idle_timeout.timeout_seconds Value: 60 - Key: routing.http.desync_mitigation_mode Value: defensive - Key: routing.http.drop_invalid_header_fields.enabled Value: true - Key: routing.http.preserve_host_header.enabled Value: false - Key: routing.http.x_amzn_tls_version_and_cipher_suite.enabled Value: false - Key: routing.http.xff_client_port.enabled Value: false - Key: routing.http.xff_header_processing.mode Value: append - Key: routing.http2.enabled Value: true - Key: waf.fail_open.enabled Value: false Scheme: internet-facing SecurityGroups: - !GetAtt AlbSecurityGroup.GroupId Subnets: - !Ref AlbSubnet1 - !Ref AlbSubnet2 Tags: - Key: Cost Value: !Sub ${SystemName}-${SubName} - Key: Name Value: !Sub alb-code-server-${SystemName}-${SubName} DependsOn: - AlbSecurityGroup - S3BucketLogs # HTTPS listener for routing AlbListenerHttps: Type: AWS::ElasticLoadBalancingV2::Listener Properties: LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 443 Protocol: HTTPS SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06 Certificates: - CertificateArn: !Ref CertificateArn DefaultActions: - Type: fixed-response FixedResponseConfig: StatusCode: 404 ContentType: text/plain MessageBody: Not Found ListenerAttributes: # frame タグは SAMEORIGIN でないと許可しないようにしています。DENY だと code-server でエラーが発生します。 - Key: routing.http.response.x_frame_options.header_value Value: SAMEORIGIN - Key: routing.http.response.server.enabled Value: false - Key: routing.http.response.strict_transport_security.header_value Value: max-age=31536000; includeSubdomains; preload; - Key: routing.http.response.x_content_type_options.header_value Value: nosniff # CSP は code-server の動作に抵触しないよう調整しています。もしエラーが発生したら都度調整が必要です。 - Key: routing.http.response.content_security_policy.header_value Value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src-elem 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://open-vsx.org https://marketplace.visualstudio.com; worker-src 'self' blob:; font-src 'self' data:; frame-src 'self';" DependsOn: - ApplicationLoadBalancer # ------------------------------------------------------------# # Route 53 # ------------------------------------------------------------# Route53RecordA: Type: AWS::Route53::RecordSet Properties: HostedZoneName: !Sub ${DomainName}. Name: !Sub code-server-${SystemName}-${SubName}.${DomainName}. Type: A AliasTarget: HostedZoneId: !GetAtt ApplicationLoadBalancer.CanonicalHostedZoneID DNSName: !GetAtt ApplicationLoadBalancer.DNSName DependsOn: ApplicationLoadBalancer # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # Ec2 Ec2LaunchTemplateId: Value: !Ref EC2LaunchTemplate Export: Name: !Sub ec2LtId-code-server-${SystemName}-${SubName} Ec2LaunchTemplateVersion: Value: !GetAtt EC2LaunchTemplate.LatestVersionNumber Export: Name: !Sub ec2LtVersion-code-server-${SystemName}-${SubName} # S3 S3BucketLogsName: Value: !Ref S3BucketLogs Export: Name: !Sub S3BucketLogsName-code-server-${SystemName}-${SubName} S3BucketLogsArn: Value: !GetAtt S3BucketLogs.Arn Export: Name: !Sub S3BucketLogsArn-code-server-${SystemName}-${SubName} # ALB AlbUrl: Value: !Sub https://code-server-${SystemName}-${SubName}.${DomainName} Export: Name: !Sub AlbUrl-${SystemName}-${SubName} AlbListenerArn: Value: !GetAtt AlbListenerHttps.ListenerArn Export: Name: !Sub AlbListenerArn-${SystemName}-${SubName}
続編記事
続編記事が出来次第、この章を更新します。
まとめ
いかがでしたでしょうか。
今回は実装編 2 Application Load Balancer でした。まだソリューションの核心には触れられていませんが、ベースはできてきています。続編記事で、パスベースルーティングや nginx に触れます。
本記事が皆様のお役に立てれば幸いです。