基本的には考え方の部分についての雑記です。
この内容は過去のNRUGで発表したものになりますので、いってみれば下記発表の焼き直しの記事のです。
どんな人向けの記事?
Terraformを使いたいが、使う人のスキルもバラバラだけどIaCでコードをテンプレート化したいという方。特にコード作成者とコード実行者のスキルレベルに差がある場合
概要・背景
AWSやGoogle CloudやAzureなど、インフラ環境をコードで管理したいユースケースは多くあるでしょう。例え環境が壊れてしまったとしても、データとコードさえあれば簡単に環境が復旧できるので、なるべくコードで管理することが望ましいと考えています。
しかしながらTerraformでコードを管理する場合、最新のコードを実行した環境の情報がtfstateファイルで管理されており、このファイルの扱いが割と厄介になります。どういうことかというと、元のTerraformのコードを修正したとしても、うまく環境とコードがフィットしなかったらTerraformのコードがエラーでこけてしまい実行できないからです。
こうなってしまうと、tfstateファイルとTerraformのコードと現在の設定を眺めながらどこに問題があるかデバッグをするという苦行に立たされます。まだコードを一から作成した人ならある程度意図はわかるでしょう。しかしながら初見でTerraformコードとtfstateファイルを眺めながらデバッグをするのはそれなりにハードルが高く、「Terraform?なにそれおいしいの?」レベルの知識の持ち主だったら、まずTerraformがなんたるというところから学習していく必要があり、これって運用する立場ならものすごく効率が悪いと言わざるを得ません。
なぜこのような話をするかというと、実際に私が運用している環境がまさにこの状態だったからです。当然、設定の標準化を行う場合はレベルの高い人だけでなく、レベルの低い人でも同じ品質で設定を完遂してもらう必要があります。でもtfstateファイルがあるとそれを実現するのが困難になることが目に見えているから、まずはこの課題をどのようにクリアするかという点を一番最初に検討する必要がありました。
どのようにこの課題を解決したか、次の章でお話をします。
どうやって課題を解決した?
まず、課題が出た時のアプローチは、その課題を解決するためにどのような方法が取れるかということを一番に考えます。今回の場合はその方法を探すためにTeraformの仕様やドキュメントやNew Relicのドキュメントをを一通り目を通し、どのような手法でどのようなメリット・デメリットがあるのか調査しました。
| 手法 | 特徴 | メリット | デメリット | 
|---|---|---|---|
| Terraform | HashiCorp社が提供するIaCツール | ・ドキュメントがしっかり ・AWSやGoogleCloudなど他のIaCでも知識転用できる  | 
・Terraform側で機能拡張されないと、技術的に出来ないこともある | 
| NerdGraph | ・一般的なGraphQLに基づいてコードが書ける ・必要最低限のクエリ項目で設定や更新ができる  | 
・NewRelic GraphiQLというサポートツールがあり、簡単にクエリの作成・実行できる | ・クエリ内容の詳細についてドキュメントが少なかったり英語しかない | 
| REST API | REST(Representational State Transfer)の原則に基づいて設計されたAPIのこと | ・昔からあるため、困ってもネット検索などで情報にたどり着きやすい ・REST API Explorerで利用可能なAPIの一覧が確認できる  | 
・後方互換の位置付けのため、新規学習する場合の推奨はNerdGraph | 
※New Relic設定の前提で表を作成しているため、一般的な話と異なる部分があります
今回の件を進めるにあたり、まずNew Relicのどの手法を使うかという観点で検討を行いました。導入ハードルが高いNerdGraphやRest APIを使用するよりはTerraformを使用する方がハードルが低いと判断して、ツールとしてTerraformを選定しています。
その上で、管理をどうするかという壁にぶち当たりました。前項でもお伝えした通りtfstateの管理は、コード作成者ならまだしも単なるオペレータに保守管理をお願いするにはハードルが高すぎるという課題があります。その上でオペレーションを考慮してどちらの運用パターンにマッチするか考えてみました。
| 運用パターン | ユースケース | 
|---|---|
| 案件毎に環境や設定内容に大きくバラつきがあるが、コードメンテナンスできるスキルの人がいて、障害発生時にコードから環境復旧できることにメリットがある | ・AWSなど、再構築が必要な可能性があるインフラ基盤 ・リソースの定義はコードの中に全て埋め込みコード自体をメンテナンスする  | 
| ・New Relicなど、再構築の可能性を無視できる監視基盤 ・リソースの定義はある程度コードの中に埋め込み、変更箇所はパラメータなどを使って埋め合わせする  | 
案件毎の環境や設定内容は多少のバラつきはあるものの、コードメンテナンスできない人でも自動設定の恩恵を受けられ、コードを使いまわすことで設定ミスを軽減することにメリットがある | 
上記パターンの中で、今回の要件は下の再構築の可能性を無視できる監視基盤に対してのアプローチであることやオペレータに対して難しいオペレーションを取ることなく品質担保をしたかったという目的からterraformのtfstateをリセットする state rm というコマンドを使うことにしました。本筋であればtstate rmコマンドはリソースライフサイクルの中でリソースの使用が終わり、既存コードから抹消するときによく使われるコマンドで、ドキュメント上は下記のように記載されています。
Terraform 状態の主な機能は、構成内のリソース インスタンス アドレスと、それらが表すリモート オブジェクト間のバインディングを追跡することです。通常、Terraform は、削除されたリモート オブジェクトのバインディングの削除など、プランの適用時に実行されるアクションに応じて状態を自動的に更新します。
既存のリモート オブジェクトを最初に破棄せずにそのバインディングを削除するという、あまり一般的ではない状況で使用できますterraform state rm。これにより、オブジェクトがリモート システムに存在し続けている間、Terraform がそのオブジェクトを事実上「忘れる」ようになります。
公式サイトのドキュメントより引用
https://developer.hashicorp.com/terraform/cli/commands/state/rm
リソース抹消時に使用するコマンドを使うことは通常フローからすると少しトリッキーな使い方になると思われますが、そこは用途次第ということになります。今回の目的やリスクを整理すると下記の通りとなります。
- コードに付随するtfstateを使わずに常に新規の設定を流すこと
 - 何か問題が発生した場合は再度コードを流し直してリソースの再作成を行うこと
 - 現在の設定内容(state)を使いまわすことを捨てたとしても、運用上の影響が小さいこと
 
つまり、設定を保守し続けることより使い捨てにすることの方にメリットが大きかったため、上記のような運用を取りました。これがAWSやGoogleCloudなどの実サービスに影響するリソースであれば、上記のような使い捨てをしない方が良いのかもしれません。既存環境はそのままに更新したリソースだけ設定できることや、既存リソースの変更がサービスに直接影響するためです。しかし、監視ツールであるNew RelicのConditionやPolicyなどの設定の場合はどうでしょう?監視の影響は出るかもしれませんが、直接サービス影響がでるものではありませんので、それもこのような運用を取った理由の一つになります。
なお、実際の設定は下記のように実施していきます。
terraform init
  ↓
terraform plan
  ↓
terraform apply
  ↓
terraform state rm
サンプル設定
下記はあくまでもひとつの例ととらえて頂ければ結構ですが、発表資料がボリューミーになり過ぎたためバッサリ削除したものを本文リライトしたので併せて公開します。
variable.tf
# コマンド実行時に指定する変数
variable "api_key" {
  type = string
  default = ""
}
variable "account_id" {
  type = string
  default = ""
}
variable "condition_name" {
  type = string
  default = ""
}
variable "policy_name" {
  type = string
  default = ""
}
main.tf
# New Relicプロバイダー設定
terraform {
  required_version = "~> 1.0"
  required_providers {
    newrelic = {
      source  = "newrelic/newrelic"
      version = "~> 3.12.0"
    }
  }
}
provider "newrelic" {
    api_key    = var.api_key
    account_id = var.account_id
    region = "US"
}
# ポリシー設定
resource "newrelic_alert_policy" "NewRelicPolicy" {
  # Configration policy name
  name = var.policy_name
  # Incident preference
  incident_preference = "PER_POLICY"
}
# コンディション設定
resource "newrelic_nrql_alert_condition" "NewRelicCondition" {
  account_id = var.account_id
  policy_id = newrelic_alert_policy.NewRelicPolicy.id
  type = "static"
  name = var.condition_name
  enabled = true
  violation_time_limit_seconds = 259200
  nrql {
    query = "SELECT average(`host.cpuPercent`) FROM Metric FACET entity.guid, host.hostname"
  }
  critical {
    operator = "above"
    threshold = 85
    threshold_duration = 300
    threshold_occurrences = "all"
  }
  fill_option = "none"
  fill_value  = null,
  aggregation_window = 60
  aggregation_method = "event_flow"
  aggregation_delay = 120
}
さいごに
ツールは所詮ツールです。運用者の負担が軽くなるのであれば、あまり四角定規のように考えずにどのような使い方をしてもいいのではないかというのが個人的な感想です。
こちらの記事がどなたかのお役に立てれば幸いです。
お知らせ
AWS周りに情報に関してはこちらのブログに色々と執筆しています。Qiita側の方向性はまだ決めてませんが、こちら側は同じ技術的な内容でも会社側のブログに書けないような方向性の全く違う内容や、そもそも技術的な内容じゃなくてもっと別の話題にしようかと考えています。よければ下記ブログも併せて見て頂けると嬉しいです。