AWS CDK実行環境をCloudFormationで用意する
こんにちは、エンジニアの籏野です。
フォルシアでは主にAWS上に用意した環境にアプリをデプロイしており、初期のころはCloudFormationのテンプレートを書いて環境を構築していました。
近年ではAWS CDKを利用することが多くなってきており、単なるYAML等の設定ファイルではなく自分たちに馴染みのある言語で環境を記載できてとても便利に感じています。
AWS CDKを利用しているので、最終的にはcdk deploy
コマンドを実行する必要がありますが、このコマンドをどこで実行するのかというのは重要なポイントです。
AWSのアクセスキーを発行することで、ローカルの開発環境や社内に用意するデプロイ専用環境等からの実行も可能ですが、安全に運用するためにはアクセスキーをなるべく発行しないようにしたいです。
最近はGitHubやGitLabなどでOIDC連携をして実行するのが定石になってきているような気もしますが、学習の意も込めてAWS CDK実行用のEC2を用意することにしました。
上記の記事も参考にさせていただき、AWS CDK実行用のEC2にIAMロールを付与していきます。
また今回作成する環境の設定もコードに残しておきたいので、CloudFormationで設定を記載し構築していきます。
構築すべき環境
CloudFormation設定ファイルを書く前に、どのような環境を構築するかを整理します。
- EC2はパブリックサブネットに配置
- 特定のIPからのSSH接続のみ許可
- AWS CDK実行用のIAMロールを作成し、最低限の権限を付与
- TypeScript版のAWS CDKを利用しているため、Node.jsをインストール
上3つについてはよく構築する構成なので特に問題なかったのですが、Node.jsのインストールについてはどのような方法で行うのかがわかりませんでした。
フォルシアではサーバーの構築にはAnsibleを主に利用してきており、これを使えばNode.jsのインストールも容易ですが、playbookを別で用意するのも面倒です。
そのためEC2のユーザーデータという機能を利用して、起動時にNode.jsをインストールするように設定します。
CloudFormation設定
早速結論です。
# NOTE 複数のアプリでの利用を想定し、パラメータを設定
Parameters:
BaseName:
Type: String
Default: cdk-deploy
Description: The base name of the resources
VpcCidr:
Type: String
Default: 10.0.0.0/24
Description: The CIDR block for the VPC
PublicSubnetCidr:
Type: String
Default: 10.0.0.0/24
Description: The CIDR block for the public subnet
PublicSubnetAZ:
Type: String
Default: ap-northeast-1a
Description: The availability zone for the public subnet
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
Tags:
- Key: Name
Value: !Sub ${BaseName}-VPC
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${BaseName}-IGW
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref PublicSubnetAZ
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnetCidr
Tags:
- Key: Name
Value: !Sub ${BaseName}-PublicSubnet
PublicSubnetRT:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${BaseName}-PublicSubnet-RT
PublicSubnetToInternet:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicSubnetRT
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRTAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicSubnetRT
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable Access
GroupName: !Sub ${BaseName}-SG
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
# NOTE 接続元IPを記載
CidrIp: XXX.XXX.XXX.XXX/XX
Tags:
- Key: Name
Value: !Sub ${BaseName}-SG
VpcId: !Ref VPC
CDKRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
CDKBoorstrapPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub ${BaseName}-bootstrap-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- cloudformation:*
- ecr:*
- ssm:*
- s3:*
- iam:*
Resource: "*"
Roles:
- !Ref CDKRole
CDKDeployPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: !Sub ${BaseName}-deploy-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: "AssumeCDKRoles"
Effect: Allow
Action:
- sts:AssumeRole
Resource: "*"
Condition:
"ForAnyValue:StringEquals":
"iam:ResourceTag/aws-cdk:bootstrap-role":
- image-publishing
- file-publishing
- deploy
- lookup
Roles:
- !Ref CDKRole
CDKIamInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: "/"
Roles:
- !Ref CDKRole
EC2LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub ${BaseName}-launch-template
LaunchTemplateData:
UserData:
Fn::Base64: |
#!/bin/bash -xe
# Install nvm to ec2-user
runuser -l ec2-user -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'
EC2Instance:
Type: AWS::EC2::Instance
Properties:
# Amazon Linux 2023
ImageId: ami-034c9ca2bdde7b472
# NOTE 接続用のキーペアを指定
KeyName: hoge-key
InstanceType: t3.small
IamInstanceProfile: !Ref CDKIamInstanceProfile
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: !Ref PublicSubnet
GroupSet:
- !Ref SecurityGroup
Tags:
- Key: Name
Value: !Sub ${BaseName}
LaunchTemplate:
LaunchTemplateId: !Ref EC2LaunchTemplate
Version: !GetAtt EC2LaunchTemplate.LatestVersionNumber
EC2ElasitcIP:
Type: AWS::EC2::EIP
EC2ElasticIPAssociation:
Type: AWS::EC2::EIPAssociation
Properties:
InstanceId: !Ref EC2Instance
EIP: !Ref EC2ElasitcIP
VPC、サブネット、セキュリティグループ、IAMロール、EC2インスタンスを作成しました。
また以下のような設定により作成するEC2インスタンスにユーザーデータを設定できます。
これによりインスタンス起動時にNode.js(nvm)のインストールを行うことができます。
runuser
コマンドを利用することで、特定のユーザー権限でコマンドを実行できます。
※今回は簡単のため、ec2-userにnvmをインストールしていますが、要件に合わせて実行用ユーザーを作成するなどの対応をしましょう。
EC2LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub ${BaseName}-launch-template
LaunchTemplateData:
UserData:
Fn::Base64: |
#!/bin/bash -xe
# Install nvm to ec2-user
runuser -l ec2-user -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'
また、付与したポリシーについては以下の記事を参考とさせていただきました。
上記の設定を元にCloudFormationスタックを作成し、AWS CDKの実行環境を構築することができました。
最後に
これまでは構築したサーバーに対して何かしらのインストールを行うのは、CloudFormationとは別の仕組みを用いてきました。
しかし、今回のような簡単な構築であればユーザーデータを用いることでCloudFormationで完結できることがわかりました。
より大規模な構築についてはAnsible等を利用した方がよいように思いますが、要件に合わせて柔軟に使い分けていけるとよいですね。
この記事を書いた人
籏野 拓
2018年新卒入社
Discussion