Terraformで社内のインフラ環境(AWS)をコード化(IaC化)する
( tech )会社に入って、インフラの非効率さと管理の煩雑さに課題を感じていた。リソースや優先順位の制約があったが、改善の必要性を強く感じ、実務経験が浅い中でもTerraformを用いてIaC化に取り組むことを決意した。この取り組みを通じて得られた知見と成果を共有する。
現状
2022年後半時点
一つのAWSアカウントに、すべての環境がセットアップされている状態だった。しかし、テスト段階のものやすでに利用されていないものが点在している状況。現状、どのサービスや設定が必須かどうかを判断する人がいないため、無駄なリソースが散在している。また、ビジネスの状況的に、メンバーがこの問題の改善にコミットすることが難しく、優先順位が低いとされている。
さらに、現在のプロダクトの構成は、少し過剰であったり、適切でない部分があるため、見直しが必要だった。さらには、あるミドルウェアを導入予定でもあり、検証するにも安心安全にそして迅速にできるかは不安ポイントであった。
課題
このままにしておくと、以下の課題が発生する可能性が高まる。
- 時間・費用面でのコスト増加
- 調査に時間がかかる。
- 不要なリソースを放置しているため、無駄な費用がかかる。
- 運用負荷の増加
- 構成が複雑化・煩雑化しているため、シンプルに分離しておく必要がある。
- 環境を別々にすることで解消する。
- 構成が複雑化・煩雑化しているため、シンプルに分離しておく必要がある。
- 迅速な意思決定の阻害
- アグレッシブにインフラの検証や新しい環境の構築が行えない。
- 何を削除し、変更すればよいかが分からず、理想の環境にするのが難しい。
方針
何を削除し、何を残すべきか、またどれを改善すべきかを判断するためには、現状を正確に把握する必要がある。そのために、以下の手順で進める。
- まず、現在の全ての環境設定をコード化して管理できる状態にする。
- 現在、すべての環境が入り混ざっているproduction環境全体を、Infrastructure as Code (IaC)として管理。
- 次に、新しいAWSアカウントを構築し、現状のプロダクトに必要な環境だけを移行する。
- 今ある環境をProductionにする
- Staging構築
- Sandbox構築
- Productionにある不要な環境を削除していく。
- Productionにあるstaging環境一式を削除。
- Productionにあるsandbox環境一式を削除。
これにより、以下の利点が得られる。
- 各環境で不要な設定かどうかをチェックし、安心して削除が行える。
- 万が一間違えても、terraformで簡単にリカバリーが可能。
- 新しいサービスを導入する際も、sandboxで何度でも環境を再構築でき、安心して本番に導入できる。
このようにして、メンバー全員が安心・安全・正確に運用できる状況を作り上げ、最終的に開発スピードを落とすことなくプロダクトを成長させることにコミットできる。
そのためにも、まずは、環境をIaC化するところから始めた。
やったこと
ざっくり把握
基本中の基本ではあるがそれに変わるものがなかったのである程度把握したら、どういうサービスをどのように利用しているのかをドキュメントにまとめたり、アーキテクチャ図にしたりして、徐々に深掘りできる状態にした。
サポート状態を構築する
現状のメンバーで誰も把握できていないサービスを利用していたり、設定があったし、僕も完全に運用できる知見が少ないものもあったので以下の状況を構築した
- 徹底的にAWSのテクニカルサポートの人に聞く
- コンサルしていただくメンバーを業務委託として迎え入れる
- SREチームを発足して会社のインフラを把握し開発サイドに課題を取り込めるようにしたり、経営サイドに施策実行の提案など行える状態を作る。
一括でできないかの調査
小さい構成ながら4、5年蔑ろにしてきたところはあるのでそれなりに設定はしてある状態。 一つずつ設定をみて一個ずつimportしていくのは途方もなく時間がかかる。なるべくなら自動化できるところはしていきたい。一気にimportしていきたいところだった。
代替案
https://github.com/GoogleCloudPlatform/terraformer
これを使うとしてみた。 これは指定したAWSアカウント、サービス別にコードとして一括ですべてコードにおとしこめるところ。 懸念事項としては落とし込んだ時にtfstateも合わせて作成されるしようになっているところ。 現状、リモートでtfstateを管理しているので、そちらに反映していかなければならない。 ここは何かしらの対応が必要だ。
手順
- terraformerでコードに落とし込む
- 落とし込まれたコードからterraformのimportブロックを作成する。
terraform import
を実行して実際に反映させる。
これでいけそうだ。
コード化
ディレクトリ構成
基本構成としては
- AWSアカウント別にまとめる
- 環境別にまとめる
- moduleはロール別に共通化を行う。
.
├── Gemfile
├── README.markdown
├── bin
│ ├── generate-import-cmd.rb
│ └── lib
├── piazza
│ ├── files
│ │ ├── iam-policy-*.json
│ │ └── iam-policy-*.json.tpl
│ ├── modules
│ │ ├── app
│ │ ├── app-db
│ │ ├── constants
│ │ ├── bastion
│ │ └── variables.tf
│ ├── production
│ │ ├── app
│ │ │ └── app-db
│ │ ├── bastion
│ │ ├── *.tf
│ │ ├── **no-need-*.tf**
│ │ └── variables.tf
│ ├── sandbox
│ └── staging
├── piazza-life
│ ├── modules
│ │ └── constants
│ └── production
│ └── *.tf
├── piazza-payment-production
└── terraform
├── README.md
└── boilerplate
├── backend.tf
├── main.tf
├── modules
├── provider.tf
└── variables.tf
既存の設定を取り込む
terraformerで出力されたものを解析してimportコマンドとして落とし込む
terraformer import aws -r route53 --regions ap-northeast-1 --profile="" --path-pattern route53
これだけだとコードには落とし込んでくれて、便利ではあるものの、これからimport処理をしてリモートにあるtfstateと関連付けを行う必要があった。
# import
terraform import aws_subnet.hoge subnet-aaaaaaaa
terraform import aws_subnet.hoge subnet-aaaaaaaa
terraform import aws_subnet.hoge subnet-aaaaaaaa
.
.
.
これから実際に反映していくことにするとimportコマンドを大量に売っていくことになる。書いていくのが非常に面倒くさい。 importするとものによってはかなりの量になってくるのでここは自分でスクリプトを作成してimportブロックを自動で生成するコマンドを生成して一気に関連付けできるようにして対応した。
terraformerが生成したリソース名には一定の規則があり、それがIDとして抽出できるので、それが分かれば生成コマンドは簡単に作れます。
$ ruby bin/generate-import-cmd.rb production/subnet/subnet.tf > import.tf
$ cat import.tf
# importブロックが生成される
import {
id = "subnet-xxxxxxxxxxxxxxxxx"
to = aws_subnet.tfer--subnet-xxxxxxxxxxxxxxxxx
}
import {
id = "subnet-xxxxxxxxxxxxxxxxx"
to = aws_subnet.tfer--subnet-xxxxxxxxxxxxxxxxx
}
import {
id = "subnet-xxxxxxxxxxxxxxxxx"
to = aws_subnet.tfer--subnet-xxxxxxxxxxxxxxxxx
}
.
.
.
※本当はterraform import
でコマンドを作成していたが途中からimportブロックが利用できるようになったのでそちらを利用するようにした。
そもそもterraformerは古いというか開発が去年から止まっていて更新が遅く、aws,terraform公式に追随できていないので出力されたコードが最新とは限らない。少なくとも公式で採用されているimportブロックの方が最新だし更新もある程度頻繁におこなわれている。
terraformerでの注意点、、、
terraformerで吐き出されたコードで以下のことに留意していく必要があった。
- リソース名がIDを利用した数字と英語の乱数になっているので可読性にかける
- ユーザ一覧の設定を一個づつ吐き出してしまう
- module化は考慮されていない
したがって、以下の項目を設定する必要があった。
- 可読性のある名前に変更する
- ループで回せるものは配列にして設定させる
- module化を行い、共通化する
- この時の方針としてはrole別に共通化
ここは把握しやすくそして変更しやすい状況にしておくためにリファクタリングを行っている。
階層的に情報を参照させる
data "terraform_remote_state" "state" {
backend = "s3"
config = {
bucket = "piazza-tfstate"
key = "piazza/production_v1.0.0.tfstate"
region = "ap-northeast-1"
}
}
階層的には全体的な設定を上部階層にあってその下には各ロールの設定が入ったディレクトリが存在する。しかし、terraformの特性上そのディレクトリ内で設定を完結する必要があります。現実問題として各ロールでも上の階層で設定した内容を参照したいことが多々あります。そんなときには上部のような宣言をすれば、他のtfstateを参照することが可能になります。結果上の階層の情報も拾うことができるようになります。使い回しができるようになって便利!
# 参照先
zone_id = data.terraform_remote_state.state.outputs.route53-zone-main.id
参照元は参照できるようにするために対象をoutputで宣言しないといけません
# 参照元
output "route53-zone-main" {
value = aws_route53_zone.route53-zone-main
}
躓きポイント
ポイント1
Network Interfaceは設定によって定期的に変わるものがあるのでなるべく管理しないようにした。設定する順番がことなるとエラーになる時があるので depends_on
を利用する
ポイント2
パスワードは管理したくないので無視するようにした。
lifecycle {
ignore_changes = [value]
}
秘匿情報をAWS側で管理している場合があるのでそれはGitHubで管理せず、1Passwordで管理して使う時に転換するようにしている。
不要な機能は削除
最後に、明らかに不要なものは削除する。
会社で不要になっているサービスをある程度リストアップできているもがある。もちろんまだ調査しないといけないものもあるが、、、あとは削除していくのみだ。また、Organizationレベルで権限管理や役割別に分けて管理していたAWSアカウントも、利用されていないものは削除した。ただ、一部影響範囲があるものもあったため、ドキュメントを読み、わからないところはAWSのテクニカルサポートに積極的に問い合わせて確認することにした。
まとめ
今回のプロジェクトでは、現状の課題を解決するために、環境全体をコード化(IaC化)し、効率的かつ確実にインフラを管理できる体制を整えた。この取り組みによって、不要なリソースを削除し、環境の複雑化を防ぎ、迅速な意思決定が可能となる基盤を築けと思う。具体的なステップとしては、以下のような手順を踏んでいる。
- 現状の把握とIaC化
- 全ての環境をTerraformによりコード化し、現状の構成を完全に把握。
- その後、必要な環境のみを新しいAWSアカウントに移行。
- サポート体制の構築
- AWSのテクニカルサポートや外部コンサルタントの活用、SREチームの発足などにより、インフラの全体的な把握と改善を推進。
- 不要な機能の削除
- AWS Control Towerや不要なサービスを順次削除し、運用負荷を軽減。
- コードのリファクタリング
- 可読性の向上やモジュール化により、管理しやすいコードベースを構築。
- Terraformerを利用してリソースの自動インポートを行い、効率化を図った。
これらのステップにより、現状の課題を解決し、環境の管理をより効率的かつ安全に行えるようにした。今後はこの基盤を活用し、安心・安全にメンバー全員が運用できる体制を整え、開発スピードを維持しながらプロダクトを成長させていければとおもいます。
とはいえ、まだまだスタート地点にたった印象です。さらに改善してエンジニアがプロダクトによりコミットできる状況を作っていければと思っています。