RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

レガシーシステムをDockerコンテナ化する場合に直面した4つの壁

こんにちは。
株式会社ラクスで先行技術検証をしたり、ビジネス部門向けに技術情報を提供する取り組みを行っている「技術推進課」という部署に所属している鈴木(@moomooya)です。

ラクスの開発部ではこれまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「技術推進プロジェクト」というプロジェクトがあります。

このプロジェクトで「WEBアプリケーションのDockerコンテナ移行」にまつわる検証を進めているので、その中間報告を共有しようかと思います。

続きの記事も書きました。

tech-blog.rakus.co.jp

本検証での想定環境

  • Docker Compose
    • k8sも下期に検証できたらいいなぁ
  • GitLab CI
  • CIでの動作をひとまず目標として本番運用に必要な検証は後回し
  • 対象のサービスは20年以上運用されたレガシーなPHPアプリ
    • LAMP全盛期に開発されたアプリ

構成を一部抜粋するとこんな感じ。

WEBサーバーにDBサーバーも相乗りしている構成で、詳しくは後述しますがPHPプログラムとPostfix/Cronは密結合している状態です。

CIに不必要な部分は後回し

Dockerコンテナ化してCIを回しやすくする1ことを最優先として、本番運用に必要な要件や理想論的な設計は無理に追わないようにします。
CIを活発に回しやすくすることで、その後の改善難易度が下がると想定しています。

  • ログ収集の仕組み
  • DBや設定ファイルの永続化
  • 冗長性確保

例えば、上記のような観点はついでの範囲でやれるならやるが無理はしない。
Dockerコンテナ化を最優先。

既存アプリでコンテナ化の障害になった部分

OSコマンドを利用している

まず、解決課題に上がったのがこちらの課題でした。
PHPプログラムから、awkやfindなどのOSコマンドを呼び出している部分がありました。

今ではウェブアプリケーションからOSコマンドを叩くことはあまりしないと思いますが、今から約20年前――LAMP2が提唱されて間もない頃です――には、OSとアプリケーションの分離といった概念も今ほど浸透していなかったように思います3。
各種処理を実装するにあたって、今ほどライブラリエコシステムも充実していなかったということもあり、OSコマンドを活用することは当時の状況では合理的だったと思います。

いわゆる、レガシーシステムと呼ばれるウェブアプリケーションでは多く採用されていると思われます。
なお、当該アプリケーションはLinux + Apache + PostgreSQL + PHPのLAPP構成です。
しかし、Dockerコンテナ化を進めるにあたっては課題となります。
解決策としてはいくつかの方法が考えられます。

  1. OSコマンドで行っている処理をPHP実装に置き換える
  2. コンテナイメージにOSコマンドをインストール
  3. ホストのOSコマンドを呼び出す権限を付与

理想としては1の方法なのですが、利用しているOSコマンドの種類が20以上、利用箇所も100を超える部分で利用しています。
改修コストを掛けたところでサービスの顧客提供価値は変わりません。
となると、ビジネス的にはゴーサインは出せないでしょう。

3の方法はそもそもこんなことができると思ってもいなかったのですが、社内で中間発表を行った際にこんな事ができると教えてもらいました。
ただし、この方法はセキュリティ面もさることながら4、コンテナ化して得られるはずのポータビリティがDockerコンテナ実行環境のホストOSにインストールされているコマンドによって、オミットされてしまうので避けたいです。

となると、現実的な案としては2の方法になるかと思います。
「Dockerコンテナでの実行は余計なコマンドがない分セキュアになる」というメリットが薄れはするものの、現行の余計なOSコマンドまで揃っている状態に比べれば、利用するOSコマンドしか存在しない分だけセキュアになりますし、対応するコストもDockerfile内でコマンドインストール命令を1行追加するだけです。
コンテナイメージのサイズが若干増えるデメリットもありますが、全体で見れば微々たるデメリットだと思います。

最終的にはOSコマンドへの依存はなくしたいですが、Dockerコンテナ化してテスタビリティを上げてからのほうが依存の解消もやりやすそうです。
Dockerコンテナ化を優先するのであれば、2の方法が最良だと思います。

ミドルウェアとの密結合

次の課題はこちらです。
この課題は、詳しく見ると2つに分けることができて

  1. PHPプログラムから設定ファイルを書き換えてミドルウェアをリロードしている
  2. ミドルウェアの動作をトリガーにPHPプログラムが実行されている

の2つとなります。
素直にコンテナ設計をしていくとミドルウェアはPHPコンテナとは別コンテナになると思いますが、上記の処理が含まれている場合に素直に分けることができません。

まず1つ目の課題については、王道な設計として

  • 設定ファイルをDockerコンテナ外に出して永続化
    • 永続化された設定ファイルを更新
  • Dockerコンテナを再起動

があります。
しかしこの設計ですと…

  • 設定ファイルを永続化するストレージをどうする?
  • リロードした場合と再起動した場合でミドルウェアにアクセスできないタイミングが増える?

と課題が増えます。

2つ目の課題についてはミドルウェアによるトリガーをHTTPリクエストなどに変換して投げる仕組みと、リクエストを受け付けてPHPプログラムを起動する仕組みを作れば対応できそうです。
しかし、例によって該当箇所が複数あること、この改修を行っても顧客提供価値が変わらないことから初手では取りにくい対応です。

この課題については、まだ既存処理の理解を進めている最中なのでまた方針が変わるかも知れませんが、今回はCIに利用できるところまで持っていくという前提です。
そのため、最初は割り切ってPHPコンテナに相乗りさせようと思っています。
同一コンテナ内であれば既存の処理通り、設定ファイルのリロードもできますし、PHPプログラムの起動も可能になります。

ただし、この方針はCIで動作させることを前提としたものになります。
本番運用を視野に入れると設定ファイルが永続化されないといった問題や、ログファイル出力の課題が出てきます。

それでもDockerコンテナ化によってCI環境が簡略化され、活発にCIを活用することができるようになれば問題となっている部分の解決もやりやすくなると考えています。
まずはDockerコンテナ化。

オンライン系とバッチ系の密結合

3つ目の課題は、先述のミドルウェアの件と似ているのですが、オンライン系とバッチ系が1つのコードベースになっていることです。

既存の仕組みではCronからPHPプログラムを呼び出すことで実行制御を行っています。
これ自体は問題ないのですが、コンテナアーキテクチャにおけるバッチ実行制御というと以下のようなものが王道かと思います。

  • バッチ処理を実行するバッチコンテナの外にスケジューラーを用意
  • スケジューラーがバッチ処理を実行するバッチコンテナを起動
  • バッチコンテナは処理が終わったら停止

今回はCIでの利用をターゲットに定めているので、スケジューラーによる実行制御は必要ありません。
そのため、課題として見えているものの顕現しているわけではないので、あまり問題にならないので外部スケジューラーの準備や、外部スケジューラーからの起動を受け付ける仕組みの導入は見送っています。

大雑把に対応するのであればバッチ系のコードを分離できていない以上、オンライン系も含めたコードベースを持つバッチ用コンテナイメージを用意して、コンテナ起動時の引数で実行するプログラムを選択するような仕組みが一番楽でしょうか。
オンライン系が混在しているため、ソースコードの修正が入ったときにはバッチ用コンテナイメージも更新しなければならないのが手間ではありますが…。

バッチ系の分離もオンライン系と共有しているソースコードの扱いをどうするかなど、コード管理上の課題に波及してしまうので今回はDockerコンテナ化を最優先として後回しにしています。

ひとまず目指す状態

ここまで触れた課題を考慮した結果、まず目指す状態を上記の状態と設定しました。
見ての通り、ほとんどそのままコンテナ化します。

PostgreSQLコンテナ部分については本番運用時はコンテナではなくVMだったり、ベアメタルだったりという選択肢もあると思いますが、CIで利用する分にはデータも永続化せずに毎回リセットされた方が都合が良いです。

ただし、プロセスを相乗りするのはDockerコンテナの作法から外れることは間違いないので、(今回はあまり大きな問題ではないですが)後述のように別の課題が生まれてきます。

プロセス相乗りの影響

他にも課題となる影響があるかも知れませんが、プロセスの相乗りによって発生する4つ目の課題と認識しているのはログ出力についてです。

ログが複数出力される

stdout, stderrの2種類で対応できないログ出力が行われる。

  • ログの種類
    • access.log (stdoutに出力)
    • error.log (stderrに出力)
    • php.log
    • cron
    • maillog

php.log, cron, maillogの扱いを考えないといけない。

本番で扱うとしたら

  1. Dockerコンテナ内でログエージェントを持つ
  2. すべてstdout/stderrにラベル付きで出力してDockerコンテナ外で分離する

の2択かと思います。
CIで使えればいい(=エラー検知されたら人力で確認できればいい)ので

「3. ホストをマウントしてログファイル出力」

で対応します。
冗長化とか考え出すと問題が出てきそうですが、今回は後回しにします。

まとめ

  • OSコマンドの利用を排除する
  • ミドルウェアの設定ファイルをアプリケーションから更新しない
    • どうしても必要なら設定管理マネージャーとなるサービスを立てて最低限の独立性を保つ
  • せめてバッチ処理だけでも疎結合にする
    • できれば、単体のアプリケーションとして実行可能にできると理想的

今から設計、開発する場合には最初からDockerコンテナで動作させるケースが多いと思うので自然と回避できる部分が多いと思いますが、LAMP環境全盛期のレガシーシステムなアプリケーションをリアーキテクトする場合の参考にはなるかと思います。

これらの方針をもとに下半期に検証を進めて、実際にやってみてどうだったか報告できればと思います。


エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
20210916153018
https://career-recruit.rakus.co.jp/career_engineer/

カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com

ラクスDevelopers登録フォーム
20220701175429
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/

イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!

â—†TECH PLAY
techplay.jp

â—†connpass
rakus.connpass.com


  1. 現状でもCIは回しているが、CI実行するバージョンを切り替えるたびにCI用DBのマイグレーションが必要になるなど不便な状況になっている。
  2. Linux + Apache + MySQL + PHP/Perl/Pythonという構成。
  3. 歯切れが悪いのは20年も前となると私が学生だった頃で、趣味でウェブアプリケーションも書いていたもののプロダクションレベルで携わってきたわけではないのでちょっと自信がないためです。
  4. 現状がOS上で直接動作している状況なので、それに比べるとセキュリティリスクが高いわけではないとは思います。
Copyright © RAKUS Co., Ltd. All rights reserved.