你的Docker部署能更简单吗?GitLab集成与多进程管理又怎么做?

前言

文章开始前,先尝试回答几个问题:
  1. 在日常工作中,程序的打包和部署方式流程是怎样的?

  2. 如果使用Docker容器部署方式,是用原生Docker命令,还是Kaniko这类工具?

  3. 除了业务服务外,如果还需要同时运行其他进程,应该怎么办?举个例子:除了主服务进程(比如Web服务器),还需要同时启动Nginx进程(用于代理转发部分前端流量,解决跨域问题)。

以上几个问题,拆解:
  1. 属于 CI 集成的概念:GitLab和Docker的集成是一种主流方式,其他工具也大同小异。

  2. 与内核态和用户态有关。Kaniko是一种常用的容器镜像构建工具。

  3. 需要用到进程管理工具,在Docker容器中同时管理多个进程。

一个个来说。

GitLab 和 Docker 的集成

两者的集成主要有三种方式,手段大同小异,但核心目的都是把Docker打包镜像的流程放到GitLab的流水线中执行。可参见官 https://docs.gitlab.com/ee/ci/docker/using_docker_build.html:

 

以 shell executor 为例:

 

 

可见,做法很简单:将Docker命令作为流水线脚本执行即可,镜像打包出来后可用来部署。以近期热度较高的大模型服务为例,经典的集成架构如下图所示:

 

 

其他CI集成的主流程万变不离其宗,不同之处在于在此基础上做一些优化,比如缓存加速等。

常用的容器镜像构建工具:Kaniko

同样,先看官网 https://github.com/GoogleContainerTools/kaniko 定义:

 

可见,Kaniko 是一个用于构建容器镜像的工具,它允许我们从Dockerfile构建镜像,且不需要Docker守护进程。Kaniko executor 构建镜像的详细步骤如下:
  1. Kaniko Executor Image:Kaniko执行器镜像是一个特殊的容器镜像,它包含了所有必要的工具和依赖,以便在容器内部构建新的Docker镜像。

  2. Building an Image from a Dockerfile:用户提供一个Dockerfile,Kaniko执行器镜像将根据这个Dockerfile来构建一个新的镜像。Dockerfile包含了一系列的指令,这些指令定义了如何构建一个新Docker镜像,例如FROMRUNCOPYADD等。

  3. Extracting the Filesystem of the Base Image:在Dockerfile中,FROM 指令指定了基础镜像,Kaniko 首先需要提取这个基础镜像的文件系统。这个文件系统被复制到 Kaniko 执行器镜像的临时目录中,以便后续的构建过程可以在用户空间中对其进行操作。

  4. Executing Commands in the Dockerfile:Kaniko逐个执行Dockerfile中的指令。对于每个指令,Kaniko都会在用户空间中模拟该指令的效果。例如,如果指令是RUN apt-get update,Kaniko会在容器内部执行这个命令,就像在普通的Docker构建过程中一样。

  5. Snapshotting the Filesystem in Userspace:在每个指令执行之后,Kaniko 会在用户空间中对文件系统进行快照。这个快照捕获了自上一个快照以来文件系统的所有变化,这些变化包括新创建的文件、修改过的文件和删除的文件。

  6. Appending a Layer of Changed Files:如果在执行某个指令后文件系统发生了变化,Kaniko 会将这些变化作为一个新层添加到基础镜像上。这个过程是逐层进行的,每个指令可能对应一个或多个层,这取决于文件系统的变化。

  7. Updating Image Metadata:除了添加文件系统的变化之外,Kaniko还会更新镜像的元数据,包括标签、环境变量、工作目录等。这些元数据信息也是 Dockerfile 中指令的一部分,Kaniko会确保这些信息被正确地应用到新构建的镜像上。

  8. Pushing the Image to a Registry:一旦所有的指令都被执行完毕,并且所有的变化都被添加到镜像中,Kaniko 会将这个新构建的镜像推送到指定的容器镜像仓库(registry)。这个步骤需要提供仓库的认证信息,以便Kaniko能够成功地将镜像推送到仓库。
总的来说,Kaniko与原生Docker最大的不同之处在于:Kaniko在用户空间中模拟 Dockerfile中的指令,并逐层捕获文件系统的变化,最终构建出一个新的Docker镜像,而不需要Docker守护进程的参与。这种方法提高了安全性,并且可以在多种环境中灵活地构建容器镜像(也即:可以在没有root权限的环境中运行)。另外,在GitLab官网 https://docs.gitlab.com/ee/ci/docker/using_kaniko.html 也有 Kaniko 的使用方法描述:

 

在 Docker 容器中管理多个进程

现实中,大部分情况下容器中仅会有一个进程,也即1号进程。但总会有例外发生,以上文中的例子来说,某个容器中不仅需要主服务进程提供Web服务,还需要Nginx进程用于代理转发部分前端流量,以解决跨域问题。此时就需要同时启动并管理多个进程。有需求就有实现,目前多进程管理工具有很多了,一种常见的方案是使用Python编写的进程管理工具supervisord,它可以管理多个进程,包括启动、停止、重启等操作。针对上述例子,整体集成方式如下:
  1. 制作基础镜像:基于centos系统镜像,安装supervisord 和nginx。
/bin/sh -c yum install -y epel-release
/bin/sh -c yum install -y supervisor
/bin/sh -c yum install -y nginx-1.10.3
/bin/sh -c yum clean all

# 注意只列出了本文涉及到的几个命令,还有很多其他功能需要安装
# 可参考仓库 https://github.com/CentOS/sig-cloud-instance-images
# 该仓库包括了centos系统的各种镜像的构建命令,囊括了很多实用命令安装脚本


2. 编写 /etc/supervisord.conf,它是supervisord的主配置文件,定义了supervisord 进程管理器的全局设置和行为,确保所有被管理的进程都能按照预期的方式启动和运行。通过这个配置文件,可以集中管理多个服务,使得服务的部署和维护更加方便。

[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700

[supervisord]
logfile=/var/log/supervisor/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
loglevel=info
pidfile=/var/run/supervisord.pid
nodaemon=false
minfds=1024
minprocs=200

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[include]
files = /etc/supervisor/conf.d/*.ini

[unix_http_server]:定义了 supervisord 的 Unix 套接字 HTTP 服务器的配置,用于 supervisorctl 命令行工具或其他客户端与 supervisord 进行通信。

[supervisord]:包含了 supervisord 守护进程的全局配置,如日志文件位置、日志大小限制、日志备份数量、进程文件位置、最小文件描述符和进程数量等。

[rpcinterface:x]:定义了 RPC 接口的配置,允许远程管理 supervisord。这通常用于 supervisorctl 命令行工具。

[supervisorctl]:定义了 supervisorctl 命令行工具的配置,如连接到 supervisord 的 URL。

[include] :允许 supervisord 包含其他配置文件。这使得你可以将不同的程序配置在不同的文件中,然后由主配置文件统一管理。通常,这个节会包含 /etc/supervisor/conf.d/*.ini,这样 supervisord 就会自动加载 /etc/supervisor/conf.d/ 目录下的所有 .ini 文件。

  1. 为各工程使用方便,将 /etc/supervisor/conf.d/nginx.ini 提前打包到镜像中,后续只需配置 nginx.conf 即可。

 
[program:nginx]
command=/etc/nginx/sbin/nginx
user=xiaoxi666
priority=999
numprocs=1
autostart=true
autorestart=true
startsecs=1
startretries=3
stopsignal=KILL
stopwaitsecs=10
stdout_logfile=/var/log/supervisor/nginx.log
stderr_logfile=/var/log/supervisor/nginx.err
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=5
stopasgroup=true

[program:nginx]:定义了一个名为 nginx 的程序。supervisord 会根据这个名称来识别和控制这个进程。各个字段含义:

command:指定启动进程的命令。

user:指定以哪个用户身份运行 Nginx 进程。通常建议使用非 root 用户来运行服务以提高安全性。

priority:设置程序的启动优先级。较小的数字表示更高的优先级,即 supervisord 会先启动优先级数值较小的程序。

numprocs:指定应该启动多少个进程。这里设置为1,意味着只启动一个 Nginx 进程。

autostart:设置为 true 时,supervisord 会在启动时自动启动这个程序。

autorestart:设置为 true 时,如果 nginx 进程意外退出,supervisord 会自动重启它。

startsecs:指定 Nginx 进程启动后多少秒内被认为是稳定的。如果在这个时间内进程退出,supervisord 会认为启动失败。

startretries:如果 Nginx 进程在 startsecs 时间内退出,supervisord 会尝试重启它。这个值指定了最大重启次数。

stopsignal:指定停止 Nginx 进程时发送的信号。KILL 信号是一个强制终止进程的信号。

stopwaitsecs:指定 supervisord 等待 Nginx 进程停止的时间(以秒为单位)。如果超时,supervisord 会发送 KILL 信号。

stdout_logfile:指定 Nginx 进程的标准输出日志文件的位置。

stderr_logfile:指定 Nginx 进程的标准错误日志文件的位置。

stdout_logfile_maxbytes:设置标准输出日志文件的最大大小。当文件达到这个大小后,会滚动切分输出。

stdout_logfile_backups:设置标准输出日志文件的备份数量。当日志文件滚动切分时,旧的日志文件会被保留的备份数量。

stopasgroup:设置为 true 时,supervisord 会向整个进程组发送停止信号,这可以确保所有子进程都被正确终止。

做完这一步,该镜像就可以提供给“同时需要Web服务和Nginx服务”的工程使用了。

  1. 各工程编写自己的Dockerfile文件,将自定义的服务ini文件(假设名字为supervisor-app.ini)和 nginx.conf 文件复制到指定目录,然后启动supervisord。
# 定义环境变量
ENV SUPERVISOR_CONF_DIR=/etc/supervisor/conf.d
ENV NGINX_CONF_DIR=/etc/nginx/conf.d
ENV APP_DIR=/app
ENV PROJECT_NAME=xiaoxi666_demo_project

# 复制supervisord的配置文件到supervisord的配置目录
COPY supervisor-app.ini ${SUPERVISOR_CONF_DIR}/supervisor-app.ini

# 复制nginx配置文件到指定的nginx配置目录
COPY nginx.conf ${NGINX_CONF_DIR}/nginx.conf

# 复制当前目录下的所有文件到APP_DIR
COPY${APP_DIR}

# 创建项目目录并移动target目录下的内容到项目目录
RUN mkdir -p ${APP_DIR}/${PROJECT_NAME} \
    && mv ${APP_DIR}/target/* ${APP_DIR}/${PROJECT_NAME} \
    && chown -R xiaoxi666:xiaoxi666 ${APP_DIR}

# 暴露端口
EXPOSE 8080 其他要暴露的端口

# 启动supervisord
CMD ["/usr/bin/supervisord""-n"]
其中,supervisor-app.ini文件内容为主进程的启动命令,一个示例:
[program:xiaoxi666-demo-project]
command=java -jar 其他虚拟机参数 xiaoxi666-demo-project.jar
user=root
priority=999
numprocs=1
autostart=true
autorestart=true
startsecs=1
startretries=3
stopsignal=KILL
stopwaitsecs=20
stdout_logfile=%(ENV_LOG_DIR)/supervisor/xiaoxi666-demo-project.log
stderr_logfile=%(ENV_LOG_DIR)/supervisor/xiaoxi666-demo-project.err
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=5
stopasgroup=true

而 nginx.conf 根据实际代理需求配置即可。

整体来看,文件引用层级是这样的:

 

后记

本文以实际工程中的例子为切入点,围绕“Docker容器镜像构建”、“GitLab集成部署”,以及“容器多进程管理”这几个侧重点做了一些探讨和梳理,同时给出了对应的官方参考资料链接,有兴趣的读者可进一步深入学习。也欢迎大家留言,谈谈你所经历的容器集成部署方式是怎样的。

posted @   xiaoxi666  阅读(61)  评论(0编辑  收藏  举报
TOP
点击右上角即可分享
微信分享提示