Обновление из Китая не удаётся из-за проблем с git

Мы развернули экземпляр Discourse на сервере Ubuntu 20.04 от Aliyun/Alibaba, и, как и в случае со всеми вопросами, связанными с Git, мы сталкиваемся с проблемами из-за Великого файрвола. Ручное обновление с помощью команды launcher rebuild app чаще всего завершается ошибкой из-за сбоев GnuTLS (различных типов). Это не связано с версиями Git, установленными на сервере; на самом деле проблема заключается в обработке рукопожатий внутри ГФВ. Конечно, мы не понимаем деталей, но несколько источников подробно обсуждают этот вопрос. Поэтому компиляция Git вручную с использованием OpenSSL также не является вариантом.

Иногда процесс pull проходит мимо ядра, даже удается клонировать плагин Docker Manager, но после 2–3 загрузок плагинов обычно происходит тайм-аут или возникает другая ошибка.

Пример:

$ ./launcher rebuild app
Ensuring launcher is up to date
Fetching origin
Launcher is up-to-date
Stopping old container
+ /usr/bin/docker stop -t 60 app
app
cd /pups && git pull && git checkout v1.0.3 && /pups/bin/pups --stdin
fatal: unable to access 'https://github.com/discourse/pups.git/': gnutls_handshake() failed: The TLS connection was non-properly terminated.
76630913bae18d6b45b6b3ecc3ec390c1e69222a493f2ecf424ee06adf9d1002
** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one.
./discourse-doctor may help diagnose the problem.

Также довольно распространена следующая ошибка:

fatal: unable to access 'https://github.com/discourse/discourse.git/': GnuTLS recv error (-54): Error in the pull function.

Возможное решение 1
Обычно при клонировании из GitHub использование SSH вместо HTTPS дает лучшие результаты или не приводит к сбоям, но из-за уникальной задачи пересборки в Discourse я не имею представления, где и что нужно настроить, чтобы launcher выполнял pull через SSH вместо HTTPS. Возможно ли настроить экземпляр Discourse таким образом?

Возможное решение 2
В качестве альтернативы у меня есть доступ к SOCKS5-прокси, чтобы обойти ГФВ для просмотра порнографии и получить доступ к заблокированным ресурсам из Китая. Я знаю, что Git можно настроить для использования протокола socks://, но, к сожалению, я не понимаю, как и где настроить конфигурацию в Discourse, чтобы процессы pull в launcher Discourse могли использовать прокси. Я хотел бы избежать настройки через git config --global для пользователя root, а вместо этого внести эту информацию в конфигурацию репозиториев Discourse. Может ли кто-нибудь подсказать, как этого добиться?

Это неудобно, так как мы используем этот экземпляр Discourse в нашей интранет-сети, и в настоящее время наш экземпляр фактически неработоспособен уже более месяца, что, безусловно, серьезно влияет на нашу работу.

Поможет ли передача переменных окружения прокси в секции env файла app.yml?

А вот решение для Rubygem под GFW:

Вы видели Replace rubygems.org with taobao mirror to resolve network error in China

Большое спасибо. Ruby-гемы не вызывают проблем, так как мы с самого начала внедрили шаблон, упомянутый в вашем посте, в наш app.yml, и всё работает как по маслу.

Речь идёт о клонировании основного репозитория и репозиториев плагинов.

Мне нужно разобраться с переменными окружения для флагов Git, но, к сожалению, я плохо разбираюсь в Docker и особенно в файлах docker-compose. Не могли бы вы указать какой-либо источник, где я мог бы найти информацию?

Насколько мне известно, Discourse не использует docker-compose.

Я считаю, что добавление следующей команды в хук before_web должно помочь, как это сделано в web.china.template.yml:

git config --global http.proxy socks5://вашпрокси:порт

А если после сборки прокси больше не нужен, добавьте следующее в хук after_web:

git config --global --unset http.proxy

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

Еще одно доказательство того, насколько я не разбираюсь в Docker. Да, это очевидно не файл docker-compose. Разве это называется «Docker file»? Или этот термин относится к config.json? В любом случае, ваш совет указал мне верное направление, только хук должен называться before_code, а не before_web.

Коротко: Настройте SOCKS5 с помощью shadowsocks-libev, прослушивайте локальную машину на адресе 172.17.0.1 (не localhost), передайте информацию о прокси, как в вашем сообщении, и пересоберите приложение.

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

Это немного не по теме, но моя проблема заключается не в том, чтобы запустить приложение, а в том, что существующая конфигурация redis-server — которая у нас есть на другой машине — не соответствует реальному текущему состоянию приложения. Поэтому я не могу запустить контейнер и отключить компоненты тем через графический интерфейс, так как при их клонировании возникает тайм-аут.

Большое спасибо за ссылку на ваше объяснение, но я хотел бы добавить несколько замечаний, так как пример подходит не совсем идеально.

  1. Я не понимаю этого, извините? Команда env выдает мне много информации, но ничего, связанного с моим gitconfig.
  2. Так как я не понял пункт 1), я не смог разобраться, какие переменные нужно передать. Я также не добавлял git flags в секцию env в app.yml, а вызывал их через hook.
  3. Это было не обязательно, так как я не хочу, чтобы весь контейнер работал через прокси SOCKS, а только процесс git fetch, но, полагаю, этот пункт больше относился к исходному случаю использования в теме, на которую вы сослались.

Но еще раз спасибо, ваш ответ указал мне верное направление. Палец вверх команде Discourse! :ok_hand:

Вы покупали Aliyun и выбирали регион в материковом Китае?

Версии Aliyun для Гонконга и международных рынков не сталкиваются с этой проблемой.

Также, возможно, вы уже обсуждали эту деталь, но если вы этого не сделали, вот скрипт, который вы можете запустить для установки git через openssl

Безболезненное ручное обновление из Китая

шаги

  1. Создайте SOCKS5-прокси за пределами Китая
  2. Настройте и сконфигурируйте подключение к прокси на сервере в Китае
  3. Создайте шаблон для упрощения редактирования
  4. Добавьте настройки прокси git в шаблон
  5. Включите шаблон в app.yml
  6. Пересоберите приложение

1 — удаленный SOCKS5

Для удобства использования (и благодаря их гибкой ценовой политике) рекомендую развернуть сервер Digital Ocean, например, в Сингапуре. Используйте стандартный сервер Ubuntu, выполните все базовые настройки безопасности (SSH-ключи, UFW и т. д.), затем установите Shadowsocks:

на удаленной машине
$ sudo apt install shadowsocks-libev

Сконфигурируйте настройки прокси:

$ cd /etc/shadowsocks-libev

# Я предпочитаю сохранять оригинальные файлы
$ sudo cp config.json orig.config.json
$ sudo nano config.json

Обратите особое внимание на таймаут и метод шифрования:

{
    "server":"123.123.123.123", # IP удаленного сервера
    "server_port":8400, # на ваше усмотрение
    "local_port":1080,
    "password":"Swordfish", 
    "timeout":600, # <= обязательно!
    "method":"chacha20-ietf-poly1305"
}

Обязательно дважды проверьте все настройки в конфигурации systemd (/lib/systemd/system/shadowsocks-libev-local@.service). Включите службу shadowsocks-libev-local@.service, перезагрузите систему и проверьте, что служба запущена.

2 — настройка подключения к прокси на сервере в Китае

на машине Discourse

$ sudo apt install shadowsocks-libev

Если вы используете Aliyun, найдите настройки брандмауэра в их специфической консоли и проверьте соответствующие настройки портов.

Вам не нужно возиться с настройками systemd на клиентской машине, но держите отдельные конфигурационные файлы для Docker и обычного использования, так как вы можете захотеть использовать SOCKS5-прокси вне контекста Docker. В таком случае вам нужно будет использовать 127.0.0.1 вместо сетевых адресов, доступных для Docker.

$ cd /etc/shadowsocks-libev
$ sudo cp config.json local.json
$ sudo cp config.json docker.json

Адаптируйте конфигурацию примерно так:

$ sudo nano local.json

{
    "server":["123.123.123.123"], # IP удаленной машины
    "mode":"tcp_and_udp", # эта аннотация отличается из-за разных версий shadowsocks-libev в моей настройке
    "server_port":8400,
    "local_address":"127.0.0.1",
    "local_port":1080,
    "password":"Swordfish",
    "timeout":600, # <= убедитесь в этом
    "method":"chacha20-ietf-poly1305"
}

Для удобства добавим алиас в наш .bashrc:

$ nano ~/.bashrc

# вставьте
alias dockershadow='ss-local -c /etc/shadowsocks-libev/local.json'

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

$ sudo nano docker.json

{
    "server":["123.123.123.123"],
    "mode":"tcp_and_udp",
    "server_port":8400,
    "local_address":"172.17.0.1",
    "local_port":1080,
    "password":"Swordfish",
    "timeout":600,
    "method":"chacha20-ietf-poly1305"
}

Установите алиас для использования конфигурации, специфичной для Docker:

alias dockershadow='ss-local -c /etc/shadowsocks-libev/docker.json'

3 и 4 — создание шаблона для поддержания app.yml в порядке

Это абсолютно необязательно и зависит от ваших предпочтений; я предпочитаю, чтобы app.yml был читаемым и коротким, а компоненты хранились отдельно. Назовите файл как вам угодно, я выбрал web.git.template.yml.

$ nano templates/web.git.template.yml
# вставьте:

hooks:
  before_code:
    - exec:
       cmd:
         - git config --global http.proxy socks5://172.17.0.1:1080
         - git config --global https.proxy socks5://172.17.0.1:1080
         - git config --global https.sslVerify = false 

# необязательно
  after_code:
    - exec:
       cmd:
         - git config --global --unset http.proxy
         - git config --global --unset https.proxy
         - git config --global --unset https.sslVerify

Я тестировал это с хуком after_web, но это не сработало.

5 — адаптация app.yml

Вызовите шаблон в вашем app.yml:

$ cd /<каталог discourse>
$ sudo nano containers/app.yml


templates:
  - "templates/web.template.yml"
  - "templates/web.china.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
  - "templates/web.git.template.yml"

Ваш раздел шаблонов, скорее всего, выглядит иначе, просто убедитесь, что включены web.china и web.git-blabla (или как вы его назвали) шаблоны.
Не открывайте порт 1080:1080 в вашем app.yml!

6 — пересборка

Перед пересборкой убедитесь, что настройки прокси работают при клонировании через git.

$ git config --global http.proxy socks5://172.17.0.1:1080
$ git config --global https.proxy socks5://172.17.0.1:1080
$ git config --global https.sslVerify = false 

Это, конечно, добавляет флаги прокси в файл .gitconfig вашего пользователя в домашнем каталоге, поэтому обязательно удалите их после тестирования.
Выберите случайный большой репозиторий на Github с множеством файлов и проверьте скорость клонирования. Если ваша конфигурация верна, вы должны иметь возможность клонировать со скоростью около 12–15 МБ/с в зависимости от вашей настройки Aliyun. Если скорость подключения медленно растет с 200 КБ/с до примерно 10 МБ/с, значит, ваши усилия были безуспешными.

Наконец, выполните пересборку:

$ cd /<каталог discourse>

# запустите прокси, используя установленный ранее алиас
$ dockershadow
$ ./launcher rebuild app

Процесс пересборки часто завершается неудачей, поэтому вам понадобится терпение (и, возможно, байцзю). Чем меньше плагинов указано в вашем app.yml, тем выше вероятность успешной пересборки.

7 — замечания

Я все еще считаю это обходным путем, а не готовым к использованию в production решением, поэтому, возможно, у кого-то есть идея, как зеркалить репозиторий GitHub в Китае, чтобы сделать этот процесс менее болезненным. И, как мы все знаем, непрозрачные механизмы внутри Великого китайского фаервола постоянно меняются.

Конечно, SOCKS5-прокси — лишь один из многих вариантов, но мне нравятся многоцелевые решения.

Если у кого-то есть идея, как сделать этот обходной путь пригодным для production, я буду благодарен за ваш вклад. Discourse — фантастическое программное обеспечение, но я предполагаю, что одной из причин его широкого неиспользования в Китае являются громоздкие процессы установки и обслуживания. Попытки обновления через GUI давали мне 100% неудач в прошлом году, независимо от настроек таймаута, которые я конфигурировал в моем обратном прокси nGinx.

Китайский перевод будет добавлен позже

Точно. Поскольку основное назначение этого экземпляра — быть частью корпоративной интрасети, к сожалению, вариант с Гонконгом не подходит из-за задержек. Кроме того, предстоящие клиентские экземпляры будут работать с пользователями из материкового Китая — как только я разберусь с аутентификацией через Weixin, поэтому мне нужно рабочее решение для зон Aliyun в материковом Китае.

Спасибо большое. Я изучил несколько руководств по этой теме, но поскольку основная проблема заключается не в самом TLS-аутентификации Git, а в проверке рукопожатия в процессах инспекции пакетов GFW, я воздержался от этого подхода. Компиляция git с openssl может открыть двери в новый мир, полный боли, как я читал.

Большинство компонентов темы также загружаются из GitHub при сборке (или при запуске контейнера?), поэтому, возможно, существует другой хук для добавления git-прокси, который может помочь. Не удаляйте прокси, если хотите, чтобы он работал с графическим интерфейсом. И, похоже, redis-server не может быть причиной этого.

redis-server стал ещё одной проблемой, усложнившей процесс восстановления. Получился своего рода замкнутый круг: внешняя конфигурация Redis изменилась, а состояние приложения до восстановления требовало именно этой конфигурации для запуска. Однако восстановление не удавалось, так как не работала загрузка компонентов темы.
Мне повезло: после 20–20 попыток восстановления компоненты темы наконец обновились и были загружены.

В общем контексте архитектуры приложения было бы полезно иметь документацию о том, как выполнять восстановление в «безопасном режиме», то есть независимо от плагинов или тем. Я не смог найти хук для обработки компонентов темы или способ отключить (в отличие от удаления) плагины, что стало разочарованием.

Редактирование: Вау, теперь я вижу ссылку на безопасный режим. Раньше я её не находил (в Китае у нас нет Google, пытаемся найти что угодно подходящее через Bing..). Боже, это очень бы помогло!

Итак, вы указали управляемый Redis-сервер, например Discourse with DO managed Redis - #3 by Falco, но пересборка не удалась?

Проблема с Redis была второстепенной, но добавила значительную сложность к общей проблеме с Git. Как вы можете видеть из моего подробного поста выше, я решил эти вопросы.

Да, мы подключили распределённый кластер Redis к нашему Discourse с самого начала. Однако он не управляется, а просто работает на других машинах.

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

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

https://meta.discourse.org/t/a-fork-of-discourse-docker-repo-for-china

В случае возникновения проблем даже при добавлении настройки HTTP-прокси:

GnuTLS recv error (-110): The TLS connection was non-properly terminated.

Помимо оригинального решения, добавьте свойства postBuffer, указанные ниже, в шаблон, чтобы решить мою проблему. Для установки требуется пакет gnutls-bin.

hooks:
  before_code:
    - exec:
       cmd:
         - apt-get update -y
         - apt-get install -y gnutls-bin
         - git config --global http.proxy socks5://172.17.0.1:1080
         - git config --global https.proxy socks5://172.17.0.1:1080
         - git config --global https.sslVerify false 
         - git config --global http.postBuffer 1048576000

# необязательно
  after_code:
    - exec:
       cmd:
         - git config --global --unset http.proxy
         - git config --global --unset https.proxy
         - git config --global --unset https.sslVerify
         - git config --global --unset http.postBuffer