Advertisement
  1. Code
  2. Docker

Докер с нуля: понимание образов

Scroll to top
Read Time: 8 min
This post is part of a series called Docker from the Ground Up.
Docker From the Ground Up: Building Images

() translation by (you can also view the original English article)

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

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

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

Понимание слоев

Docker управляет образами с помощью внешнего драйвера хранилища. Существует несколько поддерживаемых драйверов, таких как AUFS, BTRFS и наложения. Образы сделаны из упорядоченных слоев. Вы можете думать о слое как о наборе изменений файловой системы. Когда вы берете все слои и складываете их вместе, вы получаете новый образ, который содержит все накопленные изменения.

Порядок важен. Если вы добавите файл на один уровень и удалите его на другом уровне, вам лучше сделать это в правильном порядке. Docker отслеживает каждый уровень. Образ может состоять из десятков слоев (предел равен 127). Каждый слой очень легкий. Преимущество слоев заключается в том, что образу могут совместно использовать слои.

Если у вас много образов на основе похожих слоев, таких как базовая ОС или общие пакеты, то все эти общие слои будут храниться только один раз, а служебные данные для каждого образа будут только уникальными слоями этого конкретного образа.

Копирование при записи

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

Это не означает, что контейнер не может изменять файлы со своего уровня образа. Он определенно может. Но он создаст копию в своем верхнем слое, и с этого момента любой, кто пытается получить доступ к файлу, получит копию верхнего уровня. Когда файлы или каталоги удаляются с нижних слоев, они становятся скрытыми. Исходные слои образов идентифицируются хэш-криптографическим содержимым. Уровень чтения и записи контейнера идентифицируется с помощью UUID.

Это позволяет использовать стратегию копирования и записи как для изображений, так и для контейнеров. Докер повторно использует те же элементы, насколько это возможно. Только когда элемент будет изменен, Docker создаст новую копию.

Вопросы дизайна для образов докера

Уникальная организация в слоях и стратегия копирования на запись предоставляют некоторые методы создания и компоновки образов Docker.

Минимальные образы: чем меньше тем лучше

Образы Docker получают огромные преимущества с точки зрения стабильности, безопасности и времени загрузки, чем они меньше. Вы можете создавать действительно крошечные образы для производственных целей. Если вам необходимо устранить неполадки, вы всегда можете установить нужные инструменты в контейнер.

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

Объединение слоев

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

Например, если вы устанавливаете набор пакетов, вы можете иметь слой для каждого пакета, установив каждый пакет в отдельную команду RUN в свой файл Docker:

1
RUN apt-get update
2
3
RUN apt-get -y install package_1
4
5
RUN apt-get -y install package_2
6
7
RUN apt-get -y install package_3

Или вы можете объединить их в один слой с одной командой RUN.

1
RUN apt-get update && \

2
3
    apt-get -y install package_1 && \

4
5
    apt-get -y install package_2 && \

6
7
    apt-get -y install package_3    

Выбор базового образа

Ваш базовый образ (практически никто не создает образ с нуля) часто является важным решением. Он может содержать много слоев и добавлять множество возможностей, но также и будет иметь большой вес. Качество образа и автора также имеют решающее значение. Вы не хотите, чтобы ваши образы отображались на некотором не очень качественном образе, где вы точно не знаете, что там есть, и можете ли вы доверять его автору.

Существуют официальные образы для многих дистрибутивов, языков программирования, баз данных и сред выполнения. Иногда вариантов просто огромное количество. Не торопитесь и сделайте правильный выбор.

Проверка образов

Давайте посмотрим на некоторые образы. Вот список образов, доступных в настоящее время на моей машине:

1
REPOSITORY      TAG    IMAGE ID     CREATED      SIZE
2
python          latest 775dae9b960e 12 days ago  687 MB
3
d4w/nsenter     latest 9e4f13a0901e 4 months ago 83.8 kB
4
ubuntu-with-ssh latest 87391dca396d 4 months ago 221 MB
5
ubuntu          latest bd3d4369aebc 5 months ago 127 MB
6
hello-world     latest c54a2cc56cbb 7 months ago 1.85 kB
7
alpine          latest 4e38e38c8ce0 7 months ago 4.8 MB
8
nsqio/nsq       latest 2a82c70fe5e3 8 months ago 70.7 MB

Репозиторий и тег идентифицируют образа для людей. Если вы просто попытаетесь запустить или вытащить имя репозитория без указания тега, то по умолчанию используется «последний» тег. Идентификатор образа является уникальным идентификатором.

Давайте погрузимся и рассмотрим образ hello-world:

1
> docker inspect hello-world
2
[
3
    {
4
        "Id": "sha256:c54a2cc56cbb2f...e7e2720f70976c4b75237dc",
5
        "RepoTags": [
6
            "hello-world:latest"
7
        ],
8
        "RepoDigests": [
9
            "hello-world@sha256:0256e8a3...411de4cdcf9431a1feb60fd9"
10
        ],
11
        "Parent": "",
12
        "Comment": "",
13
        "Created": "2016-07-01T19:39:27.532838486Z",
14
        "Container": "562cadb4d17bbf30b58a...bf637f1d2d7f8afbef666",
15
        "ContainerConfig": {
16
            "Hostname": "c65bc554a4b7",
17
            "Domainname": "",
18
            "User": "",
19
            "AttachStdin": false,
20
            "AttachStdout": false,
21
            "AttachStderr": false,
22
            "Tty": false,
23
            "OpenStdin": false,
24
            "StdinOnce": false,
25
            "Env": [
26
                "PATH=/usr/bin:/usr/sbin:/usr/bin:/sbin:/bin"
27
            ],
28
            "Cmd": [
29
                "/bin/sh",
30
                "-c",
31
                "#(nop) CMD [\"/hello\"]"
32
            ],
33
            "Image": "sha256:0f9bb7da10de694...5ab0fe537ce1cd831e",
34
            "Volumes": null,
35
            "WorkingDir": "",
36
            "Entrypoint": null,
37
            "OnBuild": null,
38
            "Labels": {}
39
        },
40
        "DockerVersion": "1.10.3",
41
        "Author": "",
42
        "Config": {
43
            "Hostname": "c65bc554a4b7",
44
            "Domainname": "",
45
            "User": "",
46
            "AttachStdin": false,
47
            "AttachStdout": false,
48
            "AttachStderr": false,
49
            "Tty": false,
50
            "OpenStdin": false,
51
            "StdinOnce": false,
52
            "Env": [
53
                "PATH=/usr/sbin:/usr/bin:/sbin:/bin"
54
            ],
55
            "Cmd": [
56
                "/hello"
57
            ],
58
            "Image": "sha256:0f9bb7da10de694b...b0fe537ce1cd831e",
59
            "Volumes": null,
60
            "WorkingDir": "",
61
            "Entrypoint": null,
62
            "OnBuild": null,
63
            "Labels": {}
64
        },
65
        "Architecture": "amd64",
66
        "Os": "linux",
67
        "Size": 1848,
68
        "VirtualSize": 1848,
69
        "GraphDriver": {
70
            "Name": "aufs",
71
            "Data": null
72
        },
73
        "RootFS": {
74
            "Type": "layers",
75
            "Layers": [
76
                "sha256:a02596fdd012f22b03a...079c3e8cebceb4262d7"
77
            ]
78
        }
79
    }
80
]

Интересно посмотреть, сколько информации связано с каждым образом. Я не буду перебирать каждый. Я просто упомянул интересный лакомый кусочек, что записи «container» и «containerConfig» относятся к временному контейнеру, создаваемому Docker при его создании. Здесь я хочу сосредоточиться на последнем разделе «RootFS». Вы можете получить только эту часть, используя поддержку шаблона Go для команды проверки:

1
> docker inspect -f '{{.RootFS}}' hello-world
2
{layers [sha256:a02596fdd012f22b03af6a...8357b079c3e8cebceb4262d7] }

Это работает, но мы потеряли хорошее форматирование. Я предпочитаю использовать jq:

1
> docker inspect hello-world | jq .[0].RootFS
2
{
3
  "Type": "layers",
4
  "Layers": [
5
    "sha256:a02596fdd012f22b03af6a...7507558357b079c3e8cebceb4262d7"
6
  ]
7
}
8

Вы можете видеть, что типом является «Слои», и есть только один слой.

Давайте проверим слои образа Python:

1
> docker inspect python | jq .[0].RootFS
2
{
3
  "Type": "layers",
4
  "Layers": [
5
    "sha256:a2ae92ffcd29f7ede...e681696874932db7aee2c",
6
    "sha256:0eb22bfb707db44a8...8f04be511aba313bdc090",
7
    "sha256:30339f20ced009fc3...6b2a44b0d39098e2e2c40",
8
    "sha256:f55f65539fab084d4...52932c7d4924c9bfa6c9e",
9
    "sha256:311f330fa783aef35...e8283e06fc1975a47002d",
10
    "sha256:f250d46b2c81bf76c...365f67bdb014e98698823",
11
    "sha256:1d3d54954c0941a8f...8992c3363197536aa291a"
12
  ]
13
}

Вау. Семь слоев. Но что это за слои? Мы можем использовать команду history, чтобы узнать это:

1
IMAGE        CREATED     CREATED BY                             SIZE 
2
775dae9b960e 12 days ago /bin/sh -c #(nop)  CMD ["python3"]     0 B

3
<missing>    12 days ago /bin/sh -c cd /usr/local/bin  && { ... 48 B
4
<missing>    12 days ago /bin/sh -c set -ex  && buildDeps=' ... 66.9 MB

5
<missing>    12 days ago /bin/sh -c #(nop)  ENV PYTHON_PIP_V... 0 B

6
<missing>    12 days ago /bin/sh -c #(nop)  ENV PYTHON_VERSI...   0 B

7
<missing>    12 days ago /bin/sh -c #(nop)  ENV GPG_KEY=0D96... 0 B

8
<missing>    12 days ago /bin/sh -c apt-get update && apt-ge... 7.75 MB

9
<missing>    12 days ago /bin/sh -c #(nop)  ENV LANG=C.UTF-8    0 B

10
<missing>    12 days ago /bin/sh -c #(nop)  ENV PATH=/usr/lo... 0 B

11
<missing>    13 days ago /bin/sh -c apt-get update && apt-ge... 323 MB

12
<missing>    13 days ago /bin/sh -c apt-get update && apt-ge... 123 MB

13
<missing>    13 days ago /bin/sh -c apt-get update && apt-ge... 44.3 MB

14
<missing>    13 days ago /bin/sh -c #(nop)  CMD ["/bin/bash"... 0 B

15
<missing>    13 days ago /bin/sh -c #(nop) ADD file:89ecb642... 123 MB

ОК. Не волнуйтесь. Ничего не упущено. Это просто ужасный пользовательский интерфейс. Уровни имели идентификатор изображения до Docker 1.10, но не больше. Идентификатор верхнего уровня на самом деле не является идентификатором этого уровня. Это идентификатор изображения Python. «CREATED BY» усечен, но вы можете увидеть полную команду, если вы передадите --no-trunc. Я избавлю вас от вывода здесь из-за ограничений ширины страницы, которые потребуют экстремального переноса строк.

Как получить образ? Существует три способа:

  • Pull/Run
  • Load
  • Build

Когда вы запускаете контейнер, вы указываете его образ. Если образ не существует в вашей системе, он извлекается из реестра Docker (по умолчанию DockerHub). Кроме того, вы можете напрямую вытащить образ, не запуская контейнер.

Вы также можете загрузить образ, который кто-то отправил вам в качестве файла tar. Докер поддерживает его из коробки.

Наконец, и самое интересное, вы можете создавать свои собственные образы, что является темой второй части.

Вывод

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

Но есть некоторые ошибки, и вам нужно понять принципы и механизмы эффективного использования образов Docker. Docker предоставляет несколько команд, чтобы понять, какие образы доступны и как они структурированы.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.