ansibleのオプションが長くて覚えられないので工夫した話

結構前にansibleの実行時のオプションが長すぎて覚えられず、工夫したものをどこにも資料を残してなかったので、まとめてみました。

あくまで僕が取った一つの手段であり、ベストプラクティスだとは思ってないですが、少しでも誰かの役に立てばと思い残しておきます。

ansibleって?

サーバの構成を管理するためのツールです。これを使うことでアプリケーションを実行するための設定などをコードで管理し、設定を自動化することができます。

設定をコードで管理、自動化出来ることのメリットとしては、サーバの設定項目に対してレビューを挟むことがしやすくなり誤った設定などを防いだり、急遽サーバを増やす(スケールアウト)対応が必要になったときなどにすぐ対応することができる、などがあげられます。

ansibleのコマンド長い問題

今回の記事ではansibleのための細かい書き方などについては記載しません。(他のサイトや公式で十分いっぱい記事がありますので、そちらを見てください。)

ansibleを書いていると実行コマンドが下記のようにオプションがいっぱいついて長くなることはないだろうか?

ansible-playbook --vault-password-file $(VAULT_PASS) -i ec2.py -l tag_Environment_${ENV} --private-key=$(KEY) site.yml -u ${user} --check

上記は私が使ってる環境のプロビジョニング用のコマンドですが、もう長すぎて覚えられません。 一応解説しておくと、

  • --vault-password-file $(VAULT_PASS) はファイルを暗号化して管理しているときに複合化してプロビジョニングを実行するための鍵となるファイルを指定するオプション
  • -i ec2.py はダイナミックインベントリという仕組みを使うためのオプションで、プロビジョニング対象が動的に変化する際に使用する
  • -l tag_Environment_${ENV} はプロビジョニングを実行するホストのフィルタリングオプション
  • --private-key=$(KEY) はプロビジョニングの際に使用するssh用の鍵のオプション
  • -u ${user} はプロビジョニングの際に使用するssh用のユーザのオプション
  • --check はdry-runのためのオプションで、これをつけて実行すると実際にはプロビジョニングせずに実行したときの結果をテストすることができます

もちろん一つ一つの意味は理解しているので、思い出しながら書けば実行できないこともないですが、そう頻繁に実行するものでもなかったりするので、久しぶりに「さープロビジョニングするぞ」っていうときに「あれ、コマンドなんだっけ?」となることが多いです。

もちろん忘れても大丈夫なようにコマンドをどこかにメモしておいたり、READMEに書いておいてもいいと思うのですが、それを毎回見たりコピペして実行するのもなんだかイケてないですよね。

対策候補

上記の解決策として、下記を考えました。 1. Alias 1. シェルスクリプト 1. Makefile

Alias

一番最初に思いついたのがAliasでした。長ったらしいコマンドに対してAliasを張るというのはよくやりますよね。 ですが下記の理由から、Aliasでやることを断念しました。

  1. 1台のansible発射サーバから複数サービスのansibleを実行する際にオプションがそれぞれ違うためaliasでは対応しきれない
  2. ダイナミックインベントリを使うためのec2.pyにコマンドが依存しているが、この依存解決ができない

1台のansible発射サーバから複数サービスのansibleを実行する際にオプションがそれぞれ違うためaliasでは対応しきれない

私の触ってる環境的な問題でもあるかもしれませんが、複数のサービスを触っててそれぞれに対してansibleがある状態で、かつその実行を一台のansible実行用環境からプロビジョニングをかけているため、 aliasに登録すると他のサービスのプロビジョニングのコマンドとバッティングしてしまいます。もちろんこれをalias張る名前にサービス名などをつけて対応してもいいのですが、煩雑になりそうだったのでやめました。

ダイナミックインベントリを使うためのec2.pyにコマンドが依存しているが、この依存解決ができない

これについても私の環境によるものでもあるのですが、ダイナミックインベントリを使用しており、その解決にawsのec2.pyを使用しています。これについては公式でも書かれているので詳細はこちらを確認してください。

Working With Dynamic Inventory — Ansible Documentation

問題としては、このプロビジョニングコマンドを実行する際にあらかじめec2.pyを取得しておくことが必要なのですが、環境のセットアップはせっかくコードで管理できるようになったのに、環境セットアップするための手順がコードで管理できていないのでは、意味がないじゃないか。というのが私の意見です。なのでこのec2.pyに対しても何らかの形で取得しておくことが必要だということをコードで表現したいなと思い、aliasは却下になりました。

シェルスクリプト

次に思いついたのがシェルスクリプトです。まぁ、いくつかの手順があってそれを自動化したいとなると、カスタマイズ性の高いシェルスクリプトが候補に上がるのは当然かと思います。 シェルスクリプトであればサービスごとのansibleのレポジトリにスクリプトを含めて実行するようにすれば、aliasのようにバッティングしてしまう問題も防げます。

結論から言うと、私はシェルスクリプトを選びませんでした。もちろんシェルスクリプトでできないわけではありませんし、十分にありな選択肢ではあると思います。 ただ、自分がシェルスクリプトを書くほどのものではなく、かつシェルスクリプト書くのがめんどくさいと思ってしまったために却下しました。

Makefile

Makefileです。普段あまり触ってない人からすると「え、Makefile?」となるかもしれません。ですが、自分はMakefileを結構気に入っています。Makefileのいい点としては、依存解決ができ、簡単なコマンドのラップが書きやすい。これに尽きるかと思います。今回の目的でいうと、ansibleの長いオプションをラップし、サービスごとに微妙に違うオプションもサービスごとにMakefileを書くことで解決し、ec2.pyの依存解決も簡単に表現することができます。

具体的にどうしたか

最終的に下記のようなMakefileをサービスのansibleレポジトリに含めるようにしました。

init: ec2.py ec2.ini
.PHONY: init

ec2.py:
    wget https://raw.github.com/ansible/ansible/devel/contrib/inventory/ec2.py
    chmod +x ec2.py

ec2.ini:
    @echo 'please set up your credential ec2.ini'
    exit -1

VAULT_PASS=./default/path
ENV=default-env
KEY=default-key
USER=default-user
dry-run: init
    ansible-playbook --vault-password-file $(VAULT_PASS) -i ec2.py -l tag_Environment_${ENV} --private-key=$(KEY) site.yml -u ${USER} --check
.PHONY: dry-run

run: init
    ansible-playbook --vault-password-file $(VAULT_PASS) -i ec2.py -l tag_Environment_${ENV} --private-key=$(KEY) site.yml -u ${USER}
.PHONY: run

MODE=encrypt/decrypt
TARGET=your_target_file
vault:
    ansible-vault $(MODE) --vault-password-file $(VAULT_PASS) $(TARGET)
.PHONY: vault

encrypt:
    @$(MAKE) vault MODE=encrypt
.PHONY: encrypt

decrypt:
    @$(MAKE) vault MODE=decrypt
.PHONY: decrypt

このMakefileがあるディレクトリ上で make dry-run のように実行すると初回実行時は init のターゲットも実行されるのでec2.pyは自動でDLされ、ec2.iniにクレデンシャル情報を記入しろ、という旨のメッセージを出して終了します。また、ec2.iniがターゲットになっているのでこのファイルが生成されるまでこのエラーは解消されないので、作成されていないのにansibleが実行されることはありません。

また、おまけでファイルの暗号化のコマンドも僕は覚えられないので、make のターゲットにしてみました。こういう感じでコマンドのラップとしても使え、忘れたとしても make の補完リストから、何ができるかを判断できるので、便利ですね。

新しい人がプロジェクトに入ってきたとしてもよく使うコマンドがmakeにまとまっているとコマンドの説明を細かくしなくても理解してもらえるのでとても助かってます。

(本当は、公式でansibleの実行オプションをファイルで記述できれば一番いいんだろうけど、、

【zcrypt】Go製のツールを作る際に便利だったツールの紹介

zcrypt というgo製のCLIツールを作った。

github.com

何ができるの?

CLIで手軽にファイルの暗号化、複合化ができる。

作った背景

apiなどを使用する際の鍵などのような機密情報をバックアップ目的でgithubに乗せておきたいけど、さすがに平文で置いておくのは怖いので適当に暗号化して保存したかった。

暗号化、複合化するだけならopensslなどのコマンドを使えばよいがせっかくなのでツールを作ってみたかった。(コマンドのオプションを覚えるのも面倒だった。)

使ってる技術

使い方などはREADME見てもらえばだいたいわかると思うので、ここでは記述しない。

代わりにこのツールを作るに当たって利用させていただいた便利なツールについて書きたいと思う。

並列クロスコンパイルツール(gox)

golangはクロスコンパイル可能な言語である。コンパイル時にどの実行環境で動かすかを設定することでその環境用の実行ファイルを生成することができる。

しかし、golang単体でこれをやろうとした場合、いろんな環境で動くようにいろんなパターンの実行環境用のビルドファイルを作ろうとすると何回もビルドする必要があるし、オプションも実行環境に合わせて変更する必要があり、一つ一つコンパイルするので時間もかかりとても面倒である。

そこでこのgoxと言うツールを使うと、コマンド一つで複数の実行環境用のコンパイルを一度に実行してくれ、オプションも自動でいい感じにしてくれる。

github.com

リリースタグとファイルアップロード(ghr)

リリースタグを切るのとリリースタグにファイルをアップロードしてくれるツールがこちらである。しかも並列でアップロードできるので早い!

go製ツールのデプロイと言うと多くがリリースタグを切ってそこにバイナリファイルをおくことが多い。このツールではこの部分を自動化してくれるものだ。とても便利ですね。

github.com

デプロイのフロー

上記で紹介したツールをCircleCIからうまく使いデプロイを行ってる流れは次の通りだ。

  1. masterにマージ
  2. CircleCI上でイベントを検知
  3. CircleCIでビルドできるか確認
  4. CircleCIで正常に暗号化複合化できているか確認
    • ここはあえてgoでのtestではなく、ツールをCLI上から実行して動作確認するようにしている。コードはこちら
    • これはgoでテストしてもそれはライブラリのテストになってしまうと思ったためである。あくまでここでテストしたいのはCLIツールとして使用した時に正しく使えるかである。
  5. CircleCIで並列クロスコンパイル
  6. CircleCIからリリースタグ作成、バイナリファイルのアップロード

リリースタグ作成時に工夫したこと

自動でリリースタグを採番してリリースして欲しかったため、少し工夫した。

具体的には下記の部分である。

https://github.com/IkezoeMakoto/zcrypt/blob/master/get_tag.sh#L4

お手製のワンライナーであるが、やってることとしては簡単で

  1. git tag でタグ一覧取得
  2. sort で新しい順に並び替え
  3. tail -1 で1件のみ取得(この時点で最新のタグを取得できる)
  4. awk -F "." "{cnt=\$3+1}{printf \"%s.%s.%d\", \$1, \$2, cnt}" 少し難しいが、 . 区切りにして3つ目に+1をしてくっつけ直している

これでタグを自動でつけるようにしている。 もちろんセマンティックバージョニングに乗っ取るなら、機能性の追加や後方互換のない変更の場合には手動でタグを切る必要があるだろう。

まとめ

Go製のツール作るためのライブラリや環境が多く整っていて、簡単なものをサクッと作りやすいので何か困ったことがあればサクッとツールを作って解決してみてはいかがだろうか。

技術書典5 行ってきました

技術書典とは?

techbookfest.org

技術者の同人イベントですね。今回で5回目らしいです。私は今回が初参加でしたが欲しいものや興味あるものもあったので友人と一緒に行ってきました。

戦利品(買えたもの)

運よく早めに会場につき入ることができたので、欲しいものは一通り購入することができました。(ミュウツーは近くの公園でたまたま捕まえましたw) また、公式サイトには参加サークルのチェックリストもあり、効率的に回ることもできました。本当に助かります、ありがとうございます。

後払い決済のアプリについて

参加サークルのうち対応していたサークルのみですが、技術書典公式でQRコードでの後払い決済アプリを用意してくれていました。

これを使うと、事前にアプリをDLし登録(電話番号の登録が必要)を済ませておくと、イベント当日にサークルごとのQRを読み取ることで購入したい本を後払い決済できると言う仕組みのようです。ここまで用意してくれるなんて本当にすごいですね。。

技術書典

技術書典

  • Tatsu-zine Publishing Inc.
  • ビジネス
  • 無料

感想

結構事前にチェックしていたもの以外にも当日実際に本を手に取って読んで、「あ、これいいな」と購入したものも多くありました。実際に手にとって読んでみることの良さを久しぶりに実感できました。

また、こう言うところで本を出すことについての機会や、敷居が低くなったりといいイベントですね。やはりアウトプットは大事ですからね。

それとは反対にやはり、会場に対して参加者が多いので当日の混み具合はすごいものでした。ひどいところだと満員電車並みの混雑具合で冗談ではなく進めないレベルでした。ここに関しては上記の電子決済などで少しでも緩和されるといいなと思います。

全体的にはとてもいいイベントだと思うので、今後も開催に期待ですね。ありがとうございました。

Macでの開発環境

普段自分がMacで開発している時に利用している開発用のソフトやツールを紹介していきたいと思います。

設定系

Clipy

クリップボード監視ツールです。過去にコピーしたものを再度貼り付けとかできます。

clipy-app.com

Alfred

全然使いこなせてないですが、ランチャーアプリです。主に使うのはアプリの起動と、「sleep」「restart」「shut down」とかくらいです。 便利な使い方あれば教えてください。

www.alfredapp.com

karabiner

キーバインドを変更できるアプリです。私は右Command(Winの右Alt)を日本語変換に普段割り当てて使ってるのでその設定をするために使用しています。

Karabiner - Software for macOS

ブラウザ

Chrome

ブラウザは仕事でもプライベートでもchromeを使ってますね。やっぱり開発者コンソールが使いやすいのと、chrome拡張、ブックマークレット、アカウント連携がいいです。

エディタ

Intelij IDEA

もともとPhpStormを課金して使っていたのですが、最近php以外も書くことが増えたので思い切ってアップグレードしました。全体的にそんなに使用感は変わらないのですが、複数言語をストレスなく開発できるので最高ですね。

www.jetbrains.com

SSHクライアント

Iterm2

Macなので標準でついてるターミナルでも十分開発できると思うのですが、色々カスタマイズしたり画面分割したりするのでIterm2を愛用しています。一番好きな機能で言うとテキストを選択しただけでコピーしてくれる機能ですね。わざわざCommand+C押すの面倒だし。

www.iterm2.com

SQLクライアント

CLI

MacSQL使うときは以外とCLIから使うことが多いような気がしています。特に理由はないですが、パッと使えるからですかね。

MySQL Workbench

Mac入れた当初から使っているGUIツールです。データ量が多いものを見たかったり、ER図を生成、確認したいときには使ってます。ただやや起動が重いのが難点です。

MySQL :: MySQL Workbench

DBeaver

最近少し使い始めたSQLクライアント。まだあまり使いこなせてない。ER図も生成できるっぽい。

DBeaver Community | Free Universal Database Tool

Docker

Docker for Mac

最近は開発環境はすべてDockerなので本当に必需品ですね。私はEdge版を入れてます。こっちだとkubernatesも使えるので。勉強しながらやってます。 kitematicは使ってません。

ツイッタークライアント

夜フクロウ

そんなに思い入れがある訳でもないのですが、ウィンドウがコンパクトで複数アカウント対応していて、パッと呟けて、と言う条件で絞るとこれくらいしか見つかりませんでした。

CLIツール

screen

ターミナルマルチプレクサという種類のツールですね。同じ種類のツールではtmuxと言うのがいたりします。 仮想的にsshセッションを複数貼って作業ができるので複数のサーバに入って作業する人などにはおすすめです。

ghq

リポジトリ管理ツールです。gitリポジトリをこれでクローンしたり、リスト化したり、そのリポジトリに移動したりと言うことができます。 いろんなリポジトリで作業する人にはおすすめです。

github.com

peco

CLI上でインタラクティブに操作できるフィルターツールです。これとパイプ | をうまく使うと複数の選択肢から選択して次のコマンドに選択したものを渡して実行みたいなことが手軽にできちゃいます。

github.com

jq

jsonデータに対してクエリを使えるツールですかね。個人的にはjsonの整形だけでもすごい助かってますが、特定のフィールドのデータだけを取ってきたり、集計したりもできるようです。

jq

ブログに書きたいものリスト(随時更新予定)

そのうち書きたいやつ

思いついたら書き足す。

ISUCON8 予選参加してきました。(2018/09/18追記)

久しぶりの更新になってしまい、申し訳ありません。

今回は、 9/15 (土) に ISUCON8 予選に参加してきたので、そのことについてまとめようと思います。 あ、サムネイルは終了1時間前に偶然TOP5に乗ったもので通過はできませんでした。😭

そもそも ISUCON とは

ISUCONをご存知ない方向けに軽く説明すると、

isucon.net

お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCONです。

公式の引用ですが、Webサービスをとにかく早くしてサービスの高速化をチームごとに競う大会です。

去年に引き続き、僕も Oysters という若手エンジニアの集まりで仲間を募集して参加しました。

当日までにやったこと

メンバーの募集、確定

ISUCONはチーム戦なので、一緒に戦ってくれる信頼できる仲間を募集することが大事です。 僕は、普段一緒にプライベートで開発をしたりしている若手エンジニアの集まり Oysters があったのでそこでメンバーを募集しました。 過去にも何回か ISUCON 練習に参加してくれたメンバーが集まり心強かったです。

使用言語の選定

メンバーが集まったので、どの言語を使って問題をといて行くかを決めました。これについては、去年の参加時に次回はGo言語を使ってみたいということで話がでていたので、特に議論はなくGo言語に決まりました。使用に至った理由としては、言語としての使用が単純なためコードリーディングが用意であること、PHPなどと比較して軽量であること、コンパイル型のためエラーがコンパイル時におおよそわかり時間短縮できるだろうということ、例年の通過者比率がGo言語が多いこと、などがありました。

開発環境構築

前々回くらいから、コンパネからの操作などができなくなり、イメージからの復旧などもできなくなりました。なので、実際の作業は配布されたサーバ上で直接やるのではなく、本番環境のコードからバックアップを取りそれをローカルで動くようにし、ローカルで作業したものを本番にあげるという手順を取りました。

そのために開発環境をあらかじめ Docker + Docker-Compose + Nginx + Golang + MySQL を使って構築しておきました。 参加メンバー全員が開発環境で動かせるように事前に集まって動作確認することも忘れませんでした。 こういう場面でサクッと開発環境の構築ができるのは Docker + Docker-Compose 本当にいいですよね。

プロファイリング方法の調査

次に本番時にどこにWebサービスボトルネックがあるのかを調査するための方法であるプロファイリングについてです。 ISUCONではサービスのチューニングをやって行くわけですが、制限時間が8時間と短くそれなりの規模のWebサービスが渡されるので、チューニングのインパクトがあるものからやっていかないと時間が全然足りません。 そこで使用するのがプロファイリングです。僕は役割分担上プロファイリングをそこまで見ていなかったので詳しいことはかけないですが、自チームでは下記のプロファイリング結果を参考にしてました。

  • kataribe
  • Go の pprof
  • MySQL の slow query log
  • dstat

基本的に kataribe を見てエンドポイントごとのアクセス数とレスポンス時間を見ながら対応優先順位を決めて行く感じです。またpprofを使うとコードのうちのどこに特に時間がかかっているのか分析できるのでおすすめです。slow query log はチューニングする部分がSQLの部分の時に確認しながらインデックスを貼っていったりする用途で使ってました。

当日スケジュール決め

当日の時間は 10時 〜 18時 と短く余裕もそんなにないので事前に役割決めとタイムスケジュールを細かく決めておくと当日スムーズに行動できておすすめです。 僕のチームでは下記の写真のように時間配分と役割を決めました。

タイムスケジュール f:id:zoe302:20180916163255j:plain 役割分担 f:id:zoe302:20180916163407j:plain

スラックチャンネル

当日みんなで集まって作業はするものの、全部口頭でのやり取りは辛いので、スラックチャンネルを用意しました。 これは以前から用意していたので改めて新しく用意したわけではないですが、あったほうが便利なのであえて書いてます。 使用用途としてはわかったことをつらつらと書いていったり、不明なことを書いていったり、ボトルネックになりそうなものを書いたり、などにつかってました。

作業用レポジトリ作成

当日の本番コードのバックアップ兼作業ようのレポジトリです。ここにあらかじめ構築しておいた開発環境のコードと後述するプロビジョニングようスクリプトを入れておくことで当日スムーズにコードのバックアップ→開発、プロビジョニング→開発したものを反映する、という流れができました。

プロビジョニングスクリプト作成

プロビジョニングといってもたいしたことはないのですが、用意しておくに越したことはありません。とりあえず今回僕が用意したものとしては、メンバーがスムーズに作業できるようにメンバーの公開鍵を登録しsshできるようユーザの作成とsudoできるようにするスクリプトを用意しておきました。また必要な時に使えるよう、MySQLのアップグレードの手順とkataribe導入のスクリプトを用意しておきました。他にも事前に用意しておくといいものは用意しておきましょう。

当日やったこと

ここからは当日の流れをslackとgithubのコミットログから遡って思い出しながら書いていきます。 f:id:zoe302:20180916183648j:plain

~ 10:00

事前にメンバーと合流して朝ごはんを食べる約束をしていたので集まって朝ごはんを食べながら戦略の再確認と当日レギュレーションの再確認をしていました。

10:00 ~ 11:30

開始して最初の1時間ですね。正直ここが結構大変でした。 まず最初にとりあえずベンチマークを実行しました。が、この時点でなぜかfailしてました。これについてはすぐ運営からアナウンスがあり、MySQL のコネクションタイムアウトが原因だったようで、MySQLを再起動したら動きました。

karupanerura@運営昨日 午前10時15分 (多くのチームで)ベンチマークがfailしてしまっていますが、MySQLへのコネクションがタイムアウトしているケースが多いので、初期実装のsystemd serviceの再起動をお試しください。

この後は事前に分担していた役割に沿って作業していきます。僕の役割は初動を早くする人でしたが、最初本番のコードのバックアップを念のため開発環境構築の人とペアオペで作業しました。 また平行して、参考実装をGoに切り替えて最初にベンチマークを回しました。この時点でのスコアが1000点前後でした。 この点数をベースにこれから改善して上げていきます。 また、この作業と平行してプロファイリングする人は、最初にアプリケーションの動作をブラウザを通して確認していました。

その後僕は、複数台構成のサーバだったのでそれぞれのサーバの状況を確認していきました。便宜上プライベートIPが若い方からAサーバ、Bサーバ、Cサーバと呼びます。(当日もこの名前で識別してました。) 最初、もらった状態は全てAサーバで返している状態で、WebサーバとしてH2Oが起動しており、SQLサーバとしてMariaDBが起動していました。WebサービスはSPAで映画館の予約システムのようなものでした。 そろそろMySQL以外のSQLがきそうだなとは思っていたので、そんなに驚きはしなかったですが、用意していたMySQLの環境が使えなくなったので少し残念でした。 Bサーバ、Cサーバについては上記ミドルウェアは入っているもののstopしている状態でした。 この時点でDBサーバの処理が詰まってそうだったので早速複数台サーバを活用できるようWebサーバの分離を試みました。 構成としては、AサーバをDB用とし、BサーバをWebサーバ(H2O)として使えるように書き換えました。

11:30 ~ 12:30

Cサーバの方をkataribeを使えるようにH2Oからnginxへ載せ替えをしていました。(H2Oでの使用方法がパッとわからなかったため。) その後少し早いお昼を取りました。モスバーガー美味しかったです。

12:30 ~ 13:30

CサーバのアクセスをnginxがロードバランシングしてBサーバとCサーバのアプリケーションで返せるようにしていましたが、 静的ファイルをうまく返せないようになってしまいハマってしまっていました。 静的ファイルが返せていない原因がテンプレートのほうでoriginをセットしてhttp://{ipアドレス}/ のようにしてアクセスしているのだとわかり、正しくproxy_set_headerをつけることで解決しました。

13:30 ~ 14:30

その後、僕は少し手が空いたので複数台サーバにデプロイするためのスクリプトを用意した。ローカルからリモートのサーバにsshしてソースをpull、goのビルドを実行し、各ミドルウェアをリスタートするというものだ。

#!/bin/bash -eu

# A
ssh 172.16.67.1 <<EOC
sudo systemctl stop mariadb
sudo rm -f /var/log/mariadb/mariadb.log
sudo rm -f /var/lib/mysql/slow.log
sudo systemctl start mariadb
EOC

# B
ssh 172.16.67.2 <<EOC
sudo su - isucon;
cd /home/isucon/oysters-isucon8; git pull;
rm -f /home/isucon/torb/webapp/go/torb;
make -C torb/webapp/go;
sudo systemctl restart torb.go.service;
EOC

# C
ssh 172.16.67.3 <<EOC
sudo su - isucon;
cd /home/isucon/oysters-isucon8; git pull;
rm -f /home/isucon/torb/webapp/go/torb;
make -C torb/webapp/go;
sudo systemctl restart torb.go.service;
sudo rm -f /var/log/nginx/access.log
sudo rm -f /var/log/nginx/error.log
sudo systemctl restart nginx
EOC

その間、ほかの仲間がアプリケーション側のgetEventsのN+1を解消してくれたり、slow query logを見てSQLのチューニングをしてくれたりした。

14:30 ~ 16:00

自分もサーバ側の設定で特にすることが一旦なくなったのでアプリケーションのソースを見て改善できそうなところを直していた。 この間にアプリケーションのgetEventsの改善が終わり少しスコアが上がったがこの時点ではまだ大きくスコアに変動はなく2600点くらいだった。 その後アプリケーション担当の人はgetEventの方でのN+1問題に着手した。

f:id:zoe302:20180916185228p:plain

16:00 ~ 18:00

nginxの静的ファイルのキャッシュに着手、いつもの設定ファイルを持ってきて終わり、今回のアプリでは特に静的ファイルの容量、枚数もなかったのであまりスコアは伸びなかった。 この間にアプリケーションのgetEventのN+1が解消し、ベンチを回した結果、20000点を超え一瞬TOP5に乗りメンバーで歓声を上げましたw

f:id:zoe302:20180916183054p:plain

他のチームも最高スコアは上のチームもいたので、全然TOP5にはなれなさそうでしたが、ちょっと嬉しかったです。 その後も細かいアプリケーションのチューニングをしたのですが、どうやら予約の排他制御がうまくできていなかったようで、たまにfailしてしまう現象に遭遇してしまいました。 そこからは排他制御の再現と解消を考えている間に時間が近づいてきたので、再起動のテストを残り1時間くらいで確認して、終了となりました。

f:id:zoe302:20180916184728p:plain 最終ベンチマーク

施策まとめ

  • サーバ構成を1台構成から3台構成に A: H2O + Go + MariaDB, B: null, C: null -> A: MariaDB, B: Go, C: Nginx(兼Load Balancer) + Go
  • getEvents, getEventのN+1問題の解消
  • 予約テーブルにインデックスはる
  • MariaDBのチューニング

当日振り返り

よかったことも悪かったことも感想も雑多に

  • 勝ちにいくなら役割を固定して練習しておいたほうがよかった。
    • 役割分担はしたものの割と直前で決めたので、あまりなれていない部分もあったため、初動が遅れた。
  • プロファイリング結果を見ながら根拠を持って改修に入れた。
    • これは前回までは割りと行き当たりばったりで改修していたものを今回からちゃんと解析ツールを入れたことで得点に繋がりそうなところを優先的に手を入れられた。
  • あらかじめサーバ構成やミドルウェアの選定をある程度考えていたので、想定外のミドルウェアがきても対して揺さぶられなかった。
  • それぞれが役割を全うして戦えた。変に周りの作業に手を出したり、作業内容がバッティングしないように動けた。
  • 1時間に一回くらいの進捗報告でお互いの作業内容を把握しながら動けた。
  • (個人的に)インフラ周りのボトルネックがあまりなかったので、アプリ側をもう少し見てもよかったかも、、でも全体的にバランスはよかったと思う。
  • 複数サーバ構成を見越してデプロイスクリプトをあらかじめ用意しておけばよかった。当日30分くらいで用意できたからそんなに反省ではないが、、
  • (予想)予約のエンドポイントのレスポンスが202 Accepted になっていることを深夜に気づいてよくよく考えたらその時点で予約完了していなくてもよかったのでは、と思った。
    • 予約受付のレスポンスだけ返して、整合性を確認しながら遅延処理で予約していけばスループット向上できたのでは?
  • ポータルが使いやすい、見やすい。途中詰まって少し重かったけど許容範囲内
  • 最近静的コンテンツが多めの問題設定から少し外れて、またロジックよりだったのが意外
  • 土曜なのに開始時間遅延してなくてすごい。運営のみなさま準備お疲れ様でした
  • ちょうど最近「Web API: The Good Parts」を読んで結構役立った気がする

次回までにやること

  • 役割を決めての練習をする
  • Nginxの設定項目を復習する
  • 自分でもプロファイル結果もっと見れるようにしておく
  • デプロイスクリプト用意しておく
  • 参加に興味持った人が増えてきそうなので余裕あったら2チームで練習、参加したい

最後に

ISUCON運営メンバー様、サーバ用意してくださったGMO様、本当に毎年ありがとうございます。 これからも宜しくお願いします!!

追記1

30位 32,957 50位 24,658 100位 15,354 150位 8,938 200位 3,739 250位 1,391

最終スコアから見るに50位のラインは超えてそう。続報に期待

追記2

予選の結果が出ました。

isucon.net

確認したところうちのチームは追試の結果、failで失格でした。。

fail 23,034 Oysters1 (3)

原因はやはり排他制御周りのようです。

また続報で、1週間、バックアップ目的での予選のインスタンスへのアクセスと、復習のためにベンチマークの実行ができるらしいです。

昨年もバックアップ期間はありましたが、ベンチマーカーまで動かしてくれるのはとてもありがたいですね!

本当にありがとうございます。

追記3

予選問題の公開がきました!これでいつでも復習できますね。僕も復習して来年こそは本戦出場目指します!!

github.com

docker-composeを使って複数サービスをSSLで手軽にホスティングする

やりたいこと

仮に example.com というドメインを取得していたとする。そのドメインに対してサブドメインを発行して、 hoge.example.com というドメインhoge サービスを配信。また fuga.example.com というサブドメインを発行し、fuga サービスを配信。

このご時世なので、各サブドメインに対してSSL証明書も発行して、SSLで配信できるようにしたい、Let's Encrypt で発行するにしても、面倒なので自動で発行でできるようにしたい。

使うもの

上記の要件を満たせるものが、下記だ。

jwilder/nginx-proxy

github.com

これはコンテナと同じネットワークに存在するコンテナを監視し環境変数からドメインを取得しそのドメインにプロキシしてくれるものだ。 ベース技術としてdocker-genという設定ファイルをテンプレートから生成してくれるものを使っている。

※上記イメージはホストの/var/run/docker.sock をマウントして使用するため、不正利用の危険がありえます。使用する際には自己判断のもと自己責任でお願いします。

jrcs/letsencrypt-nginx-proxy-companion

github.com

こちらは、上記の nginx-proxy と連携して Let's Encrypt からSSL証明書を発行してくれるものだ。

また、環境は以下の通りだ。

$ docker version
Client:
 Version:      18.05.0-ce
 API version:  1.37
 Go version:   go1.9.5
 Git commit:   f150324
 Built:        Wed May  9 22:14:54 2018
 OS/Arch:      linux/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.05.0-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.5
  Git commit:   f150324
  Built:        Wed May  9 22:18:36 2018
  OS/Arch:      linux/amd64
  Experimental: false

$ docker-compose version
docker-compose version 1.21.2, build a133471
docker-py version: 3.3.0
CPython version: 3.6.5
OpenSSL version: OpenSSL 1.0.1t  3 May 2016

構成

まずホストの 80, 443 で LISTENするサービスとして docker-proxy (上記の nginx-proxy, letsencrypt-nginx-proxy-companion を立ち上げてくれる) docker-compose を作成。

github.com

この docker-compose でプロキシ用のコンテナと、SSL証明書の自動発行コンテナが作成される。また、プロキシしたいサービスはこのネットワークに属している必要があるので、名前をつけて指定しやすいようにしている。(ただしこれはcompose バージョン 3.5から使用可能なので注意)

ここまでで準備はおおよそ完了で、今度はプロキシして配信したいサービスの方の設定だ。

こちらはそんなに難しいことはない。 docker-compose.yml にnetworks にexternal に先ほどの docker-proxy で作成したネットワークの名前を指定する。 次にコンテナに環境変数を設定する。

  • VIRTUAL_HOST
    • プロキシする際の vhost に当たるもの、この名前でプロキシされるので任意のサブドメインを割り当てる。
  • VIRTUAL_PORT
    • プロキシする際のコンテナ側のポート。
  • LETSENCRYPT_HOST
    • Let's Encrypt で自動更新するホスト名。基本的には VIRTUAL_HOST と同じになるはずだ。
  • LETSENCRYPT_EMAIL
    • Let's Encrypt の更新時に使用するメールアドレスだ。

github.com

まとめ

これで、無事サービスが複数に増えても簡単にSSLホスティングすることができそうです。 途中で、docker-composeのファイルバージョンの違いによる書き方の違いや、非推奨になっていたりとで少し手間取りました。

参考

qiita.com

a-lab.biz

peeeeron.hatenablog.com

poyo.hatenablog.jp