Ubuntu 18.04 で OS 起動時の apt update と unattended-upgrade を抑制する方法
時間のない人向けのまとめ
sudo systemctl edit apt-daily.timer sudo systemctl edit apt-daily-upgrade.timer
どちらも次の内容で保存します。
[Timer] Persistent=false
もしくは、直接ファイルを編集して反映してもよいです。
sudo install -d -o root -g root -m 755 /etc/systemd/system/apt-daily.timer.d cat <<EOF | sudo tee /etc/systemd/system/apt-daily.timer.d/override.conf [Timer] Persistent=false EOF cp -pR /etc/systemd/system/apt-daily.timer.d/ /etc/systemd/system/apt-daily-upgrade.timer.d/ sudo systemctl daemon-reload
何が問題なのか?
Ubuntu 18.04、少なくとも EC2 用の Ubuntu cloud image の 20200131 版 (18.04.4) では、毎日 6:00 頃に apt update
と unattended-upgrade
(セキュリティ関連の更新パッケージのインストール) が行われる (詳しくは後述) のですが、その時間帯に電源がオフで稼働していなかった場合は、次の OS 起動時に実行される設定になっています。
「その時間帯に電源がオフで稼働していなかった」は、AMI からインスタンスを起動したときも同じ状況になるので、AMI が古ければ古いほど更新パッケージが多くなり、起動直後に負荷が高まったり、しばらく apt install
できない (unattended-upgrade
がロックを獲得しているので) 時間が続いたりします。
その結果、次のような問題が発生します。
- インスタンス起動後に自動的にプロビジョニングを行うようにしている運用の場合、プロビジョニング (の過程のパッケージのインストール) が完遂するまでの時間が安定しなかったり、場合によってはタイムアウトしてエラー終了してしまう
特にオートスケーリングの場合は、一刻も早くインスタンスを投入したいので深刻な問題になります。
またもし、AMI 採取用のインスタンスから日次で AMI を作る運用の場合は、パッケージはほぼ最新であることが期待できます。
というわけで、 OS 起動時の更新処理を実行されないようにした、というお話でした。
時間のある人向けの詳細
まず定期的に更新処理が実施される仕組みの説明から。
次の 2 つの systemd の timer ユニットがトリガーとなります。
apt-daily.timer
apt-daily-upgrade.timer
内容を確認すると、
$ systemctl cat apt-daily.timer # /lib/systemd/system/apt-daily.timer [Unit] Description=Daily apt download activities [Timer] OnCalendar=*-*-* 6,18:00 RandomizedDelaySec=12h Persistent=true [Install] WantedBy=timers.target $ systemctl cat apt-daily-upgrade.timer # /lib/systemd/system/apt-daily-upgrade.timer [Unit] Description=Daily apt upgrade and clean activities After=apt-daily.timer [Timer] OnCalendar=*-*-* 6:00 RandomizedDelaySec=60m Persistent=true [Install] WantedBy=timers.target
となっており、 apt-daily.timer
は毎日 6:00 と 18:00 頃、 apt-daily-upgrade.timer
は毎日 6:00 頃に発火するのがわかります。
発火すると、対応する systemd の service ユニットが実行されます。
systemctl cat
で確認すると、実行されるコマンドライン (ExecStart
) が確認できます。
apt-daily.service
ExecStart=/usr/lib/apt/apt.systemd.daily update
apt-daily-upgrade.service
ExecStart=/usr/lib/apt/apt.systemd.daily install
ざっくり言うと、
/usr/lib/apt/apt.systemd.daily update
は
- apt-get update
- apt-get --download-only dist-upgrade
- unattended-upgrade --download-only
/usr/lib/apt/apt.systemd.daily install
は
- unattended-upgrade
を行うためのもので、APT の設定で個別に実施する/しないを制御することができます。
デフォルトでの次のような設定になっていて、
$ apt-config dump | grep Periodic APT::Periodic ""; APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Download-Upgradeable-Packages "0"; APT::Periodic::AutocleanInterval "0"; APT::Periodic::Unattended-Upgrade "1";
apt-get update
実施する--download-only
系は実施しないunattended-upgrade
実施する
という挙動になります。
ここまでが定期的に実行される仕組みの説明で、次からは主題の OS 起動時の更新処理の仕組みについてです。
timer ユニットの定義をみると Persistent=true
というのがあり、これがキモです。
systemd.timer(5) から引用すると
Persistent=
Takes a boolean argument. If true, the time when the service unit was last triggered is stored on disk. When the timer is activated, the service unit is triggered immediately if it would have been triggered at least once during the time when the timer was inactive. This is useful to catch up on missed runs of the service when the system was powered down. Note that this setting only has an effect on timers configured with OnCalendar=. Defaults to false.
とのことなので、冒頭の方法で Persistent=false
と上書き設定したわけです。
ちなみに
定期的なのも含め一切の自動的な更新処理をオフにしたい場合は、 /usr/lib/apt/apt.systemd.daily
に
# check if the user really wants to do something AutoAptEnable=1 # default is yes eval $(apt-config shell AutoAptEnable APT::Periodic::Enable) if [ $AutoAptEnable -eq 0 ]; then exit 0 fi
という処理があるので
echo 'APT::Periodic::Enable "0";' | sudo tee /etc/apt/apt.conf.d/99disable-periodic
とかするといいんじゃないかと思います。
あと /etc/cron.daily/apt-compat
という crontab があるんですが、
if [ -d /run/systemd/system ]; then exit 0 fi
となってるので systemd な環境では無いのと同じです。
MySQL 5.7でクライアントプログラムがCPUを食いつぶす件
同根のバグレポートが散見されますが、ここ数ヶ月、状況をみるに修正される見込みがなさそうなので記録しておきます。
事象
MySQL 5.7 の libmysqlclient (libmysqlclient.so.20) を使用しているプロセスが、無限ループに突入して CPU (user%) を食いつぶし続ける。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 28150 hirose31 20 0 32488 6080 5492 R 93.8 0.3 0:16.70 mysql
発現条件
MySQLが提供しているパッケージのMySQL 5.7.25, 5.7.26, 5.7.27 で確認。
- Ubuntu 18.10, mysql-community-client 5.7.25-1ubuntu18.10
- Ubuntu 18.04, mysql-community-client 5.7.25-1ubuntu18.04
- Ubuntu 18.04, mysql-community-client 5.7.26-1ubuntu18.04
- Ubuntu 18.04, mysql-community-client 5.7.27-1ubuntu18.04
- Ubuntu 16.04, mysql-community-client 5.7.25-1ubuntu16.04
- CentOS 7.6, mysql-community-client-5.7.25-1.el7.x86_64
- Fedora 29, mysql-community-client-5.7.25-1.fc29.x86_64
- Oracle Linux 7.6, mysql-community-client-5.7.25-1.el7.x86_64
再現手順
- mysql_real_connect() でサーバーに接続する
- なんらかのシグナルを受信する
- mysqld サーバー側からコネクションを切断する
- 例えば以下のようにして切断する
- wait-timeout を超過する
- mysqld を停止する
- mysql_real_query() もしくは mysql_ping() が呼ばれる
- 無限ループに突入して CPU (user%) を食いつぶす
なお、MySQL 5.6 (libmysqlclient 18) や MySQL 8.0 (libmysqlclient 21) では発生しない。原因は yaSSL にあるのだけど、MySQL 8.0 は yaSSL ではなく OpenSSL を使っているので。
再現コード
回避方法
MySQL サーバーの my.cnf の [mysqld]
グループに skip-ssl
と書く。
# /etc/my.cnf [mysqld] ... skip-ssl ...
追記 2019-11-22
SSL の実装はソースツリー内にある yaSSL と OpenSSL とで選択できたのですが、MySQL 5.6.46, 5.7.28 から yaSSL が削除され OpenSSL のみとなりました。
当然、バイナリで配布されているパッケージも OpenSSL を使うようにビルドされています。そこで 5.7.28 で試したところ、問題が再現しないことが確認できました。
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 で動くのがいいんだけど。。。