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


Краткое содержание предыдущей серии: два разработчика в жестком дедлайне составляют Dockerfile. В процессе к ним заходит Ops Игорь Иванович. Итоговый Dockerfile плох настолько, что ИИ оказывается на грани инфаркта.



Сейчас разберемся, что не так с этим Dockerfile.


Итак, прошла неделя.


Dev Петя встречается в столовой за чашкой кофе с Ops Игорем Ивановичем.


П: Игорь Иванович, вы сильно заняты? Хотелось бы разобраться, где мы напортачили.


ИИ: Это хорошо, не часто встретишь разработчиков, которых интересует эксплуатация.
Для начала давай договоримся о некоторых вещах:


  1. Идеология Docker: один контейнер — один процесс.
  2. Чем меньше контейнер, тем лучше.
  3. Чем больше берется из кэша, тем лучше.

П: А почему в одном контейнере должен быть один процесс?


ИИ: Docker при запуске контейнера отслеживает состояние процесса с pid 1. Если процесс умирает, Docker пытается перезапустить контейнер. Допустим, у вас в контейнере запущено несколько приложений или основное приложение запущено не с pid 1. Если процесс умрет, Docker об этом не узнает.


Если больше нет вопросов, показывай ваш Dockerfile.


И Петя показал:


FROM ubuntu:latest

# Копируем исходный код
COPY ./ /app
WORKDIR /app

# Обновляем список пакетов
RUN apt-get update 

# Обновляем пакеты
RUN apt-get upgrade

# Устанавливаем нужные пакеты
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor

# Устанавливаем bundler
RUN gem install bundler

# Устанавливаем nodejs используется для сборки статики
RUN curl -sL https://deb.nodesource.com/setup_9.x | sudo bash -
RUN apt-get install -y nodejs

# Устанавливаем зависимости
RUN bundle install --without development test --path vendor/bundle

# Чистим за собой кэши
RUN rm -rf /usr/local/bundle/cache/*.gem 
RUN apt-get clean 
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 
RUN rake assets:precompile
# Запускаем скрипт, при старте контейнера, который запустит все остальное.
CMD ["/app/init.sh"]

ИИ: Ох, давай разбираться по порядку. Начнем с первой строчки:


FROM ubuntu:latest

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


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


Вдобавок чем больше образ, тем больше он занимает места на хосте и в registry (ты же где-то хранишь образы)?


П: Да, конечно, у нас registry, вы же его и настраивали.


ИИ: Так, о чем это я?.. Ах да, объемы… Так же растет нагрузка на сеть. Для единичного образа это незаметно, но когда идет непрерывная сборка, тесты и деплой, это ощутимо. А если у тебя нет God’s mode на AWS, тебе еще и космический счет прилетит.


Поэтому нужно выбирать наиболее подходящий образ, с точной версией и минимумом ПО. Например, возьми: FROM ruby:2.5.5-stretch


П: О, понятно. А как и где посмотреть имеющиеся образы? Как понять, какой мне нужен?


ИИ: Обычно образы берутся с докерхаба, не путай с порнхабом :). Для образа обычно существует несколько сборок:
Alpine: образы собраны на минималистичном образе Linux, всего 5 Мб. Его минус: он собран с собственной реализацией libc, стандартные пакеты в нем не работают. На поиск и установку нужного пакета уйдет немало времени.
Scratch: базовый образ, не используется для сборки других образов. Он предназначен исключительно для запуска бинарных, подготовленных данных. Идеально подойдет для запуска бинарных приложений, которые включают в себя все необходимое, например go-приложения.
На базе какой-либо ОС, например Ubuntu или Debian. Ну тут, думаю, пояснять не надо.


ИИ: Теперь нам нужно поставить все доп. пакеты и почистить за собой кэши. И сразу можно выкинуть apt-get upgrade. Иначе при каждой сборке, несмотря на фиксированный тэг базового имиджа, будут получаться разные образы. Обновление пакетов в образе — это задача мейнтейнера, она сопровождается изменением тэга.


П: Да, я пробовал это сделать, у меня получилось так:


WORKDIR /app
COPY ./ /app

RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -     && apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor nodejs     && gem install bundler     && bundle install --without development test --path vendor/bundle

RUN rm -rf /usr/local/bundle/cache/*.gem     && apt-get clean      && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

ИИ: Неплохо, но тут тоже есть, над чем поработать. Смотри, вот эта команда:


RUN rm -rf /usr/local/bundle/cache/*.gem     && apt-get clean      && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*  

… не удаляет данные из итогового образа, а лишь создает дополнительный слой без этих данных. Правильно так:


RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -     && apt-get -y install libpq-dev imagemagick gsfonts nodejs     && gem install bundler     && bundle install --without development test --path vendor/bundle \  
    && rm -rf /usr/local/bundle/cache/*.gem     && apt-get clean      && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 

Но это еще не все. Что у вас там, Ruby? Тогда не надо в начале копировать весь проект. Достаточно скопировать Gemfile и Gemfile.lock.


При таком подходе bundle install не будет выполняться на каждое изменение исходников, а только если изменился Gemfile или Gemfile.lock.


Те же методы работают и для других языков с менеджером зависимостей, таких как npm, pip, composer и других базирующихся на файле со списком зависимостей.


Ну и наконец, помнишь, в начале я говорил про идеологию Docker «один контейнер — один процесс»? Это означает, что supervisor не нужен. Так же не стоит устанавливать systemd, по тем же причинам. По сути, Docker сам является supervisor. И когда ты пытаешься запускать в нем несколько процессов, это как в одном процессе supervisor запускать несколько приложений.
При сборке вы сделаете единый образ, а потом запустите нужное количество контейнеров, чтобы в каждом работал один процесс.


Но об этом позже.


П: Кажется, понял. Смотрите, что получается:


FROM ruby:2.5.5-stretch

WORKDIR /app
COPY Gemfile* /app

RUN curl -sL https://deb.nodesource.com/setup_9.x | bash -     && apt-get -y install libpq-dev imagemagick gsfonts nodejs     && gem install bundler     && bundle install --without development test --path vendor/bundle \  
    && rm -rf /usr/local/bundle/cache/*.gem     && apt-get clean      && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 

COPY . /app
RUN rake assets:precompile

CMD ["bundle”, “exec”, “passenger”, “start"]

А запуск демонов переопределим при запуске контейнера?


ИИ: Да, все верно. Кстати, можно использовать как CMD так и ENTRYPOINT. А разобраться, в чем отличие, это тебе домашнее задание. На эту тему на Хабре есть хорошая статья.


Так, давай дальше. Ты качаешь файл для установки node, но при этом нет никакой гарантии, что в нем будет то, что тебе нужно. Надо добавить валидацию. Например, так:


RUN curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x     && echo "958c9a95c4974c918dca773edf6d18b1d1a41434  setup_9.x" | sha1sum -c -     &&  bash  setup_9.x     && rm -rf setup_9.x     && apt-get -y install libpq-dev imagemagick gsfonts nodejs     && gem install bundler     && bundle install --without development test --path vendor/bundle \  
    && rm -rf /usr/local/bundle/cache/*.gem     && apt-get clean      && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 

По контрольной сумме ты сможешь проверить, что скачал верный файл.


П: Но если файл изменится, то сборка не пройдет.


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


П: Спасибо. Получается, итоговый Dockerfile будет выглядеть так:


FROM ruby:2.5.5-stretch

WORKDIR /app
COPY Gemfile* /app

RUN curl -sL https://deb.nodesource.com/setup_9.x > setup_9.x     && echo "958c9a95c4974c918dca773edf6d18b1d1a41434  setup_9.x" | sha1sum -c -     &&  bash  setup_9.x     && rm -rf setup_9.x     && apt-get -y install libpq-dev imagemagick gsfonts nodejs     && gem install bundler     && bundle install --without development test --path vendor/bundle \  
    && rm -rf /usr/local/bundle/cache/*.gem     && apt-get clean      && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 

COPY . /app
RUN rake assets:precompile

CMD ["bundle”, “exec”, “passenger”, “start"]

П: Игорь Иванович, спасибо за помощь. Мне уже пора бежать, надо еще 10 коммитов за сегодня сделать.


Игорь Иванович, взглядом остановив торопливого коллегу, делает глоток крепкого кофе. Поразмыслив несколько секунд об SLA 99.9% и коде без багов, он задает вопрос.


ИИ: А где вы логи храните?


П: Конечно, в production.log. Кстати да, а как мы без ssh получим к ним доступ?


ИИ: Если вы их оставите в файлах, решение для вас уже придумали. Команда docker exec позволяет исполнить любую команду в контейнере. Например, вы можете сделать cat для логов. А использовав ключ -it и запустив bash (если он установлен в контейнере), вы получите интерактивный доступ к контейнеру.


Но хранить логи в файлах не стоит. Как минимум это приводит к бесконтрольному росту контейнера, логи же никто не ротирует. Все логи нужно кидать в stdout. Там их уже можно посмотреть с помощью команды docker logs.


П: Игорь Иванович, а может вынести логи в смонтированную директорию, на физическую ноду, как данные пользователей?


ИИ: Хорошо, что вы не забыли вынести данные, загруженные на диск ноды. С логами так тоже можно, только не забудь настроить ротирование.
Все, можешь бежать.


П: Игорь Иванович, а посоветуйте, что почитать?


ИИ: Для начала прочитай рекомендации от разработчиков Docker, вряд ли кто-то знает Docker лучше них.


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

Комментарии (60)


  1. SLASH_CyberPunk
    16.05.2019 12:44

    docker build --squash


  1. bm13kk
    16.05.2019 12:49
    +1

    Мне одному кажется что простыни
    ```
    RUN && && && && && && && && && &&
    ```
    явно говорят о проблемах с изначальным дизайном?


    1. Flaminestone
      16.05.2019 13:27

      Это не обязательно говорит о проблемах. Часто надо настроить окружение для работы, и не всегда можно найти идеально подходящий образ. Вот и приходится брать наиболее близкий к тому, что надо, и докидывать туда недостоющие компоненты.
      Если не нравятся простыни, то можно вынести их в отдельный образ и залить на докерхаб)) И рабочий докерфайл будет более чистым.


      1. Magvai69 Автор
        16.05.2019 13:32

        Ну или хотя бы в свой registry


      1. zidex
        16.05.2019 13:39

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

        LAYER 1
        RUN…
        RUN…
        COPY…

        LAYER 2
        RUN…
        RUN…
        COPY…


      1. bm13kk
        16.05.2019 14:01
        +1

        Это говорит о проблемах. Никто же не пишет код `a.b() && c.d()` кроме редких случаев. И то есть апологеты, которые говорят о разбивке этих случаев тоже ради дебага.

        Вообще скваш должен решать такую проблему. Но потом начинають вылазить артефакты при оптимизации размера образа. Или версионности.

        На самом деле сам подход когда докер файл становится баш файлом с привкусом докера — уже говорит о многом.


    1. slonopotamus
      16.05.2019 14:42

      Там и в самой это простыне ад. curl, apt-get, gem install, bundle install… Вспоминается башорг. Только в случае докера это почему-то считается нормальным.


      1. JTG
        16.05.2019 19:16
        +2

    1. Tanner
      16.05.2019 15:36

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


    1. VSOP_juDGe
      17.05.2019 09:01

      Да, и эти проблемы уже решены
      docs.docker.com/develop/develop-images/multistage-build


  1. usego
    16.05.2019 13:05

    Всё хорошо до просмотра логов. Нельзя пускать девов на ноды, пусть в сборщике логов их смотрят.


    1. Magvai69 Автор
      16.05.2019 13:10

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


  1. Sirikid
    16.05.2019 13:14

    Если ваш софт завязывается на glibc, то вы ССЗБ.


    1. rkfg
      16.05.2019 19:21
      +1

      А как надо?


    1. Prototik
      17.05.2019 08:44
      +1

      Тут не «завязывается на glibc», а собрано для glibc. Просто так взять бинарник для glibc и запустить на musl или практически любой другой реализации libc, не нацеленные на совместимость с glibc (всякие eglibc в расчёт не берём) не выйдет.


  1. yehorh
    16.05.2019 15:43
    +1

    FROM ubuntu:latest


    Использование тэга latest приводит к непредсказуемым последствиям.

    а вот устанавливать пакеты последних версий это уже нормально, да?


    apt-get -y install libpq-dev imagemagick gsfonts nodejs


    1. Magvai69 Автор
      16.05.2019 15:44
      +1

      Ну по факту, да пакеты тоже лучше ставить определенной версии. Тут вы правы.


  1. Graphite
    16.05.2019 16:16

    Вы берете тэг latest. Использование тэга latest приводит к непредсказуемым последствиям.

    Т.к. все равно есть свой registry, то можно положить конкретную версию базового образа туда и сделать так:


    ARG registry=myregistry.exampe.com
    FROM $registry/nginx:latest

    В итоге мы сами себе мейнтейнер и сами решаем какая у нас версия текущая.


    Плюсы:


    1. Даже если кто-то как-то подменит версию в докерхабе на версию с бекдором нас это не затронет.
    2. Если нужно выкатить срочный патч для уязвимости — обновляем версию в нашем registry и пересобираем образы, а не бегаем по всем докерфайлам и обновляем версии руками.
    3. За счет ARG=registry можно локально собрать контейнер с другой версией не меняя докерфайла. Качаем новую версию, тегаем ее local/nginx:latest и собираем с registry=local.

    Минусы:


    1. Нужно очень внимательно следить за состоянием своего registry — потенциально любое обновление затрагивает все.
    2. В идеале нужно хранить историю тегов — в какой момент и на какой образ указывал.

    Ну на то мы и мейнтейнеры, чтобы за этим следить. В идеале перед переназначением latest нужно собрать все контейнеры с новой версией и прогнать тесты. Например, заведя еще один registry next-release или принимая тег в качестве аргумента.


    1. Magvai69 Автор
      16.05.2019 16:24

      Даже в случае своего registry, я не рекомендовал использовать тэг latest.

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


      1. Graphite
        16.05.2019 16:46

        Ну так сначала тестируем, а потом обновляем:


        1. T минус месяц — дорогие разработчики, через месяц дефолтной станет версия Java 10.0.500. В ней такие-то изменения, из которых вот эти два потенциально могут что-то сломать. Мы собрали ваши образы с этой новой версией и прогнали тесты, посмотреть результаты можно тут. Если вы хотите переехать на новую версию уже сейчас — поменяйте в своем пайплайне arg registry на новый.
        2. Раз в неделю-две шлем напоминания с возможностью нажать кнопку "Я уже проверил, у меня все ок". Особенно надоедаем тем, у кого тесты на новой версии падают.
        3. Т минус день — напоминаем еще раз.
        4. Т — обновляем версию, снова все тестируем и всем напоминаем.
        5. Если все сделано по уму, то у ленивых жоп падает сборка или тесты, в прод ничего не уезжает. Ленивые жопы не могут релизить свои новые фичи и начинают жаловаться. Тыкаем их носом в 5 предупреждений и инструкцию по переходу, они фиксят.

        Само собой если обновление серьезное, то не месяц даем, а больше.


        Зато если прилетела критическая уязвимость в каком-то софте, мы ее патчим (и только ее), обновляем базовый образ и запускаем стандартный пайплайн build-test-release-deploy для всего. Если что-то упало — оно не деплоится и срочно фиксится. В случае же с фиксированной версией придется подменять образ не меняя тега, а это уже моветон.


        1. Magvai69 Автор
          16.05.2019 16:53

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


          1. slonopotamus
            16.05.2019 17:04
            +1

            Нельзя. Потому что все забьют и никто ничего не будет обновлять.


            1. vdonich
              16.05.2019 19:19

              Я не специалист — а можно старую версию из registry удалить?
              Разумеется, после письменного уведомления.


              1. Graphite
                16.05.2019 19:32

                Если наш registry, то конечно можно. С чужим — зависит от того, поддерживают ли они это или нет.


                1. vdonich
                  16.05.2019 22:45

                  Ну тогда и без проблем — SLO не распространяется на deprecated версии, которые, к слову, в любой момент могут быть удалены из registry из соображений целесообразности.

                  Наша команда с радостью поможет (покажет документацию) с апгрейдом!


              1. Magvai69 Автор
                16.05.2019 19:33
                +1

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


                Мне, не нравиться, любые механизмы, где что-либо происходит насильно.


                1. vdonich
                  16.05.2019 22:43

                  > Мне, не нравиться, любые механизмы, где что-либо происходит насильно.

                  Это по-человечески понятно, но иногда консенсус невозможен — у какой-то команды релиз, им какая-то вещь нужна ну прям счас, и непременно на этой версии, а на апгрейд времени нет. И не было. Может, в конце года, после запуска. И вообще, у них приоритеты, что вы тут со своими апгрейдами, мы тут прибыль делаем (и дальше по списку).

                  Кстати, почитайте про liberum veto, к слову. Для общего, так сказать, развития.


          1. Graphite
            16.05.2019 17:06

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


            Даем разработчикам полную свободу — получаем зоопарк и кучу древних версий, которые лень обновлять. Чем дольше они тянут — тем больше несовместимость, тем сложнее переход. В конце концов для старой версии найдут RCE, а переход на исправленную версию займет полгода.


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


      1. mayorovp
        17.05.2019 08:44

        Кажется, докеру тоже нужны свои лок-файлы...


  1. poxvuibr
    16.05.2019 19:16
    +1

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


    И тут опять шелл скрипт для конфигурации системы, только в докере. Что же с нами стало? Как так вышло то? Почему это опять хорошо?


    1. Magvai69 Автор
      16.05.2019 19:19
      +1

      История циклична. Уверен, рано или поздно придумают "ansible для Dockerfile":)


      1. Xop
        16.05.2019 20:31
        +1

        Вы не поверите… docs.ansible.com/ansible-container


        1. Magvai69 Автор
          16.05.2019 20:36
          +1

          Спасибо. Прикольно. Интересно, надо будет попробовать и протестировать.


          1. Xop
            16.05.2019 20:40

            Не за что. Кстати, решил поискать не было ли статей на хабре про эту штуку, и внезапно — статья от Southbridge: habr.com/ru/company/southbridge/blog/306488. История действительно похоже циклична


            1. Magvai69 Автор
              16.05.2019 22:06
              +2

              Оказалось, он уже депрекейт. А на смену ему пришел: https://github.com/ansible-community/ansible-bender


        1. george1313
          17.05.2019 09:28

          А ведь с момента вброса идеи прошел всего час…


    1. bkonst
      17.05.2019 02:28

      А тут разные ниши — Docker предполагает сборку нового артефакта, а не изменение существующего сервиса. Если что-то в цепочке падает — это повод сборке немедленно остановиться, заорать и заставить пересмотреть процесс сборки до того, как этот артефакт будет выкачен.


      1. bm13kk
        19.05.2019 21:00

        В теории я полностью согласен. На практике — фиг отдебажеш. Поэтому в 99% (а может и больше девяток) — сначала перезапустят, а потом будут проверять.

        Тем самым, баги сборки годами сидят на своих местах и никому нет дела. До тех пор пока они не просто ломают вборку — а ломают все сборки постоянно и блочат вообще все.


        1. bkonst
          20.05.2019 01:10

          Можно конкретный пример, как правильный Ansible помог бы отдебажить такой плавающий баг в сравнении с правильным Docker-ом?


          1. bkonst
            20.05.2019 01:12

            И "перезапустить, а потом проверять" — это правильный подход. Сначала быстро решить проблему с лежащим сервисов, потом копать причину.


            1. bm13kk
              20.05.2019 08:51

              Докер построен вокруг принципа отказоустойчивости и переиспользования образов. Если надо сегодня не релизить — значит не будем релизить и пережить на старом образе. А релизить с заведомо известным багом — подход известной картинки «хк, хк и в продакшен»


          1. bm13kk
            20.05.2019 08:42

            первое, ансибл все централизованно логирует
            второе, его доки просят писать каждую команду отдельной я не гонять простыню `RUN && &&`


            1. bkonst
              20.05.2019 09:42

              Можно пример бага, пожалуйста?


              1. bm13kk
                20.05.2019 10:21

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

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

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


                1. bkonst
                  20.05.2019 10:28
                  +1

                  Как ансибл бы решил проблему с битым пакетом в одном из зеркал?


                  Что значит "не успевал вытянуть зависимость" — у вас ограничение по времени на сборку докера? Или (фу-фу) зависимости подтягиваются в момент запуска контейнера? Как Ansible решил бы аналогичную проблему?


                  1. bm13kk
                    20.05.2019 14:02

                    ансибл не решает ни одну из проблем.

                    Ансибл был построен с мыслей в голове, что деплой падает, чем мы можем помочь? В нем есть инструменты которые хоть как-то помогают — еще раз централизованный лог и изолированность каждой команды.

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


        1. mayorovp
          20.05.2019 08:45

          Что именно там перезапустят при упавшей сборке?


  1. Dreyk
    17.05.2019 00:18

    в debian-based имеджах apt-get install сам за собой чистит, отдельной команды не нужно


    1. Magvai69 Автор
      17.05.2019 09:45

      Сделал тесты, вы правы.
      Но, все же, разработчики образа советуют выполнять: rm -rf /var/lib/apt/lists/*


      1. Stanislavvv
        17.05.2019 10:49

        lists — не то же самое, что и файлы пакетов, обновляются по apt-get update и их наличие требуется для последующего apt-get install, который с точки зрения apt может прилететь когда угодно, поэтому никто их удалять автоматически не будет.


  1. zurapa
    17.05.2019 04:38
    -1

    Класс! Появилась ещё одна прослойка (я о Docker), а проблемы остались всё те же (тупенькие разрабы не вникающие в детали, потому что некогда разбираться)…
    Кто-нибудь расскажет мне вообще для чего docker нужен-то?.. Это реторический вопрос админа старовера не нужно отвечать. Мне нужно просто выговориться.


    1. nikola_sa
      17.05.2019 08:15

      Серьезно, у админа есть вопрос для чего нужен Docker? А что вы администрируете если не секрет?
      Сколько у вас приложений? Предположу что Windows стек. Какой свежести у вас дистрибутивы?


  1. Ondator
    17.05.2019 10:10

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


    FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
    WORKDIR /app
    
    # copy csproj and restore as distinct layers
    COPY *.sln .
    COPY aspnetapp/*.csproj ./aspnetapp/
    RUN dotnet restore
    
    # copy everything else and build app
    COPY aspnetapp/. ./aspnetapp/
    WORKDIR /app/aspnetapp
    RUN dotnet publish -c Release -o out
    
    FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS runtime
    WORKDIR /app
    COPY --from=build /app/aspnetapp/out ./
    ENTRYPOINT ["dotnet", "aspnetapp.dll"]

    Не удобнее ли будет поступить так же, что бы не вычищать образ?


    1. Magvai69 Автор
      17.05.2019 10:11
      +1

      Для ruby, так не делают, а вот например для go, это заходит хорошо. Да и для любого языка, где на выходе вы получаете исполняемый файл.

      Так же, такой подход, хорош когда надо скопировать статику на фронт.


      1. TonyLorencio
        17.05.2019 10:26

        Для тех случаев, когда продакшн-образ содержит только самое необходимое, также встречал вариант, когда существуют как минимум два образа:


        1) минимальный — для запуска продакшна
        2) с компилятором/исходниками/полным рантаймом/дебаггером (ненужное в зависимости от языка — вычеркнуть) — для локальной разработки


      1. Graphite
        17.05.2019 10:50

        На первом образе заодно можно тесты гонять и не тащить их вместе с зависимостями в прод.


        Для фронтенда подход вообще замечательный, т.к. для сборки JS/CSS нужно одно окружение (Node, Babel, Webpack и т.п.), а в проде совсем другое.


      1. tendium
        17.05.2019 11:20
        +1

        Для go у меня был такой вот Dockerfile:

        FROM scratch
        MAINTAINER Developer <developer@company.example>
        COPY binary-artifact /
        ENTRYPOINT ["/binary-artifact"]
        

        Сам `binary-artifact` возникал в пайплайне bitbucket.


  1. Magvai69 Автор
    17.05.2019 10:13
    +2

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


    1. Graphite
      17.05.2019 11:01

      Это да, нужно pull делать перед сборкой. Можно вообще сборку проводить в докер контейнере, который создается с чистым окружением заново, но тогда теряются все преимущества кеширования. Как вариант — два пайплайна, один для релизов / nightly всегда полный билд из чистого окружения, другой с кешем и --pull для постоянных тестов.


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


  1. xkondorx
    17.05.2019 10:29
    +2

    Наконец-то Docker здорового человека…