写在前面
本文以部署本地开发环境,以实现快速部署为基准、方便开发为前提做说明
背景
本人在实际工作过程中,使用 hyperf 框架开发时遇到这种情况:
- 同时开发多个项目
- 同时使用不同的 hyperf 框架版(2.x、3.x)
- 不同项目对接不同的数据库(Mysql、Oracle、Sqlserver)
遇到的问题有很多,比如:
- 2.x 和 3.x 对 php 版本和 swoole 版本不一样
- 每个 php 版本需要单独安装对应的 扩展
- 不同的 php 版本,composer 需要重新安装
- 项目中使用 composer 添加扩展包时,还要对应自己的项目版本使用正确的 composer
- ... ... ...
这一套下来,耗费时间长,如果换台设备,重新部署也很麻烦;
所以,考虑到使用容器开发来解决当下的困局 ... ... ...
一、准备工作
环境准备
基于本人开发环境做说明
- 一个 linux 环境、已安装好 docker、docker-compose
- CentOS 版本:
CentOS Linux release 7.9.2009 (Core) - docker 版本:
Docker version 24.0.5, build ced0996 - docker-compos 版本:
Docker Compose version v2.20.2
- CentOS 版本:
二、开始部署(基础)
本文以 php7.4 + hyperf2.x 做开发环境的说明
准备 Dockerfile
dockerfile
FROM php:7.4-cli-bullseye
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 更新并安装基础依赖
RUN apt-get update && apt-get install -y \
--no-install-recommends libfreetype6-dev libjpeg62-turbo-dev libpng-dev libzip-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# 安装 redis 和 swoole 扩展
RUN pecl install redis-5.3.7 swoole-4.8.13
# 移动 php.ini 配置文件
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# 启用扩展
RUN docker-php-ext-enable redis swoole
# 配置 GD 扩展
RUN docker-php-ext-configure gd --with-jpeg=/usr/include --with-freetype=/usr/include/freetype2/
# 安装 PHP 扩展
RUN docker-php-ext-install -j$(nproc) pcntl gd zip pdo_mysql opcache
# 添加自定义 PHP 配置
RUN echo "swoole.use_shortname='Off'" >> /usr/local/etc/php/conf.d/default.ini \
&& echo "memory_limit=1G" >> /usr/local/etc/php/conf.d/default.ini \
&& echo "opcache.enable_cli='on'" >> /usr/local/etc/php/conf.d/default.ini
# 安装 Composer
ENV COMPOSER_HOME /root/composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENV PATH $COMPOSER_HOME/vendor/bin:$PATH
# 指定工作目录,如果使用docker exec进入容器时,默认目录就是指定的工作目录,如/data
WORKDIR /opt/www
针对这份文档,做一些小说明:
- 基础镜像使用
-bullseye版本:有些基础依赖在其他版本已经不做维护了,需要自己设置对应的源地址,比较麻烦,而该版本基于最新的 debain 做兼容的 - 该文件没有设置启动命令:是为了给多个项目自己的启动命令留空间
构建新镜像
shell
# 执行构建
docker build -t 镜像名称:标签 .
# 检查新镜像
docker images
容器编排
docker-compose.yml
yaml
version: "3.9"
services:
php:
image: php-hyperf:7.4-cli-bullseye
volumes:
- /etc/localtime:/etc/localtime
- /www:/opt/www
restart: always
container_name: hyperf01
# networks:
# - php-net
# ports:
# - 9505:9501
network_mode: "host"
tty: true
stdin_open: true
# command: ["php", "bin/hyperf.php", "start"]
#networks:
# php-net:
# external:
# name: php-net
针对这份文档,做一些小说明:
一般情况下,使用 docker-compose 编排容器是这样的:
yamlversion: "3.9" services: php: image: php-hyperf:7.4-cli-bullseye volumes: - /etc/localtime:/etc/localtime - /www/hyperf01:/opt/www/hyperf01 restart: always container_name: hyperf01 networks: - php-net ports: - 9505:9501 command: ["php", "bin/hyperf.php", "start"] networks: php-net: external: name: php-net
image: Dockerfile 构建出来的镜像名volumes: 挂载目录- 开发环境目录结构解释:/www 是项目目录,里面包含多个项目文件夹
- 此处挂载 /www ,是为了方便只使用一个 php 容器,可以运行多个 hyperf 项目
command: 容器启动时的执行命令- 一般情况下,我们挂载了指定的项目名称,在容器启动时需要指定启动命令
- 但是,目前挂载的 /www,没有指定的项目,如果使用
command: ["php", "bin/hyperf.php", "start"]容器会启动不成功 - 所以,我们在这里不能使用 command 启动命令,也是为了方便多项目各自启动
tty、stdin_open:- Dockerfile、docker-compose 中都没有了启动命令、容器启动不成功
- 所以,追加这两个参数,允许容器可以成功启动,容器不退出
ports: 端口映射- 为了能直接使用每个项目自己配置的端口
- 因为为了方便扩展(后续追加更多的项目)
- 每个项目都有自己的端口号,如果在这里设置,那么每增加一个项目,就需要修改一次端口映射,并重新编排容器,很不方便
- 所以就取消了这里的端口映射
network_mode: 网络模式- 固定值:
host,容器共享宿主机网络,可以实现不用配置端口号 - 与
ports参数不共存 - 使用了共享网络模式,那么就不再需要配置新的网络了,就取消了
networks相关的参数配置
- 固定值:
执行容器编排
shell
# 后台运行,创建容器并启动
docker-compose up -d
三、启动并运行项目
当我们启动好容器后、进入php容器中
shell
docker exec -it hyperf01 bash
此时可以看到我们的项目目录 /opt/www ,因为这里包含了所有的项目目录,找到你自己的使用 php7.x 的项目,启动即可,启动成功后可以看到端口号就是你再项目中配置的端口号了。
至此,便可以愉快的开启你的牛马生活了 ... ... ...
四、提升
前面,我们提供了一个完整的可直接享用的 Dockerfile 文件,虽然是方便食用,但是问题也随之出现了:
- 当前是 php7.x 如果我们要换成 php8.x 怎么办呢?对应的 swoole 扩展版本 也需要修改
- 如果我们需要追加其他扩展怎么办呢?
- ... ... ...
如果按照当前的模式,每次处理这些问题,都需要去修改 Dockerfile 文件,这样就搞得很麻烦?有没有一个简单有效的办法呢???
有、有、有,,,那就是把这些可变信息,提炼成配置文件,Dockerfile 使用读配置的方式进行,那么我们就不用频繁的修改 Dockerfile 文件了,每次有变动,那就直接修改配置文件,简单直接、方便快速!!!
那么,我现在提供一套可开箱即用的配置方案,至于中间的踩坑过程就直接略过,当然,方案实现不止这么一种,大家可以自行发掘 ... ... ...
目录结构
我们以 project 作为根目录做说明
shell
project
|--config
|--build.conf
|--pecl
|--source
|--alpine_source.list
|--debian_source.list
|--build.sh
|--Dockerfile.base
|--Dockerfile.final
project:项目根目录project/config:构建配置文件目录project/config/build.conf:构建配置文件,方便修改构建信息project/pecl:本地下载扩展目录,方便从本地安装扩展,例如:swoole-5.1.3.tarproject/source:自定义 alpine、debian 配置源目录project/source/alpine_source.list:自定义 alpine 系统源配置信息project/source/debian_source.list:自定义 debian 系统源配置信息project/build.sh:构建执行脚本 需要有执行权限project/Dockerfile.base:用于构建基础镜像,只做 apline、debian 系统依赖和工具等更新project/Dockerfile.final:用于构建最终镜像,分阶段:一阶段主要功能 --- 安装扩展;二阶段主要功能 --- 构建最终镜像
文件内容及说明
build.conf
shell
# PHP 基础镜像
[base_image]
php:8.2-cli
#php:8.2-alpine
#php:7.4-cli
#php:7.4-alpine
# PECL 扩展(pecl install)
[pecl]
#swoole-4.8.13
#redis-5.3.7
swoole-5.1.3
redis
# 本地扩展(docker-php-ext-install)
[local]
pcntl
gd
zip
pdo_mysql
opcache
# 构建模式:prod / dev
[mode]
dev
# 是否使用自定义依赖源地址 true / false
[custom_source]
false
# composer 源:auto / aliyun / tencent / ustc / packagist
[composer_mirror]
auto
# php.ini 配置
[php]
swoole.use_shortname=Off
memory_limit=1G
opcache.enable_cli=on
# 构建目标:base / final / all
[build_target]
all
# 是否强制重新构建基础镜像:true / false
[force_rebuild_base]
false
alpine_source.list
shell
# 阿里云源
https://mirrors.aliyun.com/alpine/v3.18/main
https://mirrors.aliyun.com/alpine/v3.18/community
# 腾讯云源
# https://mirrors.cloud.tencent.com/alpine/v3.18/main
# https://mirrors.cloud.tencent.com/alpine/v3.18/community
# 清华源
# https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.18/main
# https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.18/community
# 官方源(备用)
# https://dl-cdn.alpinelinux.org/alpine/v3.18/main
# https://dl-cdn.alpinelinux.org/alpine/v3.18/community
debian_source.list
shell
# 阿里云源
deb http://mirrors.aliyun.com/debian/ bullseye main non-free contrib
deb http://mirrors.aliyun.com/debian-security/ bullseye-security main
deb http://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib
deb http://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib
# 腾讯云源
# deb http://mirrors.cloud.tencent.com/debian/ bullseye main non-free contrib
# deb http://mirrors.cloud.tencent.com/debian-security/ bullseye-security main
# deb http://mirrors.cloud.tencent.com/debian/ bullseye-updates main non-free contrib
# deb http://mirrors.cloud.tencent.com/debian/ bullseye-backports main non-free contrib
# 清华源
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free
# deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free
# deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free
# 官方源(备用)
# deb http://deb.debian.org/debian bullseye main contrib non-free
# deb http://deb.debian.org/debian-security/ bullseye-security main contrib non-free
# deb http://deb.debian.org/debian bullseye-updates main contrib non-free
Dockerfile.base
dockerfile
# =============================================================================
# PHP 基础镜像 Dockerfile
# 功能:安装系统依赖、编译工具和常用库,为后续扩展安装做准备
# =============================================================================
# 构建参数
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
# 系统类型参数
ARG SYSTEM_TYPE
ARG CUSTOM_SOURCE
# 设置工作目录
WORKDIR /tmp/build
# 设置环境变量防止 OOM
ENV MAKEFLAGS="-j2"
ENV COMPOSER_MEMORY_LIMIT=-1
# 复制源配置文件
COPY source/${SYSTEM_TYPE}_source.list /tmp/source.list
# 安装基础依赖和编译工具
RUN set -eux; \
echo "========================================"; \
echo "开始构建 PHP 基础镜像"; \
echo "系统类型: ${SYSTEM_TYPE}"; \
echo "自定义源: ${CUSTOM_SOURCE}"; \
echo "========================================"; \
\
# Alpine 系统配置
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
echo "配置 Alpine 源..."; \
if [ "$CUSTOM_SOURCE" = "true" ]; then \
echo "使用自定义源"; \
cat /tmp/source.list > /etc/apk/repositories; \
fi; \
\
echo "更新软件包索引..."; \
apk update || { echo "APK 更新失败"; exit 1; }; \
\
echo "安装基础工具..."; \
apk add --no-cache \
bash \
curl \
wget \
git \
vim \
unzip || { echo "基础工具安装失败"; exit 1; }; \
\
echo "安装开发库..."; \
apk add --no-cache \
libzip-dev \
zlib-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libwebp-dev || { echo "开发库安装失败"; exit 1; }; \
\
echo "安装 XPM 和加密库..."; \
apk add --no-cache \
libxpm-dev \
openssl-dev \
curl-dev || { echo "XPM/加密库安装失败"; exit 1; }; \
\
echo "安装 XML 和数据库库..."; \
apk add --no-cache \
libxml2-dev \
oniguruma-dev \
sqlite-dev \
postgresql-dev || { echo "XML/数据库库安装失败"; exit 1; }; \
\
echo "安装编译工具..."; \
apk add --no-cache \
autoconf \
g++ \
make \
linux-headers || { echo "编译工具安装失败"; exit 1; }; \
\
echo "配置 GD 扩展依赖..."; \
docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp || { echo "GD 配置失败,继续..."; }; \
\
# Debian 系统配置
else \
echo "配置 Debian 源..."; \
if [ "$CUSTOM_SOURCE" = "true" ]; then \
echo "使用自定义源"; \
cat /tmp/source.list > /etc/apt/sources.list; \
fi; \
\
echo "更新软件包索引..."; \
apt-get update || { echo "APT 更新失败"; exit 1; }; \
\
echo "安装基础工具和编译依赖..."; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl \
wget \
git \
vim \
unzip \
libzip-dev \
zlib1g-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libwebp-dev \
libxpm-dev \
libssl-dev \
libcurl4-openssl-dev \
libxml2-dev \
libonig-dev \
libsqlite3-dev \
libpq-dev \
autoconf \
g++ \
make || { echo "Debian 包安装失败"; exit 1; }; \
\
echo "配置 GD 扩展依赖..."; \
docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp || { echo "GD 配置失败,继续..."; }; \
\
echo "清理 APT 缓存..."; \
rm -rf /var/lib/apt/lists/*; \
fi; \
\
# 清理临时文件
rm -rf /tmp/*; \
\
echo "========================================"; \
echo "基础镜像构建完成"; \
echo "========================================";
# 健康检查
RUN php -v && php -m
# 设置默认工作目录
WORKDIR /var/www/html
-
该文件注释已标注在文档中
-
简要说明
- 执行该文件,会生成类似于 php-base:8.2-debian php-base:8.2-alpine 的基础镜像
- 基础镜像的生成规则
-
镜像名称规则:固定值 php-base
-
tag 标签生成规则:自动读取配置文件中的 基础镜像名,获取版本号 和 对应的系统信息(debian/alpine)作为 tag 名称
-
Dockerfile.final
dockerfile
# =============================================================================
# PHP 最终镜像 Dockerfile(多阶段构建)
# 功能:基于基础镜像安装 PHP 扩展、配置 PHP 和 Composer
# =============================================================================
# 构建参数
ARG BASE_IMAGE_NAME
FROM ${BASE_IMAGE_NAME}
# 扩展和配置参数
ARG SYSTEM_TYPE
ARG PECL_EXTENSIONS=""
ARG LOCAL_EXTENSIONS=""
ARG PHP_INI_CONFIGS=""
ARG COMPOSER_MIRROR="auto"
ARG BUILD_MODE="dev"
# 设置工作目录
WORKDIR /tmp/build
# 复制本地扩展目录(如果存在)
COPY pecl/ /tmp/pecl/
# 安装 PHP 扩展
RUN set -eux; \
echo "========================================"; \
echo "开始安装 PHP 扩展"; \
echo "系统类型: ${SYSTEM_TYPE}"; \
echo "PECL 扩展: ${PECL_EXTENSIONS}"; \
echo "LOCAL 扩展: ${LOCAL_EXTENSIONS}"; \
echo "构建模式: ${BUILD_MODE}"; \
echo "========================================"; \
\
# 定义重试函数
retry_command() { \
local max_attempts=3; \
local attempt=1; \
local cmd="$@"; \
while [ $attempt -le $max_attempts ]; do \
echo "尝试执行 (${attempt}/${max_attempts}): ${cmd}"; \
if eval "$cmd"; then \
echo "执行成功"; \
return 0; \
fi; \
echo "执行失败,等待 5 秒后重试..."; \
sleep 5; \
attempt=$((attempt + 1)); \
done; \
echo "执行失败,已达到最大重试次数"; \
return 1; \
}; \
\
# 安装 LOCAL 扩展
if [ -n "$LOCAL_EXTENSIONS" ]; then \
echo "----------------------------------------"; \
echo "安装 LOCAL 扩展: $LOCAL_EXTENSIONS"; \
echo "----------------------------------------"; \
\
# 特殊扩展依赖处理
for ext in $LOCAL_EXTENSIONS; do \
case "$ext" in \
gd) \
echo "检测到 GD 扩展,已在基础镜像配置"; \
;; \
zip) \
echo "检测到 ZIP 扩展"; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
apk add --no-cache libzip-dev; \
fi; \
;; \
pdo_mysql) \
echo "检测到 PDO_MYSQL 扩展"; \
;; \
opcache) \
echo "检测到 OPCACHE 扩展"; \
;; \
pcntl) \
echo "检测到 PCNTL 扩展"; \
;; \
*) \
echo "扩展: $ext"; \
;; \
esac; \
done; \
\
# 一次性安装所有 LOCAL 扩展
if ! retry_command "docker-php-ext-install -j\$(nproc) $LOCAL_EXTENSIONS"; then \
echo "LOCAL 扩展安装失败"; \
exit 1; \
fi; \
echo "LOCAL 扩展安装成功"; \
fi; \
\
# 安装 PECL 扩展
if [ -n "$PECL_EXTENSIONS" ]; then \
echo "----------------------------------------"; \
echo "安装 PECL 扩展: $PECL_EXTENSIONS"; \
echo "----------------------------------------"; \
\
echo "$PECL_EXTENSIONS" | tr ',' '\n' | while read -r ext; do \
# 跳过空行
[ -z "$ext" ] && continue; \
\
ext_name=$(echo "$ext" | cut -d'-' -f1); \
echo "处理扩展: $ext_name (完整名称: $ext)"; \
\
# 检查本地扩展目录
if [ -f "/tmp/pecl/${ext}.tgz" ]; then \
echo "从本地安装: ${ext}"; \
if ! retry_command "pecl install /tmp/pecl/${ext}.tgz"; then \
echo "从本地安装失败,尝试从 PECL 仓库安装"; \
if ! retry_command "pecl install $ext"; then \
echo "扩展 $ext 安装失败"; \
exit 1; \
fi; \
fi; \
else \
echo "从 PECL 仓库安装: ${ext}"; \
if ! retry_command "pecl install $ext"; then \
echo "扩展 $ext 安装失败"; \
exit 1; \
fi; \
fi; \
\
# 启用扩展
echo "启用扩展: $ext_name"; \
docker-php-ext-enable "$ext_name"; \
done; \
echo "PECL 扩展安装成功"; \
fi; \
\
# 验证扩展安装
echo "----------------------------------------"; \
echo "验证已安装的扩展:"; \
php -m; \
echo "----------------------------------------";
# 配置 PHP.ini
RUN set -eux; \
if [ -n "$PHP_INI_CONFIGS" ]; then \
echo "========================================"; \
echo "配置 PHP.ini"; \
echo "========================================"; \
\
echo "$PHP_INI_CONFIGS" | tr '|' '\n' | while read -r config; do \
# 跳过空行
[ -z "$config" ] && continue; \
\
echo "设置配置: $config"; \
echo "$config" >> /usr/local/etc/php/conf.d/custom.ini; \
done; \
\
echo "PHP.ini 配置完成"; \
cat /usr/local/etc/php/conf.d/custom.ini; \
fi;
# 安装 Composer
RUN set -eux; \
echo "========================================"; \
echo "安装 Composer"; \
echo "Composer 镜像: ${COMPOSER_MIRROR}"; \
echo "========================================"; \
\
# 下载 Composer
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \
\
# 配置 Composer 镜像源
case "$COMPOSER_MIRROR" in \
aliyun) \
echo "配置阿里云 Composer 镜像"; \
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/; \
;; \
tencent) \
echo "配置腾讯云 Composer 镜像"; \
composer config -g repo.packagist composer https://mirrors.cloud.tencent.com/composer/; \
;; \
ustc) \
echo "配置中科大 Composer 镜像"; \
composer config -g repo.packagist composer https://packagist.mirrors.ustc.edu.cn/; \
;; \
packagist) \
echo "使用官方 Packagist 源"; \
;; \
auto) \
echo "自动检测,使用阿里云镜像"; \
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/; \
;; \
*) \
echo "未知的 Composer 镜像配置,使用默认源"; \
;; \
esac; \
\
# 验证 Composer
composer --version; \
echo "Composer 安装完成";
# 清理和优化
RUN set -eux; \
echo "========================================"; \
echo "清理临时文件和优化镜像"; \
echo "构建模式: ${BUILD_MODE}"; \
echo "========================================"; \
\
# 清理 PECL 缓存
rm -rf /tmp/pear; \
\
# 清理本地扩展
rm -rf /tmp/pecl; \
\
# 根据构建模式决定是否清理编译工具
if [ "$BUILD_MODE" = "prod" ]; then \
echo "生产模式:清理编译工具以优化镜像大小"; \
if [ "$SYSTEM_TYPE" = "alpine" ]; then \
apk del autoconf g++ make linux-headers; \
else \
apt-get purge -y autoconf g++ make; \
apt-get autoremove -y; \
rm -rf /var/lib/apt/lists/*; \
fi; \
else \
echo "开发模式:保留编译工具"; \
fi; \
\
# 清理临时文件
rm -rf /tmp/*; \
rm -rf /var/tmp/*; \
\
echo "清理完成";
# 最终验证
RUN set -eux; \
echo "========================================"; \
echo "最终验证"; \
echo "========================================"; \
echo "PHP 版本:"; \
php -v; \
echo ""; \
echo "已安装的扩展:"; \
php -m; \
echo ""; \
echo "Composer 版本:"; \
composer --version; \
echo "========================================"; \
echo "镜像构建完成!"; \
echo "========================================";
# 设置工作目录
WORKDIR /opt/www
- 该文件注释已标注在文档中
- 简要说明
- 执行该文件,会生成你指定的镜像名称和 tag 标签
- 使用多阶段构建模式,第一阶段: 安装 pecl 和 local 扩展;第二阶段: 构建最终镜像
- 执行该文件,会生成你指定的镜像名称和 tag 标签
build.sh
bash
#!/bin/bash
# =============================================================================
# PHP Docker 多阶段构建脚本
# 功能:根据配置文件自动构建 PHP 基础镜像和最终镜像
# 用法:./build.sh [最终镜像名称]
# =============================================================================
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # 无颜色
# 配置文件路径
CONFIG_FILE="./config/build.conf"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 日志函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# 检查配置文件是否存在
check_config_file() {
if [ ! -f "$CONFIG_FILE" ]; then
log_error "配置文件不存在: $CONFIG_FILE"
exit 1
fi
log_info "配置文件检查通过"
}
# 解析配置文件
parse_config() {
local section=""
local key=""
while IFS= read -r line || [ -n "$line" ]; do
# 跳过空行和注释
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
# 检测 section
if [[ "$line" =~ ^\[(.+)\]$ ]]; then
section="${BASH_REMATCH[1]}"
continue
fi
# 读取配置值
if [ -n "$section" ]; then
case "$section" in
base_image)
BASE_IMAGE="$line"
;;
pecl)
PECL_EXTENSIONS+=("$line")
;;
local)
LOCAL_EXTENSIONS+=("$line")
;;
mode)
BUILD_MODE="$line"
;;
custom_source)
CUSTOM_SOURCE="$line"
;;
composer_mirror)
COMPOSER_MIRROR="$line"
;;
php)
PHP_INI_CONFIGS+=("$line")
;;
build_target)
BUILD_TARGET="$line"
;;
force_rebuild_base)
FORCE_REBUILD_BASE="$line"
;;
esac
fi
done < "$CONFIG_FILE"
}
# 检测系统类型(Alpine 或 Debian)
detect_system_type() {
if [[ "$BASE_IMAGE" == *"alpine"* ]]; then
SYSTEM_TYPE="alpine"
else
SYSTEM_TYPE="debian"
fi
log_info "检测到系统类型: $SYSTEM_TYPE"
}
# 生成基础镜像名称
generate_base_image_name() {
local version=$(echo "$BASE_IMAGE" | grep -oP 'php:\K[0-9]+\.[0-9]+')
BASE_IMAGE_NAME="php-base:${version}-${SYSTEM_TYPE}"
log_info "基础镜像名称: $BASE_IMAGE_NAME"
}
# 检查基础镜像是否存在
check_base_image_exists() {
if docker image inspect "$BASE_IMAGE_NAME" >/dev/null 2>&1; then
log_info "基础镜像已存在: $BASE_IMAGE_NAME"
return 0
else
log_info "基础镜像不存在,需要构建"
return 1
fi
}
# 构建基础镜像
build_base_image() {
log_info "开始构建基础镜像..."
# 构建参数
docker build \
--file Dockerfile.base \
--build-arg BASE_IMAGE="$BASE_IMAGE" \
--build-arg SYSTEM_TYPE="$SYSTEM_TYPE" \
--build-arg CUSTOM_SOURCE="$CUSTOM_SOURCE" \
--tag "$BASE_IMAGE_NAME" \
--progress=plain \
.
if [ $? -eq 0 ]; then
log_success "基础镜像构建成功: $BASE_IMAGE_NAME"
else
log_error "基础镜像构建失败"
exit 1
fi
}
# 构建最终镜像
build_final_image() {
local final_image_name="$1"
if [ -z "$final_image_name" ]; then
log_error "请提供最终镜像名称"
echo "用法: ./build.sh [最终镜像名称]"
exit 1
fi
log_info "开始构建最终镜像: $final_image_name"
# 准备 PECL 扩展参数
local pecl_extensions_arg=""
if [ ${#PECL_EXTENSIONS[@]} -gt 0 ]; then
pecl_extensions_arg=$(IFS=,; echo "${PECL_EXTENSIONS[*]}")
fi
# 准备 LOCAL 扩展参数
local local_extensions_arg=""
if [ ${#LOCAL_EXTENSIONS[@]} -gt 0 ]; then
local_extensions_arg=$(IFS=' '; echo "${LOCAL_EXTENSIONS[*]}")
fi
# 准备 PHP 配置参数
local php_ini_arg=""
if [ ${#PHP_INI_CONFIGS[@]} -gt 0 ]; then
php_ini_arg=$(IFS='|'; echo "${PHP_INI_CONFIGS[*]}")
fi
# 构建最终镜像
docker build \
--file Dockerfile.final \
--build-arg BASE_IMAGE_NAME="$BASE_IMAGE_NAME" \
--build-arg SYSTEM_TYPE="$SYSTEM_TYPE" \
--build-arg PECL_EXTENSIONS="$pecl_extensions_arg" \
--build-arg LOCAL_EXTENSIONS="$local_extensions_arg" \
--build-arg PHP_INI_CONFIGS="$php_ini_arg" \
--build-arg COMPOSER_MIRROR="$COMPOSER_MIRROR" \
--build-arg BUILD_MODE="$BUILD_MODE" \
--tag "$final_image_name" \
--progress=plain \
.
if [ $? -eq 0 ]; then
log_success "最终镜像构建成功: $final_image_name"
log_info "镜像信息:"
docker images | grep "$final_image_name"
else
log_error "最终镜像构建失败"
exit 1
fi
}
# 主函数
main() {
log_info "=========================================="
log_info "PHP Docker 多阶段构建开始"
log_info "=========================================="
# 初始化变量
BASE_IMAGE=""
PECL_EXTENSIONS=()
LOCAL_EXTENSIONS=()
BUILD_MODE="dev"
CUSTOM_SOURCE="false"
COMPOSER_MIRROR="auto"
PHP_INI_CONFIGS=()
BUILD_TARGET="all"
FORCE_REBUILD_BASE="false"
SYSTEM_TYPE=""
BASE_IMAGE_NAME=""
# 检查配置文件
check_config_file
# 解析配置
log_info "开始解析配置文件..."
parse_config
# 检测系统类型
detect_system_type
# 生成基础镜像名称
generate_base_image_name
# 显示配置信息
log_info "=========================================="
log_info "配置信息:"
log_info " 基础镜像: $BASE_IMAGE"
log_info " 系统类型: $SYSTEM_TYPE"
log_info " 基础镜像名称: $BASE_IMAGE_NAME"
log_info " PECL 扩展: ${PECL_EXTENSIONS[*]}"
log_info " LOCAL 扩展: ${LOCAL_EXTENSIONS[*]}"
log_info " 构建模式: $BUILD_MODE"
log_info " 自定义源: $CUSTOM_SOURCE"
log_info " Composer 镜像: $COMPOSER_MIRROR"
log_info " 构建目标: $BUILD_TARGET"
log_info " 强制重建基础镜像: $FORCE_REBUILD_BASE"
log_info "=========================================="
# 根据构建目标执行
case "$BUILD_TARGET" in
base)
log_info "构建目标: 仅构建基础镜像"
build_base_image
;;
final)
log_info "构建目标: 仅构建最终镜像"
if ! check_base_image_exists; then
log_error "基础镜像不存在,请先构建基础镜像"
exit 1
fi
build_final_image "$1"
;;
all)
log_info "构建目标: 构建基础镜像和最终镜像"
# 检查是否需要构建基础镜像
if [ "$FORCE_REBUILD_BASE" = "true" ]; then
log_warn "强制重新构建基础镜像"
build_base_image
else
if check_base_image_exists; then
log_info "基础镜像已存在,跳过构建"
else
build_base_image
fi
fi
build_final_image "$1"
;;
*)
log_error "无效的构建目标: $BUILD_TARGET"
exit 1
;;
esac
log_success "=========================================="
log_success "构建流程完成!"
log_success "=========================================="
}
# 执行主函数
main "$@"
- 该文件是解析配置文件及控制构建情况的文件
执行构建
shell
# 进入项目目录
cd project
# 添加文件执行权限
chmod +x ./build.sh
# 执行构建
./build.sh 新的镜像名称
结语
该份套餐可以做到:
- 构建逻辑通用:不同版本、不同扩展的构建只需要修改 build.conf 配置文件
- 扩展自由度高:可自由增减扩展信息,可指定版本,也可不指定,不指定时安装最新版本
- 构建模式切换:分为 prod/dev,prod 构建会清楚构建缓存等信息,减少镜像体积;dev模式不会,方便多次构建,减少构建时间
- 自定义系统依赖源:自由配置是否使用自定义系统依赖源,减少构建成功率和构建速度
- 自定义php配置:不同的开发需求,可能需要不通过的php配置,可以自由配置,构建后覆盖原有的默认配置
- 构建目标自由:可自由配置构建 基础镜像(base)、最终镜像(final)、基础+最终(all)
- 系统依赖自动检测:可根据配置的基础镜像,自动识别镜像系统(debian/alpine)、并安装对应的依赖,如:系统依赖、系统工具等
- GD 扩展自动配置:如果 local 扩展中存在 GD 扩展,则自动做扩展特殊配置
- 镜像名称自定义:基础镜像名称自动生成,最终镜像名称可以自由定义
- 扩展安装重试机制:安装扩展容易因为各种原因失败,追加重试机制(3次)减少失败次数
okk,提升到此结束,又可以愉快的做牛马了 ... ... ...