Вступление
Docker позволяет упаковать ваши приложения и сервисы в контейнеры, чтобы вы могли запускать их где угодно. К сожалению, могут возникнуть проблемы при создании вашего изображения и интеграции всех слоев, которые нужны вашему приложению, особенно если вы новичок в изображениях и контейнерах Docker. Вы можете столкнуться с опечатками, проблемами с библиотеками и модулями времени выполнения, конфликтами имен или проблемами при взаимодействии с другими контейнерами.
В этом руководстве по устранению неполадок, предназначенном для новичков в Docker, вы будете устранять проблемы при создании образов Docker, разрешать конфликты имен при запуске контейнеров и исправлять проблемы, возникающие при взаимодействии между контейнерами.
Предпосылки
Для завершения этого урока вам понадобится
-
Docker установлен на сервере или вашем локальном компьютере.
Чтобы установить Docker на сервере, вы можете следовать инструкциям for CentOS 7 или for Ubuntu 16.04.
Вы можете посетить the веб-сайт Docker или следовать the официальная документация по установке, чтобы установить Docker на свой локальный компьютер. машина.
Шаг 1 - Решение проблем с Dockerfile
Чаще всего вы можете столкнуться с проблемами, когда создаете образ Docker из + Dockerfile
. Прежде чем мы углубимся, давайте проясним разницу между изображениями и контейнерами.
-
Image - это ресурс только для чтения, который вы создаете с помощью файла конфигурации с именем
+ Dockerfile +
. Это то, что вы отправляете и делитесь через http://dockerhub.com [Docker Hub] или ваш личный реестр. -
Container - это экземпляр для чтения и записи, который вы создаете на основе созданного вами изображения.
Вы можете узнать больше об этих понятиях в учебнике Docker объяснил: Использование Dockerfiles для автоматизации сборки изображений.
Когда вы смотрите на + Dockerfile +
, вы можете ясно видеть пошаговый процесс, который Docker использует для создания образа, потому что каждая строка в + Dockerfile +
соответствует шагу в процессе. Обычно это означает, что если вы достигли определенного шага, то все предыдущие шаги были выполнены успешно.
Давайте создадим небольшой проект, чтобы изучить некоторые проблемы, с которыми вы можете столкнуться при использовании + Dockerfile
. Создайте каталог + docker_image +
в вашем домашнем каталоге и используйте + nano +
или ваш любимый редактор, чтобы создать + Dockerfile +
в этой папке.
mkdir ~/docker_image
nano ~/docker_image/Dockerfile
Добавьте следующее содержимое в этот новый файл:
~ / Docker_image / Dockerfile
# base image
FROM debian:latest
# install basic apps
RUN aapt-get install -qy nano
В этом коде есть преднамеренная опечатка. Вы можете это заметить? Попробуйте создать образ из этого файла, чтобы увидеть, как Docker обрабатывает неверные команды. Создайте изображение с помощью следующей команды:
docker build -t my_image ~/docker_image
Вы увидите это сообщение в своем терминале, указывающее на ошибку:
OutputStep 2 : RUN aapt-get install -qy nano
---> Running in 085fa10ffcc2
Сообщение об ошибке в конце означает, что возникла проблема с командой на шаге 2. В данном случае это была наша преднамеренная опечатка: у нас есть + aapt-get +
вместо + apt-get +
. Но это также означало, что предыдущий шаг выполнен правильно.
Измените + Dockerfile +
и внесите исправление:
Dockerfile
# install basic apps
RUN install -qy nano
Теперь снова запустите команду + docker build
:
docker build -t my_image ~/docker_image
И теперь вы увидите следующий вывод:
OutputSending build context to Docker daemon 2.048 kB
Step 1 : FROM debian:latest
---> ddf73f48a05d
Step 2 : RUN apt-get install -qy nano
---> Running in 9679323b942f
Reading package lists...
Building dependency tree...
С исправленной опечаткой процесс продвинулся немного быстрее, поскольку Docker кэшировал первый шаг, а не перезагружал базовый образ. Но, как видно из вывода, у нас появилась новая ошибка.
Дистрибутив Debian, который мы использовали в качестве основы для нашего образа, не смог найти текстовый редактор + nano +
, хотя мы знаем, что он доступен в репозиториях пакетов Debian. Базовое изображение поставляется с кэшированными метаданными, такими как репозитории и списки доступных пакетов. Иногда вы можете столкнуться с некоторыми проблемами с кэшем, если изменились действующие репозитории, из которых вы извлекаете данные.
Чтобы исправить это, измените Dockerfile, чтобы выполнить очистку и обновление исходных текстов перед установкой любых новых пакетов. Снова откройте файл конфигурации:
nano ~/docker_image/Dockerfile
Добавьте следующую выделенную строку в файл, above команда для установки + nano +
:
~ / Docker_image / Dockerfile
# base image
FROM debian:latest
# clean and update sources
# install basic apps
RUN apt-get install -qy nano
Сохраните файл и снова запустите команду + docker build
:
docker build -t my_image ~/docker_image
На этот раз процесс завершается успешно.
OutputSending build context to Docker daemon 2.048 kB
Step 1 : FROM debian:latest
---> a24c3183e910
Step 2 : RUN apt-get install -qy nano
---> Running in 2237d254f172
Reading package lists...
Building dependency tree...
Reading state information...
Suggested packages:
spell
The following NEW packages will be installed:
nano
...
---> 64ff1d3d71d6
Removing intermediate container 2237d254f172
Successfully built 64ff1d3d71d6
Давайте посмотрим, что произойдет, когда мы добавим Python 3 и драйвер PostgreSQL в наш образ. Снова откройте + Dockerfile +
.
nano ~/docker_image/Dockerfile
И добавьте два новых шага для установки Python 3 и драйвера Python PostgreSQL:
~ / Docker_image / Dockerfile
# base image
FROM debian:latest
# clean and update sources
RUN apt-get clean && apt-get update
# install basic apps
RUN apt-get install -qy nano
# install Python and modules
Сохраните файл, выйдите из редактора и снова создайте изображение:
docker build -t my_image ~/docker_image
Как видно из вывода, пакеты установлены правильно. Процесс также завершается намного быстрее, потому что предыдущие шаги были кэшированы.
OutputSending build context to Docker daemon 2.048 kB
Step 1 : FROM debian:latest
---> ddf73f48a05d
Step 2 : RUN apt-get clean && apt-get update
---> Using cache
---> 2c5013476fbf
Step 3 : RUN apt-get install -qy nano
---> Using cache
---> 4b77ac535cca
Step 4 : RUN apt-get install -qy python3
---> Running in 93f2d795fefc
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
krb5-locales libgmp10 libgnutls-deb0-28 libgssapi-krb5-2 libhogweed2
libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2 libnettle4
libp11-kit0 libpq5 libsasl2-2 libsasl2-modules libsasl2-modules-db
libtasn1-6
Suggested packages:
gnutls-bin krb5-doc krb5-user libsasl2-modules-otp libsasl2-modules-ldap
libsasl2-modules-sql libsasl2-modules-gssapi-mit
libsasl2-modules-gssapi-heimdal python-psycopg2-doc
The following NEW packages will be installed:
krb5-locales libgmp10 libgnutls-deb0-28 libgssapi-krb5-2 libhogweed2
libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.4-2 libnettle4
libp11-kit0 libpq5 libsasl2-2 libsasl2-modules libsasl2-modules-db
libtasn1-6 python3-psycopg2
0 upgraded, 18 newly installed, 0 to remove and 0 not upgraded.
Need to get 5416 kB of archives.
After this operation, 10.4 MB of additional disk space will be used.
...
Processing triggers for libc-bin (2.19-18+deb8u6) ...
---> 978e0fa7afa7
Обратите пристальное внимание на вывод Docker, чтобы определить, где находятся опечатки, и запускать обновления во время сборки и внутри контейнера, чтобы убедиться, что вам не мешают кэшированные списки пакетов.
Синтаксические ошибки и проблемы с кэшированием являются наиболее распространенными проблемами, с которыми вы можете столкнуться при создании образа в Docker. Теперь давайте посмотрим на проблемы, которые могут возникнуть при запуске контейнеров из этих образов.
Шаг 2 - Решение проблем с именами контейнеров
Когда вы запускаете больше контейнеров, вы в конечном итоге столкнетесь с конфликтами имен. При столкновении имен происходит попытка создать контейнер, имя которого совпадает с именем контейнера, уже существующего в вашей системе. Давайте рассмотрим, как правильно обращаться с именами, переименованием и удалением контейнеров, чтобы избежать коллизий.
Давайте запустим контейнер из образа, который мы создали в предыдущем разделе. В этом контейнере мы запустим интерактивный интерпретатор bash, чтобы проверить все. Выполните следующую команду:
docker run -ti my_image bash
Когда контейнер запустится, вы увидите приглашение root в ожидании инструкций:
Теперь, когда у вас есть работающий контейнер, давайте посмотрим, с какими проблемами вы можете столкнуться.
Когда вы запускаете контейнер так, как вы это сделали, без явной установки имени, Docker назначает случайное имя контейнеру. Вы можете увидеть все запущенные контейнеры и соответствующие им имена, запустив команду + docker ps +
на хосте Docker, вне запущенного контейнера.
Откройте новый терминал на хосте Docker и выполните следующую команду:
docker ps
Эта команда выводит список запущенных контейнеров с их именами, как показано в следующем примере:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
80a0ca58d6ec my_image "bash" 22 seconds ago Up 28 seconds
Имя + loving_brahmagupta +
в предыдущем выводе - это имя, которое Docker автоматически назначил контейнеру в предыдущем примере; у тебя будет другое имя. Позволить Docker назначить имя вашему контейнеру хорошо в очень простых случаях, но может представлять значительные проблемы; когда мы развертываем, нам нужно последовательно называть контейнеры, чтобы мы могли ссылаться на них и легко их автоматизировать.
Чтобы указать имя для контейнера, мы можем использовать аргумент + - name +
при запуске контейнера или переименовать работающий контейнер во что-то более наглядное.
Выполните следующую команду из терминала хоста Docker:
docker rename python_box
Затем перечислите ваши контейнеры:
docker ps
В выводе вы увидите контейнер + python_box +
, подтверждающий, что вы успешно переименовали контейнер:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
80a0ca58d6ec my_image "bash" 24 minutes ago Up 24 minutes python_box
Чтобы закрыть контейнер, введите + exit +
в командной строке терминала, содержащего запущенный контейнер:
exit
Если это не вариант, вы можете уничтожить контейнер из другого терминала на хосте Docker с помощью следующей команды:
docker kill python_box
Когда вы таким образом уничтожаете контейнер, Docker возвращает имя только что уничтоженного контейнера:
Outputpython_box
Чтобы убедиться, что + python_box +
больше не существует, снова перечислите все запущенные контейнеры:
docker ps
Как и ожидалось, контейнера больше нет в списке:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Теперь вы можете подумать, что можете запустить другой контейнер с именем + python_box +, но давайте посмотрим, что произойдет, когда мы попробуем.
На этот раз мы будем использовать аргумент + - name +
для установки имени контейнера:
docker run -ti my_image bash
Outputdocker: Error response from daemon: Conflict. The name "/python_box" is already in use by container 80a0ca58d6ecc80b305463aff2a68c4cbe36f7bda15e680651830fc5f9dda772. You have to remove (or rename) that container to be able to reuse that name..
See 'docker run --help'.
Когда вы создаете изображение и повторно используете имя существующего изображения, существующее изображение будет перезаписано, как вы уже видели. Контейнеры немного сложнее, потому что вы не можете перезаписать уже существующий контейнер.
Докер говорит, что + python_box +
уже существует, хотя мы только что убили его, и он даже не указан в + docker ps +
. Он не работает, но все еще доступен, если вы захотите запустить его снова. Мы остановили это, но мы не удалили это. Команда + docker ps +
показывает только running контейнеры, но не all контейнеры.
Чтобы вывести список all контейнеров Docker, работающих и других, передайте флаг + -a +
(псевдоним для + - all +
) в + docker ps +
:
docker ps -a
Теперь наш контейнер + python_box +
появляется в выводе:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
80a0ca58d6ec my_image "bash" 12 minutes ago Exited (137) 6 minutes ago
Контейнер существует с состоянием + Exited (137) +
, поэтому мы столкнулись с проблемой именования, когда пытались создать новый контейнер с тем же именем.
Если вы хотите полностью удалить контейнер, вы используете команду + docker rm +
. Выполните эту команду в своем терминале:
docker rm python_box
Еще раз, Docker выводит имя контейнера, который был только что удален:
Outputpython_box
Давайте создадим новый контейнер с именем + python_box +
теперь, когда мы удалили предыдущий:
docker run --name python_box -ti my_image bash
Процесс завершается, и мы снова получаем корневую оболочку:
Теперь давайте убьем и удалим контейнер, чтобы избежать проблем в будущем. Из другого сеанса терминала на хосте Docker уничтожьте контейнер и удалите его с помощью следующей команды:
docker kill python_box && docker rm python_box
Мы объединили две команды вместе, поэтому в выходных данных имя контейнера отображается дважды. Первый вывод подтверждает, что мы уничтожили контейнер, а другой подтверждает, что мы его удалили.
Outputpython_box
python_box
Помните + docker ps -a +
при возникновении проблем с именами и убедитесь, что ваши контейнеры остановлены и удалены, прежде чем пытаться воссоздать их с тем же именем.
Присвоение имен контейнерам упрощает управление инфраструктурой. Имена также облегчают связь между контейнерами, как вы увидите далее.
Шаг 3 - Решение проблем с контейнерной связью
Docker позволяет легко создавать экземпляры нескольких контейнеров, чтобы вы могли запускать разные или даже избыточные сервисы в каждом. Если служба выходит из строя или подвергается риску, вы можете просто заменить ее новой, сохранив при этом остальную часть инфраструктуры. Но вы можете столкнуться с проблемами, заставляющими эти контейнеры взаимодействовать друг с другом.
Давайте создадим два контейнера, которые общаются, чтобы мы могли исследовать потенциальные проблемы общения. Мы создадим один контейнер с Python, используя наш существующий образ, а другой контейнер с экземпляром PostgreSQL. Для этого контейнера мы будем использовать официальный образ PostgreSQL, доступный на http://dockerhub.com [Docker Hub].
Давайте сначала создадим контейнер PostgreSQL. Мы дадим этому контейнеру имя, используя флаг + - name +
, чтобы мы могли легко его идентифицировать при связывании с другими контейнерами. Мы назовем это + postgres_box +
.
Ранее, когда мы запускали контейнер, он работал на переднем плане, захватывая наш терминал. Мы хотим запустить контейнер базы данных PostgreSQL в фоновом режиме, что мы можем сделать с флагом + - detach +
.
Наконец, вместо запуска + bash +
, мы запустим команду + postgres +
, которая запустит сервер базы данных PostgreSQL внутри контейнера.
Выполните следующую команду, чтобы запустить контейнер:
docker run --name postgres_box --detach postgres
Docker загрузит образ из Docker Hub и создаст контейнер. Затем он вернет полный идентификатор контейнера, работающего в фоновом режиме:
OutputUnable to find image 'postgres:latest' locally
latest: Pulling from library/postgres
6a5a5368e0c2: Already exists
193f770cec44: Pull complete
...
484ac0d6f901: Pull complete
Digest: sha256:924650288891ce2e603c4bbe8491e7fa28d43a3fc792e302222a938ff4e6a349
Status: Downloaded newer image for postgres:latest
Перечислите контейнеры, чтобы убедиться, что этот новый контейнер работает:
docker ps
Вывод подтверждает, что контейнер + postgres_box +
работает в фоновом режиме, предоставляя порт + 5432 +
, порт базы данных PostgreSQL:
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7a230b56cd64 "/docker-entrypoint.s" Less than a second ago Up 2 seconds
Теперь давайте запустим контейнер Python. Чтобы программы, работающие внутри контейнера Python, «видели» службы в контейнере + postgres_box +
, нам нужно вручную связать наш контейнер Python с контейнером + postgres_box +
с помощью аргумента + - link +
. Чтобы создать ссылку, мы указываем имя контейнера, за которым следует имя ссылки. Мы будем использовать имя ссылки для ссылки на контейнер + postgres_box +
внутри контейнера Python.
Выполните следующую команду, чтобы запустить контейнер Python:
docker run --name python_box --link -ti my_image bash
Теперь давайте попробуем подключиться к PostgreSQL из контейнера + python_box +
.
Ранее мы установили + nano +
внутри контейнера + python_box +
, поэтому давайте используем его для создания простого скрипта Python для проверки соединения с PostgreSQL. В терминале для контейнера + python_box +
выполните эту команду:
nano pg_test.py
Затем добавьте следующий скрипт Python в файл:
pg_test.py
"""Test PostgreSQL connection."""
import psycopg2
conn = psycopg2.connect(user='postgres')
print(conn)
Сохраните файл и выйдите из редактора. Давайте посмотрим, что происходит, когда мы пытаемся подключиться к базе данных из нашего скрипта. Выполните скрипт в вашем контейнере:
python3 pg_test.py
Вывод, который мы видим, указывает на проблему с подключением к базе данных:
OutputTraceback (most recent call last):
File "pg_test.py", line 5, in <module>
conn = psycopg2.connect(database="test", user="postgres", password="secret")
File "/usr/lib/python3/dist-packages/psycopg2/__init__.py", line 164, in connect
conn = _connect(dsn, connection_factory=connection_factory, async=async)
psycopg2.OperationalError:
Мы убедились, что контейнер + postgres_box +
работает, и связали его с контейнером + python_box +
, так что же тогда произошло? Ну, мы никогда не указывали хост базы данных, когда пытались подключиться, поэтому Python пытается подключиться к базе данных, работающей локально, и это не сработает, потому что служба не работает локально, она работает в другом контейнере, как если бы это было на другом компьютере.
Вы можете получить доступ к связанному контейнеру, используя имя, которое вы установили при создании ссылки. В нашем случае мы используем + postgres +
для ссылки на контейнер + postgres_box +
, на котором работает наш сервер базы данных. Вы можете проверить это, просмотрев файл + / etc / hosts +
внутри контейнера + python_box +
:
cat /etc/hosts
Вы увидите все доступные хосты с их именами и IP-адресами. Наш сервер + postgres +
хорошо виден.
Output127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 3053f74c8c13
Итак, давайте изменим наш скрипт на Python и добавим имя хоста. Откройте файл.
nano pg_test.py
Затем укажите хост в строке подключения:
/pg_test.py
"""Test PostgreSQL connection."""
import psycopg2
conn = psycopg2.connect( user='postgres')
print(conn)
Сохраните файл и снова запустите скрипт.
python3 pg_test.py
На этот раз скрипт завершается без ошибок:
Output<connection object at 0x7f64caec69d8; dsn: 'user=postgres host=7a230b56cd64', closed: 0>
Помните имена контейнеров, когда вы пытаетесь подключиться к сервисам в других контейнерах, и измените учетные данные приложения, чтобы они ссылались на связанные имена этих контейнеров.
Заключение
Мы только что рассмотрели наиболее распространенные проблемы, с которыми вы можете столкнуться при работе с контейнерами Docker: от создания образов до развертывания сети контейнеров.
Docker имеет флаг + - debug +
, который предназначен в основном для разработчиков Docker. Однако, если вы хотите узнать больше о внутренностях Docker, попробуйте запустить команды Docker в режиме отладки для более подробного вывода:
docker -D [command] [arguments]
Хотя контейнеры в программном обеспечении существуют в течение некоторого времени, сам Docker существует только три года и может быть довольно сложным. Не торопитесь, чтобы ознакомиться с условиями и the экосистемой, и вы увидите, как некоторые концепции, которые на первый взгляд были немного чуждыми скоро иметь много смысла.