takaha.siの技術メモ

勉強したことをお伝えします。ちょっとでも誰かの役に立てればいいな…

Let's Encryptの証明書失効から学ぶループと末尾再帰の関係

この間、Let's Encryptがバグで300万近くの証明書を失効させたというニュース流れました。

事の詳しい顛末は以下のBlogにかかれています。かなり詳しく書かれており、どのようなコードでバグが出たのか?というところまで解説されています。

jovi0608.hatenablog.com

さて、ちょっと話が飛ぶようなのですが、このBlogを読んで「ループと末尾再帰は本当に同等なのか?」という前々から私の頭の片隅にあった疑問がまたむくむくと頭を持ち上げてきましたので書き散らしました。

「ループと末尾再帰は同等である」というのはなんか関数型言語をかじった人の口からよく聞くような気がするのですが、私はそれに対して「そんな簡単に言ってしまっていいのか?」とずっと疑問を抱いていました。今回それを言語化しておこうと思います。

えっ?Let's Encryptの証明書失効と「ループと末尾再帰」になんの関係があるのかって?まぁちょっと話を聞いてください。

詳しくは上述したBlogを読んでほしいのですが、Go言語では以下のようなコードを書くと

func main() {
     var out []*int
     for i := 0; i < 3; i++ {
         out = append(out, &i)
     }
     fmt.Println("Values:", *out[0], *out[1], *out[2])
     fmt.Println("Addresses:", out[0], out[1], out[2])
}
$ ./test_gomistake
Values: 3 3 3
Addresses: 0xc0000160a0 0xc0000160a0 0xc0000160a0

このような結果になるそうです。要はoutに一時変数iの参照を入れてしまい、すると、ループが抜けたあとはiの値はfor文が進むにつれて変化しており最終的には3になります。結果、for文を抜けたあとoutの中身が全部3なってしまうという仕様です。

このgoの仕様をうっかりミスで忘れてしまい、outのなかは0, 1, 2になるという仮定してコードを書いてしまいバグを作り込んでしまったそうです。その結果が300万近くの証明書失効につながったと。恐ろしい。。。

これを受けて、Gauche開発者のShiro Kawaiさんがこんな事をつぶやいておられました

Shiroさんは「クロージャに閉じ込んじゃうのもありがち。」といってますが、そう。それですよ、私が言いたかったのは。ちょっとCommon Lispで以下のようなコードを書いてみましょうか。

CL-USER 1 > (do ((i 0 (+ i 1))
     (j '() (cons #'(lambda () i) j)))
    ((>= i 3) j))

(#<anonymous interpreted function 21CE99DA> #<anonymous interpreted function 21CCEA4A> #<anonymous interpreted function 20107912>)

このプログラムがやっていることはdoループ構文でiの値を0から3まで回して、lambdaでiの値を返すクロージャを作りそのクロージャのリストを返すプログラムです。つまり、このループが返すのは作ったクロージャへのポインタのリストです。

返ってきたのはクロージャのリストなので、リストの中にあるクロージャを改めて評価してみます。

CL-USER 2 > (mapcar #'funcall *)
(3 3 3)

どうでしょう?golangと似たような事が起こりました。もう分かると思いますが、要は、doループ内部のlambdaで束縛したiは、doループ外に出るときにはすでに3になってますので、doループから出たあとにlambdaを評価すると全部3になるんですね。

ちなみに、私はLispWorksを使いましたが、ANSI Common Lispの仕様としてこうなってるはずですので、どの処理系でも同じ結果が得られるはずです。

一方で、次のコードを考えてみましょう。

CL-USER 1 >(defun test ()
  (labels ((rec (acc i)
        (if (>= i 3)
            acc
          (rec (cons #'(lambda () i) acc) (incf i)))))
    (rec '() 0)))
TEST

CL-USER 2 > (test)
(#<anonymous interpreted function 21ACAE52> #<anonymous interpreted function 21ACAE3A> #<anonymous interpreted function 21ACAE22>)

CL-USER 3 > (mapcar #'funcall *)
(3 2 1)

こちらは先ほどと同じようにiで3回ループを回してそのiを束縛したlambdaを作りconsでリストに放り込んでいます。傍目には先のdo構文で上げた例と似た結果が得られているように見えます。

しかし、今回はdoループではなく、末尾再帰を使ったコードになっているのがわかるかと思います。

結果を見てましょう。3, 2, 1と異なる結果が得られました。つまり、末尾再帰とループとではiの束縛内容が異なると言えます。

ループの場合がある意味でdynamic binding的な挙動を示すのに対して、末尾再帰を用いた場合、iはlexical closureとして明確にiが閉じ込められていることがわかります。ですので、ループの場合は3, 3, 3となりますが、末尾再帰の場合は3, 2, 1となります(2, 1, 0とならないのはincfを使って破壊的に変更してるからです。やはり怖い破壊的変更)

僕が言いたかったのはこういうことです。ループと末尾再帰は「同じだ」と言われることがある気がするのですが、その実、束縛される変数。つまり環境のことを考えると同じと言えないのではないのでしょうか?また変数がどのように束縛されるかといった多分に言語仕様に依存してくる話だと思うのでそんな乱暴な話でまとめちゃっていいのかなぁと思います。関数型言語を標榜しているがloopもサポートする言語はたくさんあると思うので。

とういわけで、ループと末尾再帰が同じという言を聞くと本当かなぁ?と首を傾げてしまいます。ここらへん理論的にはどうなってんでしょうね?

p.s. いやShiroさんの件のツイートをみてつい懐かしいような気がして書きなぐってしまいした。

TLC Model CheckerをCUI(コマンドライン)で使う

TLC Model Checkerとは形式仕様記述言語であるTLA+のモデル検査ツールです。TLA+で書かれた仕様の不変式のviolationやLivenessやSafety 特性のviolationの検出などをしてくれます。

そんで、このTLC Model Checkerというのは、CUIで使えたほうが便利だと思うんですよね。実はTLA+にはTLA ToolboxというIDEがあって、GUIからマウスポチポチで呼び出して検証することもできるのですが、仕様検証という作業は(仕様規模によりますが)すごいCPUパワーとストレージを食います。

そのため、実務的な観点で言えば、DCとかにおいてある高性能なコンピュータにSSHで入って操作することになると思うのですが、この場合GUIは邪魔になります。

tkh86.hateblo.jp

この間公開したスライドでもTLC Model CheckerをCUIで使っています。でもCUITLC Model Checkerをどうやってインストールするのかを解説してませんでしたのでここで書いておきます。

github.com

このtla-binというツールを使えば簡単にオフィシャルのTLA+環境一式をCUI化してインストールできます。

マニュアルにも書いてありますが、こんな感じでやればインストールできます。インストール先はデフォルトだと /usr/local になりますが、第一引数に与えればインストール先は変えられます。

$ git clone https://github.com/pmer/tla-bin.git
$ ./download_or_update_tla.sh
$ sudo ./install.sh

やってることは、オフィシャルページのTLA+ Toolboxをダウンロードしてjarを解凍してjavaのclassファイルを取り出し、適切なオプションを付けてJVMで実行しているみたいです。

なお、TLA+のツール一式はJavaで書かれていますのでJavaのインストールも必要です。OpenJDKでは動きません。Oracle JVMじゃないとだめですね。

[xxx@host:~/xxx/tla+/raft]$ tlc -config SPEC.cfg ./first.tla
TLC2 Version 2.14 of 10 July 2019 (rev: 0cae24f)
Running breadth-first search Model-Checking with fp 82 and seed 1539151492344761609 with 1 worker on 24 cores with 21424MB heap and 64MB offheap memory (Linux 3.10.0-862.9.1.el7.x86_64 amd64, Oracle Corporation 1.8.0_181 x86_64, MSBDiskFPSet, DiskStateQueue).
Parsing file /home/xxx/xxx/tla+/raft/first.tla
Parsing file /tmp/Naturals.tla
Parsing file /tmp/FiniteSets.tla
Parsing file /tmp/Sequences.tla
Parsing file /tmp/TLC.tla
Semantic processing of module Naturals
Semantic processing of module Sequences
Semantic processing of module FiniteSets
Semantic processing of module TLC
Semantic processing of module first
Starting... (2020-02-13 11:51:47)
Error: TLC threw an unexpected exception.
This was probably caused by an error in the spec or model.
See the User Output or TLC Console for clues to what happened.
The exception was a java.lang.NullPointerException
java.lang.NullPointerException
        at tlc2.tool.impl.SpecProcessor.processSpec(SpecProcessor.java:346)
        at tlc2.tool.impl.SpecProcessor.<init>(SpecProcessor.java:157)
        at tlc2.tool.impl.Spec.<init>(Spec.java:121)
        at tlc2.tool.impl.Tool.<init>(Tool.java:126)
        at tlc2.tool.impl.Tool.<init>(Tool.java:121)
        at tlc2.tool.impl.Tool.<init>(Tool.java:116)
        at tlc2.TLC.process(TLC.java:930)
        at tlc2.TLC.main(TLC.java:247)
Finished in 00s at (2020-02-13 11:51:47)

OpenJDKだとヌルポで動かないです。Oracle JVMを使いましょう。

TLA+の社内勉強会スライド公開します

Raftを使ってDFSを作るという仕事を数年前ぐらいから続けています。そのためか最近、形式仕様記述(特にTLA+)とかModel checkingとかTheorem provingとかが私の中でアツいです。TLA+とかAlloyとかVDM++とかSpin周りのやつです。

ソフトウェアの仕様を形式仕様で厳密に書き下して検証、証明するという手法がある!ということ自体は院生時代に見聞きして知ってたんですが「難しそう」とか「一部のすごい人にしか使えなさそう」といった先入観があってなんとなく敬遠してました。正直、当時は自分の身近な問題であると感じられていませんでした。

あれから10年以上たって最近形式仕様記述とかModel checkingとかTheorem provingというものは「必須なものである」という認識がようやく私の中にも芽生えたようです。仕事で分散システムの開発を通じて「これは人間の頭(複数人の設計レビューとかコードレビューとかテスト)だけではデバッグ不可能なのだ!」というのが骨身にしみてわかったからだと思います。

10年前とはちがって、Amazonでの大規模な形式仕様記述(TLA+)とModel checkingの導入による成功例なども表立って論文の形としてまとまってたりしてて、もうこれはやるっきゃ無いだろという感じです。

今はAWSとかでちょっと気の利いたサービスつくると、もうそれは分散システムになっちゃうんですよね。ここ10年間だけを切り取ってみても、ソフトウェアエンジニアが作るシステムの複雑さが格段に上がっているように見えます。

そんな中で形式仕様記述とかModel checkingとかは今後ソフトウェアエンジニアにとって必須のスキルになっていくんじゃないかなと思っています。ので、みなさんもぜひ見てみてください。

このスライドでTLA+のさわりと、TLC Model Checkerでの仕様バグの検証のやり方とか。一通りわかると思います。

あ、間違ってたらこっそり教えて下さい!

プログラムの正しさを数学的に証明する形式検証への招待

principia.connpass.com

Twitter眺めてたら面白そうなセミナーがあったので申し込んだ。

私はオブジェクトストレージではないPOSIX IFな分散ファイルシステムを作るという面白い仕事をもう数年間ぐらい続けている。

その関係で最近TLA+とTLC Model Checkerを業務で使っている。私が「導入すべき」と宣言して先陣を切った感じ。そのため、形式仕様記述言語やTheorem ProvingやModel checkerへの興味がぐんと増している。せっかくなのでいろいろ教えてもらおう。

定理証明支援系はCoqというものがあるということぐらいしか知らないし、そもそもCoqが何を手伝ってくれるのかすらよく理解してないのでいい機会だと思う。

このセミナーではIsabelleというものを使うそうだが。これは初耳。楽しみ。

scalaのtraitの危険性

踏んだのでメモ

Scalaにはtraitという機能があります。

traitは「それ単体で動作することはなく、他classを拡張(mixin)するか、他classから継承されることによって動作するエンティティ」と定義して良いと思います。

JavaのInterfaceにたとえられることもありますけども、JavaのInterfaceが実装を持つことができないのにたいして、Scalaのtraitは実装を持つことができます。

Scalaのtraitは便利で、mixinしてclassに機能をどんどん追加していくという事ができます。つまり、実質、多重継承のようなこと事ができてしまいます。

が、この機能、利便性と引き換えに危険性が伴います。乱用すると複雑な継承関係を容易に生み出してしまい、コードの可読性が低下します。そして、最悪NullPointerException(ヌルポ)でプログラムが死にます。ScalaはOptionさえ適切に使ってればヌルポは根絶できるのだと無邪気に考えていた私は甘かったです。

以下にコードの例を示します:

class RPC(target: String) {
  def send(msg: String): Unit = {
    println(s"sent! ${msg} to ${target}\n")
  }
}

class RPCBinder(rpc: RPC) {
  def send(msg: String): Unit = {
    rpc.send(msg)
  }
}

trait Base {
  val rpc: RPC
  val binder: RPCBinder = new RPCBinder(rpc)
}

trait BaseDefault { base: Base =>
  val rpc = new RPC("Node0")
  def sendHello(): Unit = {
    binder.send("Hello")
  }
}

trait BaseFunc1 { base: Base =>
  def sendFunc1(): Unit = {
    binder.send("Func1")
  }
}

class BaseMode1 extends Base with BaseDefault with BaseFunc1 {
  def doSend(): Unit = {
    sendHello()
    sendFunc1()
  }
}

object Main {
  def main(args: Array[String]){
    println("start!!")
    val base = new BaseMode1()
    base.doSend()
    println("end!!")
  }
}

これは、基底のtraitであるBaseに対して、Base traitに限ってmixinできるBaseDefault, BaseFunc1を定義し、BaseMode1でBaseDefault, BaseFunc1をmixinしています。

このコード。実行すると、NullPointerExceptionが起こります。

[thoth@~/programs]$ scala bbbb.scala
start!!
java.lang.NullPointerException
        at RPCBinder.send(bbbb.scala:9)
        at BaseDefault.sendHello(bbbb.scala:21)
        at BaseDefault.sendHello$(bbbb.scala:20)
        at BaseMode1.sendHello(bbbb.scala:31)
        at BaseMode1.doSend(bbbb.scala:33)
        at Main$.main(bbbb.scala:42)
        at Main.main(bbbb.scala)

まあちょっと考えれば、このコードがなぜヌルポが引き起こすのかはすぐわかると思います。trait BaseでRPCBinderをnewしたときに(15行目)参照しているtrait Baseのval rpc(14行目)がまだ決定されておらず、RPCBinderにnullが放り込まれてnewされちゃうんですね。

trait Baseのval rpcはインプリされていないとコンパイルエラーが出るからヌルポは回避できるのでは?と思いきや、trait BaseDefaultの方でインプリ(19行目)されており、BaseModel1でmixinされてしまっている(31行目)ので、コンパイルは通ってしまうわけです。

そもそもこんな意味不明なコードを書くんじゃない!というのはもちろんそのとおりです。しかし、脳死プログラミングしているときでさえこんなコードには絶対ならない!と断言はできないと思いますし、なまじコンパイルが通ってしまうのでエラーは実行時に当該のパスを踏むまで見抜けません。厄介です。ちなみに私はリファクタリングをしているときにこれに陥りました。

というわけで、scalaのtraitは注意して使わないといけないなと思いました。mixinがされているコードをレビューするときは、こういうエラーを引き起こす潜在因子がないかどうかちょっと考えてみるべきかもしれません。

なお、このコードは、trait Baseを以下のように変更すれば問題なく実行されます。

trait Base {
  val rpc: RPC
  lazy val binder: RPCBinder = new RPCBinder(rpc)
}

RPCbinderをnewするときにlazyをつければ、意図したとおり、ヌルポはでずに動きます。

動くには動きますが、コードとして明示的に記述されない初期化の順番というものを、コードを読む人に意識させてしまうという点でこのコードが良くないということに変わりはないと思います。

こういうコードを書くべきではない。陥らないように注意すべきであるというそもそも論に変わりはないでしょう。

p.s.

lazyは、Scalaに対して初期化のコードの実行順番の変更を行うものでありますが、使い方によってはもっとヤバイ自体に陥ります。それはデッドロックです。以下に例が上げられています。

gist.github.com

ヌルポはまだ可愛い方で、プログラムが落ちてくれるのでわかりやすいですよね。

10GbE時代に向けたNAS環境を構築する

時代はテンジー

8年前ぐらいにQNAP社のTS-439 Pro II+を購入してから、ずっとこれで満足してました。しかし、システム領域のFlashが不穏なエラーを吐き出しはじめたのでそろそろ買い替え時かと思い立ちました。容量もそろそろいっぱいになってきましたし。。。

そこで、今回、なるべく安価に10GbEのLAN環境と10GbE対応NASを構築しましたので構成を紹介したいと思います。

もうずいぶん前に「自作PCはやんない!」と心に決めてたんですが(時間がもったいないから)Mellanox InfiniBand ConnectX-5 EDR HCAがほしい!!!!!!!!!!…じゃぁなかった10GbEが欲しいなあとかZFSを使いたいからメモリは最低最悪でも16GBは欲しいなあとか考えてると、QNAPのNASの場合なかなかヘビーなお値段になります。そこでやむなく、パーツを柔軟に取捨選択でき、結果安価に済ませられそうな自作の道をえらびました。

家庭環境のNAS10GbEなぞ必要なのか?と訝しがる人がいるかもしれません。しかし、今どきの高速なNVMe SSD、具体的にはSamsung SSD 970 EVO Plusなどですと、シーケンシャルライトで2.4GB/secぐらいの速度はふつーにでます。RAIDを組めばもっと速度は出るでしょう。これはすでに1Gbpsを優に超える速度です。

NVMe SSDRAIDを組んだNASというのは、流石に個人ユースではまだちょっとハードルが高いかもですが、HDDのRAIDであったとしても1Gbps以上の書き込み速度は普通にでます。例えば、ZFSでHDD4台のRAIDZ2だと、メモリからのコピーで、350MB/secぐらいの速度はでます。この速度もすでに1Gbpsを上回っています。

つまり、今、広く普及している1GbpsのLAN環境ではストレージへの書き込み速度を十分にカバーできているとは言えません。もはや時代は10GbEです。テンジーの時代なのです。

NASのパーツリスト

Slack上で@yoshikigと喧々諤々の議論の末。以下のパーツセットで行くことにしました

# 品名 商品名 リンクなど
1 ケース U-NAS NSC-810A Server Chassis http://www.u-nas.com/xcart/product.php?productid=17640
2 M/B Supermicro Micro ATXマザーボード X11SSH-TF-O
3 CPU Intel Xeon E3-1275 v6 3.8 GHz Quad-Core LGA 1151 Processor
4 Memory ECC U-DIMM(Unbuffered DIMM) Micron DRAM 288pin DDR4-2400 CL17 32GB(16GB x 2)
5 M.2 SSD Samsung SSD 500GB 970 EVO
6 Power Supply ザワード [Enhance] FLEX ATX規格電源 [ 80PLUS GOLD認証・最大出力450W ] ENP7145B-07YGF
7 ケーブル AINEX ATX用電源延長ケーブル [ 15cm ] WAX-2415B-BK
8 ケーブル サンワサプライ TK-PWSAD2 [シリアルATA電源変換アダプタ 15pinオス→4pinメス] https://www.yodobashi.com/product/100000001001037761/

まずケースを決定。@yoshikigが見つけてきてくれました。U-NAS社のNSC-810A。このケースにはホットスワップできるSATAのHDDスロットが8ポートついています。大きさも家庭に置けるレベルのサイズ感。つや消しのマットブラック塗装が醸し出す高級感。剛性。すべて文句なしです。

次にこのケースに乗るM/Bを決定。SupermicroのX11SSH-TF-Oをチョイス。このM/BにはデフォルトでRJ45の10GbEが2ポートもついてます。また、それとは別に1Gのポートも一個ついてます。そしてなんといってもIPMIが使用できます。これは外せません。

しかし、欠点もあります。使えるCPUは7th/6th GenのCore i3か、CeleronPentiumXeon E3-1200 v6/v5となんだかビミョーなラインナップです。Core i7も動かせるらしいという未確認情報もありますが、試してないのでわかりません。

また M.2 SSDインターフェイスフォームファクタが2260というこれまたビミョーなIFです。2260のフォームファクタSSDは選択肢がほとんどなく、あったとしても怪しい無名メーカのものばかりです。さて、ここでカンのいい人は気づいたかもですが、上述のパーツリストにあるSamsung SSD 500GB 970 EVOのフォームファクタは2280です。つまり、このM/Bには乗っけられません。でも、大丈夫です。なんとかなりました。これはあとで解説します。

CPUは無難にXeon E3-1275 v6。まぁこのラインナップのなかだとXeon以外の選択はないかなという感じ。

メモリは32GB積みます。NASにしてはちょっと多めですが、メモリバカ食いのZFSを使用するつもりですので、多いにこしたことはありません。本来であれば、X11SSH-TF-Oがサポートする最大容量である64GBまで入れたかったですが、予算の関係上断念しました。

ちなみにメーカーはArkがやたら推してるSanMaxメモリ。ECC付きにしました。ちなみに、この構成でECCは必須ではありません。このM/BにXeonでもECCなしのメモリで動作します(@yoshikigはこの構成で動かしてます)ECCを外せばもっと安くできます、が、僕は宇宙線が怖いのでECCにしました。ブラックホールも視認できたことですし、ECC付きがよろしいでしょう。

電源のチョイスはよくわかりません。会社の上司が薦めてくれたものをそのまま買いました。80PLUS GOLDなのでいいんじゃないの?よくしらんけど?程度の認識です。

ここで紹介したM/Bとケースと電源の構成ですと、ザワードのATX電源コネクタがM/Bに届かないのでATXの電源延長ケーブル(部品表7)を別途用意する必要があります。また、この電源は4pinのペリフェラル電源が一つしか出てません。U-NAS NSC-810A Server Chassisは外部ファンで一つ、SATAのバックポート電源で一つ。計2つのペリフェラル4pin電源コネクタが必要なので、変換コネクタ(部品表8)が別途一つ必要です

価格

締めて215,563円。HDD抜きでこの価格。QNAP社で同じく8ベイで同じくらいのスペックのモデルを買えば40万円ぐらい(HDD抜きで)します。が、メモリは16GBぐらい。CPUもXeonでー4C8Tでーとかできません。ましてやIMPIなんてついてません。また、ZFS snapshotも使えません(LVMスナップショットは使えますがね)

たしかにリッチなWebUIはついてこないですが、Linuxのオペレーションに十分に慣れた人で、そこの管理コストを負担しても良い人であれば断然安いと言えるでしょう。まあ、簡単な管理WebUIなら自分で書いてもいいですしね。ちなみに、私はZFS snapshotを管理するための超簡単なWebUIをRailsで作りました。

組み立て

粛々と。

f:id:tkh86:20190426130738j:plain
パーツ一覧

上述しましたが、X11SSH-TF-OはM.2のフォームファクタが2260です。一方で購入したSamsung SSD 500GB 970 EVOのフォームファクタは2280です。つまり、ハマりません。じゃあどうするのか?テープで固定します。これで動きます。なにも問題ありません。

f:id:tkh86:20190426130920j:plain
SSDはテープで無理やり固定

ファンはCPU付属のリテールファンでギリギリ収まります。

f:id:tkh86:20190426130815j:plain
Xeonのリテールファンで収まります

配線完了。ちなみに、SATAケーブル八本はケースに付属しています。ご丁寧に各ケーブルにラベルまでついてますのでわかりやすいです。M/Bを横切ってる黒いケーブルが上述したATX電源の延長ケーブルです。写真の通り、延長ケーブルがないとM/Bまで電源が届きませんので必須です。

f:id:tkh86:20190426131938j:plain
配線完了

OSのインストール

無難にUbuntu 18.04.2 LTSを選択。何の問題もなくサクッとインストール完了。しかし、インストール後に問題発生。OS起動時に高確率でVGAの出力が死にます。IPMIの画面の方でもブラックアウトしてしまい何も写りません。

これはあくまでVGA出力が死んでるだけです。裏ではOSはちゃんと動いており、つまり、SSHとかsambaとかではつなぐことができます。まあ、NASなので、VGAなんて緊急時以外は使わないわけですが、気持ちが悪いです。どうも、SupermicroのX11SSH-TF-OのVGAは難ありみたいです。ちなみにCentOS 7ではインストールの段階でVGA出力が死んでしまってにっちもさっちもいきませんでした。

とはいえ、VGAが死ぬのはいやなので、なんとかします。どうもVGAが高解像度のモードに切り替わるときに死んでるくさいので、レガシー解像度モードに変更しちゃいます。/etc/default/grubファイルをエディタで開いてnomodesetを追記します。

GRUB_CMDLINE_LINUX_DEFAULT="maybe-ubiquity nomodeset"

こんな感じ。あとは

$ sudo update-grub

とやってgrubの設定を変更。これでVGA出力は死ななくなります。

当然VGA出力はレガシーモードになりますので解像度は低くなります。ですが、繰り返しますが、NASなのでVGA出力なんて緊急時以外使いません。すべての操作はSSHでやるわけですからこれで何も問題ありません。

10GbEのLAN環境構築

次に10GbEのLANです。10GbEのスイッチは、まあまだちょっと高いかなあという感じですね。QNAP社のスイッチで5万円ちょっと。NETGEARので6万ちょっと。家庭用のスイッチとしてはちょっと悩む値段です。

ですが、これまた@yoshikigがいいものを見つけてきてくれました。MikroTikのCRS305-1G-4S+IN。アップリンクの1Gポートが一つと、10Gの4つのSFPポートがついたスイッチです。お値段117USD。同じくMikroTik社のS+RJ10モジュールが一個50USDなので、NASとメインマシンをつなげる分でSFPモジュールを2つ買っても合計200USDちょっと。2万円ちょいぐらいですね。送料と関税を入れても、QNAPのスイッチの5万円よりは安くすみます。eurodkで購入。ちなみに日本国内では売ってないです、たぶん。

www.eurodk.com

f:id:tkh86:20190426132724j:plain
MikroTik CRS305-1G-4S+IN

ちなみに、われわれ以外にも購入して使ってる人がいます。 www.bluecore.net

blog.gaftnochec.net

NASにつなげるメインマシンの方にも10GbEカードが必要です。IntelEthernet Server Adapter X520-1を購入。これ、正規品を買うと5万円ぐらいしますが、AliExpressで怪しげなOEM品が5000円ぐらい売っています。

ja.aliexpress.com

1/10の値段なので、パチモンの可能性もあるかなと思ったのですが、購入してみました。

f:id:tkh86:20190426133457j:plain
Intel Ethernet Server Adapter X520-1

f:id:tkh86:20190426142356p:plain
無事Intel NICとして認識

結果、一応Windowsのデバイスマネージャー上ではIntelEthernet Server Adapter X520-1として認識してますし、ドライバもIntelの公式のものが使えます。また、iperfで負荷を10分間連続してかけ続けてみても、概ね9Gbit/secの速度で通信しつづけてくれます。途中で劇的に速度低下したり落ちたりする様子はありません。スイッチ、NICともに問題ないようです。

f:id:tkh86:20190426142241p:plain
10分間連続転送

計測

iperfで計測

サーバ側

$ iperf -p 4000 -s

クライアント側:

$ iperf.exe -w 10M -p 4000 -c 192.168.1.38

f:id:tkh86:20190426124211p:plain
iperf測定結果

結果は9.00Gbits/sec。十分速度出てますね。ちなみに、Windows版のiperfは

iPerf - Download iPerf3 and original iPerf pre-compiled binaries

を使いましょう。Ubuntu for Linuxのiperfはめっちゃ遅いです。9Gbit/secも出ません。I/Oエミュレーションレイヤーが遅いのかな?なんかWSLはI/O周りが遅い感じがしますよね。とはいえ、上のwindows版のiperfもcygwinのdllが同梱されてるんで…条件はWSLと同じだと思うんですがね…

sambaでの計測

次にsmbdを動かして、10GbEのLANでWindowsからNASに向かってデータをコピーしてみました。smbdのエクスポート領域をNVMeのSSDSamsung SSD 500GB 970 EVO)でext4にすると

f:id:tkh86:20190426124626p:plain
sambaでのコピー速度 (NVMe領域)

1.05 GB/s。8Gbits/secぐらいでてますね。sambaのプロトコルを噛ましてもこの速度です。これはWindows上でファイルを一度全部読み込んで、オンメモリにした状態でのコピーです(余談ですが、私のWindowsマシンのRAMは128GBまであります)

次に、smbdのエクスポート領域をHDD4台のZFS RAIDZ2にします。

f:id:tkh86:20190426125102p:plain
sambaでのコピー速度(HDD4 ZFS RAIDZ2)

同じくこれはファイルを一度全部読み込んでオンメモリにした状態からのコピーです。281MB/s。ローカルでのddのシーケンシャルライトの速度に肉薄する勢いの速度です。sambaのプロトコルは十分速いですね

ちなみに、S+RJ10モジュール。発熱がすごいです。手で触るとやけどするレベルで熱くなります。さすがに壊れるんじゃないかこれ?と思い@yoshikigが冷却するため5Gや2.5Gにリンクダウンしてみて実験してみましたが、この場合、ジャンボフレームがDisableになります。あまりS+RJ10のSFPモジュールはオススメできないかなという感じです。DACケーブルかファイバーにするべきかもしれません。

雑感

f:id:tkh86:20190426133806j:plain
設置したところ

U-NAS社のケースはオススメです。剛性もあり、高級感もあり、細かな点で気が利いていて言うことなし。

SupermicroのX11SSH-TF-Oは5万とちょい高いですけど10GbEが2ポート + 1GbEが1ポート。それにIPMIが使えることを考えれば全然安い気がします。ですが、内蔵VGAの挙動が怪しいですね。。。UbuntuCentOSともにVGA出力が高確率で死にます。買うならば、SSH経由で使うのが前提のってことで。割り切ったほうがいいかもしれません

テンジーは素晴らしい。UNICACA AN8599の怪しげなOEM NICはフツーにIntel NICとして使えます。MikroTikのCRS305-1G-4S+INもやすいですがふつうに10GbEとして実用的に使えます。

が。。。上述したとおりS+RJ10のSFPモジュールが尋常ではないぐらい発熱します…触るとやけどするレベルの発熱です。あまりの発熱から、リンクダウンして冷やそうかと思うと今度はジャンボフレームがDisableになります。

いろいろな人の話を聞くと、そもそもRJ45のSFPモジュールなんぞ使うべきではないらしいです。DACケーブルか、ファイバーにするべきだそうです。というわけで次はそれかなあ。