簡単なRubyスクリプトをDockerで実行する

背景

データの修正のために、ちょっとしたバッチを書くことになりました。環境はEC2でAmazon Linux2でした。

rbenvをインストールしようかなと思って準備をし、いざrubyをインストールしようとしたところで、gccが入っておらずコンパイルができないことに気がつきました…

いつもならyum install -y gccなど行うところですが、パッケージの追加もあまりしてほしくないということだったので、Dockerコンテナで実行することにしました。

Dockerfileの作成

まずはDockerfileを作成します。

EC2上に適当なディレクトリを作成して、その中にDockerfileを作成します。

1
2
3
$ mkdir app
$ cd app
$ touch Dockerfile

ベースイメージの取得

それではDockerfileのFROMに記載するベースイメージを決めようと思います。

Docker image Rubyでググると、Rubyのオフィシャルイメージが見つかります。

https://hub.docker.com/_/ruby

以前Rubyコミッタの方が最新のバージョンを使うのが速度的には一番良いとおっしゃっていたので、最新の3.0.2を利用します。

Dockerfileつづき

先程のRubyのオフィシャルイメージのページの下の方を見ていくと、How to use this imageというセクションがあり、実際の利用方法が記載されています。

例に書いてあるのを倣って作成し一度ビルドしてみます。Dockerfileは以下のようになりました。

1
2
3
4
5
6
7
8
9
10
11
$ cat Dockerfile
FROM ruby:3.0.2

RUN bundle config --global frozen 1

WORKDIR /usr/src/app

COPY Gemfile Gemfile.lock ./
RUN bundle install

CMD ["bundle", "exec", "ruby", "./test.rb"]

Gemfileは今回必要となるデータベース(MySQL RDS)への接続と、その接続のための認証情報を取得するためのgemのみ記載しました。

1
2
3
4
5
$ cat Gemfile
source 'https://rubygems.org'

gem 'mysql2'
gem 'dotenv'

イメージのビルド

ではこれでビルドします

1
2
3
4
5
6
7
8
9
10
11
$ docker build -t my-ruby-app .
[+] Building 5.0s (8/11)
=> [internal] load build definition from Dockerfile 0.0s
...
(中略)
...
=> ERROR [4/7] COPY Gemfile Gemfile.lock ./ 0.0s
------
> [4/7] COPY Gemfile Gemfile.lock ./:
------
failed to compute cache key: "/Gemfile.lock" not found: not found

Gemfile.lockはなかったですね。Dockerfileの7行目からGemfile.lockを削除して再実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker build -t my-ruby-app .
[+] Building 5.4s (9/11)
=> [internal] load build definition from Dockerfile 0.0s
...
(中略)
...
=> CACHED [4/7] COPY Gemfile ./ 0.0s
=> ERROR [5/7] RUN bundle install 1.0s
------
> [5/7] RUN bundle install:
#9 1.002 The deployment setting requires a Gemfile.lock. Please make sure you have
#9 1.002 checked your Gemfile.lock into version control before deploying.
------
executor failed running [/bin/sh -c bundle install]: exit code: 16

エラーになってしまいました。調べてみると、Dockerfileの3行目にある

1
RUN bundle config --global frozen 1

で、GemfileやGemfile.lockの変更を禁止しているからのようです。

https://bundler.io/v2.2/man/bundle-config.1.html

その行を削除しても良いのですが、一般的にimageのビルドはそのビルドしたimageをデプロイして動作させるので、デプロイの一環でもあります。上記のbundlerのマニュアルにもある通り、--deploymentフラグを有効にすると、BUNDLE_FROZENtrueになるようですし、Gemfile.lockはソースコード管理されているはずなので、Dockerfileはこのままでいきます。

Gemfile.lockの作成

ではGemfile.lockはどのように作成すれば良いでしょうか。

先程のDockerHubのページに作成方法が書いてありました。

1
2
3
4
Generate a Gemfile.lock
The above example Dockerfile expects a Gemfile.lock in your app directory. This docker run will help you generate one. Run it in the root of your app, next to the Gemfile:

$ docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app ruby:2.5 bundle install

イメージのバージョンだけ現在の3.0.2に合わせて実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app ruby:3.0.2 bundle install
Unable to find image 'ruby:3.0.2' locally
3.0.2: Pulling from library/ruby
...
(中略)
...
Status: Downloaded newer image for ruby:3.0.2
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Using bundler 2.2.22
Fetching mysql2 0.5.3
Fetching dotenv 2.7.6
Installing dotenv 2.7.6
Installing mysql2 0.5.3 with native extensions
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

無事Gemfile.lockが作成できました。

再ビルド

ではもう一度ビルドしてみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ docker build -t my-ruby-app .
[+] Building 11.6s (12/12) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 236B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/ruby:3.0.2 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 641B 0.0s
=> [1/7] FROM docker.io/library/ruby:3.0.2 0.0s
=> CACHED [2/7] RUN bundle config --global frozen 1 0.0s
=> CACHED [3/7] WORKDIR /usr/src/app 0.0s
=> [4/7] COPY Gemfile Gemfile.lock ./ 0.0s
=> [5/7] RUN bundle install 11.2s
=> [6/7] COPY .env ./ 0.0s
=> [7/7] COPY . . 0.0s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:a67fb078890bb0301ed294fed21fe1d349a1ab731604328470ac759 0.0s
=> => naming to docker.io/library/my-ruby-app 0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

イメージを確認します

1
2
$ docker images | grep -i ruby-app
my-ruby-app latest a67fb078890b 40 seconds ago 906MB

ちょっとサイズが大きい気がしますが、ビルドできました。

スクリプトの実行

では実行します。なにも指定しなければ、Dockerfileで最後に記述したコマンドが実行されます。

1
$ docker run my-ruby-app

実行ログなどを出力したい場合は、Gemfile.lockを作成した時のようにログを置くディレクトリを-vでマウントします。

test.rb

1
2
3
4
5
require 'dotenv'
require 'mysql2'

log = File.open('test.log', 'w')
log.write('log message')
1
2
3
$ docker run -v "$PWD":/usr/src/app my-ruby-app
$ cat test.log
log message

無事書き込みできていました。

まとめ

環境を変更することなく実行できるコンテナの長所を利用するといろんなことができるようになると思います。

これからはなにかを行うにしても1から実行環境を整えることもなくなりそうですね。便利な世の中になりました。