Slide 1

Slide 1 text

Elixir on Containers

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Container is good, because: It gives an abstract of processes with environments. It's easy to deploy.

Slide 4

Slide 4 text

I'm using container for Elixir server @ dev, test & prod. A little bit stateful WebSocket servers. Umbrella Related tools: Distillery, Dialyzer, docker‑compose, Kubernetes.

Slide 5

Slide 5 text

Containers for local dev

Slide 6

Slide 6 text

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.

Slide 7

Slide 7 text

I maintain Docker images that fixes both Erlang & Elixir ver.

Slide 8

Slide 8 text

Work with the team & CI (Continuous Integration).

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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"]

Slide 11

Slide 11 text

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 [email protected] --cookie example -S mix phx.server

Slide 12

Slide 12 text

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 ~

Slide 13

Slide 13 text

docker-compose.override.yml --- version: "3" services: example: build: context: . dockerfile: Dockerfiles/Dockerfile.dev docker-compose build --pull --force-rm docker-compose push

Slide 14

Slide 14 text

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).

Slide 15

Slide 15 text

docker-compose.yml ~ example-src: image: eugenmayer/unison:2.51.2.1 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:

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Containers for prod

Slide 19

Slide 19 text

Work with CD (Continuous Delivery).

Slide 20

Slide 20 text

Umbrella apps/ app_a/ app_b/ common/

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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(:"[email protected]", :"[email protected]", 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

Slide 24

Slide 24 text

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 ~

Slide 25

Slide 25 text

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"]

Slide 26

Slide 26 text

container-structured-test.yml --- schemaVersion: "2.0.0" commandTests: - name: Application meta data command: bin/example args: [describe] expectedOutput: - example-0.0.1 - "Name: [email protected]" - name: libcrypto command: bin/example args: [eval, ":crypto.strong_rand_bytes(8)"] metadataTest: cmd: [bin/example, foreground] exposedPorts: ["80", "4369"] workdir: /var/www