Offline-развёртывание, зеркалирование репозиториев, управление зависимостями без доступа к интернету, air-gap стратегии.
Offline-развёртывание, зеркалирование репозиториев, управление зависимостями без доступа к интернету, air-gap стратегии
Представьте: вы работаете в банке, государственной организации или оборонной компании. Ваша инфраструктура полностью изолирована от интернета — никаких внешних подключений, облаков, публичных репозиториев. Это air-gap (воздушный зазор) — физическая или логическая изоляция.
В таких условиях стандартные практики DevOps не работают:
npm install не выполнится без доступа к npm registrydocker pull не скачает образы без Docker Hubgit clone с GitHub невозможенЭта тема — о том, как построить полностью автономную Git-платформу на базе Gitea для работы в закрытом контуре.
┌─────────────────────────────────────────────────────────────────┐
│ Интернет (внешний мир) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ GitHub │ │ Docker Hub │ │ npm/pypi/maven registry│ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↑
(синхронизация через промежуточный сервер)
↓
┌─────────────────────────────────────────────────────────────────┐
│ Промежуточный сервер (DMZ) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Кэш зеркал: git, docker, npm, pypi, maven │ │
│ │ Скрипты синхронизации (по расписанию) │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
↑
(односторонняя синхронизация)
↓
┌─────────────────────────────────────────────────────────────────┐
│ Закрытый контур (внутренняя сеть) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Gitea │ │ Registry │ │ Artifact Repository │ │
│ │ сервер │ │ образов │ │ (npm/pypi/maven) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
│ ↑ │
│ └────────────────────────────────────────────────────── │
│ Внутренняя сеть организации │
└─────────────────────────────────────────────────────────────────┘
Gitea имеет встроенную функцию зеркалирования репозиториев.
Шаги:
Создание зеркала:
https://github.com/organization/repo.gitНастройка интервала синхронизации:
В app.ini (или через переменные окружения):
[repository]
DEFAULT_MIRROR_INTERVAL = 60 # минут# Через веб-интерфейс: репозиторий → Настройки → Зеркало → Синхронизировать
# Через API
curl -X POST http://gitea/api/v1/repos/{owner}/{repo}/mirror-sync \
-H "Authorization: token YOUR_TOKEN"Для разового переноса без автоматической синхронизации:
# На машине с доступом к интернету
git clone --mirror https://github.com/organization/repo.git
cd repo.git
git push --mirror ssh://git@gitea.internal:2222/team/repo.gitРазбор команд:
--mirror — клонирует все refs (ветки, теги, remote-tracking)push --mirror — отправляет все refs на удалённый серверmigrate_all.sh:
#!/bin/bash
GITEA_HOST="git.internal.company.ru"
GITEA_PORT="2222"
ORG_NAME="internal"
# Список репозиториев для миграции
REPOS=(
"https://github.com/org/backend-api"
"https://github.com/org/frontend-web"
"https://github.com/org/shared-lib"
)
for repo_url in "${REPOS[@]}"; do
repo_name=$(basename "$repo_url" .git)
echo "Миграция: $repo_name"
# Клонирование зеркала
git clone --mirror "$repo_url" "$repo_name.git"
cd "$repo_name.git"
# Создание репозитория в Gitea через API
curl -X POST "http://$GITEA_HOST/api/v1/org/$ORG_NAME/repos" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$repo_name\",
\"private\": true,
\"auto_init\": false
}"
# Push зеркала
git push --mirror "ssh://git@$GITEA_HOST:$GITEA_PORT/$ORG_NAME/$repo_name.git"
# Очистка
cd ..
rm -rf "$repo_name.git"
echo "✓ $repo_name завершено"
donedocker-compose.registry.yml:
version: '3.8'
services:
registry:
image: registry:2
container_name: docker-registry
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- ./registry-data:/var/lib/registry
- ./registry-config:/etc/docker/registry
environment:
- REGISTRY_STORAGE_DELETE_ENABLED=true
networks:
- internal-net
registry-ui:
image: joxit/docker-registry-ui:latest
container_name: registry-ui
restart: unless-stopped
ports:
- "8080:80"
environment:
- DOCKER_REGISTRY_URL=http://registry:5000
- SHOW_CONTENT_DIGEST=true
- NGINX_PROXY_PASS_URL=http://registry:5000
depends_on:
- registry
networks:
- internal-net
networks:
internal-net:
driver: bridgeНа промежуточном сервере (с доступом к интернету):
# Список необходимых образов
IMAGES=(
"gitea/gitea:latest"
"postgres:15-alpine"
"nginx:alpine"
"node:20-alpine"
"python:3.11-slim"
)
# Скачивание и сохранение
for image in "${IMAGES[@]}"; do
docker pull "$image"
safe_name=$(echo "$image" | sed 's/\//_/g' | sed 's/:/-/g')
docker save -o "./images/${safe_name}.tar" "$image"
doneПеренос в закрытый контур:
# Через съёмный носитель или scp
scp ./images/*.tar user@internal-server:/opt/registry/import/Загрузка в локальный Registry:
cd /opt/registry/import
for tar_file in *.tar; do
docker load -i "$tar_file"
# Получение имени образа из метаданных
image_name=$(docker inspect --format='{{.RepoTags}}' "$(docker images -q | head -1)" | tr -d '[]' | cut -d',' -f1)
# Тегирование для локального registry
docker tag "$image_name" "localhost:5000/$image_name"
# Push в registry
docker push "localhost:5000/$image_name"
echo "✓ Загружено: $image_name"
done/etc/docker/daemon.json на всех узлах:
{
"insecure-registries": ["registry.internal:5000"],
"registry-mirrors": ["http://registry.internal:5000"]
}Перезапуск Docker:
sudo systemctl restart dockerNexus — универсальное хранилище артефактов с поддержкой npm, PyPI, Maven, Docker.
docker-compose.nexus.yml:
version: '3.8'
services:
nexus:
image: sonatype/nexus3:latest
container_name: nexus
restart: unless-stopped
ports:
- "8081:8081"
volumes:
- ./nexus-data:/nexus-data
environment:
- INSTALL4J_ADD_VM_PARAMS=-Xms2g -Xmx4g -XX:MaxDirectMemorySize=2g
networks:
- internal-net
networks:
internal-net:
driver: bridgeПервоначальная настройка Nexus:
docker exec nexus cat /nexus-data/admin.passwordВход: http://nexus:8081 (admin / пароль из файла)
Создание репозиториев:
.npmrc в проекте:
registry=http://nexus.internal:8081/repository/npm-group/
Глобальная настройка:
npm config set registry http://nexus.internal:8081/repository/npm-group/На промежуточном сервере:
# Использование verdaccio как кэша
docker run -d --name verdaccio -p 4873:4873 verdaccio/verdaccio
# Настройка .npmrc для скачивания через verdaccio
echo "registry=http://localhost:4873/" > ~/.npmrc
# Установка пакетов (кэширование)
npm install lodash express react --prefix /tmp/cache
# Экспорт кэша
cp -r /usr/local/share/.cache/verdaccio /opt/npm-cache/Перенос в закрытый контур:
# Копирование кэша
scp -r /opt/npm-cache user@internal:/opt/nexus/import/
# Импорт в Nexus (через API или UI)Nexus PyPI proxy:
Настройка pip:
~/.config/pip/pip.conf:
[global]
index-url = http://nexus.internal:8081/repository/pypi-group/simple
trusted-host = nexus.internalСинхронизация пакетов:
# На промежуточном сервере
pip download -d /opt/pypi-packages -r requirements.txt
# Перенос
scp /opt/pypi-packages/*.whl user@internal:/opt/nexus/import/pypi/
# Загрузка в Nexus через API
for pkg in /opt/nexus/import/pypi/*.whl; do
curl -u admin:password --upload-file "$pkg" \
"http://nexus.internal:8081/repository/pypi-hosted/"
doneПодготовка архива на внешней машине:
# Создание полного бэкапа
mkdir /tmp/gitea-offline
# Docker образы
docker save gitea/gitea:latest > /tmp/gitea-offline/gitea.tar
docker save postgres:15-alpine > /tmp/gitea-offline/postgres.tar
# Бэкап Gitea
docker exec gitea gitea dump -F /tmp/gitea-offline/gitea-dump.zip
# Конфигурация
cp /opt/gitea/docker-compose.yml /tmp/gitea-offline/
cp /opt/gitea/app.ini /tmp/gitea-offline/
# Архивация
tar -czf gitea-offline-$(date +%Y%m%d).tar.gz -C /tmp/gitea-offline .Восстановление в закрытом контуре:
# Распаковка
tar -xzf gitea-offline-20260319.tar.gz
# Загрузка образов
docker load -i gitea.tar
docker load -i postgres.tar
# Развёртывание
docker compose up -d
# Восстановление из бэкапа
docker exec gitea gitea restore --from /data/gitea-dump.zipПроцесс обновления:
1. Тестирование новой версии на изолированном стенде
2. Создание полного архива (образы + бэкап)
3. Перенос в закрытый контур
4. Бэкап текущей системы
5. Развёртывание новой версии
6. Проверка работоспособности
7. Откат при проблемах
Скрипт обновления:
update-airgap.sh:
#!/bin/bash
set -e
VERSION="1.21.5"
BACKUP_DIR="/opt/gitea/backups/$(date +%Y%m%d_%H%M%S)"
echo "=== Обновление Gitea до версии $VERSION ==="
# 1. Бэкап
echo "[1/4] Создание бэкапа..."
mkdir -p "$BACKUP_DIR"
docker exec gitea gitea dump -F "$BACKUP_DIR/gitea-dump.zip"
docker compose cp db:/var/lib/postgresql/data "$BACKUP_DIR/postgres-data"
# 2. Загрузка нового образа
echo "[2/4] Загрузка образа..."
docker load -i "gitea-$VERSION.tar"
# 3. Обновление docker-compose.yml
echo "[3/4] Обновление конфигурации..."
sed -i "s/gitea\/gitea:.*/gitea\/gitea:$VERSION/" docker-compose.yml
# 4. Перезапуск
echo "[4/4] Перезапуск сервисов..."
docker compose down
docker compose up -d
# Проверка
sleep 30
curl -f http://localhost:3000/api/health || {
echo "❌ Ошибка запуска! Выполняется откат..."
# Откат (удаление нового образа, восстановление бэкапа)
exit 1
}
echo "✓ Обновление завершено успешно"# Генерация хэша на внешней машине
sha256sum gitea.tar > gitea.tar.sha256
# Проверка в закрытом контуре
sha256sum -c gitea.tar.sha256| Метрика | Целевое значение | Как измерить |
|---|---|---|
| Актуальность зеркал | Не старше 7 дней | Gitea API: /repos/{owner}/{repo} |
| Доступность образов | 100% критических образов | docker images + сверка со списком |
| Время синхронизации | < 4 часов | Логирование скриптов |
| Целостность данных | 100% совпадение хэшей | SHA256 проверка |
git clone --mirrorСледующий шаг: Безопасность и комплаенс (152-ФЗ, GDPR)
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.