839の日記

趣味の話を書くブログです。

サービスのバックアップをGCSに移した

サービスのバックアップのストレージとして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にファイルをアップロードするのには少なくともRubySDKでは管理者が必要。*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を安く運用する方法の調査がなかなか進んでいないのでそろそろ頑張っていきたい。。

*1:閲覧者はオブジェクトの閲覧者しか指定できないっぽくて、ほしいのはバケットの閲覧権限だが、これには管理者を与えるしかなさそうに思えた

*2:サービスの特性上、朝5時をすぎると基本writeがなくなるのでそのぐらいにやっている

*3:3コア4GBのVPS1台で複数サービスを運用しているのになかなか頑張ってくれる

*4:とはいえ、VPSはcloudと比べて圧倒的に壊れない気がする、ほとんど壊れた記憶がない

*5:https://839.hateblo.jp/entry/2019/05/16/213836 でも述べた通り、根深い問題は落とさないとわからない