出力を入力へ

プログラミングに関する自分が考えた事を中心にまとめます

Terraform Cloudをterraformで管理する

Terraform CloudにおけるWorkspaceの管理

Terraform Cloudはtfstateの管理だけでなくterraformの実行を管理してくれる. このため,自前でTerraformのCI環境を構築する必要なしに簡単にTerraformを導入できる.

一方で,tfstateの分割単位であるworkspaceが増えるとその管理が大変になる. 具体的にはworkspace間で共通の変数(AWSのクレデンシャル情報など)の管理などが煩雑である. CircleCIのコンテキストやGitHub ActionsのOrganization Secretsのように 組織内で共通して変数を参照する仕組みがないので, workspaceごと変数を設定する必要がある. このため,AWSのクレデンシャル情報をローテートするときには, workspaceの数だけ人手で更新する必要があり,かなり面倒である.

Terraform Cloud 管理の自動化

人手で管理するのは面倒なので自動化したい. Terraform CloudにはAPIが提供されており,HashiCorpが公式にメンテするSDKとして go-tfeがある. このSDKやAPIを用いたCLIも多数存在する.

workspaceの変数管理という単目的であれば上記のCLIを利用すれば済む話ではあるが, 変数管理だけでなく通知の設定や 利用するTerraformのバージョンなども管理したくなる. まさに,Terraform Cloudの IaCがやりたい.

こうなるとCLIで操作するだけでは不十分となり, Terraform CloudをTerraformで管理したくなる.

Terraform CloudをTerraformで管理する

Terraform CloudはTerraform Enterpriseのマネージドサービスであり, Terraform Enterprise Providerが利用できる. これを利用してTerraform Cloud上のリソースをTerraformで管理する.

主な注意事項は以下の通り.

認証トークンの設定

Terraform Enterprise Providerにおける認証トークンとしては, ユーザートークンなどが利用できるが, 今回のように複数workspaceを管理するためにはチームAPIトークンの利用がよさそう. ユーザートークンや組織トークンとはアクセスレベルが大きく異なるので目的に応じて検討が必要.

チームトークンはOrganization SettingsのTeamsから発行できる.

f:id:thaim:20201025170239p:plain
チームトークンの発行

発行したトークンは tfeプロバイダに設定する.

provider "tfe" {
  token = var.token
}

variable "token" {}

リソースのインポート

多くの場合 workspaceのリソースなどは既に作成されていると思うので, これをterraform管理下におくためにインポートする. WorkspaceのインポートにはIDを指定する. IDはWorkspaceのGeneral Settingsから確認できる.

Terraform Cloudでリソースのインポートを行う場合, 特にRemote Execution Modeを利用している場合でもインポートの処理はローカルで動作する点に注意. すなわち,Terraform変数や環境変数はローカルでも設定しておく必要がある.

WorkspaceのVCS設定

WorkspaceのVCS連携を利用することで リポジトリを契機としたTerraformの実行や,プルリク画面でのplan結果の表示などができるようになる. このVCS連携は複数の選択肢があり,通常はあまり気にすることがないものの, Terraform管理しようとすると考慮する必要がある. 結論から言うと personal access token を利用する方法のみが上手くいく.

多くの人が利用するであろうGitHub.comとの連携については Configuration-Free GitHubがある. これは Terraform Cloud GitHub App を利用する方法で,細かい認証の設定なしにGitHub.comとTerraform Cloudを連携できる. この方法は通常Terraform Cloudを利用する場合の方法である一方で,tfe プロバイダを用いたterraform管理にはこの方法は利用できない.

もう1つの方法として OAuth App を登録する方法がある. OAuth Appを利用すればGitHub Organizationレベルで接続設定を登録できる. 一方で,こちらもAPIには対応していないので自動化できない.

ということで,唯一の選択肢が personal access token を利用する方法である. personal access token はAPIに対応しているので今回実現したいTerraform CloudのIaCが実現できる. personal access token で連携させる課題としては,接続設定が個人のアカウントに紐付くことである. botアカウントを利用すれば解決できる?かもしれないが自分では試していないのでわからない. APIがpersonal access token以外にも対応して欲しいというissue は挙がっているのでいずれは解決する?

具体的なコードは以下のような感じ. ちなみにGitLabでもほぼ同じような設定で連携できる.

resource "tfe_workspace" "my_workspace" {
  name = "my-workspace"
  organization = tfe_organization.my_organization.name

  file_triggers_enabled = false
  queue_all_runs = false

  vcs_repo {
    identifier = "thaim/my_workspace"
    ingress_submodules = false
    oauth_token_id = tfe_oauth_client.github.oauth_token_id
  }
}

resource "tfe_oauth_client" "github" {
  organization = tfe_organization.my_organization.name

  api_url = "https://api.github.com"
  http_url = "https://github.com"
  service_provider = "github"
  oauth_token = var.github_personal_access_token
}

resource "tfe_notification_configuration" "slack_my_workspace" {
  name = "slack-my-workspace"
  enabled = true
  workspace_id = tfe_workspace.my_workspace.id

  destination_type = "slack"
  url = var.slack_webhook_url
  triggers = ["run:needs_attention", "run:completed", "run:errored"]
}

変数の管理

Terraform CloudのIacを実現したい動機の1つであった変数の管理について. 1つはVCS設定のような共通設定の管理がある. 例えばSlack連携ではwebhook_urlを,リポジトリとの連携ではpersonal access tokenをそれぞれ各リポジトリに設定する必要がある. terraformでTerraform Cloudを管理することで,このworkspaceにおける変数として管理することができる.

例えば上記WorkspaceのVCS設定では, personal access token は変数 var.github_personal_access_token として, Slackのwebhook URLは var.slack_webhook_url として参照している. このため,このTerraform Cloudを管理するworkspaceのvariableとして設定すれば, 変更したいときも変数を更新してapplyするだけでよい.

もう1つが各workspaceの環境変数やTerraform変数の設定である. よくある例がAWSのクレデンシャル情報(アクセスキーIDおよびシークレットアクセスキー)で, 複数のworkspaceで同じ値を設定したい.

環境変数を設定するTerraformリソースの定義例は以下の通り. これは環境変数の例だがTerraform変数も同様に設定できる. 対象となるworkspaceが1つか設定できず,workspaceごとにリソースを定義しないといけないのが若干面倒.

resource "tfe_variable" "aws_access_key_id" {
  workspace_id = tfe_workspace.my_workspace.id

  key = "AWS_ACCESS_KEY"
  value = var.aws_access_key_id
  category = "env"
  sensitive = false
}

resource "tfe_variable" "aws_secret_access_key" {
  workspace_id = tfe_workspace.my_workspace.id

  key = "AWS_SECRET_KEY"
  value = var.aws_secret_access_key
  category = "env"
  sensitive = true
}