OTA обновление устройств с Linux

image-loader.svg

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:

  1. Установка чистой ОС

  2. Подготовка эталонного образа

  3. Копирование эталонного образа на ПК

  4. Преобразование эталонного образа при помощи mender-convert

  5. Подготовка устройства (provisioning)

  6. Авторизация устройства

  7. Подготовка пакета обновления (artifact)

  8. Развёртывание обновления (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.

image-loader.svg

Через раздел Troubleshoot можно запустить удалённый терминал на устройстве (работает через mender-connect). При этом, на устройстве не включен ssh-сервер. Есть возможность передавать файлы через вкладку File transfer.

image-loader.svg

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

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=$(

Делаем скрипт исполняемым:

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) можно создать для отдельного устройства, группы устройств или сразу для всех. После создания развёртывания в течении пяти минут устройство проверит есть ли для него доступное обновление и начнёт загрузку.

image-loader.svg

После обновления проверим, начало ли устройство отправлять сообщения по MQTT (под спойлером):

MQTT Explorer

Установите MQTT Explorer.

Создайте подключение к mqtt://test.mosquitto.org, порт: 1883.

Подпишитесь на топик habr/#

Наблюдаем, как устройство остывает после недавнего апдейта:

image-loader.svg

Выводы

Mender позволяет довольно легко добавить функцию обновления образа системы по воздуху в существующее устройство.

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

Open-source версия Mender позволяет делать намного больше, чем показано в этой статье, например, state-scripts, identy и inventory устройств и т.д. Open-source версию сервера Mender можно самостоятельно развернуть в облаке используя Kubernetes.

Рекомендую внимательно изучить список фич доступных в open-source и коммерческих версиях. Open-source версия вполне работоспособна, но её может не хватить для того чтобы закрыть все потребности крупного проекта (нет автоматического перезапуска процесса обновления, дельта-апдейтов, мониторинга сервисов и т.д.).

Было бы интересно сравнить Mender c RAUC или swupdate в качестве клиента и hawkBit в качестве бэкенда.

На этом все, спасибо за внимание.

© Habrahabr.ru