はじめに
6月に新しい会社に来て以来、会社ではRuby on Railsばかり触っている。
RoRの開発・Ruby自体初めてなので(本当はScalaやりたい)、開発環境構築をやってみたのだが、「これ本当に必要なの?」などなど分からないことだらけで、何度かハマった。
そして、最終的に開発環境構築をおえるまで3日もかかったという非常に厳しい結果に終わった。
今後、新しく入ってくるエンジニアがこんな苦労をしなくても良いように、なるべく簡単に、そして誰もが共通で開発環境を構築出来るスキームを・・・と考えた結果、Dockerを採用することに。
さて、RoRのいいところは「サービス開発の速さ」だと思っている。そんなRoRを利用して「最近よくありがちな機能を含むサービスを一から作るならば」という観点を元に、Dockerを使った「ぼくがかんがえたさいきょうのRails開発環境」をまとめた。
機能
機能は、ざっと下記の通り。
最近のアプリ開発では割りとありがちな内容を想定している。
これらの環境をすべて docker-compose up するだけ(さいきょうっぽいところ)で、雛形が立ち上がるようにするのがゴールだった。
使い方
ソースはコチラ
Docker for Macのインストール
Docker for MacからDocker for Macをインストール。
git clone
git@github.com:omiend/my_strongest_ror.git
docker-compose up
$ cd employees/docker $ docker-compose up
以下は、この環境を構築した時のメモ
Railsの環境構築
まずはRailsの開発環境をDockerとは別で作成する。が、これは結局Railsの雛形を作りたいためのもの。
もし既存のアプリがあるようであれば、読み飛ばして「Docker周りの環境構築」から読んでも大丈夫だと思う。
rbenv
homebrew でインストール
$ brew install rbenv
bundler
rbenv でインストール
$ rbenv exec gem install bundler
rails
rbenv でインストール
$ rbenv exec gem install rails
Railsアプリケーションの雛形を作成する
rails で作成する
employees はアプリケーション名
$ rails new employees
Railsアプリケーションの実行
$ cd employees/ $ rails s
localhost:3000にアクセスし、Railsデフォルトのウェルカム画面が表示されればOK。
かんたんなアプリの作成
例として、従業員を管理するかんたんなアプリを作成する
Gemfileの修正
rails5だと、既存でsqlite3を利用するようなので、mysql2に変更。
- gem 'sqlite3' + gem 'mysql2', '~> 0.3.13'
修正したら bundle install し直す。
database.ymlの修正
mysqlにつなげるための設定。
これは後に、dockerのコンテナで稼働するDBに置き換わる予定だが、とりあえず一旦 localhost(127.0.0.1:3306) でMySQLが稼働している想定。
default: &default
- adapter: sqlite3
+ adapter: mysql2
+ encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
- database: db/development.sqlite3
+ database: employees
+ username: root
+ password:
+ host: 127.0.0.1
+ port: 3306
DBのSchema作成
Schema定義は、下記SQLでとりあえず。
create database employees default character set utf8;
Modelの作成
scaffold で作成してしまう。
$ rails g scaffold users name:string age:integer
マイグレーションの実行
$ $ rails db:migrate
アプリ動作確認
rails s で立ち上げ、localhost:3000にアクセスし、usersテーブルにデータが保存出来る様にフォームが動けばOK。
ログイン機能の実装
ログイン機能はdeviseを利用。
Gemfileの編集
gem 'devise'
bundle install実行
$ bundle install
deviseについてはネット上に色々情報があるので、詳細はそちらを参照。
僕は下記を参考にさせてもらった。
LetterOpenerWeb の追加
LetterOpenerWebは、ローカル環境にて送信したメールをブラウザで確認できるツール。
gemの追加
Gemfileに追記
group :development, :test do gem 'letter_opener_web' # localhostで送付されたメールを確認出来るツール end
同じく bundle install を実行
$ docker exec employees_app bundle install
routing追加
config/routes.rb に下記を追加
if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end
localhost:3000にアクセスすると、ちょっとしたメールクライアントが表示される。
とても便利!
以上、ここまでがrailsの基本的なアプリ作成の流れ。
Docker周りの環境構築
ココからが本番。
作成したアプリを、MacOSにインストールしたRailsではなく、Dockerコンテナ上で動作させるようにする。
Docker for Macのインストール
まずはDocker for MacからDocker for Macをインストール。
Railsアプリをコンテナ化するためのDockerfileを作成
employees ディレクトリ直下に、下記の様なファイルを配置する。
FROM ruby:2.4.1
RUN apt-get update -qq \
&& apt-get install -y build-essential libpq-dev nodejs imagemagick libmagick++-dev \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir /employees
WORKDIR /employees
ADD Gemfile /employees/Gemfile
ADD Gemfile.lock /employees/Gemfile.lock
RUN bundle install
ADD . /employees
RUN cd /employees
docs.docker.com/compose/railsを参考に作ったもの。
Dockerfileのコマンドは、docs.docker.jp/engine/reference/builder.htmlを参考にした。
Dockerの作業ディレクトリを作成
アプリについては、アプリ用という意味で employees/ 直下にDockerfileを配置しているが、ソレ以外のサーバー(MySQLとS3)については employees/docker ディレクトリにすべてまとめて置きたい。
$ mkdir docker
MySQL用のディレクトリの作成
$ mkdir docker/mysql
MySQL用のDockerfile作成
公式のMySQL Imageを利用するためのもの
$ echo -n "FROM mysql:5.7" > docker/mysql/Dockerfile
初回起動時に実行してくれるSQLファイルを作成
公式MySQLイメージの機能で、docker-compose up 時に指定のSQLを実行してくれる機能を利用するため、下記のディレクトリを作成する。
$ mkdir docker/mysql/docker-entrypoint-initdb.d $ echo -n "create database employees default character set utf8;" > docker/mysql/docker-entrypoint-initdb.d/create_db_s.sql
後述の docker-compose.yaml にて、当該ディレクトリをコンテナにマウントさせれば、初回起動時にSQLを実行してくれるとのこと。
MySQLの設定ファイルを作成
docker-compose up する時に、コンテナ内にマウントさせる設定ファイル
$ mkdir docker/mysql/conf.d $ touch docker/mysql/conf.d/custom.cnf
中身はちょっと適当すぎるのだけど、こんな感じに。
[mysqld] character-set-server=utf8 bind-address = 0.0.0.0
MySQL用のディレクトリ構成
ディレクトリ構成はこんな感じ
docker/mysql/
├── Dockerfile
├── conf.d
│ └── custom.cnf
└── docker-entrypoint-initdb.d
└── create_db.sql
Minio用のディレクトリの作成
minioとは、簡単に言うと「ローカル環境でAmazon S3を立ち上げられる」様なもの。
Minio用のディレクトリ作成
$ mkdir docker/s3
Minio用のDockerfile作成
docker公式のMySQL Imageを利用するためのもの
$ echo -n "FROM minio/minio:latest" > docker/s3/Dockerfile
Minio用のディレクトリ構成
ディレクトリ構成はこんな感じ
docker/s3/ └── Dockerfile
この後のdocker-composeでいろいろやるにはやるのだけど、これだけでS3環境相当のものが作れるのはすごい。
docker-composeファイルを作成
全てのDockerfileを従える絡新婦の様なファイル。
$ touch docker/docker-compose.yaml
中身はこんな感じ。
version: '2'
services:
# MySQLのコンテナを作成するための設定
employees_db:
container_name: employees_db
image: employees_db
build: ./mysql
ports:
- "3306:3306"
# コンテナ内の環境変数を指定
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
# 指定のディレクトリ・ファイルを、コンテナにマウントする
volumes:
- "./mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d"
- "./mysql/conf.d/:/etc/mysql/conf.d/"
- db-data:/var/lib/mysql
# Railsアプリのコンテナを作成するための設定
employees_app:
container_name: employees_app
image: employees_app
# コンテナ作成に利用するファイル群を指定する(この場合、Dockerfileが存在するディレクトリを指定)
build: ..
# stdin_open: true = コンテナの標準入力をオープンしたままにする
stdin_open: true
# tty:true = コンテナに疑似TTYを割り当てる
tty: true
# コンテナ立ち上げ時に実行するコマンドを指定
command: bin/rails s -p 3000 -b '0.0.0.0'
volumes:
# employees/DcokerfileのRUNコマンドで作成しているディレクトリにマウント
- ..:/employees
- bundle:/bundle
# ポート指定
ports:
- "3000:3000"
environment:
- "S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE"
- "S3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
- "S3_REGION=ap-northeast-1"
- "S3_HOST=employees_s3"
- "S3_BUCKET=employees"
- "S3_ENDPOINT=http://employees_s3:9000"
- "S3_ASSET_HOST=http://localhost:9000/employees"
# コンテナ間の依存指定(この場合、employees_dbが立ち上がるまで(ビルドされるまで?)
# employees_appのコンテナ立ち上げ(ビルド?)は実行されない
depends_on:
- employees_db
employees_s3:
container_name: employees_s3
image: employees_s3
build: ./s3
stdin_open: true
tty: true
command: server /export
ports:
- "9000:9000"
volumes:
- "./s3:/export"
environment:
- "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE"
- "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# MySQLのデータや、gemなど、ボリュームとして別管理することが出来る
# コンテナ側でマウントするように設定することで、たとえdocker imageを削除したとしてもデータを引き継ぐ事ができる
volumes:
db-data:
driver: local
bundle:
driver: local
コメント文はもしかしたら僕の認識が間違っているかも知れないので、あしからず。(もし間違っていたら教えて貰いたいッス)
dockerコンテナ間の通信
MySQLは database.yamlでlocalhostを指しているので、これをdockerコンテナに向ける。
とは言っても、dockerコンテナは立ち上げるたびにIPアドレスが変わってしまう。そのため、下記のように、docker-compose.yamlに記載するサービス名をhostに指定する。
サービス名はエイリアスにもなっていて、IPアドレスを返してくれる様子(表現あってるか微妙)
development:
<<: *default
database: employees
username: root
password:
- host: 127.0.0.1
+ host: emoloyees_db
port: 3306
Dockerコンテナ版アプリケーション起動
docker-compose up
dockerディレクトリにて、 docker-compose up をすると、各種コンテナが立ち上がる。
ガーッとログが出るので、落ち着いたら
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f4e275287cb5 employees_app "bin/rails s -p 30..." 16 seconds ago Up 14 seconds 0.0.0.0:3000->3000/tcp employees_app f79473377160 employees_s3 "/usr/bin/docker-e..." 17 seconds ago Up 15 seconds (health: starting) 0.0.0.0:9000->9000/tcp employees_s3 bade6d6cf45e employees_db "docker-entrypoint..." 17 seconds ago Up 15 seconds 0.0.0.0:3306->3306/tcp employees_db
といった感じで確認。
マイグレーション
railsのコマンドなどは、 docker exec を利用して実行する。
$ docker exec employees_app rails db:migrate == 20170820223339 CreateEmployees: migrating ================================== -- create_table(:employees) -> 0.0443s == 20170820223339 CreateEmployees: migrated (0.0444s) =========================
改めて、localhost:3000にアクセスし、employeesテーブルにデータが保存出来る様にフォームが動けばOK。
minio(s3)にファイルをアップロードする機能の実装
基本的にS3へのアップロード実装方法を検索すれば、いくらでもでてくる。
minioにおいても同様。
gemの追加
3つほどgemを利用するので、Gemfileに追記
gem 'carrierwave' # 画像アップロード用 gem 'fog-aws' # AWS S3連携用 gem 'rmagick' # 画像処理用
同じく bundle install を実行
$ docker exec employees_app bundle install
アップローダーの作成
$ docker exec employees_app rails g uploader Avatar
作成された uploaders/avatar_uploader.rb は、少し編集
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :fog
def store_dir
"#{mounted_as}/#{model.id}"
end
version :thumb do
process resize_to_fit: [50, 50]
end
def extension_whitelist
%w(jpg jpeg gif png)
end
def filename
"avatar.jpg" if original_filename
end
end
carrierwaveの設定ファイルを作成
下記の様に作成する
config/initializers/carrerwave.rb
CarrierWave.configure do |config|
config.fog_provider = 'fog/aws'
config.fog_credentials = {
provider: 'AWS',
# AWSアクセスキー
aws_access_key_id: ENV['S3_ACCESS_KEY'],
# AWSシークレットキー(間違ってもpublic repositoryにcommitしてはいけない)
aws_secret_access_key: ENV['S3_SECRET_ACCESS_KEY'],
# S3リージョン(TOKYO)
region: ENV['S3_REGION'],
# S3エンドポイント名(s3-ap-northeast-1.amazonaws.com ※TOKYO)
host: ENV['S3_HOST'],
endpoint: ENV['S3_ENDPOINT'],
path_style: true
}
# バケット名
config.fog_directory = ENV['S3_BUCKET']
config.asset_host = ENV['S3_ASSET_HOST']
end
環境変数は、 docker-compose.yaml にて設定済み
カラムを追加
usres Modelに、Avatarというカラムを追加する
$ docker exec employees_app rails g migration AddColumnToUsers avatar:string
migrateも実行
$ docker exec employees_app rails db:migrate
モデルを修正
models/user.rb に下記を追加
mount_uploader :avatar, AvatarUploader
UsersControllerを修正
avatarのパラメーターを受け取れるように、 controllers/users_consroller.rb を修正する
def user_params - params.require(:user).permit(:name, :age) + params.require(:user).permit(:name, :age, :avatar) end
users/indexを修正する
アップロードした画像の表示をするために、下記の通り追加
views/users/index.html.erb
<p> <%= image_tag @user.avatar.thumb %> <%= image_tag @user.avatar %> </p>
users_formを修正する
同じく、画像アップロードを受けつけられるように、下記の通りフォームを追加する
views/users/_form.html.erb
<div class="field"> <%= form.label :avatar %> <%= form.file_field :avatar, id: :user_avatar %> </div>
あとは普通に編集画面で画像を選択して保存すると、minioにアップロードされる。
minioの確認は localhost:9000/minio/にて可能。
employees/ バケットが作成されているようにしているが、実態は docker/s3/employees ディレクトリを作ってあるから。
S3に対して画像アップロードを行う場合、環境変数を変更するだけで良い(プログラムの修正は不要)。