防火壁の中の Docker

この投稿は [Fujitsu Advent Calender 2016](http://qiita.com/advent-calendar/2016/fujitsu) の 9日目 の記事です。
この記事に書かれた見解は、個人のものであり、所属する会社・組織を代表するものではありません。

---

proxy に阻まれて Docker が思うように動かないという声を、他のソフトと比べるとよく聞きます。Docker の場合、proxy を意識しなければいけない箇所が少なくとも4つあって、何をしたいかによって、そのうちのどれを設定しないといけないかが変わるので、混乱しやすいのでしょう。加えて、[公式ドキュメント](https://docs.docker.com/)に、その4つをまとめて説明してくれるページがないのも、それを助長しているのかもしれません。
そこで、その4つをまとめて説明してみます。

以下の説明で、proxy の URL や、NO_PROXY に設定するドメイン名/IPアドレスは、各自の環境に合わせて読み替えてください。

## 1. docker pull/push したい

Docker daemon に設定します。
daemon プロセスの環境変数 (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) に設定してあれば効きます。
daemon を systemd で起動している場合は、[公式ドキュメント](https://docs.docker.com/engine/admin/systemd/#/http-proxy)に従って、
/etc/systemd/system/docker.service.d (なければ掘る) の下に、以下のような内容の http-proxy.conf を作って置くだけです。

```:http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://proxy.hogehoge.com:8080/" "HTTPS_PROXY=https://proxy.hogehoge.com:8080/" "NO_PROXY=localhost,127.0.0.1,.hogehoge.com"
```

作ったあと

```bash
systemctl daemon-reload
systemctl restart docker
```

をお忘れなく。

正しく設定できていれば、`docker info` で最後の方に以下のように出ます。

```
Http Proxy: http://proxy.hogehoge.com:8080/
Https Proxy: https://proxy.hogehoge.com:8080/
No Proxy: localhost,127.0.0.1,.hogehoge.com
```

#### 公式ドキュメント

https://docs.docker.com/engine/admin/systemd/#/http-proxy

#### 余談

swarm mode でノードにアクセスするときも、この設定が効くかも。(swarm mode が daemon に入った v1.12 以降をいまだに触れてないのでよくわかりません。すいません。)
proxy のこっち側のノードのアドレスを NO_PROXY に設定しておかないと、proxy を越えてアクセスしに行ってエラーになり (そうな気がし) ます。

## 2. コンテナの中から curl したり git clone したりしたい

コンテナ内の環境変数 (HTTP_PROXY, HTTPS_PROXY, NO_PROXY、Java なやつは \_JAVA_OPTIONS など) に設定しておけば、たいていのやつはいけます。
`docker run  --env` か docker-compose.yml の `environment:` でコンテナ実行時に設定します。docker/docker-compose を起動するプロセスの環境変数に HTTP_PROXY などが設定されていれば、それをそのまま使うと楽ちん。

```bash
docker run --env HTTP_PROXY=${HTTP_PROXY} -it centos:7 bash
```

```yaml:docker-compose.yml
version: '2'
services:
  centos:
    image: centos:7
    environment:
    - HTTP_PROXY=${HTTP_PROXY}
```

#### 公式ドキュメント

- https://docs.docker.com/engine/reference/commandline/run/#/set-environment-variables--e---env---env-file
- https://docs.docker.com/compose/compose-file/#/environment

#### 余談

Dockerfile の ENV に書いてビルドしてしまってもいいのですが、そうすると、イメージに環境変数が焼き込まれてしまいます。会社固有の proxy URL や、まして認証 proxy のクレデンシャルが入ったイメージとか、社外には配布できませんから、お勧めしません。
かつて、Dockerfile で [`UNSETENV HTTP_PROXY` みたいに書いて環境変数を unset](https://github.com/docker/docker/pull/8177) した上で、[環境変数が残ったレイヤーを潰す (squash)](https://github.com/docker/docker/pull/22641) という PR もあったのですが、前者の PRは、レビューの結果、[まっとうなユースケースが考えられないという理由でクローズ](https://github.com/docker/docker/pull/8177#issuecomment-93587164)されました。後者の PR は v1.13 で experimental として入るようです。

## 3. Docker build 中に yum install とか curl とか git clone とかしたい

build-arg を使います。
まず、Dockerfile に ARG で、ビルド中にだけ設定する環境変数を指定しておきます。

```:Dockerfile
ARG HTTP_PROXY
```

次に、`docker build` の `--build-arg` オプションか、docker-compose.yml の `build:` オプションの `args:` で、ビルド環境変数の値を指定します。
ここでも、docker/docker-compose を起動するプロセスの環境変数が設定されていれば、それをそのまま使えます。

```bash
docker build -t myimage --build-arg HTTP_PROXY=${HTTP_PROXY} .
```

```yaml:docker-compose.yml
version: '2'
services:
  myimage:
    build:
      context: .
      args:
      - HTTP_PROXY=${HTTP_PROXY}
```

Dockerfile の ARG で指定していない環境変数を `docker build --build-arg` や docker-compose.yml で指定するとエラーになります。ただし、HTTP_PROXY など proxy 関係の環境変数は predefine されています。つまり、ARG で指定していなくても、`docker build` や docker-compose.yml で指定できます。build-arg が、まさにこの目的でサポートされたのがわかりますね。

#### 公式ドキュメント

- https://docs.docker.com/engine/reference/builder/#/arg
- https://docs.docker.com/engine/reference/commandline/build/#/set-build-time-variables---build-arg
- https://docs.docker.com/compose/compose-file/#/args

#### 余談

build 中のコンテナには、docker run のように --env を渡せません。なので、v1.9.0 以前は Dockerfile に ENV で指定するしかなかったのですが、前項で書いたようにイメージに環境変数が残ってしまいます。前述の PR がクローズされた代わりかどうかわかりませんが、[v1.9.0 から build-arg がサポート](https://github.com/docker/docker/pull/15182) されました。

## 4. Docker CLI からリモートの daemon を操作したい

CLI のプロセスの環境変数 (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) に設定してあれば効きます。

```bash
export HTTP_PROXY="http://proxy.hogehoge.com:8080/"
export NO_PROXY="localhost,127.0.0.1,.hogehoge.com"
```

proxy の向こうの daemon を操作するというよりは、NO_PROXY をちゃんと設定しておかないと proxy のこっち側の daemon にアクセスできない、という場合の方が多そうです。

#### 公式ドキュメント

https://docs.docker.com/engine/reference/commandline/cli/#/environment-variables

## 5. 全部に共通の注意事項

### 5.1 NO_PROXY に IP アドレスを指定する場合
NO_PROXY に IP アドレス を指定する際、[CIDR](https://tools.ietf.org/html/rfc4632)記法やワイルドカードは書けない、というか、書いても効きません。

```
NO_PROXY=localhost,127.0.0.1,.hogehoge.com,10.0.0.0/8
NO_PROXY=localhost,127.0.0.1,.hogehoge.com,10.*
NO_PROXY=localhost,127.0.0.1,.hogehoge.com,10.
```
のように書いてもだめということ。IP アドレスは、手書きなり、シェルスクリプトで展開するなりして、個別アドレスを全部列挙するしかありません。

#### 余談

CIDR記法がサポートされていない理由が「curl も wget もサポートしていない、つまり非標準だから」って言うんだけど、curl や wget と一緒になって、みんな一斉にサポートしよー、ってならないもんですかねぇ。(他力本願
https://github.com/docker/docker/issues/9145
https://github.com/docker/docker/issues/19256

### 5.2 認証 proxy のクレデンシャル

認証 proxy のクレデンシャルは、URL に書けば、たいていの場合効きます。

```
HTTP_PROXY=http://proxy-user:proxy-pw@proxy.hogehoge.com:8080/
```

Java なやつは、

```
_JAVA_OPTIONS="-Dhttp.proxyUser=proxy-user -Dhttp.proxyPassword=proxy-pw -Dhttp.proxyPort=8080 -Dhttp.proxyHost=proxy.hogehoge.com -
Dhttp.nonProxyHosts=localhost|127.0.0.1|*.hogehoge.com"
```
みたいに、オプションで -D しないとだめなようです。(私は、Java に関してはからっきし知識がないので、つっこみはご容赦のほどを...)

## あとアがき

このところ手を動かせてなくて、ずいぶん前に書いた Dockerfile やら docker-compose.yml やらと、公式ドキュメントと、首っぴきで思い出しながら書いてみました。不備や誤りにお気づきの場合は、お手数ですが編集リクエストを投げていただけましたら、謹んで訂正させていただきます。

ちなみに関係ないけど、この記事はせっかくなので [Kobito](http://kobito.qiita.com/) で書いたのですが、残念ながら Kobito さんは proxy を通過してくれなかったので、やむなく md ソースをコピペしました。proxy サポート、期待して待ってますー。