Terraformで始めるInfrastructure as Code 〜CircleCIを添えて〜
本記事はOthloBlogからの転載です。
おはこんばんちは!@ジュジュです!
GCPの発表を見ているとコンテナという側面に対しては頭1つ抜けているなぁということを感じますね。サービスメッシュを実現する有名なOSSであるIstioはようやくバージョン1.0がリリースされたような新しいツールで、これをデフォルトで突っ込む発表というのは開発元であるということを加味してもとても挑戦的で読んでいてワクワクしたことを覚えています。
今回は、インフラをコードにして管理するInfrastructure as Code(IaC)をTerraformというツールを利用して実現する方法を紹介したいと思います。ついでにCircleCIを利用して継続的インテグレーションを実現したいなと思います!
対象読者
記事の内容
- Terraformingを利用してAWSの既存インフラをコード化します。
- S3インスタンスを利用してファイルを管理することで複数人、CIに耐えうる環境を作成します。
- CircleCIを利用して継続的インテグレーションを行います。
本記事でやらないこと
- あくまで最速でのTerraform, CircleCIの利用を目指すので、細かいプラクティスについては深く考えません。
Infrastructure as Code (IaC) とは
オライリーのInfrastructure as Codeについて書かれた書籍によると
Infrastructure as Codeは自動化、バージョン管理、テスト、継続的インテグレーションといった、ソフトウェア開発のプラクティスをシステム管理に応用するための方法論です。
ほうほう。つまり「インフラや構成管理にソフトウェアならではの開発手法を取り入れること。」ということですね。
そして狭義にはインフラの構成管理部分のみを含むようで、今回紹介するツールであるTerraformや、AWSのCloudFormation、AzureのAzure Resource Managerなどが挙げられます。広義にはOSのイメージ、ミドルウェア管理等も含まれるようでツールとしてはAnsible、Puppet、Itamaeなんかが挙げられます。狭義には後者のソフトウェア群はConfiguration as Codeと呼ばれるようですね。Dockerやrktというようなコンテナも広義にはIaCに含まれるようです。本記事ではTerraformを紹介するためにインフラやシステム管理部分のみを取り上げていきます。
Infrastructure as Codeのメリット
IaCをするメリットとしては
- ソフトウェア開発プロセスと同様の手法を取り入れることでの生産性上昇。
- OpenStackやパブリッククラウドを利用している場合についてはぽちぽち作業からの解放。
- インフラの見通しが良くなり、属人化が解消。
- 権限の管理が見える化される。
- アプリケーションエンジニアに不要な権限を与えずともインフラを制御できる。
- 同一環境を複数展開するのが容易になり、開発が高速になる。
というようなことが挙げられます。初期にぽちぽちとインフラを構築してしまい、その人しかインフラを理解できなくなってしまうというような話は割と耳にします。ある程度サービスが大きくなると行ったほうがいい作業なのかもしれません。
Terraformとは
TerraformはGo言語で記述されたインフラオーケストレーションツールです。AWS、GCP、Azure、Cloud Oceanといったパブリッククラウドはもちろん、OpenStackのようなプライベートクラウドに対しても利用することが可能です。Terraformを利用することでどんなIaaSをどのように利用してどんなセキュリティ設定なのかというような部分をコード化して管理することが可能です。
なお、Terraformではtfファイルとtfstateファイルの2種類のファイルを利用します。下記に簡単な解説を書いておきます。
- tfファイル : 「こんなリソースを作って!」という式を記述するファイル。
- tfstateファイル : 結果作成されたリソースの現状を保存するファイル。
Terraformと同様のツールにAWSのCloudFormation、AzureのAzure Resource Manager、OpenStack Heat等が存在しますがマルチクラウドも考慮したロックインを回避できるような解答としては現状Terraformが優勢なのかなと思っています。もちろん各パブリッククラウド専用のツールを利用するメリットも存在し、現状ではTerraformでやりにくい既存インフラをコード化する際には専用ツールを利用する方がお手軽なことも多いです。
インストール方法については各プラットフォーム向けにバイナリが配布されているのでこれに対してPATHを通すといいです。Macであればbrew install terraform
で問題なく動作します。運用としては自分はAWSのアクセスキーを環境変数に埋め込んで利用しています。これについてはtfファイルに書いたり、いろんな文化があるのかなと思います。
Terraformingとは
AWSの既存リソースをTerraformの形式に落とし込むためのツールです。Rubyで記述されていて、OSSとして開発されています。
Terraformでもv0.7から terraform import
という既存インフラをTerraformの形式に合わせてコード化するコマンドが追加されているのですが、2018/08時点ではインフラの現在の状態を記述するtfstateファイルは出力されるが、インフラを宣言的に記述するtfファイルは出力されません。随時作成中ということらしいです。
今回は最速でTerraform化することがコンセプトなのでtfファイルの作成にはTerraformingを利用して、tfstateファイルの作成には公式 terraform import
を利用することとします。なお、Terraformingは基本的にAWSにしか対応していない他、対応サービスについても terraform import
より少ないというデメリットが存在します。その辺りに関してはさすが公式のサポートというところでしょうか。terraformingの開発者自身がブログで述べていますが安定感が違います。
Terraformingに関してはgemを利用してインストールするか、これもMacであればbrew install terraforming
ができたりします。Homebrewはサポートがきめ細かいなと思います。多分ArchLinuxのYaourtあたりにもありそうですが。
TerraformingとTerraformを使って既存インフラをコード化する
ここから既存インフラをTerraform化していきます!既存インフラといってもかなりAWSの作成時のデフォルトのものをとりあえずターゲットにしています。
ここからの作業については
export AWS_ACCESS_KEY_ID=xxxxx export AWS_SECRET_ACCESS_KEY=yyyyy export AWS_REGION=zzzzz
が.bashrc
や.zshrc
に記述されているということを前提に進めます。
ちなみにTerraformでマルチリージョン対応したい方については下記を参考にするといいと思います。
Terraformingを使ってtfファイルを作ってみる
Terraformingではterraforming リソース名
という方法でtfファイルを作成します。一度やってみましょう。
➜ terraforming git:(master) ✗ terraforming vpc resource "aws_vpc" "vpc-6328d51b" { cidr_block = "172.31.0.0/16" enable_dns_hostnames = true enable_dns_support = true instance_tenancy = "default" tags { } }
なんかそれっぽいのができたんじゃないでしょうか。これをファイルにします。なお"vpc-6328d51b"の部分に関しては名前なので好きに意味のある名前に変更すると良いと思います。
今回具体的には下記のコマンドを流し込みました。
terraforming vpc > vpc.tf terraforming sg > security_group.tf terraforming sn > subnet.tf terraforming rt > route_table.tf terraforming ec2 > ec2.tf
おおー!なんとなくリソースがコード化されているのを感じますね。何がやられているのか気になる人は中身をみてみるといいんじゃないでしょうか?
Terraformを使ってtfstateファイルを作ってみる
次にTerraformの設定ファイルを作る必要があります。あらかじめ、terraform --version
を叩いて出力されるバージョンを確認しておきます。次にTerraformの設定ファイルにするterraform.tf
を作成します。下記データを流し込んでください。一応自分の手元ではTerraformのバージョンが11.7であったためそれを例にとって解説を行います。
terraform { required_version = ">= 0.11.0" }
ここでTerraformの初期設定とtfstateファイルの作成を行います。
terraform import
コマンドについてはterraform import リソース名称.つけた名前 リソースのID
という方法で利用します。上にあるVPCの例を取り上げるとterraform import aws_vpc.vpc-6328d51b vpc-6328d51b
となります。
ちなみに自分の場合は以下のようなコマンドになりました!(色々とダダ漏れですが該当リソースは全て削除済みです。)
terraform init terraform import aws_vpc.vpc-6328d51b vpc-6328d51b terraform import aws_security_group.vpc-6328d51b-default vpc-6328d51b-default terraform import aws_security_group.vpc-6328d51b-default sg-25112d55 terraform import aws_subnet.subnet-ea92c9b0-subnet-ea92c9b0 subnet-ea92c9b0 terraform import aws_subnet.subnet-88076dc3-subnet-88076dc3 subnet-88076dc3 terraform import aws_subnet.subnet-e3bbf29a-subnet-e3bbf29a subnet-e3bbf29a terraform import aws_route_table.rtb-e037ea9b rtb-e037ea9b terraform import aws_instance.Terraform i-0201a41fd15077ef2
結果として200行を超えるtfstateファイルが作成されます。
ここからtfstateが本当に正しいのか確認します。dry runを実行する方法としてTerraformにはterraform plan
というコマンドが用意されているので、これを実行します。ちなみに僕の場合は差分がでたので、手動で消しました。
今回遭遇したものは下記です。
- aws_security_group_ruleがterraform管理対象じゃないのにtfstateに存在する。
- revoke_rules_on_deleteの値が設定されていないのに"false"に変更される。(Terraformのデフォルトがfalseであるため記述がないとfalseと扱われる。)
これを粛々と修正しました。流石に全自動というわけにはいかないようですね。。。とはいえ、この儀式を超えてterraform plan
の差分がなくなればもう無敵と言って差し支えないでしょう!!!
tfstateファイルをS3で管理する
次にこのtfstateファイルってどこで管理するんだみたいな話になってきます。だって、これってAWSリソースの状態を生で表しています。ローカルで扱うと誰かが変更したら他のマシンでtfstateファイルが編集されません。さらにGitで管理すると場合によってはパスワード等の情報がリポジトリに上がってしまいます。これは辛い。というわけでS3にファイルを保持するように設定します。具体的には先ほど作ったterraform.tf
ファイルを下記のように変更します。
terraform { required_version = ">= 0.11.0" backend "s3" { bucket = "バケット名" key = "terraform.tfstate" region = "us-west-2" } }
変更したらterraform init
し直してください!
その後、terraform.tfstate
をS3にアップロードしましょう!
これでローカルのterrform.tfstate
, terraform.tfstate.backup
はいらない子になるほか、99.99999999999%の可用性に大事なファイルが守られます。いい話。
terraformを使ってインフラの内容を変更してみる
はい。ということでようやくTerraformの本領を発揮できますよね。リソースを変更してみましょう!
今回は一台だったEC2をおもむろに増やしてみましょう。ec2.tf
の内容をまんまクローンしてPrivateIPが競合しないようにして、名称を変更します。
resource "aws_instance" "Terraform" { ami = "ami-a9d09ed1" availability_zone = "us-west-2b" ebs_optimized = false instance_type = "t2.micro" monitoring = false key_name = "Terraform" subnet_id = "subnet-e3bbf29a" vpc_security_group_ids = ["sg-25112d55"] associate_public_ip_address = true private_ip = "172.31.16.123" source_dest_check = true root_block_device { volume_type = "gp2" volume_size = 30 delete_on_termination = true } tags { "Name" = "Terraform" } } resource "aws_instance" "Terraform2" { ami = "ami-a9d09ed1" availability_zone = "us-west-2b" ebs_optimized = false instance_type = "t2.micro" monitoring = false key_name = "Terraform" subnet_id = "subnet-e3bbf29a" vpc_security_group_ids = ["sg-25112d55"] associate_public_ip_address = true private_ip = "172.31.16.124" source_dest_check = true root_block_device { volume_type = "gp2" volume_size = 30 delete_on_termination = true } tags { "Name" = "Terraform2" } }
次に動作確認して、実際に適用しちゃいます!
terraform plan ~ Plan: 1 to add, 0 to change, 0 to destroy. ~
一台追加されることがわかりますよね!そしたら適用します!
terraform apply
お、できてるんじゃないですか!これでコード化とそれを使った運用を体験してみました!
IaCと継続的インテグレーションを組み合わせる
IaCをしただけだと正直IaCの旨味っていうのはまだ活かしきれていないんじゃないかなと思います。「インフラや構成管理にソフトウェアならではの開発手法を取り入れること。」ということを目的地と考えた時にソフトウェア開発ならではの手法は積極的に取り入れるべきでしょう。ここではまぁとりあえずGitHubを利用したバージョン管理とCircleCIを使った継続的インテグレーションを行います。とはいえ、動作確認&反映くらいで難しい設定はしないです笑
GitHubには作成されたtfファイルを適当に追加してもらえればいいなと思います。なお、Terraformの実行単位はディレクトリです。これを意識してリポジトリへのアップロードを行うと良いと思います。
GitHubとCircleCIを連携させる
ちなみに、ここまで読んで継続的インテグレーションって何?って方やCircleCIって何だろうってかたは先に下記の記事をお読みください!
CircleCIのアカウントを持っていない方についても上記ブログを参考にしながらGitHubとの連携作業を行うとイイと思います!自分はすでにアカウントがあってイイ感じに画像が撮れなかったので。。。。
CircleCIの設定を書いてみる
CircleCIの設定に関しては公式のイメージを利用してmaster以外ならterraform plan
が正常に動作することを確認。masterにpushされたらterraform apply
により実行されるようにしました。これをリポジトリの.circleci/config
に記述してください。
version: 2 jobs: build: docker: - image: hashicorp/terraform:0.11.7 working_directory: ~/リポジトリ名 steps: - checkout - run: name: "terraform init" command: | terraform init -input=false - run: name: "check terraform format" command: | terraform plan deploy: docker: - image: hashicorp/terraform:0.11.7 working_directory: ~/リポジトリ名 steps: - checkout - run: name: "terraform init" command: | terraform init -input=false - run: name: "terraform apply" command: | terraform apply -auto-approve workflows: version: 2 build-deploy: jobs: - build - deploy: requires: - build filters: branches: only: - master
これができたら、pushして一旦Start Buildingを押してしまいましょう。ちなみにビルドは失敗します。
なぜ失敗するかというと環境変数が設定されていないからですね。CircleCIではプロジェクト単位でも環境変数を設定することが可能です。Terraform用のリポジトリはなかなかたくさんの権限を与える必要があるのでなるべく狭い範囲で変数設定したいです。したがって、CircleCI全体の変数等には設定をしないほうがいいかなと思います。
変数設定は下記画面からできます。
ここで、下記の変数を定義します。これらに関してはファイルに埋め込んである場合は不要です。一応自分はマルチリージョンで管理することが多いのでリージョンに関しては割と削ぎ落とすことが多いです。
無事成功しました!これで変更をpushごとに反映されたり、プルリクでterraform plan
が正常に終わることの確認、詳細確認からどんな変更があるの確認することができます!インフラの管理が楽しくなるような気がしませんか?
終わりに
今回はTerraformingというツールを利用してAWSの既存インフラをコード化し、それに対しCircleCIを利用した継続的インテグレーションを適用しました。属人化しやすいと言われるインフラという領域ですが、属人化解消のための1つのソリューションとなってくれたら嬉しいなと思います。ちなみに、Terraformを利用するとインフラの構成だけでなく与えている権限の管理もコード化することができます。誰にどの権限が与えられているのか透明化できるというのもツール導入の1つのメリットになると思います。
普段割とコードをGitHubにあげてしまうのですが今回はインフラという特性上リポジトリへコードをアップロードして、継続的提供に関しては避けさせていただければと思います。そもそもあまり役に立たないものかと思います。
今後もインフラエンジニアとして"less Ops, More Code"を実現するための情報を提供できたらいいなと思います。それではみなさん快適なハックライフを!