飼い主不在の野良バッチをEC2からDocker環境に移行した話

この記事は[クラウドワークスアドベントカレンダー2020](https://qiita.com/advent-calendar/2020/crowdworks) 14日目の記事です。

## はじめに

こんにちは、[@shimopata](https://twitter.com/shimopata)です。最近はクラフトコーラ[^1]にはまっています。
昨年は「[2億件の本番データを無事に変換した話](https://qiita.com/shimopata/items/0cb64bd39e5d84c92934)」という記事を書かせていただきました。個人開発レベルだと経験することが少ない貴重な体験になったのですが、今年も自社サービスを提供しているような企業でないと経験することのない作業に携わらせて頂きました。あまり同じような経験をする方もいないよなーとか思いながらも、少しでも役に立ったらいいな、と思い書いてみることにしました。

## 概要

先日、EC2上で動作していたバッチ群をバッチサーバー基盤へ移行しました。バッチサーバー基盤というのは以前に、クラウドワークスエンジニアブログにて紹介したもので、Fargateを使った、バッチ実行環境のことになります。
以前紹介した記事 → [Amazon LinuxのEOLに伴いバッチをサーバレス化しFargateに移行した話](https://engineer.crowdworks.jp/entry/2020/07/20/180014) [^2]

移行のイメージとしては次の図の通り、1バッチ、1コンテナ環境を用意しました。[^3]

![upload_2c5be6cd6de0d619a85e1cf9c55c7fd3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122004/360b6734-7909-eed1-6cd0-665f3f561e48.png)

自分のチームの主な担当範囲はFargate上で動作するコンテナ以上のレイヤーの移行でした。そのため、Dockerファイルを作成し、その中で動作するようにバッチに適宜修正を入れたりする必要がありました。移行対象となるバッチは6種類ありそれぞれ対応の経緯が異なるので、1つだけピックアップして対応経緯をご紹介します。

## そもそもなぜ移行する必要があるのか

クラウドワークスでは様々なバッチが動作しており、いくつかのインスタンスに分散され実行されていました。その中にはAmazon Linux上で稼働しているものも存在したのですが、[Amazon Linuxは2020年12月末でサポート期間が切れてしまう](https://aws.amazon.com/jp/blogs/news/update-on-amazon-linux-ami-end-of-life/)という通知がありました。

期限を過ぎてもメンテナンスサポート期間が設定されると記載あるのですが、今後の脆弱性に対する対応や機能拡張性の観点から以下の理由で移行しました。

- 重要、重大なセキュリティアップデートだけが縮小パッケージセットとして提供され、全ての面倒を見てくれるわけではなくなってしまう
- 新機能に対するサポートが保証されない可能性がある

## 現状のバッチ運用の課題

移行対象となるバッチは自分が入社するよりも前から稼働していたものが多く、いわゆる歴史的経緯や担当者の異動などを原因として、次のような課題を抱えていました。

- 同じサーバーに複数のコンテクストのバッチが実行されていて、各バッチの関係性が不明確
- サーバーやバッチに関する資料が少ない。あっても、更新されてなくて実態を伴っていないものがある
- なぜ必要なのか、停止するとどんな影響があるのか知っている人が少ない
- 改修等を仕様にも当時のエンジニアが異動などで不在

などなど、野良猫ならぬ野良バッチのような状態で、各バッチの関係性や影響範囲を改めて明確にする必要がありました。

## バッチの移行に向けてやったこと

大きく分けて調査、設計、実装、移行、ドキュメント整備のステップに分かれます。各項目について詳細を説明していきます。

### 調査

移行にあたり、そもそも現行のバッチはどうやって動作しているのか、どのような実行環境なのかを調査します。具体的に調査したのは次の通りです。

- 何に使用されているものなのか、ビジネス的にどのような問題を解決するのか
    - 過去の関係者等にヒアリングを行い、そもそものバッチの必要性について再確認しました。必要で無いことが分かればそもそも移行する必要もなくなるためです。
- cron定義
    - 実行間隔やどのユーザーで実行されているのか、などを確認しました。
- cronから実行されているファイルの中身
    - 定義されているshellファイルやスクリプトの中身を確認し、処理内容を把握しました。
- サーバーに登録されている環境変数
    - 環境変数を、実行されているスクリプトと合わせて何の変数がどの様に使用されているのかを確認。

これらをすることでシステムの全体像が見えてきます。なんとなく全体が見えてきたら、サービス概要図の作成に取り掛かります。自分達が担当したバッチの概要図のイメージは以下の通りです。

![upload_6e1c020c34e3f50182910b49fa9d6907.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122004/eb604e3b-305b-d83d-25c4-d2ea16217319.png)


1. バッチを起動
2. データ分析用のデータベースに対してSQLを実行
3. 実行結果をサーバ内に保存
4. 指定の外部サーバーに転送 

### 設計

現行のバッチの全体像が把握できたところで、移行後の概要図を作成しました。
この際、実行基盤がEC2からFargateに変更した影響の検討を行いました。
今回のバッチの場合は、SQLの実行結果をCSVとして、EC2内にバックアップ用として一ヶ月間保存する仕組みになっていました。実行環境がFargateになるにあたり、S3に保存するほうが後々の勝手も良くなるだろうと考え設計の見直しをしました。

最終的に作成した設計のイメージは以下の通りです。

![upload_8a6ccd4b7e27b1ca3291f10faff93298.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122004/e0a8b2d7-52dd-7fa5-9ebd-2a3054bc039b.png)

こんな感じな設計図を作っておくと

- レビュー時にどこの機能を作っているのか説明しやすい
- 引き継ぎ用の資料として流用できる
- なにより、自分のシステムへの理解が深まる

といい事づくしなので作成するのがおすすめです。

### 実装

まずは、Dockerファイルの作成から取り掛かりました。イメージは[Alpine Linux](https://hub.docker.com/_/alpine)を使用しました。採用の理由としては

- バッチの要件がSQLの実行とファイルの転送をするだけの単純なことしかしない
- Alpine Linuxは必要最低限なパッケージしか入ってないため、セキュアな環境を構築できる
- Alpine Linuxはイメージが軽いのでビルドが早い

という点です。ビルドが早いというのは非常に重要な点です。バッチを実行するための環境を作るために、

- 必要なパッケージをインストール → 再ビルド
- 動作確認してみる → 前提パッケージが足りないことが分かる → 再ビルド
- バッチを実行するのに必要な権限が足りないことが分かる → 再ビルド
- 移行元の環境と比べてみるとユーザーを作成する必要がある事が分かる → 再ビルド

という様に、Dockerファイルに何か変更を加えるたびにビルドし、動作確認をします。1,2回程度であればビルドする時間は気にならないのですが、上記のようにトライアンドエラーを繰り返しながら何度もビルドを繰り返すので、イメージが軽くビルドが早いというのは開発効率を大きく左右します。
ということで、Dockerのイメージは出来る限り不要なものを排除した、軽いイメージを使用するのがおすすめです。

### 移行

Dockerファイルの実装や、バッチの修正が終わったら、移行の計画を立てました。なぜ移行の計画をたてる必要があるのかと言うと

 - 既存のデータを破壊してしまう可能性があるから
 - 元のバッチが冪等性のあるバッチでなく、バッチの再実行では元のデータに戻らないかもしれない

と、仮に失敗した時のリスクは無数に存在します。そこで以下の観点で移行の計画を立てました。

- 移行完了時に期待すべき結果は何かを明らかにする
    - バッチがエラー無く実行完了しただけでは、バッチが正常に動作しているとは断定できません。例えば、バッチの実行に成功したが、意図したデータが作成されてない場合もあります。場合によっては、実行したタイミングにより出力されるデータの件数が異なる場合もあります。必ず、何をもって移行が完了したのかということは決めておく必要があります。
- バッチが失敗した場合の影響はどこにあるのかを明らかにする
    - 実行に失敗した時、場合によっては、どこかしらで誰かがその影響を受けるはずです。前もってどのような影響が出る可能性があるのか、確認しました。
- 切り戻しは可能なのか、切り戻しのタイミングと切り戻し方法はどうするのか
    - 成功すると思っていたバッチが失敗すると必ず人間は焦ります。すると、余計なオペミスや判断ミスをしがちです。事前に切り戻し作業は可能なのか、元のバッチへの切り戻しのタイミングとその方法を確認しました。

事前に計画出来ることは面倒でもなるべく文章として残しておくと有事の際に役に立つのでおすすめです。

ちなみに、このバッチのケースだと以下の通りです。

- 期待すべき結果は何か
    - 移行前後のバッチ実行時に生成されるCSVのファイル行数が同じであること
- バッチが失敗した場合の影響はどこにあるのか
    - ユーザーに提示するデータの一部が古くなってしまう
- 切り戻しは可能なのか、切り戻しのタイミングと切り戻し方法はどうするのか
    - 移行前のバッチを実行することで切り戻す事が可能

切り戻しも行え、元バッチを実行したら元の状態に戻せるような、リスクの高い作業では無く、安心しました。


### ドキュメント整備

システムを移行する上で、大変だったのが、現行の仕様を読み取ることでした。バッチの資料に必要な情報が書いてあることもあるのですが、どこのシステムと連携しているのかや、cronの設定、バッチ実行時のユーザー情報など記載されていなこともありました。分からなければサーバーの中に入って値を調べたりなど、都度都度、調べては移行の作業を進める必要があり、なかなか骨の折れる作業でした。  
そこで、バッチサーバー基盤への移行が完了したタイミングで、「どういった情報が資料として残っていたら移行作業は簡単になったのだろうか」という話し合いをし、その情報をReadmeとして残すことにしました。  

打ち合わせ時に作成した資料はこちらです。
![upload_081307001f0ab5dd0d44d1270d3d3a13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122004/ca868ed4-e986-4ff8-3e1e-567b42d9b22b.png)


この中でいくつかピックアップして紹介します。

#### このサービス(バッチ)が何をしているのか、何を解決しているのか書いておく

例えば「分析用のデータベースに対して集計テーブルを作成するバッチ」などと書き記して置くことです。また、どのような問題が発生して、何を解決するために作成されたのか経緯等を簡単に記載しておくと、後からこのバッチをメンテする人は大変助かります。  
後から来た人にとって、このバッチが現在も必要なものなのかどうか判断がつかないことが多いです。実は時間が経過し別の要因により、いらないバッチになっている可能性は低くありません。コードや設定ファイルから動作を読み取ることは可能ですが、元々の要件をそれらから読み取ることは難しいです。  
そのため、何をしているかだけでなく、なぜ作ったのか、どのような課題感を持って作成したのか(何を解決したかったのか)を記載すべきです。


#### 機能全体概要図を記載する

作成したシステムやバッチによっては、アプリケーションサーバーやDB以外のリソースを利用することは少なくありません。AWSのリソース(S3,SQSなど)であったり、外部のアプリケーションのAPIを利用したりするケースあると思います。  
そのようなリソース単位で簡単な構成図を作るだけで、後から来たエンジニアの理解度は上がります。
個人的には[drowio](https://app.diagrams.net/)を使うと簡単に見やすい図が作れるのでおすすめです。

#### デプロイフローの記載

最近作成されたシステムであればCircleCIなどのCI/CDツールを使ってデプロイするのが一般的かと思います。
実際弊社のシステムの大半はそのようなツールによってほぼ自動化されています。  
しかし、何かしらの理由により、手動でデプロイする必要があったりする場合にはその手順を残しましょう。

#### 停止した時の影響

「そのサービスが何をしているのか」というとこにも通じるのですが、万が一、該当のサービスが停止した場合やバッチが停止した場合の影響も記載しておきましょう。サービスが何らかの影響により停止した際にどの程度の優先度をもって対応する必要があるのかが分かると、後任の方は非常に助かります。

## 終わりに

移行作業前に課題として上がっていた、課題は

- 同じサーバーに複数のコンテクストのバッチが実行されていて、各バッチの関係性が不明確
    -  コンテクストごとにコンテナを分けて管理することで解決
- サーバーやバッチに関する資料が少ない。あっても、更新されてなくて実態を伴っていないものがある
    - ドキュメントを整備することで解決
- なぜ必要なのか、停止するとどんな影響があるのか知っている人が少ない
    - ドキュメントを整備することで解決

といった形で解決できました。元々あった負債も解消した形で移行することが出来てよかったです。

書き終わってみて非常にニッチな記事だな、って感じですが、何か一つでもみなさんのお役に立てば幸いです。

明日の記事は@tmknomによる、バッチ移行についての組織マネジメントの記事になります。
超大作な予感しかしないので、そちらも合わせて読んでみて下さい!!

[^1]: 検索してみると通販してたり、自分でも作れたりするので興味ある方はぜひ
[^2]: よかったら、こちらの記事も読んでみて下さい!
[^3]: 同じ文脈のバッチについては、同じコンテナを利用して利用しているので、1コンテナに対して2つ以上のバッチを実行する環境もあります。