低レイヤー系大学院生のブログ
「車輪の再実装」って言葉が好き(実践はできてない)

bibtexでurlパッケージを使ったらusepackageしてなくて引用が?になっていた話(メモ)

タイトルの通りです。 例えば.bibファイルで以下の様にurlパッケージを使っていた場合、

@misc{dockerhub,
  title = {Docker Hub},
  note  = {\url{https://hub.docker.com/}}
}

.texファイル中で以下のようにusepackageしなくて引用が「?」になりました。

\usepackage{url}

overleafでやってたから1コマンドずつ叩いて確認するわけにも行かないし、デバッグは完全にエスパーでした。

paramikoでclientをcloseせずにCtrl+Cを実現する

paramikoとは

paramikoは、SSHの為のPythonライブラリです。 paramikoを使うことで、認証情報の管理、接続、そしてリモートでのコマンドの実行等を行うことができます。

例えば以下のようにすることで、リモートでコマンドを実行できます

from paramiko import SSHClient

client = SSHClient()
client.load_system_host_keys()
client.connect('ssh.example.com')
stdin, stdout, stderr = client.exec_command('sleep 1000') # この実行自体はすぐに返る

細かい使い方は公式ドキュメント等を参考にしてください。

paramikoを用いたリモートプロセスの実行中断方法

さて、上記様なコマンドを途中で中断、終了するためにはどうすれば良いでしょうか? まず、最も簡単なのは、clientをcloseすることです。

しかし、何らかの方法でclientをcloseしたくない場合、以下のようにしてCtrl+cを実現できます 以下の2つが要点になるのでそれぞれ見ていきます。

  • 疑似端末の取得
  • Ctrl+C(相当の値\x03)の送信

疑似端末の取得

疑似端末を取得(割当)することで、通常ターミナルを使って作業するときのようにプロセスを振る舞わせる事ができます。 こうすることで、Ctrl+C(相当の値)を受け付けるようになります。

具体的には exec_command 時に get_pty=True を付与することでできます。

stdin, stdout, stderr = client.exec_command('sleep 1000', get_pty=True)

Ctrl+C(相当の値\x03)の送信

以下のようにすれば可能です。

print('\x03', file=stdin, end='')

より低レベルなクラスを用いた場合

上記の例では SSHClient クラスを用いた例を示しましたが、 その内部で使われているChannel クラスを用いた場合も要点は同じです。

ただし、具体的には以下のような手順を踏む事になります。

chan.get_pty() # オプションとしてではなく、独立したコマンドとしてptyを取得
chan.exec_command(...)
中略
chan.send('\x03') # ファイルオブジェクトに対してではなく、直接データ送信

コンテナランタイムなしでDockerHubのイメージをマウントする

この記事は、東京大学 品川研究室 Advent Calendar 2020及びDocker Advent Calendar 2020の2日目の記事として書かれました。

Docker Advent Calendar のネタですが、Dockerは使わないという若干本末転倒気味な小ネタです。 (あまり埋まってない様子だったので、これでも許されるはず...)

閑話休題、僕はコンテナイメージを研究対象としているとしているのですが、時々コンテナランタイムなしでコンテナイメージをマウントしたくなることがあります。

moby プロジェクトで公開されている download-frozen-image-v2.sh というシェルスクリプトを使うと、 docker image save で出力されるtarを展開した形で、コンテナイメージをDockerHubから取得する事ができますが、これだと若干不便です。

というのも、この形式でダウンロードできるのは、それぞれのレイヤーやコンフィグ、マニフェスト等をまとめたもで、直接ファイルを触ることができないのです。

マニフェストの中身を元に、各レイヤー(tarball)を展開し、mount コマンドを使って overlayfs としてマウントすればやりたいことはできるのですが、面倒です。

というわけで以下のようなpythonスクリプトを書いてみました。

gist.github.com

使い方はこんな感じです↓

$ wget https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh
$ chmod +x download-frozen-image-v2.sh
$ wget https://gist.githubusercontent.com/progrunner17/5337c83a799dc5baa9fd4bd6f5ce1170/raw/5e6dbddc2da32bf7136a13f29553f53e2f799625/stack_layers.py
$ chmod +x stack_layers.py

$ ./download-frozen-image-v2.sh nginx_dir nginx:latest
$ cd nginx_dir
$ ../stack_layers.py mnt
image nginx:latest was mounted on /path/to/workdir/nginx_dir/mnt
$ ls ./mnt/docker-entrypoint.d                                                                                                                                                                                             
10-listen-on-ipv6-by-default.sh*  20-envsubst-on-templates.sh*

スクリプトを読むと分かることですが、--workdir--upperdir を指定すると書き込み可能になったり、 -vオプションを付けると、内部で実行しているコマンドが表示されたりします。

mountコマンドかoverlayfsの仕様なのか?lowerdirが1つでかつupperdir等が設定されていないとマウントに失敗したり、 procfs等がマウントされていなかったりと不便はありますが、少ない依存で動くので重宝しています。

download-frozen-image-v2.sh なしで、pythonスクリプトだけで完結させたいなーとは思っていますが、 それもそれで若干の手間なので、しばらくこのままキメラ的に使う状況は続きそう...

Ubuntu18.04でpython3のデフォルトを3.6から3.8に変更したらaptが壊れた話

まとめ

Ubuntu18.04で python3 パッケージをインストールするとデフォルトでは python3.6 が入ります。
ただ諸事情で新しいバージョンを使いたくなったので、python3.8 パッケージをインストールして、 update-alternativesを使って python3 コマンドを使った際に呼び出されるバージョンを変更してみたところ、後述のようにaptが壊れてしまいました。
デフォルト以外バージョンのpythonを使いたいときは、おおちゃくせずにpyenvとかを使いましょうという教訓。

詳細

前述のようにpython3のバージョンを変えて使っていたら、以下のように「apt_pkgモジュールが無いよ。」とaptに怒られました。

$ sudo apt update
中略
Traceback (most recent call last):                   
  File "/usr/lib/cnf-update-db", line 8, in <module> 
    from CommandNotFound.db.creator import DbCreator  
  File "/usr/lib/python3/dist-packages/CommandNotFound/db/creator.py", line 11, in <module>
    import apt_pkg                                    
ModuleNotFoundError: No module named 'apt_pkg'
後略

というわけで、 python3-apt をインストールしたら以下のようにエラーが...

$ sudo apt install python3-apt
中略
Setting up python3-apt (1.6.5ubuntu0.3) ...
/usr/lib/python3.8/subprocess.py:838: RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used
  self.stdin = io.open(p2cwrite, 'wb', bufsize)
後略

どうやら標準パッケージである io の挙動が変化したらしい?

雑にpyenvを使うとコンパイルに時間がかかって面倒だったので update-alternativesを使いましたが、 おとなしくpyenvを使うことにして問題を回避しました。

vagrant-libvirtで作ったVMを、dockerのネットワークにつなげる。

...というタイトルのものの、実際は既存のlinuxブリッジにlibvirt仮想マシンをつなげる方法です。

1. つなげたいdockerネットワークのブリッジ名を調べる.

特に設定をしていなければdocker0のはずですが、
以下の様にして調べても大丈夫だと思います。

docker network inspect <ネットワーク名>

2. libvirtにおける仮想マシンの名前を取得する

以下の様にすれば取得できます. ただし、defaultの部分はVagrantfileで定義(していれば)した名前になるはず。

 virsh domname $(cat .vagrant/machines/default/libvirt/id)

3. virshで仮想マシンXMLを編集する

virsh edit 取得した名前

で、仮想マシンの定義ファイルを開き

<interface type='bridge'>
<source bridge='dockeのブリッジ名'/>
...
</interface>

とすると、仮想マシンに新たなインターフェースが追加されます。 (もしかすると、VMの再起動が必要かも?)

4. 仮想マシン内部でアドレスやルーティングを追加する。

前の手順でつないだインターフェースにはアドレスが割り振られ無いので、 仮想マシン内部で自分でIPアドレスを設定します。(172.17.0.100等)

デフォルトルートもコンテナのネットワークと同じ様にすればいい感じで使えそう。

こうすれば仮想マシン内部からpingがコンテナに届きます。

起動済みのdockerコンテナのネットワーク名前空間に入る方法

まず、あらかじめ入りたいコンテナのIDか名前を取得しておき、以下の様な手順で入れる。

pid=$(docker inspect <コンテナのIDか名前> --format '{{.State.Pid}}')
sudo mkdir -p /var/run/netns
sudo mount --bind /proc/${pid}/ns/net /var/run/netns/<名前(任意)>
sudo ip netns exec <名前>  bash

基本的にホストのソフトをそのまま使えるので、ipやping等をそのまま使えて便利。

ただし、自分でunmountし無い限り、docker上でコンテナを消しても名前空間が残り続けるので注意。

ちなみに、残っている場合は、

sudo ip netns list

とすると、自分で指定した名前が見えるはず。

オンライン上のmanページまとめ(随時追記)

www.mankier.com デフォルトでついているページ内検索が優秀だったり、デザインが一番モダンな感じで個人的に一番好き。対応しているページも多い気がする。 ただSEOの問題か、google検索してこのサイトに飛ぶことは少ない。

linux.die.net ちょっとマニアックなページをgoogle で検索すると出てくるイメージ

man7.org おそらく一番オーソドックスなページ。

manpages.ubuntu.com Ubuntuのmanページ。 おそらく、各バージョンに対応したページが出てくるはず。
たまに、新しいmanを参照していて実際の挙動と違う...みたいなことがあると、このページで対応したバージョンを見る。