Cloud9をプライベートサブネットに配置する場合のCFnテンプレートを作成してみた
こんにちは。AWS事業本部コンサルティング部の戸川です。
先日、ちょっと私用で「インターネット接続可能なAWS Cloud9をプライベートサブネットに配置しAWS Systems Manager経由で接続する環境を、VPCごと作ったり消したりする」必要に駆られたので、AWS CloudFormationのテンプレートを作成しました。
せっかくなので、ブログにまとめておこうと思います。
環境構成図
今回CFnで構築する環境は以下になります。
注意点
今回はCloud9をプライベートサブネット上に構築する必要があり、尚且つインターネットとの通信を行いたかったためNATゲートウェイを作成しています。
しかし、NATゲートウェイは維持している時間分の費用とデータ転送量に応じた料金が発生します。要件によっては別の構成を検討するのが良いでしょう。
例えば
など。
CFnテンプレート
テンプレート構成
├── cloud9 │ └── cloud9.yaml ├── codecommit │ └── codecommit.yaml ├── iam │ └── iam.yaml ├── subnet │ ├── private │ │ └── subnet.yaml │ └── public │ └── subnet.yaml │── vpc │ └── vpc.yaml └── main.yaml
main.yaml
AWSTemplateFormatVersion: "2010-09-09" Description: main stack Parameters: CreateRepository: Default: "yes" Type: String AllowedValues: ["yes", "no"] Conditions: CreateRepository: !Equals - !Ref CreateRepository - "yes" Resources: VPC: Type: AWS::CloudFormation::Stack Properties: TemplateURL: vpc/vpc.yaml PrivateSubnet: Type: AWS::CloudFormation::Stack Properties: TemplateURL: subnet/private/subnet.yaml Parameters: VPC: !GetAtt VPC.Outputs.VPCID NatGateway: !GetAtt PublicSubnet.Outputs.NatGatewayID PublicSubnet: Type: AWS::CloudFormation::Stack Properties: TemplateURL: subnet/public/subnet.yaml Parameters: VPC: !GetAtt VPC.Outputs.VPCID IAM: Type: AWS::CloudFormation::Stack Properties: TemplateURL: iam/iam.yaml CodeCommit: Type: AWS::CloudFormation::Stack Condition: CreateRepository Properties: TemplateURL: codecommit/codecommit.yaml Cloud9: Type: AWS::CloudFormation::Stack Properties: TemplateURL: cloud9/cloud9.yaml Parameters: Subnet: !GetAtt PrivateSubnet.Outputs.SubnetID
補足
- スタック削除時にCodeCommitリポジトリが削除されないよう、Retainオプションを指定しています
- その結果、同アカウントで再度スタックを作成する場合には同じ名前のリポジトリが既に存在するというエラーが発生します
- そのためテンプレートのConditionsセクションにて、スタック作成時にCodeCommitも併せて作成するか否かを選択できるようにしています
- CodeCommitを作成しない場合はdeployコマンドに
--parameter-overrides CreateRepository="no"
を付加します
vpc/vpc.yaml
AWSTemplateFormatVersion: "2010-09-09" Parameters: EnvironmentName: Type: String Default: work-cloud9 VpcCIDR: Type: String Default: 10.192.0.0/16 Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCIDR EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Ref EnvironmentName Outputs: VPCID: Value: !Ref VPC
subnet/private/subnet.yaml
AWSTemplateFormatVersion: "2010-09-09" Description: "private subnet" Parameters: VPC: Type: AWS::EC2::VPC::Id PrivateSubnetCIDR: Type: String Default: 10.192.20.0/24 NatGateway: Type: String Resources: PrivateSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PrivateSubnetCIDR MapPublicIpOnLaunch: false PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: Private Routes DefaultPrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway PrivateSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable SubnetId: !Ref PrivateSubnet Outputs: SubnetID: Value: !Ref PrivateSubnet
subnet/public/subnet.yaml
AWSTemplateFormatVersion: "2010-09-09" Description: "public subnet" Parameters: VPC: Type: AWS::EC2::VPC::Id PublicSubnetCIDR: Type: String Default: 10.192.10.0/24 Resources: PublicSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PublicSubnetCIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: Public Subnet InternetGateway: Type: AWS::EC2::InternetGateway InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC NatGatewayEIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet NoIngressSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: "no-ingress-sg" GroupDescription: "Security group with no ingress rule" VpcId: !Ref VPC Outputs: SubnetID: Value: !Ref PublicSubnet NatGatewayID: Value: !Ref NatGateway
iam/iam.yaml
AWSTemplateFormatVersion: "2010-09-09" Resources: IAMRoleforCloud9: Type: AWS::IAM::Role Properties: RoleName: AWSCloud9SSMAccessRole AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com - cloud9.amazonaws.com Action: - "sts:AssumeRole" MaxSessionDuration: 3600 Path: "/service-role/" ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSCloud9SSMInstanceProfile InstanceProfileforCloud9: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: AWSCloud9SSMInstanceProfile Path: /cloud9/ Roles: - !Ref IAMRoleforCloud9 IAMPolicyforCloud9: Type: AWS::IAM::ManagedPolicy Properties: Description: Policy for Cloud9 Path: / ManagedPolicyName: Cloud9-IAM-Policy1 PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: ecr:GetAuthorizationToken Resource: "*" - Effect: Allow Action: s3:GetObject Resource: "*" - Effect: Allow Action: codecommit:GitPull Resource: "*" Roles: - !Ref IAMRoleforCloud9
codecommit/codecommit.yaml
AWSTemplateFormatVersion: "2010-09-09" Resources: CodeCommit: Type: AWS::CodeCommit::Repository DeletionPolicy: Retain Properties: RepositoryName: work-repo RepositoryDescription: Work Repo for Cloud9
補足
- スタック削除時にリポジトリが削除されないようRetainオプションを指定しています
cloud9/cloud9.yaml
AWSTemplateFormatVersion: "2010-09-09" Parameters: EnvironmentName: Type: String Default: work-cloud9 AutomaticStopTimeMinutes: Type: Number Default: 30 ConnectionType: Type: String Default: CONNECT_SSM ImageId: Type: String Default: resolve:ssm:/aws/service/cloud9/amis/amazonlinux-2023-x86_64 InstanceType: Type: String Default: t3.micro OwnerArn: Type: String Default: <ENVIRONMENT_OWNER_ARN> Subnet: Type: String Resources: Cloud9: Type: AWS::Cloud9::EnvironmentEC2 Properties: AutomaticStopTimeMinutes: !Ref AutomaticStopTimeMinutes ConnectionType: !Ref ConnectionType ImageId: !Ref ImageId InstanceType: !Ref InstanceType Name: !Ref EnvironmentName OwnerArn: !Ref OwnerArn SubnetId: !Ref Subnet Repositories: - RepositoryUrl: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/work-repo PathComponent: codecommit/work-repo
補足
ENVIRONMENT_OWNER_ARN
の部分はCloud9環境のオーナーにしたいIAMユーザー、ロールのARNを記載してください- 別アカウントからスイッチロールした先のアカウントでCloud9を構築する場合は
arn:aws:sts::スイッチ先アカウント番号:assumed-role/スイッチ先ロール名/セッション名(ユーザー名)
になります
スタック作成手順
S3バケットへのテンプレートアップロードとartifact.yamlの作成
AWS CLIが使用可能な環境で以下のコマンドを実行します。
aws cloudformation package --template-file main.yaml --s3-bucket テンプレート格納用S3バケット名 --output-template-file artifact.yaml
main.yaml内の各テンプレートファイルへの参照を、S3 URLへ置換したartifact.yamlが作成されます。
スタックの作成
次に以下のコマンドでスタックを作成します。
aws cloudformation deploy --template-file artifact.yaml --stack-name cloud9-construction スタック名 --capabilities CAPABILITY_NAMED_IAM
なお、前述の通りCodeCommitが既に作成済の場合は--parameter-overrides CreateRepository="no"
を付与します。
最後に
インプットした分だけアウトプットしてくれる、そんなCloudFormationの姿勢を私も見習っていきたいです。
この記事が少しでもどなたかのお役に立てば幸いです。