apt用のキャッシュプロキシをSquidで構築する

複数の VM において apt を用いたパッケージの更新をすると、帯域を余分に圧迫してしまったり、リポジトリ側にもダウンロードの負荷が余分にかかり迷惑になってしまいます。この問題を解決するために、Squid を用いてローカルにキャッシュプロキシを構築します。

apt用のキャッシュプロキシと言えば apt-cacher-ng が有名ですが、もう長いこと更新されていなかったり、Squid で構築している記事が少なかったりするため、今回はあえて Squid で構築してみようと思います。

構築環境

Docker を使用するためホストの環境はあまり関係ないと思いますが、一応明記しておきます。

  • ホストOS: Debian 12 (bookworm)
  • Docker: 26.1.1
  • CPU: 1 vCore
  • RAM: 1GB

構築手順

Docker のインストールは 公式ドキュメント を参照してください。

また、ホストにおけるファイル群の配置場所を以下のようにしています。

ホストパス概要
/opt/squid/compose.yamlDocker Compose の定義ファイル
/opt/squid/save/squid.confSquid の設定ファイル
/srv/squid-cache/キャッシュを保存するディレクトリ

これらのファイルやディレクトリを別の場所に配置したい場合は適宜読み替えてください。

まず compose.yaml ファイルを作成します。

compose.yaml

services:
  squid:
    image: ubuntu/squid:6.1-23.10_beta
    ports:
      - "3128:3128"
    volumes:
      - "/srv/squid-cache:/var/spool/squid"
      - "./save/squid.conf:/etc/squid/squid.conf:ro"
    environment:
      - "TZ=Asia/Tokyo"
    restart: always

Squid のイメージバージョンは現在この記事を読んでいる時点の最新バージョンに合わせてください。最新バージョンは Docker Hub の ここらへん から確認できます。

その後、以下のコマンドを用いてキャッシュ用のディレクトリを作成します。

Create cache dir

# ディレクトリ作成
mkdir -p /srv/squid-cache/

# 権限設定
chown 13:13 /srv/squid-cache/ # 13:13 または proxy:proxy でも可
chmod 750 /srv/squid-cache/

次に、squid.conf の設定を行います。これは以下の設定ファイルを直接書き込んでも良いですし、デフォルトの squid.conf を持ってきてこの内容を参考に書き換えても良いです。

squid.conf

acl localnet src 172.16.0.0/12
acl localnet src 192.168.0.0/16

acl SSL_ports port 443
acl Safe_ports port 80 # http
acl Safe_ports port 21 # ftp
acl Safe_ports port 443 # https

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager

include /etc/squid/conf.d/*.conf

http_access allow localnet
http_access allow localhost
http_access deny all

http_port 3128

cache_mem 512 MB
cache_replacement_policy heap LFUDA
maximum_object_size 512 MB
cache_dir ufs /var/spool/squid 10000 16 256

coredump_dir /var/spool/squid

refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 100% 1 refresh-ims
refresh_pattern \/InRelease$ 0 100% 1 refresh-ims
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 100% 1 refresh-ims
refresh_pattern (\.deb|\.udeb)$   10080 100% 10080
refresh_pattern .               0       20%     4320

collapsed_forwarding on

デフォルトの squid.conf には各ディレクティブごとにコメントが書いてありドキュメントとして使用できるため、そっちの方がメンテナンス性は上がるかもしれません。ここでは長すぎるので省略しています。

いくつかの重要な設定について確認していきます。

acl localnet src ... ( Docs )
プロキシを利用しても良い接続元 IP アドレス等を指定します。プロキシ利用時に Permission Denied 等のメッセージが表示される場合はこの設定を確認してください。

cache_mem 512 MB ( Docs )
RAM に一時キャッシュしても良い最大容量を指定します。これはプロセスのメモリ上限を指定するものではないため、Squidの他の機能が使用するメモリ分の空きを考慮して設定する必要があります。

cache_replacement_policy heap LFUDA ( Docs )
キャッシュ保存容量が足りなくなった場合に、どのようにキャッシュを消すかの設定です。heap LFUDA とすることで、ファイルサイズが大きいものが優先的に削除されてしまわないようにしています。

maximum_object_size 512 MB ( Docs )
キャッシュを許可する1ファイルあたりの最大容量を指定します。linux-firmware 等は 400MB を超えるため、これは十分に大きい値にする必要があります。

cache_dir ufs /var/spool/squid 10000 16 256 ( Docs )
キャッシュの場所や全体の最大容量を指定します。Docker で実行しているためパスは /var/spool/squid で良いですが、キャッシュ容量は各々のシステムによって変更しなければなりません。
キャッシュ容量はこの設定における 10000 の部分で指定されています。これは単位が MB であり、10GB を最大容量として指定していることになります。

refresh_pattern ... ( Docs )
URI ごとのキャッシュの挙動を指定しています。この設定では Packages や InRelease ファイルなど、バージョン情報を取得する URI に対しては最大で1分間のキャッシュに留め、deb や udeb 等の重いファイルに対して最大 7日間 のキャッシュを適用するようにしています。

collapsed_forwarding on ( Docs )
この設定はキャッシュ可能かどうかの判断に関する設定で、デフォルトでは off が指定されています。on を指定することで、同じURIへの複数のリクエストがキャッシュ可能かどうかが確定する前に、それらをまとめて処理するようになります。
複数 VM において同時に apt upgrade -y を実行した際に、キャッシュ可能であるにも関わらずリモートへ重複リクエストが送られるのを防いでいます。

設定が終わったら以下のコマンドを用いて Squid プロキシを起動します。

Start squid proxy

docker compose up -d

ログにエラー等が出た場合は squid.conf に構文エラーがあるか、キャッシュディレクトリのファイルパーミッションが間違えているなどの原因が考えられます。エラー文で検索しつつ対応してください。

プロキシを利用する側の設定

プロキシを利用するには、apt がプロキシを利用してくれるように設定する必要があります。
Squid プロキシを利用したいサーバーにおいて /etc/apt/apt.conf.d/01proxy にファイルを作成し、以下の内容を書き込みます。

/etc/apt/apt.conf.d/01proxy

Acquire::HTTP::Proxy "http://<Squidを起動しているホストのIPアドレス>:3128";
Acquire::HTTPS::Proxy "false";

IP アドレスはご自身の環境に合わせて設定してください。
また、今回は SSL/TLS 対応をしていないため、HTTPS のリポジトリへの通信はプロキシを介さないようにしています。

検証

わざわざアップデートが必要な VM を用意するのが面倒なので、今回は curl を用いて jp.archive.ubuntu.com から 453MB の linux-firmware をダウンロードすることで検証してみます。

$ time curl -x http://192.168.xxx.yyy:3128 -o out http://jp.archive.ubuntu.com/ubuntu/pool/main/l/linux-firmware/linux-firmware_20240318.git3b128b60-0ubuntu2_amd64.deb
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  453M  100  453M    0     0   9.8M      0  0:00:46  0:00:46 --:--:-- 10.2M

real    0m46.147s
user    0m0.208s
sys     0m1.363s

$ time curl -x http://192.168.xxx.yyy:3128 -o out http://jp.archive.ubuntu.com/ubuntu/pool/main/l/linux-firmware/linux-firmware_20240318.git3b128b60-0ubuntu2_amd64.deb
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  453M  100  453M    0     0   199M      0  0:00:02  0:00:02 --:--:--  199M

real    0m3.370s
user    0m0.106s
sys     0m1.423s

1回目のダウンロードが 46.147秒 ( 79Mbps ) なのに対し、2回目はキャッシュが適用されて 3.37秒 ( 1075Mbps ) で完了していることが分かります。同一ホストにおける別 VM からの通信で検証したので、これはネットワークで頭打ちしているのではなく、Squid のディスクIOで頭打ちしている可能性があります。
少なくともこの検証結果から、パッケージがキャッシュされていることは確認できます。

結果

Squid を用いてパッケージをキャッシュすることで、複数 VM におけるパッケージアップデートを最適化できました。時間があれば SSL/TLS にも対応して、HTTPS のリポジトリもキャッシュできるようにしたいですね。