GCPのCloud Functionsの管理にTerraformを使う機会があったので、簡単に紹介します。Terraformのバージョンはv0.12でCloud FunctionsのRuntimeはpython3.7です。
単純にTerraformを使ってみるだけであれば、ほとんどドキュメントのサンプルそのままで動いてしまうため、その他の構成管理ツールの選択肢としてServerless Frameworkと比較した時の個人的な所感も簡単にまとめました。

ディレクトリ構成

ディレクトリ構成はこんな感じです。

.
├── src
│   ├── main.py
│   ├── requirements.txt
│   └── output
│       └── functions.zip
├── provider.tf
├── functions.tf
└── terraform.tfstate

Terraformを利用するに辺りディレクトリ構成は重要ですが、今回は複雑な構成は考えずとりあえず .tf は平置き、 tfstate も一旦ローカル管理のままにしておきます。実際に利用する場合は Terraform CloudCloud Storage などで tfstate を管理することをオススメします。

ディレクトリ構成に関しては様々なブログなどでベストプラクティスが記事になっているので、気になった方はそちらを参照してみてください。

functionsのソースコードは ./src ディレクトリにまとめ、デプロイするためのパッケージは ./src/outputmain.pyrequirements.txt をzipでまとめて配置しています。

テンプレート

テンプレートファイルの中身は以下のようになっています。

# ./provider.tf

resource "google_storage_bucket" "functions_bucket" {
  name     = "0e88f73a-1aee-4122-91c2-329e6090fca6"
  location = "asia-northeast1"
}

resource "google_storage_bucket_object" "functions_packages" {
  name   = "app/functions-${formatdate("YYYYMMDD-hhmm", timestamp())}.zip"
  bucket = "${google_storage_bucket.functions_bucket.name}"
  source = "./src/output/functions.zip"
}

resource "google_cloudfunctions_function" "function" {
  name        = "sample-function"
  description = "hello world"
  runtime     = "python37"

  available_memory_mb   = 128
  source_archive_bucket = "${google_storage_bucket.functions_bucket.name}"
  source_archive_object = "${google_storage_bucket_object.functions_packages.name}"
  timeout               = 10
  entry_point           = "handler"
  trigger_http          = true
}

構成要素は3つ

  • google_storage_bucket_object
    パッケージ化し、Cloud Storageにアップロードするソースコード
  • google_storage_bucket
    ソースコードを配置するためのバケット
  • google_cloudfunctions_function
    Cloud Functions本体

利用したソースコードは、Cloud Functionsのサンプルコードです。

# ./src/main.py

def handler(request):
    request_json = request.get_json()
    if request.args and 'message' in request.args:
        return request.args.get('message')
    elif request_json and 'message' in request_json:
        return request_json['message']
    else:
        return f'Hello World!'

デプロイ方法

ソースコードをzipに固めた後、デプロイを行う。
デプロイはコマンド一発でTerraform applyを実行すると、 TerraformがCloud Storageへパッケージをアップロード -> Functionsへデプロイ まで自動で実行してくれます。

# パッケージ作成
zip -r ./src/output/functions.zip . -i ./src/main.py ./src/requirements.txt

# デプロイ
terraform apply

問題点

  1. コードのパッケージングは自分で行う必要がある
    Terraformが自動ではソースコードやライブラリをzipに固めてはくれないため、何らか方法を考える必要があります。Terraformのコマンドも含めMakefileなどでデプロイまで1コマンドで自動化してしまうのも手だとは思います。

  2. Cloud Storageにアップロードしたパッケージをバージョン管理する事ができていない
    Terraformの管理方針の都合上、デプロイの度にCloud Storageに上げたオブジェクトが再作成されてしまうため、過去にデプロイしたパッケージを残す事ができない。他に良い方法やオプションで回避できない方法が無いか模索中ですが、今の所解決方法がわかりません。

Serverless Frameworkを使わなかった理由

AWSではServerless Framework(sls)を使っていたので、初めはslsを使った管理方法を考えていたのですが、現状の自分のスキルセットを踏まえた時に以下のような問題があったのでTerraformを選択しました。

  1. Terraformは利用した事があるが、GCP版CloudFormationであるDeployment Managerを使った事がない
  2. slsはGCPのDeployment Managerのテンプレートを中間層に利用しているため、必要に応じてDeployment Managerの知識が必要になる
  3. 複数プロジェクトへデプロイする事を想定した場合鍵管理が辛い(少し調べた限り、プロジェクト毎にGCPのサービスアカウントの鍵が必要?)

Functions以外のリソースを一緒に管理しようと考えた時、特に2つ目の問題が障壁になり、悩んだ結果Terraformを利用する事にしました。slsでもCloud Storageを作ったり、CloudSQLインスタンスを作ることなどもできますが、そのためにはresourceセクションでDeployment Managerのyamlを記述する必要があります。新たにDeployment Managerを覚えたくなかったため、今回はTerraformを採用しました。

3つ目の問題に関しては 複数人で開発、かつ手元からデプロイを実行したい 場合を想定すると鍵管理がかなり辛いですが、CI/CD環境を整備して自動化しまえば問題ないのかなと思っています。

ちなみにslsを使えばTerraformを使った時の問題点に挙げていた以下の2点は解決します。

  1. コードのパッケージングは自分で行う必要がある
  2. Cloud Storageにアップロードしたパッケージをバージョン管理する事ができていない

現状の自分のスキルセットの問題もあり採用は見送りましたが、Deployment Managerの知識がある方やこれから使っていこうという方はslsでも良いと思います。

まとめ

Terraformを使ってCloud Functionsをデプロイする方法について簡単に紹介しました。
Terraformを使う事でCloud Functionsがコマンド一発でデプロイできるようになります。
今回はTerraformを紹介しましたが、Cloud Deployment Managerが使える方は構成管理ツールとしてServerless Frameworkを選択しても良いかもしれません。