cfn-lint で CloudFormation のテンプレートの検証をする

CloudFormation のテンプレートを作っている時、 いちいちコンソールにアップして確認していたら時間がかかってしまう。 そんな時に便利なのが cli でテンプレートファイルの チェックをしてくれるcfn-lint。 使い方も簡単で

$ cfn-lint validate ./cf-template.yaml

とするだけ。 こいつがすごいのがフォーマットだけでなく、 存在していないプロパティや関数までチェックしてくれる事。 なので typo が早期発見できて、開発効率がめちゃ上がります。 インストールも簡単で

$ npm install -g cfn-lint

でいけます。 ただし、ARN を設定する所にテンプレート内で定義したリソースを書いた場合 コンソールでやれば問題なく通るのに、 以下のようなエラーが出てしまいます。

Resource: Resources > AlbListener > Properties > LoadBalancerArn
Message: Expecting an ARN, got 'mock-ref-Alb'
Documentation: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listener.html

無視すればよいのですが、ちょっと気になります。

shell で tap 関数を作ってみた

rubytap メソッドというものがあって、 メソッドチェーンしてる時のデバッグに便利なんですが、 shell でパイプライン使ってる時の デバッグにも使えそうなので、作ってみました。

ただし、処理を入れる事はできなくて 標準エラーに内容を表示するだけです。

#!/bin/bash

function tap() {
    local input
    input=$(cat -)

    echo "$input" | sed -e 's/^/> /' >&2
    echo "$input" >&1
}

使い方はこんな感じで 内容を表示させたい所に tap をはさみます。

$ echo "hoge\nfuga" | tap | sed -e 's/hoge/fuga/'
> hoge
> fuga
fuga
fuga

tap で出力した行には先頭に > をつけてみました。

Swagger を試してみた

そろそろ手書きで API の仕様を作るのやめたいと思って swagger を試してみました。

swagger は別に単一のソフトではなくて、 Swagger Spec という仕様で書かれた定義書を 元にドキュメントを書いたり、コードを生成したりできるものみたいです。

Swagger Editor

オンライン のがありますが、 docker 版もあるのでそちらでやりました。

$ docker pull swaggerapi/swagger-editor
$ docker run -d -p 80:8080 swaggerapi/swagger-editor

localhost にブラウザでアクセスすればよいです。 定義を書いたら右側にドキュメントが表示されて、 さらに上のメニューから、モックとかクライアントを 作ってくれます。

Swagger UI

定義ファイルを読み込んでドキュメントを生成してくれるツールです。 デフォルトでは Swagger Editor と同じデザインですが、 カスタマイズもできるもよう。

まとめ

最初の学習コストはあるけど、導入した方がいいと思いました。 この yaml を git に入れておけば仕様決めの段階から プルリクで議論できるし、CI ツールで マージ→仕様書更新まで自動化できると思います。

ただ、そのままでは yaml の分割に対応してないんですが、 いくつかツールがあるみたいなので、なんとかなりそうな気がします。

loadtest, httperf でお手軽負荷テスト

go で作った WebAPI サーバの速度検証をしてみました。 単に GET リクエストを投げるだけでよいので、JMeter でなくて お手軽にできるツールでやりました。

loadtest

node.js 用のテストツールで 自分でリクエストを好きに定義できるのが特徴です。

例えば、パラメータをランダムに変えたければ requestGeneratorModule.js に

const querystring = require('querystring');

module.exports = function(params, options, client, callback) {
    const random = (max) => Math.floor(Math.random() * Math.floor(max));
    const getParams = { 'id': random(100) };
    const paramStr = querystring.stringify(getParams);

    options.path = options.path + '?' + paramStr

    return client(options, callback);
}

こんな感じで書いて

$ npx loadtest -R requestGeneratorModule.js webapi.example.com/path/to/api/

でいけます。

httperf

loadtest だと秒間あたりのリクエスト数を指定できないので、 こっちも入れてみました。

httperf --server webapi.example.com --port 80 --uri '/path/to/api/&id=100' --num-call 1 --num-conn 100 --rate 100

こんな感じで使えます。 この場合 --rate に 100 を指定してるので秒間 100 リクエストになります。

インストール

brew が使える環境でなかったので github から落としてきて自分でコンパイルしました。

$ git clone git@github.com:httperf/httperf.git
$ cd httperf
$ autoreconf -i
$ ./configure
$ make

python のスーパーセットな関数型言語 Coconut を試す

パイプライン演算子が使える言語がないか探していたところ Coconut というものを見つけました。

公式の説明を見ると

なにそれ面白そう。 という事で、ちょっと触ってみました。

インストールは

$ pip install coconut

でいけますが、http://coconut-lang.org/ の上の方に web の実行環境があるので、ちょっと試すならこれで十分です。

さっそく hello world

"hello, world!" |> print

次は無名関数 & 部分適用 & 関数合成。

add = (x, y) -> x + y
add1 = add$(1)

prd = (x, y) -> x * y
prd2 = prd$(2)

(add1 .. prd2)(1) |> print # -> 3
(prd2 .. add1)(1) |> print # -> 4

部分適用する関数には $ をつけるみたいですね。 これは Python との互換を保つために必要なんでしょうか。

最後にいつもの偶数自乗和

result = (
    range(10)
    |> filter$((n) -> n % 2 == 0)
    |> map$((n) -> n * n)
    |> reduce$((acc, n) -> acc + n)
)
print(result) # -> 120

python でも () の中なら自由に改行できるみたいなので パイプライン演算子も気持ちよく使えます。

インストール不要のチートシートツール cheat.sh

要は man の代わりに使うツールなんですが、 こいつのすごいところはインストールしなくても使えるところで、 例えば、ls の使い方を知りたい場合

$ curl cheat.sh/ls

だけで OK です。 DL して使うコマンドラインツールもあるみたいなんですが、 サーバーがいい感じに返してくれるんで、そのままでも十分。 他にも

$ curl cheat.sh/go
$ curl cheat.sh/go/func
$ curl cheat.sh/go/:learn

のように言語の使い方も教えてくれます。 詳しい使い方は

$ curl cheat.sh/
$ curl cheat.sh/:help

とかやると教えてくれます。 ブラウザから見る事もできるので、 気分や状況によって使い分けられるのもいいですね。

Go で errgroup を使って直列 vs 並列

直列 vs 並列シリーズ第 3 弾。 あんまり errgroup を使ったサンプルがなかったのでやってみた。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "sync"
    "time"

    "golang.org/x/sync/errgroup"
)

func createUrl(sec int) string {
    return fmt.Sprintf("http://httpbin.org/delay/%d", sec)
}

func requestBody(url string) (string, error) {
    response, err := http.Get(url)
    if (err != nil) {
        return "", err
    }
    defer response.Body.Close()

    bytes, err := ioutil.ReadAll(response.Body)
    if (err != nil) {
        return "", err
    }

    return string(bytes), nil
}

// errgroup を使って非同期に関数を実行し、結果を待つ
func awaitAll(funcs []func() error) error {
    eg := errgroup.Group{}
    for _, fn := range funcs {
        eg.Go(fn)
    }

    if err := eg.Wait(); err != nil {
        return err
    }
    return nil
}
func serial(urls []string) ([]string, error) {
    bodies := make([]string, len(urls))

    for _, url := range urls {
        body, err := requestBody(url);
        if (err != nil) {
            return nil, err
        }
        // fmt.Println(body)
        bodies = append(bodies, body)
    }

    return bodies, nil
}

func parallel(urls []string) ([]string, error) {
    mutex := new(sync.Mutex)
    bodies := make([]string, 0, len(urls))
    funcs := make([]func() error, 0, len(urls))

    for _, url := range urls {
        fn := func() error {
            body, err := requestBody(url);
            if (err != nil) {
                return err
            }

            // fmt.Println(body)
            mutex.Lock()
            bodies = append(bodies, body)
            mutex.UnLock()
            return nil
        }
        funcs = append(funcs, fn)
    }
    if err := awaitAll(funcs); err != nil {
        return nil, err
    }

    return bodies, nil
}

func main() {
    max := 4
    urls := make([]string, 0, max)
    for i := 1; i <= max; i++ {
        url := createUrl(i)
        urls = append(urls, url)
    }

    fmt.Println("serial")
    startTime := time.Now()
    serial(urls)
    fmt.Println(time.Now().Sub(startTime))

    fmt.Println("parallel")
    startTime = time.Now()
    parallel(urls)
    fmt.Println(time.Now().Sub(startTime))
}

動かしてみる

$ go version
go version go1.9.2 linux/amd64
$ go get golang.org/x/sync/errgroup
$ go run concurrent.go
serial
11.157001925s
parallel
4.398581223s

ちゃんと並列で実行されてる。