Пересаживаем проект с Vagrant + VirtualBox на Docker-контейнеры с помощью Ansible

—

от автора

До того, как все заполонили контейнеры, главными инструментами для создания локальной среды разработки были технологии наподобие Vagrant и VirtualBox. Эти инструменты в сочетании с такими средствами автоматизации, как Ansible и Chef, позволяли создать рабочую воспроизводимую среду для приложений. Однако развитие легких вариантов виртуализации, заложенное docker и постоянно упрощаемое различными облачными инновациями, привело к упадку этих некогда очень популярных среди разработчиков инструментов. Настолько стремительному, что увидев их где-нибудь, мы невольно задумываемся о возрасте кодовой базы.

И вот недавно я сам наткнулся на них. А если быть точнее, то мне достался проект, который все-еще на них полагается — он предполагает установку виртуальной машины VirtualBox под управлением Debian, созданной с помощью Vagrant, а затем настроенной с помощью Ansible. И все это работает. Ну, по большей части. Но когда не работает, разбираться, что пошло не так — настоящая боль. Поддержание координации между Vagrant и VirtualBox было особенно неприятной черной магией, которая подтолкнула меня к размышлениям о более дешевых и дружественных альтернативах виртуализации.

К концу моего ознакомления с проектом я разработал план из трех этапов:

  1. Заменить vagrant и virtualBox на docker. Т.е. использовать ansible для переноса проекта в docker-контейнеры.

  2. Заменить ansible на docker-compose, сопровождаемый специальными Dockerfile для каждого из приложений, задействованных в проекте.

  3. Распространить изменение инструментария на другие среды: staging и production.

Данная статья представляет собой документацию к первой фазе этого плана.

Почему я решил написать эту статью?

Использование ansible для настройки и запуска контейнеров docker — не самый распространенный сценарий. На самом деле, одной из причин написания этой статьи стало то, что мне не удалось найти ни одного ресурса, где было бы собрано то, что мне нужно. Такая нехватка информационных ресурсов вполне объяснима, поскольку, как только вам удается добиться какого-либо успеха в автоматизации проекта, докрутить что-то еще становится довольно просто благодаря множеству доступных инструментов.

Но это, к сожалению, другой случай. Учитывая, что я не очень хорошо ориентируюсь в проекте, на то, чтобы отследить все необходимые зависимости, разбросанные по нескольким ролям и плейбукам ansible, выяснить их взаимодействие и корректно воспроизвести их в изолированных контейнерах, потребуется немало времени и сил.

Однако, поскольку требования к этим зависимостям уже отражены в плейбуках ansible, я могу значительно упростить себе задачу, просто указав ansible на пустой контейнер, и заставить его настроить контейнер так же, как он настраивает виртуальную машину virtualBox. В конце концов, это одна из потрясающих возможностей ansible: дайте ей (ssh) доступ к любой машине, и она предоставит вам желаемое окружение. В данном случае машиной с доступом по ssh будет контейнер docker, а желаемым окружением — среда разработки, отраженная в различных плейбуках ansible.

Проект

Мой проект — довольно крупное приложение Ruby-on-Rails с кучей типичных зависимостей, таких как Sidekiq, Nginx и т.д.

По понятным причинам я не могу использовать реальный проект в этой статье. Вместо него мы будем использовать публичный проект docker-rails. Я выбрал этот проект, потому что его зависимости довольно похожи на мой собственный проект. В них присутствуют:

  • Sidekiq

  • Postgres

  • Redis

  • Opensearch

Наличие этих зависимостей даст нам возможность решить ряд задач, которые я хотел бы рассмотреть в этой статье.

Ну а наши приключения начинаются с клонирования проекта в рабочую среду (workspace).

mkdir -p ansidock cd ansidock git clone https://github.com/ledermann/docker-rails

Инвентарь

Инвентарь Ansible отвечает за список узлов/хостов, с которыми Ansible должен работать и за то, как именно он должен с ними работать. Настройки, начиная от используемого плагина подключения и заканчивая расположением интерпретатора python на каждом узле, настраиваются с помощью файла инвентаризации. В данном случае я использую его для создания топологии локальной среды разработки.

На высоком уровне нам нужно выделить два разных узла:

  1. родительский узел, на котором создаются (и управляются) контейнеры docker

  2. узел-контейнер, в котором запускается нужное приложение

Чтобы понять замысел, стоящий за этой структурой, давайте рассмотрим текущую настройку ansible + vagrant + virtualBox.

Vagrant создает виртуальную машину на базе virtualBox. После этого он передает информацию о новой виртуальной машине в Ansible. Затем Ansible настраивает новый box для запуска требуемого приложения. Другими словами, vagrant работает с virtualBox на реальном хосте, машине разработчика, чтобы предоставить виртуальную машину, которую ansible затем настраивает для запуска целевого приложения.

В результате всего этого мы получим такой файл инвентаризации:

# file: ansidock/dev [dockerhost] localhost  [app] [postgres] [redis] [opensearch]  [containers:children] app postgres redis opensearch  [all:vars] ansible_python_interpreter= /usr/bin/python3  [dockerhost:vars] ansible_connection=local  [containers:vars] ansible_connection=docker

Управляющий узел указывается как dockerhost, с подключением к localhost. Хосты redis, postgres и opensearch, удобно сгруппированные под тегом containers, являются удаленными узлами, к которым можно подключиться через docker.

Обратите внимание, что ни один из хостов контейнеров не содержит никакой информации о подключении (например, URL или IP-адрес). Это потому, что они будут создаваться на лету, и одним из приемов, которые я буду использовать в дальнейшем, будет заключатся в динамическом заполнении этой информации по мере создания контейнеров.

Сеть

В упрощенном мире внутри виртуальной машины VirtualBox все процессы смогут общаться с любым соседним процессом. Таким образом, первым делом необходимо обеспечить широкую открытую сеть для будущих сервисов.

Docker позволяет настраивать различные типы сетей. Для моих целей достаточно базовой bridge network.

# file: ansidock/network.yml --- - hosts: dockerhost   tasks:     - name: "Create docker network: {{ network_name }}"       ansible.builtin.docker_network:         name: "{{ network_name }}"         driver: bridge

Эта сеть позволит любому контейнеру в ней обращаться к любому другому контейнеру в сети, используя IP-адрес, имя или псевдоним контейнера.

Зависимости

Нашему целевому приложению необходимы три главные зависимости: Postgres, Redis и Opensearch.

В реальном проекте, над которым я работаю, эти зависимости устанавливаются непосредственно в виртуальную машину. Для этой установки также доступны сценарии ansible. Таким образом, у нас есть возможность подтянуть ту же строку и явно установить каждую зависимость в пустой контейнер.

Тем не менее, у меня в планах вторая фаза — фаза, на которой я откажусь от Ansible. Таким образом, мы уже можем приступить к развертыванию выделенных контейнеров для каждого из этих сервисов. Более того, существуют уже готовые для использования варианты контейнеров для этих проектов, что делает их довольно простыми для внедрения. Учитывая это, мы развернем эти зависимости с помощью их официальных образов docker вместо того, чтобы запускать их установочные плейбуки.

Теперь нам нужно будет сделать с контейнерами несколько повторяемых манипуляций, поэтому сейчас самое лучшее время создать многократно используемую роль ansible, чтобы абстрагировать эти задачи:

# file: ansidock/roles/container/tasks/main.yml --- - name: "pull {{ image }}:{{ image_tag }}"   ansible.builtin.docker_image:     name: "{{ image }}"     tag: "{{ image_tag }}"     source: pull  - name: "create {{ container_name }} container"   ansible.builtin.docker_container:     name: "{{ container_name }}"     image: "{{ image }}:{{ image_tag }}"     command: "{{ container_command }}"     auto_remove: yes     detach: yes     env: "{{ container_env }}"     ports: "{{ container_ports }}"     volumes: "{{ container_volumes }}"     working_dir: "{{ container_workdir }}"     networks:       - name: "{{ network_name }}"  - name: "add {{ container_name }} container to host group: {{ container_host_group }}"   ansible.builtin.add_host:     name: "{{ container_name }}"     groups:       - "{{ container_host_group }}"   changed_when: false   when: container_host_group is defined  - name: "update {{ container_name }} package register"   ansible.builtin.command:     cmd: 'docker exec {{ container_name }} /bin/bash -c "apt-get update"'   when: container_deps is defined  - name: install dependencies   ansible.builtin.command:     cmd: 'docker exec {{ container_name }} /bin/bash -c "apt-get install -y {{ container_deps | join(" ") }}"'   when: container_deps is defined

со следующими дефолтными переменными

# file: ansidock/roles/container/defaults/main.yml --- container_command: container_env: {} container_host_group: container_ports: [] container_volumes: [] container_workdir:

Эта роль включает задачи по извлечению, созданию контейнера из заданного образа и добавлению его в сеть docker. Она также может устанавливать зависимости на контейнер. Как вы заметили, здесь также есть задача add host, определенная для заполнения пустых секций хостов в инвентаре.

Используя эту роль, мы создаем плейбук для наших трех зависимостей:

# file: ansidock/dependencies.yml --- - name: Postgres database   hosts: dockerhost   vars:     image: "{{ postgres_image }}"     image_tag: "{{ postgres_version }}"     container_name: "{{ postgres_container_name }}"     container_env: "{{ postgres_env }}"     container_ports: "{{ postgres_ports }}"     container_host_group: postgres   roles:     - container  - name: Redis cache   hosts: dockerhost   vars:     image: "{{ redis_image }}"     image_tag: "{{ redis_version }}"     container_name: "{{ redis_container_name }}"     container_host_group: redis   roles:     - container  - name: Opensearch library   hosts: dockerhost   vars:     image: "{{ opensearch_image }}"     image_tag: "{{ opensearch_version }}"     container_name: "{{ opensearch_container_name }}"     container_env: "{{ opensearch_env }}"     container_host_group: opensearch   roles:     - container

Это приближает нас к цели примерно на 40%.

Вот наш прогресс на данный момент:

  • инвентарь, который будет динамически обновляться по мере создания контейнеров

  • сеть docker, чтобы все контейнеры могли связаться друг с другом

  • зависимости приложения, доступные в собственных контейнерах

Нам также не мешало бы проверить нашу работу. Для значений переменных, используемых в сценариях, мы предоставим:

# file: ansidock/group_vars/all.yml --- network_name: ansidocknet app_dir: /app/ansidock app_ruby_version: 3.2.1 app_bundler_version: 2.4.6
# file: ansidock/group_vars/dockerhost.yml --- postgres_image: postgres postgres_version: 15-alpine postgres_container_name: ansidock_db postgres_ports:   - 8765:5432 postgres_env:   POSTGRES_PASSWORD: password   POSTGRES_USER: postgres   POSTGRES_DB: ansidock  opensearch_image: opensearchproject/opensearch opensearch_version: latest opensearch_container_name: ansidock_search opensearch_env:   discovery.type: single-node   plugins.security.disabled: "true"  redis_image: redis redis_version: alpine redis_container_name: ansidock_redis

после чего запустим:

ansible-playbook -i dev network.yml dependencies.yml

После успешного завершения сценария мы выполняем следующие проверки:

docker container ls --format "{{.Names}}"  # expect three containers: ansidock_search, ansidock_redis, ansidock_db
docker network inspect --format "{{range .Containers}}{{println .Name}}{{end}}" ansidocknet # ansidock_search, ansidock_redis, ansidock_db

Отлично. Двигаемся дальше.

Приложение

Прежде чем мы погрузимся в приложение, посмотрите на значение атрибута host в плейбуках.

Обратите внимание, что до сих пор ansible работал на dockerhost, то есть на узле управления. Поэтому в некоторых случаях, когда нам нужно было выполнить операцию над контейнером, мы не использовали ansible непосредственно в контейнере, а использовали ansible для выполнения командной оболочки на узле управления, которая затем выполняла команду с помощью cli docker.

Для примера посмотрите на задачи install dependencies из роли container выше.

- name: install dependencies   ansible.builtin.command:     cmd: 'docker exec {{ container_name }} /bin/bash -c "apt-get install -y {{ container_deps | join(" ") }}"'   when: container_deps is defined

Если бы на данном этапе целью был узел контейнера, задачи были бы очень простыми:

- name: install dependencies   ansible.builtin.apt:     package: '{{ container_deps | join(" ") }}'   when: container_deps is defined

Но поскольку ansible для этой задачи работает с управляющего узла, то есть с хост-машины с контейнерами, мы должны явно выполнить команды с помощью cli docker.

Это различие проявляется в плейбуке приложения, где в первой части нам нужно будет создать контейнер (используя узел управления в качестве хоста), а во второй — настроить этот контейнер (переключившись на узел контейнера в качестве хоста). Поэтому следите за переключением.

Первым делом необходимо создать пустой контейнер, подобный виртуальной машине с debian, которую vagrant предоставляет ansible для настройки проекта. В качестве образца мы возьмем базовый образ ubuntu. Как и в случае с зависимостями, мы будем использовать для настройки роль container.

Однако прежде чем это сделать, нам нужно выяснить, что делать с главным процессом контейнера. Жизненный цикл docker-контейнера завязан вокруг его главного процесса (PID 1), и правильная работа с ним является предметом многих идей, выводов и разочарований в управлении контейнерами.

Наше затруднение заключается в том, что целевой главный процесс, сервер rails, будет доступен только после того, как Ansible поработает с контейнером. Но чтобы ansible мог добраться до контейнера, контейнер должен быть запущен. А для запуска контейнера мы хотели бы, чтобы он был сервером rails… Очевидным решением будет передать PID 1 другой долгоживущей задаче (например, sleep infinity), а затем запустить сервер rails позже, когда он будет готов. Это шаг в правильном направлении, с тем нюансом, что нам нужно, чтобы все, что работает с главными процессами, также брало на себя управление процессами rails и любыми другими дочерними процессами, которые могут появиться.

К счастью, это не такая уж сложная задача. Экосистема Linux богата приложениями, написанными как раз для этой цели. Из всего многообразия вариантов мы остановимся на supervisord. Supervisord, помимо желаемого поведения, позволяет в любой момент времени добавлять (и удалять) дочерние процессы. Мы воспользуемся этим позже, чтобы запустить наши rails-процессы.

С этим разобрались, следующая задача ясна: собрать воедино набор задач, который даст нам базовый образ с supervisord и обеспечит возможность изменять конфигурацию supervisord по мере необходимости.

# file: ansidock/roles/supervisor/tasks/build.yml --- - name: create temp directory for build   ansible.builtin.tempfile:     state: directory   register: build_dir  - name: generate dockerfile   ansible.builtin.template:     src: dockerfile.j2     dest: '{{ build_dir.path }}/Dockerfile'  - name: generate supervisord conf   ansible.builtin.template:     src: supervisord.conf.j2     dest: '{{ build_dir.path }}/supervisord.conf'  - name: build supervisord image   ansible.builtin.docker_image:     name: "{{ image }}"     tag: "{{ image_tag }}"     source: build     state: present     force_source: true     build:       path: "{{ build_dir.path }}"       pull: yes

Для выполнения задачи необходимы следующие два шаблона:

простая конфигурация supervisord

; file: ansidock/roles/supervisor/templates/supervisord.conf.j2 [supervisord] logfile=/tmp/supervisord.log loglevel=debug nodaemon=true user=root

и образ docker, который его использует

# file: ansidock/roles/supervisor/templates/dockerfile.j2 # syntax=docker/dockerfile:1 FROM ubuntu:23.04  RUN apt-get update \     && apt-get install -y supervisor \     && mkdir -p /var/log/supervisor  COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf  CMD ["/usr/bin/supervisord", "-n"]

Конечным результатом будет образ Ubuntu с установленным supervisord.

И теперь, когда у нас есть образ, мы можем передать его для запуска нашей роли container. Итак, вот первая часть нашего плейбука приложения:

# file: ansidock/application.yml --- - name: Prepare application container image   hosts: dockerhost   vars:     aim: build     image: "{{ app_image }}"     image_tag: "{{ app_image_version }}"     container_name: "{{ app_container_name }}"     container_env: "{{ app_env }}"     container_ports: "{{ app_ports }}"     container_host_group: app     container_workdir: "{{app_dir}}"     container_volumes:       - "{{playbook_dir}}/{{app_src}}:{{app_dir}}"     container_deps:       - python3-apt       - python3       - python3-pip   roles:     - supervisor     - container

Обратите внимание на дополнительные зависимости python, которые мы устанавливаем в контейнер. Они позволят нам использовать команды ansible непосредственно в контейнере (что мы будем использовать на следующем шаге). Конечно, мы можем (и должны) включить их при сборке базового образа, но это будет не так интересно.

Кроме того, если вы не заметили, на этом этапе мы добавили наш проект в контейнер.

Теперь, когда у нас есть правильно подготовленный базовый образ, для моего реального проекта все готово. Осталось только направить сценарии ansible на хост контейнера, и вуаля, у меня была рабочая среда, аналогичная существующему решению с виртуальной машиной. Но раз уж вы остались со мной до сих пор, мы закончим эту часть, запустив проект docker-rails в наше сетапе.

Осталось настроить контейнер для запуска rails-приложения. Для этого необходимо установить ruby со всеми его зависимостями, установить менеджер зависимостей ruby Bundler, Node.js и его менеджер пакетов Yarn, а также подготовить базу данных для приложения.

# file: ansidock/roles/ruby/tasks/main.yml --- - name: install rbenv and app dependencies   ansible.builtin.apt:     name:       - autoconf       - bison       - build-essential       - git       - imagemagick       - libdb-dev       - libffi-dev       - libgdbm-dev       - libgdbm6       - libgmp-dev       - libncurses5-dev       - libpq-dev       - libreadline6-dev       - libssl-dev       - libyaml-dev       - patch       - rbenv       - ruby-build       - rustc       - tzdata       - uuid-dev       - zlib1g-dev     state: present     update_cache: true  - name: register rbenv root   ansible.builtin.command:     cmd: rbenv root   register: rbenv_root  - name: install ruby-build rbenv plugin   ansible.builtin.git:     repo: https://github.com/rbenv/ruby-build.git     dest: "{{ rbenv_root.stdout }}/plugins/ruby-build"  - name: "install ruby {{ ruby_version }}"   ansible.builtin.command:     cmd: "rbenv install {{ ruby_version }}"   args:     creates: "{{ rbenv_root.stdout }}/versions/{{ ruby_version }}/bin/ruby"   environment:     CONFIGURE_OPTS: "--disable-install-doc"     RBENV_ROOT: "{{ rbenv_root.stdout }}"     PATH: "{{ rbenv_root.stdout }}/shims:{{ ansible_env.PATH }}"  - name: install bundler   ansible.builtin.gem:     name: bundler     version: "{{ bundler_version }}"   environment:     PATH: "{{ rbenv_root.stdout }}/shims:{{ ansible_env.PATH }}"  - name: install app gems   ansible.builtin.bundler:     state: present     executable: "{{ rbenv_root.stdout }}/shims/bundle"  - name: remove conflicting yarn bin   ansible.builtin.apt:     package: cmdtest     state: absent  - name: add yarn source key   block:     - name: yarn |no apt key       ansible.builtin.get_url:         url: https://dl.yarnpkg.com/debian/pubkey.gpg         dest: /etc/apt/trusted.gpg.d/yarn.asc      - name: yarn | apt source       ansible.builtin.apt_repository:         repo: "deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/yarn.asc] https://dl.yarnpkg.com/debian/ stable main"         state: present         update_cache: true  - name: install yarn   ansible.builtin.apt:     package: yarn  - name: install javascript packages   ansible.builtin.command:     cmd: yarn install --frozen-lockfile   environment:     NODE_OPTIONS: "--openssl-legacy-provider"  - name: prepare database   ansible.builtin.command:     cmd: bundle exec rails db:prepare   environment:     PATH: "{{ rbenv_root.stdout }}/shims:{{ ansible_env.PATH }}"  - name: precompile assets   ansible.builtin.command:     cmd: bundle exec rails assets:precompile   environment:     PATH: "{{ rbenv_root.stdout }}/shims:{{ ansible_env.PATH }}"     NODE_OPTIONS: "--openssl-legacy-provider"

Ничего необычного, просто стандартные задачи, которые любой хороший рубист должен был выполнить в какой-то момент своей карьеры.

Если это выглядит чересчур нагроможденным, то это лишь потому, что он борется с такими распространенными проблемами, как ключевое слово yarn, указывающее на cmdtest на Ubuntu, которое должно быть явно заменено на Yarn, менеджер зависимостей JavaScript; такие проблемы, как  устаревшая версия rbenv ruby-build в apt-репозиториях, … и т.д. В любом случае, эти все замысловатые тонкости сейчас нас не интересуют. Поэтому мы двигаемся дальше.

Теперь, когда мы готовы запустить приложение, нам нужно поручить supervisord помочь нам в этом.

# file: ansidock/roles/supervisor/tasks/reconfigure.yml --- - name: generate supervisor conf   ansible.builtin.template:     src: program.conf.j2     dest: "/etc/supervisor/conf.d/{{ filename }}"   vars:     command: "{{ item.value }}"     program: "{{ item.key }}"     filename: "{{ item.key }}.conf"     workdir: "{{ container_workdir }}"   with_dict: "{{ programs }}"  - name: restart supervisord   ansible.builtin.supervisorctl:     name: '{{ item.key }}'     config: /etc/supervisor/supervisord.conf     state: present   with_dict: "{{ programs }}"

Задача принимает карту программы для команды выполнения, генерирует конфиг supervisord из шаблона ниже и копирует его в контейнер.

; file: ansidock/roles/supervisor/templates/program.conf.j2 [program:{{ program }}] command={{ command }} directory={{ workdir }} startretries=10 stdout_logfile={{ workdir }}/log/development.log user=root

Да, задача также перезапускает supervisord для использования новой конфигурации (конфигураций).

Поскольку эта роль служит как для создания базового образа, так и для изменения конфигурации процесса supervisord, давайте добавим родительскую задачу, которая будет переключаться между этими двумя действиями:

# file: ansidock/roles/supervisor/tasks/main.yml --- - include_tasks: build.yml   when: aim == "build" - include_tasks: reconfigure.yml   when: aim == "configure"

Как вы, наверное, поняли, до сих пор мы не уделяли особого внимания Sidekiq. Это связано с тем, что он запускает то же самое rails-приложение, только через другой процесс. Поэтому все, что мы делали для главного приложения, применимо и к нему. Мы выделим его только сейчас, когда завершим работу над плейбуком нашего приложения:

# file: ansidock/application.yml --- - name: prepare application container image   hosts: dockerhost   vars:     aim: build     image: "{{ app_image }}"     image_tag: "{{ app_image_version }}"     container_name: "{{ app_container_name }}"     container_env: "{{ app_env }}"     container_ports: "{{ app_ports }}"     container_host_group: app     container_workdir: "{{app_dir}}"     container_volumes:       - "{{ playbook_dir }}/{{ app_src }}:{{ app_dir }}"     container_deps:       - python3-apt       - python3       - python3-pip   roles:     - supervisor     - container  - name: setup application container   hosts: app   vars:     aim: configure     container_workdir: "{{ app_dir }}"     ruby_version: "{{ app_ruby_version }}"     bundler_version: "{{ app_bundler_version }}"     programs:       app: "/root/.rbenv/shims/bundle exec puma -C config/puma.rb"       worker: "/root/.rbenv/shims/bundle exec sidekiq"   roles:     - ruby     - supervisor

И наша работа закончена.

Соберите все в один красивый плейбук.

# file: ansidock/site.yml --- - ansible.builtin.import_playbook: network.yml - ansible.builtin.import_playbook: dependencies.yml - ansible.builtin.import_playbook: application.yml

и сопутствующий файл vars

# file: ansidock/group_vars/dockerhost.yml --- postgres_image: postgres postgres_version: 15-alpine postgres_container_name: ansidock_db postgres_ports:   - 8765:5432 postgres_env:   POSTGRES_PASSWORD: password   POSTGRES_USER: postgres   POSTGRES_DB: ansidock  opensearch_image: opensearchproject/opensearch opensearch_version: latest opensearch_container_name: ansidock_search opensearch_env:   discovery.type: single-node   plugins.security.disabled: "true"  redis_image: redis redis_version: alpine redis_container_name: ansidock_redis  app_image: rails_supervisor app_image_version: 2 app_container_name: ansidock_app app_src: docker-rails app_ports:   - 7000:3000 app_env:   DB_HOST: "{{ postgres_container_name }}"   DB_USER: "{{ postgres_env.POSTGRES_USER }}"   DB_PASSWORD: "{{ postgres_env.POSTGRES_PASSWORD }}"   OPENSEARCH_HOST: "{{ opensearch_container_name }}"   REDIS_SIDEKIQ_URL: "redis://{{ redis_container_name }}:6379/0"   REDIS_CABLE_URL: "redis://{{ redis_container_name }}:6379/1"   REDIS_CACHE_URL: "redis://{{ redis_container_name }}:6379/2"   SECRET_KEY_BASE: some-super-secret-from-ansible-vault   RAILS_MASTER_KEY: another-super-secret-from-ansible-vault   APP_ADMIN_EMAIL: admin@example.org   APP_ADMIN_PASSWORD: secret   APP_EMAIL: reply@example.org   PLAUSIBLE_SCRIPT: https://plausible.example.com/js/script.js

Давайте-ка также проведем тест-драйв нашей работы

ansible-playbook -i dev site.yml

Если все прошло успешно, docker container ls должен показать наши 4 контейнера, работающие в нормальном режиме. А при посещении localhost:7000 нас должно встретить приложение-образец, работающее во всем своем великолепии.

Вот мы и сделали это.

Заключение

Это упражнение помогло ответить на вопросы:

  • Могу ли я заменить vagrant + virtualBox на docker?

  • Если да, то как это сделать полегче?

Но это еще не конечная остановка. Все, что нужно, помещено в контейнеры, и теперь они являются подходящем таргетом для множества современных инструментов.

Для начала мы можем сделать снапшот приложения после его создания. Создав образ из запущенного контейнера, мы получим рабочую базу, которую можно использовать для обхода всего процесса создания Ansible, через который мы только что прошли.

И вооружившись этими новыми возможностями, мы приступим ко второму этапу — docker-compose.


Материал подготовлен в преддверии старта онлайн-курса «Расширенное администрирование РЕД ОС».


ссылка на оригинал статьи https://habr.com/ru/articles/808387/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *