【AWS S3 + EC2 + CarrierWave + Fog】RailsからS3へ画像アップロード手順

#はじめに
Ruby on Rails 5.2からAWS S3へ画像をアップロードをするため、CarrierWave+fogを使って実装を進めました。

対象
EC2構築、デプロイ経験のある方
EC2へ画像をアップロードしていた方
はじめてS3を利用する方

#AWSの設定
##S3作成

S3 バケットを作成する方法
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/user-guide/create-bucket.html

- 東京リージョン
- バージョニング有効
- Static website hosting有効
- パブリックアクセスをすべてブロック

```:バケットポリシー
{
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::[バケット名]",
                "arn:aws:s3:::[バケット名]/*"
            ],
            "Condition": {
                "StringEquals": {
                    "aws:SourceIp": "[EC2のパブリックIP]"
                }
            }
        }
    ]
}
```

##VPCエンドポイントの設定
EC2とS3の間でファイルの転送を行います。

1. VPCのダッシュボードを開き、 エンドポイント の作成ボタンをクリックします。
![aws_vpc_endpoint_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465986/5ba3af2e-b4b7-4070-2118-d060ae29df59.png)

2. 宛先の選択をします。 AWS services と com.amazonaws.ap-northeast-1.s3 にチェックを入れます。
![aws_vpc_endpoint_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465986/4ba433fd-8169-4bef-478f-f63bc40cede0.png)

3. 通信したいEC2インスタンスが置かれているVPC と そのサブネットに当てられているルートテーブル を選択します。
<img width="1165" alt="スクリーンショット 2019-12-22 16.17.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465986/a106af92-b78f-70c0-9e24-3dc8f71e4022.png">




4. カスタムポリシーの設定

```:カスタムポリシーの例
{
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::[バケット名]",
                "arn:aws:s3:::[バケット名]/*"
            ]
        }
    ]
}
```

作成が完了すると、ダッシュボードにエンドポイントが表示されます。
紐付けたサブネットのルートテーブルを確認すると、エンドポイントを介したS3への経路が追加されています。

##AWS CLIを使ってS3へデータを送信テスト

```
EC2へ接続
$ ssh -i [keyペアファイル] ec2-user@[パブリックIP]

テストファイルを転送
$ touch test.txt
$ aws s3 mv test.txt s3://YOUR_S3_BUCKET/uploads

s3バケットが閲覧できるか確認
$ aws s3 ls s3://YOUR_S3_BUCKET/ --recursive

```


#Amazon Linux 2の設定
##ImageMagickを導入

```
$ yum install -y ImageMagick ImageMagick-devel
```

##アクセスキーとシークレットアクセスキーの設定
aws configure コマンドが、AWS CLI のインストールをセットアップするための最も簡単な方法です。
詳しくは
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html
https://dev.classmethod.jp/cloud/aws/how-to-configure-aws-cli/

```
$ aws configure
AWS Access Key ID [None]: AKIAXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: 5my9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]:
```

設定の確認

```
$ aws configure list
```

#Railsの設定

##Gemの設定

```:Gemfile
gem 'carrierwave'
gem 'rmagick'
gem 'fog-aws'
```


##ImageUploaderの設定

```
$ rails g uploader Image
```

```/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  # 画像サイズを取得するためにRMagick使用
  include CarrierWave::RMagick

  # developmentとtest以外はS3を使用
  if Rails.env.development? || Rails.env.test? 
    storage :file
  else
    storage :fog
  end

  # 画像ごとに保存するディレクトリを変える
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
  end

  # 許可する画像の拡張子
  def extension_whitelist
     %w(jpg jpeg gif png)
  end

  # ファイル名を書き換える
  def filename
    "#{Time.zone.now.strftime('%Y%m%d%H%M%S')}.#{file.extension}" if original_filename
  end
end

```


##CarrierWaveの設定

```/config/initializers/carrierwave.rb
# CarrierWaveの設定呼び出し
require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

# 画像名に日本語が使えるようにする
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/

# 保存先の分岐
CarrierWave.configure do |config|
  # 本番環境はS3に保存
  if Rails.env.production?
    config.storage = :fog
    config.fog_provider = 'fog/aws'
    config.fog_directory  = '[S3のバケット名]'
    config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/[S3のバケット名]'
    # iam_profile
    config.fog_credentials = {
      provider: 'AWS',
      # credentialsで管理する場合
      aws_access_key_id: Rails.application.credentials.aws[:access_key_id],
      aws_secret_access_key: Rails.application.credentials.aws[:secret_access_key],
      # 環境変数で管理する場合
      # aws_access_key_id: ENV["AWS_ACCESS_KEY_ID"],
      # aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
      region: 'ap-northeast-1' #東京リージョン
    }
    # キャッシュをS3に保存
    # config.cache_storage = :fog
  else
    # 開発環境はlocalに保存
    config.storage :file
    config.enable_processing = false if Rails.env.test? #test:処理をスキップ
  end  
end
```

##アップローダーの使い方
作成したUploaderをModelに紐付けます。Userモデルのimageカラムに紐付ける例

```User.rb
class User < ApplicationRecord
  mount_uploader :image, ImageUploader
end
```