Unyablog.

のにれんのブログ

VSCode 上で ESLint を使った TypeScript のフォーマット

  1. https://github.com/microsoft/vscode-eslint を入れる
  2. VSCode のデフォルトフォーマッタを切る

グローバルか、ワークスペース上の .vscode/settings.json などに書くと良い

{
  "typescript.format.enable": false
}
  1. TypeScript で ESLint が働くようにする
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        {
            "language": "typescript",
            "autoFix": true
        },
        {
            "language": "typescriptreact",
            "autoFix": true
        }
    ],

ISUCON9 予選に参加した(failed)

最近は O-Ku-Ri-Mo-No Sunday! にはまっている。めっちゃ良くない?

ということで先日 ISUCON9 に参加した。

isucon.net

チームは :innocent: で id:wass80, id:utgwkk と参加した。

全体的な話は utgwkk の記事を参照。

utgwkk.hateblo.jp

自分のやったことは大体以下の issue に書いてある。レポジトリも以下参照 *1

github.com

トラブル集

今回もいろんなことが起きたので列挙してみる。

サーバー構成を1台と勘違いする

全員が [重要] と書いてある場所を読み飛ばしてスコアの計算方法ばっかり気にしていた結果、複数台構成ができないと思い込んでいた!

コンテスト中は「これ複数台勝手に立てたらどうなるかな〜」とか言ってました。1台だったのでインフラ担当の自分は途中暇だった。

たとえ構成は1台限定だったとしても、3台立ててそれぞれ作業用にしたら良かったなあ、という反省も。

VSCode Remote をやめることで得点が10倍になる

なんか 410 点ぐらいしか出なくておかしいな〜とか言いながら htop を見ていたら VSCode Remote がめちゃくちゃ CPU 食ってた。やめると 4100 点ぐらいになって最大のブレイクスルーだった。

Gunicorn のオプションを間違えてプロセスが大量発生する

Gunicorn の --worker-connections を 10000 にしようとしたときに、間違えて -w 10000 として ワーカー数が 10000 になった。当然プロセスが大量に生まれるので、アクセスするとめちゃくちゃ重くて Load average が 170 になった。

sudow すらも謎のエラーで使えなくなったので、 root で入って systemctl kill した。 systemctl は kill できるので最高!*2

uWSGI の restart が効かないので reboot を繰り返す

いろいろあって Gunicorn を uWSGI に載せ換えたのだけど、なぜか restart がハングするようになってしまった。面倒なのでサーバーごと reboot することで restart の代わりにしていた。後述の Redis と同じ理由かも?

途中から systemctl kill --signal=9 すると restart=true で立ち上がることに気付いたのでそれを使うようにした。 systemctl は kill できるので最高!*3

Redis が立ち上がらないので memcached にする

Redis を apt で素朴に入れたけど apt installsystemctl restart がハングしたので諦めて memcached 入れた。おそらく↓の話っぽい。

Redis は kill して purge した。 systemctl は kill できるので最高!*4

並列 http アクセスライブラリを入れようとして未実装であることに気づく

transactions で外部 API を呼んでいるところが直列で遅かったので、並列にしよう!ということで検索して出てきた httpx というライブラリを使うことにした。

github.com

www.encode.io

このドキュメントに即して実装を始めたのだけど、20分程度して実装が終わりつつあったときにドキュメントにある警告文の存在に気付いた。

Warning

This page documents some proposed functionality that is not yet released. See pull request #52 for the first-pass of an implementation.

https://www.encode.io/httpx/parallel/

悲しいね〜。文章読めるようになる必要がある。

Gunicorn や uWSGI を使うと点数が伸びなくなる

途中、デバッグのために Gunicorn じゃなくて Flask の app.run() を直接使っていた。それでうまく伸びていたのだけど、Gunicorn にした瞬間に点数が1/10になった。uWSGI でも同様で、これについては今も謎。コンテスト中盤からずっとネックになっていて、終盤ドタバタした遠因になったと思う。

今見たら app.run() の方は threaded=True になってるので、10 workers の Gunicorn とはいろいろ違ったんだろうか。でも初めは上手くいってたので謎。

並列処理たちとのたたかい

gthread

Gunicorn は gthread を使うことができるのでなんとなく設定して使ったら動いているように見えた。ただ、大量に受け取りすぎて 499 ばかり返すようになってしまったので、もう少しチューニングが必要そうな雰囲気だった。

nodejs でも同じような悩みがあるのだろうか。それとも真にイベントループな言語は関係ないのだろうか...。

transaction

今回は transaction での外部 API が直列でめちゃくちゃ遅いのが課題になっていた。Python の並列処理にいい思い出が無いので、そこだけ nodejs に流すことも考えたものの、cookie やらを考えて結局 Python で行うことにした。

ただ Python の async await ほとんど分からないので難航。出たの最近だし機能もどんどん増えてるので、ぱっと調べるには向いていなかった *5

結局最後の最後*6には aiohttp 使って勘でなんとかなった気もするけど、他のエラーでてたので分からなかった。

次回は go か node でやってもいいなあ、でも今回で async への理解深まったので Python で使える気もする。自分が一番好きで詳しい言語である Python によく分からないメジャー機能があるのがとても悔しい…。

良かった点

反省点ばっかりなのもアレなので良かった点も。

  • 遠隔で行ったが、専用ディスプレイにずっと遠隔者のディスプレイを写していた
    • スムーズな情報共有ができたと思う。全員同じ場所にいても有効だと思った。
    • 次は VSCode Liveshare とか使ってみたいな。
  • バグったときのために、オリジナル版を別ポートで動かしていた
    • バグったときにレスポンスをオリジナル版からも取得して diff する、といった活動を行っていて低コストながら高速にバグを発見できた
  • 計測やログイン環境のセットアップは順調にできた
  • pt-query-digest や kataribe などでボトルネックの計算をちゃんと行っていた
  • 定期的に git commit していたので容易に rollback できた
  • スコア計算方法の部分はちゃんと読み込んでいた
  • 手が空いたときに弁当を買いに行くという判断をした
  • 昼飯をちゃんと食べた
  • 楽しかった

その他

@チーム

今年もありがとうございました。 wass の迅速な方針決めや実装は頼りになったし、utgw が N+1 や index をバシバシ解決していく姿は最高だった。来年も頑張りたい!!

@運営

今回も楽しかったです!

バランスの良い問題になってて良かったです。isucari や還元キャンペーンなど、いかにもメルカリっぽい話題でニヤッとしました。あと得点がレスポンス数ではなく取引価格の合計なのも、実際のサービス目標感があって良かったです。

運営・インフラ提供・問題作成者の皆さん、ありがとうございました!!

感想

今年はあんまりやる気なくて練習もせず適当に始めたのだけど、実際やったらめっちゃ楽しいし、負けたらめっちゃ悔しい。 ISUCON4 からずっと参加しているけど、最高のコンテストだと思う。

次回もあれば絶対に参加したい!!!!!本戦行くぞ!!!

*1:async wait 実装は入っていない

*2:そういう話ではない

*3:そういう話ではない

*4:そういう話ではない

*5:asyncio.sleep() じゃなくて sleep() つかって上手く行かないじゃん!とか言ってた

*6:17:59 ぐらい

各 OS の 802.1X 認証における RADIUS サーバーの検証方法

最近 802.1X 認証(WPA2 Enterprise)を構築していた。野良 RADIUS サーバーに Credentail を渡さないためには、サーバー証明書などで RADIUS サーバーの検証を行うことが必要である。 各 OS でどう検証できるかを調べたのでメモ。

今回はクライアントにサーバーの証明書をインストールしない。また、EAP 方式は EAP-TTLS-PAP / EAP-TTLS-GTC。

Android

Android 7.0 より前は、自分でサーバーのCA証明書をインストールするしか検証方法がなかった。何も検証せずに使ってる人が多いのではないか…。

Android 7.0 以降は CA 証明書に「システム証明書」が使えるようになり、RADIUS サーバーの証明書をルート証明書で検証できるようになった。

システム証明書で検証する場合はドメイン名の指定が必須になる。

サーバーの証明書が指定したドメイン名であり、かつルート証明書で検証できれば接続される。

iOS

iOS 12 で検証。

802.1x 認証の場合、ルート証明書に関わらず サーバーの証明書を信頼するかどうかのダイアログが出る。

この時、サーバー証明書ルート証明書で検証できるかどうかが分からない *1 ので、SHA256 fingerprint などを頼りにユーザーが判断することになる。

管理者は SHA256 fingerprint とドメイン名(CN)を提供すれば良いと思う。

Windows 10

記事執筆時点での最新バージョンで検証。

Windows 10 では、ルート証明書での検証はしてくれるが、接続時に証明書の SHA256 fingerprint しか確認できない。

ドメイン名を指定するには、古い設定画面からドメイン名を入力し、信頼するルート証明書を選択する必要がある。この画面に行くのはなかなか難しい *2 ので、通常は SHA256 fingerprint を見てもらうことになりそう。

macOS

macOS 10.14 で検証。

macOS は接続時に証明書の確認画面が出る。そこには

などが見れる。

なので、ユーザーにはドメイン名と、証明書が信頼されているかの確認をして接続してもらうと良い *3

まとめ

OS ルート証明書での検証(ドメイン名指定) SHA256 fingerprint 表示
Android(7.0 以降) o x
iOS x o
Windows 10 o
macOS o o

となるので、管理者は

  • ルート証明書で検証できる証明書を LE 等で作る
  • ドメイン名 (または CN)と SHA256 Fingerprint を提供して、ユーザーに検証するよう呼びかける
    • Android 7.0 未満は諦めるかサーバーのCA証明書を配布する

と良さそうだった。

*1:困ってる人は割といそう。 https://seclists.org/educause/2017/q4/54 を読んでやや納得した。

*2:アダプタ設定から辿れる

*3:タイムアウトが非常に早く、確認しているとたいてい再認証が必要になるのが玉にキズ

Ansible で大量のホストに対して実行する場合のメモリ対策

Ansible で大量のホストを対象として Playbook を実行すると、大量の変数を保持する結果、メモリが足りなくなることがある。

以下はその対策。

gather_subset を用いて収集する fact を制限する

Ansible は gather_facts(または setup モジュール)で大量の fact を取得するが、これを必要最低限にすることでメモリ使用量を抑えることができる。

gather_subset という setup モジュールのオプションを使う。

gather_facts: no
tasks:
- name: Gather facts
  setup:
    gather_subset: min

serial を用いて数回に分けて PlayBook を実行する

serial: 150 とすることで 、150ホストごとに PlayBook が走る。

使い終わった巨大な変数は初期化しておく

serial にしていても PlayBook の実行ごとにメモリ使用量は増えることがある。

このような場合は、PlayBook の最後で巨大な変数を初期化する(最終手段っぽい)。

tasks:
- name: Save large output
  command: command_outputs_large_text
  register: large_output
  
...

- name: Init values
  set_fact:
    large_output: !!null

systemd 240 と Java の OOM

ArchLinux 上で Debian stretch を systemd-nspawn で立ち上げているのだが、最近 pacman -Syu したら Debian のコンテナで Java が立ち上がらなくなった。

原因

だいたいここに書いてある。

  1. systemd 240 で RLIMIT_NOFILE が大きくなった
  2. Debian の PAM では、RLIMIT_NOFILE (hard nofile)を PID 1 からコピーしている
  3. なので、ログインセッションなどでも RLIMIT_NOFILE が大きくなった
  4. ところで、Java は起動時に RLIMIT_NOFILE 分の配列を作成する
  5. このような環境だと RLIMIT_NOFILE が大きすぎるので OOM で落ちる

というもの。

Debian sid も systemd 241 なのに落ちなくてなぜだろうなと思ったら、そもそも hard nofile limit がそんなに大きくなかった。

大きくならないように、ビルド時にフラグをつけるようにしたらしい。

Arch で ulimit -H -s するとめちゃくちゃ大きかったので、特に触ってないのでしょう*1

どうするか

/etc/security/limits.conf.d に hard nofile をそれなりの値に戻すような config を置く。

今回は /etc/init.d/hogehoge なスクリプトを立ち上げるだけなので大丈夫だが、 ssh でのセッションとかでも対処するなら UsePrivilegeSeparation あたりを変えないといけないらしい(未検証)。

おまけ

この事象は systemd が立ち上げるプロセスには関係せず、pam_limits.so を経由するものに関係がある。

今回、 Elasticsearch では動いたのに Jenkins で動かなかったのは何故だろうと調べていたら、Jenkins は今も /etc/init.d/jenkins なので su 経由で起動し、PAM の影響を受けるということだった。

こういうところで違いが出るんだなあ。

*1:Arch は PAM で PID 1 をコピーしてないので(未確認)、そのような問題は起きないのだと思う(未確認)。今回は systemd-nspawn で Debian を立ち上げたために起こった。

レポジトリから直接ダウンロードした deb ファイルを自分で検証する

apt ではなく dpkg を直接インストールするとき、 http://security-cdn.debian.org/ 等から直接 dpkg をダウンロードする。

HTTPで取得した dpkg が改ざんされていないか気になったので自前で検証してみた。

具体的な構造は以下が詳しい。

kmuto.jp

GPG Key をインポート・信頼

$ sudo apt-key list # 今 apt で使っている key を見る。ここにあるものは信頼できるだろう。(一応 fingerprint を見て確認しても良いかも)
$ gpg --import /etc/apt/trusted.gpg.d/~.gpg
$ gpg --edit-key <key id> # import したものを ultimately trust する

Release ファイルの検証

いくつかの key で署名されていたので、複数 import して確認した。

$ gpg --verify Release.gpg Release

Packages のハッシュ確認

Release の中に Packages ファイルのハッシュが記載されているので、あってるか確認する。

deb のハッシュ確認

Packages の中に deb ファイルのハッシュが記載されているので、あってるか確認する。

ここまででエラーがなければ deb ファイルが検証された、と思う(多分!!

mackerel-plugin-strongswan を作った

Mackerel アドベントカレンダーが空いていたので、半年ほど前に作ったプラグインの話を軽くします。22 23日目の記事です*1

qiita.com

昨日は sfujiwara さんで、「Mackerel と連携する外形監視エージェント maprobe でマネージドサービスのメトリック収集を自動化する」でした。

sfujiwara.hatenablog.com

作ったもの

  • mackerel-plugin-strongswan

github.com

これは何

strongSwan という広く使われている IPSec 実装があります。

このプラグインは、 strongSwan の IKE デーモンである charon のインターフェースである vici を用いて、アドレスプールの状況や確立された SA (Security Association) の数を出力するものです。

vici は 5.4.0 からデフォルトで有効になっていますが、 swanctl を使わないパッケージの場合は無効になっていて使えないことがあるかもしれません。

libreswan には vici はないので使えません。

作った理由

SA の状況を監視したかったのですが、無かったので作りました。貼ってる SA の数が上限する様子を見て楽しんでいます。

f:id:nonylene:20181223010228p:plain

0 -> 1 になっているのは端末から接続したからですね。 1 -> 2 になってるのは常時接続しているルーターが reboot したからです。このルーター不安定でたまに reboot するのですが、その様子がよく分かります。

技術的な話

Python3 で書いています。公式に提供されているバインディングPython, Ruby, Perl だったので Python にしました。設置する場合は pipenv installpip instsall して vici モジュールを入れてください。

strongswan/src/libcharon/plugins/vici/python at e4a3ef2e4da28e4553262f4cc91f0b3e17066a71 · strongswan/strongswan · GitHub

コード自体は、 vici モジュール経由で API を叩いてメトリックにしているだけなので単純です。

mackerel-plugin-strongswan/main.py at master · nonylene/mackerel-plugin-strongswan · GitHub

その他

strongSwan は ipsec.conf に設定を書いていくという形式*2でしたが、新しく swanctl というのが開発され、strongswan.conf と同じ形式で非常に分かりやすく & 書きやすくなりました。今のところ困る点もなく使えており、おすすめです。

例えば、 left right というどっちがどっちか良く分からない名前も、localremote になっています。

wiki.strongswan.org

ちなみに、 swanctl 内部では vici を使って charon とやり取りしています。

*1:初め22日目だと勘違いしていた

*2:FreeS/Wan 由来