🎃

【Docker】1 台の EC2 で RDB も Nginx も Rails も動かす

2024/01/26に公開

Rails で API を開発しています。クライアントから呼び出したくなったのですが、現時点で RDS だったり VPC だったり設定するのがちょっとめんどうだったので、とりあえず検証用として、 EC2 に Docker 環境を構築して全部入りの Rails アプリケーションを作成しました。

流れ

  1. 必要なファイル群を用意する
  2. EC2 インスタンスの準備
  3. EC2 で動かすためにローカルで行う作業
  4. EC2 で DockerImage からコンテナ群を立ち上げる

ひな型

https://github.com/ndjndj/rails-ec2-verification

0. 必要なファイル群を用意する

以下のようなディレクトリ構造にする必要があります。

.
├─ compose.verification.yml
├─ nginx
|   ├─ Dockerfile.verification
|   └─ nginx.conf
└─ rails
    ├─ app
    ├─ bin
    ├─ config
    ├─ ...(他 Rails で必要なファイル群)
    ├─ Gemfile
    ├─ Gemfile.lock
    ├─ Dockerfile.verification
    └─ entrypoint.verification.sh

以下のどちらかで操作で /rails 直下に RailsApp のソースコード群を設置する。

  • ( 選択肢 1 ) サンプルリポジトリをテンプレートとして新しいリポジトリを作り、既存の Rails App をコピーしてくる

    このパターンの場合、以下の作業が必要

    • 下記ファイルの書き換え
      1. YourRailsApp/config/puma.rb
      2. YourRailsApp/environments/development.rb
    • YourRailsApp/ に下記ファイルを _rails/ からコピーしてくる
      1. _rails/Dockerfile.verification
      2. _rails/entrypoint.verification.sh
    • compose.verification.yml を適宜書き換える
  • ( 選択肢 2 ) サンプルリポジトリをテンプレートとして新しいリポジトリを作り、新規 Rails App の開発を開始する

    1. 以下のコマンドを実行して RailsApp の開発を開始する
      rails new のオプションは適宜変更してください。
      下記は API モードで作成しています。

      docker compose run --rm rails rails new . --skip --database=postgresql --api --skip-bundle
      
    2. /rails/.git は削除しておく

1. EC2 インスタンスの準備

使用したインスタンスに関する情報は下記のとおりです。
検証したところ、nginx + Ruby on Rails + Postgresql なら t2.micro で十分でしたが、ここは適宜変更してください。
ストレージに関しても、イメージサイズに応じて変更してください。
セキュリティグループは、インバウンドの SSH と HTTP を許可する設定にしてください。

name value
インスタンスタイプ t2.micro
AMI Ubuntu
ストレージ 20GB

1-1. Docker CE をインストールする

SSH クライアントを使用するなりして、EC2 インスタンスに Docker CE をインストールします。

# 古いパッケージの削除
sudo apt-get remove docker docker-engine docker.io containerd runc
# パッケージの更新
sudo apt-get update
# 関連パッケージのインストール
sudo apt-get install ca-certificates curl gnupg lsb-release
# Docker の GPG キー(暗号化ツールのキー)をインストール
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Docker のリポジトリ情報を apt に追加しパッケージとして利用することができるようにする
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 確認と更新
sudo apt-get update
# Docker CE のインストール
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# docker というグループユーザーを追加する
# ※自動的に追加されている場合があります
sudo groupadd docker

# 現在ログインしているユーザーを docker グループに追加する
sudo usermod -aG docker $USER

2. EC2 で動かすためにローカルで行う作業

2-1. ruby file の書き換え

既に用意されている nginx と通信するために設定を変更していく。

_rails/config/puma.rb を参考に、config/puma.rb を書き換える。
ポイントは、Port の設定を無効にすることと、nginx の設定を bind させること

config/puma.rb

# config/puma.rb
# port 設定を無効にする
# port ENV.fetch("PORT") { 3000 }

# -------------中略-------------

# nginx の設定を bind させる
app_root = File.expand_path("..", __dir__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

また、ホストの許可設定も追加する

config/environments/development.rb

# config/environments/development.rb 

# -------------中略-------------
config.hosts << "<ec2 パブリック IPv4 DNS>"

2-2. DockerImage の build

docker compose -f compose.verification.yml build --no-cache

2-3. 試しに up もしてみる

docker compose -f compose.verification.yml up

2-4. EC2 に送るソース一式を圧縮する

任意のディレクトリの圧縮ファイルを作成します。
このファイルを EC2 に送信、EC2 で解凍します。
今回は、rails-ec2-verification/ でリポジトリごと圧縮しています。

tar zcvf app.tar.gz ./

2-5. EC2 に送るように DockerImage を tar ファイルに変換する

nginx, Postgresql, rails の DockerImage を tar 形式で save します。
その後、EC2 に転送するために gzip 形式での圧縮も行います。

# 対象の DockerImage の ImageID を特定する
docker images 

# アーカイブ
mkdir docker-images 
cd docker-images 
docker save <nginx image ID> > nginx.tar
docker save <postgresql image ID> > postgres.tar
docker save <rails image ID> > rails.tar

# 圧縮
tar zcvf docker-images.tar.gz ./

この方法を使わない場合は、「3-2. Rails App を解凍」までスキップしてください。

ただ、EC2 の中で build をしてもいいのですが、gem のサイズによっては bundle install をしているときにマシンの CPU リソースが枯渇してしまう場合があります。
なので、転送に時間がかかってしまいますが、DockerImage はローカルで作ってしまうこちらの方法の方がおすすめです。

2-6. compose.verification.yml の build 対象を変更する

下記のように、DockerImage から build するように設定を変更します。

DockerImage の Image ID、もしくは Repository:tag を調べます。

docker images

compose.verification.yml の設定を変更します。

version: '3'
services: 
web: 
db:
rails: 
image: <image ID or repository:tag>
# image: xxxxxxxxxxx ID 
# image: raisl-ec2-verificaiton-rails:latest 
#build: 
#  context: ./rails
#  dockerfile: ./Dockerfile.verification
command: bash -c "rails s -b '0.0.0.0'"
volumes:
  - ./rails:/usr/src/app
  - tmp-d:/usr/src/app/tmp
depends_on:
  - db 
tty: true 
stdin_open: true
volumes: 
pg-data:
tmp-d: 
bin: 
driver: local

2-7. EC2 に DockerImage を送信する

キーペアの指定とパスに注意してください。
また WSL などで ホストをマウントしている場合は、permission error が発生することがあると思います。
その場合は、ホスト側から送信する方法や、WSL 側に pem ファイルを移動したうえで権限変更などの方法を試してください。

https://qiita.com/ShortArrow/items/404c60c6a11f114f0f4f

scp -i <pem ファイル> -r ../docker-images.tar.gz ubuntu@<ip>:/home/ubuntu/
scp -i <pem ファイル> -r ./app.tar.gz ubuntu@<ip>:/home/ubuntu/

3. EC2 で DockerImage からコンテナ群を立ち上げる

3-1. DockerImage の設定

DockerImage をロードします。
また、repository と tag が none になっているので、タグ付けもします。
compose.yml で image ID を指定している場合は無視してください。

# 解凍
tar zxvf docker-images.tar.gz

# load 
docker load < docker-images/nginx.tar
docker load < docker-images/postgres.tar
docker load < docker-images/rails.tar

# タグづけ
docker images 
# compose.yml で指定したタグを設定する
docker tag xxxxxxxx rails-ec2-verification-rails:latest
docker tag xxxxxxxx rails-ec2-verification-web:latest

3-2. Rails App を解凍

Rails Application のソースファイルや compose.yml を任意の場所に移動します

sudo su -
cd /
mv /home/ubuntu/app.tar.gz /usr/src/app.tar.gz
cd usr/src 
tar zxvf /usr/src/app.tar.gz

3-3. Docker up

docker compose -f compose.verification.yml up

4. 動作確認

ブラウザで パブリック IPv4 DNS にアクセスして、Rails のいつもの画面が出てくることを確認しましょう。

詰まったところ

puma.sock を nginx コンテナから参照することができず、502 Bad Gateway を解決できませんでした。
compose ファイルで、名前付きボリュームを設定することが必要でした。

https://teratail.com/questions/j5hlwyx137nhis

nginx.conf
user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid       /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    upstream app {
        # ソケット通信したいので puma.sock を指定
        server unix://usr/src/app/tmp/sockets/puma.sock;
    }

    server {
        listen 80;
        # ドメインもしくは IP を指定
        server_name localhost; 

        access_log /var/log/nginx/access.log;
        error_log  /var/log/nginx/error.log; 

        # ドキュメントルートの指定 
        root /usr/src/app/public; 

        proxy_connect_timeout 600;
        proxy_read_timeout    600; 
        proxy_send_timeout    600;

        client_max_body_size            100m;
        error_page 404             /404.html;
        error_page 505 502 503 504 /500.html;
        keepalive_timeout 600;
        location /healthcheck {
            root /usr/share/nginx/html;
            empty_gif; 
            break;
        }

        location / {
            try_files $uri @app;
        }

        # リバースプロキシ関連の設定
        location @app {
            proxy_set_header X-Real-IP       $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host            $http_host; 
            proxy_pass http://app;
        }
    }
}

compose.verification.yml

version: '3'
services: 
  web: 
    #--------略--------
    volumes: 
      - ./rails:/usr/src/app
      - ./nginx/nginx.conf:/etc/nginx/myapp.conf
      - tmp-d:/usr/src/app/tmp
  db: 
    #--------略--------
  rails: 
    #--------略--------
    volumes:
      - ./rails:/usr/src/app
      - tmp-d:/usr/src/app/tmp
    #--------略--------
volumes: 
  #--------略--------
  tmp-d: 
  #--------略--------

さいごに

検証用に EC2 1台で Rails も RDB も nginx も動かしてみました。
結構めんどくさかったですね。
多分もっと楽な方法があると思いますが、EC2 1台分の料金しかかからないのとめんどくさいだけで複雑な方法ではない点はメリットだと思います。
次回は、インスタンスを立ち上げたら自動で docker compose up できるようにしたいと思います。

参考

https://qiita.com/koki1720/items/38a626192777b0c82bdb
https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa
https://qiita.com/YuukiMiyoshi/items/306edbfb93de7e5dcec4
https://qiita.com/ShortArrow/items/404c60c6a11f114f0f4f
https://teratail.com/questions/j5hlwyx137nhis

Discussion