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"
}
Создаём директорию ./input
и кладём в неё образ golden-image-1.img
полученный ранее.
Запускаем mender-convert.
sudo 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 в качестве бэкенда.
На этом все, спасибо за внимание.
Автор:
bao-eng