Terraformのはじめかた1

InfrabbitではAWSの構成情報をTerraformで管理し、OSレイヤ以上をPacker+Ansibleで管理するのがメインになっています。

基本的にはOSレイヤ以上はチューニングを極力排して、仮想基盤レイヤの挙動で吸収できるもの(コストの多少の上昇を容認して)はそちらへ吸収させる、というのが基本方針です。

これは、OSチューニングによる効果の永続性がそこまで高くないので、仮想基盤レイヤの仕組みに食わせてしまったほうが汎用性や永続性の面で優位である、という判断からとなります。

さて、Terraformなんてどこでも常識レベルに近くなってきてしまっているので、大変今更感はあるのですが、そもそもTerraformってどうやって使うものなんだ、とかそういうとっても基本的な部分について取りまとめた記事、というのをあまり見たことがないわけです。

じゃあ書くか。という感じで。

まあTerraformなんて要するにツールなので、重要なのはレシピであって使い方じゃあないんですが。

Terraform自体はWindowsだろうが実行できるのですが、個人的にはLinuxから使うほうが楽です。Windowsからも使ってみたりはしたのですが、なんか使いにくい。エディタと実行の行き来にコマンドラインを開いたりエクスプローラ開いたりするのはちょっとやりにくい。いちいちマウスに手を持っていくのがめんどくさい。エディタにtfファイル片っ端から食わせてタブ開きまくって、というのは悪くはないんですが、パス通したり、バージョン管理したり、というのも考えると素直にLinuxで良くね?という感じになってしまいます。VSCodeとかでうまいことやれるといいんですけど。
※だれかHCLのシンタックスハイライト書かないかなー…w

というわけで、Linuxです。WSLで構いませんので、Windows上でもUbuntuでも走らせておいてください。別にVMでも構いませんが、WSLのほうが起動さっくりしてるので、最近はWSLにシフトしてVagrantをほとんど使わなくなりました。

OS別環境をテストしたければEC2起動しちゃったほうがめんどくさくないですし。

WSLでUbuntuでも起動したら、まず何も考えずにtfenvとdirenvでも入れておきましょう。packerも使う予定があるならpkenvも。direnvとかpackerは適当に記事でも見てください。どれも似たようなもんです。tfenvだけとりあえず書いておきます。

Terraformは非常に活発にバージョンの更新が起こるツールで、一時期は数日目を離したらバージョンが二つ三つ上がってる、なんてこともありました。というか今もv0.12系にリファクタされたのでしばらくは活発なはずです。
で、そんなTerraformをいちいち公式サイトに行ってOld Version拾ってきて、とかやるのはとても面倒くさい非効率的ですので、コマンド一発で済ませられるようにしましょう、というのがtfenv利用の目的です。もちろんコード毎で異なるterraformバージョンを実行できる、というのも大事です。作者さんには感謝。

というわけで、

sudo apt install git
git clone https://github.com/tfutils/tfenv ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bash_profile
. ~/.bash_profile

何も考えずにcloneしてきます。とりあえずtfenvにパスを切ります。

rbenvみたいなもんなので、tfenvで好きなだけTerraformをインストールしましょう。まずは最新版。

tfenv install latest

v0.12系の最新版が入ってきます。v0.11系も必要なら

tfenv install 0.11.14

0.11.15-ociは内部用バージョンだ、と言及されており、v0.11の最終版は0.11.14です。バージョンの一覧はlist-remoteサブコマンドで表示されます。また、インストール済み(使用可能)なバージョンはlistサブコマンドで見ることができ、useサブコマンドで使用するバージョンを指定できます。

次に、適当にディレクトリを作ります。まあべつにGitリポジトリ用意しても構いません、というかそれが良いかとは思いますが、とりあえずVCS管理になくても構いません。このディレクトリが作業ディレクトリになります。うちではさくっとCodeCommitにぶん投げるので、この時点ではただのディレクトリ作っただけ、とかですね。

mkdir Project-name とかしちゃえばよいです。ぶっちゃけなんでも構いません。ファイルさえ置ければ。
※このファイルの置き方とか、ディレクトリ構成についてはベストプラクティスとかあったりしますが、正直な話自分が使いやすいかどうかだと思います。

さて、TerraformもansibleもCloudFormationもなんでもそうですが、いきなりすべてをIaCに落とし込もうとするとたぶん絶望しか感じません。果てしなく大量のクラウドリソースを相互関係を把握しながらコード化していくなど、普通に嫌です。TerraformingとかFarmerとか使いたくなりますが、これはこれで後から見れるコードかと言ったらどうせリファクタリングが必要になり、結局全部書き換えていくとかし始めるので、現状をとりあえずコード化しておく、以上の意味はあまりありません。

コード化した結果、コードが読みにくかったら生産性は下がるのです。どうやったって台数が多いならともかく少数のEC2管理するのにコード書いてたら確実に工数増えますし、ましてそのコードが読みにくかったら継続メンテも絶望しか生まないので、迅速に粗大ごみになりやすくなってしまいます。
数台作るだけなら圧倒的に手でやったほうが速く、IaCにするメリットは継続的に管理できるか、という一点に集約されるのです。つまり、イベント用で一週間だけ使うインスタンス!って言われてスクラッチで書き起こしてTerraform使ってたら馬鹿です(流用コードがあってそっちのほうが速いならともかくですが)。
※慣れてるとだいぶ早くはなりますが…。

新規開発で使う、というケースならゼロから書いてもよいですが、まあまずは現環境の落とし込み、とかが需要としては多いんじゃないでしょうか。そして、その現環境の維持管理について、いきなり全部IaCにしよう!とか意気込まないほうがいいです。環境がデカければデカいほど、絶望感しか生みません。

そこで、Infrabbitとしては「たまに変更したりするけど管理がめんどくさい」とこから手を付けましょう、というのがおすすめですね。

代表格としてはSecurity Groups。固定IPでフィルタしてssh制限するね!とかの要件なのにクライアントになぜか居る動的IPとかあるあるではないですかね。こいつがちょいちょいIP変わった!とか言ってきては直した結果、Security Groupのsshに消していいのか悪いのかわかんないIPがたんまり残ってる。とか。

逆にVPCとかはビルドしてからあまり変化は起こしません。というか起こしたら全リソースに影響が生じうるので、やりたくありません。EC2なんかは管理に入れたいです。というように、台帳管理していく場合に、よくいじる場所、をまずは中心に据えましょう。
これは考え方としては別に、あまり変化しないものを先にコード化していく、というのも実はOJTとしてはアリだったりはします。

あとCloudFrontとかのCDN使っててセキュリティ上CDN以外からの接続を受け付けないSecurityGroupの管理とかはTerraformにやらせたほうが良いです。いちいちCloudFrontのIPゾーンをLambdaでとってきて動的更新とかするくらいなら、Terraformに全部やらせればよいです。

xmllintとかjqとかyamllintとかあったら便利ですがなくてもどうとでもなります。ただ、どうせaws cliは使うのでjqは入れておくと便利です。

さ、というわけでまずAWSのコンソールにログインしてください。Terraformの実行用ユーザーを作りましょう。

IAMでユーザーを作成し、AdministratorAccessでも当てておいてください。プログラム実行なのでCredentialsの発行も忘れずに。EC2上で実行するならCredentials使わずにIAMロールで済ませましょう。というかそっちのほうがおすすめですが、まずは手元実行で始めましょうね。

IAMユーザー作成したら、先ほどの作業ディレクトリに戻ります。Credentialsを~/.aws/credentialsに書き加えます。aws configure –profile=Project-nameとかでCLIでやってしまえば楽ですね。
作業ディレクトリでおもむろにvi terraform.tfとかnano terraform.tfとかでファイル作成をおっぱじめます。

terraform {
 required_version = "~> 0.12.0"
}
provider "aws" {
   version = "~> 3.0"
   region = "ap-northeast-1"
   profile = Project-name
}

最初はこんなもんでいいんじゃないですかね。yamlじゃないのでインデントにも気を使わなくていいです。jsonでもないので、配列末尾にカンマつけても怒られません。コメントも#で書けます。なんて素敵。json死ねばいいのに。厳格すぎるんじゃボケが!

ここまで終わったらとりあえずterraform initを実行します。tfenvで複数のバージョンを入れた方は、tfenv useコマンドで使うterraformのバージョンを選びましょう。作業ディレクトリにバージョンナンバーだけを書いた.terraform-versionというファイルを作っておけば、勝手にそれを使ってくれるようになります(インストールされていなかったらインストールも勝手にします)。

echo 0.12.18 > .terraform-version

とかやっておけば安心ですね。

そしたら、おもむろにマネジメントコンソールで「管理したいセキュリティグループ」を一つ、開いておきましょう。

さて、ここからリソースを作成します。とりあえずSecurityGroupの定義を書いていくファイルを何か作りましょう。securitygroup.tfとかでいいです。terraformは作業ディレクトリにある.tfファイルを全部読むので、ファイルごとにいろいろリソース別で分けるほうが私は好みです。

また、関連性のあるリソースはまとめておく、実行を分けたいものもディレクトリを切って分離する、など細かい話もあったりはしますが、まずは慣れないと話が始まりません。

resource "aws_security_group" "ssh-allow" {
  name = "SSHAllowed"
  vpc_id = "vpc-xxxxxxxxxxx"  <- VPCのVPC ID
  description = "SSH Allowed from anywhere"
  ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = [ "0.0.0.0/0" ]
    ipv6_cidr_blocks = [ "::/0" ]
   description = "SSH Allowed anywhere"
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = [ "0.0.0.0/0" ]
    ipv6_cidr_blocks = [ "::/0" ]
    description = "Egress Anywhere"
  }
  tags = {
    Created_By = "Terraform"
    Terraform = true
    Name = "SSH Allowed"
  }
}

とりあえずはまあこんな感じでしょうか。SSHへの接続許容フィルタですね。Terraformは構成管理ツールですので、マネジメントコンソール上からの作成と少し変わる部分があります。

マネジメントコンソールからの作成では、セキュリティグループを作成すると自動的にデフォルトとして出ていくパケットをすべて許可する、Egress Anywhereの設定が埋め込まれますが、Terraformにおいては記載されている内容がすべてです。つまり、デフォルトで作成される、というものは原則ありません。
※この動作はAWS CLIに基づいたものです。マネジメントコンソールが親切でやってくれているだけで、APIとしてはこちらの動作が正しいものです。

そのため、Ingressとしての22番ポートの解放のほか、Egressとしての設定も加えておく必要があります。これがないと、出ていくパケットを許可しないセキュリティグループとなります。
また、タグはできるだけ付与しておきましょう。あとあと見返したときに困りません。セキュリティグループのリソース設定におけるNameとはマネジメントコンソール上でグループ名として表示されるカラムであるため、マネジメントコンソール上での名前のカラムにも何か表示したいなら、タグのNameキーをセットしておく必要があります。

さて、これだけではただ固定的に同じセキュリティグループが一つ作られるだけなのですが、まずはこれでterraform planを実行します。こちらは実行計画にあたります。同時にTerraformの文法チェックも行われます。

ずらずらっと結果が出てきますが、頭に+がついてるものが作成されるリソースです。-が付くものは削除されるリソース、そして~が付くものは変更されるリソース、ということになります。
ー/+と表示されるのは削除と作成が行われるリソースです。通常は新しいリソースが作成され、次に削除が行われます。作成に失敗すると削除は行われません。
この動作は削除を先に行わないといけないケースにおいて問題になる場合があるので、そういったリソースは削除を先に行うように指定することがあります。
※Route53のホストゾーンとかがそういったものにあたります。同名リソースの存在を許容しないためです。

さて、問題なさそうなら実際の作成を実行します。terraform applyを実行することで、まず実行計画が行われ、実際に実行するかどうかの入力待ちとなります。
ここで「yes」をタイプすることで、実行が行われます。

Terraformの利用にあたり、この実行計画の内容を理解できる、ということがまずは必要なこととなりますが、この実行計画は変更が生じるリソースの一覧表示であり、しかも内容はただTerraformのパラメータ一覧であることから、結局これを理解できる=Terraform書ける、ということと同義です。
※ステート記録ファイル(構成情報記録)であるtfstateファイルもほぼ同じようなものですが、こっちはもっと読むのが面倒くさいです。

実際に実行してみて、マネジメントコンソールから該当のセキュリティグループが生成できていることを確認できるでしょうか。
さてさて、これだけではただリテラルリソースが作られるだけです。尤も、可読性という意味では実はこれが一番読みやすいといえば読みやすい形式であり、関数や変数を用いたプログラマブルな書き方をすればするほど、実は可読性が低下していく仕様です。
※そのかわり、おなじものをたくさん書く、とかコードが長くなる、というのを防げますが。