QNAP に Apt キャッシュサーバを建てる

# はじめに
docker を使って開発をしていると apt-get をよく利用する．
特に，ディスクサイズを節約するためにキャッシュを削除したり古いイメージを削除するため，
必要なパッケージを毎回一からインストールすることも多い．
これは色々なところに負荷をかけているので，
APT のパッケージリポジトリキャッシュサーバ ([Apt-Cacher NG](https://www.unix-ag.uni-kl.de/~bloch/acng/)) を用意することにした．

ちょうど [QNAP](https://www.qnap.com/i/jp/) の NAS が利用可能であり，また QNAP は [Docker を使える](https://www.qnap.com/solution/container_station/jp/) と分かったので，
Docker ベースで Apt-Cacher NG を用意する．

# Apt-Cacher NG イメージの準備
Apt-Cacher NG イメージの準備には，
[(Docker で) ローカルに APT のパッケージリポジトリキャッシュサーバ (apt-cacher-ng) を建てたら幸せになった。](http://qiita.com/kitsuyui/items/278663db091334381108)
が参考になった．注意点としては，NAS の CPU アーキテクチャを調べること．
使っている NAS は ARM であったため，ARM 用のイメージを用意する必要があった．

なお，作成した ARM 用 Apt-Cacher NG イメージの Dockerfile は [GitHub](https://github.com/jkawamoto/dockerfiles/tree/master/armhf-apt-cacher-ng) に，
イメージそのものは，[Dockerhub](https://hub.docker.com/r/jkawamoto/armhf-apt-cacher-ng/) に置いてある．

# Apt-Cacher NG コンテナの起動
コンテナの管理には，[Container Station](https://www.qnap.com/solution/container_station/jp/)を利用する．もしなければ，App Center からインストールする．
Container Station のマニュアルは [ここ](https://www.qnap.com/i/jp/tutorial/con_show.php?op=showone&cid=179) から参照できる．

QNAP NAS の Container Station を起動すると，CPU アーキテクチャが確認できる．

<img width="1289" alt="fig1.png" src="https://qiita-image-store.s3.amazonaws.com/0/62253/be42b554-35fb-09a8-133b-8a823230ff79.png">

コンテナの作成タブを開き，使用する Docker イメージを検索する．
デフォルトでは，Docker hub にあるイメージが利用できるようだ．

<img width="1291" alt="fig2.png" src="https://qiita-image-store.s3.amazonaws.com/0/62253/053b2ecf-64e9-e1e5-afd6-29daa66b29da.png">

目的のイメージが見つかったらインストールする．
ちなみに，このイメージは x86用 (だと思う)．
(CPUを確認していなかったので，動くと思っていたら不正なバイナリエラーで落ちた．)

<img width="1308" alt="fig3.png" src="https://qiita-image-store.s3.amazonaws.com/0/62253/de0ef773-e54f-e19b-bb99-01bf619420cd.png">

インストール後に現れる，コンテナの画面．名前は好きに決めれば良い．
コマンドとエントリーポイントは Dockerfile の設定をここで上書きできるが，
特に指示されていなければデフォルトで良いだろう．
CPU やメモリの制限は環境によって変わるので，ここではデフォルトのままにしておく．
次に，詳細設定を確認する．

<img width="1286" alt="fig4.png" src="https://qiita-image-store.s3.amazonaws.com/0/62253/a0aefa38-490e-1f79-7f32-2c2fe662159f.png">

ネットワークの設定で，どのポートがフォワーディングされるか確認する．
Apt-Cacher NG はデフォルトで 3142 を利用する．
ホスト側のポート番号は，始め `auto` になっているので適当な番号に変更する．
バッティングしなければ，同じ 3142 が良いだろう．

また，ベースとなったイメージによっては関係ないポートも空いているので，削除しておく．
下の図の場合，6080 は不要なので削除する．

<img width="1311" alt="fig5.png" src="https://qiita-image-store.s3.amazonaws.com/0/62253/b656cf3a-38dc-9ac7-edfb-c19593639a97.png">

なお，共有フォルダの項目で，キャッシュサーバが利用するディレクトリをマウントすることもできる．
今回はコンテナが死んだ時にキャッシュが消えても特に困らないので，設定しないでおく．

以上の設定が終わればコンテナを作成する．

# キャッシュサーバ利用側の設定
apt-get コマンドがキャッシュサーバを見に行くように設定するには，
`/etc/apt/apt.conf.d/` 以下に適当なファイルを用意し，

```cpp:01proxy
Acquire::http { Proxy "http://xxx.xxx.xxx.xxx:3142"; };
```

と記入する．なお，`xxx.xxx.xxx.xxx` は NAS の IP アドレスである．
例えば，

```sh
echo "Acquire::http { Proxy \"http://xxx.xxx.xxx.xxx:3142\"; };" >> /etc/apt/apt.conf.d/01proxy;
```

を実行すれば良い．
apt-get を実行してみてキャッシュサーバを利用していることを確認する．
(例えば，Contaner Station でネットワークトラフィックやログを見ることができる)

# Docker での利用
Docker で apt-get を最も利用するケースは Dockerfile だろう．
`docker build` 時にキャッシュサーバを利用するためには，前節の設定を RUN コマンドで設定すれば良い．
しかし，キャッシュサーバが利用可否で別々の Dockerfile に変わるのは効率が悪い．

キャッシュサーバを利用するか否かを `docker build` に引数を渡すことで設定したい．
そこで，ARG コマンドを利用することにする．(参考：[Dockerfile ARG入門](http://qiita.com/nacika_ins/items/cf8ceb20711bd077f770))

```Dockerfile:Dockerfile
ARG APT_PROXY
RUN if [ -n "$APT_PROXY" ]; then \
      echo "Set apt proxy: $APT_PROXY"; \
      echo "Acquire::http { Proxy \"$APT_PROXY\"; };" >> /etc/apt/apt.conf.d/01proxy; \
    fi
```

上記の Dockerfile では，APT_PROXY という変数を定義している．
この変数は `docker build` 時に `--build-arg APT_PROXY=http://xxx.xxx.xxx.xxx:3142/`
というオプションで設定することができる．
なお，`--build-arg` を付けなければ，空文字になるためキャッシュサーバの設定はスキップされる．

以上により，キャッシュサーバを使うか否かをビルド時に決定できるようになった．

# 時間計測
せっかくキャッシュサーバを用意したので，ビルド時間の比較を行った．
比較には，[jkawamoto/roadie-gcp](https://github.com/jkawamoto/roadie-gcp) イメージを用いた．([GoogleCloudPlatformで単発処理を行うDockerコンテナ](http://qiita.com/jkawamoto/items/fbe28dbed533a7001f68))

ちなみに，apt-get を行っている部分を抜き出すと，

```Dockerfile:Dockerfile
RUN apt-get update && \
    apt-get install -y unzip libssl-dev python-pip python-dev libffi-dev && \
    apt-get upgrade -y && apt-get clean && \
    rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/
```

である．
比較結果は次の通り．

```shell-session
$ time docker build -t jkawamoto/roadie-gcp --no-cache .
> Successfully built 62b4e3d
>     495.09 real         0.28 user         0.24 sys
$ time docker build -t jkawamoto/roadie-gcp --no-cache \
    --build-arg APT_PROXY=http://xxx.xxx.xxx.xxx:3142/ .
> Successfully built 2b57d1c
>     131.17 real         0.29 user         0.25 sys
```

1/3 以上に速くなった．なお，`--no-cache` は docker build にキャッシュを使わないという設定で，apt-get とは異なる．

# まとめ
QNAP NAS の Container Station を使って，[Apt-Cacher NG](https://www.unix-ag.uni-kl.de/~bloch/acng/) コンテナを用意した．
ARM 環境の人は手間がかかるので，今回作成した[イメージ](https://hub.docker.com/r/jkawamoto/armhf-apt-cacher-ng/)を使うと楽だろう．
また， Dockerfile の ARG コマンドを使って，ビルド時にキャッシュサーバの利用を切り替えるようにした．
大規模にやる場合は DNS の方で切り替える方が速いとは思うが，小規模ネットワークならばこれで十分だろう．