ハトネコエ Web がくしゅうちょう

プログラミングやサーバー関連、チームマネジメントなど得た技術のまとめ

AWS S3 内の画像をすべてリサイズする方法( goofys 使用)

S3 にある大量の画像をリサイズしたい

ページ内に表示する画像の容量が大きすぎると、ページ表示に時間がかかってユーザー体験を損ねるばかりか、
AWS の通信料金も多くかかってしまい、サービス提供側の金銭にも直接ダメージを与えます。

以前 shrine への移行について書いたように、
Rails などの Web アプリケーションでは、
がんばってサムネを作り直してコードを書き換えて……で、(めちゃくちゃ大変だけど)なんとかなります。

一方で、Wordpress のような CMS では、
記事内のみならずウィジェットやユーザーアイコンなど、様々な目的のためにアップロードされた画像を
すべて一括でリサイズするというのは、なかなか簡単なことではありません。

そこで、今回は S3 にある画像自体を書き換えることとしました。

処理に失敗した場合を考え、必ず事前にバックアップを用意しておきましょう。
aws s3 sync コマンドを活用し別バケットにバックアップを取っておくのがおすすめです。( https://docs.aws.amazon.com/cli/latest/reference/s3/sync.html )

1. EC2 インスタンスの用意

今回は、

の EC2 インスタンスを用意し、作業しました。

S3 へのアクセスが必要なので、 AmazonS3FullAccess の権限を持つ IAM Role を作成し、
EC2 インスタンスにアタッチ
しています。

IAM Role の作成についてよくわからない方は、これなどを参考にするといいでしょう: https://www.ritolab.com/entry/16

また、セキュリティグループは自身のIPアドレスからのSSHログインのみ許すよう、しっかり最低限の設定だけしましょう。
それ以外に必要なインバウンドのポートはありません。

2. ImageMagick, pngquant のインストール

EC2インスタンスを作成し、SSHログインしましたら、
今回画像圧縮で用いる ImageMagickpngquant をインストールします。

ImageMagick のインストールはとても簡単です。

sudo yum install -y ImageMagick ImageMagick-devel

次に pngquant のインストールです。

sudo yum install -y gcc git
git clone --recursive https://github.com/kornelski/pngquant.git
cd pngquant
sudo make install

コンパイルのために gcc が必要なのでインストールしています。
特に configure の設定することなく make install すれば、
通常は /usr/local/bin/pngquant に pngquant がインストールされるかと思います。

3. goofys のダウンロード

S3バケットを mount することができる すごいアプリケーション goofys を使います。
同様のツール s3fs をよりパフォーマンス向上させたもののようです。

goofys は go 言語で作られているだけあって、バイナリも配布されています。
それを活用しましょう。

wget https://github.com/kahing/goofys/releases/latest/download/goofys
chmod +x goofys

これで現在のディレクトリに実行可能な goofys が用意されました。
お好みで /usr/local/bin 下への移動などおこなってもよいでしょう。

4. S3 バケットのマウント

goofys を使って S3 バケットをマウントする前に、マウントするためのディレクトリを作成します。
今回は /s3-data という名前にしました。

sudo mkdir /s3-data

必要なパッケージのインストールをします。

sudo yum install -y fuse mailcap

fuse がないと、 goofys でマウントしようとした際に
main.FATAL Unable to mount file system, see syslog for details
とエラーを吐きます。

エラーにある通り syslog を
sudo less /var/log/messages で確認すると
main.FATAL Mounting file system: Mount: mount: running fusermount: exec: "fusermount": executable file not found in $PATH#012#012stderr:
というエラーメッセージが見つかります。fuse がないために fusermount の実行ができないわけですね。

mailcap をインストールする理由は少し変わっていて、mailcap 自体が必要というよりも、
インストールによって /etc/mime.types を作成するのが目的です。

goofys が MIME TYPE を設定するための --use-content-type というオプションがあるのですが、
その際に MIME TYPE を推定するために必要なのが /etc/mime.types なのです。

こうして準備が整ったらマウントをします。
マウントする S3 のバケット名は test-s3-bucket としました。
ここは S3 ですでに作っている実際のバケット名に合わせて変更してください。

sudo ./goofys --use-content-type --acl public-read test-s3-bucket /s3-data

特にメッセージが出ることがなければ成功です
sudo ls -alh /s3-data で、S3 バケットディレクトリが見られるようになっているか確認しましょう。

URL直打ちで画像ファイルを見られるようにしたいので --acl public-read のオプションを付けているところにも注目です。

ちなみに goofys の実行時に sudo を付け忘れると
main.FATAL Mounting file system: Mount: mount: running fusermount: exit status 1#012#012stderr:#012fusermount: user has no write access to mountpoint /s3-data
というエラーが syslog に出力されつつ、マウントに失敗します。
root user でなければ権限が足りないわけですね。

5. 画像圧縮をおこなう

一度 root ユーザーになっておいた方がいろいろおこないやすいです。

sudo su
cd /s3-data

今回は JPEG, PNG それぞれの画像圧縮のために、以下のようなコマンドを打つことにしました。

convertimagemagick のコマンド)のオプションについては、
この間書いた記事をご参照ください。

find /s3-data -type f -name "*.jpg" | xargs -I {} convert {} -colorspace sRGB -resize 1280x1280\> -quality 80 -density 72 {}
find /s3-data -type f -name "*.jpeg" | xargs -I {} convert {} -colorspace sRGB -resize 1280x1280\> -quality 80 -density 72 {}
find /s3-data -type f -name "*.png" | xargs -I {} /usr/local/bin/pngquant --quality 65-80 --force --output {} -- {}
find /s3-data -type f -name "*.png" | xargs -I {} convert {} -resize 1280x1280\> {}

mogrify コマンドはサブディレクトリを掘っていって一括変換、ということが出来ないので、
find コマンドでファイルを探索したのちに xargs で
convert コマンドもしくは pngquant コマンドに渡してあげているのがポイントです。

なお、pngquant の処理後では mime type が goofys デフォルトの application/octet-stream に変わってしまいます。
今回の場合は、その後に imagemagick の処理をやったあとで mime type が image/png に戻ってくれるので気にしていませんが、
pngquant の処理のみおこなう方は、この点ご注意ください。(そしてどうやったら解決できたか教えてください…)

上のコマンド例ではバケット全体にかけていますが、
時間のかかる処理なので、ある程度ディレクトリごとに区切って実施するのが良いかと思います。

6. CDN のキャッシュリセット

CloudFront を使っている場合は、キャッシュをリセットしておきましょう。

Invalidations からおこなえます。(参考: https://dev.classmethod.jp/articles/aws-amazon-cloudfront-deleting-cache-by-invalidation/

おわりに

最初は AWS lambda でやろうとしていたんですが、
慣れない lambda の設定をしてるうちに「1度しかやらない時間のかかる処理を lambda でやらせるのって正しいのか……?」と気になってきたときに、
このStackOverflowの回答を見つけ、「こんなツールあるの! 便利じゃん!」と大きく舵を切り直して、結果的に速く作業を終えることが出来ました。

テスト用のバケットを作って検証を重ねていたので、失敗することなく済み、よかったです。

おまけ:Amazon Linux 2 への libvips のインストール

今回、最初は imagemagick でなく処理がより軽量で高速と聞いている libvips を使おうと思っていたのですが、
リサイズした画像ファイルを上書きする方法がどうしてもわからず結局 imagemagick を使いました。

というわけで、結局使っていないんですが libvips のインストール手順をメモっていたので
ここに残しておきます。

# libvips のインストール
# 参考: 
#   https://github.com/libvips/libvips/issues/1184#issuecomment-447973135
#   https://qiita.com/t-kigi/items/f6850abaaee1db2df5a4

sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install -y http://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum install -y yum-utils
sudo yum-config-manager --enable remi
sudo rpm -ivh http://mirror.centos.org/centos/7/os/x86_64/Packages/LibRaw-0.19.4-1.el7.x86_64.rpm
sudo yum install -y vips vips-devel vips-tools