четверг, 15 сентября 2016 г.

Настройка Raspberry Pi3 в качестве тонкого клиента LTSP, на базе Debian 8.5 (jessie)

    Сей девайс был заказан с aliexpress.com, и пришел в комплектации как есть на картинке, с парой радиаторов и корпусом. БП сделан самостоятельно, из того, что было под руками, поскольку пиковый ток устройства, исходя из официальной документации, может достигать 2.5А, а заказывать китайский не особо хотелось, по причине часто заниженных характеристик и нестабильной работе большинства "специально сделанных" для данного устройства, блоках.

Итак, в наличии имеем:
  • Сервер на Debian 8.5 Jessie, с установленным и работающим LTSP (с клиентами i386):
    • Linux server 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-2+deb8u3 (2016-07-02) x86_64 GNU/Linux
  • Raspberry Pi 3
Задача:
  • Добавить в LTSP образ клиента другой архитектуры
  • Установить Raspbian 8 (jessie) в качестве ОС для тонкого клиента на Raspberry Pi 3
    • Поставить последнюю актуальную версию ядра (4.4.20)
На момент написания статьи, в сети можно было найти несколько решений для данной или похожей задачи, как частичных, так и "готовых". Например, довольно популярный в этой сфере проект PiNet или универсальный boot-loader для RPi - BerryBoot, который включает в себя возможность создания из устройства LTSP-клиента (для уже готового сервера). Так или иначе, все эти варианты по разным причинам, были для меня неподходящими, да и хотелось самому поглядеть на это по-внимательнее, с разных сторон.

Предполагается, что у вас уже существует сервер с установленным и обновленным Debian jessie, и работающими на нем сервисами для нормального функционирования LTSP.
Обязательными, кроме самого LTSP, будут - SSHD, NBD, NFS, TFTPD-HPA, DHCPD.

Установка и настройка данных компонентов достаточно популярно описана на различных ресурсах в сети, поэтому тут, в полном объеме, приводиться не будет. Несколько полезных ссылок по этой теме я оставлю в конце статьи.



1. Настройка серверной стороны

Для начала, установим на сервер пакеты, позволяющие создавать образы LTSP-клиентов с архитектурой, отличной от amd64 и i386:
apt-get install qemu-user-static binfmt-support
Добавляем GPG-ключ репозитория Raspbian:
wget http://archive.raspbian.org/raspbian.public.key -O - | gpg --import

gpg --export 90FDDD2E >> /etc/ltsp/raspbian.public.key.gpg
Создаем файл конфигурации для клиента:

/etc/ltsp/ltsp-raspbian.conf
DEBOOTSTRAP_KEYRING=/etc/ltsp/raspbian.public.key.gpg
DIST=jessie
MIRROR=http://mirrordirector.raspbian.org/raspbian
SECURITY_MIRROR=none
UPDATES_MIRROR=none
LOCALE="$LANG UTF-8"
KERNEL_PACKAGES=linux-image-4.4
После этого можно запускать создание клиентской файловой системы:
ltsp-build-client --arch armhf --config /etc/ltsp/ltsp-raspbian.conf --prompt-rootpass 'pass'

2. Подготовка и создание образа

Как только создание файловой системы будет завершено, заходим в chroot:
ltsp-chroot --arch armhf
В первую очередь добавим репозиторий raspberrypi.org, в котором содержится утилита rpi-update.

/etc/apt/sources.list
deb http://mirrordirector.raspbian.org/raspbian jessie main contrib non-free rpi

deb http://archive.raspberrypi.org/debian/ jessie main
Монтируем proc:
mount -t proc proc /proc
Обновляем репозитории (пока нет GPG-ключа, ругнется на его отсутствие):
apt-get update

apt-get install vim wget less

wget http://archive.raspberrypi.org/debian/raspberrypi.gpg.key

apt-key add raspberrypi.gpg.key

apt-get upgrade
Инсталлируем окружение и пакет для обновления ядра RPi:
apt-get install lxde rpi-update
Очистим /boot и загрузим актуальное ядро:
rm -rf /boot/*

rpi-update
Есть важный момент, относительно команды rpi-update. Эта утилита является bash-скриптом, работающим через API github.com для единственного репозитория rpi-firmware, который содержит в себе все последние актуальные ядра и служебные файлы для работы Raspberry Pi. В конце статьи, я подробнее опишу некоторые аспекты, которые могут сильно помочь вам в случае проблем, а пока, просто продолжим.

Переименовываем файл ядра:
mv /boot/kernel7.img /boot/kernel7.img-4.4.20-v7+
Допишем модуль для обязательной загрузки (!):
echo "overlay" >> /etc/initramfs-tools/modules
Получив все нужные пакеты и обновив ядро на текущее (в моем случае 4.4.20), нужно собрать initrd.img, но тут есть тонкость (возможно, что это будет исправлено в дальнейшем, но данная ошибка стоила мне пары часов). Надо изменить файл скрипта инициализации, который запускается на этапе монтирования NBD-образа в клиентской системе.
Для этого, после создания initrd.img, перенесем его в пустой каталог /home и распакуем:
update-initramfs -c -k 4.4.20-v7+

mv initrd.img-4.4.20-v7+ /home && cd /home

gzip -cd initrd.img-4.4.20-v7+ | cpio -i
Нас интересует скрипт ./scripts/init-bottom/ltsp
Заменяем содержимое файла на следующее и запаковываем в каталог /boot, не забыв удалить сам образ. Настоятельно рекомендую заменять содержимое файла явно, через редактор, если есть такая возможность. По неясной для меня причине, варианты заливки через wget или curl, приводят к kernel panic при последующей загрузке RPi. Интересно, что при сравнении через diff\vimdiff, или визуально - никаких отличий нет, но в hex-виде, имеются лишние биты, что, вероятно, и является причиной проблемы.

Производим действия по замене, не забыв удалить сам файл образа из директории перед упаковкой обратно и очистив директорию от остального, после:
rm /home/initrd.img-4.4.20-v7+

find | cpio -H newc -o |gzip -9 > /boot/initrd.img-4.4.20-v7+

rm -rf /home/*
Можно еще немного изменить файл конфигурации образа:

/etc/lts.conf
[default]
    LTSP_CONFIG = True
    SOUND = True
    LOCALDEV = True
    REMOTE_APPS = True
    HOTPLUG = True
    NBD_SWAP = True

[ff:ff:ff:ff:ff:ff]
    LDM_LANGUAGE = "en_US.UTF-8"
    LDM_AUTOLOGIN = True
    LDM_USERNAME = user
    LDM_PASSWORD = pass
Все возможные переменные этого файла есть в мануале, но стоит иметь ввиду, что часть из них может быть несовместима с Raspbian.

Чтобы в логе dmesg не мелькала ошибка "Failed to start Load Kernel Modules", в файле /etc/modules-load.d/modules.conf, нужно закомментировать строку (в моем случае, подсистема печати мне нужна не была):
#lp
Выходим из chroot и собираем образ:
exit

ltsp-update-image armhf
Убедимся, что существует /etc/nbd-service/conf.d/ltsp_armhf.conf со следующим содержанием:
[/opt/ltsp/armhf]
exportname = /opt/ltsp/images/armhf.img
readonly = true
authfile = /etc/ltsp/nbd-server.allow
Перезагружаем NBD-сервис:
systemctl restart nbd-server
Также стоит проверить и /etc/ltsp/nbd-server.allow на предмет корректных ip-адресов, которым разрешен доступ к серверу по NBD.

3. Настройка клиентской стороны

Берем совершенно любую SD-карту, любого объема, форматируем в FAT:
mkfs.fat /dev/sdx
Копируем на нее файлы из директории сервера /opt/ltsp/armhf/boot
Также создаем там 2 файла:

cmdline.txt
dwc_otg.lpm_enable=0 console=serial0,115200 kgdboc=serial0,115200 console=tty1 init=/sbin/init-ltsp nbdroot=1.1.1.1:/opt/ltsp/armhf root=/dev/nbd0 elevator=deadline rootwait
config.txt


Файл конфигурации настраивается в зависимости от многих факторов, поэтому я привожу относительно универсальный, который, впоследствии стоит подкорректировать. Основной момент - в самом конце, где указываются файлы kernel7.img и initrd.img.
Больше информации по файлам конфигурации можно найти тут.

Размонтируем носитель и запускаем Raspberry.
Система должна будет загрузиться до окна логина LXDE.

На этом вся настройка завершена. В остальном, все соответствует с настройкой обычных LTSP-клиентов.
Команда uname -a должна выводить:
Linux user 4.4.20-v7+ #908 SMP Wed Sep 7 14:44:27 BST 2016 armv7l GNU/Linux



4. Troubleshooting

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

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

1. RPi зависает на этапе инициализации. На мониторе тест GPU-Rainbow:
  • Неверные данные\нечитаемые символы в файлах config.txt или cmdline.txt
  • Проблемы ФС карты памяти
  • Различные версии бинарных файлов
  • Неверно указаны версии kernel7.img\initrd.img в config.txt
Информацию по отладке, на этапе инициализации устройства, можно найти по ссылкам:

2. RPi проходит этап инициализации, но, при попытке подгрузки ядра вылетает в консоль (initramfs) [BusyBox v1.22.1]:

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

В большинстве случаев, на экран будет выведено, на каком этапе инициализации ядра произошла ошибка. Если вы используете переходник HDMI-VGA\DVI, то, чтобы вместить в монитор максимальное количество выходной информации, стоит заранее откорректировать разрешение вывода в файле config.txt. Делается это с помощью указания значения для переменных hdmi_group и hdmi_mode. К примеру, для монитора с разрешением 1280x1024 нужно использовать значения hdmi_group=2, hdmi_mode=36. Таблицы, по которым можно выбрать нужные значения, находятся на официальном сайте.

90% падений на этапе инициализации ядра происходит в процессе выполнения скриптов из директории ./scripts/, которую мы видели ранее, когда изменяли файл ltsp. Но, когда вы находитесь в консоли (initramfs), никаких действий по распаковке делать не нужно. Просто запустите тот скрипт, который выдал ошибку при загрузке, и изучите его вывод. Вероятнее всего, будет выведен и номер строки, на которой эта ошибка произошла. Если это произошло, то с помощью vi, проверьте ее содержание - это может дать подсказку о том, из-за чего загрузка была прервана.
Обязательно проверьте, получил ли клиент IP-адрес с помощью ifconfig. Можно попробовать пропинговать сервер, но учтите, что остановить пинг ничем, кроме перезагрузки, не получится. Keyboard Interrupt не перехватываются.
Если проблемы в каком-то модуле ядра, то стандартные lsmod, modprobe и прочие инструменты для работы с модулями также доступны. Порой, только этого достаточно, чтобы понять, что модуль, например, не найден или находится не там, где нужно.

Если в корневом каталоге имеется каталог /rofs, и в нем находятся папки с NBD-образа сервера, значит этап монтирования прошел успешно.

Вывод dmseg в отладочной консоли, к сожалению, особой информацией не блещет, но есть и другой способ. О нем, немного позже.
Очень помогает также, параллельно наблюдать текущий вывод логов сервера, в процессе загрузки RPi. Например, через journalctl -f.
Это дает возможность в реальном времени видеть, когда возникла ошибка, обратился ли клиент к нужным файлам, или, например, не смог их найти. Наиболее полезным тут, может оказаться лог NBD-сервера.
К примеру, ошибка "Read failed: Bad file descriptor", может возникнуть, если скрипт ltsp в образе initrd.img не изменять. 
Ошибка "virststyle ipliteral" -  при использовании wget для изменения скрипта ltsp, что подводит нас к следующему пункту.

3. При попытке загрузки системы, ядро вываливается в ранее упомянутый Kernel Panic.

Эта проблема возникла после написания статьи, во время проверки написанного и параллельного моделирования, чем довольно сильно меня удивила.
Сложность была в том, что при kernel panic система не дает возможности выполнять какие-либо команды, а, из-за большого размера вывода, становится полностью непонятно, что именно послужило причиной.
Логи сервера в этот раз, ничем не помогли, равно, как и логи сетевого трафика, снятого через tshark. Я видел, что процесс инициализации вроде как прошел, и файловая система смонтировалась. Видел и обращение к NBD-образу, но было совершенно не очевидно, в чем была сама причина.
Смазало картину еще и то, что за время, пока писалась статья, в основную ветку ядра было замерждено 2 коммита, которые меняли довольно большое количество файлов, включая бинарные файлы RPi и приличное количество модулей ядра.
В логах NBD-сервиса сервера в больших количествах сыпались "virststyle ipliteral", напоминающие какие-то проблемы на сетевом уровне.
В первую очередь были проверены все интерфейсы, просмотрен трафик, но  ни битых пакетов, ни даже ритрансмишенов найдено не было, равно как и дропнутых пакетов на интерфейсе.

Как я упоминал ранее, у скрипта rpi-update есть пара особенностей, одна из которых дает возможность скачать версию ядра именно на том коммите, который вам нужен, если в релизе возникли проблемы.
Именно это и было сделано. Через
rpi-update b0ef6e25679d3612a980708cf4c3907ce6e13e84
я получил именно ту версию файлов ядра и модулей, которые использовались в процессе настройки и отладки. Далее, как обычно, создание initrd.img, распаковка, замена скрипта ltsp через wget, и....
Разумеется, я получил тот же самый kernel panic...

Гипотеза с "битым" файлом в обновленной версии одного из последних коммитов ядра отпала, и стало ясно, что либо я что-то упустил, и забыл добавить в статью, либо что-то из этого я сделал по-другому.
Для того, чтобы относительно прояснить ситуацию, нужен был полный лог загрузки, от инициализации RPi, до kernel panic. А поскольку программно этого было не сделать, на помощь пришел... разумеется UART. Кто читал мои прошлые статьи, тот знает, что его я ищу в первую очередь :)
Разумеется для такой проработанной машинки, как Raspberry Pi, было ясно, что UART есть, и выведен на отдельные пины GPIO.
Беглый поиск привел к одному обсуждению на github, где была нужная мне информация. 14 и 15 пины GPIO, включение в config.txt строки enable_uart=1, и вот minicom уже выводит все, что я хотел увидеть.
В качестве USB-to-TTL я использовал мой традиционный шнурок от телефона на PL-2303. Команда на запуск minicom:
minicom -l -8 -c on -s -D /dev/ttyUSB3
Правда особой радости это не принесло. Судя по сетевому трафику и логу:
[ 7.470081] nbd: registered device at major 43
[ 7.686706] squashfs: version 4.0 (2009/01/31) Phillip Lougher
[ 9.344819] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000200
[ 9.344819]
[ 9.375963] CPU: 1 PID: 1 Comm: init-ltsp Not tainted 4.4.20-v7+ #909
было видно, что NDB-образ монтировался, но дальше никакой полезной информации не было. Полный лог тут.
Мелькнуло несколько мыслей о, возможно, аппаратном происхождении данной проблемы, но это было слишком маловероятно. Дядюшка Оккама не раз подтверждал свою компетентность, поэтому, для очистки совести, я успешно запустился с другой SD локально, чем подтвердил, что надо искать причину в другом месте.
Самый простой способ - просмотреть history командной строки сервера, и еще раз проверить все шаги.
Единственным шагом, который я решил изменить - было использование wget для более быстрого изменения скрипта init-bottom/ltsp. Проверка этой теории и решила данную проблему.

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

4. Система загружается полностью, запускается окружение, но интерфейсы управления и USB-порты не работают.

Данное поведение характерно для ситуации, когда версия модулей ядра каталога /lib/modules вашего образа, не соответствуют версиям модулей запущенного ядра Raspberry.
Чтобы избежать это проблемы, всегда очищайте каталог /boot вручную, перед установкой новой версии ядра. В теории, это автоматически делает утилита rpi-update (вместе с автоматическим бэкапом), но лучше все же, делать это самому.
К слову, если у вас возникла необходимость принудительно обновить ядро через rpi-update, то удалите скрытый файл /boot/.firmware_revision прежде чем ее запускать.

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





Читать дальше...

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

Отправить комментарий