BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

Terraform導入への第一歩

f:id:yk4o4:20201202181850p:plain

この記事はBASE Advent Calendar 2020の3日目の記事です。

devblog.thebase.in

BASE株式会社 SRE Groupの相原です。

BASEのインフラはAWS上に構築しておりいくつかのツールを使って構成管理していますが、主にEC2のサーバ設定ツールとして利用しているのが現状で、構成管理できていないAWSリソースもちらほらあります。

そこでまずはSRE Groupで使っている社内ツールや、直接サービス影響のないものをTerraformで構成管理をしてみて、ある程度運用が固まってきたら主サービスの管理もそちらに寄せていこうという方向で進んでいます。

Terraform導入にあたり最も悩んだのがtfstateの分け方とディレクトリ構成だったので、そこをメインに紹介できればと思います。

謝辞

以下の書籍と記事を非常に参考にさせていただきました。ありがとうございます。

前提として

最初から全てのAWSリソースをTerraformで構成管理することは諦める

AWS上ではTerraform以外ですでに構成管理されているリソースや、そもそも管理されていないものも存在しています。それらを一挙にまとめてTerraformへ移行するのは難しいと感じたので

  • まずは社内ツールなどサービスに直接影響のないものから小さくはじめる
  • 運用の形が定まってきたら徐々にTerraformへ移行していく

という前提で進めています。

tfstateの分け方とディレクトリ構成

ディレクトリ構成は以下となります。

.
├── prd
│   ├── projectA
│   │   ├── terraform.tf
│   │   ├── main.tf
│   │   └── variables.tf
│   └── projectB
│       ├── terraform.tf
│       ├── main.tf
│       └── variables.tf
├── stg
│   ├── projectA
│   │   ├── terraform.tf
│   │   ├── main.tf
│   │   └── variables.tf
│   └── projectB
│       ├── terraform.tf
│       ├── main.tf
│       └── variables.tf
├── dev
...
└── modules
    ├── moduleA
    │   ├── README.md
    │   ├── main.tf
    │   ├── outputs.tf
    │   └── variables.tf
    ├── moduleB
    │   ├── README.md
    │   ├── main.tf
    │   ├── outputs.tf
    │   └── variables.tf
    ...

方針としては以下になります。

1. 環境ごとにディレクトリを分ける

BASEでは多少なりとも環境間での差分があります。workspaceの利用も検討しましたが、その場合差分を吸収するためのロジックを書く必要があり、かえって分かりにくくなりそうでした。そのためディレクトリで環境を分けるようにしました。

2. tfstateは環境ごとではなくprojectごとにもつ

projectという単位は「なんらかの意味を持ったリソース群」といった意味合いで便宜上使っています。

前提として社内ツールなどサービス影響の少ないものから小さく始めていきたいというのがあります。環境単位でtfstateを持ってしまうと、影響範囲が大きくなることもありこの前提から外れてしまいます。そのためtfstateはprojectごとに持つようにしました。

3. moduleを利用する

resource はmoduleに書いて、projectのtfファイルから呼び出す形をとっています。これにより環境の複製はmoduleに渡す変数の値を変更するだけで良くなります。

ただこの場合、どこまでmoduleに汎用性を持たせるか難しいところではあります。また複数のprojectから同一moduleを呼び出している場合、module変更による影響範囲が大きくなってしまいます。この辺りは特に運用しながら改善していくポイントかなと考えています。

その他取り入れたこと

1. Terragruntの導入

TerragruntというTerraformのラッパーツールを導入しました。これはbackend定義周りのコードをDRYに保つためです。

というのもtfstateを環境単位ではなくproject単位で持たせるので、backendの定義もproject単位で書く必要があります。その場合にproject毎でそれらを書くのが面倒なのと、設定ミスが起きることを懸念したためです。

Terragruntの導入にあたっては、以下記事を大変参考にさせていただきました。
TerragruntでTerraformのbackend周りのコードをDRYにする | Developers.IO

以下は、ほぼ記事の内容通りに実践したものになりますが、具体的なディレクトリ構成とファイルの内容です。

.
├── prd
│   ├── projectA
│   │   ├── terragrunt.hcl
│   │   ├── terraform.tf
│   │   ├── main.tf
│   │   └── variables.tf
│   ├── projectB
│   │   ├── terragrunt.hcl
│   │   ├── terraform.tf
│   │   ├── main.tf
│   │   └── variables.tf
│   └── terragrunt.hcl
├── stg
│   ├── projectA
│   │   ├── terragrunt.hcl
│   │   ├── terraform.tf
│   │   ├── main.tf
│   │   └── variables.tf
│   ├── projectB
│   │   ├── terragrunt.hcl
│   │   ├── terraform.tf
│   │   ├── main.tf
│   │   └── variables.tf
│   └── terragrunt.hcl
├── dev
...

各環境、projectごとにterragrunt.hclが追加されています。 このファイルにTerragruntの設定を書いていきます。

環境ごとのterragrunt.hcl

{env}/terragrunt.hcl

remote_state {
  backend = "s3"
  config = {
    bucket  = "test-aihara-terraform-tfstate"
    key     = "${path_relative_to_include()}.tfstate"
    region  = "ap-northeast-1"
    dynamodb_table = "test-aihara-terraform-tfstate-lock"
  }
}

path_relative_to_include()は親ディレクトリから子ディレクトリへの相対パスを返します。 https://terragrunt.gruntwork.io/docs/reference/built-in-functions/#path_relative_to_include

projectごとのterragrunt.hcl

{env}/{project}/terragrunt.hcl

include {
  path = find_in_parent_folders()
}

find_in_parent_folders()は親ディレクトリからterragrunt.hclを探してきて読み込んでくれます。 https://terragrunt.gruntwork.io/docs/reference/built-in-functions/#find_in_parent_folders

例えばこの場合だと{env}ディレクトリにあるterragrunt.hclを読み込むことになります。こうすることでprojectごとにおけるbackendの定義は、同じ内容のterragrunt.hclを用意するだけで済みます。

2. terraformで作られたことがわかるようにtagをつける

AWS上ではTerraformで構成管理されたもの/そうでないものが混在していくことになります。その時、どれがTerraformで作られたのかわかるように特定のtagをつけるようにしています。

例えば以下のように terraform: true のtagをつけるようにするなどしています。

resource "aws_security_group" "security_group" {
  name        = var.name
  description = var.description
  vpc_id      = var.vpc_id

  tags = {
    "terraform" = "true"
  }
}

おわりに

前述したとおりTerraformの導入はまだ社内ツールやサービス影響のないものに留まっております。

一旦方針決めはしたものの、tfstateの分け方やディレクトリ構成に関してまだ試行錯誤しているというのが現状です。ここに関して決まった正解はないと思いますが、運用を続けていく中で継続的に改善しより組織やサービスにフィットするようにしていければと思います。

明日はProduct Dev DS Groupの鈴木(@rmarl)さんです!お楽しみに〜