こんにちは、広野です。
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 に触れます。
本記事が皆様のお役に立てれば幸いです。



