Unyablog.

のにれんのブログ

tmux の if-shell は sh で実行される

tmux の if-shell ではシェルコマンドの結果に応じて実行するコマンドを変更できる。

今までここにバージョンによって設定を変化させるように書いていたが上手く動いていない時があった。

if-shell "[[ `tmux -V` =~ 2.[1-9]$ ]]" "set -g mouse on"

理由

tmux は sh -c でシェル部を実行しているため、 sh の実装が bash でない場合は [[ が使えない。*1

tmux/job.c at ddffda4da55e2d4040c981bfb5257c4f7281000f · tmux/tmux · GitHub

Fix

[ など、 sh でも使えるような(POSIX に準拠した)スクリプトに修正する。

今回は grep を使うようにした。

if-shell "tmux -V | grep -q '2.[1-9]'" "set -g mouse on"

github.com

間違えて yes を入れたときに、終了するために ps 見てたら sh -c yes が起動していたので気づいた。

*1:今回は dash だった

ISUCON8 予選・本選に参加しました(チーム名 :thinking_face:)

3年前から参加している ISUCON の、予選と本選に行ってきた。

結果を先に書くと、 5981 点で学生5位、全体では14位だった。

チーム

ざっくり自分がインフラ担当、 utgw が SQL 等の改善、 wass がアプリケーションの改善と方針立てをやっていた。

予選

自分が二人とチーム組むのは初めてだったので二回ぐらい練習して挑んだ。

utgwkk.hateblo.jp

↑は utgw の記事。

自分は

  • サーバーのセットアップ
  • h2o を Nginx に
  • MariaDBMySQL 5.7 に移そうとして失敗
    • 確か未定義動作の動作が違っていた
  • 複数台構成
  • 見守り
    • レスポンス比べるとか
  • ベンチマーカーへの祈り

といったことをやっていた。現代は Systemd なので CentOS でも大体いける。

アプリはほとんど見ていなかった。チームメイトに感謝。

本選

scrapbox.io

↑ は wass の記事。

github.com

↑のレポジトリの Your Name が大体自分。

タイムライン

前日

  • 新幹線が遅延したので21時ごろに東京着
  • カプセルに止まったらいびきステレオ環境だった
    • 結局頑張って4時間ぐらい寝た

09:10 頃

新宿につく。バスタのコンビニで菓子買おうとしたら混雑していたので諦める。

09:30 前

会場に到着。標準的なテーブルを獲得。物理的な準備をする。

f:id:nonylene:20181022191824j:plain

10:00

開始。なるほど isucoin! 仮想通貨ネタ!

まずはドキュメントを読み、 tmux-cssh を使って鍵配布やアカウント作成を行う。 雑な shell script ぐらいは用意しておいても良かったかもしれない。

10:30

  • firewall 止める、ulimit 解除する、 pt-query-digest 入れるあたりのことをやる。
    • ちなみに docker-compose で行くなら ulimit は解除しなくても良かった。
  • 全体的な構成をざっくり見る。 docker-compose で立ち上がってるのを見て脱出を検討する。

Restart したら iptables がどうのこうのと出てさっぱり分からなかったので脱出を決定*1

とりあえず本体である

  • isucoin 本体
  • MySQL
  • Nginx

を脱出することに。

isucoin 本体

Python だけを systemd unit に切り出した。 pipenv なのでやるだけ *2 だったのだけど、使ってる Python が 3.7 だったのでコンパイルした *3

作業してるとコンパイルが微妙に早いホストと遅いホストがあった*4ので早いホストに MySQL 入れることに。

isucoin-python.service · wass88/isucon8-final@1eeb658 · GitHub

MySQL

MySQL は 8.0 だった。 予選の段階で 8.0 が yum でサクッと入っているのは分かっていたので入れる。

ただ、 my.cnf.d 読んでくれなかったり、 8.0 はオプションがちょくちょく違ったりで困った。validate_password で色々とハマるとか*5

データの流し込みとかはサクッとできた *6

Nginx

特に困らなかった。 upstream がなくて link でやってるのはへ〜って思った。

これらの作業が全て終わったのが 12:40 ほど。

f:id:nonylene:20181022191026p:plain

12:45

昼飯。

13:15〜

見つけたところから色々やっていく。

  • Nginx で静的ファイル送信
  • keepalive
  • http2
  • MySQL ホスト分離・メモリ増やす・スロークエリ出すなど
  • アプリケーション複数台構成
  • initialize を各ホストにくばる
  • worker 増やす
  • Nginx でセッションごとの振り分け(sticky)できるようにコンパイルし直す

その他諸々。複数台を除くと顕著な効果はなくて、他のネックが解消された際に困らない程度だと思う。

たまにペアプロを手伝ったりもした。アプリケーション側の改善のおかげで点数が上がっていった。

終盤(16:30~)

終盤は htop とか見ながら、ペアプロで手伝いをしていた。

最後再起動試験をし、 share を有効にしてみて fail し、 BAN を実装しかけて fail し、5分前に差し戻して終了。

5981 点で学生5位、全体では14位だった。まあまあですね。

f:id:nonylene:20181022192735p:plain

反省

Docker の切りだし

Docker の切り出しには2時間かかった。

その間は(iptables のエラー出てて)ベンチ回せず他の所を見てもらっていた。その時間ベンチできてれば伸ばせたかもしれない。

ただ、Docker 切り出した後構成関係で詰まることはなかったので、個人的には良かったと思っている。

Docker も docker-compose も、触りぐらいしか触ったことないので本格的に触らないとな〜〜〜〜と言い続けている。妙な苦手意識が未だにある。

Share

Share はもう少し早く見るべきだった。最後の15分ほどでアクセス大量に来て落ちたのを見て止めたけど、他のチームが 1/3 ぐらい返していたという話を聞いて、なるほど〜〜〜〜〜 AB テスト〜〜〜ってなった。

アプリケーション

自分は Python も Go も触るので非同期処理をしている様子をもう少し手伝うべきだった。 Nginx に sticky を入れたりはもう少し様子見てからでよかったのでは?

インフラ方面は大体やることやったので満足している。

その他感想

  • 3年ぶりの本選ですが、学生のレベルが優勝するほど高くなっててすごい。
  • 鍵などの準備はしてなかったけど、あまり困らなかった。
  • 非同期したくなったときに Go か node か書く練習はするべきだな〜と思う。
  • pipenv や Python 3.7.0 が使われているのは Python 大好き人間としては嬉しかった。手数は増えたけど😇。
    • 実は Python での参戦は初めて。コードが高速に読めてよかった。
  • tmux-cssh を使ってみたらめちゃくちゃ便利だった。isucon 程度であれば ansible もなくて良さそう感。
  • 情報を表示するだけの PC を持ち込んだのは正解だった。いつでも netdata や htop を眺めることができた。

チームメイトだった id:utgwkk id:wass80 に感謝。本選でもアプリケーション面はほぼやってもらった。

楽しかったです

予選も本選も特に不満がなくて素晴らしい大会だったと思います。問題もすごく良かったです。

来年あればもちろん参加するし、今度こそ優勝したい!!

運営の皆さん、本当にありがとうございました!!!!!

*1:ちなみにこれは docker restart したら直ることに後で気づいた

*2:utgw と nonylene は pipenv をよく使っている

*3:とは言っても yum-builddep して tar 持ってきて make するだけ

*4:よくある話

*5:あのときは同じパスワードじゃないと落とされるかもなとか思ったけど、今考えたらこだわる必要なかった

*6: mysqldump に mariadb のコメントがあったときはヒヤッとしたけど、初めのクライアントがそうだからそうですね

This AVD's configuration is missing a kernel file! Please ensure the file "kernel-qemu" is in the same location as your system image.

久しぶりに Android Studio を立ち上げて、公式エミュレータを作って立てたらこの事象に遭遇して立ち上がらなかった。x86_64 だとエミュレーターは立ち上がるが、画面がブラックアウトし、

resizing partition e2fsck failed with exit code 8

とログには出ている。

解決策

Android SDK Tools をアップデートする。

自分の場合、Android SDK Tools を android.bat (または android) 経由で更新していたのだけど、このときに 25 -> 26 へのアップデートが降ってこなかった。多分 26 で android コマンドが deprecated になって GUI がなくなった関係だろう。

Android Studio 経由でアップデートすると、Android SDK Tools 26 が降ってきて、 AVD を作り直すと使えるようになった。

android.bat はもう GUI では使えなさそう?なので諦めて Android StudioSDK Manager を使いましょう。

stackoverflow.com

zsh: typeset -Ug PATH

自分の zshrc 内では、$PATH の重複を排除するために typeset -U PATH と書いていた。

普段はこれで上手く行くが、関数内で source ~/.zshrc すると zshrc 内でエラーが沢山出た。

$ cat ~/.zshrc
...
function reload_zshrc {
  source ~/.zshrc
}
...

$ reload_zshrc
...
zsh:17: command not found: find
zsh:17: command not found: find
...

source 内では command not found となっているが、 reload_zshrc した後も $PATH は変わらず、コマンドも使えていた。 また、関数を経由せずに source ~/.zshrc とした時は正常に動いていた。

原因

これは、typeset は関数内の場合、 ローカル変数を作成 し、関数が終わったら unset するという挙動によるものである。

          Except  as  noted  below for control flags that change the behavior, a parameter is
          created for each name that does not already refer to one.  When inside a  function,
          a  new  parameter is created for every name (even those that already exist), and is
          unset again when the function completes.  See `Local  Parameters'  in  zshparam(1).
          The  same  rules  apply  to  special  shell  parameters, which retain their special
          attributes when made local.

Ubuntu Manpage: zshbuiltins - zsh built-in commands

なので、 reload_zshrc の中で typset -U PATH することで PATH の内容が初期化されてしまい、 command not found となっていた。

echo $PATH # /usr/local/bin:/bin: ...
typeset -U PATH
echo $PATH # (Empty output)

対処

これを防ぐためには、 typset -g で global scope の PATH を使用するようにする。

          -g     The -g (global) means that any resulting parameter will not be restricted to
                 local  scope.   Note  that this does not necessarily mean that the parameter
                 will be global, as the flag will apply to any existing  parameter  (even  if
                 unset)  from an enclosing function.  This flag does not affect the parameter
                 after creation, hence it has no effect when listing existing parameters, nor
                 does the flag +g have any effect except in combination with -m (see below).

Ubuntu Manpage: zshbuiltins - zsh built-in commands

echo $PATH # /usr/local/bin:/bin: ...
typeset -gU PATH
echo $PATH # /usr/local/bin:/bin: ...

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 しても良い