我目前正在为我的 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 之外运行得很好,但在容器中却不行。
这个问题其实和Docker没有太大关系。 Python 的
psutil
读取每个进程的 /proc 伪文件系统 - 但对于这些权限的设置,只有运行该进程的用户才能获取所有信息。换句话说:您只能看到运行脚本的同一用户的进程信息(包括套接字和端口)。
据我所知,有两种方法可以直接查找有关开放端口的信息,而所有其他工具(例如 netstat 和 ss)都只是使用这些:
ss
工具正在做的事情,高性能,但由于似乎没有 Python 库,所以很难使用。/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套接字!)。