freeeの開発情報ポータルサイト

terraform stateの再設計に取り組んだ話

こんにちは!freee enabling sre teamに所属しているhamaa(濵田雄太)です。

この記事はfreee Developers Advent Calendar 2023 - Adventar 1日目です。 自分はfreeeにjoinし、おおよそ1年がたちました。この1年間で主にterraform state再設計、移行について取り組んでいました。
今回はterraform project, stateをどのような観点を元に設計するのかについて記事を書いていこうと思います!

対象読者

  • これからterraform stateを設計、再設計する人
  • terraform stateをリファクタリングを考えている人

話さないこと

  • terraform stateの基礎的な話

話の結論

  • terraform stateは以下の観点で設計することが必要と考えています。
    • ステークホルダー、設計要件、user storyなどを洗い出し設計を行う。
    • terraform stateを安全に管理していく為にproduction, staging, developmentといった環境単位でterraform stateを分割する。
    • 洗い出した要件、インフラリソースのライフサイクルなどを考慮しterraform stateをさらに分割する。
  • 上記のterraform state分割観点はあくまでもfreeeの中で考えた理想系であり、実際の組織構成やterraform stateのサイズなどによってはマッチしない可能性もある。

※自分が話す内容はベストプラクティスを提唱しているものではありません

話しに入る前にfreeeでは現在どのようにterraform project, stateを管理しているのかを説明したいと思います。

freeeにおけるterraform projectおよびterraform state管理について

terraform projectのディレクトリ構成について

freeeではマイクロサービスを実践しており、インフラを管理しているterraform projectをモノリポジトリとして管理しています。

基本的には下記のような構成になってます。

  • repositoryのtopレベルにservice名で定義したディレクトリが存在します。
    • service名のディレクトリ配下ではaws, kubernetesといったディレクトリが存在しterraform stateを管理しています。
$ tree
.
├── serviceA
│   ├── aws
│   └── kubernetes
  • awsディレクトリではaws インフラリソースを管理しています。(production, staging, integration(develop環境)のterraform codeは同一ディレクトリ )
    • albやsg、eksなどawsリソースに関連するものを記述しています。
  • kubernetesディレクトリではkubernetesに関連したリソースを管理しています。
    • kubernetes namespace, aws-auth configmap(参考)に関するものを記述しています。
serviceA
   - aws
      - proudution.tf 
      - production_cluster.tf 
      - staging.tf
      - staging_cluster.tf
      - integration.tf
      - integraion_cluster.tf
      - rds.tf 
      - etc...
  - kubernetes
      -  production_cluster.tf 
      -  staging_cluster.tf
      -  integration_cluster.tf

terraform state管理について

上記のterraform project管理の項目通りaws, kubernetesディレクトリ配下ではproduction, staging, integration(development環境)の各インフラリソースが管理されており、それらのインフラリソースは1つのterraform stateとして管理されています。

現状のterraform stateの課題

ここまで読んでいただいて気づいているかと思いますが、現状のterraform stateに関する課題は以下になります。

  • production, staging, integration環境が同じterraform stateで管理されていることで、その環境の変更が他の環境のインフラリソースに影響を与えるリスクがある。
  • インフラリソースのライフサイクルを考慮できていないため、特定のインフラリソースの変更を加え確認すると他のインフラリソースの変更差分が混入するリスクがある。

そう、安全にterraform stateが管理できない状態になっています。
またfreeeではterraform plan, applyなどをci上で管理しています。変更のあるディレクトリ配下の 全てのresourceに対してterraform initからapplyをおこなう為、ci実行時間も長くなっています。

そして我々はprouct teamがsoftware lifrecycleを自己完結できる様に各種権限委譲を進めています。
インフラ構築もその範疇であり、staging, integration環境のインフラリソースはproduct teamの開発者が行えるように権限委譲したいのですが、現在の構成ではディレクトリ単位でgithub codeowner機能を設定できず、権限委譲の妨げになっています。
これらのことから、terraform project, stateの再設計を行うことになりました。

terraform state設計時に考慮した観点

terraform project, stateの再設計を行っていくぞ!っとなりましたが、自分自身、設計に関する知識はほぼ0に近いです。
その為、SRE senior engineerの力を借りながら以下の観点を考えて設計を行なっていきました。

  • ステークホルダーの洗い出し
  • terraform state設計の要件整理
  • user storyの洗い出し

ステークホルダーの洗いだし

以前freeeではインフラの管理、構築をSREが全て行っていました。ですが、service数が増大するに連れて中央集権的にインフラリソースをSREが管理することが難しくなり、現在はproduct teamにもインフラ構築や管理を行ってもらっています。
その為、インフラ保守を行うステークホルダーとしては開発者も含まれることになります。
またfreeeのサービスの特性上、監査人がインフラリソースをどの様に操作したかが容易に分かることを考慮すべき点としてあげました。

これらのことからステークホルダーは下記になります。

  • SRE
  • 開発者
  • 監査人

terraform stateの設計の要件整理

terraform stateを設計する上での要件は以下を考えました。

  • 保守性が担保されていること。
  • 設計したterraform stateは環境単位で別れていること。
  • インフラリソースのライフサイクルが考慮されたterraform stateであること。
  • soc1(監査) に準じた設計であること。

user storyの洗い出し

洗い出したステークホルダーを元にuser stroyを考えました。

  • SRE, 開発者が安全にインフラリソースの変更が行える。
  • SRE, 開発者がインフラ構成を容易に把握することができる。
  • 監査人が現状のインフラリソース変更履歴を追いやすい。

以上の観点を元にterraform project, state設計を行いました。

実際に考案したterraform state設計内容

SRE, 開発者が安全にインフラリソースの変更を行え、監査人がインフラ変更履歴を追いやすくするべく下記の様な2つの設計案考えました。

  • 環境単位でterraform stateを分割する。
  • 環境単位で分割したterraform stateをインフラリソースのライフサイクルを考慮しコンポーネント単位でさらに細分化する。

案1. 環境単位でterraform stateを分割する

serviceディレクトリ配下は下記の様に環境名でディレクトリを分けることとしました。

root
├── serviceA
│   ├── integration
│   │   ├── aws
│   │   └── kubernetes
│   ├── production
│   │   ├── aws
│   │   └── kubernetes
│   └── staging
│       ├── aws
│       └── kubernetes
├── serviceB
│   ├── integration
│   │   ├── aws
│   │   ├── kubernetes
│   ├── production
│   │   ├── aws
│   │   ├── kubernetes
│   └── staging
│       ├── aws
│       ├── kubernetes

案2. 環境単位で分割したterraform stateをインフラリソースのライフサイクルを考慮しコンポーネント単位でさらに細分化する

ここで「インフラリソースのライフサイクル?」となるかと思いますので補足を行います。
RDSなどのdatastoreに関連するインフラリソースはALB, auto scaling groupといったweb層のコンポーネントに比べると変更がほぼなく、インフラリソースのライフサイクルが違うと言えます。
このような観点を元にインフラリソースをライフサイクルに合わせて下記の様にコンポーネント単位でディレクトリを分けるように考えました。

.
├── production
 │   ├── application
 │   │   ├── provider.tf
 │   ├── datastore
 │   │   ├── provider.tf
 │   ├── global-routing
 │   │   ├── provider.tf
 │   ├── network
 │   │   ├── provider.tf
└── staging
    ├── application
    ├── ...  

このようなディレクトリ構成でterraform stateを管理することで他の環境、ライフサイクルの違うインフラリソースの影響を受けることなくSRE、 開発者ともに安全にインフラリソース(terraform state)を管理することができると考えました。
また監査人もディレクトリ単位で変更を追うことができると考えました。
環境、インフラコンポーネント単位でterraform stateが分割されることにより、ci実行時間改善も見込めると感じました。

設計の比較検討

上記で考えた2つの設計はどちらもterraform stateの独立性を高め、保守性を高めることが可能かと考えています。 しかしながら、案2の設計には下記のような課題感があると考えています。

  • コンポーネント単位で分割したterraform stateの依存関係
  • どこにどのインフラリソースを定義するかという指針が明確でない

コンポーネント単位で分割したterraform stateの依存関係

上記の様にコンポーネント単位でterraform stateを分割すると、作成の順序(依存関係)が発生します。
例えばインフラコンポーネント単位でディレクトリを分けた場合、インフラリソースの作成順序の方向は下記になるかと思います。

network -> {applicaiton, datastore} -> global-routing

この依存の方向(流れ)を一方方向にしないとterraform planやapplyが通らないといった問題を生じる可能性があります。
この依存関係を解決してくれるツールとしてterragruntというものが存在します。
terragruntはterraformのラッパーツールでterraform stateの依存関係を解決しつつterraform plan, applyを行うことができます。
ただし、freeeではterraformをciで管理しています。この仕組みをciに組み込む必要があります。

どこにどのインフラリソースを定義するかという指針が明確でない

コンポーネント単位で分割したディレクトリにはどんなリソースが入るのか?
どこで定義(記述する)するのがいいのか?
といったインフラリソース定義の指針を明確に作成できていませんでした。
作成者によってはインフラリソースを定義するディレクトリが変わってしまうリスクもあります。これでは開発者はインフラ作成に迷いが生まれ扱いにくくなります。


以上のことから、案2のコンポーネント単位でディレクトリを分けterraform state管理を行うことは準備不足感があり、案1の設計を採用することとなりました。案2の設計を採用するにはterraform stateを標準的に扱うための指針や仕組み作りを考える必要があると考えています。

どうやってterraform stateを分割していくのか?

環境単位でディレクトリを分けterraform state分割を本格的に行う為に、工数を割り出す必要がありました。その為、terraform state分割手順書の作成と分割対象を選定し実際に分割を行い手順書の工数に不足ないかを確認しました。

洗い出した作業内容を確認しenabling team内で自動化できる部分の選定を行いterraform state分割を効率化する為のtoolをhcleditというossを活用して作成しています。
この移行作業を効率的かつ安全に作業ができることを狙いとしてます。(今後のenabling teamの取り組み発表に期待!)

環境単位でterraform stateを分割した後の展望

環境単位でのterraform state分割が完了した後、product teamへインフラ構築権限委譲を行う為に、github codeowner機能を用いて権限委譲を進めて行きたいと考えています。
またそれと並行して、policy as codeを用いたガードレールの整備も行っていきたいと思います。

終わりに

terraform state設計を行うにはステークホルダー、要件などの洗い出しを行い、安全にterraform stateを扱う為の設計を考える必要があることを学びました。
一方で設計したものが、現時点で実現可能かそれを達成するには何が必要かを洗い出し、設計したものが現状にマッチするのかを考慮し実施する必要があることを学びました。

明日のfreee Developers Advent Calender 2023は、同じenabling sre teamのchoreさんにバトンタッチしたいと思います!!