Случилась проблема в сервисе, ты засучив рукава проваливаешься в его контейнер, а там:
root@9a5c3a45d111:/# curl ...
bash: curl: command not found
или
root@9a5c3a45d111:/# ss -ntlp
bash: ss: command not found
или
root@9a5c3a45d111:/# dig ...
bash: dig: command not found
Частый кейс. И из таких ситуаций надо выкручиваться.
На примере задачи “Какие TCP порты открыты у контейнера?” посмотрим как это можно сделать.
При наличии доступа к хосту
Большинство случаев закрывает nsenter:
# man nsenter
NSENTER(1) User Commands NSENTER(1)
NAME
nsenter - run program in different namespaces
...
Утилита позволяет запускать произвольные команды в разных namespaces операционной системы.
Это те самые вокруг которых и строится “изоляция” контейнеров в Linux -man 7 namespaces.
Для этого:
- на хостовой машине найдем
PIDконтейнера:# docker inspect --format='{{.State.Pid}}' 9a5c3a45d111 1477639 - запустим
nsenterв сетевом неймспейсе найденногоPID:что можно перевести как: “Запусти команду# nsenter -t 1477639 --net ss -ntl State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 4096 127.0.0.11:39031 0.0.0.0:* LISTEN 0 511 *:8080 *:* LISTEN 0 511 *:5555 *:*ss -ntlв сетевом неймспейсе (--net) процесса сPID1477639(-t)”.
Команды, как указал выше, можно запускать произвольные, главное они должны быть доступны на хостовой машине. В зависимости от команды могут потребоваться разные ключи неймспейсов.
Так чтобы увидеть все запущенные процессы внутри контейнера, потребуется запуститься в mount и pid неймспейсах:
# nsenter -t 1477639 --pid --mount ps aux
PID USER TIME COMMAND
1 root 1h12 node /usr/local/bin/camouflage --config config.yml
29 root 1h24 /usr/local/bin/node /usr/local/bin/camouflage --config config.yml
...
135 root 0:00 ps aux
Когда доступ к хосту отсутствует
/proc
Самый надежный способ собирать информацию из /proc, а именно парсить файлы /proc/net/tcp и /proc/net/tcp6 (вывод сокращен):
# cat /proc/net/tcp
sl local_address rem_address st ...
0: 0B00007F:9877 00000000:0000 0A ...
# cat /proc/net/tcp6
sl local_address rem_address st ...
0: 00000000000000000000000000000000:1F90 00000000000000000000000000000000:0000 0A ...
1: 00000000000000000000000000000000:15B3 00000000000000000000000000000000:0000 0A ...
Нас будут интересовать колонки local_address и st.
Так 0B00007F:9877 представляет собой <ip>:<port> в Hef формате, причем:
0B00007Fпредставлен в обратном порядке байтов (little-endian) и после перевода будет иметь вид1.0.0.127, а читать его следует справа-налево -127.0.0.1, ссылка на конвертер;9877читается “по человечески” слева-направо и переводится в39031.
А st это состояние соединения, нам интересно значение 0A, то есть TCP_LISTEN. Про состояния содинения тут.
Подробнее про файлы /proc/net/{tcp,tcp6} в документации к ядру.
Не самый удобный вариант, но вполне рабочий.
Использовать bash-магию
И заключительный на сегодня способ использовать функционал bash.
Зададим вопрос “открыт ли порт 39031 в контейнере?” и получим ответ:
# :> /dev/tcp/0.0.0.0/39031
#
Точнее прямой ответ мы не получим, но его отсутствие и будет означать, что порт открыт;)
А “открыт ли порт 39566?”:
# :> /dev/tcp/0.0.0.0/39566
-bash: connect: Connection refused
-bash: /dev/tcp/0.0.0.0/39566: Connection refused
Ответ в данном случае более явный - порт закрыт.
Поищем про /dev/tcp в man bash:
/dev/tcp/host/port
If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open the corresponding TCP socket.
А конструкция :> будет перенаправлять пустой вывод команды-заглушки : в открытый TCP сокет.
Информация по команде : так же доступна в man:
: [arguments]
No effect; the command does nothing beyond expanding arguments and performing any specified redirections. The return status is zero.
На основе конструкции мы можем накидать скрипт для сканирования портов:
# cat port-scan.sh
#!/bin/bash
for PORT in $(seq 1 60999); do
if (:> /dev/tcp/0.0.0.0/$PORT) &>/dev/null; then
echo "Port $PORT is open"
fi
done
# ./port-scan.sh
Port 5555 is open
Port 8080 is open
Port 39031 is open
Этот метод не является идеальным, так как требует наличия bash в контейнере. Однако, чем больше у нас инструментов, тем выше вероятность, что один из них окажется подходящим.
Буду признателен, если вы поделитесь и другими способами, о которых я не упомянул.
Удачи!