Rubyと筋肉とギターとわたし

筋トレが仕事です

【Rails】unicorn.rbでRails.rootが使えない

どうもてぃです。

本番環境を作ってます。環境ってすごく大事ですよね。

今回何も考えずデフォルトのサーバーpumaを使っていたので、unicornに移行しようと設定していたところ起きた問題です。

環境

起きたこと

worker_processes Integer(ENV['WEB_CONCURRENCY'] || 3)
timeout 15
preload_app true

listen Rails.root.join('tmp', 'unicorn.sock')
pid    Rails.root.join('tmp', 'unicorn.pid')

before_fork do |server, worker|
  Signal.trap 'TEAM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill('QUIT', Process.pid)
  end

  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothind. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

stderr_path File.expand_path('log/unicorn.log', Rails.root)
stdout_path File.expand_path('log/unicorn.log', Rails.root)

上記がunicornの設定ファイルです。

これを元にサーバー起動をしてみたところ、以下のエラーが発生。

# lib/tasks/unicorn.rake を実行する
$ bundle exec rails unicorn:start

unicorn -c /home/user/project/config/unicorn.rb -E development -D
Traceback (most recent call last):
    9: from /home/user/project/vendor/bundle/ruby/2.5.0/bin/unicorn:23:in `<main>'
    8: from /home/user/project/vendor/bundle/ruby/2.5.0/bin/unicorn:23:in `load'
    7: from /home/user/project/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/bin/unicorn:126:in `<top (required)>'
    6: from /home/user/project/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/bin/unicorn:126:in `new'
    5: from /home/user/project/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/http_server.rb:77:in `initialize'
    4: from /home/user/project/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/http_server.rb:77:in `new'
    3: from /home/user/project/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/configurator.rb:77:in `initialize'
    2: from /home/user/project/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/configurator.rb:84:in `reload'
    1: from /home/user/project/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/configurator.rb:84:in `instance_eval'
/home/user/project/config/unicorn.rb:5:in `reload': uninitialized constant #<Class:#<Unicorn::Configurator:0x000055a569137798>>::Rails (NameError)
master failed to start, check stderr log for details
rails aborted!
Command failed with status (1): [unicorn -c /home/user/projec...]
/home/user/project/lib/tasks/unicorn.rake:5:in `block (2 levels) in <main>'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/command.rb:48:in `invoke'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands.rb:18:in `<main>'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `block in require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => unicorn:start
(See full trace by running task with --trace)

たいていどこのファイルでも問題なくRailsを使えるので何も疑ってなかったのですが、色々調べたところ使えないようです。

やったこと

require 'rails'をやってみたんですが、Rails.rootnilで返ってくる。

故に、joinメソッドでNoMethodErrorが起きるという事態。

そのため、大人しく絶対パスを使って書き直すことにした。

worker_processes Integer(ENV['WEB_CONCURRENCY'] || 3)
timeout 15
preload_app true

# listen Rails.root.join('tmp', 'unicorn.sock')
# pid    Rails.root.join('tmp', 'unicorn.pid')
listen '/home/user/project/tmp/unicorn.sock'
pid '/home/user/project/tmp/unicorn.pid'

before_fork do |server, worker|
  Signal.trap 'TEAM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill('QUIT', Process.pid)
  end

  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothind. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

stderr_path File.expand_path('log/unicorn.log', '/home/user/project/')
stdout_path File.expand_path('log/unicorn.log', '/home/user/project/')

これで解決。

おわりに

環境変数を設定すればいいのかもなーと思いました。

本番環境ではそれで行こうかな。

【Git】現在のブランチを取得して、git branchを打たないようにする

どうもてぃです。

皆さん開発してますか?僕は全然出来てません。

時に開発すると、自分が異常にgit branchをしていることに気づき、もうどうしようもないくらい同じコマンドを打ちたくない気持ちでいっぱいだったので、今回それを解決しました。

参考

すでに神がいました。

blog.penginmura.tech

こちらを参考にさせていただきます、ありがとうございます。

やったこと

aliasを作成して、push時、pull時にbranchを打たなくていいようにします。

alias.bashrcへ記載します。それぞれの環境で.bash_profileなどに書き換えてください(alias書いてるとこならどこでもいいんじゃないすか)。

alias push="git rev-parse --abbrev-ref HEAD | xargs git push origin"
alias pull="git rev-parse --abbrev-ref HEAD | xargs git pull origin"

git rev-parse --abbrev-ref HEAD で取得したブランチ名をxargsコマンドで渡してあげる。これだけ。

終わりに

pushするときに多用していたgit branch、その後にコマンドラインにあるブランチ名をコピーしてペーストする手間をなくしました。

これでbranchの煩わしさから逃れられますね。

…checkoutするときにはgit branchが必須なんですけど。

【備忘録】Railsでcheck_box_tagがfalseのときもパラメータを飛ばしたい

Rails速習実践ガイドめっちゃいいね、どうもてぃです。

さて、昔実装したのに、しばらくやってないと忘れることよく有りますよね。

今回は備忘録として忘れてたことを書きたいと思います。

環境

やったこと

check_box_tagでパラメータを飛ばす場合、デフォルトではチェックのついたものしか飛びません。ということを完全に忘れていて、軽く30分くらい悩みました。

なので、適切な対処が必要。

まずはcheck_box_tagのリファレンスを確認。おなじみのrailsdocです。

<%= check_box_tag 'page[freezeflag]', true, false, {} %>
# <input id="page_freezeflag" name="page[freezeflag]" type="checkbox" value="true" />

これ使ってみます。

check_box_tagの第一引数はパラメータのkey名 & 属性のIDとname、第二引数は渡す値、第三引数は初期値、第四引数は状態を表してます。

チェックがついていると、strongparameterの中に

{ "page" => <ActionController::Parameters { "freezeflag" => "true" } permitted: false> }

という形で入ります。チェック無しだとそもそも、params["page"]nilになります。

これはデータを更新する上で不便。チェックがあるかどうかで無駄な処理を書かなきゃいけないのはスマートじゃないです。

それを解消するのがhidden_field_tag

必ずcheck_box_tagの前に設置しましょう!

それが以下。

<%= hidden_field_tag 'page[freezeflag]', false %>
<%= check_box_tag 'page[freezeflag]', true, false, {} %>
# <input id="page_freezeflag" name="page[freezeflag]" type="checkbox" value="true" />

check_box_tagにチェックが入らなければ同じ名前のhidden_field_tagの値(false)がパラメータとして飛ぶ。

逆にチェックがついていればcheck_box_tagの方で値が上書きされるという感じ。

必ずnameは同じにしないといけないことに注意。あと最初に言ったけど、hidden_field_tagの位置にも注意。

これで今回の要件を満たせます。

番外編(おまけ)

素晴らしいメソッドを見つけました。

ActiveRecord::Type::Boolean.new.castです。

以下の記事に書いてありました。素晴らしいです。ありがとうございます。

qiita.com

なので、今回飛ばしている値を変更します。

<%= hidden_field_tag 'page[freezeflag]', 0 %>
<%= check_box_tag 'page[freezeflag]', 1, false, {} %>
# <input id="page_freezeflag" name="page[freezeflag]" type="checkbox" value="true" />

これで、パラメータとしてチェック済みの時は文字列の1、チェック無しだと文字列の0が飛びます。

上の記事のように、models/applilcation_record.rbへ専用のメソッドを書いておくと。。。

def checked?(data)
  ActiveRecord::Type::Boolean.new.cast(data)
end

checked?の引数に飛んできたチェックボックスのパラメータを渡すとtrue or falseを判定してくれます。

すばらしい。感動しました。

【Ruby】スクレイピングに精度を求めるならNokogiriではなくSeleniumだ

どうもてぃです。

とある巨大ECサイト(通称熱帯雨林)をスクレイピングして遊んでいます。

今回は nokogiri で問題が発生したので記事にしました。

nokogiriのスクレイピング精度はあんまり良くないのを実体験で痛感しましたね。

environment

issue

ページネーション要素の取得です。

Nokogiriだと4〜6割の確率でページネーションを取得できない場合があります。

問題なのが、Seleniumだとページを読み込み終わるまで待つSelenium::WebDriver::Waitがあるのに対して、Nokogiriにはwaitが存在しません。

あと、NokogiriではJS等で動的に表示しているページは上手く取得できないことがあるとのこと。

だからSeleniumブラウジングしてスクレイピングする方が成功確率は高いと。

最初からSelenium使えばよかったですわ。PythonSeleniumだし。

solution

試しに、一番お手軽なスクレイピングサイト、巨大ECサイト熱帯雨林を使ってみます。

ここだと、ページネーションもありますので。

最近switchのスマブラが販売されて転売ヤーさんが乱立しているGCコンをスクレイピングしてみます。

require 'selenium-webdriver'
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36"

# headlessモードのoptionをつける
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument("--user-agent=#{ua}")

driver = Selenium::WebDriver.for(:chrome, options: options)
driver.get('https://www.amazon.co.jp/gp/offer-listing/B07HC2F97Q/ref=dp_olp_new?ie=UTF8&condition=new')

# ページ読み込みのwaitを設定
wait = Selenium::WebDriver::Wait.new(timeout: 10)

# find_elementで単数取得
wait.until { driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a") }
seller_info = driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a")
wait.until { driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) } }
pagination = driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) }

# ページネーションの最後までクリックする
loop.with_index(1) do |_, i|
  puts "==================================#{i}回目==================================="
  puts driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']").map(&:text)

  break if driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").last[-1] == '#'
  driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").last.click
end

# 二回目のget urlも問題ない
driver.get('https://www.amazon.co.jp/sp?_encoding=UTF8&seller=A2W8UAZHGIMLV7')

driver.close

上記ソースを超簡単に説明。

# headlessモードのoptionをつける
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument("--user-agent=#{ua}")

Selenium::WebDriver::Chrome::Options.newSeleniumのオプションを設定するインスタンスを生成します。

AWSGCPで動かすことも想定にいれて、ヘッダレスでブラウザを起動しないようなオプションをつけています。

ついでにユーザーエージェントも設定してます。

# ページ読み込みのwaitを設定
wait = Selenium::WebDriver::Wait.new(timeout: 10)

スクレイピングをした際にページが全て表示されるまで待つ必要が有ります。

その設定を上記でやっています。使い方は

wait.until { driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a") }
seller_info = driver.find_elements(:xpath, "//h3[@class='a-spacing-none olpSellerName']/span/a")
wait.until { driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) } }
pagination = driver.find_elements(:xpath, "//ul[@class='a-pagination']/li/a").map { |el| el.attribute(:href) }

のように、要素を取得する前にwait.utilを設定します。簡単です。

残りは要素の取得の処理がメインですね。

loopの部分でページネーションのリンクをクリックし続ける処理をやっています。そんな大したことはやってないです。

最後に

やはり、困った時はrubydocが役に立ちます。

Class: Selenium::WebDriver::Driver — Documentation for selenium-webdriver (0.0.28)

是非参考にしてみてください。

なにかあればコメント欄で〜。

ではでは。

【スクレイピング】HerokuでSelenium::WebDriver::Error::SessionNotCreatedError: session not created from tab crashed

あけましておめでとうございます。

どうもてぃです。

HerokuでSeleniumを使ったスクレイピングをした際にエラーが不規則に出たので共有します。

環境

参考

公式のリポジトリをまず参考にしました。issueが上がってましたね。

github.com

要するに、Heroku上のchromedriverのバージョンがサポートされているかどうか問題で動いたり動かなかったりするらしい。

最後の文にも書いてる通り

Yep, v59 confirmed. Not sure how folks are using this pre-59 :)

https://developers.google.com/web/updates/2017/04/headless-chrome

If this issue persists after v59 and associated Chromedriver are released, I'll re-open it.

chromedriverがリリースされた後に問題が上がればまたissueをopenにするぜ

だって。

対策

このスクレイピングに関しては絶対にうまく行くという保証ができないので、エラーが出た際に落ちないようにするしか無いです。

もしくはAWSGCPを使うか、ですね。

今回は一時的にテスト環境としてHerokuを使っているので、本番環境ができるまではHerokuで我慢し、エラーをキャッチする方法で対処します。

require 'selenium-webdriver'

# selenium headlessモードでchromeを立ち上げる
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')
options.add_argument('--disable-gpu')

# userAgentの設定
webdriver = Webdriver::UserAgent.driver(browser: :chrome, agent: :random, orientation: :landscape)
user_agent = webdriver.execute_script('return navigator.userAgent')
options.add_argument("--user-agent=#{user_agent}")

begin
  driver = Selenium::WebDriver.for(:chrome, options: options)
rescue => e
  # Selenium::WebDriver::Error::SessionNotCreatedError: session not created from tab crashed
  puts e
  next
end

driver.get(url) # urlにはスクレイピングしたいurlをセットする

終わりに

一旦はこれで解決です。

日本語での解決方法がなかなかみつからなかったので記事にしました。

他に解決方法がありましたら教えていただけると幸いです。

またGitHubの方で進展が有りましたら記事にすると思います。

では、今年もよろしくお願いしますー。

【Ruby】スクレイピングをするときはUserAgentを設定しましょう

どうもてぃです。

現在業務でスクレイピングをしたデータをシステム上でよしなにやって、楽しく過ごしています。

今回とある、巨大なECサイト(俗称:熱帯雨林)をスクレイピングした際にエラーに見舞われたので、解決策を残しておこうと思います。

実行環境

つかったもの

Rubyスクレイピングといえばnokogiriですよね。

もちろん、今回もこの子を使いました。

何が起こったか

以下がソース。

一般的な使い方ですね。

require 'open-uri'
require 'nokogiri'

sufixes = [1, 2, 3, 4]
sufixes.each do |sufix|
  url = Constants::BASE_URL + suffix
  charset = nil

  begin
    html = open(url) do |f|
      charset = f.charset
      f.read
    end
  rescue => e
    puts e
    next
  end

  doc = Nokogiri::HTML.parse(html, nil, charset)
 ・
 ・
 ・
end

上記の書き方だと、スクレイピング先が多くなればなるほど、一定確率で 503エラーが発生します。

これは、スクレイピングするURLをopenするときに、ユーザーエージェントが指定していないため発生するそう。

なので、擬似的にユーザーエージェントを指定してあげる。

解決方法

開発者コンソールを開いて、Network -> ctrl + rで更新 -> bookmarkのファイルを選択 -> RequestHeaderのユーザーエージェントをコピーして適当に使う。

Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/xxxxxx (KHTML, like Gecko) Chrome/xxxxxx Safari/xxxxx

こんな感じのやつ。

これでなくても、ユーザーエージェントを偽装してやればいいので、正直言えばなんでもいい。

require 'open-uri'
require 'nokogiri'

opt = {}
opt['User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/xxxxxx (KHTML, like Gecko) Chrome/xxxxxx Safari/xxxxx'

sufixes = [1, 2, 3, 4]
sufixes.each do |sufix|
  url = Constants::BASE_URL + suffix
  charset = nil

  begin
    # open時にぶち込む
    html = open(url, opt) do |f|
      charset = f.charset
      f.read
    end
  rescue => e
    puts e
    next
  end

  doc = Nokogiri::HTML.parse(html, nil, charset)
 ・
 ・
 ・
end

こうすると、503エラーでスクレイピング失敗することがなくなります。

nokogiriでこんなエラー出たの初めてだったので結構困りました。

解決できてよかった。

【GAE + CloudSQL + Rails】bundle exec rails appengine:exec -- bundle exec rails db:migrate時にCloudSQLで接続エラーになる

 f:id:rdwbocungelt5:20181112160913p:plain

どうもてぃです。

今回始めてGAEを使います。

gemのwheneverがheroku上で使えなかったので、もういっそのことステージング環境を作ってしまい、あとからそのスナップショットで本番環境を作ろうと企んでいたところ、そもそも初っ端からつまづきました。

環境

まずは参考記事

いろんな記事やリファレンスを参考にしました。

[cloudsql] Error 403: The client is not authorized to make this request., notAuthorized · Issue #10 · GoogleCloudPlatform/kubernetes-engine-samples · GitHub

Google App Engine から接続する  |  Cloud SQL ドキュメント  |  Google Cloud

Google Compute Engine(GCE)からCloud SQL接続でハマった - Qiita

RailsアプリをGAEにデプロイしCloudSQL, CloudStorageと連携させる - Qiita

GoogleAppEngine+CloudSQL(MySQL)+Rails5環境を作成する - Qiita

GAEにデプロイしたRailsからGoogle Cloud SQL に疎通できない時に確認すること - 俺、サービス売って家買うんだ

約一週間ほど溶かしました。。。

まだ完璧に解決したわけではないですが。

試したこと

Cloud SQLの有効化

こちらにアクセスしてCloud SQLを有効化します。

console.cloud.google.com

ダッシュボードにきたら「APIとサービスの有効化」をクリック。

f:id:rdwbocungelt5:20181112154547p:plain

検索フォームが出てくるので、「Cloud SQL」と入力。

f:id:rdwbocungelt5:20181112155028p:plain

出てきたCloud SQLをクリックし、遷移先でAPIを有効化させたらおk(画像貼るのめんどくさくなった)。

Unixソケットでのプロキシ接続

以下の手順で接続しました。

環境が64bitのLinuxなので

https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64

wgetする。

$ cd ~; wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy

$ chmod +x cloud_sql_proxy

$ sudo mkdir /cloudsql; sudo chmod 777 /cloudsql

$ ./cloud_sql_proxy -dir=/cloudsql -instances=<INSTANCE_CONNECTION_NAME>

INSTANCE_CONNECTION_NAMEgcloud sql instances describe [INSTANCE_NAME]で出てくるconnectionNameを使います(INSTANCE_NAMECloudSQLの名前)。

もしくはブラウザからGCPにアクセスして、プロジェクトのCloudSQLで該当のSQLインスタンスを見ればわかると思います。

プロキシで接続すると、

2018/11/12 15:40:17 Rlimits for file descriptors set to {&{8500 1048576}}
2018/11/12 15:40:23 Listening on /cloudsql/<INSTANCE_CONNECTION_NAME>/.s.PGSQL.5432 for <INSTANCE_CONNECTION_NAME>
2018/11/12 15:40:23 Ready for new connections
。

と出てきます。

問題なく接続できてるようです。認証もできてるはず。

gcloudでsqlに接続してみる

CloudSQLにgcloudで接続してみます。

$ gcloud sql connect <INSTANCE_NAME> --user=<USER_NAME>

Whitelisting your IP for incoming connection for 5 minutes...done.                                                                                                                            
Connecting to database with SQL user [USER_NAME].Password for user <USER_NAME>: 
psql (9.5.14, server 9.6.6)
WARNING: psql major version 9.5, server major version 9.6.
         Some psql features might not work.
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES128-GCM-SHA256, bits: 128, compression: off)
Type "help" for help.

USER_NAME名=>

うん、立ち上がります。

大丈夫そう。

ローカルからCloudSQLへマイグレーション

おそらく準備はある程度できているので(app.yaml環境変数も作った)、あとはappengine経由でマイグレーションするだけ。

デプロイも終わってる。

$ bundle exec rails appengine:exec -- bundle exec rails db:migrate
・
・
・
・
・
・
・
・
・
・
・
・

---------- CONNECT CLOUDSQL ----------
ERROR: Failed to start cloud_sql_proxy
2018/11/12 04:58:03 errors parsing config:
    googleapi: Error 403: The client is not authorized to make this request., notAuthorized
ERROR
ERROR: build step 0 "gcr.io/google-appengine/exec-wrapper:latest" failed: exit status 1
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

ERROR: (gcloud.builds.submit) build 33763c5f-5791-490b-bade-8e3dda956688 completed with status "FAILURE"
rails aborted!
AppEngine::Util::Gcloud::GcloudFailed: GCloud failed with result code 1
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/util/gcloud.rb:175:in `execute'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/exec.rb:302:in `start'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/tasks.rb:255:in `start_and_report_errors'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/tasks.rb:155:in `block in setup_exec_task'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/command.rb:48:in `invoke'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands.rb:18:in `<main>'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `block in require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => appengine:exec
(See full trace by running task with --trace)

なんで?

問題はこいつかな。

googleapi: Error 403: The client is not authorized to make this request., notAuthorized

IAM周りを見直し

とりあえずサービスアカウントが作成されているか確認しました。

<プロジェクトID>@appspot.gserviceaccount.comがあるかどうかブラウザで確認。存在してましたネ。

ちなみにgcloud projects get-iam-policy <PROJECT_ID>で該当のプロジェクトのIAMが取得できます。

もうひとつ足りなかった

CloudSQL用のIAMで権限が足りないようでした。

以下の記事をちゃんと見なおしたら発覚。

qiita.com

<プロジェクト番号>@cloudbuild.gserviceaccount.comに対してのroleが正しく割り当てられてませんでした。

なので、roleをEditor(編集者)へ変更。

もう一度マイグレーションしてみる

$ bundle exec rails appengine:exec -- bundle exec rails db:migrate
・
・
・
・
・
・
・
・
・
・
・
・

---------- CONNECT CLOUDSQL ----------
cloud_sql_proxy is running.

---------- EXECUTE COMMAND ----------
bundle exec rails db:migrate
rails aborted!
PG::ConnectionBad: could not connect to server: Connection refused
    Is the server running on host "localhost" (127.0.0.1) and accepting
    TCP/IP connections on port 5432?
could not connect to server: Cannot assign requested address
    Is the server running on host "localhost" (::1) and accepting
    TCP/IP connections on port 5432?
/app/vendor/bundle/ruby/2.5.0/gems/pg-1.1.3/lib/pg.rb:56:in `initialize'
/app/vendor/bundle/ruby/2.5.0/gems/pg-1.1.3/lib/pg.rb:56:in `new'
/app/vendor/bundle/ruby/2.5.0/gems/pg-1.1.3/lib/pg.rb:56:in `connect'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:684:in `connect'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:215:in `initialize'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:40:in `new'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/postgresql_adapter.rb:40:in `postgresql_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:809:in `new_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:853:in `checkout_new_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:832:in `try_to_checkout_new_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:793:in `acquire_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:521:in `checkout'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:380:in `connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:1008:in `retrieve_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_handling.rb:90:in `connection'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/tasks/database_tasks.rb:172:in `migrate'
/app/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/railties/databases.rake:60:in `block (2 levels) in <main>'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/command.rb:48:in `invoke'
/app/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands.rb:18:in `<main>'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
/app/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
/app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `block in require'
/app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
/app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
ERROR
ERROR: build step 0 "gcr.io/google-appengine/exec-wrapper:latest" failed: exit status 1
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

ERROR: (gcloud.builds.submit) build c15c595c-6d32-4aa0-9411-397d95b3f12e completed with status "FAILURE"
rails aborted!
AppEngine::Util::Gcloud::GcloudFailed: GCloud failed with result code 1
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/util/gcloud.rb:175:in `execute'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/exec.rb:302:in `start'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/tasks.rb:255:in `start_and_report_errors'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/appengine-0.4.6/lib/appengine/tasks.rb:155:in `block in setup_exec_task'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/command.rb:48:in `invoke'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/commands.rb:18:in `<main>'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `block in require_with_bootsnap_lfi'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/loaded_features_index.rb:65:in `register'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:20:in `require_with_bootsnap_lfi'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/bootsnap-1.3.2/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:29:in `require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `block in require'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
/home/user/project/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => appengine:exec
(See full trace by running task with --trace)

一旦 googleapi: error 403地獄から抜け出しました。

あとはPG::ConnectionBadの解決のみ。

後日書きます。

追記

すぐに解決しました。

config/database.ymlproductionhostを設定してなかったせいでした。

app.yaml構成はこんな感じです。

runtime: ruby
env: flex
entrypoint: bundle exec rackup --port $PORT
skip_files:
  - ^vendor
automatic_scaling:
  min_num_instances: 1
  max_num_instances: 5
  cool_down_period_sec: 120
  cpu_utilization:
    target_utilization: 0.6
beta_settings:
  cloud_sql_instances:  <INSTANCE_CONNECTION_NAME>
threadsafe: true
includes:
  - secret.yaml

secret.yamlに必要な環境変数をセットしてます。

database.ymlのpasswordやhost、socketもここに書いてます。

database.ymlがこんな感じ。

・
・
・
・
・
・
・
・
・
production:
  <<: *default
  host: <%= ENV['POSTGRES_HOST'] %>
  database: <%= ENV['POSTGRES_DATABASE'] %>
  username: <%= ENV['POSTGRES_USER'] %>
  password: <%= ENV['POSTGRES_PASSWORD'] %>
  socket: <%= ENV['CLOUDSQL_STAGING_SOCKET'] %>

環境変数名が違いますが、 sockethost は同じで /cloudsql/<INSTANCE_CONNECTION_NAME> をセットしてます。

hostを設定し、デプロイ後もう一度試したら念願のマイグレーションが通りました。

ホント長かった。

もうこの手順は忘れません。