こんにちは。サーバーサイドエンジニアの @atolix_です。
今回は37signalsが公開しているThrusterを、メドピアで本番運用をしているアプリケーションのkakari
に導入してみました。
Thrusterとは
Thrusterはアセット配信やX-Sendfileのサポートといった機能を持ったGo言語製のHTTP/2プロキシです。
詳細な設定不要でRails標準のPumaサーバーと組み合わせて動作して、nginxを構成に加えた時と同様のリクエスト処理を行うことが可能です。
導入前の状況
導入以前のkakariは以下のような構成を取っていました。 アセット配信の為にALBの背後にnginxを置いています。
今回は管理コストを下げる為に、nginxを剥がしてThrusterからアセット配信を行えるように構成を変更していきます。
※Cloudfront等のCDNを使用する選択肢もありましたが、本サービスのクライアントへの影響を抑える為に、ドメイン及びインフラ構成の変更を最小限に留めることを優先して今回はThrusterを選定しています。
移行の前にnginxでやっていた処理と同等の事がThrusterでも可能かチェックします。
チェック項目 | Thrusterで可能か |
---|---|
Body Sizeの制限 | MAX_REQUEST_BODY というオプションで設定可能。 |
gzip圧縮 | js/css ファイルは自動的に圧縮されて配信される(content-encoding: gzip が付与される) |
配信するアセットのキャッシュと最大サイズ指定 | キャッシュ可能。最大サイズはMAX_CACHE_ITEM_SIZE で指定。 |
移行手順
安全に切り替えを行う為に今回はECS serviceごと差し替える方法を取りました。
1. Rails側にThrusterを導入する
一時的にnginxでリクエストを受けるECS serviceと、Thrusterでリクエストを受けるECS serviceを並行に稼動させたいので、Thruster用のポートを8080番で開けます。
加えてnginxの設定と同等のカスタム設定を記述していきます。Thrusterのオプションに用いる環境変数は接頭辞にTHRUSTER_
を付けても認識されるので、他の環境変数と区別しやすいように今回は付け加えています。
また、ThrusterはPumaをラップして起動するのでPumaの起動コマンドの頭にthrust
を付け加えます。
# Dockerfile ENV THRUSTER_HTTP_PORT 8080 # デフォルトでは80番ですが、nginxの設定とぶつからないように8080に変更 ENV THRUSTER_MAX_REQUEST_BODY 104857600 # リクエストの最大サイズ ENV THRUSTER_MAX_CACHE_ITEM_SIZE 3145728 # キャッシュの最大サイズ ENV THRUSTER_HTTP_READ_TIMEOUT 300 # READのタイムアウト秒数 ENV THRUSTER_HTTP_WRITE_TIMEOUT 300 # WRITEのタイムアウト秒数 ... CMD ["thrust", "bundle", "exec", "puma", "-C", "config/puma.rb"] EXPOSE 3000 8080 # 3000番にリクエストを投げるとThrusterを無視してpumaにリクエストが飛ぶ
2. Thrusterでリクエストを受けるECS serviceを追加して並行稼動させる
コンテナ定義では先ほど開けた8080番をRailsコンテナのポートマッピングに指定しておきます。
# container_definition.json { "name": "rails", "image": "${rails_image}", "portMappings": [ { "hostPort": 8080, "protocol": "tcp", "containerPort": 8080 } ], ...
ALBのTarget Groupでも8080番に対応したリソースを追加して、一旦はダミーのListener Ruleを追加します。
# app_alb.tf resource "aws_alb_target_group" "app" { for_each = toset(["blue", "green"]) name = "${local.prefix}-app-tg-${each.key}" port = 8080 protocol = "HTTP" vpc_id = aws_vpc.main.id target_type = "ip" proxy_protocol_v2 = false ... } resource "aws_alb_listener_rule" "app_listener_https_thruster" { listener_arn = aws_alb_listener.app_listener_https.arn ... condition { host_header { values = ["dummy.example.com"] } }
# ecs_service_app.tf resource "aws_ecs_service" "app" { name = "${local.prefix}-app" ... load_balancer { target_group_arn = aws_alb_target_group.app["blue"].id container_name = "rails" container_port = "8080" }
この段階ではThruster駆動のECS serviceにアクセスが飛ぶことはない状態です。
3. Thruster駆動のECS serviceにリクエストを流す
nginx駆動のserviceとThruster駆動のserviceそれぞれのTarget Groupのconditionを入れ替えて、リクエストの向きを切り替えます。
ここまででnginxをThrusterに差し替えることが出来ました。
実際のリクエストを見てしっかりキャッシュヒットしている事を確認しました。
移行後の影響
パフォーマンス面
Thruster切り替え後のパフォーマンスをDatadogで一週間分計測しました。
App Latency
パーセンタイル | nginx駆動時(avg) | Thruster駆動時(avg) |
---|---|---|
p50 | 15.3ms | 13.4ms |
p90 | 69.3ms | 79.5ms |
p95 | 125.7ms | 133.5ms |
p99 | 197.6ms | 203.2ms |
レイテンシは微妙に上がっています。 nginxよりパフォーマンスは劣ることは予想していましたが、そこまで差は大きくなさそうです。
App Task CPU
nginx駆動時(avg) | Thruster駆動時(avg) |
---|---|
7.23% | 8.35% |
タスク全体ではやや増加していました(新たにgzip圧縮の処理が入っているのでその分上がっているかもしれません)
Rails Container CPU
nginx駆動時(avg) | Thruster駆動時(avg) |
---|---|
4.42% | 8.01% |
ThrusterはRailsコンテナ内で実行されるのでこちらもCPU使用率が上がっていますね。
若干のパフォーマンス低下は見られましたが、現状は問題なくアプリケーションは動作しています。
まとめ
Thrusterに切り替えることでリクエストの処理をRailsコンテナ内で完結させる事が出来る為、nginxの管理コストを減らせる点はとても良いと感じました。
一方で勿論Railsコンテナのパフォーマンスにマイナスの影響が出る場合があるので、移行をする際は今までnginxでやっていた事と同等の処理が出来るか調査することが必須になりそうです。
是非読者になってください!
メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!
■募集ポジションはこちら medpeer.co.jp
■エンジニア紹介ページはこちら engineer.medpeer.co.jp
■メドピア公式YouTube www.youtube.com
■メドピア公式note
style.medpeer.co.jp