内容简介:在一个Docker Container中运行多个服务?打扰了。Docker,或说任何基于在实际中,使用
在一个Docker Container中运行多个服务?打扰了。
0x00 前言
Docker,或说任何基于 内核namespace
的轻量级进程隔离技术,在设计之初,都不是为了当作虚拟机使用的。也就是说,其中运行的并不是一个完整的操作系统。包括 Docker 官方,也是推荐在一个Container内仅运行一个服务。如果需要运行多个服务,应通过 docker run --link
或者 docker-compose
来关联多个容器。但是在实际的应用中,我们经常希望将一个完整的可运行环境打包成一个 docker image
,不再依赖其他的容器。比如在CTF比赛中,将多个服务打包成一个Image,可以有效地提高在环境受损后恢复的效率。在经历了多场比赛,看过各种大师傅用各种奇怪的姿势完成这个任务后,觉得应该好好的讨论一下这个问题。
0x01 错误的姿势
- 使用upstart的启动方式
# Dockerfile From ubuntu:14.04 RUN apt-get update && apt-get upgrade -y && apt-get install mysql apache2 php 7.0 ADD web /var/www/html RUN service mysql start && /var/www/html/init_sql.sh && service mysql stop CMD service mysql start && service apache2 start && while true; do sleep 10;done
- 使用systemd的启动方式
# Dockerfile From ubuntu:16.04 RUN apt-get update && apt-get upgrade -y && apt-get install mysql apache2 php7.0 ADD web /var/www/html RUN systemctl start mysql && /var/www/html/init_sql.sh && systemctl stop mysql CMD systemctl start mysql && systemctl start apache2 && while true; do sleep 10;done
- 使用启动脚本启动多个服务
# Dockerfile From ubuntu:16.04 RUN apt-get update && apt-get upgrade -y && apt-get install mysql apache2 php7.0 ADD web /var/www/html ADD entrypoint.sh /sbin/ RUN chmod +x /sbin/entrypoint.sh /var/www/html/init_sql.sh&& \ /etc/init.d/mysql start && /var/www/html/init_sql.sh && /etc/init.d/mysql stop CMD /sbin/entrypoint.sh
#!/bin/bash # entrypoint.sh /usr/bin/mysqld start & /usr/bin/httpd & while true do sleep 100 done
在实际中,使用 方法1
或者 方法2
很大几率无法完成将多个服务跑在同一个container中的任务。 方法3
虽然可以,但仍然存在一些问题:
SIGTERM
方法1
和 方法2
不能成功,是因为docker只是一个进程隔离的沙箱环境,并不是真正的虚拟机。而 service xxx start
和 systemctl start xxx
分别是 upstart
和 systemd
这两个 /sbin/init
进程的替代者的服务管理命令。而 upstart
和 systemd
都要求系统必须是物理机或虚拟机,并不支持作为container的 init
进程。方法3存在问题是因为,在正常的系统中, init
进程永远占用 PID=1
的位置,回收僵尸进程、处理未处理的信号等都是由 init
进程帮我们完成的,一个子进程如果失去了父进程,也会由 init
进程接管。但是在container中, init
进程并不存在, PID=1
的进程是我们在 Dockerfile
中定义的 Entrypoint
或最后一个 CMD
指定的命令。
root@vpscn:/var/lib/docker# docker exec -it hackmd sh /hackmd # ps -ef PID USER TIME COMMAND 1 hackmd 1:03 node app.js 42 hackmd 0:00 /usr/local/bin/node ./lib/workers/dmpWorker.js 62 root 0:00 sh 69 root 0:00 ps -ef
因此,对于启动方法3的container,我们应该在启动时加上 --init
参数,来强制使用 tini
作为 init
进程。但是就算这样,在服务多了之后,进行重启等操作仍然很繁琐。
0x02 推荐的姿势
作为一个~~金牌运维~~打杂的,简单谈谈我常用的方法。
首先推荐一个超级好用的基础镜像 phusion/baseimage
。截至SUCTF2018环境准备完成时,该镜像的最新版本是 0.10.1
,基于 ubuntu 16.04
。我们常用的 apt-get
等命令都可以无缝兼容。 phusion/baseimage
采用了作者自己开发的一个基于 python
的 init
进程作为Container的 Entrypoint
,采用 runit
作为服务管理器。这个基础镜像还是在Coding打杂的时候知道的, Coding ~~WebIDE~~ Studio
的Web Terminal也是基于这个镜像做的。NUAACTF/SUCTF的PWN题基础镜像 ctf-xinetd
也是基于这个镜像做的。
然后直接贴个在SUCTF2018运维期间写的 Dockerfile
#Dockerfile FROM phusion/baseimage:0.10.1 MAINTAINER Yibai Zhang <xm1994@outlook.com> RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list &&\ sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list &&\ apt-get update && apt-get install -y apache2 libapache2-mod-php php-mysql mariadb-server &&\ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/www/html/* RUN mkdir -p /etc/service/apache2/ && \ printf "#!/bin/sh\n\ntrap \"apachectl -k graceful-stop\" 1 2 3 6 15\n\nexec /usr/sbin/apachectl -D FOREGROUND\n" > /etc/service/apache2/run &&\ chmod +x /etc/service/apache2/run && mkdir -p /etc/service/mysql/ &&\ printf "#!/bin/sh\n\ntrap \"mysqladmin -uroot -psuCTF_P1us_1s shutdown\" 1 2 3 6 15\n\nexec /usr/bin/mysqld_safe" > /etc/service/mysql/run &&\ mkdir -p /var/run/mysqld/ && chown mysql:mysql /var/run/mysqld &&\ chmod 700 /etc/service/mysql/run /etc/service/apache2/run COPY web /var/www/html COPY flag /flag RUN echo "secure-file-priv=/var/www/" >>/etc/mysql/mariadb.conf.d/50-server.cnf && chmod -R 777 /var/www/html/favicon COPY init_sql.sh /tmp/init_sql.sh RUN chmod +x /tmp/init_sql.sh && bash -c "/tmp/init_sql.sh" && rm /tmp/init_sql.sh EXPOSE 80
#!/usr/bin/env bash #init_sql.sh mysqld_safe & echo -n "Waiting for mysql startup" while ! mysqladmin --host="localhost" --silent ping ; do echo -n "." sleep 1 done echo mysql -uroot <<EOF UPDATE mysql.user SET Password=PASSWORD('XXXXXX'), plugin = '' WHERE User='root'; create database calc; use calc; create table user( id INT NOT NULL AUTO_INCREMENT primary key, username varchar(32) NOT NULL, password varchar(32) NOT NULL )ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into user values(1,'admin','aa67095d8e65d624548cb6b50bd4778e'); create table file( id INT NOT NULL AUTO_INCREMENT primary key, filename varchar(32) NOT NULL, filehash varchar(32) NOT NULL, sig varchar(120) NOT NULL )ENGINE=InnoDB DEFAULT CHARSET=utf8; create table flag( flag varchar(120) primary key )ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into flag values('SUCTF{a_very_long_long_long_long_long_fake_flag_d}'); grant SELECT, INSERT on calc.user to 'suctf'@localhost identified by 'suctf'; grant SELECT, INSERT, UPDATE on calc.file to 'suctf'@localhost ; grant SELECT on calc.flag to 'suctf'@localhost ; FLUSH PRIVILEGES; EOF mysqladmin -uroot -pXXXXXX shutdown
这里着重看一下 printf "#!/bin/sh\n\ntrap \"apachectl -k graceful-stop\" 1 2 3 6 15\n\nexec /usr/sbin/apachectl -D FOREGROUND\n" > /etc/service/apache2/run
,这个命令就是在创建runit启动脚本。具体的说明可以去看 phusion/baseimage
或者 runit
的手册。执行完成后会在 /etc/service/apache2/run
下面生成如下内容的脚本
#!/bin/sh trap "apachectl -k graceful-stop" 1 2 3 6 15 exec /usr/sbin/apachectl -D FOREGROUND
这个脚本会作为runit的子进程运行,并将Apache2保持在前台运行。在接收到 1 2 3 6 15
这几个信号的时候友好的(graceful)结束Apache2。如果在运行中需要重启Apache服务,只需要运行 docker exec container_name sv restart apache2
即可。通过这种方式,在Container停止的时候也可以通知相关的进程,而不是直接全部杀死,更可以保证服务的完整性。~~虽然在比赛中基本挂了就要恢复环境根本不需要保证完整性。~~
以上所述就是小编给大家介绍的《如何科学的在Docker Container中运行多个服务》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 运行多个 npm script
- 多个安卓墙纸APP运行恶意广告
- ansible文件内容替换+在远程主机上运行多个command
- UP Core开发板将于8月份出货,可运行多个操作系统
- 如何从PHP脚本(如批处理文件)中运行多个PHP脚本?
- 开源组件漏洞对运行多个CMS的站点构成威胁(CVE-2019-11831)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。