Kekeの日記

エンジニア、読書なんでも

Terraformで運用しているLineBot家計簿をGCPで使う

f:id:bobchan1915:20181210104636p:plain

はじめに

私は自分で作ったLineBotを使って家計簿をつけています。

クラウドにはGoogle Cloud Platform(GCP)の無料枠を使っていて、データベースにはCloud SQL(MySQL)を使っています。

自分でデータを集めているので、以前にもApache Supersetなどを用いてデータ分析をしていました。

www.1915keke.com

しかしながら、無料枠を使っていく中で、無料枠が終わって、別の保有しているアカウントに移行したい時に**まったく構成管理が宣言的にできていないことに気づいて、今回はTerraformを使ってやっていこうと思います。

Terraformについて

何をするものなのか

Terraformとはインフラのバージョニング、変更、ビルドを安全に効率的に行うためのツールです

特徴

以下のような特徴があります。

  • Instrastructure as Code: 宣言的にインフラを構築することができる
  • 実行計画を立てられる: Stateによって変更をインクリメンタルにできる
  • リサーチグラフ: 可視化できる
  • 複雑な自動化も可能: あらゆる要求に応えることができる

注意点としての特徴もあります。

  • Rollbackを自動で行ってくれない

インストール方法

Homebrewを使って簡単にインストールすることができます。

brew install terraform

インストールができたら、正しく実行できるかを確認してください。

terraform -v

Terraformの設定ファイル

指定ファイルは*.tfのようにtf拡張子を使って設定を書き込んでいきます。

インフラを構築する

ここから以下のように順番に、概念を解説しながら説明します。

目標とするアーキテクチャ以下の通りです。

f:id:bobchan1915:20181210104715p:plain

また、今回構築していくディレクトリ構造は以下の通りになっています。

- .gcp
- .terraform

APIを有効か

Terraformによって操作するにはGCPのAPIを有効化をしなければなりません。

以下の項目を有効化します。

Cloud Resource Manager API

f:id:bobchan1915:20181210124925p:plain

Cloud SQL Admin API

f:id:bobchan1915:20181210132130p:plain

App Engine Admin API

f:id:bobchan1915:20181210133018p:plain

Provider

f:id:bobchan1915:20181210123112p:plain

Providerとはどこのクラウド環境にデプロイをするのかを指定します。

以下のようにgoogleを指定してGCPへデプロイするようにします。

.terraform/provider.tfを作成します。

provider "google" {
    credentials = "${file(".gcp/private_key.json")}"
    project     = "LineBot Terraform"
    region      = "asia-northeast1"
}

このようにして指定できます。

  • credentials: 認証情報。サービスアカウントがあるディレクトリ
  • project: GCP上でのプロジェクトの名前
  • region: どのリージョンかを指定

また、これを使うためにはプラグインをいれないといけません。

terraform init .terraform/

するとpluginsディレクトリが.terraform/以下に作られますのでgitignoreに追記した方がいいと思います。

Resource

Resourceとはインスタンス、クラスタなどの構成要素のことです。

1. Project

まずProjectを作成します。

resource "google_project" "my_project" {
  name       = "LineBot Terraform"
  project_id = "line-terraform-0000"
}

2. App Engine

f:id:bobchan1915:20181210114557p:plain

Google App Engineを設定します。

resource "google_app_engine_application" "app" {
  project     = "${google_project.my_project.project_id}"
  location_id = "us-central'
}

ここで${}によって変数展開をしています。先ほど1."google_project" "my_project"とを定義したので、そのproject_idを展開しています。

3. SQL

f:id:bobchan1915:20181210114626p:plain

次にCloud SQLを設定します。

まず最初にインスタンスを作ります。

resource "google_sql_database_instance" "master" {
  name = "master-database-instance"
  project = "${google_project.my_project.project_id}"
  database_version = "MYSQL_5_7"

  settings {
    tier = "db-f1-micro"
  }
}

次にデータベースを作成します。

resource "google_sql_database" "database" {
  name      = "yamashita-bank"
  instance  = "${google_sql_database_instance.master.name}"
  charset   = "sjis"
  collation = "sjis_japanese_ci"
}

そして次にユーザーを作ります。

resource "google_sql_database" "users" {
    name = "root"
    instance = "${google_sql_database_instance.master.name}"
}

4. Cloud Functions

f:id:bobchan1915:20181210114706p:plain

次にCloud Functionsを設定していきます。

resource "google_cloudfunctions_function" "test" {
    name = "batch-functions"
    entry_point               = "MontlyBatch"
    project                   = "${google_project.my_project.project_id}"
    region                    = "asia-northeast1"
    trigger_http              = true
    source_archive_bucket     = "${google_storage_bucket.bucket.name}"
    source_archive_object     = "${google_storage_bucket_object.archive.name}"
}

5. Google Cloud Storage

f:id:bobchan1915:20181210123236p:plain

以下のようにGoogle Cloud Storageも作っておきます。

これらは、Cloud Functionsのソースが置かれたりします。

resource "google_storage_bucket" "bucket" {
  name = "cloudfunction-deploy"
}
 
data "archive_file" "http_trigger" {
  type        = "zip"
  output_path = "${path.module}/files/http_trigger.zip"
  source {
    content  = "${file("${path.module}/files/http_trigger.js")}"
    filename = "index.js"
  }
}

resource "google_storage_bucket" "bucket" {
  name     = "line-bot-bucket"
  location = "JP"
}

resource "google_storage_bucket_object" "archive" {
  name   = "file"
  bucket = "${google_storage_bucket.bucket.name}"
}

実行計画

以下のコマンドを叩いて実行計画を立てます。まだ、実際には作られませんので安心してください。

terraform plan .terraform/

主要な部分を取り出します。

Plan: 8 to add, 0 to change, 0 to destroy.

8つか追加されるということです。

実行する

applyコマンドを使って実行します。

terraform apply .terraform/

すると以下のように確認がでるのでyesを入力します。

Plan: 8 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

いくつもできるので、GAEの部分を抜粋します。

google_app_engine_application.app: Creating...
  auth_domain:         "" => "<computed>"
  code_bucket:         "" => "<computed>"
  default_bucket:      "" => "<computed>"
  default_hostname:    "" => "<computed>"
  feature_settings.#:  "" => "<computed>"
  gcr_domain:          "" => "<computed>"
  location_id:         "" => "us-central"
  name:                "" => "<computed>"
  project:             "" => "line-terraform-0000"
  serving_status:      "" => "<computed>"
  url_dispatch_rule.#: "" => "<computed>"

ここで<computed>はアプリケーションの生成時に決定されるものです。

状態ファイル(state)を設定する

backendを指定することによって.tfstateであるものにできます。

terraform {
  backend "gcs" {
    bucket  = "tf-state-prod"
    prefix  = "terraform/state"
  }
}

クリーンアップ

以下のコマンドですべて削除できます。

terraform apply .terrform/

おまけ

バリデーションやフォーマットを直すことができます。

terraform validate .terraform

またバリデーションが終わるとフォーマットすることをおすすめします。

terraform fmt

これによって差分が出にくくなります。

まとめ

Terraformによって構築をすることができました。 しかし、アーキテクチャを作っているだけでアプリケーションがデプロイできているわけではありません。

今度はAnsibleを用いて構成管理をやってみようかなと思います。