これは、なにをしたくて書いたもの?
Terraformの勉強にと、Herokuを使って、アプリケーションとデータベースをデプロイする超簡単な例を作って試してみようと。
お題
Herokuのフリープランで利用可能なPostgreSQLを使い、以下のサンプルとほぼ同等なアプリケーションをHerokuにデプロイ
してみます。
information_schema、tables内のtable_schema、table_nameを取得するだけの、簡単なサンプルです。
環境
今回の環境は、こちら。
$ terraform version Terraform v0.11.13
TerraformのHeroku Providerは、1.9.0を使用します。
また、お題でも書きましたが、Herokuはフリープランで使っています。
Terraformのリソース定義ファイル
それでは、最初にTerraformのリソース定義ファイルを作成します。
今回は、アプリケーションをデプロイするので「heroku_app」と「heroku_build」を使い、
Heroku: heroku_app - Terraform by HashiCorp
Heroku: heroku_build - Terraform by HashiCorp
PostgreSQLのデプロイには「heroku_addon」を使用します。
Heroku: heroku_addon - Terraform by HashiCorp
利用するHeroku Postgresのプランは、「hobby-dev」です。
Heroku Postgres - Add-ons - Heroku Elements
で、作成したメインのリソース定義ファイル。
heroku-app-postgresql.tf
provider "heroku" { email = "${var.heroku_email}" api_key = "${var.heroku_api_key}" } resource "heroku_app" "app_postgresql" { name = "app-postgresql" region = "us" } resource "heroku_build" "app_postgresql_build" { app = "${heroku_app.app_postgresql.name}" source = { url = "https://github.com/kazuhira-r/heroku-terraform-sandbox/raw/master/nodejs-app-postgresql.tar.gz" } } resource "heroku_addon" "postgresql" { app = "${heroku_app.app_postgresql.name}" plan = "heroku-postgresql:hobby-dev" }
「heroku_build」でアプリケーションのソースコードアーカイブを参照していますが、これは後でまた。
PostgreSQLのリソース定義は、こちらですね。
resource "heroku_addon" "postgresql" { app = "${heroku_app.app_postgresql.name}" plan = "heroku-postgresql:hobby-dev" }
「hobby-dev」プランを利用するようにしています。
TerraformでHerokuを使う時には、メールアドレスとAPIキーが必要になりますが、今回は変数として切り出すことにしました。
変数宣言をした定義ファイル。
variables.tf
variable "heroku_email" { } variable "heroku_api_key" { }
メインの定義ファイルからは、この内容をvarで参照しています。
provider "heroku" { email = "${var.heroku_email}" api_key = "${var.heroku_api_key}" }
実際に使う値は、tfvarsファイルに記載。
secret.tfvars
heroku_email = "....." heroku_api_key = "....."
このあたりは、こちらを参考に。
Input Variables | Terraform - HashiCorp Learn
Input Variables - Configuration Language - Terraform by HashiCorp
Terraformで利用する際には、以下のように「-var-file」として指定します。
$ terraform apply -var-file=secret.tfvars
とりあえず、ここまでで「terraform init」してHeroku Providerをダウンロードしておきます。
$ terraform init Initializing provider plugins... - Checking for available provider plugins on https://releases.hashicorp.com... - Downloading plugin for provider "heroku" (1.9.0)...
サンプルアプリケーションの作成
それでは、サンプルアプリケーションを作成します。
Node.js+Expressで、簡単なREST APIとして作ってみましょう。ExpressでPostgreSQLを使う例としては、以下を参照しました。
PostgreSQLとの接続には、pg-promiseを使います、と。
GitHub - vitaly-t/pg-promise: Promises/A+ interface for PostgreSQL
Expressとpg-promiseのインストール。
$ npm i express pg-promise
インストールされたライブラリのバージョン。
"dependencies": { "express": "^4.16.4", "pg-promise": "^8.7.2" }
作成したソースコード。
server.js
const express = require('express'); const pgp = require('pg-promise')({ }); const app = express(); const postgresql = pgp(`${process.env.DATABASE_URL}?ssl=true`); const port = process.env.PORT || 3000; app.get('/', async (req, res) => { return postgresql .any('SELECT table_schema,table_name FROM information_schema.tables') .then(data => res.send(JSON.stringify(data))); }); app.listen(port, () => console.log(`[${new Date()}] Server startup, port = ${port}`));
Connection StringとSSLの設定については、Herokuのドキュメントを参考にしています。
const postgresql = pgp(`${process.env.DATABASE_URL}?ssl=true`);
このスクリプトを「npm start」で起動できるようにして
"scripts": { "start": "node server.js", "test": "echo \"Error: no test specified\" && exit 1" },
tar.gzで固めて、今回はGitHubに起きました。
https://github.com/kazuhira-r/heroku-terraform-sandbox/raw/master/nodejs-app-postgresql.tar.gz
中身は、こんな感じです。
$ tar -tvf nodejs-app-postgresql.tar.gz -rw-rw-r-- xxxxx/xxxxx 487 2019-05-13 23:09 nodejs-app-postgresql/server.js -rw-rw-r-- kazuxxxxxhira/xxxxx 19095 2019-05-13 22:33 nodejs-app-postgresql/package-lock.json -rw-rw-r-- xxxxx/xxxxx 344 2019-05-13 22:34 nodejs-app-postgresql/package.json
このURLが、先ほど「heroku_build」のsourceに指定していたアーカイブです。
resource "heroku_build" "app_postgresql_build" { app = "${heroku_app.app_postgresql.name}" source = { url = "https://github.com/kazuhira-r/heroku-terraform-sandbox/raw/master/nodejs-app-postgresql.tar.gz" } }
これで、準備は完了です。
デプロイ
それでは、ここまで定義した内容を、デプロイします。
まずは、「terraform plan」で確認。「terraform plan」時にも、「-var-file」で変数の内容を指定します。
$ terraform plan -var-file=secret.tfvars 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: + heroku_addon.postgresql id: <computed> app: "app-postgresql" config_vars.#: <computed> name: <computed> plan: "heroku-postgresql:hobby-dev" provider_id: <computed> + heroku_app.app_postgresql id: <computed> all_config_vars.%: <computed> config_vars.#: <computed> git_url: <computed> heroku_hostname: <computed> internal_routing: <computed> name: "app-postgresql" region: "us" sensitive_config_vars.#: <computed> stack: <computed> uuid: <computed> web_url: <computed> + heroku_build.app_postgresql_build id: <computed> app: "app-postgresql" buildpacks.#: <computed> local_checksum: <computed> output_stream_url: <computed> release_id: <computed> slug_id: <computed> source.%: "1" source.url: "https://github.com/kazuhira-r/heroku-terraform-sandbox/raw/master/nodejs-app-postgresql.tar.gz" stack: <computed> status: <computed> user.%: <computed> uuid: <computed> Plan: 3 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.
では、デプロイ。
$ terraform apply -var-file=secret.tfvars
やや時間がかかりますが、デプロイが完了しました。
heroku_addon.postgresql: Creation complete after 1s (ID: d86ace58-a0a6-4bb8-9ba1-3def3d22921a) heroku_build.app_postgresql_build: Still creating... (10s elapsed) heroku_build.app_postgresql_build: Still creating... (20s elapsed) heroku_build.app_postgresql_build: Creation complete after 27s (ID: 8e2731d5-2929-44dd-9eea-a246d9d07cf0) Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
確認。
$ curl -s https://app-postgresql.herokuapp.com | jq [ { "table_schema": "pg_catalog", "table_name": "pg_foreign_table" }, { "table_schema": "pg_catalog", "table_name": "pg_roles" }, { "table_schema": "pg_catalog", "table_name": "pg_settings" }, { "table_schema": "pg_catalog", "table_name": "pg_subscription" }, 〜省略〜
できました!
最後に、デプロイしたリソースを破棄しておしまい。
$ terraform destroy -var-file=secret.tfvars
どちらかというと、TerraformというよりNode.jsとHerokuにてこずりましたが、とりあえずやりたいことはできましたよ、と。