CoreOS で起動中のコンテナの中に入る


## はじめに
起動させたコンテナの中に入るには ssh を使うのではなく [nsenter](https://github.com/jpetazzo/nsenter) を使うのがオフィシャルに推奨されていた。
2014年10月17日追記: [Docker1.3 からは `$ docker exec` というコマンドが追加された](https://blog.docker.com/2014/10/docker-1-3-signed-images-process-injection-security-options-mac-shared-directories/)。これは nsenter を replcace するものなので、今後はこれを利用する。

ssh を使わない方が良いという話は Docker の公式ブログ [WHY YOU DON'T NEED TO RUN SSHD IN YOUR DOCKER CONTAINERS](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/) に詳しく書かれている。（Qiita に翻訳もある [【翻訳】Docker コンテナー内で SSHd を実行する必要がない理由 - helical618](http://qiita.com/helical618/items/b0ecf70cd0e66b21bd75)）

## CoreOS で docke exec を使ってコンテナの中に入る。

hello コンテナを起動したあと、

```bash
core@core-01 ~ $ docker run -d --name hello busybox /bin/sh -c "while true; do echo Hello World; sl
eep 1; done"
ad6aa31ae8194515e3ffb2e1632849f4cd79fed6af61c36ec6d1d144e86e13f9
```

`exec` を使ってコンテナの中に入る。

```bash
core@core-01 ~ $ docker exec -it hello /bin/sh
/ # ls
bin      etc      lib      linuxrc  mnt      proc     run      sys      usr
dev      home     lib64    media    opt      root     sbin     tmp      var
/ # exit
```

より正確な説明としては、`exec` はコンテナにログインするためだけにあるのではなく、起動中のコンテナに **別のプロセスを注入する** コマンドである。


## CoreOS で nsenter を使ってコンテナの中に入る
Ubuntu や CentOS だと nsenter は公式リポジトリの README に書かれた方法でインストールする必要があるが、最新の CoreOS にはデフォルトで nsenter が入っている（素晴らしい）。

参考: [nsinit (or nsenter) should be installed and available #18](https://github.com/coreos/bugs/issues/18)


```bash
# dd-agent という名前のコンテナが起動している
core@core-01 ~ $ docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS               NAMES
60990982b230        datadog/docker-dd-agent:latest   /usr/local/bin/run-d   8 minutes ago       Up 8 minutes        8125/udp            dd-agent
# dd-agent の PID を取得
core@core-01 ~ $ PID=$(docker inspect --format {{.State.Pid}} dd-agent)
# nsenter でコンテナの中に入る
core@core-01 ~ $ sudo nsenter --target $PID --mount --uts --ipc --net --pid
root@dd-agent:/#
```

## CoreOS で docker-enter を使ってコンテナの中に入る
docker-enter は nsenter をラップしたシェルスクリプトで、コンテナ ID やコンテナ名を元にコンテナの中に入れるコマンドとして使う。

```bash
$ docker-enter <コンテナ ID>
# または
$ docker-enter <コンテナ名>
```

nsenter の README に書かれた方法でインストールした場合、デフォルトでは `/usr/local/bin/docker-enter` に置かれるが、CoreOS では `docker-enter` までは入ってないみたい。 CoreOS の `/usr` 以下は Read Only で、Cloud-Config を使っても書き込めなかった。 yungsang さんが作られている [yungsang/coreos - Vagran Cloud](https://vagrantcloud.com/yungsang/coreos) を使えば `docker-enter` 入りの CoreOS が利用可能である。今のところ Cloud-Config + Docker Image のみだけでいけたらめっちゃシンプルでいいなと思っているので Cloud-Config を使って何とか出来ないかと考え core ユーザの `PATH` を調べてみると

```bash
core@core-01 ~ $ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin:/usr/x86_64-cros-linux-gnu/gcc-bin/4.6.3
```

`/opt/bin` がある。調べるとここは書き込み可能。ということで `/opt/bin/docker-enter` に置く事にした。 `/opt/bin` はデフォルトで存在しないのでディレクトリを作成しないといけないかと思ったが、 `write_files` でファイルを存在しないディレクトリに置こうとした場合は、root 権限でディレクトリも作ってくれた。

```yaml
#cloud-init
# 一部抜粋

write_files:
  - path: /opt/bin/docker-enter
    permissions: 0755
    content: |
      #!/bin/sh
      #
      # Original Source:
      #   https://github.com/jpetazzo/nsenter/blob/master/docker-enter
      #

      if [ -e $(dirname "$0")/nsenter ]; then
          # with boot2docker, nsenter is not in the PATH but it is in the same folder
          NSENTER=$(dirname "$0")/nsenter
      else
          NSENTER=nsenter
      fi

      if [ -z "$1" ]; then
          echo "Usage: docker-enter CONTAINER [COMMAND [ARG]...]"
          echo ""
          echo "Enters the Docker CONTAINER and executes the specified COMMAND."
          echo "If COMMAND is not specified, runs an interactive shell in CONTAINER."
      else
          PID=$(docker inspect --format "{{.State.Pid}}" "$1")
          if [ -z "$PID" ]; then
              exit 1
          fi
          shift

          OPTS="--target $PID --mount --uts --ipc --net --pid --"

          if [ -z "$1" ]; then
              # No command given.
              # Use su to clear all host environment variables except for TERM,
              # initialize the environment variables HOME, SHELL, USER, LOGNAME, PATH,
              # and start a login shell.
              "$NSENTER" $OPTS su - root
          else
              # Use env to clear all host environment variables.
              "$NSENTER" $OPTS env --ignore-environment -- "$@"
          fi
      fi
```

## メモと感想
### `/opt/bin` をこんな感じで使って良いのか

`/etc` が書き込み出来るのは知っていたが、`/etc` にコマンドを置くのはアレだと思っていたら、`/opt` が writable で場所的にも悪くはないと思って使っているけどどうなんだろう。

-> 2014/09/07 追記 @yungsang さんからコメント頂きました！

* linux のお作法的にも問題ない（例えば Chef や Sensu のインストーラとかも `/opt/chef` や `/opt/sensu` とかに置くしね、自分も ubuntu のとき `/opt/app` にアプリケーション置いてる）
* CoreOS チームも `/opt` 以下に Kubernetes 置いてる
 * 参考 - [Running Kubernetes Example on CoreOS, Part 1 - CoreOS Blog](https://coreos.com/blog/running-kubernetes-example-on-CoreOS-part-1/)



### nsenter について（または ssh でログインしないことについて）
コンテナの中で sshd を立てておくというのはコンテナ内に複数プロセス動かすことになる。

これはコミュニティの中で意見が割れている:

* コンテナの中は 1 プロセスのみ動かす派
* コンテナの中で複数プロセス動かす派

にかかわるのかもしれない。この話は [Docker Misconceptions](https://devopsu.com/blog/docker-misconceptions/)（[[翻訳] Dockerについてよくある勘違い](http://techracho.bpsinc.jp/hachi8833/2014_06_16/17982)）でよく検討されている。コンテナの中を 1 プロセスにすると、プロセスのログを stdout にしておくことで `$ docker logs` でログの取得・閲覧が済むので、コンテナの中にログインしてどうこうする、ということも減ったり、コンテナの扱い方としてもシンプルになる気がしている。ただ Sensu とか色んなコンポーネントが必要になるものについては複数プロセスにした方が良い気がしている。適材適所なのかなぁ。

### Docker on Ubuntu の時は Chef を使って docker-enter を入れている

Chef から docker を扱う [bflad/chef-docker](https://github.com/bflad/chef-docker) の `docker_container` resource を使って esenter を入れている。

コマンドラインから esenter をインストールする場合は

```bash
$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
```

とする。それを `docker_container` resource を使って行うため以下のように書いてインストールしている。

```ruby
# your_recipe.rb

#
# Install docker-enter to /usr/local/bin/docker-enter
#   https://github.com/jpetazzo/nsenter
#
docker_container "nsenter" do
  image "jpetazzo/nsenter"
  detach false
  remove_automatically true
  volume "/usr/local/bin:/target"
  not_if { ::File.exists?("/usr/local/bin/docker-enter") }
  action :run
end
```

## REF

* [WHY YOU DON'T NEED TO RUN SSHD IN YOUR DOCKER CONTAINERS](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/)
* [【翻訳】Docker コンテナー内で SSHd を実行する必要がない理由 - helical618](http://qiita.com/helical618/items/b0ecf70cd0e66b21bd75)
* [nsinit (or nsenter) should be installed and available #18](https://github.com/coreos/bugs/issues/18)
* [CoreOS上のコンテナはnsenterでログインする - koh-taka@github](http://qiita.com/koh-taka@github/items/d8c911a4bfc774e4229b)
* [Docker の nsinit を CoreOS で試してみた - yungsang](http://qiita.com/yungsang/items/04e28433710554062451)

* [docker-enter (docker-attach) スクリプト- yungsang](http://qiita.com/yungsang/items/f8dd8f70ac42a60a8a7d)
* [bflad/chef-docker](https://github.com/bflad/chef-docker)