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

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

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

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

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

Утреннее редактирование. Укусив пулю и пробежав find, я обнаружил

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

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


person Altreus    schedule 06.10.2014    source источник
comment
о /var/lib/docker/aufs/diff/3afa404e[...]/.cpanm это внутренности Docker, и я бы не стал с ними связываться   -  person Thomasleveil    schedule 07.10.2014


Ответы (6)


Каждый раз докер успешно выполняет команду 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 с помощью фиксированной команды

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

person Thomasleveil    schedule 06.10.2014
comment
В этом много --rm - разве это не удалит контейнеры после того, как я сделаю это? - person Altreus; 07.10.2014
comment
Да. Нет смысла хранить контейнеры, которые предназначены только для отладки вашего Dockerfile, когда вы можете воссоздать их по своему желанию. - person Thomasleveil; 07.10.2014
comment
Хорошо, это действительно было очень полезно, но у меня есть проблема, когда в случае сбоя сборки контейнера я не могу использовать этот трюк с хешем контейнера, в котором он сказал, что работал. Образ не создается в случае сбоя RUN. Могу ли я присоединиться к промежуточному контейнеру, который никогда не очищался? - person Altreus; 07.10.2014
comment
когда одна из команд Dockerfile не работает, вам нужно найти идентификатор предыдущего уровня и запустить контейнер с оболочкой с этим идентификатором: docker run --rm -it <id_last_working_layer> bash -il, а затем в контейнере попробовать команду, которая не смогла воспроизвести проблему, затем исправьте команду и проверьте ее, наконец, обновите файл Dockerfile с помощью фиксированной команды. - person Thomasleveil; 07.10.2014
comment
Кроме того, вы можете docker diff <container> и получить подробный список конкретных изменений файловой системы, сделанных на этом конкретном уровне (добавленные, удаленные или измененные файлы во всей файловой системе для этого образа). - person L0j1k; 05.11.2015
comment
В качестве примера я делаю это путем создания и сохранения промежуточных контейнеров сборки (docker build --rm=false -t=mything ., где --rm=false является ключевым параметром), а затем с помощью docker diff <hash> для проверки изменений на конкретном уровне контейнера, о котором вам нужна дополнительная информация. . - person L0j1k; 05.11.2015
comment
Я думал, что это не работает, потому что там написано Unable to find image 'd5219f1ffda9:latest' locally. Однако меня смутило несколько видов идентификаторов. Оказывается, вам нужно использовать идентификаторы, которые стоят сразу после стрелок, а не те, которые говорят, что идет ... - person rspeer; 18.05.2017
comment
Это потрясающе; означает ли это, что каждый слой является снимком файловой системы? Итак, если слой 1 копирует foo.txt в контейнер, а слой 2 копирует bar.txt в контейнер, тогда контейнер, запущенный из post-Layer-1, будет иметь foo.txt, но не bar.txt? - person vargonian; 09.06.2018
comment
@vargonian точно. Дополнительная информация здесь: docs.docker.com/storage/storagedriver/#images- и-слои - person Thomasleveil; 10.06.2018
comment
Обратите внимание: используйте sh вместо bash при работе с альпийскими изображениями. - person vaughan; 07.02.2019
comment
@rspeer Да, я почти уверен, что идентификаторы Running in ... - это идентификаторы контейнера (что имеет смысл, он запускает команду внутри контейнера на основе изображения, а не внутри самого изображения). Хотя это довольно сбивает с толку ... - person Radon Rosborough; 08.02.2021
comment
Когда я запускаю docker build, он не дает мне хеш-идентификатора каждого слоя. Я не вижу никаких параметров команды, чтобы включить это. - person ADJenks; 17.06.2021

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

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

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

$ 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]

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

person Drew    schedule 14.02.2016
comment
Ради интереса, зачем вам создавать новый образ из контейнера? Почему бы просто не запустить контейнер? Если образ, созданный из отказавшего контейнера, может работать, то, конечно, остановленный / отказавший контейнер также может работать? Или я что-то упускаю? - person nmh; 08.07.2018
comment
@nmh Потому что это позволяет вам захватывать и проверять контейнер в состоянии сбоя без повторного запуска команды сбоя. Иногда выполнение команды с ошибкой занимает минуты или больше, поэтому это удобный способ пометить состояние ошибки. Например, в настоящее время я использую этот подход для проверки журналов неудачной сборки библиотеки C ++, которая занимает несколько минут. Изменить - только что заметил, что Дрю сказал, что в [его] ситуации неудавшаяся команда - это сборка, которая занимает несколько часов, поэтому перемотка до неудачной команды и ее повторный запуск занимает много времени и не очень помогает. < / я> - person Jaime Soto; 08.10.2018
comment
@nmh Я думаю, что проблема с попыткой запустить отказавший контейнер заключается в том, что команду запуска контейнера обычно нужно изменить, чтобы она была полезной. Если вы попытаетесь снова запустить отказавший контейнер, он снова запустит команду, которая не удалась, и вы вернетесь туда, где начали. Создав образ, вы можете запустить контейнер с другой командой запуска. - person Centimane; 13.11.2018
comment
Это не сработает, если вы используете DOCKER_BUILDKIT=1 для создания своего Dockerfile - person Clintm; 21.06.2019
comment
С точки зрения @nmh - вам не нужно фиксировать изображение, если вы сразу после вывода сборки. Вы можете использовать docker container cp, чтобы извлечь результаты файла из неудачной сборки контейнер. - person whoisthemachine; 26.07.2019
comment
docker ps -l предоставит вам последний запущенный контейнер - person Ted Elliott; 16.10.2020

Обновление для более новых версий докеров 20.10 и новее

Используйте DOCKER_BUILDKIT=0 docker build ..., чтобы получить хэши промежуточных контейнеров, известные из более старых версий.

В более новой версии Buildkit активирован по умолчанию. Рекомендуется использовать только для отладки. Build Kit может ускорить сборку.

Для справки: Buildkit не поддерживает промежуточные хэши контейнеров: https://github.com/moby/buildkit/issues/1053

person Jannis Schönleber    schedule 23.03.2021
comment
Я подозревал это в течение долгого времени, ваш ответ подтвердил это! Он также удаляет промежуточные контейнеры во время многоступенчатой ​​обработки. - person Nishant; 25.05.2021

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

Знаю это:

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

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

person DomQ    schedule 07.02.2015
comment
Это особенно полезный ответ при использовании DOCKER_BUILDKIT, поскольку buildkit, похоже, не поддерживает те же решения, что и перечисленные выше. - person M. Anthony Aiello; 30.05.2019

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

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

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 <[email protected]>

# 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
person mikaraento    schedule 26.11.2017

Что бы я сделал, так это закомментировал Dockerfile ниже и включил оскорбительную строку. Затем вы можете запустить контейнер и вручную выполнить команды докера и просмотреть журналы обычным способом. Например. если 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...
person seanmcl    schedule 06.10.2014
comment
Я бы тоже так поступил, прежде чем нашел эту ветку. Однако есть способы получше, которые не требуют повторного запуска сборки. - person Aaron McMillin; 02.06.2016
comment
@Аарон. Спасибо, что напомнили мне об этом ответе. Давно не смотрел. Не могли бы вы объяснить, почему принятый ответ лучше этого с практической точки зрения. Я определенно понимаю, почему ответ Дрю лучше. Кажется, что принятый ответ все еще требует повторного запуска. - person seanmcl; 02.06.2016
comment
Я фактически проголосовал за ответ Дрю, а не за принятый. Оба они работают без повторного запуска сборки. В принятом ответе вы можете перейти в оболочку непосредственно перед неудачной командой (вы можете запустить ее снова, чтобы увидеть ошибку, если она будет быстрой). Или с ответом Дрю вы можете получить оболочку после выполнения неудачной команды (в его случае неудачная команда была долгой и оставила состояние, которое можно было проверить). - person Aaron McMillin; 03.06.2016