Pull to refresh

Comments 66

Вот бы еще был разбор получившегося Dockerfile на предмет того, что и чем в нём не правильно… Нет, все понятно, но что конкретно?..
Мне кажется, всё же стоит пояснить. Мало ли, я чего-то не заметил.
Ну, комментарии должны быть на английском и отвечать на вопрос «зачем?», а не «что?».
RUN'ы надо объединить в один (один ли?).
Исходники надо копировать последним шагом перед точкой входа? Или прямо монтировать их директорию снаружи?
Вместо CMD, наверное, надо использовать ENTRYPOINT? Supervisor не нужен тогда, kubernetes сам справится. Но они хотят ещё ssh зачем-то, хотя Kubernetes позволяет и без них в контейнеры заходить. Да ещё passenger какой-то, что это вообще? Может тогда и правильно, что Supervisor.
Базовый образ может оказаться слишком затратным по ресурсам и тогда придётся перейти на alpine или centos? Но возможно у них лишние сервера простаивают и это преждевременная оптимизация (как и всё остальное, кстати).
Все компоненты и пакеты ставятся из внешних источников, а не со своих безопасных доверенных зеркал, это риск как обычной поломки, так и взлома.
Версии всех внешних зависимостей (и базового образа в том числе) надо фиксировать.
apt-get upgrade не нужен, актуальность пакетов базового образа — ответственность его автора.
Кэши сделаны не для того, чтобы их чистить. Но я не знаю что это за кэши, возможно, тут это оправдано, хотя в таком случае надо отразить в комментарии.
Наверное не всё верно и многое не замечено. Короче говоря, давайте вместе поможем Даше путешественнице исправить dockerfile.
init.sh, как следует из текста, запускает несколько процессов. Концепция докера: один контейнер — один процесс под конкретную задачу. Соответственно контейнер надо разбить на несколько.
Что не так? Последовательно:
1. Очистка кэшей и тд сделано отдельным RUN, это приводит к созданию еще одного слоя, где эти данные удалены. Но, предыдущих слоях они остаются. Что бы это было ок, надо чистить в том же RUN. Правильно:

RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*


В этом случае, в слое не будет лишних данных. Аналогично и с зависимостями ruby.

2. Для выполнения RUN gem install bundler весь код не нужен. Достаточно скопировать:
Gemfile и Gemfile.lock. Они редко меняются и в большинстве случаев слой будет браться из кэша.
3. ssh там не нужен
4. Supervicor там тоже не нужен, на выходе должно быть несколько контейнеров:
С бэком и passenger в качестве бэкэнда.
С демонами, по одному на каждый демон.

Идеалогия Docker один контейнер, один процесс.
5. Версии должны фиксироваться. Использовать тэг latest это плохо, так как каждый раз мы можем получить совершенно разные контейнеры с разным поведением.
6. Желательно использовать не root в контейнере. По этому поводу, даже была недавно статья на хабр.
7. Использовать большие образы, это плохая практика. Так как, чем больше базовый образ, тем больше в нем багов.

Это из основного.
ssh не нужен, потому что можно коннектиться с помощью самого докера?
С помощью докера можно выполнить любую команду в контейнере. В том числе /bin/sh и получить локальный шелл с помощью которого делать все что хочется.
Я как человек, нихрена не понимающий в слоях и суперви(з, к)орах, все-таки ожидал увидеть все это не среди комментариев, а в самой статье. И желательно — с более подробным разбором, на пальцах и «для полностью тупых» — что такое слой и почему в нем плодятся данные, что за Gemfile, почему не нужен ssh и т. п.
Так у вас бы получилась хорошая, годная статья для новичков, которая им помогала бы освоиться с докером.
Сейчас у вас получилась статья-байка для посвященных: почитать, посмеяться, сказать «да, так все и бывает!». Для новичка толку в такой статье маловато. А заставлять читать комментарии в поисках ответов на вопрос «что же не так?» — это такое изощренное издевательство над читателем.
> все-таки ожидал увидеть все это не среди комментариев, а в самой статье.
Цель этой статьи была другая. Тут, совсем не преследовалась цель показать, как надо делать и уж тем более, объяснить почему так не надо делать. Разобрать все кейсы, все ошибки невозможно. Я постарался показать, наиболее распространённые ошибки, а кому это нужно, может посмотреть документацию, комментарии и множество статей и тем самым хорошо разобраться с данным вопросом.

Если бы, статья содержала подробный разбор, это было бы не так полезно. ИМХО.
Ну вообще-то вам правильно указали — статья либо «для посвященных, посмеяться, поскольку они и так это знают», либо ни о чем.
Для кого эта статья?
Скажите, а этот стих, требует разъяснений?

Если вы еще не твердо
В жизни выбрали дорогу,
И не знаете, с чего бы
Трудовой свой путь начать,
Бейте лампочки в подъездах —
Люди скажут вам «Спасибо».
Вы поможете народу
Электричество беречь.

Я думаю что нет. Так же и со статьей, если ты не понял, почему так не надо делать. То, стоит почитать соответствующий раздел документации.
Иначе мы так и будет жить в мире копипаста.
Если вы сравниваете со стихами Остера — может быть следует просто отправить всю статью в «Юмор»?

Вкратце вся статья выглядит так: «вот этот код — плохой. Почему плохой и какой будет хороший — выясните сами».
Так же и со статьей, если ты не понял, почему так не надо делать. То, стоит почитать соответствующий раздел документации.

Какие именно «соответствующие»?

Чтобы понять, какие разделы читать, нужно сначала понять, в чем именно ошибка. А именно это вы почему-то оставили за рамками статьи.

Это для вас ясно, как божий день, что не надо много строчек RUN писать. А для новичка что так, что этак — одинаково выглядит. Именно поэтому новички и хреначат такой вот лапшекод. Ну и — спрашивается в задачнике — что для себя новичок полезного почерпнет из статьи, если плохие примеры в ней есть, а объяснений, почему они плохи — нет?

UPD: вот и первые ласточки: habr.com/ru/company/southbridge/blog/449944/#comment_20090634
А что не так с (5)? В этой статье предлагают фиксировать версии, а в соседней называют суицидниками тех, кто сидит на фиксированной WinXP (докер же, как я понял, будет доступен из внешнего мира?). Где та грань и в каких условиях проявляется?
Желательно фиксировать версии в докерфайле, чтобы минимизировать количество ситуаций «ничего не знаю, у меня всё работает — проблема на вашей стороне». Часто это одна из основных целей внедрения докера.

Это не значит, что не надо обновляться. Просто обновление происходить должно путём указания новой версии в докерфайле.
Можете объяснить или дать ссылку на объяснение, чем плохо использовать рута?
И всё-таки что было бы системным правильным решением junior'а?

  1. смена места работы (риск умереть с голода)
  2. спор до упора с senior'ом с привлечением остальной части команды (риск обзавестись личным врагом и потерять работу, и см. пункт 1)
  3. жалоба руководству на некомпетентность коллеги (риск стать проблемой, а не решением, goto пункт 1)
  4. молчаливое согласие, в надежде в будущем стать senior'ом и навести порядок (риск преждевременного выгорания или ухудшения здоровья)

На мой взгляд, тут было варианта:
1. Пообщаться с ИИ, и попросить его помочь. Потом, предложить TeamLead уже готовое решение.
2. За пару выходных разобраться самому и предложить TeamLead готовое решение.

Вряд ли Lead настолько глуп, что откажется уже от готового решения. А если это и так, то продолжать потихоньку прокачивать свои знания и предлагать свои решения, более правильные. И как знать, может через полгода год, jinior станет Lead:)
На мой взгляд было бы здорово, если бы вы после каждого отдельно взятого плохого примера действий под спойлером кратко поясняли почему это плохо) а то я вот повелся на заголовок, с докером дел не имел и вообще не понял, как делать нужно, если не так, как в статье)

девляпс:
curl -sL https://deb.nodesource.com/setup_9.x | sudo bash -


P.S. чего потом удивляться, что докерхаб ломанули? культура такая — не приучила мама в детстве грязное не брать в рот...

Да все отлично, как раз завтра, в конце рабочего дня, планируем его в прод выкатить)
Как ваши глаза поживают?)
Нипонял, а где код из репозитория копируется в имедж?

RUN git clone git@mainrepo.int.company.com:software/software.git ./


А захардкоженные пароли в Dockerfile забыли что ли?

Какие-то вредные советы слабаков…
Вот же, код копируется: COPY ./ /app

Да, еще можно еще много, чего добавить. Например, установку systemd.

К примеру из поста следующее, очевидно, не подходит, но тем не менее:


Для компилируемых языков до сих пор можно встретить, что в production-образе висит компилятор со всеми зависимостями.

Да, да, такое тоже встречаем периодически. И под go, видел контейнеры по 500Мб. Вместо, 2-3 мб.
Под go сотню-другую мегабайт может занимать просто потому что используется универсальный базовый образ, та же ubunta. А задачи минимизации образа не стоит. Или есть из разряда «nice to have».

Под Go чаще можно увидеть в качестве базового образа alpine, или вообще образ собран с нуля (scratch), чем Ubuntu/Debian и прочее даже в минимальном образе.

Где-то в паблике может и чаще («чтоб не позориться»?). Но кто знает, что творится в инхаус репозиториях :)

По моему опыту, в «кровавом ентерпрайзе» не склонны утверждать предложения " давайте поменяем базовый образ всех наших контейнеров" только потому что «меньше места занимает». Вот принесите ТЭО типа «с текущим базовым образом мы тратим 1000$ в месяц, с новым будем тратить 200$, полный регресс-тест системы обойдётся в 10000$, срок окупаемости без учёта рисков 12,5 месяцев»
Вот принесите ТЭО типа «с текущим базовым образом мы тратим 1000$ в месяц, с новым будем тратить 200$, полный регресс-тест системы обойдётся в 10000$, срок окупаемости без учёта рисков 12,5 месяцев»

На самом деле это правильно… А вдруг — экономия на размере будет мифическая?
Экономия на размере будет, а вот экономии на деньгах может и не быть :) Особенно если учесть обучение разработчиков, девопсов и админов работе с новым базовым образом. Переписывание докерфайлов. Инциденты, когда внезапно в новом образе не оказывается привычных инструментов типа bash и из-за этого привычные пути диагностики и локализации перестают работать, а перед запуском никто о них не вспоминал.

Ну и собственно риски, что прикладное ПО или непосредственная среда его исполнения в каких-то случаях рассчитывает на одно поведение ОС и относимых к ней библиотек, а новом дистрибутиве оно другое.
в новом образе не оказывается привычных инструментов типа bash

Но ведь докер совсем не об этом

То, что в одном контейнере можно запустить более одного приложения, в том числе и через docker exec, не значит, что так нужно делать

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

У вас неправильное представление о контейнерах. Контейнер — не виртуальная машина. Один контейнер — одна задача.


Приложение должно уметь отправить некоторые метрики о себе наружу.


Чем это будет отлавливаться снаружи, кто из людей и как это будет смотреть и через какой шелл/веб-интерфейс/что-то ещё — уже совсем не является ответственностью этого приложения и этого контейнера.

У меня нормальное практичное представление о контейнерах. Не «один контейнер — одна задача» (отправка метрик о себе наружу — это вторая задача, кстати), а ограничение прав процессов по принципу «всё что не разрешено явно — запрещено».
Да ну, до реальной жизни тут далеко…

Василий: а как нам либу [libaname] устанавливать?
Петр: а как ты ее себе на комп ставил?
Василий: а я по инструкции «быстрой установки» ее с какого-то докер-репозитория стащил и запустил в докере локально
Петр: хмм, ну ладно, давай пока так же сделаем, а потом разберемся как ее собирать. По-быстрому поставь там внутри контейнера еще один докер и там же имдж скопируй внутрь — быстро устанавливаться будет, нам девопсы еще спасибо скажут.
PS. Для тех, кто всё же хочет что-то полезное, и невнимательно читает «дурные советы».
Минорные советы по финальному докерфайлу сжато (чтобы не читать эту всю статью):

# Ой, не надо делать latest, ой сломается когда-нибудь что-нибудь из-за этого!
FROM ubuntu:latest
# Всё копировать не надо, это приводит к тому, что любое изменение в сорсах — и весь кеш в помойку.
# Папка приложения
WORKDIR /app

# Обновляем список пакетов и обновляем их, устанавливаем пакеты и bundler. Одной командой
# PS. А зачем там столько всего? Ладно, наше дело dockerfile, а не внутренности
RUN apt-get update && \
apt-get upgrade && \
apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor && \
gem install bundler

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

# Копируем Gemfile'ы
# Всё, что выше, будет отлично кешироваться
COPY Gemfile* ./

# Устанавливаем зависимости
# Даже это будет кешироваться, если зависимости не поменялись
RUN 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/*

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

# Запускаем скрипт, при старте контейнера, который запустит все остальное.
CMD ["/app/init.sh"]
Там выше картинка со слоупоком была уже.
Вы заголовок статьи пропустили, наверное.
Возможно, не только заголовок, но и всю статью
Да, вы правы, так делать не надо.

Весь Dockerfile показывает, как делать не надо. На то, они и вредные советы;)
(Не трогайте комментаторов моего сообщения, плз. Я сначала не заметил тег и написал комментарий. А потом понял и добавил PS. В результате комментарии от baldr и далее выглядят неуместно)
Да, теперь мой коммент выглядит грубо, сорри.
А еще:
1. сборку статики лучше сделать отдельно в предстоящих шагах CI, и в контейнер забрать готовую — Сборка отдельно \ продакшн отдельно

2. скриптом запускать всё же как-то не камильфо, стартуем 1 процесс

3. а точно ли нам нужен целый образ большой убунты? пока так не выглядит
Открываете свой Dockerfile, сравниваете с тем, что в статье.
Если есть похожие места — пора срочно искать ИИ.

Который самозародится в этом докерфайле?

Спасибо, посмеялся от души узнав многие свои ошибки, которые делал вначале, а также пару, которые делал до сих пор. На проде контейнеры не используем, только на дев-машинах.
Статья реально для тех кто уже «набил шишки», именно поэтому многие негодуют, что не было разбора ошибок. Отправлять всех кому непонятно, что здесь не так, читать документацию — по-моему не выход. Документации по докеру на неделю чтения и если бы вы описали проблемные места вашего докерфайла, то закрыли бы процентов 80% документации за 10 минут. В данном же виде статья годиться только для того, чтобы поделиться своей болью с другими бывалыми и посмеяться над новичками, но ни как не для образовательных целей. В таком случае вам действительно нужно указать тег «юмор».
К понятию, что «так делать не стоит» многие новички могут прийти и самостоятельно, но на это уйдёт куча времени. Хорошо, что уже несколько комментаторов дали пояснения проблемных участков.
Я тогда сначала скопирую Gemfile и Gemfile.lock, потом все поставлю, и потом уже копирую весь проект. Если Gemfile не меняется, слой возьмется из кэша.
Вот эту ошибку я допускаю до сих пор, потому что в зависимостях у меня всего один пакет и установка происходит быстрее чем в моей голове успевает появиться мысль, что здесь что-то неоптимально и надо бы исправить. После прочтения комментариев, в которых «разжевали» почему так делать не стоит, я больше не буду наступать на эти грабли. Согласно вашему посылу, я из-за этого одного непонятного мне момента должен был идти в документацию по докеру и читать всё ещё раз. Но кого мы пытаемся обмануть? Я думаю после прочтения вашей статьи никто не пойдёт читать ещё раз документацию по докеру. Кроме докера каждый разработчик использует ещё кучу инструментов и терять столько времени на каждый из них — непозволительная роскошь.
Большинство из нас и не собирается стать гуру в докере, достаточно всего лишь не совершать грубых ошибок.
Спасибо за развернутый комментарий.
Да, я уже понял, что надо еще одну статью писать. В ближайшее время напишу и опубликую. К этой статье добавлю ссылку.

На самом деле половина бестпрактисов про написание докерфайлов — это компромиссы. И, как говорится, your mileage (experience) may vary. Например, взять четко указанные версии ПО. В тегах и в пакетах. С одной стороны — это гарантирует более повторяющиеся сборки, чем лупить везде latest. С другой стороны — выше нагрузка на разработчика. Вот давайте подумаем. Докерфайл — это по сути чертеж. В одном чертеже важно указывать материалы, а в каком-то месте материал может быть не принципиален. И стоит ли тогда тратиться на то, чтобы полностью все определять? Потребительские качества конечного изделия одинаковы. И ещё момент, что даже наличие четких тегов не гарантирует, что разработчик этого образа не перезапишет его. Единственное спасение — пин по sha, но кто его использует?
С вендорами (go, nodejs etc.) примерно аналогичная история.


Поэтому самое главное — научить видеть подводные камни в каждом из способов/решений, а не рассуждать догмами.

Да, тем более что через даже полгода четко указанные версии могут внезапно пропасть из репозиториев (привет npm!).
RUN wget гиг_данных
RUN rm гиг_данных

создаёт два fslayer, в одном из которых гиг_данных…

В итоге удалять те же кэши apt в указанном Dockerfile нет никакого смысла, ничего не сэкономишь. Весьма распространённая ошибка.
Можете пояснить про COPY ./ /app
Не совсем понял почему это не верно и как нужно делать правильно и почему.
И еще офтоп столкнулся с проблемой, верно ли что микросервисы сами должны быть в подвешенном состоянии если они требуют зависимости которые еще не подняты, например база запускается позже чем приложение, значит на уровне приложения нужно создавать какой то чекер раз в 10 сек и ждать базу?
В докерфайле из статьи этот шаг выполняется в самом начале. То есть, при любом изменении исходного кода кэш докера окажется бесполезным, будет создан новый слой образа. И все последующие шаги будут основываться на этом новом слое, то есть опять же не смогут быть закешированы. Это приведет к по сути пересозданию образа целиком при каждой сборке, что неэффективно как по затратам времени, так и по затратам требуемого места.

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

Нет, это в общем случае неверно.
Есть две крайности:


  1. Сервис, который при условии отсутствии своей зависимости падает всегда (и потом рестартуется)
  2. Сервис, который вечно ждёт своей зависимости ( и при этом не падает с исключением, а в логах пусто).
    Обе ситуации очень мерзкими могут быть. Ещё может быть третий вариант — например, зависимость "ушла" во время работы Вашего сервиса (ну, там сеть моргнула, автоскейлинг не сработал или что-то ещё). Вот и думай — как лучше. Зависит от конкретной ситуации.

Правильно после установки глобальных системеных зависимостей (всякие apt install, wget | bash -):


COPY Gemfile* ./
RUN bundle install --without development test --path vendor/bundle && rm -rf /usr/local/bundle/cache/*.gem
COPY  ./ /app

При таком подходе bundle install не будет запускаться на каждое изменение исходников. Только если изменился Gemfile или Gemfile.lock будет обновляться vendor/bundle. Также и кэш бандлера в образ не попадёт.


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

Тут, дело не столько в микросервисах, подобный кейс бывает и при монолите.
Тут, надо учесть 2 момента:
1. Автоматическое переподключение приложения к БД.
2. Helth check который отдает данные по работе приложения, в частности с учетом доступности/недоступности БД. И вешать его на мониторинг.
UFO just landed and posted this here
А если длинны строки не хватит? Там, наверняка ограничение в 255 символов.

что за глупости? Можно и multi-line делать. Проблем-то?
Ограничений на длину выполняемой команды в блоке RUN, кстати, я не увидел.
Более корявого и контр-интуитивного способа управления слоями нельзя придумать. Неужели нельзя было в каком-то явном виде сделать управление этими слоями?

не используйте docker build. Есть же kaniko и buildah.
Dockeкfile лишь самое популярное средство построения образов. Их много разніх, начиная от ручного снятия снэпшотов контейнера
Sign up to leave a comment.