Случилась проблема в сервисе, ты засучив рукава проваливаешься в его контейнер, а там:
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
) процесса сPID
1477639
(-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
в контейнере. Однако, чем больше у нас инструментов, тем выше вероятность, что один из них окажется подходящим.
Буду признателен, если вы поделитесь и другими способами, о которых я не упомянул.
Удачи!