write ahead log

ロールフォワード用

DBマイグレーションツール gooseを使う

データベースのマイグレーションツールで自分に合ったのを探していて, 割といいのを見つけたので使い方をメモしておく.

自分としては

  1. SQLで書ける
  2. 開発言語に依存しない
  3. 開発環境に依存しない
  4. テストデータ投入まで規約がある

を備えているのが欲しかった.

gooseは最後の以外は満たしているのでかなりいい感じ.

インストー

ここは定番で.

$ go get bitbucket.org/liamstask/goose/cmd/goose

ただし, Windowsの場合SQLiteを使うせいかビルドエラーになる.

$ go get bitbucket.org/liamstask/goose/cmd/goose
# bitbucket.org/liamstask/goose/cmd/goose
C:\Go\pkg\tool\windows_amd64\link.exe: running gcc failed: exit status 1
/usr/lib/gcc/x86_64-pc-msys/5.3.0/../../../../x86_64-pc-msys/bin/ld: -lmingwex が見つかりません
/usr/lib/gcc/x86_64-pc-msys/5.3.0/../../../../x86_64-pc-msys/bin/ld: -lmingw32 が見つかりません
/usr/lib/gcc/x86_64-pc-msys/5.3.0/../../../../x86_64-pc-msys/bin/ld: -lmingwex が見つかりません
/usr/lib/gcc/x86_64-pc-msys/5.3.0/../../../../x86_64-pc-msys/bin/ld: -lmingw32 が見つかりません
collect2: エラー: ld はステータス 1 で終了しました

ここで書いた時の現象と一緒だし, たぶんそう.

解決するにはTDM GCCを入れてコマンドプロンプトからgo getを実行しなおせばいい.

(MSYS2だと動作しないので注意.たぶんCygwinもじゃないかなぁ)

設定

何がともあれディレクトリを作ります. 名前はdbでないとダメなようで.(-pathオプションで変えられますが)

$ mkdir db

設定ファイルが必要になるが, 面倒なのでサンプルをコピーしてくる.

$ cd db/
$ cp $GOPATH/src/bitbucket.org/liamstask/goose/db-sample/dbconf.yml ./

後は接続先に合わせて変更する.

サンプルはPostgresだったので, ここではMySQLにしておく.
(mymysqlは誤植ではない, 使うgolangのドライバを明示的に指定している)

development:
    driver: mymysql
    open: [DB名]/[ユーザ名]/[パスワード]

MySQLにログインして, データベースも作っておきましょう.

> CREATE DATABASE demo;

接続確認のために状態確認する.

$ goose status
goose: status for environment 'development'
    Applied At                  Migration
    =======================================

使う

ここまで来るとかなり単純.

というか単純なものが欲しかったんだけど.

テーブルを作る(upする)

マイグレーションファイルを作るには以下のコマンドを使います.

$ goose create [テーブル名] sql

とりあえずUsersテーブルを作ってみる.

$ goose create users sql
goose: created C:\msys64\home\twinbird\dropbox\lab\sample\db\migrations\20170623171909_users.sql

出来たファイルを編集.

$ vi db/migrations/20170623171909_users.sql

こんなのが出来てます.シンプルでいいっすね.

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied


-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back

それぞれ編集します.

-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied

CREATE TABLE users(
    id SERIAL,
    name varchar(20),
    email varchar(20)
);

-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back

DROP TABLE users;

いよいよ, マイグレーションしましょう.

$ goose up
goose: migrating db environment 'development', current version: 0, target: 20170623171909
OK    20170623171909_users.sql

うまくいきました.

戻す(downする)

そのままです.

$ goose down
goose: migrating db environment 'development', current version: 20170623171909, target: 0
OK    20170623171909_users.sql

バージョンを見る(dbversion)

バージョンはタイムスタンプそのままみたいですね.

$ goose dbversion
goose: dbversion 20170623171909

最新バージョンに上げ直す(redo)

過去のコミットまで戻った時は便利そうです.

$ goose redo
goose: migrating db environment 'development', current version: 20170623171909, target: 0
OK    20170623171909_users.sql
goose: migrating db environment 'development', current version: 0, target: 20170623171909
OK    20170623171909_users.sql

感想

自分が求めていたものが大体満たされていて非常に良い感じ.

自作しようかと思っていたけど, 作らなくてよかった.

ドキュメント読むとGo Migrationsってのもあるけど, これは使いそうにないなぁ.

CREATE TABLEで制約(Constraint)を付けたり外したり

追加する方法は知ってても, 削除する方法を知らなかったりする.

そんな制約ですが, 何度調べても忘れるのでメモします, はい.

MySQLでやります.

テーブル例

例がないと困るので適当に用意しました.

会社と部署にしましょう.

CREATE TABLE company(
    id      INT PRIMARY KEY,
    name    VARCHAR(20)
)
CREATE TABLE dept(
    id          INT PRIMARY KEY,
    name        VARCHAR(20),
    company_id  INT
)

deptのcompany_idにcompanyのidへの制約を付けたいと思います.

制約を付ける先はUniqueじゃないとだめなので注意です.
(ここではPrimary keyにしました)

追加する

追加する時に重要なのは名前を付けて制約を付けることです.
これで削除が楽になります.

以下のフォーマットで追加できます.

ALTER TABLE [子側のテーブル] ADD CONSTRAINT [制約名] FOREIGN KEY([親のカラム]) REFERENCES [親テーブル]([親のカラム])

例で言うとこんな感じ.

ALTER TABLE dept ADD CONSTRAINT deptToCompany FOREIGN KEY(company_id) REFERENCES company(id)

削除する

以下のフォーマットで削除できます.

ALTER TABLE [テーブル名] DROP FOREIGN KEY [制約名]

例で言うとこんな感じ.

ALTER TABLE dept DROP FOREIGN KEY deptToCompany

感想

書かないと忘れる.

LinuxからWindowsネットワークにアクセスする

要するに, CentOS7からSambaでWindowsネットワークをマウントしたい.

1. とりあえずsambaクライアントを入れる.

# yum install samba-client cifs-utils

2. Firewall許可対象へ追加

# firewall-cmd --permanent --zone=public --add-service=samba

3. 共有フォルダを探してみる

smbclient -L [Windowsホスト名]

4. マウントする

# mount -t cifs -o user=[共有フォルダのマシンのユーザ],password=[共有フォルダのマシンのパスワード] //[サーバIP]/[共有フォルダ] [マウントポイント]

ちなみにパスワード指定が無い場合は以下.

# mount -t cifs -o user=[共有フォルダのマシンのユーザ],password= //[サーバIP]/[共有フォルダ] [マウントポイント]

5. アンマウントする

書くまでもないけど….

# umount [マウントポイント]

CentOS7のKVMにCentOS7(ゲスト)をインストールする

CUIでやります.

0. KVMの設定

過去の記事に譲ります.

1. イメージをダウンロード

ISOファイルが無いと始まらないのでダウンロードします.

CentOS

上記から好きなバージョンを選べばよいかと.

今回はCentOS7で.

# mkdir /var/lib/libvirt/iso
# cd /var/lib/libvirt/iso
# curl http://ftp.iij.ad.jp/pub/linux/centos/7.3.1611/isos/x86_64/CentOS-7-x86_64-DVD-1611.iso > CentOS-7-x86_64-DVD-1611.iso

2. ゲストOS用のディスク領域を作成する

とりあえずqcow2フォーマットのディスクサイズは10Gで.

# cd /var/lib/libvirt/images
# qemu-img create -f qcow2 CentOS-7-x86_64-DVD-1611.iso 10G
Formatting 'CentOS-7-x86_64-DVD-1611.iso', fmt=qcow2 size=10737418240 encryption=off cluster_size=65536 lazy_refcounts=off

3. インストールする

以下のコマンドで.
RAMは1GB, CPUコアは1にした.
長いから忘れそう.

# virt-install \
--name centos7 \
--hvm \
--virt-type kvm \
--ram 1024 \
--vcpus 1 \
--arch x86_64 \
--os-type linux \
--os-variant rhel7 \
--boot hd \
--disk /var/lib/libvirt/images/centos7.qcow2 \
--network network=default \
--graphics none \
--serial pty \
--console pty \
--location /var/lib/libvirt/iso/CentOS-7-x86_64-DVD-1611.iso \
--extra-args "console=tty0 console=ttyS0,115200n8"

上記を実行するとCentOS7のコンソールインストーラが実行されるので画面表示に従って進めていけばいい.

extra-argsを指定しないとコンソール接続できなくなるので注意.

インストール後は画面指示通り Ctrl+] で脱出を.

4. 状態の確認

# virsh list
 Id    名前                         状態
 ----------------------------------------------------
  3     centos7                        実行中

5. ゲストへのコンソール接続

# virsh console centos7

6. ネットワークの設定(再)

が、ここまでやってもゲストからネットワークにつながらない.

ホストに戻って以下を.

# virsh attach-interface --type bridge --source br0 centos7

上記でネットワークインターフェースをホットアタッチできるそうで.

再度コンソール接続するとちゃんとIPが取れてました.

余談

ホストからゲスト上のDHCP割り当てIPを取得する方法がわからないのが辛い.

NAT状態なら以下で取れていたようですが.

# virsh net-dhcp-leases default

まぁ、ブリッジ作るとホストは関係ないともいえるので, 取得する方法はないのかも.

終えて

色んなところではまった.
いい加減まとまった知識を得たい.
良い本でもあればいいんだけど.

参考

仮想マシン構築(KVM)

Libvirt/KVM 管理の VM に NIC を追加 (ホットアタッチ) する

golangで実行中のOSの種類を判別する

簡単だった.ポータビリティがすごい.

package main

import (
    "fmt"
    "runtime"
)

func main() {
    switch runtime.GOOS {
    case "windows":
        fmt.Println("running on Windows.")
    case "darwing":
        fmt.Println("running on Mac OSX.")
    case "linux":
        fmt.Println("running on Linux.")
    case "freebsd":
        fmt.Println("running on BSD.")
    default:
        fmt.Println("running on Other OS.")
    }
}

[実行結果]

$ ./sample.exe
running on Windows.

くらいあればとりあえずは事足りる気がする. (調べてないけど, Plan9とかも対応してそうだよね….)

参考

The Go Programming Language - Package runtime

vimrcを変えた

golangを良く書くようになってきたので, gofmtを保存時に自動実行するようにした.

あとはMarkdownでfolding出来ると便利とようやく気付いたのでfoldingを足した.
(今まで使ってなかったんですよね…)

プラグインはホントわけわからなくなりそうなので極力使いたくない.
(標準でも十分すぎるぐらいvimは機能多いし…)

とはいえ100行超えてきたのでそろそろこの姿勢も見直さないとダメだろうか…

gist.github.com

参考

Stack overflow - Vim Markdown Folding?

UbuntuでUpstartを使ってプログラムをデーモン化する

golangで簡単なHTTPサーバを作ってこれをデーモン化してみましょう.

今後はSystemdが主流に代わっていくのでしょうが, まだまだUpstartも多いと思います.

1. サンプルプログラムの準備

サンプルプログラムは以下のような簡単なものです.

Helloを挨拶してくれるだけです.

package main

import (
        "net/http"
        "fmt"
)

func main() {
        http.HandleFunc("/", helloHandler)
        http.ListenAndServe(":8080", nil)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello\n")
}

[実行結果]

vagrant@vagrant-ubuntu-trusty-64:~/hello$ ./hello &
[1] 2584
vagrant@vagrant-ubuntu-trusty-64:~/hello$ curl localhost:8080
Hello

2. ランレベルのおさらい

*nixにはランレベルという概念があります.

デーモン化にあたって必要な知識になるのでここでおさらいしておきます.

参考: wikipedia - ランレベル

ランレベル/ID 名称 説明
0 停止 システムの停止、またはシャットダウン
1 シングルユーザモード デーモン起動なし.rootのみ利用可.いわゆるセーフモード
2 マルチユーザモード ネットワークが使えない.
3 マルチユーザモード(通常) いわゆる通常のモード.X11はなし
4 未使用
5 X11 ランレベル3 + Xウィンドウ
6 再起動 システム再起動

3. 起動スクリプトの準備

起動スクリプトを以下に作成します.

/etc/init/

今回は以下のようにしましょう.

vi /etc/init/hello-svr.conf

内容は以下のとおりです.

description "Hello Server"
author "twinbird<mail@hostname.domain.jp>"

start on runlevel [235]
stop on runlevel [016]

chdir /usr/local/bin/hello-svr
exec ./hello
respawn

簡単な解説を付けると

  • description => 説明
  • author => 記述者とメアド
  • start on runlevel[235] => ランレベル2,3,5で開始
  • stop on runlevel[016] => ランレベル0,1,6で停止
  • chdir => ディレクトリを移動
  • exec => プログラムを実行
  • respawn => プロセスが死んだら再度実行する

4. 設定を認識させる

# initctl reload-configuration
# initctl list

以下のようにリストが表示されます. ちゃんと出てますね.

...[中略]...
tty6 start/running, process 968
dmesg stop/waiting
hello-svr stop/waiting
...[中略]...

5. 操作する

操作コマンドinitctlを使うと色々できます.

ちなみにサービスの事をjobと呼ぶみたいです.
(サービスだとWindows用語になるのかな.デーモンと呼ぶのが一般的?)

コマンド 説明
initctl status [job名] jobの状態確認
initctl list jobと状態のリストを表示
initctl start [job名] jobの起動
initctl stop [job名] jobの停止
initctl restart [job名] jobの再起動
initctl reload [job名] jobへsighupシグナルを送る
initctl reload-configuration 設定ファイルをリロード
1. 起動する

簡単です.

root@vagrant-ubuntu-trusty-64:~# initctl start hello-svr
hello-svr start/running, process 1617

確認はcurlで.

root@vagrant-ubuntu-trusty-64:~# curl localhost:8080
Hello
2. 停止する

こちらも簡単.

root@vagrant-ubuntu-trusty-64:~# initctl stop hello-svr
hello-svr stop/waiting

確認.

root@vagrant-ubuntu-trusty-64:~# curl localhost:8080
curl: (7) Failed to connect to localhost port 8080: Connection refused

6. 起動スクリプトをもうちょっと調べる

メモしておきます.

script ~ end script

上記の例ではexecで直接バイナリを起動しましたが, script ~ end scriptの ブロックで囲むとその間を実行するようです.

script
    # comment
    if [...]; then
        ...
    fi
end script

ちょっとしたシェルスクリプト書くならこの方がいいですね.

respawnに試行回数やインターバルを付ける

respawnはジョブが死んだら再度実行してくれる便利な記述ですが, 再試行するのに回数制限を付けたいパターンもあるかもしれないですね.

respawn limit 最大再試行回数 インターバル時間(秒)

で実現できます.

job起動・停止前後で何かする
  • pre-start
  • post-start
  • pre-stop
  • post-stop

という命令で指定できます.

pre-start exec [何かコマンド]

割と簡単にデーモン化できますね.

次はSystemdを使ってみたいです.

7. 参考

Getting Started - upstart

インフラエンジニアway - Upstartを使ってお手軽deamon化

偏った言語信者の垂れ流し - Upstartを使う