こんにちは。TIG/DXユニットの伊藤です。本記事は春の入門祭り の第18弾になります。
今回はタイトルの通り、Terraformの入門記事です。Infrastructure as Code(IaC)のツールとしてはAWSからはCloudFormation、GCPからはDeployment Managerが出ています。Terraformは単一ツールで複数クラウドのリソースをコードで管理することができる、ということが大きなメリットであることは世に出ている他の記事でも言われています。
本記事では以前私がこの入門記事連載で上げた「春の入門祭り 🌸 #02 Google Cloud Platform 101 」の内容を扱います。なので、今回はGCP+Terraformの組み合わせでリソースを実際にコード化するところが基本編、Terraform Cloudの導入をを応用編として書いていきます。長くなるかもですが、お付き合いください。
はじめはTerraformの簡単な説明をしていきます。Terraformは、HashiCorp社が開発したInfrastructure as Code(IaC)のためのツールになります。 コード化していく中で以下の働きを理解しながら進めるとどんな風にTerraformが動いているかわかると思います。
resource
人が実際にリソースを作成する際に書くコード。作成後もコードを差分を適用することができる。
tfstate
実在するリソースの「あるべき状態」を定義しているファイル。コードの変更を適用するとtfstateもコードに即した状態になる。
provider
各クラウドのAPIをTerraformが実行するために必要なプラグイン。コードで定義して作成する時に必要。
準備 Terraformを実行できる準備をしましょう。今回の記事の作成に当たっての実行環境は以下です。
MacBook 16inch(2019)
OS: Catalina
Terraform version: 0.12.24
Editor: VS Code
Terraformのインストールは、Macの方であれば、Homebrew経由でできます。なので、以下のコマンドで簡単に手に入ります。便利。
1 2 $ brew install terraform $ terraform version
次にサービスアカウントと、キーを発行します。サービスアカウントキーは昨今こんな記事 も出ていますので、気をつけましょう。 サービスアカウントの作成は、GCPのコンソール画面のハンバーガーメニューより、[IAMと管理]>[サービスアカウント]からできます。何回か入力と次に進むを繰り返しますが、以下のように入力してください。
作成できたらこのサービスアカウントの詳細に行き、[鍵の追加]を行ってください。jsonかP12かを選択できますが、今回はjsonを選択します。自動でダウンロードされるので、今回はこのキー名をcredentials.json
と名前を変更して今回使用するTerraformのディレクトリに配置しておきましょう。 コード側でも一つ準備しておきます。provider
と呼ばれる各クラウドのAPIをTerraformが叩くためのバイナリを指定します。以下のコードをTerraformを実行するディレクトリに準備してください。
provider.tf 1 2 3 4 provider "google" { project = "xxxxxxxxxx" credentials = file("credentials.json" ) }
コードとサービスアカウントキーが準備できたら、以下のコマンドで、Terraformの準備をします。
1 2 3 4 5 6 7 8 9 10 11 12 13 $ terraform init Initializing the backend... Initializing provider plugins... - Checking for available provider plugins... - Downloading plugin for provider "google" (hashicorp/google) 3.26.0... ... 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.
このinit
コマンドを実行することで.terraform
ディレクトリが生成されています。このディレクトリ内に、定義されているprovider
やあとで出てくるbackend
など、Terraformのリソースではなく設定に関わる部分を初期化してくれます。なので、複数人でGit管理している時にも通常は.terraform
ディレクトリは管理に入れないようにしましょう。 さて、これでリソースを作成する準備ができたので、いよいよTerraformを書いていきましょう!!
基本編 入門記事からの再掲になりますが、以下の構成を基本編で行っていきます。手で組んでみてからの方が実際にTerraformが便利ということに気がつけるかもしれません。 まずは大枠のVPCやサブネットを作成してからGCEやNATなどを立てていきましょう。
ネットワーク構築 はじめにネットワークを構築しましょう。ここではVPC とサブネット を構築します。Terraformはコード化する時にはドキュメント必須なので、是非公式のドキュメントも読んでみてください。ネットワークについては、以下のコードにしました。auto_create_subnetworks = false
としているのは不必要なサブネット作成を抑えるためです。
compute_network.tf 1 2 3 4 resource "google_compute_network" "sample_network" { name = "sample-network" auto_create_subnetworks = false }
これでVPC部分のコードを適用してみましょう。ここでは以下のコマンドを使ってみましょう。
1 2 3 4 $ terraform fmt $ terraform validate $ terraform plan $ terraform apply
これらコマンドを是非使ってみてください。上で示したnetwork.tf
はコードの形が崩れていますが、fmt
コマンドで整形されることがわかるかと思います。apply
コマンドを実行すると以下が出力され、yes
の入力を求められます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # google_compute_network.sample_network will be created + resource "google_compute_network" "sample_network" { + auto_create_subnetworks = false + delete_default_routes_on_create = false + gateway_ipv4 = (known after apply) + id = (known after apply) + ipv4_range = (known after apply) + name = "sample-network" + project = (known after apply) + routing_mode = (known after apply) + self_link = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy.
もしかしたらApplyが「APIを有効化していないのでできない」と出る方もいるかと思いますが、その時はGUIで該当のAPIを有効化してください。APIの有効化はこちら を参照してくださいApplyが完了したらGUIで確認してみてください。ここではネットワークだけできていると思います。 次にサブネットを作成しましょう。
compute_subnetwork.tf 1 2 3 4 5 6 7 8 9 10 resource "google_compute_subnetwork" "pub_subnetwork" { name = "sample-subnet-pub" ip_cidr_range = "192.168.1.0/24" region = "asia-northeast1" network = "sample-network" } resource "google_compute_subnetwork" "prv_subnetwork" { }
ここではsample-network
に作ることを指定して書いています。オプションとして他にも様々書けるので、必要に応じて追記することができます。また、今回はサブネットを2つ作成しますが、1つは作成ぜひ実際に書いてみてください。答えはこちら にあります。コードの準備が終わったらApplyコマンドを叩いてリソースを作成してください。
ファイアウォールの作成 次にインスタンスを不正なアクセスから保護するためのファイアウォールを作成します。インスタンスなどの作成よりもこういったルール周りの方がTerraformが役立つ場面でもあります。GCPの入門記事では2つ作成したので、今回も2つ作成できるようにします。コードは以下になります。
compute_firewall.tf 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 resource "google_compute_firewall" "bastion" { name = "bastion" network = "sample-network" allow { protocol = "tcp" ports = ["22" ] } source_ranges = ["0.0.0.0/0" ] target_tags = ["bastion" ] } resource "google_compute_firewall" "from_bastion" { name = "from-bastion" network = "sample-network" allow { protocol = "tcp" ports = ["22" ] } source_tags = ["bastion" ] target_tags = ["from-bastion" ] } resource "google_compute_firewall" "sample_network_allow_http" { name = "sample-network-allow-http" network = "sample-network" allow { protocol = "tcp" ports = ["80" ] } source_ranges = ["0.0.0.0/0" ] target_tags = ["http-server" ] }
sshできるようにするファイアウォールルールは上の2つ、下のsample-network-allow-http
は、GUIであれば自動生成されますが、Terraformだとできないので、今回はコード化してApplyします。
インスタンスの作成 はじめに踏み台インスタンスから作成していきます。踏み台は、外部IPがついている必要があるので、以下の形で書きます。
compute_instance.tf 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 resource "google_compute_instance" "bastion" { name = "bastion" machine_type = "n1-standard-1" zone = "asia-northeast1-a" tags = ["bastion" ] boot_disk { initialize_params { image = "debian-cloud/debian-10" } } network_interface { subnetwork = "sample-subnet-pub" access_config { nat_ip = google_compute_address.bastion_external.address } } service_account { scopes = ["cloud-platform" ] } } resource "google_compute_address" "bastion_external" { name = "bastion-external" address_type = "EXTERNAL" region = "asia-northeast1" }
上のコードでは、インスタンス作成を行うgoogle_compute_instance
と、外部IPコードで決めて設定しているgoogle_compute_address
を記載しています。webインスタンスについては実際に書いてみましょう。外部IPは必要ないので、google_compute_instance
だけあれば作成することができます。答えの例はこちら になります。
リソースに依存関係をつける 今回、GCEの作成にあたり、access_config
ブロックの中にnat_ip = google_compute_address.bastion_external.address
という書き方をしました。この書き方には主に2つ目的があり、
他で作成したリソース値を利用する書き方
今回は該当するリソースのアドレスを引用する書き方になっている
リソースの依存関係をつけてくれる
今回はgoogle_compute_address.bastion_external
が作成されないとインスタンスが作成されない
そのため、これまで出てきたリソースだと、VPC→サブネットorファイアウォールの作成順序が好ましいので、サブネットを例にすると、
compute_subnetwork.tf 1 2 3 4 5 6 7 8 9 10 resource "google_compute_subnetwork" "pub_subnetwork" { name = "sample-subnet-pub" ip_cidr_range = "192.168.1.0/24" region = "asia-northeast1" network = google_compute_network.sample_network.id } resource "google_compute_subnetwork" "prv_subnetwork" { }
と書くことができます。他にも修正できる部分があるので直してみましょう。 webインスタンスについては実際に書いてみましょう。外部IPは必要ないので、google_compute_instance
だけあれば作成することができます。答えの例はこちら になります。
ロードバランサの作成 ユーザーからのリクエストを受けるロードバランサを作成しましょう。GUIでも設定が細かいロードバランサですが、Terraformにしておくとコードで設定が見えるので、あとで見返すのにもおすすめな部分です。
http_lb.tf 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 resource "google_compute_global_address" "web_lb" { name = "web-lb" } resource "google_compute_health_check" "web_health" { name = "web-health" timeout_sec = 1 check_interval_sec = 1 tcp_health_check { port = "80" } } resource "google_compute_backend_service" "web_backend" { name = "web-backend" port_name = "http" protocol = "HTTP" timeout_sec = 3000 backend { group = google_compute_instance_group.web_instance_group.self_link } health_checks = [google_compute_health_check.web_health.self_link] } resource "google_compute_url_map" "web_lb" { name = "web-lb" default_service = google_compute_backend_service.web_backend.self_link } resource "google_compute_target_http_proxy" "http_proxy" { name = "http-proxy" url_map = google_compute_url_map.web_lb.self_link } resource "google_compute_global_forwarding_rule" "forwarding_rule" { name = "forwarding-rule" target = google_compute_target_http_proxy.http_proxy.self_link port_range = "80" ip_address = google_compute_global_address.web_lb.address } resource "google_compute_instance_group" "web_instance_group" { name = "web-instance-group" instances = [google_compute_instance.web_instance.self_link] zone = "asia-northeast3-a" }
ロードバランサを1つ作るために様々なリソースを作成しなければいけませんが、上のコードではLBを作る部分、最後にはインスタンスグループを作るリソースを書いています。
Cloud NAT, Routerの作成 WebサーバーのパッケージをアップデートさせるためにはNATから外に出られるようにする必要があります。ここではCloud NATとCloud Routerを作成します。またまた、コードは以下です。
compute_router.tf 1 2 3 4 5 6 7 8 9 resource "google_compute_router" "seoul_router" { name = "seoul-router" region = "asia-northeast3" network = google_compute_network.sample_network.id bgp { asn = 64514 } }
compute_router_nat.tf 1 2 3 4 5 6 7 resource "google_compute_router_nat" "seoul_nat" { name = "seoul-nat" router = google_compute_router.seoul_router.name region = google_compute_router.seoul_router.region nat_ip_allocate_option = "AUTO_ONLY" source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" }
ここまで設定できたら、春の入門祭り 🌸 #02 Google Cloud Platform 101 の「6.ミドルウェアの設定」を行って、nginxの画面がブラウザから見ることができれば基本編の完了です!
応用編 応用編としてTeraform Cloudの導入を行ってみましょう。Terraform Cloudが導入できると、TerraformのCI/CDができるようになるので、グッと開発スピードが上がります!
Terraform CloudはHashiCorp社が提供するTerraformのCI/CD基盤になります。プランは様々あり、有料版にあげることでPolicy as Code(PaC)を実現できるSentinel を導入できたりと、インフラのデプロイ速度をあげる以外にも、Terraformの実行環境の統一や実行ログ、インフラの状態など秩序を守るためにも非常に大きなメリットをもたらします。
変数の設定 Terraform Cloudに本格的に移行する前に、変数の切り出しを行いましょう。特に今回は、サービスアカウントキーなどケアが必要な部分をsecret.tfvars
に切り出します。
variable.tf 1 2 3 variable "PROJECT_ID" {} variable "PROJECT_NAME" {} variable "GOOGLE_CREDENTIALS" {}
secret.tfvars 1 2 3 PROJECT_ID = "xxxxxxxx" PROJECT_NAME = "xxxxxxxx" GOOGLE_CREDENTIALS = "credentials.json"
切り出した上を元にさらにprovider.tf
も書き換えましょう。
provider.tf 1 2 3 4 provider "google" { project = var.PROJECT_ID credentials = var.GOOGLE_CREDENTIALS }
Terraform Cloudへの登録自体はこちら から行うことができるので、手順に沿って登録しましょう。
Workspaceの作成 登録が完了したら、Terraform Cloudにリポジトリの登録を行いましょう。サインアップ終わったあとはきっとこんな感じでできたてホヤホヤだと思います。
右にある[New workspace]から新しいWorkspaceを作成しましょう。 はじめにGitリポジトリを指定します。今回はGithubを使用するのでGithubを選択して次に進みます。
[Choose a repository]の項目では今回使うリポジトリを選択します。選択が終わると[Configure settings]に進むので、Workspaceの名前を変えられます。ここではとくに問題がないので、そのまま(リポジトリの名前のまま)で進みます。
変数の設定 Workspaceの作成が完了すると、そのまま変数の設定に進むことができます。なので[Configure variables]に進みましょう。設定するのは「Terraform Variables」の方になります。今回は以下のようにPROJECT_ID
、PROJECT_NAME
、GOOGLE_CREDENTIALS
の3つを設定しました。GOOGLE_CREDENTIALS
については鍵なので、sensitiveにチェックを入れると見えなくなります。
これで一通りの設定ができたので実際にQueueを走らせてみましょう。
Queueの実行 画面の右上に[Queue plan]の項目があるので実行してみましょう。入力項目が現れますが、特に何も入れずに実行できます。実行してみると以下の様にPlanの結果が返ってきます。
11 to add
になっていますが、これはローカルのStateファイルをみていないために起こります。この後で、Stateの移行を行います。
Tokenを取得する Terraform Cloud上のStateを見れるように前の作業として、ローカルから認証できる必要があります。そのために必要なTokenを取得するために以下の2パターンで取得します.
コマンドを使う コマンドを使ってTokenを取得できます。しかし、バージョンがv0.12.21 以降でしか使えないので、これより前のバージョンを使用している方はGUIを使ったパターンで取得してください。
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 $ terraform login Terraform will request an API token for app.terraform.io using your browser. If login is successful, Terraform will store the token in plain text in the following file for use by subsequent commands: /Users/itota/.terraform.d/credentials.tfrc.json Do you want to proceed? (y/n) y # ここでyを入力する Terraform must now open a web browser to the tokens page for app.terraform.io. If a browser does not open this automatically, open the following URL to proceed: https://app.terraform.io/app/settings/tokens?source=terraform-login --------------------------------------------------------------------------------- Generate a token using your browser, and copy-paste it into this prompt. Terraform will store the token in plain text in the following file for use by subsequent commands: /Users/itota/.terraform.d/credentials.tfrc.json Token for app.terraform.io: # ブラウザで表示されるTokenを入力する Retrieved token for user xxxxxxxx --------------------------------------------------------------------------------- Success! Terraform has obtained and saved an API token. The new API token will be used for any future Terraform command that must make authenticated requests to app.terraform.io.
このように出力されるので、2箇所入力が済めばこちらのコマンドは完了となるので次に進みましょう。
GUIから取得する こちらではGUIからTokenwo取得する方法を説明します。以下の画像について順を追って説明します。
① [Ortanization Setting]に遷移する
② [API Token]をクリック
③ [Create an authentication token]をクリックする
Token名を入力するとTokenが払い出される。
あとは以下のファイルを作成すればTokenの取得作業は完了です。
~/.terraformrc 1 2 3 credentials "app.terraform.io" { token = "xxxxxxxxxxxxx" }
backendの設定を有効化する backend.tf
を以下に書き換えて、stateを見に行く場所をGCSからTerraform Cloudに変えます。
backend.tf 1 2 3 4 5 6 7 8 9 10 terraform { backend "remote" { hostname = "app.terraform.io" organization = "kaedemalu" workspaces { name = "terraform_101" } } }
これで再びterraform init
コマンドを実行しましょう。
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 $ terraform init Initializing the backend... Acquiring state lock. This may take a few moments... Do you want to copy existing state to the new backend? Pre-existing state was found while migrating the previous "local" backend to the newly configured "remote" backend. No existing state was found in the newly configured "remote" backend. Do you want to copy this state to the new "remote" backend? Enter "yes" to copy and "no" to start with an empty state. Enter a value: yes # ここでyesと入力する Successfully configured the backend "remote"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... 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.google: version = "~> 3.26" 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.
これでローカルからTerraform Cloudにstateがコピーされたので、Queue Runの情報を確認してみましょう。Planの自動実行結果は以下になります。
ローカルで全てApplyが済んでいるので差分がない状態になります!ちなみにこの状態になってからローカルでapplyコマンドを実行すると、
1 2 3 4 5 6 $ terraform apply Error: Apply not allowed for workspaces with a VCS connection A workspace that is connected to a VCS requires the VCS-driven workflow to ensure that the VCS remains the single source of truth.
と返されて実行できなくなっています。これでTerraform CloudからのみコードのApplyができるようになっているので、実行環境の統制ができるようになります。
まとめ 今回はTerraform 101ということでTerraformをつかってリソース構築を行いました。今回は取り扱いませんでしたが、コードの管理を環境ごと変えずに利用できるWorkspaceやTemplateとして使えるModuleなどもあります(参考記事はこちら 。また、今回扱ったTerraformのバージョンは0.12ですが、環境構築編 や実践編 もありますのでそちらもぜひ読んでみてください。 Terraformを使うことで俗にいう手順書を無くして、インフラをコードで管理することで冪等性を保つことができます。うっかり手順を飛ばして、どこでミスを起こしたか調べることにもかなりの労力と時間を使うと思うので、是非Terraformを日々の(インフラの)お供に取り入れてみてはいかがでしょうか?
今回使用しているコードは以下のGithubに上がっているので、ぜひ参照してください。https://github.com/kaedemalu/terraform_101_handon