HHR’s diary

日記は無理でも週記…いや、月記でがんばろう。

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点ができればいいかなとなりました。

  1. 前回記事でテスト書かねば、と宣言したので継続化できる環境がほしい。
  2. バージョンアップ作業はルーチンワークなので自動化したい、オペミスするとカッコ悪いので。
  3. Mavenなのでビルド成果物も作成される、バージョン毎に自動で保存しておきたいでもMavenレポジトリ持ってないとりまGitHub Releaseで。

ということで、まずはこの3点が達成されることを目的にします。

まぁ、1.はCIサービスを使い始めた時点でほぼ達成かなと、コードをGitHubにcommitすれば勝手にテストを実施してくれるので。後はテストを書くだけですね、書いていませんが…

2.と3.が今回の本題です。

試行錯誤の結果、maven-release-plugingithub-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-plugingithub-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 APImvn 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を修正してprepareperformを実行するようにします。

こんな感じ。

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)

Mavenpackageフェーズで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にしました。

idgithub決め打ちです。

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は対応していなかったっていう…

(´・ω・`)

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化

OANDA(つーかFIX API)で自動売買プログラム作成が大変

前回↓から約1年(!)、とりあえずFIX APIでレート取得が出来ました。

hhr.hatenablog.com

いやー、牛歩もいいところですね。

まぁ、実際のとろこはほとんど放置状態だったんですが。

たまに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ファイルが別途、置かれています。

何のために?

Javaソースコードをそのまま置いてほしかったっす…

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日間連続稼動させると気になることがあります。

メモリリークとかGCとかGCとか。

JMX + JConsoleを使うと簡単にメトリクスを見れるので便利です。

5日間連続稼動を何回か繰り返し運用してみた感じだと大体↓な傾向が見れました。

  • Max80MBのヒープで動作
  • GCで0.5秒くらいSTW
    • 週1回発生
    • CPUがAtomなので…

ヒープ使用率や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 Japan

OANDAのデモ口座でFIX APIを使えるまでの手順が難解

前回↓から2週間、とりあえずデモ口座APIの疎通確認まで出来ました。

hhr.hatenablog.com

なかなか分かりづらい点が有ったので、そのことも含めて書き残そうかと思います。

先にまとめ。

  • APIは3種類でSDKは今回スルー
    1. REST
    2. OANDA独自SDK(Java)
    3. FIX(FX業界デファクトプロトコルらしい)
  • デモ口座REST APIは本番口座開設不要(たぶん)
  • SDK(Java)とFIXは口座開設必須
    • なおかつデモ口座も必要
  • 本番&デモ口座でRESTで動作確認
  • 本番&デモ口座でFIXのサンプルで動作確認

以下、自分が行ったことを時系列で記します。

ではでは。

口座開設

特筆すべきことは無いです。

今年からマイナンバーが増えて面倒臭くなりました。が、これに限った話ではないです。

本人確認書類、マイナンバーの提出は、最近よくある画像ファイルアップロード形式でした。
自分はスマホでパシャリと両方ともに表裏を撮影してアップしました。

不備は生じなかったようで、翌日には「簡易書留でID&パス送付しましたー」的なメールを受信、その週のうちに受け取れてログインまで出来ました。

最近は早くて良いンゴねぇ。

余談ですが、よく見かける「◯分で完了」をOANDAさんも謳うのですが、ご多分に漏れず、無理っす。入力、早すぎんだろ。

OANDA Japan

入金

API紹介ページ↓に「API使うなら25万入金してねっ」とあるのでログイン後の入金画面から入金手続きをします。

www.oanda.jp

他の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 Japan MT4

つづき

hhr.hatenablog.com

OANDAはデモ口座でWebAPIを使ってFXの自動売買が試せるみたい

Brexit

随所で阿鼻叫喚の様相を呈していましたが、僕もご多分に漏れず、株&FX&投信でちょっとやられました(まだ含み損状態)

まぁ、僕も日本人。日本人らしく資産はほとんどは銀行の普通預金なので微々たるダメージなのですが、

やっぱり悲しいンゴ…

それはさておき、最近、OANDA Japanの広告にやたらターゲティングされておりまして、ブラウジングしていると高確率で表示されます。

WebAPIが提供されているFX会社としてOANDAのことは知っていました。1年ほど前に僕の周辺でやたらシストレ&自動売買が流行りまして、やれMT4だのOandaAPIだのと盛り上がっていましたので。

みんなスイスフランショックとか今回のBrexitは大丈夫だったのかなぁー。

一方、話題は変わりまして、最近のトレンドとして人工知能、AI、ディープラーニングが有りますね。

FXは売買がわりかしシンプルであるおかげかOANDAAPIを使ってオレオレアルゴリズムディープラーニングでなんちゃらなんちゃらな記事をちょくちょく見かけます。

実際に自分のお金でオレオレアルゴリズムのテストするとか、

まぢパネェっす

って思っていたのですが、OANDAさん、口座開設すると、なんともれなくデモ口座でAPIが試せるみたいです。

すごい。

というわけで口座開設してデモ口座でAPI試してみようかなと思います。

しかし、金融口座がどんどん増えていく…
マネーアグリゲーションサービス使っていても辛いレベルです…

OANDA Japan MT4

つづき

hhr.hatenablog.com

いまさらk10statでLlanoの低電圧化をして自鯖の省エネを夢見る

5/14のやつです。

タイトルにも有りますが、夢見てます。まだ、道半ばです。

いまさらなLlanoなので、より新しいCPUのk10stat相当は以下の通りです。(使ったこと無い)

  • Trinity/Richland → PSCheck
  • Kaveri → K15TK

しかし、最近はスマホCPUの話題ばかりで寂しいなぁ。



chromesafariで動作確認

クリックで進む。右クリックで戻る。

 

AMD A-series プロセッサ A10 7870K BlackEdition FM2+ AD787KXDJCBOX

AMD A-series プロセッサ A10 7870K BlackEdition FM2+ AD787KXDJCBOX

Redisのreplication構成+自動failover(sentinel)をVagrantで構築してnode.js(express + ioredis)から利用する

はじめに

Redisのcluster構成に関する記述はありません。

Redisの構成に関しては公式ドキュメント(英語)にあるreplicationsentinelの記述の通りです。
目新しいことは無いと思います。

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を使用
  • expressからの利用
    • 構築したRedisをexpressのsession-storeとして利用

ローカルの仮想マシンでreplicationと自動failoverをしてもあまり意味がないので、可能ならばAWS複数リージョンを使うなど、IaaSを活用したかったのですが、お金がかかるので諦めました。

以下は全てmacで実行していますが、Vagrantに依存するものがほとんどのため、Vagrantが動作する環境ならばどこでも大丈夫だと思います。

Redisの準備

f:id:HHR:20160116165435p:plain:right

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

f:id:HHR:20160116165328p:plain:w270,right

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からの利用

f:id:HHR:20160116233047p:plain:right

自分の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

ふむふむ、

  1. 192.168.50.10:26379に接続(sentinelに接続)
  2. redis-cli -h 192.168.50.10 -p 26379 sentinel get-master-addr-by-name mymaster的なコマンド実行(sentinelにmasterの場所を問い合わせ)
  3. 192.168.50.10が返ってくる)
  4. 192.168.50.10:6379に接続(masterに接続)
  5. redis-cli -h 192.168.50.10 -p 6379 info的なコマンド実行(masterであることを確認している?)
  6. 192.168.50.10:26379を切断

のようです。

HTTPサーバが起動したので、ブラウザからアクセスして挙動を確認します。

http://localhost:3000

  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とかではたいてい機能が用意されているので地味なネタなのかもです。

Redis入門 インメモリKVSによる高速データ管理

Redis入門 インメモリKVSによる高速データ管理

コマンドラインだけでoAuth(Y!ID連携)のアクセストークンを取得してみる

11/07のやつです。

 

chromesafariで動作確認

クリックで進む。右クリックで戻る。