RHEL/CentOS 6で Docker に nsinit/nsenter する

RHEL/CentOS 6 上で Docker に ``nsinit``/``nsenter`` を使って attach する方法のメモです。

CoreOS ですと、すでに Qiita に投稿されている方がいらっしゃいます。

* [Docker の nsinit を CoreOS で試してみた](http://qiita.com/yungsang/items/04e28433710554062451)
* [nsinit が上手く行かなくて困っていたら nsenter があまりにもあっさりだった件](http://qiita.com/yungsang/items/915f7c2ae6ccb239d5ee)

結論から言いますと、 [util-linux](https://www.kernel.org/pub/linux/utils/util-linux/) に入っている ``nsenter`` を使う方がよいです。 ``nsinit`` のほうはちょっと苦労します。

## util-linux に入っている ``nsenter`` を使う

[Attaching to a container with Docker 0.9 and libcontainer](http://jpetazzo.github.io/2014/03/23/lxc-attach-nsinit-nsenter-docker-0-9/) に
書いてある方法でいけます。
ただし、 [gettext](https://www.gnu.org/software/gettext/) を 0.18.3 以上にしないとビルドできないため、 gettext もソースからビルドします。

途中、ルーチンとエイリアス定義をしていますが、好みに応じて変更してください。

1. gettext インストール
    1. ``curl -LO http://ftp.gnu.org/pub/gnu/gettext/gettext-0.19.1.tar.xz`` 
    2. ``tar xf gettext-0.19.1.tar.xz``
    3. ``cd gettext-0.19.1``
    4. ``./configure --prefix=/opt/gettext-0.19.1``
    5. ``make -j4 && sudo make install``
2. util-linux インストール
    1. ``curl -LO https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.2.tar.xz``
    2. ``tar xf util-linux-2.24.2.tar.xz``
    3. ``cd util-linux-2.24.2``
    4. ``CFLAGS=-I/opt/gettext-0.19.1/include LDFLAGS=-L/opt/gettext-0.19.1/lib ./configure --prefix=/opt/util-linux-2.24.2``
    5. ``make -j4 && sudo make install``
3. ``nsenter`` するためのシェルスクリプトのルーチンを ``~/.bashrc`` などに定義する

    ```sh
    docker_nsenter() {
        declare container_id=$1
        if [ -z "$container_id" ]; then
            echo "Usage: docker_container <container_id>" >&2
            return 1
        fi
        /opt/util-linux-2.24.2/bin/nsenter --mount --uts --ipc --net --pid --target $(docker inspect --format '{{.State.Pid}}' $container_id)
    }
    alias docker-nsenter='docker_nsenter'
    ```
4. ``source ~/.bashrc`` でルーチン読み込み
5. ``docker-nsenter`` でプロセスに attach する
    * ``docker ps`` でコンテナID確認

        ```
# docker ps
CONTAINER ID        IMAGE                COMMAND                CREATED             STATUS              PORTS                   NAMES
dc1681d20f84        ssh-gateway:latest   /bin/sh -c /sbin/ini   8 days ago          Up 6 days           0.0.0.0:49153->22/tcp   suspicious_pasteur
        ```
    * ``docker-nsenter <container_id>`` で attach

        ```
# docker-nsenter dc16
[root@dc1681d20f84 /]#
        ```

## [docker/libcontainer](https://github.com/docker/libcontainer) に入っている ``nsinit`` を使う

こちらは CentOS の場合、現状それなりに苦労します（最初諦めた）。
``nsinit`` を使うためには、 [Go](http://golang.org/) が必要です。

``go get`` をしたあと、 Docker 1.0 では動かないため（HEAD の Docker が必要）、タグに巻き戻して [パッチ](https://github.com/docker/libcontainer/pull/58) をあてています。

お作法などがありそうな気がしていますが、 Go に未熟で分かっていないので、もっと良い方法がありましたら教えて下さい。

1. Go 環境をインストール
    * ``yum install golang``
2. パッケージを入れるためのディレクトリ（``GOPATH``) を作成
    1. ``mkdir /var/lib/go``
    2. ``echo 'export GOPATH=/var/lib/go' >> ~/.bashrc; source ~/.bashrc`` 
3. ``nsinit`` をインストール（要パッチ）
    * Docker v1.0 の場合 
        1.  ``go get -d github.com/docker/libcontainer/nsinit``
        2.  ``cd $GOPATH/src/github.com/docker/libcontainer``
        3.  ``git checkout -b v1.0.1+patch v1.0.1``
        4.  ``git fetch origin refs/pull/58/head``
        5.  ``git cherry-pick FETCH_HEAD``
        6.  ``go install github.com/docker/libcontainer/nsinit``
    * Docker v1.1 の場合
        1.  ``go get -d github.com/docker/libcontainer/nsinit``
        2.  ``cd $GOPATH/src/github.com/docker/libcontainer``
        3.  ``git checkout -b v1.1.0+patch v1.1.0``
        4.  ``git fetch origin refs/pull/58/head``
        5.  ``git cherry-pick FETCH_HEAD``
        6.  ``go install github.com/docker/libcontainer/nsinit/main``
            * **注** v1.1.0 ではコマンド構成が代わり、  github.com/docker/libcontainer/nsinit **/main** としないとインストールできません
        7.  ``mv $GOPATH/bin/main $GOPATH/bin/nsinit``
4. ``nsinit`` するためのシェルスクリプトのルーチンを ``~/.bashrc`` などに定義する

    ```sh
    docker_nsinit() {
        declare container_id=$1
        if [ -z "$container_id" ]; then
            echo "Usage: docker_nsinit <container_id> <command> [[args]...]" >&2
            return 1
        fi

        shift

        declare full_container_id=$(docker inspect --format '{{.Id}}' $container_id)
        (cd /var/lib/docker/execdriver/native/$full_container_id && $GOPATH/bin/nsinit $*)
    }
    alias docker-nsinit='docker_nsinit'
    ```
5. ``source ~/.bashrc`` でルーチン読み込み
6. ``docker-nsinit`` でプロセスに attach する
    * ``docker ps`` でコンテナID確認

        ```
# docker ps
CONTAINER ID        IMAGE                COMMAND                CREATED             STATUS              PORTS                   NAMES
dc1681d20f84        ssh-gateway:latest   /bin/sh -c /sbin/ini   8 days ago          Up 6 days           0.0.0.0:49153->22/tcp   suspicious_pasteur
        ```
    * ``docker-nsinit <container_id> exec -- bash`` で attach

        ```
# docker-nsinit dc16 exec -- bash
[root@dc1681d20f84 /]#
        ```

なお、 ``nsinit`` に関しては、以下のエラーメッセージが表示されるというはまりを経験しました。

* ``nsenter: Failed to open ns file "/proc/1408/ns/pid" for ns "pid" with error: "No such file or directory"`` と怒られる
    * [Issue#42](https://github.com/docker/libcontainer/issues/42) で報告したあと、[パッチ](https://github.com/docker/libcontainer/pull/58) を作っていただいた方がいらっしゃいます。
    * こちらのパッチを [v1.0.1](https://github.com/docker/libcontainer/tree/v1.0.1) (Docker v1.1.0 の場合は [v1.1.0](https://github.com/docker/libcontainer/tree/v1.1.0))にマージすることで解決します
* ``2014/06/29 23:22:06 failed to exec: bridge is not specified`` と怒られる
    * Docker v1.0 において、パッチを最新の master にマージすると発生します
    * [Victor Marmol](https://github.com/docker/libcontainer/issues/42#issuecomment-47462528) によれば、

        > Yes, there have been a few changes to libcontainer that have not yet been integrated into Docker. If you're using libcontainer alongside Docker you must build with the version tagged for that Docker release. In this case, we've changed the checkpoint to include the network information. 

      とのことで、Docker 1.0 を使う場合は v1.0.1 タグにマージしないといけません。