docker上でpip installのcache-dirが効くようにする方法を調べてみた

以下の記事の続き。

pythonのバッチ用のイメージを作りたくなったのでサイズがどれくらいになるか調べてみた - podhmo's diary

調べてみたところ、どうやらbuildkitの機能を使うと行けるらしいという事がわかったので色々調べてみた。

python - Using a pip cache directory in docker builds - Stack Overflow

まとめると以下のような状況なので結構嬉しそう。

  • pip installのcache-dirが効くようになる
  • install時に --no-cache-dir を付けていたのと同程度にimage sizeが小さくなる

また以前と同様にpythonのdocker imageをベースに色々調べてみる。 前回と同様にテキトーにbotoやpandasあたりをインストールして試してみる。

cache-dirが効いているかの確認

dockerはbuild時に --no-cache をつけるとレイヤーのキャッシュを使わずにbuildし直してくれる。 以下の様に2回実行してみたとき、pip installの速度が早くなった。

buildkitの機能を使うには DOCKER_BUILDKIT=1環境変数を追加して実行する必要がある。

$ DOCKER_BUILDKIT=1 docker build -t foo:0.1.0 . -f Dockefile.cached --no-cache
# --no-cache は dockerの話。 Dockerfile.cachedの.cachedはpipの話。紛らわしい

2回実行してみたときに以下のようにキャッシュが使われていた(出力を一部抜粋)。

#8 4.405 Collecting s3transfer<0.5.0,>=0.4.0
#8 4.408   Using cached s3transfer-0.4.2-py2.py3-none-any.whl (79 kB)
#8 4.453 Collecting jmespath<1.0.0,>=0.7.1
#8 4.456   Using cached jmespath-0.10.0-py2.py3-none-any.whl (24 kB)
#8 5.512 Collecting botocore<1.21.0,>=1.20.112
#8 5.530   Using cached botocore-1.20.112-py2.py3-none-any.whl (7.7 MB)

このときのDockefileは以下。

Dockerfile.cached

# syntax = docker/dockerfile:experimental
FROM python:3.9-slim

RUN --mount=type=cache,mode=0755,target=/root/.cache/pip python3 -m pip install boto3 pandas
CMD ["python3"]

今回の例では1度目が20s掛かったところが16sになっていた。

1度目

$ DOCKER_BUILDKIT=1 docker build -t foo:0.1.0 . -f Dockerfile.cached --no-cache
[+] Building 24.8s (9/9) FINISHED
 ...
 => [stage-0 2/2] RUN --mount=type=cache,mode=0755,target=/root/.cache/pip python3 -m pip install boto3 pandas          20.9s
 ...
 => => writing image sha256:dcae12aebff6bff55d9a443e5596f6e0b660171c1788eb842743d07ab0feb43b                             0.0s
 => => naming to docker.io/library/foo:0.1.0                                                                             0.0s

2度目

$ DOCKER_BUILDKIT=1 docker build -t foo:0.1.0 . -f Dockerfile.cached --no-cache
[+] Building 19.4s (9/9) FINISHED
...
 => [stage-0 2/2] RUN --mount=type=cache,mode=0755,target=/root/.cache/pip python3 -m pip install boto3 pandas          16.6s
...
 => => writing image sha256:d4a67db6ae9982a8cd55b675c94cfab05dff9fd11a4519a9cd1f837cbd46668b                             0.0s
 => => naming to docker.io/library/foo:0.1.0                                     

image sizeの比較

今度はimage sizeの比較。こちらも前回と同様に調べておきたい。

最もシンプルなfoo:0.0.0

ベースになるDockefileは以下の様なもの。これをfoo:0.0.0とする。これは全くイメージサイズなどを気にせずpip installしただけのイメージ。

Dockefile

FROM python:3.9-slim

RUN python3 -m pip install boto3 pandas
CMD ["python3"]
$ docker build -t foo:0.0.0

冒頭で試したbuildkitを使った foo:0.1.0

再掲。

Dockefile.cached

# syntax = docker/dockerfile:experimental
FROM python:3.9-slim

RUN --mount=type=cache,mode=0755,target=/root/.cache/pip python3 -m pip install boto3 pandas
CMD ["python3"]

--no-cache-dir を付けた foo:0.2.0

前回の記事でのimage sizeを気にした比較の時に試した pip install --no-cache-dir を使ったほうのイメージ。これと同じサイズならいちいちpip install後にファイル削除などする必要がないということになる。

Dockerfile.nocache

# syntax = docker/dockerfile:experimental
FROM python:3.9-slim

RUN python3 -m pip install --no-cache-dir boto3 pandas
CMD ["python3"]

結果

無事foo:0.1.0が --no-cache-dir をつけて実行したfoo:0.2.0と同じimage sizeとなった。

$ (docker images | ioknife rest | ggrep -P 'foo') |& gsed 's/   */\t/g'
REPOSITORY      TAG     IMAGE ID        CREATED SIZE
foo     0.2.0   d981b2beccb7    2 minutes ago   293MB
foo     0.1.0   7c6ea4f4d154    8 minutes ago   293MB
foo     0.0.0   fb6ec39eb3b5    10 minutes ago  329MB

参考

gist

windows上のpythonでカジュアルにopen()したら UnicodeDecodeError: 'cp932' codec can't decode byte が出た話

windows環境での作業でなるほどと思ったのでメモ。 自作のスクリプトwindows環境で動かしたら動かなかった。以下のようなエラーメッセージが出る。

UnicodeDecodeError: 'cp932' codec can't decode byte 0x81 in position 59: illegal multibyte sequence

$ ~/.local/pipx/venvs/shosai/Scripts/shosai hatena push --publish about-terraform-nested-output.md
extra commands module is not found (shosai.hatena.extra_commands)
INFO:shosai.hatena.configuration:read: C:\Users\podhmo/.config/shosai/config.json
Traceback (most recent call last):
  File "C:\Users\podhmo\.local\pipx\venvs\shosai\Scripts\shosai-script.py", line 33, in <module>
    sys.exit(load_entry_point('shosai', 'console_scripts', 'shosai')())
  File "c:\users\podhmo\ghq\github.com\podhmo\shosai\shosai\commands\shosai.py", line 215, in main
    return submain(args.service, rest_argv)
  File "c:\users\podhmo\ghq\github.com\podhmo\shosai\shosai\commands\shosai.py", line 300, in submain
    return params.pop("subcommand")(service, **params)
  File "c:\users\podhmo\ghq\github.com\podhmo\shosai\shosai\commands\shosai.py", line 137, in push
    parsed = parsing.parse_article(rf.read())
UnicodeDecodeError: 'cp932' codec can't decode byte 0x81 in position 59: illegal multibyte sequence

原因

原因はUTF-8で作られたファイルをcp932で開いているため。ところで、いまだにwindowsではUTF-8以外の値がエンコーディングのデフォルトとして使われる場合があることに驚いた。

>>> import pathlib
>>> open(pathlib.Path("~/.config/shosai/config.json").expanduser())
<_io.TextIOWrapper name='C:\\Users\\podhmo\\.config\\shosai\\config.json' mode='r' encoding='cp932'>

encodingがcp932になっている。

うーん、sys.getdefaultencoding()はUTF-8を返すらしい。

>>> sys.getdefaultencoding()
'utf-8'

真面目にドキュメントをのぞいてみる。

https://docs.python.org/ja/3/library/functions.html?highlight=open#open

encoding が指定されていない場合に使われるエンコーディングはプラットフォームに依存します:locale.getpreferredencoding(False) を使って現在のロケールエンコーディングを取得します。

どうやら、localeモジュールの関数を使って現在のロケールエンコーディングを取得するようだ。

>>> import locale
>>> locale.getpreferredencoding(False)
'cp932'

たしかにcp932。

修正

とはいえ、個人的にopenにencodingを指定して回りたくない。真面目にデフォルトのエンコーディングUTF-8にする方法を調べてみよう。

どうやら、PYTHONUTF8=1環境変数を設定するか -X utf8 というオプション付きで実行すれば良いらしい。

$ python -c 'from pathlib import Path; print(Path("~/.config/shosai/config.json").expanduser().open().encoding)'
cp932

$ python -X utf8 -c 'from pathlib import Path; print(Path("~/.config/shosai/config.json").expanduser().open().encoding)'
UTF-8

うまく機能していそう。

powershell

あとはこれをデフォルトで使うように設定する。 powershellでは以下のような感じ。

[Environment]::SetEnvironmentVariable("PYTHONUTF8", ”1”, 'User')

bash

とりあえず雑に.bashrcに以下を書いた(.bash_profileでも別に良い)

export PYTHONUTF8=1

気になること

とりあえず動くようになったが、設定は片方だけが良い。git bashで使われる環境変数powershellで設定されるuser毎の環境変数は別物何だろうか?

参考