unless’s blog

日々のちょっとした技術的なことの羅列

Datadog Profilerで継続的なProfilingを実施できるようにした話

おまえだれ?

株式会社Kyash でサーバサイドエンジニアをしている @uncke__ko です
Fundsチームに所属していて主にお金の入出金部分を担当しています

これは何?

Kyashではシステムの監視やモニタリングにDatadogを使用しています

www.datadoghq.com

Datadogにある Continuous Profilerという機能を使い、継続的にProfilingできる環境を整えたのでその話になります

docs.datadoghq.com

Profilerとは

プロファイラとはアプリケーションの性能を解析するためのツールです
ソフトウェア開発をしていると下記のようなことが度々起こった経験が1度はあると思います

  • OOMが発動するようになる
  • latencyが遅くなる
  • アプリケーションの動作が徐々に重くなる

Profilerがあると、このようなときにアプリケーションのCPU使用率やメモリ使用率、関数のCall数など様々な情報が手に入るので調査が行いやすくなり原因を特定できるようになることが多いです

いままでのKyash

KyashのサーバサイドではGolangを使用しています
Golangにはpprofというツールが用意されているので、Kyashでもこれを使用して問題が起こったときにProfileを取得していました Call GraphやFlame Graphなどもあり見やすいです

CallGraph https://github.com/google/pprof/blob/master/doc/images/callgraph.png

https://github.com/google/pprof/blob/master/doc/images/callgraph.png?raw=true

なぜDatadog Profilerに移行したか

Profilingするだけであればpprofでも問題はありませんでした
しかし、pprofの特性上、webサーバに対してProfileを取得する場合はhttpRequestを行わなければなりません
どこからhttpRequestを行うのか、portはどこに当てるのか、ECS Fargateにインフラを移行中なのでServiceによってProfileの取得方法に差異がでてしまったりしました
また、profilerを導入してる企業だとあるあるかと思いますが、問題が起こったときにしかProfilerを見ずに、継続的にProfilingを実施して改善するActionにつなげにくいという問題もあります
そこでKyashで使っているDatadogにProfiler機能が追加されたので移行することにしました
メリットとしては下記かと思います

  • 見るべきツールが統一されるのでDatadogだけを気にしていればいい
  • Serviceのインフラにより取得方法が変わることがない
  • 簡単にGUIで結果が見れる
  • 定期的にProfilingしてくれるので障害時の情報もサクッと見れる
  • 定期的にProfilingしてくれるのでEngineerの参入障壁が低く継続的にProfileベースでコードを改善していけるポテンシャルがある

GolangでProfilerを有効にする

すでにDatadogを導入しているならかなり簡単です

  1. agentのverを 7.20.2以上 or 6.20.2以上にする

  2. packageをinstallする

go get gopkg.in/DataDog/dd-trace-go.v1/profiler
  1. profilerをimportする
import "gopkg.in/DataDog/dd-trace-go.v1/profiler"
  1. profilerを開始する
err := profiler.Start(
    profiler.WithService("<SERVICE_NAME>"),
    profiler.WithEnv("<ENVIRONMENT>"),
    profiler.WithVersion("<APPLICATION_VERSION>"),
    profiler.WithTags("<KEY1>:<VALUE1>,<KEY2>:<VALUE2>"),
    profiler.WithProfileTypes(
      profiler.CPUProfile,
      profiler.HeapProfile,
      // The profiles below are disabled by default to keep overhead
      // low, but can be enabled as needed.

      // profiler.BlockProfile,
      // profiler.MutexProfile,
      // profiler.GoroutineProfile,
    ),
)
if err != nil {
    log.Fatal(err)
}
defer profiler.Stop()

導入方法なども公式にDocmentにしっかり記載されてるので迷うことはないと思います

docs.datadoghq.com

GolangのProfilerで取得できる項目

GolangのDatadog Profilerは裏でpprofが使用されています

github.com

ですので基本的にpprofで取得できる内容とほぼ同じですが下記が取得できる内容です(documentからの引用です)

CPU Time

各関数がCPUでの実行に費やした時間を示します。ネットワーキング、チャンネル、ミューテックス、スリープの待機のような Off-CPU の時間は、このプロファイルでキャプチャされません。ミューテックスおよびブロックのプロファイルをご確認ください。

Allocations

アプリケーションの開始以降、各関数によってヒープメモリに割り当てられたオブジェクトの数を示します。これには、後で解放された割り当ても含まれます。これは、ガベージコレクションの負荷を調査する際に役立ちます。

Allocated Memory

アプリケーションの開始以降、各関数によって割り当てられたヒープメモリの量を示します。これには、後で解放された割り当ても含まれます。 Go ではこれを alloc_space と呼びます。これは、ガベージコレクションの負荷を調査する際に役立ちます。

Heap Live Objects

各関数によってヒープメモリに割り当てられたオブジェクトの数と、アプリケーションの開始以降に割り当てられたまま残り、最後のガベージコレクション以降存在するオブジェクトの数を示します。これは、サービスの全体的なメモリ使用量を調査する際に役立ちます。

Heap Live Size

各関数によって割り当てられ、アプリケーションの開始以降に割り当てられたまま残り、最後のガベージコレクション以降存在するヒープメモリの量を示します。Go ではこれを inuse_space と呼びます。これは、サービスの全体的なメモリ使用量を調査する際に役立ちます。

Mutex

アプリケーションの開始以降、関数がミューテックスを待機している時間を示します。このプロファイルのスタックトレースは、ミューテックスで続行をブロックされた別の goroutine を許可した Unlock() 演算子をポイントします。スピンロックを使用したショートミューテックスの競合はこのプロファイルでキャプチャされませんが、CPU プロファイルで確認できます。

Block

アプリケーションの開始以降、関数がミューテックスおよびチャンネルオペレーションを待機している時間を示します。スリープ、GC、ネットワーク、Syscall オペレーションは、このプロファイルでキャプチャされません。ブロッキングオペレーションは、ブロックが解除されてからのみキャプチャされるため、スタックしていると思われるアプリケーションのデバッグにこのプロファイルを使用することはできません。ミューテックスの競合の場合、このプロファイルのスタックトレースはブロックされた Lock() 演算子をポイントします。これにより、ブロックされているプログラムがわかり、ミューテックスプロファイルにより、競合の原因となっているプログラムの部分がわかります。この点に関する詳しい情報は、Go におけるプロファイリングのブロックリサーチをご覧ください。

Goroutines

同じ関数(CPU 上および待機中の Off-CPU)で現在実行中の goroutines の数のスナップショットを示します。スナップショット間での goroutines の増加は、プログラムで goroutines がリークしていることを示しています。最も健康なアプリケーションでは、このプロファイルはワーカープールにより使用され、その goroutines 使用数が表示されます。遅延の影響を非常に受けやすく、大量の goroutines(10.000 以上)を使用するアプリケーションの場合、このプロファイルを有効にすると O(N) stop-the-world 型の一時停止が必要になることにご留意ください。一時停止はプロファイリング期間(デフォルトは 60 秒)ごとに発生し、通常 goroutine あたり ~1µsec 継続します。~100ms の p99 レイテンシー SLO の典型的なアプリケーションでは、この警告を無視することが可能です。より詳しい情報については、Go における Goroutine プロファイリングリサーチをご覧ください。

docs.datadoghq.com

実際にProfilingしてみる

APM->Profile Search でprofilerを確認します

CPU Timeはこのような感じ
frame grapthも出て見やすいです call数が出てくれないのが少し不便なところなので、今後のupdateに期待です

HeapLiveSizeはこんな感じです こちらもかなり見やすくgrapth化してくれています

個人的にGolangでDatadog Profilerを使う上で便利だと感じているのはGoroutineの項目です
Goroutineのリークなどの調査に役立っていてとても助かっています
とはいえ、Goroutinesの項目でも触れてますがSTWが発生するので高頻度でのGoroutinesの取得は慎重になったほうがよいかもしれません
リーク周りでおもしろい記事を弊社メンバーが書いてるので興味があればこちらもぜひご覧ください

ECSへ移行したらマシンリソースを使い果たした問題がnet/httpのhttp.Transportの使い方の誤りにあった話 - Qiita

移行してみてどうだったか

まだすべてのマイクロサービスの移行が完了したわけではありませんが、主要なマイクロサービスには導入できてきました
実際に導入してみて、logやdashboardを見るついでにprofilerが気軽に見れる状態なので、profilerを見ることへの抵抗は減ったのかなと思います
また、障害時にもProfilerのおかげで調査の足がかりになったこともあり、改めてProfilerを見ることの大切さに気が付きました
とはいえ、まだまだ継続的にProfilerをみて改善する環境には程遠いと思ってますので、この辺は徐々に改善していきたいなと思っています

おわりに

最近、Kyashに導入したDatadogProfilerの話を書いてみました DatadogはかなりDocumentが充実しているので導入に詰まるようなことはないと思います 継続的なProfilingを気軽にできる点はとても素晴らしいと思いますので、Datadogを使用していて、まだProfilerを導入していない企業の方はぜひ導入を検討してみてください

また、Kyashでは一緒に働くエンジニアの皆様を募集しております
もしご興味ありましたら、以下のリンクをご覧ください

Kyash募集職種一覧
カジュアル面談の申し込み
Podcast kyash.fm

子育てソフトフェアエンジニア、家を買い換える

f:id:gettergot:20210818175811j:plain

まえがき

Fintechベンチャーでサーバサイドエンジニアをしている @uncle__ko です
この度、都内に所有していたマンション(居住用)を売却し、戸建て(居住用)を購入しました
かなり大変だったのと色々と知見を得られたのでブログに残しておこうかなと思い、今回この記事を書いてみました
ちなみにエンジニア要素は特にないのですw
*1

なぜ買い換える?

コロナ渦で在宅ワークになり、家にいる時間が増えました
通勤もあまりしなくなり、家で快適に仕事が出来て、プライベートも充実させたいと考えるようになり、都内のマンションより郊外の戸建てに住んだほうが僕や家族のライフスタイルにはあっているかなと思ったのがきっかけです
また、引越すなら娘が小学校に上がる前には引越しておきたいというのもありました

さあ、家探しだ!

要件の整理

間取り

僕の家族構成を説明すると、夫婦+子供1人です
いまのところ子供を増やす予定はありません
僕も妻もITエンジニアでリモートワークなので仕事部屋は欲しいです
ただ、1人1部屋ほしいかと言われると、1年間リモートワークをしてみた感想だと欲しいけど必須ではないという感覚です
また、子供部屋は1部屋ほしいです

まとめると

  • 夫婦の寝室
    • 1
  • 子供部屋
    • 1
  • 仕事部屋
    • 1

があれば問題ないということになります

つまり、3LDK以上の建物
ということになります

立地

僕も妻も田舎の出身で、都会より田舎がよいという考えでした
僕は持病の喘息やアトピーもあり、田舎のほうが過ごしやすいというのもあります
また、自然の中で子育する方が子供にもいいのかなという思考をもっていました(人によると思うので、あくまで個人の感覚です)
そして、リモートワークであることもあり都心に住む理由もない
ただ、いつまでもリモートワークとは限りません
最悪、出社になっても通える範囲が理想

さらに僕が海沿いの出身で海が好きなので海の近くならいいな
という思いもありました

まとめると
郊外だけど都心にギリギリ通える範囲がよい
さらに海近だと最高
という立地で探すことにしました

マンションか戸建てか

f:id:gettergot:20210818175905j:plain

もともと高い建物より地に足をつけていたいタイプの人間なので低層のマンション or 戸建てがよいと思ってました
また、子供が庭で遊べたほうがいいよなとか考えたり、車が好きで10代の頃から車を所有しているので、出来れば洗車が出来る戸建てがいいかなと思いました

まとめると
戸建てが理想
ということです

まとめ

ここまでをまとめると
3LDK以上の間取りで、ある程度田舎だけど、最悪都心へ通える範囲の海が近い戸建て
ということになります

売却だ!

https://1.bp.blogspot.com/-rFpZh9viF6s/VwIpfsKEZ4I/AAAAAAAA5i4/g0ok-LCitpMlkI-tTYSsRZznRstI6tNCQ/s400/job_fudousan_kanteishi.png

欲しい物件の要件が整理できても、すでに所有しているマンションを売却しなければ、しがないエンジニアの自分では新しい家を買うことはできません
なので売却からスタートします

住み替えの方法

まず、住み替えるには2通りの方法があります

  • 買い先行
  • 売り先行

です

買い先行

そのままですね
先に欲しい物件なり土地を探すなりして買ってしまい、売りを後で行う形です
メリデメは下記

  • メリット
    • いくら時間を掛けてもいいのでじっくり家探しができる
    • 仮住まいがいらない
  • デメリット
    • 2重ローンを組む可能性が高い
    • 売却がかけ足になるので売却金額が安くなりやすい

売り先行

それもそのままです
先に所有している物件を売却してから物件を購入することになります

メリデメは下記

  • メリット
    • 売却に時間を使えるので買い手との売却額の交渉がしやすい(高値で売れる可能性がある)
    • 2重ローンにならないので資金計画は立てやすい
  • デメリット
    • 仮住まいを挟むことになる可能性が高い

どちらを選択したか?

理想は同時進行で売りと買いを同時に実施したい
また、リスクは最小限で進めたい
という理由で売り先行で実施しました
*2

売却の仲介業者探し

suumoも一括査定で地元の不動産屋と大手の不動産屋と希望してる買い替え先の不動産屋に頼みました

suumo.jp

どこにお願いしたか?

自分は最後まで買いと売りのタイミングを同日に行うことを諦めたくなかったので大手で全国に支店がある大手の不動産屋に仲介をお願いしました

媒介契約

不動産の売却を正式に依頼する際には、不動産仲介会社と媒介契約を締結することが必要になります
売却時の媒介契約とは、宅地建物取引業者が不動産を売却しようとする者との間で締結する契約です

不動産仲介会社は、宅地建物取引業法によって依頼者との媒介契約の締結が義務付けられてます
依頼者が不動産仲介会社にどのようなサービスを受けるのか?、また仲介手数料はいくらか?などを明記することで、仲介時のトラブルを未然に防ぐ目的があります

媒介契約は下記3種類があります

  • 専属専任媒介契約
  • 専任媒介契約
  • 一般媒介契約

それぞれのメリデメを書いていきます

専属専任媒介契約

専属専任媒介契約は売却活動を1社に任せる契約となります
業務報告義務があったり仲介手数料が絶対もらえるので不動産仲介会社の中での優先度は高くなる傾向にあります

  • メリット
    • レインズ*3への登録が5営業日以内
    • 業務報告義務が1週間に1回以上
  • デメリット
    • 自分で買主を見つけたとしても仲介手数料は支払わなければならない
    • 囲い込みをされる可能性がある
      • 売り買いどちらからも仲介手数料を取るために囲い込みを行う悪い不動産会社もいるかもしれないです

専任媒介契約

専属専任媒介契約と同様に、売却活動を1社に任せる契約となります
違いとしては、売り主自ら見つけてきた相手との取引は仲介手数料を払わなくていい点です
ただし、複数社との契約はできません

  • メリット
    • レインズへの登録が7営業日以内
    • 業務報告義務が2週間に1回以上
  • デメリット
    • 囲い込みをされる可能性がある
      • 売り買いどちらからも仲介手数料を取るために囲い込みを行う悪い不動産会社もいるかもしれないです

一般媒介契約

一般媒介契約は、複数の会社に売却活動を依頼できる契約となります
不動産仲介会社の中での優先度は低くなる傾向にあります
競合してがんばってもらえると思いきや、色々と面倒なのと手数料をもらえない可能性があるので、実は優先度を下げられたりします

  • メリット
    • 複数の不動産会社と契約できる
  • デメリット
    • レインズへの登録が任意
    • 業務報告も任意

結局どの媒介契約にしたの?

専属専任媒介契約でお願いしました
理由としては出来るだけ高く売却したかったので専属でお願いしたくて、マンション内に知り合いもいなかったし、不動産購入したい知り合いもいなかったので、専属専任でいいかなって思って契約しました

売却額の設定

持ち主は自分なので好きな額に設定することができます
言ってしまえば購入額の2倍で売りに出しても文句は言われません
とはいえその金額だと誰も購入してはくれないでしょう

周りの相場や、同じマンションの過去の売却額等を考慮しつつ金額を設定していきます

現状、都内のマンション価格は上がり続けていて、自分の所有していたマンションは相場を見ても購入金額より高く売却できそうな感じでした
これはとても運が良かったと思います
ですので指値されることも見越して相場よりも少し高い金額で売りに出しました

売却活動スタート

売りに出して最初の週末には5組の内覧がありました
また、次の週には3組の内覧がありました

その中の1組の方に指値なしで購入いただき2週間で売却が完了することになりました
ただ、この方も買い替えでした
そのため、もし、指値なしで申し込みをしてくれた方に売却する場合は仮住まいを余儀なくされます
とはいえ、指値なしだと利益がある程度見込めるため仮住まいを挟んで売却することにしました

購入だ!

https://1.bp.blogspot.com/-nl6Li1Z-P8I/VpjCk0Z8lrI/AAAAAAAA3DQ/ISdp9j0fvJ8/s400/house_ie_sagashi.png

思ったより早く売却が完了してしまい、急いで購入物件を探すことになりました また、仮住まいを挟まなければいけないので、出来るだけ早く住めることが条件となりました

住宅ローンの事前審査

自分達の収入から予算はある程度把握していましたが借りられなければ買えません 適当な予算にあった物件でネット銀行とメガバンク地方銀行の事前審査を通しておきました これにより、思い描いてる予算で自分が物件を探せることがわかるので早めに行っておきました

住宅ローンの金利タイプ

f:id:gettergot:20210818180119j:plain

住宅ローンを調べると最初に出てきてみんな悩む金利タイプを書いておきます それぞれの考えがあるのと、どちらが正解というのもないと思ってるのと、僕はFP(ファイナンシャルプランナー)でもなんでもないので、どちらがいいとかの言及は避けておきます

固定金利

固定金利はずっと同じ金利

  • 全期間固定金利
    • メリット
      • 完済までの返済金額が確定しているため、返済計画が立てやすい
      • 金利の低い時期に契約すると、最終支払いまで低金利のまま
    • デメリット
      • 一般的に、変動金利型に比べて金利が高めに設定されている
  • 固定金利期間選択型
    • 最初の契約時に2年、5年、10年などの期間を選び、その期間の金利を固定するタイプ
    • メリット
      • 固定金利期間中は返済額が変わる心配がなく、また変動金利型のような未払利息のリスクもない
    • デメリット
      • 固定期間が終了すると、その時点の金利金利タイプを選択し直すのでその後の返済額は最初にローンを組んだ時点ではわからない
      • 返済開始後の金利の変動によっては将来の返済額が変わる可能性がある
変動金利

変動金利を選べば金利は定期的に変わる

  • メリット
    • 金利水準が下がり、ローンに適用される金利も下がると、将来の返済額は少なくなる
    • 通常半年ごとに金利が見直されるが金利が大きく上昇した場合でも返済額は通常5年ごとに見直すためすぐに家計に影響することが避けらる
  • デメリット
    • 金利の変動によって将来の返済額が変わる可能性があり、ローンを組んだ時点では返済総額がいくらになるかはわからない
    • 多くの金融機関では半年ごとに金利の見直しが行われるが、元利均等返済の場合は返済額の見直しは5年ごとになっているため金利が上昇すると、返済額のうち金利が占める割合が高くなるので元金があまり減らないということも考えられる
    • 金利の上昇度合いによっては、本来支払わなければいけない利息部分の金額が返済額を上回る「未払利息の発生」の危険性がある

伏兵、保育園の転園問題現る

https://2.bp.blogspot.com/-JE-zVpHjbKo/WMdcg40zONI/AAAAAAABChg/7xNrmKSTMb85O06UH7nskGTMARLJO83bgCLcB/s450/hoikujo_ninka.png

娘は都内の認可保育園に通っています 出来る限り、引越し後も認可保育園に通わせたいです ただ、ここが苦労したポイントなので諸々まとめていきたいと思います

保育園の入園日

保育園は月の途中からの入園は出来ず、毎月1日に受け入れる方針でした*4

転園申請の提出期限

転園申請を出して、審査を実施し問題がなければ転園ができます 審査は転園も入園も関係なく毎月10日に申請書が役所に届いている人が対象になります そのことから転園したい月の前月10日には申請書を出しておく必要があります

申請資料

これは自治体によるので自分で調べる必要がありますが、僕の引越し先の自治体では、住民票 or 引越してくることがわかる書類(賃貸借契約書 or 不動産売買契約書)が必要でした 不動産売買契約書については売却ではなく、購入の不動産売買契約書が必要です(売却では引越してくる証拠にはならないので)

保育園に在籍できる条件

保育園に在籍できる条件には 「月の1日に住民票が保育園のある自治体にあること」 というものがあります

総合すると

上記の条件と我が家のスケジュールを総合して考えると
売却の引き渡しが6月半ばであったため6/1には転園したい
6/1に転園するのであれば、5月10日には役所に転園申請資料が到着してなければならない
転園申請資料には住民票 or 引越してくることがわかる書類(賃貸借契約書 or 不動産売買契約書)が必要
5月はGWがあるので4月末には資料の準備が完了している必要がある

ということになります

賃貸契約

4月までに家を買うのはさすがに不可能なので賃貸契約をすることにしました
賃貸契約をすると、6月の引き渡しまではローン返済が続くので家賃とローン返済が2重で請求されます
しかも5月末までは住むこともないです
ここまでしたところで、審査次第では転園できないかもしれません
とはいえ認可保育園に入れるためなら背に腹は代えられないのです

購入活動スタート

保育園の転園問題のスケジュールは見えたので家の購入を進めていきます
このスケジュールでやっていくので僕の中では注文住宅の選択肢は消えて行きました

物件の情報収集

コロナ禍になり、僕のように住まいを見直す世帯が増えたことにより不動産業界はかつてないバブル状態です
建売住宅が土地の段階で買われてくような状態でした

常にsuumoも見ながら生活しつつ、3つくらいの不動産屋に探してもらいました

保育園の転園の承認

このタイミングで転園の承認がおりてすごく安心したのを覚えています

購入物件に出会う

そんな中、6月完成予定の理想的な立地の建売住宅を見つけることができました
しかも、普段は注文住宅を作っている工務店がモデルハウスとして作った建売だったの、で設備も仕様も豪華でおしゃれでした
中々土地も建売も出ない立地だったので即決しました

売買契約

購入の売買契約を行います 重要事項説明等を受けつつたくさんの印鑑を押していきます 売却よりは難しくないです

引越し

ここで賃貸に引越して、保育園に通いはじめました

住宅ローンの本審査

こちらは事前審査で通っていた中で条件が良かった銀行で行いました
ネット銀行がよかったのですが、3ヶ月くらい審査にかかる状況でスケジュールに合わないため地方銀行に出しました

そして無事に審査は通過しました

金銭消費貸借契約

www.athome.co.jp

銀行からお金を借りて物件を買うための契約です

内覧

完成した物件を最終チェックします ホームインスペクションを入れる方もいるので検討するのもいいと思います

www.jshi.org

個人的には、契約前にホームインスペクションを入れられない日本の不動産購入フローには違和感を覚えます 思想が売り主に寄りすぎてバランスバグってる気がしますね 欧米では契約前にホームインスペクションを実施するのが一般的です

引き渡し

f:id:gettergot:20210818171527j:plain

f:id:gettergot:20210818171602j:plain

無事、家が引き渡されました!

まとめ

今回、不動産の買い替えを経験してみてかなりハードルが高いなと思いました
特に保育園の転園周りはかなり大変でした
今回は運と入念な準備のおかげで、なんとか無事に買い替えを行うことができました
買い替え期間は何かと有給を使うことも多く、チームメンバーにはご迷惑をお掛けしました
サポートありがとうございました
さすがにもう買い換えることもないだろうとは思いつつ、なかなか出来ない経験かと思うのでいい経験が出来たと思ってます

売却益が出てる関係で今年の確定申告はめんどくさい予感はしますが、忙しい時期は終わったので、のんびりと海の近くの生活を楽しんでいきたいと思います

*1: 注意事項: 僕は不動産について素人なので個人的見解の記事です。マンションがいいとか戸建てがいいとか賃貸がいいとかはそれぞれのライフスタイルに依ると思うので特に触れないです

*2:ちなみに買い替え特約をつけることも出来たりしますが、断られることもあったりします。詳しくは調べて見てください

*3:レインズ(REINS)とは「Real Estate Information Network System」の頭文字「REINS」を示したもので、不動産物件情報交換のためのネットワークシステムを指します

*4:僕の自治体の話であり自治体によるのかもしれません

FAXで障害報告を受信したときにSlack通知する

やりたいこと

FAXで障害報告が送られて来たときにSlackに内容を通知してエンジニアが気がつけるようにしたい

前置き

Kyashでサーバサイドエンジニアをやっている @uncle__ko です
色々なシステムやベンダーと接続をしていると障害報告がFAXで来るなんてこともあります
現実問題、FAXで連絡が来たところでリモートワークなこのご時世では気がつくことは困難
しかも休日や夜間にもFAXで届くこともあります
レガシーだと嘆いていても仕方がないのでSlackに通知させて気がつけるようしたいと思います

Zapier

zapier.com

ポチポチするだけで色々なツールを繋ぐことが出来る便利なやつです

KyashではZapierを使って様々なものを自動化しています

blog.kyash.co

Zap Summary

Trigger: FAX.PLUS

FAX PLUSとはオンライン上でFAXを送受信できるサービス FAX PLUSはSlackやZapier等色々なツールのintegrationがあって便利です

www.fax.plus

  • Trigger Eventは New Fax Received

Action: Filter by Zapier

  • Only continue if... にfilterを追加します
    • 今回はベンダーの送信番号でFilterしてます
      f:id:gettergot:20210402173844p:plain
      action_filter

Action: Slack

  • Action Eventは Send Channel Message
  • fileはFAX PLUSで受信したものを設定します
    f:id:gettergot:20210402174035p:plain
    action_slack

FAX PLUSは色々なツールとのintegrationが豊富でとてもよいのですが
admin権限がないとZapier側のTriggerやActionの連携が出来なくて無駄にハマったりしたので気を付けましょう
admin権限以外でも連携出来るようになるともっと使いやすくなるのになと思ったりしました

実行結果

f:id:gettergot:20210402174414p:plain
slack_notify

Happy !!

Goのhello worldを小さくする

これはなに?

ふとGolangのbinaryを小さく出来ないかなと思い立ったのでhello worldをスリムにしていこうと思う

hello world

Golangでのhello worldはこちら

package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}

普通にbuild

これを普通にbuildするとどうなるか

# go build hello.go
# ll hello
-rwxr-xr-x 1 root root 2008801 Feb 20 02:01 hello*
# ll -h hello
-rwxr-xr-x 1 root root 2.0M Feb 20 02:01 hello*

2MBもあるようだ

-ldflags

とりあえずldflagsをつけてbuildしてみる
-sはデバッグのために使われるシンボルテーブルを生成しないようにする

# go build -o hello_ld -ldflags="-s" hello.go
# ll hello_ld
-rwxr-xr-x 1 root root 1433600 Feb 20 02:05 hello_ld*
# ll -h hello_ld
-rwxr-xr-x 1 root root 1.4M Feb 20 02:05 hello_ld*

だいぶ削れた
けどまだ削れる気がする

elfファイルを見てみる

とりあえず消せそうなsectionあるか見てみる

# readelf -S hello_ld
There are 14 section headers, starting at offset 0x1c8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000401000  00001000
       000000000008bf19  0000000000000000  AX       0     0     16
  [ 2] .rodata           PROGBITS         000000000048d000  0008d000
       000000000004f550  0000000000000000   A       0     0     32
  [ 3] .shstrtab         STRTAB           0000000000000000  000dc560
       000000000000008a  0000000000000000           0     0     1
  [ 4] .typelink         PROGBITS         00000000004dc600  000dc600
       0000000000000c68  0000000000000000   A       0     0     32
  [ 5] .itablink         PROGBITS         00000000004dd268  000dd268
       0000000000000050  0000000000000000   A       0     0     8
  [ 6] .gosymtab         PROGBITS         00000000004dd2b8  000dd2b8
       0000000000000000  0000000000000000   A       0     0     1
  [ 7] .gopclntab        PROGBITS         00000000004dd2c0  000dd2c0
       000000000006b67c  0000000000000000   A       0     0     32
  [ 8] .go.buildinfo     PROGBITS         0000000000549000  00149000
       0000000000000020  0000000000000000  WA       0     0     16
  [ 9] .noptrdata        PROGBITS         0000000000549020  00149020
       000000000000d0d8  0000000000000000  WA       0     0     32
  [10] .data             PROGBITS         0000000000556100  00156100
       0000000000007050  0000000000000000  WA       0     0     32
  [11] .bss              NOBITS           000000000055d160  0015d160
       000000000001b870  0000000000000000  WA       0     0     32
  [12] .noptrbss         NOBITS           00000000005789e0  001789e0
       0000000000002768  0000000000000000  WA       0     0     32
  [13] .note.go.buildid  NOTE             0000000000400f9c  00000f9c
       0000000000000064  0000000000000000   A       0     0     4
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

.gosymtabとか.note.go.buildidは削れそうだけどスズメの涙だろうなと思いつつ削除

# readelf -x13 hello_ld

Hex dump of section '.note.go.buildid':
  0x00400f9c 04000000 53000000 04000000 476f0000 ....S.......Go..
  0x00400fac 6c656668 6e656c37 74716b75 72507254 lefhnel7tqkurPrT
  0x00400fbc 5335634a 2f794f50 62666478 494d4357 S5cJ/yOPbfdxIMCW
  0x00400fcc 6147766b 66435546 792f7a4f 78425866 aGvkfCUFy/zOxBXf
  0x00400fdc 4f7a632d 505f4d41 43306d56 675f2f6e Ozc-P_MAC0mVg_/n
  0x00400fec 6e50494a 6c6c532d 632d4443 6842386b nPIJllS-c-DChB8k
  0x00400ffc 674c4e00                            gLN.

# strip -R .gosymtab hello_ld
# strip -R .note.go.buildid hello_ld
# ./hello_ld
hello, world
# ll hello_ld
-rwxr-xr-x 1 root root 1430712 Feb 20 02:11 hello_ld*
# ll -h hello_ld
-rwxr-xr-x 1 root root 1.4M Feb 20 02:11 hello_ld*

これ以上むりそうに感じる...
upxを使えば削減できるだろうが負けた気分になるので他の手を使いたい

medium.com

fmt.Printlnやめればいいのでは?

syscallを直接よべばもう少し削減できるんじゃね?(なんの意味もないけど減らしたい気持ちになった)
と思ったのでsyscallを呼んでみることにした

golang.org

package main

import (
    "syscall"
    "unsafe"
)

func main() {
  p := []byte("hello, world\n")
  var _p0 unsafe.Pointer
  _p0 = unsafe.Pointer(&p[0])
  syscall.Syscall(syscall.SYS_WRITE, uintptr(1), uintptr(_p0), uintptr(len(p)))
}

buildしてみる

# go build -o hello_sys -ldflags='-s' hello_sys.go
# ll hello_sys
-rwxr-xr-x 1 root root 851968 Feb 20 02:17 hello_sys*
# ll -h hello_sys
-rwxr-xr-x 1 root root 832K Feb 20 02:17 hello_sys*

めっさ小さい!

もうちょっとだけ悪あがき

# strip -R .gosymtab hello_sys
# strip -R .note.go.buildid hello_sys
# strip -s hello_sys

消せそうなのを消していく

# ll hello_sys
-rwxr-xr-x 1 root root 849880 Feb 20 02:19 hello_sys*
# ll -h hello_sys
-rwxr-xr-x 1 root root 830K Feb 20 02:19 hello_sys*

個人的にはこれが限界かな
そもそもsyscallなんて普通呼ばないから意味もないし自己満足だけど
容量は減ってなんだかうれしくなったからいいか

CとGoのhello world時のシステムコール数

特に何かの役に立つわけでもないが取得する機会があったのでここに置いておく

環境

# cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.1 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

C

cc version

# cc --version
cc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

hello.c

# include <stdio.h>

int main(void) {
  puts("hello, world");
  return 0;
}

build

# cc -o hello hello.c

strace

# strace -T -o hello.log ./hello
hello, world
# cat hello.log
execve("./hello", ["./hello"], 0x7ffdcbe878f8 /* 9 vars */) = 0 <0.000859>
brk(NULL)                               = 0x559b13f8e000 <0.000379>
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc73131180) = -1 EINVAL (Invalid argument) <0.000110>
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory) <0.000475>
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000190>
fstat(3, {st_mode=S_IFREG|0644, st_size=12932, ...}) = 0 <0.000137>
mmap(NULL, 12932, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc2d788e000 <0.000163>
close(3)                                = 0 <0.000144>
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000161>
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832 <0.000148>
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 <0.000147>
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 <0.000147>
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 <0.000147>
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0 <0.000145>
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc2d788c000 <0.000145>
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784 <0.000240>
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32 <0.000238>
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68 <0.000168>
mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fc2d769a000 <0.000205>
mprotect(0x7fc2d76bf000, 1847296, PROT_NONE) = 0 <0.000234>
mmap(0x7fc2d76bf000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7fc2d76bf000 <0.000232>
mmap(0x7fc2d7837000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7fc2d7837000 <0.000406>
mmap(0x7fc2d7882000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fc2d7882000 <0.000219>
mmap(0x7fc2d7888000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fc2d7888000 <0.000319>
close(3)                                = 0 <0.000345>
arch_prctl(ARCH_SET_FS, 0x7fc2d788d540) = 0 <0.000117>
mprotect(0x7fc2d7882000, 12288, PROT_READ) = 0 <0.000168>
mprotect(0x559b12344000, 4096, PROT_READ) = 0 <0.000122>
mprotect(0x7fc2d78bf000, 4096, PROT_READ) = 0 <0.000121>
munmap(0x7fc2d788e000, 12932)           = 0 <0.000170>
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0 <0.000128>
brk(NULL)                               = 0x559b13f8e000 <0.000123>
brk(0x559b13faf000)                     = 0x559b13faf000 <0.000119>
write(1, "hello, world\n", 13)          = 13 <0.000618>
exit_group(0)                           = ?
+++ exited with 0 +++

call数

# cat hello.log | wc -l
36

Go

go version

# go version
go version go1.13.8 linux/amd64

hello.go

package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}

build

# go build hello.go

strace

# strace -T -o hello.log ./hello
hello, world
# cat hello.log
execve("./hello", ["./hello"], 0x7fff18c90078 /* 9 vars */) = 0 <0.000343>
arch_prctl(ARCH_SET_FS, 0x55e0f0)       = 0 <0.000023>
sched_getaffinity(0, 8192, [0, 1])      = 16 <0.000028>
openat(AT_FDCWD, "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", O_RDONLY) = 3 <0.000050>
read(3, "2097152\n", 20)                = 8 <0.000029>
close(3)                                = 0 <0.000012>
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f655058f000 <0.000032>
mmap(0xc000000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xc000000000 <0.000025>
mmap(0xc000000000, 67108864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xc000000000 <0.000030>
mmap(NULL, 33554432, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f654e58f000 <0.000025>
mmap(NULL, 2164736, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f654e37e000 <0.000029>
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f654e36e000 <0.000025>
mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f654e35e000 <0.000030>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=825706741}) = 0 <0.000024>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=825791841}) = 0 <0.000042>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=825877341}) = 0 <0.000029>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=825952241}) = 0 <0.000061>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=826186341}) = 0 <0.000042>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=826313741}) = 0 <0.000052>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=826501941}) = 0 <0.000095>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=826726741}) = 0 <0.000035>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=826836541}) = 0 <0.000042>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=826922741}) = 0 <0.000034>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=827010941}) = 0 <0.000034>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=827093841}) = 0 <0.000042>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=827173641}) = 0 <0.000027>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=827258941}) = 0 <0.000046>
rt_sigprocmask(SIG_SETMASK, NULL, [], 8) = 0 <0.000028>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=827907041}) = 0 <0.000025>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=828037441}) = 0 <0.000056>
sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0 <0.000032>
sigaltstack({ss_sp=0xc000002000, ss_flags=0, ss_size=32768}, NULL) = 0 <0.000026>
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000031>
gettid()                                = 5133 <0.000026>
rt_sigaction(SIGHUP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000036>
rt_sigaction(SIGHUP, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000180>
rt_sigaction(SIGINT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000029>
rt_sigaction(SIGINT, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000025>
rt_sigaction(SIGQUIT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000022>
rt_sigaction(SIGQUIT, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000024>
rt_sigaction(SIGILL, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000027>
rt_sigaction(SIGILL, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGTRAP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000127>
rt_sigaction(SIGTRAP, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000021>
rt_sigaction(SIGABRT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGABRT, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000021>
rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGBUS, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGFPE, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGFPE, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGUSR1, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGUSR1, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGSEGV, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGUSR2, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGUSR2, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGPIPE, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGPIPE, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGALRM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGALRM, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGTERM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGTERM, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGSTKFLT, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGSTKFLT, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGCHLD, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGCHLD, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGURG, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGURG, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGXCPU, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGXCPU, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGXFSZ, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGXFSZ, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000023>
rt_sigaction(SIGVTALRM, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGVTALRM, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGPROF, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGPROF, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGWINCH, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGWINCH, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000021>
rt_sigaction(SIGIO, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGIO, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGPWR, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGPWR, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGSYS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGSYS, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRTMIN, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_1, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_2, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_2, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_3, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_3, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_4, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_4, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_5, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_5, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_6, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_6, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_7, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000134>
rt_sigaction(SIGRT_7, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_8, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_8, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_9, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_9, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_10, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_10, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_11, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_11, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_12, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_12, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_13, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_13, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000021>
rt_sigaction(SIGRT_14, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_14, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_15, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_15, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_16, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_16, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_17, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_17, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_18, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_18, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000020>
rt_sigaction(SIGRT_19, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000020>
rt_sigaction(SIGRT_19, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000017>
rt_sigaction(SIGRT_20, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000118>
rt_sigaction(SIGRT_20, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000017>
rt_sigaction(SIGRT_21, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_21, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_22, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_22, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_23, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_23, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_24, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_24, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_25, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_25, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_26, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_26, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_27, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_27, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000094>
rt_sigaction(SIGRT_28, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_28, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_29, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_29, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_30, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_30, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000017>
rt_sigaction(SIGRT_31, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_31, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigaction(SIGRT_32, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0 <0.000016>
rt_sigaction(SIGRT_32, {sa_handler=0x4551e0, sa_mask=~[], sa_flags=SA_RESTORER|SA_ONSTACK|SA_RESTART|SA_SIGINFO, sa_restorer=0x455310}, NULL, 8) = 0 <0.000016>
rt_sigprocmask(SIG_SETMASK, ~[], [], 8) = 0 <0.000017>
clone(child_stack=0xc000042000, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM) = 5134 <0.000090>
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000014>
rt_sigprocmask(SIG_SETMASK, ~[], [], 8) = 0 <0.000017>
clone(child_stack=0xc000044000, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM) = 5135 <0.000064>
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000007>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=837951841}) = 0 <0.000021>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=838005841}) = 0 <0.000025>
rt_sigprocmask(SIG_SETMASK, ~[], [], 8) = 0 <0.000016>
clone(child_stack=0xc00003e000, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM) = 5136 <0.000037>
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000016>
futex(0xc000032848, FUTEX_WAKE_PRIVATE, 1) = 1 <0.000041>
futex(0xc0000324c8, FUTEX_WAKE_PRIVATE, 1) = 1 <0.000039>
clock_gettime(CLOCK_MONOTONIC, {tv_sec=3985, tv_nsec=838769641}) = 0 <0.000021>
readlinkat(AT_FDCWD, "/proc/self/exe", "/root/testgo/hello", 128) = 18 <0.000043>
fcntl(0, F_GETFL)                       = 0x8002 (flags O_RDWR|O_LARGEFILE) <0.000018>
futex(0xc0000324c8, FUTEX_WAKE_PRIVATE, 1) = 1 <0.000028>
mmap(NULL, 262144, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f654e31e000 <0.000013>
fcntl(1, F_GETFL)                       = 0x8002 (flags O_RDWR|O_LARGEFILE) <0.000015>
fcntl(2, F_GETFL)                       = 0x8002 (flags O_RDWR|O_LARGEFILE) <0.000013>
write(1, "hello, world\n", 13)          = 13 <0.000051>
exit_group(0)                           = ?
+++ exited with 0 +++

call数

# cat hello.log | wc -l
171

Locust + AWS ECS(Fargate)で負荷試験環境を作った話

これはなに?

Kyash Advent Calendar 2020 21日目の記事です。

今回はKyashに導入した負荷試験環境について紹介します。

経緯

Kyashでは決済や入金周りの性能試験は実施していましたが、シナリオベースの負荷試験など不十分な部分がありました。
そのためキャンペーンなどで通常より多くのアクセスが発生した場合に、

  • どのくらい耐えれるのか
  • どこのサーバがボトルネック
  • 適切なスケール具合はどの程度か

などの情報で曖昧な部分がありました。

そこでシナリオベースな負荷試験を実施することにより、
事前にどの程度までスケールすればどの程度の負荷まで耐えられるのかを把握し
適切なタイミングでの適切なスケーリングを実現することで
システムを安定稼働をさせていきたいと考えました。

ツールの選定

負荷試験を実施するにあたり主な要件は下記でした。

  • loginしないといけないのでsession管理ができるもの
  • ある程度のシナリオを組めるもの
  • CloudServiceとのintegrationが容易なもの

色々なツールを比較し下記理由からLocustを使うことにしました。

  • 情報が多いのでいろいろ参考にできそう
  • ecsやfargateとの親和性も高そう
  • 分散実行がサポートされているためスケールも容易そう

locust.io

また、インフラ部分は基本的にAWSを使用しているのでAWS ECS(Fargate) でworkerを簡単にスケールできるようにしました。

構成

f:id:gettergot:20201203105802p:plain
kyash_locust

workerはFargateのtask数を変更することでスケール可能にしてます。
オートスケールにしなくても負荷が出ないなら手でtask数あげれば問題ないかなという判断です。
また、LocustのUIはOneLoginで認証(OIDC)を行ってます。
さらにDatadogでLocustの監視をしながらworkerの負荷状況もdashboardで確認できるようになっています。

Locustの構成

Locust側のDirectory構成は下記

$ tree -I '__pycache__' --prune locust/
locust/
├── common
│   ├── __init__.py
│   ├── auth.py
│   ├── const.py
│   └── env.py
├── locustfiles
│   └── 20203Q
│       └── locustfile.py
└── task_set
    ├── __init__.py
    ├── const.py
    └── wallet_tab.py

基本的には、どこをシミュレートして負荷を掛けるかによってファイルを別けています。

例としてtop画面に負荷を掛けるシナリオだと、SequentialTaskSetを使用してTop画面を表示するのにCallされるAPIを登録しています。 (コードはあくまで例なので実際は少し違います)

docs.locust.io

locustfile.py

from locust import HttpUser, constant
from task_set import wallet_tab

class KyashUser(HttpUser):
    tasks = {wallet_tab.WalletTabTaskSet: 1}
    wait_time = constant(0.01)

wallet_tab.py

from locust import SequentialTaskSet, task
from common import auth
from task_set.const import *


class WalletTabTaskSet(SequentialTaskSet):
    token = ""

    def on_start(self):
        auth_task = auth.AuthTask(self)
        self.token = auth_task.login(const.EMAIL, const.PASSWORD)

    @task
    def device(self):
        res = self.client.post("/v1/hoge", json={"device": {
            "token": const.DEVICE_TOKEN}}, headers={auth.AuthTask.getHeader(): self.token})
        print(res.text)

.....

LocustのUI

f:id:gettergot:20201208104632p:plain
locust_UI
負荷試験を開始するときは上記画面から値を設定します。

Number of users to simulateには、作成するクライアント数を指定します。 Hatch rateには、クライアント数の増加ペースを指定します。

どのくらい負荷を掛けているのかが簡易的ではありますが、ビジュアライズされていてわりと見やすい感じになってます。

f:id:gettergot:20201204101202p:plain
UI

まとめ

負荷試験を実施することで、課題であった、

  • どのくらい耐えれるのか
  • どこのサーバがボトルネック
  • 適切なスケール具合はどの程度か

を把握して戦略的にスケールすることが可能になりました。
また、いつの日かCI環境でリリースフローの中に組み込みたいという夢もあるので、引き続き負荷試験環境も整備していければと思ってます。

Kyash Advent Calendar 2020 の他の記事もぜひ読んでみてください。

goenvでGOPATHの間にversionが入ってしまう場合の対処法

Golangの環境構築をしていてハマった箇所があったので解決法を書いておく

goenvを使ってGolangをインストールした時にGOPATHにversionが入ってしまって困った

goenv/CHANGELOG.md at master · syndbg/goenv · GitHub

Changed goenv's bootstrap (eval $(goenv init -)) now to call goenv-sh-rehash --only-manage-paths. This means that it'll export and manage GOROOT and GOPATH env vars

上記にあるように goenv2.0.0beta6 以上だとGOPATHが $HOME/go/$GO_VERSION のようになってしまう

Add management of env variable GOPATH that can be disabled with env var GOENV_DISABLE_GOPATH=1, when calling goenv-sh-rehash (goenv rehash when eval $(goenv init -) was previously executed).

対処法も載っていて下記のように環境変数を設定すればよい

export GOENV_DISABLE_GOPATH=1