Docker 构建多进程容器 SpringBoot + Redis

  • 背景
  • 安装 Docker
  • 编写 Dockerfile
  • Redis
  • Supervisor
  • yum 安装 supervisor
  • 配置多进程
  • Dockerfile 剩余片段
  • 用 Shell 来统一管理命令集
  • 修订记录


背景

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。

一个微服务架构用到了 Redis,想尝试下用 Docker 部署。虽然官方推荐一个 Docker 容器运行一个进程,但是这次受限于资源本身和考虑到部署的便利程度,干脆就部署到一起好了。
关键词:Dockerfile,Shell,Supervisor,Redis

安装 Docker

docker 构建脚本 安装。手动执行命令如下1

# 获取 Shell 文件
curl -fsSL https://get.docker.com -o get-docker.sh
# 执行
sh get-docker.sh
# 启动服务
systemctl start docker

编写 Dockerfile

自己手写 Dockerfile 构建镜像2
文件名 Dockerfile,首先引入基础镜像(BASE-IMAGE),可以用更轻量级的镜像,我用的是 centos:7。

FROM centos:7

实际上这一句会执行最小化构建镜像,所以安装完啥都没有,一些基础命令需要自己手动装。这里需要用到 Dockerfile 的指令 RUN,为了尽可能的压缩镜像大小,建议将所有 RUN 命令合并(&& 用于连接,\ 用于换行) 3
下面开始一步一步构建一个容器:

RUN && \
# 首先导入 GPG-KEY 公钥(GPG在Linux上的应用主要是实现官方发布的包的签名机制)
# 避免 "Could not open/read file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL" 的错误
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-* && \
# 设置时区
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
# 安装 JDK
yum -y install java-1.8.0-openjdk-devel.x86_64 && \

Redis

编译 Redis 的代码片段

yum -y install wget && \
yum -y install gcc && yum -y install automake autoconf libtool make && \
yum -y install net-tools && \
yum -y install tar && \
cd /usr/local && \
wget http://download.redis.io/releases/redis-3.2.13.tar.gz && \
tar -xvzf redis-3.2.13.tar.gz && \
mv redis-3.2.13/ redis &&  rm -rf redis-3.2.13.tar.gz && \
cd redis && \
make && make install && \

Supervisor

Supervisor 是用 Python 开发的一个 client / serve r服务,是 Linux / Unix 系统下的一个进程管理工具

在本次场景中用于在一个 Docker 容器中运行多个进程。

yum 安装 supervisor

# CentOS7 自带的 yum repo 没有 supervisor,需要安装 epel repo
yum install -y epel-release && \
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 && \
yum install -y supervisor

配置多进程

安装完 supervisor 后,生成默认配置文件:

echo_supervisord_conf > /etc/supervisord.conf

大部分配置都沿用了默认的,具体的请查阅[官方文档](http://www.supervisord.org/index.html)
然后编辑配置文件,这里我们需要启用2个进程,将他们的配置文件分开,方便管理:

; 用于指明子进程配置文件位置
[include]
files = supervisord_redis_serverd.conf supervisord_finance_serverd.conf

子进程的文件配置相对简单:
摘录了部分核心部分,以下是启动 SpringBoot jar 程序的片段。
其中,%(ENV_APP_NAME)s 为 supervisor 语法,意思是获取环境变量 APP_NAME;finance_serverd 为子进程名

[program:finance_serverd]
command=java -jar /usr/local/app/%(ENV_APP_NAME)s
autostart=true

Dockerfile 剩余片段

主要完成以下工作:

  1. 将预定义的主机配置文件添加到 Docker 容器中
  2. 将 SpringBoot 主程序添加到 Docker 容器中
  3. 配置环境变量
  4. 分别对主机暴露 Redis 和 SpringBoot 主程序的接口
  5. 运行 supervisor
# Config config-file
ADD ./config/redis.conf /etc/redis/
ADD ./config/supervisord.conf /etc/supervisor/
ADD ./config/supervisord_finance_serverd.conf /etc/supervisor/
ADD ./config/supervisord_redis_serverd.conf /etc/supervisor/
ADD ./app/$ARG_APP_FILE_NAME /usr/local/app/$ARG_APP_FILE_NAME
# COnfig Java
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64
ENV JRE_HOME ${JAVA_HOME}/jre
ENV APP_NAME $ARG_APP_FILE_NAME

# Expose Port (redis:6379, finance-server:8070)
EXPOSE 8070 6379
# Run Docker
ENTRYPOINT ["supervisord", "-nc", "/etc/supervisor/supervisord.conf"]

用 Shell 来统一管理命令集

到此为止,一个同时支持2个进程的 Docker 容器脚本就编写完了。
为了进一步简化部署成本,用 Shell 文件来封装一些常用的操作,并将相关文件归集成一个部署套件。
于主机 /usr/local/docker 下,定义一个文件结构:

./app/
./config/
./Dockerfile
./sh/

文件夹 app 为存放 SpringBoot 核心 jar 程序;
文件夹 config 保存所有容器需要的配置文件,如 redis.conf, supervisord.conf …
Dockerfile 为 docker 镜像构建脚本;
文件夹 sh 存放 Shell 文件:

docker-1-build.sh # 安装 Docker
docker-2-image-build.sh # 依托 Dockerfile 构建镜像
docker-3-container-build.sh # 启动容器

值得一提的是构建镜像时的命令为:

docker build \
 -f ../Dockerfile \
 -t $DOCKER_IMAGE_NAME:latest \
 --build-arg ARG_APP_FILE_NAME=$APP_FILE_NAME ../

经常见到 build 命令后为一个 .
这部分实际上是表示构建镜像的 Docker 上下文,Docker 构建时,会将上下文下的所有文件读入,所以构建脚本中的 ADD & COPY 其实并不是找的主机目录下的文件,而是这个相对于这个上下文下。
这里之所以用 …/ 因为执行 build 的 shell 在 ./sh/,所以上下文实际上应该是 …/ 指向的上一层。

修订记录

  • 2019年8月9日16:46:24 - The very first version.

  1. 这里是在 root 用户下,不过也可以给其他用户安装,详情翻阅 官方文档 ↩︎
  2. 镜像和容器的关系呢,就类似于 JAVA 的类与对象 ↩︎
  3. Dockerfile 中,每一条指令都会创建一个镜像层,继而增大整体镜像 ↩︎