为什么要用s6
容器的哲学就是“容器里最好只有一个模块”,也就是常说的容器即进程(one process per container)
。但是现在这个哲学有所动摇,很多人认为容器即服务(one thing per container)
,毕竟总有些特殊场景我们需要在一个docker里安装多个模块。容器虽然可以在docker run
的时候可以指定--restart=always
,但是这个并不能对pid不为1的进程起到看门狗的作用,如果pid不为1的进程crash了,那么就不会重启而出现故障了。于是需要一个真正的看门狗来重启这个服务。可用于docker的看门狗品种有很多,详情可见 https://www.iamle.com/archives/tag/runit 这篇文章。我在那篇文章里选择了s6
。虽然听说有赞在生产环境里已经用了runit
,但是我搞了一天都没有搞明白runit
怎么用,而且关于runit
的资料都是2017年之前的,就放弃了…
首先先在/opt/service
里准备两个服务,分别是app1
和app2
,在各自的文件家里分别创建run和finish两个文件,结构如下:
1
2
3
4
5
6
7
8
9
10[root@func-lcshop-Harbor opt]# tree service/
service/
├── app1
│ ├── finish
│ └── run
└── app2
├── finish
└── run
2 directories, 4 files
这个run文件就是进程的启动文件,app1/run
内容如下:
1
2
3
4
5
6
7
8
9
10
11
12[root@func-lcshop-Harbor app1]# cat run
#!/bin/bash
echo "Started APP1 service..."
for i in {1..3}
do
echo "这里是第一个程序!"
sleep 1
done
echo "Oh no!我嗝屁了..." >&2 #3秒就死掉
exit 1
而app2/run
的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12#!/bin/bash
echo "Started app2 service..."
for i in {1..3000} #这里懒,就直接写了3000秒
do
echo "这是第二个程序!" #这里做了区分
date
sleep 1
done
echo "Oh no!我嗝屁了..." >&2
exit 1
我上面主要是为了模拟程序挂掉的场景,实际run脚本应该是长时间运行的。
而finish的作用主要是执行程序退出后的操作,也就是run结束后的操作。这里要注意!因为s6会自动重启run脚本,如果在finish里也写了启动run脚本,那么就会有两个run脚本运行!
app1/finish
内容如下:
1
2
3#!/bin/bash
echo "app1 挂掉啦!..."
echo "app1 restart ok!"
然后我们准备一个dockerfile
,如下:
1
2
3
4FROM ubuntu
ADD https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-amd64.tar.gz /tmp/ #这个网站可能链接比较慢,所以推荐先下载然后COPY到容器里
RUN tar xzf /tmp/s6-overlay-amd64.tar.gz -C / #解压缩到根目录下
CMD ["/bin/s6-svscan","/opt/service"] #/opt/service就是app1和app2的路径
执行docker build -t="s6:0.1" .
创建镜像,再使用docker run -it --name s6demo -v /opt/service:/opt/service s6:0.1
启动这个s6demo的容器,在容器启动时就会发现s6会扫描/opt/service
文件夹,执行对应的run脚本,当run脚本意外退出时,s6会自动重启,如图:
参考资料
https://github.com/just-containers/s6-overlay#the-docker-way
https://sourcediver.org/blog/2014/11/17/using-runit-in-a-docker-container/
https://segmentfault.com/a/1190000006644578
https://it.ismy.fun/2018/03/09/runit-example/