Unyablog.

のにれんのブログ

systemd の dbus のパス

よく混乱するのでメモ。

Init としての systemd

/run/systemd/private

systemd 専用の direct な socket。 なので、ここに投げたら直接 systemd に飛ぶ*1。 systemctl などはここに投げている。

systemd が直接 bind / listen しているので、 dbus-monitor で見たりはできない。はず。

/run/systemd/private Used internally as communication channel between systemctl(1) and the systemd process. This is an AF_UNIX stream socket. This interface is private to systemd and should not be used in external projects.OSOCKADDR_UN_LEN

https://www.freedesktop.org/software/systemd/man/systemd.html#/run/systemd/private

/run/dbus/system_bus_socket

こちらは system bus と呼ばれるもので、 dbus-daemon が作成している。/etc/dbus-1/system.d/foo.conf に然るべき設定を置けば dbus-monitor --system で monitor できる。

DebuggingDBus - Ubuntu Wiki

systemctl の場合、 systemd の private で機能が足りない時に使われる。

例えば systemd-run --wait など*2

systemd --user 時

/run/user/$uid/systemd/private

これは systemd --user が作成する同様の private な socket。 systemctl --user を使うとこちらに飛ぶ。

/run/user/$uid/bus

これは session bus と呼ばれるもので、ユーザーのセッションごとに作られるもの。自分の環境だと dbus-daemon によってここに作られる。 このパスは $DBUS_SESSION_BUS_ADDRESS に入る事が多い。

system bus と同様に、 /etc/dbus-1/session.d/foo.conf で設定できる。

GUIsystemd --user に対する method_call はここに投げると良さそう。

*1:A よって A みたいな文章だ

*2: ref/unref が足りないらしい

Debian で systemd --user 時に dbus-daemon を起動する

systemd を user 単位で動かした際、主に CUI 環境では*1dbus-daemon は systemd が内部で用いるものしか生成されない*2

このような状況で、dbus 経由で systemd 等を触りたい場合は dbus-user-session を入れると良い。

Debian -- stretch の dbus-user-session パッケージに関する詳細

このパッケージの実体は systemd unit (/usr/lib/systemd/user/dbus.{service,socket} など) である。stretch の時点ではファイルを設置しているだけなので、実際に dbus-daemon を起動するには

$ systemctl start dbus --user

などとする。

そうすれば、 /run/user/$uid/bus ができて使えるようになる。

$ busctl --user tree
Service org.freedesktop.DBus:
Only root object discovered.

Service org.freedesktop.systemd1:
└─/org
  └─/org/freedesktop
...

busctl さっき知ったけど便利。

*1:gnome など dbus 使う GUI 環境で使っていれば gnome-session が dbus-daemon を起動してくれる

*2:/run/user/$uid/systemd/private がそれ

Jenkins でローカルレポジトリを github のレポジトリに同期する

Jenkins でローカルにあるレポジトリから github に同期したい。pipline で行う場合、 GitPublisher Plugin が使えないので、自分で git push する必要がある。

1. cred-idgithub にアクセスするためのユーザー名とトークンを Jenkins の credential に登録する。
2. Declarative pipeline で以下のスクリプトを書く。
withCredentials(
        [usernamePassword(
            credentialsId: 'cred-id',
            passwordVariable: 'GIT_TOKEN',
            usernameVariable: 'GIT_USERNAME'
            )]
        ) {
    sh('git push https://${GIT_USERNAME}:${GIT_TOKEN}@github.com/foo/bar master')
}

これで、 withCredentialsGIT_TOKENトークンが入り、 GIT_USERNAME にユーザー名が入る*1

3. ローカルレポジトリに Jenkinsfile を push して、そのレポジトリから pipeline project を設定する。

ここで、Jenkins のプロジェクトの設定で Additional Behaviours -> Check out to specific local branch を加えて Branch name を ** または空欄にする必要がある。何故なら、初期状態ではワークスペース上の git はブランチから外れているからである*2

参考

[JENKINS-28335] Step to run Git commands w/ credentials & tool (was: GitPublisher support) - Jenkins JIRA

*1:変数部はコンソールでは ** と表示される

*2:pipeline 上で checkout しても良い

Actions on Google SDK を用いた Action の開発で詰まった点

Google Assistant 用のアプリケーションを開発するときは Actions on Google SDK を使う。

よくあるのは Dialogflow 経由で設定して Firebase などで返答を決定するものだが、 Actions on Google SDK というものもある。

これを使えば、json で Action について記述して、gactions コマンドでデプロイすることができる*1

https://developers.google.com/actions/sdk/

基本的にドキュメントに書いてあるとおりなのだが、詰まった点をいくつか。

Dialogflow から Actions SDK に変更する時

$ gactions test では Dialogflow から変更されない。 $ gactions update を行うことで、 Dialogflow から Actions SDK にターゲットが変更される。

1プロジェクト1Actionなのは微妙に不便 *2

初めの呼びかけ以降は基本的に生のテキストが降ってくる

intent に独自の intent を記述して、 queryPattern に反応する語句とパースする変数を書くとその intent が使えるようになるのだが、これは最初だけである。 一度でも app.ask を行うと、 app.intent.TEXT 等のはじめからある intent しか降ってこないので注意。

For the first conversation turn, the intent will refer to the intent of the action that is being triggered. For subsequent conversation turns, the intent will be a built-in intent. For example, if the expected input is actions.intent.OPTION, then the the intent specified here will either be actions.intent.OPTION if the Google Assistant was able to satisfy that intent, or actions.intent.TEXT if the user provided other information.

https://developers.google.com/actions/reference/rest/Shared.Types/AppRequest#Input

要するに、

「Ok Google、○○ に XX と言って」

のような言い方をしないとパースされない。 Dialogflow を使えってことらしい(↓で Google のエンジニアが言っている)。

queryPattern の変数は前後に空白を置く

空白を置かないと反応しない。

"$color:SchemaOrg_Color の服を教えて"

個人用の Action に使っていたのだけど、微妙なので Alexa skill も使ってみたい。

*1:この時にプレビューの expire を指定できるので100年ぐらいにしようという目論見だった。しかし、もうじきそのオプションは消えるらしい

*2:Action だけ削除ができないのも難点

systemd-nspawn に関するメモ (--as-pid2 / ネットワーク関係)

--as-pid2

指定されたものを pid 2 で実行する。pid 1 には STUBINIT が入り、pid 1 に課せられた仕事を代わりにやってくれる(シグナル処理等)。何か(実質)シングルプロセスで立ち上げたい時はコレ使ってると良さそう。

root@piyo:~# systemd-nspawn --machine hoge -n --settings=no --as-pid2 bash
Spawning container hoge on /var/lib/machines/hoge.  
Press ^] three times within 1s to kill container
root@hoge:/# ps auxwwf 
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  56984  3044 ?        Ss   06:41   0:00 STUBINIT
root         2  0.0  0.1  18220  3176 ?        Ss   06:41   0:00 bash
root         3  0.0  0.1  36636  2752 ?        R+   06:41   0:00  \_ ps auxwwf

https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html#-a

ネットワーク関係

--port --network-veth --network-bridge などを使う場合、親子ともに systemd-networkd が立ち上がっているべきである。

systemd 自体は ve-hoge 等のデバイスを新たに作る。この prefix がついていると、 /lib/systemd/network/80-container-***.network が反映されて諸々の設定が行われる。

コンテナ側も同様で、 host0/lib/systemd/network/80-container-host0.network で設定がなされるので systemd-networkd が立ち上がっていないと上手く動かないことがある。*1

https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html#-n

--port

--port (ポートフォワーディング)の場合、 IPMasquerade が使われているので、 systemd-networkd は必須。

このポートフォワーディングには iptables の NAT が使われており、 # iptables -L -t nat でその様子を眺めることが出来る。DebianUbuntu の場合、古い (systemd 231-5 より前) と iptables の lib がない状態でコンパイルされてて IPMasquerade は使うことが出来ない。

$ sudo machinectl start hoge

$ sudo iptables -t nat -L -v
Chain PREROUTING (policy ACCEPT 2 packets, 200 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DNAT       tcp  --  any    any     anywhere             anywhere             tcp dpt:2000 ADDRTYPE match dst-type LOCAL to:10.0.0.2:2000

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 2 packets, 126 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DNAT       tcp  --  any    any     anywhere            !127.0.0.0/8          tcp dpt:2000 ADDRTYPE match dst-type LOCAL to:10.0.0.2:2000

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

ちなみに !127.0.0.0/8 となっていることからも分かる通り、親コンテナからはポートフォワーディングは見えないので注意。外部 IP などから確認する必要がある。

github.com

#787480 - build with iptables support - Debian Bug report logs

*1:DHCP を使っているので networking でも初期状態でまあまあ上手くはいく。

option が disabled の時の jQuery の select multiple に対する val の挙動

<select multiple>
  <option>foo</option>
  <option disabled selected>bar</option>
</select>

上のような select を用いた時に、jQuery で選択中の値を取得しようとすると disabled な option は省かれてしまう。

jquery/val.js at 56136897f241db22560b58c3518578ca1453d5c7 · jquery/jquery · GitHub

bootstrap-select が上手く動かなくて気づいた。

w3c を見ても

The disabled attribute is a boolean attribute. An option element is disabled if its disabled attribute is present or if it is a child of an optgroup element whose disabled attribute is present. An option element that is disabled must prevent any click events that are queued on the user interaction task source from being dispatched on the element.

https://www.w3.org/TR/html5/forms.html#the-option-element

と書いてあって、省くような記述はない。

また、<disabled option element>.selectedtrue を返すし、 <select element>.selectedOptions では disabled のも帰ってくる。

調べてみたら、どうやらそういうものらしい…。

#13097 (using .val() on a disabled html-select-option has different results since 1.8.3) – jQuery - Bug Tracker

javascript - Get Value of Disabled Option in Select Multiple Jquery - Stack Overflow

ISUCON7 の予選に出た (95352点)

今年も isucon に参加した。 id:tyageid:non_117 と一緒に参加した。

isucon.net

max 113846、最終的なスコアは 95252 だった。言語は ruby 。今年は社会人枠で出たので本戦には10万点ぐらい足りなかった。

まあ学生の中だけで考えると上の方に位置していたので良いんでは? 前回は fail だったし大躍進である。

レポジトリはこちら。 Systemd の設定ファイルなどコミットしていないのも一部あるけど。

github.com

準備

もともと何度か出場していたので今回は特に練習は行わなかった。デプロイ用のスクリプト、負荷試験用スクリプトなどの軽い環境整備はチームの人がやってくれていた。

当日

自分は11時過ぎに起きた。一時間遅れていなかったら遅刻するところだった。

ついたあとは3人で軽く作戦会議。初動の分担を決めたりしていた。

開始

蓋を開けてみたらまさかの複数台構成だった。しかもちゃんと DB と app が分かれていて。

あとは時系列で。

  • 13:00 mackerel やデプロイスクリプトssh 鍵などを整備。
  • 13:30 実装を眺める。nginx のログを仕込む。
  • 14:00 goaccess で計測結果の確認。 今度はスローログを見始める。
  • 14:30 nginx で /js, favicon.ico, /css, /fonts を cache で返すようにする。
  • 15:00 icons のキャッシュが上手く行かなくて調べ始める。 また、スローログから index を貼ったりする。

この時、なかなか 304 が返らなくて調べていた。最終的に tcpdump して正常時とヘッダーを見比べ、一回目のレスポンスに Last-Modified ヘッダがないことが原因とわかった。

icons は一回置かれたら変更されないことがわかっていたので、 nginx 側で add-header Last-Modified "Sat, 04 Jun 2011 08:51:44 GMT"; と固定してしまった。これで、 304 が二回目のリクエストから適用されるようになり点数がだいぶ上がった。

次は一回目の icons 取得、その他一般の api がネックになるようになったので、アプリケーションのコード自体を変更する必要が出てきた。

  • 16:30 icons 以外の遅い部分を改善し始める。 sleep はとりあえず消した。

なぜか2台目の app がうまく動かない。よく見たら python が動き続けていた!! ちゃんと disable する。

  • 17:00 proxy_cache を使って nginx で img をキャッシュすることで一回目のアイコン取得の高速化を試みる。

うまくキャッシュできたが、結局 /register で新しく登録されたアイコンを取ってくるのは遅いままなので、DB をやめてファイルに置くことを検討する。

  • 18:30 リクエストはすべて一台の nginx で受けるようにして、app を分配することにする。

また、 mysql に画像を保存するのをやめてすべて LB のあるサーバーに置くようにする( /profile もこのサーバーに飛ばす )。これで nginx から静的配信ができるようになった。この時 3~5 万ぐらい?

  • 19:00 app をよく見ると N+1 がたくさんあったので改善する。サーバー側では FD 増やしたり mysql のキャッシュ増やしたりしていた。

この頃から DB のコネクションが残り続けることが問題になり始める。結局 statement をすべて close するようにした。

  • 20:00 puma のスレッドを増やしたらスコアが増えていくことに気づく。結局二台とも50スレッドぐらい動かした。11万点ぐらい取れたので満足し始める。

この辺りでやっと CPU が全部 100% 回るようになった。

  • 20:45 諸々サービス切ったりして reboot 、最終スコアを出す。ベンチマークガチャを引いて終了。

自分は主に nginx・キャッシュ、その他サーバー周りを見ていた。

反省とか

  • キャッシュ周りの解決に2~3時間かかっており、ここを N+1 の残りとかに回せればもう少し点取れてたかもしれない。さっさとファイルに保存するようにしておけばよかった。
  • unix domain socket にしようとして失敗したのだけど、できていればファイル投稿周りは早くなっていたと思う。
  • もうちょっと早くなってくると、雑な nginx LB 一台だと帯域が〜〜ってなってきそう。
  • 結局今年は redis 一切使わなかった。 varnish とか使うと icons 周りでもうちょっと捌けたかもしれない?

  • log をみてちゃんとキャッシュができているか(304 が返っているか)丁寧に確認したのは良かった。ブラウザと挙動が微妙に違ったので。

  • mackerel-agent を入れていたのも良かった。はじめにサーバー情報を取得できるし、どこで詰まってるかもわかりやすい。

感想

バランスが取れていて良い問題だったと思います。いろいろ改善できる場所があり、楽しくて良かった。

来年も絶対に参加したい。社会人枠でも本戦に出れるぐらいにならないとな〜。

運営の方々、今年もありがとうございました。本戦の問題も楽しみです。