kubectl -o custom-columnsで、配列内の任意のデータを表示したい

kubectlの–outputオプションを使うと、表示するデータとヘッダを自由に指定できる。これを使うと、例えばどのノードがDiskPressureやMemoryPressureのStatusがTrueか?を一覧で見ることができる。

方法としてはこんな感じ。costum-columnsは <header>:<json-path-expr> の形式で、これをカンマで区切って指定する。.status.conditionsは配列なのだけど、フィルタすることも可能で、以下のようにすれば良い。

~ $ k get node -o custom-columns='NAME:.metadata.name,DiskPressure:.status.conditions[?(@.type == "DiskPressure")].message'

これで、Nodeの名前とDiskPressure Conditionのメッセージを一覧で表示することができる。-o jsonpath でフィルタリングしてもいいけど、ヘッダ付きで一覧表示できるとやはり便利。

4Kディスプレイやめました。そして曲面ウルトラワイドディスプレイの道へ

1年と少しの間27インチの4Kディスプレイを使っていて、最近32インチの4Kディスプレイを購入した。しかし、その数週間後に4Kディスプレイを使うのをやめて、34インチウルトラワイド ディスプレイに乗り換えたのでその辺の話を書きます。

27インチ4K

2019年1月に購入して使っていた。悪くはないけど、27インチで4Kは持て余す。スケーリングして使っていた。後、MacBook Pro 13インチモデルだと4KでスケーリングするとGolandのようなJetbrains製IDEが滅茶苦茶重くなる。レンダリング周りが原因らしく、eGPUを繋ぐと快適になるので購入して運用していた。しかし、eGPUは接続が切れるとeGPUを使っていたアプリケーションが全てクラッシュするのが不便。

32インチ4K

2020年4月に購入。どうにも27インチだと厳しいなと思い、画面サイズを広げることにした。とはいえこれもスケーリングして使用。悪くはないが、画面の端をみるのが結構大変で、効率よく使えていないように感じられた。27インチを縦向きにしてデュアルディスプレイ状態にしていたが、これも上の方や下の方はほぼ使えず。

34インチUWQHD

自分のユースケースだと4Kはフル活用できないなということで再度吟味してウルトラワイドディスプレイであるU3419Wを購入。解像度は3440×1440と4Kに比べるとかなり下がるが、横幅が広く曲面になっており画面をフル活用できるだろうという判断。3840×1600(WQHD+)とどちらにするか悩んだが、横幅が広すぎると考えUWQHDにした。今日届いたので設置したのだが、最初からこれにすればよかったなあとしか言えない。幅が広いのでウィンドウを3枚並べても十分使用できる。僕は開発している時IDE、ターミナル、ブラウザの3ウィンドウは表示したいのでちょうどよかった。曲面ディスプレイ、端の方は想像以上に見やすい。解像度は下がりドットピッチ は4Kに比べて広がるので文字などの滑らかさは損なわれるが、プログラミングなどの作業に支障が出るほどではないと感じた。

さらに、4Kでは無くなったのでeGPUがなくてもGolandが快適に使えるのでは?と思い、数時間ほどMacBook ProからUSB Type-Cケーブルで直接繋いでみている。今のところキビキビと動いているように思える。Type-Cの給電も90Wまで対応なので、MacBook Pro 16インチを今後買ったとしても問題なく使えるだろう。

そういうわけで、4Kディスプレイから曲面ウルトラワイドディスプレイに移行して快適という話でした。

MacOSのAlacrittyで複数ウィンドウ表示する

僕はターミナルの画面を複数並べて作業することがあるのだけど、Alacrittyだとそれができないと思っていた。しかし、今日調べてみるとどうやらできるらしい。

https://github.com/alacritty/alacritty/pull/3461

このPRによると、SpawnNewInstanceというアクションを使うと新しいインスタンスを作れる(要は新しいウィンドウが起動してくる)ようだ。なので、Cmd+Nにこのアクションを紐付けるとウィンドウを複数並べることができるというわけ。早速設定して、ほぼ不便な点はなくなって最高。なお、0.5.0でデフォルトキーバインドに追加されるっぽい。

DragonflyのCDN機能を使って効率的なイメージダウンロードを実現する

Dragonflyという、Alibabaが中心となって開発されているイメージ・ファイルのP2Pベース分散配布ツールがあります。今回はDragonflyのCDN機能について調査・検証しました。なお、検証に使ったDragonflyのバージョンはv1.0.0です。

Dragonflyとは何か

Dragonflyが何か、一言で述べるとP2Pベースのイメージやファイルの配布ツールと言えます。大規模な環境において、ファイル転送の効率をあげ、ネットワーク帯域の利用効率を最大化することを目的としています。What Is Dragonfly? によるとアリババでは月に20億回Dragonflyが呼び出され、3.4PBのデータ転送が行われているとのこと。Dragonfly自体は汎用的に作られているが、わかりやすい利用事例としてはコンテナイメージの配布が挙げられます。例えば、Kubernetesのノード台数が増えれば増えるほど、それぞれのノードでイメージのpullが行われ、帯域や効率へ影響が出ます。イメージのサイズが大きくなると、イメージpullのパフォーマンスはさらに悪化する可能性もあります。Dragonflyを使うと単純なDocker利用より57倍のスループットを実現し、レジストリの帯域を最大99.5%節約できると書かれていました。

なぜ必要か

Dragonflyがどのような機能を持つのか、What Is Dragonfly? から紹介します。アリババの内部ではさらに多くの機能があるそうです。

  • P2Pベースのファイル配布
    • ファイル転送にP2Pの技術を使うことで、各ピアが持つ帯域リソースを最大限活用する。また、IDC間の帯域を節約できる。
  • あらゆる種類のコンテナ技術に対して、非侵襲的にサポートする
    • Dragonflyは様々なイメージ配布のためのコンテナ技術をシームレスにサポートする
  • ホストレベルの速度制限
    • wgetやcurlのようなダウンロードが持つタスクレベルの速度制限に加えて、Dragonflyはホスト全体の速度制限を提供する
  • パッシブCDN
    • CDNの機構を備えており、リモートからのダウンロードの繰り返しを回避できる
  • 強力な整合性
    • Dragonflyはユーザーがmd5のようなチェックサムを提供しなくても全てのダウンロードファイルに整合性があることを確認できる
  • ディスクの保護と効率的なIO
    • ディスク容量の事前チェックや同期の遅延、最適な順序でのファイルブロック書き込み、net-read/disk-writeの分離など
  • 高いパフォーマンス
    • SuperNode(Dragonflyのコンポーネントの1つ)は完全に閉制御されていて、データベースや分散キャッシュを必要とせずに高いパフォーマンスでリクエストを処理する
  • 例外の自動隔離
    • Dragonflyは自動的に例外ノード(peerもしくはSuperNode)を隔離し、ダウンロードの安定性を改善する
  • ファイルソースへのプレッシャーがない
    • ソースからダウンロードするSuperNodeはごくわずかで良い
  • 標準的なHTTPヘッダのサポート
    • HTTPヘッダを介した認証情報の送信をサポート
  • Registry Authの効果的な同時実行制御
    • RegistryAuthサービスへのプレッシャーを軽減する
  • シンプルで簡単に使える
    • 必要な設定はほとんどない

なるほど、イメージは掴めてきました。では実際に触って行ってみましょう。今回はCDN機能について主に試します。

一般的なファイルダウンロードの場合

適当なKubernetesクラスターを用意して、Dragonflyをインストールします。helm chartがあるのでこれを使うと比較的楽です。

NAME                                       READY   STATUS    RESTARTS   AGE
d7y-dragonfly-dfclient-2q7jw               1/1     Running   0          3d
d7y-dragonfly-dfclient-4w77g               1/1     Running   0          3d
d7y-dragonfly-dfclient-6hhw4               1/1     Running   0          3d
d7y-dragonfly-dfclient-7xtw6               1/1     Running   0          3d
d7y-dragonfly-dfclient-vpzgb               1/1     Running   0          3d
d7y-dragonfly-dfclient-xxnj5               1/1     Running   0          3d
d7y-dragonfly-supernode-6b96dfc674-wwrls   1/1     Running   0          3d

適当なPodに入って、dfgetコマンドでファイルダウンロードしてCDNが動作しているのかを確かめてみます。

$ k exec -it d7y-dragonfly-dfclient-vpzgb bash
bash-4.4#
bash-4.4#
bash-4.4# time /opt/dragonfly/df-client/dfget --url https://dl.k8s.io/v1.18.1/kubernetes-server-linux-amd64.tar.gz --output ./kubernetes-server-linux-amd64.tar.gz --node d7y-drago
nfly-supernode --verbose
--2020-04-20 03:31:06--  https://dl.k8s.io/v1.18.1/kubernetes-server-linux-amd64.tar.gz
dfget version:1.0.0
workspace:/root/.small-dragonfly
sign:29-1587353466.555
client:10.100.0.156 connected to node:d7y-dragonfly-supernode:8002
start download by dragonfly...
download SUCCESS cost:42.585s length:363231097 reason:0

real    0m42.754s
user    0m3.072s
sys     0m6.552s
bash-4.4#
bash-4.4#
bash-4.4# rm ./kubernetes-server-linux-amd64.tar.gz
bash-4.4# time /opt/dragonfly/df-client/dfget --url https://dl.k8s.io/v1.18.1/kubernetes-server-linux-amd64.tar.gz --output ./kubernetes-server-linux-amd64.tar.gz --node d7y-dragonfly-supernode --verbose
--2020-04-20 03:31:56--  https://dl.k8s.io/v1.18.1/kubernetes-server-linux-amd64.tar.gz
dfget version:1.0.0
workspace:/root/.small-dragonfly
sign:45-1587353516.929
client:10.100.0.156 connected to node:d7y-dragonfly-supernode:8002
start download by dragonfly...
download SUCCESS cost:19.322s length:363231097 reason:0

real    0m19.405s
user    0m1.976s
sys     0m1.000s

1回目と2回目でダウンロードにかかった時間がかなり違うことが確認できました。dfgetコマンドで手軽に扱えるのは便利です。

アーキテクチャとしてはこのようになっています。supernodeとdfgetの2コンポーネントが使われて、dfgetを使うとsupernode経由でファイルをダウンロードします。また、dfgetはP2Pクライアント(ピア)として動き、ピア間でのブロック転送も行われます。supernodeはCDNとして働き、各ピア間のブロック転送のスケジューリングなどを行います。

コンテナイメージpullの場合

次に、コンテナイメージのpullについて試します。なお、コンテナランタイムとしてはdockerではなくcontainerdを使っています。

dfdaemonの設定はこのような感じ。dfdaemonはデフォルトでindex.docker.ioをレジストリミラーとして扱うようになっています。また、proxiesでURLにblobs/sha256.*が含まれている場合はdfgetを使ってリクエストする、という設定にしました。

dfget_flags:
- --verbose
- -f
- Expires&OSSAccessKeyId&Signature
proxies:
- regx: blobs/sha256.*
  use_https: true
node:
  - 10.233.60.241:8002

containerdにregistry mirrorの設定をします。docker.ioのミラーエンドポイントとしてdfdaemonを指定します。

    [plugins.cri.registry]
      [plugins.cri.registry.mirrors]
        [plugins.cri.registry.mirrors."docker.io"]
          endpoint = ["http://127.0.0.1:65001"]

これで準備はOK。イメージをpullしてみます。初回と2回目で約7.3秒の差があ離ました。centosイメージなのでそもそもそんなに大きくないのですが、大きなイメージを扱うときには役に立つであろうことがわかります。

$ time sudo crictl pull centos
Image is up to date for sha256:470671670cac686c7cf0081e0b37da2e9f4f768ddc5f6a26102ccd1c6954c1ee

real    0m23.898s
user    0m0.028s
sys     0m0.020s
$ sudo crictl rmi centos
Deleted: docker.io/library/centos:latest
$ time sudo crictl pull centos
Image is up to date for sha256:470671670cac686c7cf0081e0b37da2e9f4f768ddc5f6a26102ccd1c6954c1ee

real    0m16.698s
user    0m0.032s
sys     0m0.024s

アーキテクチャはこのようになっていて、supernodeとdfgetに加えて、dfdaemonが使われます。dfdaemonはdockerのようなコンテナランタイムからのpushやpullリクエストをインターセプトし、必要に応じてdfgetで処理します。

プライベートレジストリからのイメージをpullする

前節ではDockerHubからのイメージpullをDragonfly経由で行いました。しかし、場合によってはプライベートレジストリからのイメージpullをDragonfly経由で行いたいのではないでしょうか(僕がやりたい)。これを行う方法は2つあります。一つ目は、dfdaemonをHTTP Proxyとして扱う方法。もう一つは、dfdaemonをregistry mirrorとして扱う方法です。

HTTP Proxyとして扱う

Use Dfdaemon as HTTP Proxy for Docker Daemon に方法が記載されています。Dockerはregistry-mirrorsフラグを使ったプライベートレジストリの設定に対応していないため、この方法を使う必要があります。しかしdfdaemonをdockerで起動していると、docker再起動時にdfdaemonが止まり、dockerがdfdaemonに繋がらず起動しませんでした。systemdなどを使って起動すれば問題ないかと思います。

registry mirrorとして扱う

dfdaemonをHTTP Proxyとして使うのは若干扱いにくいと感じたため、別の策として検証しました。containerdはプライベートレジストリをregistry mirrorとして設定できるため、HTTP Proxyの設定がなくても動作するはずです。

dfdaemonの設定はこのようにします。dfdaemon自身がレジストリミラーとして参照するURLを変更します。

registry_mirror:
  remote: https://your.private.registry
dfget_flags:
- --verbose
- -f
- Expires&OSSAccessKeyId&Signature
proxies:
- regx: blobs/sha256.*
  use_https: true
verbose: true
node:
  - 10.233.60.241:8002

containerdのレジストリ設定はこのようにします。containerd、レジストリの認証情報もtomlにまとめて設定できて便利です。

    [plugins.cri.registry]
      [plugins.cri.registry.auths]
        [plugins.cri.registry.auths."http://127.0.0.1:65001"]
          username = "username"
          password = "password"
          auth = "XXXXXXXXXXXXXXXXXXXXXXXX"
      [plugins.cri.registry.mirrors]
        [plugins.cri.registry.mirrors."your.private.registry"]
          endpoint = ["http://127.0.0.1:65001"]

これで、イメージのダウンロード時にdfdaemonはdfgetを用いてsupernodeへリクエストします。1回目と2回目でpullの時間が変わるかどうかみてみましょう。

$ time sudo crictl pull your.private.registry/image:0.0.2
Image is up to date for sha256:4358591c9919667bf6a8baca78c2e85f1ffe82700ca81e2550a63c532e49c142

real    1m30.891s
user    0m0.040s
sys     0m0.036s
$
$ sudo crictl rmi your.private.registry/image:0.0.2
Deleted: your.private.registry/image:0.0.2
$ time sudo crictl pull your.private.registry/image:0.0.2
Image is up to date for sha256:4358591c9919667bf6a8baca78c2e85f1ffe82700ca81e2550a63c532e49c142

real    1m31.800s
user    0m0.040s
sys     0m0.024s

あれ、ほとんど変わりませんね… Dragonflyは帯域制御などの機能もあるので、その辺りの設定を変えてみました。今回変えたのは以下。

  • dfdaemon
    • ratelimit (dfgetのlocallimitにセットされる。ダウンロード時のネットワーク帯域の上限を指定)
  • supernode
    • systemReservedBandwidth

設定を変更したところ、初回のpullは1m58s、2回目のpullは59sと変化が感じられました(初回は遅くなってるじゃんという)。しかしcontainerd単体でpullすると40sくらいで終わるので、もう少し調べたいところです。構成要素が複数あり、その設定がどこに影響するのかを把握するのが難しいなと感じました。特にdfdaemonは設定と内部の構造体が分かれているので、追いかけるのが大変です。

まとめ

DragonflyのCDN機能について、一般的なファイルダウンロードとコンテナイメージPullについて調査と検証を行いました。大規模なクラスターでイメージのPullが帯域を圧迫していたり、巨大なイメージのPullに時間がかかって困っているような場合は試してみるといいかもしれません。目玉であろうP2P機能については今回は試さなかったので、今後試していきたいですね。また、複数のレジストリについてDragonflyでミラーすることができるのかについても検証しようと考えています(やりたい)。

オマケ:元々はJavaで書かれていたらしい

DragonflyはGoで書かれています。しかし、ドキュメントを読んでいると、Java関係の記述が割と出てくる。なぜかなと思ったら、元々はJavaで書かれていたのがGoで書き直されたようです。0.4.0で完全にGoになったとのことです。普段よく読み書きしているのがGoなので、同じ感覚で扱えるのはありがたいです。

kubeletが使うコンテナランタイムをdockerからcontainerdに変更する

kubeletが使うコンテナランタイムはこれまでdockerを使っていたのだが、別のものを試してみようということでcontainerdに変更してみた。

containerdをインストールする

containerdはaptでインストールできる(パッケージ: containerd)。また、containerdが使うLow Level Runtimeであるruncも同時にインストールされる。インストールした後、以下のコマンドを実行して設定のデフォルト値を /etc/containerd/config.toml に書き込み、systemctl start containerd で起動する。

$ containerd config default

kubeletからcontainerdを使う

kubeletの引数に以下を追加して再起動することで、containerdを使うようになる。

 --container-runtime=remote
 --container-runtime-endpoint=/run/containerd/containerd.sock

CRIのおかげで、これだけでランタイムを切り替えることができてとても便利。

ノード上で動いているコンテナ一覧を見るには

これまではdocker psを実行してそのノード上で動いているコンテナの一覧を表示していた。containerdの場合はどうすればいいのかというと、2つ方法がある。

まずはcontainerdに付属しているctrコマンド。namespaceフラグでk8s.ioを指定することで、kubeletによって作成されたコンテナの一覧を見ることができる。

$ sudo ctr --namespace k8s.io containers list
CONTAINER                                                           IMAGE                                                                                                    RUNTIME
24ff8b4bb5d6cdfdc5311aa44716a53fa7cd578da76b125e063660e8459ea96f    k8s.gcr.io/metrics-server-amd64:v0.3.1                                                                   io.containerd.runtime.v1.linux

しかし、コンテナのIDとイメージ、ランタイムしか見えずトラブルシュートに使うには物足りない。そこで、もう1つの方法としてcrictlを使うことができる。

https://github.com/kubernetes-sigs/cri-tools/blob/master/docs/crictl.md

crictlはCRIに互換性のあるコンテナランタイム向けのCLIツールである。crictl psでコンテナ一覧を見ることができる。表示項目はdocker psに近く、使いやすいだろう。また、docker psコマンドでコンテナ一覧を見るとコンテナ名が長くなり読みにくいが、crictlだと素朴に名前が表示されるので視認性も高い。しかし、pauseコンテナは表示されないのでその点は注意(見えないことで困ることがあるのかはよく分からない)。

$ sudo crictl ps
CONTAINER ID        IMAGE               CREATED             STATE               NAME                       ATTEMPT             POD ID
24ff8b4bb5d6c       61a0c90da56e7       22 hours ago        Running             metrics-server             0                   68dfed1214a0a

crictlはexecやlogsなど、ノード内でコンテナを見るときに使うコマンドも備えているので基本的にはこれを使い、必要に応じてctrコマンドを使うのが良さそう。なお、runcにもlistコマンドがありコンテナ一覧を表示できそうだったが何も見えなかった。

containerdをkubernetesから使ってみたい方、参考になれば幸いです。

reference