サービスのバックアップのストレージとしてS3を利用していたが、GCSに移した。 移行のモチベーションはいつかGKEに行くぞ、という気持ちからGCSに移しておいたほうがいいかなーという気持ち。
前にcarrierwaveからactivestorageに移行した - 839の日記という記事を書いたが、だいたいそれと同じ気持ち。 別にしないとやばいというわけでもなかったが、とりあえず少し値段も下げれそうだしやるか〜みたいな。
元々はap-northeast-1にライフサイクルを30日で仕掛けてアップロードしていた。 移行のときによく考えたら30日もいらないな、と思い14日にしたのとregionをオレゴンにしたのでAWSのap-northeast-1と比べたらそこそこ安いはず。
1日のバックアップ容量がtar.gzにして大体660MBぐらいなので30日だと20GB弱である。 ap-northeast-1だと0.025$/GBなので0.5$(50円ちょっと)しかかかってないのに、削ってどうするんだという話もある。 料金は今書きながら計算したので思ったより何も変わらないなーという気持ちにはなったが、GKEに移すとなるとネットワーク的にもまぁ移しておいて損はない。 保持期間を14日にしてオレゴンに置くと8.5GBで0.02$/GBなので0.17$で少しお得…な気持ちになったと思いこんでおく。
GCSが圧倒的にいけてないなーと思うところはライフサイクル周りの設定。 S3であればあるprefixのパスから始まっているやつはn日といった柔軟な設定ができるのに対して、GCSはバケット全体にしか適応できない。 なので、ライフサイクルの種類を分けたくなった場合はバケットごと分けるしかなく、ここはいまいちだなーと感じる。
あとはGCPのIAM/サービスアカウント周りがわかりにくい上に結局したいことができない感がすごい。 AWSであればIAMであるバケットのwriteだったりreadだったりを細かく権限設定できるのだが、 GCPは閲覧者、作成者、管理者ぐらいの区分しかないし、結局GCSにファイルをアップロードするのには少なくともRubyのSDKでは管理者が必要。*1
どうも、調べた感じだとアップロード前にbucket一覧を取得しに行く動作をしているようで、その兼ね合いでオブジェクト作成権限だけではだめっぽい。 しかも管理者にしてもあるバケットだけに絞れないので、すべてのバケットの管理者を与えるしかないように思える。 このあたり、無知なだけかもしれないが、AWSを使ってた身からするとかなりイケてない権限体系だなーと感じる。
バックアップは複数のサービスで使い回したかったので以下のようなものを書いている。 なお、前提としてactivestorageはlocalで利用している(現在はVPSの上で動いているので)。
require 'google/cloud/storage' class ServiceDumper def dump_and_upload storage = Google::Cloud::Storage.new( project_id: 'gcp-project-id', credentials: "#{Rails.root}/service-dumper.json" ) bucket = storage.bucket('bucket-name') Rails.logger.info('uploading gcs...') bucket.create_file(dump, upload_path) Slack::GcsDispatcher.success(ENV['RAILS_ENV']) `rm #{dump_path}.tar.gz` Rails.logger.info('done service dump') rescue StandardError => e Slack::GcsDispatcher.failed(ENV['RAILS_ENV'], e) end private def project_name 'project_name' end def upload_path "#{project_name}/#{Date.today.year}-#{Date.today.month}-#{Date.today.day}/#{ENV['RAILS_ENV']}_service_dumper.tar.gz" end def dump Rails.logger.info('clean dump path') `rm -rf #{dump_path}` Rails.logger.info('create dump path') `mkdir #{dump_path}` Rails.logger.info('file dumping...') file_dump Rails.logger.info('pg dumping...') pg_dump `echo #{Time.now} > #{dump_path}/info.txt` Rails.logger.info('tar.gz creating...') `cd #{Rails.root}/tmp && tar czf service_dumper.tar.gz service_dumper` Rails.logger.info('remove raw files') `rm -r #{dump_path}` "#{dump_path}.tar.gz" end def file_dump `cp -r #{files_path} #{dump_path}/storage` end def pg_dump `pg_dump -U#{database['username']} #{database['database']} > #{dump_path}/#{database['database']}.sql` end def dump_path "#{Rails.root}/tmp/service_dumper" end def database Rails.configuration.database_configuration[ENV['RAILS_ENV'] || 'production'] end def files_path "/absolute_path/app/#{project_name}/shared/storage" end end
上記のような構成で組んでおいて、基本プロジェクトを跨いだときに変更するのは #project_name
の文字列だけ変えている。
ちょっと前まで一部がcarrierwaveで動いていたが、全部がactivestorageになったので、他のロジックは完全に使い回せて少しメンテナンスコストが下がった。
GcsDispatherは自作のslack通知くんで、dumpが終わったら教えてくれる。処理はactivejobでwrapしている状態。
dump自体は利用者が少なそうな朝方に sidekiq-cron
というgemを使って定期実行を仕掛けている状態。*2
2000万レコード程度のサービスだと大体4分弱ぐらいでdumpがuploadまで含めて終わる感じ。*3
なお、DBだけではなくactivestorageの内容もまとめてdumpしている。
ここはactivestorageをlocalモードで使っててよかったなと思う点なのだが、ローカルで画像をmissingにせずかつ安全に扱える点が楽である。 missingにするとraiseしまくって鬱陶しいし、GCSの本番バケットを参照するようにしていると誤って登録者のデータを変えてしまう恐れもある。 多分自分がactivestorageをGCSに移すときは何らかの手段でGCSからローカルに落としてlocalモードで動かす、みたいなことを模索しそうな気はするが今は困ってないのでどのくらいのコスト感なのか不明。
VPSで冗長構成も組んでない(お金的な意味で)サービスなので、とりあえずまめにバックアップをとっといていつVPSが飛んでもいいようにしている、という話だった。*4 単純に根が深い問題を検証するときでも、やはりバックアップが定期的に取られているとシュッと手元で本番環境を模せるし、色々と運用が楽になるなと感じる。*5
GKEに移るぞ!という気持ちと下準備だけ先行していて本筋のGKEを安く運用する方法の調査がなかなか進んでいないのでそろそろ頑張っていきたい。。