概要
-
この記事は弊社ァ内で行った準備作業の備忘録メモでございますわっ
-
次の新規案件に備え、RubyとRails最新版のスケルトン(?)なプロジェクトを準備する
-
各バージョンは以下の予定
- Ruby2.7系
- Rails6.0系
- MySQL8.0系
- Nginx1.17系
- redis(いちおう)
-
Docker前提です
- 新規ならDockerでしょ?って、僕も過激派になっている感。
- AWS ECSでの運用が前提のため。
-
Rubyのイメージはalpine3.11を使用。カーネル5.4系でセキュリティも向上してるらしい(?)し、軽いし1、
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ruby 2.7-slim-buster b913dc62d63c 10 days ago 149MB ruby 2.7-buster 0c1ee6efe061 10 days ago 842MB ruby 2.7-alpine3.10 78005ca97a7f 2 weeks ago 52.9MB ruby 2.7-alpine3.11 1f7033feacdb 3 weeks ago 53.5MB
-
docker-compose化する。最終的にはね。
- そこだけ読みたいならここまでスクロール
やりたいこと
- 作業PC的な観点で
PCローカル内のモダンな過去プロジェクトを汚さずに環境構築したい。PCローカル内のレガシーな過去プロジェクトを汚さずに環境構築したい。- PCローカル内のヴィンテージな過去プロジェクトを汚さずに環境構築したい。
- PC内のパッケージに悪影響が出ないようにしたい。
-
rails new
すらもDockerの中でやりたい。 - dockerコンテナ落としてもDBの内容やbundleのgemが消えないようにしたい。
- 他の開発者(後輩くん)が使える程度のものを残しておきたい。
- 運用想定的な観点で(
何様)- 環境作成
- AP,DBコンテナの構成で十分
-
rails new
してソースコード準備したい - できれば動作確認したい
- 開発環境(弊社ァ内エンジニア用)
- WEB,AP,DBコンテナに加えてRedisも建てたい
- ↑↑環境差異による不具合を減らすため、本番環境にできるだけ合わせたい・・・!!(切実)
- docker-compose化しておいて、少しでも普段遣いのコマンドを減らしたい
- WEB,AP,DBコンテナに加えてRedisも建てたい
- 本番環境
- AWS ECSで動かす想定
- dockerイメージはできるだけ開発環境そのままECRにプッシュする
- 環境変数だけで環境変えたいよね
- ECSタスク定義とかRDS設定はdocker-compose見ればほぼ設定できるようにしておきたい
- 環境作成
Railsプロジェクトの新規作成
目的
- docker内で
rails new
してWebアプリケーションのソースを用意する - ここの見出しでやるのは
rails new
だけ。開発環境整える方法は次の見出しまでスクロールしてね
やることを決める
- dockerボリュームの有効活用
- 環境構築途中で失敗してもdbとgemを消さないようにして時短。
- dockerのマウントタイプは
bind
でわなくvolume
を使う2。Macのファイルシステムへのマウントは遅い(?)ので
- 動作確認のためDBコンテナも建てる
事前準備
docker network(一時) を作成
$ # host OS (local PC)
$ # コンテナ間通信用のネットワークを用意しておく
$
$ APP_NETWORK=tmp_network # 名前をきめてね
$
$ docker network create ${APP_NETWORK}
$
$ # 確認しよう
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
c31a38da01e0 tmp_network bridge local
docker volume(一時) を作成
$ # host OS (local PC)
$ # DBデータ保持用と、gem保存用の2つのdockerボリュームを作っておく。
$
$ DB_VOLUME=tmp_dbdata_vol # 名前をきめてね
$ BUNDLE_VOLUME=tmp_bundle_vol # 名前をきめてね
$
$ docker volume create ${DB_VOLUME}
$ docker volume create ${BUNDLE_VOLUME}
$
$ # 確認しよう
$ docker volume ls
DRIVER VOLUME NAME
local tmp_bundle_vol
local tmp_dbdata_vol
- これ以降のコンテナ作成作業中で、エラーが出まくったら、volumeを一度作り直すなどして問題解決していこうかと考えていた。
コンテナの作成
DBコンテナ(一時)を建てる
-
mysqld
コマンドのオプションで色々宣言している。**.cnf
を用意するのでも良かったけど、本番はRDS使うだろうし、
$ # host OS (local PC)
$
$ # パスワードはあくまで一例。ちゃんと毎回違うランダム文字を使う癖をつけるように教えるの()
$ docker run \
--detach \
--env MYSQL_ROOT_PASSWORD=4h%zpW8wrb+G \
--env TZ="Asia/Tokyo" \
--mount type=volume,source=${DB_VOLUME},target=/var/lib/mysql \
--name tmp_db_mysql \
--network ${APP_NETWORK} \
--publish 127.0.0.1:13306:3306 \
--tty \
mysql:8.0 \
mysqld \
--block_encryption_mode aes-256-cbc \
--character_set_server utf8mb4 \
--collation-server utf8mb4_bin \
--default-authentication-plugin mysql_native_password \
--explicit_defaults_for_timestamp \
--init-connect 'SET NAMES utf8mb4' \
--skip-character-set-client-handshake
APコンテナ(一時)を建てる
$ # host OS (local PC)
$
$ # DBパスワードはDBコンテナと同じもの
$ docker run \
--detach \
--env APP_DATABASE_PASSWORD=4h%zpW8wrb+G \
--mount type=volume,source=${BUNDLE_VOLUME},target=/usr/local/bundle \
--name tmp_ap_rails \
--network ${APP_NETWORK} \
--publish 127.0.0.1:3000:3000 \
--tty \
--workdir /app \
ruby:2.7-alpine3.11
プロジェクトの新規作成
Railsプロジェクトを新規作成
$ # host OS (local PC)
$
$ docker exec -it tmp_ap_rails sh
/app # # guest OS (ruby:2.7-alpine3.11)
/app #
/app # # 必要そうなパッケージをAlpineLinuxPackagesからインストール
/app # apk update
/app # apk upgrade
/app # apk add --update --no-cache \
build-base \
git \
imagemagick \
imagemagick-dev \
libxml2-dev \
libxslt-dev \
mysql-client \
mysql-dev \
nodejs \
ruby-dev \
tzdata \
yarn
/app #
/app # # yarnのインストールコマンドを叩いておく
/app # yarn install
- ローカルPC内でGemfileを作って、それをAPコンテナ(一時)にコピーする
- ここでrailsのバージョンを決めている
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem 'rails', '~> 6.0', '>= 6.0.2.1'
$ # host OS (local PC)
$
$ # cpコマンドでdocker内にファイルをコピーする
$ docker cp Gemfile tmp_ap_rails:/app/Gemfile
$ # host OS (local PC)
$
$ docker exec -it tmp_ap_rails sh
/app # # guest OS (ruby:2.7-alpine3.11)
/app #
/app # # railsをインストールする
/app # bundle install
/app #
/app # rails --version
Rails 6.0.2.1
/app #
/app # # Railsプロジェクトを新規作成する(各オプションはプロジェクトに応じて変える)
/app # rails new . \
--database=mysql \
--force \
--skip-coffee \
--skip-sprockets \
--skip-turbolinks \
--skip-test \
--webpack=vue
設定値の書き換え
/app # # guest OS (ruby:2.7-alpine3.11)
/app #
/app # # development と test の設定を変更
/app # vi config/database.yml
# --- L20..L24
development:
<<: *default
database: app_development
password: <%= ENV['APP_DATABASE_PASSWORD'] %>
host: tmp_db_mysql
# --- L29..L33
test:
<<: *default
database: app_test
password: <%= ENV['APP_DATABASE_PASSWORD'] %>
host: tmp_db_mysql
動作確認
/app # # guest OS (ruby:2.7-alpine3.11)
/app #
/app # rails db:create
/app #
/app # rails server -b 0.0.0.0
- ローカルPCで http://localhost:3000 にアクセス
- うまく、いった、みたい、
ソースコードのコピー
- ディレクトリ内をコピーするときは
.
をつけるらしい3。 - この作業で新規プロジェクトのソースコードがローカルPCにコピーされるので、これをgithubで管理するなりしておく
$ # host OS (local PC)
$
$ # Docker内のソースコードをホスト側にコピーする
$ docker cp tmp_ap_rails:/app/. ./
ゴミ掃除
APコンテナ(一時)の削除
- APコンテナ(一時)は
rails new
と動作確認のためだけのコンテナだったので、消す。
$ # host OS (local PC)
$
$ docker stop tmp_ap_rails
$ docker rm tmp_ap_rails
$
$ # 消えていることを確認
$ docker ps -a
DBコンテナ(一時)の削除
- DBコンテナ(一時)も、消す。
$ # host OS (local PC)
$
$ docker stop tmp_db_mysql
$ docker rm tmp_db_mysql
$
$ # 消えていることを確認
$ docker ps -a
docker network(一時)の削除
- docker network(一時)も、消す。
$ # host OS (local PC)
$
$ docker network rm ${APP_NETWORK}
$
$ # 消えていることを確認
$ docker network ls
docker volume(一時)の削除
- docker volume(一時)も、消す。
$ # host OS (local PC)
$
$ docker volume rm ${DB_VOLUME}
$ docker volume rm ${BUNDLE_VOLUME}
$
$ # 消えていることを確認
$ docker volume ls
Railsプロジェクトの新規作成まとめ
- 長文になってしまって申し訳ナソス
- ローカルPCのrubyバージョンに影響されずにrailsプロジェクトが新規作成できた。
- (rbenvとかを使用してもこの程度はできる気がするけど・・・)
- これを応用してエンジニア用の開発環境も整えていく形で良さそう。(何様)
- 上記に記してはいないが、volume設定でgemを別に保持できていたのを確認できているので、感触はOK
- 直接volume内を見に行くのは少々面倒だが、bindよりは動作速そう(?)。
docker-compose化
目的
- ここから先が本当にやりたかったことやで。。。
- 上記までの準備(新規作成したrailsプロジェクト)をもとに、開発環境(弊社ァ内エンジニア用)を整えてゆく
やることを決める
-
docker-compose.yml
を用意する - 基本
docker-compose up -d
,docker-compose down
コマンドだけで動くようにしておく - ログローテートはちゃんと設定する(もし無限ログ吐き出されてもPCをパンクさせない教訓)
-
Dockerfileの用意をしてビルド時には必要パッケージがインストールされている状態にする
- Dockerfileはマルチステージビルド4を仕込んでおき、本番リリースに使えるようにしておく
- WEBコンテナ(nginx)を追加して、静的ページを表示できるようにする
- 静的ファイル、ソケットはAPコンテナとつなげておく
- ソケットはrails6だからpuma(unixソケットにする予定)
- 起動順の依存関係には一番最後
- 静的ファイル、ソケットはAPコンテナとつなげておく
- CACHEコンテナ(redis)を追加しておく
- 起動順の依存関係はAPより前
- 今回は使わないけど。開発で使うので
Dockerfileの作成
FROM nginx:1.17-alpine
LABEL maintainer="なまえ <メアド>"
# 設定ファイルを上書き
RUN rm -f /etc/nginx/conf.d/*
ADD ./containers/web/default.conf /etc/nginx/conf.d/default.conf
# nginxの起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
# @note ホストサーバーとのファイル共有は以下
# - tmp/ # pumaソケット通信用
# - public/ # rails静的ファイル共有(404ページとかjs,cssとか)
# nginxとpumaのソケット通信の設定
# @see{https://github.com/puma/puma/blob/master/docs/nginx.md}
upstream rails_app {
server unix:///rails_app/tmp/sockets/puma.sock;
}
server {
listen 80;
server_name localhost;
# HTTPの持続的な接続維持時間(軽量なので0秒(off)でもいいかも)
keepalive_timeout 5;
# アップロードサイズの上限を設定
client_max_body_size 10m;
# 静的ファイルのパス
root /rails_app/public;
# ログ出力(dockerイメージの設定より、このパスは標準出力となる)
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log info;
# メンテナンスファイルが置かれている場合はメンテ画面を出す
if (-f $document_root/maintenance.html) {
rewrite ^(.*)$ /maintenance.html last;
break;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
# 静的ファイル
if (-f $request_filename) {
break;
}
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://rails_app;
break;
}
}
# 後方一致で画像などの静的ファイルが指定された場合はpublic/配下のファイルを直接返す(Railsを介さない)
location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
# ファイルパスが存在しない場合はRailsで処理
if (!-f $request_filename) {
proxy_pass http://rails_app;
break;
}
expires max;
break;
}
}
- ↓↓マルチステージビルドの使い方が完全に自己流なんだけどこれで良いのかしら・・・。
FROM ruby:2.7-alpine3.11 as dev_mode
LABEL maintainer="なまえ <メアド>"
# ビルド時の作業ディレクトリ
WORKDIR /app
# AlpineLinuxPackagesで必要そうなコマンドをインストール
RUN apk update && \
apk upgrade && \
apk add --update --no-cache \
build-base \
git \
imagemagick \
imagemagick-dev \
libxml2-dev \
libxslt-dev \
mysql-client \
mysql-dev \
nodejs \
ruby-dev \
tzdata \
yarn
# yarnインストールの実行(先にlockファイルをコピーしてバージョンを固定)
COPY package.json yarn.lock ./
RUN yarn install
# マルチステージビルドで本番用イメージを作成
FROM dev_mode as prod_mode
# Railsの秘密情報のマスターキー(docker build時にオプションで宣言する)
ARG RAILS_MASTER_KEY
ENV RAILS_MASTER_KEY ${RAILS_MASTER_KEY}
# 本番モードで起動する
ENV RAILS_ENV production
# アプリのソースをイメージ内にコピー
COPY ./ ./
RUN bundle install
# フロント用のライブラリのインストールと静的ファイルの作成
RUN rails yarn:install
RUN rails webpacker:compile
# コンパイル後、マスターキー情報はイメージから消しておく
ENV RAILS_MASTER_KEY=
docker-compose.ymlの作成
- 2020/02くらいに確認したときは、バージョン3.7が最新だった5ので、それで。
- パスワードはあくまで一例。
version: '3.7'
# @see {https://docs.docker.com/compose/compose-file/}
services:
# DBコンテナの設定
db:
# ビルドに使うイメージ
image: mysql:8.0
# 環境変数
environment:
MYSQL_DATABASE: app_development
MYSQL_ROOT_PASSWORD: F-O75%kSG6gz
TZ: "Asia/Tokyo"
# ログローテート設定
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# コンテナ間通信用
networks:
- app_net
# ホストOSに開放するポートの指定
ports:
- 127.0.0.1:3306:3306
# ファイルのマウント設定
volumes:
# データベースの内容をDocker領域に同期する(データ永続化のため)
- type: volume
source: mysql_data_vol
target: /var/lib/mysql
# コンテナを永続化
tty: true
# 認証を旧式(パスワード)に変更、デフォルトの文字コードとcollate設定を指定、暗号化のモード選択
command: >
mysqld
--block_encryption_mode aes-256-cbc
--character_set_server utf8mb4
--collation-server utf8mb4_bin
--default-authentication-plugin mysql_native_password
--explicit_defaults_for_timestamp
--init-connect 'SET NAMES utf8mb4'
--skip-character-set-client-handshake
# Redisコンテナの設定
redis:
# ビルドに使うイメージ
image: redis
# ログローテート設定
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# コンテナ間通信用
networks:
- app_net
# コンテナを永続化
tty: true
# APコンテナの設定
app:
# ビルド時の設定
build:
# プロジェクトのディレクトリでビルドする
context: .
# マルチステージビルドのターゲットを指定
target: dev_mode
# Dockerfileの場所を指定
dockerfile: containers/app/Dockerfile
# コンテナ依存関係
depends_on:
- db
- redis
# 環境変数
environment:
APP_DATABASE_PASSWORD: F-O75%kSG6gz
# RAILS_MASTER_KEY:
# RAILS_MAX_THREADS:
TZ: "Asia/Tokyo"
# ログローテート設定
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# コンテナ間通信用
networks:
- app_net
# ファイルのマウント設定
volumes:
# アプリのソースをホストOSと共有する
- type: bind
source: .
target: /app
# gemをDocker領域に同期する
- type: volume
source: bundle_vol
target: /usr/local/bundle
# コンテナを永続化
tty: true
# bundle install 、yarn install を行い、pumaサーバーを起動する
command: >
sh -c "
bundle install &&
rails yarn:install &&
rails webpacker:compile &&
pumactl start"
# WEBコンテナの設定
web:
# ビルド時の設定
build:
# プロジェクトのディレクトリでビルドする
context: .
# Dockerfileの場所を指定
dockerfile: containers/web/Dockerfile
# コンテナ依存関係
depends_on:
- app
# ログローテート設定
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# ホストOSに開放するポートの指定
ports:
- 127.0.0.1:80:80
# ファイルのマウント設定
volumes:
# 静的ファイルの共有
- type: bind
source: ./public
target: /rails_app/public
# pumaソケットファイルの共有
- type: bind
source: ./tmp
target: /rails_app/tmp
# ネットワークの定義
networks:
# コンテナ間通信用
app_net:
# ボリュームの定義
volumes:
mysql_data_vol:
bundle_vol:
- rails側のDB設定も修正修正(ホストをdocker-composeで定義した名前に変えよう)
# --- L20..L24
development:
<<: *default
database: app_development
password: <%= ENV['APP_DATABASE_PASSWORD'] %>
host: db
# --- L29..L33
test:
<<: *default
database: app_test
password: <%= ENV['APP_DATABASE_PASSWORD'] %>
host: db
- あと、WEBコンテナとソケット通信させるためにconfigも少し修正が必要だった6
# --- L13
# unixソケット通信はポートを使用しない
# port ENV.fetch("PORT") { 3000 }
# --- 最後に追加
# ソケット通信の設定(WORK_DIRを/appにしているからもう直書きで良いかしら)
bind "unix:///app/tmp/sockets/puma.sock"
動作確認
$ # host OS (local PC)
$
$ # いよいよdocker-compose起動するぜ!!
$ docker-compose up -d
$
$ # 確認
$ docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------------------
skl_rails6021_app_1 sh -c bundle install && r ... Up
skl_rails6021_db_1 docker-entrypoint.sh mysql ... Up 127.0.0.1:3306->3306/tcp, 33060/tcp
skl_rails6021_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
skl_rails6021_web_1 /bin/sh -c /usr/sbin/nginx ... Up 127.0.0.1:80->80/tcp
$
$ # 確認
$ docker-compose logs --tail 30 app
$ docker-compose logs --tail 30 web
$
$ # MySQLもローカルPCから接続できるよね
$ mysql -u root -p
- ローカルPCで http://localhost:80 にアクセス(初回は時間がかかるのでログを見て落ち着いたら)
- うまく、できた、みたい、
READMEに説明を書く
- やる。
- 僕だけ使えても意味がない。
- もしかしたらこの作業が一番骨折れるかもしれない。
最後に
- 長文失礼。
- ポート開放不親切失礼。必要に応じて書き換え願う
- ruby2.7対応していないgemがいくつかあるみたいで、ログが汚い。
warning
の大量発生や。。。(2020/02ころ現在) - WEBコンテナ追加してnginxとソケット通信する作業でに労力増えてる感ある。
rails sever
のありがたみを感じる。。。 - redisの設定は開発時にやるで良いよね。。。
-
https://hub.docker.com/_/ruby ←←2020/02くらいにここを閲覧した、そして全部取得して比べてみた。 ↩
-
https://docs.docker.com/storage/volumes/ ←Docker領域内に名前つけて保存されるから、万が一ソースコードごと紛失してもワンチャンある() ↩
-
https://medium.com/veltra-engineering/how-to-copy-a-directory-using-docker-cp-command-f2c73f9ccf75 ←←こちらのサイトがとても参考になりました ↩
-
https://docs.docker.com/develop/develop-images/multistage-build/#use-a-previous-stage-as-a-new-stage ↩
-
https://docs.docker.com/compose/compose-file/compose-versioning/ ↩
-
https://github.com/puma/puma#binding-tcp--sockets unixソケットにするためにbindのオプションを
config/puma.rb
に書く必要があった。こことかこれとかこのへん読んでも書き方迷いそう。。。 ↩