问题描述
当我为现有应用程序构建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)不会发生变化,因此构建速度会更快,并且只需拉取应用程序本身的较新/较小的层,这非常高效。
对于这个问题,实际上没有一个神奇的答案。它完全取决于你的应用程序及其依赖关系,以及在开发/部署环境中随着时间的推移可能发生的变化。