この記事は、リレーブログ企画「24卒リレーブログ」の記事です。
はじめに
こんにちは!
始めまして、24卒新入社員の滝川です
現在、ジョブローテ期間中で入会システムチームに所属しており、@nifty光や@nifty withドコモ光の開発や運用、Toil削減の業務を日々行っています。その中で既存のAWSリソースをTerraform化する業務がありました。その際、トレーナーの方に特に意識してほしいと言われたことが「ディレクトリ構成をしっかり考えること」でした。この経験を通じて得た知見はとても有益だと考えています。そこで、この記事では、Terraformのディレクトリ構成とモジュール化について、私が得た知見を共有していきたいと思います。
ディレクトリ構成を考えなければいけない理由
Terraformは、インフラストラクチャをコードとして管理する(IaC)ためのツールです。その主な利点は、インフラの構築や運用を簡素化し、一貫性を保ちながら効率的に管理できることです。しかし、ディレクトリ構成を考慮せず記述してしまうとこの本来の利点が損なわれてしまいます。
具体的には
- 可読性の低下:どのリソースがどこで定義されているのか把握しづらくなる
- 重複コードの増加:似たような設定が複数の場所に散らばり、DRY原則に反する
- 変更の難しさ:一つの変更が複数の場所に影響を及ぼし、予期せぬエラーの原因となる
- チーム協業の障害:ほかのメンバーがコードを理解し、修正することが困難になる
- スケーラビリティの制限:プロジェクトの成長に伴い、管理が難しくなる
結果として、Terraformを使用する利点が大幅に減少してしまいます。これらの課題を解決し、Terraformの利点を最大限に活かすには「モジュール化」が必須です。
モジュール化とは?
Terraformにおけるモジュール化とは、インフラストラクチャのコードを論理的に分割し、再利用可能なコンポーネントとして組織化する手法です。と言われてもイメージがつきにくいと思います。
実際にディレクトリ構造やコードを見た方がイメージがつきやすいと思いますので、具体的なモジュールの例を見ながら説明していきます。
全体のディレクトリ構造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
project-root/ ├── modules/:再利用可能なインフラストラクチャコンポーネントを管理 │ ├── vpc/ │ │ ├── main.tf :リソースの主要な定義を含む │ │ ├── variables.tf :モジュールの入力変数を定義 │ │ └── outputs.tf :モジュールの出力値を定義 │ └── ecs/ │ ├── main.tf │ ├── variables.tf │ └── outputs.tf ├── environments/ :環境固有の設定を管理(dev,staging,prod等) │ └── dev/ │ └── main.tf :開発環境固有のリソース構成を定義し、モジュールを呼び出す │ └── prod/ │ └── main.tf └── README.md |
- module配下にリソース単位でモジュール化(vpc,ecs)
- 再利用性を高める
- 可読性を高める
- 環境分離をしている
- 他の環境に影響を与えることなく作業ができるためリスク軽減が図れる
- 環境ごとの差分を簡単に管理することができる
実際にECSを作成してみた
enviroments/dev/main.tf
ECSモジュールの呼び出しと環境固有の設定を行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# プロバイダーの設定 provider "aws" { region = "ap-northeast-1" } # モジュール呼び出しとdev環境固有の変数の定義 module "ecs" { source = "../../modules/ecs" # ecs/main.tfで定義された変数に値を入れている project_name = "my-ecs-project" task_cpu = 256 task_memory = 512 container_name = "my-app" container_image = "nginx:latest" container_port = 80 desired_count = 1 # module/vpc/outputs.tfファイルから値を持ってきている subnet_ids = module.vpc.private_subnet_ids } |
modules/ecs/main.tf
ECSクラスター、サービス、タスク定義などの主要なリソースを定義しています。
環境に関わらず一意な値はハードコードで指定し、環境ごとに変更される可能性のあるものは変数として定義しています。
var.project_nameを再利用することで変数定義する数を減らしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
resource "aws_ecs_cluster" "main" { name = "${var.project_name}-cluster" } resource "aws_ecs_service" "main" { name = "${var.project_name}-service" cluster = aws_ecs_cluster.main.id task_definition = aws_ecs_task_definition.main.arn launch_type = "FARGATE" desired_count = var.desired_count network_configuration { subnets = var.subnet_ids assign_public_ip = false } } resource "aws_ecs_task_definition" "main" { family = "${var.project_name}-task" network_mode = "awsvpc" requires_compatibilities = ["FARGATE"] cpu = var.task_cpu memory = var.task_memory container_definitions = jsonencode([ { name = var.container_name image = var.container_image portMappings = [ { containerPort = var.container_port hostPort = var.container_port } ] } ]) } |
modules/ecs/variables.tf
モジュールの入力変数を定義しています。各変数のタイプ、デフォルト値、説明を指定することで、モジュールの使用者が適切な値を設定できるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
variable "project_name" { description = "Name of the project" type = string } variable "task_cpu" { description = "CPU units for the ECS task" type = number default = 256 } variable "task_memory" { description = "Memory for the ECS task in MiB" type = number default = 512 } variable "container_name" { description = "Name of the container" type = string } variable "container_image" { description = "Docker image for the container" type = string } variable "container_port" { description = "Port exposed by the container" type = number default = 80 } variable "desired_count" { description = "Desired number of tasks" type = number default = 1 } variable "subnet_ids" { description = "List of subnet IDs for the ECS tasks" type = list(string) } |
modules/ecs/outputs.tf
モジュールの出力値を定義しています。他のモジュールや設定で使用する値、特にリソース作成時に動的に変化する値(例:ARN)を定義します。
dev/main.tfのsubnet_idsのように他モジュールで呼び出す際に利用されます。
1 2 3 4 5 6 7 8 9 |
output "cluster_id" { description = "The ID of the ECS cluster" value = aws_ecs_cluster.main.id } output "service_name" { description = "The name of the ECS service" value = aws_ecs_service.main.name } |
実際にapplyしたい方!
- サンプルコードを用意しました!
- AWSの資格情報の設定が別途必要になるので下記を参考にしてみてください!
おわりに
今回はファイル構成を意識しながらTerraformでECSを実装していきました。
ファイル構成は人それぞれで私はリソース単位でモジュール化していきましたが、機能単位(API、バッチ、ネットワーク、データ処理)でモジュール化するファイル構成もあるようでまだまだ奥が深そうでした。
Terraformはニフティ内で盛んに使われている技術なのでこれからも継続して学習し、知見を深めていきたいです。
最後まで読んでいただきありがとうございました!!
次回は、緑川さんです!どんな記事になるのか楽しみですね!!