fix: deduplicate sidecars/systemd, block system-critical stops, filter runtime paths

This commit is contained in:
2026-05-22 22:56:00 +04:00
parent 186ab2325f
commit 76b06b157a
4 changed files with 45 additions and 60 deletions

View File

@@ -137,55 +137,6 @@ def reset_state(mode=None):
save_state(state) save_state(state)
def mark_completed(step):
state = load_state()
if step not in state["completed_steps"]:
state["completed_steps"].append(step)
save_state(state)
def is_completed(step):
return step in load_state().get("completed_steps", [])
def set_error(step, stdout, stderr, suggestion=""):
state = load_state()
state["paused"] = True
state["last_error"] = {
"step": step,
"timestamp": datetime.now().isoformat(),
"stdout": stdout,
"stderr": stderr,
"suggestion": suggestion,
}
save_state(state)
def clear_error():
state = load_state()
state["paused"] = False
state["last_error"] = None
save_state(state)
def reset_state(mode=None):
"""Сбрасывает состояние для нового запуска (source, target или полностью)."""
global _LAST_STATE
# Загружаем текущее, чтобы не потерять пути к архивам если пользователь хочет
# Но completed_steps и stage сбрасываем
state = load_state()
state["stage"] = "INIT"
state["mode"] = mode
state["completed_steps"] = []
state["interrupted_at"] = None
state["resumable_hint"] = None
state["last_error"] = None
state["paused"] = False
state["updated_at"] = datetime.now().isoformat()
_LAST_STATE = state
save_state(state)
def set_interrupted(): def set_interrupted():
"""Помечает, что скрипт был прерван (Ctrl+C, SIGTERM).""" """Помечает, что скрипт был прерван (Ctrl+C, SIGTERM)."""
state = load_state() state = load_state()

View File

@@ -72,10 +72,12 @@ def find_listeners_on_host(port):
def find_sidecar_processes(cid, container_ports): def find_sidecar_processes(cid, container_ports):
""" """
Ищет sidecar-процессы на хосте, к которым контейнер стучится через loopback. Ищет sidecar-процессы на хосте, к которым контейнер стучится через loopback.
Дедуплицирует по (host_process, container_port_target).
""" """
info("Ищем loopback-соединения контейнера (sidecar / proxy / WARP) ...") info("Ищем loopback-соединения контейнера (sidecar / proxy / WARP) ...")
conns = get_container_host_connections(cid) conns = get_container_host_connections(cid)
sidecars = [] sidecars = []
seen = set()
for c in conns: for c in conns:
local = c.get("local", "") local = c.get("local", "")
@@ -87,11 +89,16 @@ def find_sidecar_processes(cid, container_ports):
listeners = find_listeners_on_host(port) listeners = find_listeners_on_host(port)
if listeners: if listeners:
for l in listeners: for l in listeners:
info(f" Контейнер подключается к {local} → процесс на хосте: {l.get('process', '?')} (pid={l.get('pid', '?')})") proc = l.get("process", "?")
key = (proc, port)
if key in seen:
continue
seen.add(key)
info(f" Контейнер подключается к {local} → процесс на хосте: {proc} (pid={l.get('pid', '?')})")
sidecars.append({ sidecars.append({
"type": "loopback_listener", "type": "loopback_listener",
"container_port_target": port, "container_port_target": port,
"host_process": l.get("process"), "host_process": proc,
"host_pid": l.get("pid"), "host_pid": l.get("pid"),
"method": "ss_lsof", "method": "ss_lsof",
}) })
@@ -176,6 +183,7 @@ def gather_host_network_info():
def find_systemd_units_related(procs): def find_systemd_units_related(procs):
""" """
Ищет systemd unit-файлы для процессов (sidecar и других). Ищет systemd unit-файлы для процессов (sidecar и других).
Дедуплицирует по имени unit.
""" """
units = [] units = []
seen = set() seen = set()

View File

@@ -205,8 +205,9 @@ def discover_nginx(service_ports, service_domain_hints):
def get_nginx_systemd_unit(): def get_nginx_systemd_unit():
"""Ищет unit-файл nginx""" """Ищет unit-файл nginx, возвращает уникальные (deduplicated по пути)"""
units = [] units = []
seen_paths = set()
for unit in ("nginx.service", "nginx"): for unit in ("nginx.service", "nginx"):
out = run(f"systemctl is-active {unit}", check=False) out = run(f"systemctl is-active {unit}", check=False)
if out.returncode == 0 or out.stdout.strip() in ("active", "inactive"): if out.returncode == 0 or out.stdout.strip() in ("active", "inactive"):
@@ -214,7 +215,8 @@ def get_nginx_systemd_unit():
try: try:
uout = run(f"systemctl show {unit} -p FragmentPath --value", check=False) uout = run(f"systemctl show {unit} -p FragmentPath --value", check=False)
path = uout.stdout.strip() path = uout.stdout.strip()
if path: if path and path not in seen_paths:
seen_paths.add(path)
units.append({"unit": unit, "path": path}) units.append({"unit": unit, "path": path})
except Exception: except Exception:
pass pass

View File

@@ -20,6 +20,13 @@ from manifest.manifest import build_manifest, save_manifest, review_manifest
_ARCHIVE_DIR = "/tmp/docker-migrate-archives" _ARCHIVE_DIR = "/tmp/docker-migrate-archives"
# Сервисы, которые НЕЛЬЗЯ останавливать/включать в архив — они управляют средой выполнения
SYSTEM_CRITICAL = {
"docker.service", "containerd.service", "docker.socket",
"systemd-journald.service", "systemd-networkd.service",
"systemd-timesyncd.service", "systemd-resolved.service",
"systemd-logind.service", "ssh.service", "sshd.service",
}
def run_source_mode(): def run_source_mode():
state.reset_state(mode="source") state.reset_state(mode="source")
@@ -224,9 +231,16 @@ def do_pack():
details = s.get("details", {}) details = s.get("details", {})
for f in details.get("files", []): for f in details.get("files", []):
if os.path.isfile(f): if os.path.isfile(f):
# Пропускаем runtime/system пути — они не относятся к сервису
if any(f.startswith(p) for p in (
"/proc/", "/sys/", "/dev/",
"/var/lib/containerd/", "/var/lib/docker/",
"/run/containerd/", "/run/docker/",
)):
continue
files_to_pack.add(f) files_to_pack.add(f)
unit = details.get("unit") unit = details.get("unit")
if unit: if unit and unit not in SYSTEM_CRITICAL:
try: try:
uout = run(f"systemctl show {unit} -p FragmentPath --value", check=False) uout = run(f"systemctl show {unit} -p FragmentPath --value", check=False)
path = uout.stdout.strip() path = uout.stdout.strip()
@@ -237,6 +251,9 @@ def do_pack():
# systemd units # systemd units
for u in manifest.get("systemd_units", []): for u in manifest.get("systemd_units", []):
uname = u.get("name")
if uname and uname in SYSTEM_CRITICAL:
continue
p = u.get("path") p = u.get("path")
if p and os.path.isfile(p): if p and os.path.isfile(p):
files_to_pack.add(p) files_to_pack.add(p)
@@ -290,14 +307,21 @@ def do_stop_service():
success(f"Контейнер {cid[:12]} остановлен") success(f"Контейнер {cid[:12]} остановлен")
except Exception as e: except Exception as e:
warn(f"Не удалось остановить контейнер: {e}") warn(f"Не удалось остановить контейнер: {e}")
# Дедупликация + фильтр system-critical
stopped = set()
for u in manifest.get("systemd_units", []): for u in manifest.get("systemd_units", []):
uname = u.get("name") uname = u.get("name")
if uname: if not uname or uname in stopped:
try: continue
run(f"systemctl stop {uname}", check=False) stopped.add(uname)
info(f"Остановлен systemd unit: {uname}") if uname in SYSTEM_CRITICAL:
except Exception: warn(f"Пропуск system-critical unit: {uname}")
pass continue
try:
run(f"systemctl stop {uname}", check=False)
info(f"Остановлен systemd unit: {uname}")
except Exception:
pass
else: else:
info("Пропуск остановки (сервис продолжает работать)") info("Пропуск остановки (сервис продолжает работать)")