Девопсолог | DevOps, GitOps и прочий Ops
272 subscribers
60 photos
5 videos
2 files
91 links
Пишу про docker, k8s, gitops и прочие инфраструктурные штуки

Чат канала: https://t.me/AppliedTryndology
Download Telegram
CozyStack.io

Сегодня узнал про прикольную штуку - https://cozystack.io/
Это опенсорсная платформа для создания своего собственного облака.
Т.е. можно на своих железках в серверной (Или железных серверах в ДЦ) сделать себе практически тоже самое, что предоставляют Cloud провайдеры. Есть даже Managed Postgres.

Сам я пока не щупал, но выглядит очень перспективно. На первый взгляд должно быть гораздо проще и удобнее, чем OpenStack, который используют популярные облака в РФ.

Подробнее можно посмотреть в подкасте https://t.me/kubernetes_ru/800561
👍3🤔1
Как мы в Happy Inc связываем frontend и backend в feature окружениях

Здесь у нас есть несколько схем, в зависимости от задачи:

1. Бэкендеры что-то доработали и им нужно задеплоить frontend приложение для каких-нибудь ручных самопроверок

2. Фронтендеры что-то доработали и им нужно проверить только frontend, backend не менялся

3. Доработка была совместная и нужно протестировать frontend и backend в связке

Как это реализовать с помощью Gitlab CI:

1. В гитлабе есть такая штука, как downstream pipelines. Мы можем запустить из пайплайна backend репозитория дочерний из frontend репозитория, передав ему какие-то переменные окружения. Соответственно сначала мы нажимаем deploy-feature-stage, затем deploy-feature-stage-frontend. Получем frontend приложение, которое смотрит на наш задеплоенный backend. Обратите внимание на deploy-prod action в frontend репозитории, там прописана rules секция, чтобы избежать деплоя на прод при запуске downstream пайплайна. При закрытии или мерже MR - оба окружения схлопываются.

2. Тут просто деплоим feature-stage фронтенда. По умолчанию он смотрит на master backend, который деплоится автоматически при каждом деплое в master на бэке. Смотрите action deploy-master-feature-stage в бэкендовом .gitlab-ci.yml

3. Тут можно пойти различными путями. Я решил сделать в таком ключе - в MR на бэкенде и фронтенде прописывается milestone с одинаковым именем, например test. Бэкендовый и фронтовый .gitlab-ci.yml дорабатываются таким образом, что если прописан milestone - окружение деплоится с именем из milestone. Фронты и бэки деплоят свои приложения независимо, в MR прописан milestone, таким образом фронт смотрит на нужный бэк. Доработка тестируется с изменениями и на фронте и на бэке

Доработанный backend pipeline - .gitlab-ci.yml
Пример frontend pipeline - .gitlab-ci.yml

Если у вас остались какие-то вопросы или есть идеи как реализовать все попроще - добро пожаловать в комментарии
👍2
tree

Пока готовлю пост про настройку логов - хочу поделиться свежим открытием. Консольная утилита tree в удобном виде показывает дерево каталогов. Вводим tree -a . и получаем вот такую красоту
👍4
Установка Grafana

Решил разбить пост про настройку сборки логов на несколько частей.
Сегодня начнем с установки Grafana.
Будем использовать подход Infrastructure as a Code, почти ничего на сервере руками не настраиваем, все деплоим в докер с помощью Gitlab.

1. Нужно подготовить чистый сервер к работе, создать юзера, прописать ключи вашего юзера на сервер и т.д. Я для этого использую свой ansible playbook. (Для этого на вашем компьютере должен быть установлен ansible и должен быть доступ по ssh на целевую машину).
2. Создаем на сервере пользователя gitlab-deploy и добавляем его в группу docker. Команды adduser gitlab-deploy и usermod -a -G docker gitlab-deploy (Этот и следующий пункт я еще не автоматизировал с помощью ansible).
3. Теперь нужно на сервере, где у вас запускается gitlab runner скопировать публичный ключ пользователя gitlab-runner на наш новый сервер. Для этого выполняем sudo su - gitlab-runner и ssh-copy-id -p 1234 -i ~/.ssh/id_rsa.pub gitlab-deploy@example.com.
4. Теперь можем деплоить traefik на сервер. Создаем проект в гитлабе, регистрируем для него gitlab runner и прописываем CI/CD переменную TRAEFIK_USERS. Значение для переменной можно сгенерировать с помощью команды htpasswd -n traefik. Соответственно логин будет traefik, а пароль тот, который вы зададите.
5. Пушим в репозиторий следующее содержимое (Поправить пример под себя). Получаем задеплоенный traefik на сервере. Отличие от traefik на feature-stage в том, что тут сертификаты автоматически получаются с помощью Let's Encrypt.
6. Теперь создаем проект для grafana, регистрируем для него gitlab-runner и прописываем CI/CD переменную POSTGRES_GRAFANA_PASSWORD.
7. Пушим в репозиторий следующее содержимое (Поправить пример под себя).
8. Когда отработает pipeline в гитлабе - можно открывать https://grafana.example.com/ (тут будет ваш домен) и логиниться под дефолтным логином admin и паролем admin. Система сразу попросит изменить пароль. Я также советую в настройках Administration / Users сразу поменять и логин.
🔥3
ncdu

Я немного приболел, поэтому на этой неделе продолжения по настройке логов не будет.
Но спешу с вами поделиться ещё одной полезной утилитой.
Наверняка многие знают команду du. А вот про ncdu не все слышали.
Запускаем в нужной директории и видим вот такую красоту. Плюс можно перемещаться по файловой системе, как в mc
👍5
Grafana loki

Сегодня мы добавляем loki для сбора и хранения логов + добавляем datasource в графану, чтобы она увидела loki. Loki наружу не пробрасываем, так как логи должны (в идеале) литься внутри локальной сети.

1. Делаем свой Dockerfile для loki. Нам нужно создать папку для хранения данных и выдать ей соответствующие права, иначе при монтировании named volume в контейнер у пользователя в контейнере не будет прав на запись. Плюс добавляем curl, для того чтобы сделать healthcheck.
2. Пишем конфиг для loki. Я руководствовался вот этой статьей и добавил немного отсебятины, вроде зачистки данных в хранилище логов старше 1 недели.
3. Пишем datasource для графаны, чтобы она увидела loki. Ничего не добавляем ручками, все можно сделать с помощью кода!
4. Правим docker-compose.yaml и .gitlab-ci.yml, чтобы это добро заработало.
5. Пушим все в репозиторий, дожидаемся выполнения pipeline.
6. Заглядываем в графану в раздел Connections -> Data sources и убеждаемся, что loki подключился.

Всё готово! В следующей статье будем учиться лить логи приложения в loki.
🔥2👍1
Льем логи приложения в loki

Сегодня учимся отправлять логи приложения в наш loki. Для примера задеплоим приложение на тот же сервер.

1. Устанавливаем на сервер docker driver client для loki.
2. Открываем 3100 порт на хост машине, так как лить логи, используя только сеть докера не получилось (В продовом окружении все равно порт будет открыт в локальную сеть, так как графана и локи, вероятнее всего, будут стоять на отдельной машине для сбора логов от всех систем).
3. Берем обычное приложение на symfony и конфигуриуем его так, чтобы логи писались в файл в формате json
when@prod:
monolog:
handlers:
main:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.json'
formatter: monolog.formatter.json

4. В продовом конфиге docker compose создаем отдельный контейнер, который будет лить логи приложения в loki (Основной контейнер в stdout шлет логи RoadRunner, с помощью которого запускается наше приложение, их тоже можно лить в loki, если требуется). За отправку в loki отвечает эта секция:
logging:
driver: loki
options:
loki-url: http://172.17.0.1:3100/loki/api/v1/push

Тут вписан ip docker host, в реальном приложении впишите сюда ip сервера, где крутится ваш loki.
5. Пушим изменения, дожидаемся выполнения pipeline.
6. Заходим в графану, раздел Explore, делаем запрос {compose_project="app-example"} |= `` | json и видим наши логи.

Всё готово! На этом завершаю цикл постов про графану и локи. Если есть непонятные моменты - велкам в комментарии. Также в комментах можете предлагать темы для следующих постов, которые вам интересны
👍1🔥1
Как правильно лить логи приложения из контейнера с RoadRunner?

В варианте с docker compose я делал так, как описано в посте выше. Отдельный контейнер, который связан с основным через volume. Основной контейнер стримит логи RoadRunner, второй стримит логи приложения. И все бы хорошо, но в кластере (если это не k8s) такой вариант не пройдет. Контейнеры там никак не связаны и могут гулять по разным нодам кластера.

Какой выход? Оказывается RoadRunner умеет сам через себя стримить логи приложения. Чтобы это корректно работало - нужно настроить приложение так, чтобы оно писало логи в stderr (Пример для Symfony + Monolog):
main:
type: stream
path: php://stderr
formatter: monolog.formatter.json


И настроить RoadRunner, чтобы он не оборачивал логи приложения в свой json:
logs:
mode: production
output: stdout
channels:
server:
mode: raw


И всё! Теперь, посмотрев docker logs, вы увидите и логи RoadRunner и логи приложения, всё в формате json.
👍4
О чем хотите почитать?

В эти выходные увлекся пет-проектом и пост не успеваю подготовить. Поэтому давайте проведем опрос. Тема поста на следующие выходные:
Anonymous Poll
50%
Grafana + Prometheus (Мониторинг железа, алерты)
56%
Best practics по написанию Dockerfile для php проектов
11%
У меня есть классная идея (Напишу в комментарии)
Как я собираю php приложение в докере (Часть 1)

С небольшим отрывом победили лучшие практики написания Dockerfile для php.

Начнем. На примере symfony приложения:

1. Указываем все необходимые секции FROM. Их может быть сколько угодно.
В данном случае это будет composer, roadrunner, php-extension-installer (Удобная штука, чтобы самому не прописывать зависимости расширений php) и сам php на базе Debian bookworm. Здесь мы указываем первые две цифры версии, чтобы не получить breaking changes, но получать обновления безопасности. И редакцию ОС для php образа.
FROM composer:2.7 as composer
FROM spiralscout/roadrunner:2024.1 AS roadrunner
FROM mlocati/php-extension-installer:2.2 as php-extension-installer
FROM php:8.3-cli-bookworm as builder


2. Прописываем UID и GID для пользователя, под которым будет запускаться приложение. Для production окружений считается безопасным выбирать ID выше 10000, чтобы не получить случайно id пользователя, который будет совпадать с пользователем хост системы и избежать эскалации привилегий.
ARG UID=10001
ARG GID=10001


3. Копируем из FROM образов бинарники, которые нам понадобятся для сборки
COPY --from=composer /usr/bin/composer /usr/bin/composer
COPY --from=roadrunner /usr/bin/rr /usr/bin/rr
COPY --from=php-extension-installer /usr/bin/install-php-extensions /usr/bin/install-php-extensions
👍2
Как я собираю php приложение в докере (Часть 2)

4. Далее идет большой блок, где мы одной огромной командой ставим нужный нам софт, расширения php, тут же удаляем лишнее, чтобы в текущем слое docker образа не добавилось лишнего объема, вносим необходимые настройки php и присваиваем пользователю www-data выше определенные UID и GID.
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -q -y \
unzip \
&& install-php-extensions opcache intl sockets zip \
&& rm /usr/bin/install-php-extensions \
&& apt-get clean \
&& apt-get remove -q -y \
$PHPIZE_DEPS \
$BUILD_DEPENDS \
&& echo 'date.timezone = Europe/Moscow' >> /usr/local/etc/php/conf.d/project.ini \
&& echo 'opcache.max_accelerated_files = 20000' >> /usr/local/etc/php/conf.d/project.ini \
&& echo 'realpath_cache_size = 4096K' >> /usr/local/etc/php/conf.d/project.ini \
&& echo 'realpath_cache_ttl = 600' >> /usr/local/etc/php/conf.d/project.ini \
&& echo 'short_open_tag = off' >> /usr/local/etc/php/php.ini \
&& groupmod --gid=${GID} www-data \
&& usermod --uid=${UID} --gid=${GID} www-data


5. Определяем рабочую директорию приложения.
WORKDIR /app


6. Указываем env переменную, которая определяет, где будет хранится кэш composer. Она нам дальше понадобится, когда мы будем проделывать один фокус :)
ENV COMPOSER_CACHE_DIR="/tmp/composer/cache"


7. Копируем файлы, необходимые для установки зависимостей приложения. Пока только их. Они изменяются реже, поэтому установка зависимостей идет до копирования файлов всего приложения, иначе при изменении любого файла в проекте - каждый раз будут устанавливаться зависимости, чего мы хотим избежать.
COPY ./composer.json ./composer.lock ./symfony.lock ./


8. Делаем владельцем директории /app нашего пользователя www-data.
RUN chown www-data:www-data /app


9. Переключаем пользователя, от которого выполняются дальнейшие команды Dockerfile.
USER www-data


10. А дальше делаем фокус :) Монтируем директорию кэша композера с помощью специальной инструкции. Это позволяет переиспользовать кэш при сборках приложения и не качать зависимости каждый раз заново. Ну и ставим зависимости собственно. При установке зависимостей пока отключаем генерацию файлов для autoload, отключаем скрипты и делаем все это в неинтерактивном режиме.
RUN \
--mount=type=cache,uid=${UID},gid=${GID},target=/tmp/composer/cache \
composer install --no-dev --no-scripts --no-autoloader --prefer-dist --no-progress --no-interaction


11. Копируем файлы приложения, одновременно делая их владельцем www-data.
COPY --chown=www-data:www-data ./ ./


12. Теперь можно сгенерировать autoload, собрать env переменные в один файл и скомпилировать DI контейнер.
RUN composer dump-autoload --classmap-authoritative --no-dev \
&& composer dump-env prod \
&& bin/console c:c


13. Остается только определить команду запуска roadrunner.
CMD rr serve -d -c ./docker/php-prod/.rr.yaml


Дополнительно можно тут сразу указывать секцию HEALTHCHECK, но я предпочитаю это делать за пределами Dockerfile.

Вот и всё. Мы получили быстро собирающийся Dockerfile, который без лишней необходимости не пересобирает медленные слои.

P.S. Обратите внимание также на файл .dockerignore, он позволяет поместить в docker образ только те файлы приложения, которые действительно нужны в production среде.
👍5
Мониторим железо вместе с Prometheus

Сегодня займемся добавлением Prometheus к нашей графане и подключим мониторинг железа host машины, на которой она работает.

1. На саму host машину ставим prometheus-node-expoter. Либо ручками, либо, как мы любим, автоматически. Можно использовать мой ansible playbook, там есть команда make node-exporter

2. Добавляем конфиг для prometheus. Говорим, что надо раз в 15 секунд дергать node_exporter и запрашивать у него данные. ip опять же указываем docker gateway, как это было с loki

3. Добавляем datasource для графаны.

4. Добавляем provider для дашбордов.

5. Добавляем сам dashboard. Он скачан отсюда.

6. Правим .gitlab-ci.yml, чтобы все нужное копировалось на сервер.

7. Правим docker-compose.yaml. Тут добавляем сам prometheus. Под него создаем отдельный volume для данных, указываем, что данные нужно чистить раз в неделю и пробрасываем дашборды для графаны

8. Пушим всё в репозиторий. Дожидаемся выполнения pipeline, заходим в графану в раздел Dashboards и видим там наш Node exporter.

Готово! Рисуются красивые графики, где мы можем наблюдать за всеми нужными показателями сервера.

В следующий раз поговорим как на основе проделанного настроить алерты на почту, в случае достижения критических показателей
👍2
PostgreSQL 16 Internal

Пост про алерты пока не готов, скорее всего будет в следующие выходные. А пока предлагаю посмотреть интереснейший материал про всеми нами любимый postgres:
https://www.youtube.com/watch?v=2gcBOhY6Xe8&list=PLlghaO_0b1OepxMpMZAIoI3dHgozIEyLp
🔥1
Настраиваем алерты вместе с Grafana

Сегодня наша задача в том, чтобы настроить уведомления на почту при достижении критических показателей по железу на наш email.
Алерты будем рассылать с помощью самой графаны, настраивать все будем как всегда с помощью подхода Infrastructute as a code.

1. В конфиг datasource-prometheus.yaml вносим идентификатор uid, чтобы потом его задействовать в настройке алертов. Так как мы уже ранее настраивали этот datasource - я просто взял и прописал сюда uid существующего datasource из графаны. Апдейтить uid при provisioning графана не умеет.

2. В grafana.ini в секцию [smtp] прописываем настройки отправки почты. SMTP сервер, юзер, пароль и т.д.

3. Создадим папку alerting в корне проекта. Добавим туда файл contact-point.yaml. Здесь задается на какие email отправлять письма с алертами.

4. Добавим файл notification-policy.yaml чтобы связать новый contact point с дефолтной политикой отправки алертов.

5. Добавим файлы cpu.yaml, ram.yaml и rootfs.yaml - это алерты по нагрузке на процессор, процент занятой ОЗУ и процент заполненности диска на сервере. Здесь указываем uid datasource прометеуса, который мы задали в конфиге выше. И в разделе expr ip нашего docker host, на котором у нас работает node-exporter. Содержимое самого expr я взял из дашборда, который мы создавали в прошлый раз. В каждом показателе там можно щелкнуть на ... -> More -> New alert rule и подсмотреть какие expr используются для построения графика. Там же можно и указать процент, после которого будет срабатывать алерт. После этого в разделе Alerting -> Alert Rules правила можно экспортировать для provisioning через код.

6. Остается только поправить .gitlab-ci.yml и docker-compose.yaml, чтобы все конфиги копировались при деплое на сервер и подтягивались в графану при поднятии сервисов в докере.

7. Пушим всё в репозиторий, дожидаемся выполнения pipeline и проверяем в разделе Alerting -> Alert Rules, что правила добавились и работают корректно.
👍2
Корректная настройка retention в loki

Как оказалось, в прошлый раз мы некорректно настроили удаление старых логов в loki.
Table manager не работает на одном инстансе и с недавних пор стал deprecated.
Давайте исправлять ситуацию.

Внимательно читаем доку и вносим изменения:

1. Добавляем в конфиг секцию compactor:

compactor:
working_directory: /data/loki/retention
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150


2. В limits_config добавляем желаемый период хранения логов:

retention_period: 7d


3. Ну и закомментируем секцию table_manager.

4. Пушим изменения в репо, дожидаемся выполнения pipeline.

Готово, теперь удаление старых логов будет происходить корректно. Хочу заметить, что удаление произойдет не сразу, нужно какое-то время, чтобы loki занялся очисткой старых данных
Отпуск

Ухожу на пару недель в отпуск :) Посты продолжу писать после отпуска, не теряйте
👌3
Блокировка docker hub

Проснулись, потянулись и обнаружили, что докер хаб теперь не работает в РФ :) Ci/Cd встал колом, образы не скачиваются.
Но паниковать рано. На серверах вносим изменения в файл /etc/docker/daemon.json

Добавляем:
{
"registry-mirrors": ["https://mirror.gcr.io"]
}

Делаем sudo systemctl restart docker

Все снова работает, но советую все нужные образы скачать во внутренний registry, чтобы избежать новых приключений :)
👍10😱1
10 лет Kubernetes!

Сегодня исполняется ровно 10 лет с момента первого коммита в репозиторий кубера. Это, без сомнения, самый интересный и крутой инструмент для управления контейнерными нагрузками. Коллеги из компании Флант написали хороший пост о том, как все начиналось - https://habr.com/ru/companies/flant/articles/820153
🔥3
PHP исполнилось 29!

На днях нашему любимому пыху исполнилось 29. По этому случаю Роман Пронский собрал и запустил 1 версию. Сам я начинал с 4-ки в далеком 2008. С тех пор php сильно вырос как язык, очень серьезно подросла и экосистема вокруг. Появились такие классные инструменты как psalm, php-cs-fixer, rector, deptrac. Live long and prosper, php!
🔥4👍2
FluxCD в действии! Воркшоп от Георга Гаала

Пока собираюсь с мыслями и думаю о чем еще написать - поделюсь с вами отличным воркшопом по использованию Gitops инструмента FluxCD.

https://www.youtube.com/live/T4fkWIGahiQ
Зависимости при сборке образа приложения

Важная, но не очень очевидная, мысль - ваше приложение при сборке образа не должно зависеть ни от чего, кроме кода приложения и рантайма, который его запускает. Все настройки под конкретные окружения (prod, stage, dev) должны осуществляться только через переменные среды (env). Мы собираем только один образ и используем его в разных окружениях. Если этого не делать - увеличится время сборки, так как мы будем собирать приложение под разные среды, увеличится объём хранимых в registry образов. А это доп. расходы времени и финансов на дисковое пространство