kakts-log

programming について調べたことを整理していきます

v8 "Launching ignition and Turbofan" 和訳

先日リリースされたv8 5.9において、新たなコード実行パイプラインが導入され、大きくパフォーマンスが向上したとのことです。
今後リリースされるNode.js ver8においてもv8 5.9が使われるため、概要を整理するためにv8本家のブログを和訳してまとめてみました。
v8project.blogspot.jp

Launching ignition and Turbofan

v8 5.9から新しいjavascript実行パイプラインが導入されました。 Chromeでは、バージョン59からこれが導入されます。
新しい実行パイプラインでは、従来と比べてパフォーマンスが大きく向上し、javascript アプリケーションにおいてメモリの使用量に関してもかなり改善されます。

新しいパイプラインは、Ignitionというv8のインタプリタによって動作し、さらにTurboFanとよばれるv8の新しいコンパイラによって最適化されます。

v8 5.9において初めて、IgnitionとTurboFanが導入されました。
これは、2010年からv8によって使われていたFull-codegenとCrankshaftが最新のjavascriptの機能と最適化にキャッチアップできなくなってきたことによります。
v8チームは、Full-codegenとCrankShaftを近々完全に除外する予定で、これはv8が今後シンプルでさらにメンテナブルなアーキテクチャを採用することを意味します。

A Long Journey

IgnitionとTurboFanを組み合わせたパイプラインは3年半ほど開発が続いていました。
この開発により、v8チームは実際に稼働しているjavascriptのパフォーマンス計測の知見を集め、Full-codegenとCrankshaftによるパイプラインの欠点を注意深く考慮しました。
これによりv8チームはjavascript全体の最適化を続けるための基礎を固めることができました。

TurboFanについて

TurboFan プロジェクトは、Crankshaftによる欠点に対処するために2013年後半にスタートしました。
Crankshaftはjavascriptのサブセットのみ最適化することができます。
例えば、Crankshaftは、try-catch-finallyキーワードを利用した例外ハンドリングを行うjavascriptコードを最適化するようには設計されていませんでした。
Crankshaftにおいては、javascriptの新たな機能に対応することが難しく、これらの新機能はほぼ常に9つものプラットフォームのためにアーキテクチャに依存したコードを書く必要がありました。
さらに、Crankshaftのアーキテクチャは、最適化されたマシンコードを生成することのみに制限されています。
これにより、チップアーキテクチャ毎に1万行を超えるコードをメンテナンスするv8チームの要求にも関わらず、パフォーマンスを最大限に発揮できませんでした。

TurboFanはES5当時のすべてのjavascriptの機能に対して最適化するだけでなく、ES2015やそれ以降の新たな機能に対しても最適化を行えるように設計されました。
TurboFanは高レベル・低レベルの間をうまく分割することができるようなレイヤー構造のコンパイラデザインを導入し、アーキテクチャ依存のコードを変更することなく新たな機能を追加することが容易です。
さらに、暗黙的な命令コンパイルのフェーズを導入し、各プラットフォームに対するアーキテクチャ依存のコードを極力減らすことが可能です。
この新たなフェーズの導入により、アーキテクチャ依存のコードは1度きりだけ書いて、後に変更する必要がほとんどありません。
このフェーズや他の機能に対する決定によって、v8がサポートするすべてのアーキテクチャにおいて、メンテナンス性が向上し、コンパイルの最適化コードにかかるコストが軽減しました。

Ignitionについて

v8のIgnitionインタプリタにおける開発当初の動機としては、モバイル端末におけるメモリ消費の削減が挙げられます。
Ignition導入前は、v8のFull-codegenによって生成されたコードは、Chromejavascriptヒープメモリの1/3ものメモリを占めていました。
これにより、実際のwebアプリケーションのデータのための容量が小さくなってしまっていました。

Chrome M53でIgnitionが導入された当時、制限されたRAM容量をもつAndroidバイスにおいて、最適化されていないjavascriptコードのためのメモリ領域が減少していることが ARM64ベースのモバイル端末において確認されました。

後に、v8チームはIgnitionのバイトコードが、Crankshaftがソースコードからリコンパイルするよりも、最適化されたマシンコードを生み出すために活用できるという利点を活かしました。
Ignitionのバイトコードはよりクリーンで、よりエラーを起こしにくいベースライン実行モデルをv8にもたらし、v8のAdaptive Optimization*1の重要な特徴である、Deoptimizationメカニズムをシンプルな構造にしました。
最終的に、バイトコードの生成がFull-codegenコンパイルコードの生成よりも早くなり、Ignitionの実行によってスクリプトの高速化をできるようになりました。

IgnitionとTurboFanのデザインを密接に組み合わせることで、アーキテクチャ全体の利点があります。
例えば、v8チームは、Ignitionのハイパフォーマンスなバイトコードハンドラを手で書いて組み合わせるよりも、
TurboFanの中間表現をバイトコードハンドラの機能を表現するために用い、TurboFanに最適化と多くのプラットフォームのためのコンパイル済みコードを生成させる方針をとりました。
これにより、Igtionプラットフォームがすべてのチップアーキテクチャにおいて改善され、メンテナンスの負荷がなくなりました。

Running the Numbers

ここでは新しいパイプラインによるパフォーマンスとメモリ消費を見ていきましょう。

v8チームは定期的にTelemetry-Catapultフレームワークを使って、javascriptのアプリのパフォーマンスを調査してきました。
以前にブログ記事*2に書いたように、実際のアプリケーションにおけるパフォーマンスのテストを行うことが、いかに我々の最適化の仕事をすすめるかといったことを議論してきました。
Ignition-TurboFanパイプラインへの変更によって、実際のアプリケーションにおいて改善が見られました。
特に、幾つかの有名なウェブサイトにおいて、ユーザインタラクションテストの実行スピードの向上が見られました。
f:id:kakts:20170517001921p:plain

f:id:kakts:20170517001930p:plain

Speedometerは統合的なベンチマークツールであるが、我々は、他の統合的なベンチマークよりもSpeedometerがより実際のアプリケーションの負荷を近似していることをあきらかにしました。
Ignition-TurboFanパイプラインへの変更によって、v8のSpeedometerベンチマークが5-10%向上しました。

サーバサイドjavascriptにおいても同様に速度向上がみられ、AcmeAirとよばれるnode.jsのベンチマークにおいても10%以上の向上がみられました。

f:id:kakts:20170517002624p:plain

IgnitionとTurboFanはv8全体のメモリ使用量を減らします。 Chrome M59において、このパイプラインはデスクトップやハイエンドなモバイル端末のメモリ使用量を5-10%減らします。
このメモリ使用量削減は、以前のブログ記事*3で取り上げたIgnitionのメモリ 管理をv8にもたらしたことに起因します。

これらの改善はあくまでも序章で、IgnitionとTurboFanのパイプラインは、今後のjavascriptのパフォーマンス向上のための最適化と、ChromeとNode.jsにおけるv8のメモリ使用量の削減を容易にします。

How browserify works

browserifyの仕組みに関して調べてみたのでメモ

How browserify works

browserifyはコマンドで指定したソースコードからAbstract syntax treeAbstract syntax tree - Wikipedia による静的解析を行い、コードの中に書かれているrequire()を探す。
ソースコード中にrequire()が存在した場合、ファイル中の module 文字列を元にファイルパスを特定し、更に再帰的にrequire()があるかたどっていく。

各ファイルは、静的に解析され、1つのマップにまとめられた1つのrequire()関数にマッピングされ、1つのjsファイルに結合される。
これは、browserifyによって吐き出されたjsファイルは、完全にself-containedであり、アプリケーションが必要とするすべてのものをオーバーヘッド無しで保持できることを意味する。

why concatenate

browserify はサーバ上で動作するビルドステップです。 アプリケーションのすべての依存関係を含んだ単一のbundle fileを生成します。
ここでは、ブラウザ用のモジュールシステムの実装方法を紹介し、各手法のメリット・デメリットについてまとめます。

window glocals

module システムを使う代わりに、各jsファイルがwindow グローバルオブジェクトにプロパティを定義するか、内部の名前空間を利用した実装を行います。 このアプローチは、結論から言うとうまくスケールしません。なぜならば、扱うファイルが増えるたびに、すべてのhtmlページに scriptタグを使って各ファイルを読み込む必要があります。
さらに、あるファイル内で、他のファイルで定義されるモジュールを使っている場合、order-sensitiveであり、読み込む順序を意識する必要があります。
これは非常にリファクタリングとメンテナンスがしづらいのがデメリットですが、すべてのブラウザでこのアプローチを取れることと、サーバサイドのツールが必要ないことがメリットになります。

concatenate

window globalsにプロパティを定義する代わりに、サーバサイドであらかじめすべてのスクリプトを結合する手法があります。
この手法ではいまだにorder-sensitiveで、順番に依存しますが、script タグ1つですむため、ローディングが早くなります。

source mapが無い場合、スローされた例外からは簡単に元のファイルのコードを辿れません。

参考

github.com

ChromeのHeadlessモードを使ってみる。

Headless Chromeとは

chrome 59から、Headlessブラウザとしてchromeを使うことができるようになりました。
https://www.chromestatus.com/features/5678767817097216

2017年5月3日現在では chrome beta版でchrome 59、chrome canary版でchrome 60を利用することができます

https://www.google.co.jp/chrome/browser/beta.html https://www.google.co.jp/chrome/browser/canary.html

Headless ブラウザ

HeadlessブラウザとはGUIの無いブラウザのことで、モニター上では見ることができないですが、
指定したurlに対してhttp httpsで実際にローディングして、DOMやhttpヘッダー情報などを取得して操作することができます。
Headless computer - Wikipedia 主にUIのテストなどで活用されていて、現在までHeadlessブラウザとして有名だったphantom.jsは、今回のHeadless chromeの登場により 開発終了となるそうです。
https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE

Headless Chromeを使えるようにする。

今回はGoogle本家の解説記事を参考に進めていきます。
Getting Started with Headless Chrome  |  Web  |  Google Developers

本記事では、chromeのcanary版を利用します。 利用OSはmacOSを前提に進めます。
2017年5月3日現在 chromeのバージョンは60.0.3088.0で、 バージョン59以降なら本記事の通りにHeadless Chromeを利用できます。
下記ページから chrome canary版をダウンロードしてください。 https://www.google.com/chrome/browser/canary.html

Headless Chromeを使うためには、ここでダウンロードしたchrome canary版をターミナルから直接実行することで利用できます。
macOSにおいて、chrome canary版のアプリは/Applications/Google Chrome Canary.appにインストールされるので、コレを実行するようにします。

まずターミナルで楽に使えるようにエイリアスを設定します。

~/.bash_profileに以下のものを記述します。
ここでは3つエイリアスに設定していますが。 chrome-canaryのみ使います。 chrome通常版がバージョン59以上になっている場合はchromeエイリアスを使って実行してください。

# chrome通常版が ver59以上の場合 コレを使う。  
alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"

# chrome canary版 本記事ではこのエイリアスを使って実行する。  
alias chrome-canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"

# 本家の解説記事で書いてあるが、本記事では実際には使いません。  
alias chromium="/Applications/Chromium.app/Contents/MacOS/Chromium"

source ~/.bash_profile で設定を読み込んだ上で、canaryのバージョンを確認してコマンドが呼び出せるか確認します。

$ chrome-canary --version
Google Chrome 60.0.3088.0 canary

これで、chrome-canaryへのエイリアスが効いているのと、バージョンが59以上になっていることが確認できたのでHeadlessモードでchromeが使える状態になりました。

Headlessモードを使ってみる

ここでターミナルからchrome-canaryコマンドを使ってHeadlessモードを実際に試してみます。
注意点としてはHeadlessモードを使う際は必ず –headlessフラグを付ける必要があります。
–headlessフラグに加えて、いくつかのフラグを実行時に追加することで様々なことができます。
全てではないですが、利用可能なフラグは下記のソースで見ることができます。
https://cs.chromium.org/chromium/src/headless/app/headless_shell_switches.cc

指定urlのDOMを取得する

–dump-dom フラグを追加することで、指定urlでアクセスした際の、document.body.innerHTML の値を出力します。

$chrome-canary --headless --dump-dom https://www.chromestatus.com/
指定urlのinnerHTML出力
<body class="loading">

<!--<div id="site-banner">
  <a href="https://www.youtube.com/watch?v=Rd0plknSPYU" target="_blank">
  <iron-icon icon="chromestatus:ondemand-video"></iron-icon> How we built it</a>
</div>-->

<app-drawer-layout fullbleed="">
  <app-drawer swipe-open="" style="transition-duration: 200ms;">
    <div class="drawer-content-wrapper">
      
<h3>Chrome versions</h3>
..........

スクリーンショットを撮る

–screenshop フラグを追加して、 指定urlでブラウザでアクセスした際のスクリーンショットを撮ることができます。
–window-sizeを追加で指定することで特定のwindowサイズで表示した場合も撮ることができます。

chrome-canary --headless --screenshot https://www.chromestatus.com/
[0503/175140.000000:INFO:headless_shell.cc(437)] Written to file screenshot.png.

# Size of a standard letterhead.
chrome-canary --headless --screenshot --window-size=1280,1696 https://www.chromestatus.com/
[0503/175141.000000:INFO:headless_shell.cc(437)] Written to file screenshot.png.

# Nexus 5x
chrome-canary --headless --screenshot --window-size=412,732 https://www.chromestatus.com/
[0503/175142.00000:INFO:headless_shell.cc(437)] Written to file screenshot.png.

実行したカレントディレクトリにスクリーンショットの画像がダウンロードされます。

参考

Getting Started with Headless Chrome  |  Web  |  Google Developers

オライリー Docker 第3章までの読書メモ

オライリーのDocker本を買って読み始めました。 第3章までメモをとりつつ読み進めたので、箇条書きですがメモを公開します。

Docker

Docker

第1章 コンテナとは何か、そしてなぜ注目されているのか

コンテナとは

コンテナ: アプリケーションを依存対象とともにカプセル化したもの。どの環境であろうと同じように動作する環境を作れる。

コンテナがVMと似ているもの

・隔離されたOS環境をもち、その中でアプリケーションを動作させれる
→ コンテナは軽量な仮想マシンのように見える。

コンテナがVMよりも優れていること

  • 起動の速さ、効率性
    コンテナはホストOSとリソースを共有するので、はるかに効率的 コンテナの起動・停止が一瞬で行える。
    コンテナ内で動作するアプリのオーバーヘッドは ホストOS上のアプリと比較してもごくわずか
  • コンテナのポータビリティ
    どの環境でも全く同じ環境で動作できる
  • 軽量
    数十のコンテナを同時に実行可能 実働環境そのままの分散システムをエミュレートできる。
    VMに比べて 1台のホストマシンではるかに多くのコンテナを実行できる。
  • 設定やインストールが手軽
    ユーザは実行環境や利用可能な依存対象のさいを気にせずにすむようになる。

そもそも、VMとコンテナでは目指すゴールが違う。 VM: 異なる環境を完全にエミュレートする コンテナ: アプリケーションをポータブルにし、単体で動作できるようにする

1.1 コンテナとVM

VMを動作させるためには、下位層のOSやハードウェアへのアクセスを制御し、必要に応じてシステムコールを解釈するタイプ2ハイパーバイザが必要(タイプ2ハイパーバイザとは ホストOS上で動作するもの virtualboxvmwareなど)
それぞれのVMでは、OS、実行するアプリ、必要なライブラリなどの完全なコピーが必要

コンテナの場合は、ホストカーネルは実行されるコンテナと共有される。
つまり、コンテナは常にホストと同じカーネルを実行しなければならない。

アプリケーションYとZは同じライブラリを使っていて、そのデータはそれぞれのアプリで別々のコピーでなく、共有されている。
コンテナエンジンはハイパーバイザ上のVMと同様のやり方でコンテナの起動や終了を待ち受ける。
コンテナ内で動作しているプロセスは、ホスト上で直接動作しているプロセスと同等であり、ハイパーバイザ実行によるオーバーヘッドがない。

コンテナとVMでは、隔離の度合いが違っている。
コンテナの仕組みはまだ日が浅く、多くの組織では動作実績がつまれるまではコンテナの分離昨日を完全に使用するのをためらっている。 そのため,VMとコンテナのハイブリッドシステムがよく見られる。

1.2 Dockerとコンテナ

コンテナ自体はもともと古い概念で、chrootコマンドに代表されるファイルシステムの隔離機能が前からあった。
コンテナはいわば、chrootサンドボックスをプロセスにまで拡張したものと言ってよいものです。

Dockerは ポータブルなイメージと、ユーザフレンドリなインターフェースを中心とする様々な方法で、既存のコンテナ技術をラップして拡張し、コンテナの生成と配布のための完全なソリューションを提供している。
Dockerというプラットフォームには2つの構成要素があり、 1つはDockerエンジンで、 コンテナの生成と実行を待ち受ける。
もう1つはDockerHubで、コンテナを配布するためのクラウドサービスを指す。

Dockerエンジンは、動作中コンテナへの高速で便利なインターフェースを提供する。
DockerHubには ダウンロード可能な大量なコンテナの公開イメージがある。

Dockerエンジンのオープンソース化により、大きなコミュニティができ、コンテナ関連のデファクトになっており現在でも開発が活発である。

2章 インストール

linux環境やmacでのインストールについて解説しているが、ここでは長くなるため省略。

3章 はじめの一歩

3.1 イメージの実行

下記コマンドを実行するとhello worldが表示される。

 docker run debian echo "hello world"

docker run: コンテナの起動を受け持つコマンド
run に続くものは 使いたいイメージの名前(イメージ: コンテナのテンプレート的なもの)
debian: 最小限に抑えられたバージョンのdebian linux ディストリビューションを指定する

 $ docker run debian echo "hello world"  
Unable to find image 'debian:latest' locally
latest: Pulling from library/debian
Status: Downloaded newer image for debian:latest
hello world

docker runの実行時に、毎回イメージのローカルコピーがあるかチェックする。
ない場合はDocker Hubをオンラインでチェックして、最新版のdebianイメージをダウンロードする

イメージダウンロード完了すると dockerはそのイメージを実行中のコンテナに変え、その中で、指定されたコマンド echo “hello world"を実行する

まとめると、docker run実行により、コンテナに対して下記の処理を短い時間で行える。

  • コンテナをプロビジョニングして起動
  • 指定されたコマンド実行
  • コンテナシャットダウン

3.2 基本のコマンド群

コンテナを立ち上げた上で、コンテナ内でシェルを使う

$ docker run -i -t debian /bin/bash

コンテナ内のコマンドプロンプトが表示される。
リモートマシンでssh接続するのと似ている。

-i -tで、 dockerに対してtty月のインタラクティブセッションを要求する
シェルを終了するとコンテナも停止する。

コンテナが動作するのは、そのコンテナのメインプロセスが動作している間だけ。

コンテナにホスト名をつけて実行する

$ docker run -h CONTAINER -i -t debian /bin/bash

起動中のDockerコンテナは docker psで見れる。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
e7a1de88dd53        debian              "/bin/bash"         5 seconds ago       Up 3 seconds                            inspiring_poincare

docker inspect コマンドにコンテナ名やIDを入れると 指定したコンテナの詳細情報が見れる。

実行中のコンテナで変更されたファイルリストを見たい場合

docker psで表示されたコンテナ名を指定する
$ docker diff inspiring_poincare
C /bin
D /bin/bash
A /test

コンテナ内で起きたことの全リストを取得

$ docker logs inspiring_poincare
root@e7a1de88dd53:/# mv /bin/bash /test

3.3 Dockerfileからのイメージの構築

dockerfile Dockerのイメージを生成するための一連の手順を含む、テキストファイルのこと

debian環境でcowsayとfortuneをインストールしたイメージを作る場合 dockerfile というファイルで以下のものを記述する

FROM debian:wheezy

RUN apt-get update && apt-get install -y cowsay fortune

FROM: 使用するベースイメージを指定する。 wheezyというタグのバージョンを使うよう指定している。

Dockerfileのコメントを除く最初の命令は 必ずFROMでなければならない。

dockerfileを元にイメージを構築する。
dockerfileと同一ディレクトリ上で、docker buildコマンドを実行構築できる。

起動時に特定のコマンドを実行させたい場合はDockerfileの末尾にENTRYPOINT命令を指定する

COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

そして、entrypoint.shというファイルをDockerfileと同一ディレクトリ上に作成し、下記の処理を記述する。

#!/bin/bash

if[ $# -eq 0]; then
  /usr/games/fortune | /usr/game/cowsay
else
  /usr/games/cowsay "$@"
fi

引数がある場合は cowsayにそのまま渡す。 なければ fortuneの出力をパイプでcowsayに渡す。

Dockerfileを元にイメージを構築する

docker build -t test/cowsay-dockerfile .

構築したイメージからコンテナを立ち上げる

docker run test/cowsay-dockerfile

イメージ・コンテナ・union file systemについて

イメージとコンテナの関係を理解するためには Dockerのベースとなる技術であるUFSを理解する必要がある。

UFSは、複数のファイルシステムをオーバーレイし、単一のファイルシステムとしてユーザに見せてくれる。
Dockerイメージは複数のレイヤから構成され、イメージの各レイヤはread onlyのファイルシステムです。 新しいレイヤはDockerfileの命令毎に作成されて既存レイヤの上に置かれる。
イメージがコンテナに変換される(つまり、docker run もしくは docker createコマンドの実行時)場合、Dockerエンジンはイメージの上に読み書き可能なファイルシステムを追加する。
不要なレイヤはイメージを肥大化させるため、多くのDockerファイルでは1つのRUN命令で複数のコマンドを指定してレイヤ数を抑える試みが見られる。

コンテナの状態は以下の5つのいずれかになる。
- created : docker createコマンドで初期化されたがまだ起動していない。
- running : コンテナが起動中の状態。
- exited : 一般的には「停止」した状態としてみなされ、コンテナ内で実行中のプロセスがない状態を示す。 exitedになっているコンテナは一度でも起動されたことがある。
- restarting : 障害をおこしたコンテナをDockerエンジンが再起動しようとする際の状態 あまり見ることはない。
- paused : コンテナが一時停止している状態。

3.4 レジストリでの作業

作成したイメージはDocker Hubにアップして公開できる。 他人が作ったものも使うことができる。

https://hub.docker.com/ Docker hubでアカウントを作成する。

docker loginコマンドで作成したアカウントで認証させる。

先程作成したイメージを再構築して、DockerHubにアップロードする。

作成位したDockerHubのアカウント(リポジトリ)名の後に/をつけて、イメージ名をつける。 ここでは testuser/cowsayとする まずはdocker build docker build -t testuser/cowsay .

docker pushでアップロードする docker push testuser/cowsay

リポジトリ名のあとに:をつけて名前を入れれば その名前でタグが作れる

docker pullで他の人が作ったイメージをダウンロードできる。

Redisの公式イメージの使用

まずはredisのイメージを取得

$docker pull redis

redisコンテナを起動する。
-dという引数を付けてみる $ docker run –name myredis -d redis -dはバックグラウンドで実行するオプション

redisコンテナが立ち上がったので rediscliで接続する。

redis-cliを動作させるためのコンテナを立ち上げて、2つのコンテナをリンクするほうが簡単です。

$ docker run –rm -it –link myredis:redis redis /bin/bash これでコンテナ内のシェルを操作する

redis-cli -h redis -p 6379
> ping
PONG

>set "abc" 123
OK

get "abc"
"123"

上記でコンテナをリンクさせる際に指定した –link myredis:redisについて
これは新しいコンテナを既存の"myredis"コンテナに接続するようにDockerに指示する。
Docker側では、新しいコンテナの/etc/hostsにredisというエントリをセットアップし、そのエントリが"myredis"のIPアドレスを指すようにする。
これで、redis-cliコマンド指定時に -h redisと指定して、既存のredisコンテナに接続できる。

実際に、redis-cli用の新しいコンテナ内でのシェルで/etc/hostsを見ると以下のようにredisが指定されている。

# cat /etc/hosts
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2  redis 238a0b42046b myredis

ここで2つのコンテナでredisサーバとクライアントの接続ができるようになった。
しかしredisサーバの方のデータの永続化ができていないため、コンテナを停止したらデータが消えてしまう。
コンテナとホスト、または他のコンテナ間でデータを共有したい。

それを実現するためにボリュームという概念がある。
ボリュームはUFSの一部でなく、ホスト側に直接マウントされているファイルまたはディレクトリのことです。 つまり、ボリュームは他のコンテナと共有できて、すべての変更はホストのファイルシステムに対して行われます。

ボリュームは、DockerfileないでVOLUME命令を使うか、 docker run時に -v フラグを使って指定できる。
デフォルトでは、ホスト上のDockerをインストールしたディレクトリ(/var/lib/docker/が多い)

Dockerfileに指定する場合
コンテナ内の/dataにマウントされる。
VOLUME /data

docker runで指定する場合
$ docker run -v /data test/webserver

これで一旦redisイメージを使ってコンテナを立ち上げ、 redis-cli用コンテナから接続することができました。

参考

www.infoq.com

CentOS node.js 6.x系最新版を ansibleでインストールする

CentOS でnode.js 6.x系のインストール方法

CentOSサーバにて、node.js6.x系最新版をインストールする際、 公式のドキュメントにインストール方がまとまっています。
パッケージマネージャを利用した Node.js のインストール | Node.js
このドキュメントの記載に従ってインストールを行うことができます。

まずは手動でインストールする

サーバ内で6.x系のセットアップスクリプトcurlで取った上でbashを実行します。

sudo curl --silent --location https://rpm.nodesource.com/setup_6.x | sudo bash -

ここでcurlで取得しているセットアップスクリプトは、github上で公開されていて、気になる人は読んでみると良いです。
https://github.com/nodesource/distributions/blob/master/rpm/setup_6.x

このスクリプトの実行により、yumでnode.js 6.x系の最新版パッケージが利用可能になります。 実際に、yum listで利用可能なパッケージを見てみます。

$ yum list nodejs
読み込んだプラグイン:fastestmirror, security
Loading mirror speeds from cached hostfile
 * base: www.ftp.ne.jp
 * epel: ftp.riken.jp
 * extras: www.ftp.ne.jp
 * updates: www.ftp.ne.jp
利用可能なパッケージ
nodejs.i686   0.10.48-3.el6        epel      
nodejs.x86_64   →6.10.2が利用可能になっている  2:6.10.2-2nodesource.el6    nodesource

これでnodejsパッケージをインストールする準備ができましたので、yum installを実行します。
確認のため、インストール後にnode.js と npmのバージョンを確認します。

$sudo yum install nodejs

# node -v
v6.10.2

# npm -v
3.10.10

補足

ここでは、特に必要ないので行わないですが、 もしネイティブアドオンのビルドをするために、デベロップメントツールが必要な場合は
以下のものもインストールする必要があります。

yum install gcc-c++ make

ansible playbookでインストール手順を書いてみる

ansibleのplaybookに、前項で手動で行った処理を書いてみます。

---
- hosts: node_servers
  remote_user: ope_user
  tasks:
    - name: setup nodejs6.x
      become: yes
      shell: curl -sL https://rpm.nodesource.com/setup_6.x | sudo -E bash -
    - name: installing latest version of node.js
      become: yes
      yum:
        name: nodejs
        state: latest

セットアップスクリプトのインストール時、shellモジュールを使ってパイプで bash - を実行したのですが、
root権限が無いためにsudo権限を追加しています。 https://github.com/nodesource/distributions/blob/master/rpm/setup_6.x#L9

curl -sL https://rpm.nodesource.com/setup_6.x | sudo -E bash -

ローカルpcでansible-playbookを実行すると、 対象サーバにnode.js6.x系最新版がインストールされます。

Hive posexplode関数を使った配列操作について (配列のインデックスを保持したまま処理を行う方法)

Hiveをつかってクエリを書く際に、配列データのクエリでの操作についてハマったのでメモ。

複数の配列データを持ったテーブルの操作

ユーザ毎に複数のアイテムIDとアイテム名をもたせた配列を それぞれitem_ids, item_namesとして、 以下のような構造のuser_itemというテーブルがあるとします。

user_itemテーブル

user_id item_ids item_names
user1 [item_a, item_b, item_c] [“アイテムA”, “アイテムB”, “アイテムC”]

ここでは、item_idsとitem_namesの配列の同じインデックスの要素同士で1対1に対応するとします。
例えば、 item_aに紐づくアイテム名は “アイテムA” とします。
このデータを以下のような感じで 対応するアイテムIDとアイテム名毎に1行ずつ出力させたいとします。

出力させたい構造

user_id itemId itemName
user1 item_a “アイテムA”
user1 item_b “アイテムB”
user1 item_c “アイテムC”

この場合 hiveにデフォルトで用意されているビルトイン関数のexplodeを使ってitem_idsの各要素に対してテーブル操作ができますが、
item_idsの各要素のインデックス番号が取れないため、 item_names配列から対応するアイテム名を取ることができません。

posexplode 関数を使って 複数配列間で同一のインデックス番号を対応させる

複数の配列データに対して、配列のインデックスの順序を保ったまま処理させたい場合、 Hive0.13.0から導入された posexplodeというビルトイン関数を使えば実現できます。
LanguageManual UDF - Apache Hive - Apache Software Foundation

LATERAL VIEW句で、 以下のようにposexplodeを使うことで、item_idsの各要素とインデックス番号を取得できます。

LATERAL VIEW (item_ids) item_ids_table AS item_index, itemId

ここで仮に指定している item_ids_table は、posexplode実行時にできる内部テーブルのテーブル名となります。
本記事では特にこの内部テーブルを使わないので、特に説明はしません。

LATERAL VIEW句で posexplode関数を使うことで、カンマ区切りで item_idsの各要素と、そのインデックスを取ることができます。
下記のような感じでitem_names配列に対して取得したインデックスを指定すると 対応したアイテム名も取得できます。

SELECT
  itemId,
  item_names[item_index] AS itemName # itemIdのインデックスに対応したアイテム名を item_names配列から取得
FROM
  user_item
LATERAL VIEW (item_ids) item_ids_table AS item_index, itemId

参考

stackoverflow.com

qiita.com

ansible commandとshellモジュールの違い

ansibleで使って対象ノードで特定のコマンドを実行する際、
デフォルトで用意されているcommandモジュールとshellモジュールを使うことができます。
環境変数や、パイプを使ったコマンドを実行させる場合 この2つは挙動が異なるのでまとめてみました。

command モジュール

まずはcommandモジュールについて
command - Executes a command on a remote node — Ansible Documentation

The given command will be executed on all selected nodes. It will not be processed through the shell,
so variables like $HOME and operations like “<”, “>”, “|”, “;” and “&” will not work (use the shell module if you need these features).

commandモジュールはシェルを介して実行されないため、 環境変数($HOMEなど)や、 “<”, “>”, “|”, “;” などのパイプやリダイレクトは使えません。
commandモジュールの意図として、ユーザ環境に依存せずにセキュアにコマンドを実行することがあげられます。
ユーザ環境変数や、パイプ・リダイレクトを使った処理を行いたい場合は shell モジュールを利用するように公式のドキュメントにも書かれています。

If you want to run a command through the shell (say you are using <, >, |, etc), you actually want the shell module instead. The command module is much more secure as it’s not affected by the user’s environment.

shell モジュール

shell - Execute commands in nodes. — Ansible Documentation

commandモジュールの章でも説明したとおり、このshellモジュールは、指定したコマンドをシェル(/bin/sh)を介して実行します。
実行環境に依存した環境変数や、パイプ・リダイレクトを使った処理を実行できます。
これは悪い点もあり、もし実行時に playbookないで変数を使っていて、その変数が意図しない者の場合、そのまま対象ノードに対して実行されてしまうので危険です。
shell moduleで変数を使う場合は、サニタイズのために “{{ var }}” のかわりに “{{ var | quote }}“ を使うべきです。

shell commandモジュール双方のデメリット

qiita.com 上記の記事に、この2つの問題点が書いてあり、読んでみましたが、概ね同意です。
この2つのモジュールをつかってplaybookを書く場合 コマンド実行による冪等性の保証が困難であり、
playbookを読んだだけではシステムの本来あるべき姿(ansbile playbookは本来システムのある「べき」状態を記述するのが正しいはず)がわからなくなること、
そして実行処理は本来シェルスクリプトに任せればよいと思います。
playbookを書く際には他のモジュールを調べた上で、どうしても他のモジュールでは記述できないときにだけ、このshell commandモジュールを使えばよいかと思います。