Один контейнер с утилитами для обслуживания кластера DOCKER-SWARM
- clean-node
- Удалить все EXITED/STOPPED/COMPLETED containers: docker container prune -f
- Удаление ненужных images: docker image prune -f -a
- Очистка кэша билдера: docker builder prune -f
- ВАЖНО
- На данный момент не реализован механизм Labels у Node
- Все Node в кластере - будут очищаться
- Параметры API запроса
- token - обязательный параметр
- nodeName - обязательный параметр (super-worker-1)
- Запуск: CRON / API
- backup-service
- Резервное копирование
- Работает только на service
- Загрузка копий на S3 хранилище (Через offen-backup)
- Creds для S3 - общие на весь кластер. Указываеются через ENV переменные
swarm-utils
- Creds для S3 - общие на весь кластер. Указываеются через ENV переменные
- Параметры API запроса
- token - обязательный параметр
- serviceName - обязательный параметр (stack-name_service-name)
- Запуск: CRON / API
- clean-service
- Очистка одного или нескольких сервисов. (Например - registry, smtp cache)
- Параметры API запроса
- token - обязательный параметр
- serviceName - обязательный параметр (stack-name_service-name)
- Запуск: CRON / API
- update-service
- Перезапуск (Обновление) сервисов. Через команду docker service update SERVICE_NAME
- Можно указать: serviceName, image?
- Параметры API запроса
- token - обязательный параметр
- serviceName - обязательный параметр (stack-name_service-name)
- image - обязательный параметр. Полное название.
image-name:tag
- isForce - необязательный параметр. Обновляем FORCE или нет
- Если force === true -> task, будет пересоздана. Даже если image - не изменился
- Запуск: API
ENV | Default | Описание |
---|---|---|
SWARM_UTILS_IS_CRON_BACKUP_SERVICE | true | Делать бэкап по кроне - бэкап сервисов |
SWARM_UTILS_IS_CRON_CLEAN_SERVICE | true | Производить чистку кластера по кроне (SERVICES) |
SWARM_UTILS_IS_CRON_CLEAN_NODE | true | Производить чистку кластера по кроне (NODES) |
SWARM_UTILS_CRON_EXPR | 0 0 3 * * * | Интервал работы крон (Cron string). Пакет |
SWARM_UTILS_ADMIN_TOKEN_LIST | - | Список из токенов, которае имеют админ права. Пример: tokenA,tokenB,tokenC |
SWARM_UTILS_DOCKER_CLI_IMAGE_NAME | docker:25.0.5-cli-alpine3.20 | Название docker-cli image, который будет запускаться на каждой NODE |
SWARM_UTILS_BACKUP_SERVICE_EXEC_SHELL | /bin/sh | shell - для EXEC комманды в момент BACKUP_SERVICE. docker exec ... SHELL -c |
SWARM_UTILS_CLEAN_SERVICE_EXEC_SHELL | /bin/sh | shell - для EXEC комманды в момент CLEAN_SERVICE. docker exec ... SHELL -c |
SWARM_UTILS_BACKUP_SERVICE_EXEC_TIMEOUT | 600_000 | Сколько времени на EXEC команду в момент BACKUP_SERVICE. Значение в MS |
SWARM_UTILS_BACKUP_SERVICE_STOP_TIMEOUT | 30_000 | Сколько времени на STOP команду в момент BACKUP_SERVICE. Значение в MS |
SWARM_UTILS_BACKUP_SERVICE_VOLUME_LIST_UPLOAD_TIMEOUT | 600_000 | Сколько времени на UPLOAD команду в момент BACKUP_SERVICE. Значение в MS |
SWARM_UTILS_BACKUP_SERVICE_START_TIMEOUT | 300_000 | Сколько времени на START команду в момент BACKUP_SERVICE. Значение в MS |
SWARM_UTILS_CLEAN_SERVICE_EXEC_TIMEOUT | 60_000 | Сколько времени на EXEC команду в момент CLEAN_SERVICE. Значение в MS |
SWARM_UTILS_UPDATE_SERVICE_TIMEOUT | 60_000 | Сколько времени на UPDATE_SERVICE. Значение в MS |
SWARM_UTILS_CLEAN_NODE_IMAGE_TIMEOUT | 60_000 | Сколько времени на IMAGE PRUNE команду в момент CLEAN_NODE. Значение в MS |
SWARM_UTILS_CLEAN_NODE_BUILDER_TIMEOUT | 60_000 | Сколько времени на BUILDER PRUNE команду в момент CLEAN_NODE. Значение в MS |
SWARM_UTILS_CLEAN_NODE_CONTAINER_TIMEOUT | 60_000 | Сколько времени на CONTAINER PRUNE команду в момент CLEAN_NODE. Значение в MS |
SWARM_UTILS_PENDING_SERVICE_TIMEOUT | 20_000 | Сколько времени на запуск сервиса. Значение в MS |
SWARM_UTILS_LOCK_TIMEOUT | 10_000 | 10 секунд - сколько времени на уствновку блокировки. Значение в MS |
SWARM_UTILS_EXTRA_TIMEOUT | 10_000 | Дополнительное время для блокировки. Задержки сети и ТД. Значение в MS |
SWARM_UTILS_S3_URL | s3-api.domain.com | Доменное имя где находится облако S3 |
SWARM_UTILS_S3_HTTPS | true | Использовать HTTPS или нет. Если нет - подключение будет идти через http:// |
SWARM_UTILS_S3_BUCKET | my-bucket-name | Название bucket - куда заливать бэкап |
SWARM_UTILS_S3_ACCESS_KEY | ... | Ключ для доступа к S3 |
SWARM_UTILS_S3_SECRET_KEY | ... | Секрет для доступа к S3 |
SWARM_UTILS_S3_RETENTION_DAYS | 5 | Сколько времени живет каждый бэкап в S3 |
SWARM_UTILS_REGISTRY_USER | ... | Имя пользователя, для доступа к docker registry |
SWARM_UTILS_REGISTRY_PASSWORD | ... | password/token от registry. token - справами на чтение/запись в registry |
SWARM_UTILS_REGISTRY_URL | registry.domain.com | url регистри. Обязательно используется HTTPS |
- swarm-utils.clean
- enable=true/false
- exec
- exec.shell='/bin/bash'
- token
- swarm-utils.backup
- enable=true/false
- exec
- exec.shell='/bin/sh'
- stop=true/false
- volume-list-upload=volume1,volume2,volume3,...
- volume-list-upload.s3.url=s3-api.domain.com
- volume-list-upload.s3.https=true/false
- volume-list-upload.s3.access-key=...
- volume-list-upload.s3.secret-key=...
- volume-list-upload.s3.bucket=...
- volume-list-upload.s3.retention-days=8
- token
- swarm-utils.update
- enable=true/false
- registry.auth=true/false
- registry.user=...
- registry.password=...
- registry.url=...
- token
На данный момент - НЕТ
- Есть кластер -
docker-swarm
- 1 голова
- 2 воркера
- 2 билдера
- 1 хранилище
- Доступ к серверам по SSH - есть только у Админа (Админов)
- То есть - НИКТО из обычных разрабов не имеет доступа к серверам (Напрямую)
- На голове установлен -
swarm-utils
- Наружу - никаких портом не открывается (По умолчанию)
- Если надо - можно. НО - тогда нужно подумать про безопасность
- Доступ - возможен только через внутренню
overlay
сеть
- CI/CD
- На сборщиках - установлены раннеры (GitLab runner, Github runner и ТД)
- У раннера есть доступ к docker.sock - ЭТО ВАЖНО
- Производится сборка - docker build . -t ...
- Производится push - на regisdtry. Не важно - что это за registry
- Поднимается сервис - alpine-curl (Условно) + attach к overlay сети, которая имеет доступ к swarm-utils
- Запрос - на /update с параметрами
- Готово
- Идея токенов - .labels.token
- У каждого сервиса, который обслуживается через
swarm-utils
- свой токен доступа - Токен доступа - один на сервис+действие
- У каждого сервиса, который обслуживается через
- Работаем через GitHub, организация
- Работаем через просто GitHub
- Работаем через GitLab (self-hosted или нет)
- Когда вызывается /update - через параметры можно указать ЛЮБОЙ image
- Ничего не мешает разработчику - модифицировать CI/CD и указать вредоносный image
- Есть разработчик X
- У него есть доступ к двум проектам - a, b
- Следовательно - разработчик знает токены для перезапуска проектов a, b
- Для кажого проекта - свой токен (Помним правило)
- В какой-то момент - принято решение забрать права доступа у разработчика X к провекту a
- Все - разработчик X больше не может делать коммиты в проект a
- Он все еще может делать коммиты в проект b -> тригерить CI/CD в кластере -> имеет доступ к swarm-utils
- Он знает токен от проекта a -> модифицирует CI/CD проекта b и указывает токен от проекта a
- Запускает вредоносный image (Что следует из первой уязвимости)
- Админы
- Cписок токенов указан через ENV
SWARM_UTILS_ADMIN_TOKEN_LIST
- Могут делать ВСЕ
- Не зависят от
labels.token
- Cписок токенов указан через ENV
- Все остальные пользователи
- Могут делать ВСЕ
- Зависят от
labels.token
- Работают только с теми контейнерами, к которым у них есть доступ
- Бэкап и очистку - можно запустить по API
- Бэкап name: nodeName_volumeName_date
- Бэкап + Очистка идут по одной кроне
- CLEAN
- BACKUP
- Через API запустили clean-service - service_A
- Запустился процесс. Процесс займет условно 30 секунд
- Через 5 секунд - стартует CRON для очситки всех сервисов
- И запускает очситку на service_A
- Итог: НЕИЗВЕСТНО. Но ничего хорошего...
- На все действия с конкретным service, node - ставится MUTEX
- На уровне кода в TypeScript (JS) NodeJS - используется async-lock
- Ключ для блокировки - название сервиса или название node
- И только после успешной установки блокировки - NodeJS идет выполнять работу с сервисом
- Через API запустили clean-service - service_A
- Запустился процесс. Процесс займет условно 30 секунд
- NodeJS - упал, перезапустился, сервер перезапустился
- Итог: async-lock сбросился
- Через 5 секунд - стартует CRON для очситки всех сервисов
- И запускает очситку на service_A
- Итог: НЕИЗВЕСТНО. Но ничего хорошего...
- очистка сервиса - это exec команда на docker container
- Чтобы выполнить docker exec - нужно иметь доступ к docker.sock, где запущен контейнер
- Мы работаем в условиях - DockerSwarm cluster (10 отдельных серверов)
- Чтобы выполнить docker-exec на Node_A
- Создается service с образом docker-cli на указанной Node
- Название сервиса -
CONSTANT+SERVICE_NAME
- Монтирование docker.sock - через volume
- Как параметр запуска: docker exec CONTAINER_ID /bin/sh ...
- Создать более обного сервса с одинаковым названием - НЕЛЬЗЯ (На уровне docker engine)
- Через API запустили clean-service - service_A
- Запустился процесс. Процесс займет условно 30 секунд
- NodeJS - упал, перезапустился, сервер перезапустился
- Итог: async-lock сбросился
- Через 5 секунд снова кинули запрос на очистку сервиса - service_A
- LOCK - поставился (Так-как он сбросился после перезапуска сервиса)
- А новый сервис - создать не можем
- Так как - на уровне docker engine запрещено создавать сервисы с одинаковым названием
- Перед запуском нового сервиса - для работы с service_A - проверка и удаление
- Проверить и удалить существующие ВСЕ сервисы для service_A
- Все сервисы NOT_EXIST - продолжаем
- Некоторые сервисы EXIST && canRemove (isComplete) - завершаем их и продолжаем
- Есть хоть один сервис который EXIST && !canRemove - выброс с ошибкой
- Это значит - что в один момент времени с service_A может происходить только ОДНА операция
- Время на блокиррку = 10 секунд
- Время на выполнение = 10 минут
- Общее время = 10 минут 10 секунд
- Приходит два запроса через API на update, service=service_a
- Прежде чем выполнить команду - ставится AsyncLock с ключем: update_service_a
- Один из запрсоов - сможет поставить LOCK и начать процесс обновления
- Второй из запросов - вывалится с ошибкой, так как не сможет поставить LOCK
- async-lock, Timeout состоит из нескольких частей
- Время на установку LOCK (пакет: async-lock)
- Время на выполнение операции, после установки LOCK
- Общее время: установка LOCK + выполнение операции
- dockerWaitForServiceComplete - Ожидание, пока сервис закончит работу
- Для каждой операции есть фиксированный timeout - указан в ENV
- SWARM_UTILS_PENDING_SERVICE_TIMEOUT - Время на запуск сервиса. Указано в ENV
- Ситуация: Запуск сервиса с неправильным Constraint -> вечный pending
- Как считается
- Время операции = SUM((timeout операции + SWARM_UTILS_PENDING_SERVICE_TIMEOUT) * количество операций) + EXTRA_TIMEOUT
- Время суммарно = Время операции + LOCK_TIMEOUT
- Количество операций: Например надо сделать clean-service, а у service 4 реплики (4 task, на разных Node) = execTimeout * 4
- Остановка сервиса в момент BACKUP
- Остановка === docker service scale SERVICE_NAME=0
- Работает только на сервисы, которые запущены в mode=Replicated
- Кластер подразумевает (Хоть и не всегда) работу с приватным container-registry
- Доступ по логин/пароль
- Указать конкретный URL
- Могут быть контейнеры, из разных registry, с разными CREDS
- Для каждого сервиса, который .update.enable=true -> можно указать через labels данные для registry
- auth = true/false
- user
- password
- url
- Если auth = true (Нужна авторизация, чтобы обновить этот service)
- НО - не указаны через labels: user || password || url
- -> будут взяты из ENV
- Если в ENV они не указаны (Хоть одна из переменных - пустая)
- Будет ошибка
- Авторизация требует наличия файла: $HOME/.docker/config.json
- Этот файл НЕ МОНТИРУЕТСЯ внутрь контейнера при запуске
- -> Авторизация будет производиться внутри контейнера, который отвечает за обновление сервиса
- Немного теории
- Сервис swarm-utils - работает в контуре docker-swarm
- Все контейнеры в кластере docker-swarm - можно связать через overlay сеть
- В этом сценарии - контейнеры могут общаться между собой
- Если overlay сеть сделть attachable -> к ней можно присоединять обычные контейнеры, запущенные через docker run ...
- Практика
- Создать overlay сеть, отдельную для swarm-utils
- docker network create --driver overlay --subnet 10.48.0.0/16 --gateway 10.48.0.1 --attachable swarm-utils-overlay-net
- 10.48.0.0/16 и 10.48.0.1 -> можно менять. Тут кому как надо
- --attachable -> обязательный параметр
- Как известно из документации Docker: 1 service === MANY tasks
- Введем определения
- TASK
- HELP_SERVICE
- TARGET_SERVICE/TARGET_NODE
- После завершения команды
dockerWaitForServiceComplete
запускается командаdockerHelpServiceCompleteInfo
- Это команда собирает логи с каждой
TASK
в указанном SERVICE (по serviceName) - Что собирать логи с
SERVICE/TASK
- нужно запускатьSERVICE
с log-driver=json-file/journald - В данном случае serviceName ===
HELP_SERVICE.name
- Это команда собирает логи с каждой
- Если хоть одна
TASK
упала с ошибкой -> ВЕСЬHELP_SERVICE
упал с ошибкой- За это отвечает параметр isFailed в результате команды
dockerHelpServiceCompleteInfo
- За это отвечает параметр isFailed в результате команды
- На самом верхнем уровне (Где
async-lock
) - возвращается массив из результатов по каждомуHELP_SERVICE
- Например: При clean-node - заускается 3 helpService. Container, image, builder
- Если хоть один
HELP_SERVICE
упал с ошибкой -> вся процедура поTARGET_SERVICE/TARGET_NODE
прошла с ошибкой - Из метода возвращается массив с резльтатами
- docker node ls --format json
- docker volume ls -f driver=local --format json
- docker service ls --filter label="LABEL" --format json
- Получение списка сервисов
- docker service ps SERVICE_NAME --format json
- Получение списка TASKS
- taskId = .ID (380xcsrylpmc0wvl5ntd3xfa5)
- taskName = .Name (nginx_master.2)
- taskNode = .Node (internal-manager-1) (hostname)
- docker inspect TASK_ID --format json
- Чтобы получить id контейнера - нужно проинспектировать task
- containerId = .[0].Status.ContainerStatus.ContainerID
- docker inspect SERVICE_ID --format json
- docker service ps SERVICE_NAME --filter 'desired-state=running' --format json
- Команда, для запуска внутри контейнера: docker exec CONTAINER_ID /bin/sh -c 'LABEL_STRING'
- docker service ls --filter label="docker-backuper.volume-list-upload" --format json
- docker service ls --filter mode=replicated --filter label="docker-backuper.stop=true" --format json
- docker service scale SERVICE_NAME=123
- docker service scale SERVICE_NAME=0
- docker service logs SERVICE_NAME/SERVICE_ID/TASK_NAME/TASK_ID
- Проблема: работает только на сервисы с log-driver = json-file/journald
- docker service remove SERVICE_NAME
- docker inspect --type
- container|image|node|network|secret|service|volume|task|plugin
- Сделать метод, для переноса docker-volumes между серверами
- Нужно перенести Minio с сервера A -> на сервер B
- В момент update добавить labels
- .update.exec-pre
- .update.exec-post
- Добавить node.labels.token
- Чтобы можно было по АПИ дергать Clean на Node
- Labels. Добавить возможность настрйоки timeout для каждого действия
- Если label не указан - timeout из ENV (по умолчанию)
- Список labels (SERVICE)
- .backup.exec.timeout
- .backup.stop.timeout
- .backup.volume-list-upload.timeout
- .backup.start.timeout
- .clean.exec.timeout
- Список labels (NODE)
- .clean.image.timeout
- .clean.builder.timeout
- .clean.container.timeout
- Использовать node-labels
- Сколько времени на каждую операцию - для очистки NODE
- Какие пользователи, имеют права доступа на очистку этой NODE
- Список labels
- clean.enable
- clean.image.enable
- clean.image.timeout
- clean.builder.enable
- clean.builder.timeout
- clean.container.enable
- clean.container.timeout
- clean.token
- API. Во все методы - добавить параметр waitRes=true/false
- Если ждать ответ не надо - запускать без await. И сразу отдать res: процесс запущен
- Labels. Добавить labels для Node
- swarm-utils.clean
- enable=true/false
- exec
- token
- swarm-utils.backup
- enable=true/false
- exec
- volume-list-upload=volume1,volume2,volume3,...
- token
- swarm-utils.clean
- Добавить ENV переменную для маскирования логов
- Скрыкает полный вывод в команде bashExec. Добавляет звездочки
- Добавить API методы
- GET /service/status (info)
- получение списка Service
- получение писка Service по labels
- Получение списка Nodes по Labels
- Уменьшить общее количество логов
- Сейчас для CLEAN одного сервиса - тонна логов....
- Логи bashExec, логи вывода
- Там могут быть секретные данные - надо придумать, как их скрывать
- Передавать функцию в команду bashExec - для модификации ввода/вывода ?
- API. Маска на токен
- боди
- bashExec
- На данный момент - токен, с которым идет запрос, светится в логах, без маски
- Немного переработать структуру файлов
- Связанных с Docker
- Они стали достаточно большими
- При update - есть проблема с логами
- Хотим собрать логи с сервиса, которы обновляли
- А у сервиса настроен log-drivar как syslog или что-то еще
- Выполнить команду docker service logs XXX -> не получится, работает на сервисы с log-driver=json-file/journald
- А при перезапуске - контейнер упал ...
- В логах команды docker service update будет только:
service update paused: update paused due to failure or early termination of task TASK_ID
- Функционал, обновления версий контейнеров
- Отдельный сервис (Контейнер) - для обновление сервисов. Вызов команды /service/update
- Отдельный сервис для PUSH собранный образов в registry ?
- Через docker info --format json - можно получить Swarm.NodeID
- Swarm: {"NodeID":"gw97a8q5pfqsfqhncmj987qc2","NodeAddr":"185.224.248.118","LocalNodeState":"active","ControlAvailable":false,"Error":"","RemoteManagers":[{"NodeID":"cxfiamgp1mwxqcjzvbl6jd7ox","Addr":"185.224.248.76:2377"}]}
- Произвести сборку на этой Node
- После - обратиться в swarm-utils и попросить его запушить в registry ?
- Получить список неиспользуемых volumes
- Получить список node
- на каждой node - получить список volumes
- проверить - что они
unused
- Добавить возможность указать адрес docker.sock
- На примере с Traefik + HA-proxy + WorkerNode
- Можно поднять HA-proxy на ManagerNode и открыть доступ к docker.sock через TCP
- multi-tenant
- В одном swarm cluster - может быть запущено НЕСКОЛЬКО swarm-utils
- Учесть проблему с CRON. Там нет выборки по токен - там используются ВСЕ и СРАЗУ