備忘録、はじめました。

作業したこと忘れないようにメモっておきます。

Nginx (reverse proxy) + Phoenix でschemeを強制的にhttpsにする

memo: たどり着くのに時間が掛かったで残しておくことにした.

課題

  • oauth認証ueberauthのライブラリを使っていた
  • callback urlを動的に生成させるときに Plug.Connschemeを参照している
  • backend側のサーバーはhttpで動かしたい
  • それだと Plug.Connのschemeはhttpなので、https のcallback_urlを生成できない

解決方法

Plug.SSLのプラグを使って強制的にhttpsschemeを変更しました.

以下、phoenix側のconfigの設定です.

config :my_web, MyWeb.Endpoint,
  url: [host: "localhost", scheme: "https", port: 443],
  force_ssl: [
    host: nil,
    rewrite_on: [:x_forwarded_proto]
  ]

上記のように :force_ssl の設定を追加します. (参考はここ Using SSL – Phoenix v1.3.0-rc.2.)

Forcing requests to use SSL:

In many cases, you’ll want to force all incoming requests to use SSL by redirecting http to https. This can be accomplished by setting the :force_ssl option in your endpoint. It expects a list of options which are forwarded to Plug.SSL. By default it sets the “strict-transport-security” header in https requests, forcing browsers to always use https. If an unsafe request (http) is sent, it redirects to the https version using the :host specified in the :url configuration.

記述を読むと、endpointのconfigの:force_sslの設定がPlug.SSLのオプションになるようです.

また、nginx側は以下の設定が必要です.

        ...

        location / {

            ...

            proxy_set_header X-Forwarded-Proto $scheme; # ここの一行が必要

            proxy_pass http://backend/;
        }

        ...

これは、Plug.SSLの仕様で :rewrite_on に設定したヘッダー名がrewriteされるためです.

:rewrite_on - rewrites the scheme to https based on the given headers

なので、そのヘッダーをproxy_set_headerしています.

(apacheなどの他のproxyサーバーでも同様な設定を入れれば期待する挙動になると思います.)

Plug.Connの確認

%Plug.Conn{
  adapter: {Plug.Adapters.Cowboy.Conn, :...},
  assigns: %{},
  before_send: [#Function<0.116269836/1 in Plug.CSRFProtection.call/2>,
   #Function<4.117387578/1 in Phoenix.Controller.fetch_flash/2>,
   #Function<0.58261320/1 in Plug.Session.before_send/2>,
   #Function<1.112466771/1 in Plug.Logger.call/2>,
   #Function<0.61641163/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>],
  body_params: %{},
  cookies: %{
    "_my_web_session_id" => "***"
  },
  halted: false,
  host: "localhost",
  method: "GET",
  owner: #PID<0.467.0>,
  params: %{},
  path_info: [],
  path_params: %{},
  port: 80,
  private: %{
    ***.Router => {[], %{}},
    :phoenix_action => :index,
    :phoenix_controller => ***.PageController,
    :phoenix_endpoint => ***.Endpoint,
    :phoenix_flash => %{},
    :phoenix_format => "html",
    :phoenix_layout => {***.LayoutView, :app},
    :phoenix_pipelines => [:browser],
    :phoenix_router => ***.Router,
    :phoenix_view => ***.PageView,
    :plug_session => %{"_csrf_token" => "gnjVVSwCXhyfv4pu4rBfVA=="},
    :plug_session_fetch => :done
  },
  query_params: %{},
  query_string: "",
  remote_ip: {172, 21, 0, 6},
  req_cookies: %{
    "_my_web_session_id" => "***"
  },
  req_headers: [
    {"host", "localhost"},
    {"x-forwarded-for", "172.21.0.1"},
    {"x-real-ip", "172.21.0.1"},
    {"x-forwarded-proto", "https"},
    {"cache-control", "max-age=0"},
    {"upgrade-insecure-requests", "1"},
    {"user-agent",
     "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"},
    {"accept",
     "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"},
    {"referer", "https://localhost/"},
    {"accept-encoding", "gzip, deflate, br"},
    {"accept-language", "ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7"},
    {"cookie",
     "_my_web_session_id=***"}
  ],
  request_path: "/",
  resp_body: nil,
  resp_cookies: %{},
  resp_headers: [
    {"cache-control", "max-age=0, private, must-revalidate"},
    {"x-frame-options", "SAMEORIGIN"},
    {"x-xss-protection", "1; mode=block"},
    {"x-content-type-options", "nosniff"},
    {"x-download-options", "noopen"},
    {"x-permitted-cross-domain-policies", "none"}
  ],
  scheme: :https,
  script_name: [],
  secret_key_base: :...,
  state: :unset,
  status: nil
}

scheme: の部分が https であることが確認できました.

参考

erlangでsnowflakeを実装してみた

はじめに

erlang製のsnowflakeほしいなと言う思いで色々探していて、思い描く感じのがなかったので自作してみた話。

リンク: GitHub - tkyshm/esnowflake: Uniq id generator based on Twitter's snowflake in Erlang

実装

snowflakeアルゴリズムに関しては以下を参照していただければわかるはず。

www.slideshare.net

snowflakeのnodeは最大1024台立ち上げることができ、machine_id に0~1023を割り当てることが出来ます。 esnowflakeでは、この設定をworker_min_max_idで設定できるようにし、 1つのesnowflakeで複数のmachine_idを持てるように設計しています。

以下、アプリケーションの設定で起動するworker数を設定できます。

[
    {esnowflake, [{worker_min_max_id, [0, 1]}]}
]

使い方

% IDを生成
> Id = esnowflake:generate_id().
896221795344384

% IDを複数生成
> esnowflake:generate_ids(2).
[896498611015681,896498611015680]

% IDからUnixTime(ミリ秒)取得
> esnowflake:to_unixtime(Id).
1509193995927

% IDからUnixTime(秒)取得
> esnowflake:to_unixtime(Id, seconds).
1509193995

% UnixTime(ミリ秒)からID(最小値)を知りたいとき
> esnowflake:unixtime_to_id(1509193995927).
896221795319808

% UnixTime(秒)からID(最小値)を知りたいとき
> esnowflake:unixtime_to_id(1509193995, seconds).
896217907200000

generate_idのベンチ

# (1個生成を100,000回施行した平均値)
----------------------------------------------------
2017-10-29 16:03:27.671
b_generate_id   100000  4564.13864 ns/op


----------------------------------------------------
2017-10-29 16:03:28.703
b_generate_id   232502 op/sec

%%% esnowflake_SUITE ==> bench.b_generate_id: OK


# (100個生成を10,000回施行した平均値)
----------------------------------------------------
2017-10-29 16:03:29.700
b_generate_ids_100  10000   96834.8342 ns/op


----------------------------------------------------
2017-10-29 16:03:30.702
b_generate_ids_100  686 op/sec

%%% esnowflake_SUITE ==> bench.b_generate_ids: OK

おまけ(elixirからつかう)

  # mix.exsのdeps
  defp deps do
    [
      {:esnowflake, "~> 0.2.0"}
    ]
  end
elixir/sample » iex -S mix
Erlang/OTP 20 [erts-9.0.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> t = :esnowflake.generate_id()
20433515594121216

iex(2)> t = :esnowflake.to_unixtime(id) 
1513852072018

iex(3)> t2 = :esnowflake.to_unixtime(id, :seconds)
1513852072

iex(4)> :esnowflake.unixtime_to_id(t2, :seconds)
20433608900608000

iex(5)> :esnowflake.generate_ids(10) 
[20434393545850889, 20434393545850888, 20434393545850887, 20434393545850886,
 20434393545850885, 20434393545850884, 20434393545850883, 20434393545850882,
 20434393545850881, 20434393545850880]

感想

1台1台にworker数の設定をするのは面倒な気がするので、worker idを動的に割り当てできる方法は考え中です。 何かいい方法を思いついて、時間があれば追加で作ろうかと思ってます。

台数制限があるのにworker数が取得できないのは問題あるので、取得できるようにする予定。

おまけのは元々elixirからも使うのを考えてたのでついでに載せておきました。

参照URL

Twitterのsnowflakeについて

GitHub - kayac/go-katsubushi: ID generator server

erlangでfluentdクライアントのOTPアプリケーションを実装した

はじめに

アプリケーションにログ、データを集めたりするのにしばしば利用される Fluentd というソフトウェアのクライアントをErlang/OTPで実装した話です。

本家のerlangのfluentdのクライアント の実装を眺めていて、 gen_eventで実装されていたため、過負荷や障害時などを考慮したい場合はひと工夫を入れる必要がありそうと思い、 今回本家と異なるビヘイビアで実装してみました。

何故gen_event使わなかったのか?

結論から言うと、別システムにデータを送信するようなケースでのgen_eventの利用は避けたかったからです。 何故避けたのかという説明をするために、簡単にgen_eventの話をします。

gen_eventは一つのイベントマネージャーと0個以上のイベントハンドラーで構成され、 マネージャーにメッセージを送信する(notify, sync_notify関数)と、 マネージャに登録したハンドラーの処理が走るという感じのビヘイビアーです。

f:id:tkyshm:20171216231642p:plain

gen_eventの便利なところは、流したいイベントは同じだけれど別目的でそれぞれ処理をしたいというのが簡単に実装できることが挙げられます。 例えば、ログのコンソール出力、ファイル書き込み、エラーのアラート発報を一つのgen_eventでまとめることができます。

ただ、gen_eventを扱う場合に気をつけなければいけない点があります。 まず、これらの複数ハンドラーは並列実行されない点です。そのため、一つのハンドラーの処理に時間がかかるとハンドラー全体に影響が出ます。 次に、1プロセスにメッセージが集中するので過負荷に弱いです。 事前に設計するイベントマネージャの流量が多いと分かっている場合、 そのマネージャーに処理が大幅に遅れるようなハンドラーを一つだけでも追加するというのは避けたほうが良いでしょう。

fluentdとのやり取りをそのままハンドラーに書き込むと、 レイテンシが大きくなったり、fluentdダウン時などの影響でシステム全体が遅くなる可能性があります。

なので、今回その点を注力したfluentdのクライアントアプリケーションを別ビヘイビアで実装することにしました。

続きを読む

Erlangのバージョン管理ツール自作

前はkerlを使って複数バージョンを持ってたけど、buildのときのoption指定とかが思う感じに指定できなかったので自前で用意した話。

GitHub - tkyshm/erlch: Erlang version manager

やりたいことは、erlのバージョンを気軽に切り替えられることと、インストールの手間を1コマンドでできるようにすることです。

helpを使うと以下の感じで出てきます。

 erlch command list:

        help: display how to use erlch command.
         use: switches your erlang version.
       fetch: caches erlang versions for completions.
        list: lists your erlang versions.
     install: install erlang.
   uninstall: uninstall your installed erlang.

fetchコマンドはzshまたはbashのcompletionファイルを設定してもらった前提で使ってもらうと便利なものとなってます(ほとんど解説ないので結構不親切ですが…)。 fetchするとインストール可能なバージョンをtab補完できるようになります。ちなみに、バージョン情報はgithubのrepoのtagからとってきてます。

補完すると以下な感じです。

~/.erlch ❯❯❯ erlch install OTP
 -- available versions --
OTP-17.0                            OTP-17.5.6.3                        OTP-18.2.4.1                        OTP-19.1.4                          OTP_17.0-rc2                      
OTP-17.0.1                          OTP-17.5.6.4                        OTP-18.3                            OTP-19.1.5                          OTP_R13B03                        
OTP-17.0.2                          OTP-17.5.6.5                        OTP-18.3.1                          OTP-19.1.6                          OTP_R13B04                        
OTP-17.1                            OTP-17.5.6.6                        OTP-18.3.2                          OTP-19.1.6.1                        OTP_R14A                          
OTP-17.1.1                          OTP-17.5.6.7                        OTP-18.3.3                          OTP-19.2                            OTP_R14B                          
OTP-17.1.2                          OTP-17.5.6.8                        OTP-18.3.4                          OTP-19.2.1                          OTP_R14B01                        
OTP-17.2                            OTP-17.5.6.9                        OTP-18.3.4.1                        OTP-19.2.2                          OTP_R14B02                        
OTP-17.2.1                          OTP-18.0                            OTP-18.3.4.2                        OTP-19.2.3                          OTP_R14B03                        
OTP-17.2.2                          OTP-18.0-rc1                        OTP-18.3.4.3                        OTP-19.3                            OTP_R14B04                        
OTP-17.3                            OTP-18.0-rc2                        OTP-18.3.4.4                        OTP-19.3.1                          OTP_R15A                          
OTP-17.3.1                          OTP-18.0.1                          OTP-18.3.4.5                        OTP-19.3.2                          OTP_R15B                          
OTP-17.3.2                          OTP-18.0.2                          OTP-19.0                            OTP-19.3.3                          OTP_R15B01                        
OTP-17.3.3                          OTP-18.0.3                          OTP-19.0-rc1                        OTP-19.3.4                          OTP_R15B02                        
OTP-17.3.4                          OTP-18.1                            OTP-19.0-rc2                        OTP-19.3.5                          OTP_R15B03                        
OTP-17.4                            OTP-18.1.1                          OTP-19.0.1                          OTP-19.3.6                          OTP_R15B03-1                      
OTP-17.4.1                          OTP-18.1.2                          OTP-19.0.2                          OTP-19.3.6.1                        OTP_R16A_RELEASE_CANDIDATE        
OTP-17.5                            OTP-18.1.3                          OTP-19.0.3                          OTP-19.3.6.2                        OTP_R16B                          
OTP-17.5.1                          OTP-18.1.4                          OTP-19.0.4                          OTP-20.0                            OTP_R16B01                        
OTP-17.5.2                          OTP-18.1.5                          OTP-19.0.5                          OTP-20.0-rc1                        OTP_R16B01_RC1                    
OTP-17.5.3                          OTP-18.2                            OTP-19.0.6                          OTP-20.0-rc2                        OTP_R16B02                        
OTP-17.5.4                          OTP-18.2.1                          OTP-19.0.7                          OTP-20.0.1                          OTP_R16B03                        
OTP-17.5.5                          OTP-18.2.2                          OTP-19.1                            OTP-20.0.2                          OTP_R16B03-1                      
OTP-17.5.6                          OTP-18.2.3                          OTP-19.1.1                          OTP-20.0.3                          OTP_R16B03_yielding_binary_to_term
OTP-17.5.6.1                        OTP-18.2.4                          OTP-19.1.2                          OTP-20.0.4                                                            
OTP-17.5.6.2                        OTP-18.2.4.0.1                      OTP-19.1.3                          OTP_17.0-rc1    `

$HOME/.erlch 以下はこんな感じ。

~/.erlch ❯❯❯ tree -L 2 .
.
├── bin -> /home/tkyshm/.erlch/versions/20.0.4/bin
├── configure_opts
├── repo
│   └── otp
└── versions
    ├── 20.0
    └── 20.0.4

6 directories, 1 file

useを使ってバージョンを切り替えます。

~/.erlch ❯❯❯ erlch use 20.0
switched erlang version 20.0.

切り替えるとbinのシンボリックリンク先が変更されるだけです。なので、binのpathを通しておきます。

Erlangプロセスのmessage_queue_dataの影響について見てみた

前回の「Erlangのメッセージングでバッファを使う - 備忘録、はじめました。」でバッファがないときに大量のメッセージを送受信すると非常に遅くなることが分かりました。 この劣化の要因は大量のメッセージを一斉に送信していることです。

なので、プロセスのメッセージパッシングの流れについて調べてみました。

  1. 送信されたメッセージの大きさの計算
  2. メッセージに対して十分の領域をアロケーション
  3. メッセージのペイロードをコピー
  4. メタデータを含めたメッセージの箱(message container)をアロケーション
  5. “受信プロセスのメッセージキュー”にそのメッセージの箱を挿入

プロセスがメッセージを送受信するときのパフォーマンスは、上記のステップ2が大きく影響を受けます。

ステップ2は、デフォルトではプロセスのロックを取得し、ヒープの空き領域の計算、割当をします。 この処理の間、受信プロセスはメッセージを待ち続けることになります。

erlang 19.0から、ステップ2のアロケーション戦略をプロセス単位で変更できるようになりました。 アロケーション戦略を変更することで、メインへのロックなしにメッセージを送信できるようになるようです。 変更方法は以下の感じです。

process_flag(message_queue_data, on_heap|off_heap)

今回は、この「message_queue_data」について少し調べてみた話です。

続きを読む

Erlangのメッセージングでバッファを使う

Erlang Angerの3章、3.3.2あたりで過負荷についての対処法として、キューのバッファを作るのが効果的という話が載っていたので、実際にどのくらい差が出るのか簡単に計測してみた話です。

問題設定:

buffer,erlang,message passing

この二つの設定に対して、1,000〜500,000メッセージをbenchサーバーから並列で送信します。

PCの環境:

続きを読む

go-i18nで必要なjsonをgoのソースコードから生成するgoi18n-parser

モチベーションとして、

  • 多言語対応をしたい
  • goのソースコードから自動で翻訳ファイルを作成したい
  • シンプルでいい(複雑な機能は要らない)

があり、最小限で作ってみたので紹介します。

github.com

goi18n-parser

goi18n-parser ではソースコードの解析に go/ast を使用しています。

go/astを使うと、goのソースコードを抽象構文木にして中身を解析することが出来ます。

今回解析したい内容は、

  • 翻訳関数の使われている部分

となります(この「翻訳関数」とは便宜的に書いてるだけです)。 goのソースコード内に翻訳関数が使われていて、その翻訳関数の引数が翻訳対象となるメッセージのIDとして記憶されていきます。

具体的には次のようなコードです:

    ...

    fmt.Println(SomeStruct.T("server_error_something"))

    return fmt.Error(T("server_error_invalid_user_session"))

    ...

上記の例だと翻訳関数は T になります。この Tgo/ast を使って見つけていきます。

今回作成したパッケージ goi18np を使って取得する場合は次のように書けます:

a := goi18np.Analyzer{}
a.AnalyzeFromFile("/path/to/code.go")
a.SaveJSON("/path/to/translate.json")

また、複数ファイルも同時に解析できます:

a.AnalyzeFromFiles([]string{"/path/to/code.go", "/path/to/code2.go"})

入力と出力の例を以下に紹介します。

package sample

import (
    "fmt"

    "github.com/nicksnyder/go-i18n/i18n"
)

type A struct {
    T i18n.TranslateFunc
}

func SampleCaller() {
    T, _ := i18n.Tfunc("eu-US")

    fmt.Println(T("sample_uniq_key"))
    for i := 0; i > 100; i++ {
        if i%10 == 0 {
            fmt.Println(T("sample_uniq_key_2"))
            x := T("sample_uniq_key_3")
            fmt.Println(x)
        }
    }

    T2, _ := i18n.Tfunc("en-US")
    a := A{T: T2}

    fmt.Println(a.T("sample_uniq_key_4"))

    fmt.Printf("%s,%s,%s", a.T("sample_uniq_key_4"), T("sample_uniq_key_5"), T("sample_uniq_key_2"))
    return
}

このソースコードを解析した結果が次のようになります:

[
  {
    "id": "sample_uniq_key",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_2",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_3",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_4",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_5",
    "translation": ""
  }
]

翻訳内容の部分を one または other をつけて、単数形と複数形の文章に分ける処理はしていません。 そこまでの自動化はせずに、必要になったときに人の手で追加する感じになると思います。

また、実際にjsonで保存するときは go1.8 で出てきた sort.Slice を使うなどして、ID順にソートして差分が少なくなるように工夫が必要かと思います。