Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Elixir on Containers

Elixir on Containers

Know-how that ops Elixir on Docker containers.



May 19, 2019


  1. Elixir on Containers

  2. .。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

  3. Container is good, because: It gives an abstract of processes

    with environments. It's easy to deploy.
  4. I'm using container for Elixir server @ dev, test &

    prod. A little bit stateful WebSocket servers. Umbrella Related tools: Distillery, Dialyzer, docker‑compose, Kubernetes.
  5. Containers for local dev

  6. Why use containers for local dev? Use the same env

    between Alchemist team members. Use the same env between non‑Alchemist team members. (Rubyist, frontend enginier, etc.) Use the near env between dev, test & prod.
  7. I maintain Docker images that fixes both Erlang & Elixir

  8. Work with the team & CI (Continuous Integration).

  9. My application have some umbrellas. apps/ app_a/ app_b/

  10. Dockerfile (deps.compile & dialyzer ‑‑plt) FROM nesachirou/elixir:1.8_erl21 SHELL ["/bin/ash", "-ex",

    "-o", "pipefail", "-c"] RUN apk add --no-cache -t .build-deps \ ~ \ git \ && rm -rf /var/cache/apk/* WORKDIR /var/www VOLUME /var/www COPY mix.exs mix.lock ./ COPY apps/app_a/mix.exs apps/app_a/ COPY apps/app_b/mix.exs apps/app_b/ RUN mix "do" deps.get, deps.compile \ && MIX_ENV="test" mix deps.compile \ && mix dialyzer --plt \ && mv _build deps /tmp RUN apk add --no-cache -t .runtime-deps \ inotify-tools \ rsync \ && rm -rf /var/cache/apk/* EXPOSE 4000 4001 CMD ["./entrypoint.sh"]
  11. entrypoint.sh #!/bin/sh -eux # shellcheck shell=dash if test "$(stat /.dockerenv

    > /dev/null || echo $?)" ; then : "Outside Docker" mix "do" deps.get, compile else : "Inside Docker" rsync -au /tmp/_build /tmp/deps . mix "do" deps.get, compile fi elixir --name example@ --cookie example -S mix phx.server
  12. docker-compose.yml --- version: "3" services: example: image: example.docker.registry/example/example:latest depends_on: [example-src]

    logging: options: max-size: "100m" max-file: "2" ports: - 4000:4000 - 4001:4001 volumes: - example-src:/var/www ~
  13. docker-compose.override.yml --- version: "3" services: example: build: context: . dockerfile:

    Dockerfiles/Dockerfile.dev docker-compose build --pull --force-rm docker-compose push
  14. docker‑sync http://docker‑sync.io/ Docker (for MacOS) is too slow in it's

    file IO between containers & the host. docker‑sync solves this, but troublesome. I do a little bit different way (unison + rsync).
  15. docker-compose.yml ~ example-src: image: eugenmayer/unison: environment: APP_VOLUME: /app_sync HOST_VOLUME: /host_sync

    TZ: Asia/Tokyo UNISON_ARGS: | -debug default -ignore 'Name .git/*' -ignore 'Name _build/*' \ -ignore 'Name deps/*' -prefer /host_sync -numericids -auto -batch UNISON_DEST: /app_sync UNISON_SRC: /host_sync UNISON_WATCH_ARGS: -repeat watch volumes: - ../example:/host_sync:cached - example-src:/app_sync volumes: example-src:
  16. precopy_appsync #!/bin/bash -eux rsync -auv \ --delete \ --exclude='.git/*' --exclude='_build/*'

    --exclude='deps/*' \ /host_sync/ /app_sync docker-compose -f example/docker-compose.yml stop example-src docker-compose -f example/docker-compose.yml run --rm --no-deps example-src \ /host_sync/tools/precopy_appsync docker-compose -f example/docker-compose.yml start example-src
  17. Testing container image yamllint https://github.com/adrienverge/yamllint hadolint https://github.com/hadolint/hadolint container‑structure‑test https://github.com/GoogleContainerTools/container‑ structure‑test

  18. Containers for prod

  19. Work with CD (Continuous Delivery).

  20. Umbrella apps/ app_a/ app_b/ common/

  21. Distillery https://github.com/bitwalker/distillery {:distillery, "~> 2.0", runtime: false}, No hot code

    swapping, RollingUpdate.
  22. rel/config.exs ~ environment :prod do set(include_erts: true) set(include_src: false) set(cookie:

    :" ~") set(vm_args: Path.join("rel", "vm.args.prod")) end release :app_a do set(version: "0.0.1") set(applications: [:app_a, :common, :runtime_tools]) set(commands: [app_terminate: "rel/commands/app_terminate.sh"]) set(overlay_vars: [ ~]) end
  23. rel/commands/app_terminate.sh → ./bin/app_a app_terminate (PreStop hook) #!/bin/bash -eu COOKIE=$(awk '/-setcookie/{print$2}'

    releases/0.0.1/vm.args) export COOKIE bin/app_a command Elixir.AppA.Command app_terminate AppA.Command defmodule AppA.Command do def app_terminate do :ok = do_rpc(:"app_a@", :"terminate@", AppA.AppTerminator, :request_terminate, [], 1_800_000) end defp do_rpc(target_node, node_name, module, fun, args, timeout) do Node.start(node_name) Node.set_cookie(String.to_atom(System.get_env("COOKIE"))) :rpc.call(target_node, module, fun, args, timeout) end end
  24. Dockerfile for app_a (multi stage build) FROM nesachirou/elixir:1.8_erl21 as builder

    SHELL ["/bin/ash", "-ex", "-o", "pipefail", "-c"] RUN apk add --no-cache -t .build-deps \ build-base \ ~ \ git ARG REVISION ENV MIX_ENV=prod WORKDIR /var/www COPY mix.exs mix.lock ./ COPY apps/app_a/mix.exs apps/app_a/ COPY apps/common/mix.exs apps/common/ RUN mix "do" deps.get --only prod, deps.compile COPY apps/app_a apps/app_a COPY apps/common apps/common COPY config config COPY rel rel # hadolint ignore=DL3003 RUN mix compile \ && mix release --verbose --name=app_a ~
  25. Dockerfile for app_a (multi stage build) ~ FROM alpine:3.9 SHELL

    ["/bin/ash", "-ex", "-o", "pipefail", "-c"] RUN apk add --no-cache -t .runtime-deps \ bash \ inotify-tools \ openssl \ && rm -rf /var/cache/apk/* WORKDIR /var/www # hadolint ignore=DL3010 COPY --from=builder /var/www/_build/prod/rel/app_a/releases/0.0.1/app_a.tar.gz ./ RUN tar xzf app_a.tar.gz \ && rm -rf app_a.tar.gz EXPOSE 80 4369 CMD ["bin/app_a", "foreground"]
  26. container-structured-test.yml --- schemaVersion: "2.0.0" commandTests: - name: Application meta data

    command: bin/example args: [describe] expectedOutput: - example-0.0.1 - "Name: example_maint_@" - name: libcrypto command: bin/example args: [eval, ":crypto.strong_rand_bytes(8)"] metadataTest: cmd: [bin/example, foreground] exposedPorts: ["80", "4369"] workdir: /var/www