docker镜像最小化实践(php-cli 为例)

docker的镜像有大又小,大的镜像在于功能齐全,扩展方便,实操性更多,而小的镜像则在于麻雀虽小,五脏俱全,部署方便,代价小。
最近在工作中发现每次生成镜像极为缓慢和庞大,然后抽空对之前写的Dockefile做了梳理,重新写了一下,把镜像构建优化了30%,镜像大小缩小了2.5倍。

实践展示

镜像前后对比

基础包的构建

目前我们很多镜像都基于Ubuntu构建,但是我们在构建镜像上还是过于臃肿,所以我们采用 alpine linux 来作为基础镜像,可以看看下面的对比。


ubuntu基础镜像

alpine 基础镜像

使用alpine需要注意的一点是我们的包管理工具变成了apk,需要通过apk进行操作,而alpine 默认的源是http://dl-cdn.alpinelinux.org/,其实也不是很慢,但是如果觉得速度不满意的,可以替换为aliyun

#编辑源文件 /etc/apk/repositories
https://mirrors.aliyun.com/alpine/v3.6/main/
https://mirrors.aliyun.com/alpine/v3.6/community/

apk的使用

大家可以参考这篇文章,在这里不做详讲。https://blog.csdn.net/liupeifeng3514/article/details/80418887

php 基础镜像的选择

我所需要的php版本是php7.2,所以我一开始采用的是php:7.2的镜像,大约有200MB左右,后面特地去docker hub上查找,发现了php:7.2.10-cli-alpine3.8基础镜像,完全基于alpine环境,这个镜像大概有78MB左右,可能大概有人还觉得比较大,那可以自己基于alpine来构建,我在github找到了一个自己写的版本,构建后大概40MB左右,可惜的是7.1版本的,所以最后我没采用,使用还是官方的版本。
这里附下链接,https://github.com/swoftcloud/alphp/blob/alpine3.8/alphp-base.Dockerfile

时区的设置

RUN apk add -U tzdata \
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && apk del tzdata

因为默认的时区是UTC,所以我们需要搭建镜像时就变成中国时区

基础库的配置变更

我们现在需要把我们之前搭建基础库的方式从apt-get变更为apk获取,比如

RUN apt-get update \
    && apt-get install -y \
        curl \
        wget \
        git \
        zip \
        librdkafka-dev \
    && apt-get clean \
    && apt-get autoremove

这是我们之前的ubuntu的

RUN set -ex \
        && apk update \
        && apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS \
        && apk add --no-cache libaio \
         openssl-dev \
         librdkafka-dev \
        && apk del .phpize-deps \ 
        && rm -rf /var/cache/apk/* /tmp/* /usr/share/man

这个则是替换为alpine的方式,因为curl在php的基础镜像里已经有了,所以我没有重新添加。
这里有一点很重要,即时你用了很基础很基础的镜像,也最好学会阅读它的Dockerfile,至少不会让你重复在安装一遍一些比较基础的东西。

怎么查看基础镜像已经具备的功能或者命令?

当然是基于镜像构建一个容器,进去试试咯

docker run -it php:7.2.10-cli-alpine3.8 sh

可以进到容器里的sh,然后你就可以试试了

扩展的安装

当我们把之前讲的都写进Dockerfile后,可以先build一个镜像出来,我们把它叫做my:base

docker build . -t my:base

然后建一个容器进去执行php -m,看看我们安了哪些扩展,发现扩展貌似缺了几个我们想要的,那我们就得通过Dockerfile来安装我们的扩展,假设我们还需要安装swoole,redis这两个扩展。
因为版本可能会存在更新,所以我们会把版本号单独拿出来作为一个常量,哇,方便多了。

ENV SWOOLE_VERSION=4.1.2 \
    REDIS_VERSION=4.0.2 

接下来就是我们下载安装我们的扩展

RUN set -ex \
        && cd /tmp \
        && curl -SL "https://github.com/swoole/swoole-src/archive/v${SWOOLE_VERSION}.tar.gz" -o swoole.tar.gz \
        && curl -SL "http://pecl.php.net/get/redis-${REDIS_VERSION}.tgz" -o redis.tgz \
        # php extension: redis
        && pecl install redis.tgz \
        && docker-php-ext-enable redis \
        # php extension: swoole
        && cd /tmp \
        && mkdir -p swoole \
        && tar -xf swoole.tar.gz -C swoole --strip-components=1 \
        && rm swoole.tar.gz \
                && ( \
                    cd swoole \
                    && phpize \
                    && ./configure  --enable-mysqlnd --enable-coroutine --enable-openssl \
                    && make -j$(nproc) && make install \
                ) \
       && docker-php-ext-enable swoole 

可以看到我们先把网络文件全部下载到本地才开始安装,当然为了方便我们可以使用COPY命令把安装文件先下载好,拷贝进去。pecl 也是之前安装好的命令,所以我们直接安装,然后把扩展加载到php.ini可以直接用docker(docker-php-ext-enable)命令,感觉真的是极为友好方便了。至于swoole为什么不用pecl,因为我在configure阶段需要可配置项,所以没有。

一些常用的扩展我们还可以通过下面的方式来安装

docker-php-ext-install pdo_mysql

至此

至此,我们的一个基础的php镜像搭建完成了,我个人建议将镜像分为两部分,一部分是基础镜像,基本不会改变的,一部分是业务镜像,属于经常变更的。

业务镜像的构建

这个就根据自己的业务来搭建了,假设我的Dockerfile存放在我的项目根目录下

ADD . /var/www/my
WORKDIR /var/www/my

然后我们的项目是依赖于composer的,但是代码仓库为了源代码尽量的小且整洁,我们默认是不上传vendor目录的,所以需要我们安装composer 引入依赖的包。

RUN curl -sS https://getcomposer.org/installer | php \
    && mv composer.phar /usr/local/bin/composer \
    && apk add git \
    && composer self-update --clean-backups \
    && composer install --no-dev \
    && composer dump-autoload -o \
    && composer clearcache \
    && apk del git \
    && rm -rf /var/cache/apk/* /tmp/* /usr/share/man

至于这个时候才安装git,就是因为composer需要,不需要则立马卸载,而之前那些我下载的包都放在了/tmp目录下,所以我也同时删除了所有包,这也就是我们镜像的特点,干净,整洁,统一管理。

启动我们的cli

EXPOSE 80

CMD ["php", "/var/www/my/index", "start"]

最后当然是声明我们容器的端口并且启动咯。
当然启动的命令就可以参照下面格式了

docker run -d -p 10002:80 --restart=always -u="root" -v /apps/:/apps -v --name "my-php" my:latest

my:latest就是我们的业务镜像了。

最后

把我们完整的镜像文件贴出来给大家参考:
my.base.Dockerfile

# docker build . -f my-base.Dockerfile -t my:base

FROM php:7.2.10-cli-alpine3.8
LABEL maintainer="失忆的决 <masixun71@foxmail.com>" version="1.0"
ENV SWOOLE_VERSION=4.1.2 \
    REDIS_VERSION=4.0.2 \


#Timezone and lib
RUN apk add -U tzdata \
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && apk del tzdata

RUN set -ex \
        && cd /tmp \
        && curl -SL "https://github.com/swoole/swoole-src/archive/v${SWOOLE_VERSION}.tar.gz" -o swoole.tar.gz \
        && curl -SL "http://pecl.php.net/get/redis-${REDIS_VERSION}.tgz" -o redis.tgz \
        && ls -alh \
        && apk update \
        && apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS \
        # for swoole ext
        && apk add --no-cache libaio \
         linux-headers \
         libaio-dev \
         openssl-dev \
         libstdc++ \
         librdkafka-dev \
        # php extension: redis
        && pecl install redis.tgz \
        && docker-php-ext-enable redis \
        # php extension: pdo_mysql
        && docker-php-ext-install pdo_mysql \
        # php extension: bcmath
        && docker-php-ext-install bcmath \
        # php extension: swoole
        && cd /tmp \
        && mkdir -p swoole \
        && tar -xf swoole.tar.gz -C swoole --strip-components=1 \
        && rm swoole.tar.gz \
                && ( \
                    cd swoole \
                    && phpize \
                    && ./configure --enable-async-redis --enable-mysqlnd --enable-coroutine --enable-openssl \
                    && make -j$(nproc) && make install \
                ) \
                && rm -r swoole \
                && docker-php-ext-enable swoole \
        && apk del .phpize-deps \

Dockerfile

FROM my:base
LABEL maintainer="失忆的决<masixun71@foxmail.com>" version="1.0"
ADD . /var/www/my
WORKDIR /var/www/my

RUN curl -sS https://getcomposer.org/installer | php \
    && mv composer.phar /usr/local/bin/composer \
    && apk add git \
    && composer self-update --clean-backups \
    && composer install --no-dev \
    && composer dump-autoload -o \
    && composer clearcache \
    && apk del git \
    && rm -rf /var/cache/apk/* /tmp/* /usr/share/man

EXPOSE 80

CMD ["php", "/var/www/my/index", "start"]

要注意的点

1.docker 支持的层级是有限的,层级越少,越利于维护和镜像大小的降低,所以尽量把多层合并为1层。
2.把不再需要的东西做好删除,不然会一直存放在镜像中。
3.镜像注意划分基础镜像和业务镜像,提高复用性和可维护性。

推荐阅读更多精彩内容