erlware / relx

Sane, simple release creation for Erlang
http://erlware.github.io/relx
Apache License 2.0
695 stars 233 forks source link

Install release without erts or system libs and run release #652

Open expelledboy opened 6 years ago

expelledboy commented 6 years ago

I am building docker images for erlang releases, with inherited images that use the least possible data - system/base -> erlang/base -> app/<release>.

I have a system/base docker image which installs common packages such as vim etc.

Then I have a erlang/build image FROM system/base:latest which installs two erlang releases using kerl as follows;

RUN set -x \
    && curl -o /usr/bin/kerl https://raw.githubusercontent.com/spawngrid/kerl/master/kerl \
    && chmod a+x /usr/bin/kerl \
    && kerl build ${ERL_VERSION} ${ERL_VERSION} \
    && KERL_CONFIGURE_APPLICATIONS="kernel stdlib sasl compiler crypto erl_interface inets parsetools runtime_tools syntax_tools tools" kerl build ${ERL_VERSION} ${ERL_VERSION}-install \
    && kerl install ${ERL_VERSION}-install /opt/erlang \
    && find /opt/erlang -name *.erl -exec rm {} \; \
    && mkdir -p /tmp/erlang && cd /tmp/erlang \
    && kerl install ${ERL_VERSION} \
    && kerl cleanup all

As you can see one of the installations includes only a subset of the erlang applications. With erlang/build I simply copy the stripped install found at /opt/erlang and set the PATH to build a erlang/base docker image.

In a final multistage Dockerfile FROM erlang/build:latest AS builder I build the release tar as follows;

ARG RELEASE
RUN . /opt/erlang/activate \
    && set -x \
    && make ${RELEASE} \
    && mkdir -p /rel \
    && tar -zxf /src/release.tar.gz -C /rel \
    && find /rel -name *.erl -exec rm {} \; \
    && rm -rf /rel/*.tar.gz

Which runs this make target;

$(RELEASES):
    cd $(shell dirname $@) && relx \
        --include-erts false \
        --system_libs false \
        --lib-dir /tmp/erlang/lib \
        --root $(PWD) \
        release tar
    find $(shell dirname $@) -name *.tar.gz -exec mv {} release.tar.gz \;

So this runs relx in an environment using the erlang/base erlang install, but then I include the lib dir to the complete installation found in /tmp/erlang/lib incase it needs a dependancy.

The last step in the multistage build then copies /rel to /opt/app and the entry point I run as /opt/app/bin/${BIN} foreground.

What this accomplishes is;

But I just cant get this to actually run without adding the libs to the path manually as follows;

ENTRYPOINT /opt/app/bin/${BIN} foreground -- -pa /opt/app/lib/*/ebin -pa /opt/erlang/lib/*/ebin

Otherwise it crashes with;

$ docker run -it --rm -p 8000:8000 --entrypoint /bin/bash app/server
root@20f9ffc97848:/opt/app# ./bin/server foreground
Exec: /opt/erlang/erts-7.3/bin/erlexec -noshell -noinput +Bd -boot /opt/app/releases/0.0.0+build.1.ref18a7d73/start -mode embedded -boot_var ERTS_LIB_DIR /opt/erlang/lib -config /opt/app/releases/0.0.0+build.1.ref18a7d73/sys.config -args_file /opt/app/releases/0.0.0+build.1.ref18a7d73/vm.args -pa -- foreground
Root: /opt/app
/opt/app
{"init terminating in do_boot",{'cannot load',error_handler,get_files}}

Crash dump is being written to: erl_crash.dump...done
init terminating in do_boot ()
root@20f9ffc97848:/opt/app# cd ../
root@20f9ffc97848:/opt# find . -name error_handler.beam
./erlang/lib/kernel-4.2/ebin/error_handler.beam

My relx.config file is very basic;

$ cat releases/server/relx.config
%-*-Erlang-*-

{release, {server, semver}, [server]}.
{extended_start_script, true}.

{sys_config, "./system.config"}.
{vm_args, "./vm.args"}.

Dont know whats cutting 🆘 any ideas?

tsloughter commented 6 years ago

@expelledboy haven't looked close enough at the actual issue you are having, but you don't need to do this if your goal is the smallest possible image, you should instead include erts and system libs in your release and copy it to a fresh image without Erlang installed in the last stage.

expelledboy commented 6 years ago

@tsloughter If I included erts and system libs in the release the image layer will be larger than necessary when pushing them between data centers. Unfortunately given the size of some of our services including it with the release would triple the size in some cases, accumulated to 200+ deployments all adds up.

I have it working in the meantime just by manually including the paths. Was just thinking that there seems to be mechanisms is place to achieve it, and I was doing something wrong.

tsloughter commented 6 years ago

Ah, you want a separate layer that all releases can share.