愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

fishでzshのhistory-beginning-search-backwardとhistory-beginning-search-forwardをやる

たまーに「久々に少しは整えるかぁ」という気になって環境構築始めるものの、途中で力尽きる。最近ではvimからneovimに中々乗り換えられない。そんなお年頃。

で、もう頑張って設定ファイルのメンテしたくない。プラグインマネージャも使いたくない。

設定ファイルも増やしたくない。どちらかというと減らしたい。

というわけで、zshからfishにしてみることにした。あまり設定がいらないとのことなので。

zshでネックになりそうな設定は実はこれだけだった

bindkey '^P' history-beginning-search-backward
bindkey '^N' history-beginning-search-forward

これ出来んのかいな?と思って調べたら、あった

github.com

それぞれ、

という名前。

まあこれだけの話なんだけど、若干検索しづらいトピックだったので。

じゃあの。

家のMac mini (Mid 2011) にManjaro入れた

まんじゃろ。まんじゃろってなんじゃろ。なんじゃろ。

High Sierraのサポートがぼちぼち切れることが分かり、別にMacじゃないと特別困ることもなさそうなので入れてみた。

結果的に、Magic Trackpad (2じゃないよ、古いやつだよ) の動作以外は問題なかった。

何が問題になったかというと、慣性スクロールが効かないってこと。どうやらlibinputだとsynapticsの時にあったような設定ができないらしく、そのためだけにsynapticsにするのもなあ、ということで諦めてマウスを使うことに。

マウス、マウス〜 (マウスコンピュータのCMの歌)

あと、CPUの負荷が高そうな状況で如実にトラックパッドの操作がもたつく。

Macのいいところはハードとソフトにまたがってええ感じにしてるところや、とはよく言うが、こういうのがあると「確かにな」と思わざるを得ない。

でもまあ、それだけっちゃそれだけだよな。みたいな。

じゃあの。

いまさらAIY Voice Kit V1でおうちハックデビューした

子供のライフログの記録・共有にぴよログというアプリを使っている。

play.google.com

おしっこのたびにアプリを起動するのは面倒なので、まずAmazon Dash Buttonで「ボタン押したらおしっこが記録される」というものを作った。リンクは適当

墨田区でこんなに役立つボタンを作ったのは俺くらいだろう、ワッハッハ

後に調べたらアプリ公式にGoogle Assistant連携機能があり、「OK Google, おしっこ 」みたいに音声入力できることがわかった。オーノー、俺が作ったものはまったくの無駄じゃないか。

しかし公式のこの機能、微妙にバグっていて使っているうちにアカウント連携が切れてしまうようだ。ここいらでちょっとだけIoT(死語)周りに興味が出てきたので、会社にあったAIY Voice Kit V1を持ち帰って何とかしてみることにした。

これな。アマゾンでものを買え

色々調べた結果、公式イメージに同梱されている↓を改造すると比較的簡単だということがわかった。

aiyprojects-raspbian/assistant_library_with_local_commands_demo.py at v0.9 · google/aiyprojects-raspbian · GitHub

これをいじれば特定の音声コマンドだけローカルでオーバライド、つまり、"OK Google, xxx" のxxx部分を適当にパースして特定の処理を走らせることができる。細かいことは考えずにGoogle Assistantに乗っかれるので大変ラクである。

(なお余談だが、Voice Kit V2はRaspberry Zeroの処理性能の問題で「OK Google」からの起動ができず、ボタンを押して会話を開始するという音声入力インタフェースの良さを完全に殺したようなシロモノなので買ってはいけない

没案

  • 真面目にGoogle Assistantのスキルを作る => Dialogflow意味分かんないので却下
  • snowboy入れて「OK Google」以外のホットワードで起動する => めんどくさいので却下
  • それ以外 => なんだかんだでめんどくさいので却下

以下、実装したコマンド

  1. OK Google, おしっこ => おしっこ入力
  2. OK Google, うんち => うんち入力
  3. OK Google, 両方 => おしっことうんち入力
  4. OK Google, おっぱい|左|右 => 授乳入力
  5. OK Google, xxミリ => ミルク入力

墨田区でこんなに便利なGoogle Homeを持っているのは俺くらいだろう、ワッハッハ

以前はこうしたハックにあまり興味がなかったのだが、子供が産まれてから「やりたい時にやりたい勉強をやりゃいいか」という気分になった。目的意識にとらわれない、学ぶ楽しさやモノを作る楽しさが伝えられたらいいなと。

gRPCサーバをAWSのNLBでロードバランスする場合のtips

※ この記事に書いてあるのは「ベストな方法」ではないです。時間に制約がある中でとりあえずこうやって問題解決した、今のところは良さげ、という記事です。

gRPCサーバのロードバランスを行う方法については色々な方法があって、それをくだくだ書くことはしない。リンク先を参照されたし。

grpc.io

hakobe932.hatenablog.com

まー要は

  1. クライアント側でやる
  2. Proxy挟んでそいつにやらせる
  3. L4でロードバランス
  4. L7でロードバランス

のどれかを選ぶことになる。サービスがコンテナベースで動いているなら、EnvoyとかのSideCarにやらせるのが一番センスいい気がしている。

で、ちょっとそのためだけに実環境をコンテナ化するわけにもいかんし、Proxyサーバを別に運用したくなんてないし、そしてAWSのL7ロードバランサであるALBはバックエンドのHTTP/2通信に対応していないクソである。どうするか。色々考えた末にNLBを使ってL4でロードバランスすることにした。

この方法には問題があって、要はL4だとメッセージ単位でなくコネクション単位でしかロードバランスしないので「あんまりロードバランスできていない」感じになってしまう。まあそこは妥協しよう。

で、やっていったところ、開発環境で問題が起こった。

blog.manabusakai.com

上記リンク先にあるように、NLBは勝手にtimeoutしてコネクションを切る。最大3600秒か。これが起こるとクライアント側が通信しようとした時点でサーバ側からRSTが送りつけられ、gRPC的にはUNAVAILABLEエラーが発生する。困ったことにこれが発生するとコネクションが腐ったような状態になってしまい、gRPCサーバを一度再起動しないと直らない。困ったな、困ったな。

だったらそもそもkeepaliveせずに都度接続すりゃいいんじゃないか?と思って色々試したが、gRPCクライアントのコネクション管理は内部的にChannelと呼ばれる何かによって行われており、Rubyレベルでオブジェクトを破棄してもTCPコネクションを閉じてはくれない。そして明示的に閉じるインタフェースもない。困ったな、困ったな。どうやらgRPCではユーザ側でTCPコネクションの状態を制御するもんじゃないらしい。全ては俺たち (Channel) に任せろ、お前らは手を出すな。そういう思想らしい。

それならkeepaliveの上でheartbeatをちゃんと打つように設定するか、と思ってクライアント側やサーバ側の設定値をいろいろいじっても、heartbeat送ってる感全然なし。俺はtsharkとにらめっこ。なんだこりゃ!設定が反映されないぞ。困ったな、困ったな。こんなことやってるとリリースに間に合わんぞ。

どーも調べると、クライアント側にも設定自体はできるが意味ないオプションがあったりするようだ。それで色々いじっているうち、「サーバ側から一定時間でコネクションを切る」というオプションはちゃんと動くことがわかった。

grpc.max_connection_age_ms

これだ。

これを設定すると、一定時間以上接続の続いているコネクション(ストリーム?)にはHTTP/2のGOAWAYフレームが送出されるようになる。なお、クライアント側では切断時にGOAWAYエラーが発生するようになった。

ちなみにgRPCの公式な仕様として、「サーバ側から明示的にコネクション切る際にはGOAWAYを送る」ということになっている。らしい。

grpc/PROTOCOL-HTTP2.md at master · grpc/grpc · GitHub

なので、この設定を入れることによって

「3600秒で勝手にNLBがコネクションを切り、クライアント側はUNAVAILABLEエラーを受け取る。以降、コネクションが腐ったような状態になる」

から

「一定時間経過で明示的にGOAWAYフレームが送られ、クライアント側はGOAWAYエラーを受け取る」

に変わったことになる。微妙な違いのように見えるが、事態をハンドリングできているだけ後者の方がだいぶマシである。

で、このままだと新規リクエストを送る際にGOAWAYエラーが発生してしまうことがある(明示的にGOAWAY送る設定にしたんだから当たり前だけど)。カバーするためにクライアント側でGOAWAYの時だけリトライを行うようにして解決しました。ちゃんちゃん。

あ、ちなみにクライアント側の言語はRubyでした。

CベースのgRPCサーバをgraceful restartする、たった一つの冴えたやり方

たった一つかどうかは知らんけど、「まあとりあえずこれでいいや」みたいな方法。

「CベースのgRPCサーバ」とは何ぞやっていうと、gRPCの実装はC言語ベースの実装・golang実装・Java実装に分かれているっぽくて、いわゆるRubyとかPythonとかのLLにおけるgRPC Serverは「各言語のライブラリ => 各言語のC言語バインディング => Cで書かれたコア」という構成になっているっぽい。早い話が、goとJava以外ではCベースの実装を使っていることになる。

で、ある程度真面目にRuby / gRPCで書かれたAPIサーバを運用しようとするにあたって「これどうやってgraceful restartしようかな」と思ったわけですね。

今まで僕がgraceful restartをやる方法といえば、

  1. UnicornとかNginxとか、サーバアプリケーション自体がgraceful restartを実装しているのでそれに乗っかる
  2. H2OとかRhebokとか、server-starter経由でgraceful restartできるサーバアプリケーションを使う

のどっちかしかやってこなかったわけですが、gRPC Serverは少なくとも1ではなさそうであり、2のためにコードに手を入れるのもなんだかな、という感じだったので「どうしたもんかな〜」と思いながらgRPCのCのソースを眺めいていたわけです。

そしたら、アレですよアレ。なんとCベースのgRPCサーバはデフォルトでSO_REUSEPORTが有効になってるんですね。やった!これや!ということで超手抜きなgraceful restartの実現方法がこちら

  1. 新サーバを立ち上げる (同じportをlistenしても死なない、なぜならREUSEPORTが効いてるからな!)
  2. 暫し待つ
  3. 旧サーバを殺す

Great.

ただしこの方法には明らかな問題があって、もし新サーバを立ち上げるのに失敗した場合は爆死してしまう。そこは新サーバの立ち上がりをちゃんと確認してから旧サーバを殺すようにすればいいだろう。

イマドキはgraceful restartなんてやらずにBlue / Greenなdeployを行うのが主流だったりするのかもだけど、まあやりたい人はいるだろうということで。