Move from standalone container to separate web and data containers

Привет, Джей @pfaffman! Спасибо за этот пост и другие сообщения на тему «два контейнера», включая статьи Сэма по этой же теме.

Вопрос:

Мы пытаемся настроить два контейнера, как вы упоминали: один для data и один для web-only, но на macOS у нас возникло множество проблем с запуском этой конфигурации.

Однако прежде чем мы начнём отлаживать эту «конфигурацию с двумя контейнерами» на macOS или Ubuntu, мы хотим убедиться, что делаем это по правильной причине.

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

Также я сталкиваюсь с трудностями при настройке «быстрой и удобной» среды веб-разработки, но это тоже тема для другого дня.

Итак, мой вопрос: действительно ли конфигурация с «двумя контейнерами» значительно сокращает время простоя при пересборке только веб-части приложения?

Разве это не правильный подход к решению задачи?

Когда мы устанавливаем плагин или вносим в него изменения, нам нужно пересобирать только файл yml для «web-only», а не файл yml для данных?

Мы пришли из мира LAMP-форумов, где изменения в плагинах обычно вносятся в реальном времени на работающем сайте (без простоя, если только мы не допустим грубой ошибки). Также у нас есть опыт работы с VueJS-приложениями, где мы собираем приложение на рабочем столе, а затем просто загружаем его и заменяем старую версию новой — обновление происходит практически без простоя. Однако с Discourse мы сталкиваемся с простоями, чего мы хотим избежать (даже нескольких секунд).

Демонстрирует ли решение с «двумя контейнерами» значительное улучшение времени простоя в следующих случаях: (1) при пересборке приложения (для установки плагинов, внесения изменений в код и т. д.) или (2) при восстановлении из полной резервной копии?

Мне кажется, меня снова «накажут» за этот вопрос, поскольку мы ищем способ запустить Discourse в продакшене и вносить изменения практически без простоя, но пока не нашли способа сделать то, что так легко реализуется в LAMP или VueJS-приложениях.

Отсюда и наши трудности / интерес к методу с «двумя контейнерами», который нам пока не удалось запустить.

Спасибо!

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

При восстановлении из резервной копии разницы нет.

4 лайка

Спасибо, Джей @pfaffman,

Вы, несомненно, являетесь здесь ценнейшим ресурсом высшего класса!

Что вы думаете об этой, возможно, безумной идее (основанной на моём пока ещё ограниченном понимании):

Настроить nginx как обратный прокси на фронтенде; согласно этому руководству:

Затем создать два каталога / экземпляра с настроенным discourse_docker (standalone), например:

  1. /var/discourse1
  2. /var/discourse2

В обоих этих экземплярах настроить discourse_docker (standalone) на прослушивание разных сокетов, модифицируя этот шаблон в каждом экземпляре:

 - "templates/web.socketed.template.yml"

Таким образом, по сути, мы просто пересобираем продакшн (в какое-то тихое время) для запуска в другом контейнере, слушающем другой сокет (nginx.https.sock2). Таким образом, конфликта сокетов не возникнет; при этом мы можем собирать его также в режиме standalone (с целью устранения необходимости в двух контейнерах: data и web-only).

Например (для обсуждения / иллюстрации), в файле web.socketed.template.yml в discourse1:

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from unix:;

а в discourse2:

 - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock2;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock2 ssl http2;
       set_real_ip_from unix:;

Однако, вместо того чтобы заставлять шаблон Discourse выполнять магию, мы просто вручную переключаем сокеты в файле /etc/nginx/conf.d/discourse.conf и перезапускаем nginx. Таким образом, мы уберём директиву replace: из шаблона web.socketed.template.yml.

В предлагаемой (возможно, безумной) конфигурации у нас будут два контейнера standalone, слушающих два разных сокета (не конфликтующих), и мы просто настроим nginx на подключение к нужному сокету и перезапустим nginx.

Это кажется понятным, простым и, возможно, полезным (в период затишья, когда в работающем экземпляре нет новых постов) для тех, кто не хочет (или не нуждается) в сложности двух контейнеров (data и web-only) для одного экземпляра Discourse (приложения).

Конечно, наиболее надёжная конфигурация (с точки зрения данных) и, так сказать, идеальное решение для нагруженных сайтов — это решение с «двумя контейнерами», поскольку нам потребуются экземпляры data и web-only (теперь слушающие два разных сокета: sock и sock2).

В решении с «двумя контейнерами» и фронтендом nginx «стандартная конфигурация» предполагает, что оба контейнера web-only слушают один и тот же сокет, поэтому они не могут работать одновременно; но если (например) заставить их слушать разные сокеты, они смогут работать одновременно, и мы сможем просто использовать файл конфигурации nginx (и перезапуск nginx) для переключения между ними.

Правильно ли я понял?

Начинаю ли я (медленно, но, надеюсь, верно) понимать это?

Спасибо!

Только примечание к продолжению: У меня работает конфигурация с «двумя контейнерами» на одном из моих настольных Mac:

Screen Shot 2020-04-11 at 12.41.24 PM

Единственные оговорки при нашей установке заключались в необходимости вручную создать эти каталоги (и установить владельца и права доступа), так как по какой-то причине скрипты не создают эти каталоги:

~discourse/discourse/shared/data
~discourse/discourse/shared/web-only

И, конечно, сначала я пробовал с пустым паролем для базы данных, и это не сработало (в инструкциях сказано установить пароль, но я просто экспериментировал).

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

Это много информации. Но нет причин иметь две директории Discourse. Просто создайте несколько файлов YML в директории containers. Назовите их как угодно.

2 лайка

Спасибо за подтверждение. Именно так мы и настроились (один каталог) после экспериментов сегодня.

Всё прошло хорошо с конфигурацией из 2 контейнеров (2CC), но возникают трудности с настройкой обратного прокси-сервера nginx на macOS.

Не удаётся установить рабочее соединение с сокетом Unix в каталоге /shared, хотя сокет доступен извне контейнера. Пробовали через nginx, а также Python и socat (для тестирования). Всегда ошибка 61 — соединение отказано. Хм-м-м.

Весь день застрял на ошибке «соединение отказано»!!

Завтра будет новый день.

У меня небольшой вопрос.
Если у нас есть только одна конфигурация контейнера (только ‘app.yml’) и мы выполняем команду ./launcher bootstrap app,
то остановится ли наш веб-сайт/фронтенд или нет?

Если да, то почему команда ‘bootstrap web-only’ не останавливает наш сайт?

Если нет, то в чём преимущество двухконтейнерной настройки с точки зрения экономии времени при пересборке контейнера? Иными словами, если мы можем поддерживать работу сайта даже во время запуска bootstrap для единственного контейнера, зачем нам нужны два отдельных контейнера?

1 лайк

Нет, если вы создадите новый файл .yml, назовите его, например, new_image

При правильной настройке процесс бутстрапа не запускает и не останавливает какие-либо службы. Образы, прошедшие бутстрап, не запущены. Именно поэтому их называют «обработанными через бутстрап».

Однако вам нужно создать новый файл yml, так как необходимо создать новый образ с новым именем.

Таким образом, с новым именем образа новый обработанный через бутстрап образ еще не запущен. Он лишь «прошел бутстрап».

Вы можете собрать новый образ с новым именем (не app), и этап сборки будет завершен. Назовем его «new_image».

Затем, если мы захотим заменить запущенный образ, назовем его «old_image», вы можете сделать следующее:

./launcher stop old_image; ./launcher start new_image

В вашем случае:

./launcher bootstrap new_image          #образ данных и веб-образ "app" запущены
./launcher stop app; ./launcher start new_image

Поскольку контейнер данных уже собран, вы экономите время: (1) не собирая образ данных и (2) избегая простоя при пересборке веб-образа, так как можете собрать его (пройти бутстрап образа), пока другие образы работают.

Это значительно быстрее; однако это не так быстро, как использование обратного прокси-сервера перед контейнерами.

В вашем исходном вопросе у вас есть запущенный образ с именем app. Если вы попытаетесь снова выполнить бутстрап этого образа с именем app, то вы будете пересобирать тот же образ (с тем же именем). Это не сэкономит вам время, как подсказывала ваша интуиция.

Теперь всё ясно?

Если нет, пожалуйста, задайте вопрос. Каждый может учиться; и обучение — это увлекательно. На самом деле это легко понять, но потребуется немного времени, если вы новичок в этих концепциях.

2 лайка

Если быть честным, я ничего не понял. Я пытался, но у меня не вышло.

Мой вопрос был простым.
Если у нас есть настройка с двумя контейнерами и мы «запускаем» (не пересобираем) наш «только веб»-контейнер, то наш текущий веб-контейнер продолжает работать (поскольку наш сайт отображается как работающий/в порядке).

А если у нас настройка с одним контейнером, например, «app.yml», и мы запускаем этот контейнер «app», будет ли наш сайт продолжать работать?

И если объяснение простое, то почему это так?

1 лайк

Может, вам стоит нанять кого-нибудь, чтобы помочь?

1 лайк

Никаких проблем.

Это был лишь небольшой вопрос. На его часть, возможно, можно было бы ответить «да» или «нет».

Ничего серьёзного.

1 лайк

Мой совет — просто попробуйте сделать это самостоятельно.

На самом деле, протестировать это в собственной тестовой среде занимает меньше времени, чем задавать вопрос и ждать ответа на форуме.

Если вы попробуете, то получите ответ на свой вопрос; поэтому стоит проводить такие эксперименты в тестовом сценарии, чтобы не сломать ваше рабочее приложение :slight_smile:

Discourse — это проект с открытым исходным кодом, который можно бесплатно скачать и настроить благодаря щедрости соучредителей. Это означает, что вы можете и должны воспользоваться этим: создавать, удалять, воссоздавать и снова удалять тестовые приложения столько раз, сколько захотите.

Если вы не готовы приложить усилия для выполнения базовых задач системного администратора, @codinghorror предложил отличную идею — нанять местных специалистов.

1 лайк

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

Нет. Два процесса базы данных не могут одновременно обращаться к одним и тем же файлам.

3 лайка

Ох!!
Спасибо за разъяснение этого момента.

1 лайк

Таким образом, если база данных не находится в создаваемом вами контейнере, вы можете создать новый веб-контейнер, который будет работать с этой базой данных, в то время как другой веб-контейнер продолжит функционировать.

1 лайк

Быстрый вопрос по этому подходу: как в этой конфигурации выполнять Rebuilds?

Если мы с нуля используем настройку из двух контейнеров, которую добавил @pfaffman, то у нас будут два контейнера: “app”: “data” и “web_only”.

Все операции Rebuild и прочее направлены на контейнер “web_only”, но что-то нужно делать с контейнером “data” или за это отвечает bootstrap контейнера “web_only”? (Просто спрашиваю, потому что я провожу тесты, и контейнер data никогда не останавливается ни на момент).

Мы используем «три контейнера» и обратный прокси-сервер nginx:

  1. data
  2. socket1
  3. socket2

Допустим, сейчас запущен socket1 (то, что вы называете «только веб»), и мы хотим пересобрать и протестировать что-то. Мы пересоберём socket2, пока socket1 продолжает работать. Поскольку каждый из этих контейнеров использует доменный сокет Unix, они могут работать одновременно, так как общие сокеты находятся в разных файлах (разных путях).

Затем, допустим, socket2 собран и готов к работе.

Я делаю следующее:

ln -sf /var/discourse/shared/socket2/nginx.http.sock /var/run/nginx.http.sock

Теперь мы работаем на socket2.

Если, например, возникла проблема, я могу просто сделать следующее:

ln -sf /var/discourse/shared/socket1/nginx.http.sock /var/run/nginx.http.sock

и мы вернёмся к тому, что было раньше, работая на socket1.

Для интереса я добавил немного кода в профиль входа bash, чтобы при входе на сервер мне всегда сообщали, какой сокет (контейнер) работает:

Last login: Fri May 15 09:39:39 2020 from 159.192.33.138
srw-rw---- 1 root docker  0 May  5 07:38 /var/run/docker.sock
lrwxrwxrwx 1 root root   44 May 15 09:16 /var/run/nginx.http.sock -> /var/discourse/shared/socket/nginx.http.sock

Надеюсь, это поможет.

На мой взгляд, это (в целом) правильный подход (и это мой «стандарт по умолчанию» в продакшене, а не в staging или dev), но это лишь моё мнение, у каждого может быть своё. Это требует немного больше усилий при настройке, но, на мой взгляд, оно того стоит (в продакшене).


Предупреждение:


Хорошей идеей будет хранить копии ваших исходных файлов шаблонов yml в надёжном месте, поскольку шаблоны могут быть перезаписаны, и это может стать проблемой. Поэтому мы склонны «сохранять все yml», «сначала выполнить pull», затем «проверить yml» и только потом выполнять bootstrap (из-за излишней осторожности).

2 лайка

Вам не нужно пересобирать контейнер с данными, если только не произошло обновление PostgreSQL (как только что) или Redis (что произошло примерно за последние 6 месяцев).

В большинстве случаев вам просто нужно заменить web_only на app в приведённых инструкциях. Мои заметки доступны по ссылке: Managing a Two-Container Installation - Documentation - Literate Computing Support

5 лайков

То есть в этом подходе у вас есть два веб-экземпляра, и вы используете простой для тестирования с теми же данными? Это интересно, обязательно попробую, как только разберусь с этой стратегией «попыток сэкономить место» :stuck_out_tongue:

Большое спасибо. Прочитал вашу статью от начала до конца, только один небольшой вопрос: здесь вы имели в виду данные, верно? (то есть пересобирать данные вместо web_only), как сказано в статье? Или, возможно, есть какой-то другой трюк, который я упускаю (например, при крупном обновлении нужно объединить их, а затем снова разделить).

Да, мы тестируем, добавляем изменения и пересобираем в одном контейнере на основе сокетов, в то время как другой работает.

Да, оба используют один и тот же контейнер с данными. Контейнер с данными «не заботится» о том, с каким веб-приложением он «общается».

На самом деле это очень просто, как только вы поймёте базовую идею запуска контейнеров через общие Unix-сокеты, а не через внешние TCP/IP-порты.

Мы не считаем, что это занимает много места, но, с другой стороны, мы не запускаем (в продакшене) с ограниченным пространством, так как дисковое место не является дорогим.

1 лайк

Спасибо за подробности. Только что попробовал настроить два «контейнера приложений», указывающих на один Data Container в тестовой среде, и всё работает как по маслу.

Однако я не могу перенести это в продакшн, так как не могу пересобрать контейнер данных в продакшн, и не хочу ничего менять, пока это не будет решено. Система просто прекращает работу с ошибкой discourse@discourse FATAL: terminating connection due to administrator command.