背景 データの修正のために、ちょっとしたバッチを書くことになりました。環境は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_FROZENもtrueになるようですし、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から実行環境を整えることもなくなりそうですね。便利な世の中になりました。