Оформить опыт с Gitea CI-раннерами в легенду и docker-кейс
Реальная настройка self-hosted CI Gitea Actions (починка раннера из restart-loop, кастомный cross-builder под ARM) вписана в опыт АО ТНИИС: LEGEND.md/STORY.md — расширены строки Gitea/Docker и раздел про два CI-инструмента отдела, docker/CASE.md — новый раздел 9 с воспроизводимыми фрагментами, ci-cd/CASE.md — ссылка на реальный кейс. SERVER.md/PROGRESS.md актуализированы: исправлена ошибочная запись про порт 3000 (это Grafana, не Gitea) и добавлен чек-лист, что нельзя ломать в CI-инфраструктуре сервера при прохождении курса Kubernetes.
This commit is contained in:
@@ -106,13 +106,84 @@ docker cp ./local-file.txt <container>:/app/file.txt
|
||||
docker cp <container>:/app/output.log ./output.log
|
||||
```
|
||||
|
||||
### 9. Реальный кейс: self-hosted CI-раннеры Gitea Actions
|
||||
|
||||
В отличие от разделов 1–8 (демонстрационная практика), это реально работающая инфраструктура: собственный инстанс Gitea с двумя CI-раннерами Gitea Actions под кросс-сборку встроенного C-кода для ARM-плат (проект на базе SDR-платформы Pluto — сигнальная обработка на Cortex-A9). Все конфиги ниже — не иллюстрация, а рабочие файлы сервера.
|
||||
|
||||
#### 9.1. Диагностика контейнера в restart-loop
|
||||
|
||||
```bash
|
||||
docker ps -a # статус Restarting — контейнер падает и рестартует по кругу
|
||||
docker logs --tail 50 gitea_runner # видно, на каком шаге падает
|
||||
docker inspect -f '{{.State.Status}}' gitea_runner # быстрая проверка без полного inspect
|
||||
```
|
||||
|
||||
Симптом в логах — раннер не может подтвердить свою регистрацию на сервере (`unregistered runner`). Причина — файл состояния регистрации лежит на bind mount и пережил пересоздание контейнера, но сервер эту регистрацию (токен) больше не признаёт. Урок: персистентный state на volume — это одновременно удобство (не нужно перерегистрироваться при каждом пересоздании контейнера) и риск (протухшее состояние переживает контейнер и требует ручной диагностики, а не просто `docker restart`). Лечение — остановить контейнер, удалить устаревший файл состояния, поднять заново с актуальным токеном регистрации.
|
||||
|
||||
#### 9.2. Кастомный образ cross-builder'а
|
||||
|
||||
```dockerfile
|
||||
FROM debian:bookworm-slim
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential gcc-arm-linux-gnueabihf libc6-dev-armhf-cross \
|
||||
autoconf automake libtool pkg-config cmake git wget curl socat
|
||||
COPY build_deps.sh /root/build_deps.sh
|
||||
RUN bash /root/build_deps.sh # кросс-сборка статических ARM-библиотек прямо на этапе сборки образа
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
```
|
||||
|
||||
Ключевой момент, в котором легко ошибиться: кросс-компиляция **не требует** эмуляции ARM. `arm-linux-gnueabihf-gcc` — обычный x86-бинарь, который на входе берёт `.c` и на выходе даёт ARM-машинный код, выполняясь на полной скорости x86-ядра. Первая наивная попытка задать в compose `platform: linux/arm/v7` привела к `exec /sbin/tini: exec format error` — Docker пытался выполнить ARM-бинарь на x86-ядре без qemu/binfmt-регистрации. Рабочее решение — нативный `linux/amd64`-образ, внутрь которого установлен ARM-кросс-тулчейн как обычный пакет.
|
||||
|
||||
#### 9.3. Сети контейнеров: коллизия портов и host-gateway
|
||||
|
||||
Реальная ситуация: CI-джоба клонирует репозиторий по `http://.../localhost:3000/...`, но порт 3000 на хосте занят другим сервисом (не Gitea), а сама Gitea слушает другой порт. Контейнер раннера работает не в `network_mode: host`, а в обычной bridge-сети — значит его `localhost` изолирован от хостового. Решение:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
runner-armhf:
|
||||
extra_hosts:
|
||||
- "gitea-host:host-gateway" # спец-имя host-gateway — резолвится в реальный IP хоста
|
||||
```
|
||||
|
||||
```bash
|
||||
# entrypoint.sh — проброс localhost:3000 контейнера на реальный порт Gitea на хосте
|
||||
socat TCP-LISTEN:3000,fork,reuseaddr,bind=127.0.0.1 TCP:gitea-host:3030 &
|
||||
```
|
||||
|
||||
Это тот же принцип DNS-резолвинга сервисов по имени, что и в разделе 3, только в обратную сторону — контейнеру нужно достучаться не до соседнего сервиса в той же сети, а до порта на самом хосте, при этом не отдавая ему `network_mode: host` целиком (это сломало бы остальную изоляцию раннера).
|
||||
|
||||
#### 9.4. Маршрутизация job'ов по меткам
|
||||
|
||||
```bash
|
||||
# entrypoint.sh — регистрация только при первом старте (файл состояния ещё не существует)
|
||||
if [ ! -f /data/.runner ]; then
|
||||
act_runner register --no-interactive --instance "$GITEA_INSTANCE_URL" \
|
||||
--token "$GITEA_RUNNER_REGISTRATION_TOKEN" --labels "$GITEA_RUNNER_LABELS"
|
||||
fi
|
||||
exec act_runner daemon --config /data/config.yaml
|
||||
```
|
||||
|
||||
Два раннера с разными метками (`ubuntu-latest`/общие задачи и `armhf`/кросс-сборка) разбирают джобы по `runs-on:` в workflow — маршрутизация нагрузки на уровне меток, аналогично тому, как в Kubernetes джобы распределяются по нодам через `nodeSelector`. `restart: unless-stopped` в compose — минимальная альтернатива systemd-юниту там, где не нужен сложный порядок запуска.
|
||||
|
||||
#### 9.5. Проверка результата сборки: ELF-заголовок
|
||||
|
||||
Файл лежит на диске и даже отвечает на `command -v` — это ещё не значит, что он собран под нужную архитектуру. Финальная проверка в pipeline — чтение байта `e_machine` прямо из ELF-заголовка:
|
||||
|
||||
```bash
|
||||
od -An -tx1 -j 18 -N 2 build/binary | tr -d ' \n' # ждём 2800 = EM_ARM в little-endian
|
||||
```
|
||||
|
||||
## Что это даёт в разговоре с интервьюером
|
||||
|
||||
- Понимание, что контейнер технически — обычный процесс хоста с изоляцией через namespaces/cgroups, а не мини-VM.
|
||||
- Практика multi-stage build и осознанного слоёного кеширования, а не просто «Dockerfile работает».
|
||||
- Знание разницы bind mount/named volume не абстрактно, а с привязкой к тому, где какой тип реально применён в других кейсах репозитория.
|
||||
- Воспроизведённый вживую OOM kill (код 137) — понимание на практике, а не только в теории.
|
||||
- Реальная диагностика упавшего в продакшене контейнера по логам и статусу, а не смоделированная ситуация.
|
||||
- Сборка кастомного образа под нестандартную задачу (кросс-компилятор + прекомпилированные зависимости внутри образа), а не использование готового образа из Docker Hub.
|
||||
- Сетевая коллизия портов, решённая без изменения архитектуры (host-gateway + проброс), — практика того, что сети Docker не всегда «просто работают из коробки».
|
||||
|
||||
## Как это ложится в легенду
|
||||
|
||||
В реальной работе (АО ТНИИС) Docker упоминается в стеке отдела как средство контейнеризации сервисов сопровождения. Этот кейс показывает собственный опыт сборки, сетевого взаимодействия и диагностики контейнеров поверх уже готовых стендов мониторинга и nginx.
|
||||
В реальной работе (АО ТНИИС) Docker упоминается в стеке отдела как средство контейнеризации сервисов сопровождения. Разделы 1–8 — практика поверх уже готовых стендов мониторинга и nginx. Раздел 9 — реальный рабочий кейс: администрирование Gitea и её CI-раннеров, включая сборку кастомного образа cross-builder'а для кросс-компиляции встроенного C-кода под ARM (проект отдела на SDR-платформе, см. [../../legend/LEGEND.md](../../legend/LEGEND.md) → «АО ТНИИС» → Gitea).
|
||||
|
||||
Reference in New Issue
Block a user