8179 lines
696 KiB
TeX
8179 lines
696 KiB
TeX
\documentclass[10pt,oneside,a4paper]{article}
|
||
\usepackage{cmap} % Copy-paste из PDF без проблем с кодировкой
|
||
\usepackage[utf8]{inputenc}
|
||
\usepackage[english,russian]{babel} % Русские переносы и проч.
|
||
\usepackage{graphicx,color,pmboxdraw}
|
||
\usepackage[T1,T2A]{fontenc}
|
||
\usepackage{indentfirst} % Отступ в первом абзаце главы
|
||
\usepackage{fancyvrb} % Продвинутые листинги и in-line commands
|
||
% listings в данной ситуации, IMHO, избыточен
|
||
\usepackage{verbatim} % Окружение comment
|
||
\usepackage{pdflscape} % Внимание! При выводе в DVI выборочный
|
||
% поворот страниц работать не будет, хотя текст будет повернут.
|
||
\usepackage[colorlinks,unicode,urlcolor=blue]{hyperref}
|
||
% Заполняем поля PDF уже со включенной опцией unicode
|
||
\hypersetup{pdftitle={systemd для администраторов},%
|
||
pdfauthor={Lennart Poettering, Sergey Ptashnick}}
|
||
% Не засоряем оглавление подразделами
|
||
%\setcounter{tocdepth}{1} % А почему бы и нет?
|
||
% Несколько сокращений
|
||
\newcommand{\sectiona}[1]{\section*{#1}\addcontentsline{toc}{section}{#1}}
|
||
\newcommand{\subsectiona}[1]{\subsection*{#1}%
|
||
\addcontentsline{toc}{subsection}{#1}}
|
||
\newcommand{\hreftt}[2]{\href{#1}{\texttt{#2}}}
|
||
\newenvironment{caveat}[1][]{\smallskip\par\textbf{Предупреждение#1: }}%
|
||
{\smallskip\par}
|
||
\newcommand{\sfnote}[1]{\texorpdfstring{\protect\footnote%
|
||
{Прим. перев.: #1}}{}}
|
||
\newcommand{\qna}[1]{\medskip\par\textbf{Вопрос: #1}\nopagebreak\par Ответ:}
|
||
\newcommand\yousaywtf[1]{\emph{#1}}
|
||
\newcommand\yousaywtfsk[1]{\yousaywtf{#1}\medskip\par}
|
||
\newcommand\llquote{\texorpdfstring{<<}{"}}
|
||
\newcommand\rrquote{\texorpdfstring{>>}{"}}
|
||
% Настройка макета страницы
|
||
\setlength{\hoffset}{-1.5cm}
|
||
\addtolength{\textwidth}{2cm}
|
||
\setlength{\voffset}{-2cm}
|
||
\addtolength{\textheight}{3cm}
|
||
\addtolength{\footskip}{5pt}
|
||
% Настройка форматирования in-line commands
|
||
\DefineShortVerb{\+}
|
||
\VerbatimFootnotes
|
||
% И листингов
|
||
\definecolor{gray}{gray}{0.75}
|
||
\fvset{frame=leftline,rulecolor=\color{gray},framerule=1mm}
|
||
\definecolor{dgreen}{rgb}{0,0.6,0}
|
||
% Запрет висячих строк
|
||
\clubpenalty=10000
|
||
\widowpenalty=10000
|
||
|
||
\begin{document}
|
||
\sloppy
|
||
\title{systemd для администраторов}
|
||
\author{Lennart Poettering (автор)\thanks{Первоисточник (на английском
|
||
языке) опубликован на сайте автора: \url{http://0pointer.de/blog/projects}}\\%
|
||
Сергей Пташник (русский перевод)\thanks{Актуальная версия перевода
|
||
доступна на личной странице переводчика:
|
||
\url{http://www2.kangran.su/~nnz/pub/s4a/}}\\%
|
||
\small Данный документ доступен на условиях лицензии
|
||
\href{http://creativecommons.org/licenses/by-sa/3.0/legalcode}{CC-BY-SA 3.0
|
||
Unported}}
|
||
\maketitle
|
||
\tableofcontents
|
||
%\newpage
|
||
\sectiona{Предисловие автора}
|
||
Многие из вас, наверное, уже знают, что
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd}{systemd}~--- это новая
|
||
система инициализации дистрибутива Fedora, начиная с Fedora~14\footnote{Прим.
|
||
перев.: к сожалению, разработчики Fedora приняли решение оставить в Fedora~14 в
|
||
качестве системы инициализации по умолчанию upstart, однако systemd все равно
|
||
включен в этот релиз и может быть использован в качестве альтернативной системы
|
||
инициализации. Окончательный переход на systemd произошел лишь в Fedora~15.}.
|
||
Помимо Fedora, systemd также поддерживает и другие дистрибутивы, в частности,
|
||
\href{http://en.opensuse.org/SDB:Systemd}{OpenSUSE}\footnote{Прим. перев.:
|
||
Сейчас systemd поддерживается практически во всех популярных дистрибутивах для
|
||
настольных систем.}. systemd предоставляет администраторам целый ряд новых
|
||
возможностей, значительно упрощающих процесс обслуживания системы. Эта статья
|
||
является первой в серии публикаций, планируемых в ближайшие месяцы. В каждой из
|
||
этих статей я попытаюсь рассказать об очередной новой возможности systemd.
|
||
Большинство этих возможностей можно описать легко и просто, и подобные статьи
|
||
должны быть интересны довольно широкой аудитории. Однако, время от времени мы
|
||
будем рассматривать ключевые новшества systemd, что может потребовать несколько
|
||
более подробного изложения.
|
||
\begin{flushright}
|
||
Lennart Poettering, 23 августа 2010~г.
|
||
\end{flushright}
|
||
|
||
\section{Контроль процесса загрузки}
|
||
\label{sec:verify}
|
||
|
||
Как правило, во время загрузки Linux по экрану быстро пробегает огромное
|
||
количество различных сообщений. Так как мы интенсивно работаем над
|
||
параллелизацией и ускорением процесса загрузки, с каждой новой версией
|
||
systemd эти сообщения будут пробегать все быстрее и быстрее, вследствие чего,
|
||
читать их будет все труднее. К тому же, многие пользователи применяют
|
||
графические оболочки загрузки (например, Plymouth), полностью скрывающие эти
|
||
сообщения. Тем не~менее, информация, которую они несут, была и остается
|
||
чрезвычайно важной~--- они показывают, успешно ли запустилась каждая служба, или
|
||
попытка ее запуска закончилась ошибкой (зеленое
|
||
\texttt{[~\textcolor{dgreen}{OK}~]} или красное
|
||
\texttt{[~\textcolor{red}{FAILED}~]} соответственно). Итак, с ростом скорости
|
||
загрузки систем, возникает неприятная ситуация: информация о результатах
|
||
запуска служб бывает очень важна, а просматривать ее все тяжелее. systemd
|
||
предлагает выход из этой ситуации: он отслеживает и запоминает факты успешного
|
||
или неудачного запуска служб на этапе загрузки, а также сбои служб во время
|
||
работы. К таким случаям относятся выходы с ненулевым кодом, ошибки
|
||
сегментирования и т.п. Введя +systemctl status+ в своей командной оболочке, вы
|
||
можете ознакомиться с состоянием всех служб, как <<родных>> (native) для
|
||
systemd, так и классических SysV/LSB служб, поддерживаемых в целях
|
||
совместимости:
|
||
|
||
\begin{landscape}
|
||
\begin{Verbatim}[fontsize=\small]
|
||
[root@lambda] ~# systemctl
|
||
UNIT LOAD ACTIVE SUB JOB DESCRIPTION
|
||
dev-hugepages.automount loaded active running Huge Pages File System Automount Point
|
||
dev-mqueue.automount loaded active running POSIX Message Queue File System Automount Point
|
||
proc-sys-fs-binfmt_misc.automount loaded active waiting Arbitrary Executable File Formats File System Automount Point
|
||
sys-kernel-debug.automount loaded active waiting Debug File System Automount Point
|
||
sys-kernel-security.automount loaded active waiting Security File System Automount Point
|
||
sys-devices-pc...0000:02:00.0-net-eth0.device loaded active plugged 82573L Gigabit Ethernet Controller
|
||
[...]
|
||
sys-devices-virtual-tty-tty9.device loaded active plugged /sys/devices/virtual/tty/tty9
|
||
-.mount loaded active mounted /
|
||
boot.mount loaded active mounted /boot
|
||
dev-hugepages.mount loaded active mounted Huge Pages File System
|
||
dev-mqueue.mount loaded active mounted POSIX Message Queue File System
|
||
home.mount loaded active mounted /home
|
||
proc-sys-fs-binfmt_misc.mount loaded active mounted Arbitrary Executable File Formats File System
|
||
abrtd.service loaded active running ABRT Automated Bug Reporting Tool
|
||
accounts-daemon.service loaded active running Accounts Service
|
||
acpid.service loaded active running ACPI Event Daemon
|
||
atd.service loaded active running Execution Queue Daemon
|
||
auditd.service loaded active running Security Auditing Service
|
||
avahi-daemon.service loaded active running Avahi mDNS/DNS-SD Stack
|
||
bluetooth.service loaded active running Bluetooth Manager
|
||
console-kit-daemon.service loaded active running Console Manager
|
||
cpuspeed.service loaded active exited LSB: processor frequency scaling support
|
||
crond.service loaded active running Command Scheduler
|
||
cups.service loaded active running CUPS Printing Service
|
||
dbus.service loaded active running D-Bus System Message Bus
|
||
getty@tty2.service loaded active running Getty on tty2
|
||
getty@tty3.service loaded active running Getty on tty3
|
||
getty@tty4.service loaded active running Getty on tty4
|
||
getty@tty5.service loaded active running Getty on tty5
|
||
getty@tty6.service loaded active running Getty on tty6
|
||
haldaemon.service loaded active running Hardware Manager
|
||
hdapsd@sda.service loaded active running sda shock protection daemon
|
||
irqbalance.service loaded active running LSB: start and stop irqbalance daemon
|
||
iscsi.service loaded active exited LSB: Starts and stops login and scanning of iSCSI devices.
|
||
iscsid.service loaded active exited LSB: Starts and stops login iSCSI daemon.
|
||
livesys-late.service loaded active exited LSB: Late init script for live image.
|
||
livesys.service loaded active exited LSB: Init script for live image.
|
||
lvm2-monitor.service loaded active exited LSB: Monitoring of LVM2 mirrors, snapshots etc. using dmeventd or progress polling
|
||
mdmonitor.service loaded active running LSB: Start and stop the MD software RAID monitor
|
||
modem-manager.service loaded active running Modem Manager
|
||
netfs.service loaded active exited LSB: Mount and unmount network filesystems.
|
||
NetworkManager.service loaded active running Network Manager
|
||
ntpd.service loaded maintenance maintenance Network Time Service
|
||
polkitd.service loaded active running Policy Manager
|
||
prefdm.service loaded active running Display Manager
|
||
rc-local.service loaded active exited /etc/rc.local Compatibility
|
||
rpcbind.service loaded active running RPC Portmapper Service
|
||
rsyslog.service loaded active running System Logging Service
|
||
rtkit-daemon.service loaded active running RealtimeKit Scheduling Policy Service
|
||
sendmail.service loaded active running LSB: start and stop sendmail
|
||
sshd@172.31.0.53:22-172.31.0.4:36368.service loaded active running SSH Per-Connection Server
|
||
sysinit.service loaded active running System Initialization
|
||
systemd-logger.service loaded active running systemd Logging Daemon
|
||
udev-post.service loaded active exited LSB: Moves the generated persistent udev rules to /etc/udev/rules.d
|
||
udisks.service loaded active running Disk Manager
|
||
upowerd.service loaded active running Power Manager
|
||
wpa_supplicant.service loaded active running Wi-Fi Security Service
|
||
avahi-daemon.socket loaded active listening Avahi mDNS/DNS-SD Stack Activation Socket
|
||
cups.socket loaded active listening CUPS Printing Service Sockets
|
||
dbus.socket loaded active running dbus.socket
|
||
rpcbind.socket loaded active listening RPC Portmapper Socket
|
||
sshd.socket loaded active listening sshd.socket
|
||
systemd-initctl.socket loaded active listening systemd /dev/initctl Compatibility Socket
|
||
systemd-logger.socket loaded active running systemd Logging Socket
|
||
systemd-shutdownd.socket loaded active listening systemd Delayed Shutdown Socket
|
||
dev-disk-by\x1...x1db22a\x1d870f1adf2732.swap loaded active active /dev/disk/by-uuid/fd626ef7-34a4-4958-b22a-870f1adf2732
|
||
basic.target loaded active active Basic System
|
||
bluetooth.target loaded active active Bluetooth
|
||
dbus.target loaded active active D-Bus
|
||
getty.target loaded active active Login Prompts
|
||
graphical.target loaded active active Graphical Interface
|
||
local-fs.target loaded active active Local File Systems
|
||
multi-user.target loaded active active Multi-User
|
||
network.target loaded active active Network
|
||
remote-fs.target loaded active active Remote File Systems
|
||
sockets.target loaded active active Sockets
|
||
swap.target loaded active active Swap
|
||
sysinit.target loaded active active System Initialization
|
||
|
||
LOAD = Reflects whether the unit definition was properly loaded.
|
||
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
|
||
SUB = The low-level unit activation state, values depend on unit type.
|
||
JOB = Pending job for the unit.
|
||
|
||
221 units listed. Pass --all to see inactive units, too.
|
||
[root@lambda] ~#
|
||
\end{Verbatim}
|
||
(Листинг был сокращен за счет удаления строк, не~относящихся к теме статьи.)
|
||
\end{landscape}
|
||
|
||
Обратите внимание на графу ACTIVE, в которой отображается обобщенный статус
|
||
службы (или любого другого юнита systemd: устройства, сокета, точки
|
||
монтирования~--- их мы рассмотрим подробнее в последующих статьях). Основными
|
||
значениями обобщенного статуса являются active (служба выполняется) и inactive
|
||
(служба не~была запущена). Также существуют и другие статусы. Например,
|
||
внимательно посмотрев на листинг выше, вы можете заметить, что служба ntpd
|
||
(сервер точного времени) находится в состоянии, обозначенном как maintenance.
|
||
Чтобы узнать, что же произошло с ntpd, воспользуемся командой
|
||
+systemctl status+\footnote{Прим. перев.: Стоит заметить, что формат вывода
|
||
данной команды менялся по мере развития systemd~--- появлялись дополнительные
|
||
поля с информацией, был добавлен вывод журнала службы
|
||
(см.~главу~\ref{sec:journal}) и т.д. Здесь приведен пример вывода этой команды
|
||
на момент написания исходной статьи (лето 2010 года).}:
|
||
\begin{Verbatim}[commandchars=\\\{\}]
|
||
[root@lambda] ~# systemctl status ntpd.service
|
||
ntpd.service - Network Time Service
|
||
Loaded: loaded (/etc/systemd/system/ntpd.service)
|
||
Active: \textcolor{red}{maintenance}
|
||
Main: 953 (code=exited, status=255)
|
||
CGroup: name=systemd:/systemd-1/ntpd.service
|
||
[root@lambda] ~#
|
||
\end{Verbatim}
|
||
|
||
systemd сообщает нам, что ntpd был запущен (с идентификатором процесса 953) и
|
||
аварийно завершил работу (с кодом выхода 255)\footnote{Прим. перев.:
|
||
Впоследствии, про просьбам пользователей, считавших, что слово <<maintenance>>
|
||
недостаточно точно отражает ситуацию, оно было заменено на <<failed>>.}.
|
||
|
||
В последующих версиях systemd, мы планируем добавить возможность вызова в
|
||
таких ситуациях ABRT (Automated Bug Report Tool), но для этого необходима
|
||
поддержка со стороны самого ABRT. Соответствующий запрос уже
|
||
\href{https://bugzilla.redhat.com/show_bug.cgi?id=622773}{направлен} его
|
||
разработчикам, однако пока не~встретил среди них поддержки.
|
||
|
||
Резюме: использование +systemctl+ и +systemctl status+ является современной,
|
||
более удобной и эффективной альтернативой разглядыванию быстро пробегающих по
|
||
экрану сообщений в классическом SysV. +systemctl status+ дает возможность
|
||
получить развернутую информацию о характере ошибки и, кроме того, в отличие
|
||
от сообщений SysV, показывает не~только ошибки при запуске, но и ошибки,
|
||
возникшие во время исполнения службы.
|
||
|
||
\section{О службах и процессах}
|
||
\label{sec:cgls}
|
||
|
||
В большинстве современных Linux-систем количество одновременно работающих
|
||
процессов обычно весьма значительно. Понять, откуда взялся и что делает тот
|
||
или иной процесс, становится все сложнее и сложнее. Многие службы используют
|
||
сразу несколько рабочих процессов, и это отнюдь не~всегда можно легко
|
||
распознать по выводу команды +ps+. Встречаются еще более сложные ситуации,
|
||
когда демон запускает сторонние процессы~--- например, веб-сервер выполняет
|
||
CGI-программы, а демон cron~--- команды, предписанные ему в crontab.
|
||
|
||
Немного помочь в решении этой проблемы может древовидная иерархия процессов,
|
||
отображаемая по команде +ps xaf+. Именно <<немного помочь>>, а не~решить
|
||
полностью. В частности, процессы, родители которых умирают раньше их самих,
|
||
становят потомками PID~1 (процесса init), что сразу затрудняет процесс
|
||
выяснения их происхождения. Кроме того, процесс может избавиться от связи с
|
||
родителем через две последовательные операции +fork()+ (в целом, эта возможность
|
||
признается нужной и полезной, и является частью используемого в Unix подхода
|
||
к разработке демонов). Также, не~будем забывать, что процесс легко может
|
||
изменить свое имя посредством +PR_SETNAME+, или задав значение
|
||
+argv[0]+, что также усложняет процесс его опознания\footnote{Прим.
|
||
перев.: Стоит отметить, что перечисленные ситуации могут возникнуть не~только
|
||
вследствие ошибок в коде и/или конфигурации программ, но и в результате злого
|
||
умысла. Например, очень часто встречается ситуация, когда установленный на
|
||
взломанном сервере процесс-бэкдор маскируется под нормального демона, меняя
|
||
себе имя, скажем, на httpd.}.
|
||
|
||
systemd предлагает простой путь для решения обсуждаемой задачи. Запуская
|
||
новый процесс, systemd помещает его в отдельную контрольную группу
|
||
с соответствующим именем. Контрольные группы Linux предоставляют очень
|
||
удобный инструмент для иерархической структуризации процессов: когда
|
||
какой-либо процесс порождает потомка, этот потомок автоматически включается в
|
||
ту же группу, что и родитель. При этом, что очень важно, непривилегированные
|
||
процессы не~могут изменить свое положение в этой иерархии. Таким образом,
|
||
контрольные группы позволяют точно установить происхождение конкретного
|
||
процесса, вне зависимости от того, сколько раз он форкался и переименовывал
|
||
себя~--- имя его контрольной группы невозможно спрятать или изменить. Кроме
|
||
того, при штатном завершении родительской службы, будут завершены и все
|
||
порожденные ею процессы, как бы они ни~пытались сбежать. С systemd уже
|
||
невозможна ситуация, когда после остановки web-сервера, некорректно
|
||
форкнувшийся CGI-процесс продолжает исполняться вплоть до последних секунд
|
||
работы системы.
|
||
|
||
В этой статье мы рассмотрим две простых команды, которые позволят вам
|
||
наглядно оценить схему взаимоотношений systemd и порожденных им процессов.
|
||
Первая из этих команд~--- все та же +ps+, однако на этот раз в ее параметры
|
||
добавлено указание выводить сведения по контрольным группам, а также другую
|
||
интересную информацию:
|
||
|
||
\begin{landscape}
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ ps xawf -eo pid,user,cgroup,args
|
||
PID USER CGROUP COMMAND
|
||
2 root - [kthreadd]
|
||
3 root - \_ [ksoftirqd/0]
|
||
[...]
|
||
4281 root - \_ [flush-8:0]
|
||
1 root name=systemd:/systemd-1 /sbin/init
|
||
455 root name=systemd:/systemd-1/sysinit.service /sbin/udevd -d
|
||
28188 root name=systemd:/systemd-1/sysinit.service \_ /sbin/udevd -d
|
||
28191 root name=systemd:/systemd-1/sysinit.service \_ /sbin/udevd -d
|
||
1096 dbus name=systemd:/systemd-1/dbus.service /bin/dbus-daemon --system --address=systemd: --nofork --systemd-activation
|
||
1131 root name=systemd:/systemd-1/auditd.service auditd
|
||
1133 root name=systemd:/systemd-1/auditd.service \_ /sbin/audispd
|
||
1135 root name=systemd:/systemd-1/auditd.service \_ /usr/sbin/sedispatch
|
||
1171 root name=systemd:/systemd-1/NetworkManager.service /usr/sbin/NetworkManager --no-daemon
|
||
4028 root name=systemd:/systemd-1/NetworkManager.service \_ /sbin/dhclient -d -4 -sf /usr/libexec/nm-dhcp-client.action -pf /var/run/dhclient-wlan0.pid -lf /var/lib/dhclient/dhclient-7d32a784-ede9-4cf6-9ee3-60edc0bce5ff-wlan0.lease -
|
||
1175 avahi name=systemd:/systemd-1/avahi-daemon.service avahi-daemon: running [epsilon.local]
|
||
1194 avahi name=systemd:/systemd-1/avahi-daemon.service \_ avahi-daemon: chroot helper
|
||
1193 root name=systemd:/systemd-1/rsyslog.service /sbin/rsyslogd -c 4
|
||
1195 root name=systemd:/systemd-1/cups.service cupsd -C /etc/cups/cupsd.conf
|
||
1207 root name=systemd:/systemd-1/mdmonitor.service mdadm --monitor --scan -f --pid-file=/var/run/mdadm/mdadm.pid
|
||
1210 root name=systemd:/systemd-1/irqbalance.service irqbalance
|
||
1216 root name=systemd:/systemd-1/dbus.service /usr/sbin/modem-manager
|
||
1219 root name=systemd:/systemd-1/dbus.service /usr/libexec/polkit-1/polkitd
|
||
1242 root name=systemd:/systemd-1/dbus.service /usr/sbin/wpa_supplicant -c /etc/wpa_supplicant/wpa_supplicant.conf -B -u -f /var/log/wpa_supplicant.log -P /var/run/wpa_supplicant.pid
|
||
1249 68 name=systemd:/systemd-1/haldaemon.service hald
|
||
1250 root name=systemd:/systemd-1/haldaemon.service \_ hald-runner
|
||
1273 root name=systemd:/systemd-1/haldaemon.service \_ hald-addon-input: Listening on /dev/input/event3 /dev/input/event9 /dev/input/event1 /dev/input/event7 /dev/input/event2 /dev/input/event0 /dev/input/event8
|
||
1275 root name=systemd:/systemd-1/haldaemon.service \_ /usr/libexec/hald-addon-rfkill-killswitch
|
||
1284 root name=systemd:/systemd-1/haldaemon.service \_ /usr/libexec/hald-addon-leds
|
||
1285 root name=systemd:/systemd-1/haldaemon.service \_ /usr/libexec/hald-addon-generic-backlight
|
||
1287 68 name=systemd:/systemd-1/haldaemon.service \_ /usr/libexec/hald-addon-acpi
|
||
1317 root name=systemd:/systemd-1/abrtd.service /usr/sbin/abrtd -d -s
|
||
1332 root name=systemd:/systemd-1/getty@.service/tty2 /sbin/mingetty tty2
|
||
1339 root name=systemd:/systemd-1/getty@.service/tty3 /sbin/mingetty tty3
|
||
1342 root name=systemd:/systemd-1/getty@.service/tty5 /sbin/mingetty tty5
|
||
1343 root name=systemd:/systemd-1/getty@.service/tty4 /sbin/mingetty tty4
|
||
1344 root name=systemd:/systemd-1/crond.service crond
|
||
1346 root name=systemd:/systemd-1/getty@.service/tty6 /sbin/mingetty tty6
|
||
1362 root name=systemd:/systemd-1/sshd.service /usr/sbin/sshd
|
||
1376 root name=systemd:/systemd-1/prefdm.service /usr/sbin/gdm-binary -nodaemon
|
||
1391 root name=systemd:/systemd-1/prefdm.service \_ /usr/libexec/gdm-simple-slave --display-id /org/gnome/DisplayManager/Display1 --force-active-vt
|
||
1394 root name=systemd:/systemd-1/prefdm.service \_ /usr/bin/Xorg :0 -nr -verbose -auth /var/run/gdm/auth-for-gdm-f2KUOh/database -nolisten tcp vt1
|
||
1495 root name=systemd:/user/lennart/1 \_ pam: gdm-password
|
||
1521 lennart name=systemd:/user/lennart/1 \_ gnome-session
|
||
1621 lennart name=systemd:/user/lennart/1 \_ metacity
|
||
1635 lennart name=systemd:/user/lennart/1 \_ gnome-panel
|
||
1638 lennart name=systemd:/user/lennart/1 \_ nautilus
|
||
1640 lennart name=systemd:/user/lennart/1 \_ /usr/libexec/polkit-gnome-authentication-agent-1
|
||
1641 lennart name=systemd:/user/lennart/1 \_ /usr/bin/seapplet
|
||
1644 lennart name=systemd:/user/lennart/1 \_ gnome-volume-control-applet
|
||
1646 lennart name=systemd:/user/lennart/1 \_ /usr/sbin/restorecond -u
|
||
1652 lennart name=systemd:/user/lennart/1 \_ /usr/bin/devilspie
|
||
1662 lennart name=systemd:/user/lennart/1 \_ nm-applet --sm-disable
|
||
1664 lennart name=systemd:/user/lennart/1 \_ gnome-power-manager
|
||
1665 lennart name=systemd:/user/lennart/1 \_ /usr/libexec/gdu-notification-daemon
|
||
1670 lennart name=systemd:/user/lennart/1 \_ /usr/libexec/evolution/2.32/evolution-alarm-notify
|
||
1672 lennart name=systemd:/user/lennart/1 \_ /usr/bin/python /usr/share/system-config-printer/applet.py
|
||
1674 lennart name=systemd:/user/lennart/1 \_ /usr/lib64/deja-dup/deja-dup-monitor
|
||
1675 lennart name=systemd:/user/lennart/1 \_ abrt-applet
|
||
1677 lennart name=systemd:/user/lennart/1 \_ bluetooth-applet
|
||
1678 lennart name=systemd:/user/lennart/1 \_ gpk-update-icon
|
||
1408 root name=systemd:/systemd-1/console-kit-daemon.service /usr/sbin/console-kit-daemon --no-daemon
|
||
1419 gdm name=systemd:/systemd-1/prefdm.service /usr/bin/dbus-launch --exit-with-session
|
||
1453 root name=systemd:/systemd-1/dbus.service /usr/libexec/upowerd
|
||
1473 rtkit name=systemd:/systemd-1/rtkit-daemon.service /usr/libexec/rtkit-daemon
|
||
1496 root name=systemd:/systemd-1/accounts-daemon.service /usr/libexec/accounts-daemon
|
||
1499 root name=systemd:/systemd-1/systemd-logger.service /lib/systemd/systemd-logger
|
||
1511 lennart name=systemd:/systemd-1/prefdm.service /usr/bin/gnome-keyring-daemon --daemonize --login
|
||
1534 lennart name=systemd:/user/lennart/1 dbus-launch --sh-syntax --exit-with-session
|
||
1535 lennart name=systemd:/user/lennart/1 /bin/dbus-daemon --fork --print-pid 5 --print-address 7 --session
|
||
1603 lennart name=systemd:/user/lennart/1 /usr/libexec/gconfd-2
|
||
1612 lennart name=systemd:/user/lennart/1 /usr/libexec/gnome-settings-daemon
|
||
1615 lennart name=systemd:/user/lennart/1 /usr/libexec/gvfsd
|
||
1626 lennart name=systemd:/user/lennart/1 /usr/libexec//gvfs-fuse-daemon /home/lennart/.gvfs
|
||
1634 lennart name=systemd:/user/lennart/1 /usr/bin/pulseaudio --start --log-target=syslog
|
||
1649 lennart name=systemd:/user/lennart/1 \_ /usr/libexec/pulse/gconf-helper
|
||
1645 lennart name=systemd:/user/lennart/1 /usr/libexec/bonobo-activation-server --ac-activate --ior-output-fd=24
|
||
1668 lennart name=systemd:/user/lennart/1 /usr/libexec/im-settings-daemon
|
||
1701 lennart name=systemd:/user/lennart/1 /usr/libexec/gvfs-gdu-volume-monitor
|
||
1707 lennart name=systemd:/user/lennart/1 /usr/bin/gnote --panel-applet --oaf-activate-iid=OAFIID:GnoteApplet_Factory --oaf-ior-fd=22
|
||
1725 lennart name=systemd:/user/lennart/1 /usr/libexec/clock-applet
|
||
1727 lennart name=systemd:/user/lennart/1 /usr/libexec/wnck-applet
|
||
1729 lennart name=systemd:/user/lennart/1 /usr/libexec/notification-area-applet
|
||
1733 root name=systemd:/systemd-1/dbus.service /usr/libexec/udisks-daemon
|
||
1747 root name=systemd:/systemd-1/dbus.service \_ udisks-daemon: polling /dev/sr0
|
||
1759 lennart name=systemd:/user/lennart/1 gnome-screensaver
|
||
1780 lennart name=systemd:/user/lennart/1 /usr/libexec/gvfsd-trash --spawner :1.9 /org/gtk/gvfs/exec_spaw/0
|
||
1864 lennart name=systemd:/user/lennart/1 /usr/libexec/gvfs-afc-volume-monitor
|
||
1874 lennart name=systemd:/user/lennart/1 /usr/libexec/gconf-im-settings-daemon
|
||
1903 lennart name=systemd:/user/lennart/1 /usr/libexec/gvfsd-burn --spawner :1.9 /org/gtk/gvfs/exec_spaw/1
|
||
1909 lennart name=systemd:/user/lennart/1 gnome-terminal
|
||
1913 lennart name=systemd:/user/lennart/1 \_ gnome-pty-helper
|
||
1914 lennart name=systemd:/user/lennart/1 \_ bash
|
||
29231 lennart name=systemd:/user/lennart/1 | \_ ssh tango
|
||
2221 lennart name=systemd:/user/lennart/1 \_ bash
|
||
4193 lennart name=systemd:/user/lennart/1 | \_ ssh tango
|
||
2461 lennart name=systemd:/user/lennart/1 \_ bash
|
||
29219 lennart name=systemd:/user/lennart/1 | \_ emacs systemd-for-admins-1.txt
|
||
15113 lennart name=systemd:/user/lennart/1 \_ bash
|
||
27251 lennart name=systemd:/user/lennart/1 \_ empathy
|
||
29504 lennart name=systemd:/user/lennart/1 \_ ps xawf -eo pid,user,cgroup,args
|
||
1968 lennart name=systemd:/user/lennart/1 ssh-agent
|
||
1994 lennart name=systemd:/user/lennart/1 gpg-agent --daemon --write-env-file
|
||
18679 lennart name=systemd:/user/lennart/1 /bin/sh /usr/lib64/firefox-3.6/run-mozilla.sh /usr/lib64/firefox-3.6/firefox
|
||
18741 lennart name=systemd:/user/lennart/1 \_ /usr/lib64/firefox-3.6/firefox
|
||
28900 lennart name=systemd:/user/lennart/1 \_ /usr/lib64/nspluginwrapper/npviewer.bin --plugin /usr/lib64/mozilla/plugins/libflashplayer.so --connection /org/wrapper/NSPlugins/libflashplayer.so/18741-6
|
||
4016 root name=systemd:/systemd-1/sysinit.service /usr/sbin/bluetoothd --udev
|
||
4094 smmsp name=systemd:/systemd-1/sendmail.service sendmail: Queue runner@01:00:00 for /var/spool/clientmqueue
|
||
4096 root name=systemd:/systemd-1/sendmail.service sendmail: accepting connections
|
||
4112 ntp name=systemd:/systemd-1/ntpd.service /usr/sbin/ntpd -n -u ntp:ntp -g
|
||
27262 lennart name=systemd:/user/lennart/1 /usr/libexec/mission-control-5
|
||
27265 lennart name=systemd:/user/lennart/1 /usr/libexec/telepathy-haze
|
||
27268 lennart name=systemd:/user/lennart/1 /usr/libexec/telepathy-logger
|
||
27270 lennart name=systemd:/user/lennart/1 /usr/libexec/dconf-service
|
||
27280 lennart name=systemd:/user/lennart/1 /usr/libexec/notification-daemon
|
||
27284 lennart name=systemd:/user/lennart/1 /usr/libexec/telepathy-gabble
|
||
27285 lennart name=systemd:/user/lennart/1 /usr/libexec/telepathy-salut
|
||
27297 lennart name=systemd:/user/lennart/1 /usr/libexec/geoclue-yahoo
|
||
\end{Verbatim}
|
||
(Данный листинг был сокращен за счет удаления из него строк, описывающих
|
||
потоки ядра, так как они никак не~относятся к обсуждаемой нами теме.)
|
||
\end{landscape}
|
||
|
||
Обратите внимание на третий столбец, показывающий имя контрольной группы,
|
||
в которую systemd поместил данный процесс. Например, процесс +udev+
|
||
находится в группе +name=systemd:/systemd-1/sysinit.service+. В эту группу
|
||
входят процессы, порожденные службой +sysinit.service+, которая запускается
|
||
на ранней стадии загрузки.
|
||
|
||
Вы можете очень сильно упростить себе работу, если назначите для
|
||
вышеприведенной команды какой-нибудь простой и короткий псевдоним, например
|
||
\begin{Verbatim}
|
||
alias psc='ps xawf -eo pid,user,cgroup,args'
|
||
\end{Verbatim}
|
||
---~теперь для получения исчерпывающей информации по процессам достаточно будет
|
||
нажать всего четыре клавиши.
|
||
|
||
Альтернативный способ получить ту же информацию~--- воспользоваться утилитой
|
||
+systemd-cgls+, входящей в комплект поставки systemd. Она отображает иерархию
|
||
контрольных групп в виде псевдографической диаграммы-дерева\footnote{Прим.
|
||
перев.: Стоит заметить, что в нижеприведенном листинге используется
|
||
ASCII-псевдографика. Между тем, уже довольно давно отображение иерархии
|
||
процессов в +systemd-cgls+ и +systemctl status+ производится в более
|
||
выразительной ├юникодной └псевдографике. ASCII-вариант выводится только в том
|
||
случае, если консоль не~поддерживает Unicode.}:
|
||
|
||
\begin{landscape}
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ systemd-cgls
|
||
+ 2 [kthreadd]
|
||
[...]
|
||
+ 4281 [flush-8:0]
|
||
+ user
|
||
| \ lennart
|
||
| \ 1
|
||
| + 1495 pam: gdm-password
|
||
| + 1521 gnome-session
|
||
| + 1534 dbus-launch --sh-syntax --exit-with-session
|
||
| + 1535 /bin/dbus-daemon --fork --print-pid 5 --print-address 7 --session
|
||
| + 1603 /usr/libexec/gconfd-2
|
||
| + 1612 /usr/libexec/gnome-settings-daemon
|
||
| + 1615 /ushr/libexec/gvfsd
|
||
| + 1621 metacity
|
||
| + 1626 /usr/libexec//gvfs-fuse-daemon /home/lennart/.gvfs
|
||
| + 1634 /usr/bin/pulseaudio --start --log-target=syslog
|
||
| + 1635 gnome-panel
|
||
| + 1638 nautilus
|
||
| + 1640 /usr/libexec/polkit-gnome-authentication-agent-1
|
||
| + 1641 /usr/bin/seapplet
|
||
| + 1644 gnome-volume-control-applet
|
||
| + 1645 /usr/libexec/bonobo-activation-server --ac-activate --ior-output-fd=24
|
||
| + 1646 /usr/sbin/restorecond -u
|
||
| + 1649 /usr/libexec/pulse/gconf-helper
|
||
| + 1652 /usr/bin/devilspie
|
||
| + 1662 nm-applet --sm-disable
|
||
| + 1664 gnome-power-manager
|
||
| + 1665 /usr/libexec/gdu-notification-daemon
|
||
| + 1668 /usr/libexec/im-settings-daemon
|
||
| + 1670 /usr/libexec/evolution/2.32/evolution-alarm-notify
|
||
| + 1672 /usr/bin/python /usr/share/system-config-printer/applet.py
|
||
| + 1674 /usr/lib64/deja-dup/deja-dup-monitor
|
||
| + 1675 abrt-applet
|
||
| + 1677 bluetooth-applet
|
||
| + 1678 gpk-update-icon
|
||
| + 1701 /usr/libexec/gvfs-gdu-volume-monitor
|
||
| + 1707 /usr/bin/gnote --panel-applet --oaf-activate-iid=OAFIID:GnoteApplet_Factory --oaf-ior-fd=22
|
||
| + 1725 /usr/libexec/clock-applet
|
||
| + 1727 /usr/libexec/wnck-applet
|
||
| + 1729 /usr/libexec/notification-area-applet
|
||
| + 1759 gnome-screensaver
|
||
| + 1780 /usr/libexec/gvfsd-trash --spawner :1.9 /org/gtk/gvfs/exec_spaw/0
|
||
| + 1864 /usr/libexec/gvfs-afc-volume-monitor
|
||
| + 1874 /usr/libexec/gconf-im-settings-daemon
|
||
| + 1882 /usr/libexec/gvfs-gphoto2-volume-monitor
|
||
| + 1903 /usr/libexec/gvfsd-burn --spawner :1.9 /org/gtk/gvfs/exec_spaw/1
|
||
| + 1909 gnome-terminal
|
||
| + 1913 gnome-pty-helper
|
||
| + 1914 bash
|
||
| + 1968 ssh-agent
|
||
| + 1994 gpg-agent --daemon --write-env-file
|
||
| + 2221 bash
|
||
| + 2461 bash
|
||
| + 4193 ssh tango
|
||
| + 15113 bash
|
||
| + 18679 /bin/sh /usr/lib64/firefox-3.6/run-mozilla.sh /usr/lib64/firefox-3.6/firefox
|
||
| + 18741 /usr/lib64/firefox-3.6/firefox
|
||
| + 27251 empathy
|
||
| + 27262 /usr/libexec/mission-control-5
|
||
| + 27265 /usr/libexec/telepathy-haze
|
||
| + 27268 /usr/libexec/telepathy-logger
|
||
| + 27270 /usr/libexec/dconf-service
|
||
| + 27280 /usr/libexec/notification-daemon
|
||
| + 27284 /usr/libexec/telepathy-gabble
|
||
| + 27285 /usr/libexec/telepathy-salut
|
||
| + 27297 /usr/libexec/geoclue-yahoo
|
||
| + 28900 /usr/lib64/nspluginwrapper/npviewer.bin --plugin /usr/lib64/mozilla/plugins/libflashplayer.so --connection /org/wrapper/NSPlugins/libflashplayer.so/18741-6
|
||
| + 29219 emacs systemd-for-admins-1.txt
|
||
| + 29231 ssh tango
|
||
| \ 29519 systemd-cgls
|
||
\ systemd-1
|
||
+ 1 /sbin/init
|
||
+ ntpd.service
|
||
| \ 4112 /usr/sbin/ntpd -n -u ntp:ntp -g
|
||
+ systemd-logger.service
|
||
| \ 1499 /lib/systemd/systemd-logger
|
||
+ accounts-daemon.service
|
||
| \ 1496 /usr/libexec/accounts-daemon
|
||
+ rtkit-daemon.service
|
||
| \ 1473 /usr/libexec/rtkit-daemon
|
||
+ console-kit-daemon.service
|
||
| \ 1408 /usr/sbin/console-kit-daemon --no-daemon
|
||
+ prefdm.service
|
||
| + 1376 /usr/sbin/gdm-binary -nodaemon
|
||
| + 1391 /usr/libexec/gdm-simple-slave --display-id /org/gnome/DisplayManager/Display1 --force-active-vt
|
||
| + 1394 /usr/bin/Xorg :0 -nr -verbose -auth /var/run/gdm/auth-for-gdm-f2KUOh/database -nolisten tcp vt1
|
||
| + 1419 /usr/bin/dbus-launch --exit-with-session
|
||
| \ 1511 /usr/bin/gnome-keyring-daemon --daemonize --login
|
||
+ getty@.service
|
||
| + tty6
|
||
| | \ 1346 /sbin/mingetty tty6
|
||
| + tty4
|
||
| | \ 1343 /sbin/mingetty tty4
|
||
| + tty5
|
||
| | \ 1342 /sbin/mingetty tty5
|
||
| + tty3
|
||
| | \ 1339 /sbin/mingetty tty3
|
||
| \ tty2
|
||
| \ 1332 /sbin/mingetty tty2
|
||
+ abrtd.service
|
||
| \ 1317 /usr/sbin/abrtd -d -s
|
||
+ crond.service
|
||
| \ 1344 crond
|
||
+ sshd.service
|
||
| \ 1362 /usr/sbin/sshd
|
||
+ sendmail.service
|
||
| + 4094 sendmail: Queue runner@01:00:00 for /var/spool/clientmqueue
|
||
| \ 4096 sendmail: accepting connections
|
||
+ haldaemon.service
|
||
| + 1249 hald
|
||
| + 1250 hald-runner
|
||
| + 1273 hald-addon-input: Listening on /dev/input/event3 /dev/input/event9 /dev/input/event1 /dev/input/event7 /dev/input/event2 /dev/input/event0 /dev/input/event8
|
||
| + 1275 /usr/libexec/hald-addon-rfkill-killswitch
|
||
| + 1284 /usr/libexec/hald-addon-leds
|
||
| + 1285 /usr/libexec/hald-addon-generic-backlight
|
||
| \ 1287 /usr/libexec/hald-addon-acpi
|
||
+ irqbalance.service
|
||
| \ 1210 irqbalance
|
||
+ avahi-daemon.service
|
||
| + 1175 avahi-daemon: running [epsilon.local]
|
||
+ NetworkManager.service
|
||
| + 1171 /usr/sbin/NetworkManager --no-daemon
|
||
| \ 4028 /sbin/dhclient -d -4 -sf /usr/libexec/nm-dhcp-client.action -pf /var/run/dhclient-wlan0.pid -lf /var/lib/dhclient/dhclient-7d32a784-ede9-4cf6-9ee3-60edc0bce5ff-wlan0.lease -cf /var/run/nm-dhclient-wlan0.conf wlan0
|
||
+ rsyslog.service
|
||
| \ 1193 /sbin/rsyslogd -c 4
|
||
+ mdmonitor.service
|
||
| \ 1207 mdadm --monitor --scan -f --pid-file=/var/run/mdadm/mdadm.pid
|
||
+ cups.service
|
||
| \ 1195 cupsd -C /etc/cups/cupsd.conf
|
||
+ auditd.service
|
||
| + 1131 auditd
|
||
| + 1133 /sbin/audispd
|
||
| \ 1135 /usr/sbin/sedispatch
|
||
+ dbus.service
|
||
| + 1096 /bin/dbus-daemon --system --address=systemd: --nofork --systemd-activation
|
||
| + 1216 /usr/sbin/modem-manager
|
||
| + 1219 /usr/libexec/polkit-1/polkitd
|
||
| + 1242 /usr/sbin/wpa_supplicant -c /etc/wpa_supplicant/wpa_supplicant.conf -B -u -f /var/log/wpa_supplicant.log -P /var/run/wpa_supplicant.pid
|
||
| + 1453 /usr/libexec/upowerd
|
||
| + 1733 /usr/libexec/udisks-daemon
|
||
| + 1747 udisks-daemon: polling /dev/sr0
|
||
| \ 29509 /usr/libexec/packagekitd
|
||
+ dev-mqueue.mount
|
||
+ dev-hugepages.mount
|
||
\ sysinit.service
|
||
+ 455 /sbin/udevd -d
|
||
+ 4016 /usr/sbin/bluetoothd --udev
|
||
+ 28188 /sbin/udevd -d
|
||
\ 28191 /sbin/udevd -d
|
||
\end{Verbatim}
|
||
(Как и предыдущий, этот листинг был сокращен за счет удаления перечня потоков
|
||
ядра.)
|
||
\end{landscape}
|
||
|
||
Как видно из листинга, данная команда наглядно показывает принадлежность
|
||
процессов к их контрольным группам, а следовательно, и к службам, так как
|
||
systemd именует группы в соответствии с названиями служб. Например, из
|
||
приведенного листинга нетрудно понять, что служба системного аудита
|
||
+auditd.service+ порождает три отдельных процесса: +auditd+,
|
||
+audispd+ и +sedispatch+.
|
||
|
||
Наиболее внимательные читатели, вероятно, уже заметили, что некоторые процессы
|
||
помещены в группу +/user/lennart/1+. Дело в том, что systemd занимается
|
||
отслеживанием и группировкой не~только процессов, относящихся к системным
|
||
службам, но и процессов, запущенных в рамках пользовательских сеансов. В
|
||
последующих статьях мы обсудим этот вопрос более подробно.
|
||
|
||
\section{Преобразование SysV init-скрипта в systemd service-файл}
|
||
\label{sec:convert}
|
||
|
||
Традиционно, службы Unix и Linux (демоны) запускаются через SysV init-скрипты.
|
||
Эти скрипты пишутся на языке Bourne Shell (+/bin/sh+), располагаются в
|
||
специальном каталоге (обычно +/etc/rc.d/init.d/+) и вызываются с одним из
|
||
стандартных параметров (+start+, +stop+, +reload+ и т.п.)~--- таким образом
|
||
указывается действие, которое необходимо произвести над службой (запустить,
|
||
остановить, заставить перечитать конфигурацию). При запуске службы такой
|
||
скрипт, как правило, вызывает бинарник демона, который, в свою очередь,
|
||
форкается, порождая фоновый процесс (т.е. демонизируется). Заметим, что
|
||
shell-скрипты, как правило, отличается низкой скоростью работы, излишней
|
||
подробностью изложения и крайней хрупкостью. Читать их, из-за изобилия
|
||
всевозможного вспомогательного и дополнительного кода, чрезвычайно тяжело.
|
||
Впрочем, нельзя не~упомянуть, что эти скрипты являются очень гибким
|
||
инструментом (ведь, по сути, это всего лишь код, который можно модифицировать
|
||
как угодно). С другой стороны, многие задачи, возникающие при работе со
|
||
службами, довольно тяжело решить средствами shell-скриптов. К таким
|
||
задачам относятся: организация параллельного исполнения, корректное
|
||
отслеживание процессов, конфигурирование различных параметров среды исполнения
|
||
процесса. systemd обеспечивает совместимость с init-скриптами, однако, с учетом
|
||
описанных выше их недостатков, более правильным решением будет использование
|
||
штатных service-файлов systemd для всех установленных в системе служб. Стоит
|
||
отметить что, в отличие от init-скриптов, которые часто приходится
|
||
модифицировать при переносе из одного дистрибутива в другой, один и тот же
|
||
service-файл будет работать в любом дистрибутиве, использующем systemd (а таких
|
||
дистрибутивов с каждым днем становится все больше и больше). Далее мы вкратце
|
||
рассмотрим процесс преобразования SysV init-скрипта в service-файл systemd.
|
||
Вообще говоря, service-файл должен создаваться разработчиками каждого демона, и
|
||
включаться в комплект его поставки. Если вам удалось успешно создать
|
||
работоспособный service-файл для какого-либо демона, настоятельно рекомендуем
|
||
вам отправить этот файл разработчикам. Вопросы по полноценной интеграции
|
||
демонов с systemd, с максимальным использованием всех его возможностей, будут
|
||
рассмотрены в последующих статьях этого цикла, пока же ограничимся ссылкой на
|
||
\href{http://www.freedesktop.org/software/systemd/man/daemon.html}{страницу}
|
||
официальной документации.
|
||
|
||
Итак, приступим. В качестве примера возьмем init-скрипт демона ABRT (Automatic
|
||
Bug Reporting Tool, службы, занимающейся сбором crash dump'ов). Исходный
|
||
скрипт (в варианте для дистрибутива Fedora) можно загрузить
|
||
\href{http://0pointer.de/public/abrtd}{здесь}.
|
||
|
||
Начнем с того, что прочитаем исходный скрипт (неожиданный ход, правда?) и
|
||
выделим полезную информацию из груды хлама. Практически у всех init-скриптов
|
||
б\'{о}льшая часть кода является чисто вспомогательной, и мало чем отличается от
|
||
одного скрипта к другому. Как правило, при создании новых скриптов этот код
|
||
просто копируется из уже существующих (разработка в стиле copy-paste). Итак,
|
||
в исследуемом скрипте нас интересует следующая информация:
|
||
|
||
\begin{itemize}
|
||
\item Строка описания службы: <<Daemon to detect crashing apps>>. Как
|
||
нетрудно заметить, комментарии в заголовке скрипта весьма
|
||
пространны и описывают не~сколько саму службу, сколько
|
||
скрипт, ее запускающий. service-файлы systemd тоже содержат
|
||
описание, но оно относится исключительно к службе, а не~к
|
||
service-файлу.
|
||
\item LSB-заголовок\footnote{LSB-заголовок~--- определенная в
|
||
\href{http://refspecs.freestandards.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/initscrcomconv.html}{Linux
|
||
Standard Base} схема записи метаданных о службах в блоках
|
||
комментариев соответствующих init-скриптов. Изначально эта
|
||
схема была введена именно для того, чтобы стандартизировать
|
||
init-скрипты во всех дистрибутивах. Однако разработчики
|
||
многих дистрибутивов не~считают нужным точно исполнять
|
||
требования LSB, и поэтому формы представления метаданных в
|
||
различных дистрибутивах могут отличаться. Вследствие этого,
|
||
при переносе init-скрипта из одного дистрибутива в другой,
|
||
скрипт приходится модифицировать. Например, демон пересылки
|
||
почты при описании зависимостей может именоваться
|
||
+MTA+ или +smtpdaemon+ (Fedora), +smtp+
|
||
(openSUSE), +mail-transport-agent+ (Debian и Ubuntu),
|
||
+mail-transfer-agent+. Таким образом, можно утверждать, что
|
||
стандарт LSB не~справляется с возложенной на него задачей.},
|
||
содержащий информацию о зависимостях. systemd, базирующийся
|
||
на идеях socket-активации, обычно не~требует явного описания
|
||
зависимостей (либо требует самого минимального описания).
|
||
Заметим, что основополагающие принципы systemd, включая
|
||
socket-активацию, рассмотрены в статье
|
||
\href{http://0pointer.de/blog/projects/systemd.html}{Rethinking
|
||
PID~1}, в которой systemd был впервые представлен широкой
|
||
публике\footnote{Прим. перев.: Ее русский перевод (сделанный
|
||
совершенно независимо от данного документа, совсем другим
|
||
человеком) можно прочитать здесь:
|
||
\href{http://tux-the-penguin.blogspot.com/2010/09/systemd.html}{часть~1},
|
||
\href{http://tux-the-penguin.blogspot.com/2010/09/systemd-ii.html}{часть~2}.}.
|
||
Возвращаясь к нашему примеру: в данном случае ценной
|
||
информацией о зависимостях является только строка
|
||
+Required-Start: $syslog+, сообщающая, что для работы
|
||
abrtd требуется демон системного лога. Информация о второй
|
||
зависимости, +$local_fs+, является избыточной, так как
|
||
systemd приступает к запуску служб уже после того, как все
|
||
файловые системы готовы для работы.
|
||
\item Также, LSB-заголовок сообщает, что данная служба должна быть
|
||
запущена на уровнях исполнения (runlevels) 3 (консольный
|
||
многопользовательский) и 5 (графический
|
||
многопользовательской).
|
||
\item Исполняемый бинарник демона называется +/usr/sbin/abrtd+.
|
||
\end{itemize}
|
||
|
||
Вот и вся полезная информация. Все остальное содержимое 115-строчного скрипта
|
||
является чисто вспомогательным кодом: операции синхронизации и упорядочивания
|
||
запуска (код, относящийся к lock-файлам), вывод информационных сообщений
|
||
(команды +echo+), разбор входных параметров (монструозный блок
|
||
+case+).
|
||
|
||
На основе приведенной выше информации, мы можем написать следующий
|
||
service-файл:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=Daemon to detect crashing apps
|
||
After=syslog.target
|
||
|
||
[Service]
|
||
ExecStart=/usr/sbin/abrtd
|
||
Type=forking
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
\end{Verbatim}
|
||
|
||
Рассмотрим этот файл поподробнее.
|
||
|
||
Секция +[Unit]+ содержит самую общую информацию о службе. Не~будем
|
||
забывать, что systemd управляет не~только службами, но и многими другими
|
||
объектами, в частности, устройствами, точками монтирования, таймерами и т.п.
|
||
Общее наименование всех этих объектов~--- юнит (unit). Одноименная секция
|
||
конфигурационного файла определяет наиболее общие свойства, которые могут
|
||
быть присущи любому юниту. В нашем случае это, во-первых, строка описания, и
|
||
во-вторых, указание, что данный юнит рекомендуется активировать после запуска
|
||
демона системного лога\footnote{Строго говоря, эту зависимость здесь
|
||
указывать не~нужно~--- в системах, в которых демон системного лога активируется
|
||
через сокет, данная зависимость является избыточной. Современные реализации
|
||
демона системного лога (например, rsyslog начиная с пятой версии)
|
||
поддерживают активацию через сокет. В системах, использующих такие
|
||
реализации, явное указание +After=syslog.target+ будет избыточным, так
|
||
как соответствующая функциональность поддерживается автоматически. Однако,
|
||
эту строчку все-таки стоит указать для обеспечения совместимости с системами,
|
||
использующими устаревшие реализации демона системного лога.}. Эта информация,
|
||
как мы помним, была указана в LSB-заголовке исходного init-скрипта. В нашем
|
||
конфигурационном файле мы указываем зависимость от демона системного лога при
|
||
помощи директивы +After+, указывающей на юнит +syslog.taget+. Это
|
||
специальный юнит, позволяющий ссылаться на любую реализацию демона системного
|
||
лога, независимо от используемой программы (например, rsyslog или syslog-ng)
|
||
и типа активации (как обычной службы или через log-сокет). Подробнее о таких
|
||
специальных юнитах можно почитать
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.special.html}%
|
||
{страницу} официальной документации. Обратите внимание, что директива +After+,
|
||
в отсутствие директивы +Requires+, задает лишь порядок загрузки, но
|
||
не~задает жесткой зависимости. То есть, если при загрузке конфигурация
|
||
systemd будет предписывать запуск как демона системного лога, так и abrtd, то
|
||
сначала будет запущен демон системного лога, и только потом abrtd. Если же
|
||
конфигурация не~будет содержать явного указания запустить демон системного
|
||
лога, он не~будет запущен даже при запуске abrtd. И это поведение нас
|
||
полностью устраивает, так как abrtd прекрасно может обходиться и без демона
|
||
системного лога. В противном случае, мы могли бы воспользоваться директивой
|
||
+Requires+, задающей жесткую зависимость между юнитами.
|
||
|
||
Следующая секция, +[Service]+, содержит информацию о службе. Сюда включаются
|
||
настройки, относящие именно к службам, но не~к другим типам юнитов. В нашем
|
||
случае, таких настроек две: +ExecStart+, определяющая расположение бинарника
|
||
демона и аргументы, с которыми он будет вызван (в нашем случае они
|
||
отсутствуют), и +Type+, позволяющая задать метод, по которому systemd определит
|
||
окончание периода запуска службы. Традиционный для Unix метод демонизации
|
||
процесса, когда исходный процесс форкается, порождая демона, после чего
|
||
завершается, описывается типом +forking+ (как в нашем случае). Таким образом,
|
||
systemd считает службу запущенной с момента завершения работы исходного
|
||
процесса, и рассматривает в качестве основного процесса этой службы
|
||
порожденный им процесс-демон.
|
||
|
||
И наконец, третья секция, +[Install]+. Она содержит рекомендации по
|
||
установке конкретного юнита, указывающие, в каких ситуациях он должен быть
|
||
активирован. В нашем случае, служба abrtd запускается при активации юнита
|
||
+multi-user.target+. Это специальный юнит, примерно соответствующий роли
|
||
третьего уровня исполнения классического SysV\footnote{В том контексте, в
|
||
котором он используется в большинстве дистрибутивов семейства Red Hat, а
|
||
именно, многопользовательский режим без запуска графической оболочки.}.
|
||
Директива +WantedBy+ никак не~влияет на уже работающую службу, но она
|
||
играет важную роль при выполнении команды +systemctl enable+, задавая, в каких
|
||
условиях должен активироваться устанавливаемый юнит. В нашем примере, служба
|
||
abrtd будет активироваться при переходе в состояние +multi-user.target+,
|
||
т.е., при каждой нормальной\footnote{Прим. перев.: К <<ненормальным>> загрузкам
|
||
можно отнести, например, загрузки в режимах +emergency.target+ или
|
||
+rescue.target+ (является аналогом первого уровня исполнения в классической
|
||
SysV).} загрузке\footnote{Обратите внимание, что режим графической загрузки в
|
||
systemd (+graphical.target+, аналог runlevel 5 в SysV) является надстройкой над
|
||
режимом многопользовательской консольной загрузки (+multi-user.target+, аналог
|
||
runlevel 3 в SysV). Таким образом, все службы, запускаемые в режиме
|
||
+multi-user.target+, будут также запускаться и в режиме +graphical.target+.}.
|
||
|
||
Вот и все. Мы получили минимальный рабочий service-файл systemd. Чтобы
|
||
проверить его работоспособность, скопируем его в
|
||
+/etc/systemd/system/abrtd.service+, после чего командой
|
||
+systemctl daemon-reload+ уведомим systemd об изменении конфигурации.
|
||
Теперь нам остается только запустить нашу службу:
|
||
+systemctl start abrtd.service+. Проверить состояние службы можно
|
||
командой +systemctl status abrtd.service+, а чтобы остановить ее, нужно
|
||
скомандовать +systemctl stop abrtd.service+. И наконец, команда
|
||
+systemctl enable abrtd.service+ выполнит установку service-файла,
|
||
обеспечив его активацию при каждой загрузке (аналог +chkconfig abrtd on+
|
||
в классическом SysV).
|
||
|
||
Приведенный выше service-файл является практический точным переводом
|
||
исходного init-скрипта, и он никак не~использует широкий спектр возможностей,
|
||
предоставляемых systemd. Ниже приведен немного улучшенный вариант этого же
|
||
файла:
|
||
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=ABRT Automated Bug Reporting Tool
|
||
After=syslog.target
|
||
|
||
[Service]
|
||
Type=dbus
|
||
BusName=com.redhat.abrt
|
||
ExecStart=/usr/sbin/abrtd -d -s
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
\end{Verbatim}
|
||
|
||
Чем же новый вариант отличается от предыдущего? Ну, прежде всего, мы уточнили
|
||
описание службы. Однако, ключевым изменением является замена значения +Type+ с
|
||
+forking+ на +dbus+ и связанные с ней изменения: добавление имени службы в шине
|
||
D-Bus (директива +BusName+) и задание дополнительных аргументов abrtd
|
||
<<+-d -s+>>. Но зачем вообще нужна эта замена? Каков ее практический смысл?
|
||
Чтобы ответить на этот вопрос, мы снова возвращаемся к демонизации. В ходе
|
||
данной операции процесс дважды форкается и отключается от всех терминалов. Это
|
||
очень удобно при запуске демона через скрипт, но в случае использования таких
|
||
продвинутых систем инициализации, как systemd, подобное поведение не~дает
|
||
никаких преимуществ, но вызывает неоправданные задержки. Даже если мы оставим в
|
||
стороне вопрос скорости загрузки, останется такой важный аспект, как
|
||
отслеживание состояния служб. systemd решает и эту задачу, контролируя работу
|
||
службы и при необходимости реагируя на различные события. Например, при
|
||
неожиданном падении основного процесса службы, systemd должен зарегистрировать
|
||
идентификатор и код выхода процесса, также, в зависимости от настроек, он может
|
||
попытаться перезапустить службу, либо активировать какой-либо заранее заданный
|
||
юнит. Операция демонизации несколько затрудняет решение этих задач, так как
|
||
обычно довольно сложно найти связь демонизированного процесса с исходным
|
||
(собственно, смысл демонизации как раз и сводится к уничтожению этой связи) и,
|
||
соответственно, для systemd сложнее определить, какой из порожденных в рамках
|
||
данной службы процессов является основным. Чтобы упростить для него решение этой
|
||
задачи, мы и воспользовались типом запуска +dbus+. Он подходит для всех служб,
|
||
которые в конце процесса инициализации регистрируют свое имя на шине
|
||
D-Bus\footnote{В настоящее время практически все службы дистрибутива Fedora
|
||
после запуска регистрируется на шине D-Bus.}. ABRTd относится к ним. С новыми
|
||
настройками, systemd запустит процесс abrtd, который уже не~будет форкаться
|
||
(согласно указанным нами ключам <<+-d -s+>>), и в качестве момента окончания
|
||
периода запуска данной службы systemd будет рассматривать момент регистрации
|
||
имени +com.redhat.abrt+ на шине D-Bus. В этом случае основным для данной службы
|
||
будет считаться процесс, непосредственно порожденный systemd. Таким образом,
|
||
systemd располагает удобным методом для определения момента окончания запуска
|
||
службы, а также может легко отслеживать ее состояние.
|
||
|
||
Собственно, это все, что нужно было сделать. Мы получили простой
|
||
конфигурационный файл, в 10 строчках которого содержится больше полезной
|
||
информации, чем в 115 строках исходного init-скрипта. Добавляя в наш файл по
|
||
одной строчке, мы можем использовать различные полезные функции systemd,
|
||
создание аналога которых в традиционном init-скрипте потребовало бы
|
||
значительных усилий. Например, добавив строку +Restart=restart-always+, мы
|
||
приказываем systemd автоматически перезапускать службу после каждого ее
|
||
падения. Или, например, добавив +OOMScoreAdjust=-500+, мы попросим ядро сберечь
|
||
эту службу, даже если OOM Killer выйдет на тропу войны. А если мы добавим
|
||
строчку +CPUSchedulingPolicy=idle+, процесс abrtd будет работать только в те
|
||
моменты, когда система больше ничем не~занята, что позволит не~создавать помех
|
||
для процессов, активно использующих CPU.
|
||
|
||
За более подробным описанием всех опций настройки, вы можете обратиться к
|
||
страницам руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}{systemd.unit},
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.service.html}{systemd.service},
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}{systemd.exec}.
|
||
Полный список доступных страниц можно просмотреть
|
||
\href{http://www.freedesktop.org/software/systemd/man/}{здесь}.
|
||
|
||
Конечно, отнюдь не~все init-скрипты так же легко преобразовать в
|
||
service-файлы. Но, к счастью, <<проблемных>> скриптов не~так уж и много.
|
||
|
||
\section{Убить демона}
|
||
|
||
Убить системного демона нетрудно, правда? Или\ldots{} все не~так просто?
|
||
|
||
Если ваш демон функционирует как один процесс, все действительно очень просто.
|
||
Вы командуете +killall rsyslogd+, и демон системного лога останавливается.
|
||
Впрочем, этот метод не~вполне корректен, так как он действует не~только на
|
||
самого демона, но и на другие процессы с тем же именем. Иногда подобное
|
||
поведение может привести к неприятным последствиям. Более правильным будет
|
||
использование pid-файла: \verb+kill $(cat /var/run/syslogd.pid)+. Вот, вроде
|
||
бы, и все, что вам нужно\ldots{} Или мы упускаем еще что-то?
|
||
|
||
Действительно, мы забываем одну простую вещь: существуют службы, такие, как
|
||
Apache, crond, atd, которые по роду служебной деятельности должны запускать
|
||
дочерние процессы. Это могут быть совершенно посторонние, указанные
|
||
пользователем программы (например, задачи cron/at, CGI-скрипты) или полноценные
|
||
серверные процессы (например, Apache workers). Когда вы убиваете основной
|
||
процесс, он может остановить все дочерние процессы. А может и не~остановить. В
|
||
самом деле, если служба функционирует в штатном режиме, ее обычно останавливают
|
||
командой +stop+. К прямому вызову +kill+ администратор, как правило,
|
||
прибегает только в аварийной ситуации, когда служба работает неправильно и
|
||
может не~среагировать на стандартную команду остановки. Таким образом, убив,
|
||
например, основной сервер Apache, вы можете получить от него в наследство
|
||
работающие CGI-скрипты, причем их родителем автоматически станет PID~1 (init),
|
||
так что установить их происхождение будет не~так-то просто.
|
||
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd}{systemd} спешит к нам
|
||
на помощь. Команда +systemctl kill+ позволит отправить сигнал всем
|
||
процессам, порожденным в рамках данной службы. Например:
|
||
|
||
\begin{Verbatim}
|
||
# systemctl kill crond.service
|
||
\end{Verbatim}
|
||
|
||
Вы можете быть уверены, что всем процессам службы cron будет отправлен сигнал
|
||
+SIGTERM+. Разумеется, можно отправить и любой другой сигнал. Скажем, если ваши
|
||
дела совсем уж плохи, вы можете воспользоваться и +SIGKILL+:
|
||
|
||
\begin{Verbatim}
|
||
# systemctl kill -s SIGKILL crond.service
|
||
\end{Verbatim}
|
||
|
||
После ввода этой команды, служба cron будет жестоко убита вместе со всеми ее
|
||
дочерними процессами, вне зависимости от того, сколько раз она форкалась, и
|
||
как бы она ни пыталась сбежать из-под нашего контроля при помощи двойного
|
||
форка или
|
||
\href{http://ru.wikipedia.org/wiki/Fork-%D0%B1%D0%BE%D0%BC%D0%B1%D0%B0}{форк-бомбардировки}%
|
||
\footnote{Прим. перев.: Стоит особо отметить, что использование контрольных
|
||
групп не~только упрощает процесс уничтожения форк-бомб, но и значительно
|
||
уменьшает ущерб от работающей форк-бомбы. Так как systemd автоматически помещает
|
||
каждую службу и каждый пользовательский сеанс в свою контрольную группу по
|
||
ресурсу процессорного времени, запуск форк-бомбы одним пользователем или службой
|
||
не~создаст значительных проблем с отзывчивостью системы у других пользователей и
|
||
служб. Таким образом, в качестве основной угрозы форк-бомбардировки остаются
|
||
лишь возможности исчерпания памяти и идентификаторов процессов (PID). Впрочем, и
|
||
их тоже можно легко устранить: достаточно задать соответствующие лимиты в
|
||
конфигурационном файле службы (см.
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}%
|
||
{systemd.exec(5)} и
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html}%
|
||
{systemd.resource-control(5)}).}.
|
||
|
||
В некоторых случаях возникает необходимость отправить сигнал именно основному
|
||
процессу службы. Например, используя +SIGHUP+, мы можем заставить демона
|
||
перечитать файлы конфигурации. Разумеется, передавать HUP вспомогательным
|
||
процессам в этом случае совершенно необязательно. Для решения подобной задачи
|
||
неплохо подойдет и классический метод с pid-файлом, однако у systemd и на этот
|
||
случай есть простое решение, избавляющее вас от необходимости искать нужный
|
||
файл:
|
||
|
||
\begin{Verbatim}
|
||
# systemctl kill -s HUP --kill-who=main crond.service
|
||
\end{Verbatim}
|
||
|
||
Итак, что же принципиально новое привносит systemd в рутинный процесс
|
||
убийства демона? Прежде всего: впервые в истории Linux представлен способ
|
||
принудительной остановки службы, не~зависящий от того, насколько
|
||
добросовестно основной процесс службы выполняет свои обязательства по
|
||
остановке дочерних процессов. Как уже упоминалось выше, необходимость
|
||
отправить процессу +SIGTERM+ или +SIGKILL+ обычно возникает именно
|
||
в нештатной ситуации, когда вы уже не~можете быть уверены, что демон
|
||
корректно исполнит все свои обязанности.
|
||
|
||
После прочтения сказанного выше у вас может возникнуть вопрос: в чем разница
|
||
между +systemctl kill+ и +systemctl stop+? Отличие состоит в том,
|
||
что +kill+ просто отправляет сигнал заданному процессу, в то время как
|
||
+stop+ действует по <<официально>> определенному методу, вызывая команду,
|
||
определенную в параметре +ExecStop+ конфигурации службы. Обычно команды
|
||
+stop+ бывает вполне достаточно для остановки службы, и к +kill+
|
||
приходится прибегать только в крайних случаях, например, когда служба
|
||
<<зависла>> и не~реагирует на команды.
|
||
|
||
Кстати говоря, при использовании параметра <<+-s+>>, вы можете указывать
|
||
названия сигналов как с префиксом SIG, так и без него~--- оба варианта будут
|
||
работать.
|
||
|
||
В завершение стоит сказать, что для нас весьма интересным и неожиданным
|
||
оказался тот факт, что до появления systemd в Linux просто не~существовало
|
||
инструментов, позволяющих корректно отправить сигнал службе в целом, а
|
||
не~отдельному процессу.
|
||
|
||
\section{Три уровня выключения}
|
||
\label{sec:off}
|
||
|
||
В \href{http://www.freedesktop.org/wiki/Software/systemd}{systemd} существует
|
||
три уровня (разновидности) действий, направленных на прекращение работы службы
|
||
(или любого другого юнита):
|
||
|
||
\begin{itemize}
|
||
\fvset{gobble=3}
|
||
\item Вы можете \emph{остановить} службу, то есть прекратить
|
||
выполнение уже запущенных процессов службы. При этом
|
||
сохраняется возможность ее последующего запуска, как ручного
|
||
(через команду +systemctl start+), так и автоматического (при
|
||
загрузке системы, при поступлении запроса через сокет или
|
||
системную шину, при срабатывании таймера, при подключении
|
||
соответствующего оборудования и т.д.). Таким образом,
|
||
остановка службы является временной мерой, не~дающей никаких
|
||
гарантий на будущее.
|
||
|
||
В качестве примера рассмотрим остановку службы NTPd
|
||
(отвечающей за синхронизацию времени по сети):
|
||
\begin{Verbatim}
|
||
systemctl stop ntpd.service
|
||
\end{Verbatim}
|
||
|
||
Аналогом этой команды в классическом SysV init является
|
||
\begin{Verbatim}
|
||
service ntpd stop
|
||
\end{Verbatim}
|
||
|
||
Заметим, что в Fedora~15, использующей в качестве системы
|
||
инициализации systemd, в целях обеспечения обратной
|
||
совместимости допускается использование классических
|
||
SysV-команд, и systemd будет корректно воспринимать их.
|
||
В~частности, вторая приведенная здесь команда будет эквивалентна
|
||
первой.
|
||
|
||
\item Вы можете \emph{отключить} службу, то есть отсоединить ее от всех
|
||
триггеров активации. В результате служба уже не~будет
|
||
автоматически запускаться ни~при загрузке системы, ни~при
|
||
обращении к сокету или адресу на шине, ни~при подключении
|
||
оборудования, и т.д. Но при этом сохраняется возможность
|
||
<<ручного>> запуска службы (командой +systemctl start+).
|
||
Обратите внимание, что при отключении уже запущенной службы, ее
|
||
выполнение в текущем сеансе не~останавливается~--- это нужно
|
||
сделать отдельно, иначе процессы службы будут работать до
|
||
момента выключения системы (но при следующем включении,
|
||
разумеется, уже не~запустятся).
|
||
|
||
Рассмотрим отключение службы на примере все того же NTPd:
|
||
\begin{Verbatim}
|
||
systemctl disable ntpd.service
|
||
\end{Verbatim}
|
||
|
||
В классических SysV-системах аналогичная команда будет иметь
|
||
вид
|
||
\begin{Verbatim}
|
||
chkconfig ntpd off
|
||
\end{Verbatim}
|
||
|
||
Как и в предыдущем случае, в Fedora~15 вторая из этих команд
|
||
будет действовать аналогично первой.
|
||
|
||
Довольно часто приходится сочетать действия отключения и
|
||
остановки службы~--- такая комбинированная операция
|
||
гарантирует, что уже исполняющиеся процессы службы будут
|
||
прекращены, и служба больше не~будет запускаться автоматически
|
||
(но может быть запущена вручную):
|
||
\begin{Verbatim}
|
||
systemctl disable ntpd.service
|
||
systemctl stop ntpd.service
|
||
\end{Verbatim}
|
||
Подобное сочетание команд используется,
|
||
например, при деинсталляции пакетов в Fedora.
|
||
|
||
Обратите внимание, что отключение службы является перманентной
|
||
мерой, и действует вплоть до явной отмены соответствующей
|
||
командой. Перезагрузка системы не~отменяет отключения службы.
|
||
|
||
\item Вы можете \emph{заблокировать} (замаскировать) службу. Действие
|
||
этой операции аналогично отключению, но дает более сильный
|
||
эффект. Если при отключении отменяется только возможность
|
||
автоматического запуска службы, но сохраняется возможность
|
||
ручного запуска, то при блокировке исключаются обе эти
|
||
возможности. Отметим, что использование данной опции при
|
||
непонимании принципов ее работы может привести к трудно
|
||
диагностируемым ошибкам.
|
||
|
||
Тем не~менее, рассмотрим пример блокировки все той же службы NTPd:
|
||
\begin{Verbatim}
|
||
ln -s /dev/null /etc/systemd/system/ntpd.service
|
||
systemctl daemon-reload
|
||
\end{Verbatim}
|
||
|
||
Итак, блокировка сводится к созданию символьной ссылки
|
||
с именем соответствующей службы, указывающей на
|
||
+/dev/null+\footnote{Прим. перев.: Впоследствии в программу
|
||
+systemctl+ была добавлена поддержка команд +mask+ и +unmask+,
|
||
упрощающих процесс блокирования и разблокирования юнитов.
|
||
Например, для блокирования службы +ntpd.service+ теперь
|
||
достаточно команды +systemctl mask ntpd.service+. Фактически
|
||
она сделает то же самое, что и приведенная выше команда +ln+.}.
|
||
После такой операции служба не~может быть запущена ни~вручную,
|
||
ни~автоматически. Символьная ссылка создается в каталоге
|
||
+/etc/systemd/system/+, а ее имя должно соответствовать имени
|
||
файла описания службы из каталога +/lib/systemd/system/+ (в
|
||
нашем случае +ntpd.service+).
|
||
|
||
Заметим, что systemd читает файлы конфигурации из обоих этих
|
||
каталогов, но файлы из +/etc+ (управляемые системным
|
||
администратором) имеют приоритет над файлами из +/lib+ (которые
|
||
управляются пакетным менеджером). Таким образом, создание
|
||
символьной ссылки (или обычного файла)
|
||
+/etc/systemd/system/ntpd.service+ предотвращает чтение
|
||
штатного файла конфигурации +/lib/systemd/system/ntpd.service+.
|
||
|
||
В выводе +systemctl status+ заблокированные службы отмечаются
|
||
словом +masked+. Попытка запустить такие службы командой
|
||
+systemctl start+ завершится ошибкой.
|
||
|
||
В рамках классического SysV init, штатная реализация такой
|
||
возможности отсутствует. Похожий эффект может быть
|
||
достигнут с помощью <<костылей>>, например, путем добавления
|
||
команды +exit 0+ в начало init-скрипта. Однако, подобные решения
|
||
имеют ряд недостатков, например, потенциальная возможность
|
||
конфликтов с пакетным менеджером (при очередном обновлении
|
||
исправленный скрипт может быть просто затерт соответствующим
|
||
файлом из пакета).
|
||
|
||
Стоит отметить, что блокировка службы, как и ее отключение,
|
||
является перманентной мерой\footnote{Прим. перев.: Подробно
|
||
описав принцип работы блокировки службы (юнита), автор забывает
|
||
привести практические примеры ситуаций, когда эта возможность
|
||
оказывается полезной.
|
||
|
||
В частности, иногда бывает необходимо
|
||
полностью предотвратить запуск службы в любой ситуации. При этом
|
||
не~стоит забывать, что в post-install скриптах пакетного
|
||
менеджера или, скажем, в~заданиях cron, вместо
|
||
+systemctl try-restart+ (+service condrestart+) может быть
|
||
ошибочно указано +systemctl restart+ (+service restart+), что
|
||
является прямым указанием на запуск службы, если она еще
|
||
не~запущена. Вследствие таких ошибок, отключенная служба может
|
||
<<ожить>> в самый неподходящий момент.}.
|
||
\end{itemize}
|
||
|
||
После прочтения изложенного выше, у читателя может возникнуть вопрос: как
|
||
отменить произведенные изменения? Что ж, ничего сложного тут нет:
|
||
+systemctl start+ отменяет действия +systemctl stop+, +systemctl enable+
|
||
отменяет действие +systemctl disable+, а +rm+ отменяет действие
|
||
+ln+.
|
||
|
||
\section{Смена корня}
|
||
\label{sec:chroots}
|
||
|
||
Практически все администраторы и разработчики рано или поздно встречаются с
|
||
\href{http://linux.die.net/man/1/chroot}{chroot-окружениями}. Системный вызов
|
||
+chroot()+ позволяет задать для определенного процесса (и его потомков) каталог,
|
||
который они будут рассматривать как корневой +/+, тем самым ограничивая для них
|
||
область видимости иерархии файловой системы отдельной ветвью. Большинство
|
||
применений chroot-окружений можно отнести к двум классам задач:
|
||
\begin{enumerate}
|
||
\item Обеспечение безопасности. Потенциально уязвимый демон chroot'ится
|
||
в отдельный каталог и, даже в случае успешной атаки, взломщик
|
||
увидит лишь содержимое этого каталога, а не~всю файловую
|
||
систему~--- он окажется в ловушке chroot'а.
|
||
\item Подготовка и управление образом операционной системы при отладке,
|
||
тестировании, компиляции, установке или восстановлении. При этом
|
||
вся иерархия файловых систем гостевой ОС монтируется или
|
||
создается в каталоге системы-хоста, и при запуске оболочки (или
|
||
любого другого приложения) внутри этой иерархии, в качестве
|
||
корня используется данный каталог. Система, которую <<видят>>
|
||
такие программы, может сильно отличаться от ОС хоста. Например,
|
||
это может быть другой дистрибутив, или даже другая аппаратная
|
||
архитектура (запуск i386-гостя на x86\_64-хосте). Гостевая ОС
|
||
не~может увидеть полного дерева каталогов ОС хоста.
|
||
\end{enumerate}
|
||
|
||
В системах, использующих классический SysV init, использовать chroot-окружения
|
||
сравнительно несложно. Например, чтобы запустить выбранного демона внутри дерева
|
||
каталогов гостевой ОС, достаточно смонтировать внутри этого дерева +/proc+,
|
||
+/sys+ и остальные API ФС, воспользоваться программой +chroot(1)+ для входа в
|
||
окружение, и выполнить соответствующий init-скрипт, запустив +/sbin/service+
|
||
внутри окружения.
|
||
|
||
Но в системах, использующих systemd, уже не~все так просто. Одно из важнейших
|
||
достоинств systemd состоит в том, что параметры среды, в которой запускаются
|
||
демоны, никак не~зависят от метода их запуска. В системах, использующих SysV
|
||
init, многие параметры среды выполнения (в частности, лимиты на системные
|
||
ресурсы, переменные окружения, и т.п.) наследуются от оболочки, из которой был
|
||
запущен init-скрипт. При использовании systemd ситуация меняется радикально:
|
||
пользователь просто уведомляет процесс init о необходимости запустить ту или
|
||
иную службу, и тот запускает демона в чистом, созданном <<с нуля>> и тщательно
|
||
настроенном окружении, параметры которого никак не~зависят от настроек среды, из
|
||
которой была отдана команда. Такой подход полностью отменяет традиционный метод
|
||
запуска демонов в chroot-окружениях: теперь демон порождается процессом init
|
||
(PID~1) и наследует корневой каталог от него, вне зависимости от того, находился
|
||
ли пользователь, отдавший команду на запуск, в chroot-окружении, или нет. Кроме
|
||
того, стоит особо отметить, что взаимодействие управляющих программ с systemd
|
||
происходит через сокеты, находящиеся в каталоге +/run/systemd+, так что
|
||
программы, запущенные в chroot-окружении, просто не~смогут взаимодействовать с
|
||
init-подсистемой (и это, в общем, неплохо, а если такое ограничение будет
|
||
создавать проблемы, его можно легко обойти, используя bind-монтирование).
|
||
|
||
В свете вышесказанного, возникает вопрос: как правильно использовать
|
||
chroot-окружения в системах на основе systemd? Что ж, постараемся дать подробный
|
||
и всесторонний ответ на этот вопрос.
|
||
|
||
Для начала, рассмотрим первое из перечисленных выше применений chroot: изоляция
|
||
в целях безопасности. Прежде всего, стоит заметить, что защита, предоставляемая
|
||
chroot'ом, весьма эфемерна и ненадежна, так как chroot не~является <<дорогой с
|
||
односторонним движением>>. Выйти из chroot-окружения сравнительно несложно, и
|
||
соответствующее предупреждение даже
|
||
\href{http://linux.die.net/man/2/chroot}{присутствует на странице руководства}.
|
||
Действительно эффективной защиты можно достичь, только сочетая chroot с другими
|
||
методиками. В большинстве случаев, это возможно только при наличии поддержки
|
||
chroot в самой программе. Прежде всего, корректное конфигурирование
|
||
chroot-окружения требует глубокого понимания принципов работы программы.
|
||
Например, нужно точно знать, какие каталоги нужно bind-монтировать из основной
|
||
системы, чтобы обеспечить все необходимые для работы программы каналы связи. С
|
||
учетом вышесказанного, эффективная chroot-защита обеспечивается только в том
|
||
случае, когда она реализована в коде самого демона. Именно разработчик лучше
|
||
других знает (\emph{обязан} знать), как правильно сконфигурировать
|
||
chroot-окружение, и какой минимальный набор файлов, каталогов и файловых систем
|
||
необходим внутри него для нормальной работы демона. Уже сейчас существуют
|
||
демоны, имеющие встроенную поддержку chroot. К сожалению, в системе Fedora,
|
||
установленной с параметрами по умолчанию, таких демонов всего два:
|
||
\href{http://avahi.org/}{Avahi} и RealtimeKit. Оба они написаны одним очень
|
||
хитрым человеком ;-) (Вы можете собственноручно убедиться в этом, выполнив
|
||
команду +ls -l /proc/*/root+.)
|
||
|
||
Возвращаясь к теме нашего обсуждения: разумеется, systemd позволяет помещать
|
||
выбранных демонов в chroot, и управлять ими точно так же, как и остальными.
|
||
Достаточно лишь указать параметр +RootDirectory=+ в соответствующем
|
||
service-файле. Например:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=A chroot()ed Service
|
||
|
||
[Service]
|
||
RootDirectory=/srv/chroot/foobar
|
||
ExecStartPre=/usr/local/bin/setup-foobar-chroot.sh
|
||
ExecStart=/usr/bin/foobard
|
||
RootDirectoryStartOnly=yes
|
||
\end{Verbatim}
|
||
|
||
Рассмотрим этот пример подробнее. Параметр +RootDirectory=+ задает каталог, в
|
||
который производится chroot перед запуском исполняемого файла, заданного
|
||
параметром +ExecStart=+. Заметим, что путь к этому файлу должен быть указан
|
||
относительно каталога chroot (так что, в нашем случае, с точки зрения основной
|
||
системы, на исполнение будет запущен файл +/srv/chroot/foobar/usr/bin/foobard+).
|
||
Перед запуском демона будет вызван сценарий оболочки +setup-foobar-chroot.sh+,
|
||
который должен обеспечить подготовку chroot-окружения к запуску демона
|
||
(например, смонтировать в нем +/proc+ и/или другие файловые системы, необходимые
|
||
для работы демона). Указав +RootDirectoryStartOnly=yes+, мы задаем, что
|
||
+chroot()+ будет выполняться только перед выполнением файла из +ExecStart=+, а
|
||
команды из других директив, в частности, +ExecStartPre=+, будут иметь полный
|
||
доступ к иерархии файловых систем ОС (иначе наш скрипт просто не~сможет
|
||
выполнить bind-монтирование нужных каталогов). Более подробную информацию по
|
||
опциям конфигурации вы можете получить на
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.service.html}{страницах}
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}{руководства}.
|
||
|
||
Поместив приведенный выше текст примера в файл
|
||
+/etc/systemd/system/foobar.service+, вы сможете запустить chroot'нутого демона
|
||
командой +systemctl start foobar.service+. Информацию о его текущем состоянии
|
||
можно получить с помощью команды +systemctl status foobar.service+. Команды
|
||
управления и мониторинга службы не~зависят от того, запущена ли она в chroot'е,
|
||
или нет. Этим systemd отличается от классического SysV init.
|
||
|
||
Новые ядра Linux поддерживают возможность создания независимых пространств имен
|
||
файловых систем (в дальнейшем FSNS, от <<file system namespaces>>). По
|
||
функциональности этот механизм аналогичен +chroot()+, однако предоставляет
|
||
гораздо более широкие возможности, и в нем отсутствуют проблемы с безопасностью,
|
||
характерные для chroot. systemd позволяет использовать при конфигурировании
|
||
юнитов некоторые возможности, предоставляемые FSNS. В частности, использование
|
||
FSNS часто является гораздо более простой и удобной альтернативой созданию
|
||
полновесных chroot-окружений. Используя директивы +ReadOnlyDirectories=+,
|
||
+InaccessibleDirectories=+, вы можете задать ограничения по использованию
|
||
иерархии файловых систем для заданной службы: ее корнем будет системный корневой
|
||
каталог, однако указанные в этих директивах подкаталоги будут доступны только
|
||
для чтения или вообще недоступны для нее. Например:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=A Service With No Access to /home
|
||
|
||
[Service]
|
||
ExecStart=/usr/bin/foobard
|
||
InaccessibleDirectories=/home
|
||
\end{Verbatim}
|
||
|
||
Такая служба будет иметь доступ ко всей иерархии файловых систем ОС, с
|
||
единственным исключением~--- она не~будет видеть каталог +/home+, что позволит
|
||
защитить данные пользователей от потенциальных хакеров. (Подробнее об этих
|
||
опциях можно почитать на
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}%
|
||
{странице руководства}.)
|
||
|
||
Фактически, FSNS по множеству параметров превосходят +chroot()+. Скорее всего,
|
||
Avahi и RealtimeKit в ближайшем будущем перейдут от +chroot()+ к использованию
|
||
FSNS.
|
||
|
||
Итак, мы рассмотрели вопросы использования chroot для обеспечения безопасности.
|
||
Переходим ко второму пункту: подготовка и управление образом операционной
|
||
системы при отладке, тестировании, компиляции, установке или восстановлении.
|
||
|
||
chroot-окружения, по сути, весьма примитивны: они изолируют только иерархии
|
||
файловых систем. Даже после chroot'а в определенный подкаталог, процесс
|
||
по-прежнему имеет полный доступ к системным вызовам, может убивать процессы,
|
||
запущенные в основной системе, и т.п. Вследствие этого, запуск полноценной ОС
|
||
(или ее части) внутри chroot'а несет угрозу для хост-системы: у гостя и хоста
|
||
отличается лишь содержимое файловой системы, все остальное у них общее.
|
||
Например, если вы обновляете дистрибутив, установленный в chroot-окружении, и
|
||
пост-установочный скрипт пакета отправляет +SIGTERM+ процессу init для его
|
||
перезапуска\footnote{Прим. перев.: Во избежание путаницы отметим, что перезапуск
|
||
процесса init (PID~1) <<на лету>> при получении +SIGTERM+ поддерживается только
|
||
в systemd, в классическом SysV init такой возможности нет.}, на него среагирует
|
||
именно хост-система! Кроме того, хост и chroot'нутая система будут иметь общую
|
||
разделяемую память SysV (SysV shared memory), общие сокеты из абстрактных
|
||
пространств имен (abstract namespace sockets) и другие элементы IPC. Для
|
||
отладки, тестирования, компиляции, установки и восстановлении ОС не~требуется
|
||
абсолютно неуязвимая изоляция~--- нужна лишь надежная защита от
|
||
\emph{случайного} воздействия на ОС хоста изнутри chroot-окружения, иначе вы
|
||
можете получить целый букет проблем, как минимум, от пост-инсталляционных
|
||
скриптов при установке пакетов в chroot-окружении.
|
||
|
||
systemd имеет целый ряд возможностей, полезных для работы с chroot-системами:
|
||
|
||
Прежде всего, управляющая программа +systemctl+ автоматически определяет, что
|
||
она запущена в chroot-системе. В такой ситуации будут работать только команды
|
||
+systemctl enable+ и +systemctl disable+, во всех остальных случаях +systemctl+
|
||
просто не~будет ничего делать, возвращая код успешного завершения операции.
|
||
Таким образом, пакетные скрипты смогут включить/отключить запуск <<своих>> служб
|
||
при загрузке (или в других ситуациях), однако команды наподобие
|
||
+systemctl restart+ (обычно выполняется при обновлении пакета) не~дадут никакого
|
||
эффекта внутри chroot-окружения\footnote{Прим. перев.: Автор забывает отметить
|
||
не~вполне очевидный момент: такое поведение +systemctl+ проявляется только в
|
||
<<мертвых>> окружениях, т.е. в тех, где не~запущен процесс init, и
|
||
соответственно отсутствуют управляющие сокеты в +/run/systemd+. Такая ситуация
|
||
возникает, например, при установке системы в chroot через
|
||
debootstrap/febootstrap. В этом случае возможности +systemctl+ ограничиваются
|
||
операциями с символьными ссылками, определяющими триггеры активации юнитов, т.е.
|
||
выполнением действий +enable+ и +disable+, не~требующих непосредственного
|
||
взаимодействия с процессом init.}.
|
||
|
||
Однако, куда более интересные возможности предоставляет программа
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html}{systemd-nspawn},
|
||
входящая в стандартный комплект поставки systemd. По сути, это улучшенный аналог
|
||
+chroot(1)+~--- она не~только подменяет корневой каталог, но и создает отдельные
|
||
пространства имен для дерева файловых систем (FSNS) и для идентификаторов
|
||
процессов (PID NS), предоставляя легковесную реализацию системного
|
||
контейнера\footnote{Прим. перев.: Используемые в +systemd-nspawn+ механизмы
|
||
ядра Linux, такие, как FS NS и PID NS, также лежат в основе
|
||
\href{http://lxc.sourceforge.net/}{LXC}, системы контейнерной изоляции для
|
||
Linux, которая позиционируется как современная альтернатива классическому
|
||
\href{http://wiki.openvz.org/Main_Page}{OpenVZ}. Стоит отметить, что LXC
|
||
ориентирована прежде всего на создание независимых виртуальных окружений,
|
||
с поддержкой раздельных сетевых стеков, ограничением на ресурсы, сохранением
|
||
настроек и т.п., в то время как +systemd-nspawn+ является лишь более удобной и
|
||
эффективной заменой команды +chroot(1)+, предназначенной прежде всего для
|
||
развертывания, восстановления, сборки и тестирования операционных систем. Далее
|
||
автор разъясняет свою точку зрения на этот вопрос.}.
|
||
+systemd-nspawn+ проста в использовании как +chroot(1)+, однако изоляция
|
||
от хост-системы является более полной и безопасной. Всего одной командой
|
||
вы можете загрузить внутри контейнера \emph{полноценную} ОС (на базе systemd
|
||
или SysV init). Благодаря использованию независимых пространств идентификаторов
|
||
процессов, процесс init внутри контейнера получит PID~1, что позволит работать
|
||
ему в штатном режиме. Также, в отличие от +chroot(1)+, внутри окружения
|
||
будут автоматически смонтированы +/proc+ и +/sys+.
|
||
|
||
Следующий пример иллюстрирует возможность запустить Debian на Fedora-хосте
|
||
всего тремя командами:
|
||
\begin{Verbatim}
|
||
# yum install debootstrap
|
||
# debootstrap --arch=amd64 unstable debian-tree/
|
||
# systemd-nspawn -D debian-tree/
|
||
\end{Verbatim}
|
||
|
||
Вторая из этих команд обеспечивает развертывание в подкаталоге +./debian-tree/+
|
||
файловой структуры дистрибутива Debian, после чего третья команда запускает
|
||
внутри полученной системы процесс командной оболочки. Если вы хотите запустить
|
||
внутри контейнера полноценную ОС, воспользуйтесь командой
|
||
\begin{Verbatim}
|
||
# systemd-nspawn -D debian-tree/ /sbin/init
|
||
\end{Verbatim}
|
||
|
||
После быстрой загрузки вы получите приглашение оболочки, запущенной внутри
|
||
полноценной ОС, функционирующей в контейнере. Изнутри контейнера невозможно
|
||
увидеть процессы, которые находятся вне его. Контейнер сможет пользоваться сетью
|
||
хоста\footnote{Прим. перев.: Впоследствии в +systemd-nspawn+ была добавлена
|
||
опция +--private-network+, позволяющая изолировать систему внутри контейнера от
|
||
сети хоста: такая система будет видеть только свой собственный интерфейс
|
||
обратной петли.}, однако не~имеет возможности изменить ее настройки (это может
|
||
привести к серии ошибок в процессе загрузки гостевой ОС, но ни~одна из этих
|
||
ошибок не~должна быть критической). Контейнер получает доступ к +/sys+ и
|
||
+/proc/sys+, однако, во избежание вмешательства контейнера в конфигурацию ядра и
|
||
аппаратного обеспечения хоста, эти каталоги будут смонтированы только для
|
||
чтения. Обратите внимание, что эта защита блокирует лишь \emph{случайные},
|
||
\emph{непредвиденные} попытки изменения параметров. При необходимости, процесс
|
||
внутри контейнера, обладающий достаточными полномочиями, сможет перемонтировать
|
||
эти файловые системы в режиме чтения-записи.
|
||
|
||
Итак, что же такого хорошего в +systemd-nspawn+?
|
||
\begin{enumerate}
|
||
\item Использовать эту утилиту очень просто. Вам даже не~нужно вручную
|
||
монтировать внутри окружения +/proc+ и +/sys+~--- она сделает
|
||
это за вас, а ядро автоматически отмонтирует их, когда последний
|
||
процесс контейнера завершится.
|
||
\item Обеспечивается надежная изоляция, предотвращающая случайные
|
||
изменения параметров ОС хоста изнутри контейнера.
|
||
\item Теперь вы можете загрузить внутри контейнера полноценную ОС, а
|
||
не~одну-единственную оболочку.
|
||
\item Эта утилита очень компактна и присутствует везде, где установлен
|
||
systemd. Она не~требует специальной установки и настройки.
|
||
\end{enumerate}
|
||
|
||
systemd уже подготовлен для работы внутри таких контейнеров. Например, когда
|
||
подается команда на выключение системы внутри контейнера, systemd на последнем
|
||
шаге вызывает не~+reboot()+, а просто +exit()+.
|
||
|
||
Стоит отметить, что +systemd-nspawn+ все же не~является полноценной системой
|
||
контейнерной виртуализации/изоляции~--- если вам нужно такое решение,
|
||
воспользуйтесь \href{ http://lxc.sourceforge.net/}{LXC}. Этот проект использует
|
||
те же самые механизмы ядра, но предоставляет куда более широкие возможности,
|
||
включая виртуализацию сети. Могу предложить такую аналогию: +systemd-nspawn+ как
|
||
реализация контейнера похожа на GNOME~3~--- компактна и проста в использовании,
|
||
опций для настройки очень мало. В то время как LXC больше похож на KDE: опций
|
||
для настройки больше, чем строк кода. Я создал +systemd-nspawn+ специально для
|
||
тестирования, отладки, сборки, восстановления. Именно для этих задач вам стоит
|
||
ее использовать~--- она неплохо с ними справляется, куда лучше, чем +chroot(1)+.
|
||
|
||
Что ж, пора заканчивать. Итак:
|
||
\begin{enumerate}
|
||
\item Использование +chroot()+ для обеспечения безопасности дает
|
||
наилучший результат, когда оно реализовано непосредственно в
|
||
коде самой программы.
|
||
\item +ReadOnlyDirectories=+ и +InaccessibleDirectories=+ могут быть
|
||
удобной альтернативой созданию полновесных chroot-окружений.
|
||
\item Если вам нужно поместить в chroot-окружение какую-либо службу,
|
||
воспользуйтесь опцией +RootDirectory=+.
|
||
\item +systemd-nspawn+~--- очень неплохая штука.
|
||
\item chroot'ы убоги, FSNS~---
|
||
\href{http://ru.wikipedia.org/wiki/Leet}{1337}.
|
||
\end{enumerate}
|
||
|
||
И все это уже сейчас доступно в Fedora~15.
|
||
|
||
\section{Поиск виновных}
|
||
|
||
Fedora~15\footnote{Величайший в истории релиз свободной ОС.}
|
||
является первым релизом Fedora, использующим systemd в качестве системы
|
||
инициализации по умолчанию. Основной нашей целью при работе над выпуском F15
|
||
является обеспечение полной взаимной интеграции и корректной работы всех
|
||
компонентов. При подготовке следующего релиза, F16, мы сконцентрируемся на
|
||
дальнейшей полировке и ускорении системы. Для этого мы подготовили ряд
|
||
инструментов (доступных уже в F15), которые должны помочь нам в поиске проблем,
|
||
связанных с процессом загрузки. В этой статье я попытаюсь рассказать о том, как
|
||
найти виновников медленной загрузки вашей системы, и о том, что с ними делать
|
||
дальше.
|
||
|
||
Первый инструмент, который мы можем вам предложить, очень прост: по завершении
|
||
загрузки, systemd регистрирует в системном журнале информацию о суммарном
|
||
времени загрузки:
|
||
\begin{Verbatim}
|
||
systemd[1]: Startup finished in 2s 65ms 924us (kernel) + 2s 828ms 195us (initrd)
|
||
+ 11s 900ms 471us (userspace) = 16s 794ms 590us.
|
||
\end{Verbatim}
|
||
|
||
Эта запись означает следующее: на инициализацию ядра (до момента запуска initrd,
|
||
т.е. dracut) ушло 2 секунды. Далее, чуть менее трех секунд работал initrd. И
|
||
наконец, почти 12 секунд было потрачено systemd на запуск программ из
|
||
пространства пользователя. Итоговое время, начиная с того момента, как загрузчик
|
||
передал управление коду ядра, до того момента, как systemd завершил все
|
||
операции, связанные с загрузкой системы, составило почти 17 секунд. Казалось
|
||
бы, смысл этого числа вполне очевиден\ldots{} Однако не~стоит делать поспешных
|
||
выводов. Прежде всего, сюда не~входит время, затраченное на
|
||
инициализацию вашего сеанса в GNOME, так как эта задача уже выходит за рамки
|
||
задач процесса init. Кроме того, в этом показателе учитывается только время
|
||
работы systemd, хотя часто бывает так, что некоторые демоны продолжают
|
||
\emph{свою} работу по инициализации уже после того, как секундомер остановлен.
|
||
Проще говоря: приведенные числа позволяют лишь оценить общую скорость
|
||
загрузки, однако они не~являются точной характеристикой длительности процесса.
|
||
|
||
Кроме того, эта информация носит поверхностный характер: она не~сообщает, какие
|
||
именно системные компоненты заставляют systemd ждать так долго. Чтобы исправить
|
||
это упущение, мы ввели команду +systemd-analyze blame+:
|
||
\begin{Verbatim}
|
||
$ systemd-analyze blame
|
||
6207ms udev-settle.service
|
||
5228ms cryptsetup@luks\x2d9899b85d\x2df790\x2d4d2a\x2da650\x2d8b7d2fb92cc3.service
|
||
735ms NetworkManager.service
|
||
642ms avahi-daemon.service
|
||
600ms abrtd.service
|
||
517ms rtkit-daemon.service
|
||
478ms fedora-storage-init.service
|
||
396ms dbus.service
|
||
390ms rpcidmapd.service
|
||
346ms systemd-tmpfiles-setup.service
|
||
322ms fedora-sysinit-unhack.service
|
||
316ms cups.service
|
||
310ms console-kit-log-system-start.service
|
||
309ms libvirtd.service
|
||
303ms rpcbind.service
|
||
298ms ksmtuned.service
|
||
288ms lvm2-monitor.service
|
||
281ms rpcgssd.service
|
||
277ms sshd.service
|
||
276ms livesys.service
|
||
267ms iscsid.service
|
||
236ms mdmonitor.service
|
||
234ms nfslock.service
|
||
223ms ksm.service
|
||
218ms mcelog.service
|
||
...
|
||
\end{Verbatim}
|
||
|
||
Она выводит список юнитов systemd, активированных при загрузке, с указанием
|
||
времени инициализации для каждого из них. Список отсортирован по убыванию этого
|
||
времени, поэтому наибольший интерес для нас представляют первые строчки. В нашем
|
||
случае это +udev-settle.service+ и
|
||
+cryptsetup@luks\x2d9899b85d\x2df790\x2d4d2a\x2da650\x2d8b7d2fb92cc3.service+,
|
||
инициализация которых занимает более одной секунды. Стоит отметить, что к
|
||
анализу вывода команды +systemd-analyze blame+ тоже следует подходить с
|
||
осторожностью: она не~поясняет, \emph{почему} тот или иной юнит тратит
|
||
столько-то времени, она лишь констатирует факт, что время было затрачено. Кроме
|
||
того, не~стоит забывать, что юниты могут запускаться параллельно. В частности,
|
||
если две службы были запущены одновременно, то время их инициализации будет
|
||
значительно меньше, чем сумма времен инициализации каждой из них.
|
||
|
||
Рассмотрим повнимательнее первого осквернителя нашей загрузки: службу
|
||
+udev-settle.service+\footnote{Прим. перев.: После объединения проектов systemd
|
||
и udev, эта служба называется +systemd-udev-settle.service+.} Почему ей
|
||
требуется так много времени для запуска, и что мы можем с этим сделать? Данная
|
||
служба выполняет очень простую функцию: она ожидает, пока udev завершит опрос
|
||
устройств, после чего завершается. Опрос же устройств может занимать довольно
|
||
много времени. Например, в нашем случае опрос устройств длится более 6~секунд
|
||
из-за подключенного к компьютеру 3G-модема, в котором отсутствует SIM-карта.
|
||
Этот модем очень долго отвечает на запросы udev. Опрос устройств является
|
||
частью схемы, обеспечивающей работу ModemManager'а и позволяющей
|
||
NetworkManager'у упростить для вас настройку 3G. Казалось бы, очевидно, что
|
||
виновником задержки является именно ModemManager, так как опрос устройств для
|
||
него занимает слишком много времени. Но такое обвинение будет заведомо
|
||
ошибочным. Дело в том, что опрос устройств очень часто оказывается довольно
|
||
длительной процедурой. Медленный опрос 3G-устройств для ModemManager является
|
||
частным случаем, отражающим это общее правило. Хорошая система опроса устройств
|
||
обязательно должна учитывать тот факт, что операция опроса любого из устройств
|
||
может затянуться надолго. Истинной причиной наших проблем является необходимость
|
||
ожидать завершения опроса, т.е., наличие службы +udev-settle.service+ как
|
||
обязательной части нашего процесса загрузки.
|
||
|
||
Но почему эта служба вообще присутствует в нашем процессе загрузки? На самом
|
||
деле, мы можем прекрасно обойтись и без нее. Она нужна лишь как часть
|
||
используемой в Fedora схемы инициализации устройств хранения, а именно, набора
|
||
скриптов, выполняющих настройку LVM, RAID и multipath-устройств. На сегодняшний
|
||
день, реализация этих систем не~поддерживает собственного механизма поиска и
|
||
опроса устройств, и поэтому их запуск должен производиться только после того,
|
||
как эта работа уже проделана~--- тогда они могут просто последовательно
|
||
перебирать все диски. Однако, такой подход противоречит одному из
|
||
фундаментальных требований к современным компьютерам: возможности подключать и
|
||
отключать оборудование в любой момент, как при выключенном компьютере, так и при
|
||
включенном. Для некоторых технологий невозможно точно определить момент
|
||
завершения формирования списка устройств (например, это характерно для USB и
|
||
iSCSI), и поэтому процесс опроса таких устройств обязательно должен включать
|
||
некоторую фиксированную задержку, гарантирующую, что все подключенные устройства
|
||
успеют отозваться. С точки зрения скорости загрузки это, безусловно, негативный
|
||
эффект: соответствующие скрипты заставляют нас ожидать завершения опроса
|
||
устройств, хотя большинство этих устройств не~являются необходимыми на данной
|
||
стадии загрузки. В частности, в рассматриваемой нами системе LVM, RAID и
|
||
multipath вообще не~используются!\footnote{Наиболее правильным решением в данном
|
||
случае будет ожидание событий подключения устройств (реализованное через libudev
|
||
или аналогичную технологию) с соответствующей обработкой каждого такого
|
||
события~--- подобный подход позволит нам продолжить загрузку, как только будут
|
||
готовы все устройства, необходимые для ее продолжения. Для того, чтобы загрузка
|
||
была быстрой, мы должны ожидать завершения инициализации только тех устройств,
|
||
которые \emph{действительно} необходимы для ее продолжения на данной стадии.
|
||
Ожидать остальные устройства в данном случае смысла нет. Кроме того, стоит
|
||
отметить, что в числе программ, не~приспособленных для работы с динамически
|
||
меняющимся оборудованием и построенных в предположении о неизменности списка
|
||
устройств, кроме служб хранения, есть и другие подсистемы. Например, в нашем
|
||
случае работа initrd занимает так много времени главным образом из-за того, что
|
||
для запуска Plymouth необходимо дождаться завершения инициализации всех
|
||
устройств видеовывода. По неизвестной причине (во всяком случае, неизвестной для
|
||
меня) подгрузка модулей для моих видеокарт Intel занимает довольно
|
||
продолжительное время, что приводит к беспричинной задержке процесса загрузки.
|
||
(Нет, я возражаю не~против опроса устройств как такового, но против
|
||
необходимости ожидать его завершения, чтобы продолжить загрузку.)}
|
||
|
||
С учетом вышесказанного, мы можем спокойно исключить +udev-settle.service+ из
|
||
нашего процесса загрузки, так как мы не~используем ни~LVM, ни~RAID,
|
||
ни~multipath. Замаскируем эти службы, чтобы увеличить скорость загрузки:
|
||
\begin{Verbatim}
|
||
# ln -s /dev/null /etc/systemd/system/udev-settle.service
|
||
# ln -s /dev/null /etc/systemd/system/fedora-wait-storage.service
|
||
# ln -s /dev/null /etc/systemd/system/fedora-storage-init.service
|
||
# systemctl daemon-reload
|
||
\end{Verbatim}
|
||
|
||
После перезагрузки мы видим, что загрузка стала на одну секунду быстрее. Но
|
||
почему выигрыш оказался таким маленьким? Из-за второго осквернителя нашей
|
||
загрузки~--- cryptsetup. На рассматриваемой нами системе зашифрован раздел
|
||
+/home+. Специально для нашего тестирования я записал пароль в файл, чтобы
|
||
исключить влияние скорости ручного набора пароля. К сожалению, для подключения
|
||
шифрованного раздела cryptsetup требует более пяти секунд. Будем ленивы, и
|
||
вместо того, чтобы исправлять ошибку в cryptsetup\footnote{На самом деле, я
|
||
действительно пытался исправить эту ошибку. Задержки в cryptsetup, по
|
||
моим наблюдениям, обусловлены, главным образом, слишком большим значением по
|
||
умолчанию для опции +--iter-time+. Я попытался доказать сопровождающим пакета
|
||
cryptsetup, что снижение этого времени с 1 секунды до 100~миллисекунд
|
||
не~приведет к трагическим последствиям для безопасности, однако они мне
|
||
не~поверили.}, попробуем найти обходной путь\footnote{Вообще-то я предпочитаю
|
||
решать проблемы, а не~искать пути для их обхода, однако в данном конкретном
|
||
случае возникает отличная возможность продемонстрировать одну интересную
|
||
возможность systemd\ldots{}}. Во время загрузки systemd должен дождаться, пока
|
||
все файловые системы, перечисленные в +/etc/fstab+ (кроме помеченных как
|
||
+noauto+) будут обнаружены, проверены и смонтированы. Только после этого systemd
|
||
может продолжить загрузку и приступить к запуску служб. Но первое обращение
|
||
к +/home+ (в отличие, например, от +/var+), происходит на поздней стадии
|
||
процесса загрузки (когда пользователь входит в систему). Следовательно, нам
|
||
нужно сделать так, чтобы этот каталог автоматически монтировался при загрузке,
|
||
но процесс загрузки не~ожидал завершения работы +cryptsetup+, +fsck+ и +mount+
|
||
для этого раздела. Как же сделать точку монтирования доступной, не~ожидая, пока
|
||
завершится процесс монтирования? Этого можно достичь, воспользовавшись
|
||
магической силой systemd~--- просто добавим опцию монтирования
|
||
+comment=systemd.automount+ в +/etc/fstab+. После этого, systemd будет создавать
|
||
в +/home+ точку автоматического монтирования, и при первом же обращении к этому
|
||
каталогу, если файловая система еще не~будет готова к работе, systemd подготовит
|
||
соответствующее устройство, проверит и смонтирует ее.
|
||
|
||
После внесения изменений в +/etc/fstab+ и перезагрузки мы получаем:
|
||
\begin{Verbatim}
|
||
systemd[1]: Startup finished in 2s 47ms 112us (kernel) + 2s 663ms 942us (initrd)
|
||
+ 5s 540ms 522us (userspace) = 10s 251ms 576us.
|
||
\end{Verbatim}
|
||
|
||
Прекрасно! Несколькими простыми действиями мы выиграли почти семь секунд. И эти
|
||
два изменения исправляют только две наиболее очевидные проблемы. При более
|
||
аккуратном и детальном исследовании, обнаружится еще множество моментов, которые
|
||
можно улучшить. Например, на другом моем компьютере, лаптопе X300 двухлетней
|
||
давности (и даже два года назад он был не~самым быстрым на Земле), после
|
||
небольшой доработки, время загрузки до полноценной среды GNOME
|
||
составило около четырех секунд, и это еще не~предел совершенства.
|
||
|
||
+systemd-analyze blame+~--- простой и удобный инструмент для поиска медленно
|
||
запускающихся служб, однако он обладает одним существенным недостатком: он
|
||
не~показывает, насколько эффективно параллельный запуск снижает потерю времени
|
||
для медленно запускающихся служб. Чтобы вы могли наглядно оценить этот фактор,
|
||
мы подготовили команду +systemd-analyze plot+. Использовать ее очень просто:
|
||
\begin{Verbatim}
|
||
$ systemd-analyze plot > plot.svg
|
||
$ eog plot.svg
|
||
\end{Verbatim}
|
||
|
||
Она создает наглядные диаграммы, показывающие моменты запуска служб и время,
|
||
затраченное на их запуск, по отношению к другим службам. На текущий момент, она
|
||
не~показывает явно, кто кого ожидает, но догадаться обычно
|
||
несложно\footnote{Прим. перев.: Начиная с systemd 203, добавлена поддержка
|
||
специальной команды +systemd-analyze critical-chain+, которая выводит самую
|
||
медленную цепочку юнитов (цепь в графе зависимостей, формируемом при
|
||
загрузке). Время запуска всех юнитов в этой цепочке определяет итоговое время
|
||
загрузки системы.}.
|
||
|
||
Чтобы продемонстрировать эффект, порожденный двумя нашими оптимизациями,
|
||
приведем ссылки на соответствующие графики:
|
||
\href{http://0pointer.de/public/blame.svg}{до} и
|
||
\href{http://0pointer.de/public/blame2.svg}{после} (для полноты описания приведем также и
|
||
соответствующие данные +systemd-analyze blame+:
|
||
\href{http://0pointer.de/public/blame.txt}{до} и
|
||
\href{http://0pointer.de/public/blame2.txt}{после}).
|
||
|
||
У наиболее эрудированных читателей может возникнуть вопрос: как это соотносится с
|
||
программой \href{https://github.com/mmeeks/bootchart}{bootchart}? Действительно,
|
||
+systemd-analyze plot+ и +bootchart+ рисуют похожие графики. Однако, bootchart
|
||
является намного более мощным инструментом~--- он детально показывает, что
|
||
именно происходило во время загрузки, и отображает соответствующие графики
|
||
использования процессора и ввода-вывода. +systemd-analyze plot+ оперирует более
|
||
высокоуровневой информацией: сколько времени затратила та или иная служба во
|
||
время запуска, и какие службы были вынуждены ее ожидать. Используя оба этих
|
||
инструмента, вы значительно упростите себе поиск причин замедления вашей
|
||
загрузки.
|
||
|
||
Но прежде, чем вы вооружитесь описанными здесь средствами и начнете
|
||
отправлять багрепорты авторам и сопровождающим программ, которые замедляют вашу
|
||
загрузку, обдумайте все еще раз. Эти инструменты предоставляют вам <<сырую>>
|
||
информацию. Постарайтесь не~ошибиться, интерпретируя ее. Например, в
|
||
при рассмотрении приведенного выше примера я показал, что проблема была вовсе
|
||
не~в +udev-settle.service+, и не~в опросе устройств для ModemManager'а, а в
|
||
неудачной реализации подсистемы хранения, требующей ожидать окончания опроса
|
||
устройств для продолжения загрузки. Именно эту проблему и нужно исправлять.
|
||
Поэтому постарайтесь правильно определить источник проблем. Возлагайте вину на
|
||
тех, кто действительно виноват.
|
||
|
||
Как уже говорилось выше, все три описанных здесь инструмента доступны в
|
||
Fedora~15 <<из коробки>>.
|
||
|
||
Итак, какие же выводы мы можем сделать из этой истории?
|
||
\begin{itemize}
|
||
\item +systemd-analyze+~--- отличный инструмент. Фактически, это
|
||
встроенный профилировщик systemd.
|
||
\item Постарайтесь не~ошибиться, интерпретируя вывод профилировщика!
|
||
\item Всего два небольших изменения могут ускорить загрузку системы на
|
||
семь секунд.
|
||
\item Программное обеспечение, не~способное работать с динамически
|
||
меняющимся набором устройств, создает проблемы, и должно быть
|
||
исправлено.
|
||
\item Принудительное использование в стандартной установке Fedora~15
|
||
промышленной реализации подсистем хранения, возможно, является
|
||
не~самым правильным решением.
|
||
\end{itemize}
|
||
|
||
\section{Новые конфигурационные файлы}
|
||
|
||
Одно из ключевых достоинств
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd}{systemd}~--- наличие
|
||
полного набора программ, необходимых на ранних стадиях загрузки, причем эти
|
||
программы написаны на простом, быстром, надежном и легко поддающемся
|
||
распараллеливанию языке C. Теперь можно отказаться от <<простыней>>
|
||
shell-скриптов, разработанных для этих задач различными дистрибутивами. Наш
|
||
<<Проект нулевой оболочки>>\footnote{Наш девиз~--- <<Первой оболочкой,
|
||
запускающейся при старте системы, должна быть GNOME shell>>. Формулировка
|
||
оставляет желать лучшего, но все же неплохо передает основную идею.} увенчался
|
||
полным успехом. Уже сейчас возможности предоставляемого нами инструментария
|
||
покрывают практически все нужды настольных и встраиваемых систем, а также
|
||
б\'{о}льшую часть потребностей серверов:
|
||
\begin{itemize}
|
||
\item Проверка и монтирование всех файловых систем.
|
||
\item Обновление и активация квот на всех файловых системах.
|
||
\item Установка имени хоста.
|
||
\item Настройка сетевого интерфейса обратной петли (+lo+).
|
||
\item Подгрузка правил SELinux, обновление
|
||
меток безопасности в динамических каталогах +/run+ и +/dev+.
|
||
\item Регистрация в ядре дополнительных бинарных форматов (например,
|
||
Java, Mono, WINE) через API-файловую систему +binfmt_misc+.
|
||
\item Установка системной локали.
|
||
\item Настройка шрифта и раскладки клавиатуры в консоли.
|
||
\item Создание, очистка, удаление временных файлов и каталогов.
|
||
\item Применение предписанных в +/etc/fstab+ опций к смонтированным
|
||
ранее API-файловым системам.
|
||
\item Применение настроек +sysctl+.
|
||
\item Поддержка технологии упреждающего чтения (read ahead), включая
|
||
автоматический сбор информации.
|
||
\item Обновление записей в +utmp+ при включении и выключении системы.
|
||
\item Сохранение и восстановление затравки для генерации случайных чисел
|
||
(random seed).
|
||
\item Принудительная загрузка указанных модулей ядра.
|
||
\item Поддержка шифрованных дисков и разделов.
|
||
\item Автоматический запуск +getty+ на serial-консолях.
|
||
\item Взаимодействие с Plymouth.
|
||
\item Создание уникального идентификатора системы.
|
||
\item Настройка часового пояса.
|
||
\end{itemize}
|
||
|
||
В стандартной установке Fedora~15 запуск shell-скриптов требуется только для
|
||
некоторых устаревших служб, а также для подсистемы хранения данных (поддержка
|
||
LVM, RAID и multipath). Если они вам не~нужны, вы легко можете отключить их, и
|
||
наслаждаться загрузкой, полностью очищенной от shell-костылей (лично я это
|
||
сделал уже давно). Такая загрузка является уникальной возможностью Linux-систем.
|
||
|
||
Большинство перечисленных выше компонентов настраиваются через конфигурационные
|
||
файлы в каталоге +/etc+. Некоторые из этих файлов стандартизированы для всех
|
||
дистрибутивов, и поэтому реализация их поддержки в наших инструментах
|
||
не~представляла особого труда. Например, это относится к файлам +/etc/fstab+,
|
||
+/etc/crypttab+, +/etc/sysctl.conf+. Однако множество других, нестандартно
|
||
расположенных файлов и каталогов вынуждали нас добавлять в код огромное
|
||
количество операторов +#ifdef+, чтобы обеспечить поддержку различных вариантов
|
||
расположения конфигураций в разных дистрибутивах. Такое положение дел сильно
|
||
усложняет жизнь нам всем, и при этом ничем не~оправдано~--- все эти файлы решают
|
||
одни и те же задачи, просто немного по-разному.
|
||
|
||
Чтобы улучшить ситуацию и установить единый стандарт расположения базовых
|
||
конфигурационных файлов во всех дистрибутивах, мы заставили systemd пользоваться
|
||
дистрибутивно-специфическими конфигурациями только в качестве \emph{резервного}
|
||
варианта~--- основным источником информации становится определенный нами
|
||
стандартный набор конфигурационных файлов. Разумеется, там, где это возможно, мы
|
||
старались не~придумывать чего-то принципиально нового, а брали лучшее из
|
||
решений, предложенных существующими дистрибутивами. Ниже приводится небольшой
|
||
обзор этого нового набора конфигурационных файлов, поддерживаемых systemd во
|
||
всех дистрибутивах:
|
||
\begin{itemize}
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/hostname.html}%
|
||
{/etc/hostname}:
|
||
имя хоста для данной системы. Одна из наиболее простых и важных
|
||
системных настроек. В разных дистрибутивах оно настраивалось
|
||
по-разному: Fedora использовала +/etc/sysconfig/network+,
|
||
OpenSUSE~--- +/etc/HOSTNAME+, Debian~--- +/etc/hostname+. Мы
|
||
остановились на варианте, предложенном Debian.
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/vconsole.conf.html}%
|
||
{/etc/vconsole.conf}:
|
||
конфигурация раскладки клавиатуры и шрифта для консоли.
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/locale.conf.html}%
|
||
{/etc/locale.conf}:
|
||
конфигурация общесистемной локали.
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/modules-load.d.html}%
|
||
{/etc/modules-load.d/*.conf}:
|
||
каталог\footnote{Прим. перев.: Для описания этого и трех
|
||
последующих каталогов автор пользуется термином <<drop-in
|
||
directory>>. Данный термин означает каталог, в который можно
|
||
поместить множество независимых файлов настроек, и при чтении
|
||
конфигурации все эти файлы будут обработаны (впрочем, часто
|
||
накладывается ограничение~--- обрабатываются только файлы с
|
||
именами, соответствующими маске, обычно +*.conf+). Такой подход
|
||
позволяет значительно упростить процесс как ручного, так и
|
||
автоматического конфигурирования различных компонентов~--- для
|
||
внесения изменений в настройки уже не~нужно редактировать
|
||
основной конфигурационный файл, достаточно лишь
|
||
скопировать/переместить в нужный каталог небольшой файл с
|
||
указанием специфичных параметров.} для перечисления модулей
|
||
ядра, которые нужно принудительно подгрузить при загрузке
|
||
(впрочем, необходимость в этом возникает достаточно редко).
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/sysctl.d.html}%
|
||
{/etc/sysctl.d/*.conf}:
|
||
каталог для задания параметров ядра (+sysctl+). Дополняет
|
||
классический конфигурационный файл +/etc/sysctl.conf+.
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/tmpfiles.d.html}%
|
||
{/etc/tmpfiles.d/*.conf}:
|
||
каталог для управления настройками временных файлов (systemd
|
||
обеспечивает создание, очистку и удаление временных файлов и
|
||
каталогов, как во время загрузки, так и во время работы
|
||
системы).
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/binfmt.d.html}%
|
||
{/etc/binfmt.d/*.conf}:
|
||
каталог для регистрации дополнительных бинарных форматов
|
||
(например, форматов Java, Mono, WINE).
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/os-release.html}%
|
||
{/etc/os-release}:
|
||
стандарт для файла, обеспечивающего идентификацию дистрибутива и
|
||
его версии. Сейчас различные дистрибутивы используют для этого
|
||
разные файлы (например, +/etc/fedora-release+ в Fedora), и
|
||
поэтому для решения такой простой задачи, как вывод имени
|
||
дистрибутива, необходимо использовать базу данных, содержащую
|
||
перечень возможных названий файлов. Проект LSB попытался создать
|
||
такой инструмент~---
|
||
\hreftt{http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/lsbrelease.html}{lsb\_release}~---
|
||
однако реализация столь простой функции через скрипт на Python'е
|
||
является не~самым оптимальным решением. Чтобы исправить
|
||
сложившуюся ситуацию, мы решили перейти к единому простому
|
||
формату представления этой информации.
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/machine-id.html}%
|
||
{/etc/machine-id}:
|
||
файл с идентификатором данного компьютера (перекрывает
|
||
аналогичный идентификатор D-Bus). Гарантируется, что в любой
|
||
системе, использующей systemd, этот файл будет существовать и
|
||
содержать корректную информацию (если его нет, он автоматически
|
||
создается при загрузке). Мы вынесли этот файл из-под эгиды
|
||
D-Bus, чтобы упростить решение множества задач, требующих
|
||
наличия уникального и постоянного идентификатора компьютера.
|
||
\item
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/machine-info.html}%
|
||
{/etc/machine-info}:
|
||
новый конфигурационный файл, хранящий информации о полном
|
||
(описательном) имени хоста (например, <<Компьютер Леннарта>>) и
|
||
значке, которым он будет обозначаться в графических оболочках,
|
||
работающих с сетью (раньше этот значок мог определяться,
|
||
например, файлом +/etc/favicon.png+). Данный конфигурационный
|
||
файл обслуживается демоном
|
||
\hreftt{http://www.freedesktop.org/wiki/Software/systemd/hostnamed}{systemd-hostnamed}.
|
||
\end{itemize}
|
||
|
||
Одна из важнейших для нас задач~--- убедить \emph{вас} использовать эти новые
|
||
конфигурационные файлы в ваших инструментах для настройки системы. Если ваши
|
||
конфигурационные фронтенды будут использовать новые файлы, а не~их старые
|
||
аналоги, это значительно облегчит портирование таких фронтендов между
|
||
дистрибутивами, и вы внесете свой вклад в стандартизацию Linux. В конечном счете
|
||
это упростит жизнь и администраторам, и пользователям. Разумеется, на текущий
|
||
момент эти файлы полностью поддерживаются только дистрибутивами, основанными на
|
||
systemd, но уже сейчас в их число входят практически все ключевые дистрибутивы,
|
||
\href{http://www.ubuntu.com/}{за исключением
|
||
одного}\footnote{Прим. перев.: В конце 2010~года энтузиаст Andrew Edmunds
|
||
\href{http://cgit.freedesktop.org/systemd/systemd/commit/?id=858dae181bb5461201ac1c04732d3ef4c67a0256}{добавил}
|
||
в systemd базовую поддержку Ubuntu и
|
||
\href{https://wiki.ubuntu.com/systemd}{подготовил} соответствующие пакеты,
|
||
однако его инициатива не~встретила поддержки среди менеджеров Canonical. На
|
||
момент написания этих строк проект остается заброшенным с декабря 2010~г.}. В
|
||
этом есть что-то от <<проблемы курицы и яйца>>: стандарт становится настоящим
|
||
стандартом только тогда, когда ему начинают следовать. В будущем мы намерены
|
||
аккуратно форсировать процесс перехода на новые конфигурационные файлы:
|
||
поддержка старых файлов будет удалена из systemd. Разумеется, процесс будет
|
||
идти медленно, шаг за шагом. Но конечной его целью является переход всех
|
||
дистрибутивов на единый набор базовых конфигурационных файлов.
|
||
|
||
Многие из этих файлов используются не~только программами для настройки системы,
|
||
но и апстримными проектами. Например, мы предлагаем проектам Mono, Java, WINE и
|
||
другим помещать конфигурацию для регистрации своих бинарных форматов в
|
||
+/etc/binfmt.d/+ средствами их собственной сборочной системы. Специфичные для
|
||
дистрибутивов механизмы поддержки бинарных форматов больше не~нужны, и ваш
|
||
проект будет работать одинаково хорошо во всех дистрибутивах. Аналогичное
|
||
предложение мы обращаем и ко всем разработчикам программ, которым требуется
|
||
автоматическое создание/очистка временных файлов и каталогов,
|
||
например, в каталоге +/run+ (\href{http://lwn.net/Articles/436012/}{ранее
|
||
известном} как +/var/run+). Таким проектам достаточно просто поместить
|
||
соответствующий конфигурационный файл в +/etc/tmpfiles.d/+, тоже средствами
|
||
собственной сборочной системы. Помимо прочего, подобный подход позволит
|
||
увеличить скорость загрузки, так как, в отличие от SysV, не~требует множества
|
||
shell-скриптов, выполняющих тривиальные задачи (регистрация бинарных форматов,
|
||
удаление/создание временных файлов/каталогов и т.п.). И пример того случая,
|
||
когда апстримная поддержка стандартной конфигурации дала бы огромные
|
||
преимущества~--- X11 (и его аналоги) могли бы устанавливать раскладку клавиатуры
|
||
на основании данных из +/etc/vconsole.conf+.
|
||
|
||
Разумеется, я понимаю, что отнюдь не~всех полностью устроят выбранные нами имена
|
||
и форматы конфигурационных файлов. Но нам все же нужно было что-то выбрать, и мы
|
||
выбрали то, что должно устроить большинство людей. Форматы конфигурационных
|
||
файлов максимально просты, и их можно легко читать и записывать даже из
|
||
shell-скриптов. Да, +/etc/bikeshed.conf+ могло бы быть неплохим именем
|
||
для файла конфигурации!\footnote{Прим. перев.: Здесь автор намекает на
|
||
\href{http://en.wikipedia.org/wiki/Parkinson's_Law_of_Triviality}{Паркинсоновский
|
||
Закон Тривиальности}, который гласит, что самые жаркие споры возникают вокруг
|
||
наиболее простых вопросов. В частности, в качестве примера Паркинсон приводит
|
||
обсуждение строительства атомной электростанции и гаража для велосипедов (bike
|
||
shed)~--- если первое из этих решений принимается довольно быстро, то вокруг
|
||
второго разгорается множество дискуссий по самым разным аспектам.}
|
||
|
||
\textbf{Помогите нам стандартизировать Linux! Используйте новые конфигурационные
|
||
файлы! Поддерживайте их в апстриме, поддерживайте их во всех дистрибутивах!}
|
||
|
||
И если у вас возникнет такой вопрос: да, все эти файлы так или иначе
|
||
обсуждались с разными разработчиками из различных дистрибутивов. И некоторые из
|
||
разработчиков планируют обеспечить поддержку новой конфигурации даже в
|
||
системах без systemd.
|
||
|
||
\section{О судьбе /etc/sysconfig и /etc/default}
|
||
|
||
В дистрибутивах, основанных на Red Hat и SUSE, это каталог называется
|
||
+/etc/sysconfig+. В дистрибутивах на базе Debian, его зовут +/etc/default+.
|
||
Во многих других дистрибутивах также присутствуют каталоги похожего назначения.
|
||
Связанные с ними вопросы неоднократно появляются в дискуссиях пользователей и
|
||
разработчиков systemd. В этой статье мне хотелось бы рассказать, что я, как
|
||
разработчик systemd, думаю об этих каталогах, и пояснить, почему, на мой взгляд,
|
||
от них лучше отказаться. Стоит отметить, что это мое личное мнение, и оно
|
||
может не~совпадать с позицией проекта Fedora или моего работодателя.
|
||
|
||
Начнем с небольшого исторического экскурса. Каталог +/etc/sysconfig+ появился в
|
||
дистрибутивах Red Hat и SUSE задолго до того, как я присоединился к этим
|
||
проектам~--- иными словами, это было очень давно.
|
||
Некоторое время спустя, в Debian появился аналогичный по смыслу каталог
|
||
+/etc/default+. Многие дистрибутивы используют такие каталоги, называя их
|
||
по-разному. Они имеются даже в некоторых ОС семейства Unix. (Например, в SCO.
|
||
Если эта тема вас заинтересовала~--- рекомендую обратиться к вашему знакомому
|
||
ветерану Unix, он расскажет гораздо подробнее и интереснее, чем я.) Несмотря на
|
||
то, что подобные каталоги широко используются в Linux и Unix, они совершенно
|
||
не~стандартизированы~--- ни в POSIX, ни в LSB/FHS, и результате мы имеем целый
|
||
зоопарк их различных реализаций в разных дистрибутивах.
|
||
|
||
Назначение этих каталогов определено весьма расплывчато. Абсолютное большинство
|
||
находящихся в них файлов являются включаемыми\footnote{Прим. перев.: Здесь автор
|
||
использует термин sourcable, происходящий от bash'овской директивы +source+,
|
||
обеспечивающей включение в скрипт кода из внешнего файла. В классическом POSIX
|
||
shell это соответствует оператору-точке <<+.+>>. В отличие от прямого запуска
|
||
одного скрипта из другого, включаемый код исполняется той же самой оболочкой,
|
||
что и основной код, и при возвращении в основной скрипт сохраняются переменные
|
||
окружения, определенные во включаемом коде. Как правило, код для включения
|
||
не~содержит shebang'а (+#!/bin/sh+ в начале файла).} shell-скриптами, содержащими,
|
||
главным образом, определения переменных. Большинство файлов из этих каталогов
|
||
включаются в одноименные скрипты SysV init. Данный принцип отражен в
|
||
\href{http://www.debian.org/doc/debian-policy/ch-opersys.html#s-sysvinit}{Debian
|
||
Policy Manual (раздел 9.3.2)} и в
|
||
\href{http://fedoraproject.org/wiki/Packaging:SysVInitScript}{Fedora Packaging
|
||
Guidelines}, однако в обоих дистрибутивах иногда встречаются файлы,
|
||
не~соответствующие описанной схеме, например, не~имеющие соответствующего
|
||
init-скрипта, или даже сами не~являющиеся скриптами.
|
||
|
||
Но почему вообще появились эти каталоги? Чтобы ответить на такой вопрос,
|
||
обратимся к истории развития концепции SysV init-скриптов. Исторически,
|
||
сложилось так, что они располагаются в каталоге под названием +/etc/rc.d/init.d+
|
||
(или что-то похожее). Отметим, что каталог +/etc+ вообще-то предназначен для
|
||
хранения файлов конфигурации, а не~исполняемого кода (в частности, скриптов).
|
||
Однако, в начале своей истории, init-скрипты рассматривались именно как файлы
|
||
конфигурации, и редактирование их администратором было общепринятой практикой.
|
||
Но со временем, по мере роста и усложнения этих скриптов, их стали рассматривать
|
||
уже не~как файлы конфигурации, а как некие программы. Чтобы упростить их
|
||
настройку и обеспечить безопасность процесса обновления, настройки были вынесены
|
||
в отдельные файлы, загружаемые при работе init-скриптов.
|
||
|
||
Попробуем составить некоторое представление о настройках, которые можно сделать
|
||
через эти файлы. Вот краткий и неполный список различных параметров, которые
|
||
могут быть заданы через переменные окружения в таких файлах (составлен мною по
|
||
результатам исследования соответствующих каталогов в Fedora и Debian):
|
||
\begin{itemize}
|
||
\item Дополнительные параметры командной строки для бинарника демона.
|
||
\item Настройки локали для демона.
|
||
\item Тайм-аут остановки для демона.
|
||
\item Режим остановки для демона.
|
||
\item Общесистемные настройки, например, системная локаль, часовой пояс,
|
||
параметры клавиатуры для консоли.
|
||
\item Избыточная информация о системных настройках, например, указание,
|
||
установлены ли аппаратные часы по Гринвичу или по местному
|
||
времени.
|
||
\item Списки правил брандмауэра, не~являются скриптами (!).
|
||
\item Привязка к процессорным ядрам для демона.
|
||
\item Настройки, не~относящиеся к процессу загрузки, например,
|
||
информация по установке пакетов с новыми ядрами, конфигурация
|
||
nspluginwrapper, разрешение на выполнение
|
||
предварительного связывания (prelinking) библиотек.
|
||
\item Указание, нужно ли запускать данную службу или нет.
|
||
\item Настройки сети.
|
||
\item Перечень модулей ядра, которые должны быть подгружены
|
||
принудительно.
|
||
\item Нужно ли отключать питание компьютера при остановке системы
|
||
(+poweroff+) или нет (+halt+).
|
||
\item Права доступа для файлов устройств (!).
|
||
\item Описание соответствующей SysV службы.
|
||
\item Идентификатор пользователя/группы, значение umask для демона.
|
||
\item Ограничения по ресурсам для демона.
|
||
\item Приоритет OOM killer'а для демона.
|
||
\end{itemize}
|
||
|
||
А теперь давайте ответим на вопрос: что же такого неправильного в
|
||
+/etc/sysconfig+ (+/etc/default+) и почему этим каталогам нет места в мире
|
||
systemd?
|
||
\begin{itemize}
|
||
\item Прежде всего, утрачены основная цель и смысл существования таких
|
||
каталогов: файлы конфигурации юнитов systemd не~являются
|
||
программами, в отличие от init-скриптов SysV. Эти файлы
|
||
представляют собой простые, декларативные описания конкретных
|
||
задач и функций, и обычно содержат не~более шести строк. Они
|
||
легко могут быть сгенерированы и проанализованы без
|
||
использования Bourne shell. Их легко читать и понимать. Кроме
|
||
того, их легко модифицировать: достаточно скопировать файл из
|
||
+/lib/systemd/system+ в +/etc/systemd/system+, после чего внести
|
||
в созданную копию необходимые изменения (при этом можно быть
|
||
уверенным, что изменения не~будут затерты пакетным менеджером).
|
||
Изначальная причина появления обсуждаемых каталогов~---
|
||
необходимость разделять код и параметры конфигурации~--- больше
|
||
не~существует, так как файлы описания юнитов не~являются кодом.
|
||
Проще говоря, обсуждаемые каталоги являются решением проблемы,
|
||
которой уже не~существует.
|
||
\item Обсуждаемые каталоги и файлы в них очень сильно привязаны к
|
||
специфике дистрибутивов. Мы же планируем, используя systemd,
|
||
способствовать стандартизации и унификации дистрибутивов. В
|
||
частности, одним из факторов такой стандартизации является
|
||
рекомендация распространять соответствующие файлы конфигурации
|
||
юнитов сразу с апстримным продуктом, а не~возлагать эту работу
|
||
на создателей пакетов, как это делалась во времена SysV.
|
||
Так как расположение обсуждаемых каталогов и настраиваемые через
|
||
них параметры сильно отличаются от дистрибутива к дистрибутиву,
|
||
пытаться поддерживать их в апстримных файлах конфигурации юнитов
|
||
просто бессмысленно. Хранение параметров конфигурации в этих
|
||
каталогах~--- один из факторов, превращающих Linux в зоопарк
|
||
несовместимых решений.
|
||
\item Большинство настроек, задаваемых через эти каталоги, являются
|
||
избыточными в мире systemd. Например, различные службы позволяют
|
||
задать таким методом параметры исполнения процесса, в частности,
|
||
идентификатор пользователя/группы, ограничения ресурсов,
|
||
привязки к ядрам CPU, приоритет OOM killer'а. Однако, эти
|
||
настройки поддерживаются лишь некоторыми init-скриптами, причем
|
||
одна и та же настройка в различных скриптах может называться
|
||
по-разному. С другой стороны, в мире systemd, все эти настройки
|
||
доступны для всех служб без исключения, и всегда задаются
|
||
одинаково, через одни и те же параметры конфигурационных файлов.
|
||
\item Файлы конфигурации юнитов имеют множество удобных и простых в
|
||
использовании настроек среды исполнения процесса, гораздо
|
||
больше, чем могут предоставить файлы из +/etc/sysconfig+.
|
||
\item Необходимость в некоторых из этих настроек весьма сомнительна.
|
||
Например, возьмем вышеупомянутую возможность задавать
|
||
идентификатор пользователя/группы для процесса. Эту задачу
|
||
должен решать разработчик ПО или дистрибутива. Вмешательство
|
||
администратора в данную настройку, как правило, лишено
|
||
смысла~--- только разработчик располагает всей информацией,
|
||
позволяющий предотвратить конфликты идентификаторов и имен
|
||
пользователей и групп.
|
||
\item Формат файлов, используемых для сохранения настроек, плохо
|
||
подходит для данной задачи. Так как эти файлы, как правило,
|
||
являются включаемыми shell-скриптами, ошибки при их чтении очень
|
||
трудно отследить. Например, ошибка в имени переменной приведет к
|
||
тому, что переменная не~будет изменена, однако никакого
|
||
предупреждения при этом не~выводится.
|
||
\item Кроме того, такая организация не~исключает влияния
|
||
конфигурационных параметров на среду исполнения: например,
|
||
изменение переменных +IFS+ и +LANG+ может существенно повлиять
|
||
на результат интерпретации init-скрипта.
|
||
\item Интерпретация этих файлов требует запуска еще одного экземпляра
|
||
оболочки, что приводит к задержкам при загрузке\footnote{Прим.
|
||
перев.: Здесь автор несколько заблуждается. Скрипты, включенные
|
||
через директиву +source+, исполняются тем же экземпляром
|
||
оболочки, что и вызвавший их скрипт.}.
|
||
\item Файлы из +/etc/sysconfig+ часто пытаются использовать в качестве
|
||
суррогатной замены файлов конфигурации для тех демонов, которые
|
||
не~имеют встроенной поддержки конфигурационных файлов. В
|
||
частности, вводятся специальные переменные, позволяющие задать
|
||
аргументы командной строки, используемые при запуске демона.
|
||
Встроенная поддержка конфигурационных файлов является более
|
||
удобной альтернативой такому подходу, ведь, глядя на ключи
|
||
<<+-k+>>, <<+-a+>>, <<+-f+>>, трудно догадаться об их
|
||
назначении. Очень часто, из-за ограниченности словаря, на
|
||
различных демонов одни и те же ключи действуют совершенно
|
||
по-разному (для одного демона ключ <<+-f+>> содержит указание
|
||
демонизироваться при запуске, в то время как для другого эта
|
||
опция действует прямо противоположным образом.) В отличие от
|
||
конфигурационных файлов, строка запуска не~может включать
|
||
полноценных комментариев.
|
||
\item Некоторые из настроек, задаваемых в +/etc/sysconfig+, являются
|
||
полностью избыточными. Например, во многие дистрибутивах
|
||
подобным методом указывается, установлены ли аппаратные часы
|
||
компьютера по Гринвичу, или по местному времени. Однако эта же
|
||
настройка задается третьей строкой файла +/etc/adjtime+,
|
||
поддерживаемого во всех дистрибутивах. Использование
|
||
избыточного и не~стандартизированного параметра конфигурации
|
||
только добавляет путаницу и не~несет никакой пользы.
|
||
\item Многие файлы настроек из +/etc/sysconfig+ позволяют отключать
|
||
запуск соответствующей службы. Однако эта операция уже
|
||
поддерживается штатно для всех служб, через команды
|
||
+systemctl enable+/+disable+ (или +chkconfig on+/+off+).
|
||
Добавление дополнительного уровня настройки не~приносит никакой
|
||
пользы и лишь усложняет работу администратора.
|
||
\item Что касается списка принудительно загружаемых модулей ядра~--- в
|
||
настоящее время существуют куда более удобные пути для
|
||
автоматической подгрузки модулей при загрузке системы. Например,
|
||
многие модули автоматически подгружаются +udev+'ом при
|
||
обнаружении соответствующего оборудования. Этот же принцип
|
||
распространяется на ACPI и другие высокоуровневые технологии.
|
||
Одно из немногих исключений из этого правила~--- к сожалению, в
|
||
настоящее время не~поддерживается автоматическая загрузка
|
||
модулей на основании информации о возможностях процессора,
|
||
однако это будет исправлено в ближайшем будущем\footnote{Прим.
|
||
перев.: Необходимые патчи уже приняты в ядро, и
|
||
соответствующая функция поддерживается Linux, начиная с версии
|
||
3.3.}. В случае, если нужный вам модуль ядра все же не~может
|
||
быть подгружен автоматически, все равно существует гораздо более
|
||
удобные методы указать его принудительную подгрузку~---
|
||
например, путем создания соответствующего файла в каталоге
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/modules-load.d.html}%
|
||
{/etc/modules-load.d/} (стандартный метод настройки
|
||
принудительной подгрузки модулей).
|
||
\item И наконец, хотелось бы отметить, что каталог +/etc+ определен как
|
||
место для хранения системных настроек (<<Host-specific system
|
||
configuration>>, согласно FHS). Наличие внутри него подкаталога
|
||
+sysconfig+, который тоже содержит системную конфигурацию,
|
||
является очевидно избыточным.
|
||
\end{itemize}
|
||
|
||
Что же можно предложить в качестве современной, совместимой с systemd
|
||
альтернативы настройке системы через файлы в этих каталогах? Ниже приведены
|
||
несколько рекомендаций, как лучше поступить с задаваемыми таким образом параметрами
|
||
конфигурации:
|
||
\begin{itemize}
|
||
\item Попробуйте просто отказаться от них. Если они полностью избыточны (например,
|
||
настройка аппаратных часов на Гринвич/местное время), то убрать
|
||
их будет довольно легко (если не~рассматривать вопросы
|
||
обеспечения совместимости). Если аналогичные по смыслу опции
|
||
штатно поддерживаются systemd, нет никакого смысла дублировать
|
||
их где-то еще (перечень опций, которые можно задать для любой
|
||
службы, приведен на страницах справки
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}%
|
||
{systemd.exec(5)} и
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.service.html}%
|
||
{systemd.service(5)}.)
|
||
Если же ваша настройка просто добавляет еще один уровень
|
||
отключения запуска службы~--- не~плодите лишние сущности,
|
||
откажитесь от нее.
|
||
\item Найдите для них более подходящее место. Например, в случае с
|
||
некоторыми общесистемными настройками (такими, как локаль или
|
||
часовой пояс), мы надеемся аккуратно подтолкнуть дистрибутивы в
|
||
правильном направлении (см. предыдущую главу).
|
||
\item Добавьте их поддержку в штатную систему настройки демона через
|
||
собственные файлы конфигурации. К счастью, большинство служб,
|
||
работающих в Linux, являются свободным программным обеспечением,
|
||
так что сделать это довольно просто.
|
||
\end{itemize}
|
||
|
||
Существует лишь одна причина поддерживать файлы +/etc/sysconfig+ еще некоторое
|
||
время: необходимо обеспечить совместимость при обновлении. Тем не~менее, как
|
||
минимум в новых пакетах, от таких файлов лучше отказаться.
|
||
|
||
В том случае, если требование совместимости критично, вы можете задействовать
|
||
эти конфигурационные файлы даже в том случае, если настраиваете службы через
|
||
штатные unit-файлы systemd. Если ваш файл из +sysconfig+ содержит лишь
|
||
определения переменных, можно воспользоваться опцией
|
||
+EnvironmentFile=-/etc/sysconfig/foobar+ (подробнее об этой опции см.
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}{systemd.exec(5)}),
|
||
позволяющей прочитать из файла набор переменных окружения, который будет
|
||
установлен при запуске службы. Если же для задания настроек вам необходим
|
||
полноценный язык программирования~--- ничто не~мешает им воспользоваться.
|
||
Например, вы можете создать в +/usr/lib/<your package>/+ простой скрипт,
|
||
который включает соответствующие файлы, а затем запускает бинарник демона через
|
||
+exec+. После чего достаточно просто указать этот скрипт в опции +ExecStart=+
|
||
вместо бинарника демона.
|
||
|
||
\section{Экземпляры служб}
|
||
\label{sec:instances}
|
||
|
||
Большинство служб в Linux/Unix являются одиночными (singleton): в каждый момент
|
||
времени на данном хосте работает только один экземпляр службы. В качестве
|
||
примера таких одиночных служб можно привести Syslogd, Postfix, Apache. Однако,
|
||
существуют службы, запускающие по несколько экземпляров себя на одном хосте.
|
||
Например, службы наподобие Dovecot IMAP запускают по одному экземпляру на каждый
|
||
локальный порт и/или IP-адрес. Другой пример, который можно встретить
|
||
практически во всех системах~--- \emph{getty}, небольшая служба, запускающаяся на
|
||
каждом TTY (от +tty1+ до +tty6+). На некоторых серверах, в зависимости от
|
||
сделанных администратором настроек или параметров загрузки, могут запускаться
|
||
дополнительные экземпляры getty, для подключаемых к COM-портам терминалов или
|
||
для консоли системы виртуализации. Еще один пример службы, работающей в
|
||
нескольких экземплярах (по крайней мере, в мире systemd)~--- \emph{fsck},
|
||
программа проверки файловой системы, которая запускается по одному экземпляру
|
||
на каждое блочное устройство, требующее такой проверки. И наконец, стоит
|
||
упомянуть службы с активацией в стиле inetd~--- при обращении через сокет, по
|
||
одному экземпляру на каждое соединение. В этой статье я попытаюсь рассказать,
|
||
как в systemd реализовано управление <<многоэкземплярными>> службами, и какие
|
||
выгоды системный администратор может извлечь из этой возможности.
|
||
|
||
Если вы читали предыдущие статьи из этого цикла, вы, скорее всего, уже знаете,
|
||
что службы systemd именуются по схеме \emph{foobar}+.service+, где
|
||
\emph{foobar}~--- строка, идентифицирующая службу (проще говоря, ее имя), а
|
||
+.service+~--- суффикс, присутствующий в именах всех файлов конфигурации служб.
|
||
Сами эти файлы могут находиться в каталогах +/etc/systemd/systemd+ и
|
||
+/lib/systemd/system+ (а также, возможно, и в других\footnote{Прим. перев.:
|
||
Перечень каталогов, в которых выполняется поиск общесистемных юнит-файлов,
|
||
приведен на странице руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.html}{systemd(1)}
|
||
(раздел <<System unit directories>>). Указанные выше каталоги
|
||
+/etc/systemd/systemd+ и +/lib/systemd/system+ соответствуют значениям по
|
||
умолчанию для упомянутых там переменных pkg-config +systemdsystemconfdir+ и
|
||
+systemdsystemunitdir+ соответственно. Начиная с systemd версии 198, данный
|
||
перечень, в более точной и развернутой форме, присутствует на странице
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}%
|
||
{systemd.unit(5)}.}). Для служб, работающих в нескольких экземплярах, эта схема
|
||
становится немного сложнее: \emph{foobar}+@+\emph{quux}+.service+, где
|
||
\emph{foobar}~--- имя службы, общее для всех экземпляров, а \emph{quux}~---
|
||
идентификатор конкретного экземпляра. Например, +serial-gett@ttyS2.service+~---
|
||
это служба getty для COM-порта, запущенная на +ttyS2+.
|
||
|
||
При необходимости, экземпляры служб можно легко создать динамически. Скажем, вы
|
||
можете, безо всяких дополнительных настроек, запустить новый экземпляр getty на
|
||
последовательном порту, просто выполнив +systemctl start+ для нового экземпляра:
|
||
\begin{Verbatim}
|
||
# systemctl start serial-getty@ttyUSB0.service
|
||
\end{Verbatim}
|
||
|
||
Получив такую команду, systemd сначала пытается найти файл конфигурации юнита с
|
||
именем, точно соответствующим запрошенному. Если такой файл найти не~удается
|
||
(при работе с экземплярами служб обычно так и происходит), из имени файла
|
||
удаляется идентификатор экземпляра, и полученное имя используется при поиске
|
||
\emph{шаблона} конфигурации. В нашем случае, если отсутствует файл с именем
|
||
+serial-getty@ttyUSB0.service+, используется файл-шаблон под названием
|
||
+serial-getty@.service+. Таким образом, для всех экземпляров данной службы,
|
||
используется один и тот же шаблон конфигурации. В случае с getty для COM-портов,
|
||
этот шаблон, поставляемый в комплекте с systemd
|
||
(файл +/lib/systemd/system/serial-getty@.service+) выглядит примерно так:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=Serial Getty on %I
|
||
BindTo=dev-%i.device
|
||
After=dev-%i.device systemd-user-sessions.service
|
||
|
||
[Service]
|
||
ExecStart=-/sbin/agetty -s %I 115200,38400,9600
|
||
Restart=always
|
||
RestartSec=0
|
||
\end{Verbatim}
|
||
(Заметим, что приведенная здесь версия немного сокращена, по сравнению с реально
|
||
используемой в systemd. Удалены не~относящиеся к теме нашего обсуждения
|
||
параметры конфигурации, обеспечивающие совместимость с SysV, очистку экрана и
|
||
удаление предыдущих пользователей с текущего TTY. Если вам интересно, можете
|
||
посмотреть
|
||
\href{http://cgit.freedesktop.org/systemd/systemd/plain/units/serial-getty@.service.m4}{полную
|
||
версию}.)
|
||
|
||
Этот файл похож на обычный файл конфигурации юнита, с единственным отличием: в
|
||
нем используются спецификаторы \%I и \%i. В момент загрузки юнита, systemd
|
||
заменяет эти спецификаторы на идентификатор экземпляра службы. В нашем случае,
|
||
при обращении к экземпляру +serial-getty@ttyUSB0.service+, они заменяются на
|
||
<<+ttyUSB0+>>. Результат такой замены можно проверить, например, запросив
|
||
состояние для нашей службы:
|
||
\begin{Verbatim}
|
||
$ systemctl status serial-getty@ttyUSB0.service
|
||
serial-getty@ttyUSB0.service - Getty on ttyUSB0
|
||
Loaded: loaded (/lib/systemd/system/serial-getty@.service; static)
|
||
Active: active (running) since Mon, 26 Sep 2011 04:20:44 +0200; 2s ago
|
||
Main PID: 5443 (agetty)
|
||
CGroup: name=systemd:/system/getty@.service/ttyUSB0
|
||
└ 5443 /sbin/agetty -s ttyUSB0 115200,38400,9600
|
||
\end{Verbatim}
|
||
Собственно, это и есть ключевая идея организации экземпляров служб. Как видите,
|
||
systemd предоставляет простой в использовании механизм шаблонов, позволяющих
|
||
динамически создавать экземпляры служб. Добавим несколько дополнительных
|
||
замечаний, позволяющих эффективно использовать этот механизм:
|
||
|
||
Вы можете создавать дополнительные экземпляры таких служб, просто добавляя
|
||
символьные ссылки в каталоги +*.wants/+. Например, чтобы обеспечить запуск getty
|
||
на ttyUSB0 при каждой загрузке, достаточно создать такую ссылку:
|
||
\begin{Verbatim}
|
||
# ln -s /lib/systemd/system/serial-getty@.service \
|
||
/etc/systemd/system/getty.target.wants/serial-getty@ttyUSB0.service
|
||
\end{Verbatim}
|
||
При этом файл конфигурации, на который указывает ссылка (в нашем случае
|
||
+serial-getty@.service+), будет вызван с тем именем экземпляра, которое указанно
|
||
в названии этой ссылки (в нашем случае~--- +ttyUSB0+).
|
||
|
||
Вы не~сможете обратиться к юниту-шаблону без указания идентификатора экземпляра.
|
||
В частности, команда +systemctl start serial-getty@.service+ завершится ошибкой.
|
||
|
||
Иногда возникает необходимость отказаться от использования общего шаблона
|
||
для конкретного экземпляра (т.е. конфигурация данного экземпляра настолько
|
||
сильно отличается от параметров остальных экземпляров данной службы, что
|
||
механизм шаблонов оказывается неэффективен). Специально для таких случаев, в
|
||
systemd и заложен предварительный поиск файла с именем, точно соответствующим
|
||
указанному (прежде чем использовать общий шаблон). Таким образом, вы можете
|
||
поместить файл с именем, точно соответствующим полному титулу экземпляра, в
|
||
каталог +/etc/systemd/system/+~--- и содержимое этого файла, при обращении
|
||
к выбранному экземпляру, полностью перекроет все настройки, сделанные в общем
|
||
шаблоне.
|
||
|
||
В приведенном выше файле, в некоторых местах используется спецификатор +%I+, а
|
||
в других~--- +%i+. У вас может возникнуть закономерный вопрос~--- чем они
|
||
отличаются? +%i+ всегда точно соответствует идентификатору экземпляра, в то
|
||
время, как +%I+ соответствует неэкранированной (unescaped) версии этого
|
||
идентификатора. Когда идентификатор не~содержит спецсимволов (например,
|
||
+ttyUSB0+), результат в обоих случаях одинаковый. Но имена устройств, например,
|
||
содержат слеши (<</>>), которые не~могут присутствовать в имени юнита (и в имени
|
||
файла на Unix). Поэтому, перед использованием такого имени в качестве
|
||
идентификатора устройства, оно должно быть экранировано~--- <</>> заменяются на
|
||
<<->>, а большинство других специальных символов (включая <<->>) заменяются
|
||
последовательностями вида +\xAB+, где AB~--- ASCII-код символа, записанный в
|
||
шестнадцатеричной системе счисления\footnote{Согласен, этот алгоритм дает на
|
||
выходе не~очень читабельный результат. Но этим грешат практически все алгоритмы
|
||
экранирования. В данном случае, был использован механизм экранирования из udev,
|
||
с одним изменением. В конце концов, нам нужно было выбрать что-то. Если вы
|
||
собираетесь комментировать наш алгоритм экранирования~--- пожалуйста, оставьте
|
||
свой адрес, чтобы я мог приехать к вам и раскрасить ваш сарай для велосипедов в
|
||
синий с желтыми полосами. Спасибо!}. Например, чтобы обратиться к
|
||
последовательному USB-порту по его адресу на шине, нам нужно использовать имя
|
||
наподобие +serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0+. Экранированная
|
||
версия этого имени~---
|
||
+serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0+. +%I+ будет
|
||
ссылаться на первую из этих строк, +%i+~--- на вторую. С практической точки
|
||
зрения, это означает, что спецификатор +%i+ можно использовать в том случае,
|
||
когда надо сослаться на имена других юнитов, например, чтобы описать
|
||
дополнительные зависимости (в случае с +serial-getty@.service+, этот
|
||
спецификатор используется для ссылки на юнит +dev-%i.device+, соответствующий
|
||
одноименному устройству). В то время как +%I+ удобно использовать в командной
|
||
строке (+ExecStart+) и для формирования читабельных строк описания. Рассмотрим
|
||
работу этих принципов на примере нашего юнит-файла\footnote{Прим. перев.: как
|
||
видно из нижеприведенного примера, в командной строке +systemctl+ используется
|
||
экранированное имя юнита, что создает определенные трудности даже при
|
||
наличии в оболочке <<умного>> автодополнения. Однако, начиная с systemd v186,
|
||
при работе с +systemctl+ можно указывать неэкранированные имена юнитов.}:
|
||
\begin{landscape}
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# systemctl start 'serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service'
|
||
# systemctl status 'serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service'
|
||
serial-getty@serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0.service - Serial Getty on serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0
|
||
Loaded: loaded (/lib/systemd/system/serial-getty@.service; static)
|
||
Active: active (running) since Mon, 26 Sep 2011 05:08:52 +0200; 1s ago
|
||
Main PID: 5788 (agetty)
|
||
CGroup: name=systemd:/system/serial-getty@.service/serial-by\x2dpath-pci\x2d0000:00:1d.0\x2dusb\x2d0:1.4:1.1\x2dport0
|
||
└ 5788 /sbin/agetty -s serial/by-path/pci-0000:00:1d.0-usb-0:1.4:1.1-port0 115200 38400 9600
|
||
\end{Verbatim}
|
||
\end{landscape}
|
||
Как видите, в качестве идентификатора экземпляра используется экранированная
|
||
версия, в то время как в строке описания и в командной строке +getty+
|
||
фигурирует неэкранированный вариант, как и предполагалось.
|
||
|
||
(Небольшое замечание: помимо +%i+ и +%I+, существует еще несколько
|
||
спецификаторов, и большинство из них доступно и в обычных файлах конфигурации
|
||
юнитов, а не~только в шаблонах. Подробности можно посмотреть на
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}%
|
||
{странице руководства}, содержащей полный перечень этих спецификаторов с
|
||
краткими пояснениями.)
|
||
|
||
\section{Службы с активацией в стиле inetd}
|
||
\label{sec:inetd}
|
||
|
||
В одной из предыдущих глав (гл.~\ref{sec:convert}) я рассказывал, как можно
|
||
преобразовать SysV init-скрипт в юнит-файл systemd. В этой главе мы рассмотрим,
|
||
как провести аналогичное преобразование для служб inetd.
|
||
|
||
Начнем с небольшого экскурса в историю вопроса. Уже многие годы
|
||
inetd считается одной из базовых служб Unix-систем. Работая как суперсервер, он
|
||
слушает сетевые сокеты от имени различных служб, и активирует соответствующие
|
||
службы при поступлении на эти сокеты входящих соединений. Таким образом, он
|
||
обеспечивает механизм активации по запросу (on-demand activation). Подобный
|
||
подход избавляет от необходимости держать постоянно работающими все серверные
|
||
процессы, что позволяет поддерживать множество служб даже на системах с очень
|
||
ограниченными ресурсами. В дистрибутивах Linux можно встретить
|
||
несколько различных реализаций inetd. Наиболее популярные из них~--- BSD
|
||
inetd и xinetd. Хотя inetd во многих дистрибутивах до сих пор устанавливается по
|
||
умолчанию, сейчас он уже редко используется для запуска сетевых служб~--- теперь
|
||
большинство из них запускаются автономно при загрузке системы (основной
|
||
аргумент в пользу такой схемы~--- она позволяет обеспечить более высокую
|
||
производительность служб).
|
||
|
||
Одной из ключевых возможностей systemd (а также launchd от Apple) является
|
||
сокет-активация~--- тот же самый механизм, давным-давно реализованный inetd,
|
||
однако в данном случае ключевые цели немного другие. Сокет-активация в стиле
|
||
systemd прежде всего ориентирована на локальные сокеты (+AF_UNIX+), хотя
|
||
поддерживаются также и сетевые сокеты (+AF_INET+). И более важное отличие~---
|
||
сокет-активация в systemd обеспечивает не~только экономию системных ресурсов,
|
||
но также и эффективную параллелизацию работы (так как она позволяет запускать
|
||
клиентские и серверные процессы одновременно, непосредственно после создания
|
||
сокета), упрощение процесса конфигурации (отпадает необходимость явно указывать
|
||
зависимости между службами) и увеличение надежности (перезапуск службы,
|
||
служебный или экстренный~--- в случае падения~--- не~приводит к недоступности
|
||
сокета). Тем не~менее, systemd ничуть не~хуже inetd может запускать службы в
|
||
ответ на входящие сетевые соединения.
|
||
|
||
Любая сокет-активация требует соответствующей поддержки со стороны самой
|
||
запускаемой службы. systemd предоставляет очень простой интерфейс, который может
|
||
быть использован службами для обеспечения сокет-активации. В основе этого
|
||
\href{http://0pointer.de/blog/projects/socket-activation.html}{простого и
|
||
минималистичного} механизма лежит функция
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/sd_listen_fds.html}%
|
||
{sd\_listen\_fds()}.
|
||
Однако, интерфейс, традиционно используемый в inetd, еще проще. Он позволяет
|
||
передавать запускаемой службе только один сокет, который формируется из потоков
|
||
STDIN и STDOUT запущенного процесса. Поддержка этого механизма также
|
||
присутствует в systemd~--- для обеспечения совместимости со множеством служб,
|
||
у которых сокет-активация реализована только в стиле inetd.
|
||
|
||
Прежде, чем мы перейдем к примерам, рассмотрим три различных схемы, использующих
|
||
сокет-активацию:
|
||
\begin{enumerate}
|
||
\item \textbf{Сокет-активация, ориентированная на параллелизацию,
|
||
упрощение и надежность:} сокеты создаются на ранних стадиях
|
||
загрузки, и единственный экземпляр службы тут же начинает
|
||
обслуживать все поступающие запросы. Такая схема подходит для
|
||
часто используемых системных служб~--- очевидно, что такие
|
||
службы лучше запускать как можно раньше, и по возможности
|
||
параллельно. В качестве примера можно привести D-Bus и Syslog.
|
||
\item \textbf{Сокет-активация для одиночных служб:} сокеты создаются на
|
||
ранних стадиях загрузки, и единственный экземпляр службы
|
||
запускается при получении входящих запросов. Такая схема больше
|
||
подходит для редко используемых служб, позволяя обеспечить
|
||
экономию системных ресурсов и уменьшить время загрузки, просто
|
||
отложив активацию службы до того момента, когда она
|
||
действительно понадобится. Пример~--- CUPS.
|
||
\item \textbf{Сокет-активация для служб, запускающих по экземпляру на
|
||
каждое соединение:} сокеты создаются на ранней стадии загрузки,
|
||
и при каждом входящем соединении запускается экземпляр службы,
|
||
которому передается сокет соединения (слушающий сокет при этом
|
||
остается у суперсервера, inetd или systemd). Эта схема подходит
|
||
для редко используемых служб, не~критичных по производительности
|
||
(каждый новый процесс занимает сравнительно немного ресурсов).
|
||
Пример: SSH.
|
||
\end{enumerate}
|
||
|
||
Описанные схемы отнюдь не~эквивалентны с точки зрения производительности. Первая
|
||
и вторая схема, после завершения запуска службы, обеспечивают точно такую же
|
||
производительность, как и в случае с независимой (stand-alone) службой (т.е.
|
||
службой, запущенной без использования суперсервера и сокет-активации), так как
|
||
слушающий сокет передается непосредственно процессу службы, и дальнейшая
|
||
обработка входящих соединений происходит точно так же, как и в независимой
|
||
службе. В то же время, производительность третьей из предложенных схем порою
|
||
оставляет желать лучшего: каждое входящее соединение порождает еще один процесс
|
||
службы, что, при большом количестве соединений, может привести к значительному
|
||
потреблению системных ресурсов. Впрочем, эта схема имеет и свои достоинства.
|
||
В частности, обеспечивается эффективная изоляция обработки клиентских запросов
|
||
и, кроме того, выбор такой схемы активации значительно упрощает процесс
|
||
разработки службы.
|
||
|
||
В systemd наибольшее внимание уделяется первой из этих схем, однако остальные
|
||
две тоже прекрасно поддерживаются. В свое время, я
|
||
\href{http://0pointer.de/blog/projects/socket-activation2.html}{рассказывал},
|
||
какие изменения в коде службы нужно сделать для обеспечения работы по второй
|
||
схеме, на примере сервера CUPS. Что же касается inetd, то он предназначен прежде
|
||
всего для работы по третьей схеме, хотя поддерживает и вторую (но не~первую).
|
||
Именно из-за специфики этой схемы inetd получил репутации <<медленного>> (что,
|
||
на самом деле, немного несправедливо).
|
||
|
||
Итак, изучив теоретическую сторону вопроса, перейдем к практике и рассмотрим,
|
||
как inetd-служба может быть интегрирована с механизмами сокет-активации systemd.
|
||
В качестве примера возьмем SSH, известную и широко распространенную службу. На
|
||
большинстве систем, где она используется, частота обращений к ней не~превышает
|
||
одного раза в час (как правило, она \emph{значительно} меньше этой величины).
|
||
SSH уже очень давно поддерживает сокет-активацию в стиле inetd, согласно третьей
|
||
схеме. Так как необходимость в данной службе возникает сравнительно редко, и
|
||
число одновременно работающих процессов обычно невелико, она хорошо подходит для
|
||
использования по этой схеме. Перерасход системных ресурсов должен быть
|
||
незначителен: большую часть времени такая служба фактически не~выполняется и
|
||
не~тратит ресурсы. Когда кто-либо начинает сеанс удаленной работы, она
|
||
запускается, и останавливается немедленно по завершении сеанса, освобождая
|
||
ресурсы. Что ж, посмотрим, как в systemd можно воспользоваться режимом
|
||
совместимости с inetd и обеспечить сокет-активацию SSH.
|
||
|
||
Так выглядит строка с конфигурацией службы SSH для классического inetd:
|
||
\begin{Verbatim}
|
||
ssh stream tcp nowait root /usr/sbin/sshd sshd -i
|
||
\end{Verbatim}
|
||
|
||
Аналогичный фрагмент конфигурации для xinetd:
|
||
\begin{Verbatim}
|
||
service ssh {
|
||
socket_type = stream
|
||
protocol = tcp
|
||
wait = no
|
||
user = root
|
||
server = /usr/sbin/sshd
|
||
server_args = -i
|
||
}
|
||
\end{Verbatim}
|
||
|
||
Б\'{о}льшая часть опций интуитивно понятна, кроме того, нетрудно заметить, что оба
|
||
этих текста повествуют об одном и том же. Однако, некоторые моменты не~вполне
|
||
очевидны. Например, номер прослушиваемого порта (22) не~указывается в явном
|
||
виде. Он определяется путем поиска имени службы в базе данных +/etc/services+.
|
||
Подобный подход был некогда весьма популярен в Unix, но сейчас он уже постепенно
|
||
выходит из моды, и поэтому в современных версиях xinetd поддерживается возможность
|
||
явного указания номера порта. Одна из наиболее интересных настроек названа
|
||
не~вполне очевидно~--- +nowait+ (+wait=no+). Она определяет, будет ли служба
|
||
работать по второй (+wait+) или третьей (+nowait+) схеме. И наконец, ключ +-i+
|
||
активирует inetd-режим в SSH-сервере (без этого ключа он работает в независимом
|
||
режиме).
|
||
|
||
На языке systemd эти фрагменты конфигурации превращаются в два файла. Первый из
|
||
них, +sshd.socket+, содержит информацию о прослушиваемом сокете:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=SSH Socket for Per-Connection Servers
|
||
|
||
[Socket]
|
||
ListenStream=22
|
||
Accept=yes
|
||
|
||
[Install]
|
||
WantedBy=sockets.target
|
||
\end{Verbatim}
|
||
|
||
Смысл большинства опций вполне очевиден. Сделаю лишь пару замечаний.
|
||
+Accept=yes+ соответствует режиму +nowait+. Надеюсь, предложенное мною название
|
||
более точно отражает смысл опции~--- в режиме +nowait+ суперсвервер сам вызывает
|
||
+accept()+ для слушающего сокета, в то время как в режиме +wait+ эта работа
|
||
ложится на процесс службы. Опция +WantedBy=sockets.target+ обеспечивает
|
||
активацию данного юнита в нужный момент при загрузке системы.
|
||
|
||
Второй из этих файлов~--- +sshd@.service+:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=SSH Per-Connection Server
|
||
|
||
[Service]
|
||
ExecStart=-/usr/sbin/sshd -i
|
||
StandardInput=socket
|
||
\end{Verbatim}
|
||
|
||
Большинство представленных здесь опций, как всегда, понятны интуитивно. Особое
|
||
внимание стоит обратить лишь на строчку +StandardInput=socket+, которая,
|
||
собственно, и включает для данной службы режим совместимости с inetd-активацией.
|
||
Опция +StandardInput=+ позволяет указать, куда будет подключен поток STDIN
|
||
процесса данной службы (подробности см.
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}%
|
||
{на странице руководства}). Задав для нее значение +socket+, мы обеспечиваем
|
||
подключение этого потока к сокету соединения, как того и требует механизм
|
||
inetd-активации. Заметим, что явно указывать опцию +StandardOutput=+ в данном
|
||
случае необязательно~--- она автоматически устанавливается в то же значение, что
|
||
и +StandardInput+, если явно не~указано что-то другое. Кроме того, можно
|
||
отметить наличие <<+-+>> перед именем бинарного файла sshd. Таким образом мы
|
||
указываем systemd игнорировать код выхода процесса sshd. По умолчанию, systemd
|
||
сохраняет коды выхода для всех экземпляров службы, завершившихся с ошибкой
|
||
(сбросить эту информацию можно командой +systemctl reset-failed+). SSH довольно
|
||
часто завершается с ненулевым кодом выхода, и мы разрешаем systemd
|
||
не~регистрировать подобные <<ошибки>>.
|
||
|
||
Служба +sshd@.service+ предназначена для работы в виде независимых экземпляров
|
||
(такие службы мы рассматривали в предыдущей главе~\ref{sec:instances}). Для
|
||
каждого входящего соединения systemd будет создавать свой экземпляр этой службы,
|
||
причем идентификатор экземпляра формируется на основе реквизитов соединения.
|
||
|
||
Быть может, вы спросите: почему для настройки inetd-службы в systemd требуется
|
||
два файла конфигурации, а не~один? Отвечаем: чтобы избежать излишнего
|
||
усложнения, мы обеспечиваем максимально прозрачную связь между работающими
|
||
юнитами и соответствующими им юнит-файлами. Такой подход позволяет независимо
|
||
оперировать юнитом сокета и юнитами соответствующих служб при формировании графа
|
||
зависимостей и при управлении юнитами. В частности, вы можете остановить
|
||
(удалить) сокет, не~затрагивая уже работающие экземпляры соответствующей службы,
|
||
или остановить любой из этих экземпляров, не~трогая другие экземпляры и сам
|
||
сокет.
|
||
|
||
Посмотрим, как наш пример будет работать. После того, как мы поместим оба
|
||
предложенных выше файла в каталог +/etc/systemd/system+, мы сможем включить
|
||
сокет (то есть, обеспечить его активацию при каждой нормальной загрузке) и
|
||
запустить его (то есть активировать в текущем сеансе работы):
|
||
|
||
\begin{Verbatim}
|
||
# systemctl enable sshd.socket
|
||
ln -s '/etc/systemd/system/sshd.socket' '/etc/systemd/system/sockets.target.wants/sshd.socket'
|
||
# systemctl start sshd.socket
|
||
# systemctl status sshd.socket
|
||
sshd.socket - SSH Socket for Per-Connection Servers
|
||
Loaded: loaded (/etc/systemd/system/sshd.socket; enabled)
|
||
Active: active (listening) since Mon, 26 Sep 2011 20:24:31 +0200; 14s ago
|
||
Accepted: 0; Connected: 0
|
||
CGroup: name=systemd:/system/sshd.socket
|
||
\end{Verbatim}
|
||
|
||
Итак, наш сокет уже прослушивается, но входящих соединений на него пока
|
||
не~поступало (счетчик +Accepted:+ показывает количество принятых соединений
|
||
с момента создания сокета, счетчик +Connected:+~--- количество активных
|
||
соединений на текущий момент).
|
||
|
||
Подключимся к нашему серверу с двух разных хостов, и посмотрим на список служб:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ systemctl --full | grep ssh
|
||
sshd@172.31.0.52:22-172.31.0.4:47779.service loaded active running SSH Per-Connection Server
|
||
sshd@172.31.0.52:22-172.31.0.54:52985.service loaded active running SSH Per-Connection Server
|
||
sshd.socket loaded active listening SSH Socket for Per-Connection Servers
|
||
\end{Verbatim}
|
||
|
||
Как и следовало ожидать, работают два экземпляра нашей службы, по одному на
|
||
соединение, и их в названиях указаны IP-адреса и TCP-порты источника и
|
||
получателя. (Заметим, что в случае с локальными сокетами типа +AF_UNIX+ там были
|
||
бы указаны идентификаторы процесса и пользователя, соответствующие подключенному
|
||
клиенту.) Таким образом, мы можем независимо выслеживать и убивать отдельные
|
||
экземпляры sshd (например, если нам нужно прервать конкретный удаленный сеанс):
|
||
\begin{Verbatim}
|
||
# systemctl kill sshd@172.31.0.52:22-172.31.0.4:47779.service
|
||
\end{Verbatim}
|
||
|
||
Вот, пожалуй, и все, что вам нужно знать о портировании inetd-служб в systemd и
|
||
дальнейшем их использовании.
|
||
|
||
Применительно к SSH, в большинстве случаев схема с inetd-активацией позволяет
|
||
сэкономить системные ресурсы и поэтому оказывается более эффективным решением,
|
||
чем использование обычного service-файла sshd, обеспечивающего запуск одиночной
|
||
службы без использования сокет-активации (поставляется в составе пакета и может
|
||
быть включен при необходимости). В ближайшее время я собираюсь направить
|
||
соответствующий запрос относительно нашего пакета SSH в багтрекер Fedora.
|
||
|
||
В завершение нашей дискуссии, сравним возможности xinetd и systemd, и выясним,
|
||
может ли systemd полностью заменить xinetd, или нет. Вкратце: systemd
|
||
поддерживает далеко не~все возможности xinetd и поэтому отнюдь не~является его
|
||
полноценной заменой на все случаи жизни. В частности, если вы заглянете в
|
||
\href{http://linux.die.net/man/5/xinetd.conf}{список параметров конфигурации}
|
||
xinetd, вы заметите, что далеко не~все эти опции доступны в systemd. Например, в
|
||
systemd нет и никогда не~будет встроенных служб +echo+, +time+, +daytime+,
|
||
+discard+ и т.д. Кроме того, systemd не~поддерживает TCPMUX и RPC. Впрочем,
|
||
б\'{о}льшая часть этих опций уже не~актуальна в наше время. Подавляющее
|
||
большинство inetd-служб не~используют эти опции (в частности, ни~одна из
|
||
имеющихся в Fedora xinetd-служб не~требует поддержки перечисленных опций). Стоит
|
||
отметить, что xinetd имеет некоторые интересные возможности, отсутствующие в
|
||
systemd, например, списки контроля доступа для IP-адресов (IP ACL). Однако,
|
||
большинство администраторов, скорее всего, согласятся, что настройка брандмауэра
|
||
является более эффективным решением подобных задач\footnote{Прим. перев.: Стоит
|
||
отметить, что приведенный пример является не~единственным случаем, когда
|
||
возможности брандмауэра Linux дублируются опциями xinetd. Например, количество
|
||
соединений с каждого хоста может быть ограничено критерием connlimit, а
|
||
скорость поступления входящих соединений можно контролировать сочетанием
|
||
критериев limit и conntrack (+ctstate NEW+). Критерий recent позволяет создать
|
||
аналог простейшей IDS/IPS, реализованной механизмом SENSORS в xinetd. Кроме
|
||
того, в ряде случаев возможности брандмауэра значительно превосходят
|
||
функциональность xinetd. Например, критерий hashlimit, опять-таки в сочетании с
|
||
conntrack, позволяет ограничить скорость поступления входящих соединений с
|
||
каждого хоста (не~путать с критерием connlimit, ограничивающим количество
|
||
одновременно открытых соединений). Также стоит отметить, что интегрированная в
|
||
Linux подсистема ipset гораздо лучше подходит для работы с большими списками
|
||
разрешенных/запрещенных адресов, нежели встроенные механизмы xinetd.}, а для
|
||
ценителей устаревших технологий systemd предлагает поддержку tcpwrap. С другой
|
||
стороны, systemd тоже предоставляет ряд возможностей, отсутствующих в xinetd, в
|
||
частности, индивидуальный контроль над каждым экземпляром службы (см. выше), и
|
||
внушительный
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}{набор
|
||
настроек} для контроля окружения, в котором запускаются экземпляры. Я надеюсь,
|
||
что возможностей systemd должно быть достаточно для решения большинства задач, а
|
||
в тех редких случаях, когда вам потребуются специфические опции xinetd~--- ничто
|
||
не~мешает вам запустить его в дополнение к systemd. Таким образом, уже сейчас в
|
||
большинстве случаев xinetd можно выкинуть из числа обязательных системных
|
||
компонентов. Можно сказать, что systemd не~просто возвращает функциональность
|
||
классического юниксового inetd, но еще и восстанавливает ее ключевую роль в
|
||
Linux-системах.
|
||
|
||
Теперь, вооруженные этими знаниями, вы можете портировать свои службы с inetd на
|
||
systemd. Но, конечно, будет лучше, если этим займутся разработчики из апстрима
|
||
приложения, или сопровождающие вашего дистрибутива.
|
||
|
||
\section{К вопросу о безопасности}
|
||
\label{sec:security}
|
||
|
||
Одно из важнейших достоинств Unix-систем~--- концепция разделения привилегий
|
||
между различными компонентами ОС. В частности, службы обычно работают от имени
|
||
специальных системных пользователей, имеющих ограниченные полномочия, что
|
||
позволяет уменьшить ущерб для системы в случае взлома этих служб.
|
||
|
||
Однако, такой подход предоставляет лишь самую минимальную защиту, так как
|
||
системные службы, хотя уже и не~получают полномочий администратора (root), все
|
||
равно имеют практически те же права, что и обычные пользователи. Чтобы
|
||
обеспечить более эффективную защиту, нужно поставить более жесткие ограничения,
|
||
отняв у служб некоторые привилегии, присущие обычным пользователям.
|
||
|
||
Такая возможность предоставляется системами мандатного контроля доступа (далее
|
||
MAC, от Mandatory Access Control), например, SELinux. Если вам нужно обеспечить
|
||
высокий уровень безопасности на своем сервере, то вам определенно стоит обратить
|
||
свое внимание на SELinux. Что же касается systemd, то он предоставляет
|
||
разработчикам и администраторам целый арсенал возможностей по ограничению
|
||
локальных служб, и эти механизмы работают независимо от систем MAC. Таким
|
||
образом, вне зависимости от того, смогли ли вы разобраться с SELinux~--- у вас
|
||
появляется еще несколько инструментов, позволяющих повысить уровень
|
||
безопасности.
|
||
|
||
В этой главе мы рассмотрим несколько таких опций, предоставляемых systemd, и
|
||
обсудим вопросы их практического применения. Реализация этих опций основана на
|
||
использовании ряда уникальных технологий безопасности, интегрированных в ядро
|
||
Linux уже очень давно, но при этом практически неизвестных для большинства
|
||
разработчиков. Мы постарались сделать соответствующие опции systemd максимально
|
||
простыми в использовании, чтобы заинтересовать администраторов и апстримных
|
||
разработчиков. Вот краткий перечень наиболее интересных
|
||
возможностей\footnote{Прим. перев.: В приведенном здесь списке не~упомянута
|
||
встроенная в systemd поддержка фильтров seccomp (опция +SystemCallFilter=+),
|
||
так как она была добавлена уже после написания исходной статьи, в выпуске
|
||
systemd 187.}:
|
||
\begin{itemize}
|
||
\item Изолирование служб от сети
|
||
\item Предоставление службам независимых каталогов +/tmp+
|
||
\item Ограничение доступа служб к отдельным каталогам
|
||
\item Принудительное отключение полномочий (capabilities) для служб
|
||
\item Запрет форка, ограничение на создание файлов
|
||
\item Контроль доступа служб к файлам устройств
|
||
\end{itemize}
|
||
|
||
Все эти опции описаны в man-страницах systemd, главным образом, в
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.exec.html}{systemd.exec(5)}%
|
||
\footnote{Прим. перев.: Начиная с systemd версии 206, значительная часть
|
||
обсуждаемых здесь настроек вынесена на отдельную страницу
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.cgroup.html}%
|
||
{systemd.cgroup(5)}. Начиная с systemd 208, эта страница переименована в
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html}%
|
||
{systemd.resource-control(5)}.}. Если вам потребуется дополнительная информация,
|
||
вы можете обратиться к ним.
|
||
|
||
Все эти опции доступны на системах с systemd, вне зависимости от использования
|
||
SELinux или любой другой реализации MAC.
|
||
|
||
Все эти опции не~так уж и обременительны, и поэтому их разумнее будет
|
||
использовать даже в тех случаях, когда явная необходимость в них, казалось бы,
|
||
отсутствует. Например: даже если вы полагаете, что ваша служба никогда не~пишет
|
||
в каталог +/tmp+, и поэтому использование +PrivateTmp=yes+ (см. ниже) вроде бы и
|
||
не~обязательно~--- лучше включить эту опцию, просто потому, что вы не~можете
|
||
знать наверняка, как будут вести себя используемые вами сторонние библиотеки (и
|
||
плагины для них). В частности, вы никогда не~узнаете, какие модули NSS могут
|
||
быть включены в каждой конкретной системе, и пользуются ли они каталогом +/tmp+.
|
||
|
||
Мы надеемся, что перечисленные опции окажутся полезными как для администраторов,
|
||
защищающих свои системы, так и для апстримных разработчиков, желающих сделать
|
||
свои службы безопасными <<из коробки>>. Мы настоятельно рекомендуем
|
||
разработчикам использовать такие опции по умолчанию в апстримных
|
||
service-файлах~--- это сравнительно несложно, но дает значительные преимущества
|
||
в плане безопасности.
|
||
|
||
\subsection{Изолирование служб от сети}
|
||
|
||
Простая, но мощная опция, которой вы можете воспользоваться при настройке
|
||
службы~--- +PrivateNetwork=+:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=...
|
||
PrivateNetwork=yes
|
||
...
|
||
\end{Verbatim}
|
||
Добавление этой строчки обеспечивает полную изоляцию от сети всех процессов
|
||
данной службы. Они будут видеть лишь интерфейс обратной петли (+lo+), причем
|
||
полностью изолированный от обратной петли основной системы. Чрезвычайно
|
||
эффективная защита против сетевых атак.
|
||
|
||
\begin{caveat}
|
||
Некоторым службам сеть необходима для нормальной работы. Разумеется, никто и
|
||
не~говорит о том, чтобы применять +PrivateNetwork=yes+ к сетевым службам, таким,
|
||
как Apache. Однако даже те службы, которые не~ориентированы на сетевое
|
||
взаимодействие, могут нуждаться в сети для нормального функционирования, и порой
|
||
эта потребность не~вполне очевидна. Например, если ваша система хранит
|
||
учетные записи пользователей в базе LDAP, для выполнения системных вызовов
|
||
наподобие +getpwnam()+ может потребоваться обращение к сети. Впрочем, даже в
|
||
такой ситуации, как правило, можно использовать +PrivateNetwork=yes+, за
|
||
исключением случаев, когда службе может потребоваться информация о пользователях
|
||
с UID~$\geq1000$.
|
||
\end{caveat}
|
||
|
||
Если вас интересуют технические подробности: эта возможность реализована с
|
||
использованием технологии сетевых пространств имен (network namespaces). При
|
||
задействовании данной опции, для службы создается новое пространство имен, в
|
||
котором настраивается только интерфейс обратной петли.
|
||
|
||
\subsection{Предоставление службам независимых каталогов \texttt{/tmp}}
|
||
\label{ssec:privatetmp}
|
||
|
||
Еще одна простая, но мощная опция настройки служб~--- +PrivateTmp=+:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=...
|
||
PrivateTmp=yes
|
||
...
|
||
\end{Verbatim}
|
||
При задействовании этой опции, служба получит свой собственный каталог +/tmp+,
|
||
полностью изолированный от общесистемного +/tmp+. По давно сложившейся традиции,
|
||
в Unix-системах каталог +/tmp+ является общедоступным для всех локальных служб и
|
||
пользователей. За все эти годы, он стал источником огромного количества проблем
|
||
безопасности. Чаще всего встречаются атаки с использованием символьных ссылок
|
||
(symlink attacks) и атаки на отказ в обслуживании (DoS attacks). Использование
|
||
независимой версии данного каталога для каждой службы делает подобные уязвимости
|
||
практически бесполезными.
|
||
|
||
Для релиза Fedora~17
|
||
\href{https://fedoraproject.org/wiki/Features/ServicesPrivateTmp}{утверждена}
|
||
инициатива, направленная на включение этой опции по умолчанию для большинства
|
||
системных служб.
|
||
|
||
\begin{caveat}
|
||
Некоторые службы используют +/tmp+ не~по назначению,
|
||
помещая туда сокеты IPC и другие подобные элементы, что само по себе уже
|
||
является уязвимостью (хотя бы потому, что используемые при передаче информации
|
||
файлы и каталоги должны иметь предсказуемые имена, что делает подобные службы
|
||
уязвимыми к атакам через символьные ссылки и атакам на отказ в обслуживании).
|
||
Подобные объекты лучше помещать в каталог +/run+, так как в нем присутствует
|
||
строгое разделение доступа. В качестве примера такого некорректного
|
||
использования +/tmp+ можно привести X11, размещающий там свои коммуникационные
|
||
сокеты (впрочем, в данном конкретном случае некоторые меры безопасности все же
|
||
присутствуют: сокеты размещаются в защищенном подкаталоге, который создается на
|
||
ранних стадиях загрузки). Разумеется, для служб, использующих +/tmp+ в целях
|
||
коммуникации, включение опции +PrivateTmp=yes+ недопустимо. К счастью, подобных
|
||
служб сейчас уже не~так уж и много\footnote{Прим. перев.: Начиная с systemd 209,
|
||
поддерживается опция +JoinsNamespaceOf=+ (секция +[Unit]+), позволяющая
|
||
поместить два и более юнитов в одну <<песочницу>> (пространство имен), в
|
||
результате чего такие юниты смогут взаимодействовать между собой, как через
|
||
сетевой интерфейс обратной петли, так и через файлы в каталоге +/tmp+,
|
||
при этом оставаясь изолированными от остальной системы. Подробности можно
|
||
уточнить на странице руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}{systemd.unit(5)}.}.
|
||
\end{caveat}
|
||
|
||
Эта опция использует технологию пространств имен файловых систем (filesystem
|
||
namespaces), реализованную в Linux. При включении данной опции, для службы
|
||
создается новое пространство имен, отличающееся от иерархии каталогов основной
|
||
системы только каталогом +/tmp+.
|
||
|
||
\subsection{Ограничение доступа служб к отдельным каталогам}
|
||
|
||
Используя опции +ReadOnlyDirectories=+ и +InaccessibleDirectories=+, вы можете
|
||
ограничить доступ службы к указанным каталогам только чтением, либо вообще
|
||
запретить его:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=...
|
||
InaccessibleDirectories=/home
|
||
ReadOnlyDirectories=/var
|
||
...
|
||
\end{Verbatim}
|
||
Добавление этих двух строчек в файл конфигурации приводит к тому, что служба
|
||
полностью утрачивает доступ к содержимому каталога +/home+ (она видит лишь
|
||
пустой каталог с правами доступа 000), а также не~может писать внутрь каталога
|
||
+/var+.
|
||
|
||
\begin{caveat}
|
||
К сожалению, в настоящее время опция +ReadOnlyDirectories=+ не~применяется
|
||
рекурсивно к точкам монтирования, расположенным в поддереве указанного каталога
|
||
(т.е. файловые системы, смонтированные в подкаталогах +/var+, по-прежнему
|
||
останутся доступными на запись, если, конечно, не~перечислить их все поименно).
|
||
Мы собираемся исправить это в ближайшее время.
|
||
\end{caveat}
|
||
|
||
Механизм работы этой опции тоже реализован с использованием пространств имен
|
||
файловых систем.
|
||
|
||
\subsection{Принудительное отключение полномочий (capabilities) для служб}
|
||
|
||
Еще одна чрезвычайно эффективная опция~--- +CapabilityBoundingSet=+, позволяющая
|
||
контролировать список capabilities, которые смогут получить процессы службы:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=...
|
||
CapabilityBoundingSet=CAP_CHOWN CAP_KILL
|
||
...
|
||
\end{Verbatim}
|
||
В нашем примере, служба может иметь лишь полномочия +CAP_CHOWN+ и +CAP_KILL+.
|
||
Попытки какого-либо из процессов службы получить любые другие полномочия, даже с
|
||
использованием suid-бинарников, будут пресекаться. Список возможных полномочий
|
||
приведен на странице документации
|
||
\href{http://linux.die.net/man/7/capabilities}{capabilities(7)}. К сожалению,
|
||
некоторые из них являются слишком общими (разрешают очень многое), например,
|
||
+CAP_SYS_ADMIN+, однако выборочное делегирование полномочий все же является
|
||
неплохой альтернативой запуску службы с полными административными (рутовыми)
|
||
привилегиями.
|
||
|
||
Как правило, определение списка полномочий, необходимых для работы конкретной
|
||
службы, является довольно трудоемким процессом, требующим ряда проверок. Чтобы
|
||
немного упростить эту задачу, мы добавили возможность создания <<черного
|
||
списка>> привилегий, как альтернативы описанному выше механизму <<белого
|
||
списка>>. Вместо того, чтобы указывать, какие привилегии можно оставить, вы
|
||
можете перечислить, какие из них оставлять точно нельзя. Например: привилегия
|
||
+CAP_SYS_PTRACE+, предназначенная для отладчиков, дает очень широкие полномочия
|
||
в отношении всех процессов системы. Очевидно, что такие службы, как Apache,
|
||
не~занимаются отладкой чужих процессов, и поэтому мы можем спокойно отнять у них
|
||
данную привилегию:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=...
|
||
CapabilityBoundingSet=~CAP_SYS_PTRACE
|
||
...
|
||
\end{Verbatim}
|
||
Наличие символа +~+ после знака равенства инвертирует принцип работы опции:
|
||
следующий за ним перечень привилегий рассматривается уже не~как белый, а как
|
||
черный список.
|
||
|
||
\begin{caveat}
|
||
Некоторые службы, при отсутствии определенных привилегий, могут вести себя
|
||
некорректно. Как уже говорилось выше, формирование списка необходимых полномочий
|
||
для каждой конкретной службы может оказаться довольно трудной задачей, и лучше
|
||
всего будет обратиться с соответствующим запросом к разработчикам службы.
|
||
\end{caveat}
|
||
|
||
\begin{caveat}[~2]
|
||
\href{https://forums.grsecurity.net/viewtopic.php?f=7&t=2522}{Привилегии~---
|
||
отнюдь не~лекарство от всех бед.} Чтобы они работали действительно эффективно,
|
||
возможно, придется дополнить их другими методиками защиты.
|
||
\end{caveat}
|
||
|
||
Чтобы проверить, какие именно привилегии имеют сейчас ваши процессы, вы можете
|
||
воспользоваться программой +pscap+ из комплекта +libcap-ng-utils+.
|
||
|
||
Применение опции +CapabilityBoundingSet=+ является простой, прозрачной и удобной
|
||
альтернативой введению аналогичных ограничений через модификацию исходного кода
|
||
всех системных служб.
|
||
|
||
\subsection{Запрет форка, ограничение на создание файлов}
|
||
|
||
Некоторые меры безопасности можно ввести при помощи механизма ограничения
|
||
ресурсов. Как следует из его названия, этот механизм предназначен прежде всего
|
||
для контроля использования ресурсов, а не~для контроля доступа. Однако, две его
|
||
настройки могут быть использованы для запрета определенных действий:
|
||
с помощью +RLIMIT_NPROC+ можно запретить службе форкаться (запускать
|
||
дополнительные процессы), а +RLIMIT_FSIZE+ позволяет блокировать запись в файлы
|
||
ненулевого размера:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=...
|
||
LimitNPROC=1
|
||
LimitFSIZE=0
|
||
...
|
||
\end{Verbatim}
|
||
Обратите внимание, что эти ограничения будут эффективно работать только в том
|
||
случае, если служба запускается от имени простого пользователя (не~root) и
|
||
без привилегии +CAP_SYS_RESOURCE+ (блокирование этой привилегии можно
|
||
обеспечить, например, описанной выше опцией +CapabilityBoundingSet=+). В
|
||
противном случае, ничто не~мешает процессу поменять соответствующие ограничения.
|
||
|
||
\begin{caveat}
|
||
+LimitFSIZE=+ действует очень жестко. Если процесс пытается записать файл
|
||
ненулевого размера, он немедленно получает сигнал +SIGXFSZ+, который, как
|
||
правило, прекращает работу процесса (в случае, если не~назначен обработчик
|
||
сигнала). Кроме того, стоит отметить, что эта опция не~запрещает создание файлов
|
||
нулевого размера.
|
||
\end{caveat}
|
||
|
||
Подробности об этих и других опциях ограничения ресурсов можно уточнить на
|
||
странице документации \href{http://linux.die.net/man/2/setrlimit}{setrlimit(2)}.
|
||
|
||
\subsection{Контроль доступа служб к файлам устройств}
|
||
|
||
Файлы устройств предоставляют приложениям интерфейс для доступа к ядру и
|
||
драйверам. Причем драйверы, как правило, менее тщательно тестируются и не~так
|
||
аккуратно проверяются на предмет наличия уязвимостей, по сравнению с основными
|
||
компонентами ядра. Именно поэтому драйверы часто становятся объектами атаки
|
||
злоумышленников. systemd позволяет контролировать доступ к устройствам
|
||
индивидуально для каждой службы:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=...
|
||
DeviceAllow=/dev/null rw
|
||
...
|
||
\end{Verbatim}
|
||
Приведенная конфигурация разрешит службе доступ только к файлу +/dev/null+,
|
||
запретив доступ ко всем остальным файлам устройств.
|
||
|
||
Реализация данной опции построена на использовании cgroups-контроллера
|
||
+devices+.
|
||
|
||
\subsection{Прочие настройки}
|
||
|
||
Помимо приведенных выше, простых и удобных в использовании опций, существует и
|
||
ряд других других настроек, позволяющих повысить уровень безопасности. Но
|
||
для их использования, как правило, уже требуются определенные изменения в
|
||
исходном коде службы, и поэтому такие настройки представляют интерес прежде
|
||
всего для апстримных разработчиков. В частности, сюда относится опция
|
||
+RootDirectory=+ (позволяющая запустить службу +chroot()+-окружении), а также
|
||
параметры +User=+ и +Group=+, определяющие пользователя и группу, от имени
|
||
которых работает служба. Использование этих опций значительно упрощает
|
||
написание демонов, так как работа по понижению привилегий ложится на плечи
|
||
systemd.
|
||
|
||
Возможно, у вас возникнет вопрос, почему описанные выше опции не~включены по
|
||
умолчанию. Отвечаем: чтобы не~нарушать совместимость. Многие из них несколько
|
||
отступают от традиций, принятых в Unix. Например, исторически сложилось, так что
|
||
+/tmp+ является общим для всех процессов и пользователей. Существуют программы,
|
||
использующие этот каталог для IPC, и включение по умолчанию режима изоляции для
|
||
+/tmp+ нарушит работу таких программ.
|
||
|
||
И несколько слов в заключение. Если вы занимаетесь сопровождением unit-файлов
|
||
в апстримном проекте или в дистрибутиве, пожалуйста, подумайте о том, чтобы
|
||
воспользоваться приведенными выше настройками. Если сопровождаемая вами служба
|
||
станет более защищенной в конфигурации по умолчанию (<<из коробки>>), от этого
|
||
выиграют не~только ваши пользователи, но и все мы, потому что Интернет станет
|
||
чуть более безопасным.
|
||
|
||
\section{Отчет о состоянии службы и ее журнал}
|
||
\label{sec:journal}
|
||
|
||
При работе с системами, использующими systemd, одной из наиболее важных и часто
|
||
используемых команд является +systemctl status+. Она выводит отчет о состоянии
|
||
службы (или другого юнита). В таком отчете приводится статус службы (работает
|
||
она или нет), список ее процессов и другая полезная информация.
|
||
|
||
В Fedora~17 мы ввели
|
||
\href{http://0pointer.de/blog/projects/the-journal.html}{Journal}, новую
|
||
реализацию системного журнала, обеспечивающую структурированное, индексированное
|
||
и надежное журналирование, при сохранении совместимости с классическими
|
||
реализациями syslog. Собственно, мы начали работу над~Journal только потому, что
|
||
хотели добавить одну, казалось бы, простую, возможность: включить в вывод
|
||
+systemctl status+ последние 10 сообщений, поступивших от службы в системный
|
||
журнал. Но на практике оказалось, что в рамках классических механизмов syslog,
|
||
реализация этой возможности чрезвычайно сложна и малоэффективна. С другой
|
||
стороны, сообщения службы в системный журнал несут очень важную информацию о ее
|
||
состоянии, и такая возможность действительно была бы очень полезной, особенно
|
||
при диагностике нештатных ситуаций.
|
||
|
||
Итак, мы интегрировали Journal в systemd, и научили +systemctl+ работать с ним.
|
||
Результат выглядит примерно так:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ systemctl status avahi-daemon.service
|
||
avahi-daemon.service - Avahi mDNS/DNS-SD Stack
|
||
Loaded: loaded (/usr/lib/systemd/system/avahi-daemon.service; enabled)
|
||
Active: active (running) since Fri, 18 May 2012 12:27:37 +0200; 14s ago
|
||
Main PID: 8216 (avahi-daemon)
|
||
Status: "avahi-daemon 0.6.30 starting up."
|
||
CGroup: name=systemd:/system/avahi-daemon.service
|
||
├ 8216 avahi-daemon: running [omega.local]
|
||
└ 8217 avahi-daemon: chroot helper
|
||
|
||
May 18 12:27:37 omega avahi-daemon[8216]: Joining mDNS multicast group on interface eth1.IPv4 with address 172.31.0.52.
|
||
May 18 12:27:37 omega avahi-daemon[8216]: New relevant interface eth1.IPv4 for mDNS.
|
||
May 18 12:27:37 omega avahi-daemon[8216]: Network interface enumeration completed.
|
||
May 18 12:27:37 omega avahi-daemon[8216]: Registering new address record for 192.168.122.1 on virbr0.IPv4.
|
||
May 18 12:27:37 omega avahi-daemon[8216]: Registering new address record for fd00::e269:95ff:fe87:e282 on eth1.*.
|
||
May 18 12:27:37 omega avahi-daemon[8216]: Registering new address record for 172.31.0.52 on eth1.IPv4.
|
||
May 18 12:27:37 omega avahi-daemon[8216]: Registering HINFO record with values 'X86_64'/'LINUX'.
|
||
May 18 12:27:38 omega avahi-daemon[8216]: Server startup complete. Host name is omega.local. Local service cookie is 3555095952.
|
||
May 18 12:27:38 omega avahi-daemon[8216]: Service "omega" (/services/ssh.service) successfully established.
|
||
May 18 12:27:38 omega avahi-daemon[8216]: Service "omega" (/services/sftp-ssh.service) successfully established.
|
||
\end{Verbatim}
|
||
Очевидно, это отчет о состоянии всеми любимого демона mDNS/DNS-SD, причем после
|
||
списка процессов, как мы и обещали, приведены сообщения этого демона в системный
|
||
журнал. Миссия выполнена!
|
||
|
||
Команда +systemctl status+ поддерживает ряд опций, позволяющих настроить вывод
|
||
информации в соответствии с вашими пожеланиями. Отметим две наиболее интересные
|
||
из них: ключ +-f+ позволяет в реальном времени отслеживать обновление сведений
|
||
(по аналогии с +tail -f+), а ключ +-n+ задает количество выводимых журнальных
|
||
записей (как нетрудно догадаться, по аналогии с +tail -n+).
|
||
|
||
Журнальные записи собираются из трех различных источников. Во-первых, это
|
||
сообщения службы в системный журнал, отправленные через функцию libc
|
||
+syslog()+. Во-вторых, это сообщения, отправленные через штатный API системы
|
||
Journal. И наконец, это весь текст, выводимый демоном в STDOUT и STDERR. Проще
|
||
говоря, все, что демон считает нужным сказать администратору, собирается,
|
||
упорядочивается и отображается в одинаковом формате.
|
||
|
||
Добавленная нами возможность, при всей своей простоте, может оказаться
|
||
чрезвычайно полезной практически любому администратору. По-хорошему, ее стоило
|
||
реализовать еще 15 лет назад.
|
||
|
||
\section{Самодокументированный процесс загрузки}
|
||
|
||
Нам часто приходится слышать жалобы, что процесс загрузки при использовании
|
||
systemd очень сложен для понимания. Не~могу с этим согласиться. Более того, я
|
||
берусь даже утверждать обратное: по сравнению с тем, что мы имели раньше (когда
|
||
для того, чтобы разобраться в загрузке, нужно было иметь хорошие навыки
|
||
программирования на языке Bourne Shell\footnote{Чья привлекательность очень
|
||
коварна и обманчива.}), процесс загрузки стал более простым и прозрачным. Но
|
||
определенная доля истины в этом критическом замечании все же есть: даже опытному
|
||
Unix-администратору при переходе на systemd нужно изучить некоторые новые для
|
||
себя вещи. Мы, разработчики systemd, обязаны максимально упростить такое
|
||
обучение. Решая поставленную задачу, мы подготовили для вас кое-какие приятные
|
||
сюрпризы, и предоставили хорошую документацию даже там, где это казалось
|
||
невозможным.
|
||
|
||
Уже сейчас systemd располагает довольно обширной документацией, включая
|
||
\href{http://www.freedesktop.org/software/systemd/man/}{страницы руководства}
|
||
(на данный момент, их около сотни),
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd}{wiki-сайт проекта}, а
|
||
также ряд статей в моем блоге. Тем не~менее, большое количество документации еще
|
||
не~гарантирует простоты понимания. Огромные груды руководств выглядят пугающе, и
|
||
неподготовленный читатель не~может разобраться, с какого места ему начинать
|
||
чтение, если его интересует просто общая концепция.
|
||
|
||
Чтобы решить данную проблему, мы добавили в systemd небольшую, но очень изящную
|
||
возможность: самодокументированный\footnote{Прим. перев.: В оригинале
|
||
использован термин <<self-explanatory>>, который также можно перевести как
|
||
<<само-объясняющий>>, <<самоочевидный>>.} процесс загрузки. Что мы под этим
|
||
подразумеваем? То, что для каждого элемента процесса загрузки теперь имеется
|
||
документация, прозрачно привязанная к этому элементу, так что ее поиск
|
||
не~представляет трудности.
|
||
|
||
Иными словами, все штатные юниты systemd (которые, собственно, и формируют
|
||
процесс загрузки, вызывая соответствующие программы) включают ссылки на страницы
|
||
документации, описывающие назначение юнита/программы, используемые ими
|
||
конфигурационные файлы и т.д. Если пользователь хочет узнать, для чего
|
||
предназначен какой-либо юнит, какова его роль в процессе загрузки и как его
|
||
можно настроить, может получить ссылки на соответствующую документацию, просто
|
||
воспользовавшись давно известной командой +systemctl status+. Возьмем для
|
||
примера +systemd-logind.service+:
|
||
\begin{Verbatim}[fontsize=\small,commandchars=\\\{\},%
|
||
% В окружении Verbatim ссылки, содержащие знак дефиса/минуса "-",
|
||
% повреждаются. Обходим это при помощи черной магии в стиле sysvinit:
|
||
codes={\catcode`+=\active},defineactive=\def+{-}]
|
||
$ systemctl status systemd-logind.service
|
||
systemd-logind.service - Login Service
|
||
Loaded: loaded (/usr/lib/systemd/system/systemd-logind.service; static)
|
||
Active: active (running) since Mon, 25 Jun 2012 22:39:24 +0200; 1 day and 18h ago
|
||
Docs: \href{http://www.freedesktop.org/software/systemd/man/systemd+logind.service.html}{man:systemd-logind.service(7)}
|
||
\href{http://www.freedesktop.org/software/systemd/man/logind.conf.html}{man:logind.conf(5)}
|
||
\url{http://www.freedesktop.org/wiki/Software/systemd/multiseat}
|
||
Main PID: 562 (systemd-logind)
|
||
CGroup: name=systemd:/system/systemd-logind.service
|
||
└ 562 /usr/lib/systemd/systemd-logind
|
||
|
||
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event2 (Power Button)
|
||
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event6 (Video Bus)
|
||
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event0 (Lid Switch)
|
||
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event1 (Sleep Button)
|
||
Jun 25 22:39:24 epsilon systemd-logind[562]: Watching system buttons on /dev/input/event7 (ThinkPad Extra Buttons)
|
||
Jun 25 22:39:25 epsilon systemd-logind[562]: New session 1 of user gdm.
|
||
Jun 25 22:39:25 epsilon systemd-logind[562]: Linked /tmp/.X11-unix/X0 to /run/user/42/X11-display.
|
||
Jun 25 22:39:32 epsilon systemd-logind[562]: New session 2 of user lennart.
|
||
Jun 25 22:39:32 epsilon systemd-logind[562]: Linked /tmp/.X11-unix/X0 to /run/user/500/X11-display.
|
||
Jun 25 22:39:54 epsilon systemd-logind[562]: Removed session 1.
|
||
\end{Verbatim}
|
||
|
||
На первый взгляд, в выводе этой команды почти ничего не~изменилось. Но если вы
|
||
вглядитесь повнимательнее, то заметите новое поле +Docs+, в котором приведены
|
||
ссылки на документацию по данной службе. В нашем случае случае это два URI,
|
||
ссылающихся на man-страницы, и один URL, указывающий на веб-страницу.
|
||
man-страницы описывают соответственно предназначение службы и ее настройки, а
|
||
веб-страница~--- базовые концепции, которые реализуются этой службой.
|
||
|
||
Тем, кто использует современные графические эмуляторы терминала, чтобы открыть
|
||
соответствующую страницу документации, достаточно щелкнуть мышью по
|
||
URI\footnote{Для нормальной работы данной функции, в эмуляторе терминала должен
|
||
быть исправлена \href{https://bugzilla.gnome.org/show_bug.cgi?id=676452}{эта
|
||
ошибка}, а в программе просмотра страниц помощи~---
|
||
\href{https://bugzilla.gnome.org/show_bug.cgi?id=676482}{вот эта}.}. Иными
|
||
словами, доступ к документации компонентов загрузки становится предельно
|
||
простым: достаточно вызвать +systemctl status+, после чего щелкнуть по
|
||
полученным ссылкам.
|
||
|
||
Если же вы используете не~графический эмулятор терминала (где можно просто
|
||
щелкнуть по URI), а настоящий терминал, наличие URI где-то в середине вывода
|
||
+systemctl status+ вряд ли покажется вам удобным. Чтобы упростить обращение к
|
||
соответствующим страницам документации без использования мыши и
|
||
копирования/вставки, мы ввели новую команду
|
||
\begin{Verbatim}
|
||
systemctl help systemd-logind.service
|
||
\end{Verbatim}
|
||
которая просто откроет в вашем терминале соответствующие man-страницы.
|
||
|
||
URI, ссылающиеся на документацию, формируются в соответствии со страницей
|
||
руководства
|
||
\href{https://www.kernel.org/doc/man-pages/online/pages/man7/url.7.html}{uri(7)}.
|
||
Поддерживаются схемы +http+/+https+ (URL для веб-страниц) и +man+/+info+
|
||
(локальные страницы руководства).
|
||
|
||
Разумеется, добавленная нами возможность не~обеспечивает полной очевидности
|
||
абсолютно всего, хотя бы потому, что пользователь должен как минимум знать про
|
||
команду +systemctl status+ (а также вообще про программу +systemctl+ и про то,
|
||
что такое юнит). Но при наличии этих базовых знаний, пользователю уже
|
||
не~составит труда получить информацию по интересующим его вопросам.
|
||
|
||
Мы надеемся, что добавленный нами механизм для связи работающего кода и
|
||
соответствующей документации станет значительным шагом к полностью прозрачному и
|
||
предельно простому для понимания процессу загрузки.
|
||
|
||
Описанная функциональность частично присутствует в Fedora~17, а в полном
|
||
объеме она будет представлена в Fedora~18.
|
||
|
||
В завершение, стоит отметить, что использованная нами идея не~является такой уж
|
||
новой: в SMF, системе инициализации Solaris, уже используется практика
|
||
указания ссылок на документацию в описании службы. Однако, в Linux такой
|
||
подход является принципиально новым, и systemd сейчас является наиболее
|
||
документированной и прозрачной системой загрузки для данной платформы.
|
||
|
||
Если вы занимаетесь разработкой или сопровождением пакетов и создаете файл
|
||
конфигурации юнита, пожалуйста, включите в него ссылки на документацию. Это
|
||
очень просто: достаточно добавить в секции +[Unit]+ поле +Documentation=+ и
|
||
перечислить в нем соответствующие URI. Подробнее об этом поле см. на странице
|
||
руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}{systemd.unit(5)}.
|
||
Чем больше будет документированных юнитов, тем проще станет работа системного
|
||
администратора. (В частности, я уже направил в FPC
|
||
\href{https://fedorahosted.org/fpc/ticket/192}{предложение} внести
|
||
соответствующие пожелания в нормативные документы, регулирующие подготовку
|
||
пакетов для Fedora.)
|
||
|
||
Да, кстати: если вас интересует общий обзор процесса загрузки systemd, то вам
|
||
стоит обратить внимание на
|
||
\href{http://www.freedesktop.org/software/systemd/man/bootup.html}{новую
|
||
страницу руководства}, где представлена псевдографическая потоковая диаграмма,
|
||
описывающая процесс загрузки и роль ключевых юнитов.
|
||
|
||
\section{Сторожевые таймеры}
|
||
|
||
Разрабатывая systemd, мы ориентируемся на три основных области его применения:
|
||
мобильные/встраиваемые устройства, настольные системы и промышленные серверы.
|
||
Мобильные и встраиваемые системы, как правило, располагают очень скромными
|
||
ресурсами и вынуждены очень экономно расходовать энергию. Настольные системы
|
||
уже не~так сильно ограничены по мощности, хотя все же проигрывают в
|
||
этом плане промышленным серверам. Но, как ни~странно, существуют
|
||
возможности, востребованные в обоих крайних случаях (встраиваемые системы и
|
||
серверы), но не~очень актуальные в промежуточной ситуации (десктопы). В
|
||
частности, к ним относится поддержка
|
||
\href{http://ru.wikipedia.org/wiki/Watchdog}{сторожевых таймеров} (watchdogs),
|
||
как аппаратных, так и программных.
|
||
|
||
Во встраиваемых системах часто используются аппаратные сторожевые таймеры,
|
||
выполняющие сброс системы, когда программа перестает отвечать (точнее, перестает
|
||
периодически посылать таймеру сигнал <<все в порядке>>). Таким образом,
|
||
обеспечивается повышение надежности системы: что бы ни~случилось, система
|
||
обязательно попытается привести себя в рабочее состояние. На десктопах такая
|
||
функциональность практически не~востребована\footnote{Тем не~менее, сейчас
|
||
аппаратные сторожевые таймеры все чаще появляются и в настольных системах, так
|
||
как стоят они относительно дешево и доступны практически во всех современных
|
||
чипсетах.}. С другой стороны, она весьма актуальна для высокодоступных серверных
|
||
систем.
|
||
|
||
Начиная с версии 183, systemd полностью поддерживает аппаратные сторожевые
|
||
таймеры (доступные через интерфейс +/dev/watchdog+), а также обеспечивает
|
||
программный сторожевой контроль системных служб. В целом эта схема (если она
|
||
задействована) работает так. systemd периодически посылает сигналы аппаратному
|
||
таймеру. В том случае, если systemd или ядро зависают, аппаратный таймер,
|
||
не~получив очередного сигнала, автоматически перезапустит систему. Таким
|
||
образом, ядро и systemd защищены от бесконечного зависания~--- на аппаратном
|
||
уровне. В то же время, сам systemd предоставляет интерфейс, реализующий логику
|
||
программных сторожевых таймеров для отдельных служб. Таким образом можно
|
||
обеспечить, например, принудительный перезапуск службы в случае ее зависания.
|
||
Для каждой службы можно независимо настроить частоту опроса и задать
|
||
соответствующее действие. Соединив оба звена (аппаратный сторожевой таймер,
|
||
контролирующий ядро и systemd, и программные сторожевые таймеры, посредством
|
||
которых systemd контролирует отдельные службы), мы получаем надежную схему
|
||
надзора за всеми системными компонентами.
|
||
|
||
Чтобы задействовать аппаратный таймер, достаточно задать ненулевое значение
|
||
параметра +RuntimeWatchdogSec=+ в файле +/etc/systemd/system.conf+. По умолчанию
|
||
этот параметр равен нулю (т.е. аппаратный таймер не~задействован). Установив его
|
||
равным, например, <<+20s+>>, мы включим аппаратный сторожевой таймер. Если в
|
||
течение 20 секунд таймер не~получит очередного сигнала <<все в порядке>>,
|
||
система автоматически перезагрузится. Отметим, что systemd отправляет такие
|
||
сигналы с периодом, равным половине заданного интервала~--- в нашем случае,
|
||
через каждые 10 секунд. Собственно, это все. Просто задав один параметр,
|
||
вы обеспечите аппаратный контроль работоспособности systemd и
|
||
ядра\footnote{Небольшой совет: если вы занимаетесь отладкой базовых системных
|
||
компонентов~--- не~забудьте отключить сторожевой таймер. Иначе ваша система
|
||
может неожиданно перезагрузиться, когда отладчик остановит процесс init и тот
|
||
не~может вовремя отправить сообщение таймеру.}.
|
||
|
||
Заметим, что с аппаратным сторожевым таймером (+/dev/watchdog+)
|
||
должна работать только одна программа. Этой программой может быть либо systemd,
|
||
либо отдельный демон сторожевого таймера (например,
|
||
\href{http://linux.die.net/man/8/watchdog}{watchdogd})~--- выбор остается за
|
||
вами.
|
||
|
||
Стоит упомянуть здесь еще одну опцию из файла +/etc/systemd/system.conf+~---
|
||
+ShutdownWatchdogSec=+. Она позволяет настроить аппаратный сторожевой таймер для
|
||
контроля процесса перезагрузки. По умолчанию она равна <<+10min+>>. Если в
|
||
процессе остановки системы перед перезагрузкой она зависнет, аппаратный таймер
|
||
принудительно перезагрузит ее по истечении данного интервала.
|
||
|
||
Это все, что я хотел сказать об аппаратных таймерах. Двух вышеописанных опций
|
||
должно быть вполне достаточно для полноценного использования их возможностей.
|
||
А сейчас мы рассмотрим логику программных сторожевых таймеров, обеспечивающих
|
||
контроль работоспособности отдельных служб.
|
||
|
||
Прежде всего отметим, что для полноценной поддержки сторожевого контроля,
|
||
программа должна содержать специальный код, периодически отправляющий таймеру
|
||
сигналы <<все в порядке>>. Добавить поддержку такой функциональности довольно
|
||
просто. Для начала, демон должен проверить переменную окружения +WATCHDOG_USEC=+.
|
||
Если она определена, то ее значение задает контрольный интервал в микросекундах,
|
||
сформатированный в виде текстовой (ASCII) строки. В этом случае демон должен
|
||
регулярно выполнять вызов
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/sd_notify.html}{sd\_notify}+("WATCHDOG=1")+
|
||
с периодом, равным половине указанного интервала. Таким образом, поддержка
|
||
программного сторожевого контроля со стороны демона сводится к проверке значения
|
||
переменной окружения, и выполнении определенных действий в соответствии с этим
|
||
значением.
|
||
|
||
Если интересующая вас служба обеспечивает поддержку такой
|
||
функциональности, вы можете включить для нее сторожевой контроль, задав опцию
|
||
+WatchdogSec=+ в ее юнит-файле. Эта опция задает период работы таймера
|
||
(подробнее см.
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.service.html}{systemd.service(5)}).
|
||
Если вы зададите ее, то systemd при запуске службы передаст ей соответствующее
|
||
значение +WATCHDOG_USEC=+ и, если служба перестанет своевременно отправлять
|
||
сигналы <<все в порядке>>, присвоит ей статус сбойной (failure state).
|
||
|
||
Очевидно, что одного только присвоения статуса недостаточно для обеспечения
|
||
надежной работы системы. Поэтому нам также пригодятся настройки, определяющие,
|
||
нужно ли перезапускать зависшую службу, количество попыток перезапуска, и
|
||
дальнейшие действия, если она все равно продолжает сбоить. Чтобы включить
|
||
автоматический перезапуск службы при сбое, нужно задать опцию
|
||
+Restart=on-failure+ в ее юнит-файле. Чтобы настроить, сколько раз systemd будет
|
||
пытаться перезапустить службу, воспользуйтесь настройками +StartLimitBurst=+
|
||
+StartLimitInterval=+ (первая из них определяет предельное количество попыток,
|
||
вторая~--- интервал времени, в течение которого они подсчитываются). В том
|
||
случае, если достигнут предел количества попыток за указанное время, выполняется
|
||
действие, заданное параметром +StartLimitAction=+. По умолчанию он установлен в
|
||
+none+ (никаких дополнительных действий не~будет, службу просто оставят в покое со
|
||
статусом сбойной). В качестве альтернативы можно указать одно из трех
|
||
специальных действий: +reboot+, +reboot-force+ и +reboot-immediate+.
|
||
+reboot+ соответствует обычной перезагрузке системы, с выполнением всех
|
||
сопутствующих процедур (корректное завершение всех служб, отмонтирование
|
||
файловых систем и т.д.). +reboot-force+ действует более грубо~--- даже
|
||
не~пытаясь корректно остановить службы, оно просто убивает все их процессы,
|
||
отмонтирует файловые системы и выполняет принудительную перезагрузку (в
|
||
результате, перезагрузка происходит быстрее, чем обычно, но файловые системы
|
||
остаются неповрежденными). И наконец, +reboot-immediate+ даже не~пытается отдать
|
||
дань вежливости (убить процессы и отмонтировать файловые системы)~--- оно
|
||
немедленно выполняет жесткую перезагрузку системы (это поведение практически
|
||
аналогично срабатыванию аппаратного сторожевого таймера). Все перечисленный
|
||
настройки подробно описаны на странице руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.service.html}{systemd.service(5)})%
|
||
\footnote{Прим. перев.: Автор упускает из виду одну полезную опцию,
|
||
непосредственно относящуюся к обсуждаемому вопросу~--- +OnFailure=+, задающую
|
||
юнит, который будет активирован в случае сбоя исходного юнита. Таким образом
|
||
можно обеспечить, например, запуск скриптов, отправляющих администратору
|
||
уведомление о сбое в сочетании с дополнительной информацией (для сбора которой
|
||
целесообразно задействовать команды +systemctl status+ и +journalctl+). Кроме
|
||
того, комбинирование данной настройки с опцией +OnFailureIsolate=+ позволяет
|
||
при сбое юнита перевести систему в определенное состояние (например, остановить
|
||
некоторые службы, перейти в режим восстановления, выполнить перезагрузку).}.
|
||
|
||
Таким образом, мы мы получаем гибкий механизм для настройки сторожевого контроля
|
||
служб, их перезапуска при зависании, и реагирования в ситуации, когда перезапуск
|
||
не~помогает.
|
||
|
||
Рассмотрим применение этих настроек на простом примере:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=My Little Daemon
|
||
Documentation=man:mylittled(8)
|
||
|
||
[Service]
|
||
ExecStart=/usr/bin/mylittled
|
||
WatchdogSec=30s
|
||
Restart=on-failure
|
||
StartLimitInterval=5min
|
||
StartLimitBurst=4
|
||
StartLimitAction=reboot-force
|
||
\end{Verbatim}
|
||
Данная служба будет автоматически перезапущена, если она не~передаст системному
|
||
менеджеру очередной сигнал <<все в порядке>> в течение 30 секунд после
|
||
предыдущего (кроме того, перезапуск будет произведен и в случае любого другого
|
||
сбоя, например, завершения основного процесса службы с ненулевым кодом выхода).
|
||
Если потребуется более 4 перезапусков службы за 5 минут~--- будет предпринято
|
||
специальное действие, в данном случае, быстрая перезагрузка системы с корректным
|
||
отмонтированием файловых систем.
|
||
|
||
Это все, что я хотел рассказать о сторожевых таймерах в systemd. Мы надеемся,
|
||
что поддержки аппаратного отслеживания работоспособности процесса init, в
|
||
сочетании с контролем функционирования отдельных служб, должно быть достаточно
|
||
для решения большинства задач, связанных со сторожевыми таймерами.
|
||
Разрабатываете ли вы встраиваемую либо мобильную систему, или работаете с
|
||
высокодоступными серверами~--- вам определенно стоит попробовать наши решения!
|
||
|
||
(И да, если у вас возникнет вопрос, почему с аппаратным таймером должен работать
|
||
именно init, и что мешает вынести эту логику в отдельный демон~--- пожалуйста,
|
||
перечитайте эту главу еще раз, и попытайтесь увидеть всю цепочку сторожевого
|
||
контроля как единое целое: аппаратный таймер надзирает за работой systemd, а
|
||
тот, в свою очередь, следит за отдельными службами. Кроме того, мы полагаем, что
|
||
отсутствие своевременного ответа от службы нужно рассматривать и обрабатывать
|
||
так же, как и любые другие ее сбои. И наконец, взаимодействие с
|
||
+/dev/watchdog+~--- одна из самых тривиальных задач в работе ОС (обычно, она
|
||
сводится к простому вызову +ioctl()+), и для ее решения достаточно нескольких
|
||
строк кода. С другой стороны, вынос данной функции в отдельный демон потребует
|
||
организации сложного межпроцессного взаимодействия между этим демоном и
|
||
процессом init~--- очевидно, реализация такой схемы предоставит значительно
|
||
б\'{о}льший простор для ошибок, не~говоря уже о повышенном потреблении
|
||
ресурсов.)
|
||
|
||
Отметим, что встроенная в systemd поддержка аппаратного сторожевого таймера
|
||
в~конфигурации по умолчанию отключена, и поэтому никак не~мешает работе с этим
|
||
таймером из других программ. Вы без лишних проблем можете выбрать внешний
|
||
сторожевой демон, если он лучше подходит для вашей задачи.
|
||
|
||
Да, и еще: если у вас возникнет вопрос, имеется ли в вашей системе аппаратный
|
||
таймер~--- скорее всего да, если ваш компьютер не~очень старый. Чтобы получить
|
||
точный ответ, вы можете воспользоваться утилитой
|
||
\hreftt{http://karelzak.blogspot.de/2012/05/eject1-sulogin1-wdctl1.html}{wdctl},
|
||
включенной в последний релиз util-linux\footnote{Прим. перев.: Утилита +wdctl+
|
||
присутствует в util-linux, начиная с версии~2.22, которая, на~момент написания
|
||
этих строк, еще не~вышла.}. Эта программа выведет всю необходимую
|
||
информацию о вашем аппаратном сторожевом таймере.
|
||
|
||
И в завершение, я хотел бы поблагодарить ребят из
|
||
\href{http://www.pengutronix.de/}{Pengutronix}, которым приндалежит основная
|
||
заслуга реализации сторожевого контроля в systemd.
|
||
|
||
\section{Запуск getty на последовательных (и не~только) консолях}
|
||
\label{sec:getty}
|
||
|
||
\emph{Если вам лень читать всю статью целиком: для запуска getty на
|
||
последовательной консоли\footnote{Прим. перев.: Здесь и в дальнейшем автор
|
||
использует термин <<serial console>>. Точный перевод этого выражения на русский
|
||
язык звучит как <<консоль, подключаемая к последовательному порту>>. Однако,
|
||
для краткости изложения, при переводе используется не~вполне корректный, но
|
||
хорошо знакомый администраторам жаргонизм <<последовательная консоль>>. Также
|
||
отметим, что в данном документе термины <<консоль>> и <<терминал>> используются
|
||
как синонимы.} достаточно указать в загрузчике параметр ядра
|
||
\verb+console=ttyS0+, и systemd автоматически запустит getty на этом терминале.}
|
||
|
||
Физический последовательный порт
|
||
\href{https://ru.wikipedia.org/wiki/RS-232}{RS-232}, хотя уже и стал редкостью
|
||
на современных настольных компьютерах, тем не~менее, продолжает играть важную
|
||
роль как на серверах, так и во встраиваемых системах. Он предоставляет простой и
|
||
надежный доступ к управлению системой, даже когда сеть упала, а основной
|
||
интерфейс управления завис. Кроме того, эмуляция последовательной консоли часто
|
||
используется при управлении виртуальными машинами.
|
||
|
||
Разумеется, в Linux уже давно реализована поддержка работы с последовательными
|
||
консолями но, при разработке
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd}{systemd}, мы постарались
|
||
сделать работу с ними еще проще. В этой статье я хочу рассказать о том, как в
|
||
systemd реализован запуск \href{https://ru.wikipedia.org/wiki/Getty}{getty} на
|
||
терминалах различных типов.
|
||
|
||
Для начала, хотелось бы отметить один важный момент: в большинстве случаев, чтобы
|
||
получить приглашение к логину на последовательном терминале, вам не~нужно
|
||
совершать никаких дополнительных действий: systemd сам проверит настройки ядра,
|
||
определит их них используемую ядром консоль, и автоматически запустит на ней
|
||
getty. Таким образом, вам достаточно лишь правильно указать ядру соответствующую
|
||
консоль (например, добавив к параметрам ядра в загрузчик +console=ttyS0+).
|
||
|
||
Тем не~менее, для общего образования все же стоит рассмотреть некоторые
|
||
тонкости запуска getty в systemd. Эта задача решается двумя шаблонами
|
||
юнитов\footnote{Прим. перев.: Принципы работы с шаблонами и экземплярами служб
|
||
изложены в главе~\ref{sec:instances}. Для лучшего понимания нижеприведенного
|
||
материала, рекомендуется перечитать эту главу, если вы успели ее подзабыть.}:
|
||
\begin{itemize}
|
||
\item +getty@.service+ отвечает за
|
||
\href{https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%80%D1%82%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%BA%D0%BE%D0%BD%D1%81%D0%BE%D0%BB%D1%8C}%
|
||
{виртуальные консоли} (virtual terminals, VT, известные в
|
||
системе под именами +/dev/tty1+, +/dev/tty2+ и т.д.)~--- те,
|
||
которые вы можете увидеть безо всякого дополнительного
|
||
оборудования, просто переключившись на них из графического
|
||
сеанса.
|
||
|
||
\item +serial-getty@.service+ обеспечивает поддержку всех прочих
|
||
разновидностей терминалов, в том числе, подключаемых к
|
||
последовательным портам (+/dev/ttyS0+ и т.д.). Этот шаблон имеет
|
||
ряд отличий от +getty@.service+, в частности, переменная \verb+$TERM+
|
||
в нем устанавливается в значение +vt102+ (должно хорошо работать
|
||
на большинстве физических терминалов), а не~+linux+ (которое
|
||
работает правильно только на виртуальных консолях), а также
|
||
пропущены настройки, касающиеся очистки буфера прокрутки (и
|
||
поэтому имеющие смысл только на VT).
|
||
\end{itemize}
|
||
|
||
\subsection{Виртуальные консоли}
|
||
|
||
Рассмотрим механизм запуска +getty@.service+, обеспечивающий появление
|
||
приглашений логина на виртуальных консолях (последовательны терминалы пока
|
||
оставим в покое). По устоявшейся традиции, init-системы Linux обычно
|
||
настраивались на запуск фиксированного числа экземпляров getty, как правило,
|
||
шести (на первых шести виртуальных консолях, с +tty1+ по +tty6+).
|
||
|
||
В systemd мы сделали этот процесс более динамичным: чтобы добиться большей
|
||
скорости и эффективности, мы запускаем дополнительные экземпляры getty только
|
||
при необходимости. Например, +getty@tty2.service+ стартует только после того,
|
||
как вы переключитесь на вторую виртуальную консоль. Отказавшись от
|
||
обязательного запуска нескольких экземпляров getty, мы сэкономили немного
|
||
системных ресурсов, а также сделали загрузку системы чуть-чуть быстрее. При
|
||
этом, с точки зрения пользователя, все осталось так же просто: как только он
|
||
переключается на виртуальную консоль, на ней запускается getty, которая выводит
|
||
приглашение к логину. Пользователь может и не~подозревать о том, что до момента
|
||
переключения приглашения не~было. Тем не~менее, если он войдет в систему и
|
||
выполнит команду +ps+, он увидит, что getty запущены только на тех консолях, на
|
||
которых он уже побывал.
|
||
|
||
По умолчанию, автоматический запуск getty производится на виртуальных консолях с
|
||
первой по шестую (чтобы свести к минимуму отличия от привычной
|
||
конфигурации)\footnote{Тем не~менее, это поведение можно легко изменить,
|
||
задавая параметр +NAutoVTs=+ в файле
|
||
\href{http://www.freedesktop.org/software/systemd/man/logind.conf.html}{logind.conf}.}.
|
||
Отметим, что автоматический запуск getty на конкретной консоли производится
|
||
только при условии, что эта консоль не~занята другой программой. В частности,
|
||
при интенсивном использовании механизма
|
||
\href{https://en.wikipedia.org/wiki/Fast_user_switching}{быстрого переключения
|
||
пользователей} графические сеансы могут занять первые несколько консолей (чтобы
|
||
такое поведение не~заблокировало возможность запуска getty, мы предусмотрели
|
||
специальную защиту, см. чуть ниже).
|
||
|
||
Две консоли играют особую роль: +tty1+ и +tty6+. +tty1+, при загрузке в
|
||
графическом режиме, используется для запуска дисплейного менеджера, а при
|
||
загрузке в многопользовательском текстовом режиме, systemd принудительно
|
||
запускает на ней экземпляр getty, не~дожидаясь переключений\footnote{В данном
|
||
случае нет принципиальной разницы между принудительным запуском и запуском по
|
||
запросу: первая консоль используется по умолчанию, так что запрос на ее
|
||
активацию обязательно поступит.}.
|
||
|
||
Что касается +tty6+, то она используется исключительно для автоматического
|
||
запуска getty, и недоступна другим подсистемам, в частности, графическому
|
||
серверу\footnote{При необходимости, вы можете легко поменять номер резервируемой
|
||
консоли (или отключить резервирование), используя параметр +ReserveVT=+ в файле
|
||
\href{http://www.freedesktop.org/software/systemd/man/logind.conf.html}{logind.conf}.}.
|
||
Мы сделали так специально, чтобы гарантировать возможность входа в систему в
|
||
текстовом режиме, даже если графический сервер займет более пяти консолей.
|
||
|
||
\subsection{Последовательные консоли}
|
||
|
||
Работа с последовательными консолями (и всеми остальными видами не-виртуальных
|
||
консолей) реализована несколько иначе, чем с VT. По умолчанию, systemd запускает
|
||
один экземпляр +serial-getty@.service+ на основной консоли ядра\footnote{Если
|
||
для ядра настроен вывод в несколько консолей, \emph{основной} считается та, которая
|
||
идет \emph{первой} в +/sys/class/tty/console/active+, т.е. указана
|
||
\emph{последней} в строке параметров ядра.} (если она не~является виртуальной).
|
||
Консолью ядра~--- это та консоль, на которую выводятся сообщения ядра. Обычно
|
||
она настраивается в загрузчике, путем добавления к параметрам ядра аргумента
|
||
наподобие +console=ttyS0+\footnote{Подробнее об этой опции см. в файле
|
||
\href{https://www.kernel.org/doc/Documentation/kernel-parameters.txt}{kernel-parameters.txt}.}.
|
||
Таким образом, если пользователь перенаправил вывод ядра на последовательную
|
||
консоль, то по завершении загрузки он увидит на этой консоли приглашение для
|
||
логина\footnote{Отметим, что getty, а точнее, +agetty+ на такой консоли
|
||
вызывается с параметром +-s+, и поэтому не~изменяет настроек символьной
|
||
скорости (baud rate)~--- сохраняется то значение, которое было указано в строке
|
||
параметров ядра.}. Кроме того, systemd выполняет поиск консолей, предоставляемых
|
||
системами виртуализации, и запускает +serial-getty@.service+ на первой из этих
|
||
консолей (+/dev/hvc0+, +/dev/xvc0+ или +/dev/hvsi0+). Такое поведение
|
||
реализовано специальной
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd/Generators}{программой-генератором}~---
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-getty-generator.html}{systemd-getty-generator}.
|
||
Генераторы запускаются в самом начале загрузки и автоматически настраивают
|
||
различные службы в зависимости от соответствующих факторов.
|
||
|
||
В большинстве случаев, вышеописанного механизма автоматической настройки должно
|
||
быть достаточно, чтобы получить приглашение логина там, где нужно~--- без
|
||
каких-либо дополнительных настроек systemd. Тем не~менее, иногда возникает
|
||
необходимость в ручной настройке~--- например, когда необходимо запустить getty
|
||
сразу на нескольких последовательных консолях, или когда вывод сообщений ядра
|
||
направляется на один терминал, а управление производится с другого. Для решения
|
||
таких задач достаточно определить по экземпляру +serial-getty@.service+ для
|
||
каждого последовательного порта, на котором вы хотите запустить
|
||
getty\footnote{Отметим, что +systemctl enable+ \emph{для экземпляров служб}
|
||
работает только начиная с systemd версии 188 и старше (например, в Fedora 18). В
|
||
более ранних версиях придется напрямую манипулировать символьными ссылками:
|
||
\texttt{ln -s /usr/lib/systemd/system/serial-getty@.service
|
||
/etc/systemd/system/getty.target.wants/serial-getty@ttyS2.service ; systemctl
|
||
daemon-reload}.}\footnote{\label{ftn:enableserial}Прим. перев.: На самом деле,
|
||
работать с символьными ссылками пришлось бы даже в более свежих версиях
|
||
systemd (до 209 включительно), так как в файле +serial-getty@.service+
|
||
отсутствовала секция +[Install]+, в результате чего попытка выполнения
|
||
+systemctl enable+ для экземпляра соответствующей службы приводила к
|
||
закономерной ошибке. Данная проблема была устранена только в systemd 210.
|
||
В~случае использования более старых версий, исправить ее можно и самостоятельно:
|
||
достаточно скопировать указанный файл из +/usr/lib/systemd/system/+ в
|
||
+/etc/systemd/system/+, после чего дописать в него секцию +[Install]+,
|
||
содержащую параметр +WantedBy=getty.target+, и затем выполнить
|
||
+systemctl daemon-reload+.}:
|
||
\begin{Verbatim}
|
||
# systemctl enable serial-getty@ttyS2.service
|
||
# systemctl start serial-getty@ttyS2.service
|
||
\end{Verbatim}
|
||
После выполнения этих команд, getty будет принудительно запускаться для
|
||
указанных последовательных портов при всех последующих загрузках.
|
||
|
||
В некоторых ситуациях может возникнуть необходимость в тонкой настройке
|
||
параметров getty (например, заданная для вывода сообщений ядра символьная
|
||
скорость непригодна для интерактивного сеанса). Тогда просто скопируйте штатный
|
||
шаблон юнита в каталог +/etc/systemd/system+ и отредактируйте полученную копию:
|
||
\begin{Verbatim}
|
||
# cp /usr/lib/systemd/system/serial-getty@.service /etc/systemd/system/serial-getty@ttyS2.service
|
||
# vi /etc/systemd/system/serial-getty@ttyS2.service
|
||
... редактируем параметры запуска agetty ...
|
||
# ln -s /etc/systemd/system/serial-getty@ttyS2.service /etc/systemd/system/getty.target.wants/
|
||
# systemctl daemon-reload
|
||
# systemctl start serial-getty@ttyS2.service
|
||
\end{Verbatim}
|
||
В приведенном примере создает файл настроек, определяющий запуск getty на порту
|
||
+ttyS2+ (это определяется именем, под которым мы скопировали файл~---
|
||
+serial-getty@ttyS2.service+). Все изменения настроек, сделанные в данном файле,
|
||
будут распространяться только на этот порт.
|
||
|
||
Собственно, это все, что я хотел рассказать о последовательных портах,
|
||
виртуальных консолях и запуске getty на них. Надеюсь, рассказ получился
|
||
интересным.
|
||
|
||
\section{Работа с Journal}
|
||
|
||
В свое время, я уже рассказывал о некоторых возможностях journal
|
||
(см.~главу~\ref{sec:journal}), доступных из утилиты +systemctl+. Сейчас я
|
||
попробую рассказать о journal более подробно, с упором на практическое
|
||
применение его возможностей.
|
||
|
||
Если вы еще не~в курсе, что такое journal: это компонент
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd}{systemd}, регистрирующий
|
||
сообщения из системного журнала (syslog), сообщения ядра (kernel log) и initrd,
|
||
а также сообщения, которые процессы служб выводят на STDOUT и STDERR. Полученная
|
||
информация индексируется и предоставляется пользователю по запросу. Journal
|
||
может работать одновременно с традиционными демоном syslog (например, rsyslog
|
||
или syslog-ng), либо полностью его заменять. За подробностями стоит
|
||
обратиться к \href{http://0pointer.de/blog/projects/the-journal.html}{первому
|
||
анонсу}.
|
||
|
||
Journal был включен в Fedora начиная с F17. В Fedora~18 journal вырос в мощный и
|
||
удобный механизм работы с системным журналом. Однако, и в~F17, и в~F18 journal
|
||
по умолчанию сохраняет информацию только в небольшой кольцевой буфер в каталоге
|
||
+/run/log/journal+. Как и все содержимое каталога +/run+, эта информация
|
||
теряется при перезагрузке\footnote{Прим. перев.: Разумеется, это никак
|
||
не~относится к традиционному демону системного лога, даже если он работает
|
||
поверх journal.}. Такой подход сильно ограничивает использование
|
||
полезных возможностей journal, однако вполне достаточен для вывода актуальных
|
||
сообщений от служб в +systemctl status+. Начиная с Fedora~19, мы собираемся
|
||
включить сохранение логов на диск, в каталог +/var/log/journal+. При этом,
|
||
логи смогут занимать гораздо больше места\footnote{Прим. перев.: В journal
|
||
отдельно задаются ограничения на размер для логов во временном хранилище
|
||
(+/run+) и в постоянном (+/var+). При превышении лимита старые журналы
|
||
удаляются. Так как +/run+ размещается на tmpfs, т.е. в
|
||
оперативной памяти, для временного хранения по умолчанию установлены более
|
||
жесткие ограничения. При необходимости, соответствующие настройки можно задать
|
||
в файле
|
||
\href{http://www.freedesktop.org/software/systemd/man/journald.conf.html}{journald.conf}.},
|
||
а значит, смогут вместить больше полезной информации. Таким образом, journal
|
||
станет еще более удобным.
|
||
|
||
\subsection{Сохранение логов на диск}
|
||
|
||
В F17 и~F18 вы можете включить сохранение логов на диск вручную:
|
||
\begin{Verbatim}
|
||
# mkdir -p /var/log/journal
|
||
\end{Verbatim}
|
||
После этого рекомендуется перезагрузить систему, чтобы заполнить журнал новыми
|
||
записями.
|
||
|
||
Так как теперь у вас есть journal, syslog вам больше не~нужен (кроме ситуаций,
|
||
когда вам совершенно необходимо иметь +/var/log/messages+ в текстовом виде), и
|
||
вы спокойно можете удалить его:
|
||
\begin{Verbatim}
|
||
# yum remove rsyslog
|
||
\end{Verbatim}
|
||
|
||
\subsection{Основы}
|
||
|
||
Итак, приступим. Нижеприведенный текст демонстрирует возможности systemd~195,
|
||
входящего в Fedora~18\footnote{Обновление со 195-й версией systemd на момент
|
||
написания этих строк находится
|
||
\href{https://admin.fedoraproject.org/updates/FEDORA-2012-16709/systemd-195-1.fc18}{на
|
||
тестировании} и вскоре будет включено в состав Fedora~18.}, так что, если
|
||
некоторые из описанных трюков не~сработают в F17~--- пожалуйста, дождитесь F18.
|
||
|
||
Начнем с основ. Доступ к логам journal осуществляется через утилиту
|
||
\href{http://www.freedesktop.org/software/systemd/man/journalctl.html}{journalctl(1)}.
|
||
Чтобы просто взглянуть на лог, достаточно ввести
|
||
\begin{Verbatim}
|
||
# journalctl
|
||
\end{Verbatim}
|
||
Если вы выполнили эту команду с полномочиями root, вы увидите все
|
||
журнальные сообщения, включая исходящие как от системных компонентов, так и от
|
||
залогиненных пользователей\footnote{Прим. перев.: А если вы выполнили эту
|
||
команду от имени непривилегированного пользователя, не~входящего в группу
|
||
+adm+, и при этом не~включили сохранение логов на диск, то вы не~увидите
|
||
ничего~--- без специальных полномочий пользователь может просматривать только
|
||
собственный лог, а он по умолчанию ведется только если логи записываются на
|
||
диск.}. Вывод этой команды форматируется в стиле
|
||
+/var/log/messages+, но при этом добавлены кое-какие улучшения:
|
||
\begin{itemize}
|
||
\item Строки с приоритетом error и выше подсвечены красным.
|
||
\item Строки с приоритетом notice и warning выделены жирным шрифтом.
|
||
\item Все отметки времени сформированы с учетом вашего часового пояса.
|
||
\item Для навигации по тексту используется просмотрщик (pager), по
|
||
умолчанию +less+\footnote{Прим. перев.: В инструментах systemd,
|
||
включая +journalctl+, просмотрщик включается только при прямом
|
||
выводе на экран, и отключается при перенаправлении вывода в файл
|
||
или передаче его по каналу (shell pipe).}.
|
||
\item Выводятся \emph{все} доступные данные, включая информацию из
|
||
файлов, прошедших ротацию (rotated logs).
|
||
\item Загрузка системы отмечается специальной строкой, отделяющей
|
||
записи, сгенерированные между (пере)загрузками.
|
||
\end{itemize}
|
||
|
||
Отметим, что в данной статье не~приводятся примеры такого вывода~--- прежде
|
||
всего, для краткости изложения, но также и для того, чтобы дать вам повод
|
||
поскорее попробовать Fedora~18 с systemd~195. Надеюсь, отсутствие таких примеров
|
||
не~помешает вам уловить суть.
|
||
|
||
\subsection{Контроль доступа}
|
||
|
||
Итак, мы получили удобный и эффективный метод просмотра логов. Но для полного
|
||
доступа к системным сообщениям требуются привилегии root, что не~всегда
|
||
удобно~--- в наше время администраторы предпочитают работать от имени
|
||
непривилегированного пользователя, переключаясь на root только при крайней
|
||
необходимости. По умолчанию, непривилегированные пользователи могут
|
||
просматривать в journal только свои собственные логи (сообщения, сгенерированные
|
||
их процессами). Чтобы предоставить пользователю доступ ко всем системным логам,
|
||
нужно включить его в группу +adm+:
|
||
\begin{Verbatim}
|
||
# usermod -a -G adm lennart
|
||
\end{Verbatim}
|
||
Разлогинившись, а затем вновь залогинившись под именем +lennart+\footnote{Прим.
|
||
перев.: Для того, чтобы обновить групповые полномочия в уже запущенных сеансах,
|
||
можно воспользоваться командой
|
||
\href{http://linux.die.net/man/1/newgrp}{newgrp(1)}: +newgrp adm+.}, я могу
|
||
просматривать сообщения от всех пользователей и системных
|
||
компонентов\footnote{Прим. перев.: Группа +adm+ была выбрана на основании опыта
|
||
дистрибутива Debian, в котором она устанавливается в качестве группы-владельца
|
||
большинства лог-файлов. При этом авторы четко разделяют полномочия групп +adm+ и
|
||
+wheel+: если последняя используется для предоставления прав \emph{изменять}
|
||
что-либо в системе, то первая дает возможность лишь \emph{просматривать}
|
||
системную информацию. Начиная с версии systemd 198, группа-владелец файлов
|
||
журнала изменена с +adm+ на +systemd-journal+, однако штатный алгоритм установки
|
||
systemd все равно выдает права на их чтение группам +adm+ и +wheel+ (через
|
||
ACL).}:
|
||
\begin{Verbatim}
|
||
$ journalctl
|
||
\end{Verbatim}
|
||
|
||
\subsection{Отслеживание логов в реальном времени}
|
||
|
||
Когда вы запускаете программу +journalctl+ без параметров, она выводит все
|
||
сообщения, сгенерированные на текущий момент, и возвращает управление оболочке.
|
||
Однако, иногда бывает полезно отслеживать их появление в режиме реального
|
||
времени. В классической реализации syslog это осуществлялось командой
|
||
+tail -f /var/log/messages+. В journal ее аналог выглядит так:
|
||
\begin{Verbatim}
|
||
$ journalctl -f
|
||
\end{Verbatim}
|
||
И работает он точно так же: выводит последние десять сообщений, после чего
|
||
переходит в режим ожидания, и выводит новые сообщения по мере их появления.
|
||
|
||
\subsection{Простейшие методы выборки записей}
|
||
|
||
При вызове +journalctl+ без параметров, она выводит все сообщения, начиная с
|
||
самого первого из сохраненных. Разумеется, это огромный объем информации. На
|
||
практике иногда бывает достаточно ограничиться сообщениями, сгенерированными с
|
||
момента последней загрузки системы\footnote{Прим. перев.: Начиная с systemd
|
||
версии 206, синтаксис опции <<+-b+>> был расширен: теперь она позволяет
|
||
просматривать логи не~только за текущую, но и за предыдущие загрузки, например,
|
||
<<+-b -1+>>~--- прошлая загрузка, <<+-b -2+>>~--- позапрошлая и т.д.}:
|
||
\begin{Verbatim}
|
||
$ journalctl -b
|
||
\end{Verbatim}
|
||
Но часто даже после такой фильтрации записей остается довольно много. Что ж, мы
|
||
можем ограничиться только наиболее важными. Итак, все сообщения с приоритетом
|
||
error и выше, начиная с момента последней загрузки:
|
||
\begin{Verbatim}
|
||
$ journalctl -b -p err
|
||
\end{Verbatim}
|
||
|
||
Если вы уже успели перезагрузить систему после того, как произошли интересующие
|
||
вас события, целесообразнее будет воспользоваться выборкой по времени:
|
||
\begin{Verbatim}
|
||
$ journalctl --since=yesterday
|
||
\end{Verbatim}
|
||
В результате мы увидим все сообщения, зарегистрированные начиная со вчерашнего
|
||
дня вплоть до настоящего момента. Прекрасно! Разумеется, этот критерий отбора можно
|
||
комбинировать с другими, например, с +-p err+. Но, допустим, нам нужно узнать о
|
||
чем-то, что случилось либо 15-го октября, либо 16-го:
|
||
\begin{Verbatim}
|
||
$ journalctl --since=2012-10-15 --until="2011-10-16 23:59:59"
|
||
\end{Verbatim}
|
||
Отлично, мы нашли то, что искали. Но вот вам сообщают, что сегодня ранним утром
|
||
наблюдались проблемы с CGI-скриптами Apache. Ладно, послушаем, что нам скажет
|
||
индеец:
|
||
\begin{Verbatim}
|
||
$ journalctl -u httpd --since=00:00 --until=9:30
|
||
\end{Verbatim}
|
||
Да, мы нашли это. Хм, похоже, что причиной стала проблема с диском +/dev/sdc+.
|
||
Посмотрим, что с ним случилось:
|
||
\begin{Verbatim}
|
||
$ journalctl /dev/sdc
|
||
\end{Verbatim}
|
||
Кошмар, ошибка ввода-вывода\footnote{Ну ладно, признаюсь, здесь я немножко
|
||
считерил. Индексирование сообщений ядра по блочным устройствам пока что
|
||
не~принято в апстрим, но Ганс
|
||
\href{http://www.spinics.net/lists/linux-scsi/msg62499.html}{проделал огромную
|
||
работу}, чтобы реализовать эту функциональность, и я надеюсь, что к релизу F18
|
||
все будет.}! Нужно срочно заменить диск, пока не~начались более серьезные
|
||
проблемы. Ладно, пойдем дальше. Что у нас там случилось с процессом vpnc?
|
||
\begin{Verbatim}
|
||
$ journalctl /usr/sbin/vpnc
|
||
\end{Verbatim}
|
||
Хм, ничего подозрительного. Но, кажется, проблема где-то во взаимодействии между
|
||
+vpnc+ и +dhclient+. Посмотрим объединенный и отсортированный по времени список
|
||
сообщений от этих процессов:
|
||
\begin{Verbatim}
|
||
$ journalctl /usr/sbin/vpnc /usr/sbin/dhclient
|
||
\end{Verbatim}
|
||
Отлично, мы нашли причину проблемы!
|
||
|
||
\subsection{Продвинутые методы выборки}
|
||
\label{ssec:metadata}
|
||
|
||
Да, это все, конечно, здорово, но попробуем подняться еще на ступеньку выше.
|
||
Чтобы понять описанные ниже приемы, нужно знать, что systemd добавляет к
|
||
каждой лог-записи ряд скрытых метаданных. Эти метаданные по структуре напоминают
|
||
набор переменных окружения, хотя на самом деле дают даже больше возможностей:
|
||
во-первых, метаданные могут включать большие бинарные блоки данных (впрочем, это
|
||
скорее исключение~--- обычно они содержат текст в кодировке UTF-8), и во-вторых,
|
||
в пределах одной записи поле метаданных может содержать сразу несколько
|
||
значений (и это тоже встречается нечасто~--- обычно поля содержат по одному
|
||
значению). Эти метаданные автоматически собираются и добавляются для каждой
|
||
лог-записи, безо всякого участия пользователя. И вы легко можете их использовать
|
||
для более тонкой выборки записей. Посмотрим, как они выглядят:
|
||
\begin{Verbatim}
|
||
$ journalctl -o verbose -n1
|
||
Tue, 2012-10-23 23:51:38 CEST [s=ac9e9c423355411d87bf0ba1a9b424e8;i=4301;b=5335e9cf5d954633bb99aefc0ec38c25;m=882ee28d2;t=4ccc0f98326e6;x=f21e8b1b0994d7ee]
|
||
PRIORITY=6
|
||
SYSLOG_FACILITY=3
|
||
_MACHINE_ID=a91663387a90b89f185d4e860000001a
|
||
_HOSTNAME=epsilon
|
||
_TRANSPORT=syslog
|
||
SYSLOG_IDENTIFIER=avahi-daemon
|
||
_COMM=avahi-daemon
|
||
_EXE=/usr/sbin/avahi-daemon
|
||
_SYSTEMD_CGROUP=/system/avahi-daemon.service
|
||
_SYSTEMD_UNIT=avahi-daemon.service
|
||
_SELINUX_CONTEXT=system_u:system_r:avahi_t:s0
|
||
_UID=70
|
||
_GID=70
|
||
_CMDLINE=avahi-daemon: registering [epsilon.local]
|
||
MESSAGE=Joining mDNS multicast group on interface wlan0.IPv4 with address 172.31.0.53.
|
||
_BOOT_ID=5335e9cf5d954633bb99aefc0ec38c25
|
||
_PID=27937
|
||
SYSLOG_PID=27937
|
||
_SOURCE_REALTIME_TIMESTAMP=1351029098747042
|
||
\end{Verbatim}
|
||
(Чтобы не~утомлять вас огромным количеством текста, ограничимся одной записью.
|
||
Ключ +-n+ позволяет задать число выводимых записей, в нашем случае 1. Если
|
||
указать его без параметра, будут показаны 10 последних записей.)
|
||
|
||
Задав параметр +-o verbose+, мы переключили формат вывода~--- теперь, вместо
|
||
скупых строчек, копирующих +/var/log/messages+, для каждой записи выводится
|
||
полный перечень всех метаданных. В том числе, информация о пользователе и
|
||
группе, контекст SELinux, идентификатор компьютера и т.д. Полный список
|
||
общеизвестных полей метаданных приведен на соответствующей
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html}%
|
||
{странице руководства}.
|
||
|
||
База данных Journal индексируется по \emph{всем} этим полям! И мы можем
|
||
использовать любое из них в качестве критерия выборки:
|
||
\begin{Verbatim}
|
||
$ journalctl _UID=70
|
||
\end{Verbatim}
|
||
Как нетрудно догадаться, в результате будут выведены все сообщения от
|
||
процессов пользователя с UID 70. При необходимости, критерии можно
|
||
комбинировать:
|
||
\begin{Verbatim}
|
||
$ journalctl _UID=70 _UID=71
|
||
\end{Verbatim}
|
||
Указание нескольких значений для одного и того же поля эквивалентно логическому
|
||
ИЛИ. Таким образом, будут выведены записи как от процессов с UID 70, так и от
|
||
процессов с UID 71.
|
||
\begin{Verbatim}
|
||
$ journalctl _HOSTNAME=epsilon _COMM=avahi-daemon
|
||
\end{Verbatim}
|
||
А вот указание нескольких \emph{различных} полей дает эффект логического И. В
|
||
результате, будут выведены записи только от процесса +avahi-daemon+, работающего
|
||
на хосте с именем +epsilon+.
|
||
|
||
Но мы этим не~ограничимся! Мы же суровые компьютерщики, нам нужны сложные
|
||
логические выражения!
|
||
\begin{Verbatim}
|
||
$ journalctl _HOSTNAME=theta _UID=70 + _HOSTNAME=epsilon _COMM=avahi-daemon
|
||
\end{Verbatim}
|
||
При помощи плюса мы можем явно задать логическое ИЛИ, применяя его к разным
|
||
полям и даже И-выражениям. Поэтому наш пример выведет как записи с хоста
|
||
+theta+ от процессов с UID 70, так и с хоста +epsilon+ от процесса
|
||
+avahi-daemon+\footnote{Прим. перев.: Стоит отметить, что приоритет логических
|
||
операций стандартный: сначала выполняются операции И, и только потом~---
|
||
операции ИЛИ. Используемая в +journalctl+ система записи выражений аналогична
|
||
принятой в классической алгебре: умножение (имеющее более высокий приоритет)
|
||
не~указывается знаком операции, а обозначается просто последовательной
|
||
записью величин.}.
|
||
|
||
\subsection{И немного магии}
|
||
|
||
Уже неплохо, правда? Но есть одна проблема~--- мы же не~сможем запомнить все
|
||
возможные значения всех полей журнала! Для этого была бы нужна очень хорошая
|
||
память. Но +journalctl+ вновь приходит к нам на помощь:
|
||
\begin{Verbatim}
|
||
$ journalctl -F _SYSTEMD_UNIT
|
||
\end{Verbatim}
|
||
Эта команда выведет все значения поля +_SYSTEMD_UNIT+, зарегистрированные в базе
|
||
данных журнала на текущий момент. То есть, имена всех юнитов +systemd+, которые
|
||
писали что-либо в журнал. Аналогичный запрос работает для всех полей, так что
|
||
найти точное значение для выборки по нему~--- уже не~проблема. Но тут самое
|
||
время сообщить вам, что эта функциональность встроена в механизм автодополнения
|
||
оболочки\footnote{Прим. перев.: В исходной статье речь идет только о bash,
|
||
однако, начиная с релиза systemd 196, аналогичная функциональность доступна и
|
||
для zsh.}! Это же просто прекрасно~--- вы можете просмотреть перечень значений
|
||
поля и выбрать из него нужно прямо при вводе выражения. Возьмем для примера
|
||
метки SELinux. Помнится, имя поля начиналось с букв SE\ldots{}
|
||
\begin{Verbatim}[commandchars=\\\{\}]
|
||
$ journalctl _SE\textbf{<TAB>}
|
||
\end{Verbatim}
|
||
и оболочка сразу же дополнит:
|
||
\begin{Verbatim}
|
||
$ journalctl _SELINUX_CONTEXT=
|
||
\end{Verbatim}
|
||
Отлично, и какое там значение нам нужно?
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ journalctl _SELINUX_CONTEXT=<TAB><TAB>
|
||
kernel system_u:system_r:rtkit_daemon_t:s0
|
||
system_u:system_r:accountsd_t:s0 system_u:system_r:syslogd_t:s0
|
||
system_u:system_r:avahi_t:s0 system_u:system_r:system_cronjob_t:s0-s0:c0.c1023
|
||
system_u:system_r:bluetooth_t:s0 system_u:system_r:system_dbusd_t:s0-s0:c0.c1023
|
||
system_u:system_r:chkpwd_t:s0-s0:c0.c1023 system_u:system_r:systemd_logind_t:s0
|
||
system_u:system_r:chronyd_t:s0 system_u:system_r:systemd_tmpfiles_t:s0
|
||
system_u:system_r:crond_t:s0-s0:c0.c1023 system_u:system_r:udev_t:s0-s0:c0.c1023
|
||
system_u:system_r:devicekit_disk_t:s0 system_u:system_r:virtd_t:s0-s0:c0.c1023 c0.c1023
|
||
system_u:system_r:dhcpc_t:s0 system_u:system_r:vpnc_t:s0 sd_t:s0-s0:c0.c1023
|
||
system_u:system_r:dnsmasq_t:s0-s0:c0.c1023 system_u:system_r:xdm_t:s0-s0:c0.c1023
|
||
system_u:system_r:init_t:s0 unconfined_u:system_r:rpm_t:s0-s0:c0.c1023
|
||
system_u:system_r:local_login_t:s0-s0:c0.c1023 unconfined_u:system_r:unconfined_t:s0-s0:c0.c1023
|
||
system_u:system_r:lvm_t:s0 unconfined_u:system_r:useradd_t:s0-s0:c0.c1023
|
||
system_u:system_r:modemmanager_t:s0-s0:c0.c1023 unconfined_u:unconfined_r:unconfined_dbusd_t:s0-s0:c0.c1023
|
||
system_u:system_r:NetworkManager_t:s0 unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
|
||
system_u:system_r:policykit_t:s0
|
||
\end{Verbatim}
|
||
Ага, нас интересуют записи с меткой PolicyKit! Пользуясь дополнением, вводим:
|
||
\begin{Verbatim}
|
||
$ journalctl _SELINUX_CONTEXT=system_u:system_r:policykit_t:s0
|
||
\end{Verbatim}
|
||
Очень просто, не~правда ли! Пожалуй, самое простое из действий, связанных с
|
||
SELinux ;-) Разумеется, такое дополнение работает для всех полей Journal.
|
||
|
||
На сегодня все. Впрочем, на странице руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/journalctl.html}%
|
||
{journalctl(1)} можно узнать и о многих других возможностях, не~описанных здесь.
|
||
Например, о том, что +journalctl+ может выводить данные в формате JSON, или в
|
||
формате +/var/log/messages+, но с относительными метками времени, как в dmesg.
|
||
|
||
\section{Управление ресурсами с помощью cgroups}
|
||
\label{sec:resources}
|
||
|
||
Важную роль в современных компьютерных системах играют механизмы управления
|
||
использованием ресурсов: когда вы запускаете на одной системе несколько
|
||
программ, возникает необходимость распределять между ними ресурсы системы,
|
||
в соответствии с некоторыми правилами. В частности, это особенно актуально на
|
||
маломощных встраиваемых и мобильных системах, обладающих очень скудными
|
||
ресурсами. Но та же задача актуальна и для очень мощных вычислительных
|
||
кластеров, которые располагают огромными ресурсами, но при этом несут и огромную
|
||
вычислительную нагрузку.
|
||
|
||
Исторически, в Linux поддерживался только одна схема управления ресурсами: все
|
||
процессы получают примерно равные доли процессорного времени или потока
|
||
ввода-вывода. При необходимости соотношение этих долей можно изменить при
|
||
помощи значения \emph{nice}, задаваемого для каждого процесса. Такой подход
|
||
очень прост, и на протяжении долгих лет покрывал все нужды пользователей Linux.
|
||
Но у него есть существенный недостаток: он оперирует лишь отдельными процессами,
|
||
но не~их группами. В результате, например, веб-сервер Apache с множеством
|
||
CGI-процессов при прочих равных получает гораздо больше ресурсов, чем служба
|
||
syslog, у которой не~так много процессов.
|
||
|
||
В процессе проектирования архитектуры systemd, мы практически сразу поняли, что
|
||
управление ресурсов должно быть одной из базовых функций, заложенных в
|
||
основы его структуры. В современной системе~--- неважно, серверной или
|
||
встраиваемой~--- контроль использования процессора, памяти и ввода-вывода для
|
||
различных служб нельзя добавлять задним числом. Такая функциональность должна
|
||
быть доступна изначально, через базовые настройки запуска служб. При этом,
|
||
ресурсы должны распределяться на уровне служб, а не~процессов, как это делалось
|
||
при помощи значений nice или \href{http://linux.die.net/man/2/setrlimit}{POSIX
|
||
Resource Limits}.
|
||
|
||
В этой статье я попробую рассказать о методах управления механизмами
|
||
распределения ресурсов между службами systemd. Эта функциональность присутствует
|
||
в systemd уже долгое время, и давно пора рассказать о ней пользователям и
|
||
администраторам.
|
||
|
||
В свое время я
|
||
\href{http://0pointer.de/blog/projects/cgroups-vs-cgroups.html}{пояснял}%
|
||
\footnote{Прим. перев.: В указанном документе автор рассказывает, что
|
||
контрольные группы Linux состоят из двух сущностей: \textbf{(A)} механизма
|
||
иерархической группировки и маркировки процессов, и \textbf{(B)} механизма,
|
||
позволяющего распределять ресурсы между полученными группами. Для работы (B)
|
||
необходимо (A), но не~наоборот~--- (A) может прекрасно работать без (B). Для
|
||
нормально функционирования systemd (A) \emph{необходим}, а (B) опционален (он
|
||
лишь обеспечивает работу некоторых настроек). Вы можете собрать ядро только с
|
||
необходимой для (A) опцией +CONFIG_CGROUPS=y+, отключив все связанные с (B)
|
||
опции (такие как {\tiny +CONFIG_CGROUP_FREEZER=y+, +CONFIG_CGROUP_DEVICE=y+,
|
||
+CONFIG_CGROUP_CPUACCT=y+, +CONFIG_CGROUP_MEM_RES_CTLR=y+,
|
||
+CONFIG_CGROUP_MEM_RES_CTLR_SWAP=y+, +CONFIG_CGROUP_MEM_RES_CTLR_KMEM=y+,
|
||
+CONFIG_CGROUP_PERF=y+, +CONFIG_CGROUP_SCHED=y+, +CONFIG_BLK_CGROUP=y+,
|
||
+CONFIG_NET_CLS_CGROUP=y+, +CONFIG_NET_PRIO_CGROUP=y+}), и systemd будет
|
||
нормально работать на такой системе (за исключением того, что связанные с этими
|
||
контроллерами настройки не~будут срабатывать). Однако, если собрать ядро без
|
||
+CONFIG_CGROUPS=y+, функциональность systemd будет сильно ограничена. При этом,
|
||
автор особо подчеркивает, что все негативные эффекты влияния контрольных групп
|
||
на производительность обусловлены именно (B), в то время как (A) на
|
||
производительность практически не~влияет.}, что контрольные группы Linux
|
||
(cgroups) могут работать и как механизм группировки и отслеживания процессов, и
|
||
как инструмент управления использованием ресурсов. Для функционирования systemd
|
||
необходим только первый из этих режимов, а второй опционален. И именно этот
|
||
опциональный второй режим дает вам возможность распределять ресурсы между
|
||
службами. (А сейчас очень рекомендую вам, прежде чем продолжать чтение этой
|
||
статьи, ознакомиться с \href{https://en.wikipedia.org/wiki/Cgroups}{базовой
|
||
информацией о cgroups}. Хотя дальнейшие рассуждения и не~будут затрагивать
|
||
низкоуровневые аспекты, все же будет лучше, если у вас сформируется некоторое
|
||
представление о них.)
|
||
|
||
Основными контроллерами cgroups, отвечающими за управление ресурсами, являются
|
||
\href{http://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt}{cpu},
|
||
\href{http://www.kernel.org/doc/Documentation/cgroups/memory.txt}{memory} и
|
||
\href{http://www.kernel.org/doc/Documentation/cgroups/blkio-controller.txt}{blkio}.
|
||
Для их использования необходимо, чтобы они были включены на этапе сборки ядра;
|
||
большинство дистрибутивов (в том числе и Fedora), включают их в штатных ядрах.
|
||
systemd предоставляет ряд высокоуровневых настроек, позволяющих использовать эти
|
||
контроллеры, не~вникая в технические детали их работы.
|
||
|
||
\subsection{Процессор}
|
||
|
||
Если в ядре включен контроллер +cpu+, systemd по умолчанию создает контрольную
|
||
группу по этому ресурсу для каждой службы. Даже без каких-либо дополнительных
|
||
настроек это дает положительных эффект: на системе под управлением systemd все
|
||
службы получают равные доли процессорного времени, независимо от количества
|
||
процессов, запущенных в рамках службы. Например, на вашем веб-сервере MySQL с
|
||
несколькими рабочими процессами получит такую же долю процессорного времени,
|
||
как и Apache, даже если тот запустил 1000 CGI-процессов. Разумеется, такое
|
||
поведение при необходимости можно легко отключить~--- см. опцию
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd-system.conf.html}%
|
||
{DefaultControllers=} в файле +/etc/systemd/system.conf+.
|
||
|
||
Если \emph{равномерное} распределение процессорного времени между службами вас
|
||
не~устраивает, и вы хотите выделить определенным службам больше или меньше
|
||
времени~--- используйте опцию
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html}%
|
||
{CPUShares=} в конфигурационном файле службы. По умолчанию это значение равно
|
||
1024. Увеличивая это число, вы даете службе больше процессорного времени,
|
||
уменьшая~--- соответственно, меньше.
|
||
|
||
Рассмотрим небольшой практический пример. Допустим, вам нужно увеличить
|
||
для службы Apache относительную долю потребления процессора до 1500. Для этого
|
||
создаем файл <<ручных>> настроек +/etc/systemd/system/httpd.service+, который
|
||
включает в себя все те же опции, что и файл настроек по умолчанию
|
||
+/usr/lib/systemd/system/httpd.service+, отличаясь от него только значением
|
||
+CPUShares=+:
|
||
\begin{Verbatim}
|
||
.include /usr/lib/systemd/system/httpd.service
|
||
|
||
[Service]
|
||
CPUShares=1500
|
||
\end{Verbatim}
|
||
Первая строка обеспечивает включение в нашу конфигурацию файла с настройками по
|
||
умолчанию, сделанными разработчиками Apache или его сопровождающими в вашем
|
||
дистрибутиве (если это включение не~указать явно, данный файл будет
|
||
проигнорирован). Далее, мы указываем тот параметр, который хотим
|
||
изменить\footnote{Прим. перев.: В новых версиях systemd, начиная со 198, можно
|
||
поступить проще: вместо того, чтобы создавать свой юнит-файл в каталоге +/etc+ и
|
||
включать в него содержимое штатного (из +/usr+), достаточно просто создать
|
||
каталог +/etc/systemd/system/httpd.service.d/+ и поместить в него файл
|
||
+my_resource_limit.conf+ (суффикс +.conf+ обязателен), в котором указываются
|
||
только те настройки, которые необходимо изменить (последние две строки
|
||
вышеприведенного листинга). Это никак не~отменяет основного конфигурационного
|
||
файла юнита (который может находиться в +/usr+ или +/etc+), однако настройки из
|
||
+.d+-каталогов имеют более высокий приоритет и могут перекрывать настройки
|
||
основного файла. Все вышесказанное относится и к другим примерам из этого
|
||
раздела.}. Сохраняем файл, приказываем systemd перечитать конфигурацию, и
|
||
перезапускаем Apache, чтобы настройки вступили в силу\footnote{Прим. перев.:
|
||
systemd версий до 197 (включительно) не~имел штатного механизма для изменения
|
||
параметров контрольных групп <<на лету>> (без перезапуска службы). Если у вас
|
||
все же возникнет такая необходимость, вы можете узнать контрольную группу службы
|
||
командой +systemctl show -p ControlGroup имя_службы.service+, и выполнить
|
||
требуемые настройки напрямую через запись значений в псевдофайлы cgroupfs
|
||
(разумеется, при следующем запуске службы к ней будут применены параметры,
|
||
указанные в конфигурационном файле). В версиях systemd со 198 по 204,
|
||
программа +systemctl+ поддерживала команды +set-cgroup-attr+,
|
||
+unset-cgroup-attr+ и +get-cgroup-attr+, позволявшие манипулировать настройками
|
||
контрольных групп. Начиная с systemd 205, они были заменены командой
|
||
+set-property+, работающей с параметрами конфигурации юнитов. Установленные
|
||
через +systemctl+ настройки сохраняются во вспомогательных конфигурационных
|
||
файлах, которые размещаются в +.d+-каталогах (см. примечание выше).}:
|
||
\begin{Verbatim}
|
||
systemctl daemon-reload
|
||
systemctl restart httpd.service
|
||
\end{Verbatim}
|
||
Готово!
|
||
|
||
Обратите внимание, что явное указание значения +CPUShares=+ в конфигурации
|
||
службы заставит systemd создать для нее контрольную группу в иерархии контроллера
|
||
+cpu+, даже если этот контроллер не~указан в +DefaultControllers=+ (см. выше).
|
||
|
||
\subsection{Отслеживание использования ресурсов}
|
||
|
||
Для того, чтобы правильно распределять ресурсы между службами, неплохо бы знать
|
||
реальные потребности этих служб. Чтобы упростить для вас отслеживание
|
||
потребления ресурсов службами, мы подготовили утилиту
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-cgtop.html}{systemd-cgtop},
|
||
которая находит все имеющиеся в системе контрольные группы, определяет для
|
||
каждой из них количество потребляемых ресурсов (процессорное время, память и
|
||
ввод-вывод) и выводит эти данные в динамически обновляемой сводной таблице, по аналогии
|
||
с программой \href{http://linux.die.net/man/1/top}{top}. Используя вводимое
|
||
systemd распределение служб по контрольным группам, эта утилита выводит для
|
||
служб те же сведения, которые top выводит для отдельных процессов.
|
||
|
||
К сожалению, по умолчанию +cgtop+ может раздельно отслеживать для каждой службы
|
||
только потребление процессорного времени, а сведения по использованию памяти и
|
||
ввода-вывода доступны только для всей системы в целом. Это ограничение возникает
|
||
из-за того, что в конфигурации по умолчанию контрольные группы для служб
|
||
создаются только в иерархии контроллера +cpu+, но не~+memory+ и~+blkio+. Без
|
||
создания групп в иерархии этих контроллеров невозможно отследить использование
|
||
ресурса по службам. Самый простой способ обойти это ограничение~--- приказать
|
||
systemd создавать соответствующие группы, добавив +memory+ и +blkio+ в перечень
|
||
+DefaultControllers=+ в файле +system.conf+.
|
||
|
||
\subsection{Память}
|
||
|
||
Используя опции +MemoryLimit=+ и +MemorySoftLimit=+, вы можете ограничивать
|
||
суммарное потребление оперативной памяти всеми процессами службы. В них
|
||
указывается предел потребления памяти в байтах\footnote{Прим. перев.: Разница
|
||
между +MemorySoftLimit=+ и +MemoryLimit=+ состоит в том, что первый предел можно
|
||
превышать, если в системе еще есть достаточное количество свободной памяти.
|
||
Второй из этих пределов превышать нельзя, независимо от наличия свободной
|
||
памяти. Подробнее см. раздел <<Soft limits>> в
|
||
\href{http://www.kernel.org/doc/Documentation/cgroups/memory.txt}{файле
|
||
документации}. Отметим, что из-за планируемого прекращения поддержки параметра
|
||
+memory.soft_limit_in_bytes+ на уровне ядра, опция +MemorySoftLimit=+ была
|
||
\href{http://cgit.freedesktop.org/systemd/systemd/commit/?id=ddca82aca08712a302cfabdbe59f73ee9ed3f73a}%
|
||
{удалена} из systemd, начиная с версии 208.}. При этом поддерживаются суффиксы
|
||
K, M, G и T, обозначающие соответственно, килобайт, мегабайт, гигабайт и
|
||
терабайт (по основанию 1024).
|
||
\begin{Verbatim}
|
||
.include /usr/lib/systemd/system/httpd.service
|
||
|
||
[Service]
|
||
MemoryLimit=1G
|
||
\end{Verbatim}
|
||
|
||
По аналогии с +CPUShares=+, явное указание этих опций заставит systemd создать
|
||
для службы контрольную группу в иерархии контроллера +memory+, даже если он
|
||
не~был указан в +DefaultControllers=+.
|
||
|
||
\subsection{Ввод-вывод}
|
||
|
||
Для контроля пропускной полосы ввода-вывода с блочных устройств, доступно
|
||
несколько настроек. Первая из них~--- +BlockIOWeight=+, задающая \emph{долю} полосы
|
||
ввода-вывода для указанной службы. Принцип похож на +CPUShares=+ (см. выше), однако
|
||
здесь величина относительной доли ограничена значениями от 10 до 1000. По
|
||
умолчанию, она равна 1000. Уменьшить долю для службы Apache можно так:
|
||
\begin{Verbatim}
|
||
.include /usr/lib/systemd/system/httpd.service
|
||
|
||
[Service]
|
||
BlockIOWeight=500
|
||
\end{Verbatim}
|
||
|
||
При необходимости, вы можете задать такое значение отдельно для каждого
|
||
устройства:
|
||
\begin{Verbatim}
|
||
.include /usr/lib/systemd/system/httpd.service
|
||
|
||
[Service]
|
||
BlockIOWeight=/dev/disk/by-id/ata-SAMSUNG_MMCRE28G8MXP-0VBL1_DC06K01009SE009B5252 750
|
||
\end{Verbatim}
|
||
|
||
При этом, точное название устройства знать не~обязательно~--- достаточно указать
|
||
интересующий вас каталог:
|
||
\begin{Verbatim}
|
||
.include /usr/lib/systemd/system/httpd.service
|
||
|
||
[Service]
|
||
BlockIOWeight=/home/lennart 750
|
||
\end{Verbatim}
|
||
Если заданный вами путь не~указывает на файл устройства, systemd автоматически
|
||
определит, на каком устройстве расположен указанный файл/каталог, и выставит для
|
||
этого устройства соответствующую настройку.
|
||
|
||
Вы можете добавить несколько таких строк, задавая долю пропускной полосы
|
||
отдельно для различных устройств, и при этом также допускается указать <<общее>>
|
||
значение (как в первом примере), которое будет использовано для всех остальных
|
||
устройств.
|
||
|
||
В качестве альтернативы относительной доле пропускной полосы, вы также можете
|
||
ограничивать абсолютную долю, используя настройки +BlockIOReadBandwidth=+ и
|
||
+BlockIOWriteBandwidth=+. В них нужно указать устройство или любой находящийся
|
||
на нем файл/каталог, а также предельную скорость чтения/записи в байтах в
|
||
секунду:
|
||
\begin{Verbatim}
|
||
.include /usr/lib/systemd/system/httpd.service
|
||
|
||
[Service]
|
||
BlockIOReadBandwith=/var/log 5M
|
||
\end{Verbatim}
|
||
В результате, для данной службы скорость чтения с устройства, содержащего
|
||
каталог +/var/log+, будет ограничена величиной 5 мегабайт в секунду.
|
||
|
||
По аналогии с вышеописанными +CPUShares=+ и +MemoryLimit=+, явное указание любой
|
||
из приведенных настроек пропускной полосы заставит systemd создать для службы
|
||
контрольную группу в иерархии контроллера +blkio+.
|
||
|
||
\subsection{Прочие параметры}
|
||
|
||
Вышеописанные опции покрывают лишь малую толику настроек, поддерживаемых
|
||
различными контроллерами Linux cgroups. Мы добавили высокоуровневый интерфейс
|
||
только к тем настройкам, которые кажутся нам наиболее важным для большинства
|
||
пользователей. Из соображений удобства мы добавили механизмы, обеспечивающие
|
||
поддержку крупных единиц измерения (килобайты, мегабайты и т.д.) и
|
||
автоматическое определение блочных устройств по указанному файлу/каталогу.
|
||
|
||
В некоторых случаях описанных высокоуровневых настроек может оказаться
|
||
недостаточно~--- допустим, вам нужно задать низкоуровневую настройку cgroups,
|
||
для которой мы (пока) не~добавили высокоуровневого аналога. На этот случай мы
|
||
предусмотрели универсальных механизм задания таких опций в конфигурационных
|
||
файлах юнитов\footnote{Прим. перев.: Описываемый здесь механизм управления
|
||
низкоуровневыми настройками контрольных групп (+ControlGroupAttribute=+)
|
||
присутствовал в systemd до 204 версии включительно. Начиная с версии 205, он был
|
||
удален по требованию разработчиков cgroups. Также были удалены возможности
|
||
напрямую задавать для юнита контрольную группу (+ControlGroup=+) и устанавливать
|
||
права доступа для управления группами (+ControlGroupModify=+). Взамен,
|
||
разработчики systemd планируют значительно расширить поддержку высокоуровневых
|
||
настроек для параметров cgroups~--- сразу после того, как разработчики
|
||
cgroups определятся, какие именно параметры они будут поддерживать. Наличие
|
||
некоторого хаоса в этой области обусловлено происходящей сейчас полной
|
||
переработкой реализации контрольных групп на уровне ядра и механизмов работы с
|
||
ними из пространства пользователя. Техническая сторона данного вопроса раскрыта
|
||
в статье
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface}%
|
||
{The New Control Group Interfaces} (пока не~переведена).}. Рассмотрим,
|
||
например, задание для службы параметра \emph{swappiness} (относительная
|
||
интенсивность использования подкачки для процессов службы). В systemd нет
|
||
высокоуровневой настройки для этого значения. Однако вы можете задать его,
|
||
используя низкоуровневую настройку +ControlGroupAttribute=+:
|
||
\begin{Verbatim}
|
||
.include /usr/lib/systemd/system/httpd.service
|
||
|
||
[Service]
|
||
ControlGroupAttribute=memory.swappiness 70
|
||
\end{Verbatim}
|
||
Как обычно, явное указание настройки, относящейся к какому-либо контроллеру (в
|
||
нашем случае +memory+) приведет к автоматическому созданию группы в иерархии
|
||
данного контроллера.
|
||
|
||
В дальнейшем, возможно, мы расширим возможности высокоуровневой настройки
|
||
различных параметров контрольных групп. Если вы часто пользуетесь какими-то из
|
||
них и полагаете, что для них можно добавить соответствующие опции~---
|
||
не~стесняйтесь обращаться к нам. А лучше всего~--- присылайте сразу патч!
|
||
|
||
\begin{caveat}
|
||
Обратите внимание, что использование некоторых контроллеров может сильно
|
||
сказаться на производительности системы. Это та цена, которую приходится
|
||
платить за контроль над ресурсами. Использование таких контроллеров
|
||
может ощутимо замедлить некоторые операции. В частности, весьма
|
||
нелестная в этом плане репутация закрепилась за контроллером +memory+
|
||
(хотя, не~исключено, что эта проблема уже исправлена в свежих выпусках
|
||
ядра).
|
||
\end{caveat}
|
||
|
||
Для углубленного изучения темы, затронутой в этой статье, вы можете обратиться к
|
||
документации по
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html}%
|
||
{поддерживаемым настройкам юнитов}, а также по контроллерам
|
||
\href{http://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt}{cpu},
|
||
\href{http://www.kernel.org/doc/Documentation/cgroups/memory.txt}{memory} и
|
||
\href{http://www.kernel.org/doc/Documentation/cgroups/blkio-controller.txt}{blkio}.
|
||
|
||
Стоит подчеркнуть, что мы сейчас обсуждали распределение ресурсов \emph{между
|
||
службами}. В дополнение к этим современным механизмам, systemd также
|
||
поддерживает и традиционные настройки, касающиеся распределения ресурсов
|
||
\emph{между отдельными процессами}. Хотя такие настройки обычно наследуются
|
||
порожденными процессами, они, тем не~менее, все равно ограничивают ресурсы
|
||
на уровне отдельных процессов. В частности, к ним относятся +IOSchedulingClass=+,
|
||
+IOSchedulingPriority=+, +CPUSchedulingPolicy=+, +CPUSchedulingPriority=+,
|
||
+CPUAffinity=+, +LimitCPU=+ и т.п. Для их работы не~требуют контроллеры cgroups,
|
||
и они не~так сильно ухудшают производительность. Возможно, мы рассмотрим их в
|
||
последующих статьях.
|
||
|
||
\section{Проверка на виртуальность}
|
||
|
||
Еще в начале разработки systemd, мы внимательно изучали существовавшие на тот
|
||
момент init-скрипты, выделяя наиболее типичные для них операции. Среди прочих, в
|
||
составленный нами список попала и такая функция, как определение виртуализации:
|
||
некоторые скрипты проверяли, запускаются они в виртуальном окружении (например,
|
||
KVM, VMWare, LXC и т.д.) или на полноценной, физической системе. Часть этих
|
||
скриптов отказывалась работать на виртуальных системах (например, службы
|
||
управления устройствами совершенно излишни в виртуальных контейнерах, не~имеющих
|
||
доступа к устройствам), другие же, наоборот, запускались только в определенных
|
||
виртуальных окружениях (например, всевозможные <<guest additions>>,
|
||
рекомендуемые к запуску на гостевых системах VMWare и VirtualBox). По-хорошему,
|
||
в некоторых ситуациях было бы более правильно проверять некоторые другие
|
||
условия, а не~пытаться явно определить наличие виртуализации. Тем не~менее,
|
||
всесторонне изучив вопрос, мы пришли к выводу, что во многих случаях
|
||
возможность явной проверки такого условия при запуске служб была бы очень
|
||
кстати. В результате, мы добавили поддержку соответствующей опции настройки
|
||
юнитов~---
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}{ConditionVirtualization};
|
||
кроме того, мы создали небольшую утилиту, которую можно вызывать из
|
||
скриптов~---
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd-detect-virt.html}{systemd-detect-virt(1)};
|
||
и наконец, мы предоставили простой интерфейс для шины D-Bus, позволяющий
|
||
получить информацию о виртуализации даже непривилегированным программам.
|
||
|
||
Определить, запущен код на виртуальной системе, или на физической, на самом деле
|
||
\href{http://cgit.freedesktop.org/systemd/systemd/tree/src/shared/virt.c#n30}{не~так
|
||
уж и сложно}. В зависимости от того, какие именно механизмы виртуализации вы
|
||
хотите определить, основная работа сводится к выполнению инструкции CPUID и,
|
||
возможно, проверке некоторых файлов в +/sys+ и +/proc+. Основная трудность
|
||
здесь~--- точно знать строки, которые нужно искать. Список таких строк
|
||
необходимо поддерживать в актуальном состоянии. В настоящий момент, systemd
|
||
определяет следующие механизмы виртуализации:
|
||
\begin{itemize}
|
||
\item Полная виртуализация (т.е. виртуальные машины):
|
||
\begin{itemize}
|
||
\item qemu
|
||
\item kvm
|
||
\item vmware
|
||
\item microsoft
|
||
\item oracle
|
||
\item xen
|
||
\item bochs
|
||
\end{itemize}
|
||
\item Виртуализация на уровне ОС (т.е. контейнеры):
|
||
\begin{itemize}
|
||
\item chroot
|
||
\item openvz
|
||
\item lxc
|
||
\item lxc-libvirt
|
||
\item \hyperref[sec:chroots]{systemd-nspawn}
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
Рассмотрим, как можно использовать эту функциональность.
|
||
|
||
\subsection{Условия на запуск юнитов}
|
||
|
||
При помощи опции
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}{ConditionVirtualization},
|
||
добавленной в секцию +[Unit]+ файла конфигурации юнита, вы можете обеспечить
|
||
запуск (или наоборот, отмену запуска) данного юнита в зависимости от того,
|
||
работает ли он на виртуальной системе, или нет. В случае утвердительного ответа,
|
||
также можно уточнить, какая система виртуализации при этом используется.
|
||
Например:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Name=My Foobar Service (runs only only on guests)
|
||
ConditionVirtualization=yes
|
||
|
||
[Service]
|
||
ExecStart=/usr/bin/foobard
|
||
\end{Verbatim}
|
||
|
||
Помимо <<+yes+>> или <<+no+>>, вы также можете указать идентификатор конкретной
|
||
системы виртуализации (согласно списку выше, например, <<+kvm+>>, <<+vmware+>> и
|
||
т.д.), либо <<+container+>> или <<+vm+>> (что позволит отличить виртуализацию на
|
||
уровне ОС от полной виртуализации). Кроме того, вы можете добавить перед
|
||
значением восклицательный знак, и результат проверки будет инвертирован (юнит
|
||
запустится только в том случае, если указанная технология
|
||
\emph{не}~используется). Подробности вы можете узнать на
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd.unit.html}{странице
|
||
руководства}.
|
||
|
||
\subsection{В скриптах}
|
||
|
||
В скриптах оболочки вы можете выполнить аналогичные проверки при помощи утилиты
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd-detect-virt.html}{systemd-detect-virt(1)}.
|
||
Например:
|
||
\begin{Verbatim}
|
||
if systemd-detect-virt -q ; then
|
||
echo "Virtualization is used:" `systemd-detect-virt`
|
||
else
|
||
echo "No virtualization is used."
|
||
fi
|
||
\end{Verbatim}
|
||
|
||
Эта утилита возвращает код 0 (успех), обнаружив виртуализацию, или ненулевое
|
||
значение, если виртуализация не~выявлена. Также она выводит идентификатор
|
||
обнаруженной системы виртуализации (согласно списку выше), если это не~было
|
||
запрещено опцией +-q+. Кроме того, опции +-c+ и +-v+ позволяют ограничить
|
||
проверки только механизмами виртуализации на уровне ОС, либо полной
|
||
виртуализации, соответственно. Подробности см. на
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-detect-virt.html}{странице
|
||
руководства}.
|
||
|
||
\subsection{В программах}
|
||
|
||
Информация о виртуализации также представлена на системной шине:
|
||
\begin{Verbatim}
|
||
$ gdbus call --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1 \
|
||
> --method org.freedesktop.DBus.Properties.Get org.freedesktop.systemd1.Manager Virtualization
|
||
(<'systemd-nspawn'>,)
|
||
\end{Verbatim}
|
||
|
||
Если виртуализация не~выявлена, это свойство содержит пустую строку. Обратите
|
||
внимание, что некоторые контейнерные системы не~могут быть обнаружены напрямую
|
||
из непривилегированного кода. Именно поэтому мы не~стали создавать библиотеку, а
|
||
воспользовались шиной D-Bus, которая позволяет корректно решить проблему
|
||
привилегий.
|
||
|
||
Стоит отметить, что все эти инструменты определяют только <<самый внутренний>>
|
||
из задействованных механизмов виртуализации. Если вы используете несколько
|
||
систем, вложенных друг в друга, вышеописанные инструменты обнаружат только ту, в
|
||
которой они непосредственно запущены. В частности, если они работают в
|
||
контейнере, находящемся внутри виртуальной машины, они увидят только контейнер.
|
||
|
||
\section{Сокет-активация служб и контейнеров}
|
||
|
||
\href{http://0pointer.de/blog/projects/socket-activation.html}{Сокет}-%
|
||
\href{http://0pointer.de/blog/projects/socket-activation2.html}{активация}~---
|
||
это одна из наиболее интересных возможностей systemd. Еще в
|
||
\href{http://0pointer.de/blog/projects/systemd.html}{первом анонсе} нашего
|
||
проекта мы рассказывали о том, как этот механизм улучшает
|
||
параллелизацию и отказоустойчивость использующих сокеты служб, а также упрощает
|
||
формирование зависимостей между службами при загрузке. В данной статье я
|
||
продолжу рассказ о его возможностях~--- на этот раз речь пойдет об увеличении
|
||
числа служб и контейнеров, работающих на одной и той же системе, без повышения
|
||
потребления ресурсов. Другими словами: как можно увеличить количество клиентских
|
||
сайтов на хостинговых серверах без затрат на новое оборудование.
|
||
|
||
\subsection{Сокет-активация сетевых служб}
|
||
|
||
Начнем с небольшого отступления. Итак, что же такое сокет-активация, и как она
|
||
работает? На самом деле все довольно просто. systemd создает <<слушающие>>
|
||
сокеты (не~обязательно IP) от имени вашей службы (которая пока не~запущена) и
|
||
ожидает входящие соединения. Как только в сокет поступает первый запрос, systemd
|
||
запускает службу и передает ей полученные данные. После обработки запроса, в
|
||
зависимости от реализации службы, она может продолжать работу, ожидая новых
|
||
соединений, или завершиться, переложив эту задачу обратно на systemd (который
|
||
вновь запустит ее при поступлении очередного запроса). При этом, со стороны
|
||
клиента невозможно отличить, когда служба запущена, а когда~--- нет. Сокет
|
||
постоянно остается открытым для входящих соединений, и все они обрабатываются
|
||
быстро и корректно.
|
||
|
||
Такая конфигурация позволяет снизить потребление системных ресурсов: службы
|
||
работают и потребляют ресурсы только тогда, когда это действительно необходимо.
|
||
Многие интернет-сайты и службы могут использовать это с выгодой для себя.
|
||
Например, хостеры веб-сайтов знают, что из огромного количества существующих в
|
||
Интернете сайтов лишь малая часть получает непрерывный поток запросов.
|
||
Большинство же сайтов, хотя и должны постоянно оставаться доступными, получают
|
||
запросы очень редко. Используя сокет-активацию, вы можете воспользоваться этим:
|
||
разместив множество таких сайтов на одной системе и активируя их службы только
|
||
при необходимости, вы получаете возможность <<оверкоммита>>: ваша система будет
|
||
обслуживать сайтов больше, чем формально позволяют ее ресурсы\footnote{Прим.
|
||
перев.: Стоит отметить, что подобная схема работает только при условии, что для
|
||
каждого клиентского сайта запускаются отдельные процессы служб, хотя это и
|
||
происходит в рамках одного хоста. Не~очень распространенный в отчественном
|
||
хостинге вариант: обычно следующей опцией после shared-хостинга (одна служба на
|
||
всех клиентов) идет VPS (каждому клиенту по виртуальному хосту).}. Разумеется,
|
||
увлекаться оверкоммитом не~стоит, иначе в моменты пиковой нагрузки ресурсов
|
||
может действительно не~хватить.
|
||
|
||
С помощью systemd вы без труда можете организовать такую схему. Множество
|
||
современных сетевых служб уже поддерживают сокет-активацию <<из коробки>> (а в
|
||
те, которые пока не~поддерживают, ее
|
||
\href{http://0pointer.de/blog/projects/socket-activation.html}{не~так уж} и
|
||
\href{http://0pointer.de/blog/projects/socket-activation2.html}{сложно}
|
||
добавить\footnote{Прим. перев.: Начиная с версии 209, в состав systemd входит
|
||
утилита
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html}%
|
||
{systemd-socket-proxyd}, позволяющая использовать сокет-активацию даже для
|
||
служб, которые ее не~поддерживают.}). Реализованный в systemd механизм управления
|
||
\hyperref[sec:instances]{экземплярами служб} позволяет подготовить
|
||
универсальные шаблоны конфигурации служб, и в соответствии с ними для каждого
|
||
сайта будет запускаться свой экземпляр службы. Кроме того, не~стоит забывать,
|
||
что systemd предоставляет \hyperref[sec:security]{внушительный арсенал}
|
||
механизмов обеспечения безопасности и разграничения доступа, который позволит
|
||
изолировать клиентские сайты друг от друга (например, службы каждого клиента
|
||
будут видеть только его собственный домашний каталог, в то время как каталоги
|
||
всех остальных пользователей будут им недоступны). Итак, в конечном итоге вы
|
||
получаете надежную и масштабируемую серверную систему, на сравнительно небольших
|
||
ресурсах которой функционирует множество безопасно изолированных друг от друга
|
||
служб~--- и все это реализовано штатными возможностями вашей ОС\footnote{Прим.
|
||
перев.: В качестве практического примера использования сокет-активации служб
|
||
systemd в промышленных серверных платформах, можно предложить
|
||
\href{http://savanne.be/articles/deploying-node-js-with-systemd/}{вот эту
|
||
статью}, наглядно описывающую применение этой и других технологий (мониторинг,
|
||
использование Journal, ограничение ресурсов и доступа) на примере Node.js.}.
|
||
|
||
Подобные конфигурации уже используются на рабочих серверах ряда компаний. В
|
||
частности, специалисты из \href{https://www.getpantheon.com/}{Pantheon}
|
||
используют такую схему для обслуживания масштабируемой инфраструктуры множества
|
||
сайтов на базе Drupal. (Стоит упомянуть, что заслуга ее внедрения в компании
|
||
Pantheon принадлежит Дэвиду Штрауссу. Дэвид, ты крут!)
|
||
|
||
\subsection{Сокет-активация контейнеров}
|
||
|
||
Все вышеописанные технологии уже реализованы в ранее вышедших версиях systemd.
|
||
Если ваш дистрибутив поддерживает systemd, вы можете воспользоваться этими
|
||
механизмами прямо сейчас. А теперь сделаем шаг вперед: начиная с
|
||
systemd 197 (и, соответственно, с Fedora~19), мы добавили поддержку
|
||
сокет-активации \emph{виртуальных контейнеров} с полноценными ОС внутри. И
|
||
я считаю это действительно важным достижением.
|
||
|
||
Сокет-активация контейнеров работает следующим образом. Изначально, systemd
|
||
хост-системы слушает порты от имени контейнера (например, порт SSH, порт
|
||
веб-сервера и порт сервера СУБД). При поступлении на любой из этих портов
|
||
входящего запроса, systemd запускает контейнер и передает ему все его сокеты.
|
||
Внутри контейнера, еще один systemd (init гостевой системы) принимает эти
|
||
сокеты, после чего вступает в работу вышеописанная схема обычной сокет-активации
|
||
служб. При этом, SSH-сервер, веб-сервер и СУБД-сервер <<видят>> только ОС
|
||
контейнера~--- хотя они были активированы сокетами, созданными на хосте! Для
|
||
клиента все эти тонкости скрыты. Таким образом, мы получаем виртуальный
|
||
контейнер с собственной ОС, активируемый при поступлении входящего сетевого
|
||
соединения, причем совершенно прозрачно для клиента\footnote{Кстати говоря,
|
||
\href{https://plus.google.com/115547683951727699051/posts/cVrLAJ8HYaP}{это еще
|
||
один аргумент} в пользу важности быстрой загрузки для серверных систем.}.
|
||
|
||
Внутри контейнера функционирует полноценная ОС, причем ее дистрибутив
|
||
не~обязательно совпадает с дистрибутивом хост-системы. Например, вы можете
|
||
установить на хосте Fedora, и запускать на нем несколько контейнеров с Debian.
|
||
Контейнеры имеют собственные init-системы, собственные SSH-серверы, собственные
|
||
списки процессов и т.д., но при этом пользуются некоторыми механизмами ОС хоста
|
||
(например, управлением памятью).
|
||
|
||
К настоящему моменту сокет-активация контейнеров поддерживается лишь встроенным
|
||
в systemd простейшим контейнерным менеджером~---
|
||
\hyperref[sec:chroots]{systemd-nspawn}. Мы надеемся, что соответствующая
|
||
возможность вскоре появится и в
|
||
\href{http://libvirt.org/drvlxc.html}{libvirt-lxc}\footnote{Прим. перев.:
|
||
\href{https://www.redhat.com/archives/libvir-list/2013-July/msg00825.html}{Патчи},
|
||
реализующие эту возможность, были подготовлены и приняты разработчиками libvirt
|
||
во второй декаде июля 2013 г., и входят в libvirt начиная с версии~1.1.1.}. А
|
||
пока, за отсутствием альтернатив, рассмотрим использование этого механизма на
|
||
примере systemd-nspawn.
|
||
|
||
Начнем с установки файлов ОС контейнера в выбранный каталог. Детальное
|
||
рассмотрение этого вопроса выходит далеко за рамки нашего обсуждения, и
|
||
при том детально рассмотрено во многих статьях и руководствах. Поэтому
|
||
ограничусь лишь несколькими наиболее важными замечаниями. В частности, команда
|
||
для установки Fedora будет выглядеть следующим образом:
|
||
\begin{Verbatim}
|
||
$ yum --releasever=19 --nogpg --installroot=/srv/mycontainer/ --disablerepo='*' \
|
||
> --enablerepo=fedora install systemd passwd yum fedora-release vim-minimal
|
||
\end{Verbatim}
|
||
а для Debian~---
|
||
\begin{Verbatim}
|
||
$ debootstrap --arch=amd64 unstable /srv/mycontainer/
|
||
\end{Verbatim}
|
||
Также см. последние абзацы страницы руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html}{systemd-nspawn(1)}.
|
||
Стоит отметить, что в настоящее время реализация системного аудита в Linux
|
||
не~поддерживает виртуальные контейнеры, и ее включение в ядре хоста вызовет
|
||
множество ошибок при попытке запустить контейнер. Отключить ее можно добавлением
|
||
+audit=0+ в строку параметров загрузки ядра хоста.
|
||
|
||
Разумеется, внутри контейнера должен должен быть установлен systemd версии
|
||
не~ниже 197. Установить его и произвести другие необходимые настройки можно при
|
||
помощи того же \hyperref[sec:chroots]{systemd-nspawn} (пока что в режиме аналога
|
||
+chroot+, т.е. без параметра +-b+). После этого можно попробовать загрузить ОС
|
||
контейнера, используя systemd-nspawn уже с параметром +-b+.
|
||
|
||
Итак, ваш контейнер нормально загружается и работает. Подготовим для него
|
||
service-файл, при помощи которого systemd сможет запускать и останавливать
|
||
виртуальное окружение. Для этого, создадим на хост-системе файл
|
||
+/etc/systemd/system/mycontainer.service+ со следующим содержанием:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=My little container
|
||
|
||
[Service]
|
||
ExecStart=/usr/bin/systemd-nspawn -jbD /srv/mycontainer 3
|
||
KillMode=process
|
||
\end{Verbatim}
|
||
|
||
Теперь мы можем запускать и останавливать эту службу командами +systemctl start+
|
||
и +stop+. Однако, пока что мы не~можем войти в эту систему\footnote{Прим.
|
||
перев.: Ручной запуск на хосте соответствующей команды +systemd-nspawn -b+ во
|
||
время работы такой службы просто создаст \emph{еще один контейнер}. Хотя
|
||
корневой каталог у них один и тот же, пространства имен (например, списки
|
||
процессов) у каждого будут свои. Подключиться к работающему контейнеру можно при
|
||
помощи утилиты +nsenter+, которая включена в состав util-linux начиная с версии
|
||
2.23 (на момент написания исходной статьи этой утилиты еще не~существовало).}.
|
||
Чтобы исправить это упущение, настроим на контейнере SSH-сервер, причем таким
|
||
образом, что подключение к его порту активировало весь контейнер, а затем
|
||
активировало сервер, работающий внутри. Начнем с того, что прикажем хосту
|
||
слушать порт SSH для контейнера. Для этого создадим на хосте файл
|
||
+/etc/systemd/system/mycontainer.socket+:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=The SSH socket of my little container
|
||
|
||
[Socket]
|
||
ListenStream=23
|
||
\end{Verbatim}
|
||
|
||
После того, как мы запустим этот юнит командой +systemctl start+, systemd будет
|
||
слушать 23-й TCP-порт хоста. В примере выбран именной 23-й, потому что 22-й
|
||
скорее всего окажется занят SSH-сервером самого хоста. nspawn виртуализует
|
||
списки процессов и точек монтирования, но не~сетевые стеки, поэтому порты хоста
|
||
и гостей не~должны конфликтовать\footnote{Прим. перев.: До выхода 209 версии
|
||
systemd, возможности по виртуализации сети в +systemd-nspawn+ были весьма
|
||
ограниченными (поддерживалась только полная изоляция сети от хоста), что весьма
|
||
затрудняло использование описываемой технологии. Впрочем, опытные администраторы
|
||
легко могли найти обходные пути, например: присваивание хосту дополнительного
|
||
IP-адреса (безо всякой виртуализации, командой +ip addr add+) и привязка
|
||
слушающих сокетов к конкретным адресам (в параметре +ListenStream=+ сокет-файлов
|
||
и/или в директиве +ListenAddress+ файла +sshd_config+ хоста). Начиная с systemd
|
||
версии 209, +systemd-nspawn+ предоставляет довольно широкие возможности по
|
||
виртуализации сети: проброс сетевого интерфейса хоста в контейнер
|
||
(+--network-interface=+), а также создание в контейнере виртуального сетевого
|
||
интерфейса (+--network-veth+), связывающего его с хостом. Такой виртуальный
|
||
интерфейс при необходимости может быть объединен в мост с интерфейсами хоста
|
||
(+--network-bridge=+), что обеспечит доступ из контейнера к соответствующим
|
||
сегментам сети. Подробности см. на~странице руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html}{systemd-nspawn(1)}.}.
|
||
|
||
Пока что systemd, работающий внутри контейнера, не~знает, что делать с тем
|
||
сокетами, которые ему передает systemd хоста. Если вы попробуете подключиться к
|
||
порту 23, контейнер запустится, но сетевое соединение немедленно будет закрыто,
|
||
так как данному сокету не~соответствует пока никакой сервер. Давайте это
|
||
исправим!
|
||
|
||
Настройка сокет-активации службы SSH была детально рассмотрена
|
||
\hyperref[sec:inetd]{в одной из предыдущих статей}, поэтому ограничусь
|
||
приведением содержимого необходимых для этого конфигурационных файлов. Файл
|
||
настроек для сокета
|
||
(+/srv/mycontainer/etc/systemd/system/sshd.socket+)\footnote{Прим. перев.:
|
||
Обратите внимание на разницу между файлами конфигурации сокетов на хосте и
|
||
в контейнере. Внутри контейнера используются параметры +Accept=yes+ и
|
||
+StandardInput=socket+, которые не~задействованы на хосте. Это обусловлено тем
|
||
фактом, что внутри контейнера сокет-активация производится в стиле inetd (служба
|
||
получает только один сокет, причем замкнутый на ее потоки STDIN и STDOUT), в то
|
||
время как на стороне хоста используется штатный стиль активации systemd, при
|
||
котором запущенному процессу (в данном случае +systemd-nspawn+) просто
|
||
передаются открытые файловые дескрипторы (+fd+) всех сокетов. Одно из достоинств
|
||
такого подхода~--- возможность передавать сразу несколько сокетов, что позволяет
|
||
активировать внутри контейнера несколько серверов.}:
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=SSH Socket for Per-Connection Servers
|
||
|
||
[Socket]
|
||
ListenStream=23
|
||
Accept=yes
|
||
\end{Verbatim}
|
||
|
||
Соответствующий ему файл конфигурации службы \\
|
||
(+/srv/mycontainer/etc/systemd/system/sshd@.service+):
|
||
\begin{Verbatim}
|
||
[Unit]
|
||
Description=SSH Per-Connection Server for %I
|
||
|
||
[Service]
|
||
ExecStart=-/usr/sbin/sshd -i
|
||
StandardInput=socket
|
||
\end{Verbatim}
|
||
|
||
После чего, добавим наш сокет в зависимости к цели +sockets.target+, чтобы
|
||
systemd создавал его автоматически при загрузке контейнера\footnote{Прим.
|
||
перев.: Возиться вручную с командой +ln+ здесь совершенно необязательно. Можно
|
||
просто добавить в файл +sshd.socket+ секцию +[Install]+, содержащую параметр
|
||
+WantedBy=sockets.target+, после чего добавление правильного симлинка будет
|
||
выполняться куда более очевидной командой
|
||
+systemctl --root=/srv/mycontainer enable sshd.socket+.}:
|
||
\begin{Verbatim}
|
||
$ chroot /srv/mycontainer ln -s /etc/systemd/system/sshd.socket \
|
||
> /etc/systemd/system/sockets.target.wants/
|
||
\end{Verbatim}
|
||
|
||
Собственно, все. После того, как мы запустим на хосте юнит +mycontainer.socket+,
|
||
systemd начнет прослушивать TCP-порт 23. При подключении к этому порту, systemd
|
||
запустит контейнер и передаст сокет ему. Внутри контейнера свой systemd, в
|
||
соответствии с файлом +sshd.socket+, примет этот сокет и запустит для нашего
|
||
соединения экземпляр +sshd@.service+, что позволит нам залогиниться в контейнер
|
||
по SSH.
|
||
|
||
Если нам нужно запуститьунтри контейнера другие службы с сокет-активацией, мы
|
||
можем добавить в +mycontainer.socket+ дополнительные сокеты. Все они будут
|
||
прослушиваться, обращение к любому из них приведет к активации контейнера, и все
|
||
эти сокеты будут переданы контейнеру при его активации. Внутри контейнера они
|
||
будут обработаны соответствии с настройками имеющихся там сокет-юнитов. Те
|
||
сокеты, для которых соответствующих юнитов не~найдется, будут
|
||
закрыты\footnote{Прим. перев.: Стоит особо отметить, что описанная технология
|
||
работает только для служб, поддерживающих сокет-активацию в режимах inetd (все
|
||
классические inetd-службы, кроме встроенных) или systemd (обычно зависят от
|
||
библиотеки +libsystemd-daemon.so+, либо содержат в исходниках заголовочный файл
|
||
+sd-daemon.h+). Службы, которые сами открывают себе слушающий сокет и
|
||
не~содержат кода для приема уже открытого сокета, так активировать нельзя.}, а
|
||
те сокеты, которые будут настроены для прослушивания внутри контейнера, но
|
||
не~получены от хоста, будут активированы и доступны изнутри контейнера (а если
|
||
это сетевые или файловые unix-сокеты, то и извне).
|
||
|
||
Итак, давайте отступим чуть назад и полюбуемся на результаты наших трудов. Что
|
||
мы получили в итоге? Возможность настраивать на одном хосте множество
|
||
контейнеров с полноценными ОС внутри, причем контейнеры запускаются только по
|
||
запросу, что позволяет снизить потребление системных ресурсов и, соответственно,
|
||
увеличить количество контейнеров (по сравнению с принудительной их активацией
|
||
при загрузке хоста).
|
||
|
||
Разумеется, описанный подход работает только для контейнерной виртуализации, и
|
||
неприменим к полной, т.е. может быть использован только с технологиями наподобие
|
||
libvirt-lxc или nspawn, но не~c qemu/kvm или xen.
|
||
|
||
Если вы будете администрировать несколько таких контейнеров, вас наверняка
|
||
порадует одна из возможностей journal: при запуске на хосте утилиты +journalctl+
|
||
с ключом +-m+, она автоматически обнаружит журналы гостевых контейнеров и
|
||
объединит их вывод с выводом журнала хоста\footnote{\label{ftn:jrnmerge}Прим.
|
||
перев.: Этот трюк работает благодаря опции +-j+, которую мы передаем программе
|
||
+systemd-nspawn+ при запуске контейнера (см. файл юнита выше). В соответствии с
|
||
ней, в каталоге +/var/log/journal+ хоста создается символьная ссылку на
|
||
соответствующий каталог гостя. При этом, так как сообщения от гостей имеют
|
||
другие machine ID, journalctl хоста не~выводит их, если явно не~указать +-m+.
|
||
Подробности см. на страницах руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html}{systemd-nspawn(1)}
|
||
и
|
||
\href{http://www.freedesktop.org/software/systemd/man/journalctl.html}{journalctl(1)}.}.
|
||
Ловко, не~правда ли?
|
||
|
||
Необходимый минимум технологий для сокет-активации контейнеров, присутствует в
|
||
systemd, начиная с версии 197. Тем не~менее, наша работа в этой области еще
|
||
не~закончена, и в ближайшее время мы планируем доработать некоторые моменты.
|
||
Например, сейчас, даже если все серверные службы внутри контейнера
|
||
закончили обработку запросов и завершились, контейнер все равно продолжает
|
||
функционировать и потреблять ресурсы хоста. Мы просто обязаны реализовать
|
||
возможность автоматического завершения работы гостевой системы в такой ситуации.
|
||
Причем у нас уже есть готовые наработки в этой области~--- мы можем
|
||
задействовать уже существующую инфраструктуру, обеспечивающую автоматическое
|
||
засыпание/выключение ноутбука при отсутствии активных задач и пользователей.
|
||
|
||
Впрочем, пора закругляться, а то статья получается чересчур длинной. Надеюсь,
|
||
что вы смогли продраться через все эти длинные и скучные рассуждения о
|
||
виртуализации, сокетах, службах, различных ОС и прочем колдунстве. Также
|
||
надеюсь, что эта статья станет хорошей отправной точкой при конфигурировании
|
||
мощных и хорошо масштабируемых серверных систем. За дополнительной информацией
|
||
обращайтесь к документации или приходите на наш IRC-канал. Спасибо за внимание!
|
||
|
||
\section{Интеграция с контейнерами}
|
||
|
||
В последнее время, все большую популярность набирает тема использования
|
||
Linux-контейнеров, а вместе с ней на первый план выходят различные реализации
|
||
систем управления контейнерами, такие, как libvirt-lxc, LXC и Docker. В данной
|
||
статье я попробую рассказать о некоторых механизмах интеграции между systemd и
|
||
подобными системами, обеспечивающих унифицированное, <<бесшовное>>
|
||
управление службами, работающими на хосте и в его контейнерах.
|
||
|
||
В центре нашего внимания будут находиться контейнеры операционных систем (OS
|
||
containers), в которых запускается собственный процесс init. Работающая внутри
|
||
такого контейнера система во многом аналогична полноценной ОС, выполняющейся на
|
||
физическом компьютере\footnote{Прим. перев.: Этим они отличаются от контейнеров
|
||
приложений (apps containers), в которых запускается только само приложение и его
|
||
вспомогательные процессы. Стоит отметить, что из-за широкого использования
|
||
механизмов cgroups и namespaces в systemd, практически любой service-юнит можно
|
||
превратить в контейнер приложения при помощи ряда настроек в его
|
||
конфигурационном файле. См. главы~\ref{sec:chroots}, \ref{sec:security},
|
||
\ref{sec:resources}.}. Б\'{о}льшая часть описанного в данной статье применима ко
|
||
всем программам для управления контейнерами, разработанным с учетом
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd/ContainerInterface/}{рекомендаций}
|
||
по интеграции с systemd (в частности, такая интеграция обеспечена в libvirt-lxc).
|
||
Однако, для простоты изложения, в практических примерах мы будем использовать
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html}{systemd-nspawn},
|
||
минималистичную утилиту для управления контейнерами, поставляемую в комплекте с
|
||
systemd. Она использует те же низкоуровневые механизмы, что и другие системы
|
||
управления контейнерами, но отличается предельной простотой использования, пусть
|
||
даже в ущерб гибкости, универсальности и настраиваемости. Мы сами активно
|
||
используем эту утилиту при разработке и тестировании systemd.
|
||
|
||
Итак, начнем. Первой нашей задачей является развертывание в отдельном каталоге
|
||
образа гостевой операционной системы:
|
||
\begin{Verbatim}
|
||
# yum -y --releasever=20 --nogpg --installroot=/srv/mycontainer \
|
||
> --disablerepo='*' --enablerepo=fedora \
|
||
> install systemd passwd yum fedora-release vim-minimal
|
||
\end{Verbatim}
|
||
|
||
Эта команда загрузит пакеты, необходимых для создания минимального образа
|
||
Fedora~20, и установит их в каталог +/srv/mycontainer+. Аналогичные команды
|
||
существуют и для других дистрибутивов, в частности, некоторые из них приведены в
|
||
разделе примеров страницы руководства
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html\#Examples}{systemd-nspawn(1)}.
|
||
|
||
Наш контейнер практически готов, осталось лишь задать для него пароль +root+:
|
||
\begin{Verbatim}
|
||
# systemd-nspawn -D /srv/mycontainer
|
||
Spawning container mycontainer on /srv/mycontainer
|
||
Press ^] three times within 1s to kill container.
|
||
-bash-4.2# passwd
|
||
Changing password for user root.
|
||
New password:
|
||
Retype new password:
|
||
passwd: all authentication tokens updated successfully.
|
||
-bash-4.2# ^D
|
||
Container mycontainer exited successfully.
|
||
#
|
||
\end{Verbatim}
|
||
|
||
Мы использовали systemd-nspawn в <<режиме chroot>>, чтобы получить
|
||
командную оболочку внутри контейнера (не~запуская гостевую систему целиком), и из этой
|
||
оболочки задать пароль +root+. На этом предварительная настройка заканчивается.
|
||
Теперь мы можем загрузить ОС внутри контейнера и войти в нее, используя заданный
|
||
нами пароль:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ systemd-nspawn -D /srv/mycontainer -b
|
||
Spawning container mycontainer on /srv/mycontainer.
|
||
Press ^] three times within 1s to kill container.
|
||
systemd 208 running in system mode. (+PAM +LIBWRAP +AUDIT +SELINUX +IMA +SYSVINIT +LIBCRYPTSETUP +GCRYPT +ACL +XZ)
|
||
Detected virtualization 'systemd-nspawn'.
|
||
|
||
Welcome to Fedora 20 (Heisenbug)!
|
||
|
||
[ OK ] Reached target Remote File Systems.
|
||
[ OK ] Created slice Root Slice.
|
||
[ OK ] Created slice User and Session Slice.
|
||
[ OK ] Created slice System Slice.
|
||
[ OK ] Created slice system-getty.slice.
|
||
[ OK ] Reached target Slices.
|
||
[ OK ] Listening on Delayed Shutdown Socket.
|
||
[ OK ] Listening on /dev/initctl Compatibility Named Pipe.
|
||
[ OK ] Listening on Journal Socket.
|
||
Starting Journal Service...
|
||
[ OK ] Started Journal Service.
|
||
[ OK ] Reached target Paths.
|
||
Mounting Debug File System...
|
||
Mounting Configuration File System...
|
||
Mounting FUSE Control File System...
|
||
Starting Create static device nodes in /dev...
|
||
Mounting POSIX Message Queue File System...
|
||
Mounting Huge Pages File System...
|
||
[ OK ] Reached target Encrypted Volumes.
|
||
[ OK ] Reached target Swap.
|
||
Mounting Temporary Directory...
|
||
Starting Load/Save Random Seed...
|
||
[ OK ] Mounted Configuration File System.
|
||
[ OK ] Mounted FUSE Control File System.
|
||
[ OK ] Mounted Temporary Directory.
|
||
[ OK ] Mounted POSIX Message Queue File System.
|
||
[ OK ] Mounted Debug File System.
|
||
[ OK ] Mounted Huge Pages File System.
|
||
[ OK ] Started Load/Save Random Seed.
|
||
[ OK ] Started Create static device nodes in /dev.
|
||
[ OK ] Reached target Local File Systems (Pre).
|
||
[ OK ] Reached target Local File Systems.
|
||
Starting Trigger Flushing of Journal to Persistent Storage...
|
||
Starting Recreate Volatile Files and Directories...
|
||
[ OK ] Started Recreate Volatile Files and Directories.
|
||
Starting Update UTMP about System Reboot/Shutdown...
|
||
[ OK ] Started Trigger Flushing of Journal to Persistent Storage.
|
||
[ OK ] Started Update UTMP about System Reboot/Shutdown.
|
||
[ OK ] Reached target System Initialization.
|
||
[ OK ] Reached target Timers.
|
||
[ OK ] Listening on D-Bus System Message Bus Socket.
|
||
[ OK ] Reached target Sockets.
|
||
[ OK ] Reached target Basic System.
|
||
Starting Login Service...
|
||
Starting Permit User Sessions...
|
||
Starting D-Bus System Message Bus...
|
||
[ OK ] Started D-Bus System Message Bus.
|
||
Starting Cleanup of Temporary Directories...
|
||
[ OK ] Started Cleanup of Temporary Directories.
|
||
[ OK ] Started Permit User Sessions.
|
||
Starting Console Getty...
|
||
[ OK ] Started Console Getty.
|
||
[ OK ] Reached target Login Prompts.
|
||
[ OK ] Started Login Service.
|
||
[ OK ] Reached target Multi-User System.
|
||
[ OK ] Reached target Graphical Interface.
|
||
|
||
Fedora release 20 (Heisenbug)
|
||
Kernel 3.18.0-0.rc4.git0.1.fc22.x86_64 on an x86_64 (console)
|
||
|
||
mycontainer login: root
|
||
Password:
|
||
-bash-4.2#
|
||
\end{Verbatim}
|
||
|
||
Наш испытательный стенд готов. Начнем рассмотрение механизмов интеграции systemd
|
||
и контейнеров с утилиты machinectl. Запустив ее без параметров, мы получим
|
||
список работающих в данный момент контейнеров:
|
||
\begin{Verbatim}
|
||
$ machinectl
|
||
MACHINE CONTAINER SERVICE
|
||
mycontainer container nspawn
|
||
|
||
1 machines listed.
|
||
\end{Verbatim}
|
||
|
||
Команда +machinectl status+ позволяет получить детальную информацию о выбранном
|
||
контейнере:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ machinectl status mycontainer
|
||
mycontainer:
|
||
Since: Mi 2014-11-12 16:47:19 CET; 51s ago
|
||
Leader: 5374 (systemd)
|
||
Service: nspawn; class container
|
||
Root: /srv/mycontainer
|
||
Address: 192.168.178.38
|
||
10.36.6.162
|
||
fd00::523f:56ff:fe00:4994
|
||
fe80::523f:56ff:fe00:4994
|
||
OS: Fedora 20 (Heisenbug)
|
||
Unit: machine-mycontainer.scope
|
||
├─5374 /usr/lib/systemd/systemd
|
||
└─system.slice
|
||
├─dbus.service
|
||
│ └─5414 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-act...
|
||
├─systemd-journald.service
|
||
│ └─5383 /usr/lib/systemd/systemd-journald
|
||
├─systemd-logind.service
|
||
│ └─5411 /usr/lib/systemd/systemd-logind
|
||
└─console-getty.service
|
||
└─5416 /sbin/agetty --noclear -s console 115200 38400 9600
|
||
\end{Verbatim}
|
||
В частности, она отображает дерево контрольных групп и процессов контейнера, его
|
||
IP-адреса и корневой каталог.
|
||
|
||
Команда +machinectl login+ позволяет войти внутрь работающего контейнера,
|
||
запустив в нем еще одну командную оболочку:
|
||
\begin{Verbatim}
|
||
# machinectl login mycontainer
|
||
Connected to container mycontainer. Press ^] three times within 1s to exit session.
|
||
|
||
Fedora release 20 (Heisenbug)
|
||
Kernel 3.18.0-0.rc4.git0.1.fc22.x86_64 on an x86_64 (pts/0)
|
||
|
||
mycontainer login:
|
||
\end{Verbatim}
|
||
|
||
Команда +machinectl reboot+ перезагружает контейнер:
|
||
\begin{Verbatim}
|
||
# machinectl reboot mycontainer
|
||
\end{Verbatim}
|
||
|
||
Команда +machinectl poweroff+ выключает контейнер:
|
||
\begin{Verbatim}
|
||
# machinectl poweroff mycontainer
|
||
\end{Verbatim}
|
||
|
||
Пожалуй, этих минимальных сведений о machinectl должно быть достаточно. Стоит
|
||
заметить, что ее функциональность не~ограничиваются приведенными здесь командами.
|
||
За подробностями рекомендуем обратиться
|
||
к~\href{http://www.freedesktop.org/software/systemd/man/machinectl.html}{странице
|
||
руководства}. Еще раз отметим, что данные команды применимы не~только к
|
||
контейнерам на базе systemd-nspawn, но и к другим системам управления
|
||
контейнерами, реализованным с учетом
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd/writing-vm-managers/}{наших
|
||
рекомендаций}, в частности, libvirt-lxc.
|
||
|
||
Помимо machinectl, в systemd существует и ряд других инструментов, упрощающих
|
||
работу с контейнерами. В частности, большинство управляющих утилит systemd имеют
|
||
встроенную поддержку контейнеров: при помощи ключа +-M+ вы можете указать имя
|
||
нужного вам контейнера, и соответствующая утилита будет работать так, как будто
|
||
запущена внутри этого контейнера\footnote{Прим. перев.: Поддержка данной опции
|
||
добавлена в systemctl, journalctl, loginctl, machinectl, hostnamectl,
|
||
timedatectl, localectl, busctl, systemd-analyze, systemd-run начиная с systemd
|
||
209, в systemd-cgls~--- с systemd 203.}. Например (не~забудьте снова запустить
|
||
наш тестовый контейнер, если вы его до этого выключили):
|
||
\begin{Verbatim}
|
||
# hostnamectl -M mycontainer set-hostname "wuff"
|
||
\end{Verbatim}
|
||
Приведенная здесь команда
|
||
\href{http://www.freedesktop.org/software/systemd/man/hostnamectl.html}{hostnamectl(1)}
|
||
устанавливает в локальном контейнере +mycontainer+ имя хоста <<+wuff+>>.
|
||
|
||
А вот пример использования того же ключа +-M+ в программе
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemctl.html}{systemctl(1)}:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# systemctl -M mycontainer
|
||
UNIT LOAD ACTIVE SUB DESCRIPTION
|
||
-.mount loaded active mounted /
|
||
dev-hugepages.mount loaded active mounted Huge Pages File System
|
||
dev-mqueue.mount loaded active mounted POSIX Message Queue File System
|
||
proc-sys-kernel-random-boot_id.mount loaded active mounted /proc/sys/kernel/random/boot_id
|
||
[...]
|
||
time-sync.target loaded active active System Time Synchronized
|
||
timers.target loaded active active Timers
|
||
systemd-tmpfiles-clean.timer loaded active waiting Daily Cleanup of Temporary Directories
|
||
|
||
LOAD = Reflects whether the unit definition was properly loaded.
|
||
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
|
||
SUB = The low-level unit activation state, values depend on unit type.
|
||
|
||
49 loaded units listed. Pass --all to see loaded but inactive units, too.
|
||
To show all installed unit files use 'systemctl list-unit-files'.
|
||
\end{Verbatim}
|
||
Как нетрудно догадаться, эта команда выводит список активных юнитов гостевой
|
||
системы контейнера. (Чтобы не~загромождать нашу статью лишним текстом, часть
|
||
листинга пропущена.)
|
||
|
||
Перезапустить службу внутри гостевой системы также не~представляет проблемы:
|
||
\begin{Verbatim}
|
||
# systemctl -M mycontainer restart systemd-resolved.service
|
||
\end{Verbatim}
|
||
|
||
Поддержка контейнеров в утилите +systemctl+ не~ограничивается ключом +-M+.
|
||
Она имеет еще и ключ +-r+ (рекурсивный режим)\footnote{Прим. перев.:
|
||
Поддержка этой опции при выводе списка юнитов появилась начиная с systemd~212,
|
||
при выводе списков таймеров и сокетов (+systemctl list-timers+ и
|
||
+systemctl list-sockets+)~--- начиная с systemd~213.}, позволяющий выводить
|
||
объединенный список юнитов для хоста и всех работающих гостевых систем:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# systemctl -r
|
||
UNIT LOAD ACTIVE SUB DESCRIPTION
|
||
boot.automount loaded active waiting EFI System Partition Automount
|
||
proc-sys-fs-binfmt_misc.automount loaded active waiting Arbitrary Executable File Formats File Syst
|
||
sys-devices-pci0000:00-0000:00:02.0-drm-card0-card0\x2dLVDS\x2d1-intel_backlight.device loaded active plugged /sys/devices/pci0000:00/0000:00:02.0/drm/ca
|
||
[...]
|
||
timers.target loaded active active Timers
|
||
mandb.timer loaded active waiting Daily man-db cache update
|
||
systemd-tmpfiles-clean.timer loaded active waiting Daily Cleanup of Temporary Directories
|
||
mycontainer:-.mount loaded active mounted /
|
||
mycontainer:dev-hugepages.mount loaded active mounted Huge Pages File System
|
||
mycontainer:dev-mqueue.mount loaded active mounted POSIX Message Queue File System
|
||
[...]
|
||
mycontainer:time-sync.target loaded active active System Time Synchronized
|
||
mycontainer:timers.target loaded active active Timers
|
||
mycontainer:systemd-tmpfiles-clean.timer loaded active waiting Daily Cleanup of Temporary Directories
|
||
|
||
LOAD = Reflects whether the unit definition was properly loaded.
|
||
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
|
||
SUB = The low-level unit activation state, values depend on unit type.
|
||
|
||
191 loaded units listed. Pass --all to see loaded but inactive units, too.
|
||
To show all installed unit files use 'systemctl list-unit-files'.
|
||
\end{Verbatim}
|
||
|
||
Как видно из листинга, сначала выводится список юнитов хоста, после которого
|
||
идет список юнитов нашего контейнера. Перед именами юнитов гостевой системы,
|
||
через двоеточие, указывается имя контейнера. (По традиции, листинг сокращен.)
|
||
|
||
Команда +systemctl list-machines+ опрашивает процессы init всех работающих
|
||
контейнеров, и по результатам выводит список работающих систем
|
||
(хостовой и всех гостевых), с указанием их текущего состояния и наличия
|
||
ошибок загрузки.
|
||
\begin{Verbatim}
|
||
# systemctl list-machines
|
||
NAME STATE FAILED JOBS
|
||
delta (host) running 0 0
|
||
mycontainer running 0 0
|
||
miau degraded 1 0
|
||
waldi running 0 0
|
||
|
||
4 machines listed.
|
||
\end{Verbatim}
|
||
|
||
Для большей наглядности мы запустили еще два контейнера. В одном из них
|
||
произошла ошибка запуска юнита, поэтому его состояние характеризуется как
|
||
<<+degraded+>>.
|
||
|
||
А теперь обсудим поддержку контейнеров в утилите
|
||
\href{http://www.freedesktop.org/software/systemd/man/journalctl.html}{journalctl(1)}.
|
||
Во-первых, она позволяет использовать описанную выше опцию +-M+:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# journalctl -M mycontainer -n 8
|
||
Nov 12 16:51:13 wuff systemd[1]: Starting Graphical Interface.
|
||
Nov 12 16:51:13 wuff systemd[1]: Reached target Graphical Interface.
|
||
Nov 12 16:51:13 wuff systemd[1]: Starting Update UTMP about System Runlevel Changes...
|
||
Nov 12 16:51:13 wuff systemd[1]: Started Stop Read-Ahead Data Collection 10s After Completed Startup.
|
||
Nov 12 16:51:13 wuff systemd[1]: Started Update UTMP about System Runlevel Changes.
|
||
Nov 12 16:51:13 wuff systemd[1]: Startup finished in 399ms.
|
||
Nov 12 16:51:13 wuff sshd[35]: Server listening on 0.0.0.0 port 24.
|
||
Nov 12 16:51:13 wuff sshd[35]: Server listening on :: port 24.
|
||
\end{Verbatim}
|
||
|
||
Во-вторых, стоит упомянуть опцию +-m+ (режим слияния), выводящую объединенный
|
||
поток журнальных записей хоста и всех его гостевых систем\footnote{Прим. перев.:
|
||
Чтобы данная опция действительно работала именно так, необходимо, чтобы в
|
||
каталоге +/var/log/journal+ хоста присутствовали символьные ссылки на каталоги с
|
||
логами гостевых систем, либо было выполнено их bind-монтирование. См.
|
||
примечание~\ref{ftn:jrnmerge}. Стоит заметить что, помимо логов контейнеров, в
|
||
этом каталоге могут оказаться и логи других компьютеров, если у вас настроен их
|
||
сбор с помощью
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-journal-remote.html}{systemd-journal-remote(8)}
|
||
или иных инструментов.}.:
|
||
\begin{Verbatim}
|
||
# journalctl -m -e
|
||
\end{Verbatim}
|
||
(Здесь вывод команды опущен полностью. Думаю, вам будет несложно представить
|
||
себе, как он выглядит.)
|
||
|
||
Поддержка контейнеров присутствует не~только в утилитах systemd, но и в
|
||
программах из комплекта procps\footnote{Прим. перев.: Начиная с выпуска procps
|
||
3.3.8.}:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# ps -eo pid,machine,args
|
||
PID MACHINE COMMAND
|
||
1 - /usr/lib/systemd/systemd --switched-root --system --deserialize 20
|
||
[...]
|
||
2915 - emacs contents/projects/containers.md
|
||
3403 - [kworker/u16:7]
|
||
3415 - [kworker/u16:9]
|
||
4501 - /usr/libexec/nm-vpnc-service
|
||
4519 - /usr/sbin/vpnc --non-inter --no-detach --pid-file /var/run/NetworkManager/nm-vpnc-bfda8671-f025-4812-a66b-362eb12e7f13.pid -
|
||
4749 - /usr/libexec/dconf-service
|
||
4980 - /usr/lib/systemd/systemd-resolved
|
||
5006 - /usr/lib64/firefox/firefox
|
||
5168 - [kworker/u16:0]
|
||
5192 - [kworker/u16:4]
|
||
5193 - [kworker/u16:5]
|
||
5497 - [kworker/u16:1]
|
||
5591 - [kworker/u16:8]
|
||
5711 - sudo -s
|
||
5715 - /bin/bash
|
||
5749 - /home/lennart/projects/systemd/systemd-nspawn -D /srv/mycontainer -b
|
||
5750 mycontainer /usr/lib/systemd/systemd
|
||
5799 mycontainer /usr/lib/systemd/systemd-journald
|
||
5862 mycontainer /usr/lib/systemd/systemd-logind
|
||
5863 mycontainer /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
|
||
5868 mycontainer /sbin/agetty --noclear --keep-baud console 115200 38400 9600 vt102
|
||
5871 mycontainer /usr/sbin/sshd -D
|
||
6527 mycontainer /usr/lib/systemd/systemd-resolved
|
||
[...]
|
||
\end{Verbatim}
|
||
Здесь приводится (сокращенный) список процессов хоста и всех гостевых систем.
|
||
Во втором столбце указывается имя контейнера (<<+-+>> соответствует системе
|
||
хоста).
|
||
|
||
Но наша работа по поддержке взаимодействия с контейнерами этим
|
||
не~ограничивается. В частности, мы добавили такую поддержку в <<sd-bus>>~---
|
||
разрабатываемую нами клиентскую библиотеку D-Bus/kdbus. Если вызов
|
||
+sd_bus_open_system()+ позволяет подключаться к шине вашей локальной системы, то
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/sd_bus_open_system_container.html}{sd\_bus\_open\_system\_container()}
|
||
открывает соединение к шине выбранного гостевого контейнера, обеспечивая прямой
|
||
доступ к ее интерфейсам и методам.
|
||
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/sd_pid_get_machine_name.html}{sd-login.h}
|
||
и \href{http://www.freedesktop.org/wiki/Software/systemd/machined/}{D-Bus
|
||
интерфейс демона machined} предоставляют API для реализации поддержки
|
||
контейнеров в сторонних программах. В частности, они содержат функции для
|
||
определения имени контейнера по PID процесса, получения списка работающих
|
||
контейнеров и т.д.
|
||
|
||
systemd-networkd также имеет встроенную поддержку контейнеров. Когда он
|
||
запускается внутри контейнера, он автоматически задействует DHCP-клиент и
|
||
выполняет настройку IPv4LL-адреса\footnote{Прим. перев.: Механизм IPv4LL
|
||
предоставляет простой метод автоматической настройки сетевых адресов в пределах
|
||
одного сегмента. Сетевому интерфейсу присваивается случайно выбранный незанятый
|
||
IP-адрес из специально зарезервированного диапазона 162.254.0.0/16.} на
|
||
виртуальном сетевом интерфейсе с именем +host0+ (имя и назначение этого
|
||
интерфейса оговаривается в
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd/ContainerInterface/}{наших
|
||
рекомендациях}, которые должны поддерживаться вашей системой управления
|
||
контейнерами). Когда networkd работает на хост-системе, он
|
||
предоставляет функции DHCP-сервера и настраивает IPv4LL-адреса на всех
|
||
виртуальных интерфейсах с именами вида +ve-имя_контейнера+.
|
||
|
||
И наконец, последний из рассматриваемых в этой статье аспектов интеграции
|
||
systemd и контейнеров: взаимодействие с NSS. В составе свежих версий
|
||
systemd\footnote{Прим. перев.: Начиная с версии 216.} поставляется NSS-модуль
|
||
nss-mymachines, позволяющий преобразовывать имена контейнеров в их IP-адреса
|
||
при помощи вызовов
|
||
\hreftt{http://man7.org/linux/man-pages/man3/gethostbyname.3.html}{gethostbyname()}
|
||
и
|
||
\hreftt{http://man7.org/linux/man-pages/man3/getaddrinfo.3.html}{getaddrinfo()}.
|
||
Разумеется, это применимо лишь к контейнерам, которые имеют собственное сетевое
|
||
пространство имен (netns), т.е. свой сетевой стек. Команда systemd-nspawn,
|
||
приведенная выше, запускает контейнер в сетевом стеке хоста, поэтому, чтобы
|
||
продемонстрировать работу обсуждаемых механизмов, нам придется перезапустить
|
||
этот контейнер с отдельным сетевым стеком, в котором systemd-nspawn создаст
|
||
виртуальный сетевой интерфейс (+veth+), связывающий контейнер с хостом:
|
||
\begin{Verbatim}
|
||
# machinectl poweroff mycontainer
|
||
# systemd-nspawn -D /srv/mycontainer --network-veth -b
|
||
\end{Verbatim}
|
||
|
||
Теперь, если в контейнере и на хосте работает systemd-networkd, мы можем
|
||
обращаться к контейнеру через сеть просто по его имени, которое при помощи
|
||
модуля nss-mymachines будет автоматически преобразовано в его
|
||
IP-адрес\footnote{Прим. перев.: Для корректной работы модуля nss-mymachines,
|
||
он должен быть указан в конфигурационном файле +/etc/nsswitch.conf+, в строке
|
||
hosts. В частности, в поставляемом с systemd варианте этого файла, данная
|
||
строчка выглядит так: <<+hosts: files mymachines resolve myhostname+>>. Помимо
|
||
mymachines, в ней фигурируют и другие NSS-модули systemd: resolve отвечает
|
||
за взаимодействие с кэширующим DNS-сервером systemd-resolved, а myhostname
|
||
обеспечивает корректное преобразование системного имени хоста в IP-адрес
|
||
127.0.0.2, даже при отсутствии соответствующей записи в +/etc/hosts+.}:
|
||
\begin{Verbatim}
|
||
# ping mycontainer
|
||
PING mycontainer (10.0.0.2) 56(84) bytes of data.
|
||
64 bytes from mycontainer (10.0.0.2): icmp_seq=1 ttl=64 time=0.124 ms
|
||
64 bytes from mycontainer (10.0.0.2): icmp_seq=2 ttl=64 time=0.078 ms
|
||
\end{Verbatim}
|
||
|
||
Разумеется, преобразование имен контейнеров в IP-адреса и обратно работает
|
||
не~только для утилиты +ping+, но и для всех остальных программ, использующих
|
||
функции libc +gethostbyname()+ и +getaddrinfo()+, в том числе и для нашей
|
||
любимой и незаменимой +ssh+.
|
||
|
||
Вот и все, о чем я хотел рассказать в данной статье. Мы кратко прошлись по
|
||
основным моментам интеграции systemd и контейнеров. Изложение не~претендует на
|
||
полноту, и очень многие детали оказались <<за бортом>>~--- о них вы можете
|
||
узнать из документации. Кроме того, в настоящее время мы продолжаем работу над
|
||
более тесной интеграцией с контейнерами, так что ожидайте новых возможностей в
|
||
этой области с ближайшими выпусками systemd.
|
||
|
||
В завершение стоит отметить, что концепция машины (machine) применима не~только
|
||
к контейнерам, но и, в некоторой степени, к полноценным виртуальным машинами.
|
||
Однако, в случае виртуальных машин, доступ к гостевым системам с хоста
|
||
реализуется несколько сложнее, чем в контейнерах~--- вместо прямого доступа к
|
||
системным вызовам, приходится использовать взаимодействие через
|
||
сеть\footnote{Прим. перев.: Можно предположить, что здесь автор пытается
|
||
намекнуть на опцию +-H [user@]host[:container]+, позволяющую обращаться к
|
||
другим хостам и их контейнерам через сеть при помощи SSH-транспорта,
|
||
реализованного в шине D-Bus. Эта опция поддерживается в большинстве управляющих
|
||
утилит systemd (systemctl, loginctl, machinectl, hostnamectl, timedatectl,
|
||
localectl, busctl, systemd-analyze, systemd-run).}.
|
||
|
||
В любом случае, надеюсь, что статья для кого-то оказалась полезной. За
|
||
подробностями обращайтесь к документации (начать изучение темы можно со ссылок,
|
||
приведенных в этой статье).
|
||
|
||
\sectiona{Защита от уязвимостей \texttt{AF\_PACKET} при помощи
|
||
systemd\sfnote{Эта и последующие статьи официально не входят в цикл <<systemd
|
||
для администраторов>>, и поэтому не~имеют порядковых номеров. Однако они сходны
|
||
по тематике с остальными статьями цикла и несут довольно много полезной
|
||
информации, и поэтому их перевод все же присутствует в данном документе.}}
|
||
|
||
Небольшая заметка о том, как ограничительные механизмы, встроенные в последние
|
||
версии systemd, позволяют защитить ваши службы от эксплуатации
|
||
уязвимости \href{http://seclists.org/oss-sec/2016/q4/607}{CVE-2016-8655}%
|
||
\footnote{Прим. перев.: Решение перевести эту заметку связано с появлением
|
||
информации о целом ряде уязвимостей, связанных с +AF_PACKET+. Помимо уже
|
||
упомянутой автором CVE-2016-8655, это
|
||
\href{https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html}%
|
||
{CVE-2017-7308},
|
||
\href{http://seclists.org/oss-sec/2017/q3/279}{CVE-2017-10001111},
|
||
\href{http://seclists.org/oss-sec/2017/q3/476}{CVE-2017-14497}, и
|
||
\href{http://seclists.org/fulldisclosure/2017/Oct/44}{еще одна}, на момент
|
||
написания этих строк не~имеющая идентификатора CVE. Все они имеют схожую природу
|
||
(повышение привилегий до root путем манипуляций с сокетами +AF_PACKET+)
|
||
и успешно блокируются описанным в статье методом. Соответственно, был изменен и
|
||
заголовок статьи (в оригинале речь шла только о CVE-2016-8655).}.
|
||
|
||
Начиная с версии 211, в systemd добавлена поддержка новой директивы
|
||
для service-файлов~---
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RestrictAddressFamilies=}%
|
||
{RestrictAddressFamilies=}, которая отбирает у службы возможность создавать
|
||
сокеты определенного типа\footnote{Прим. перев.: Под <<типом сокета>> здесь
|
||
подразумевается address family. Не~вполне корректно, зато кратко.}. Достаточно
|
||
добавить в секцию +[Service]+ юнит-файла вашей службы строчку
|
||
+RestrictAddressFamilies=~AF_PACKET+, и служба утратит возможность создавать
|
||
сокеты типа +AF_PACKET+, в результате чего эксплуатация соответствующих
|
||
уязвимостей будет исключена. Приведенный пример демонстрирует режим <<черного
|
||
списка>>, когда указываются \emph{запрещенные} типы сокетов. Однако, более
|
||
безопасным подходом является <<белый список>> \emph{разрешенных} типов. В
|
||
отличие от черного, белый список указывается без символа +~+. Простой пример:
|
||
\begin{Verbatim}
|
||
...
|
||
[Service]
|
||
ExecStart=/usr/bin/mydaemon
|
||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||
...
|
||
\end{Verbatim}
|
||
Служба, настроенная таким образом, может использовать только сокеты типа
|
||
+AF_INET+, +AF_INET6+ и +AF_UNIX+, что должно быть достаточно для большинства
|
||
системных демонов (+AF_INET+~--- низкоуровневое название для IPv4,
|
||
+AF_INET6+~--- для IPv6, а +AF_UNIX+ соответствует локальным UNIX-сокетам).
|
||
|
||
\href{https://github.com/systemd/systemd/blob/8e458bfe4e2aa36c939db62561b2a59206d78577/NEWS#L45}%
|
||
{Начиная с systemd 232}, мы добавили директивы +RestrictAddressFamilies=+ в
|
||
юнит-файлы всех служб, входящих в состав systemd, указав минимально
|
||
необходимый набор типов сокетов для каждой службы.
|
||
|
||
С выходом systemd 233 мы
|
||
\href{https://github.com/systemd/systemd/pull/4536}{добавили} еще один метод
|
||
блокировки подобных уязвимостей. Директива
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RestrictNamespaces=}%
|
||
{RestrictNamespaces=} позволяет ограничить перечень пространств имен
|
||
(namespaces), которые может использовать служба\footnote{Прим. перев.:
|
||
Пространства имен~--- специфичная для ядра Linux технология контейнерной
|
||
изоляции, позволяющая группе процессов иметь свой собственный сетевой стек
|
||
(network namespace), иерархию монтирования (mount namespace, также использовался
|
||
термин filesystem namespace), иерархию контрольных групп (cgroup namespace),
|
||
очереди сообщений POSIX и интерфейсы SysV IPC (IPC namespace), имя хоста (UTS
|
||
namespace), независимые списки идентификаторов процессов (PID namespace) и
|
||
пользователей/групп (user namespace). Лежат в основе систем контейнерной
|
||
изоляции, таких как LXC и Docker. Также широко используются в systemd для
|
||
обеспечения безопасности~--- см. главы \ref{sec:chroots} и
|
||
\ref{sec:security}.}. В частности, +RestrictNamespaces=yes+ полностью отключает
|
||
доступ к пространствам имен. Перечисление, например
|
||
+RestrictNamespaces=net ipc+, позволяет задать список разрешенных к
|
||
использованию пространств имен (в нашем случае это пространства имен для сети и
|
||
IPC)\footnote{Прим. перев.: Как и в предыдущем случае, можно поменять белый
|
||
список на черный, добавив перед ним символ +~+.}. Учитывая, что механизм
|
||
пространства имен пользователей (user namespace) уже давно является перманентным
|
||
источником опасных уязвимостей\footnote{Прим. перев.: В частности, для создания
|
||
сокетов +AF_PACKET+ требуется наличие полномочий +CAP_NET_RAW+ (или прав рута),
|
||
отсутствующих у большинства программ. Однако, если система разрешает
|
||
непривилигерованным процессам создавать новое пространство имен
|
||
пользователей (+sysctl kernel.unprivileged_userns_clone=1+), то злоумышленник
|
||
может создать новое user namespace, в котором его процесс будет иметь
|
||
полномочия рута и, соответственно, сможет создать новое сетевое пространство
|
||
имен, и в нем сокет типа +AF_PACKET+, с помощью которого он попытается получить
|
||
полномочия рута уже в основной системе.}, возможно, будет правильным
|
||
заблокировать использование пространств имен для тех служб, которым они
|
||
не~требуются (а таких служб большинство).
|
||
|
||
Хочется надеяться, что в будущем сопровождающие дистрибутивов и апстримные
|
||
разработчики программ будут сами добавлять соответствующие ограничительные
|
||
директивы в поставляемые по умолчанию юнит-файлы, так как эти люди лучше знают,
|
||
какие именно привилегии минимально необходимы для функционирования их демонов.
|
||
|
||
\sectiona{Динамические пользователи}
|
||
|
||
\emph{Коротко о главном: теперь вы можете настроить systemd таким образом, чтобы
|
||
он автоматически выделял новый идентификатор пользователя (UID) для службы при
|
||
ее запуске, и автоматически освобождал его при остановке службы. Такой подход
|
||
абсолютно безопасен и может также применяться в сочетании с другими технологями
|
||
systemd, в частности, одноразовыми службами (transient services), службами с
|
||
активацией через сокет и экземплярами служб.}
|
||
|
||
В выпуске
|
||
\href{https://lists.freedesktop.org/archives/systemd-devel/2017-October/039589.html}%
|
||
{systemd 235}, помимо прочих улучшений, значительно расширена поддержка
|
||
\emph{динамических пользователей} (базовая функциональность для которой
|
||
появилась еще в версии 232). Это мощная и удобная, но пока малоизвестная
|
||
технология, и в данной статье я попытаюсь исправить последний недостаток,
|
||
рассказав о ней более подробно.
|
||
|
||
Понятие \emph{пользователя} является одной из ключевых концепций безопасности
|
||
POSIX-совместимых операционных систем. Большинство созданных впоследствии
|
||
механизмов обеспечения безопасности (SELinux и другие системы мандатного
|
||
контроля доступа, capabilities, пространства имен пользователей и т.д.) так или
|
||
иначе связаны или взаимодействуют с концепцией пользователя. Даже если вы хотите
|
||
пересобрать ядро Linux, полностью отключив все возможные механизмы безопасности,
|
||
от пользователей вы, скорее всего, не~избавитесь.
|
||
|
||
Изначально, понятие пользователя было введено при создании многопользовательских
|
||
систем, рассчитанных на одновременную работу нескольких
|
||
пользователей-\emph{людей}, обеспечивая взаимную изоляцию и разделение ресурсов.
|
||
Однако, большинство современных UNIX-систем используют концепцию пользователей
|
||
иначе: несмотря на то, что реальный пользователь у них может быть только один
|
||
(или вообще ни одного!), в их списках пользователей (то есть файлах
|
||
+/etc/passwd+), записей намного больше. В наше время, большинство пользователей
|
||
типичной UNIX-системы являются \emph{системными}~--- они соответствуют
|
||
не~сидящим перед компьютером живым людям, а наборам полномочий (security
|
||
identity), с которыми запускаются системные службы (т.е. программы). Хотя
|
||
традиционные многопользовательские системы постепенно теряют свою актуальность,
|
||
лежащее в их основе понятие <<пользователь>> стало одним из краеугольных камней
|
||
в технологиях защиты UNIX-систем. Большинство служб в современной системе
|
||
работает от имени своего пользователем, которому даны минимально возможные
|
||
полномочия.
|
||
|
||
Создатели ОС Android хорошо понимали значимость концепции пользователя для
|
||
безопасности системы, и пошли еще дальше: в Android пользователи создаются
|
||
не~только для системных служб, но и для каждого графического приложения, что
|
||
позволяет обеспечить разделение ресурсов и защиту процессов разных приложений
|
||
друг от друга.
|
||
|
||
Тем не~менее, в традиционных Linux-системах концепции пользователей пока
|
||
уделяется меньше внимания. Несмотря на то, что она являтся ключевой технологией
|
||
обеспечения безопасности UNIX-системы, механизмы создания пользователей и
|
||
управления ими пока не~обладают достаточной гибкостью. В большинстве случаев,
|
||
если вы устанавливаете службу, которая выполняется от своего пользователя,
|
||
работу по созданию учетных записей берут на себя установочные скрипты RPM или
|
||
DEB-пакетов. После этого, созданный пользователь останется в системе уже
|
||
навсегда, даже если вы удалите соответствующий пакет. Большинство дистрибутивов
|
||
Linux используют для идентификаторов системных пользователей диапазон от 0 до
|
||
1000~--- не~так уж и много. Следовательно, создание пользователей является
|
||
<<дорогой>> операцией: количество идентификаторов ограничено, и не~существует
|
||
механизма для их автоматического освобождения после использования. Если вы
|
||
будете активно использовать концепцию системных пользователей, вы рано или
|
||
поздно исчерпаете доступный лимит.
|
||
|
||
У вас может возникнуть вопрос: почему системные пользователи не~удаляются при
|
||
удалении создавшего их пакета (как минимум, в большинстве дистрибутивов)?
|
||
Причиной этому является одно из важных свойств концепции пользователя (его можно
|
||
даже назвать \emph{ошибкой дизайна}): идентификаторы пользователей
|
||
сохраняются в атрибутах файлов (и некоторых других объектов, в частности,
|
||
элементов IPC). Если служба, работающая из-под отдельного системного
|
||
пользователя, создаст где-либо новый файл, то даже после остановки службы и
|
||
удаления ее пакета, числовой идентификатор ее пользователя (UID) останется в
|
||
метаданных этого файла. Если бы наша система удаляла системных пользователей
|
||
вместе с их пакетами, то рано или поздно этот идентификатор заняла бы
|
||
какая-нибудь другая служба. И это уже можно считать уязвимостью, так как файл с
|
||
данными одной службы становится доступен для другой. Как следствие, дистрибутивы
|
||
склонны избегать повторного использования UID, и каждый созданный системный
|
||
пользователь остается в системе навсегда.
|
||
|
||
Выше мы рассматривали сложившуюся к настоящему моменту ситуацию. А теперь
|
||
посмотрим, что нового вносит наша концепция динамических пользователей, и какие
|
||
проблемы она может решить.
|
||
|
||
\subsectiona{Что такое \llquote{}динамический пользователь\rrquote{}?}
|
||
|
||
Реализованный в systemd механизм динамических пользователей нацелен на упрощение
|
||
и удешевление операций создания пользователей <<на лету>>, и в перспективе
|
||
позволит значительно расширить область примнения концепции пользователей в
|
||
целом.
|
||
|
||
При создании или редактировании service-файла вашей службы, вы можете включить
|
||
для нее механизм динамических пользователей, добавив в секцию +[Service]+
|
||
директиву
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#DynamicUser=}%
|
||
{DynamicUser=yes}. После этого, при каждом запуске данной службы, для нее будет
|
||
на лету создаваться системный пользователь. При остановке службы он будет
|
||
автоматически удаляться. UID такого пользователя выбирается из диапазона
|
||
61184--65519 (при этом производится автоматическая проверка, исключающая
|
||
использование UID, занятых кем-либо еще).
|
||
|
||
Вы спросите, как же в таком случае решается вышеописанная проблема привязки
|
||
идентификаторов пользователей к файлам? Существуют как минимум два очевидных
|
||
подхода к ее решению:
|
||
\begin{enumerate}
|
||
\item Запретить службе создавать файлы, каталоги и объекты IPC.
|
||
\item Автоматически удалять созданные службой файлы, каталоги и объекты
|
||
IPC при ее остановке.
|
||
\end{enumerate}
|
||
|
||
systemd реализует одновременно обе стратегии, но применяет их к различным
|
||
областям рабочего окружения службы. А именно:
|
||
\begin{enumerate}
|
||
\item Установка +DynamicUser=yes+ также применяет директивы
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#ProtectSystem=}%
|
||
{ProtectSystem=strict} и
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#ProtectHome=}%
|
||
{ProtectHome=read-only}. В результате служба теряет возможность
|
||
что-либо писать практически во все каталоги системы, за
|
||
исключением специальных файловых систем (+/dev+, +/proc+ и
|
||
+/sys+) и временных каталогов (+/tmp+ и +/var/tmp+). (Кстати,
|
||
включать эти опции имеет смысл даже для служб, которые
|
||
не~используют режим +DynamicUser=yes+, так как это сильно
|
||
снижает возможный ущерб при взломе службы.)
|
||
\item Установка +DynamicUser=yes+ автоматически применяет директиву
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#PrivateTmp=}%
|
||
{PrivateTmp=yes}. В результате для этой службы создаются свои
|
||
собственные, изолированные от других служб экземпляры каталогов
|
||
+/tmp+ и +/var/tmp+\footnote{Прим. перев.: Технически это
|
||
реализовано следующим образом: в системных +/tmp+ и +/var/tmp+
|
||
создаются подкаталоги со специальными именами (построенными на
|
||
основе имени службы и (псевдо)случайных последовательностей
|
||
символов), принадлежащие руту и имеющие права доступа 0700 (это
|
||
граничные каталоги~--- принцип их работы описан в данной статье
|
||
ниже), а внутри них создаются подкаталоги +tmp+ с правами 0777.
|
||
Для самой службы создается пространство имен монтирования (mount
|
||
namespace), в котором эти подкаталоги bind-монтируются в +/tmp+
|
||
и +/var/tmp+ соответственно. См. также раздел
|
||
\ref{ssec:privatetmp}.}, причем их жизненный цикл привязан к
|
||
жизненному циклу службы: при остановке службы удаляется
|
||
не~только ее пользователь, но и ее временные каталоги. (Опять же
|
||
замечу, что эту директиву имеет смысл применять и без
|
||
+DynamicUser=yes+, так как она тоже повышает безопасность вашей
|
||
системы.)
|
||
\item Установка +DynamicUser=yes+ автоматически применяет директиву
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RemoveIPC=}%
|
||
{RemoveIPC=yes}, которая обеспечивает автоматическое удаление
|
||
объектов межпроцессного взаимодействия (IPC) SysV и POSIX (общая
|
||
память, очереди сообщений, семафоры), принадлежащих службе, при
|
||
ее остановке. Как следствие, жизненный цикл IPC-объектов также
|
||
привязан к жизненному циклу службы и ее пользователя. (И снова:
|
||
эту директиву имеет смысл применять и для обычных служб!)
|
||
\end{enumerate}
|
||
|
||
Использование этих четырех опций позволяет практически полностью изолировать
|
||
службу с динамическим пользователем от основной системы. Она не~может создавать
|
||
файлы или каталоги где-либо, кроме +/tmp+ и +/var/tmp+, где они будут удалены
|
||
при ее остановке, как и созданные ею объекты IPC. Таким образом, проблема
|
||
владения файлами, каталогами и объектами IPC, успешно решена.
|
||
|
||
Если вам нужно немного приоткрыть <<железный занавес>> и дать службе возможность
|
||
взаимодействовать с другими программами, это можно сделать при помощи параметра
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RuntimeDirectory=}%
|
||
{RuntimeDirectory=}. Подкаталог с указанным в этом параметре именем создается в
|
||
каталоге +/run+ при запуске службы, принадлежит ее пользователю, и тоже
|
||
автоматически удаляется при ее остановке. Однако, при этом он открыт для доступа
|
||
других программ, так что служба может размещать в нем различные интерфейсные
|
||
объекты (например, UNIX-сокеты), жизненный цикл которых также будет привязан к
|
||
жизненному циклу службы. Например, если вы зададите для своей службы
|
||
+RuntimeDirectory=foobar+, то увидите, что при ее запуске создается каталог
|
||
+/run/foobar+, а при ее остановке он удаляется. (Как и другие описанные здесь
|
||
директивы, +RuntimeDirectory=+ прекрасно может работать и без +DynamicUser=+,
|
||
предоставляя для вашей службы автоматически создаваемый и удаляемый каталог с
|
||
правильным владельцем.)
|
||
|
||
\subsectiona{Долговременное хранение данных}
|
||
|
||
Вышеописанный способ изоляции службы, хотя и может оказаться полезен в некоторых
|
||
случаях, имеет одно существенное ограничение: служба не~может сохранить
|
||
какие-либо данные так, чтобы получить к ним доступ при последующих запусках.
|
||
Почти все системное дерево каталогов доступно ей только для чтения, а все
|
||
доступные на запись каталоги удаляются при остановке и перезапуске.
|
||
|
||
В выпуске systemd 235 это ограничение было снято: мы добавили три новых
|
||
опции~---
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RuntimeDirectory=}%
|
||
{StateDirectory=}, +LogsDirectory=+ и +CacheDirectory=+. По большей части они
|
||
действуют аналогично вышеописанной опции +RuntimeDirectory=+, но создают
|
||
подкаталоги не~в +/run+, а в +/var/lib+, +/var/log+ и +/var/cache+
|
||
соответственно. Но самое главное их отличие состоит в том, что эти подкаталоги
|
||
\emph{не~удаляются} при остановке службы, что позволяет использовать их для
|
||
долговременного хранения данных.
|
||
|
||
Очевидный вопрос: как при этом решается вышеописанная проблема с динамическими
|
||
идентификаторами пользователей?
|
||
|
||
Для решения этой задачи мы обратились к опыту систем контейнерной изоляции.
|
||
При работе с контейнерами возникает схожая проблема: контейнеры и хост
|
||
используют частично или полностью перекрывающиеся наборы числовых идентификаторов
|
||
пользователей, а значит, пользователи хоста могут получить доступ к
|
||
файлам контейнера, которые принадлежат пользователям с таким же UID~--- а ведь
|
||
это может быть совсем другой пользователь, никак не~связанный со своим хостовым
|
||
<<двойником>> (кроме случаев, когда используются пространства имен
|
||
пользователей\footnote{Прим. перев.: В таких случаях используется механизм
|
||
отображения UID (user-remap), когда пользователям, работающим с user
|
||
namespaces (например, +dockremap+ в Docker), выдаются диапазоны UID хоста (см.
|
||
\hreftt{http://man7.org/linux/man-pages/man5/subuid.5.html}{/etc/subuid}), на
|
||
которые отображаются UID гостевой системы. Это решает проблему пересечения UID
|
||
хоста и гостя. Без присвоения такого диапазона гость будет иметь только один
|
||
UID (в пространстве гостя он может быть любым, даже нулевым), который для хоста
|
||
(и его файловой системы) будет виден как UID пользователя контейнера.}).
|
||
Особенно неприятно, если это происходит с исполняемым файлом, имеющим
|
||
+suid+-бит~--- тогда речь идет уже не~просто о доступе к чужим данным, а о
|
||
потенциальной возможности повышения привилегий. Чтобы предотвратить подобные
|
||
ситуации, системы управления контейнерами обычно помещают корневые системы
|
||
гостей в специальный \emph{граничный} каталог, имеющий ограниченные права
|
||
доступа (обычно: права 0700, владелец +root:root+). При этом,
|
||
непривилигерованные пользователи хоста уже не~могут добраться до файлов,
|
||
принадлежащих их контейнерным <<двойникам>>, просто потому, что не~имеют доступа
|
||
ко всему, что лежит в граничном каталоге. В UNIX, чтобы получить доступ к файлу,
|
||
вы должны иметь доступ ко всем каталогам по пути к нему, начиная с корневого.
|
||
|
||
Как это работает для наших служб с динамическими пользователями? Предположим,
|
||
что вы указали +StateDirectory=foobar+, но \emph{не~включали} режим
|
||
+DynamicUser=+. В момент запуска такой службы, для нее будет создан каталог
|
||
+/var/lib/foobar+, принадлежащий пользователю, от которого запускается
|
||
служба\footnote{Прим. перев.: Если режим +DynamicUser=+ отключен, пользователь
|
||
службы определяется директивой
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#User=}%
|
||
{User=}. Более подробно ее роль при включенном и выключенном режиме
|
||
динамических пользователей рассмотрена \hyperref[itm:setuser]{ниже}.}. Этот
|
||
каталог и его содержимое \emph{не~удаляются} после завершения службы. Если же мы
|
||
включим для нашей службы режим +DynamicUser=+, алгоритм станет немного
|
||
сложнее~--- теперь +/var/lib/foobar+ уже не~каталог, а принадлежащая руту
|
||
символьная ссылка на каталог +/var/lib/private/foobar+, который принадлежит
|
||
динамическому пользователю, выделенному для службы. Каталог +/var/lib/private+
|
||
играет роль граничного: он принадлежит +root:root+, и имеет права 0700. При
|
||
остановке службы, и символьная ссылка, и каталог, на который она указывает,
|
||
не~удаляются. Каталог продолжает принадлежать теперь уже освобожденному
|
||
идентификатору пользователя, однако граничный каталог не~позволит получить к
|
||
нему доступ ни пользователям хоста, ни другим службам, получившим тот же UID.
|
||
|
||
Следующий вопрос: если граничный каталог столь успешно защищает подкаталог с
|
||
данными службы, то как сама служба сможет получить к ним доступ? Для этого,
|
||
служба запускается в специально модифицированном пространстве имен точек
|
||
монтирования (mount namespace): помимо уже упомянутых хитростей с монтированием
|
||
+/tmp+ и +/var/tmp+\footnote{Прим. перев.: Строго говоря, <<хитростей>> там
|
||
гораздо больше~--- упомянутые выше директивы +ProtectSystem=strict+ и
|
||
+ProtectHome=read-only+ предполагают перемонтирование целого ряда каталогов,
|
||
включая корневой. Но к теме это действительно не~относится.}, добавляется
|
||
+tmpfs+, смонтированная в +/var/lib/private+ и доступная только на чтение, с
|
||
правами, разрешающими чтение из этого каталога (0755). Внутри этой файловой
|
||
системы создан каталог +foobar+, в который bind-монтируется каталог
|
||
+/var/lib/private/foobar+ с хоста. В результате и для хоста, и для службы
|
||
содержимое этого каталога находится по одному и тому же пути, но при этом <<с
|
||
точки зрения>> службы каталог +/var/lib/private+ уже не~является
|
||
ограничительным, и в нем отсутствуют подкаталоги, принадлежащие другим службам,
|
||
что обеспечивает полную изоляцию служб друг от друга. Символьная сслыка
|
||
+/var/lib/foobar+ позволяет не~задумываться о том, используется граничный
|
||
каталог или нет: и при включенном, и при выключенном режиме динамических
|
||
пользователей, каталог с данными программы доступен по указанному пути.
|
||
|
||
Назревает очередной вопрос. Предположим, что мы запустили службу от динамического
|
||
пользователя и с настроенным каталогом для хранения данных. Она получила
|
||
некоторый UID (назовем его $X$), которому принадлежит этот каталог. Потом мы
|
||
перезапустили службу, и она получила новый UID $Y\neq X$. Что тогда произойдет?
|
||
Неужели каталог и его содержимое будут все еще принадлжать UID $X$, и наша
|
||
служба уже не~сможет получить к ним доступ? Конечно же, нет~--- systemd
|
||
рекурсивно поменяет владельца для каталога и его содержимого.
|
||
|
||
Разумеется, операция рекурсивной смены владельца (+chown()+) для дерева
|
||
каталогов может оказаться весьма затратной (хотя, по моему личному опыту, для
|
||
большинства служб это не~так критично, как кажется на первый взгляд), и поэтому
|
||
мы ввели две оптимизации, сводящие к минимуму вероятность такой операции.
|
||
Во-первых, systemd начинает подбор подходящего UID с некоторого значения,
|
||
полученного путем хеширования имени службы. Таким образом, если имя службы
|
||
не~поменялось, то при следующем запуске она, скорее всего, получит тот же UID. В
|
||
результате, необходимость в +chown()+ отпадает (разумеется, после
|
||
соответствующих проверок). Во-вторых, если указанный каталог уже существует, и
|
||
его владельцем является неиспользуемый UID из динамического диапазона, то службе
|
||
присваивается именно этот UID, что также увеличивает шансы избежать +chown()+.
|
||
(На самом деле, выделенный сейчас диапазон на четыре с лишним тысячи UID не~так
|
||
уж и велик, и при активном применении динамических пользователей рано или поздно
|
||
появятся ситуации, когда обойтись без +chown()+ уже не~удастся.)
|
||
|
||
Директивы +CacheDirectory=+ и +LogsDirectory=+ работают по аналогии со
|
||
+StateDirectory=+. Единственное их отличие состоит в том, что они управляют
|
||
подкаталогами в +/var/cache+ и +/var/log+, и используют граничные каталоги
|
||
+/var/cache/private+ и +/var/log/private+ соответственно.
|
||
|
||
\subsectiona{Примеры}
|
||
|
||
Итак, мы ознакомились с теорией. Попробуем теперь посмотреть, как все это
|
||
работает на практике. Простой пример:
|
||
\begin{Verbatim}
|
||
# cat > /etc/systemd/system/dynamic-user-test.service <<EOF
|
||
[Service]
|
||
ExecStart=/usr/bin/sleep 4711
|
||
DynamicUser=yes
|
||
EOF
|
||
# systemctl daemon-reload
|
||
# systemctl start dynamic-user-test
|
||
# systemctl status dynamic-user-test
|
||
dynamic-user-test.service
|
||
Loaded: loaded (/etc/systemd/system/dynamic-user-test.service; static; vendor preset: disabled)
|
||
Active: active (running) since Fri 2017-10-06 13:12:25 CEST; 3s ago
|
||
Main PID: 2967 (sleep)
|
||
Tasks: 1 (limit: 4915)
|
||
CGroup: /system.slice/dynamic-user-test.service
|
||
└─2967 /usr/bin/sleep 4711
|
||
|
||
Okt 06 13:12:25 sigma systemd[1]: Started dynamic-user-test.service.
|
||
# ps -e -o pid,comm,user | grep 2967
|
||
2967 sleep dynamic-user-test
|
||
# id dynamic-user-test
|
||
uid=64642(dynamic-user-test) gid=64642(dynamic-user-test) groups=64642(dynamic-user-test)
|
||
# systemctl stop dynamic-user-test
|
||
# id dynamic-user-test
|
||
id: ‘dynamic-user-test’: no such user
|
||
\end{Verbatim}
|
||
|
||
Мы создали юнит-файл службы с включенным режимом +DynamicUser=+, запустили эту
|
||
службу, убедились, что она работает нормально, запросили данные о пользователе,
|
||
от которого она запущена (в нашем случае его имя совпадает с именем службы~---
|
||
systemd использует его автоматически, если оно соответствует синтаксису имени
|
||
пользователя, и вы не~указали явно другого имени), остановили службу и
|
||
убедились, что такого пользователя больше не~существует.
|
||
|
||
Уже неплохо, правда? Следующий шаг~--- повторим то же самое с интерактивной
|
||
\emph{одноразовой} службой. Для тех, кто не~очень хорошо разбирается в тонкостях
|
||
systemd: одноразовая служба (transient service) создается и запускается <<на
|
||
лету>>, без каких-либо конфигурационных файлов~--- например, с помощью программы
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-run.html}%
|
||
{systemd-run}, которую можно запустить непосредственно из командной оболочки.
|
||
Короче: запуск службы без предварительного создания юнит-файла.
|
||
\begin{Verbatim}
|
||
# systemd-run --pty --property=DynamicUser=yes --property=StateDirectory=wuff /bin/sh
|
||
Running as unit: run-u15750.service
|
||
Press ^] three times within 1s to disconnect TTY.
|
||
sh-4.4$ id
|
||
uid=63122(run-u15750) gid=63122(run-u15750) groups=63122(run-u15750) context=system_u:system_r:initrc_t:s0
|
||
sh-4.4$ ls -al /var/lib/private/
|
||
total 0
|
||
drwxr-xr-x. 3 root root 60 6. Okt 13:21 .
|
||
drwxr-xr-x. 1 root root 852 6. Okt 13:21 ..
|
||
drwxr-xr-x. 1 run-u15750 run-u15750 8 6. Okt 13:22 wuff
|
||
sh-4.4$ ls -ld /var/lib/wuff
|
||
lrwxrwxrwx. 1 root root 12 6. Okt 13:21 /var/lib/wuff -> private/wuff
|
||
sh-4.4$ ls -ld /var/lib/wuff/
|
||
drwxr-xr-x. 1 run-u15750 run-u15750 0 6. Okt 13:21 /var/lib/wuff/
|
||
sh-4.4$ echo hello > /var/lib/wuff/test
|
||
sh-4.4$ exit
|
||
exit
|
||
# id run-u15750
|
||
id: ‘run-u15750’: no such user
|
||
# ls -al /var/lib/private
|
||
total 0
|
||
drwx------. 1 root root 66 6. Okt 13:21 .
|
||
drwxr-xr-x. 1 root root 852 6. Okt 13:21 ..
|
||
drwxr-xr-x. 1 63122 63122 8 6. Okt 13:22 wuff
|
||
# ls -ld /var/lib/wuff
|
||
lrwxrwxrwx. 1 root root 12 6. Okt 13:21 /var/lib/wuff -> private/wuff
|
||
# ls -ld /var/lib/wuff/
|
||
drwxr-xr-x. 1 63122 63122 8 6. Okt 13:22 /var/lib/wuff/
|
||
# cat /var/lib/wuff/test
|
||
hello
|
||
\end{Verbatim}
|
||
|
||
В приведенном примере, мы запускаем интерактивную оболочку +/bin/sh+ как
|
||
одноразовую службу +run-u15750.service+ (+systemd-run+ выбрал это имя
|
||
автоматически, так как мы не~указали имя службы явно\footnote{Прим. перев.: Это
|
||
можно было бы сделать параметром +systemd-run+ +--unit=+.}) под динамическим
|
||
пользователем, имя которого, как и в предыдущем примере, унаследовано от имени
|
||
службы. Так как мы задали +StateDirectory=wuff+, то каталог для долговременного
|
||
хранения данных нашей службы должен быть доступен под именем +/var/lib/wuff+. В
|
||
интерактивной оболочке, запущенной в рамках службы, команда +ls+ показывает
|
||
граничный каталог +/var/lib/private+ и его содержимое, а также символьную ссылку
|
||
+/var/lib/wuff+, указывающую на его подкаталог +wuff+. Наконец, перед
|
||
завершением оболочки, мы создаем там тестовый файл. Вернувшись в нашу исходную
|
||
оболочку, мы проверяем, существует ли еще пользователь, выделенный для нашей
|
||
службы~--- нет, он автоматически удален в момент завершения службы (оболочки).
|
||
При помощи аналогичных команд +ls+ мы снова проверяем каталог долговременного
|
||
хранения данных службы, на этот раз с хоста. Видим мы почти то же самое, с
|
||
двумя исключениями: во-первых, пользователь и группа, владеющие каталогом и его
|
||
содержимым, отображаются уже в виде числовых идентификатов, а не~имен. Это
|
||
обусловлено тем, что пользователь (то есть ассоциация числового идентификатора с
|
||
некоторым именем) был удален в момент завершения службы. Во-вторых, отличаются
|
||
права доступа к граничному каталогу: 0755 (читать могут все) изнутри службы, и
|
||
0700 (читать может только владелец, т.е. рут)~--- с хоста.
|
||
|
||
А теперь попробуем запустить еще одну одноразовую службу, указав ей тот же
|
||
каталог с данными:
|
||
\begin{Verbatim}
|
||
# systemd-run --pty --property=DynamicUser=yes --property=StateDirectory=wuff /bin/sh
|
||
Running as unit: run-u16087.service
|
||
Press ^] three times within 1s to disconnect TTY.
|
||
sh-4.4$ cat /var/lib/wuff/test
|
||
hello
|
||
sh-4.4$ ls -al /var/lib/wuff/
|
||
total 4
|
||
drwxr-xr-x. 1 run-u16087 run-u16087 8 6. Okt 13:22 .
|
||
drwxr-xr-x. 3 root root 60 6. Okt 15:42 ..
|
||
-rw-r--r--. 1 run-u16087 run-u16087 6 6. Okt 13:22 test
|
||
sh-4.4$ id
|
||
uid=63122(run-u16087) gid=63122(run-u16087) groups=63122(run-u16087) context=system_u:system_r:initrc_t:s0
|
||
sh-4.4$ exit
|
||
exit
|
||
\end{Verbatim}
|
||
|
||
Как видим, +systemd-run+ сгенерировал для этой службы уже другое имя, которое
|
||
перешло и к ее пользователю, однако числовой идентификатор пользователя остался
|
||
прежним~--- systemd подхватил UID владельца каталога с данными (предварительно
|
||
убедившись, что он больше никем не~используется). Этим иллюстрируется
|
||
вышеописанная оптимизация алгоритма выбора UID (цикл выбора идентификатора
|
||
начинается с UID владельца существующего каталога с данными): рекурсивного
|
||
выполнения +chown()+ удалось избежать.
|
||
|
||
Надеюсь, вышеприведенные примеры помогли вам разобраться и в самой идее, и в
|
||
особенностях ее реализации.
|
||
|
||
\subsectiona{Практическое применение}
|
||
|
||
Итак, мы рассмотрели, как включить механизм динамических пользователей для
|
||
юнита, и разобрались, как он реализован. Самое время попытаться понять, где и
|
||
для чего он может применяться.
|
||
|
||
\begin{itemize}
|
||
\item Одно из главных достоинств технологии динамического выделения
|
||
UID~--- возможность запускать службы с урезаннмыми привилегиями
|
||
(то есть, от отдельного пользователя), не~оставляя в системе
|
||
никаких артефактов. Системный пользователь создается и
|
||
используется, но после применения автоматически удаляется, и
|
||
повторное применение его UID не~несет никаких рисков для
|
||
безопасности. Мы можем одной командой запускать одноразовые
|
||
службы для выполнения каких-либо операций, изолируя их под
|
||
отдельным идентификатором пользователя, без необходимости
|
||
вручную создавать и удалять учетную запись, и не~расходуя
|
||
доступные UID попусту.
|
||
\item Во многих случаях, запуск службы уже не~требует предварительной
|
||
подготовки со стороны пакетного менджера. Другими словами,
|
||
большинство операций +useradd+/+mkdir+/+chown+/+chmod+,
|
||
выполняемых пост-инсталляционными скриптами пакетов, а также
|
||
дополнительные конфигурационные файлы в
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/sysusers.d.html}%
|
||
{sysusers.d} и
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html}%
|
||
{tmpfiles.d} становятся необязательными, так как эти операции
|
||
выполняются автоматически благодаря директивам +DynamicUser=+ и
|
||
+StateDirectory=+/+CacheDirectory=+/+LogsDirectory=+, причем
|
||
не~при установке/удалении пакета, а непосредственно при
|
||
запуске/остановке службы.
|
||
\item Сочетание технологий динамических пользователей и одноразовых
|
||
служб предоставляет простой механизм изоляции приложений.
|
||
Например, предположим, что мы не~доверяем бинарнику +sort+.
|
||
Мы можем поместить его в простую и надежную песочницу
|
||
динамического пользователя при помощи +systemd-run+, и при этом
|
||
сохранить возможность подключать ее через каналы (pipelines) к
|
||
другим программам. Простенький пример конвейера, второй элемент
|
||
которого запущен от динамического пользователя (который
|
||
уничтожается после завершения конвейера):
|
||
\begin{Verbatim}
|
||
# cat some-file.txt | systemd-run --pipe --property=DynamicUser=1 sort -u | \
|
||
grep -i foobar > some-other-file.txt
|
||
\end{Verbatim}
|
||
\item Сочетая технологию динамических пользователей и экземпляров
|
||
служб\footnote{Прим перев.: Более подробно работа с экземплярами
|
||
служб рассмотрена в главе~\ref{sec:instances}.}, можно получить
|
||
гибкой и полностью автоматический механизм управления
|
||
идентификаторами пользователей. Допустим, мы создаем шаблон
|
||
службы +/etc/systemd/system/foobard@.service+:
|
||
\begin{Verbatim}
|
||
[Service]
|
||
ExecStart=/usr/bin/myfoobarserviced
|
||
DynamicUser=1
|
||
StateDirectory=foobar/%i
|
||
\end{Verbatim}
|
||
Теперь предположим, что вы запускаете экземпляр этой службы
|
||
для одного из своих клиентов:
|
||
\begin{Verbatim}
|
||
# systemctl enable foobard@customerxyz.service --now
|
||
\end{Verbatim}
|
||
Готово! (Надеюсь, понятно, что эту операцию можно повторять
|
||
многократно, подставляя каждый раз вместо +customerxyz+
|
||
идентификаторы различных клиентов.)
|
||
\item Сочетая технологии динамических пользователей и сокет-активации,
|
||
вы легко можете получить систему, где каждое входящее соединение
|
||
обслуживается экземпляром процесса, работающим в песочнице
|
||
динамически выделенного UID\footnote{Прим перев.: Здесь идет
|
||
речь о сокет-активации <<в стиле inetd>>, когда на каждое
|
||
соединение создается экземпляр службы. Более подробно она
|
||
обсуждается в главе~\ref{sec:inetd}.}. Пример конфигурации
|
||
сокета +waldo.socket+:
|
||
\begin{Verbatim}
|
||
[Socket]
|
||
ListenStream=2048
|
||
Accept=yes
|
||
\end{Verbatim}
|
||
И соответствующего ему шаблона службы +waldo@.service+:
|
||
\begin{Verbatim}
|
||
[Service]
|
||
ExecStart=-/usr/bin/myservicebinary
|
||
DynamicUser=yes
|
||
\end{Verbatim}
|
||
В результате, systemd будет слушать TCP-порт 2048, и на каждое
|
||
входящее соединение создавать новый экземпляр +waldo@.service+,
|
||
каждый раз с новым идентификатором пользователя, обеспечивающим
|
||
его изоляцию от остальных экземпляров.
|
||
\item Динамическое выделение пользователей хорошо сочетается с
|
||
<<системами без состояния>> (state-less systems), то есть
|
||
системами, которые запускаются с пустыми каталогами +/etc+ и
|
||
+/var+. Динамическим выделение UID и директивы
|
||
+StateDirectory=+, +CacheDirectory=+, +LogsDirectory=+ и
|
||
+RuntimeDirectory=+ позволяет автоматических создавать
|
||
пользователя и необходимые службе каталоги непосредственно перед
|
||
ее запуском.
|
||
\end{itemize}
|
||
|
||
Динамическое выделение пользователей~--- масштабная и глобальная концепция, и ее
|
||
применение, разумеется, не~ограничивается приведенным списком. Этот список~---
|
||
всего лишь попытка разбудить ваше воображение, дать начальный импульс к
|
||
размышлениям.
|
||
|
||
\subsectiona{Рекомендации сопровождающим пакетов}
|
||
|
||
Я уверен, что для значительной доли служб, поставляемых в современных
|
||
дистрибутивах, опции +DynamicUser=+, +StateDirectory=+ и т.д., могут оказаться
|
||
весьма полезны. Во многих случаях они позволят вообще отказаться от +post-inst+
|
||
скриптов, а также конфигурационных файлов в +sysusers.d+ и +tmpfiles.d+,
|
||
объединив все необходимые настройки непосредственно в юнит-файле. Так что, если
|
||
вы сопровождаете какой-либо пакет со службой, пожалуйста, рассмотрите
|
||
возможность использования этих директив. Тем не~менее, существует ряд ситуаций,
|
||
когда данные директивы неэффективны или неприменимы:
|
||
\begin{enumerate}
|
||
\item Службы, которым нужно писать куда-либо за пределами разрешенного
|
||
списка каталогов (+/run/<package>+, +/var/lib/<package>+,
|
||
+/var/cache/<package>+, +/var/log/<package>+, +/var/tmp+, +/tmp+,
|
||
+/dev/shm+) не~совместимы с описанным подходом. Например, демон,
|
||
обновляющий систему~--- ему, как минимум, необходим доступ на
|
||
запись в +/usr+.
|
||
\item Службы, которые управляют набором процессов, запущенных от
|
||
различных пользователей, например, некоторые SMTP-серверы. Если
|
||
ваша служба построена по принципу \emph{суперсервера}, то
|
||
управление идентификаторами пользователей для своих процессов
|
||
она должна осуществлять сама~--- systemd не~должен в это
|
||
вмешиваться.
|
||
\item Службы, запускаемые от рута, и вообще требующие расширенных
|
||
привилегий.
|
||
\item Службы, котороые должны запускаться в пространстве имен
|
||
монтирования хоста (например, если служба должна создавать точки
|
||
монтирования, видимые для всей системы). Как уже упоминалось
|
||
выше, +DynamicUser=+ задействует механизмы +ProtectSystem=+,
|
||
+PrivateTmp=+ и т.д., которые основаны на запуске службы в
|
||
отдельном пространстве имен монтирования.
|
||
\item В вашем дистрибутиве пока нет свежих версий systemd: 232
|
||
(поддержка +DynamicUser=+) или 235 (поддержка +StateDirectory=+
|
||
и аналогичных ей опций).
|
||
\item Правила создания пакетов для вашего дистрибутива не~разрешают
|
||
подобный подход. Уточните эти моменты в правилах и, при
|
||
необходимости, обсудите данный вопрос в рассылке вашего
|
||
дистрибутива.
|
||
\end{enumerate}
|
||
|
||
\subsectiona{Дополнительные замечания}
|
||
|
||
Еще несколько замечаний, непосредственно относящихся к обсуждаемой теме:
|
||
\begin{enumerate}
|
||
\item Обратите внимание, что процесс выделения и удаления динамических
|
||
пользователей никак не~затрагивает +/etc/passwd+. Добавление
|
||
пользователя в базу данных оусществляется при помощи NSS-модуля
|
||
glibc
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/nss-systemd.html}%
|
||
{nss-systemd}\footnote{Прим. перев.: Разумеется, чтобы
|
||
преобразование <<имя-идентификатор>> для пользователя и его
|
||
группы работало корректно, это модуль должен быть указан в
|
||
строках +passwd:+ и +group:+ файла +/etc/nsswitch.conf+. Примеры
|
||
приведены на странице руководства модуля.}, и эта информация
|
||
никогда не~попадает на диск.
|
||
\item В традиционных UNIX-системах, демоны сбрасывают привилегии с рута
|
||
до обычного пользователя самостоятельно, в то время как механизм
|
||
динамических пользователей предполагает, что этим должен
|
||
заниматься systemd. В версии systemd 235 добавлена возможность
|
||
совместить механизм динамических пользователей с самостоятельным
|
||
сбросом привилегий процессом службы. Для этого, включите опцию
|
||
+DynamicUser=+, а в опции
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#User=}%
|
||
{User=} укажите имя пользователя, в которого ваша служба
|
||
перевоплотится (+setuid()+) после инициализации. В результате, в
|
||
момент запуска службы systemd создаст динамического пользователя
|
||
с указанным именем. Далее, в директиве
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.service.html\#ExecStart=}%
|
||
{ExecStart=} непосредственно перед командой запуска укажите
|
||
символ <<+!+>>. После этого, пользователь для службы будет
|
||
создаваться, но запускаться она будет от рута~--- systemd
|
||
будет считать, что служба сама сбросит полномочия в ходе
|
||
инициализации. Например: +ExecStart=!/usr/bin/mydaemond+. При
|
||
этом, регистрация соответствия имени и идентификатора
|
||
пользователя в базе данных производится, как и прежде, при
|
||
запуске службы, и поэтому процесс демона сможет без проблем
|
||
преобразововать имя пользователя в UID.
|
||
\item У вас может возникнуть вопрос: почему для динамического выделения
|
||
UID выбран именно диапазон 61184--65519 (в шестнадцатеричной
|
||
записи 0xEF00--0xFFEF)? Он был выбран потому, что большинство
|
||
дистрибутивов (например, Fedora) используют для обычных
|
||
пользователей идентификаторы ниже 60000, и мы не~хотим
|
||
переступать эту границу. Также мы делаем небольшой отступ от
|
||
65535, так как некоторые UID вблизи этого значения имеют
|
||
специальное значение (65535 часто трактуется как
|
||
<<некорректный>> или <<отсутствующий>> UID, так как является
|
||
представлением числа $-1$ в 16-битном целом типе; 65534 обычно
|
||
соответствует пользователю +nobody+, и некоторые подсистемы ядра
|
||
отображают в это значение <<посторонние>>
|
||
идентификаторы\footnote{Прим. перев.: Например, изнутри
|
||
пространства имен пользователей, все пользователи хоста, кроме
|
||
создателя пространства, выглядят как +nobody+.}). И наконец, мы
|
||
не~хотим выходить за пределы 16-битного целого типа. Даже с
|
||
распространением технологии пространств имен пользователей,
|
||
контейнерам все равно не~нужен весь диапазон значений,
|
||
предоставляемый 32-битным целым типом, который используется в
|
||
ядре Linux для UID. Там не~менее, очевидно, что контейнеры
|
||
должны поддерживать весь 16-битный диапазон~--- как минимум,
|
||
из-за +nobody+. (Если честно, я считаю выделение 64 тысяч
|
||
идентификаторов на контейнер оптимальным вариантом: верхние 16
|
||
бит из 32-битного UID можно использовать как идентификатор
|
||
контейнера, в том время как нижние будут соответствовать
|
||
идентификатору пользователя в этом контейнере\ldots{} Надеюсь,
|
||
вы не~потеряли нить рассуждений.) И, не~дожидаясь вашего
|
||
вопроса: пока нет никакого способа изменить этот диапазон~---
|
||
его границы заданы в исходном коде. Но когда-нибудь мы
|
||
обязательно добавим соответствующие настройки.
|
||
\item Вы можете поинтересоваться, что произойдет, если вы уже
|
||
используете идентификаторы из диапазона 61184--65519 для других
|
||
целей? systemd должен обработать такую ситуацию корректно, если
|
||
эти идентификаторы зарегистрированы в базе даных пользователей:
|
||
выбрав UID, systemd проверят, не~используется ли он кем-то, и
|
||
если он занят, выбирает другой~--- до тех пор, пока не~найдет
|
||
свободный. Проверка производится прежде всего при помощи
|
||
функций NSS. Также просматриваются списки объектов IPC, и
|
||
их владельцы проверяются на совпадение с нашим кандидатом. Таким
|
||
образом, systemd избегает использования UID, занятых кем-то еще.
|
||
Тем не~менее, это сокращает набор доступных идентификаторов, и в
|
||
худшем случае выделение пользователя может завершиться ошибкой
|
||
из-за отсутствия свободных UID в рабочем диапазоне.
|
||
\item Если имя для выделяемого пользователя не~указано явно, systemd
|
||
пытается вывести его из имени службы. Однако, далеко не~каждое
|
||
корректное имя службы является также корректным именем
|
||
пользователя, и чтобы обойти это, используется случайное имя.
|
||
Возможно, вам будет удобнее задать имя пользователя вручную~---
|
||
используйте для этого директиву +User=+.
|
||
\item \label{itm:setuser}
|
||
Если вы используете +User=+ в сочетании с +DynamicUser=on+, но
|
||
пользователь с указанным именем уже существует, то для службы
|
||
будет использован именно он, а механизм динамического выделения
|
||
пользователя для этой службы автоматически отключится. Таким
|
||
образом, упрощается переход между статическими и динамическими
|
||
пользователями: вы указываете нужное вам имя в +User=+, и пока
|
||
этот пользователь существует в системной базе, система будет
|
||
использовать его, и лишь при отутствии такого пользователя он
|
||
будет создаваться в динамическом режиме. Также это может быть
|
||
полезно в других ситуациях, например, чтобы подготовить службы,
|
||
использующие динамических пользователей, к возможности перехода
|
||
на статические UID, скажем, чтобы применить к ним квоты файловой
|
||
системы.
|
||
\item systemd всегда выделяет вместе с пользователем еще и группу, с
|
||
тем же самым значением идентификатора (UID = GID).
|
||
\item Если бы ядро Linux имело механизм наподобие +shiftfs+, то есть
|
||
способ смонтировать существующий каталог куда-либо с подменой
|
||
UID/GID по некоторому правилу, задаваемому при монтировании, это
|
||
значительно упростило бы реализацию работы +StateDirectory=+ в
|
||
сочетании с +DynamicUser=+, в частности, позволив отказаться от
|
||
рекурсивной смены владельца, и просто монтировать каталог с
|
||
хоста в пространство имен гостя, подменив владельца каталога на
|
||
UID/GID службы. Однако я не~питаю больших надежд на подобный
|
||
вариант, так как все работы в этой области сейчас завязаны на
|
||
пространство имен пользователей~--- механизм, который
|
||
\emph{никак не~используется} в обсуждаемой технологии (есть
|
||
мнение, что он создает гораздо больше проблем, чем решает, хотя
|
||
вы можете с этим и не~согласиться).
|
||
\end{enumerate}
|
||
|
||
На сегодня все!
|
||
|
||
\sectiona{Учет и фильтрация IP-трафика служб}
|
||
|
||
\emph{Коротко о главном: теперь systemd может подсчитывать и фильтровать
|
||
по подсетям IP-трафик любой службы.}
|
||
|
||
В недавно вышедшем выпуске
|
||
\href{https://lists.freedesktop.org/archives/systemd-devel/2017-October/039589.html}%
|
||
{systemd 235}, помимо расширения поддержки динамических пользователей
|
||
(рассмотренной в предыдущей статье), добавлена еще одна важная возможность:
|
||
учет и фильтрация IP-трафика.
|
||
|
||
systemd и раньше поддерживал механизмы управления ресурсами, доступными юнитам:
|
||
процессорным временем, дисковым вводом-выводом, потреблением памяти, количеством
|
||
запускаемых процессов. А в версии 235 был добавлен контроль над еще одним видом
|
||
ресурсов~--- IP-трафиком.
|
||
|
||
Для этого были введены три новых директивы конфигурации юнитов:
|
||
\begin{enumerate}
|
||
\item \hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IPAccounting=}%
|
||
{IPAccounting=}~--- булева переменная, позволяющая включить
|
||
подсчет IP-трафика, принятого и полученного службой (как
|
||
количества пакетов, так и их суммарного объема в байтах).
|
||
\item \hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IPAddressAllow=ADDDRESS[/PREFIXLENGTH]\%E2\%80\%A6}%
|
||
{IPAddressDeny=} указывает <<черный>> список адресов подсетей
|
||
или хостов для службы. Весь трафик, отправляемый на эти адреса
|
||
процессами службы, а также полученный ими с этих адресов,
|
||
будет заблокирован.
|
||
\item \hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IPAddressAllow=ADDDRESS[/PREFIXLENGTH]\%E2\%80\%A6}%
|
||
{IPAddressAllow=} имеет противоположный смысл~--- процессы
|
||
службы могут обмениваться трафиком с перечисленными в этой
|
||
директиве адресами, даже если они пересекаются с тем, что задано
|
||
в +IPAddressDeny=+ (т.е. белый список имеет приоритет над
|
||
черным).
|
||
\end{enumerate}
|
||
|
||
Эти три опции предоставляют интерфейс к новой функциональности ядра Linux,
|
||
добавленной в выпуске 4.11, а именно, eBPF-хукам контрольных групп. Основную
|
||
работу берет на себя ядро, а systemd лишь обеспечивает возможность настройки
|
||
этих механизмов. Обратите внимание, что cgroup/eBPF никак не~относятся к
|
||
традиционному брандмауэру Linux~--- NetFilter/+iptables+. Вы можете использовать
|
||
любую из этих технологий, или обе сразу, или вообще не~пользоваться ни~одной из
|
||
них.
|
||
|
||
\subsectiona{Учет IP-трафика}
|
||
|
||
Давайте посмотрим, как работает учет трафика. Создадим тестовую службу
|
||
+/etc/systemd/system/ip-accounting-test.service+:
|
||
\begin{Verbatim}
|
||
[Service]
|
||
ExecStart=/usr/bin/ping 8.8.8.8
|
||
IPAccounting=yes
|
||
\end{Verbatim}
|
||
|
||
Этот простой юнит вызывает команду
|
||
\hreftt{http://man7.org/linux/man-pages/man8/ping.8.html}{ping(8)}, которая
|
||
отправляет серию ICMP эхо-запросов на IP-адрес 8.8.8.8 (это адрес DNS-сервера
|
||
Google; он используется в нашем примере потому, что его легко запомнить, он
|
||
отовсюду доступен и отвечает на эхо-запросы; вы можете использовать любой другой
|
||
адрес, отвечающий на пинги). Опция +IPAccounting=yes+ включает учет IP-трафика
|
||
для нашей службы.
|
||
|
||
Запустим нашу службу и посмотрим ее состояние:
|
||
\begin{Verbatim}
|
||
# systemctl daemon-reload
|
||
# systemctl start ip-accounting-test
|
||
# systemctl status ip-accounting-test
|
||
ip-accounting-test.service
|
||
Loaded: loaded (/etc/systemd/system/ip-accounting-test.service; static; vendor preset: disabled)
|
||
Active: active (running) since Mon 2017-10-09 18:05:47 CEST; 1s ago
|
||
Main PID: 32152 (ping)
|
||
IP: 168B in, 168B out
|
||
Tasks: 1 (limit: 4915)
|
||
CGroup: /system.slice/ip-accounting-test.service
|
||
└─32152 /usr/bin/ping 8.8.8.8
|
||
|
||
Okt 09 18:05:47 sigma systemd[1]: Started ip-accounting-test.service.
|
||
Okt 09 18:05:47 sigma ping[32152]: PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
|
||
Okt 09 18:05:47 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=29.2 ms
|
||
Okt 09 18:05:48 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=2 ttl=59 time=28.0 ms
|
||
\end{Verbatim}
|
||
Как видим, программа +ping+ сейчас работает. Судя по логу в конце вывода
|
||
+systemctl status+, она как раз закончила второй цикл пинга. Однако, для нас
|
||
сейчас более интересна строка выше, начинающаяся с +IP:+ и содержащая счетчики
|
||
принятых и полученных байт IP-трафика. Сайчас она показывает, что было
|
||
отправлено 168 байт, и ровно столько же принято. Это вполне предсказуемо: ICMP
|
||
эхо-запросы и эхо-ответы должны иметь одинаковый размер\footnote{Прим. перев.:
|
||
Если не~считать заголовков IP и ICMP, имеющих фиксированную длину, размер пакета
|
||
определяется только телом сообщения (payload), которое должно полностью
|
||
копироваться из запроса в ответ.}. Обратите внимание, что эта строка появляется
|
||
только для юнитов с включенной опцией +IPAccounting=+. Если она выключена,
|
||
трафик юнита не~подсчитывается\footnote{Прим. перев.: Можно заметить, что для
|
||
приведенного в примере юнита подсчитывается не~только трафик, но и количество
|
||
процессов/потоков (строка +Tasks:+). Это связано с введенным лимитом на
|
||
их количество
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#TasksMax=N}%
|
||
{TasksMax=4915} (+TasksMax=+ автоматически включает
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#TasksAccounting=}%
|
||
{TasksAccounting=}).}.
|
||
|
||
Подождем немного, и вызовем +systemctl status+ снова:
|
||
\begin{Verbatim}
|
||
# systemctl status ip-accounting-test
|
||
ip-accounting-test.service
|
||
Loaded: loaded (/etc/systemd/system/ip-accounting-test.service; static; vendor preset: disabled)
|
||
Active: active (running) since Mon 2017-10-09 18:05:47 CEST; 4min 28s ago
|
||
Main PID: 32152 (ping)
|
||
IP: 22.2K in, 22.2K out
|
||
Tasks: 1 (limit: 4915)
|
||
CGroup: /system.slice/ip-accounting-test.service
|
||
└─32152 /usr/bin/ping 8.8.8.8
|
||
|
||
Okt 09 18:10:07 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=260 ttl=59 time=27.7 ms
|
||
Okt 09 18:10:08 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=261 ttl=59 time=28.0 ms
|
||
Okt 09 18:10:09 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=262 ttl=59 time=33.8 ms
|
||
Okt 09 18:10:10 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=263 ttl=59 time=48.9 ms
|
||
Okt 09 18:10:11 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=264 ttl=59 time=27.2 ms
|
||
Okt 09 18:10:12 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=265 ttl=59 time=27.0 ms
|
||
Okt 09 18:10:13 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=266 ttl=59 time=26.8 ms
|
||
Okt 09 18:10:14 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=267 ttl=59 time=27.4 ms
|
||
Okt 09 18:10:15 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=268 ttl=59 time=29.7 ms
|
||
Okt 09 18:10:16 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=269 ttl=59 time=27.6 ms
|
||
\end{Verbatim}
|
||
Как видно из вывода, после 269 пингов счетчики достигли значения 22 килобайт.
|
||
|
||
Несмотря на то, что +systemctl status+ показывает только счетчики байт,
|
||
ведется также учет и количества пакетов. Для просмотра этих значений можно
|
||
использовать низкоуровневую команду +systemctl show+:
|
||
\begin{Verbatim}
|
||
# systemctl show ip-accounting-test -p IPIngressBytes -p IPIngressPackets \
|
||
-p IPEgressBytes -p IPEgressPackets
|
||
IPIngressBytes=37776
|
||
IPIngressPackets=449
|
||
IPEgressBytes=37776
|
||
IPEgressPackets=449
|
||
\end{Verbatim}
|
||
|
||
Разумеется, эта информация доступна также и через API D-Bus. Если вы хотите
|
||
автоматизировать обработку таких данных, использование вызовов D-Bus будет
|
||
гораздо удобнее, чем разбор вывода +systemctl show+.
|
||
|
||
Теперь остановим нашу службу:
|
||
\begin{Verbatim}
|
||
# systemctl stop ip-accounting-test
|
||
\end{Verbatim}
|
||
|
||
После остановки службы, для которой включен учет потребления ресурсов, в
|
||
системный журнал добавляется запись с итоговой суммой потребленных службой
|
||
ресурсов. Просмотреть ее можно командой +journalctl+:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# journalctl -u ip-accounting-test -n 5
|
||
-- Logs begin at Thu 2016-08-18 23:09:37 CEST, end at Mon 2017-10-09 18:17:02 CEST. --
|
||
Okt 09 18:15:50 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=603 ttl=59 time=26.9 ms
|
||
Okt 09 18:15:51 sigma ping[32152]: 64 bytes from 8.8.8.8: icmp_seq=604 ttl=59 time=27.2 ms
|
||
Okt 09 18:15:52 sigma systemd[1]: Stopping ip-accounting-test.service...
|
||
Okt 09 18:15:52 sigma systemd[1]: Stopped ip-accounting-test.service.
|
||
Okt 09 18:15:52 sigma systemd[1]: ip-accounting-test.service: Received 49.5K IP traffic, sent 49.5K IP traffic
|
||
\end{Verbatim}
|
||
Последняя строка~--- и есть та самая запись о потребленных ресурсах. На самом
|
||
деле, эта запись является структурной\footnote{Прим. перев.: Подробнее о
|
||
структуре записей Journal можно почитать в разделе~\ref{ssec:metadata}.}, и
|
||
содержит поля метаданных, в которых приводится более полная информация:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# journalctl -u ip-accounting-test -n 1 -o verbose
|
||
-- Logs begin at Thu 2016-08-18 23:09:37 CEST, end at Mon 2017-10-09 18:18:50 CEST. --
|
||
Mon 2017-10-09 18:15:52.649028 CEST [s=89a2cc877fdf4dafb2269a7631afedad;i=14d7;b=4c7e7adcba0c45b69d612857270716d3;m=137592e75e;t=55b1f81298605;x=c3c9b57b28c9490e]
|
||
PRIORITY=6
|
||
_BOOT_ID=4c7e7adcba0c45b69d612857270716d3
|
||
_MACHINE_ID=e87bfd866aea4ae4b761aff06c9c3cb3
|
||
_HOSTNAME=sigma
|
||
SYSLOG_FACILITY=3
|
||
SYSLOG_IDENTIFIER=systemd
|
||
_UID=0
|
||
_GID=0
|
||
_TRANSPORT=journal
|
||
_PID=1
|
||
_COMM=systemd
|
||
_EXE=/usr/lib/systemd/systemd
|
||
_CAP_EFFECTIVE=3fffffffff
|
||
_SYSTEMD_CGROUP=/init.scope
|
||
_SYSTEMD_UNIT=init.scope
|
||
_SYSTEMD_SLICE=-.slice
|
||
CODE_FILE=../src/core/unit.c
|
||
_CMDLINE=/usr/lib/systemd/systemd --switched-root --system --deserialize 25
|
||
_SELINUX_CONTEXT=system_u:system_r:init_t:s0
|
||
UNIT=ip-accounting-test.service
|
||
CODE_LINE=2115
|
||
CODE_FUNC=unit_log_resources
|
||
MESSAGE_ID=ae8f7b866b0347b9af31fe1c80b127c0
|
||
INVOCATION_ID=98a6e756fa9d421d8dfc82b6df06a9c3
|
||
IP_METRIC_INGRESS_BYTES=50880
|
||
IP_METRIC_INGRESS_PACKETS=605
|
||
IP_METRIC_EGRESS_BYTES=50880
|
||
IP_METRIC_EGRESS_PACKETS=605
|
||
MESSAGE=ip-accounting-test.service: Received 49.6K IP traffic, sent 49.6K IP traffic
|
||
_SOURCE_REALTIME_TIMESTAMP=1507565752649028
|
||
\end{Verbatim}
|
||
Нас интересуют поля +IP_METRIC_INGRESS_BYTES=+, +IP_METRIC_INGRESS_PACKETS=+,
|
||
+IP_METRIC_EGRESS_BYTES=+, +IP_METRIC_EGRESS_PACKETS=+, отображающие значения
|
||
соответствующих счетчиков.
|
||
|
||
Все подобные записи имеют один и тот же идентификатор типа сообщения
|
||
(\href{https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html\#MESSAGE_ID=}%
|
||
{message ID}), при помощи которого их можно легко найти в журнале
|
||
(+ae8f7b866b0347b9af31fe1c80b127c0+). Вызовем +journalctl+ с указанием этого
|
||
идентификатора, добавив также ключ +-u+, который ограничит выборку только
|
||
записями нашей службы:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
# journalctl -u ip-accounting-test MESSAGE_ID=ae8f7b866b0347b9af31fe1c80b127c0
|
||
-- Logs begin at Thu 2016-08-18 23:09:37 CEST, end at Mon 2017-10-09 18:25:27 CEST. --
|
||
Okt 09 18:15:52 sigma systemd[1]: ip-accounting-test.service: Received 49.6K IP traffic, sent 49.6K IP traffic
|
||
\end{Verbatim}
|
||
Приведенный вывод содержит пока только одну запись, так как мы запускали службу
|
||
всего один раз. Каждый последующий запуск будет добавлять новую запись.
|
||
|
||
Учет IP-трафика доступен также и для одноразовых служб, запускаемых через
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-run.html}%
|
||
{systemd-run}, что позволяет прозрачно выполнять различные команды как службы
|
||
systemd, и подсчитывать их трафик:
|
||
\begin{Verbatim}
|
||
# systemd-run -p IPAccounting=yes --wait \
|
||
wget https://cfp.all-systems-go.io/en/ASG2017/public/schedule/2.pdf
|
||
Running as unit: run-u2761.service
|
||
Finished with result: success
|
||
Main processes terminated with: code=exited/status=0
|
||
Service runtime: 878ms
|
||
IP traffic received: 231.0K
|
||
IP traffic sent: 3.7K
|
||
\end{Verbatim}
|
||
Мы использовали \hreftt{https://linux.die.net/man/1/wget}{wget}, чтобы загрузить
|
||
\href{https://cfp.all-systems-go.io/en/ASG2017/public/schedule/2.pdf}{PDF с
|
||
расписанием второго дня} нашей любимой конференции
|
||
\href{https://all-systems-go.io/}{All Systems Go! 2017}. Эта операция
|
||
потребовала 231 килобайт входящего трафика и 4 килобайта исходящего.
|
||
Особого внимания заслуживают параметры командной строки, с которыми мы вызвали
|
||
+systemd-run+. Первый, +-p IPAccounting=yes+, включает режим учета IP-трафика
|
||
для нашей одноразовой службы (аналогично тому, как та же строка в юнит-файле
|
||
делает это для обычной службы). Второй параметр, +--wait+, приказывает
|
||
+systemd-run+ дождаться завершения работы созданной службы, после чего вывести
|
||
информацию о результатах ее работы, включая интересующую нас статистику по
|
||
IP-трафику (разумеется, только при условии, что мы включили его подсчет).
|
||
|
||
Еще интереснее получается, если применять учет IP-трафика для
|
||
\emph{интерактивных} одноразовых служб:
|
||
\begin{Verbatim}
|
||
# systemd-run -p IPAccounting=1 -t /bin/sh
|
||
Running as unit: run-u2779.service
|
||
Press ^] three times within 1s to disconnect TTY.
|
||
sh-4.4# dnf update
|
||
...
|
||
sh-4.4# dnf install firefox
|
||
...
|
||
sh-4.4# exit
|
||
Finished with result: success
|
||
Main processes terminated with: code=exited/status=0
|
||
Service runtime: 5.297s
|
||
IP traffic received: ...B
|
||
IP traffic sent: ...B
|
||
\end{Verbatim}
|
||
Мы использовали ключ +systemd-run+ +--pty+ (в краткой форме +-t+), который
|
||
открывает интерактивное псевдо-терминальное подключение к запускаемой службе~---
|
||
в нашем случае это оболочка +/bin/sh+. Таким образом, мы получаем полноценную
|
||
оболочку, с управлением фоновыми заданиями и прочими возможностями, запущенную
|
||
в рамках службы systemd. Так как для этой службы включен учет IP-трафика,
|
||
при ее завершении мы получаем суммарную статистику по входящим и исходящим
|
||
данным. (Для краткости я не~стал показывать вывод целиком, а оставил только
|
||
ключевые моменты. Если вам хочется посмотреть на полный вывод~--- попробуйте
|
||
запустить что-нибудь сами.)
|
||
|
||
Иногда бывает нужно включить учет IP-трафика для юнита, который уже запущен.
|
||
Это можно сделать командой
|
||
\begin{Verbatim}
|
||
systemctl set-property foobar.service IPAccounting=yes
|
||
\end{Verbatim}
|
||
Обратите внимание, что она не~имеет обратной силы: трафик учитывается только с
|
||
момента выполнения этой команды. Аналогичной командой (+IPAccounting=no+) учет
|
||
трафика можно отключить.
|
||
|
||
Если вы хотите подсчитывать IP-трафик сразу для всех служб, вам вовсе
|
||
не~обязательно добавлять +IPAccounting=yes+ во все юнит-файлы. Достаточно
|
||
задействовать глобальную опцию
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html\#DefaultCPUAccounting=}%
|
||
{DefaultIPAccounting=} в файле +/etc/systemd/system.conf+.
|
||
|
||
\subsectiona{Фильтрация IP-трафика}
|
||
|
||
От учета трафика переходим к его фильтрации. Рассмотрим поподробнее механизм
|
||
контрольных списков IP-адресов (IP ACL), добавленный в systemd 235. Как уже
|
||
упоминалось выше, за него отвечают две директивы: +IPAddressAllow=+ и
|
||
+IPAddressDeny=+. Работают они следующим образом:
|
||
\begin{enumerate}
|
||
\item Если адрес источника входящего пакета, либо адрес назначения
|
||
исходящего пакета соответствует какому-либо из адресов хостов
|
||
или попадает в одну из подсетей, указанных в +IPAddressAllow=+,
|
||
пакет проходит свободно.
|
||
\item Если пакет не~соответствует +IPAddressAllow=+, но подпадает
|
||
под +IPAddressDeny=+ (аналогично, проверяются исходные адреса
|
||
входящих пакетов и адреса назначения исходящих), он блокируется.
|
||
\item Если пакет не~подпадает ни под одну из этих директив, он тоже
|
||
проходит свободно.
|
||
\end{enumerate}
|
||
Иными словами, +IPAddressDeny=+ является черным списком, но белый список
|
||
+IPAddressAllow=+ имеет над ним приоритет.
|
||
|
||
Посмотрим, как это работает. Поменяем предыдущий пример, чтобы получить
|
||
интерактивную оболочку, работающую в рамках одноразовой службы с настроенными
|
||
контрольными списками IP-адресов:
|
||
\begin{Verbatim}
|
||
# systemd-run -p IPAddressDeny=any -p IPAddressAllow=8.8.8.8 \
|
||
-p IPAddressAllow=127.0.0.0/8 -t /bin/sh
|
||
Running as unit: run-u2850.service
|
||
Press ^] three times within 1s to disconnect TTY.
|
||
sh-4.4# ping 8.8.8.8 -c1
|
||
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
|
||
64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=27.9 ms
|
||
|
||
--- 8.8.8.8 ping statistics ---
|
||
1 packets transmitted, 1 received, 0% packet loss, time 0ms
|
||
rtt min/avg/max/mdev = 27.957/27.957/27.957/0.000 ms
|
||
sh-4.4# ping 8.8.4.4 -c1
|
||
PING 8.8.4.4 (8.8.4.4) 56(84) bytes of data.
|
||
ping: sendmsg: Operation not permitted
|
||
^C
|
||
--- 8.8.4.4 ping statistics ---
|
||
1 packets transmitted, 0 received, 100% packet loss, time 0ms
|
||
sh-4.4# ping 127.0.0.2 -c1
|
||
PING 127.0.0.1 (127.0.0.2) 56(84) bytes of data.
|
||
64 bytes from 127.0.0.2: icmp_seq=1 ttl=64 time=0.116 ms
|
||
|
||
--- 127.0.0.2 ping statistics ---
|
||
1 packets transmitted, 1 received, 0% packet loss, time 0ms
|
||
rtt min/avg/max/mdev = 0.116/0.116/0.116/0.000 ms
|
||
sh-4.4# exit
|
||
\end{Verbatim}
|
||
Мы задали +IPAddressDeny=any+, чтобы работать по схеме белого списка: для нашей
|
||
службы разрешен обмен трафика только с теми адресами, которые перечислены в
|
||
+IPAddressAllow=+. А это, в данном примере, уже знакомый нам адрес 8.8.8.8
|
||
(указанный без маски подсети, что соответствует адресу хоста, т.е. маске +/32+),
|
||
и подсеть 127.0.0.0/8. Таким образом, служба может взаимодействовать только с
|
||
одним из DNS-серверов Google и адресами обратной петли, и больше ни с кем.
|
||
Команды, запущенные в оболочке, иллюстрируют это. Сначала мы пытаемся пинговать
|
||
8.8.8.8~--- успешно. Затем, мы пробуем пропинговать 8.8.4.4 (это другой
|
||
DNS-сервер Google, не~входящий в наш белый список), и видим ошибку <<Operation
|
||
not permitted>>. Наконец, мы пингуем адрес 127.0.0.2 (принадлежащий подсети
|
||
обратной петли), и снова успешно.
|
||
|
||
Обратите внимание на специальное значение +any+, которое мы использовали в
|
||
примере выше. Оно является сокращением для <<+0.0.0.0/0 ::/0+>>, то есть
|
||
\emph{всех} возможных адресов IPv4 и IPv6. Есть и другие сокращения. Например,
|
||
вместо +127.0.0.0/8+ мы могли бы указать более понятное обозначение +localhost+,
|
||
соответствующее <<+127.0.0.0/8 ::1/128+>>, то есть IPv4 и IPv6 подсетям обратной
|
||
петли.
|
||
|
||
Возможность настраивать контрольные списки IP-адресов независимо для каждого
|
||
юнита~--- это уже неплохо. Однако, для большего удобства, существует
|
||
возможность задавать их для целых групп юнитов, или даже для всей системы. Это
|
||
можно сделать при помощи юнитов
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.slice.html}%
|
||
{.slice} (для тех, кто не~очень хорошо разбирается в systemd: slice-юниты
|
||
предоставляют возможность организовать юниты в группы для управления системными
|
||
ресурсами\footnote{Прим. перев.: Чуть более развернутое определение:
|
||
slice-юниты соответствуют <<промежуточным>> уровням иерархии контрольных групп,
|
||
которые не~содержат непосредственно процессов, а объединяют другие +.slice+,
|
||
+.service+ и +.scope+-юниты. Последние два типа юнитов уже соответствуют
|
||
контрольным группам процессов, причем группы +.service+ создаются автоматически
|
||
при запуске служб systemd, а +.scope+~--- это специально сформированные
|
||
контрольные группы процессов, запущенных другими программами, что позволяет
|
||
<<перекидывать>> отдельные процессы в другие точки иерархии контрольных
|
||
групп. В частности, процессы, созданные в рамках пользовательских сессий, а
|
||
также деревья процессов контейнеров, автоматически перемещаются из контрольных
|
||
групп породивших их служб (например, +kdm.service+) в специальные scope-юниты
|
||
(например, +session-1.scope+).}): контрольные списки адресов юнита формируются
|
||
путем объединения списков самого юнита, а также всех slice-юнитов, в которые он
|
||
вложен.
|
||
|
||
По умолчанию, все системные службы помещаются в группу
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.special.html\#system.slice}%
|
||
{system.slice}, которая, в свою очередь, входит в корневую группу
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.special.html\#-.slice}%
|
||
{-.slice}. Соответственно, при помощи любой из этих групп можно заблокировать
|
||
\emph{все} системные службы разом. Разница состоит в том, что ограничения
|
||
+system.slice+ будут применяться только к системным службам, а +-.slice+
|
||
действует сразу на все процессы системы, включая не~только службы, но и процессы
|
||
пользовательских сеансов (объединенные в группу +user.slice+, которая находится
|
||
в корневой группе).
|
||
|
||
Воспользуемся этим:
|
||
\begin{Verbatim}
|
||
# systemctl set-property system.slice IPAddressDeny=any IPAddressAllow=localhost
|
||
# systemctl set-property apache.service IPAddressAllow=10.0.0.0/8
|
||
\end{Verbatim}
|
||
Две приведенные команды имеют очень мощный эффект: сначала мы отключаем
|
||
взаимодействие через IP (кроме обратной петли) для всех служб системы, а затем
|
||
добавляем белый список 10.0.0.0/8 (скажем, это локальная сеть вашей компании)
|
||
только для службы веб-сервера Apache.
|
||
|
||
\subsectiona{Практическое применение}
|
||
|
||
Вот несколько идей по практическому применению рассмотренных возможностей:
|
||
\begin{enumerate}
|
||
\item Механизм контрольных списков IP адресов можно рассматривать как
|
||
современную альтернативу классической технологии
|
||
\href{https://en.wikipedia.org/wiki/TCP_Wrapper}{TCP Wrapper}.
|
||
Однако, в отличие от нее, контрольные списки применяются сразу
|
||
ко всем IP-сокетам службы, и не~требуют никакой поддержки со
|
||
стороны приложения. С другой стороны, TCP Wrapper предоставляет
|
||
ряд опций, которые остутствуют в нашей схеме, в частности,
|
||
возможность указания DNS-имен вместо IP-адресов (лично я считаю
|
||
это весьма сомнительной опцией~--- выполнять сетевые операции
|
||
(разрешение имен), причем незащищенные, для того, чтобы
|
||
ограничить работу сети).
|
||
\item В некоторых аспектах наши механизмы могут заменить или хотя бы
|
||
дополнить классический брандмауэр Linux~---
|
||
NetFilter/+iptables+. На текущий момент, контрольные списки
|
||
IP-адресов systemd предоставляют гораздо меньше возможностей,
|
||
чем NetFilter, однако имеют перед ним серьезное преимущество:
|
||
они работают на уровне \emph{приложений}, а не~абстрактных
|
||
TCP/UDP-портов. Классические брандмауэры, в частности,
|
||
NetFilter, вынуждены делать предположение о принадлежности
|
||
пакетов тем или иным службам, основываясь только на номерах
|
||
портов\footnote{Прим. перев.: В ядрах Linux 2.4-2.6
|
||
существовала теоретическая возможность выборки пакетов по
|
||
идентификатору процесса (+-m owner --pid-owner PID+), однако она
|
||
была удалена в выпуске 2.6.14. Много лет спустя, в ядре 3.14 был
|
||
добавлен модуль netfilter +xt_cgroup+, позволяющий выбирать
|
||
пакеты по индексу класса, присвоенного cgroup-контроллером
|
||
+net_class+. Кстати, поддержка соответствующей опции
|
||
+NetClass=+ существовала в systemd с 227 по 229 версию, но была
|
||
удалена при миграции на
|
||
\href{https://www.kernel.org/doc/Documentation/cgroup-v2.txt}%
|
||
{cgroup v2}, где такого контроллера уже нет, зато есть
|
||
вышеописанный механизм контрольных списков.}, однако на практике
|
||
порты часто выбираются динамически. Например, клиенты
|
||
BitTorrent для передачи данных могут использовать любые удобные
|
||
им порты, что крайне затрудняет корректную выборку таких
|
||
пакетов. В случае с контрольными списками все просто: достаточно
|
||
настроить разрешения для юнита службы BitTorrent, и дело в
|
||
шляпе.
|
||
|
||
Замечу что, по большей части, сравнение NetFilter и контрольных
|
||
списков systemd~--- это сравнение теплого с мягким. Контрольные
|
||
списки ориентированы исключительно на конечные машины (клиенты и
|
||
серверы), так как работают только с локальными службами. В то
|
||
время как NetFilter отлично работает на промежуточных
|
||
маршрутизаторах, передающих чистый IP-трафик, никак
|
||
не~привязанный к их локальным процессам.
|
||
\item Контрольные списки предоставляют простой способ обеспечить
|
||
безопасность служб <<из коробки>>. Например, если вы
|
||
сопровождаете пакет службы, которая не~должна требовать доступа
|
||
к сети, добавьте в ее юнит-файл +IPAddressDeny=any+ (и, при
|
||
необходимости, +IPAddressAllow=localhost+), и она будет помещена
|
||
в песочницу, из которой не~сможет сбежать. В systemd подобные
|
||
ограничения уже введены по умолчанию для целого ряда служб,
|
||
например, для системного журнала
|
||
+systemd-journald.service+\footnote{Прим. перев.: Знаменитый
|
||
HTTP-сервер вынесен в отдельную службу
|
||
\href{https://www.freedesktop.org/software/systemd/man/systemd-journal-gatewayd.html}%
|
||
{systemd-journal-gatewayd(8)}, которая
|
||
\hreftt{https://github.com/systemd/systemd/blob/master/system-preset/90-systemd.preset}%
|
||
{отключена} по умолчанию, а в ряде дистрибутивов вообще
|
||
поставляется в отдельном опциональном пакете (в частности,
|
||
RHEL/CentOS, Debian/Ubuntu).}, менеджера логинов
|
||
+systemd-logind+ и обработчика дампов памяти
|
||
+systemd-coredump@.service+, так как мы знаем, что этим службам
|
||
ни~при каких условиях не~нужна сеть.
|
||
\item Механизм контрольных списков IP-адресов можно сочетать с
|
||
одноразовыми службами, что позволяет быстро и эффективно
|
||
изолировать произвольные команды, и даже включать их в конвееры.
|
||
Предположим, что мы не~доверяем нашему бинарнику
|
||
\hreftt{https://linux.die.net/man/1/curl}{curl} (может быть, он
|
||
модифицирован хакером, и обращается на подконтрольные ему
|
||
серверы?), но все равно хотим использовать его для загрузки
|
||
\href{http://0pointer.de/public/casync-kinvolk2017.pdf}{презентации
|
||
с моего последнего доклада по casync}, чтобы распечатать их, и
|
||
при этом быть уверенными, что он не~будет связываться ни~с кем,
|
||
кроме нужного нам сервера (а чтобы было еще интереснее и
|
||
безопаснее, включим описанный в предыдущей главе механизм
|
||
динамических пользователей):
|
||
\begin{Verbatim}
|
||
# systemd-resolve 0pointer.de
|
||
0pointer.de: 85.214.157.71
|
||
2a01:238:43ed:c300:10c3:bcf3:3266:da74
|
||
-- Information acquired via protocol DNS in 2.8ms.
|
||
-- Data is authenticated: no
|
||
# systemd-run --pipe -p IPAddressDeny=any \
|
||
-p IPAddressAllow=85.214.157.71 \
|
||
-p IPAddressAllow=2a01:238:43ed:c300:10c3:bcf3:3266:da74 \
|
||
-p DynamicUser=yes \
|
||
curl http://0pointer.de/public/casync-kinvolk2017.pdf | lp
|
||
\end{Verbatim}
|
||
\end{enumerate}
|
||
|
||
Как и в прошлой главе, в силу масштабности обсуждаемой концепции, приведенный
|
||
список применений не~претендует на полноту, а лишь является затравкой для вашего
|
||
воображения.
|
||
|
||
\subsectiona{Рекомендации сопровождающим пакетов}
|
||
|
||
Механизмы учета и контроля IP-трафика ориентированы прежде всего на системных
|
||
администраторов, а не~разработчиков. Тем не~менее, как я уже заметил выше, для
|
||
служб, которые никак и никогда не~требуют сети, целесообразно добавлять
|
||
настройку +IPAddressDeny=any+ (и опционально +IPAddressAllow=localhost+), чтобы
|
||
повысить безопасность системы сразу <<из коробки>>.
|
||
|
||
Для специализированных дистрибутивов, ориентированных на максимальную
|
||
безопасность, можно предложить более радикальный подход: добавить
|
||
+IPAddressDeny=any+ сразу в +-.slice+ или +system.slice+, чтобы администратор,
|
||
когда ему нужно выпустить в сеть какую-либо службу, вручную командовал
|
||
+systemctl set-property ... IPAddressAllow=...+. Разумеется, это вариант для
|
||
тех дистрибутивов, которые не~боятся ломать обратную совместимость.
|
||
|
||
\subsectiona{Дополнительные замечания}
|
||
|
||
И еще несколько замечаний:
|
||
\begin{enumerate}
|
||
\item Описанные механизмы учета и фильтрации IP-трафика можно совмещать
|
||
с сокет-активацией. При этом, целесообразно настраивать их
|
||
и для сокета, и для активируемой им службы, так как это
|
||
разные юниты с независимыми настройками. Обратите внимание, что
|
||
учет и фильтрация трафика, настроенные для сокет-юнита,
|
||
применяются для всех сокетов, созданных в рамках этого юнита,
|
||
включая переданные активированным службам. Как следствие, трафик
|
||
учитывается именно для юнита сокета, а не~службы. Тот факт, что
|
||
для сокетов, созданных под эгидой сокет-юнита (т.е. при
|
||
сокет-активации), и для сокетов, созданных из кода службы,
|
||
используются \emph{разные} контрольные списки, открывает весьма
|
||
интересные возможности. Например, можно настроить относительно
|
||
свободный доступ для сокета, при этом полностью запретив
|
||
IP-трафик для активируемых им служб~--- в результате эти службы
|
||
смогут взаимодейстовать с внешним миром только через переданный
|
||
им при активации сокет.
|
||
\item Учет и фильтрация IP-трафика работают только с IP-сокетами. В
|
||
частности, сокеты типа +AF_PACKET+ (так называемые <<сырые
|
||
сокеты>>) под эти ограничения не~подпадают\footnote{Прим.
|
||
перев.: Они также <<обходят>> и netfilter~--- именно поэтому
|
||
бесполезно пытаться фильтровать трафик DHCP-клиента с той же
|
||
машины.}. Поэтому целесообразно дополнять контрольные списки
|
||
опцией
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#RestrictAddressFamilies=}%
|
||
{RestrictAddressFamilies=AF\_UNIX AF\_INET AF\_INET6}.
|
||
\item Вы также можете поинтересоваться, может ли журнальная запись о
|
||
потребленных ресурсах, а также +systemd-run+ с ключом +--wait+
|
||
показывать статистику по другим видам ресурсов?
|
||
Ответ утвердительный: например, если вы зададите для юнита
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#CPUAccounting=}%
|
||
{CPUAccounting=yes}, то журнальная запись и +systemd-run+ будут
|
||
показывать статистику по потребленному процессорному времени. В
|
||
ближайшее время мы планируем добавить аналогичную поддержку для
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html\#IOAccounting=}%
|
||
{IOAccounting=}.
|
||
\item Обратите внимание, что учет и фильтрация IP-трафика сами могут
|
||
потреблять определенные ресурсы. Чтобы эти функции работали,
|
||
systemd вставляет в путь прохождения IP-трафика специальную
|
||
eBPF-программу. Однако, в последних версиях ядра выполнение eBPF
|
||
очень неплохо оптимизировано, и работа по оптимизации
|
||
продолжается, так что серьезных проблем с производительностью
|
||
ожидать не~стоит.
|
||
\item Учет трафика не~является иерархическим, то есть, счетчики
|
||
slice-юнита не~суммируют результаты вложенных в него юнитов. Это
|
||
одна из задач, которую мы намерены решить, однако она требует
|
||
доработки кода ядра.
|
||
\item У вас может возникнуть вопрос~--- как соотносятся механизмы
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.exec.html\#PrivateNetwork=}%
|
||
{PrivateNetwork=yes} и +IPAccessDeny=any+? На первый взгляд, они
|
||
работают одинаково: блокируют доступ к сети для службы. Однако,
|
||
при более пристальном рассмотрении обнаруживается ряд отличий.
|
||
Механизм +PrivateNetwork=+ реализован на основе сетевых
|
||
пространств имен ядра Linux. Он полностью изолирует от хоста
|
||
все сетевые операции службы~--- не~только обычные IP-сокеты, но
|
||
и другие виды сетевого взаимодействия (в частности, сырые
|
||
сокеты). Для этого он создает службе отдельное сетевое
|
||
пространство имен, в котором она может общаться только сама
|
||
с собой (через свой личный интерфейс обратной петли). Однако,
|
||
при использовании опции
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.unit.html\#JoinsNamespaceOf=}%
|
||
{JoinsNamespaceOf=} в ту же песочницу могут быть помещены другие
|
||
службы~--- в результате, службы из одной песочницы смогут
|
||
взаимодействовать между собой, но при этом будут изолированы от
|
||
остального мира. С другой стороны, +IPAddressAllow=+ и
|
||
+IPAccessDeny=+ действуют не~столь жестко. Прежде всего, они
|
||
работают только с IP-сокетами и IP-адресами. Кроме того, служба
|
||
с выключенным режимом +PrivateNetwork=+, но включенным
|
||
+IPAccessDeny=any+, все равно способна прочитать список сетевых
|
||
интерфейсов хоста и узнать присвоенные им адреса, хотя и
|
||
не~сможет пересылать данные по протоколу IP. В то же время, с
|
||
включенным режимом +PrivateNetwork=+ служба может видеть только
|
||
свой личный интерфейс +lo+. Короче: в зависимости от конкретной
|
||
ситуации, для изоляции службы можно использовать тот или иной
|
||
вариант, или оба сразу, или вообще ни~одного. Когда это
|
||
возможно, для максимальной безопасности лучше использовать обе
|
||
эти защиты, как это делаем мы для всех основных служб из состава
|
||
systemd.
|
||
\end{enumerate}
|
||
|
||
На этом пока все.
|
||
|
||
\newpage
|
||
\appendix
|
||
|
||
\section{FAQ (часто задаваемые вопросы)\sfnote{Перевод статьи
|
||
<<\href{http://www.freedesktop.org/wiki/Software/systemd/FrequentlyAskedQuestions}%
|
||
{Frequently Asked Questions}>> с официального сайта проекта, по состоянию на
|
||
2013-05-26 08:17:02 (коммит 8cf2b).}}
|
||
|
||
Также смотрите статью
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd/TipsAndTricks}{Tips \&
|
||
Tricks}\footnote{Прим. перев.: На самом деле, смотреть там особо не~на что.
|
||
Б\'{о}льшая часть приведенного там материала вполне соответствует формату FAQ, и
|
||
поэтому была перенесена в текущую главу. Исключением являются только
|
||
<<cgroup tree>> и <<ps with cgroups>>, но они подробно рассмотрены в
|
||
главе~\ref{sec:cgls}.}.
|
||
|
||
\qna{Как изменить текущий уровень выполнения (runlevel)?}
|
||
В systemd уровни выполнения оформлены в виде целевых состояний (target units).
|
||
Команда перехода в заданное состояние выглядит так:
|
||
\begin{Verbatim}
|
||
# systemctl isolate runlevel5.target
|
||
\end{Verbatim}
|
||
|
||
Отметим, что концепция уровней исполнения уже порядком устарела, и вместо
|
||
номеров уровней удобнее оперировать более выразительными именами целевых
|
||
состояний:
|
||
\begin{Verbatim}
|
||
# systemctl isolate graphical.target
|
||
\end{Verbatim}
|
||
|
||
Подобные команды изменят только текущий уровень выполнения. Их действие никак
|
||
не~повлияет на последующие загрузки системы.
|
||
|
||
\qna{Как изменить уровень выполнения, в который система грузится по умолчанию?}
|
||
Соответствующее целевое состояние задается символьной ссылкой
|
||
+/etc/systemd/system/default.target+. Для его смены достаточно перезаписать эту
|
||
ссылку\footnote{Прим. перев.: Начиная с systemd 205, в программу +systemctl+
|
||
были добавлены команды +set-default+ и +get-default+, упрощающие подобные
|
||
действия, например, +systemctl set-default multi-user.target+.}. Например, так:
|
||
\begin{Verbatim}
|
||
# ln -sf /usr/lib/systemd/system/multi-user.target /etc/systemd/system/default.target
|
||
\end{Verbatim}
|
||
или так
|
||
\begin{Verbatim}
|
||
# ln -sf /usr/lib/systemd/system/graphical.target /etc/systemd/system/default.target
|
||
\end{Verbatim}
|
||
|
||
\qna{Как узнать текущий уровень выполнения?}
|
||
В один и тот же момент времени может быть активно несколько целевых состояний,
|
||
поэтому понятие текущего уровня выполнения (единственного и однозначно
|
||
определенного) применимо уже далеко не~всегда. Узнать, какие состояния сейчас
|
||
активны, вы можете при помощи команды
|
||
\begin{Verbatim}
|
||
$ systemctl list-units --type=target
|
||
\end{Verbatim}
|
||
|
||
Если вам нужна именно одна цифра, вы можете воспользоваться классической
|
||
командой +runlevel+, однако, по изложенным выше причинам, она далеко не~всегда
|
||
способна адекватно оценить текущую ситуацию.
|
||
|
||
\qna{Я изменил service-файл, но пакетный менеджер перезаписывает мои изменения
|
||
при каждом обновлении. Что делать?}
|
||
Наиболее разумным решением будет скопировать исходный файл из
|
||
+/usr/lib/systemd/system+ в +/etc/systemd/system+ и внести изменения в
|
||
полученную копию. Юнит-файлы из +/etc+ защищены от посягательств пакетного
|
||
менеджера, и при этом имеют приоритет над файлами из +/usr+. Если же вы захотите
|
||
вернуться к настройкам по умолчанию, достаточно будет просто удалить или
|
||
переименовать созданный вами файл\footnote{Прим. перев.: Начиная с версии
|
||
systemd 198, необязательно копировать файл целиком, если вы хотите
|
||
добавить/изменить отдельные настройки. Можно просто создать в
|
||
+/etc/systemd/system/+ подкаталог +имя_службы.service.d+, а в нем~--- файл с
|
||
именем, заканчивающимся на +.conf+, например, +mysettings.conf+, после чего
|
||
вписать в этот файл необходимые настройки. Они будут применены <<поверх>>
|
||
настроек из штатного файла (расположенного в +/usr+).}.
|
||
|
||
\qna{Служба \texttt{foo.service} по умолчанию запускается только при поступлении
|
||
входящего соединения (или подключении определенного оборудования, или при
|
||
появлении заданного файла). Как сделать так, чтобы она запускалась сразу при
|
||
загрузке?}
|
||
Достаточно поместить символьную ссылку на соответствующий service-файл в каталог
|
||
+multi-user.target.wants/+.
|
||
\begin{Verbatim}
|
||
# ln -sf /usr/lib/systemd/system/foobar.service \
|
||
> /etc/systemd/system/multi-user.target.wants/foobar.service
|
||
# systemctl daemon-reload
|
||
\end{Verbatim}
|
||
|
||
В это каталоге содержатся ссылки на все юниты, которые нужно запустить в
|
||
состоянии multi-user (эквивалент уровня выполнения 3, т.е. загрузка с запуском
|
||
всех служб, но не~графического интерфейса). Так как цель +graphical.target+
|
||
тянет по зависимости и +multi-user.target+, то ваша служба будет запущена также
|
||
и при <<графических>> загрузках.
|
||
|
||
\qna{Я хочу запустить еще один экземпляр getty, как это сделать?}
|
||
Для запуска getty на последовательном порту, достаточно запустить
|
||
соответствующий экземпляр службы +serial-getty@.service+:
|
||
\begin{Verbatim}
|
||
# systemctl start serial-getty@ttyS2.service
|
||
\end{Verbatim}
|
||
|
||
Чтобы обеспечить автоматически запуск getty на этом порту при каждой загрузке,
|
||
нужно поместить соответствующую символьную ссылку в каталог
|
||
+getty.target.wants/+\footnote{Прим. перев.: Приведенная в оригинале команда
|
||
+systemctl enable serial-getty@ttyS2.service+ в systemd версии 209 и ниже
|
||
работать не~будет. Подробнее см.~примечание~\ref{ftn:enableserial}.}:
|
||
\begin{Verbatim}
|
||
# ln -s /usr/lib/systemd/system/serial-getty@.service \
|
||
> /etc/systemd/system/getty.target.wants/serial-getty@ttyS2.service
|
||
# systemctl daemon-reload
|
||
\end{Verbatim}
|
||
|
||
Обратите внимание, что на виртуальных терминалах getty запускаются
|
||
автоматически, как только пользователь переключается на данный терминал.
|
||
Максимальное количество таких терминалов задается параметром +NAutoVTs=+ в файле
|
||
\href{http://www.freedesktop.org/software/systemd/man/logind.html}%
|
||
{logind.conf(7)}. Также см. главу~\ref{sec:getty}.
|
||
|
||
\qna{Как узнать, какой службе принадлежат вот эти процессы?}
|
||
Для этого можно воспользоваться командой +ps+
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ alias psc='ps xawf -eo pid,user,cgroup,args'
|
||
$ psc
|
||
PID USER CGROUP COMMAND
|
||
...
|
||
1 root name=systemd:/systemd-1 /bin/systemd systemd.log_target=kmsg systemd.log_level=debug selinux=0
|
||
415 root name=systemd:/systemd-1/sysinit.service /sbin/udevd -d
|
||
928 root name=systemd:/systemd-1/atd.service /usr/sbin/atd -f
|
||
930 root name=systemd:/systemd-1/ntpd.service /usr/sbin/ntpd -n
|
||
932 root name=systemd:/systemd-1/crond.service /usr/sbin/crond -n
|
||
935 root name=systemd:/systemd-1/auditd.service /sbin/auditd -n
|
||
943 root name=systemd:/systemd-1/auditd.service \_ /sbin/audispd
|
||
964 root name=systemd:/systemd-1/auditd.service \_ /usr/sbin/sedispatch
|
||
937 root name=systemd:/systemd-1/acpid.service /usr/sbin/acpid -f
|
||
941 rpc name=systemd:/systemd-1/rpcbind.service /sbin/rpcbind -f
|
||
944 root name=systemd:/systemd-1/rsyslog.service /sbin/rsyslogd -n -c 4
|
||
947 root name=systemd:/systemd-1/systemd-logger.service /lib/systemd/systemd-logger
|
||
950 root name=systemd:/systemd-1/cups.service /usr/sbin/cupsd -f
|
||
955 dbus name=systemd:/systemd-1/messagebus.service /bin/dbus-daemon --system --address=systemd: --nofork --systemd-activation
|
||
969 root name=systemd:/systemd-1/getty@.service/tty6 /sbin/mingetty tty6
|
||
970 root name=systemd:/systemd-1/getty@.service/tty5 /sbin/mingetty tty5
|
||
971 root name=systemd:/systemd-1/getty@.service/tty1 /sbin/mingetty tty1
|
||
973 root name=systemd:/systemd-1/getty@.service/tty4 /sbin/mingetty tty4
|
||
974 root name=systemd:/user/lennart/2 login -- lennart
|
||
1824 lennart name=systemd:/user/lennart/2 \_ -bash
|
||
975 root name=systemd:/systemd-1/getty@.service/tty3 /sbin/mingetty tty3
|
||
988 root name=systemd:/systemd-1/polkitd.service /usr/libexec/polkit-1/polkitd
|
||
994 rtkit name=systemd:/systemd-1/rtkit-daemon.service /usr/libexec/rtkit-daemon
|
||
...
|
||
\end{Verbatim}
|
||
или просто посмотреть \verb+/proc/$PID/cgroup+. Также см. главу~\ref{sec:cgls}.
|
||
|
||
\qna{Почему вы не~используете inotify для отслеживания изменений в настройках?}
|
||
К сожалению, реализовать это весьма проблематично. За подробностями вы можете
|
||
обратиться к обсуждению в
|
||
\href{https://bugzilla.redhat.com/show_bug.cgi?id=615527}{bugzilla}%
|
||
\footnote{\label{ftn:cons}Прим. перев.: Вкратце, суть проблемы состоит в том,
|
||
что перемещение/переименование файла не~является атомарной операцией, а состоит
|
||
из удаления одного файла и создания другого. Эти операции могут следовать в
|
||
произвольном порядке с некоторым интервалом, что может привести к временному
|
||
исчезновению службы, либо к появлению двух конфликтующих служб.}.
|
||
|
||
\qna{У моей службы есть как штатный service-файл, так и init-скрипт (с тем же
|
||
именем). Какой из этих файлов главнее~---
|
||
\texttt{/usr/lib/systemd/system/foobar.service} или
|
||
\texttt{/etc/init.d/foobar}?}
|
||
При наличии обоих этих файлов, приоритет всегда отдается штатному service-файлу,
|
||
независимо от того, включен ли запуск соответствующего скрипта (например, через
|
||
+chkconfig+) или нет. Обратите внимание, что отключение юнит-файла (в частности,
|
||
через +systemctl disable+) приведет к полному отключению службы, даже если
|
||
init-скрипт при этом будет включен.
|
||
|
||
\qna{Как заставить \texttt{journalctl} выводить полные (не~сокращенные) строки?}
|
||
Используйте
|
||
\begin{Verbatim}
|
||
# journalctl --full
|
||
\end{Verbatim}
|
||
|
||
\qna{Моя служба пытается получить приоритет реального времени, но получает
|
||
ошибку EPERM, хотя обладает всеми необходимыми привилегиями. А без вашего
|
||
systemd все работало!}
|
||
По умолчанию, systemd помещает каждую системную службу в собственную контрольную
|
||
группу контроллера +cpu+. К сожалению, из-за существующего в реализации данного
|
||
контроллера ограничения, это приводит к невозможности получения приоритета
|
||
реального времени для служб. Подробное обсуждение этой проблемы и методы ее
|
||
решения смотрите в приложении~\ref{sec:realtime}.
|
||
|
||
\qna{Моя служба настроена на запуск после \texttt{network.target}, однако она
|
||
все равно запускается раньше, чем появляется сеть. Почему?}
|
||
Это длинная история. См. приложение~\ref{sec:networktarget}.
|
||
|
||
\qna{systemd монтирует в \texttt{/tmp} \texttt{tmpfs}. Как это отключить?}
|
||
Это тоже долгая история. См. приложение~\ref{sec:apifs}.
|
||
|
||
\qna{Как просмотреть список работающих служб?}
|
||
Запустите команду +systemctl+ без параметров\footnote{Прим. перев.: +systemctl+
|
||
без параметров выведет состояние всех активных юнитов (в т.ч. сокетов,
|
||
устройств, точек монтирования, целевых состояний, таймеров, и т.д.). Чтобы
|
||
ограничиться только службами, добавьте параметр +-t service+. Чтобы вывести все
|
||
службы/юниты, включая неактивные, добавьте параметр +-a+.}:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ systemctl
|
||
UNIT LOAD ACTIVE SUB JOB DESCRIPTION
|
||
accounts-daemon.service loaded active running Accounts Service
|
||
atd.service loaded active running Job spooling tools
|
||
avahi-daemon.service loaded active running Avahi mDNS/DNS-SD Stack
|
||
bluetooth.service loaded active running Bluetooth Manager
|
||
colord-sane.service loaded active running Daemon for monitoring attached scanners and registering them with colord
|
||
colord.service loaded active running Manage, Install and Generate Color Profiles
|
||
crond.service loaded active running Command Scheduler
|
||
cups.service loaded active running CUPS Printing Service
|
||
dbus.service loaded active running D-Bus System Message Bus
|
||
...
|
||
\end{Verbatim}
|
||
|
||
\qna{Как узнать текущее состояние службы?}
|
||
Воспользуйтесь командой +systemctl status+:
|
||
\begin{Verbatim}[fontsize=\small]
|
||
$ systemctl status udisks2.service
|
||
udisks2.service - Storage Daemon
|
||
Loaded: loaded (/usr/lib/systemd/system/udisks2.service; static)
|
||
Active: active (running) since Wed, 27 Jun 2012 20:49:25 +0200; 1 day and 1h ago
|
||
Main PID: 615 (udisksd)
|
||
CGroup: name=systemd:/system/udisks2.service
|
||
└ 615 /usr/lib/udisks2/udisksd --no-debug
|
||
|
||
Jun 27 20:49:25 epsilon udisksd[615]: udisks daemon version 1.94.0 starting
|
||
Jun 27 20:49:25 epsilon udisksd[615]: Acquired the name org.freedesktop.UDisks2 on the system message bus
|
||
\end{Verbatim}
|
||
|
||
\qna{Как отследить зависимости между юнитами?}
|
||
Допустим, вы хотите узнать, какие юниты будут запущены при активации
|
||
+multi-user.target+:
|
||
\begin{Verbatim}
|
||
$ systemctl show -p "Wants" multi-user.target
|
||
Wants=rc-local.service avahi-daemon.service rpcbind.service
|
||
NetworkManager.service acpid.service dbus.service atd.service crond.service
|
||
auditd.service ntpd.service udisks.service bluetooth.service cups.service
|
||
wpa_supplicant.service getty.target modem-manager.service portreserve.service
|
||
abrtd.service yum-updatesd.service upowerd.service test-first.service
|
||
pcscd.service rsyslog.service haldaemon.service remote-fs.target
|
||
plymouth-quit.service systemd-update-utmp-runlevel.service sendmail.service
|
||
lvm2-monitor.service cpuspeed.service udev-post.service mdmonitor.service
|
||
iscsid.service livesys.service livesys-late.service irqbalance.service
|
||
iscsi.service netfs.service
|
||
\end{Verbatim}
|
||
|
||
Вместо +Wants+ вы можете использовать другие типы зависимостей: +WantedBy+,
|
||
+Requires+, +RequiredBy+, +Conflicts+, +ConflictedBy+, +Before+,
|
||
+After+\footnote{\label{ftn:wants}Прим. перев.: Разница между +*s+ и +*edBy+~---
|
||
в направлении зависимости. Например, если цель +multi-user.target+ запрашивает
|
||
запуск (+Wants+) службы +rc-local.service+, то она будет
|
||
указана в свойстве +WantedBy+ этой службы. Интересно, что свойство
|
||
+ConflictedBy+ существует, однако задать его напрямую нельзя~--- оно
|
||
формируется только на основании +Conflicts+ других служб. Что касается
|
||
разницы между +Wants+ и +Requires+, то первое является пожеланием, а второе
|
||
требованием. Если требуемый (required) юнит не~сможет запуститься, то
|
||
не~запустится и сам зависящий от него юнит. Пожелания (wants) не~накладывают
|
||
такого ограничения~--- будет сделана попытка запуска зависимого юнита, но
|
||
результат ее никак не~отразится на зависящем юните. А указания \emph{порядка}
|
||
запуска (+Before+, +After+) вообще не~формируют зависимостей и работают только
|
||
тогда, когда такие зависимости, прямо или косвенно, уже заданы.}\footnote{Прим.
|
||
перев. Отметим, что начиная с systemd 198, +systemctl+ поддерживает
|
||
специализированную команду +list-dependencies+, позволяющую отследить прямые и
|
||
косвенные (<<рекурсивные>>) зависимости заданного юнита.}.
|
||
|
||
\qna{Как узнать, что будет запущено при загрузке системы в заданное целевое
|
||
состояние?}
|
||
Вы можете заставить systemd рассчитать <<начальную>> транзакцию, определяющую
|
||
алгоритм загрузки в заданное состояние +foobar.target+:
|
||
\begin{Verbatim}
|
||
$ systemd --test --system --unit=foobar.target
|
||
\end{Verbatim}
|
||
|
||
Обратите внимание, что эта команда предназначена исключительно для отладки, и ее
|
||
работа на самом деле не~ограничивается расчетом транзакции, поэтому не~стоит
|
||
вызывать ее в скриптах.
|
||
|
||
\section{Диагностика неполадок\sfnote{Перевод статьи
|
||
<<\href{http://freedesktop.org/wiki/Software/systemd/Debugging}{Debugging
|
||
systemd Problems}>> с официального сайта проекта, по состоянию на 2017-07-28
|
||
09:44:56 (коммит e8c76).}}
|
||
|
||
\subsection{Диагностика проблем с загрузкой}
|
||
|
||
Если система зависает во время загрузки, прежде всего нужно разобраться, на
|
||
каком этапе возникает проблема~--- до запуска systemd, или после.
|
||
|
||
Для этого надо удалить из командной стоки ядра параметры +quiet+ и +rhgb+. При
|
||
работе systemd на экран выводятся примерно такие сообщения:
|
||
\begin{Verbatim}[commandchars=\\\{\}]
|
||
Welcome to \textcolor{blue}{Fedora \emph{ВЕРСИЯ} (\emph{имя релиза})}!
|
||
Starting \emph{название}...
|
||
[ \textcolor{dgreen}{OK} ] Stared \emph{название}...
|
||
\end{Verbatim}
|
||
(Пример можно посмотреть на
|
||
\href{http://freedesktop.org/wiki/Software/systemd/Debugging?action=AttachFile&do=view&target=f17boot.png}{скриншоте}.)
|
||
|
||
Если у вас есть доступ к оболочке, это значительно упрощает диагностику и
|
||
решение проблем. В том случае, когда до приглашения входа в систему дело так и
|
||
не~доходит, попробуйте переключиться на другую виртуальную консоль, нажав
|
||
CTRL--ALT--F<\emph{цифра}>. При проблемах, связанных с запуском графического
|
||
сервера, может возникать ситуация, когда на первой консоли (+tty1+) приглашение
|
||
ко входу отсутствует, но все остальные консоли при этом работают нормально.
|
||
|
||
Если ни~на одной из виртуальных консолей приглашение так и не~появилось~---
|
||
попробуйте выждать еще \emph{не~менее 5 минут}. Только после этого можно
|
||
будет считать процесс загрузки окончательно зависшим. Если подвисание
|
||
обусловлено всего лишь сбоем при запуске какой-то службы, то после истечения
|
||
тайм-аута проблемная служба будет убита, и загрузка продолжится. Другой
|
||
вариант~--- отсутствует устройство, которое должно быть смонтировано для
|
||
нормальной работы системы. В этом случае загрузка будет остановлена, и система
|
||
перейдет в \emph{аварийный режим (emergency mode)}.
|
||
|
||
\subsubsection{Если у вас нет~доступа к оболочке}
|
||
|
||
Если система не~предоставила вам ни~нормального приглашения, ни~аварийной
|
||
оболочки, то для диагностики проблемы нужно выполнить следующие действия:
|
||
\begin{itemize}
|
||
\item Попытайтесь перезагрузить систему, нажав CTRL--ALT--DEL. Если после
|
||
этого перезагрузки не~произойдет~--- обязательно укажите данное
|
||
обстоятельство, когда будете писать отчет об ошибке (bugreport).
|
||
Чтобы перезагрузить систему, вы можете воспользоваться
|
||
\href{http://fedoraproject.org/wiki/QA/Sysrq}{SysRq} или
|
||
<<аппаратным>> методом.
|
||
\item При следующей загрузке попробуйте воспользоваться некоторыми
|
||
нижеописанными стратегиями.
|
||
\end{itemize}
|
||
|
||
\begin{description}
|
||
\item[Вывод диагностических сообщений на последовательную консоль]%
|
||
\hypertarget{it:serial}{} Если у вас под рукой есть терминал
|
||
последовательной консоли, либо дело происходит в виртуальной машине (в
|
||
частности, virt-manager позволяет просматривать вывод виртуальной машины
|
||
на последовательную консоль: меню Вид (View)~$\Rightarrow$ Текстовые
|
||
консоли (Text Consoles); также можно воспользоваться командой
|
||
+virsh console ИМЯ+), вы можете попросить systemd выводить на эту
|
||
консоль подробную отладочную информацию о ходе загрузки, добавив к
|
||
параметрам ядра следующие аргументы\footnote{\label{ftn:netconsole}
|
||
Прим. перев.: Стоит упомянуть еще об одном отладочном инструменте~---
|
||
\hreftt{https://www.kernel.org/doc/Documentation/networking/netconsole.txt}%
|
||
{netconsole}. Это механизм ядра, позволяющий отправлять содержимое
|
||
буфера +kmsg+ (см.
|
||
\hreftt{http://linux.die.net/man/8/dmesg}{dmesg(8)}) по сети на заданный
|
||
компьютер. Обратите внимание, что его настройка через командную строку
|
||
ядра (параметр +netconsole=...+) работает только если он вкомпилирован в
|
||
ядро монолитно. Если же он собран модулем, его необходимо настраивать
|
||
через +/etc/modprobe.d/*.conf+ или +configfs+ (впрочем, некоторые версии
|
||
+modprobe+ поддерживают чтение параметров модулей из командной строки
|
||
ядра, так что можно попробовать добавить туда
|
||
+netconsole.netconsole=...+), а также задавать его принудительную
|
||
подгрузку через параметр ядра +modules-load=netconsole+. Кроме того, при
|
||
этом надо обеспечить перенаправление логов systemd в буфер ядра~---
|
||
соответствующие параметры см. в разделе~\ref{sssec:kmsg}.}:
|
||
\begin{Verbatim}
|
||
systemd.log_level=debug systemd.log_target=console console=ttyS0,38400 console=tty1
|
||
\end{Verbatim}
|
||
|
||
Вышеприведенный способ удобен для диагностики проблем, непосредственно
|
||
связанных с systemd. Если же сбой происходит при запуске системных служб
|
||
(например, сети), целесообразно перенаправить на консоль вывод
|
||
системного журнала:
|
||
\begin{Verbatim}
|
||
systemd.journald.forward_to_console=1 console=ttyS0,38400 console=tty1
|
||
\end{Verbatim}
|
||
|
||
Вы можете указывать +console=+ несколько раз~--- systemd будет выводить
|
||
информацию во все перечисленные консоли.
|
||
|
||
\item[Загрузка в восстановительном (rescue) или аварийном (emergency)
|
||
режимах] Чтобы загрузиться в восстановительном режиме, добавьте к
|
||
параметрам ядра +systemd.unit=rescue.target+, или просто +1+. Это режим
|
||
эффективен для решения проблем, возникающих на этапе запуска обычных
|
||
служб, когда ключевые компоненты системы уже инициализированы. В такой
|
||
ситуации, вы можете просто отключить проблемную службу. Если же загрузка
|
||
не~доходит даже до восстановительного режима~--- попробуйте менее
|
||
требовательный, аварийный режим.
|
||
|
||
Для загрузки напрямую в режим аварийной оболочки, добавьте к параметрам
|
||
ядра +systemd.unit=emergency.target+, или просто +emergency+. Обратите
|
||
внимание, что в аварийном режиме корневая система по умолчанию
|
||
монтируется в режиме <<только для чтения>>, поэтому перед
|
||
восстановительными работами, связанными с записью на диск, необходимо
|
||
перемонтировать ее в режиме <<для чтения и записи>>:
|
||
\begin{Verbatim}
|
||
mount -o remount,rw /
|
||
\end{Verbatim}
|
||
|
||
Как правило, аварийная оболочка используется для исправления
|
||
некорректных записей в +/etc/fstab+. После внесения необходимых
|
||
изменений, скомандуйте +systemctl daemon-reload+, чтобы systemd увидел
|
||
ваши исправления.
|
||
|
||
Если не~работает даже аварийный режим, попробуйте загрузиться напрямую в
|
||
оболочку, добавив к параметрам ядра +init=/bin/sh+. Такая ситуация может
|
||
возникнуть вследствие повреждения бинарного файла systemd, либо
|
||
библиотек, которые он использует. В этом случае может помочь
|
||
переустановка соответствующих пакетов.
|
||
|
||
Если не~срабатывает даже +init=/bin/sh+, остается лишь попробовать
|
||
загрузиться с другого носителя.
|
||
|
||
\item[Отладочная оболочка]\hypertarget{it:dbgshell}{}
|
||
Вы можете включить специальную отладочную оболочку, которая запускается
|
||
в отдельной консоли на раннем этапе загрузки и позволяет собрать
|
||
необходимую диагностическую информацию, а также провести
|
||
восстановительные операции. Для включения отладочной оболочки
|
||
скомандуйте
|
||
\begin{Verbatim}
|
||
systemctl enable debug-shell.service
|
||
\end{Verbatim}
|
||
или укажите
|
||
\begin{Verbatim}
|
||
systemd.debug-shell=1
|
||
\end{Verbatim}
|
||
в строке параметров ядра при загрузке.
|
||
|
||
\textbf{Совет:} Если вышеприведенная команда +systemctl enable+ сообщает
|
||
об ошибке соединения с системным менеджером (такое возможно, например,
|
||
если вы загрузились с помощью другой операционной системы), вы можете
|
||
запустить ее в <<автономном режиме>>, явно указав параметр +--root=+:
|
||
\begin{Verbatim}
|
||
systemctl --root=/ enable debug-shell.service
|
||
\end{Verbatim}
|
||
|
||
Отладочная оболочка будет запущена с правами +root+ на консоли +tty9+
|
||
при следующей загрузке системы. Чтобы переключиться на нее, нажмите
|
||
CTRL--ALT--F9. Оболочка запускается на самом раннем этапе загрузки и
|
||
позволяет вам проверять состояние служб, читать системные журналы,
|
||
выявлять зависшие задачи (командой +systemctl list-jobs+) и т.д.
|
||
|
||
\textbf{Предупреждение:} Используйте эту оболочку только для отладки!
|
||
Не~забудьте отключить ее после того, как разберетесь с проблемами.
|
||
Оставлять доступную всем и каждому оболочку с правами +root+, мягко
|
||
говоря, небезопасно.
|
||
|
||
Также можно настроить +kbrequest.target+ как псевдоним для
|
||
+debug-shell.service+\footnote{Прим. перев.: Надо полагать, речь идет о
|
||
чем-то вроде <<\texttt{systemctl add-wants kbrequest.target
|
||
debug-shell.service}>>.}~--- тогда отладочная оболочка будет запускаться
|
||
по запросу (ALT–$\uparrow$ из tty). Указанные проблемы с безопасностью
|
||
при этом остаются, но оболочка хотя бы не~будет работать постоянно.
|
||
|
||
\item[Проверка параметров ядра] Для корректной загрузки системы необходимо,
|
||
чтобы каталог +/dev+ был заполнен, хотя бы частично. Убедитесь, что ядро
|
||
Linux собрано с опциями +CONFIG_DEVTMPFS+ и +CONFIG_DEVTMPFS_MOUNT+.
|
||
Кроме того, для корректной работы systemd рекомендуется включить
|
||
поддержку контрольных групп и fanotify (опции +CONFIG_CGROUPS+ и
|
||
+CONFIG_FANOTIFY+ соответственно). Отключение этих опций может привести
|
||
к появлению сообщений об ошибках вида <<Failed to get D-Bus
|
||
connection: No connection to service manager.>> при попытке запуска
|
||
+systemctl+.
|
||
\end{description}
|
||
|
||
\subsubsection{Если у вас есть доступ к оболочке}
|
||
\label{sssec:kmsg}
|
||
|
||
Если вам все-таки удалось получить доступ к оболочке системы, вы можете
|
||
воспользоваться ею для сбора диагностической информации. Загрузите систему со
|
||
следующими параметрами ядра:
|
||
\begin{Verbatim}
|
||
systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M printk.devkmsg=on
|
||
\end{Verbatim}
|
||
В соответствии с ними, systemd будет выводить максимально подробные сообщения о
|
||
процессе загрузки, и направлять их в кольцевой буфер ядра (предпоследний параметр
|
||
обеспечивает соответствующее увеличение размера буфера, последний параметр
|
||
снимает ограничение на скорость вывода сообщений в буфер). Дождавшись запуска
|
||
оболочки, вы можете просмотреть полученный журнал:
|
||
\begin{Verbatim}
|
||
journalctl -b
|
||
\end{Verbatim}
|
||
Отправляя отчет об ошибке, перенаправьте вывод этой команды в файл и
|
||
присоедините его к отчету.
|
||
|
||
Также, вы можете просмотреть список операций, чтобы выявить зависшие задачи:
|
||
\begin{Verbatim}
|
||
systemctl list-jobs
|
||
\end{Verbatim}
|
||
Операции, находящиеся в состоянии <<waiting>>, будут запущены на исполнение
|
||
только после того, как завершатся операции, выполняемые в данный момент
|
||
(состояние <<running>>).
|
||
|
||
\subsection{Диагностика проблем с выключением системы}
|
||
|
||
При зависании системы во время выключения, как и в случае с загрузкой,
|
||
рекомендуется подождать \emph{минимум 5 минут}, чтобы отличить полное зависание
|
||
системы от временного подвисания из-за проблем с отдельными службами. Также
|
||
стоит проверить, реагирует ли система на нажатие CTRL--ALT--DEL.
|
||
|
||
Если процесс остановки системы (при выключении или перезагрузке) зависает
|
||
полностью, прежде всего нужно убедиться, способно ли ядро Linux выключить или
|
||
перезагрузить систему. Для этого воспользуйтесь одной из команд:
|
||
\begin{Verbatim}
|
||
reboot -f
|
||
poweroff -f
|
||
\end{Verbatim}
|
||
|
||
Если хотя бы одна из этих команд не~сработает~--- значит, проблема скорее всего
|
||
не~в systemd, а в ядре.
|
||
|
||
\subsubsection{Система очень долго выключается}
|
||
|
||
Если ваша система все же может выключиться/перезагрузиться, но этот процесс
|
||
длится подозрительно долго, выполните нижеописанные операции:
|
||
\begin{itemize}
|
||
\item Загрузите систему со следующими параметрами ядра:
|
||
\begin{Verbatim}
|
||
systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M printk.devkmsg=on enforcing=0
|
||
\end{Verbatim}
|
||
|
||
\item Создайте файл +/usr/lib/systemd/system-shutdown/debug.sh+,
|
||
добавьте ему право на запуск и запишите в него следующие строки:
|
||
\begin{Verbatim}
|
||
#!/bin/sh
|
||
mount -o remount,rw /
|
||
dmesg > /shutdown-log.txt
|
||
mount -o remount,ro /
|
||
\end{Verbatim}
|
||
|
||
\item Перезагрузите систему.
|
||
\end{itemize}
|
||
|
||
После этого вы можете самостоятельно проанализировать файл +/shutdown-log.txt+,
|
||
и/или присоединить его к вашему сообщению об ошибке.
|
||
|
||
\subsubsection{Система не~может выключиться самостоятельно}
|
||
|
||
Если процесс выключения или перезагрузки вашей системы не~завершается даже через
|
||
несколько минут, и вышеописанный метод с +shutdown-log+ не~сработал, вы можете
|
||
собрать диагностическую информацию другими методами (которые мы уже
|
||
рассматривали применительно к проблемам загрузки):
|
||
\begin{itemize}
|
||
\item Используйте \hyperlink{it:serial}{последовательную
|
||
консоль}\footnote{Прим. перев.: Здесь опять же стоит напомнить
|
||
про +netconsole+ (см. примечание~\ref{ftn:netconsole}).}.
|
||
\item Воспользуйтесь \hyperlink{it:dbgshell}{отладочной оболочкой}~---
|
||
она полезна не~только на ранних стадиях загрузки, но и на
|
||
поздних стадиях остановки системы.
|
||
\end{itemize}
|
||
|
||
\subsection{Просмотр состояния службы и ее журнала}
|
||
|
||
Когда при запуске службы происходит сбой, systemd выводит весьма абстрактное
|
||
сообщение об ошибке:
|
||
\begin{Verbatim}
|
||
# systemctl start foo.service
|
||
Job failed. See system journal and 'systemctl status' for details.
|
||
\end{Verbatim}
|
||
|
||
При этом сама служба может выводить собственное сообщение, но вы (пока что) его
|
||
не~видите. Дело в том, что запуск служб происходит не~из вашей оболочки, а из
|
||
процесса systemd, и поэтому вывод программы не~привязан к вашей консоли. Тем
|
||
не~менее, это вовсе не~означает, что выводимые сообщения теряются. По умолчанию,
|
||
потоки STDOUT и STDERR, принадлежащие запускаемым службам, перенаправляются в
|
||
системный журнал (journal). Туда же попадают и сообщения, отправляемые с помощью
|
||
функции +syslog(3)+. Кроме того, systemd записывает код выхода сбойных
|
||
процессов. Посмотреть собранные данные можно, например, так:
|
||
\begin{Verbatim}
|
||
# systemctl status foo.service
|
||
foo.service - mmm service
|
||
Loaded: loaded (/etc/systemd/system/foo.service; static)
|
||
Active: failed (Result: exit-code) since Fri, 11 May 2012 20:26:23 +0200; 4s ago
|
||
Process: 1329 ExecStart=/usr/local/bin/foo (code=exited, status=1/FAILURE)
|
||
CGroup: name=systemd:/system/foo.service
|
||
|
||
May 11 20:26:23 scratch foo[1329]: Failed to parse config
|
||
\end{Verbatim}
|
||
|
||
В нашем примере, служба запустилась как процесс с идентификатором (PID) 1329,
|
||
после чего завершилась с кодом выхода 1. Если вы запустили команду
|
||
+systemctl status+ от имени пользователя +root+, либо от имени пользователя,
|
||
входящего в группу +adm+, вы также увидите последние несколько строчек,
|
||
записанные службой в журнал. В нашем примере служба выдала всего одно сообщение
|
||
(<<Failed to parse config>>).
|
||
|
||
Чтобы просмотреть весь журнал целиком, воспользуйтесь командой +journalctl+.
|
||
|
||
Если одновременно с journal вы используете и классический демон системного лога
|
||
(например, rsyslog), то все сообщения из журнала будут переданы также и этому
|
||
демону, который запишет их в традиционные лог-файлы (в какие именно~--- зависит
|
||
от его настроек, обычно +/var/log/messages+).
|
||
|
||
\subsection{Подготовка сообщений об ошибках}
|
||
|
||
Если вы собираетесь отправить сообщение об ошибке systemd, пожалуйста,
|
||
включите в него диагностическую информацию, в частности, содержимое системных
|
||
журналов. Журналы должны быть полными (без вырезок) и не~заархивированными.
|
||
|
||
Прежде всего, отправьте сообщение в багтрекер своего дистрибутива. Если же вы
|
||
твердо уверены, что проблема именно в апстримном systemd, проверьте сначала
|
||
список
|
||
\href{https://github.com/systemd/systemd/issues/}{уже
|
||
известных ошибок}. Если вы не~найдете в нем своего случая~---
|
||
\href{https://github.com/systemd/systemd/issues/new}{заводите новую
|
||
запись}.
|
||
|
||
\subsubsection{Что нужно включить в сообщение об ошибке}
|
||
|
||
По возможности, пожалуйста, укажите в самом сообщении, либо присоедините к нему,
|
||
следующую информацию:
|
||
\begin{itemize}
|
||
\item Строку параметров ядра. Ее можно найти в файле конфигурации
|
||
загрузчика (например, +/boot/grub2/grub.cfg+) или в специальном
|
||
файле +/proc/cmdline+.
|
||
\item Файл +journal.txt+, полученный после выполнения команды
|
||
\begin{Verbatim}
|
||
journalctl -b > journal.txt
|
||
\end{Verbatim}
|
||
Перед ее выполнением желательно загрузиться с параметрами ядра
|
||
\begin{Verbatim}
|
||
systemd.log_level=debug systemd.log_target=kmsg log_buf_len=1M printk.devkmsg=on
|
||
\end{Verbatim}
|
||
\item Файл +systemd-dump.txt+, полученный в результате выполнения
|
||
команды\footnote{Прим. перев.: В версиях systemd до 206
|
||
включительно, вместо +systemd-analyze dump+ нужно было
|
||
использовать +systemctl dump+.}
|
||
\begin{Verbatim}
|
||
systemd-analyze dump > systemd-dump.txt
|
||
\end{Verbatim}
|
||
\item Файл +systemd-test.txt+, полученный при помощи команды
|
||
\begin{Verbatim}
|
||
/usr/bin/systemd --test --system --log-level=debug > systemd-test.txt 2>&1
|
||
\end{Verbatim}
|
||
\end{itemize}
|
||
|
||
\section{Совместимость с SysV\sfnote{Перевод статьи
|
||
<<\href{http://www.freedesktop.org/wiki/Software/systemd/Incompatibilities}%
|
||
{Compatibility with SysV}>> с официального сайта проекта, по
|
||
состоянию на 2017-05-23 08:04:14 (коммит 4c503).}}
|
||
|
||
systemd обеспечивает высокий уровень совместимости с поведением классической
|
||
системы инициализации SysV init, реализованной во многих дистрибутивах. Это
|
||
касается как непосредственного взаимодействия с пользователем, так и поддержки
|
||
API для скриптов. Тем не~менее, существует ряд ограничений, обусловленных
|
||
техническими соображениями, а также особенностями дизайна systemd и/или
|
||
дистрибутивов. Ниже приведен список таких ограничений. Б\'{о}льшая их часть
|
||
затрагивает дистрибутивно-специфичные расширения SysV init, и поэтому будет
|
||
ощутима только для пользователей отдельных дистрибутивов.
|
||
\begin{itemize}
|
||
\item Если разработчики вашего дистрибутива отказались от SysV
|
||
init-скрипта в пользу штатного юнит-файла, прямой вызов этого
|
||
скрипта (например, +/etc/init.d/foobar start+), очевидно,
|
||
работать не~будет. Лучше воспользоваться стандартной и
|
||
универсальной командой +/sbin/service foobar start+, которая
|
||
одинаково хорошо работает как с systemd, так и с SysV init.
|
||
Отметим, что прямое обращение к скрипту в любом случае не~вполне
|
||
корректно, так как запущенная служба унаследует часть контекста
|
||
выполнения (переменные окружения, umask, ограничения по
|
||
ресурсам, идентификаторы аудита, и т.д.) от пользовательской
|
||
оболочки, из которой был запущен скрипт. Вызов скрипта через
|
||
+/sbin/service+ исправляет этот недостаток, пусть и частично.
|
||
Кроме того, стоит заметить, что стандарт LSB предписывает
|
||
обращаться к службам только через +/sbin/service+.
|
||
(Вышеуказанное ограничение \emph{не}~распространяется на
|
||
дистрибутивы, которые одновременно поддерживают и init-скрипты,
|
||
и юнит-файлы. В таких дистрибутивах даже прямое обращение к
|
||
скрипту при необходимости будет преобразовано в соответствующий
|
||
запрос к systemd\footnote{Прим. перев.: Такое поведение
|
||
задают разработчики дистрибутива, при помощи
|
||
корректировки файла, содержащего стандартные функции для
|
||
init-скриптов (например, +/etc/rc.d/init.d/functions+ в
|
||
Fedora или +/etc/rc.status+ в openSUSE). Этот файл
|
||
вызывается практически из всех init-скриптов в самом начале их
|
||
работы.}.)
|
||
\item Информация о зависимостях служб, прописанная в LSB-заголовке
|
||
скрипта, играет существенную роль. Большинство реализаций SysV
|
||
в различных дистрибутивах практически не~используют эту
|
||
информацию, либо используют ее очень ограниченно. Вследствие
|
||
этого, информацию о зависимостях часто указывали некорректно
|
||
или не~полностью. В отличие от подобных реализаций, systemd
|
||
интерпретирует эти заголовки корректно, и использует
|
||
содержащуюся в них информацию при выполнении различных операций
|
||
со службами (а не~только в момент их установки, как это делают
|
||
некоторые другие реализации SysV\footnote{Прим. перев.:
|
||
Например, +insserv+, используемый как дополнение к SysV init в
|
||
Debian (а ранее и в openSUSE).}).
|
||
\item Все операции со скриптами ограничены по времени при помощи
|
||
тайм-аутов. В отличие от классических SysV-систем, где зависший
|
||
init-скрипт полностью останавливает загрузку, systemd
|
||
ограничивает максимальную длительность работы скрипта пятью
|
||
минутами.
|
||
\item Службы запускаются в совершенно чистом контексте исполнения.
|
||
Они уже не~наследуют контекст у оболочки, из которой был вызван
|
||
их скрипт. В частности, это касается специфических переменных,
|
||
таких как \verb+$HOME+. Как следствие, скрипты, их использующие,
|
||
не~смогут работать корректно.
|
||
\item Службы и их скрипты не~могут читать с STDIN~--- для них этот поток
|
||
подключен к +/dev/null+. Следовательно, интерактивные
|
||
init-скрипты не~поддерживаются (в частности, игнорируется
|
||
используемый в LSB-заголовках скриптов Debian флаг
|
||
+X-Interactive+). К счастью, большинство дистрибутивов все равно
|
||
не~поддерживало интерактивные init-скрипты. Если вашему скрипту
|
||
нужно запросить пароль для зашифрованного диска или
|
||
SSL-ключа, вы можете воспользоваться для этого специальным
|
||
механизмом, предоставляемым systemd (см.
|
||
\href{http://www.freedesktop.org/wiki/Software/systemd/PasswordAgents}%
|
||
{описание} и
|
||
\href{http://www.freedesktop.org/software/systemd/man/systemd-ask-password.html}%
|
||
{страницу руководства}).
|
||
\item Дополнительные команды для init-скриптов (кроме стандартных,
|
||
таких, как +start+, +stop+, +status+, +restart+, +reload+,
|
||
+try-restart+, +force-reload+\footnote{Прим. перев.: Точный
|
||
список <<стандартных>> команд для SysV init-скриптов зависит от
|
||
используемого дистрибутива. Так уж исторически сложилось, что
|
||
практически каждый дистрибутив использовал собственные стандарты
|
||
интерфейсов SysV init. Это отразилось и на <<заглушках>>,
|
||
используемых для обратной совместимости. Выше приведен список
|
||
команд, которые поддерживаются как в Fedora, так и в openSUSE.})
|
||
не~поддерживаются\footnote{Прим. перев.: В частности, это
|
||
ограничение касается специфичных для init-скрипта
|
||
+/etc/init.d/iptables+ команд +save+ и +panic+.}.
|
||
\item Также не~поддерживается возможность указывать после стандартных
|
||
команд скрипту дополнительные параметры. Такое расширение SysV
|
||
не~прописано ни~в каких стандартах, и реализация его поддержки
|
||
была бы весьма проблематичной.
|
||
\item Переопределение команды +restart+ не~поддерживается. Она
|
||
реализована непосредственно в systemd как последовательное
|
||
выполнение команд +stop+ и +start+.
|
||
\item systemd останавливает только те службы, которые выполняются.
|
||
Традиционный SysV init при завершении
|
||
работы системы запускает все скрипты, предписанные +K*+-ссылками
|
||
для текущего уровня исполнения. systemd в аналогичной ситуации
|
||
действует более последовательно, и не~пытается остановить те
|
||
службы, которые не~были запущены.
|
||
\item Для уровней исполнения 0 и 6 все +S*+- и +K*+-ссылки игнорируются.
|
||
При завершении работы системы останавливаются все работающие
|
||
службы, и никаких новых SysV-служб не~запускается.
|
||
\item Если systemd не~знает PID главного процесса службы, он не~сможет
|
||
отслеживать ее работу. В частности, если этот процесс
|
||
<<самостоятельно>> (без сигнала от системного менеджера) завершит
|
||
работу, systemd не~будет знать о том, что служба остановилась.
|
||
Поэтому настоятельно рекомендуется указывать в init-скрипте
|
||
предложенный Red Hat заголовок-комментарий +pidfile:+,
|
||
позволяющий systemd найти PID-файл вашей службы и получить из
|
||
него PID главного процесса. Из-за того, что в некоторых
|
||
init-скриптах запускаются дополнительные процессы, выполняющие
|
||
предварительную настройку для нужд службы, в общем случае
|
||
systemd не~должен рассматривать завершение процесса, запущенного
|
||
из init-скрипта, как завершение работы всей службы. Именно
|
||
поэтому для корректного отслеживания ее статуса systemd
|
||
требуется явное указание PID-файла. (Обратите внимание, что
|
||
заголовок +pidfile:+ можно указывать в init-скрипте не~более
|
||
одного раза.)
|
||
\item Поддержка уровней исполнения (runlevels) в systemd имеет некоторые
|
||
ограничения. Хотя все уровни исполнения SysV имеют
|
||
соответствующие им целевые состояния (target units), далеко
|
||
не~все целевые состояния имеют соответствующие им уровни
|
||
исполнения. Это обусловлено тем, что механизм целевых состояний
|
||
отличается гораздо б\'{о}льшей гибкостью и эффективностью,
|
||
чем система уровней исполнения. Как следствие, в некоторых
|
||
случаях попытка узнать текущий уровень исполнения (например,
|
||
при помощи +/sbin/runlevel+) может вернуть просто <<+N+>> (т.е.
|
||
<<уровень выполнения неизвестен>>), что приведет к нарушению
|
||
работы скриптов, использующих явную проверку текущего уровня
|
||
исполнения. Избегайте подобных проверок.
|
||
\item +/sbin/chkconfig+ и аналогичные программы могут выводить
|
||
некорректную информацию о состоянии службы (включена/отключена).
|
||
Они оперируют только init-скриптами, игнорируя штатные
|
||
юнит-файлы. Кроме того, они опираются на механизм уровней
|
||
выполнения, который поддерживается не~полностью (см. выше).
|
||
\item По умолчанию, уровни исполнения 2, 3 и 4 являются эквивалентами
|
||
целевого состояния +multi-user.target+. Если включить службу на
|
||
одном из них, то она будет включена и на остальных. Впрочем, это
|
||
лишь настройка по умолчанию, и ничто не~мешает вам определить их
|
||
независимо. Тем не~менее, мы рекомендуем отказаться от уровней
|
||
исполнения в пользу более гибкого механизма целевых состояний
|
||
systemd.
|
||
\item Дистрибутивно-специфичные квази-уровни исполнения, используемые
|
||
для организации запуска скриптов на ранних стадиях загрузки, больше
|
||
не~поддерживаются\footnote{Прим. перев.: Начиная с версии 196,
|
||
см. коммит
|
||
\href{http://cgit.freedesktop.org/systemd/systemd/commit/?id=3cdebc217c42c8529086f2965319b6a48eaaeabe}%
|
||
{3cdeb} от 16 ноября 2012 г.}. Примерами таких уровней являются
|
||
+S+ в Debian и +b+ в openSUSE. Разумеется, это никак
|
||
не~затрагивает поддержку обычных уровней исполнения, которую мы
|
||
намерены сохранять еще очень долго.
|
||
\item В системах с SysV, изменение init-скриптов и любых других файлов,
|
||
отвечающих за настройку процесса загрузки (например,
|
||
+/etc/fstab+), обычно вступают в силу немедленно. В то же время,
|
||
в системах с systemd, init-скрипты и другие конфигурационные
|
||
файлы, влияющие на процесс загрузки, перечитываются только
|
||
после выполнения команды +systemctl daemon-reload+ (обратите
|
||
внимание, что некоторые команды, например, +systemctl enable+ и
|
||
+systemctl disable+, выполняют данную операцию
|
||
автоматически)\footnote{Прим. перев.: Стоит отметить, что на
|
||
самом деле все немного сложнее. В частности, даже в системе с
|
||
systemd изменения в исполняемом коде SysV init-скриптов должны
|
||
вступать в действие немедленно. В то же время, изменения в их
|
||
\emph{заголовках} (оформленных в виде комментариев и содержащих
|
||
служебную информацию, в частности, о зависимостях между
|
||
службами), а также сделанные <<вручную>> (командами +ln+ и +rm+)
|
||
изменения символьных ссылок в +/etc/rc?.d/+ действительно
|
||
срабатывают только после +systemctl daemon-reload+. Это
|
||
обусловлено тем, что симлинки и информация из заголовков
|
||
используются при генерации unit-файлов на основе
|
||
init-скриптов, которая осуществляется программой
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-sysv-generator.html}%
|
||
{systemd-sysv-generator(8)} в начале процесса загрузки, а
|
||
также каждый раз после выполнения +systemctl daemon-reload+.
|
||
Аналогично и с +/etc/fstab+, на основе которого
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd-fstab-generator.html}%
|
||
{systemd-fstab-generator(8)} генерирует набор mount-юнитов.}.
|
||
Это сделано специально, для обеспечения консистентности вносимых
|
||
изменений, и позволяет избежать ситуации, когда считывание
|
||
происходит непосредственно в момент редактирования конфигурации
|
||
администратором\footnote{Прим. перев.: Пример подобной проблемы
|
||
рассмотрен примечании~\ref{ftn:cons}.}.
|
||
\item Не~поддерживается задание двух и более записей для одного и
|
||
того же пути монтирования в +/etc/fstab+. В systemd каждой точке
|
||
монтирования в каждый момент времени может соответствовать
|
||
только один mount-юнит. Также стоит отметить, что порядок
|
||
перечисления записей в +/etc/fstab+ не~играет никакой роли:
|
||
монтирование всех точек выполняется параллельно, с учетом
|
||
зависимостей, автоматически рассчитанных на основе расположения
|
||
монтируемого объекта и точки монтирования\footnote{Прим. перев.:
|
||
Например, если указаны точки монтирования +/var/tmp+ и +/var+,
|
||
сначала монтируется +/var+, и только потом +/var/tmp+. Более
|
||
подробно автоматические зависимости mount-юнитов рассмотрены в
|
||
\href{https://www.freedesktop.org/software/systemd/man/systemd.mount.html\#Implicit\%20Dependencies}%
|
||
{руководстве}.}.
|
||
\end{itemize}
|
||
|
||
|
||
\section{Предсказуемые имена сетевых интерфейсов\sfnote{Перевод статьи
|
||
<<\href{http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames}%
|
||
{Predictable Network Interface Names}>> с официального сайта проекта, по
|
||
состоянию на 2016-11-17 17:52:54 (коммит fe089).}}
|
||
|
||
Начиная с версии 197, systemd/udev присваивает сетевым интерфейсам (Ethernet,
|
||
WLAN, WWAN\footnote{Прим. перев.: WWAN (Wireless Wide Area Network)~---
|
||
относительно новый термин, обозначающий технологии глобальных беспроводных
|
||
компьютерных сетей, такие, как GPRS, 3G, WiMAX и т.д.}) стабильные,
|
||
предсказуемые имена. Новая схема именования несколько отличается от классической
|
||
(+eth0+, +eth1+, +wlan0+, \ldots{}), однако при этом лишена и многих ее
|
||
недостатков.
|
||
|
||
\subsection{Зачем?}
|
||
|
||
Классическая схема именования сетевых интерфейсов, используемая ядром,
|
||
предполагает последовательное присвоение интерфейсам имен +eth0+, +eth1+ и т.д.,
|
||
по мере опроса оборудования соответствующими драйверами (device probing). На
|
||
современных системах такой опрос не~обеспечивает гарантированного соблюдения
|
||
одного и того же порядка поступления ответов. Вследствие этого, соответствие
|
||
имен реальным интерфейсам не~является чем-то фиксированным, и очень может быть,
|
||
что интерфейс, который сейчас называется +eth0+, при следующей загрузке
|
||
превратится в +eth1+. Такая ситуация приводит к целому ряду проблем, в
|
||
частности, к проблемам с безопасностью: в настройках брандмауэра интерфейсы
|
||
идентифицируются как раз по именам.
|
||
|
||
Существует несколько подходов к решению этой проблемы. В течение многих лет udev
|
||
поддерживал механизм постоянной привязки имен к интерфейсам на основе
|
||
MAC-адресов. Такой подход имел множество недостатков, в том числе: требование
|
||
доступности корневого каталога на запись (что возможно далеко не~всегда);
|
||
необходимость внесения изменений в образ системы после загрузки на новом
|
||
оборудовании; привязка к MAC-адресам, которые далеко не~всегда являются
|
||
фиксированными (в частности, это касается многих встраиваемых систем и
|
||
механизмов виртуализации). Тем не~менее, основная проблема такого подхода
|
||
состояла в том, что он использовал имена из того же адресного пространства
|
||
(+ethX+), что и ядро. Вследствие этого, возникали ситуации <<гонки>> между udev
|
||
и ядром, когда udev пытался присвоить интерфейсу имя, которое ядро уже выдало
|
||
другому интерфейсу, что приводило к сбою операции переименования. Вследствие
|
||
перечисленных недостатков, данный механизм был удален из
|
||
systemd/udev\footnote{Прим. перев.: См. коммит
|
||
\href{http://cgit.freedesktop.org/systemd/systemd/commit/?id=3e2147858f21943d5f4a781c60f33ac22c6096ed}%
|
||
{3e214} от 3 апреля 2012 года, в котором, среди прочего, был удален каталог
|
||
+src/udev/src/rule_generator+.}.
|
||
|
||
Другая попытка решения обсуждаемой проблемы~--- biosdevname, программа,
|
||
формирующая имена интерфейсов на основании их физического расположении на
|
||
материнской плате. Соответствующая информация запрашивается у BIOS. В чем-то
|
||
такая схема похожа на ту, которую udev уже давно использует для формирования
|
||
стабильных символьных ссылок на различные устройства (+/dev/*/by-path/*+), но,
|
||
тем не~менее, в ее основе лежат несколько иные алгоритмы (udev, формируя свои
|
||
символьные ссылки, опирается на идентификационные данные, предоставленные
|
||
ядром, в то время как biosdevname использует свои собственные схемы).
|
||
|
||
И наконец, многие дистрибутивы в своих сетевых скриптах поддерживают механизмы,
|
||
позволяющие присваивать интерфейсам имена, выбранные пользователем (например,
|
||
+internet0+, +dmz0+, \ldots). Если бы не~необходимость явного вмешательства
|
||
пользователя, это было бы замечательным решением.
|
||
|
||
Мы пришли к выводу, что наилучшим вариантом для настроек по умолчанию будет
|
||
схема, являющаяся обобщением и развитием механизма biosdevname. Присвоение
|
||
интерфейсам имен на основании информации об их физическом расположении имеет ряд
|
||
существенных достоинств: такие имена формируются автоматически, всегда
|
||
предсказуемы, а также остаются неизменными даже при добавлении и удалении
|
||
оборудования, что позволяет без лишних проблем заменять сломанные сетевые
|
||
устройства. Тем не~менее, такие выглядят немного сложнее, чем привычные +eth0+ и
|
||
+wlan0+, например: +enp5s0+.
|
||
|
||
\subsection{Что именно было изменено в версии 197?}
|
||
|
||
В версии 197 мы добавили в systemd/udevd встроенную поддержку нескольких
|
||
различных механизмов именования сетевых интерфейсов, получив в итоге схему,
|
||
похожую на biosdevname, но отличающуюся большей гибкостью и максимально
|
||
приближенную к алгоритмам идентификации устройств, используемым в ядре.
|
||
В частности, udev теперь штатно поддерживает следующие схемы именования
|
||
сетевых интерфейсов:
|
||
\begin{enumerate}
|
||
\item Имена устройств, встроенных в материнскую плату, формируются на
|
||
основании информации от прошивки\footnote{Прим. перев.:
|
||
BIOS, (U)EFI, SPARC Boot PROM, \ldots.}, например: +eno1+.
|
||
\item Имена устройств, подключенных в слоты PCI Express, формируются
|
||
также на основании информации от прошивки, например: +ens1+.
|
||
\item Имена устройств формируются исходя из физического расположении
|
||
точки подключения оборудования, например, +enp2s0+.
|
||
\item Имена устройств формируются на основе их MAC-адресов, например,
|
||
+enx78e7d1ea46da+.
|
||
\item Используются классические, непредсказуемые имена, присвоенные
|
||
ядром, например, +eth0+.
|
||
\end{enumerate}
|
||
|
||
По умолчанию, systemd 197 при именовании сетевых интерфейсов последовательно
|
||
пытается применить схемы 1--3 (для первых двух требуется информация от
|
||
прошивки). Если ни одна из них не~подходит, используется схема 5. Что касается
|
||
схемы 4~--- она не~задействована по умолчанию, однако ее можно включить вручную.
|
||
|
||
Вышеописанная комбинированная схема используется лишь \emph{в последнюю
|
||
очередь}. Если у вас установлена программа biosdevname, для именования сетевых
|
||
устройств будет использоваться именно она. Кроме того, приоритет предоставляется
|
||
любым правилам, добавленным пользователем или разработчиками дистрибутива.
|
||
|
||
\subsection{Еще раз, что здесь хорошего?}
|
||
|
||
Достоинства нашей новой схемы:
|
||
\begin{itemize}
|
||
\item Имена интерфейсов остаются неизменными после перезагрузок.
|
||
\item Имена интерфейсов остаются неизменными при добавлении или
|
||
удалении устройств (если ваша прошивка это позволяет).
|
||
\item Имена интерфейсов остаются неизменными при обновлении/изменении
|
||
ядра и драйверов\footnote{Прим. перев.: На самом деле, не~все
|
||
так просто. Если, в результате обновлении ядра/драйверов, в них
|
||
появится ранее отсутствовавшая поддержка вашей прошивки,
|
||
не~исключена вероятность, что имена некоторых интерфейсов
|
||
перейдут с третьей схемы на первую или вторую. Будьте готовы к
|
||
такому развитию событий.}.
|
||
\item Имена интерфейсов остаются неизменными при замене сломанных
|
||
сетевых карт новыми.
|
||
\item Имена формируются автоматически, безо всякого вмешательства
|
||
пользователя.
|
||
\item Имена являются предсказуемыми: достаточно просто взглянуть на
|
||
вывод +lspci+, чтобы узнать, как будет называться конкретное
|
||
устройство.
|
||
\item Изменения в аппаратной конфигурации не~приводят к необходимости
|
||
записи в каталог +/etc+.
|
||
\item Полная поддержка систем, корень которых доступен только
|
||
для чтения.
|
||
\item Логика именования аналогична той, которая используется
|
||
udev для формирования стабильных символьных ссылок в каталоге
|
||
+/dev+ (+by-path+).
|
||
\item Работает как на x86, так и на~других архитектурах.
|
||
\item Работает одинаково во всех дистрибутивах, использующих
|
||
systemd/udev.
|
||
\item От этой схемы очень легко отказаться (см. ниже).
|
||
\end{itemize}
|
||
|
||
Есть ли у нее недостатки? Да. Раньше, если на компьютере имелся всего один
|
||
сетевой интерфейс, можно было с высокой долей вероятности утверждать, что он
|
||
называется +eth0+. Теперь же, прежде чем администратор начнет настраивать сеть,
|
||
он должен как минимум просмотреть список сетевых интерфейсов.
|
||
|
||
\subsection{Мне не~нравится ваша схема. Как ее отключить?}
|
||
|
||
У вас есть три варианта:
|
||
\begin{enumerate}
|
||
\item Вы можете полностью отключить новую схему, вернувшись к
|
||
классическим непредсказуемым именам. Для этого достаточно
|
||
заблокировать (замаскировать) link-файл udev, отвечающий за
|
||
именование интерфейсов
|
||
\begin{Verbatim}
|
||
ln -s /dev/null /etc/systemd/network/99-default.link
|
||
\end{Verbatim}
|
||
\item Вы можете вручную назначить интерфейсам наиболее понятные для вас
|
||
имена (например, +internet0+, +dmz0+, +lan0+). Для этого
|
||
создайте соответствующие link-файлы в каталоге
|
||
+/etc/systemd/network+ и укажите в них нужные вам имена (или
|
||
схемы именования) для одного, нескольких или сразу всех
|
||
интерфейсов\footnote{Прим. перев.: Там же можно изменить
|
||
используемую по умолчанию схему именования, скопировав файл
|
||
+/usr/lib/systemd/network/99-default.link+ и поменяв значение
|
||
параметра
|
||
\hreftt{https://www.freedesktop.org/software/systemd/man/systemd.link.html\#NamePolicy=}%
|
||
{NamePolicy=} на нужное значение.}. За
|
||
подробностями вы можете обратиться к странице руководства
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd.link.html}{systemd.link(5)}.
|
||
\item Вы можете указать параметр +net.ifnames=0+ в строке параметров
|
||
ядра (отключает использование предсказуемых имен для текущей
|
||
загрузки).
|
||
\end{enumerate}
|
||
|
||
\subsection{Как именно работает новая схема?}
|
||
|
||
Подробности технической реализации описаны в блоке комментариев в
|
||
\href{https://github.com/systemd/systemd/blob/master/src/udev/udev-builtin-net_id.c#L20}%
|
||
{исходном коде net\_id built-in}. Ознакомьтесь с ним, если у вас возникают
|
||
вопросы, касающиеся расшифровки новых имен\footnote{Прим. перев.: Далее
|
||
приводится перевод упомянутого блока комментариев. Последним коммитом,
|
||
затронувшим данный файл, на момент перевода является 86a48 от 18 мая 2017 г.}.
|
||
|
||
\begin{Verbatim}
|
||
Предсказуемые имена сетевых интерфейсов формируются на основании:
|
||
- индексов встроенных в материнскую плату устройств (по информации от прошивки)
|
||
- индексов hotplug-слотов PCI-E (по информации от прошивки)
|
||
- физического расположения точки подключения оборудования
|
||
- MAC-адресов
|
||
|
||
http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames
|
||
|
||
Первые два символа в имени определяют тип интерфейса:
|
||
en -- ethernet
|
||
sl -- SLIP (IP через последовательный порт)
|
||
wl -- WLAN
|
||
ww -- WWAN
|
||
|
||
Последующие символы определяеются используемой схемой:
|
||
b<number> -- для устройств, подключенных по шине BCMA (<number> соответствует
|
||
BCMA bus core number)
|
||
c<bus_id> -- для устройств, адресуемых через Channel Command Words (CCW).
|
||
<bus_id> соответствует CCW bus-ID для простого (использующего один
|
||
субканал) или группированного (использующего несколько субканалов)
|
||
устройства, указывается без ведущих нулей [s390]
|
||
o<index>[n<phys_port_name>|d<dev_port>] -- для устройств, встроенных
|
||
в материнскую плату
|
||
s<slot>[f<function>][n<phys_port_name>|d<dev_port>] -- для hotplug-слотов
|
||
x<MAC> -- при именовании по MAC-адресу
|
||
[P<domain>]p<bus>s<slot>[f<function>][n<phys_port_name>|d<dev_port>]
|
||
-- на основании физического расположения PCI-устройства
|
||
[P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>]
|
||
-- идентификационная цепочка для USB-устройств
|
||
v<slot> -- номер слота для VIO-устройств (IBM PowerVM)
|
||
a<vendor><model>i<instance> -- идентификатор ACPI для устройств, подключенных
|
||
через шину платформы (ARM64)
|
||
|
||
Все многофункциональные (multi-function) PCI-устройства содержат в своем имени
|
||
номер функции "f<function>" (отсчитываются от нуля).
|
||
|
||
При именовании по расположению устройства, индекс PCI-домена "P<domain>"
|
||
указывается только в том случае, если он отличен от нулевого.
|
||
|
||
Для USB-устройства формируется полная цепочка номеров портов хабов, через
|
||
которые оно подключено. Если итоговая строка будет длиннее 15 символов, она
|
||
не экспортируется.
|
||
Стандартные значения config == 1 и interface == 0 опускаются.
|
||
|
||
Примеры:
|
||
|
||
Подключенная к PCI сетевая карта, которая идентифицируется прошивкой
|
||
по индексу "1":
|
||
ID_NET_NAME_ONBOARD=eno1
|
||
ID_NET_NAME_ONBOARD_LABEL=Ethernet Port 1
|
||
|
||
Сетевая карта, включенная в hotplug PCI-слот, который идентифицируется
|
||
прошивкой:
|
||
/sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1
|
||
ID_NET_NAME_MAC=enx000000000466
|
||
ID_NET_NAME_PATH=enp5s0
|
||
ID_NET_NAME_SLOT=ens1
|
||
|
||
Multi-function PCI устройство с двумя портами:
|
||
/sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.0/net/enp2s0f0
|
||
ID_NET_NAME_MAC=enx78e7d1ea46da
|
||
ID_NET_NAME_PATH=enp2s0f0
|
||
/sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.1/net/enp2s0f1
|
||
ID_NET_NAME_MAC=enx78e7d1ea46dc
|
||
ID_NET_NAME_PATH=enp2s0f1
|
||
|
||
Подключенная к PCI WLAN-карта:
|
||
/sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0/net/wlp3s0
|
||
ID_NET_NAME_MAC=wlx0024d7e31130
|
||
ID_NET_NAME_PATH=wlp3s0
|
||
|
||
Встроенный USB 3G модем:
|
||
/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.6/net/wwp0s29u1u4i6
|
||
ID_NET_NAME_MAC=wwx028037ec0200
|
||
ID_NET_NAME_PATH=wwp0s29u1u4i6
|
||
|
||
Подключенный по USB Android-смартфон:
|
||
/sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/net/enp0s29u1u2
|
||
ID_NET_NAME_MAC=enxd626b3450fb5
|
||
ID_NET_NAME_PATH=enp0s29u1u2
|
||
|
||
Группированный CCW-интерфейс на s390:
|
||
/sys/devices/css0/0.0.0007/0.0.f5f0/group_device/net/encf5f0
|
||
ID_NET_NAME_MAC=enx026d3c00000a
|
||
ID_NET_NAME_PATH=encf5f0
|
||
\end{Verbatim}
|
||
|
||
\section{Специальные файловые системы\sfnote{Перевод статьи
|
||
<<\href{http://www.freedesktop.org/wiki/Software/systemd/APIFileSystems}{API
|
||
File Systems}>> с официального сайта проекта, по состоянию на 2015-02-20
|
||
15:48:02 (коммит e2db9).}}
|
||
\label{sec:apifs}
|
||
|
||
\yousaywtfsk{Итак, вы запустили программу mount(8), и увидели в ее выводе
|
||
множество странных файловых систем, не~указанных в /etc/fstab. У вас могут
|
||
возникнуть вопросы: <<Как от них избавиться?>> или <<Как задать для них
|
||
параметры монтирования?>>.}
|
||
|
||
В Linux предусмотрено несколько способов взаимодействия программ из пространства
|
||
пользователя с ядром. Наиболее популярными механизмами являются системные вызовы
|
||
(syscall), интерфейсы Netlink, а также виртуальные файловые системы (ФС), такие,
|
||
как +/proc+ и +/sys+. Подобные ФС являются лишь программными интерфейсами, и
|
||
не~обеспечивают долговременного хранения данных (в частности, между
|
||
перезагрузками системы). Они просто используют классические механизмы работы с
|
||
файлами для предоставления доступа к различным данным и настройкам. Кроме того,
|
||
существуют специальные ФС, используемые программами для собственных нужд, в
|
||
частности, для хранения сегментов разделяемой памяти (shared memory), временных
|
||
файлов и сокетов. В данной статье мы обсудим все эти разновидности
|
||
\emph{специальных файловых систем}. Ниже представлен список таких ФС,
|
||
поддерживаемых в типовых Linux-системах:
|
||
\begin{itemize}
|
||
\item +/sys+ предоставляет доступ к драйверам и устройствам, а также
|
||
некоторым другим параметрам ядра;
|
||
\item +/proc+ дает доступ к информации о выполняемых процессах,
|
||
настройкам ядра, а также другим параметрам;
|
||
\item +/dev+ отображает файлы устройств (device nodes);
|
||
\item +/run+ содержит файлы и сокеты, используемые программами;
|
||
\item +/tmp+ хранит временные и часто изменяемые объекты (X);
|
||
\item +/sys/fs/cgroup+ (и другие файловые системы, смонтированные в
|
||
подкаталогах этого каталога) позволяют работать с иерархией
|
||
контрольных групп;
|
||
\item +/sys/kernel/security+, +/sys/kernel/debug+ (X),
|
||
+/sys/kernel/config+ (X) предоставляют доступ к
|
||
специализированным механизмам ядра;
|
||
\item +/sys/fs/selinux+ используется для взаимодействия с SELinux;
|
||
\item +/dev/shm+ содержит объекты разделяемой памяти;
|
||
\item +/dev/pts+ обеспечивает доступ к псевдо-TTY устройствам;
|
||
\item +/proc/sys/fs/binfmt_misc+ используется для регистрации в ядре
|
||
дополнительных бинарных форматов (X);
|
||
\item +/dev/mqueue+ содержит объекты IPC-механизма mqueue (X);
|
||
\item +/dev/hugepages+ позволяет программам запрашивать выделение
|
||
<<гигантских>> страниц памяти (X);
|
||
\item +/sys/fs/fuse/connections+ обеспечивает доступ к
|
||
FUSE-соединениям (X);
|
||
\item +/sys/firmware/efi/efivars+ предоставляет доступ к переменным EFI;
|
||
\end{itemize}
|
||
|
||
systemd монтирует все эти файловые системы на ранних стадиях загрузки, даже если
|
||
они не~указаны явно в +/etc/fstab+. В зависимости от настроек вашего ядра,
|
||
некоторые из перечисленных выше ФС могут быть недоступны, и наоборот, могут
|
||
присутствовать другие специальные ФС, не~приведенные в этом списке. Все эти
|
||
механизмы играют важную роль во взаимодействии ядра с программами и программ
|
||
между собой. Именно поэтому они подключаются автоматически, безо всякого участия
|
||
пользователя. Необдуманное вмешательство в их работу (отключение или изменение
|
||
параметров) может нарушить нормальную работу ваших программ, так как они утратят
|
||
доступ к необходимым для них интерфейсам.
|
||
|
||
В абсолютном большинстве случаев достаточно настроек, используемых по умолчанию.
|
||
Однако, могут возникнуть ситуации, когда необходимо изменить параметры
|
||
монтирования специальных ФС, либо полностью отключить монтирование некоторых из
|
||
них.
|
||
|
||
Несмотря на то, что эти ФС по умолчанию отсутствуют в +/etc/fstab+, ничто
|
||
не~мешает вам их туда добавить. Параметры монтирования, которые вы при этом
|
||
укажете, будут автоматически применяться к соответствующим ФС. Проще говоря:
|
||
если вам нужно изменить параметры монтирования для специальных ФС, просто
|
||
добавьте их в +/etc/fstab+ с указанием соответствующих опций. Кроме параметров
|
||
монтирования, так можно изменить и сам тип файловой системы (в частности,
|
||
перенести +/tmp+ из +tmpfs+ на раздел физического диска).
|
||
|
||
Также вы можете полностью отключить монтирование некоторых (но не~всех)
|
||
специальных систем, если это действительно необходимо. Отключаемые ФС в списке
|
||
выше отмечены (X). Для их отключения, достаточно заблокировать (замаскировать)
|
||
соответствующий юнит:
|
||
\begin{Verbatim}
|
||
systemctl mask dev-hugepages.mount
|
||
\end{Verbatim}
|
||
|
||
В результате выполнения такой операции, соответствующая ФС больше не~будет
|
||
монтироваться по умолчанию, начиная со следующей загрузки системы. О том, что
|
||
такое блокировка юнита, вы можете прочитать в~главе~\ref{sec:off}.
|
||
|
||
На всякий случай отметим, что применение к специальным ФС параметров монтирования,
|
||
указанных в +/etc/fstab+, обеспечивается службой
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd-remount-fs.service.html}%
|
||
{systemd-remount-fs.service}.
|
||
|
||
\subsection*{Зачем вы мне все это рассказываете? Я просто хочу убрать tmpfs из
|
||
/tmp!}
|
||
|
||
У вас есть три варианта:
|
||
\begin{enumerate}
|
||
\item Вы можете отключить любое монтирование в +/tmp+, в результате чего
|
||
содержимое данного каталога будет храниться на том же разделе, что
|
||
и корень. Для этого достаточно выполнить команду
|
||
\begin{Verbatim}
|
||
systemctl mask tmp.mount
|
||
\end{Verbatim}
|
||
\item Вы можете смонтировать в +/tmp+ обычную файловую систему,
|
||
размещенную на диске. Для этого достаточно создать
|
||
соответствующую запись в +/etc/fstab+.
|
||
\item Если вы хотите оставить в +/tmp+ +tmpfs+, но при этом задать для
|
||
нее другой размер~--- это тоже делается через внесение в
|
||
+/etc/fstab+ соответствующей записи, предписывающей монтирование
|
||
+tmpfs+ в +/tmp+ с нужным вам значением параметра +size=+.
|
||
\end{enumerate}
|
||
|
||
\section{Запуск служб после появления сети\sfnote{Перевод статьи
|
||
<<\href{http://www.freedesktop.org/wiki/Software/systemd/NetworkTarget}{Running
|
||
Services After the Network is up}>> с официального сайта проекта, по состоянию
|
||
на 2016-08-21 13:14:53 (коммит 4d5d5).}}
|
||
\label{sec:networktarget}
|
||
|
||
\yousaywtfsk{Итак, ваша служба настроена на запуск только после достижения цели
|
||
network.target, однако, несмотря на это, она все равно запускается до появления
|
||
сети. У вас возникают вопросы: <<Почему так происходит?>> и <<Как это
|
||
исправить?>>.}
|
||
|
||
В некоторых LSB-совместимых скриптах инициализации, при описания зависимостей
|
||
используется сущность (facility) \verb+$network+. Определение этой сущности в
|
||
стандарте
|
||
\href{http://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/facilname.html}%
|
||
{довольно расплывчато} и оставляет широкий простор для различных трактовок. Вот
|
||
примеры некоторых из них:
|
||
\begin{itemize}
|
||
\item Запущен демон управления сетью (например, NetworkManager или
|
||
systemd-networkd).
|
||
\item Все заданные в настройках сетевые интерфейсы переведены в состояние
|
||
UP и получили IP-адреса.
|
||
\item Все имеющиеся физические сетевые интерфейсы, на которых
|
||
зарегистрирована несущая (link beat), получили IP-адреса.
|
||
Наличие или отсутствие явных настроек роли не~играет.
|
||
\item Доступен DNS-сервер.
|
||
\item Доступна некоторая выбранная сетевая служба.
|
||
\item Доступен некий абстрактный <<интернет>>.
|
||
\item Все заданные в настройках ethernet-интерфейсы переведены в
|
||
состояние UP, однако процесс настройки PPP-интерфейсов еще
|
||
не~начался.
|
||
\item Активирован некий профиль сетевых настроек, задающий одно из
|
||
условий выше. В разных профилях могут использоваться разные
|
||
условия.
|
||
\item Выполняется некоторый набор условий (выбор условий определяется
|
||
физическим расположением системы).
|
||
\item Настроен как минимум один глобальный адрес IPv4.
|
||
\item Настроен как минимум один глобальный адрес IPv6.
|
||
\item Настроен как минимум один глобальный адрес IPv4 или IPv6.
|
||
\end{itemize}
|
||
|
||
Этот список можно продолжать и дальше. Каждый предложенный в нем вариант можно
|
||
использовать в качестве критерия появления сети, однако ни~один из них
|
||
не~подходит в качестве варианта по умолчанию, пригодного для абсолютного
|
||
большинства задач.
|
||
|
||
Современные компьютерные сети живут в очень динамичном ритме: компьютеры
|
||
перемещаются между сетями, сетевые настройки меняются, устройства подключаются и
|
||
отключаются, виртуальные сети настраиваются, перенастраиваются и выключаются.
|
||
Наличие сетевого соединения не~является чем-то постоянным и безусловным. В
|
||
разные моменты времени компьютер может быть подключен к разным сетям. И это
|
||
справедливо не~только для мобильных устройств (смартфонов, планшетов и
|
||
ноутбуков), но и, в определенной мере, для встраиваемых и серверных систем.
|
||
Программы, которые считают, что сеть является чем-то неизменным и непрерывно
|
||
доступным, не~могут нормально функционировать в таких условиях. Хорошо
|
||
написанная программа должна корректно реагировать на изменение состояния сети, и
|
||
не~унывать в беде. Если нужный ей сервер не~доступен в настоящий момент, она
|
||
должна не~зависать по тайм-ауту, а пытаться достучаться к нему снова и снова.
|
||
Если сетевое соединение утрачено, она должна отреагировать на это. Реализовать
|
||
такое реагирование в коде демона, на самом деле, не~так уж и сложно. Существует
|
||
множество хорошо известных сетевых служб, которые уже давно поддерживают такую
|
||
возможность. Подобные службы можно запускать в любой момент, они устойчивы к
|
||
сбоям, и работают корректно во всех возможных ситуациях.
|
||
|
||
\verb+$network+ предназначена для поддержки программ, созданных в
|
||
предположении, что сеть доступна постоянно (т.е. написанных не~очень аккуратно).
|
||
Конкретные требования таких программ могут сильно отличаться. Например,
|
||
IMAP-серверу будет достаточно наличия IP-адреса, на котором можно слушать. В то
|
||
время как для клиентов сетевых файловых систем требуется требуется доступность и
|
||
работоспособность сервера, а также, опционально, наличие работоспособного DNS.
|
||
Таким образом, конкретные требования к \verb+$network+ сильно зависят от
|
||
решаемой задачи.
|
||
|
||
По соображениям надежности, загрузка системы не~должна зависеть от второстепенных
|
||
служб. В частности, отсутствие ответа от DHCP-сервера не~должно завешивать
|
||
процесс загрузки системы (за исключением ситуаций, когда сетевое соединение
|
||
действительно необходимо для работы системы, например, при загрузке по сети).
|
||
|
||
\begin{comment}
|
||
% Настоящий фрагмент документации применим к systemd версий 212 и ниже.
|
||
% Начиная с 11.06.2014 он удален из официальной документации.
|
||
% Пока остается здесь в виде комментария, для особо дотошных пользователей
|
||
% старых версий systemd.
|
||
|
||
По умолчанию, +network.target+ не~несет какой-либо сакральной смысловой
|
||
нагрузки. Сама по себе настройка службы на запуск после достижения этой цели
|
||
не~дает видимого эффекта. Наполнить +network.target+ смыслом должен сам
|
||
администратор, в зависимости от задачи которую он решает, и от конкретных
|
||
потребностей тех программ, с которыми ему приходится работать (а эти
|
||
потребности, в свою очередь, могут зависеть от их настроек). По умолчанию, мы
|
||
оставили +network.target+ <<пустышкой>>, что позволяет обеспечить максимальную
|
||
скорость загрузки, и предоставили администратору возможность самостоятельно
|
||
определить список проверок, который он считает наиболее целесообразным для
|
||
данной системы.
|
||
|
||
\subsection*{Короче, как заставить network.target работать?}
|
||
|
||
Конкретный рецепт зависит от решаемой вами задачи и потребностей ваших служб
|
||
(см. выше). Если вы используете NetworkManager, вы можете задействовать
|
||
специальную службу +NetworkManager-wait-online.service+:
|
||
\begin{Verbatim}
|
||
systemctl enable NetworkManager-wait-online.service
|
||
\end{Verbatim}
|
||
|
||
Включение этой службы позволит гарантировать, что загрузка продолжится только
|
||
после того, как все заданные в настройках NM сетевые интерфейсы будут переведены
|
||
в состояние UP и получат IP-адреса. Максимальное время ожидания~--- 90
|
||
секунд\footnote{Прим. перев.: В апстримной конфигурации по умолчанию сейчас
|
||
используется значение 30 секунд. См. параметр +--timeout+ программы +nm-online+
|
||
в файле настроек службы
|
||
+/usr/lib/systemd/system/NetworkManager-wait-online.service+.}.
|
||
Обратите внимание, что включение данной службы может сильно замедлить загрузку.
|
||
|
||
Если же такое вариант вас не~устраивает, вы можете подготовить собственный
|
||
service-файл, запускающий любую заданную вами программу или скрипт. Не~забудьте
|
||
указать, что он должен быть запущен до достижения цели +network.target+ (при
|
||
помощи параметра +Before=+). Кроме того, стоит указать зависимость от
|
||
+network.target+ при помощи директивы +Wants=+\footnote{Прим. перев.: Также
|
||
не~стоит забывать, что ваша служба сама должна быть кем-то активирована. Это
|
||
определяется параметром +WantedBy=+ или +RequiredBy=+ секции +[Install]+. Проще
|
||
всего указать здесь ту же самую +network.target+: в результате, ваша служба и
|
||
+network.target+ будут взаимно зависеть друг от друга, но при этом
|
||
+network.target+ будет активирована только после того, как отработает ваша
|
||
служба (о разницы между +WantedBy=+ и +RequiredBy=+ см.
|
||
примечание~\ref{ftn:wants}). В качестве основы вы можете взять
|
||
\href{http://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/data/NetworkManager-wait-online.service.in}%
|
||
{апстримный файл} +NetworkManager-wait-online.service+. В завершение,
|
||
не~забудьте выполнить для своей службы +systemctl enable+.}.
|
||
|
||
\end{comment}
|
||
|
||
% Далее приводится новая редакция (с 11.06.2014) соответствующего фрагмента
|
||
% документации, применимая к systemd версий 213 и выше.
|
||
|
||
\subsection{Как это реализовано в systemd}
|
||
|
||
В systemd существует сразу три юнита (целевых состояния, target units), в
|
||
совокупности берущих на себя роль LSB-сущности \verb+$network+\footnote{Прим.
|
||
перев.: Приведенные здесь сведения применимы только к systemd версий 213 и выше,
|
||
в которых появился юнит +network-online.target+. О том, как сущность
|
||
\verb+$network+ работала в предыдущих версиях systemd, желающие могут
|
||
прочитать в примечании~\ref{ftn:lsbnetwork}.}:
|
||
|
||
\begin{itemize}
|
||
\item +network.target+ не~играет существенной роли в процессе
|
||
\emph{загрузки} системы. Активация данного целевого состояния
|
||
лишь показывает, что программа управления сетью была запущена.
|
||
А вот будет ли к этому моменту настроена сеть~---
|
||
не~регламентируется. Основное назначение данного юнита~---
|
||
синхронизация операций при \emph{остановке} системы. Так как
|
||
порядок остановки юнитов обратен порядку их запуска, все юниты,
|
||
в описании которых указано +After=network.target+, при
|
||
выключении системы будут остановлены до того, как отключится
|
||
сеть. Это позволит программам корректно закрывать все сетевые
|
||
соединения, а не~оставлять их <<повисшими>>. Обратите внимание,
|
||
что +network.target+ является \emph{пассивным} юнитом: вы
|
||
не~можете запустить его ни~вручную (через +systemctl start+),
|
||
ни~через зависимости своих служб (+Wants+ и +Requires+).
|
||
Активацией и деактивацией данного юнита занимается программа,
|
||
управляющая сетью в вашей системе. В юнит-файлах служб,
|
||
использующих сеть, целесообразно указывать
|
||
+After=network.target+, но ни~в~коем случае
|
||
не~+Wants=network.target+, и уж тем более
|
||
не~+Requires=network.target+\footnote{Прим. перев.: Концепция
|
||
активных и пассивных юнитов более подробно пояснена на странице
|
||
руководства
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd.special.html}{systemd.special(7)}.
|
||
Она описывает взаимосвязь двух классов служб: потребители
|
||
(consumers) и поставщики (providers). Например, служба
|
||
управления сетью является поставщиком (сети), а демон
|
||
торрент-клиента~--- потребителем. Синхронизация между ними может
|
||
осуществляться посредством как пассивных, так и активных целевых
|
||
состояний. Разница между активными и пассивными целевыми
|
||
юнитами состоит в том, что для активных целей
|
||
+Requires+/+Wants+ зависимости прописываются в юнит-файле
|
||
службы-потребителя, а для пассивных~--- в юнит-файле
|
||
службы-поставщика. Никакой магии здесь нет~--- это
|
||
просто соглашение между авторами юнит-файлов. В рамках этого же
|
||
принципа, в конфигурационные файлы пассивных юнитов
|
||
добавляется директива +RefuseManualStart=yes+, запрещающая их
|
||
активацию <<вручную>>.}.
|
||
\item +network-online.target+ активируется только после появления сети.
|
||
Трактовка понятия <<появилась сеть>> остается на совести
|
||
разработчиков программы, управляющей сетью\footnote{Прим.
|
||
перев.: Определением момента готовности сети обычно занимается
|
||
отдельная утилита. В~NetworkManager это +nm-online+, в
|
||
systemd-networkd~---
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.html}{systemd-networkd-wait-online(8)}.
|
||
В~случае NM, сеть считается готовой после появления на любом из
|
||
управляемых им устройств корректного IP-адреса (глобального или
|
||
локального), в случае networkd~--- после того, как для всех
|
||
интерфейсов, прописанных в его конфигурации, завершится процесс
|
||
настройки (успешно или с ошибкой), причем как минимум для одного
|
||
интерфейса настройка должна завершиться успешно.}. Обычно оно
|
||
подразумевает, что одному или нескольким сетевым интерфейсам
|
||
присвоены маршрутизируемые IP-адреса. Основная задача
|
||
обсуждаемого юнита~--- обеспечить задержку запуска отдельных
|
||
служб до того момента, как появится сеть. Данный юнит является
|
||
\emph{активным}, т.е. его можно указывать в зависимостях служб,
|
||
которым необходима запущенная сеть (и, в то же время, нельзя
|
||
указывать в зависимостях у самой службы управления сетью). По
|
||
умолчанию, этот юнит автоматически прописывается в зависимости к
|
||
точкам монтирования всех удаленных файловых систем (например,
|
||
NFS, SMBFS/CIFS), приведенным в файле +/etc/fstab+. Таким
|
||
образом, монтирование этих файловых систем начнется только после
|
||
того, как появится сеть. Однако, если таких точек монтирования
|
||
не~указано, а также отсутствуют службы, явно требующие по
|
||
зависимостям данный юнит, он вообще не~активируется в процессе
|
||
загрузки системы, что позволяет избежать нежелательных задержек
|
||
в случае проблем с сетью. Настоятельно рекомендуется
|
||
не~злоупотреблять зависимостями от этого юнита. В частности, для
|
||
серверных приложений, как правило, такая зависимость избыточна
|
||
(обычно, сервер может работать даже в отсутствие внешней сети,
|
||
обслуживая локальные соединения). Основная задача обсуждаемого
|
||
юнита~--- своевременный запуск клиентских программ, которые
|
||
не~могут работать без сети.
|
||
\item +network-pre.target+\footnote{Прим. перев.: Поддержка данного
|
||
юнита добавлена в systemd-networkd начиная с systemd 214. Тогда
|
||
же в официальной документации появились первые упоминания о нем.
|
||
Однако, ничто не~мешает использовать этот юнит и в более ранних
|
||
версиях systemd~--- достаточно скопировать соответствующий
|
||
\href{http://cgit.freedesktop.org/systemd/systemd/tree/units/network-pre.target}%
|
||
{конфигурационный файл} в каталог +/etc/systemd/system+.}
|
||
активируется до того, как начнется настройка сетевых
|
||
интерфейсов. Основная функция этого юнита~--- своевременный
|
||
запуск служб, выполняющих настройку брандмауэра. Таким образом,
|
||
к моменту появления сети брандмауэр будет уже готов к отражению
|
||
возможных атак. Указанный юнит является \emph{пассивным}~--- вы
|
||
не~можете запустить его вручную, и его нельзя указывать в
|
||
качестве +Requires+/+Wants+-зависимости в юнит-файлах службы
|
||
управления сетью. Напротив, такие зависимости должны
|
||
прописываться у тех служб, которые должны быть запущены до
|
||
появления сети. В юнит-файле службы управления сетью
|
||
целесообразно указать +After=network-pre.target+, но
|
||
не~+Wants=network-pre.target+/+Requires=network-pre.target+. В
|
||
то же время, в юнит-файлах служб, которые должны быть запущены
|
||
до появления сети (например, уже обсуждавшиеся выше службы
|
||
брандмауэра), наоборот, рекомендуется указывать
|
||
+Before=network-pre.target+ и +Wants=network-pre.target+. Таким
|
||
образом, данное целевое состояние будет активироваться в нужный
|
||
момент только в том случае, если у какой-либо из ваших служб
|
||
действительно имеется такая зависимость. Если же подобных служб
|
||
нет, +network-pre.target+ не~будет активироваться вообще.
|
||
\end{itemize}
|
||
|
||
Когда systemd встречает в LSB-заголовках init-скриптов зависимость
|
||
\verb+$network+, он преобразовывает ее в зависимости
|
||
+Wants=network-online.target+ и +After=network-online.target+, что позволяет
|
||
обеспечить поведение, более или менее соответствующее требованиям
|
||
LSB\footnote{\label{ftn:lsbnetwork}Прим. перев.: Трансляция LSB-сущности
|
||
\verb+$network+ в +network-online.target+ введена в systemd начиная с systemd
|
||
213. В systemd версий до 212 включительно, \verb+$network+ транслировалась в
|
||
+network.target+. Что приводило к довольно неожиданным эффектам~--- как уже
|
||
упоминалось выше, активация +network.target+ вовсе не~означает, что сеть уже
|
||
настроена. В связи с этим, официальная документация рекомендовала привязывать
|
||
+network.target+ к моменту запуска сети, например, через
|
||
+NetworkManager-wait-online.service+. Соответствующие команды приведены чуть
|
||
ниже по тексту. Они действуют даже в новых версиях systemd, но особого смысла
|
||
уже не~несут~--- задача синхронизации решается юнитом +network-online.target+,
|
||
который в случае необходимости активируется автоматически (реализовано в
|
||
юнит-файлах systemd-networkd 213 и выше, NetworkManager 0.9.9.95 и выше).}.
|
||
|
||
За дальнейшими подробностями вы можете обратиться к странице руководства
|
||
\hreftt{http://www.freedesktop.org/software/systemd/man/systemd.special.html}{systemd.special(7)}.
|
||
|
||
\subsection{Короче, как заставить network.target работать?}
|
||
|
||
Это зависит от конфигурации вашей системы и конкретных требований ваших служб
|
||
(см. выше). Многие службы управления сетью предоставляют возможность
|
||
принудительной активации +network-online.target+, в результате чего
|
||
+network.target+ по своему эффекту становится практически эквивалентной
|
||
+network-online.target+.
|
||
|
||
Если вы используете NetworkManager, вы можете задействовать
|
||
специальную службу +NetworkManager-wait-online.service+:
|
||
\begin{Verbatim}
|
||
systemctl enable NetworkManager-wait-online.service
|
||
\end{Verbatim}
|
||
|
||
Если же вы используете systemd-networkd, соответствующая служба будет называться
|
||
+systemd-networkd-wait-online.service+:
|
||
\begin{Verbatim}
|
||
systemctl enable systemd-networkd-wait-online.service
|
||
\end{Verbatim}
|
||
|
||
Включение такой службы позволит гарантировать, что загрузка продолжится только
|
||
после того, как все заданные в настройках сетевые интерфейсы будут переведены
|
||
в состояние UP и получат IP-адреса. Максимальное время ожидания~--- 90
|
||
секунд\footnote{Прим. перев.: У NetworkManager в апстримной конфигурации по
|
||
умолчанию сейчас используется значение 30 секунд. См. параметр +--timeout+
|
||
программы +nm-online+ в файле настроек службы
|
||
+/usr/lib/systemd/system/NetworkManager-wait-online.service+. У systemd-networkd
|
||
время ожидания ограничивается тайм-аутом systemd для запуска служб, по умолчанию
|
||
90 секунд.}.
|
||
Обратите внимание, что включение подобных служб может сильно замедлить загрузку.
|
||
Обе эти службы по умолчанию отключены\footnote{Прим. перев.: У внимательного
|
||
читателя может возникнуть вопрос: как же определяется момент активации
|
||
+network-online.target+, если юниты, отвечающие за ожидание сети, отключены? На
|
||
самом деле все довольно просто: при установке NetworkManager/systemd-networkd
|
||
соответствующие +*-wait-online+-юниты прописываются в зависимости к
|
||
+network-online.target+ при помощи симлинков в
|
||
+/etc/systemd/system/network-online.target.wants+. В результате, когда
|
||
какая-нибудь служба укажет зависимость от +network-online.target+, будет
|
||
автоматически активирована и соответствующая служба +*-wait-online+. Если таких
|
||
служб нет, то и активации не~произойдет. Однако, если вы принудительно включите
|
||
службу +*-wait-online+ при помощи приведенных выше команд, она будет прописана
|
||
в зависимости уже к +multi-user.target+, а значит, будет активироваться при
|
||
каждой загрузке.}.
|
||
|
||
Как альтернативный вариант, вы можете поменять непосредственно юнит-файл той
|
||
службы, которая нуждается в сети, добавив туда опции
|
||
+After=network-online.target+ и +Wants=network-online.target+\footnote{Прим.
|
||
перев.: Собственно, этот путь и является единственно разумным в большинстве
|
||
случаев. Привязка +network.target+ к +network-online.target+, описанная выше,
|
||
фактически, является пережитком проблем старых версий systemd (212 и ниже),
|
||
когда, по воле его разработчиков, LSB-сущность \texttt{\$network}
|
||
транслировалась в +network.target+.}.
|
||
|
||
\subsection{А что делать нам, разработчикам?}
|
||
|
||
Если вы~--- не~администратор, а разработчик сетевой службы, то вам стоит
|
||
задуматься не~о том, что делать с +network.target+, а о том, как можно исправить
|
||
вашу службу, чтобы она нормально реагировала на изменение состояния сети. Это
|
||
позволит порадовать ваших пользователей (когда программа работает без
|
||
дополнительной возни~--- это не~может не~радовать), а также уменьшит количество
|
||
сообщений об ошибках (так как ваша программа уже не~будет паниковать по
|
||
пустякам). Кроме того, системы ваших пользователей будут грузиться быстрее,
|
||
потому что их уже не~будет задерживать необходимость ожидать появления сети (в
|
||
случае с медленным DHCP-сервером, прогресс может быть весьма ощутимым).
|
||
|
||
Можно предложить несколько подходов к решению этой задачи:
|
||
\begin{itemize}
|
||
\item Отслеживайте изменений конфигурации сети при помощи
|
||
\href{https://www.kernel.org/doc/man-pages/online/pages/man7/rtnetlink.7.html}%
|
||
{rtnetlink} и реагируйте соответствующим образом. Это наиболее
|
||
правильный, но далеко не~всегда самый простой способ.
|
||
\item Если вы разрабатываете серверное приложение: слушайте только
|
||
адреса [::], [::1], 0.0.0.0 и 127.0.0.1. Все эти псевдо-адреса
|
||
должны быть доступны постоянно. Если ваша программа будет
|
||
слушать только их, ей будут глубоко безразличны изменения
|
||
конфигурации сети.
|
||
\item Если вы разрабатываете серверное приложение и вам нужно слушать
|
||
некий заданный адрес: воспользуйтесь опцией
|
||
\href{https://www.kernel.org/doc/man-pages/online/pages/man7/ip.7.html}%
|
||
{IP\_FREEBIND}, доступной на Linux-системах. Она позволит вашей
|
||
программе слушать даже те адреса, которые не~присвоены локальным
|
||
сетевым интерфейсам (в данный момент или вообще), что также
|
||
сделает ваш код устойчивым к изменению сетевой конфигурации.
|
||
\end{itemize}
|
||
|
||
\section{Моя служба не~может получить приоритет realtime\sfnote{Перевод статьи
|
||
<<\href{http://www.freedesktop.org/wiki/Software/systemd/MyServiceCantGetRealtime}%
|
||
{My Service Can't Get Realtime!}>> с официального сайта проекта, по состоянию на
|
||
2013-05-18 08:20:36 (коммит 2f77b).}}
|
||
\label{sec:realtime}
|
||
|
||
\yousaywtf{Итак, у вас есть служба, которая требует приоритет реального времени
|
||
(realtime). Однако, когда вы пытаетесь запустить ее на системе, работающей под
|
||
управлением systemd, ваша служба не~может получить этот приоритет, хотя обладает
|
||
всеми необходимыми для этого привилегиями. Вы хотите понять, почему так
|
||
происходит, и как это можно исправить.}
|
||
|
||
\subsection*{В чем же дело?}
|
||
|
||
По умолчанию, systemd помещает каждую системную службу в свою контрольную группу
|
||
в иерархии контроллера +cpu+. Такое поведение дает очень полезный эффект: службы
|
||
с множеством рабочих потоков (например, Apache с сотнями CGI-процессов),
|
||
получают примерно такую же долю процессорного времени, как и службы с небольшим
|
||
количеством рабочих потоков (например, MySQL). Проще говоря, процессорное время
|
||
распределяется уже не~\emph{между процессами}, а \emph{между службами}.
|
||
|
||
К сожалению, в настоящее время реализация cpu-контроллера в Linux имеет один
|
||
существенный недостаток: она требует явного задания realtime-бюджета времени (RT
|
||
budget) для своих контрольных групп. Если же бюджет группы не~задан, то ее
|
||
процессы не~смогут получить приоритет реального времени (соответствующая
|
||
операция будет завершаться с ошибкой +EPERM+~--- <<недостаточно полномочий>>).
|
||
systemd не~может присваивать такой бюджет \emph{каждой} группе, просто потому,
|
||
что не~знает, как его правильно делить между ними. Бюджет выдается в абсолютных
|
||
единицах времени, и их общее количество ограничено. Мы не~можем предложить
|
||
механизм распределения бюджета, который бы подходил по умолчанию для большинства
|
||
случаев. Поэтому, в конфигурации по умолчанию, приоритет реального времени
|
||
недоступен для системных служб.
|
||
|
||
\subsection*{Как это исправить?}
|
||
|
||
Обойти это ограничение несложно. Существует несколько различных путей:
|
||
\begin{itemize}
|
||
\item Можно просто отключить использование cpu-контроллера по умолчанию
|
||
для системных служб. Для этого, задайте в файле
|
||
+/etc/systemd/system.conf+ параметр +DefaultControllers=+ равным
|
||
пустой строке, после чего перезагрузите систему. (Либо вы
|
||
можете отключить контроллер +cpu+ на этапе сборки ядра. systemd
|
||
не~пытается использовать контроллеры, которые не~поддерживаются
|
||
ядром.)
|
||
\item Также вы можете отключить группировку по +cpu+ только для
|
||
конкретных служб. Для этого отредактируйте конфигурацию службы,
|
||
добавив параметр
|
||
\begin{Verbatim}
|
||
ControlGroup=cpu:/
|
||
\end{Verbatim}
|
||
в секцию +[Service]+. В
|
||
результате, процессы данной службы будут помещены не~в
|
||
собственную контрольную группу (как это делалось по умолчанию),
|
||
а в корневую контрольную группу иерархии +cpu+. Процессы из
|
||
корневой группы располагают полным realtime-бюджетом.
|
||
\item И наконец, вы можете явно выделить своей службе некоторый бюджет.
|
||
Для этого добавьте в секцию +[Service]+ строку наподобие
|
||
\begin{Verbatim}
|
||
ControlGroupAttribute=cpu.rt_runtime_us 500000
|
||
\end{Verbatim}
|
||
Подробнее о правильном распределении бюджета читайте в
|
||
\href{http://www.kernel.org/doc/Documentation/scheduler/sched-rt-group.txt}%
|
||
{документации к ядру}.
|
||
\end{itemize}
|
||
|
||
Последние две опции неприменимы к SysV-службам. Тем не~менее, вы можете
|
||
подготовить для таких служб соответствующие service-файлы, которые, помимо
|
||
упомянутых выше параметров, содержат вызов соответствующего init-скрипта с
|
||
аргументом +start+ в +ExecStart=+, и с аргументом +stop+~--- в +ExecStop=+.
|
||
(Также имеет смысл задать для них параметры +RemainAfterExit=1+ и
|
||
+Type=forking+.)
|
||
|
||
Отметим, что все вышесказанное касается только системных служб, и не~затрагивает
|
||
пользовательские сеансы. По умолчанию, программы пользователя размещаются в
|
||
корневой группе контроллера +cpu+, и поэтому вышеописанные ограничения их
|
||
не~касаются.
|
||
|
||
Мы надеемся, что в будущем ядро будет исправлено таким образом, чтобы
|
||
не~требовать явного задания realtime-бюджета для получения приоритета реального
|
||
времени (а при получении такого приоритета, бюджет процесса должен автоматически
|
||
выделяться из бюджета ближайшей родительской группы). В идеале, мы хотели бы
|
||
распространить практику выделения cpu-групп не~только на системные службы, но и
|
||
на пользовательские сеансы, чтобы уравнять пользователей в правах на
|
||
процессорное время, вне зависимости от того, сколько процессов запускает каждый
|
||
конкретный пользователь.
|
||
|
||
\end{document}
|
||
|
||
vim:ft=tex:tw=80:spell:spelllang=ru
|