投稿内容は私個人の意見であり、所属企業・部門見解を代表するものではありません。
目的
AWS Fargate とEC2 起動タイプでAWS SDK呼び出しに差があるのか計測したかったため、X-Ray SDK for Node.jsでレイテンシを比較したときのメモです。
流れ
- 事前準備
- 計測用のサンプルプロジェクトの準備
- ローカル環境でトレース実行
- Dockerコンテナ化してローカル環境でトレース実行
- EC2 起動タイプでトレース実行
- AWS Fargate でトレース実行
- 比較
事前準備
X-Rayのサンプリングレートを100%にする
商用環境だとすべてのSDK呼び出しを取得することはパフォーマンスが劣化する恐れがあるため推奨されないが、今回はテストなのでトレースを100%取得するように設定する。デフォルトは5%となる。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-gettingstarted.html
ECRでレポジトリを作成
Node.jsのサンプルアプリケーションとX-Rayデーモン用の2つのレポジトリを作成する。ここでは「my-nodejs」と「xray」という名称でレポジトリを作成した。
ECSクラスタ作成
AWS FargateとEC2タイプのクラスタをそれぞれ作成する。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create_cluster.html
EC2タイプは、セキュリティグループのインバウンドルールでサンプルアプリケーションが使用する3000ポートへのアクセスを許可する。
EC2タイプは、クラスタのEC2にパブリックIPが付与される。
ネットワークモードがawsvpcの場合は、EC2タイプ/Fargateを問わずサービスの定義でパブリックIPの付与を有効にすることができる。
計測用のサンプルプロジェクトの準備
Express、express-generatorをインストール
npm install -g express
npm install -g express-generator
X-rayは、Express フレームワークおよび Restify フレームワークをサポートしている。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-nodejs-middleware.html
X-Ray SDK for Node.js は Express フレームワークおよび Restify フレームワークを使用するアプリケーションのミドルウェアを提供します
Expressプロジェクトを作成
express sampleapp
依存モジュールのインストール
cd sampleapp && npm install
npm install aws-sdk
npm install aws-xray-sdk
app.js修正
今回はSystemManagerのパラメータストアの値を取得するため、aws-sdkとaws-xray-sdkをインポートし、パラメータストアストア取得用のGetメソッドを宣言します。
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
//追加
var ssm_get = require('./routes/get');
var app = express();
//追加
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');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
//追加
app.use('/get', ssm_get);
app.use(AWSXRay.express.closeSegment());
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
./routes/get.jsを作成
local実行するため、アクセスキー、シークレットキーで認証していますが、EC2やECSで動作させる場合は、IAMロールからクレデンシャルを取得するほうがセキュリティレベルが高くなる。
var express = require('express');
var router = express.Router();
var AWSXRay = require('aws-xray-sdk');
var AWS = AWSXRay.captureAWS(require('aws-sdk'));
AWS.config.loadFromPath('./credentials.json');
AWS.config.update({region: 'ap-northeast-1'});
var ssm = AWSXRay.captureAWSClient(new AWS.SSM());
router.get('/', function(req, res) {
var params = {
Name: '<パラメータストアのキー>', /* required */
WithDecryption: true || false
};
ssm.getParameter(params, function(err, data) {
if (err) {
console.log(err, err.stack);
}else{
console.log(data);
res.render('ssm_get', { message: "OK" });
}
});
});
module.exports = router;
クレデンシャルを定義するファイル作成する(credentials.json)
{"accessKeyId": "<アクセスキー>", "secretAccessKey": "<シークレットアクセスキー>"}
Jade作成(./views/ssm_get.jade) ※エラーが出力されるので適当に作成。。
extends layout
block content
h1= title
p #{title}
ここまででテスト対象となるアプリケーションが完成。
ローカル環境でトレース実行
ここではMacOSでの手順を記載します
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-daemon-local.html
X-Rayデーモンのダウンロードして解凍する。
cp ../../Downloads/aws-xray-daemon-macos-2.x.zip .
unzip aws-xray-daemon-macos-2.x.zip
Archive: aws-xray-daemon-macos-2.x.zip
inflating: xray_mac
inflating: cfg.yaml
X-Rayデーモンの起動
./xray_mac -o -n ap-northeast-1 &
[1] 74741
8c8590130da5:sampleapp atsum$ 2018-11-12T16:06:02+09:00 [Info] Initializing AWS X-Ray daemon 2.1.3
2018-11-12T16:06:02+09:00 [Info] Using buffer memory limit of 81 MB
2018-11-12T16:06:02+09:00 [Info] 1296 segment buffers allocated
2018-11-12T16:06:02+09:00 [Info] Using region: ap-northeast-1
-o: ローカル実行
-n: リージョン指定
サンプルアプリケーションの実行
node bin/www
getしてみる
curl localhost:3000/get
X-Rayのコンソールでトレースを確認する
Dockerコンテナ化してローカル環境でトレース実行
./routes/get.jsの編集
//コメントアウト
//AWS.config.loadFromPath('./credentials.json');
//追加
AWS.config.loadFromPath('/src/credentials.json');
Dockerfileを作成
先程作成したサンプルアプリケーションをDockerコンテナ上にコピーするようにDockerファイルを作成します。
FROM node
RUN cd /src; npm install express;npm install aws-sdk;npm install aws-xray-sdk
COPY . /src
EXPOSE 3000
CMD ["node", "/src/bin/www", "&"]
ビルド実行
docker build -t my-nodejs .
コンテナ起動
docker run -it my-nodejs:latest /bin/bash
getしてみる
node /src/bin/www &
curl localhost:49160/get
EC2 起動タイプでトレース実行
X-RayデーモンをECSで実行するためにDockerfileを作成する
FROM amazonlinux
RUN yum install -y unzip
RUN curl -o daemon.zip https://s3.dualstack.us-east-2.amazonaws.com/aws-xray-assets.us-east-2/xray-daemon/aws-xray-daemon-linux-2.x.zip
RUN unzip daemon.zip && cp xray /usr/bin/xray
ENTRYPOINT ["/usr/bin/xray", "-b", "0.0.0.0:2000"]
EXPOSE 2000/udp
X-RayデーモンのコンテナイメージをBuildする
docker build -t xray .
BuildしたDockerimageをECRにPush(サンプルアプリケーション)
$(aws ecr get-login --no-include-email --region ap-northeast-1)
docker tag my-nodejs:latest <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/my-nodejs:latest
docker push <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/my-nodejs:latest
BuildしたDockerimageをECRにPush(X-Rayデーモン)
$(aws ecr get-login --no-include-email --region ap-northeast-1)
docker tag xray:latest <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/xray:latest
docker push <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/xray:latest
タスク定義作成
ネットワークモード:host #簡単に検証するためにhostモードを使用する
タスク実行ロール:SystemMangerのパラメータストアの値が取得できる権限が必要
コンテナの定義:サンプルアプリケーションとX-Rayデーモン用のコンテナを2つ登録する
タスク定義の例
{
"executionRoleArn": "arn:aws:iam::<AWSアカウント>:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"dnsSearchDomains": null,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/p-test-node-ec2",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"entryPoint": null,
"portMappings": [],
"command": null,
"linuxParameters": null,
"cpu": 0,
"environment": [],
"ulimits": null,
"dnsServers": null,
"mountPoints": [],
"workingDirectory": null,
"dockerSecurityOptions": null,
"memory": null,
"memoryReservation": null,
"volumesFrom": [],
"image": "<AWSアカウント>.dkr.ecr.ap-northeast-1.amazonaws.com/my-nodejs",
"disableNetworking": null,
"interactive": null,
"healthCheck": null,
"essential": true,
"links": null,
"hostname": null,
"extraHosts": null,
"pseudoTerminal": null,
"user": null,
"readonlyRootFilesystem": null,
"dockerLabels": null,
"systemControls": null,
"privileged": null,
"name": "my-amazonlinux"
},
{
"dnsSearchDomains": null,
"logConfiguration": null,
"entryPoint": null,
"portMappings": [],
"command": null,
"linuxParameters": null,
"cpu": 0,
"environment": [],
"ulimits": null,
"dnsServers": null,
"mountPoints": [],
"workingDirectory": null,
"dockerSecurityOptions": null,
"memory": null,
"memoryReservation": null,
"volumesFrom": [],
"image": "<AWSアカウント>.dkr.ecr.ap-northeast-1.amazonaws.com/xray",
"disableNetworking": null,
"interactive": null,
"healthCheck": null,
"essential": true,
"links": null,
"hostname": null,
"extraHosts": null,
"pseudoTerminal": null,
"user": null,
"readonlyRootFilesystem": null,
"dockerLabels": null,
"systemControls": null,
"privileged": null,
"name": "xray-deamon"
}
],
"placementConstraints": [],
"memory": "2048",
"taskRoleArn": "arn:aws:iam::<AWSアカウント>:role/ecsTaskExecutionRole",
"compatibilities": [
"EC2"
],
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:<AWSアカウント>:task-definition/p-test-node-ec2:8",
"family": "p-test-node-ec2",
"requiresAttributes": [
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.execution-role-ecr-pull"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.ecr-auth"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.task-iam-role"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.execution-role-awslogs"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.task-iam-role-network-host"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
}
],
"requiresCompatibilities": [
"EC2"
],
"networkMode": "host",
"cpu": "1024",
"revision": 8,
"status": "ACTIVE",
"volumes": []
}
ECSサービス作成
EC2タイプのECSクラスタでサービスを定義する。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-service.html
先程作成したタスク定義を指定する。
ELBオプション、サービスの検出なし。
Service Auto Scaling無効化
タスク(コンテナ)がRUNNINGになるまで待つ
getしてみる
curl <EC2のパブリックIPアドレス>:3000/get
EC2のパブリックIPアドレスは、クラスタのEC2インスタンスタブにある対象のEC2インスタンスIDをクリックしてEC2のコンソールで確認する。
X-Rayのコンソールでトレースを確認する
AWS Fargate でトレース実行
タスク定義作成
ネットワークモード:awsvpc
タスク実行ロール:SystemMangerのパラメータストアの値が取得できる権限が必要
コンテナの定義:サンプルアプリケーションとX-Rayデーモン用のコンテナを2つ登録する
テスク定義の例
{
"executionRoleArn": "arn:aws:iam::<AWSアカウント>:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"dnsSearchDomains": null,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/p-test-node-fargate",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"entryPoint": null,
"portMappings": [],
"command": null,
"linuxParameters": null,
"cpu": 0,
"environment": [],
"ulimits": null,
"dnsServers": null,
"mountPoints": [],
"workingDirectory": null,
"dockerSecurityOptions": null,
"memory": null,
"memoryReservation": null,
"volumesFrom": [],
"image": "<AWSアカウント>.dkr.ecr.ap-northeast-1.amazonaws.com/my-nodejs",
"disableNetworking": null,
"interactive": null,
"healthCheck": null,
"essential": true,
"links": null,
"hostname": null,
"extraHosts": null,
"pseudoTerminal": null,
"user": null,
"readonlyRootFilesystem": null,
"dockerLabels": null,
"systemControls": null,
"privileged": null,
"name": "my-amazonlinux"
},
{
"dnsSearchDomains": null,
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/p-test-node-fargate",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"entryPoint": null,
"portMappings": [],
"command": null,
"linuxParameters": null,
"cpu": 0,
"environment": [],
"ulimits": null,
"dnsServers": null,
"mountPoints": [],
"workingDirectory": null,
"dockerSecurityOptions": null,
"memory": null,
"memoryReservation": null,
"volumesFrom": [],
"image": "<AWSアカウント>.dkr.ecr.ap-northeast-1.amazonaws.com/xray",
"disableNetworking": null,
"interactive": null,
"healthCheck": null,
"essential": true,
"links": null,
"hostname": null,
"extraHosts": null,
"pseudoTerminal": null,
"user": null,
"readonlyRootFilesystem": null,
"dockerLabels": null,
"systemControls": null,
"privileged": null,
"name": "xray-daemon"
}
],
"placementConstraints": [],
"memory": "2048",
"taskRoleArn": "arn:aws:iam::<AWSアカウント>:role/ecsTaskExecutionRole",
"compatibilities": [
"EC2",
"FARGATE"
],
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:<AWSアカウント>:task-definition/p-test-node-fargate:5",
"family": "p-test-node-fargate",
"requiresAttributes": [
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.execution-role-ecr-pull"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.task-eni"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.ecr-auth"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.task-iam-role"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "ecs.capability.execution-role-awslogs"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
},
{
"targetId": null,
"targetType": null,
"value": null,
"name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
}
],
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "1024",
"revision": 5,
"status": "ACTIVE",
"volumes": []
}
ECSサービス作成
FargateのECSクラスタでサービスを定義する。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-service.html
先程作成したタスク定義を指定する。
セキュリティグループ:セキュリティグループのインバウンドルールでサンプルアプリケーションが使用する3000ポートへのアクセスを許可する
パブリック IP の自動割り当て:有効
ELBオプション、サービスの検出なし。
Service Auto Scaling無効化
タスク(コンテナ)がRUNNINGになるまで待つ
getしてみる
curl <fargateのコンテナのパブリックIPアドレス>:3000/get
fargateのコンテナのパブリックIPアドレスは、クラスタのタスクタブのタスクをクリックして、Public IPを確認する。
比較
前提
EC2タイプ
インスタンスタイプ:m4.large
タスクサイズ:
タスクメモリ (MiB)2048
タスク CPU (単位):1024
Fargate
タスクサイズ:
タスクメモリ (MiB)2048
タスク CPU (単位)1024
(雑な)比較結果
_人人人人人人人人_
> そんなに差はなし <
 ̄Y^Y^Y^Y^Y^Y^Y ̄
参考
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-nodejs-middleware.html
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-sdk-nodejs-awssdkclients.html
https://inokara.hateblo.jp/entry/2017/05/07/195717