RoadMovie

write down memos or something I found about tech things

【実践: 詳しくわかる】TerraformでCircleCIを通してAWSにECS環境を自動構築する方法

まず最初に今回の記事で扱う内容を書かせていただきます。

1. docker-composeで開発環境を構築し
2. masterにpushした時のみCircleCIでECRにDockerイメージを構築し
3. 同時にCircleCIからterraformで本番環境のインフラをAWSにECSで整え
4. Deployを完了する

ということを実際のコードを交えながら解説していきたいと思います。つまるところmasterにプッシュしたら自動でDockerイメージが作られて自動で環境整えてデプロイされている状態を目指す、という話です。Blue/Greenデプロイと呼ばれるやつの自動化ですね。


技術的には、下記のものを扱いますが、ところどころご自身の技術スタックと違っていても読み替えられるかと思います。

  • terraform v0.12.18 (v0.11とv0.12で少し違いがあるので注意)
  • Rails (他のWAFでも読み替えられると思います)
  • CircleCI ( Continuous Integration and Delivery - CircleCI )
  • AWS & ECS (AWSに関してはECS以外にも様々なサービスをterraformを通して利用します)


Terraformの構成としては、環境ごとのVPC内に、2つのavailability zoneを用意し、それぞれの中にpublic, private subnetを1つずつ用意します。ロードバランサがpublicにいて、Clusterはprivateにいるような形です。また、web app環境としてstagingとproductionを用意し、RDS(MySQL)とつなぎます。Route53で設定しているドメインの設定や、taskでmigrationを走らせるところなどもカバーしています。


1. docker-composeで開発環境を構築

まずは手始めに一番簡単なところから行きます。最早あらゆる視点から考えて開発環境はdocker化しておいたほうが楽なので必須ですね。ここは簡単にyamlファイルだけ載せておきます。

ポイントとしては、Dockerfileのdev用を用意しておくことでしょうか。Rails envの設定などをproductionとわけたり、productionではサイズの小さいimageを使いたいなどの要件に対応できるようにしておきます。

2. masterにpushした時のみCircleCIでECRにDockerイメージを構築

次に、CircleCIの設定をしていきましょう。本当はTerraformから書いたほうが順序的には良いと思うのですが、Terraformの方は長くなるので、後にまわします。CircleCIをRailsに組み込むあたりは、それほど難しくないと思うので割愛します。CircleCI公式サイトの手順に沿って行ってください。

早速ですが、先に .circleci/config.yml の中身を載せます。その後、重要なポイントを解説していきます。

いくつかポイントになりそうな部分を見ていきましょう。

テスト

このファイルでもいくつかポイントが有るのですが、まず、CircleCI上でテストをまわすところです。CIでテストを回す方法は色々ありますが、ここではローカルでテストを回すのと同じように、docker-composeで構築してテストをさせています。jobs -> testの部分になります。deploy前にテストを回すようにしています。

条件によってデプロイする

このファイルでは、masterブランチにマージされたときにstagingにデプロイして、tagが打たれたときにproductionにデプロイするようにしています。この設定は filters で行えます。

ECRにイメージを作る

まずこの話に入る前に、CircleCIにはorbsという仕組みがあり、それを使えばある程度処理が楽になることを知っておきましょう。これにより、ECRへのpushは非常に簡単になっています。workflowの aws-ecr/build-and-push-image がその設定になります。この際にいくつかパラメータが必須になっているので、設定しています。ひとつポイントとして、 extra-build-args というパラメータを設定でき、ここで渡したものは、Rails側でENVとして受け取ることができます。こうすることで環境変数の管理は楽になります。また、 ${CIRCLE_SHA1} は、CircleCI側で乱数を生成して設定してくれるもので、自分で設定する必要はありません。ちなみに、ENV(環境変数)はそもそもどこに設定するのかという話ですが、CircleCIに環境変数を設定する箇所があります。(もしわかりにくければ画像キャプチャを載せることを検討します)

Terraformでデプロイする

Terraformの中身をまだ見ていないので細かいところはわからないと思うのですが、流れとしては、

  • git cloneでterraformのrepositoryを落としてくる(ここではterraform_ecs)
  • terraform getでmoduleを使用可能にする
  • terraform init
  • terraform applyで実際にデプロイする

という感じです。applyの部分にいくつかポイントがあるのですが、まず、 -input=false -auto-approve これでterraformが対話的に聞いてくるのをdisabledしています。その代わりに必要な変数などを -var-var-file で補っています。-varはgit管理したくない機密情報を、-var-fileは環境ごとに違う値を設定したいだけで特にpublicでも問題ないものをファイルに設定して読み込むようにしています。

3. 4. CircleCIからterraformで本番環境のインフラをAWSにECSで整えデプロイ完了

さて、いよいよ本題です。CircleCIでterraform applyするところは先程書いたので、terraformの中身を見ていきましょう。
※ terraformのインストールや、terraform自体の詳しい使い方はご自身で調べてください

Repositoryはこちら github.com

terraformのディレクトリ構成のベストプラクティスはいくつかあるようですが、今回は下記のように配置しています。modulesで使いまわしできるものを用意し、staging, productionなどのように環境ごとにmainファイルを配置します。

.
├── modules
│   ├── ecs
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── policies
│   │   │   ├── ecs-autoscale-role-policy.json
│   │   │   ├── ecs-autoscale-role.json
│   │   │   ├── ecs-execution-role-policy.json
│   │   │   └── ecs-task-execution-role.json
│   │   ├── tasks
│   │   │   ├── db_create_task_definition.json
│   │   │   ├── db_migrate_task_definition.json
│   │   │   └── web_task_definition.json
│   │   └── variables.tf
│   ├── networking
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   └── rds
│       ├── main.tf
│       ├── output.tf
│       └── variables.tf
├── production
│   └── production.tf
└── staging
    ├── _main.tf
    ├── route53.tf
    ├── staging_key
    ├── staging_key.pub
    ├── terraform.tfvars
    └── variables.tf

modules/ecs

まずはmodulesのecsの中から見ていきましょう。こちらになります。
https://github.com/mr-myself/terraform_ecs/blob/master/modules/ecs/main.tf

ECS自体の説明は端折りますが、web appが動くものをserviceとして管理し、migrationなどの単発で良いものはtaskとして定義します。ここでは、まず、Clusterを環境名をprefixに用意し、その中にtaskを用意していきます。細かい説明をするよりはファイルを見ていただいたほうが早いかと思いますが、ロードバランサの設定などやAutoScallingの設定などもここで行っています。

modules/networking

次にnetworkです。vpcgatewayの設定を行います。最初に書いたpublic, private subnetもここで定義します。
https://github.com/mr-myself/terraform_ecs/blob/master/modules/networking/main.tf

modules/rds

次にRDS周り。subnet, security group, dbのインスタンス定義などを行っています。MySQLの5.7として作っています。
https://github.com/mr-myself/terraform_ecs/blob/master/modules/rds/main.tf

補足として、上記の各所で出てくる、 var.xxx という記述ですが、名前の通り変数を渡しています。使いたい箇所で var.xxx のように書き、同じディレクトリ内のvariables.tfに使いたい変数を定義し、 staging/_main.tf などから値を渡すことができます。

staging/__.tf

最後にstaging環境の方を説明して終わりにします(productionもほぼ同じなので)。まずは、アクセスキーを作る必要があるので、staging_keyとしてkey-genで作っておいてください。今回はsampleなので空のファイルを配置していますが、通常はprivate keyはこんな風にさらさないようにしてください。

デフォルトの値としていくつか用意しておきたいものは、terraform.tfvarsに予め値を記載しています。また、短いのでさらっと書きますが、 route53.tfドメインの設定などを行っています。

最後に、 _main.tf です。まず、terraformはstateをどこかに保持しておく必要があり、保持することができる場所は様々あるのですが、今回はせっかくガッツリAWSを使っているので、S3に置くような設定にしています。

terraform {
  backend "s3" {
    encrypt = "true"
    region = "ap-northeast-1"
    bucket = "terraform_ecs-tfstates"
    key = "terraform.tfstate"
    acl = "bucket-owner-full-control"
  }
}

あとは、先程説明してきたmodulesをstaging環境として使えるように、変数に値を入れていっているだけです。




以上となります。さらっと書いたのですが、構築中はいろいろ模索したので、ハマリポイントを思い出したらまた追記していきたいと思います。わかりにくい点などあればコメントいただければと思います。terraformで構築するのは初めてだったのですが、一旦作ってしまうと楽ですし良いですね。