TL;DR:
OTA A/B обновление образа rootfs для IoT устройств с Linux при помощи проекта Mender.
Вступление
Недавно у меня возникла задача обновлять удалённо некоторое двузначное число IoT-устройств с Linux через интернет. Задачу нужно было решить быстро. Времени и желания изобретать что-то своё не было совсем. Устанавливать обновления вручную по ssh — нереально. Автоматизировать установку по ssh тоже, устройства могут неожиданно выключаться или терять сеть.
Поиск решений по обновлению IoT устройств показал, что, судя по всему, мне больше всего подходит Mender:
end-to-end решение — клиент + сервер + инструменты
A/B обновление rootfs — на устройстве есть два одинаковых раздела rootfs A и B. Система загружается с активного раздела A, устанавливает обновление на раздел B, загружается с раздела B и в случае успеха делает его активным. В случае неудачного обновления активным остаётся раздел A.
работает с debian и yocto
не использует контейнеры
есть open-source версия под лицензией Apache v2
На сайте проекта можно почитать как всё это работает.
Кстати, Mender уже упоминался на хабре в статье @MooooM, но внедрить его тогда не получилось.
Workflow
Я продемонстрирую процесс обновления системы на примере Raspberry Pi 3B c Raspberry Pi OS. Карта памяти от 8 ГБ и выше.
В качестве сервера Mender будем использовать бесплатный демо-сервер hosted.mender.io (не более 10 устройств и 12 месяцев работы).
Подготовка образов будет производится на компьютере с Ubuntu 20.04.
В своей документации Mender рекомендуют примерно такой подход для устройств с debian:
Установка чистой ОС
Подготовка эталонного образа
Копирование эталонного образа на ПК
Преобразование эталонного образа при помощи mender-convert
Подготовка устройства (provisioning)
Авторизация устройства
Подготовка пакета обновления (artifact)
Развёртывание обновления (deploy)
0. Сервер
Регистрируемся на hosted.mender.io.
1. Установка чистой ОС
Устанавливаем последнюю Raspberry Pi OS Lite по инструкции.
Включаем последовательную консоль /boot/config.txt
: enable_uart=1
2. Подготовка эталонного (golden) образа
Вставляем SD-карту в устройство(Raspberry Pi 3B) и подключаемся по uart при помощи minicom.
Меняем стандартный пароль при помощи raspi-config
.
Настройте и проверьте подключение к интернету. Я подключил свою Raspberry по ethernet (не совсем over-the-air, знаю).
Создадим директорию /data
, в которой будут храниться данные, которые должны сохраняться при обновлении образа системы.
Положим туда текстовый файл important_file.txt
содержащий одну строку hello_habr
.
3. Копирование эталонного образа
Выключаем устройство и вставляем SD-карту в компьютер:
Выводим список разделов:
lsblk -p
/dev/sda 8:0 1 7,4G 0 disk
├─/dev/sda1 8:1 1 256M 0 part
└─/dev/sda2 8:2 1 7,1G 0 part
Размонтируем разделы:
umount /dev/sdX1
umount /dev/sdX2
Считываем образ с карты:
sudo dd if=/dev/sdX of=golden-image-1.img bs=4M status=progress
4. Преобразование эталонного образа при помощи mender-convert
Для подготовки образа нам понадобится mender-convert и Docker.
git clone -b 2.5.0 https://github.com/mendersoftware/mender-convert.git
cd mender-convert
./docker-build
Проверяем ёмкость SD-карты, на которую будем устанавливать систему:
sudo fdisk -l
Disk /dev/sda: 7,38 GiB, 7910457344 bytes, 15450112 sectors
Переводим байты в мегабайты: 7910457344 / 1024 / 1024 = 7544 MB
Создаём файл с конфигурацией mender-convert ./configs/raspberrypi3_custom_config
с вот таким содержимым:
RASPBERRYPI_CONFIG="raspberrypi3"
RASPBERRYPI_KERNEL_IMAGE="kernel7.img"
MENDER_KERNEL_IMAGETYPE="zImage"
MENDER_DEVICE_TYPE="raspberrypi3"
MENDER_STORAGE_TOTAL_SIZE_MB="7500"
MENDER_DATA_PART_SIZE_MB="1000"
IMAGE_ROOTFS_SIZE="-1"
MENDER_ADDON_CONNECT_INSTALL="y"
source configs/raspberrypi_config
MENDER_STORAGE_TOTAL_SIZE_MB
— общий размер SD полученный ранее
MENDER_DATA_PART_SIZE_MB
— размер раздела /data
MENDER_ADDON_CONNECT_INSTALL
— флаг установки mender-connect (позволит запускать командную строку на удалённом устройстве и передавать файлы)
Создаём директорию ./rootfs_overlay/etc/mender
. Это оверлей файловой системы который будет записан на наш эталонный образ при запуске mender-convert.
Создаём файл конфигурации mender-client ./rootfs_overlay/etc/mender/mender.conf
:
{
"ServerURL": "https://hosted.mender.io/",
"TenantToken": "XXXX",
"UpdatePollIntervalSeconds": 300,
"InventoryPollIntervalSeconds": 300
}
TenantToken
- токен который нужно посмотреть в hosted.mender.io (Settings->Organization and billing->Organization token).
Создаём файл конфигурации mender-connect ./rootfs_overlay/etc/mender/mender-connect.conf
:
{
"ShellCommand": "/bin/bash",
"User": "pi"
}
Меняем владельца и группу оверлея:
sudo chown -R 0 ./rootfs_overlay/
sudo chgrp -R 0 ./rootfs_overlay/
Создаём директорию ./input
и кладём в неё образ golden-image-1.img
полученный ранее.
Запускаем mender-convert.
MENDER_ARTIFACT_NAME=release-1 ./docker-mender-convert --disk-image input/golden-image-1.img --config configs/raspberrypi3_custom_config --overlay rootfs_overlay/
MENDER_ARTIFACT_NAME
— название релиза, которое будет отображаться в hosted.mender.io.
Дальше происходит магия:
в образ устанавливается U-Boot
в Raspberry OS доустанавливается mender-client и mender-connect
создаются A и B разделы rootfs исходя из общего размера SD-карты, а также размера раздела data
данные из директории
/data
автоматически переносятся на новый раздел, который не будет перезаписываться при обновлении системы
Подробнее можно посмотреть в ./logs/convert.log.XXXX
.
На выходе получаем:
./deploy/golden-image-1-raspberrypi3-mender.img
- преобразованный образ системы для записи на SD-карту
./deploy/golden-image-1-raspberrypi3-mender.mender
- архив с обновлением, который загружается на сервер Mender(519МБ) и потом скачивается устройствами.
5. Подготовка устройства (provisioning)
Записываем образ golden-image-1-raspberrypi3-mender.img
на SD-карту:
sudo dd if=./deploy/golden-image-1-raspberrypi3-mender.img of=/dev/sdX bs=4M oflag=sync status=progress
Теперь разделы выглядят так:
/dev/sda 8:0 1 7,4G 0 disk
├─/dev/sda1 8:1 1 256M 0 part
├─/dev/sda2 8:2 1 3G 0 part
├─/dev/sda3 8:3 1 3G 0 part
└─/dev/sda4 8:4 1 1000M 0 part
Вывод uart при загрузке (у нас теперь есть U-Boot):
U-Boot 2020.01-g83cf4883ec (Jul 09 2021 - 14:23:27 +0000)
DRAM: 948 MiB
RPI 3 Model B (0xa02082)
MMC: mmc@7e202000: 0, sdhci@7e300000: 1
Loading Environment from MMC... OK
In: serial
Out: serial
Err: serial
Net: No ethernet found.
Hit any key to stop autoboot: 0
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
568 bytes read in 10 ms (54.7 KiB/s)
## Executing script at 02400000
switch to partitions #0, OK
mmc0 is current device
6320888 bytes read in 538 ms (11.2 MiB/s)
Kernel image @ 0x080000 [ 0x000000 - 0x6072f8 ]
## Flattened Device Tree blob at 2eff8c00
Booting using the fdt blob at 0x2eff8c00
Using Device Tree in place at 2eff8c00, end 2f002f2b
Starting kernel ...
[ 0.000000] Booting Linux on physical CPU 0x0
6. Авторизация устройства
Через пару минут после включения устройства на главной странице hosted.mender.io должно появиться сообщение о том, что новое устройство ожидает аутентификации. После подтверждения статус устройства поменяется с pending на accepted.
Через раздел Troubleshoot можно запустить удалённый терминал на устройстве (работает через mender-connect). При этом, на устройстве не включен ssh-сервер. Есть возможность передавать файлы через вкладку File transfer.
Теперь мы можем обновлять устройство по воздуху и физический доступ к нему больше не понадобится.
7. Подготовка пакета обновления (artifact)
Возвращаемся к нашему эталонному образу (golden-image-1.img). Загружаем ОС и вносим необходимые изменения. Я, например, сделаю MQTT-клиента, который шлёт на брокер температуру процессора (под спойлером).
MQTT-клиент
Устанавливаем пакеты:
sudo apt-get update
sudo apt install -y mosquitto mosquitto-clients
Создаём systemd таймер: /etc/systemd/system/mqtt.timer
[Unit]
Description=run mqtt.service every minute
[Timer]
AccuracySec=1
OnCalendar=*:*:0/30
Unit=mqtt.service
[Install]
WantedBy=multi-user.target
Создаём сервис, который будет вызываться таймером mqtt.timer: /etc/systemd/system/mqtt.service
[Unit]
Description=mqtt publisher service
[Service]
Type=simple
ExecStart=/home/pi/mosquitto_pub.sh
[Install]
WantedBy=multi-user.target
Скрипт, который будет вызываться сервисом mqtt.service: /home/pi/mosquitto_pub.sh
#!/bin/bash
tcpu=$(</sys/class/thermal/thermal_zone0/temp)
mosquitto_pub -h test.mosquitto.org -t "habr/$(cat /data/important_file.txt)" -m "{\"tcpu\": $((tcpu/1000))}"
Делаем скрипт исполняемым:
chmod +x /home/pi/mosquitto_pub.sh
Запускаем таймер:
sudo systemctl enable mqtt.timer
sudo systemctl start mqtt.timer
После внесения изменений снова считываем образ с карты:
sudo dd if=/dev/sda of=golden-image-2.img bs=4M status=progress
Преобразовываем образ при помощи mender-convert:
sudo MENDER_ARTIFACT_NAME=release-2 ./docker-mender-convert --disk-image input/golden-image-2.img --config configs/raspberrypi3_custom_config --overlay rootfs_overlay/
Загружаем в hosted.mender.io новый релиз golden-image-2-raspberrypi3-mender.mender. Заодно можно загрузить первый релиз, если захотим откатиться.
8. Deploy
Развёртывание (deploy) можно создать для отдельного устройства, группы устройств или сразу для всех. После создания развёртывания в течении пяти минут устройство проверит есть ли для него доступное обновление и начнёт загрузку.
После обновления проверим, начало ли устройство отправлять сообщения по MQTT (под спойлером):
MQTT Explorer
Установите MQTT Explorer.
Создайте подключение к mqtt://test.mosquitto.org, порт: 1883.
Подпишитесь на топик habr/#
Наблюдаем, как устройство остывает после недавнего апдейта:
Выводы
Mender позволяет довольно легко добавить функцию обновления образа системы по воздуху в существующее устройство.
Внедрение Mender никак не повлияло на процесс разарботки эталонного образа и последующих обновлений. Эталонный образ служит лишь источником для создания пакетов обновления.
Open-source версия Mender позволяет делать намного больше, чем показано в этой статье, например, state-scripts, identy и inventory устройств и т.д. Open-source версию сервера Mender можно самостоятельно развернуть в облаке используя Kubernetes.
Рекомендую внимательно изучить список фич доступных в open-source и коммерческих версиях. Open-source версия вполне работоспособна, но её может не хватить для того чтобы закрыть все потребности крупного проекта (нет автоматического перезапуска процесса обновления, дельта-апдейтов, мониторинга сервисов и т.д.).
Было бы интересно сравнить Mender c RAUC или swupdate в качестве клиента и hawkBit в качестве бэкенда.
На этом все, спасибо за внимание.
Комментарии (4)
me21
10.10.2021 13:50+1Тоже смотрю сейчас в сторону Mender. Но не хочу раскатывать полный образ устройства, думаю обновлять только приложение на основе Docker-контейнера, а образ системы обновлять пореже. Радует возможность установить свой сервер в локалке, а на крайний случай вообще обновить устройство с флэшки.
Ещё смотрел в сторону Balena, но там в случае локального сервера придётся накатывать образ системы целиком, и нет возможности оффлайн-обновлений.
RAUC и swupdate смотрел, но почему-то осталось впечатление, что информации по ним меньше, чем по этим двум системам, больше придётся работать напильником самому.
bao-eng Автор
10.10.2021 17:45Согласен, каждый раз выкачивать образ всей системы это оверкилл. Приложение можно обновить выпустив artifact с новыми бинарниками, пакетом или контейнером.
YMA
Картинка титульная с text2art.com? Интересно, какой был текст запроса? :)
bao-eng Автор
Это Big Sleep. Запрос: «linux update».