ECSでunicornを動かすときの注意点
ECSはタスクが停止すると、各コンテナにまず SIGTERM
を送ります。
しかし、Railsと一緒によく使われるunicornは SIGTERM
を受け取ると、gracefulに終了してくれません。
graceful shutdownさせるためには QUITシグナルをmasterプロセスに対して送る必要があります。 Signal handling
デフォルトの停止シグナルは SIGTERM ですが、これは Dockerfile に STOPSIGNAL ディレクティブを追加することによってオーバーライドできます。この停止シグナルは、シャットダウンの命令をアプリケーションに通知します。
やり方としてはshell scriptのハンドラーを書いたり、STOPSIGNAL
ディレクティブをDockerfileに追加する方法もあると思いますが、今回の場合は
unicorn.rb
に Signal.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違反のメールアドレス
日本の大手キャリアであるdocomo、auが2009年ごろまでに作成したメールアドレスはRFCに準拠していないものも作成可能でした。 今日はRFC違反のメールアドレスを考慮した正規表現とAmazon SES、SendGridの対応状況について調べてみました。
RFC違反のメールアドレスを考慮した正規表現
https://qiita.com/sakuro/items/1eaa307609ceaaf51123
HTML5の input[type=email]
と同じ正規表現をかけると良いです。
Amazon SES
検証結果を見る限り、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 をしっかり書くのは初めてなので、色々触りながら「う〜ん、これはいかんな・・・」とか試行錯誤してる毎日です。
俺はやるぞ!何かを!
— shmokmt / Shoma Okamoto (@shmokmt) 2020年10月9日
転職の決めて
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メソッドから送信する)
上記の記事が参考になりました。
multipart/form-data とは何か
先述した multipart のsubtype の一つです。
これはHTTPのPOSTメソッドでmultipartで表現したものを送信したいときに使います。
Go の標準パッケージにも multipart パッケージが存在しますが、 HTTP での利用を想定したものです*2。 APIはそれなりに揃っているので、読み込みや書き込みはこれを使えば大体のことはできると思います。
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言語の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 になります。