Обзор
Rc-система — это init-подсистема FreeBSD, которая управляет всем жизненным циклом системы от момента, когда ядро передаёт управление в userland, до корректного завершения работы. Она определяет, какие сервисы запускаются, в каком порядке, с какими зависимостями, и предоставляет единый интерфейс для управления ими в runtime.
В отличие от systemd в Linux, rc — это набор shell-скриптов с детерминированным порядком исполнения. Это делает систему полностью прозрачной и отлаживаемой: любой аспект поведения можно проследить, прочитав конкретный sh-скрипт. Trade-off — отсутствие параллельного запуска, сокет-активации и встроенной интеграции с cgroups/namespaces (хотя service jails частично закрывают последний пункт).
Историческая эволюция
Утилита rc(8) впервые появилась в 4.0BSD в 1980 году. Классический процесс загрузки был предельно прост: ядро BSD запускало init(8), который передавал управление скрипту /etc/rc, а тот вызывал /etc/netstart для настройки сети и /etc/rc.local для запуска пользовательских демонов.
Исторический BSD имел монолитный стартовый скрипт /etc/rc, который выполнял все задачи перехода в многопользовательский режим. Проблема монолитного подхода была в отсутствии контроля над отдельными компонентами: чтобы перезапустить один демон, администратору приходилось вручную находить процесс, убивать его и восстанавливать параметры запуска из скрипта.
В 2000 году NetBSD начала модернизацию этой подсистемы: сначала появился каталог /etc/rc.d для отдельных скриптов каждого сервиса, затем команда rcorder(8) для определения порядка запуска. FreeBSD интегрировала систему rc.d из NetBSD в 2002 году.
Процесс загрузки
При загрузке ядро запускает init(8) (PID 1), который выполняет /etc/rc. Далее rc действует по следующей логике:
При автозагрузке устанавливается флаг rc_fast=yes, который предотвращает проверку уже запущенных процессов для ускорения загрузки. Затем rc загружает /etc/rc.subr — библиотеку shell-функций, используемую всеми скриптами. После этого загружаются конфигурационные файлы и определяется, выполняется ли загрузка в jail (в этом случае скрипты с ключевым словом nojail пропускаются).
Далее вызывается rcorder(8) для упорядочивания файлов в /etc/rc.d/, не имеющих ключевого слова nostart. Скрипты выполняются последовательно до early_late_divider (по умолчанию это виртуальная точка FILESYSTEMS, то есть момент, когда смонтированы корень и остальные критичные файловые системы). После этого rcorder(8) вызывается повторно, уже включая скрипты из каталогов local_startup (по умолчанию /usr/local/etc/rc.d/), и выполнение продолжается.
Двухфазная загрузка (early/late) — архитектурно важный момент. early_late_divider определяет границу между стадиями: ранняя стадия содержит все сервисы, необходимые для монтирования дисков, чтобы поздняя стадия могла включать скрипты из local_startup. Это решает проблему курицы и яйца: скрипты из /usr/local/etc/rc.d/ не могут быть прочитаны, пока /usr/local не смонтирован.
При завершении работы /etc/rc.shutdown отбирает через rcorder(8) только скрипты с ключевым словом shutdown, разворачивает порядок и вызывает их с аргументом faststop — предварительные проверки вроде чтения PID-файла при этом пропускаются ради скорости выключения.
rcorder: механизм разрешения зависимостей
rcorder(8) — ключевой компонент системы. Он разбирает метаданные зависимостей в заголовках скриптов и выстраивает их в топологически корректном порядке. Программа rcorder(8) анализирует комментарии в начале каждого стартового скрипта для определения порядка вызова сервисов с учётом зависимостей.
Доступные директивы:
PROVIDE — объявляет, какой сервис (или виртуальную «возможность») предоставляет скрипт. Имя в PROVIDE — это то, на что могут ссылаться REQUIRE и BEFORE других скриптов. Рекомендуется совпадение имени файла, значения PROVIDE и переменной $name — это упрощает отладку.
REQUIRE — перечисляет сервисы, которые должны быть запущены до данного скрипта. Если скрипт A содержит REQUIRE: B, то A будет запущен после B.
BEFORE — обратная логика: скрипт будет запущен до перечисленных сервисов. Используется реже и с осторожностью.
KEYWORD — теги, влияющие на поведение скрипта в определённых контекстах: shutdown (запускать скрипт при выключении), nojail (не запускать в jail-окружении), nojailvnet (не запускать в jail без VNET), firstboot (запускать только при первой загрузке), nostart (не запускать автоматически).
Существуют также виртуальные PROVIDE-точки (ALLUPPERCASE), которые обозначают этапы загрузки, а не конкретные сервисы; в порядке запуска это FILESYSTEMS, NETWORKING, SERVERS, DAEMON, LOGIN. Они работают как барьеры — группируют скрипты по стадиям. Например, REQUIRE: LOGIN означает «запустить после того, как система полностью готова к пользовательским сервисам», и это обязательное требование для скриптов, запускающих процессы от имени непривилегированных пользователей.
Посмотреть вычисленный порядок запуска можно командой:
rcorder /etc/rc.d/* /usr/local/etc/rc.d/* 2>/dev/null
Это одна из сильных сторон init-системы rc(8) — просто определить и настроить порядок запуска сервисов, тогда как более сложный systemd(1) может загружаться быстрее, но значительно сложнее в отладке.
rc.conf: центральная конфигурация
/etc/rc.conf — единственный файл, который определяет, что и как запускается в системе. Файл rc.conf содержит описательную информацию о локальном имени хоста, параметрах конфигурации сетевых интерфейсов и сервисах, которые должны быть запущены при начальной загрузке.
Архитектура конфигурации трёхуровневая:
/etc/defaults/rc.conf — дефолтные значения всех переменных. Этот файл никогда не редактируется — он перезаписывается при обновлении системы.
/etc/rc.conf — переопределения дефолтов. Именно здесь администратор включает сервисы, задаёт сетевые настройки, определяет hostname.
/etc/rc.conf.local — дополнительные переопределения для конкретной машины. Рекомендуемый подход для кластерных конфигураций: общие настройки в /etc/rc.conf, машино-специфичные в /etc/rc.conf.local. Файл rc.conf распространяется через rsync/puppet, rc.conf.local уникален для каждого хоста.
Помимо этих трёх файлов load_rc_config подхватывает пофайловые фрагменты из /etc/rc.conf.d/<имя сервиса> (и из каталогов rc.conf.d, соседних с каждым каталогом из local_startup); если по такому пути лежит не файл, а каталог, загружается всё его содержимое.
Все эти файлы — это обычный sh(1)-код, который sourced последовательно: переменные, определённые позже, переопределяют более ранние. Типичные записи:
hostname="myserver.example.org"
ifconfig_vtnet0="inet 10.0.0.1 netmask 255.255.255.0"
defaultrouter="10.0.0.254"
sshd_enable="YES"
nginx_enable="YES"
Принцип прост: для сервиса с именем foo переменная foo_enable="YES" включает его при загрузке. Если переменная не установлена или установлена в "NO" — скрипт при загрузке пропустит запуск.
Дополнительные переменные для сервиса foo: foo_flags (аргументы командной строки), foo_user (пользователь), foo_chroot (chroot-директория), foo_cpuset (привязка к CPU), foo_fib (FIB для setfib). Полный список возможностей документирован в rc.conf(5).
rc.subr: фреймворк для скриптов
/etc/rc.subr — библиотека shell-функций, которая реализует всю логику управления сервисами. Именно она обеспечивает единообразное поведение команд start/stop/restart/status для всех скриптов. Благодаря ей типичный скрипт может состоять всего из нескольких строк sh(1)-кода.
Ключевая функция — run_rc_command "$1". Она принимает аргумент (start, stop, restart, status и др.) и выполняет соответствующее действие, включая: проверку rcvar в rc.conf, проверку required_files/required_dirs/required_vars, выполнение *_precmd перед основным действием, запуск/остановку процесса, работу с PID-файлами.
Стандартные аргументы, которые должен поддерживать каждый скрипт: start, stop, restart, status (проверяет, запущен ли сервис), rcvar (показывает, какие переменные rc.conf управляют сервисом), enabled (возвращает 0 если сервис включён).
Дополнительные аргументы: reload (отправляет SIGHUP для перечитывания конфига), poll (ожидает завершения процесса), а также enable, disable и delete, которые правят переменную _enable прямо в конфиге — в том файле, где она уже определена (это может быть и /etc/rc.conf.d/<имя сервиса>), иначе дописывают её в /etc/rc.conf. Скрипты могут определять собственные команды через extra_commands.
Модификаторы поведения:
onestart/onestop — запустить/остановить сервис однократно, игнорируя значение _enable в rc.conf. Безопасный способ запустить сервис, не включённый при загрузке.
forcestart/forcestop — как one*, но дополнительно пропускает все prerequisite-проверки и игнорирует ошибки. Использовать с осторожностью.
faststart — пропускает проверку на уже запущенный процесс.
Анатомия rc.d-скрипта
Минимальный рабочий скрипт для пользовательского сервиса:
#!/bin/sh
# PROVIDE: myapp
# REQUIRE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name="myapp"
rcvar=myapp_enable
command="/usr/local/sbin/myapp"
pidfile="/var/run/${name}.pid"
load_rc_config $name
: ${myapp_enable:="NO"}
run_rc_command "$1"
Разберём ключевые элементы:
Заголовок с PROVIDE/REQUIRE/KEYWORD — метаданные для rcorder(8). REQUIRE: LOGIN — стандартный выбор для пользовательских сервисов; означает запуск после того, как базовая система полностью инициализирована. KEYWORD: shutdown — означает, что сервис нужно корректно останавливать при выключении (без этого тега rc.shutdown проигнорирует скрипт).
. /etc/rc.subr — подключение фреймворка. Без этой строки ничего не работает.
name="myapp" — идентификатор сервиса. Определяет имя для service(8), переменные в rc.conf (myapp_enable, myapp_flags), и PID-файл по умолчанию.
rcvar=myapp_enable — связывает скрипт с переменной включения в rc.conf.
command — путь к бинарнику. rc.subr автоматически реализует start (запуск command), stop (kill по PID), status (проверка PID).
load_rc_config $name — загружает переменные из rc.conf, относящиеся к данному сервису.
: ${myapp_enable:="NO"} — дефолтное значение, если не задано в rc.conf. Bourne shell-синтаксис: установить переменную, только если она не определена или пуста.
run_rc_command "$1" — передать управление фреймворку.
Для сервисов, которые не демонизируются сами, используется daemon(8):
command="/usr/sbin/daemon"
command_args="-f -p ${pidfile} /usr/local/bin/myapp --some-flags"
Хуки для кастомизации: start_precmd, start_postcmd, stop_precmd, stop_postcmd — функции, вызываемые до/после соответствующих действий. Полезны для проверки конфигурации, создания директорий, ротации логов.
Управление сервисами в runtime
service(8)
Основной инструмент для взаимодействия с rc-скриптами:
service sshd status # проверить состояние
service sshd start # запустить (если включён в rc.conf)
service sshd stop # остановить
service sshd restart # перезапустить
service sshd reload # отправить SIGHUP (если поддерживается)
service sshd onestart # запустить однократно (даже если не включён)
service -e # список включённых сервисов
service -l # список всех доступных скриптов
service -r # полный rcorder-список скриптов, включённых и нет
service(8) — это обёртка, которая находит нужный скрипт в /etc/rc.d/ или /usr/local/etc/rc.d/ и вызывает его с переданным аргументом.
sysrc(8)
Безопасный инструмент для редактирования rc.conf из командной строки:
sysrc nginx_enable=YES # включить сервис
sysrc nginx_enable=NO # выключить
sysrc -n nginx_enable # показать текущее значение
sysrc -x nginx_enable # удалить переменную из rc.conf
sysrc -a # показать все установленные переменные
sysrc -f /etc/rc.conf.local hostname="node1" # записать в конкретный файл
sysrc предпочтительнее ручного редактирования — он корректно обрабатывает кавычки, проверяет синтаксис и работает атомарно.
Расположение скриптов
/etc/rc.d/ — скрипты base system. Сюда попадают rc-скрипты для sshd, cron, syslogd, pf, devd и других системных сервисов. Редактировать их не нужно и не рекомендуется — при обновлении системы они перезаписываются.
/usr/local/etc/rc.d/ — скрипты из пакетов и портов. nginx, postgresql, redis и прочий сторонний софт устанавливает свои скрипты сюда. Это следствие архитектурного разделения base/local, описанного в статье о файловой структуре.
Начиная с FreeBSD 6.1-RELEASE, локальные rc.d-скрипты (включая установленные из портов) включаются в общий rcorder(8) базовой системы. Это значит, что пользовательский скрипт в /usr/local/etc/rc.d/ может объявлять зависимости от системных сервисов — и rcorder корректно разрешит порядок запуска.
Service Jails
Механизм появился в FreeBSD 15.0; в ветке 14.x его нет — ни rc.subr, ни rc.conf(5) там про svcj ничего не знают. Service Jails позволяют автоматически запускать rc.d-сервис внутри jail(8) с минимальной конфигурацией — не более двух строк.
В отличие от ручной настройки jail с отдельной файловой системой и сетью, service jail наследует файловую систему хоста, но применяет jail-ограничения: изоляция процессов, ограничение сетевого доступа, запрет монтирования файловых систем.
Включение для конкретного сервиса:
nginx_svcj_options="net_basic"
nginx_svcj="YES"
Это не имеет тех же преимуществ безопасности, что и ручная настройка jail с отдельной файловой системой и IP/VNET, но значительно проще в настройке, обеспечивая при этом часть преимуществ jail — например, сокрытие процессов других пользователей.
Все базовые системные сервисы уже содержат конфигурацию для service jails (переменные *_svcj_options), хотя не все протестированы. Подробности — в rc.conf(5), раздел "SERVICE JAILS".
Отладка
Если сервис не запускается, последовательность диагностики:
service foo rcvar — показывает, какие переменные rc.conf управляют сервисом и их текущие значения. Первое, что нужно проверить.
service foo onestart — попытка запуска вне зависимости от rc.conf. Если работает — проблема в конфигурации, не в скрипте.
Чтение скрипта: less /usr/local/etc/rc.d/foo — скрипт целиком доступен для анализа, включая все зависимости, переменные и хуки.
sh -x /etc/rc.d/foo start — запуск с трассировкой shell. Показывает каждую исполняемую строку — наиболее мощный инструмент отладки.
Для диагностики порядка загрузки: rcorder /etc/rc.d/* /usr/local/etc/rc.d/* 2>/dev/null | grep foo — показывает позицию скрипта в последовательности.
Сравнение с systemd
Rc и systemd решают одну задачу, но с фундаментально разными подходами.
Rc — это shell-скрипты с последовательным исполнением. Полная прозрачность: любой администратор, знающий sh(1), может прочитать и понять любой аспект поведения системы. Порядок загрузки детерминирован — одинаков при каждой загрузке. Расширение — написание shell-скрипта. Недостатки: последовательный запуск (медленнее на системах с большим количеством сервисов), нет сокет-активации, нет встроенного мониторинга процессов (restart-on-failure требует daemon -r), нет нативной интеграции с cgroups.
Systemd — единый бинарный PID 1 с декларативными unit-файлами. Параллельный запуск, сокет-активация, встроенные cgroups, journald, таймеры, мониторинг состояния. Значительно сложнее внутри, отладка менее прозрачна, единая точка отказа.
Для серверных задач с десятками сервисов разница во времени загрузки обычно незначительна. Преимущество rc — в предсказуемости и простоте отладки, что на production-серверах часто важнее скорости загрузки.
Источники
rc(8): man.freebsd.org/cgi/man.cgi?query=rc&sektion=8rc.conf(5): man.freebsd.org/rc.confrc.subr(8): man.freebsd.org/cgi/man.cgi?query=rc.subr&sektion=8rcorder(8): man.freebsd.org/cgi/man.cgi?query=rcorder&sektion=8service(8): man.freebsd.org/cgi/man.cgi?query=service&sektion=8sysrc(8): man.freebsd.org/cgi/man.cgi?query=sysrc&sektion=8- Practical rc.d scripting in BSD: docs.freebsd.org/en/articles/rc-scripting/
- FreeBSD Handbook, Configuration and Services: docs.freebsd.org/en/books/handbook/config/
- Klara Systems, rc(8): FreeBSD Services and Automation: klarasystems.com/articles/rc8-freebsd-services-and-automation/
- Alexander Leidinger, FreeBSD Service Jails: leidinger.net/blog/2024/06/11/freebsd-service-jails-another-layer-in-the-security-onion/
Связанные заметки
- FreeBSD — архитектура и философия — вводная статья цикла
- FreeBSD — pkg и Ports Collection — откуда берутся rc-скрипты в
/usr/local/etc/rc.d - FreeBSD — сеть — конфигурация интерфейсов через
ifconfig_*в rc.conf - Linux — systemd: init, сервисы, таймеры — альтернативная модель init в Linux, для сравнения
- FreeBSD — сводный cheatsheet — быстрая справка по управлению сервисами
Из библиотеки института уносят только знания. Читателей здесь не считают: посещение не регистрируется, отметок в издании не остаётся, счётчиков нет ни у одного издания фонда.
Издание пополняется и пересверяется. Текст доступен на условиях CC BY-SA 4.0 — унесённое можно передавать дальше на тех же условиях.