首页 > 解决方案 > Docker `COPY` 文件到容器中不是持久的

问题描述

我已经为一个应用程序创建了一个 docker 图像,在该应用程序中我正在将文件夹复制到图像中,如下所示:

COPY extra-addons/ /mnt/extra-addons/pos_item_price/

但是当我使用 kubernetes 使用该图像并转到 /mnt/extra-addons 文件夹时

$ kubectl --insecure-skip-tls-verify --namespace my-app exec -it my-app-55d464dd78-7h7x7 -- /bin/bash
root@my-app-55d464dd78-7h7x7:/# cd /mnt/extra-addons/
root@my-app-55d464dd78-7h7x7:/mnt/extra-addons# ls
root@my-app-55d464dd78-7h7x7:/mnt/extra-addons#

我什么也没看到

但是我确实看到在构建图像时正在复制数据

Step 19/26 : COPY extra-addons/ /mnt/extra-addons/pos_item_price/
---> 47fda7baba98
Step 20/26 : RUN ls -la /mnt/extra-addons/*
---> Running in ab93cf423db5
total 12
drwxr-xr-x. 3 odoo root 4096 Apr 21 11:13 .
drwxr-xr-x. 3 odoo root 4096 Apr 21 11:13 ..
drwxrwxrwx. 7 root root 4096 Apr 21 11:13 pos_item_price
Removing intermediate container ab93cf423db5
---> 645bc64741e0
Step 21/26 : RUN ls -la /mnt/extra-addons/pos_item_price/*
---> Running in f6ad09d6d83c
total 44
drwxrwxrwx. 7 root root 4096 Apr 21 11:13 .
drwxr-xr-x. 3 odoo root 4096 Apr 21 11:13 ..
-rw-rw-rw-. 1 root root 77 Apr 21 11:10 .git
-rw-rw-rw-. 1 root root 579 Apr 21 11:10 .gitignore
-rw-rw-rw-. 1 root root 45 Apr 21 11:10 __init__.py
-rw-rw-rw-. 1 root root 571 Apr 21 11:10 __manifest__.py
drwxrwxrwx. 2 root root 4096 Apr 21 11:13 data
drwxrwxrwx. 2 root root 4096 Apr 21 11:13 models
drwxrwxrwx. 2 root root 4096 Apr 21 11:13 security
drwxrwxrwx. 3 root root 4096 Apr 21 11:13 static
drwxrwxrwx. 2 root root 4096 Apr 21 11:13 views
Removing intermediate container f6ad09d6d83c
---> dc35af25b2a8

我想知道为什么当我将它复制到图像中时它不是持久的,我希望数据存在于 kubernetes pod 中?

完整的 Dockerfile

FROM debian:stretch

# Generate locale C.UTF-8 for postgres and general locale data
ENV LANG C.UTF-8

# Install some deps, lessc and less-plugin-clean-css, and wkhtmltopdf
RUN set -x; \
        apt-get update \
        && apt-get install -y --no-install-recommends \
            ca-certificates \
            curl \
            dirmngr \
            fonts-noto-cjk \
            gnupg \
            libssl1.0-dev \
            node-less \
            python3-pip \
            python3-pyldap \
            python3-qrcode \
            python3-renderpm \
            python3-setuptools \
            python3-vobject \
            python3-watchdog \
            xz-utils \
        && curl -o wkhtmltox.deb -sSL https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/0.12.5/wkhtmltox_0.12.5-1.stretch_amd64.deb \
        && echo '7e35a63f9db14f93ec7feeb0fce76b30c08f2057 wkhtmltox.deb' | sha1sum -c - \
        && dpkg --force-depends -i wkhtmltox.deb\
        && apt-get -y install -f --no-install-recommends \
        && rm -rf /var/lib/apt/lists/* wkhtmltox.deb

# install latest postgresql-client
RUN set -x; \
        echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' > etc/apt/sources.list.d/pgdg.list \
        && export GNUPGHOME="$(mktemp -d)" \
        && repokey='B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8' \
        && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \
        && gpg --armor --export "${repokey}" | apt-key add - \
        && gpgconf --kill all \
        && rm -rf "$GNUPGHOME" \
        && apt-get update  \
        && apt-get install -y postgresql-client \
        && rm -rf /var/lib/apt/lists/*

# Install rtlcss (on Debian stretch)
RUN set -x;\
    echo "deb http://deb.nodesource.com/node_8.x stretch main" > /etc/apt/sources.list.d/nodesource.list \
    && export GNUPGHOME="$(mktemp -d)" \
    && repokey='9FD3B784BC1C6FC31A8A0A1C1655A0AB68576280' \
    && gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "${repokey}" \
    && gpg --armor --export "${repokey}" | apt-key add - \
    && gpgconf --kill all \
    && rm -rf "$GNUPGHOME" \
    && apt-get update \
    && apt-get install -y nodejs \
    && npm install -g rtlcss \
    && rm -rf /var/lib/apt/lists/*

# Install Odoo
ENV ODOO_VERSION 12.0
ARG ODOO_RELEASE=20190128
ARG ODOO_SHA=9e34aaed2eb1e7697aaf36767247dbf335e9fe7a
RUN set -x; \
        curl -o odoo.deb -sSL http://nightly.odoo.com/${ODOO_VERSION}/nightly/deb/odoo_${ODOO_VERSION}.${ODOO_RELEASE}_all.deb \
        && echo "${ODOO_SHA} odoo.deb" | sha1sum -c - \
        && dpkg --force-depends -i odoo.deb \
        && apt-get update \
        && apt-get -y install -f --no-install-recommends \
        && rm -rf /var/lib/apt/lists/* odoo.deb

# Copy entrypoint script and Odoo configuration file
RUN pip3 install num2words xlwt
COPY ./entrypoint.sh /
COPY ./odoo.conf /etc/odoo/
RUN chown odoo /etc/odoo/odoo.conf

# Mount /var/lib/odoo to allow restoring filestore and /mnt/extra-addons for users addons
RUN mkdir -p /mnt/extra-addons/pos_item_price \
        && chown -R odoo /mnt/extra-addons
VOLUME ["/var/lib/odoo", "/mnt/extra-addons"]

RUN ls -la /mnt/extra-addons/*
RUN echo "-------- Before LS END -----"
COPY extra-addons/ /mnt/extra-addons/pos_item_price/
RUN ls -la /mnt/extra-addons/*
RUN ls -la /mnt/extra-addons/pos_item_price/*

# Expose Odoo services
EXPOSE 8069 8071

# Set the default config file
ENV ODOO_RC /etc/odoo/odoo.conf

# Set default user when running the container
USER odoo

ENTRYPOINT ["/entrypoint.sh"]
CMD ["odoo"]

标签: dockerkubernetescopy

解决方案


我相信您面临的问题与您的卷有关,但与 Dockerfile 中定义的卷无关(尽管我个人不喜欢 Dockerfile 中定义的任何卷,因为它们会引起问题)。

为了解释 Dockerfile 中的 VOLUME 导致的问题,您可以查看以下示例来测试 COPY、ADD 和 RUN:

$ cat df.vol
FROM busybox:latest

VOLUME ["/data"]
CMD find /data

COPY sample-data/file.txt /data/file.txt
COPY sample-data/dir /data/dir
ADD sample-data/tar-file.tgz /data/tar-dir
RUN echo "hello world" >/data/run.txt \
 && find /data \
 && sleep 5m

这是用于 COPY 和 ADD 命令的示例数据目录:

$ ls -al sample-data/
total 32 
drwxr-xr-x  3 bmitch bmitch  4096 Jan 22  2017 .
drwxr-xr-x 34 bmitch bmitch 12288 Apr 17 15:16 ..
drwxr-xr-x  2 bmitch bmitch  4096 Jan 22  2017 dir
-rw-r--r--  1 bmitch bmitch    14 Jan 22  2017 file2.txt
-rw-r--r--  1 bmitch bmitch    12 Jan 22  2017 file.txt
-rw-r--r--  1 bmitch bmitch   214 Jan 22  2017 tar-file.tgz

让我们运行一个构建(没有 BUILDKIT,因为我们希望能够调试它):

$ DOCKER_BUILDKIT=0 docker build -f df.vol -t test-vol .
Sending build context to Docker daemon  23.04kB
Step 1/7 : FROM busybox:latest
 ---> 59788edf1f3e
Step 2/7 : VOLUME ["/data"]
 ---> Using cache
 ---> 14b4f1130806                      
Step 3/7 : CMD find /data     
 ---> Running in 75673363d1e3             
Removing intermediate container 75673363d1e3
 ---> 262714d065fc
Step 4/7 : COPY sample-data/file.txt /data/file.txt
 ---> d781519c584e
Step 5/7 : COPY sample-data/dir /data/dir
 ---> 34b5b4a83b1e
Step 6/7 : ADD sample-data/tar-file.tgz /data/tar-dir
 ---> 3fc45f2e62a4                                                
Step 7/7 : RUN echo "hello world" >/data/run.txt  && find /data  && sleep 5m
 ---> Running in d75794387274
/data
/data/dir
/data/dir/file1.txt
/data/dir/file2.txt
/data/run.txt
/data/tar-dir
/data/tar-dir/dir
/data/tar-dir/dir/file1.txt
/data/tar-dir/dir/file2.txt
/data/tar-dir/file.txt
/data/file.txt
Removing intermediate container d75794387274
 ---> 5af322be539a
Successfully built 5af322be539a
Successfully tagged test-vol:latest

注意run.txt上面的文件。我们还可以看到来自 COPY 和 ADD 命令的文件。但是,如果我们运行另一个 RUN 命令,或者任何时候使用生成的图像,我们会看到:

$ docker run -it --rm test-vol:latest
/data
/data/dir
/data/dir/file1.txt
/data/dir/file2.txt
/data/tar-dir
/data/tar-dir/dir
/data/tar-dir/dir/file1.txt
/data/tar-dir/dir/file2.txt
/data/tar-dir/file.txt
/data/file.txt

只有来自 COPY 和 ADD 的文件在那里。如果我们查看 docker 用于 RUN 步骤的临时容器,则更容易看出原因(这就是我sleep 5m在构建期间使用的原因)。这是 5 分钟睡眠期间另一个窗口的输出:

$ docker ps -l                         
CONTAINER ID        IMAGE               COMMAND                   CREATED             STATUS           PORTS               NAMES
d75794387274        3fc45f2e62a4        "/bin/sh -c 'echo \"h…"   1 second ago        Created                               brave_dubinsky

$ docker diff d75

$ docker inspect d75                                                                                                                                                                            
[                                                                                                                                                                                               
    {                                                                                                                                                                                           
        "Id": "d75794387274cc222391065c14581a29ff9fcc898ef367db64b9f145bd9325c7",                                                                                                               
        "Created": "2019-04-21T18:19:19.449392301Z",                                                                                                                                            
        "Path": "/bin/sh",                                                                                                                                                                      
        "Args": [                                                                                                                                                                               
            "-c",                                                                                                                                                                               
            "echo \"hello world\" >/data/run.txt  && find /data  && sleep 5m"                                                                                                                   
        ],                                                                                                                                                                                      
        "State": {                                                                                                                                                                              
            "Status": "running",       
            "Running": true,      
            "Paused": false,        
            "Restarting": false,                                                                    
            "OOMKilled": false,                                                                      
            "Dead": false,                     
            "Pid": 31620,                        
            "ExitCode": 0,            
            "Error": "",              
            "StartedAt": "2019-04-21T18:19:22.699031557Z",                         
            "FinishedAt": "0001-01-01T00:00:00Z"
        },                                            
        ...
        "Mounts": [
            {
                "Type": "volume",
                "Name": "07b9d30dfdfcae91b820dc6fa249030fd8d7a4ad9c50ee928aaab104c07c8a9d",
                "Source": "/home/var-docker/volumes/07b9d30dfdfcae91b820dc6fa249030fd8d7a4ad9c50ee928aaab104c07c8a9d/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
        ...

您在上述命令中看到的是 docker 使用临时容器和匿名卷运行构建。输出显示了对该容器的更改,这些diff更改将被构建捕获为 Dockerfile 中的一个层。在这种情况下,什么都没有。

对卷进行更改不会修改容器文件系统,因此您永远不会看到更改,但是 ADD 和 COPY 命令直接针对映像层运行,因此您确实可以看到这些更改。

从 Dockerfile 中删除 VOLUME 会解决这个问题吗?可能不会(除非您运行映像的方法是从映像创建和重用匿名卷)。我还是建议删除 VOLUME 吗?是的,稍后在运行容器时不需要指定卷,您可以为任何目录定义卷,并且在 Dockerfile 中会中断尝试稍后使用 RUN 命令以非直观方式扩展映像。


因此,如果不是 VOLUME 命令与您的 COPY 交互,您为什么会看到您的更改丢失?最可能的原因是在运行容器时定义了一个卷。我们需要查看您的 yml 规范才能确定。该卷,如果它是一个命名卷,将使用您的图像的内容进行初始化,但只有一次。之后,无论您对映像进行什么更改,该卷都将保持不变,并显示您上次使用该卷时卷中的文件。

如果要根据映像的更改更新卷,则需要在映像中配置一个入口点,以将文件从映像中保存的位置复制到容器中的卷位置。我的docker-base映像中有这样做的示例。在那里查看save-volumeload-volume脚本。


推荐阅读