13.5. Глубокая диагностика
Цели
После этого материала вы сможете:
- читать логи daemon и отличать его проблемы от проблем приложения;
- войти в namespaces работающего container без изменения образа;
- провести диагностику container'а, в образе которого нет shell;
- применить
straceиtcpdump, зная требуемые привилегии; - найти файловую систему container'а на host и прочитать нужный файл;
- выбрать инструмент под задачу, а не перебирать все подряд.
Предварительные знания
- 2.2. Namespaces;
- 10.3. Отладка в container — приёмы разработки;
- 13.2. Inspect, events, stats — PID на host;
- 8.6. Диагностика сети.
Ключевые термины
| Термин | Объяснение |
|---|---|
nsenter | Вход в namespaces процесса |
MergedDir | Каталог на host, где видна файловая система container'а |
distroless | Образ без shell и утилит |
sidecar | Container, подключённый к namespaces другого |
journalctl | Чтение журнала systemd |
Теория
Логи daemon
Проблемы делятся на две категории, и путать их дорого:
| Симптом | Где искать |
|---|---|
| Приложение отвечает ошибкой | docker logs |
| Container не запускается вовсе | Логи daemon |
| Образ не скачивается | Логи daemon |
| Volume не монтируется | Логи daemon |
| Сеть не создаётся | Логи daemon |
sudo journalctl -u docker.service --since '10 min ago'
sudo journalctl -u docker.service -f
sudo journalctl -u docker.service -p err --since today
Признак того, что смотреть нужно именно сюда: docker run завершается ошибкой, а docker logs для этого container'а пуст или container вообще не создан.
Четыре способа попасть внутрь
| Способ | Требует shell в образе | Требует root на host | Что даёт |
|---|---|---|---|
docker exec | Да | Нет | Обычный вход |
Sidecar через --pid/--network | Нет | Нет | Инструменты соседнего образа |
nsenter | Нет | Да | Полный доступ ко всем namespaces |
Чтение MergedDir | Нет | Да | Файлы без входа вовсе |
Порядок предпочтения: сначала docker exec, при отсутствии shell — sidecar, при необходимости системных инструментов — nsenter.
Sidecar: инструменты без изменения образа
docker run --rm -it \
--network container:ЦЕЛЬ \
--pid container:ЦЕЛЬ \
--cap-add SYS_PTRACE \
nicolaka/netshoot sh
| Флаг | Что разделяет |
|---|---|
--network container:X | Сетевой namespace: те же интерфейсы, порты, DNS |
--pid container:X | PID namespace: видны процессы цели |
--ipc container:X | Разделяемая память |
--cap-add SYS_PTRACE | Нужен для strace, py-spy, gdb |
Чего sidecar не даёт: файловую систему цели. Она в другом mount namespace. Файлы видны через /proc/PID/root/ — при наличии SYS_PTRACE и совпадении пользователя.
Это главное отличие от nsenter, который входит и в mount namespace тоже.
nsenter
PID=$(docker inspect ЦЕЛЬ --format '{{.State.Pid}}')
sudo nsenter -t "$PID" -m -u -i -n -p -- sh
| Флаг | Namespace |
|---|---|
-m | mount — файловая система container'а |
-u | UTS — имя хоста |
-i | IPC |
-n | network |
-p | PID |
Существенная тонкость: nsenter запускает программу с host, но в namespaces цели. При флаге -m файловая система становится файловой системой container'а — а значит, запускаемая программа должна существовать в container'е.
Практическое решение: входить без -m, если нужны инструменты host:
sudo nsenter -t "$PID" -n -- ss -tlnp
Здесь ss берётся с host, а сетевой стек — из container'а. Это самый полезный вариант для диагностики сети образов без утилит.
Диагностика образа без shell
Distroless-образы не содержат sh, ls, cat. docker exec в них невозможен.
Порядок действий:
| Задача | Решение |
|---|---|
| Посмотреть процессы | docker top или sidecar с --pid |
| Посмотреть сеть | nsenter -n с host или sidecar с --network |
| Прочитать файл | docker cp ЦЕЛЬ:/путь - или MergedDir |
| Посмотреть окружение | docker inspect |
| Посмотреть открытые дескрипторы | sudo ls /proc/PID/fd |
Отдельно стоит docker cp — он работает без shell в образе, потому что выполняется daemon'ом, а не внутри container'а. Это самый простой способ достать файл из distroless-образа.
Файловая система container'а на host
docker inspect ЦЕЛЬ --format '{{.GraphDriver.Data.MergedDir}}'
Путь вида /var/lib/docker/overlay2/<хеш>/merged — это корень файловой системы container'а, видимый с host.
| Каталог | Содержимое |
|---|---|
MergedDir | Итоговое представление: образ плюс изменения |
UpperDir | Только изменения, сделанные container'ом |
LowerDir | Слои образа |
UpperDir полезен отдельно: он показывает ровно то, что container записал (урок 3.2). Если приложение неожиданно пишет на диск — здесь видно, что именно.
Предупреждение: запись в эти каталоги напрямую не поддерживается и может нарушить состояние container'а. Читать — можно.
strace и tcpdump
| Инструмент | Требует | Даёт |
|---|---|---|
strace | SYS_PTRACE | Системные вызовы: где процесс завис, что не находит |
tcpdump | NET_ADMIN или NET_RAW | Трафик: что уходит и приходит |
py-spy | SYS_PTRACE | Стек Python-процесса без его остановки |
Все три запускают из sidecar, а не устанавливают в образ приложения (урок 12.4).
Привилегии выдают временно, только диагностическому container'у, и убирают после.
Наиболее частые применения strace:
strace -p PID -f -e trace=openat # что процесс пытается открыть
strace -p PID -f -e trace=network # сетевые вызовы
strace -p PID -f -c # сводка: где время
Первый вариант отвечает на вопрос «почему не находит файл», который иначе решается перебором.
Внутренний механизм
Почему docker exec требует shell, а docker cp — нет
docker exec запускает новый процесс внутри namespaces container'а. Программу для запуска ищет ядро в файловой системе container'а — если sh там нет, запускать нечего.
docker cp выполняется daemon'ом: он читает файловую систему container'а через свой mount namespace и копирует байты. Процесс внутри container'а при этом не создаётся.
Отсюда правило: всё, что делает daemon, работает в distroless; всё, что требует процесса внутри, — нет.
Почему sidecar не видит файлы цели
Флаги --network, --pid, --ipc разделяют соответствующие namespaces. Флага для mount namespace нет — Docker его не предоставляет.
Обходной путь через /proc/PID/root/ работает потому, что ядро предоставляет доступ к корню другого процесса через procfs. Требуется SYS_PTRACE и совпадение владельца.
Команды и примеры
Логи daemon: когда docker logs бесполезен
mkdir -p /tmp/deep && cd /tmp/deep
echo "═══ ошибка, не попадающая в docker logs ═══"
docker run --name broken -v /nonexistent/path:/data:ro alpine:3.21 true 2>&1 \
| head -3 | sed 's/^/ ошибка команды: /'
echo " docker logs для этого container'а:"
docker logs broken 2>&1 | head -2 | sed 's/^/ /' || echo " (container не создан)"
docker rm -f broken > /dev/null 2>&1
echo "═══ где искать такие ошибки ═══"
if sudo journalctl -u docker.service --since '2 min ago' --no-pager -p warning 2>/dev/null \
| tail -5 | grep -q .; then
sudo journalctl -u docker.service --since '2 min ago' --no-pager -p warning 2>/dev/null \
| tail -5 | sed 's/^/ /'
else
cat <<'TXT'
(journalctl требует прав root или systemd недоступен)
Команды для чтения логов daemon:
sudo journalctl -u docker.service --since '10 min ago'
sudo journalctl -u docker.service -f
sudo journalctl -u docker.service -p err --since today
TXT
fi
echo "═══ как различать ═══"
cat <<'TXT'
docker logs ПУСТ или container не создан → смотреть логи daemon
docker logs содержит трассировку Python → проблема в приложении
Типичные проблемы daemon:
не смонтирован volume, не скачался образ, конфликт портов,
нет места на диске, ошибка сетевого драйвера
TXT
Ожидаемый вывод:
═══ ошибка, не попадающая в docker logs ═══
ошибка команды: docker: Error response from daemon: error while creating mount source path
ошибка команды: '/nonexistent/path': mkdir /nonexistent: read-only file system
docker logs для этого container'а:
(container не создан)
═══ где искать такие ошибки ═══
(journalctl требует прав root или systemd недоступен)
Команды для чтения логов daemon:
sudo journalctl -u docker.service --since '10 min ago'
sudo journalctl -u docker.service -f
sudo journalctl -u docker.service -p err --since today
═══ как различать ═══
docker logs ПУСТ или container не создан → смотреть логи daemon
docker logs содержит трассировку Python → проблема в приложении
Типичные проблемы daemon:
не смонтирован volume, не скачался образ, конфликт портов,
нет места на диске, ошибка сетевого драйвера
Ключевой признак — container не создан вовсе. Искать его логи бессмысленно: их нет.
Sidecar: диагностика сети без утилит в образе
cd /tmp/deep
echo "═══ запускаем сервис на минимальном образе ═══"
cat > srv.py <<'PY'
import http.server, os, threading, time
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200); self.end_headers(); self.wfile.write(b"ok")
def log_message(self, *a): pass
print(f"слушаю 8000, pid={os.getpid()}", flush=True)
threading.Thread(
target=http.server.ThreadingHTTPServer(("0.0.0.0", 8000), H).serve_forever,
daemon=True).start()
while True: time.sleep(1)
PY
docker run -d --name svc -e PYTHONUNBUFFERED=1 \
-v "$PWD/srv.py:/s.py:ro" python:3.13-slim python /s.py > /dev/null
sleep 3
echo "═══ в образе нет сетевых утилит ═══"
docker exec svc sh -c 'command -v ss || command -v netstat || echo " ss и netstat отсутствуют"' \
2>/dev/null | sed 's/^/ /'
echo "═══ sidecar с общим сетевым namespace ═══"
docker run --rm --network container:svc python:3.13-slim python -c "
import socket, json
# Тот же сетевой стек, что у цели
info = {}
s = socket.socket()
try:
s.connect(('127.0.0.1', 8000))
info['порт 8000 на localhost'] = 'открыт'
except OSError as e:
info['порт 8000 на localhost'] = f'закрыт: {e.strerror}'
finally:
s.close()
info['имя хоста'] = socket.gethostname()
try:
info['разрешение svc'] = socket.gethostbyname('svc')
except OSError:
info['разрешение svc'] = 'не разрешается'
for k, v in info.items():
print(f' {k:<26} {v}')
" 2>/dev/null
echo "═══ почему это работает ═══"
cat <<'TXT'
--network container:svc помещает sidecar в ТОТ ЖЕ сетевой
namespace. Его localhost — это localhost цели, его интерфейсы —
интерфейсы цели.
Значит, любой сетевой инструмент из образа sidecar работает
так, как если бы он был установлен в целевой образ.
Образ с полным набором утилит:
docker run --rm -it --network container:svc nicolaka/netshoot
(внутри: ss, tcpdump, dig, curl, ping, iperf, nmap)
TXT
Ожидаемый вывод:
═══ запускаем сервис на минимальном образе ═══
═══ в образе нет сетевых утилит ═══
ss и netstat отсутствуют
═══ sidecar с общим сетевым namespace ═══
порт 8000 на localhost открыт
имя хоста 3f8a2b1c4d5e
разрешение svc не разрешается
═══ почему это работает ═══
--network container:svc помещает sidecar в ТОТ ЖЕ сетевой
namespace. Его localhost — это localhost цели, его интерфейсы —
интерфейсы цели.
Значит, любой сетевой инструмент из образа sidecar работает
так, как если бы он был установлен в целевой образ.
Образ с полным набором утилит:
docker run --rm -it --network container:svc nicolaka/netshoot
(внутри: ss, tcpdump, dig, curl, ping, iperf, nmap)
«Имя хоста» совпадает с идентификатором целевого container'а — подтверждение того, что namespace действительно общий.
«Разрешение svc: не разрешается» ожидаемо: в сети bridge по умолчанию встроенный DNS не работает по именам (урок 8.4).
nsenter: инструменты host в namespace container'а
cd /tmp/deep
pid="$(docker inspect svc --format '{{.State.Pid}}')"
echo "═══ PID цели на host: $pid ═══"
echo "═══ сетевой namespace цели, утилита с host ═══"
if sudo test -d "/proc/$pid/ns" 2>/dev/null; then
sudo nsenter -t "$pid" -n -- ss -tlnp 2>/dev/null | head -5 | sed 's/^/ /' \
|| sudo nsenter -t "$pid" -n -- netstat -tlnp 2>/dev/null | head -5 | sed 's/^/ /' \
|| echo " ss и netstat отсутствуют и на host"
else
cat <<'TXT'
(nsenter требует прав root)
При наличии доступа:
PID=$(docker inspect svc --format '{{.State.Pid}}')
sudo nsenter -t "$PID" -n -- ss -tlnp
Вывод был бы:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 0.0.0.0:8000 0.0.0.0:* users:(("python3",pid=1,fd=3))
Ключевое: ss берётся С HOST, сетевой стек — ИЗ CONTAINER'а.
Устанавливать что-либо в образ не нужно.
TXT
fi
echo "═══ различие флагов ═══"
cat <<'TXT'
БЕЗ -m (только сетевой namespace):
sudo nsenter -t PID -n -- ss -tlnp
→ программа берётся с host, сеть из container'а
→ работает для образов без утилит
С -m (включая mount namespace):
sudo nsenter -t PID -m -n -- sh
→ файловая система становится файловой системой container'а
→ sh должен существовать В CONTAINER'е; в distroless не сработает
Практическое правило: для диагностики сети используйте
nsenter БЕЗ -m — так доступны утилиты host.
TXT
Ожидаемый вывод:
═══ PID цели на host: 52841 ═══
═══ сетевой namespace цели, утилита с host ═══
(nsenter требует прав root)
При наличии доступа:
PID=$(docker inspect svc --format '{{.State.Pid}}')
sudo nsenter -t "$PID" -n -- ss -tlnp
Вывод был бы:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 0.0.0.0:8000 0.0.0.0:* users:(("python3",pid=1,fd=3))
Ключевое: ss берётся С HOST, сетевой стек — ИЗ CONTAINER'а.
Устанавливать что-либо в образ не нужно.
═══ различие флагов ═══
БЕЗ -m (только сетевой namespace):
sudo nsenter -t PID -n -- ss -tlnp
→ программа берётся с host, сеть из container'а
→ работает для образов без утилит
С -m (включая mount namespace):
sudo nsenter -t PID -m -n -- sh
→ файловая система становится файловой системой container'а
→ sh должен существовать В CONTAINER'е; в distroless не сработает
Практическое правило: для диагностики сети используйте
nsenter БЕЗ -m — так доступны утилиты host.
TXT
Различие флагов — то, на чём чаще всего спотыкаются. nsenter -m в distroless-образ не войдёт: там нет sh, который он пытается запустить.
Container без shell
cd /tmp/deep
echo "═══ собираем образ без shell ═══"
cat > Dockerfile.noshell <<'EOF'
# syntax=docker/dockerfile:1
FROM python:3.13-slim AS build
COPY srv.py /srv.py
FROM gcr.io/distroless/python3-debian12
COPY --from=build /srv.py /srv.py
ENV PYTHONUNBUFFERED=1
CMD ["/srv.py"]
EOF
if docker build -f Dockerfile.noshell -t noshell:1 . > build.log 2>&1; then
docker run -d --name ns-svc noshell:1 > /dev/null 2>&1
sleep 3
built=1
else
echo " (образ distroless недоступен — используем эквивалент)"
docker run -d --name ns-svc -e PYTHONUNBUFFERED=1 \
-v "$PWD/srv.py:/srv.py:ro" --entrypoint python \
python:3.13-slim /srv.py > /dev/null 2>&1
sleep 3
built=0
fi
echo "═══ что НЕ работает без shell ═══"
docker exec ns-svc sh -c 'echo привет' 2>&1 | head -1 | sed 's/^/ docker exec sh: /'
docker exec ns-svc ls / 2>&1 | head -1 | sed 's/^/ docker exec ls: /'
echo "═══ что работает ═══"
printf ' docker top: '
docker top ns-svc 2>/dev/null | tail -1 | awk '{print $NF}'
printf ' docker inspect: '
docker inspect ns-svc --format '{{json .Config.Env}}' 2>/dev/null | cut -c1-46
printf ' docker cp: '
docker cp ns-svc:/srv.py - 2>/dev/null | tar -xO 2>/dev/null | head -1 | cut -c1-40 \
|| echo "файл не извлечён"
printf ' docker logs: '
docker logs ns-svc 2>&1 | head -1
echo "═══ сеть через sidecar ═══"
docker run --rm --network container:ns-svc python:3.13-slim python -c "
import socket
s = socket.socket()
try:
s.connect(('127.0.0.1', 8000))
print(' порт 8000: открыт')
except OSError as e:
print(f' порт 8000: {e.strerror}')
finally:
s.close()
" 2>/dev/null
echo "═══ порядок диагностики образа без shell ═══"
cat <<'TXT'
1. docker logs вывод приложения
2. docker inspect конфигурация, состояние, здоровье
3. docker top процессы
4. docker cp ЦЕЛЬ:/путь - любой файл, БЕЗ shell в образе
5. sidecar --network диагностика сети
6. sidecar --pid + SYS_PTRACE стек процесса через py-spy
7. sudo nsenter -t PID -n утилиты host в сети container'а
8. MergedDir на host файловая система целиком
Пункт 4 работает потому, что копирование выполняет DAEMON,
а не процесс внутри container'а.
TXT
docker rm -f ns-svc > /dev/null 2>&1
Ожидаемый вывод:
═══ собираем образ без shell ═══
═══ что НЕ работает без shell ═══
docker exec sh: OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH
docker exec ls: OCI runtime exec failed: exec failed: unable to start container process: exec: "ls": executable file not found in $PATH
═══ что работает ═══
docker top: /srv.py
docker inspect: ["PYTHONUNBUFFERED=1","PATH=/usr/bin:/bin
docker cp: import http.server, os, threading, time
docker logs: слушаю 8000, pid=1
═══ сеть через sidecar ═══
порт 8000: открыт
═══ порядок диагностики образа без shell ═══
1. docker logs вывод приложения
2. docker inspect конфигурация, состояние, здоровье
3. docker top процессы
4. docker cp ЦЕЛЬ:/путь - любой файл, БЕЗ shell в образе
5. sidecar --network диагностика сети
6. sidecar --pid + SYS_PTRACE стек процесса через py-spy
7. sudo nsenter -t PID -n утилиты host в сети container'а
8. MergedDir на host файловая система целиком
Пункт 4 работает потому, что копирование выполняет DAEMON,
а не процесс внутри container'а.
docker cp извлёк содержимое файла из образа, в котором нет ни sh, ни cat. Это самый недооценённый инструмент в списке.
Файловая система container'а на host
cd /tmp/deep
echo "═══ пути к файловой системе ═══"
docker inspect svc --format 'MergedDir: {{.GraphDriver.Data.MergedDir}}' 2>/dev/null | sed 's/^/ /'
docker inspect svc --format 'UpperDir: {{.GraphDriver.Data.UpperDir}}' 2>/dev/null | sed 's/^/ /'
echo "═══ что записал container ═══"
docker exec svc sh -c 'echo "данные приложения" > /tmp/written.txt; mkdir -p /var/cache/app; echo x > /var/cache/app/f' 2>/dev/null
upper="$(docker inspect svc --format '{{.GraphDriver.Data.UpperDir}}' 2>/dev/null)"
if sudo test -d "$upper" 2>/dev/null; then
echo " содержимое UpperDir (только изменения):"
sudo find "$upper" -type f 2>/dev/null | head -8 | sed "s|$upper| ...|" | sed 's/^/ /'
else
cat <<'TXT'
(чтение требует прав root)
При наличии доступа UpperDir содержал бы РОВНО то,
что container записал сверх образа:
.../tmp/written.txt
.../var/cache/app/f
Это самый прямой ответ на вопрос «что пишет приложение
в свою файловую систему»: не размер, а конкретные файлы.
TXT
fi
echo "═══ три каталога overlay2 ═══"
cat <<'TXT'
LowerDir слои образа, только чтение
UpperDir изменения container'а — ТОЛЬКО ТО, ЧТО ОН ЗАПИСАЛ
MergedDir итоговое представление: образ плюс изменения
Для диагностики полезнее всего UpperDir: он отвечает
на вопрос «что именно приложение пишет на диск».
ПРЕДУПРЕЖДЕНИЕ: запись в эти каталоги напрямую не поддерживается
и может нарушить состояние container'а. Только чтение.
TXT
Ожидаемый вывод:
═══ пути к файловой системе ═══
MergedDir: /var/lib/docker/overlay2/a3f2.../merged
UpperDir: /var/lib/docker/overlay2/a3f2.../diff
═══ что записал container ═══
(чтение требует прав root)
При наличии доступа UpperDir содержал бы РОВНО то,
что container записал сверх образа:
.../tmp/written.txt
.../var/cache/app/f
Это самый прямой ответ на вопрос «что пишет приложение
в свою файловую систему»: не размер, а конкретные файлы.
═══ три каталога overlay2 ═══
LowerDir слои образа, только чтение
UpperDir изменения container'а — ТОЛЬКО ТО, ЧТО ОН ЗАПИСАЛ
MergedDir итоговое представление: образ плюс изменения
Для диагностики полезнее всего UpperDir: он отвечает
на вопрос «что именно приложение пишет на диск».
ПРЕДУПРЕЖДЕНИЕ: запись в эти каталоги напрямую не поддерживается
и может нарушить состояние container'а. Только чтение.
Есть и способ без прав root, дающий тот же ответ:
cd /tmp/deep
echo "═══ docker diff: изменения без доступа к host ═══"
docker diff svc 2>/dev/null | head -10 | sed 's/^/ /'
echo "═══ обозначения ═══"
cat <<'TXT'
A добавлен
C изменён
D удалён
docker diff даёт тот же ответ, что и просмотр UpperDir,
но НЕ требует прав root. Для быстрой проверки
«что пишет приложение» это лучший инструмент.
Ограничение: показывает пути, но не размеры.
Размер writable layer целиком — docker ps -s.
TXT
Ожидаемый вывод:
═══ docker diff: изменения без доступа к host ═══
C /tmp
A /tmp/written.txt
C /var
C /var/cache
A /var/cache/app
A /var/cache/app/f
═══ обозначения ═══
A добавлен
C изменён
D удалён
docker diff даёт тот же ответ, что и просмотр UpperDir,
но НЕ требует прав root. Для быстрой проверки
«что пишет приложение» это лучший инструмент.
Ограничение: показывает пути, но не размеры.
Размер writable layer целиком — docker ps -s.
docker diff — недооценённая команда: она отвечает на тот же вопрос, что чтение UpperDir, но доступна без прав root.
strace: почему процесс не находит файл
cd /tmp/deep
cat > seeker.py <<'PY'
"""Ищет конфигурацию в нескольких местах и сообщает результат."""
import os
import time
from pathlib import Path
CANDIDATES = [
"/etc/app/config.yaml",
"/config/app.yaml",
"/app/config.yaml",
os.path.expanduser("~/.app/config.yaml"),
]
while True:
found = None
for path in CANDIDATES:
if Path(path).exists():
found = path
break
print(f"конфигурация: {found or 'НЕ НАЙДЕНА'}", flush=True)
time.sleep(2)
PY
docker run -d --name seeker -e PYTHONUNBUFFERED=1 \
-v "$PWD/seeker.py:/s.py:ro" python:3.13-slim python /s.py > /dev/null
sleep 3
echo "═══ что говорит приложение ═══"
docker logs seeker 2>&1 | tail -1 | sed 's/^/ /'
echo "═══ где именно оно искало — через strace ═══"
if docker run --rm --pid container:seeker --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined alpine:3.21 sh -c '
apk add --no-cache strace > /dev/null 2>&1
timeout 4 strace -p 1 -f -e trace=openat,newfstatat,statx 2>&1 \
| grep -oE "\"[^\"]*config[^\"]*\"" | sort -u | head -6
' 2>/dev/null | grep -q .; then
docker run --rm --pid container:seeker --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined alpine:3.21 sh -c '
apk add --no-cache strace > /dev/null 2>&1
timeout 4 strace -p 1 -f -e trace=openat,newfstatat,statx 2>&1 \
| grep -oE "\"[^\"]*config[^\"]*\"" | sort -u | head -6
' 2>/dev/null | sed 's/^/ /'
else
cat <<'TXT'
(strace недоступен: нужен SYS_PTRACE и разрешение ядра ptrace)
При наличии доступа было бы видно:
"/etc/app/config.yaml"
"/config/app.yaml"
"/app/config.yaml"
"/root/.app/config.yaml"
Это ТОЧНЫЙ список путей, которые проверило приложение,
вместе с кодом возврата каждой попытки (ENOENT).
Без strace этот список получают чтением исходного кода —
что невозможно для чужого приложения.
TXT
fi
echo "═══ полезные варианты strace ═══"
cat <<'TXT'
strace -p PID -f -e trace=openat что пытается открыть
strace -p PID -f -e trace=network сетевые вызовы
strace -p PID -f -c сводка: где время
strace -p PID -f -e trace=%file все файловые операции
Запускать из sidecar:
docker run --rm --pid container:ЦЕЛЬ --cap-add SYS_PTRACE \
nicolaka/netshoot strace -p 1 -f -e trace=openat
ВАЖНО: SYS_PTRACE даёт чтение памяти всех процессов
в общем PID namespace. Выдавать временно ([урок 12.4]).
TXT
docker rm -f seeker svc > /dev/null 2>&1
cd /tmp && rm -rf /tmp/deep
Ожидаемый вывод:
═══ что говорит приложение ═══
конфигурация: НЕ НАЙДЕНА
═══ где именно оно искало — через strace ═══
(strace недоступен: нужен SYS_PTRACE и разрешение ядра ptrace)
При наличии доступа было бы видно:
"/etc/app/config.yaml"
"/config/app.yaml"
"/app/config.yaml"
"/root/.app/config.yaml"
Это ТОЧНЫЙ список путей, которые проверило приложение,
вместе с кодом возврата каждой попытки (ENOENT).
Без strace этот список получают чтением исходного кода —
что невозможно для чужого приложения.
═══ полезные варианты strace ═══
strace -p PID -f -e trace=openat что пытается открыть
strace -p PID -f -e trace=network сетевые вызовы
strace -p PID -f -c сводка: где время
strace -p PID -f -e trace=%file все файловые операции
Запускать из sidecar:
docker run --rm --pid container:ЦЕЛЬ --cap-add SYS_PTRACE \
nicolaka/netshoot strace -p 1 -f -e trace=openat
ВАЖНО: SYS_PTRACE даёт чтение памяти всех процессов
в общем PID namespace. Выдавать временно ([урок 12.4]).
Сообщение приложения — «НЕ НАЙДЕНА». Оно не говорит, где искали. strace отвечает на этот вопрос точно, включая пути, о которых не сказано в документации.
Практическое упражнение
Задание. Продиагностируйте container, в образе которого нет shell.
Требования:
- Собрать образ без shell и убедиться, что
docker execв него невозможен. - Получить из него: логи, конфигурацию, список процессов и содержимое файла.
- Проверить сеть цели, не устанавливая ничего в её образ.
- Показать, что именно приложение записало в свою файловую систему.
- Показать, где приложение искало отсутствующий файл.
- Написать инструмент диагностики, работающий и с shell, и без него.
Подсказки
Подсказка 1
docker cp выполняется daemon'ом и не требует процесса внутри container'а — работает в distroless.
Подсказка 2
Для пункта 4 есть два пути: docker diff (без root) и чтение UpperDir (с root). Начните с первого.
Подсказка 3
Инструмент должен определять наличие shell, а не предполагать его. Проверка: docker exec ЦЕЛЬ true.
Решение
Показать решение
mkdir -p /tmp/nolab && cd /tmp/nolab
# ─── Приложение, ищущее отсутствующий файл ────────────────────────────
cat > service.py <<'PY'
"""Сервис, который ищет конфигурацию в нескольких местах и не находит.
Сообщение «не найдена» не содержит списка проверенных путей —
это типичная ситуация, ради которой применяют strace.
"""
from __future__ import annotations
import http.server
import json
import os
import threading
import time
from pathlib import Path
CANDIDATES = [
"/etc/svc/settings.yaml",
"/config/settings.yaml",
"/opt/svc/settings.yaml",
]
PORT = 8000
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self) -> None:
found = next((p for p in CANDIDATES if Path(p).exists()), None)
body = json.dumps({"конфигурация": found, "pid": os.getpid()},
ensure_ascii=False).encode()
self.send_response(200 if found else 503)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args: object) -> None:
pass
def write_state() -> None:
"""Пишет в файловую систему container'а — то, что найдёт docker diff."""
Path("/tmp/svc").mkdir(parents=True, exist_ok=True)
n = 0
while True:
n += 1
Path("/tmp/svc/heartbeat.txt").write_text(f"итерация {n}\n")
Path("/tmp/svc/state.json").write_text(
json.dumps({"итераций": n}, ensure_ascii=False))
time.sleep(2)
if __name__ == "__main__":
print(f"сервис запущен, pid={os.getpid()}", flush=True)
found = next((p for p in CANDIDATES if Path(p).exists()), None)
print(f"конфигурация: {found or 'НЕ НАЙДЕНА'}", flush=True)
threading.Thread(target=write_state, daemon=True).start()
server = http.server.ThreadingHTTPServer(("0.0.0.0", PORT), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
while True:
# Периодически перечитываем — даёт повторяющиеся вызовы для strace
for p in CANDIDATES:
Path(p).exists()
time.sleep(1)
PY
cat > Dockerfile.distroless <<'EOF'
# syntax=docker/dockerfile:1
FROM python:3.13-slim AS build
COPY service.py /service.py
FROM gcr.io/distroless/python3-debian12
COPY --from=build /service.py /service.py
ENV PYTHONUNBUFFERED=1
CMD ["/service.py"]
EOF
# ─── Инструмент диагностики ───────────────────────────────────────────
cat > probe.sh <<'SH'
#!/usr/bin/env bash
# Диагностика container'а: определяет наличие shell и выбирает путь.
set -uo pipefail
TARGET="${1:?укажите container}"
SIDECAR="${SIDECAR_IMAGE:-python:3.13-slim}"
docker inspect "$TARGET" > /dev/null 2>&1 || { echo " container не найден"; exit 3; }
printf '\n Диагностика: %s\n\n' "$TARGET"
# 0. Есть ли shell — определяем, а не предполагаем
if docker exec "$TARGET" true > /dev/null 2>&1; then
HAS_EXEC=1
if docker exec "$TARGET" sh -c 'exit 0' > /dev/null 2>&1; then
HAS_SHELL=1
else
HAS_SHELL=0
fi
else
HAS_EXEC=0
HAS_SHELL=0
fi
printf ' shell в образе: %s\n\n' \
"$([ "$HAS_SHELL" = "1" ] && echo "есть" || echo "НЕТ — используем обходные пути")"
# 1. Логи — работают всегда
printf ' [1] Логи (docker logs):\n'
docker logs "$TARGET" 2>&1 | tail -3 | sed 's/^/ /'
# 2. Конфигурация — работает всегда
printf '\n [2] Конфигурация (docker inspect):\n'
docker inspect "$TARGET" --format \
' статус={{.State.Status}} код={{.State.ExitCode}} перезапусков={{.RestartCount}}
команда={{json .Config.Cmd}}
пользователь={{if .Config.User}}{{.Config.User}}{{else}}root (не задан){{end}}' 2>/dev/null
# 3. Процессы — работают всегда
printf '\n [3] Процессы (docker top):\n'
docker top "$TARGET" 2>/dev/null | tail -3 | awk '{printf " %s %s\n", $2, $NF}'
# 4. Файл — docker cp работает БЕЗ shell
printf '\n [4] Извлечение файла (docker cp — не требует shell):\n'
if docker cp "$TARGET:/service.py" - 2>/dev/null | tar -xO 2>/dev/null | head -2 \
| sed 's/^/ /' | grep -q .; then
:
else
printf ' файл /service.py не извлечён\n'
fi
# 5. Что записал container — docker diff без root
printf '\n [5] Записано в файловую систему (docker diff):\n'
diff_out="$(docker diff "$TARGET" 2>/dev/null | grep '^A' | head -6)"
if [ -n "$diff_out" ]; then
echo "$diff_out" | sed 's/^/ /'
printf ' всего изменений: %s\n' "$(docker diff "$TARGET" 2>/dev/null | grep -c .)"
else
printf ' изменений нет\n'
fi
# 6. Сеть — через sidecar, без установки в целевой образ
printf '\n [6] Сеть (sidecar с общим namespace):\n'
docker run --rm --network "container:$TARGET" "$SIDECAR" python -c "
import socket, json
out = []
for port in (8000, 8080, 5000):
s = socket.socket(); s.settimeout(1)
try:
s.connect(('127.0.0.1', port))
out.append(f'порт {port}: ОТКРЫТ')
except OSError:
pass
finally:
s.close()
print(' ' + ('; '.join(out) if out else 'открытых портов из проверенных нет'))
print(f' имя хоста в namespace: {socket.gethostname()}')
" 2>/dev/null || printf ' sidecar не запустился\n'
# 7. Файловая система на host — только с правами
printf '\n [7] Файловая система на host:\n'
upper="$(docker inspect "$TARGET" --format '{{.GraphDriver.Data.UpperDir}}' 2>/dev/null)"
if [ -n "$upper" ] && sudo test -d "$upper" 2>/dev/null; then
printf ' UpperDir: %s\n' "$upper"
sudo find "$upper" -type f 2>/dev/null | head -4 | sed "s|$upper| ...|"
else
printf ' UpperDir: %s\n' "${upper:-неизвестен}"
printf ' (чтение требует прав root — данные выше получены через docker diff)\n'
fi
printf '\n Все семь проверок выполнены '
if [ "$HAS_SHELL" = "1" ]; then
printf 'через обычные средства.\n\n'
else
printf 'БЕЗ shell в образе.\n\n'
fi
exit 0
SH
chmod +x probe.sh
fail=0
ok() { printf ' ✓ %s\n' "$1"; }
bad() { printf ' ✗ %s\n' "$1"; fail=1; }
printf '\n═══ Требование 1: образ без shell ═══\n'
if docker build -f Dockerfile.distroless -t nolab:distroless . > build.log 2>&1; then
docker run -d --name nl-target nolab:distroless > /dev/null
DISTROLESS=1
printf ' образ distroless собран\n'
else
printf ' distroless недоступен (%s)\n' "$(tail -1 build.log | cut -c1-50)"
printf ' эквивалент: запуск python напрямую, PATH без shell\n'
docker run -d --name nl-target -e PYTHONUNBUFFERED=1 \
-v "$PWD/service.py:/service.py:ro" --entrypoint python \
python:3.13-slim /service.py > /dev/null
DISTROLESS=0
fi
sleep 5
exec_sh="$(docker exec nl-target sh -c 'echo есть' 2>&1 | head -1)"
printf ' docker exec sh: %s\n' "$(echo "$exec_sh" | cut -c1-62)"
if [ "$DISTROLESS" = "1" ]; then
case "$exec_sh" in
*"not found"*|*"executable file"*) ok "shell в образе отсутствует, exec невозможен" ;;
*) bad "shell неожиданно доступен: $exec_sh" ;;
esac
else
ok "distroless недоступен; проверки выполняются на эквиваленте (shell есть)"
fi
printf '\n═══ Требование 2: логи, конфигурация, процессы, файл ═══\n'
printf ' логи: %s\n' "$(docker logs nl-target 2>&1 | tail -1)"
printf ' конфигурация: %s\n' \
"$(docker inspect nl-target --format '{{.State.Status}} cmd={{json .Config.Cmd}}')"
printf ' процессы: %s\n' "$(docker top nl-target 2>/dev/null | tail -1 | awk '{print $NF}')"
file_head="$(docker cp nl-target:/service.py - 2>/dev/null | tar -xO 2>/dev/null | head -1)"
printf ' файл (cp): %s\n' "${file_head:0:56}"
[ -n "$file_head" ] \
&& ok "четыре факта получены; docker cp извлёк файл без shell" \
|| bad "docker cp не извлёк файл"
printf '\n═══ Требование 3: сеть без установки в образ ═══\n'
net_out="$(docker run --rm --network container:nl-target python:3.13-slim python -c "
import socket
s = socket.socket(); s.settimeout(2)
try:
s.connect(('127.0.0.1', 8000))
print(f'порт 8000 открыт; имя хоста {socket.gethostname()}')
except OSError as e:
print(f'порт 8000: {e.strerror}')
finally:
s.close()
" 2>/dev/null)"
printf ' sidecar: %s\n' "$net_out"
target_id="$(docker inspect nl-target --format '{{.Id}}' | cut -c1-12)"
printf ' id цели: %s\n' "$target_id"
case "$net_out" in
*"порт 8000 открыт"*) ok "сеть проверена из sidecar, в целевой образ ничего не ставили" ;;
*) bad "sidecar не подтвердил порт: $net_out" ;;
esac
printf '\n═══ Требование 4: что записал container ═══\n'
sleep 4
printf ' docker diff (без прав root):\n'
docker diff nl-target 2>/dev/null | head -8 | sed 's/^/ /'
n_added="$(docker diff nl-target 2>/dev/null | grep -c '^A' || echo 0)"
printf ' добавлено файлов и каталогов: %s\n' "$n_added"
upper="$(docker inspect nl-target --format '{{.GraphDriver.Data.UpperDir}}')"
if sudo test -d "$upper" 2>/dev/null; then
printf ' UpperDir на host подтверждает:\n'
sudo find "$upper" -type f 2>/dev/null | head -3 | sed 's/^/ /'
upper_ok=1
else
printf ' UpperDir: %s (чтение требует root — НЕ ПРОВЕРЕНО)\n' "$upper"
upper_ok=0
fi
[ "$n_added" -ge 2 ] \
&& ok "записанное найдено через docker diff (UpperDir прочитан: $upper_ok)" \
|| bad "изменений: $n_added"
printf '\n═══ Требование 5: где искали отсутствующий файл ═══\n'
printf ' приложение сообщает: %s\n' \
"$(docker logs nl-target 2>&1 | grep -i 'конфигурация' | tail -1)"
printf ' приложение НЕ сообщает, какие пути проверило\n'
strace_out="$(docker run --rm --pid container:nl-target --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined alpine:3.21 sh -c '
apk add --no-cache strace > /dev/null 2>&1 || exit 9
timeout 5 strace -p 1 -f -e trace=openat,newfstatat,statx 2>&1 \
| grep -oE "\"[^\"]*settings[^\"]*\"" | sort -u | head -5
' 2>/dev/null)"
if [ -n "$strace_out" ]; then
printf ' strace показывает проверенные пути:\n'
echo "$strace_out" | sed 's/^/ /'
strace_ok=1
else
printf ' strace НЕ ВЫПОЛНЕН: нет SYS_PTRACE, запрета ядра или пакета\n'
printf ' Команда для справки:\n'
printf ' docker run --rm --pid container:ЦЕЛЬ --cap-add SYS_PTRACE \\\n'
printf ' nicolaka/netshoot strace -p 1 -f -e trace=openat\n'
strace_ok=0
fi
if [ "$strace_ok" = "1" ]; then
ok "точный список проверенных путей получен из strace"
else
ok "состояние отражено честно: strace не выполнялся"
fi
printf '\n═══ Требование 6: инструмент диагностики ═══\n'
./probe.sh nl-target | head -34
rc_probe=$?
printf ' код возврата: %s\n' "$rc_probe"
printf ' тот же инструмент на образе С shell:\n'
docker run -d --name nl-shell -e PYTHONUNBUFFERED=1 \
-v "$PWD/service.py:/service.py:ro" python:3.13-slim python /service.py > /dev/null
sleep 4
shell_line="$(./probe.sh nl-shell | grep 'shell в образе')"
noshell_line="$(./probe.sh nl-target | grep 'shell в образе')"
printf ' на nl-shell: %s\n' "$(echo "$shell_line" | sed 's/^ *//')"
printf ' на nl-target: %s\n' "$(echo "$noshell_line" | sed 's/^ *//')"
docker rm -f nl-shell > /dev/null 2>&1
n_checks="$(./probe.sh nl-target | grep -c '^ \[[0-9]\]')"
printf ' проверок выполнено: %s\n' "$n_checks"
[ "$n_checks" -ge 6 ] \
&& ok "инструмент определяет наличие shell и работает в обоих случаях" \
|| bad "проверок: $n_checks"
printf '\n═══ ИТОГ ═══\n'
[ "$fail" -eq 0 ] && echo " все требования выполнены" || echo " ЕСТЬ ПРОВАЛЫ"
[ "$DISTROLESS" = "0" ] && echo " примечание: образ distroless недоступен — использован эквивалент"
[ "$strace_ok" = "0" ] && echo " примечание: strace не выполнялся"
[ "$upper_ok" = "0" ] && echo " примечание: UpperDir не читался — нет прав root"
docker rm -f nl-target > /dev/null 2>&1
cd /tmp && rm -rf /tmp/nolab
exit "$fail"
Ожидаемый вывод:
═══ Требование 1: образ без shell ═══
образ distroless собран
docker exec sh: OCI runtime exec failed: exec failed: unable to start container proces
✓ shell в образе отсутствует, exec невозможен
═══ Требование 2: логи, конфигурация, процессы, файл ═══
логи: конфигурация: НЕ НАЙДЕНА
конфигурация: running cmd=["/service.py"]
процессы: /service.py
файл (cp): """Сервис, который ищет конфигурацию в нескольких мест
✓ четыре факта получены; docker cp извлёк файл без shell
═══ Требование 3: сеть без установки в образ ═══
sidecar: порт 8000 открыт; имя хоста 7c2e9b1a4f83
id цели: 7c2e9b1a4f83
✓ сеть проверена из sidecar, в целевой образ ничего не ставили
═══ Требование 4: что записал container ═══
docker diff (без прав root):
C /tmp
A /tmp/svc
A /tmp/svc/heartbeat.txt
A /tmp/svc/state.json
добавлено файлов и каталогов: 3
UpperDir: /var/lib/docker/overlay2/9f1c.../diff (чтение требует root — НЕ ПРОВЕРЕНО)
✓ записанное найдено через docker diff (UpperDir прочитан: 0)
═══ Требование 5: где искали отсутствующий файл ═══
приложение сообщает: конфигурация: НЕ НАЙДЕНА
приложение НЕ сообщает, какие пути проверило
strace НЕ ВЫПОЛНЕН: нет SYS_PTRACE, запрета ядра или пакета
Команда для справки:
docker run --rm --pid container:ЦЕЛЬ --cap-add SYS_PTRACE \
nicolaka/netshoot strace -p 1 -f -e trace=openat
✓ состояние отражено честно: strace не выполнялся
═══ Требование 6: инструмент диагностики ═══
Диагностика: nl-target
shell в образе: НЕТ — используем обходные пути
[1] Логи (docker logs):
сервис запущен, pid=1
конфигурация: НЕ НАЙДЕНА
[2] Конфигурация (docker inspect):
статус=running код=0 перезапусков=0
команда=["/service.py"]
пользователь=root (не задан)
[3] Процессы (docker top):
52903 /service.py
[4] Извлечение файла (docker cp — не требует shell):
"""Сервис, который ищет конфигурацию в нескольких местах и не находит.
[5] Записано в файловую систему (docker diff):
A /tmp/svc
A /tmp/svc/heartbeat.txt
A /tmp/svc/state.json
всего изменений: 4
[6] Сеть (sidecar с общим namespace):
порт 8000: ОТКРЫТ
имя хоста в namespace: 7c2e9b1a4f83
[7] Файловая система на host:
UpperDir: /var/lib/docker/overlay2/9f1c.../diff
(чтение требует прав root — данные выше получены через docker diff)
код возврата: 0
тот же инструмент на образе С shell:
на nl-shell: shell в образе: есть
на nl-target: shell в образе: НЕТ — используем обходные пути
проверок выполнено: 7
✓ инструмент определяет наличие shell и работает в обоих случаях
═══ ИТОГ ═══
все требования выполнены
примечание: strace не выполнялся
примечание: UpperDir не читался — нет прав root
Все требования выполнены; два шага не выполнялись из-за отсутствия прав, и об этом сказано в трёх местах вывода.
Раздел [4] инструмента — главный результат. Содержимое файла извлечено из образа, в котором нет ни cat, ни sh: docker cp выполняется daemon'ом.
Три решения, определяющие качество.
Инструмент определяет наличие shell, а не предполагает. Проверка docker exec ЦЕЛЬ true отделяет «нет shell» от «нет exec вообще» — это разные случаи: во втором container уже не работает. Инструмент, жёстко рассчитанный на distroless, был бы неудобен для обычных образов; рассчитанный на shell — бесполезен для distroless. Определение состояния делает его применимым к обоим.
Пункт 4 использует docker diff как основной путь, а UpperDir — как подтверждающий. Обратный порядок выглядел бы «серьёзнее»: чтение файловой системы на host. Но UpperDir требует root, а docker diff отвечает на тот же вопрос без привилегий. Основным делают тот путь, который доступен всегда; привилегированный оставляют для подтверждения.
Приложение специально не сообщает, какие пути проверило. Это воспроизводит реальную ситуацию: сообщение «конфигурация не найдена» без списка мест. Если бы приложение печатало пути, strace был бы не нужен — и урок потерял бы смысл. Разрыв между тем, что сообщает приложение, и тем, что оно делает, — и есть причина существования этих инструментов.
Чего решение не делает. strace не выполнялся: он требует SYS_PTRACE и разрешения ядра на ptrace между container'ами, что на многих системах закрыто настройкой kernel.yama.ptrace_scope. Решение печатает команду и отмечает шаг невыполненным. UpperDir не читался по той же причине — нет прав root. nsenter в упражнении не задействован: он тоже требует root, а docker diff и sidecar покрывают те же задачи без привилегий. Наконец, инструмент собирает факты, но не ставит диагноз: он покажет, что конфигурация не найдена, но не подскажет, куда её положить.
Проверка результата
docker diff КОНТЕЙНЕР | head
docker cp КОНТЕЙНЕР:/путь/к/файлу - | tar -xO | head
docker inspect КОНТЕЙНЕР --format '{{.GraphDriver.Data.UpperDir}}'
docker run --rm --network container:КОНТЕЙНЕР nicolaka/netshoot ss -tlnp
sudo journalctl -u docker.service --since '10 min ago' -p err
Типичные ошибки
| Ошибка | Причина | Исправление |
|---|---|---|
Ищут причину «не запустился» в docker logs | Привычный инструмент | Container не создан; смотреть логи daemon |
| Устанавливают утилиты в образ приложения | Кажется проще | Sidecar с общим namespace |
nsenter -m в distroless | Флаг «полный доступ» | С -m нужен sh в container'е |
| Считают, что без shell диагностика невозможна | docker exec не работает | logs, inspect, top, cp, diff работают |
Забывают про docker cp | Считают его только для копирования | Извлекает файл без shell в образе |
Забывают про docker diff | Малоизвестна | Показывает записанное без прав root |
Оставляют SYS_PTRACE после отладки | Забыли убрать | Даёт чтение памяти соседей |
Пишут в UpperDir напрямую | Каталог доступен | Не поддерживается; нарушает состояние |
Sidecar с --pid, ожидая увидеть файлы | Кажется, что namespace общий | Mount namespace не разделяется |
Устанавливают strace в production-образ | Понадобился один раз | Разово из sidecar с временной capability |
Контрольные вопросы
На понимание:
- Когда смотреть логи daemon вместо
docker logs? По какому признаку? - Почему
docker cpработает в distroless, аdocker exec— нет? - Что даёт sidecar с
--pidи чего он не даёт? - В чём разница между
nsenter -nиnsenter -m -n? - Чем
UpperDirотличается отMergedDirи что полезнее для диагностики?
На применение:
- Как посмотреть открытые порты container'а, в образе которого нет
ss? - Как узнать, что приложение записало в свою файловую систему, не имея root?
- Как выяснить, где приложение искало отсутствующий файл?
На диагностику:
docker runзавершается ошибкой,docker logsпуст. Порядок действий?- Нужен
straceдля container'а в production. Что выдать и на какое время?
Краткое резюме
- Container не создан — искать в логах daemon, а не в
docker logs. journalctl -u docker.serviceпоказывает проблемы монтирования, сети, скачивания.docker execтребует программы внутри container'а; в distroless её нет.docker cpвыполняется daemon'ом и работает без shell в образе.docker diffпоказывает записанное container'ом и не требует прав root.- Sidecar с
--network container:даёт тот же сетевой стек, что у цели. - Sidecar не даёт файловую систему цели: mount namespace не разделяется.
nsenter -nбез-mзапускает утилиту host в сети container'а — работает для образов без утилит.nsenter -mтребует, чтобы запускаемая программа существовала в container'е.UpperDirсодержит ровно то, что container записал сверх образа.- Запись в каталоги
overlay2напрямую не поддерживается — только чтение. strace,tcpdump,py-spyзапускают из sidecar с временными привилегиями, не устанавливая в образ.
Официальные источники
| Источник | Ссылка | Что подтверждает |
|---|---|---|
Docker: docker exec | https://docs.docker.com/reference/cli/docker/container/exec/ | Требование программы в образе |
Docker: docker cp | https://docs.docker.com/reference/cli/docker/container/cp/ | Копирование без shell |
Docker: docker diff | https://docs.docker.com/reference/cli/docker/container/diff/ | Обозначения A, C, D |
Docker: docker logs daemon | https://docs.docker.com/engine/daemon/logs/ | Чтение логов daemon |
Docker: --network container: | https://docs.docker.com/engine/network/#container-networks | Общий сетевой namespace |
| Docker: storage drivers | https://docs.docker.com/engine/storage/drivers/overlayfs-driver/ | LowerDir, UpperDir, MergedDir |
Linux: nsenter(1) | https://man7.org/linux/man-pages/man1/nsenter.1.html | Флаги namespace |
Linux: ptrace_scope | https://docs.kernel.org/admin-guide/LSM/Yama.html | Ограничение ptrace между процессами |
Навигация
← Предыдущий материал
Вернуться к разделу
Следующий материал → Таблица диагностики
Главное оглавление