Как устроено наше зеркало

Mirror Selectel

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

Свое зеркало пакетов, в котором хранятся копии репозиториев популярных linux-систем, есть и у нашей компании. В этой статье мы хотели бы подробно рассказать о его устройстве.

Запуская в 2010 году проект облачных серверов, мы выбрали для них модель установки net-install, при которой дистрибутивы устанавливаются «родным» исталлятором с одного из официальных зеркал. Благодаря такой модели можно всегда получать актуальные версии ПО со всеми последними изменениями, внесенными мейнтейнерами дистрибутива. Еще одно преимущество модели net-install заключается в том, что она позволяет избавиться от целого ряда проблем, связанных с клонированными инстансами (необходимость генерации SSH-ключей, UUID’ов файловых систем и т.п.).

В качестве основного зеркала мы выбрали mirror.yandex.ru, потому что оно близко расположено и содержит все нужные нашим клиентам репозитории. Сначала оно нас вполне устраивало. Но потом случилось непредвиденное. Число установок росло, инженеры налегали на тестирование шаблонов; в конце концов Яндекс, возмутившись огромным количеством одинаковых запросов, просто закрыл доступ к своему зеркалу для наших подсетей.

Мы стали искать решение, с помощью которого можно было бы обеспечить стабильность и свести вероятность возникновения внештатных ситуаций к минимуму. У нас возникла следующая идея: поднять nginx в качестве проксирующего сервера для нескольких зеркал. Такое решение казалось нам вполне разумным и надежным: даже если один из аплинков упадет, мы без проблем сможем скачать файлы с другого. Однако мы сразу же столкнулись с проблемой разнородной структуры зеркал: например, репозиторий CentOS на одном аплинке мог лежать в /centos, на другом — в /CentOS, а на третьем — вообще в /www/mirror/srv/pub/centos.

Так как универсальные зеркала, содержащие репозитории всех нужных нам дистрибутивов (CentOS, Debian, Ubuntu, OpenSUSE) можно пересчитать по пальцам, для каждого из дистрибутивов приходилось составлять отдельный список зеркал.

Претворив описанную идею в жизнь, мы столкнулись с куда более серьезными трудностями:

  • скорость работы аплинков отличается непостоянством: очень часто бывает так, что один и тот же хост отдает 5-10 Mб/с, а уже чере пару часов — не более 5-20 Кб/с. Так как инсталлятор скачивает пакеты один за другим, из-за перепадов в скорости установка может затянуться на неопределенное время;
  • некоторые аплинки могли быть неправильно сконфигурированы: бывало так, что в ответ на запрос вместо RPM-пакета они получали HTML-страницу «It works!»;
  • на некоторых аплинках могли отсутствовать указанные в каталоге пакеты. Или же пакеты присутствовали, но имели неверные контрольные суммы. Такое могло происходить, например, из-за нарушенного порядка синхронизазции с апстримом: сначала индексные файлы, а потом пакеты, а не наоборот. Ошибки могли возникать и по причине неправильно настройки rsync, который записывал файлы in place, а не сохранял содержимое во временный файл с последующей атомарной заменой.

Из-за всех этих трудностей у нас не раз случался сбой автоматической установки. Чтобы раз и навсегда избавиться от сбоев, мы создали собственное зеркало — mirror.selectel.ru. Оно доступно только с IP-адресов Селектела (исходящий трафик для нас платный и предоставить его общественности мы не рискуем, ибо получить 10-20 гигабит можно запросто).

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

  • синхронизация с аплинками происходит без прерывания обслуживания клиентов и никак не затрагивает отдаваемую им рабочую копию;
  • синхронизированная копия заменяет текущую только в том случае, если у всех новых пакетов сходятся контрольные суммы;
  • если аплинк по какой-то причине не доступен или возвращает ошибочные данные, зеркало продолжает отдавать данные со старой, но рабочей копии;
  • синхронизация аплинков разделена по дистрибутивам: для некоторых дистрибутивов ее можно проводить реже, чем для других. Имеется также возможность частичного клонирования некоторых репозиториев.

С этого зеркала осуществляется установка операционных систем на выделенные серверы.

Как устроены репозитории

Как правило, репозитории состоят из двух основных частей: каталог (индекс) и пул (хранилище пакетов).

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

В пуле хранятся сами файлы пакетов. Они могут быть разложены в соответствии с какой-либо иерархией или просто сложены в одну директорию.

RPM-репозитории

В корне каждого RPM-репозитория находится директория с файлами каталога — repodata. Описание всех секций каталога хранится в файле repomd.xml. Каждая секция представлена отдельным файлом в директории каталога. В описании указан путь к файлу, содержащему секцию, а также его контрольная сумма.

Содержимое файла repomd.xml может выглядеть, например, так:

<?xml version="1.0" encoding="UTF-8"?>
<repomd xmlns="http://linux.duke.edu/metadata/repo" xmlns:rpm="http://linux.duke.edu/metadata/rpm">
<revision>1362531727</revision>
<data type="primary"> <!-- Описание секции primary - XML база данных содержащая информацию о пакетах репозитория -->
<checksum type="sha256">87aa4c4e19f9a3ec93e3d820f1ea6b6ece8810cb45f117a16354465e57a1b50d</checksum>
<open-checksum type="sha256">77b5cfcf2c06156858a14a52595e1f69cd8cbb58c09699a3ea4391379260e943</open-checksum>
<location href="repodata/87aa4c4e19f9a3ec93e3d820f1ea6b6ece8810cb45f117a16354465e57a1b50d-primary.xml.gz"/>
<timestamp>1362531876</timestamp>
<size>2043735</size>
<open-size>12931923</open-size>
</data>
<data type="primary_db"> <!-- Описание секции primary_db - то же что и primary только в sqlite БД -->
<checksum type="sha256">243fdef956d09cb6d022e894e40d145f497bcf3d6d2bed79814e1c88452b9d29</checksum>
<open-checksum type="sha256">533872a158160ac3a83746a676c125b5cfb2411725079502b0d5be4f4d05196e</open-checksum>
<location href="repodata/243fdef956d09cb6d022e894e40d145f497bcf3d6d2bed79814e1c88452b9d29-primary.sqlite.bz2"/>
<timestamp>1362531897.21</timestamp>
<database_version>10</database_version>
<size>3605913</size>
<open-size>14942208</open-size>
</data>
...
</repomd>

RPM-каталог состоит из следующих секций:

  • primary — содержит описание всех пакетов, хранимых в репозитории, пути к файлам этих пакетов и их контрольные суммы;
  • filelists — содержит списки файлов, входящих в каждый пакет;
  • group — содержит в себе описания групп пакетов, устанавливаемых с помощью yum groupinstall;
  • other — содержит дополнительную информацию (например, журналы изменений — changelogs).

Структурирование и группировка пакетов для разных ОС организованы по-разному. Например, CentOS хранит все файлы пакетов в директории Packages, расположенной в корне репозитория. Кроме того, для каждой из имеющихся архитектур создан отдельный репозиторий.

OpenSUSE хранит пакеты для всех архитектур в одном репозитории с раздельными пулами в директориях i686/x86_64/etc.

DEB-репозитории

В DEB-репозиториях все пакеты хранятся в общем пуле. Это позволяет избежать дублирования пакетов, входящих в разные релизы. Для каждого релиза в репозитории создан отдельный каталог.

Разбор каталога начинается с файла /dists/[distribution]/Release (distribution здесь означает кодовое имя релиза — squeeze/wheezy/jessie). В нем содержится перечень компонентов релиза, а также информация о размере и контрольных суммах всех индексных файлов. Release-файл подпиcывается мейнтенерами архива; подпись хранится в файле Release.gpg(иногда содержимое Release вместе с подписью может находится в файле InRelease).

Описание содержимого пула находится в индексных файлов двух типов: Packages(в них перечислены бинарные пакеты) и Sources (в них перечислены исходники).

Путь к файлу Packages — /dists/[distribution]/[component]/binary-[architecture]/Packages, а к файлу Sources — /dists/[distribution]/[component]/source/Sources.

Примечание: иногда индексные файлы сжимаются с помощью gzip или bzip2 — в этом случае к имени файла соответственно добавляется расширение .gz или .bz2 . Некоторые клиенты поддерживают LZMA(.lzma), XZ(.xz) и LZIP(.lz).

Приведем пример записи из файла Packages:

Package: openssh-server
Source: openssh
Version: 1:6.2p2-6
Installed-Size: 747
Maintainer: Debian OpenSSH Maintainers 
Architecture: amd64
Replaces: openssh-client (<= 2.16), libcomerr2 (>= 1.01), libgssapi-krb5-2 (>= 1.10+dfsg~), libkrb5-3 (>= 1.6.dfsg.2), libpam0g (>= 0.99.7.1), libselinux1 (>= 1.32), libssl1.0.0 (>= 1.0.1), libwrap0 (>= 7.6-4~), zlib1g (>= 1:1.1.4), openssh-client (= 1:6.2p2-6), sysv-rc (>= 2.88dsf-24) | file-rc (>= 0.8.16), libpam-runtime (>= 0.76-14), libpam-modules (>= 0.72-9), adduser (>= 3.9), dpkg (>= 1.9.0), lsb-base (>= 4.1+Debian3), procps
Recommends: xauth, ncurses-term
Suggests: ssh-askpass, rssh, molly-guard, ufw, monkeysphere, openssh-blacklist, openssh-blacklist-extra
Conflicts: rsh-client (<< 0.16.1-1), sftp, ssh (<< 1:3.8.1p1-9), ssh-krb5 (<< 1:4.3p2-7), ssh-nonfree (<< 2), ssh-socks, ssh2
Description: secure shell (SSH) server, for secure access from remote machines
Multi-Arch: foreign
Homepage: http://www.openssh.org/
Description-md5: 842cc998cae371b9d8106c1696373919
Tag: admin::login, implemented-in::c, interface::daemon, network::server,
protocol::ssh, role::program, security::authentication,
security::cryptography, use::login, use::transmission
Section: net
Priority: optional
Filename: pool/main/o/openssh/openssh-server_6.2p2-6_amd64.deb
Size: 257438
MD5sum: 1f18e568c17d81cc2c493ee48c93a03f
SHA1: 207f131bbd4d709a47bcb69c997520c998ed7593
SHA256: 242b7f041292dea0702b24e19dc6355f47147796b227f1024665920a493641f2

Как работает наше зеркало

Репозиторий каждого дистрибутива на зеркале хранится в двух экземплярах: теневом (background) и рабочем (foreground). Обе части лежат на отдельном LVM-томе, что позволяет на ходу добавлять им дисковое пространство. В рабочей части хранится проверенная копия зеркала, она раздавается с помощью nginx. Теневая часть синхронизируется с upstream-зеркалом, а затем проходит тщательную проверку на валидность.

Процедура валидации включает проверку каталога, его цифровой подписи (если таковая имеется), а также проверку контрольных сумм всех индексных файлов. Проверить контрольные суммы всех пакетов довольно затруднительно: в пулах некоторых репозиториев могут храниться пакеты на десятки, а то и на сотни гигабайт. Поэтому контрольные суммы проверяются только у новых пакетов, до которых «дотронулся» rsync. После проверки теневая и рабочая часть меняются местами. Эта операция производится при помощи простого mv. Таким образом можно практически обеспечить атомарность подмены (достаточно трех быстрых вызовов mv, чтобы поменять директории местами) и минимизировать возможный простой. Отдача открытых файлов во время замены не прекращается.

После того, как две части поменялись местами, теневая часть локально «догоняется» до актуального состояния из рабочей копии.

Mirror-sync

Описанный выше алгоритм реализован в нашем наборе скриптов под названием mirror-sync, недавно опубликованном на GitHub под лицензией GNU GPL. Надеемся, что наши наработки окажутся полезными широкой аудитории, и кто-то из наших читателей воспользуется нашим опытом при создании собственного зеркала. Все комментарии, содержащие замечания и предложения по улучшению зеркала, мы обязательно учтем в дальнейшей работе.