今の環境
転職したらTerraformを使うから覚えてね。と言われた。
手元にはRails tutorialで作ったRailsアプリケーションのプロジェクトがある。Git管理(GitHubにpushしてる)されている。これをTerraformでAWSにデプロイできるようにしてみる。
…のが最後の目標だけど、そもそもTerraformわからん!なのでEC2インスタンスを作るところからスタートしてみる。
作成したいAmazonイメージを取得する
- Amazonナビゲーションコンソールから
EC2
を選択 インスタンスの作成
からAMIイメージ一覧をみる- 作成したいイメージのAMIを見つける
今回はami-04d3eb2e1993f679b
を選択することにしてみた。
Terraformファイルを作る
作業しているプロジェクトフォルダの直下に.tf
ファイルと.tfvars
ファイルを作成する。
.tf
ファイルはTerraformのコンフィグレーションファイルとされていて、実際のクラウドリソースをどう組むかを設定する。
記載はTerraform独自のフォーマット形式っぽい。JSONも使えみたいだけど、そうすると拡張子が.tf.json
になる。違いはあるのかな〜。
.tfvars
ファイルはvariableを指定する。variable = 変数なので、環境情報やクレデンシャルキーを変数として登録し、.tfファイルでは生でデータを書かなくていいですってことらしい。これは.gitignoreに追加しておかないと、普通にアカウント乗っ取られちゃうやつ!
.tfvarsファイルをバージョン管理しないようにする
というわけで、ファイルを作成する前に.gitignoreを編集する。GitHubのgitignore
プロジェクトにTerraform用のファイルもあるので、それを参考にしてもいいかも。
# .gitignoreの一番下に追加 # Ignore .tfvars file *.tfvars # Ignore terraform logs *.tfstate *.tfstate.backup # Local .terraform directories **/.terraform/*
拡張子.tfvars
は全部バージョン管理しない、とした。こうしておけば間違えて接続情報をgit commitしなくてすむはず!
.tfvarsファイルを作る
さっそく.tfvars
ファイルをつくる。環境情報ならみて写せばいいだけだし、やってる感が出るしね!1場所は先ほども書いたプロジェクト直下とした。Dockerfileとかと扱いが一緒なのかね。こういうのどうしてるのか気になるわ〜
➜ Rails-tutorial-sampleApp git:(terraform) ✗ ls -al total 168 drwxr-xr-x 37 mallow staff 1184 10 13 11:36 . drwxr-xr-x 18 mallow staff 576 9 25 10:17 .. -rw-r--r--@ 1 mallow staff 6148 6 15 11:56 .DS_Store drwxr-xr-x 3 mallow staff 96 6 4 17:23 .bundle -rw-r--r-- 1 mallow staff 192 7 2 09:56 .byebug_history drwxr-xr-x 3 mallow staff 96 8 31 14:38 .circleci -rw-r--r-- 1 mallow staff 61 8 16 13:47 .dockerignore drwxr-xr-x 4 mallow staff 128 8 31 14:38 .ebextentions drwxr-xr-x 3 mallow staff 96 8 31 13:10 .elasticbeanstalk drwxr-xr-x 16 mallow staff 512 10 13 11:37 .git -rw-r--r-- 1 mallow staff 710 10 13 11:28 .gitignore -rw-r--r-- 1 mallow staff 5 6 4 17:01 .ruby-version -rw-r--r-- 1 mallow staff 256 8 31 14:38 Dockerfile -rw-r--r-- 1 mallow staff 374 8 20 11:27 Dockerrun.aws.json -rw-r--r-- 1 mallow staff 1175 9 5 13:08 Gemfile -rw-r--r-- 1 mallow staff 10417 9 16 13:07 Gemfile.lock -rw-r--r-- 1 mallow staff 1935 6 4 22:07 Guardfile -rw-r--r--@ 1 mallow staff 39 7 3 16:05 Procfile -rw-r--r-- 1 mallow staff 1310 6 4 17:01 README.md -rw-r--r-- 1 mallow staff 227 6 4 17:01 Rakefile drwxr-xr-x 11 mallow staff 352 7 13 22:27 app drwxr-xr-x 9 mallow staff 288 8 16 18:44 bin drwxr-xr-x 15 mallow staff 480 8 16 17:44 config -rw-r--r-- 1 mallow staff 130 6 4 17:01 config.ru drwxr-xr-x 7 mallow staff 224 7 31 18:17 db -rw-r--r-- 1 mallow staff 223 8 21 15:58 docker-compose.yml -rw-r--r-- 1 mallow staff 508 6 15 12:35 example_user.rb drwxr-xr-x 4 mallow staff 128 6 4 17:01 lib drwxr-xr-x 7 mallow staff 224 8 30 14:26 log -rw-r--r-- 1 mallow staff 68 6 4 17:01 package.json drwxr-xr-x 9 mallow staff 288 6 4 17:01 public -rw-r--r--@ 1 mallow staff 198 8 21 17:50 start.sh -rw-r--r--@ 1 mallow staff 0 10 13 11:36 terraform.tfvars ←これを新規に作成 drwxr-xr-x 11 mallow staff 352 7 8 16:52 test drwxr-xr-x 7 mallow staff 224 6 4 18:09 tmp drwxr-xr-x 3 mallow staff 96 6 4 17:01 vendor
肝心のファイルの中身はこういう風に書く。
aws_access_key_id = "IAMの認証用ID" aws_secret_access_key = "IAMの認証用キー"
変数を書いておくだけなので、aws_access_key_id
のところは好きな名前にしておけば良い。プログラミング言語と似ているな〜と思った。
.tfファイルを作る
今度は実際の設定を書く。
variable "aws_access_key_id" {} variable "aws_secret_access_key" {} variable "region" { default = "ap-northeast-1" } provider "aws" { access_key = "${var.aws_access_key_id}" secret_key = "${var.aws_secret_access_key}" region = "${var.region}" } resource "aws_instance" "hello_world_terraform" { ami = "ami-04d3eb2e1993f679b" instance_type = "t2.micro" tags { Name = "rails-tutorial-application" } }
変数の宣言はvariable "変数名" {代入したい値}
とする。最近仕事でVue.js始めたので、この辺は「なるほど〜〜」感がある。「インフラエンジニアじゃなくてもリソース操作がしやすいんです!」って売りは、こういう風にプログラミング言語と似ている感を出しているところにあるのかもしれない。違うかもしれない。
AWSはリソースをどのリージョンで起動するか選択する必要があるので、変数にregionを追加しておく。tfvarsファイルに書いてもいいんだろうけど、そうするとGitでバージョン管理できないので「これどのリージョンで上がってるんですかねえ」とかいう話になっちゃうんだろうな。
provider "aws" { access_key = "${var.aws_access_key_id}" secret_key = "${var.aws_secret_access_key}" region = "${var.region}" }
provider
の欄では、どのサービスを利用するか指定する。TerraformはAWS以外にもHerokuとかOpenStackとかGoogle Cloudも操作できるので指定しないといけないのだ。一覧はドキュメントに載っていた。いっぱいあるな〜。
変数を利用するときはJavaScript(ECMAScript2015)のテンプレートリテラルのように${変数名}
とする。2この辺りはVSCodeでTerraform拡張を入れておくと、予測して入力できるので便利。
resource "aws_instance" "hello_world_terraform" { ami = "ami-04d3eb2e1993f679b" instance_type = "t2.micro" tags { Name = "rails-tutorial-application" } }
ここではAWSのどんなサービスを利用するのかを指定する。S3だったらaws_instance
をaws_s3_bucket
にする。一覧はAWS Providerのページに載っていた。
hello_world_terraform
の部分はリソースにつけるお名前らしい。
あとはどのAMIイメージを利用するか、とかインスタンスタイプどうする?とかを記述する。
terraformコマンドでEC2を立てる
プロジェクトにTerraformの動作に必要なファイルをダウンロードする。
$ terraform init
➜ Rails-tutorial-sampleApp git:(terraform) terraform init Initializing provider plugins... - Checking for available provider plugins on https://releases.hashicorp.com... - Downloading plugin for provider "aws" (1.40.0)... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.aws: version = "~> 1.40" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
.tfファイルのproviderを読み込んで必要なファイルをダウンロードしてくれるんだね。
今度は.tfファイルが間違っていないか、などを確認する。
$ terraform plan
➜ Rails-tutorial-sampleApp git:(terraform) ✗ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + aws_instance.hello_world_terraform id: <computed> ami: "ami-04d3eb2e1993f679b" arn: <computed> associate_public_ip_address: <computed> availability_zone: <computed> cpu_core_count: <computed> cpu_threads_per_core: <computed> ebs_block_device.#: <computed> ephemeral_block_device.#: <computed> get_password_data: "false" instance_state: <computed> instance_type: "t2.micro" ipv6_address_count: <computed> ipv6_addresses.#: <computed> key_name: <computed> network_interface.#: <computed> network_interface_id: <computed> password_data: <computed> placement_group: <computed> primary_network_interface_id: <computed> private_dns: <computed> private_ip: <computed> public_dns: <computed> public_ip: <computed> root_block_device.#: <computed> security_groups.#: <computed> source_dest_check: "true" subnet_id: <computed> tags.%: "1" tags.Name: "rails-tutorial-application" tenancy: <computed> volume_tags.%: <computed> vpc_security_group_ids.#: <computed> Plan: 1 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run. ➜ Rails-tutorial-sampleApp git:(terraform) ✗
実際にリソースを作る
terraform apply
コマンドでリソースを作成する。
$ terraform apply
➜ Rails-tutorial-sampleApp git:(terraform) ✗ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + aws_instance.hello_world_terraform id: <computed> ami: "ami-04d3eb2e1993f679b" arn: <computed> associate_public_ip_address: <computed> availability_zone: <computed> cpu_core_count: <computed> cpu_threads_per_core: <computed> ebs_block_device.#: <computed> ephemeral_block_device.#: <computed> get_password_data: "false" instance_state: <computed> instance_type: "t2.micro" ipv6_address_count: <computed> ipv6_addresses.#: <computed> key_name: <computed> network_interface.#: <computed> network_interface_id: <computed> password_data: <computed> placement_group: <computed> primary_network_interface_id: <computed> private_dns: <computed> private_ip: <computed> public_dns: <computed> public_ip: <computed> root_block_device.#: <computed> security_groups.#: <computed> source_dest_check: "true" subnet_id: <computed> tags.%: "1" tags.Name: "rails-tutorial-application" tenancy: <computed> volume_tags.%: <computed> vpc_security_group_ids.#: <computed> Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_instance.hello_world_terraform: Creating... ami: "" => "ami-04d3eb2e1993f679b" arn: "" => "<computed>" associate_public_ip_address: "" => "<computed>" availability_zone: "" => "<computed>" cpu_core_count: "" => "<computed>" cpu_threads_per_core: "" => "<computed>" ebs_block_device.#: "" => "<computed>" ephemeral_block_device.#: "" => "<computed>" get_password_data: "" => "false" instance_state: "" => "<computed>" instance_type: "" => "t2.micro" ipv6_address_count: "" => "<computed>" ipv6_addresses.#: "" => "<computed>" key_name: "" => "<computed>" network_interface.#: "" => "<computed>" network_interface_id: "" => "<computed>" password_data: "" => "<computed>" placement_group: "" => "<computed>" primary_network_interface_id: "" => "<computed>" private_dns: "" => "<computed>" private_ip: "" => "<computed>" public_dns: "" => "<computed>" public_ip: "" => "<computed>" root_block_device.#: "" => "<computed>" security_groups.#: "" => "<computed>" source_dest_check: "" => "true" subnet_id: "" => "<computed>" tags.%: "" => "1" tags.Name: "" => "rails-tutorial-application" tenancy: "" => "<computed>" volume_tags.%: "" => "<computed>" vpc_security_group_ids.#: "" => "<computed>" aws_instance.hello_world_terraform: Still creating... (10s elapsed) aws_instance.hello_world_terraform: Still creating... (20s elapsed) aws_instance.hello_world_terraform: Still creating... (30s elapsed) aws_instance.hello_world_terraform: Creation complete after 37s (ID: i-0964794aa28ae38c5) Apply complete! Resources: 1 added, 0 changed, 0 destroyed. ➜ Rails-tutorial-sampleApp git:(terraform) ✗
どんなものができたかはterraform.tsstate
ファイルで確認できる。これもgitignoreに入れといていいかも。
実際にコンソールで確認してみる。
お!できている!
設定を確認すると、publicIPの割り当てがされていて、かつセキュリティグループもデフォルト(in/outオールオッケー)でできてしまう。これは細かく設定しないとダメだなー。
作ったものを消す
というわけで、早速リソースを削除する。
$ terraform destory
➜ Rails-tutorial-sampleApp git:(terraform) ✗ terraform destroy aws_instance.hello_world_terraform: Refreshing state... (ID: i-0964794aa28ae38c5) An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: - aws_instance.hello_world_terraform Plan: 0 to add, 0 to change, 1 to destroy. Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes aws_instance.hello_world_terraform: Destroying... (ID: i-0964794aa28ae38c5) aws_instance.hello_world_terraform: Still destroying... (ID: i-0964794aa28ae38c5, 10s elapsed) aws_instance.hello_world_terraform: Still destroying... (ID: i-0964794aa28ae38c5, 20s elapsed) aws_instance.hello_world_terraform: Still destroying... (ID: i-0964794aa28ae38c5, 30s elapsed) aws_instance.hello_world_terraform: Still destroying... (ID: i-0964794aa28ae38c5, 40s elapsed) aws_instance.hello_world_terraform: Still destroying... (ID: i-0964794aa28ae38c5, 50s elapsed) aws_instance.hello_world_terraform: Destruction complete after 52s Destroy complete! Resources: 1 destroyed.
Terminatedなので、リソースの削除中ということがわかる。
詰まったところ
変数ファイルをどうやったら読み込めるのかがわからない
terraform plan
などを実行する際、どうやって変数ファイルを読み込むのかわからなかった。
-var-file=変数名ファイル
オプションをつける- ファイル名を
terraform.tfvars
としておく - ファイル名に
.auto.tfvars
をつけて保存する
これらのどれかを利用すれば、読み込むことができる。ただし、tfファイルに記述した変数名とtfvarsファイルに記述した変数名が合わなければ、変数名のファイルが読み込まれても意味ないので注意(1敗)。
べんりだったもの
普段からVisual Studio Codeを使っているのだけど、Terraform拡張があったのでインストールした。シンタックスハイライトがつくだけで見通しがよくなるのですき。入力補完もやってくれる。あと、コンフィグの解説がポップアップで出てくる。大変便利です。