トップ 最新 追記

2017-12-24 [長年日記]

_ terraform import heroku_appはできたけど…使い方が悩ましい

まとめ

  • とりあえず terraform init と import の使い方が分かった
  • terraform init 超重要
  • terraform v 0.11.1 で heroku_app と heroku_addon の import できた
    • 安易に import からやれば練習が早くできると思ったけどそういうわけじゃなかった
  • import はあくまで .tfstate の更新であり、これと矛盾しない .tf を書いておかないと diff が plan と apply を阻害する
  • 話題が少ないのでもしかしてと思っていたけど Heroku と Terraform は相性が悪いというか、考え方が被ってて混ぜるな危険かも
  • Heroku は heroku コマンドとか API か wrap した何かを用意した方がよさげ

目標

  • Terraform を CI で回してクラウドインフラを管理

以下、試してみた。

Provider を使うには plugin のインストールが必要

  • Provider が本体から plugin 管理されるようになった ( v 0.10 ) 
  • init した際に .tf を認識して自動的に plugin をインストールしてくれる
  • 逆に言うと import で始める前に provider の情報を理解していないといけない

↑ つらい

provider.heroku: no suitable version installed
 version requirements: "(any version)"
 versions installed: none

みたいなエラーが出たら init してあげるとよい。

initしたディレクトリ/.terraform

以下に plugin が入る。

credentialの与え方

基本的な resource 定義

HCL 全般のメモではありません。以下の import に必要な分だけ。

resource <type> <name> {
  id   = <id>
  name = <name>
  ...
}
  • <> で囲んだ部分は基本的には文字列
  • type は Provider の中で定義されている。
  • type と name の組み合わせは unique である必要がある
    • 例えば Heroku の app について扱いたければ resource type は "heroku_app"

Configuring Resources - Terraform by HashiCorp

import

terraform import TYPE.NAME ID

で import を行う。この際、

  • TYPE は Provider で決まっているが NAME は任意に設定可能
  • ID も Provider によって決まる

ので、NAME と ID を合わせておくと管理しやすそう。

Heroku

  • email と api key で動く
  • plugin があれば .tf を元に import して .tfstate を作成することはできる(v0.11.1)

terraform の heroku_app resource は dyno の定義などはできないみたい。おまけに取得した環境変数が全部入りで、add-on の定義によって自動でセットされる場合は .tfstate とぶつかっちゃう。

調べても話題が少ないのでもしかしてと思っていたけど、どうも Heroku と Terraform は相性が悪いというか、考え方が被ってて混ぜるな危険なのかもしれない。

※そうなると Heroku を含むすべてのインフラを自動管理しようと思うと Terraform 一本やりというわけにはいかず、工夫が必要。

heroku_app

heroku_app resource では heroku の app name と terraform resource の id は同じになる。

example では

resource "heroku_app" "default" {
  name   = "my-cool-app"
  region = "us"
}

こんな風になっているが、ここで resource syntax 上の name と argument の name は一致させておいた方が管理しやすいように思う。例えば

resource "heroku_app" "lt-timer" {
  name    = "lt-timer"
  resgion = "us"
}

みたいな感じにしておくと

terraform import heroku_app.lt-timer lt-timer

で import できる。コマンド打っているときは重複に見えてアレだけど、変に名前を付けて管理を複雑にするよりはマシなのではないか。

Heroku: heroku_app - Terraform by HashiCorp

ただし、add-on の追加によって自動設定される環境変数の値はともかく、リリース情報とかまで全部環境変数に乗って取得できてしまっている。例えば以下のような感じ。

"all_config_vars.%": "23",
"all_config_vars.HEROKU_RELEASE_VERSION": "vxxx",
"all_config_vars.HEROKU_SLUG_COMMIT": "xxxxxxxxxxx",
"all_config_vars.HEROKU_SLUG_DESCRIPTION": "Deploy xxxxxxx",

これはさすがに困る。仮に .tfstate をもとに次回以降の apply をするのであれば、もはや deploy すらできないことを意味するのではないか。一方で

heroku apps:info --json

では普通に取得できる Dyno の情報は .tfstate に乗ってこない。

 "dynos": [
   {
     "attach_url": null,
     "command": "bundle exec unicorn -p $PORT -c ./config/unicorn.rb",
     "created_at": xxxxxxxx,
     "id": xxxxxxx,
     "name": "web.1",
     "app": {
       "id": xxxxxx,
       "name": xxxxx
     },
     "release": {
       "id": xxxxxx,
       "version": xxxx
     },
     "size": "Standard-1X",
     "state": "up",
     "type": "web",
     "updated_at": xxxxxxxxx
   },
   {
     "attach_url": null,
     "command": "bundle exec sidekiq -C config/sidekiq.yml",
     "created_at": xxxxxx,
     "id": xxxxxxxx,
     "name": "worker.1",
     "app": {
       "id": xxxxxxxxx,
       "name": xxxxxxxx
     },

それでいて heroku_dyno という resource type があるわけでもない。つまり Dyno の管理は Terraform ではできないということになる。

以下のように Heroku と AWS S3 を使ったよくあるサイトを Terraform で作るよ、という情報はあるのだが、継続してアプリを成長させていく際のインフラ管理を Terraform 一本でやるのは難しそうに見える。

Using Terraform to Build Heroku/S3 Site - Ficksworkshop

heroku_addon

id は add-on の識別名 ( papertrail-xxxxx-xxxxx など ) になる。import の際に必要なだけだろう、たぶん。これを取得するには Web コンソールか heroku コマンドなどが必要。

heroku apps:info --json

とか。

Heroku: heroku_addon - Terraform by HashiCorp

AWS IAM Role And Policy

IAM Roleの権限不足なのか、とりあえず今回は確認できなかった。

今後

  • Herokuの管理の自動化はterraformよりはherokuコマンドやAPIを真面目に使う方がよさそう
    • 当然本家なのでなんでもできるんだけど、状態を把握しやすくし、diff だけで副作用が分かる、って状態じゃないのが課題。
  • AWSの方の準備を進める
    • 自動化用の IAM ユーザーと credential を作成
    • いったん静的サイト方面の resource を中心に
    • Terraform 用の backend bucket を作成

以上のようなところが今後の課題っぽい。自動化しようと思ったら backend を remote に用意しておかないと複数人で回す際に .tfstate が一本化されずに安定動作しないということらしい。

※ CI で回す気まんまんなんだけど、さすがに CI の cache に期待するのはスジが悪いだろうな…。

参考


2017-12-25 [長年日記]

_ HCL ( HashiCorp Configuration Language ) は他の言語の実装もあるんだな

先日初めて terraform import のために .tf を書いたが(このインキー感がすごく微妙)、当然何回かハマったのでその際に使ったものをメモとして残しておく。

まとめ

  • .tf というか HCL ( HashiCorp Configuration Language ) がカギ
    • HCL は JSON 完全互換で Terraform だけじゃなくて HashiCorp 製品を扱ううえで大事
  • HCL の仕様はオープンなので実装はいくつかある
  • repl が欲しければ gem も使える
  • もしかして独自のツールの設定文法を HCL にするのもアリかも?

使ったもの

  • terraform plan && validate

何はともあれコレ。

  • irb + rhcl

複雑な構造を持つ variable や resource を扱う場合、HCL の書き方がよく分からなくなるので、ムカついて調べたら HCL の Ruby 実装である rhcl を見つけた。Ruby の強力な repl と組み合わせればとりあえず parse できるかどうか、parse したらどうなるのかは分かる。repl に乗ってるだけで安心する人向け。

もしかして

今後何かのツールを書いたり使ったりする際に

  • JSONはコメントが書けない!
  • でも独自文法を実装するのもダルい!

みたいな場合に HCL を使ってみたりするのもいいのかもしんない。

参考


2017-12-26 [長年日記]

_ terraform importで差し当りS3とCloudFrontの情報を引っこ抜けた&気づいたこと

前回の heroku の import の続き。

まとめ

  • Terraforming は本当にすごい。超簡単。.tfstate だけでなく .tf も出力できるし、IAM の KEY さえあれば特に何も書かなくてよい。
  • 逆に terraform import はなかなか大変。(詳細は前回のterraform import heroku_appはできたけど…使い方が悩ましい参照。)import はまだおまけ的な感じで、terraform が正しく動作するために .tf を書き、これを validate すると required な option がボロボロ出てくる。
  • 対応サービスの数は Terraform の方が多いが、ものによっては Terraforming でしか対応できていなかったりするので、しばらくは両方知っておくのがよさそう。
  • AWS Certificate Manager は承認のプロセスが挟まるので完全自動にはできないんだなぁ
  • AWS S3 の Bucket の Policy は使い方別に整理した方がよさげ

Terraforming by dtan4

ちなみに似たツールとして terraformer というものがあったが、これは terraforming の fork でかつ実運用もしていない、対応 provider も少ないものだった。

S3 + CloudFrontで配信する静的サイトが複数あるものとする

すると必要なものは

  1. S3 ( aws_s3_bucket )
  2. IAM Policy ( aws_iam_policy_document ) / IAM User Policy
  3. CloudFront ( aws_cloudfront_distribution )
  4. ACM ( AWS Certificate Manager )

いったん Log および Metrics は置いておく(CloudWatch とか)。また、ACM については承認プロセスの関係上、素直な方法では自動化できないので除外とする。というわけで 1 から 3 について試してみた。

aws_s3_bucket

特に難しいことはなかった。

resource "aws_s3_bucket" <bucket_name> {
  bucket = <bucket_name>
}

こんな感じで bucket_name で統一して、

terraform import aws_s3_bucket.<name> <bucket_name>

ちなみに

terraforming s3

にすると何も考えずに権限のある S3 の情報が全部ぶっこぬけます。めっちゃ楽。

AWS: aws_s3_bucket - Terraform by HashiCorp

aws_cloudfront_distribution

これはいろいろ required な attribute があって面倒くさかった。何がつらいって terraforming が対応していないこと。どうにか調べて試して動くようにしなければいけない。結局 Terraform の Provider と Resource の情報は自分で調べられないといけない。

resource "aws_cloudfront_distribution" <distribution_cname> {
  default_cache_behavior {
    allowed_methods  = []
    cached_methods   = []
    forwarded_values = []
    default_ttl      = 0
    min_ttl          = 0
    max_ttl          = 0
    target_origin_id = ""
    viewer_protocol_policy = ""
  }
  viewer_certificate {}
  enabled = ""
  origin {
    domain_name = ""
    origin_id   = ""
  }
  restrictions {
    geo_restriction {
      restriction_type = ""
    }
  }

  (logging_config)
}

として

terraform import cloudfront_distribution.<distribution_cname> <distribution_id>

みたいな感じ。import は

terraform import TYPE.NAME ID

が基本だが、NAME として理解しやすいのは Distribution ID ではなく CNAME の方なのではないかと思う。CNAME を指定していないなら Distribution ID でよいと思う。

AWS: cloudfront_distribution - Terraform by HashiCorp

aws_iam_user

これは簡単。

resource "aws_iam_user" <user_name> {
  user_name = <user_name>
}

terraform import aws_iam_user.<user_name> <user_name>

AWS: aws_iam_user - Terraform by HashiCorp

aws_iam_user_policy

今度は terraform v 0.11.1 が 2017-12-26 時点では import に対応していなかったので terraforming で。

terraforming iamup

やってみて気づいたこと

IAM Policy の適用ポリシー (言ってみたかっただけ :-) が揺れている。

これについては後日。

まぁ、これまで WebUI であっちこっちに行くことでよく分からなくなっていた、立ち止まるのが面倒で放置していたこういう状況の俯瞰のためにも terraform import あるいは terraforming は使えるってことだなーと思った。

参考


2017-12-28 [長年日記]

_ IAM Policyの作り方に悩む - とりあえずS3 + CloudFront静的サイトを考える -

まとめ

  • AWS の Policy の種類が復習できた
  • AWS の Policy 関連用語と Terraform 用語の Resource, Data Source 用語との紐付けができた
  • 静的サイト向けの Policy に集中して考えた

操作から切り離して概念を整理しやすいのでドキュメントベースになるとありがたい。

AWS S3 の Bucket を読み書きできるポリシーとその適用方法が揺れていた

これまでに存在しているポリシーが以下のようなひどいことになっていた。

  1. S3 の Bucket Policy で Role と Action を割り当てているもの
  2. S3 の読み書きに特化した IAM User を作り、そのインラインポリシー ( IAM User Policy ) 側に Bucket ( arn ) 情報が書かれているもの
  3. IAM User Policy Attachment ( 未適用 )

図にするとこんな感じ。

S3 Bucketの読み書きのPolicy設定方法が複数ある様子

※ 上の図では AWS 用語を枠線で囲んでおいて、Terraform 用語を無地の上にそのまま置いている。そもそもこれまでこの辺の用語の理解が曖昧だったことに今回ほんとに気づかされている。(どうしても触る機会の少ないインフラ周りがおざなりになってしまう。)今回気づいたのは Bucket でも User でもない真ん中の Managed-Policy を aws_iam_policy で管理するようにしたらどうか?という話。

初期は 1 でやっていた。この頃の目的は静的サイト向けの Bucket ではなく、Web アプリの無限ストレージとして使い始めた。まだ AWS 自体がよく分かっておらず、読み書きを割り当てている IAM User の概念もボンヤリしていた。

で、いくつかそういうアプリが増えてきたときに Bucket 作って Policy 開いて、みたいな操作がダルくてイヤになって、Policy だけ別に作って Bucket に適用した方がよいのでは? と思い始めて 2 の形になった。

しかしこれは今にして思うと失敗だった。たぶん当初 3 を意識していたがやはり操作が面倒くさくて 2 の形に流れてしまった。

しかもこの形での指定が妙に複雑になっている部分がある。確か

  • ログイン可能な IAM User が手動で読み書き可能な Bucket を確認できるように
  • Rails + fog で bucket を覗く際になぜか指定が足りないと怒られた

辺りが理由と記憶しているが、これは静的サイトを CI で deploy するようにという今回の目的のためには不要な配慮になるはず。

解決案

静的サイトに限定して考える。

上のポリシーは Web アプリのストレージとしての Bucket のポリシーと混ざっているのがそもそもよくない。

AWS のマネージメントコンソールを手作業でぽちぽちしていくやり方と Terraform を共存(怖いけど)させる場合、恐らく 1 の形に近づけて Policy を Data Source で定義したうえで、せめてコピペせずに Bucket ごとに 個別に割り当ててあると直感的に分かりやすいと思う。(Bucket のプロパティを覗けばどういう状況か分かるので。)

ただ、ちゃんと自動化するなら第4の方式、

  • S3 Bucket
  • IAM User
  • IAM Policy
  • IAM Policy Document

をそれぞれ独立して定義した上で IAM User に IAM Policy をアタッチしておいて、最終的に IAM Policy Document だけをメンテすればよい感じにする方が .tf も .tfstate も diff が小さくなると思う。

少なくとも IAM User Policy の側に Bucket (正確には arn)を列挙してしまっている現状は、手作業での手間は若干減ってはいる(少なくとも bucket ごとに丸ごと policy をコピペしたうえで書き換える必要はない)が、自動化前提ではアホくさい。

User の関心事は 1) シンプルに静的サイトの deploy であり、2) deploy は手作業でなく CI/CD サービス経由であり、3) ログインは不要であるならば、 ユーザーの知っているべき情報は特に変化はない

手作業を捨てるならコードは DRY にして設定ファイルとしての HCL の可読性に寄せる方がスジがよいと思われる。

要確認

そもそも CloudFront 経由でしか publish しない方針なら、実は S3 の Read Only Policy って必要ないのでは?

参考