ジョギング
- 香椎浜 x 2 周
日課
- (腕立て x 30 + 腹筋 x 30) x 3
出棺のタイミングで偶然に流れた義父が好きだった石原裕次郎の「わが人生に悔いなし」。
5 月 9 日(火) AM 2:02 に義父が急逝した。
諸事情で頻繁にお会いしてお酒を酌み交わす等の義父と婿のような関係は築くことが出来なかったのをとても後悔しているので、この曲が更に染みる。
お通夜、葬儀を通して感じたこと。
身内に不幸があって、ここ数日は本当にバタバタな状態だったけど、故人を送るという一つのプロジェクトの中で色々と学ぶ事があった。特にタスク管理。個々のタスクを細分化して家族にアサインして、進捗を管理していくあたりは、今後の参考にしたい。
— Yohei Kawahara(かっぱ) (@inokara) 2017年5月14日
あと、喪主というプロジェクトリーダーが悲しみに打ちひしがれる中、故人を安らかに見送るという明確な目標を見失わずに、メンバーにタスクをアサインしていくことが重要。故人に対する色々な思いの中でそれを行うのは本当に大変だと思うけど。
— Yohei Kawahara(かっぱ) (@inokara) 2017年5月14日
仮通夜、お通夜、葬儀を通して、お経を上げて下さった住職の話が面白くて仏教について少し興味が湧いた。
お通夜、葬儀だけならこんなに疲れることも無いんだろうけど、これら以外の部分で色々な事が有りすぎて本当に疲れた。人を送るというのはこんなにも大変なのかと思うと、自分が逝く時には何も要らないから、粉々にしたお骨をその辺に適当に撒いてもらって構わないと思った。
www.slideshare.net
上記の資料が簡潔に纏まっていてとても参考になった。
そして、FAQ の記述を引用すると…
開発者は、AWS X-Ray を使用して、本番環境や分散アプリケーション (マイクロサービスアーキテクチャを使用して構築されたアプリケーションなど) を分析およびデバッグできます。X-Ray を使用すると、アプリケーションやその基盤となるサービスの実行状況を把握し、パフォーマンスの問題やエラーの根本原因を特定して、トラブルシューティングを行えます。X-Ray では、アプリケーション内で転送されるリクエストがエンドツーエンドで表示され、アプリケーションの基盤となるコンポーネントのマップも表示されます。X-Ray を使用すると、開発中か本番環境かにかかわらず、シンプルな 3 層アプリケーションから数千のサービスで構成される複雑なマイクロサービスアプリケーションまで、さまざまなアプリケーションを分析できます。
ざっくり言うと…アプリケーションのモニタリングサービスで、競合は NewRelic や Datadog APM になると思う。
こちら より転載。
こちら より転載。
キーワード | 内容 |
---|---|
Trace | サービスをまたがった単⼀リクエストに関連したエンドツーエンドのデータ |
Segments | 1 つのサービスに対応するトレースの⼀部 |
Sub-segments | サービス内でのリモートコールもしくはローカルコンピュートのセクション |
Annotations | トレースをフィルタするのに使⽤できるビジネスデータ |
Metadata | トレースに対して付与できるビジネスデータ ただし、トレースのフィルタリングには使⽤されない |
Errors | 正規化されたエラーメッセージとスタックトレース |
Sampling | トレースとして取得するアプリケーションへのリエストの割合 |
Service Map とはトレースデータを視覚化したもの。
各ノード(上図だと緑色◯)をクリックするとトレースデータを確認する事が出来る。
トレース画面ではアプリケーションへのリクエストのレイテンシやステータスコードを確認する事が出来る。
アプリケーションへのトレースデータには ID が付与されていて、トレース ID をクリックするとトレースの詳細を確認する事が出来る。
Trace の詳細画面ではアプリケーションのリクエスト開始から終了までの個々の処理が 1 つのセグメントとして詳細を確認する事が出来る。
例えば、上図だと MyApp
や DynamoDB
はそれぞれ別のセグメントとして認識され、アクセスしたリソースや例外が発生した場合等は例外の情報まで確認する事が出来る。
X-Ray SDK がサポートしていない言語からも X-Ray API を利用してトレースデータを送信することは出来、以下のような API が提供されている。こちらより抜粋。
API | 用途 |
---|---|
PutTraceSegments | AWS X-Rayへセグメントのドキュメントをアップロード |
BatchGetTraces | IDにより指定されたトレースのリストを検索 |
GetServiceGraph | アプリケーションとコネクション内でサービスを⽰すドキュメントを検索 |
GetTraceSummaries | オプションのフィルタを使⽤して、指定された時間 枠で使⽤可能なトレースのIDとメタデータを取得 |
以下のような環境でチュートリアルする。
$ 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 デーモンのパッケージを取得して 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 用の設定ファイルを以下のように作成する。(ファイル名は任意の名前で構わない)
Processor: Region: "ap-northeast-1" Logging: LogLevel: "warn" LogPath: "/tmp/xray-daemon.log" LocalMode: true
ローカル環境で動かす場合、LocalMode
は true
にしておく必要がある。尚、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 デーモンを起動する。
$ 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 を用意した。
$ tree -L 1 sampleapp sampleapp ├── app.js ├── bin ├── node_modules ├── package.json ├── public ├── routes └── views 5 directories, 2 files
以下のように AWS SDK for JavaScript と X-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" } }
以下のような修正を加えた。
$ 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
にアクセスすると以下の処理が実行される。
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 を利用しているので endpoint
は http://localhost:8000
を指定している。
/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 のダッシュボードを確認すると…
以下のように出力されている。
なかなかカッコイイ。
セグメントをクリックすると…
書きたいと思っている。
AWS 以外で動いているアプリケーションもトレースする事が出来るのは嬉しい。
X-Ray SDK に対応している言語が少ないのが残念(Datadog APM は Python と Ruby と Go をサポートしている)だけど、Datadog APM や NewRelic のようなアプリケーションのパフォーマンスの計測が AWS だけで行えるというのも嬉しい。(※ AWS SDK や AWS CLI でトレースデータを送信や取得は出来る)
気になっていた Elixir という言語について、以下の資料を写経して引き続きチュートリアルしてみた。
www.slideshare.net
ubuntu@ubuntu-xenial:~$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=16.04 DISTRIB_CODENAME=xenial DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
ubuntu@ubuntu-xenial:~$ elixir --version Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] Elixir 1.4.1
以下のライブラリを導入する。
ライブラリ名 | Github リポジトリ | 用途 |
---|---|---|
HTTPoison | https://github.com/edgurgel/httpoison | HTTP クライアント |
Poison | https://github.com/devinus/poison | JSON パーサー |
sample/mix.exs を以下のように修正する。
--- mix.exs.original 2017-05-05 02:36:31.000000000 +0000 +++ mix.exs 2017-05-05 02:37:13.000000000 +0000 @@ -28,6 +28,9 @@ # # Type "mix help deps" for more examples and options defp deps do - [] + [ + { :httpoison, "~> 0.7.2" }, + { :poison, "~> 1.5" } + ] end end
Ruby だと Gemfile みたいなものですな、きっと。
以下を実行して各モジュールを取得する。
ubuntu@ubuntu-xenial:~$ mix deps.get
以下のように出力される。
Running dependency resolution... Dependency resolution completed: hackney 1.3.2 httpoison 0.7.5 idna 1.0.3 poison 1.5.2 ssl_verify_hostname 1.0.6 * Getting httpoison (Hex package) Checking package (https://repo.hex.pm/tarballs/httpoison-0.7.5.tar) Using locally cached package * Getting poison (Hex package) Checking package (https://repo.hex.pm/tarballs/poison-1.5.2.tar) Using locally cached package * Getting hackney (Hex package) Checking package (https://repo.hex.pm/tarballs/hackney-1.3.2.tar) Using locally cached package * Getting idna (Hex package) Checking package (https://repo.hex.pm/tarballs/idna-1.0.3.tar) Using locally cached package * Getting ssl_verify_hostname (Hex package) Checking package (https://repo.hex.pm/tarballs/ssl_verify_hostname-1.0.6.tar) Using locally cached package
Ruby だと bundle install
みたいなものですが、きっと。
以下のように get()
関数を追加する。
defmodule Sample do ... def get() do HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir") end end
Sample 関数を recompile()
した後に Sample 関数を実行すると以下のように Qiita API から Elixr 関連記事を取得する。
iex(5)> Sample.get ... {"X-Request-Id", "850ad9f5-9b62-4a65-8ffb-d1f37ffb511d"}, {"X-Runtime", "0.432123"}, {"X-XSS-Protection", "1; mode=block"}, {"transfer-encoding", "chunked"}, {"Connection", "keep-alive"}], status_code: 200} iex(6)>
おお、簡単ばい。
以下のように get()
関数を修正して Body だけを抜き出す。
defmodule Sample do def get() do response = HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir") body = body( response ) Poison.decode!( body ) end def body( %{ status_code: 200, body: json_body } ), do: json_body end
改めて recompile()
を実行して Sample 関数を実行すると Qiita API レスポンスから Body のみを取得出来る。
iex(17)> Sample.get ... "user" => %{"description" => "", "facebook_id" => "", "followees_count" => 0, "followers_count" => 1, "github_login_name" => nil, "id" => "mdmom", "items_count" => 5, "linkedin_id" => "", "location" => "", "name" => "", "organization" => "", "permanent_id" => 152421, "profile_image_url" => "https://avatars.githubusercontent.com/u/18415389?v=3", "twitter_screen_name" => nil, "website_url" => ""}}]
おお。いい感じ。
status_code
が 200
にマッチしたレスポンスを処理しているが、もし status_code
が 200
以外の場合を考慮した場合にはどのように書けば良いのか。
defmodule Sample do def get() do response = HTTPoison.get!("https://qiita.com/api/v3/items?query=Elixir") if response.status_code == 200 do body = body( response ) Poison.decode!( body ) else response.status_code end end def body( %{ status_code: 200, body: json_body } ), do: json_body end
こんな感じかな…おお、Ruby となんか似てる。こちらによると、cond do ~
という書き方もあるらしい。
defmodule Sample do def get() do response = HTTPoison.get!("https://qiita.com/api/v3/items?query=Elixir") cond do response.status_code == 200 -> body = body( response ) Poison.decode!( body ) response.status_code != 200 -> response.status_code end end def body( %{ status_code: 200, body: json_body } ), do: json_body end
現状は以下のように Qiita からデータを取得して Body を抽出し JSON をデコードするという流れを変数で受け渡しを行っている。
defmodule Sample do def get() do response = HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir") body = body( response ) Poison.decode!( body ) end def body( %{ status_code: 200, body: json_body } ), do: json_body end
まあ、別にコレでもいいかなーと思うけど。
Elixir ではパイプ演算子を使えばスマートに書くことが出来る。
defmodule Sample do def get() do HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir") |> body |> Poison.decode! end def body( %{ status_code: 200, body: json_body } ), do: json_body end
|>
は前の処理の出力を次の処理の第一引数として渡すことが出来る。引数が複数ある場合には、第二引数を以降を ()
で括って指定が可能。
レスポンスを解析すると以下のような内容となっている。
[ %{ "body" => "body1", ..., "title" => "titile1" }, %{ "body" => "body2", ..., "title" => "titile2" }, %{ "body" => "body3", ..., "title" => "titile3" }, ]
以下のように (1) 〜 (6) を追加する。
defmodule Sample do def get() do HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir") |> body |> Poison.decode! |> title_list( [] ) # 追加 1 end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( [ head | tail ], titles ) do # 追加 2 %{ "title" => json_title } = head # 追加 3 added_titles = [ json_title ] ++ titles # 追加 4 title_list( tail, added_titles ) # 追加 5 end def title_list( [], titles ), do: titles # 追加 6 end
以下のような処理が追加されたことになる。
[]
は空のリストを引数として title_list()
関数を呼び出すhead
それ以降を tail
で処理出来るようにするhead
で取得した Body リストから title
キーをパターンマッチで抽出して json_title
に保持するjson_title
を []
で囲むことでリスト化し、titles
に連結して added_titles
に保持するtail
に保持しているデータに対して 2 〜 4 の処理を繰り返し呼び出すtail
が空になった際に呼ばれて、titles
を出力するrecompile()
を実行した後に関数を実行すると以下のように出力される。
iex(45)> Sample.get ["Amazon EC2上にRed Hatインスタンスを作成してElixir Reportをコンソールインストールする手順", "Mastodon を Node.js で遊んでみる", "CowboyのHelloWorldまで", ... "Elixirで弱々しいAI#3を作る「MeCab辞書差し替え |> CaboChaモジュールの作成」"]
おお。
例のごとく、Ruby で書くと以下のように書けると思う。
irb(main):001:0> require 'net/http' => true irb(main):002:0> require 'json' => true irb(main):003:0> res = Net::HTTP.get(URI.parse('https://qiita.com/api/v2/items?query=Elixir')) => ... ... irb(main):004:0> titles = [] => [] irb(main):005:0> JSON.parse(res).each { |body| titles << body['title'] } irb(main):006:0> titles.reverse => ["Amazon EC2上にRed Hatインスタンスを作成してElixir Reportをコンソールインストールする手順", "Mastodon を Node.js で遊んでみる", "CowboyのHelloWorldまで", "[翻訳] Elixirでスモールデータを扱う", "Elixir製のプロジェクトをTravisCIで運用するための.travis.yml", "Amazon EC2上のRed Hat Enterprise LinuxにX11転送でElixir ReportをGUIインストールする手順", "Amazon EC2上のRed Hat Enterprise Linuxにリモートデスクトップ接続してElixir ReportをGUIインストールする手順", "Ectoでバリデーション", "【elixir】slackbotをつくり定期的にコメントさせる", "Laravel5系blade中でassetファイルのlastModified追加", "Ectoでカスタムバリデーションを追加する", "Google Compute EngineのRed Hat VMインスタンス作成と、X11転送でGUIインストーラをWindowsから操作する手順 - Elixir Report", "PhoenixでPlugを使用しアクションの前にフィルターを実装する", "Goの構造体にメ タ情報を付与するタグの基本", "Google Compute EngineでのRed Hat Enterprise Linux VMインスタンス作成と、Elixir Reportのコマンドインストール手順", "ElixirでSleep Sort書いてみた", "Elixirで弱々しいAI#1を作る「MeCabで文章パース」", "Elixirのソースコードにバイナリを埋めこむ方法とそのバイナリ表現の生成方法", "Elixirで弱々しいAI#2を作る「構文解釈してオウム返し(ついでにPhoenixでWebアプリ化)」", "Elixirで弱々しいAI#3を作る「MeCab辞書差し替え |> CaboChaモジュールの作成」"]
これまでのチュートリアルで作成した sample.ex は以下のようになった。
defmodule Sample do # def sort( values ) do # Enum.sort( values ) # end def sort( values ), do: Enum.sort( values ) def match( %{ Yes: "we can" } ), do: "Barack Obama" def match( %{ Yes: need } ), do: need def match(_), do: "Yes...Not EXIST" def match_inner( input_map ) do %{ Yes: need } = input_map need end # def nums( [ head | tail ], rows ), do: nums( tail, [ head | rows ] ) # def nums( [], rows ), do: rows def nums( [ head | tail ], rows ) do #...1 nums( tail, [ head | rows ] ) #...2 end def nums( [], rows ) do #...3 rows #...4 end def sum_list( [ head | tail ], total ) do sum_list( tail, head + total ) end def sum_list( [], total ) do total end def get() do HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir") |> body |> Poison.decode! |> title_list( [] ) end def body( %{ status_code: 200, body: json_body } ), do: json_body def title_list( [ head | tail ], titles ) do %{ "title" => json_title } = head added_titles = [ json_title ] ++ titles title_list( tail, added_titles ) end def title_list( [], titles ), do: titles end
って便利だなと思ったけど、エラーハンドリングの方法が見つけられずに悩んでいるところ。