/home/by-natures/dev*

データ界隈で働くエンジニアとしての技術的なメモと、たまに普通の日記。

2019/06/18 AWS ELB 配下の tomcat が X-Forwarded-For を取得できない? -> 解決しました

ロードバランサー配下のWebサーバは、ロードバランサーがもともとのクライアントのリクエスト情報を保持するために X-Forwarded- というHTTPヘッダを追加します。このリクエスト元の情報を保存するヘッダはただのデファクトスタンダードでしたが、今は RFC にもなっているようです。

X-Forwarded-For - Wikipedia

AWS 環境でもこれは同じで、公式サイトや色々なブログでも、ELB配下のWebサーバは X-Forwarded-For を利用してクライアントのIPアドレスを取得できると記載されています。例えば tomcat のアクセスログでは、以下のように変更することでリクエスト元のIPアドレスを取得することができます:

# デフォルトのアクセスログフォーマット
pattern="%h %l %u %t "%r" %s %b"

# X-Forwarded-For などの、リクエスト元の情報を追加したアクセスログフォーマット
pattern="%{X-Forwarded-For}i %{X-Forwarded-Proto}i %h %l %u %t "%r" %s %b"

ここで私がハマった、、というか今でもよくわからないのですが、サーブレット側で X-Forwarded-For を取得しようとすると null が返ってきてしまいました。アクセスログで X-Fowarded-For にクライアントのIPが出力されたところで安心していて、アクセスログで出力している HTTPヘッダがサーブレットで取得できないなんて考えも及びませんでした。。サーブレットでHTTPヘッダを全て出力させましたが、 X-Forwarded-Proto はあるのに X-Forwarded-For が見当たりません。

// これが null になる、アクセスログでは取得できているのに
String clientIP = request.getHeader("X-Forwarded-For");

検索すると同じ問題を質問している AWS スレッドがありました: Forums | AWS re:Post

かいつまむと、理由はわからないが Tomcat(というかサーブレット) だと request.getRemoteAddr() でリクエスト元のIPアドレスが取得できる・・・ということです。 getRemoteAddr() はネットワーク層のリクエスト元 IP アドレスを返却するようなので、本来であれば EC2 インスタンスの手前にある ELB のIPアドレスになるはずです。上のスレッドに回答は付いていないのですが、手元のAWS環境で試すと、確かに getRemoteAddr() でリクエスト元のIPアドレスが取得できました。

今後 X-Forwarded-For でリクエスト元の IPアドレスが取得でき、 getRemoteAddr() でELBのIPアドレスが返ってこないとも限らないので、 X-Forwarded-For をチェックして null であれば getRemoteAddr() で埋めておくのが無難でしょうか。どなたかこの辺りの事情詳しければぜひ教えてください。

追記

Twitter で先輩から、tomcat の機能でそうなっているのではないか、ということで以下のモジュールを紹介してもらいました:

RemoteIpValve (Apache Tomcat 8.5.90 API Documentation)

このモジュールはクライアントのIPアドレスなどを X-Forwarded- 系のプロトコルの値と置き換えるモジュールで、今回の挙動が説明できます。実際に、Elastic beanstalk 経由で入れた tomcat の server.xml には上記モジュールが設定されていて、内部プロキシのIPアドレスも定義されていました。

<Valve className="org.apache.catalina.valves.RemoteIpValve" protocolHeader="X-Forwarded-Proto" internalProxies="10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|169\.254\.\d+\.\d+|127\.\d+\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[0-1])\.\d+\.\d+" />

2019/06/12 Hive CLI のアーキテクチャ

hive CLI コマンドは非推奨で、今後は beeline 推奨(JDBC/Thrift で HiveServer2 へ接続)とのことですが、どうにも HiveServer2 は運用していて良い思い出がないので(私が利用している環境だと定期的に再起動しないと応答がなくなる。。)、最近あまり状況追っていないのでどうなっているのかなぁと思いつつ、そもそも hive CLI ってどういう風に動いているんだっけ?と思って調べました。

cwiki.apache.org

公式はざっくりした図です。

beeline は HS2 に接続するだけなのでシンプルですが、hive CLI は自分自身で metastore に接続したりクエリパースしたり、ジョブを投げたりしています。ですので hive CLI を動かす環境にも hive-site.xml などクラスタに接続する設定情報が必要になります。

ただ、簡単なクエリ(例えばパーティションに対する SELECT * )を動かすと、MapReduce に変換されずに hive CLI が直接 HDFS にデータを取得しに行っているようです。上の図だと Execution Engine が簡易的なものになるイメージでしょうか。簡単なクエリでも巨大なパーティション群に対してスキャンが掛かると kill したいことがたまにあるのですが、YARN アプリケーションに変換されていないのでやや面倒です。

2019/04/09 Java でのメモリチューニング

メモリチューニングを久々に。前回は OOM でアプリケーションが落ちる問題に対して調査したのですが、今回はマイナーGCが多発していました。

簡単な処理を大量にさばくアプリケーションなので基本的には Eden からすぐにメモリ解放されるオブジェクトばかりなのですが、処理時間は数十msほど、頻度が数分に一度だったので気になって調整を試みました。当たり前といえば当たり前なのですが、Eden 領域を増やすとマイナーGCにかかる時間が増え、頻度が減ります。Eden 領域を減らすと逆のことが起きました。Eden 領域だけが対象になる GC だとアルゴリズム間で大差ないかもしれません。(環境が Java7 だったので G1GC は試していないです)

余談ですが tomcat は <tomcat>/bin/setenv.sh に Java のオプションを追加していました。Java の起動オプションを変更したくて、起動スクリプトや conf/ のしたばかり探してしまって少し手間取りました。 bin/ の下のファイルを書き換えるのはいつも抵抗あります。

以下のブログではコンカレントGCを利用しない場合の各種パラメータの意味や設定例が紹介されています。コンカレントGCを利用すると、いくつかの値のデフォルト値が大きく変わってしまいますので注意。

d.hatena.ne.jp

GCViewer の紹介。簡単に使えました。

qiita.com

CMS

各種オプションの紹介。コンカレントGC を利用した時の各値の変化も記載されていて分かりやすいです。

fomsan.sakura.ne.jp

G1GC

G1GC を簡単に解説しつつ、他の GC のアルゴリズムとの比較も紹介されています。

qiita.com

2019/04/05 ITP 2.1 対応, ElasticBeanstalk での tomcat 設定値書き換え

4月からオフィスが新しくなって、また一緒に働いていた人が退職したこともあり、労働環境が大きく変わりました。やることは増えましたが、やれることも増えそうなので案外楽しめています。開発リソースが足らず、一緒に働く方を増やしたいので色々と整備しているところです。

ITP 2.1

iOS12.2以降で搭載されるSafari 12.1からITP 2.1が導入される ということで、この対応で手を煩わせている方も多いかもしれません。公式アナウンス:

webkit.org

日本語の解説サイト:

webtan.impress.co.jp

サーバ側の対策としては、secure 属性と HttpOnly 属性をつけましょう、ということです。ただし、HttpOnly をつけてしまうと、JavaScript の document.cookie で呼び出せなくなってしまうので注意です。また、secure 属性をつけると、HTTPS での通信時にしかクライアントからサーバへ該当クッキーは送信されません。

逆に捉えると、HTTP での通信では8日以上保存するクッキーはもう取り扱えない、ということなんですかね。HttpOnly なので document.cookie では読めず、サーバから読もうとすると secure なので送信されず、と。

www.marketechlabo.com

kenzo0107.hatenablog.com

Customize Elastic Beanstalk Using Configuration Files

aws.amazon.com

Beanstalk は便利なのですが、環境の値を変えようと思うと結構面倒です。今回は tomcat の web.xml の値を書き換えたかったのですが、 web.xml ファイルが大きくて ebextensions で扱えず、XML を部分的に置換するような形で対応しました。

扱おうと思っているアプリケーションはアクセスが多いので、ネットワークの値もチューニングしたいのですが、ebextensions がどんどん肥大化します。。

network パフォーマンスチューニング

TIME_WAIT でポート食い尽くしそうだったので少しチューニングしました。

https://www.ginnokagi.com/2012/03/tomcat-3.html

2019/04/01 CORS

他サーバからアクセスしてもらうエンドポイントを追加したのですが、その際に CORS が必要だったので設定した際の記録です。

説明

medium.com

dev.classmethod.jp

Tomcat での対策

Tomcat なら web.xml のフィルタ機能で CORS 対策ができます。7系の途中から追加されているため、7系の場合はバージョンに注意してください。

cors.allowed.origins* が指定できますが、その場合は Access-Control-Allow-Credentials を true にしていてもクッキーのやりとりができません。クッキーのやりとりを行うためには cors.allowed.origins にドメイン、プロトコル、ポートなどのアクセス情報を個別に記述する必要があります。

tomcat.apache.org

CSRF 対策

CSRF 対策に、CORS の preflight の機能を使う説明。preflight なんて何に使うんだろうと思っていましたが、認証のような役割を果たすようです。

numb86-tech.hatenablog.com

2019/03/28 Tomcat リリース備忘録

古いシステムのメンテナンスをする場面があり、Tomcat へ WAR ファイルをデプロイすることになりました。概念はなんとなく理解していたのですが Tomcat への実作業は初めてだったのでメモ。小さいシステムだったのでよかったですが、大きなシステムでドキュメント等が不足していると大変だったかもしれないです。

WAR ファイルのリリース方法

ozuma.hatenablog.jp

pom.xml の packaging が war になっていれば、普通に mvn package などで WAR ファイルが生成されます。これはただの ZIP ファイルなので展開すれば中身のチェックも可能です。

上のブログで詳しく紹介されていますが、Tomcat には unpackWARs と autoDeploy というオプションがあり、リリースする際には注意しなければいけません。特に tomcat を停止して WAR ファイルを差し替えて起動しなおしても、tomcat は WAR ファイルの差し替えが認識できないため、すでに展開されているアプリケーションディレクトリ(WAR ファイルを展開したもの)からスクリプトを読み込んで動作してしまいます。

自動化してしまえば気にしなくても良いことだと思いますが、環境移設の合間にリリースしなければならずに急遽手動でリリースしました。一度経験すると理解が深まります。