ECSでunicornを動かすときの注意点

ECSはタスクが停止すると、各コンテナにまず SIGTERM を送ります。 しかし、Railsと一緒によく使われるunicornSIGTERM を受け取ると、gracefulに終了してくれません。

graceful shutdownさせるためには QUITシグナルをmasterプロセスに対して送る必要があります。 Signal handling

aws.amazon.com

デフォルトの停止シグナルは SIGTERM ですが、これは Dockerfile に STOPSIGNAL ディレクティブを追加することによってオーバーライドできます。この停止シグナルは、シャットダウンの命令をアプリケーションに通知します。

やり方としてはshell scriptのハンドラーを書いたり、STOPSIGNAL ディレクティブをDockerfileに追加する方法もあると思いますが、今回の場合は unicorn.rbSignal.trap して自身のmasterプロセスに対してシグナルを送るのが一番簡単だと思います。

before_fork do |server, worker|
  Signal.trap 'TERM' do
  Process.kill 'QUIT', Process.pid
end

参考

Nginx の limit_req

Nginxの流量制限する場合に使用される limit_req の使い方について調べてみました。 NGINX Rate Limiting の簡単なメモになります。

limit_req

Nginxのrate limitでは、パケット通信の帯域制限などで使われているleaky bucket algorithmに従ってキューイングします。

基本的な設定

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    ...

    server {

        ...

        location /search/ {
            limit_req zone=one burst=5;
        }

ひとつずつ設定を見ていきます。

まず、limit_req_zone directive で httpリクエストに対する制限を定義できます。 ここではあくまでも定義しているだけなので、実際に実装する場合は location ディレクティブ内などに設定を書く必要があります。 limit_req_zone は key、zone、rateという3つのパラメーターを持ちます。

key

その右にある $binary_remote_addr はクライアントのIPアドレスを表現するNginxの変数です。

zone

アクセス制限のかけたURLに対して頻繁にアクセスしてくるそれぞれのIPアドレスを記録するためのメモリについての設定です。 これはNginxのワーカープロセス間で共有されます。 目安として1メガバイトには約16,000のIPアドレスを記録することが可能です。 Nginxが新しいIPアドレスを記録する必要があるときにメモリが枯渇している場合は、最も古いIPアドレスを削除し、メモリを開放します。 それでもまだスペースが足りず不十分な場合は503を返します。 また、上記の枯渇問題を防ぐためにNginxは新しいIPアドレスを登録するたびに過去60秒間に使用されていない最大2つのエントリを削除します。

rate

最大のリクエストレートを設定します。 上記の例では、1秒あたり1リクエストを超えることができないことを表していますが、 実際にはNginxはミリ秒の粒度でリクエストを追跡するため100msごとに1つのリクエストに対応します。

burst

burst パラメーターをzoneで指定されたrateを超えてクライアントが実行できるリクエストの数を定義できます。 burstを超えたリクエストは全てキューイングされます。

例: 20スロットが空でリクエストが21個飛んできた場合は、1つをアップストリームに流す。 次に残りの20リクエストをキューイングし、100ミリ秒ごとにデキューする。

burst with nodelay

nodelay が設定された場合は空きスロットがない状態は503、 空きスロットがある場合はリクエストは処理され、スロットが使用済みになります。

例: 20スロットが空でリクエストが21個同時に飛んできた場合は、21リクエスト全て処理をし、20個のスロットを確保する。 その後、100ミリ秒ごとにデキューして消化。

まとめ

burstなし、burstあり、burstあり+nodelayの3パターンあります。

本番環境で使う場合は nodelay をつける場合が多そう。

参考

Goで自作パッケージを作るときのちょっとした動作確認

SongmuさんのGo Conference 2019 Summerでの発表資料に

パッケージ内の関数の動作確認とかは gore -pkg . が異常に便利です

と書いてあって、使ってみたら結構便利だったので備忘録として残しておきます。

  -pkg string
        the package where the session will be run inside

合わせて読みたい

RFC違反のメールアドレス

日本の大手キャリアであるdocomoauが2009年ごろまでに作成したメールアドレスはRFCに準拠していないものも作成可能でした。 今日はRFC違反のメールアドレスを考慮した正規表現Amazon SES、SendGridの対応状況について調べてみました。

RFC違反のメールアドレスを考慮した正規表現

https://qiita.com/sakuro/items/1eaa307609ceaaf51123

HTML5input[type=email] と同じ正規表現をかけると良いです。

Amazon SES

blog.serverworks.co.jp

検証結果を見る限り、RFC違反のメールアドレスでも送れるものと送れないものがあるみたいです。

SendGrid

2013年頃はRFC違反のメールアドレスでも一部送れるようだったが、2019年に仕様が代わり ローカルパートをダブルクォーテーションで囲まないといけないらしいです。

合わせて読みたい

git 備忘録

任意のコミットで変更されたファイルの一覧を出力する

通常のコミット: git diff-tree --no-commit-id --name-only -r <tree-ish>

マージコミット: git log -m -1 --name-only --pretty="format:" <commit-id>

mainブランチにマージ済みのブランチを削除する

git branch --merged main | grep -v '^\*' | xargs -n 1 git branch -d

フラーをやめて、READYFORに入る。

1月末でフラーを退職して、2月頭からREADYFORで働いています。

Ruby / Rails をしっかり書くのは初めてなので、色々触りながら「う〜ん、これはいかんな・・・」とか試行錯誤してる毎日です。

転職の決めて

VP of Engineeringの伊藤さん からスカウトをいただき、転職しました。

個人的にはガバナンスが効いており、CTOやVPoE、EMなどの技術経営やマネジメントをするポジションが確立されているというのが大きな決め手となりました。

エンジニアの人数が一年で2倍に!CTOに聞く、サービスの未来を描ける技術組織のつくり方|READYFOR note  

想定外のイベントを前向きに楽しむ

READYFORで働くことは紛れもなく偶然なんですが、 前向きにチャレンジすることで自分にとってプラスとなる機会をたくさんつくろうと思います。

思いがけない出来事がキャリアに大きな影響を与えることを計画的偶発性理論(Planned Happenstance)等といったりするらしいですが、 現職ではこれを体験してみたいなと思っています。偶然の出会いやご縁こそ大切にしたいものです。

社会的な問題を解決する

コロナ基金のプロジェクトがREADYFOR史上最大の支援額になったりしており、サービスのインパクトと使命感を感じています。 創業当初のRailsのコードベースが結構でかいんですが、今のシステムにそぐわないものも当然あるわけでそういう箇所に アグレッシブに手を加えていけたらいいなと思っています。

これから

という感じでREADYFORはエンジニアリング組織に対して理解のある会社です。

エンジニアが毎月入社しており、急成長段階で面白いフェーズだと思っています。 一皮剥けるように頑張ります。

皆様READYFORをよろしくお願いいたします。

Go で multipart/form-data を扱ってみる

この記事は フラー Advent Calendar 2020 の24日目の記事です。 23日目は @tmshr06 さんで

Firestore Emulator を使って GitHub Actions でテストを動かしてみる - Qiita

でした。


今回は Multipart について勉強したときのメモを残しておきます。

multipart とは何か

電子メールにASCII文字データ以外の任意のデータを含めるMIMEの拡張仕様の一つです。 具体的にはHTTPヘッダーに以下のような フォーマットの Content-Type で表現します。

Content-Type: multipart/form-data; boundary=c7245ee369df31f524686275eb89381b30581b1ca5557de2453f9f8cf66c

※ boundary は一例

Content-Type は multipart/{subtype} の形式を取ります。 boundary は複数のコンテンツの区切りを示す文字列です。ランダムな英数文字列*1が用いられることが多いです。

それぞれのsubtypeを以下の用途にしたがって使い分けられることが多いです。

multipart/alternative(内容の同じテキストメールとHTMLメールのように、形式は違っても内容は同じもの)
multipart/mixed(個々の要素はそれぞれ異なる)
multipart/related(関連するものを表現)
multipart/form-data(HTMLフォーム、POSTメソッドから送信する)

www.softel.co.jp

上記の記事が参考になりました。

multipart/form-data とは何か

先述した multipart のsubtype の一つです。

これはHTTPのPOSTメソッドでmultipartで表現したものを送信したいときに使います。

Go の標準パッケージにも multipart パッケージが存在しますが、 HTTP での利用を想定したものです*2APIはそれなりに揃っているので、読み込みや書き込みはこれを使えば大体のことはできると思います。

multipart package (クライアントのサンプルコード)

  file, _ := os.Open(filePath)
  defer file.Close()

  body := &bytes.Buffer{}
  writer := multipart.NewWriter(body)
  part, _ := writer.CreateFormFile("file", filepath.Base(file.Name()))
  io.Copy(part, file)
  writer.Close()

  r, _ := http.NewRequest("POST", "http://example.com", body)
  r.Header.Add("Content-Type", writer.FormDataContentType())
  client := &http.Client{}
  client.Do(r)

上記のように表現することができます。

NewWriter は全体のマルチパートメッセージを持つためのWriterで、生成するときにrandomにboundary(base16)が決定します。

パートを追加するときは、CreatePart() を使って追加することができますが、 より高レベルのAPIとして、CreateFromFile()CreateFromField() が提供されており、これらのメソッドを使うことで各パートのContent-Typeの設定などを省略することが可能です。

送信する前に最後のboundaryを挿入するために writer.Close() を呼び出す必要があります。


明日は okuzawats さんで 「Androidアプリ開発入門2020」 です。お楽しみに。

合わせて読みたい

RFC 7578 - Returning Values from Forms: multipart/form-data

RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types

multipart - The Go Programming Language

Go言語のio.Pipeでファイルを効率よくアップロードする方法. io.Pipeと非同期処理を活かし、ファイルアップロードのメモリ使用量を減らす | by James Kirk | Eureka Engineering | Medium

Go言語のmime/multipartパッケージでファイルをアップロードしましょう | by James Kirk | Eureka Engineering | Medium

golang multipart file upload example

html - HTMLマルチパートフォーム- “boundary”文字列の最大長?

*1:具体的なフォーマットはここでは割愛させていただきますが、

RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types

に記載されています。

*2:FormDataContentType() を呼び出したときに Content-Type は必ず multipart/form-data になります。