将Docker镜像分成多个小层而不是一个大层是否更好

40次阅读
没有评论

问题描述

当我为现有应用程序构建Docker镜像时,我尽量使用尽可能少的层,并清理任何不需要的文件。例如,构建Moodle镜像:

# Dockerfile for moodle instance.
# Forked from Jonathan Hardison's <jmh@jonathanhardison.com> docker version. https://github.com/jmhardison/docker-moodle
# Original Maintainer Jon Auer <jda@coldshore.com>
FROM  php:7.2-apache
# Replace for later version
ARG VERSION=37
ARG DB_TYPE="all"
VOLUME ["/var/moodledata"]
EXPOSE 80
ENV MOODLE_DB_TYPE="${DB_TYPE}"
# Let the container know that there is no tty
ENV DEBIAN_FRONTEND noninteractive \
    MOODLE_URL http://0.0.0.0 \
    MOODLE_ADMIN admin \
    MOODLE_ADMIN_PASSWORD Admin~1234 \
    MOODLE_ADMIN_EMAIL admin@example.com \
    MOODLE_DB_HOST '' \
    MOODLE_DB_PASSWORD '' \
    MOODLE_DB_USER '' \
    MOODLE_DB_NAME '' \
    MOODLE_DB_PORT '3306'
COPY ./scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN echo "Build moodle version ${VERSION}" &&\
    chmod +x /usr/local/bin/entrypoint.sh &&\
    apt-get update && \
    if [ $DB_TYPE = 'mysqli' ] || [ $DB_TYPE = 'all' ]; then echo "Setup mysql and mariadb support" && docker-php-ext-install pdo mysqli pdo_mysql; fi &&\
    if [ $DB_TYPE = 'pgsql' ] || [ $DB_TYPE = 'all' ]; then echo "Setup postgresql support" &&\
        apt-get install -y --no-install-recommends libghc-postgresql-simple-dev &&\
        docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql &&\
        docker-php-ext-install pdo pgsql pdo_pgsql; \
     fi &&\
    apt-get -f -y install --no-install-recommends rsync unzip netcat libxmlrpc-c++8-dev libxml2-dev libpng-dev libicu-dev libmcrypt-dev libzip-dev &&\
    docker-php-ext-install xmlrpc && \
    docker-php-ext-install mbstring && \
    whereis libzip &&\
    docker-php-ext-configure zip --with-libzip=/usr/lib/x86_64-linux-gnu/libzip.so &&\
    docker-php-ext-install zip && \
    docker-php-ext-install xml && \
    docker-php-ext-install intl && \
    docker-php-ext-install soap && \
    docker-php-ext-install gd && \
    docker-php-ext-install opcache && \
    echo "Installing moodle" && \
    curl https://download.moodle.org/download.php/direct/stable${VERSION}/moodle-latest-${VERSION}.zip -o /tmp/moodle-latest.zip  && \
    rm -rf /var/www/html/index.html && \
    cd /tmp &&  unzip /tmp/moodle-latest.zip && cd / \
    mkdir -p /usr/src/moodle && \
    mv /tmp/moodle /usr/src/ && \
    chown www-data:www-data -R /usr/src/moodle && \
    apt-get purge -y unzip &&\
    apt-get autopurge -y &&\
    apt-get autoremove -y &&\
    apt-get autoclean &&\
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* cache/* /var/lib/log/*
COPY ./scripts/moodle-config.php /usr/src/moodle/config.php
COPY ./scripts/detect_mariadb.php /opt/detect_mariadb.php
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

但是,当我为另一个(生产项目)构建镜像时,我注意到一旦我拉取现有容器的更新镜像时,只有更改的层实际上被下载。
所以我想知道,是否将每个目的(例如,一个用于PHP本身,另一个用于应用程序本身)拆分为一个层会更好?因此,在部署过程中,只需下载更改的部分,而不是一个巨大的单个层。
但是,将构建分成较小的层可能需要重新下载和删除许多构建PHP扩展所需的软件包。您认为在大型应用程序中可能会出现问题吗?或者在部署过程中进行较长的构建以进行较小的下载是否值得,还是我在胡说八道?
另一方面,我了解到使用单个层可以使整体镜像大小更小。但是,结果将需要重新下载整个层本身。因此,如果我的镜像大小为100MB,则需要重新下载整个100MB,而不是几KB的更改层。

解决方案

请注意以下操作注意版本差异及修改前做好备份。

方案1

通常来说,你应该:
– 将多个相关的操作/文件合并到一个层中,以避免有大量的层。
– 将不相关的操作/文件分开,这些操作/文件可能会独立更改。
– 按照最不可能更改的顺序排列层,使其出现在文件中的最前面。
– 如果镜像/层中有很多不需要的垃圾文件,可以使用多阶段构建来进行清理。

如果你必须对Docker文件进行任何更改,那么在该点以下的所有内容都必须重新构建;此时缓存是无用的。你应该避免这种情况。

因此,与其尝试将所有内容合并到一个层中或类似的奇怪操作,不如专注于理解什么是可能更改的内容,以及如何减少必须重新构建的层数量。

下面是一个很好的解释示例,用于Spring Boot,它解释了这个问题:

一个更好的Dockerfile
Spring Boot的fat jar自然具有“层”,因为jar本身的打包方式。如果我们首先解压它,它将自动分为外部和内部依赖项。
Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

现在有3个层,后两个层中包含所有应用程序资源。如果应用程序依赖项不发生变化,则第一个层(来自BOOT-INF/lib)不会发生变化,因此构建速度会更快,并且只需拉取应用程序本身的较新/较小的层,这非常高效。

对于这个问题,实际上没有一个神奇的答案。它完全取决于你的应用程序及其依赖关系,以及在开发/部署环境中随着时间的推移可能发生的变化。

正文完