Terraforming: 既存のインフラリソースを Terraform コードに落としこむ

インフラ界隈のみなさま、 Terraform 使ってますか? 導入したいけど、既存のリソースの管理をどうするか悩んでないですか?

少し前からこの問題を解決する Terraforming というツールを作っていて、今回 v0.1.0 を公開したので紹介します :tada:

https://github.com/dtan4/terraforming

https://rubygems.org/gems/terraforming

Gem Version Docker Repository on Quay.io

これはなに

Vagrant で有名な HashiCorp が開発している Terraform というインフラをコードで管理するためのツールがあります。 Infrastructure as Code というやつです。 Terraforming は、AWSAPI を叩いていま動いている既存の AWS リソースから Terraform のコードを生成するツールです。

インストール

RubyGems として公開されているので、

$ gem install terraforming

Docker Image も用意しているので、そちらを使いたい方は

$ docker pull quay.io/dtan4/terraforming:latest

使い方

予め AWS のクレデンシャルを環境変数に入れておきます。 Mac ユーザなら envchain おすすめです。

export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_DEFAULT_REGION=xx-yyyy-0

リソース名を指定するだけだと、tf 形式で出力されます。 S3 bucket の場合だと:

$ terraforming s3
resource "aws_s3_bucket" "hoge" {
    bucket = "hoge"
    acl    = "private"
}

resource "aws_s3_bucket" "fuga" {
    bucket = "fuga"
    acl    = "private"
}

これを s3.tf とかに書き出せばよいです。

--tfstate オプションをつけると、tfstate 形式で出力されます。

$ terraforming s3 --tfstate
{
  "version": 1,
  "serial": 1,
  "modules": {
    "path": [
      "root"
    ],
    "outputs": {
    },
    "resources": {
      "aws_s3_bucket.hoge": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "hoge",
          "attributes": {
            "acl": "private",
            "bucket": "hoge",
            "id": "hoge"
          }
        }
      },
      "aws_s3_bucket.fuga": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "fuga",
          "attributes": {
            "acl": "private",
            "bucket": "fuga",
            "id": "fuga"
          }
        }
      }
    }
  }
}

また、tfstate は既存の terraform.tfstate にマージした形で出力することもできます。ちゃんと serial (terraform.tfstate のバージョン番号) もインクリメントされます*1

$ terraforming s3 --tfstate --merge=/path/to/tfstate
{
  "version": 1,
  "serial": 89,
  "remote": {
    "type": "s3",
    "config": {
      "bucket": "terraforming-tfstate",
      "key": "tf"
    }
  },
  "modules": {
    "path": [
      "root"
    ],
    "outputs": {
    },
    "resources": {
      "aws_iam_user.dtan4": {
        "type": "aws_iam_user",
        "primary": {
          "id": "dtan4",
          "attributes": {
             "arn": "arn:aws:iam::012345678901:user/dtan4",
             "id": "dtan4",
             "name": "dtan4",
             "path": "/",
             "unique_id": "ABCDEFGHIJKLMN1234567"
          }
        }
      },
      "aws_s3_bucket.hoge": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "hoge",
          "attributes": {
            "acl": "private",
            "bucket": "hoge",
            "id": "hoge"
          }
        }
      },
      "aws_s3_bucket.fuga": {
        "type": "aws_s3_bucket",
        "primary": {
          "id": "fuga",
          "attributes": {
            "acl": "private",
            "bucket": "fuga",
            "id": "fuga"
          }
        }
      }
    }
  }
}

これを terraform.tfstate に上書きすればよいです。

最後に terraform plan を実行して、差分が出ないことを確認して下さい。

$ terraform plan
No changes. Infrastructure is up-to-date. This means that Terraform
could not detect any differences between your configuration and
the real physical resources that exist. As a result, Terraform
doesn't need to do anything.

対応している AWS リソース

v0.1.0 時点では以下のリソースに対応しています。

  • Database Parameter Group
  • Database Security Group
  • Database Subnet Group
  • EC2
  • ELB
  • IAM Group
  • IAM Group Policy
  • IAM Instance Profile
  • IAM Policy
  • IAM Role
  • IAM Role Policy
  • IAM User
  • IAM User Policy
  • Network ACL
  • Route53 Record
  • Route53 Hosted Zone
  • RDS
  • S3
  • Security Group
  • Subnet
  • VPC

もちろん鋭意追加予定です。

兄弟分

DNSimple に対応した Terraforming::DNSimple があります*2。 一応プロバイダが違うのでツールを分割したけど、いつかは Terraforming に統合するかもしれません*3

ここから余談

作ったきっかけ

インターン先*4でインフラをコードで管理しようという機運が盛り上がり、Terraform を導入することになりました。

公開されている中だと、KAIZEN Platform の Terraform 導入事例CyberAgent の Terraform 導入事例があります。 どちらもインフラ刷新や新規構築のタイミングで Terraform を導入しています。 つまり一からコードを書いてそれを元に Terraform でインフラを構築するということで、 Terraform のユースケースには合っています。

しかし、自分たちは別に刷新のタイミングなんかではなかったので、今動いている既存のインフラをコードに起こす必要がありました。 そこで既存のインフラを Terraform で管理する術を調べていたところ、人間が手作業でやるのは相当厳しいとわかったので生成ツールを作ろうと思い立ったのでした。

Terraform がどうやってインフラを管理しているのか

Terraform は、状態管理用の terraform.tfstate というファイルを用いて自分が管理しているインフラを把握しています。 terraform.tfstate の実体は JSON で、Terraform が管理しているインフラの状態が記述されています。

{
    "version": 1,
    "serial": 88,
    "remote": {
        "type": "s3",
        "config": {
            "bucket": "terraforming-tfstate",
            "key": "tf"
        }
    },
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_iam_user.dtan4": {
                    "type": "aws_iam_user",
                    "primary": {
                        "id": "dtan4",
                        "attributes": {
                            "arn": "arn:aws:iam::012345678901:user/dtan4",
                            "id": "dtan4",
                            "name": "dtan4",
                            "path": "/",
                            "unique_id": "ABCDEFGHIJKLMN1234567"
                        }
                    }
                }
            }
        }
    ]
}

terraform planterraform apply では、この terraform.tfstate と開発者が書いた tf ファイルを突き合わせることで、どのリソースが増えた消えた変更されたを計算するのです。

既存のインフラリソースを追加する場合は、tf ファイルにそのリソースを書くだけではダメなのです。 terraform.tfstate に記述されていないと、Terraform はそのリソースがないものとみなして新しく作成しようとします。 そのため、同時に terraform.tfstate にも手を加えるする必要があるのです。

ほら、手で JSON を書くのは辛いでしょう? そのための Terraforming なのですよ。

既存のインフラを Terraform で管理する

かなり早い段階でこの問題に触れていたのは下の記事です。

Handling extant resources in Terraform « dan phrawzty's blog

自分もこの記事を見て terraform.tfstate の更新がいることを知ったのでした。

Terraform のリポジトリでも半年前から issue は上がっているのですが、大して進展していません(どこから見つけたのか、Terraforming が登場していたりします)。

Import resources into Terraform · Issue #581 · hashicorp/terraform

ちなみに cookpad が作っている codenize.tools には、我々が求めていた既存インフラの export 機能がちゃんとあります。 もともとは cookpad のインフラをコード化するために作られたツールなので、この機能は必須だったのでしょう。

開発方法

tf はドキュメント化されているのでともかく、tfstate は出力形式がわからなかったので実際にリソースを Terraform で作ってその結果をもとに実装したりしていました。 結構ドキュメント化されていない暗黙の決まりみたいなのが多く(API レスポンスのどのパラメータを tfstate に反映するのだとか)、Terraform 本体のコードもそれなりに読みました。

おわりに

Terraforming という便利ツールを開発した話と、それにまつわるいろいろを書きました。 既存リソースの export 機能、それなりに需要ありそうですがどうなんでしょう…

Issue, Pull Request は大歓迎です!!!ぜひ使ってみてください :tada:

https://github.com/dtan4/terraforming

*1:リモート (S3, Atlas etc.) で terraform.tfstate を管理している場合、手元でいじったあと serial を上げないとリモートのものと一致しないと怒られる。S3 で terraform.tfstate を管理する方法はこちら

*2:インターン先では Route53 ではなく DNSimple を使っているので

*3:本家 Terraform もひとつのツール複数プロバイダ対応しているわけだし

*4:インフラチームにいるのです