🤖

意外と便利!こんなところにもTerraform

2024/04/22に公開

※本記事は、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はガッツリ利用しています
機会があればそのあたりもまたご紹介できればと思います

フェズ開発ブログ

Discussion