ECS(Fargate)で動かすのが目的ですが、その設定は別途記載するとして、
先ずはDockerイメージをどう作るべきか試したのでメモしておきます。

どこに書くのか?

composeコマンドは使用しないので、docker-compose.ymlは使用しない。
dockerコマンドを使用するので、Dockerfileを使用する。

何で使うか?

アプリケーションサーバー(Unicorn等) → ECRで「タスク定義」して「サービスの作成」
バッチ処理(Rakeタスク) → ECRで「タスク定義」して「タスクの実行」

Dockerイメージは複数必要か?

サービスで動かすものは、DockerfileのCMDにコマンドを記載する。
コマンドが終了するとDockerも終了するので、デーモンにしないようにする。

bundle exec unicorn -c config/unicorn.rb -D
	↓
bundle exec unicorn -c config/unicorn.rb

unicorn.rake作成して、rails unicorn:startで起動できるようにしたけど、これは使えない。

    sh "bundle exec unicorn -c config/unicorn.rb -D -E #{rails_env}"

本題に戻り、ECRの「タスク定義」や「新しいタスクの実行」で、コンテナの「コマンドの上書き」で、DockerfileのCMDを上書きできるので、Dockerイメージは1つで良い。
docker-compose.ymlのcommandと動き。

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"

ENTRYPOINTは必要か?

ENTRYPOINT(entrypoint.sh等)には、起動時に必ず実行したいものを書く。
良く使われるのは下記だけど、db:createは初回のみだし、
db:migrateやdb:seedはリリース直後に1回で良い。
複数同時に起動する場合も無駄に走る事になるし、
他にやるべき事もなかったので、使わない事にしました。

rails db:create
rails db:migrate
rails db:seed

Dockerfile作成

Railsアプリにローカル用のDockerfileが既にあるので、サーバー用に別ファイル名で作成しました。
近くに置きたかったので、ディレクトリ作らずにファイル名を変更。

Dockerfile_production

最新のコードこちら → https://dev.azure.com/nightonly/_git/rails-app-origin?path=/Dockerfile_production&version=GBdevelop

# https://hub.docker.com/_/ruby
FROM ruby:3.0.0-alpine
RUN apk update && apk add --no-cache --update build-base tzdata bash yarn python2 imagemagick graphviz ttf-freefont
RUN apk add --no-cache --update sqlite-dev
RUN apk add --no-cache --update mysql-dev mysql-client
RUN apk add --no-cache --update postgresql-dev postgresql-client

WORKDIR /workdir
ENV LANG="ja_JP.UTF-8"

ここまでは、Dockerfile(ローカル用)と同じにしておく。


ENV RAILS_ENV="production"
ENV RAILS_LOG_TO_STDOUT=1

ログを標準出力に出すと、CloudWatchログに出力されます。
config/environments/production.rb で定義されているのを使用。

  if ENV['RAILS_LOG_TO_STDOUT'].present?
    logger           = ActiveSupport::Logger.new($stdout)
    logger.formatter = config.log_formatter
    config.logger    = ActiveSupport::TaggedLogging.new(logger)
  end

COPY Gemfile Gemfile.lock ./
RUN bundle config set --local without 'test development'
RUN bundle install --no-cache

COPY package.json yarn.lock ./
RUN yarn install && yarn cache clean

COPY . ./
RUN mkdir -p tmp/{pids,sockets}

# Tips: 存在チェック
COPY public/robots.txt ./public/

コピペ忘れを早めに気づけるように、チェックの為にCOPYしています。


# Configure the main process to run when running the image
# Tips: assets:precompileは起動時に実施。SECRET_KEY_BASEが必要な為
CMD bash -c "bundle exec rails assets:precompile && rm -f tmp/pids/unicorn.pid && bundle exec unicorn -c config/unicorn.rb"

assets:precompileで作成されるファイル名に入るハッシュ作成にSECRET_KEY_BASEを使っている。迷いましたが、イメージに入れたくないので、サービス起動時に実行する事にしました。
CMDの実行が続くようにする為、unicornに「-D」は入れない。

ログファイルを使わないように改修

Unicornのstderr/stdout

Unicornをかなり良い感じに設定 で作成したファイルを修正。

config/unicorn.rb

最新のコードこちら → https://dev.azure.com/nightonly/_git/rails-app-origin?path=/config/unicorn.rb&version=GBdevelop

+ if ENV['RAILS_LOG_TO_STDOUT'].nil?
  p "STDERR_PATH: #{ENV['STDERR_PATH'] || "#{DEFAULT_STDERR_PATH}(default)"}"
  p "STDOUT_PATH: #{ENV['STDOUT_PATH'] || "#{DEFAULT_STDOUT_PATH}(default)"}"
+ else
+   p "RAILS_LOG_TO_STDOUT: #{ENV['RAILS_LOG_TO_STDOUT']}"
+ end
+ if ENV['RAILS_LOG_TO_STDOUT'].nil?
  stderr_path ENV['STDERR_PATH'] || DEFAULT_STDERR_PATH
  stdout_path ENV['STDOUT_PATH'] || DEFAULT_STDOUT_PATH
+ end

stderr_pathやstdout_pathを設定しなければ、標準出力に表示されます。

序でにbacklogも変更できようにしました。

増やせば大量の同時アクセスも受け付けてくれるようになるとか。
実際は、worker数やtimeoutのチューニングも必要ですが。

+  DEFAULT_LISTEN_BACKLOG = 1024
+ p "LISTEN_BACKLOG: #{ENV['LISTEN_BACKLOG'] || "#{DEFAULT_LISTEN_BACKLOG}(default)"}"
- listen ENV['LISTEN'] || DEFAULT_LISTEN
+ listen ENV['LISTEN'] || DEFAULT_LISTEN, backlog: Integer(ENV['LISTEN_BACKLOG'] || DEFAULT_LISTEN_BACKLOG)

taskでログファイルを指定していたので改修

task毎にログファイルを分けた方が、調査しやすいので指定していました。
ECRでは「タスク定義」単位でCloudWatchのロググループが作られるので、RAILS_LOG_TO_STDOUTがある場合は標準出力するように変更しました。

lib/tasks/user.rake

-  task(:destroy, [:dry_run] => :environment) do |_, args|
+  task(:destroy, [:dry_run] => :environment) do |task, args|
    args.with_defaults(dry_run: 'true')
    dry_run = (args.dry_run != 'false')

-    logger = Logger.new("log/user_destroy_#{Rails.env}.log")
+    logger = new_logger(task.name)

lib/tasks/application.rake

def new_logger(task_name)
  return Rails.logger if ENV['RAILS_LOG_TO_STDOUT'].present?

  Logger.new("log/#{task_name.gsub(/:/, '_')}_#{Rails.env}.log")
end

メール送信方法を変更できるように改修

EC2やGCE等のサーバーの場合は、sendmailでメールサーバーに中継して送るようにしていましたが、IP変わると縛れないし、Dockerでその辺の設定をするのは違うかなと思ったので、環境変数で変更できるようにしました。

config/environments/production.rb

-  config.action_mailer.delivery_method = :sendmail
+  config.action_mailer.delivery_method = ENV['DELIVERY_METHOD'].present? ? ENV['DELIVERY_METHOD'].to_sym : :sendmail
+  config.action_mailer.smtp_settings = ENV['SMTP_SETTINGS'].present? ? eval(ENV['SMTP_SETTINGS']) : nil
序でにログレベルも変えられるようにしました。

Dockerイメージ作り直さなくても変えられるので、いざという時に役立ちそう。

-  config.log_level = :info
+  config.log_level = ENV['LOG_LEVEL'].present? ? ENV['LOG_LEVEL'].to_sym : :info

環境依存の情報を変更できるように改修

Config(Gem)を使っていて、dotenv-railsは使っていない。
本番・STG・テスト環境で同じイメージを使いたいので、環境依存の情報を入れたくない。
既存コードは、できれば変えたくない。Configのymlを見ればどの環境変数使っているか解るようにしたい。
これを満たす書き方がありました。

config/settings/production.yml

- env_name: ''
+ env_name: <%= ENV['ENV_NAME'] %> # (空) or 【STG環境】 or 【テスト環境】
- base_domain: 'example.com'
+ base_domain: <%= ENV['BASE_DOMAIN'] %> # example.com
- base_image_url: 'https://example.com'
+ base_image_url: <%= ENV['BASE_IMAGE_URL'] || "https://#{ENV['BASE_DOMAIN']}" %> # https://example.com
- redirect_whitelist: ['https://front.example.com/*']
+ redirect_whitelist: <%= eval(ENV['REDIRECT_WHITELIST'] || '[]') %> # ['https://front.example.com/*']
- default_confirm_success_url: ''
+ default_confirm_success_url: <%= ENV['DEFAULT_CONFIRM_SUCCESS_URL'] %> # (空)
- default_password_reset_url: ''
+ default_password_reset_url: <%= ENV['DEFAULT_PASSWORD_RESET_URL'] %> # (空)
- default_unlock_success_url: ''
+ default_unlock_success_url: <%= ENV['DEFAULT_UNLOCK_SUCCESS_URL'] %> # (空)
  mailer_from:
-   name: '%{app_name}'
+   name: <%= ENV['MAILER_FROM_NAME'] || "'%{app_name}'" %>
-   email: 'noreply@example.com'
+   email: <%= ENV['MAILER_FROM_EMAIL'] || "noreply@#{ENV['BASE_DOMAIN']}" %> # noreply@example.com

ECRにリポジトリを作成してpush

リポジトリを作成

https://ap-northeast-1.console.aws.amazon.com/ecr/create-repository?region=ap-northeast-1

リポジトリを作成
	一般設定
		リポジトリ名: (Gitリポジトリ名と同じ)
	[リポジトリを作成]


push

(リポジトリ)を選択
	[プッシュコマンドの表示]

親切ですね。
基本的には記載の通りでOKなのですが、2番目だけパラメータ追加が必要。

docker build --platform=linux/amd64 -f Dockerfile_production -t rails-app-origin .

–platform=linux/amd64: 下記エラーで起動できなかった為。m1 Macだからかな?
> standard_init_linux.go:228: exec user process caused: exec format error

-f Dockerfile_production: ファイル名を変更している為。

確認

pushしたら使わなくなったのは消した方が良さそうですね。

料金 – Amazon ECR | AWS

AWS 無料利用枠の一部として、プライベートリポジトリ用に 1 年間月々 500 MB のストレージをご利用いただけます。
パブリックリポジトリ用に 50 GB/月のストレージを常時無料でご利用いただけます。
GB/月あたり 0.10USD です。


今回のコミット内容
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/0949eb70c7b65d98ca7ee39a2f8b0dba586a85a6

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です