![当 docker 容器运行相同的应用程序(例如 apache2)时,主机上的 `pidof` 检查失败](https://linux22.com/image/713147/%E5%BD%93%20docker%20%E5%AE%B9%E5%99%A8%E8%BF%90%E8%A1%8C%E7%9B%B8%E5%90%8C%E7%9A%84%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%EF%BC%88%E4%BE%8B%E5%A6%82%20apache2%EF%BC%89%E6%97%B6%EF%BC%8C%E4%B8%BB%E6%9C%BA%E4%B8%8A%E7%9A%84%20%60pidof%60%20%E6%A3%80%E6%9F%A5%E5%A4%B1%E8%B4%A5.png)
apache2 init 脚本会pidof
检查 apache 是否已在运行。
if pidof $DAEMON > /dev/null 2>&1 ; then
if [ -e $PIDFILE ] && pidof $DAEMON | tr ' ' '\n' | grep -w $(cat $PIDFILE) > /dev/null 2>&1 ; then
AP_RET=2
else
AP_RET=1
fi
...
elif [ $AP_RET = 1 ] ; then
APACHE2_INIT_MESSAGE="There are processes named 'apache2' running which do not match your pid file which are left untouched in the name of safety, Please review the situation by hand".
(文件:Ubuntu 16.04.3 LTS 上的 /etc/init/apache2 - 为简洁起见已截断)
然而,在 docker 主机上,VM 容器中可能已经有 apache。在这种情况下,pidof
即使主机上没有运行 apache,也会返回非空值。
$ sudo service apache2 stop
$ pidof apache2
32742 32480 32379 32365 31295 31294 31293 31292 31291 31274 31270
这意味着只有当全部包含 apache 的 docker 容器已停止(或尚未启动)。因此,主机上的 apache 无法restart
启动。
如何解决这种情况,以便主机的 apache 可以独立于虚拟机重新启动?是否有一个版本pidof
可以仅检测 init 直接拥有的 pid?
答案1
遗憾的是,init 脚本中有一个错误# can't use pidofproc from LSB here
,但没有真正的解释。我仍然认为这个 apache2 脚本有一个值得报告的错误。
TL;DR:解决方案:替换pidof apache2
为pgrep --ns 1 ^apache2$
(或者,如果这不起作用,pgrep --ns 1 --nslist uts ^apache2$
)
关于命名空间的详细解释以及我之前写的示例pgrep
可以这样做:
使用 获得“候选者”后pidof
,可以使用以下方法将它们分开:检查它们的命名空间,并将它们与pid 1
(init/systemd) 的命名空间进行比较。使用lxc
和inetd
进程的示例,但这是容器的技术和进程名称不可知的:
# lxc-start stretch-amd64
# pidof inetd
10285 3372
# ls -l /proc/1/ns/
total 0
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 cgroup -> cgroup:[4026531835]
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 net -> net:[4026531993]
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 pid_for_children -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 nov. 9 19:49 uts -> uts:[4026531838]
# ls -l /proc/3372/ns/
total 0
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 cgroup -> cgroup:[4026531835]
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 net -> net:[4026531993]
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 pid_for_children -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 nov. 9 19:51 uts -> uts:[4026531838]
# ls -l /proc/10285/ns/
total 0
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 cgroup -> cgroup:[4026532516]
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 ipc -> ipc:[4026532415]
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 mnt -> mnt:[4026532410]
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 net -> net:[4026532418]
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 pid -> pid:[4026532416]
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 pid_for_children -> pid:[4026532416]
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 nov. 9 19:50 uts -> uts:[4026532414]
这里可以清楚地看到pid 3372
共享的pid 1
命名空间。3372
正在主机上运行。10285
不共享任何命名空间(ok 用户是相同的:容器以 root 身份运行),因此它在容器中。有时主机上运行的某些程序可能会因为某些原因(通常与安全有关)而更改其中一些,但不应该更改 uts(主机名)命名空间。所以这里有一个使用和的脚本,stat
在参数“$1”(例如:或脚本的参数)中给出进程的名称,set -- inetd
将仅提供相同 uts 命名空间中的进程,通常意味着(相同)主机。
pid1uts="$(stat -c %N /proc/1/ns/uts|cut -d' ' -f3)"
for i in $(pidof "$1"); do
if [ "$pid1uts" = "$(stat -c %N /proc/$i/ns/uts|cut -d' ' -f3)" ]; then
echo $i
fi
done | xargs -r
在我的示例中,返回3372
。
我解释了如何去做,但是当pgrep
有其他选择来处理这个问题时,为什么要重新发明轮子呢:
# pgrep ^inetd$
3372
10285
# pgrep --ns 1 --nslist uts ^inetd$
3372
或者对于大多数情况来说:
# pgrep --ns 1 ^inetd$
3372
答案2
如果你的主机上的服务监听端口 80,你可以使用 netstats 找出进程 ID
#netstat -plan | grep :80
容器进程应该与主机上的其他端口号绑定,并在容器内部使用 80 端口。这样您就可以轻松找出主机进程并终止它。