この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
今日のひとこと:止まない雨はない
はじめに
コマンドラインベースのCloudFormationの管理を便利にするrainを触り倒していきます。
rainとは
rainとは、AWS CloudFormationテンプレート(以下、テンプレート)やスタックを操作するためのコマンドラインツールです。
rainの特徴
rainの特徴は、既存のテンプレートを活用できることだと思っています。Terraform、Pulumi、AWS CDKなど、様々なソリューションがありますが、どれもIaCを再コーディングする必要があります。既存の資産を有効活用するならrainはもってこいのツールです。
rainのセットアップ
まずはrainのインストールして利用できるようにしてきます。筆者の環境はMacOS(Catalina10.15.7)です。Windowsにもインストールして利用できますがここでは割愛します。
brew insatll rain
でインストールできます。
rain -v
でバージョンが表示されればインストールOKです。
% rain -v
rain v1.2.0 darwin/amd64
rainは .aws/config
、 .aws/credentials
を参照して実行します。 rain info
を実行するとcredentialsから認証情報をできます。
% rain info
Account: 012345678910
Region: ap-northeast-1
Identity: arn:aws:iam::012345678910:user/iamusername
他のprofileはAWS CLI同様に、 -p, --profile
オプションを指定して実行できます。AssumeRoleで複数のAWSアカウントを操作するユーザーとしては嬉しいです。
% rain ls --profile profile-name
rainの基本操作
ls(スタック表示)
rainはCloudFormationサービスに特化したツールです。まずは以下のコマンドでスタックの一覧を表示します。
% rain ls
CloudFormation stacks in ap-northeast-1:
stack-name1: CREATE_COMPLETE
stack-name2: CREATE_COMPLETE
CloudFormation stacks in us-east-1:
stack-name1: CREATE_COMPLETE
Shellを使い慣れている人には直感的にわかりやすいコマンドです。AWS CLIで同じ内容を表示しようとするとオプション含めて
% aws cloudformation describe-stacks --query 'Stacks[*].[StackName,StackStatus]' --output text
を入力する必要があります。
rain ls stack-name1
とすると指定したスタックのみ表示できます。-a, --all
オプションを指定するとスタックの詳細が表示されます。
outputsを表示してくれるところがめっちゃ良きです。
% rain ls stack-name1
Stack stack-name1: CREATE_COMPLETE
Outputs:
AssumeRole: arn:aws:iam::012345678910:role/AssumeRole # AssumeRoleArn
% rain ls -a stack-name1
Stack stack-name1: CREATE_COMPLETE
Parameters:
ExternalID: e3c2d49e-903e-xxxx-xxxx-e68bbb592da9
Resources:
stack-name1: CREATE_COMPLETE
stack-name1
Outputs:
stack-name1: arn:aws:iam::012345678910:role/stack-name1 # stack-name1Arn
fmt(テンプレートのフォーマット)
まずはスタックを作成してみます。ローカル環境にあるテンプレートを指定してスタックを作成できます。以下のサンプルテンプレートを用意しました。
AWSTemplateFormatVersion: "2010-09-09"
Description: Network Template.
Parameters:
Env:
Type: String
vpcCidr:
Type: String
Resources:
igw:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${Env}-igw
igwAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref igw
VpcId: !Ref vpc1
vpc1:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref vpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${Env}-vpc1
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: "AWS::Region"
CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]]
VpcId: !Ref vpc1
Tags:
- Key: Name
Value: !Sub ${Env}-public-subnet-1
Outputs:
vpc1:
Value: !Ref vpc1
rainは、cfn-formatによる標準フォーマットでデプロイされます。手元のテンプレートと差分をなくす為、デプロイ前にテンプレートをフォーマットします。
% rain fmt ./template.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Network Template.
Parameters:
Env:
Type: String
vpcCidr:
Type: String
Resources:
igw:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${Env}-igw
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref igw
VpcId: !Ref vpc1
vpc1:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref vpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${Env}-vpc1
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]]
VpcId: !Ref vpc1
Tags:
- Key: Name
Value: !Sub ${Env}-public-subnet-1
Outputs:
vpc1:
Value: !Ref vpc1
オプションを指定しない場合は、標準出力です。 -w, --write
オプションを指定してテンプレートを上書きできます。
% rain fmt -w ./template.yml
deploy(ローカル環境にあるテンプレートからスタック展開)
fmtしたローカルにあるテンプレートをデプロイしてきます。
--params
でParametersに代入できます。key1=Value,key2=Value,key3=Value...
ファイルの指定は、ファイルパスです。(file://~ではありません)
% rain deploy ./template.yml stack-name1 --params Env=dev,vpcCidr="10.10.10.0/16"
rain needs to create an S3 bucket called 'rain-artifacts-0123456789010-ap-northeast-1'. Continue? (Y/n) Y
CloudFormation will make the following changes:
Stack stack-name1:
+ AWS::EC2::VPC vpc1
Do you wish to continue? (Y/n) Y
Deploying template 'stack-name1.yml' as stack 'stack-name1' in ap-northeast-1.
Stack stack-name1: CREATE_COMPLETE
Outputs:
vpc1: vpc-08c7ec1ca037ac008
Successfully deployed stack-name1
deployの流れは以下のとおりです。
- rain用S3バケットのチェック
- ChangeSet
- スタックのデプロイ
deployには、パッケージ化したテンプレートのアーティファクトの格納先としてrain用のS3バケットの有無がチェックされます。バケットがない場合は、 rain needs to create an S3 bucket called 'rain-artifacts-0123456789010-ap-northeast-1'. Continue? (Y/n)
が出力されるのでS3バケットを作成します。ChangeSetを確認し、スタックをデプロイします。 Successfully deployed stack-name1
が表示されればデプロイの成功です。
なお、(Y/n)の入力は、Shellでもよく見かける -y, --yes
オプションを指定すれば省略されます。
スタックの作成またはアップデートが失敗すると、エラー対象のLogicalIDと理由が出力されます。template.ymlのPublicSubnet1を一部を修正してデプロイしてみます。
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- - 0
+ - 3
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]]
VpcId: !Ref vpc1
Tags:
- Key: Name
Value: !Sub ${Env}-public-subnet-1
% rain deploy -y ./template.yml stack-name1 --params Env="dev",vpcCidr="10.10.0.0/16"
Deploying template 'template.yml' as stack 'stack-name1' in ap-northeast-1.
Stack stack-name1: UPDATE_ROLLBACK_COMPLETE
Outputs:
vpc1: vpc-0ab94716b91314657
Messages:
- PublicSubnet1 - subnet-003e0bd623f3a0a12: Template error: Fn::Select cannot select nonexistent value at index 3
failed deploying stack 'stack-name1'
デプロイエラーのLogicalIDだけ表示されるので確認しやすいです。ちなみに「3.スタックのデプロイ」で新規デプロイの場合は、スタックの削除までやってくれます。
logs(スタックのイベントログを出力)
スタックのイベント履歴を確認できます。主にのエラーイベント(UPDATE_FAILEDなど)を出力します。
% rain logs stack-name1
No interesting log messages to display. To see everything, use the --all flag
上記にも出力されている通り、エラーイベントなどがなければ出力されません。すべてのイベント履歴を表示するには、 -a, --all
オプションを指定します。
% rain logs stack-name1 --all
Aug 31 12:30:23 stack-name1/vpc1 (AWS::EC2::VPC) CREATE_COMPLETE
Aug 31 12:30:07 stack-name1/vpc1 (AWS::EC2::VPC) CREATE_IN_PROGRESS "Resource creation Initiated"
Aug 31 12:30:06 stack-name1/vpc1 (AWS::EC2::VPC) CREATE_IN_PROGRESS
~
rm(スタックの削除)
デプロイしたスタックを削除します。
% rain rm stack-name1
Stack stack-name1: CREATE_COMPLETE
Are you sure you want to delete this stack? (y/N) y
Successfully deleted stack 'stack-name1'
(Y/n)の入力は、deployコマンドと同様に -y, --yes
オプションを指定すれば省略されます。
cat(スタックのテンプレート取得)
デプロイされているスタックのテンプレートを出力します。デフォルトフォーマットされた形式で出力されます。
% rain cat stack-name1
AWSTemplateFormatVersion: "2010-09-09"
Description: Network Template.
Parameters:
Env:
Type: String
vpcCidr:
Type: String
Resources:
igw:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${Env}-igw
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref igw
VpcId: !Ref vpc1
vpc1:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref vpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${Env}-vpc1
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]]
VpcId: !Ref vpc1
Tags:
- Key: Name
Value: !Sub ${Env}-public-subnet-1
Outputs:
vpc1:
Value: !Ref vpc1
フォーマットせずにスタックからテンプレートを出力する場合は、-u, --unformatted
オプションを指定します。
diff(テンプレートの比較)
ローカル環境にあるテンプレートを比較します。セクション(Resources,Parameters,Outputsなど)やLogicalID、Propertiesの差分を確認できます。
- セクション差分(Outputsなし)
% rain diff template.yml template1.yml
(-) Outputs: {...}
- LogicalID差分(ResourcesセクションのPublicSbunet1なし)
% rain diff template.yml template1.yml
(|) Resources:
(-) PublicSubnet1: {...}
- Propeties差分(Tagsプロパティなし)
% rain diff template.yml template1.yml
(|) Resources:
(|) PublicSubnet1:
(|) Properties:
(-) Tags: [...]
- Propeties値差分
% rain diff template.yml template1.yml
(|) Resources:
(|) PublicSubnet1:
(|) Properties:
(|) Tags:
(|) [0]:
(|) Value:
(>) Fn::Sub: ${Env}-public-subnet-2
catおよびdiffコマンドの用途は、スタックのテンプレートとローカル環境にある修正したテンプレートの差分チェックというところでしょうか。
tree(リソース間依存関係の確認)
テンプレート内の各リソースの依存関係(DependsOn)を確認できます。
% rain tree template.yml
Resources:
igw:
DependsOn:
Parameters:
- Env
vpc1:
DependsOn:
Parameters:
- Env
- vpcCidr
MyVPCGatewayAttachment:
DependsOn:
Resources:
- igw
- vpc1
PublicSubnet1:
DependsOn:
Parameters:
- AWS::Region
- Env
Resources:
- vpc1
Outputs:
vpc1:
DependsOn:
Resources:
- vpc1
igw(LogicalID))だと、ParametersのEnvが依存関係にあります。MyVPCGatewayAttachment(LogicalID)では、Resourcesのigw、vpc1が依存関係にあります。このような形で確認できるのは、めっちゃ良きです。
build(テンプレートの作成)
テンプレートを作成するには、AWSドキュメントを見ながらコーディングしていく必要があります。必須のプロパティだったり書き方を理解するのに時間がかかるものですが、 rain build
であっという間にテンプレートが生成されます。template.ymlと同様のリソースを指定してrainでテンプレートを生成してみます。
% rain build "AWS::EC2::InternetGateway" "AWS::EC2::VPCGatewayAttachment" "AWS::EC2::VPC" "AWS::EC2::Subnet"
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Resources:
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: CHANGEME
Value: CHANGEME
MySubnet:
Type: AWS::EC2::Subnet
Properties:
AssignIpv6AddressOnCreation: false # Optional
AvailabilityZone: CHANGEME # Optional
CidrBlock: CHANGEME
Ipv6CidrBlock: CHANGEME # Optional
MapPublicIpOnLaunch: false # Optional
OutpostArn: CHANGEME # Optional
Tags:
- Key: CHANGEME
Value: CHANGEME
VpcId: CHANGEME
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: CHANGEME
EnableDnsHostnames: false # Optional
EnableDnsSupport: false # Optional
InstanceTenancy: CHANGEME # Optional
Tags:
- Key: CHANGEME
Value: CHANGEME
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: CHANGEME # Optional
VpcId: CHANGEME
VpnGatewayId: CHANGEME # Optional
Outputs:
MySubnetAvailabilityZone:
Value: !GetAtt MySubnet.AvailabilityZone
MySubnetIpv6CidrBlocks:
Value: !GetAtt MySubnet.Ipv6CidrBlocks
MySubnetNetworkAclAssociationId:
Value: !GetAtt MySubnet.NetworkAclAssociationId
MySubnetOutpostArn:
Value: !GetAtt MySubnet.OutpostArn
MySubnetVpcId:
Value: !GetAtt MySubnet.VpcId
MyVPCCidrBlock:
Value: !GetAtt MyVPC.CidrBlock
MyVPCCidrBlockAssociations:
Value: !GetAtt MyVPC.CidrBlockAssociations
MyVPCDefaultNetworkAcl:
Value: !GetAtt MyVPC.DefaultNetworkAcl
MyVPCDefaultSecurityGroup:
Value: !GetAtt MyVPC.DefaultSecurityGroup
MyVPCIpv6CidrBlocks:
Value: !GetAtt MyVPC.Ipv6CidrBlocks
リソースタイプを指定するだけであっという間にテンプレートが生成されます。 CHANGEME
(# Optional無し)は必須のプロパティ、 CHANGEME # Optional
は任意のプロパティです。必要に応じて値を設定します。
なお、最低限のプロパティだけ生成する場合は、-b, --bare
オプションを指定します。
% rain build -b "AWS::EC2::InternetGateway" "AWS::EC2::VPCGatewayAttachment" "AWS::EC2::VPC" "AWS::EC2::Subnet"AWSTemplateFormatVersion:
"2010-09-09"
Description: Template generated by rain
Resources:
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties: {}
MySubnet:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: CHANGEME
VpcId: CHANGEME
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: CHANGEME
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: CHANGEME
また、リソースタイプは複数指定も可能です。以下、AWS::EC2::Subnet
を複数指定して生成できます。
% rain build -b "AWS::EC2::InternetGateway" "AWS::EC2::VPCGatewayAttachment" "AWS::EC2::VPC" "AWS::EC2::Subnet" "AWS::EC2::Subnet" "AWS::EC2::Subnet" "AWS::EC2::Subnet"
AWSTemplateFormatVersion: "2010-09-09"
Description: Template generated by rain
Resources:
MyInternetGateway:
Type: AWS::EC2::InternetGateway
Properties: {}
MySubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: CHANGEME
VpcId: CHANGEME
MySubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: CHANGEME
VpcId: CHANGEME
MySubnet3:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: CHANGEME
VpcId: CHANGEME
MySubnet4:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: CHANGEME
VpcId: CHANGEME
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: CHANGEME
MyVPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: CHANGEME
ちなみに -l, --list
で指定できるリソースタイプを確認できます。v1.2.0だと 740 のリソースタイプが指定できます。
buildコマンドでベースのテンプレートを生成して、ParametersやOutputsセクションなど追加コーディングしていきましょう。
ファーストインプレッションを終えて
Shellに慣れている人間には直感的に扱いやすいです。コマンドオプションも多くなく、簡単な教育で現場に浸透させやすいツールだと感じます。
面倒な部分として -p, --profile
オプションを指定するとrainコマンドの都度、MFA Tokenの入力が求められます。
ソースコードを見ると WithAssumeRoleCredentialOptionsによって以前の一時資格情報が上書きされる為です。
stsやdirenvなどでprofileオプションを指定しない方法が扱いやすいです。
個人的にはコマンドラインによるCloudFormataion管理で、rainの右に出るものは現時点でないと思っています。次回は、rainを使ったテンプレートのCI/CDパイプラインの実装を検証していきたいと思います。