Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05881e8d74 |
@@ -634,6 +634,7 @@ make custom-images # справка по собственным
|
|||||||
### Основная документация
|
### Основная документация
|
||||||
|
|
||||||
- **[docs/getting-started.md](docs/getting-started.md)** - Быстрый старт
|
- **[docs/getting-started.md](docs/getting-started.md)** - Быстрый старт
|
||||||
|
- **[docs/quickstart-for-dummies.md](docs/quickstart-for-dummies.md)** - Пошаговое руководство для новичков (роли, плейбук, линт, тесты, пресеты, инвентори, деплой)
|
||||||
- **[docs/molecule-guide.md](docs/molecule-guide.md)** - Руководство по Molecule
|
- **[docs/molecule-guide.md](docs/molecule-guide.md)** - Руководство по Molecule
|
||||||
- **[docs/creating-roles.md](docs/creating-roles.md)** - Создание ролей
|
- **[docs/creating-roles.md](docs/creating-roles.md)** - Создание ролей
|
||||||
- **[docs/devops-role.md](docs/devops-role.md)** - Универсальная роль devops для настройки пользователей и SSH
|
- **[docs/devops-role.md](docs/devops-role.md)** - Универсальная роль devops для настройки пользователей и SSH
|
||||||
|
|||||||
@@ -35,8 +35,12 @@ RUN apt-get install -y \
|
|||||||
RUN wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64 \
|
RUN wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64 \
|
||||||
&& chmod +x /usr/local/bin/yq
|
&& chmod +x /usr/local/bin/yq
|
||||||
|
|
||||||
# Устанавливаем Docker CLI
|
# Устанавливаем Podman (вместо Docker). Для Ubuntu 22.04 — из universe
|
||||||
RUN apt-get install -y docker.io docker-compose
|
RUN apt-get install -y software-properties-common \
|
||||||
|
&& add-apt-repository -y universe \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y podman \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
# Устанавливаем kubectl
|
# Устанавливаем kubectl
|
||||||
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
|
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
|
||||||
@@ -46,10 +50,10 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s
|
|||||||
# Устанавливаем Helm
|
# Устанавливаем Helm
|
||||||
RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||||
|
|
||||||
# Устанавливаем Kind
|
# Устанавливаем Minikube (вместо Kind, для использования с драйвером podman)
|
||||||
RUN curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64 \
|
RUN curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 \
|
||||||
&& chmod +x ./kind \
|
&& chmod +x minikube-linux-amd64 \
|
||||||
&& mv ./kind /usr/local/bin/
|
&& mv minikube-linux-amd64 /usr/local/bin/minikube
|
||||||
|
|
||||||
## Устанавливаем Istio CLI
|
## Устанавливаем Istio CLI
|
||||||
#RUN curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.22.1 sh - \
|
#RUN curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.22.1 sh - \
|
||||||
@@ -79,18 +83,17 @@ RUN chown -R ansible:ansible /ansible
|
|||||||
# Переключаемся на пользователя ansible
|
# Переключаемся на пользователя ansible
|
||||||
USER ansible
|
USER ansible
|
||||||
|
|
||||||
# Устанавливаем дополнительные роли
|
# Устанавливаем дополнительные роли (коллекция containers.podman в requirements.yml)
|
||||||
RUN ansible-galaxy install geerlingguy.docker \
|
RUN ansible-galaxy install geerlingguy.kubernetes
|
||||||
&& ansible-galaxy install geerlingguy.kubernetes
|
|
||||||
|
|
||||||
# Устанавливаем molecule как root
|
# Устанавливаем molecule как root (delegated driver для Podman)
|
||||||
RUN pip3 install ansible ansible-core ansible-lint molecule molecule-docker passlib
|
RUN pip3 install ansible ansible-core ansible-lint molecule passlib
|
||||||
|
|
||||||
# Проверяем, что molecule установлен
|
# Проверяем, что molecule установлен
|
||||||
RUN which molecule || echo "molecule not found"
|
RUN which molecule || echo "molecule not found"
|
||||||
|
|
||||||
# Настройки для работы с Docker
|
# Настройки для работы с Podman (сокет монтируется с хоста)
|
||||||
ENV DOCKER_HOST=unix:///var/run/docker.sock
|
ENV CONTAINER_HOST=unix:///run/podman/podman.sock
|
||||||
ENV ANSIBLE_FORCE_COLOR=1
|
ENV ANSIBLE_FORCE_COLOR=1
|
||||||
ENV ANSIBLE_STDOUT_CALLBACK=yaml
|
ENV ANSIBLE_STDOUT_CALLBACK=yaml
|
||||||
ENV ANSIBLE_CALLBACKS_ENABLED=profile_tasks
|
ENV ANSIBLE_CALLBACKS_ENABLED=profile_tasks
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# Ansible Controller для Podman (сокет Podman)
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
version: "3.9"
|
version: "3.9"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@@ -7,10 +10,10 @@ services:
|
|||||||
privileged: true
|
privileged: true
|
||||||
command: sleep infinity
|
command: sleep infinity
|
||||||
environment:
|
environment:
|
||||||
DOCKER_HOST: unix:///var/run/docker.sock
|
CONTAINER_HOST: unix:///run/podman/podman.sock
|
||||||
ANSIBLE_VAULT_PASSWORD_FILE: /ansible/vault/.vault
|
ANSIBLE_VAULT_PASSWORD_FILE: /ansible/vault/.vault
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /run/podman/podman.sock:/run/podman/podman.sock
|
||||||
- .:/ansible
|
- .:/ansible
|
||||||
working_dir: /ansible
|
working_dir: /ansible
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
---
|
---
|
||||||
# Ansible Collections for Molecule Universal
|
# Ansible Collections for Molecule Universal (Podman)
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
collections:
|
collections:
|
||||||
- name: community.docker
|
- name: containers.podman
|
||||||
version: ">=3.0.0"
|
version: ">=1.10.0"
|
||||||
- name: community.general
|
- name: community.general
|
||||||
version: ">=7.0.0"
|
version: ">=7.0.0"
|
||||||
- name: ansible.posix
|
- name: ansible.posix
|
||||||
|
|||||||
86
docs/podman.md
Normal file
86
docs/podman.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Работа с Podman (ветка podman)
|
||||||
|
|
||||||
|
**Автор:** Сергей Антропов
|
||||||
|
**Сайт:** https://devops.org.ru
|
||||||
|
|
||||||
|
В ветке `podman` проект переведён на использование **только Podman** (без Docker и Docker Compose).
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- **Podman** установлен и запущен (rootful: сокет `/run/podman/podman.sock`)
|
||||||
|
- **podman compose** (встроен в Podman 4.1+ или пакет `podman-compose`)
|
||||||
|
- Для K8s: **Minikube** и **kubectl** на хосте
|
||||||
|
|
||||||
|
## Основные изменения
|
||||||
|
|
||||||
|
### Molecule
|
||||||
|
|
||||||
|
- Драйвер: `delegated` (создание/удаление контейнеров в `create.yml` / `destroy.yml` через Ansible).
|
||||||
|
- Используется коллекция **containers.podman** (`podman_network`, `podman_container`).
|
||||||
|
- Сокет: `/run/podman/podman.sock` (DOoD-узлы заменены на POoD с монтированием сокета Podman).
|
||||||
|
- Инвентарь: `ansible_connection=containers.podman.podman`.
|
||||||
|
|
||||||
|
### Makefile
|
||||||
|
|
||||||
|
- Все вызовы `docker` заменены на `podman`.
|
||||||
|
- Сборка образов: `podman build --platform ...` (без buildx).
|
||||||
|
- Compose: `podman compose` (вместо `docker-compose`).
|
||||||
|
- Сокет в целях контроллера: `PODMAN_SOCKET ?= /run/podman/podman.sock`, монтируется в контейнер ansible-controller.
|
||||||
|
|
||||||
|
### Ansible-controller
|
||||||
|
|
||||||
|
- Образ собирается и запускается через Podman.
|
||||||
|
- В образе установлен **Podman** (вместо Docker).
|
||||||
|
- При запуске монтируется сокет хоста: `-v $(PODMAN_SOCKET):/run/podman/podman.sock`.
|
||||||
|
- Переменная окружения: `CONTAINER_HOST=unix:///run/podman/podman.sock`.
|
||||||
|
|
||||||
|
### Kubernetes
|
||||||
|
|
||||||
|
- Вместо **Kind** используется **Minikube** с драйвером `podman`.
|
||||||
|
- Кластер создаётся на хосте: `minikube start --driver=podman`.
|
||||||
|
- Скрипт: `scripts/create_minikube_cluster.py` (читает пресет из `molecule/presets/k8s/*.yml`).
|
||||||
|
- В пресетах K8s: `minikube_profile`, `minikube_addons`, `minikube_cpus`, `minikube_memory` (вместо `kind_clusters`).
|
||||||
|
|
||||||
|
## Локальные образы (без доступа к registry)
|
||||||
|
|
||||||
|
Во всех пресетах при использовании Podman **используются только локальные образы**. Выгрузка из registry отключена.
|
||||||
|
|
||||||
|
1. Соберите все образы локально один раз:
|
||||||
|
```bash
|
||||||
|
make buildall
|
||||||
|
```
|
||||||
|
2. Либо один образ для нужного пресета:
|
||||||
|
```bash
|
||||||
|
make buildall-image IMAGE=ubuntu22
|
||||||
|
make buildall-image IMAGE=debian11
|
||||||
|
```
|
||||||
|
3. После этого Molecule при `create` проверяет наличие образов локально и не выполняет `podman pull`. Если какого-то образа нет — выведет сообщение с указанием выполнить `make buildall`.
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Собрать локальные образы (без registry)
|
||||||
|
make buildall
|
||||||
|
|
||||||
|
# 2. Сеть labnet для compose (если нужен controller)
|
||||||
|
podman network create labnet 2>/dev/null || true
|
||||||
|
|
||||||
|
# 3. Запуск ansible-controller
|
||||||
|
make controller run
|
||||||
|
|
||||||
|
# 4. Тест ролей (контейнеры создаются через Podman из локальных образов)
|
||||||
|
make role test minimal
|
||||||
|
|
||||||
|
# Minikube (на хосте)
|
||||||
|
make k8s create k8s-minimal
|
||||||
|
make k8s status
|
||||||
|
kubectl get nodes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Переменные
|
||||||
|
|
||||||
|
- **PODMAN_SOCKET** — путь к сокету Podman на хосте (по умолчанию `/run/podman/podman.sock` для rootful).
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
Секция CI/CD (**cicd/**) в этой ветке **не менялась** — рассчитана на окружения с Docker. Для Podman в пайплайнах нужно отдельно устанавливать Podman и вызывать `podman` / `podman compose` в job’ах.
|
||||||
332
docs/quickstart-for-dummies.md
Normal file
332
docs/quickstart-for-dummies.md
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
# DevOpsLab: пошаговое руководство для новичков
|
||||||
|
|
||||||
|
**Автор:** Сергей Антропов
|
||||||
|
**Сайт:** https://devops.org.ru
|
||||||
|
|
||||||
|
Это руководство описывает по шагам: создание роли, объединение ролей в плейбук, линт, тестирование на контейнерах (подах) с разным количеством и разными ОС, а также формирование инвентори для деплоя на реальные серверы. Подходит и для **Docker**, и для **Podman** (отличия указаны в конце).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что нужно установить
|
||||||
|
|
||||||
|
- **Make** (обычно уже есть в Linux/macOS).
|
||||||
|
- **Podman** (ветка podman) или **Docker** (ветка main) — для контейнеров.
|
||||||
|
- **Ansible** не обязателен на хосте: тесты и деплой можно запускать через контейнер из Makefile.
|
||||||
|
|
||||||
|
Клонируйте репозиторий и перейдите в каталог проекта. Все команды ниже выполняются из корня проекта.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 1. Создать новую роль
|
||||||
|
|
||||||
|
Роль — это набор задач (tasks), шаблонов и переменных для одной цели (например, установка nginx или настройка пользователей).
|
||||||
|
|
||||||
|
**Команда:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role create
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт спросит имя роли (латиницей, например `nginx` или `myapp`). Будет создана структура:
|
||||||
|
|
||||||
|
- `roles/<имя_роли>/tasks/main.yml` — основные задачи
|
||||||
|
- `roles/<имя_роли>/handlers/main.yml`
|
||||||
|
- `roles/<имя_роли>/defaults/main.yml` — переменные по умолчанию
|
||||||
|
- `roles/<имя_роли>/meta/main.yml`
|
||||||
|
- и др.
|
||||||
|
|
||||||
|
**Что сделать после создания:**
|
||||||
|
|
||||||
|
1. Открыть `roles/<имя_роли>/tasks/main.yml` и добавить свои задачи (модули `package`, `copy`, `template`, `service` и т.д.).
|
||||||
|
2. При необходимости задать переменные в `roles/<имя_роли>/defaults/main.yml`.
|
||||||
|
|
||||||
|
Роль автоматически попадёт в общий плейбук развёртывания после следующего шага (обновления плейбуков).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 2. Объединить роли в один плейбук
|
||||||
|
|
||||||
|
Все роли, которые должны выполняться на серверах, собираются в один плейбук: **`roles/deploy.yml`**.
|
||||||
|
|
||||||
|
**Вариант А — обновить плейбук автоматически (все роли из `roles/`):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make update-playbooks
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт перезапишет `roles/deploy.yml`: для каждой роли в каталоге `roles/` будет добавлен свой play (hosts: all, одна роль в play).
|
||||||
|
|
||||||
|
**Вариант Б — править вручную:**
|
||||||
|
|
||||||
|
Откройте `roles/deploy.yml`. Каждая роль описывается своим блоком:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Установка роли repo
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- repo
|
||||||
|
|
||||||
|
- name: Установка роли devops
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
roles:
|
||||||
|
- devops
|
||||||
|
```
|
||||||
|
|
||||||
|
- Чтобы **включить** роль — добавьте такой блок или раскомментируйте существующий.
|
||||||
|
- Чтобы **выключить** роль — закомментируйте блок (перед каждой строкой поставьте `#`).
|
||||||
|
- Порядок блоков задаёт порядок применения ролей.
|
||||||
|
|
||||||
|
Итог: в `roles/deploy.yml` перечислены все роли, которые будут и тестироваться в контейнерах, и деплоиться на реальные серверы (см. шаги 4 и 6).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 3. Запуск линт-проверок
|
||||||
|
|
||||||
|
Линт проверяет синтаксис и типичные ошибки в ролях (и в плейбуках, которые их вызывают).
|
||||||
|
|
||||||
|
**Проверить все роли:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role lint
|
||||||
|
```
|
||||||
|
|
||||||
|
**Проверить одну роль:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role lint <имя_роли>
|
||||||
|
# Пример:
|
||||||
|
make role lint repo
|
||||||
|
make role lint devops
|
||||||
|
```
|
||||||
|
|
||||||
|
При ошибках будут указаны файл и строка. Исправьте замечания и запустите линт снова. Без ошибок линт завершится без падения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 4. Тестирование ролей на контейнерах (подах)
|
||||||
|
|
||||||
|
Тесты поднимают контейнеры с разными ОС, применяют к ним плейбук (в т.ч. ваши роли) и проверяют результат. Контейнеры в этом шаге — это и есть «поды» (тестовые хосты).
|
||||||
|
|
||||||
|
### 4.1. Подготовка образов (чтобы не качать из registry)
|
||||||
|
|
||||||
|
**Если используете Podman (ветка podman):**
|
||||||
|
|
||||||
|
Образы должны быть собраны **локально**. Один раз выполните:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make buildall
|
||||||
|
```
|
||||||
|
|
||||||
|
Собираются все образы (Ubuntu, Debian, CentOS и т.д.). Долго, но делается один раз. Для одного образа, например для минимального теста:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make buildall-image IMAGE=ubuntu22
|
||||||
|
make buildall-image IMAGE=debian12
|
||||||
|
```
|
||||||
|
|
||||||
|
**Если используете Docker (ветка main):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make docker build
|
||||||
|
```
|
||||||
|
|
||||||
|
(при необходимости сначала `make docker setup-builder`).
|
||||||
|
|
||||||
|
### 4.2. Запуск тестов
|
||||||
|
|
||||||
|
**С пресетом по умолчанию (обычно 2 хоста):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role test
|
||||||
|
```
|
||||||
|
|
||||||
|
**С конкретным пресетом:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role test minimal
|
||||||
|
make role test default
|
||||||
|
make role test all-images
|
||||||
|
```
|
||||||
|
|
||||||
|
Что происходит: создаются контейнеры по выбранному пресету, к ним применяется плейбук (converge), затем они удаляются. Результат виден в выводе команды.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 5. Менять количество подов и ОС (пресеты)
|
||||||
|
|
||||||
|
Количество «подов» (контейнеров) и их ОС задаются **пресетами** — файлами в `molecule/presets/` и `molecule/presets/examples/`.
|
||||||
|
|
||||||
|
### 5.1. Посмотреть доступные пресеты
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make presets list
|
||||||
|
```
|
||||||
|
|
||||||
|
Будет выведен список пресетов и краткое описание (в т.ч. количество хостов).
|
||||||
|
|
||||||
|
### 5.2. Что задаёт пресет
|
||||||
|
|
||||||
|
В пресете задаётся:
|
||||||
|
|
||||||
|
- **hosts** — список хостов (контейнеров): имя, семейство ОС (`family`), группы.
|
||||||
|
- **images** — соответствие `family` и образа (например `ubuntu22: "inecs/ansible-lab:ubuntu22-latest"`).
|
||||||
|
- Общие настройки (сеть, systemd и т.д.).
|
||||||
|
|
||||||
|
От количества элементов в **hosts** и от выбранных **family** зависит, сколько подов поднимется и какие ОС будут использоваться.
|
||||||
|
|
||||||
|
### 5.3. Примеры пресетов по количеству и ОС
|
||||||
|
|
||||||
|
| Пресет | Описание | Кол-во хостов |
|
||||||
|
|-------------|------------------------------------|----------------|
|
||||||
|
| `minimal` | Один хост (быстрый тест) | 1 |
|
||||||
|
| `default` | Два хоста (например Ubuntu + Debian) | 2 |
|
||||||
|
| `all-images`| Максимум образов/ОС | много |
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role test minimal
|
||||||
|
make role test default
|
||||||
|
make role test all-images
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4. Создать свой пресет (свои поды и ОС)
|
||||||
|
|
||||||
|
1. Скопируйте существующий пресет, например:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp molecule/presets/default.yml molecule/presets/my-preset.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Откройте `molecule/presets/my-preset.yml`.
|
||||||
|
|
||||||
|
3. В секции **hosts** укажите свои хосты. Формат одного хоста:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
hosts:
|
||||||
|
- name: web1
|
||||||
|
family: ubuntu22
|
||||||
|
groups: [web, test]
|
||||||
|
- name: db1
|
||||||
|
family: centos9
|
||||||
|
groups: [db, test]
|
||||||
|
- name: lb1
|
||||||
|
family: debian12
|
||||||
|
groups: [lb, test]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **name** — уникальное имя контейнера (пода).
|
||||||
|
- **family** — ключ из секции **images** (ubuntu20, ubuntu22, ubuntu24, debian9–debian12, centos7–centos9, alma, rocky, rhel, redos, astra, alt9, alt10 и т.д.).
|
||||||
|
- **groups** — группы для инвентори (можно использовать в плейбуках через `hosts: web` или `hosts: db`).
|
||||||
|
|
||||||
|
4. Сохраните файл. Запуск теста с вашим пресетом:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role test my-preset
|
||||||
|
```
|
||||||
|
|
||||||
|
Количество подов = количество элементов в списке **hosts**. Разные ОС = разные **family** при наличии соответствующих образов в **images** (и собранных локально при Podman через `make buildall`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Шаг 6. Инвентори и деплой на реальные серверы
|
||||||
|
|
||||||
|
Когда роли и плейбук готовы и протестированы в контейнерах, их можно применять к реальным серверам. Для этого нужен **инвентори** и команды деплоя.
|
||||||
|
|
||||||
|
### 6.1. Где лежит инвентори
|
||||||
|
|
||||||
|
Файл инвентори по умолчанию:
|
||||||
|
|
||||||
|
- **`inventory/hosts.ini`**
|
||||||
|
|
||||||
|
Именно его используют команды `make role deploy` и `make role dryrun` (см. ниже).
|
||||||
|
|
||||||
|
### 6.2. Как сформировать инвентори
|
||||||
|
|
||||||
|
Откройте `inventory/hosts.ini`. Формат — обычный ini-инвентори Ansible.
|
||||||
|
|
||||||
|
**Пример минимального инвентори:**
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Группа веб-серверов
|
||||||
|
[web_servers]
|
||||||
|
web1 ansible_host=192.168.1.10 ansible_user=deploy
|
||||||
|
web2 ansible_host=192.168.1.11 ansible_user=deploy
|
||||||
|
|
||||||
|
# Группа БД
|
||||||
|
[db_servers]
|
||||||
|
db1 ansible_host=192.168.1.20 ansible_user=deploy
|
||||||
|
|
||||||
|
# Общие переменные для всех хостов
|
||||||
|
[all:vars]
|
||||||
|
ansible_ssh_private_key_file=~/.ssh/id_rsa
|
||||||
|
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Группы** — `[web_servers]`, `[db_servers]`. Имена можно менять и добавлять свои.
|
||||||
|
- **Хосты** — каждая строка: имя хоста + переменные. Обязательно укажите **ansible_host** (IP или hostname) и **ansible_user** (пользователь для SSH).
|
||||||
|
- **all:vars** — переменные для всех хостов (ключ SSH, опции подключения и т.д.).
|
||||||
|
|
||||||
|
Чтобы применять плейбук только к части серверов, в плейбуке можно использовать группы (например `hosts: web_servers`) или ограничивать запуск через `--limit` (см. ниже).
|
||||||
|
|
||||||
|
### 6.3. Проверка без изменений (dry-run)
|
||||||
|
|
||||||
|
Перед реальным деплоем полезно проверить, что плейбук выполнится без ошибок и что изменения именно те, что нужны:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role dryrun <имя_роли>
|
||||||
|
# Пример:
|
||||||
|
make role dryrun repo
|
||||||
|
```
|
||||||
|
|
||||||
|
Команда запускает плейбук в режиме **check** (без реальных изменений) для указанной роли. Убедитесь, что в `inventory/hosts.ini` указаны ваши серверы и что с них есть SSH-доступ.
|
||||||
|
|
||||||
|
### 6.4. Реальный деплой плейбука на серверы
|
||||||
|
|
||||||
|
Когда инвентори заполнен и dry-run устраивает:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make role deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Будет выполнен плейбук **roles/deploy.yml** на всех хостах из **inventory/hosts.ini** (с подтверждением перед применением). При необходимости можно запускать Ansible вручную, например:
|
||||||
|
|
||||||
|
- Только на одной группе:
|
||||||
|
```bash
|
||||||
|
ansible-playbook -i inventory/hosts.ini roles/deploy.yml --limit web_servers
|
||||||
|
```
|
||||||
|
- Только проверка (аналог dry-run для всего плейбука):
|
||||||
|
```bash
|
||||||
|
ansible-playbook -i inventory/hosts.ini roles/deploy.yml --check --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
(Если Ansible запускается через контейнер в Makefile, пути и вызов могут отличаться; логика та же: тот же инвентори и тот же плейбук.)
|
||||||
|
|
||||||
|
### 6.5. Кратко по шагам деплоя
|
||||||
|
|
||||||
|
1. Заполнить **inventory/hosts.ini** (группы, хосты, ansible_host, ansible_user, ключ SSH).
|
||||||
|
2. Проверить доступ: `ansible -i inventory/hosts.ini all -m ping` (если Ansible установлен локально).
|
||||||
|
3. Запустить **make role dryrun <роль>** для проверки.
|
||||||
|
4. Запустить **make role deploy** для применения плейбука к реальным серверам.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Универсальность: Docker и Podman
|
||||||
|
|
||||||
|
Один и тот же сценарий (роли, плейбук, пресеты, инвентори) работает и с Docker, и с Podman. Отличия только в подготовке образов и в том, как вызываются контейнеры внутри Makefile.
|
||||||
|
|
||||||
|
| Действие | Podman (ветка podman) | Docker (ветка main) |
|
||||||
|
|-----------------------|---------------------------|----------------------------|
|
||||||
|
| Сборка образов | `make buildall` | `make docker build` |
|
||||||
|
| Один образ | `make buildall-image IMAGE=ubuntu22` | `make docker build-image IMAGE=ubuntu22` |
|
||||||
|
| Запуск тестов | `make role test [preset]` | то же |
|
||||||
|
| Линт | `make role lint` | то же |
|
||||||
|
| Деплой / инвентори | `make role deploy`, `inventory/hosts.ini` | то же |
|
||||||
|
|
||||||
|
- **Пресеты, роли, deploy.yml, inventory** — одни и те же.
|
||||||
|
- **Линт, тесты, деплой** — одни и те же команды `make role ...`.
|
||||||
|
- Разница только в том, как и откуда берутся образы (локальная сборка через `buildall` у Podman или сборка/пулл через `docker build` у Docker).
|
||||||
|
|
||||||
|
K8s в этом руководстве не рассматривается; см. отдельную документацию по Kubernetes при необходимости.
|
||||||
@@ -10,8 +10,9 @@
|
|||||||
# Проверяем сначала в папке k8s, затем в основной папке presets
|
# Проверяем сначала в папке k8s, затем в основной папке presets
|
||||||
preset_file: "{{ '/workspace/molecule/presets/k8s/' + preset_name + '.yml' if (preset_name in ['k8s-minimal', 'kubernetes', 'k8s-full'] or preset_name.startswith('k8s-')) else '/workspace/molecule/presets/' + preset_name + '.yml' }}"
|
preset_file: "{{ '/workspace/molecule/presets/k8s/' + preset_name + '.yml' if (preset_name in ['k8s-minimal', 'kubernetes', 'k8s-full'] or preset_name.startswith('k8s-')) else '/workspace/molecule/presets/' + preset_name + '.yml' }}"
|
||||||
|
|
||||||
# Fallback значения если preset файл не найден
|
# Fallback значения если preset файл не найден (Podman использует ту же сеть)
|
||||||
docker_network: labnet
|
docker_network: labnet
|
||||||
|
podman_network: "{{ docker_network }}"
|
||||||
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
|
||||||
images:
|
images:
|
||||||
alt9: "inecs/ansible-lab:alt9-latest"
|
alt9: "inecs/ansible-lab:alt9-latest"
|
||||||
@@ -89,20 +90,20 @@
|
|||||||
hosts: "{{ filtered_hosts | default(hosts) }}"
|
hosts: "{{ filtered_hosts | default(hosts) }}"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# СЕТЕВОЕ ПОДКЛЮЧЕНИЕ
|
# СЕТЕВОЕ ПОДКЛЮЧЕНИЕ (Podman)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
- name: Network setup
|
- name: Network setup
|
||||||
debug:
|
debug:
|
||||||
msg: |
|
msg: |
|
||||||
================================================================================
|
================================================================================
|
||||||
НАСТРОЙКА СЕТИ
|
НАСТРОЙКА СЕТИ (Podman)
|
||||||
================================================================================
|
================================================================================
|
||||||
Network: {{ docker_network }}
|
Network: {{ podman_network | default(docker_network) }}
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- name: Ensure network exists
|
- name: Ensure network exists
|
||||||
community.docker.docker_network:
|
containers.podman.podman_network:
|
||||||
name: "{{ docker_network }}"
|
name: "{{ podman_network | default(docker_network) }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -117,39 +118,39 @@
|
|||||||
Count: {{ hosts | selectattr('type','undefined') | list | length }}
|
Count: {{ hosts | selectattr('type','undefined') | list | length }}
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- name: Pull systemd images with correct platform
|
# Только локальные образы (registry запрещён). Сборка: make buildall
|
||||||
command: "docker pull --platform {{ ansible_architecture }} {{ images[item.family] }}"
|
- name: Проверка наличия локальных образов (Podman)
|
||||||
|
command: "podman image exists {{ images[item.family] }}"
|
||||||
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
when: item.family is defined and images[item.family] is defined
|
when: item.family is defined and images[item.family] is defined
|
||||||
register: pull_result
|
register: image_check
|
||||||
ignore_errors: yes
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Display pull results
|
- name: Остановка при отсутствии локальных образов
|
||||||
debug:
|
fail:
|
||||||
msg: "Pulled {{ item.item.name }}: {{ 'OK' if (item.rc is defined and item.rc == 0) else 'SKIPPED (not available for this platform)' }}"
|
msg: |
|
||||||
loop: "{{ pull_result.results | default([]) }}"
|
Локальные образы не найдены (доступ к registry запрещён).
|
||||||
loop_control:
|
Выполните: make buildall
|
||||||
label: "{{ item.item.name }}"
|
Отсутствуют образы: {{ image_check.results | default([]) | selectattr('rc', 'ne', 0) | map(attribute='item') | map(attribute='family') | list | join(', ') }}
|
||||||
|
when: image_check.results is defined and (image_check.results | selectattr('rc', 'ne', 0) | list | length > 0)
|
||||||
|
|
||||||
- name: Start systemd nodes
|
- name: Start systemd nodes
|
||||||
community.docker.docker_container:
|
containers.podman.podman_container:
|
||||||
name: "{{ item.name }}"
|
name: "{{ item.name }}"
|
||||||
image: "{{ images[item.family] }}"
|
image: "{{ images[item.family] }}"
|
||||||
networks:
|
network: "{{ podman_network | default(docker_network) }}"
|
||||||
- name: "{{ docker_network }}"
|
|
||||||
privileged: "{{ systemd_defaults.privileged }}"
|
privileged: "{{ systemd_defaults.privileged }}"
|
||||||
command: "{{ '/bin/bash -c \"while true; do sleep 30; done\"' if item.family in ['alt10', 'alt9'] else systemd_defaults.command }}"
|
command: "{{ '/bin/bash -c \"while true; do sleep 30; done\"' if item.family in ['alt10', 'alt9'] else systemd_defaults.command }}"
|
||||||
volumes: "{{ systemd_defaults.volumes | default([]) + (item.volumes | default([])) + ['/Users/inecs/PycharmProjects/DevOpsLab/vault:/workspace/vault:ro', '/Users/inecs/PycharmProjects/DevOpsLab/files:/workspace/files:ro', '/Users/inecs/PycharmProjects/DevOpsLab/roles:/workspace/roles:ro'] }}"
|
volume: "{{ systemd_defaults.volumes | default([]) + (item.volumes | default([])) + ['/workspace/vault:/workspace/vault:ro', '/workspace/files:/workspace/files:ro', '/workspace/roles:/workspace/roles:ro'] }}"
|
||||||
tmpfs: "{{ systemd_defaults.tmpfs | default([]) }}"
|
tmpfs: "{{ systemd_defaults.tmpfs | default([]) }}"
|
||||||
capabilities: "{{ systemd_defaults.capabilities | default([]) }}"
|
cap_add: "{{ systemd_defaults.capabilities | default([]) }}"
|
||||||
published_ports: "{{ item.publish | default([]) }}"
|
ports: "{{ item.publish | default([]) }}"
|
||||||
env: "{{ item.env | default({}) }}"
|
env: "{{ item.env | default({}) }}"
|
||||||
# Специальные настройки для Astra Linux и RedOS
|
security_opt: "{{ ['seccomp=unconfined', 'apparmor=unconfined'] if item.family in ['astra', 'redos'] else [] }}"
|
||||||
security_opts: "{{ ['seccomp=unconfined', 'apparmor=unconfined'] if item.family in ['astra', 'redos'] else [] }}"
|
|
||||||
platform: "{{ item.docker_platform | default(item.platform) | default(omit) }}"
|
|
||||||
state: started
|
state: started
|
||||||
restart_policy: unless-stopped
|
restart_policy: "unless-stopped"
|
||||||
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
when: item.family is defined and images[item.family] is defined
|
when: item.family is defined and images[item.family] is defined
|
||||||
@@ -160,75 +161,44 @@
|
|||||||
seconds: 10
|
seconds: 10
|
||||||
when: hosts | length > 0
|
when: hosts | length > 0
|
||||||
|
|
||||||
# Проверка готовности контейнеров
|
# Проверка готовности контейнеров (Podman)
|
||||||
- name: Wait for containers to be running
|
- name: Wait for containers to be running
|
||||||
community.docker.docker_container_info:
|
command: "podman inspect --format '{{ '{{' }}.State.Running{{ '}}' }}' {{ item.name }}"
|
||||||
name: "{{ item.name }}"
|
|
||||||
register: container_info
|
register: container_info
|
||||||
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
when: item.family is defined and images[item.family] is defined
|
when: item.family is defined and images[item.family] is defined
|
||||||
retries: 10
|
retries: 10
|
||||||
delay: 5
|
delay: 5
|
||||||
until: container_info.container.State.Running | default(false)
|
until: container_info.stdout == 'true'
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# DIND NODES - Создание контейнеров Docker-in-Docker
|
# POoD NODES - Создание контейнеров Podman-out-of-Podman (сокет Podman)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
- name: DinD nodes setup
|
- name: POoD nodes setup
|
||||||
debug:
|
debug:
|
||||||
msg: |
|
msg: |
|
||||||
================================================================================
|
================================================================================
|
||||||
DIND NODES - Создание контейнеров Docker-in-Docker
|
POoD NODES - Контейнеры с монтированием сокета Podman
|
||||||
================================================================================
|
|
||||||
Count: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
- name: Start DinD nodes (docker:27-dind)
|
|
||||||
community.docker.docker_container:
|
|
||||||
name: "{{ item.name }}"
|
|
||||||
image: "docker:27-dind"
|
|
||||||
networks:
|
|
||||||
- name: "{{ docker_network }}"
|
|
||||||
privileged: true
|
|
||||||
env:
|
|
||||||
DOCKER_TLS_CERTDIR: ""
|
|
||||||
published_ports: "{{ item.publish | default([]) }}"
|
|
||||||
volumes: "{{ (item.volumes | default([])) + [item.name + '-docker:/var/lib/docker'] }}"
|
|
||||||
state: started
|
|
||||||
restart_policy: unless-stopped
|
|
||||||
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}"
|
|
||||||
loop_control: { label: "{{ item.name }}" }
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# DOOD NODES - Создание контейнеров Docker-out-of-Docker
|
|
||||||
# =============================================================================
|
|
||||||
- name: DOoD nodes setup
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
================================================================================
|
|
||||||
DOOD NODES - Создание контейнеров Docker-out-of-Docker
|
|
||||||
================================================================================
|
================================================================================
|
||||||
Count: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
|
Count: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- name: Start DOoD nodes (systemd + docker.sock mount)
|
- name: Start POoD nodes (systemd + podman.sock mount)
|
||||||
community.docker.docker_container:
|
containers.podman.podman_container:
|
||||||
name: "{{ item.name }}"
|
name: "{{ item.name }}"
|
||||||
image: "{{ images[item.family] }}"
|
image: "{{ images[item.family] }}"
|
||||||
networks:
|
network: "{{ podman_network | default(docker_network) }}"
|
||||||
- name: "{{ docker_network }}"
|
|
||||||
privileged: "{{ systemd_defaults.privileged }}"
|
privileged: "{{ systemd_defaults.privileged }}"
|
||||||
command: "{{ systemd_defaults.command }}"
|
command: "{{ systemd_defaults.command }}"
|
||||||
volumes: "{{ (systemd_defaults.volumes | default([])) + ['/var/run/docker.sock:/var/run/docker.sock'] + (item.volumes | default([])) + ['/Users/inecs/PycharmProjects/DevOpsLab/vault:/workspace/vault:ro', '/Users/inecs/PycharmProjects/DevOpsLab/files:/workspace/files:ro', '/Users/inecs/PycharmProjects/DevOpsLab/roles:/workspace/roles:ro'] }}"
|
volume: "{{ (systemd_defaults.volumes | default([])) + ['/run/podman/podman.sock:/run/podman/podman.sock'] + (item.volumes | default([])) + ['/workspace/vault:/workspace/vault:ro', '/workspace/files:/workspace/files:ro', '/workspace/roles:/workspace/roles:ro'] }}"
|
||||||
tmpfs: "{{ systemd_defaults.tmpfs | default([]) }}"
|
tmpfs: "{{ systemd_defaults.tmpfs | default([]) }}"
|
||||||
capabilities: "{{ systemd_defaults.capabilities | default([]) }}"
|
cap_add: "{{ systemd_defaults.capabilities | default([]) }}"
|
||||||
published_ports: "{{ item.publish | default([]) }}"
|
ports: "{{ item.publish | default([]) }}"
|
||||||
env: "{{ item.env | default({}) }}"
|
env: "{{ (item.env | default({})) | combine({'CONTAINER_HOST': 'unix:///run/podman/podman.sock'}) }}"
|
||||||
platform: "{{ item.docker_platform | default(item.platform) | default(omit) }}"
|
|
||||||
state: started
|
state: started
|
||||||
restart_policy: unless-stopped
|
restart_policy: "unless-stopped"
|
||||||
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
|
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
when: item.family is defined and images[item.family] is defined
|
when: item.family is defined and images[item.family] is defined
|
||||||
@@ -264,7 +234,7 @@
|
|||||||
set_fact:
|
set_fact:
|
||||||
inv_content: |
|
inv_content: |
|
||||||
[all:vars]
|
[all:vars]
|
||||||
ansible_connection=community.docker.docker
|
ansible_connection=containers.podman.podman
|
||||||
ansible_remote_tmp=/tmp/.ansible-tmp
|
ansible_remote_tmp=/tmp/.ansible-tmp
|
||||||
|
|
||||||
{% for group, members in (groups_map | dictsort) %}
|
{% for group, members in (groups_map | dictsort) %}
|
||||||
|
|||||||
@@ -56,71 +56,37 @@
|
|||||||
Count: {{ hosts | length }} containers
|
Count: {{ hosts | length }} containers
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- name: Stop and remove containers
|
- name: Stop and remove containers (Podman)
|
||||||
community.docker.docker_container:
|
containers.podman.podman_container:
|
||||||
name: "{{ item.name }}"
|
name: "{{ item.name }}"
|
||||||
state: absent
|
state: absent
|
||||||
force_kill: true
|
force_delete: true
|
||||||
cleanup: true
|
|
||||||
loop: "{{ hosts }}"
|
loop: "{{ hosts }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Force remove any remaining containers
|
- name: Force remove any remaining containers
|
||||||
shell: |
|
shell: |
|
||||||
docker ps -a --filter "name={{ item.name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f
|
podman ps -a --filter "name={{ item.name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r podman rm -f 2>/dev/null || true
|
||||||
loop: "{{ hosts }}"
|
loop: "{{ hosts }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Remove DinD volumes
|
|
||||||
community.docker.docker_volume:
|
|
||||||
name: "{{ item.name }}-docker"
|
|
||||||
state: absent
|
|
||||||
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}"
|
|
||||||
loop_control: { label: "{{ item.name }}" }
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Remove custom volumes
|
|
||||||
community.docker.docker_volume:
|
|
||||||
name: "{{ item.volumes | default([]) | select('match', '^[^:]+$') | list }}"
|
|
||||||
state: absent
|
|
||||||
loop: "{{ hosts }}"
|
|
||||||
loop_control: { label: "{{ item.name }}" }
|
|
||||||
ignore_errors: true
|
|
||||||
when: item.volumes is defined
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# ОЧИСТКА СЕТИ - Удаление Docker сети
|
|
||||||
# =============================================================================
|
|
||||||
- name: Network cleanup
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
================================================================================
|
|
||||||
ОЧИСТКА СЕТИ - Удаление Docker сети
|
|
||||||
================================================================================
|
|
||||||
Network: {{ docker_network }}
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
- name: Remove network
|
- name: Remove network
|
||||||
community.docker.docker_network:
|
containers.podman.podman_network:
|
||||||
name: "{{ docker_network }}"
|
name: "{{ podman_network | default(docker_network) }}"
|
||||||
state: absent
|
state: absent
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Force cleanup all project containers
|
- name: Force cleanup all project containers
|
||||||
shell: |
|
shell: |
|
||||||
# Удаляем все контейнеры из загруженного пресета
|
|
||||||
{% for host in hosts %}
|
{% for host in hosts %}
|
||||||
docker ps -a --filter "name={{ host.name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true
|
podman ps -a --filter "name={{ host.name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r podman rm -f 2>/dev/null || true
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
# Удаляем все контейнеры с образами ansible-lab
|
podman ps -a --filter "ancestor=inecs/ansible-lab" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r podman rm -f 2>/dev/null || true
|
||||||
docker ps -a --filter "ancestor=inecs/ansible-lab" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true
|
podman ps -a --filter "network=labnet" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r podman rm -f 2>/dev/null || true
|
||||||
# Удаляем все контейнеры с сетью labnet
|
|
||||||
docker ps -a --filter "network=labnet" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true
|
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
vars:
|
vars:
|
||||||
# Используем переменную hosts из загруженного пресета
|
|
||||||
hosts: "{{ hosts }}"
|
hosts: "{{ hosts }}"
|
||||||
|
|
||||||
- name: Display cleanup summary
|
- name: Display cleanup summary
|
||||||
@@ -131,8 +97,8 @@
|
|||||||
================================================================================
|
================================================================================
|
||||||
Containers: {{ hosts | length }}
|
Containers: {{ hosts | length }}
|
||||||
Volumes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
|
Volumes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
|
||||||
Network: {{ docker_network }}
|
Network: {{ podman_network | default(docker_network) }}
|
||||||
Clusters: {{ kind_clusters | default([]) | length }}
|
Clusters: {{ minikube_profiles | default([]) | length }}
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- name: Display filtered hosts
|
- name: Display filtered hosts
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
# Автор: Сергей Антропов
|
# Автор: Сергей Антропов
|
||||||
# Сайт: https://devops.org.ru
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
# Используем delegated: создание/удаление контейнеров в create.yml и destroy.yml через Podman
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
driver:
|
driver:
|
||||||
name: docker
|
name: delegated
|
||||||
|
|
||||||
platforms:
|
platforms:
|
||||||
# Платформы будут созданы динамически через preset файлы
|
# Платформы будут созданы динамически через preset файлы
|
||||||
|
|||||||
@@ -46,10 +46,8 @@
|
|||||||
Count: {{ hosts | selectattr('type','undefined') | list | length }}
|
Count: {{ hosts | selectattr('type','undefined') | list | length }}
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
- name: Check systemd nodes status
|
- name: Check systemd nodes status (Podman exec)
|
||||||
community.docker.docker_container_exec:
|
command: "podman exec {{ item.name }} systemctl is-system-running"
|
||||||
container: "{{ item.name }}"
|
|
||||||
command: systemctl is-system-running
|
|
||||||
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
loop: "{{ hosts | selectattr('type','undefined') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
register: systemd_status
|
register: systemd_status
|
||||||
@@ -61,43 +59,23 @@
|
|||||||
loop: "{{ systemd_status.results | default([]) }}"
|
loop: "{{ systemd_status.results | default([]) }}"
|
||||||
when: systemd_status is defined
|
when: systemd_status is defined
|
||||||
|
|
||||||
# Проверка DinD узлов
|
# Проверка POoD узлов (Podman-out-of-Podman)
|
||||||
- name: Check DinD nodes docker daemon
|
- name: Check POoD nodes podman access
|
||||||
community.docker.docker_container_exec:
|
command: "podman exec {{ item.name }} podman ps --format '{{'{{' }}.Names{{ '}}' }}'"
|
||||||
container: "{{ item.name }}"
|
|
||||||
command: docker version --format '{{.Server.Version}}'
|
|
||||||
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}"
|
|
||||||
loop_control: { label: "{{ item.name }}" }
|
|
||||||
register: dind_status
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Display DinD nodes status
|
|
||||||
debug:
|
|
||||||
msg: "DinD node {{ item.0.name }}: Docker {{ item.1.stdout | default('not running') }}"
|
|
||||||
loop: "{{ dind_status.results | default([]) }}"
|
|
||||||
when: dind_status is defined
|
|
||||||
|
|
||||||
# Проверка DOoD узлов
|
|
||||||
- name: Check DOoD nodes docker access
|
|
||||||
community.docker.docker_container_exec:
|
|
||||||
container: "{{ item.name }}"
|
|
||||||
command: docker ps --format '{{.Names}}'
|
|
||||||
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
|
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
register: dood_status
|
register: dood_status
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
- name: Display DOoD nodes status
|
- name: Display POoD nodes status
|
||||||
debug:
|
debug:
|
||||||
msg: "DOoD node {{ item.0.name }}: Can access {{ item.1.stdout_lines | length | default(0) }} containers"
|
msg: "POoD node {{ item.0.name }}: Can access {{ item.1.stdout_lines | length | default(0) }} containers"
|
||||||
loop: "{{ dood_status.results | default([]) }}"
|
loop: "{{ dood_status.results | default([]) }}"
|
||||||
when: dood_status is defined
|
when: dood_status is defined
|
||||||
|
|
||||||
# Проверка сетевого подключения
|
# Проверка сетевого подключения
|
||||||
- name: Test network connectivity between nodes
|
- name: Test network connectivity between nodes
|
||||||
community.docker.docker_container_exec:
|
command: "podman exec {{ item.0.name }} ping -c 1 {{ item.1.name }}"
|
||||||
container: "{{ item.0.name }}"
|
|
||||||
command: ping -c 1 {{ item.1.name }}
|
|
||||||
loop: "{{ hosts | subelements(hosts, 'name') }}"
|
loop: "{{ hosts | subelements(hosts, 'name') }}"
|
||||||
loop_control: { label: "{{ item.0.name }} -> {{ item.1.name }}" }
|
loop_control: { label: "{{ item.0.name }} -> {{ item.1.name }}" }
|
||||||
when: item.0.name != item.1.name
|
when: item.0.name != item.1.name
|
||||||
@@ -112,9 +90,7 @@
|
|||||||
|
|
||||||
# Проверка портов
|
# Проверка портов
|
||||||
- name: Check published ports
|
- name: Check published ports
|
||||||
community.docker.docker_container_exec:
|
command: "podman exec {{ item.name }} netstat -tlnp 2>/dev/null || podman exec {{ item.name }} ss -tlnp"
|
||||||
container: "{{ item.name }}"
|
|
||||||
command: netstat -tlnp
|
|
||||||
loop: "{{ hosts | selectattr('publish','defined') | list }}"
|
loop: "{{ hosts | selectattr('publish','defined') | list }}"
|
||||||
loop_control: { label: "{{ item.name }}" }
|
loop_control: { label: "{{ item.name }}" }
|
||||||
register: port_status
|
register: port_status
|
||||||
@@ -139,11 +115,10 @@
|
|||||||
- name: Display verification summary
|
- name: Display verification summary
|
||||||
debug:
|
debug:
|
||||||
msg: |
|
msg: |
|
||||||
✅ Verification Summary:
|
✅ Verification Summary (Podman):
|
||||||
- Total hosts: {{ hosts | length }}
|
- Total hosts: {{ hosts | length }}
|
||||||
- Systemd nodes: {{ hosts | selectattr('type','undefined') | list | length }}
|
- Systemd nodes: {{ hosts | selectattr('type','undefined') | list | length }}
|
||||||
- DinD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
|
- POoD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
|
||||||
- DOoD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
|
|
||||||
- Groups: {{ groups_map.keys() | list | join(', ') }}
|
- Groups: {{ groups_map.keys() | list | join(', ') }}
|
||||||
- Network: {{ docker_network }}
|
- Network: {{ docker_network }}
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ systemd_defaults:
|
|||||||
tmpfs: ["/run", "/run/lock"]
|
tmpfs: ["/run", "/run/lock"]
|
||||||
capabilities: ["SYS_ADMIN"]
|
capabilities: ["SYS_ADMIN"]
|
||||||
|
|
||||||
# Минимальный Kind кластер без аддонов
|
# Minikube (драйвер podman) — минимальный кластер без аддонов
|
||||||
kind_clusters:
|
minikube_profile: minikube
|
||||||
- name: minimal
|
minikube_cpus: "2"
|
||||||
workers: 0 # Только control-plane
|
minikube_memory: "4096"
|
||||||
api_port: 6443
|
minikube_addons: []
|
||||||
|
|
||||||
hosts: []
|
hosts: []
|
||||||
|
|||||||
74
scripts/create_minikube_cluster.py
Normal file
74
scripts/create_minikube_cluster.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Скрипт для создания Minikube кластера с драйвером Podman.
|
||||||
|
Автор: Сергей Антропов
|
||||||
|
Сайт: https://devops.org.ru
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(cmd, check=True):
|
||||||
|
"""Выполнить команду на хосте."""
|
||||||
|
print(f"[run] {cmd}")
|
||||||
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
print(f"[error] {result.stderr}")
|
||||||
|
sys.exit(1)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: create_minikube_cluster.py <preset_file>")
|
||||||
|
print(" Создаёт Minikube кластер с драйвером podman и опционально включает аддоны из пресета.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
preset_file = sys.argv[1]
|
||||||
|
print(f"📋 Читаю пресет: {preset_file}")
|
||||||
|
|
||||||
|
with open(preset_file, "r", encoding="utf-8") as f:
|
||||||
|
preset = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
profile = preset.get("minikube_profile", "minikube")
|
||||||
|
addons = preset.get("minikube_addons", [])
|
||||||
|
cpus = preset.get("minikube_cpus", "2")
|
||||||
|
memory = preset.get("minikube_memory", "4096")
|
||||||
|
|
||||||
|
print(f"\n☸️ Создание Minikube кластера (драйвер: podman)")
|
||||||
|
print(f" Профиль: {profile}")
|
||||||
|
print(f" CPU: {cpus}, Memory: {memory}")
|
||||||
|
|
||||||
|
# Проверяем, запущен ли уже кластер
|
||||||
|
result = subprocess.run(
|
||||||
|
f"minikube profile list 2>/dev/null | grep -E '^{profile}' | grep 'Running'",
|
||||||
|
shell=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
|
print(f"⚠️ Кластер с профилем '{profile}' уже запущен.")
|
||||||
|
print(" Для пересоздания выполните: minikube delete -p " + profile)
|
||||||
|
else:
|
||||||
|
run_cmd(
|
||||||
|
f"minikube start --driver=podman --profile={profile} --cpus={cpus} --memory={memory}"
|
||||||
|
)
|
||||||
|
print(f"✅ Minikube кластер '{profile}' создан и запущен.")
|
||||||
|
|
||||||
|
# Включаем аддоны из пресета
|
||||||
|
if addons:
|
||||||
|
print(f"\n📦 Включение аддонов: {', '.join(addons)}")
|
||||||
|
for addon in addons:
|
||||||
|
run_cmd(f"minikube addons enable {addon} -p {profile}", check=False)
|
||||||
|
|
||||||
|
print("\n🎉 Готово. Использование:")
|
||||||
|
print(f" kubectl config use-context {profile}")
|
||||||
|
print(" minikube kubectl -- get nodes")
|
||||||
|
print(" minikube dashboard -p " + profile)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -29,11 +29,11 @@ def main():
|
|||||||
host_name = host['name']
|
host_name = host['name']
|
||||||
|
|
||||||
# Проверяем существование контейнера
|
# Проверяем существование контейнера
|
||||||
result = subprocess.run(f"docker ps -a --format '{{{{.Names}}}}' | grep -x {host_name}",
|
result = subprocess.run(f"podman ps -a --format '{{{{.Names}}}}' | grep -x {host_name}",
|
||||||
shell=True, capture_output=True, text=True)
|
shell=True, capture_output=True, text=True)
|
||||||
if result.stdout.strip():
|
if result.stdout.strip():
|
||||||
print(f"🗑️ Удаление контейнера: {host_name}")
|
print(f"🗑️ Удаление контейнера: {host_name}")
|
||||||
subprocess.run(f"docker rm -f {host_name}", shell=True, capture_output=True, text=True)
|
subprocess.run(f"podman rm -f {host_name}", shell=True, capture_output=True, text=True)
|
||||||
print(f"✅ Контейнер '{host_name}' удален")
|
print(f"✅ Контейнер '{host_name}' удален")
|
||||||
else:
|
else:
|
||||||
print(f"⚠️ Контейнер '{host_name}' не найден")
|
print(f"⚠️ Контейнер '{host_name}' не найден")
|
||||||
|
|||||||
@@ -9,23 +9,18 @@ import subprocess
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
def get_cluster_name():
|
def get_cluster_name():
|
||||||
"""Получает имя кластера"""
|
"""Получает имя текущего контекста (Minikube)."""
|
||||||
result = subprocess.run("docker exec k8s-controller kind get clusters | head -1", shell=True, capture_output=True, text=True)
|
result = subprocess.run(
|
||||||
|
"kubectl config current-context 2>/dev/null", shell=True, capture_output=True, text=True
|
||||||
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def run_kubectl_cmd(cmd):
|
def run_kubectl_cmd(cmd):
|
||||||
"""Выполняет команду kubectl внутри контейнера k8s-controller"""
|
"""Выполняет команду kubectl на хосте (контекст Minikube)."""
|
||||||
cluster_name = get_cluster_name()
|
full_cmd = f"kubectl {cmd}"
|
||||||
if cluster_name:
|
|
||||||
# Используем прямой адрес control-plane
|
|
||||||
server = f"https://{cluster_name}-control-plane:6443"
|
|
||||||
cmd_with_server = f"--server={server} --insecure-skip-tls-verify {cmd}"
|
|
||||||
else:
|
|
||||||
cmd_with_server = cmd
|
|
||||||
|
|
||||||
full_cmd = f"docker exec k8s-controller kubectl {cmd_with_server}"
|
|
||||||
result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True)
|
result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True)
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import signal
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
def get_cluster_name():
|
def get_cluster_name():
|
||||||
"""Получаем имя кластера из preset файла"""
|
"""Получаем имя профиля Minikube из preset файла"""
|
||||||
preset_file = "molecule/presets/k8s/kubernetes.yml"
|
preset_file = "molecule/presets/k8s/kubernetes.yml"
|
||||||
with open(preset_file, 'r') as f:
|
if not os.path.exists(preset_file):
|
||||||
preset = yaml.safe_load(f)
|
return "minikube"
|
||||||
return preset['kind_clusters'][0]['name']
|
with open(preset_file, "r", encoding="utf-8") as f:
|
||||||
|
preset = yaml.safe_load(f) or {}
|
||||||
|
return preset.get("minikube_profile", "minikube")
|
||||||
|
|
||||||
def run_cmd(cmd):
|
def run_cmd(cmd):
|
||||||
"""Выполняет команду и возвращает результат"""
|
"""Выполняет команду и возвращает результат"""
|
||||||
@@ -80,38 +82,24 @@ def clear_portforwards():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def create_portforwards():
|
def create_portforwards():
|
||||||
"""Создает port-forward для всех сервисов из preset на локальном компьютере"""
|
"""Создает port-forward для всех сервисов из preset на локальном компьютере (Minikube)."""
|
||||||
# Загружаем preset
|
|
||||||
preset_file = "molecule/presets/k8s/kubernetes.yml"
|
preset_file = "molecule/presets/k8s/kubernetes.yml"
|
||||||
with open(preset_file, 'r') as f:
|
preset = {}
|
||||||
preset = yaml.safe_load(f)
|
if os.path.exists(preset_file):
|
||||||
|
with open(preset_file, "r", encoding="utf-8") as f:
|
||||||
|
preset = yaml.safe_load(f) or {}
|
||||||
|
# Поддержка minikube_profile и addon_ports (на верхнем уровне или в kind_clusters[0])
|
||||||
|
cluster_name = preset.get("minikube_profile", "minikube")
|
||||||
|
addon_ports = preset.get("addon_ports") or (preset.get("kind_clusters") or [{}])[0].get("addon_ports", {})
|
||||||
|
|
||||||
cluster_name = preset['kind_clusters'][0]['name']
|
# Minikube обновляет ~/.kube/config — используем его
|
||||||
addon_ports = preset['kind_clusters'][0].get('addon_ports', {})
|
kubeconfig_file = os.environ.get("KUBECONFIG", os.path.expanduser("~/.kube/config"))
|
||||||
|
if not os.path.exists(kubeconfig_file):
|
||||||
# Получаем kubeconfig из контейнера k8s-controller
|
print(f"❌ Kubeconfig не найден: {kubeconfig_file}. Запустите: make k8s create")
|
||||||
print(f"🔌 Создание port-forward для кластера: {cluster_name}")
|
|
||||||
print("📋 Получение kubeconfig из контейнера k8s-controller...")
|
|
||||||
|
|
||||||
# Копируем kubeconfig из контейнера
|
|
||||||
result = subprocess.run(
|
|
||||||
f"docker exec k8s-controller kind get kubeconfig --name {cluster_name}",
|
|
||||||
shell=True, capture_output=True, text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
|
||||||
print(f"❌ Ошибка получения kubeconfig: {result.stderr}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Сохраняем kubeconfig во временный файл
|
print(f"🔌 Создание port-forward для Minikube (профиль: {cluster_name})")
|
||||||
kubeconfig_file = "/tmp/kubeconfig-lab.yaml"
|
print(f"📋 Kubeconfig: {kubeconfig_file}")
|
||||||
with open(kubeconfig_file, 'w') as f:
|
|
||||||
f.write(result.stdout)
|
|
||||||
|
|
||||||
# Меняем server с 0.0.0.0 на localhost для локального доступа
|
|
||||||
subprocess.run(f"sed -i.bak 's|server: https://0.0.0.0:6443|server: https://localhost:6443|g' {kubeconfig_file}", shell=True)
|
|
||||||
|
|
||||||
print("✅ Kubeconfig подготовлен")
|
|
||||||
|
|
||||||
# Ingress HTTP (80)
|
# Ingress HTTP (80)
|
||||||
if addon_ports.get('ingress_http'):
|
if addon_ports.get('ingress_http'):
|
||||||
|
|||||||
Reference in New Issue
Block a user