くりにっき

フルスタックキュアエンジニアです

gitのログをとにかく全部出したい

git log ではなくgitの内部のログの話です。

最近gitコマンドの通信周りを追っていてログをとにかく全部出して挙動確認してるのでメモ。

  • --verbose :git以外のコマンドにもよくあるオプションなので定番
  • GIT_TRACE, GIT_TRACE_SETUP:git内部のデバッグログ
  • GIT_CURL_VERBOSE:gitがリモートリポジトリに通信する時の詳細なログを表示

ということで、gitのログをとにかく全部出したい時は

GIT_TRACE=true GIT_TRACE_SETUP=true GIT_CURL_VERBOSE=true git fetch --verbose

export GIT_TRACE=true
export GIT_TRACE_SETUP=true
export GIT_CURL_VERBOSE=true
git fetch --verbose

のようにする。

参考URL

git-scm.com

faraday v2対応を行った&faraday-mashifyをリリースした

公式ドキュメント

https://github.com/lostisland/faraday/blob/main/UPGRADING.md

具体的な対応内容

faraday v0系とv1系は共存できたんですが、v2系はプラグイン周りに大きな変更があった関係でそれまでのバージョンと共存できなかったので拙作のAPIクライアントgemに関してもfaraday v2系未満のサポートを切ってメジャーバージョンアップを行いました。

faraday v2系でRuby 2.6系未満のサポートが切られてるので必然的に自分のgemでもRuby 2.6未満のサポートを切っています。

faraday_boolean v1.0.0

pixela v3.0.0, prismdb-ruby v1.0.0, chatwork-ruby v1.0.0

いずれのgemもmashify(普通のHashを ActiveSupport::HashWithIndifferentAccess のようなメソッドアクセスできるいい感じのHashにするやつ)を使ってたのですが、mashifyが組み込まれてる faraday_middleware がfaraday v2系だと非推奨になって対応をどうするちょっと困りました。(gemspecに ~> 1.0 が書かれてるのでv2系と共存もできない *1

v2系だとfaraday_middlewareに組み込まれてたプラグインは各gemに分割される方針になってるようなのでfaraday-mashifyというgemがあるかと思いきやなかったのでIssueで聞いてみました。

github.com

そうしたらfaradayのメンテナから

We welcome people who would like to create and maintain them though, it doesn't have to be us!

という返事をもらったので自分でfaraday-mashifyを作り、自分のgemに組み込んで今回の対応を行いました。

github.com

その他地味にハマったところ

faraday v2対応時にテストコードが軒並みコケて調べたところ、レスポンスヘッダのContent-Typeが正しく設定されていないと response :json が動かなくて *2 地味にハマりました...

普通にWeb APIを叩いたらContent-Typeにjsonが入らないというのはまず無いはずなんですが、自分のテストコードだと webmock で外部通信部分をスタブにしていてスタブ側でContent-Typeを設定してなくてテストコードが軒並みコケていたというオチでした、、、

実際の対応内容 https://github.com/sue445/pixela/pull/91/commits/2adf922689b023fd458f6293f04c7e4b5b5b01fa

個人gemを軒並みRuby 3.1対応した

毎年やる作業ということもあり、個人gem全部のGitHub Actionsの設定を修正してPRを投げるツール自体は作ってるのでそんなに大変ではなかったです。

ツールについては詳しくは https://speakerdeck.com/sue445/ruby-on-ci-number-ginzarails?slide=79 を読んでください。

例年だとPRを一斉に作ってビルドが通ったらヨシッ!って感じでマージするだけの定形作業なんですが、今年はいくつかハマったのでピックアップ

Psych 4.0の非互換で動かなくなった

詳細は下記を参照

techlife.cookpad.com

Psych.load のデフォルトが safe_load になった関係でYAMLファイルを読み込んでるところで

Psych::DisallowedClass:
  Tried to load unspecified class: Date

のようなエラーが出るようになったので対応しました。

https://github.com/sue445/rubicure/runs/4672373245?check_suite_focus=true

で、permitted_classes を使おうとしたところ permitted_classes はPsych 3.1.0以降にしか存在せず、かつPsych 3.1.0以降を含んでるのがRuby 2.6.0以降だったので*1、いくつかのgemでRuby 2.6.0未満もサポートをきってメジャーバージョンアップしました。

リリースして気づいたけどgemspec側でPsych 3.1.0以降を指定してたら古いRubyのバージョンのサポートを切らなくてよかったかもしれない。(Ruby 2.5は既にEOLなのでサポートきっても支障はないんだけど)

Ruby 3.1.0 + Rails(activerecord) 7.0が現時点では動かない

詳しくは https://gist.github.com/yahonda/2776d8d7b6ea7045359f38c10449937b を参照。

そのうち7.0.1が出るだろうと思ってactiverecordを使ってるgemは gem "activerecord", "~> 7.0.1" をつけてDraft PRで寝かせておくことにしました

github.com

github.com

github.com

PRを作るとCircleCIからBANされた

gemが軒並みRuby 3.1対応したのでアプリもやるかーと思ってPRを作った瞬間CircleCIからBANされて厳しい顔になってます。

このPRの後にもいくつかPRを作ったけど同様にBANされたので諦めてサポートの対応待ち。

2022/1/1 16:00追記

CircleCIのサポートチケット作った数時間後に対応されてた。(自動検知システムでブロックされてたらしい)

1プロジェクトだけどうしてもビルドできなかったのでまだ完全解決ではないんだけど正月に対応してもらえるとは思わなかったので神対応すぎる。

2021/1/6 18:00追記

書くの忘れてたけど残り1つも1/3にBAN解除されていました。

Keyless Terraformに特化したTerraformテンプレートリポジトリを作った(AWS, GCP対応)

tl;dr;

github.com

github.com

前置き

9月くらいにGitHub ActionsでOpenID Connector(以下OIDC)を用いた認証を利用することができるようになりました。

dev.classmethod.jp

cloud.google.com

CI上でAWSGCPAPIを利用する場合は通常IAM UserのAWS_ACCESS_KEY_IDやAWS_SECRET_ACCESS_KEY(AWSの場合)やサービスアカウントのキーファイル(GCPの場合)をリポジトリのSecretsに設定することになりますが、OIDCによりこれらの機微情報の生成自体が不要になりました。(keyless)

モチベーション

OIDCは便利だしググればいくらでも情報は出てくるのですが、Terraformのリポジトリを作る度に調べたり諸々設定するのが大変なので楽をするためにテンプレートリポジトリを作りました。

テンプレートリポジトリについて

github.com

github.com

Terraformリポジトリの作り方やCIのワークフローは様々な流派がありますが、自分がよくやる

  • PullRequestで terraform plan, terraform fmt, tflint を実行しつつ、plan結果をPullRequestにコメントする
  • mainブランチでは terraform apply を実行
  • Slack通知

のような一番シンプルなパターンをテンプレートリポジトリにしています。

頑張った点:Terraformを実行するための初期設定をCloud FormationやDeployment Managerで行うようにした

Terraformを実行するためには terraform.tfstate を置くためのバケットを作成したりAWSの場合は排他ロックのためのDynamoDBのテーブルが必要で、GitHubのOIDCのためにもいくつか設定が必要です。

このような初期設定を(ほぼ)一発で終わらせるためにCloud FormationやDeployment Managerの設定ファイルを作成しました。

リポジトリのREADMEにも書いてますがテンプレートから新規リポジトリを作った後にいくつかの手順を踏むだけでGitHub ActionsでTerraformが実行できるようになります。

ただしGCPのDeployment Managerだと現時点でWorkload Identity Poolを作成できないため、Deployment ManagerでTerraform用のGCSバケットやサービスアカウントを作った後でローカルからの terraform apply でWorkload Identity Poolを作るようにしています。

Terraformの実行に必要なリソースをTerraformで作るのは個人的には気持ち悪さがあるのですが、gcloudコマンドを2~3回叩かせるのもセットアップの手間が増えて嫌なのでTerraformで作ってます。(Deployment ManagerがWorkload Identity Poolに対応したらやめたい...)

AWSに関してはCloud Formationのコンソールから設定ファイルをアップロードするだけでTerraformの実行に必要なリソースを全て作れます。

個人gemにrubygems_mfa_requiredをつけた

rubocop 1.23.0で Gemspec/RequireMFA が増えていたので rubygems_mfa_required の存在に偶然気づきました。

guides.rubygems.org

gemリリース時のMFA *1 は元から設定していたんですが、gemspecに

spec.metadata = { "rubygems_mfa_required" => "true" }

# or

spec.metadata["rubygems_mfa_required"] = "true"

みたいのを書いておくことで *2 gemのリリースや削除でMFAが必須になってさらにセキュアになるので、この機会に手持ちのgemに軒並み rubygems_mfa_required をつけました。

github.com

多分3日がかりで40〜50個のgemに適用してリリースしたと思います

*1: https://rubygems.org/settings/edit で設定できるアカウントに対するMFA

*2:最近のbundlerだとbundle gemした時に最初からspec.metadataがついてるので既存設定を上書きしない後者がいいと思います

AWS 認定ソリューションアーキテクト – アソシエイト(AWS SAA-C02)に合格した

モチベーション

業務だとAWSGCPを半々くらい触っているんですが、GCPの認定資格である Professional Cloud Architect(通称PCA) は持っているのにAWSの認定資格を持っていないのはバランスが悪いのでPCAのAWS版ということでAWS 認定ソリューションアーキテクト – アソシエイト(AWS SAA-C02) をとってみることにしました *1

aws.amazon.com

1回目の試験は落ちて2回目で合格しました

俺氏スペック

  • 実務だとAWSは6~7年くらい、GCPは2~3年くらい *2

やったこと

1回目の試験

1回目は実力試しのつもりで参考書を軽く読んでチャレンジ。準備期間は1~2週間くらい。

1回目の時はあと2~3問正解してたくらいのスコアでした。

2回目の試験

1回目の試験を受ける前後で下記のエントリが出てたので参考にしました。

kiryuanzu.hatenablog.com

developers.prtimes.jp

上記エントリを参考にし、1回落ちたので本気を出すためにまずはUdemyの動画講座を受講しました。

www.udemy.com

平日は業務前後の時間を使って1日2~3時間ずつくらい受講。業務後は疲れて寝落ちする確率が高かったので朝受講することの方が多かったです。

32時間と長丁場だったので倍速も試したんですがあまり速いと頭に入ってこないので1.25倍速で聞いてました。(1.5倍速だと厳しかった)

全部の動画を見終わるのに1ヶ月くらいかかったと思います。講座の中にはハンズオンもあったのですが講座内のハンズオンはだいたい実務でやってたので特に手は動かしていないです。

動画を見終わった後に参考書をもう1冊買って勉強しました

参考書を読み終わって試験の直前にはUdemyの講座の最後の対策問題だけをもう1周やりました。

感想

他者に対して自分のスキルを客観的に伝える術が増えてよかったです。

www.credly.com

*1:資格とってから気づいたんだけどPCAのAWS版はSAP-C01の方だったかもしれない...

*2:Google App Engineは2009~2010年くらいから使ってたんだけど業務ではないのでノーカン

ISUCON11に1人チームで参加するためにやったこと #isucon

最初に

予選落ちなので勝者エントリを読みたい人はここで回れ右を推奨。

  • 使用言語:Ruby
  • 最終スコア:0
  • 最高スコア:5822

モチベーション

ISUCONの出題範囲であれば(多少濃淡あるけど)一応1人で全部できると思ってたので腕試しのために1人チームで参加しました。

ISUCON歴

ISUCON6(2016年)とISUCON9(2019年)にそれぞれ当時の同僚と参加したので今回が3回目。(どっちも予選落ち)

事前準備

準備期間

6月中旬くらいから本番を想定した素振り(1セット8時間)をやってたのでだいたい2ヶ月くらいです。

7月は毎週末8時間素振りをやって、平日の夜に感想戦をやってました。

そしてISUCON開催週は有給で全部休んでひたすら毎日10〜18時で素振りをしてました。

やったこと

ひたすら過去問をやっていました。

古すぎると今と傾向が違うので比較的新し目のをときつつ、予選と本戦では問題の傾向が全然違う(予選は割とオーソドックスだけど本戦はトリッキーな問題が多い)ので予選を重点的にやりました。

解いた問題数だけでいうと下記。(計13セット)

  • 6予選 x 1
  • 7予選 x 2
  • 8予選 x 2
  • 9予選 x 2
  • 9本戦 x 1
  • 10予選 x 3
  • 10本戦 x 1
  • 11事前講習 x 1

7月の週末と8月第3週目に草が濃いのはISUCON素振りのためです。 *1

f:id:sue445:20210822092120p:plain

いやまぁ、2ヶ月間で100時間以上使っても予選突破できなかったのでもう少し素振りの質を上げるべきだったかなと後悔。

事前に用意したもの&やったこと

Sentryのbillingを有効化した

普段 https://sentry.io/ は無料枠で利用してるんですが、ISUCONの素振りをやったら一瞬で無料枠を使い切ったのでTeamプラン(月26ドル)にしました

SentryのSpike Protectionを無効化した

Sentryには突発的にエラーが送られてきた時にそれをブロックして課金を抑えるSpike Protectionという機能がデフォルトで有効になってます。

blog.sentry.io

通常はこれは有効でいいんですがISUCONの場合この機能を有効にしてるとベンチマーク実行時のエラーがSentryに飛んでこないということがあったので無効にしました。

ちなみにSpike Protectionはプロジェクト単位ではなくorganization全体の設定なのでISUCONのチーム専用のorganizationを作ってそこで無効化するのがいいと思います。

f:id:sue445:20210822210805p:plain

デプロイスクリプト

素振りの度にリファクタリングしつつ、実際にISUCON11の予選で使った最終版は https://github.com/sue445/isucon11-qualify/blob/main/Rakefile になります

デプロイスクリプト解説

自分がRubyで参戦するし書き慣れてるのでRakefileを使いました。Makefileと違って動的にタスクを書きやすいのが特徴。

ここまで重厚になるとさすがにcapistranoでええんちゃう?って気持ちになるんですが、スニペットリポジトリからコピペするだけで使えるの便利なんすよね、、、

ローカルで rake を叩くだけで git push -> 本番全台で git pull & 必要なデプロイ処理を全部実施しつつ、スコア記録用のissueに下記のようなコメントをつけるところまでやってます。(ベンチマーカーで出たスコアは手でissueに貼り付けてる)

f:id:sue445:20210821213901p:plain

実際にスコア記録用に使ったissueは https://github.com/sue445/isucon11-qualify/issues/1 なのでこれ見てもらうと当日の雰囲気は伝わると思います。

こだわりポイント

  • 複数台に直列でデプロイすると遅いので multitask で並列デプロイできるようにしてる
  • サーバ全台にデプロイ後に /initialize を叩いて簡単な動作確認までやってる
    • ただ、今回のISUCONだとパラメータ無しで /initialize を叩けない仕様だったので /initialize_from_local というエンドポイントをはやしてそいつをRakefileから叩いています
  • /initialize まで終わったら 20210821-183139-sue445 のような日付ベースのtagを作ってpush
    • 最初はデプロイしたリビジョンだけissueにコメントするようにしてたんですが、それだと直前のデプロイからのcompareが取りづらいのでタイムスタンプベースのtagにしています
    • 実際に直前のデプロイからのcompareリンクを作ってるのは https://github.com/sue445/isucon11-qualify/blob/main/Rakefile#L166-L171
    • issueの最新のコメントに書かれてるリビジョンをparseしてもよかったんだけどこっちの方が手軽だったので採用
    • 昔の癖でtagにデプロイした人の名前を入れたけど1人チームだったので全然ありがたみはなかったw

スニペット

https://github.com/sue445/isucon11-qualify/tree/main/ruby/confighttps://github.com/sue445/isucon11-qualify/tree/main/infra開始直後にスニペットリポジトリからコピペした もので、configは必要に応じてrequireして使ってました。

過去問を解いてる過程であると便利だったユーティリティが https://github.com/sue445/isucon11-qualify/tree/main/ruby/config に集約されています。

スニペット系は元々個人esaに置いてたんですが、数が多くなってきたしテストコード書いてCIも回したくなってきたので専用のリポジトリを作りました。

その中からいくつかピックアップ

enable_monitoring.rb

アプリケーションコードを一切修正しなくてもファイル先頭でこれをrequireするだけでNewRelic, Sentry, Stackprofを有効化できる便利モンキーパッチ

NewRelicは便利なんだけどそれなりに重いのでどうしてもスコアが下がってしまいます。(過去問解いた感じだとNewRelicほどじゃないけどSentryもちょっと重かった)

設定が複数箇所にあると無効にするのが大変なので、ISUCON終了直前に一瞬で全部オフれるようにこのような構成になってます。

class Sinatra::Baseclass Mysql2::Clientオープンクラスしてモンキーパッチ仕込んでるのが個人的なおすすめポイントです。

実際の終了直前対応は https://github.com/sue445/isucon11-qualify/commit/fd049a5834a03df9705c83c05ff2b3f86042e6ae になんですが、アプリだけでいうと enable_monitoring のrequireをやめるだけですんでいます。

nr_mysql2_client.rb

https://github.com/newrelic/newrelic-ruby-agent は標準でmysql2をサポートしていません。

そこで下記エントリを参考に色々魔改造したのがnr_mysql2_clientです

ohbarye.hatenablog.jp

元のソースからの差分は下記

  • SQLのクエリに含まれてるテーブル名を正規表現で全部parseしつつ、joinやサブクエリなどで1つのSQL文で複数のテーブルが登場する場合には reservations,users のようにカンマ区切りで連結した文字列を返却
  • 実際に実行されたSQL/tmp/sql.log に出力
    • スロークエリログとるまでもないけど雑に見たいクエリ見たい時に便利
  • mysql2経由ではなく system "mysql ~" のようにRubyのアプリから直接システムのmysqlコマンドを叩いた時にもNewRelicにメトリクスが送ることができるようにした
    • 具体的には with_newrelicのブロック の中で system "mysql ~" を実行
    • 普通に考えたらアプリから system "mysql ~" を叩くなんてまずないんですがISUCON8の予選の過去問解いてた時に取得したレコード数が多すぎ *2 てpumaがメモリを食いすぎてサーバがOOMで死ぬということがあったのでそういうことも一応想定してました *3

最初の頃は

# Mysql2Client = Mysql2::Client
Mysql2Client = NRMysql2Client

def db
  Mysql2Client.new(
    host: @host,
    port: @port,
    username: @user,
    database: @db_name,
    password: @password,
    charset: 'utf8mb4',
    database_timezone: :local,
    cast_booleans: true,
    symbolize_keys: true,
    reconnect: true,
  )
end

のようにしてコメントで NRMysql2Client を使うかどうか切り替えてたんですが、面倒くさくなってきたので最終的に前述の enable_monitoring.rbclass Mysql2::Client に直接モンキーパッチ仕込むようにしました

redis_methods.rb , memcached_methods.rb

Rails.cache.fetch が好きすぎて with_rediswith_memcached のようなメソッドを作ってます。

Rubyだとmemcachedクライアントの dalli がバイナリプロトコルをサポートしてるのでRubyのオブジェクトをそのまま保存できるんですが、redisだとそれがない *4 のでちょっと苦労しました。

でそれぞれベンチマークをとった結果、ojが一番速かったのでredisにRubyのオブジェクトを保存する時はojを使うようにしました。

ベンチマークの結果は下記です。

github.com

nginx.conf

設定はいたって普通なんですが

include /home/isucon/webapp/infra/nginx/sites-enabled/*.conf;

のように /etc/nginx/nginx.conf から直接 /home/isucon/webapp 配下のconfをincludeするのが個人的なマイブームです。

チェックリスト

ISUCON11の事前講習 で講師の人が「『nginxのworker_connectionsが1024になってるか確認』ぐらいの細かい粒度でチェックリストを作るのがいい」と言ってたのでそれにならってチェックリスト作りました。

esa-pages.io

個人esaにチェックリストのテンプレを作って過去問を解く時に利用し、ちょいちょい更新していったら最終的にこんな感じになりました。

サーバ構築用のItamae

過去問だとサーバ1台構成である程度動かせた後にAMIを作って2台目以降はそれベースで作っていたので、個人esaに構築手順のコマンドをメモってコピペで構築していました。

しかし直前に「当日はCloudFormationを使ってサーバを構築する」というアナウンスがありました。

運営の人に質問したところCloudFormation以外でサーバを作ってもいいけどサポート対象外だしあまり推奨しないという旨の回答をもらったので急遽構築手順をItamaeでコード化しました。

github.com

このItamaeでやってるのは主に下記です

  • 各サーバ毎にhostnameの設定
  • 必要そうなpackageのインストール
  • 必要に応じてserviceの有効化と無効化
    • 自分はRubyで参加のためRubyの参考実装のserviceのみItamaeで有効化してる
    • redisとmemcachedもインストールはするけど使う時まで無効にして、デプロイスクリプト側で有効化
  • /etc/security/limits.confsoft nofilehard nofile を65536に設定
  • /etc/newrelic-infra.ymlenable_process_metrics (起動してるプロセスのメトリクスを送信)を有効化
  • /lib/systemd/system/mysql.serviceLimitNOFILE=65535 を設定
    • これをやらないと max_connections が増えなかったので脳死で設定
  • /home/isuconssh秘密鍵を作ったり自分のdotfilesリポジトリからtigやtmuxの設定ファイルをダウンロード
  • 最新のRubyをインストール
    • 今回の予選は実行環境に3.0.2がインストール済だったんですが、過去問のAMIだと当然Rubyのバージョンが古いです。素振りの時にRactorが使えないかの検証もやりたかったので過去問も常にRuby 3系でやってました

過去問素振り用のTerraform

個人のAWSアカウントで素振りするために下記のようなTerraformを書きました。

gist.github.com

Terraform 1.0.1, Terraform Provider for AWS 3.25.0で動作確認してますが多少古くても大丈夫なはず。

VPC, Subnet, SecurityGroup辺り全部入りなのでこれを既存のTerraformのリポジトリにコピペすればだいたい動くと思います。

過去問毎に想定スペックがまちまちだし前述の通り当初は1台ずつサーバ起動する想定だったのでEC2はTerraform化していません。

【おまけ】stackprof-webnavをRuby 3.0対応した

github.com

Ruby 3.0で素振りをしてた時にstackprof-webnavが動かなくて困ったのでいくつかパッチを投げました。

github.com

github.com

github.com

無事マージされてISUCON直前にRuby 3.0対応版がリリースされています。

github.com

余談ですがgemリリースが間に合わなかった場合を想定して自分のPR全部入りの状態で手元でビルドしてISUCON当日に gem install --local して使う手順も一応用意してました

当日やったこと

予選落ちなので多くは語りません。

リポジトリは公開してるのでコードの意図を知りたい場合はブログのコメントやTwitterとかで聞いてください。

github.com

敗因は下記

  • sinatraで返してる静的ファイルをnginxで返すのに苦労した
  • 途中から急に context deadline exceeded (Client.Timeout exceeded while awaiting headers) が出始めて原因調査に時間を取られた
    • 原因はstackprofだったんですが、まさか初手で入れてるstackprofのせいで途中から動かなくなるとは思わなかった。(過去問の素振りでも一切問題なかったので余計にハマった)

1人チームで参加した感想

過去に複数人チームで参加した時と比べて下記のようなメリデメがありました

1人チームのメリット

  • 他のメンバーとの共有作業が不要
    • 複数人だとぱっと思いつくだけでリポジトリAWS、NewRelic、Sentry、作業時のチェックリストやスニペット集あたりの共有が必要だと思う
  • コードレビュー不要
    • 普通のチームだとトピックブランチ作ってpushしてPR出してレビュー後にマージという流れになるが、1人チームだとレビューしようがないしブランチ作るのも面倒なので必然的にmainブランチでガンガンコミットしていくことになる
  • 複数人での認識共有や意思疎通などのコミュニケーション作業が不要
  • 他メンバーの予定を気にせずに好きな時間に好きなだけ素振りができる
  • 実行環境を壊しても誰にも迷惑かけない
    • ローカルでアプリを動かす必要がないのでガンガンデプロイしてガンガンベンチまわせる

1人チームのデメリット

  • 1人

おまけ:素振りで使ったAWSの費用

ISUCON終わってAWSの個人アカウントの予算アラートの設定値も元に戻すのでその前に記念スクショ

f:id:sue445:20210823094917p:plain

f:id:sue445:20210823094932p:plain

*1:https://github.com/sue445

*2:https://github.com/isucon/isucon8-qualify/blob/master/webapp/ruby/lib/torb/web.rb#L423-L459

*3: https://isucon.net/archives/52520045.html によるとmysql_use_resultって選択肢もあったんだけどmysql2でmysql_use_resultを使う方法がよく分からなかったのでアプリから SELECT ... INTO OUTFILE を使ってた

*4:redisに保存すると基本的に全部stringになる

*5:後述のstackprof-webnavで必要だった