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

Зеркалом называется копия данных одного информационного ресурса на другом. Зеркала используются для предоставления доступа к копиям информации через несколько источников. С помощью зеркал, например, осуществляется распространение дистрибутивов *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. Надеемся, что наши наработки окажутся полезными широкой аудитории, и кто-то из наших читателей воспользуется нашим опытом при создании собственного зеркала. Все комментарии, содержащие замечания и предложения по улучшению зеркала, мы обязательно учтем в дальнейшей работе.

Что еще почитать по теме

T-Rex 30 марта 2021

Что такое SMTP-протокол и как он устроен?

SMTP (Simple Mail Transfer Protocol) — протокол передачи почты. Он был представлен еще в 1982 году, но не теряет актуальности до сих пор. В статье разбираемся, какие задачи решает протокол и как он ра…
T-Rex 30 марта 2021
Владимир Туров 1 сентября 2020

Дело совершенно секретного iPod

Это был обычный серый день в конце 2005 года. Я сидел на рабочем месте и писал код для следующей версии iPod. Вдруг без стука ворвался директор ПО для iPod, начальник моего начальника, и закрыл дверь.
Владимир Туров 1 сентября 2020
T-Rex 21 августа 2020

TrendForce: цены на SSD упадут

Эксперты DRAMeXchange предсказывают значительное падение цен на оперативную память и твердотельные накопители в ближайшее время. Причина — сокращение спроса на чипы для NAND и DRAM.
T-Rex 21 августа 2020

Новое в блоге

Михаил Фомин 24 июня 2022

Docker Swarm VS Kubernetes — как бизнес выбирает оркестраторы

Рассказываем, для каких задач бизнесу больше подойдет Docker Swarm, а когда следует выбрать Kubernetes.
Михаил Фомин 24 июня 2022
Ульяна Малышева 30 сентября 2022

«Нулевой» локальный диск. Как мы запустили облако только с сетевыми дисками и приручили Ceph

Чем хороши сетевые диски и почему именно Ceph, рассказал директор по развитию ядра облачной платформы Иван Романько.
Ульяна Малышева 30 сентября 2022
Валентин Тимофеев 30 сентября 2022

Как проходит онбординг сотрудников ИТО? Что нужно, чтобы выйти на смену в дата-центр

Рассказываем, как обучаем новых сотрудников, какие задачи и испытания проходят инженеры прежде, чем выйти на свою первую смену.
Валентин Тимофеев 30 сентября 2022
T-Rex 28 сентября 2022

Книги по SQL: что почитать новичкам и специалистам

Собрали 6 книг, которые помогут на старте изучения SQL и при углублении в тему.
T-Rex 28 сентября 2022