Terraformでセキュリティグループをモジュール化
はじめに
はじめまして、Iwakiと申します。
最近Terraformの学習を通して、AWSのリソースの設定ををコード化しています。
その中で、現状セキュリティグループの設定は繰り返し使用する場面が多く、毎回似たようなコードを書くのが手間に感じていました。
そこで今回は、Terraformでセキュリティグループをモジュール化し、再利用性の高い構成を作る方法について紹介したいと思います。
Terraformを使ったAWS環境の自動化や、コードの効率化に興味のある方の参考になれば幸いです。
セキュリティグループのモジュール
モジュールは以下の3つの要素から構成されています。
- variable :モジュールが受け取るパラメータの定義
- resource:実際に作成するリソース(今回はセキュリティグループ)の定義
- output:リソース作成時にモジュールが出力する値の定義
上記の要素ごとにtfファイルを分けてモジュールを作成していきます。
モジュールを作成する前にモジュール作成用のディレクトリを用意し、ディレクトリ構成は以下とします。
terraform/
├─ security_group.tf
└─ modules/
└─ security_group/
├─ variables.tf
├─ main.tf
└─ outputs.tf
variables.tf
モジュールが受け取るパラメータを見ていきます。
variable "name" {
type = string
}
variable "description" {
type = string
}
variable "vpc_id" {
type = string
}
variable "ingress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = optional(list(string))
security_groups = optional(list(string))
}))
}
variable "egress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
}
variable "tags" {
type = map(string)
default = {}
}
上記のingress_rulesとegress_rulesではそれぞれインバウンド・アウトバウンドルールを定義しています。typeのlist(object)では、listによって任意の型のパラメータ(object)を管理することを表しています。また、cidr_blocksとsecurity_groupsの型に設定しているoptional(list(string))は省略可能な文字列のリストを意味します。セキュリティグループでは送信元と送信先にCIDR Blockやセキュリティグループを指定したい場合もあるため、optionalとすることで、呼び出し側はどちらかのみを設定する形になります。
main.tf
以下でセキュリティグループを定義します。
resource "aws_security_group" "this" {
name = var.name
description = var.description
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = lookup(ingress.value, "cidr_blocks", null)
security_groups = lookup(ingress.value, "security_groups", null)
}
}
dynamic "egress" {
for_each = var.egress_rules
content {
from_port = egress.value.from_port
to_port = egress.value.to_port
protocol = egress.value.protocol
cidr_blocks = egress.value.cidr_blocks
}
}
tags = var.tags
}
cidr_blocksとsecurity_groupsでそれぞれlookup関数を使用しています。
この場合、cider_blocksとsecurity_groupsが設定されていればその値を返しますが、設定されていない場合はnullを返します。
outputs.tf
リソース作成時に出力する値を定義します。
以下はセキュリティグループIDを出力しています。
output "security_group_id" {
value = aws_security_group.this.id
}
モジュール呼び出し側(security_group.tf)
モジュールの呼び出し方は以下になります。
module "リソース名" {
source = "モジュールを作成したフォルダのパス"
パラメーター名 = "パラメーターの値"
}
リソース名はモジュールを参照するためにコード内で使用できる識別子です。
モジュールを呼び出す側では、以下のようにALBに割り当てるセキュリティグループを例に作成したモジュールを呼び出します。
#SG for ELB
module "elb_sg" {
source = "./modules/security_group"
name = "elb-sg"
description = "Allow HTTP and HTTPS from anywhere"
vpc_id = aws_vpc.main.id
ingress_rules = [
{
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
]
egress_rules = [
{
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
]
tags = {
Name = "elb-sg"
}
}
sourceでモジュールを作成したフォルダを定義しています。
それ以降はvariables.tfで定義したモジュールが受け取るパラメータに具体的な値を設定しています。
おわりに:モジュール化のメリット
- 再利用性の向上:同じ構成を複数環境・複数用途で簡単に使い回すことができ、コピペによるコードの重複を防ぐことができます。
- 保守性の向上:今回のセキュリティグループの場合は、ルールを修正する際、モジュール内のコードを一箇所修正するだけで済みます。
- 可読性・構造の明確化:役割ごとにファイルやディレクトリが整理されることで、コードの読みやすさが向上します。
まだ勉強し始めたばかりで間違っている部分があるかもしれませんが、今回の記事が、皆さんのTerraform学習の一助になれば幸いです。
Discussion
間違ってるわけではないですが、SG リソースに ingress と egress をベタで書くと管理が面倒になるので、なるべく早めにやめたほうがいいです。
一番上に書いてあります。
また、AWS に関していえば、公式的なモジュールが十分にあるので、自作するのは時間と労力がもったいない。
自作のコードでどう作るか、ではなく、共通のモジュールで、どう設定するか、を考えるほうが有意義です。
ご指摘いただきありがとうございます!
AWSに関して公式のモジュールがあるのですね。
指摘いただいた内容どちらもとても勉強になりましたので参考にさせていただきます!