おはようございます。冪等性が大好きなうなすけです。
さて、つい先日にridgepole applyをECS RunTaskを用いて行うようにしたので、今までどのようにapplyしていたのか、RunTaskにする過程でハマッたことなどを書いていこうと思います。
ridgepoleとは
ridgepoleは、Railsのmigrationと同様のDSLでデータベースのスキーマを管理できるコマンドラインツールです。どのようなものであるかは、cookpadさんの以下のブログに詳細が書かれています。
TMIXとridgepole
TMIXは、歴史的経緯により、複数のRails applicationが共通のRDBを参照しにいくという構成になっています。database schemaをridgepoleで管理するようになるまでは、複数のリポジトリ間で db/schema.rb
のコピペを都度行なっていました。
ridgepoleのapplyについては、capistranoを用いて、アプリケーションの動作するインスタンスを間借りしてRDSに対してschema applyをするという形にしていました。理由としては、都度インスタンスを構築するのは面倒で複雑だし、schema apply用のインスタンスを起動したままにしておくのは無駄なコストが発生するためです。実際、この方法でのschema applyは後述する問題以外の不具合は発生することなく今まで動作していました。
簡単に構成を描いてみました。
インスタンス間借り方式の不具合
ある日のこと、schemaのapplyができなくなっていることに気づきました。Schemafileにtypoがまざっているというわけでもなく、開発用データベースへのapplyは正常にできるというのに、です。
原因は、間借りしているアプリケーションのサーバー側にありました。というのも、数日前にそのRailsアプリケーションの動作するRubyのバージョンを上げてしまっていたのです。その時にインスタンスを新規に作り直していたので、新しいバージョンのRubyのみが入っている状態になっていました。capistranoではdeployする際に指定されたバージョンのRubyを使用するように設定されているので、要求しているバージョンのRubyが存在せずdeployが失敗するようになってしまいました。
この問題については、ridgepoleで使用するRubyのバージョンをインスタンスにインストールされているものと揃えればいいだけ、と言ってしまえばそれまでです。
Docker化したときにどうするのか
また、今後TMIXのインフラをDocker化していくとなった場合には、いわゆる「Rubyがインストールされているインスタンス」というものが存在しなくなるため、今までのcapistranoを用いたschema applyは不可能になります。
ECSによるschema apply
前述の不具合と今後の懸念から、ECS RunTaskによるschema applyを行なうことにしました。では、ECS RunTaskによるschema applyのためには、どのような作業が必要になるでしょうか?
Docker imageの作成
まずは実行するためのDocker imageが無くては始まりません。一部加工しましたが、実際のDockerfileを以下に記載します。
localeをja_JP.UTF-8
にしていますが、これは何故かというと、いくつかのcolumnのdefault値に日本語の文字列が含まれているため、schema applyのときにridgepoleがinvalid multibyte char
で落ちてしまうためです。
CMD
がridgepole --apply
ではなく独自のRake taskになっているのは、schemaのapply結果を社内チャットツールに投稿するためです。
FROM ruby:2.4.1-slim WORKDIR /var/schema RUN apt update \ && apt install -y \ gcc \ libmysqlclient-dev \ locales \ make \ wget \ && rm -rf /var/lib/apt/lists/* # set locale to ja_JP.UTF-8 ENV LANG ja_JP.UTF-8 ENV LANGUAGE ja_JP:ja ENV LC_ALL ja_JP.UTF-8 RUN echo ja_JP.UTF-8 UTF-8 > /etc/locale.gen \ && locale-gen \ && update-locale LANG=ja_JP.UTF-8 LC_ALL=ja_JP.UTF-8 COPY Gemfile /var/schema COPY Gemfile.lock /var/schema RUN bundle install COPY . /var/schema CMD bundle exec rake apply:$DATABASE_ENV
task definitionの作成
Docker imageができていれば、task definitionは悩むことなく簡単に作成できるでしょう。今回はproduction用とstaging用の2つのtask definitionを作成しました。
テストの実行
ただschemaの定義がされているリポジトリですが、もちろんテストは存在します。どのようなテストなのかというと、そのschemaが実際にapplyできるかどうか、ということを確認するものです。tableやcolumnの名前のtypoは防ぐことができませんが、DSLの文法が間違っていないかなどの確認をすることができます。
運用をDockerに寄せるのであれば、テストもDockerで行なうのが筋というものでしょうか。なので、CIで稼動するMySQLに対してridgepole --apply
を実行する形式から、docker-composeでlinkしているMySQLのコンテナに対してridgepole --apply
を実行する形式に変更しました。
ただ、手元のマシンで実行したり、CI上のMySQLに対して実行したりするのとは違い、docker-composeでの実行は、mysqldが上がってくる前にridgepole --apply
を実行しようとして落ちてしまうこともあります。
そこでCIで実行するテストは、事前にdocker-compose up -d mysql
でMySQLのコンテナを立ち上げた後、mysqladmin ping
が成功するまで数秒間隔のループを回すことにしてこの問題を解決しています。
Deployする
さて、本命のdeployについてです。deployは特定のbranchへのmergeがされた時に実行するようにしていますが、具体的に何をしているかというと……
- ridgepole applyを実行するDocker imageのbuild
- buildしたimageにcommit hashのtagを付けてECRにpush
- 今buildしたDocker imageのtagが指定されたtask definitionの作成
- 作成したtask definitionを用いてRunTaskの実行
となっています。Docker imageのbuildをmaster branchへのmergeのタイミングで実行していないのは、deploy用branchからはテストがpassしている最新のcommit hashがわからないためです。deploy branchは常にテストがpassしているmasterからmergeされるので、mergeされた時点でのcommit hashを用いることにしています。
(厳密に言えば、git rev-parse master
を実行すればmasterのcommit hashは判明しますが、deployのタイミングでCI側にgitがインストールされていないため、環境変数に設定されている現在のcommit hashを使用しています)
ECS RunTask化成功
deployが成功すると、前述したRake taskによってこのようなメッセージが投稿されます。これによって、ECS RunTaskでのdeployが成功したことが確認できました!
今後の課題
さて、schema applyのDocker化はつつがなく終えることができましたが、Docker化という意味ではまだ課題が残っています。その一番大きなものが、「schemaがapplyされているMySQLのDocker imageを作成する」というものです。なぜこれが必要なのかというと、TMIXを構成する各Rails applicationは、ridgepoleにschemaの運用を移譲した結果、db/schema.rb
の中身が空なのです!
そのため、各Rails applicationでテストを実行したいときには、まずschema repositoryをcloneしてridgepole --apply
を実行するようにしており、ここで大幅な時間がかかっています。そのため、schemaが適用されているMySQLのimageをpullしてこれれば時間短縮になるのですが、まだ手が出せていません。
まとめ
課題は山積みですが、この調子でどんどんコンテナ化をやっていきたいと思っています!