ここではCloudFormationのサンプルコードを提供します。 以下README.mdの内容に従って、CloudFormationを実行することができます。
- リポジトリ直下のREADME.mdによる環境準備が完了していること。
本書でも説明したとおり、CloudFormationのメリットはJSON/YAMLが記述できるエディタさえあれば、追加のツールなしでも実行できる点です。
本ハンズオンでももちろん、提供するYAMLをそのままCloudFormationダッシュボードからアップロードするだけで実行も可能です。
しかし、毎回GUIを操作してファイルをアップロードすることも手間です。 本ハンズオンではAWS CLIを利用してCloudFormationのスタックを作成します。
Cloud9にはデフォルトでAWS CLIがインストールされており、追加の設定は不要です。 AWS CLIのバージョンは次のとおりです。
$ aws --version
aws-cli/1.19.94 Python/2.7.18 Linux/4.14.232-176.381.amzn2.x86_64 botocore/1.20.94
# アカウントIDの取得
$ AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
# バケット名の設定
$ BUCKET_NAME=cnis-cfn-bucket-${AWS_ACCOUNT_ID}
# リージョン名の設定
$ REGION=ap-northeast-1
# S3の作成とパブリックアクセスの禁止設定
$ aws s3api create-bucket \
--bucket ${BUCKET_NAME} \
--region ${REGION} \
--create-bucket-configuration LocationConstraint=${REGION}
$ aws s3api put-public-access-block \
--bucket $BUCKET_NAME \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
いよいよサンプルソースコードを利用してAWSリソースを作成します。 今回、CloudFormationでは3つのスタックを用意しています。
スタック | 内容 |
---|---|
infrastructure | VPC、サブネットなどネットワーク周りやライフサイクルが長いリソース |
app-base | ECRなどアプリに必要なベースリソース |
application | ECSサービスなどアプリに必要なリソース |
それぞれのテンプレートからスタックを作成して、リソースをデプロイします。
サンプルコードではフラットにCloudFormationを記述するのではなく、Nested Stackを利用しています。親となるスタックからNestされたスタックを利用するためにはテンプレートのURLを指定します。S3にNestしたテンプレートを格納し、S3のURLを指定することとします。
$ cd ~/environment/iac-story-code/cloudformation/
$ aws s3 cp infrastructure/ s3://${BUCKET_NAME}/infra --recursive
upload: infrastructure/iam.yml to s3://[BUCKET_NAME]/infra/iam.yml
︙
$ aws s3 cp appbase/ s3://${BUCKET_NAME}/appbase --recursive
upload: appbase/ecr.yml to s3://[BUCKET_NAME]/appbase/ecr.yml
$ aws s3 cp application/ s3://${BUCKET_NAME}/application --recursive
upload: application/cloudwatch.yml to s3://[BUCKET_NAME]/app/cloudwatch.yml
upload: application/alb.yml to s3://[BUCKET_NAME]/application/alb.yml
upload: application/ecs.yml to s3://[BUCKET_NAME]/application/ecs.yml
infrastructure.yml
のAWSアカウントIDを置き換えます。
$ sed -i -e "s/\[AWS_ACCOUNT_ID\]/${AWS_ACCOUNT_ID}/" infrastructure.yml && grep ${AWS_ACCOUNT_ID} infrastructure.yml
AWS CLIからスタック作成を実行します。
$ pwd
/home/ec2-user/environment/iac-story-code/cloudformation
# IAMの作成を伴うのでcapabilitiesの指定が必要
$ aws cloudformation create-stack --stack-name cnis-infrastructure --template-body file://infrastructure.yml --capabilities CAPABILITY_NAMED_IAM
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-infrastructure/addb4cf0-cfdb-11eb-8dff-0e9cfcf32e9f"
}
# リソースの作成状況を確認します。次のように、LogicalResourceId:cnis-infrastructureのResourceStatusがCREATE_COMPLETEとなるまで待ちましょう。
# watchコマンドにより、数秒おきにCloudFormationのスタック状態が更新されます。
# 完了まで数分かかることがあります。
$ watch "aws cloudformation describe-stack-events --stack-name cnis-infrastructure | head -20"
:
{
"StackEvents": [
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-infrastructure/46e59460-d03f-11eb-92eb-0e9b5e8bc693",
"EventId": "ed5e9d50-d03f-11eb-9e37-068875dc9763",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceType": "AWS::CloudFormation::Stack",
"Timestamp": "2021-06-18T14:17:34.872Z",
"StackName": "cnis-infrastructure",
"PhysicalResourceId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-infrastructure/46e59460-d03f-11eb-92eb-0e9b5e8bc693",
"LogicalResourceId": "cnis-infrastructure"
},
# Ctrl+Cで終了
app-base.yml
のAWSアカウントIDを置き換えます。
$ sed -i -e "s/\[AWS_ACCOUNT_ID\]/${AWS_ACCOUNT_ID}/" app-base.yml && grep ${AWS_ACCOUNT_ID} app-base.yml
AWS CLIからスタック作成を実行します。
$ pwd
/home/ec2-user/environment/iac-story-code/cloudformation
$ aws cloudformation create-stack --stack-name cnis-appbase --template-body file://app-base.yml
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:
123456789012:stack/cnis-appbase/73de9b30-cfde-11eb-8a91-0e2c15a96cb9"
}
# リソースの作成状況を確認します。次のように、LogicalResourceId:cnis-appbaseのResourceStatusがCREATE_COMPLETEとなるまで待ちましょう。
$ watch "aws cloudformation describe-stack-events --stack-name cnis-appbase | head -20"
:
{
"StackEvents": [
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-appbase/7bc67f30-d041-11eb-b500-0ac1c07a5d9b",
"EventId": "85a3d390-d041-11eb-8dff-0e9cfcf32e9f",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceType": "AWS::CloudFormation::Stack",
"Timestamp": "2021-06-18T14:28:59.840Z",
"StackName": "cnis-appbase",
"PhysicalResourceId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-appbase/7bc67f30-d041-11eb-b500-0ac1c07a5d9b",
"LogicalResourceId": "cnis-appbase"
},
# Ctrl+Cで終了
作られたAWSリソースにおいて、ECSはECRからコンテナイメージを取得してデプロイするのですが、 現状ではECRにコンテナが登録されていません。 そこで、次に従ってサンプルアプリをコンテナビルドし、ECRに対してプッシュします。
# Dockerビルドにてコンテナイメージを作成
$ cd ~/environment/iac-story-code/app/
$ export CONTAINER_NAME="cnisapp"
$ export CONTAINER_TAG="init"
$ docker build -t ${CONTAINER_NAME}:${CONTAINER_TAG} .
Sending build context to Docker daemon 11.82MB
Step 1/14 : FROM golang:1.16.5-alpine3.13 AS build-env
1.16.5-alpine3.13: Pulling from library/golang
:
Successfully built 7aa88fd39158
Successfully tagged cnisapp:v1
# ECRにログインしてコンテナイメージをプッシュ
$ export AWS_ACCOUNT_ID=`aws sts get-caller-identity | jq .Account -r`
$ $(aws ecr get-login --no-include-email --registry-ids ${AWS_ACCOUNT_ID} --region ap-northeast-1)
$ AWS_ECR_URL=`aws ecr describe-repositories | jq .repositories[].repositoryUri -r | grep cnis-ecr-app`; docker tag ${CONTAINER_NAME}:${CONTAINER_TAG} ${AWS_ECR_URL}:${CONTAINER_TAG}
$ docker push ${AWS_ECR_URL}:${CONTAINER_TAG}
# プッシュしたイメージ内容の確認
$ AWS_ECR_REPO_NAME=`aws ecr describe-repositories | jq .repositories[].repositoryName -r | grep cnis`; aws ecr describe-images --repository-name $AWS_ECR_REPO_NAME
以上でデプロイするコンテナイメージがECRに登録できました。
application.yml
のAWSアカウントIDを置き換えます。
$ cd ~/environment/iac-story-code/cloudformation
$ sed -i -e "s/\[AWS_ACCOUNT_ID\]/${AWS_ACCOUNT_ID}/" application.yml && grep ${AWS_ACCOUNT_ID} application.yml
AWS CLIからスタック作成を実行します。
$ pwd
/home/ec2-user/environment/iac-story-code/cloudformation
$ aws cloudformation create-stack --stack-name cnis-application --template-body file://application.yml
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-infrastructure/addb4cf0-cfdb-11eb-8dff-0e9cfcf32e9f"
}
# リソースの作成状況を確認します。次のように、LogicalResourceId:cnis-applicationのResourceStatusがCREATE_COMPLETEとなるまで待ちましょう。
# 完了まで数分かかることがあります。
$ watch "aws cloudformation describe-stack-events --stack-name cnis-application | head -20"
:
{
"StackEvents": [
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-application/4ab99f70-d042-11eb-a387-0ea6b7b9f1b7",
"EventId": "125727a0-d043-11eb-88bf-069d1cdf7719",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceType": "AWS::CloudFormation::Stack",
"Timestamp": "2021-06-18T14:40:05.392Z",
"StackName": "cnis-application",
"PhysicalResourceId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/cnis-application/4ab99f70-d042-11eb-a387-0ea6b7b9f1b7",
"LogicalResourceId": "cnis-application"
},
# Ctrl+Cで終了
続けて、以下コマンドによりプッシュしたコンテナがECS上にデプロイされるか確認します。 デプロイが完了すると、以下のようにECSタスクのARNが返却されます。
$ while true; do aws ecs list-tasks --cluster cnis-ecs-cluster-app; sleep 10; done
{
"taskArns": []
}
{
"taskArns": []
}
:
{
"taskArns": [
"arn:aws:ecs:ap-northeast-1:123456789012:task/cnis-ecs-cluster-app/8e2be702a59a4d5d9847b0f1cfdb52b0"
]
}
# Ctrl+C で停止
デプロイされたアプリに対してリクエストを送ります。
$ export APP_FQDN=`aws elbv2 describe-load-balancers | jq .LoadBalancers[].DNSName -r | grep cnis-`
$ curl http://${APP_FQDN}/cnis/v1/helloworld
"Hello world!"
ハローワールドのレスポンスが返ってきました! IaCから作成されたアプリが正常稼働していそうです。 続けて、Systems Manager パラメータストアに格納された値がアプリの環境変数として取り込まれているので、 その値を取得してみましょう。
まずはパラメータストアの値を見てみます。
$ aws ssm get-parameter --name cnis-ssm-param-cnis-app | jq .Parameter.Value -r
Cloud Native IaC Story
"Cloud Native IaC Story"という値が設定されていますね。 続けてデプロイアプリに対してリクエストを行います。
$ curl http://${APP_FQDN}/cnis/v1/param
"Cloud Native IaC Story"
同じ文字列が返却されました! IaCから作成されたECSやSSMパラメータストアを通して、アプリが稼働する一連のAWSリソースを作成できました。
以上でハンズオンは終了です。 ぜひ、こちらのIaCサンプルコードを活用して、自分達のIaCサービスに活用していってください。
作成したAWSリソースを順番に削除していきます。
ECRにコンテナイメージが残っている状態でリポジトリを削除しようとすると次のエラーが発生します。 エラーメッセージでは「このリポジトリにコンテナイメージが含まれているから削除できません」という内容です。
Resource handler returned message: "The repository with name 'cnis-ecr-app' in registry with id 'xxxxxxx' cannot be deleted because it still contains images
Cloud9から次のコマンドを実行してECRに格納されているイメージを削除します。
$ aws ecr batch-delete-image \
--repository-name cnis-ecr-app \
--image-ids imageTag=init
{
"failures": [],
"imageIds": [
{
"imageTag": "init",
"imageDigest": "sha256:759116eef9c1d191dc83a574220a9052a6af555dac6a369da7cb8b5ce8563e13"
}
]
}
failures
が空となっていれば完了です。ECRのダッシュボードでコンテナイメージが削除され、イメージが存在しないことを確認してください。
AWS マネジメントコンソールから削除していきましょう。 まず、CloudFormationのダッシュボードへ遷移します。
3つのリソース生成用スタックと、1つのCloud9用のスタックがあります。
リソース生成用スタックをcnis-application
→cnis-appbase
→cnis-infrastructure
の順に削除していきましょう。
cnis-application
はECSサービスの削除となり、時間がかかります。筆者が実施した際は7分ほどかかりました。気長に待ちましょう。
- AWSマネジメントコンソール上部の [サービス] タブより [S3] を選択。
- S3ダッシュボードの左側ナビゲーションメニューから [バケット] を選択。
- バケット一覧から[cnis-cfn-bucket-[AWSアカウントID]] を選択し、[空にする] ボタンを押下。
- バケットを空にする画面にて、テキストフィールドに[完全に削除] と入力し、[空にする] ボタンを押下。
- 正常に空になったことを確認し、[終了] ボタンを押下。
- バケット一覧から[cnis-cfn-bucket-[AWSアカウントID]] を選択し、[削除] ボタンを押下。
- バケットを空にする画面にて、テキストフィールドにバケット名を入力し、[バケットを削除] ボタンを押下。
- 正常に削除されたことを確認。
以上で作成したリソース削除は完了です。 必要に応じてリポジトリ直下のREADME.mdに従ってCloud9を削除してください。
お疲れ様でした。
- 2021年6月現在、CloudFormation にはCloudFormation Modulesが用意されていますが、今回は利用しません。
- テンプレートよりさらに細かい粒度のモジュールを定義できる機能です。
- 2021年4月にYAMLがサポートされてようやく書ける体制が整いつつある状態です。
- モジュール利用のためには事前にCloudFormation レジストリへのモジュール登録が必要です。
- モジュール登録がかなり手間で微妙なので今回は利用を見送りました。
- テンプレートよりさらに細かい粒度のモジュールを定義できる機能です。
- ニーズがあれば、ハンズオン資料を充実させたいと思うので、必要であればプルリク上げてください。