あらすじ
身内向けの小さなサービス作ってまわしてたら、Herokuの無償プランが終わった。なのでfly.ioとRailwayに移民したらRailwayの無償プランもおなくなり。そこで白羽の矢が立ったのがGCP。GCPでSeleniumの定期実行スクリプトをまわすよ!
GCPのメリットとデメリット
メリット
とりあえず無料枠があること。基本的に有料になる場合は承認が必要なので、知らず知らず有料になることはないらしいこと。定期実行のCloud Schedulerがあるので、redis+sidekiq+sidekiq schedulerとか使わなくても、cron的な設定も簡単にできること。rubyも対応してること。ただ、Railsが動くのかは知らないし試してない。そういうのはPaaSでやった方がいいのかな、どうなんだろう。
デメリット
今のところRuby対応がわりと最近というのもあって情報が少ないことが挙げられる。あととにかくUIがわけわからんし、サービスの量も多すぎて何が何だかわからない。デプロイコマンドもやたら長い。
あと自分だけなのかわからないが、再デプロイするとなぜかエラーを吐いてまったくデプロイできなくなることがある。そういうときは一旦Functionsからファンクションを削除して、デプロイし直すという行程が必要になることがある。自分が悪いのかもしれないが、何がおかしいのかわからない。
大まかな流れ
- GCPのアカウントを作る
- GoogleCloudCLIをインストールする
- プロジェクトを新規作成する
- 各種APIを有効化する
- Cloud Pub/Subでトピックを作成
- サービスアカウントとやらを作る(よくわかってない)
- Cloud Functionsを新規作成
- またサービスアカウントとやらをつくる(よくわかってない)
- Cloud FunctionsのトリガーにPub/Subで作ったトピックを設定
- Cloud FunctionsにRubyスクリプトをデプロイ
- Cloud Schedulerを新規作成し、cron記法でPub/Subにメッセージを送る間隔を設定する
要するに、Cloud SchedulerがPub/Subにn時間ごとにメッセージを送るように設定して、Pub/Subからメッセージを受けたらFunctionsのスクリプトが動くという感じ。えらいまわりくどい、Schedulerから直接Functions叩ければよくない?とか思う。
今回はとりあえず、以上のなかでスクリプトを書いてデプロイするところだけとりあえずメモしておく。
Cloud Functions内でのスクリプト
デプロイコマンド
以下、全てCloud Functionsにデプロイするという前提で。
まずはカレントディレクトリ(デプロイ予定のファイルの入ってるディレクトリ)をプロジェクトと紐付けしないといけない。
gcloud config set project <プロジェクトID>
で、デプロイの呪文がとにかく面倒くさい。
gcloud functions deploy <ファンクションID> --memory=<割り当てたいメモリー容量> --runtime <使用言語の指定> --gen2 --timeout=<タイムアウトまでの時間> --entry-point=<エントリーポイント> --region=<地域指定> --trigger-topic=<pub/subのトピック名> --env-vars-file .env.yaml
ふつう、こういうのって hogehoge deploy とかいれたら初回にコンソール上で質問に答えていくだけでいいと思うんだけど、これはちゃんとこういうパラメーターをずらずらつけないといけない。めんどくさい。
これらは設定によって違うので、適時変更すべきところや追加すべきところもある。あと二度目以降は省略していい項目もあるが、省略しちゃダメとなってる項目もあって面倒くさいので、もう全部コピペしてる。今回はトリガーはpub/subからのメッセージ送信ということにしてるので、トリガーの指定は-topicになっている。
それと環境変数は.env.yamlで指定する。環境変数自体は個別でも足せるが、複数あるならいちいちここに書き足すのは面倒くさいし、Functionsはエラーで度々消さないといけないことがあるので。
ファイルの構成
rubyの場合は
- app.rb
- Gemfile
- Gemfile.lock
をとりあえず作っておけばOK。で、Seleniumを動かすのでのちのちはここに「chromedriver」と、「chrome」もしくは「headless-chromium」をいれることになる。
環境変数を指定する場合は
- .env.yaml
- .gcloudignore
こちらを作り、.gcloudignoreには.env.yamlなどをかいておいてデプロイされないようにしておく。
Gemfile
Gemfile.lockはとくに中身なにもかかなくてよし。
Gemfileについてはとりあえず
source "https://rubygems.org"
gem "functions_framework"
これらはつけておく。あとは普通どおり、追加したいgemを足していくだけ。ちなみに、Gemfileの更新が一番なぜか再デプロイ時にひっかかりやすい。よくわからんけど、そうなったら上に書いてるように一旦Functionを消してしまってデプロイし直した方が速い。
app.rb
まずは最初のフォーマットとしては
require "functions_framework"
require "base64"
FunctionsFramework.cloud_event "エントリポイント名" do
puts "hello, world"
end
base64の呼び出しは必要なのかわからないが、Functionsでrubyの新規作成するとこのコードを書いてくるので、知らんけどいるのかもしれない。.cloud_event
後のエントリポイント名が、上のコマンドでも書いたようにエントリポイントというやつなので、必ずスクリプト内にいれておかないといけない(よくわかってない)
とりあえずここまでやったら一旦デプロイして、pubsubから適当にメッセージを送信して、functionsのログタブからログを確認して、ちゃんとへろーわーるどされてるかを試しておく。Pub/SubとFunctionsの紐付けは他の方の記事をとりあえず参照。
Selenium導入の大まかに必要な行程
で、肝心のSelenium導入までの流れ。Gemfileにgem 'selenium-webdriver'
を書き入れておくのは当然なので省略。
デプロイするファイル
必要なものとしては、chromeもしくはheadless-chromium、chromedriverの二つである。chrome本体を持ってきてもいいんだけどそれはメモリを喰っちゃうので、ヘッドレス用にしてあるheadless-chromiumの方が多分望ましい。
この二つはGithub gcf-packsから抜き出してくるのが手っ取り早い。これを解凍して、中のpack.zipも解凍するとchromedriver
とheadless-chromium
というファイルが入っているので、これをデプロイするフォルダにコピーしておく。
具体的なコード
require "functions_framework"
require "base64"
FunctionsFramework.cloud_event "エントリポイント名" do
require 'selenium-webdriver'
Selenium::WebDriver::Chrome::Service.driver_path = './chromedriver'
options = Selenium::WebDriver::Chrome::Options.new(binary:"./headless-chromium")
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
driver = Selenium::WebDriver.for :chrome, options: options
driver.navigate.to 'https://www.yahoo.co.jp/'
puts driver.title
end
これでyahoo japanのタイトルがログに流れてきたら成功。おもったよりかんたん。
pythonで同様の記事を書いてる先行者の例をみると、tmpフォルダにいれないとアクセスできないから、tmpフォルダへのコピーとパーミッション設定をしないといけないという内容が書かれている。
でも自分が試した限り、別にそんなことしなくても普通に使える。gen1だとそういう風にしないといけないとかあるんだろうか?謎。