【Rails】AWS「S3」に画像アップロードする

# はじめに
`Rails`で作成したアプリケーションの画像投稿を`S3`にアップロードする方法です。

# 環境
Rails 6.0.0
MacOS Catalina 10.15.7
ローカルシェル：zsh
本番環境：AWS EC2
自動デプロイツール：Capistrano
アプリケーションサーバー：Unicorn

# IAM作成
S3アクセス用のユーザーを作成。

1.ユーザー名は任意で作成
2.次にアクセス権限設定
3.既存のポリシーを直接アタッチを選択
4.「AmazonS3FullAccess」に設定
5.次のステップへ進みユーザー作成

作成後、認証情報をダウンロード（.csv）
後に「AWS_ACCESS_KEY_ID」、「AWS_SECRET_ACCESS_KEY」の値が必要です。

また`IAM`の「ユーザーのARN」も必要になります。


# S3 バケット作成

・バケット名は任意で
・リージョンも任意で（今回は東京リージョン）

続いてセキュリティの設定を行う。今回はバケットポリシーを使用して、セキュリティ設定を行う。
1.`「パブリックアクセスをすべてブロック」`のチェックを外す
2.`「新しいパブリックバケットポリシーまたはアクセスポイントポリシーを介して〜」`のチェックを入れる。
3.`「任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介して〜」`のチェックを入れる。
4.チェックを入れ終わったら、「次へ」をクリック
5.バケットを作成する。

作成したらバケットポリシーの設定をします。
該当のバケットの「アクセス権限」から「バケットポリシー」を選択

以下のように設定します。

```
    "Version": "2012-10-17",
    "Id": "Policy1544152951996",
    "Statement": [
        {
            "Sid": "Stmt1544152948221",
            "Effect": "Allow",
            "Principal": {
                "AWS": "************①****************"
            },
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::************②**********"
        }
    ]
}
```

①にIAMユーザーのARNが入ります。
②にS3のバケット名が入ります。

最後に「保存」を押し完了します。

# Railsにfog-awsをインストール
AWSに最適化されたfog-awsというGEMをインストールします。
Gemfileを以下のように編集。

```rb
gem 'fog-aws'
```
本番環境でも使用するためgroup ~ end で囲まれていない部分に追記します。
そして`bundle install`コマンドを実行。

# アップロードにfogを使用するための設定
デフォルトでは、アップロード先がpublicフォルダになっていました。これをfogを使う設定をするため、image_uploader.rbを編集。

```rb

class ImageUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick
  process resize_to_fit: [800, 800]

  # Choose what kind of storage to use for this uploader:
  storage :fog #←ここを変更します

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

# 後略
```
storageを「:file」から「:fog」に変更。

# fogのアップロード先の設定
`carrierwave.rb`を編集。
アプリケーションのルートから`config/initializers`直下に、`carrierwave.rb`というファイルを作成。

```rb
require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

CarrierWave.configure do |config|
  config.storage = :fog
  config.fog_provider = 'fog/aws'
  config.fog_credentials = {
    provider: 'AWS',
    aws_access_key_id: ENV["AWS_ACCESS_KEY_ID"],
    aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
    region: 'ap-northeast-1'
  }

  config.fog_directory  = 'ここにバケット名を入れます'
  config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/ここにバケット名を入れます'
end
```
バケット名の部分は、自身で付けられた名前を入力します。

regionで設定してある「ap-northeast-1」は、アジアパシフィック（東京）を表します。

# 環境変数を設定
S3で使用するキーの設定を行います。S3の接続に必要な認証情報を、環境変数として設定します。
私のMacは`zsh`使用のため以下になります。
それぞれのシェルに合わせて環境変数を設定してください。
ターミナルで以下のコマンド。

```
# ローカル環境
% vim ~/.zshrc

# 下記を追記する。環境変数設定のコマンドを入れます。
export AWS_ACCESS_KEY_ID='ここにCSVファイルのAccess key IDの値をコピー'
export AWS_SECRET_ACCESS_KEY='ここにCSVファイルのSecret access keyの値をコピー'

# 編集が終わったら保存して終了

# 以下のコマンドで変更点を再度読み込ませます。
% source ~/.zshrc
```
この設定によりcarrierwave.rbの

```r
aws_access_key_id: ENV["AWS_ACCESS_KEY_ID"],
aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"]
```
が環境変数として反映されるようになります。

# ローカルで実際に画像をアップロードしてみる
S3のバケットを見て、ファイルがアップロードされているか確認します。
正常に投稿できていれば、バケットを選択した際に「uploads」というディレクトリができています。

# EC2（本番環境）から画像アップロード
ローカルでS3にアップロードできるようになったら本番環境反映させます。
変更内容を`EC2`へデプロイします。

# 本番環境での環境変数の設定
通常は`.bash_profile`に追記することで環境変数を設定できるが私の場合は　自動デプロイツールの`Capistrano`を使用しているため違う方法で設定します。
本番環境の「etc/environment」というファイルにキーの情報を書き込みます。

まずは`EC2`へsshでリモートでログイン。
以下のコマンドを打ちます。

```
$ sudo vim /etc/environment

# 下記を追記する。既存の記述は消去しない。
AWS_ACCESS_KEY_ID='ここにCSVファイルのAccess key IDの値をコピー'
AWS_SECRET_ACCESS_KEY='ここにCSVファイルのSecret access keyの値をコピー'
# 編集が終わったら保存して終了
```
変更を反映させるため一旦ログアウトし再度ログインして以下で確認。

```
$ env | grep AWS_SECRET_ACCESS_KEY
$ env | grep AWS_ACCESS_KEY_ID
```
以上で環境変数を設定完了しました。

# 本番環境で動作確認
`unicorn`(アプリケーションサーバー)を再起動させ実際に画像を投稿して確認する。
これで終了です。

# うまくいかないとき
・まずは環境変数が正確に追記されているか再度確認。
・環境変数はローカルなら`rails c`で確認できます。
・バケットポリシーのバケット名はARNが誤ってないか確認
・`carrierwave.rb`の記述に誤りないか再度確認。
・サーバーの再起動（NginxやUnicorn）