Преимуществом Linux является отсутствие необходимости перезагружать машину после установки обновлений или новых программ. Кроме одного случая – обновления ядра ОС.
Если сервисы, предоставляемые клиентам, – это виртуальные окружения, то при необходимости можно переносить с одного сервера на другой, чем и можно воспользоваться для обновления ОС. Кроме того, ошибки загрузки сервера после обновления также никак не скажутся на пользователях.
Обратной стороной такой миграции является ощутимая потребность в дополнительных ресурсах – в идеале вам надо иметь под рукой столько же свободных машин, сколько вы собираетесь обновлять. Но даже в такой ситуации обновление может занять заметное время, а если мощностей не хватает и обновление машин проводится поэтапно, то процесс может сильно затянуться.
Наконец третья альтернатива – внести необходимые изменения непосредственно в работающее ядро без перезагрузки ОС, абсолютно прозрачно для всех работающих приложений. Звучит заманчиво, и инструменты для такого обновления существуют. Но, как нетрудно догадаться, универсальными их назвать нельзя, иначе о перезагрузке серверов можно было бы забыть. Ниже мы подробнее рассмотрим механизм работы подобных инструментов и присущие им ограничения.
Заплатки без перезагрузки
Наиболее старым из существующих инструментов для обновлений ядра Linux без перезагрузки является Ksplice. Изначально разработанный студентами MIT на основе магистерской диссертации одного из них [3] ныне Ksplice принадлежит компании Oracle и является одним из основных компонентов ее дистрибутива Oracle Linux. Изначально коммерческие сервисы Ksplice были доступны для ядер RHEL и CentOS, однако в настоящее время эти системы в число официально поддерживаемых не входят, хотя на сайте ksplice по-прежнему доступна пробная подписка на 30 дней для RHEL. Также имеется бесплатная версия ksplice для Ubuntu Desktop и Fedora.
Работает ksplice на уровне функций ядра, загружая в память новые версии функций и добавляя в начало кода старых функций передачу управления их новым реализациям. Для отката наложенных изменений достаточно восстановить замененную часть кода функций.
Для осуществления подмены адресов функций ksplice останавливает выполнение всех процессов в системе и убеждается, что никто из них не находится в процессе выполнения одной их обновляемых процедур. Таким образом, на некоторое время система все-таки приостанавливает свою работу, однако эти миллисекунды простоя мало заметны по сравнению с минутами, которых потребовала бы полная перезагрузка. Впрочем, чем больше функций вы хотите подменить, тем теоретически выше время ожидания. А некоторые базовые функции (например, функцию планировщика schedule()) обновить не получится вовсе, поскольку они всегда находятся в состоянии использования одним из потоков ядра.
Подготовка обновления для ядра производится на основе патча для его исходного кода и скомпилированного образа ядра. Инструментарий ksplice анализирует такой патч и бинарный код собранного ядра и определяет, какие изменения необходимо внести в работающее ядро, и подготавливает соответствующий патч.
Использование для анализа уже собранного ядра, а не его исходного кода позволяет избежать проблем, связанных с поведением компилятора, – например, современные компиляторы любят сделать функцию встраиваемой (inline), даже если программист это не указал явно. Вызов такой функции в других местах программы заменяется непосредственно ее кодом, и при наложении патча необходимо этот код заменять на новый. В итоге фактически необходимо подменить все процедуры, которые используют нашу встроенную функцию, на новые реализации. В свою очередь это увеличивает размер патча, а то и вовсе может сделать его создание невозможным.
Помимо подмены функций, ksplice позволяет вносить изменения в структуры данных – если эти изменения не меняют семантики данных, то они могут быть применены в полностью автоматическом режиме, при изменении же семантики при создании патча потребуется написать некоторое количество вспомогательного кода.
Вносить какие-либо изменения в ядро непосредственно для работы ksplice не требуется – инструментарий теоретически способен работать с любыми ядрами без модификаций.
Однако недостаток ksplice с точки зрения большинства системных администраторов и производителей дистрибутивов – сильная привязанность к конкретному производителю, который фактически контролирует весь проект.
Неудивительно, что основные конкуренты Oracle на рынке дистрибутивов Linux – Red Hat и SUSE – занялись созданием альтернативных решений и в 2014 году представили свои наработки всему миру. Примерно в это же время увидел свет проект KernelCare – проприетарный инструментарий от компании CloudLinux .
Red Hat и SUSE решили не держать свои наработки при себе и в начале 2014 года открыли код проектов, получивших названия Kpatch и kGraft соответственно.Оба инструментария нацелены в первую очередь на подмену функций и используют для этого штатный механизм ядра Ftrace. «Патч» для наложения в обоих случаях оформляется как подгружаемый модуль ядра. Тем не менее как в механизме применения патчей, так и в их формате и процедуре их подготовки у Kpatch и kGraft есть существенные различия. В частности, при применении патчей Kpatch, как и ksplice, проверяет, что ни один из процессов не выполняет функции, которые будут обновлены. Как уже говорилось выше, для этого приходится ненадолго останавливать все процессы в системе. kGraft же работает с каждым процессом отдельно – некоторые из них при этом могут использовать старые варианты функций, некоторые – уже новые. Постепенно все процессы в системе переходят на использование новых функций. Необходимости останавливать их при этом нет. Правда, нельзя и сказать заранее, когда именно закончится такая процедура применения патча (и закончится ли вообще).
Оба проекта являются открытыми, и разработчики были бы не прочь внедрить свои наработки в основное ядро, однако наличие двух альтернативных реализаций для схожего функционала в основном ядре – явное излишество. Поэтому разработчики Red Hat и SUSE решили объединить усилия в рамках проекта livepatch, объединяющего работающие на уровне ядра Linux части обоих проектов в некоторый универсальный компонент, пригодный для наложения как патчей Kpatch, так и патчей kGraft. Так что выбор, какой формат патчей использовать, остается за пользователем (а точнее, за компаниями, предоставляющими такой сервис, о чем мы поговорим ниже).
Работа над объединенным livepatch несколько затормозила развитие собственно Kpatch и kGraft. Тем не менее Kpatch был включен в Red Hat Enterprise Linux 7 в состоянии technical preview, а kGraft – в SUSE Linux Enterprise 12. Доработанная версия Kpatch лежит в основе инструментария ReadyKernel, включенного в состав дистрибутива Virtuozzo Linux 7 и платформы виртуализации Virtuozzo 7. Именно на его основе мы продемонстрируем, как эти программы работают в реальной жизни.
Устанавливаем Kpatch в Virtuozzo
Пакеты kpatch и kpatch-kmod, необходимые для работы сервиса ReadyKernel, установлены по умолчанию в Virtuozzo 7 и в дистрибутиве Virtuozzo Linux 7 (лежащем в основе Virtuozzo, но доступного и в виде отдельного продукта). Kpatch-kmod содержит модуль ядра, управляющий процессом наложения изменений, а kpatch предоставляет утилиты командной строки для загрузки/выгрузки таких модулей.
Непосредственно патчи, содержащие исправления уязвимостей для ядра, находятся в отдельном пакете, имя которого начинается с readykernel-patch. Для установки пакета, соответствующего загруженному в системе ядру, необходимо выполнить команду:
# yum install -y readykernel-patch-$(uname -r)
Пакет readykernel-patch содержит один модуль ядра, в котором находится кумулятивный патч сразу для всех уязвимостей и ошибок, которые можно исправить в текущем ядре без его перезагрузки.
При установке пакета этот модуль автоматически загружается и будет загружаться каждый раз при старте системы с этим ядром.
Суммарную информацию о загруженном модуле и исправляемых им уязвимостей можно узнать с помощью команды readykernel info (см. рис. 1).
По мере подготовки новых исправлений для данной версии ядра эти исправления будут добавляться все в тот же пакет readykernel-patch и автоматически загружаться при его обновлении. Все эти процессы происходят автоматически, так что в штатном режиме работы достаточно установить пакет и не забывать его обновлять (либо настроить автоматическую установку новых версий).
Сводную информацию об уязвимостях в различных ядрах, которые можно исправить с помощью ReadyKеrnel, можно найти на сайте сервиса (рис. 2).
На момент написания статьи сервис поддерживал только ядра продуктов Virtuozzo, однако в будущем планируется добавить поддержку и других дистрибутивов.
Если у вас возникла необходимость либо просто желание более детально посмотреть на работу инструмента, то нужно обратиться к утилите kpatch.
С ее помощью осуществляются базовые действия с модулями kpatch:
- kpatch list – отображает список загруженных модулей;
- kpatch load/unload – загружает/выгружает заданный модуль. Можно указать опцию —all для загрузки/выгрузки всех имеющихся модулей (для ReadyKernel эта опция большого смысла не имеет, поскольку для каждого ядра есть всего один модуль, однако в общем случае ничто не мешает иметь несколько модулей, накладывающих разные исправления);
- kpatch install/uninstall – добавляет/убирает автоматическую загрузку указанных модулей при старте системы;
- kpatch replace – загружает указанный модуль, предварительно выгрузив все уже загруженные.
Загрузку модулей kpatch можно отменить при старте системы, передав ядру параметр kpatch.enable=0.
На этом набор знаний, необходимых для работы с kpatch, исчерпывается – как мы видим, в штатном режиме работы системный администратор может и не вспоминать о его существовании. Однако это все при условии, что у вас есть сервис наподобие ReadyKernel, предоставляющий готовые бинарные патчи для вашего ядра. Но как формируются эти патчи и насколько сложно подготовить их самостоятельно?
Подготовка бинарных патчей
Для создания модулей kpatch с бинарными патчами служит утилита kpatch-build из одноименного пакета, которую надо снабдить исходным кодом ядра, использовавшимся при его сборке файлом конфигурации, отладочной информацией ядра и собственно патчем для исходного кода, который необходимо превратить в бинарный патч и оформить как модуль. Подготовив все необходимое, можно запустить утилиту:
$ kpatch-build -s <путь к исходному коду ядра> ↵ -c <путь к конфигурационному файлу> ↵ -v <путь к образу ядра с отладочной информацией> ↵ <путь к патчу для исходного кода>
Если патч затрагивает только сам образ ядра, но не его модули, то можно добавить опцию -t vmlinux. Она укажет kpatch-build, что можно проигнорировать сборку модулей, тем самым ускорив процесс сборки.
Kpatch-build собирает модуль с именем kpatch-<patch_ name>.ko. По умолчанию этот модуль содержит отладочную информацию, которую можно убрать с помощью команды strip —strip-debug.
Для сборки бинарных патчей рекомендуется использовать ровно такую же версию компилятора gcc, которая использовалась для сборки целевого ядра. Нередко эта версия отличается от той, что установлена в ОС по умолчанию. Например, ядро для Ubuntu 14.04 собирается посредством gcc 5.2, в то время как в самой системе установлен gcc 4.8. Разные версии gcc могут генерировать немного различающийся код, а даже минимальное различие будет рассмотрено как изменение, которое необходимо включить в бинарный патч. Чем обернется внесение такого изменения в работающее ядро, предсказать сложно.
Может показаться, что подготовка бинарного патча не очень-то и сложна, однако самое время вспомнить о том, что далеко не все изменения можно накладывать на ядро в процессе его работы. И самое важное при подготовке бинарного патча – это определить, а имеет ли смысл это делать, и не нужно ли предварительно модифицировать исходный патч так, чтобы устранить возможные проблемы при его наложении во время работы (ведь создатели оригинального патча вряд ли заботились о том, чтобы его можно было применить «на лету»). Частично в этом помогает сама утилита kpatch-build, способная определить потенциально опасные изменения, однако обходиться совсем без ручного анализа патча не рекомендуется.
В итоге анализ исправлений и их преобразование в бинарные патчи для kpatch превращается в достаточно трудоемкое занятие, требующее немалых познаний в исходном коде ядра Linux. Разработчики уверяют, что свод правил по подготовке патчей, если их кто-нибудь когда-нибудь напишет, будет размером с книгу.
Ситуация с kGraft, livepatch и ksplice принципиально ничем не отличается. Неудивительно, что разработчики всех подобных технологий делают ставку не на продажу самих инструментов (и, как следствие, большинство из них открыто), а на подписку на готовые патчи, которые можно будет с помощью этих инструментов накладывать. При этом все производители (Oracle, SUSE, Virtuozzo) предоставляют пробный период, в течение которого можно бесплатно попробовать инструментарий в действии.
Эффективность
Основная проблема систем типа Kpatch – это то, что не все изменения можно применить без перезагрузки. Но какова доля таких изменений в реальной жизни? Ведь если большинство исправлений безопасности и критических ошибок применить не получится, то и большого смысла в таких инструментах нет?
Детальный анализ этой проблемы был проведен в работе изначальные авторы Ksplice проанализировали 64 критических исправления безопасности в ядре Linux, сделанных с мая 2005 по май 2008 года. Из них ksplice в полностью автоматическом режиме смог обработать 56, а остальные могли быть применены после доработки программистами. Разработчики Kpatch утверждали, что их детище способно автоматически создать патчи для 80% всех известных уязвимостей
Конечно, коммерческие компании редко рассказывают о том, что у них не получилось, однако согласно заверениям Рисунок 3.
Список уязвимостей в ядре, не обновлявшемся полтора года, сильно превосходит размеры экрана Администрирование 10 октябрь 2016 системный администратор тонкая настройка Oracle, патчи Ksplice применялись подписчиками сервиса уже более четырех миллионов раз и предотвратили четыре миллиона перезагрузок серверов. К тому же вы всегда можете посмотреть, для каких уязвимостей есть патчи в том или ином сервисе, а для каких – нет.
Сайт Ksplice поддерживает базу данных об уязвимостях в ядрах некоторых дистрибутивов и предоставляет внешний REST API для доступа к ней. Так что обратиться к этой базе можно, например, с помощью утилиты curl. С помощью следующей команды можно узнать, какие уязвимости имеются в работающем у вас ядре:
$ (uname -s; uname -m; uname -r; uname -v) | ↵ curl https://uptrack.api.ksplice.com/api/1/update-list/ ↵ -L -H "Accept: text/text" --data-binary @
Для CentOS 7 с ядром 3.10.0-229.el7, к моменту написания данной статьи не обновлявшимся более полугода, список выглядит внушительно (см. рис. 3).
Правда, сервис не отображает критичность тех или иных уязвимостей, к тому же многие из них могут оказаться для вас не актуальны – например, если не используете виртуализацию, то проблемы модулей KVM вас вряд ли обеспокоят.
Таким образом, инструменты наподобие Kpatch (в совокупности с сервисами наподобие ReadyKernel) могут принести немалую пользу, прикрыв бреши в системе без прерывания ее работы. Считать их панацеей нельзя – некоторые обновления они провести не в состоянии, и придется-таки иногда перегружать систему. Однако перезагрузиться мы всегда успеем – так почему бы предварительно не попробовать подлатать ядро «по живому»?
Услуги поддержки и администрирования серверов, подробнее [email protected]