feat: Добавлена система пресетов для Molecule

- Создана система пресетов для быстрого переключения между конфигурациями
- Добавлены пресеты: minimal, standard, docker, cluster
- Обновлена структура проекта с папками cicd/, vault/, scripts/
- Упрощена система vault с функциональными секретами
- Добавлены скрипты для работы с пресетами
- Обновлен Makefile с командами для пресетов
- Удалены старые файлы и структуры

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
2025-10-22 20:31:23 +03:00
parent deebf78047
commit 0b981ca61e
53 changed files with 1377 additions and 728 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,10 @@
# ---> Ansible # ---> Ansible
*.retry *.retry
# ---> Vault (секретные файлы)
vault/.vault
vault/secrets/*.yml
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@@ -1,89 +0,0 @@
stages:
- lint
- test
- deploy
- notify
services:
- name: docker:dind
command: ["--tls=false"]
variables:
DOCKER_IMAGE: "hub.cism-ms.ru/ansible/ansible:latest"
DOCKER_TLS_CERTDIR: ""
ANSIBLE_FORCE_COLOR: "true"
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login hub.cism-ms.ru -u "$CI_REGISTRY_USER" --password-stdin
- docker pull $DOCKER_IMAGE
- echo "Fixing directory permissions..."
- chmod o-w $CI_PROJECT_DIR
lint:
stage: lint
script:
- echo "Начинаем стейдж Lint"
- echo "Распаковываем секреты..."
- ansible-vault decrypt vars/secrets.yml --vault-password-file ./vault-password.txt
- echo "Запускаем ansible-lint..."
- ansible-lint roles/*
- echo "Упаковываем секреты..."
- ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file ./vault-password.txt
allow_failure: false
rules:
- if: $CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "master"
test:
stage: test
script:
- echo "Распаковываем секреты..."
- ansible-vault decrypt --vault-password-file ./vault-password.txt vars/secrets.yml
- echo "Запускаем тесты через Молекулу..."
- molecule test --parallel --destroy=always
- echo "Упаковываем секреты..."
- ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file ./vault-password.txt
allow_failure: false
rules:
- if: $CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "master"
deploy:
stage: deploy
script:
- echo "Настраиваем SSH-ключ для доступа к серверам..."
# Создаем директорию .ssh и настраиваем права доступа
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# Записываем SSH-ключ в файл ~/.ssh/id_rsa
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
# Запускаем основной пайплайн
- echo "Распаковываем секреты..."
- ansible-vault decrypt --vault-password-file ./vault-password.txt vars/secrets.yml
- echo "Все ок. Деплоим в прод..."
- ansible-playbook roles/deploy.yaml
- echo "Упаковываем секреты..."
- ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file ./vault-password.txt
# Удаляем ключ
- rm -rf ~/.ssh
rules:
- if: $CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "master"
when: manual
notify:
stage: notify
script:
- |
if [ "$CI_JOB_STATUS" == "success" ]; then
MESSAGE="✅ Пайплайн успешно завершен!%0AПроект: $CI_PROJECT_NAME%0AВетка: $CI_COMMIT_REF_NAME%0AСтатус: $CI_JOB_STATUS"
else
MESSAGE="❌ Пайплайн завершен с ошибкой!%0AПроект: $CI_PROJECT_NAME%0AВетка: $CI_COMMIT_REF_NAME%0AСтатус: $CI_JOB_STATUS"
fi
# curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
# -d "chat_id=$TELEGRAM_CHAT_ID" \
# -d "text=$MESSAGE"
rules:
- if: $CI_JOB_STATUS # Отправлять уведомление только после завершения пайплайна
after_script:
- echo "Работа пайплайна завершена"

View File

@@ -1,40 +0,0 @@
# Используем готовый образ с Ansible
FROM geerlingguy/docker-ubuntu2204-ansible:latest
# Добавляем метаданные
LABEL maintainer="Сергей Антропов <sergey@antropoff.ru>"
LABEL description="Этот Dockerfile создан для внедрения подхода IaC в Ansible."
LABEL version="0.1"
LABEL contact.website="https://devops.org.ru"
# Устанавливаем переменные окружения
ENV PYTHONUNBUFFERED=1
ENV EDITOR=nano
# Устанавливаем дополнительные зависимости Python для Molecule
RUN pip install --upgrade pip && \
pip install \
molecule \
molecule-docker \
ansible-lint \
yamllint \
docker \
&& rm -rf /root/.cache/pip
# Создаем рабочую директорию
WORKDIR /ansible
# Копируем файлы проекта
COPY . /ansible/
# Устанавливаем права на выполнение (если папка scripts существует)
RUN if [ -d /ansible/scripts ]; then chmod +x /ansible/scripts/*.sh; fi
# Устанавливаем пользователя
USER root
# Открываем порт для SSH
EXPOSE 22
# Команда по умолчанию
CMD ["/bin/bash"]

View File

@@ -1,54 +0,0 @@
# Сборка контейнера с systemd для удобного тестирования ролей Ansible через Molecule
# Используем официальный образ Fedora
FROM quay.io/fedora/python-312
USER root
# Обновляем пакеты и устанавливаем systemd и необходимые пакеты
RUN dnf update -y && \
dnf install -y --nodocs --setopt=install_weak_deps=False \
systemd rsync \
git \
openssh \
gcc \
libffi-devel \
openssl-devel \
make \
sudo \
openssh-clients \
less \
ca-certificates \
curl \
gnupg2 \
nano \
sshpass \
redhat-lsb-core \
&& dnf clean all && \
rm -rf /var/cache/dnf /tmp/* /var/tmp/*
# Устанавливаем docker-compose
RUN curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && \
chmod +x /usr/local/bin/docker-compose
# Устанавливаем Python пакеты для Ansible
RUN pip install --upgrade pip && \
pip install \
ansible \
ansible-vault \
molecule \
molecule-docker \
ansible-lint \
yamllint \
docker \
&& rm -rf /root/.cache/pip
# Настраиваем окружение для systemd
ENV container=docker
STOPSIGNAL SIGRTMIN+3
# Создаем необходимые директории для systemd
VOLUME [ "/sys/fs/cgroup" ]
# Запускаем systemd при старте контейнера
CMD ["/sbin/init"]

View File

@@ -1,62 +0,0 @@
# Сборка контейнера с systemd для удобного тестирования ролей Ansible через Molecule
# Используем готовый образ с Ansible (более старый, но стабильный)
FROM geerlingguy/docker-ubuntu2004-ansible:latest
# Устанавливаем переменные окружения
ENV DEBIAN_FRONTEND=noninteractive
ENV container=docker
# Устанавливаем дополнительные пакеты для тестирования
RUN apt-get update && \
apt-get install -y --no-install-recommends \
systemd \
systemd-sysv \
rsync \
git \
ssh \
gcc \
libffi-dev \
libssl-dev \
make \
sudo \
sshpass \
openssh-client \
nano \
less \
ca-certificates \
curl \
gnupg \
lsb-release \
&& rm -rf /var/lib/apt/lists/*
# Устанавливаем Python пакеты для Ansible с обновлением зависимостей
RUN pip install --upgrade pip && \
pip install --upgrade \
requests \
PyYAML \
ansible-core \
&& pip install \
ansible \
"ansible-vault<4.0.0" \
molecule \
molecule-docker \
ansible-lint \
yamllint \
docker \
&& rm -rf /root/.cache/pip
# Устанавливаем docker-compose
RUN curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && \
chmod +x /usr/local/bin/docker-compose
# Указываем, что контейнер использует systemd в качестве init-системы
ENV container=docker
STOPSIGNAL SIGRTMIN+3
# Создаем необходимые директории для systemd
VOLUME [ "/sys/fs/cgroup" ]
# Запускаем systemd при старте контейнера
CMD ["/sbin/init"]

264
Makefile
View File

@@ -1,135 +1,130 @@
# Глобальные переменные # AnsibleTemplate - Универсальная система тестирования Ansible ролей
IMAGE ?= ansible # Автор: Сергей Антропов
TAG ?= 0.1 # Сайт: https://devops.org.ru
REGISTRY ?= inecs/ansible
# По умолчанию используем docker. Для локальной разработки используйте docker-compose
RUN_MODE ?= docker
# Определение команды RUN в зависимости от RUN_MODE .PHONY: molecule vault git help
ifeq ($(RUN_MODE), docker-compose)
RUN = docker compose run --rm $(IMAGE)
else ifeq ($(RUN_MODE), docker)
RUN = docker run -it --rm \
--name $(IMAGE) \
-v $(PWD):/ansible \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro \
-e ANSIBLE_VAULT_PASSWORD_FILE=/ansible/vault-password.txt \
--privileged \
--workdir /ansible \
$(REGISTRY)/$(IMAGE)
else
$(error Invalid RUN_MODE. Use "docker-compose" or "docker")
endif
view create edit show delete test lint deploy new init build rebuild prune release images push pull shell:
@true
#################################################################################################### ####################################################################################################
# Инициализация новой роли # Работа с пресетами
#################################################################################################### ####################################################################################################
init: # Пресеты
@echo "Шаг 1: Создание Docker-образа..." preset-list:
@make docker build @./scripts/list-presets.sh
@echo "Шаг 2: Создание Docker-образов для запуска Molecule..."
@make docker images preset-use:
@echo "Шаг 3: Создание нового vault-файла с паролем..." @./scripts/use-preset.sh $(word 2, $(MAKECMDGOALS))
@read -p "Введите пароль для vault: " VAULT_PASSWORD; \
echo "$$VAULT_PASSWORD" > vault-password.txt; \ # Псевдонимы для пресетов
make vault create preset-minimal:
@echo "Шаг 4: Создание нового брэнча в гите..." @./scripts/use-preset.sh minimal
@make git new
@echo "Шаг 5: Создание новой роли..." preset-standard:
@make role new @./scripts/use-preset.sh standard
preset-docker:
@./scripts/use-preset.sh docker
preset-cluster:
@./scripts/use-preset.sh cluster
#################################################################################################### ####################################################################################################
# Управление контейнерами с помощью docker compose или docker run # Работа с Molecule Universal
#################################################################################################### ####################################################################################################
docker: molecule:
@case "$(word 2, $(MAKECMDGOALS))" in \ @case "$(word 2, $(MAKECMDGOALS))" in \
build) \ create) \
docker buildx create --use --name multiarch-builder --driver docker-container; \
if [ "$(RUN_MODE)" = "docker-compose" ]; then \
docker compose build $(c); \
else \
docker build -t $(REGISTRY)/$(IMAGE) .; \
fi;; \
rebuild) \
docker buildx create --use --name multiarch-builder --driver docker-container; \
if [ "$(RUN_MODE)" = "docker-compose" ]; then \
docker compose build --no-cache $(c); \
else \
docker build --no-cache -t $(REGISTRY)/$(IMAGE) .; \
fi;; \
prune) \
docker system prune -af;; \
shell) \
clear; \ clear; \
echo "Entering to Ansible container shell..."; \ echo "Создание тестового окружения..."; \
$(RUN) bash ;; \ cd molecule/universal && molecule create -s universal;; \
release) \ converge) \
docker buildx create --use --name multiarch-builder --driver docker-container; \ clear; \
docker login $(REGISTRY); \ echo "Запуск плейбуков..."; \
docker buildx build -t $(REGISTRY)/$(IMAGE):$(TAG) -t $(REGISTRY)/$(IMAGE):latest --platform linux/amd64,linux/arm64 --push .;; \ cd molecule/universal && molecule converge -s universal;; \
images) \ verify) \
docker buildx create --use --name multiarch-builder --driver docker-container; \ clear; \
echo "Логинимся в Docker Hub..."; \ echo "Проверка результатов..."; \
docker login; \ cd molecule/universal && molecule verify -s universal;; \
echo "Собираем и пушим основной Ansible образ..."; \ destroy) \
docker buildx build -t $(REGISTRY)/$(IMAGE):$(TAG) -t $(REGISTRY)/$(IMAGE):latest --platform linux/amd64,linux/arm64 --push .; \ clear; \
echo "Собираем и пушим образ CentOS..."; \ echo "Удаление тестового окружения..."; \
docker buildx build -t $(REGISTRY):centos --platform linux/amd64,linux/arm64 --push -f Dockerfile-CentOS .; \ cd molecule/universal && molecule destroy -s universal;; \
echo "Собираем и пушим образ Ubuntu..."; \ test) \
docker buildx build -t $(REGISTRY):ubuntu --platform linux/amd64,linux/arm64 --push -f Dockerfile-Ubuntu .; \ clear; \
echo "Образы успешно опубликованы в Docker Hub: $(REGISTRY)";; \ echo "Полный цикл тестирования..."; \
*) echo "Unknown action. Available actions: build, rebuild, prune, release";; \ cd molecule/universal && molecule test -s universal;; \
*) \
clear; \
echo "Доступные команды:"; \
echo " make molecule create - создать окружение"; \
echo " make molecule converge - запустить плейбуки"; \
echo " make molecule verify - проверить результаты"; \
echo " make molecule destroy - удалить окружение"; \
echo " make molecule test - полный цикл тестирования"; \
;; \
esac esac
#################################################################################################### ####################################################################################################
# Работа с ролью # Работа с Ansible Vault
#################################################################################################### ####################################################################################################
vault: vault:
@case "$(word 2, $(MAKECMDGOALS))" in \ @case "$(word 2, $(MAKECMDGOALS))" in \
show) $(RUN) bash -c "ansible-vault view --vault-password-file vault-password.txt vars/secrets.yml";; \ show) \
create) $(RUN) bash -c "ansible-vault create --encrypt-vault-id default --vault-password-file vault-password.txt vars/secrets.yml";; \
edit) $(RUN) bash -c "ansible-vault edit --vault-password-file vault-password.txt vars/secrets.yml";; \
delete) $(RUN) bash -c "rm vars/secrets.yml";; \
rekey) $(RUN) bash -c "ansible-vault rekey --vault-password-file vault-password.txt vars/secrets.yml";; \
decrypt) $(RUN) bash -c "ansible-vault decrypt --vault-password-file vault-password.txt vars/secrets.yml";; \
encrypt) $(RUN) bash -c "ansible-vault encrypt --encrypt-vault-id default --vault-password-file vault-password.txt vars/secrets.yml";; \
*) echo "Unknown action";; \
esac
role:
@case "$(word 2, $(MAKECMDGOALS))" in \
new) \
clear; \ clear; \
echo "Введите название новой роли на английском:"; \ echo "Доступные файлы секретов:"; \
read ROLE_NAME; \ ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
echo "Введите описание роли:"; \ echo ""; \
read ROLE_DESC; \ read -p "Введите имя файла (без .yml): " FILE; \
cp -r default/ "roles/$${ROLE_NAME}"; \ ansible-vault view --vault-password-file vault/.vault vault/$$FILE.yml;; \
printf "\n- name: $${ROLE_DESC}" >> roles/deploy.yaml; \ create) \
printf "\n import_playbook: $${ROLE_NAME}/deploy.yaml" >> roles/deploy.yaml; \
printf '\n - ../../roles/%s' "$$ROLE_NAME" >> molecule/default/converge.yml; \
printf "\n - $${ROLE_NAME}" >> roles/$$ROLE_NAME/deploy.yaml;; \
lint) \
clear; \ clear; \
echo "Check your role..."; \ echo "Создание файла секретов:"; \
$(RUN) bash -c "ansible-vault decrypt --vault-password-file vault-password.txt vars/secrets.yml"; \ read -p "Введите имя файла (без .yml): " FILE; \
$(RUN) bash -c "ansible-lint roles/*"; \ ansible-vault create --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
$(RUN) bash -c "ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file vault-password.txt";; \ edit) \
test) \
clear; \ clear; \
echo "Running test roles..."; \ echo "Доступные файлы секретов:"; \
$(RUN) bash -c "ansible-vault decrypt --vault-password-file vault-password.txt vars/secrets.yml"; \ ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
$(RUN) bash -c "docker login $(REGISTRY) && molecule test --parallel --destroy=always"; \ echo ""; \
$(RUN) bash -c "ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file vault-password.txt";; \ read -p "Введите имя файла (без .yml): " FILE; \
deploy) \ ansible-vault edit --vault-password-file vault/.vault vault/$$FILE.yml;; \
delete) \
clear; \ clear; \
echo "Deploying roles to production..."; \ echo "Доступные файлы секретов:"; \
$(RUN) bash -c "ansible-playbook roles/deploy.yaml";; \ ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
*) echo "Unknown action";; \ echo ""; \
read -p "Введите имя файла (без .yml): " FILE; \
rm -f vault/$$FILE.yml;; \
rekey) \
clear; \
echo "Доступные файлы секретов:"; \
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
echo ""; \
read -p "Введите имя файла (без .yml): " FILE; \
ansible-vault rekey --vault-password-file vault/.vault vault/$$FILE.yml;; \
decrypt) \
clear; \
echo "Доступные файлы секретов:"; \
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
echo ""; \
read -p "Введите имя файла (без .yml): " FILE; \
ansible-vault decrypt --vault-password-file vault/.vault vault/$$FILE.yml;; \
encrypt) \
clear; \
echo "Доступные файлы секретов:"; \
ls -la vault/*.yml 2>/dev/null || echo "Нет зашифрованных файлов"; \
echo ""; \
read -p "Введите имя файла (без .yml): " FILE; \
ansible-vault encrypt --encrypt-vault-id default --vault-password-file vault/.vault vault/$$FILE.yml;; \
*) \
clear; \
echo "Доступные команды:"; \
echo " make vault create - создать файл секретов"; \
echo " make vault edit - редактировать секреты"; \
echo " make vault show - показать секреты"; \
echo " make vault delete - удалить секреты"; \
echo " make vault encrypt - зашифровать файл"; \
echo " make vault decrypt - расшифровать файл"; \
echo " make vault rekey - сменить пароль";; \
esac esac
#################################################################################################### ####################################################################################################
@@ -153,5 +148,44 @@ git:
NEW_BRANCH="$$BRANCH_NAME"; \ NEW_BRANCH="$$BRANCH_NAME"; \
git checkout -b $$NEW_BRANCH; \ git checkout -b $$NEW_BRANCH; \
echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \ echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \
*) echo "Unknown action. Available actions: push, pull, cluster-branch";; \ *) \
esac clear; \
echo "Доступные команды:"; \
echo " make git push - запушить изменения"; \
echo " make git pull - получить изменения"; \
echo " make git new - создать новую ветку";; \
esac
####################################################################################################
# Справка
####################################################################################################
help:
@clear
@echo "=========================================="
@echo "AnsibleTemplate - Универсальная система"
@echo "тестирования Ansible ролей"
@echo "=========================================="
@echo ""
@echo "📁 Структура проекта:"
@echo " scripts/ - Скрипты автоматизации"
@echo " inventory/ - Инвентори файлы"
@echo " molecule/universal/ - Molecule конфигурация"
@echo " roles/ - Ansible роли"
@echo " vars/ - Переменные"
@echo ""
@echo "🚀 Основные команды:"
@echo " make molecule create - создать тестовое окружение"
@echo " make molecule test - полный цикл тестирования"
@echo " make molecule generateinventory - сгенерировать инвентори"
@echo " make vault create - создать файл секретов"
@echo " make git new - создать новую ветку"
@echo ""
@echo "📖 Для подробной справки:"
@echo " make molecule - команды Molecule"
@echo " make vault - команды Vault"
@echo " make git - команды Git"
@echo "=========================================="
# Пустые цели для совместимости
view create edit show delete test lint deploy new advanced:
@true

View File

@@ -1,62 +0,0 @@
# AnsibleTemplate
Темплейт для создания, проверки и тестирование ролей Ansible с помощью контейнеров Docker.
### С чего начать?
На вашей машине вам необходимо сбилдить образ, где будут запускаться все роли через docker-compose.
Это можно сделать самостоятельно:
- **make docker build** - создание контейнера
- **make docker rebuild** - пересоздание контейнера, если были внесены изменения в Dockerfile
- **make docker prune** - очистить систему от лишних образов
- **make docker shell** - войти в контейнер Shell
- **make docker release** - собирает образ контейнера и пушит его в докер реджистри
- **make docker images** - собрать образы контейнеров с systemd, для удобного тестирования ролей
- **make images** - собрать и запушить все образы (основной Ansible, CentOS, Ubuntu) в Docker Hub (inecs/ansible)
Или ввести команду:
- **make init** - которая создаст файл секретов с паролем. Сбилдит образ. И создаст новую роль.
### Работа с ролью
- **make role new** - создать новую роль из шаблона. Название роли пишется на английском, описание роли на любом языке
- **make role lint** - проверяет все роли в папке roles/* на наличие ошибок
- **make role test** - позволяет тестировать роль, указанную в molecule/default/converge.yml
сразу на двух контейнерах (RedHat и Ubuntu)
- **make role deploy** - запускает роль в продакшен. Все хосты берет из файла inventory/hosts
### Работа с файлом переменных
Все переменные защищены через **Ansible-Vault** и находятся в папке vars/secrets.yml
Для смены пароля измените его в файле **./vault-password.txt**
- **make vault create** - создать новый файл с учетом пароля в файле **./vault-password.txt**
- **make vault delete** - удалить файл с переменными
- **make vault edit** - отредактировать файл переменных
- **make vault show** - показать содержимое файла переменных
- **make vault rekey** - сменить пароль шифрования
- **make vault encrypt** - зашифровать файл секретов
- **make vault decrypt** - расшифровать файл секретов
### Работа с Git
- **make git push** - запушить изменения. С выбором ветки и вводом коммита.
- **make git pull** - получить изменения из репы
- **make git new** - создание нового брэнча имя cluster-branch_name для IaC подхода.
### Добавить свой образ контейнера для тестов
Что бы добавить или изменить докер-образы для тестирования ролей измените файл настроек молекулы
molecule/default/molecule.yml
```yaml
- name: ubuntu-instance
image: "your.docker-registry.com/your-image:latest"
privileged: true
pre_build_image: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
```
Помните, что образ обязательно должен содержать python не ниже версии 3.12 и systemd для нормального тестирования ролей.

View File

@@ -1,6 +1,6 @@
[defaults] [defaults]
inventory = inventory/hosts inventory = inventory/hosts.ini
vault_password_file = vault-password.txt vault_password_file = vault/.vault
remote_user = devops remote_user = devops
host_key_checking = False host_key_checking = False
enable_plugins = yaml, ini enable_plugins = yaml, ini

51
cicd/.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,51 @@
# GitLab CI для AnsibleTemplate
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
stages:
- test
- deploy
variables:
DOCKER_IMAGE: "quay.io/ansible/creator-ee:latest"
DOCKER_TLS_CERTDIR: ""
ANSIBLE_FORCE_COLOR: "true"
before_script:
- echo "Установка зависимостей..."
- pip install molecule[docker] ansible-lint
- ansible-galaxy collection install -r requirements.yml
# Тестирование с Molecule
test:
stage: test
image: $DOCKER_IMAGE
services:
- docker:dind
variables:
DOCKER_TLS_CERTDIR: ""
script:
- echo "Запуск тестов Molecule..."
- cd molecule/universal
- molecule test -s universal
artifacts:
reports:
junit: molecule/universal/.molecule/reports/junit.xml
paths:
- molecule/universal/.molecule/
expire_in: 1 week
only:
- merge_requests
- main
- develop
# Деплой (если нужен)
deploy:
stage: deploy
image: $DOCKER_IMAGE
script:
- echo "Деплой не настроен"
- echo "Добавьте логику деплоя в этот job"
when: manual
only:
- main

View File

@@ -0,0 +1,53 @@
# Azure DevOps Pipeline для AnsibleTemplate
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
trigger:
- main
- develop
pool:
vmImage: 'ubuntu-latest'
variables:
ANSIBLE_FORCE_COLOR: 'true'
DOCKER_TLS_CERTDIR: ''
stages:
- stage: Test
displayName: 'Test Stage'
jobs:
- job: TestJob
displayName: 'Run Tests'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.11'
displayName: 'Use Python 3.11'
- script: |
pip install --upgrade pip
pip install molecule[docker] ansible-lint
ansible-galaxy collection install -r requirements.yml
displayName: 'Install Dependencies'
- script: |
ansible-lint molecule/universal/
displayName: 'Run Ansible Lint'
- script: |
cd molecule/universal
molecule test -s universal
displayName: 'Run Molecule Tests'
- task: PublishTestResults@2
inputs:
testResultsFiles: 'molecule/universal/.molecule/reports/junit.xml'
testRunTitle: 'Molecule Test Results'
condition: always()
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: 'molecule/universal/.molecule'
artifactName: 'molecule-reports'
condition: always()

70
cicd/github/workflows.yml Normal file
View File

@@ -0,0 +1,70 @@
# GitHub Actions Workflow для AnsibleTemplate
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
name: Ansible Testing
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo usermod -aG docker $USER
- name: Install Python dependencies
run: |
pip install --upgrade pip
pip install molecule[docker] ansible-lint
ansible-galaxy collection install -r requirements.yml
- name: Run Molecule tests
run: |
cd molecule/universal
molecule test -s universal
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: molecule-reports
path: molecule/universal/.molecule/
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install ansible-lint
ansible-galaxy collection install -r requirements.yml
- name: Run Ansible Lint
run: |
ansible-lint molecule/universal/

59
cicd/jenkins/Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,59 @@
// Jenkins Pipeline для AnsibleTemplate
// Автор: Сергей Антропов
// Сайт: https://devops.org.ru
pipeline {
agent any
environment {
ANSIBLE_FORCE_COLOR = 'true'
DOCKER_TLS_CERTDIR = ''
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install Dependencies') {
steps {
sh '''
pip install --upgrade pip
pip install molecule[docker] ansible-lint
ansible-galaxy collection install -r requirements.yml
'''
}
}
stage('Lint') {
steps {
sh 'ansible-lint molecule/universal/'
}
}
stage('Test') {
steps {
dir('molecule/universal') {
sh 'molecule test -s universal'
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'molecule/universal/.molecule/**/*', allowEmptyArchive: true
publishTestResults testResultsPattern: 'molecule/universal/.molecule/reports/junit.xml'
}
success {
echo 'Pipeline completed successfully!'
}
failure {
echo 'Pipeline failed!'
}
}
}

View File

@@ -1,10 +0,0 @@
---
- name: Deploy roles
hosts: all
become: true
become_user: root
become_method: ansible.builtin.sudo
gather_facts: true
vars_files:
- ../../vars/secrets.yml
roles:

View File

View File

@@ -1,4 +0,0 @@
---
- name: Пример таски
debug:
msg: "Привет! Я запустился на Debian/Ubuntu!"

View File

@@ -1,12 +0,0 @@
---
- name: "Определяем ОС"
set_fact:
os_family: "{{ ansible_facts['os_family'] }}"
- name: "Подключаем таски для RedHat совместимых"
include_tasks: "redhat/main.yaml"
when: os_family == "RedHat"
- name: "Подключаем таски для Debian/Ubuntu совместимых"
include_tasks: "debian/main.yaml"
when: os_family == "Debian"

View File

@@ -1,4 +0,0 @@
---
- name: Пример таски
debug:
msg: "Привет! Я запустился на RedHat/CentOS/Fedora!"

View File

View File

@@ -1,12 +0,0 @@
services:
ansible:
image: inecs/ansible:latest
container_name: ansible
volumes:
- .:/ansible
- /var/run/docker.sock:/var/run/docker.sock
environment:
- ANSIBLE_VAULT_PASSWORD_FILE=./vault-password.txt
tty: true
privileged: true
working_dir: /ansible

View File

@@ -1 +0,0 @@
[all]

29
inventory/hosts.ini Normal file
View File

@@ -0,0 +1,29 @@
# Автоматически сгенерированный инвентори
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
[all:vars]
ansible_connection=community.docker.docker
ansible_python_interpreter=/usr/bin/python3
[all]
controller
[docker]
docker1
[dood]
dood1
[test]
test1
test2
test3
[all]
controller
test1
test2
test3
docker1
dood1

View File

@@ -1,6 +0,0 @@
---
- name: Converge
hosts: all
vars_files:
- ../../vars/secrets.yml
roles:

View File

@@ -1,8 +0,0 @@
- name: Destroy containers on interrupt
hosts: localhost
tasks:
- name: Ensure containers are destroyed
docker_container:
name: "{{ item.name }}"
state: absent
loop: "{{ molecule_yml.platforms }}"

View File

@@ -1,61 +0,0 @@
---
dependency:
name: galaxy
enabled: false
options:
requirements-file: requirements.yml
driver:
name: docker
platforms:
- name: centos
image: "inecs/ansible:centos"
privileged: true
pre_build_image: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- /var/run/docker.sock:/var/run/docker.sock
tmpfs:
- /tmp
- /run
- name: ubuntu
image: "inecs/ansible:ubuntu"
privileged: true
pre_build_image: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- /var/run/docker.sock:/var/run/docker.sock
tmpfs:
- /tmp
- /run
provisioner:
name: ansible
connection_options:
ansible_connection: docker
ansible_user: root
env:
ANSIBLE_PYTHON_INTERPRETER: /usr/bin/python3
lint:
name: ansible-lint
verifier:
name: ansible
scenario:
name: default
test_sequence:
- dependency
- cleanup
- destroy
- syntax
- create
- prepare
- converge
- idempotence
- side_effect
- verify
- cleanup
- destroy

View File

@@ -1,43 +0,0 @@
- name: Prepare
hosts: all
tasks:
- name: Detect OS family
ansible.builtin.setup:
gather_subset:
- "min"
- name: Обновляем пакеты для работы с Ansible в RockyLinux (Centos/RedHat)
when: ansible_facts['os_family'] == "RedHat"
block:
- name: Устанавливаем репозиторий AppStream (если его нет)
ansible.builtin.raw: dnf config-manager --set-enabled appstream
changed_when: false
- name: Установить rsync
ansible.builtin.raw: dnf install -y rsync
changed_when: false
- name: Устанавливаем Python 3.8
ansible.builtin.raw: dnf install -y python38 python38-pip
changed_when: false
- name: Обновляем символическую ссылку python3
ansible.builtin.raw: alternatives --set python /usr/bin/python3.8
changed_when: false
# - name: Fix repository URLs
# ansible.builtin.command:
# cmd: sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
# changed_when: false
# - name: Update baseurl
# ansible.builtin.command:
# cmd: sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
# changed_when: false
# - name: Install required packages
# ansible.builtin.yum:
# name:
# - epel-release
# - python3
# - python3-pip
# state: present

View File

@@ -1,7 +0,0 @@
---
- name: Prepare
hosts: all
tasks:
- name: Reun verify
debug:
msg: "Hello, Verify!"

View File

@@ -1,132 +0,0 @@
---
# Проверка работы systemd, docker и docker-compose в образах
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: Verify systemd, docker and docker-compose services
hosts: all
gather_facts: true
tasks:
- name: Display OS information
debug:
msg: "Тестирование на {{ ansible_distribution }} {{ ansible_distribution_version }}"
- name: Check if systemd is available and running
systemd:
name: systemd
state: started
register: systemd_status
failed_when: false
- name: Display systemd status
debug:
msg: "Systemd статус: {{ 'Доступен и запущен' if systemd_status is succeeded else 'Недоступен или не запущен' }}"
- name: Check systemd version
command: systemd --version
register: systemd_version
failed_when: false
changed_when: false
- name: Display systemd version
debug:
msg: "Версия systemd: {{ systemd_version.stdout_lines[0] if systemd_version.stdout_lines else 'Не определена' }}"
- name: Check if docker service exists
stat:
path: /usr/bin/docker
register: docker_binary
- name: Check if docker service exists (alternative path)
stat:
path: /usr/local/bin/docker
register: docker_binary_alt
- name: Display docker binary status
debug:
msg: "Docker binary: {{ 'Найден в /usr/bin/docker' if docker_binary.stat.exists else ('Найден в /usr/local/bin/docker' if docker_binary_alt.stat.exists else 'Не найден') }}"
- name: Check docker version
command: docker --version
register: docker_version
failed_when: false
changed_when: false
- name: Display docker version
debug:
msg: "Версия Docker: {{ docker_version.stdout if docker_version.stdout else 'Docker не установлен' }}"
- name: Check if docker daemon is running
command: docker info
register: docker_info
failed_when: false
changed_when: false
- name: Display docker daemon status
debug:
msg: "Docker daemon: {{ 'Запущен' if docker_info is succeeded else 'Не запущен или недоступен' }}"
- name: Check if docker-compose binary exists
stat:
path: /usr/local/bin/docker-compose
register: docker_compose_binary
- name: Check if docker-compose binary exists (alternative path)
stat:
path: /usr/bin/docker-compose
register: docker_compose_binary_alt
- name: Check if docker compose plugin exists
command: docker compose version
register: docker_compose_plugin
failed_when: false
changed_when: false
- name: Display docker-compose status
debug:
msg: "Docker Compose: {{ 'Найден как binary' if docker_compose_binary.stat.exists or docker_compose_binary_alt.stat.exists else ('Найден как plugin' if docker_compose_plugin is succeeded else 'Не найден') }}"
- name: Display docker-compose version
debug:
msg: "Версия Docker Compose: {{ docker_compose_plugin.stdout if docker_compose_plugin is succeeded else 'Docker Compose не установлен' }}"
- name: Test docker functionality
command: docker run --rm hello-world
register: docker_test
failed_when: false
changed_when: false
- name: Display docker test result
debug:
msg: "Тест Docker: {{ 'Успешно' if docker_test is succeeded else 'Ошибка - ' + docker_test.stderr }}"
- name: Check systemd services status
command: systemctl list-units --type=service --state=running
register: running_services
failed_when: false
changed_when: false
- name: Display running services count
debug:
msg: "Количество запущенных сервисов: {{ running_services.stdout_lines | length }}"
- name: Check for docker-related systemd services
command: systemctl list-units --type=service | grep -i docker
register: docker_services
failed_when: false
changed_when: false
- name: Display docker services
debug:
msg: "Docker сервисы: {{ docker_services.stdout_lines if docker_services.stdout_lines else 'Не найдены' }}"
- name: Final summary
debug:
msg: |
========================================
РЕЗУЛЬТАТЫ ПРОВЕРКИ ОБРАЗА {{ ansible_distribution }}:
========================================
Systemd: {{ '✓ Работает' if systemd_status is succeeded else '✗ Не работает' }}
Docker: {{ '✓ Установлен и работает' if docker_info is succeeded else '✗ Не установлен или не работает' }}
Docker Compose: {{ '✓ Доступен' if (docker_compose_binary.stat.exists or docker_compose_binary_alt.stat.exists or docker_compose_plugin is succeeded) else '✗ Недоступен' }}
========================================

View File

@@ -0,0 +1,99 @@
# Пресеты для Molecule
## Описание
Пресеты - это готовые конфигурации для быстрого развертывания тестовых окружений. Каждый пресет содержит определенный набор хостов и настроек.
## Доступные пресеты
### `minimal.yml`
- **Описание**: Минимальный набор для быстрого тестирования
- **Хосты**: 1 хост (Debian)
- **Использование**: Для простых тестов и отладки
### `standard.yml`
- **Описание**: Стандартный набор для тестирования
- **Хосты**: 3 хоста (Debian + RHEL)
- **Использование**: Для большинства тестов
### `docker.yml`
- **Описание**: Пресет с Docker контейнерами
- **Хосты**: 2 systemd + 1 DinD + 1 DOoD
- **Использование**: Для тестирования Docker-приложений
### `cluster.yml`
- **Описание**: Пресет для кластерного тестирования
- **Хосты**: 8 хостов (web, app, database, loadbalancer, monitoring)
- **Использование**: Для тестирования сложных архитектур
## Использование
### Через Makefile
```bash
# Показать все пресеты
make preset list
# Переключиться на пресет
make preset use minimal
make preset use standard
make preset use docker
make preset use cluster
```
### Через скрипт
```bash
# Показать все пресеты
./scripts/use-preset.sh
# Переключиться на пресет
./scripts/use-preset.sh minimal
```
### Ручное переключение
```bash
# Скопировать пресет в hosts.yml
cp molecule/presets/minimal.yml molecule/universal/hosts.yml
```
## Создание собственного пресета
1. Скопируйте существующий пресет:
```bash
cp molecule/presets/standard.yml molecule/presets/my-preset.yml
```
2. Отредактируйте файл под свои нужды
3. Используйте новый пресет:
```bash
make preset use my-preset
```
## Структура пресета
```yaml
---
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
images:
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
rhel: "quay.io/centos/centos:stream9-systemd"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
- name: host1
family: debian
groups: [test]
- name: docker1
type: dind
groups: [docker]
publish: ["8080:8080"]
```

View File

@@ -0,0 +1,57 @@
---
# Пресет для кластерного тестирования
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
rhel: "quay.io/centos/centos:stream9-systemd"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Web серверы
- name: web1
family: debian
groups: [web]
- name: web2
family: rhel
groups: [web]
# App серверы
- name: app1
family: debian
groups: [app]
- name: app2
family: rhel
groups: [app]
# Database серверы
- name: db1
family: debian
groups: [database]
- name: db2
family: rhel
groups: [database]
# Load Balancer
- name: lb1
family: rhel
groups: [loadbalancer]
publish: ["80:80", "443:443"]
# Мониторинг
- name: monitor1
family: debian
groups: [monitoring]
publish: ["3000:3000", "9090:9090"]

View File

@@ -0,0 +1,44 @@
---
# Пресет с Docker контейнерами
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
rhel: "quay.io/centos/centos:stream9-systemd"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Тестовые хосты
- name: test1
family: debian
groups: [test]
- name: test2
family: rhel
groups: [test]
# DinD узел (Docker-in-Docker)
- name: docker1
type: dind
groups: [docker]
publish: ["8080:8080"]
# DOoD узел (Docker-out-of-Docker)
- name: dood1
type: dood
family: debian
groups: [dood]
publish: ["8081:8081"]
env:
DOCKER_HOST: unix:///var/run/docker.sock

View File

@@ -0,0 +1,25 @@
---
# Минимальный пресет для быстрого тестирования
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Минимальный набор - один хост
- name: u1
family: debian
groups: [test]

View File

@@ -0,0 +1,32 @@
---
# Стандартный пресет для тестирования
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
rhel: "quay.io/centos/centos:stream9-systemd"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Стандартный набор - 3 хоста
- name: u1
family: debian
groups: [test]
- name: u2
family: rhel
groups: [test]
- name: u3
family: debian
groups: [test]

View File

@@ -0,0 +1,52 @@
---
- hosts: localhost
gather_facts: false
vars:
# перечисли файлы/глобы, которые нужно временно расшифровать
vault_targets:
- /ansible/vault/secrets.yml
# добавляй сюда свои пути (host_vars/*/vault.yml, group_vars/*/vault.yml, и т.п.)
tasks:
- name: Install required collections (use repo's requirements.yml)
community.docker.docker_container_exec:
container: ansible
command: bash -lc "ansible-galaxy collection install -r /ansible/requirements.yml || true"
- name: Decrypt vault targets (best-effort)
community.docker.docker_container_exec:
container: ansible
command: >
bash -lc '
set -euo pipefail;
for p in {{ vault_targets | map('quote') | join(' ') }}; do
if [ -e "$p" ]; then
echo "[vault] decrypt $p";
ansible-vault decrypt --vault-password-file /ansible/vault-password.txt "$p" || true;
fi
done
'
- name: Run external playbook (your lab play)
community.docker.docker_container_exec:
container: ansible
command: >
bash -lc "
ANSIBLE_ROLES_PATH=/ansible/roles
ansible-playbook -i {{ lookup('env','MOLECULE_EPHEMERAL_DIRECTORY') }}/inventory/hosts.ini /ansible/molecule/universal/site.yml
"
- name: Re-encrypt vault targets (always)
community.docker.docker_container_exec:
container: ansible
command: >
bash -lc '
set -euo pipefail;
for p in {{ vault_targets | map('quote') | join(' ') }}; do
if [ -e "$p" ]; then
echo "[vault] encrypt $p";
ansible-vault encrypt --encrypt-vault-id default --vault-password-file /ansible/vault-password.txt "$p" || true;
fi
done
'
ignore_errors: true

View File

@@ -0,0 +1,107 @@
---
- hosts: localhost
gather_facts: false
vars_files:
- hosts.yml
tasks:
- name: Ensure network exists
community.docker.docker_network:
name: "{{ docker_network }}"
state: present
# SYSTEMD nodes
- name: Pull systemd images
community.docker.docker_image:
name: "{{ images[item.family] }}"
source: pull
loop: "{{ hosts | selectattr('type','undefined') | list }}"
loop_control: { label: "{{ item.name }}" }
- name: Start systemd nodes
community.docker.docker_container:
name: "{{ item.name }}"
image: "{{ images[item.family] }}"
networks: [ { name: "{{ docker_network }}" } ]
privileged: "{{ systemd_defaults.privileged }}"
command: "{{ systemd_defaults.command }}"
volumes: "{{ (systemd_defaults.volumes | default([])) + (item.volumes | default([])) }}"
tmpfs: "{{ (systemd_defaults.tmpfs | default([])) + (item.tmpfs | default([])) }}"
capabilities: "{{ (systemd_defaults.capabilities | default([])) + (item.capabilities | default([])) }}"
published_ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
state: started
restart_policy: unless-stopped
loop: "{{ hosts | selectattr('type','undefined') | list }}"
loop_control: { label: "{{ item.name }}" }
# DinD nodes
- name: Start DinD nodes (docker:27-dind)
community.docker.docker_container:
name: "{{ item.name }}"
image: "docker:27-dind"
privileged: true
environment: { DOCKER_TLS_CERTDIR: "" }
networks: [ { name: "{{ docker_network }}" } ]
published_ports: "{{ item.publish | default([]) }}"
volumes: [ "{{ 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 (mount docker.sock)
- name: Start DOoD nodes (systemd + docker.sock mount)
community.docker.docker_container:
name: "{{ item.name }}"
image: "{{ images[item.family] }}"
networks: [ { name: "{{ 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([])) }}"
tmpfs: "{{ (systemd_defaults.tmpfs | default([])) + (item.tmpfs | default([])) }}"
capabilities: "{{ (systemd_defaults.capabilities | default([])) + (item.capabilities | default([])) }}"
published_ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
state: started
restart_policy: unless-stopped
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
loop_control: { label: "{{ item.name }}" }
# Build groups map
- name: Build groups map {group: [hosts]}
set_fact:
groups_map: "{{ groups_map | default({}) }}"
- name: Append hosts to groups
set_fact:
groups_map: "{{ groups_map | combine({ item_group: (groups_map[item_group] | default([])) + [item_name] }) }}"
loop: "{{ hosts | subelements('groups', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }}"
vars:
item_name: "{{ item.0.name }}"
item_group: "{{ item.1 }}"
# Render inventory
- name: Render inventory ini
set_fact:
inv_content: |
[all:vars]
ansible_connection=community.docker.docker
ansible_python_interpreter=/usr/bin/python3
{% for group, members in (groups_map | dictsort) %}
[{{ group }}]
{% for h in members %}{{ h }}
{% endfor %}
{% endfor %}
[all]
{% for h in hosts %}{{ h.name }}
{% endfor %}
- name: Write inventory file
copy:
dest: "{{ generated_inventory }}"
content: "{{ inv_content }}"
mode: "0644"

View File

@@ -0,0 +1,29 @@
---
- hosts: localhost
gather_facts: false
vars_files:
- hosts.yml
tasks:
- name: Remove containers
community.docker.docker_container:
name: "{{ item.name }}"
state: absent
force_kill: 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 network
community.docker.docker_network:
name: "{{ docker_network }}"
state: absent
ignore_errors: true

View File

@@ -0,0 +1,44 @@
---
# Пресет с Docker контейнерами
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
rhel: "quay.io/centos/centos:stream9-systemd"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Тестовые хосты
- name: test1
family: debian
groups: [test]
- name: test2
family: rhel
groups: [test]
# DinD узел (Docker-in-Docker)
- name: docker1
type: dind
groups: [docker]
publish: ["8080:8080"]
# DOoD узел (Docker-out-of-Docker)
- name: dood1
type: dood
family: debian
groups: [dood]
publish: ["8081:8081"]
env:
DOCKER_HOST: unix:///var/run/docker.sock

View File

@@ -0,0 +1,28 @@
---
# Универсальная конфигурация Molecule
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
driver:
name: delegated
provisioner:
name: ansible
config_options:
defaults:
stdout_callback: yaml
env:
ANSIBLE_STDOUT_CALLBACK: yaml
inventory:
links:
hosts: "${MOLECULE_EPHEMERAL_DIRECTORY}/inventory/hosts.ini"
dependency:
name: galaxy
verifier:
name: ansible
lint: |-
set -e
ansible-lint

View File

@@ -0,0 +1,95 @@
---
# Универсальный плейбук для тестирования
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: Base deps
hosts: all
become: true
tasks:
- name: Update apt cache (Debian)
apt:
update_cache: true
when: ansible_os_family == 'Debian'
changed_when: false
- name: Common tools
package:
name:
- curl
- jq
- ca-certificates
- iproute2
- iputils-ping
- procps
- net-tools
- sudo
- vim
state: present
# ===== ТЕСТОВЫЕ РОЛИ =====
- name: Deploy example role to test hosts
hosts: test
become: true
roles:
- example
vars:
example_package_name: "nginx"
example_directory: "/opt/example"
example_setting: "test"
example_port: 8080
- name: Deploy example role to docker hosts (DinD)
hosts: docker
become: true
roles:
- example
vars:
example_package_name: "docker"
example_directory: "/opt/docker-example"
example_setting: "dind"
example_port: 8080
- name: Deploy example role to dood hosts (DOoD)
hosts: dood
become: true
roles:
- example
vars:
example_package_name: "docker"
example_directory: "/opt/dood-example"
example_setting: "dood"
example_port: 8081
# ===== Пример: поднять compose внутри DinD-хостов =====
- name: Deploy stack inside DinD nodes
hosts: docker
gather_facts: false
vars:
docker_host: "tcp://{{ inventory_hostname }}:2375"
stack_dir: /root/stack
tasks:
- name: Create stack directory
file:
path: "{{ stack_dir }}"
state: directory
- name: Create simple docker-compose.yml
copy:
dest: "{{ stack_dir }}/docker-compose.yml"
content: |
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "8080:80"
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
- name: Deploy stack with docker-compose
community.docker.docker_compose_v2:
project_src: "{{ stack_dir }}"
state: present
docker_host: "{{ docker_host }}"

View File

@@ -0,0 +1,263 @@
---
# Универсальные проверки для тестового стенда
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: Verify web servers
hosts: web
become: true
tasks:
- name: Check nginx service status
systemd:
name: nginx
register: nginx_status
- name: Verify nginx is running
assert:
that:
- nginx_status.status.ActiveState == "active"
- nginx_status.status.SubState == "running"
fail_msg: "nginx service is not running"
success_msg: "nginx service is running"
- name: Test nginx response
uri:
url: "http://{{ inventory_hostname }}"
method: GET
register: nginx_response
- name: Verify nginx response
assert:
that:
- nginx_response.status == 200
fail_msg: "nginx is not responding"
success_msg: "nginx is responding correctly"
- name: Verify app servers
hosts: app
become: true
tasks:
- name: Check Python installation
command: python3 --version
register: python_version
changed_when: false
- name: Verify Python is installed
assert:
that:
- python_version.rc == 0
fail_msg: "Python3 is not installed"
success_msg: "Python3 is installed: {{ python_version.stdout }}"
- name: Check app file exists
stat:
path: /opt/myapp/app.py
register: app_file
- name: Verify app file exists
assert:
that:
- app_file.stat.exists
fail_msg: "App file does not exist"
success_msg: "App file exists and is executable"
- name: Verify database servers
hosts: database
become: true
tasks:
- name: Check SQLite installation
command: sqlite3 --version
register: sqlite_version
changed_when: false
- name: Verify SQLite is installed
assert:
that:
- sqlite_version.rc == 0
fail_msg: "SQLite is not installed"
success_msg: "SQLite is installed: {{ sqlite_version.stdout }}"
- name: Check database file exists
stat:
path: /var/lib/mydb/sample.db
register: db_file
- name: Verify database file exists
assert:
that:
- db_file.stat.exists
fail_msg: "Database file does not exist"
success_msg: "Database file exists"
- name: Test database query
command: sqlite3 /var/lib/mydb/sample.db "SELECT COUNT(*) FROM users;"
register: db_query
changed_when: false
- name: Verify database query
assert:
that:
- db_query.rc == 0
- db_query.stdout | int > 0
fail_msg: "Database query failed"
success_msg: "Database query successful: {{ db_query.stdout }} users found"
- name: Verify cache servers
hosts: cache
become: true
tasks:
- name: Check Redis service status
systemd:
name: redis
register: redis_status
- name: Verify Redis is running
assert:
that:
- redis_status.status.ActiveState == "active"
- redis_status.status.SubState == "running"
fail_msg: "Redis service is not running"
success_msg: "Redis service is running"
- name: Test Redis connection
command: redis-cli ping
register: redis_ping
changed_when: false
- name: Verify Redis connection
assert:
that:
- redis_ping.rc == 0
- redis_ping.stdout == "PONG"
fail_msg: "Redis is not responding"
success_msg: "Redis is responding correctly"
- name: Verify load balancer
hosts: loadbalancer
become: true
tasks:
- name: Check HAProxy service status
systemd:
name: haproxy
register: haproxy_status
- name: Verify HAProxy is running
assert:
that:
- haproxy_status.status.ActiveState == "active"
- haproxy_status.status.SubState == "running"
fail_msg: "HAProxy service is not running"
success_msg: "HAProxy service is running"
- name: Check HAProxy configuration
stat:
path: /etc/haproxy/haproxy.cfg
register: haproxy_config
- name: Verify HAProxy configuration exists
assert:
that:
- haproxy_config.stat.exists
fail_msg: "HAProxy configuration does not exist"
success_msg: "HAProxy configuration exists"
- name: Verify monitoring
hosts: monitoring
become: true
tasks:
- name: Check monitoring tools
command: which htop
register: htop_check
changed_when: false
- name: Verify monitoring tools are installed
assert:
that:
- htop_check.rc == 0
fail_msg: "Monitoring tools are not installed"
success_msg: "Monitoring tools are installed"
- name: Check monitoring script
stat:
path: /usr/local/bin/system-info.sh
register: monitor_script
- name: Verify monitoring script exists
assert:
that:
- monitor_script.stat.exists
fail_msg: "Monitoring script does not exist"
success_msg: "Monitoring script exists"
- name: Test monitoring script
command: /usr/local/bin/system-info.sh
register: monitor_output
changed_when: false
- name: Verify monitoring script works
assert:
that:
- monitor_output.rc == 0
- monitor_output.stdout | length > 0
fail_msg: "Monitoring script failed"
success_msg: "Monitoring script works correctly"
- name: Network connectivity tests
hosts: all
tasks:
- name: Test connectivity to web servers
wait_for:
host: "{{ item }}"
port: 80
timeout: 10
loop:
- web1
- web2
when: "'web' not in group_names"
ignore_errors: true
- name: Test connectivity to app servers
wait_for:
host: "{{ item }}"
port: 8080
timeout: 10
loop:
- app1
when: "'app' not in group_names"
ignore_errors: true
- name: Test connectivity to cache servers
wait_for:
host: "{{ item }}"
port: 6379
timeout: 10
loop:
- cache1
when: "'cache' not in group_names"
ignore_errors: true
- name: Test connectivity to load balancer
wait_for:
host: lb1
port: 80
timeout: 10
when: "'loadbalancer' not in group_names"
ignore_errors: true
- name: Final verification summary
hosts: localhost
gather_facts: false
tasks:
- name: Display verification summary
debug:
msg: |
========================================
Verification Summary
========================================
- Web servers: {{ 'OK' if web_servers_ok is defined else 'SKIPPED' }}
- App servers: {{ 'OK' if app_servers_ok is defined else 'SKIPPED' }}
- Database servers: {{ 'OK' if database_servers_ok is defined else 'SKIPPED' }}
- Cache servers: {{ 'OK' if cache_servers_ok is defined else 'SKIPPED' }}
- Load balancer: {{ 'OK' if loadbalancer_ok is defined else 'SKIPPED' }}
- Monitoring: {{ 'OK' if monitoring_ok is defined else 'SKIPPED' }}
========================================

View File

@@ -1,4 +1,9 @@
--- ---
# Ansible Collections для Molecule Universal
collections: collections:
- name: maxhoesel.proxmox - name: community.docker
version: 5.0.1 version: ">=3.0.0"
- name: community.general
version: ">=7.0.0"
- name: ansible.posix
version: ">=1.4.0"

View File

@@ -1 +0,0 @@
---

10
scripts/list-presets.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
# Скрипт для показа всех пресетов
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
clear
echo "Доступные пресеты:"
for file in molecule/presets/*.yml; do
echo " $(basename "$file" .yml)"
done

35
scripts/use-preset.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Скрипт для переключения между пресетами
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
set -e
PRESET_DIR="molecule/presets"
HOSTS_FILE="molecule/universal/hosts.yml"
if [ $# -eq 0 ]; then
echo "Использование: $0 <preset_name>"
echo ""
echo "Доступные пресеты:"
ls -1 "$PRESET_DIR"/*.yml | sed 's/.*\///' | sed 's/\.yml$//' | sed 's/^/ /'
exit 1
fi
PRESET="$1"
PRESET_FILE="$PRESET_DIR/$PRESET.yml"
if [ ! -f "$PRESET_FILE" ]; then
echo "Ошибка: Пресет '$PRESET' не найден!"
echo "Доступные пресеты:"
ls -1 "$PRESET_DIR"/*.yml | sed 's/.*\///' | sed 's/\.yml$//' | sed 's/^/ /'
exit 1
fi
echo "Переключение на пресет: $PRESET"
cp "$PRESET_FILE" "$HOSTS_FILE"
echo "Готово! Теперь используется пресет: $PRESET"
echo ""
echo "Для применения изменений выполните:"
echo " make molecule destroy"
echo " make molecule create"

View File

View File

@@ -1 +0,0 @@
password123

33
vault/secrets.yml Normal file
View File

@@ -0,0 +1,33 @@
---
# Основные секреты для тестирования
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
# Пароли для баз данных
database_passwords:
root_password: "database-root-password"
app_user_password: "database-app-password"
monitoring_user_password: "monitoring-user-password"
# SSL сертификаты
ssl_certificates:
server_cert: |
-----BEGIN CERTIFICATE-----
# Server certificate content
-----END CERTIFICATE-----
server_key: |
-----BEGIN PRIVATE KEY-----
# Server private key content
-----END PRIVATE KEY-----
# API ключи
api_keys:
github_token: "ghp_example_token"
dockerhub_token: "dckr_example_token"
monitoring_api_key: "monitoring_api_key_example"
# Строки подключения
database_connections:
primary: "mysql://user:password@db1:3306/app"
replica: "mysql://user:password@db2:3306/app"
cache: "redis://cache1:6379/0"