「車輪の再実装」って言葉が好き(実践はできてない)

Weekly Log 2 -- Kubernetesについて今知っていること・知らないこと --

はじめに

Kubernetesをやるぞ!と言いながら、実はこれまでまともにk8sを使ったことがない。 とはいえ、大学院でコンテナ関連の研究をしていたり、バイトでOCIランタイムの改良をしていたりしたので、 偏ってはいるもののそれなりに知識はある。

今後効率よく勉強を進めるためにも、 あえて知らないことを明示して心理的安全性を高めるためにも? 僕がkubernetesについて今知っていること、知らないことを整理してみようと思う。

ところで、この「知っていること、知らないこと」というまとめ方は、下記記事をちょっとだけ意識していたりする。

overreacted.io

知っていること、知らないこと

概要
  • O k8sはコンテナオーケストレーターだということは知っている
  • X Docker Swarm, Apache Mesos等と比べてなぜk8sが勝ち残ったかは説明できない
  • O"マニフェストファイル"というyaml/jsonを書いて宣言的にリソースを管理することは知っている
  • O各リソースは対応するコントローラに管理される
    • X カスタムコントローラは作ったことがない
Pod関連
  • O マニフェストにはPodを定義できることは知っている
    • O Pod単位でデプロイ等が管理されることは知っている*1
    • Pod作成時には特殊なコンテナを作るらしい事は知っているが、どんな名前でどんな役割かは忘れた・知らない
    • X Podとコンテナの厳密な区別ができていない(少なくともnetnsは共通というぐらいの認識)
  • Podを管理するリソースとしてReplicaSet, StatefulSet, DaemonSet, Deploymentが定義できる事は知っているが、他にあるかどうかは知らない
    • O ReplicaSetはPodのレプリカ数を維持するために使える事は知っている
    • O StatefulSetは順番にPodを一つづつ作る事は知っている
    • X StatefulSetはDB等に使うイメージだが、何がどう嬉しいかは知らない
    • O DaemonSetは各ノードに1つずつPodを配置するという認識
    • X Deploymentが何かは忘れた
NW
  • O PodをつなぐネットワークはCNIに準拠したプラグインによって作られる事は知っている。
    • CNIは多分L3担当?
    • O CNIプラグインには、デフォルトの例の他にflannelとかcalicoとかがあるらしいことは知っている
    • X CNIプラグインを意識して動かしたことはない
    • CNIはRPCやRESTのAPIというよりはコマンドのインターフェースだったはず
  • O Ingress(L7)とService(L4)というリソースが提供されていることは知っている
    • X Ingress、Serviceの細かい機能は知らない
    • X Ingress、Serviceの実現方法も知らない
Storage
  • O docker -v の様なボリュームは CSI準拠のプラグインによって作られることは知っている
    • X PVやPVCの詳細は知らない
    • CSIプラグインの例はtopolvmぐらいしか知らない
    • X CSIの実装方法は何も知らない
  • X Rookについては名前ぐらいしか知らない
Image
  • X k8s内部でイメージビルドする仕組み、方法を知らない
  • X 内部レジストリ的なものがあるかもしれないと予想(つまり何も知らない)
その他リソース
  • O シークレットを提供する仕組みを持っている事は知っている
    • X シークレットの仕組みや実体を知らない
  • X その他のリソースに何があるか知らない
    • 内部DNS的なものはありそう
拡張
  • OCNI, CSI, ランタイム, 以外にも選択・拡張を許す仕組みがあるらしい事は知っている
  • Oカスタムコントローラというワードを聞いたことがある
  • X 上記以外の拡張の存在や、カスタムコントローラ含めどんなインターフェースかは知らない
アーキテクチャ、高位ランタイム
  • O k8s環境を準備・構築する方法として、クラウドサービス, Ranchar, k8s the hard way, minikube, kind, k3s等複数の選択肢がある事は知っている。
    • X 上記各実装の差はあまり知らない
  • O 高可用な構成を取ることができるらしいことは知っている
    • X どの部分をどの様に冗長化できるのかわかっていない
  • O 全体の構造は大まかにkubelet(CLI) <--> コントロールプレーン <-->>> kubelet <->高位ランタイム ->低位ランタイム(コンテナ)の様な構造になっている事は知っている
  • X コントロールプレーンの実体がよくわかっていない
  • X kubeletの役割をあまり知らない
    • O ノード単位の管理はしているというのは分かる
    • X 1ノードに複数kubeletが置かれる場合はあるのか?
    • X 1kubelet辺り複数のCRIランタイムが置かれる場合はあるのか?
    • X なぜマスターとコンテナランタイムを直接通信させなかったのか?
サンドボックス、低位ランタイム
  • O containerdは多少ソースコードを読んだ
    • O containerdは1バイナリ中に複数のサービスを詰め込んだ構成となっている o
      • O サービスは部分的にgRPC経由で外部に切り出す事もできる
    • O containerd-shim経由でOCIランタイム・コンテナを制御する
  • O runcはLinux名前空間、cgroups、chroot/pivot_root、seccomp等で実現
  • O gvisorは、ptrace or kvmシステムコールをフックしてエミュレートすることでサンドボックスを実現
  • O Firecrackerは軽量なQEMU相当物だということは知っている
    • O kata-containerがQEMUの代わりにfirecrackerを使えるという認識はある
    • X firecracker単体で使えるか、高位ランタイムとのインターフェースはどうなってるか等は知らない。
    • X もちろん細かい実装も知らない
  • X shimの周りの事はあまりわかっていない
その他
  • X サービスメッシュやIstioについてはほとんど知らない
  • X HelmやKustomize等は、YAML補助ツール?についてもその詳細をほとんど知らない
  • X Ops系の知識もない

最後に

こうして列挙してみると、知らないことがかなり多いなぁという気持ちになる。 unknown unknown(知らないことすら知らないこと)は更にその数倍多いはず さらに言うと知っているつもりで知らないこともきっとある気がする。

全て知り尽くすのは不可能なので、方向性をある程度定める必要はあるものの、 まずは深く考えず、known known, known unknownを増やして少しずつk8sに関するunkonwn unknownを減らしていきたい。

余談: known unknown辺りを端的に表せる日本語が欲しい

*1:「コンテナ単位ではなく」と書いている文献もみるが、何をしてコンテナとするかは微妙なのでこの表現は好きではないのでお茶を濁す

Weekly Log 1 -- 社会人になったので --

4月から社会人になった。

ということで、せっかくなので誓約みたいな物を立ててみることにする。 「楽しく生きる!」っていう目標?は変わらないだろうけれど、 とはいえ生きていれば色々と状況は変わるので、とりあえず今年の誓いを立ててみる。

2021年の誓い

  • 毎週ブログを書く
  • 毎月1冊本を読む
  • CKA, CKAD, CKSを取る
  • 毎日オンライン英会話をする

「毎週ブログを書く」ということ

学生のうちからtwitterとかで凄そうなエンジニアを見ると、 大抵ブログ記事とか、勉強会での話とかが面白い。 自分はそういう話術的なところが弱いので、 表現力の向上の為にまずは手っ取り早いということで、ブログを書くことにする。 ゆくゆくは対外発表とかもできたらいいなと思う。

「毎月1冊本を読む」ということ

アウトプットだけだとバランスが取れないので、インプット量を増やす。 というのが建前で、実のところは積読を解消したいと言うのが本音。 せっかくなので、メモがてら書評をブログに書こうとも思う。

「CKA, CKAD, CKSを取る」ということ

ほとんど想定通りではあるけれど、どうやらKubernetesのチームに配属されるようなので Kunernetes関連のめぼしい資格を全部取ることにした。 6月半ばから配属らしいので、できればそれまでにCKAは取りたいところ。 正直なところ、Kubernetesはあんまり知らないのだけど、 Docker等ランタイム周りはそれなりに知っているし何とかなるでしょ。 と思って頑張ってみる。

「毎日オンライン英会話をする」ということ

配属予定のチームのかなりの割合の方が外国籍らしく、業務で英語が必須らしい... というわけで、頑張って英語力を身に付けないといけない。 技術的な文章はある程度読み慣れているし、最悪文章であればdeeplとか最近の優秀な翻訳機にかければ良いので、 会話重視で頑張ってみる。

最後に

達成できたらどうするかはこれから考える。

ともあれ、楽しく仕事できたらいいなと思う。

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がコンテナに届きます。