docker の snap パッケージをプロキシ環境で使う方法と、 compose で build エラーになる話

Pocket

本記事は、 Docker/コンテナ仮想環境 Advent Calendar 2024 - Qiita 5日目の記事だ。

Linux で docker をインストールする場合、どのようにしているだろうか?

OS 標準のパッケージマネージャーからのインストールが一般的?
それとも、 Docker 公式のリポジトリ (Install - Docker Docs) を使っているだろうか?

そんな docker インストールする方法のひとつに Snap (Snapcraft) がある。

Snap (Snapcraft) とは

Snap (Snapcraft) は、Canonical が開発・管理している、アプリケーションをパッケージ化して配布するためのシステムのひとつだ。 Canonical は Ubuntu を支援している企業だが、 Ubuntu 以外の多くの主要なディストリビューションでも Snap を利用できる。

依存関係を含む一式がコンテナ化されており、異なる Linux ディストリビューション間で共通のパッケージで動作するのが特徴だ。 サンドボックス化したファイルシステム内でインストールしたアプリケーションを動かすという、セキュリティ面の特徴もある。

snap 版 docker は、 /home/ 以下のファイルしか参照できない強い制限 1 や、インストール時に docker グループを作成してくれない制限があるが、それ以外はわりと問題なく動く。

Snap 版 docker に於けるプロキシ設定手順

プロキシの設定さえ適切に行えば、 snap 版 docker もプロキシ環境下で動作する。

基本的な設定箇所は、 Docker DaemonDocker CLI の設定と同じだが、前述のサンドボックス化の関係で、記述するファイルの場所が異なる。

具体的には

  • /etc/docker/daemon.json の替わりに /var/snap/docker/current/config/daemon.json
  • ~/.docker/config.json の替わりに ~/snap/docker/current/.docker/config.json

をそれぞれ書き換える。

特に後者 (~/snap/docker/current/* 以下) については、一度そのユーザーで何らかの docker コマンド (後述の例では docker info) を実行してから設定しなくてはならない。
そうしないと、フォルダが Snap サンドボックスへマッピングが行われないからだ。

では、インストールからの手順を追ってみよう

  1. docker グループを作成する
    sudo addgroup --system docker
    sudo adduser $USER docker
    newgrp docker
  2. Snap のプロキシ設定と docker のインストール
    sudo snap set system proxy.https=http://proxy.example.com:8080
    sudo snap install docker
  3. docker daemon と docker CLI のプロキシ設定を行う
    sudo tee /var/snap/docker/current/config/daemon.json << 'EOF' > /dev/null
    {
      "log-level": "error",
      "proxies": {
        "http-proxy": "http://proxy.example.com:8080",
        "https-proxy": "http://proxy.example.com:8080",
        "no-proxy": "localhost,127.0.0.1,host.docker.internal,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.internal"
      }
    }
    EOF
    sudo snap restart docker
    docker info
    mkdir ~/snap/docker/current/.docker/
    cat << 'EOF' > ~/snap/docker/current/.docker/config.json
    {
      "proxies": {
        "default": {
          "httpProxy": "http://proxy.example.com:8080",
          "httpsProxy": "http://proxy.example.com:8080",
          "noProxy": "localhost,127.0.0.1,host.docker.internal,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.internal"
        }
      }
    }
    EOF
    export http_proxy=http://proxy.example.com:8080; export https_proxy=$http_proxy

これで完成だ。

なお、 docker daemon のプロキシ設定手段は、上述の daemon.json を設定する方法の他、 systemd の unit ファイルで設定する方法 も存在する。
その場合も、 /etc/systemd/system/docker.service.d/http-proxy.conf の替わりに /etc/systemd/system/snap.docker.dockerd.service.d/http-proxy.conf を書き換えれば OK だ。

プロキシが機能していることの確認

daemon.json の設定によって daemon にプロキシが通っているので pull もできる。

$ docker pull alpine:3.19
3.19: Pulling from library/alpine
a7cd7d9a2144: Pull complete
Digest: sha256:7a85bf5dc56c949be827f84f9185161265c58f589bb8b2a6b6bb6d3076c1be21
Status: Downloaded newer image for alpine:3.19
docker.io/library/alpine:3.19
$

.../.docker/config.json の設定によって 起動したコンテナの環境変数にもプロキシが通っているので、 apk も使える。

$ docker run -it --rm alpine:3.19
/ # printenv | grep "proxy"
HTTPS_PROXY=http://proxy.example.com:8080
no_proxy=localhost,127.0.0.1,host.docker.internal,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.internal
https_proxy=http://proxy.example.com:8080
http_proxy=http://proxy.example.com:8080
HTTP_PROXY=http://proxy.example.com:8080
/ # apk add curl
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
(1/8) Installing ca-certificates (20240226-r0)
...
(8/8) Installing curl (8.9.1-r1)
Executing busybox-1.36.1-r19.trigger
Executing ca-certificates-20240226-r0.trigger
OK: 12 MiB in 23 packages
/ # exit
$

勿論 Dockerfile のビルドコンテナでも。

$ cat << 'EOF' > Dockerfile
FROM alpine:3.20
RUN printenv | grep "proxy" | cat
RUN apk add curl
EOF
$ docker build .
[+] Building 3.6s (7/7) FINISHED                                           docker:default
 => [internal] load build definition from Dockerfile                                 0.0s
 => => transferring dockerfile: 105B                                                 0.0s
 => [internal] load metadata for docker.io/library/alpine:3.20                       0.8s
 => [internal] load .dockerignore                                                    0.0s
 => => transferring context: 2B                                                      0.0s
 => [1/3] FROM docker.io/library/alpine:3.20@sha256:1e42bbe2508154c9126d48c2b8a7542  0.0s
 => CACHED [2/3] RUN printenv | grep "proxy" | cat                                   0.0s
 => [3/3] RUN apk add curl                                                           2.4s
 => exporting to image                                                               0.2s
 => => exporting layers                                                              0.1s
 => => writing image sha256:b4d3285b3df88a27d93b5cb8f2356e9b060e9d495b05d8020f4d42f  0.0s
$

…ところが。

docker compose でプロキシが効かない

同じ Dockerfiledocker compose からビルドしようとすると、 pull までは成功するのだが apk add で失敗する。
(…ことが多い。後述の原因のため若干ランダム性があるかもしれない。)

$ cat << 'EOF' > Dockerfile
FROM alpine:3.20
RUN printenv | grep "proxy" | cat
RUN apk add curl
EOF
$ cat << 'EOF' > compose.yaml
services:
  test:
    build:
      context: ./
    restart: "no"
EOF
$ docker compose up --build
[+] Building 40.9s (6/6) FINISHED                                          docker:default
 => [test internal] load build definition from Dockerfile                            0.0s
 => => transferring dockerfile: 105B                                                 0.0s
 => [test internal] load metadata for docker.io/library/alpine:3.20                  0.9s
 => [test internal] load .dockerignore                                               0.0s
 => => transferring context: 2B                                                      0.0s
 => CACHED [test 1/3] FROM docker.io/library/alpine:3.20@sha256:1e42bbe2508154c9126  0.0s
 => [test 2/3] RUN printenv | grep "proxy" | cat                                     0.3s
 => ERROR [test 3/3] RUN apk add curl                                               39.5s
------
 > [test 3/3] RUN apk add curl:
0.315 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
14.76 WARNING: updating and opening https://dl-cdn.alpinelinux.org/alpine/v3.20/main: Permission denied
14.76 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
39.40 WARNING: updating and opening https://dl-cdn.alpinelinux.org/alpine/v3.20/community: Permission denied
39.40 ERROR: unable to select packages:
39.40   curl (no such package):
39.40     required by: world[curl]
------
failed to solve: process "/bin/sh -c apk add curl" did not complete successfully: exit code: 1

はて?

原因

問題を深堀する為に、 --progress=plain をつけてビルドしてみる。

$ docker compose --progress=plain build
#0 building with "default" instance using docker driver
...
#4 [test 1/3] FROM docker.io/library/alpine:3.20@sha256:1e42bbe2508154c9126d48c2b8a75420c3544343bf86fd041fb7527e017a4b4a
...
#4 DONE 1.0s

#5 [test 2/3] RUN printenv | grep "proxy" | cat
#5 0.286 no_proxy=localhost,127.0.0.1,host.docker.internal,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.internal
#5 0.286 https_proxy=localhost,127.0.0.1,host.docker.internal,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.internal
#5 0.286 http_proxy=localhost,127.0.0.1,host.docker.internal,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.internal
#5 DONE 0.3s

#6 [test 3/3] RUN apk add curl
#6 0.322 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
#6 14.72 WARNING: updating and opening https://dl-cdn.alpinelinux.org/alpine/v3.20/main: Permission denied
#6 14.72 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
#6 40.39 WARNING: updating and opening https://dl-cdn.alpinelinux.org/alpine/v3.20/community: Permission denied
#6 40.39 ERROR: unable to select packages:
#6 40.39   curl (no such package):
#6 40.39     required by: world[curl]
#6 ERROR: process "/bin/sh -c apk add curl" did not complete successfully: exit code: 1
------
 > [test 3/3] RUN apk add curl:
0.322 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
14.72 WARNING: updating and opening https://dl-cdn.alpinelinux.org/alpine/v3.20/main: Permission denied
14.72 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
40.39 WARNING: updating and opening https://dl-cdn.alpinelinux.org/alpine/v3.20/community: Permission denied
40.39 ERROR: unable to select packages:
40.39   curl (no such package):
40.39     required by: world[curl]
------
failed to solve: process "/bin/sh -c apk add curl" did not complete successfully: exit code: 1

おわかりいただけるだろうか?
http_proxyhttps_proxy の環境変数に no_proxy の内容が入っていることに。

実はコレ、厳密には snap の問題では無く docker-compose v2 プラグイン の問題だ。

クロージャで変数のキャプチャ範囲を間違えるという、非っ常~~にありがちな不具合だ。

で、この不具合は 2023年8月リリースの v2.21.0 でとっくに修正されているのだが、 snap docker に同梱しているプラグインが古いままのため、問題になっているのだ。

$ docker --version
Docker version 27.2.0, build 3ab4256
$ docker compose version
Docker Compose version v2.20.3

docker 本体は 27.2 まで更新されているのにね。

docker-snap のリポジトリには Issue を投げているので、近いうちにプラグインのバージョンが更新されることを願う。

Issue に サムアップ しておいてもらえると、対応が早まるかもしれない。

ワークアラウンド

Snap パッケージ側の修正を待てない場合のワークアラウンドについて。

上述の通り docker-compose プラグインの問題なので、ユーザー単位のプラグインフォルダに更新した docker-compose v2 をダウンロードしてしまえば良い。

mkdir ~/snap/docker/current/.docker/cli-plugins
# 動作中の CPU のアーキテクチャ次第で、ダウンロードするファイルは変わるので注意
curl -SL https://github.com/docker/compose/releases/download/v2.29.7/docker-compose-linux-x86_64 -o ~/snap/docker/current/.docker/cli-plugins/docker-compose
chmod u=rwx,g=rx,o=rx ~/snap/docker/current/.docker/cli-plugins/docker-*

ダウンロードするバージョンは v2.21.0 以降なら何でも良いが、現在の snap パッケージ内の docker のバージョン (27.2.0) との組み合わせでテストされている v2.29.7 あたりがいいんじゃなかろうか。

この状態で改めて compose の build すると、以下の通り http_proxy, https_proxy, HTTP_PROXY, HTTPS_PROXY の各環境変数が期待通り .../.docker/config.json で設定したものになっており、 apk add も含めビルドが成功することがわかる。

$ docker compose --progress=plain build
#0 building with "default" instance using docker driver
...
#4 [test 1/3] FROM docker.io/library/alpine:3.20@sha256:1e42bbe2508154c9126d48c2b8a75420c3544343bf86fd041fb7527e017a4b4a
...
#4 DONE 0.9s

#5 [test 2/3] RUN printenv | grep "proxy" | cat
#5 0.309 HTTPS_PROXY=http://proxy.example.com:8080
#5 0.309 no_proxy=localhost,127.0.0.1,host.docker.internal,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,*.internal
#5 0.309 https_proxy=http://proxy.example.com:8080
#5 0.309 http_proxy=http://proxy.example.com:8080
#5 0.309 HTTP_PROXY=http://proxy.example.com:8080
#5 DONE 0.3s

#6 [test 3/3] RUN apk add curl
#6 0.308 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
#6 0.960 fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/community/x86_64/APKINDEX.tar.gz
#6 2.036 (1/10) Installing ca-certificates (20240705-r0)
...
#6 DONE 2.8s

#7 [test] exporting to image
#7 exporting layers 0.2s done
#7 writing image sha256:e80c51d304a37734fab6a0506616fd2ef5bfd56d20658aec988958a7d342a099 done
#7 naming to docker.io/library/myubuntu-test 0.0s done
#7 DONE 0.2s

#8 [test] resolving provenance for metadata file
#8 DONE 0.0s

その他のトラブルシューティング

ユーザーに docker グループを割り当てているのにもかかわらず、 docker コマンド実行時に dial unix /var/run/docker.sock: connect: permission denied となってしまう場合、

$ groups
myubuntu adm cdrom sudo dip plugdev lxd docker
$ docker info
Client:
 ...
Server:
ERROR: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.47/info": dial unix /var/run/docker.sock: connect: permission denied
errors pretty printing info

docker グループ作成前に snap パッケージをインストールしてしまったせいで、 /var/run/docker.sock のグループパーミッションが root になってしまっている可能性がある。

$ ls -la /var/run/docker.sock
srw-rw---- 1 root root 0 Dec  4 15:03 /var/run/docker.sock

このため、 chgrpchown を使って管理グループを docker に変更してやれば解決する。

$ sudo chgrp docker /var/run/docker.sock
$ ls -la /var/run/docker.sock
srw-rw---- 1 root docker 0 Dec  4 15:03 /var/run/docker.sock
$ docker infoinfo
Client:
 ...
Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 ...

snap 版ではない docker では、インストール時に自動的に docker グループを作成してくれるので、それに慣れているとハマってしまいがちだ。


  1. /var/run/docker.sock のマウントはできるので、 DooD は可能。 

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください