WebpackでAWS SDKをバンドルするとサイズが大きいのでダイエットする

経緯

サーバレス、流行ってますね。
ウェブホスティングしたS3上にReactやRiot.jsなどでSPAを作って、認証はCognito UserPoolというのがテッパン構成でしょう。

そして、SPAを作るならWebpackでES6で書いたソースをトランスパイルして、バンドル(ひとつの*.jsファイルにまとめること)して…というのが通例ですね。(サーバーサイドの人にはこの辺がツラい)

しかしながら、いざやってみると…でかいよバンドルされたJSファイル。minifyして2MB近くとか…。

何が大きいのかwebpack-bundle-size-analyzerを使って見てみましょう。

$ $(npm bin)/webpack --json | webpack-bundle-size-analyzer 
aws-sdk: 2.2 MB (79.8%)
lodash: 100.65 KB (3.56%)
amazon-cognito-identity-js: 94.85 KB (3.36%)
node-libs-browser: 84.87 KB (3.00%)
  buffer: 47.47 KB (55.9%)
  url: 23.08 KB (27.2%)
  punycode: 14.33 KB (16.9%)
  <self>: 0 B (0.00%)
riot: 80.64 KB (2.85%)
jmespath: 56.94 KB (2.02%)
xmlbuilder: 47.82 KB (1.69%)
util: 16.05 KB (0.568%)
  inherits: 672 B (4.09%)
  <self>: 15.4 KB (95.9%)
crypto-browserify: 14.8 KB (0.524%)
riot-route: 8.92 KB (0.316%)
debug: 8.91 KB (0.316%)
events: 8.13 KB (0.288%)
uuid: 5.54 KB (0.196%)
process: 5.29 KB (0.187%)
querystring-es3: 5.06 KB (0.179%)
obseriot: 4.43 KB (0.157%)
<self>: 27.78 KB (0.983%)

なるほど、AWS SDKですね、やっぱり。

なぜ大きくなっちゃうのか

Webpackによるバンドルではnpm installしたモジュールが全て闇雲にバンドルされるわけではありません。
結構賢くて実際に使われているものだけがバンドルされるように考えられています。具体的にはimport(require)されているモジュールだけを再帰的にたどって1つのJSファイルにまとめていきます。
つまり、importしているファイル数が多ければ多いほどバンドルされたファイルは大きくなります。

import AWS from "aws-sdk"

普通のサンプルだとAWS SDKを使うときはこのようにimportすると思います。これが大きな罠なのです。
AWS SDKのソースを見てみましょう。

lib/aws.js
// Load all service classes
require('../clients/all');

ここがサイズを肥大化させる要因となっています。全てのモジュールを読み込んでいるので使いもしないAWSの機能も含めて全て読み込んでしまっているのです。

どうしたらダイエットできるのか

http://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/webpack.html#webpack-importing-services

を見てみると書いてあります。

The require() function specifies the entire SDK. A webpack bundle generated with this code would include the full SDK but the full SDK is not required when only the Amazon S3 client class is used.

require("aws-sdk")と書いてしまうと、たとえS3しか使わなくても全部の機能を読み込んでしまいます。
注) import from "aws-sdk"と書いても同じ意味になります。

その下の方に、

Here is what the same code looks like when it includes only the Amazon S3 portion of the SDK.

// Import the Amazon S3 service client
var S3 = require('aws-sdk/clients/s3');

// Set credentials and region
var s3 = new S3({
    apiVersion: '2006-03-01',
    region: 'us-west-1', 
    credentials: {YOUR_CREDENTIALS}
  });

と答えが載っています。

clientsディレクトリ以下のファイルを個別にimportすることができるようになっているということです。

なるほど、試してみた

https://github.com/NewGyu/riot-sample/compare/cognito-login…fat-cognito-login

$ $(npm bin)/webpack --json | webpack-bundle-size-analyzer 
aws-sdk: 325.01 KB (36.3%)
lodash: 100.65 KB (11.2%)
amazon-cognito-identity-js: 94.85 KB (10.6%)
 :

2.2MB -> 325KB と激ヤセです!

続きを読む

CloudFrontでモバイル端末を判別する

サーバー側で User-Agent を見て端末を判別するとキャッシュ効率が落ちるので、代わりに下記の4つのHTTPヘッダをWhitelistに入れると、判別済みの情報が送られてきます。

  • CloudFront-Is-Desktop-Viewer
  • CloudFront-Is-Mobile-Viewer
  • CloudFront-Is-SmartTV-Viewer
  • CloudFront-Is-Tablet-Viewer

これらの動作を調べてみました。

iOSスマートフォン (iPhone)

User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1

Desktop Mobile SmartTV Tablet
false true false false

iOSタブレット (iPad)

User-Agent: Mozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1

Desktop Mobile SmartTV Tablet
false true false true

MobileとTablet両方trueになる

Mac (Firefox)

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0

Desktop Mobile SmartTV Tablet
true false false false

Androidスマートフォン (Nexus 5X)

User-Agent: Mozilla/5.0 (Linux; Android 7.1.1; Nexus 5X Build/N4F26I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36

Desktop Mobile SmartTV Tablet
false true false false

Androidタブレット (Nexus 7)

User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MMB29O) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Safari/537.36

Desktop Mobile SmartTV Tablet
false true false true

ちゃんとスマートフォンとタブレット判別できてる!

Fire TV

User-Agent: Mozilla/5.0 (Linux; U; Android 4.2.2; en-us; AFTB Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30

Desktop Mobile SmartTV Tablet
false false true false

検証に用いたソースコード

PHPで殴り書きしました。

<?php
echo($_SERVER['HTTP_CLOUDFRONT_IS_DESKTOP_VIEWER']);
echo($_SERVER['HTTP_CLOUDFRONT_IS_MOBILE_VIEWER']);
echo($_SERVER['HTTP_CLOUDFRONT_IS_SMARTTV_VIEWER']);
echo($_SERVER['HTTP_CLOUDFRONT_IS_TABLET_VIEWER']);

参考文献

続きを読む

Amazon Elasticsearch Service へ Embulk を使ってデータロード

はじめに

AWS ElasticSearch Serviceはフルマネージドで運用要らず、今のところ(2017/5/18)他の追随を許さ無い魅力があります(筆者はElastic社のElastic Cloudは使った事がありません)
Azureの場合、MarketPlaceからElastic-Stackが提供されて完全占有クラスターが自動構築可能です。GCPもblogにある通り、Azureとほぼ同じ。完全占有クラスターのメリット?がありそうな反面、ElasticClusterの運用/管理がtrade-offとしてつきまといます。

当初はAzureで個別クラスターを使っていましたがトラブルが続き、運用などはしたく無いのでAWSに浮気してみましたのでその記録とTipsをまとめます

環境

  • 既存の物を使い回しでAzureのVMにembulkインストール、データロード等の制御に使います
  • データは Azure Blobに保存
  • AWS ElasticSearch Service (ver5.1が選択できたのでこれ)
  • 図解
    Load

セットアップ

実行

以下の手順で実施

  • BlobにElasticSearchに投入したいログをUploadしておく
  • embulkから吸い出し、ElasticSearch Serviceへ投げ込み用のconfig.yml作成
in:
  type: azure_blob_storage
  account_name: <BLOB NAME>
  account_key: <BLOB KEY>
  container: <CONTAINER NAME>
  path_prefix: <PREFIX as you like>
  decoders:
    - {type: gzip}
  parser:
    charset: UTF-8
    newline: CRLF
    type: jsonl
    schema:

out:
  type: elasticsearch_using_url
  mode: normal
  nodes:
    - url: "<YOUR ElasticSearch Domain>.us-east-2.es.amazonaws.com:80"
  index: "sample"
  index_type: "sample"
  • 実行
$ embulk preview config.yml
$ embulk run config.yml -l warn -r resume_state_aws.yml &>> embulk_awses.log

確認

  • AWS consoleで確認
    Load OK

まとめ

  • ElasticSearchへの大量のデータロードにはembulkは最適な手段
  • AWS ElasticSearch Serviceでは 9200 portが使えない為、embulk pluginの選択に注意、http経由で実施するのがおすすめ

余談

  • およその費用比較
AWS Azure GCP
費用 \$250 – $300 程度 \$530 未確認
  • VirtulMachineのサイズ、データ量は1TB程度

    • AWS

      • EC2: t2.midium x3 , \$150
      • EBS: 1TB, \$100
      • データ通信料
    • Azure
      • VM: D2v2 , \$84.82 x6 = $505.44
      • Elastic-Stack構築時、VMサイズを選択可能だが、D1v2(default)では使い物にならなかった
      • Blob: 1TB , \$24
      • データ通信料
    • GCP
      • 未検証

続きを読む

【MySQL】AWS Aurora上でもgh-ostオンラインマイグレーション

【MySQL】gh-ostでオンラインマイグレーションの応用編です。

gh-ostはMySQLのバイナリログを使ってテーブルを同期します。
一方で、AWSのAmazon Auroraは、バイナリログを使わない方法でリードレプリカを作成します。

※Auroraのリードレプリカが一体どういう仕組みなのかここでは省きますが、過去のAWS SummitのDeep DiveとかでAmazonの超技術を垣間見ることができます。
[レポート] Amazon Aurora deep dive ~性能向上の仕組みと最新アップデート~ #AWSSummit | Developers.IO

さてバイナリログを使っていないとなると、gh-ostの利用はどうなるのでしょうか。
gh-ostにもドキュメントがありますが

https://github.com/github/gh-ost/blob/master/doc/rds.md

gh-ost has been updated to work with Amazon RDS however due to GitHub not relying using AWS for databases, this documentation is community driven so if you find a bug please open an issue!

(gh-ostはAmazon RDSでも動くようになったが、我々GitHubはDBにAWSは使ってないので、このドキュメントはコミュニティに懸かっている。だからバグを見つけたら是非issueを上げて欲しい)

とあり、gh-ostの特徴のひとつである「GitHub社での実績」が欠けます。
実際に使ってみました。

準備

下記条件でAWS RDS Auroraインスタンスを作ります。
t2.smallインスタンスが解禁されたので、検証用途でもAuroraを作りやすくなりました。

  • Aurora 5.6.10a
  • db.t2.small
  • writer×1、reader×1

また、接続元のIPアドレスを調べてセキュリティグループでポートを開けておきます。

gh-ostを使うにはバイナリログが必要です。
先述のとおりAuroraのリードレプリカ作成にはバイナリログは使われませんが、Auroraと他のMySQL等とのレプリケーションのためにバイナリログの出力機能は存在しています。

Aurora と MySQL との間、または Aurora と別の Aurora DB クラスターとの間のレプリケーション – Amazon Relational Database Service

gh-ostを利用するには、DB Cluster Parameter Groupにて「binlog_format=ROW」に設定しておく必要があります。

クラスターエンドポイントに接続し、show binary logsでバイナリログファイルが確認できればOKです。

mysql> SHOW VARIABLES LIKE "binlog_format";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+
1 row in set (0.01 sec)

mysql> show binary logs;
+----------------------------+-----------+
| Log_name                   | File_size |
+----------------------------+-----------+
| mysql-bin-changelog.000001 |       120 |
| mysql-bin-changelog.000002 |     16062 |
+----------------------------+-----------+
2 rows in set (0.01 sec)

今回の検証には下記のデータベース/テーブルを使います。

USE sushiya;

CREATE TABLE sushi(
  id   INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20),
  INDEX(id)
);

gh-ostの実行

まず入手します。

$ wget https://github.com/github/gh-ost/releases/download/v1.0.36/gh-ost-binary-linux-20170403125842.tar.gz
$ tar xzvf ./gh-ost-binary-linux-20170403125842.tar.gz

gh-ostには3つのモードがありますが、Aurora上ではb. Connect to masterにて実行します。

$ ./gh-ost 
  --user="(ユーザー)" 
  --password="(パスワード)" 
  --host="(クラスターエンドポイント)" 
  --port=3306 
  --database="sushiya" 
  --table="sushi" 
  --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" 
  --allow-on-master 
  --execute

確認します。

mysql> show create table sushiG
*************************** 1. row ***************************
       Table: sushi
Create Table: CREATE TABLE `sushi` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `price` int(11) DEFAULT '100',
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.01 sec)

カラムが増えていることを確認できました。

gh-ostの特徴としてa. Connect to replica, migrate on masterモードやc. Migrate/test on replicaモードがありますが、これらを使うためにリードレプリカに接続して実行しようとしても「バイナリログが無効」というエラーで失敗してしまいます。

$ ./gh-ost 
>   --user="(ユーザー)" 
>   --password="(パスワード)" 
>   --host="(リーダーエンドポイント)" 
>   --port=3306 
>   --database="sushiya" 
>   --table="sushi" 
>   --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" 
>   --test-on-replica
2017-05-02 01:11:40 FATAL (リーダーエンドポイント):3306 must have binary logs enabled

もしAuroraからバイナリログを受け取って同期しているSlaveデータベースがあれば、c. Migrate/test on replicaモードでの実行も可能なのではないかと思います。

Interactive commandsの使用

通常のgh-ost利用時と同様に、UNIXドメインソケットを使った制御もAurora経由で可能です。

gh-ost/interactive-commands.md at master · github/gh-ost

テーブル切り替えを保留にするフラグファイルを作成します。

$ touch /tmp/ghost.postpone.flag

実行します。

$ ./gh-ost 
  --user="(ユーザー)" 
  --password="(パスワード)" 
  --host="(クラスターエンドポイント)" 
  --port=3306 
  --database="sushiya" 
  --table="sushi" 
  --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" 
  --allow-on-master 
  --postpone-cut-over-flag-file=/tmp/ghost.postpone.flag 
  --execute

--postpone-cut-over-flag-fileオプションで、先程作成したフラグファイルを指定するとテーブルの同期だけが進み、切り替えが保留状態になります。

ここからステータスの確認など、gh-ost実行途中での操作ができます。

$ echo status | nc -U /tmp/gh-ost.sushiya.sushi.sock

unposeponeでテーブルの切り替えが実行されます。

$ echo unpostpone | nc -U /tmp/gh-ost.sushiya.sushi.sock

確認します。

mysql> SHOW CREATE TABLE sushiG
*************************** 1. row ***************************
       Table: sushi
Create Table: CREATE TABLE `sushi` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `price` int(11) DEFAULT '100',
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.01 sec)

カラムが追加されました。

Auroraでgh-ostの必要性がどれくらいあるかはさておき、Auroraでも一部条件下にてgh-ostを利用できることがわかりました。

参考文献

続きを読む

Amazon lexで音声チャットボットを作成してみた

天気の良い週末にやることがなく、昼の12時ごろ、趣味の長時間歯磨きをしながらtechCrunchを見ていたら、amazonのlexの記事が乗っていた。

http://jp.techcrunch.com/2017/04/21/20170420amazon-lex-the-technology-behind-alexa-opens-up-to-developers/

Amazonの仮想アシスタントAlexaを支えているテクノロジーであるAmazon Lexが、今朝(米国時間20日)のロイターの記事によれば、プレビュー段階を終了したということだ。

↓↓↓
https://aws.amazon.com/jp/lex/

Amazon Lex は、音声やテキストを使用した会話型インターフェイスをさまざまなアプリケーションに構築するためのサービスです。
Lex では、自動音声認識 (ASR) という音声をテキストに変換するための高度な深層学習機能と、
テキストの意図を理解するための自然言語理解 (NLU) を利用できます。
これにより、非常に魅力的なサービスと生き生きとした音声対話を実現するアプリケーションを構築できます。
Amazon Lex を使うと、すべての開発者が Amazon Alexa に採用されている深層学習技術と同じ技術を利用し、
自然言語での高度な対話ボット (チャットボット) を短時間で簡単に構築できるようになります。

このページにある動画は去年の11月末のものだ。
その頃、某ゲームの開発と並行して走る運営の現場の両方が修羅場で、毎日トラブルと戦っていた。さらにトラブルは現場だけとは限らず、外野がキナ臭くなり、モヒカンと肩パッドの荒くれ供から平和を守るために北斗百烈拳を繰り出していた。明日を見失って、CV千葉繁になっていた記憶がうっすらとある。
当然re:InventのKeynoteなど見る暇はなかった。

Try a sample

まずはこちらのページでサンプルを作ってみる。
https://console.aws.amazon.com/lex/home?region=us-east-1#bot-create:

cb-1.png
BookTripをクリック。

cb-2.png

IAM:自動的に作ってくれるんはありがたい。念のため後でデタッチされた権限を見ることにするけどな。
Child-Directed?:子供専用ではないからNo。

Createを押しやす。


マイクを許可しますかのプロンプトが出るので、許可にする。
このmacには内臓マイクがないので、iPhoneのヘッドホンを繋いでおく。

cb-3.png

しかしこの後、何をどうしたらいいのか、説明がないからさっぱりわからない。。。。。。
困った。

tutorialの動画をみながらもう一度sample chat botをつくる

AWS Lex Demo Tutorial (Jan 2017)
https://www.youtube.com/watch?v=7uG9cuxNo5k
「花の注文をするチャットボット」を10分くらいで作る様子。

4ヶ月ほど前に親切な人が投稿してくれたチュートリアル動画で、
やさしい英語をえらんで説明してくれているが、
英語のききとりがやっとの自分はYoutobeの字幕機能で補いながらみた。
この通りやってみると、なんとなくわかる。

なおこの動画の中では、まだ「Channels」がFacebookしかないが、
3ヶ月後の現在は、3つに増えている。

  • Facebook
  • Twilio SMS ←増えた
  • Slack ←増えた

花屋のサンプルをそのまま真似して、以下のように作ってみた。

name:OrderFlowersSitopp
Description:Bot to order flowers on the behalf of a user
Output voice:Joey

cb-8.png

Buildをするとtest Botでtext入力できるようになる。
Sample Utterancesの1個めをコピペして、
I would like to pick up flowers
と記入してエンターしてみるとすぐ答えが返ってきた。ほうほう。
cb-7.png
あっ、これもうできちゃったのか。早いなぁ。(΄◉◞౪◟◉`)

音声入力してみる

AWSのLexのコンソールに行く
https://console.aws.amazon.com/lex/home?region=us-east-1#bots:

さっき作った「name:OrderFlowersSitopp」を選ぶcb-15.png

右側にチャットボットのタブが現れるので、
入力欄のマイク部分をクリックする。そうすると音声入力モードになった。

cb-13.png


Macに繋いだiPhone用のヘッドフォンのマイクのあたりに話しかけると、テキスト化して、チャットにしてくれます。

上の例だと、私の発音が悪いので、rosesがlosesとかclosesになっている。ひどい(笑)
しかもボットはそのまま「Ok your closes will be ready 」と予約終了してしまった。
いったいなんの花が届くのか。。笑

もちろん、これはサンプルだから何も届かないけど、
ちゃんとカスタムスロットに無いものはエラーにしないと、そのまま注文を受け取ったことになってしまうから、
注意が必要ということで。

sampleを作ってわかったこと

  • サンプルの3種類は以下の通り。主たる目的はこういうものなのかな。

    • 旅行の予約
    • 花屋のオーダー
    • 歯医者の予約

考えられる用途

  • 保険の見積もり
  • 通販サイトのパスワード忘れた
  • 美容院の予約
  • ゲームの進行不具合の通報
  • ゲームのチート疑惑の通報
  • ゲーム掲示板の炎上の通報
  • ゲームの..いかん、つい仕事が

これがFacebookのお店のページに備わってたら、確かに気楽に呼び出しやすい。
でも受け取る側はどうなんだろう? botが応答して解決しない場合もあるだろうし、ちゃんと過去の履歴を呼び出せるのかな?

大変そうな点

Sample UtterancesとSlot typesの用意が大変そう。

Sample Utterancesとは、ユーザーがこういったらこの部分をパラメタとして受け取るという設定。花屋の予約sampleでは2個しかなかったけど、こんなんで済むはずがない。

  • I would like to pick up flowers
  • I would like to order some flowers

様々な言い方を網羅しておかないと、「おっしゃることがわかりません」を連発するポンコツbotになってしまう。
(参考動画 Virgin Flight – Saturday Night Live

Slot typesは、ユーザーの返答の中から受け付けるキーワードの種類で、花屋の予約sampleでは花の種類3つが該当する。だがこんな少なくて済むはずがない。3種類しか売ってない花屋っていうのだったら別だけど。
– roses
– lilies
– tulips

Sample utterancesもSlot TypesもAlexa Skillsにも同様の設定項目があり、これらをいかにきめ細かに定義できるかでbotの優秀さが決まると思う。

まとめ

  • AWSのlexを使ってchatbotが作れる。
  • 連携先は、Facebook、Twilio SMS、slackが選べる。ただし音声入力は、声の入力装置があるプラットフォームじゃないとダメ。(Twilioは電話と連携してるから、これが候補か。)
  • Utterancesとslot typesをちゃんと定義しないと、ポンコツbotになってしまう。人工知能というより人工無能。これはAlexa skillsも同じ。
  • しゃべった言葉を分解してテキストにするところが人工知能。ここはブラックボックス。
  • サンプルでは必要なかったが、ちゃんとbotを作るときにはLambda関数も書く必要がある。(http://docs.aws.amazon.com/ja_jp/lex/latest/dg/getting-started-ex2.html)
  • botの会話ログを見るのってどうするんだろ?

以上!

続きを読む

Terraformでstate lockを触ってみた。

はじめに

初めてTerraformを触る折に
「v0.9以上だとstate lockっていう機能があるから使ったほうが良いよ」
というアドバイスをもらいました。
(そもそも、stateってなんですか?から始まるわけですが・・・)

初めてTerraformを触った時に、公式ドキュメントだけでは???な状態だったので
痕跡として残しておきます。

結果として、まだ使いこなせてません。というか僕の使い方は果たしてあっているのだろうか
なので情報としては不足しているかもしれませんが、とりあえず備忘録的に残します。
またわかったらUpdateします。

そもそもState Lockとは

完全に理解しないで書いてますが
Terraformは「自分が知っているリソース(EC2やS3)しか関与しないよ」というポリシーで
どのリソースを自分が扱ったか、というのをtfstateというJSONファイルで管理しているようです。

このtfstateファイルは、一人でTerraformを動かす場合は問題無いのですが
複数人でTerraformをいじる必要が出てきた時に問題で、それは「backend」モジュールを使うことで回避してきたようですが
同じタイミングでterraformを実施した場合、その部分までは制御しきれてなかったようです。

で、v0.9以上?から、「Plan/Applyをしているタイミングではロックする」機能が実装されたようで。
せっかくなので導入してみました。

公式サイト:https://www.terraform.io/docs/state/locking.html

準備

手動で準備が必要なものは
・terraformを実行するユーザのCredential情報
→ めんどくさかったので test-terraformとかいうユーザを別途作成しました。
・S3 Bucketの作成
→ terraform-s3-state とかいう名前で作りましょう
・DynamoDBの作成
→ 名前はなんでも良いです。terraform-state-lock とかで作りましょう。
  プライマリキーを「LockID」としてください。それ以外では動きません。
作り終えたら、読み込み・書き込み容量ユニットは最低の1にしておいたほうが良いと思います。

※ S3とDynamoDBをterraformで管理はしないほうが良さげです。
どのみち、初回実行時に「そんなリソース無いんだけど!」ってTerraformが怒ります。

動くまで

今回はState Lockの話がメインなので作るリソースはなんでも良いです。
リージョンが関係無いRoute53を題材にします。

route53.tf
# 適当に1ゾーン作ります。

resource "aws_route53_zone" "test_zone" {
    name    =   "test.lockstate.com"
}
settings.tf
# ネーミングよくないですが、providerとbackendの設定します

provider "aws" {
    access_key = "ACCESS_KEY"
    private_key = "PRIVATE_KEY"
}

terraform {
    backend "s3" {
        bucket     = "terraform-s3-state"
        key        = "terraform.tfstate"
        region     = "s3とdynamoがいるregion"
        lock_table = "terraform-state-lock"
    }
}

これで、「該当のディレクトリに移動して」$ terraform plan してください。(怒られます。)

Backend reinitialization required. Please run "terraform init".
Reason: Initial configuration of the requested backend "s3"

The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.

とりあえず terraform init しろよと言われるので言われるがままに。

$ terraform init

ただし、以下の通り、認証情報がねーんだけど!って怒られる。なんやねん。

Initializing the backend...

Error configuring the backend "s3": No valid credential sources found for AWS Provider.
  Please see https://terraform.io/docs/providers/aws/index.html for more information on
  providing credentials for the AWS Provider

Please update the configuration in your Terraform files to fix this error
then run this command again.

ここらへんちゃんとよくわかってないですが、この問題の対処として2つありました。
A. settings.tfのbackendにaccess_key/secret_keyの2つを渡してCredential情報を平文で書く。
-> 変数でいいじゃん!と思ったんですが、変数で渡すと、Terraformがよしなにやる「前に」
  Backendが立ち上がるために違う方法で渡してくれ、と怒られました。
以下のようなメッセージ

% terraform init 
Initializing the backend...
Error loading backend config: 1 error(s) occurred:

* terraform.backend: configuration cannot contain interpolations

The backend configuration is loaded by Terraform extremely early, before
the core of Terraform can be initialized. This is necessary because the backend
dictates the behavior of that core. The core is what handles interpolation
processing. Because of this, interpolations cannot be used in backend
configuration.

If you'd like to parameterize backend configuration, we recommend using
partial configuration with the "-backend-config" flag to "terraform init".

B. 環境変数にaccess_key/secret_keyの2つを食わせる
  → 今はこちらを採用中。

init or plan実行時に、状況に応じて「ローカルのStateをS3にコピーするか / S3からStateファイルをローカルにコピーするか」聞かれるかもしれません。
初回(もしくはテスト)であればYes、すでに何回か実行しているような状況ではnoでいいんじゃないでしょうか。

これで、terraform plan (or apply)を実行すると
DynamoDBのLockDBに対して誰が使用しているか、のロック情報が書き込まれ
終わったタイミングでリリースされます。

ターミナルやシェルを複数立ち上げ、同じぐらいのタイミングで
terraform plan( or apply )を実行すると、ロックが成功できた奴以外はエラーで落とされます。
ただし、ロックがリリースされる前の実行中などにCtrl-Cなどで強制終了させるとロックが残り続けるので注意。
$ terraform force-unlock ID情報
で強制ロック解除できます。

今僕が解決できてない問題点

公式ドキュメントを見る限り、「terraform remote コマンドは terraform initコマンドに置き換えたよ!」と言っています。
また、backendを使用する、backendを書き換えた、などのタイミングに起因して terraform init を求められます。

が、terraform initは、「実行されたディレクトリに対して .terraform/terraform.tfstate」を吐き出します。
そしてそれが無いと怒ります。
まだ試せてはいませんが、以下のようなtfの配置をしていたりした場合
root配下でinitを実行しても、tfファイルがありませんと怒られる状況で
tfファイルがあるディレクトリまで移動しないとinitができない状態です。

$ terraform init ./route53/tf
とするとroot直下にtfファイルがコピーされる状況です。
なので、リソースごとに区切る、とか環境ごとに区切る、とかはうまくできていません。
別の見方をすれば、環境ごとに一つのディレクトリでリソース類は全部その中で管理しろよ!
というHashiCorpからのお達しなのか・・・?
これは、新しく実装されたEnvironmentを試せ、ということなんでしょうか。。。

と思ったらBackendConfigなるものがあるらしいのでそれを試してみよう。

root
├── settings
│   └── tf
│   └── settings.tf
├── route53
│   └── tf
│   └── route53.tf
└── ec2
└── tf
└── ec2.tf

Terraformとの付き合い方がよくわからない

続きを読む

Terraform v0.8からTerraform v0.9へのアップグレード S3の状態管理ファイルを移行方法

Terraformをアップグレードした際にS3に保存していた状態管理ファイルも移行する必要が
あったのでその作業メモです。

Terraformのアップグレード

https://www.terraform.io/downloads.html から環境に適したファイルを
ダウンロードして、解凍して作成されるterraformファイルをパスの通ったところに
配置します。
例えば以下のようにパスを通しておきます。

bash_profile
export PATH=$PATH:${HOME}/.terraform_0.9.3_darwin_amd64

状態管理ファイルの移行

Terraformのアップグレード自体は数分で終了しますが、planコマンドを実行すると以下の
メッセージが表示されます。

$ terraform plan
Deprecation warning: This environment is configured to use legacy remote state.
Remote state changed significantly in Terraform 0.9. Please update your remote
state configuration to use the new 'backend' settings. For now, Terraform
will continue to use your existing settings. Legacy remote state support
will be removed in Terraform 0.11.

You can find a guide for upgrading here:

https://www.terraform.io/docs/backends/legacy-0-8.html

There are warnings related to your configuration. If no errors occurred,
Terraform will continue despite these warnings. It is a good idea to resolve
these warnings in the near future.

どうやら、Terraformがv0.9になったタイミングで状態管理ファイルの管理方法が変更になったようです。
メッセージを見る限りではv0.11までは変更しなくても使えそうな感じですが、
https://www.terraform.io/docs/backends/legacy-0-8.html ではv0.10になったタイミングで
移行作業が必要になりそうなので、上記URLの手順に沿って新しい状態管理ファイルに移行してみます。

既存の状態管理ファイルの取得

最新の状態管理ファイルをremoteから取得します。

$terraform remote pull

ローカルの状態管理ファイルのバックアップ

.terraform/terraform.tfstateファイルをterraformのプロジェクトの外に置きます。

$cp .terraform/terraform.tfstate /tmp/

backendの設定の追加

backendの設定ファイルを作成します。
今までもS3に状態管理ファイルを置いていたので以下のURLを参考にしながら
backend.tfファイルを作成しました。
https://www.terraform.io/docs/backends/types/s3.html

backend.tf
terraform {
  backend "s3" {
    bucket = "terraform-state"
    key    = "tf"
    region = "ap-northeast-1"
  }
}

initコマンドの実行

backendの初期化コマンドを実行します。

$ terraform init -backend=true -force-copy  -lock=false
Initializing the backend...
New backend configuration detected with legacy remote state!

Terraform has detected that you're attempting to configure a new backend.
At the same time, legacy remote state configuration was found. Terraform will
first configure the new backend, and then ask if you'd like to migrate
your remote state to the new backend.




Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your environment. If you forget, other
commands will detect it and remind you to do so if necessary.

上記のようなメッセージが表示され、移行が無事に行われたようです。

terraform planで動作確認

最後にterraform planで動作確認をしてみます。
Deprecation warningが消えて通常の状態に戻りました。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
...

続きを読む

Let’s Encrypt を使った SSL 設定 for Amazon Linux

SSLのサイトを作ってみたくなった

ちょっとした無償サービスを作るにあたって、Let’s Encrypt を使った SSL を試しに使ってみたくてやってみました。
環境は、Amazon Linux。

手順

$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto
$ ./certbot-auto
(中略)
FATAL: Amazon Linux support is very experimental at present...
if you would like to work on improving it, please ensure you have backups
and then run this script again with the --debug flag!
Alternatively, you can install OS dependencies yourself and run this script
again with --no-bootstrap.

Amazon Linux は、–debug をつけろというのでつけました。

$ ./certbot-auto --debug
(中略)
The following errors were reported by the server:

Domain: XXX.XXX.jp
Type:   connection
Detail: Failed to connect to XXX.XXX.XXX.XXX:443 for tls-sni-01 challenge
(後略)

よくよく調べると以下に該当。

https://letsencrypt.jp/usage/dvsni-challenge-error.html

セキュリティグループに、443を指定するのを忘れていたので指定して、コンソソールからインスタンス再起動。

$ ./certbot-auto renew

インストールが終わったら、httpd を再起動。

$ sudo service httpd graceful

https でアクセスして完了を確認。

参考

https://letsencrypt.jp/
http://qiita.com/takahiko/items/a08895550727b95b6c36
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/SSL-on-an-instance.html#ssl_enable

続きを読む

lammaで始める等身大のAWS Lambda

(https://medium.com/@ayemos/lamma%E3%81%A7%E5%A7%8B%E3%82%81%E3%82%8B%E7%AD%89%E8%BA%AB%E5%A4%A7%E3%81%AEaws-lambda-d032addc5f4d より転載)

TL;DR

AWS Lambda

Amazon S3上にcsvファイルがアップロードされたら、その集計データをS3上の別のcsvファイルに保存したい

毎週金曜日の21:00に特定のタグがついたEc2インスタンスの過去数時間のCPUUtilization値をチェックして、アイドル状態にあるものは自動的にシャットダウンしたい

などなど、AWS上に構築されたインフラシステムにおいて、あるトリガーが発生したときに、ちょっとしたロジックと実装でもって処理を継続したい、というシチュエーションに最適なサービスがAWS Lambdaです。

AWS Lambdaで予め用意された計算環境(python, Node.jsなどの言語環境と、 AWS SDKの各言語実装などが含まれる)を利用することで、EC2上に計算環境をプロビジョンする手間が省けたり、実行回数とその長さに基づいた課金システムで、非常に安価に利用できるなどの利点があります。

しかし、計算機環境の構築と付き合わなくなった一方で、実装された関数のLambda上でのバージョンコントロールやデプロイ/ロールバックが面倒であるという現状があり、実際apexやlamveryなど、Lambdaのマネジメントツールが開発されています。

lamma

https://github.co/ayemos/lamma
ruby製のAWS Lambdaのマネジメントツール。

すでにあるツールとの差別化という意味で「IoT、ChatBotなどの目的でAWS Lambdaをなるべく手軽に今すぐ使いたい」という人をターゲットに作っているつもりです。

$ gem install lamma
$ lamma init my_function --runtime=python2.7
Looks like you didn't specified role arn for the function.
Do you want me to create default IAM role and configure it (my_function-lamma-role)? (y/n) y
I, [2017-04-06T22:28:53.952278 #5027]  INFO -- : Creating role my_function-lamma-role
I, [2017-04-06T22:28:54.863304 #5027]  INFO -- : Checking attached role policies for my_function-lamma-role
I, [2017-04-06T22:28:55.243400 #5027]  INFO -- : Could not find AWSLambdaBasicExecutionRole policy. Attatching.
I, [2017-04-06T22:28:55.243449 #5027]  INFO -- : Attaching minimal policy (AWSLambdaBasicExecutionRole) to my_function-lamma-role
I, [2017-04-06T22:28:55.466092 #5027]  INFO -- : Done
      create  /workspace/my_function/lambda_function.py
      create  /workspace/my_function/lamma.yml
I, [2017-04-06T22:28:55.468021 #5027]  INFO -- : Initializing git repo in /workspace/my_function
$ ls
total 16
drwxr-xr-x   5 yuichiro-someya  staff  170  4  6 22:28 ./
drwxr-xr-x   3 yuichiro-someya  staff  102  4  6 22:28 ../
drwxr-xr-x  10 yuichiro-someya  staff  340  4  6 22:28 .git/
-rw-r--r--   1 yuichiro-someya  staff  357  4  6 22:28 lambda_function.py
-rw-r--r--   1 yuichiro-someya  staff  207  4  6 22:28 lamma.yml
$ lamma deploy -a production
Function my_function doesn't seem to be exist on remote.
Do you want me to create it? (y/n) y
I, [2017-04-06T22:29:14.113561 #9209]  INFO -- : Creating new function my_function...
I, [2017-04-06T22:29:14.123244 #9209]  INFO -- : Saved the build: /var/folders/kd/_chs6sw13jvg01hlq6nd50k00000gp/T/lamma/05f92074cf833092343cd5414754efef
I, [2017-04-06T22:29:14.448724 #9209]  INFO -- : Created new function arn:aws:lambda:ap-northeast-1::function:my_function
I, [2017-04-06T22:29:14.448780 #9209]  INFO -- : Publishing...
I, [2017-04-06T22:29:14.605540 #9209]  INFO -- : Published $LATEST version as version 1 of funtion: arn:aws:lambda:ap-northeast-1::function:my_function:1
Function alias PRODUCTION doesn't seem to be exist on remote.
Do you want me to create it? (y/n) y

とまあこんな感じに、
shell-session
gem install lamma

して、

lamma init my_function --runtime=python2.7

して、 lambda_function.py 編集して

lamma deploy -a production

とすると、my_functionとPRODUCTIONエイリアスがクラウド上に生成されます。便利。

一方で、init時に生成されたlamma.ymlは次のように設定値がまるっとはいってます。

$ cat lamma.yml
function:
  name: my_function
  role_arn: arn:aws:iam:::role/my_function-lamma-role
  description: Hello, world.
  timeout: 3
  memory_size: 128
  runtime: python2.7
  region: ap-northeast-1

また、複数回デプロイした後は、

lamma rollback -a production

でエイリアスを指定して(デプロイごとに1回のみ)ロールバックすることが出来ます。
(deploy時にエイリアス毎に古いバージョンに向けたエイリアス(LAST)を用意することで実現している。)

lammaの紹介は以上。issueを上げてもらえると開発頑張れます。

続きを読む

Fluentdの正規表現を攻略する

formatのデバッグツール

Fluentdのログの正規表現が正しいかはFluentularで確認しならが操作できる。
http://fluentular.herokuapp.com/

スクリーンショット 2017-03-27 3.18.16.png

ただし、これはHerokuの無料プランで動いているらしく使用できないことも多いのでDockerからでもできる

$ docker pull tomohiro/fluentular
$ docker run -p 8080:8080 tomohiro/fluentular

作成方法

定義されたフォーマットを指定するか正規表現を利用します。

定義されたフォーマット一覧

fluentd側で予め10個の定義が用意されています。デフォルトのログの設定を利用している場合はこれらを使用することができると思われるので使用すると良いでしょう。

  • apache2
  • apache_error
  • nginx
  • syslog
  • tsv
  • csv
  • ltsv
  • json
  • none
  • multiline

ただし、注意点としてこれは常に通用するものではなく、該当する場合にのみ使用することができます。

apache2

format /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

apache_error

format /^\[[^ ]* (?<time>[^\]]*)\] \[(?<level>[^\]]*)\](?: \[pid (?<pid>[^\]]*)\])? \[client (?<client>[^\]]*)\] (?<message>.*)$/

nginx

format /^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z

syslog

format /^(?<time>[^ ]*\s*[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$/
time_format %b %d %H:%M:%S

tsv

format tsv
keys key1,key2,key3
time_key key2

csv

format csv
keys key1,key2,key3
time_key key3

ltsv

format ltsv
delimiter =               # Optional. ':' is used by default
time_key time_field_name

json

format json
time_key key3

none

format none
message_key my_message

multiline

format multiline
format_firstline /^Started/
format1 /Started (?<method>[^ ]+) "(?<path>[^"]+)" for (?<host>[^ ]+) at (?<time>[^ ]+ [^ ]+ [^ ]+)\n/
format2 /Processing by (?<controller>[^\u0023]+)\u0023(?<controller_method>[^ ]+) as (?<format>[^ ]+?)\n/
format3 /(  Parameters: (?<parameters>[^ ]+)\n)?/
format4 /  Rendered (?<template>[^ ]+) within (?<layout>.+) \([\d\.]+ms\)\n/
format5 /Completed (?<code>[^ ]+) [^ ]+ in (?<runtime>[\d\.]+)ms \(Views: (?<view_runtime>[\d\.]+)ms \| ActiveRecord: (?<ar_runtime>[\d\.]+)ms\)/

正規表現で記述する

名前付きキャプチャ

正規表現から該当箇所と通り出したいときに()で括ったところは$1,$2,$3,…のように取り出しますが、名前付きキャプチャを利用することで取り出しの部分に名前をつけることができます。

(?<name>pattern)

http://qiita.com/jnchito/items/cceb669cb06fc044f411

これだけは抑えておきたい正規表現

^ log $

^以降にログのパターンを書き始め、$で終端。

[pattern]

patternの文字列

[^pattern]

pattarn以外の文字列

\S

空白文字(半角スペース、\t、\n、\r、\f)以外すべて

.

任意の1文字

*

0文字以上

+

1文字以上

\special

エスケープ文字の役割を担う文字を文字そのものとして表現したいときは\を文字の前に付ける
[,],”, 空白

(?: pattern )

パターンのグループを表す。

( pattern1 | pattern2 )

pattern1またはpattern2

実際に正規表現を組み立てる

Apacheのhttpd.confにおけるログの設定は以下の通りとなります。

ErrorLog "logs/error_log"
LogLevel warn

<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %q" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b %q" common

    <IfModule logio_module>
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O %q" combinedio
    </IfModule>
    CustomLog "logs/access_log" combined
</IfModule>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

LogFormatの各項目の詳細は以下のURLを参照する。
https://httpd.apache.org/docs/2.4/ja/mod/mod_log_config.html

実際に吐き出されたアクセスログは以下の通りとなります。

10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"
10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1
10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"

3つめはELBによるヘルスチェックが記録されています。4つめはリクエストがタイムアウトされた様子を表しています。

10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"

上記のようなApacheのアクセスログを正規表現します。

正規表現 作成手順

1.最初に1つめから考えていきます。
最初は10.0.0.85から始まり、IPアドレスを表します。厳密に考えるとプライベートアドレスなどの範囲を考えてXXX.XXX.XXX.XXXの表記となるような表現となりますが、ログの吐き出しは吐き出しが限定されるので厳密にやる必要はなく、空白以外の文字が0文字以上となる表現でOK。空白以外は[^ ]で表されこれが0文字以上になるので[~ ]*で表される。これに名前付けをするために(?<host>[^ ]*)となる。最後にこのホスト名から始まるので^を先頭につけて、^(?<host>[^ ]*)となる。

2.次に空白を挟み、-が2回繰り返される。最初のremotelogが存在しないことによるもの。次の-はuserが存在しないことによるもの。(?<remotelog>[^ ]*) (?<user>[^ ]*)で表されます。空白に注意。以降空白の説明は省略します。

3.[03/Feb/2017:11:53:21 +0900]は時間を表します。時間については別途time_formatで指定します。[, ]ですが、これは特殊文字なので\でエスケープして使うのでこれは\[ time \]で表現します。次にtimeに相当するものですが、時間中には]は含まれないのでこれ以外の任意の文字列なので]は除外するためにこれ以外の文字が0文字以上となるので[^\]*を組み合わせて[^\]*で表現します。これに名前付けをするために(?<time>[^\]]*)となり、全て合わせて\[(?<time>[^\]]*)\]となります。
time_formatですが03/Feb/2017:11:53:21 +0900なのでそれぞれの日付表記に対応するフォーマットを使用して%d/%b/%Y:%H:%M:%S %zとなります。

4."GET /requests/form/34 HTTP/1.1"ですが、これは"で囲まれた要素に対して、GET/requests/form/34HTTP/1.1の要素から成ります。最初に"で囲まれているので"pattern"のようにします。
次にpatternについてですが、
GET /requests/form/34 HTTP/1.1ですが、GET(?<method>\S+)、このあとに空白が来て続いてくるので+/requests/form/34(?<path>[^ ]*)、空白が来るので、+HTTP/1.1が来るので\S*とします。

以下、同様にして組み立てていくと例えば、以下のような正規表現を作成することができます。

^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$

作成したものとFluentularの出力の見本は以下のURLとなります。
http://fluentular.herokuapp.com/parse?regexp=%5E%28%3F%3Chost%3E%5B%5E+%5D*%29+%28%3F%3Cremotelog%3E%5B%5E+%5D*%29+%28%3F%3Cuser%3E%5B%5E+%5D*%29+%5C%5B%28%3F%3Ctime%3E%5B%5E%5C%5D%5D*%29%5C%5D+%22%28%3F%3Cmethod%3E%5CS%2B%29%28%3F%3A+%2B%28%3F%3Cpath%3E%5B%5E+%5D*%29+%2B%5CS*%29%3F%22+%28%3F%3Cstatus%3E%5B%5E+%5D*%29+%28%3F%3Csize%3E%5B%5E+%5D*%29+%22%28%3F%3Creferer%3E%5B%5E%5C%22%5D*%29%22+%22%28%3F%3Cagent%3E.*%29%22%5C+*%28%3F%3Cquerystring%3E%5B%5E%5C%22%5D*%29%24&input=10.0.0.85+-+-+%5B03%2FFeb%2F2017%3A11%3A53%3A21+%2B0900%5D+%22GET+%2Frequests%2Fform%2F34+HTTP%2F1.1%22+200+95409+%22https%3A%2F%2Fsample.jp%2Fsample%3Fstation%3D%25E4%25B8%25AD%25E9%2587%258E%25E6%2596%25B0%25E6%25A9%258B%22+%22Mozilla%2F5.0+%28iPhone%3B+CPU+iPhone+OS+10_2_1+like+Mac+OS+X%29+AppleWebKit%2F602.4.6+%28KHTML%2C+like+Gecko%29+Mobile%2F14D27%22&time_format=%25d%2F%25b%2F%25Y%3A%25H%3A%25M%3A%25S+%25z

正しく作成されている場合、以下のように名前付きラベルに対応する値の内容が表示される。

スクリーンショット 2017-03-27 3.17.58.png

サンプル

アクセスログ

ログ

10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"


10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1

10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"

10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"

正規表現

^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$
%d/%b/%Y:%H:%M:%S %z

エラーログ

ログ

[Sun Feb 19 03:08:02.063361 2017] [auth_digest:notice] [pid 26649] AH01757: generating secret for digest authentication ...
[Sun Feb 19 03:08:02.065290 2017] [lbmethod_heartbeat:notice] [pid 26649] AH02282: No slotmem from mod_heartmonitor
[Sun Feb 19 03:08:02.203091 2017] [mpm_prefork:notice] [pid 26649] AH00163: Apache/2.4.23 (Amazon) PHP/5.6.22 configured -- resuming normal operations
[Sun Feb 19 03:08:02.203473 2017] [core:notice] [pid 26649] AH00094: Command line: '/usr/sbin/httpd'
[Mon Feb 20 23:34:38.774239 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Mon Feb 20 23:34:39.373401 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Wed Feb 22 10:12:12.180706 2017] [:error] [pid 17757] [client 10.0.1.172:10406] CSRF state token does not match one provided.
[Wed Feb 22 16:45:54.469282 2017] [:error] [pid 27395] [client 10.0.0.85:59532] CSRF state token does not match one provided.
[Wed Feb 22 16:48:53.326150 2017] [:error] [pid 27433] [client 10.0.0.85:59628] PHP Fatal error:  Call to a member function is_in_area() on null in /var/www/sample/releases/20170222074811/fuel/app/classes/controller/api/requests.php on line 116, referer: https://sample.jp/requests/confirm?judge=2
~

正規表現

^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$
%a %b %d %H:%M:%S.%N %Y

td-agent.confの設定例

td-agent.conf
<source>
  type tail
  format /^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$/
  time_format %d/%b/%Y:%H:%M:%S %z
  path /var/log/httpd/access_log
  tag sample.service.access.log.aggregate
  pos_file /tmp/fluent/logpos/access_log.pos
</source>

<source>
  type tail
  format /^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$/
  time_format %a %b %d %H:%M:%S.%N %Y
  path /var/log/httpd/error_log
  tag sample.service.error.log.aggregate
  pos_file /tmp/fluent/logpos/error_log.pos
</source>

<match **>
  type forward

  <server>
    name fluent01
    host 10.0.0.31
    port 24224
  </server>
</match>
td-agent.conf
<source>
  type forward
  port 24224
</source>

<match sample.service.access.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name access_log
    logstash_format true
    logstash_prefix sample-service-log-access
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path access-logs/
    buffer_path /tmp/fluent/s3/access/
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

<match sample.service.error.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name error_log
    logstash_format true
    logstash_prefix sample-service-log-error
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path error-logs/
    buffer_path /tmp/fluent/s3/error
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

(参照)権限設定

$ sudo chgrp td-agent /var/log/httpd/
$ sudo chgrp td-agent /var/log/messages

$ sudo chmod g+rx /var/log/httpd/
$ sudo chmod g+rx /var/log/messages

以上

Appendix

td-agent.conf パラメータ一覧

type
tag
path
exclude_path
refresh_interval
limit_recently_modified
skip_refresh_on_startup
read_from_head
read_lines_limit
multiline_flush_interval
pos_file
format
time_format
path_key
rotate_wait
enable_watch_timer

参考

http://docs.fluentd.org/v0.12/articles/in_tail
http://www.tachibanakikaku.com/2013/12/fluentdformat.html

続きを読む