- "А расскажите, пожалуйста, что не так или так в этом докерфайле. Что выглядит нормально, что смущает, что кажется вы сделали бы иначе? Что может быть вы переделали. На что и почему. Или вообще тут всё идеально и трогать ничего не надо. Поделитесь мыслями. можете пользоваться любой документацией, любой информацией в интернете, кроме AI. Особого лимита времени тут нет, просто говорите всё, что вы об этом думаете, это главное. Правильного/правильных ответов тут нет, интересно всё то, что вы об этом всём думаете."
Давным-давно я проводил интервью и спрашивал кандидатов обо всяком.
В один момент времени я начал использовать этот замечательный докерфайл.
Лично мне он помогал определить сильные и слабые стороны кандидата(да, надо было много работать с кубером и докер имаджами на этой вакансии). На базе него я иногда задавал дополнительные наводящие вопросы: про readOnlyRootFilesystem/allowPrivilegeEscalation/capabilities, про опыт с бекендом, опыт с фронтом, про контекст и так далее. Не для доёба, нет, кому оно нужно.
Просто понять насколько глубоко в бездну нырял кандидат со своим опытом и сможет ли не перейти на темную сторону упоротых девелоперов, пройдя этапы собеседования.
Конечно же я знаю множество инженеров, кто найдёт миллионов доводов, что собеседование неправильное, не верное, вопросы говно, файл тупорылый, такое не надо спрашивать и многое другое.
Да всё возможно.
Однако тут нанимал я, а не кто-то другой, так что козырь самодура🤡 интервьюера был у меня в руках.
Сейчас подобные наработки я использую, чтобы гонять своих интернов на логику и получения опыта без хлебания половника подливы говна из клоунско-птушных легаси проектов с монорепой монолитом.
#собеседование #интервью #всратость #docker
FROM ubuntu:latest
COPY ./ /app
WORKDIR /app
RUN apt-get update
RUN apt-get install -y cowsay
RUN apt-get upgrade
RUN apt-get -y install libpq-dev imagemagick gsfonts ruby-full ssh supervisor
RUN gem install bundler
RUN echo "vm.swappiness=10" >> /etc/sysctl.conf && chmod -R 777 /tmp
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/*
CMD [“/app/init.sh”]
Давным-давно я проводил интервью и спрашивал кандидатов обо всяком.
В один момент времени я начал использовать этот замечательный докерфайл.
Лично мне он помогал определить сильные и слабые стороны кандидата(да, надо было много работать с кубером и докер имаджами на этой вакансии). На базе него я иногда задавал дополнительные наводящие вопросы: про readOnlyRootFilesystem/allowPrivilegeEscalation/capabilities, про опыт с бекендом, опыт с фронтом, про контекст и так далее. Не для доёба, нет, кому оно нужно.
Просто понять насколько глубоко в бездну нырял кандидат со своим опытом и сможет ли не перейти на темную сторону упоротых девелоперов, пройдя этапы собеседования.
Конечно же я знаю множество инженеров, кто найдёт миллионов доводов, что собеседование неправильное, не верное, вопросы говно, файл тупорылый, такое не надо спрашивать и многое другое.
Да всё возможно.
Однако тут нанимал я, а не кто-то другой, так что козырь самодура🤡 интервьюера был у меня в руках.
Сейчас подобные наработки я использую, чтобы гонять своих интернов на логику и получения опыта без хлебания половника подливы говна из клоунско-птушных легаси проектов с монорепой монолитом.
#собеседование #интервью #всратость #docker
👍9👌1
#начинающим #docker
Контекст(context).
Мне понадобилось некоторое время на этапе моего обучения несколько лет назад, чтобы поесть всякого, чтобы осознать существование контекста.
Основной пример буду пояснять на примере Docker.
Например, когда вы пишете😈 .
Допустим мы собираем имадж так:
Вот точка это и есть контекст. Точка это наша директория нашего проекта.
Что же нам надо знать о контексте?
- нельзя выйти за пределы контекста
Например у нас есть такая структура
Находясь в директории
Так как shared находится выше по иерархии и не входит в контекст текущей директории "."
- докер(и другие известные мне утилиты) сканирует все файлы контекста( то есть в текущей директории)
То есть условно если у вас есть 50000 файлов по мегабайту или один файл 50 гигабайт в гит репозитории в контексте, то это замедлит сборку
Даже если мы НЕ используем
Не думаю, что этот случай для всех, с этим можно столкнутся при не совсем корректных воркфлоу и при монолит приложениях.
Например у вас npm build идёт ВНЕ сборки докера, а потом вы копируете файлы(я видел и такое).
Проблема с переполнением контекста решается через
В этом файле мы указываем те директории/файлы, которые НЕ должны попасть в контекст, по аналогии с
- контекст в
Контекст доступен для всех этапов сборки, однако при использовании
Концепция контекста применима не только к Docker, но и к другим инструментам, например, к Helm - менеджеру пакетов для кубера.
Некоторые инженеры сталкиваются с ситуацией, когда во время деплоя секрет Helm не удаётся сохранить в ETCD, потому что его размер превышает ограничение в 1 МБ(ограничение ETCD).
Чаще всего это происходит из-за того, что в директорию случайно попадают ненужные файлы: логи, бинарники, архивы или другие "левые" данные. Да, прям внутри кубера в секрете хелма по релизу помимо манифестов yaml валяются бинари/файлы, которые мы по незнанию туда засунули.
Чтобы избежать таких проблем, стоит использовать файл
Контекст - это не просто техническая деталь, а инструмент, который нужно понимать и оптимизировать.
Используйте
Контекст(context).
Мне понадобилось некоторое время на этапе моего обучения несколько лет назад, чтобы поесть всякого, чтобы осознать существование контекста.
Основной пример буду пояснять на примере Docker.
Контекст сборки - это архив (по сути tar-файл), который Docker создаёт из указанной директории и отправляет демону для обработки. Например, когда вы пишете
docker build -t image ., Docker упаковывает все файлы из текущей директории (.) и передаёт их демонуДопустим мы собираем имадж так:
docker build -t image .
Вот точка это и есть контекст. Точка это наша директория нашего проекта.
Что же нам надо знать о контексте?
- нельзя выйти за пределы контекста
Например у нас есть такая структура
/home/user/projects/
├── my_project/
│ ├── Dockerfile
│ ├── app.py
└── shared/
└── library.so
Находясь в директории
my_project мы не сможем собрать имадж типа такогоFROM ubuntu:20.04
COPY ../shared/library.so /usr/lib/
Так как shared находится выше по иерархии и не входит в контекст текущей директории "."
- докер(и другие известные мне утилиты) сканирует все файлы контекста( то есть в текущей директории)
То есть условно если у вас есть 50000 файлов по мегабайту или один файл 50 гигабайт в гит репозитории в контексте, то это замедлит сборку
Даже если мы НЕ используем
COPY . . , докер всё равно сканирует эти файлы(точнее Docker демон сканирует и упаковывает файлы для передачи) и мы собираем дольше по времени.Не думаю, что этот случай для всех, с этим можно столкнутся при не совсем корректных воркфлоу и при монолит приложениях.
Например у вас npm build идёт ВНЕ сборки докера, а потом вы копируете файлы(я видел и такое).
Проблема с переполнением контекста решается через
.dockerignore файл.В этом файле мы указываем те директории/файлы, которые НЕ должны попасть в контекст, по аналогии с
.gitignore.- контекст в
multi-stage билдахКонтекст доступен для всех этапов сборки, однако при использовании
COPY --from=build копирование идет из предыдущего этапа, а не из исходного контекста.Концепция контекста применима не только к Docker, но и к другим инструментам, например, к Helm - менеджеру пакетов для кубера.
Некоторые инженеры сталкиваются с ситуацией, когда во время деплоя секрет Helm не удаётся сохранить в ETCD, потому что его размер превышает ограничение в 1 МБ(ограничение ETCD).
Чаще всего это происходит из-за того, что в директорию случайно попадают ненужные файлы: логи, бинарники, архивы или другие "левые" данные. Да, прям внутри кубера в секрете хелма по релизу помимо манифестов yaml валяются бинари/файлы, которые мы по незнанию туда засунули.
Чтобы избежать таких проблем, стоит использовать файл
.helmignore, который позволяет исключить лишние файлы из контекста чарта - примерно так же, как .dockerignore помогает в Docker.Контекст - это не просто техническая деталь, а инструмент, который нужно понимать и оптимизировать.
Используйте
.dockerignore/.helmignore и следите за содержимым директорий, чтобы сборка была быстрой и эффективной.Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🔥7💯2
#docker #devsecops #paranoia #devops
Что у нас есть в пайплайнах?
Линтер питон скриптов, валидация YAML, tflint для терраформа, checkov для безопасности, git-leaks, sonarqube, скоринг кода, sast, dast и многое другое. Миллионы всего.
Всем и всё это знакомо.
А что по
Есть великолепный проект для простых проверок:
https://github.com/hadolint/hadolint
Его можно добавить в pre-commit hook
https://github.com/hadolint/hadolint/blob/master/.pre-commit-hooks.yaml
Можно добавить в pipeline.
Что он умеет?
- проверка и анализ Docker на соответствие лучшим практикам
- интеграция с ShellCheck (моя любовь ❤️), но нужно не для всех
- гибкий конфиг через .hadolint.yaml
- поддержка игнора
- интеграция с CICD
Всё-всё, хватит пустых слов, что он умеет можно почитать и в документации, давайте к практике и примеру.
Добавляем его в наш основной канико(да когда ж ты умрёшь?!) имадж, используемый для сборок:
Ок, базовый имадж есть.
Теперь добавляем его в основной общий пайплайн.
Например запускать можно так на этапе сборки имаджа.
Что тут проверяем?
- чтобы всегда стоял тег бейз имаджа
https://github.com/hadolint/hadolint/wiki/DL3006
- чтобы тег бейз имаджа был НЕ latest
https://github.com/hadolint/hadolint/wiki/DL3007
Выше пример лишь для двух проверок(полный список есть на гитхабе).
Можно использовать всё и игнорировать что-то(в комментах докерфайла например).
Как вам надо, так и делайте. Конкретно для себя мне нравятся лишь эти две проверки.
Ещё один инструмент в нашем бесконечном списке бесконечных инструментов.
Что у нас есть в пайплайнах?
Линтер питон скриптов, валидация YAML, tflint для терраформа, checkov для безопасности, git-leaks, sonarqube, скоринг кода, sast, dast и многое другое. Миллионы всего.
Всем и всё это знакомо.
А что по
Dockerfile?Есть великолепный проект для простых проверок:
https://github.com/hadolint/hadolint
Его можно добавить в pre-commit hook
https://github.com/hadolint/hadolint/blob/master/.pre-commit-hooks.yaml
Можно добавить в pipeline.
Что он умеет?
- проверка и анализ Docker на соответствие лучшим практикам
- интеграция с ShellCheck (моя любовь ❤️), но нужно не для всех
- гибкий конфиг через .hadolint.yaml
- поддержка игнора
- интеграция с CICD
Всё-всё, хватит пустых слов, что он умеет можно почитать и в документации, давайте к практике и примеру.
Добавляем его в наш основной канико(да когда ж ты умрёшь?!) имадж, используемый для сборок:
FROM gcr.io/kaniko-project/executor:v1.23.1-debug
ENV TRIVY_VERSION=0.61.0 \
CRANE_VERSION=0.19.0 \
HADOLINT=v2.12.0
RUN wget --no-verbose https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz && \
tar -zxvf trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz -C /kaniko && \
rm -f trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz
RUN wget --no-verbose https://github.com/google/go-containerregistry/releases/download/v${CRANE_VERSION}/go-containerregistry_Linux_x86_64.tar.gz && \
tar -zxvf go-containerregistry_Linux_x86_64.tar.gz -C /kaniko && \
rm -f go-containerregistry_Linux_x86_64.tar.gz && \
rm -f /kaniko/LICENSE /kaniko/README.md
RUN wget --no-verbose https://github.com/hadolint/hadolint/releases/download/${HADOLINT}/hadolint-Linux-x86_64 && \
mv hadolint-Linux-x86_64 /kaniko/hadolint && \
chmod +x /kaniko/hadolint
Ок, базовый имадж есть.
Теперь добавляем его в основной общий пайплайн.
Например запускать можно так на этапе сборки имаджа.
# Lint the Dockerfile
echo "Executing hadolint check on $dockerfilePath"
/kaniko/hadolint $dockerfilePath --error DL3006 --error DL3007 --failure-threshold error
Что тут проверяем?
- чтобы всегда стоял тег бейз имаджа
https://github.com/hadolint/hadolint/wiki/DL3006
- чтобы тег бейз имаджа был НЕ latest
https://github.com/hadolint/hadolint/wiki/DL3007
Выше пример лишь для двух проверок(полный список есть на гитхабе).
Можно использовать всё и игнорировать что-то(в комментах докерфайла например).
Как вам надо, так и делайте. Конкретно для себя мне нравятся лишь эти две проверки.
Ещё один инструмент в нашем бесконечном списке бесконечных инструментов.
👍17
#docker #troubleshooting #одинденьизжизни
"Тревога-тревога, волк унёс зайчат!"
"Алекс, не работает VPN"
И сразу от нескольких человек.
Ну не работает, ну ладно.
Чего хоть не работает? Спрашиваем.
Ага, с ресолвами.
Ага, с доступом до ресурсов. Снова днс.
Пробую сам сделать коннект - ошибка.
Захожу в аминку впн - ошибка в UI.
Ладно.
Хер знает как у нас работает впн, сам никогда не лез.
Сам только тыкал мышкой в "коннект" на клиенте и всё.
Заходим в репозиторий с VPN/infra.
Ага, виртуальная машина, внутри докеры-полупокеры, юзердата шелл скрипт и tailscale админка.
Ну в целом понятно.
Всё на виртуалке, никаких кубернетисов.
Логи вряд ли мне, несведущему, чему-то помогут, пойдём сперва попроще.
Что может быть? Может диск заполнился.
Графана, дашборд node exporter - всё ок. Ни скачков по диску, ни по IOPS, ни по сети.
Эм, ну ок.
Может ажур поднасрал? Ни автоапдейтов, ни ивентов, ничего по ажуру.
Жаль, а так хотелось поху*сосить ажур снова.
Ну чо, впн это блокер для команды, нет времени разбираться.
Семь бед - один ресет.
Захожу в ажур, выбираю VM, делаю рестарт.
Помогло, но лишь частично - админка UI заработала и начали работать коннекты, но проблема с днс осталась.
Копаем дальше.
Дальше нырнув в код, вижу есть и пара других VM в инфре для впн.
У них так же явный трабл с DNS.
Ну я же синёр - повторяем семь бед - один ресет обеих тачек.
После рестарта всё заработало, клиенты довольны, команда больше не в блокере, днс ресолвит.
Теперь можно налить чай и дебажить "а чо было", да и надо куда-то часы трекера списать.
На час окунулся в логи приложения - ничего особо не понял, я половины терминов и компонентов даже не знаю, ну так же про днс что-то.
Пошли на виртуалки. Джампхост, ссш, шелл.
Смотрим какие у нас services есть, доступны ли все.
Все есть, все доступны.
Однако логи докера(компоуз) дали наводку.
И так по кругу на миллион сообщений.
Значит проблема не со стороны аппликейшна в докере(компоузе), а с сетью - уровень докер лейера или операционной системы(скорее всего).
Ладно, а чо у нас с остальными логами.
Видим, что утром начался авто апдейт (
То есть авто апдейт не на уровне ажура, а самой операционке.
И.. и всё, компонент
"Алекс, не работает VPN"
И сразу от нескольких человек.
Ну не работает, ну ладно.
Чего хоть не работает? Спрашиваем.
Ага, с ресолвами.
Ага, с доступом до ресурсов. Снова днс.
Пробую сам сделать коннект - ошибка.
Захожу в аминку впн - ошибка в UI.
Ладно.
Хер знает как у нас работает впн, сам никогда не лез.
Сам только тыкал мышкой в "коннект" на клиенте и всё.
Заходим в репозиторий с VPN/infra.
Ага, виртуальная машина, внутри докеры-полупокеры, юзердата шелл скрипт и tailscale админка.
Ну в целом понятно.
Всё на виртуалке, никаких кубернетисов.
Логи вряд ли мне, несведущему, чему-то помогут, пойдём сперва попроще.
Что может быть? Может диск заполнился.
Графана, дашборд node exporter - всё ок. Ни скачков по диску, ни по IOPS, ни по сети.
Эм, ну ок.
Может ажур поднасрал? Ни автоапдейтов, ни ивентов, ничего по ажуру.
Жаль, а так хотелось поху*сосить ажур снова.
Ну чо, впн это блокер для команды, нет времени разбираться.
Семь бед - один ресет.
Захожу в ажур, выбираю VM, делаю рестарт.
Помогло, но лишь частично - админка UI заработала и начали работать коннекты, но проблема с днс осталась.
Копаем дальше.
Дальше нырнув в код, вижу есть и пара других VM в инфре для впн.
У них так же явный трабл с DNS.
Ну я же синёр - повторяем семь бед - один ресет обеих тачек.
После рестарта всё заработало, клиенты довольны, команда больше не в блокере, днс ресолвит.
Теперь можно налить чай и дебажить "а чо было", да и надо куда-то часы трекера списать.
На час окунулся в логи приложения - ничего особо не понял, я половины терминов и компонентов даже не знаю, ну так же про днс что-то.
Пошли на виртуалки. Джампхост, ссш, шелл.
Смотрим какие у нас services есть, доступны ли все.
Все есть, все доступны.
Однако логи докера(компоуз) дали наводку.
~$ sudo journalctl -u docker
...
****** 02 14:41:32 ******-vm-us-3 dockerd[1989830]: time="20**-06-02T14:41:32.495924354Z" level=error msg="[resolver] failed to query external DNS server" client-addr="udp:127.0.0.1:57484" dns-server="udp:12>
****** 05 05:43:42 ******-vm-us-3 dockerd[1989830]: time="20**-06-05T05:43:42.432809963Z" level=error msg="[resolver] failed to query external DNS server" client-addr="udp:127.0.0.1:60244" dns-server="udp:12>
И так по кругу на миллион сообщений.
Значит проблема не со стороны аппликейшна в докере(компоузе), а с сетью - уровень докер лейера или операционной системы(скорее всего).
Ладно, а чо у нас с остальными логами.
Видим, что утром начался авто апдейт (
unattended-upgrades в убунту).То есть авто апдейт не на уровне ажура, а самой операционке.
******-06-10 06:17:03,994 INFO Starting unattended upgrades script
******-06-10 06:17:03,995 INFO Allowed origins are: o=Ubuntu,a=jammy, o=Ubuntu,a=jammy-security, o=Docker,a=jammy, o=UbuntuESMApps,a=jammy-apps-security, o=UbuntuESM,a=jammy-infra-security
******-06-10 06:17:03,995 INFO Initial blacklist:
******-06-10 06:17:03,995 INFO Initial whitelist (not strict):
******-06-10 06:17:09,777 INFO Packages that will be upgraded: apport libnss-systemd libpam-systemd libsystemd0 libudev1 python3-apport python3-problem-report systemd systemd-sysv udev
******-06-10 06:17:09,777 INFO Writing dpkg log to /var/log/unattended-upgrades/unattended-upgrades-dpkg.log
******-06-10 06:18:00,563 INFO All upgrades installed
И.. и всё, компонент
systemd-resolved не захотел подниматься после апдейта.****** 10 06:17:24 ******-vm-us-3 systemd-resolved[2663737]: Positive Trust Anchors:
****** 10 06:17:24 ******-vm-us-3 systemd-resolved[2663737]: . IN DS 20326 8 2 e06d44b80b8f1d3457104237c7f8ec8d
****** 10 06:17:24 ******-vm-us-3 systemd-resolved[2663737]: Negative trust anchors: home.arpa *.in-addr.arpa *.172.in-addr.arpa ***.192.in-addr.arpa d.f.ip6.arpa corp home internal intranet lan local private test
****** 10 06:17:24 ******-vm-us-3 systemd-resolved[2663737]: Using system hostname '******-vm-us-3'.
****** 10 06:17:24 ******-vm-us-3 systemd-resolved[2663737]: Failed to connect to system bus: Permission denied
****** 10 06:17:24 ******-vm-us-3 systemd-resolved
🔥8👍3👀3😨2❤1😁1
#docker #troubleshooting #одинденьизжизни
Иногда нам всем нужна Лицензия наУбийство Костыль.
Как в одном из фильмов про Бонда.
Ситуация: резко к вам прибегают и говорят "У НАС ПРОБЛЕЕЕМА".
Нырнув внутрь "проблемы", понимаю, что 19 из 20 разных микросервисов на разных стеках начали ругаться на один из веб-сайтов партнёров. Что-то не так с сертификатом.
Проверяем изнутри пода
Проверяю локально - всё ок с сайтом(он не наш).
Ладно.
Лезем в гугл спрашиваем в чате девопс_ру чего есть ныне для анализа SSL.
Дали рекомендацию - https://www.ssllabs.com/
Проверяю сайт - ранг B, в целом всё ок, но есть вопросики.
Они обновили новый серт, у нового серта новый УЦ(я так понял).
Созвон с коллегами, говорю так и так, сайт чужой, проблема с ним, у нас вроде всё ок.
Однако бизнес не согласен со мной(и, наверное, прав на 100%).
Говорят "да, мы знаем, что проблема скорее всего не у нас. Да, мы поняли, что это надо им писать. Но у нас бизнес и денежки, Пока они(владельцы сайта-партнёра) отреагируют, у нас не ок работает НАШ бизнес.
А где НАШ бизнес - там проблема у нас. Возможно ли быстро решить нашими средствами эту проблему?"
Чуток поэкспериментировал локально, за пять минут нашёл пару очевидных решений.
Спрашиваю лицензию на костыль, мне дают карт-бланш.
Ну ок, лицензия получена, пилим фиксы.
В одном имадже (где старые from имаджи)
В другом имадже залепить, если ещё нет, и пересобрать
(показываю одним слоём, но вообще он был добавлен к один длинный run).
Да, было добавление ca-certificates, но не было команды на апдейт 🐒
И так далее.
Пересобираем, деплоим, проблема ушла.
Ура, бизнес ликует.
Выводы:
А кто тут виноват? Я не знаю.
Владелец сайта и новый УЦ?
Наши "устаревшие сервисы" со старым списком со старыми сертами?
Отсутствие регулярных пересборок имаджей и скачивание всех новых сертов?
Чёрт его знает.
Иногда нам всем нужна Лицензия на
Как в одном из фильмов про Бонда.
Ситуация: резко к вам прибегают и говорят "У НАС ПРОБЛЕЕЕМА".
Нырнув внутрь "проблемы", понимаю, что 19 из 20 разных микросервисов на разных стеках начали ругаться на один из веб-сайтов партнёров. Что-то не так с сертификатом.
Проверяем изнутри пода
> curl https://*******.com/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
Проверяю локально - всё ок с сайтом(он не наш).
Ладно.
Дали рекомендацию - https://www.ssllabs.com/
Проверяю сайт - ранг B, в целом всё ок, но есть вопросики.
Они обновили новый серт, у нового серта новый УЦ(я так понял).
Созвон с коллегами, говорю так и так, сайт чужой, проблема с ним, у нас вроде всё ок.
Однако бизнес не согласен со мной(и, наверное, прав на 100%).
Говорят "да, мы знаем, что проблема скорее всего не у нас. Да, мы поняли, что это надо им писать. Но у нас бизнес и денежки, Пока они(владельцы сайта-партнёра) отреагируют, у нас не ок работает НАШ бизнес.
А где НАШ бизнес - там проблема у нас. Возможно ли быстро решить нашими средствами эту проблему?"
Чуток поэкспериментировал локально, за пять минут нашёл пару очевидных решений.
Спрашиваю лицензию на костыль, мне дают карт-бланш.
Ну ок, лицензия получена, пилим фиксы.
В одном имадже (где старые from имаджи)
FROM node:*****
...
# temporary fix for https://*****.com/
RUN wget https://sectigo.tbs-certificats.com/SectigoPublicServerAuthenticationRootR46.crt -O /usr/local/share/ca-certificates/sectigo-root.crt && update-ca-certificates
...
В другом имадже залепить, если ещё нет, и пересобрать
...
RUN update-ca-certificates
...
(показываю одним слоём, но вообще он был добавлен к один длинный run).
И так далее.
Пересобираем, деплоим, проблема ушла.
Ура, бизнес ликует.
Выводы:
А кто тут виноват? Я не знаю.
Владелец сайта и новый УЦ?
Наши "устаревшие сервисы" со старым списком со старыми сертами?
Отсутствие регулярных пересборок имаджей и скачивание всех новых сертов?
Чёрт его знает.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤1🤡1
#docker #aws
Я решил отказаться от долгих часов, проведенных за оптимизацией размера Docker-образов.
Несмотря на провокационный заголовок, который мог показаться кликбейтом, я действительно перестал уделять этому столько времени.
Хочу поделиться своими размышлениями - возможно, они окажутся правильными, а может, и нет.
Я не топлю, что мои размышления правильные, я лишь ими делюсь.
Давайте попробую обосновать свою позицию.
Все дальнейшие расчеты будут основаны на работе с облачными провайдерами, хотя, как мне кажется, на bare metal затраты могли бы быть ниже.
Выпущено миллиард книг, написано сотни утилит и опубликовано гигадзиллион статьей по теме оптимизации размера имаджа.
А зачем?
Вспомним основные причины оптимизации размера Docker-образа:
- Ускорение развертывания: Меньшие образы быстрее загружаются и передаются по сети.
- Экономия ресурсов: Снижение потребления дискового пространства и памяти.
- Безопасность: Уменьшение количества компонентов снижает уязвимости.
- Эффективность CI/CD: Ускорение сборки и доставки в конвейерах.
- Снижение затрат: Меньше данных для хранения и передачи в облаке.
и так далее.
Скачивание имаджа.
Когда в основном скачивается имадж:
- при скейле ноды
- при релизе
Есть ещё редкие случаи связок imagePullPolicy Always, скейлинге приложения и антиаффинити, но мы о не будем говорить, они редкие.
А сколько скачивается имадж?
Начиная с версии 1.30 кубера у нас есть метрика
Мы говорим про средние имаджи, допустим в 900 мегабайт.
Построим график
Скорость варьируется от 6 секунд до 16 секунд за последние 7 дней.
Я лично считаю это небольшой цифрой для добавления времени к релизу или скейлингу.
Нода или само приложение порой дольше проходит хелсчеки.
Специально ставил
Экономия ресурсов.
А сколько сейчас стоит в облаках ресурс дискового пространства?
Например у AWS так https://aws.amazon.com/ecr/pricing/
Ну или просто в биллинг можно залезть и посчитать сколько тратим в месяц на ECR.
В среднем за последний год тратим 35-50 долларов в месяц.
Диски для самих нод выходят так мало, что их почти не видно в прайсе
https://aws.amazon.com/ebs/pricing/
Ну то есть в самый пик на дисковое пространство тратим не больше 50 долларов в месяц.
Безопасность.
Я и согласен и несогласен.
Безопасность в последнее время перешла границы от разумного до параноидального бреда, лишь бы мы покупали решения от крупных вендоров и мастурбировали на CVE, которая вышла 0.000001мс назад.
Простые решения в виде "не добавлять ничего лишнего в имадж" + trivy вполне покрывают этот пункт для нас.
Да и уменьшение размера диска не гарантирует безопасность, лишь снижает вероятность инцидентов.
К сожалению в некоторых моих проектах невозможно использовать подходы с SBOM+distroless.
Разработчики пилят микросервисы, макросервисы, сейчас один только nodejs и python тянет за собой миллиард депенденси и отказаться от них невозможно, совершенно невозможно.
А этот софт в отличии от меня приносит деньги.
Эффективность CI/CD
Тот же пункт, что и пул имаджей с нод.
6-16 секунд для несжатых имаджей.
Снижение затрат
Тот же пункт, что и с костами для ECR, но тут за трафик.
Трата за трафик небольшая. Не больше 5 долларов в месяц.
Я решил отказаться от долгих часов, проведенных за оптимизацией размера Docker-образов.
Несмотря на провокационный заголовок, который мог показаться кликбейтом, я действительно перестал уделять этому столько времени.
Хочу поделиться своими размышлениями - возможно, они окажутся правильными, а может, и нет.
Я не топлю, что мои размышления правильные, я лишь ими делюсь.
Давайте попробую обосновать свою позицию.
Все дальнейшие расчеты будут основаны на работе с облачными провайдерами, хотя, как мне кажется, на bare metal затраты могли бы быть ниже.
Выпущено миллиард книг, написано сотни утилит и опубликовано гигадзиллион статьей по теме оптимизации размера имаджа.
А зачем?
Вспомним основные причины оптимизации размера Docker-образа:
- Ускорение развертывания: Меньшие образы быстрее загружаются и передаются по сети.
- Экономия ресурсов: Снижение потребления дискового пространства и памяти.
- Безопасность: Уменьшение количества компонентов снижает уязвимости.
- Эффективность CI/CD: Ускорение сборки и доставки в конвейерах.
- Снижение затрат: Меньше данных для хранения и передачи в облаке.
и так далее.
Скачивание имаджа.
Когда в основном скачивается имадж:
- при скейле ноды
- при релизе
Есть ещё редкие случаи связок imagePullPolicy Always, скейлинге приложения и антиаффинити, но мы о не будем говорить, они редкие.
А сколько скачивается имадж?
Начиная с версии 1.30 кубера у нас есть метрика
kubelet_image_pull_duration_seconds_sum и лейбл image_size_in_bytes (не сжатый!!!! размер) с 4 значениями ( 0-10MB, 10MB-100MB, 100MB-500MB, 500MB-1GB).Мы говорим про средние имаджи, допустим в 900 мегабайт.
Построим график
avg(kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="500MB-1GB"} / kubelet_image_pull_duration_seconds_count{image_size_in_bytes="500MB-1GB"})
(и для сравнения мелкие имаджи)
avg(kubelet_image_pull_duration_seconds_sum{image_size_in_bytes="100MB-500MB"} / kubelet_image_pull_duration_seconds_count{image_size_in_bytes="100MB-500MB"})
Скорость варьируется от 6 секунд до 16 секунд за последние 7 дней.
Я лично считаю это небольшой цифрой для добавления времени к релизу или скейлингу.
Нода или само приложение порой дольше проходит хелсчеки.
Специально ставил
blackbox exporter + релейбл с путем до проб и навешивал на поды, чтобы рассчитать probe_duration_seconds. Пробы в среднем стартуют с 25 секунд.Экономия ресурсов.
А сколько сейчас стоит в облаках ресурс дискового пространства?
Например у AWS так https://aws.amazon.com/ecr/pricing/
Ну или просто в биллинг можно залезть и посчитать сколько тратим в месяц на ECR.
В среднем за последний год тратим 35-50 долларов в месяц.
Диски для самих нод выходят так мало, что их почти не видно в прайсе
https://aws.amazon.com/ebs/pricing/
Ну то есть в самый пик на дисковое пространство тратим не больше 50 долларов в месяц.
Безопасность.
Я и согласен и несогласен.
Безопасность в последнее время перешла границы от разумного до параноидального бреда, лишь бы мы покупали решения от крупных вендоров и мастурбировали на CVE, которая вышла 0.000001мс назад.
Простые решения в виде "не добавлять ничего лишнего в имадж" + trivy вполне покрывают этот пункт для нас.
Да и уменьшение размера диска не гарантирует безопасность, лишь снижает вероятность инцидентов.
К сожалению в некоторых моих проектах невозможно использовать подходы с SBOM+distroless.
Разработчики пилят микросервисы, макросервисы, сейчас один только nodejs и python тянет за собой миллиард депенденси и отказаться от них невозможно, совершенно невозможно.
А этот софт в отличии от меня приносит деньги.
Эффективность CI/CD
Тот же пункт, что и пул имаджей с нод.
6-16 секунд для несжатых имаджей.
Снижение затрат
Тот же пункт, что и с костами для ECR, но тут за трафик.
Трата за трафик небольшая. Не больше 5 долларов в месяц.
👍9❤1
#docker #aws
Итого, что мы имеем:
Средняятемпература по больнице зарплата синёра это $4000-6000 (*ЕС, Россия, без США само собой), если верить словам приятелей моего круга общения и статьям в интернете и статистике, которую нашёл час назад, а значит в час, при 168 часовой неделе, это $23-35. Ну, наверное, самые крутые получают $50h.
Средняя время оптимизации докерфайла (постоянный запуск, смена слоёв, подгон под аппликейшн, убирание лишнего и так далее) это 2-3 часа в моей практике. Лишь за счёт долгого ковыряния.
То есть чтобы "сэкономить" бизнесу 5-10 долларов в месяц(!) за трафик сети и хранение и 3-5 секунд при деплое я потрачу $50-105. И это лишь на один имадж. 30 имаджей-микросервисов и всё, я потратил несколько тысяч долларов.
Оптимизировать чтобы что? Только лишь потому, что это правильно?
Да, оптимизация размера имаджа это правильно, я согласен с этим тезисом. не оспариваю его ни в коем случае.
Как минимум это приятно для глаз инженера.
Стоит ли делать это? Да, но только в том случае, когда освободилось время от всех других задач по работе.
Приоритет у этой задачи один из самых низких на мой взгляд.
У меня сейчас нет времени, чтобы тратить деньги бизнеса на оптимизацию.
Безусловно если мне коллеги выкатят имадж на 5 гигабайт я брошу всё и займусь им.
Образы до 900 мегабайт считаю оптимизировать пока не надо.
В данный момент развития технологий в 2025 году(привет хелл депенденси питона и ноджс и все ML фреймворки), с ценами облачных провайдеров за трафик, реджистри и диск для нод, средней зарплатой инженера в час, считаю полностью нецелесообразным тратить часы на оптимизацию размера имаджа.
Бизнес не выигрывает ничего, во всяком случае наш.
В каком же случае оптимизация всё же нужна?
Когда важная каждая секунда при деплое и скейлинге и когда этого просто хочется.
* Данная заметка не является рекомендацией, не является советом, лишь поделился своими размышлениями.
Итого, что мы имеем:
Средняя
Средняя время оптимизации докерфайла (постоянный запуск, смена слоёв, подгон под аппликейшн, убирание лишнего и так далее) это 2-3 часа в моей практике. Лишь за счёт долгого ковыряния.
То есть чтобы "сэкономить" бизнесу 5-10 долларов в месяц(!) за трафик сети и хранение и 3-5 секунд при деплое я потрачу $50-105. И это лишь на один имадж. 30 имаджей-микросервисов и всё, я потратил несколько тысяч долларов.
Оптимизировать чтобы что? Только лишь потому, что это правильно?
Да, оптимизация размера имаджа это правильно, я согласен с этим тезисом. не оспариваю его ни в коем случае.
Как минимум это приятно для глаз инженера.
Стоит ли делать это? Да, но только в том случае, когда освободилось время от всех других задач по работе.
Приоритет у этой задачи один из самых низких на мой взгляд.
У меня сейчас нет времени, чтобы тратить деньги бизнеса на оптимизацию.
Безусловно если мне коллеги выкатят имадж на 5 гигабайт я брошу всё и займусь им.
Образы до 900 мегабайт считаю оптимизировать пока не надо.
В данный момент развития технологий в 2025 году(привет хелл депенденси питона и ноджс и все ML фреймворки), с ценами облачных провайдеров за трафик, реджистри и диск для нод, средней зарплатой инженера в час, считаю полностью нецелесообразным тратить часы на оптимизацию размера имаджа.
Бизнес не выигрывает ничего, во всяком случае наш.
В каком же случае оптимизация всё же нужна?
Когда важная каждая секунда при деплое и скейлинге и когда этого просто хочется.
* Данная заметка не является рекомендацией, не является советом, лишь поделился своими размышлениями.
❤16👌5💯1🤝1
#azure #docker #opersource
OpenSource. Latest. Apache.
Да, пусть заметка называется Apache.
Прихожу на работу, а там десятокалёртов жалоб пользователей и всё UI нерабочий.
Просто не работает, и никаких алёртов.
Ладно.
На этом проекте уже не первый день, знаю, что в кишках там внутри проекты с Apache SuperSet.
https://superset.apache.org/
Иду в UI - да, какая-то ошибка (неизвестная мне). Ошибка говорит проблемы с подключением к каталогу Trino
https://trino.io
По ссылкам можно зайти, только если это интересно, в целом хватает триггера - Apache.
Ошибка говорит о том, что невозможно подключится к trino по http(а его и нет), везде написано https(только он и есть).
Явно баг.
Иду в кубернетис, там у нас и трино и суперсет.
Методом тыка и быстрого анализа узнаю, что проблема не везде - не на всех инстансах SuperSet.
Там, где утром был автоматический апдейт AKS кластер(спасибо за неотключаемый секьюрити патч, мистар Ажур), есть проблема. Где апдейта не было - всё ок работает.
Смотрю как устанавливается суперсет - везде хелм чарт и точное указание версии чарта.
Ну пусть будет версия 3.0.0.
Но проблема есть и не стоит закрывать глаза на интуицию, что это пока Apache.
Смотрю откуда тащится чарт и приходим к GitHub, на то и опенсорс.
https://github.com/apache/superset/blob/3.0.0/helm/superset/values.yaml#L178
Сук, as is это приведёт к latest тегу.
Бегу сравнивать SHA имаджей на SuperSet где работает и где не работает.
Всё так, имаджи разные.🤡 Хотя чарт одинаковый, как и values.yaml
Быстро пушу имадж работающий в внутренний реджистри, чтобы не проетерять в суете.
Для того, чтобы не аффектило юзеров просто меняем image путь вместо дефолтного - на внутренний registry с некрасивым tag в виде sha.
Работа сервиса восстановилась, ошибок нет, пользователи больше не кидают кизяки в нас/меня.
Есть время потраблшутить: а ну и чего у нас в докерфайле
https://github.com/apache/superset/blob/3.0.0/Dockerfile
Сук, красота с pip install но вроде не в этом дело.
Не понимаю, отупел с облаками🐒 , натравливаю нейронку на репу, она говорит мне про
https://github.com/apache/superset/blob/3.0.0/setup.py
Ага, значит у нас где-то есть прикрученная к забору версия, а где-то нет. Красиво.
Предполагаю в этом причина
https://github.com/apache/superset/blob/3.0.0/setup.py#L177
Начинаю гуглить по всем связанным репозиториям(чтобы сократить вероятность, что копну не туда), хотя я мог бы это сделать с самого начала и не ковыряться в докерах шмокерах.🤡
https://github.com/trinodb/trino-python-client/issues/557
То есть проблема не в самом суперсете, а сторонней библиотеке, которая ставится через trino>=0.324.0 и тянет за собой ещё и клиента само собой. И при каждом обновлении докеримаджа для трино каждый раз пере собирается новый, сук, имадж, который по latest идет к ЛЮБОЙ версии чарта.
Варианта у нас два(нет, больше, но пусть будет проще так):
- ждать исправление бага и нового чарта хелма от суперсет
- перетащить всё к себе и впоследствии менеджить самому
Ещё раз с коллегами оценили опенсорс, риски и решили перетащить докерфайл к себе.
Создаю новый репозиторий, пихаю туда докерфайл, пайплайн для CICD со сборкой имаджа и публикация по тегам во внутреннем реджистри, requriments.txt
Докерфайл простой (тут уже версия выше, но это другая история)
Открываю тот самый рабочий supserset имадж, и командами
Копирую всё оттуда, хардкорю версии в
Локальная сборка, тест, коммит, пуш, тегируем 0.0.1.
Сперва dev, потом stage/uat/prod - меняю путь до локального реджистри и тега.
Проблема решена и ура, теперь у нас + новый форк имаджа.
Да, пусть заметка называется Apache.
Прихожу на работу, а там десяток
Просто не работает, и никаких алёртов.
Ладно.
На этом проекте уже не первый день, знаю, что в кишках там внутри проекты с Apache SuperSet.
https://superset.apache.org/
Иду в UI - да, какая-то ошибка (неизвестная мне). Ошибка говорит проблемы с подключением к каталогу Trino
https://trino.io
По ссылкам можно зайти, только если это интересно, в целом хватает триггера - Apache.
Ошибка говорит о том, что невозможно подключится к trino по http(а его и нет), везде написано https(только он и есть).
Явно баг.
Иду в кубернетис, там у нас и трино и суперсет.
Методом тыка и быстрого анализа узнаю, что проблема не везде - не на всех инстансах SuperSet.
Там, где утром был автоматический апдейт AKS кластер(спасибо за неотключаемый секьюрити патч, мистар Ажур), есть проблема. Где апдейта не было - всё ок работает.
Смотрю как устанавливается суперсет - везде хелм чарт и точное указание версии чарта.
Ну пусть будет версия 3.0.0.
Но проблема есть и не стоит закрывать глаза на интуицию, что это пока Apache.
Смотрю откуда тащится чарт и приходим к GitHub, на то и опенсорс.
https://github.com/apache/superset/blob/3.0.0/helm/superset/values.yaml#L178
Сук, as is это приведёт к latest тегу.
Бегу сравнивать SHA имаджей на SuperSet где работает и где не работает.
Всё так, имаджи разные.
Быстро пушу имадж работающий в внутренний реджистри, чтобы не проетерять в суете.
Для того, чтобы не аффектило юзеров просто меняем image путь вместо дефолтного - на внутренний registry с некрасивым tag в виде sha.
Работа сервиса восстановилась, ошибок нет, пользователи больше не кидают кизяки в нас/меня.
Есть время потраблшутить: а ну и чего у нас в докерфайле
https://github.com/apache/superset/blob/3.0.0/Dockerfile
Сук, красота с pip install но вроде не в этом дело.
Не понимаю, отупел с облаками
https://github.com/apache/superset/blob/3.0.0/setup.py
Ага, значит у нас где-то есть прикрученная к забору версия, а где-то нет. Красиво.
Предполагаю в этом причина
https://github.com/apache/superset/blob/3.0.0/setup.py#L177
Начинаю гуглить по всем связанным репозиториям(чтобы сократить вероятность, что копну не туда), хотя я мог бы это сделать с самого начала и не ковыряться в докерах шмокерах.
https://github.com/trinodb/trino-python-client/issues/557
То есть проблема не в самом суперсете, а сторонней библиотеке, которая ставится через trino>=0.324.0 и тянет за собой ещё и клиента само собой. И при каждом обновлении докеримаджа для трино каждый раз пере собирается новый, сук, имадж, который по latest идет к ЛЮБОЙ версии чарта.
Варианта у нас два(нет, больше, но пусть будет проще так):
- ждать исправление бага и нового чарта хелма от суперсет
- перетащить всё к себе и впоследствии менеджить самому
Ещё раз с коллегами оценили опенсорс, риски и решили перетащить докерфайл к себе.
Создаю новый репозиторий, пихаю туда докерфайл, пайплайн для CICD со сборкой имаджа и публикация по тегам во внутреннем реджистри, requriments.txt
Докерфайл простой (тут уже версия выше, но это другая история)
FROM apachesuperset.docker.scarf.sh/apache/superset:4.1.2
USER root
COPY requirements.txt /app/requirements.txt
RUN pip install -r /app/requirements.txt
USER superset
Открываю тот самый рабочий supserset имадж, и командами
docker run ... смотрю версии библиотек (вроде команда pip list). Копирую всё оттуда, хардкорю версии в
requirements.txt ровно то, что было в рабочем образе.Локальная сборка, тест, коммит, пуш, тегируем 0.0.1.
Сперва dev, потом stage/uat/prod - меняю путь до локального реджистри и тега.
Проблема решена и ура, теперь у нас + новый форк имаджа.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7😁2
#azure #docker #opersource
В последствии оказалось, что это было на 100% верное решение, потому что у библиотек выходило ещё пару багов и были проблемы другие, а так менеджить свой докерфайл это удобнее, потому что можно добавлять свои библиотеки.
Например штатно ни в одной из версий SuperSet не хватает🐒
Да, это можно добавить в
https://superset.apache.org/docs/configuration/alerts-reports/
Удобнее в форке держать и это ок.
Итог:
а как обычно, мог бы поиском в гугле выйти сразу на репу с кривой либой, но нет, мамкин инженер хочет во всём разобраться пошагово и это просто трата времени.
Ну главное жалоб больше нет, как и рисков latest.
В последствии оказалось, что это было на 100% верное решение, потому что у библиотек выходило ещё пару багов и были проблемы другие, а так менеджить свой докерфайл это удобнее, потому что можно добавлять свои библиотеки.
Например штатно ни в одной из версий SuperSet не хватает
psycopg2 Да, это можно добавить в
bootstrap, но потом мы захотели включить алертинг, а это подтянуло ещё и selenium webdriver и многое многое другое. https://superset.apache.org/docs/configuration/alerts-reports/
Удобнее в форке держать и это ок.
Итог:
а как обычно, мог бы поиском в гугле выйти сразу на репу с кривой либой, но нет, мамкин инженер хочет во всём разобраться пошагово и это просто трата времени.
Ну главное жалоб больше нет, как и рисков latest.
Please open Telegram to view this post
VIEW IN TELEGRAM
👏6
#longread #docker #debezium #всратость #mysql #kafka #realtime
https://telegra.ph/Catch-the-Latest-07-30
https://telegra.ph/Catch-the-Latest-07-30
Telegraph
Catch the Latest
Alexandr Kruchkov Ах сколько же славных историй, ах сколько же копий было разбито об использовании великолепного image:latest. В этой старой истории сразу несколько факапов, но основной это latest. Просыпаюсь утром, иду на работу, а там прод лежит. Ну как…
🔥8😁5👏2😍1
Приветствую всех.
Поскольку все читатели здесь ради контента, а не моей биографии, сразу перейду к сути.
Этот блог - мои заметки на полях.
Почти не делаю репосты, пишу для души и лишь когда на это есть время/желание.
Обычно это 2-4 поста в неделю.
В основном делюсь:
- информацией, которую узнал только что (даже если она пятилетней давности, но я узнал её сейчас)
- лонгридами, байками или всратыми историями, без указания срока давности
- последовательным описанием моего процесса мышления на работе при решении задач
Интересные, на мой взгляд, сообщения я публикую с тегами:
- пример основных тем канала:
#aws #azure #kubernetes #troubleshooting #costoptimization #longread #devops
- пример второстепенных категорий:
#terragrunt #victoriametrics #git #docker #одинденьизжизни #helm
- для того, чтобы на работе не поехать кукухой, у меня есть:
#пятница #всратость #байки
Сообщения без тегов это просто шутка-минутка или мысль, которая была актуальна лишь на момент написания.
Все заметки не имеют строгой последовательности, читать их можно как угодно:
- начать с самого основания канала (за год постов около 230)
- использовать интересующие теги/поиск
- ну или просто начать с новых постов, пропустив всё ранее написанное😭
Каждый решает, как ему удобно.
Буду рад, если мои заметки помогут кому-то узнать что-то новое, избежать повтора чужих ошибок или просто улыбнуться.
На крайний случай, самоутвердиться за счёт моих факапов или незнания🐒
Всем привет и желаю приятного чтения.
Поскольку все читатели здесь ради контента, а не моей биографии, сразу перейду к сути.
Этот блог - мои заметки на полях.
Почти не делаю репосты, пишу для души и лишь когда на это есть время/желание.
Обычно это 2-4 поста в неделю.
В основном делюсь:
- информацией, которую узнал только что (даже если она пятилетней давности, но я узнал её сейчас)
- лонгридами, байками или всратыми историями, без указания срока давности
- последовательным описанием моего процесса мышления на работе при решении задач
Интересные, на мой взгляд, сообщения я публикую с тегами:
- пример основных тем канала:
#aws #azure #kubernetes #troubleshooting #costoptimization #longread #devops
- пример второстепенных категорий:
#terragrunt #victoriametrics #git #docker #одинденьизжизни #helm
- для того, чтобы на работе не поехать кукухой, у меня есть:
#пятница #всратость #байки
Сообщения без тегов это просто шутка-минутка или мысль, которая была актуальна лишь на момент написания.
Все заметки не имеют строгой последовательности, читать их можно как угодно:
- начать с самого основания канала (за год постов около 230)
- использовать интересующие теги/поиск
- ну или просто начать с новых постов, пропустив всё ранее написанное
Каждый решает, как ему удобно.
Буду рад, если мои заметки помогут кому-то узнать что-то новое, избежать повтора чужих ошибок или просто улыбнуться.
На крайний случай, самоутвердиться за счёт моих факапов или незнания
Всем привет и желаю приятного чтения.
Please open Telegram to view this post
VIEW IN TELEGRAM
10👍31👨💻1