Slide 1

Slide 1 text

Yet Another Isolation Debian Packageと紐づく環境分離 GROOVE X 株式会社 末田 卓巳

Slide 2

Slide 2 text

• Python歴 5年, Go歴 3年 • 社内の主な担当は Linux Kernel, FPGA • WEB+DB Press Vol. 104
 特集 「イマドキPython入門」 第1, 2章 執筆 主なPythonの使いみち
 • Pythonの新機能で遊ぶ • 便利道具を作る・時々PyPIに公開する 例: FPGAにRLEで焼き込むアセットの圧縮率最適化 例: Googleのロケーション履歴から出退勤時間を算出 自己紹介 末田 卓巳 SUEDA Takumi @puhitaku

Slide 3

Slide 3 text

2015年末に創業して以降、数回の調達を経て
 家族型ロボット「LOVOT(ラボット)」を開発中。 現在は出荷に向けて準備を進めています。 弊社紹介

Slide 4

Slide 4 text

Y 弊社紹介

Slide 5

Slide 5 text

弊社紹介 Y

Slide 6

Slide 6 text

LOVOTのスタック Intel x86 kernel rootfs Linux
 (Debian 9) ARM AArch64 kernel rootfs Linux
 (Debian 9) Xilinx FPGA Y

Slide 7

Slide 7 text

TOC • 開発の背景 • LOVOTのソフトウェア管理 • 類似技術の検討 • 開発目標 • 実現手法 / CLI設計 • 実装 • venv をラップして env を作る • 実行ファイルをルックアップする • env 内の実行ファイルをいい感じに走らせる • Shebangから呼び出すには • APTとsystemd • 現状の問題点

Slide 8

Slide 8 text

開発の背景

Slide 9

Slide 9 text

LOVOTのソフトウェア管理 Debian 9 (Stretch) Daemon D (Python) Daemon C (Python) CPython etc. Daemon B (C++) Daemon A (Go) シングルバイナリ, 外部依存なし libfoo.so.3 Pythonパッケージ Pythonパッケージ Python には Python パッケージの依存関係が独自に存在するため
 それらも何らかの方法でインストールしなければならない
 → Python パッケージも APT でインストールすれば良いのでは? ★ LOVOT にデプロイするソフトウェア (daemon) はすべて
 Debian パッケージに格納して APT でインストールしている libbar.so.1

Slide 10

Slide 10 text

APTの依存とPythonの依存 ※ bleson = Bluetooth LE を HCI のレイヤーで操作できるパッケージ ★ 依存する Python パッケージの必要なバージョンを
 的確に APT でインストールできるとは限らない

Slide 11

Slide 11 text

APTの依存とPythonの依存 Daemon Y の依存パッケージ Daemon X の依存パッケージ パッケージ A 3.9.39 パッケージ B 0.1.1 パッケージ C 19.09 パッケージ D 4.2.8 パッケージ B 0.2.13 パッケージ C 18.12 Root Filesystem /usr/share/python3.x/dist-packages/ パッケージ A 3.9.39 パッケージ B ?????? パッケージ C ?????? パッケージ D 4.2.8 ★ つまり、依存パッケージ同士は グローバルでなく 隔離された
 env (Python 環境) に住まわせなくてはならない ★ APT は Python パッケージを全てグローバル環境に置くため
 依存が被った場合 パッケージ間の衝突が発生する

Slide 12

Slide 12 text

APTの依存とPythonの依存 それなら… venv を使えばいいんじゃね? ★ LOVOT にデプロイするソフトウェア (daemon) はすべて
 Debian パッケージに格納して APT でインストールしている ★ 依存する Python パッケージの必要なバージョンをすべて的確に
 APT でインストールできるとは限らない ★ APT は Python パッケージを全てグローバル環境に置くため
 依存が被った場合 パッケージ間の衝突が発生する ★ 依存パッケージ同士は グローバルでなく 隔離された
 env に住まわせなくてはならない

Slide 13

Slide 13 text

例 APTから直接venvを呼ぶ? APTでは Debian パッケージのインストール・アンインストールをフックして
 自由にスクリプトを実行できる → apt install 時に env を作れば良さそう #!/bin/sh -e python3 -m venv /usr/share/python-envs/miku source /usr/share/python-envs/miku/bin/activate pip install -r /usr/share/miku/requirements.txt systemctl enable miku postinst deb パッケージがインストールされた後に実行する #!/bin/sh -e
 systemctl disable --now miku rm -rf /usr/share/python-envs/miku prerm インストールされたファイルが削除される前に実行する Mikuというdaemon専用のenvを作成・削除する

Slide 14

Slide 14 text

APTから直接venvを呼ぶ? 確かに問題は解決するけれど… Debian パッケージごとに env が分離できる ⭕ システムの 「どこに」 「いくつ」 「誰が作った」 env があるか
 わからない ❌ 全員が思ったとおりにenvを作って消してくれる保証がない
 例: 場所や --system-site-packages など ❌ いちいち activate するのが面倒 ❌ Debian Repository にない Python パッケージも導入可能 ⭕

Slide 15

Slide 15 text

APTから直接pyenvを呼ぶ? env を集中管理したいなら…
 pyenv を使えばいいんじゃね?

Slide 16

Slide 16 text

APTから直接pyenvを呼ぶ? pyenvでも確かに問題は解決するけれど… Debian パッケージごとに Python 環境が分離できる ⭕ virtualenvとの連携機能により 決まった箇所にenvを置き
 集中管理できる ⭕ pyenv に不慣れな人は極めて事故りやすい ❌ Shell実装で難解 ❌ いちいち activate するのが面倒 ❌ Debian Repository にない Python パッケージも導入可能 ⭕

Slide 17

Slide 17 text

Slide 18

Slide 18 text

… APT, venv, pyenv その他を調べたものの 欲しいものは手に入らなさそうだったので 自分で欲しいツールを作ることにした

Slide 19

Slide 19 text

開発目標 こういうのが欲しい!! Debian パッケージごとに Python 環境が分離できる ⭕ 決まった箇所にenvを置き集中管理できる ⭕ env と実行したいファイルを明示的に指定して一発で実行できる
 (activateが不要) ⭕ Shebangからでも一発で呼べる ⭕ APT や systemd といい感じに連携可能 ⭕

Slide 20

Slide 20 text

作りました github.com/groove-x/gxenv pypi.org/project/gxenv

Slide 21

Slide 21 text

実現手法と
 CLIをどうするか考えてみる

Slide 22

Slide 22 text

実現したいこと 実現手法とCLIを考える Debian パッケージごとに Python 環境が分離できる ⭕ $ gxenv create foo $ gxenv list /var/lib/python3/envs/foo $ gxenv purge foo 手法 • 設定ファイルで指定されたパスに env を作る • create で作り、list で一覧表示し、purge で消す 決まった箇所にenvを置き集中管理できる ⭕

Slide 23

Slide 23 text

実現したいこと 実現手法とCLIを考える $ gxenv run foo pip install requests Collecting requests ... 手法 • run サブコマンドを用意し、 env 名・コマンド名・コマンド引数を
 もとにパスを解決して実行 (プロセスの切り替え手法は後述) env と実行したいファイルを明示的に指定して一発で実行できる ⭕ Shebangからでも一発で呼べる ⭕ #!/usr/bin/gxenv run foo python3 print('Hello World!')

Slide 24

Slide 24 text

実現したいこと 実現手法とCLIを考える 手法 • APT の postinst, prerm から create/purge を呼ぶ • systemd の ExecStart から run を呼ぶ APT や systemd といい感じに連携可能 ⭕

Slide 25

Slide 25 text

実現手法とCLIを考える #!/bin/sh gxenv purge miku #!/bin/sh gxenv create miku gxenv run miku pip install /usr/share/miku/requirements.txt ... [Service] ExecStart = /usr/bin/gxenv run miku hatsune_miku --arg1 --arg2 postinst prerm miku.service

Slide 26

Slide 26 text

CLI env を作る ・ 消す ・ 一覧表示する 結果、サブコマンドはこんな感じで行くことに gxenv create, purge, list env の中の実行ファイルのパスを表示 gxenv which $ gxenv create miku $ gxenv list /var/lib/python3/envs/miku $ gxenv purge miku $ gxenv which miku python3 /var/lib/python3/envs/miku/bin/python3

Slide 27

Slide 27 text

CLI env の中の実行ファイルを実行 結果、サブコマンドはこんな感じで行くことに gxenv run $ gxenv run miku python -m site sys.path = [ '/home/user', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/plat-x86_64-linux-gnu', '/usr/lib/python3.7/lib-dynload', '/var/lib/python3/envs/miku/lib/python3.7/site-packages', ] ... #!/usr/bin/gxenv run miku python3 print('Hello World!')

Slide 28

Slide 28 text

実装! venv をラップして env を作る

Slide 29

Slide 29 text

おさらい • PEP 405で提案され、Python 3.3 より導入された 「仮想環境を
 作成する軽量なメカニズム」 • PyPA がホストしていた virtualenv を改良し標準ライブラリに導入 • 標準ライブラリにあるので Python プログラムより呼び出し可能
 (Batteries Included!!) venv とは?

Slide 30

Slide 30 text

venv の API • path: envを作るディレクトリ • system_site_packages: グローバルなパッケージを参照するかどうか • clear: 既にディレクトリがあった場合削除するかどうか • symlinks: envの中に置くインタプリタ等をsymlinkにするかどうか • with_pip: pipをインストールするかどうか • prompt: (optional) activateした時の prompt を指定する venv.create(env_dir, system_site_packages=False,
 clear=False, symlinks=False, with_pip=False, prompt=None) venv には 動作をカスタマイズできる EnvBuilder class と
 簡易に呼び出して env を作るための関数がある モジュール関数

Slide 31

Slide 31 text

gxenv で env を作る実装 gxenv/__init__.py: fmt_env_path() p = pathlib.Path(const.env_base) / env_name ... return str(p) 30 33 env の作成先を venv.create に渡すだけで env ができる!簡単! gxenv/__init__.py: cmd_create() path = fmt_env_path(env_name) ... venv.create( path, system_site_packages=False, clear=False, ↩ symlinks=True, with_pip=True ) 134 139 140 141 ※1 const.env_base = envを置く場所のbase path ※2 env_name = envの名前

Slide 32

Slide 32 text

実装! 実行ファイルをルックアップする

Slide 33

Slide 33 text

実行ファイルの見つけ方 実行可能ファイルを見つけるのに手動での glob やフィルタは必要ない • cmd: 見つけたい実行ファイル名 • mode: 見つけたいファイルのパーミッションマスク • path: 探すPATH, Noneだと os.environ['PATH'] か os.defpath shutil.which(cmd, mode=os.F_OK | os.X_OK, path=None) モジュール関数 >>> import shutil >>> shutil.which('vim') '/usr/local/bin/vim'

Slide 34

Slide 34 text

実行ファイルの見つけ方 env のパスを出して、そのディレクトリに対して which するだけ gxenv/__init__.py: which() env_dir = fmt_env_path(env_name, "bin") ... found = shutil.which(executable_name, path=env_dir) ... return found 226 232 241 gxenv/__init__.py: cmd_which() found = which(env_name, executable_name, ↩ verbose=verbose) ... print(found) 254 260

Slide 35

Slide 35 text

実装! env 内の実行ファイルを
 いい感じに走らせる

Slide 36

Slide 36 text

env 内の実行ファイルをいい感じに走らせる • 別のプログラムを実行するには subprocess を使うことが多い • subprocess は Python の子プロセスとして起動されるため
 プロセスツリーにPythonが鎮座する事になる • gxenv が目的のプログラムを起動した時点で仕事を終えているため邪魔 >>> import subprocess >>> subprocess.run(['vim']) subprocess? tmux \_ /bin/bash \_ python \_ vim ps -xf -o cmd ← いらない

Slide 37

Slide 37 text

ところで Shebang (#!) でよく使われる env コマンドは
 目的のコマンドを起動したあとプロセスツリーから消える $ /usr/bin/env vim tmux \_ /bin/bash \_ vim ps -xf -o cmd Shell ← Shell の直下で動いている! gxenv もこれになりたい…どうやって?

Slide 38

Slide 38 text

env コマンドに学ぶ /usr/bin/env が別のプログラムにすり替わるからくりは
 システムコール execve(2) で実現している execveとは? from manpage execve(2) execve() executes the program pointed to by filename. ... execve() does not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded. execve() は filename で示されたプログラムを実行します。 … execve() が成功したときはreturnしません。呼び出したプロセスの text, data, bss (セクション), そしてスタックは、ロードされたプログラムのもので上書きされます。

Slide 39

Slide 39 text

env コマンドに学ぶ coreutils/src/env.c: main() execvp (argv[optind], &argv[optind]); Source: github.com/coreutils/coreutils/blob/master/src/env.c 943 glibc/posix/execvp.c: execvp() return __execvpe (file, argv, __environ); Source: sourceware.org/git/?p=glibc.git;a=blob;f=posix/execvp.c 26 glibc/posix/execvpe.c: __execvpe_common() return __execve (file, argv, envp); Source: sourceware.org/git/?p=glibc.git;a=blob;f=posix/execvpe.c 190 ・・・ syscallを呼んでいる execvp = 実行ファイルの絶対パスを出してからexecveするlibcの関数 execveに至るまでのコールスタック

Slide 40

Slide 40 text

env 内の実行ファイルをいい感じに走らせる Python から execve(2) を呼ぶのは超簡単! import os os.execv('/usr/local/bin/vim', ['vim']) 実行すると、Python インタプリタは vim に差し替わる os.execve(path, args, env) path: 実行ファイルの絶対パス args: 渡す引数(長さは必ず1以上) env: 新しい環境変数のdict 実行例 API os.execv(path, args) path: 実行ファイルの絶対パス args: 渡す引数(長さは必ず1以上) (本来execveは新しい環境変数の組を与えるが、変更しない場合はexecvが使える)

Slide 41

Slide 41 text

env 内の実行ファイルをいい感じに走らせる ここまでに出てきた 実行ファイルを見つける処理と
 execve で実行する処理を組み合わせると run サブコマンドが実装できる!

Slide 42

Slide 42 text

実装! Shebang から呼び出すには

Slide 43

Slide 43 text

おさらい • #! で始まるテキストファイルの冒頭行のこと • 解釈方法は統一されていないが Unix-like OS なら直接実行できる
 See also: github.com/torvalds/linux/blob/master/fs/binfmt_script.c #!/usr/bin/env python3 print('Hello World!') $ ./helloworld.py
 Hello World! Shebang で直接 env を指定できると activate 要らずで嬉しい! #!/usr/bin/gxenv run foo python3 print('Hello World!') 実行 helloworld.py Shebang とは?

Slide 44

Slide 44 text

Shebang から呼び出すには Shebang に書かれた env の Python を実行するには
 何をしてやる必要がある? #!/usr/bin/gxenv run foo python3 print('Hello World!')

Slide 45

Slide 45 text

動作を観察する Shebang 経由で呼ばれた Python の sys.argv を観察してみる gxenv #!/usr/bin/env python3 import sys print('\n'.join(sys.argv)) run $ ./run gxenv run foo python3 ./run 実行 "gxenv" "run" "foo" "python3" "./run" が渡ってきた #!/usr/bin/env -S gxenv run ↩ foo python3 Linux / macOS 互換には /usr/bin/env -S が有用

Slide 46

Slide 46 text

gxenv での shebang 実装 argparse に流し込んで gxenv に対する引数とそうでない引数を分ける "gxenv" "run" "foo" "python3" "./run" parsed parsed, unknown = parser.parse_known_args() env名 コマンド名 gxenv unknown コマンド引数 あとは 「foo」 という env にある 「python3」 を
 execve して 「./run」を渡す サブコマンド

Slide 47

Slide 47 text

実装! APT と systemd との
 インテグレーション gxenv を使ったアプリケーションの
 Debian パッケージ例

Slide 48

Slide 48 text

インテグレーション例 例 冒頭で出た 「APT の install/remove をフックして env を作る」 と同じ流れ Mikuというdaemon専用のenvを作成・削除する

Slide 49

Slide 49 text

インテグレーション例 #!/bin/sh -e gxenv create miku gxenv run miku pip install -r /path/to/requirements.txt systemctl enable miku postinst deb パッケージがインストールされた後に実行する #!/bin/sh -e
 systemctl disable --now miku gxenv purge miku prerm インストールされたファイルが削除される前に実行する

Slide 50

Slide 50 text

インテグレーション例 miku.service systemd の daemon (service) 定義 [Unit] Description=HatsuneMiku [Service] ExecStart=/usr/bin/gxenv run miku miku Restart=always [Install] WantedBy=multi-user.target

Slide 51

Slide 51 text

まとめ

Slide 52

Slide 52 text

開発目標 こういうのが作れた!! Debian パッケージごとに Python 環境が分離できる ⭕ 決まった箇所にenvを置き集中管理できる ⭕ env と実行したいファイルを明示的に指定して一発で実行できる
 (activateが不要) ⭕ Shebangからでも一発で呼べる ⭕ APT や systemd といい感じに連携可能 ⭕

Slide 53

Slide 53 text

現状の問題点 Numpy のようなヘビーな Python パッケージを複数の env でインストールすると容量を圧迫する ❌

Slide 54

Slide 54 text

gxenv を使う方法 https://pypi.org/project/gxenv/ PyPI にあります。 注意!
 PyPI 版は /usr/bin/gxenv のような実行ファイルを置かないので
 python -m gxenv で CLI を呼ぶ必要があります ♨ 質問があれば Twitter @puhitaku 等でお聞きください