クラウドワークスの缶コーヒーエンジニアの森田(@minamijoyo)です。
だいたい毎日缶コーヒーを飲みながら主にインフラ周りの仕事をしてるので、クラウドワークスのインフラの一部は缶コーヒーでできていると言っても過言ではないんじゃなかろうかと思う今日この頃。ちなみに最近のマイブームはFIREのハワイアン微糖です。
これまでのあらすじ
クラウドワークスではAWS(Amazon Web Services)でサーバを運用しており、AWSでのIAM(Identity and Access Management)ユーザのグループポリシーの管理について、以前こんな記事を書きました。
TerraformでAWSのIAMユーザのグループポリシーを管理する - Qiita
Terraform+Atlas+GitHubでAWSのIAMユーザのグループポリシーをいいかんじに管理する - クラウドワークス エンジニアブログ
上記の記事では、IAMのグループポリシーの部分をTerraformで管理する方法について説明していますが、Terraformそのものは既に存在しているAWSリソースは管理できないため、既存のIAMユーザそのものについては、一旦Terraformの管理対象外としていました。
この記事で書くこと
しかしながら、IAMユーザがTerraform管理外だと片手落ちです。というわけで、Terraformingという既存のAWSリソースをTerrafromに変換するツールを使って、既存のIAMユーザをTerraform管理下に入れる方法について説明します。この記事では Terraform v0.6.12、Terraforming v0.7.0を使用しています。
ポイントは以下のとおりです。
- Terraformはリソースの状態をtfstateファイルに保存している。tfstateはただのJSONなので手動で編集可能。(お約束ですが自己責任で)
- Terraform自体には既存のAWSリソースを管理する機能はないが、Terraformingという非公式ツールがあるのでこれを使おう。
- Terraformingは非公式ツールだけど、生成したファイルをマージしてterraform planして差分がなければTerraformとして問題ないことは確認できるので互換性はそこで担保。
- Terraformingで自動生成されるのそのまま使っても良いけど、tfstateの構造が分かれば必要なところだけ手動でリソース名をリネームしたり個別調整することも可能。
Terraformとtfstate
Terraformでは、*.tf
ファイルにリソースを定義して terraform apply
コマンドでAWSなどのクラウドリソースに反映します。*.tf
ファイルのリソース定義を修正した場合は terraform plan
でリソースへの変更差分を確認することができます。この前回からの変更差分をどうやって検出しているかというと、 デフォルトではローカルのカレントディレクトリの.terraform/terraform.tfstate
というファイルにリソース状態を保持しています。
この .tfstate
ファイルはテキストエディタで開くとわかりますが、ただのJSONファイルです。
例えば以下のような main.tf
というファイルがあった場合、
resource "aws_iam_group" "group1" { name = "group1" }
以下のような .terraform/terraform.tfstate
ファイルが生成されています。
{ "version": 1, "serial": 38, "remote": { "type": "s3", "config": { "bucket": "xxxx", "key": "xxxx" } }, "modules": [ { "path": [ "root" ], "outputs": {}, "resources": { "aws_iam_group.group1": { "type": "aws_iam_group", "primary": { "id": "group1", "attributes": { "arn": "arn:aws:iam::xxxx:group/group1", "id": "group1", "name": "group1", "path": "/", "unique_id": "xxxx" } } }, "aws_iam_group.xxxx": { ...(以下、Terraformのリソースが順に出力される)
resourcesのところに、Terraformが認識しているリソースの情報が並んでます。これを見るとTerraformがどのようにリソースを認識しているかが分かってなかなか面白いです。
tfstateファイルの複数人での共有
余談ですが、tfstateを複数人で共有する場合は、これをAtlasやS3などに保存して共有します。 以前紹介したHashiCorp公式のAtlasはベータ期間が終了して有料化されたようです。(既存ユーザは2016/3/8から6ヶ月間の猶予期間が与えられています。)
New Interface Design, User Experience, and Pricing for Atlas - HashiCorp
他のHashiCorpプロダクトと合わせて使うならAtlasを使うメリットがあると思いますが、ただのtfstate置き場だけであればS3でも十分かもしれません。tfstateをS3に置く方法は以下が参考になります。
Amazon S3 で Terraform の状態管理ファイル terraform.tfstate を管理 / 共有する - Qiita
Terraformig とは
tfstateの構造を覗いたら簡単に推測できると思いますが、当然ながらTerraformでは自分で作ったリソースは管理できますが、既に存在するリソースを管理することはできません。既存リソースも管理したいという需要は一定存在すると思いますが、今のところTerraformの公式機能としては存在せず、非公式にTerraformingというツールが開発されているので、これを使ってみます。
Terraformingは現状実質的にAWS用ですが、既存のAWSリソースの情報を取得して、.tf
ファイルと .tfstate
ファイルを生成してくれます。
Terraformは活発に開発されているので、非公式ツールを使うと互換性が気になるところですが、最終的に生成したファイルをマージして terraform plan
して差分がなければTerraformとして問題ないことは確認できるので、そこで何かを壊してないかは担保できるんじゃなかろうかと思います。あと、TerraformingはAWSのリソースの種類ごとにサブコマンドが分かれているので、必要な部分だけ少しずつTerraformingしていくという作戦も可能です。tfstateのdiffが目視で確認レベルであれば最終的になんとでもなるはず。
Terraformingのインストール
それではTerraformingをインストールしてみましょう。 Terraformを既に使っていればAWSのアクセスキーなどの設定はできていると思うのでそのへんは割愛します。 TerraformingはRubyのgemとして公開されているのでgemコマンドでインストールします。
$ gem install terraforming $ gem list | grep terraforming terraforming (0.7.0)
terraforming help
してサブコマンドの一覧が表示されればインストールできてます。
$ terraforming help Commands: terraforming asg # AutoScaling Group terraforming dbpg # Database Parameter Group terraforming dbsg # Database Security Group terraforming dbsn # Database Subnet Group terraforming ec2 # EC2 terraforming ecc # ElastiCache Cluster terraforming ecsn # ElastiCache Subnet Group terraforming eip # EIP terraforming elb # ELB terraforming help [COMMAND] # Describe available commands or one specific command terraforming iamg # IAM Group terraforming iamgm # IAM Group Membership terraforming iamgp # IAM Group Policy terraforming iamip # IAM Instance Profile terraforming iamp # IAM Policy terraforming iamr # IAM Role terraforming iamrp # IAM Role Policy terraforming iamu # IAM User terraforming iamup # IAM User Policy terraforming igw # Internet Gateway terraforming nacl # Network ACL terraforming nif # Network Interface terraforming r53r # Route53 Record terraforming r53z # Route53 Hosted Zone terraforming rds # RDS terraforming rs # Redshift terraforming rt # Route Table terraforming rta # Route Table Association terraforming s3 # S3 terraforming sg # Security Group terraforming sn # Subnet terraforming vpc # VPC Options: [--merge=MERGE] # tfstate file to merge [--overwrite], [--no-overwrite] # Overwrite existng tfstate [--tfstate], [--no-tfstate] # Generate tfstate [--profile=PROFILE] # AWS credentials profile
helpの出力を見るとサブコマンドはAWSのリソースごとに分かれているようです。
既存のIAMユーザをterraformingする
これ以降、tfstateファイルを色々いじるので、壊してしまった場合に備えて .terraform/*.tfstate
を適宜バックアップしておいて下さい。
$ cp -p .terraform/*.tfstate (backup_dir)/
それでは本題のIAMユーザのterraformingをしましょう。 IAMユーザ用のサブコマンドはiamuです。
terraforming iamu
コマンドを実行するとこんなかんじで標準出力に .tf
形式でaws_iam_userリソースのTerraformコードが生成されます。
$ terraforming iamu resource "aws_iam_user" "MasayukiMORITA" { name = "MasayukiMORITA" path = "/" } resource "aws_iam_user" "xxxx" { name = "xxxx" path = "/" } ....(以下、ユーザが順に出力される)
ユーザ数が多いと大量に出力されるので、ファイルにリダイレクトしておきます。
$ terraforming iamu > aws_iam_user.tf
次にterraformingコマンドに --tfstate
オプションを付けて、tfstateファイルを作ります。
$ terraforming iamu --tfstate { "version": 1, "serial": 1, "modules": [ { "path": [ "root" ], "outputs": { }, "resources": { "aws_iam_user.MasayukiMORITA": { "type": "aws_iam_user", "primary": { "id": "MasayukiMORITA", "attributes": { "arn": "arn:aws:iam::xxxx:user/MasayukiMORITA", "id": "MasayukiMORITA", "name": "MasayukiMORITA", "path": "/", "unique_id": "xxxx" } } }, "aws_iam_user.xxxxx": { ...(以下、ユーザが順に出力される)
このまま必要な部分を既存のtfstateファイルに手動で貼り付けてマージしてもよいのですが、terraformingコマンドに --merge
オプションがあり、既存のtfstateとマージした出力を生成できます。
$ terraforming iamu --tfstate --merge=./.terraform/terraform.tfstate > ./.terraform/terraform.tfstate.merged
ちなみにterraformingには --overwrite
というオプションもあるようなのですが、いきなり上書きされるのはちょっと怖いので、適当なファイルにリダイレクトしてdiffしてみて、どういう変更が入るかを確認した方が無難かと思います。diffは空白の出力の仕方が微妙に違って差分があるので -b
で空白差分を無視してます。
$ diff -bu .terraform/terraform.tfstate .terraform/terraform.tfstate.merged --- .terraform/terraform.tfstate 2016-05-25 13:39:27.000000000 +0900 +++ .terraform/terraform.tfstate.merged 2016-05-25 13:57:24.000000000 +0900 @@ -1,6 +1,6 @@ { "version": 1, - "serial": 38, + "serial": 39, "remote": { "config": { @@ -12,7 +12,8 @@ "path": [ "root" ], - "outputs": {}, + "outputs": { + }, "resources": { @@ -376,6 +377,838 @@ + }, + "aws_iam_user.MasayukiMORITA": { + "type": "aws_iam_user", + "primary": { + "id": "MasayukiMORITA", + "attributes": { + "arn": "arn:aws:iam::xxxx:user/MasayukiMORITA", + "id": "MasayukiMORITA", + "name": "MasayukiMORITA", + "path": "/", + "unique_id": "xxxx" + } + } + }, + "aws_iam_user.xxxx": { ...
mergeオプションで出力するとserialが1増えてます。このserialはtfstateが更新される度にインクリメントされる値で、複数人でtfsateを共有する場合に、この数値によってリモートとローカルでどちらが最新かを判断するのに使用されています。
diffの差分が問題なさそうであればであれば .terraform/terraform.tfstate
で上書きして、 terraform plan
を実行してみます。
$ cp .terraform/terraform.tfstate.merged .terraform/terraform.tfstate $ terraform plan
planの差分がなければ、既存のリソースをTerraform管理下に置くことができたといえます。
Terraformのリソース名を手動で調整したい
ここからは必須ではないですが、ネーミングルールなどを調整します。 Terrafomingで生成されたtfファイルですが、Terraformのリソース名にはAWSリソース名がそのまま使われています。
$ terraforming iamu resource "aws_iam_user" "MasayukiMORITA" { name = "MasayukiMORITA" path = "/" }
しかしながら、既存のTerraformリソースは公式マニュアル同様にスネークケースなネーミングにしていたのでこんなかんじにしたかったのです。
$ terraforming iamu resource "aws_iam_user" "masayuki_morita" { name = "MasayukiMORITA" path = "/" }
とりあえず上記のようにtfファイルを修正しますが、tfファイルを変更すると、対応するtfstateの記述も調整する必要があります。
resourcesのところを aws_iam_user.MasayukiMORITA
から aws_iam_user.masayuki_morita
に手動で編集して、serialをインクリメントして保存します。
{ "version": 1, "serial": 40, "remote": { "type": "s3", "config": { "bucket": "xxxx", "key": "xxxx" } }, "modules": [ { "path": [ "root" ], "outputs": {}, "resources": { "aws_iam_user.masayuki_morita": { "type": "aws_iam_user", "primary": { "id": "MasayukiMORITA", "attributes": { "arn": "arn:aws:iam::xxxx:user/MasayukiMORITA", "id": "MasayukiMORITA", "name": "MasayukiMORITA", "path": "/", "unique_id": "xxxx" } } }, "aws_iam_user.xxxx": { ...
※id、arn、nameの箇所はAWS上のリソース名なのでリネームしません。
terraform plan
を実行して差分が検出されなければOKです。
$ terraform plan
IAMグループメンバ定義からIAMユーザのリソースを参照する
これも必須ではないですが、IAMユーザもTerraform管理下になったのでIAMグループメンバの定義もIAMユーザを参照するように書き換えて明示的な依存を付けておきましょう。 依存があればリソースの変更時にTerraformが正しく変更順序の依存を解決してくれます。
resource "aws_iam_group" "group1" { name = "group1" } resource "aws_iam_group_membership" "group1_membership" { name = "group1" group = "${aws_iam_group.group1.name}" users = [ "${aws_iam_user.masayuki_morita.name}", "${aws_iam_user.xxxx.name}", ... ] } ...
以上で既存のIAMユーザをTerraform管理下に入れることができました。 要領が分かれば、IAMユーザ以外でも既存のAWSリソースをTerraform化できそうですね。
まとめ
Terraformingを使うことで既存のIAMユーザもTerraform管理下に置くことができました。最近は新規に作るAWSリソースは基本的にTerraformに寄せていってますが、既存リソースについても徐々にコード化していくつもりです。クラウドソーシングのクラウドワークス では、インフラもコードで書きたくて仕方がないエンジニアを募集しています。