记录我的一些生活写照、无聊的牢骚、内心世界的活动 注册 | 登陆

Docker中应用s6-overlay

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代码
  1. /var/lib/mysql true mysql 0600 0700
初始化脚本
执行完权限脚本(/etc/fix-attrs.d/)后,在启动服务(/etc/services.d/)前可以做一些准备工作,如环境变量设置、文件夹创建等。
举例:
/etc/cont-init.d/02-confd-onetime:
XML/HTML代码
  1. #!/usr/bin/execlineb -P  
  2. with-contenv  
  3. s6-envuidgid nginx  
  4. multisubstitute  
  5. {  
  6.   import -u -D0 UID  
  7.   import -u -D0 GID  
  8.   import -u CONFD_PREFIX  
  9.   define CONFD_CHECK_CMD "/usr/sbin/nginx -t -c {{ .src }}"  
  10. }  
  11. 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代码
  1. #!/usr/bin/execlineb -P
  2. nginx -g "daemon off;"
自定义重启策略
守护进程默认是自动重启服务,如果想要不自动重启可以在finish脚本里写终止
/etc/services.d/myapp/finish:
XML/HTML代码
  1. #!/usr/bin/execlineb -S0  
  2. s6-svscanctl -t /var/run/s6/services  
可以实现更高级做法,当服务崩溃,就不再重启
/etc/services.d/myapp/finish:
XML/HTML代码
  1. #!/usr/bin/execlineb -S1  
  2. if { s6-test ${1} -ne 0 }  
  3. if { s6-test ${1} -ne 256 }  
  4. 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代码
  1. #!/bin/sh  
  2. mkdir -p /var/log/myapp  
  3. chown nobody:nogroup /var/log/myapp
输出stdin所有日志
/etc/services.d/myapp/log/run:
XML/HTML代码
  1. #!/bin/sh  
  2. exec logutil-service /var/log/myapp  
Docker基础镜像
Dockerfile文件参考smebberson/docker-alpine,我增加了镜像时区设置
DOCKERFILE
XML/HTML代码
  1. FROM alipine:3.9  
  2. LABEL MAINTAINER=chobon@aliyun.com  
  3. # Add s6-overlay  
  4. ENV S6_OVERLAY_VERSION=v1.22.1.0 \  
  5.     GO_DNSMASQ_VERSION=1.0.7 \  
  6.     TIME_ZONE=Asia/Shanghai  
  7. RUN apk add --update --no-cache bind-tools curl libcap && \  
  8.     curl -sSL https://github.com/just-containers/s6-overlay/releases/download/${S6_OVERLAY_VERSION}/s6-overlay-amd64.tar.gz \  
  9.     | tar xfz - -C / && \  
  10.     curl -sSL https://github.com/janeczku/go-dnsmasq/releases/download/${GO_DNSMASQ_VERSION}/go-dnsmasq-min_linux-amd64 -o /bin/go-dnsmasq && \  
  11.     chmod +x /bin/go-dnsmasq && \  
  12.     apk del curl && \  
  13.     # create user and give binary permissions to bind to lower port  
  14.     addgroup go-dnsmasq && \  
  15.     adduser -D -g "" -s /bin/sh -G go-dnsmasq go-dnsmasq && \  
  16.     setcap CAP_NET_BIND_SERVICE=+eip /bin/go-dnsmasq  
  17. RUN ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime && \  
  18.     echo "${TIME_ZONE}" > /etc/timezone  
  19. COPY root /  
  20. ENTRYPOINT ["/init"]  
  21. CMD []  
root文件下目录结构
XML/HTML代码
  1. ├─ configroot  
  2. │  ├─etc  
  3. │  │  ├─fix.attrs.d  
  4. │  │  │  ├─01-resolver-resolv  
  5. │  │  ├─cont.init.d  
  6. │  │  │  ├─30-resolver  
  7. │  │  │  ├─40-resolver  
  8. │  │  ├─services.d  
  9. │  │  │  ├─resolver  
  10. │  │  │  │  ├─run  
  11. │  │  │  │  ├─finish  
构建S6程序
这里拿Hexo作为守护服务
DOCKERFILE
XML/HTML代码
  1. FROM dobor/alpine-base:latest  
  2. LABEL MAINTAINER=chobon@aliyun.com  
  3.   
  4. ENV HEXO_MODE=server  
  5.   
  6. # change ALIYUN apk source  
  7. #RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories  
  8.   
  9. RUN addgroup hexo && \  
  10.     adduser -D -g "" -s /bin/sh -G hexo hexo  
  11.   
  12. WORKDIR /home/hexo  
  13.   
  14. RUN apk --update --no-progress --no-cache add git nodejs npm openssh && \  
  15.     npm config set registry https://registry.npm.taobao.org && \  
  16.     npm install -g hexo-cli && \  
  17.     hexo init . && \  
  18.     npm install hexo-deployer-git && \  
  19.     #npm install hexo-generator-json-content && \  
  20.     npm install hexo-tag-aplayer && \  
  21.     npm install cheerio@0.22.0 && \  
  22.     npm install hexo-renderer-pug && \  
  23.     npm install hexo-renderer-stylus && \  
  24.     npm install hexo-wordcount && \  
  25.     npm install hexo-abbrlink && \  
  26.     rm -rf /var/cache/apk/*  
  27.   
  28. # copy local files  
  29. ADD root /  
  30.   
  31. VOLUME /home/hexo/source /home/hexo/themes /home/hexo/.ssh  
  32.   
  33. RUN chown -R hexo .  
  34.   
  35. EXPOSE 4000  
目录结构
新增/root文件结构
XML/HTML代码
  1. ├─ configroot  
  2. │  ├─etc  
  3. │  │  ├─fix.attrs.d  
  4. │  │  │  ├─02-hexo  
  5. │  │  ├─cont.init.d  
  6. │  │  │  ├─10-hexo  
  7. │  │  │  ├─20-hexo  
  8. │  │  ├─services.d  
  9. │  │  │  ├─hexo  
  10. │  │  │  │  ├─run  
权限
给git配置文件.config赋予访问权限
/etc/fix-attrs.d/02-hexo:
XML/HTML代码
  1. /root/.config true root 0666 0666  
初始化
设置执行命令的HEXO_RUNAS环境变量
/etc/cont-init.d/10-hexo:
XML/HTML代码
  1. #!/usr/bin/with-contenv sh  
  2. # Unless this has already been defined, set it.  
  3. if [ -z "$HEXO_RUNAS" ]; then  
  4.     printf "hexo" > /var/run/s6/container_environment/HEXO_RUNAS  
  5. fi  
生成博客静态文件和启用插件
/etc/cont-init.d/20-hexo:
XML/HTML代码
  1. #!/usr/bin/with-contenv sh  
  2. # Generate Blog  
  3. exec s6-setuidgid $HEXO_RUNAS hexo g -f --cwd /home/hexo  
  4. # Generate Douban Page  
  5. exec s6-setuidgid $HEXO_RUNAS hexo douban --cwd /home/hexo  
服务启动
通过环境变量HEXO_MODE来作为web站点服务还是发布静态文件到GitHub上。
/etc/services.d/run:
XML/HTML代码
  1. #!/usr/bin/with-contenv sh  
  2. if [ $HEXO_MODE = 's' ] || [ $HEXO_MODE = 'server' ]; then  
  3.     # Start Hexo Server.  
  4.     exec s6-setuidgid $HEXO_RUNAS hexo server -p 4000 --cwd /home/hexo  
  5. fi  
  6. if [ $HEXO_MODE = 'd' ] || [ $HEXO_MODE = 'deploy' ]; then  
  7.     # Start Hexo Deploy.  
  8.     exec s6-setuidgid $HEXO_RUNAS hexo deploy --cwd /home/hexo  
  9. fi  
总结:
通过把s6整合进hexo镜像内部,使得hexo容器更新一个稳定的微型linux服务器,既保留容器占用资源低,启动快等特点,又使得容器内部可以执行多进程,可以整合更复杂的功能于一个容器内。
参考 参考 Github Dockerhub:alpine-s6 debian-s6 ubuntu-s6 s6-overlay
简单来说,在容器中安装的某个程序不能自动启动,可以通过s6写一个启动命令进去,从而启动系统的某个服务或者程序。
例如ssh server安装后重启容器就不能启动了,可以创建开机启动脚本/etc/cont-init.d/001-ssh.sh从而实现开机启动ssh server服务
XML/HTML代码
  1. echo '#!/bin/bash' > /etc/cont-init.d/001-ssh.sh
  2. echo '/etc/init.d/ssh start' >> /etc/cont-init.d/001-ssh.sh
  3. chmod +x /etc/cont-init.d/001-ssh.sh
想启动其他程序可以自己写入启动命令。

« 上一篇 | 下一篇 »

发表评论

评论内容 (必填):