【Laravel】AWS Elastic Beanstalkのワーカー環境を使ってバックグラウンド処理用のサーバを構築しました

Qiita Laravel #2 Advent Calendar 2019 17日目の記事です。

こんにちは、M&Aクラウドの津崎です。

今日は、AWS Elastic Beanstalk Worker環境でLaravelを動かして、キュー処理、定期実行処理の専用環境を構築した話をします。

最初はQiitaに投稿する予定で記事を書いていたんですが、自社の環境に限った話が多かったので、自社ブログで公開しました。

記事の最後には、本番環境でやらかしちゃった話もありますので、Laravel でワーカー環境の構築を検討されている方はチェックしていただけたらと思います。

弊社が運用しているM&Aクラウドというウェブアプリケーションは、AWS Elastic Beanstalkのウェブサーバー環境で構成されています。

macloud.jp

AWS Elastic Beanstalkは、お手軽にWebアプリケーション構築できる、インフラに手が回らないスタートアップ企業にはとてもありがたいAWSのサービスです。 冗長化、オートスケーリングをAWSのWebコンソール上から簡単に構築・設定することができます。 (弊社のインフラを最強にしてみたいエンジニアの方、お待ちしてます🙇‍♂️)

サーバ構成

導入前の構成

f:id:zacky2:20191216211131p:plain

導入後の構成

f:id:zacky2:20191216211100p:plain

ワーカー環境を導入する以前は、キュー処理や定期実行のタスクなどのバックグラウンド処理もウェブサーバ環境で実行していました。

バックグラウンド処理のトリガーは、CloudWatch Eventsを使っています。 CloudWatch EventsからLambdaを実行し、HTTPによってLaravel製のウェブアプリケーションの特定URLにアクセスしています。

ウェブアプリケーションは、特定URLにアクセスされた時にバックグラウンド処理を開始します。 図では、バックグラウンド処理の代表的な例として、Amazon SQSからキューを受け取って処理を行い、Amazon SNSを呼び出してユーザにメールを送信するケースを表しています。

構成上の問題

ウェブサーバ環境でバックグラウンド処理する構成にはいくつかの問題がありました。

  • セキュリティについて
    • タスク実行のためのURLが外部に露出しており、URLが流出しアクセスされると不正に処理が実行されてしまうリスクがある
  • 性能ついて
    • キュー処理は1分に一回、100個のキューを取得して処理するように実装しているため、キューを数百個積まれると処理が詰まってしまう
      • 例えば、2000通送る場合、メール送信に20分かかってしまい、その間他のメールが送られないなど
      • ※これは実装上の問題なので上手く作ればもっと効率的になるがキチンと実装しようとするとチョットムズカシイ
  • 負荷について
    • 将来的に利用者が増えていくと、ウェブサーバにおけるバックグラウンド処理による負荷を無視できなくなる

これらの問題はワーカー環境を使わなくても様々な工夫で改善できますが、 ワーカー環境にはこれらの問題を一度に解決できるというメリットがあったので採用しました。

AWS Elastic Beanstalk ワーカー環境とは

Beanstalkには、2つの環境枠があります。 1つは、通常のウェブサーバに用いられるウェブサーバー環境です。

もう1つが、ワーカー環境です。 ワーカー環境は、ウェブサーバ環境と違い、SQSデーモン😈という、Amazon SQSからキューを読み取り、ローカルのポート80にHTTP POSTリクエストを送ってくれるデーモンプロセスが各インスタンス内に常駐しています。 自前のアプリケーションでSQSデーモンからPOSTされたデータを処理するように実装すれば、バックグラウンド処理用のサーバの完成になります。

f:id:zacky2:20191216211118p:plain (https://docs.aws.amazon.comから引用)

https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html

ワーカー環境の導入

ワーカー環境の導入には、SQSのキューを受け取るエンドポイントを作る必要があります。 このエンドポイントの作成には、dusterio/laravel-aws-workerというパッケージを利用しました。 インストールするだけで/worker/queueというエンドポイントが作成されます。 めちゃくちゃ便利です。

https://github.com/dusterio/laravel-aws-worker

あとは、ワーカー環境を作って各種設定をすればOK!

導入結果

f:id:zacky2:20191216211100p:plain

ワーカー環境の導入によって、当初抱えていた問題は解決されました。

  • セキュリティについて
    • ワーカー環境はデフォルトでインターネットからアクセスできない
  • 性能について
    • キューを並列に最大100個処理できるので、リソースの利用効率がよくなり処理が早くなった
    • 並列数を高めてもSNSで詰まってしまうので、徐々に並列数を増やしている
    • 今の所4並列で運用しており、ワーカー環境導入前よりメールの一括送信が4.8倍が早くなった
  • 負荷について

本番環境でやらかしちゃいました

ワーカー環境導入にあたって、本番環境でユーザーに迷惑をかけてしまうような不具合が発生してしまいました。 Laravelでワーカー環境を導入する方は、同じ過ちを犯さないように十分注意して欲しいです😢

発生した現象

ワーカー環境から送信されたメールにおいて、メール内のボタン動作しないとユーザから連絡がありました。

なんと、メールに表示されたボタンのリンク先がhttp://localhost/から始まるURLになってしまっていたのです。

原因

Laravelのデフォルトの設定では、自サイトのURL生成の時に、リクエスト時のドメイン名を利用するようになっています。 ウェブサーバ環境では、ユーザからmacloud.jpというドメインでアクセスされるので、自サイトのURLを生成するとmacloud.jpというドメインのURLが生成されます。 一方、ワーカー環境では、SQSデーモンがLaravelに対してlocalhostでアクセスを行います。 そのため、Laravelは自サイトのURLをhttp://localhost~として生成してしまったのです。

対策

app/Providers/AppServiceProvider.phpboot()メソッドに、 環境変数で指定したルートURLを使用するように追記しました。

    public function boot()
    {       {
        if (env('REGISTER_WORKER_ROUTES', true)) {
            URL::forceRootUrl(config('app.url'));
        }

   // 略

反省

大きな機能の開発の時には、エンジニアとプロダクトマネージャーが集まって、機能やデグレードの確認会を必ず行うようにしています。 しかし、今回のインフラの変更は、コードの変更が少なく、メールが送られれば大丈夫だろうとチーム全員が思っていたため、担当者レベルでの確認に留まっていました。

ベースURLが異なるという不具合は、事前にその可能性に気づくのが難しく、テストコードを書いておくことは困難ですので、どうしても人間の手によるチェックが必要になります。

今後は、インフラの変更においても確認会を実施し、周辺の機能も含めての確認を徹底していきます!

まとめ

  • ワーカー環境は、キュー処理、定期実行などのバックグラウンド処理を行わせるのに便利
  • dusterio/laravel-aws-workerで簡単に導入できる
  • ベースURLに気をつけて

最後に

我々は、一緒にインフラを改善をしてくれるエンジニアを随時募集中です。 www.wantedly.com

www.wantedly.com