AWSコンソールを5年間ぽちぽちしてましたが、やっとTerraformに完全移行しました。-マイクロサービスインフラの移行とその運用について-

Ryo Kubota
FiNC Tech Blog
Published in
15 min readApr 28, 2020

FiNC Technologies(以下FiNC)で SREグループのマネージャーをしている@ryok6tです。

今回は Infrastructure as Code(IaC)のお話です。

今や IaC、すなわちインフラの構成管理はコードで、というのが当たり前になっています。

しかし、以前のFiNC では全くインフラがコード管理されておらず、約50のマイクロサービスが持つ数千のAWSリソースが全て手動で作成された状態でした。

これにより生じた多くの問題に対処するため、Terraformを導入し、現在ではマイクロサービスごとに独立したstateを持った状態でコード管理されています。

この記事では、

  • 既にある大量のインフラを、後から Terraform 管理下に入れた方法
  • マイクロサービス x Terraform の運用
  • Terraform の CI の運用

などについて書いていきます。

*今回は「Terraform ってなに?」については書きません。

Terraform導入前の状況

FiNCでは、約50のマイクロサービスをAWS上に展開しています。

Lambda などの一部は Terraform 管理されているものもありましたが、基本的にはほぼ全てが AWS の管理画面から「ポチポチで」作られた状態でした。

そして、その resource の数はマイクロサービスに関わるものだけで約2000ほどになっていました。

Terraform導入前の問題点

どんな問題があったかと言うと、一般的に IaC が無いことで生じるとされている問題が全てありました、という感じです。

この記事を読んでいる方には詳しく説明する必要はないと思うので、以下で簡単に挙げるだけとします。

  • 繰り返し作業(トイル)の増加
  • インフラ設定の属人化
  • インフラのレビューが困難
  • SREがボトルネックに

Terraform管理の構想

これらの問題を解決するために、Terraform の導入を開始しました。

ここでの主な論点として

  • レポジトリはどの単位で切るのか?
  • どのような単位でstateを分割するのか?
  • staging/production 環境をどう管理するのか?

などが挙げられます。

FiNCでは以下のように決定しました。

レポジトリは中央集権

現段階では、1つの大きなレポジトリが全てのマイクロサービスの Terraform コードを管理しています。

これにより、以下が可能になります。

  • 単一のレポジトリで各サービスの設定を横断的に把握
  • CIの設定を共有

各マイクロサービスは分散したstateで管理

1つの state で中央管理する場合と、サービスごとに分散管理する場合がありますが、それぞれのメリット/デメリットを挙げてみます。

【中央管理の場合】

メリット

  • 編集するファイルが少なくて済む。 管理者が少ない場合は運用しやすい
  • CIがシンプルになる
  • 他のリソースの参照が楽
  • 既存のリソースの import が楽(terraforming, terraformer などのツールをそのまま使える)

デメリット

  • あるサービスへの変更が他に影響を与え得る
  • 権限の委譲がしにくい
  • state にあるリソース数が膨大になるので、CIが長くなる

【分散管理の場合】

メリット

  • あるサービスの変更の影響範囲はそのサービスに閉じている
  • 権限を移譲しやすい
  • 変更のあるサービスのみをCIの対象にすれば、時間短縮可能

デメリット

  • ファイル数が増えるのでメンテナンスが大変(特に全サービスでの一括変更など)
  • CIが複雑になる
  • 他のリソースの参照がしにくい(remote state などが必要)
  • 既存リソースの import が大変(既存ツールではサービスごとに分割するところまではできない)

このようなメリット/デメリットを踏まえ、FiNCではサービスごとの分散管理をしています。

これは「将来的にマイクロサービスのインフラ構成をその開発チームに完全に移譲しきる」という構想を持っているためです。

具体的なディレクトリ構成は以下です。

.
└── microservices
├── service_a
│ ├── production
│ │ ├── foo.tf
│ │ └── bar.tf
│ └── staging
└── service_b
├── production
└── staging
(...以下略)

このように、マイクロサービスごとに独立したディレクトリと、独立したstateを持たせています。

ステージングと本番環境は別のコードで

環境を別のコードで管理するのは Infrastructure as Code ではアンチパターンとされています。

Terraform Workspaces などを使えば、単一のコードベースで複数環境に対応することもできます。

しかし、以下のことを考慮し、ディレクトリを分割して別のコードで管理することを決定しました。

  • 今回は既存の AWS の環境を Terraform 管理に移行する必要があり、既にステージングと本番で微妙な設定の差が存在していたこと
  • Terraform Workspaces の公式に、「ステージングと本番、のようなユースケースには適さない」とあること

In particular, organizations commonly want to create a strong separation between multiple deployments of the same infrastructure serving different development stages (e.g. staging vs. production) or different internal teams. In this case, the backend used for each deployment often belongs to that deployment, with different credentials and access controls. Named workspaces are not a suitable isolation mechanism for this scenario.

膨大なリソースを terraform import

途中から Terraform 管理を始めるためには、既存のインフラを Terraform のコードに落とし込む必要があります。

勿論、既存のものは放置して、新規作成のみ Terraform で管理する、ということも可能です。

しかし、少なくともマイクロサービスのリソースは全て Terraform で参照できるようにしたかったため、膨大な量のリソースを全て import することにしました。

この作業はうまく自動化することで効率化しました。

既存のリソースからコードを生成

まず最初のステップです。

これには terraforming と言うツールを使用しました。(当時は terraformer を知らなかったため。)

対象のリソースを全て(例えば AWS SNS topic なら、全ての topic を)単一のファイルにコードとして書き出すことができます。

しかし今回は、マイクロサービス毎に分散管理することがゴールなので、この巨大なファイルをサービスごとに分割していく必要があります。

マイクロサービス毎に分割するシェルスクリプトを作成

単一の .tfファイルからマイクロサービス毎にファイルを分割するために、 grep-terraformというスクリプトを作成しました。

grep-terraform

これは、標準入力、あるいは引数で指定したファイルの内容から、特定のキーワードを含む resource block のみを標準出力します。

例えば、以下のような、ecs.tf というファイルがあったとします。

resource "aws_ecs_service" "hoge_staging" {
name = "hoge_staging"
}
resource "aws_ecs_service" "fuga_staging" {
name = "fuga_staging"
}
resource "aws_ecs_service" "hoge_production" {
name = "hoge_production"
}
resource "aws_ecs_service" "fuga_production" {
name = "fuga_production"
}

これに対し、

$ grep-terraform hoge ecs.tf

とすると、以下を出力します。

resource "aws_ecs_service" "hoge_staging" {
name = "hoge_staging"
}
resource "aws_ecs_service" "hoge_production" {
name = "hoge_production"
}

更に(例えば staging だけに)絞り込みたければ、

$ grep-terraform hoge ecs.tf | grep-terraform staging

などとすることができます。

幸い、リソースの命名規則がしっかりしていたため、ほとんどをこれで自動化することができました。(一部例外もありましたが、そこは手動で対応しました。)

terraform import で state に反映

ここまでで、マイクロサービスごとにコードを分割することができました。

最後に、これを state に import すれば作業完了です。

これもほぼ全て自動化しました。

import に必要な情報は多くの場合 .tf ファイルに書かれているため、それを sed などのコマンドで取得することができます。

arn が必要になる場合もありますが、AWS CLI と jq などを組み合わせれば大抵取得することができます。

こういった自動化により、数千のリソースをマイクロサービスごとに分割された管理に落とし込む、ということを効率良く行うことができました。

*ここで紹介した方法は一例です。他にも state を生成してから terraform state mv を使う、などの方法も考えられるでしょう。

CIを使った plan, apply

FiNC では CircleCI 上で terraform plan/applyを実行しています。

そしてその結果を、tfnotify という OSS を使用し、GitHub や Slack に送信しています。

terraform plan

変更を加えたブランチを GitHub に push すると、自動で terraform plan が実行され、その結果がプルリクエストのコメントとして追加されます。

これにより、毎回 CircleCI を見に行かなくても、プルリクエスト上でどんな変更が加わるかを確認することができます。

プルリクエストに自動で追加されるコメント

terraform apply

プルリクエストが master ブランチにマージされると、以下が行われます。

  • 改めて terraform plan
  • Bot が Slack に plan 結果を通知 & Apply前の最終確認を要求
  • 問題がなければterraform apply

master の場合、plan 結果は Slack の専用のチャンネルに流れます。

それが終わると、「plan が終わったので、結果が問題なければ apply を承認してください」という内容のメッセージが bot から送られてきます。

plan 結果を確認し、問題がなければリンクから CircleCI を開き、”hold” のボタンをクリックすると apply されます。

これは、CircleCI の approval という job type を使うことで実現しています。

その他の工夫

その他にも、CIを運用する上で幾つかの工夫をしています。

  1. 変更のあったディレクトリのみで plan, apply を実行する

plan/apply 対象のリソース数に比例して、実行時間は増えてしまいます。

実行時間短縮のため、変更のあったディレクトリを検知する簡単なシェルスクリプトを書きました。

changed_dirs

全体としては大量のリソースがありますが、1つのマイクロサービスが持つリソース数は限られているため、かなり素早く CI を回すことができます。(ほとんどの plan は10~30秒程度)

2. master から遅れているブランチは plan の前に弾く

master よりも古くなっているブランチは、plan を回しても正しい結果が得られないため、CI でエラーにしています。

これには、ブランチに対して master から遅れているかを判定するスクリプトを使っています。

3. プルリクエストのコメントを一掃するスクリプトを作成

上記のように、CI で plan が実行されると、その結果がプルリクエストのコメントに追加されます。

これは便利なのですが、一度 push した後に修正して再 push…ということを繰り返しているうちに、古いコメントが大量に溜まってしまうということがありました。(特に変更するディレクトリが多い場合)

こういった問題を解決するため、対象の bot からのコメントを全て消すスクリプトを作成しました。

Terraform化して良かったこと

Terraform導入により、上で挙げていた問題点はほぼ全て解決されました。

同じ設定を再現したければコピペするだけですし、開発者はSREに依頼して作業を待たなくても、プルリクエストを出してレビューを依頼するだけで良くなりました。

また、その他にも様々な恩恵があったので幾つか紹介します。

マイクロサービス作成の自動化

以前は、マイクロサービスのステージング/本番環境の構築は、作成すべきリソースや設定すべき項目が多く、かなり骨の折れる作業でした。(”環境構築職人”でも最低3時間はかかっていました。)

それがコード化により、今では対話式のコマンドを1つ実行するだけで最低限必要なリソースは全て作成することができるようになりました。

ドキュメンテーションの促進

以前はマイクロサービスに関するドキュメントが無い(不足している)ことも多い状態でした。

例えばマイクロサービス同士の依存関係でも「なんのための依存なのか?まだ使っているのか?」などが分からない(あるいは知ってそうな人を探さないと分からない)ということがしばしばありました。

Terraform を管理するレポジトリには、プルリクエストのテンプレートにチェックリストがあり、関連するドキュメントを貼ることが必須になっています。

これにより、ドキュメンテーションが以前より活発に行われるようになり、またそれをきっかけにアーキテクチャレビューなどを行うような文化もできつつあります。

AWS の tag の活用

AWS には tag という機能があり、ほとんどのリソースに任意のキーと値を持つ tag をつけることができます。

上で述べたプルリクエストのチェックリストでは、最低限以下の tag を付ることが必須化されています。

  • Author(リソースの作成者)
  • Service(どのマイクロサービスに属するか)
  • Environment

これにより、

  • 「これについて聞きたいけど、誰に聞けばいいか分からない」
  • 「これどのサービスで使ってるのか分からない」

といった問題が改善されました。

リソースの削除漏れの改善

使わなくなったリソースは当然消す必要がありますが、手動だと削除漏れが発生しがちです。

しかし、コード管理により変更履歴が辿れるようになったため、ある時点で追加したリソースを漏れなく削除することができるようになりました。

今後やっていきたいこと

今後は以下の大きく2つのことをやっていきたいと考えています。

マイクロサービスのインフラの更なる移譲

現在は全てのプルリクエストをSREがレビューしているため(以前より圧倒的に負荷は減りましたが)、今後SREがボトルネックになる可能性があります。

そのため、マイクロサービスのディレクトリに関してはその開発チームのリーダーに権限を移譲したいと考えています。(GitHub のコードオーナーなどの機能が使えます。)

そのために、社内での更なる Terraform の認知向上や、レビュワーガイドラインの策定などを行っていきます。

監視のコード化

FiNCでは主に Datadog を使って監視を行っているのですが、幾つかの問題があります。

  • サービスごとに設定がまばら(例: あるサービスで監視が漏れている)
  • 1つの監視が全てのサービスを見ていて、サービス個別の事情に対処できない(例: 閾値の違い)
  • サービスが増えるたびに手動で設定が必要

こういった問題に対処するため、Datadog も一部は Terraform 化し、各マイクロサービスごとに分散管理していきたいと考えています。

また、所謂”プロダクションレディ”に最低限必要となる共通の監視やダッシュボードなどを(ほぼ)自動的に作成するような仕組みも、コード化により作っていけると考えています。

まとめ

今回は Terraform によるインフラのコード管理や、その運用、そしてその中で得られたメリットについて書いていきました。

コード管理によるメリットは多くある一方、何でもかんでも Terraform でガチガチに管理しようとすると、副作用が生じる場合もあるでしょう。

今後もそういったバランスを見ながら、Terraform とは程よい距離感を保って付き合っていきたいと思います。

--

--