fix: deduplicate sidecars/systemd, block system-critical stops, filter runtime paths
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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("Пропуск остановки (сервис продолжает работать)")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user