TerraformとCopilotでFargateを使ったモダンなAWS環境をかんたん構築

TerraformとCopilotでFargateを使ったモダンなAWS環境をかんたん構築

概要

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で通信可能にしています。

図にすると以下のような構成です。

image

前提条件

  • 開発環境 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をインストールする

下記リンクよりIAMアカウントのアクセスキーとシークレットキーをプロファイル登録する

※ コマンド実行端末に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

copilot/kon-yu-service/manifest.yml
# The manifest for the "kon-yu-service" service.
# Read the full specification for the "Load Balanced Web Service" type at:
#  https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/

# Your service name will be used in naming your resources like log groups, ECS services, etc.
name: kon-yu-service
type: Load Balanced Web Service

# Distribute traffic to your service.
http:
  # Requests to this path will be forwarded to your service.
  # To match all requests you can use the "/" path.
  path: '/'
  # You can specify a custom health check path. The default is "/".
  # healthcheck: '/'

# Configuration for your containers and service.
image:
  # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#image-build
  build: Dockerfile.aws
  # Port exposed through your container to route traffic to it.
  port: 3000

cpu: 256       # Number of CPU units for the task.
memory: 512    # Amount of memory in MiB used by the task.
count: 1       # Number of tasks that should be running in your service.
exec: true     # Enable running commands in your container.

# Optional fields for more advanced use-cases.
#
#variables:                    # Pass environment variables as key value pairs.
#  LOG_LEVEL: info

#secrets:                      # Pass secrets from AWS Systems Manager (SSM) Parameter Store.
#  GITHUB_TOKEN: GITHUB_TOKEN  # The key is the name of the environment variable, the value is the name of the SSM parameter.

# You can override any of the values defined above by environment.
#environments:
#  test:
#    count: 2               # Number of tasks to run for the "test" environment.

デプロイ先の環境 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できるようになっています

発生するエラーメッセージ

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

Codepipelineの設定を修正する

CodepipelineからGithubと接続するには、手動でAWSを接続する設定を行います。

CodePipelineのAWSコンソールを表示し、Sourceの箇所で失敗しているで、編集ボタンを押し、次のSourceの箇所にある ステージの編集 を押して編集する

アクションの編集らんで失敗しているのでGithubとの接続を行う。

この設定をする際にはGithubのOwner権限が必要なので注意が必要です。

image

上記変更を行うと、PipelineのSourceのブロックを成功させることができます。

独自ドメインを使ってHTTPSで接続するための手動設定

前提条件

Route53でドメインの取得をしていること。

Route53ではなくお名前ドットコムなどのDNSでも可能だが、圧倒的にRoute53で行うのが楽です。

ACMの設定する

HTTPSで接続するためのACMを作成する必要があります。Route53でCNAMEを設定してACMで証明書を取得する。詳しくは詳しくは下記を参照してください。

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メール認証をするには下記のリンクに従って作業してください

サーバの端末に入ってシェルを実行する

デバッグのために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に接続する

  1. 秘密鍵をコピーする
  2. 踏み台サーバ(Bastion host)にSSHでアクセスする
  3. 踏み台サーバから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のしくみも構築することができました。これにより本番、ステージング、更にもう一つ環境が必要な際にも少ない工数で構築することができます。

参考リンク

PR

XではUZUMAKIの新しい働き方や日常の様子を紹介!ぜひフォローをお願いします!

noteではUZUMAKIのメンバー・クライアントインタビュー、福利厚生を紹介!

UZUMAKIではRailsエンジニアを絶賛募集中です。

↓の記事を読んでご興味を持っていただいた方は、ぜひ応募よろしくお願いします!

是非応募宜しくおねがいします!