podman: переход на Podman, Minikube, локальные образы и док для новичков

- Molecule: драйвер delegated, коллекция containers.podman, create/destroy/verify на Podman
- Makefile: все вызовы docker заменены на podman, сокет /run/podman/podman.sock
- Сборка образов: podman build (без buildx), buildall/buildall-image — только локально без push
- Ansible-controller: Podman в образе, docker-compose на podman compose, сокет Podman
- K8s: Kind заменён на Minikube (драйвер podman), скрипты и Makefile обновлены
- Пресеты: проверка локальных образов, без podman pull (registry запрещён)
- Документация: docs/podman.md, docs/quickstart-for-dummies.md (роли, плейбук, линт, тесты, пресеты, инвентори)
- README: ссылка на quickstart-for-dummies

Made-with: Cursor
This commit is contained in:
Sergey Antropoff
2026-03-11 19:59:47 +03:00
parent 23e1a6037b
commit 05881e8d74
16 changed files with 859 additions and 790 deletions

805
Makefile

File diff suppressed because it is too large Load Diff

View File

@@ -634,6 +634,7 @@ make custom-images # справка по собственным
### Основная документация
- **[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/creating-roles.md](docs/creating-roles.md)** - Создание ролей
- **[docs/devops-role.md](docs/devops-role.md)** - Универсальная роль devops для настройки пользователей и SSH

View File

@@ -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 \
&& chmod +x /usr/local/bin/yq
# Устанавливаем Docker CLI
RUN apt-get install -y docker.io docker-compose
# Устанавливаем Podman (вместо Docker). Для Ubuntu 22.04 — из universe
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
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
RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Устанавливаем Kind
RUN curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64 \
&& chmod +x ./kind \
&& mv ./kind /usr/local/bin/
# Устанавливаем Minikube (вместо Kind, для использования с драйвером podman)
RUN curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 \
&& chmod +x minikube-linux-amd64 \
&& mv minikube-linux-amd64 /usr/local/bin/minikube
## Устанавливаем Istio CLI
#RUN curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.22.1 sh - \
@@ -79,18 +83,17 @@ RUN chown -R ansible:ansible /ansible
# Переключаемся на пользователя ansible
USER ansible
# Устанавливаем дополнительные роли
RUN ansible-galaxy install geerlingguy.docker \
&& ansible-galaxy install geerlingguy.kubernetes
# Устанавливаем дополнительные роли (коллекция containers.podman в requirements.yml)
RUN ansible-galaxy install geerlingguy.kubernetes
# Устанавливаем molecule как root
RUN pip3 install ansible ansible-core ansible-lint molecule molecule-docker passlib
# Устанавливаем molecule как root (delegated driver для Podman)
RUN pip3 install ansible ansible-core ansible-lint molecule passlib
# Проверяем, что molecule установлен
RUN which molecule || echo "molecule not found"
# Настройки для работы с Docker
ENV DOCKER_HOST=unix:///var/run/docker.sock
# Настройки для работы с Podman (сокет монтируется с хоста)
ENV CONTAINER_HOST=unix:///run/podman/podman.sock
ENV ANSIBLE_FORCE_COLOR=1
ENV ANSIBLE_STDOUT_CALLBACK=yaml
ENV ANSIBLE_CALLBACKS_ENABLED=profile_tasks

View File

@@ -1,3 +1,6 @@
# Ansible Controller для Podman (сокет Podman)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
version: "3.9"
services:
@@ -7,10 +10,10 @@ services:
privileged: true
command: sleep infinity
environment:
DOCKER_HOST: unix:///var/run/docker.sock
CONTAINER_HOST: unix:///run/podman/podman.sock
ANSIBLE_VAULT_PASSWORD_FILE: /ansible/vault/.vault
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /run/podman/podman.sock:/run/podman/podman.sock
- .:/ansible
working_dir: /ansible
networks:

View File

@@ -1,8 +1,10 @@
---
# Ansible Collections for Molecule Universal
# Ansible Collections for Molecule Universal (Podman)
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
collections:
- name: community.docker
version: ">=3.0.0"
- name: containers.podman
version: ">=1.10.0"
- name: community.general
version: ">=7.0.0"
- name: ansible.posix

86
docs/podman.md Normal file
View 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ах.

View 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, debian9debian12, centos7centos9, 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 &lt;роль&gt;** для проверки.
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 при необходимости.

View File

@@ -10,8 +10,9 @@
# Проверяем сначала в папке 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' }}"
# Fallback значения если preset файл не найден
# Fallback значения если preset файл не найден (Podman использует ту же сеть)
docker_network: labnet
podman_network: "{{ docker_network }}"
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
images:
alt9: "inecs/ansible-lab:alt9-latest"
@@ -89,20 +90,20 @@
hosts: "{{ filtered_hosts | default(hosts) }}"
# =============================================================================
# СЕТЕВОЕ ПОДКЛЮЧЕНИЕ
# СЕТЕВОЕ ПОДКЛЮЧЕНИЕ (Podman)
# =============================================================================
- name: Network setup
debug:
msg: |
================================================================================
НАСТРОЙКА СЕТИ
НАСТРОЙКА СЕТИ (Podman)
================================================================================
Network: {{ docker_network }}
Network: {{ podman_network | default(docker_network) }}
================================================================================
- name: Ensure network exists
community.docker.docker_network:
name: "{{ docker_network }}"
containers.podman.podman_network:
name: "{{ podman_network | default(docker_network) }}"
state: present
# =============================================================================
@@ -117,39 +118,39 @@
Count: {{ hosts | selectattr('type','undefined') | list | length }}
================================================================================
- name: Pull systemd images with correct platform
command: "docker pull --platform {{ ansible_architecture }} {{ images[item.family] }}"
# Только локальные образы (registry запрещён). Сборка: make buildall
- name: Проверка наличия локальных образов (Podman)
command: "podman image exists {{ images[item.family] }}"
loop: "{{ hosts | selectattr('type','undefined') | list }}"
loop_control: { label: "{{ item.name }}" }
when: item.family is defined and images[item.family] is defined
register: pull_result
ignore_errors: yes
register: image_check
failed_when: false
changed_when: false
- name: Display pull results
debug:
msg: "Pulled {{ item.item.name }}: {{ 'OK' if (item.rc is defined and item.rc == 0) else 'SKIPPED (not available for this platform)' }}"
loop: "{{ pull_result.results | default([]) }}"
loop_control:
label: "{{ item.item.name }}"
- name: Остановка при отсутствии локальных образов
fail:
msg: |
Локальные образы не найдены (доступ к registry запрещён).
Выполните: make buildall
Отсутствуют образы: {{ 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
community.docker.docker_container:
containers.podman.podman_container:
name: "{{ item.name }}"
image: "{{ images[item.family] }}"
networks:
- name: "{{ docker_network }}"
network: "{{ podman_network | default(docker_network) }}"
privileged: "{{ systemd_defaults.privileged }}"
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([]) }}"
capabilities: "{{ systemd_defaults.capabilities | default([]) }}"
published_ports: "{{ item.publish | default([]) }}"
cap_add: "{{ systemd_defaults.capabilities | default([]) }}"
ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
# Специальные настройки для Astra Linux и RedOS
security_opts: "{{ ['seccomp=unconfined', 'apparmor=unconfined'] if item.family in ['astra', 'redos'] else [] }}"
platform: "{{ item.docker_platform | default(item.platform) | default(omit) }}"
security_opt: "{{ ['seccomp=unconfined', 'apparmor=unconfined'] if item.family in ['astra', 'redos'] else [] }}"
state: started
restart_policy: unless-stopped
restart_policy: "unless-stopped"
loop: "{{ hosts | selectattr('type','undefined') | list }}"
loop_control: { label: "{{ item.name }}" }
when: item.family is defined and images[item.family] is defined
@@ -160,75 +161,44 @@
seconds: 10
when: hosts | length > 0
# Проверка готовности контейнеров
# Проверка готовности контейнеров (Podman)
- name: Wait for containers to be running
community.docker.docker_container_info:
name: "{{ item.name }}"
command: "podman inspect --format '{{ '{{' }}.State.Running{{ '}}' }}' {{ item.name }}"
register: container_info
loop: "{{ hosts | selectattr('type','undefined') | list }}"
loop_control: { label: "{{ item.name }}" }
when: item.family is defined and images[item.family] is defined
retries: 10
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:
msg: |
================================================================================
DIND NODES - Создание контейнеров Docker-in-Docker
================================================================================
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
POoD NODES - Контейнеры с монтированием сокета Podman
================================================================================
Count: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
================================================================================
- name: Start DOoD nodes (systemd + docker.sock mount)
community.docker.docker_container:
- name: Start POoD nodes (systemd + podman.sock mount)
containers.podman.podman_container:
name: "{{ item.name }}"
image: "{{ images[item.family] }}"
networks:
- name: "{{ docker_network }}"
network: "{{ podman_network | default(docker_network) }}"
privileged: "{{ systemd_defaults.privileged }}"
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([]) }}"
capabilities: "{{ systemd_defaults.capabilities | default([]) }}"
published_ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
platform: "{{ item.docker_platform | default(item.platform) | default(omit) }}"
cap_add: "{{ systemd_defaults.capabilities | default([]) }}"
ports: "{{ item.publish | default([]) }}"
env: "{{ (item.env | default({})) | combine({'CONTAINER_HOST': 'unix:///run/podman/podman.sock'}) }}"
state: started
restart_policy: unless-stopped
restart_policy: "unless-stopped"
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
loop_control: { label: "{{ item.name }}" }
when: item.family is defined and images[item.family] is defined
@@ -264,7 +234,7 @@
set_fact:
inv_content: |
[all:vars]
ansible_connection=community.docker.docker
ansible_connection=containers.podman.podman
ansible_remote_tmp=/tmp/.ansible-tmp
{% for group, members in (groups_map | dictsort) %}

View File

@@ -56,71 +56,37 @@
Count: {{ hosts | length }} containers
================================================================================
- name: Stop and remove containers
community.docker.docker_container:
- name: Stop and remove containers (Podman)
containers.podman.podman_container:
name: "{{ item.name }}"
state: absent
force_kill: true
cleanup: true
force_delete: true
loop: "{{ hosts }}"
loop_control: { label: "{{ item.name }}" }
ignore_errors: true
- name: Force remove any remaining containers
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_control: { label: "{{ item.name }}" }
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
community.docker.docker_network:
name: "{{ docker_network }}"
containers.podman.podman_network:
name: "{{ podman_network | default(docker_network) }}"
state: absent
ignore_errors: true
- name: Force cleanup all project containers
shell: |
# Удаляем все контейнеры из загруженного пресета
{% 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 %}
# Удаляем все контейнеры с образами ansible-lab
docker ps -a --filter "ancestor=inecs/ansible-lab" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true
# Удаляем все контейнеры с сетью labnet
docker ps -a --filter "network=labnet" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true
podman ps -a --filter "ancestor=inecs/ansible-lab" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r podman rm -f 2>/dev/null || true
podman ps -a --filter "network=labnet" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r podman rm -f 2>/dev/null || true
ignore_errors: true
vars:
# Используем переменную hosts из загруженного пресета
hosts: "{{ hosts }}"
- name: Display cleanup summary
@@ -131,8 +97,8 @@
================================================================================
Containers: {{ hosts | length }}
Volumes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
Network: {{ docker_network }}
Clusters: {{ kind_clusters | default([]) | length }}
Network: {{ podman_network | default(docker_network) }}
Clusters: {{ minikube_profiles | default([]) | length }}
================================================================================
- name: Display filtered hosts

View File

@@ -3,8 +3,11 @@
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Используем delegated: создание/удаление контейнеров в create.yml и destroy.yml через Podman
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
driver:
name: docker
name: delegated
platforms:
# Платформы будут созданы динамически через preset файлы

View File

@@ -46,10 +46,8 @@
Count: {{ hosts | selectattr('type','undefined') | list | length }}
================================================================================
- name: Check systemd nodes status
community.docker.docker_container_exec:
container: "{{ item.name }}"
command: systemctl is-system-running
- name: Check systemd nodes status (Podman exec)
command: "podman exec {{ item.name }} systemctl is-system-running"
loop: "{{ hosts | selectattr('type','undefined') | list }}"
loop_control: { label: "{{ item.name }}" }
register: systemd_status
@@ -61,43 +59,23 @@
loop: "{{ systemd_status.results | default([]) }}"
when: systemd_status is defined
# Проверка DinD узлов
- name: Check DinD nodes docker daemon
community.docker.docker_container_exec:
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}}'
# Проверка POoD узлов (Podman-out-of-Podman)
- name: Check POoD nodes podman access
command: "podman exec {{ item.name }} podman ps --format '{{'{{' }}.Names{{ '}}' }}'"
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
loop_control: { label: "{{ item.name }}" }
register: dood_status
ignore_errors: true
- name: Display DOoD nodes status
- name: Display POoD nodes status
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([]) }}"
when: dood_status is defined
# Проверка сетевого подключения
- name: Test network connectivity between nodes
community.docker.docker_container_exec:
container: "{{ item.0.name }}"
command: ping -c 1 {{ item.1.name }}
command: "podman exec {{ item.0.name }} ping -c 1 {{ item.1.name }}"
loop: "{{ hosts | subelements(hosts, 'name') }}"
loop_control: { label: "{{ item.0.name }} -> {{ item.1.name }}" }
when: item.0.name != item.1.name
@@ -112,9 +90,7 @@
# Проверка портов
- name: Check published ports
community.docker.docker_container_exec:
container: "{{ item.name }}"
command: netstat -tlnp
command: "podman exec {{ item.name }} netstat -tlnp 2>/dev/null || podman exec {{ item.name }} ss -tlnp"
loop: "{{ hosts | selectattr('publish','defined') | list }}"
loop_control: { label: "{{ item.name }}" }
register: port_status
@@ -139,11 +115,10 @@
- name: Display verification summary
debug:
msg: |
✅ Verification Summary:
✅ Verification Summary (Podman):
- Total hosts: {{ hosts | length }}
- Systemd nodes: {{ hosts | selectattr('type','undefined') | list | length }}
- DinD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
- DOoD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
- POoD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
- Groups: {{ groups_map.keys() | list | join(', ') }}
- Network: {{ docker_network }}

View File

@@ -33,10 +33,10 @@ systemd_defaults:
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
# Минимальный Kind кластер без аддонов
kind_clusters:
- name: minimal
workers: 0 # Только control-plane
api_port: 6443
# Minikube (драйвер podman) — минимальный кластер без аддонов
minikube_profile: minikube
minikube_cpus: "2"
minikube_memory: "4096"
minikube_addons: []
hosts: []

View 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()

View File

@@ -29,11 +29,11 @@ def main():
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)
if result.stdout.strip():
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}' удален")
else:
print(f"⚠️ Контейнер '{host_name}' не найден")

View File

@@ -9,23 +9,18 @@ import subprocess
import json
def get_cluster_name():
"""Получает имя кластера"""
result = subprocess.run("docker exec k8s-controller kind get clusters | head -1", shell=True, capture_output=True, text=True)
"""Получает имя текущего контекста (Minikube)."""
result = subprocess.run(
"kubectl config current-context 2>/dev/null", shell=True, capture_output=True, text=True
)
if result.returncode == 0:
return result.stdout.strip()
return None
def run_kubectl_cmd(cmd):
"""Выполняет команду kubectl внутри контейнера k8s-controller"""
cluster_name = get_cluster_name()
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}"
"""Выполняет команду kubectl на хосте (контекст Minikube)."""
full_cmd = f"kubectl {cmd}"
result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True)
return result.stdout

View File

@@ -12,11 +12,13 @@ import signal
import time
def get_cluster_name():
"""Получаем имя кластера из preset файла"""
"""Получаем имя профиля Minikube из preset файла"""
preset_file = "molecule/presets/k8s/kubernetes.yml"
with open(preset_file, 'r') as f:
preset = yaml.safe_load(f)
return preset['kind_clusters'][0]['name']
if not os.path.exists(preset_file):
return "minikube"
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):
"""Выполняет команду и возвращает результат"""
@@ -80,38 +82,24 @@ def clear_portforwards():
pass
def create_portforwards():
"""Создает port-forward для всех сервисов из preset на локальном компьютере"""
# Загружаем preset
"""Создает port-forward для всех сервисов из preset на локальном компьютере (Minikube)."""
preset_file = "molecule/presets/k8s/kubernetes.yml"
with open(preset_file, 'r') as f:
preset = yaml.safe_load(f)
preset = {}
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']
addon_ports = preset['kind_clusters'][0].get('addon_ports', {})
# Получаем kubeconfig из контейнера k8s-controller
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}")
# Minikube обновляет ~/.kube/config — используем его
kubeconfig_file = os.environ.get("KUBECONFIG", os.path.expanduser("~/.kube/config"))
if not os.path.exists(kubeconfig_file):
print(f"❌ Kubeconfig не найден: {kubeconfig_file}. Запустите: make k8s create")
return
# Сохраняем kubeconfig во временный файл
kubeconfig_file = "/tmp/kubeconfig-lab.yaml"
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 подготовлен")
print(f"🔌 Создание port-forward для Minikube (профиль: {cluster_name})")
print(f"📋 Kubeconfig: {kubeconfig_file}")
# Ingress HTTP (80)
if addon_ports.get('ingress_http'):