8

I am interested in a zero-downtime deployment system that does not use Elixir/Erlang hot upgrades (due to the complexity of data migrations while the code is running).

I have heard that I may be able to use the SO_REUSEPORT option when binding the server to the adapter, such that I can run two instances of the same application bound to the the same address and port. My intent is to deploy version 2 on the same server as a running version 1, start version 2, and then gracefully stop version 1, which should allow incoming connections to naturally begin connecting exclusively to version 2.

Regardless of whether this works exactly as I plan - my intent is to test this configuration knowing that it behaves differently on different OSes - I would like to know the specific steps necessary to configure Phoenix to do this, as this seems to be a lower-level configuration within :gen_tcp.

If, alternatively, there is a way to configure the OS or Erlang VM to make all connections with this option enabled by default, that would be even better.

ringmaster
  • 2,379
  • 1
  • 22
  • 27
  • 1
    There's no mention of `SO_REUSEPORT` in Erlang/OTP source so I believe this is not possible even with raw `:gen_tcp` right now (except for rolling your own wrapper in a NIF). – Dogbert Feb 21 '17 at 08:23
  • Perhaps the option is S_REUSEADDR and not SO_REUSEPORT. Regardless, I've seen someone in person do something with the exact effect that I've described above, whether it was with one flag or the other, but on a Mac. He was able to start two Elixir processes, the second one with changed output, and when the first process terminated, the output switched immediately to the second process' output with no downtime. I know it's possible, I just don't know how, and if it's possible only on a Mac, I think the same should be close on Linux, if not exactly what I'm asking for. – ringmaster Feb 21 '17 at 15:08
  • And he was doing this with Phoenix or with raw `gen_tcp` or something else? (`gen_tcp` at least does have a `reuseaddr`.) – Dogbert Feb 21 '17 at 15:14
  • For reference: http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t – Brandon Joyce Feb 21 '17 at 15:30
  • @Dogbert IIRC it was a new Phoenix app, although whether he edited the Phoenix app code/config or started the app in a different way, I don't recal. – ringmaster Feb 21 '17 at 18:40
  • I would suggest putting a load balancer or proxy in front of two running Erlang instances and just toggle between instances instead of implementing a more sophisticated way of doing upgrades. – evnu Mar 23 '17 at 10:05

1 Answers1

1

You should specify raw SO_REUSEPORT flag for a socket in the format {:raw, protocol, option_num, value_bin} gen_tcp option/raw and pass it to the underlying transport.

Please note, that flags are different for mac/linux. In your config.exs:

so_reuseport =
  case :os.type() do
    {:unix, :linux} -> {:raw, 1, 15, <<1::32-native>>}
    {:unix, :darwin} -> {:raw, 0xffff, 0x0200, <<1::32-native>>}
  end

config :yourapp, YourApp.Endpoint,
  http: [port: {:system, "PORT"}, transport_options: [socket_opts: [so_reuseport]]]

Tested on Phoenix 1.4.9, but I guess older versions should be ok too. Here are the corresponding docs for used options.

r_black
  • 544
  • 5
  • 9