2022年のOSS関連収入まとめ

2022年も押し詰まって来ましたね。OSSの収入まとめがはやっているようなので、自分もまとめておきます。

最初にまとめ

GitHub Sponsors ¥116,308 + Zenn ¥56,626 = ¥172,934 でした。

ただし、今年の収入のうち$550はGitHubが突然始めた Thank you to our maintainers だったので、それがなければ年間10万円以下だったと思います。

52週で割ると3,325円/週、金曜日の夜に自宅で開発しながら美味しいお酒を飲むための資金考えると、多くはないですが十分いいお酒が買えるぐらい、というところでしょうか。

GitHub Sponsorsではスポンサーの条件として何か確約していることはありません (開発のお供のビール代、と明記しています)。

なお、自分がメンテしているOSSは勤務先でも広く使っているため、業務時間中にメンテナンスをすることも多くあります。

以下は収入の内訳です。今年は ISUCON本 を執筆したり、いろいろありましたが、商業出版関係は含みません。

GitHub Sponsors

スポンサーはこちらからどうぞ github.com

ECSのデプロイツールecspressoなどを作成、メンテナンスしています。 設定しているプランは以下の通りです。

  • $5 / month
  • $2 / month
  • $5 one time

収入は合計で¥116,308でした。

  • 12月¥3,851
  • 11月 ¥3,372
  • 10月 ¥4,020
  • 9月 ¥4,585
  • 8月 ¥4,422
  • 7月¥77,679 github.blog $550を含む
  • 6月 ¥2,366
  • 5月 ¥2,960
  • 4月 ¥2,208
  • 3月 ¥3,442
  • 2月 ¥5,357
  • 1月 ¥2,045

Zenn

ecspressoの解説本を有料で販売しています。

zenn.dev

収入は合計で¥56,626でした。

  • 12月 ¥10,850 (25日時点)
  • 11月 ¥3,642
  • 10月 ¥5,642
  • 9月 ¥3,472
  • 8月 ¥3,906
  • 7月 ¥3,038
  • 6月 ¥3,906
  • 5月 ¥9,548
  • 4月 ¥2,170
  • 3月 ¥2,604
  • 2月 ¥4,774
  • 1月 ¥3,038

AnySan+AnyEvent::SlackRTMを使ったbotを延命させるproxy、sock2rtm

この記事は Perl Advent Calendar 2022 14日目の記事です。

最初に3行でまとめ

  • Slackのrtm.start APIが廃止された
  • AnySan+AnyEvent::SlackRTMが正常に動作しなくなった
  • それを解決するproxyをGoで書いたよ

プロダクトの開発・運用のお供に Slack bot、いると思います。とある会社で7〜8年前に作られたPerlによるWebサービス(ゲームサーバー、2タイトル)でもご多分に漏れず、Slackで動作しているbotが大変重用されています。

特にブランチごとの開発環境の立ち上げ、終了、マスターデータの操作などはSlackでのbotへのコマンドで操作するのがもっぱらになっていて、これができないと開発も運用もほぼ止まってしまうような状態です。

これらのプロジェクトではPerlで非同期に動作するbotを書くために、AnySanAnySan::Provider::Slackが使われており、更にそこから AnyEvent::SlackRTM が実際のSlackに対する通信を行っている作りになっていました。

Slackのrtm.start API廃止

api.slack.com

ところが2022年9月、AnyEvent::SlackRTM (v1.1まで)が使っているAPIである rtm.start が廃止されることになりました。

このAPIは最初期からあった関係か「WebSocket接続の最初にそのworkspaceにいるメンバー全員(!!)の情報と、WebSocketの接続エンドポイントを返す」という豪気な作りになっており、workspaceに人がたくさんいるとレスポンスが極めて遅いという特徴がありました (なので廃止されたんでしょう)。

実際にそれでAPIタイムアウトしてbotが起動できないことがあり、HTTPクライアントのタイムアウトを伸ばすPRを同僚が送って凌いでいたりしました。

github.com

代替としては rtm.connect というAPIを使うことができます。このAPIは、単純にWebSocketの接続エンドポイントを返すもので高速です。AnyEvent::SlackRTM にもこれに切り替えるPRが送られていたのですが、作者がもうPerlをあまり使っていないらしく、しばらく反応がありませんでした。

github.com

何度かコメントした結果、取り込んでリリースしてもらえたのですが、作者曰く

I'm not doing a lot of Perl these days, and I am not writing any bots at all, unfortunately.

とのことで、今後のメンテナンスはちょっと期待しづらい感じです。

rtm.startが廃止されてどうなったか

さて、rtm.connectを使えばそれで問題ないかというと、実際にこれに切り替えた場合にいくつか問題が発生したのでした。

  • AnySan::Provider::Slack はrtm.startでメンバーリストが返されるのをメモリ上にhashで持つ
    • この情報を使ってメッセージの送信者(IDが含まれている)からnicknameを解決している
    • AnySanのAPIでは、IDではなくnicknameを受け取るようになっている (もともとIRC用だったので概念がnicknameベース)
    • メンバーリストがないとメッセージの送信者が取れないため、それに依存した処理ができなくなった
  • rtm.start 廃止後、頻繁にbotが「分身」するようになった
    • これは以前はなかった挙動なのですが、どうやらbot中で数秒程度ブロッキングする処理をしてしまうとWebSocketが切断された時のような挙動を示すようです
    • 加えてAnyEvent::WebSocket::Client あたりの再接続の挙動が原因なのか、再接続が起きると内部的に2個、おなじbotが重複するような挙動をするようになりました→分身

後者はかなり深刻で、不意にbotが内部的に1→2→4→8と分身してそれぞれがメッセージに反応するため、1つ話しかけると複数のbotが一斉に返事をする (だけならまだしも、開発環境を重複して起動したりCIを重複して実行したりする) ようになってしまいました。

とりあえずプロセスを再起動すればリセットされて復活するので、定期的に再起動して凌ぐというワークアラウンドで凌いでいたのですが、不意に分身するたびに手動で再起動するのはあまりにもひどい運用ですね。

解決法 proxy を書く

分身まわりの挙動はAnyEvent::WebSocket::Client (か更にそこで使っているモジュール)を頑張って追えば、直せたかもしれません。しかしbotの行動には発言者をメンバーリストで解決して、その発言者の情報を元になにかするコードがあったため、そこもなんとかして解決しないとbotの改修が必要になってしまいます。

しかも今からAnySanを頑張って直しても、おそらくモジュールのメンテナも使っていなさそうだし…

と悩んだ結果、「Slackのrtm.startのフリをするProxyを書く方が早いんじゃないか(Goで)」と思い立ち、1日頑張って書いたら動いたので投入しました。よかったですね。

sock2rtm

github.com

Goで書かれた「Slack Socket Modeでメッセージを受信してSlack RTMのように振る舞うproxy」です。

使いたい人がいるかは分かりませんが、READMEは日本語で書いてあるので使う場合は読んでみてください。

/start/{チャンネルID 複数指定する場合は , 区切り} に接続すると、そのチャンネルにいるメンバーを取得してrtm.startと同様のレスポンスを返します。WebSocket APIも提供しているため、レスポンスの中のWebSocket URLに接続すると、botがjoinしているチャンネルのメッセージが(Slack RTM同様の形式で)降ってきます。

つまりAnyEvent::SlackRTM を使用する場合は、$AnyEvent::SlackRTM::START_URL をこのAPIのURLに書き換えればOKなのです!

Socket Mode側の再接続はGoのSlackクライアントがよしなにしてくれるのですが、WebSocket側で再接続が発生すると相変わらず分身することがあります。

いろいろ調べた結果、AnyEvent::WebSocket::Connection::closeが呼ばれたらそこで自死する(そしてsupervisorに再起動してもらう)のが手っ取り早かったため、以下のようなコードをbotの最初に入れて解決しました。

BEGIN {
    if ($ENV{SLACK_START_URL} ne '') {
        # sock2rtmのエンドポイントに差し替える
        # https://metacpan.org/dist/AnyEvent-SlackRTM/source/lib/AnyEvent/SlackRTM.pm#L14
        warn "Set SLACK_START_URL to $ENV{SLACK_START_URL}";
        $AnyEvent::SlackRTM::START_URL = $ENV{SLACK_START_URL};

        # 再接続時に複数のwebsocket接続が発生して分身する現象がある
        # close()を上書きすることで、websocket切断時にプロセスが死ぬようになる
        # start_serverから起動することで、プロセスが再起動されることを期待する
        sub AnyEvent::WebSocket::Connection::close {
            die "websocket connection closed";
        }
    }
};

なお、それでもなぜか、稀に分身してしまうことがあります。sock2rmには/metricsというエンドポイントがあり、JSON形式でメトリクスを取得できます。websocket.current_connectionsがsock2rtmに接続しているクライアントのコネクション数(つまりbotの分身数)なので、これをモニタリングして再起動を走らせる、などもできます。こういう小物であっても可観測性は大事ですね。

{
  "slack": {
    "hello": 1,
    "connecting": 1,
    "connected": 1,
    "disconnect": 0
  },
  "websocket": {
    "total_connections": 9,
    "current_connections": 1
  },
  "messages": {
    "received_from_slack": 0,
    "delivered_to_websocket": 0,
    "unsupported_from_slack": 0,
    "write_errored_to_websocket": 0
  }
}

さいごに

最近は新規でPerlを使った開発をすることは少なくなりましたが、運用中のプロダクトは引き続き運用していかなければいけません。問題が起きた場合、もちろんPerlの中で解決することが一番いいのですが、最近の趨勢だとなかなかアクティブなメンテナンスが期待しづらいこともあります。その場合は、外側で一層包んであげて解決することもできるんじゃないか、というお話でした。

ISUCON12予選作問ネタメモ

ISUCON12が大盛況のうちに無事終わって早3ヶ月が過ぎました。もうすっかり冬ですね。

この記事はISUCONアドベントカレンダー 2日目の記事です。

私はISUCON12の予選作問チーム(兼ポータル運用係)だったので、この記事ではISUCON12の予選作問に当たってチームでどのような出題を考えていたのか、没ネタ、やらなかったこと、やれなかったことを含めてざっくばらんに公開してしまいたいと思います。

予選に参加されたかたは、実際にどの要素が採用されていたのかなどを問題を思い出しつつ、ご笑覧ください。

アイディア出し

まず最初に、予選作問チーム全員で問題の要素ネタを出しました。

  • 箱庭諸島(CGIゲーム)みたいなやつ
    • データベースがファイルデータベース  - 初期実装がRDBじゃない  - ファイルロックが必要
    • ユーザーアクセスのタイミングでターン進行処理がある(定期実行バッチの代わり)
      • これをどうにかするのも面白そう
  • 確率的にエラーになる外部APIをなんとかするとかやりたい
    • 確率を入れると1分では運ゲー要素がでちゃって微妙かも
  • 外部コマンド呼び出し、初期は都度起動stdinから入力、stdoutから出力して終了
    • だけど起動しっぱなしでstdinからストリームで流しこむことができるようになっているのでそうすると起動コストが0になる
  • データベースがsqlite
    • 無闇に全部MySQL/PostgreSQLに持っていかないで、更新不要なmasterdataはそのままsqliteにすると複数台で参照がスケールするのでよいとか
    • マルチテナントデータベース(同一スキーマのDBが大量にある
      • 掲示板を作るたびにcreate databaseするとか
      • ベンチ開始時のinitの初期化が難しそう
  • 複数台かつ非同期が本質的な解決になる問題
    • Goで単にgoroutineで解決されないやつがいいな
  • 2014年頃に流行った技術スタックで「お買い得なサービスを買ってきたのねん、プロモするのねん」みたいなやつ
  • Railsとかに代表されるフルスタックなWAFで作って「さぁ軽くして…!」みたいなやつ
    • 移植する言語によってどんだけ重いWAFとかORMになるかみたいなのがあるから言語差激しそう
  • 既に十分速いんだけどさらに雑巾を絞りきらないといけないサービス
  • 予選の問題を解いてもらって高速化したものを機能追加しつつ本選にだして「さらに速くして!」

そしてこれらの要素を元に、具体的な問題を3案ほど立案しました。

【案1】マルチテナントな給与計算システム

  • ****(伏字) みたいなSaaS
  • 複数DBは初のお題
    • 3台あるので最初から分割した状態、1000個DBありまーす
    • 最初から水平分割
    • マルチテナント
      • テナントを作るとDB(SQLite)が1つ作成される
      • 複数台にするにはSQLiteをやめてMySQL / PostgreSQL にするか、ワールド単位で物理ホストを分配するか
  • 全体の管理用DBも1個ある (これはMySQL?)
    • 全テナント横断でクエリするなにか(検索とか?)がある
  • SaaSなのでアクティブユーザー人数課金の計算が必要
  • 更新した人のログが各テナントのDBに格納されていてすべてのテナントを収集する必要がある
  • 全テナントをロックかけないと総課金売上の数字に不整合が起こる
  • 全テナントに対してALTER TABLEが掛かる
  • SQLアンチパターンで言うところのマルチカラムアトリビュートなテーブルがある
    • 運営からのおしらせにタグカラムがあるみたいな
  • テナントごとにリクエストがflockで排他されることでデータ保護を保証する仕組みがある
    • SQLiteをやめても安易に複数台にするとロックがかからなくなって壊れる
    • 何か別の手段でロックするかDBのトランザクションをちゃんと使うか
    • 初期はテナント単位で丸ごとロック
    • ロックを細粒にすれば並列に動作できるようになる
  • テナントごとのアクセス量を公平にする必要がある
    • テナント単位でレスポンスに掛かった時間を積算、単位時間当たりの消費が多ければ回復までブロックされるみたいなの
    • レイテンシが大きいといっぱいアクセスできない = スコアが伸びない
  • 定期実行ジョブがある
    • ターン進行(n秒に1回)
    • 初期状態ではリクエストが来たタイミングで、処理されていなかったものがまとめて実行される
      • これをちゃんとバックグラウンド処理にすると表のリクエストを阻害しない(低レイテンシ)
      • バックグラウンドでcacheを作って配信とかすれば更に低レイテンシに
      • 負荷増加の要素としてターン進行が最初が10秒に1回程度だったのがどんどんリアルタイムに近くなっていく
  • 社員に入社順に連番を出す必要があり、しかも退職したら間を詰めないといけない

【案2】データの署名をするだけのマイクロサービス

  • テーマは「毎分cronをqueueにしてなんとかする」
  • ジョブキューになにを使うかなど選択肢は多そう
  • MySQLにインデックスやります、みたいな初手が意味なくなっちゃうから本選向きかも
  • POSTとGETのAPIが1個ずつあるだけ
    • 画面とかないので動作確認用 cli のバイナリ(リファレンス実装)が提供される
  • マイクロサービスなのでクライアントはいろんなやつがいる
    • リトライを exponential back off してくるやつ
    • 即時リトライでひたすら爆撃してくるやつ
  • 署名を作る方法は運営が提供したバイナリを動かすしかない
    • 署名には有効期限がある(生成後10秒とか?)
  • マイクロサービスなのでmetrics機能がある
    • 過去5秒間のtokenごとのリクエスト回数、署名発行回数、発行した署名のバイト数合計、署名が取得されるまでのレイテンシ(avg, p90, p95, p99)
    • POST時にログテーブルにエントリ作成
    • GET時にログテーブルを更新
    • 管理API /api/metrics で集計結果を取得できる

【案3】ユーザークラスごとの優先度をちゃんと処理するpriority qeueu的な何か

  • 案2と同様の「なにかを遅延処理して返す」サービス
  • 無料ユーザー、プレミアムユーザーがある
  • free のアクセスはお金にならないのでスコアにならない、premium のアクセスだけスコアとする
  • free には所定の rate limit を掛けてよい
    • 規約には書いてあるが手が回っていないので現状何もしていない、という設定が初期状態
    • やるつもりだけはあるので、アクセスログをDBに毎回書き込んでいる
    • 別の手段で適切な rate limit を掛けられるなら、DBに書き込む必要はない
  • premium は遅延処理のレイテンシに保証がある
    • 「n秒以内に完了していること」
    • 保証レイテンシを(error budget的な意味で一定以上)超過するとサービスとして障害 = 失格
  • freeユーザーはrate limitを食らうと一定確率でpremiumになってくれる
    • しかし、レイテンシがfreeの時と変わらないことを観測すると、premiumを解約してしまう
    • ちゃんとpremiumとfreeで優先度に差を付けてあげる必要がある
  • freeユーザーの処理も一定以上遅延すると障害
    • 単に放置するのではだめで遅くてもちゃんと処理はしないといけない
  • 負荷が増えすぎてレイテンシが保証できなくなりそうなら、premiumの登録をsuspendしてよい

最終的には

案1をベースに、題材を「マルチテナントISUCONリーダーボードのSaaS」とアレンジして着地しました。

実際の予選問題でやらなかったネタ

これは作問中にやりたかったけど、予選問題にしては複雑すぎる作りになるので、諦めたネタです。

  • SQLiteだけで実装されたシングルテナントアプリケーションがある
  • それをマルチテナントに改修するため、表にMySQLを使った管理アプリケーションを立てた(という想定)
    • テナント管理、認証などはこっちでやる
  • 2つのアプリケーションは独立したプロセスで動いていてHTTPで通信している

予選問題はSQLiteMySQLを1つのアプリケーションで共用していて、そんな作りのアプリは実際にはない!といわれたりしました。こういうふうに分割されていたら、それっぽいと感じてもらえたかも、とは思います。

ecspresso v2をもうすぐリリースします (v1.99をお試し下さい)

Amazon ECSデプロイツールのecspressoについて、もうすぐv2.0をリリースする予定ですのでお知らせします。先行してバージョン v1.99.x をプレリリースしていますので、利用できる方はお試し頂ければと思います。

(2022-12-15追記: v2.0.0をリリースしました!)

もし不具合や不審な挙動を見つけた場合、GitHub issue や作者の Twitter (@fujiwara) へのメンションで教えていただけると嬉しいです。

github.com

CircleCI Orb をご利用の方に大事なお知らせ

まず最初に大事なお知らせです。CircleCI Orbを利用していて次の条件に両方合致している場合、v2が正式リリースされるとv2がインストールされるため、ワークフローが期待通り動かなくなる恐れがあります。

Orbのバージョンを@1.0.0 にあらかじめ変更するか、versionを明示的に v1.7.14 などと明示することをお勧めします。

v2の安定版がリリースされた後、version: latest を指定した場合の挙動は次のようになります。

  • fujiwara/ecspresso@0.0.15 (それ以前を含む): v2系の安定版をインストール
  • fujiwara/ecspresso@1.0.0 : v1系の安定版をインストール
    • ecspresso v2がリリースされた後、@2.0.0をリリース予定です
    • fujiwara/ecspresso@2.0.0: v2系の安定版をインストール

なお、GitHub Actionsでは、現時点で以下の挙動になっています。@v1では latest を指定してもv2系がインストールされることはありません。

  • kayac/ecspresso@v1 : v1系の安定版最新をインストール
  • kayac/ecspresso@v2 : v1.99系のプレリリース版をインストール
    • ecspresso v2がリリースされた後、v2系の安定版をリリースするように変更予定です

バージョンを明示的に指定した場合は、v1, v2のリリース状況に関わらず、そのバージョンをインストールします。

v1.99 の利用方法

GitHubのリリースページから、バイナリをダウンロードしてインストールできます。 Release v1.99.5 · kayac/ecspresso · GitHub

homebrewでは、brew install ecspresso@1.99 でインストールできます。これは ecspresso v1 と衝突するため、インストール済の場合は一旦 brew uninstall ecspresso で削除してから ecspresso@1.99 をインストールし直して下さい。

GitHub Actionsでは kayac/ecspresso@v2version: latest を指定するか、kayac/ecspresso@v1version: v1.99.5 など特定のバージョンを指定できます。

steps:
  - uses: kayac/ecspresso@v2
     with:
       version: v1.99.5 # or latest

CircleCI Orbでは、fujiwara/ecspresso@1.0.0 を利用して、version に特定のバージョンを指定して下さい。

orbs:
  ecspresso: fujiwara/ecspresso@1.0.0
jobs:
  install:
    steps:
      - ecspresso/install:
          version: v1.99.5

v1からv2への変更点

関連するISSUEとPull Request、コントリビューターの方などはドキュメントに記載しています。(もし漏れがあったら教えて下さい)

ecspresso/v1-v2.md at v2 · kayac/ecspresso · GitHub

非互換な変更

基本的にはv1.xと互換を保っています。ただし、次の非互換があります。

ecspresso create コマンドを廃止しました。deployコマンドでサービスを作成できます

事前のサービスの有無に関係なく、常に deploy を実行するだけでよくなりました。

なおこの挙動と Terraformの null_resource を組み合わせることで、TerraformによるECS関連リソースの作成とecspressoのデプロイまでが terraform apply 一発で完了できるようになります。具体的な方法については別途記事にする予定です。

(2022-12-07 追記: カヤックアドベントカレンダーのほうに書きました) techblog.kayac.com

rollback --deregister-task-definition フラグはデフォルトで真になりました

rollback済のタスク定義を残しておきたい場合(あまりないと思います)、--no-deregister-task-definition を指定できます。

ecspresso render コマンドは、レンダリング対象を指定するフラグの代わりに、引数で指定するようになりました

これまではフラグを何も指定しない場合、空の出力が得られるが正常終了する、という不思議な挙動だったため、必須の引数を指定するように変更しました。複数指定も可能です。

v1

$ ecspresso render --task-definition

v2

$ ecspresso render task-definition service-definition

ecspresso verify コマンドは、ELB関係のAPI呼び出しを taskExecutionRole で行いません

verify時のELB関係のAPI操作を、これまではタスク実行ロールで行うことがありました。

ECSタスク実行ロールには一般的にはELBへの権限は必要がなく、権限を最小化したい場合に不便なため、mタスク実行ロールによるELB API呼び出しは行わないようにしました。常に現在のecspressoを実行しているIAM権限で呼び出します。

ecspresso diff --unified フラグはデフォルトで真になりました

短くなるので便利ですね。

ログメッセージの出力でターミナルを同一行で書き換えたり、メッセージを行折り返ししなくなりました

v1ではデプロイ中の進行状況を表示するためにターミナルの同一行を書き換えたり、その都合でメッセージを固定文字数で折り返したりしていました。しかし、

  • ecspressoはCI/CD環境で実行される場合も多く、その場合は同一行書き換えに意味がない
  • 行が折り返されるとID (task IDとか)をコピーするのに不便

などの理由があったため、ログメッセージは単純に行を送って出力するように変更しました。

設定ファイルでの filter_command 指定は非推奨になりました

filter_commandは、ターミナルでタスクなどを選択する際のキーボード操作をアシストする peco, fzf などのツールを指定する設定です。これは個人向けの性格が強い設定のため、複数人で共通で使用する設定ファイルに定義するのは相応しくないと判断しました。

代わりに 環境変数ECSPRESSO_FILTER_COMMANDを使用して下さい。

設定ファイルの記述も引き続き動作しますが、警告が表示されます。ECSPRESSO_FILTER_COMMANDが指定されている場合はそちらが優先されます。

機能強化

v2で対応予定、としてお待たせしていた機能追加が含まれています!

複数のtfstate読み込みをサポートしました

v1では、読み込む対象のtfstateは1つだけでした。

v2では、プラグイン定義時に func_prefix という設定を指定することで、tfstateごとに呼び出す関数名を変更できます。

# ecspresso.yml
plugins:
   - name: tfstate
     config:
       url: s3://tfstate/first.tfstate
     func_prefix: first_
   - name: tfstate
     config:
       url: s3://tfstate/second.tfstate
     func_prefix: second_

このように定義すると、テンプレートで first_tfstate で呼び出すと s3://tfstate/first.tfstate を対象に、second_tfstate を呼び出すと s3://tfstate/second.tfstate を対象にしてtfstate参照を行います。

[
  "{{ first_tfstate `aws_s3_bucket.main.arn` }}",
  "{{ second_tfstate `aws_s3_bucket.main.arn` }}"
]

設定ファイルに Jsonnet が使用できるようになりました

v1では、タスク定義ファイルとサービス定義ファイルがJSONもしくはJsonnet、設定ファイル(ecspresso.yml) がYAMLのみでした。

v2では、設定ファイルに JSON, Jsonnet, YAML のいずれも使用できます。

既存のECSサービスを設定ファイル化する ecspresso init コマンドで --jsonnet を指定することで、全てのファイルがJsonnetで生成されるようになりました。

設定ファイルのデフォルト値は ecspresso.yml のままです。ただし ecspresso.yml が存在しない場合、ecspresso.json, ecspresso.jsonnet を探して見つかったらそれを使用します。

各種ファイルを解釈して標準出力に書き出す ecspresso render コマンドにも --jsonnet フラグが追加されました。

設定ファイルでCodeDeployアプリケーション名とデプロイグループ名を指定できます

CodeDeployを利用する場合、ecspressoはデプロイ対象のECSサービスに関連するCodeDeployアプリケーションとデプロイグループを自動で発見します。

しかしCodeDeployに大量のアプリケーションが存在する場合には、この発見処理のための大量のAPI呼び出しが原因でスロットリングエラーが発生することがありました。

設定ファイルにあらかじめアプリケーション名とデプロイグループ名を明示することで、API呼び出しを減らすことができます。

# ecspresso.yml
codedeploy:
  application_name: myapp
  deployment_group_name: mydeployment

CodeDeploy でのデプロイ中にプログレスバーが表示されます

デプロイの進行自体はecspresso以外の手段(マネージメントコンソール)で行う必要があります。ecspressoでデプロイ完了を待っている状態で、トラフィックの移行状況がターミナルのプログレスバーに表示されるようになりました。

ecspresso verifyでコンテナイメージのplatformも確認するようになりました

タスク定義でCPUアーキテクチャにARM64を指定している場合に、verifyコマンドでimageを検証する場合にArm64用のイメージが存在するかを確認するようになりました。

SSMパラメータストアを参照するプラグインが追加されました

SSMパラメータストアの値を直接参照できるようになりました。

# ecspresso.yml
plugins:
  - name: ssm
{
  "string": "{{ ssm `/path/to/string` }}",
  "stringlist": "{{ ssm `/path/to/stringlist` 1 }}",
  "securestring": "{{ ssm `/path/to/securestring` }}"
}

秘匿値はタスク定義のsecretsを利用してタスク実行時に取得するべきですが、特に秘匿しなくてもよい値はタスク定義に直接埋め込むことができます。

その他の変更点

見た目の機能の追加/非互換よりも、内部的な変更が実は多かったりします。

AWS SDK Go v2 に切り替えました

AWS SDK Go v1も現時点ではEoL予定はなく引き続き更新されていますが、今後のことを考えて依存ライブラリも含めて v2 への移行を行いました。

CLI option parserを変更しました

alecthomas/kingpin がメンテナンスモードになったため、同作者の alecthomas/kong へ移行しました

単体タスク操作関連のコードを ecsta に切り出しました

ecspresso には tasks, exec など、ECSタスク単体を相手に操作する便利なコマンドがいくつかあります。

これらの操作を ecspresso 管理下以外のタスクにも行いたい需要があったので、ecsta というCLIツールを作成しました。どうぞご利用下さい。

github.com

ecspresso側のコマンドはv1と変更ありませんが、内部的にはecstaをライブラリとして呼び出すようになっています。

まとめ

  • ecspresso v2をもうすぐリリースします
  • v1.99.x を試していただいて、フィードバックを頂けると嬉しいです
  • CircleCI Orb @0.0.15 かつ version: latest を指定している方はご注意下さい

「達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践」を執筆しました

「達人が教えるWebパフォーマンスチューニング 〜ISUCONから学ぶ高速化の実践」という本を6名の共著で執筆しました。技術評論社さんから、2022年6月4日発売予定です。電子版もでます。

gihyo.jp

Amazon はこちら。

タイトルの通り、ISUCON で出題されるようなWebサービスを例にして、Webサービスのサーバーサイドパフォーマンスチューニングを指南する内容です。通称「ISUCON本」と呼んでください。

2020年の末に、技術評論社さんからWebサービス高速化 × ISUCONに関する書籍を執筆しませんか、と藤原までお誘いをいただいたのが発端でした。

書きたい気持ちはあったものの、内容的にとても一人では書き切れる気がしなかったので、とあるISUCON関連のSlackチャンネルで一緒に書きたい人はいませんか、と募集しました。結果、6名での共著という形で本にすることができました。

執筆者のプロフィールは以下です。大変豪華なメンバーで、"ISUCON"がゲシュタルト崩壊を起こすレベルで埋まっておりますね…

藤原俊一郎 @fujiwara (2011年より面白法人カヤック。SREチーム所属。ISUCON優勝4回、出題3回)

馬場俊彰 @netmarkjp (株式会社X-Tech 5取締役CTO、株式会社iCARE技術顧問。ISUCON第一回にプロジェクターを持ち込んで参加しSELinux=Enforcingで入賞)

中西建登 @whywaita (株式会社サイバーエージェント所属。ISUCON8にて史上初の学⽣総合優勝)

長野雅広 @kazeburo (さくらインターネット株式会社所属。ISUCON1、ISUCON2、ISUCON9予選で問題作成。参加者として優勝も予選落ちも経験)

金子達哉 @catatsuy (株式会社PR TIMES開発本部長CTO。ピクシブ・メルカリを経て現職。ISUCON9予選・ISUCON6本選出題)

草野 翔 @rosylilly (宇宙海賊合同会社代表、株式会社ハンマーキットCTO、株式会社 Tech Consiglie CTO、プロモータル株式会社相談役、IPTech特許業務法人技術顧問。ISUCON9優勝、ISUCON4とISUCON10出題)

目次

  • 1章 チューニングの基礎知識 (@netmarkjp)
  • 2章 モニタリング (@whywaita)
  • 3章 基礎的な負荷試験 (@fujiwara)
  • 4章 シナリオを持った負荷試験 (@fujiwara)
  • 5章 データベースのチューニング (@kazeburo)
  • 6章 リバースプロキシの利用 (@catatsuy)
  • 7章 キャッシュの活用 (@catatsuy)
  • 8章 押さえておきたい高速化手法 (@catatsuy)
  • 9章 OSの基礎知識とチューニング (@whywaita)
  • 付録A private-isuの攻略実践 (@fujiwara)
  • 付録B ベンチマーカーの実装 (@rosylilly)

どんな人向けの本か

通称「ISUCON本」ですが、ISUCONに特化した本ではありません。一般的なWebアプリケーションやミドルウェアに対して、パフォーマンスを改善するためにはどうしたらいいかをまとめた本になります。

ただしパフォーマンスチューニングを説明するための例は、共著者の @catatsuy さんが公開している private-isu という社内ISUCONの問題を採用しています。そのため「ISUCONに学ぶ」というサブタイトルになっているわけですね。

github.com

本書「はじめに」からの引用です。

ISUCONは技術のコンテストですが、そこで題材として提供されるWebサービスは、通常のWebサービスと同様の技術で作られています。つまり ISUCON での高速化手法は、業務で作成されるような 通常のWebサービスにも適用できるのです。本書は、次のような方にお勧めします。

  • Webサービスを開発、運用しているが動作が重くて困っている
  • これまでWebサービスの速度について深く考えたことがなかった
  • ISUCONにこれから出場してみたい
  • ISUCONに出場したことはあるが、よい成績を収められなかった

本書を書いてみて改めて実感したのですが、Webサービスのパフォーマンスチューニングには関わる技術が非常に多く、多数のコンポーネントに対する知識が必要です。

しかも、これも本書内で再三触れたことなのですが、システム全体の中でボトルネックになっている1点見つけ出してそこを改善しない限り、他の何をやっても大きな性能の向上は見込めないという厄介な性質があります。

そのため、小手先のチューニングテクニックというよりは全体的な概念を掴むこと、ボトルネックを見つけてそこに向き合うことを重視してほしいなという気持ちで執筆しました。

この本に書いてあることを理解していれば、仮にISUCONに参加しても手も足も出ないことはないと思います。予選通過ラインに近いところまではいけると思いますが、実際に予選を突破できるかどうかはさすがに運次第、というところでしょうか(著者陣でも予選を突破できないことがあるのでこれは仕方ないですね)。

個人的な見所

こういってはなんですが、本編もですが付録も面白いです!なにしろ付録だけで70ページ以上あります。

付録Aは本書内で例として取り上げた catatsuy/private-isu を実際に段階を踏んで攻略してみた、いわゆる攻略記事になっています。初期スコアの650点から最終的に32万点まで500倍に性能アップするところはもちろんですが、最後にとあることをして1万点に失速するところまで含めて、なかなか面白い読み物になっていると思います。

付録Bは、ISUCON のベンチマーカーを書くためのフレームワーク isucandar を使って実際にベンチマーカーを書くための勘所を、isucandar の作者が解説しています。普段から ISUCON の作問をする人はそれほどいないと思いますが、社内 ISUCON を開催するとか、本物 ISUCON の作問が突然降ってきたみたいな場合には必読です。

github.com

謝辞

執筆のお話を頂いた技術評論社の皆様、共著者の皆様、レビューに協力していただいた皆様、ISUCONを運営しているLINE株式会社の皆様(特に@941さん)、大変ありがとうございました。

(お断り) 本書と ISUCON 12 との関連

自分は今年の ISUCON 12 の作問担当でもありますが、作問の話自体はこの本がほぼ書き上がってから来たため、本書の内容と ISUCON 12 で出題される問題の内容に、関連は特にありません。念のため。

ecspresso tasks --trace でECSタスクのイベントログを一括表示する tracer を使えるようにした

メリークリスマス!(フライング)

AWS Containers Advent Calendar 2021 6日目の記事です。


先日、github.com/fujiwara/tracer という結構便利なやつを作りました。Amazon ECS タスクに関連するイベントとログを一括で出してくれる、シンプルな CLI です。

github.com

解説記事はこちら(会社のアドベントカレンダー)に書きましたのであわせてご覧下さい。

techblog.kayac.com

tracer は単体の CLI で動作するのでどのような手段で管理している ECS タスクにも使えるのですが、これを ecspresso からも呼び出せると便利では? と思い立ったのでそうしました、というのがこの記事です。

ecspresso tasks --trace

tracer は ECS クラスタ名とタスクIDを指定して動作します。ecspresso には tasks コマンドがあり、これは ecspresso が管理しているクラスタからタスクを特定する機能があります。つまり、

$ ecspresso tasks --trace

として、ecspresso 側でタスクを特定して、そのIDをtracerに引き渡して実行すれば完成です。実質10行ちょっとのpatchで実装できました。よかったですね。(tracer のコードを package として ecspresso 内部に組み込んであるので、tracer コマンドは不要です)

動作デモはこんな感じです。

ecspresso v1.7.2 で使えます

先ほどリリースした ecspresso v1.7.2 から使えます。

github.com

どうぞご利用ください。

ecspresso v1.7.0 をリリースしました

3ヶ月ぶりの ecspresso リリースのお知らせです。

Amazon ECS デプロイツール ecspresso の v1.7.0 をリリースしました。

github.com

新機能

定義ファイル (サービス / タスク / run --overrides-file) で Jsonnet の直接読み込みをサポートしました

これまで JSON 形式が要求されていた各種定義ファイルで、Jsonnet の読み込みがサポートされました。

ファイルの拡張子が .jsonnet の場合、ecspresso 内部で先に Jsonnet を処理して JSON に変換後、これまで通り (テンプレート記法による env, tfstate の展開などが行われて) 読み込まれるようになります。

JSON は人が読み書きするのは不便なことが多いため、 ecspresso advent calendar 2020 day 20 - Jsonnetによる定義ファイル生成 の記事などで Jsonnet 形式をお勧めしていました。これまでは事前に自分で jsonnet コマンドを実行して JSON に変換してから ecspresso を起動する必要がありましたが、その処理が不要になりました。

github.com/google/go-jsonnet をライブラリとして組み込んでいるため、jsonnet コマンドは不要です。CI/CD 環境でも jsonnet のインストールが不要になって嬉しいですね。

各種定義ファイルで使用できない key を発見すると警告を表示するように

ecspresso が使用するタスク/サービス定義のファイルは aws-sdk-go で定義している構造体にマッピングされます。

これまでは JSON ファイル中の要素名(key) を間違った場合など、構造体に対応するものがない場合は、単に無視されていました。そのため要素名を typo した場合など、気が付かないうちに要素自体が省略されてしまい、意図しない構成がデプロイされてしまうことがありました。

v1.7 では、構造体に対応する要素が存在しない場合は warning を表示するようになりました。

f:id:sfujiwara:20211101152937p:plain

タスク定義リビジョンを取り扱う revisions, deregister コマンド追加

revisons コマンドは、存在しているタスク定義のリビジョンを表示できます。 IN USE の欄には、現在サービス内で利用しているタスクの情報が表示されます。単体タスクが使用している場合は RUNNING task, サービスに設定されているものは *** deployment となります。

$ ecspresso revisions
ecspresso --envfile envfile revisions
|        NAME        |       IN USE       |
+--------------------+--------------------+
| ecspresso-test:386 |                    |
| ecspresso-test:387 |                    |
| ecspresso-test:388 |                    |
| ecspresso-test:389 | ACTIVE deployment  |
| ecspresso-test:390 | RUNNING task       |
| ecspresso-test:391 |                    |
| ecspresso-test:392 | PRIMARY deployment |

古い revision を登録解除する、deregsiter コマンドも追加されました。

deregister コマンドでは以下のオプションのどちらかが必要です。

  • --revision=リビジョン番号 を指定すると指定したリビジョンを登録解除します
  • --keeps=保持する数 現在利用中のものを除いて指定した数だけリビジョンを保持し、古いリビジョンを登録解除します
    • 「現在利用中のものを除いて」= revisions コマンドで IN USE の欄が空欄のものが対象

revision が増えすぎて困る!とか、間違って動作しない revision を登録してしまったのでうっかり使わないようにしたい、という場合にご利用ください。

Fargate Windows Containers 対応

先日リリースされたばかりの、Fargate での Windows Containers の起動に対応しました。

aws.amazon.com

タスク定義の image, runtimePlatform を適切に指定すると起動できます。

// せっかくなので Jsonnet 形式で
{
  image: 'mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019',  
  runtimePlatform: {
    operatingSystemFamily: 'WINDOWS_SERVER_2019_CORE',
  },
  // ....
}

f:id:sfujiwara:20211101155543p:plain

バグ修正

サービス定義の desiredCount が設定されている(未定義ではない) 場合に create を行うと、その設定を無視して desiredCount=1 を設定してしまうバグを修正しました。


おしらせ

ecspresso handbook で、コマンドリファレンスの章も追加で無料公開しました。

現在 v1.5 対応版ですが、近日中に v1.7 対応に更新予定です。

zenn.dev