日頃の行い

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

phpredisをPHP7系で触るためにやったこと

ISUCONでredis使おうと思った時にPHPからredis触ったこと無いなということに気がついたので、
触れる用にphpredisを試してみた備忘録です。
利用したPHPのversionは 7.1.10 です。

php -v
PHP 7.1.10 (cli) (built: Oct 10 2017 01:16:36) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

phpredisはCで書かれたPHP Extensionなのでbuildしてphp.iniに設定を追加する必要があります。

github.com

やることは、phpredisのレポジトリをcloneしてビルドし、php.iniに設定を追加するだけです。

https://github.com/phpredis/phpredis#installation

検証に利用したコードはここに置きました。

github.com

検証に利用したコードではDockerfile内で行いました。 設定が反映されているかは php -i で確認できます。

# 反映されている場合
$php -i |grep -e redis -e Redis 
Additional .ini files parsed => /usr/local/etc/php/conf.d/redis.ini
redis
Redis Support => enabled
Redis Version => develop
Registered save handlers => files user redis rediscluster
This program is free software; you can redistribute it and/or modify

# 反映されていない場合
$php -i |grep -e redis -e Redis
This program is free software; you can redistribute it and/or modify

雑に値をsetするphpコードを書いてみました。

<?php

function connect($server, $port = 6379, $db = 0): \Redis {
    $redis = new \Redis();
    $success = $redis->connect($server, $port);
    if ($success !== true) {
        throw new \Exception(spritnf('failed to connect to %s. port: %s', $server, $port));
    }
    $success = $redis->select($db);
    if ($success !== true) {
        throw new \Exception(spritnf('failed to select database %s. server: %s. port: %s', $db, $server, $port));
    }

    return $redis;
}


function main() {
    // docker-composeのlinksで追加しているのでredisとなっています。
    // アドレス入れる場合は下記のような形になります。
    // $redis = connect('127.0.0.1');
    $redis = connect('redis');
    $redis->set('xxx', 'aaa');
    $redis->set('yyy', 'bbb');
    $saved = $redis->get('yyy');
    var_dump($saved);
}

main();

実行すると var_dump の結果が出るだけですね。

$php main.php
string(3) "bbb"

redis-cliで確認すると xxx, yyy というkeyに書き込まれているのがわかります。

$redis-cli
127.0.0.1:6379> keys *
1) "yyy"
2) "xxx"

もっと色々やりたい場合はUsageが充実していたのでそっちが参考になりそうです。

https://github.com/phpredis/phpredis#usage

そんなに難しくないので使う際には使っていこうと思いました。

terraform v0.8.8からv0.10.3に移行した時remote state周りでやったことメモ

気がついたらterraformのバージョンが上がってました。
使ってた時は0.8.xだった気がしたけど、
この前見たら0.10.xになってて、なるほど〜となりました。

細かいCHANGE LOGは terraform/CHANGELOG.md at master · hashicorp/terraform · GitHub をみるとして、
0.9.x から terraform remote hogehoge がなくなっていたので
0.10.x のversionでresourceのremote管理する方法を確認してみました。

確認は v0.10.3terraform plan したら出てきたURLを参考にしました。

$terraform --version
Terraform v0.10.3

$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

www.terraform.io

↑のページの流れに沿って、移行の手順を書いてみました。

1. terraform v0.8.x で remote state を持ってくる

With the older Terraform version (version 0.8.x), run terraform remote pull. This will cache the latest legacy remote state data locally. We’ll use this for a backup in case things go wrong.

古いversionのterraformでローカルに最新の状態を持ってきて、
古いバージョンはここからダウンロードできました。

Terraform Versions | HashiCorp Releases

2. .terraform/terraform.tfstate のバックアップ

Backup your .terraform/terraform.tfstate file. This contains the cache we just pulled. Please copy this file to a location outside of your Terraform module.

何かあった時用にバックアップをとっておきましょう。
私はとりあえず /tmp に置きました。

3. backendの設定

Configure your backend in your Terraform configuration. The backend type is the same backend type as you used with your legacy remote state. The configuration should be setup to match the same configuration you used with remote state.

www.terraform.io

backendの設定を↑ページを参考に追加しましょう。
backendの設定がファイルになったんですね。
設定ファイルはこんな感じになりました。

backend.tf

terraform {
    backend "s3" {
        bucket = "terraform-sample-tfstate"
        key = "sample.tfstate"
        region = "ap-northeast-1"
    }
}

もしくはこのように最低限の設定ファイルを書いて、
initコマンド時にoptionで指定することもできます。

terraform {
    backend "s3" {}
}
$terraform init \
    -backend-config="bucket=terraform-sample-tfstate" \
    -backend-config="key=sample.tfstate" \
    -backend-config="region=ap-northeast-1" 
# backend configオプションにはファイルを指定するのかと思ったら違いました(ヘルプ読まないせい

(backend configの使い方はここで気が付きました。
Terraform backend init: settings in -backend-config file are ignored · Issue #13552 · hashicorp/terraform · GitHub

4. initコマンドの実行

Run the init command. This is an interactive process that will guide you through migrating your existing remote state to the new backend system. During this step, Terraform may ask if you want to copy your old remote state into the newly configured backend. If you configured the identical backend location, you may say no since it should already be there.

あとはinitコマンドを打つだけです。

$terraform init \
        -backend=true \
        -force-copy \
        -get=true \
        -input=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.

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (0.1.4)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 0.1"

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 working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

# terraform {
#    backend "s3" {}
# }
# backend.tfが↑のときは↓のようなコマンドになります

$terraform init \
        -backend-config="bucket=terraform-sample-tfstate" \
        -backend-config="key=sample.tfstate" \
        -backend-config="region=ap-northeast-1" \
        -backend=true \
        -force-copy \
        -get=true \
        -input=false

5. terraform planを実行してstateが正しいか確認

Verify your state looks good by running terraform plan and seeing if it detects your infrastructure. Advanced users may run terraform state pull which will output the raw contents of your state file to your console. You can compare this with the file you saved. There may be slight differences in the serial number and version data, but the raw data should be almost identical.

terraform planを実行してみて、問題なさそうか確認します。

$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.

aws_s3_bucket.terraform-sample-tfstate: Refreshing state... (ID: terraform-sample-tfstate)
No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, Terraform
doesn't need to do anything.

up-to-dateみたいなので問題なさそうです。
init時にbackendの設定がうまくいっていない場合、
はじめにplanしたときみたいに Deprecation warning みたいなのが出ます。
その時はbackendの設定がうまく言ってないはずなので、
initを頑張るといい感じになるかなと思います・・・

最終的にできたレポジトリはこんな感じでした。
S3は確か名前の重複ができない気がしたのでそのままでは試せないかもしれないですが、
参考になれば幸いです。
terraformガンガン進んでいって追いつくの大変だけど、とても便利なので使っていきたいと思います。

github.com

Golangのsqlxでテーブルをjoinした結果をstructにbindする

ちょい前に簡単なSQLをstructにbindしたり、
IN句を使ったりしたやつは書いたんですが、
joinした時とか、structがstructを持ってる場合、
dbタグをどうやってbindするんだろうと思ったら、
友人が記事見つけてくれたので試してみました。

arata.hatenadiary.com

arata.hatenadiary.com

最終的にはSQLエイリアス (AS ...) のほうで . でつなげれば良いみたいでした。
SQLをこんな感じにhoge.fooエイリアスとして利用して、

SELECT h.id AS "hoge.id", h.foo AS "hoge.foo", f.id AS id, f.bar AS bar 
FROM fuga AS f 
JOIN hoge AS h ON f.hoge_id = h.id

Fuga structにbindしようとすると Fuga.Hoge.Foo にその値がbindされるようでした。

type Hoge struct {
    ID  int    `db:"id"`
    Foo string `db:"foo"`
}

type Fuga struct {
    ID   int    `db:"id"`
    Hoge Hoge   `db:"hoge"`
    Bar  string `db:"bar"`
}

検証に使ったコードはこんな感じでした。

gist.github.com

join周りもいい感じにbindしてくれて便利。

参考

こちらもおすすめ

arata.hatenadiary.com arata.hatenadiary.com

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