CircleCI Local で setup_remote_docker を使えるようにする


CircleCI 2.0 は docker ネイティブなので、setup_remote_docker をすることで docker を使ったテストやビルド等の様々なジョブが実行できます。
しかしながら、例えば `docker volume create` をやっている場合は、CLI だとそのままでは以下のようなエラーが出てしまいます。

```shell-session
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.38/volumes/create: dial unix /var/run/docker.sock: connect: permission denied
```

いろいろと不便なので、なんとかしてみた結果がこちら。

## 結論

config.yml の steps の序盤で以下の処理を実行すれば解決。

```bash
if [[ $CIRCLE_SHELL_ENV =~ "localbuild" ]]; then
  sudo chown root /usr/bin/docker
  sudo chmod u+s /usr/bin/docker
fi
```

## 調査内容メモ

とりあえず setup_remote_docker した後、sleep をかますだけの config を用意。（10分で強制的に切れるようです）
 
```.circleci/config.yml
version: 2
jobs:
  build:
    working_directory: ~/app
    docker:
      - image: circleci/python:3.6
    steps:
      - setup_remote_docker
      - run:
          name: test
          command: sleep 600
```

`circleci local execute` で実行。

```shell-session
% circleci local execute
Docker image digest: sha256:525c91e01875050fbf65cdb4bc83a45744c54f9027fa05f76f88713f3d37f4e3
====>> Spin up Environment
Build-agent version 0.0.7725-9681bc36 (2018-08-09T15:10:50+0000)
Starting container circleci/python:3.6
  using image circleci/python@sha256:3d2fcd53ad4b75c4e7cdeb0bbf18264ffa708c4a9216dbbe419df786c9c8310e

Using build environment variables:
  BASH_ENV=/tmp/.bash_env-localbuild-1536222453
  CI=true
  CIRCLECI=true
  CIRCLE_BRANCH=xxxxxxxx
  CIRCLE_BUILD_NUM=
  CIRCLE_JOB=build
  CIRCLE_NODE_INDEX=0
  CIRCLE_NODE_TOTAL=1
  CIRCLE_REPOSITORY_URL=git@github.com:xxxxxxxx.git
  CIRCLE_SHA1=2760e26695363d6497c2c236709a705db92d0009
  CIRCLE_SHELL_ENV=/tmp/.bash_env-localbuild-1536222453
  CIRCLE_WORKING_DIRECTORY=~/app

====>> Setup a remote Docker engine
Using local docker engine bind-mounted
====>> test
  #!/bin/bash -eo pipefail
sleep 600
```

Using local docker engine bind-mounted と言ってます。
sleep したところで、別ターミナルで `docker ps` してみると、circleci/picard（CLI 本体）と circleci/python:3.6（メインコンテナ）が動いてます。

```shell-session
% docker ps
CONTAINER ID        IMAGE                 COMMAND             CREATED             STATUS              PORTS               NAMES
e1ebbeccc823        circleci/python:3.6   "/bin/sh"           7 seconds ago       Up 5 seconds                            vigorous_brattain
16ba46868114        circleci/picard       "circleci build"    9 seconds ago       Up 7 seconds                            hardcore_murdock
```

`docker inspect` してみると、circleci コマンドが動かすコンテナはどうやら /var/run/docker.sock を /var/run/docker-temp.sock としてマウントしており、docker コマンドが入っているイメージ（circleci/python:3.6 等）を使えばそのまま接続できそうです。

```shell-session
% docker inspect vigorous_brattain
...snip...
        "Mounts": [                                                                                                                                     
            {                   
                "Type": "bind",                   
                "Source": "/var/run/docker.sock",
                "Destination": "/var/run/docker-temp.sock",
                "Mode": "",   
                "RW": true,  
                "Propagation": "rprivate"                                                                                                              
            },                     
...snip...
```

`docker exec` で覗いてみます。

```shell-session
% docker exec -it vigorous_brattain bash
circleci@e1ebbeccc823:/$ ls -al /var/run
lrwxrwxrwx 1 root root 4 Aug 31 00:00 /var/run -> /run
circleci@e1ebbeccc823:/$ ls -al /run
total 16
drwxr-xr-x 1 root        root        4096 Sep  6 08:33 .
drwxr-xr-x 1 root        root        4096 Sep  6 08:33 ..
srw-rw---- 1 root        root           0 Sep  5 02:54 docker-temp.sock
lrwxrwxrwx 1 root        root          25 Sep  6 08:33 docker.sock -> /var/run/docker-temp.sock
drwxr-x--- 2 Debian-exim Debian-exim 4096 Sep  6 00:06 exim4
drwxrwxrwt 2 root        root        4096 Aug 31 00:00 lock
-rw-rw-r-- 1 root        utmp           0 Aug 31 00:00 utmp
```
`/var/run` は `/run` への symlink、さらに `/run/docker.sock` が `/var/run/docker-temp.sock` になっています。

普通に `docker run` すると、件のエラーが出ます。

```shell-session
circleci@e1ebbeccc823:/$ docker run --rm -it busybox
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.38/containers/create: dial unix /var/run/docker.sock: connect: permission denied.
See 'docker run --help'.
```

せっかくローカルでは Docker for Mac が動いているのに permission denied とは惜しい感じですが、sudo かますと繋がります。

```shell-session
circleci@e1ebbeccc823:/$ sudo docker run --rm -it busybox
/ #
```

ただ、config.yml の docker コマンドに sudo をつけてしまうと変更箇所が多いのと、circleci.com 上で動かなくなってしまうので、コマンド自体は変更せずに setuid でやってみます。

```shell-session
circleci@e1ebbeccc823:/$ which docker
/usr/bin/docker
circleci@e1ebbeccc823:/$ ls -al /usr/bin/docker
-rwxr-xr-x 1 1000 1000 37589867 Aug 21 17:29 /usr/bin/docker
```

オーナーが root ではなく uid:1000 になっているので、chown と chmod のコンボで。

```shell-session
circleci@e1ebbeccc823:/$ sudo chown root /usr/bin/docker
circleci@e1ebbeccc823:/$ sudo chmod u+s /usr/bin/docker
circleci@e1ebbeccc823:/$ ls -al /usr/bin/docker
-rwsr-xr-x 1 root 1000 37589867 Aug 21 17:29 /usr/bin/docker
```

sudo なしで動くかどうか確認してみます。

```shell-session
circleci@e1ebbeccc823:/$ docker run --rm -it busybox
/ #
```

無事 setuid した状態で動きました。

後は、circleci.com と local の切り分けですが、`circleci local execute` した際には `CIRCLE_SHELL_ENV` に localbuild という文字が含まれるので、これを検知して chown/chmod するように、config.yml の序盤の step に仕掛ければ良いことになります。