日立製作所の小出です。この記事ではDockerを使う際にプロキシを設定する方法と、それに関連するDockerの挙動について説明します。
はじめに
会社からインターネットに接続するためにはプロキシを経由する必要がある、というのはありがちなシチュエーションだと思いますが、Dockerは他のツールに比べて設定につまづくポイントが多いように思います。実際、Qiitaで検索してみると、以下の件数の記事がヒットします (2019/12/5時点)。
-
yum proxy
: 1281件 -
apt proxy
: 1042件 -
npm proxy
: 832件 -
docker proxy
: 2055件
という具合で、開発者の苦労が伺えます。
また、Docker本体にたびたび設定方法や細かい挙動の変更が入ることも混乱の一因かと思われます。そこで、この記事では2019年12月現在のDocker事情を反映した設定方法を紹介したいと思います。
動作環境
この記事は以下の環境での動作を基に執筆しました。
- Ubuntu 18.04.3 LTS (Bionic Beaver)
- Docker 19.03.5
なお、Dockerの実行や設定にはroot権限が必要です。
TL; DR
先に結論を書いておくと、以下の設定をしておけばイメージの取得からビルド、コンテナ内のコマンド実行まで、プロキシ経由で作業できます。
192.168.0.10:8080
でプロキシサーバが動いていると仮定した場合の例を記載するので、環境に合わせてIPとポートを書き換えてください。また、認証プロキシの場合は http_proxy=http://USER:PASSWORD@example.com:8080
のような書式でユーザ名とパスワードが指定できます。
1. systemdに環境変数を設定する
systemctl edit docker
で設定ファイルを開き、以下を記載します。
[Service]
Environment = 'http_proxy=http://192.168.0.10:8080' 'https_proxy=http://192.168.0.10:8080' # 必要なら 'no_proxy=...'
2. Dockerクライアントを設定する
{
"proxies": {
"default": {
"httpProxy": "http://192.168.0.10:8080",
"httpsProxy": "http://192.168.0.10:8080"
}
}
}
Docker proxy deep dive
ここからはなぜ上記の設定で良いのかの解説です。
dockerdとdocker
Dockerはクライアントサーバモデルのソフトウェアです。MySQLを使うときmysqld
に対してmysql
コマンドで話しかけるように、Dockerはdockerd
に対してdocker
コマンドで話しかけることができます。プロキシもこれら2つのプログラムに対して設定する必要があります。
コンテナの中で動くプログラムに対してプロキシを設定する場合はdocker
を設定し、それ以外の場合 (pullとか) はdockerd
を設定する、という風に分かれているようです。
dockerdの設定
dockerdにプロキシを設定する方法は、環境変数ただ1つです。
systemctl stop docker
でサービスを止めて、dockerdをフォアグラウンドで動かしてみると検証しやすいと思います。
単にdockerd
コマンドを実行すると、フォアグラウンドでdockerdが起動します。
$ sudo dockerd
INFO[2019-12-05T06:46:10.612966150Z] detected 127.0.0.53 nameserver, assuming systemd-resolved, so using resolv.conf: /run/systemd/resolve/resolv.conf
<いろいろ出ますが省略>
INFO[2019-12-05T06:46:10.892366184Z] Docker daemon commit=633a0ea838 graphdriver(s)=overlay2 version=19.03.5
INFO[2019-12-05T06:46:10.892416069Z] Daemon has completed initialization
INFO[2019-12-05T06:46:10.909923228Z] API listen on /var/run/docker.sock
端末をもう一つ開いて、イメージをpullしてみます。この時点では何も設定していないのでpullに失敗します。
$ sudo docker pull alpine
Using default tag: latest
Error response from daemon: Get https://registry-1.docker.io/v2/: dial tcp: lookup registry-1.docker.io: No address associated with hostname
dockerdの標準出力にも、名前解決ができずエラーになったという旨のメッセージが表示されます。
WARN[2019-12-05T07:34:29.915501297Z] Error getting v2 registry: Get https://registry-1.docker.io/v2/: dial tcp: lookup registry-1.docker.io: No address associated with hostname
INFO[2019-12-05T07:34:29.915555514Z] Attempting next endpoint for pull after error: Get https://registry-1.docker.io/v2/: dial tcp: lookup registry-1.docker.io: No address associated with hostname
ERRO[2019-12-05T07:34:29.915607313Z] Handler for POST /v1.40/images/create returned error: Get https://registry-1.docker.io/v2/: dial tcp: lookup registry-1.docker.io: No address associated with hostname
dockerdにhttp_proxy
, https_proxy
を渡せば、pullできるようになります。
$ export http_proxy=http://192.168.0.10:8080
$ export https_proxy=http://192.168.0.10:8080
$ sudo -E dockerd
$ sudo docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
また、docker info
でdockerdが認識しているプロキシを確認することもできます。
$ sudo docker info
Client:
Debug Mode: false
Server:
Containers: 0
Running: 0
<略>
Debug Mode: false
HTTP Proxy: http://192.168.0.10:8080
HTTPS Proxy: http://192.168.0.10:8080
Registry: https://index.docker.io/v1/
<略>
この時点でdocker pull
がうまく動かない場合は、そもそもネットワーク設定が正しくできていない可能性が高いので、OSの流儀に従ってネットワーク設定を見直してください。
systemdの設定
上記と同じ設定をdockerサービスに反映するために、systemdのserviceファイルを編集します。
systemdの設定ファイルは /etc/systemd/system
ディレクトリにあります (他にもたくさんありますが、詳細は man systemd.unit
を参照してください)。このディレクトリに docker.service
というファイルを作って設定を記述すれば良いのですが、プロキシ設定のような「既存の設定にプラスアルファで付け加えたい」という類のものに適したドロップインファイルという機能があるので、それを使うのがおすすめです。
以下のコマンドを実行すると、ドロップインファイルの編集画面が開きます。
$ sudo systemctl edit docker
このファイル (/etc/systemd/system/docker.service.d/override.conf
) に対して、環境変数の設定を記載すれば、dockerサービスがプロキシ経由で動くようになります。
[Service]
Environment = "http_proxy=http://192.168.0.10:8080" "https_proxy=http://192.168.0.10:8080"
あとはサービスを起動すれば、dockerdの設定は完了です。
$ sudo systemctl start docker
$ sudo docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
かつて /etc/sysconfig/docker
というファイルに環境変数を設定する、という方法もありましたが、今はdeprecatedになったようです。
dockerクライアントの設定
冒頭に書いた通り、 ~/.docker/config.json
に設定を書けば良いのですが、この設定方法はDocker 17.07以降で有効な設定方法で、昔のバージョンでは使えませんでした (CentOS 7のextrasリポジトリで配布されているdockerは1.13.1のようなので、この方法は使えません)。
この設定ファイルができる前は、以下のように docker run
の引数で環境変数を指定したり、
$ sudo docker run -it -e http_proxy=http://192.168.0.10:8080 -e https_proxy=http://192.168.0.10:8080 alpine
/ # wget https://qiita.com
コンテナの中で環境変数を設定していました。もちろん今でもこれらの方法は有効です。
$ sudo docker run -it alpine
/ # export http_proxy=http://192.168.0.10:8080
/ # export https_proxy=http://192.168.0.10:8080
/ # wget https://qiita.com
~/.docker/config.json
にプロキシ設定を書いておくと、自動的に上記の環境変数を設定してくれます (大文字のHTTP_PROXY
, HTTPS_PROXY
も設定されます)。
$ sudo docker run -it alpine # ~/.docker/config.json を記載した上で起動
/ # env
HTTPS_PROXY=http://192.168.0.10:8080
HOSTNAME=e60420de06d3
SHLVL=1
HOME=/root
https_proxy=http://192.168.0.10:8080
http_proxy=http://192.168.0.10:8080
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
HTTP_PROXY=http://192.168.0.10:8080
ただし、環境変数が設定されるのはコンテナの生成時なので、起動済みのコンテナにexec
するときは設定されません。
$ mv ~/.docker/config.json{,.bak} # ファイルをリネームして設定を無効化
$ sudo docker run -itd --name alpine alpine
$ mv ~/.docker/config.json{.bak,} # 有効化
$ sudo docker exec -it alpine ash
/ # env
<http_proxy は表示されない>
docker build
先述した設定がしてあれば、ビルド時に特別何かする必要はありません。「あれ? --build-arg
でプロキシ指定するんじゃないの?」と思った方もいるかもしれません。もちろん指定しても動きますが、 ~/.docker/config.json
で指定してあれば不要です。
ちなみに、17.05より前のバージョンでは --build-arg
に指定した内容が docker history
に漏れなく残っていたので、認証プロキシを使っているとどうしてもイメージにユーザ名やパスワードが残ってしまう問題がありました。17.06以降のバージョンであれば、 --build-arg
を使う方法でも、~/.docker/config.json
に指定する方法でも、イメージに情報が残ることはないので安心です (逆に残したい場合は、DockerfileにARGで明示的に指定するとhistoryに残せます)。
付録
~/.docker/config.json
に書いてある "default" の部分には、dockerdが動いているマシンが指定できます。もし複数のマシンでdockerdを動かしていて、それぞれのマシンで起動するコンテナに別々のプロキシを設定したい場合は以下のように設定できます。
{
"proxies": {
"tcp://docker-host1:2375": {
"httpProxy": "http://proxy1.example.com:8080",
"httpsProxy": "http://proxy1.example.com:8080"
},
"tcp://docker-host2:2375": {
"httpProxy": "http://proxy2.example.com:8080",
"httpsProxy": "http://proxy2.example.com:8080"
},
"default": { // 上2つ以外のマシンに適用される
"httpProxy": "http://proxy.example.com:8080",
"httpsProxy": "http://proxy.example.com:8080"
}
}
}