見出し画像

いろいろハマった結果うっかり脱 ImageMagick してしまった話

ファームノートのエンジニアのながぬまです。Farmnote ではこれまで画像処理に ImageMagick を利用していましたが最近とある事情により脱 ImageMagick しました!

別に最初から脱 ImageMagick しようと思っていたわけではなくて、今回やりたかったことに対していろいろとうまくいかなかった結果「脱 ImageMagick しちゃいました」というのが実情だったりします。
そんなわけで「やってみたけどうまくいかなかった」エピソードを中心に、脱 ImageMagick しちゃいましたという話をお送りします。

背景:Pixel Flood攻撃がこわいなぁ

Pixel Flood 攻撃は特定の条件を持つ画像をアップロードすることでサーバーに負荷をかける攻撃です。ImageMagick を利用している場合、Pixel Flood 攻撃による影響を受ける場合があります。
当時の Farmnote は ImageMagick を API サーバー上で動かしており、こちらの攻撃によって API サーバーがユーザーからのリクエストをハンドリングできなくなってしまう可能性がありました。 実際に試したところ、この攻撃の影響を受けてしまうとわかったため対応することにしました。

Pixel Flood 攻撃の詳細については以下の記事が詳しいのでそちらをご覧ください。

方針:APIサーバー上で行っている画像処理をLambdaに移そう!

API サーバー上で行っている画像処理を Lambda に移すことで攻撃を受けた場合でもコンピュータ資源の大量消費を Lambda 環境側に押し付けることができ、攻撃の影響の大部分を API サーバーから切り離すことができると考え、この方針で行くことにしました。

Lambda の Blueprint(設計図)を確認してみると "nodejs - image-processing" というものがあり、Lambda 上で ImageMagick を使うことができるようだったので、ImageMagick の利用についてはやめることをせず、画像処理ロジックを単純に Lambda に移植することで画像に関する処理を API サーバーと切り離すことを考えました。
単純に現行のコードを載せ替えるだけで実装できるので、すぐに実装できるだろうという狙いでした。

スクリーンショット 2019-10-03 10.16.52

うまくいかなかった①:nodejs10.x は Amazon Linux 2 ベースになって ImageMagick が入っていない

困りました。

あれ、ImageMagick が入っていない・・・?

私たちは Lambda の nodejs10.x 環境を利用しようとしていたのですが、こちらの環境には ImageMagick が入っていないようで、Blueprint のコードが動作しませんでした。

スクリーンショット 2019-09-30 15.46.40

調べてみると Lambda の nodejs10.x 環境は Amazon Linux 2 になっており、nodejs8.10 以前の Amazon Linux をベースとした環境と差異があるようだということがわかりました。試しにnodejs8.10環境で Blueprint のコードを動かすと問題なく動き、ImageMagick による画像処理を行うことができました。

私達が観測した挙動の裏付けを取るため AWS の公式ドキュメントを探してみたのですが、残念ながら node8.10 以前のランタイムで ImageMagick がインストールされているという記事も node10.x でそれが削除されたという記事も見つけることはできませんでした。
仕方ないので、これはそういうものかと観測した事実を受け入れることにしました。

これから作成するものを EOL が近い node8.10 で動かすという選択はうまくないと判断した私達は Lambda 上で ImageMagick を使うのを諦め、別の方法を試すことにしました。

AWS 公式で Lambda で画像処理を行う別の方法が紹介されていたので、これをベースにすることにした

AWSの公式の記事で Lambda@Edge を利用して、リクエスト時に画像をリサイズして返す仕組みを構築するというものがあります。

ImageMagick をそのまま利用するというのを諦めた私達はこちらの仕組みをほとんどそのまま踏襲して画像処理用の Lambda を作成することにしました。私達の用途では画像をアップロードする際にリサイズ等の処理をすることで十分だったので、構成については一部変更し API サーバーから直接 Lambda を呼び出すという方法に変更しました。

紹介されていた方法では libvips というライブラリをラップした Sharp という npm を利用しています。libvipsは実行環境用のバイナリが必要となっています。Sharp では npm install をする際にバイナリをビルドし node_modules 内に含める仕組みになっているようでした。

このため、記事では Amazon Linux イメージから Docker コンテナを立ち上げ、そこで npm install しビルドしたバイナリを Lambda にアップロードするという手順をとっていました。

手元でテストするときは手元のマシンで npm install した node_modules で実施しないといけなくて、Lambda にアップロードするときには Docker で npm install した node_modules を利用しないといけないというのがミスの原因になりそうで怖いなぁという懸念はありましたが、いったんそこは呑んでこの方法を利用することにしました。

うまくいかなかった②:コンテナ内での node 管理を 出来ごころで nodenv に変更しようとしたらハマった

Docker コンテナ内で node をインストールするのに AWS の記事では nvm を利用していたのですが、普段私達が利用している nodenv を利用するように変更しようとしました。
nvm でも使えないことはないですが、使っているツールはなるべく揃えていくとよいかなという程度の気持ちで手を入れたところえらいハマりました。

nodenv は nvm に比べてインストールするために必要な依存関係が多く、1時間くらい依存関係の解決に取り組んでも解決することができませんでした。実のところどうでもいいところの変更だよね、というところを確認しなおし私達は nodenv を諦めたのでした。。。

解決していないこと

以上に紹介したような諸々を経て Farmnote は Pixel Flood 攻撃対策の結果、脱 ImageMagick することになりました。
今回の対応を経て、まだこれから変更していきたいなと思ってはいるものの解決できていない課題がいくつかあります。

・lambdaのCloudWatch metricsが出ていない:なんでだろ・・・
・Apex deprecated 問題:Lambda のデプロイに Apex 使ってたんだけど、deprecated になってた。別のいい感じのツールを探すか
・libvips の脆弱性への対応:今後は libvips の脆弱性を注視して対応していく必要があるぞ
・画像変換処理の非同期化:いまは同期的にやっているので、時間がかかる処理だとブロックしちゃっている。できれば非同期にしておきたい

まとめ

・AWS Lambda の Node 10.x では ImageMagick がつかえない
・nvm だって別にいいじゃない
・まだ解決していないこともある。もっとよくしていくぞ

結果的にたまたま ImageMagick をやめることになり、手元で ImageMagick をビルドする必要がなくなりました。
これまでまっさらな開発環境では ImageMagick のバージョンの縛りなどの問題でビルドがなかなかうまくできないみたいなことがよくあったのですが、今後開発に入ってくるメンバーはこの問題に遭遇しないで済むようになりました。やったね