Хочется собрать один файл, закинуть его на плату и забыть. На практике между «хочется» и «работает» лежит чужой libc.
Исходная боль понятна каждому, кто возил софт на встраиваемое железо. На плате — Debian с ядром 5.10-bsp от вендора и glibc 2.31. На ноутбуке — Ubuntu 24.04 и glibc 2.39. Собираешь у себя, копируешь на плату, получаешь:
./agent: /lib/aarch64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found
Классика. glibc обратно совместим вперёд, но не назад: бинарь, собранный против новой версии, на старой не пойдёт. А обновить libc на вендорском образе — это отдельный вид развлечения, обычно заканчивающийся кирпичом.
Из этого есть ровно три выхода, и я прошёл все:
Выбрал третье, о чём в целом не жалею, но нюансов там больше, чем обещают туториалы.
Начал со стандартного:
rustup target add aarch64-unknown-linux-musl
cargo build --release --target aarch64-unknown-linux-musl
Пока в проекте чистый Rust — это просто работает. Как только появляется хоть один crate с C-кодом (а он появляется: ring, zstd, любой -sys), нужен ещё и кросс-компилятор C. Возиться с musl-cross-make я не захотел и взял cargo-zigbuild:
cargo install cargo-zigbuild
cargo zigbuild --release --target aarch64-unknown-linux-musl
Zig таскает с собой полный кросс-тулчейн и умеет прикидываться C-компилятором под любую тройку. Один инструмент вместо зоопарка. Единственная плата — Zig в зависимостях сборки, но он ставится одним бинарём и в CI ведёт себя прилично.
Если нужен вариант совсем без сторонних утилит — есть cross, который прячет всё в докер. Мне не подошло: на сборочной машине докера нет принципиально.
Вот главная грабля, ради которой и писалась заметка.
Резолвер у musl свой, минималистичный, и он не поддерживает NSS. Всё, что описано в /etc/nsswitch.conf, для musl-бинаря не существует. Практические последствия:
hosts: files dns mdns4_minimal — mDNS не отработает. Имена вида plate.local не резолвятся, хотя с glibc-бинарём резолвились.sssd, любые экзотические источники имён — мимо.У меня это выстрелило ровно так: агент ходил на nvr.local, всё работало на ноуте и намертво не работало на плате. Полдня на «да сеть же есть, ping идёт» — ping шёл, потому что ping системный, glibc'шный.
Лечение — не полагаться на системный резолвер вообще. В Rust это делается легко: берём hickory-resolver (бывший trust-dns) и резолвим сами, читая /etc/resolv.conf. Заодно получаем контроль над таймаутами, чего с getaddrinfo не добиться никак.
Либо — просто выкинуть имена и ходить по IP. На стенде это, честно говоря, чаще правильный ответ.
Вторая засада тише и потому противнее. Аллокатор в musl оптимизирован под размер и простоту, а не под скорость и многопоточность. На однопоточной утилите разницы нет. На чём-то, что активно аллоцирует из нескольких потоков, можно словить замедление в разы — у меня парсер метрик просел примерно втрое.
Лечится одной строчкой:
[dependencies]
mimalloc = { version = "0.1", default-features = false }
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
После этого разрыв с glibc-сборкой ушёл в пределы погрешности. jemalloc тоже подходит, но он потяжелее и с musl исторически капризнее.
Отдельный совет, который экономит часы. Если в проекте есть HTTPS — не связывайтесь с openssl-sys при кросс-сборке. Либо vendored-сборка (медленно, но работает), либо, что гораздо лучше, rustls:
reqwest = { version = "0.12", default-features = false,
features = ["rustls-tls", "json"] }
Чистый Rust, никакого C, кросс-компиляция становится скучной — то есть именно такой, какой должна быть. Единственное: корневые сертификаты придётся принести самому — webpki-roots зашьёт их в бинарь, либо rustls-native-certs прочитает системные.
Что получилось на выходе:
$ file agent
agent: ELF 64-bit LSB executable, ARM aarch64, statically linked, stripped
$ ls -lh agent
-rwxr-xr-x 1 ilya ilya 4.1M agent
$ ldd agent
statically linked
4 мегабайта, ноль зависимостей, копируется scp и запускается на любой aarch64-плате независимо от того, что у неё за дистрибутив и какого года. Деплой превратился в одну команду.
Стоило ли терпеть ради этого musl со всеми его особенностями — да, безусловно. Но с оговоркой: если ваш софт полагается на системный резолвер и NSS, musl вас укусит, и укусит не там, где вы будете искать. Резолвите сами.