<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Namiking.net</title>
    <link>http://blog.namiking.net/</link>
    <description>Recent content on Namiking.net</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>jp-ja</language>
    <lastBuildDate>Mon, 27 Nov 2017 18:00:00 +0900</lastBuildDate>
    <atom:link href="http://blog.namiking.net/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>パーサーコンビネーターを使って自然言語風のテキストからパラメーターを抽出する</title>
      <link>http://blog.namiking.net/post/2017/11/parse-text-for-bot-using-parsimmon/</link>
      <pubDate>Mon, 27 Nov 2017 18:00:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2017/11/parse-text-for-bot-using-parsimmon/</guid>
      <description>

&lt;p&gt;CLI と違い、引数を渡す方法が標準化しておらず、パースを実装する必要があった。Slack の &lt;code&gt;/remind&lt;/code&gt; スラッシュコマンドのように、自然言語風に Bot へのパラメーターを抽出したい。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/remind me to drink water at 3pm every day
/remind me on June 1st to wish Linda happy birthday
/remind #team-alpha to update the project status every Monday at 9am
/remind @jessica about the interview in 3 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;正規表現を使うと複雑になりがちな上のようなパラメーター抽出のため、パーサーコンビネーターを使って、パーサーを実装してみる。&lt;/p&gt;

&lt;h3 id=&#34;実装したいこと:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;実装したいこと&lt;/h3&gt;

&lt;p&gt;例えば、CircleCI の特定のジョブを Bot を通じて Slack から起動するために、自然言語風のテキストからパラメーターを抽出したい。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;post build to (username)/(reponame) [on (ブランチ名)] [at (コミットハッシュ)] [for (ジョブ名)]
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;パラメーター抽出例:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;パラメーター抽出例&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;post build to namikingsoft/namikingsoft.github.io on master for deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;repo&amp;quot;: &amp;quot;namikingsoft/namikingsoft.github.io&amp;quot;,
  &amp;quot;branch&amp;quot;: &amp;quot;master&amp;quot;,
  &amp;quot;job&amp;quot;: &amp;quot;deploy&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;実装してみる:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;実装してみる&lt;/h3&gt;

&lt;p&gt;上の要件を実装したコード例と軽い解説。&lt;/p&gt;

&lt;h4 id=&#34;00-ソースコード全体:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;00. ソースコード全体&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;const P = require(&#39;parsimmon&#39;);
const R = require(&#39;ramda&#39;);

// map functions
const mapToThirdArg = (_1, _2, _3) =&amp;gt; _3;
const mapToRepo = (_1, _2, _3, _4, _5) =&amp;gt; `${_1}/${_5}`;
const reduceNodeToObj = R.reduce((acc, x) =&amp;gt; R.merge({ [x.name]: x.value })(acc), {});
const transForSentence = R.pipe(mapToThirdArg, reduceNodeToObj);

// atoms
const _ = P.whitespace;
const _o = P.optWhitespace;
const to = P.string(&#39;to&#39;);
const on = P.string(&#39;on&#39;);
const at = P.string(&#39;at&#39;);
const fr = P.string(&#39;for&#39;); // TODO: alt `for`
const slash = P.string(&#39;/&#39;);
const command = P.regex(/post +build/i);
const digit = P.digit;
const letterSmall = P.range(&#39;a&#39;, &#39;z&#39;);
const letterLarge = P.range(&#39;A&#39;, &#39;Z&#39;);
const letter = P.alt(letterSmall, letterLarge);
const hex = P.alt(P.range(&#39;a&#39;, &#39;f&#39;), digit);
const symbolForSep = P.oneOf(&#39;._-&#39;);
const symbolForBranch = P.oneOf(&#39;._-/#+&#39;);

// parameters
const username = P.alt(letter, digit, symbolForSep).many().tie();
const reponame = P.alt(letter, digit, symbolForSep).many().tie();
const branch = P.alt(letter, digit, symbolForBranch).many().tie();
const job = P.alt(letter, digit, symbolForSep).many().tie();
const repo = P.seqMap(username, _o, slash, _o, reponame, mapToRepo);
const revision = hex.many().tie();

// nodes
const nodeRepo = P.seqMap(to, _, repo, mapToThirdArg).node(&#39;repo&#39;);
const nodeJob = P.seqMap(fr, _, job, mapToThirdArg).node(&#39;job&#39;);
const nodeBranch = P.seqMap(on, _, branch, mapToThirdArg).node(&#39;branch&#39;);
const nodeRevision = P.seqMap(at, _, revision, mapToThirdArg).node(&#39;revision&#39;);
const node = P.alt(nodeRepo, nodeJob, nodeBranch, nodeRevision);
const sentence = P.seqMap(command, _, node.sepBy(_), transForSentence);

// parse
const text1 =
  &#39;post build to namikingsoft/namikingsoft.github.io on master at abcd1234 for deploy&#39;;
const text2 =
  &#39;pOSt  BuilD   for   deploy on   master to  namikingsoft  /  namikingsoft.github.io&#39;;

sentence.tryParse(text1);
// {
//   &amp;quot;job&amp;quot;: &amp;quot;deploy&amp;quot;,
//   &amp;quot;revision&amp;quot;: &amp;quot;abcd1234&amp;quot;,
//   &amp;quot;branch&amp;quot;: &amp;quot;master&amp;quot;,
//   &amp;quot;repo&amp;quot;:&amp;quot;namikingsoft/namikingsoft.github.io&amp;quot;
// }

sentence.tryParse(text2);
// {
//   &amp;quot;job&amp;quot;: &amp;quot;deploy&amp;quot;,
//   &amp;quot;branch&amp;quot;: &amp;quot;master&amp;quot;,
//   &amp;quot;repo&amp;quot;:&amp;quot;namikingsoft/namikingsoft.github.io&amp;quot;
// }

sentence.tryParse(&#39;illegal text example&#39;);
// -&amp;gt; Exception!
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;RunKit でコードを実行する&lt;br /&gt;
&lt;a href=&#34;https://runkit.com/namikingsoft/parse-text-for-bot-using-parsimmon&#34;&gt;https://runkit.com/namikingsoft/parse-text-for-bot-using-parsimmon&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&#34;01-使っている-npm-モジュール:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;01. 使っている npm モジュール&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;const P = require(&#39;parsimmon&#39;);
const R = require(&#39;ramda&#39;);
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;parsimmon-パーサーコンビネーターライブラリ:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;Parsimmon - パーサーコンビネーターライブラリ&lt;/h5&gt;

&lt;p&gt;JS のパーサーコンビネーターライブラリの１つ。Haskell の &lt;code&gt;Parserc&lt;/code&gt; ライクに使える。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: jneen/parsimmon&lt;br /&gt;
&lt;a href=&#34;https://github.com/jneen/parsimmon&#34;&gt;https://github.com/jneen/parsimmon&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5 id=&#34;ramda-関数型プログラミング支援ライブラリ:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;Ramda - 関数型プログラミング支援ライブラリ&lt;/h5&gt;

&lt;p&gt;関数型プログラミングライブラリの１つ。今回はパーサーの戻り値調整のみに使った。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ramda Documentation&lt;br /&gt;
&lt;a href=&#34;http://ramdajs.com/&#34;&gt;http://ramdajs.com/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&#34;02-字句の定義:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;02. 字句の定義&lt;/h4&gt;

&lt;p&gt;入力文を構成する要素を BNF のような感覚で字句の定義を行っていく。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// atoms
const _ = P.whitespace;
const _o = P.optWhitespace;
const to = P.string(&#39;to&#39;);
const on = P.string(&#39;on&#39;);
const at = P.string(&#39;at&#39;);
const fr = P.string(&#39;for&#39;); // TODO: alt `for`
const slash = P.string(&#39;/&#39;);
const command = P.regex(/post +build/i);
const digit = P.digit;
const letterSmall = P.range(&#39;a&#39;, &#39;z&#39;);
const letterLarge = P.range(&#39;A&#39;, &#39;Z&#39;);
const letter = P.alt(letterSmall, letterLarge);
const hex = P.alt(P.range(&#39;a&#39;, &#39;f&#39;), digit);
const symbolForSep = P.oneOf(&#39;._-&#39;);
const symbolForBranch = P.oneOf(&#39;._-/#+&#39;);

// parameters
const username = P.alt(letter, digit, symbolForSep).many().tie();
const reponame = P.alt(letter, digit, symbolForSep).many().tie();
const branch = P.alt(letter, digit, symbolForBranch).many().tie();
const job = P.alt(letter, digit, symbolForSep).many().tie();
const repo = P.seqMap(username, _o, slash, _o, reponame, mapToRepo);
const revision = hex.many().tie();
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;03-パーサーの構築:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;03. パーサーの構築&lt;/h4&gt;

&lt;p&gt;パーサーを構成する字句を組み合わせたり、関数出力の調整を行う。ノード定義は &lt;code&gt;alt&lt;/code&gt; を使っても、どの要素が該当したか識別できるするために &lt;code&gt;node&lt;/code&gt; で名前をつけていくイメージ。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// map functions
const mapToThirdArg = (_1, _2, _3) =&amp;gt; _3;
const mapToRepo = (_1, _2, _3, _4, _5) =&amp;gt; `${_1}/${_5}`;
const reduceNodeToObj = R.reduce((acc, x) =&amp;gt; R.merge({ [x.name]: x.value })(acc), {});
const transForSentence = R.pipe(mapToThirdArg, reduceNodeToObj);

// nodes
const nodeRepo = P.seqMap(to, _, repo, mapToThirdArg).node(&#39;repo&#39;);
const nodeJob = P.seqMap(fr, _, job, mapToThirdArg).node(&#39;job&#39;);
const nodeBranch = P.seqMap(on, _, branch, mapToThirdArg).node(&#39;branch&#39;);
const nodeRevision = P.seqMap(at, _, revision, mapToThirdArg).node(&#39;revision&#39;);
const node = P.alt(nodeRepo, nodeJob, nodeBranch, nodeRevision);
const sentence = P.seqMap(command, _, node.sepBy(_), transForSentence);
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;04-パーサーを使う:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;04. パーサーを使う&lt;/h4&gt;

&lt;p&gt;定義した構文に沿ったテキストが入力された場合は、ノードの名前をキーとしたオブジェクトとして返され、そうでない場合は&lt;strong&gt;例外が発生&lt;/strong&gt;する。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// parse
const text1 =
  &#39;post build to namikingsoft/namikingsoft.github.io on master at abcd1234 for deploy&#39;;
const text2 =
  &#39;pOSt  BuilD   for   deploy on   master to  namikingsoft  /  namikingsoft.github.io&#39;;

sentence.tryParse(text1);
// {
//   &amp;quot;job&amp;quot;: &amp;quot;deploy&amp;quot;,
//   &amp;quot;revision&amp;quot;: &amp;quot;abcd1234&amp;quot;,
//   &amp;quot;branch&amp;quot;: &amp;quot;master&amp;quot;,
//   &amp;quot;repo&amp;quot;:&amp;quot;namikingsoft/namikingsoft.github.io&amp;quot;
// }

sentence.tryParse(text2);
// {
//   &amp;quot;job&amp;quot;: &amp;quot;deploy&amp;quot;,
//   &amp;quot;branch&amp;quot;: &amp;quot;master&amp;quot;,
//   &amp;quot;repo&amp;quot;:&amp;quot;namikingsoft/namikingsoft.github.io&amp;quot;
// }

sentence.tryParse(&#39;illegal text example&#39;);
// -&amp;gt; Exception!
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;まとめ:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;まとめ&lt;/h3&gt;

&lt;p&gt;頑張れば正規表現で書けなくもなさそうなテキストパースを、パーサーコンビネーターで書いてみて、思ったこといくつか。&lt;/p&gt;

&lt;h4 id=&#34;各要素に名前を付けられる-組み合わせることができる:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;各要素に名前を付けられる ＆ 組み合わせることができる&lt;/h4&gt;

&lt;p&gt;正規表現で複雑なパーサーを書くと、意味不明な文字列の羅列になりやすく、リーダブルに書くことが難しいが、字句レベルから変数にできて、再利用もしやすい点が良いと感じた。&lt;/p&gt;

&lt;h4 id=&#34;parsimmon-ドキュメントが-parsec-より簡潔でわかりやすい:7121f45fda8b34b5935206c9ca0a9e6f&#34;&gt;Parsimmon ドキュメントが Parsec より簡潔でわかりやすい&lt;/h4&gt;

&lt;p&gt;Haskell の Parsec でパーサーを実装していたときは、&lt;a href=&#34;https://hackage.haskell.org/package/parsec&#34;&gt;Hackage&lt;/a&gt; を見ても、&lt;a href=&#34;https://www.google.com/search?q=haskell+parsec+documentation&#34;&gt;ググっても&lt;/a&gt;、いまいち使い方がわからず、入門用にまとまっているドキュメントを探すのに苦労したが、&lt;a href=&#34;https://github.com/jneen/parsimmon/blob/master/API.md&#34;&gt;Parsimmon ドキュメント&lt;/a&gt;はコード例とともに簡潔にまとまっていて、実装がしやすかった。Parsec の練習用にも良いかもしれない。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/jneen/parsimmon/tree/master/examples&#34;&gt;Examples&lt;/a&gt; には、軽量スクリプト言語のパーサーや JS Linter の実装例もあったので、より複雑なパーサーを構築したくなったときに参照したい。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>静的型チェッカーflowでReact&#43;Reduxのサンプルアプリを組んでみた</title>
      <link>http://blog.namiking.net/post/2016/05/react-redux-using-flow-example/</link>
      <pubDate>Sun, 22 May 2016 20:00:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/05/react-redux-using-flow-example/</guid>
      <description>

&lt;p&gt;JavaScript型チェッカー&lt;a href=&#34;http://flowtype.org/&#34;&gt;flow&lt;/a&gt;を使って、React+Reduxで簡単なカウンターのサンプルアプリケーションを組んでみたので、その際のいくつかのポイントなどをまとめておきます。&lt;/p&gt;

&lt;h3 id=&#34;サンプルアプリについて:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;サンプルアプリについて&lt;/h3&gt;

&lt;p&gt;ボタンを押したら数字がインクリメントされるタイプのよくあるサンプルプログラム。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/05/react-redux-flow-sample/preview.gif&#34; alt=&#34;サンプルアプリPreview&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: namikingsoft/react-redux-using-flow-example
&lt;a href=&#34;https://github.com/namikingsoft/react-redux-using-flow-example&#34;&gt;https://github.com/namikingsoft/react-redux-using-flow-example&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&#34;ソース周りのファイル構成:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;ソース周りのファイル構成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;react-redux-using-flow-example
|-- src
|   |-- actions
|   |   `-- counter.js         # カウンターアクションの定義
|   |-- components
|   |   `-- Button.js          # ボタン用コンポーネント
|   |-- containers
|   |   `-- LayoutContainer.js # 各ページの側端コンテナ
|   |-- declares               # 外部モジュールの型定義 (ほぼanyをexports)
|   |   `-- ****.js
|   |-- index.html             # ベースHTML
|   |-- index.js               # フロント側エンドポイント
|   |-- pages
|   |   |-- CounterPage.js     # カウンターページ
|   |   |-- HelloPage.js       # 挨拶用ページ
|   |   `-- TopPage.js         # トップページ
|   |-- reducers
|   |   |-- counter.js         # カウンターReducer
|   |   `-- index.js           # Reducerのインデックス
|   |-- sagas
|   |   |-- counter.js         # カウンターの非同期処理
|   |   `-- index.js           # 非同期処理のインデックス
|   |-- server.js              # サーバー側エンドポイント
|   `-- types
|       |-- Action.js          # Action(Fluxスタンダード)の型定義
|       `-- Counter.js         # カウンター関連の型定義
`-- package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;非同期周りの処理に&lt;a href=&#34;https://github.com/yelouafi/redux-saga&#34;&gt;redux-saga&lt;/a&gt;を使ってますが、今回はその辺りの解説は省きます。&lt;/p&gt;

&lt;h2 id=&#34;ポイントいくつか:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;ポイントいくつか&lt;/h2&gt;

&lt;p&gt;サンプルアプリ実装の際に、工夫した点/苦労した点を以下にまとめておきました。&lt;/p&gt;

&lt;h3 id=&#34;stateやactionの型定義をする:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;StateやActionの型定義をする&lt;/h3&gt;

&lt;p&gt;ReduxのStateやActionのPayload値は、動的言語らしく何でも入れる事が可能です。一人で開発するのなら良いですが、複数人で開発する場合、&lt;strong&gt;Action-&amp;gt;Reducer-&amp;gt;Viewで引き回す型の認識が合わず、思わぬバグが発生&lt;/strong&gt;しかねません。&lt;/p&gt;

&lt;p&gt;なるべく一つの型定義を使いまわし、値の引き回しに規約を与える必要があります。&lt;/p&gt;

&lt;h4 id=&#34;fluxスタンダードなaction型の定義:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;FluxスタンダードなAction型の定義&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// src/types/Action.js
export interface Action {
  type: string;
  error?: boolean;
  meta?: any;
}

export interface PayloadAction&amp;lt;T&amp;gt; extends Action {
  payload: T;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Action関数が返すべきオブジェクトの型定義をします。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: acdlite/flux-standard-action&lt;br /&gt;
&lt;a href=&#34;https://github.com/acdlite/flux-standard-action&#34;&gt;https://github.com/acdlite/flux-standard-action&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;非公式ではありますが、HumanフレンドリーなAction型として定評のあるFluxスタンダードに沿う形のAction型を組みました。&lt;/p&gt;

&lt;p&gt;個人的に、PayloadがあるActionとないActionとで、型を分けたかったので、分離してあります。Payloadに使う型は以下項目のように、ジェネリクスで指定できます。&lt;/p&gt;

&lt;h4 id=&#34;counter関連の型を定義する:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;Counter関連の型を定義する&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// src/types/Counter.js
import type { Action, PayloadAction } from &amp;quot;types/Action&amp;quot;

export interface CounterState {
  num: number;
}

export interface IncrementPayload {
  num: number;
}

export interface IncrementAction extends PayloadAction&amp;lt;IncrementPayload&amp;gt; {}
export interface ResetAction extends Action {}
export type CounterAction = IncrementAction &amp;amp; ResetAction
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;StateやActionの型を一つのファイルに定義しておきます。&lt;/p&gt;

&lt;h3 id=&#34;定義した型をactionやreducerで使い回す:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;定義した型をActionやReducerで使い回す&lt;/h3&gt;

&lt;p&gt;上で定義した共通の型設定をAction関数やReducer関数で読み込みます。&lt;/p&gt;

&lt;h4 id=&#34;action関数の定義で使い回す:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;Action関数の定義で使い回す&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// src/actions/counter.js
import type { IncrementAction, ResetAction } from &amp;quot;types/Counter&amp;quot;

export const REQUEST_INCREMENT = &amp;quot;COUNTER__REQUEST_INCREMENT&amp;quot;
export const EXECUTE_INCREMENT = &amp;quot;COUNTER__EXECUTE_INCREMENT&amp;quot;
export const RESET = &amp;quot;COUNTER__RESET&amp;quot;

export function requestIncrement(num: number): IncrementAction {
  return { type: REQUEST_INCREMENT, payload: { num } }
}

export function executeIncrement(num: number): IncrementAction {
  return { type: EXECUTE_INCREMENT, payload: { num } }
}

export function reset(): ResetAction {
  return { type: RESET }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;src/types/Counter.js&lt;/code&gt;で定義したものを返り値の型として使いまわしています。Action関数ごとに型定義をするかしないかは、個人の好みとなります。&lt;/p&gt;

&lt;h4 id=&#34;reducer関数の定義で使い回す:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;Reducer関数の定義で使い回す&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;import { EXECUTE_INCREMENT, RESET } from &amp;quot;actions/counter&amp;quot;
import type { CounterState, CounterAction } from &amp;quot;types/Counter&amp;quot;

export const initialState: CounterState = { num: 0 }

export default function counter(
  state: CounterState = initialState,
  action: CounterAction,
): CounterState {
  switch (action.type) {
    case EXECUTE_INCREMENT: {
      return { num: state.num + action.payload.num }
    }
    case RESET: {
      return { ...initialState }
    }
    default: {
      return state
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Actionで使っている&lt;code&gt;CounterAction&lt;/code&gt;は全てのAction関数の返り値型をIntersectionした型です。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;export type CounterAction = IncrementAction &amp;amp; ResetAction
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;以下のように、Unionにしてもよいのですが、&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;export type CounterAction = IncrementAction | ResetAction
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;payloadキーが存在するかしないか、逐一チェックする必要があるため、めんどうです。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;case EXECUTE_INCREMENT: {
  const incrementNum = action.payload ? action.payload.num || 0 : 0
  return { num: state.num + incrementNum }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;コンポーネントのprops型はプロパティ変数で定義:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;コンポーネントのProps型はプロパティ変数で定義&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;props&lt;/code&gt;というプロパティ変数に型をつけることで、ReactのPropTypesのようなチェックをできます。PropTypesは定義方法(isRequredとか)が独特な点、ランタイムエラーしかでない点で、個人的には使いづらい印象でした。&lt;/p&gt;

&lt;p&gt;flowの&lt;code&gt;props&lt;/code&gt;プロパティを利用すれば、実行前の型チェック時にエラーが出るため、見逃しづらいのと、型定義もflowと同様な方法でできるので、統一感が出ます。&lt;/p&gt;

&lt;h4 id=&#34;コンポーネントのプロパティ変数でpropsの型定義ができる:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;コンポーネントのプロパティ変数でPropsの型定義ができる&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// src/pages/CounterPage.js を改変
class CounterPage extends Component {
  props: {
    counter: CounterState, // クラス型やオブジェクト型も指定しやすい
    dispatch: (action: Action) =&amp;gt; any, // 関数型も定義可能
    num?: number, // 任意項目については、プロパティ名後ろに`?`をつける
  };
  // ...
  render() {
    const { counter } = this.props // connectしたCounterState型
    const { hoge } = this.props // Err! 未定義のプロパティは取り出せない
    // ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// 使う側の例
render() {
  return &amp;lt;CounterPage num=&amp;quot;String&amp;quot; /&amp;gt; // Err! 型に合わないプロパティは設定できない
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;コンポーネント内で使うactionはconnectしない:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;コンポーネント内で使うActionはconnectしない&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;react-redux&lt;/code&gt;のconnectで、Action関数をpropsに関連付けてしまうと、自分で定義した型情報が消し飛んでしまうので、コンポーネントのPropsで、Action関数の型を再定義する必要があります。&lt;/p&gt;

&lt;p&gt;それは面倒＆冗長なので、直接Action関数を使い、その返り値をdispatchすれば、Action関数の元の型定義を使いまわせます。&lt;/p&gt;

&lt;h4 id=&#34;dispatch関数を直接propsに回す:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;dispatch関数を直接propsに回す&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// src/pages/CounterPage.js
export default connect(
  ({ counter }) =&amp;gt; ({ counter }),
)(CounterPage)
// 第二引数に何も書かなければ、dispatch関数が直接、propsに渡される
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;action関数をconnectを通さないことで-元の型定義のまま使える:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;action関数をconnectを通さないことで、元の型定義のまま使える&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// src/pages/CounterPage.js
import * as actions from &amp;quot;actions/counter&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;handlePressIncrement() {
  const { dispatch } = this.props
  dispatch(actions.executeIncrement(1)) // ここ
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;まとめ:9406df7df5662b8fb9ed14fb8f2e9e94&#34;&gt;まとめ&lt;/h2&gt;

&lt;p&gt;React+Reduxでflowを使った型定義の方法をまとめました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;StateやActionの型定義をする&lt;/li&gt;
&lt;li&gt;定義した型をActionやReducerで使い回す&lt;/li&gt;
&lt;li&gt;コンポーネントのProps型はプロパティ変数で定義&lt;/li&gt;
&lt;li&gt;コンポーネント内で使うActionはconnectしない&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;今回はあまり使いませんでしたが、業務ドメイン関係の型定義(ActionのPayloadやStateの中で使う型)は、Reduxなどのフレームワークに依存せず、別フレームワークでも使いまわせる可能性があるので、積極的に型定義をしていきたいです。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>静的型チェッカーflowのクラスでPrivateなフィールドを定義するメモ</title>
      <link>http://blog.namiking.net/post/2016/05/flow-class-private-fields/</link>
      <pubDate>Thu, 12 May 2016 08:00:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/05/flow-class-private-fields/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;http://flowtype.org/&#34;&gt;flow&lt;/a&gt;はJavaScriptの型チェッカーだが、TypeScriptみたくPrivateフィールドを定義できるわけではなく、ちょっとした工夫が必要だったので、メモ。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#munge&#34;&gt;flowのmunge_underscoresオプションを使う方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#weakmap&#34;&gt;ES6のWeakMapを使う方法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;なんでprivateフィールドが必要:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;なんでPrivateフィールドが必要？&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;インスタンス生成後に外部からフィールド値を変更させたくないため。

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ドメイン駆動設計&lt;/strong&gt;(DDD)的なクラス設計をしていると、&lt;strong&gt;イミュータブル(不変)&lt;/strong&gt;のエンティティや値オブジェクトのようなものを多用する。&lt;/li&gt;
&lt;li&gt;イミュータブルであることを保証できれば、安心してインスタンスを参照で保持できる。(DeepCopyをする必要がなくなる)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Public箇所(API)を最小限にしておおきたい。

&lt;ul&gt;
&lt;li&gt;リファクタリングやテスト記述が楽になる。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;などなど。&lt;/p&gt;

&lt;p&gt;&lt;a name=&#34;munge&#34;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;flowのmunge-underscoresオプションを使う方法:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;flowのmunge_underscoresオプションを使う方法&lt;/h2&gt;

&lt;p&gt;flowオプションの&lt;a href=&#34;http://flowtype.org/docs/advanced-configuration.html&#34;&gt;munge_underscores&lt;/a&gt;を有効にすると、先頭に&lt;code&gt;_&lt;/code&gt;(アンダースコア)を付けたフィールド/メソッドは、継承先で使えない。というルールを追加することができる。&lt;/p&gt;

&lt;h3 id=&#34;flowconfig-追記:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;.flowconfig 追記&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;[options]
+ munge_underscores=true
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;実装例:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;実装例&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/facebook/flow/blob/7e35d0bd45db81826868022b644c2c2b2b60c895/tests/class_munging/with_munging.js&#34;&gt;GitHub上の使用例&lt;/a&gt;を参考にして、Privateフィールドを実現してみる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// @flow

type Param = {
  field1: number,
  field2: string,
}

class PrivateSample {
  _param: Param;

  constructor(param: Param) {
    this._param = param;
  }

  getField1(): number {
    return this._param.field1;
  }

  _getField2(): string {
    return this._param.field2;
  }
}

export default class Sample extends PrivateSample {}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;実際にflowをかけると、以下の様なエラーになる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;const sample = new Sample({
  field1: 5,
  field2: &amp;quot;test&amp;quot;,
})
assert(sample.getField1() === 5) // OK
assert(sample._getField2() === &amp;quot;test&amp;quot;) // NG
assert(sample._param.field1 === 5) // NG
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;error| property `_param` Property not found in (:0:1,0) Sample
error| property `_getField2` Property not found in (:0:1,0) Sample
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;先頭に&lt;code&gt;_&lt;/code&gt;(アンダースコア)、ハンガリアン記法的なキモさがあって、あまり使いたくないが、一番flowっぽい解決法といえる。&lt;/p&gt;

&lt;h3 id=&#34;継承元のクラスを直接インスタンス化すると使えちゃう:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;継承元のクラスを直接インスタンス化すると使えちゃう&lt;/h3&gt;

&lt;p&gt;ちなみに、継承元の&lt;code&gt;PrivateSample&lt;/code&gt;を直接使うと、エラーは出ない。継承しないと効果がないみたいなので、継承元のクラスは&lt;code&gt;export&lt;/code&gt;しないほうが良さそう。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;const sample = new PrivateSample({
  field1: 5,
  field2: &amp;quot;test&amp;quot;,
})
assert(sample.getField1() === 5) // OK
assert(sample._getField2() === &amp;quot;test&amp;quot;) // OK
assert(sample._param.field1 === 5) // OK
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a name=&#34;weakmap&#34;&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;es6のweakmapを使う方法:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;ES6のWeakMapを使う方法&lt;/h2&gt;

&lt;p&gt;flowに限ったものではないが、ES6でPrivateなフィールドを定義する方法論がある。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ES6 class での private プロパティの定義&lt;br /&gt;
&lt;a href=&#34;http://qiita.com/k_ui/items/889ec276fc04b1448674&#34;&gt;http://qiita.com/k_ui/items/889ec276fc04b1448674&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Symbolアクセスを使う方法は、&lt;code&gt;Object.getOwnPropertySymbols&lt;/code&gt;を使えば、外部から値を変更することが可能なため、今回は避けた。&lt;/p&gt;

&lt;p&gt;WeakMapでも同じファイル内ならアクセスできるが、インスタンスを作るのは概ね別ファイルなので、あまり問題ないと思った。&lt;/p&gt;

&lt;h3 id=&#34;実装例-1:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;実装例&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// @flow

type Param = {
  field1: number,
  field2: string,
}

const privates: WeakMap&amp;lt;Object, Param&amp;gt; = new WeakMap();

export default class Sample {

  constructor(param: Param) {
    privates.set(this, param);
  }

  getField1(): number {
    return privates.get(this).field1;
  }

  getField2(): string {
    return privates.get(this).field2;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;コンソールデバッグがしづらい:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;コンソールデバッグがしづらい&lt;/h3&gt;

&lt;p&gt;WeakMapの方法で、Privateフィールド化していると、コンソールでのデバッグに苦労する。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;const sample = new Sample({
  field1: 5,
  field2: &amp;quot;test&amp;quot;,
});
console.log(sample);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;としても、フィールドの内容は表示されず、以下の様なダンプに。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Sample {}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;実質、インスタンス内のプロパティには含まれていないので、表示出ないのは当たり前ではある。&lt;code&gt;privates&lt;/code&gt;のWeakMapをダンプすれば、以下の様な表示はされるが、ファイル外からでは参照できないので、厳しい。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;WeakMap {Sample {} =&amp;gt; Object {field1: 1234, field2: &amp;quot;test&amp;quot;}}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;おまけ-privateなメソッドも定義できる:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;[おまけ] Privateなメソッドも定義できる？&lt;/h3&gt;

&lt;p&gt;同ファイル内のClass外に関数を定義して、Classメソッド内で使えば実現できなくもない。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;const privates: WeakMap&amp;lt;Object, Param&amp;gt; = new WeakMap();

export default class Sample {

  constructor(param: Param) {
    privates.set(this, param);
  }

  getField1(): number {
    return privates.get(this).field1;
  }

  getField2(): string {
    return privates.get(this).field2;
  }
+
+  getPowField1(num: number) {
+    return powField1(this, num);
+  }
}

+// Private method
+function powField1(instance: Sample, num: number) {
+  return Math.pow(privates.get(instance).field1, num);
+}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ただ、ESLintを併用していると、&lt;code&gt;no-use-before-define&lt;/code&gt;に引っかかったりする。
ちとまどろっこしいね。&lt;/p&gt;

&lt;h2 id=&#34;備考-注意点:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;備考・注意点&lt;/h2&gt;

&lt;p&gt;コンストラクタ引数にObjectを渡して、WeakMapにそのままセットする。名前引数的に使えるので、コードの見通しがよくなる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;const sample = new Sample({
  field1: 1234,
  field2: &amp;quot;Text&amp;quot;,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ただし、コンストラクタ引数へ渡すObjectを変更可能にしておくと、イミュータブルじゃなくなってしまうので、注意。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;let param = {
  field1: 1234,
  field2: &amp;quot;Text&amp;quot;,
};
const sample = new Sample(param);

// non-immutable
param.field1 = 2345;

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;コンストラクタ内で、ObjectのShallowCopyを行うなどして、対策すると良いかもしれない。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;constructor(param: Param) {
  // ES7の`object-rest-spread`を使うと楽
  Sample.privates.set(this, { ...param });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;まとめ:f379f7becb8d42a55814e7f1243a74c2&#34;&gt;まとめ&lt;/h2&gt;

&lt;p&gt;JavaScriptの言語仕様上、Private関係は実装しにくく、どうしてもまどろっこしい書き方になってしまう。それでも、TypeScriptを含め、様々なトランスパイラが生まれた今、以前に比べれば、ずいぶんとPrivateを実現しやすくなったと思う。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScriptとprivateの見果てぬ夢&lt;br /&gt;
&lt;a href=&#34;http://blog.tojiru.net/article/238901975.html&#34;&gt;http://blog.tojiru.net/article/238901975.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;言語仕様を超える夢を見て、戦い続けた男たちに敬意を表したい。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>静的型チェッカーflowを使ってみて、微妙に気になったこと４つ</title>
      <link>http://blog.namiking.net/post/2016/05/flow-disadvantage/</link>
      <pubDate>Thu, 12 May 2016 07:00:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/05/flow-disadvantage/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;http://flowtype.org/&#34;&gt;flow&lt;/a&gt;はJavaScriptの静的型チェックツールの一つ。&lt;/p&gt;

&lt;p&gt;同じような静的型関連ツールであるTypeScriptと比較して、ESLintやBabelとの併用がしやすかったり、型付けが強めだったり(初期からnon-nullableに対応)と、受ける恩恵も多い。&lt;/p&gt;

&lt;p&gt;が、使っていて「おやっ？」っと思う点もいくつかあったので、まとめてみた。&lt;/p&gt;

&lt;h2 id=&#34;気になったこと４つ:9faef884f5c95a0fa0683d966b12c34f&#34;&gt;気になったこと４つ&lt;/h2&gt;

&lt;h3 id=&#34;eslintとの併用でめんどうな事が多かった:9faef884f5c95a0fa0683d966b12c34f&#34;&gt;ESLintとの併用でめんどうな事が多かった&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/babel/babel-eslint&#34;&gt;babel-eslint&lt;/a&gt;というBabelパーサーを用いれば、概ねのESLintルールがflow文法でも適応できるが、例外もあった。&lt;/p&gt;

&lt;p&gt;例えば、インターフェイスの定義の場合、&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;interface I {
  field: number;
  method(): string;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ESLintの&lt;code&gt;no-undef&lt;/code&gt;ルールを有効にしていると、以下のエラーが発生する。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;error| &#39;I&#39; is not defined. (no-undef)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ESLintの誤爆ではあるが、そもそも、&lt;code&gt;interface&lt;/code&gt;なんて文法はECMAScriptにはないので、対応しろっていうのも無茶な話。&lt;/p&gt;

&lt;p&gt;ESLintの&lt;code&gt;no-undef&lt;/code&gt;まわりを無効にすればエラーは出ないが、有志の方が、flow用のBabelカスタムルールを実装してくれているので、ありがたく利用させていただく。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: zertosh/eslint-plugin-flow-vars&lt;br /&gt;
&lt;a href=&#34;https://github.com/zertosh/eslint-plugin-flow-vars&#34;&gt;https://github.com/zertosh/eslint-plugin-flow-vars&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;このBabelプラグインを使ってカスタムルールを有効にすれば、&lt;code&gt;no-undef&lt;/code&gt;を無効にすることなく、ESLintとflowを併用できる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;// .eslintrc.jsの修正例
module.exports = {
  &amp;quot;parser&amp;quot;: &amp;quot;babel-eslint&amp;quot;,
+ &amp;quot;plugins&amp;quot;:
+   &amp;quot;flow-vars&amp;quot;,
+ ],
  &amp;quot;rules&amp;quot;: {
    &amp;quot;no-undef&amp;quot;: 1,
+   &amp;quot;flow-vars/define-flow-type&amp;quot;: 1,
+   &amp;quot;flow-vars/use-flow-type&amp;quot;: 1,
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;その他、いろいろ試行錯誤している時にTwitter上でご教示いただいたPluginも、いくつかのflow用カスタムルールがあった。何かflow+ESLintで不都合があり次第、逐一有効にしていく必要があるかも。&lt;/p&gt;

&lt;p&gt;&lt;blockquote class=&#34;twitter-tweet&#34; data-conversation=&#34;none&#34; data-lang=&#34;ja&#34;&gt;&lt;p lang=&#34;ja&#34; dir=&#34;ltr&#34;&gt;&lt;a href=&#34;https://twitter.com/namikingsoft&#34;&gt;@namikingsoft&lt;/a&gt; eslint-plugin-babel はすでにお試しでしょうか？ eslint 組込ルールの中で、ECMA標準外の構文に起因する誤検知などに対処しているプラグインです。&lt;a href=&#34;https://t.co/CD5p5ejHIq&#34;&gt;https://t.co/CD5p5ejHIq&lt;/a&gt;&lt;/p&gt;&amp;mdash; Toru Nagashima (@mysticatea) &lt;a href=&#34;https://twitter.com/mysticatea/status/728262384771432448&#34;&gt;2016年5月5日&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;//platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&lt;/p&gt;

&lt;h3 id=&#34;babelを通しても-es6文法で使えないものがある:9faef884f5c95a0fa0683d966b12c34f&#34;&gt;Babelを通しても、ES6文法で使えないものがある&lt;/h3&gt;

&lt;p&gt;例えば、&lt;strong&gt;ES6のget/setプロパティ&lt;/strong&gt;が使えなかった。&lt;/p&gt;

&lt;p&gt;イミュータブルなオブジェクトを作って、Privateな値を返すゲッターを作った際、プロパティのように扱えるので、好んで使っていたが、&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;class A {
  get field(): number {
    return 1234
  }
}

const a = new A()
console.log(a.field)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;flowに通すと、怒られる。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;error| get/set properties not yet supported
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;yet&lt;/code&gt;ってあるので、いずれサポートされるのだろうか。&lt;/p&gt;

&lt;h3 id=&#34;ジェネリクスなクラス-関数の使い方次第でエラー:9faef884f5c95a0fa0683d966b12c34f&#34;&gt;ジェネリクスなクラス/関数の使い方次第でエラー&lt;/h3&gt;

&lt;p&gt;flow的には&lt;strong&gt;Polymorphism&lt;/strong&gt;というのかな？&lt;br /&gt;
TypeScriptやJava感覚で以下のように書くと、flowだとSyntaxエラーになった。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;const map = new Map&amp;lt;string, number&amp;gt;()
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;error| Parsing error: Unexpected token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;そもそもBabelがエラーを吐く。&lt;code&gt;babel-plugin-transform-flow-strip-types&lt;/code&gt;の限界っぽい。&lt;/p&gt;

&lt;p&gt;以下のように書くのがflow流っぽいが、なんか冗長な感じがして、あまり好みじゃない。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;// 変数定義で型を決めとく
const map: WeakMap&amp;lt;string, number&amp;gt; = new WeakMap()

// 型のキャスト
const map = (new WeakMap(): WeakMap&amp;lt;string, number&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;関数の場合も似たようなSyntaxエラーを吐くが、
以下の様な型推論が効く例だと、逆にスマートな感じで良い。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;function toArray&amp;lt;T&amp;gt;(x: T): Array&amp;lt;T&amp;gt; {
  return [x]
}

// 引数の型(number)を元に、返り値の型(Array&amp;lt;number&amp;gt;)が推論される
const nums = toArray(2)
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;クラスのフィールド定義などでセミコロンが強制:9faef884f5c95a0fa0683d966b12c34f&#34;&gt;クラスのフィールド定義などでセミコロンが強制&lt;/h3&gt;

&lt;p&gt;クラスやインタフェースの定義などで、以下のように書くと、&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-typescript&#34;&gt;class A {
  num: number

  constructor(num: number) {
    this.number = num
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;次のエラーが、constructorの行で発生する。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;error| Unexpected identifier
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;フィールド定義の行末尾にセミコロン(;)を入れれば、エラーは出ない。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;class A {
- num: number
+ num: number;

  constructor(num: number) {
    this.number = num
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;アンチ・セミコロン派には吐きそうなほどキツイ仕様。&lt;br /&gt;
&lt;a href=&#34;https://github.com/facebook/flow/issues/825&#34;&gt;https://github.com/facebook/flow/issues/825&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;まとめ:9faef884f5c95a0fa0683d966b12c34f&#34;&gt;まとめ&lt;/h2&gt;

&lt;p&gt;flowを使ってみて、以下４つの気になったことをまとめてみた。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLintとの併用でめんどうな事が多かった&lt;/li&gt;
&lt;li&gt;Babelを通しても、ES6文法で使えないものがある&lt;/li&gt;
&lt;li&gt;ジェネリクスなクラス/関数の使い方次第でエラー&lt;/li&gt;
&lt;li&gt;クラスのフィールド定義などでセミコロンが強制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;flowは現在も、定期的にVerUPされているツールなので、今回上げた点もいつの間に修正されているかもしれない。今後も細かく見ていきたい。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>サーバーサイドReactをwebpackを使って最小構成で試す (ES6 ＆ TypeScript)</title>
      <link>http://blog.namiking.net/post/2016/02/react-server-using-webpack/</link>
      <pubDate>Sat, 13 Feb 2016 07:30:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/02/react-server-using-webpack/</guid>
      <description>

&lt;p&gt;サーバーサイドのReactに触れたことがなかったので、React+Express+webpackで試してみた。今回試行した手順をチュートリアル的にまとめておく。まずは、シンプルにできそうなECMAScript6で試して、後半にTypeScriptで組んだソースも、おまけ的に載せておきます。&lt;/p&gt;

&lt;h3 id=&#34;この記事の方針:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;この記事の方針&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;クライアント -&amp;gt; サーバーサイド -&amp;gt; 結びつける。の順に実装を行う&lt;/li&gt;
&lt;li&gt;なるべくシンプルにするために、実用構成というよりは、最小構成で動かす。

&lt;ul&gt;
&lt;li&gt;コンポーネントのプリレンダやState遷移の確認までを行う。&lt;/li&gt;
&lt;li&gt;サーバーAPIとの通信や画面遷移は、今回扱わない。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;実装するサンプルアプリの内容:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;実装するサンプルアプリの内容&lt;/h4&gt;

&lt;p&gt;チュートリアルでよくありそうな、シンプルなカウンターアプリを動かす。
&lt;img src=&#34;http://blog.namiking.net/images/post/2016/02/react-server-using-webpack/sample.png&#34; alt=&#34;Sample&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;事前に必要なソフトウェア:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;事前に必要なソフトウェア&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;node.js (v5.6.0)&lt;/li&gt;
&lt;li&gt;npm (v3.6.0)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;現時点の安定版を使ってみたが、そこまで新しくなくても問題ない。&lt;/p&gt;

&lt;h4 id=&#34;memo-利用したnpmパッケージのバージョン:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;[Memo] 利用したnpmパッケージのバージョン&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;dependencies&amp;quot;: {
  &amp;quot;express&amp;quot;: &amp;quot;^4.13.4&amp;quot;,
  &amp;quot;react&amp;quot;: &amp;quot;^0.14.7&amp;quot;,
  &amp;quot;react-dom&amp;quot;: &amp;quot;^0.14.7&amp;quot;
},
&amp;quot;devDependencies&amp;quot;: {
  &amp;quot;babel-cli&amp;quot;: &amp;quot;^6.5.1&amp;quot;,
  &amp;quot;babel-loader&amp;quot;: &amp;quot;^6.2.2&amp;quot;,
  &amp;quot;babel-preset-es2015&amp;quot;: &amp;quot;^6.5.0&amp;quot;,
  &amp;quot;babel-preset-react&amp;quot;: &amp;quot;^6.5.0&amp;quot;,
  &amp;quot;dtsm&amp;quot;: &amp;quot;^0.13.0&amp;quot;,
  &amp;quot;ts-loader&amp;quot;: &amp;quot;^0.8.1&amp;quot;,
  &amp;quot;typescript&amp;quot;: &amp;quot;^1.7.5&amp;quot;,
  &amp;quot;webpack&amp;quot;: &amp;quot;^1.12.13&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;バージョンが新しくなったりすると、この記事の書き方と変わってくる可能性があるので、注意。&lt;/p&gt;

&lt;h2 id=&#34;ecmascript6-版-チュートリアル:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;ECMAScript6 版 (チュートリアル)&lt;/h2&gt;

&lt;p&gt;Reactドキュメントの&lt;a href=&#34;https://facebook.github.io/react/docs/getting-started.html&#34;&gt;Getting Started&lt;/a&gt;でも、Babelを利用しているようなので、まずは、Babelを使って、ECMAScript6で記述できるようにしてみる。なお、サンプルソースの完成版を以下のリポジトリに置いといたので、参考にされたい。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECMAScript6版サンプルソースの完成版&lt;br /&gt;
&lt;a href=&#34;https://github.com/namikingsoft/sample-react-server/tree/typescript&#34;&gt;https://github.com/namikingsoft/sample-react-server/tree/typescript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;まずはクライアント側で動かしてみる:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;まずはクライアント側で動かしてみる&lt;/h3&gt;

&lt;p&gt;ファイル構成は以下の様な感じになるように、作業をすすめる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-axapta&#34;&gt;react-server
|-- .babelrc            # Babel設定
|-- package.json        # npm設定
|-- public
|   |-- client.js       # webpackによって吐き出されたフロント用のJS
|   `-- test.html       # クライアント確認用
|-- src
|   |-- client.js       # クライアントJSエントリーポイント
|   `-- components
|       `-- Counter.js  # カウンター用Reactコンポネント
`-- webpack.config.js   # webpack設定
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;npm-init:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;npm init&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;mkdir react-server
cd react-server

npm init
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;適当なディレクトリを作り、package.jsonのテンプレートを作っておく。&lt;code&gt;npm init&lt;/code&gt;の選択肢も全て空Enterで問題ない。&lt;/p&gt;

&lt;h4 id=&#34;必要なnpmパッケージをインストール:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;必要なnpmパッケージをインストール&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;npm install --save react react-dom
npm install --save-dev webpack babel-loader babel-preset-es2015 babel-preset-react
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;モダンブラウザによっては、&lt;code&gt;babel-preset-es2015&lt;/code&gt;はいらないかもだが、一応。&lt;/p&gt;

&lt;h4 id=&#34;webpack-config-js:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;webpack.config.js&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;var webpack = require(&#39;webpack&#39;);

module.exports = {
  entry: {
    client: &amp;quot;./src/client.js&amp;quot;,
  },
  output: {
    filename: &#39;[name].js&#39;,
    path: &amp;quot;./public&amp;quot;,
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loaders: [&#39;babel&#39;],
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [&#39;&#39;, &#39;.js&#39;, &#39;.jsx&#39;],
    modulesDirectories: [&#39;node_modules&#39;],
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;なるべくシンプルにするため、HotLoaderなど記述は入れていない。&lt;/p&gt;

&lt;h4 id=&#34;babelrc:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;.babelrc&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;{
  &amp;quot;presets&amp;quot;: [&amp;quot;es2015&amp;quot;, &amp;quot;react&amp;quot;],
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Babelの設定ファイル。&lt;code&gt;React -&amp;gt; ES6 -&amp;gt; ES5&lt;/code&gt;のような感じで、どのブラウザでも割りかし動作するように変換する。&lt;/p&gt;

&lt;h4 id=&#34;public-test-html:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;public/test.html&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;App&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&amp;quot;app&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;script src=&amp;quot;app.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;クライアントJSの動作確認用HTML。&lt;code&gt;app.js&lt;/code&gt;が実行された後に、&lt;code&gt;&amp;lt;div id=&amp;quot;app&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;の中身がCounterコンポーネントに置き換わる。&lt;/p&gt;

&lt;h4 id=&#34;src-client-js:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;src/client.js&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;import React from &#39;react&#39;
import ReactDOM from &#39;react-dom&#39;
import Counter from &#39;./components/Counter&#39;

ReactDOM.render(
  &amp;lt;Counter /&amp;gt;,
  document.getElementById(&#39;app&#39;)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Counterコンポーネントを&lt;code&gt;&amp;lt;div id=&amp;quot;app&amp;quot; /&amp;gt;&lt;/code&gt;に表示する。&lt;/p&gt;

&lt;h4 id=&#34;src-components-counter-js:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;src/components/Counter.js&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;import React, {Component} from &#39;react&#39;

export default class Counter extends Component {

  constructor() {
    super()
    this.state = {
      count: 0
    }
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;Count: {this.state.count}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={e =&amp;gt; this.increment()}&amp;gt;Increment&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }

  increment() {
    this.setState({
      count: this.state.count + 1
    })
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Incrementボタンを押したら、内部Stateが変化して、コンポーネントを再描写される。&lt;/p&gt;

&lt;h4 id=&#34;webpack実行:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;webpack実行&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;./node_modules/.bin/webpack
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;これで、&lt;code&gt;public/client.js&lt;/code&gt;にブラウザで動作するJSが生成される。&lt;/p&gt;

&lt;p&gt;webpackコマンドについては、&lt;code&gt;npm run build&lt;/code&gt;コマンドで実行できるように、package.jsonのscriptsに登録しておくと良いかも。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;scripts&amp;quot;: {
  &amp;quot;build&amp;quot;: &amp;quot;webpack&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;動作確認:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;動作確認&lt;/h4&gt;

&lt;p&gt;Incrementボタンを押して、Stateの変動やコンポーネントの再描写が確認できる。
&lt;img src=&#34;http://blog.namiking.net/images/post/2016/02/react-server-using-webpack/client-result.png&#34; alt=&#34;Client Result&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;サーバーサイドからコンポーネントを描写する:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;サーバーサイドからコンポーネントを描写する&lt;/h3&gt;

&lt;p&gt;ファイル構成としては以下。&lt;code&gt;src/server.js&lt;/code&gt;が追加されただけ。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-axapta&#34;&gt;react-server
|-- package.json
|-- public
|   |-- client.js
|   `-- test.html
|-- src
|   |-- client.js
|   |-- components
|   |   `-- Counter.js
|   `-- server.js     # 追加: ExpressでCounterコンポーネントをプリレンダリング
`-- webpack.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;必要なnpmパッケージをインストール-1:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;必要なnpmパッケージをインストール&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;npm install --save express
npm install --save-dev babel-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;軽量Webフレームワークの&lt;code&gt;express&lt;/code&gt;と、node.jsの実行をbabelに通すための&lt;code&gt;babel-cli&lt;/code&gt;をインストールする。&lt;/p&gt;

&lt;h4 id=&#34;src-server-js:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;src/server.js&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;import express from &#39;express&#39;
import React from &#39;react&#39;
import ReactDOMServer from &#39;react-dom/server&#39;
import Counter from &#39;./components/Counter&#39;

// init express
const app = express()

// add top page routing
app.get(&#39;/&#39;, (req, res) =&amp;gt; {
  res.send(
    ReactDOMServer.renderToString(
      &amp;lt;Counter /&amp;gt;
    )
  )
})

// start listen
app.listen(3000, () =&amp;gt; {
  console.log(&#39;Example app listening on port 3000!&#39;);
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;ReactDOMServer.renderToString()&lt;/code&gt;を使って、コンポーネントをプリレンダリングできる。&lt;br /&gt;
(HTMLの側端は端折ってます)&lt;/p&gt;

&lt;h4 id=&#34;サーバー起動:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;サーバー起動&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;node_modules/.bin/babel-node src/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;babel-node&lt;/code&gt;はbabel-cliでインストールされるコマンドで、実行対象のJSを自動的にBabel変換した上でnodeコマンドを実行してくれる便利なラッパー。&lt;/p&gt;

&lt;p&gt;ビルドと同じく、&lt;code&gt;npm start&lt;/code&gt;コマンドで実行できるように、package.jsonのscriptsに登録しておくと良い。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;&amp;quot;scripts&amp;quot;: {
  ...
  &amp;quot;start&amp;quot;: &amp;quot;babel-node src/server.js&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;動作確認-1:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;動作確認&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;open http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;クライアントのみの実行と、全く同じ画面が表示される。ブラウザのソース表示やcurlなどからも、コンポーネントの中身がプリレンダリングされたHTMLを確認できた。&lt;/p&gt;

&lt;p&gt;しかし。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/02/react-server-using-webpack/server-result.png&#34; alt=&#34;Client Result&#34; /&gt;&lt;/p&gt;

&lt;p&gt;サーバーサイドでプリレンダしただけで、クライアントでは何もしてないし、react.jsも読み込んでないため、と思われる。&lt;/p&gt;

&lt;h3 id=&#34;サーバーサイドとクライアントの処理をつなげる:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;サーバーサイドとクライアントの処理をつなげる&lt;/h3&gt;

&lt;p&gt;Fluxフレームワークで有名なReduxのドキュメントの&lt;a href=&#34;https://github.com/rackt/redux/blob/master/docs/recipes/ServerRendering.md&#34;&gt;Server Rendering&lt;/a&gt;を見るに、サーバーサイドでプリレンダした要素に、再度クライアントからレンダリングをかけている様な処理になっていたので、試してみる。&lt;/p&gt;

&lt;h4 id=&#34;src-server-js-の修正:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;src/server.js の修正&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;import express from &#39;express&#39;
import React from &#39;react&#39;
import ReactDOMServer from &#39;react-dom/server&#39;
import Counter from &#39;./components/Counter&#39;

// init express
const app = express()

+ // add static path
+ app.use(express.static(&#39;public&#39;))

// add top page routing
app.get(&#39;/&#39;, (req, res) =&amp;gt; {
  res.send(
    ReactDOMServer.renderToString(
-      &amp;lt;Counter /&amp;gt;
+      &amp;lt;div&amp;gt;
+        &amp;lt;div id=&amp;quot;app&amp;quot;&amp;gt;
+          &amp;lt;Counter /&amp;gt;
+        &amp;lt;/div&amp;gt;
+        &amp;lt;script src=&amp;quot;client.js&amp;quot; /&amp;gt;
+      &amp;lt;/div&amp;gt;
    )
  )
})

// start listen
app.listen(3000, () =&amp;gt; {
  console.log(&#39;Example app listening on port 3000!&#39;);
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;app.use(express.static(&#39;public&#39;))&lt;/code&gt;で、publicディレクトリ以下のファイルを静的ファイルとして、読み込み可能として、プリレンダする内容をクライアント側の時に試した&lt;code&gt;test.html&lt;/code&gt;と同じような記述に変更する。&lt;/p&gt;

&lt;h4 id=&#34;再度-動作確認:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;再度、動作確認&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;npm start
open http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;今度は、Incrementボタン押下で、正常動作を確認できるはず。&lt;/p&gt;

&lt;h2 id=&#34;typescript-版-要約:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;TypeScript 版 (要約)&lt;/h2&gt;

&lt;p&gt;型がついていないと落ち着かない自分のためにも、TypeScriptで導入できるようにもしておきたい。クライアント側は&lt;code&gt;ts-loader&lt;/code&gt;を挟むぐらいで概ね対応できるが、サーバーサイドは&lt;code&gt;babel-node&lt;/code&gt;に相当するものがないようので、一度コンパイルしてから実行するようなイメージ。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Typescript版サンプルソースの完成版&lt;br /&gt;
&lt;a href=&#34;https://github.com/namikingsoft/sample-react-server/tree/typescript&#34;&gt;https://github.com/namikingsoft/sample-react-server/tree/typescript&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;ECMAScript6版との差分&lt;br /&gt;
&lt;a href=&#34;https://github.com/namikingsoft/sample-react-server/compare/typescript&#34;&gt;https://github.com/namikingsoft/sample-react-server/compare/typescript&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;要約:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;要約&lt;/h3&gt;

&lt;p&gt;TypeScript版については、上のようにチュートリアル形式にはせず、要約解説にしたい。詳しくは上の&lt;a href=&#34;https://github.com/namikingsoft/sample-react-server/compare/typescript&#34;&gt;ECMAScript6版との差分&lt;/a&gt;を見ていただいたほうが、早いかもしれない。&lt;/p&gt;

&lt;h4 id=&#34;型定義ファイルマネージャにはdtsmを使った:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;型定義ファイルマネージャにはdtsmを使った。&lt;/h4&gt;

&lt;p&gt;npmとほぼ同じインタフェースなので使いやすい。以下コマンド例。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;npm install --save-dev dtsm
export PATH=./node_modules/.bin:$PATH

dtsm init
dtsm install --save react.d.ts
dtsm install --save react-dom.d.ts
dtsm install --save express.d.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;サーバーサイドのコンパイルはtscを直接使った:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;サーバーサイドのコンパイルはtscを直接使った。&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;babel-node&lt;/code&gt;のようなラッパーコマンドがあることを期待したが、観測内ではなさそうなので、通常通り、&lt;code&gt;dist&lt;/code&gt;ディレクトリあたりに、コンパイル済みのJSを展開して、&lt;code&gt;node dist/server.js&lt;/code&gt;みたいにする作戦にした。&lt;/p&gt;

&lt;p&gt;tsconfig.jsonは以下のとおり。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;compilerOptions&amp;quot;: {
    &amp;quot;target&amp;quot;: &amp;quot;es5&amp;quot;,
    &amp;quot;jsx&amp;quot;: &amp;quot;react&amp;quot;,
    &amp;quot;module&amp;quot;: &amp;quot;commonjs&amp;quot;,
    &amp;quot;moduleResolution&amp;quot;: &amp;quot;node&amp;quot;,
    &amp;quot;experimentalDecorators&amp;quot;: true,
    &amp;quot;outDir&amp;quot;: &amp;quot;dist&amp;quot;
  },
  &amp;quot;files&amp;quot;: [
    &amp;quot;typings/bundle.d.ts&amp;quot;,
    &amp;quot;src/server.tsx&amp;quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;なお、tsconfig.jsonはクライアント側のコンパイルにも使いまわしたいので、React変換なども有効にしてある。&lt;code&gt;experimentalDecorators&lt;/code&gt;はいらないかもだが、ReduxなどのFluxフレームワークで、割りとデコレータ(@connectなど)が使われていたりするので、一応有効にしてある。&lt;/p&gt;

&lt;p&gt;コンパイルについては、package.jsonのscriptsを以下のように修正して、&lt;code&gt;npm run build&lt;/code&gt;でやると良い。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;&amp;quot;scripts&amp;quot;: {
  ...
- &amp;quot;build&amp;quot;: &amp;quot;webpack&amp;quot;,
+ &amp;quot;build&amp;quot;: &amp;quot;webpack &amp;amp;&amp;amp; tsc -p .&amp;quot;,
  ...
},
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;クライアントのコンパイルにはwebpackのts-loaderを使った:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;クライアントのコンパイルにはwebpackのts-loaderを使った。&lt;/h4&gt;

&lt;p&gt;webpack.config.jsの修正差分は以下の様な感じになる。なお、TypeScript自体が、&lt;code&gt;React -&amp;gt; ES6 -&amp;gt; ES5&lt;/code&gt;変換機能を備えているので、無理にBabelに通さなくてもよい。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;var webpack = require(&#39;webpack&#39;);

module.exports = {
  entry: {
-   client: &amp;quot;./src/client.js&amp;quot;,
+   client: &amp;quot;./src/client.tsx&amp;quot;,
  },
  output: {
    filename: &#39;[name].js&#39;,
    path: &amp;quot;./public&amp;quot;,
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        loaders: [&#39;babel&#39;],
        exclude: /node_modules/,
      },
+     {
+       test: /\.tsx?$/,
+       loaders: [&#39;ts&#39;],
+       exclude: /node_modules/,
+     },
    ],
  },
  resolve: {
-   extensions: [&#39;&#39;, &#39;.js&#39;, &#39;.jsx&#39;],
+   extensions: [&#39;&#39;, &#39;.js&#39;, &#39;.jsx&#39;, &#39;ts&#39;, &#39;.tsx&#39;],
    modulesDirectories: [&#39;node_modules&#39;],
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;あとがき:5d27ec92afc6a1472e1a3eed36d540c0&#34;&gt;あとがき&lt;/h2&gt;

&lt;p&gt;なるべくシンプルな構成で、サーバーサイドReactを試してみた。&lt;/p&gt;

&lt;p&gt;今回はサーバーサイドにExpressを使ってみたが、Railsなどでも、&lt;code&gt;react-rails&lt;/code&gt;のようなgemを利用して、クライアントとの連携ができるはず。&lt;/p&gt;

&lt;p&gt;ReduxやReactRouterなどを利用した、もうちょっと実践的なやり方については、以下のReduxドキュメントやQiita記事が詳しそうだったので、載せておきます。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Redux: Server Rendering&lt;br /&gt;
&lt;a href=&#34;https://github.com/rackt/redux/blob/master/docs/recipes/ServerRendering.md&#34;&gt;https://github.com/rackt/redux/blob/master/docs/recipes/ServerRendering.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Qiita: React + Expressでのサーバーサイドレンダリング方法のまとめ&lt;br /&gt;
&lt;a href=&#34;http://qiita.com/hmarui66/items/4f75e624c4f70d596873&#34;&gt;http://qiita.com/hmarui66/items/4f75e624c4f70d596873&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Sparkで機械学習： 回帰モデルで値を予測する</title>
      <link>http://blog.namiking.net/post/2016/01/spark-mllib-regression/</link>
      <pubDate>Sun, 31 Jan 2016 23:15:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/01/spark-mllib-regression/</guid>
      <description>

&lt;p&gt;Apache Spark上にて、簡単なCSVのサンプルデータを取り込み、線形回帰や決定木回帰を利用して、穴が空いた項目を予測するサンプルプログラムを書いてみる。&lt;/p&gt;

&lt;h3 id=&#34;サンプルデータ-身体情報から結婚時期を予測する:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;サンプルデータ (身体情報から結婚時期を予測する)&lt;/h3&gt;

&lt;p&gt;実データではありません。入門用にシンプルで法則性のあるデータを探したのですが、なかなか見つからなかったので、自分で訓練用のデータを作ってみた。&lt;/p&gt;

&lt;p&gt;題材としては、性別や血液型、身長、体重から、結婚適齢期を予測するみたいなことをやってみる。例えば、以下の様なデータを学習用データとして取り込み、&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;結婚した歳&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;血液型&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;性別&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;身長(cm)&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;体重(kg)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;32歳&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;O&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;女&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;152&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;60&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;42歳&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;A&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;男&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;180&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;80&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;26歳&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;O&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;男&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;155&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;55&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;20歳&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;B&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;女&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;166&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;55&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&amp;hellip;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&amp;hellip;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&amp;hellip;&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;&amp;hellip;&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;&amp;hellip;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;以下の「？」になっているところの値を予測する、みたいなことをやってみる。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;結婚する歳&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;血液型&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;性別&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;身長(cm)&lt;/th&gt;
&lt;th align=&#34;right&#34;&gt;体重(kg)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;？&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;O&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;女&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;152&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;60&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;？&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;A&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;男&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;180&lt;/td&gt;
&lt;td align=&#34;right&#34;&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;サンプルデータ： 身体情報から結婚時期を予測する&lt;br /&gt;
&lt;a href=&#34;http://blog.namiking.net/files/post/2016/01/spark-mllib-regression/training.csv&#34;&gt;CSV形式のダウンロード&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&#34;データの傾向:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;データの傾向&lt;/h4&gt;

&lt;p&gt;ただ無差別にデータを作っても、予測が合ってるかどうかの判断がつかないため、&lt;br /&gt;
以下の様な&lt;strong&gt;事実無根&lt;/strong&gt;な法則で値をでっちあげてみた。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;B型は早婚&lt;/li&gt;
&lt;li&gt;O型は晩婚&lt;/li&gt;
&lt;li&gt;AB型はとても早婚&lt;/li&gt;
&lt;li&gt;女性は早婚&lt;/li&gt;
&lt;li&gt;肥満とモヤシは晩婚&lt;/li&gt;
&lt;li&gt;男性の高身長はとても晩婚&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;コーディング前の準備:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;コーディング前の準備&lt;/h3&gt;

&lt;h4 id=&#34;apache-zeppelinのインストール:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;Apache Zeppelinのインストール&lt;/h4&gt;

&lt;p&gt;Spark(ScalaやPython)の記述やその他細かいシェルスクリプトなどの操作をWeb上でインタラクティブに行えるノートブック系OSS&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:c26ed1f8424ab1bb2406ee54bb00b33a:1&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:c26ed1f8424ab1bb2406ee54bb00b33a:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。この記事では、Sparkの操作は基本的にこのソフトを用いてコーディングを行っている。Sparkも一緒に含まれているので、これをローカルにインストールするだけで概ね動くはず。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Apache Zeppelin (incubating)&lt;br /&gt;
&lt;a href=&#34;https://zeppelin.incubator.apache.org/&#34;&gt;https://zeppelin.incubator.apache.org/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;サンプルデータの取り込み:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;サンプルデータの取り込み&lt;/h3&gt;

&lt;p&gt;ここからは、Zeppelin上での作業となる。事前にZeppelinを起動して、適当な新しいノートブックを作成しておく。&lt;/p&gt;

&lt;h4 id=&#34;サンプルデータのダウンロード:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;サンプルデータのダウンロード&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;%sh
curl -Lo /tmp/training.csv \
  &amp;quot;http://blog.namiking.net/files/post/2016/01/spark-mllib-regression/training.csv&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;先ほどのCSV形式のサンプルデータを一時保存領域にダウンロードするシェルスクリプトを記述する。&lt;/p&gt;

&lt;h4 id=&#34;１レコード毎のcaseクラスを作成:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;１レコード毎のCaseクラスを作成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;case class Profile(
  marriedAge: Option[Double],
  blood: String,
  sex: String,
  height: Double,
  weight: Double
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;CSVデータなどをDataFrame形式に変換する際に必要になる。
&lt;code&gt;marriedAge&lt;/code&gt;をOption型にしているのは、テストデータを取り込む際に入れる値が無いため。&lt;/p&gt;

&lt;h4 id=&#34;csvをパースして-dataframe形式に変換:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;CSVをパースして、DataFrame形式に変換&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;var csvRDD = sc.textFile(&amp;quot;/tmp/training.csv&amp;quot;)
var csvHeadRDD = sc.parallelize(Array(csvRDD.first))
var training = csvRDD
  .subtract(csvHeadRDD) // ヘッダ除去
  .map { line =&amp;gt;
    val cols = line.split(&#39;,&#39;)
    Profile(
      marriedAge = Some(cols(0).toDouble),
      blood = cols(1),
      sex = cols(2),
      height = cols(3).toDouble,
      weight = cols(4).toDouble
    )
  }.toDF
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;RDDだと、&lt;code&gt;tail&lt;/code&gt;や&lt;code&gt;slice&lt;/code&gt;関数みたいなものがなくて、ヘッダ除去程度でもちょっとまどろっこしい。分散処理を考えるとしかたないのだろうか。&lt;/p&gt;

&lt;h4 id=&#34;おまけ-spark-csvモジュールを使ったサンプルデータの取り込み:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;[おまけ] spark-csvモジュールを使ったサンプルデータの取り込み&lt;/h4&gt;

&lt;p&gt;先ほどの手順で、サンプルデータの取り込みは完了だが、もう１パターン、CSVのパースやDataFrame変換を手伝ってくれるspark-csvモジュールを使ったCSV取り込みを書いておく。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Zeppelinはパラグラフごとに実行するしないを制御できるので、別パターンのコードや使わなくなったコードも、そのまま残しておいても支障はない。後になって再利用できたりするので、残しておくと便利かも。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5 id=&#34;依存モジュールロード:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;依存モジュールロード&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;%dep
z.reset()
z.load(&amp;quot;com.databricks:spark-csv_2.11:1.3.0&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;%dep&lt;/code&gt;(Dependency)については、Sparkが起動する前に行わないと、以下の様なエラーが出る。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Must be used before SparkInterpreter (%spark) initialized
Hint: put this paragraph before any Spark code and restart Zeppelin/Interpreter
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;既に起動してしまっている場合は、Zeppelinを再起動するか、InterpreterページのSpark欄の&lt;code&gt;restart&lt;/code&gt;ボタンを押下する。&lt;/p&gt;

&lt;h5 id=&#34;spark-csvを使ったcsvの取り込み:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;spark-csvを使ったCSVの取り込み&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.sql.types.{
  StructType,
  StructField,
  StringType,
  DoubleType
}
val customSchema = StructType(Seq(
  StructField(&amp;quot;marriedAge&amp;quot;, DoubleType, true),
  StructField(&amp;quot;blood&amp;quot;, StringType, true),
  StructField(&amp;quot;sex&amp;quot;, StringType, true),
  StructField(&amp;quot;height&amp;quot;, DoubleType, true),
  StructField(&amp;quot;weight&amp;quot;, DoubleType, true)
))
val training = sqlContext.read
  .format(&amp;quot;com.databricks.spark.csv&amp;quot;)
  .option(&amp;quot;header&amp;quot;, &amp;quot;true&amp;quot;)
  .schema(customSchema)
  .load(&amp;quot;/tmp/training.csv&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;CSVのパースやヘッダ除去(項目名に利用)まで自動で行ってくれる。型指定まで自動で行うオプション(inferSchema)もあるが、Double型がInteger型になってしまったので、今回は手動で指定した。&lt;/p&gt;

&lt;h3 id=&#34;サンプルデータの前処理:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;サンプルデータの前処理&lt;/h3&gt;

&lt;p&gt;Sparkの機械学習ライブラリでは、各分析アルゴリズムにデータを引き渡す前処理として、特徴データ(血液型、性別、身長、体重)を、まとめてベクトル形式に変換する必要がある。&lt;/p&gt;

&lt;p&gt;そういった前処理を楽にするために、前処理や回帰モデル設定、訓練データ取り込みを一貫して行えるspark.mlのPipelineを利用してみる。Pipelineについては、&lt;a href=&#34;https://spark.apache.org/docs/1.6.0/ml-guide.html&#34;&gt;公式のドキュメント&lt;/a&gt;が詳しい。別サイトに&lt;a href=&#34;http://mogile.web.fc2.com/spark/ml-guide.html&#34;&gt;日本語訳&lt;/a&gt;もあった。&lt;/p&gt;

&lt;h4 id=&#34;文字列インデクサ:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;文字列インデクサ&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.ml.feature.StringIndexer

val bloodIndexer = new StringIndexer()
  .setInputCol(&amp;quot;blood&amp;quot;)
  .setOutputCol(&amp;quot;bloodIndex&amp;quot;)
val sexIndexer = new StringIndexer()
  .setInputCol(&amp;quot;sex&amp;quot;)
  .setOutputCol(&amp;quot;sexIndex&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;StringIndexerはパイプラインをつなぐための部品の一つ。&lt;code&gt;A&lt;/code&gt;,&lt;code&gt;B&lt;/code&gt;,&lt;code&gt;O&lt;/code&gt;,&lt;code&gt;AB&lt;/code&gt;といった文字列のカテゴリデータを&lt;code&gt;0.0&lt;/code&gt;,&lt;code&gt;1.0&lt;/code&gt;,&lt;code&gt;2.0&lt;/code&gt;,&lt;code&gt;3.0&lt;/code&gt;みたいに実数のインデックスに変換してくれる。&lt;/p&gt;

&lt;h4 id=&#34;複数項目のベクトル化:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;複数項目のベクトル化&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.ml.feature.VectorAssembler

val assembler = new VectorAssembler()
  .setInputCols(Array(
    &amp;quot;bloodIndex&amp;quot;,
    &amp;quot;sexIndex&amp;quot;,
    &amp;quot;height&amp;quot;,
    &amp;quot;weight&amp;quot;
  ))
  .setOutputCol(&amp;quot;features&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;VectorAssemblerはパイプラインをつなぐための部品の一つ。複数項目の実数データを一つの特徴ベクトルデータに変換してくれる。&lt;/p&gt;

&lt;h4 id=&#34;ベクトル標準化:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;ベクトル標準化&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.ml.feature.StandardScaler

val scaler = new StandardScaler()
  .setInputCol(assembler.getOutputCol)
  .setOutputCol(&amp;quot;scaledFeatures&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;StandardScalerはパイプラインをつなぐための部品の一つ。基準が違うデータを取り込むと予測が不安定になるため、特徴ベクトルデータを標準化する。&lt;/p&gt;

&lt;p&gt;これで前処理のためのパイプライン部品は揃った。&lt;/p&gt;

&lt;h3 id=&#34;線形回帰モデルの作成して-予測値を得る:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;線形回帰モデルの作成して、予測値を得る&lt;/h3&gt;

&lt;p&gt;まずは線形回帰を用いて、値の予測を行うためのモデルを作成する。&lt;/p&gt;

&lt;h4 id=&#34;線形回帰:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;線形回帰&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.ml.regression.LinearRegression

val regression = new LinearRegression()
  .setLabelCol(&amp;quot;marriedAge&amp;quot;)
  .setFeaturesCol(scaler.getOutputCol)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;パイプライン部品の一つ。&lt;code&gt;setLabelCol&lt;/code&gt;には、予想したい項目を指定する。&lt;/p&gt;

&lt;h4 id=&#34;パイプライン作成:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;パイプライン作成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.ml.Pipeline

val pipeline = new Pipeline()
  .setStages(Array(
    bloodIndexer,
    sexIndexer,
    assembler,
    scaler,
    regression
  ))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;今までのパイプライン部品を繋げて、パイプラインを作成する。&lt;/p&gt;

&lt;h4 id=&#34;クロス検証でチューニング設定をして-モデルを作成:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;クロス検証でチューニング設定をして、モデルを作成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.tuning.{
  ParamGridBuilder,
  CrossValidator
}

val paramGrid = new ParamGridBuilder()
  .addGrid(regression.regParam, Array(0.1, 0.5, 0.01))
  .addGrid(regression.maxIter, Array(10, 100, 1000))
  .build()

val evaluator = new RegressionEvaluator()
  .setLabelCol(regression.getLabelCol)
  .setPredictionCol(regression.getPredictionCol)

val cross = new CrossValidator()
  .setEstimator(pipeline)
  .setEvaluator(evaluator)
  .setEstimatorParamMaps(paramGrid)
  .setNumFolds(3)

val model = cross.fit(training)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;モデルの精度を検証するために、クロス検証の設定をする。以下のような、めんどくさいチューニング処理を自動で行ってくれる便利な機能。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;paramGrid&lt;/code&gt;で設定した配列のチューニング値で、全組み合わせのモデルを作成する。&lt;/li&gt;
&lt;li&gt;サンプルデータを訓練データと検証データに分けて、一番精度の高いモデルを選択する。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最終的には&lt;code&gt;model&lt;/code&gt;変数に最適なモデルが束縛される。&lt;/p&gt;

&lt;h5 id=&#34;ちなみに-クロス検証を行わない場合は:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;ちなみに、クロス検証を行わない場合は&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;val model = pipeline.fit(training)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;パイプラインに直接学習データを突っ込む。&lt;/p&gt;

&lt;h4 id=&#34;テストデータ作成:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;テストデータ作成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;var test = sc.parallelize(Seq(
  // A型標準体型男
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;男&amp;quot;, 170, 65),
  // B型標準体型男
  Profile(None, &amp;quot;B&amp;quot;, &amp;quot;男&amp;quot;, 170, 65),
  // O型標準体型男
  Profile(None, &amp;quot;O&amp;quot;, &amp;quot;男&amp;quot;, 170, 65),
  // AB型標準体型男
  Profile(None, &amp;quot;AB&amp;quot;, &amp;quot;男&amp;quot;, 170, 65),
  // A型標準体型女
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;女&amp;quot;, 160, 50),
  // B型標準体型女
  Profile(None, &amp;quot;B&amp;quot;, &amp;quot;女&amp;quot;, 160, 50),
  // O型標準体型女
  Profile(None, &amp;quot;O&amp;quot;, &amp;quot;女&amp;quot;, 160, 50),
  // AB型標準体型女
  Profile(None, &amp;quot;AB&amp;quot;, &amp;quot;女&amp;quot;, 160, 50),
  // A型もやし男
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;男&amp;quot;, 170, 35),
  // A型でぶ男
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;男&amp;quot;, 170, 100),
  // A型もやし女
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;女&amp;quot;, 170, 35),
  // A型でぶ女
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;女&amp;quot;, 170, 100),
  // A型高身長男
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;男&amp;quot;, 190, 80),
  // A型小人(男)
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;男&amp;quot;, 17, 6),
  // A型巨人(男)
  Profile(None, &amp;quot;A&amp;quot;, &amp;quot;男&amp;quot;, 17000, 6500)
)).toDF
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;学習データの特徴データから大幅に外れるデータの予測も下の方に入れてみた。&lt;/p&gt;

&lt;h4 id=&#34;モデルから予測値を得る:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;モデルから予測値を得る&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;model.transform(test)
  .select(&amp;quot;blood&amp;quot;, &amp;quot;sex&amp;quot;, &amp;quot;height&amp;quot;, &amp;quot;weight&amp;quot;, &amp;quot;prediction&amp;quot;).show
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;+-----+---+-------+------+------------------+
|blood|sex| height|weight|        prediction|
+-----+---+-------+------+------------------+
|    A|  男|  170.0|  65.0| 32.79763046781005|
|    B|  男|  170.0|  65.0|32.810260236687924|
|    O|  男|  170.0|  65.0|   32.803945352249|
|   AB|  男|  170.0|  65.0| 32.79131558337113|
|    A|  女|  160.0|  50.0|  28.7197777975515|
|    B|  女|  160.0|  50.0|28.732407566429345|
|    O|  女|  160.0|  50.0|28.726092681990423|
|   AB|  女|  160.0|  50.0| 28.71346291311255|
|    A|  男|  170.0|  35.0|36.194018649003766|
|    A|  男|  170.0| 100.0|28.835177589750728|
|    A|  女|  170.0|  35.0| 36.18913457728998|
|    A|  女|  170.0| 100.0| 28.83029351803694|
|    A|  男|  190.0|  80.0|  42.6417617554965|
|    A|  男|   17.0|   6.0|-48.82159525304273|
|    A|  男|17000.0|6500.0|  9017.13917142714|
+-----+---+-------+------+------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;結果考察:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;結果考察&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;男性/女性の結婚時期の違いは、うまいこと現れた。&lt;/li&gt;
&lt;li&gt;血液型による違いが、ほとんど現れなかった。&lt;/li&gt;
&lt;li&gt;肥満/標準/もやしの違いは、よくわからない。&lt;/li&gt;
&lt;li&gt;男性高身長のルールはうまく反映されている。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ちなみに、南くんの恋人は生まれる50年前に結婚しており、巨神兵は結婚までに100世紀かかるらしい。突拍子もない結果に見えるが、&lt;code&gt;身長が高いほど結婚が遅い&lt;/code&gt;というルールを線形的に捉えてくれているようにも見える。&lt;/p&gt;

&lt;h3 id=&#34;決定木回帰モデルを作成して-予測値を得る:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;決定木回帰モデルを作成して、予測値を得る&lt;/h3&gt;

&lt;p&gt;決定木はクラス分類が得意な手法なので、今回のような細かいルールの設定でも、うまく予測してくれるかもしれない。&lt;/p&gt;

&lt;p&gt;解析手法を変わるとはいえ、インタフェースが変わるわけではないので、
大幅にコードを変える必要はなく、試行錯誤が楽。&lt;/p&gt;

&lt;h4 id=&#34;クロス検証からモデルの作成まで:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;クロス検証からモデルの作成まで&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;import org.apache.spark.ml.regression.DecisionTreeRegressor
import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.ml.tuning.{
  ParamGridBuilder,
  CrossValidator
}

val regression = new DecisionTreeRegressor()
  .setLabelCol(&amp;quot;marriedAge&amp;quot;)
  .setFeaturesCol(scaler.getOutputCol)

val pipeline = new Pipeline()
  .setStages(Array(
    bloodIndexer,
    sexIndexer,
    assembler,
    scaler,
    regression
  ))

val paramGrid = new ParamGridBuilder()
  .addGrid(regression.maxBins, Array(2, 3, 4))
  .addGrid(regression.maxDepth, Array(10, 20, 30))
  .build()

val evaluator = new RegressionEvaluator()
  .setLabelCol(regression.getLabelCol)
  .setPredictionCol(regression.getPredictionCol)

val cross = new CrossValidator()
  .setEstimator(pipeline)
  .setEvaluator(evaluator)
  .setEstimatorParamMaps(paramGrid)
  .setNumFolds(3)

val model = cross.fit(training)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Regressorクラスの種類、paramGridで設定する項目が変わるぐらいで、その他は線形回帰のコードと変わらない。&lt;/p&gt;

&lt;h5 id=&#34;評価実行:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;評価実行&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;model.transform(test)
  .select(&amp;quot;blood&amp;quot;, &amp;quot;sex&amp;quot;, &amp;quot;height&amp;quot;, &amp;quot;weight&amp;quot;, &amp;quot;prediction&amp;quot;).show
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;+-----+---+-------+------+------------------+
|blood|sex| height|weight|        prediction|
+-----+---+-------+------+------------------+
|    A|  男|  170.0|  65.0|41.666666666666664|
|    B|  男|  170.0|  65.0|22.857142857142858|
|    O|  男|  170.0|  65.0|              47.0|
|   AB|  男|  170.0|  65.0|19.666666666666668|
|    A|  女|  160.0|  50.0|              34.0|
|    B|  女|  160.0|  50.0|              20.0|
|    O|  女|  160.0|  50.0|              29.5|
|   AB|  女|  160.0|  50.0|              19.0|
|    A|  男|  170.0|  35.0|              35.0|
|    A|  男|  170.0| 100.0|41.666666666666664|
|    A|  女|  170.0|  35.0|              35.0|
|    A|  女|  170.0| 100.0|              41.0|
|    A|  男|  190.0|  80.0|              44.0|
|    A|  男|   17.0|   6.0|29.333333333333332|
|    A|  男|17000.0|6500.0|              44.0|
+-----+---+-------+------+------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;結果考察-1:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;結果考察&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;どのルールも学習データの値に近い形で、うまいこと予測された。&lt;/li&gt;
&lt;li&gt;ただ、下２つの人外データに関しても、学習データに近い値が出てしまっているので、学習データの特徴値から大きくハズレるレコードの予測値は大味になってしまう？&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;まとめ:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;まとめ&lt;/h3&gt;

&lt;p&gt;今回は、CSV形式のサンプルデータを線形回帰と決定木回帰を用いて、値の予測を行った。&lt;/p&gt;

&lt;h5 id=&#34;線形回帰-1:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;線形回帰&lt;/h5&gt;

&lt;p&gt;細かいルールまでは予測しきれなかったが、学習データにない特徴を持つデータでも、うまく特徴を捉えようと、努力していた感があった。&lt;/p&gt;

&lt;h5 id=&#34;決定木回帰:c26ed1f8424ab1bb2406ee54bb00b33a&#34;&gt;決定木回帰&lt;/h5&gt;

&lt;p&gt;細かいルールに基づいた値をうまく予測してくれていたが、学習データにない特徴を持つデータに関しては、諦めていた感があった。&lt;/p&gt;

&lt;p&gt;分析手法によって、予測結果の傾向が変わることを確認できた。今後、ランダムフォレスト回帰、勾配ブースト木回帰、生存回帰など色々な手法も試してみたい。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34;&gt;

&lt;hr /&gt;

&lt;ol&gt;
&lt;li id=&#34;fn:c26ed1f8424ab1bb2406ee54bb00b33a:1&#34;&gt;類似のOSSに、Jupyter(iPython Notebook)やspark-notebookがある。データの加工やモデルのチューニングで試行錯誤することが多いので、こういうソフトはかなり重宝する。
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:c26ed1f8424ab1bb2406ee54bb00b33a:1&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>クラウドとローカルをVPNでガッチャンコしたDockerネットワークを組んでみる</title>
      <link>http://blog.namiking.net/post/2016/01/docker-swarm-over-vpn/</link>
      <pubDate>Fri, 22 Jan 2016 02:30:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/01/docker-swarm-over-vpn/</guid>
      <description>

&lt;p&gt;クラウド上で組んだDockerのオーバーレイネットワークの中に、屋内ファイアウォール内のマシンで組んでたDockerを参加させることができないか、試してみた。&lt;/p&gt;

&lt;h3 id=&#34;なぜに-余談:de820086aea356a53ede32388b7ce8bb&#34;&gt;なぜに？ (余談)&lt;/h3&gt;

&lt;p&gt;例えば、&lt;a href=&#34;http://blog.namiking.net/post/2016/01/docker-swarm-spark/&#34;&gt;以前の記事&lt;/a&gt;のようにクラウドで組んだSparkクラスタを利用するために、Apache Zeppelin&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:de820086aea356a53ede32388b7ce8bb:1&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:de820086aea356a53ede32388b7ce8bb:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;を使ってみたい。最初は、SSHトンネルやSocksプロキシで手軽にできないだろうかと色々やってみたが、Sparkは一方通行な通信ではなく、双方向な通信を行うため、クラウドからローカルのネットワークに直接アクセス(pingなど)できる必要があるようで、動作するには至らなかった。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-over-vpn/zeppelin-local.svg&#34; alt=&#34;Zeppelin on Local&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Spark関連は基本的に同じネットワーク内で動かすのが前提の仕様なため、Swarmクラスタと同じネットワーク内に入れる必要があるが、Zeppelinは、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;割りとメモリ食い(推奨4GBほど)のため、Sparkクラスタと同じホストでは、動かしたくない。かといって、専用のホストを用意するできるブルジョアではない。&lt;/li&gt;
&lt;li&gt;クラウドのSparkクラスタは使い終わったらすぐに破壊したいが、Zeppelinはnotebookや設定のデータを内部で持つため、作った後や消す前の同期が少し面倒。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;なので、できれば、ローカルネットワーク内にZeppelinを持っておきたい。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-over-vpn/zeppelin-local-ideal.svg&#34; alt=&#34;Zeppelin on Local Ideal&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;この記事でやること:de820086aea356a53ede32388b7ce8bb&#34;&gt;この記事でやること&lt;/h3&gt;

&lt;p&gt;上のApache Sparkの例は置いといて、今回は取り急ぎクラウドとローカルを跨いだSwarmクラスタを構築して、Dockerオーバーレイネットワークの疎通確認を行いたい。検証のため、以下の図の様な構成を準備して、オーバーレイネットワークの疎通を確認する。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-over-vpn/docker-swarm-over-vpn.svg&#34; alt=&#34;Zeppelin on Local Ideal&#34; /&gt;&lt;/p&gt;

&lt;h5 id=&#34;要点:de820086aea356a53ede32388b7ce8bb&#34;&gt;要点&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;ローカル環境とクラウドのDockerをVPNを用いて、オーバーレイネットワークで繋ぎたい。&lt;/li&gt;
&lt;li&gt;VPN接続には、SoftEther VPN&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:de820086aea356a53ede32388b7ce8bb:2&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:de820086aea356a53ede32388b7ce8bb:2&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;を使ってみる。&lt;/li&gt;
&lt;li&gt;ローカル側とクラウド側１台ずつ、Swarm Masterをレプリケーションした。

&lt;ul&gt;
&lt;li&gt;クラウドのみでも、Swarmクラスタとして機能させたかったため。&lt;/li&gt;
&lt;li&gt;ローカルの作業用PCは、いちいちVPNに接続しなくても、Swarmクラスタを操作できるようにするため。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;事前準備:de820086aea356a53ede32388b7ce8bb&#34;&gt;事前準備&lt;/h3&gt;

&lt;h4 id=&#34;必要なソフトウェアのインストール:de820086aea356a53ede32388b7ce8bb&#34;&gt;必要なソフトウェアのインストール&lt;/h4&gt;

&lt;p&gt;作業で使うPC(またはホスト)に以下のソフトウェアをインストールしておく。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docker (バージョン1.9以上, オーバーレイネットワーク機能を使う)&lt;/li&gt;
&lt;li&gt;docker-compose (動作検証に使うだけなので任意)&lt;/li&gt;
&lt;li&gt;Virtualbox (vagrantを併用してもよい)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;digitaloceanの登録とアクセストークンの取得:de820086aea356a53ede32388b7ce8bb&#34;&gt;DigitalOceanの登録とアクセストークンの取得&lt;/h4&gt;

&lt;p&gt;登録後、管理画面からdocker-machineとの連携に必要なアクセストークンを発行できる。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.digitalocean.com/&#34;&gt;https://www.digitalocean.com/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;ノード用のホストを用意する:de820086aea356a53ede32388b7ce8bb&#34;&gt;ノード用のホストを用意する&lt;/h3&gt;

&lt;p&gt;今回の例では、DigitalOceanでノードを２台とローカルでVirtualbox１台を用意する。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;Type&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Host&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;OS&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Mem&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;VPNのIP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;DigitalOcean&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;swarm-node0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;ubuntu-15-10-x64&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;512MB&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;192.168.30.2 (手動)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;DigitalOcean&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;swarm-node1&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;ubuntu-15-10-x64&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;512MB&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;192.168.30.x (自動)&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Vitualbox(local)&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;swarm-local&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;ubuntu-15-10-x64&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;-&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;192.168.30.y (自動)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h5 id=&#34;備考:de820086aea356a53ede32388b7ce8bb&#34;&gt;備考&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;swarm-node0はマスターノードとして使う&lt;/li&gt;
&lt;li&gt;ホスト名(hostname)は別になんでもよい&lt;/li&gt;
&lt;li&gt;プライベートネットワークは無効にしておく&lt;/li&gt;
&lt;li&gt;VPNのIPについては、VPN接続後に割り当てる。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;softether-vpn-serverを動かす:de820086aea356a53ede32388b7ce8bb&#34;&gt;SoftEther VPN Serverを動かす&lt;/h3&gt;

&lt;p&gt;swarm-node0にSSHなどでログインして、作業を行う。&lt;/p&gt;

&lt;p&gt;Linux版については、&lt;a href=&#34;https://ja.softether.org/&#34;&gt;SoftEther VPNのサイト&lt;/a&gt;からソースコードをダウンロードして、コンパイルする。後で自動化しやすいように、GUIやインタラクティブCUIを使わないように書いておく。&lt;/p&gt;

&lt;h4 id=&#34;ダウンロード:de820086aea356a53ede32388b7ce8bb&#34;&gt;ダウンロード&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 必要なパッケージのインストール
apt-get install -y curl gcc make

# SoftEther VPN ソースのダウンロード
cd /usr/local/src
curl -LO http://jp.softether-download.com/files/softether/v4.19-9599-beta-2015.10.19-tree/Linux/SoftEther_VPN_Server/64bit_-_Intel_x64_or_AMD64/softether-vpnserver-v4.19-9599-beta-2015.10.19-linux-x64-64bit.tar.gz
tar xzf softether-vpnserver-v4.19-9599-beta-2015.10.19-linux-x64-64bit.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;コンパイルとインストール:de820086aea356a53ede32388b7ce8bb&#34;&gt;コンパイルとインストール&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# コンパイル
cd vpnserver
make i_read_and_agree_the_license_agreement

# PATH設定
export PATH=&amp;quot;/usr/local/src/vpnserver:$PATH&amp;quot;
echo &#39;export PATH=&amp;quot;/usr/local/src/vpnserver:$PATH&amp;quot;&#39; &amp;gt;&amp;gt; /etc/profile
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;サービス登録と起動-systemd:de820086aea356a53ede32388b7ce8bb&#34;&gt;サービス登録と起動 (systemd)&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# サービス登録
cat &amp;lt;&amp;lt; EOS &amp;gt; /lib/systemd/system/vpnserver.service
[Unit]
Description=SoftEther VPN Server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/src/vpnserver/vpnserver start
ExecStop=/usr/local/src/vpnserver/vpnserver stop

[Install]
WantedBy=multi-user.target
EOS

# 自動起動設定＆起動
systemctl enable vpnserver
systemctl start vpnserver
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;vpnサーバー設定:de820086aea356a53ede32388b7ce8bb&#34;&gt;VPNサーバー設定&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 各種設定項目 (値は任意で決める)
export HUBNAME=cluster
export HUBPASS=password
export USERNAME=user
export USERPASS=something
export SHAREDKEY=sharedkey

# 仮想HUB作成
vpncmd localhost /SERVER /CMD HubCreate $HUBNAME \
  /PASSWORD:$HUBPASS &amp;amp;&amp;amp; true

# SNAT＆DHCP有効化
vpncmd localhost /SERVER /HUB:$HUBNAME /PASSWORD:$HUBPASS /CMD \
  SecureNatEnable

# ユーザー登録
vpncmd localhost /SERVER /HUB:$HUBNAME /PASSWORD:$HUBPASS /CMD \
  UserCreate $USERNAME \
  /GROUP:none \
  /REALNAME:none \
  /NOTE:none

# ユーザーパスワード設定
vpncmd localhost /SERVER /HUB:$HUBNAME /PASSWORD:$HUBPASS /CMD \
  UserPasswordSet $USERNAME \
  /PASSWORD:$USERPASS

# IPsec VPN有効化
vpncmd localhost /SERVER /CMD \
  IPsecEnable \
  /L2TP:yes \
  /L2TPRAW:no \
  /ETHERIP:yes \
  /PSK:$SHAREDKEY \
  /DEFAULTHUB:$HUBNAME
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;VPN設定のリファレンスは以下を参照。&lt;br /&gt;
&lt;a href=&#34;https://ja.softether.org/4-docs/1-manual/6/6.4&#34;&gt;https://ja.softether.org/4-docs/1-manual/6/6.4&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;softether-vpn-clientを動かす:de820086aea356a53ede32388b7ce8bb&#34;&gt;SoftEther VPN Clientを動かす&lt;/h3&gt;

&lt;p&gt;全てのノードにSSHなどでログインして、作業を行う。&lt;br /&gt;
Serverと同じく、&lt;a href=&#34;https://ja.softether.org/&#34;&gt;SoftEther VPNのサイト&lt;/a&gt;からソースコードをダウンロードして、コンパイルする。&lt;/p&gt;

&lt;h4 id=&#34;ダウンロード-1:de820086aea356a53ede32388b7ce8bb&#34;&gt;ダウンロード&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 必要なパッケージのインストール
apt-get install -y curl gcc make

# SoftEther VPN ソースのダウンロード
cd /usr/local/src
curl -LO http://jp.softether-download.com/files/softether/v4.19-9599-beta-2015.10.19-tree/Linux/SoftEther_VPN_Client/64bit_-_Intel_x64_or_AMD64/softether-vpnclient-v4.19-9599-beta-2015.10.19-linux-x64-64bit.tar.gz
tar xzf softether-vpnclient-v4.19-9599-beta-2015.10.19-linux-x64-64bit.tar.gz
rm softether-vpnclient-v4.19-9599-beta-2015.10.19-linux-x64-64bit.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;コンパイルとインストール-1:de820086aea356a53ede32388b7ce8bb&#34;&gt;コンパイルとインストール&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# コンパイル
cd vpnclient
make i_read_and_agree_the_license_agreement

# PATH設定
export PATH=&amp;quot;/usr/local/src/vpnclient:$PATH&amp;quot;
echo &#39;export PATH=&amp;quot;/usr/local/src/vpnclient:$PATH&amp;quot;&#39; &amp;gt;&amp;gt; /etc/profile
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;サービス登録と起動-systemd-1:de820086aea356a53ede32388b7ce8bb&#34;&gt;サービス登録と起動 (systemd)&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# サービス登録
cat &amp;lt;&amp;lt; EOS &amp;gt; /lib/systemd/system/vpnclient.service
[Unit]
Description=SoftEther VPN Client
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/src/vpnclient/vpnclient start
ExecStop=/usr/local/src/vpnclient/vpnclient stop

[Install]
WantedBy=multi-user.target
EOS

# 自動起動設定＆起動
systemctl enable vpnclient
systemctl start vpnclient
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;vpnサーバー設定-1:de820086aea356a53ede32388b7ce8bb&#34;&gt;VPNサーバー設定&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 各種設定項目 (基本的にはServerの値と合わせる)
ACCOUNT=private
NICNAME=vlan0
SERVER=&amp;quot;(swarm-node0のグローバルIP):443&amp;quot;
HUBNAME=cluster
USERNAME=user
USERPASS=something

# クライアント管理へのリモートログインを無効
vpncmd localhost /CLIENT /CMD RemoteDisable

# NIC作成 (この例だと、vpn_vlan0というインタフェースが作成される)
vpncmd localhost /CLIENT /CMD NicCreate $NICNAME

# アカウント作成
vpncmd localhost /CLIENT /CMD AccountCreate $ACCOUNT \
  /SERVER:$SERVER \
  /HUB:$HUBNAME \
  /USERNAME:$USERNAME \
  /NICNAME:$NICNAME

# アカウントパスワード設定
vpncmd localhost /CLIENT /CMD AccountPasswordSet $ACCOUNT \
  /PASSWORD:$USERPASS \
  /TYPE:standard

# アカウント自動起動設定
vpncmd localhost /CLIENT /CMD AccountStartupSet $ACCOUNT

# アカウント接続
vpncmd localhost /CLIENT /CMD AccountConnect $ACCOUNT

# swarm-node0では固定IP割り当て
ip addr add 192.168.30.2/24 dev vpn_$NICNAME

# その他ノードは自動割り当て
dhclient vpn_$NICNAME
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;swarm-node0だけは、DHCPでIPを振らずに固定IPを設定する。&lt;/p&gt;

&lt;h4 id=&#34;vpn接続確認:de820086aea356a53ede32388b7ce8bb&#34;&gt;VPN接続確認&lt;/h4&gt;

&lt;p&gt;SoftEther VPNのClientのセッティングが完了すると、以下の様な成果が出る。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;各ノードに&lt;code&gt;vpn_vlan0&lt;/code&gt;というインタフェースができる。

&lt;ul&gt;
&lt;li&gt;vpn_vlan0を通して、pingなどの疎通ができるようになる。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;各ノードに192.168.30.0/24のネットワークのIPが割り当てられる。

&lt;ul&gt;
&lt;li&gt;192.168.30.0/24はSoftEther VPNのデフォルト設定。&lt;/li&gt;
&lt;li&gt;DHCPで割り当てると、192.168.30.10〜が振られる。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これから設置するConsulやDockerは、このネットワークにのせるように設定する。&lt;/p&gt;

&lt;h3 id=&#34;各ノードでconsulを動かす:de820086aea356a53ede32388b7ce8bb&#34;&gt;各ノードでConsulを動かす&lt;/h3&gt;

&lt;p&gt;インストールとサービス登録手順については、&lt;a href=&#34;http://blog.namiking.net/post/2016/01/docker-swarm-build-using-tls#各ノードでconsulを動かす:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;以前の記事&lt;/a&gt;にまとめてあるので参照されたい。ここではConsulサーバーの設定手順を示す。&lt;/p&gt;

&lt;h4 id=&#34;swarm-node0にてサーバーモードで動かす設定:de820086aea356a53ede32388b7ce8bb&#34;&gt;swarm-node0にてサーバーモードで動かす設定&lt;/h4&gt;

&lt;p&gt;SSHでログインして、作業を行う。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 自分自身のVPNのIPを取得
export MY_VPN_IP=$(
  ip addr show vpn_vlan0 \
  | grep -o -e &#39;[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+&#39; \
  | head -n1
)

# 設定ファイルに書き出す
cat &amp;lt;&amp;lt; EOS &amp;gt; /etc/consul.d/config.json
{
  &amp;quot;server&amp;quot;: true,
  &amp;quot;bootstrap&amp;quot;: true,
  &amp;quot;bind_addr&amp;quot;: &amp;quot;$MY_VPN_IP&amp;quot;,
  &amp;quot;datacenter&amp;quot;: &amp;quot;swarm0&amp;quot;,
  &amp;quot;ui_dir&amp;quot;: &amp;quot;/var/local/consul/webui&amp;quot;,
  &amp;quot;data_dir&amp;quot;: &amp;quot;/var/local/consul/data&amp;quot;,
  &amp;quot;log_level&amp;quot;: &amp;quot;INFO&amp;quot;,
  &amp;quot;enable_syslog&amp;quot;: true
}
EOS
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;VPNネットワークである&lt;code&gt;vpn_vlan0&lt;/code&gt;のIPにバインドする。&lt;br /&gt;
設定が終わったら、自動起動設定と起動を行っておく。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;systemctl enable consul
systemctl start consul
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;swarm-node1とswarm-localにてクライアントモードで動かす設定:de820086aea356a53ede32388b7ce8bb&#34;&gt;swarm-node1とswarm-localにてクライアントモードで動かす設定&lt;/h4&gt;

&lt;p&gt;SSHでログインして、作業を行う。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 自分自身のvpnのipを取得
export my_vpn_ip=$(
  ip addr show vpn_vlan0 \
  | grep -o -e &#39;[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+&#39; \
  | head -n1
)

# 設定ファイルに書き出す
cat &amp;lt;&amp;lt; eos &amp;gt; /etc/consul.d/config.json
{
  &amp;quot;server&amp;quot;: false,
  &amp;quot;start_join&amp;quot;: [&amp;quot;192.168.30.2&amp;quot;],
  &amp;quot;bind_addr&amp;quot;: &amp;quot;$MY_VPN_IP&amp;quot;,
  &amp;quot;datacenter&amp;quot;: &amp;quot;swarm0&amp;quot;,
  &amp;quot;ui_dir&amp;quot;: &amp;quot;/var/local/consul/webui&amp;quot;,
  &amp;quot;data_dir&amp;quot;: &amp;quot;/var/local/consul/data&amp;quot;,
  &amp;quot;log_level&amp;quot;: &amp;quot;INFO&amp;quot;,
  &amp;quot;enable_syslog&amp;quot;: true
}
EOS
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;joinするIPは固定で割り当てたswarm-node0のものを指定。&lt;br /&gt;
設定が終わったら、自動起動設定と起動を行っておく。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;systemctl enable consul
systemctl start consul
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;メンバー確認:de820086aea356a53ede32388b7ce8bb&#34;&gt;メンバー確認&lt;/h4&gt;

&lt;p&gt;各ノードのConsulが連携できているかを確認する。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ consul members

Node     Address            Status  Type    Build  Protocol  DC
******  192.168.30.2:8301   alive   server  0.6.1  2         swarm0
******  192.168.30.10:8301   alive   client  0.6.1  2         swarm0
******  192.168.30.11:8301   alive   client  0.6.1  2         swarm0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;WebUIで確認する場合、ローカルPCのOS設定などから、swarm-node0へVPN接続を行えば、ブラウザで閲覧できる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;open http://192.168.30.2:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;各ノードでdockerを動かす:de820086aea356a53ede32388b7ce8bb&#34;&gt;各ノードでDockerを動かす&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;全3ノードに対して、SSHでログインして作業を行う。&lt;/li&gt;
&lt;li&gt;リモートからDockerを操作するため、TCPの2375ポートを使う。&lt;/li&gt;
&lt;li&gt;リモートからSwarmマスターを操作するために、TCPの3375ポートを使う。&lt;/li&gt;
&lt;li&gt;VPNネットワークがあるので、TLS認証は使わない。&lt;/li&gt;
&lt;li&gt;ただし、&lt;code&gt;vpn_vlan0&lt;/code&gt;以外はポートをファイアウォールで閉じておく。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;インストール:de820086aea356a53ede32388b7ce8bb&#34;&gt;インストール&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;wget -qO- https://get.docker.com/ | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;ファイアウォール設定:de820086aea356a53ede32388b7ce8bb&#34;&gt;ファイアウォール設定&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;iptables -A INPUT -i eth0 -p tcp -m tcp --dport 2375 -j DROP
iptables -A INPUT -i eth0 -p tcp -m tcp --dport 3375 -j DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;DigitalOceanのノードのみでよい。&lt;/p&gt;

&lt;h4 id=&#34;dockerデーモン起動時の引数設定:de820086aea356a53ede32388b7ce8bb&#34;&gt;Dockerデーモン起動時の引数設定&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi /lib/systemd/system/docker.service

# 変更前
ExecStart=/usr/bin/docker daemon -H fd://
# 変更後
ExecStart=/usr/bin/docker daemon \
  -H=0.0.0.0:2375\
  --cluster-store=consul://localhost:8500 \
  --cluster-advertise=vpn_vlan0:2375 \
  -H fd://
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;変更後は見やすさのため複数行で書いているが、&lt;code&gt;\&lt;/code&gt;を消して1行ぶっ続けで記述する。&lt;/p&gt;

&lt;h4 id=&#34;docker再起動:de820086aea356a53ede32388b7ce8bb&#34;&gt;Docker再起動&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;service docker restart
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;各ノードでswarmコンテナを動かす:de820086aea356a53ede32388b7ce8bb&#34;&gt;各ノードでSwarmコンテナを動かす&lt;/h3&gt;

&lt;p&gt;各ノードにSSHでログインして、Swarmコンテナを起動させる。&lt;/p&gt;

&lt;h4 id=&#34;swarm-node0-swarm-local:de820086aea356a53ede32388b7ce8bb&#34;&gt;swarm-node0, swarm-local&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# Swarm Manager
docker run -d --name=swarm-agent-master \
  -v=/etc/docker:/etc/docker --net=host --restart=always \
  swarm manage -H=0.0.0.0:3375 --replication \
    --strategy=spread --advertise=192.168.30.2:3375 consul://localhost:8500

# Swarm Agent
docker run -d --name=swarm-agent --net=host --restart=always \
  swarm join --advertise=192.168.30.2:2375 consul://localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Swarm Managerについては、双方レプリケーションするために&lt;code&gt;--replication&lt;/code&gt;引数をつける。&lt;/p&gt;

&lt;h4 id=&#34;swarm-node1:de820086aea356a53ede32388b7ce8bb&#34;&gt;swarm-node1&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# Swarm Agent
docker run -d --name=swarm-agent --net=host --restart=always \
  swarm join --advertise=192.168.30.x:2375 consul://localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;192.168.30.x&lt;/code&gt;のところには、swarm-node1がVPNのDHCPに割り当てられたのIPを入れる。&lt;/p&gt;

&lt;h4 id=&#34;swarm-local:de820086aea356a53ede32388b7ce8bb&#34;&gt;swarm-local&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# Swarm Manager
docker run -d --name=swarm-agent-master \
  -v=/etc/docker:/etc/docker --net=host --restart=always \
  swarm manage -H=0.0.0.0:3375 --replication \
    --strategy=spread --advertise=192.168.30.y:3375 consul://localhost:8500

# Swarm Agent
docker run -d --name=swarm-agent --net=host --restart=always \
  swarm join --advertise=192.168.30.y:2375 consul://localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;192.168.30.y&lt;/code&gt;のところには、swarm-localがVPNのDHCPに割り当てられたIPを入れる。&lt;/p&gt;

&lt;h3 id=&#34;動作確認:de820086aea356a53ede32388b7ce8bb&#34;&gt;動作確認&lt;/h3&gt;

&lt;h4 id=&#34;ローカルpcのos設定にて-vpn接続を行う:de820086aea356a53ede32388b7ce8bb&#34;&gt;ローカルPCのOS設定にて、VPN接続を行う&lt;/h4&gt;

&lt;p&gt;Macであれば、&lt;a href=&#34;http://ja.softether.org/4-docs/2-howto/L2TP_IPsec_Setup_Guide/5&#34;&gt;SoftEther VPNのドキュメント&lt;/a&gt;を参考にして、設定を行う。入力値に関しては、この記事の通りにやった場合は以下のようになる。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;サーバーアドレス: (swarm-node0のグローバルIP)&lt;/li&gt;
&lt;li&gt;ユーザーID: user&lt;/li&gt;
&lt;li&gt;パスワード: something&lt;/li&gt;
&lt;li&gt;共有シークレット： sharedkey&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;ローカルpcから-swarmマスターに接続してみる:de820086aea356a53ede32388b7ce8bb&#34;&gt;ローカルPCから、Swarmマスターに接続してみる&lt;/h4&gt;

&lt;p&gt;ターミナルなどから以下のコマンドを入力して、Swarmクラスタの接続状況を確認してみる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker -H=192.168.30.2:3375 info

Containers: 4
Images: 4
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 3
 swarm-local: 192.168.30.11:2375
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 4.053 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.2.0-25-generic, operatingsystem=Ubuntu 15.10, storagedriver=aufs
 swarm-node0: 192.168.30.2:2375
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 513.4 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, storagedriver=aufs
 swarm-node1: 192.168.30.10:2375
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 513.4 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, storagedriver=aufs
CPUs: 3
Total Memory: 5.055 GiB
Name: swarm-node0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ノードが３つ接続されていれば、クラウドとローカルを跨いだSwarmクラスタの構築に成功している状態になる。&lt;/p&gt;

&lt;p&gt;ちなみに、&lt;code&gt;-H=192.168.30.2:3375&lt;/code&gt;の部分は、環境変数&lt;code&gt;DOCKER_HOST&lt;/code&gt;に&lt;code&gt;192.168.30.2:3375&lt;/code&gt;と設定しておけば省略できる。&lt;/p&gt;

&lt;h4 id=&#34;各ノードにコンテナを置いてみる:de820086aea356a53ede32388b7ce8bb&#34;&gt;各ノードにコンテナを置いてみる&lt;/h4&gt;

&lt;h5 id=&#34;オーバーレイネットワークの作成:de820086aea356a53ede32388b7ce8bb&#34;&gt;オーバーレイネットワークの作成&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;export DOCKER_HOST=&amp;quot;192.168.30.2:3375&amp;quot;
docker network create testnet
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Swarmマスターへの接続であれば、デフォルトでドライバが&lt;code&gt;overlay&lt;/code&gt;に設定される。&lt;/p&gt;

&lt;h5 id=&#34;docker-compose-yml:de820086aea356a53ede32388b7ce8bb&#34;&gt;docker-compose.yml&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-ruby&#34;&gt;nginx:
  image: nginx
  net: testnet
  ports:
    - &amp;quot;8080:80&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;portsを設定しておけば、コンフリクトを防ぐため、コンテナ配置が自然とバラける。&lt;/p&gt;

&lt;h5 id=&#34;docker-compose-up-scale:de820086aea356a53ede32388b7ce8bb&#34;&gt;docker-compose up &amp;amp; scale&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-compose up -d
docker-compose scale nginx=3
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;コンテナ配置確認:de820086aea356a53ede32388b7ce8bb&#34;&gt;コンテナ配置確認&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker ps --format &amp;quot;{{.Names}}&amp;quot;

swarm-node0/****_nginx_3
swarm-node1/****_nginx_2
swarm-local/****_nginx_1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ちゃんとすべてのホストにコンテナが配置できることを確認。&lt;code&gt;http://192.168.30.x:8080/&lt;/code&gt;各々をブラウザで打ちこめば、nginxのデフォルトページが表示されるはず。&lt;/p&gt;

&lt;p&gt;あとは、コンテナの中に&lt;code&gt;docker exec&lt;/code&gt;で入って、&lt;code&gt;/etc/hosts&lt;/code&gt;の中を見れば、他のコンテナのIPが書いてあるはずなので、pingを打ってみたりで、オーバーレイネットワークの疎通を確認できる。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34;&gt;

&lt;hr /&gt;

&lt;ol&gt;
&lt;li id=&#34;fn:de820086aea356a53ede32388b7ce8bb:1&#34;&gt;&lt;a href=&#34;https://zeppelin.incubator.apache.org/&#34;&gt;Zeppelin&lt;/a&gt;はWeb上からSparkの操作をインタラクティブに行えたり、結果をビジュアライズできたりする、ノートブック系Webアプリ
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:de820086aea356a53ede32388b7ce8bb:1&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li id=&#34;fn:de820086aea356a53ede32388b7ce8bb:2&#34;&gt;TCP/IPベースのVPNで、動かしてみたら割とすんなり動作したので、これを使ってみた。PPTPdも試したが、&lt;a href=&#34;http://askubuntu.com/questions/621820/pptpd-failed-after-upgrading-ubuntu-server-to-15&#34;&gt;Ubuntu15.10で上手く動作しなかった&lt;/a&gt;ので、見送り
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:de820086aea356a53ede32388b7ce8bb:2&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>TLS認証なDocker Swarmクラスタを構築 (docker-machineなしで)</title>
      <link>http://blog.namiking.net/post/2016/01/docker-swarm-build-using-tls/</link>
      <pubDate>Mon, 18 Jan 2016 20:00:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/01/docker-swarm-build-using-tls/</guid>
      <description>

&lt;p&gt;TSL認証なSwarmクラスタはdocker-machineで構築すると、勝手に設定してくれて非常に楽だが、ホストのネットワークを事前に弄りたかったり、Terraformなどの他オーケストレーションツールを組み合わせたいときに、ちょっと融通がきかない。&lt;/p&gt;

&lt;p&gt;なので、TSL認証を用いたDocker Swarmクラスタを&lt;strong&gt;自力で&lt;/strong&gt;構築できるように、手順をまとめておきたい。また、docker-machineの代替として&lt;strong&gt;Terraform&lt;/strong&gt;を使い、自動化できるようにしたい。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-build-using-tls/eyecatch.png&#34; alt=&#34;Docker Swarm using TLS&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;作業の流れ:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;作業の流れ&lt;/h3&gt;

&lt;p&gt;概ね以下の様な流れでSwarmクラスタの構築を行う。基本的には、docker-machineで行われていることを模倣している。docker-machine自体ではサービスディスカバリの準備は行わないので、そこら辺の手順も残しておく。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-build-using-tls/overview.svg&#34; alt=&#34;Overview&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;付録-swarmクラスタをterraformで構築するサンプル:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;[付録] SwarmクラスタをTerraformで構築するサンプル&lt;/h4&gt;

&lt;p&gt;今回の記事で行う作業をTerraformで自動化したものを、以下のリポジトリに置いておくので、ご参考までに。terraform.tfの&lt;code&gt;count&lt;/code&gt;の値を弄ることで、指定数のノードを自動で作成するので、大量にノードが必要な場合に便利かも。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build docker swarm cluster over TLS  using Terraform
&lt;a href=&#34;https://github.com/namikingsoft/sample-terraform-docker-swarm-over-tls&#34;&gt;https://github.com/namikingsoft/sample-terraform-docker-swarm-over-tls&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;事前準備:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;事前準備&lt;/h3&gt;

&lt;h4 id=&#34;必要なソフトウェアのインストール:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;必要なソフトウェアのインストール&lt;/h4&gt;

&lt;p&gt;作業で使うPC(またはホスト)に以下のDocker関連のソフトウェアをインストールしておく。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docker (Engine)&lt;/li&gt;
&lt;li&gt;OpenSSL (Linux系やOSXなら、デフォルトで入っているはず)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;digitaloceanの登録とアクセストークンの取得:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;DigitalOceanの登録とアクセストークンの取得&lt;/h4&gt;

&lt;p&gt;この記事の例では、ホストにDigitalOceanを使うが、AWSとかでも可能と思います。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.digitalocean.com/&#34;&gt;https://www.digitalocean.com/&lt;/a&gt;&lt;br /&gt;
登録後、管理画面からdocker-machineとの連携に必要なアクセストークンを発行できる。&lt;/p&gt;

&lt;h3 id=&#34;ノード用のホストを用意する:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;ノード用のホストを用意する&lt;/h3&gt;

&lt;p&gt;今回の例では、DigitalOceanでノードを２台用意して、Swarmクラスタの連携を確認した。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;Host&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;OS&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Mem&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;IP (eth0)&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;IP (eth1)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;swarm-node0&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;ubuntu-15-10-x64&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;512MB&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;x.x.x.1&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;y.y.y.1&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;swarm-node1&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;ubuntu-15-10-x64&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;512MB&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;x.x.x.2&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;y.y.y.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;h5 id=&#34;備考:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;備考&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;swarm-node0はマスターノードとして使う&lt;/li&gt;
&lt;li&gt;ホスト名(hostname)は別になんでもよい&lt;/li&gt;
&lt;li&gt;プライベートネットワークを有効にしておく&lt;/li&gt;
&lt;li&gt;eth0はグローバルネットワークに繋がるインタフェース&lt;/li&gt;
&lt;li&gt;eth1はプライベートネットワークに繋がるインタフェース&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;各ノードでconsulを動かす:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;各ノードでConsulを動かす&lt;/h3&gt;

&lt;p&gt;Swarmクラスタのサービスディスカバリー(分散KVS)であるConsulを各ノードにインストールする。使わないでもSwarmクラスタは構築できるが、マルチホスト間でオーバーレイ・ネットワークを作れるようになったりと色々利点が多いので。(etcdやZookeeperでも構築可能)&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-build-using-tls/consul.svg&#34; alt=&#34;Swarm Structure&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;swarm-node0にてconsulをサーバーモードで動かす:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node0にてConsulをサーバーモードで動かす&lt;/h4&gt;

&lt;p&gt;SSHでログインして作業を行う。&lt;/p&gt;

&lt;h5 id=&#34;インストール:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;インストール&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 必要なパッケージのインストール
apt-get install -y curl zip

# Consulインストール
cd /tmp
curl -LO https://releases.hashicorp.com/consul/0.6.1/consul_0.6.1_linux_amd64.zip
unzip consul_0.6.1_linux_amd64.zip
mv consul /usr/local/bin

# ConsulのWebUIを設置(任意)
curl -LO https://releases.hashicorp.com/consul/0.6.1/consul_0.6.1_web_ui.zip
unzip consul_0.6.1_web_ui.zip -d consul-webui
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;サービス登録:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;サービス登録&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# service
cat &amp;lt;&amp;lt; EOS &amp;gt; /lib/systemd/system/consul.service
[Unit]
Description=consul agent
After=network-online.target

[Service]
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d
Type=simple
Restart=always

[Install]
WantedBy=multi-user.target
EOS
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;設定:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;設定&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 自分自身のプライベートIPを取得
export MY_PRIVATE_IP=$(
  ip addr show eth1 \
  | grep -o -e &#39;[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+&#39; \
  | head -n1
)

# 設定ファイルに書き出す
cat &amp;lt;&amp;lt; EOS &amp;gt; /etc/consul.d/config.json
{
  &amp;quot;server&amp;quot;: true,
  &amp;quot;bootstrap&amp;quot;: true,
  &amp;quot;bind_addr&amp;quot;: &amp;quot;$MY_PRIVATE_IP&amp;quot;,
  &amp;quot;node_name&amp;quot;: &amp;quot;consul0&amp;quot;,
  &amp;quot;datacenter&amp;quot;: &amp;quot;dc0&amp;quot;,
  &amp;quot;ui_dir&amp;quot;: &amp;quot;/var/local/consul/webui&amp;quot;,
  &amp;quot;data_dir&amp;quot;: &amp;quot;/var/local/consul/data&amp;quot;,
  &amp;quot;log_level&amp;quot;: &amp;quot;INFO&amp;quot;,
  &amp;quot;enable_syslog&amp;quot;: true
}
EOS
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;１台構成のサーバーモードをWebUI付き(任意)で起動する。プライベートネットワークであるeth1のIPにバインドする。&lt;/p&gt;

&lt;h5 id=&#34;自動起動設定-起動:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;自動起動設定＆起動&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;systemctl enable consul
systemctl start consul
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;swarm-node1にてconsulをクライアントモードで動かす:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node1にてConsulをクライアントモードで動かす&lt;/h4&gt;

&lt;p&gt;SSHでログインして作業を行う。設定以外は同じなので割愛。&lt;/p&gt;

&lt;h5 id=&#34;設定-1:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;設定&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# 自分自身のプライベートIPを取得
export MY_PRIVATE_IP=$(
  ip addr show eth1 \
  | grep -o -e &#39;[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+&#39; \
  | head -n1
)

# 設定ファイルに書き出す
cat &amp;lt;&amp;lt; eos &amp;gt; /etc/consul.d/config.json
{
  &amp;quot;server&amp;quot;: false,
  &amp;quot;start_join&amp;quot;: [&amp;quot;y.y.y.1&amp;quot;],
  &amp;quot;bind_addr&amp;quot;: &amp;quot;$MY_PRIVATE_IP&amp;quot;,
  &amp;quot;datacenter&amp;quot;: &amp;quot;dc0&amp;quot;,
  &amp;quot;ui_dir&amp;quot;: &amp;quot;/var/local/consul/webui&amp;quot;,
  &amp;quot;data_dir&amp;quot;: &amp;quot;/var/local/consul/data&amp;quot;,
  &amp;quot;log_level&amp;quot;: &amp;quot;INFO&amp;quot;,
  &amp;quot;enable_syslog&amp;quot;: true
}
EOS
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;swarm-node0のプライベートIPにジョインする。&lt;br /&gt;
設定が終わったら、自動起動設定と起動を行っておく。&lt;/p&gt;

&lt;h4 id=&#34;メンバー確認:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;メンバー確認&lt;/h4&gt;

&lt;p&gt;各ノードのConsulが連携できているかを確認する。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ consul members

Node     Address            Status  Type    Build  Protocol  DC
consul0  y.y.y.1:8301   alive   server  0.6.1  2         dc1
consul1  y.y.y.2:8301   alive   client  0.6.1  2         dc1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;WebUIで確認する場合、ローカルPCと各ノードの間に8500ポートのSSHトンネルを開ければ、ブラウザで閲覧できる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ssh root@x.x.x.1 -L8500:localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;open http://localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;各ノードにdockerをインストール:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;各ノードにDockerをインストール&lt;/h3&gt;

&lt;p&gt;各ノードのSSHにて、以下のコマンドを実行する。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;wget -qO- https://get.docker.com/ | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;デーモン起動時の引数設定などは後ほど行う。&lt;/p&gt;

&lt;h3 id=&#34;tls認証用の鍵を生成する:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;TLS認証用の鍵を生成する&lt;/h3&gt;

&lt;p&gt;クライアント側(ローカルPC)で生成する。後ほど各ノードに必要なファイルを転送する。&lt;/p&gt;

&lt;h4 id=&#34;caの証明書を生成:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;CAの証明書を生成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;openssl genrsa -out ca-key.pem 4096
openssl req -subj &amp;quot;/CN=ca&amp;quot; -new -x509 -days 365 -key ca-key.pem -out ca.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;CommonName(CN)は任意。&lt;/p&gt;

&lt;h4 id=&#34;クライアントの秘密鍵と証明書を生成:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;クライアントの秘密鍵と証明書を生成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# extfile
echo &amp;quot;extendedKeyUsage = clientAuth&amp;quot; &amp;gt;&amp;gt; extfile-client.cnf

# client cert
openssl genrsa -out key.pem 4096
openssl req -subj &#39;/CN=client&#39; -new -key key.pem -out client.csr
openssl x509 -req -days 365 -sha256 -in client.csr -out cert.pem \
  -CA ca.pem -CAkey ca-key.pem -CAcreateserial -extfile extfile-client.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;CommonName(CN)は任意。&lt;/p&gt;

&lt;h4 id=&#34;swarm-node0-master-の秘密鍵と証明書を生成:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node0(master)の秘密鍵と証明書を生成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# extfile
echo &amp;quot;subjectAltName = IP:x.x.x.1&amp;quot; &amp;gt; extfile.cnf
echo &amp;quot;extendedKeyUsage = clientAuth,serverAuth&amp;quot; &amp;gt;&amp;gt; extfile.cnf

# server cert
openssl genrsa -out node0-key.pem 4096
openssl req -subj &amp;quot;/CN=node0&amp;quot; -new -key node0-key.pem -out node0.csr
openssl x509 -req -days 365 -sha256 -in node0.csr -out node0-cert.pem \
  -CA ca.pem -CAkey ca-key.pem -CAcreateserial -extfile extfile.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Swarm Managerを動かすノードなので、clientAuthも設定しておく。subjectAltNameにグローバルIPを設定するので、CommonName(CN)は割と何でもよいが、ドメイン名があれば、それを設定するとよい。&lt;/p&gt;

&lt;h4 id=&#34;swarm-node1の秘密鍵と証明書を生成:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node1の秘密鍵と証明書を生成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# extfile
echo &amp;quot;subjectAltName = IP:x.x.x.2&amp;quot; &amp;gt; extfile.cnf

# server cert
openssl genrsa -out node1-key.pem 4096
openssl req -subj &amp;quot;/CN=node1&amp;quot; -new -key node1-key.pem -out node1.csr
openssl x509 -req -days 365 -sha256 -in node1.csr -out node1-cert.pem \
  -CA ca.pem -CAkey ca-key.pem -CAcreateserial -extfile extfile.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;CommonName(CN)は任意。&lt;/p&gt;

&lt;h4 id=&#34;tls認証鍵を各ノードへアップロード:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;TLS認証鍵を各ノードへアップロード&lt;/h4&gt;

&lt;p&gt;SFTPやSCPなどを使って、各ノードの&lt;code&gt;/etc/docker&lt;/code&gt;&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:357fa4bd2e644c21db7997b0a9ea5cf8:1&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:357fa4bd2e644c21db7997b0a9ea5cf8:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;あたりにアップロードする。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;swarm-node0

&lt;ul&gt;
&lt;li&gt;ca.pem&lt;/li&gt;
&lt;li&gt;node0-cert.pem&lt;/li&gt;
&lt;li&gt;node0-key.pem&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;swarm-node1

&lt;ul&gt;
&lt;li&gt;ca.pem&lt;/li&gt;
&lt;li&gt;node1-cert.pem&lt;/li&gt;
&lt;li&gt;node1-key.pem&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;各ノードのdockerの設定を変更する:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;各ノードのDockerの設定を変更する&lt;/h3&gt;

&lt;p&gt;各ノードにSSHでログインして、設定を行う。&lt;/p&gt;

&lt;h4 id=&#34;dockerデーモン起動時の引数設定:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;Dockerデーモン起動時の引数設定&lt;/h4&gt;

&lt;h5 id=&#34;swarm-node0:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node0&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi /lib/systemd/system/docker.service

# 変更前
ExecStart=/usr/bin/docker daemon -H fd://
# 変更後
ExecStart=/usr/bin/docker daemon \
  --tlsverify \
  --tlscacert=/etc/docker/ca.pem \
  --tlscert=/etc/docker/node0-cert.pem \
  --tlskey=/etc/docker/node0-key.pem \
  -H=0.0.0.0:2376 \
  --cluster-store=consul://localhost:8500 \
  --cluster-advertise=eth0:2376 \
  -H fd://
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;swarm-node1:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node1&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;vi /lib/systemd/system/docker.service

# 変更前
ExecStart=/usr/bin/docker daemon -H fd://
# 変更後
ExecStart=/usr/bin/docker daemon \
  --tlsverify \
  --tlscacert=/etc/docker/ca.pem \
  --tlscert=/etc/docker/node1-cert.pem \
  --tlskey=/etc/docker/node1-key.pem \
  -H=0.0.0.0:2376 \
  --cluster-store=consul://localhost:8500 \
  --cluster-advertise=eth0:2376 \
  -H fd://
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;備考-1:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;備考&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;変更後は見やすさのため複数行で書いているが、&lt;code&gt;\&lt;/code&gt;を消して1行ぶっ続けで記述する。&lt;/li&gt;
&lt;li&gt;この設定はUbuntu15.10の場合なので、他のOSの場合は&lt;code&gt;/etc/default/docker&lt;/code&gt;の&lt;code&gt;DOCKER_OPTS&lt;/code&gt;に引数を付け加えたりなど、やり方が違ってくると思うので、各々調節する。&lt;/li&gt;
&lt;li&gt;cluster-storeとcluster-advertiseの指定は、オーバーレイ・ネットワーク機能のためなので、使わない場合は特に指定しなくても、Swarmクラスタの動作は可能。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;docker再起動:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;Docker再起動&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;service docker restart
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;tls接続確認:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;TLS接続確認&lt;/h4&gt;

&lt;p&gt;ローカルPCから、TLS(TCP)でホストのDockerを利用できるか確認してみる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# swarm-node0
docker --tlsverify \
  --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \
  -H=x.x.x.1:2376 \
  version

# swarm-node1
docker --tlsverify \
  --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \
  -H=x.x.x.2:2376 \
  version
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ClientとServerのDockerバージョンが表示されれば、正しく設定できている。&lt;/p&gt;

&lt;p&gt;また、以下の様な環境変数を設定することで、いちいちTLS認証鍵やIP指定をしなくても、普通にdockerコマンドが扱えるようになる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;export DOCKER_TLS_VERIFY=&amp;quot;1&amp;quot;
export DOCKER_HOST=&amp;quot;tcp://(dockerホストのIP):2376&amp;quot;
export DOCKER_CERT_PATH=&amp;quot;/path/to/クライアント認証鍵があるディレクトリ&amp;quot;

docker version
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;DOCKER_CERT_PATH&lt;/code&gt;については、&lt;code&gt;~/.docker/&lt;/code&gt;にクライアント認証鍵(ca.pem, cert.pem, key.pem)を設置すれば、省略可能。&lt;/p&gt;

&lt;h3 id=&#34;各ノードでswarmコンテナを動かす:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;各ノードでSwarmコンテナを動かす&lt;/h3&gt;

&lt;p&gt;各ノードにSSHでログインして、Swarmコンテナを起動させる。&lt;/p&gt;

&lt;h4 id=&#34;swarm-node0-1:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node0&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# Swarm Manager
docker run -d --name=swarm-agent-master \
  -v /etc/docker:/etc/docker --net=host --restart=always \
  swarm manage --tlsverify \
    --tlscacert=/etc/docker/ca.pem \
    --tlscert=/etc/docker/node0-cert.pem \
    --tlskey=/etc/docker/node0-key.pem \
    -H=tcp://0.0.0.0:3376 --strategy=spread \
    --advertise=x.x.x.1:2376 consul://localhost:8500

# Swarm Agent
docker run -d --name=swarm-agent --net=host --restart=always \
  swarm join --advertise=x.x.x.1:2376 consul://localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;swarm-node1-1:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;swarm-node1&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# Swarm Agent
docker run -d --name swarm-agent --net=host --restart=always \
  swarm join --advertise=x.x.x.2:2376 consul://localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;備考-2:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;備考&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;ネットワークのホストと共有する必要があるので、&lt;code&gt;--net=host&lt;/code&gt;を指定している。&lt;/li&gt;
&lt;li&gt;Swarm Managerで&lt;code&gt;/etc/docker&lt;/code&gt;を共有Volume指定しているのは、TLS認証鍵の共有だけではなく、&lt;code&gt;/etc/docker/key.json&lt;/code&gt;の共有のため。DockerユニークIDの識別に必要とのこと。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;動作確認:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;動作確認&lt;/h3&gt;

&lt;p&gt;クライアント側(ローカルPC)から、Swarmクラスタへの接続を試みる。&lt;/p&gt;

&lt;h4 id=&#34;swarm-masterにtls接続:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;Swarm MasterにTLS接続&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;export DOCKER_TLS_VERIFY=&amp;quot;1&amp;quot;
export DOCKER_HOST=&amp;quot;tcp://x.x.x.1:3376&amp;quot;
export DOCKER_CERT_PATH=&amp;quot;/path/to/クライアント認証鍵があるディレクトリ&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker info

Containers: 3
Images: 2
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 swarm-node0: x.x.x.1:2376
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 513.4 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, storagedriver=aufs
 swarm-node1: x.x.x.2:2376
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 513.4 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, storagedriver=aufs
CPUs: 2
Total Memory: 1.003 GiB
Name: swarm-node0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上のように、ノードが2つ接続されていることが確認できれば、Swarmクラスタの構築がうまく行えている。&lt;code&gt;DOCKER_HOST&lt;/code&gt;のポート指定を&lt;code&gt;2376&lt;/code&gt;ではなく、&lt;code&gt;3376&lt;/code&gt;にすることで、dockerコマンドでSwarm関連の操作を行うことができる。&lt;/p&gt;

&lt;h4 id=&#34;swarmクラスタにコンテナを配置してみる:357fa4bd2e644c21db7997b0a9ea5cf8&#34;&gt;Swarmクラスタにコンテナを配置してみる&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker run -d --name container1 nginx
$ docker run -d --name container2 nginx
$ docker ps --format &amp;quot;{{.Names}}&amp;quot;

swarm-node1/container1
swarm-node2/container2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Swarm Masterのストラテジーが&lt;code&gt;spread&lt;/code&gt;なので、各ノードにコンテナが分散配置される。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34;&gt;

&lt;hr /&gt;

&lt;ol&gt;
&lt;li id=&#34;fn:357fa4bd2e644c21db7997b0a9ea5cf8:1&#34;&gt;TLS認証鍵の置き場所は任意。Dockerデーモン起動時の引数で指定する。
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:357fa4bd2e644c21db7997b0a9ea5cf8:1&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Docker SwarmでApache Sparkクラスタを構築してみる</title>
      <link>http://blog.namiking.net/post/2016/01/docker-swarm-spark/</link>
      <pubDate>Tue, 12 Jan 2016 23:30:23 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/01/docker-swarm-spark/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;http://blog.namiking.net/post/2016/01/docker-swarm-digitalocean/&#34;&gt;前回の記事&lt;/a&gt;でSwarmクラスタを構築したので、Apache Sparkのクラスタを載せてみる。&lt;/p&gt;

&lt;p&gt;本来ならオンプレでクラスタを組んだり、AmazonのEMRを使うのが一般的かもだが、安めのクラウドでもできないかなーという試み。&lt;/p&gt;

&lt;p&gt;まずはシンプルに、Standaloneモードから動かしてみたい。&lt;/p&gt;

&lt;h3 id=&#34;事前準備:27882acbebbf2525e531e2163851a874&#34;&gt;事前準備&lt;/h3&gt;

&lt;h4 id=&#34;マルチホスト同士の通信が可能なswarmクラスタを構築しておく:27882acbebbf2525e531e2163851a874&#34;&gt;マルチホスト同士の通信が可能なSwarmクラスタを構築しておく&lt;/h4&gt;

&lt;p&gt;Sparkのクラスタ同士は、一方通行な通信ではなく、割りと親密な双方向通信をするため、オーバーレイ・ネットワーク上に構築しないとうまく動作しない。&lt;/p&gt;

&lt;p&gt;オーバーレイ・ネットワーク構築するには、Consul, etcd, Zookeeperのようなキーストアを自身で導入する必要があるので、
&lt;a href=&#34;http://blog.namiking.net/post/2016/01/docker-swarm-digitalocean/&#34;&gt;DigitalOceanでマルチホストなDockerSwarmクラスタを構築&lt;/a&gt;を参考に、以下の様なSwarmクラスタを構築しておいた。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-spark/structure-swarm.svg&#34; alt=&#34;Swarm Structure&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;sparkクラスタをコンテナで構築する:27882acbebbf2525e531e2163851a874&#34;&gt;Sparkクラスタをコンテナで構築する&lt;/h3&gt;

&lt;p&gt;docker-composeを用いて、各ノードにSparkクラスタを構築する。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-spark/spark-containers.svg&#34; alt=&#34;Spark Containers&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;apache-sparkのdockerイメージ:27882acbebbf2525e531e2163851a874&#34;&gt;Apache SparkのDockerイメージ&lt;/h4&gt;

&lt;p&gt;Standaloneモードで動くシンプルなものが使いたかったので、SparkのDockerイメージは自前で用意したものを使った。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;namikingsoft/docker-spark&lt;br /&gt;
&lt;a href=&#34;https://github.com/namikingsoft/docker-spark&#34;&gt;https://github.com/namikingsoft/docker-spark&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;masterなら&lt;code&gt;bin/start-master.sh&lt;/code&gt;を実行し、workerなら&lt;code&gt;bin/start-slave.sh ${MASTER_HOSTNAME}:7077&lt;/code&gt;を実行するだけのシンプルなもの。&lt;/p&gt;

&lt;h4 id=&#34;docker-compose-yml:27882acbebbf2525e531e2163851a874&#34;&gt;docker-compose.yml&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-ruby&#34;&gt;master:
  image: namikingsoft/spark
  hostname: master
  container_name: master
  environment:
    - constraint:node==/node0/
  privileged: true
  command: master

worker:
  image: namikingsoft/spark
  environment:
    - MASTER_HOSTNAME=master
    - constraint:node!=/node0/
    - affinity:container!=/worker/
  privileged: true
  command: worker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;environment&lt;/code&gt;からの&lt;code&gt;constraint&lt;/code&gt;や&lt;code&gt;affinity&lt;/code&gt;の指定によってコンテナの配置をコントロールできる。コンテナの数をスケールするときもこのルールに沿って配置される。&lt;br /&gt;
&lt;a href=&#34;https://docs.docker.com/swarm/scheduler/filter/&#34;&gt;&amp;gt;&amp;gt; 指定方法の詳細&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;イメージ各コンテナはDockerのオーバーレイネットワークで繋がるので、
ポートも特に指定しなくてもよい。WebUIなどは後ほど、Socksプロキシ経由で確認する。&lt;/p&gt;

&lt;h4 id=&#34;swarm-masterの環境変数を設定:27882acbebbf2525e531e2163851a874&#34;&gt;Swarm Masterの環境変数を設定&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;eval &amp;quot;$(docker-machine env --swarm swarm-node0)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;docker-compose-up:27882acbebbf2525e531e2163851a874&#34;&gt;docker-compose up&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-compose --x-networking up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;--x-networking&lt;/code&gt;を引数で指定すれば、各コンテナがDockerのオーバーレイ・ネットワークで繋がる。&lt;code&gt;--x-network-driver overlay&lt;/code&gt;を省略しているが、デフォルトで指定されるみたい。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker-compose up&lt;/code&gt;直後の状態では、ワーカーコンテナが１つしか立ち上がらないので、以下の様な感じになっている。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-spark/spark-containers-progress.svg&#34; alt=&#34;Spark Containers Progress&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;ワーカーをスケールしてみる:27882acbebbf2525e531e2163851a874&#34;&gt;ワーカーをスケールしてみる&lt;/h4&gt;

&lt;p&gt;docker-composeのscaleコマンドでワーカーノードを指定数分スケールすることができる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-compose scale worker=2
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;コンテナ配置の確認:27882acbebbf2525e531e2163851a874&#34;&gt;コンテナ配置の確認&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker ps --format &amp;quot;{{.Names}}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;swarm-node0/master
swarm-node1/dockerspark_worker_1
swarm-node2/dockerspark_worker_2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;各ノードにコンテナが配置されたのがわかる。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ちなみに、MasterとWorkerを一緒のノードで動かしたら&lt;/strong&gt;&lt;br /&gt;
spark-shell起動時に&lt;code&gt;Cannot allocate memory&lt;/code&gt;的なエラーを吐いた。
チューニング次第かもだが、DigitalOcean 2GBだとリソース的には厳しそう。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;sparkシェルを動かしてみる:27882acbebbf2525e531e2163851a874&#34;&gt;Sparkシェルを動かしてみる&lt;/h3&gt;

&lt;p&gt;仮にSparkマスターコンテナをドライバーとして、動かしてみる。
(ワーカーでも動作可能)&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-exec -it master bash
spark-shell --master spark://master:7077
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-scala&#34;&gt;scala&amp;gt; sc.parallelize(1 to 10000).fold(0)(_+_)
res0: Int = 50005000
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;spark-uiを確認:27882acbebbf2525e531e2163851a874&#34;&gt;Spark UIを確認&lt;/h3&gt;

&lt;p&gt;SparkのWebUIから、ワーカーが接続されているか確認したいが、docker-compose.ymlではポートを開けていない。(本来閉じたネットワークで動かすので、ポートを開放するのはあまりよろしくない)&lt;/p&gt;

&lt;p&gt;なので、新たにSSHdコンテナを設置して、Socksプロキシ経由でWebUIを確認する。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-spark/spark-sshsocks.svg&#34; alt=&#34;Sparkマスター WebUI&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;sshdコンテナを追加:27882acbebbf2525e531e2163851a874&#34;&gt;SSHdコンテナを追加&lt;/h4&gt;

&lt;p&gt;先ほどのdocker-compose.ymlに追加する。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-ruby&#34;&gt;master:
  image: namikingsoft/spark
  hostname: master
  container_name: master
  environment:
    - constraint:node==/node0/
  privileged: true
  command: master

worker:
  image: namikingsoft/spark
  hostname: woker
  environment:
    - constraint:node!=/node0/
    - affinity:container!=/worker/
  privileged: true
  command: worker

# SSHdコンテナを追加
sshd:
  image: fedora/ssh
  ports:
    - &amp;quot;2222:22&amp;quot;
  environment:
    SSH_USERNAME: user
    SSH_USERPASS: something
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;以下のコマンドで、SSHdコンテナが起動する。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-compose --x-networking up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;ローカルpcでsocksプロキシを起動:27882acbebbf2525e531e2163851a874&#34;&gt;ローカルPCでSocksプロキシを起動&lt;/h4&gt;

&lt;p&gt;swarm-masterのIPを確認。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-machine ls
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Socksプロキシの起動。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ssh user@(swarm-masterのIP) -p2222 -D1080 -fN
Password: something
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Socksプロキシを止めるときは&lt;code&gt;Ctrl-c&lt;/code&gt;を押下。&lt;/p&gt;

&lt;h4 id=&#34;ローカルpcにてsocksプロキシを設定:27882acbebbf2525e531e2163851a874&#34;&gt;ローカルPCにてSocksプロキシを設定&lt;/h4&gt;

&lt;p&gt;ローカルPCのSocksプロキシ設定を&lt;code&gt;localhost:1080&lt;/code&gt;に設定する。
SSHのSocks周りについては以下のページが参考になった。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://blog.wktk.co.jp/ja/entry/2014/03/11/ssh-socks-proxy-mac-chrome&#34;&gt;ssh経由のSOCKSプロキシを通じてMac上のGoogle Chromeでブラウジング&lt;/a&gt;&lt;br /&gt;
&lt;a href=&#34;https://www.kmc.gr.jp/advent-calendar/ssh/2013/12/14/tsocks.html&#34;&gt;ssh -D と tsocks -  京大マイコンクラブ (KMC)&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&#34;ブラウザで確認:27882acbebbf2525e531e2163851a874&#34;&gt;ブラウザで確認&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;open http://(swarm-masterのIP):8080
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-spark/spark-master-ui.png&#34; alt=&#34;Sparkマスター WebUI&#34; /&gt;&lt;/p&gt;

&lt;p&gt;先ほど、スケールした2つのワーカーコンテナがマスターに接続されているのがわかる。
IPや名前解決はSSH接続先のものを参照してくれて便利。(OSX10.9+Chromeで確認)&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-spark/spark-worker-ui.png&#34; alt=&#34;Sparkワーカー WebUI&#34; /&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>DigitalOceanでマルチホストなDockerSwarmクラスタを構築するときのポイント</title>
      <link>http://blog.namiking.net/post/2016/01/docker-swarm-digitalocean/</link>
      <pubDate>Sun, 10 Jan 2016 18:00:23 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2016/01/docker-swarm-digitalocean/</guid>
      <description>

&lt;p&gt;Docker公式の&lt;a href=&#34;https://docs.docker.com/engine/userguide/networking/get-started-overlay/&#34;&gt;Get started with multi-host networking&lt;/a&gt;を参考に、DigitalOceanでSwarmクラスタを構築してみたが、いくつか工夫が必要なポイントがあったので、まとめておく。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-digitalocean/logos.png&#34; alt=&#34;Docker Swarm + DigitalOcean&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;事前準備:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;事前準備&lt;/h3&gt;

&lt;h4 id=&#34;必要なソフトウェアのインストール:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;必要なソフトウェアのインストール&lt;/h4&gt;

&lt;p&gt;作業で使うPC(またはホスト)に以下のDocker関連のソフトウェアをインストールしておく。Dockerのオーバーレイネットワーク機能を使うので、それを利用できるバージョンを入れる。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;docker (1.9以上)&lt;/li&gt;
&lt;li&gt;docker-compose (1.5以上)&lt;/li&gt;
&lt;li&gt;docker-machine&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;digitaloceanの登録とアクセストークンの取得:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;DigitalOceanの登録とアクセストークンの取得&lt;/h4&gt;

&lt;p&gt;登録後、管理画面からdocker-machineとの連携に必要なアクセストークンを発行できる。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.digitalocean.com/&#34;&gt;https://www.digitalocean.com/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;気をつけたいポイント４つ:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;気をつけたいポイント４つ&lt;/h3&gt;

&lt;h4 id=&#34;01-ホストosはkernel3-16以上のものを使う:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;01. ホストOSはKernel3.16以上のものを使う&lt;/h4&gt;

&lt;p&gt;例えば、ホストOSにUbuntu14.04を選んでいると、オーバーレイ・ネットワーク上にのコンテナを立ち上げる時にエラーが出る。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ docker-compose up -d -x-networking

Creating network &amp;quot;xxx&amp;quot; with driver &amp;quot;overlay&amp;quot;
Creating yyy
ERROR: Cannot start container (container_id):
  subnet sandbox join failed for &amp;quot;10.0.0.0/24&amp;quot;:
  vxlan interface creation failed for
  subnet &amp;quot;10.0.0.0/24&amp;quot;: failed in prefunc:
  failed to set namespace on link &amp;quot;vxlanf9ac2ad&amp;quot;: invalid argument
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/engine/userguide/networking/get-started-overlay/&#34;&gt;Get started with multi-host networking&lt;/a&gt;によると、「A host with a 3.16 kernel version or higher.」とのこと。DigitalOceanのUbuntu14.04はカーネルが古いようなので、14.10とか15.04以上を使う必要がある。&lt;/p&gt;

&lt;h4 id=&#34;02-cluster-advertiseはeth0にするか-privatenetworkを有効にする:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;02. cluster-advertiseはeth0にするか、PrivateNetworkを有効にする&lt;/h4&gt;

&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/engine/userguide/networking/get-started-overlay/&#34;&gt;Get started with multi-host networking&lt;/a&gt;のサンプルをそのまま使うと、&lt;code&gt;docker-machine create&lt;/code&gt;時に概ね次のようなエラーに遭遇する。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ docker-machine create \
  ...
  --engine-opt &amp;quot;cluster-advertise=eth1:2376&amp;quot; \
  ...

Error creating machine: Error running provisioning:
Unable to verify the Docker daemon is listening: Maximum number of retries (10) exceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;現状、DigitalOceanの&lt;code&gt;eth1&lt;/code&gt;はプライベートネットワークのインタフェースで、デフォルト設定では、プライベートネットワークにIPが割り当てられない。&lt;/p&gt;

&lt;p&gt;なので、公開ネットワークの&lt;code&gt;eth0&lt;/code&gt;を使うか、&lt;code&gt;docker-machine create&lt;/code&gt;時に、全てのノードに&lt;code&gt;--digitalocean-private-networking&lt;/code&gt;をつけて、プライベートネットワークを有効にする。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-machine create
  ...
  --engine-opt &amp;quot;cluster-advertise=eth0:2376&amp;quot; \
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;docker-machine create
  ...
  --engine-opt &amp;quot;cluster-advertise=eth1:2376&amp;quot; \
  --digitalocean-private-networking \
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;03-consulはswarmクラスタ上に置いて節約する:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;03. ConsulはSwarmクラスタ上に置いて節約する&lt;/h4&gt;

&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/engine/userguide/networking/get-started-overlay/&#34;&gt;公式ドキュメント&lt;/a&gt;のサンプルでは、Consulキーストアで専用のホストを用意しているが、安いとはいえ、DigitalOceanでConsulキーストア専用にもつのは守銭奴らしからぬので、Swarmクラスタのノードに含めてしまおう。&lt;/p&gt;

&lt;p&gt;Swarmクラスタのノードを作成するときに、ConsulのURLを指定する場所があるが、作成時にはConsulキーストアが存在しなくても、Swarmが後々定期的にキーストアを更新してくれるようなので、割りと気にせずにノード構築後にConsulを導入できる。&lt;/p&gt;

&lt;p&gt;ただ、サンプルのように、ConsulをDockerコンテナで導入してしまうと、&lt;code&gt;docker ps&lt;/code&gt;時に、いつもリストに出てきてしまうので、気になる方はDocker上ではなく、ホストに直接インストールしてしまったほうが良い。(バイナリファイル１つだし)&lt;/p&gt;

&lt;p&gt;あと、Consulらしく全ノードに設置して、ヘルスチェックなどが出来ると良さそう。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2016/01/docker-swarm-digitalocean/structure.svg&#34; alt=&#34;Docker Swarm + DigitalOcean&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;04-consulはプライベートネットワークに置く:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;04. Consulはプライベートネットワークに置く&lt;/h4&gt;

&lt;p&gt;&lt;a href=&#34;https://docs.docker.com/engine/userguide/networking/get-started-overlay/&#34;&gt;公式ドキュメント&lt;/a&gt;の感覚で、DigitalOceanにConsulキーストアを導入すると、グローバルにWebUIが公開されてしまい、精神的によろしくない。&lt;/p&gt;

&lt;p&gt;DigitalOceanには、一応プライベートネットワーク機能が用意されていて、管理画面やdocker-machineの引数から有効にできる。Consulはプライベートネットワーク側(eth1)のIPをバインドするとよい。&lt;/p&gt;

&lt;p&gt;プライベートネットワークにConsulを置くと、WebUIなどはグローバルIPから確認できなくなる。ブラウザからWebUIを確認したくなった場合は、SSHトンネルを利用して、8500ポートをフォワーディングしてやると楽。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;ssh root@(マシンIP) \
  -i ~/.docker/machine/machines/(マシンID)/id_rsa \
  -L8500:localhost:8500

# ブラウザから開く
open http://localhost:8500
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br /&gt;
プライベートネットワークといっても、
リージョンごとのネットワークのようなので、
あまり過信しないほうがよいみたい。&lt;/p&gt;

&lt;p&gt;使う前に知りたかったDigitalOceanまとめ
&lt;a href=&#34;http://pocketstudio.jp/log3/2015/04/13/digitalocean_introduction/&#34;&gt;http://pocketstudio.jp/log3/2015/04/13/digitalocean_introduction/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;具体的な実行手順:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;具体的な実行手順&lt;/h3&gt;

&lt;p&gt;自動化しやすいように、上のポイント４つを踏まえたシェルの実行手順を以下にまとめる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# マスターノード作成
docker-machine create \
  --driver digitalocean \
  --digitalocean-access-token ${DIGITALOCEAN_TOKEN} \
  --digitalocean-image &amp;quot;ubuntu-15-10-x64&amp;quot; \
  --digitalocean-region &amp;quot;sgp1&amp;quot; \
  --digitalocean-size &amp;quot;512mb&amp;quot; \
  --digitalocean-private-networking \
  --swarm --swarm-master \
  --swarm-discovery \
    &amp;quot;consul://localhost:8500&amp;quot; \
  --engine-opt \
    &amp;quot;cluster-store=consul://localhost:8500&amp;quot; \
  --engine-opt &amp;quot;cluster-advertise=eth1:2376&amp;quot; \
  swarm-node0

# マスターノードにConsulを設置
docker-machine ssh swarm-node0 &amp;quot;
  apt-get install -y at zip &amp;amp;&amp;amp;\
  cd /tmp &amp;amp;&amp;amp;\
  curl -LO https://releases.hashicorp.com/consul/0.6.1/consul_0.6.1_linux_amd64.zip &amp;amp;&amp;amp;\
  unzip consul_0.6.1_linux_amd64.zip &amp;amp;&amp;amp;\
  mv consul /usr/local/bin &amp;amp;&amp;amp;\
  curl -LO https://releases.hashicorp.com/consul/0.6.1/consul_0.6.1_web_ui.zip &amp;amp;&amp;amp;\
  unzip consul_0.6.1_web_ui.zip -d consul-webui &amp;amp;&amp;amp;\
  echo \&amp;quot;
    consul agent \
      -server -bootstrap-expect=1 \
      -node=consul00 \
      -data-dir=/tmp/consul \
      --ui-dir=/tmp/consul-webui \
      -bind=\$(
        ip addr show eth1 \
        | grep -o -e &#39;[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+&#39; \
        | head -n1
      ) \
    &amp;gt;&amp;gt; /var/log/consul.log
  \&amp;quot; | at now
&amp;quot;

# マスターのプライベートIPを取得
MASTER_PRIVATE_IP=$(
  docker-machine ssh swarm-node0 &amp;quot;
    ip addr show eth1 \
    | grep -o -e &#39;[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+&#39; \
    | head -n1
  &amp;quot;
)

# ノード作成
docker-machine create \
  --driver digitalocean \
  --digitalocean-access-token ${DIGITALOCEAN_TOKEN} \
  --digitalocean-image &amp;quot;ubuntu-15-10-x64&amp;quot; \
  --digitalocean-region &amp;quot;sgp1&amp;quot; \
  --digitalocean-size &amp;quot;512mb&amp;quot; \
  --digitalocean-private-networking \
  --swarm \
  --swarm-discovery \
    &amp;quot;consul://localhost:8500&amp;quot; \
  --engine-opt \
    &amp;quot;cluster-store=consul://localhost:8500&amp;quot; \
  --engine-opt &amp;quot;cluster-advertise=eth1:2376&amp;quot; \
  swarm-node1

# ノードにConsulを設置
docker-machine ssh swarm-node1 &amp;quot;
  apt-get install -y at zip &amp;amp;&amp;amp;\
  cd /tmp &amp;amp;&amp;amp;\
  curl -LO https://releases.hashicorp.com/consul/0.6.1/consul_0.6.1_linux_amd64.zip &amp;amp;&amp;amp;\
  unzip consul_0.6.1_linux_amd64.zip &amp;amp;&amp;amp;\
  mv consul /usr/local/bin &amp;amp;&amp;amp;\
  echo \&amp;quot;
    consul agent \
      -join=$MASTER_PRIVATE_IP \
      -node=consul01 \
      -data-dir=/tmp/consul \
      -bind=\$(
        ip addr show eth1 \
        | grep -o -e &#39;[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+&#39; \
        | head -n1
      ) \
    &amp;gt;&amp;gt; /var/log/consul.log
  \&amp;quot; | at now
&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;注記いくつか:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;注記いくつか&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;シェル中の&lt;code&gt;${DIGITALOCEAN_TOKEN}&lt;/code&gt;はDigitalOceanの管理画面から取得したアクセストークンに置き換える。&lt;/li&gt;
&lt;li&gt;Consul起動に&lt;code&gt;at now&lt;/code&gt;コマンドを使っているのは、&lt;code&gt;nohup&lt;/code&gt;や&lt;code&gt;&amp;amp;&lt;/code&gt;を使っても、Consulがフォアグラウンドで走ってしまい、バッチ処理が途中で止まってしまうため。&lt;code&gt;docker-machine ssh&lt;/code&gt;の仕様の問題？&lt;/li&gt;
&lt;li&gt;Consul実行時にbindに指定しているのは、自身のプライベートIP。&lt;/li&gt;
&lt;li&gt;途中で、マスターのプライベートIPを取得しているのは、各ノードに設置したConsulクライアントをサーバーにJOINさせるため。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;動作確認:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;動作確認&lt;/h3&gt;

&lt;h4 id=&#34;swarmクラスタ確認:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;Swarmクラスタ確認&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ eval $(docker-machine env --swarm swarm-node0)
$ docker info

Containers: 3
Images: 2
Role: primary
Strategy: spread
Filters: health, port, dependency, affinity, constraint
Nodes: 2
 swarm-node0: x.x.x.x:2376
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 513.4 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, provider=digitalocean, storagedriver=aufs
 swarm-node1: y.y.y.y:2376
  └ Status: Healthy
  └ Containers: 1
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 513.4 MiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.2.0-16-generic, operatingsystem=Ubuntu 15.10, provider=digitalocean, storagedriver=aufs
CPUs: 2
Total Memory: 1.003 GiB
Name: swarm-node0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ノードが２つあることが確認できる。&lt;/p&gt;

&lt;h4 id=&#34;オーバーレイ-ネットワークの確認:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;オーバーレイ・ネットワークの確認&lt;/h4&gt;

&lt;h5 id=&#34;docker-compose-yml:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;docker-compose.yml&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-ruby&#34;&gt;container1:
  image: busybox
  container_name: container1
  environment:
    - constraint:node==/node0/
  command: tail -f /dev/null

container2:
  image: busybox
  container_name: container2
  environment:
    - constraint:node==/node1/
  command: tail -f /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;ネットワークモードでコンテナを立ち上げる:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;ネットワークモードでコンテナを立ち上げる&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker-compose --x-networking up -d
$ docker ps --format &amp;quot;{{.Names}}&amp;quot;

swarm-node0/container1
swarm-node1/container2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;それぞれのノードにコンテナが作られた。&lt;/p&gt;

&lt;h5 id=&#34;疎通確認:fa799c0a70e6a2da19a2eb6d62e423f4&#34;&gt;疎通確認&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker exec -it container1 ping container2 -c4

PING container2 (10.0.0.3): 56 data bytes
64 bytes from 10.0.0.3: seq=0 ttl=64 time=1.470 ms
64 bytes from 10.0.0.3: seq=1 ttl=64 time=0.691 ms
64 bytes from 10.0.0.3: seq=2 ttl=64 time=0.620 ms
64 bytes from 10.0.0.3: seq=3 ttl=64 time=0.699 ms

--- container2 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.620/0.870/1.470 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker exec -it container2 ping container1 -c4

PING container1 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: seq=0 ttl=64 time=1.379 ms
64 bytes from 10.0.0.2: seq=1 ttl=64 time=0.854 ms
64 bytes from 10.0.0.2: seq=2 ttl=64 time=0.847 ms
64 bytes from 10.0.0.2: seq=3 ttl=64 time=0.861 ms

--- container1 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.847/0.985/1.379 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;オーバーレイ・ネットワークの疎通も問題なさそう。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Dockerイメージのビルド中にExitedしたコンテナに入る方法</title>
      <link>http://blog.namiking.net/post/2015/09/docker-exec-exited/</link>
      <pubDate>Tue, 29 Sep 2015 08:00:00 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2015/09/docker-exec-exited/</guid>
      <description>

&lt;p&gt;長めのansible-playbookをRUNしてる途中でエラーが出た時に役立ったので、まとめておく。&lt;/p&gt;

&lt;h3 id=&#34;どういう時に使う:f5fa53b0fb4decf6fe205e1fa30242da&#34;&gt;どういう時に使う？&lt;/h3&gt;

&lt;p&gt;例えば、&lt;code&gt;docker build&lt;/code&gt;途中によくわからない理由でエラー落ちした時、
直前状態のコンテナに入ってデバッグしたい事がある。&lt;/p&gt;

&lt;p&gt;ビルドに失敗した後、&lt;code&gt;docker ps -a&lt;/code&gt;すると、
&lt;code&gt;Exited&lt;/code&gt;したビルド作業用のコンテナが消されずに残っているので、
このコンテナの中にシェルで入れれば、エラーの詳細を調べられる。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;コンテナだけでなく、直近のイメージも残っているが、
Dockerコマンド単位でコミットされるため、
数珠つなぎのRUNとか、ansibleやchefなどのプロビジョニングツールを併用した時に、
大幅にロールバックしていることがある。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&#34;ビルド途中にエラーで落ちるdockerfileの例:f5fa53b0fb4decf6fe205e1fa30242da&#34;&gt;ビルド途中にエラーで落ちるDockerfileの例&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-docker&#34;&gt;FROM busybox

RUN touch /step1
RUN touch /step2 &amp;amp;&amp;amp; errcmd &amp;amp;&amp;amp; touch /step3
RUN touch /step4
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;errcmd&lt;/code&gt;という架空のコマンドで、わざとエラーを起こしてみる。&lt;/p&gt;

&lt;h4 id=&#34;docker-build-の途中で落ちる:f5fa53b0fb4decf6fe205e1fa30242da&#34;&gt;docker build の途中で落ちる&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker build . -t tset

Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM busybox
latest: Pulling from library/busybox
cfa753dfea5e: Pull complete
d7057cb02084: Pull complete
library/busybox:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:16a2a52884c2a9481ed267c2d46483eac7693b813a63132368ab098a71303f8a
Status: Downloaded newer image for busybox:latest
 ---&amp;gt; d7057cb02084
Step 1 : RUN touch /step1
 ---&amp;gt; Running in f1ca76c9072d
 ---&amp;gt; ef649ff08895
Removing intermediate container f1ca76c9072d
Step 2 : RUN touch /step2 &amp;amp;&amp;amp; errcmd &amp;amp;&amp;amp; touch /step3
 ---&amp;gt; Running in d117aa39bacd
/bin/sh: errcmd: not found
The command &#39;/bin/sh -c touch /step2 &amp;amp;&amp;amp; errcmd &amp;amp;&amp;amp; touch /step3&#39; returned a non-zero code: 127
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;途中にある&lt;code&gt;---&amp;gt; Running in d117aa39bacd&lt;/code&gt;の次で止まっている。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker ps -q --filter status=exited

d117aa39bacd
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;d117aa39bacd&lt;/code&gt;コンテナが消されずに残っている。&lt;br /&gt;
これが直近の作業コンテナで&lt;code&gt;touch /step2&lt;/code&gt;までのコマンドを実行されているはず。&lt;/p&gt;

&lt;h4 id=&#34;exitedなコンテナには入れない:f5fa53b0fb4decf6fe205e1fa30242da&#34;&gt;Exitedなコンテナには入れない？&lt;/h4&gt;

&lt;p&gt;生きているコンテナであれば、&lt;code&gt;docker exec&lt;/code&gt;+シェルでコンテナの中に入れるが、
Exitedしていると、以下の様なエラーが出る。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker exec -it d117aa39bacd sh

Error response from daemon: Container d117aa39bacd is not running
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;解決手順:f5fa53b0fb4decf6fe205e1fa30242da&#34;&gt;解決手順&lt;/h3&gt;

&lt;h4 id=&#34;１-exitedコンテナを一旦コミットして-イメージ化する:f5fa53b0fb4decf6fe205e1fa30242da&#34;&gt;１．Exitedコンテナを一旦コミットして、イメージ化する&lt;/h4&gt;

&lt;p&gt;死んでいるコンテナをコミットするという機会があまりなかったので、
ちょっと戸惑ったが、以下のコマンドでイメージ化できる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker commit -t exited d117aa39bacd
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;２-インタラクティブ-ttyモードで実行する:f5fa53b0fb4decf6fe205e1fa30242da&#34;&gt;２．インタラクティブ+TTYモードで実行する&lt;/h4&gt;

&lt;p&gt;そのまま&lt;code&gt;docker run&lt;/code&gt;しても速攻で死ぬので、&lt;code&gt;-it bash&lt;/code&gt;をつけて実行。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ docker run --rm -it exited sh
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# ls / | grep step
step1
step2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ちょっとまどろっこいが、一応エラーコマンド直前の状態のコンテナに入れた。
同じRUNコマンドの中でエラーが起きても、&lt;code&gt;touch /step2&lt;/code&gt;までは実行されているようだ。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>webpack-dev-serverで継続的なクライアントサイドテスト</title>
      <link>http://blog.namiking.net/post/2015/09/test-webpack-browser/</link>
      <pubDate>Fri, 11 Sep 2015 08:30:23 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2015/09/test-webpack-browser/</guid>
      <description>

&lt;p&gt;webpackの&lt;a href=&#34;http://webpack.github.io/docs/testing.html&#34;&gt;Testing&lt;/a&gt;を眺めてたら、
ブラウザ上でアプリを動作させながらMochaのSpecを走らせて、
クライアントサイドのテストをする、
みたいなことが手軽に出来そうだったので、やってみた。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;webpack-dev-server&lt;/strong&gt;を利用すれば、
ソースやテストを修正直後に自動リロードされるので、
継続的テストみたいな手法もとりやすい。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/test-webpack-browser/index.jpg&#34; alt=&#34;ScreenShot&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;動作サンプル:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;動作サンプル&lt;/h3&gt;

&lt;p&gt;ひと通り動くサンプルを作ったので、以下のGitHubに上げておきます。
nodeやnpmがインストールされていれば、動作すると思われます。&lt;/p&gt;

&lt;p&gt;&amp;ldquo;Greetingボタンを押したら、その下に挨拶が追加される&amp;rdquo;&lt;br /&gt;
みたいな動作のサンプルアプリとそのSpecをjQueryでシンプルに組んであります。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: sample-webpack-test&lt;br /&gt;
&lt;a href=&#34;https://github.com/namikingsoft/sample-webpack-test&#34;&gt;https://github.com/namikingsoft/sample-webpack-test&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;試しにブラウザで動かしてみる:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;試しにブラウザで動かしてみる&lt;/h3&gt;

&lt;p&gt;GitHub上の&lt;a href=&#34;https://github.com/namikingsoft/sample-webpack-test&#34;&gt;動作サンプル&lt;/a&gt;をcloneして、
webpack-dev-serverを起動したら、
お使いのブラウザから以下のURLにアクセスすることで動作を確認できる。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://localhost:8080/webpack-dev-server/&#34;&gt;http://localhost:8080/webpack-dev-server/&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&#34;コマンド例:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;コマンド例&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;git clone https://github.com/namikingsoft/sample-webpack-test
cd sample-webpack-test
npm install &amp;amp;&amp;amp; npm start
open http://localhost:8080/webpack-dev-server/
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;動作画面の例:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;動作画面の例&lt;/h4&gt;

&lt;p&gt;フロントエンドアプリとMochaのSpecを同時に動かしている図。
Seleniumみたいにギュンギュン動いて楽しい。
Mochaのテスト中にに割り込んでワザとテストを失敗させたりできる。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/test-webpack-browser/animation.gif&#34; alt=&#34;Animation&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Specは走らなくていいから、
アプリの動作確認だけしたいときは以下のURL&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:83d5efb00d572fc7f727b6dff95ecdf6:1&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:83d5efb00d572fc7f727b6dff95ecdf6:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;で可能。
&lt;a href=&#34;http://localhost:8081/webpack-dev-server/app&#34;&gt;http://localhost:8081/webpack-dev-server/app&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;ざっくり解説:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;ざっくり解説&lt;/h3&gt;

&lt;h4 id=&#34;ファイル構成:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;ファイル構成&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;sample-webpack-test
|-- build
|   |-- app.js # webpackが吐き出したアプリ本体のバンドルJS
|   |-- index.html # アプリとSpecを動作させるHTML
|   `-- spec.js # webpackが吐き出したSpecのバンドルJS
|-- spec # このディレクトリ以下に置いた*Spec.jsが実行される
|   `-- mainSpec.js
|-- src
|   `-- main.js # アプリ本体のソース
`-- webpack.config.js # webpackの設定ファイル
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;build&lt;/code&gt;ディレクトリ内の&lt;code&gt;app.js&lt;/code&gt;と&lt;code&gt;spec.js&lt;/code&gt;は、
webpack-dev-server内の動作であれば、
メモリ内のものが呼び出されるようなので、特に設置する必要はなさそう。&lt;/p&gt;

&lt;h4 id=&#34;複数のspecファイルに対応する:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;複数のSpecファイルに対応する&lt;/h4&gt;

&lt;p&gt;npmのglobモジュールを利用して、
複数のSpecファイルをエントリーポイントに含めることができる。
また、requireでglobを書きたい場合は、&lt;a href=&#34;https://github.com/seanchas116/glob-loader&#34;&gt;glob-loader&lt;/a&gt;を使えば、同じようなことができる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;// webpack.config.jsのmodule.exports内

entry: {
  app: &amp;quot;./src/main.js&amp;quot;,
  spec: glob.sync(&amp;quot;./spec/**/*Spec.js&amp;quot;),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;修正後に自動的にテストが走るようにする:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;修正後に自動的にテストが走るようにする&lt;/h4&gt;

&lt;p&gt;webpack-dev-serverには、ファイル修正を検知して再読み込みをしてくれる、
hotモードという機能が付いているので、webpack.config.jsでそれをを有効にする。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;// webpack.config.jsのmodule.exports内

devServer: {
  // Document Root
  contentBase: &amp;quot;./build&amp;quot;,
  // 動作ポート指定
  port: 8080,
  // hotモード有効化
  hot: true,
  // これがないと、ブラウザで
  inline: true,
},
plugins: [
  // hotモードに必要なプラグイン
  new webpack.HotModuleReplacementPlugin(),
],
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;specファイルをブラウザで動作するように変換する:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;Specファイルをブラウザで動作するように変換する&lt;/h4&gt;

&lt;p&gt;webpackの&lt;a href=&#34;http://webpack.github.io/docs/testing.html&#34;&gt;Testing&lt;/a&gt;のページにもあるが、
mocha-loaderを噛ますことで、ブラウザでMochaが利用可能になるJSが吐き出せるようになる。&lt;/p&gt;

&lt;p&gt;SpecファイルをES6で書きたければ、
babel-loaderを挟むと、一般ブラウザ用のJSに変換できる。
CoffeeScript+Chaiとかで書いても気持ちよさそう。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;// webpack.config.jsのmodule.exports内

module: {
  loaders: [
    // App用
    {
      test: /\.js$/,
      loaders: [&#39;babel&#39;],
      exclude: /(node_modules|bower_components)/,
    },
    // Spec用
    {
      test: /Spec\.js$/,
      loaders: [&#39;mocha&#39;, &#39;babel&#39;],
      exclude: /(node_modules|bower_components)/,
    },
  ],
},
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;appとspecを同時に走らせるhtmlを用意:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;AppとSpecを同時に走らせるHTMLを用意&lt;/h4&gt;

&lt;p&gt;アプリの動作画面にSpec結果のレイアウトを入れ込む。&lt;br /&gt;
&lt;code&gt;app.js&lt;/code&gt;で必要なUIを書き出すなどの初期処理を行ってから、&lt;code&gt;spec.js&lt;/code&gt;を走らせている。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;!-- build/index.html --&amp;gt;

&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ja&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;App&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
      /* Spec実行結果を表示するレイアウトCSS */
      .layout-spec {
        position: fixed;
        overflow: scroll;
        top: 0; bottom: 0; right: 0;
        width: 45%;
        background-color: #eee;
      }
      .layout-spec pre {
        background-color: #fff;
      }
    &amp;lt;/style&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!-- App実行 --&amp;gt;
    &amp;lt;script src=&amp;quot;app.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;div class=&amp;quot;layout-spec&amp;quot;&amp;gt;
      &amp;lt;!-- Spec実行 --&amp;gt;
      &amp;lt;script src=&amp;quot;spec.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;あとがき:83d5efb00d572fc7f727b6dff95ecdf6&#34;&gt;あとがき&lt;/h3&gt;

&lt;p&gt;ChromeとかFirefoxなどの複数のブラウザ上で手軽にフロント動作仕様を自動チェックしたい、
みたいなときの方法論の一つとして紹介してみた。&lt;/p&gt;

&lt;p&gt;ただ、CLI動作とかCIと連携する場合は、
Kermaのようなテストランナーを使ったほうが良いかも。
webpackはKermaとも簡単に連動できるようなので、
その辺も後々まとめておきたい。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34;&gt;

&lt;hr /&gt;

&lt;ol&gt;
&lt;li id=&#34;fn:83d5efb00d572fc7f727b6dff95ecdf6:1&#34;&gt;URLの&lt;code&gt;app&lt;/code&gt;の部分はバンドルJS出力先のパスを指定すれば、webpack-dev-serverが出力用HTMLを自動生成してくれるみたい。(&lt;code&gt;.js&lt;/code&gt;は省略してます)
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:83d5efb00d572fc7f727b6dff95ecdf6:1&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>オープンソースのかんばん式管理ツールを３つほど試してみた</title>
      <link>http://blog.namiking.net/post/2015/09/kanban-board/</link>
      <pubDate>Fri, 04 Sep 2015 08:30:23 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2015/09/kanban-board/</guid>
      <description>

&lt;p&gt;コンプライアンス&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:c734c5cb2dac2213cc062a5b86e79214:1&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:c734c5cb2dac2213cc062a5b86e79214:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;を重視する現場で、
アジャイルなプロジェクト管理ツールが現場で必要になったときに、
外部Webサービスな&lt;code&gt;Trello&lt;/code&gt;や&lt;code&gt;Pivotal Tracker&lt;/code&gt;を導入しづらいことがある。&lt;/p&gt;

&lt;p&gt;そこで、OSSのかんばん式管理ツールをいくつか探して、&lt;br /&gt;
色々試して感じたことをまとめておきます。&lt;/p&gt;

&lt;h3 id=&#34;今回試した-かんばん式-プロジェクト管理ツール:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;今回試した &amp;lsquo;かんばん式&amp;rsquo; プロジェクト管理ツール&lt;/h3&gt;

&lt;p&gt;以下に挙げる以外にも、OSSのかんばん式管理ツールは数多く存在するが、
機能面・とっつきやすさ・導入の容易さなどに基いて、３つだけピックアップさせていただいた。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;TAIGA&lt;br /&gt;
&lt;a href=&#34;https://taiga.io/&#34;&gt;https://taiga.io/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Wekan (旧LibreBoard)&lt;br /&gt;
&lt;a href=&#34;http://newui.libreboard.com&#34;&gt;http://newui.libreboard.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Restyaboard&lt;br /&gt;
&lt;a href=&#34;http://restya.com/board/&#34;&gt;http://restya.com/board/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;個別のサーバーに実行環境を構築したい場合:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;個別のサーバーに実行環境を構築したい場合&lt;/h4&gt;

&lt;p&gt;各々デモ用のURLが用意されているが、OSSなので個別のサーバー環境や&lt;code&gt;Docker&lt;/code&gt;で試したい場合は、
当ブログに構築手順の記事があるので、参照されたい。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.namiking.net/post/2015/09/docker-taiga/&#34;&gt;TAIGA on Dockerで本格アジャイル開発管理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.namiking.net/post/2015/09/docker-wekan/&#34;&gt;Wekan on Dockerでお手軽かんばん式プロジェクト管理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.namiking.net/post/2015/09/docker-restyaboard/&#34;&gt;Restyaboard on Dockerで多機能かんばん式プロジェクト管理&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;taiga-本格的なアジャイル開発管理:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;TAIGA - 本格的なアジャイル開発管理&lt;/h2&gt;

&lt;p&gt;本格的なアジャイル開発を支援するプロジェクト管理ツール。
他のかんばん管理ツールに比べて、デザイン洗礼されていて、使っていて気持ちいい。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/taiga01.jpg&#34; alt=&#34;TAIGA ScreenShot&#34; /&gt;
&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/taiga03.jpg&#34; alt=&#34;TAIGA ScreenShot&#34; /&gt;
&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/taiga04.jpg&#34; alt=&#34;TAIGA ScreenShot&#34; /&gt;
&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/taiga02.jpg&#34; alt=&#34;TAIGA ScreenShot&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;ここが良かった:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;ここが良かった&lt;/h3&gt;

&lt;h4 id=&#34;アジャイル開発管理に特化したテンプレート:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;アジャイル開発管理に特化したテンプレート&lt;/h4&gt;

&lt;p&gt;全体を見渡せるかんばんボードだけではなく、
期間区切りのスプリントを作成して、バックログからストーリーカードを割り振り、
各タスクの進捗状況を確認できる&lt;code&gt;タスクボード&lt;/code&gt;機能。&lt;/p&gt;

&lt;p&gt;プロジェクト全体の遅延動向をより正確に把握するために、
ストーリーカード毎に設定するポイント(難度重み付け)&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:c734c5cb2dac2213cc062a5b86e79214:2&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:c734c5cb2dac2213cc062a5b86e79214:2&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;と、
ストーリーカードの進捗に基いて描写される&lt;code&gt;バーンダウンチャート&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;感覚的には以前使っていた&lt;code&gt;Redmine&lt;/code&gt;のアジャイル拡張の&lt;code&gt;Alminium&lt;/code&gt;に似ている。&lt;br /&gt;
(アジャイルの管理ツールって概ねこんな感じなのかな)&lt;/p&gt;

&lt;h4 id=&#34;ユーザーごとに権限グループ設定をすることができる:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;ユーザーごとに権限グループ設定をすることができる&lt;/h4&gt;

&lt;p&gt;スプリントを追加できるのはオーナーのみとか、
エンジニア/デザイナーはタスクのみを登録できる。とか、
グループによって、事細かに権限を設定することができる。&lt;/p&gt;

&lt;p&gt;デフォルトの権限グループとしては、&lt;code&gt;UX&lt;/code&gt;,&lt;code&gt;DESIGN&lt;/code&gt;,&lt;code&gt;FRONT&lt;/code&gt;,&lt;code&gt;BACK&lt;/code&gt;,&lt;code&gt;PRODUCT OWNER&lt;/code&gt;などがあるが、こちらも設定で変更できるっぽい。&lt;/p&gt;

&lt;h3 id=&#34;微妙なところ:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;微妙なところ&lt;/h3&gt;

&lt;h4 id=&#34;軽めに使うには複雑すぎるか:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;軽めに使うには複雑すぎるか&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;Redmine&lt;/code&gt;拡張の&lt;code&gt;Alminium&lt;/code&gt;を使った時も思ったことだが、
メンバーがアジャイル開発手法に慣れていない場合、
バックログ &amp;gt; スプリント &amp;gt; タスクボードの階層構造に少し戸惑うかもしれない。&lt;/p&gt;

&lt;p&gt;一応、設定からアジャイル開発系のモジュールを無効にできたりするが、
かんばんボードの列名を別の設定画面で行う必要があったりと、少し面倒くさい。
シンプルなTODO表として使うなら、&lt;code&gt;Trello&lt;/code&gt;,&lt;code&gt;Wekan&lt;/code&gt;とかの方がとっつきやすいかもしれない。&lt;/p&gt;

&lt;h2 id=&#34;wekan-お手軽かんばん式プロジェクト管理:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;Wekan - お手軽かんばん式プロジェクト管理&lt;/h2&gt;

&lt;p&gt;ちょっと前は、&lt;code&gt;Libreboard&lt;/code&gt;と呼ばれていて、日本語の紹介サイトも豊富に存在した。
その頃はボード画面から設定画面から、何から何までTrelloにそっくりだったが、
&lt;code&gt;Wekan&lt;/code&gt;に名を変えてから、少しUIにオリジナリティが増したように感じる。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/wekan01.jpg&#34; alt=&#34;Wekan ScreenShot&#34; /&gt;
&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/wekan02.jpg&#34; alt=&#34;Wekan ScreenShot&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;ここが良かった-1:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;ここが良かった&lt;/h3&gt;

&lt;h4 id=&#34;シンプル-イズ-ベスト:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;シンプル・イズ・ベスト&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;Trello&lt;/code&gt;の基本機能から、更に必要最低限のものに絞ってるので、非常にとっつきやすい。
システム開発に限らず、シンプルなTODO管理にも使えそう。
ひょっとすると、ITアレルギー持ちの老若男女にも使っていただけるかもしれない。&lt;/p&gt;

&lt;p&gt;こういうツールの真髄は、&lt;strong&gt;今どんな課題があるか。どういう状況か。誰が着手してるか。&lt;/strong&gt;を素早く把握することにあるので、
コミュニケーションツールとして割り切るならば、十分だとは思う。&lt;/p&gt;

&lt;h4 id=&#34;日本語uiに対応:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;日本語UIに対応&lt;/h4&gt;

&lt;p&gt;上のログイン画面のスクリーンショットのとおり、デフォルトで日本語に対応している。
ログイン後の画面も右上メニューの&lt;code&gt;Change Languege&lt;/code&gt;から日本語にできる。
英語アレルギー持ちの老若男女にも使っていただけるだろう。&lt;/p&gt;

&lt;h3 id=&#34;微妙なところ-1:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;微妙なところ&lt;/h3&gt;

&lt;h4 id=&#34;アジャイル開発管理に使うにはシンプルすぎるか:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;アジャイル開発管理に使うにはシンプルすぎるか&lt;/h4&gt;

&lt;p&gt;本格的にアジャイル開発に使うには、複数ボードにまたいだりと、
運用テクニックが必要になりそう。
あと、&lt;code&gt;Trello&lt;/code&gt;や&lt;code&gt;Restyaboard&lt;/code&gt;にあるようなチェックリスト機能がないので、
各ストーリーカードの進行状況(タスク状況)をボード上から確認するのは難しい。&lt;/p&gt;

&lt;h2 id=&#34;restyaboard-多機能かんばん式プロジェクト管理:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;Restyaboard - 多機能かんばん式プロジェクト管理&lt;/h2&gt;

&lt;p&gt;基本的には&lt;code&gt;Trello&lt;/code&gt;クローンなのだが、
シンプル化の&lt;code&gt;Wekan&lt;/code&gt;と違い、多機能化を目指している。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/restya01.jpg&#34; alt=&#34;Restyaboard ScreenShot&#34; /&gt;
&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/restya02.jpg&#34; alt=&#34;Restyaboard ScreenShot&#34; /&gt;
&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/kanban-board/restya03.jpg&#34; alt=&#34;Restyaboard ScreenShot&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;ここが良かった-2:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;ここが良かった&lt;/h3&gt;

&lt;h4 id=&#34;他ツールにない前衛的な機能やtrello有料機能が使えたりする:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;他ツールにない前衛的な機能やTrello有料機能が使えたりする&lt;/h4&gt;

&lt;p&gt;例えば、ボードのオーナー以外がストーリーカードを追加することができないが、
ボード一覧による進捗状況確認、カレンダー表示、デスクトップ通知&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:c734c5cb2dac2213cc062a5b86e79214:3&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:c734c5cb2dac2213cc062a5b86e79214:3&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;、ファビコンに新着通知数表示など、他ツールにない前衛的な機能/UIが豊富。&lt;/p&gt;

&lt;p&gt;チェックリスト、Due Date、ボードの背景に写真が使える、などを見ると、
機能面では&lt;code&gt;Wekan&lt;/code&gt;より、こちらのほうが&lt;code&gt;Trello&lt;/code&gt;を意識しているかも。
また、&lt;code&gt;Trello&lt;/code&gt;からデータをインポートする機能が付いているので、
もし無償版では満足できなくなったら、試してみるのもいいかもしれない。&lt;/p&gt;

&lt;h3 id=&#34;微妙なところ-2:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;微妙なところ&lt;/h3&gt;

&lt;h4 id=&#34;ユーザーインタフェースに少し難あり:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;ユーザーインタフェースに少し難あり&lt;/h4&gt;

&lt;p&gt;定期的に再読み込みのロードバーが表示されたり、
デスクトップ通知が絶え間なく表示されたり、
ストーリーカードの詳細ポップアップがグィングイン動いたり、
若干UI効果がうるさいかも。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/RestyaPlatform/board&#34;&gt;GitHub&lt;/a&gt;を見るに、機能面含めて現在開発中のアルファ版という位置づけと思われるので、
今後のバージョンアップに期待したいところ。&lt;/p&gt;

&lt;h2 id=&#34;あとがき:c734c5cb2dac2213cc062a5b86e79214&#34;&gt;あとがき&lt;/h2&gt;

&lt;p&gt;１年ほど前にOSSのかんばんツールを探してた時は、
&lt;code&gt;Redmine&lt;/code&gt;の&lt;code&gt;Alminium&lt;/code&gt;ぐらいしか選択肢がなかった気がしたが、
ものすごいスピードで新サービスが生まれるWeb系の恐ろしさを感じる。&lt;/p&gt;

&lt;p&gt;OSS作者様に感謝すると共に、自分もなにか残してえなあ。と色々弄ってて思った。&lt;/p&gt;
&lt;div class=&#34;footnotes&#34;&gt;

&lt;hr /&gt;

&lt;ol&gt;
&lt;li id=&#34;fn:c734c5cb2dac2213cc062a5b86e79214:1&#34;&gt;いまいちピンとこない用語だが、ここでは情報漏洩を気にする企業という意味。内部サーバーだからバッチグーという事ではなく、いかにお固い上席を説得しやすいか。
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:c734c5cb2dac2213cc062a5b86e79214:1&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li id=&#34;fn:c734c5cb2dac2213cc062a5b86e79214:2&#34;&gt;フィボナッチ数列っぽいものから重みを選択できる。数列は設定から変更可能。
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:c734c5cb2dac2213cc062a5b86e79214:2&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li id=&#34;fn:c734c5cb2dac2213cc062a5b86e79214:3&#34;&gt;Trelloにもデスクトップ通知機能は付いているみたい。&lt;a href=&#34;http://n2p.co.jp/blog/planning/trellotips/&#34;&gt;[参考]&lt;/a&gt;
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:c734c5cb2dac2213cc062a5b86e79214:3&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>docker-composeのインストールとバージョン差異エラー回避方法</title>
      <link>http://blog.namiking.net/post/2015/09/install-docker-compose/</link>
      <pubDate>Wed, 02 Sep 2015 08:30:23 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2015/09/install-docker-compose/</guid>
      <description>

&lt;p&gt;&lt;code&gt;docker-compose&lt;/code&gt;はDockerコンテナの構成管理ツール。&lt;br /&gt;
昔は&lt;code&gt;fig&lt;/code&gt;という名前のツールだったが、Dockerと統合して名前を変更したとのこと。&lt;/p&gt;

&lt;p&gt;データボリューム, DB, バックエンド、フロントエンドなど、
サービスの稼働に複数コンテナが必要な場合のビルドや立ち上げが非常に楽になる。&lt;/p&gt;

&lt;p&gt;インストール自体はシンプルだが、
Dockerのバージョンによっては実行時にエラーが出てしまうようなので、
エラー回避方法もまとめておく。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Overview of Docker Compose&lt;br /&gt;
&lt;a href=&#34;https://docs.docker.com/compose/&#34;&gt;https://docs.docker.com/compose/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;インストール手順:fce77b3f97c5854a016ed64fe44c5a99&#34;&gt;インストール手順&lt;/h3&gt;

&lt;p&gt;公式ドキュメントに対応OSごとのインストール方法がまとめてあった。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Docker Compose: Supported installation&lt;br /&gt;
&lt;a href=&#34;https://docs.docker.com/installation/&#34;&gt;https://docs.docker.com/installation/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OS共通の方法で一番簡単なのが、&lt;code&gt;pip&lt;/code&gt;でインストールする方法。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pip install -U docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;pip&lt;/code&gt;がない場合は、&lt;code&gt;easy_install&lt;/code&gt;でインストールできる。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;easy_install pip
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;バージョン差異エラー回避方法:fce77b3f97c5854a016ed64fe44c5a99&#34;&gt;バージョン差異エラー回避方法&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;docker-compose up&lt;/code&gt;などの実行時に、以下の様なエラーが表示されることがある。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;client and server don&#39;t have same version (client : 1.19, server: 1.18)
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;原因:fce77b3f97c5854a016ed64fe44c5a99&#34;&gt;原因&lt;/h4&gt;

&lt;p&gt;このエラーは、インストールされている&lt;code&gt;Dockerサーバー&lt;/code&gt;のバージョンと、
&lt;code&gt;docker-composeクライアント(API)&lt;/code&gt;のバージョンに互換性がない時に表示されるらしい。&lt;/p&gt;

&lt;h4 id=&#34;回避方法:fce77b3f97c5854a016ed64fe44c5a99&#34;&gt;回避方法&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;COMPOSE_API_VERSION&lt;/code&gt;環境変数にサーバーのバージョンを設定すれば、
&lt;code&gt;docker-compose&lt;/code&gt;の方で、サーバーのバージョンに合わせて通信してくれるとのこと。
&lt;code&gt;auto&lt;/code&gt;を設定すれば、自動で調整してくれるみたい。便利。&lt;/p&gt;

&lt;p&gt;以下のコマンドを入力するか、&lt;code&gt;/etc/profile&lt;/code&gt;とか&lt;code&gt;~/.bash_profile&lt;/code&gt;あたりに追記しておく。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export COMPOSE_API_VERSION=auto
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;/etc/environment&lt;/code&gt;に追記する場合は以下のようにする。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;COMPOSE_API_VERSION=auto
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;docker-composeの利用例:fce77b3f97c5854a016ed64fe44c5a99&#34;&gt;docker-composeの利用例&lt;/h3&gt;

&lt;p&gt;当ブログの以下の記事で、具体的な使用例を紹介しているので、参照されたい。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.namiking.net/post/2015/09/docker-taiga/&#34;&gt;TAIGA on Dockerで本格アジャイル開発管理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.namiking.net/post/2015/09/docker-wekan/&#34;&gt;Wekan on Dockerでお手軽かんばん式プロジェクト管理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blog.namiking.net/post/2015/09/docker-restyaboard/&#34;&gt;Restyaboard on Dockerで多機能かんばん式プロジェクト管理&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>TAIGA on Dockerで本格アジャイル開発管理</title>
      <link>http://blog.namiking.net/post/2015/09/docker-taiga/</link>
      <pubDate>Tue, 01 Sep 2015 10:30:23 +0900</pubDate>
      
      <guid>http://blog.namiking.net/post/2015/09/docker-taiga/</guid>
      <description>

&lt;p&gt;&lt;code&gt;TAIGA&lt;/code&gt;は、やたらデザインがきれいなアジャイルプロジェクト管理ツール。&lt;br /&gt;
&lt;code&gt;Trello&lt;/code&gt;クローンという感じはなく、&lt;code&gt;Redmine&lt;/code&gt;拡張の&lt;code&gt;Alminium&lt;/code&gt;に似ている。&lt;/p&gt;

&lt;p&gt;ストーリーカードの重み付けやタスクボード、バーンダウンチャートなども備えており、
しっかりとアジャイル開発をやりたい場合はおすすめ。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/docker-taiga/taiga01.png&#34; alt=&#34;TAIGA ScreenShot&#34; /&gt;
&lt;img src=&#34;http://blog.namiking.net/images/post/2015/09/docker-taiga/taiga02.png&#34; alt=&#34;TAIGA ScreenShot&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Taiga.Io | Agile, Open Source, Free Project Management System&lt;br /&gt;
&lt;a href=&#34;https://taiga.io/&#34;&gt;https://taiga.io/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;dockerを利用した導入手順:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;Dockerを利用した導入手順&lt;/h3&gt;

&lt;p&gt;以下の&lt;code&gt;docker-compose.yml&lt;/code&gt;とイメージを参考/利用させていただきました。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub: htdvisser/taiga-docker&lt;br /&gt;
&lt;a href=&#34;https://github.com/htdvisser/taiga-docker&#34;&gt;https://github.com/htdvisser/taiga-docker&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&#34;00-事前準備:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;00. 事前準備&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;Docker&lt;/code&gt;と&lt;code&gt;docker-compose&lt;/code&gt;&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:bd8b9a47038f3244ff294ee6ef501def:1&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:bd8b9a47038f3244ff294ee6ef501def:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;をインストールしておく。&lt;/p&gt;

&lt;h4 id=&#34;01-docker-compose-yml-設置:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;01. docker-compose.yml 設置&lt;/h4&gt;

&lt;p&gt;以下の内容の&lt;code&gt;docker-compose.yml&lt;/code&gt;を設置する。&lt;br /&gt;
&lt;code&gt;hostname&lt;/code&gt;, &lt;code&gt;EMAIL_*&lt;/code&gt;辺りは各々の環境に合わせて書き換える。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;data:
  image: tianon/true
  volumes:
    - /var/lib/postgresql/data
    - /usr/local/taiga/media
    - /usr/local/taiga/static
    - /usr/local/taiga/logs

db:
  image: postgres
  environment:
    POSTGRES_USER: taiga
    POSTGRES_PASSWORD: password
  volumes_from:
    - data
  restart: always

taigaback:
  image: htdvisser/taiga-back:stable
  hostname: example.com
  environment:
    SECRET_KEY: examplesecretkey
    EMAIL_USE_TLS: True
    EMAIL_HOST: smtp.gmail.com
    EMAIL_PORT: 587
    EMAIL_HOST_USER: example@gmail.com
    EMAIL_HOST_PASSWORD: password
  links:
    - db:postgres
  volumes_from:
    - data
  restart: always

taigafront:
  image: htdvisser/taiga-front-dist:stable
  hostname: example.com
  links:
    - taigaback
  volumes_from:
    - data
  ports:
    - 0.0.0.0:80:80
  restart: always
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;02-docker起動:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;02. Docker起動&lt;/h4&gt;

&lt;p&gt;先ほどの&lt;code&gt;docker-compose.yml&lt;/code&gt;があるディレクトリ内で、以下のコマンドを入力。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;03-足りないdbレコードを挿入:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;03. 足りないDBレコードを挿入&lt;/h4&gt;

&lt;p&gt;Dockerコンテナ起動時に、初期DBデータの挿入が行われるが、
おそらく&lt;code&gt;TAIGA&lt;/code&gt;のバージョンアップで、必要なDBデータが増えたっぽい。&lt;/p&gt;

&lt;p&gt;動作確認時にプロジェクトが作成できない&lt;sup class=&#34;footnote-ref&#34; id=&#34;fnref:bd8b9a47038f3244ff294ee6ef501def:2&#34;&gt;&lt;a rel=&#34;footnote&#34; href=&#34;#fn:bd8b9a47038f3244ff294ee6ef501def:2&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;、などの不具合を起こしていたので、
取り急ぎ、こちらのコマンドで入れておく。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;docker exec -it (taigabackコンテナID) \
  python /usr/local/taiga/taiga-back/manage.py \
    loaddata initial_project_templates

docker exec -it (taigabackコンテナID) \
  python /usr/local/taiga/taiga-back/manage.py \
    loaddata initial_project_templates initial_user

docker exec -it (taigabackコンテナID) \
  python /usr/local/taiga/taiga-back/manage.py \
    loaddata initial_project_templates initial_role
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;04-動作確認:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;04. 動作確認&lt;/h4&gt;

&lt;h5 id=&#34;通常画面:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;通常画面&lt;/h5&gt;

&lt;p&gt;画面内の「create your free account here」からユーザー登録を行う。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://(SERVER_IP)/
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;管理画面:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;管理画面&lt;/h5&gt;

&lt;p&gt;DBレコード操作などを行う画面？&lt;br /&gt;
通常運用であれば、使わなくてもよい画面と思われる。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://(SERVER_IP)/admin/
Username: admin
Password: 123123
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;導入でつまづいた点:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;導入でつまづいた点&lt;/h3&gt;

&lt;h5 id=&#34;新規ユーザー登録ボタン押下後に-次画面に遷移しない:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;新規ユーザー登録ボタン押下後に、次画面に遷移しない。&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;SMTP設定が正しくない。&lt;/li&gt;
&lt;li&gt;Docker起動直後に登録した。(初期DBデータ登録が終わっていない？)&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&#34;プロジェクトの作成途中で次画面に遷移しなくなる:bd8b9a47038f3244ff294ee6ef501def&#34;&gt;プロジェクトの作成途中で次画面に遷移しなくなる。&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;追加DBデータを挿入していない。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34;&gt;

&lt;hr /&gt;

&lt;ol&gt;
&lt;li id=&#34;fn:bd8b9a47038f3244ff294ee6ef501def:1&#34;&gt;参考： &lt;a href=&#34;http://blog.namiking.net/post/2015/09/install-docker-compose/&#34;&gt;docker-composeのインストールとバージョン差異エラー回避方法&lt;/a&gt;
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:bd8b9a47038f3244ff294ee6ef501def:1&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li id=&#34;fn:bd8b9a47038f3244ff294ee6ef501def:2&#34;&gt;参考： &lt;a href=&#34;https://github.com/taigaio/taiga-scripts/issues/23&#34;&gt;https://github.com/taigaio/taiga-scripts/issues/23&lt;/a&gt;
 &lt;a class=&#34;footnote-return&#34; href=&#34;#fnref:bd8b9a47038f3244ff294ee6ef501def:2&#34;&gt;&lt;sup&gt;[return]&lt;/sup&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
  </channel>
</rss>