しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

Docker+Railsでサーバー立ち上げメモ

Docker+Railsでサーバーを立ち上げた時のメモです。
元々Dockerを使わずに運営していたのですが、CentOS6がサポート切れになったことやbundle installでよく苦戦する事もあって環境を作り直す事にしました。
今回のOSはmacOSになります。

今回の構成ですがまずはCentOSのImageにRailsMySQLを乗せていく形で作っていきます。
DBやアプリケーションサーバーを分ける構成が推奨されているのですが、いきなり分割するのは難易度が高いので最初は1つのコンテナにすべて詰め込みます。

ローカル環境構築

まずはMacにDockerをインストールします。
インストールは下のサイトから行いました。

hub.docker.com

続けてDockerfileを作っていきます。
ここではrbenvを使ったRubyのインストールをしています。

下の方にあるENVはパスを通すためのコマンドです。
Dockerの実行中は.bash_profileを読んでくれないのでENVコマンドでパスを通しています。

FROM centos:8

# Ruby
RUN yum -y install git gcc make bzip2 openssl-devel readline-devel zlib-devel
RUN git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
RUN cd ~/.rbenv && src/configure && make -C src
RUN git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
RUN echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile && echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
ENV PATH /root/.rbenv/bin:/root/.rbenv/shims:$PATH
RUN rbenv install 2.5.1 && rbenv global 2.5.1

続けて下コマンドでImageを作成します。
tオプションはImageの名前やタグを指定するためのものです。

docker build -t my_image .

buildの後にdocker imagesコマンドを打つとImageが作られている事が分かります。

f:id:llcc:20210807172951p:plain

Imageを消したい場合はdocker rmi xxx(ImageのIDか名前)で削除する事ができます。
ImageIDは先頭の数文字だけでも問題ありません。
すべて一気に削除したい時は下コマンドが便利です。

docker images -aq | xargs docker rmi

次はImageからコンテナを作成して起動します。
起動は下のコマンドです。

docker run -itd --name my_container -p 3000:3000 --privileged my_image /sbin/init

http://localhost:3000でDockerの3000ポートに繋ぎたかったので下のように-pオプションでポートを指定しています。
他オプションですがdはバックグラウンド起動のため、iはホストの入力をコンテナに送るため、tはコンテナからの出力を受けるためのものです。
--privilegedはホスト側などへのアクセス権限を与えるオプションです。
マウント周りで権限関連のエラーが出たので追加したのですが、本番環境などでは避けた方が良いかもしれません。

コンテナへのアクセスする場合は下コマンドを使います。
loginオプションはbash_profileの読み込みの為に入れています、ただDockerfileのENVでRubyのパスを追加したので除いてもいいかもしれません。

docker exec -it my_container bash --login

試しにRubyバージョンを確認した所、無事に2.5.1が入っていました。

f:id:llcc:20210807173800p:plain

execを使うとコンテナ上でコマンドを実行します。
そのためbash --loginをlsに置き換えると下のようにファイル一覧を表示します。

f:id:llcc:20210809153556p:plain

立ち上げたコンテナですが停止したい時はdocker stop xxx(コンテナのIDか名前)、削除したい時はdocker rm xxx(コンテナのIDか名前)を使います。
起動中のコンテナを削除したい時は下のようにrmにfオプションを付けます。

docker rm -f xxx(コンテナのIDか名前)

次にMySQLをインストールします。
Dockerfileの下部に下の行を追加してImageを作り直して実行します。

# MySQL
RUN yum -y install mysql mysql-server

起動すると下のように実行できるようになっています。

f:id:llcc:20210807213453p:plain

最後にRailsを導入します。
Dockerfile最下部に下の処理を追加します。

# Rails
RUN yum -y install mysql-devel gcc-c++ nodejs crontabs wget fontconfig-devel
RUN wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 && tar jxf phantomjs-2.1.1-linux-x86_64.tar.bz2 && cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/bin/
RUN gem install bundler --version=1.16.3
RUN mkdir /my_app
WORKDIR /my_app
COPY . /my_app
RUN bundle install

rails s の-bオプションは127.0.0.1で立ち上げるとうまく動かないためです。
こちらは下サイトを参考にしました。

docker上のアプリにlocalhostでアクセスしたらERR_EMPTY_RESPONSEが出る - Qiita

この状態でコンテナに入り下コマンドを実行するとサーバーを起動できます。

systemctl start mysqld
rake db:create && rake db:migrate
rake db:seed
bin/rails s -b 0.0.0.0

MySQLのデータの永続化

次はデータベースのデータの永続化をします。
今はコンテナを削除するとデータが消えてしまうので、volumeを作る事で永続化しようと思います。
最初はホスト側にデータを保存しようと思ったのですが、MySQLの起動がうまくいかなかったのでvolumeを作る事にしました。

まずは下コマンドでvolumeを作成します。

docker volume create my_volume

docker volume lsコマンドを打つと今作ったvolumeを確認する事ができます。
削除はdocker volume rmコマンドを使います。

f:id:llcc:20210808195004p:plain

volumeはdocker runの-vオプションで指定します。
今回はMySQLのデータ部分をvolumeに保存したかったので-v my_volume:/var/lib/mysqlというオプションを付けました。

docker run -itd --name my_container -p 3000:3000 -v my_volume:/var/lib/mysql --privileged my_image /sbin/init

これでコンテナを作り直してもデータを保持するようになりました。

Docker Composeでコンテナを管理

次はDocker Composeを使ってコンテナを管理します。
Docker Composeを複数のコンテナを管理できるツールです。

まずは下コマンドでDocker Composeが入っていることを確認します。
入ってない場合は各環境に応じた方法でインストールします。

docker-compose version

次はDocker Composeの設定ファイルであるdocker-compose.ymlを作成して下のように記述します。

version: '3'
services:
  app:
    container_name: my_container
    privileged: true
    volumes:
      - my_volume:/var/lib/mysql
    ports:
      - 3000:3000
    command: /sbin/init
    build: .

volumes:
  my_volume:

起動コマンドは下のとおりです。
先程使っていたdocker runに比べると非常にシンプルになりました。

docker-compose up -d

コンテナの停止と削除は下コマンドになります。

docker-compose down

Imageの削除も行いたい場合は下のオプションを追加します。

docker-compose down --rmi all

データベースを別コンテナ化

Docker Composeを入れたので、コンテナもアプリケーションとデータベースで分けたいと思います。
下のようにdocker-compose.ymlにデータベースコンテナを追加します。
ホスト側からDBにアクセスしない場合はportsの部分は削除して構いません。

environmentのMYSQL_USERとMYSQL_PASSWORDではユーザーを作成しています。
MYSQL_DATABASEでデータベースの作成とそのデータベースへの権限付与を行います。

appの方にはdepends_onを追加します。
これによってdbを起動してからappを起動できるようになります。

version: '3'
services:
  app:
    container_name: my_container_app
    privileged: true
    volumes:
      - my_volume:/var/lib/mysql
    ports:
      - 3000:3000
    command: /sbin/init
    build: .
    depends_on:
      - "db"
  db:
    container_name: my_container_db
    image: mysql:8.0
    volumes:
      - my_volume:/var/lib/mysql
    environment:
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_DATABASE: my_db
      MYSQL_ROOT_PASSWORD: password
    hostname: db_container
    ports:
      - 3306:3306

volumes:
  my_volume:

起動するとデータベースのコンテナも作られている事が分かります。

f:id:llcc:20210809220013p:plain

ホスト側からは下コマンドでアクセスできるようになります。

mysql -h localhost -u test --protocol=tcp -p

次はアプリケーション用コンテナから今作ったデータベースを見るようにします。
まずはDockerfileから下のようにMySQL関連の処理を削除します。

FROM centos:8

# Ruby
RUN yum -y install git gcc make bzip2 openssl-devel readline-devel zlib-devel
RUN git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
RUN cd ~/.rbenv && src/configure && make -C src
RUN git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
RUN echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile && echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
ENV PATH /root/.rbenv/bin:/root/.rbenv/shims:$PATH
RUN rbenv install 2.5.1 && rbenv global 2.5.1

# Rails
RUN yum -y install mysql-devel gcc-c++ nodejs crontabs wget fontconfig-devel
RUN wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 && tar jxf phantomjs-2.1.1-linux-x86_64.tar.bz2 && cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/bin/
RUN gem install bundler --version=1.16.3
RUN mkdir /my_app
WORKDIR /my_app
COPY . /my_app
RUN bundle install

Railsのdatabase.ymlにDBコンテナのパスワードやホスト情報を追加します。

default: &default
  adapter: mysql2
  reconnect: false
  pool: 5
  username: user
  password: password
  host: db_container

development:
  <<: *default
  database: my_db

test:
  <<: *default
  database: my_db

production:
  <<: *default
  database: my_db

これでデータベースのコンテナを分割する事ができました。
あとは下コマンドでアプリサーバーを立ち上げれば完了です。

rake db:create && rake db:migrate
rake db:seed
bin/rails s -b 0.0.0.0

起動に関する処理の自動化

次は上に記載したrails sなども自動化していきます。
まずは自動化用のファイルdocker_app_command.shを作成して下のように記述します。

#!/bin/sh

rake db:create && rake db:migrate
rake db:seed
bin/rails s -b 0.0.0.0

ファイルには実行権限を与えておきます。

chmod 755 docker_app_command.sh

次はdocker-compose.ymlにcommandを追加して先程追加したファイルをセットします。
commandはコンテナ起動時に呼ばれる処理になります。

services:
  app:
    container_name: my_container_app
    ports:
      - 3000:3000
    build: .
    command:
      /sbin/init && ./docker_app_command.sh

ローカルでの更新を即時反映する

次はホスト側で編集したコードを即時反映されるようにします。
今は変更の度にImageの作り直しが必要なので時間がかかってしまいます。

コードの反映はdocker-syncというツールを使います。
ホスト側をマウントする形も考えたのですがパフォーマンスが良くないようなのでdocker-syncで進めていきたいと思います。

まずは下コマンドでdocker syncをインストールします。

gem install docker-sync

次にdocker-sync.ymlというファイルを作って下のように記述します。

version: "2"

syncs:
  my_sync_volume:
    src: "."
    sync_excludes:
      - "log"
      - "tmp"
      - ".git"

docker-compose.ymlのservices → app → volumesとvolumesにdocker-sync.ymlで定義したVolumeを追加します。

services:
  app:
    volumes:
      - "my_sync_volume:/my_app:nocopy"

volumes:
  my_volume:
  my_sync_volume:
    external: true

Docker Syncは下コマンドで動かします。

docker-sync start

docker-sync利用時ですが.docker-syncというフォルダが作られるので下のように.gitignoreに追加すると良いかと思います。

.docker-sync

同時にアプリケーションサーバーのDockerfileも修正します。
今までプロジェクト全体をCOPY . /my_appでコピーしていましたが下のように最小限にします。
そうする事で更新が最小限になってbundle install部分もレイヤーキャッシュを使ってくれるようになります。

COPY Gemfile /my_app/Gemfile
COPY Gemfile.lock /my_app/Gemfile.lock
RUN bundle install

コンテナ起動時にもbundle installをしてほしいのでdocker_app_command.shにbundle installを追加します。

#!/bin/sh

bundle install
rake db:create && rake db:migrate
rake db:seed
bin/rails s -b 0.0.0.0

これで無事にRailsを立ち上げられるようになりました。

その他利用したコマンド

キャッシュの全削除。

docker builder prune

キャッシュ利用状況の確認

docker system df

CentOS8へのインストール

Dockerをyumでインストールしたらうまくいかなかったので下コマンドでインストールと起動をしました。

dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf -y install --nobest docker-ce docker-ce-cli
systemctl enable docker
systemctl start docker

Docker Composeのインストールは下コマンドで実施しました。

wget https://github.com/docker/compose/releases/download/1.25.5/docker-compose-Linux-x86_64
mv docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

参考URL

Docker入門(第一回)~Dockerとは何か、何が良いのか~ | さくらのナレッジ

rbenvの使い方と仕組みについて - Qiita

docker images を全削除する - Qiita

【Docker】コンテナの停止と削除を同時に行う - (O+P)ut

Ruby on Railsプロジェクトの開発環境をDocker化する - Qiita

CentOS8 Docker/Docker Composeインストール - Qiita