日頃の行い

個人的な日頃の行いをつらつら書いてます\\\\ ٩( 'ω' )و ////

docker-composeでmysqlにmigrationかけたい時にconnection refusedで失敗したときの対応

開発環境にdocker-composeを使っていて、
テーブルスキーママイグレーションをしようとしたらconnection refusedと言われて困った時の対処法です。
結論はマイグレーションをかける側はmysqlが立ち上がるまで待ちましょうという感じです。
(素のSQLでDBがmysqlなら /docker-entrypoint-migrations.dSQLを置くでもいいですね。

https://hub.docker.com/r/mathewhall/mysql_migration/

困っていた時に人づてに聞いてこのページにたどり着きました。
そこで wait-for とか使いましょうという空気感を受け取りました。

docs.docker.com

github.com

成功する場合と失敗する場合を検証してみました。
検証に使ったコードはこのレポジトリにおいてあります。
成功するパターンと失敗するパターンはそれぞれ make successmake fail で試せます。

github.com

失敗するパターンがこちら

app_1 | 2017/08/16 11:08:24 error: dial tcp 172.26.0.2:3306: getsockopt: connection refused

と出てしまってマイグレーションが走っていません。

$make fail
/Applications/Xcode.app/Contents/Developer/usr/bin/make docker/start conf=fail.yml
/usr/local/bin/docker-compose -f fail.yml build
db uses an image, skipping
Building app
... # 中略

app_1  | mv tmp/migrate.linux-amd64 bin/migrate
app_1  | make[1]: Leaving directory '/opt/app'
app_1  | make migrate/up
app_1  | make[1]: Entering directory '/opt/app'
app_1  | bin/migrate -path ./migrations -database 'mysql://root:root@tcp(db.local:3306)/test?x-migrations-table=migrate_schema_versions' -verbose up
db_1   | 2017-08-16 20:08:24 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
db_1   | 2017-08-16 20:08:24 0 [Note] mysqld (mysqld 5.6.36) starting as process 1 ...
app_1  | 2017/08/16 11:08:24 error: dial tcp 172.26.0.2:3306: getsockopt: connection refused
app_1  | Makefile:51: recipe for target 'migrate/up' failed
app_1  | make[1]: *** [migrate/up] Error 1
app_1  | make[1]: Leaving directory '/opt/app'
app_1  | Makefile:17: recipe for target 'start/fail' failed
app_1  | make: *** [start/fail] Error 2
app_1  | make: Leaving directory '/opt/app'
waitforondockercomposesample_app_1 exited with code 2

成功するパターンがこちら
繋がるまで待って、繋がったらマイグレーションを走らせてくれています。
便利。

app_1 | bin/migrate -path ./migrations -database 'mysql://root:root@tcp(db.local:3306)/test?x-migrations-table=migrate_schema_versions' -verbose up
app_1 | 2017/08/16 11:12:55 Start buffering 1/u AddHogeTable
app_1 | 2017/08/16 11:12:55 Read and execute 1/u AddHogeTable
app_1 | 2017/08/16 11:12:56 Finished 1/u AddHogeTable (read 71.110899ms, ran 227.120362ms)
app_1 | 2017/08/16 11:12:56 Finished after 303.666771ms
app_1 | 2017/08/16 11:12:56 Closing source and database

$make success
/Applications/Xcode.app/Contents/Developer/usr/bin/make docker/start conf=success.yml
/usr/local/bin/docker-compose -f success.yml build
db uses an image, skipping
Building app
... # 中略

app_1  | mv tmp/migrate.linux-amd64 bin/migrate
app_1  | make[1]: Leaving directory '/opt/app'
app_1  | /opt/wait-for db:3306 -- make migrate/up
db_1   | Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.
db_1   | Warning: Unable to load '/usr/share/zoneinfo/leap-seconds.list' as time zone. Skipping it.
db_1   | Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.
db_1   | Warning: Using a password on the command line interface can be insecure.
db_1   |
db_1   |
db_1   | MySQL init process done. Ready for start up.
db_1   |
db_1   | 2017-08-16 20:12:54 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
db_1   | 2017-08-16 20:12:54 0 [Note] mysqld (mysqld 5.6.36) starting as process 1 ...
app_1  | make[1]: Entering directory '/opt/app'
app_1  | bin/migrate -path ./migrations -database 'mysql://root:root@tcp(db.local:3306)/test?x-migrations-table=migrate_schema_versions' -verbose up
app_1  | 2017/08/16 11:12:55 Start buffering 1/u AddHogeTable
app_1  | 2017/08/16 11:12:55 Read and execute 1/u AddHogeTable
app_1  | 2017/08/16 11:12:56 Finished 1/u AddHogeTable (read 71.110899ms, ran 227.120362ms)
app_1  | 2017/08/16 11:12:56 Finished after 303.666771ms
app_1  | 2017/08/16 11:12:56 Closing source and database
app_1  | make[1]: Leaving directory '/opt/app'
app_1  | bash
app_1  | make: Leaving directory '/opt/app'
waitforondockercomposesample_app_1 exited with code 0

docker-composeで他のコンテナのサービスに対して何かやりたい場合は、
立ち上がりきるまでちゃんと待ってあげないといけないみたいですね。
コンテナ内のサービスが立ち上がるまで待ってくれる便利コマンドを知れてよかったです。
めでたしめでたし。

redashで操作履歴とか見れる画面がなかなか見つからなくて困った話

redash便利ですよね。
色んなデータソースに対してクエリを発行できて、
それを可視化できて、共有できる。
とても便利。

redash.io

便利なので会社で使おうと思った時、
誰がどんなクエリ発行したのかとか見れたらいいなぁと思ってそんな画面を探していました。
結論から言うと /admin (e.g. https://redash.example.com/admin ) にありました。
こんな画面でした。

_人人人人人人_  
> bootstrap <  
 ̄Y^Y^Y^Y^Y ̄  

なんで困ったかというと、どこにもこの管理画面(?)へのリンクが無いんですね。
会社の先輩が「なんか /admin って打ったらあった」と言ってて、そのおかげで見つかりました。😇
(もしどこかにリンクがあれば誰か教えていただけると謎が判明して気持ちが晴れやかになるのとてもうれしいです。

まとめ

  • redashで /adminに行くと操作ログとかが見れる
  • bootstrap 便利ですよね!

最後にredashをdocker-composeで立てる検証用に作ったMakefileを置いておきます。

gist.github.com

ファイルの変更を検知してなんらかのコマンドを走らせるreflexが便利

ファイル変更があったらテストを流すとかいろんなライブラリありますが、
言語に依存しないやつないかなぁと思って探してたらgo製のものがありました。

github.com

go製なのでbinaryが配布されていればそれを持ってくればすぐ使えますね
そう思っている時期が私にはありました。
releasesみたらソースコードがおいてあってbinaryはありませんでした。
悲しい。

Release v0.1.0 · cespare/reflex · GitHub

まあIssueになってたり、READMEにもTODOと書いてあるのでそのうちbinary配布されるでしょう。
今回はとりあえずgo getでインストールして使ってみました。

GitHub - cespare/reflex: Run a command when files change

github.com

今回検証に使ったスクリプトこちらです。
cloneして go get github.com/cespare/reflex をすれば make test/watch, make test/watch/config, make test/watch/config/fancy あたりが動くはずです。

gist.github.com

1. あるファイルが更新されたらそのファイルに対してあるコマンドを走らせる例

$make test/watch
reflex -r '\.go$' -- go test {}
[00] --- FAIL: TestHoge (0.00s)
[00]   hoge_test.go:8: 1 + 1 != 2 !?
[00] FAIL
[00] FAIL  command-line-arguments  0.009s
[00] (error exit: exit status 1)
[00] ok    command-line-arguments  0.014s

別画面でhoge_test.goのテストを変えて、戻してみました。
いい感じに動きます

2. あるファイルにはあるコマンドを走らせて、違うファイルには他のコマンドを走らせる例

ある拡張子のものにはこのコマンドを走らせて、このディレクトリにあるものにはこのコマンドを走らせたい。
みたいなときにはconfigにまとめて使うと良さそうです。

$make test/watch/config
reflex -c reflex.conf
[01] No syntax errors detected in hoge.php
[00] ok    _/Users/a-tanaka/hogehoge   0.008s
[01] No syntax errors detected in hoge.php
[01] PHP Parse error:  syntax error, unexpected '"', expecting ',' or ';' in hoge.php on line 3
[01]
[01] Parse error: syntax error, unexpected '"', expecting ',' or ';' in hoge.php on line 3
[01] Errors parsing hoge.php
[01] (error exit: exit status 255)
[01] No syntax errors detected in hoge.php
[00] --- FAIL: TestHoge (0.00s)
[00]   hoge_test.go:8: 1 + 1 != 2 !?
[00] FAIL
[00] FAIL  _/Users/a-tanaka/hogehoge   0.007s
[00] (error exit: exit status 1)
[00] ok    _/Users/a-tanaka/hogehoge   0.008s

このブログ上だとシンタックスハイライトが効いてますが、 ターミナル上だと殺風景でちょっと見づらいなと思ったらいい感じのオプションがありました。 

-d, –decoration=“plain”: How to decorate command output. Choices: none, plain, fancy.

_人人人人_
> fancy <
 ̄Y^Y^Y ̄

なんかおしゃれになりそう。

Before

f:id:arata3da4:20170622201242p:plain

After

f:id:arata3da4:20170622201245p:plain

なんかおしゃれになりました。

まとめ

  • reflexファイル変更があったときに任意のコマンドを実行できて便利
  • まだbinaryはないけどそのうちできたらgoへの依存もなく使えるので便利になりそう
  • fancy

今更ながらRetryするGolangのpackage書いてみた

何番煎じかわからないですが、かいてみました。
呼び出し方がretry.Retryってなってなんかびみょ・・・

github.com

使い方

retry.Retryの第三引数の実装にリトライがありえる処理を書く感じです。
errorが返った場合にはretry.Retryの第一引数に与えた回数分だけリトライします。
リトライの間の待ち時間は第二引数で設定できます。
第二引数の関数は、今のリトライ回数と前回の結果を受け取って待ち時間を返す関数にする必要があります。
retry.Retryの返り値は、第三引数に与えた関数の返り値と一致します。
第一引数分リトライをしてもerrorが返った場合はerrorが返ります。

main.go

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/ara-ta3/retry"
)

func main() {
    c := someClient{
        n: 0,
    }
    // retry.Retry returns the return values of "func" at third args
    r, err := retry.Retry(
        10,
        func(n int, r interface{}) time.Duration {
            // waiting time
            return 0 * time.Second
        },
        func() (interface{}, error) {
            // your codes
            // if this returns error, this will retry this "func"
            return c.DoSomething()
        },
    )
    if err != nil {
        log.Fatalln(err)
    }

    res, ok := r.(*result)

    if !ok {
        log.Fatalf("result cannot be cast to Result struct. r: %+v", r)
    }

    fmt.Printf("%+v\n", res)
}

type someClient struct {
    n int
}

type result struct {
    Message string
}

func (c *someClient) DoSomething() (*result, error) {
    if c.n >= 5 {
        return &result{
            Message: fmt.Sprintf("some message. n: %d", c.n),
        }, nil
    }
    c.n++
    fmt.Printf("count: %d\n", c.n)
    return nil, fmt.Errorf("return error for retry")
}

これを実行すると下記のような感じになります。
待ち時間は0秒にしているので一瞬で終わります。

$time go run main.go
count: 1
count: 2
count: 3
count: 4
count: 5
&{Message:some message. n: 5}
go run main.go  0.14s user 0.08s system 98% cpu 0.231 total

感想

  • retry色んな所でやりたくなったりするので、書いてみました。
  • 便利なので使っていきたい。