Docker中应用s6-overlay
背景
在最开始使用Docker容器的时候,一直把Docker当作一个Linux虚拟机,后来用久了发现Docker只会起来一个特殊的进程Pid=1,Dockerfile里有两个关键字可以指定指令,ENTRYPOINT 和 CMD。当主进程退出的时候容器所拥有的PID命名空间就会被销毁,容器生命周期也会结束。
但是有时候需要在Docker容器中运行多个进程,基本做法用shell或者第三方守护进程(dumb-init、tini、Monit、Supervisor、skaware、s6-overlay、runit、Systemd)作为主进程运行其他程序。
S6介绍
s6 是skarnet(Laurent Bercot) 开发的一款轻量级守护进程套件
s6-overlay 是容器内部初始化s6的工具包,我在搜索基础镜像的时候在github上发现,看到readme描述我就被吸引了。
S6使用
这里主要介绍s6-overlay使用
s6-overlay生命周期:
step1:容器初始化会启动s6-svscan作为pid1
step2:s6-svscan扫描服务目录并由s6-supervise执行相应脚本和服务
1、使用修复所有权和权限/etc/fix-attrs.d
2、执行中包含的初始化脚本/etc/cont-init.d
3、将服务(/etc/services.d)复制到s6文件夹下,并向s6-supervise发出信号,并交由s6-supervise接管。
step3:收到docker stop,转送s6-supervise进程,停止服务并执行终结脚本/etc/cont-finish.d,确保内置服务不会成为僵尸进程。
依照文档描述,系统启动会扫描几个文件夹
/etc/fix-attrs.d : 权限脚本文件夹
/etc/cont-init.d :初始化脚本文件夹
/etc/services.d : 服务启动执行脚本文件夹
/etc/cont-finish.d : 服务结束执行脚本文件夹
脚本例子
权限脚本
linux下执行程序需要很多权限授权,例如文件挂载后需要对挂载文件进行赋予权限等,需要在容器启动后操作。s6-overlay提供了初始化脚本文件夹/etc/fix-attrs.d。匹配格式如下:
path(路径) recurse(是否嵌套) account(用户名) fmode(文件模式) dmode(目录模式)
path: 文件或路径
recurse: 如果时文件夹,是否嵌套所有子文件
account: 用户名。如果用户没找到就按照默认UID和GID
fmode: 文件模式。如:0644
dmode: 目录模式。如:0755
举例:
XML/HTML代码
- /var/lib/mysql true mysql 0600 0700
执行完权限脚本(/etc/fix-attrs.d/)后,在启动服务(/etc/services.d/)前可以做一些准备工作,如环境变量设置、文件夹创建等。
举例:
/etc/cont-init.d/02-confd-onetime:
XML/HTML代码
- #!/usr/bin/execlineb -P
- with-contenv
- s6-envuidgid nginx
- multisubstitute
- {
- import -u -D0 UID
- import -u -D0 GID
- import -u CONFD_PREFIX
- define CONFD_CHECK_CMD "/usr/sbin/nginx -t -c {{ .src }}"
- }
- confd --onetime --prefix="${CONFD_PREFIX}" --tmpl-uid="${UID}" --tmpl-gid="${GID}" --tmpl-src="/etc/nginx/nginx.conf.tmpl" --tmpl-dest="/etc/nginx/nginx.conf" --tmpl-check-cmd="${CONFD_CHECK_CMD}" etcd
服务启动脚本
自定义启动脚本
/etc/services.d/myapp/run:
XML/HTML代码
- #!/usr/bin/execlineb -P
- nginx -g "daemon off;"
自定义重启策略
守护进程默认是自动重启服务,如果想要不自动重启可以在finish脚本里写终止
/etc/services.d/myapp/finish:
XML/HTML代码
- #!/usr/bin/execlineb -S0
- s6-svscanctl -t /var/run/s6/services
可以实现更高级做法,当服务崩溃,就不再重启
/etc/services.d/myapp/finish:
XML/HTML代码
- #!/usr/bin/execlineb -S1
- if { s6-test ${1} -ne 0 }
- if { s6-test ${1} -ne 256 }
- s6-svscanctl -t /var/run/s6/services
日志输出
s6-overlay采用s6已经提供了开箱即用的s6-log,在这基础上提供了进一步封装logutil-service,主要提供以下几个功能
在s6-log中执行环境变量S6_LOGGING_SCRIPT中的脚本
移除权限,任何人都可以写文件,不需要再通过s6-setuidgid来启动
清除所有环境变量
初始化s6-log日志程序
在初始化时候创建日志文件夹和任意操作权限
/etc/cont-init.d/myapp-logfolder:
XML/HTML代码
- #!/bin/sh
- mkdir -p /var/log/myapp
- chown nobody:nogroup /var/log/myapp
输出stdin所有日志
/etc/services.d/myapp/log/run:
XML/HTML代码
- #!/bin/sh
- exec logutil-service /var/log/myapp
Docker基础镜像
Dockerfile文件参考smebberson/docker-alpine,我增加了镜像时区设置
DOCKERFILE
XML/HTML代码
- FROM alipine:3.9
- LABEL MAINTAINER=chobon@aliyun.com
- # Add s6-overlay
- ENV S6_OVERLAY_VERSION=v1.22.1.0 \
- GO_DNSMASQ_VERSION=1.0.7 \
- TIME_ZONE=Asia/Shanghai
- RUN apk add --update --no-cache bind-tools curl libcap && \
- curl -sSL https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-amd64.tar.gz \
- | tar xfz - -C / && \
- curl -sSL https://github.com/janeczku/go-dnsmasq/releases/download/${GO_DNSMASQ_VERSION}/go-dnsmasq-min_linux-amd64 -o /bin/go-dnsmasq && \
- chmod +x /bin/go-dnsmasq && \
- apk del curl && \
- # create user and give binary permissions to bind to lower port
- addgroup go-dnsmasq && \
- adduser -D -g "" -s /bin/sh -G go-dnsmasq go-dnsmasq && \
- setcap CAP_NET_BIND_SERVICE=+eip /bin/go-dnsmasq
- RUN ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime && \
- echo "${TIME_ZONE}" > /etc/timezone
- COPY root /
- ENTRYPOINT ["/init"]
- CMD []
root文件下目录结构
XML/HTML代码
- ├─ configroot
- │ ├─etc
- │ │ ├─fix.attrs.d
- │ │ │ ├─01-resolver-resolv
- │ │ ├─cont.init.d
- │ │ │ ├─30-resolver
- │ │ │ ├─40-resolver
- │ │ ├─services.d
- │ │ │ ├─resolver
- │ │ │ │ ├─run
- │ │ │ │ ├─finish
构建S6程序
这里拿Hexo作为守护服务
DOCKERFILE
XML/HTML代码
- FROM dobor/alpine-base:latest
- LABEL MAINTAINER=chobon@aliyun.com
- ENV HEXO_MODE=server
- # change ALIYUN apk source
- #RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
- RUN addgroup hexo && \
- adduser -D -g "" -s /bin/sh -G hexo hexo
- WORKDIR /home/hexo
- RUN apk --update --no-progress --no-cache add git nodejs npm openssh && \
- npm config set registry https://registry.npm.taobao.org && \
- npm install -g hexo-cli && \
- hexo init . && \
- npm install hexo-deployer-git && \
- #npm install hexo-generator-json-content && \
- npm install hexo-tag-aplayer && \
- npm install cheerio@0.22.0 && \
- npm install hexo-renderer-pug && \
- npm install hexo-renderer-stylus && \
- npm install hexo-wordcount && \
- npm install hexo-abbrlink && \
- rm -rf /var/cache/apk/*
- # copy local files
- ADD root /
- VOLUME /home/hexo/source /home/hexo/themes /home/hexo/.ssh
- RUN chown -R hexo .
- EXPOSE 4000
目录结构
新增/root文件结构
XML/HTML代码
- ├─ configroot
- │ ├─etc
- │ │ ├─fix.attrs.d
- │ │ │ ├─02-hexo
- │ │ ├─cont.init.d
- │ │ │ ├─10-hexo
- │ │ │ ├─20-hexo
- │ │ ├─services.d
- │ │ │ ├─hexo
- │ │ │ │ ├─run
权限
给git配置文件.config赋予访问权限
/etc/fix-attrs.d/02-hexo:
XML/HTML代码
- /root/.config true root 0666 0666
初始化
设置执行命令的HEXO_RUNAS环境变量
/etc/cont-init.d/10-hexo:
XML/HTML代码
- #!/usr/bin/with-contenv sh
- # Unless this has already been defined, set it.
- if [ -z "$HEXO_RUNAS" ]; then
- printf "hexo" > /var/run/s6/container_environment/HEXO_RUNAS
- fi
生成博客静态文件和启用插件
/etc/cont-init.d/20-hexo:
XML/HTML代码
- #!/usr/bin/with-contenv sh
- # Generate Blog
- exec s6-setuidgid $HEXO_RUNAS hexo g -f --cwd /home/hexo
- # Generate Douban Page
- exec s6-setuidgid $HEXO_RUNAS hexo douban --cwd /home/hexo
服务启动
通过环境变量HEXO_MODE来作为web站点服务还是发布静态文件到GitHub上。
/etc/services.d/run:
XML/HTML代码
- #!/usr/bin/with-contenv sh
- if [ $HEXO_MODE = 's' ] || [ $HEXO_MODE = 'server' ]; then
- # Start Hexo Server.
- exec s6-setuidgid $HEXO_RUNAS hexo server -p 4000 --cwd /home/hexo
- fi
- if [ $HEXO_MODE = 'd' ] || [ $HEXO_MODE = 'deploy' ]; then
- # Start Hexo Deploy.
- exec s6-setuidgid $HEXO_RUNAS hexo deploy --cwd /home/hexo
- fi
总结:
通过把s6整合进hexo镜像内部,使得hexo容器更新一个稳定的微型linux服务器,既保留容器占用资源低,启动快等特点,又使得容器内部可以执行多进程,可以整合更复杂的功能于一个容器内。
简单来说,在容器中安装的某个程序不能自动启动,可以通过s6写一个启动命令进去,从而启动系统的某个服务或者程序。
例如ssh server安装后重启容器就不能启动了,可以创建开机启动脚本/etc/cont-init.d/001-ssh.sh从而实现开机启动ssh server服务
XML/HTML代码
- echo '#!/bin/bash' > /etc/cont-init.d/001-ssh.sh
- echo '/etc/init.d/ssh start' >> /etc/cont-init.d/001-ssh.sh
- chmod +x /etc/cont-init.d/001-ssh.sh