Blog

清掃員がGCP環境構築をTerraform化してみた

札幌オフィス清掃員 技術ブログ

blogではじめまして。札幌オフィス清掃員です。
COLSISでは業界一強のAWSにこだわることなく、数多のクラウドIaaSを利用しています。

これまでGCPに関しては、案件数が少ないこともあり、弊社CTOが手作業で環境を構築していたのですが、ちょっと真似できないのと、私はメイン業務が清掃であり、IT知識や技術に乏しい為、今ある環境をそのままコピーしたいなぁという事でTerraformer を用いて Terraform 化してみることにした顛末をお届けしたいと思います。

既に擦り尽くされた感のあるTerraformネタではあるのですが、ちょっと調べるだけだとAWSで利用しているという情報が多く、GCPでの利用についての情報を公開するのも無駄ではないかなと思いましてblogを書いてみる次第でございます。

お手柔らかにどうぞ。

GCP構成

弊社では大体下図のような構成をとっています。
もちろんこれ以外にもバックアップであったりログ収集であったりと細かいのはあるのですがそこは割と案件毎に異なっていたりするため、どの案件でも基本変わらない部分のみの抜粋です。
今回はこの構成をTerraformで作れることを目標にしていきます。

Untitled.png

なぜ Terraform なのか?

ご存知ない方はこの記事は開いていないと思いますが、Terraform は、数多のクラウドIaaSに対応した構成管理ツールです。
Terraformで構成をコード化することで、何度でも同じ構成を構築できるようになります。

ちょっと実際どれに対応しているのかは把握していないので公式を見て頂くとして、AWS で言うところの CloudFormation の汎用化版みたいなイメージで良いと思います。

これまでもCloudFormationは利用していたのですが、CloudFormationだとAWSにしか対応できないため、今後利用するIaaSが増えても学習コストが増加しない(であろう)Terraformを使ってみることにしました。

Terraform by HashiCorp

Deliver infrastructure as code with Terraform Collaborate and share configurations Evolve and version your infrastructure Automate provisioning Define infrastructure as code to manage the full lifecycle - create new resources, manage existing ones, and destroy those no longer needed.

Terraformer とは何なのか?

手作業で構築したIaaS環境を読み取り、Terraformコードを生成するTerraformのインポートツールです。
terraform import という公式のインポートコマンドもあるのですが、こちらは terraform の構成情報ファイルである tfstate のみインポート可能ですが、Terraformer では実際にそのまま環境構築に使うことができる tf ファイルまで生成してくれるため、terraform学習にも便利に利用できると思います。

GoogleCloudPlatform/terraformer

A CLI tool that generates tf/ json and tfstate files based on existing infrastructure (reverse Terraform). Disclaimer: This is not an official Google product Created by: Waze SRE Generate tf/json + tfstate files from existing infrastructure for all supported objects by resource. Remote state can be uploaded to a GCS bucket.

私の環境

  • macOS catalina 10.15.7
  • homebrew

terraform / terraformer インストール

さっそくterraformを初めていきましょう。

# brew install terraform
# brew install terraformer

かんたんですね。

GCP で terraform & terraformer をつかってみる

さて、適当なプロジェクトを用意したら、terraformer で環境をコード化してみましょう。

gcloud コマンドをローカル環境で利用できるようにしている場合は認証等の特別な設定は必要ありません。
gcloud コマンドを利用できるようになっていない場合、プロジェクトの "APIとサービス" -> "認証情報" と辿り、サービスアカウントを作成してください。

terraform + GCP の認証まわりについては素人にもわかりやすくシンプルにまとまった良い記事を見つけましたのでこちらを参考にしました。

Terraformツールを使ってGCPリソース管理 | DevSamurai

※Terraformのv0.12.16バージョンを使っています。(この記事記載時点の最新バージョンです) 本記事の目的・Terraformを使ってGCPの環境を構築する時に、必要な設定のご紹介・Terraformのよく使 ...

terraformer については terraform が使えるようになっていれば特に設定は必要ありませんので、早速適当なディレクトリを作り、その直下で下記コマンドを実行してみましょう。

# terraformer import google --resources=addresses,autoscalers,backendBuckets,backendServices,bigQuery,disks,dns,firewall,forwardingRules,gcs,gke,globalAddresses,globalForwardingRules,healthChecks,httpHealthChecks,httpsHealthChecks,images,instanceGroupManagers,instanceGroups,instanceTemplates,instances,interconnectAttachments,logging,networkEndpointGroups,networks,nodeGroups,nodeTemplates,project,pubsub,regionAutoscalers,regionBackendServices,regionDisks,regionInstanceGroupManagers,routers,routes,securityPolicies,sslPolicies,subnetworks,targetHttpProxies,targetHttpsProxies,targetInstances,targetPools,targetSslProxies,targetTcpProxies,targetVpnGateways,urlMaps,vpnTunnels --projects=gcp-example

--resources には、対象のGCPサービスを指定しますが、ここで指定するキーワードは一般的な名称ではないため、取得したいリソースのキーワードは こちら から探して指定することになります。

--projects には、対象のGCPプロジェクト名を指定します。

上記コマンド例は、ほとんど全てのGCPサービスを網羅しようと思ったら一部サービスでエラーが出たのでエラーを削っていった結果完成したものです。
基本的には resources 部分には、使っているサービスのみを記載するようにしたほうが生成されるファイル数も減るので参照しやすくなると思います。

「使っているサービスがどれなのかもわからない!」というような場合のみ、このような無節操な指定を使うことになりますが、GCPでは一度でも利用しようとしてAPIを有効化したことのあるサービスでなければ基本的にはエラーが返ってしまうため、このコマンド例をコピペ実行して完走できなかった場合は resources 指定を減らす方向で調整してみてください。

これで生成されるのは下記のようなディレクトリ構造となります。

generated
└── google
    └── gcp-example
        ├── addresses
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── autoscalers
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── backendBuckets
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── backendServices
        │   └── global
        │       ├── compute_backend_service.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── bigQuery
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── disks
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── dns
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── firewall
        │   └── global
        │       ├── compute_firewall.tf
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── forwardingRules
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── gcs
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── gke
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── globalAddresses
        │   └── global
        │       ├── compute_global_address.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── globalForwardingRules
        │   └── global
        │       ├── compute_global_forwarding_rule.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── healthChecks
        │   └── global
        │       ├── compute_health_check.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── httpHealthChecks
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── httpsHealthChecks
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── images
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── instanceGroupManagers
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── instanceGroups
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── instanceTemplates
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── instances
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── interconnectAttachments
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── logging
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── networkEndpointGroups
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── networks
        │   └── global
        │       ├── compute_network.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── nodeGroups
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── nodeTemplates
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── project
        │   └── global
        │       ├── outputs.tf
        │       ├── project.tf
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── pubsub
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── regionAutoscalers
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── regionBackendServices
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── regionDisks
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── regionInstanceGroupManagers
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── routers
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── routes
        │   └── global
        │       ├── compute_route.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── securityPolicies
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── sslPolicies
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── subnetworks
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── targetHttpProxies
        │   └── global
        │       ├── compute_target_http_proxy.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── targetHttpsProxies
        │   └── global
        │       ├── compute_target_https_proxy.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── targetInstances
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── targetPools
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── targetSslProxies
        │   └── global
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        ├── targetTcpProxies
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── targetVpnGateways
        │   └── global
        │       ├── provider.tf
        │       └── terraform.tfstate
        ├── urlMaps
        │   └── global
        │       ├── compute_url_map.tf
        │       ├── outputs.tf
        │       ├── provider.tf
        │       ├── terraform.tfstate
        │       └── variables.tf
        └── vpnTunnels
            └── global
                ├── provider.tf
                └── terraform.tfstate

こうツリー構造で見ると膨大になるので、ちょっと面食らってしまいます。
また、サービス毎に分かれてしまうのでこれをそのまま terraform に食わせるのはなかなかに骨が折れる事がわかります。

基本的には、サービス名.tf ファイルだけを見ていけば大丈夫で、他はオマケみたいなものです。
サービス名.tf ファイルが無いディレクトリは、デフォルト状態から変化の無いサービスなのでresources 指定から外してしまって良さそうです。

こうして生成されたコードをベースに、修正など加えながらterraformコードを作っていく事にしました。
ロードバランサ周りについてはよくできたモジュールが公開されていたので、こちらを利用させていただく事にしました。

gruntwork-io/terraform-google-load-balancer

This repo contains modules to perform load balancing on Google Cloud Platform (GCP) using Google Cloud Load Balancing. Load balance HTTP and HTTPS traffic across multiple backend instances, across multiple regions with HTTP(S) Load Balancing.

そうして出来上がったものがこちらです。

colsis-sapporo-cleaner/terraform-gcp

よくある構成でGCP環境を構築するためのterraform定義ファイル郡。 はじめて利用される場合は、global.tfvars.sample をご参照ください。 # brew install terraform # cp global.tfvars.sample global.tfvars global.tfvars ファイルを構築する環境に合わせて編集してください。 # terraform plan -var-file global.tfvars # terraform apply -var-file global.tfvars 本当に実行しますか?的な確認がうざったい場合は # terraform apply -var-file global.tfvars -auto-approve terraformで構築した環境は全て削除されるのでご注意ください # terraform destroy -var-file global.tfvars

Terraformで構築してみる

それでは遂に、まっさらなプロジェクトに対し、ここまでで作成したterraformを適用してみます。

まずはmodule類のインストールのため、initを実行。

# terraform init

実行前には必ず、plan コマンドで実行計画を確認しておきましょう。

# terraform plan -var-file global.tfvars

満を持して実行します。

# terraform apply -var-file global.tfvars
Error: Error loading zone 'asia-northeast1-a': googleapi: 
Error 403: Compute Engine API has not been used in project 174020479117 before or it is disabled.
Enable it by visiting https://console.developers.google.com/apis/api/compute.googleapis.com/overview?project=174020479117 then retry.
If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry., accessNotConfigured

おっと盛大にエラー!

そうでした。
GCPは利用前にサービスのAPIを有効化しなければいけないのでしたね。
GUIでポチポチしてもいいのですが、面倒なので全APIをコマンドで有効にしてしまいます。

# gcloud services list --available | sed -e '1d' | cut -d ' ' -f1 | xargs -I{} gcloud services enable {}

もっといいコマンドもありそうですし、これは全てのサービスを有効にするのでとても時間がかかります。
必要なサービスだけを有効化するようにリストを作ってxargsで実行するような事もできると思いますが、私は清掃員なので大目に見て頂きたく。
これでもう一度 terraform apply を実行してあげればある程度の環境ができるはずです。

利用に際する注意点

  • インスタンス名がそのままOSのhostnameになってしまうので、気になる方は modules/instances/terraform.tf を編集したりしてください。
    pull-req もお待ちしています。
  • 公開したソース内では、マネージドSSL証明書のために、一部で google-beta provider を利用しています。
    そのため、ご利用には十分ご注意ください。

まとめ

  • Terraformer で tf ファイルを生成する事ができるからといって、無編集ですぐにコピー環境が作れるわけではない。
    特にTerraformerではデフォルトで存在するリソース類まで tfファイル化してしまうため、そのままだとterraformでエラーが出たりする。
  • Terraformer は Terraform の学習用と割り切って使うととても良いツールなので Terraform 導入用としては楽しく使うことができました。
  • もっとmodule化したりブラッシュアップできる余地がたくさんあるので今後もアップデートしていきたい。

以上、誰かの何かの参考にでもなれば。

ここで公開している内容について、一切の責任を負いませんので実行する際は自己責任にて、よくご確認の上実行してください。

そして弊社では清掃員が清掃以外の作業をしなくても済むよう、諸々の職種の方を募集しております。詳しくは以下をご覧の上、奮ってご応募ください。よろしくおねがいします!(切実)

image
Blog
はじめまして、コルシス広報ブログはじめます!
一覧にもどる