CircleCIで{maven,github}-release-pluginを使ってGitHub Releaseに自動登録
一発ネタやちょっとしたオモチャならばともかく、プログラムを継続的に維持したいとなると、CIやCDの導入を検討したくなりm(ry
まぁ、長ったらしい前置きはさておき、CircleCIを使い始めました。
今回は、CircleCIを使用してMavenビルドを行い、作成したjarをGitHub Releaseへ自動登録するというネタです。
ところで、CIサービスの有名所と言えばもう一つ、Travis CIがありますね。
このTravis CI、ビルドマトリクスというCircleCIには無い機能を備えていて、ざっくり言うとJDK7と8を同時にビルドするなどができるようです。
この機能には少し惹かれるものがあったんですが、まぁ、ライブラリ提供する訳でも無いし、最新のJDKでビルドができれば良いかな、複雑な物を管理したくないしー、という感じで今回はCircleCIにしてみました。
あとは、CircleCIのv2がちょっぱやと言う話を耳にしていて気になっていたという点もあったり。
しかし、後述しますがCircleCIには無いとある機能がTravis CIにありそうなのでちょっち失敗したかなぁと思っています…
さて、CI&CDやるぞと言ってもなんか漠然としていますし、ダレないためにもまずはどこまでやるかを決めるべきかなと思いました。
さしあたって、まず何がしたいんだっけと考えた結果、ざっくり以下の3点ができればいいかなとなりました。
- 前回記事でテスト書かねば、と宣言したので継続化できる環境がほしい。
- バージョンアップ作業はルーチンワークなので自動化したい、オペミスするとカッコ悪いので。
- Mavenなのでビルド成果物も作成される、バージョン毎に自動で保存しておきたいでもMavenレポジトリ持ってないとりまGitHub Releaseで。
ということで、まずはこの3点が達成されることを目的にします。
まぁ、1.はCIサービスを使い始めた時点でほぼ達成かなと、コードをGitHubにcommitすれば勝手にテストを実施してくれるので。後はテストを書くだけですね、書いていませんが…
2.と3.が今回の本題です。
試行錯誤の結果、maven-release-plugin
とgithub-release-plugin
を使用することによって達成することができました。
これからその詳細を載せるのですが、これまた長ったらしいので、先に出来上がったシステム概要を載っけます。
- CircleCI v2
- build docker imageは
circleci/openjdk:8-jdk
- ビルドツールはMaven
- 連携先はGitHub
master
以外のブランチにcommitすることをトリガーにmvn package
- 要するに、packagingできることの確認
master
ブランチにcommitすることをトリガーにmvn deploy
- つまり、
master
ブランチへのPull requestをmergeでmvn deploy
される - その際、バージョンアップ等が自動で行われる
- ついでにGitHub Releaseにdraftとして登録される
- なぜdraftかと言うと、単純に直公開にビビった
- つまり、
ちゃんとやろうとするとgit-flowで言うところのrelease
ブランチとdevelop
ブランチが最低限は欲しいかなと思いましたが、まぁ必要に迫られたときでいいやとしました。
ではでは、詳細に記していこうと思います。
目次。
- バージョンアップの自動化(
maven-release-plugin
)maven-release-plugin
の設定- CircleCIで
maven-release-plugin
を実行
- GitHub Releaseへのjar自動登録(
github-release-plugin
)github-release-plugin
の設定maven-release-plugin
とgithub-release-plugin
の連携- CircleCIで
github-release-plugin
を実行
- ブランチ毎のMavenフェーズ選択
バージョンアップの自動化(maven-release-plugin)
バージョンアップ作業って面倒くさいです。
Mavenの場合、SNAPSHOT
を外してテストを通してjarとかにパッケージングして出来上がったパッケージをどっかに登録してgitのtag打って次の開発用にversion
をインクリメントしつつSNAPSHOT
付けてみたいな。
ミスるともっと面倒くさいことになります。しかも、恥ずかしい><
例えばv1.2.3
にしたつもりがv12.3
にインフレしちゃったりとかあるあるネタっすね。ヤヴァイ…
なので自動化したいわけです。
Mavenの場合、maven-release-plugin
を使うことで大体解決することができます。
大体というかほぼ全て解決してくれます。逆にmaven-release-plugin
で解決できないことは、パッケージの登録だけかと思います。
そんでもってパッケージの登録は後述するgithub-release-plugin
の領分です。
さて、やりたいこととしてはよくある事例かなぁとおもむろにググるとCircleCIの公式ドキュメントがヒット!
これは勝つるとふんすふんすしますが、よく見るとv1のドキュメントやん…orz
しかもよくよく読むとCircleCIのREST APIでmvn release:prepare
を実行しているだけです。
これでは、maven-release-plugin
をローカルで実行するかCircleCIで実行するかぐらいの差しかありません。
まぁ、ビルド環境が統一されるのでビルドできないそれおまかん的な問題は回避できます。
が、そうじゃない。
やりたいことは、ある条件をトリガーに(ここでは、master
ブランチのmergeなどでcommitが発生したら)自動でバージョンアップ作業を開始したい、なので、テストのビルドが通ったことを自分で確認してからAPIを呼び出すというのはちょっと今回のケースには合わないなぁという感じです。
他にも色々とインターネッツを漁りますが良い記事が見つかりません…
と、いうことで自力で頑張ります。
maven-release-pluginの設定
まず、maven-release-plugin
の設定です。
公式ドキュメントを参考にpomファイルを編集します。
projectタグ直下にscm
タグを追加して、そこに連動させたいGitHubレポジトリ情報を記載。
こんな感じ。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <scm> <connection>scm:git:git@github.com:hoge/fuga.git</connection> <tag>HEAD</tag> </scm> : :
そして、project/build/plugins
タグ内にmaven-release-plugin
用のplugin
タグを追加します。
こんな感じ。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> <version>2.5.3</version> <configuration> <tagNameFormat>v@{project.version}</tagNameFormat> <scmCommentPrefix>'[maven-release-plugin][ci skip] '</scmCommentPrefix> </configuration> </plugin>
tagNameFormat
はお好みです。
上記の設定だとproject
タグ直下のversion
タグの値が参照されて、v1.0
みたいな感じになります。
注意すべきはscmCommentPrefix
です。
ここに[ci skip]
を含めたほうがいいと思います
表現が曖昧なのはちゃんと試していないからですすみません。
これが無いと無限ループします、たぶん。
pomファイルの編集が終わったらGitHubにpushする前にローカルで動作確認します。
masterのcommit履歴が汚れるのは嫌なので適当なブランチを切って動作確認を行います。
git checkout -b hoge mvn --batch-mode -DskipTests=true release:prepare release:perform
maven-release-plugin
の動作確認が目的なのでskipTests
を指定しています。
--batch-mode
を指定しないとインタラクティブにバージョン番号とかを聞かれます。
ローカルで実行する場合は対話式に実行しても良いのですが、CircleCIで実行することを想定している以上、対話式では無理なので--batch-mode
指定で実行します。
その場合、バージョン番号は最小位の値をひとつインクリメントするようです。
動作確認が済んだらおk。次はCircleCI上で動作させます。
CircleCIでmaven-release-pluginを実行
唐突、というか今更ですが、ここでmaven-release-plugin
の動作についてざっくり説明します。
maven-release-plugin
で使用するのはおおよそ2つのgoalだけです。1つはprepare
でもう1つはperform
。
それぞれざっくり何をしているかというと、prepare
はpomファイルのversion
タグのSNAPSHOT
を外してそれをcommitして(!)git tag
を打って(!)次の開発用にversion
タグをインクリメントしつつSNAPSHOT
付けてcommitし(!)、それをpushします(!)。
実際にprepare
で作成される1回目のcommitを貼っつけましょう。こんな感じです。
commit 0123456789abcdef........................ Author: circleci <circleci@circleci.com> Date: Sat Sep 2 00:00:00 2017 +0000 [maven-release-plugin][ci skip] prepare release v1.2 diff --git a/pom.xml b/pom.xml index 0123456..789abcd 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ <groupId>io.github.hoge</groupId> <artifactId>fuga</artifactId> - <version>1.2-SNAPSHOT</version> + <version>1.2</version> <packaging>jar</packaging> <name>fuga</name> @@ -11,7 +11,7 @@ <scm> <connection>scm:git:git@github.com:hoge/fuga.git</connection> - <tag>HEAD</tag> + <tag>v1.2</tag> </scm> <properties>
一方perform
は何をしているかというと、prepare
で作成されたtagでcheckoutして、(デフォルト設定の場合)mvn deploy
を実施します。
maven-release-plugin
の動作概要おわりっ!
まぁ、何が言いたいかというと、!したように自動でcommit&push等を行うということです。
CircleCIでmaven-release-plugin
を使う場合、当然ながらCircleCIから上記のcommit&push等を行います。ところが、CircleCIのGitHub連携ではRead-onlyの秘密鍵しか作成されません。
つまり、Writableな秘密鍵を新規作成してCircleCIに登録、そしてその公開鍵を連携するレポジトリに登録する必要があります。
詳細はCircleCIのドキュメントを参照。
これでCircleCIはWritableな秘密鍵を使用してGitHubにpushできるようになります。
最後にconfig.yml
を修正してprepare
とperform
を実行するようにします。
こんな感じ。
version: 2 jobs: build: docker: - image: circleci/openjdk:8-jdk steps: - checkout - add_ssh_keys: fingerprints: - "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx" - run: git config --global user.email "circleci@circleci.com"; git config --global user.name "circleci"; mvn --batch-mode release:prepare release:perform
gitのuser設定はお好みです。
これをGitHubにpushしてCircleCIで動作確認。うまくいったらおk。
めでたしめでたし。
しかし、セキュリティしっかりしているとメンドイ…
GitHub Releaseへのjar自動登録(github-release-plugin)
Mavenはpackage
フェーズでjarを作成します。
せっかくなので、このjarをどこか手軽な場所に自動で保存しておきたいと思ったりします。
まぁ、CircleCIにもjarをArtifactsとして置いておくことは出来るのですが、ログインしないとArtifactsへのリンクを辿れないようなので手軽な場所とは言えません。
Mavenを使っているので、一般的にはどこかのMavenリポジトリに対してdeploy
フェーズでjarを登録するべきでしょうが、Mavenリポジトリなんて持ってないし、今回の対象は前回記事で作成した実行可能形式fat jarなのでMavenリポジトリに登録するメリットがそんな無いっす。
ということで、どうしよっかなーと考えていたらGitHub Releaseに置けばいいじゃんむふーとなりました。
maven-release-plugin
同様、やりたいこととしてはよくある事例かなぁとおもむろにググるとCircleCIの公式ブログがヒット!
これは勝つるとふんすふんすしますが、よく見るとpackagecloudにデプロイするやり方やん…orz(デジャヴ)
気を取り直して、そういえば、GitHub ReleaseにアップロードするMavenビルドプラグインがあったなぁということを思い出します。
ようするにGitHub APIを使用してGitHub Releaseへの登録をMavenビルドに組み込めるプラグインですね。
それが表題のgithub-release-plugin
です。
公式プラグインでは無く、おそらく個人が公開しているサードパーティプラグインなので、メンテナンスされなくなったりする点が怖いですが、コードをみた感じシンプルなのでいざという時は自分でなんとかできる範囲です。
ということで、github-release-plugin
を使うことにしました。
github-release-pluginの設定
以下の4つが必要です。
scm
タグの追加plugin
タグの追加maven-deploy-plugin
のskip設定settings.xml
の作成
scm
タグはデプロイ先のGitHubの解決に使用しているようです。こいつはmaven-release-plugin
を設定する際に既に記述してあるので割愛。
次のplugin
タグはこんな感じ。
<plugin> <groupId>de.jutzig</groupId> <artifactId>github-release-plugin</artifactId> <version>1.2.0</version> <executions> <execution> <id>github-upload</id> <phase>deploy</phase> <goals> <goal>release</goal> </goals> <configuration> <releaseName>v${project.version}</releaseName> <tag>v${project.version}</tag> <artifact>${project.build.directory}/${project.artifactId}-${project.version}.jar</artifact> </configuration> </execution> </executions> </plugin>
github-release-plugin
は自身を使用してGitHub Releaseに登録しているようなのでその設定をほぼ丸パクリです。
そしてmaven-deploy-plugin
のskip設定です。以下のplugin
タグを追記します。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.7</version> <configuration> <skip>true</skip> </configuration> </plugin>
こいつを設定しないとmvn deploy
実行時にこんな感じで怒られます。
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy (default-deploy) on project hoge: Deployment failed: repository element was not specified in the POM inside distributionManagement element or in -DaltDeploymentRepository=id::layout::url parameter -> [Help 1]
ようするにデプロイ先を示せということなんですが、前述の通りGitHub Releaseに登録できれば良いのでmaven-deploy-plugin
さんはskip設定にして黙っていてもらいますm(_ _)m
最後のsettings.xml
はこんな感じ。
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <servers> <server> <id>github</id> <privateKey>${env.GITHUB_API_TOKEN}</privateKey> </server> </servers> </settings>
こいつを適当なファイル名で保存します。自分は.circleci.settings.xml
にしました。
id
はgithub
決め打ちです。
privateKey
にはGitHubのPersonal access tokensを記述します。Personal access tokensの発行方法はGitHubのドキュメントを参照。
ただし、べた書き記述してしまうとGitHubにpushできなくなってしまいます。pushしちゃうとAccess tokenがバレてしまうので。なので環境変数経由で渡すようにします。
実は認証にユーザ名とパスワードを記述するやり方もあります。が、自分はGitHubの2段階認証を有効にしているのでユーザ名とパスワードを記述するやり方は使えないっす。
話を戻して、動作確認はこんな感じ。
export GITHUB_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx mvn -s ./.circleci.settings.xml deploy -Dgithub.draft=true -DskipTests=true
以下のようしてgithub-release-plugin
単独の動作確認もできます。
export GITHUB_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx mvn -s ./.circleci.settings.xml de.jutzig:github-release-plugin:release -Dgithub.draft=true -DskipTests=true
GitHub Releaseを確認して登録できていればおk。
maven-release-pluginとgithub-release-pluginの連携
今回はmaven-release-plugin
と連携させたいので、そのための設定をmaven-release-plugin
に1つ追加します。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> <version>2.5.3</version> <configuration> <tagNameFormat>v@{project.version}</tagNameFormat> <scmCommentPrefix>'[maven-release-plugin][ci skip] '</scmCommentPrefix> <!-- vvv ココ vvv --> <arguments>-Dgithub.draft=true</arguments> </configuration> </plugin>
maven-release-plugin
はその実行過程で別プロセスとしてmvn deploy
を実行します。arguments
タグはその引数を指定するタグです。こいつに-Dgithub.draft=true
を設定します。
先述のとおり、自分の場合はいきなりの公開にビビっているのでdraft公開です。
動作確認はこんな感じ。前述のようにcommitが発生するので、一時的なブランチを作成してそのブランチで確認すると良いと思います。
export GITHUB_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx mvn -s ./.circleci.settings.xml --batch-mode release:prepare release:perform
さっきと同様、GitHub Releaseを確認して登録できていればおk。
お次はCircleCIで動作させます。
CircleCIでgithub-release-pluginを実行
前述の通り、github-release-plugin
を利用するにあたりGitHubのPersonal access tokensを使用します。
こいつを環境変数経由で渡すように組んだので、CircleCIのビルドSettingsからEnvironment Variablesに今回の環境変数名であるGITHUB_API_TOKEN
を追加します。
やり方は察してください(雑w
疲れてきたンゴ…
だがあと少しなので頑張る。
最後に前述の.circleci.settings.xml
を読み込むようconfig.yml
を修正して完了。
version: 2 jobs: build: docker: - image: circleci/openjdk:8-jdk steps: - checkout - add_ssh_keys: fingerprints: - "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx" # vvv ココ vvv - run: git config --global user.email "circleci@circleci.com"; git config --global user.name "circleci"; mvn -s ./.circleci.settings.xml --batch-mode release:prepare release:perform
めでたしめでたし。
ちなみにですが、Travis CIではGitHub Releaseへのアップロードは標準機能っぽいです。これがCircleCIでちょっち失敗したかなぁと思ったやつです。
隣の芝生は青い案件であることを祈るンゴ。
ブランチ毎のMavenフェーズ選択
CircleCIはどのブランチでcommitが生じてもビルドが開始します。
それ自体は良いのですが、GitHub Releaseの自動登録はmaster
ブランチのcommitをトリガーにしたビルド時のみに実施してほしいです。
ということで、master
以外のブランチのcommitをトリガーにしたビルドはpackage
フェーズまでに留めるようにします。
これは簡単。環境変数CIRCLE_BRANCH
に実行中ビルドのブランチが格納されるので、これを使って条件分岐するだけです。
version: 2 jobs: build: docker: - image: circleci/openjdk:8-jdk steps: - checkout - add_ssh_keys: fingerprints: - "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx" # if文で分岐するだけ - run: if [ $CIRCLE_BRANCH = 'master' ]; then git config --global user.email "circleci@circleci.com"; git config --global user.name "circleci"; mvn -s ./.circleci.settings.xml --batch-mode release:prepare release:perform; else mvn package; fi
めでたしめでたし。
むすび
(ヽ´ω`)
記事書くのは大変ですね…
気を取り直して、実際にCircleCIを使っての感想です。
キャッシュはえー。ローカル実行よりずば抜けてはえーよ!
Maven Central Repositoryは日本からだと遠いんですかね。
逆にCircleCIへのアクセスはラグりまくりですw
CircleCIにはデバックビルド機能が有り、これを有効にしてビルドを実施するとビルドコンテナにssh接続できるようになります。が、遅いおw
何はともあれ、これでやっとacceptanceテスト書けるぞっと思ったら、initiatorは対応していなかったっていう…
継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化
- 作者: Jez Humble,David Farley,和智右桂,高木正弘
- 出版社/メーカー: KADOKAWA
- 発売日: 2017/07/31
- メディア: 単行本
- この商品を含むブログを見る
OANDA(つーかFIX API)で自動売買プログラム作成が大変
前回↓から約1年(!)、とりあえずFIX APIでレート取得が出来ました。
いやー、牛歩もいいところですね。
まぁ、実際のとろこはほとんど放置状態だったんですが。
たまにOANDAのドキュメント読んでFIX APIわかんねーって悶ていただけ…
しかし!
さるGWに一念発起しまして、OANDAのFIX APIを使っての自動売買プログラム作成を再開しました!
GWなら取引時間内での検証も出来ますしね。
これ結構重要で、放置の理由にFIX APIわからんということもあったのですが、むしろFXは平日しかレートが動いていないということの方が放置の原因だったり。
株も同じですが平日しか取引していないのはやっぱりやりづらいですね。
まぁ、株は平日の日中のみに対して、FXは平日であれば24時間取引しているので気合があればなんとかなるんですが…
僕にはその気合は無かった…
それはさておき、GWにわりとガッツリ取り組んでから約二ヶ月、とりあえずレート取得のみですが一週間連続で安定稼動させることが出来ました。
前回同様、なかなか分かりづらい点が有ったので、そのことも含めてやったことを書き残そうかと思います。
先に作った物の概要。
# 実行例 $ java -jar \ -Dpassword=$PASS \ -Daccount=$ACCOUNT \ -Dsymbols=USD/JPY,EUR/JPY \ fix-prototype-1.5-SNAPSHOT.jar
- 標準出力にレート
USD/JPY Mon May 29 09:22:23 JST 2017 111.239 0.008 USD/JPY Mon May 29 09:22:23 JST 2017 111.24 0.008 EUR/JPY Mon May 29 09:22:23 JST 2017 124.264 0.013 USD/JPY Mon May 29 09:22:23 JST 2017 111.242 0.008 USD/JPY Mon May 29 09:22:23 JST 2017 111.242 0.008
- 標準エラー出力にアプリケーションログ
6 12, 2017 4:55:02 午前 com.example.FixApplication onMessage 重大: Notes on market data entry: Halted 6 13, 2017 9:32:02 午前 quickfix.Session disconnect 情報: [FIX.4.4:hogehoge->OANDA/RATES:testusr1-Session1] Disconnecting: Encountered END_OF_STREAM 6 13, 2017 9:32:03 午前 quickfix.mina.initiator.IoSessionInitiator$ConnectTask resetIoConnector 情報: [FIX.4.4:hogehoge->OANDA/RATES:testusr1-Session1] - reset IoConnector
以上。
ではでは、以下について記していこうと思います。
- OANDAサンプル付属のcfg
- DataDictionary
- StartTimeとEndTime
- その他
- Tips
- JMX
- FileLogPath
- cfgのシステムProperty展開機能
OANDAサンプル付属のcfg
OANDAにFIX APIの設定をしてもらう際、サンプルのJavaアプリケーションを貰えます。
そのサンプルには、FIXにおける設定ファイルに相当するcfgファイルが含まれており、そのcfgファイルに自身のアカウントとパスワードを記述することによってサンプルJavaアプリケーションを動作させることができます。
(詳細は前回記事を参照)
このcfgを自作のFIXアプリケーションでそのまま利用するとハマります…
UseDataDictionary
OANDAのサンプルFIXアプリケーション付属のcfgではUseDataDictionary=N
が指定されています。
にも関わらず、DataDictionary=FIX42.xml
が指定されています。
UseDataDictionary=N
だと意味がないはずなんですが…
それはさておき、QuickFIX/Jのドキュメントを読む感じではこの指定方法だとrepeating groupsが作用せずにうまく値が取得できないはずです。
実際、自分が作成したFIXアプリケーションではこの指定方法でrepeating groupsがうまく作用せずに値が取れない状況になり、随分とハマりました。
具体的にどうなるかというと、例えば↓のFIXメッセージはとある時点でのUSD/JPYのbitとofferですが、
8=FIX.4.4 9=211 35=W 34=4 49=OANDA 50=RATES 52=20170620-09:09:19.900 56=account-id 55=USD/JPY 262=1497949759660 268=2 269=0 270=111.612 271=3000000 272=20170620 273=09:09:17 269=1 270=111.62 271=3000000 272=20170620 273=09:09:17 10=165 (読みやすいように整形)
repeating groupsが効いていない状態では、bitの値しか取れません…
だがしかし、OANDAのサンプルFIXアプリケーションはうまく動作しているようです。
OANDAサンプルもQuickFIX/Jを使っているっぽいのですが…
謎です…
/(^o^)\
それはさておき、UseDataDictionary=Y
にしなければならないということはわかったのですが、
DataDictionaryどこでゲットすんの?
ってなります。
これはわりとすぐに解決してQuickFIX/Jのリポジトリを漁ったら発見しました。
後は簡単。
src/main/resources/
に発見したDataDictionaryファイル(FIX44.xml
)を置いておけばmaven-resources-plugin
がbuild時に良しなにやってくれます。
これでrepeating groupsが作用して、先ほどの例でのbitしか取れない問題が解決してbitとofferの値がきちんと取れるようになります。
めでたしめでたし。
と思いきや、続きがあります。
ふと、OANDAのサンプルFIXアプリケーションはどうなっているんだろう、と思い立って漁ってみたらquickfixj-all-1.4.0-oanda.jar
なる意味深なjarを読み込んで実行していることを発見。
jarの中身を見てみると、ありましたFIX44.xml
。
QuickFIX/Jリポジトリにあるものと比べてみると結構diffがあり、ざっと見た感じquickfixj-all-1.4.0-oanda.jar
にあったDataDictionaryのほうが良さそうだったのでこっちを使うことにしました。
我ながらよく気がついたなぁと思います(ドヤァァ
めでたしめでたし。
余談ですが、OANDAのサンプルFIXアプリケーションはやっぱりツッコミどころ満載だと思います。
実行可能jarが配置されているにもかかわらず、その中身らしきclassファイルが別途、置かれています。
何のために?
StartTimeとEndTime
自作のFIXアプリケーションを何日か連続稼働していると、必ず1日1回、決まった時間にセッションを貼り直している(再起動している)ログに気が付きました。
なんでだろなーっと思ってドキュメントを調べているとOANDAサンプルのcfgの指定方法だとdailyなアプリケーションの設定になっていることがわかりました。
その設定値が表題のStartTimeとEndTimeです。
この2つの設定のみだとアプリケーションはdailyでセッションを貼り直します。
歴史的経緯でしょうか。
少なくとも現在のOANDAは月曜朝のオープンから土曜朝のクローズまでダウンタイム無しで稼働しているようなのでweeklyの設定に変更しました。
これは簡単でStartDayとEndDayを追加して何曜日の何時オープンで何曜日の何時にクローズかを指定するだけです。
具体的には↓な感じ。
StartDay=sun StartTime=19:55:00 EndDay=fri EndTime=21:05:00
念のため、ゆとりを持たせて5分ずつ前後に挟んでいます。
が、運用実績的に1分もいらないかなぁという感想です。
めでたしめでたし。
その他
FIXプロトコルにもVersionがいくつかあるようで、OANDAのサンプルFIXアプリケーションでは4.2が指定されていたのですが、ドキュメントを読む限り4.4の方が勝手が良さそうなので4.4に変更しました。
ちなみに、前述の通り、OANDAのサンプルFIXアプリケーションには4.4のDataDictionaryが含まれています。
なして4.2指定なの…
Tips
いやー、いろいろとハマったのは辛かったんですが、そのかいあってか調べている過程でいろいろな機能を発見できました。
その中から使えるなーと思った機能は自作のFIXアプリケーションに取り込んでいます。
せっかくなので、それらを紹介しようと思います。
JMX
Javaあるあるのアレです。JConsoleとかでつなぐ奴。
これを有効化するといろいろと便利になる奴です。(問われる語彙力w
QuickFIX/JがJmxExporter
クラスを用意してくれているので有効化はとっても簡単。
new JmxExporter().register(initiator);
これだけ。
そーすると何が嬉しいかというと、いろいろとあるんですが、おおきく以下の2つがあります。
- (ヒープの利用状況などの)メトリクスの可視化
- ログレベルの動的変更
それぞれについて記載します。
メトリクスの可視化
24時間365日とまでいかなくても、5日間連続稼動させると気になることがあります。
JMX + JConsoleを使うと簡単にメトリクスを見れるので便利です。
5日間連続稼動を何回か繰り返し運用してみた感じだと大体↓な傾向が見れました。
ヒープ使用率やGCの時間などを監視&通知までできるとベストですが、ただでさえ牛歩なのでいったんはこれで満足としましょう(諦め大事
ただ、0.5秒のGCはなんとかしたいなぁ…
G1GCとか検証したいとは思います。
ログレベルの動的変更
アプリケーション起動時のログレベルはinfoです。
つーかロギングの設定を何もしていないのでデフォルトの動作なんですがw
まぁ、それはゆくゆくやるとして、 JConsoleを使うとアプリケーション起動後、つまり動作中に動的にログレベルを変更できます。
やり方は簡単で、JConsleのMBeansタブ→左のBeans一覧からjava.util.logging
→Logging→Operations→setLoggerLevel(p1,p2)
のp1にクラス名(例:com.example.FixApplication
)、p2にログレベルを入れて実行するだけ。
ちなみにですが、QuickFIX/J関連も同様の手順でいろいろできます。
リセットとかHeartBeat送信とか。
FileLogPath
cfgの設定にFileLogPathという項目があります。
こいつにパスを指定するだけでイベントと送受信FIXメッセージを指定したパスにロギングしてくれます。
例えば、↓をcfgに記述すると、
[LOGGING] FileLogPath=log/
↓なログファイルが生成されます。
$ tail -n5 log/FIX.4.4-hoge0011-OANDA_RATES-testusr2-Session1.event.log 20170625-09:20:25: Session FIX.4.4:hoge0011->OANDA/RATES:testusr2-Session1 schedule is daily, 00:00:00-UTC - 00:00:00-UTC 20170625-09:20:25: Created session: FIX.4.4:hoge0011->OANDA/RATES:testusr2-Session1 20170625-09:20:27: Initiated logon request 20170625-09:20:28: Logon contains ResetSeqNumFlag=Y, resetting sequence numbers to 1 20170625-09:20:28: Received logon
$ tail -n5 log/FIX.4.4-hoge0011-OANDA_RATES-testusr2-Session1.messages.log | tr "^A" " " 8=FIX.4.4 9=421 35=B 34=2 49=OANDA 50=RATES 52=20170625-09:20:28.497 56=hoge0011 33=4 58=version: 2.4.21 (fxTrade rates server 03 [3,130] 1498382428 163.49.210.211) 58=notice: Market Data Request now supports new MarketDepth and MDUpdateType settings 58=notice: halted pairs are now marked QuoteCondition=B Text=Halted 58=notice: please include the complete server version string in all support requests 148=OANDA FIX Server Information 10=213 8=FIX.4.4 9=158 35=V 34=2 49=hoge0011 52=20170625-09:20:28.553 56=OANDA 57=RATES 262=1498382428547 263=1 264=1 265=1 146=3 55=USD/JPY 55=EUR/JPY 55=EUR/USD 267=2 269=0 269=1 10=206 8=FIX.4.4 9=243 35=W 34=3 49=OANDA 50=RATES 52=20170625-09:20:28.801 56=hoge0011 55=USD/JPY 262=1498382428547 268=2 269=0 270=111.258 271=3000000 272=20170623 273=20:59:59 276=B 58=Halted 269=1 270=111.34 271=3000000 272=20170623 273=20:59:59 276=B 58=Halted 10=231 8=FIX.4.4 9=244 35=W 34=4 49=OANDA 50=RATES 52=20170625-09:20:28.801 56=hoge0011 55=EUR/USD 262=1498382428547 268=2 269=0 270=1.11922 271=3000000 272=20170623 273=20:59:59 276=B 58=Halted 269=1 270=1.11953 271=3000000 272=20170623 273=20:59:59 276=B 58=Halted 10=026 8=FIX.4.4 9=244 35=W 34=5 49=OANDA 50=RATES 52=20170625-09:20:28.801 56=hoge0011 55=EUR/JPY 262=1498382428547 268=2 269=0 270=124.515 271=3000000 272=20170623 273=20:59:59 276=B 58=Halted 269=1 270=124.606 271=3000000 272=20170623 273=20:59:59 276=B 58=Halted 10=035
とくにmessages.log
はAcceptanceテスト用の入力データ(↓な感じらしい)に使えそうです。
I8=FIX.4.2␁35=A␁34=1␁49=TW␁52=<time>␁56=ISLD␁98=0␁108=30␁ E8=FIX.4.2␁9=57␁35=A␁34=1␁49=ISLD␁52=00000000-00:00:00␁56=TW␁98=0␁108=30␁10=0␁
ただし、全てのFIXメッセージをロギングするのでディスクを大量消費する点と、ログインパスワードもロギングされちゃうので扱いに注意です><
cfgのシステムProperty展開機能
cfgに↓のように記述するとJavaのシステムPropertyを展開することができます。
SenderCompID=${account}
# アカウントIDをhoge0011にして実行 $ java -jar -Daccount=hoge0011 fix-prototype-1.5-SNAPSHOT.jar
これ、とっても便利です。
なのにドキュメントに書いていないですorz
QuickFIX/Jのコード読んでいて違和感を感じて、デバッガでステップ実行してやっと気が付きました。
ドキュメントェ
これまた、我ながらよく気がついたなぁと思います(ドヤァァ
まとめ
ふぅ。疲れた。
FIXつらたん。
編集後記
上記以外にもMarketDataRequestでの値の取得方法がいまいちわからずGithubを検索したりと牛歩要因がほんとに多いなと思いました。
果たして完成する日は来るのか…
自動売買の道のりは長いンゴねぇ…
今後、特にやりたいことリストです。
- Acceptance(受け入れ)テストの導入
- 単体テストもまだですが…
- これができると取引時間外の土日での開発が捗る(はず)
- order(売買)実装
- 何をトリガーとして売買するかが悩みどころ
- とりあえず、単純に移動平均線に触れたことをトリガーにする
ちなみにですが、OANDAのルールがわりと大きく変更されています。
FIX使うにはプロコース必須になって、スプレッドが0.8銭になったり。
OANDAのデモ口座でFIX APIを使えるまでの手順が難解
前回↓から2週間、とりあえずデモ口座APIの疎通確認まで出来ました。
なかなか分かりづらい点が有ったので、そのことも含めて書き残そうかと思います。
先にまとめ。
- APIは3種類でSDKは今回スルー
- デモ口座REST APIは本番口座開設不要(たぶん)
- SDK(Java)とFIXは口座開設必須
- なおかつデモ口座も必要
- 本番&デモ口座でRESTで動作確認
- 本番&デモ口座でFIXのサンプルで動作確認
以下、自分が行ったことを時系列で記します。
ではでは。
口座開設
特筆すべきことは無いです。
今年からマイナンバーが増えて面倒臭くなりました。が、これに限った話ではないです。
本人確認書類、マイナンバーの提出は、最近よくある画像ファイルアップロード形式でした。
自分はスマホでパシャリと両方ともに表裏を撮影してアップしました。
不備は生じなかったようで、翌日には「簡易書留でID&パス送付しましたー」的なメールを受信、その週のうちに受け取れてログインまで出来ました。
最近は早くて良いンゴねぇ。
余談ですが、よく見かける「◯分で完了」をOANDAさんも謳うのですが、ご多分に漏れず、無理っす。入力、早すぎんだろ。
入金
API紹介ページ↓に「API使うなら25万入金してねっ」とあるのでログイン後の入金画面から入金手続きをします。
他のFX業者と同じく即時入金が可能です。
そのため、すぐさまAPI利用手続きに進むことが出来ます。
API利用手続き
ログイン後のトップ画面にAPI関連の項目があるので、そこを辿って行くと以下の2つが表示されます。
1つずつやっていきます。
REST API
こちらは簡単。
アクセスするとおもむろにアクセストークンの発行、と出るのでクリック。
アクセストークンが即時、発行されます。
ドキュメント通りに叩いてみます。
$ curl -H "Authorization: Bearer アクセストークン" \ https://api-fxtrade.oanda.com/v1/accounts { "accounts" : [ { "accountId" : ****, "accountName" : "hogehoge", "accountCurrency" : "JPY", "marginRate" : 0.04 } ] }
やっほい。
ちなみに、本番口座のアクセストークンがバレると/(^o^)\です。どこかの誰かに口座の残高が溶かされます。たぶん。
そんなリスクを抱え込むOANDAはやっぱり攻めてるなぁ。
FIX、Java API
こちらはやっかい。
アクセスするとまず、FIX or Javaの選択があります。
JavaとあるのはおそらくOANDA独自SDKのことです。
こちらは日本語ドキュメントが無い、かつ、英語ドキュメント内にあるJavaDocのコピーライトが2005と不安を煽るので今回はスルー。
ということで、FIXを選択。
次に、OANDA fxTrade PracticeのID
の入力欄があります。
※デモにてAPIをご利用になられる場合は、下記にご利用になるデモのログインIDを記載してください。 後程弊社からご連絡いたします。
とあるので、適当なIDを入力すれば後で連絡が来るのかな、と思って適当な文字列を入力しました。
これ、間違いでした。
後述します。
入力完了後、すぐにメールを受信します。おおよそ以下の旨が記載されています。
- 本番口座のAPI設定が完了したよ
- デモ口座は設定後にまた連絡すんね
- サンプルはココね(URL)
ふむふむ。
待てとのことなのでデモ口座は置いといて、とりま、本番口座でサンプルを試してみます。
サンプルを提示されたURLからダウンロードして、zipだったので展開して、READMEは無くlogファイルを見るも何も書いてない…
batファイルがあるので起動するとサッと落ちる…
仕方がないのでbatファイルをテキストエディタで開いて、絶対パスでCドライブ直下にzip展開想定ってマジかよなんか別のbat呼んでるあっ設定ファイルっぽいの指定してるlogファイルって実行時ログファイルかよ設定ファイルの説明がねぇJavaかよshell実行ファイルあるのに使われてねぇーetc…
と、奮闘します。(割愛)
そして…
C:\fix_sample\fix-client>java -Xmx32m -Djava.util.logging.config.file=etc/logging.properties -jar fix-client.jar etc/client.cfg Created session FIX.4.2:****->OANDA/TEST:testusr1-Session1 IN: ======== +-HEADER | 8 BeginString = FIX.4.2 | 9 BodyLength = 82 | 34 MsgSeqNum = 1 | 35 MsgType = A | 49 SenderCompID = OANDA | 50 SenderSubID = TEST | 52 SendingTime = 20160716-14:41:54.350 | 56 TargetCompID = **** +-BODY | 98 EncryptMethod = NONE_OTHER (0) | 108 HeartBtInt = 60 | 141 ResetSeqNumFlag = YES_RESET_SEQUENCE_NUMBERS (Y) +-TRAILER | 10 CheckSum = 129 +-END OF MESSAGE IN: ======== +-HEADER | 8 BeginString = FIX.4.2 | 9 BodyLength = 425 | 34 MsgSeqNum = 2 | 35 MsgType = B | 49 SenderCompID = OANDA | 50 SenderSubID = TEST | 52 SendingTime = 20160716-14:41:54.350 | 56 TargetCompID = **** +-BODY | 33 LinesOfText = 4 | 148 Headline = OANDA FIX Server Information | 33 LinesOfText (count = 4) | 58 Text = version: 2.4.21 (fxTrade Practice order server [3,45] 1468680114 ***.***.***.***) | 58 Text = notice: Market Data Request now supports new MarketDepth and MDUpdateType settings | 58 Text = notice: halted pairs are now marked QuoteCondition=B Text=Halted | 58 Text = notice: please include the complete server version string in all support requests +-TRAILER | 10 CheckSum = 040 +-END OF MESSAGE Operations will be executed as **** with FIX.4.2 command [M]: Market Data Request [O]: Market Order [E]: Entry Order [L]: Limit Order [T]: Stop Order [C]: Cancel Order [F]: Modify Order [S]: Order Status [R]: Enter Raw Text Message [D]: Delay [U]: Choose FIX User --------------------------- [Q]: Quit Choose Command >
キタ━━━━(゚∀゚)━━━━!!
[M]: Market Data Request
のコマンドから為替レート取得等、動作確認が出来ました。
しかし早いです。簡易書留を受け取ったら当日中にAPI使えます。
デモ口座じゃなくて本番口座ですが。
デモ口座API利用手続き
さて、目的はデモ口座でのAPI利用です。
前回も書きましたが、本番口座でAPI利用とか怖すぎるので、目的であるデモ口座でのAPI利用手続きを進めます。
さきほど、本番口座のFIX利用手続きと一緒にデモ口座利用の手続きをして「設定後にまた連絡すんね」と有りましたが、しばらく経っても連絡が有りませんでした。
ということでサポートに問合せます。
以下、意訳。
僕「まだっすか?」
サ「お前の言うデモ口座IDなんて存在しねぇよ。デモ口座開設してね♡」
衝撃の事実。
ということで、デモ口座を開設します。
こちらは開設フォーム入力後すぐにログイン出来ました。やったー。
デモ口座にログインして本番口座同様トップ画面からAPI関連の項目を辿って行きますが、本番口座にはあったFIXの利用手続き画面が有りません…
むむむ。
僕「デモ口座ID取ってログインしたけど見っかんねーんだけど。どうすればいいかおせーて?」
と、ここで本番口座FIX API利用手続き時のOANDA fxTrade PracticeのID
入力欄を思い出します。
まさかと思い、本番口座にログインしてFIX API利用手続き画面のOANDA fxTrade PracticeのID
に取得したデモ口座IDを入力して決定します。
僕「本番口座のFIX利用手続きでデモ口座ID入れて送ったけど、あってる?」
サ「おk」
fxTrade Practiceってデモ口座かーい!
サ「あと、デモ口座のFIX利用、設定したンゴ」
はやっ
何はともあれ、無事にデモ口座でFIXが使えるとの事なので、早速、さきほどのサンプルで設定ファイルを書き換えて実行してみます。
C:\fix_sample\fix-client>java -Xmx32m -Djava.util.logging.config.file=etc/logging.properties -jar fix-client.jar etc/client.cfg Created session FIX.4.2:****->OANDA/TEST:testusr1-Session1 Session FIX.4.2:****->OANDA/TEST:testusr1-Session1 disconnected Could not logon to FIX server; Exiting.
ファッ
接続できんとな。
僕「接続できん」
サ「デモ口座はドメイン違うンゴ。これな。っfxgame-fix.oanda.com」
どこにも書いてないやんけ…
fxgame site:www.oanda.jp - Google 検索
サ「あと、APIはサポ外な。海外事業部に聞いて。あ、英語な」
サーセン。
気を取り直して、設定ファイルを修正して実行します。
C:\fix_sample\fix-client>java -Xmx32m -Djava.util.logging.config.file=etc/logging.properties -jar fix-client.jar etc/client.cfg Created session FIX.4.2:****->OANDA/TEST:testusr1-Session1 IN: ======== +-HEADER | 8 BeginString = FIX.4.2 | 9 BodyLength = 82 | 34 MsgSeqNum = 1 | 35 MsgType = A | 49 SenderCompID = OANDA | 50 SenderSubID = TEST | 52 SendingTime = 20160716-15:21:22.904 | 56 TargetCompID = **** +-BODY | 98 EncryptMethod = NONE_OTHER (0) | 108 HeartBtInt = 60 | 141 ResetSeqNumFlag = YES_RESET_SEQUENCE_NUMBERS (Y) +-TRAILER | 10 CheckSum = 126 +-END OF MESSAGE IN: ======== +-HEADER | 8 BeginString = FIX.4.2 | 9 BodyLength = 416 | 34 MsgSeqNum = 2 | 35 MsgType = B | 49 SenderCompID = OANDA | 50 SenderSubID = TEST | 52 SendingTime = 20160716-15:21:22.904 | 56 TargetCompID = **** +-BODY | 33 LinesOfText = 4 | 148 Headline = OANDA FIX Server Information | 33 LinesOfText (count = 4) | 58 Text = version: 2.4.21 (fxTrade order server [2,53] 1468682482 ***.***.***.***) | 58 Text = notice: Market Data Request now supports new MarketDepth and MDUpdateType settings | 58 Text = notice: halted pairs are now marked QuoteCondition=B Text=Halted | 58 Text = notice: please include the complete server version string in all support requests +-TRAILER | 10 CheckSum = 226 +-END OF MESSAGE Operations will be executed as **** with FIX.4.2 command [M]: Market Data Request [O]: Market Order [E]: Entry Order [L]: Limit Order [T]: Stop Order [C]: Cancel Order [F]: Modify Order [S]: Order Status [R]: Enter Raw Text Message [D]: Delay [U]: Choose FIX User --------------------------- [Q]: Quit Choose Command >
キタ━━━━(゚∀゚)━━━━!!
さいごに
やっていて気が付いたのですが、デモ口座でRESTは口座開設しなくても利用できますね。たぶん。
あと、後で気が付きましたが、API紹介ページのドメインはwww.oanda.jpであるのに対し、APIドキュメントページのドメインはdeveloper.oanda.comですね。
もしやと思ってググる(fxgame site:developer.oanda.com - Google 検索)とTroubleshootingのページが引っかかり、見てみるとProblems with Connecting
の項目(一番上)にデモ口座のドメインが書いてありました。
/(^o^)\
サーセン。
しかし、サポートのレスが早いです。休日でもレスが来ます。とっても助かりました。
いやぁ~、もっぱらデモ口座APIのために口座開設したけれど、手動トレードでも使ってみるンゴ!
しかし、FIXのSEO最悪ですね。RESTにもストリーミングがあるので悩みます。
ちなみにですが、Brexitの傷はほとんど癒えました。ありがとう参院選!
だが、改憲かぁ…
つづき
OANDAはデモ口座でWebAPIを使ってFXの自動売買が試せるみたい
随所で阿鼻叫喚の様相を呈していましたが、僕もご多分に漏れず、株&FX&投信でちょっとやられました(まだ含み損状態)
まぁ、僕も日本人。日本人らしく資産はほとんどは銀行の普通預金なので微々たるダメージなのですが、
やっぱり悲しいンゴ…
それはさておき、最近、OANDA Japanの広告にやたらターゲティングされておりまして、ブラウジングしていると高確率で表示されます。
WebAPIが提供されているFX会社としてOANDAのことは知っていました。1年ほど前に僕の周辺でやたらシストレ&自動売買が流行りまして、やれMT4だのOandaAPIだのと盛り上がっていましたので。
みんなスイスフランショックとか今回のBrexitは大丈夫だったのかなぁー。
一方、話題は変わりまして、最近のトレンドとして人工知能、AI、ディープラーニングが有りますね。
FXは売買がわりかしシンプルであるおかげかOANDAのAPIを使ってオレオレアルゴリズムでディープラーニングでなんちゃらなんちゃらな記事をちょくちょく見かけます。
実際に自分のお金でオレオレアルゴリズムのテストするとか、
まぢパネェっす
って思っていたのですが、OANDAさん、口座開設すると、なんともれなくデモ口座でAPIが試せるみたいです。
すごい。
というわけで口座開設してデモ口座でAPI試してみようかなと思います。
しかし、金融口座がどんどん増えていく…
マネーアグリゲーションサービス使っていても辛いレベルです…
つづき
いまさらk10statでLlanoの低電圧化をして自鯖の省エネを夢見る
5/14のやつです。
タイトルにも有りますが、夢見てます。まだ、道半ばです。
いまさらなLlanoなので、より新しいCPUのk10stat相当は以下の通りです。(使ったこと無い)
- Trinity/Richland → PSCheck
- Kaveri → K15TK
しかし、最近はスマホCPUの話題ばかりで寂しいなぁ。
クリックで進む。右クリックで戻る。
AMD A-series プロセッサ A10 7870K BlackEdition FM2+ AD787KXDJCBOX
- 出版社/メーカー: AMD
- 発売日: 2015/05/29
- メディア: Personal Computers
- この商品を含むブログを見る
Redisのreplication構成+自動failover(sentinel)をVagrantで構築してnode.js(express + ioredis)から利用する
はじめに
Redisのcluster構成に関する記述はありません。
Redisの構成に関しては公式ドキュメント(英語)にあるreplicationとsentinelの記述の通りです。
目新しいことは無いと思います。
node.jsのRedisクライアントにはioredisを利用します。node_redisに関する記述はありません。
expressのsession-storeにRedisを利用する前提で記述してあります。
なぜCookieだけでないのか、なぜsession-storeにRDBなどの他のミドルウェアを利用しないのか、のような話題の記述はありません。
取り扱う内容
- Vagrant
- 下記構成のRedisサーバを構築
- Redis
- replication
- master-slave構成のこと
- 自動failover
- masterのdown検知時に自動で(人の手を介さずに)slaveがmasterに昇格すること
- masterの監視はredis-sentinelを使用
- replication
- expressからの利用
- 構築したRedisをexpressのsession-storeとして利用
ローカルの仮想マシンでreplicationと自動failoverをしてもあまり意味がないので、可能ならばAWSの複数リージョンを使うなど、IaaSを活用したかったのですが、お金がかかるので諦めました。
以下は全てmacで実行していますが、Vagrantに依存するものがほとんどのため、Vagrantが動作する環境ならばどこでも大丈夫だと思います。
Redisの準備
Vagrantを使用してRedisサーバを3台、作成します。
1台のサーバあたりメモリを500MBちょっとつかうのでメモリをとっても圧迫します…。使用しているmacのメモリの8GBでは複数の仮想マシンを動作させるには辛い様子で、途中、動作が重たくなったのでchromeを閉じました。
あと、電池がよく減ります。
構成
1台はmasterのRedis、他2台はslaveのRedisを配置します。
それぞれのサーバにはRedisのプロセスとは別にsentinelのプロセスを起動します。
+---------------+
| 192.168.50.10 |
|---------------|
| master |
| sentinel-1 |
+---------------+
|
+---------------+ | +---------------+
| 192.168.50.11 |---------+---------| 192.168.50.12 |
|---------------| |---------------|
| slave-1 | | slave-2 |
| sentinel-2 | | sentinel-3 |
+---------------+ +---------------+
Vagrantのインストール
brew install Caskroom/cask/vagrant
Vagrantはとっても便利です。よく利用します。
今回はubuntu公式のBoxを使用しました。
余談ですが、仮想マシンに特化したメジャーなBoxは無いのでしょうか…。Boxのダウンロードも前述したメモリ使用量も電池の減りも、とにかく重いです…
それとも自分が情弱なだけですでに存在するのでしょうか。
Vagrantfile
仮想マシンの構成を定義するVagrantfileを用意します。
ココにRedisをインストールするVagrantfileとプロビジョニングのスクリプトがあったので利用させてもらいました。
ただし、これは1台の仮想マシン用のコードであるため、前述の構成に対応する変更を加えます。
Rubyは詳しくないのですが、end
の数がすごいことなっています。JavaScriptのコールバックヘルみたいです。
プロビジョニングのスクリプトにも変更を加えます。
1台はmaster、他2台はslaveにする処理とsentinelを起動する処理です。
本番運用ではmasterとslaveで設定ファイルを分け、sentinelは自動起動スクリプトを登録したりするべきかと思いますが手を抜きます。
sentinelの設定ファイルを追加します。
デーモン化とPIDファイル置き場所の指定、初期時点でのmasterの場所とquorumを設定します。
quorumについては割愛します。
まとめたコードはGithubに置いてあります。
起動
vagrant up
初回はとっても長いです。
osのboxイメージ取得から処理されるので強いネットワーク回線必須です。
自分は最初、テザリング環境でトライしてむりぽを悟り、ご自宅でリトライをいたしました。わかっていましたけどね…
構成の確認
ホストマシン(ここではmac)にRedisのクライアントをインストールしてRedisの状態を確認します。
クライアントをインストールと言いつつ、サーバもインストールされてしまいますが無視します。
brew install redis
replication
$ redis-cli -h 192.168.50.10 info replication # Replication role:master <- ココ connected_slaves:2 slave0:ip=192.168.50.11,port=6379,state=online,offset=245191,lag=0 slave1:ip=192.168.50.12,port=6379,state=online,offset=245191,lag=0 master_repl_offset:245191 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:245190 $ redis-cli -h 192.168.50.11 info replication # Replication role:slave <- ココ master_host:192.168.50.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:247884 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 $ redis-cli -h 192.168.50.12 info replication # Replication role:slave <- ココ master_host:192.168.50.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:248744 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
少し使った後の実行結果なのでまっさらではないですが、良い感じです。
sentinel
$ redis-cli -h 192.168.50.10 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.10:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.11 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.10:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.12 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.10:6379,slaves=3,sentinels=3
大丈夫そうです。
動作確認
実際にreplicationと自動failoverの動作確認をします。
replication
masterに値をセットしてslaveで取得できるかを確認します。
$ redis-cli -h 192.168.50.10 set hoge 'fuga' OK $ redis-cli -h 192.168.50.10 get hoge "fuga" $ redis-cli -h 192.168.50.11 get hoge "fuga" $ redis-cli -h 192.168.50.12 get hoge "fuga"
良いですが、どうしてもローカル環境なので当たり前感が…。
AWSの複数のリージョンにサーバを設置するなど、遠隔地環境でタイムラグ等の実測値がどうなるかは気になります。
自動failoverの確認
masterを擬似的に落とします。
$ redis-cli -h 192.168.50.10 DEBUG sleep 30 OK
roleがどうなったかを確認。
$ redis-cli -h 192.168.50.10 info replication # Replication role:slave <- slaveに変わった master_host:192.168.50.11 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:374134 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:66095 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:66094 $ redis-cli -h 192.168.50.11 info replication # Replication role:master <- masterに変わった connected_slaves:2 slave0:ip=192.168.50.10,port=6379,state=online,offset=376249,lag=0 slave1:ip=192.168.50.12,port=6379,state=online,offset=376263,lag=0 master_repl_offset:376263 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:341012 repl_backlog_histlen:35252 $ redis-cli -h 192.168.50.12 info replication # Replication role:slave <- そのまま master_host:192.168.50.11 master_port:6379 master_link_status:up master_last_io_seconds_ago:1 master_sync_in_progress:0 slave_repl_offset:383073 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
グッドです。
sentinelも確認します。
$ redis-cli -h 192.168.50.10 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.11:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.11 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.11:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.12 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.11:6379,slaves=3,sentinels=3
がはは、グッドだー
因みにですが、slaveになった192.168.50.10には書き込めなくなります。
$ redis-cli -h 192.168.50.10 set hoge huga (error) READONLY You can't write against a read only slave.
つまり、クライアントはRedisサーバがfailoverで切り替わった際に向き先を変更しなければ書き込みができなくなってしまいます。
せっかくRedisサーバが自動failoverしてもクライアントが対応しないと意味無いです。
クライアントがnode.jsの場合どうするかを後述します。
再度、192.168.50.10
をmasterにします。
$ redis-cli -h 192.168.50.11 DEBUG sleep 30 OK
expressからの利用
自分のmacのスペック的にこれ以上は仮想マシンを追加したくなかったので、ホストマシンでnode.jsのアプリケーションを起動してそこから仮想マシン上のRedisをコールすることにします。
node.jsもプラットフォームの対応が素晴らしいのでmacに限らず大体どこでも大丈夫だと思います。
brew install node.js
npm install express-generator -g
express redis-failover-hello-world
cd redis-failover-hello-world && npm install
前述のとおり、ioredisを使用します。
npm install --save ioredis
セッションの保存先をRedisにするnpmパッケージをインストール。
npm install --save connect-redis express-session
app.jsを書き換えて、セッションの保存先をVagrant上に構築したRedisに指定します。
起動。
$ DEBUG=ioredis:* npm start > redis-failover-hello-world@0.0.0 start /Users/hhr/git/redis-failover-hello-world > node ./bin/www ioredis:redis status[localhost:6379]: [empty] -> connecting +0ms ioredis:redis status[192.168.50.10:26379]: [empty] -> connecting +7ms ioredis:redis queue command[0] -> sentinel(get-master-addr-by-name,mymaster) +4ms ioredis:redis status[192.168.50.10:26379]: connecting -> connect +76ms ioredis:redis status[192.168.50.10:26379]: connect -> ready +1ms ioredis:connection send 1 commands in offline queue +0ms ioredis:redis write command[0] -> sentinel(get-master-addr-by-name,mymaster) +1ms ioredis:redis status[192.168.50.10:6379]: connecting -> connect +11ms ioredis:redis write command[0] -> info() +1ms ioredis:redis status[192.168.50.10:6379]: connect -> ready +3ms ioredis:redis status[192.168.50.10:26379]: ready -> close +1ms ioredis:connection skip reconnecting since the connection is manually closed. +0ms ioredis:redis status[192.168.50.10:26379]: close -> end +0ms
ふむふむ、
192.168.50.10:26379
に接続(sentinelに接続)redis-cli -h 192.168.50.10 -p 26379 sentinel get-master-addr-by-name mymaster
的なコマンド実行(sentinelにmasterの場所を問い合わせ)- (
192.168.50.10
が返ってくる) 192.168.50.10:6379
に接続(masterに接続)redis-cli -h 192.168.50.10 -p 6379 info
的なコマンド実行(masterであることを確認している?)192.168.50.10:26379
を切断
のようです。
HTTPサーバが起動したので、ブラウザからアクセスして挙動を確認します。
ioredis:redis write command[0] -> set(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,{"cookie":{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"}},EX,86400) +2m GET / 200 43.978 ms - 170 ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +47ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +3ms GET /stylesheets/style.css 200 1.763 ms - 111 ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +162ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +41ms GET /favicon.ico 404 40.017 ms - 1919 ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +27ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +28ms GET /favicon.ico 404 27.736 ms - 1919
favicon.ico
が無い、は無視します。
セッション情報をRedisに書き込んでそれを取得している様子が確認できます。
ブラウザの開発ツール等でHTTPヘッダやクッキーを見るとs:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs...
があり、これがセッションIDであることがわかります。
ブラウザリロード。
ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +4m ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +14ms GET / 304 13.421 ms - - ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +40ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +4ms GET /stylesheets/style.css 304 2.832 ms - -
2回目以降はセッションIDの有効期限内であれば書き込みはされないようです。
有効期限切れのパターンは割愛します。
ターミナルからも見てみます。
$ redis-cli -h 192.168.50.10 keys '*' 1) "hoge" 2) "sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs" $ redis-cli -h 192.168.50.10 get 'sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs' "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"}}"
セッションの保存、良い感じです。
slaveに保存されているかの確認は割愛します。
failoverさせます。
redis-cli -h 192.168.50.10 DEBUG sleep 30
ioredis:redis status[192.168.50.10:6379]: ready -> close +7m ioredis:connection reconnect in 2ms +0ms ioredis:redis status[192.168.50.10:6379]: close -> reconnecting +1ms ioredis:redis status[192.168.50.10:6379]: reconnecting -> connecting +2ms ioredis:redis status[192.168.50.11:26379]: [empty] -> connecting +1ms ioredis:redis queue command[0] -> sentinel(get-master-addr-by-name,mymaster) +1ms ioredis:redis status[192.168.50.11:26379]: connecting -> connect +1ms ioredis:redis status[192.168.50.11:26379]: connect -> ready +0ms ioredis:connection send 1 commands in offline queue +1ms ioredis:redis write command[0] -> sentinel(get-master-addr-by-name,mymaster) +0ms ioredis:redis status[192.168.50.11:6379]: connecting -> connect +2ms ioredis:redis write command[0] -> info() +0ms ioredis:redis status[192.168.50.11:26379]: ready -> close +1ms ioredis:connection skip reconnecting since the connection is manually closed. +1ms ioredis:redis status[192.168.50.11:26379]: close -> end +0ms ioredis:redis status[192.168.50.11:6379]: connect -> ready +1ms
おぉ、切り替わりました。 注目すべきは、HTTPアクセスが無い状態で勝手に接続先が切り替わったことです。大変よい感じです。うふふ。
さいごに
以上、ざっとですが自動failoverするRedisサーバをexpressで使用することができました。
masterがdownして、その後に復帰したらどうなるかとか無いので本当にざっとです…
個人で自動failoverする構成の需要が高くないためか、割りと調べることに苦労しました…
まぁ、AWSやherokuとかではたいてい機能が用意されているので地味なネタなのかもです。
- 作者: Josiah L. Carlson,長尾高弘
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2013/12/27
- メディア: 大型本
- この商品を含むブログ (4件) を見る