search
LoginSignup
0

posted at

updated at

Organization

S3×CloudFront を使用したActionTextの画像アップロード・配信方法

はじめに

ActionTextから登録された画像や添付ファイルはデフォルトだとサーバー内のローカルディスクに保存されます。

大容量のファイルが溜まり続けた場合、サーバがいつかキャパオーバーになってしまう懸念があります。

そこで、ActionTextから投稿された画像の保存先を、ローカルディスクからAmazon S3に変更しストレージ容量を確保し、高速コンテンツ配信サービス(CDN)であるAmazon CloudFrontを使用して、配信を行う構成を実装してみたいと思います。


【環境】
・ AWS(EC2, S3, CloudFront, IAM etc.)
・ Ruby 2.7.3
・ Ruby on Rails 6.1.5

ActionTextを使用するには、Railsバージョン6系以上が必要です。

Action Text の概要

スクリーンショット 2022-05-11 6.38.44.png

インフラ構成

ざっくり、全体のインフラ構成は↓のような感じで実現しました。

  • アプリケーションはEC2インスタンス(web01, web02)内にデプロイし、可用性を高めるためマルチAZ構成
  • ActionTextからアップロードされた画像は、VPCエンドポイントを経由して、S3バケットにアップロード
  • ユーザーは画像にアクセスする際に、CloudFrontエッジロケーションにアクセスする
  • CloudFrontはキャッシュとして画像を保持し、存在すれば、そのままユーザーにレスポンスを返し、なければS3に対して、取得リクエストを送る

スクリーンショット 2022-05-09 11.21.42.png

■ アプリケーション側(Rails)の設定

Gemfile

Gemfile
#適用したい環境で囲んであげる
group :staging, :production do
  gem 'aws-sdk-s3', require: false
end

environments/環境.rb

環境ファイルに保存先の情報を追記する

development.rb, staging.rb, production.rbなど、各々、実現したい環境のファイルを変更する

config/environments/環境.rb
#activestorageファイル保存
config.active_storage.service = :amazon

#複数環境で実現したい場合は、↓のようにキーの指定の仕方を一意になるようにしてあげれば良い
config.active_storage.service = :amazon_production

storage.yml

ここに、ActionTextの画像格納先情報が記載されており、デフォルトだとローカルディスク内に保存するようになっている(コメントアウト箇所)

それではいつかサーバーがキャパオーバーになりかねないので、保存先をS3に変更する

また、ローカル環境や、EC2にIAMロール or 認証情報を設定しない場合は、storage.yml内にアクセスキーを指定する
(今回はEC2自体にIAMロールをアタッチするので、コメントアウト)

storage.yml
#local:
  #service: Disk
  #root: <%= Rails.root.join("storage") %>

amazon:
  service: S3
  #access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  #secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: リージョン名
  bucket: バケット名

#複数環境で実現したい場合
amazon_production:
  service: S3
  region: リージョン名
  bucket: バケット名

■ インフラ側の設定

▼準備

EC2にS3操作権限のあるIAMロールをアタッチ

  • 適当な名前のIAMロールを作成
  • IAMロールにはS3FullAccessもしくは、書き込み権限のあるポリシーを付与
  • 作成したIAMロールをアプリケーションをデプロイしたEC2にアタッチ

スクリーンショット 2022-05-09 14.25.47.png

EC2には認証情報ではなく、IAMロールを割り当てた方が良い

AWSのベストプラクティス的にもEC2内に直接、認証情報を記述するのは良くないとされているので、権限は基本的に最小権限のIAMロールで付与します。

aws credentialsの認証情報とIAMロールで同じポリシーを設定している場合、認証情報の方が優先度が高いので、aws cliからの操作の時に予期せぬ挙動となる可能性がある。

▼S3の設定

バケットの作成

ActionTextの画像ファイル格納用のバケットを作成。
バケット名は全世界で一意である必要があります。

バージョニングは各々の要件に合わせて有効/無効を設定してください

Cross-Origin Resource Sharing (CORS)の記述

新コンソールからはjsonでの記述が必須となったので、以下の内容をバケット内のCORSに記述。

CORS
[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

VPCエンドポイントを使用してS3にアクセスする

バケットを作成後、EC2→S3への通信を実現するために、以下記事を参考にVPCエンドポイントを構築。

VPCエンドポイントを使用することで、EC2→S3間の通信をインターネットにでることなく、セキュアに通信することができる。

このとき、VPCエンドポイントのポリシーを以下のように設定。

EC2インスタンスがVPCエンドポイント経由でS3バケットに対して行いたいポリシーを設定する。

VPCエンドポイントポリシー
{
	"Version": "2012-10-17",
	"Id": "Policy1651800381415",
	"Statement": [
		{
			"Sid": "Stmt1651800377576",
			"Effect": "Allow",
			"Principal": "*",
			"Action": [
				"s3:GetObject",
				"s3:PutObject",
				"s3:List*"
			],
			"Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
		}
	]
}

VPCエンドポイント経由で通信できているか確認

VPCにエンドポイントを作成して、S3にアクセスする設定にしたものの、本当にエンドポイント経由でアクセスされているか確認したかったので、検証してみました。

確認する方法は、以下の2種類がありますが、今回は、CloudTrailのデータイベントの記録を参照し確認します。

  • S3のサーバーアクセスログ
  • CloudTrailのデータイベントの記録

なお、事前にCloudTrailのデータイベントログの取得を有効化しておく必要があります。

EC2内からaws cliでListイベントを呼び出します。
(VPCエンドポイントポリシーにs3:List*のアクションが必要です)

EC2
//バケット内のオブジェクトの一覧を表示
$ aws s3 ls バケット名

=> 2022-05-09 16:55:43 590866 test.png

ちょっと待つとS3内のCloudTrailのイベントが記録されたバケットにイベントログが入ります。

ListObjectsが呼び出されたログを確認・・・

sourceIPAddress": "EC2のプライベートIPアドレス"となっていれば、EC2からVPCエンドポイントを使用して、S3にアクセスできています!
(※EC2のプライベートIPアドレスではなく、自分が接続しているネットワークのIPアドレスならNATゲートウェイ経由でインターネットを通過したS3へのアクセスになってしまっているかと思います)

cloudtrailデータイベントログ
{
  "eventTime": "2022-05-10T02:00:16Z",
      "eventSource": "s3.amazonaws.com",
      "eventName": "ListObjects",
      "awsRegion": "ap-northeast-1",
      "sourceIPAddress": "EC2のプライベートIPアドレス",
      "userAgent": "[aws-cli/2.4.7 Python/3.8.8 Linux/5.10.102-99.473.amzn2.x86_64 exe/x86_64.amzn.2 prompt/off command/s3.ls]",
      "requestParameters": {
        "list-type": "2",
        "bucketName": "バケット名",
        "encoding-type": "url",
        "prefix": "",
        "delimiter": "/",
        "Host": "バケット名.s3.ap-northeast-1.amazonaws.com"
      }
}

(※注意)VPCエンドポイントの料金

▼CloudFrontの設定

ディストリビューションの作成

ディストリビューションの作成から、OAIの設定まで、↓記事を参考にすると良いと思います!

CloudFrontから配信されているか確認

CloudFront がディストリビューションのドメイン名としてd111111XXXXXXX.cloudfront.netを返した場合は、
S3バケット内またはHTTPサーバーのルートディレクトリに存在する foo.jpgのURLはhttp://d111111XXXXXXX.cloudfront.net/foo.jpgとなります。

え、、、? どゆこと、、、?

つまり、バケット内にhoge.jpegというオブジェクトがCloudFrontから配信されているか確認したい場合、

ブラウザのURLでhttp://d111111XXXXXXX.cloudfront.net/hoge.jpg を叩いて、アクセスできればCloudFrontから配信できています!

URL
https://"CloudfrontのDomainName"/"オブジェクト名"

オリジンアクセスアイデンティティ(OAI)とは

S3バケットから直接ではなく、CloudFront経由でのみファイルにアクセスできるようにするための特別なユーザー

これを画像を格納するバケットのバケットポリシーとして設定してあげるとCloudFront経由でのみに制限できる。
(ディストリビューションの作成画面からOAIの作成、バケットポリシーの自動変更ができます)

CloudFrontアクセスログをS3バケットに格納

S3バケットはデフォルトだとACL無効化により、バケットへの書き込みが制限されている。

ACLを無効に設定したS3バケットは選択できないようになっているため、ログを取得するにはバケットのACLの設定を変更する必要があります。

バケットに格納されたCloudFrontのログ確認方法は以下記事がすごく参考になりました。

さいごに

S3×CloudFrontという王道のアーキテクチャ構成実装をしてみましたが、ActionTextと併用されていることは記事としてもなかなか少なかったので、すごく良い経験になりました。

記載はしませんでしたが、この後他にも、S3ライフサイクルポリシーの設定や、S3へのアクセスログの取得なども行いました。

まだまだやり残したこととして↓のようなこともあるので、今後はこれらも挑戦していきたいです!

  • Rails側でActionTextの元のイベントを削除したときに、S3にアップロードされた画像どうするか問題
  • CloudFrontのキャッシュヒット率の向上

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0