【Terraform GCP検証】GAE で DBマイグレーション、バージョントラフィック移行などのライフサイクルをどう表現するか
やってみてわかった結論
- 少なくとも App Engine については、
app.yml
を Cloud Build から実行できるし、トラフィックの移行も Cloud Shell で実行できるので、全てを Terraform に寄せる必要はなさそう - DB や Cloud Tasks など、生成したらそこに居続けるリソースは Terraform で管理する価値がある
- アプリケーションのデプロイサイクルで入れ替わるリソースについては、無理にterrafromに寄せる必要はなし
- 上記ポリシーにしたがって、Cloud Build のビルドトリガーは
cloudbuild.yml
+ terraform で管理するのはアリです
今の所の考え
- マイグレーションタスクも CloudBuild に落とし込んでおくのはどうか
- GAE の バージョンと、ソースコードのバージョンをどうにかして紐付ける必要がありそう。git のタグか、ソースコードのmd5ハッシュか…
- => 結局、terraform 側でアーティファクトZIPを参照し、
md5(data.google_storage_bucket_object.zip.crc32c)
でハッシュ値をバージョンにした - これでうまくいったが、やはり流動的に変わるアプリケーションリソースを宣言的なterraform記述で書くというのは違和感がある。バージョンの計算も
app.yaml
だと自動でやってくれるし。
- => 結局、terraform 側でアーティファクトZIPを参照し、
- ビルドとデプロイの Cloud Build トリガーを分けたほうが良さそう
- デプロイ側で terraform を使ってデプロイを試みる
Cloud SQL をデプロイ - のためにまずはローカルで Rails アプリを作成
まとめ
- terraform -migrate-state は使えなくて terraform -reconfigure した
- まずは手元で Rails アプリを起動してみることに。Docker 周りは繰り返し触らないとおぼえなそう
- bundler が上手くインストールされないことがあって、 docker compose build --no-cache で解消した
以下試行錯誤
GAE バージョン自体のデプロイはできたので次はアプリケーション開発のライフサイクルに乗せることを考えていく。
まずは Cloud SQL をデプロイしないことには始まらない(はず)なのでそこからやっていく。
terraform plan
差分がないことを確認する。initを要求された。ver1.0にしたからかな?
terraform init -backend-config="bucket=artifact"
Initializing modules...
Initializing the backend...
╷
│ Error: Backend configuration changed
│
│ A change in the backend configuration has been detected, which may require migrating existing state.
│
│ If you wish to attempt automatic migration of the state, use "terraform init -migrate-state".
│ If you wish to store the current configuration with no changes to the state, use "terraform init -reconfigure".
なんかmigrateしてと言われました。
terraform init -migrate-state -backend-config="bucket=artifact"
│ Error: Error inspecting states in the "gcs" backend:
│ querying Cloud Storage failed: storage: bucket doesn't exist
え?存在しない?そんははずは…
まったく同じ症状で困っておられる方が。
僕も reconfigure してみます。
terraform init -reconfigure -backend-config="bucket=artifact"
Initializing modules...
Initializing the backend...
Successfully configured the backend "gcs"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Reusing previous version of hashicorp/google from the dependency lock file
- Installing hashicorp/google v3.68.0...
- Installed hashicorp/google v3.68.0 (signed by HashiCorp)
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.
通ったっぽい。migration
is 何。
Rails アプリケーションを作成してみる
自分の記事を参考に。
データベースにつなぎたいのと、APIモードでOKな点が今回違うところ。
- Rubyの Dockerfile を作成する
- Rails と postgresql が実行できる docker-compose.yml を作成する
ここまでは一緒。rails new
するところで、
docker-compose run --no-deps web rails new . --force --api --database=postgresql
としました。
これを参考にモデルを作り、マイグレーションしていきます。
$ docker-compose run --no-deps web bundle install
$ docker-compose run --no-deps web bundle exec rails g model post title:string
Creating backend-rails_web_run ... done
Could not find msgpack-1.4.2 in any of the sources
Run `bundle install` to install missing gems.
ERROR: 7
msgpack-1.4.2
がないというエラー。何だ?
キャッシュで bundle install が実行されない場合があるっぽい!
docker-compose build --no-cache を実行する
キャッシュ…?ひとまず素直に実行する。
docker build . --no-cache
docker compose build --no-cache
$ docker compose run --no-deps web bundle exec rails g model post title:string
Creating backend-rails_web_run ... done
Running via Spring preloader in process 19
invoke active_record
create db/migrate/20210610075749_create_posts.rb
create app/models/post.rb
invoke test_unit
create test/models/post_test.rb
create test/fixtures/posts.yml
お、ほんとにうごいた、ありがとうございます。続きも。
$ docker compose run --no-deps web bundle exec rails g controller posts
$ docker compose run web bundle exec rails db:create
[+] Running 1/1
⠿ Container backend-rails_db_1 Started 2.1s
could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
db:create
で失敗。config/database.yml
を設定するべきですねこれは。
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ host: db
+ username: postgres
+ password: password
あらためて。
$ docker compose run web bundle exec rails db:create
$ docker compose run web bundle exec rake db:migrate
Rails Console でいくつかデータを作成する
docker compose run web bundle exec rails console
> Post.create(title:'title1')
> Post.create(title:'title2')
よさそう。起動してみる。
docker compose up
web_1 | => Booting Puma
web_1 | => Rails 6.1.3.2 application starting in development
web_1 | => Run `bin/rails server --help` for more startup options
web_1 | Exiting
web_1 | A server is already running. Check /myapp/tmp/pids/server.pid.
なぜだ…。Intellij が勝手に作ってしまったかもしれないので、ひとまず手で削除する(起動時にrmしてるんだけども)。
docker compose up
web_1 | => Booting Puma
web_1 | => Rails 6.1.3.2 application starting in development
web_1 | => Run `bin/rails server --help` for more startup options
web_1 | Puma starting in single mode...
web_1 | * Puma version: 5.3.2 (ruby 2.7.2-p137) ("Sweetnighter")
web_1 | * Min threads: 5
web_1 | * Max threads: 5
web_1 | * Environment: development
web_1 | * PID: 1
web_1 | * Listening on http://0.0.0.0:3000
web_1 | Use Ctrl-C to stop
起動した模様。よく考えたらAPIのルーティングを全く設定していない。
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
resources :posts
end
docker compose exec web bundle exec rails routes
Prefix Verb URI Pattern Controller#Action
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
post GET /posts/:id(.:format) posts#show
PATCH /posts/:id(.:format) posts#update
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
rails6.1 から rake routes
じゃなくてrails routes
になったみたいです。
curl http://localhost:3000/posts
{"status":"SUCCESS","message":"Loaded posts","data":[{"id":2,"title":"title2","created_at":"2021-06-10T08:23:16.613Z","updated_at":"2021-06-10T08:23:16.613Z"},{"id":1,"title":"title1","created_at":"2021-06-10T08:23:08.128Z","updated_at":"2021-06-10T08:23:08.128Z"}]}⏎
先程 rails console で追加したデータが意図どおりえられています。ひとまずデータベースとつながったAPIが作成できました。次はこれを GCP 上でやっていきます。
Rails アプリを GAE + Cloud SQL としてデプロイ with Terraform
まずTerraformは置いといて、うごかすためには最終的にどういう構成になればいいかを探ります。
なるほどわからん。
- GAE フレキシブルのしごとなのか
- SQL Proxy のしごとなのか
- CloudBuild なのか
がわからん。いったん公式ドキュメントをみていくのがよさそう。
このあたりか。
Cloud Build からterraform を使ってデプロイする試み
間が空いたけどこの期間でだいぶ理解が深まりました。ビルド自体はCloud Buildで完結できるようになっていて、あとはデプロイをどうやるかというところ。デプロイ自体も、terraform の app_engine_version をうまく使えば実現できそうです。手元からはできるようになったので、これをあとは Cloud Build にできればのせたい。
Terraform on Cloud Build を検討
すでに手元からはデプロイできるのでどうやって Cloud Build 上でデプロイしていくか考えていきます。基本路線はやはり Dokcer Image を使うことになるのか?というところ。
resource を書いてみる。
resource "google_cloudbuild_trigger" "deploy-trigger" {
name = "deploy-app"
github {
owner = "cm-wada-yusuke"
name = "app"
push {
branch = "deploy"
}
}
build {
timeout = "600s"
step {
id = "tf init"
name = "hashicorp/terraform:latest"
entrypoint = "sh"
args = [
"-c",
"terraform init"
]
dir = "packages/infra-gcp"
}
step {
id = "tf apply"
name = "hashicorp/terraform:latest"
entrypoint = "sh"
args = [
"-c",
"terraform apply -auto-approve -target=module.app_version"
]
dir = "packages/infra-gcp"
}
}
}
ポイント的なやつ:
- デプロイタイミングはこちらで制御したいので main ブランチではなく何も触る予定のない deploy ブランチへ反応するようにした。デプロイしたいときは GCP コンソールから実行してブランチを
main
へ上書きする - terraform module を使っているので 実行コマンドで
target
を指定している - これを実行すると一回失敗した。App Engine の Admin APIを 有効にする必要があった模様
これでビルド・デプロイを分離しつつ、どちらも Cloud Build で実行できるようにできた。