How to use Parcel with Phoenix framework 1.3

I like Parcel bundler which is zero configuration web app bundler tool.

I’ll show you how to use Parcel bundler on Phoenix framework 1.3.

Create a phoenix project

mix phx.new your_project
cd your_project

It installs brunch. But it’s easier to let it install brunch. Because there will be package.json and directories.

Update assets/package.json

Then let’s configure package.json. It’s under assets directory. You can replace it to like this.

{
  "repository": {},
  "license": "MIT",
  "scripts": {
    "deploy": "parcel build js/app.js --out-dir ../priv/static/assets",
  },
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html"
  },
  "devDependencies": {
    "parcel-bundler": "^1.9.7"
  }
}

Then yarn.

Actually, I’m not sure if I need to set scripts here. Because there is a setting section in config/dev.exs.

Update config/dev.exs

In config/dev.exs, you will find Endpoint config.

Update watchers like this. This will be ran when you run phoenix.

config :your_project, YourProject.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  check_origin: false,
  watchers: [node: ["node_modules/parcel-bundler/bin/cli.js",
                    "watch", "js/app.js",
                    "--out-dir", "../priv/static/assets",
                    cd: Path.expand("../assets", __DIR__)]]

Update lib/your_project_web/endpoint.ex

In lib/your_project_web/endpoint.ex, you will find Plug.Static config.

Add assets directory. Then phx.server serves files under priv/static/assets directory.

plug Plug.Static,
  at: "/", from: :vegan, gzip: false,
  only: ~w(assets favicon.ico robots.txt)

Start phoenix server

iex -S mix phx.server

Then you will see a compiled JavaScript file at priv/static/js which you configured in dev.exs.

Cleanup OSX ~/Library/Developer/

~/Library/Developer/ is getting fatter and fatter. Even after Xcode is updated, there still are old iOS images and Core simulators.

I’ll show you what and how you can delete these files.

Delete old archives

You can delete old Archives, under this directory:

~/Library/Developer/Xcode/Archives/

Delete old CoreSimulators

There are many olc CoreSimulator devices under ~/Library/Developer/CoreSimulator/Devices

You can run this to delete these devices.

xcrun simctl delete unavailable

Delete old iOS DeviceSupports

There are many old iOS DeviceSupport under this directory. You can delete old iOS versions of directories.

~/Library/Developer/Xcode/iOS DeviceSupport

Change key repeat interval on OSX

We can change the intervals of key repeat and delay until repeat at the System preferences > Keyboard. But you may think it too slow even if you set the minimum values.

We can set faster value with Karabiner.

Or we can set it at a Terminal. My recommendation is following :)

$ defaults write NSGlobalDomain KeyRepeat -int 1
$ defaults write NSGlobalDomain InitialKeyRepeat -int 12

First one is the repeat interval when you keep pressing a key. Second is delay until starting to repeat. 1 is not milliseconds. I’m not sure about the value, but maybe 1 is equivalent to 16 milliseconds.

So the config will get you KeyRepeat -> 16ms, Delay until repeat -> 200ms.

If it doesn’t work, try following. (try true and false)

$ defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false

How to use DynamoDB Local on CircleCI

We can use DynamoDB Local on our local machines. Also we can use it on CircleCI. All you have to do is put configurations to circle.yml

circle.yml example

This is an example for testing Rails application.

machine:
  timezone:
    Asia/Tokyo

  ruby:
    version: 2.3.1

  java:
    version: openjdk7

  environment:
    DYNAMODB_ENDPOINT: http://localhost:8456

database:
  override:
    - cp config/database.yml.ci config/database.yml
    - bundle exec rake db:create db:migrate

dependencies:
  cache_directories:
    - "~/DynamoDBLocal"
  post:
    - "[ -d ~/DynamoDBLocal ] || (mkdir ~/DynamoDBLocal; cd ~/DynamoDBLocal; curl -s -L http://dynamodb-local.s3-website-us-west-2.amazonaws.com/dynamodb_local_latest.tar.gz | tar xz)"
    - "java -Xms1024m -Xmx1024m -Djava.library.path=~/DynamoDBLocal/DynamoDBLocal_lib -jar ~/DynamoDBLocal/DynamoDBLocal.jar --port 8456":
        background: true

xcodeproj を CONFLICT しづらくする mergepbx

Xcode を使って開発していると、.xcodeproj が、論理的には CONFLICT していないのに Xcodeのバージョンアップ等で激しく CONFLICT することがある。 mergepbx を使うと、理不尽に CONFLICT されるケースをある程度解消してくれる。複数人で開発している時は入れておいて助かることが多い。

1年くらい使ってるけど、特に問題が起きたことはないです :-)

Install

brew install mergepbx

Usage

以下の2ファイルに設定を追記(プロジェクト単位でもglobalでもOK)すれば、あとは git merge や git rebase とか通常の操作で mergepbx を使ってくれるようになります。

.gitconfig

下記を追加。

[merge "mergepbx"]
  name = Xcode project files merger
  driver = mergepbx %O %A %B

.gitattributes

下記を追加。

*.pbxproj merge=mergepbx

How to change webpack config between envirionments

How to change webpack config between environments?

When using React, the document says we have to set environment variable NODE_ENV to production. And I also want to use UglifyJsPlugin only on production build.

Note: by default, React will be in development mode. To use React in production mode, set the environment variable NODE_ENV to production (using envify or webpack’s DefinePlugin). A minifier that performs dead-code elimination such as UglifyJS is recommended to completely remove the extra code present in development mode.

https://facebook.github.io/react/downloads.html

Use process.env.NODE_ENV

I found a good example :-)

webpack issues#868

Then I can switch config using process.env.NODE_ENV.

Example

This is simple webpack.config.js to use React with ES2015.

Use

Then you can use like these

on development

./node_modules/webpack/bin/webpack.js --watch -d --progress --color

on production build

NODE_ENV=production ./node_modules/webpack/bin/webpack.js --progress --color

TTTAttributedLabelでUILabelの一部をリンクにする

利用規約に同意してログインする」的な UILabel で、タップしたら遷移するようにする。TTTAttributedLabel を使う。

Podfile に TTTAttributedLabel を追加

# Podfile

pod 'TTTAttributedLabel'

InterfaceBuilder

UILabel を配置するのと同じように、適当に配置する。Class を、 TTTAttributedLabel にしておく。

Linkをコードで設定する

var label : TTTAttributedLabel!

的なプロパティが設定してあるという前提で、文字列は「利用規約に同意してログインする」になってるとします。

Linkの設定

class SomeViewController: UIViewController, TTTAttributedLabelDelegate {

// ...

label.delegate = self
        
label.linkAttributes = [
    kCTForegroundColorAttributeName: UIColor.grayColor(),
    NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleNone.rawValue
]
        
legalLabel.activeLinkAttributes = [
    kCTForegroundColorAttributeName: UIColor.lightGrayColor(),
]
        
if let text = legalLabel.text {
    let termsRange = NSString(string: text).rangeOfString("利用規約")
    legalLabel.addLinkToURL(termsUrl, withRange: termsRange)
}

とする。 linkedAttributes でリンクが設定された文字列の見た目を規定して、activeLinkAttributes でリンクがタップされている間の文字列の見た目を設定できる。

delegateメソッドの設定

// MARK: TTTAttributedLabelDelegate
    
func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithURL url: NSURL!) {
    if url == termsUrl {
        // ここで某かの処理をすればOK
        performSegueWithIdentifier("goToTerms", sender: nil)
    }
}

WKWebViewでnetworkActivityIndicatorVisibleを表示

(追記) iOS10 になって色々変わっていたので、書き直した。

networkActivityIndicatorVisible (iPhoneの上部のステータスバーで読込中にぐるぐるさせるやつ) を表示する方法。UIWebView は、html,js,css,画像などの区別がつかなくて結構たいへんだったけど、そういうの気にせずだいぶ楽に実装できる。

Controller に直接書くパターン

KVOを監視する

Controller に直接書く場合は、viewDidLoad とかで、addObserver する。

var webView : WKWebView!

的なプロパティが設定してあるという前提で

webView.addObserver(self, forKeyPath: "loading", options: NSKeyValueObservingOptions.New, context: nil)

とする。

controller がなくなるタイミングで

webView.removeObserver(self, forKeyPath: "loading")

を呼ぶのを忘れずに。

KVOの変化にフックさせる

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if keyPath == "loading" {
        UIApplication.sharedApplication().networkActivityIndicatorVisible = webView.loading
    }
}

loading状態が変わると、このメソッドが呼ばれるので、そのタイミングで webView.loading (BOOL) がどうなっているのかを見るだけで良い。

WKWebView のサブクラスを作るパターン

WKWebView を使って、特に何もせず networkActivityIndicatorVisible だけを変えたい場合は、下記のようなサブクラスを作って WKWebView を呼んでるところに置き換えれば良い。

import UIKit
import WebKit

class CustomWebView: WKWebView {
    init() {
        super.init(frame: CGRectZero, configuration: WKWebViewConfiguration())
        self.translatesAutoresizingMaskIntoConstraints = false

        addObserver(self, forKeyPath: "loading", options: NSKeyValueObservingOptions.New, context: nil)
    }
    
    deinit {
        removeObserver(self, forKeyPath: "loading")
    }
    
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if keyPath == "loading" {
            UIApplication.sharedApplication().networkActivityIndicatorVisible = loading
        }
    }
}

使い方

全画面表示で、特定のURLを読み込むというもの。

let webView = CustomWebView()

view.addSubview(webView)
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[webView]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["webView": webView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[webView]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["webView": webView]))

webView.loadRequest(NSURL(string : "https://memo.ecp.plus/"))

AJAXでファイルダウンロード

バイナリでもテキストでも、AJAX でリクエストしたら JavaScript で受けることになるので、ブラウザにファイルをダウンロードさせるように処理を作る必要がある。

Blob オブジェクトはファイルに似たオブジェクトで、immutable な生データです。データを表す blob は必ずしも JavaScript ネイティブなフォーマットではありません。File インターフェースは Blob を基礎にしており、その機能を継承する一方で、ユーザのシステム上のファイルをサポートするための機能を拡張しています。

https://developer.mozilla.org/ja/docs/Web/API/Blob

とのことです。レスポンスから Blob オブジェクトを作って、createObjectURL すれば、ダウンロード可能なURLを生成する事ができます。

よくありがちな、CSVデータを管理画面からダウンロードさせたい、みたいなパターンを jQuery + ES6 で書いた場合の例。

IEの場合、IE10以上であれば msSaveBlob という、Blob をダウンロードするという機能を使うことが出来る。それ以外のブラウザは、createObjectURL を使って URL を生成したら、a タグの href にセットしてそれにクリックイベントを発火させるという方法で実現できます。

$.ajax({
  url: 'https://example.com/api/admin/users.csv',
  type: 'GET',
}).done((data, status, jqXHR) => {
  let downloadData = new Blob([data], {type: 'text/csv'});
  let filename = 'users.csv'

  if (window.navigator.msSaveBlob) {
    window.navigator.msSaveBlob(downloadData, filename); // IE用
  } else {
    let downloadUrl  = (window.URL || window.webkitURL).createObjectURL(downloadData);
    let link = document.createElement('a');
    link.href = downloadUrl;
    link.download = filename;
    link.click();
    (window.URL || window.webkitURL).revokeObjectURL(downloadUrl);
  }
}).fail((data, status, jqXHR) => {
  alert('OMG!');
});

HamlでTextarea(Rails)

Rails + Haml で textarea を出力しようとすると、textarea 内に表示しようとしたテキストまでインデントされてしまう。

find_and_preserve というヘルパーメソッドを使えば、中身のインデントは保持されるようだ。

= find_and_preserve(f.text_area)

Simple Form を使ってる場合も、同様にいける。

= find_and_preserve(f.input :comment, as: :text)

参考URL: How do I stop Haml from indenting the contents of my pre and textarea tags?

MacでIEの確認をリモートで行う

IEの確認で、Azure の RemoteIE というのが知らないうちに出てた。これだと、手元に virtual machines のイメージを置いておかなくて良いので、ディスク容量の節約になる。

登録すれば無料で使えるようだが、inactive な時間が10分続くか、 active な状態でも60分経過すると切断される制限がある。短時間の確認なら問題無さそうだが、長時間の確認は繋ぎ直す必要がありそう。

まずは、RemoteIE で Register for access して、自分の Region を選ぶ。日本なら East Asia が良い。

Mac で使う場合は、Microsoft Remote Desktop アプリを App Store からダウンロードして、Azure RemoteApp というところをクリックすると、ログイン画面になるので、ログインする。 すると、InternetExplorer(email: iewebeco@microsoft.com) という Invitations があるので、チェックを入れると自動的に起動する。いきなり IE の画面が開いているので検証できる。

kswapd0 プロセスが CPU を食ってた

Load average が高いので、top でプロセスを見たら kswapd0 というプロセスが CPU を使いまくってた。

メモリがスワップアウトしたときに、ディスクに書き込んだりするプロセスらしいけれど、貧弱なサーバだとその処理が重くて Load average が上がってサーバが遅くなって…のループでつらそうだった。

vm.drop_caches の値を変えることで、メモリ内にあるキャッシュを削除して、このプロセスがやるべきことをなくしてあげれば CPU は解放されるようだ。

例えば、ページキャッシュだけ解放するときは、下記のようにする。sync をして、強制的にキャッシュの内容をディスクに書き込んだ後に実行します。

sudo sync
sudo /sbin/sysctl -w vm.drop_caches=1

数字は、下記の中から適切なものを選ぶ必要がある。

  • [0] 初期値
  • [1] ページキャッシュ解放
  • [2] dentry、inode 解放
  • [3] ページキャッシュ、dentry、inode 解放

Linux におけるメモリの解放

なお、0 を指定するとエラーになって設定が出来ないのだが、よく分かっていない。

$ sudo /sbin/sysctl -w vm.drop_caches=0
sysctl: setting key "vm.drop_caches": Invalid argument
vm.drop_caches = 0

Ubuntuを14.10->15.10へアップデート

Ubuntuを14.10->15.10へアップデートした。

sudo vi /etc/update-manager/release-upgrades

として、Prompt=ltsとなっているところを、Prompt=normalに変更する。

[DEFAULT]
Prompt=normal

その後、下記コマンドを実行すればアップグレードが対話形式で始まるので、表示されるメッセージを見ながら進めればよい。

sudo do-release-upgrade

AWS API Gateway で ifconfig API を作ってみる

サーバが自分自身のグローバルIPアドレスを調べるとき面倒なことがよくあるので、API Gateway を使って ifconfig のようなことをする。単純に、リクエストしてきたIPアドレスを返すだけ。(こういうサービスは他にもあるけど、レスポンスが遅めだったり、信用できるか分からなかったりするので、自分で作るのが安全そう)

curl https://ip.ecp.plus

としたら自分のIPアドレスが返るというのがゴール。ブラウザで見ても同じ。ちなみに↑はもう動いているのでご自由にお使いください。

最初、API Gateway -> Lambda として、Lambda に IPアドレスを渡して、Lambda はそれを返して、API Gateway はレスポンスをそのままスルーする。というのをやったが、実は Lambda なしで出来たようだ。

はじめ Lambda を使ったバージョンをやったので、両方書く。

API Gateway の設定

Create Method

URLは短くしたいので、ドメイン直下をGETしたときに返したい。 / に対して Create Method で GET を指定する。

Method Request は認証などしないのでそのまま。

/ - GET - Method Execution

API Gateway にアクセスしてきた IP アドレスは、下記のようにして取れる。

{
  "ipaddress" : "$context.identity.sourceIp"
}

のだが、API Gateway のデフォルトで割り振られる URL は非常に長く、自分のドメインで使いたい。Custom Domain Names を使うためには、TLS の証明書が必要なのだが、お手軽さが無いのでやめた。既に動いている Caddy でリクエストを転送することにした。

ただ、そうするとリクエスト元のIPアドレスが Caddy が動いているサーバの IPアドレスになってしまうため、ヘッダーの X-Forwarded-For を見ることにする。ここの先頭に、オリジナルのIPアドレスが入ってる。

{
  "ipaddress" : "$input.params().header.get('X-Forwarded-For')"
}

Lambda

ipaddress として渡ってきたデータの中から、一番最初のIPアドレスを返すだけの処理を書く。

exports.handler = function(event, context) {
    context.succeed([event.ipaddress.split(/\s*,\s*/)[0], "\n"].join(''));
};

/ - GET - Integration Response

再び API Gateway に戻ってきて、レスポンスの加工をする。一見、Output passthrough で良さそうだが、これだとダブルクオーテーションで囲まれたものが返ってしまう。

Content-Type: application/json で Mapping Template として下記のようにする。

$input.path('$')

これでAWS側の実装は完了。

Caddy の設定

ip.ecp.plus に来たものを API Gateway に飛ばす。API Gateway 側で GET / を叩くために、転送先の末尾にスラッシュを忘れず付ける必要がある。

ip.ecp.plus {
  proxy / https://EXAMPLE.execute-api.ap-northeast-1.amazonaws.com/prod/
}

とりあえずここまでで完成。

だが、Lambda がどう見てもあまり意味がなさそうだったので、API Gateway だけで出来ないか見ていたところ、開発中に使う用途と思っていた Mock Integration を使えば出来た。

API Gateway の設定(再)

/ - GET - Method Execution

Integration type として Mock Integration を選択。Mapping template として

{
  "statusCode" : 200
}

のようなものが定義されてるが、これがないと勝手にエラーにされてしまうのでそのままにしておく。

/ - GET - Integration Response

どうやら、Response 側でも、リクエストで渡ってきた値がそのまま取れるようなので、こちらにIPアドレスを返す処理を書けば良い。

#set($ipAddresses = $input.params().header.get('X-Forwarded-For').split(','))
$ipAddresses[0]
#set($dummy = "dummy")

最後の行でよくわからないことをしているが、レスポンスの末尾に改行を入れたいためこうしている。”\n” をどうにか入れたかったが、うまく行かずバッドノウハウ的な解決方法になっている…。本当は良い方法があるはずだ。

ここまでで完成。

尤も、こんな単純なものなら Caddy 動かしてるサーバで処理しろよという感じだけれど、最近 API Gateway にハマっているので作ってみた。API Gateway 単体で動かないと、Caddy のサーバが落ちたときに使えなくて激しく意味がないが、Custom Domain Names の設定までやるのは面倒であった。Route53 使ってたらシームレスに連携出来たりすれば良いのに、と思った。

(追記)CloudFront を使ってサーバレスに出来た

その後、Caddy の代わりに CloudFront を使えば、TLS証明書も無料で、好きなドメインで出来ることが分かった。

CloudFront の画面から、Create Distribution -> Web の Get Started で、作成画面へ入る。

Origin Domain Name は、API Gateway の Invoke URL を指定する。https:// を付けても勝手に外してくれるので、URLコピペで良い。stage名は消す。つまりドメイン部分だけを記述する。 Origin Path は、stage名を入れる。 prod なら /prod とする。

あとは項目を読みながら設定すれば良い。CloudFront は独自ドメインのTLS証明書が無料なので、とてもありがたい。注意点としては、 CloudFront にキャッシュされると前に実行した人のIPアドレスが返ってしまうので、キャッシュさせないようにしなければいけない。 Behaviors のタブで、初期から存在しているものを編集して、Object Caching : Customizeとして、Minimum TTL : 0, Maximum TTL : 0, Default TTL : 0 とすれば良い。

これで、自己管理するインフラ無しで、当初のやりたかったことが出来た。

API Gateway から Export した Swagger 用の YAML

Let's Encrypt を Caddy で簡単に使う

Caddy という web server がデフォルトTLSというポリシーで、Let’s Encrypt を簡単に設定出来て、証明書の自動更新をしてくれるようだったので使ってみた。Let’s Encrypt は無料でTLS証明書使えるサービスです。

VultrのVPSを使ってみたら、DocumentsにCaddyのインストールについて載ってたので知りました。Vultr の Tokyo リージョン良さそうですね。

Caddy のインストール

https://caddyserver.com/download から、ダウンロードするとバイナリが落ちてくるので、それを実行するだけです。バイナリ生成時に、Select Features として、追加機能を入れる事ができます。

Caddy の設定

/path/to/caddy.conf

hoge.example.com {
  tls hoge@example.com
}

最小構成だと、ドメインに対して自分のメールアドレスを設定したら動くみたいです。ecpplus.net のドメインだと動いたけれど、 ecp.plus のドメインだと動かなかった。あまり調べていないけれど、Let’s Encrypt 側で何かやる必要がありそう。

Caddy の起動

80, 443 ポートをListenするので、setcap しておくか、sudo での実行が必要です。

sudo setcap cap_net_bind_service=+ep /path/to/caddy
/path/to/caddy --conf /path/to/caddy.conf -agree=true

-agree は、Agree to Let’s Encrypt Subscriber Agreement で、-emailは Default Let’s Encrypt account email address とのことです。

これで、 https://hoge.example.com としてサーバが立ち上がります。http でアクセスしても自動的に https に遷移します。

Caddy のコマンド引数

$ caddy --help
Usage of caddy:
  -agree=false: Agree to Let's Encrypt Subscriber Agreement
  -ca="https://acme-v01.api.letsencrypt.org/directory": Certificate authority ACME server
  -conf="": Configuration file to use (default=Caddyfile)
  -cpu="100%": CPU cap
  -email="": Default Let's Encrypt account email address
  -grace=5s: Maximum duration of graceful shutdown
  -host="": Default host
  -http2=true: HTTP/2 support
  -log="": Process log file
  -pidfile="": Path to write pid file
  -port="2015": Default port
  -quiet=false: Quiet mode (no initialization output)
  -revoke="": Hostname for which to revoke the certificate
  -root=".": Root path to default site
  -version=false: Show version

Caddy の機能

https://caddyserver.com/docs に、ドキュメントがあります。

気になった機能

  • markdown で、Caddy 自体が Markdown を HTML に変換してくれる。テンプレートの指定もできる。デフォルトで入ってるのは面白いかも。
  • gitで、git push で自動的にサイト更新出来るので、markdown と合わせてお手軽なサイトに便利そう。
  • gzip は使えるぽいですが、キャッシュ系の機能はまだないっぽいので、現状だと別で用意する必要がありそうです。
  • fastcgi で指定すれば、裏でWordpressとか動かしても大丈夫そうです。
  • proxy でリバースプロキシの設定もできるので、Rails とか裏においても大丈夫そうです。
  • websocket で、WebSocket のコネクションが張られたときに実行するコマンドが指定できる。
  • startup, shutdown で、起動・停止時に任意のコマンドを実行できる。例えば Rails のアプリケーションサーバを立ち上げるとか。

安いTLS証明書使ってたけど、失効したら乗り換えても良いかもという感じがしました。

Rails5 で使う場合

ActionCable 試してないけど、Websocket の設定も出来るので多分いけそう。とりあえず、 /assets /system だけ Caddy で返して、バックエンドは Unicorn とか Puma とかを 9292 番ポート動かしてる場合の設定例。proxy_header を適切に設定しないと、 redirect_to とかさせたときにおかしなことになるので注意。

example.com {
  gzip
  tls caddy@example.com
  log /var/log/access.log

  root /var/www/rails_root/current/public

  proxy / localhost:9292 {
    proxy_header Host {host}
    proxy_header X-Real-IP {remote}
    proxy_header X-Forwarded-Proto {scheme}
    except /assets /system
  }
}

Daemonize

caddy 自身ではデーモンとして起動出来ないようだ。 supervisord を使って daemon にしてみた。

/etc/supervisord.d/caddy.ini

[program:caddy]
command=/usr/local/bin/caddy -conf="/etc/Caddyfile" -agree=true
directory=/var/www/rails_root/current/public
autostart=true
user=root
redirect_stderr=true
stdout_logfile=/var/log/caddy.log
stderr_logfile=/var/log/caddyerr.log

/etc/supervisord.conf

[supervisord]
minfds=4096

caddy を起動するとき、少なくとも ulimit -n 4096 にしろと言われるので、supervisord の方で指定しておくと良い。