Victor Latushkin's Weblog
Understanding the Ocean
Архив
« Ноябрь 2009
ПнВтСрЧтПтСбВс
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
      
Сегодня
Click me to subscribe
Search

Связи
 

Хиты страниц за сегодня: 2

« Как загрузить систем... | Main | О производительности... »
пятница ноя 24, 2006
Как контракты помогают управлять сервисами

Как вы уже наверное знаете, в Solaris 10 реализована система управления сервисами SMF (Service Management Framework), призванная заменить старые добрые инициализационные скрипты. О том, что такое SMF, написано и сказано много. Например, Андрей Дорофеев рассказывал об этом на Solaris TechDays в Москве в этом году. Но о важном нововведении, которое позволяет рассматривать набор взаимосвязанных процессов как единое целое, информации достаточно мало. Речь идет о контактах (см. contract(4)), а точнее о процессных контрактах (см. process(4)).

Что это такое? 

Механизм процессных контрактов позволяет создать границу распространения сбоев вокруг некоторого множества подпроцессов, а затем наблюдать и реагировать на события, происходящие внутри этой границы. Этот механизм активно используется менеджером сервисов svc.startd(1M). Сейчас мы поговорим немного подробнее о процессных контрактах, а затем о том, как они используются менеджером сервисов svc.startd(1M).

Создание процессного контракта

Новый процессный контракт создается тогда, когда LWP, имеющий активный шаблон контракта вызывает системный вызов fork(2). Изначально, только дочерний процесс, созданный в результате выполнения системного вызова fork(2), попадает в рамких вновь созданного контракта и управляется в соответствии с его условиями. В случае, если LWP, вызывающий системный вызов fork(2), не имеет активного шаблона контракта, то дочерний процесс добавляется в качестве ресурса к тому контракту, в рамки которого входит родительский процесс.

Управление контрактами производится при помощи файловой системы contract(4) и библиотеки libcontract(3LIB). Этот способ хорош тогда, когда мы сами разрабатываем приложение и можем вызывать в необходимых местах соответствующие функции из библиотеки libcontract(3LIB). Но что делать, если мы лишены такой возможности, например, хотим воспользоваться механизмом контрактов процессов для готового приложения? Здесь нам поможет тот факт, что контракты наследуются при создании дочерних процессов и утилита ctrun(1).

События в процессных контрактах

Для процессных контрактов, в дополнение к событиям, общим для всех типов контрактов, определены следующие типы событий:

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

Типы событий, общих для всех типов контрактов, можно найти в contract(4).

Возникающие события можно получать и обрабатывать, используя программные интерфейсы библиотеки libcontract(3LIB). Однако для простого мониторинга этих событий есть удобная утилита ctwatch(1). Мы обязательно воспользуемся этой утилитой далее.

Условия процессных контрактов

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

По умолчанию, для процессных контрактов последние два условия имеют следующие значения:

Для процессных контрактов существуют также дополнительные условия, некоторые из которых мы рассмотрим ниже:

Перечисленные типы событий и условия контрактов позволяют описывать достаточно сложные правила. Это может быть использовано при разработке новых сервисов и приложений (например, так называемых Delegated Restarters для smf(5)). Эти механизмы активно используется в smf(5), и дальше мы это увидим. Утилита ctrun(1) позволяет пользоваться этими средствами в ограниченном объеме и для существующих приложений, облегчая задачу преобразования их в сервисы.

Статусы процессных контрактов

Как и в предыдущих случаях, для процессных контрактов определены все статусы, определенные для контрактов (см. contract(4)). В дополнение к ним определены также присущие только процессным контрактам статусы. Детальное обсуждение всех статусов выходит за рамки этого повествования. Отметим, что для получения этих статусов также имеются соответствующие программные интерфейсы в библиотеке libcontract(3LIB), а также удобная утилита ctstat(1), которой мы обязательно воспользуемся далее.

Управление сервисами и изоляция сбоев

Какое же отношение имеет все изложенное выше к управлению сервисами? Самое непосредственное. Менеджер сервисов svc.startd(1M) активно использует контракты для запуска и управления сервисами. Для того, чтобы управлять процессами, входящими в сервис, как единым целым, менеджер сервисов создает контракты для каждого запускаемого сервиса. Как владелец соответствующих контрактов, менеджер сервисов svc.startd(1M) затем получает от ядра системы уведомления о происходящих событиях, и реагирует на них в соответствии с правилами определения сбоев и перезапуска сервисов. Далее мы рассмотрим, как обрабатывается событие signal, имеющее отношение к рассматриваемой проблеме, а рассмотрение других оставим для другого случая.

Итак, менеджер сервисов svc.startd(1M) создает отдельный контракт для каждого сервиса. Посмотрим, как это выглядит, на примере сервиса печати. Для этого нам понядобится утилита ctstat(1). Необходимо отметить также, что помимо утилит ctstat(1), ctwatch(1), ctrun(1), необходимые изменения для вывода информации о контрактах были внесены также и в другие утилиты Solaris, например ptree(1), ps(1) и так далее. Мы воспользуемся ptree(1), ps(1) же и другие утилиты оставим в качестве упражнения читателю.

Итак, для сервиса печати мы можем получить следующий вывод (с точностью до номеров процессов и контрактов):

root@theorem # ptree -c `pgrep lpsched`
[process contract 1]
1 /sbin/init
[process contract 4]
7 /lib/svc/bin/svc.startd
[process contract 744]
15892 /usr/lib/lp/local/lpsched

Видно, что сервис печати (процесс lpsched) работает в рамках отдельного контракта с номером 744, а его родительский процесс svc.startd имеет номер 7 и вылоняется в рамках контракта с номером 4. Теперь с помощью утилиты ctstat(1) мы посмотрим на статус контракта 744:


root@theorem # ctstat -vi 744
CTID ZONEID TYPE STATE HOLDER EVENTS QTIME NTIME
744 0 process owned 7 0 - -
cookie: 0x20
informative event set: none
critical event set: core signal hwerr empty
fatal event set: none
parameter set: inherit regent
member processes: 15892
inherited contracts: none

Информации о контрактах, представленной ранее, вполне достаточно, чтобы разобраться с выводом этой команды. Контракт 744 является процессным контрактом (TYPE=process), у которого в настоящий момент есть владелец, или держатель (STATE=owned), которым является процесс с номером 7. Множество информационных событий для данного контракта пусто, множество критических событий для данного контракта содержит 4 события - core (CT_PR_EV_CORE), signal (CT_PR_EV_SIGNAL), hwerr (CT_PR_EV_HWERR), empty (CT_PR_EV_EMPTY), что означает, что менеджер сервисов svc.startd самостоятельно позаботится необходимой реакции на эти события. Множество фатальных событий пусто, то есть операционная система не будет инициировать принудительное завершение всех процессов. Множество параметров включает inherit (CT_PR_INHERIT) и regent (CT_PR_REGENT), что означает что данный контракт будет унаследован процессным контрактом номер 4, если менеджер сервисов svc.startd(1M) неожиданно завершится, а также будет наследовать контракты, созданные входящими в него процессами в случае их преждевременного завершения. В процессный контракт 744 входит только 1 процесс с номером 15892.

Теперь настало время рассмотреть, как менеджер сервисов svc.startd(1M) принимает решение о том, что произошел сбой сервиса или метода этого сервиса, а также о том, нужно ли перезапускать сервис.

Определение сбоев и перезапуск сервисов

Для начала необходимо отметить, что менеджер сервисов svc.startd(1M) предлагает три модели сервисов: контрактные (contract), временные (transient) и с ожиданием (wait). Эти модели описаны во "Введении для разработчиков сервисов", здесь мы рассмотрим только первые две - контрактную и временную.

Также важно иметь ввиду, что с точки зрения менеджера сервисов svc.startd(1M) существует различие между сбоем метода и сбоем сервиса. Поэтому мы рассмотрим каждый и начнем со сбоя метода.

Менеджер сервисов считает, что имел место сбой метода, если код завершения метода отличен от нуля. Сбой метода приводит к немедленному переводу сервиса в состояние maintenance, если кодом ошибки был $SMF_EXIT_ERR_CONFIG или $SMF_EXIT_ERR_FATAL (см. /lib/svc/share/smf_include.sh). В случае других отличных от нуля кодов возврата, сервис переводится в состояние offline. Как известно, менеджер сервисов svc.startd(1M) пытается выполнить перезапуск сервисов, находящихся в состоянии offline, если все сервисы, от которых он зависит, работоспособны. Однако, если происходит подряд три сбоя сервиса, или сервис перезапускается слишком быстро, такой сервер будет также переведен в состояние maintenance.

Отказ сервиса определяется несколько сложнее - помимо внешних событий при этом учитывается модель сервиса и свойство startd/ignore_error. По умолчанию, сервис с контрактной моделью считается отказавшим, если произошло одно из следующих событий.

Последние два условия можно проигнорировать, указав ключевые слова core или signal (или оба сразу через запятую) в свойстве startd/ignore_error. Как вы уже наверное догадались, все эти отказы сервисов определяются благодаря соответствующим типам событий в контракте данного сервиса.

Использование временной модели сервиса означает, что менеджер сервисов может не следить за процессами данного сервиса, поэтому отказ такого сервиса определяется только по сбою метода.

Пример: сервис печати

Рассмотрим теперь, как это все работает на примере сервиса печати.

Для нашего примера мы воспользуемся сетевым принтером, сымитируем его сбой и отправим задание на печать, а затем отправим сигнал одному из дочерних процессов сервиса печати.

В первую очередь мы запустим сервис печати и посмотрим состояние его контракта:

root@theorem # svcadm enable print/server 
root@theorem # ptree -c `pgrep lpsched`
[process contract 1]
1 /sbin/init
[process contract 4]
7 /lib/svc/bin/svc.startd
[process contract 744]
15892 /usr/lib/lp/local/lpsched

root@theorem # ctstat -vi 744
CTID    ZONEID  TYPE    STATE   HOLDER  EVENTS  QTIME   NTIME
744     0       process owned   7       0       -       -
        cookie:                0x20
        informative event set: none
        critical event set:    core signal hwerr empty
        fatal event set:       none
        parameter set:         inherit regent
        member processes:      15892
        inherited contracts:   none
root@theorem #

Теперь мы сымитируем недоступность по сети путем внесения неправильного MAC-адреса в arp-таблицу:

root@theorem # arp -s lj4300 08:00:ba:dd:ca:fe

Далее, мы отправим задание на печать и убедимся в том, что у нашего сервиса печати появятся дочерние процессы:

root@theorem # lp -d hp /etc/passwd
request id is hp-43 (1 file(s))
root@theorem # ptree -c 15892
[process contract 1]
1 /sbin/init
[process contract 4]
7 /lib/svc/bin/svc.startd
[process contract 744]
15892 /usr/lib/lp/local/lpsched
15914 /usr/lib/lp/local/lpsched
15915 /bin/sh -c /etc/lp/interfaces/hp hp-43 root@theorem "/etc/p
15916 /bin/sh -c /etc/lp/interfaces/hp hp-43 root@theorem "/etc
15935 /usr/spool/lp/bin/lp.tell hp
15936 /bin/sh -c /etc/lp/interfaces/hp hp-43 root@theorem "
15937 /opt/hpnpl/bin/hpnpf -j hp-43+root@theorem -w -o/op
root@theorem #

Итак, мы видим, что у сервиса печати появились дочерние процессы, отвечающие за печать, которые не завершаются потому, что принтер сейчас недоступен. Посмотрим, как это отразилось в контракте:

root@theorem # ctstat -vi 744
CTID    ZONEID  TYPE    STATE   HOLDER  EVENTS  QTIME   NTIME
744     0       process owned   7       0       -       -
        cookie:                0x20
        informative event set: none
        critical event set:    core signal hwerr empty
        fatal event set:       none
        parameter set:         inherit regent
        member processes:      15892 15914 15915 15916 15935 15936 15937
        inherited contracts:   none

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

Теперь запустим утилиту ctwatch(1), чтобы наблюдать за происходящим, и параллельно в другом окне инициируем принудительное завершение процесса 15915 (с помощью kill -9):

root@theorem # ctwatch -rv 744
CTID EVID CRIT ACK CTTYPE SUMMARY
744 144 crit no process process 15915 received a fatal signal
signal: 9 (SIGKILL)
sender pid: 15545
sender ctid: 712
744 145 crit no process process 15937 received a fatal signal
signal: 9 (SIGKILL)
sender pid: 7
sender ctid: 4
744 146 crit no process process 15936 received a fatal signal
signal: 9 (SIGKILL)
sender pid: 7
sender ctid: 4
744 147 crit no process process 15935 received a fatal signal
signal: 9 (SIGKILL)
sender pid: 7
sender ctid: 4
744 148 crit no process process 15916 received a fatal signal
signal: 9 (SIGKILL)
sender pid: 7
sender ctid: 4
744 149 crit no process contract empty 

К сожалению, утилита ctwatch(1) не отображает время возникновения события, поэтому придется прокомментировать каждое.

Сразу после выполнения команды kill -9 15915, в выводе появляется событие #144: мы видим, что в результате нашего сигнала было сгенерировано критическое событие для процессного контракта, сигнал был сгенерирован процессом 15545, входящим в контракт #712. После этого некоторое время никаких событий более не генерируется. По истечении некоторого времени генерируются события #145-148, инициатором которых является уже сам менеджер сервисов, а затем, после завершения работы всех процессов в контракте, генерируется событие #149.

Если сейчас посмотреть на состояние сервисов, то мы увидим, что наш сервис печати перешел в состояние maintenance:

root@theorem # svcs -xv
svc:/application/print/server:default (LP print server)
State: maintenance since Thu Nov 23 02:03:59 2006
Reason: Method failed.
See: http://sun.com/msg/SMF-8000-8Q
See: man -M /usr/share/man -s 1M lpsched
See: /var/svc/log/application-print-server:default.log
Impact: 2 dependent services are not running:
svc:/application/print/rfc1179:default
svc:/application/print/ipp-listener:default
root@theorem #

Причиной перевода сервиса в состояние maintenance явился сбой метода. Вспоминая, что это случается тогда, когда метод возвращает либо код ошибки конфигурации ($SMF_EXIT_ERR_CONFIG), либо код фатальной ошибки ($SMF_EXIT_ERR_FATAL), можно предположить, что имело место второе. Однако, если мы обратимся к журналам менеджера сервисов, то журнале сервиса печати мы обнаружим следующее:

[ Nov 23 02:02:58 Stopping because process received fatal signal from 
outside the service. ]
[ Nov 23 02:02:58 Executing stop method ("/lib/svc/method/print-svc stop" ) ]
Print services stopped.
[ Nov 23 02:02:58 Method "stop" exited with status 0 ]
[ Nov 23 02:03:59 Method or service exit timed out.  Killing contract 744 ]

Первое сообщение соответствует отправке сигнала SIGKILL и возникновению события #144. Поскольку событие signal присутствует в множестве критических событий, в ответ на него менеджер сервисов инициирует остановку сервиса печати с помощью стандартного метода stop. Следующие две строки сообщают, что сервис печати благополучно остановился и метод успешно завершился с кодом 0, что явно противоречит выводу svcs -x. Но что же дальше? Дальше мы видим, что спустя минуту система сообщает нам о том, что истекло время ожидания завершения метода или сервиса, поскольку мы знаем, что метод завершился, то очевидно, что речь идет о сервисе, то есть о том, что сервис по какой-то причине не завершился. Что в данном случае имеется ввиду? Имеется ввиду, что после завершения метода stop, все оставшиеся в рамках этого сервиса (читай - контракта) процессы должны завершиться. Если они завершаются, то сервис благополучно перезапускается, если же в течении отведенного времени они не завершаются, менеджер сервисов рассматривает это как ошибку, с которой нужно разбираться администратору, инициирует удаление контракта и завершение оставшихся процессов (события #145-148) и переводит сервис в состояние maintenance.

Почему же в нашем примере не завершаются все процессы? Следующая теория, в предположении, что она правильная, позволяет объяснить это. Дело в том, что после того, как процесс 15937 завершает работу, его дочерний процесс усыновляется процессом init, а не lpsched. У процесса lpsched детей не остается, поэтому при выполнении остановки сервиса печати у нас нет возможности остановить все процессы, входящие в контракт, поскольку они не связаны друг с другом. Поэтому часть процессов остается жить, и это в конечном итоге приводит к переводу сервиса в состояние maintenance. Впрочем, это только теория, и она требует проверки.

Этого, однако, не происходит, если использовать сетевой принтер, сконфигурированный при помощи стандартных средств Solaris без программного обеспечения третьих фирм. Однако, это уже выходит за рамки нашей темы, поэтому оставим эту тему дл следующего раза.

Что делать?

Что же можно сделать в данной ситуации? Здесь самое время вспомнить о том, какие события считаются сбоями сервиса, а также то, что у нас есть свойство ignore_errors. Для того чтобы система перестала реагировать на фатальные сигналы как на сбои сервера, можно указать, что сигналы нужно игнорировать. Посмотрим, как это будет выглядеть в деле:

root@theorem # svccfg -s print/server:default
svc:/application/print/server:default> addpg startd framework
svc:/application/print/server:default> setprop startd/ignore_error = astring: signal
svc:/application/print/server:default> exit
root@theorem #
root@theorem # svcprop -p startd/ignore_error print/server:default
signal
root@theorem #

Итак, мы добавили свойство startd/ignore_error к нашему сервису печати. Осталось только обновить конфигурацию сервиса и перезапустить или заставить переинициализироваться менеджер сервисов, для того чтобы это изменение вступило в силу:

root@theorem # svcadm refresh print/server
root@theorem # pkill -HUP svc.startd
root@theorem # ptree -c `pgrep lpsched`
[process contract 1]
1 /sbin/init
[process contract 758]
16094 /lib/svc/bin/svc.startd
[process contract 759]
16107 /usr/lib/lp/local/lpsched
root@theorem # ctstat -vi 764
CTID ZONEID TYPE STATE HOLDER EVENTS QTIME NTIME
764 0 process owned 16094 0 - -
cookie: 0x20
informative event set: none
critical event set: core hwerr empty
fatal event set: none
parameter set: inherit regent
member processes: 16154
inherited contracts: none
root@theorem #

Отлично! Утилита ctstat(1) показывает нам, что событие signal более не входит в множество критических событий. Осталось только убедиться, что теперь сервис не будет реагировать на принудительное завершение его процессов с помощью фатальных сигналов. Проверка этого остается читателю в качестве упражнения.

В следующий раз мы продолжим рассмотрение тем, которые были затронуты, но не были освещены в этом тексте.

Posted at 11:28AM ноя 24, 2006 by Victor Latushkin in OpenSolaris  | 

Комментарии:

Опубликовать комментарий:
Комментарии запрещены.