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

Phoenix и Docker

Phoenix и Docker

Александр Малаев: Доклад о том как мы используем Docker для сборки и деплоя наших Phoenix приложений; сборка и тестирование в Gitlab CI; текущие проблемы связанные с Docker и как их решать: http://elixir-lang.moscow/events/2/talks/phoenix-i-docker

Sobolev Nikita

October 25, 2016
Tweet

More Decks by Sobolev Nikita

Other Decks in Programming

Transcript

  1. Требования к релизу • Минимальный размер • Быстрая сборка •

    Миграция схемы данных • Возможность конфигурировать через ENV переменные • Простой и надежный деплой
  2. Как запускать phoenix- приложение? mix phoenix.server ✓легко, быстро и просто

    ✓прокинул порт и работает • нужно иметь elixir-окружение на сервере • нужно иметь системные зависимости в проде • нет нормальных init-скриптов • содержит ненужные артефакты
  3. Как запускать phoenix- приложение? OTP-релиз ✓ содержит erts и зависимости

    ✓ code hot swapping ✓ есть утилиты для управления релизом - start/stop/remote_console/etc… ✓ поддерживается в Erlang OTP и production-ready • релиз нужно собирать на машине с той же архитектурой • нет возможности запускать mix-таски в проде • зоопарк с утилитами для сборки(exrm/distillery/etc…) • проблема с системными зависимостями тоже есть
  4. Как запускать phoenix- приложение? OTP-релиз в Docker-окружении ✓ все плюшки

    otp-релиза, с некоторыми ограничениями: ‣ docker и hot swapping — взаимоисключающие вещи ‣ сложности с distributed erlang ✓ системные зависимости пишем в Dockerfile ✓ легкая интеграция в CI ✓ можно использовать с docker-swarm, kubernetes, coreos, etc.
  5. Собираем релиз • mix deps.get && mix compile • npm

    install, brunch build, etc… подождать, пока выполнится npm install… • mix digest • mix test && mix release • копируем релиз на сервер и запускаем
  6. Сборщики и инструменты • Relx/rebar: • erlang-сборка • Exrm •

    много bash-кода внутри • устаревает в пользу Distillery • Distillery • проще чем exrm внутри • тоже много bash-кода :( • Deploy-тулзы: • edeliver • capistrano/mina/ansible/etc.
  7. CI Pipeline build |> check styles |> run tests |>

    release |> cleanup |> deploy Dockerfile.build Dockerfile .gitlab-ci.yml
  8. Сложности • build-time зависимости в проектах • приватные зависимости •

    кэширование между этапами сборки • запуск интеграционных тестов Как обойти? • Зависимости в Dockerfile.build проекта • Dockerfile ENTRYPOINT • Кэширование артефактов в docker volume • Запуск сервисов из services в .gitlab-ci.yml
  9. .gitlab-ci.yml Общие параметры сборки: image: docker:latest stages: - build -

    styles - test - release - cleanup variables: CONTAINER_RELEASE_IMAGE: our.registry.url:4567/ccs/my_app:latest POSTGRES_HOST: postgres POSTGRES_USER: postgres POSTGRES_PASSWORD: password MIX_ENV: test APP_NAME: my_app APP_VERSION: 0.0.1 Настраиваем этап сборки: build: before_script: - docker build -f Dockerfile.build -t ci-project-build-$CI_PROJECT_ID:$CI_BUILD_REF . - docker create -v /build/deps -v /build/_build -v /build/rel/$APP_NAME -v /root/.cache/rebar3/ --name build_data_$CI_PROJECT_ID_$CI_BUILD_REF busybox /bin/true tags: - docker stage: build
  10. .gitlab-ci.yml Проверяем стили: styles: tags: - docker stage: styles script:

    - docker run --rm --volumes-from build_data_$CI_PROJECT_ID_$CI_BUILD_REF ci-project-build-$CI_PROJECT_ID:$CI_BUILD_REF "mix credo --strict" Запускаем тесты: test: services: - postgres tags: - docker stage: test script: - docker run --rm --link $POSTGRES_NAME:postgres -e POSTGRES_HOST=$POSTGRES_USER -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD -e POSTGRES_USER=$POSTGRES_USER -e SSH_PRIVATE_KEY="$SSH_PRIVATE_KEY" --volumes-from build_data_$CI_PROJECT_ID_$CI_BUILD_REF -t ci-project-build-$CI_PROJECT_ID:$CI_BUILD_REF "mix test"
  11. .gitlab-ci.yml Собираем релиз: release: tags: - docker stage: release script:

    - docker run --volumes-from build_data_$CI_PROJECT_ID_$CI_BUILD_REF -e MIX_ENV=prod -e SSH_PRIVATE_KEY="$SSH_PRIVATE_KEY" --rm -t ci-project-build-$CI_PROJECT_ID:$CI_BUILD_REF "mix deps.get && mix compile && mix release --env=prod" - docker cp build_data_$CI_PROJECT_ID_$CI_BUILD_REF:/build/rel/$APP_NAME/releases/$APP_VERSION/ $APP_NAME.tar.gz . - docker build -t $CONTAINER_RELEASE_IMAGE . - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN our.registry.url:4567 - docker push $CONTAINER_RELEASE_IMAGE only: - master Убираем за собой: cleanup_job: tags: - docker stage: cleanup script: - docker rm -v build_data_$CI_PROJECT_ID_$CI_BUILD_REF - docker rmi ci-project-build-$CI_PROJECT_ID:$CI_BUILD_REF when: always
  12. Dockerfile.build FROM msaraiva/elixir-gcc RUN apk add postgresql-client erlang-xmerl erlang-tools openssh-client

    erlang-eldap --no-cache WORKDIR /build ADD . /build ENTRYPOINT ["/build/docker-entrypoint.sh"] CMD ["mix", "deps.get"] #!/bin/sh eval `ssh-agent -s` > /dev/null echo "$SSH_PRIVATE_KEY" > /tmp/privkey chmod 600 /tmp/privkey ssh-add /tmp/privkey; rm /tmp/privkey mkdir -p ~/.ssh echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config exec sh -c "$*" docker-entrypoint.sh
  13. Dockerfile FROM alpine:edge RUN apk --update add postgresql-client erlang erlang-sasl

    erlang-crypto erlang-syntax-tools RUN rm -rf /var/cache/apk/* ENV APP_NAME my_app ENV PORT 4000 RUN mkdir -p /app COPY $APP_NAME.tar.gz /app/ WORKDIR /app RUN tar -zxvf $APP_NAME.tar.gz RUN rm -f /app/$APP_NAME.tar.gz EXPOSE $PORT CMD trap exit TERM; /app/bin/$APP_NAME foreground & wait
  14. Как мы деплоимся? • В docker-compose.yml все сервисы с их

    docker-образами из docker registry • обновляем образы из registry • при старте релиза запускаем миграции • логи собираются в ELK • краши из приложений падают в Sentry
  15. Миграции • в релизе нет mix ecto.migrate • используем Ecto.Migrator

    • запускаем в pre_start хуках distillery • bin/my_app command Elixir.ReleaseTasks migrate
  16. release_tasks.ex defmodule ReleaseTasks do alias Ecto.Migrator alias MyApp.Repo def migrate

    do start_applications([:logger, :postgrex, :ecto]) :ok = Application.load(:my_app) case repo_storage.storage_up(repo_params) do :ok -> migrate_repo() {:error, :already_up} -> migrate_repo() {:error, error} -> {:error, error} end :init.stop() end defp start_applications(apps) do Enum.each(apps, fn app -> {:ok, _} = Application.ensure_all_started(app) end) end defp migrate_repo do {:ok, _} = Repo.start_link() Migrator.run(Repo, migrations_path, :up, all: true) end defp migrations_path, do: Application.app_dir(:my_app, "priv/repo/migrations") defp repo_params, do: Application.get_env(:my_app, Repo) defp repo_storage, do: repo_params[:adapter] end
  17. Что можно улучшить? • Переход на Erlang-In-Docker для коммуникации между

    сервисами. https://github.com/spscream/eid • Персистентные Docker-контейнеры, вместо рестартуемых и поддержка hot swapping • Поддержка rollback-миграций при downgrade релиза • Реализация OTP-registry, для хранения релизов
  18. Спасибо за внимание! Вопросы? • twitter: spscream • email: [email protected]

    Мои проекты и вклад в OSS: https://github.com/progress-engine/tarantool.ex https://github.com/spscream Проекты нашей команды тут: https://github.com/ccsteam