読者です 読者をやめる 読者になる 読者になる

人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

PHPが動くApacheのコンテナ環境をhaconiwaで1万個動かそうとしてみた

研究 運用

RubyKaigiに行くと本にサインを求められるすごいエンジニアが書いたhaconiwaというmruby製のコンテナエンジン(コンテナ環境構築の基盤ツール)があるのですが、少し試してみようと思って、とりあえず1サーバ上に1万コンテナぐらい動かそうとしてみました。久々に今回は自分の作ったOSSではなく、OSSの検証レポート的な記事になります。

haconiwaは僕の好きなOSSの一つで、それはなぜかと言うと、

  • haconiwaでコンテナを作る際に、haconiwa実行環境にはコンテナの要素機能が全て入っている必要はない
  • 必要なコンテナの要素機能を簡単に組み合わせて、自分が実現したいコンテナ、あるいは、それに準ずる環境を作れる
  • haconiwaによるコンテナ定義をRubyのDSLで表現でき、動的な設定や組み合わせの設定を簡単にかける

ということができるからです。その特性から、CentOS6のような比較的ライブラリが古い環境でも、要求に応じて簡単に動かす事ができます。

コンテナエンジンのようなソフトウェアでコンテナ環境を作る時に、これまではコンテナに関する機能を多数要求し、それを満たせない環境では使うことができなかったり、作ったり試すために非常に新しい環境や不安定な環境を必要とするパターンが多かったのですが、haconiwaは動作させるOSが提供している機能だけを使って、その状況状況に合わせて作りたいコンテナ(例えばchrootとcgroupだけ使いたい、とか)を作ることができます。さらに、設定をRubyのDSLで簡単に書けるため(その良さは後述)、拡張性と生産性を高度に実現しているコンテナエンジンなのではないかと思っています。

ということで、まずは早速試してみました。

haconiwaをビルドする

github.com

今回試す環境は、Kernelは4.8系が動いているけど、glibcは2.1.2が動いているという謎環境、というか、僕の手元のCentOS6テスト環境なのですが、そこでhaconiwaをビルドして試してみました。例えばこういう特殊な状況においても、haconiwaは上記に挙げた特性を活かして、利用者が作りたい仕様のコンテナを簡単に作る事ができます。

今回使ったハードウェアスペックは、CPU24コア、メモリ32GBです。

haconiwaのビルドは非常に簡単で、

rake compile

としてやれば良いだけです。ビルドで詰まった所としては、

  • libcapのビルドにkernel-headersがいる
  • glibc2.1.2はsetnsの呼び出し関数が実装されておらずその場合ビルドがエラーになる

あたりですが、後者については隣のエンジニアにソっと告げると、あっという間に改修して頂けるので非常に仕事が捗るわけですね。

github.com

そしてビルドが通ると、haconiwa/mruby/bin/haconiwa バイナリができるので、それをどこかにコピーして使ってみましょう。

コンテナ環境を作る

haconiwaはbootstrap的にlxcなどを使ってコンテナ環境のテンプレートを作ってくれる便利機能があるのですが、今回はそういうものが入っていない環境でしたので、普通にchroot環境を作って試すことにしました。

そこで、Apache+mod_phpが動いて、phpinfo.phpがひとまず動くような環境をudzuraさんにいただきました(これはまた今度公開します)。このあたりは、chroot環境の作り方がこれまでにも沢山色々なサイトやツールで紹介されていると思うので省略します。普通にchroot環境をつくれば良いというイメージです。

そのchroot環境を/で固めたtarファイルをもらった上で、haconiwaバイナリでDSLテンプレートを作ります。テンプレートは以下のコマンドで簡単に作成できます。

$ ./haconiwa new phpinfo.haco
assign  new haconiwa name = haconiwa-2d057697
assign  rootfs location = /var/lib/haconiwa/2d057697
create  phpinfo.haco

これで、DSLテンプレートができました。

まずは、haconiwaの最小設定などを試すべく、コメントを読みながら以下のようにDSLの定義をしました。

unless ENV['APACHE_PORT']
  raise "env APACHE_PORT not found"
end

Haconiwa.define do |config|
  # The container name and container's hostname:
  config.name = "haconiwa-2d057697-#{ENV['APACHE_PORT']}"
  # The first process when invoking haconiwa run:
  config.init_command = ["/usr/bin/env", "APACHE_PORT=#{ENV['APACHE_PORT']}", "APACHE_PID=/var/run/httpd/httpd.#{ENV['APACHE_PORT']}.pid", "/usr/sbin/httpd", "-X"]
  # If your first process is a daemon, please explicitly daemonize by:
  config.daemonize!

  # The rootfs location on your host OS
  # Pathname class is useful:
  root = Pathname.new("/var/lib/haconiwa/2d057697")
  config.chroot_to root

  # mount point configuration:
  config.add_mount_point "tmpfs", to: root.join("tmp"), fs: "tmpfs"

  # Share network etc files from host to contianer
  # You can reuse /etc/netns/${netnsname}/* files:
  config.mount_network_etc(root, host_root: "/etc")

  # Re-mount specific filesystems under new container namespace
  # These are recommended when namespaces such as pid and net are unshared:
  config.mount_independent "procfs"
  config.mount_independent "sysfs"
  config.mount_independent "devtmpfs"
  config.mount_independent "devpts"
  config.mount_independent "shm"
end

今回ひとまずは上記のようなDSLにした理由として、

  • まずはchrootやmountとコマンドのデーモン化レベルを試したい
  • その他の機能が必要になれば順次試す(これがhaconiwaの良い所)
  • ひとつのchroot環境を共有してコンテナを複数立ち上げたい
  • シェルからワンラインで複数コンテナ立ち上げたい
  • コンテナのファイルは共有するので、pidファイルやportは動的に記述したい

があります。通常のコンテナエンジンにこれを求めるのはちょっと厳しいのですが、haconiwaを使えばあっという間に実現できます。上記のようにRuby書いたことがある人なら大抵書いた事があるような、環境変数の受け渡しによって実現することができます。

その上で、 /var/lib/haconiwa/2d057697 がchroot環境のrootになりますので、このディレクトリの中で、phpinfoが動くchroot環境のtarを展開しておきます。

cd /var/lib/haconiwa/2d057697
tar xvf phpinfo-chroot.tar.gz

その上で、chroot配下のApacheは2.4系で動いているので、環境変数から設定を受け取れるように、以下のようなhttpd.confにしておきます。

Listen ${APACHE_PORT}
PidFile ${APACHE_PID}

これによって、Apacheはhaconiwaから受け取った環境変数を元に、動的に設定を変えてApacheを起動させることができます。

1万コンテナ動かしてみる

ということで、mod_phpを組み込んで、通常デフォルトで入っているようなApacheモジュールがコミコミのhttpd(RSSが5MB〜10MBぐらい)を1万個動かしてみましょう。上記のようなhaconiwaの設定を行ったので、以下のようにワンラインでコンテナを起動させることができます。

for port in `seq 10001 20000`; do echo APACHE_PORT=$port ./haconiwa start phpinfo.haco; sleep 0.01; done

こういう大量のデーモンをバックグラウンドで起動したり、一気に殺したりすると、高負荷になってサーバが落ちることがあるので、できるだけシーケンシャルな動きになるように今回はsleepをはさんで負荷を調整しました。そして、上記コマンドを実行すると、haconiwaによってコンテナが大量に起動されていきます。

Container successfully up. PID={container: 13302, supervisor: 13301}
Container successfully up. PID={container: 13306, supervisor: 13305}
Container successfully up. PID={container: 13310, supervisor: 13309}
Container successfully up. PID={container: 13314, supervisor: 13313}
Container successfully up. PID={container: 13318, supervisor: 13317}
Container successfully up. PID={container: 13322, supervisor: 13321}
Container successfully up. PID={container: 13326, supervisor: 13325}
Container successfully up. PID={container: 13330, supervisor: 13329}
Container successfully up. PID={container: 13335, supervisor: 13334}
Container successfully up. PID={container: 13339, supervisor: 13338}
Container successfully up. PID={container: 13344, supervisor: 13343}
Container successfully up. PID={container: 13348, supervisor: 13347}
Container successfully up. PID={container: 13352, supervisor: 13351}
Container successfully up. PID={container: 13356, supervisor: 13355}
Container successfully up. PID={container: 13360, supervisor: 13359}
Container successfully up. PID={container: 13364, supervisor: 13363}
Container successfully up. PID={container: 13368, supervisor: 13367}
Container successfully up. PID={container: 13372, supervisor: 13371}
Container successfully up. PID={container: 13376, supervisor: 13375}
Container successfully up. PID={container: 13380, supervisor: 13379}
Container successfully up. PID={container: 13384, supervisor: 13383}
Container successfully up. PID={container: 13388, supervisor: 13387}
Container successfully up. PID={container: 13392, supervisor: 13391}
Container successfully up. PID={container: 13396, supervisor: 13395}
Container successfully up. PID={container: 13400, supervisor: 13399}
Container successfully up. PID={container: 13404, supervisor: 13403}
・
・
・

そして、しばらく起動し続けていると、4000コンテナあたりで起動しなくなりました。そこで、コンテナ内の起動に失敗しているhttpdのエラーログを確認しました。すると、

[Mon Nov 07 09:53:00.327685 2016] [core:emerg] [pid 4454] (28)No space left on device: AH00023: Couldn't create the rewrite-map mutex 
AH00016: Configuration Failed

とか

[Mon Nov 07 09:40:11.671895 2016] [auth_digest:error] [pid 32150] (28)No space left on device: AH01762: Failed to create shared memory segment on file /run/httpd/authdigest_shm.32150

というエラーが出力されていました。これは、複数のコンテナで同一のhttpdの環境を共有しているため、セマフォ識別子が同一のものとなり、その上限に引っかかっていたようです。実際にうづらさんがその辺さっと調査して、

ipcs -s | grep apache | wc -l
32000

このApacheの値が、以下のようにkernel.semの上限にひっかかっていました。

# sysctl kernel.sem
kernel.sem = 32000      1024000000      500     32000

また、もう一つの問題として、各コンテナでhostnameを変更するため、コンテナ起動時のhostnameの変更が、親のホストにまで影響を与え、コンテナ生成中にころころと親のhostnameを変更し続けるような挙動もしていました。

namespace機能を使う

現在のDSLの設定は、ある種マウントやchrootを工夫しているだけなので、これらの要求に答えることができません。しかし、haconiwaは様々なコンテナの要素機能を持っているため、ここでは実行コンテキストの一部を分離する機能を使い、このセマフォ識別のnamespaceであるIPCと、hostnameの変更などに対するnamespaceであるUTSを、コンテナ単位で分離するようにしました。これによって、コンテナ間やホストとnamespaceが分離されるため、上記のセマフォの問題やIPCの問題がおきないはずです。

ということで、以下のようにDSLの最終行に2行追記しました。

unless ENV['APACHE_PORT']
  raise "env APACHE_PORT not found"
end

Haconiwa.define do |config|
  # The container name and container's hostname:
  config.name = "haconiwa-2d057697-#{ENV['APACHE_PORT']}"
  # The first process when invoking haconiwa run:
  config.init_command = ["/usr/bin/env", "APACHE_PORT=#{ENV['APACHE_PORT']}", "APACHE_PID=/var/run/httpd/httpd.#{ENV['APACHE_PORT']}.pid", "/usr/sbin/httpd", "-X"]
  # If your first process is a daemon, please explicitly daemonize by:
  config.daemonize!

  # The rootfs location on your host OS
  # Pathname class is useful:
  root = Pathname.new("/var/lib/haconiwa/2d057697")
  config.chroot_to root

  # mount point configuration:
  config.add_mount_point "tmpfs", to: root.join("tmp"), fs: "tmpfs"

  # Share network etc files from host to contianer
  # You can reuse /etc/netns/${netnsname}/* files:
  config.mount_network_etc(root, host_root: "/etc")

  # Re-mount specific filesystems under new container namespace
  # These are recommended when namespaces such as pid and net are unshared:
  config.mount_independent "procfs"
  config.mount_independent "sysfs"
  config.mount_independent "devtmpfs"
  config.mount_independent "devpts"
  config.mount_independent "shm"

  # namespeaceの設定を2行追記
  config.namespace.unshare "ipc"
  config.namespace.unshare "uts"

end

たったこれだけで、IPCとUTSのnamespaceが分離されます。

そこで、再度、シェルから1万コンテナの起動を行いました。

すると、4000を超えたあたりでもエラーが出ることなく起動を続け、6615コンテナぐらいまで立ち上がった所で、メモリ不足となり、それ以上は起動しませんでした。これによってIPCやUTSの問題は解決し、ハードウェアのリソースの問題に移り変わったことがわかります。

つまり、メモリ32GBのサーバでhaconiwaを使ってphpinfoが動き、それなりに通常使うようなApachモジュールがデフォルトで入っているコンテナを1worker(masterも兼務)で動かすと、6600ぐらい動かすことができる、ということになります。実際に計算してみても、その時に起動していたコンテナ内のhttpdのRSSが5MBぐらいでほとんどCoWは効かないforkの仕方だと思うので(haconiwa -> httpdというプロセスツリーが6600組みあるイメージ)、

irb(main):001:0> 5 * 6600 / 1024
=> 32

となるので、swap領域も入れると大体そんなもんか、という感じで計算でも概ね納得の値です。

また、haconiwa自体のメモリフットプリントは以下のように非常に小さく、1MB以下になってました。

root     11969  0.0  0.0  16412   668 ?        Ss   20:09   0:00 ./haconiwa start phpinfo.haco
apache   11970  0.0  0.0 413556  9636 ?        S    20:09   0:00  \_ /usr/sbin/httpd -X

cgroup機能を使う

ここで、概ね5000コンテナ位だったらいい感じで動かせそうなので、さらに各コンテナにcgroup機能を適用して、リソースを制御することにしました。その際に、以下のようにcgroupの設定を最終行に更に2行追加します。

unless ENV['APACHE_PORT']
  raise "env APACHE_PORT not found"
end

Haconiwa.define do |config|
  # The container name and container's hostname:
  config.name = "haconiwa-2d057697-#{ENV['APACHE_PORT']}"
  # The first process when invoking haconiwa run:
  config.init_command = ["/usr/bin/env", "APACHE_PORT=#{ENV['APACHE_PORT']}", "APACHE_PID=/var/run/httpd/httpd.#{ENV['APACHE_PORT']}.pid", "/usr/sbin/httpd", "-X"]
  # If your first process is a daemon, please explicitly daemonize by:
  config.daemonize!

  # The rootfs location on your host OS
  # Pathname class is useful:
  root = Pathname.new("/var/lib/haconiwa/2d057697")
  config.chroot_to root

  # mount point configuration:
  config.add_mount_point "tmpfs", to: root.join("tmp"), fs: "tmpfs"

  # Share network etc files from host to contianer
  # You can reuse /etc/netns/${netnsname}/* files:
  config.mount_network_etc(root, host_root: "/etc")

  # Re-mount specific filesystems under new container namespace
  # These are recommended when namespaces such as pid and net are unshared:
  config.mount_independent "procfs"
  config.mount_independent "sysfs"
  config.mount_independent "devtmpfs"
  config.mount_independent "devpts"
  config.mount_independent "shm"

  # namespeaceの設定を2行追記
  config.namespace.unshare "ipc"
  config.namespace.unshare "uts"

  # cgroupによって、コンテナのCPU使用上限を30%に制限
  config.cgroup["cpu.cfs_period_us"] = 100000
  config.cgroup["cpu.cfs_quota_us"]  =  30000

end

この設定により、各コンテナのCPU使用率が30%に制限されるようになるはずです。これで、再度shellからワンラインで5000コンテナを上げてみます。

for port in `seq 10001 15000`; do APACHE_PORT=$port ./haconiwa start phpinfo.haco; sleep 0.01; done

すると、以下のように想定どおりきちんとコンテナが動いていることがわかります。また、cgroupに関する設定も漏れなく適用されているようです。

  • 起動後のプロセス数
$ ps auwxf | grep http[d] | wc -l
5000
  • 起動後のlisten数
# netstat -lnpt | grep httpd | wc -l
5000
  • 起動後のcgroupの設定数
# ls -l /sys/fs/cgroup/cpu | grep haconiwa | wc -l
5000
  • 起動後のmemoryの状態
$ free -m
             total       used       free     shared    buffers     cached
Mem:         32091      31699        392          0         12        438
-/+ buffers/cache:      31247        844
Swap:        16383       5992      10391
  • curlで適当にコンテナにリクエスト送ってみる
$ curl -s localhost:13000/phpinfo.php | grep "Server API"
<tr><td class="e">Server API </td><td class="v">Apache 2.0 Handler </td></tr>
  • すべてのコンテナにリクエスト
$ for port in `seq 10001 15000`; do curl -s localhost:$port/phpinfo.php; done | grep "Server API" | wc -l
5000

ちゃんと動いているようです。

ベンチマークでリソース制御ができているか確認

最後に、ちゃんとコンテナ単位で設定どおりリソースが分離されているかを確認します。

ab -l -k -c 10 -n 10000000 http://localhost:14010/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:13010/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:12010/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:12222/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14410/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14411/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14412/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14413/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14414/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14415/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14416/phpinfo.php >> result.log 2>&1 &
ab -l -k -c 10 -n 10000000 http://localhost:14417/phpinfo.php >> result.log 2>&1 &

こんな感じで適当なポートに負荷をかけてみました。すると、その時のtopは以下のようになっていました。

top - 20:41:37 up  3:47,  5 users,  load average: 7.75, 2.24, 61.35
Tasks: 10402 total,  13 running, 10387 sleeping,   0 stopped,   2 zombie
Cpu(s):  1.9%us,  1.8%sy,  0.0%ni, 90.8%id,  4.4%wa,  0.0%hi,  1.0%si,  0.0%st
Mem:  32862188k total, 32529360k used,   332828k free,     8040k buffers
Swap: 16777212k total,  8576304k used,  8200908k free,   416080k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
29771 apache    20   0  404m 7632 2400 R 30.4  0.0   0:12.95 httpd
19855 apache    20   0  404m 5616 2400 R 29.0  0.0   0:12.94 httpd
22084 root      20   0 20944 9688 1632 R 29.0  0.0   0:00.33 top
23951 apache    20   0  404m 7628 2400 R 29.0  0.0   0:12.94 httpd
28012 apache    20   0  404m 7628 2400 R 29.0  0.0   0:12.97 httpd
29743 apache    20   0  404m 7628 2400 R 29.0  0.0   0:12.98 httpd
29747 apache    20   0  404m 7628 2400 R 29.0  0.0   0:12.97 httpd
29751 apache    20   0  404m 7632 2400 R 29.0  0.0   0:12.97 httpd
29759 apache    20   0  404m 7628 2400 R 29.0  0.0   0:12.96 httpd
29763 apache    20   0  404m 7632 2400 R 29.0  0.0   0:12.98 httpd
29767 apache    20   0  404m 7632 2400 R 29.0  0.0   0:12.96 httpd
29775 apache    20   0  404m 7628 2400 R 29.0  0.0   0:12.60 httpd
20739 apache    20   0  404m 7012 2400 R 27.6  0.0   0:12.95 httpd
18971 root      20   0 89824 2296 1292 S  6.9  0.0   0:02.26 ab
18987 root      20   0 89824 2320 1316 S  6.9  0.0   0:02.33 ab
18970 root      20   0 89824 2320 1324 S  5.5  0.0   0:02.24 ab
18974 root      20   0 89824 2340 1340 S  5.5  0.0   0:02.31 ab
18975 root      20   0 89824 2316 1316 S  5.5  0.0   0:02.28 ab
18978 root      20   0 89824 2332 1324 S  5.5  0.0   0:02.22 ab
18979 root      20   0 89824 2276 1276 S  5.5  0.0   0:02.31 ab
18982 root      20   0 89824 2312 1304 S  5.5  0.0   0:02.33 ab
18983 root      20   0 89824 2340 1340 S  5.5  0.0   0:02.36 ab
18991 root      20   0 89824 2316 1316 S  5.5  0.0   0:02.21 ab
18986 root      20   0 89824 2320 1320 S  4.1  0.0   0:02.25 ab
18990 root      20   0 89824 2228 1232 S  4.1  0.0   0:02.29 ab
    8 root      20   0     0    0    0 S  2.8  0.0   3:55.93 rcu_sched
  103 root      20   0     0    0    0 S  1.4  0.0   0:19.20 rcuos/11
 6886 root      20   0  4268  960  784 S  1.4  0.0   0:00.93 fsupdated

ちゃんとコンテナ単位でリソースが30%に抑えられていますね!!!!!!

参考までに、1コアCPU30%に抑え込まれた条件下でのreq/secは以下のような結果でした。

# ab -l -k -c 10 -n 10000 http://localhost:14000/phpinfo.php

Server Software:        Apache/2.4.6
Server Hostname:        localhost
Server Port:            14000

Document Path:          /phpinfo.php
Document Length:        Variable

Concurrency Level:      10
Time taken for tests:   51.239 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    0
Total transferred:      450678756 bytes
HTML transferred:       448848756 bytes
Requests per second:    195.16 [#/sec] (mean)
Time per request:       51.239 [ms] (mean)
Time per request:       5.124 [ms] (mean, across all concurrent requests)
Transfer rate:          8589.49 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     2   51  34.6     83      87
Waiting:        2   49  34.7     17      87
Total:          3   51  34.6     83      87

Percentage of the requests served within a certain time (ms)
  50%     83
  66%     85
  75%     85
  80%     85
  90%     86
  95%     86
  98%     86
  99%     86
 100%     87 (longest request)

停止時も、一気にkillallなどすると高負荷になるため、シーケンシャルにゆっくり安全に殺していきましょう。数千起動してkillallとかしちゃうと、以下のようなロードアベレージになって、kernelが大変苦しくなります。

$ cat /proc/loadavg 
8098.64 5875.62 2985.01 8/9805 25330

また、haconiwaは.hacoファイルを使ってきれいにコンテナを停止する機能もサポートしていますので、startと同様、以下のように書くことができます。

for port in `seq 10001 20000`; do APACHE_PORT=$port ./haconiwa kill phpinfo.haco; sleep 0.1; done

startオプションをkillにしただけですね。

まとめ

ということで、haconiwaを色々と実際に触って検証してみましたが、とにかく痒い所に手がとどく拡張性を持ち、それでいてDSLによって簡単かつ高い生産性をもってコンテナをコントロールすることができるソフトウェアであると改めて認識しました。

ディストリビューションによっては、コンテナを構成する要素機能がそろっていなくてうまく動かせない、というパターンが多い中で、使用可能な機能だけでちょっとしたコンテナやそれに準ずる環境をプログラマブルに書きたい、という用途にhaconiwaはピッタリです。また、拡張性と生産性を両立するソフトウェアの設計というのはなかなか難しいのですが、それをコンテナの内部実装という高度な領域においても、高いレベルで両立できています。

今回はglibcが古いなどという制約がある環境下での検証でしたが、今後はuidやgid、setns、capabilityなど、haconiwaはその他沢山の機能が実装されているので、そういったセキュリティ周りの機能もフルに使って、コンテナ環境を色々とカスタマイズできれば良いなと思っています。また、それもきっと非常に簡単にできるのだろうなという印象を持ちましたので、是非コンテナに興味ある方はhaconiwaを使ってみて、コンテナ環境構築を楽しみつつ、haconiwa自体へフィードバックなどを行って、OSS活動的楽しみも同時に味わっていけると良いのではないでしょうか。

ということで、久々にOSSの検証レポートをお送りしました。次はメモリ256GBの環境で試してみようと思いますのでお楽しみに。