检查 Docker 中开放的端口失败

问题描述 投票:0回答:1

我目前正在为我的 IT 环境创建一个定制的 Docker 镜像。我想利用 Docker 的可能性来执行容器的健康检查。由于我使用不同的端口来配置系统,因此我希望尽可能启用动态检查并灵活地确定 PID 的 TCP 端口。

#! /usr/sbin/python
import psutil
import socket

def get_httpd_pids():
    pids = []
    for proc in psutil.process_iter(['name', 'cmdline']):
        if proc.name() == 'httpd':
            pids.append(proc.pid)
    return pids

def get_open_tcp_ports(pid):
    process = psutil.Process(pid)
    connections = process.net_connections()
    tcp_connections = [conn for conn in connections if conn.type == socket.SocketKind.SOCK_STREAM]
    open_ports = [(conn.laddr.ip, conn.laddr.port) for conn in tcp_connections]
    return open_ports

def check_port_open(ip, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)
    try:
        s.connect((ip, port))
        print(f"Port {port} on {ip} is open")
        return True
    except socket.error:
        print(f"Port {port} on {ip} is closed")
        return False
    finally:
        s.close()

def main():
    ports = []
    for pid in get_httpd_pids():
        ports += get_open_tcp_ports(pid)
    ports = [(ip.replace('::', 'localhost'), port) for ip, port in ports]
    for ip, port in ports:
        if not check_port_open(ip, port):
            print("At least one port is closed. Exiting.")
            exit(1)
    exit(0)

if __name__ == "__main__":
    main()

不幸的是,我在 Docker 中的测试失败并出现以下错误消息:

Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/psutil/_pslinux.py", line 1717, in wrapper
    return fun(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/psutil/_pslinux.py", line 2348, in net_connections
    ret = _net_connections.retrieve(kind, self.pid)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/psutil/_pslinux.py", line 1034, in retrieve
    inodes = self.get_proc_inodes(pid)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/psutil/_pslinux.py", line 857, in get_proc_inodes
    inode = readlink("%s/%s/fd/%s" % (self._procfs_path, pid, fd))
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/psutil/_pslinux.py", line 215, in readlink
    path = os.readlink(path)
           ^^^^^^^^^^^^^^^^^
PermissionError: [Errno 13] Permission denied: '/proc/16/fd/0'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/etc/httpd/scripts/ApacheHC.py", line 44, in <module>
    main()
  File "/etc/httpd/scripts/ApacheHC.py", line 35, in main
    ports += get_open_tcp_ports(pid)
             ^^^^^^^^^^^^^^^^^^^^^^^
  File "/etc/httpd/scripts/ApacheHC.py", line 14, in get_open_tcp_ports
    connections = process.net_connections()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/psutil/__init__.py", line 1223, in net_connections
    return self._proc.net_connections(kind)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/psutil/_pslinux.py", line 1719, in wrapper
    raise AccessDenied(self.pid, self._name)
psutil.AccessDenied: (pid=16)

如果有人可以帮助我解决问题,我会很高兴。不幸的是,我没有足够的 Docker 经验来解决这个错误。我的脚本在 Docker 之外运行得很好,但在容器中却不行。

python docker psutil
1个回答
0
投票

这个问题其实和Docker没有太大关系。 Python 的

psutil
读取每个进程的 /proc 伪文件系统 - 但对于这些权限的设置,只有运行该进程的用户才能获取所有信息。换句话说:您只能看到运行脚本的同一用户的进程信息(包括套接字和端口)。

据我所知,有两种方法可以直接查找有关开放端口的信息,而所有其他工具(例如 netstat 和 ss)都只是使用这些:

  1. 打开NETLINK_SOCK_DIAG套接字直接向内核API询问信息。这就是
    ss
    工具正在做的事情,高性能,但由于似乎没有 Python 库,所以很难使用。
  2. 解析例如
    /proc/net/tcp
    获取打开的 TCP 套接字,过滤 rem_address 00000000:0000 以仅进行监听,端口位于 local_address (十六进制)中。还有 Python 脚本和库可以做到这一点。

但是,这两种方法都只能获取文件描述符的索引节点,并且你只能通过

/proc/<process>
找到拥有这些索引节点的进程,这会遇到与你已经遇到的相同的问题。 因此,除非像 UID 这样的东西可以帮助你,否则这些不会帮助你 - 像 netstat 和 ss 这样的工具也有同样的问题。

快速而肮脏的修复是以root身份运行,但有充分的理由不这样做。

因此,最好扭转这一局面 - docker 容器不会随机选择要运行的端口,因此与其搜索端口,不如尝试让它知道运行状况检查在何处运行。

您的问题暗示,检查将在容器内部进行,但我想知道为什么您要更改端口:不同容器中的多个进程都可以打开同一个端口,然后您只需将其“映射”到不同的端口主持人“在外面”。 我认为您的健康检查正在容器的

外部

运行 - 但与容器端口映射到主机端口的位置相同的外部,因此最好检查并解析该映射。或者以与在那里设置相同的方式在运行状况检查中进行设置。 如果由于某种原因这确实不是一个选项,您可以使用

docker inform

来检查 docker 容器的所有端口映射(网络设置 -> 端口)。 Python Docker SDK 通过容器对象 (doc) 的属性公开这些内容,因此您应该能够查询和解析它们: import docker client = docker.from_env() ports = list() for container in client.containers.list(): ... # Add check whether this actually is a container you are interested in! for insideport, outsideports in container.attrs["NetworkSettings"]["Ports"].items(): if outsideports is None: continue # Port is not mapped to the outside for bind in outsideports: # Maybe you want to check bind["HistIp"] as well for IPv4/IPv6 or something ports.append(bind["HostPort"])

您可以例如向容器添加标签以检查您感兴趣的容器或其他内容,否则此代码将为您提供所有 docker 容器的所有开放端口。
(添加异常处理,注意它必须连接到docker套接字!)。

© www.soinside.com 2019 - 2024. All rights reserved.