🌥️

asset_syncを使ってCloud Front + S3でassetファイルを配信する

2022/11/02に公開

既に知見が転がっている内容だけど、個人的な備忘メモも兼ねて書いておきます。

やりたかったこと

  • ECSで動いているRailsのimageサイズを小さくしたかった
    • Railsアプリケーションに(画像等)assetファイルが大量にあった
    • そのため、assetファイルをCloud Front + S3で配信することでimageサイズが大きく改善しそうだった

やったこと

Before

After

環境・各バージョン

  • Ruby: 3.0.4
  • Rails: 6.1.6
    • webpacker: 5.4.3
  • asset_sync: 2.16.0

内容

大まかな流れ

ざっくり、以下のような流れで進めました。Railsアプリケーションに後からasset_syncを適応する流れとなりましたが、特に事故もなく思ったよりスッと進められてよかったです。

  1. Rails: asset_syncの設定を行い、S3バケットにassetファイルをアップロードできるようにする
  2. Terraform: (S3にファイルがアップロードされたので)CloudFrontの設定を変更し、特定パスへのリクエスト(=assetファイルの配信)をS3にリダイレクトさせる
  3. Docker: (S3にあるassetファイルを見るようになったので)assetファイル群をimageに含めないようにする

実装内容

1. Rails: asset_syncの設定

i. Railsの各環境設定

  • gemのREADME通り、asset_syncの設定を進めます
    • 具体的には、assetパスの指定と、配信を行うhostの指定を行います
    • staging環境など他の環境についても、該当ファイルに同様の設定を行います
    • 開発環境(development)でのasset利用に変更がない場合は、config.action_controller.asset_hostはlocalhostを指定すると良いです
config/environments/production.rb
Rails.application.configure do
  config.action_controller.asset_host = 'https://example.com'
  config.assets.prefix = '/assets'
end

ii. asset_syncのカスタマイズ設定

  • 必要に応じて、asset_syncの設定をカスタマイズします
    • 今回はfogwebPackerを使用しているRailsアプリケーションで、以下のような設定を行いました
    • 各種設定値の意味はgemのREADMEを参照ください(名前から意味がわかるものが多いです)
config/initializers/asset_sync.rb
# frozen_string_literal: true

if defined?(AssetSync)
  AssetSync.configure do |config|
    config.fog_provider = 'AWS'
    config.fog_directory         = if Rails.env.production?
                                     'prd.hoge.assets'
                                   elsif Rails.env.staging?
                                     'stg.hoge.assets'
                                   end
    config.fog_region            = 'ap-northeast-1'
    config.existing_remote_files = 'keep'
    config.gzip_compression      = true
    config.manifest              = true
    config.aws_acl               = 'private'

    # webPacker を assets:precompile 時に S3 に同期する設定
    config.add_local_file_paths do
      Dir.glob('public/packs/**/*').map { |str| str.sub('public/', '') }
    end
  end
end

2. Terraform: CloudFrontの設定を変更

i. S3のoriginを追加

  • aws_cloudfront_distributionのoriginに、s3_origin_configが設定されたS3向けのオリジンを追加します
    • domain_nameorigin_idには、(当然ですが)assetファイルを配信するS3バケットの値を指定してください
resource "aws_cloudfront_distribution" "app" {
  # 略
  origin {
    domain_name = aws_s3_bucket.hoge.bucket_regional_domain_name
    origin_id   = "s3-${aws_s3_bucket.hoge.bucket}"

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.hoge.cloudfront_access_identity_path
    }
  }
}

ii. ordered_cache_behaviorの設定を変更

  • 同じくaws_cloudfront_distributionのキャッシュ配信の設定を変更します
    • ordered_cache_behaviorでリダイレクトさせるパスパターンを指定し、リダイレクト先をS3に変更します
    • 今回は/assets/*/packs/*以下のパスへのアクセスをS3へリダイレクトさせています(元々は図の通りALBに通していました)
resource "aws_cloudfront_distribution" "app" {
  default_cache_behavior { 
    # 略
  }
  ordered_cache_behavior {
    path_pattern     = "/packs/*"
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${aws_s3_bucket.hoge.bucket}" # 変更
    # 略
  }

  ordered_cache_behavior {
    path_pattern     = "/assets/*"
    allowed_methods  = ["GET", "HEAD", "OPTIONS"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "S3-${aws_s3_bucket.hoge.bucket}" # 変更
    # 略
  }
}

3. Docker: assetファイル群をコンテナに含めないようにする

  • DockerfileでCOPYしているassetファイルがあれば、それをCOPYしないようにする
  • 注意点として、Railsの動作に必要となるmanifest関連のファイルはそのまま残しておく必要がある
Dockerfile
# 修正前
COPY /public/packs ./public/packs
COPY /public/assets ./public/assets

# 修正後
COPY /public/packs/manifest.json public/packs/
COPY /public/assets/.sprockets-manifest-* public/assets/

結果

  • AWSのコンソール等から、該当するECSのimageサイズが小さくなっていることが確認できます

参考記事

  • 実装するにあたりお世話になった記事を貼っておきます

https://zenn.dev/tanktabox/articles/722e4ca4cb7052
https://nishinatoshiharu.com/rails-asset-sync/

Discussion