Как я могу проверить файловую систему неудачной сборки Docker?

272

Я пытаюсь создать новый образ Docker для нашего процесса разработки, используя cpanmдля установки несколько модулей Perl в качестве базового образа для различных проектов.

При разработке Dockerfile cpanmвозвращает код ошибки, потому что некоторые модули были установлены неправильно.

Я уверен, что мне нужно aptустановить еще кое-что.

У меня вопрос, где я могу найти /.cpanm/workкаталог, указанный в выходных данных, чтобы проверить журналы? В общем случае, как я могу проверить файловую систему неудачной docker buildкоманды?

Утро править После прикуса пули и запуска findя обнаружил

/var/lib/docker/aufs/diff/3afa404e[...]/.cpanm

Это надежно, или мне лучше собрать «голый» контейнер и запускать вещи вручную, пока у меня не будет всего, что мне нужно?

Altreus
источник
о /var/lib/docker/aufs/diff/3afa404e[...]/.cpanmтех внутренних
делах Докера,

Ответы:

357

Каждый раз, когда docker успешно выполняет RUNкоманду из Dockerfile, фиксируется новый слой в файловой системе образа . Удобно использовать идентификаторы этих слоев в качестве изображений для запуска нового контейнера.

Возьмите следующий Dockerfile:

FROM busybox
RUN echo 'foo' > /tmp/foo.txt
RUN echo 'bar' >> /tmp/foo.txt

и построить это:

$ docker build -t so-2622957 .
Sending build context to Docker daemon 47.62 kB
Step 1/3 : FROM busybox
 ---> 00f017a8c2a6
Step 2/3 : RUN echo 'foo' > /tmp/foo.txt
 ---> Running in 4dbd01ebf27f
 ---> 044e1532c690
Removing intermediate container 4dbd01ebf27f
Step 3/3 : RUN echo 'bar' >> /tmp/foo.txt
 ---> Running in 74d81cb9d2b1
 ---> 5bd8172529c1
Removing intermediate container 74d81cb9d2b1
Successfully built 5bd8172529c1

Теперь вы можете создать новый контейнер с 00f017a8c2a6, 044e1532c690и 5bd8172529c1:

$ docker run --rm 00f017a8c2a6 cat /tmp/foo.txt
cat: /tmp/foo.txt: No such file or directory

$ docker run --rm 044e1532c690 cat /tmp/foo.txt
foo

$ docker run --rm 5bd8172529c1 cat /tmp/foo.txt
foo
bar

Конечно, вы можете запустить оболочку для изучения файловой системы и попробовать команды:

$ docker run --rm -it 044e1532c690 sh      
/ # ls -l /tmp
total 4
-rw-r--r--    1 root     root             4 Mar  9 19:09 foo.txt
/ # cat /tmp/foo.txt 
foo

В случае сбоя одной из команд Dockerfile вам нужно найти идентификатор предыдущего уровня и запустить оболочку в контейнере, созданном из этого идентификатора:

docker run --rm -it <id_last_working_layer> bash -il

Однажды в контейнере:

  • попробуйте команду, которая потерпела неудачу, и воспроизведите проблему
  • затем исправьте команду и проверьте ее
  • наконец обновите ваш Dockerfile с помощью фиксированной команды

Если вам действительно нужно поэкспериментировать с реальным слоем, который вышел из строя, а не работать с последним рабочим слоем, см . Ответ Дрю .

Thomasleveil
источник
2
Да, это так. Нет смысла хранить контейнеры, которые просто предназначены для отладки вашего Dockerfile, когда вы можете воссоздать их по своему желанию.
Thomasleveil
1
Хорошо, это на самом деле было очень полезно, но у меня есть проблема, когда в случае сбоя сборки контейнера я не могу использовать этот трюк с хешем контейнера, в котором, по его словам, он работает. При неудачном запуске RUN не создается изображение. Могу ли я прикрепить к промежуточному контейнеру, который никогда не был очищен?
Altreus
6
в случае сбоя одной из команд Dockerfile вам нужно найти идентификатор предыдущего уровня и запустить контейнер с оболочкой с таким идентификатором: docker run --rm -it <id_last_working_layer> bash -ilи, попав в контейнер, попробовать команду, которая не смогла воспроизвести проблему, затем исправьте команду и протестируйте ее, наконец обновите ваш Dockerfile с помощью фиксированной команды.
Thomasleveil
2
Кроме того, вы можете docker diff <container>и получить полный список конкретных изменений файловой системы, внесенных в этот конкретный слой (файлы, добавленные, удаленные или измененные во всей файловой системе для этого образа).
L0j1k
14
Я думал, что это не работает, потому что он сказал Unable to find image 'd5219f1ffda9:latest' locally. Тем не менее, я был смущен несколькими видами удостоверений личности. Оказывается, вы должны использовать идентификаторы, которые следуют непосредственно за стрелками, а не те, которые говорят "Running in ...".
rspeer
201

Верхний ответ работает в том случае, если вы хотите проверить состояние непосредственно перед неудачной командой.

Однако возникает вопрос, как проверить состояние самого сбойного контейнера. В моей ситуации сбойная команда - это сборка, которая занимает несколько часов, поэтому перемотка перед неудачной командой и ее повторный запуск занимает много времени и не очень полезна.

Решение здесь состоит в том, чтобы найти контейнер, который потерпел неудачу:

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                          PORTS               NAMES
6934ada98de6        42e0228751b3        "/bin/sh -c './utils/"   24 minutes ago      Exited (1) About a minute ago                       sleepy_bell

Передайте это изображению:

$ docker commit 6934ada98de6
sha256:7015687976a478e0e94b60fa496d319cdf4ec847bcd612aecf869a72336e6b83

А затем запустите образ [при необходимости, запустив bash]:

$ docker run -it 7015687976a4 [bash -il]

Теперь вы на самом деле смотрите на состояние сборки во время ее сбоя, а не перед тем, как запустить команду, вызвавшую сбой.

Нарисовался
источник
Из интереса, зачем вам нужно создавать новое изображение из контейнера? Почему бы просто не запустить контейнер? Если образ, созданный из сбойного контейнера, может быть запущен, то, конечно, остановленный / сбойный контейнер также может работать? Или я что-то упустил?
Нмч
@nmh Потому что это позволяет вам захватывать и проверять контейнер в состоянии сбоя без необходимости повторного запуска команды сбоя. Иногда выполнение сбойной команды занимает несколько минут или дольше, поэтому это удобный способ пометить сбойное состояние. Например, в настоящее время я использую этот подход для проверки журналов неудачной сборки библиотеки C ++, которая занимает несколько минут. Редактировать - только что заметил, что Дрю сказал, что в его ситуации неудачная команда - это сборка, которая занимает несколько часов, поэтому перемотка перед неудачной командой и ее повторное выполнение занимает много времени и не очень помогает.
Хайме Сото
@nmh Я думаю, что проблема с попыткой запуска сбойного контейнера заключается в том, что обычно нужно изменить команду запуска контейнера, чтобы она была полезной. Если вы попытаетесь запустить сбойный контейнер снова, он запустит команду, которая снова не удалась, и вы вернетесь туда, откуда начали. Создав изображение, вы можете запустить контейнер с другой командой запуска.
Сентиман
2
Это не сработает, если вы используете DOCKER_BUILDKIT=1для созданияDockerfile
Клинт
В смысле @ nmh - вам не нужно фиксировать изображение, если вы только после вывода сборки. Вы можете использовать док-контейнер cp для извлечения результатов файла из сбойного контейнера сборки.
whoisthemachine
7

Docker кэширует все состояние файловой системы после каждой успешной RUNстроки.

Знаю это:

  • чтобы проверить последнее состояние перед вашей RUNошибочной командой, закомментируйте ее в Dockerfile (а также любые и все последующие RUNкоманды), затем запустите docker buildи docker runснова.
  • чтобы проверить состояние после неудачной RUNкоманды, просто добавьте || trueк нему, чтобы заставить его преуспеть; затем действуйте, как описано выше (оставьте все последующие RUNкоманды закомментированными, запустите docker buildи docker run)

Тада, нет необходимости связываться с внутренностями Docker или идентификаторами слоев, и в качестве бонуса Docker автоматически минимизирует объем работы, который необходимо сделать заново.

DomQ
источник
1
Это особенно полезный ответ при использовании DOCKER_BUILDKIT, так как buildkit, похоже, не поддерживает те же решения, что перечислены выше.
М. Энтони Айелло
3

Отладка сбоев на этапе сборки действительно очень раздражает.

Лучшее решение, которое я нашел, состоит в том, чтобы убедиться, что каждый шаг, который выполняет реальную работу, успешен, и добавив проверку после тех, которые терпят неудачу. Таким образом, вы получаете зафиксированный слой, который содержит результаты неудачного шага, которые вы можете проверить.

Dockerfile, с примером после # Run DB2 silent installerстроки:

#
# DB2 10.5 Client Dockerfile (Part 1)
#
# Requires
#   - DB2 10.5 Client for 64bit Linux ibm_data_server_runtime_client_linuxx64_v10.5.tar.gz
#   - Response file for DB2 10.5 Client for 64bit Linux db2rtcl_nr.rsp 
#
#
# Using Ubuntu 14.04 base image as the starting point.
FROM ubuntu:14.04

MAINTAINER David Carew <carew@us.ibm.com>

# DB2 prereqs (also installing sharutils package as we use the utility uuencode to generate password - all others are required for the DB2 Client) 
RUN dpkg --add-architecture i386 && apt-get update && apt-get install -y sharutils binutils libstdc++6:i386 libpam0g:i386 && ln -s /lib/i386-linux-gnu/libpam.so.0 /lib/libpam.so.0
RUN apt-get install -y libxml2


# Create user db2clnt
# Generate strong random password and allow sudo to root w/o password
#
RUN  \
   adduser --quiet --disabled-password -shell /bin/bash -home /home/db2clnt --gecos "DB2 Client" db2clnt && \
   echo db2clnt:`dd if=/dev/urandom bs=16 count=1 2>/dev/null | uuencode -| head -n 2 | grep -v begin | cut -b 2-10` | chgpasswd && \
   adduser db2clnt sudo && \
   echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Install DB2
RUN mkdir /install
# Copy DB2 tarball - ADD command will expand it automatically
ADD v10.5fp9_linuxx64_rtcl.tar.gz /install/
# Copy response file
COPY  db2rtcl_nr.rsp /install/
# Run  DB2 silent installer
RUN mkdir /logs
RUN (/install/rtcl/db2setup -t /logs/trace -l /logs/log -u /install/db2rtcl_nr.rsp && touch /install/done) || /bin/true
RUN test -f /install/done || (echo ERROR-------; echo install failed, see files in container /logs directory of the last container layer; echo run docker run '<last image id>' /bin/cat /logs/trace; echo ----------)
RUN test -f /install/done

# Clean up unwanted files
RUN rm -fr /install/rtcl

# Login as db2clnt user
CMD su - db2clnt
mikaraento
источник
0

То, что я хотел бы сделать, это прокомментировать Dockerfile ниже и включая строку обидчика. Затем вы можете запустить контейнер и запустить команды docker вручную, а также просмотреть журналы обычным способом. Например, если Dockerfile

RUN foo
RUN bar
RUN baz

и он умирает в баре, я бы сделал

RUN foo
# RUN bar
# RUN baz

затем

$ docker build -t foo .
$ docker run -it foo bash
container# bar
...grep logs...
seanmcl
источник
Это то, что я бы сделал тоже, прежде чем найти эту тему. Однако есть и лучшие способы, которые не требуют повторного запуска сборки.
Аарон Макмиллин
@Аарон. Спасибо, что напомнили мне об этом ответе. Я давно на это не смотрел. Не могли бы вы объяснить, почему принятый ответ лучше, чем этот, с практической точки зрения? Я определенно понимаю, почему ответ Дрю лучше. Кажется, принятый ответ все еще требует повторного запуска.
seanmcl
Я фактически голосовал за ответ Дрю, а не за принятый. Они оба работают без повторного запуска сборки. В принятом ответе вы можете перейти в оболочку непосредственно перед неудачной командой (вы можете запустить ее снова, чтобы увидеть ошибку, если она будет быстрой). Или, ответив Дрю, вы можете получить оболочку после выполнения неудачной команды (в его случае неудавшаяся команда долго выполнялась и оставила состояние, которое можно было проверить).
Аарон Макмиллин