その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

独自ドメインのメールアドレスを使うようにした

2021年の記事とは思えないタイトルですが、そのようにしたのです。

特定のメールサービスが提供するメールアドレスに依存していると、そのメールサービスからBANされた際に人権を維持できない可能性があります。というのも仮にメールアドレスが凍結すると、そのアドレスをアカウントのidentifierとして登録しているサービスを巻き込んでしまい大惨事が起きてしまいます。
プレッパーじみた危機意識ではありますが、そのような気持ちになったのでこのたび独自ドメインでメールアドレスを払い出し、それを使うようにしてみました。

しかし自前でpostfixを運用する……みたいなことは断固やりたくなかったので、今回はさくらのメールボックスを利用して、元々保有していたドメインサブドメインを使ったメールアドレスを払い出し、そこに送られてくる全てのメールをGmailへと転送するという構成を取りました。
メールボックスに保存せず、即座に転送するような設定とすることで、さくらのメールボックスの容量制限を考えずに済むので便利です。GmailのUI/Appをそのまま使えるというのもグッドポイント。これでGoogleから仮にBANされてもメール機能については「UIが使えなくなるだけ」というところで被害を食いとめることができますね。

自分のドメインにメール用のサブドメインを切り、そのサブドメインに対して以下のようにNSエントリを設定すると、ドメインの移管等をすることなくメール用にドメインが使えるのは簡単で良いですね。

subdomain    NS    ns1.dns.ne.jp.
subdomain    NS    ns2.dns.ne.jp.

なぜサブドメインをわざわざ使っているかというと、主ドメインを使おうとするとさくらが管理するネームサーバーを利用する必要があるのですが、しかし今使っているネームサーバーはCloudflareのものなので引越し等がだるい。しかし、サブドメインだとサブドメインだけNSレコードの設定 (僕の場合はCloudflareで設定する) によりネームサーバーを差し向けることができるので楽ちん、という感じの動機でした。


さくらのメールボックスの特筆事項としては以下のような感じ。

  • ユーザーを制限無く作れる

※1 同じメールアカウント名で異なるドメイン毎のメールアドレスの運用は利用できません。
(例:「info@◯◯◯.com」と「info@△△△.net」の場合、同じ「info」のメールボックスに配信されます)

さくらのレンタルサーバとメールボックスには同一のドメインを登録することはできません。
https://www.sakura.ne.jp/mail/

  • DKIMは使えない


というわけで最小労力で自ドメインのメールアドレスをゲットしましたから、今後はこれを使っていきたいと思います。
メールサービスの選定のようなものは以下にメモを残しておきました:
scrapbox.io

moznion@mail.moznion.net
メル友募集中です。ちなみに僕はメールを見るという習慣がありません。


[追記]

「メールをメールボックスに保存せずにGmailへ転送だけ行うと、GmailからBANされたときに送られてきたメールがロストするのでは?」という指摘があり、それはその通りだと思いますが、僕はまあそれで良いかなと思っています。GoogleからBANされたらメールだけでなくありとあらゆるものが大倒壊し、暮らしがめちゃくちゃなことになると思うので、メールのロスト程度はかすり傷でしょう……という心意気です。メール自体の機能よりも「安定したアカウントのIdentifier」として使いたいというところが大きいというのもありますね。
とはいえ心配であれば、メールボックスに保存した上で転送、という方法を採用するのも手だと思います。さくらのメールボックスは10GBもあるし、メール用途であれば実質無限と言っても差し支えなさそうですし。

eclipse/leshan で最近直した問題について

かなりニッチな話題ですが、OMA LwM2Mのサーバ・クライアントのJava実装であるEclipse Leshanにパッチを送って取りこまれたのでそのご報告です。

github.com

Leshanのクライアントライブラリには予期せぬ例外が発生した際にクライアントアプリケーションがスタックし、プロセスは生き続けるものの一切の仕事をしなくなるという問題がありました。

Better handle unexpected error in DefaultRegistrationEngine. · Issue #933 · eclipse/leshan · GitHub


例えば以下のようなコードの箇所でRuntimeExceptionが発生すると、エラーログが記録されるだけで他のエラーハンドリングが成されません。

https://github.com/eclipse/leshan/blob/leshan-1.2.0/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java#L557

この RegistrationTask は個別のスレッドで動くのですが、意図しない実行時例外 (例えばUDP通信のレイヤで失敗するなど) が発生した場合はこのスレッド (RegistrationTask) はそれ以降何もしない、かつ孤児のような状況になり、孤児スレッドは回収されずプロセスは生き続けることとなります。
LwM2M的に register が成功しない限りプロトコルとして意味のある通信はできませんから、つまりこの状態のクライアントアプリケーションは一生register処理を行うことのできない、無意味なプロセスと化します。


その他にも複数箇所同様の問題がある部分があったため、それらを修正したというのが以下になります。

これらの変更により、不慮の例外が当該箇所で発生した場合は自動的にリソースを開放する (つまりシャットダウンを正しく行なう) ようになるため、何もしないゾンビのようなプロセス (注: ゾンビプロセスではない) は基本的に発生しなくなります。


基本的、と記述した理由は利用者が独自に定義して利用している LwM2mInstanceEnabler 及び LwM2mObjectEnabler に対して適切に StartableStoppableおよびDestroyableというそれぞれのInterfaceを実装してあげる必要があるためです。
不慮の例外が発生した際にしっかりと終了させたい場合は Destroyable を実装する必要がありますし、ホットな状態での再起動処理を正しく行いたい場合は Startable および Stoppable を適切に満足させる必要があります。
この詳細についてはLwM2mInstanceEnabler及びLwM2mObjectEnablerjavadocをご参照ください。


これらの修正はバージョン1.3.0以降から利用可能となっています。

github.com

現在開発中の次世代バージョンである2.0.0については順次対応予定 (2.0.0-M1よりも新しいバージョンで) となっているようです。

以上です。ご活用ください。

awesome-perlのご紹介およびメンテナの大募集

こんにちは。id:moznionと申します。Hachioji.pmというIT技術コミュニティに所属しています。

本記事はPerl Advent Calendar 2020の記事として記述されています。前日の記事は@mihyaeru21さんのGitHub Actions で Perl を動かすときのテンプレートでした。


Hachioji.pmという名前からわかるように、ここは元来はPerlを書く人が多かったコミュニティなのですが、時代の推移によりPerlを書く人は徐々に少なくなりつつあります。かく言う私自身も、かつてはPerlでそこそこ大規模なWebアプリケーションを書いて糊口を凌いでいましたが、ここ最近は仕事で (というかそこそこ規模の大きなコードを) Perlを書いたことは久しくありません。
Perl Advent Calendarなのになにを突然不敬なことを言い出すのかという感じですが、これは本筋に深く関係のある話のため記しました。


さて、Hachioji.pmではawesome-perlと呼ばれる、いわば「素晴しいCPANモジュール」のキュレーションプロジェクトのようなものを運用しています。元々は個人で運用されていたプロジェクトだったのですが、様々な事情があってHachioji.pmに運用が移管されたという経緯であったと認識しています。

github.com

awesome-goawesome-dockerなどといった、いわゆるawesome-*perl版だとお考えください。

awesome-perlはおかげさまで、多くの人に参照され活用されているようであります。モジュール追加等のpull-requestも定期的に送られてきておりありがたい限りです。


しかし、先に書いたように、Hachioji.pmではPerlを主戦場としているメンバーが少なくなってきており、awesome-perlのメンテナンスに一抹の不安を覚えるようになりました。
現状、メインでawesome-perlのメンテナンスを行っているのは私とid:uzullaさんの2名なのですが、私は先に述べたようにPerlからは一線を退いたに近い状況ですし、uzullaさんはPHPを主として活動されている方 (もちろんPerlは深く理解されています) となるため、両名とも現代のPerlのエキスパートとは言いがたい状況になっています。

その結果として、awesome-perlに送られてきたpull-requestについてはフォーマット等の議論・指摘はするものの、その内容についてはほぼ議論がバイパスされ、アッサリとマージされるようになってきているというのが近況となっています。廃墟にはしたくないのでpull-request自体のハンドルはされている、というような状況だとお考えください。
これについては質の観点での懸念があります。本来であれば提案されたモジュールの内容について議論をして精査し、然るのちにマージされるべきです。なんでもかんでも受け入れていては掃き溜めになることは避けられず、キュレーションとしては価値が失なわれてしまうためです。
しかし、何度か触れたようにPerlを現場で利用している人が少なくなってきているため、この健康的な手順を踏むことが徐々に難しくなってきています。


というわけで単刀直入に言いますと、awesome-perlを一緒にメンテナンスしてくれるメンバーを募集しています。現在も現役でPerlのプロジェクトを行われている方であれば非常にありがたいですが、そうでなくともメンテナンスを手伝ってくれる意思のある方であれば大歓迎です。

もし興味がある方がいましたら、私に直接連絡していただいても結構ですし、Hachioji.pmのSlackに来ていただいても歓迎です (普段は雑談をしているSlack Teamです)。


以上です。よろしくお願いします。awesome-perlでサクセスしませんか。

RADIUSのサーバー・クライアント実装をRustで書いた

An English article is here: https://dev.to/moznion/released-radius-rs-2e1o

タイトルの通り、AAA (Authentication, Authorization and Accounting) のためのデファクト・スタンダードプロトコルであるところのRADIUSのサーバー・クライアント実装をRustで書きました。

github.com

また、crates.io にもpublishしてあります: https://crates.io/crates/radius

このRADIUS実装の特徴としてはtokioを使用することで非同期 (async/await) ネイティヴな実装になっていることが挙げられます。
また、RADIUSgolang実装であるところのlayeh/radiusを参考にして、FreeRADIUSのdictionaryをコード生成器に食わせることによって、RADIUSアプリケーションの構築で必要となるRustの定義ファイルがRFCに基づいて自動生成されるようになっています。

簡単なサーバーとクライアントアプリケーションのサンプルは以下のような感じです。

割と簡単に書けて良いですね。


なんやかや、今でもRADIUS使うことってあるじゃないですか? もしも突然、RustでRADIUSアプリケーションを書く必要が出てきた際にはぜひご利用ください。

ところで、Rustでそこそこの規模感のあるコードを書いたのは実際初めてみたいなものなので実装上の問題などあるかもしれませんが、その場合はissue等でご指摘いただけると助かります。

ネットワーク越しリトライ考

ここ最近では何らかのインターネットサービスを構築・運用するにあたって、ネットワーク越しのリトライを考えることは避けられなくなりつつあります。
micro services のようなアーキテクチャを採用している場合はサービス間のメッセージのやり取りはまず失敗する前提 (つまりリトライをする前提) で組む必要がありますし、たくさんのクライアントがいてそのクライアントが定期的に何かを処理してセントラルにデータを送ってくる IoT のようなシステムを構築する時もその処理のリトライをよく考える必要があります。

というわけで「ネットワーク越しのリトライ」についてここ最近考えていることをざっくりと書き留めるものであります。

前提
  • リトライをする側をクライアント、リトライを試みられる側をサーバと呼称します
  • リトライにおいて、サーバおよびネットワークはクライアントよりも弱者です
    • クライアントはリトライをコントロール出来る側にいますが、サーバとネットワークはそれをまったくコントロールできないためです
  • クライアントはリトライ時、サーバに迷惑をかけてはいけません
    • クライアントが1つポシャるのはそのクライアントだけで不具合が完結しますが、サーバがポシャると自分も含めた多数のクライアントが被害を受けるためです
  • クライアントはリトライ時、ネットワークに迷惑をかけてはいけません
    • クライアントが1つポシャるのはそのクライアントだけで不具合が完結しますが、ネットワークがポシャるとクライアントどころではなくネットワークに所属しているホストすべてが被害を受けるためです
リトライタイミングについて
  • リトライをする際にはインターバルを設けましょう
    • インターバルなしでリトライしてリクエストを殺到させてはいけません
    • 最悪いつまで経ってもサービスが復活しなくなる可能性があります
      • サーバが高負荷でリクエストを捌けなくなる
      • ネットワークが輻輳してすべてが終わる
  • リトライをする際のインターバルには backoff を設けましょう
    • 一定周期のインターバルは無いよりはマシですが、一定よりもリトライ回数の増加に応じて間隔を伸ばしていく方が好ましいでしょう
    • インターバルを設けた上で複数回失敗するということは、サーバにパフォーマンス等の深刻な問題が生じているか、クライアントに「そもそものリクエストがおかしい (サーバ側で受け入れられない)」などの致命的な問題が生じている可能性が高いためです
      • サーバに問題が生じているときは時間を置いたほうが良いので backoff を設けたほうが良いでしょう
      • クライアントの問題である場合はそのゴミリクエストを頻繁に投げるのは迷惑なのでリクエストの間隔を広げて頻繁にリクエストを投げないようにしたほうが良いでしょう
    • Exponential Backoff などがよく使われる方法だと思います (たまに Fibonacci Backoff を見ることがある)
  • インターバルにはジッターを設けましょう
    • サーバあるいはネットワーク起因で問題が発生した場合、問題が起きるタイミングは複数のクライアントでほぼ同一です
    • その複数のクライアントが同時にリトライを試みた場合、リクエストが殺到するのでサーバ・ネットワークが負荷に耐えきれなくなる場合があります
    • リトライ間隔にブレを持たせることで、リトライタイミングがある程度分散してくれると期待できるようになります
  • キリがよい時間のリトライを避けましょう
    • 例えば定期的に実行するようなクライアントの場合、「定期実行に失敗したので次の0:00 GMTちょうどに再度実行してデータを送る」というふうな「キリの良い時間」の再実行はできるだけ避けましょう
    • これはリトライの話題というよりバッチ処理のタイミングの話題ですが、「毎時ゼロ分」のようなキリの良い時間はリクエストが殺到する傾向があります
    • まあこれは民間療法に近い……
  • ジッターと同等の話題ですが、クライアントのハードウェアの電源投入時に即リトライを試みるのはやめましょう
    • IoT 的な話題ですが、メンテ等でハードウェアを一斉に再起動させることがあると思います
    • この際に全デバイスが一斉にリトライを試みるとサーバ・ネットワークが負荷に耐えきれなくなる可能性があります
    • ジッターを入れましょう
    • ハードウェアプロダクトの場合、一度アプリケーションをハードに焼き込むとリプレースが大変な場合が多いので注意しましょう。リプレースができないということは、ずっとその問題と付き合っていく必要が出てきます
リトライリクエストについて
  • リトライを前提とするリクエストについては冪等性 (何度実行しても結果に一貫性があるという性質) を担保しましょう
    • リクエストが冪等でない場合、最悪システムが矛盾した状態に陥ります
  • 破棄して良いリトライリクエストなのか、破棄してはならないものなのかをしっかり区別しましょう
  • ゴミリクエストは破棄しましょう
    • サーバは内容由来で処理不能なリクエストを受け取った場合は処理不能の旨 (例: HTTPの4xxレスポンス) をクライアントに通達しましょう
    • クライアントは処理不能なリクエストを受け取った場合はそのリクエストを破棄しましょう
    • 一定回数リトライを試みても成功しない場合はそれ以上送っても受け入れられない可能性が高いので破棄することを検討しましょう
  • リクエストを破棄する際は、そのリクエストをリプレイ可能な形でログかなにかに残しましょう
    • トラブルシューティングやマニュアルオペレーションでのリクエストの再実行に用いることができます
    • 破棄時にアラートを発報するなども良いでしょう
  • クライアントとサーバの間にバッファ (例: ジョブキュー) を挟むことができる状況の場合はバッファを挟むことを検討しましょう
    • クライアントの責任を「バッファにリトライリクエストを詰める」というところに限定できる
    • サーバはバッファから「自分のタイミング」でリクエストを取り出して処理することに集中できるようになるので、コントロールをある程度サーバ側に引き寄せられるようになるでしょう
    • 破棄してはならないリトライリクエストについてはバッファを入れた方が堅牢にしやすくなると思います
    • とはいえ
      • バッファに詰める時にポシャったらどうするかと言うと、ここにもリトライを考える必要が出てくる……
      • バッファから取ってきたデータの処理に失敗した時にどうするかと言うと、ここにも場合によってはリトライを考える必要が出てくる……
        • バッファ環境で、サーバサイドのリトライをやるにあたっては冪等性が必須となるでしょう
  • リトライリクエストの内容をメモリに蓄積している場合はデータロストの可能性を考えましょう
    • メモリに内容を保っているプロセスがダウンするとリトライすべき内容が失われます
    • それが致命的な場合はなんらかのストレージに保存しておくか、リプレイ可能なログを残すべきです
  • 失敗した部分だけをリトライする「ソフトリトライ」と、失敗した部分を含む一連の処理ごとやり直す「ハードリトライ」の両方の方法を用意すると便利です
    • 基本的にはソフトリトライを実行して、そのソフトリトライで解決しない (例: リトライが一向に成功しない) 場合はハードリトライにフォールバックして結果整合を保てるようになっているとなにかと良いです
    • ここを自律的に行うのは少々大変ですが……
  • リトライのメトリクスが取れるのであれば取りましょう
    • 常にリトライされている状況はおそらく何かがおかしいのでそれは検出したほうが良いでしょう
    • 場合によってはアラート等を上げるのも良いでしょう
    • とはいえどうメトリクスを取るのか、サーバで取るのかクライアントで取るのか、など考えることはあります
強いリアルタイムが求められる時はどうするか

とはいえ強いリアルタイム性を求められる際には「インターバルをたくさん入れる」とか「backoff を入れる」とかが難しい場合があり、そういうときはどうすれば良いんでしょうね……正直明確な答えはありませんが、考えられるのは

  • そもそもネットワーク越しでのリトライをやめる
  • サーキットブレイカ
    • リクエストに失敗してもその時点でのリクエストは通るようにしておく
    • バックグラウンドでリトライ処理を走らせておいて結果整合を図る

などでしょうか……まあ他にも色々あると思いますが。
しかし例に示した「サーキットブレイカーを入れて結果整合を図る」というのは複雑度がバリ上がりそうで大変そうな雰囲気がありますね。大変です。そもそも結果整合で良いのか? (結果整合で良いのであれば強リアルタイム性いらなくない?) というところもあるでしょう。

まあこのへんは歯を食いしばって頑張るしか無いのでしょうね……

まとめ

結局これがキングです:

  • クライアントからサーバにリトライリクエストを送る時にリクエストを殺到させるのはやめましょう
    • サーバが死にます
    • ネットワークが死にます (特にこちらは死んでしまうとどうにもならなくなる)

気をつけましょう。気をつけます。

追記

追記2

そういえば TCP 等の再送処理の話を一切していなかったことに気づきました……まあ本記事のスコープ外とさせて下さい。

pprof を使って nodejs アプリケーションのプロファイルを取る

pprof って go のやつでしょ? node のプロファイルが取れるわけ無いやろ,と僕も思っていたんですが以下のライブラリを使うことで取れることがわかりました.

github.com

使い方については Using the Profiler に書いてあるとおりで,アプリケーション側に

const profile = await pprof.time.profile({
  durationMillis: 10000,    // time in milliseconds for which to 
                            // collect profile.
});
const buf = await pprof.encode(profile);
fs.writeFile('wall.pb.gz', buf, (err) => {
  if (err) throw err;
});

という風に書いてあげるとwall time (所要時間) ベースのプロファイルを取ることができます *1.また,その下の Collect a Heap Profile に書いてある手順に従うと heap のプロファイルをとることもできます.


というわけで Express を使ったアプリケーションのプロファイルを pprof で取得するサンプルプロジェクトを作成しました.

github.com

これを試しに実行してみると,以下のようなプロファイル結果を得ることが可能です.

f:id:moznion:20201005120750p:plain

見慣れた pprof の UI ですね.

f:id:moznion:20201005120815p:plain

Flame Graph も取れて非常に便利.これ普通にガチで便利に使えます.




最初は pprof を node に使うとかめっちゃ hacky だな〜と思っていたのですが,確かに go ではないプロジェクトであっても "pprof" というプロファイリングフォーマットに乗っておくと,プロファイルの解析や UI のエコシステムに乗れるから便利なんだな〜という気づきを得ました.共通フォーマットを活用することでレバレッジを効かせていくのは重要ですね.

*1:ところで "Requiring from the command line" に書いてある手順に従ってもうまくプロファイルが取れなかったのですが,これどうやるのが正解なんですかね……?

ISUCON 9 決勝を AWS 環境に本番さながらに構築するメモ

github.com

今日 (2020-09-24) の時点では「ローカル環境」で動かす方法については記載がある一方で,何らかのリモートの環境に「本番」っぽく動かす方法についての記載が無いので,それを AWS 上に構築するためのメモを記します.

競技用 application のデプロイ

isucon.net

これを見る限り,参加者側の環境は以下の通り:

アリババクラウドさんの ecs.sn1ne.large を採用しました。
CPUは2コア (Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz)、メモリは4GBの、オーバーコミットのないインスタンスです。ネットワーク帯域も100Mbpsです。

ただし、今回のアプリケーションではメモリに全ての切らない環境を再現するために、Linuxにはメモリを1GBしか認識させていません。CPUは2コアで、メモリ1GBの環境を再現しています。
参加者にはこちらのインスタンスを3台提供しました。
デプロイには Docker Compose を採用しており、ホストのUbuntu 18.04にはDockerとNginxのみをインストールしています。

AWS で言うところの c5.large で良さそうに見えます.
c5.large だとメモリが 4GB なのでメモリを絞る必要がありそうですが,コレは大丈夫でした (後述).

application のセットアップについては普通に userplaybook 流したら動きました (ansibleのバージョンによっては細々手直しする必要はありそう,実際あります).

前述したメモリの量については ulimit かなんかで素朴に制限かけるかな〜と思っていたところ,playbook がいい感じで /etc/default/grub を以下のように (mem=1G) 書き換えて制限してくれたので問題ありませんでした *1.ただし reboot が必須.

...
GRUB_CMDLINE_LINUX=" net.ifnames=0 vga=792 console=tty0 console=ttyS0,115200n8 noibrs mem=1G"
...

「ハーンなるほど,それであればこの mem=1G を外して reboot すればフルでメモリ使えるから優勝だな? ガッハッハ!!」と悪巧みをしていたところ,それは以下のレギュレーション項目によって潰されていたのでズル不能でした.しっかりしていますね.

OSのメモリサイズが提供時と同じ状態であること

帯域幅については「ネットワーク帯域も100Mbps」とのことで制限が必要そうなのでこれは tc によって行いました *2

tc qdisc add dev ens5 root tbf rate 100mbit burst 50kb limit 500kb

これを手っ取り早く /etc/rc.local にでも書いておくと再起動しても適用されるので便利ですね.

ところでこの素朴な tc による帯域制御は egress にしか適用されないので,ingress にも適用しようとすると若干工夫をする必要があります.まあちょっと頑張って ingress にも適用できるスクリプトを書くか〜と思ったのですが,冷静に考えるとベンチマーカーと外部 APIインスタンスの egress にも帯域制限を適用すれば,とりあえず通る経路が一通り 100Mbps に制限されるのでそれで良しとしました.

あと TLS 対応しているのでなんとかする必要があるのですが……これについては諦めました. HTTP:80 で listen させるようにして,ベンチマーカーにはネットワーク内の private IP を参照してもらうようにしました.

ベンチマーカーと外部 API のデプロイ

ISUCON 9 のベンチマーカーおよび外部 API の動作環境についての記載がなかったので,代わりに ISUCON 8 決勝の環境を参照しました:

ベンチマーカー x 1
 - CPU 3コア : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz
 - メモリ 2GB
 - ネットワーク帯域 1Gbps
 - ディスク SSD
外部API x 1
 - vCPU 3コア : Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz
 - メモリ 2GB
 - ネットワーク帯域 1Gbps
 - ディスク SSD

ISUCON 9 決勝の環境と大差なさそうだったので,ひとまずコレに従うことにしましょう.パッと見 c5.xlarge で良さそう.
メモリはgrubで絞れば良いとして,vCPUは……まあ良いかということで放置.あと tc は application と同様に適用する.

セットアップについては bench playbook を流し込めばOK.

こちらについても TLS 証明書を用意してあげる必要があり,まあ application と同じく平文でやっても良いか……と思ったのですが,なぜか気分が乗ったので certbot + nginx で乗り切りました (手動オペレーション,この辺読みながらやった: How To Secure Nginx with Let's Encrypt on Ubuntu 20.04 | DigitalOcean).




とりあえずコレで動きます.1GB しかメモリがないので mysqlinnodb_buffer_pool とかを潤沢に使うとインスタンスごとポシャったりして大変でした(なお潤沢に使わなくてもポシャる).swap が無いことに起因する話題な気がする.大変でしたね.

*1:それはそうとしてgrub書き換えてくるansible,良いですね……ちょい怖!

*2:本番は tc ではなかったでしょうが