Dockerfile内部でapt-cacher-ngを自動検出する

スポンサード リンク

Dockerfileを作っていると,何度もbuildしてはテストを繰り返す。 何度も何度も apt-get install していると時間がかかるので apt-cacher-ngを使うと便利だ。

しかし,DockerfileにproxyのURLを決め打ちしてしまうと 可搬性が損なわれる。特にDocker Hubでbuildするときはapt-cacher-ng を使えないので致命的だ。

そこでproxyを自動検出する,つまり指定のURLへ到達できない場合は apt-cacher-ngを使わないようにDockerfileを記述してみた。 これによりローカルで開発したDockerfileをそのままDocker Hubで公開できる。

2017-08-21追記

記録として本記事を残しておくが,本方式は推奨しない。

squid を使ってhttpの透過型プロキシを立てれば, Dockerfileに何も書かなくていいし,apt以外のyumやwget等も 高速化するからだ。

squidの構築に関しては Docker buildを高速化するためにsquidで透過型プロキシを立ててみた | 電脳手帳 を参照のこと。

apt-cacher-ngコンテナの構築

まずはapt-cacher-ngが動作していないと始まらない。 下記の内容でDockerfileを用意する。

FROM ubuntu:xenial
MAINTAINER M. Tsuyuki

ARG DEBIAN_FRONTEND=noninteractive

RUN apt update \
&&  apt install -y apt-cacher-ng \
&&  apt clean \
&&  rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*

VOLUME ["/var/cache/apt-cacher-ng"]
EXPOSE 3142

CMD chmod 777 /var/cache/apt-cacher-ng \
&&  /etc/init.d/apt-cacher-ng start \
&&  tail -f /var/log/apt-cacher-ng/*

buildしてrunする。

docker build --compress --pull -t apt-cacher-ng .
docker network create usernw
docker run -d --restart=always \
           --net=usernw \
           --name apt-cacher \
           --hostname apt-cacher \
           apt-cacher-ng

ここでわざわざユーザ定義ネットワークを使っているのは コンテナ名でDNSの名前解決をしたいから。

そのうちデフォルトのネットワークでもコンテナ名で名前解決 できるようになると思うが,現在は後方互換性を保つために ユーザ定義ネットワークでないと名前解決できない仕様のようだ。

デフォルトのネットワークを使いたい!という場合は,hostへ ポートを露出しておく。そうすればコンテナをlinkしなくても ホストのIPアドレス(大抵は172.17.0.1)でアクセスできるようになる。

docker run -d --restart=always \
           -p 3142 \
           --name apt-cacher \
           --hostname apt-cacher \
           apt-cacher-ng

Dockerfileの記述

これでDockerfileの話を始められる。 Dockerfileのうえから順を追って説明していこう。

1. 環境変数によるURLの指定

今回は可搬性を意識しているのだから,proxyのURLはbuild-argとして docker build 時に指定できるようにするのが妥当だろう。 そこでARGでURLとポートを指定するようにする。

ARG APTCACHER_URL="apt-cacher:3142"

これだけでは docker run した後にこの環境変数が残らない。 build時のproxy設定を使えるようにARGをENVで受ける。

ENV APTCACHER_URL="${APTCACHER_URL}"

2. Proxyの自動検出

Acquire::http::ProxyAutoDetect をapt.confに記述すれば,aptは proxyのURLを任意のスクリプトから読み込むようになる。 詳細は How do I ignore a proxy if not available? - Ask Ubuntu が詳しい。

これを利用して以下のようにDockerfileに記述する。

# enable apt cacher auto detection
RUN echo 'Acquire::http::ProxyAutoDetect "/etc/apt/detect-http-proxy";' >> /etc/apt/apt.conf.d/30detectproxy \
&&  echo '#!/bin/bash\ntimeout 1 bash -c "cat < /dev/null > /dev/tcp/${APTCACHER_URL/://}" && echo http://${APTCACHER_URL} || echo DIRECT' > /etc/apt/detect-http-proxy \
&&  chmod +x /etc/apt/detect-http-proxy

ここではスクリプトの中身をワンライナーで書いているため読みづらいが, 展開すれば下記のシェルスクリプトになる。

このスクリプトは http://${APTCACHER_URL} へ 到達できればURLを返し,到達できなければProxyの不使用を指示する DIRECTの文字列を返す。

#!/bin/bash
if timeout 1 bash -c "cat < /dev/null > /dev/tcp/${APTCACHER_URL/://}"; then
    echo http://${APTCACHER_URL}
else
    echo DIRECT
fi

ちなみに オリジナルのスクリプト ではURLへの到達確認にncコマンドを使っていたのだが, dockerの library/ubuntu イメージにncは含まれない。 なので下記のようにbashで接続確認する手法を採用した。 bashならば大抵のapt系Linuxのベースイメージに含まれるだろう。

bash -c "cat < /dev/null > /dev/tcp/${APTCACHER_URL/://}"

この手法に関しては "nc -z"の代替コマンド - Qiita とそのリンク先が参考になる。

3. docker build

後はお好きに RUN apt-get install すれば良いだけ。 ただし,apt-cacher-ngでユーザ定義ネットワークを使っている場合は, build時に --network が必要となる。

docker build --network usernw -t ${image} .

Dockerfileのテンプレート

ということで,今回は以下のDockerfileのテンプレを作った。 もう少し短く記述したいところだが...許容範囲ということにしておこう。

FROM ubuntu:xenial
MAINTAINER M. Tsuyuki

ARG DEBIAN_FRONTEND=noninteractive
ARG APTCACHER_URL="apt-cacher:3142"
ENV APTCACHER_URL="${APTCACHER_URL}"

## enable apt cacher auto detection
RUN echo 'Acquire::http::ProxyAutoDetect "/etc/apt/detect-http-proxy";' >> /etc/apt/apt.conf.d/30detectproxy \
&&  echo '#!/bin/bash\ntimeout 1 bash -c "cat < /dev/null > /dev/tcp/${APTCACHER_URL/://}" && echo http://${APTCACHER_URL} || echo DIRECT' > /etc/apt/detect-http-proxy \
&&  chmod +x /etc/apt/detect-http-proxy \

&&  apt update \
&&  apt install -y vim \
&&  apt clean \
&&  rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*

余談: DEBIAN_FRONTEND

ちなみに上記のDockerfileで ENVではなくARGで DEBIAN_FRONTEND noninteractive の環境変数を指定しているのは, docker run した後に この環境変数を残したくないから。

docker exec -it ${image} /bin/bash してから apt-get install するときに困ることがないようにとの配慮だ。

Comments !

social