異なるstorage driver を持つ複数のDocker デーモンを起動する

# 経緯 
Docker 上のコンテナにSamba4 をインストールし、samba-tool を実行した時に下記エラーが出力され、インストールに失敗してしまい、先に進めなくなってしまった経験があります。


```text:Samba4のsamba-toolを実行した時に出たエラー
......
ERROR(<class 'samba.provision.ProvisioningError'>): Provision failed - ProvisioningError: Your filesystem or build does not support posix ACLs, which s3fs requires.  Try the mounting the filesystem with the 'acl' option.
  File "/usr/lib/python2.7/dist-packages/samba/netcmd/domain.py", line 401, in run
    use_rfc2307#use_rfc2307, skip_sysvolaclFalse)
  File "/usr/lib/python2.7/dist-packages/samba/provision/__init__.py", line 2160, in provision
    skip_sysvolacl=skip_sysvolacl)
  File "/usr/lib/python2.7/dist-packages/samba/provision/__init__.py", line 1799, in provision_fill
    names.domaindn, lp, use_ntvfs)
  File "/usr/lib/python2.7/dist-packages/samba/provision/__init__.py", line 1551, in setsysvolacl
    raise ProvisioningError("Your filesystem or build does not support posix ACLs, which s3fs requires.  "
```

このエラーの原因を調べていくと、Docker デーモンで使われているストレージドライバaufs がacl に対応していないということで出るものでした。

ストレージドライバはDocker デーモンを起動するときに指定することが可能です。
今使っているDocker デーモンの設定をacl 対応のストレージドライバ(例:devicemapper) に変更するのが手っ取り早いですが、既に作成されているDocker コンテナがaufs の旨みを手放すのは嫌だ…。
うーんどうしたものか…f(´－｀；) と考え、調べること数時間、以下の2 通りの方法で解決に挑戦してみることにしました。

* Docker in Docker で、子コンテナのストレージドライバをdevicemapper にする
* 現在起動しているDocker デーモンとは別のDocker デーモンを起動して、そちらのストレージドライバをdevicemapper にする

## 結論から先に... 
個人的にはDocker in Docker で解決する方法をおすすめします。
別のDocker デーモンを起動する方法では他の人の環境へコンテナを配布するときに、もしその人が自分と同様にaufs な環境でDocker デーモンが起動していた場合、その人へ多くの構築手順を実施してしまうことになるためです。
Docker in Docker で解決する方法では、Dockerfile を配布して実行してもらうことで解決できます。


# 構成 
今回本手順を実施した環境は下記のとおりです。

* 構成
<table><tbody>
  <tr>
    <th> Host OS</th>
    <td> Ubuntu 15.10</td>
  </tr>
  <tr>
    <th> Docker version</th>
    <td> 1.9.1</td>
  </tr>
</tbody></table>

# Docker-in-Docker で解決する 

## 概要 
![Docker_DifferentStorageDriver0000.png](https://qiita-image-store.s3.amazonaws.com/0/70152/c6b325c4-9f9c-1165-eeee-89034f3bb94e.png)

Docker in Docker を使用して子Docker デーモンのストレージドライバをdevicemapper にする方法について説明していきます。
Docker のbacking filesystem の要件として、devicemapper を利用する場合は、Docker デーモンを起動しているHost マシン上のbacking filesystem は一致している必要性がありません。


```text:BackingfilesystemのHostシステムとのマッチ条件
|Storage driver |Must match backing filesystem |
|---------------|------------------------------|
|overlay        |No                            |
|aufs           |No                            |
|btrfs          |Yes                           |
|devicemapper   |No                            |
|vfs*           |No                            |
|zfs            |Yes                           |

抜粋:
https://docs.docker.com/engine/userguide/storagedriver/selectadriver/
```


すなわち、devicemapper なbacking filesystem なDocker コンテナ環境がほしいのであれば、例えばaufs なDocker デーモンのコンテナ上にdevicemapper なDocker デーモンをを起動させることができるということです。

なお、この説明で使用されている最新の資材は以下のGitHub リポジトリにpush しておきました。
<dl>
  <dd> https://github.com/TsutomuNakamura/DockerDock/tree/master/docker-in-docker-simple</dd>
</dl>

## 親コンテナの準備 
Docker-in-Docker の親となるコンテナを作成して起動します。


```text:Dockerfile(親コンテナ)
FROM debian:jessie

RUN apt-get update
RUN apt-get install -y apt-transport-https
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
RUN echo "deb https://apt.dockerproject.org/repo debian-jessie main" > /etc/apt/sources.list.d/docker.list
RUN apt-get update
RUN apt-get install -y docker-engine
RUN docker daemon &

COPY child /root/child

RUN cd /root/child && chmod u+x BuildChild.sh
EXPOSE 80

ENTRYPOINT ["/root/child/BuildChild.sh"]
```


## 子コンテナの準備 
子コンテナを構築するためのスクリプトを作成します。
今回、子コンテナに関する資材は親コンテナのDockerfile の場所から`child` というディレクトリを作成してその中に作成していくことにします。


```bash:child/BuildChild.sh
#!/bin/bash

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd ${SCRIPT_DIR}

CGROUP_DIR=/sys/fs/cgroup

[ -d ${CGROUP_DIR} ] || mkdir ${CGROUP_DIR}

mountpoint -q ${CGROUP_DIR} || {
    mount -n -t tmpfs -o uid#0,gid=0,mode0755 cgroup ${CGROUP_DIR} || {
        echo "Could not mount tmpsfs. Did you use --privileged?" >&2
        exit 1
    }
}

for SUBSYS in $(cut -d: -f2 /proc/1/cgroup); do
    [ -d ${CGROUP}/${SUBSYS} ] || mkdir ${CGROUP}/${SUBSYS}
    mountpoint -q ${CGROUP}/${SUBSYS} || {
        mount -n -t cgroup -o ${SUBSYS} cgroup ${CGROUP}/${SUBSYS}
    }
done

pushd /proc/self/fd
for FD in *; do
    case "${FD}" in
    [012])
        ;;
    *)
        eval exec "$FD>&-"
        ;;
    esac
done

popd

ensure_loop(){
    num="$1"
    dev="/dev/loop$num"
    if test -b "$dev"; then
        echo "$dev is a usable loop device."
        return 0
    fi

    echo "Attempting to create $dev for docker ..."
    if ! mknod -m660 $dev b 7 $num; then
        echo "Failed to create $dev!" 1>&2
        return 3
    fi

    return 0
}

LOOP_A=$(losetup -f)
LOOP_A=${LOOP_A#/dev/loop}
LOOP_B=$(expr $LOOP_A + 1)

ensure_loop $LOOP_A || exit 1
ensure_loop $LOOP_B || exit 1

[ -f /var/run/docker.pid ] && {
    pgrep docker > /dev/null && {
        echo "Some docker daemons are already running." >&2
        exit 1
    } || {
        rm -f /var/run/docker.pid
    }
}

docker daemon --storage-driver="devicemapper" &
sleep 1

docker build -t="taro/docker-in-docker-child:latest" .
docker run --rm -p 0.0.0.0:80:80 --name docker-child -h docker-child -ti "taro/docker-in-docker-child" /bin/bash
```


子コンテナのDockerfile を作成します。


```text:child/Dockerfile
FROM debian:jessie

RUN apt-get update
RUN apt-get -y install nodejs
RUN ln -s /usr/bin/nodejs /usr/bin/node

COPY nodeprog /opt/nodeprog
EXPOSE 80

ENTRYPOINT ["node", "/opt/nodeprog/test.js"]
```


後ほど子コンテナとの疎通確認を行うときに使われるNodeJs テストプログラムを下記の用に作成しておきます。


```javascript:child/nodeprog/test.js
var http = require("http");

console.log("#######################################################");
console.log("# Starting http server ...                            #");
console.log("#######################################################");

http.createServer(function (request, response) {
    var ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
    console.log("Received a request from " + ip);
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.end("This is a test response.\n");
}).listen(80, "0.0.0.0");
```


## コンテナのbuild 
子コンテナの準備もできたら、親コンテナのbuild を実行してみましょう。


```text:親コンテナのbuild
$ sudo docker build -t="taro/docker-in-docker-parent" .
```


親コンテナを起動します。
親コンテナを起動するタイミングで子コンテナの構築が開始されます。
親コンテナを起動するときにHost OS のデバイスを特権で利用できるように`--privilege` オプションをつけて起動するようにしてください。
このオプションがないと、Docker 内にDocker コンテナを作成することができません。


```text:親コンテナの起動(--privilegeオプションをつけて起動する)
$ sudo docker run -p 127.0.0.1:80:80 --name docker-parent -h docker-parent --privileged -ti taro/docker-in-docker-parent:latest
# 初回は結構時間を要します...
...

#######################################################
# Starting http server ...                            #
#######################################################
```


docker が起動したら、curl コマンドを実行して、子コンテナ上のhttp サーバにリクエストを送信してみましょう。
以下のコマンドを実行することで、レスポンスが取得できれば成功です。


```text:子コンテナのhttpサーバにリクエストを送信する
$ curl http://localhost/
This is a test response.
```


# 現在起動しているDocker デーモンとは別のDocker デーモンを起動して解決する 

## 概要 
![Docker_DifferentStorageDriver0001.png](https://qiita-image-store.s3.amazonaws.com/0/70152/4839f764-af72-11c5-4187-0a4e90dd7d3c.png)

現在起動しているDocker デーモンとは別のDocker デーモンを起動し、その別のデーモンのストレージドライバをdevicemapper なものにする方法です。

なお、この説明で使用されている最新の資材は以下のGitHub リポジトリにpush しておきました。
<dl>
  <dd> https://github.com/TsutomuNakamura/DockerDock/tree/master/altdocker</dd>
</dl>

## 新しいDocker デーモン起動について 
Docker デーモンを何も考えずにもうひとつ起動しようとすると、コマンドオプションの以下の物らが競合状態を起こすことになります。


* 競合を起こすDocker デーモンのオプション
<table><tbody>
  </tr>
  <tr>
  </tr>
  <tr>
    <th> -b, --bridge=</th>
    <td> Docker のネットワークbridge</td>
  </tr>
  <tr>
    <th> --exec-root=</th>
    <td> Docker デーモンの状態情報を格納するディレクトリ。デフォルトは"/var/run/docker"</td>
  </tr>
  <tr>
    <th> -g, --graph=</th>
    <td> Docker のイメージ情報が格納されるディレクトリ。デフォルトは"/var/lib/docker"</td>
  </tr>
  <tr>
    <th> -H, --host=[]</th>
    <td> Docker デーモンに接続するためのsocket。デフォルトは/var/run/docker.sock</td>
  </tr>
  <tr>
    <th> -p, --pidfile=</th>
    <td> Docker デーモンのプロセスID が格納されるファイル</td>
  </tr>
</tbody></table>

Docker デーモンをもうひとつ起動する場合は、上記のオプションの値を現在起動しているDocker デーモンと重複しないようにしてください。
またこれらに加えて、今回はストレージドライバをdevicemapper にするため、`-s, --storage-driver=` オプションを使用してdevicemapper を使うように指定します。

## もうひとつDocker デーモンを起動する 
今回起動するもうひとつのDocker デーモンの名前をaltdocker とします。
altdocker を起動する時にaltdocker 用のbridge インタフェースを作成するために`bridge-utils` パッケージをインストールしておきます。


```text:installbridge-utils
$ sudo apt-get install bridge-utils
```


## altdocker サービスを登録する 
`/lib/systemd/system` ディレクトリに下記2 つのスクリプトを作成します。


```bash:/lib/systemd/system/altdocker.service
[Unit]
Description=Alt Docker Application Container Engine
Documentation=https://docs.docker.com
After=network.target altdocker.socket
Requires=altdocker.socket

[Service]
Type=simple
EnvironmentFile=-/etc/default/altdocker
ExecStart=/usr/local/bin/altdocker/altdocker-daemon
MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity

[Install]
WantedBy=multi-user.target
```



```bash:/lib/systemd/system/altdocker.socket
[Unit]
Description=Alt Docker Socket for the API
PartOf=altdocker.service

[Socket]
ListenStream=/var/run/altdocker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target
```


ファイルを作成したら、`systemctl daemon-reload` コマンドを実行してサービスを登録します。


```text:altdockerサービスを登録する
$ sudo systemctl daemon-reload
```


## altdocker-daemon コマンドを作成する 
起動時にaltdocker のbridge ネットワークを作成するために、デーモンを起動するためのスクリプトを用意します。
今回はファイルを下記のように作成しました。


```bash:/usr/local/bin/altdocker/altdocker-daemon
#!/bin/bash

if [ "$(id -u)" != "0" ]
then
    echo "$0 must be run as root." >&2
    exit 1
fi

if [ ! -r /etc/default/altdocker ]
then
    echo "Failed to read file /etc/default/altdocker" >&2
    exit 1
fi
. /etc/default/altdocker

function create_docker_bridge() {
    if ! ip link show $DOCKER_BRIDGE > /dev/null 2>&1
    then
        brctl addbr $DOCKER_BRIDGE
        ip addr add $DOCKER_BRIDGE_IP
    fi
    sleep 0.5
}

function inactivate_docker_bridge() {
    if ip link show $DOCKER_BRIDGE > /dev/null 2>&1
    then
        ip link set dev $DOCKER_BRIDGE down
    fi
}

trap inactivate_docker_bridge EXIT

create_docker_bridge
/usr/bin/docker daemon -H $DOCKER_SOCKET -b $DOCKER_BRIDGE -p $DOCKER_PID_FILE --exec-root $DOCKER_EXEC_ROOT --graph $DOCKER_GRAPH --storage-driver $DOCKER_STORAGE_DRIVER
EXIT_STATUS=$?

if [ $EXIT_STATUS -ne 0 ]
then
    echo "Some error occured when running altdocker daemon" >&2
fi

exit $EXIT_STATUS
```


ファイルを作成したら権限を追加します。

```text:altdocker-daemonの権限追加
$ sudo chown root:root /usr/share/bin/altdocker/altdocker-daemon
$ sudo chmod u+x /usr/share/bin/altdocker/altdocker-daemon
```


## altdocker 管理コマンドを作成する 
altdocker を管理するために`/usr/local/bin/altdocker/altdocker` スクリプトを作成します。


```text:altdocker管理コマンドの作成
$ sudo mkdir /usr/share/bin/altdocker/
$ sudo vim /usr/share/bin/altdocker/altdocker
```


今回は管理コマンドを以下のように作成しています。

```bash:/usr/local/bin/altdocker/altdocker
#!/bin/bash
[ -r /etc/default/altdocker ] && . /etc/default/altdocker
docker -H ${DOCKER_SOCKET:-unix:///var/run/altdocker.sock} $@
```


権限を追加します。

```text:権限の追加
$ sudo chown root:root /usr/share/bin/altdocker/altdocker
$ sudo chmod u+x /usr/share/bin/altdocker/altdocker
```


スクリプトに権限を追加したら、`update-alternatives` で`altdocker` コマンドとして追加します。

```text:update-alternatives
$ sudo update-alternatives --install /usr/bin/altdocker altdocker /usr/local/bin/altdocker/altdocker 10
```


## サービスを起動する 
`systemctl` コマンドを使用してaltdocker デーモンを起動します。
altdocker デーモンを起動するには下記コマンドを実行します。


```text:altdockerデーモンを起動する
$ sudo systemctl start altdocker
```


altdocker デーモンが起動したら、コマンドで動作確認をしてみましょう。
Storage Driver が`devicemapper` になっていることが確認できるはずです。


```text:altdockerの動作確認
$ sudo altdocker info
Containers: 0
Images: 2
Server Version: 1.9.1
Storage Driver: devicemapper
 Pool Name: docker-8:1-3804427-pool
......
```


altdocker にコンテナを作成してみます。


```text:altdockerにコンテナを作成してみる
$ sudo altdocker run -ti debian /bin/bash
......
root@c6ed40e4c1c9:/#
```


問題なくコンテナが起動できれば、成功です。

# 参考 
<dl>
  <dt> Run docker inside a docker container?</dt>
  <dd> http://stackoverflow.com/questions/26239116/run-docker-inside-a-docker-container</dd>
</dl>

<dl>
  <dt> Running out of loopback devices</dt>
  <dd> https://github.com/jpetazzo/dind/issues/19</dd>
</dl>

<dl>
  <dt> Modprobe error while installing Docker on Ubuntu 14.04</dt>
  <dd> http://stackoverflow.com/questions/31904201/modprobe-error-while-installing-docker-on-ubuntu-14-04</dd>
</dl>

<dl>
  <dt> Select a storage driver</dt>
  <dd> https://docs.docker.com/engine/userguide/storagedriver/selectadriver/</dd>
</dl>

<dl>
  <dt> Is it possible to start multiple docker daemons on the same machine</dt>
  <dd> http://stackoverflow.com/questions/32334167/is-it-possible-to-start-multiple-docker-daemons-on-the-same-machine</dd>
</dl>

<dl>
  <dt> brctl でLinuxマシンをHUBにする</dt>
  <dd> http://www.usupi.org/sysad/162.html</dd>
</dl>