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

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

Jitsi Meet を使って自鯖でビデオチャットサービスを動かしてみた

外出自粛でビデオチャットが流行

新型コロナウイルスの影響もあって、いろんなビデオチャットサービスが出ていますね。
ZOOMは色々ありましたが、すぐにセキュリティ対策に乗り出したおかげで、画質と通信の安定性や料金のバランスが取れているため依然強いです。

料金は高いですが「席の移動」という概念がある Remo はおもしろそうですし、
今は無料で公開されている、距離の概念がある Spatial Chat も流行りつつあります。
ネット配信のようにPC画面を背景に映せる mmhmm というのも最近出てきました。

(参考)

こういうサービスを使うのも楽しいですが、
エンジニアですし、無駄に自鯖独自ドメインビデオチャットルームを立ち上げたくなりますよね? ね?

というわけで Jitsi Meet を使ってみました!

Jitsi Meet 構築にあたり

環境構築をやったのが3ヶ月前の4月なのでハマリポイントを全然覚えていなくてすみません。
今記事では具体的な構築手順を省略させていただきます。

Jitsi Meet(ビデオ会議システム)のサーバを Docker Compose で起動する手順』の記事を参考に、
docker-jitsi-meetVPS上で動くよう、Ansible Playbook を書いて立ち上げました。

nginx の設定としては以下のようにしました。

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    # 中略

    location / {
        proxy_pass https://localhost:4443;
    }

    location ~ .*\.(mp3|wav) {
        expires 7d;
        proxy_pass https://localhost:4443;
    }

    location = / {
        auth_basic "Please input username and password";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

サイズが大きく、かつ、中身が変わるわけではない音声ファイルはクライアント側にキャッシュさせるようにしたこと、
ルートには自分だけ入れればいいのでBASIC認証をかけたこと、が普段の設定とは異なった点でしょうか。
(ルートにBASIC認証をかけましたが、 /hogehoge などにアクセスすれば誰でも部屋が作成できてしまうので実はほとんど意味がありません……)

VPS としては DigitalOcean を選び、Standard 1 CPU / 2GB Memory / 50GB Disk のプランを選びました。
これで月10ドルだから安いですよね。

Jitsi Meet を使ってみて

動かしてみると、ほぼ https://meet.jit.si と同様のサイトが立ち上がります。

f:id:nekonenene:20200712121313p:plain

部屋に入ってみるとけっこう多機能で、YouTube動画を共有する』機能はおもしろかったです。
参加者でひとつのYouTube動画を見られる面白いコンセプトなんですが、
再生中に全員のマイクがミュートされちゃって、マイクのミュートを解除すると動画がミュートされるという、
動画を見ながらガヤガヤするという肝心なところが出来ずそこは残念でした。

テキストチャットはできるので、YouTube の「プレミア公開」ごっこは出来ます。

f:id:nekonenene:20200712121627p:plain
部屋に入ったあと右下の「その他のアクション」から色々できます

4人で話していたときの負荷は以下のようになりました。
CPU負荷が通話している間だけ高まっているのが特徴的ですね。30%前後をウロウロします。

f:id:nekonenene:20200711172202p:plain

f:id:nekonenene:20200711172300p:plain

メモリ負荷は50%くらいまで行きます。
Docker Compose を restart したあとでもすぐ40%程度にはなるので、
メモリ 2GB を選んでおいて正解でしたね。1GB だとなんらかの悪影響が出るかと思います。スワップ領域は少なくとも確保しておくべきでしょう。

アプリにも対応していて、Jitsi Meet アプリを入れて、「設定」からURL設定をおこなえばスマホからでもアクセスできます。

f:id:nekonenene:20200712130217j:plain
「サーバーのURL」を設定します

つらいところ

JavaScriptファイルが大きくて、サーバー側でgzip圧縮かけてても 1.2MB、素だと 3.7MB もあるので、
アクセス時にページを開くまでまあまあ時間かかるのが最大の欠点ですね。

ネットスピードが速くない環境だと読み込みが途中でキャンセルされて
以下のようなエラー画面が表示される場合まであります……(すぐにリトライが走ってくれますが、あんまり良くはないですね)

f:id:nekonenene:20200712124604p:plain

Webにおけるビデオチャットの難しさを感じます。
もちろんアプリ版ではこの問題が起こらないわけですが、ビデオチャット相手にこのためだけにアプリを入れてもらうのは申し訳ないですからね…(笑

自分のドメインのページに人を呼ぶのはおもしろいんですけれど、
結局のところ、よく使われてるビデオチャットサービスを使うのが参加者のストレスとしては少なくて良いでしょうね。

Rails 4 から Rails 5 へのアップデートでおこなった10個以上の対応

関わっている会社の Rails アプリを 4 から 5 に上げましたので、おこなったことを書いておきます。

1. なぜ上げたかったか

Rails 5 は多くの便利メソッドが足されていて開発がいっそう効率的になる、だとか、
世の中で書かれている記事の多くが Rails 5 を対象にしているものになってきた、だとか、
Rails 6 が安定してきて、Rails 4 の対応を外す gem がそろそろ増えていきそう、などの理由があります。

そして一番大きかった理由が Ruby 2.6 にした場合に deprecation warning が出てしまったこと、ここにありました。

/bundle/gems/activesupport-4.2.11.3/lib/active_support/core_ext/object/duplicable.rb:111: warning: BigDecimal.new is deprecated; use BigDecimal() method instead.

実は Rails 4 は v4.2.11.3 が 2020/05/15 にリリースされるなど、
今でもしっかりセキュリティメンテナンスがおこなわれています。

しかし Ruby 2.5 については来年2021年3月にはサポート終了することが
Ruby のリリースサイクル上決まっています。

deprecation warning を無視して Ruby 2.6 に上げる手段も取れますが、
この警告が出ているということは Ruby 2.7 ではメソッドが削除されることを示唆されており、*1
そのような警告を無視したまま Ruby のバージョンを上げてしまうのは危険です。

deprecation warning が確認できない状態に周辺を揃えてから Ruby のバージョンは上げるべきです。

……というわけで、セキュリティサポートの対象から外れないよう
Rails 4 を2021年3月までに卒業しなくては、という意識でいました。
(最悪警告を無視して Ruby 2.6 に上げるとしても、それのサポート終了は 2022年3月)

2. 下準備

Rails 5 に上げることは頭の中にあったので、着々と準備を進めていました。

関わり始めた2019年12月の時点では、

でしたので、リリースノートの確認とステージングでの入念な確認をおこないつつ、

  • Ruby 2.4.9 へのバージョンアップ
  • Rails 4.2.11.1 へのバージョンアップ
  • Ruby 2.5.7 へのバージョンアップ
  • Rails 4.2.11.3 へのバージョンアップ

を順々におこなっていきました。

Ruby のバージョンアップや Rails のパッチバージョンアップはわりと破壊的変更が少ないので、期待していたとおり特に新たな対応をおこなう必要なくバージョンアップをおこなえました。
Ruby 2.5 系で CSV.generate のバグがあるのでそこだけ注意ですかね。

また、不必要な gem の削除や、gem のバージョンアップを可能な限りおこない、
依存関係上 Rails 5 に上げる妨げとなる refile からの脱却もするなど(単純に refile で出来ないことがあったのが一番の理由だけど)、
着々と準備を進めてきました。

おかげで、 Gemfile 内を

gem 'rails', '5.0.0'

と書き換えた上で bundle update rails をおこなってもエラーが起こらない状態にまではなっていました。

テストについては、gem の機能そのものを試しているような無駄なテストがある一方で、
モデルやビューなどのテストがほぼ皆無でしたので、機会を見つけて少しずつ(ほんの少しですが)足していました。

3. Rails 5 に上げた際の作業内容

主にこちらの記事を参考にしました。

3-1. Rails 5.2.4.3 を選択

最初は Rails 5.0.0 にアップデートして少しずつ上げていく予定でしたが、
v5.0.0 では bin/rails app:update をおこなうと

undefined method `instance' for ActiveSupport::Deprecation:Class (NoMethodError)

とエラーが出てしまい、これを解決するには v5.0.6 を使うという話を見て、
「そういう Rails 5 の初期の方のバグを踏むくらいなら、はじめから Rails 5 の最新版を使ったほうがいいのでは」という気持ちになってきて、
5系の最新である v5.2.4.3 にアップデートすることに決めました。

……ただし、これはサービス利用者があまり多くなく、ふだんのDB書き込み量から考えて、
ある程度の問題が起きても対応できると考えての判断なので、
通常は Rails 5.0 → 5.1 → 5.2 と上げていくことをおすすめします。

Rails 5.0 で deprecated warning、Rails 5.1 でメソッド削除、などの変更が、
特に Rails 5.0 と 5.1 間では多いですから、慎重を取るに越したことはありません。

Gemfile に

gem 'rails', '5.2.4.3'

と記載したら

bundle update rails

です。

3-2. bin/rails app:update

bin/rails app:update

で config ファイルの更新などがおこなえます。

上書きするか聞かれる時に「d」を押せば diff が見られますので、
それを見て上書きするか考えると良いでしょう。

diff を見ることで、「あれ? もしかしてこの config ってなくなってる?」などに気付けます。
例えば config.raise_in_transactional_callbacks なんかは Rails 5.0 で deprecated、Rails 5.1 で削除されています。

3-3. まずは web サーバーが起動するようになるまで

私の場合は routing に問題があって引っかかりました。

resources :user_feedbacks, only: :index do
  get "/:year/:month", on: :collection, to: :monthly_index, as: :monthly
end

と書かれている場所が「controller 指定しないとダメよ〜」ってエラーが出ていたので、

resources :user_feedbacks, only: :index do
  get "/:year/:month", on: :collection, to: "user_feedbacks#monthly_index", as: :monthly
end

と直しました。

Web サーバーが起動しない系の問題は、明確なエラーが出るのでここはなんとかなることでしょう。

3-4. Single Table Inheritance

STI (Single Table Inheritance) の仕組みを使っている箇所があったのですが、これが通常の Rails way に乗っかっていない書き方であったため少し苦労しました。 親のモデルに self.find_sti_class(type) メソッドと self.sti_name メソッドをいい感じに定義してあげることでなんとかしました。

どんな感じかと言うと、Music モデルから派生する Jazz モデル、Rock モデルがあるとして、
通常は Music.find(123).is_a?(Jazz) みたく判定させればいいのですが、
Music.find(123).jazz? で判定させたかったようで、

class Music < ActiveRecord::Base
  enum type: { jazz: "Jazz", rock: "Rock" }
end

このように type カラムについての定義がありました。
しかしこれだと、 jazz モデルは存在しないので
SubclassNotFound エラーを吐いてしまいます。

そういうわけで、 find_sti_class, sti_name メソッドを上書きするように

class Music < ActiveRecord::Base
  enum type: { jazz: "Jazz", rock: "Rock" }

  class << self
    def find_sti_class(type)
      type.camelize.constantize
    end

    def sti_name
      types[name.demodulize.underscore]
    end
  end
end

と書いたわけです。本来のコードとは離れるので、動くけど微妙感もあります。

今思いついたんですけど、enum 的な使用が jazz?, rock? メソッドだけなのであれば、

以下のような対処でよかったですね。
不必要に override しちゃうのは今後問題を起こす可能性があるので、これでもいけるか後で見ておきます。

class Music < ActiveRecord::Base
  def jazz?
    is_a?(Jazz)
  end

  def rock?
    is_a?(Rock)
  end
end

余談ですが、Single Table Inheritance のような機能は、
サービス運営を続けていくうちに共通部分がほとんどなくなる、というケースが往々にしてあるので、できれば使わないほうがいいと思います。

3-5. belongs_to にデフォルトでバリデーションが働くようになったことへの対応

Rails 5 への変更で一番大きかったのはここだと思います。

class Song < ActiveRecord::Base
  belongs_to :vocal
end

のような定義がある場合、Song 作成時・更新時に vocal_id が nil だと
バリデーションエラーが出るようになりました。

vocal_id が nil の場合がありえるのであれば、

class Song < ActiveRecord::Base
  belongs_to :vocal, optional: true
end

と書かなくてはいけません。optional: true を付ける必要があります。

逆に言えば、今まで

class Song < ActiveRecord::Base
  belongs_to :vocal
  validates :vocal_id, presence: true
end

となっていたのであれば、 validates の行を消しても問題ないわけですね。

この対応はしっかりおこなわないと、
アプリケーションエラーが起こらないぶん、ユーザーが困っているのに開発者が気付かない、ということが起こりえますので充分に確認しましょう。
自信がなければすべてに optional: true を付けてしまうのも、無くはない選択だと思います。

3-6. skip_before_filter を skip_before_action に

xxxxx_filter コールバックは無くなり xxxxx_action に統一されましたので、 skip_before_filterskip_before_action に、 before_filterbefore_action に変更しましょう。

3-7. create.js → create.js.erb に

Rails非同期通信処理をおこないたい場合に、
create.js というファイル名でも ERB 記法を埋め込むことが出来ましたが、
.erb の拡張子でないとおこなえないように変更されていますので修正しました。

3-8. Ridgepole に --allow-pk-change オプションを付ける

Rails 5 では、id カラムのデフォルトの型が integer から bigint に変更されました。

id の型を bigint にしたい場合は、ridgepole の実行に --allow-pk-change オプションを付与することをお勧めします。
このオプションを使用するには、ridgepole が v0.7.1 以上である必要があります。

オプションを付けていないと rspec 実行時にこんな warning を吐いたりします。

[WARNING] Primary key definition of `versions` differ but `allow_pk_change` option is false
  from: {:id=&gt;:serial}
    to: {}

もしくは、id の型を integer のままにする選択もあります。
テーブル作成のオプションとして MySQL の場合は id: :integerPostgreSQL の場合は id: :serial を追加します。
参考: https://kamihikouki.hatenablog.com/entry/2017/04/11/141447

私は integer のままにする選択を取りました。
vocal_id などの外部キーと関連するカラムも bigint に変更する必要がありますし、
bigint は integer の2倍の 8 Byte ですから、多少データベースサイズが大きくなってしまうことが予想されます。

integer で足りないということは21億行を超えるデータ数となるわけですが、
将来的にそうなりそうなテーブルはほとんどなかったため、 bigint に変えない選択を取りました。
必要に応じて bigint に変更していけばいいと思います。

3-9. autoload 廃止への対策

Rails 5 では autoload が development 環境以外では効かなくなりました

この変更はけっこう悪魔的で、
development 環境では autoload が効くので、
本番(ステージング)にリリースした時に初めて動かないことに気付く、ということが起こりえます。

詳しくはこちらをお読みください ↓
https://qiita.com/joooee0000/items/3ab0f3d791e0d0beb639

いくつかの解決方法が述べられていますが、Rails 4 からのバージョンアップの場合であれば、

config.enable_dependency_loading = true

config/application.rb に足して autoload を本番環境でも有効にする、というやり方が一番安全だと思います。
autoload を廃止しようという動きからこの変更がなされたことを考えると邪道に思いますが、
開発環境で動くのに本番環境で動かない、という恐ろしい罠を避けるには安全を取りたいかなと……。

autoload_paths に追加しているのが lib だけならば、

config.paths.add "lib", eager_load: true

と書くやり方でいいと思います。

3-10. time 型の挙動変更への対応

Rails 5.1 から time 型はデフォルトでタイムゾーンに対応しました。
参考: https://blog.willnet.in/entry/2015/06/12/063731

これにより、データベースのタイムゾーンUTC、アプリケーションのタイムゾーンが日本時間の場合、
Rails 4 では日本時間 19:00 を示す場合、データベースにも 19:00 と保存されていましたが、
Rails 5.1 ではデータベースに 10:00 と保存されるようになります。

つまり!

Rails 4 の時代にデータベースに 19:00 と保存されているデータが、
Rails 5.1 では日本時間 04:00 と解釈されます! 時刻がズレます!!

これを防ぐためには、 config/application.rb

config.active_record.time_zone_aware_types = [:datetime]

と記述しましょう。

私はリリース後にこの問題に気付いてしまったので、 config を変えるのではなく
保存されてるデータを 9 時間前にして保存し直すスクリプトを書いて対応しましたが、
ユーザーから見える時間がズレることが1秒たりとも許されない場合は、
上で書いた config を上書きする対応をするしかないかと思います。

3-11. その他いろいろ

autoprefixer に「display: box; じゃなくて display: flex; を使うようにして」って警告出されたので対応したり、

validates :office_email, format: { with: VALID_EMAIL_REGEXP }, if: "office_email.present?"

のように validates の if 内で String を使う方法は廃止されたので、

validates :office_email, format: { with: VALID_EMAIL_REGEXP }, if: Proc.new { |vocal| vocal.office_email.present? }

というふうに直したり、 enum の扱いの微妙な違いなど、
細かい修正をいくらかおこないました。

モデルについての仕様変更が多いように感じました。
主要なモデルのテストを作っておいたために気付けたことが多く、テストの偉大さを再認識しました。

3-12. あとでやればいいこと

  • ActiveRecord::Base を継承した ApplicationRecord を作成し、model はそれを継承するようにする
  • form_for, form_tag から form_with への切り替え
  • jquery_ujs を rails-ujs に変える

これらも Rails 5 においての大きな変化ですが、急いでおこなう必要はないです
Rails 5 対応は大きなプルリクエストになりますから、必要以上に diff を増やしてレビュアーの負担を増やす必要もないと思います。

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

ApplicationRecord クラスを作ってそれを継承するようにすることは、
文字列置換をすればササッとできることなのでやってもいいと思いますが、
Rails 5 対応リリース後の別のプルリクで良いでしょう。

form_with に切り替えるのは、失敗した場合の影響が大きいので、
remote: true がデフォルトになっていることなどの理解を深めつつ、
Rails 5 対応リリース後に少しずつ進めていきましょう。

jquery_ujs を rails-ujs に切り替えるのも注意が必要です。
例えば SweetAlert2 のような Rails の操作に関係する JavaScript ライブラリを使っている場合、実装方法を大きく変える必要があります

rails-ujs は jQuery 依存をなくすために新たに1から書き直されてる別のライブラリだという認識を持ち、
現在 jQuery への依存があるなら「別に変えなくてもいいか」という気持ちも大切です。

フロントエンドにおけるミスは、開発者が気付かずにユーザーが困って初めて気付く、ということが起こりがちですので。

4. Rails 5 対応をリリースしての感想

ステージングでの1週間程度の様子見のあと、本番リリースしました。

作業前にRailsガイドで触れてる変更点なんかを眺める限りだと、
「関係ありそうな箇所はそんなに多くないかな」と思ってしまうんですけれど、
実際に作業してみるとけっこう対応しないといけない点ありましたね……。

3-10. で書いたように、time 型の挙動の違いについてはリリース後に気付くという失敗をしでかしたのであせりましたが、
データベースに影響する問題はそれくらいでしたのでよかったです。
リリースしてから2日程度はそれらバグ対応をおこないましたが、その後は安定しています。

特にリリースしてサイト速度が上がったなどの実感はありませんが、
Rails 5 に上げられたことで Rails 6 へのバージョンアップの道もだいぶ見えてきました。
今年12月にリリースされる予定の Ruby 3.0 にも対応できるよう、着々と準備を進めていきたいですね!

*1:実際エラーになるようです https://tech.recruit-mp.co.jp/server-side/post-19932/

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