こんばんは、七色メガネです。
今日は Terraform を使って GCP 上のリソースを管理する方法を学んでいきます。
そも Terraform ってなに?
Terraform (テラフォーム)とは、HashiCorp 社によってオープンソースとして公開されているインフラストラクチャの管理ツールです。
従来インフラストラクチャの構成・管理という分野は、例えば作業者が「作業手順書」に従って一つずつコマンドを実行しなくてはいけなかったり、また時には作業者の独断で各種パラメータが設定されたり(特に小規模なプロジェクトにおいて)など、兎にも角にも可視化されづらく、そして可視化されていても管理コストのかかるものでした。
そんな問題を解決するために生み出されたのが、Terraform です。
Terraform は「安全に、かつ効率的に、インフラストラクチャの構成、変更、バージョン管理を行えるようにする (公式より)」ことを目的としたツールです。
Terraform ではインフラリソースを宣言的に定義して構成を実行することができ、かつそれをコードとして管理することができるため、煩雑な作業手順に悩まされることなく対象のリソース構成だけに集中することができ、かつ変更履歴管理も容易に行えるという優れものです。
https://www.terraform.io/intro/index.html
Terraform を使ってみる!
では前置きは早々に切り上げて、早速 Terraform を使ってみましょう!
今回は以下の手順で作業を進めていきます。
- 作業前提の確認
- GCP プロジェクト側で Terraform を使うための準備をする
- Terraform の準備をする
- Terraform で GCP プロジェクトに GCS のバケットを作ってみる
作業前提の確認
今回作業するに当たって、以下の前提が必要になります。
・インフラストラクチャの管理が行われる GCP プロジェクトが存在している
・作業 PC に Cloud SDK が導入されており、gcloud コマンドが実行できる。
・ローカルに作業用のディレクトリがある。(中身は空で良い)
もし自由にできるプロジェクトが存在していなかったり SDK が入っていない場合、ここから先に進む前に予め準備しておきましょう。
GCP プロジェクト側で Terraform を使うための準備をする
ではまず GCP 側で、Terraform を使う準備を行いたいと思います。必要な準備は次の3つです。
- Terraform 用のサービスアカウントを作成する
- Terraform 用のサービスアカウントにリソース管理のための権限を付与する
- サービスアカウントのキーを取得する
全てサービスアカウント(以下、SA ) 関連ですね。Terraform での操作は全て SA 経由で行うため、予め準備しておきましょう。
サービスアカウントを作成する
まずはサービスアカウントの作成です。コンソール画面からでも作成できますが、今回は gcloud コマンドベースで進めたいと思います。下記のコマンドを実行しましょう。
1 |
gcloud iam service-accounts create terraform-test-serviceaccount |
成功していれば、コンソールから SA の存在を確認できます。(確認しなくても良いですが)
サービスアカウントに権限を付与する
次にこの SA に 権限 (IAM) を設定しましょう。本来であればミニマムな IAM ロールを付与すべきですが、今回はテストなので「プロジェクト編集者」という強い権限を付与してしまいましょう。コマンドは次の通りです。
1 2 3 |
gcloud projects add-iam-policy-binding <YourProjectID> \ --member serviceAccount:terraform-test-serviceaccount@<YourProjectID>.iam.gserviceaccount.com \ --role roles/editor |
成功すると、コンソールの IAM から以下のように確認ができるはずです。
サービスアカウントのキーを作成する
最後に、ローカルからこの SA を使用するため、 SA の json キーを発行します。保存場所は、作業ディレクトリ直下の credential/account.json とします。(どこでもいいですが)
以下のコマンドを実行しましょう。
1 2 |
gcloud iam service-accounts keys create credential/account.json \ --iam-account terraform-test-serviceaccount@<YourProjectID>.iam.gserviceaccount.com |
無事にキーが作成・ 保存されていれば GCP 側の設定は終了です。最後に key の位置を export しておきましょう。
1 |
export GOOGLE_CLOUD_KEYFILE_JSON=../credential/account.json |
これでローカルから Terraform を実行した時、この SA を介してこのプロジェクトに対してアクションされます。
Terraform の準備をする
ではここからは、ローカルで Terraform の準備をしていきましょう。
Terraform をインストールする
まずは Terraform のインストールからですね。今回は brew を使います。下記のコマンドを実行してください。
1 |
brew install terraform |
成功確認のため、インストールが終わったらバージョンを確認してみましょう。正常にバージョンが表示できていれば問題ありません。
1 2 |
$ terraform version Terraform v0.12.29 |
tf ファイルを作成する
次に .tf ファイルを作成します。terraform でのリソース宣言は、全てこの tf ファイル中で行われます。
この tf ファイルは後ほど追記していくのですが、まずは terraform の初期化のために tf ファイルを用意するのがここでの目的なので、まずは main.tf という名前で、以下のような簡易的な構成にて作成しましょう。各項目の意味は後ほど説明します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
terraform { required_version = "~>0.12.29" } locals { region = "asia-northeast1" project = "sandbox-renkobayashi" } # Provider provider "google" { project = local.project region = local.region } |
1 2 |
$ ls credential main.tf |
Terraform ワークスペースを初期化する
basic な main.tf を作成したら、ワークスペースの作成準備が整いました。main.tf を配置した回想にて、以下のコマンドを実行して初期化を実行しましょう。
1 |
terraform init |
成功すると以下のようなログが流れ、.terraform という隠しファイルが作成されます。それが確認できたら、Terraform の準備は完了です。
1 2 3 4 5 6 7 8 9 |
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. |
1 2 3 4 5 6 7 |
$ ls -la total 8 drwxr-xr-x 5 kren staff 170 8 2 17:36 . drwxr-xr-x 6 kren staff 204 8 2 17:50 .. drwxr-xr-x 3 kren staff 102 8 2 16:52 .terraform drwx------ 3 kren staff 102 8 2 17:36 credential -rw-r--r-- 1 kren staff 376 8 2 17:51 main.tf |
Terraform で GCP プロジェクトに GCS のバケットを作ってみる
ここまでで GCP 側の準備と、Terraform を利用する準備が整いました。説明されていないおまじない的な書き方が散見されますが、とりあえずはリソース作成まで進んでみましょう!
ここでは次の2つの操作を行って、GCP プロジェクト上に GCS のバケットを作成するというアクションをしてみたいと思います。
- main.tf に GCS バケットを定義する
- GCP プロジェクト上に GCS バケットを作成する
main.tf に GCS バケットの定義を記述する
まずは tf ファイルに、バケットの定義を追記しましょう。
記述内容は後ほど解説しますので、一旦はこの通りに記述してみてください。とはいえ、雰囲気はなんとなくわかると思います。”terraform_test_0802″ という名前のバケットを、クラス: “Regional” で作ってください、という宣言ですね。
1 2 3 4 5 6 |
# GCS resource "google_storage_bucket" "test-bucket" { name = "terraform_test_0802" location = local.region storage_class = "REGIONAL" } |
実行計画を確認する
さてリソース作成の前に、「今回の操作によってどのようなリソース変更が実行されるか」ということを確認しましょう。実行計画確認のための以下のコマンドを実行してみてください。同名のバケットが存在していない限り、次のようなログが出るはずです。
1 |
terraform plan |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
$ 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: # google_storage_bucket.test-bucket will be created + resource "google_storage_bucket" "test-bucket" { + bucket_policy_only = (known after apply) + force_destroy = false + id = (known after apply) + location = "ASIA-NORTHEAST1" + name = "terraform_test_0802" + project = (known after apply) + self_link = (known after apply) + storage_class = "REGIONAL" + url = (known after apply) } 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. |
まずここで注目したいのは2点、作成されるリソースの構成と、実行されるアクションの種類ですね。
今回は作成するバケットについて名前とクラスしか指定していないため、それだけの情報を持ったバケットが作成されますよということが表示されています。
1 2 3 4 5 6 7 8 9 10 11 12 |
# google_storage_bucket.test-bucket will be created + resource "google_storage_bucket" "test-bucket" { + bucket_policy_only = (known after apply) + force_destroy = false + id = (known after apply) + location = "ASIA-NORTHEAST1" + name = "terraform_test_0802" + project = (known after apply) + self_link = (known after apply) + storage_class = "REGIONAL" + url = (known after apply) } |
また、今回は新規作成が1件であること、更新や削除は伴わないことも情報として表示されています。事前に plan しておくことで、予期せぬ操作ミスを防ぐことができますね。
1 |
Plan: 1 to add, 0 to change, 0 to destroy. |
リソースを作成する
では実行計画も確認しましたし、定義した内容で実際にリソースを作成してみましょう!
操作は簡単、以下のコマンドを実行するだけです。
1 |
terraform apply |
この時、最終確認としてもう実行計画が表示されます。内容に問題がなければ、yes をコンソール上で入力しましょう。
1 2 3 4 5 6 7 8 |
... 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 |
成功すると、次のようなログが流れます。実際にコンソールで GCS を確認してみても、期待した通りのバケットが作成されたことが確認されます。
1 2 3 4 |
google_storage_bucket.test-bucket: Creating... google_storage_bucket.test-bucket: Creation complete after 2s [id=terraform_test_0802] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. |
最初の準備こそ少し必要ですが、目的のリソース管理のための記述は数行で行えるということを考えると、確かにこれはとても便利ですよね。
Terraform のコマンドや文法を学ぶ!
さて、ここで一度足を止めて Terraform のコマンドや文法を学んでみましょう。特に文法はリソース毎に書き方が異なるので基本的には常に公式 doc を参照することになるものの、どういったものがあって何を規定していくのか、という初歩の知識をここで確認していきましょう。
Terraform のコマンド
Terraform で使用できる主なコマンドには次のようなものがあります。
init
terraform init
コマンドを実行すると、作業ディレクトリが初期化されます。
初めて terraform での作業を開始するときはもちろん、後述する state ファイルの保存場所を更新するときなどにも使用します。
fmt
terraform fmt
コマンドを実行すると、カレントディレクトリに存在する tf ファイルに対してフォーマッティングを実行してくれます。構成の変更時や git への push 前などに行うようにしましょう。
plan
terraform plan
コマンドを実行すると、現在の設定で更新作業を実行したことで発生するインフラの更新計画が表示されます。
計画は主に、差分の表示と、追加・変更・削除されるリソースの数、という構成で表示されます。
例えば 2 リソースの追加、1 リソースの変更が行われることが予想される際の実行計画は次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create ~ update in-place Terraform will perform the following actions: # google_pubsub_subscription.test-subscription will be created + resource "google_pubsub_subscription" "test-subscription" { + ack_deadline_seconds = 20 + id = (known after apply) + message_retention_duration = "1200s" + name = "test-subscription" + path = (known after apply) + project = (known after apply) + retain_acked_messages = true + topic = "test-topic" + expiration_policy { + ttl = "300000.5s" } } # google_pubsub_topic.test-topic will be created + resource "google_pubsub_topic" "test-topic" { + id = (known after apply) + name = "test-topic" + project = (known after apply) + message_storage_policy { + allowed_persistence_regions = (known after apply) } } # google_storage_bucket.test-bucket will be updated in-place ~ resource "google_storage_bucket" "test-bucket" { bucket_policy_only = false default_event_based_hold = false force_destroy = false id = "terraform_test_0802" labels = {} location = "ASIA-NORTHEAST1" name = "terraform_test_0802" project = "sandbox-renkobayashi" requester_pays = false self_link = "https://www.googleapis.com/storage/v1/b/terraform_test_0802" storage_class = "REGIONAL" url = "gs://terraform_test_0802" - versioning { - enabled = true -> null } } Plan: 2 to add, 1 to change, 0 to destroy. |
なお、この実行計画の表示は後続する apply を実行した際にも行われます。
が、apply を行う前には plan を実行して変更内容をあらかじめ把握しておくようにしましょう。
apply
terraform apply
コマンドを実行すると、指定の構成情報に従ってリソースの更新作業を実行します。
と言ってもすぐに実行されるわけではなく、apply を実行することで実行計画が表示され、コンソール上で問われる最終確認に対して Yes
を入力することで初めて実行されます。
import
terraform import
コマンドを実行すると、既に存在しているリソースの情報を terraform にインポートすることができます。terraform で 1 からリソース構成を作成するのではなく、既に存在するリソース構成に合わせて terraform を構築する場合に用いられるコマンドです。
この使用方法については、別途紹介したいと思います。
.tf ファイルの書き方
Provider ブロック
Provider ブロックはその名の通り、管理したいリソースの属するプロバイダーの情報を指定するブロックです。今回は GCP を使用するので、プロジェクト情報やリージョン情報、認証情報などをここで指定します。
1 2 3 4 5 6 |
# Provider provider "google" { project = <YourProjectID> region = <YourProjectRegion> credentials = file("../credential/account.json") } |
GOOGLE_CLOUD_KEYFILE_JSON 変数に認証ファイルの情報を入れておけば、credential は無くてもいけるみたいです。(逆も然り)
provider 毎に設定が異なると思うので、GCP 以外を使用する場合には公式 doc を確認しましょう。
https://www.terraform.io/docs/providers/index.html
terraform ブロック
terraform ブロックでは、使用する terraform の情報を指定します。最低限 terraform のバージョン情報があれば良いのですが、terraform の state 情報を管理する backend の指定もここで行うことができます。今回は管理場所として GCS のバケットを指定しています。
1 2 3 4 5 6 7 8 |
# Terraform terraform { required_version = "~>0.12.29" backend "gcs" { bucket = "terraform_test_0802" credentials = "../credential/account.json" } } |
backend を指定する際に注意することは以下の 2 点です。
- state 情報を管理するバケットは、既に存在しているバケットを指定しなければならない。
- backend を指定したあとは、terraform init を実行しなければいけない。
初期状態で、resource ブロックで作成したバケットを指定してもエラーになります。
そのため、あらかじめ手作業でバケットを作成しておくか、あるいは terraform ブロックで backend を指定しない状態で resource 宣言を実行し、バケットを作成してから backend 宣言を記載する、などの対応が必要になります。
なお、backend を指定しない場合はローカルに state ファイルが保存されます。逆に backend を指定すると、ローカルには state ファイルは保存されないようです。
今回は手元で state ファイルを見守っていたいので、あえて backend は指定しないで進めたいと思います。
locals ブロック
locals ブロックで宣言した変数は、他のブロックから値を参照できるようになります。
例えば以下のように宣言された project と region は
1 2 3 4 |
locals { project = "sandbox-renkobayashi" region = "asia-northeast1" } |
次のように、”local.xxx” の形で参照できます。
1 2 3 4 5 6 |
# Provider provider "google" { project = local.project region = local.region credentials = file("../credential/account.json") } |
気をつけることとしては、ブロックは locals なのに参照するときは local なことくらいですかね。
Resource ブロック
resource ブロックは、各種インフラリソースを定義するブロックです。リソースによって定義する方法が変わってくるので、ここも都度ドキュメントを参照するようにしましょう。
ここでは試しに、GCS と Pub/Sub の設定をしてみましょう。
GCS
簡単な GCS のバケット作成のための定義は以下のようになります。
1 2 3 4 5 6 |
# GCS resource "google_storage_bucket" "test-bucket" { name = "terraform_test_0802" location = local.region storage_class = "REGIONAL" } |
名前と場所、クラスを指定するだけの簡易構成ですね。もちろんそれ以上の設定を行うこともできます。
GCS に関するドキュメントは次の URL 先にあります。
https://www.terraform.io/docs/providers/google/r/storage_bucket.html
Pub/Sub
今度は pubsub で簡単なトピックとサブスクリプションを定義してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# PubSub ## Topic resource "google_pubsub_topic" "test-topic" { name = "test-topic" } ## Subscription resource "google_pubsub_subscription" "test-subscription" { name = "test-subscription" topic = google_pubsub_topic.test-topic.name message_retention_duration = "1200s" retain_acked_messages = true ack_deadline_seconds = 20 expiration_policy { ttl = "300000.5s" } } |
今回は雰囲気を見るだけなので、細かい設定値は気にしません。親トピックと、pull 型の子サブスクリプションを設定したという感じです。
上記設定で terraform apply を実行すると、期待通りにリソースが作成されました。
この設定に関しても公式を読みましょう。こんなの何もみないで書けるわけがないんです。
https://www.terraform.io/docs/providers/google/r/pubsub_subscription.html
次回
今回の記事は既に長くなったのでここで終わろうと思います。
まだまだ出来ていない操作もあるので、それは次回以降の記事で触れていこうと思います。
以上、ここまでご覧いただきありがとうございました。
おすすめ書籍
Google Cloud Platform エンタープライズ設計ガイド
Google Cloud Platform 実践Webアプリ開発 ストーリーで学ぶGoogle App Engine
プログラマのためのGoogle Cloud Platform入門 サービスの全体像からクラウドネイティブアプリケーション構築まで
実践Terraform AWSにおけるシステム設計とベストプラクティス (技術の泉シリーズ(NextPublishing))