LayerX エンジニアブログ

LayerX の エンジニアブログです。

Terraform Module はいつ使う?

こんにちは、LayerX で主にインフラを担当している高江です。
この記事は LayerX Advent Calender 2021 の記事です。

先日、JAWS-UG コンテナ支部さんのイベントに登壇させていただき、LayerX インボイスの DevOps について発表いたしました。

jawsug-container.connpass.com

動画はコチラ

www.youtube.com

このセッションでは、

  • 開発スピードを落とさないようインフラ開発をボトルネックにしない
  • 本番環境で使用するリソースは IaC で管理する

ことを目的として、チーム全体でインフラを管理するために

  • IaC リポジトリのディレクトリ構成やファイルの命名規則
  • リソースのタグ付け
  • terraform plan 実行時の差分をなくす工夫
  • CI/CD の自動化
  • IaC ツールの使い分け

などの実践的な方法についてご紹介しました。

発表後に視聴者の方から

f:id:shnjtk:20211103170527p:plain

という質問をいただきましたので、今日はこれに対する回答として、Terraform Module はいつどのように使うのか?について自分なりの考えを述べたいと思います。

Terraform Module とは

公式サイトでは次のように説明されています。

A module is a container for multiple resources that are used together.

ざっくり解説すると、お互いに関連する複数のリソースをまとめて1つの単位として管理するための仕組みです。一般的なプログラミング言語におけるライブラリやパッケージのようなものをイメージしていただければよいかと思います。
使い方としては、Module として定義したファイル群をある場所で管理しておき、その Module を利用する側は module ブロック内の source でその場所を参照するという感じになります。

公式サイトに掲載されている Module の利用例:

module "consul" {
  source = "hashicorp/consul/aws"
  version = "0.0.5"

  servers = 3
}

「ある場所」と書きましたが、公式サイトに記載されているように、様々な場所を指定することが可能です。 ここでは次の3つに分類します。それぞれの名前は説明のために便宜上付けたもので、公式な名称でありませんのでご注意ください。

  • Local Module
  • 3rd-party Module
  • Proprietary Remote Module

以降では、それぞれの Module について概要や長所・短所について説明します。なお、前提として「チーム全員でインフラを管理する」という観点での考察になります。

ちなみに、作業ディレクトリ直下も Module という扱いで、これは Root Module と呼びます。

Every Terraform configuration has at least one module, known as its root module, which consists of the resources defined in the .tf files in the main working directory.

Local Module

1つの IaC リポジトリ内で Module を定義して使用するパターンです。

最初の例として、2つのサービスからなるプロダクトのインフラを、各サービスごとに Module として管理する場合のリポジトリ構造を以下に示します。

LocalModule1
├── main.tf
├── modules
│   ├── serviceA
│   │   ├── aws_cloudwatch_logs.tf
│   │   ├── aws_ecs.tf
│   │   ├── aws_iam_ecs.tf
│   │   ├── aws_lb.tf
│   │   ├── aws_rds.tf
│   │   ├── aws_route53.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── serviceB
│       ├── aws_cloudwatch_event.tf
│       ├── aws_lambda.tf
│       ├── aws_sqs.tf
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf
└── variables.tf

このパターンでは、以下の長所と短所が挙げられます。

  • 長所
    • サービスごとに定義場所がはっきりしているので、インフラを変更したい場合にどこに手を入れればいいかが明確
    • リソース名の重複を気にしなくてよい (一種のネームスペースとして利用できる)
  • 短所
    • serviceA と serviceB で相互にリソースを参照するようになると管理がつらい
      • 例えば、serviceA で定義されている LB の URL を serviceB で参照する場合など
      • serviceA の outputs.tf で LB の URL を外出し → ディレクトリトップの main.tf で serviceB の Module を呼び出す際に引数として与える、という流れになる
      • Terraform Module 間のデータの受け渡し方法など、Module の仕組みについて詳しくないと手を入れにくい

短所がマイクロサービスの境界を見誤ってつらくなるパターンに似ていますが、マイクロサービスは最初から意図的にするものではなく開発が進む中で自然な境界が見えてきてから分割するもの、という考え方からすると、少なくとも初手でこのディレクトリ構造にするのはちょっとリスクがあるかなと個人的には思います。
このパターンにするなら、serviceA と serviceB でリポジトリを分け、必要なリソースは Data Source として参照した方がいいかと思います。(循環参照にならないように依存関係には注意する必要がありますが)

2つ目の例として、環境変数(データ)とリソース定義(テンプレート)を分離するパターンのリポジトリ構造を示します。 最初の例を少し拡張した感じですが、こちらのパターンの方が Module の使い方を検索したときによく出てくるかなという印象です。

LocalModule2
├── envs
│   ├── dev
│   │   ├── main.tf
│   │   └── variables.tf
│   └── prd
│       ├── main.tf
│       └── variables.tf
└── modules
    └── serviceA
        ├── aws_cloudwatch_logs.tf
        ├── aws_ecs.tf
        ├── aws_iam_ecs.tf
        ├── aws_lb.tf
        ├── aws_rds.tf
        ├── aws_route53.tf
        ├── main.tf
        ├── outputs.tf
        └── variables.tf

このパターンの長所と短所は以下のようになります。

  • 長所
    • データとテンプレートが分かれているので、「ある環境固有のパラメータを変えたいのか」「全環境に共通の構成を変えたいのか」に応じて手を入れる先が明確
  • 短所
    • 最初の例と同じく、modules 以下に複数の Module が配置され Module 間の参照が発生すると管理がつらくなる

データとテンプレートが分離されているので見通しはいいのですが、Module をどのように切り出すかという点については最初の例と同じ課題を抱えています。 また、この例のように modules 以下の Module 数が1つしかないシンプルな構成なのであれば、下記スライド(p.9)でご紹介したフラットな構成にして環境変数を env ディレクトリ以下に配置するシンプルな構成の方がいいのではと思います。

speakerdeck.com

以上の理由により、LayerX インボイスではこの Local Module は使用していません。

余談ですが、ここではある1つのプロダクトの環境別の情報を envs 以下に配置したケースについて考察しましたが、AWS マルチアカウント運用で、共通して設定したい課金管理やガードレールのような構成を適用する場合には向いていると思います。(Module 間の参照は起きにくいという前提)
その場合、envs 以下には各 AWS アカウントの値(AWS アカウント ID など)を定義する形になります。

3rd-party Module

第三者により公開されている Module を使用するパターンです。 Terraform Registry のほか、GitHub や Bitbucket などで公開されている Module を利用するケースが該当します。

このパターンの長所と短所は以下になります。

  • 長所
    • よくある構成について定義済みのものを利用することで、自分で開発する手間を省くことができる
  • 短所
    • プロダクト固有の構成変更が困難
    • リポジトリを消されるとある日突然使えなくなる

プロトタイプ開発などで、よくある構成のインフラを早く楽に作りたい場合には適していると思いますが、本格的に運用するサービスについては細かい変更が難しいことから向いてないと思います。 よって、LayerX インボイスではこの 3rd-party Module も使用していません。

なお、例えば GitHub で公開されている Module であれば fork すれば上記短所は解決できますが、それは次に説明する Proprietary Remote Module に分類されます。

Proprietary Remote Module

自分の都合で変更可能な、リモートで管理されている Module を使用するパターンです。 公開か非公開かは問いません。

LayerX インボイスでは、この Proprietary Remote Module を使用しています。 以降では、このパターンの Module を使用するに至った経緯からご説明いたします。

LayerX インボイスのサービス構成

LayerX が開発・提供するサービスには、請求書を AI-OCR で処理して各種会計サービスと連携する「LayerX インボイス」のほか、購買申請や支払申請などの申請・承認フローを管理する「LayerX ワークフロー」や、ちょうど先日発表した改正電子帳簿保存法に対応した書類保管サービスである「LayerX 電子帳簿保存」など、複数のプロダクトが存在します。

これらのプロダクトにおいて、「IaC のコードを実行してインフラを構成管理する」という機能、つまり IaC の CI/CD パイプラインは共通して必要なものであり、かつそれほど頻繁に変更されるものではありません。

リポジトリ直下に .tf ファイルをフラットに配置し、各プロダクトごとに個別の IaC リポジトリを作って管理するこれまでの方針の場合、新プロダクトの開発に伴ってパイプライン関連のコードのコピペが多く発生し、共通して構成を変更したい場合でもファイルが複数箇所に分かれていることによって DRY ではなく、コードの管理が煩雑になるという課題がでてきました。

そこで、IaC の CI/CD パイプラインを Module 化し、各プロダクトが共通して利用するようにいたしました。
この判断の根拠としては、コードを一箇所で定義して再利用性を高めるというだけでなく、アプリ開発エンジニアが IaC の CI/CD パイプラインに変更を加えることは基本的にないため、Module 化したとしてもチーム全員でインフラを管理するという観点からは影響はほぼないと考えられることも一因として挙げられます。

IaC の CI/CD Module

現在の CI/CD パイプラインの構成図と Module 構成を以下に示します。

f:id:shnjtk:20211103203006p:plain
IaC CI/CD パイプライン

IaCModuleRepository
└── aws
    └── infra-deploy-pipeline
        ├── aws_cloudwatch_logs.tf
        ├── aws_codebuild.tf
        ├── aws_codepipeline.tf
        ├── aws_codestar_notifications.tf
        ├── aws_iam.tf
        ├── aws_s3.tf
        ├── main.tf
        ├── outputs.tf
        └── variables.tf

このパイプラインがやっていることは単純で、

  • GitHub にある IaC リポジトリからコードをチェックアウト
  • CodeBuild で terraform apply を実行

というフローなのですが、構成するリソースとしては CodePipeline や CodeBuild だけでなく

  • CodePipeline および CodeBuild に付与する IAM Role
  • チェックアウトしたコードを CodeBuild に渡すアーティファクトストアとしての S3 バケット
  • CodeBuild のログを保管するための CloudWatch Logs
  • パイプライン処理の開始・終了を Slack に通知するための CodeStar Connection

なども必要になります。
これらのコードを Module としてまとめて管理することで、各プロダクトの IaC リポジトリでは、以下のように Module ブロックを追加するだけでよくなります。

module "infra_deploy_pipeline" {
  source = "git@github.com:(GitHub user name)/(IaC Module repo name)//aws/infra-deploy-pipeline"

  tf_version                = var.tf_version
  service_id                = local.service_id
  managed_by                = local.managed_by
  env                       = var.env
  repository_id             ="${local.gh_org_name}/${local.gh_repo_name}"
  branch_name               = var.branch_name
  codestar_connection_arn   = var.aws_codestar_connection_arn
  aws_chatbot_arn           = var.aws_chatbot_slack_arn
}

Module に与えるインプットとしては主に以下になります。

  • Terraform のバージョン
  • チェックアウトするリポジトリ名やブランチ名
  • CodeStar Source Connection ARN
  • Slack 通知のための AWS Chatbot ARN

CodeStar Source Connection や Chatbot は、AWS アカウント内で1つ作成しそれを共用しているため、その既存リソースの ARN を指定するようにしています。 また、 source 指定では varlocal などの変数は使えないため、GitHub ユーザー名やリポジトリ名をハードコードすることになります。

なお、この Module はプライベートリポジトリで管理しているため上記のような source 指定となりますが、リポジトリ内のサブディレクトリを指定する時は、リポジトリ名の後の / は2つ必要になります。((IaC Module repo name)//(directory)

このような Module を作成することで、今後新たなプロダクトを開発する際に IaC の CI/CD パイプラインが必要になっても、上記 Module ブロックを記述するだけで他のプロダクトと同じパイプラインをすぐに構築することができ、コードも一箇所で管理されるため DRY になります。

まとめ

今回は、チーム全員でインフラを管理するという前提のもと、Terraform Module の使用例と LayerX インボイスではどのように Module を使用しているかについてご紹介いたしました。 まとめると以下となります。

  • Local Module は初手で使用するのは避ける
  • 3rd-party Module は本格運用するサービスで使用するのは避ける
  • Proprietary Remote Module はプロダクトラインナップが増えてコードのコピペが増えてきたら使用を検討する
    • プロジェクト全体で共通の構成、かつ頻繁に変更されないリソース群は Module 化の良い対象となる
    • アプリ開発エンジニアも触る可能性があるリソースについては無理に Module 化しなくてもよい

これまでにも色々な Module の使い方を試してみて、現時点でこれがいいのではという私自身の見解になります。Module の使用について悩まれている方のヒントになれば幸いです。

We are hiring!!

LayerX インボイスでは、サービスの拡大に伴いインフラもこれからどんどん発展していきますので、成長する SaaS のインフラ開発スキルを磨きたい、チームの規模に合わせた実践的で効率的なインフラ管理を追求したいなど、インフラ開発に興味がある方はぜひ一緒に開発しましょう! もちろん、インフラだけでなくアプリやQA、CSなどサービス開発に関わる全ての職種で積極的に採用中ですので、お気軽にご連絡ください!

また、私が所属する CTO 室では、社内全体のインフラ管理やセキュリティ向上のための様々な施策に取り組んでいます。 今回ご紹介したような Terraform の活用方法をはじめとして、定期的にメンバーが集まって上手くいったことや新しく出てきた課題などを日々情報共有してレベルアップに努めておりますので、 クラウド管理ツールやクラウドプロバイダーを活用して組織の活動をテクノロジーで支えるという「屋台骨エンジニア」に興味がある方はぜひこちらの求人もご覧ください。

herp.careers

LayerX の事業やチームについて、もっと知りたい方はこちら

layerx.notion.site

LayerX のメンバーとカジュアルに話してみたい方はこちら

meety.net

LayerX の募集ポジション一覧はこちら

herp.careers