gunicornをsupervisorで制御しつつhot deployできるようにする
タイトルの通り、gunicornなWebアプリをsupervisorで制御しつつhot deployできるようにしたメモです。
登場人物
- supervisor v3.3.1
- gunicorn v19.9.0
- start_server v0.34
- unicornherder v0.1.0
- envdir v0.7 の standalone版
- ちなみに checkinstall で雑にdeb化
問題
- gunicornのhot deployを利用する場合、直接はsupervisorの制御下に置けない
- gunicornはhot deployの仕組みを持っている
- 流れ
- gunicornのmasterプロセスにUSR2シグナルを送ると、新しいmasterプロセスを産む
- つまり新masterの親プロセスは旧master
- 旧masterと旧workerは生き続けているので、ほどよい頃合いで旧masterにQUITシグナルなどを送って死んでもらう
- 新masterの親が死んだので、init (PID 1) が新masterの親となる
- gunicornのmasterプロセスにUSR2シグナルを送ると、新しいmasterプロセスを産む
- というわけで、supervisor視点だとgunicorn(旧master)が死んだのでまた立ち上げようとするが、新masterはinitを親として起動しているので競合しちゃう次第
結論
- supervisor + unicornherder + gunicorn
案1: start_server配下でgunicornを立ちあげ、hot deployはstart_serverの機能を使う
## /etc/supervisor/conf.d/oreno.conf [program:oreno] command=/home/hirose31/oreno/script/start directory=/home/hirose31/oreno ...
## /home/hirose31/oreno/script/start #!/bin/bash export PYENV_ROOT="/home/hirose31/oreno/pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" exec start_server \ --port 127.0.0.1:1919 \ --signal-on-term=TERM \ --signal-on-hup=QUIT \ --kill-old-delay=10 \ -- \ /home/hirose31/oreno/script/start-gunicorn
## /home/hirose31/oreno/script/start-gunicorn #!/bin/bash if [[ -n "$SERVER_STARTER_PORT" ]]; then GUNICORN_FD= for l in $(echo $SERVER_STARTER_PORT | tr ';' ' '); do GUNICORN_FD="$GUNICORN_FD $(echo $l | cut -d= -f2)" done export GUNICORN_FD=$(echo $GUNICORN_FD | tr ' ' ',') fi exec /usr/local/bin/envdir /home/hirose31/oreno/env \ gunicorn -w 3 --reuse-port --capture-output oreno:application
これで supervisorctl signal HUP oreno
で start_server に HUP を送ると、start_serverが新たにgunicornを起動して、10秒後に古いgunicornにQUITを送って殺してくれる。はず。
つまり、(gunicornの機能ではなく)start_serverの機能でhot deployが実現できる。はず。
start_server に対応するにはいくつかの要件が必要ですが、
- Server::Starterに対応するとはどういうことか - limitusus’s diary
- 「Server::Starterに対応するとはどういうことか」の補足 - sonots:blog
- Server::Starter で Unicorn を起動する場合の Slow Restart - sonots:blog
Unicornの UNICORN_FD
と同様のものが gunicorn には GUNICORN_FD
という環境変数名で存在するので、gunicornもstart_serverに対応できる。はず。
そう。はず、なのであった!
gunicornの このpull request で、GUNICORN_FD
を評価するところのがこのような条件付きになり、
### gunicorn/arbiter.py: start elif self.master_pid: fds = [] for fd in os.environ.pop('GUNICORN_FD').split(','): fds.append(int(fd))
初っ端の起動時は master_pid
は 0 なので GUNICORN_FD
は評価されないのであった!!
条件を elif os.environ.get('GUNICORN_FD', ''):
にすれば期待通り動くことは確認できたんだけど、できればgunicornに手を入れないで使いたいのでこの案はとりあえずボツに…
将来、外部由来の GUNICORN_FD
を評価するようになったら、gunicornは--config
オプションでpythonの文法で設定が書かれたファイルを指定することができるので、そこで SERVER_STARTER_PORT
を参照して GUNICORN_FD
をセットする処理を書いたり、pre_fork, post_fork hookを使って、Server::Starter で Unicorn を起動する場合の Slow Restart - sonots:blog と同じことも実現できるんじゃないかと思います。
余談ですが、start_server
の server-prog
の指定は、ラップしたスクリプトのみ、オプション指定無しにするのをお勧めします。
例えば start_server -- /my/app -p 8
で起動していて -p 16
に変えたいときは、start_server
自体の再起動が必要(つまりhot deployできない)になってしまいます。これがラップしていて start_server -- /my/app_wrapper
で起動している場合は、app_wrapper
の中を書き換えて start_server
に HUP を送ればOKになります。
start_server -- envdir ./env sh -c 'exec /my/app -p ${MAX_PROCESS:-8}'
と書けばHUPで引数の値を(envdir経由で)変えることは可能ですが、やや技巧的だしオプションの種類の増減には対応できないので、素直にラッパースクリプトを用意した方がよいでしょう。
案2: unicornherder配下でgunicornを立ちあげ、hot deployはgunicornの機能を使う
「gunicorn supervisor」でググるとみなさんお困りのようで、対応として rainbow-saddle と unicornherder がよく挙げられています。
rainbow-saddleは「cannot actively maintain it」だそうなので、unicornherderを試してみます。
## /etc/supervisor/conf.d/oreno.conf [program:oreno] command=/home/hirose31/oreno/script/start directory=/home/hirose31/oreno ...
## /home/hirose31/oreno/script/start #!/bin/bash export PYENV_ROOT="/home/hirose31/oreno/pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" exec unicornherder -o 10 -p /var/run/oreno.pid -- \ -w 3 --reuse-port --capture-output oreno:application
unicornherderの流れはこんな感じです。
- gunicornを
--pid FILE --daemon
つきで起動する- masterのPIDは所定のファイルに書き出される
- unicornherderの
--pidfile PATH
でPIDファイルを指定しないと、cwdにPIDファイルが作られるので注意
- unicornherderの
- gunicornはunicornherderの元を離れ、initの子プロセスとなる
- masterのPIDは所定のファイルに書き出される
- unicornherderはHUPを受けると、gunicornのmasterにUSR2を送る
- gunicornのhot deployが開始され、新masterが誕生する
- gunicornによってPIDファイルが更新される
- unicornherderはPIDファイルを2秒間隔でポーリングして、masterの変化を検知する
- masterの変化を検知すると、
- overlap (デフォルト120秒) の時間だけsleepする
- この間は新旧workerが混在する
- 後、旧masterを殺す
- masterにWINCH, QUITシグナルを送る
- QUITを受けたworkerは処理中であっても即、死ぬ
- overlap (デフォルト120秒) の時間だけsleepする
unicornherderはgunicornを子プロセスとしておかず、gunicornはinitの子プロセスとなる点に最初「オッ!」っと思いましたが、これは古き良きdaemonしぐさですからまぁ違和感はないです。
これで一応、問題は解決できたんですが、unicornherderはgunicornコマンドでのgunicornの起動しかサポートしていません。
gunicornの起動方法、Pythonの各種WAFとの連携方法は gunicornコマンド以外にもあります。例えば、Bottle だと bottle.run()
に server='gunicorn'
を与えると、gunicornが起動します。
その場合は、unicornherderは--gunicorn-bin
, -g
オプションでgunicornのフルパスを指定できるのでこれを使います。
## /home/hirose31/oreno/script/start #!/bin/bash export PYENV_ROOT="/home/hirose31/oreno/pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" exec unicornherder -o 10 -p /var/run/oreno.pid \ -g /home/hirose31/oreno/script/oreno-gunicorn \ --
oreno-gunicorn
はお好みの方法でgunicornなプロセスを立ち上げる処理を書いたスクリプトです。
## /home/hirose31/oreno/script/oreno-gunicorn #!/bin/bash exec /home/hirose31/oreno/bin/oreno-python /home/hirose31/oreno/omaeno.py
注意点は以下の通りです。
- unicornherderの
-p
オプションで指定したPIDファイルと、gunicornが書き出すPIDファイルを一致させる - gunicornはdaemonモードで起動するようにする
おわりに
unicornherderを使えば目的が達成できることがわかりましたが、個人的に安心と信頼と実績のある start_server が使いたい今日この頃です。
DropboxからGoogle Driveに乗り換えた
以前から Mac 複数台、Linux 複数台、iPhoneでDropboxを使ってきたのだけど、最近マシンを交換したら3台制限に引っかかって同期できなくなってしまったので、Google One を契約してるのもあり無難なところでGoogle Driveに移行することにした。
方針
- Googleドライブ直下に
Sync
というフォルダを作って、このフォルダだけ同期する (以前の~/Dropbox/
的な感じ)
セットアップ
macOSはまぁ普通にダウンロードしてインストールして設定すればいいんだけど、Googleドライブ直下のファイルの同期を抑止する方法がわからんかった。
LinuxはGUIは必要ないのでGrive2を使うことにした。
0.5.1-dev(2019-07-12現在未リリース)から .griveignore
ファイルで同期する対象の指定ができるようになったのでこれを使う。
ビルドとインストールはドキュメントの通り(ソースはリリースページのじゃなくて、git clone
するかmaster.zip
を使う)ににして、初回実行はこんな感じ。
$ grive --version grive version 0.5.1-dev Jul 11 2019 18:10:47 $ mkdir ~/GoogleDrive/ && cd ~/GoogleDrive/ $ cat << EOF > .griveignore * !Sync EOF $ grive -a
これで Sync
だけが同期される。
griveは同期が終わると終了して常駐はしないので、定期的に、更新があったときに同期が実行されるようにする。
今回はホームディレクトリ下のディレクトリ GoogleDrive
を基点ディレクトリとしたので、一般ユーザーで次のように仕込めばよい。
systemctl --user enable grive-timer@$(systemd-escape GoogleDrive).timer systemctl --user start grive-timer@$(systemd-escape GoogleDrive).timer systemctl --user enable grive-changes@$(systemd-escape GoogleDrive).service systemctl --user start grive-changes@$(systemd-escape GoogleDrive).service
ログは journalctl
で確認できる。
journalctl -f --user -u grive-timer@GoogleDrive.timer journalctl -f --user -u grive-changes@GoogleDrive.service
というわけで今までありがとうDropbox!
今回のおハマり
sshで入った先で systemctl --user
がエラーになるときは、
$ systemctl --user Failed to connect to bus: No such file or directory
sshd_configでUsePAM yesになっているか確認しましょう。
pam経由からのsystemd-logindからのログインじゃないのでセッションが作られません。
あわせて読みたい
Ubuntu 18.04 LTSからAMIを作る前にやらなければならないこと
AWS Marketplace の Ubuntu 18.04 LTS 20190514 からインスタンスを立てて、そのインスタンスからAMIを作る前には次のことをやらなければなりません。
ifupdownを削除する
sudo apt purge ifupdown
ifupdownパッケージがインストールされている状態で作ったAMIで起動すると、起動はするもののNICが見つけられずに一切のネットワークアクセスができないインスタンスになってしまいます。
/etc/netplan/50-cloud-init.yaml
は削除する必要はなく、ifupdown がインストールされていなければ、新規インスタンスの MAC アドレスにあわせて適切な設定ファイルが生成されました。
深追いしてませんが、ifupdown がインストールされていると、ifupdownに代わる新しいネットワーク管理システムの netplan が NIC を見つけられない/見つけようとしないんじゃないかと思います。
ちなみに ifupdown に依存しているパッケージはまだ結構あって( apt-cache rdepends ifupdown
調べ)、例えば ifenslave をインストールすると ifupdown もインストールされたりします。
なお、一度起動してしまえば ifupdown をインストールしても問題ありません。が、その後で /etc/netplan/50-cloud-init.yaml
を削除して再起動すると、NIC がみつけられないインスタンスになってしまい詰むので注意してください。言い換えると、適切な /etc/netplan/50-cloud-init.yaml
が存在するならば、ifupdown をインストールしても問題ありません。
/etc/machine-id を0バイトのファイルにする
: | sudo tee /etc/machine-id
/etc/machine-id
にはそのホスト固有のIDが格納されています。
/etc/machine-id
がある状態で作ったAMIで起動すると、同じIDを持ったインスタンスが作られてしまいます。
なので /etc/machine-id
を初期状態にする必要があるのですが、先に示したように、0バイトのファイルにしなければなりません。
sudo rm -f /etc/machine-id
でファイルを消したり、sudo bash -c 'echo > /etc/machine-id'
で改行1バイトのみファイルにした場合は、起動時に再生成されないので注意してください。
CentOS 6でPython 3でTensorFlowを使う方法、もしくはdynamic linkerとshared objectの差し替え
Python 3
CentOS 6のopensslのバージョン(1.0.1e)の関係で、Python 3.7以上はビルドが失敗します。
- https://docs.python.org/ja/3/whatsnew/3.7.html
The improved host name check requires a libssl implementation compatible with OpenSSL 1.0.2 or 1.1. Consequently, OpenSSL 0.9.8 and 1.0.1 are no longer supported
今回は3.7にこだわりはないので、pyenvでPython 3.6の最新の3.6.8をインストールして、pipでtensorflowをインストールします。詳細は割愛ググってね。
glibc, stdc++
インストールに成功しても、使おうとするとエラーが出ます。
$ python3.6 -c 'import tensorflow; print("hello")' Traceback (most recent call last): ... ImportError: /lib64/libc.so.6: version `GLIBC_2.16' not found (required by /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so) ... $ ldd /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so |& grep 'not found' | awk '{print $4}' `GLIBC_2.16' `GLIBC_2.14' `GLIBC_2.17' `GLIBC_2.15' `GLIBCXX_3.4.15' `GLIBCXX_3.4.19' `GLIBCXX_3.4.14' `CXXABI_1.3.7' `GLIBCXX_3.4.17' `GLIBCXX_3.4.18' `CXXABI_1.3.5' `GLIBC_2.14' `GLIBC_2.16' `GLIBC_2.17' `GLIBCXX_3.4.14' `GLIBCXX_3.4.18' `CXXABI_1.3.5' `GLIBCXX_3.4.15' `GLIBCXX_3.4.19' `GLIBCXX_3.4.17' `CXXABI_1.3.7'
どようやらglibc 2.17とlibstdc++ 3.4.19が必要そうです。
さすがにシステムのglibcを置き換えるのは怖いので、別のディレクトリに展開して今回のpython3.6はそこのshared objectを使うようにします。
glibcとlibstdc++は、https://pkgs.org/ で探してCentOS 7のを拝借します。
$ cd ~/tmp/ $ wget \ http://mirror.centos.org/centos/7/updates/x86_64/Packages/glibc-2.17-260.el7_6.5.x86_64.rpm \ http://mirror.centos.org/centos/7/updates/x86_64/Packages/glibc-devel-2.17-260.el7_6.5.x86_64.rpm \ http://mirror.centos.org/centos/7/os/x86_64/Packages/libstdc++-4.8.5-36.el7.x86_64.rpm \ http://mirror.centos.org/centos/7/os/x86_64/Packages/libstdc++-devel-4.8.5-36.el7.x86_64.rpm \ ;
~/lib/
の下に展開します。
$ cd ~/lib/ $ for p in ~/tmp/*.rpm; do echo $p; rpm2cpio $p | cpio -idv; done $ ls -F etc/ lib64/ sbin/ usr/ var/
以下、パス指定が長くなるので簡略化のため変数に入れときます。
py36="$HOME/.pyenv/versions/3.6.8/bin/python3.6" pywrap_tensorflow_internal="$HOME/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so" my_ld="$HOME/lib/lib64/ld-linux-x86-64.so.2" my_libs="$HOME/lib/usr/lib64:$HOME/lib/lib64"
ld-linux.so
はコマンドとしても実行できて、 --library-path
でdynamic loadするshared objectのパスを指定できます。環境変数 LD_LIBRARY_PATH
でも同様のことができますが、作用範囲が子プロセスまで及ぶかどうかという違いがあります。
ld-linux.so
経由で python3.6
を起動して、先程エラーになったコードを実行してみます。
$ $my_ld --library-path $my_libs $py36 -c 'import tensorflow; print("hello")' hello
今度は正常終了しました!やったネ!!
child process
早速、最終的に走らせたいスクリプトを実行してみると…
$ $my_ld --library-path $my_libs $py36 oreno.py * Serving Flask app "server" (lazy loading) * Environment: production ... Traceback (most recent call last): ... ImportError: /lib64/libc.so.6: version `GLIBC_2.16' not found (required by /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so) ...
失敗しました。
エラーメッセージからすると、システムのglibcをロードしてしまっているようです。strace -f -s 1000 $my_ld ...
で確認してみると、後半で $my_ld
を介さないで $py36
を execve
して異常終了していました。詳細は確認してませんが、どっかで fork
してるんじゃないかと思います。
子プロセスにも新しいglibcの場所を教えてあげればよいので、試しに LD_LIBRARY_PATH
をセットして実行してみると…
$ env LD_LIBRARY_PATH=$my_libs $my_ld --library-path $my_libs $py36 oreno.py ... /home/ore/.pyenv/versions/3.6.8/bin/python3.6: relocation error: /home/ore/lib/lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference
失敗しました。
たぶん、システムの /lib64/ld-linux-x86-64.so.2
で新しいglibcをロードしようとして失敗しているんだと思います。
もし、成功したとしても、LD_LIBRARY_PATH
は作用範囲が大きいので、他の方法があれば避けたい手段です。
patchelf
Linuxの実行ファイル形式のELFには、dynamic linker (interpreter) とライブラリのサーチパス(runpath)を埋め込むことができます。これらは readelf
や patchelf
で確認することができます。
CentOS 6用の patchelf
はEPELにあります。
$ readelf -a /usr/bin/mailq | grep -e interpreter -e runpath [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] 0x000000000000001d (RUNPATH) Library runpath: [/usr/lib/postfix] $ patchelf --print-interpreter --print-rpath /usr/bin/mailq /lib64/ld-linux-x86-64.so.2 /usr/lib/postfix
patchelf
はこれらの情報を変更することができるので、$py36
のinterpreterとrunpathを変更してしまえば、 $my_ld --library-path $my_libs
の前置が不要になるというわけです。
やってみましょう。
$ cp -p $py36 ${py36}.bak $ patchelf --set-interpreter $my_ld --set-rpath $my_libs $py36
さっそく、$my_ld
の前置なしで実行してみると…
$ $py36 -c 'import tensorflow; print("hello")' Traceback (most recent call last): ... ImportError: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so)
エラーが出ました。が、これは想定内です。
$py36
は期待したとおり新しいglibcをロードできたのですが、_pywrap_tensorflow_internal.so
がlibstdc++をロードしようとして新しいのが見つけられずエラーになっているわけです。(glibcは既に $py36
が新しいのをロード済みなので大丈夫)
なので、_pywrap_tensorflow_internal.so
もrunpathを書き換えてしまいましょう。
_pywrap_tensorflow_internal.so
は既にrunpathが設定されているので、それに追加する感じでセットします。(純粋なshared objectなのでinterpreterの変更の必要はありません)
$ORIGIN
が展開されないようにシングルクォートするのを忘れないでください。
$ patchelf --print-rpath $pywrap_tensorflow_internal $ORIGIN/../../_solib_k8/_U_S_Stensorflow_Spython_C_Upywrap_Utensorflow_Uinternal.so___Utensorflow:$ORIGIN/:$ORIGIN/.. $ cp -p $pywrap_tensorflow_internal ${pywrap_tensorflow_internal}.bak $ patchelf --set-rpath '$ORIGIN/../../_solib_k8/_U_S_Stensorflow_Spython_C_Upywrap_Utensorflow_Uinternal.so___Utensorflow:$ORIGIN/:$ORIGIN/..'":$my_libs" $pywrap_tensorflow_internal
さて、
$ $py36 -c 'import tensorflow; print("hello")' hello $ $py36 oreno.py * Serving Flask app "server" (lazy loading) * Environment: production ... INFO:werkzeug: * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) ...
ようやく正常に起動できました!!!
が、socket.getaddrinfo()
が失敗して名前が引けず…
まとめ
ld-linux.so
はコマンドとしても実行できて、--library-path
でdynamic loadするshared objectのパスを指定できる(環境変数LD_LIBRARY_PATH
に比べ作用範囲を限定的にできる)- ELFには、dynamic linker (interpreter) とライブラリのサーチパス(runpath)を埋め込むことができ、
patchelf
コマンドで変更できる
ところで、
debootstrap
で bionic の環境を作ってまるっとCentOS 6にコピーして chroot
した方がよかったかもしれん… 試してないけど動くんじゃないかと…
kernelが古くて残念、ダメでした!
# chroot ./bionic /bin/bash FATAL: kernel too old # file bionic/bin/bash bionic/bin/bash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.2.0, stripped
メインマシンをUbuntu 14.04から18.04にしてみた
Ubuntu 14.04 (trusty) が 2019-04-30 で EOL になる
Ubuntu 14.04 LTS Trusty Tahr Extended Security Maintenance | Ubuntu blog
のと、day jobの方でも18.04 (bionic)を使う予定なので、メインマシンのtrustyをbionicにしてみた。
Ubuntu
に書いてある通り、do-release-upgrade 実行するだけ。楽ちん。一足飛びにはいけないので、trusty→xenial、xenial→bionic した。
アップグレード自体は特に問題なかった。
グラフィカルログインの無効化
漢は黙ってコンソールログイン派なのでlightdmとかgdmとかそういうのを無効化する。
systemctl set-default multi-user.target
dig
デフォルトで +edns するようになったようで、一部のDNSサーバーがFORMERRを返して名前が引けないので無効化した。
echo '+noedns' > ~/.digrc
Emacs
なんとなく勢いで24.4から26.1にしてみた。
ファイルが意図しないwindowで開く
例えば、1つのframeを上下2つのwindowに分割してて、上のwindowでfind-fileすると下のwindowで開く、とか謎挙動しだした。
原因は popwin の古い設定
(setq display-buffer-function 'popwin:display-buffer)
が残っていたせいで、これを削除したらOKになった。
さようならsense-region、こんにちはsense-expand-region
sense-region.elをロードすると警告が出るようになった。
Warning (bytecomp): ‘interactive-p’ is an obsolete function (as of 23.2); use ‘called-interactively-p’ instead. Warning (bytecomp): ‘mapcar’ called for effect; use ‘mapc’ or ‘dolist’ instead [16 times]
動作はするけど、sense-region.el自体、配布元にアクセスできなくなって久しいので、結局、sense-expand-region.el に乗り換えました。
- https://emacs.g.hatena.ne.jp/k1LoW/20120319/1332169099
- https://github.com/k1LoW/emacs-sense-expand-region
sense-region.elと同じように C-SPC に割当。
(global-set-key (kbd "C-SPC") 'sense-expand-region)
最初 emacs-jp Slack の人たちに相談したら、矩形選択は標準の rectangle-mark-mode を、選択リージョンの拡張は expand-region を教えてもらいました。(あざます!!)
expand-region は最初の発動でいきなり範囲選択になる(sense-regionはset-mark-command)のに慣れなくて、ぐぐったら sense-expand-region.el を見つけて、なんとこれで矩形選択もできるので乗り換えた次第。
synergy
今まで
- server
- Ubuntu trusty
- synergys 1.4.12
- client
で問題なく使えてたんだけど、bionicのsynergys 1.8.8 にしたらキーボードとマウスは共有されるんだけど、クリップボードが共有されない。これが思いの外、ストレスマックスボンバー。
1.8.8のバイナリ配布
https://sourceforge.net/projects/synergy-stable-builds/
から macOS 版をダウンロードして試したら、マウスとクリップボードは共有されるけど、なんとキーボードが共有されない!!
server, client共に最新の1.10.1(今回の件で買った!)にしても、キーボードが動かない。
trustyで動いてた1.4.12をbionicでビルドしたところ、たぶん libcrypto++ のシグネチャが違うかなんかでビルドできず。
他にもdebootstrapでtrustyの環境作ってchroot実行してみた(SEGV!)り、他のバージョンでも試したりいろいろやったけどダメで、結局、trustyからsynergysとlibcrypto++.so.9.0.0をコピーしてきて、
#!/bin/sh exec env LD_LIBRARY_PATH=/usr/local/app/synergy-1.4.12/lib \ /usr/local/app/synergy-1.4.12/bin/synergys "$@"
な感じで立ちあげて無事、全部共有できて一旦めでたしめでたし。
でも、macOSの方の1.3.1は、この前MacBookを乗り換えたときにソースコード消しちゃったようだし、ソースがあってもCarbonのヘッダーファイルないとコンパイルできないと思うのでもう最新のXcodeではビルドできないので、本当はせっかく買った 1.10 で動くのがいいんだけど。。。
PythonのClickのサブコマンドをsymlinkで表現する作例
command [--debug] foo [--force] [--yes]
なのを実装したとして、これと同じのを command-foo というsymbolic linkを作って
command-foo [--debug] [--force] [--yes]
でも実行できるようにしたい作例。
command-foo --force --debug
と実行するとエラーになるのがイマイチ。。。
他にいい方法があったら教えてください!!
REST API フレームワーク Connexion のススメとその作例
Python の Connexion というフレームワークとそのサンプルアプリケーションを書いたのでその紹介です。
Connexion は「API (spec) First」を謳うフレームワークです。
「API First」とは、 ご存じ The Twelve-Factor App の追補として 2016 年に Pivotal 社が提唱したガイドライン Beyond the Twelve-Factor App の中のひとつです。
簡単にいうと、コードを書き始める前にまずAPIの仕様を定める。たとえば昨今のマルチデバイス対応のような複数チームで開発を進めるような現場で、お互いこのAPI仕様を拠り所とすることで、他方の開発プロセスに干渉することなく、円滑に開発を進めることができる、というものです。 (たぶん)
Connexion はこの API First を実践するのを助けてくれるプロダクトで、OpenAPI (過去に Swagger spec と呼ばれていたものです)で記述した仕様を軸として次のことが実現できます。
- API 利用者向けのドキュメントを生成できる
- 仕様に記述した入力パラメータの validation ルール(必須パラメータのあり/なしとか、正規表現で記述した値の形式チェックとか)を自動で実施してくれるので、適用コードを一切書く必要がない
現職で API サーバーを 3 つ実装して、今は Connexion で 4 つめを実装しているのですが、ドキュメントと実装の乖離は頭の痛い問題でした。具体的にいうと、値の形式チェックのルールをちょっと変えたんだけどドキュメントに反映しわすれていたとか、初期に書いたドキュメントの一部が実装から漏れていたとか、みなさんも容易に想像できるんじゃないでしょうか。
また、エンドポイントの情報(HTTPのメソッドとパス)をドキュメントと controller を呼ぶ router の両方に書かないといけないのが常々無駄だと思っていたのですが、Connexion が仕様を読んで所定の controller のメソッドを呼んでくれるので router を記述する必要がなくなったのもよい点の1つです。
Connexion を使うことでこういった問題から解放されたので本当に助かっています。
ほかにも API の動作をちょっと試したりデモするのに便利な Web UI のダッシュボードがついていたり、OAuth 2 のトークンベースの認証に対応していたりといろいろと特徴があるので詳しくは Connexion のサイトをみてみてください。
そんなこんなでここしばらく Connexion の試し書きをしていました。
などのサンプル実装を大いに参考にさせてもらったのですが、もうちょっと実践的な作例を書いてみました。
- データストアは RDBMS で、 SQLAlchemy ORM の declarative mapping を使用
- Connexion (の内部の Flask )との連携は Flask-Alchemy を使用
- ORM のシリアライズ(dict化)には sqlathanor を使用
- サンプルとして 2 つのテーブル(リソース)を定義して、one-to-many のリレーション
- 検索条件の処理は、所定の SQL っぽい記法(JSON)でリクエストをすると、いい感じに SQLAlchemy の query オブジェクトを返してくれる簡易 query builder を書いた
- pytest と WebTest を使ってテストも用意した
まだまだ Python は B- ぐらいウデマエの上に SQLAlchemy なども使ったことがなかったので、「こう書いたほうがいいでし!」とかあったらぜひ プルリ お待ちしています!