お家インフラ日記 (3)- RTX830でお家ネットワーク構築

お家インフラ日記 (2)- Raspbian Stretch Liteの再インストール - etogenのブログの続きです。

今回はYAMAHAルーターと"ノン"インテリジェントスイッチを使ったラズパイのネットワークの構築をしたいと思います。大きく分けてこの3つのことをします。

で、しょうもないですけど、こんなネットワークを構築します(図のスイッチに繋がってる3つのサーバーはラズパイを意味しています)

f:id:etogen:20190226185931p:plain

YAMAHAルーターはまともに操作したことないので手探り感満載でやっていきます。。。

使うもの

ラズパイ3個

sshdを動かしたり色々設定したけど、まだ固定IPが設定されていません。

RTX830

わたしのへっぽこネットワークを構築するために使います。

ビジュアルが可愛いいからって理由で、お年玉で衝動買いしてしまった(本当に深い理由がない)

2019年2月26日現在ではYAMAHAから発売されているルーターの中では最新のモデル。RTX810よりスループットとか向上してたり、USBminiのシリアルポートが追加されたみたいです。詳しくは自分で調べてね。。。

スイッチングハブ(EHC-G05PA-B-K)

足りないポートを増やすために一応購入しました。

エレコムのお安いスイッチです。SNMPには非対応。本当はインテリジェントなやつを買って色々いじりたい思っていたんですけど、RTX830を買ったりしたせいで本当にお金がなくなっちゃった。お金が入ったらNETGEARのカッコイイのを買おうかな。

ルーターの初期設定

WebGUIを使って設定(インターネットへのルーティングなどの設定)

VPNルータ「RTX1210」で最小限のLANを構築してみようを参考にして進めました。これは初心者にも優しいWebGUIからの設定方法について書かれた記事です。

わたしはYAMAHAルーターを全然いじったことないので、コンソールからwanやフィルタリングの設定するのはちょっと恐かった。ここ参考にして設定したらLANポートに接続した機器がルーターを通じてインターネットにアクセスできるようになるはずです。

コンソールからの設定

甘えは最初だけにしてあとの設定はコンソールからやりたいと思いました。とりあえずこれらのことを設定します。

  • 文字化けの解決
  • 自動的にログアウトするまでの時間を設定(無効化)
  • sshdの起動
  • telnetdの停止

シリアルコンソールからルーターに接続する(telnetで接続するならこれやる必要なし)

まずルーターのCONSOLEのポートとMacをケーブルで繋いだら、/devの下にtty.usbserial-XXXcu.usbserial-XXXってファイルができたことを確認します。

$ ls /dev/tty.usb*
/dev/tty.usbserial-AH069FV9
$ ls /dev/cu.usb*
/dev/cu.usbserial-AH069FV9

そしたらcu -l デバイス名で接続。Connectedって表示されてその後何も表示されないけど、Enterするとルーターの認証用のパスワードを聞かれます。初期設定ではパスワードは設定されてないのでそのままEnterすればログインできます。

ちなみにcu~.と入力すると終了できます。

$ sudo cu -l /dev/cu.usbserial-AH069FV9

telnetルーターに接続する(シリアルコンソールで接続するならこれやる必要なし)

YAMAHAルーターはデフォルトでtelnetで接続できるようになっています。

$ telnet 192.168.100.1

こっちもパスワードの入力はなしでおっけー。

文字化けの解決

YAMAHAルーターはShift-JISなので、ターミナルがUTF-8だったりしたらこんな感じに文字化けします。

> show 
?G???[: ?R?}???h?????m?F???Ă???????
>

ターミナルのテキストエンコーディングを変更したくなかったので、ルーターエンコーディングの設定を変更

> console character ja.utf8
> show 
エラー: コマンド名を確認してください
>

ルーターの諸々の設定はsaveを実行しないと不揮発性メモリに保存されないようになっています。何かやったらsaveを実行するのを忘れないようにしましょう。

また、saveはadministratorじゃないと実行できません。administratorはLinuxでいうところのrootユーザーみたいなやつです。administratorになるにはadministratorというコマンドを実行します。

> administrator
Password:
# save

自動的にログアウトするまでの時間を無効化

セキュリティ的にはこれは設定しない方がいいんですけど、ログインしたままルーターのマニュアルを読んで調べ物をしちゃうと思うし、そのたびに勝手にログアウトさせられるのはムカつくので設定しました。

ログインタイマーはデフォルトでは300秒に設定されています。無制限にする場合は以下のコマンド

# login timer clear
# save

ただしsshなどで接続している場合はclearの場合も300秒でログアウトさせられるみたいです。

sshdの起動

正直これ設定する必要あるかはわからないんですけど、一応。

RTX830にはデフォルトで無名ユーザーとadministratorの2つのユーザーが存在するんですが、どちらもsshではログインできないようになっているので、新しくssh接続用のユーザーを作成しないといけません。

# login user etogen 
New_Password: 
New_Password: 
# sshd host key generate     
Generating public/private dsa key pair ...
|*******
Generating public/private rsa key pair ...
|*******
# sshd service on
# save 

これでユーザーの作成とsshdの設定が完了。実際に接続する時はこんな感じです。

$ ssh etogen@192.168.100.1

telnetdの停止

talnetd service offって実行するだけ。 

# telnetd service off
# save 

この後、telnetで接続を試みてうまくいかなかったら成功

$ telnet 192.168.100.1
Trying 192.168.100.1...
telnet: connect to address 192.168.100.1: Connection refused
telnet: Unable to connect to remote host
$



ラズパイのネットワークの構築

RTX830のネットワークを分割してラズパイ3台が接続するVLANを作ります。ラズパイには固定IPも設定します。

とりあえず以下の作業をする前にスイッチにラズパイ3台を接続し、さらにlan1.1(RTX830の一番左側にあるLANポート)とスイッチを接続します。

lanの状態を確認

現在ルーターで設定されたlanは2つあります。

# show status lan?
? lan1 lan2 
# 

lan1はYAMAHAルーターでデフォルト(?)で設定してあるLANで、192.168.100.1/24です。lan2はWAN側のネットワークで、これは外部ネットワークと通信する時のデフォルトゲートウェイとして設定されています。ちなみにlan2は「WebGUIを使って設定(インターネットへのルーティングなどの設定)」で設定したものです。

show ip routeでルーティングテーブルを確認できます。

# show ip route 
宛先ネットワーク    ゲートウェイ     インタフェース  種別  付加情報
default             192.168.3.1      LAN2(DHCP)    static  
192.168.3.0/24      192.168.3.16           LAN2  implicit  
192.168.100.0/24    192.168.100.1          LAN1  implicit  
# 

RTX830はLANポートが4つあるんですが全てのインタフェースがlan1の同一のインタフェースとして機能するようになっています。lan1.1からlan1.4までが192.168.100.1のIPアドレスが割り当てられている状態...って言っていいのかな?

さて、今lan1に接続している3台のラズパイなんですが、これらにはIPアドレスが自動で割り当てられています。つまりはYAMAHAルーターDHCPが動いてるってわけです。show status dhcpDHCPのステータスを表示するとLANのどのマシンにどのIPが割り当てられているかがわかります。

# show status dhcp
DHCPスコープ番号: 1
   ネットワークアドレス: 192.168.100.0
               割り当て中アドレス: 192.168.100.2
          (タイプ) クライアントID: (01) XX XX XX XX XX XX
                         ホスト名: mbp2016
                     リース残時間: 2日 23時間 8分 7秒
               割り当て中アドレス: 192.168.100.3
          (タイプ) クライアントID: (01) XX XX XX XX XX XX
                         ホスト名: 2goki
                     リース残時間: 2日 23時間 8分 6秒
               割り当て中アドレス: 192.168.100.4
          (タイプ) クライアントID: (01) XX XX XX XX XX XX
                         ホスト名: 1goki
                     リース残時間: 2日 23時間 8分 7秒
               割り当て中アドレス: 192.168.100.5
          (タイプ) クライアントID: (01) XX XX XX XX XX XX
                         ホスト名: 3goki
                     リース残時間: 2日 23時間 8分 42秒
 スコープの全アドレス数: 190
         除外アドレス数: 0
   割り当て中アドレス数: 4
     利用可能アドレス数: 186
# 

ホスト名とか、リース残時間とかも表示されてて超わかりやすい!(;ω; )

ラズパイに静的なIPアドレスを設定する

ここから本番。まずラズパイに接続して固定IPを設定します。前々回ラズパイでsshdが動くようにしたので、PCをルーターに繋げばラズパイにsshで接続できるようになっているはず。

/etc/dhcpcd.confを編集します。

/etc/dhcpcd.conf
interface eth0
static ip_address=192.168.10.2/24
static routers=192.168.10.1
static domain_name_servers=192.168.10.1

設定したら下記コマンドで設定を反映させます。反映後はip addrでインタフェースにIPアドレスが設定されているかを確認しましょう。

# systemctl daemon-reload
# systemctl restart dhcpcd

ルーターの設定

ここからはRTX830の設定

LAN分割とDHCPの設定

LAN分割機能を使ってlan1.1に192.168.10.1/24を割り当てます。ちなみにMacSSHルーターに接続している場合は、後述のip vlanのコマンドを実行した瞬間に接続が切れるので、コンソールで接続すること。

# lan type lan1 port-based-option=divide-network
# ip vlan1 address 192.168.10.1/24
# dhcp scope 1 192.168.10.10-192.168.10.254/24
# save

dhcp scopeDHCPが割り振るIPアドレスの範囲であり、今回はラズパイの固定IPが含まれないような範囲で適当に設定しました。

vlanとインタフェースをマッチングさせるためにvlan port mapping lan1.1 vlan1のようなコマンドを実行する必要があると思ったんですが、デフォルトでvlan1はlan1.1、vlan2はlan1.2というように設定されているみたいなので、実行する必要はありませんでした。

特に深い意味はないけど他のインタフェースにもvlanを設定してみます。

# ip vlan2 address 192.168.20.1/24
# ip vlan3 address 192.168.30.1/24
# ip vlan4 address 192.168.40.1/24
# dhcp scope 2 192.168.20.10-192.168.20.254/24 
# dhcp scope 3 192.168.30.10-192.168.30.254/24
# dhcp scope 4 192.168.40.10-192.168.40.254/24

dnsホストの設定

vlan1にMacを接続した場合はブラウザでいろんなサイトを見ることができるんですが、vlan2などの他のネットワークに接続した時はこれができませんでした。

ちょっと原因を調べてみるとChromeのURLバーにサイトのURLを打ち込んだ場合は接続できないけど、サイトのIPを直打ちしたら接続できたのでDNSの設定が原因だと察しました。

show configを実行したら、vlan1にだけdnsホストの設定がしてありました。自分で設定した覚えはないのでルーター側が自動でやってくれたんだと思います。

dns host vlan1

というわけでこれをvlan2などのネットワークにも適用します。具体的には下記コマンドを実行します。

# dns host vlan1 vlan2 vlan3 vlan4

これでvlan2に繋いでもvlan3に繋いでもネットサーフィンができるようになるわけです。

WebGUIへのアクセスを許可する設定

show configを実行したらvlan1に接続しているマシンだけがGUIへアクセスできるように設定してありました。この時点ではvlan2やvlan3に接続したマシンはルーターのWebGUIのページにアクセスしても503エラーが返されるようになっていて、アクセスができません。

httpd host vlan1

というわけでWebGUIへアクセスできるホストの設定をします。

# httpd host vlan1 vlan2 vlan3 vlan4

まとめ

ここまでの作業でラズパイ用のvlanを構築することができました。また、ラズパイのIPアドレスを固定化し、ルーターに繋げばラズパイにアクセスできるようになりました。

正直ラズパイをサーバーとして動かすだけなら、静的なIPを設定するだけで十分だしVLANを構築する必要は皆無なんですけど、YAMAHAルーターの操作の練習がてらやっちゃいました。

フィルタリングルールの設定とかはまた後々やっていこうかなと思います。

RTX830のメモ

noで始まるコマンド

コマンドの入力形式に no で始まる形のものが並記されているコマンドが多数あります。no で始まる形式を使うと、特別な記述がない限り、そのコマンドの設定を削除し、初期値に戻します。また、show config コマンドでの表示からも外します。言い換えれば、no で始まる形式を使わない限り、入力されたコマンドは、たとえ初期値をそのまま設定する場合でも、show config コマンドでの表示の対象となります。コマンドの入力形式で、no で始まるものに対して、省略可能なパラメータが記載されていることがあります。これらは、パラメータを指定してもエラーにならないという意味で、パラメータとして与えられた値は no コマンドの動作になんら影響を与えません。

コマンドリファレンス

初期設定に戻るから、たとえばデフォルトで動いているサービス(telnetとか)はno telnetd serviceとか実行すると停止するんじゃなくて動き出すので注意。

ルーターのシャットダウンの手順

1.pp disable all コマンドで、すべての接続先(相手先)に対する通信を無効にします。 2.disconnect all コマンドで、すべての接続先(相手先)との通信を切断します。 3.POWER スイッチを STANDBY にします。 注意: 本製品の電源を入れ直す場合には、POWER ランプが消灯してから 10 秒以上の時間をおいてください

説明書

参考

ラズパイ関連

YAMAHAルーター関連

お家インフラ日記 (2)- Raspbian Stretch Liteの再インストール

お家インフラ日記 (1)- Raspbian Stretch Liteのインストールと初期設定 - etogenのブログの続きです。

なぜか原因がよくわからないんですけど、ついこの間raspbianをインストールした3つのrasberry piのうち2つにログインできなくなりました。あってる(はずの)パスワードを何回入力してもログインできない。イラッ。

というわけで今回はOSの再インストールをしていきます...

Raspbian Stretch Liteの再インストール

ネットでraspbianの再インストール方法を調べると主に2種類あることがわかりました。

  • 起動時にラズパイのロゴが表示されている画面でShiftキーを押す方法
  • SDカードをフォーマットして再度OSを書き込む方法

まず前者を試してみたんですけど、liteではうまくいきませんでした。そもそもラズパイのロゴが出てくる画面(?)っていうのが表示されないんですよね。もしかしたらうまくいってないのはわたしのやり方が悪いせいかもしれませんが...

で、しょうがないので後者の方法をとることにしました。

SDカードのフォーマット

Macではdiskutilでフォーマットが可能です。

まずdiskutil listで現在Macに取り付けられているディスク一覧を確認 

$ diskutil list
/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:                        EFI EFI                     314.6 MB   disk0s1
   2:          Apple_CoreStorage Macintosh HD            499.3 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3

/dev/disk1 (internal, virtual):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           +499.0 GB   disk1
                                 Logical Volume on disk0s2
                                 C7F4AAD8-D7F2-4997-9FFD-7CA99EE02414
                                 Unlocked Encrypted

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *63.9 GB    disk2
   1:             Windows_FAT_32 boot                    46.0 MB    disk2s1
   2:                      Linux                         63.8 GB    disk2s2

$

diskutil eraseDisk <フォーマット形式> <パーティション名> <対象のディスクのIDENTIFIER>でディスクのフォーマットをします。

$ diskutil eraseDisk exFAT sd1 disk2
Started erase on disk2
Unmounting disk
Creating the partition map
Waiting for partitions to activate
Formatting disk2s2 as ExFAT with name sd1
Volume name      : sd1
Partition offset : 411648 sectors (210763776 bytes)
Volume size      : 124321792 sectors (63652757504 bytes)
Bytes per sector : 512
Bytes per cluster: 131072
FAT offset       : 2048 sectors (1048576 bytes)
# FAT sectors    : 4096
Number of FATs   : 1
Cluster offset   : 6144 sectors (3145728 bytes)
# Clusters       : 485608
Volume Serial #  : 5c67f444
Bitmap start     : 2
Bitmap file size : 60701
Upcase start     : 3
Upcase file size : 5836
Root start       : 4
Mounting disk
Finished erase on disk2
$ 

イメージの書き込み

前回と同様にEtcherを使って書き込みます。

あとはraspberry piにSDカードを差し込んで起動すればおっけい

tar-splitとレイヤーのDiffIDとdocker saveの話

お久しぶりです。etogenです。今回はtar-splitとそれに関わることについて書きたいと思います。

tar-splitって何

github.com

tarアーカイブメタデータを保存しといて、tarアーカイブを展開しちゃった後でも元のtarアーカイブに戻すことができるGolangのパッケージ。また、CLIツールとしても使用できます。

ここで言う「元のtarアーカイブ」とは、アーカイブに含まれているファイルやディレクトリだけでなく、メタデータまでもが一致するもののことです。tar-splitでアセンブルされてできあがったtarアーカイブは、元のtarアーカイブチェックサムが一致します。

インストール

$ go get github.com/vbatts/tar-split/cmd/tar-split

基本的な使い方

tar-splitのREADMEにデモ動画があるのでそれを見ればわかりやすいんですが、ここでも一応書いておきます。

例としてまずtest_folderアーカイブを作ります。ハッシュ値も確認しておきましょう。

$ tar c test_folder/ | sha512sum      
ef50d5ec6c3c314b9bd14c11b5edea8dfa7109a558b8604bfd022a60a00c59148ac9d7518068bac05067d8aa2a3774404cc276fe7ddfd8f83d38df86d026a671  -
$ tar cf test_folder.tar test_folder/
$ sha512sum test_folder.tar 
ef50d5ec6c3c314b9bd14c11b5edea8dfa7109a558b8604bfd022a60a00c59148ac9d7518068bac05067d8aa2a3774404cc276fe7ddfd8f83d38df86d026a671  test_folder.tar
$ 

tar-split disasmはtarアーカイブをディスアセンブルしてメタデータを含んだファイルを作成します。

  • --outputで出力先のファイルを指定
$ tar-split disasm --output test_folder.json.gz test_folder.tar | sha512sum
INFO[0000] created test_folder.json.gz from test_folder.tar (read 40960 bytes) 
ef50d5ec6c3c314b9bd14c11b5edea8dfa7109a558b8604bfd022a60a00c59148ac9d7518068bac05067d8aa2a3774404cc276fe7ddfd8f83d38df86d026a671  -
$

zcatで中をちょっと覗いてみるとこんな感じになってる(ようわからん)

$ zcat test_folder.json.gz | head -n 3
{"type":2,"payload":"dGVzdF9mb2xkZXIvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA3NzUAMDAwMTc1MAAwMDAxNzUwADAwMDAwMDAwMDAwADEzNDMwNDUzMjI0ADAxMjQxMAAgNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhciAgAHNob3RhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc2hvdGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","position":0}
{"type":1,"name":"test_folder/","payload":null,"position":1}
{"type":2,"payload":"dGVzdF9mb2xkZXIvcnVuYy1zdGF0ZS44Lm1kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA2NjQAMDAwMTc1MAAwMDAxNzUwADAwMDAwMDAwNDE3ADEzNDMwNDUzMjI0ADAxNTE2NwAgMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhciAgAHNob3RhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc2hvdGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","position":2}
$

tar-split asmメタデータのファイルと、tarを展開してできあがったディレクトリ・ファイル群を使って、元のtarアーカイブを作成します。

$ tar-split asm --input test_folder.json.gz  --path ./ --output test_folder_split.tar
INFO[0000] created test_folder_split.tar from ./ and test_folder.json.gz (wrote 40960 bytes) 
$ sha512sum test_folder_split.tar
ef50d5ec6c3c314b9bd14c11b5edea8dfa7109a558b8604bfd022a60a00c59148ac9d7518068bac05067d8aa2a3774404cc276fe7ddfd8f83d38df86d026a671  test_folder_split.tar
$

作成されたハッシュ値を調べると元のtarアーカイブと一致していることがわかります。

tar-splitとレイヤーのDiffIDの話

ここからはDockerの仕様とかを勉強してるような変態さん向けのことを書きます。

/var/lib/dockerがどうなっているかを調べたり、mobyプロジェクトのソースコードを読んだことある方ならご存知だと思いますが、tar-splitは現在のlatestバージョンであるDocker v17.03.2-ceで使用されています。

そして、tar-splitの役割についてはまだ調査中ですが、わたしはレイヤーを再構成してtarアーカイブを作った時に、registry serverからダウンロードして解凍したtarアーカイブとDiffIDが一致しなかったから困るから使ってるんだと思ってます。

何が困るの?

具体的な例をあげましょう。たとえばDockerを使ってXXXというIDのイメージをpullしてきたとします。XXXに含まれるレイヤーは1つで、そのDiffIDはABCであるとします。Dockerはdocker pullの過程で.tar.gzの圧縮されたレイヤーのアーカイブのダウンロードを行なっています。Dockerはこれをまずtempディレクトリに保存したあと、/var/lib/docker/overlay2//diff/`の下に展開し、tempに保存したアーカイブを削除します。

ここで、docker loadで読み込めるようなイメージのtarアーカイブを手動で作成している人物Eちゃんがいるとします。Eちゃんはそれを作るためにネットワークを通じてconfig.jsonやレイヤーのアーカイブをダウンロードするのは邪道だと考えています。そんなわけでDockerのストレージに存在するファイルや情報だけを使ってイメージのtarアーカイブを作ることにしました。

Eちゃんは/var/lib/docker/overlay2/<cache id>/diff/の下にあるファイル群からレイヤーのtarアーカイブを作ったり、/var/lib/docker/ image/overlay2/imagedb/content/sha256/<image ID>からconfig.jsonをコピーしたりして、頑張ってイメージのtarアーカイブを完成させました。しかしながら、これを使ってDockerでイメージを作成することはできませんでした。なぜならconfig.jsondiff_idsフィールドにはABCというハッシュ値が書いてありますが、実際に作成してできあがったtarアーカイブハッシュ値DEFであったからです。

Eちゃんは次に「config.jsondiff_idsにあるABCDEFに変更すればええやん!」と考えconfig.jsonを編集しました。すると今度はイメージの作成が成功したのですが、そのイメージのIDはYYYで、期待していたイメージのIDXXXとは違っていました。それもそのはず、イメージのIDはconfig.jsonハッシュ値なので、このファイルを編集した時点でハッシュ、つまりはイメージのIDは変わってしまったのです。

Eちゃんは次にどうにかしてハッシュ値ABCのtarアーカイブを作ろうとしますが。。。DEFハッシュ値のtarアーカイブしか作れません。

-- BAD END --

DiffID変わっちゃう問題

「ダウンロードしてきたtarアーカイブをローカルに展開されちゃうと、元のハッシュ値アーカイブ作れなくなるんだよね」っていうのをまず見せましょう。

まず適当なイメージに含まれるレイヤーをダウンロードします。

$ curl -L  -H "Authorization: Bearer $(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:google/cadvisor:pull" | jq --raw-output .token)" "https://index.docker.io/v2/google/cadvisor/blobs/sha256:ef94a70c87ede188530b904d75339cec23ed226a7e5cc193b843c53ad119dd55" > layer.tar.gz

layer.tar.gzを解凍してアーカイブハッシュ値を確認しましょう。これが本来のDiffID。

$ sha256sum layer.tar 
f6367c2e07c76227c564fab480483be7b4e5b6fe4bc18302c99480fa6bee78f9  layer.tar
$

で、展開した後、tarアーカイブを再作成します。

$ tar xf layer.tar 
$ ls
layer.tar  usr
$ tar cf layer2.tar usr/
$ sha256sum layer2.tar 
ac7b9a7982263eb38b1c8d1caa3ce74ce75d50c7959c9cd561306018091fbc5d  layer2.tar
$ 

はいDiffID変わりました。

DiffIDが変わる原因は、再作成をする際tarアーカイブにローカル環境のunameなどの情報などが含まれてしまうからです。要するにメタデータが変化したせいでtarアーカイブのバイナリも変わり、ハッシュ値も変わったわけです。

hexdump -Cで元のtarアーカイブと再作成したtarアーカイブのバイナリデータを見比べてみると「ふ〜ん」ってなると思います。

tar-splitを使った解決

本題中の本題。tar-splitを使って再作成をすれば本来のハッシュ値と等しいtarアーカイブを作ることができ、完全に復元することができます。

まずtar-split disasmメタデータのファイルを作成。

$ tar-split disasm --output layer-split.json.gz layer.tar | sha256sum
INFO[0000] created layer-split.json.gz from layer.tar (read 23442944 bytes) 
f6367c2e07c76227c564fab480483be7b4e5b6fe4bc18302c99480fa6bee78f9  -
$

そしてtar展開後のファイル群が存在するディレクトリを指定してtar-split asmを実行します。

$ tar-split asm --input layer-split.json.gz  --path ./ --output layer-split.tar
INFO[0000] created layer-split.tar from ./ and layer-split.json.gz (wrote 23442944 bytes)
$ sha256sum layer-split.tar 
f6367c2e07c76227c564fab480483be7b4e5b6fe4bc18302c99480fa6bee78f9  layer-split.tar
$

すると見事に元のtarアーカイブを復元することができました。

docker saveとtar-splitの話

docker saveはDockerイメージをtarアーカイブとして出力できるコマンドです。実はこのコマンドの裏ではtar-splitによるアセンブルが行われています。mobyソースコードを見てみましょう。

func (ls *layerStore) assembleTarTo(graphID string, metadata io.ReadCloser, size *int64, w io.Writer) error {
    diffDriver, ok := ls.driver.(graphdriver.DiffGetterDriver)
    if !ok {
        diffDriver = &naiveDiffPathDriver{ls.driver}
    }

    defer metadata.Close()

    // get our relative path to the container
    fileGetCloser, err := diffDriver.DiffGetter(graphID)
    if err != nil {
        return err
    }
    defer fileGetCloser.Close()

    metaUnpacker := storage.NewJSONUnpacker(metadata)
    upackerCounter := &unpackSizeCounter{metaUnpacker, size}
    logrus.Debugf("Assembling tar data for %s", graphID)
    return asm.WriteOutputTarStream(fileGetCloser, upackerCounter, w)
}

https://github.com/moby/moby/blob/d147fe0582f44c0fc22ec8bdffff44939dd54d52/layer/layer_store.go#L702-L720

このassembleTarToというメソッドは雑に説明すると、tar-split.jsonを読み込み、tar-splitパッケージのasm.WriteOutputTarStreamメソッドでアセンブルをし、tarアーカイブを復元するというものです。

コードを遡っていくと、このメソッドはイメージをtarアーカイブとして出力するimage/tarexport/save.gosaveメソッドの内部で呼ばれています。tar-splitで復元したファイルはlayer.tarという名前で保存されます。docker saveで作ったアーカイブの中身を見ると、layer.tarがイメージのレイヤーの数だけあるのが確認できるでしょう。これがassembleTarToで作られたものです。

まとめ

ちょっと長くなりましたが、要するに「docker saveでエクスポートしたアーカイブdocker loadで読み込んでもイメージのIDが変わらないカラクリの裏にはtar-splitがいた!」ってこと。

参考

お家インフラ日記 (1)- Raspbian Stretch Liteのインストールと初期設定

唐突ですが、今日Raspberry Pi 3 Model Bを3つ買いました。今日からこの子たちを使ってk8sクラスタを作ったり、gitサーバー立てたりして、そのことを記事にしていこうかなと考えてます。

購入したものについて

Raspberry Pi 3 Model B

今発売されてるモデルで一番新しいのはRaspberry Pi 3 Model B+だと思うんですけど、あえてこの旧モデルを購入しました。その理由はお安いからです。以上。

AmazonにはRaspberry Piを売ってるショップがいくつかあるんですけど、中には対応がひどいショップもあるらしいんで注意してください。自分は日本の正規代理店(以下のリンク)から購入しました。

Sandisk Ultra 64GB (microSD)

microSDはラズパイのストレージとして使います(最近はPiServerを使えばmicroSDなしでもラズパイを使っていけるらしいですね)

microSDは正直どれを選べばいいかよくわからないんですけど、とりあえず容量が64GBあって、安いけど信頼できるような有名な会社のがいいなと思ってました。で、なんとなくSanDiskmicroSDにしようと思いました。

SanDiskHow To Choose MicroSDを見るとUltraはReadが80mbps、Writeが60mbpsらしいです。その一個上のクラスのExtremeは特典としてデータ復旧ソフトが使えたり、Readの速度がちょっと速くなったりするんですけど、Ultraより2000円くらい高かかったのでやめときました。

AC電源アダプタ

ラズパイが動けばなんでもいいや(適当)

追記(2019/2/16)

このACアダプタのせいかはしらないけど起動時に下記のようなメッセージが表示されます。

[12.471526] Under-voltage detected! (0x00050005)

どうやら入力電圧が低い時に表示されるものらしい(参考: RaspberryPi Under-voltage detected!と言われる

ちなみにケーブルはこの商品に同梱されていたものを使っています。とりあえず今使ってる感じは何も問題がないので、何か起きたらここにまた追記しようと思います。

ケース

ラズパイをサーバーとして常時稼働させるため熱がこもりにくく、かつ見栄えがクラスタっぽくなる(?)ケースがいいなと思いました。で、これにぴったりの積層式ケースというのを見つけました。

ラズパイ1個1個のために個別にケース買うよりもお得ですし、ヒートシンクなんかも付いてくるみたいですね

Raspbianのインストール

インストールの作業にはMacを使います

microSDカードのフォーマット

購入したSanDiskmicroSDはすでにexFATでフォーマットしてあったので、microSDのフォーマットの作業はやってません。

$ diskutil list
/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:                        EFI EFI                     314.6 MB   disk0s1
   2:          Apple_CoreStorage Macintosh HD            499.3 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3

/dev/disk1 (internal, virtual):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           +499.0 GB   disk1
                                 Logical Volume on disk0s2
                                 C7F4AAD8-D7F2-4997-9FFD-7CA99EE02414
                                 Unlocked Encrypted

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *63.9 GB    disk2
   1:               Windows_NTFS                         63.8 GB    disk2s1

$ diskutil info /dev/disk2s1
   Device Identifier:        disk2s1
   Device Node:              /dev/disk2s1
   Whole:                    No
   Part of Whole:            disk2

   Volume Name:              
   Mounted:                  Yes
   Mount Point:              /Volumes/Untitled

   Partition Type:           Windows_NTFS
   File System Personality:  ExFAT
   Type (Bundle):            exfat
   Name (User Visible):      ExFAT

   OS Can Be Installed:      No
   Media Type:               Generic
   Protocol:                 USB
   SMART Status:             Not Supported
   Volume UUID:              B5D683B0-13DD-3ABB-8A4E-1B19743E59B6

   Disk Size:                63.8 GB (63847792640 Bytes) (exactly 124702720 512-Byte-Units)
   Device Block Size:        512 Bytes

   Volume Total Space:       63.8 GB (63831015424 Bytes) (exactly 124669952 512-Byte-Units)
   Volume Used Space:        8.5 MB (8519680 Bytes) (exactly 16640 512-Byte-Units) (0.0%)
   Volume Available Space:   63.8 GB (63822495744 Bytes) (exactly 124653312 512-Byte-Units) (100.0%)
   Allocation Block Size:    131072 Bytes

   Read-Only Media:          No
   Read-Only Volume:         No

   Device Location:          External
   Removable Media:          Removable
   Media Removal:            Software-Activated

$ 

(/dev/disk2はSDカードリーダー)

フォーマットの作業をする場合は、ネットで調べるとSD Card Formatterを使え!って記事がいっぱい出てきますが、Mac OSだったらDisk Utilityを使えばそれをダウンロードする必要はなさそうですね。

Raspbianのダウンロードと書き込み

Raspbianのイメージは公式サイトのDownloadsからダウンロードできます。サーバーとして動かすのならデスクトップ環境なんかいらないし、Raspbian Stretch Liteという軽量のイメージを使うことにしました。

イメージのzipをダウンロードしたら、ハッシュを確認して解凍。

$ shasum -a 256 2018-11-13-raspbian-stretch-lite.zip
47ef1b2501d0e5002675a50b6868074e693f78829822eef64f3878487953234d  2018-11-13-raspbian-stretch-lite.zip
$ unzip 2018-11-13-raspbian-stretch-lite.zip 
Archive:  2018-11-13-raspbian-stretch-lite.zip
  inflating: 2018-11-13-raspbian-stretch-lite.img  
$

イメージの書き込みはInstalling operating system imagesに「Etcherを使ったら簡単にできるよー」って書いてあったんで、これを使うことにしました。

Etcherを使った作業の流れは

  1. 「Select Image」で解凍済みのイメージのファイルを選択
  2. 「Select Drive」でmicroSDを選択
  3. Flash」で書き込み開始

これだけです。

ラズパイの起動

ラズパイにイメージを書き込んだmicroSDを挿して、ACアダプタを繋げたら起動します。ちょっと待ってると

raspberrypi login:

っていうメッセージが表示されるので、ここでユーザー名とパスワードを入力してログインします。デフォルトではユーザー名pi、パスワードはraspberryでログインできます。

起動したあとやったこと

※以下の設定には一部インターネットへの接続が必要なものがあります(aptとかね)

piユーザーのパスワードの変更

# passwd pi

パッケージの更新

# apt update
# apt upgrade -y

ファームウェアの更新

ファームウェアの更新は推奨されていないみたいですが、やっちゃった。最新版が安定しているとは言えないので、もしかしたら何かしらの不具合が起きるみたいです。

# rpi-update
# reboot

キーボードのJIS配列に対応させる

注意してほしいのが、これはレイアウトの設定であって日本語入力を可能にするものではないです。

# nano /etc/default/keyboard
/etc/default/keyboard
...
XKBLAYOUT="jp"   <-ここたぶん"gb"になってるので"jp"に変更
...

あとは再起動

sshdの起動

# systemctl enable ssh
# systemctl status sshd

ちなみにデフォルトでsshのパッケージがインストールされてます。systemctl start sshdを実行した場合はFailed to restart sshd.service: Unit sshd.service not found.と表示され失敗するので注意。(How do I restart sshd on raspbian?

別の方法として/boot/の下にsshというファイルを作ることで、起動時にsshdが起動するようにできるみたいです。(A security update for Raspbian PIXEL - Raspberry Pi

ホスト名の変更

/etc/hostname/etc/hostsを編集します。まずhostnameの方

/etc/hostname
1goki

ラズパイ3つにそれぞれ1goki、2goki、3gokiという名前をつけました。次はhostsファイル。127.0.0.1の右側に書いてある古いホスト名(おそらくデフォルトだとraspberrypiになってるはず)を新しいホスト名に書き換えます。

/etc/hosts
127.0.0.1    1goki

あとは再起動すればこの設定が反映されます。

とりあえず今回はここまで。ラズパイをサーバーとして動かすならIPの固定化とかもしたいんですけど、その辺の設定はまた後々。

参考(作業中に読んでてためになった記事)

containerd v1.1.2を動かして色々操作してみた

containerdを動かしてコンテナを作ったりイメージをプルしたり色々やってみようっていう内容です。containerdに対する操作はCLIツールのctrを使用します。

containerdのインストールと実行

GitHubのリリースページよりtarballをダウンロードしてきます。今回はバージョンv1.1.2をダウンロードします。

containerd-1.1.2.linux-amd64.tar.gzはビルド済みのバイナリファイルが入ってるので、解凍すればすぐ使えて便利です。

github.com

ダウンロードして解凍するとbinディレクトリが出てきます。その中にはcontainerdの他にctrなどのツールも入ってます。

ubuntu@v-Ubuntu:~$ wget https://github.com/containerd/containerd/releases/download/v1.1.2/containerd-1.1.2.linux-amd64.tar.gz
ubuntu@v-Ubuntu:~$ tar -xzf containerd-1.1.2.linux-amd64.tar.gz
ubuntu@v-Ubuntu:~$ rm containerd-1.1.2.linux-amd64.tar.gz 
ubuntu@v-Ubuntu:~$ cd bin
ubuntu@v-Ubuntu:~/bin$ ls
containerd  containerd-release  containerd-shim  containerd-stress  ctr
ubuntu@v-Ubuntu:~/bin$ sudo cp * /usr/bin/

さっそくcontianerdを起動してみましょう。

ubuntu@v-Ubuntu:~/bin$ sudo containerd
INFO[0000] starting containerd                           revision=468a545b9edcd5932818eb9de8e72413e616e86e version=v1.1.2
INFO[0000] loading plugin "io.containerd.content.v1.content"...  type=io.containerd.content.v1
INFO[0000] loading plugin "io.containerd.snapshotter.v1.btrfs"...  type=io.containerd.snapshotter.v1
WARN[0000] failed to load plugin io.containerd.snapshotter.v1.btrfs  error="path /var/lib/containerd/io.containerd.snapshotter.v1.btrfs must be a btrfs filesystem to be used with the btrfs snapshotter"
INFO[0000] loading plugin "io.containerd.snapshotter.v1.aufs"...  type=io.containerd.snapshotter.v1
INFO[0000] loading plugin "io.containerd.snapshotter.v1.native"...  type=io.containerd.snapshotter.v1
INFO[0000] loading plugin "io.containerd.snapshotter.v1.overlayfs"...  type=io.containerd.snapshotter.v1
INFO[0000] loading plugin "io.containerd.snapshotter.v1.zfs"...  type=io.containerd.snapshotter.v1
WARN[0000] failed to load plugin io.containerd.snapshotter.v1.zfs  error="path /var/lib/containerd/io.containerd.snapshotter.v1.zfs must be a zfs filesystem to be used with the zfs snapshotter"
INFO[0000] loading plugin "io.containerd.metadata.v1.bolt"...  type=io.containerd.metadata.v1
WARN[0000] could not use snapshotter zfs in metadata plugin  error="path /var/lib/containerd/io.containerd.snapshotter.v1.zfs must be a zfs filesystem to be used with the zfs snapshotter"
WARN[0000] could not use snapshotter btrfs in metadata plugin  error="path /var/lib/containerd/io.containerd.snapshotter.v1.btrfs must be a btrfs filesystem to be used with the btrfs snapshotter"
INFO[0000] loading plugin "io.containerd.differ.v1.walking"...  type=io.containerd.differ.v1
INFO[0000] loading plugin "io.containerd.gc.v1.scheduler"...  type=io.containerd.gc.v1
INFO[0000] loading plugin "io.containerd.service.v1.containers-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.service.v1.content-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.service.v1.diff-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.service.v1.images-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.service.v1.leases-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.service.v1.namespaces-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.service.v1.snapshots-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.monitor.v1.cgroups"...  type=io.containerd.monitor.v1
INFO[0000] loading plugin "io.containerd.runtime.v1.linux"...  type=io.containerd.runtime.v1
INFO[0000] loading plugin "io.containerd.service.v1.tasks-service"...  type=io.containerd.service.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.containers"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.content"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.diff"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.events"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.healthcheck"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.images"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.leases"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.namespaces"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.snapshots"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.tasks"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.version"...  type=io.containerd.grpc.v1
INFO[0000] loading plugin "io.containerd.grpc.v1.cri"...  type=io.containerd.grpc.v1
INFO[0000] Start cri plugin with config {PluginConfig:{ContainerdConfig:{Snapshotter:overlayfs DefaultRuntime:{Type:io.containerd.runtime.v1.linux Engine: Root:} UntrustedWorkloadRuntime:{Type: Engine: Root:}} CniConfig:{NetworkPluginBinDir:/opt/cni/bin NetworkPluginConfDir:/etc/cni/net.d NetworkPluginConfTemplate:} Registry:{Mirrors:map[docker.io:{Endpoints:[https://registry-1.docker.io]}]} StreamServerAddress: StreamServerPort:10010 EnableSelinux:false SandboxImage:k8s.gcr.io/pause:3.1 StatsCollectPeriod:10 SystemdCgroup:false EnableTLSStreaming:false MaxContainerLogLineSize:16384} ContainerdRootDir:/var/lib/containerd ContainerdEndpoint:/run/containerd/containerd.sock RootDir:/var/lib/containerd/io.containerd.grpc.v1.cri StateDir:/run/containerd/io.containerd.grpc.v1.cri} 
INFO[0000] Connect containerd service                   
INFO[0000] Get image filesystem path "/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs" 
ERRO[0000] Failed to load cni during init, please check CRI plugin status before setting up network for pods  error="cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config"
INFO[0000] loading plugin "io.containerd.grpc.v1.introspection"...  type=io.containerd.grpc.v1
INFO[0000] Start subscribing containerd event           
INFO[0000] Start recovering state                       
INFO[0000] serving...                                    address="/run/containerd/containerd.sock"
INFO[0000] containerd successfully booted in 0.032078s  
INFO[0000] Start event monitor                          
INFO[0000] Start snapshots syncer                       
INFO[0000] Start streaming server                       

CNIのエラーが起きていますが、今回はお触りするだけなのでほっといていいです。

containerdの操作

ctrを使うことでCLIからcontainerdを操作することができます。

ubuntu@v-Ubuntu:~$ sudo ctr
NAME:
   ctr - 
        __
  _____/ /______
 / ___/ __/ ___/
/ /__/ /_/ /
\___/\__/_/

containerd CLI


USAGE:
   ctr [global options] command [command options] [arguments...]

VERSION:
   v1.1.2

COMMANDS:
     plugins, plugin           provides information about containerd plugins
     version                   print the client and server versions
     containers, c, container  manage containers
     content                   manage content
     events, event             display containerd events
     images, image, i          manage images
     namespaces, namespace     manage namespaces
     pprof                     provide golang pprof outputs for containerd
     run                       run a container
     snapshots, snapshot       manage snapshots
     tasks, t, task            manage tasks
     shim                      interact with a shim directly
     cri                       interact with cri plugin
     help, h                   Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --debug                      enable debug output in logs
   --address value, -a value    address for containerd's GRPC server (default: "/run/containerd/containerd.sock")
   --timeout value              total timeout for ctr commands (default: 0s)
   --connect-timeout value      timeout for connecting to containerd (default: 0s)
   --namespace value, -n value  namespace to use with commands (default: "default") [$CONTAINERD_NAMESPACE]
   --help, -h                   show help
   --version, -v                print the version
ubuntu@v-Ubuntu:~$

イメージのプル

ctr image pullでプルができます。

ubuntu@v-Ubuntu:~$ sudo ctr image pull --help
NAME:
   ctr images pull - pull an image from a remote

USAGE:
   ctr images pull [command options] [flags] <ref>

DESCRIPTION:
   Fetch and prepare an image for use in containerd.

After pulling an image, it should be ready to use the same reference in a run
command. As part of this process, we do the following:

1. Fetch all resources into containerd.
2. Prepare the snapshot filesystem with the pulled resources.
3. Register metadata for the image.


OPTIONS:
   --skip-verify, -k       skip SSL certificate validation
   --plain-http            allow connections using plain HTTP
   --user value, -u value  user[:password] Registry user and password
   --refresh value         refresh token for authorization server
   --snapshotter value     snapshotter name. Empty value stands for the default value. (default: "overlayfs") [$CONTAINERD_SNAPSHOTTER]
   --label value           labels to attach to the image
   --platform value        Pull content from a specific platform (default: "linux/amd64")
   --all-platforms         pull content from all platforms
   
ubuntu@v-Ubuntu:~$ 

docker pullでは引数にイメージ名を渡すだけでdocker.io/library/<image name>:latestなイメージをプルすることができましたが、ctrでは「ドメインリポジトリ、イメージ名、タグ」を全て指定しないとできないようです。ちょいと面倒臭い。

ubuntu@v-Ubuntu:~$ sudo ctr image pull redis
ctr: failed to resolve reference "redis": object required
ubuntu@v-Ubuntu:~/bin$ sudo ctr image pull redis:latest
ctr: failed to resolve reference "redis:latest": object required
ubuntu@v-Ubuntu:~/bin$ sudo ctr image pull docker.io/redis:latest
docker.io/redis:latest: resolving      |--------------------------------------| 
elapsed: 1.6 s          total:   0.0 B (0.0 B/s)                                         
ctr: failed to resolve reference "docker.io/redis:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
ubuntu@v-Ubuntu:~/bin$ sudo ./ctr image pull docker.io/library/redis:latest
docker.io/library/redis:latest:                                                   resolved       |++++++++++++++++++++++++++++++++++++++| 
index-sha256:096cff9e6024603decb2915ea3e501c63c5bb241e1b56830a52acfd488873843:    done           |++++++++++++++++++++++++++++++++++++++| 
manifest-sha256:f20ec679e1bdf0b6a83b4b4d4097dddf08edf0aa3cc6ff2ef4b9afebb0c8b5ef: done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:ce8aaa9fe90ad0dada1d2b2fed22ee9bb64bcfc1a5a4d5f7d2fe392df35050aa:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:d6f5ea773ca39a0094e300aec4c30478b616d1631382c6eba21fdc2fa20c0385:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:735cc65c0db489710ba88840b2075d6d4195f0d0c5dfc5e59fdfc6243aca0daf:    done           |++++++++++++++++++++++++++++++++++++++| 
config-sha256:f06a5773f01e1f77eb4487acb3333649716f45b3c32aad038765dc0ab0337bd4:   done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:ff89c30e4d8c53c3403ed2c09737963e5e1c2052f285e34c3ae3baf2d49b2017:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:59bf782a86b38d9f3b9e9c20588467b427460403d06146099026cdf1310a7285:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:be8881be8156e4068e611fe956aba2b9593ebd953be14fb7feea6d0659aa3abe:    done           |++++++++++++++++++++++++++++++++++++++| 
elapsed: 1.9 s                                                                    total:  9.2 Mi (4.8 MiB/s)                                       
unpacking sha256:096cff9e6024603decb2915ea3e501c63c5bb241e1b56830a52acfd488873843...
done
ubuntu@v-Ubuntu:~$ 

イメージ一覧の取得

ctr image lsで取得可能。

ubuntu@v-Ubuntu:~$ sudo ctr image ls --help
NAME:
   ctr images list - list images known to containerd

USAGE:
   ctr images list [command options] [flags] <ref>

DESCRIPTION:
   list images registered with containerd

OPTIONS:
   --quiet, -q  print only the image refs
   
ubuntu@v-Ubuntu:~$ 

ターミナルの横幅が狭いと一行で収まらないのがなんか嫌ですね(PLATFORMSとかTYPEのカラムはオプションで表示できるようにしたいんですけど...)

ubuntu@v-Ubuntu:~$ sudo ctr image ls
REF                            TYPE                                                      DIGEST                                                                  SIZE     PLATFORMS                                                                                LABELS 
docker.io/library/redis:latest application/vnd.docker.distribution.manifest.list.v2+json sha256:096cff9e6024603decb2915ea3e501c63c5bb241e1b56830a52acfd488873843 30.6 MiB linux/386,linux/amd64,linux/arm/v5,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -      
ubuntu@v-Ubuntu:~/bin$ sudo ./ctr image ls -q
docker.io/library/redis:latest
ubuntu@v-Ubuntu:~$

コンテナの作成・起動

ctr runでできます。

ubuntu@v-Ubuntu:~$ sudo ctr run --help
NAME:
   ctr run - run a container

USAGE:
   ctr run [command options] [flags] Image|RootFS ID [COMMAND] [ARG...]

OPTIONS:
   --rm                      remove the container after running
   --null-io                 send all IO to /dev/null
   --detach, -d              detach from the task after it has started execution
   --fifo-dir value          directory used for storing IO FIFOs
   --snapshotter value       snapshotter name. Empty value stands for the default value. (default: "overlayfs") [$CONTAINERD_SNAPSHOTTER]
   --config value, -c value  path to the runtime-specific spec config file
   --checkpoint value        provide the checkpoint digest to restore the container
   --cwd value               specify the working directory of the process
   --env value               specify additional container environment variables (i.e. FOO=bar)
   --label value             specify additional labels (i.e. foo=bar)
   --mount value             specify additional container mount (ex: type=bind,src=/tmp,dst=/host,options=rbind:ro)
   --net-host                enable host networking for the container
   --privileged              run privileged container
   --read-only               set the containers filesystem as readonly
   --runtime value           runtime name (io.containerd.runtime.v1.linux, io.containerd.runtime.v1.windows, io.containerd.runtime.v1.com.vmware.linux) (default: "io.containerd.runtime.v1.linux")
   --tty, -t                 allocate a TTY for the container
   --with-ns value           specify existing Linux namespaces to join at container runtime (format '<nstype>:<path>')
   --pid-file value          file path to write the task's pid
   
ubuntu@v-Ubuntu:~$ 

ここでちょいと詰まります。redisのコンテナを動かそうとしたらOCI runtime errorなるものが発生。どうやらruncが必要なようです。

ubuntu@v-Ubuntu:~$ sudo ctr run -d docker.io/library/redis:latest redis1
ctr: OCI runtime create failed: unable to retrieve OCI runtime error (open /run/containerd/io.containerd.runtime.v1.linux/default/redis1/log.json: no such file or directory): exec: "runc": executable file not found in $PATH: unknown
ubuntu@v-Ubuntu:~$

というわけでruncをインストールします。GithHubのリリースページより1.0.0のバイナリファイルをダウンロードしました。

github.com

ubuntu@v-Ubuntu:~$ wget https://github.com/opencontainers/runc/releases/download/v1.0.0-rc5/runc.amd64
ubuntu@v-Ubuntu:~$ chmod +x runc.amd64 
ubuntu@v-Ubuntu:~$ sudo cp runc.amd64 /usr/bin/runc

これでどうじゃ!と思ったらまた詰まります。

ubuntu@v-Ubuntu:~$ sudo ctr run -d docker.io/library/redis:latest redis1
ctr: snapshot "redis1": already exists
ubuntu@v-Ubuntu:~$

redis1のスナップショットが存在するということです。スナップショットが何かということについてはあとで説明します。

ctr runが失敗してもスナップショットとコンテナ(ただし停止している)が作成されるようです。これらを削除するためにctr container rm redisを実行します。

ubuntu@v-Ubuntu:~$ sudo ctr container rm redis1

これでどうじゃ!

ubuntu@v-Ubuntu:~$ sudo ctr run -d docker.io/library/redis:latest redis1
1:C 30 Jul 11:18:28.266 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 30 Jul 11:18:28.266 # Redis version=4.0.10, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 30 Jul 11:18:28.266 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 30 Jul 11:18:28.267 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
1:M 30 Jul 11:18:28.267 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
1:M 30 Jul 11:18:28.267 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
1:M 30 Jul 11:18:28.267 * Running mode=standalone, port=6379.
1:M 30 Jul 11:18:28.267 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 30 Jul 11:18:28.267 # Server initialized
1:M 30 Jul 11:18:28.267 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 30 Jul 11:18:28.267 * Ready to accept connections
ubuntu@v-Ubuntu:~$

なんかうまくいったようです。

コンテナがRUNNINGになっているかを確認する

containerdでは起動中のコンテナ(プロセス)は「タスク」として扱います。

 ubuntu@v-Ubuntu:~$ sudo ctr task
NAME:
   ctr tasks - manage tasks

USAGE:
   ctr tasks command [command options] [arguments...]

COMMANDS:
     attach      attach to the IO of a running container
     checkpoint  checkpoint a container
     delete      [flags] delete a task
     exec        execute additional processes in an existing container
     list, ls    list tasks
     kill        signal a container (default: SIGTERM)
     pause       pause an existing container
     ps          list processes for container
     resume      resume a paused container
     start       start a container that have been created

OPTIONS:
   --help, -h  show help
   
ubuntu@v-Ubuntu:~$

ctr task lsでタスクの一覧を表示できます。

ubuntu@v-Ubuntu:~$ sudo ctr task ls
TASK      PID      STATUS    
redis1    16722    RUNNING
ubuntu@v-Ubuntu:~$

redis1が動いているのがわかりますね。

ついでにpsコマンドでどんなプロセスが動いているか確認しましょう。

ubuntu@v-Ubuntu:~$ ps axu
...
root     16705  0.0  0.0   7512  4184 pts/0    Sl   20:18   0:00 containerd-shim -namespace default -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/default/redis1 -address /run/containerd/containerd.sock -containerd-binary /home/ubuntu/bin/containerd
999      16722  0.0  0.0  41668  3528 ?        Ssl  20:18   0:00 redis-server *:6379
...
ubuntu@v-Ubuntu:~$

containerd-shimのプロセスがありますね。またredisは999というユーザーによって実行されているようです。

コンテナに入る

ctr task execdocker execライクなコマンドで、コンテナ内で特定のコマンドを実行できます。

ubuntu@v-Ubuntu:~$ sudo ctr task exec --help
NAME:
   ctr tasks exec - execute additional processes in an existing container

USAGE:
   ctr tasks exec [command options] [flags] CONTAINER CMD [ARG...]

OPTIONS:
   --cwd value       working directory of the new process
   --tty, -t         allocate a TTY for the container
   --exec-id value   exec specific id for the process
   --fifo-dir value  directory used for storing IO FIFOs
   
ubuntu@v-Ubuntu:~$ 

コンテナ内でシェルを起動してコンテナの中で操作をしてみましょう。

ubuntu@v-Ubuntu:~$ sudo ctr task exec -t redis1 /bin/bash 
ctr: exec id must not be empty: invalid argument
ubuntu@v-Ubuntu:~$ 

ありゃりゃ。exec idは絶対設定しないといけないようです。じゃあ適当にIDを指定して再度実行してみましょう。

ubuntu@v-Ubuntu:~$ sudo ctr task exec -t --exec-id 5000 redis1 /bin/bash 
root@v-Ubuntu:/data# cat /etc/hostname
debuerreotype
root@v-Ubuntu:/data# exit
exit
ubuntu@v-Ubuntu:~$ 

というわけでちゃんとコンテナの中に入れましたね。ちなみにctr attachでもコンテナの中に入れます(今回の場合はコンテナ内部でシェルが動いてないんでアタッチしたところで何もできませんが)

コンテナのリストやコンテナの情報を表示する

ubuntu@v-Ubuntu:~$ sudo ctr container
NAME:
   ctr containers - manage containers

USAGE:
   ctr containers command [command options] [arguments...]

COMMANDS:
     create           create container
     delete, del, rm  delete one or more existing containers
     info             get info about a container
     list, ls         list containers
     label            set and clear labels for a container

OPTIONS:
   --help, -h  show help
   
ubuntu@v-Ubuntu:~$

ctr container lsでコンテナ一覧を表示。停止しているコンテナも表示されます。今回はコンテナが1個だけ表示されます。

ubuntu@v-Ubuntu:~$ sudo ctr container ls 
CONTAINER    IMAGE                             RUNTIME                           
redis1       docker.io/library/redis:latest    io.containerd.runtime.v1.linux    
ubuntu@v-Ubuntu:~$

ctr container infoでコンテナの情報を見ることができます。

ubuntu@v-Ubuntu:~$ sudo ctr container info redis1
{
    "ID": "redis1",
    "Labels": null,
    "Image": "docker.io/library/redis:latest",
    "Runtime": {
        "Name": "io.containerd.runtime.v1.linux",
        "Options": null
    },
    "Spec": {
        "type_url": "types.containerd.io/opencontainers/runtime-spec/1/Spec",
        "value": "eyJvY2lWZXJzaW9uIjoiMS4wLjEiLCJwcm9jZXNzIjp7InVzZXIiOnsidWlkIjowLCJnaWQiOjB9LCJhcmdzIjpbImRvY2tlci1lbnRyeXBvaW50LnNoIiwicmVkaXMtc2VydmVyIl0sImVudiI6WyJQQVRIPS91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2JpbiIsIlBBVEg9L3Vzci9sb2NhbC9zYmluOi91c3IvbG9jYWwvYmluOi91c3Ivc2JpbjovdXNyL2Jpbjovc2JpbjovYmluIiwiR09TVV9WRVJTSU9OPTEuMTAiLCJSRURJU19WRVJTSU9OPTQuMC4xMCIsIlJFRElTX0RPV05MT0FEX1VSTD1odHRwOi8vZG93bmxvYWQucmVkaXMuaW8vcmVsZWFzZXMvcmVkaXMtNC4wLjEwLnRhci5neiIsIlJFRElTX0RPV05MT0FEX1NIQT0xZGI2NzQzNWE3MDRmOGQxOGFlYzliOTYzN2IzNzNjMzRhYTIzM2Q2NWI2ZTE3NGJkYWM0YzFiMTYxZjM4Y2E0Il0sImN3ZCI6Ii9kYXRhIiwiY2FwYWJpbGl0aWVzIjp7ImJvdW5kaW5nIjpbIkNBUF9DSE9XTiIsIkNBUF9EQUNfT1ZFUlJJREUiLCJDQVBfRlNFVElEIiwiQ0FQX0ZPV05FUiIsIkNBUF9NS05PRCIsIkNBUF9ORVRfUkFXIiwiQ0FQX1NFVEdJRCIsIkNBUF9TRVRVSUQiLCJDQVBfU0VURkNBUCIsIkNBUF9TRVRQQ0FQIiwiQ0FQX05FVF9CSU5EX1NFUlZJQ0UiLCJDQVBfU1lTX0NIUk9PVCIsIkNBUF9LSUxMIiwiQ0FQX0FVRElUX1dSSVRFIl0sImVmZmVjdGl2ZSI6WyJDQVBfQ0hPV04iLCJDQVBfREFDX09WRVJSSURFIiwiQ0FQX0ZTRVRJRCIsIkNBUF9GT1dORVIiLCJDQVBfTUtOT0QiLCJDQVBfTkVUX1JBVyIsIkNBUF9TRVRHSUQiLCJDQVBfU0VUVUlEIiwiQ0FQX1NFVEZDQVAiLCJDQVBfU0VUUENBUCIsIkNBUF9ORVRfQklORF9TRVJWSUNFIiwiQ0FQX1NZU19DSFJPT1QiLCJDQVBfS0lMTCIsIkNBUF9BVURJVF9XUklURSJdLCJpbmhlcml0YWJsZSI6WyJDQVBfQ0hPV04iLCJDQVBfREFDX09WRVJSSURFIiwiQ0FQX0ZTRVRJRCIsIkNBUF9GT1dORVIiLCJDQVBfTUtOT0QiLCJDQVBfTkVUX1JBVyIsIkNBUF9TRVRHSUQiLCJDQVBfU0VUVUlEIiwiQ0FQX1NFVEZDQVAiLCJDQVBfU0VUUENBUCIsIkNBUF9ORVRfQklORF9TRVJWSUNFIiwiQ0FQX1NZU19DSFJPT1QiLCJDQVBfS0lMTCIsIkNBUF9BVURJVF9XUklURSJdLCJwZXJtaXR0ZWQiOlsiQ0FQX0NIT1dOIiwiQ0FQX0RBQ19PVkVSUklERSIsIkNBUF9GU0VUSUQiLCJDQVBfRk9XTkVSIiwiQ0FQX01LTk9EIiwiQ0FQX05FVF9SQVciLCJDQVBfU0VUR0lEIiwiQ0FQX1NFVFVJRCIsIkNBUF9TRVRGQ0FQIiwiQ0FQX1NFVFBDQVAiLCJDQVBfTkVUX0JJTkRfU0VSVklDRSIsIkNBUF9TWVNfQ0hST09UIiwiQ0FQX0tJTEwiLCJDQVBfQVVESVRfV1JJVEUiXX0sInJsaW1pdHMiOlt7InR5cGUiOiJSTElNSVRfTk9GSUxFIiwiaGFyZCI6MTAyNCwic29mdCI6MTAyNH1dLCJub05ld1ByaXZpbGVnZXMiOnRydWV9LCJyb290Ijp7InBhdGgiOiJyb290ZnMifSwibW91bnRzIjpbeyJkZXN0aW5hdGlvbiI6Ii9wcm9jIiwidHlwZSI6InByb2MiLCJzb3VyY2UiOiJwcm9jIn0seyJkZXN0aW5hdGlvbiI6Ii9kZXYiLCJ0eXBlIjoidG1wZnMiLCJzb3VyY2UiOiJ0bXBmcyIsIm9wdGlvbnMiOlsibm9zdWlkIiwic3RyaWN0YXRpbWUiLCJtb2RlPTc1NSIsInNpemU9NjU1MzZrIl19LHsiZGVzdGluYXRpb24iOiIvZGV2L3B0cyIsInR5cGUiOiJkZXZwdHMiLCJzb3VyY2UiOiJkZXZwdHMiLCJvcHRpb25zIjpbIm5vc3VpZCIsIm5vZXhlYyIsIm5ld2luc3RhbmNlIiwicHRteG1vZGU9MDY2NiIsIm1vZGU9MDYyMCIsImdpZD01Il19LHsiZGVzdGluYXRpb24iOiIvZGV2L3NobSIsInR5cGUiOiJ0bXBmcyIsInNvdXJjZSI6InNobSIsIm9wdGlvbnMiOlsibm9zdWlkIiwibm9leGVjIiwibm9kZXYiLCJtb2RlPTE3NzciLCJzaXplPTY1NTM2ayJdfSx7ImRlc3RpbmF0aW9uIjoiL2Rldi9tcXVldWUiLCJ0eXBlIjoibXF1ZXVlIiwic291cmNlIjoibXF1ZXVlIiwib3B0aW9ucyI6WyJub3N1aWQiLCJub2V4ZWMiLCJub2RldiJdfSx7ImRlc3RpbmF0aW9uIjoiL3N5cyIsInR5cGUiOiJzeXNmcyIsInNvdXJjZSI6InN5c2ZzIiwib3B0aW9ucyI6WyJub3N1aWQiLCJub2V4ZWMiLCJub2RldiIsInJvIl19LHsiZGVzdGluYXRpb24iOiIvcnVuIiwidHlwZSI6InRtcGZzIiwic291cmNlIjoidG1wZnMiLCJvcHRpb25zIjpbIm5vc3VpZCIsInN0cmljdGF0aW1lIiwibW9kZT03NTUiLCJzaXplPTY1NTM2ayJdfV0sImxpbnV4Ijp7InJlc291cmNlcyI6eyJkZXZpY2VzIjpbeyJhbGxvdyI6ZmFsc2UsImFjY2VzcyI6InJ3bSJ9XX0sImNncm91cHNQYXRoIjoiL2RlZmF1bHQvcmVkaXMxIiwibmFtZXNwYWNlcyI6W3sidHlwZSI6InBpZCJ9LHsidHlwZSI6ImlwYyJ9LHsidHlwZSI6InV0cyJ9LHsidHlwZSI6Im1vdW50In0seyJ0eXBlIjoibmV0d29yayJ9XSwibWFza2VkUGF0aHMiOlsiL3Byb2MvYWNwaSIsIi9wcm9jL2tjb3JlIiwiL3Byb2Mva2V5cyIsIi9wcm9jL2xhdGVuY3lfc3RhdHMiLCIvcHJvYy90aW1lcl9saXN0IiwiL3Byb2MvdGltZXJfc3RhdHMiLCIvcHJvYy9zY2hlZF9kZWJ1ZyIsIi9zeXMvZmlybXdhcmUiLCIvcHJvYy9zY3NpIl0sInJlYWRvbmx5UGF0aHMiOlsiL3Byb2MvYXNvdW5kIiwiL3Byb2MvYnVzIiwiL3Byb2MvZnMiLCIvcHJvYy9pcnEiLCIvcHJvYy9zeXMiLCIvcHJvYy9zeXNycS10cmlnZ2VyIl19fQ=="
    },
    "SnapshotKey": "redis1",
    "Snapshotter": "overlayfs",
    "CreatedAt": "2018-07-30T11:18:28.151489308Z",
    "UpdatedAt": "2018-07-30T11:18:28.151489308Z",
    "Extensions": null
}
ubuntu@v-Ubuntu:~$ 

スナップショットを表示する

スナップショットはDockerイメージでいうところのレイヤーです。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot
NAME:
   ctr snapshots - manage snapshots

USAGE:
   ctr snapshots command [command options] [arguments...]

COMMANDS:
     commit            commit an active snapshot into the provided name
     diff              get the diff of two snapshots. the default second snapshot is the first snapshot's parent.
     info              get info about a snapshot
     list, ls          list snapshots
     mounts, m, mount  mount gets mount commands for the snapshots
     prepare           prepare a snapshot from a committed snapshot
     remove, rm        remove snapshots
     label             add labels to content
     tree              display tree view of snapshot branches
     unpack            unpack applies layers from a manifest to a snapshot
     usage             usage snapshots
     view              create a read-only snapshot from a committed snapshot

OPTIONS:
   --snapshotter value  snapshotter name. Empty value stands for the default value. (default: "overlayfs") [$CONTAINERD_SNAPSHOTTER]
   --help, -h           show help
   
ubuntu@v-Ubuntu:~$ 

ctr snapshot lsでスナップショット一覧を表示します。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot ls
KEY                                                                     PARENT                                                                  KIND      
redis1                                                                  sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85 Active    
sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85 sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22 Committed 
sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016 sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b Committed 
sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632 sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af Committed 
sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af                                                                         Committed 
sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632 Committed 
sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22 sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016 Committed 
ubuntu@v-Ubuntu:~$ 

どのスナップショットがどのスナップショットの親かが分かるのがいいですね。ちなみにスナップショットredis1はコンテナredis1のwritableなスナップショットです。それ以外はredisのイメージに含まれるRead Onlyなスナップショットですね。

ctr snapshot treeはスナップショットの親子関係をツリーで表示してくれます。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot tree
 sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af
  \_ sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632
    \_ sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b
      \_ sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016
        \_ sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22
          \_ sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85
            \_ redis1
ubuntu@v-Ubuntu:~$ 

タスクを削除する

ctr task deleteでタスクを終了させることができます。

ubuntu@v-Ubuntu:~$ sudo ctr task ls
TASK      PID      STATUS    
redis1    16722    RUNNING
ubuntu@v-Ubuntu:~$ sudo ctr task delete redis1
ctr: task must be stopped before deletion: running: failed precondition
ubuntu@v-Ubuntu:~$ 

タスクを削除する前にタスクを停止しろって言ってるので、一回停止してから削除をやりましょう。ctr killでコンテナにSIGTERMのシグナルを送って停止させます。

ubuntu@v-Ubuntu:~$ sudo ctr task kill redis1
ubuntu@v-Ubuntu:~$ sudo ctr task ls
TASK      PID      STATUS    
redis1    16722    STOPPED
ubuntu@v-Ubuntu:~$ sudo ctr task delete redis1
ubuntu@v-Ubuntu:~$ sudo ctr task ls
TASK    PID    STATUS    
ubuntu@v-Ubuntu:~$ 

コンテナを削除する

タスクを削除してもコンテナは残り続けます。

ubuntu@v-Ubuntu:~$ sudo ctr container ls
CONTAINER    IMAGE                             RUNTIME                           
redis1       docker.io/library/redis:latest    io.containerd.runtime.v1.linux    
ubuntu@v-Ubuntu:~$ 

ctr container rmでコンテナを削除しましょう。

ubuntu@v-Ubuntu:~$ sudo ctr container rm redis1
ubuntu@v-Ubuntu:~$ sudo ctr container ls
CONTAINER    IMAGE    RUNTIME    
ubuntu@v-Ubuntu:~$

これを実行するとついでにwritableなスナップショットも削除されます。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot ls
KEY                                                                     PARENT                                                                  KIND      
sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85 sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22 Committed 
sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016 sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b Committed 
sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632 sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af Committed 
sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af                                                                         Committed 
sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632 Committed 
sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22 sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016 Committed 
ubuntu@v-Ubuntu:~$ 

ここで注意なんですが、コンテナより先にスナップショットを削除しないようにしましょう。スナップショットを削除してからコンテナを削除しようとすると以下のようなエラーが発生します。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot rm redis1
ubuntu@v-Ubuntu:~$ sudo ctr container ls
CONTAINER    IMAGE                             RUNTIME                           
redis1       docker.io/library/redis:latest    io.containerd.runtime.v1.linux    
ubuntu@v-Ubuntu:~$ sudo ctr container rm redis1
ERRO[0000] failed to delete container "redis1"           error="snapshot redis1 does not exist: not found"
ctr: snapshot redis1 does not exist: not found
ubuntu@v-Ubuntu:~$ 

「redis1のスナップショットがねーよ」って言ってますね。

こういう時の対処法としてはctr snapshot viewで同名のスナップショットを作ってやるって方法があります。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot view --help
NAME:
   ctr snapshots view - create a read-only snapshot from a committed snapshot

USAGE:
   ctr snapshots view [command options] [flags] <key> [<parent>]

OPTIONS:
   --target value, -t value  mount target path, will print mount, if provided
   
ubuntu@v-Ubuntu:~$

parentは適当に指定してスナップショットredis1を作成します。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot view redis1 sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85
ubuntu@v-Ubuntu:~$ sudo ctr snapshot ls
KEY                                                                     PARENT                                                                  KIND      
redis1                                                                  sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85 View      
sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85 sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22 Committed 
sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016 sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b Committed 
sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632 sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af Committed 
sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af                                                                         Committed 
sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632 Committed 
sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22 sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016 Committed 
ubuntu@v-Ubuntu:~$ sudo ctr container rm redis1
ubuntu@v-Ubuntu:~$ sudo ctr container ls
CONTAINER    IMAGE    RUNTIME    
ubuntu@v-Ubuntu:~$ 

すると見事に削除できるようになりました。

イメージを削除する

ctr image rmでイメージを削除できます。

ubuntu@v-Ubuntu:~$ sudo ctr image rm docker.io/library/redis:latest
docker.io/library/redis:latest
ubuntu@v-Ubuntu:~$ sudo ctr image ls 
REF TYPE DIGEST SIZE PLATFORMS LABELS 
ubuntu@v-Ubuntu:~$ 

スナップショットも綺麗さっぱりなくなります。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot ls
KEY PARENT KIND 
ubuntu@v-Ubuntu:~$ 

まとめ

ctrを使ってcontainerdに対して基本的な操作を行いました。今の世の中コンテナランタイムは色々種類がありますし、色々触って使い心地(笑)を比較してみるのもいいかもしれませんね。

ちなみにちなみに、containerdのバージョン1.1にはcriのプラグインが備わっており、CRI v1alpha2に対応しています。というわけでctr以外にcrictlのようなCLIツールで操作を行うことが可能になっています。

今度はcrictlで操作してみたさがありますね。また、CNIの設定もちゃんとやりたいです。

おまけ

ボツネタにしようと思ったけどやっぱ記事に残しておくことにした内容

スナップショットを削除する

再度redisのイメージをプル

ubuntu@v-Ubuntu:~$ sudo ctr image pull docker.io/library/redis:latest
ubuntu@v-Ubuntu:~$ sudo ctr snapshot tree
 sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af
  \_ sha256:aee8b479b9a768a64f4c32d69108566fbdbb71c8e541496dd1fa9f7ad19d8632
    \_ sha256:e6b3eda8746c5cc312ebb40e1ca5c064638af429b9b3848280aab8ed882bd10b
      \_ sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016
        \_ sha256:f69435472d99c717999e1e458828567320e5f5375365c9551c2d13b489f30a22
          \_ sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85
ubuntu@v-Ubuntu:~$

特に深い意味はないんですけどスナップショットsha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016の削除を試みます。スナップショットの削除はctr snapshot rmコマンド。

ubuntu@v-Ubuntu:~$ sudo ctr snapshot rm sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016
ctr: failed to remove "sha256:7460940a3a5274245b091035dbbb1b724e5bd2d2135326d61f542de962f3a016": cannot remove snapshot with child: failed precondition
ubuntu@v-Ubuntu:~$ 

子のスナップショットがいるときは削除できないようになっているようです。賢い。

じゃあ子がいないスナップショットを削除してみましょうか

ubuntu@v-Ubuntu:~$ sudo ctr snapshot rm sha256:3c7bfc410c0e316ba2b72a18e1ec45263b81921eafbec05d87cfb6dfd2a31f85
ubuntu@v-Ubuntu:~$ sudo ctr snapshot ls
KEY PARENT KIND 
ubuntu@v-Ubuntu:~$ sudo ctr image ls -q
docker.io/library/redis:latest
ubuntu@v-Ubuntu:~$ 

スナップショット全部消えるんですね・・・でもイメージは残ってるのか・・・

これでctr image rmiを実行したら何が起こるか気になりました(なんとなくctr container rmの時のエラーみたいに「スナップショット○○が存在しない」ってメッセージが出ると思いました)

ubuntu@v-Ubuntu:~$ sudo ctr image rm docker.io/library/redis:latest
docker.io/library/redis:latest
ubuntu@v-Ubuntu:~$ sudo ctr image ls
REF TYPE DIGEST SIZE PLATFORMS LABELS 
ubuntu@v-Ubuntu:~$ 

普通に削除できちゃった。

コンテナのポートバインディング

タスクのポートバインディングって言った方がいいのかな?containerdではDockerのdcoker0みたいな仮想のブリッジネットワークは備わってないみたいです。ただホストネットワークは使用できます。そんなわけでコンテナの80番ポートをホストの80番ポートにバインドするなんてことができます。

それでは再度redisのイメージをプルしてコンテナを起動してしましょう。オプション--net-hostをつけてctr runを実行します。

ubuntu@v-Ubuntu:~$ sudo ctr image pull docker.io/library/redis:latest
ubuntu@v-Ubuntu:~$ sudo ctr run -d --net-host docker.io/library/redis:latest redis1
ubuntu@v-Ubuntu:~$ ss -atn | grep 6379
LISTEN     0      128          *:6379                     *:*                  
LISTEN     0      128         :::6379                    :::*                  
ubuntu@v-Ubuntu:~$ 

ホストの6379番ポートが空いています。このポートで何のプロセスが動いているかを調べてみると、redisのプロセスが動いていることがわかりました。

ubuntu@v-Ubuntu:~$ sudo lsof -i:6379
lsof: no pwd entry for UID 999
lsof: no pwd entry for UID 999
COMMAND     PID     USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
lsof: no pwd entry for UID 999
redis-ser 18748      999    6u  IPv6 3558672      0t0  TCP *:6379 (LISTEN)
lsof: no pwd entry for UID 999
redis-ser 18748      999    7u  IPv4 3558673      0t0  TCP *:6379 (LISTEN)
ubuntu@v-Ubuntu:~$ 

というわけでポートバインディングは成功です。

docker-containerd、docker-containerd-shim、 docker-containerd-ctr、docker-runc

stack overflowのdockerd vs docker-containerd vs docker-runc vs docker-containerd-ctr vs docker-containerd-shimの補足的な記事です。

各コマンドの説明と、これらを説明する上で必要なcontainerdやCRIの話もちょっと書きます。

一応自分の環境の情報

ubuntu@v-Ubuntu:~$  docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 6
Server Version: 17.03.2-ce
Storage Driver: overlay2
 Backing Filesystem: extfs
 Supports d_type: true
 Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins: 
 Volume: local
 Network: bridge host macvlan null overlay
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 4ab9917febca54791c5f071a9d1f404867857fcc
runc version: 54296cf40ad8143b62dbcaa1d90e520a2136ddfe
init version: 949e6fa
Security Options:
 apparmor
 seccomp
  Profile: default
Kernel Version: 4.4.0-130-generic
Operating System: Ubuntu 16.04.2 LTS
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 23.54 GiB
Name: v-Ubuntu
ID: QWK4:E7QH:2SSD:QIOB:PQ2V:D4GL:M43V:5WVY:GQBT:XJCG:OSHA:5WBT
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Experimental: false
Insecure Registries:
 127.0.0.0/8
Live Restore Enabled: false

WARNING: No swap limit support

事前知識1: Dockerとcontainerd

f:id:etogen:20180730010524j:plain

https://hackernoon.com/docker-containerd-standalone-runtimes-heres-what-you-should-know-b834ef155426

Docker 1.1からDocker Engineにcontainerdが統合されました。containerdはコンテナランタイムの一種でコンテナの管理やコンテナイメージの管理を行ってくれます。

docker runなどでコンテナを作成する時はcontainerdの方に命令が渡されるんですが、そこからさらにruncに命令が行きます。runcは実際にコンテナを実行・作成するコンテナランタイムで、こちらはコンテナイメージの管理などは行いません。

両方ともコンテナランタイムでちょっと紛らわしい。どこで聞いたかは覚えてないんですが、containerdはHigh Level Container Runtime、runcはLow Level Container Runtimeって呼ばれているのを耳にしたことがあります。

詳しくはここがすごいわかりやすいのでを読んでくださいね(投げやり)

事前知識2: CRI

CRI(Container Runtime Interface)は簡単に言うと「kubeletとお話するために、これこれこういうserviceを実装してくださいね」というgRPCの仕様です。その仕様はapi.protoを見ればわかると思います。

containerdはCRIに対応していて、実際Kubernetesのコンテナランタイムとして動かすことができます。

CRIにはバージョンが複数存在するので注意してください。containerd/criのリポジトリのREADMEKubernetesとCRIのバージョンの対応表は見ておくと良いでしょう。

また、containerdはバージョンによってはkubeletとの通信でcri-contianerdを仲介させなければならない場合があります。Kubernetes対応コンテナランタイム「containerd 1.1」正式リリース。CRIにネイティブ対応し、Dockerより軽量で高速な動作を実現Containerd Brings More Container Runtime Options for Kubernetesをご参考に。

各コマンドの説明

docker-containerd

Dockerに統合されているcontainerdちゃんです。こいつはdockerdを起動すると、子プロセスとして一緒に起動されます。

dockerdを実行した後にpsコマンドを実行するとこいつが動いているのが確認できます。

ubuntu@v-Ubuntu:~$ sudo systemctl start docker
ubuntu@v-Ubuntu:~$ ps -alfx | grep docker
0  1001 14525 11858  20   0  13956   988 pipe_w S+   pts/0      0:00  |           \_ grep --color=auto docker
4     0 14327     1  20   0 637884 38944 -      Ssl  ?          0:00 /usr/bin/dockerd -H fd:// -s overlay2 -H tcp://127.0.0.1:2375
4     0 14334 14327  20   0 349300  9832 -      Ssl  ?          0:00  \_ docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
ubuntu@v-Ubuntu:~$ 

コマンドのオプションを見ればわかると思うんですが、unixドメインソケットをエンドポイントとしていてここを叩くと色々操作ができるようになっています。ちなみにこのソケットではgRPCの通信が行われるのでcurlAPIを叩くことはできません。docker-containerd-ctrを使えばこのAPIを叩くことができます。

docker-containerd-ctr

上でも書きましたがcontainerdを操作するCLIツールです。実際に使ってみましょう。

ubuntu@v-Ubuntu:~$ sudo docker-containerd-ctr
NAME:
   ctr - High performance container daemon cli

USAGE:
   docker-containerd-ctr [global options] command [command options] [arguments...]
   
VERSION:
   0.2.3 commit: 4ab9917febca54791c5f071a9d1f404867857fcc
   
COMMANDS:
   checkpoints  list all checkpoints
   containers   interact with running containers
   events   receive events from the containerd daemon
   state    get a raw dump of the containerd state
   version  return the daemon version
   help, h  Shows a list of commands or help for one command
   
GLOBAL OPTIONS:
   --debug                      enable debug output in the logs
   --address "unix:///run/containerd/containerd.sock"   proto://address of GRPC API
   --conn-timeout "1s"                  GRPC connection timeout
   --help, -h                       show help
   --version, -v                    print the version
   
ubuntu@v-Ubuntu:~$ sudo docker-containerd-ctr version
[ctr] rpc error: code = 14 desc = grpc: the connection is unavailable
ubuntu@v-Ubuntu:~$ 

containerdのunixドメインソケットのパスをオプションで渡してやる必要があるようですね。

ubuntu@v-Ubuntu:~$ sudo docker-containerd-ctr version
[ctr] rpc error: code = 14 desc = grpc: the connection is unavailable
ubuntu@v-Ubuntu:~$ sudo docker-containerd-ctr --address "unix:///var/run/docker/libcontainerd/docker-containerd.sock" version
daemon version 0.2.3 commit: 4ab9917febca54791c5f071a9d1f404867857fcc
ubuntu@v-Ubuntu:~$ sudo docker-containerd-ctr --address "unix:///var/run/docker/libcontainerd/docker-containerd.sock" state
{"machine":{"cpus":4,"memory":24102}}ubuntu@v-Ubuntu:~$ 

OK〜!ちなみにここのgRPCの通信はCRIではなくcontainerd独自のgRPCのインタフェースに乗っ取った通信なので注意してください。crictl(CRIを叩くことができるCLIツール)でこのAPIを叩くなんてことは当前できません。

docker-containerd-shim

コンテナを作成する際はruncを実行するんですけど、これを使うことでruncを終了させてコンテナをデーモンレスで実行させることができます。このコマンド、実はdocker runでコンテナを作成するたびに裏で実行しています。実際に確認してみましょう。

現在dockerddocker-containerdのプロセスが存在します。

ubuntu@v-Ubuntu:~$ ps aux | grep docker
root     13339  0.0  0.1 643428 42508 ?        Ssl  02:01   0:02 /usr/bin/dockerd -H fd:// -s overlay2 -H tcp://127.0.0.1:2375
root     13347  0.0  0.0 226552 11248 ?        Ssl  02:01   0:01 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
ubuntu   13934  0.0  0.0  13956   932 pts/0    S+   02:41   0:00 grep --color=auto docker
ubuntu@v-Ubuntu:~$ 

ここでnginxのコンテナを作成するとdocker-containerd-shimのプロセスが開始されます。

ubuntu@v-Ubuntu:~$ docker run -d  nginx
94d5e8ba1dc72f68ae4c0d636e841701f8f1c812da240e562b4e47d9978c1b3f
ubuntu@v-Ubuntu:~$ ps aux | grep docker
root     13339  0.0  0.1 643428 42700 ?        Ssl  02:01   0:02 /usr/bin/dockerd -H fd:// -s overlay2 -H tcp://127.0.0.1:2375
root     13347  0.0  0.0 226552 11268 ?        Ssl  02:01   0:01 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
root     13999  0.0  0.0 413060  3224 ?        Sl   02:42   0:00 docker-containerd-shim 94d5e8ba1dc72f68ae4c0d636e841701f8f1c812da240e562b4e47d9978c1b3f /var/rundocker/libcontainerd/94d5e8ba1dc72f68ae4c0d636e841701f8f1c812da240e562b4e47d9978c1b3f docker-runc
ubuntu@v-Ubuntu:~$ 

このプロセスは1コンテナにつき1つ実行されます。

docker-runc

OCI準拠の設定を読み込んでコンテナを作成するruncちゃんです。Linuxのcgroupsとかnamespacesとかを使って本当に低いレイヤの処理を行ってくれてるようです(ここら辺ちゃんと勉強してないのであんまりよくわかってない)

Linux上にDockerをインストールするまでの作業

これと同じことをやることが頻繁にあるので、手順書(笑)にしておくことにしました。リモートマシンのUbuntuにDockerをインストールすることを想定。

SSH鍵転送

SSH鍵作成

TARGETは適宜変更。

TARGET=sushi

鍵作成

mkdir ~/.ssh/$TARGET
ssh-keygen -t rsa -b 4096 -N "" -C "$TARGET" -f ~/.ssh/$TARGET/id_rsa

鍵転送

変数は適宜変更。

TARGET_HOST=sushi1
TARGET_NAME=10.10.10.10
TARGET_USER=jony
TARGET_PORT=22

鍵送信

cat << EOF >> ~/.ssh/config
Host $TARGET_HOST
    HostName $TARGET_NAME
    User $TARGET_USER
    Port $TARGET_PORT
    IdentityFile ~/.ssh/$TARGET/id_rsa

EOF

ssh-copy-id -i ~/.ssh/$TARGET/id_rsa $TARGET_HOST

Dockerインストール

sshリモートホストに接続する。

sudoをパスワードなしでできるようにする

sudo -s

TARGET_USERは適宜変更

TARGET_USER=jony

sudoers編集

echo "$TARGET_USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
exit

Dockerインストール

変数は適宜変更。

TARGET_USER=jony
TARGET_DOCKER_VERSION=17.03

Hosts in Rancherにあるスクリプトを使うと、楽に特定のバージョンのDockerをインストールできます。

sudo apt-get update
sudo apt-get -y upgrade
curl https://releases.rancher.com/install-docker/${TARGET_DOCKER_VERSION}.sh | sh
sudo usermod -aG docker $TARGET_USER