InfluxDB と Grafana で可視化して Puppeteer で PDF を生成

はじめに

計測データのグラフや表をまとめたいことが 何度かあったんですがちょうどいいツールを見つけられていませんでした。

エクセルでグラフを作ったりgnuplotでグラフを作ったりもできますが 見た目をかっこよくまとめるのは慣れないと難しそうに感じました。

Grafana は前に目にしたことがあったんですが 常時計測したデータの可視化用でちょっと使いたい用途には合わないかなと思ってました。

ただ docker-compose を使って CLI ツールのようにまとめることで やりたい用途にも使えそうだなと思い試してみました。

実験に使ったリポジトリはここに置いています。

github.com

やり方

InfluxDB, Grafana, Puppeteer のコンテナを docker-compose で連携させるというやり方です。 なんでも docker でやってみたくなっている感じが少しあります。 色々なツールを使っていて全部直接入れるのはちょっと気が引けるし、環境構築も大変なので実際こういう用途では便利だと思います。

InfluxDB

www.influxdata.com

InfluxDB は時系列データを扱うデータベースで Go 言語で書かれているようです。 Grafana とセットでよく使われているのを目にする気がします。 データの有効期限を設定できるようで常時計測しているデータの保存などに使うことが多いようです。

InfluxDB Line Protocol reference | InfluxData Documentation

Writing data with the HTTP API | InfluxData Documentation

Line Protocol の書き方と curl での API 呼び出し方の例を真似してデータを取り込むことができました。

公式のイメージが Docker Hub にありました。

https://hub.docker.com/r/library/influxdb/

Grafana

grafana.com

Grafana はデータの可視化用のウェブサーバーで GUIダッシュボードに色々なグラフを表示したりできます。 InfluxDB 以外にも色々なデータソースを扱うことができるようです。 Grafana もサーバー部分は Go 言語のようです。

Grafana も Docker Hub にイメージがありました。

https://hub.docker.com/r/grafana/grafana/

Puppeteer

github.com

Puppeteer は Chrome の開発用 API を利用できる Node ライブラリです。

ウェブページの PDF を作るのに他のツールも少し使ってみたのですが

表示が終わるのを待つなど細かい制御をするには Puppeteer を使ったほうがうまく行きそうだったので使ってみることにしました。

下のような感じで PDF の保存ができました。

const puppeteer = require('puppeteer');
const url = process.argv[2];
const output_path = process.argv[3];
const width = 1080;
const height = 1920;

(async() => {
    const browser = await puppeteer.launch({
        headless: true,
        executablePath: 'chromium-browser',
        args: ['--no-sandbox', '--disable-setuid-sandbox']
    });

    const page = await browser.newPage();
    await page.setExtraHTTPHeaders({
        Authorization: 'Basic ' + new Buffer('admin:admin').toString('base64')
    });

    await page.setViewport({
        width: width,
        height: height,
        isMobile: false
    });

    await page.goto(url, {waitUntil: 'networkidle2'});

    await page.pdf({
        path: output_path,
        width: width + 'px',
        height: height + 'px',
    });

    await browser.close();
})();

puppeteer/troubleshooting.md at master · GoogleChrome/puppeteer · GitHub

ここの Dockerfile の書き方を参考にイメージを作りました。

docker-compose 設定

docker-compose 設定は下のようになりました。 Grafana のダッシュボードとデータソースの設定は /etc/grafana/provisioning/ 以下に設定ファイルを置くと反映されるので 設定ファイルを置いたディレクトリをマウントするようにしています。

version: "3"
services:
  influxdb:
    image: influxdb:1.6.2
    ports:
      - "8086:8086"
  grafana:
    image: grafana/grafana:5.2.4
    ports:
      - "3000:3000"
    volumes:
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
    depends_on:
      - influxdb
  puppeteer:
    build:
      context: .
      dockerfile: Dockerfile.puppeteer
    volumes:
      - ./volume:/volume
    entrypoint:
      - ./run-puppeteer.sh
    depends_on:
      - grafana
      - influxdb

生成した PDF

一連の流れを試してみるのが目的だったので見栄えしないグラフ一つしかありませんが下のような感じで生成できました。 Grafana のダッシュボードは kiosk=true の GET パラメーターを付けるとメニューを非表示にできるようです。 やろうと思えば Puppeteer で少し見た目を加工するなどもできるのかなと思います。

f:id:tkaaad97:20180919004337p:plain

まとめ

InfluxDB にデータを取り込み Grafana で表示して Puppeteer で PDF 作成という一連の流れをdocker-compose で CLI 化することができました。 はじめて使うものが多かったので少し時間がかかりましたが今後も使う機会がありそうなので少しは慣れることができてよかったと思います。

Puppeteer で PDF 生成する前に Grafana のサーバーが立ち上がるのを少し待つ必要があるのを今回は10秒スリープするといういい加減な方法で対応しました。 こういう用途には wait-for-it や dockerise が使えるらしいです。

Compose の起動順番を制御 — Docker-docs-ja 17.06.Beta ドキュメント

Docker 内で GLFW のプログラムを動かすサンプル

はじめに

Docker の利用は CLI やサーバーサイドの利用が主なのかなと思っていましたが GUI のプログラムもある程度は動かせるようです。

軽いゲームなどを作る場合も使えるかもしれません。

実験用に GLFW のデモアプリを動かす Docker イメージを作ってみました。

Dockerfile などはここのリポジトリに置きました。

github.com

マウントしたり特権コンテナにしたりという設定があるため docker-compose を使っています。

Docker での GUI

以下のページなどを参考にさせてもらいました。

docker/Tutorials/GUI - ROS Wiki

https://unskilled.site/dockerコンテナの中でguiアプリケーションを起動させる/

https://tail-island.github.io/programming/2017/07/11/docker-for-development-container-on-linux.htmltail-island.github.io

medium.com

kunst1080.hatenablog.com

いくつか方法があるようです。

  • X11 ソケットを共有する
    • ホストの X サーバーとコンテナの X クライアントがソケット通信
  • コンテナで X サーバーを動かす
    • コンテナで X サーバーも X クライアントも動かす
  • ssh を使った X11 forwarding
    • コンテナで ssh サーバーを動かす
  • VNC を使う
    • コンテナで VNC サーバーを動かす

ホスト側も X ウィンドウシステムの場合は上の二つの方法が使えそうです。

自分ではホストも Ubuntu などで動かすと思うので一番簡単そうな X11 ソケットを共有する方法を試してみました。

Docker イメージ

前に作った haskell 用のイメージを元にして作っています。

開発用として使う想定なので他の言語ではそれぞれの開発環境が入ったイメージを元にして必要なパッケージをいれることになると思います。

FROM tkaaad97/haskell-docker:8.2.2

# install dev tools
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        make \
        pkgconf \
        xz-utils \
        xorg-dev \
        libgl1-mesa-dev \
        libglu1-mesa-dev \
        libxrandr-dev \
        libxinerama-dev \
        libxcursor-dev \
        libxi-dev \
        libxxf86vm-dev

WORKDIR /app/

ENTRYPOINT []
CMD ["bash"]

下は docker-compose 設定ファイルです。

/tmp/.X11-unix:/tmp/.X11-unix:rw の部分で X11 ソケットをマウントして共有しています。

command で GLFW-b-demo のパッケージをインストールして起動しています。

version: "2"
services:
  app:
    build: .
    command: bash -c 'stack install GLFW-b-demo && /root/.local/bin/GLFW-b-demo'
    working_dir: /app
    privileged: true
    environment:
      - DISPLAY=${DISPLAY}
    volumes:
      - .:/app
      - .stack:/root/.stack
      - /tmp/.X11-unix:/tmp/.X11-unix:rw

xhost

コンテナとホストで X11 ソケット共有して通信する必要があるのですが コンテナ上のユーザーとホスト側で接続しているユーザーが 違う場合にそのままでは通信が許可されません。 この場合に xhost を使って許可することができます。

xhost local:

これを実行するとローカルの接続であればユーザーが違っても許可されます。

ただしあまり安全な方法では無いらしくコンテナのユーザーとホストのユーザーを合わせる方法なども紹介されていました。

スクリーンショット

以下のようにデモアプリが表示されました。

f:id:tkaaad97:20180902224447p:plain

Amazon Aurora オートスケーリングでも ScheduledAction が使えた

はじめに

オートスケーリング機能でよく使われるのは EC2 のオートスケーリング機能だと思います。 ポリシーを設定すると自動でインスタンスを増減させてスケールアウト、スケールインできます。 またスケジュールされたアクションも設定することができて、これで最大、最小、希望のインスタンス数を変えることができます。 時間が予定されるイベントなどではスケジュールされたアクションを使うことがあります。

Aurora では Read Replica のオートスケーリング機能があります。 CPU 使用率やデータベース接続数でのオートスケーリングポリシーを設定して自動で Read Replica 数を増減することができます。 Aurora Read Replica でもスケジュールされたアクションが使えるようで便利だと思うんですが、これについて書いてる記事があまりなかったので書いておきます。

使えそうなケース

時間が決まっていて負荷がかかることがわかっているイベントであらかじめ Read Replica 数を増やしておくというのがあると思います。

docs.aws.amazon.com

cron 式でのスケジュール設定もできるので毎週のスケジュールや毎日のスケジュールなどで Read Replica 数を変えたりもできます。 アクセスが少ない時間帯に Read Replica 数を減らしておくなどしてコスト削減できるかもしれません。

やり方

  • スケーラブルターゲットとオートスケーリングポリシー作成
  • ScheduledAction 作成

という感じでできました。

オートスケーリングポリシー作成は AWS のウェブ上からもできたんですが ScheduledAction は RDS の画面からは表示も編集もできませんでした。 今のところは CLIAPI などを使ってやる必要があるようです。 もしかするとオートスケーリングのサービスの方から設定したりできるのかもしれないんですが ap-northeast-1 リージョンではまだ提供されていないので使えませんでした。

スケーラブルターゲットとオートスケーリングポリシー作成はこちらのドキュメントの方法でできました。

docs.aws.amazon.com

ScheduledAction の設定は put-scheduled-action で行います。 autoscaling サブコマンドではなく application-autoscaling サブコマンドを使います。

put-scheduled-action — AWS CLI 1.15.76 Command Reference

aws application-autoscaling put-scheduled-action \
  --service-namespace rds \
  --schedule at(2018-12-31T20:00:00) \
  --scheduled-action-name '{{schedule_name}}' \
  --resource-id 'cluster:{{db_cluster}}' \
  --scalable-dimension rds:cluster:ReadReplicaCount \
  --scalable-target-action 'MinCapacity=2,MaxCapacity=2'

describe-scheduled-actions — AWS CLI 1.15.76 Command Reference

describe-scheduled-actions で登録されているスケジュールが取得できます。

aws application-autoscaling describe-scheduled-actions --service-namespace rds --resource-id 'cluster:{{db_cluster}}'

参考

docs.aws.amazon.com qiita.com dev.classmethod.jp

VS Code でも intero が使えてなかなか便利

はじめに

前に haskell-ide-engine について書いたんですがビルドが面倒などの理由であまり使わなくなりました。 intero は Emacs だけかと思ってたんですがいつの間にか VS Code や neovim でも使えるようになってたため導入してしばらく使ってみています。 intero は stack で簡単に入れられて自分でビルドする必要が無いというのがとても良いです。 VS Code + intero は docker 内の ghc や stack を使っている場合でも導入できました。

intero (haskero)

intero は haskell 開発用のツールでいくつかのエディタや IDE から利用できます。

github.com

VS Code 用の intero プラグインが haskero です。

gitlab.com

他に Haskelly というプラグインもあるんですが haskero.intero.stackPath に相当する設定がなくて docker 環境で使う場合は haskero の方が使える可能性ありそうということで haskero を選びました。

下のページで gif で動作している様子が見られるのでわかりやすいと思います。

marketplace.visualstudio.com

マウスホバーで変数の型を表示したり、ファイルを保存した時にエラーや警告を教えてくれます。 定義にジャンプしたり補完したりもできます。

docker 環境の場合

docker 環境を使っていない場合は多分すごく簡単で特にはまるところなく導入できると思います。 docker 環境の場合は前に haskell-ide-engine を使ったときと同じような方法で導入できました。

ちょっと導入してから時間が経っているためうろ覚えですが下の感じでできたと思います。

  • stack install intero でプロジェクト内に intero をインストール
  • プロジェクトのディレクトリをホスト側と同じパスでマウントする
  • stack 設定で allow-different-user: true にしておく
  • VS Code ワークスペース設定で haskero.intero.stackPath で docker 内の stack を実行するスクリプトを設定する
  • VS Code ワークスペース設定で "haskero.intero.ghciOptions": [] に設定する (何かエラー出てしまったため)

haskero.intero.stackPath で設定したスクリプトは現在 docker-compose を使っていたため下のような感じにしました。

#!/bin/bash
docker-compose run --rm --no-deps app stack $@

VS Code でも使えたその他の haskell 関連ツール

intero の他にもいくつか便利なツールが使えました。

現在は hlint と stylish-haskellプラグインを入れて使っています。

hlint と stylish-haskell は基本的に開いているファイルしか見ないようなので docker 環境でもあまり関係なく使えそうです。 また両方とも github で実行ファイルも配布されているので無理に docker 環境に入れなくても手軽に導入できると思います。

hlint は以下のプラグインを使いました。

marketplace.visualstudio.com

GitHub - hoovercj/vscode-haskell-linter: An extension to bring hlint to vscode

GitHub - ndmitchell/hlint: Haskell source code suggestions

stylish-haskell は下のプラグインを使いました。

marketplace.visualstudio.com

GitHub - vigoo/stylish-haskell-vscode: stylish-haskell support for VS code

GitHub - jaspervdj/stylish-haskell: Haskell code prettifier

Tiled フォーマットの覚え書き

Tiled

ゲームを作る時に使ってみたいと思って前に Tiled について調べたことについて書いて置きます。

Tiled はアクションゲームや RPG などのマップをタイルで構成するためのツールです。

Tiled Map Editor | A flexible level editor

ゲームのマップ数が少ない場合はマップごとに一枚の画像を使うことも可能だと思いますが データ量を抑えたり、マップを動的に生成したりなどする場合は Tiled のようにタイルに分割して描画することになると思います。

ソースも公開されていて、色々な言語でのライブラリも作られていて利用しやすそうです。

GitHub - bjorn/tiled: A flexible level editor

Libraries and Frameworks — Tiled 1.1.0 documentation

フォーマット

Tiled で使われるのは基本的にはマップファイルとタイルセットファイルそれからタイルセットの画像ファイルのようです。

JSONXML のフォーマットがあります。

JSON Map Format — Tiled 1.1.0 documentation

TMX Map Format — Tiled 1.1.0 documentation

マップファイルとタイルセットファイルは普通分けて使われるようです。

ソースコードにいくつか例が含まれていました。

tiled/examples at master · bjorn/tiled · GitHub

例は XML のものしか無いんですが、これを読み込んで JSON にエクスポートすることもできました。 JSON の方が慣れているので使うときは JSON 使おうかと考えています。

tiled/examples/perspective_walls.tmx を JSON エクスポートすると下のようになりました。 (長いため一部省略しています)

{"height":32,
 "infinite":false,
 "layers":[
        {
         "data":[0, 0, 0, 0, 0...],
         "height":32,
         "name":"Walls",
         "opacity":1,
         "type":"tilelayer",
         "visible":true,
         "width":32,
         "x":0,
         "y":0
        }],
 "nextobjectid":1,
 "orientation":"orthogonal",
 "renderorder":"right-down",
 "tiledversion":"1.1.5",
 "tileheight":31,
 "tilesets":[
        {
         "firstgid":1,
         "source":"perspective_walls.tsx"
        }],
 "tilewidth":31,
 "type":"map",
 "version":1,
 "width":32
}

タイルセットは下のようになりました。

{"columns":4,
 "image":"perspective_walls.png",
 "imageheight":256,
 "imagewidth":256,
 "margin":0,
 "name":"perspective_walls",
 "spacing":0,
 "tilecount":16,
 "tileheight":64,
 "tileoffset":
    {
     "x":-32,
     "y":0
    },
 "tileproperties":
    {
     "13":
        {
         "door":"true"
        },
     "14":
        {
         "door":"true"
        },
     "15":
        {
         "pickup":"true"
        }
    },
 "tilepropertytypes":
    {
     "13":
        {
         "door":"string"
        },
     "14":
        {
         "door":"string"
        },
     "15":
        {
         "pickup":"string"
        }
    },
 "tilewidth":64,
 "type":"tileset"
}

Map

http://doc.mapeditor.org/en/stable/reference/json-map-format/#map

Map はゲームのマップでどのようにタイルを配置するか、それからマップのサイズなどが定義されています。

Tile Layer

http://doc.mapeditor.org/en/stable/reference/json-map-format/#layer

Tile Layer はマップ中のタイル配置です。 data のフィールドで GID で各マスに配置するタイルを指定します。 data のマスは行優先順 (raw-major order) で左上から順番になっています。 GID は tileset の firstgid を使ってタイルセット中の各タイル定義に固有に振られた global id になります。 GID が0は何もないという意味になるようです。 タイルセットを複数使う場合は GID が被らないように firstgid 指定する必要があると思います。

perspective_walls の例を見るとタイルのサイズとオフセットの関係は下の図のようになっているようです。

f:id:tkaaad97:20180708201252p:plain

かなり分かりにくいですがマスやタイルの順番は左上原点になっているのに オフセットの座標計算については左下原点となっているようでした。

関連すると思われる issue がありました。

Tile object Y coordinate not saved properly · Issue #386 · bjorn/tiled · GitHub

Object Layer

Tile Layer の他に Object Layer というのもあるようです。 これはおそらく描画には関係ないデータで床や壁の当たり判定やリスポーン位置などの定義に使われるのではないかと思います。

Tileset

Tileset は各タイルの定義で画像のサイズやオフセットなどです。 Tileset に使う画像は全て同じサイズで、行優先順 (row-major order) で並べる想定になっています。

Tiled の描画方法

ライブラリが色々な言語で作られていて利用できる場合もありますが、パーサーしかなかったり描画は自前で書く場合もあると思います。 基本的にはタイルの画像がそれぞれのマスに描画できればいいのでそんなに難しくは無いと思います。

OpenGL では下のような流れでの描画になると思います。

  • タイルセット画像を読み込みテクスチャを作成する
  • GID からテクスチャ座標を取得できるようにしておく
  • 描画するマス座標とテクスチャ座標、オフセットから頂点バッファを作る
  • 三角形にテクスチャを貼り付けるシェーダで描画する

IAM ポリシーでのリソースに対する設定について

はじめに

AWS IAM の機能を使うとユーザーや EC2 に付けるロールの権限を細かく設定できて不慮の事故などを防ぐことができます。

IAM ポリシーの設定をリソースに対して行うと例えば本番環境へのリソースを間違って削除したり更新する事故を予防できると思います。

開発環境で色々な機能を扱うときに本番リソースに影響がないかなど不安が出てきて IAM 設定方法を調べて見たためまとめておきます。

権限の評価方法

IAM JSON ポリシーの評価論理 - AWS Identity and Access Management

こちらに書かれていました。

以下のような流れで評価されるようです。

  • 一つでも Deny ポリシーが当てはまると explicit deny
  • 一つでも Allow ポリシーが当てはまると Allow
  • なにも無いと Deny

リソースに対する設定が難しい

リソースに対するポリシーの付け方は難しくなっていると感じました。

例えば EC2 だと関連する IAM 権限についてはこちらに書かれていますが

Actions, Resources, and Condition Keys for Amazon EC2 - AWS Identity and Access Management

これを見てもリソースは インスタンスやイメージ、サブネット、VPC など様々あって しかもアクションによって関連するリソースも違うためどうやって設定すればいいのか全然わかりません。

AWS IAM Policy Summaries Now Help You Identify Errors and Correct Permissions in Your IAM Policies | AWS Security Blog

この記事によくある間違った設定方法が書かれていましたが、このように書きたいというのはよく分かります。

This policy does not work. Do not copy.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ThisPolicyDoesNotGrantAllListandGetActions",
            "Effect": "Allow",
            "Action": ["s3:List*",
                       "s3:GetObject"],
            "Resource": ["arn:aws:s3:::HumanResources"]
        }
    ]
}

これがうまく動かないのはリソースが関連しないIAMアクションが有効にならないからです。

記事の後半の方に正しい設定方法について書かれています。 リソースが関係ないIAMアクションについては別にしてステートメントにレコードを追加するというやり方です。

しかしリソースからアクションを絞り込んで設定するというのが手動で簡単にできるとは思えません。

アクションを一つ一つ見て設定していくことは時間をかければできると思いますが、メンテナンスすることは難しい気がします。 複雑なポリシーを書いてそれが正しいことを確認することもかなり難しいと思います。

しばらく悩んでリソースについての権限は Deny のポリシーを活用するといいのではないかという考えが浮かびました。

例えば EC2 インスタンスに Environment のタグを付けて、これが development でないインスタンスへの全てのアクションを禁止するには下のように書けます。 Condition の Null の条件はインスタンスの起動時などタグがついてない場合に禁止にならないようにするために付けました。 これだとアクションを一つ一つ書かなくてすみます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": [
                "ec2:*"
            ],
            "Resource": [
                "arn:aws:ec2:*:accountid:instance/*"
            ],
            "Condition": {
                "StringNotEquals": {
                    "ec2:ResourceTag/Environment": "development"
                },
                "Null": {
                    "ec2:ResourceTag/Environment": "false"
                }
            }
        }
    ]
}

これだけだと Deny だけで何もできないのでユーザーまたはロールに別に AmazonEC2FullAccess のポリシーをつけます。 FullAccess だとつけすぎかもしれませんがその場合は許可用のポリシーを用意するなどします。 タグを付ける部分の権限もポリシーに入れる方がいいかもしれません。

意図したように権限設定できていることを aws cli で確認してみました。 ec2 に関するアクションは --dry-run というオプションを使うと簡単に試すことができます。

上のは development 以外禁止というポリシーですが逆に production のみ禁止なども書けます。 またサブネットや VPC のリソースについて Deny で同様に設定を作るということができます。

まとめ

IAM でのリソースに関する権限設定の方法について検討してみました。 個人的には Deny を使った設定を活用すると設定しやすいのではという結論になりました。 Allow のみで細かい設定を作る場合は手動でポリシーを作成するのは難しいので、 設定項目を管理可能なパラメーターで入力してポリシーを生成するようなツールを作ったりする方法もあるかなと思いました。

S3 Select の機能を awscli から使ってみた

S3 Select

S3 Select は去年の AWS re:Invent で話に上がっていて少し気になっていたものの忘れていました。

S3 Select と Glacier Select – オブジェクトのサブセットを取得 | Amazon Web Services ブログ

S3 Select(Preview)を試してみました #reinvent | Developers.IO

もう二ヶ月ぐらい前ですが今年の四月に一般公開で使えるようになっていました。

Amazon S3 Select が一般公開

S3 Select は S3 に保存しているログなどから SQL で必要なデータを絞り込んで検索したり、集計したりに使えるようです。

ログの量が膨大で全てダウンロードして処理すると時間がかかってしまうようなケースで使えるのかなと思います。

この S3 Select を awscli から少し試してみました。

Amazon Athena

似たようなサービスとして Amazon Athena というものもあります。

Amazon Athena (サーバーレスのインタラクティブなクエリサービス) | AWS

Athena は S3 を内部で使っている別のサービスで、機能としては Athena の方が色々と充実していてできることが多いのかなと思います。

ただ S3 Select は S3 に組み込まれているので単純な処理を素早くすませたいという場合には使えるのではないかと思います。

awscli からの利用

select-object-content というのが S3 Select の機能にあたるようです。

新し目の機能なので awscli 更新しないと使えない可能性があります。

CSVJSON に対応していて、GZIP 圧縮されているファイルも対応されています。

select-object-content — AWS CLI 1.15.31 Command Reference

API もありました。

SELECT Object Content - Amazon Simple Storage Service

SQL の仕様はここにありました。SQL といってもかなり機能が限られていて、JOINGROUP BY などは使えません。

Amazon S3 Select および Amazon Glacier Select の SQL リファレンス - Amazon Simple Storage Service

単純な例: ログの検索

CSVファイルから ERROR という文字列を含む行だけを絞り込んで取得するという例です。

下のコマンドで実行できました。

aws s3api select-object-content \
    --bucket=[バケット名] --key=[オブジェクトキー] \
    --input-serialization '{"CSV":{"QuoteEscapeCharacter":"\\"}}' \
    --output-serialization '{"CSV":{"QuoteFields":"ALWAYS","QuoteEscapeCharacter":"\\"}}' \
    --expression "select * from s3object s where _3 LIKE '%ERROR%'" \
    --expression-type SQL tmp.txt

そこまで複雑では無いもののこの書き方を覚えて毎回打ち込むというのはつらいので 実際使うときはシェルスクリプトなどを書いてフォーマットなどは決め打ちで使うのが楽かなと思います。

S3 に置いたテストデータ

1,2018-06-04T10:00:01,"hello, world"
2,2018-06-04T10:00:02,"log1"
3,2018-06-04T10:00:03,"log2"
4,2018-06-04T10:00:04,"aaa"
5,2018-06-04T10:00:05,"bbb"
6,2018-06-04T10:00:06,"ERROR: unknown error"
7,2018-06-04T10:00:07,"ERROR: timeout"
8,2018-06-04T10:00:08,"INFO: xxx"
9,2018-06-04T10:00:09,"ERROR: some error"
10,2018-06-04T10:00:10,"\"hello, world\""

取得された結果

"6","2018-06-04T10:00:06","ERROR: unknown error"
"7","2018-06-04T10:00:07","ERROR: timeout"
"9","2018-06-04T10:00:09","ERROR: some error"

集計関数

GROUP BY は無いですが AVG, COUNT, MAX, MIN, SUM の集計関数がありました。where で絞り込んだ範囲全体で集計するようです。

集計関数 (Amazon S3 Select のみ) - Amazon Simple Storage Service

どうも集計関数を使う場合は数値型に cast しないとエラーになってしまうようでした。下の記事がとても参考になりました。

Amazon S3 Select で扱う JSON データの形式と Type 指定について | Developers.IO

複数ファイルから検索する例

複数ファイルから検索するには list-objects-v2 などと組み合わせてパイプでつなげれば一応はできそうです。 ただこれもかなり長くなるのでスクリプトなどを書いておくか python などから使うほうが簡単かもしれません。

下のような感じでスクリプトを書いてみました。

#!/bin/bash

if [ $# -lt 3 ]; then
        echo "$0 [BUCKET] [KEY_PREFIX] [OUTPATH]"
        exit 1
fi

bucket=$1
prefix=$2
outpath=$3

aws s3api list-objects-v2 --bucket $bucket --prefix $prefix \
    --query 'Contents[].[Key]' --output text 2>/dev/null |\。
    xargs -n1 bash -c "aws s3api select-object-content \
        --bucket=\"$bucket\" --key=\"\$0\" \
        --input-serialization '{\"CSV\":{\"QuoteEscapeCharacter\":\"\\\\\"}}' \
        --output-serialization '{\"CSV\":{\"QuoteFields\":\"ALWAYS\",\"QuoteEscapeCharacter\":\"\\\\\"}}' \
        --expression \"select * from s3object s\" \
        --expression-type SQL \"$outpath/\$0.select\" || true"

まとめ

S3 Select の機能を awscli から試してみました。

コマンドの引数など色々あって面倒さはありますがドキュメントなど読めばすぐ使える感じでした。

ログからのデータ抽出など使いどころは色々とありそうなので機会があれば使っていきたいです。