ようへいの日々精進XP

よかろうもん

ゴールデンウィークスペシャル:AWS X-Ray を出来るだけローカル環境でチュートリアルする(Node.js + Express + DynamoDB Local を使って)

AWS X-Ray について

AWS X-Ray とは何ですか?

www.slideshare.net

上記の資料が簡潔に纏まっていてとても参考になった。

そして、FAQ の記述を引用すると…

開発者は、AWS X-Ray を使用して、本番環境や分散アプリケーション (マイクロサービスアーキテクチャを使用して構築されたアプリケーションなど) を分析およびデバッグできます。X-Ray を使用すると、アプリケーションやその基盤となるサービスの実行状況を把握し、パフォーマンスの問題やエラーの根本原因を特定して、トラブルシューティングを行えます。X-Ray では、アプリケーション内で転送されるリクエストがエンドツーエンドで表示され、アプリケーションの基盤となるコンポーネントのマップも表示されます。X-Ray を使用すると、開発中か本番環境かにかかわらず、シンプルな 3 層アプリケーションから数千のサービスで構成される複雑なマイクロサービスアプリケーションまで、さまざまなアプリケーションを分析できます。

ざっくり言うと…アプリケーションのモニタリングサービスで、競合は NewRelic や Datadog APM になると思う。

AWS X-Ray が出来ること

こちら より転載。

  • パフォーマンスボトルネックとエラーの特定
  • アプリケーション内の特定サービスへの問題を特定
  • アプリケーションのユーザに対する問題のインパクトを特定
  • アプリケーションのサービスコールグラフの可視化

概念

docs.aws.amazon.com

こちら より転載。

キーワード 内容
Trace サービスをまたがった単⼀リクエストに関連したエンドツーエンドのデータ
Segments 1 つのサービスに対応するトレースの⼀部
Sub-segments サービス内でのリモートコールもしくはローカルコンピュートのセクション
Annotations トレースをフィルタするのに使⽤できるビジネスデータ
Metadata トレースに対して付与できるビジネスデータ ただし、トレースのフィルタリングには使⽤されない
Errors 正規化されたエラーメッセージとスタックトレース
Sampling トレースとして取得するアプリケーションへのリエストの割合

AWS X-Ray の画面

Service Map

Service Map とはトレースデータを視覚化したもの。

f:id:inokara:20170507193403p:plain

各ノード(上図だと緑色◯)をクリックするとトレースデータを確認する事が出来る。

Traces

トレース画面ではアプリケーションへのリクエストのレイテンシやステータスコードを確認する事が出来る。

f:id:inokara:20170507193410p:plain

アプリケーションへのトレースデータには ID が付与されていて、トレース ID をクリックするとトレースの詳細を確認する事が出来る。

Trace の詳細

Trace の詳細画面ではアプリケーションのリクエスト開始から終了までの個々の処理が 1 つのセグメントとして詳細を確認する事が出来る。

f:id:inokara:20170507193420p:plain

例えば、上図だと MyAppDynamoDB はそれぞれ別のセグメントとして認識され、アクセスしたリソースや例外が発生した場合等は例外の情報まで確認する事が出来る。

AWS X-Ray を利用する方法

X-Ray SDK

  • Java、.NET、Node.js で利用可能
  • 主要なアプリケーション呼び出し対するメタデータを自動的にキャプチャするフィルタを標準で用意

X-Ray デーモン

  • SDK から UDP でデータを受信し、ローカルバッファとして振る舞う
  • データは毎秒でバックエンドに送信し、ローカルバッファが一杯になるとフラッシュされる
  • Amazon LinuxRHELUbuntuOS X 及び Windows で利用可能

X-Ray API

X-Ray SDK がサポートしていない言語からも X-Ray API を利用してトレースデータを送信することは出来、以下のような API が提供されている。こちらより抜粋。

API 用途
PutTraceSegments AWS X-Rayへセグメントのドキュメントをアップロード
BatchGetTraces IDにより指定されたトレースのリストを検索
GetServiceGraph アプリケーションとコネクション内でサービスを⽰すドキュメントを検索
GetTraceSummaries オプションのフィルタを使⽤して、指定された時間 枠で使⽤可能なトレースのIDとメタデータを取得

AWS CLI

ローカル環境でチュートリアル

環境

以下のような環境でチュートリアルする。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"

$ xray --version
AWS X-Ray daemon version: 1.0.1

$ node --version
v7.10.0

$ java -version
openjdk version "1.8.0_121"
OpenJDK Runtime Environment (build 1.8.0_121-8u121-b13-0ubuntu1.16.04.2-b13)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

$ ps aux | grep DynamoDB
ubuntu    6193  0.4 12.2 2658392 124676 pts/1  Sl   05:31   1:03 java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb

xray デーモンが動けば EC2 や ECS 上のコンテナである必要が無いのは嬉しい限り。

そして、Node.js アプリケーションは express を利用して、DynamoDB Local テーブルにデータを登録したり、登録データを取得する雑なアプリケーションを利用する。(見よう見真似で作ってみた)

X-Ray デーモンの用意

X-Ray デーモンのインストール

X-Ray デーモンのパッケージを取得して dpkg コマンドでインストールする。

$ wget https://s3.amazonaws.com/aws-xray-assets.us-east-1/xray-daemon/aws-xray-daemon-1.x.deb
$ sudo dpkg -i aws-xray-daemon-1.x.deb

インストール後、--help オプションでヘルプ一覧を確認。

$ xray --help
Usage: X-Ray [options]
        -m      --memory-limit  Change the amount of memory (in MB) that the daemon can use.
        -o      --local-mode    Don\'t check for EC2 instance metadata.
        -b      --bind          Overrides default UDP address (127.0.0.1:2000).
        -r      --role-arn      Assume the specified IAM role to upload segments to a different account.
        -c      --config        Load a configuration file from the specified path.
        -f      --log-file      Output logs to the specified file path.
        -l      --log-level     Log level, from most verbose to least: dev, debug, info, warn, error, prod (default).
        -v      --version       Show AWS X-Ray daemon version.
        -h      --help          Show this screen

X-Ray デーモン設定ファイルの作成

X-Ray 用の設定ファイルを以下のように作成する。(ファイル名は任意の名前で構わない)

Processor:
  Region: "ap-northeast-1"
Logging:
  LogLevel: "warn"
  LogPath: "/tmp/xray-daemon.log"
LocalMode: true

ローカル環境で動かす場合、LocalModetrue にしておく必要がある。尚、false の場合、EC2 メタデータから情報を取得しようとする。

アクセスキー、シークレットアクセスキーを用意

マネージドポリシーの AWSXrayWriteOnlyAccess が付与された IAM User のアクセスキー、シークレットアクセスキーを用意する。

$ cat ~/.aws/credentials
[default]
aws_access_key_id = AKxxxxxxxxxxxxxxxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
region = ap-northeast-1

X-Ray デーモンの起動

以下のように設定ファイルを指定して X-Ray デーモンを起動する。

$ xray --config /path/to/xray-daemon.yaml &
$ ps aux | grep xray
ubuntu    6216  0.0  1.8 230788 18848 pts/1    Sl   05:31   0:05 xray --config /vagrant/xray/xray-daemon.yaml

アプリケーションの用意

Node.js と Express の用意

こちら の記事を参考にさせて頂いて、Node.js と Express を用意した。

$ tree -L 1 sampleapp
sampleapp
├── app.js
├── bin
├── node_modules
├── package.json
├── public
├── routes
└── views

5 directories, 2 files

AWS SDK for JavaScriptX-Ray SDK の導入

以下のように AWS SDK for JavaScriptX-Ray SDK の導入。

$ cd sampleapp
$ npm install aws-sdk --save --no-bin-links
$ npm install aws-xray-sdk --save --no-bin-links

今回、Vagrant の共有ディレクトリにアプリケーションプロジェクトのディレクトリを作成した為、--no-bin-links オプションを付けてインストールした。

$ cat sampleapp/package.json
{
  "name": "sampleapp",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "aws-sdk": "^2.49.0",
    "aws-xray-sdk": "^1.1.0",
    "body-parser": "~1.17.1",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.3",
    "express": "~4.15.2",
    "jade": "~1.11.0",
    "morgan": "~1.8.1",
    "serve-favicon": "~2.4.2"
  }
}

app.js

以下のような修正を加えた。

$ diff -u app.js.original app.js
--- app.js.original     2017-05-07 10:01:23.000000000 +0000
+++ app.js      2017-05-07 06:15:55.000000000 +0000
@@ -7,9 +7,15 @@

 var index = require('./routes/index');
 var users = require('./routes/users');
+var ddb_set = require('./routes/set');
+var ddb_get = require('./routes/get');

 var app = express();

+// for aws-xray
+var AWSXRay = require('aws-xray-sdk');
+app.use(AWSXRay.express.openSegment('MyApp'));
+
 // view engine setup
 app.set('views', path.join(__dirname, 'views'));
 app.set('view engine', 'jade');
@@ -24,6 +30,11 @@

 app.use('/', index);
 app.use('/users', users);
+app.use('/set', ddb_set);
+app.use('/get', ddb_get);
+
+// for aws-xray
+app.use(AWSXRay.express.closeSegment());

 // catch 404 and forward to error handler
 app.use(function(req, res, next) {

以下を設定することで、Express アプリケーションが提供する受信 HTTP リクエストをトレースする事が出来るようになる。

var AWSXRay = require('aws-xray-sdk');
app.use(AWSXRay.express.openSegment('MyApp'));
...
app.use('/', index);
app.use('/users', users);
app.use('/set', ddb_set);
app.use('/get', ddb_get);
...
app.use(AWSXRay.express.closeSegment());

MyApp がアプリケーション名として X-Ray ダッシュボードの Service Map でも確認する事が出来る。

尚、/set が DynamoDB に値をセットし、/get で DynamoDB のテーブルを Scan して結果を返すようにしてみた。

/set

本当に見よう見真似の書き方でだいぶん怪しいが、/set にアクセスすると以下の処理が実行される。

var express = require('express');
var router = express.Router();

var AWSXRay = require('aws-xray-sdk');
var AWS = AWSXRay.captureAWS(require('aws-sdk'));
var dynamodb = AWSXRay.captureAWSClient(new AWS.DynamoDB({ endpoint: new AWS.Endpoint('http://localhost:8000'), region: 'ap-northeast-1' }));

router.get('/:key', function(req, res) {
  var params = {
      TableName: 'SampleTable',
      Item: {
          'Artist': {"S": req.params.key},
          'SongTitle': {"S": req.params.key}
      }
  };
  dynamodb.putItem(params, function (err, data) {
      if (err) {
          console.log(err, err.stack);
      } else {
          console.log(req.params.key);
          res.render('ddb_set', { message: "OK" });
      }
  });
});

module.exports = router;

以下のように記述して AWS SDK を利用した DynamoDB への呼び出しを AWSXRay.captureAWSClient でラップすることで、DynamoDB を呼び出す処理についてもトレースする。

var AWSXRay = require('aws-xray-sdk');
var AWS = AWSXRay.captureAWS(require('aws-sdk'));
var dynamodb = AWSXRay.captureAWSClient(new AWS.DynamoDB({ endpoint: new AWS.Endpoint('http://localhost:8000'), region: 'ap-northeast-1' }));

DynamoDB Local を利用しているので endpointhttp://localhost:8000 を指定している。

/get

/set 同様にだいぶん怪しいが、/get にアクセスすると以下の処理が実行される。

var express = require('express');
var router = express.Router();

var AWSXRay = require('aws-xray-sdk');
var AWS = AWSXRay.captureAWS(require('aws-sdk'));
var dynamodb = AWSXRay.captureAWSClient(new AWS.DynamoDB({ endpoint: new AWS.Endpoint('http://localhost:8000'), region: 'ap-northeast-1' }));

router.get('/', function(req, res) {
  dynamodb.scan({TableName: "SampleTable",
      Select: "ALL_ATTRIBUTES",
  }, function (err, datas) {
    console.log(JSON.stringify(datas));
    res.render('ddb_get', { datas: datas });
  });
});

module.exports = router;

/set と同様の設定で DynamoDB への呼び出しをトレースするようにする。

アプリケーションを実行して計測してみる

アプリケーションの起動

以下のようにアプリケーションを起動する。

node bin/www &

アプリケーションへの断続的なアクセス

以下のように実行してアプリケーションへ断続的にアクセスさせる。

while true; do curl -s localhost:3000/set/$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1 | sort | uniq); sleep 15; curl -s localhost:3000/get; sleep 15; done

暫く動かしつつ X-Ray のダッシュボードを確認すると…

Service Map

以下のように出力されている。

f:id:inokara:20170507193332p:plain

なかなかカッコイイ。

Traces

f:id:inokara:20170507193340p:plain

Trace の詳細

f:id:inokara:20170507194737p:plain

セグメントをクリックすると…

f:id:inokara:20170507194756p:plain

ということで…

フィルタについても

書きたいと思っている。

X-Ray デーモンを使えば

AWS 以外で動いているアプリケーションもトレースする事が出来るのは嬉しい。

X-Ray SDK 待ち

X-Ray SDK に対応している言語が少ないのが残念(Datadog APMPythonRuby と Go をサポートしている)だけど、Datadog APM や NewRelic のようなアプリケーションのパフォーマンスの計測が AWS だけで行えるというのも嬉しい。(※ AWS SDKAWS CLI でトレースデータを送信や取得は出来る)