WebAPI The Good Partsを再読した

そういえば昔に読んだな、 とかおもってまた引っ張り出して読んでいた。

Web API: The Good Parts

Web API: The Good Parts

改めて学びがあったトピックは

  • バージョン番号をどこに入れるといいんだっけ?
  • ページネーションの仕様について(相対参照・絶対参照)
  • オーケストレーション層(実質BFFと解釈している)についての話が簡単に
  • パブリックなAPIとして運用していく色々なTips(レートリミットなど)

このあたり。

オーケストレーションは昔のRebuildでも話したそうだけど、(今度また聴いてみる) やはりNexflixすごいよなぁとなった。

あとはセキュリティの話があったんだけど、だいたいこれはAPIにかかわらない内容でなるほどーとなっていた。

全体的に、パブリックにエンジニアにAPIが公開を前提として話を進めているためか、 イケてないURL設計とかレスポンス内容ってダサいから、特別な理由がない限り利用者の印象良くないからやめようぜよく言ってた気がした。

公開APIとしての運用面のノウハウ、エラーレスポンスをこうするとわかりやすいみたい話はあんまりネットなくて良い感じにまとまっているので、 APIを始めて設計したりする時に参考になるところはいくつかあるのでちょっとおすすめしたい。

Docker HubのSource Repositoryを変更したい場合

Docker HubでGithubあたりの自動ビルドを有効にしている時に、Source RepositoryのRename時に参照するGithubリポジトリ変更したい場合がある。 この変更を忘れると、Github上でエイリアスを設定していてもDocker Hubには適用されず、自動でビルドされない状態になる。

ではどうすればいいか

直接設定を変更する解決方法はない。

取るとすれば下記の2つが有力。

1. 潔くDocker Hub上のリポジトリを削除して、再設定する。

削除されることが許容できればこれが一番手っ取り早い。 削除されることのデメリットは、Docker Hub上のStar数がリセットされるとかそのくらいだとはと思う。

2. Docker Cloudでビルド先を設定して、Docker Hubではなく、Docker Cloudでビルドする

この辺を参照してみてほしいんだけど、面倒だし、Docker Cloudという別のサービス使わないと行けないのがとにかく良くないので、 特にこだわりがなければ、この対応よりも、前者のほうが簡単でラクでいいんでは。と思っている。

No way to change source project for automated build · Issue #313 · docker/hub-feedback · GitHub

なんでこれ調べていたか

ngx_mrubyのDockerイメージのベースイメージを新しいものに変更して、 特に問題なくMergeしてもらったのだけど、この時に1年くらい前から自動ビルドがされてないですねー。ということがわかって少し調べていた。

1年くらい前にタイミングでngx_mrubyはAuthor名が変わって、Github上のリポジトリ名が変更されていたのが原因だったらしい。

今回は面倒なので、2.を提案して対応していただいた。(その節はありがとうございました!🙏)

あまり日の目を見ないPassengerにスポットを当てる

Phusion Passengerとは

非公式にはmod_railsとmod_rackとも呼ばれている。 役割としても、Apacheにインストールして、そのまま動かす当たりはやはり、mod_phpとかと被ってる。

イマドキだと、PumaやUnicornRubyのAppサーバーとしての主流だけど、こっちは「nginx + php-fpm」に近いイメージ。

今だからというのはあるけど、PassengerはDockerが無かったりした時代ではある程度メジャーな手法だったんだろうし、何より当時としては簡単なインストール方法だったのだ。

メリット

ApacheやNginxのモジュールとして稼働する

これのメリットとしては、特別にRailsアプリケーション用のコマンドを用意する必要がない、 すなわちsudo service apache2 (nginx) restartでだけで再起動できるし、色々できるというわけだ。

複数アプリケーションを立てたときのメモリの有効活用

これは具体的に言うと、Passengerでは一定期間リクエストがないインスタンスに関しては終了させ、 またリクエストがあったときにインスタンスを生成するという仕組みを取っているので、トラフィックが少ないサイトを沢山まとめて1つのサーバーで管理したい時に重宝する。 この終了までの期間に関してはPassengerの設定で変更することも可能である。

定期的な終了による小規模アプリケーションの安定稼働

Unicorn当たりでよくあるパターンは、ActiveRecordなどのCacheをどんどん溜めてしまってメモリが溜まってしまうというものだ。 Pumaは本番で余り触ったことがないので、よくわからないのだが、Unicornの場合は、unicorn-woker-killer によって、 一定回数のリクエストや、プロセス単体のメモリ容量によってプロセスの再起動を行う対策を取るのが一般的ではある。

Passengerではリクエストが無いときにはインスタンスを終了させるので、小規模なアプリケーションの場合は定期的に再起動を行っていることになり、 メモリ解放について気を使うことは、小規模なアプリケーションに限って言えば他のアプリケーションサーバーに比べると少ないのかもしれない。

デメリット

Apacheに同梱されているので、静的ファイルを返すときでもApache + Passengerが動く

結構痛い。 なるべく余計な処理をさせないようにするには、 静的ファイルを返すときにはアプリケーションサーバー(Passenger)は動いてほしくないものである。 静的ファイルが多めのサイトを運用する場合は避けたほうがいいかもしれない。

一応対策もあるが、それは別途Nginxを立ててReverseProxyする方法。 静的ファイルはNginxから、動的に処理しないといけないところをApacheでという方法。 でもそれだったら、Unicornとか、Puma使えば良くないですかということになる…

ビジネス向けでないとDeploy時・再起動時にダウンタイムが発生する

コレはPhusion Passengerの機能制限。有料のビジネス版を利用しないとリスタート時にダウンタイムが発生する。 すなわちgraceful startができない。 どのくらいダウンタイムが発生して、どのくらいの時間かは未検証だけど、止まるということは頭に入れておいてほしい。

どういうときに利用するのか

止むにやまれない事情で、少メモリ環境で、複数アプリケーションを立ち上げる案件

インフラの費用がなかったり、管理上1つのVMインスタンスで管理したいという時。あんまりないねー。

小規模なRedmineを自前で立てる必要がある場合

いや、もうDocker使えよ…という話なんだろうけど、複数のRedimineアプリケーションを1つのサーバーに立てる時に何かと重宝する。 あと、単純に資料が多いというの公式のインストールマニュアルがあるしね。

積極的に使う理由が余り見当たらない…

普通にアプリケーション作っているんだったら、やっぱり、PumaかUnicornが無難。 元々Passenger使っていたとか、複数のRailsアプリケーションを1つのサーバーに同居させて管理する場合のみ、Passengerを使うべきであるというのが自分の出した答えである。

Nginxでリクエスト毎に発番して、Railsのログに書き込むまで

いきなりまとめ

  • マイクロサービスとかでいくつものアプリケーションに対してリクエストを行うような構造のアプリケーションはリクエストIDを設定しておき、異なるマイクロサービスでも同一のIDがログ上で記録されるので、ログのチェックなどがはかどって便利。
  • Nginx 1.10以降で利用できる $request_id を使うと簡単に リクエストごとに発番できる。
  • Railsは HEADER X-Request-Id を取得してログにタグを簡単に設定できる。

なぜ必要なのかをもっと詳しく

Railsアプリケーションの手前でNginxでTLSを終端していたり、 複数のアプリケーションをまたいだりしているマイクロサービスでアプリケーションを実装していると、もし大げさなサービスを構築していなくとも、リクエストが多いとログを確認するのが大変なので、 なにかとNginxでリクエストごとに生成したIDを振ったものをProxyしているアプリケーションに渡してやることでログの確認を補助することができる。

やりたいこと・サンプルアプリケーションの構成

  1. 今回は前段にTLS終端を想定した、Nginxでロードバランサを立てる。
  2. 1.のNginxアプリケーション(コンテナ)でNginxでUUIDを割り当て、Proxyする。
  3. 2のコンテナから、ProxyからのアクセスにX-Request-Id が設定されている場合は、それをそのまま X-Request-Id に設定する。設定されていない場合は、3のコンテナでリクエストIDを発番する。
  4. 更にRailsアプリケーション用にProxyして、ProxyHeaderに書き込まれた X-Request-IdRailsのログにタグとして設定して書き込む

図解

サンプルリポジトリ

github.com

これをGit cloneしてきて、docker-compose up ってすればシュシュッとサンプルのアプリケーションが動くので、よかったら動かしてみて欲しい。

TLS終端用のNginx側の設定

リクエストごとにIDを発番する機構はNginx1.10以上であれば、$request_id を利用して、簡単に設定することができる。

http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_id

  server {
    listen       80;
    server_name  _;
    client_max_body_size 4G;
    keepalive_timeout 5;

    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP $remote_addr;
    proxy_set_header    X-Forwarded-Host $host;
    proxy_set_header    X-Forwarded-Server $host;
    proxy_set_header    X-Forwarded-For http;

    location / {
      # X-Request-IdをProxy Headerに設定
      proxy_set_header X-Request-Id $request_id;
      proxy_pass http://nginx_rails_proxy;
    }
  }

上記は設定の抜粋なので、下記にすべて記載したものを掲載しておく。*1

nginx_proxy_uuid_rails/nginx.conf at master · webuilder240/nginx_proxy_uuid_rails · GitHub

Rails Proxy用のNginxコンテナの設定

基本的にはTLS終端用のNginxの設定と同じ。RailsアプリケーションへのProxy設定部分以外で異なるのは、 先程のTLS終端用のコンテナから受け取った X-Request-Id をProxyするアプリケーションに対して ProxyHeaderに設定してあげればOKということ。 もし X-Request-Idがない場合は、このNginxでリクエストIDを生成して設定すればOK。

  server {
    listen       80;
    server_name  _;
    client_max_body_size 4G;
    keepalive_timeout 5;

   # この生成したRequestIdをProxy用のRequest IDに設定。
    set $proxy_request_id $request_id;
    if ($http_x_request_id) {
      # Proxy Headerに `X-Request-Id`が設定されている場合、 Proxy用のリクエストIDに設定する
      set $proxy_request_id $http_x_request_id;
    }

    root /usr/src/app/public;

    try_files $uri/index.html $uri.html $uri @app;

    location @app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      # Header, Proxy Headerに `X-Request-Id` を設定する。
      add_header X-Request-Id $proxy_request_id;
      proxy_set_header X-Request-Id $proxy_request_id;
      proxy_redirect off;
      proxy_pass http://app_server;
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /usr/src/app/public;
    }
  }

nginx_proxy_uuid_rails/nginx.conf at master · webuilder240/nginx_proxy_uuid_rails · GitHub

Railsの設定

Rails側の設定は至って簡単。 application.rbに下記設定を追記すればOK。 なお、X-Request-Idが設定されていない場合は、Rails側で生成したものが反映される。

require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module NginxProxyUuid
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2
    config.log_tags = [:request_id]
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

ログのフォーマットはこんな感じ。

[43a62d59ee9f05db194539c71e113601] Started GET "/" for 172.20.0.2 at 2018-07-28 05:00:26 +0000
[43a62d59ee9f05db194539c71e113601] Cannot render console from 172.20.0.4! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
[43a62d59ee9f05db194539c71e113601] Processing by Rails::WelcomeController#index as HTML

*1:ここではNginxのアクセスログの設定を省略しているが、ちゃんとRequestIdを記録しておこう https://github.com/webuilder240/nginx_proxy_uuid_rails/blob/master/containers/nginx_tls/nginx.conf#L15

Rails + SQLServer 航海日誌:実際に運用している編

実際にSQLServerRailsを利用して、アプリケーションを運用した知見をまとめておく。 この記事の続き。

webuilder240.hatenablog.com

いきなりまとめ

  • DB周りで何か問題あったとすればMySQLとの違いはユニーク制約の違いくらい.
  • 逆に言えば他でハマるようなことはなかった。
  • 1年近くこの構成で5〜6個近いアプリケーションを運用しているが、特にDB起因での障害とかで大きなものはいまのところはない*1
  • AzureでMySQL / PostgresqlのRDSっぽいサービスがリリースされたので、AzureからSQLServer一択ということではなくなり旨味がかなり減った。
  • 前回話していたSQLServer for LinuxをDockerで利用すれば開発環境のDBも困らない状態に。
  • 動作スピードについては未検証・未計測だが大きな差異はないと思ってる。
  • MySQLSQLServerを両方をメンテするのは辛いので、事情がなければサポートするRDBMSは1つだけにしたほうがいい。
  • 最近は特に事情がなければ、Azure Database for MySQLを基本的には使ってる。

Azure Database for MySQL / PostgreSQLについて

まずはこの一番大きなトピックからです。 みなさんご存知かと思いますが、かねてから噂されていたサービスがPreviewですがリリースを発表しました。

azure.microsoft.com

こちらもDB無停止でのスケールアップが可能なので、AzureでSQLServerを利用する理由が1つ減ってしまったように感じています。 まぁSQLServerに比べると割高なのは否めないですが… 現状MySQL / PostgreSQLで動かしているようなサービスの場合は、こちらに移行させるのが一番いいと思います。*2

PHPアプリケーションサーバーをWebAppsで幾分ラクラクに運用できることを考えても、 大型WordPressを運用する環境でAzureも検討材料の1つとして候補になるのではないかなーと思っています。

ただし、AWSでいうRDS相当なので性能がAuroraくらいのものかというとそういうわけではないです。

開発DBについて

DockerでSQLServerを運用できるということは前回軽く話していたけど、 実際に試してみて問題なく使えそうなので、現在開発環境のDBとして利用しています。 あとはCIをSQLServerでも回したかったので、そちらでもMSSQL Dockerを使っています。

CircleCIでSQLServerMySQLでテストを回している話

まだCircleCI1.0を利用している勢なのですが、現在開発中のアプリケーションでは両方のRDBMSでテストを回しています。 この辺については少し旬が過ぎてしまった感もあるのですが、また今後ブログにしたいと思います。

そもそも複数のRDBMSをサポートするのしんどい話

弊社ではまだまだプロダクトの中でそこまで分析系のクエリを書いていないので、まだまだマシだと思っているけど、 union allはどうしてもActiveRecordでは使えない(複雑なArelのコードを書くことで実現できるだろうが、どっちにしろメンテコストは高くつく)ので、 基本的には生でSQLを書かなければ行けないと思ってて、そういう箇所が数箇所だけある。

そこに関してはどうしてもSQLServerMySQLでは使用できるSQL文に多少違い・挙動の違いが存在するので、どうしてもコードをMySQLSQLServerで分けて書くということをしなくてはならない。 とは言え、自分は生SQLが得意なエンジニアではないのを自覚しているし、いまのところは2箇所で済んでいるけど、コレが増えるとまずいなぁとも感じている。 そもそもダッシュボードの項目部分だったり、分析クエリに関してはRDBMSではなくTresureDataなどの分析基盤などを別途利用した方が良さそうな感じはしている。

さいごに

  • 複数のRDBMSをサポートするのしんどいし、サービスとしてそこまでメリットがないと思っているので、SQLServerのサポートはすっぱり捨てて全部MySQLにまとめたいですね。

*1:Solidus使っててなにかバグ引いた人はいたっぽいけど...

*2:かなり翻弄されてしまっている感が…