воскресенье, 24 февраля 2013 г.

systemd. Часть 2. service-файлы

В systemd, в отличие от классической системы инициализации sysv initd, вместо shell-скриптов для запуска сервисов используются специальные service-файлы. service-файлы являются подтипом более общих файлов инициализации systemd - юнит-файлов. юнит-файлы позволяют запускать не только файлы сервисов, но и создавать Unix-сокеты, обрабатывать события от устройств и т.п.

Безусловно, shell-скрипты обладают большей гибкостью, однако большинство операций, выполняемых shell-скриптами инициализации сервисов, не отличаются оригинальностью и довольно громоздки. service-файлы позволяют описывать типовые операции во много раз проще.

Системные service-файлы, установленные средствами пакетного менеджера, располагаются в каталоге /lib/systemd/system/. Пользовательские service-файлы располагаются в каталоге /etc/systemd/system/. Они имеют приоритет над системными. Если нужно изменить системный service-файл, достаточно скопировать его в каталог пользовательских service-файлов и отредактировать необходимым образом.

1. Формат service-файла

service-файл является файлом в формате INI и состоит из трёх разделов, имена которых берутся в квадратные скобки. После строки с именем раздела следуют строки с параметрами, специфичными для данного раздела. Рассмотрим основные параметры каждого раздела.

  • Раздел Unit

    Этот раздел содержит общую информацию о сервисе, устройстве, точке монтирования и т.п.

    Description - строка описания.

    After - указывает, что этот сервис должен запускаться после указанного. Эта директива не описывает зависимость, для описания зависимостей используется директива Require.
  • Раздел Service

    Содержит информацию, специфичную для описания сервиса.

    ExecStart - путь к демону и аргументы демона для его запуска. При указании минуса перед строкой команды systemd не будет запоминать код завершения процесса.

    Type - определяет, каким образом systemd будет отслеживать, запустился ли сервис.

    • forking - запущенный процесс порождает другой процесс, а сам завершается. systemd считает сервис запущенным, когда завершается запущенный процесс. Главным процессом сервиса считается порождённый процесс.
    • dbus - после запуска сервис зарегистрируется на шине Dbus под именем, указанным в настройке BusName. systemd сможет отслеживать состояние сервиса через Dbus.

    BusName - имя сервиса, под которым он зарегистрируется на шине Dbus. Эта директива используется в том случае, когда директива Type принимает значение dbus.

    Restart - директива указывает на способ перезапуска сервиса в случае его падения.

    • restart-always - при падении перезапускать сервис всегда.

    EnvironmentFile - позволяет задать файл с переменными окружения для сервиса. В файле допускается указывать только определения переменных.
  • Раздел Install

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

    WantedBy - описывает, в каких случаях юнит должен быть активирован.
Пример простейшего service-файла уже был приведён в прошлой заметке:
[Unit]
Description=openntpd
After=network.target

[Service]
Type=simple
ExecStart=/usr/sbin/ntpd -dsf /etc/openntpd/ntpd.conf
ExecStartPost=/bin/chown ntpd /var/lib/openntpd/ntpd.drift
ExecStop=/sbin/hwclock -w

[Install]
WantedBy=multi-user.target
2. Экземпляры сервисов

Большинство сервисов запускается в единственном экземпляре, однако существуют и сервисы, которые необходимо запустить в нескольких экземплярах. Для того, чтобы не создавать отдельный service-файл для каждого экземпляра сервиса, в systemd была реализована поддержка шаблонов сервисов. Шаблон многоэкземплярного сервиса содержит символ @ в имени service-файла. Перед этим символом в имени service-файла указывается имя многоэкземплярного сервиса.

Когда возникает необходимость создать дополнительный экземпляр сервиса на основе шаблона, в каталоге /etc/systemd/system/*.wants создаётся символическая ссылка на шаблон. Имя символической ссылки совпадает с именем шаблона, но после символа @ добавляется идентификатор экземпляра сервиса. Для примера создадим экземпляр сервиса serial-getty на последовательном порту /dev/ttyS2 (команда systemctl enable для экземпляров сервисов в Debian Wheezy не поддерживается):
# systemctl start serial-getty@ttyS2.service
# mkdir /etc/systemd/system/getty.target.wants/
# ln -s /lib/systemd/system/serial-getty@.service /etc/systemd/system/getty.target.wants/serial-getty@ttyS2.service
Идентификатор экземпляра сервиса подставляется в шаблон service-файла на место следующих символов-заменителей:
%I - идентификатор экземпляра сервиса подставляется в шаблон как есть,
%i - идентификатор экземпляра сервиса подставляется в шаблон в экранированном виде. Это бывает нужно, если идентификатором экземпляра сервиса является путь к устройству, содержащий косые черты. Чтобы использовать этот путь в имени юнит-файла устройства (dev-%i.device), нужно заэкранировать все специальные символы.

Что делать, если один из экземпляров сервиса нужно запустить с какими-то индивидуальными настройками, не совпадающими с шаблонными? В этом случае можно просто создать для экземпляра сервиса индивидуальный service-файл, в котором и указать необходимые настройки. systemd, прежде чем использовать шаблоны сервисов, пытается найти service-файл с указанным именем. И только если найти такой файл не удалось, пытается воспользоваться шаблоном.

Например, если необходимы какие-то индивидуальные настройки для getty на tty2, достаточно создать service-файл /etc/systemd/system/getty@tty2.service и прописать в него индивидуальные настройки. Символы-заменители продолжают действовать и внутри этого файла. Их полный список можно найти на странице руководства systemd.unit(5).

Пример шаблона service-файла serial-getty@.serice:
[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
Полную версию файла можно посмотреть в реальной системе.

3. Запуск getty на консолях

Раз уж мы коснулись темы многоэкземплярных сервисов на примере getty, разберёмся в этом вопросе подробнее.

В systemd имеется два шаблона service-файлов для терминалов:
getty@.service - шаблон для запуска виртуальных терминалов, в переменной TERM используется значение linux,
serial-getty@.service - шаблон для запуска терминалов на последовательных портах. в переменной TERM используется значение vt102. В этом шаблоне, в отличие от предыдущего, отсутствуют настройки для очистки буфера прокрутки, поскольку он попросту отсутствует.

Главное отличие systemd от SysV init заключается в том, что терминалы запускаются по запросу, а не при загрузке системы. Имеется только два исключения: консоль tty1 запускается автоматически, в графическом режиме на ней запускается дисплейный менеджер, а в текстовом - getty, консоль tty6 тоже запускается автоматически и на ней всегда запускается getty, чтобы всегда имелась хотя-бы одна текстовая консоль для решения различных проблем.

Настройки по умолчанию можно изменить в файле /etc/systemd/systemd-logind.conf (в руководстве был указан файл /etc/systemd/logind.conf):
NAutoVTs - количество терминалов, запускаемых автоматически (по умолчанию - 6),
ReserveVT - номер зарезервированной виртуальной консоли (по умолчанию - 6).

Если необходимо настроить последовательную консоль в качестве системной, нужно добавить к строке загрузки ядра дополнительную опцию: console=ttyS0.

Для автоматической настройки других последовательных консолей systemd использует программу systemd-getty-generator, которая обнаруживает доступные последовательные консоли и запускает на каждой из них по экземпляру сервиса serial-getty@.service. Это, в том числе, могут быть консоли, предоставляемые системами виртуализации. Для запуска на какой-либо последовательной консоли терминала, нужно воспользоваться уже известными командами:

4. Сокет-активация

Сокет-активация - это техника запуска сервисов по факту обращения к соответствующему сокету, подобная той, что реализуется в демонах inetd и xinetd. systemd поддерживает три схемы запуска сервисов, использующие сокет-активацию:

1. Схема, ориентированная на параллельный запуск сервисов и упрощение зависимостей между ними. Например, syslog и dbus могут запускаться параллельно с сервисами, использующими их. systemd создаёт файл сокета и запускает одновременно и сервисы, обслуживающие файл сокета, и сервисы, пользующиеся им. Если сервис-потребитель запустится раньше сервиса-поставщика, то запрос попадёт в сокет-файл и будет ожидать обработки до того момента, когда запустится сервис-поставщик.

2. Схема, ориентированная на активацию по запросу одиночных редко используемых сервисов. Примером такого сервиса может быть демон печати CUPS. systemd создаёт сокет-файл, который прослушивает самостоятельно. При поступлении запроса в сокет-файл, systemd запустит CUPS и передаст сокет-файл ему.

3. Схема, ориентированная на активацию по запросу многоэкземплярных сервисов, в которых каждое соединение или запрос обрабатываются отдельным процессом. Например, сервер ssh. systemd создаёт сокет-файл и прослушивает его самостоятельно, а при поступлении запроса запускает экземпляр сервиса и передаёт ему сокет-файл соединения, оставляя за собой сокет, ожидающий соединений.

Для примера настроим сокет-активацию SSH. Для этого необходимо создать два юнит-файла: юнит-файл, описывающий сокет, и юнит-файл, описывающий сервис.

Содержимое файла sshd.socket приведено ниже:
[Unit]
Description=SSH Socket for Per-Connection Servers

[Socket]
ListenStream=22
Accept=yes

[Install]
WantedBy=sockets.target
Директива ListenStream задаёт номер TCP-порта, на котором следует ожидать подключений.

Директива Accept указывает, как systemd должен поступать с поступающими запросами. В случае значения yes, systemd самостоятельно принимает соединение и передаёт сокет-файл соединения в сервис, что соответствует третьей схеме запуска сервисов. В случае значения no, systemd передаёт прослушивающий сокет-файл сервису, не принимая соединения, что соответствует второй схеме запуска.

В свою очередь, файл sshd@.service выглядит следующим образом:
[Unit]
Description=SSH Per-Connection Server for %I

[Service]
ExecStart=-/usr/sbin/sshd -i
StandartInput=socket
Минус перед строкой команды ExecStart означает, что systemd не будет запоминать код завершения каждого процесса. Сбросить коды завершения всех сервисов можно с помощью команды systemctl reset-failed.

Директива StandartInput указывает, что на стандартный ввод команды будут подаваться данные, принятые через сокет-файл соединения. Директива StandartOutput, если она не указана, принимает то же значение, что и директива StandartInput. В данном случае это означает, что стандартный вывод процесса будет перенаправляться в сокет-файл соединения.

Для запуска сервиса используется файл, описывающий сокет сервиса:
# systemctl enable sshd.socket
# systemctl start sshd.socket
Как и обычно, статус сервиса можно проверить с помощью команды systemctl status sshd.socket. Среди прочей информации в статусе будут присутствовать счётчик количества соединений, принятых с момента запуска сервиса (Accepted) и счётчик количества активных соединений (Connected).

Список активных сеансов можно увидеть с помощью команды:
# systemctl --full | grep sshd
А любой из экземпляров сервиса можно принудительно завершить, указав его идентификатор:
# systemctl kill sshd@171.31.0.52:22-172.31.0.4:47779.service
Подобным образом можно активировать только те сервисы, которые поддерживают получение сокет-файла. Сервисы, открывающие сокет-файл самостоятельно, активировать таким образом не получится.

Стоит отметить, что systemd не реализует полный набор возможностей, предоставляемых демонами inetd и xinetd. Однако, большинство из них либо устарели, либо легко реализуются другими средствами. Устаревшими можно считать, например, поддержку TCPMUX, RPC и встроенных сервисов типа echo, time, daytime, discard. Примером возможности, которую можно реализовать другими средствами, являются списки управления доступом с IP-адресов: её можно реализовать с помощью пакетного фильтра iptables. При этом, iptables позволяет организовать более тонкое и эффективное ограничение доступа, например, блокировать IP-адреса, превышающие лимит попыток подключения за единицу времени и т.п.

5. Сокет-активация контейнеров

Описанная в предыдущем разделе возможность активации сервисов по требованию оказалась особенно интересна компаниям, предоставляющим услуги веб-хостинга, поскольку позволяет экономить ресурсы сервера за счёт редко посещаемых сайтов. К стати пришлась и возможность создать один шаблон service-файла для всех сайтов клиентов, а также возможности по увеличению безопасности и ограничению использования системных ресурсов (эта тема будет подробно рассмотрена в следующей заметке).

Следующим логичным шагом в этом направлении является запуск по требованию не только сервисов, но и контейнеров с виртуальными серверами клиентов. Схема активации в этом случае выглядит так: процесс systemd, работающий на хост-системе, ожидает подключений к сокету. При поступлении запроса на подключение, запускается контейнер, а сокет передаётся на обслуживание процессу systemd, работающему внутри контейнера. Тот, в свою очередь, действует по схеме, описанной в предыдущем разделе, запуская необходимый сервис с помощью сокет-активации. Всё происходит совершенно прозрачно для клиента, разве что задержка между подключением и началом обслуживания может оказаться больше, чем обычно.

Для создания виртуального окружения в настоящее время в systemd используется утилита systemd-nspawn, которую, как надеются авторы, вскоре заменит libvirt-lxc (чуть более подробное описание systemd-nspawn появится в следующей заметке).

Внутри контейнера может быть установлен дистрибутив Linux, отличный от дистрибутива на хост-системе. Например, для установки Fedora и Debian можно воспользоваться соответствующими командами:
# yum --releasever=19 --nogpg --installroot=/srv/mycontainer/ --disablerepo='*' --enablerepo=fedora install systemd passwd yum fedora-release vim-minimal
# debootstrap --arch=amd64 unstable /srv/mycontainer/
После того, как вы убедились, что контейнер загружается и работает, можно приступить к настройке service-файла для активации контейнера /etc/systemd/system/mycontainer.service:
[Unit]
Description=My little container

[Service]
ExecStart=/usr/bin/systemd-nspawn -jbD /srv/mycontainer 3
KillMode=process
В полученный контейнер пока что нельзя попасть, поэтому нужно настроить на нём SSH-сервер. Для начала, настроим ожидание подключения к SSH-серверу контейнера на хост-системе в файле /etc/systemd/system/mycontainer.socket:
[Unit]
Description=The SSH socket of my little container

[Socket]
ListenStream=23
Для подключения по SSH к контейнеру будет использоваться 23 порт хост-системы. При желании можно использовать и 22 порт, ожидающий подключения на отдельном IP-адресе.

Теперь нужно настроить сокет-активацию сервера SSH внутри контейнера. Для этого можно воспользоваться файлами sshd.socket и ssh@.service из предыдущего раздела, нужно только не забыть поменять 22 порт на 23. Осталось лишь создать правильные символические ссылки для включения сервиса внутри контейнера. Поскольку команда systemctl enable не поддерживается в ранних версиях systemd, можно сделать это следующим образом:
# chroot /srv/mycontainer ln -s /etc/systemd/system/sshd.socket /etc/systemd/system/sockets.target.wants
В более свежих версиях systemd это можно сделать проще:
# systemctl --root=/srv/mycontainer enable sshd.socket
Теперь, при попытке подключиться к 23 TCP порту на хост-системе, автоматически будет запущен контейнер, внутрь него будет передан сокет, а systemd, находящийся внутри контейнера, запустит экземпляр сервиса SSH для обслуживания подключения.

Аналогичным образом можно добавить внутрь контейнера дополнительные сервисы, добавляя дополнительные директивы ListenStream в service-файл контейнера на хост-системе. При запуске контейнера, в него будут переданы все сокеты, описанные в его service-файле. Те сокеты, обслуживание которых не настроено внутри контейнера, будут закрыты.

В команде запуска systemd-nspawn из примера service-файла контейнера, указана опция -j, которая предписывает команде создать на хост-системе символическую ссылку на файл журнала journald, работающего внутри контейнера. При вводе команды systemd-journalctl с опцией -m на хост-системе, можно просматривать объединённый журнал хост-системы и контейнеров.

В настоящее время у такого подхода к активации контейнеров есть только один существенный недостаток - после обслуживания клиента контейнер продолжает работать, вхолостую используя ресурсы хост-системы. Над исправлением этого недостатка уже ведутся работы.

6. Проверка на виртуальность

Некоторые сценарии должны выполнять запуск сервиса только внутри виртуального окружения, или наоборот - только на хост-системе. Для этого в сценарии инициализации добавляются соответствующие средства обнаружения виртуального окружения и определения его типа. Чтобы упростить эти типовые действи, в systemd были добавлены несколько средств определения виртуальных окружений, каждое из которых может пригодиться в соответствующей ситуации. Эти средства позволяют определить конкретный тип виртуализации одного из следующих типов:

1. Полная виртуализация (vm): quemu, kvm, vmware, microsoft, oracle, xen, bochs,

2. Контейнерная виртуализация (container): chroot, openvz, lxc, lxc-libvirt, systemd-nspawn.

С помощью директивы ConditionVirtualization можно указать условие запуска сервиса: yes - сервис будет запускаться внутри виртуального окружения, no - сервис будет запускаться только на хост-системе. Можно указать значение vm или container, если сервис должен быть запущен только при обнаружении виртуального окружения из одной из этих групп. Можно указать и конкретный тип виртуального окружения. Для инверсии значения опции используется восклицательный знак в начале значения. Например, значение "!xen" предписывает запуск сервиса на хост-системе и внутри любого виртуального окружения, за исключением xen.

С помощью команды systemd-detect-virt можно определить наличие виртуального окружения и его тип из скриптов. Если команда обнаружит, что она была запущена из виртуальной среды, она вернёт код завершения 0 и ненулевое значение, если виртуальная среда не обнаружена. В случае обнаружения виртуальной среды команда выводит её тип на стандартный вывод. С помощью опции -q можно отключить вывод типа виртуальный среды. Опции -c и -v позволяют обнаруживать только средства контейнерной виртуализации или только средства полной виртуализации.

Программы, работающие с шиной DBus, могут узнать тип виртуального окружения, запросив соответствующее свойство у системной шины:
$ gdbus call --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1 --method org.freedesktop.DBus.Properties.Get org.freedesktop.systemd1.Manager Virtualization
Если виртуальное окружение не обнаружена, это свойство содержит пустую строку. (Команда очень проста для запоминания, не правда-ли? Слава роботам!)

В комплекте с Debian Wheezy поставляется версия systemd, в которой отсутствует поддержка проверки на виртуальность. В man systemd.unit встречается упоминание директивы ConditionVirtualization, но нет команды systemd-detect-virt, а запрос свойства DBus возвращает ошибку.

7. Документация сервисов

Файлы с описаниями сервисов могут содержать директивы со ссылками на документацию, относящуюся к данному сервису. Например, раздел Unit service-файла может выглядеть следующим образом:
[Unit]
Description=openntpd
Documentation=man:openntpd(8) http://www.openntpd.org
After=network.target
Поддерживаются схемы man/info и http/https. При наличии в service-файле ссылок на документацию, в выводе информации о состоянии сервиса с помощью команды systemctl status можно будет увидеть строки Docs и ссылки на документацию. Если используется терминал, работающий в графическом режиме, ссылки будут подсвечены и при щелчке по ним откроется документация.

При отсутствии графического терминала, открыть ссылки на документацию можно с помощью следующей команды:
$ systemctl help openntpd.service
К сожалению, эта возможность не поддерживается в systemd, поставляющемся в комплекте с Debian Wheezy.

8. Сторожевые таймеры

systemd поддерживает два вида сторожевых таймеров - аппаратные и программные.

Аппаратный сторожевой таймер принимает сигналы от программного обеспечения, работающего на компьютере и позволяет автоматически перезагрузить компьютер, если в течение определённого интервала времени от программного обеспечения не поступали сигналы.

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

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

8.1. Аппаратный сторожевой таймер

Аппаратный сторожевой таймер представлен в системе файлом устройства /dev/watchdog. Настройки systemd, связанные с аппаратным таймером, находятся в файле /etc/systemd/system.conf.

Директива RuntimeWatchdogSec по умолчанию принимает нулевое значение, что отключает использование аппаратного таймера systemd. При указании ненулевого значения systemd будет отправлять в аппаратный сторожевой таймер сигналы с периодом, равным половине указанного.

Директива ShutdownWatchdogSec задаёт длительность, отводимую на процесс перезагрузки системы. После сработки сторожевого таймера, пока система загружается, сторожевой таймер не будет предпринимать никаких действий в течение этого периода времени. Если по истечение этого периода времени сигналы не начнут поступать, аппаратный сторожевой таймер предпримет повторную попытку перезагрузки системы, посчитав что система зависла в процессе загрузки. По умолчанию эта директива принимает значение 10min.

Определить наличие в системе аппаратного сторожевого таймера можно с помощью команды wdctl из пакета util-linux (в Debian Wheezy такой команды в этом пакете нет).

8.2. Программные сторожевые таймеры

Если сервис поддерживает периодическую отправку сигналов с помощью системного вызова sd_notify, можно настроить его перезапуск средствами systemd в случае длительного отсутствия сигналов от него. Значение периода ожидания сигналов сообщается сервису с помощью переменной окружения WATCHDOG_USEC и задаётся в микросекундах. Сервис должен отправлять сигналы с периодичностью, равной половине указанной. При отсутствии этой переменной в окружении сервиса, он не должен отправлять сигналы с помощью sd_notify.

Настройки в service-файле позволяют определить реакцию systemd на отсутствие сигналов от сервиса в течение периода ожидания.

Директива WatchdogSec задаёт период ожидания сигналов от сервиса. Это значение передаётся сервису в переменной окружения WATCHDOG_USEC.

Директива Restart задаёт настройки условий перезапуска сервиса. Значение on-failure соответствует перезапуску сервиса в случае его сбоя и включает в себя в том числе ситуацию, когда сработал сторожевой таймер.

С помощью трёх следующих директив можно задать действия, предпринимаемые в случае слишком частых сбоев сервиса.

Директива StartLimitBurst задаёт количество сбоев, при достижении которого будет выполнено действие.

Директива StartLimitInterval задаёт интервал времени, за который должно произойти указанное количество сбоев, чтобы действие было выполнено.

Директива StartLimitAction задаёт выполняемое действие. Значение none не задаёт никаких действий. Значение reboot указывает на необходимость перезагрузить всю систему в штатном режиме. Значение reboot-force отправит систему на перезагрузку без корректной остановки сервисов. Значение reboot-immediate отправит систему в немедленную перезагрузку без корректной остановки сервисов и размонтирования файловых систем.

Директива OnFailure позволяет указать юнит, инициируемый в случае сбоя сервиса. В этом юните может быть настроен, например, скрипт отправки уведомлений системному администратору по электронной почте или по SMS.

Директива OnFailureIsolate позволяет перевести систему в другое состояние, например, остановив некоторые другие сервисы.

Ссылки:
1. systemd.directives — Index of configuration directives - указатель на страницы руководства, описывающие директивы юнит-файлов
2. Deploying Node.js with systemd - использование сокет-активации на веб-хостинге с сайтами на node.js

воскресенье, 17 февраля 2013 г.

systemd. Часть 1. Установка и использование

systemd - это новый демон инициализации Linux-систем, который в последующем должен прийти на смену классическому демону SysV initd. Основные задачи, которые он призван решить - это, во-первых, ускорение загрузки системы за счёт максимального увеличения количества параллельно запускающихся сервисов, во-вторых, улучшение управляемости системы за счёт использования специфических возможностей, предоставляемых ядром Linux, в-третьих, унификация общесистемных настроек и максимальное обобщение кода запуска различных сервисов. По крайней мере, такие ощущения у меня сложились после ознакомления с документацией.

Хочу поблагодарить Сергея Пташника за отличный перевод документации на systemd: systemd для администратора от автора самой системы, Леннарта Потеринга. Также хочется сказать спасибо за многочисленные примечания к документации, которые всегда оказываются к месту. Эта и последующие мои заметки существенным образом основываются именно на этой документации, а точнее, на её PDF-версии.

1. Установка systemd

Установить systemd довольно просто. Сначала поставим пакет:
# apt-get install systemd
И пропишем использование systemd в настройках ядра Linux. Для этого откроем файл с настройками загрузчика GRUB 2:
# vi /etc/default/grub
И добавим в настройку GRUB_CMDLINE_LINUX_DEFAULT дополнительную опцию init=/lib/systemd/systemd. После редактирования у меня эта опция стала выглядеть следующим образом:
GRUB_CMDLINE_LINUX="video=VGA-1:640x480 video=TV-1:640x480 rootfstype=ext4 init=/lib/systemd/systemd"
Теперь применим изменения, сгенерировав новый файл конфигурации загрузчика:
# update-grub
Теперь можно перезагрузить систему, чтобы начать использовать systemd:
# shutdown -r now
2. Решение проблем

2.1. Локализация текстовой консоли

Первая проблема, с которой я столкнулся - это ошибка запуска скрипта console-cyrillic. Этот скрипт должен запускаться при активной текстовой консоли, а systemd запускает все скрипты инициализации асинхронно, в результате чего этот скрипт отрабатывает к моменту, когда уже запущен X-сервер. Как следствие - в консоли вместо русских букв отображаются квадраты, нельзя переключить раскладку и переключиться обратно в графический сеанс.

console-cyrillic - это устаревший пакет, который остался в моей системе с тех времён, когда я её устанавливал (а было это во времена Etch). Этот пакет имеется в новых версиях Debian и до сих пор поддерживается, однако более универсальной альтернативой для него являются пакеты console-setup и keyboard-configuration. В статье Отображение русского в консоли Ubuntu 11.04 Natty описаны необходимые действия по их настройке.

Установим необходимые пакеты:
# apt-get install console-setup keyboard-configuration
Если по каким-то причинам конфигуратор пакетов не был запущен при установке пакетов, можно запустить конфигурирование пакетов принудительно:
# dpkg-reconfigure console-setup
# dpkg-reconfigure keyboard-configuration
Вообще, в systemd предусмотрены новые файлы конфигурации для настройки текстовой консоли, но в моей системе эти настройки не сработали.

2.2. Сохранение системного времени в аппаратные часы BIOS

При смене настроек времени, по каким-то причинам, не происходит сохранение времени в часах BIOS. Сохранить текущее системное время в аппаратные часы можно с помощью команды:
# hwclock -w
Реальное время, которое будет сохранено в часы BIOS, зависит от третьей строчки файла /etc/adjtime. В случае строки UTC будет сохраняться время по Гринвичу, в случае строки LOCAL - будет сохранено время текущего часового пояса.

Нужно отметить, что начиная с Wheezy, настройка UTC удалена из файла /etc/default/rcS и вместо неё теперь используется настройка из файла /etc/adjtime: release-notes: utc/local timezone no longer in /etc/default/rcS.

Корень же проблемы кроется в том, что я использую openntpd, а не ntpd. В случае работающего ntpd в ядре системы включается сохранение текущего системного времени в аппаратный таймер каждые 11 минут. Разработчики systemd посчитали, что этого достаточно. Если же ntpd не используется, то нельзя определить, какие из часов точнее - аппаратные или системные, поэтому и нет смысла предпочитать одни часы другим. В случае необходимости, пользователь должен сам настроить часы вручную необходимым образом.

Для решения этой проблемы я решил написать свой service-файл для демона openntpd. Содержимое service-файла /etc/systemd/system/openntpd.service:
[Unit]
Description=openntpd
After=network.target

[Service]
Type=simple
ExecStart=/usr/sbin/ntpd -dsf /etc/openntpd/ntpd.conf
ExecStartPost=/bin/chown ntpd /var/lib/openntpd/ntpd.drift
ExecStop=/sbin/hwclock -w

[Install]
WantedBy=multi-user.target
Теперь нужно остановить уже запущенный экземпляр openntpd, сообщить systemd о том, что его конфигурация изменилась:
# systemctl stop openntpd.service
# systemctl daemon-reload
Включить только что созданный файл сервиса в автозагрузку и запустить openntpd:
# systemctl enable openntpd.service
# systemctl start openntpd.service
3. Управление сервисами

systemd, в отличие от inetd, умеет самостоятельно отслеживать все процессы, порождённые сервисом. Для этого используются так называемые контрольные группы процессов - cgroups. Каждый сервис запускается с собственным идентификатором группы. Все дополнительные процессы, порождаемые в рамках сервиса, так же получают этот идентификатор. Благодаря этому отпадает необходимость в использовании PID-файлов для управления сервисом. Также, благодаря контрольным группам, процессы, порождённые сервисом, никогда не теряются. Например, CGI-процесс будет остановлен вместе с веб-сервером, даже если веб-сервер не позаботится о его остановке и не поместит идентификатор процесса в PID-файл. Процессы пользователей тоже помещаются в отдельную контрольную группу, и отслеживаются подсистемой logind, пришедшей на смену ConsoleKit.

Вторая важная особенность systemd заключается в том, что он обладает собственной системой журналирования, которая называется journald. Эта система агрегирует информацию из разных источников и привязывает её к сервисам. В одном месте с привязкой к сервису собираются сообщения ядра, сообщения процессов, отправленные через syslog, сообщения, отправленные с помощью собственного API journald и сообщения, отправленные процессом на стандартный вывод - STDOUT и на стандартный поток для диагностических сообщений - STDERR. Также systemd отслеживает коды завершения процессов. Благодаря этому, всю диагностическую информацию сервиса можно просматривать в одном месте и удобно диагностировать неисправности.

systemctl - просмотр статусов сервисов. Если вывод команды не перенаправляется куда-либо, а попадает на консоль, то для просмотра статусов сервисов автоматически запускается программа-пейджер, обычно это less,
systemctl status openntpd.service - подробный просмотр статуса указанного сервиса (openntpd),
systemctl status --follow openntpd.service - просмотр статуса указанного сервиса (openntpd) с выводом сообщений от сервиса в процессе их поступления. У этой опции есть более короткий аналог - -f, который в моей системе по каким-то причинам не заработал. Возможно дело в том, что опция -f имеет ещё второе значение - --force и systemctl не умеет определять, какая именно из опций имеется в виду,
systemctl status -n10 openntpd.service - просмотр статуса указанного сервиса (openntpd) с выводом 10 последних сообщений сервиса,
systemctl reset-failed - сброс всех статусов завершения, отображаемых по команде просмотра статуса сервиса,
systemd-cgls - просмотр иерархии контрольных групп процессов (нечто подобное можно получить при помощи команды ps xawf -eo pid,user,cgroups,args),
systemctl daemon-reload - перезагрузка конфигурации systemd,
systemctl start openntpd.service - запуск указанного сервиса (openntpd), аналогично update-rc.d openntpd start для SysV initd,
systemctl stop openntpd.service - остановка указанного сервиса (openntpd), аналогично update-rc.d openntpd stop для SysV initd,
systemctl restart openntpd.service - перезапуск указанного сервиса (openntpd),
systemctl enable openntpd.service - включение запуска указанного сервиса (openntpd) при загрузке системы, аналогично update-rc.d openntpd enable для SysV initd,
systemctl disable openntpd.service - отключение запуска указанного сервиса (openntpd) при загрузке системы, аналогично update-rc.d openntpd disable для SysV initd,
systemctl mask openntpd.service - запрет запуска указанного сервиса (openntpd), его даже будет нельзя запустить вручную,
systemctl unmask openntpd.service - разрешение запуска указанного сервиса (openntpd), его можно будет запустить вручную, также будет разрешён запуск при загрузке системы, если он был настроен,
systemctl kill openntpd.service - отправка сигнала (по умолчанию отправляется сигнал SIGTERM) всем процессам в контрольной группе сервиса (openntpd),
systemctl kill -s SIGKILL openntpd.service - отправка сигнала SIGKILL всем процессам в контрольной группе сервиса (openntpd). Можно также использовать сокращённое название сигнала - KILL,
systemctl kill -s HUP --kill-who=main crond.service - отправка сигнала SIGHUP главному процессу контрольной группы сервиса (crond). Этот пример заставит crond перечитать файл конфигурации, при этом задания, запущенные crond этот сигнал не получат и продолжат нормальную работу,
systemctl help openntpd.service - просмотр документации сервиса (не работает в Debian Wheezy),
systemd-analyze blame - вывод списка сервисов, отсортированного по убыванию времени, потраченного на их запуск. Команда может быть полезной для поиска узких мест в процессе инициализации системы,
systemd-analyze plot > plot.svg - вывод временнОй диаграммы в формате SVG, иллюстрирующей последовательность (и параллельность) запуска сервисов.

4. Новые файлы конфигурации системы

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

Кроме того, поскольку теперь все сервисы будут запускаться с помощью service-файлов, отпадает необходимость в использовании файлов, включаемых в shell-скрипты. Это файлы, располагающиеся в каталогах /etc/default (семейство Debian) и /etc/sysconfig (семейство RedHat). Поскольку в service-файлах ориентироваться намного проще, чем в shell-скриптах, нет необходимости выносить настройки сервисов в отдельные файлы - все необходимые настройки можно вписать прямо в service-файл.

Перечислим новые файлы конфигурации, вводимые systemd:

/etc/hostname - сетевое имя системы,
/etc/vconsole.conf - настройки шрифта системной консоли и раскладки клавиатуры,
/etc/locale.conf - языковые настройки системы,
/etc/modules-load.d/*.conf - каталог для перечисления модулей ядра, которые нужно принудительно загрузить при загрузке системы,
/etc/sysctl.d/*.conf - каталог для настроек параметров ядра, дополняет классический файл /etc/sysctl.conf,
/etc/tmpfiles.d/*.conf - каталог для управления настройками временных файлов,
/etc/binfmt.d/*.conf - каталог для регистрации форматов исполняемых файлов, например форматов Java, Mono, WINE,
/etc/os-release - файл с идентификатором дистрибутива и его версии,
/etc/machie-id - файл с постоянным уникальным идентификатором системы,
/etc/machie-info - файл с описательным сетевым именем системы. Здесь же настраивается значок системы, который будет отображаться в графических оболочках. Файл обслуживается демоном systemd-hostnamed.

5. Подсистема журналирования journald

Как уже было сказано, systemd вводит новую систему журналирования, которая собирает информацию из разных источников (сообщения ядра, сообщения, отправленные в syslog, на стандартный вывод STDOUT и на стандартный поток диагностических сообщений STDERR) в одном месте. Эта система не заставляет отказываться от стандартного демона журналирования syslog - можно пользоваться обеими системами журналирования параллельно.

journald использует для хранения журнальной информации два каталога:
/run/log/journal - каталог с кольцевым буфером последних сообщений,
/var/log/journal - каталог с постоянным хранением всех сообщений.

По умолчанию используется только первый каталог, а для включения постоянного хранения всех сообщений второй каталог нужно создать вручную (в Debian Wheezy этот каталог создаётся автоматически, при установке systemd, а первый каталог не используется):
# mkdir -p /var/log/journald
После этого можно, но совершенно не обязательно, удалить стандартный демон журналирования rsyslog или ng-syslog.

Настройки демона journald в Debian Wheezy хранятся в файле /etc/systemd/systemd-journald.conf. В частности, там можно задать настройки сжатия файла журнала, задать лимит размера файлов журнала, настроить дублирование сообщений в системную консоль или в демон syslog.

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

Для просмотра сообщений из журнала в Debian Wheezy можно воспользоваться командой systemd-journalctl (в руководстве указана команда journalctl):
systemd-journalctl - просмотр всех сообщений. Как и в случае systemctl, если вывод команды никуда не перенаправляется, а попадает на консоль, для более удобного просмотра сообщений автоматически запускается программа-пейджер, обычно less,
systemd-journalctl -f - просмотр сообщений в процессе их поступления,
systemd-journalctl -n10 - просмотр 10 последних сообщений,
systemd-journalctl -b - просмотр сообщений, сгенерированных с момента загрузки системы (не работает в Debian Wheezy),
systemd-journalctl -b -p err - просмотр сообщений, сгенерированных с момента загрузки системы и имеющих приоритет error или выше (не работает в Debian Wheezy),
systemd-journalctl --since=yesterday - просмотр всех сообщений, сгенерированных со вчерашнего дня (не работает в Debian Wheezy),
systemd-journalctl --since=2012-10-15 --until="2012-10-16 23:59:59" - просмотр всех сообщений, сгенерированных 15 и 16 октября 2012 года (не работает в Debian Wheezy),
systemd-journalctl -u httpd --since=00:00 --until=09:30 - просмотр всех сообщений, сгенерированных пользователем httpd сегодня с полуночи до полдесятого (не работает в Debian Wheezy),
systemd-journalctl /dev/sdc - просмотр всех сообщений, упоминающих диск sdc (не работает в Debian Wheezy),
systemd-journalctl /usr/sbin/vpnc - просмотр всех сообщений от процессов /usr/sbin/vpnc (не работает в Debian Wheezy),
systemd-journalctl /usr/sbin/vpnc /usr/sbin/dhclient - просмотр всех сообщений от процессов /usr/sbin/vpnc и /usr/sbin/dhclient, объединённый и отсортированный по времени (не работает в Debian Wheezy),

Кроме простого просмотра текстовых сообщений имеется возможность просматривать метаданные, которые journald самостоятельно добавляет к каждой записи в журнале. Чтобы увидеть эти поля, достаточно воспользоваться следующей опцией, переключающей формат вывода данных:
systemd-journalctl -o verbose - выводит вместе с сообщением из журнала все сопутствующие сообщению метаданные в удобном для восприятия человеком виде. Другие доступные форматы: export - тот же самый формат verbose, но без отступов, json - вывод в формате JSON, cat - вывод только текста сообщений без каких-либо дополнительных данных.

Названия полей, начинающиеся со знака подчёркивания, можно использовать для фильтрации сообщений. Журнал индексируется по всем полям, поэтому поиск выполняется быстро. Примеры фильтрации сообщений по метаданным:
systemd-journalctl _UID=70 - вывод всех сообщений от процессов пользователя с идентификатором 70,
systemd-journalctl _UID=70 _UID=71 - вывод всех сообщений от процессов пользователей с идентификаторами 70 и 71. Указание одноимённых полей автоматически подразумевает операцию логического ИЛИ,
systemd-journalctl _HOSTNAME=epsilon _COMM=avahi-daemon - вывод всех сообщений от процессов с именем avahi-daemon, работающих на компьютере с именем epsilon. В данном случае указаны разные поля, поэтому подразумевается операция логического И,
systemd-journalctl _HOSTNAME=theta _UID=70 + _HOSTNAME=epsilon _COMM=avahi-daemon - вывод сообщений, соответствующих любому из двух фильтров, объединённых знаком +. Знак + указывает явную операцию логического ИЛИ, которая имеет более низкий приоритет, чем И,
systemd-journalctl -F _SYSTEMD_UNIT - вывод всех значений поля _SYSTEMD_UNIT, имеющихся в журнале. Полученные значения можно использовать для фильтрации интересующих записей (не работает в Debian Wheezy).

Ссылки:
1. systemd - о systemd в Debian
2. Система инициализации Systemd. Часть I
3. Система инициализации Systemd. Часть II
4. systemd для администратора - русский перевод документации от автора systemd, Леннарта Потеринга
5. systemd для администратора - предыдущий перевод в формате PDF
6. Отображение русского в консоли Ubuntu 11.04 Natty
7. release-notes: utc/local timezone no longer in /etc/default/rcS
8. [systemd-devel] [ANNOUNCE] systemd v28

воскресенье, 10 февраля 2013 г.

Переход на ext4

Опустим обсуждения о том, зачем вам может понадобиться ext4 вместо ext3, решайте за себя сами. Меня привлекла лишь функция онлайн-дефрагментации, поэтому решил попробовать, тем более что из детских штанишек эта файловая система, вроде как, уже вышла.

Конвертировать можно только не смонтированные файловые системы, поэтому понадобится LiveCD или загрузочная флешка с каким-нибудь подходящим Linux'ом. Мне приглянулся Clonezilla, т.к. он основывается на Debian и в целом предназначен для работ подобного рода - разметки дисков, клонирования файловых систем и т.п.

Скачиваем дистрибутив Clonezilla со страницы http://clonezilla.org/downloads.php. Есть два варианта - скачать образ компакт-диска, или скачать архив для записи на флешку. С образом компакт-диска всё ясно - нужно просто скачать его и записать на диск. С архивом для записи на флешку всё немного сложнее - нужно разметить флешку, смонтировать её, распаковать на неё содержимое архива и запустить скрипт инсталляции.

Отформатируем раздел флешки:
# mkfs.vfat -F 32 /dev/sdb1
Смонтируем раздел, перейдём в него и распакуем в него архив:
# mount /dev/sdb1 /mnt/usb1
# cd /mnt/usb1
# unzip /home/stupin/Downloads/clonezilla-live-2.0.1-15-amd64.zip
Теперь осталось сделать флешку загрузочной. Для этого перейдём в каталог со скриптом установки и запустим скрипт:
# cd /mnt/usb1/utils/linux
# bash makeboot.sh /dev/sdb1
Осталось отмонтировать флешку:
# umount /dev/sdb1
Теперь нужно загрузить компьютер с флешки (или с компакт-диска) и перейти в режим командной строки.

Для конвертирования файловых систем из ext3 в ext4 нам нужны привилегии суперпользователя. Повысим привилегии пользователя, под которыми нас пустили в систему:
$ sudo su -
Теперь вводим пару команд для каждой файловой системы, которую нужно конвертировать (у меня это разделы sda2 и sda6):
# tune2fs -O extents,uninit_bg,dir_index /dev/sda2
# fsck -pf /dev/sda2
Первая команда отрабатывает довольно быстро, вторая же выполняется долго. На разделе размером 852 гигабайта вторая команда у меня работала полчаса.

Теперь можно, хотя и совершенно не обязательно, дефрагментировать файловые системы с помощью команд:
# mount /dev/sda2 /mnt
# e4defrag /dev/sda2
Команда дефрагментации требует, чтобы дефрагментируемая файловая система была смонтирована. У меня дефрагментация выполнялась в несколько раз дольше, чем конвертирование.

Настало время поправить настройки системы, чтобы она правильно загрузилась.Во-первых, нужно поправить тип файловой системы в файле /etc/fstab. Для этого смонтируем корневой раздел системы и заменим ext3 на ext4 для каждого сконвертированного раздела:
# mount /dev/sda2 /mnt
# vi /mnt/etc/fstab
Если был сконвертирован раздел с корневой файловой системой, то нужно ещё внести изменения в файл настройки загрузчика GRUB, добавив к опциям загрузки ядра текст rootfstype=ext4:
# vi /mnt/boot/grub/grub.cfg
Теперь можно отмонтировать файловые системы и выполнить перезагрузку с жёсткого диска:
# reboot
Загрузившись с жёсткого диска, первым делом нужно сделать настройки GRUB'а постоянными. Для этого откроем файл с настройками GRUB, добавим в опцию GRUB_CMDLINE_LINUX текст rootfstype=ext4 и перегенерируем, на всякий случай, файл конфигурации загрузчика:
# vi /etc/defaults/grub
# update-grub
Ссылки:
1. How-to`s — Ускоряем debian
2. Clonezilla Live on USB flash drive or USB hard drive

воскресенье, 3 февраля 2013 г.

Проблема с кодировками в пакете MySQLdb

Столкнулся с проблемой при подключении к MySQL с помощью пакета MySQLdb для python. При указании кодировки, содержащей знак минус в своём названии:
db = MySQLdb.connect(user = 'user',
                     passwd = 'p4assw0rd',
                     db = 'db',
                     charset = 'koi8r')
пакет отказывается работать, вываливая трейсбэк:
Traceback (most recent call last):
  File "./myapp.py", line 559, in 
    charset = 'koi8-r')
  File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/__init__.py", line 81, in Connect
    return Connection(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 215, in __init__
    self.set_character_set(charset)
  File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 294, in set_character_set
    super(Connection, self).set_character_set(charset)
_mysql_exceptions.OperationalError: (2019, "Can't initialize character set koi8-r (path: /usr/local/share/mysql/charsets/)")
Я знаю, что в MySQL приняты названия кодировок, из которых выкинуты минусы. Однако, если указать кодировку без минуса, то пакет всё равно отказывается работать, выдавая несколько другой трейсбэк:
Traceback (most recent call last):
  File "./myapp.py", line 612, in 
    merge_lists(list1, list2)
  File "./myapp.py", line 576, in merge_lists
    value = myfunction(list['key'])
  File "./myapp.py", line 514, in myfunction
    WHERE param = %s''', (value,))
  File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/cursors.py", line 159, in execute
    query = query % db.literal(args)
  File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 264, in literal
    return self.escape(o, self.encoders)
  File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 202, in unicode_literal
    return db.literal(u.encode(unicode_literal.charset))
LookupError: unknown encoding: koi8r
Что характерно, для кодировки UTF-8 один из вариантов написания всё-же работает. Почитал, что об этом пишут в интернете. Один умный анонимный товарищ объяснил ситуацию так:
А не MySQLdb ли там у Вас крутится?
если да - то тут две проблемы:
1. В mysql принято считать, что они лучше знают названия кодировок.
2. Модуль MySQLdb для питона считает, что в mysql действительно знают названия кодировок и верит,
что python в курсе, что кодировка KOI8-R называется "koi8r", хотя это далеко не так.
В модуле codecs python'а кодировка KOI8-R называется "koi8-r", что вообще-то соответствует стандарту.
python любит кодировать все строки в unicode. После чего, если они пытаются передаться в mysql, модуль MySQLdb
перекодирует из unicode в кодировку, установленную для соединения. А там 'koi8r', вот и ошибка вылетает.

Это лечится отстрелом авторов модуля MySQLdb.
Хотя впрочем, можно и всех mysqlистых ребят...
Это подтвердило мои собственные догадки. С момента написания этого высказывания прошло уже почти 5 лет, но ничего не изменилось. Видимо, это не нужно ни пользователям модуля, ни его разработчикам. Честно говоря, мне тоже не нужно - если бы у меня была возможность выбора, то я бы выбрал UTF-8, но у меня такой возможности не было.

Отредактируем модуль connections.py, входящий в пакет MySQLdb:
# vi /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py
И отредактируем метод set_character_set, как показано ниже:
def set_character_set(self, charset):
        """Set the connection character set to charset. The character
        set can only be changed in MySQL-4.1 and newer. If you try
        to change the character set from the current value in an
        older version, NotSupportedError will be raised."""
        mysql_charset = charset.replace('-', '')
        if self.character_set_name() != mysql_charset:
            try:
                super(Connection, self).set_character_set(mysql_charset)
            except AttributeError:
                if self._server_version < (4, 1):
                    raise NotSupportedError("server is too old to set charset")
                self.query('SET NAMES %s' % mysql_charset)
                self.store_result()
        self.string_decoder.charset = charset
        self.unicode_literal.charset = charset
Пересобирём модуль, чтобы им можно было пользоваться:
# python -m compileall /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py
Теперь откроем файл cursors.py:
# vi /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/cursor.py
и добавим метод character_set_name() в класс BaseCursor:
def character_set_name():
    db = self._get_db()
    charset = db.character_set_name()
    map = {'koi8r': 'koi8-r', 'koi8u': 'koi8-u'}
    return map.get(charset.lower(), charset)
А во всех следующих ниже методах заменим строки
db = self._get_db()
charset = db.character_set_name()
на строку:
charset = self.character_set_name()
И перекомпилируем модуль:
# python -m compileall /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/cursors.py
С такими костылями всё работает нормально, во всяком случае на ошибки я при этом не натыкался. При необходимости можно добавить дополнительные отображения названий кодировок в хэш map.