stefafafan の fa は3つです

"すてにゃん" こと id:stefafafan のブログです

ISUCON Go実装のデプロイスクリプトを組むにあたって、アプリケーションのバイナリ名やデーモンのサービス名を機械的に取得する

ISUCONのデプロイスクリプトを組むにあたって、なるべく一撃でデプロイできるように準備したい。そう思った時に、Goのアプリケーションをビルドする際のバイナリ名やsystemdで動いているサービスの名前が毎回変わっていることを思い出したのでそこも機械的に判定できるといいな、と思って試行錯誤してみました。

アプリケーションの場所を見つける

webapp というディレクトリの中に各言語のアプリケーション実装が配置されているというのがいつもの流れですが、大体 /home/isucon/webapp にあると思いきや catatsuy/private-isu の場合は /home/isucon/private_isu/webapp にあって、微妙に配置場所が変わることもあるかもしれません。

findコマンドを使って webapp ディレクトリを見つけるというのをまずやっておきます。

$ find /home/isucon -maxdepth 3 -type d -name "webapp*"
/home/isucon/webapp

その上で言語の名前のディレクトリが来ますが、Goの場合は go という名前だったり golang という名前であることがあります。

ビルドの際はこの両方のディレクトリを念の為試しておくと安心できます。

$ cd webapp/golang 2>/dev/null || cd webapp/go 2>/dev/null || exit 1;
$ pwd
/home/isucon/webapp/go

サービス名を特定する

例年のISUCONではsystemdを使って各言語の実装が動いていて、サービス名には isugo のように言語名が含まれているがちです。

  • ISUCON14の場合は isuride-go.service

この前提を元にサービス名を特定することができます。

systemctl list-unit-files を実行するとユニットファイルの一覧を取得できるので、その中で isugogrep しつつ、 enabled という文字列が邪魔なので削るとサービス名だけを抽出できます。

$ systemctl list-unit-files | grep 'isu' | grep 'go'
isuride-go.service                             enabled         enabled
$ systemctl list-unit-files | grep 'isu' | grep 'go' | awk '{print $1}'
isuride-go.service

go build 時のバイナリの名前を特定する

アプリケーションをビルドする際、ISUCONの回ごとにバイナリの名前が違うものになっているがちです。

  • ISUCON14の場合は isuride

go build -o isuride-o の引数をどうすればいいのかを探りましょう。

ISUCONでは systemd を使ってサービスのデーモン化をしているので、ユニットファイルの中身を確認することで実行しているバイナリが何かを特定することができます。

systemctl cat コマンドを使うとユニットファイルの中身を出力できます。

$ systemctl cat isuride-go.service
# /etc/systemd/system/isuride-go.service
[Unit]
Description=isuride-go
After=syslog.target
After=mysql.service
Requires=mysql.service

[Service]
WorkingDirectory=/home/isucon/webapp/go
EnvironmentFile=/home/isucon/env.sh

User=isucon
Group=isucon
ExecStart=/home/isucon/webapp/go/isuride
ExecStop=/bin/kill -s QUIT $MAINPID

Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

ExecStart に実行しているバイナリが指定されているのでここの最後の isuride を抽出すれば完成です。

$ systemctl cat isuride-go.service | grep ExecStart
ExecStart=/home/isucon/webapp/go/isuride

$ systemctl cat isuride-go.service | grep ExecStart | sed -E 's|.*/([^/ ]+).*|\1|'
isuride

Makefile の中ですべてを組み合わせる

ここまでわかってきたことを組み合わせることで、ISUCONの回ごとにMakefileをカスタマイズしなくとも同じ設定でwebappの中身をデプロイできるようになります(多分)。
前提: このデプロイ方式は手元のwebappの内容をrsyncでpushしています。

deploy-webapp:
	@for server in $(SERVERS); do \
		WEBAPP_DIR=$$(ssh $$server 'find /home/isucon -maxdepth 3 -type d -name "webapp*" | head -n 1'); \
		rsync -avz ./webapp/ $$server:$$WEBAPP_DIR; \
		GO_SERVICE=$$(ssh $$server "systemctl list-unit-files | grep 'isu' | grep 'go' | awk '{print \$$1}'"); \
		BUILD_OUTPUT=$$(ssh $$server "systemctl cat $$GO_SERVICE | grep ExecStart | sed -E 's|.*/([^/ ]+).*|\1|'"); \
		ssh $$server "cd $$WEBAPP_DIR/go 2>/dev/null || cd $$WEBAPP_DIR/golang 2>/dev/null || exit 1; go build -o $$BUILD_OUTPUT"; \
		ssh $$server "sudo systemctl restart $$GO_SERVICE"; \
		ssh $$server "sudo systemctl status $$GO_SERVICE"; \
	done

そのほか: ssh越しだと go コマンドが見つからない

private-isu や ISUCON14 でsshごしに go build を実行しようとしてもコマンドが見つからないというエラーになりました。

サーバ上で which go を実行するとどうやら /usr/bin/go ではない場所のバイナリを使っていることがわかりました。

$ which go
/home/isucon/local/golang/bin/go

PATHを通すなどすると解消される気がしますが、改めて最新のGoを入れ直すのもありかと思い、以下の手順を元に毎回最新のGoを入れ直すことにしています。

go.dev