ECS(Fargate)で動かすのが目的ですが、その設定は別途記載するとして、
先ずはDockerイメージをどう作るべきか試したのでメモしておきます。
- どこに書くのか?
- 何で使うか?
- Dockerイメージは複数必要か?
- ENTRYPOINTは必要か?
- Dockerfile作成
- ログファイルを使わないように改修
- メール送信方法を変更できるように改修
- 環境依存の情報を変更できるように改修
- ECRにリポジトリを作成してpush
どこに書くのか?
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したら使わなくなったのは消した方が良さそうですね。
AWS 無料利用枠の一部として、プライベートリポジトリ用に 1 年間月々 500 MB のストレージをご利用いただけます。
パブリックリポジトリ用に 50 GB/月のストレージを常時無料でご利用いただけます。
GB/月あたり 0.10USD です。