概要
Infrastructure as codeが出来るようなり数年がたち今日ではTerraformやCloudFormation、AWS CDKなどのしくみが作られるようになりました。
同時にDockerを使ったコンテナをプロダクション環境で利用する機会も増えてきました。
今回はAWSのECS Fargateを使った基本的なウェブサービスのインフラをCopilotとTerraformを使って作成するものです。
今までは簡単にAWSでのウェブサービスはElastic Beanstalkを使うことが定番でしたが本資料では同じぐらい簡単にECS Fargateを使って実現することができます。
terraformアドベントカレンダー 2021 4日目の記事です。
目次
目的
GithubのmainブランチがコミットされたらAWS環境にデプロイするような基本的なCI/CDのインフラをterraformとcopilotの合わせ技で基本的なWebアプリケーションの構築します。
copilotではFargateを使ったWebアプリケーションを簡単に作成することができますが、2021年現在のcopilitの仕様ではカバーしきれない部分をterraformで補完する方法を提案します
対象読者
インフラのコード化に二の足を踏んでいるインフラエンジニアやSRE
Elastic BeanstalkからそろそろFargateにインフラ構成を乗り換えたいソフトウェアアーキテクト
PR
UZUMAKIではアジャイル開発で新規事業の開発から、大規模Webアプリケーションのアーキテクチャ更新などの開発をしています。
お問い合わせはUZUMAKIのHPのお問合せフォームから
本文
サーバ構成
今回の記事で作成される全体像
構成概要
インターネットからのアクセスをALBで受けつけ、負荷分散してパグリックサブネットにあるECS Fargateで動くWebアプリケーションサーバ(Rails)と通信します。
WebアプリケーションサーバはプライベートサブネットにあるRedisやAurora(それぞれ複数台で冗長化)と通信します。
またWebアプリケーションサーバはSESやSNSとも通信します。
プライベートサブネットにあるAuroraへ簡易にアクセスするために踏み台サーバをパブリックサブネットに用意しSSHで通信可能にしています。
図にすると以下のような構成です。
前提条件
- 開発環境 mac os かつhomebrew、Docker Desktop がインストール済み
- デプロイ先のクラウドサービスはAWS
- macはintel cpuで有ること
- 手動デプロイの際にARMだと2021/11/30の時点では動きません
- 今年のRe:InventでARMのFargateがサポートが発表されましたので今後はできるようになるかもしれません
Terraform編 VPC及びミドルウェア構築
terraformで作られるネットワーク構成
- 東京リージョンのVPC
- パブリックサブネット(MultiAZ) x 2
- プライベートサブネット(MultiAZ) x 2
- RDB(Aurora MySQL MultiAZ) ※ staging環境はAuroraサーバレスを利用
- ElastiCache(Redis クラスター MultiAZ) ※staging環境はシングルノード構成
- IAMユーザ(アプリケーションサーバで利用。SES, SNSへの実行権限)
細かいコードについては下記リポジトリを参照してください
Terrafform の CLI インストール
# brew に hashicorp 社の repository を追加
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# インスールの確認
terraform -v
Terraform v1.0.11
on darwin_amd64
各環境用のterraform作業用のディレクトリへ移動
各環境ごとにterraform用のディレクトリを作成して作業を実施します。
terraformではWorkSpacesの概念があり環境ごとにインフラを構築することは出来るが構成が若干違う場合にうまくワークしないため今回は環境ごとに作成しています。moduleを利用すればもっと記述にかぶりがなく作成することが出来ると思います。
- staging環境: terraform/staging
- 本番環境: terraform/production
terraformの初期化
terraformを利用する場合に初期化作業を実施します。
terraform の初期化コマンドの実行
terraform init
.terraform.lock.hcl ファイルが作成されることを確認します
シークレット情報を作成する
シークレット情報を、各環境(terraform/stagingやterraform/production)のsecret.tfvarsに記載する こうすることでシークレット情報をGit管理しないようにしています。
ファイルの形式は以下のよう パラメータ名 = "値" とする
rds_master_username = "AAAAAAAAAAAAAA"
rds_master_password = "XXXXXXXXXXXXXXX"
ドライランの実行
これから作られる・変更される・削除されるもの差分をチェックしてくれる
terraform plan -var-file="secret.tfvars"
(option)AWS vault から使う方法
aws-vault exec YOUR_PROFILE -- terraform plan -var-file="secret.tfvars"
実際に構築
AWS上でキーペアの作成
踏み台サーバのEC2で使うキーペア(kon-yu-key)を予め作成しておきます。
(terraform力がもっとあればここも自動化出来るのかもしれません)
構築コマンドの実行
terraform apply -var-file="secret.tfvars
terraformの構築に成功すると下記のようなvpcのid情報が出力されるのでメモして、copilotでの環境構築に使用します。
aws_vpc_id = "vpc-xxxxxxxxxx"
private_subnet_a_id = "subnet-xxxxxxxxxxx"
private_subnet_c_id = "subnet-yyyyyyyyyyy"
public_subnet_a_id = "subnet-zzzzzzzzzzzz"
public_subnet_c_id = "subnet-aaaaaaaaaaaa"
(option)削除する場合
terraform destroy
動作確認
AWSのウェブコンソールに入り構成通りのVPC及びAurora, Redisが構築されていることを確認してください。
Copilotアプリケーションサーバ構築手順
準備作業
AWSのWebコンソールから下記の情報をメモする
- terraformで作成したIAMユーザのアクセスキーとシークレットキーを作成
- redisのエンドポイント
- auroraのエンドポイント
Copilit CLIのインストール
IAMアカウントの準備
IAMのアカウントがない場合は管理者アカウントを持っているユーザに発行してもらてもらい、AWSのWebコンソールにログインできることを確認する。
AWS CLIのインストールおよびプロファイルの設定
下記リンクよりAWS CLIをインストールする
AWS コマンドラインインターフェイス(CLI: AWSサービスを管理する統合ツール)| AWS
下記リンクよりIAMアカウントのアクセスキーとシークレットキーをプロファイル登録する
設定ファイルと認証情報ファイルの設定 - AWS Command Line Interface
※ コマンド実行端末にAWSのプロファイルが複数ある場合は
AWS_PROFILE=プロファイル名 コマンド としてください
copilot -v ✭ main
copilot version: v1.9.0
Copilot で アプリケーション実行環境を構築する
本章作られる構成
- terraformで作成したVPCのパブリックサブネットにECS Fargateの設定をしRailsを動くようにする
- ALBによる負荷分散
- Fargateではオートスケール
Copilotの初期化
リポジトリのルートディレクトリから実行してください。
copilot init --app kon-yu-app --dockerfile Dockerfile.aws \
--name kon-yu-service --port 3000 --type "Load Balanced Web Service"
Cloudformationが作成され必要なAWS::IAM::Roleが作成されます。
またcopilotのアプリケーションとサービスの設定が記述されたmanifest.ymlが作成されます。
アプリケーションとサービスは
- アプリケーションはサービスと環境の郡をまとめたもの
- サービスはECSのサービスの設定をまとめたもの
ではあるが基本的に1リポジトリで一つのウェブサービスを立ち上げる場合はサービスだけ意識しておけば良いでしょう。
—portと—typeを指定することでALBから3000ポートへアクセスするようにしています
実行すると下記のようなログが表示されます
Welcome to the Copilot CLI! We're going to walk you through some questions
to help you get set up with a containerized application on AWS. An application is a collection of
containerized services that operate together.
Ok great, we'll set up a Load Balanced Web Service named kon-yu-service in application kon-yu-app listening on port 3000.
✔ Created the infrastructure to manage services and jobs under application kon-yu-app..
✔ Wrote the manifest for service kon-yu-service at copilot/kon-yu-service/manifest.yml
Your manifest contains configurations like your container size and port (:3000).
✔ Created ECR repositories for service kon-yu-service..
All right, you're all set for local development.
Deploy: No
No problem, you can deploy your service later:
- Run `copilot env init --name test --profile default --app kon-yu-app` to create your staging environment.
- Update your manifest copilot/kon-yu-service/manifest.yml to change the defaults.
- Run `copilot svc deploy --name kon-yu-service --env test` to deploy your service to a test environment.
⠸ Creating the infrastructure to manage services and jobs under application kon-yu-app.
※ Would you like to deploy a test environment? [? for help] (y/N) とコンソールで聞かれた際にNを選択しないと、terraformで作ったVPCではないところに新たにVPC等を作成してしまうので注意が必要です。
自動で生成されたmanifest.yml
デプロイ先の環境 env の作成
terraformを実行した際に取得したvpcやパブリックサブネット、プライベートサブネットを指定してenvを作成します。ここでCloudformationでAWS::ECS::Clusterの設定等を行います。
こうすることでプライベートサブネットにあるAuroraやRedisにアクセス可能なFargateが作成されます。
その際に環境名を指定(今回はstaging), —prodは—test 2つオプションがありtestであると作成するコンテナを2つに制限します。prodの場合はmanifest.ymlに記載してた通りの台数でオートスケールしたりできます。
copilot env init --name staging --prod \
--import-vpc-id vpc-xxxxxxxxxxxxx \
--import-public-subnets subnet-xxxxxxxxxxxx,subnet-yyyyyyyyyyyyy \
--import-private-subnets subnet-zzzzzzzzzzz,subnet-aaaaaaaaa
環境変数の登録
保護する必要のない環境変数は以下の例のように copilot/kon-yu-service/manifest.yml のvariablesに追記します。
variables:
RAILS_ENV: production
# ログを標準出力する。Fargate(ECS)はログを標準出力に出すと CloudWatch Logs に転送する
RAILS_LOG_TO_STDOUT: true
RACK_ENV: production
NODE_ENV: production
シークレット(環境変数)の登録
保護が必要な環境変数はシークレットコマンドで AWS Systems Manager Parameter Store を利用してパラメータを保存します。
下記のようにキーを登録すると、copilot/kon-yu-service/manifest.ymlに追記を促され通り追記します。
copilot secret init --name RAILS_MASTER_KEY
✔ Successfully put secret RAILS_MASTER_KEY in environment staging as /copilot/kon-yu-app/staging/secrets/RAILS_MASTER_KEY.
You can refer to these secrets from your manifest file by editing the `secrets` section.
staging
secrets:
RAILS_MASTER_KEY: /copilot/kon-yu-app/staging/secrets/RAILS_MASTER_KEY
手動デプロイ
ローカル環境からDockerイメージをビルドしてECRにプッシュしつつ、Fargateにデプロイします。
copilot svc deploy --name kon-yu-service --env staging
Environment staging is already on the latest version v1.5.1, skip upgrade.
[+] Building 6.6s (4/20)
=> [internal] load build definition from Dockerfile.aws 0.1s
=> => transferring dockerfile: 41B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 4.71kB 0.0s
=> [internal] load metadata for docker.io/library/ruby:3.0.1 0.0s
=> [internal] load build context
....
- Creating the infrastructure for stack kon-yu-app-staging-kon-yu-service [create complete] [289.4s]
- Service discovery for your services to communicate within the VPC [create complete] [1.0s]
- Update your environment's shared resources [update complete] [119.9s]
- A security group for your load balancer allowing HTTP and HTTPS traffic [create complete] [6.8s]
- An Application Load Balancer to distribute public traffic to your services [create complete] [91.3s]
- An IAM Role for the Fargate agent to make AWS API calls on your behalf [create complete] [24.1s]
- A CloudWatch log group to hold your service logs [create complete] [2.1s]
- An ECS service to run and maintain your tasks in the environment cluster [create complete] [99.6s]
Deployments
Revision Rollout Desired Running Failed Pending
PRIMARY 1 [completed] 1 1 0 0
- A target group to connect the load balancer to your service [create complete] [0.0s]
- An ECS task definition to group your containers and run them on ECS [create complete] [1.5s]
- An IAM role to control permissions for the containers in your tasks [create complete] [24.1s]
✔ Deployed kon-yu-service, you can access it at http://kon-y-Publi-I0NAAAWGK10B-1849777453.ap-northeast-1.elb.amazonaws.com. 6.4s
Rails のログを Cloud watch logs に出力する
標準出力にログを出していればCloud watch logsに出力されます
(Option)デプロイ失敗時のワークアラウンド
デプロイ途中にCntl + Cを押すと、デプロイの段階によってデプロイコマンドが打った時にエラーになります。 その際はデプロイコマンドでCloudformationが止まっていない可能性があります。
その場合は下記のデプロイに失敗するときを参考してください
- CloudFormationをWebコンソールで開き、参照してスタックを止める
- スタック名はcopilotのアプリ名-サービス名 になっているので簡単に見つけることができます
- UPDATE_IN_PROGRESSで対象のスタックが止まっているのを見つけたら
- スタックアクションのメニューから更新をキャンセルする を選択してください
オートスケールの設定
manifest file の count 領域を書き換えてください。
またcpu_percentageやmemory_percentageでオートスケールするしきい値を設定します。
count: range: 1-10
# Specify a range for how many tasks you'd like to run in your service.
cpu_percentage: 70 # To scale on average CPU. memory_percentage: 80 # Or, to scale on average Memory.
CI/CD(CodePipeline)設定
概要
Copilotで、CodePipelineを構築しGithubで特定のブランチに変更時に、自動ビルドするよう設定します。
初期化
gitのブランチを指定して初期化します。
copilot pipeline init -a kon-yu-app -e staging -b main
copilotディレクトリ下にpipeline.ymlとbuildspec.ymlが作成されます。
pipeline設定を反映させる
copilot pipeline update
2021年11月現在 コンソール上では、下記のような作成時の作成中が終わらないので、AWSのウェブコンソールでCloudformationのスタックを確認し、ステータスがCompletedになっていれば Ctrl+Cで抜けてOK
⠏ Creating a new pipeline: pipeline-kon-yu-app
パイプラインのワークアラウンド
パイプライン名に_は使えない
自動生成されるpipeline.ymlではリポジトリ名をnameに利用しますが、AWSの命名規則上アンダーバーが使えないのでnameの値に_ があれば置き換えが必要です。
❌ name: pipeline-kon-yu-app-copilit_with_terraform_sample
⭕ name: pipeline-kon-yu-app-copilit-with-terraform-sample
時々ビルドが失敗する問題への対応
原因はAWS CodeBuildでDockerHubからRubyのコンテナイメージをPullするときにこのようなエラーが発生します。
【更新】AWS ECSのパブリックライブラリからコンテナイメージをPullすることでこちらの対応をすることなく無制限にDLできるようになっています
https://aws.amazon.com/jp/blogs/aws/amazon-ecr-public-a-new-public-container-registry/
発生するエラーメッセージ
toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit
DockerHubに公開されているRubyのイメージをPullしてビルドする際にAWSの同一のIPアドレスPullされる件数が多く上記エラーが発生します。
こちらを参考に適当なDockerhubアカウントを取得して適用して、
CodeBuildでビルドする際にDockerHubにCLIでログインすることでこの問題を回避しています。
buildspec.yml の変更点は下記
env:
variables:
# Dockerhubのアカウント情報
DOCKER_HUB_ID: ID
DOCKER_HUB_PASSWORD: パスワード
phases:
install:
runtime-versions:
docker: 18
ruby: 2.6
commands:
- echo "cd into $CODEBUILD_SRC_DIR"
- cd $CODEBUILD_SRC_DIR
# Download the copilot linux binary.
- wget https://ecs-cli-v2-release.s3.amazonaws.com/copilot-linux-v1.9.0
- mv ./copilot-linux-v1.9.0 ./copilot-linux
- chmod +x ./copilot-linux
pre_build:
# ビルドする前の処理を追加
commands:
# DockerHubにログイン
- echo Logging in to Docker Hub...
- echo ${DOCKER_HUB_PASSWORD} | docker login -u ${DOCKER_HUB_ID} --password-stdin
- https://aws.amazon.com/jp/premiumsupport/knowledge-center/codebuild-docker-pull-image-error/
- https://dev.classmethod.jp/articles/codebuild-has-to-use-dockerhub-login-to-avoid-ip-gacha/
Codepipelineの設定を修正する
CodepipelineからGithubと接続するには、手動でAWSを接続する設定を行います。
GitHub connections - AWS CodePipeline
CodePipelineのAWSコンソールを表示し、Sourceの箇所で失敗しているで、編集ボタンを押し、次のSourceの箇所にある ステージの編集 を押して編集する
アクションの編集らんで失敗しているのでGithubとの接続を行う。
この設定をする際にはGithubのOwner権限が必要なので注意が必要です。
上記変更を行うと、PipelineのSourceのブロックを成功させることができます。
独自ドメインを使ってHTTPSで接続するための手動設定
前提条件
Route53でドメインの取得をしていること。
Route53ではなくお名前ドットコムなどのDNSでも可能だが、圧倒的にRoute53で行うのが楽です。
ACMの設定する
HTTPSで接続するためのACMを作成する必要があります。Route53でCNAMEを設定してACMで証明書を取得する。詳しくは詳しくは下記を参照してください。
パブリック証明書のリクエスト - AWS Certificate Manager
オプション 1: DNS での検証 - AWS Certificate Manager
ALBの設定
ALBのリスナーでHTTPSで転送するリスナーを追加してください。
セキュリティポリシーは ELBSecurityPolicy-TLS-1-2-Ext-2018-06
ALBの詳細からDNSをメモし、Route53のドメインのあるところに行って、CNAMEで対象のFQDNとALBのDNSを紐付けると、5分ぐらい待つとにHTTPSで設定できるようになります。
詳しくは下記のページの 「ACM SSL 証明書と Classic Load Balancer の関連付け」を参照してください。
SESの設定
本校の本筋とははずれるがSESを使う場合はACMやALBと同じようにRoute53でレコードを追加する作業をします(また本番利用するためにAWS への新生が必要です)。
SESを新規作成して、下記リンクに従いDKIMのEメール認証をするには下記のリンクに従って作業してください
Amazon SES における DKIM を使った E メールの認証 - Amazon Simple Email Service Classic
サーバの端末に入ってシェルを実行する
デバッグのためにRailsコンソールなど実行する場合
実行コマンド
copilot svc exec -n kon-yu-service -e staging
Execute `/bin/sh` in container kon-yu-service in task 1d496fee6c514b81bb10d95fe119d2f8.
Starting session with SessionId: ecs-execute-command-05dcb2945872a8b6e
# この時点でサーバに入っているのでRails consoleなど実行できる
# rails c
Loading production environment (Rails 6.1.3.2)
irb(main):001:0> User.first
踏み台サーバを使ったDBアクセス方法
以下3ステップでAWS内にあるAurora RDBに接続する
- 秘密鍵をコピーする
- 踏み台サーバ(Bastion host)にSSHでアクセスする
- 踏み台サーバからmysqlコマンドを実行する
秘密鍵をコピーする
踏み台サーバのEC2の秘密鍵をダウンロードし秘密鍵の権限を変更する
chmod 600 kon-yu-app-key.pem
踏み台サーバ(Bastion host)にSSHでアクセスする
踏み台サーバにSSHで接続する例
ssh -i kon-yu-app-key.pem ec2-user@xx.xxx.xxx.xxx
踏み台サーバからmysqlコマンドを実行する
ステージング環境
mysql -u USER_NAME -p -h AURORA_HOST_NAME
パスワードを入力してください
考察・提案
terraformで作成したベースにcopilotでFargateを使ったウェブアプリケーションをCI/CDを環境を構築できた。
今回とは逆にcopilotで作成した構成にterraformでプライベートサブネットとAuroraやRedisを設置するように作成する作戦出来るでしょう。どちらが簡単試してみても良いかもしれません。
またCopilotを内包したサービスApp Runnerももう少ししたらもっと自由度が高くなるのでより便利になるでしょう。
個人的には合わせてAurora Serverless v2がGAになるとより便利になるのではと思っています。
まとめ
TerraformとCoilotを使ってサクッと基本的になウェブサービスのインフラを構成することができました。本番環境に必要なマルチAZやオートスケール、CI/CDのしくみも構築することができました。これにより本番、ステージング、更にもう一つ環境が必要な際にも少ない工数で構築することができます。
参考リンク
- インストール Install Terraform | Terraform - HashiCorp Learn
- terraform に入門した(aws-vault を使う ver) - あきろぐ
- 「Terraform 入門 on AWS」という動画を投稿しました #devio2020 | DevelopersIO
- Terraform で AWS 構築・運用自動化の入門 - Qiita
- 完全初心者向け Terraform 入門(AWS) | DCS blog
- 「Terraform 入門 on AWS」という動画を投稿しました #devio2020 | DevelopersIO
- 【Terraform 再入門】EC2 + RDS によるミニマム構成な AWS 環境をコマンドライン一発で構築してみよう – PSYENCE:MEDIA
- Rails Docker Amazon ECS で動かす Rails アプリの Dockerfile と GitHub Actions のビルド設定 - メドピア開発者ブログ
- AWS Copilot を使用した Amazon ECS の開始方法 - Amazon Elastic Container Service
- Frontend Rails App :: Amazon ECS Workshop
- AWS Copilot で Amazon ECS の環境と CI/CD の超簡単構築を試してみた - SMARTCAMP Engineer Blog
- 『AWS Copilot』で ECS の CI/CD パイプラインをサクッと作る - 継続は力なり
- ELB に HTTPS にする AWS で Web サイトを HTTPS 化 その 1:ELB(+ACM 発行証明書)→EC2 編 – ナレコム AWS レシピ | AI に強い情報サイト
- SES の設定 Amazon SES によるメール送信環境の構築と実践 | DevelopersIO Amazon SES によるメール送信環境の構築と実践 | DevelopersIO
- [新機能]Amazon SES でメール受信が出来るようになりました! | DevelopersIO
- SES の AWS 設定で参考になった AWS SES で送信ドメイン認証を設定する - Qiita
- Rails の具体的な設定 [Rails6 対応, 公式 SDK] AWS SES を使って Rails から送信元が独自ドメインのメールを送ってみた - Qiita
PR
XではUZUMAKIの新しい働き方や日常の様子を紹介!ぜひフォローをお願いします!
noteではUZUMAKIのメンバー・クライアントインタビュー、福利厚生を紹介!
UZUMAKIではRailsエンジニアを絶賛募集中です。
↓の記事を読んでご興味を持っていただいた方は、ぜひ応募よろしくお願いします!
是非応募宜しくおねがいします!