DropboxからGoogle Driveに乗り換えた

以前から Mac 複数台、Linux 複数台、iPhoneDropboxを使ってきたのだけど、最近マシンを交換したら3台制限に引っかかって同期できなくなってしまったので、Google One を契約してるのもあり無難なところでGoogle Driveに移行することにした。

方針

  • Googleドライブ直下に Sync というフォルダを作って、このフォルダだけ同期する (以前の ~/Dropbox/ 的な感じ)

セットアップ

macOSはまぁ普通にダウンロードしてインストールして設定すればいいんだけど、Googleドライブ直下のファイルの同期を抑止する方法がわからんかった。

LinuxGUIは必要ないので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以上はビルドが失敗します。

今回は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 を介さないで $py36execve して異常終了していました。詳細は確認してませんが、どっかで 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)を埋め込むことができます。これらは readelfpatchelf で確認することができます。

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 はこれらの情報を変更することができるので、$py36interpreterと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 に乗り換えました。

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
    • macOS Mojave (10.14.3)
    • synergyc 1.3.1 (数年前に昔のmacOSで自ビルドしたやつ)

で問題なく使えてたんだけど、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で表現する作例

PythonClick

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 なども使ったことがなかったので、「こう書いたほうがいいでし!」とかあったらぜひ プルリ お待ちしています!