Начало проекта
This commit is contained in:
commit
6fa364ad7b
7
.ansible-lint
Normal file
7
.ansible-lint
Normal file
@ -0,0 +1,7 @@
|
||||
skip_list:
|
||||
- fqcn
|
||||
- yaml[new-line-at-end-of-file]
|
||||
- yaml[truthy]
|
||||
- yaml[line-length]
|
||||
- var-naming[no-role-prefix]
|
||||
- 'ignore-errors'
|
173
.gitignore
vendored
Normal file
173
.gitignore
vendored
Normal file
@ -0,0 +1,173 @@
|
||||
# ---> Ansible
|
||||
*.retry
|
||||
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
88
.gitlab-ci.yml
Normal file
88
.gitlab-ci.yml
Normal file
@ -0,0 +1,88 @@
|
||||
stages:
|
||||
- lint
|
||||
# - test
|
||||
- deploy
|
||||
- notify
|
||||
|
||||
services:
|
||||
- name: docker:dind
|
||||
command: ["--tls=false"]
|
||||
|
||||
variables:
|
||||
DOCKER_IMAGE: "hub.antropoff.ru/ansible/ansible:latest"
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
ANSIBLE_FORCE_COLOR: "true"
|
||||
|
||||
before_script:
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login hub.antropoff.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 "Работа пайплайна завершена"
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
15
.idea/AnsibleTemplate.iml
generated
Normal file
15
.idea/AnsibleTemplate.iml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
<option name="TEMPLATE_FOLDERS">
|
||||
<list>
|
||||
<option value="$MODULE_DIR$/roles/role/templates" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</module>
|
6
.idea/encodings.xml
generated
Normal file
6
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/roles/deploy.yaml" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
47
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
47
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,47 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="setup" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="7">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
||||
<item index="4" class="java.lang.String" itemvalue="embed" />
|
||||
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||
<item index="6" class="java.lang.String" itemvalue="router-view" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myCustomValuesEnabled" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="WARNING" enabled_by_default="false">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="0" />
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredIdentifiers">
|
||||
<list>
|
||||
<option value="app.main.*" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.9 (Test)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (Test)" project-jdk-type="Python SDK" />
|
||||
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/AnsibleTemplate.iml" filepath="$PROJECT_DIR$/.idea/AnsibleTemplate.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
62
Dockerfile
Normal file
62
Dockerfile
Normal file
@ -0,0 +1,62 @@
|
||||
# Используем более легкий базовый образ
|
||||
FROM python:3.12.9-slim-bullseye
|
||||
|
||||
# Добавляем метаданные
|
||||
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
|
||||
|
||||
# Устанавливаем системные зависимости
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
ssh \
|
||||
gcc \
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
make \
|
||||
sudo \
|
||||
sshpass \
|
||||
openssh-client \
|
||||
nano \
|
||||
less \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
diffutils \
|
||||
jq \
|
||||
lsb-release \
|
||||
&& mkdir -p /etc/apt/keyrings \
|
||||
&& curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bullseye stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
|
||||
&& apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
docker-ce-cli \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Устанавливаем зависимости Python для Ansible и Molecule
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install \
|
||||
ansible \
|
||||
ansible-lint \
|
||||
ansible-vault \
|
||||
molecule \
|
||||
molecule-docker \
|
||||
molecule-plugins \
|
||||
ansible-compat \
|
||||
docker
|
||||
|
||||
# Копируем ssh ключ
|
||||
#COPY id_rsa /root/.ssh/id_rsa
|
||||
#RUN chmod 600 /root/.ssh/id_rsa
|
||||
|
||||
# Устанавливаем рабочую директорию
|
||||
WORKDIR /ansible
|
||||
|
||||
# Команда по умолчанию
|
||||
CMD ["/bin/bash"]
|
22
Dockerfile-CentOS
Normal file
22
Dockerfile-CentOS
Normal file
@ -0,0 +1,22 @@
|
||||
# Сборка контейнера с systemd для удобного тестирования ролей Ansible через Molecule
|
||||
|
||||
# Используем официальный образ Fedora
|
||||
FROM quay.io/fedora/python-312
|
||||
|
||||
USER root
|
||||
|
||||
# Обновляем пакеты и устанавливаем systemd
|
||||
RUN dnf -y update && \
|
||||
dnf -y install systemd rsync diffutils jq && \
|
||||
dnf clean all && \
|
||||
rm -rf /var/cache/dnf /tmp/* /var/tmp/*
|
||||
|
||||
# Настраиваем окружение для systemd
|
||||
ENV container=docker
|
||||
STOPSIGNAL SIGRTMIN+3
|
||||
|
||||
# Создаем необходимые директории для systemd
|
||||
VOLUME [ "/sys/fs/cgroup" ]
|
||||
|
||||
# Запускаем systemd при старте контейнера
|
||||
CMD ["/sbin/init"]
|
20
Dockerfile-Ubuntu
Normal file
20
Dockerfile-Ubuntu
Normal file
@ -0,0 +1,20 @@
|
||||
# Сборка контейнера с systemd для удобного тестирования ролей Ansible через Molecule
|
||||
|
||||
# Используем официальный образ Ubuntu 20.04
|
||||
FROM geerlingguy/docker-ubuntu2004-ansible:latest
|
||||
|
||||
# Обновляем пакеты и устанавливаем systemd
|
||||
RUN apt-get update && \
|
||||
apt-get install -y systemd systemd-sysv rsync diffutils jq && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Указываем, что контейнер использует systemd в качестве init-системы
|
||||
ENV container=docker
|
||||
STOPSIGNAL SIGRTMIN+3
|
||||
|
||||
# Создаем необходимые директории для systemd
|
||||
VOLUME [ "/sys/fs/cgroup" ]
|
||||
|
||||
# Запускаем systemd при старте контейнера
|
||||
CMD ["/sbin/init"]
|
151
Makefile
Normal file
151
Makefile
Normal file
@ -0,0 +1,151 @@
|
||||
# Глобальные переменные
|
||||
IMAGE ?= ansible
|
||||
TAG ?= 0.1
|
||||
REGISTRY ?= hub.antropoff.ru/ansible
|
||||
# По умолчанию используем docker. Для локальной разработки используйте docker-compose
|
||||
RUN_MODE ?= docker
|
||||
|
||||
# Определение команды RUN в зависимости от RUN_MODE
|
||||
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-образа..."
|
||||
@make docker build
|
||||
@echo "Шаг 2: Создание Docker-образов для запуска Molecule..."
|
||||
@make docker images
|
||||
@echo "Шаг 3: Создание нового vault-файла с паролем..."
|
||||
@read -p "Введите пароль для vault: " VAULT_PASSWORD; \
|
||||
echo "$$VAULT_PASSWORD" > vault-password.txt; \
|
||||
make vault create
|
||||
@echo "Шаг 4: Создание нового брэнча в гите..."
|
||||
@make git new
|
||||
@echo "Шаг 5: Создание новой роли..."
|
||||
@make role new
|
||||
|
||||
####################################################################################################
|
||||
# Управление контейнерами с помощью docker compose или docker run
|
||||
####################################################################################################
|
||||
docker:
|
||||
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||
build) \
|
||||
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; \
|
||||
echo "Entering to Ansible container shell..."; \
|
||||
$(RUN) bash ;; \
|
||||
release) \
|
||||
docker buildx create --use --name multiarch-builder --driver docker-container; \
|
||||
docker login $(REGISTRY); \
|
||||
docker buildx build -t $(REGISTRY)/$(IMAGE):$(TAG) -t $(REGISTRY)/$(IMAGE):latest --platform linux/amd64,linux/arm64 --push .;; \
|
||||
images) \
|
||||
docker buildx create --use --name multiarch-builder --driver docker-container; \
|
||||
docker login $(REGISTRY); \
|
||||
docker buildx build -t $(REGISTRY)/centos:latest --platform linux/amd64,linux/arm64 --push -f Dockerfile-CentOS .; \
|
||||
docker buildx build -t $(REGISTRY)/ubuntu:latest --platform linux/amd64,linux/arm64 --push -f Dockerfile-Ubuntu .;; \
|
||||
*) echo "Unknown action. Available actions: build, rebuild, prune, release";; \
|
||||
esac
|
||||
|
||||
####################################################################################################
|
||||
# Работа с ролью
|
||||
####################################################################################################
|
||||
vault:
|
||||
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||
show) $(RUN) bash -c "ansible-vault view --vault-password-file vault-password.txt vars/secrets.yml";; \
|
||||
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; \
|
||||
echo "Введите название новой роли на английском:"; \
|
||||
read ROLE_NAME; \
|
||||
echo "Введите описание роли:"; \
|
||||
read ROLE_DESC; \
|
||||
cp -r default/ "roles/$${ROLE_NAME}"; \
|
||||
printf "\n- name: $${ROLE_DESC}" >> roles/deploy.yaml; \
|
||||
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; \
|
||||
echo "Check your role..."; \
|
||||
$(RUN) bash -c "ansible-vault decrypt --vault-password-file vault-password.txt vars/secrets.yml"; \
|
||||
$(RUN) bash -c "ansible-lint roles/*"; \
|
||||
$(RUN) bash -c "ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file vault-password.txt";; \
|
||||
test) \
|
||||
clear; \
|
||||
echo "Running test roles..."; \
|
||||
$(RUN) bash -c "ansible-vault decrypt --vault-password-file vault-password.txt vars/secrets.yml"; \
|
||||
$(RUN) bash -c "molecule test --parallel --destroy=always"; \
|
||||
$(RUN) bash -c "ansible-vault encrypt vars/secrets.yml --encrypt-vault-id default --vault-password-file vault-password.txt";; \
|
||||
deploy) \
|
||||
clear; \
|
||||
echo "Deploying roles to production..."; \
|
||||
$(RUN) bash -c "ansible-playbook roles/deploy.yaml";; \
|
||||
*) echo "Unknown action";; \
|
||||
esac
|
||||
|
||||
####################################################################################################
|
||||
# Работа с Git
|
||||
####################################################################################################
|
||||
git:
|
||||
@case "$(word 2, $(MAKECMDGOALS))" in \
|
||||
push) \
|
||||
git branch; \
|
||||
read -p "Выберите ветку для пуша: " BRANCH; \
|
||||
read -p "Введите описание коммита: " COMMIT; \
|
||||
commitname=$$COMMIT; \
|
||||
git add . ; \
|
||||
git commit -m "$$commitname"; \
|
||||
git push -u origin $$BRANCH; \
|
||||
echo "Изменения внесены в Git";; \
|
||||
pull) \
|
||||
git pull;; \
|
||||
new) \
|
||||
read -p "Введите имя новой ветки: " BRANCH_NAME; \
|
||||
NEW_BRANCH="$$BRANCH_NAME"; \
|
||||
git checkout -b $$NEW_BRANCH; \
|
||||
echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \
|
||||
*) echo "Unknown action. Available actions: push, pull, cluster-branch";; \
|
||||
esac
|
109
README.md
Normal file
109
README.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Patroni GitOps Config Manager
|
||||
|
||||
### Назначение роли
|
||||
Эта роль предназначена для безопасного управления конфигурацией кластера Patroni. Она предоставляет следующие возможности:
|
||||
|
||||
- Создание резервных копий текущей конфигурации с timestamp
|
||||
- Валидация изменений конфигурации перед применением
|
||||
- Наглядное отображение различий между текущей и новой конфигурацией
|
||||
- Безопасное применение изменений через REST API Patroni
|
||||
- Проверка состояния кластера после изменений
|
||||
- Уведомление о необходимости перезагрузки нод (если требуется)
|
||||
- Автоматическое управление историей конфигурационных файлов
|
||||
|
||||
### Требования
|
||||
- Ansible 2.9+
|
||||
- Доступ к Patroni REST API (порт 8008)
|
||||
- Python 3 для валидации YAML
|
||||
- Утилита diff с поддержкой цветного вывода
|
||||
|
||||
### Переменные роли
|
||||
Основные переменные, которые можно переопределить:
|
||||
|
||||
- config_dir (по умолчанию: "/ansible/history") - директория для хранения истории конфигураций
|
||||
- config_file (по умолчанию: "/ansible/patroni_config.yaml") - путь к файлу с изменениями конфигурации
|
||||
- patroni_host (по умолчанию: "10.14.0.180") - хост кластера Patroni
|
||||
|
||||
### Как внести изменения в конфиг кластера?
|
||||
1. Для начала создайте новый branch по имени кластера.
|
||||
```bash
|
||||
# Переключиться на основную ветку (если нужно)
|
||||
git checkout main
|
||||
|
||||
# Получить последние изменения
|
||||
make git pull
|
||||
|
||||
# Создать новую ветку
|
||||
make git new
|
||||
```
|
||||
2. Сбилдите образ Ansible для локальной работы, тестов и линта.
|
||||
```bash
|
||||
make docker build
|
||||
```
|
||||
3. Внесите изменения в файл **patroni_config.yaml**. Все изменения буду добавлены в конфиг кластера.
|
||||
Если такие переменные в конфиге существуют, они будут заменены. Если переменных нет, будут добавлены.
|
||||
|
||||
```yaml
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: 100
|
||||
shared_buffers: "1GB"
|
||||
use_pg_rewind: true
|
||||
```
|
||||
|
||||
3. Линт, тесты и пуш в гит
|
||||
```
|
||||
# Внести изменения в secret.yaml
|
||||
make vault edit
|
||||
|
||||
# Проверить роль
|
||||
make role lint
|
||||
|
||||
# Протестировать роль
|
||||
make role test
|
||||
|
||||
# Пушим в гит...
|
||||
make git push
|
||||
```
|
||||
|
||||
4. Если у вас GitLab - смотрим и наслаждаемся. Не забудьте нажать кнопочку в
|
||||
GitLab на deploy изменений конфига в кластере. Ну, или закомментируйте в gitlab-ci.yml строку
|
||||
```gitlab
|
||||
when: manual
|
||||
```
|
||||
что бы можно было деплоить автоматом...
|
||||
|
||||
### Анализ результатов
|
||||
После выполнения этапа подготовки:
|
||||
|
||||
- Будет показан цветной diff изменений
|
||||
- В директории config_dir появятся:
|
||||
- Резервная копия текущей конфигурации с timestamp
|
||||
- Файл last.yaml с новой конфигурацией
|
||||
|
||||
После выполнения этапа применения:
|
||||
|
||||
- Будет выведен статус кластера
|
||||
- Появится предупреждение, если требуется перезагрузка нод
|
||||
- Старые конфигурации будут автоматически удалены (остаются последние 10)
|
||||
|
||||
### Особенности работы
|
||||
Безопасность:
|
||||
- Конфигурация всегда сохраняется перед изменениями
|
||||
- Изменения проверяются на валидность перед применением
|
||||
- Показывается подробный diff перед применением
|
||||
|
||||
Информирование:
|
||||
- Явное предупреждение о необходимости перезагрузки
|
||||
- Подробный вывод статуса кластера после изменений
|
||||
- Сохранение истории изменений
|
||||
|
||||
Автоматизация:
|
||||
- Управление историей конфигураций (автоочистка старых файлов)
|
||||
- Проверка состояния кластера после изменений
|
||||
|
||||
### Важные примечания
|
||||
- Роль не выполняет автоматическую перезагрузку нод Patroni, так как это потенциально опасная операция
|
||||
- При необходимости перезагрузки будет показана команда для ручного выполнения
|
||||
- Все изменения конфигурации сохраняются с timestamp для возможности отката
|
||||
- Для работы требуется доступ к API Patroni (порт 8008)
|
11
ansible.cfg
Normal file
11
ansible.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
[defaults]
|
||||
inventory = inventory/hosts
|
||||
vault_password_file = vault-password.txt
|
||||
remote_user = devops
|
||||
host_key_checking = False
|
||||
enable_plugins = yaml, ini
|
||||
roles_path = roles/
|
||||
interpreter_python = auto
|
||||
stdout_callback = yaml
|
||||
bin_ansible_callbacks = True
|
||||
force_color = 1
|
0
default/defaults/.gitkeep
Normal file
0
default/defaults/.gitkeep
Normal file
10
default/deploy.yaml
Normal file
10
default/deploy.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Deploy roles
|
||||
hosts: all
|
||||
become: true
|
||||
become_user: root
|
||||
become_method: ansible.builtin.sudo
|
||||
gather_facts: true
|
||||
vars_files:
|
||||
- ../../vars/secrets.yml
|
||||
roles:
|
0
default/files/.gitkeep
Normal file
0
default/files/.gitkeep
Normal file
0
default/handlers/.gitkeep
Normal file
0
default/handlers/.gitkeep
Normal file
0
default/meta/.gitkeep
Normal file
0
default/meta/.gitkeep
Normal file
4
default/tasks/debian/main.yaml
Normal file
4
default/tasks/debian/main.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: Пример таски
|
||||
debug:
|
||||
msg: "Привет! Я запустился на Debian/Ubuntu!"
|
12
default/tasks/main.yaml
Normal file
12
default/tasks/main.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- 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"
|
4
default/tasks/redhat/main.yaml
Normal file
4
default/tasks/redhat/main.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: Пример таски
|
||||
debug:
|
||||
msg: "Привет! Я запустился на RedHat/CentOS/Fedora!"
|
0
default/templates/.gitkeep
Normal file
0
default/templates/.gitkeep
Normal file
0
default/tests/.gitkeep
Normal file
0
default/tests/.gitkeep
Normal file
15
docker-compose.yaml
Normal file
15
docker-compose.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
services:
|
||||
ansible:
|
||||
build:
|
||||
context: .
|
||||
ssh:
|
||||
- default
|
||||
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
|
8
gitlab/config.json
Normal file
8
gitlab/config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"auths": {
|
||||
"hub.cism-ms.ru": {
|
||||
"username": "your-username",
|
||||
"password": "your-password"
|
||||
}
|
||||
}
|
||||
}
|
33
gitlab/docker-compose.yaml
Normal file
33
gitlab/docker-compose.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
services:
|
||||
gitlab-runner:
|
||||
image: gitlab/gitlab-runner:latest
|
||||
container_name: gitlab-runner
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./runner:/etc/gitlab-runner
|
||||
- ./config.json:/root/.docker/config.json
|
||||
environment:
|
||||
- CI_SERVER_URL=https://gitlab.com # Замените на URL вашего GitLab
|
||||
- REGISTRATION_TOKEN=your-registration-token # Замените на ваш токен регистрации
|
||||
- RUNNER_NAME=ansible-runner # Имя раннера
|
||||
- RUNNER_EXECUTOR=docker # Используем Docker как исполнитель
|
||||
- DOCKER_IMAGE=hub.cism-ms.ru/ansible/ansible:latest # Образ Docker по умолчанию
|
||||
- RUNNER_TAGS=docker,linux,ansible # Теги для раннера
|
||||
- DOCKER_PRIVILEGED=true # Привилегированный режим
|
||||
- DOCKER_TLS_VERIFY=false # Отключить проверку TLS
|
||||
- DOCKER_TTY=false # Отключить TTY
|
||||
command: >
|
||||
register --non-interactive
|
||||
--executor ${RUNNER_EXECUTOR}
|
||||
--docker-image ${DOCKER_IMAGE}
|
||||
--url ${CI_SERVER_URL}
|
||||
--registration-token ${REGISTRATION_TOKEN}
|
||||
--description ${RUNNER_NAME}
|
||||
--tag-list ${RUNNER_TAGS}
|
||||
--run-untagged="true"
|
||||
--locked="false"
|
||||
--docker-privileged=${DOCKER_PRIVILEGED}
|
||||
--docker-tlsverify=${DOCKER_TLS_VERIFY}
|
||||
--docker-disable-entrypoint-overwrite="false"
|
||||
--docker-tty=${DOCKER_TTY}
|
31
gitlab/runner/config.toml
Normal file
31
gitlab/runner/config.toml
Normal file
@ -0,0 +1,31 @@
|
||||
concurrent = 1
|
||||
check_interval = 0
|
||||
shutdown_timeout = 0
|
||||
|
||||
[session_server]
|
||||
session_timeout = 1800
|
||||
|
||||
[[runners]]
|
||||
name = "faa4e539628d"
|
||||
url = "http://git.cism-ms.ru"
|
||||
id = 115
|
||||
token = "glrt-t3_5MD8hLs_69nChDbwXHLM"
|
||||
token_obtained_at = 2025-03-17T15:35:01Z
|
||||
token_expires_at = 0001-01-01T00:00:00Z
|
||||
executor = "docker"
|
||||
[runners.cache]
|
||||
MaxUploadedArchiveSize = 0
|
||||
[runners.cache.s3]
|
||||
[runners.cache.gcs]
|
||||
[runners.cache.azure]
|
||||
[runners.docker]
|
||||
tty = false
|
||||
tls_verify = false
|
||||
image = "hub.cism-ms.ru/ansible/ansible:latest"
|
||||
privileged = true
|
||||
disable_entrypoint_overwrite = false
|
||||
oom_kill_disable = false
|
||||
disable_cache = false
|
||||
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
|
||||
shm_size = 0
|
||||
network_mtu = 0
|
0
history/.gitkeep
Normal file
0
history/.gitkeep
Normal file
0
inventory/.gitkeep
Normal file
0
inventory/.gitkeep
Normal file
1
inventory/hosts
Normal file
1
inventory/hosts
Normal file
@ -0,0 +1 @@
|
||||
[all]
|
7
molecule/default/converge.yml
Normal file
7
molecule/default/converge.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
vars_files:
|
||||
- ../../vars/secrets.yml
|
||||
roles:
|
||||
- ../../roles/prepare
|
8
molecule/default/destroy.yml
Normal file
8
molecule/default/destroy.yml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Destroy containers on interrupt
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: Ensure containers are destroyed
|
||||
docker_container:
|
||||
name: "{{ item.name }}"
|
||||
state: absent
|
||||
loop: "{{ molecule_yml.platforms }}"
|
60
molecule/default/molecule.yml
Normal file
60
molecule/default/molecule.yml
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
enabled: true
|
||||
options:
|
||||
requirements-file: requirements.yml
|
||||
|
||||
driver:
|
||||
name: docker
|
||||
|
||||
platforms:
|
||||
# - name: centos
|
||||
# image: "hub.antropoff.ru/ansible/centos:latest"
|
||||
# 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: "hub.antropoff.ru/ansible/ubuntu:latest"
|
||||
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
|
43
molecule/default/no-prepare.yml
Normal file
43
molecule/default/no-prepare.yml
Normal file
@ -0,0 +1,43 @@
|
||||
- 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
|
7
molecule/default/no-verify.yml
Normal file
7
molecule/default/no-verify.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Prepare
|
||||
hosts: all
|
||||
tasks:
|
||||
- name: Reun verify
|
||||
debug:
|
||||
msg: "Hello, Verify!"
|
5
patroni_config.yaml
Normal file
5
patroni_config.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: 300
|
||||
shared_buffers: "12GB"
|
||||
use_pg_rewind: true
|
4
requirements.yml
Normal file
4
requirements.yml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
collections:
|
||||
- name: maxhoesel.proxmox
|
||||
version: 5.0.1
|
0
roles/apply/defaults/.gitkeep
Normal file
0
roles/apply/defaults/.gitkeep
Normal file
11
roles/apply/deploy.yaml
Normal file
11
roles/apply/deploy.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Deploy roles
|
||||
hosts: localhost
|
||||
become: true
|
||||
become_user: root
|
||||
become_method: ansible.builtin.sudo
|
||||
gather_facts: true
|
||||
vars_files:
|
||||
- ../../vars/secrets.yml
|
||||
roles:
|
||||
- apply
|
0
roles/apply/files/.gitkeep
Normal file
0
roles/apply/files/.gitkeep
Normal file
0
roles/apply/handlers/.gitkeep
Normal file
0
roles/apply/handlers/.gitkeep
Normal file
4
roles/apply/handlers/main.yaml
Normal file
4
roles/apply/handlers/main.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: Log cleanup results
|
||||
ansible.builtin.debug:
|
||||
msg: "Removed {{ (old_configs.files | sort(attribute='mtime'))[:-10] | length }} old config files"
|
0
roles/apply/meta/.gitkeep
Normal file
0
roles/apply/meta/.gitkeep
Normal file
3
roles/apply/tasks/main.yaml
Normal file
3
roles/apply/tasks/main.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
- name: "Подключаем таски"
|
||||
include_tasks: "role/main.yaml"
|
130
roles/apply/tasks/role/main.yaml
Normal file
130
roles/apply/tasks/role/main.yaml
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
- name: Apply new configuration
|
||||
ansible.builtin.uri:
|
||||
url: "http://{{ patroni_host }}:8008/config"
|
||||
method: PATCH
|
||||
body: "{{ new_config | to_json }}"
|
||||
body_format: json
|
||||
status_code: 200
|
||||
headers:
|
||||
Content-Type: "application/json"
|
||||
register: apply_result
|
||||
changed_when: apply_result.status == 200
|
||||
|
||||
- name: Force wait for config to apply # noqa: no-handler
|
||||
ansible.builtin.wait_for:
|
||||
timeout: 30
|
||||
delay: 5
|
||||
when: apply_result is changed
|
||||
|
||||
- name: Get verified cluster status # noqa: no-handler
|
||||
ansible.builtin.uri:
|
||||
url: "http://{{ patroni_host }}:8008/cluster"
|
||||
method: GET
|
||||
return_content: yes
|
||||
status_code: 200
|
||||
register: verified_cluster_status
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
when: apply_result is changed
|
||||
|
||||
- name: Display confirmed cluster status
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
=== CONFIRMED CLUSTER STATUS ===
|
||||
Leader: {{ (verified_cluster_status.json.members | selectattr('role', 'equalto', 'leader') | map(attribute='name') | first) | default('UNKNOWN') }}
|
||||
Members:
|
||||
{% for member in verified_cluster_status.json.members %}
|
||||
- {{ member.name }} [{{ member.role | upper }}]
|
||||
State: {{ member.state | default('UNKNOWN') }}
|
||||
Lag: {{ member.lag | default(0) }}MB
|
||||
Timeline: {{ member.timeline | default('N/A') }}
|
||||
Pending restart: {{ member.pending_restart | default(false) | ternary('YES', 'NO') }}
|
||||
{% endfor %}
|
||||
Config Applied: {{ apply_result is changed | ternary('YES', 'NO') }}
|
||||
================================
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
run_once: true
|
||||
|
||||
- name: Refresh cluster status
|
||||
ansible.builtin.uri:
|
||||
url: "http://{{ patroni_host }}:8008/cluster"
|
||||
method: GET
|
||||
return_content: yes
|
||||
status_code: 200
|
||||
register: refreshed_cluster_status
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
when: verified_cluster_status is defined
|
||||
|
||||
- name: Safe check for pending restarts
|
||||
ansible.builtin.set_fact:
|
||||
needs_restart: >-
|
||||
{{
|
||||
(refreshed_cluster_status.json.members |
|
||||
map(attribute='pending_restart', default=false) |
|
||||
select('equalto', true) | list | count > 0) or
|
||||
(refreshed_cluster_status.json.members |
|
||||
map(attribute='tags.pending_restart', default=false) |
|
||||
select('equalto', true) | list | count > 0)
|
||||
}}
|
||||
node_names: >-
|
||||
{{
|
||||
refreshed_cluster_status.json.members |
|
||||
map(attribute='name') |
|
||||
list
|
||||
}}
|
||||
when:
|
||||
- refreshed_cluster_status.json is defined
|
||||
- refreshed_cluster_status.json.members is defined
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Show restart warning if needed
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
{% if needs_restart %}
|
||||
==================================
|
||||
ВНИМАНИЕ: ТРЕБУЕТСЯ ПЕРЕЗАГРУЗКА
|
||||
==================================
|
||||
|
||||
Не, я конечно могу и сам ролью, но вдруг кластер в проде или еще где!!!
|
||||
Так что лучше выполнить следующую команду на одной из нод кластера:
|
||||
|
||||
patronictl restart {{ node_names | join(' ') }}
|
||||
|
||||
Затронутые ноды:
|
||||
{% for node in node_names %}
|
||||
- {{ node }}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
==================================
|
||||
СТАТУС: Перезагрузка не требуется
|
||||
==================================
|
||||
{% endif %}
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
|
||||
- name: Archive old configurations
|
||||
block:
|
||||
- name: Find old config files
|
||||
ansible.builtin.find:
|
||||
path: "{{ config_dir }}"
|
||||
pattern: "*-config.yaml"
|
||||
age: "10s"
|
||||
register: old_configs
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
|
||||
- name: Remove excess configs (keep last 10)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ (old_configs.files | sort(attribute='mtime'))[:-10] }}"
|
||||
when:
|
||||
- old_configs.matched > 10
|
||||
- apply_result is changed
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
notify: Log cleanup results
|
0
roles/apply/templates/.gitkeep
Normal file
0
roles/apply/templates/.gitkeep
Normal file
0
roles/apply/tests/.gitkeep
Normal file
0
roles/apply/tests/.gitkeep
Normal file
5
roles/deploy.yaml
Normal file
5
roles/deploy.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Подготовка ро<D180>к изменению конфнастроек кла<D0BB>стера
|
||||
import_playbook: prepare/deploy.yaml
|
||||
- name: Применение изменений нас<D0B0>троек кластера
|
||||
import_playbook: apply/deploy.yaml
|
0
roles/prepare/defaults/.gitkeep
Normal file
0
roles/prepare/defaults/.gitkeep
Normal file
11
roles/prepare/deploy.yaml
Normal file
11
roles/prepare/deploy.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Deploy roles
|
||||
hosts: localhost
|
||||
become: true
|
||||
become_user: root
|
||||
become_method: ansible.builtin.sudo
|
||||
gather_facts: true
|
||||
vars_files:
|
||||
- ../../vars/secrets.yml
|
||||
roles:
|
||||
- prepare
|
0
roles/prepare/files/.gitkeep
Normal file
0
roles/prepare/files/.gitkeep
Normal file
0
roles/prepare/handlers/.gitkeep
Normal file
0
roles/prepare/handlers/.gitkeep
Normal file
0
roles/prepare/meta/.gitkeep
Normal file
0
roles/prepare/meta/.gitkeep
Normal file
15
roles/prepare/tasks/main.yaml
Normal file
15
roles/prepare/tasks/main.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
# - 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"
|
||||
|
||||
- name: "Подключаем таски"
|
||||
include_tasks: "role/main.yaml"
|
128
roles/prepare/tasks/role/main.yaml
Normal file
128
roles/prepare/tasks/role/main.yaml
Normal file
@ -0,0 +1,128 @@
|
||||
---
|
||||
- name: Validate config file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ config_file }}"
|
||||
register: config_check
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
|
||||
- name: Fail if config missing
|
||||
ansible.builtin.fail:
|
||||
msg: "Configuration file {{ config_file }} not found!"
|
||||
when: not config_check.stat.exists
|
||||
|
||||
- name: Ensure history directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ config_dir }}"
|
||||
state: directory
|
||||
mode: '0750'
|
||||
owner: "{{ ansible_user_id | default(omit) }}"
|
||||
group: "{{ ansible_user_gid | default(omit) }}"
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
changed_when: false
|
||||
|
||||
- name: Get current Patroni configuration
|
||||
ansible.builtin.uri:
|
||||
url: "http://{{ patroni_host }}:8008/config"
|
||||
method: GET
|
||||
return_content: yes
|
||||
status_code: 200
|
||||
register: patroni_config
|
||||
changed_when: false
|
||||
|
||||
- name: Save current config with timestamp
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
# Original config from {{ ansible_date_time.iso8601 }}
|
||||
{{ patroni_config.content | from_json | to_nice_yaml }}
|
||||
dest: "{{ config_dir }}/{{ ansible_date_time.iso8601 | replace('T', '_') | replace(':', '-') | replace('+', '-UTC') }}-config.yaml"
|
||||
mode: '0640'
|
||||
owner: "{{ ansible_user_id | default(omit) }}"
|
||||
group: "{{ ansible_user_gid | default(omit) }}"
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
changed_when: false
|
||||
|
||||
- name: Load configuration changes
|
||||
ansible.builtin.include_vars:
|
||||
file: "{{ config_file }}"
|
||||
name: config_changes
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
|
||||
- name: Create merged configuration
|
||||
ansible.builtin.set_fact:
|
||||
new_config: "{{ patroni_config.content | from_json | combine(config_changes, recursive=True) }}"
|
||||
|
||||
- name: Validate configuration changes
|
||||
ansible.builtin.command:
|
||||
cmd: |
|
||||
python3 -c '
|
||||
import yaml, json, sys;
|
||||
try:
|
||||
yaml.safe_load(sys.stdin.read())
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"Invalid YAML: {str(e)}")
|
||||
sys.exit(1)'
|
||||
stdin: "{{ new_config | to_nice_yaml }}"
|
||||
register: config_validation
|
||||
changed_when: false
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
|
||||
- name: Save new config as last.yaml
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
# Updated at {{ ansible_date_time.iso8601 }}
|
||||
{{ new_config | to_nice_yaml }}
|
||||
dest: "{{ config_dir }}/last.yaml"
|
||||
mode: '0640'
|
||||
owner: "{{ ansible_user_id | default(omit) }}"
|
||||
group: "{{ ansible_user_gid | default(omit) }}"
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
changed_when: false
|
||||
|
||||
- name: Generate and display colored diff
|
||||
block:
|
||||
- name: Create temp files for diff
|
||||
ansible.builtin.shell: |
|
||||
cat > /tmp/old_config.yml << 'EOL'
|
||||
{{ patroni_config.content | from_json | to_nice_yaml }}
|
||||
EOL
|
||||
cat > /tmp/new_config.yml << 'EOL'
|
||||
{{ new_config | to_nice_yaml }}
|
||||
EOL
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
changed_when: false
|
||||
|
||||
- name: Execute diff with colors
|
||||
ansible.builtin.command: >
|
||||
diff --color=always -u /tmp/old_config.yml /tmp/new_config.yml
|
||||
register: diff_result
|
||||
changed_when: diff_result.rc in [0,1]
|
||||
failed_when: diff_result.rc > 1
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
|
||||
- name: Cleanup temp files
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- old_config.yml
|
||||
- new_config.yml
|
||||
delegate_to: localhost
|
||||
connection: local
|
||||
changed_when: false
|
||||
|
||||
- name: Display diff line by line
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ item }}"
|
||||
loop: "{{ diff_result.stdout_lines }}"
|
||||
when: diff_result.stdout_lines | length > 0
|
||||
loop_control:
|
||||
label: ""
|
0
roles/prepare/templates/.gitkeep
Normal file
0
roles/prepare/templates/.gitkeep
Normal file
0
roles/prepare/tests/.gitkeep
Normal file
0
roles/prepare/tests/.gitkeep
Normal file
0
vars/.gitkeep
Normal file
0
vars/.gitkeep
Normal file
10
vars/secrets.yml
Normal file
10
vars/secrets.yml
Normal file
@ -0,0 +1,10 @@
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
37376136623761343135636239653137353661303631663536613265366431333339663866643265
|
||||
3033653765613632313661393166363238643137346330620a643233623433633963333035646466
|
||||
34633366623262643165326331333937623064356131306663623362663663343861383735616365
|
||||
3363646132393166310a353965346531616330396666383732656430633630323438326161323965
|
||||
64323865636265303331663166393232376138663965613361623361303663353737623238373435
|
||||
30316161616234356264643762653036626132613664316137646665323335663232393535353131
|
||||
37636331646364313839653438323461353638363936623131626161353936303839393533326162
|
||||
31623833313834646233303961656633633933386135396439373463623362316561313138643631
|
||||
6663
|
1
vault-password.txt
Normal file
1
vault-password.txt
Normal file
@ -0,0 +1 @@
|
||||
password123
|
Loading…
x
Reference in New Issue
Block a user