Docker + Google Cloud Storage + active_storageを導入したステップ
すでにできあがったRailsアプリケーションに対して、active_storageを導入してログです。アップロード先はGoogle Cloud Storageにしてみましたが、基本的にはAWSでも同様だと思います。
導入のキッカケ
すでにこのアプリケーションには自作のactive_storageもどきみたいなモジュールが存在していて、CarrierWaveを使いながらファイルアップロードができていた。
ただ、さすがにactive_storge使わないといけないよね?これから辛くなるよね?ということで途中から導入してみた感じです。
環境
gem 'rails', '~> 5.2.3'
environmentとして、以下があります。
- development
- test
- staging-a
- staging-b
- staging-c
- production
installする
$ docker-compose run --rm app bundle exec rails active_storage:install あるいは $ rails active_storage:install
とすると、 active_storage_blobs
と active_storage_attachments
という2つのテーブルのmigrationファイルができあがります。
念のため解説すると、
- active_storage_blobs:アップロードしたい対象のモデルにひもづくもの、imageとか
- active_storage_attachments:実際のファイル
という違いがあります。
...続いて、できあがったmigrationファイルをdb:migrateします。
設定周り
続いては、アップロード先などの設定を書きます。一旦localで動くことを目標とします。とはいえ、localでは基本的にデフォルトで動くようになっているかと思います。以下のようになっていることを確認します。
config/storage.yml
local: service: Disk root: <%= Rails.root.join("storage") %> test: service: Disk root: <%= Rails.root.join("tmp/storage") %>
localの場合は、Diskのstorage/に、testの場合はtmp/storageに画像がアップロードされていくイメージです。
そして、これを各環境ごとに指定します。local/testであれば...
config/environment/development.rb
config.active_storage.service = :local
config/environment/test.rb
config.active_storage.service = :test
この記述があればOKです。なければ追加して、サーバーを再起動します。
ざっくり説明すると、config/storage.yml
はactive_storageそのものの設定で、:test
:local
のほかに :stage_hoge
のように設定できたり各種サービスごとに分かりやすく :aws
などの名前を振ることができます。そしてその内容は、yml形式で記述していきます。後ほど詳しく説明しますが、aws, gcpなどのサービスを利用する場合も必要なkey:valueを記述すれば設定がカンタンにできます。例えば...
awsを使う場合:
amazon: service: S3 access_key_id: "" secret_access_key: ""
gcp(gcs)を使う場合:
google: service: GCS credentials: <%= Rails.root.join("path/to/keyfile.json") %> project: "" bucket: ""
のような感じです。
そして、各種環境でどれを使うのか、を環境ごとのファイルで指定します。いまの段階ではtestとlocalだけでしたが、productionではgoogleを使いたいとなったら、
config.active_storage.service = :google
とすればOKです。
念のため確認...
ここで2つ確認すべきことがあります。
1つは、適切なアップロード先のディレクトリが掘ってあるか。
上の例でいえば、
local: service: Disk root: <%= Rails.root.join("storage") %> test: service: Disk root: <%= Rails.root.join("tmp/storage") %>
の2つ、つまり /storage
と /tmp/storage
が存在するかどうか、です。もしなければ作成しておいたうえで、チーム開発であれば以下の点を追加で対応するといいと思います。
もう一つは .gitignore
に storage周りのディレクトリを追加することです。おそらくすでにtmpディレクトリはignoreされているかもしれませんが、storageディレクトリも追加しておくと、localで検証した画像がそのままgitにのってしまうことを防げます。
モデルの修正
設定周りが終わったら、アップロードしたい画像にひもづくモデルに修正を加えます。例えば、userモデル(models/user.rb
)に対して、avatar画像(user.avatar)をアップロードしたいとします。その場合、以下の1行を追加するだけです。
class User < ActiveRecord::Base # これで :icon として画像をアップロード、取得ができるようになる has_one_attached :avatar
また、例えばモデルと画像がhas_manyの関係にある場合は以下のようにします。
class User < ActiveRecord::Base # これで :images として複数画像をアップロード、取得ができるようになる has_many_attached :images
この場合、images
と複数系になることに注意です。
モデルの変更は一旦これだけです。単にアップロード、取得するだけならこれでOKです。取得する場合は以下のようにできます。
# userに紐づいたavatarをとってくる user.avatar # userに紐づいたavatarに画像があるか?を確認する user.avatar.attached?
画像をアップロードする
今回はslimでアップロードするためのインターフェースとそれに対応するコントローラーを作成します。
class Administration::UsersController < Administration::ApplicationController def new @user = User.new end end
.field = f.label :avatar = f.file_field :avatar
細かいところは省略していますが、ここは普通にアップロード、保存ができればOKです。細かい記述は不要です。
他のパターンで添付する
インターフェースを用意せずに画像を添付することもできます。その場合は以下の通りです。
user.avatar.attach(params[:avatar])
attach()
でモデルに画像を添付することができます。そして、添付できたかどうか?は先ほど記載したuser.avatar.attached?
で確認することができます。rails consoleで調べるときも重宝します。
ここで注意すべきは、複数画像が紐づくモデルの場合(
@message.images.attached?
)は、「いずれかの画像が添付されているか」という判断になるので、どれかが足りない、ということはattach?
ではチェックできません。
また、別パターンとして、HTTP経由で配信されない画像を添付したい場合は以下のようにします。一応Railsガイドではcontent_typeを指定することも推奨されています。
@message.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf')
staging, productionにアップロードする
local, testではアップロードができた(はず)ので、続いてstagingからやっていきます。基本的には storage.yml
と環境ごとに用意されているであろう environment/staging.rb
をいじるだけです。
今回僕の環境ではstagingが3つ存在していて、仮にここではstaging_a, staging_b, staging_cとします。それぞれにアップロードできるようにしてみます。
ここで必要なもの
staging、productionのGCP(GCS)に入るためのアカウントで環境に入り、Service Accountを作っていきます。これはGCSに対するIAMで、いまなければ新規作成する必要があります。作り方は以下の記事が詳しいです。
Active StorageでGoogle Cloud Storageに画像を保存する - ツナワタリマイライフ
ここで用意すべき情報はこちらです。
hoge: service: GCS credentials: type: "service_account" project_id: "コレ" private_key_id: "コレ" private_key: "コレ" client_email: "コレ" client_id: "コレ" auth_uri: "https://accounts.google.com/o/oauth2/auth" token_uri: "https://accounts.google.com/o/oauth2/token" auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs" client_x509_cert_url: "コレ" project: "コレ" bucket: "コレ"
それ以外の項目(service, type, auth_url...etc)については固定でそのままで大丈夫です。
上記の項目をそれぞれ入力したら、storage.ymlは完成です。
credentialを使う場合
GCSのprivate_key_idなど秘匿性の高い情報はRails.credentialsに格納するのもアリだと思います。使い方は以下の記事がわかりやすかったです。
その場合、このようになります。(一部抜粋)
private_key_id: <%= Rails.application.credentials.dig(:production, :private_key_id) %>, private_key: <%= Rails.application.credentials.dig(:production, :private_key)&.dump %>, client_email: <%= Rails.application.credentials.dig(:production, :client_email) %>,
注意したい点
2019年12月時点ではGCSのprivate_key
をRails.credentialsに入れている場合、取り出すときに一旦dumpしないといけませんでした。なので、以下のようにしています。
private_key: <%= Rails.application.credentials.dig(:production, :private_key)&.dump %>,
不要でしたらそのままcredentialsの中身を参照させちゃっていいと思います。
環境変数周りをしあげる
最後に、staging-a, staging-b, staging-c, productionそれぞれでアップロードができるようにします。今回はstaging-a,b,cはそれぞれ同じGCSのインスタンスを向いている前提です。
config.active_storage.service = :staging Rails.application.routes.default_url_options[:protocol] = 'https' Rails.application.routes.default_url_options[:host] = ENV['ADMIN_HOST']
運良くADMIN_HOST
にa,b,cをいれているので、それをみるようにしました。これでそれぞれ正しくアップロードされるはず...。
一方productionはもっとシンプルで、
config.active_storage.service = :production Rails.application.routes.default_url_options[:protocol] = 'https' Rails.application.routes.default_url_options[:host] = 'hoge.net'
だけです。
まとめ
ご指摘があればいつでもくださいmm