意外と便利!こんなところにもTerraform
※本記事は、2022年12月22日に公開済みの記事を移行して再掲載したものです。
はじめに
フェズでエンジニアをしている321です
フェズではプロダクトやデータ基盤のIaCにTerraformを利用しています
個人的には前職でもTerraformを利用していたのですが、基本的にはプロダクトの環境構築用途で利用する事がほとんどでした
フェズに入ってアカウント管理などもミッションとする中で、意外と便利にTerraformが利用できるシーンがいくつかあったので
今回はそれを実際にコードと合わせてご紹介しようと思います
Why Terraform ?
アカウント管理は全ての企業で大切ですが、状況によって要求されるレベルが異なる事もあると思っています
厳密な監査等に対応するために、台帳などで管理が必要な場合もあります
フェズではまだその段階に至っていませんが、とはいえ全く管理が行き届いておらず現状把握が困難だったり履歴が追えないのはどの企業でも困ります
そこで、コードという形で履歴を残す事ができるTerraformを使ってみました
※ 作成されたtfstateから台帳を自動的に作成する。ような事もできるとは思いますが、現時点ではそこまでやっていません
※ tfstate = Terraformが管理しているリソースの現在の状態を表すファイル
Google Group
フェズではGoogle Workspaceを使っているので、メンバーのグルーピングにはGoogle Groupが利用できます
Google Cloud におけるアカウント管理は基本的にGoogle Groupに対して権限を付与しています
Google Groupの作成&メンバー管理、そしてGoogle CloudのProjectへの権限付与は以下のようなコードで実現しています
利用した Resource
Terraform Registry: cloud_identity_group
Terraform Registry: cloud_identity_group_membership
ディレクトリ&ファイル
┣ group.yaml ## グループとメンバーのリスト
┣ locals.tf ## 変数定義
┣ group.tf ## グループとメンバー追加 (コード的にはmoduleを呼び出しているだけ)
┗ modules ## 何度も利用できるようにグループの作成とメンバー追加はmodules化している
┗ group
┣ variables.tf
┗ main.tf
実際のコード
- group.yaml
- HCLでも書けますが、どのグループに誰が属しているのか見やすくするためにYamlで書いて別ファイルに切り出しています
- メンバーの追加/削除をするだけなら、このファイルを編集してApplyすれば完了です
group_admin:
description: "管理者グループ"
display_name: "管理者グループ"
email_address: "group_admin@your_domain.com"
member_email_addresses:
- "admin_1@your_domain.com"
- "admin_2@your_domain.com"
group_analyst:
description: "アナリストグループ"
display_name: "アナリストグループ"
email_address: "group_analyst@your_domain.com"
member_email_addresses:
- "analyst_1@your_domain.com"
- "analyst_2@your_domain.com"
group_developer:
description: "開発者グループ"
display_name: "開発者グループ"
email_address: "group_developer@your_domain.com"
member_email_addresses:
- "developer_1@your_domain.com"
- "developer_2@your_domain.com"
- locals.tf
locals {
// YamlをJsonに変換
groups = yamldecode(file("./group.yaml"))
group = {
group_admin = local.groups.group_admin
group_analyst = local.groups.group_analyst
group_developer = local.groups.group_developer
}
}
- group.tf
- 作成するグループの数だけmoduleを呼び出しているだけ
module "group_admin" {
source = "./modules/group"
description = local.group.group_admin.description
display_name = local.group.group_admin.display_name
email_address = local.group.group_admin.email_address
member_email_addresses = local.group.group_admin.member_email_addresses
}
module "group_analyst" {
source = "./modules/group"
description = local.group.group_analyst.description
display_name = local.group.group_analyst.display_name
email_address = local.group.group_analyst.email_address
member_email_addresses = local.group.group_analyst.member_email_addresses
}
module "group_developer" {
source = "./modules/group"
description = local.group.group_developer.description
display_name = local.group.group_developer.display_name
email_address = local.group.group_developer.email_address
member_email_addresses = local.group.group_developer.member_email_addresses
}
- modules/group/variables.tf
variable "description" {
type = string
default = null
}
variable "display_name" {
type = string
}
variable "email_address" {
type = string
}
variable "member_email_addresses" {
type = list(string)
default = []
}
// 管理者は固定
variable "owner_email_addresse" {
type = string
default = "group_owner@your_domain.com"
}
variable "parent" {
type = string
default = "customers/XXXXXX"
}
- modules/group/main.tf
resource "google_cloud_identity_group" "cloud_identity_group" {
description = var.description
display_name = var.display_name
initial_group_config = "EMPTY"
parent = var.parent
group_key {
id = var.email_address
}
labels = {
"cloudidentity.googleapis.com/groups.discussion_forum" = ""
}
}
resource "google_cloud_identity_group_membership" "owner" {
group = google_cloud_identity_group.cloud_identity_group.id
preferred_member_key {
id = var.owner_email_addresse
}
roles {
name = "OWNER"
}
roles {
name = "MEMBER"
}
}
resource "google_cloud_identity_group_membership" "member" {
for_each = toset(var.member_email_addresses)
group = google_cloud_identity_group.cloud_identity_group.id
preferred_member_key {
id = each.key
}
roles {
name = "MEMBER"
}
}
Github Account
フェズはコード管理にGithubを使っていて、GithubアカウントとリポジトリもTerraform管理しています
こちらもTerraformにproviderがあったので導入してみました
※ 紹介するコード以外にもTeamの作成や各リポジトリへの権限追加などもIaCしているのですが、コードが長くなってしまうので割愛しています
Terraform Registry: github_membership
Terraform Registry: github_repository
ディレクトリ&ファイル
┣ member.yaml ## アカウントのリスト
┣ reposiroty.yaml ## リポジトリのリスト
┣ locals.tf ## 変数定義
┣ membership.tf ## アカウント作成
┗ repository.tf ## リポジトリ作成
実際のコード
- member.yaml
- HCLでも書けますが、どのメンバーがどのロールを持っているか見やすくするためにYamlで書いて別ファイルに切り出しています
admin:
- "admin_1"
- "admin_2"
member:
- "member_1"
- "member_2"
- repository.yaml
- HCLでも書けますが、リポジトリの一覧を見やすくするためにYamlで書いて別ファイルに切り出しています
repo_1:
name: "repo_1"
description: "This is repo_1"
repo_2:
name: "repo_2"
description: "This is repo_2"
repo_3:
name: "repo_3"
description: "This is repo_3"
## リポジトリをアーカイブする場合
archived: true
- locals.tf
locals {
// YamlをJsonに変換
membership = yamldecode(file("./member.yaml"))
repository = {
default = {
allow_merge_commit = true
allow_rebase_merge = true
allow_squash_merge = true
archived = false
delete_branch_on_merge = true
description = null
has_downloads = true
has_issues = true
has_projects = true
has_wiki = true
}
repos = yamldecode(file("./repository.yaml"))
}
}
- membership.tf
resource "github_membership" "admin" {
for_each = toset(local.membership.admin)
username = each.key
role = "admin"
}
resource "github_membership" "member" {
for_each = toset(local.membership.member)
username = each.key
role = "member"
}
- repository.tf
resource "github_repository" "repository" {
for_each = local.repository.repos
allow_merge_commit = lookup(each.value, "allow_merge_commit", local.repository.default.allow_merge_commit)
allow_rebase_merge = lookup(each.value, "allow_rebase_merge", local.repository.default.allow_rebase_merge)
allow_squash_merge = lookup(each.value, "allow_squash_merge", local.repository.default.allow_squash_merge)
archived = lookup(each.value, "archived", local.repository.default.archived)
delete_branch_on_merge = lookup(each.value, "delete_branch_on_merge", local.repository.default.delete_branch_on_merge)
description = lookup(each.value, "description", local.repository.default.description)
has_downloads = lookup(each.value, "has_downloads", local.repository.default.has_downloads)
has_issues = lookup(each.value, "has_issues", local.repository.default.has_issues)
has_projects = lookup(each.value, "has_projects", local.repository.default.has_projects)
has_wiki = lookup(each.value, "has_wiki", local.repository.default.has_wiki)
name = each.key
visibility = "private"
// Repoを管理するチームごとに設定を更新しそうなものはignoreに設定しておく
lifecycle {
ignore_changes = [
allow_merge_commit,
allow_rebase_merge,
allow_squash_merge,
delete_branch_on_merge,
is_template,
pages,
]
}
}
工夫したポイント
慣れたTerraformerじゃなくても直感的にアカウントの状況が伝わりやすく、修正箇所がなるべく少なくなるように書いてみました
というのも、この記事を書いている段階では基本的にこのあたりのコードは私とごく一部のメンバーが管理しています
各所からの申請ベースでアカウントの追加/削除に対してPullRequestを我々が作成する、という運用になっています
ですが、組織の規模が拡大した時には、、、
- 部署やプロダクト毎に管理者を立ててもらって、その管理者からユーザ追加のPullRequestを出してもらう
- 横断部門やコーポレートITなどの最終的に責任を持つ部門がPullRequestのレビューをする
というような可能性も無くはないと思います
(その段階に組織が成長した時に、ツールとしてTerraformでいいのか?という議論は勿論ありますが・・・)
まとめ
コードが多めの記事になってしまいましたが、Terraformでアカウント管理もできるよ
というご紹介でした
勿論、プロダクトやデータ基盤の環境構築などにもTerraformはガッツリ利用しています
機会があればそのあたりもまたご紹介できればと思います
フェズは、「情報と商品と売場を科学し、リテール産業の新たな常識をつくる。」をミッションに掲げ、リテールメディア事業・リテールDX事業を展開しています。 fez-inc.jp/recruit
Discussion