Files
UpdateHaproxyConfig/readme.md
T
2026-10-13 11:05:58 +00:00

576 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Документация проекта: Развертывание резервной конфигурации HAProxy по требованию
## 1. Архитектурное описание
Комплекс предназначен для контролируемого применения резервной конфигурации балансировщика **HAProxy** по клику оператора с рабочей станции Windows.
```
[ Рабочая станция Windows ]
|
(Ярлык на рабочем столе)
|
deployfile.exe (.NET 4.8)
|
| (1. SSH connection: ed25519)
| (2. Генерация status/change.me + status/sha256.hash)
v
[ Linux-сервер (HAProxy) ]
/var/lib/depfile/ (Домашний каталог depfile)
├── .ssh/authorized_keys
├── status/
│ ├── change.me
│ └── sha256.hash
└── deployment/
└── success.me
|
(cron: раз в минуту)
v
deploy_haproxy.sh (Bash)
|-- 1. Валидация SHA-256 суммы файла change.me
|-- 2. Валидация синтаксиса backup/haproxy.cfg (haproxy -c)
|-- 3. Подмена /etc/haproxy/haproxy.cfg
|-- 4. Перезапуск службы haproxy (systemctl restart)
|-- 5. Удаление каталога status/
`-- 6. Запись метки в deployment/success.me
```
### Принцип работы
1. **Windows-клиент (`deployfile.exe`):**
* Разработан на .NET Framework 4.8 без внешних библиотек.
* Вызывает встроенный клиент Windows OpenSSH (`ssh.exe`) с автоопределением путей (включая обход WOW64-редиректа `Sysnative` для 32-битных процессов) и автоматической установкой OpenSSH через PowerShell при его отсутствии.
* Генерирует случайный текстовый блок, подключается к серверу по SSH под пользователем `depfile` с использованием ED25519-ключа и записывает данные в `/var/lib/depfile/status/change.me`.
* На стороне сервера генерирует файл контрольной суммы `/var/lib/depfile/status/sha256.hash`.
2. **Серверный обработчик (`deploy_haproxy.sh`):**
* Выполняется по cron от имени `root`.
* Проверяет наличие файла `/var/lib/depfile/status/change.me`. Если файл отсутствует — завершает работу без нагрузки на систему.
* Валидирует целостность `change.me` через `sha256sum -c`.
* Выполняет проверку синтаксиса резервного файла (`haproxy -c -f /etc/haproxy/backup/haproxy.cfg`). При обнаружении ошибок применение отменяется.
* Копирует резервную конфигурацию в боевой файл `/etc/haproxy/haproxy.cfg`.
* Перезапускает службу `haproxy`.
* Удаляет временную директорию `/var/lib/depfile/status`.
* Фиксирует статус и метку времени в файле `/var/lib/depfile/deployment/success.me`.
---
## 2. Настройка Linux-сервера
### 2.1. Создание сервисного пользователя и директорий
Подключитесь к серверу с правами `root` или через `sudo`:
```bash
# 1. Создаем системного пользователя с домашним каталогом /var/lib/depfile
sudo useradd -m -d /var/lib/depfile -s /bin/bash -c "File deploment service" depfile
# 2. Выставляем права доступа на домашнюю директорию
sudo chown -R depfile:depfile /var/lib/depfile
sudo chmod 750 /var/lib/depfile
# 3. Создаем каталог резервных конфигураций HAProxy и копируем текущий рабочий конфиг
sudo mkdir -p /etc/haproxy/backup
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/backup/haproxy.cfg
sudo chown -R root:root /etc/haproxy/backup
sudo chmod 750 /etc/haproxy/backup
```
### 2.2. Подготовка каталога SSH-ключей
Так как домашний каталог пользователя расположен в `/var/lib/depfile`, файл доверенных ключей находится по пути `/var/lib/depfile/.ssh/authorized_keys`:
```bash
sudo -u depfile mkdir -p /var/lib/depfile/.ssh
sudo -u depfile touch /var/lib/depfile/.ssh/authorized_keys
sudo chmod 700 /var/lib/depfile/.ssh
sudo chmod 600 /var/lib/depfile/.ssh/authorized_keys
```
### 2.3. Создание скрипта развертывания
Создайте файл `/usr/local/bin/deploy_haproxy.sh`:
```bash
sudo nano /usr/local/bin/deploy_haproxy.sh
```
Вставьте следующий код:
```bash
#!/usr/bin/env bash
set -Eeuo pipefail
# ================= КОНФИГУРАЦИЯ =================
BASE_DIR="/var/lib/depfile"
STATUS_DIR="${BASE_DIR}/status"
CHANGE_FILE="${STATUS_DIR}/change.me"
HASH_FILE="${STATUS_DIR}/sha256.hash"
DEPLOY_DIR="${BASE_DIR}/deployment"
SUCCESS_FILE="${DEPLOY_DIR}/success.me"
BACKUP_CFG="/etc/haproxy/backup/haproxy.cfg"
TARGET_CFG="/etc/haproxy/haproxy.cfg"
# ===============================================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
err() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ОШИБКА] $*" >&2
}
# 1. Проверяем наличие триггер-файла change.me
if [ ! -f "${CHANGE_FILE}" ]; then
exit 0
fi
log "Обнаружен файл ${CHANGE_FILE}. Запуск процедуры развертывания..."
# 2. Проверяем наличие файла с хешем
if [ ! -f "${HASH_FILE}" ]; then
err "Файл хеша ${HASH_FILE} не найден. Отмена операции."
exit 1
fi
# 3. Проверяем целостность файла change.me по sha256.hash
log "Проверка контрольной суммы SHA-256..."
if ! (cd "${STATUS_DIR}" && sha256sum -c --status "sha256.hash"); then
err "Несовпадение контрольной суммы файла ${CHANGE_FILE}!"
exit 2
fi
log "Контрольная сумма успешно подтверждена."
# 4. Проверяем наличие файла резервной копии конфигурации
if [ ! -f "${BACKUP_CFG}" ]; then
err "Файл резервной копии не найден: ${BACKUP_CFG}"
exit 3
fi
# 5. Валидация синтаксиса резервного конфига перед его применением
log "Валидация синтаксиса конфигурации из бэкапа..."
if ! haproxy -c -f "${BACKUP_CFG}" > /dev/null 2>&1; then
err "Резервная конфигурация содержит синтаксические ошибки. Отмена применения."
haproxy -c -f "${BACKUP_CFG}" || true
exit 4
fi
log "Синтаксис резервного конфига корректен."
# 6. Замена рабочей конфигурации
log "Копирование конфигурации в ${TARGET_CFG}..."
cp -f "${BACKUP_CFG}" "${TARGET_CFG}"
# Повторный контрольный тест целевого файла
if ! haproxy -c -f "${TARGET_CFG}" > /dev/null 2>&1; then
err "Критическая ошибка: целевой файл после копирования не прошел валидацию!"
exit 5
fi
# 7. Перезапуск службы haproxy
log "Перезапуск службы haproxy..."
if ! systemctl restart haproxy; then
err "Не удалось перезапустить сервис haproxy!"
exit 6
fi
log "Сервис haproxy успешно перезапущен."
# 8. Очистка каталога status
log "Удаление директории ${STATUS_DIR}..."
rm -rf "${STATUS_DIR}"
# 9. Формирование каталога deployment и файла success.me
mkdir -p "${DEPLOY_DIR}"
chown depfile:depfile "${DEPLOY_DIR}"
CURRENT_DATETIME=$(date '+%Y-%m-%d %H:%M:%S')
# Перезаписываем файл актуальной датой и временем
echo "deploment success {${CURRENT_DATETIME}}" > "${SUCCESS_FILE}"
chown depfile:depfile "${SUCCESS_FILE}"
log "Зафиксирован статус в ${SUCCESS_FILE}."
log "Процесс успешно завершен."
exit 0
```
Назначьте права на запуск:
```bash
sudo chmod +x /usr/local/bin/deploy_haproxy.sh
```
### 2.4. Настройка периодического запуска в cron
Откройте планировщик суперпользователя:
```bash
sudo crontab -e
```
Добавьте задачу запуска раз в минуту:
```cron
* * * * * /usr/local/bin/deploy_haproxy.sh >> /var/log/deploy_haproxy.log 2>&1
```
---
## 3. Настройка рабочей станции Windows
### 3.1. Генерация SSH-ключа
В командной строке (`cmd`) или PowerShell выполните:
```cmd
ssh-keygen -t ed25519 -f "D:\sys\C-Sharp\deployfile\key\test_key.key" -N ""
```
Создаются:
* `test_key.key` — закрытый ключ (хранится строго локально).
* `test_key.key.pub` — открытый ключ для передачи на сервер.
### 3.2. Добавление открытого ключа на сервер
Передайте содержимое открытого ключа в `authorized_keys` пользователя `depfile`:
```powershell
type "D:\sys\C-Sharp\deployfile\key\test_key.key.pub" | ssh depfile@10.228.48.110 "cat >> /var/lib/depfile/.ssh/authorized_keys"
```
*(При первом подключении введите пароль пользователя `depfile` на сервере).*
Проверьте беспарольный вход:
```cmd
ssh -i "D:\sys\C-Sharp\deployfile\key\test_key.key" depfile@10.228.48.110 "whoami && pwd"
```
*Вывод должен вернуть:*
```text
depfile
/var/lib/depfile
```
---
## 4. Клиентское приложение C# (.NET Framework 4.8)
### 4.1. Исходный код (`Program.cs`)
```csharp
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace SshStatusUpdater
{
internal class Program
{
// ================= НАСТРОЙКИ ПОДКЛЮЧЕНИЯ =================
private const string RemoteUser = "depfile"; // Логин пользователя на сервере
private const string RemoteHost = "10.228.48.110"; // IP-адрес или доменное имя сервера
private const int RemotePort = 22; // Порт SSH-сервера
// Путь к приватному ключу по умолчанию (если не задан аргументом)
private const string DefaultKeyPath = @"D:\sys\C-Sharp\deployfile\key\test_key.key";
// ========================================================
static int Main(string[] args)
{
// 1. Определение пути к ключу (аргумент командной строки либо константа по умолчанию)
string rawKeyPath = (args.Length > 0 && !string.IsNullOrWhiteSpace(args[0]))
? args[0].Trim('"')
: DefaultKeyPath;
string keyPath = Environment.ExpandEnvironmentVariables(rawKeyPath);
if (!File.Exists(keyPath))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Ошибка: Файл ключа не найден по указанному пути: {keyPath}");
Console.ResetColor();
return 2;
}
Console.WriteLine($"Используется ключ: {keyPath}");
// 2. Проверка доступности OpenSSH клиента (с возможностью автоустановки)
string sshExecutable = EnsureOpenSshAvailable();
if (string.IsNullOrEmpty(sshExecutable))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("\n[ОШИБКА] Клиент OpenSSH не найден в системе и не удалось выполнить его установку.");
Console.WriteLine("Для работы приложения необходимо установить компонент OpenSSH вручную.");
Console.WriteLine("Откройте PowerShell от имени Администратора и выполните:");
Console.WriteLine(" Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0");
Console.ResetColor();
return 4;
}
// 3. Формирование случайных данных и удаленной команды
string randomContent = GenerateRandomPayload(linesCount: 5, charsPerLine: 32);
// Так как домашний каталог /var/lib/depfile, команда выполняется относительно него
string remoteCommand = "mkdir -p status && cat > status/change.me && (cd status && sha256sum change.me > sha256.hash)";
string sshArgs = $"-i \"{keyPath}\" -p {RemotePort} -o BatchMode=yes -o StrictHostKeyChecking=accept-new {RemoteUser}@{RemoteHost} \"{remoteCommand}\"";
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = sshExecutable,
Arguments = sshArgs,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
Console.WriteLine($"Подключение к {RemoteUser}@{RemoteHost}:{RemotePort}...");
try
{
using (Process proc = new Process { StartInfo = psi })
{
proc.Start();
// Передаем сгенерированный текст в STDIN удаленного процесса ssh
using (StreamWriter sw = new StreamWriter(proc.StandardInput.BaseStream, new UTF8Encoding(false)))
{
sw.Write(randomContent);
}
string stdOut = proc.StandardOutput.ReadToEnd();
string stdErr = proc.StandardError.ReadToEnd();
proc.WaitForExit();
if (proc.ExitCode == 0)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Файл status/change.me и хеш status/sha256.hash успешно созданы на сервере.");
Console.ResetColor();
return 0;
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Ошибка выполнения SSH (Код: {proc.ExitCode}):");
if (!string.IsNullOrWhiteSpace(stdErr))
Console.WriteLine(stdErr.Trim());
if (!string.IsNullOrWhiteSpace(stdOut))
Console.WriteLine(stdOut.Trim());
Console.ResetColor();
return proc.ExitCode;
}
}
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Критическая ошибка выполнения SSH: {ex.Message}");
Console.ResetColor();
return 3;
}
}
private static string EnsureOpenSshAvailable()
{
string path = FindSshPath();
if (!string.IsNullOrEmpty(path))
return path;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Клиент OpenSSH не обнаружен. Попытка автоматической установки компонента Windows...");
Console.ResetColor();
bool installed = TryInstallOpenSsh();
if (installed)
{
path = FindSshPath();
if (!string.IsNullOrEmpty(path))
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Клиент OpenSSH успешно установлен.");
Console.ResetColor();
return path;
}
}
return null;
}
private static string FindSshPath()
{
string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
string[] candidates = new[]
{
Path.Combine(windir, "Sysnative", "OpenSSH", "ssh.exe"),
Path.Combine(windir, "System32", "OpenSSH", "ssh.exe"),
@"C:\Windows\System32\OpenSSH\ssh.exe"
};
foreach (var candidate in candidates)
{
if (File.Exists(candidate))
return candidate;
}
try
{
ProcessStartInfo testPsi = new ProcessStartInfo
{
FileName = "ssh",
Arguments = "-V",
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true
};
using (var testProc = Process.Start(testPsi))
{
testProc?.WaitForExit(1000);
return "ssh";
}
}
catch
{
}
return null;
}
private static string TryInstallOpenSsh()
{
try
{
ProcessStartInfo installPsi = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = "-NoProfile -ExecutionPolicy Bypass -Command \"Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0\"",
UseShellExecute = true,
Verb = "runas",
WindowStyle = ProcessWindowStyle.Normal
};
using (Process installProc = Process.Start(installPsi))
{
if (installProc == null)
return false;
installProc.WaitForExit();
return installProc.ExitCode == 0;
}
}
catch (Win32Exception)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Запрос прав Администратора был отклонён пользователем.");
Console.ResetColor();
return false;
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Не удалось запустить автоматическую установку: {ex.Message}");
Console.ResetColor();
return false;
}
}
private static string GenerateRandomPayload(int linesCount, int charsPerLine)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
Random rng = new Random();
for (int i = 0; i < linesCount; i++)
{
char[] buffer = new char[charsPerLine];
for (int j = 0; j < charsPerLine; j++)
{
buffer[j] = chars[rng.Next(chars.Length)];
}
sb.AppendLine(new string(buffer));
}
return sb.ToString();
}
}
}
```
### 4.2. Сборка и создание ярлыка на рабочем столе
1. В Visual Studio в свойствах проекта (**Свойства** -> **Сборка** / **Build**) рекомендуется выставить целевую платформу **x64** (или снять флаг *Prefer 32-bit*).
2. Скомпилируйте проект в конфигурации **Release** (результирующий бинарник: `D:\sys\C-Sharp\deployfile\bin\Release\deployfile.exe`).
3. Создайте ярлык на рабочем столе:
* **Расположение объекта:**
```cmd
"D:\sys\C-Sharp\deployfile\bin\Release\deployfile.exe"
```
* **Имя ярлыка:** `Откатить конфигурацию HAProxy`.
---
## 5. Проверка работы комплекса
1. **Запуск с клиента:**
* Дважды кликните по созданному ярлыку на рабочем столе.
* Консоль отобразит процесс подключения и результат:
```text
Используется ключ: D:\sys\C-Sharp\deployfile\key\test_key.key
Подключение к depfile@10.228.48.110:22...
Файл status/change.me и хеш status/sha256.hash успешно созданы на сервере.
```
2. **Проверка на сервере (в пределах 1 минуты):**
* Каталог `/var/lib/depfile/status` удален.
* Файл конфигурации `/etc/haproxy/haproxy.cfg` перезаписан из `/etc/haproxy/backup/haproxy.cfg`.
* Статус HAProxy: `systemctl status haproxy` в состоянии `active (running)`.
* Проверка фиксации развертывания:
```bash
cat /var/lib/depfile/deployment/success.me
```
*Вывод:*
```text
deploment success {2026-10-13 12:45:00}
```
* Журнал работы скрипта доступен в `/var/log/deploy_haproxy.log`.