Сетевые инструменты в DE-workflow
Урок-сборник. Берём весь арсенал из предыдущих уроков (curl, wget, ssh, rsync, ss, nc, dig) и применяем его к типичным задачам DE-инженера. Готовые рецепты, которые можно копировать в работу.
Сценарий 1: Проверка работоспособности всего стека
DE-команда хочет каждое утро видеть «зелёный/красный» status каждого сервиса. Это первый bash-скрипт, который пишет Junior DE.
#!/bin/bash
# health-check.sh — утренний health check продакшен-стека
set -uo pipefail
# Сервисы для проверки
declare -A SERVICES=(
["Airflow UI"]="https://airflow.company.com/health"
["Airflow API"]="https://airflow.company.com/api/v1/health"
["Postgres metadata"]="pg.internal.company.com:5432"
["S3 (data lake)"]="data-lake.s3.amazonaws.com:443"
["Kafka brokers"]="kafka-1.internal.company.com:9092"
["Grafana"]="https://grafana.company.com/api/health"
)
check_http() {
local url="$1"
local code
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url")
[ "$code" = "200" ]
}
check_tcp() {
local host_port="$1"
local host="${host_port%:*}"
local port="${host_port##*:}"
nc -zv -w 5 "$host" "$port" 2>/dev/null
}
echo "Health check at $(date -Iseconds)"
echo "----------------------------------"
FAIL_COUNT=0
for name in "${!SERVICES[@]}"; do
target="${SERVICES[$name]}"
if [[ "$target" == http* ]]; then
check_http "$target" && echo "OK : $name" || { echo "FAIL: $name ($target)"; ((FAIL_COUNT++)); }
else
check_tcp "$target" && echo "OK : $name" || { echo "FAIL: $name ($target)"; ((FAIL_COUNT++)); }
fi
done
echo "----------------------------------"
echo "Total failures: $FAIL_COUNT"
# Notify в Slack при ошибках
if [ "$FAIL_COUNT" -gt 0 ]; then
curl -s -X POST -H 'Content-type: application/json' \
--data "{\"text\":\":fire: Health check $FAIL_COUNT failures, see logs.\"}" \
"$SLACK_WEBHOOK_URL"
fi
exit $FAIL_COUNT
Cron каждое утро в 9:00:
0 9 * * * /opt/scripts/health-check.sh > /var/log/health-$(date +\%Y\%m\%d).log 2>&1
Диагностика сети — ping, traceroute, netstat, lsof
Сценарий 2: Daily data download с rsync resume
Партнёр-компания раз в день кладёт CSV-дампы на FTP. Сеть прерывистая, файлы большие (5-50GB). Нужно качать reliable.
#!/bin/bash
# fetch-partner-data.sh — ежедневный sync data dumps
set -euo pipefail
DATE=$(date +%Y-%m-%d)
SRC="[email protected]:/exports/$DATE/"
DST="/data/imports/partner/$DATE/"
LOG="/var/log/partner-import-$DATE.log"
mkdir -p "$DST"
# rsync с resume и retry
for attempt in 1 2 3; do
echo "Attempt $attempt at $(date -Iseconds)" >> "$LOG"
if rsync -avhP --partial \
--timeout=120 \
"$SRC" "$DST" >> "$LOG" 2>&1; then
echo "Success" >> "$LOG"
break
fi
echo "Failed, retry in 60s" >> "$LOG"
sleep 60
done
# Проверить, что файлы реально скачались
EXPECTED=$(curl -s "https://api.partner.com/exports/$DATE/manifest.json" | jq '.file_count')
ACTUAL=$(ls "$DST" | wc -l)
if [ "$ACTUAL" -ne "$EXPECTED" ]; then
echo "WARNING: expected $EXPECTED files, got $ACTUAL" | tee -a "$LOG"
exit 1
fi
# Уведомить о успехе
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\":white_check_mark: Partner data $DATE imported: $ACTUAL files\"}" \
"$SLACK_WEBHOOK_URL"
Ключевые техники:
rsync -avhP --partial— resume прерванных передач.--timeout=120— таймаут per file.- Цикл retry — если первая попытка fail, пробуем ещё.
- Проверка count через manifest API партнёра.
- Notify в Slack об успехе.
Сценарий 3: Бэкап data lake на NAS с rotation
Daily incremental backup с использованием --link-dest для эффективности места (hardlinks).
#!/bin/bash
# backup-lake.sh — nightly incremental backup
set -euo pipefail
DATE=$(date +%Y-%m-%d)
SRC="/data/lake/"
BACKUP_HOST="[email protected]"
BACKUP_BASE="/backups/lake"
DST="$BACKUP_BASE/$DATE"
LATEST="$BACKUP_BASE/latest"
# Incremental backup: hardlink файлов которые не изменились
rsync -avh -P \
--link-dest="$LATEST" \
--exclude='*.tmp' \
--exclude='_temp/' \
--exclude='__staging__/' \
"$SRC" "$BACKUP_HOST:$DST/"
# Обновить symlink latest
ssh "$BACKUP_HOST" "ln -sfn '$DST' '$LATEST'"
# Rotation: оставить последние 30 дней + по 1 на каждый месяц для последних 12 месяцев
ssh "$BACKUP_HOST" "find $BACKUP_BASE -maxdepth 1 -mtime +30 -type d -name '????-??-??' | head -n -12 | xargs rm -rf"
echo "Backup $DATE complete: $(du -sh $DST | awk '{print $1}') on remote"
--link-dest — каждый день вы видите полную копию, но на диске занимает только размер изменений. 30 daily backups при изменениях 1GB/день — занимают ~30GB вместо 30 × full size.
Сценарий 4: SSH tunnel для доступа к UI
Airflow UI на production — не выставлен в интернет. Нужно зайти из дому.
# В ~/.ssh/config
Host airflow-tunnel
HostName airflow.internal.company.com
User ec2-user
IdentityFile ~/.ssh/work_key
ProxyJump bastion.company.com
LocalForward 8080 localhost:8080
LocalForward 5432 localhost:5432
ServerAliveInterval 60
ExitOnForwardFailure yes
# Подключение
ssh airflow-tunnel
# Появится shell на airflow-сервере
# В браузере: http://localhost:8080 — Airflow UI
# psql -h localhost -p 5432 — Postgres metadata
Чтобы tunnel жил без интерактивного shell — -N:
# В фоне, без shell
ssh -N -f airflow-tunnel
# -N = no remote command, -f = fork в background
# Killнуть когда не нужно
pkill -f "ssh -N -f airflow-tunnel"
В системных скриптах — лучше через autossh, который автоматически переподключается:
# Установить autossh
sudo apt install autossh
# Tunnel который сам переподключается
autossh -M 0 -N -f airflow-tunnel
Сценарий 5: Скачать data dump из internal API
#!/bin/bash
# download-dump.sh — скачать вчерашний дамп
set -euo pipefail
API_BASE="https://api.internal.company.com"
TOKEN="${API_TOKEN:?Set API_TOKEN env var}"
YESTERDAY=$(date -d yesterday +%Y-%m-%d)
# 1. Получить presigned URL дампа
PRESIGNED=$(curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"date\":\"$YESTERDAY\"}" \
"$API_BASE/exports/dump/presigned" \
| jq -r '.url')
if [ -z "$PRESIGNED" ] || [ "$PRESIGNED" = "null" ]; then
echo "Failed to get presigned URL" >&2
exit 1
fi
# 2. Скачать через wget с retry
wget --tries=3 --timeout=300 \
-O "/data/dumps/$YESTERDAY.tar.zst" \
"$PRESIGNED"
# 3. Проверить checksum
EXPECTED=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$API_BASE/exports/dump/$YESTERDAY/checksum" \
| jq -r '.sha256')
ACTUAL=$(sha256sum "/data/dumps/$YESTERDAY.tar.zst" | awk '{print $1}')
if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "Checksum mismatch! Expected $EXPECTED, got $ACTUAL" >&2
rm -f "/data/dumps/$YESTERDAY.tar.zst"
exit 1
fi
echo "Downloaded and verified: $YESTERDAY.tar.zst"
Сценарий 6: Deploy code на сервер через rsync + SSH
#!/bin/bash
# deploy.sh — деплой ETL-проекта
set -euo pipefail
LOCAL="$(pwd)/"
REMOTE="airflow:/opt/etl/"
# Preview
echo "=== Preview ==="
rsync -avhn --delete \
--exclude='.git' \
--exclude='__pycache__' \
--exclude='.env' \
--exclude='*.pyc' \
--exclude='node_modules' \
--exclude='.venv' \
"$LOCAL" "$REMOTE"
read -p "Continue? (y/n) " yn
[ "$yn" != "y" ] && exit 1
# Real
echo "=== Deploying ==="
rsync -avh --delete \
--exclude='.git' \
--exclude='__pycache__' \
--exclude='.env' \
--exclude='*.pyc' \
--exclude='node_modules' \
--exclude='.venv' \
"$LOCAL" "$REMOTE"
# Restart scheduler
echo "=== Restarting scheduler ==="
ssh airflow "sudo systemctl restart airflow-scheduler"
ssh airflow "sudo systemctl status airflow-scheduler --no-pager"
Сценарий 7: sshfs для удалённой работы с файлами
Иногда удобно работать с remote директорией как локальной — открывать в VS Code, копировать через Finder.
# Установить
sudo apt install sshfs # Ubuntu/Debian
brew install --cask macfuse && brew install gromgit/fuse/sshfs-mac # macOS
# Смонтировать
mkdir -p ~/mounts/airflow
sshfs airflow:/home/ec2-user/etl ~/mounts/airflow
# Работать как с локальной
cd ~/mounts/airflow
ls
vim dag.py # сохранится прямо на сервер
code . # открыть в VS Code
# Размонтировать
fusermount -u ~/mounts/airflow # Linux
umount ~/mounts/airflow # macOS
Минусы sshfs:
- Медленнее нативной FS (особенно
lsбольших директорий). - Может «зависнуть» при потере сети.
- Не для тяжёлой работы (build, индексирование).
Для production-разработки лучше: ssh + tmux + vim/neovim, или VS Code Remote SSH (нативная интеграция).
Сценарий 8: Проверка cron + SSH работают
#!/bin/bash
# self-test.sh — проверить что наша автоматизация жива
set -euo pipefail
# 1. SSH access к ключевым серверам
HOSTS=("airflow" "postgres-prod" "spark-master")
for h in "${HOSTS[@]}"; do
if ssh -o ConnectTimeout=5 -o BatchMode=yes "$h" "echo ok" > /dev/null 2>&1; then
echo "SSH $h: OK"
else
echo "SSH $h: FAIL"
fi
done
# 2. Cron сам себя запускает
CRON_HEARTBEAT_FILE="/var/run/cron-heartbeat"
# (этот файл должен обновляться каждые 5 минут другим cron-jobом)
if [ -f "$CRON_HEARTBEAT_FILE" ]; then
AGE=$(( $(date +%s) - $(stat -c %Y "$CRON_HEARTBEAT_FILE") ))
if [ "$AGE" -gt 600 ]; then
echo "Cron heartbeat stale: ${AGE}s old"
else
echo "Cron heartbeat OK"
fi
else
echo "Cron heartbeat file missing!"
fi
-o BatchMode=yes — не спрашивать пароль интерактивно (если ключ не подходит — сразу fail).
Цепочка инструментов для типичных задач
Какую задачу каким инструментом
Попробуй сам
# 1. Простой health-check для пары публичных сайтов
for url in https://github.com https://google.com https://nonexistent12345.com; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>&1)
echo "$url: $CODE"
done
# 2. Rsync local backup (с --link-dest для incremental)
mkdir -p /tmp/src /tmp/backup/latest
echo "data v1" > /tmp/src/file1.txt
echo "data v1" > /tmp/src/file2.txt
# День 1
rsync -avhP /tmp/src/ /tmp/backup/$(date +%F)/
ln -sfn $(date +%F) /tmp/backup/latest
# Изменить файл
echo "data v2" > /tmp/src/file1.txt
# День 2 (через минуту) — будет hardlink на file2.txt (не изменился)
sleep 60
NEW_DATE="$(date +%F)-2"
rsync -avhP --link-dest=/tmp/backup/latest /tmp/src/ /tmp/backup/$NEW_DATE/
# Проверить — file2 имеет одинаковый inode в двух бэкапах
ls -li /tmp/backup/*/file2.txt
# Будет один и тот же inode — hardlink
# 3. SSH multi-host check
hosts=(localhost) # или твои реальные сервера
for h in "${hosts[@]}"; do
ssh -o ConnectTimeout=2 -o BatchMode=yes "$h" "uptime" 2>&1 | head -1
done
# 4. Combined: download + checksum
URL="https://example.com/data.csv"
curl -sLO "$URL"
sha256sum data.csv
# 5. Скачать кучу файлов параллельно
URLS=("https://example.com/2026-04.csv"
"https://example.com/2026-05.csv"
"https://example.com/2026-06.csv")
for url in "${URLS[@]}"; do
wget -q "$url" &
done
wait
echo "All done"
Cross-link: предыдущий урок 04 — debugging. Модуль 12 — архивирование, часто комбинируется с rsync для бэкапов. Модуль 15 — cron для планирования этих скриптов. Модуль 17 — production-bash для надёжных скриптов с error handling.