Closed4

【Terraform GCP検証】GAE で DBマイグレーション、バージョントラフィック移行などのライフサイクルをどう表現するか

waddy_uwaddy_u

やってみてわかった結論

  • 少なくとも 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 だと自動でやってくれるし。
  • ビルドとデプロイの Cloud Build トリガーを分けたほうが良さそう
  • デプロイ側で terraform を使ってデプロイを試みる
waddy_uwaddy_u

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

え?存在しない?そんははずは…

まったく同じ症状で困っておられる方が。

https://qiita.com/Suguru_Toyohara/items/7ba13e525d41c6c5454c

僕も 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 アプリケーションを作成してみる

自分の記事を参考に。

https://zenn.dev/waddy/articles/ruby-remote-container

データベースにつなぎたいのと、APIモードでOKな点が今回違うところ。

  • Rubyの Dockerfile を作成する
  • Rails と postgresql が実行できる docker-compose.yml を作成する

ここまでは一緒。rails newするところで、

docker-compose run --no-deps web rails new . --force --api --database=postgresql

としました。

https://qiita.com/k-penguin-sato/items/adba7a1a1ecc3582a9c9

これを参考にモデルを作り、マイグレーションしていきます。

$ 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 がないというエラー。何だ?

https://fuqda.hatenablog.com/entry/2019/03/21/204118

キャッシュで 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 を設定するべきですねこれは。

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のルーティングを全く設定していない。

config.rb
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 上でやっていきます。

waddy_uwaddy_u

Rails アプリを GAE + Cloud SQL としてデプロイ with Terraform

まずTerraformは置いといて、うごかすためには最終的にどういう構成になればいいかを探ります。

https://qiita.com/hypermkt/items/213e59bedb6689fc862f

なるほどわからん。

  • GAE フレキシブルのしごとなのか
  • SQL Proxy のしごとなのか
  • CloudBuild なのか

がわからん。いったん公式ドキュメントをみていくのがよさそう。

https://cloud.google.com/ruby/rails/using-cloudsql-postgres?hl=ja

このあたりか。

waddy_uwaddy_u

Cloud Build からterraform を使ってデプロイする試み

間が空いたけどこの期間でだいぶ理解が深まりました。ビルド自体はCloud Buildで完結できるようになっていて、あとはデプロイをどうやるかというところ。デプロイ自体も、terraform の app_engine_version をうまく使えば実現できそうです。手元からはできるようになったので、これをあとは Cloud Build にできればのせたい。

Terraform on Cloud Build を検討

すでに手元からはデプロイできるのでどうやって Cloud Build 上でデプロイしていくか考えていきます。基本路線はやはり Dokcer Image を使うことになるのか?というところ。

https://hub.docker.com/r/hashicorp/terraform/

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 で実行できるようにできた。

このスクラップは2021/08/23にクローズされました