Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deploying with releases #76

Closed
ghost opened this issue Jul 27, 2019 · 10 comments
Closed

Deploying with releases #76

ghost opened this issue Jul 27, 2019 · 10 comments

Comments

@ghost
Copy link

ghost commented Jul 27, 2019

Elixir 1.9's releases are a built-in replacement for Distillery. Like Distillery, you can't run Mix tasks against a release.

I tried following the deployment guide in the docs but run into an error:

Starting dependencies...
Starting repos...
Starting clusters...
Building indexes...
** (exit) exited in: GenServer.call(Backend.Elasticsearch.Cluster, :config, 5000)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir) lib/gen_server.ex:1000: GenServer.call/3
    (elasticsearch) lib/elasticsearch/indexing/index.ex:32: Elasticsearch.Index.hot_swap/2
    (elixir) lib/enum.ex:783: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir) lib/enum.ex:783: Enum.each/2
    lib/backend/release.ex:29: Backend.Release.build_elasticsearch_indexes/0
    (stdlib) erl_eval.erl:680: :erl_eval.do_apply/6
    (elixir) lib/code.ex:240: Code.eval_string/3

This is the release module that I'm running:

defmodule Backend.Release do
  @app :backend
  @start_apps [
    :crypto,
    :ssl,
    :postgrex,
    :ecto,
    :elasticsearch
  ]

  # Ecto repos to start, if any
  @repos Application.get_env(:backend, :ecto_repos, [])
  # Elasticsearch clusters to start
  @clusters [Backend.Elasticsearch.Cluster]
  # Elasticsearch indexes to build
  @indexes [:instances]

  def build_elasticsearch_indexes() do
    start_services()
    IO.puts("Building indexes...")
    Enum.each(@indexes, &Elasticsearch.Index.hot_swap(Backend.Elasticsearch.Cluster, &1))
    stop_services()
  end

  # Ensure that all OTP apps, repos used by your Elasticsearch store,
  # and your Elasticsearch Cluster(s) are started
  defp start_services do
    IO.puts("Starting dependencies...")
    Enum.each(@start_apps, &Application.ensure_all_started/1)
    IO.puts("Starting repos...")
    Enum.each(@repos, & &1.start_link(pool_size: 1))
    IO.puts("Starting clusters...")
    Enum.each(@clusters, & &1.start_link())
  end

  defp stop_services do
    :init.stop()
  end
end

If I replace the contents of start_services() with a single line that calls Application.ensure_all_started(@app) then things work fine, but this starts my entire app which I'd prefer to avoid.

Does anyone know if there's a major difference between Distillery and Elixir releases that could be causing this? It seems like Enum.each(@clusters, & &1.start_link()) is not starting the cluster as it should.

@ghost
Copy link
Author

ghost commented Jul 31, 2019

Some additional details here:

With Application.ensure_all_started(@app), the indexing runs, but attempts to read from the index fail with a no such index error. No indexes are actually created by the hot_swap task.

@danielberkompas
Copy link
Owner

@brortao You are going to need to add your app's name to the list of apps to start, since the Backend.Elasticsearch.Cluster process is part of your app and won't start otherwise. If you prefer, you could possibly spawn it as part of this task, but I'm not sure exactly what that would look like.

Regarding the no such index error, are you able to get hot_swap to run and create an index when you run it locally?

@ghost
Copy link
Author

ghost commented Aug 1, 2019

Hmm, yes, running the task locally does create the index. It's just on my deployed service. I'm guessing this is a problem with my infrastructure so I'll close this and continue there!

@ghost ghost closed this as completed Aug 1, 2019
@ghost
Copy link
Author

ghost commented Aug 1, 2019

Ah, hang on, reopening. It's failing because it cannot find the index config JSON file. That's why it was working on my local machine: when I run it from my project folder, it works; when I run it from elsewhere I get an enoent error.

I think the problem is in hot_swap when it calls create_from_file, which calls File.read.

Here are the results of File.ls on the deployed app:

iex(backend@5714fba14b09)10> File.ls()                          
{:ok, ["lib", "tmp", "bin", "erts-10.4.4", "releases", "Procfile"]}

The index configuration is hidden in a subfolder:

iex(backend@5714fba14b09)12> File.ls("./lib/backend-2.2.0/priv/elasticsearch")
{:ok, ["instances.json"]}

I've tried to create an index from this file, but there isn't a neat solution:

iex(backend@5714fba14b09)6> Elasticsearch.Index.create_from_file(Backend.Elasticsearch.Cluster, "instances-1", "instances.json")                                     
{:error, :enoent}

iex(backend@5714fba14b09)4> Elasticsearch.Index.create_from_file(Backend.Elasticsearch.Cluster, "instances-1", "elasticsearch/instances.json")
{:error, :enoent}

iex(backend@5714fba14b09)5> Elasticsearch.Index.create_from_file(Backend.Elasticsearch.Cluster, "instances-1", "lib/backend-2.2.0/priv/elasticsearch/instances.json")
:ok

@ghost ghost reopened this Aug 1, 2019
@ghost
Copy link
Author

ghost commented Aug 2, 2019

I opened a thread on the Elixir forums to find out if there's a good way to access files in priv/ from a Mix release: https://elixirforum.com/t/accessing-the-priv-folder-from-mix-release/24403

@semarco
Copy link

semarco commented Aug 8, 2019

@brortao you can simply use :code.priv_dir(:your-app) to get the absolute priv/path of your OTP application at runtime. There is also Application.app_dir(:your-app) and Application.app_dir(:your-app, "relative_path/foo/bar").

It would really help to simplify deployments if it would be possible to put index-settings.json files into the priv folder. :-)
Thanks for looking into!!

@ghost
Copy link
Author

ghost commented Aug 10, 2019

@pixelvitamina yeah, those work at runtime, but as far as I can tell this library needs the path during configuration at compile-time. You can't use those functions in e.g config.exs.

Since you can't pass the settings file directory to hot_swap, I think it may need to be included there?

%{settings: settings_file} = index_config = config[:indexes][alias]

@ImmaculatePine
Copy link
Contributor

@brortao You can add a runtime configuration by overriding init/1 callback in your cluster module: https://github.com/danielberkompas/elasticsearch-elixir/blob/master/lib/elasticsearch/cluster/cluster.ex#L37

@ghost
Copy link
Author

ghost commented Aug 15, 2019

Amazing, I didn't realize this. Thank you!

@millacas
Copy link

millacas commented Sep 3, 2020

Arrived a little late to the conversation. If anyone is interested this is what I used to make the indexes work on a release:

defmodule MyApp.ElasticsearchCluster do
  use Elasticsearch.Cluster, otp_app: :my_app
  @app :my_app

  def init(config) do
    indexes =
      config[:indexes]

    indexes =
      Map.keys(indexes)
      |> Enum.reduce(indexes, fn index_key, indexes ->
        Map.update!(indexes, index_key, fn index ->
          Map.update!(index, :settings, fn settings_file_path ->
            Application.app_dir(@app, settings_file_path)
          end)
        end)
      end)

    config =
      Map.put(config, :indexes, indexes)

    {:ok, config}
  end
end

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants