Change real_ip
This commit is contained in:
parent
f2998502db
commit
707407a433
11 changed files with 259 additions and 82 deletions
|
|
@ -114,6 +114,8 @@ waf-host-02 | instance-c
|
||||||
- debug # Режим отладки (bool, влияет на лог-уровень)
|
- debug # Режим отладки (bool, влияет на лог-уровень)
|
||||||
- shm_size # Размер /dev/shm в GB (дефолт: 1)
|
- shm_size # Размер /dev/shm в GB (дефолт: 1)
|
||||||
- ptaf_fallback_code # Код ответа ptaf_fallback: число или 'pass' (дефолт: 418)
|
- ptaf_fallback_code # Код ответа ptaf_fallback: число или 'pass' (дефолт: 418)
|
||||||
|
- sp_antiddos # Использовать антиддос сети в set_real_ip (дефолт: true)
|
||||||
|
- real_ip_networks # Кастомные сети для set_real_ip если sp_antiddos = false (inet[])
|
||||||
```
|
```
|
||||||
|
|
||||||
**Важно:** `waf_instance` определяет на каком хосте должен работать клиент
|
**Важно:** `waf_instance` определяет на каком хосте должен работать клиент
|
||||||
|
|
@ -516,13 +518,43 @@ proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
|
||||||
2. Ожидаем полного освобождения портов (до 180 сек)
|
2. Ожидаем полного освобождения портов (до 180 сек)
|
||||||
3. Запуск нового контейнера (до 3 попыток с паузой 3 сек)
|
3. Запуск нового контейнера (до 3 попыток с паузой 3 сек)
|
||||||
|
|
||||||
|
### Сохранение портов
|
||||||
|
|
||||||
|
`.ports.json` сохраняется **только после успешного запуска контейнера**:
|
||||||
|
- При пересоздании контейнера (`recreateContainer`) — сохраняется если `recreateContainer` вернул `true`
|
||||||
|
- При первом запуске нового контейнера — сохраняется если контейнер успешно запустился
|
||||||
|
- При неизменённом compose — сохраняется сразу (порты не менялись)
|
||||||
|
|
||||||
|
Если контейнер не запустился — `.ports.json` остаётся старым, и при следующем запуске magos порты будут перевыделены заново.
|
||||||
|
|
||||||
### Проверка после запуска
|
### Проверка после запуска
|
||||||
|
|
||||||
Если контейнер запущен менее 5 минут и порты не слушают — автоматическое пересоздание.
|
Если контейнер запущен менее 5 минут и порты не слушают — автоматическое пересоздание.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Константы и лимиты
|
## Именование контейнеров
|
||||||
|
|
||||||
|
Имя Docker-контейнера формируется динамически на основе hostname хоста:
|
||||||
|
|
||||||
|
```
|
||||||
|
PTAF-TB-docker-AZ3-HW-node01 → {clientTitle}_AZ3_hn01_a001
|
||||||
|
PTAF-TB-docker-AZ1-node04 → {clientTitle}_AZ1_n04_a001
|
||||||
|
```
|
||||||
|
|
||||||
|
**Правила парсинга hostname:**
|
||||||
|
- `AZ{N}` — номер зоны доступности
|
||||||
|
- Наличие `HW` в hostname → префикс `hn` (hardware node), отсутствие → `n`
|
||||||
|
- `node{NN}` — номер ноды
|
||||||
|
- `a{001}` — порядковый номер агента
|
||||||
|
|
||||||
|
**Fallback:** если hostname не соответствует формату — используется старый формат `ptaf_{clientTitle}_{001}`.
|
||||||
|
|
||||||
|
**Важно:** директории на диске (`/home/install/ptaf/{clientTitle}/{clientTitle}-ptaf-agent{001}/`) не меняются — только `container_name` и `hostname` в Docker.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Производительность
|
### Производительность
|
||||||
|
|
||||||
|
|
@ -561,9 +593,13 @@ large_client_header_buffers 4 128k;
|
||||||
|
|
||||||
### Фильтрация IP адресов
|
### Фильтрация IP адресов
|
||||||
|
|
||||||
**WAF Networks** - исключаются из origins (предотвращение routing loops)
|
**WAF Networks** - исключаются из origins (предотвращение routing loops). Всегда добавляются в `set_real_ip_from` в Angie.
|
||||||
|
|
||||||
**Antibot Networks** - используются для идентификации antibot трафика
|
**Antibot Networks** - используются для `set_real_ip_from` в Angie по умолчанию (`sp_antiddos = true`).
|
||||||
|
|
||||||
|
**Custom Real IP Networks** - если у клиента `sp_antiddos = false` и заполнено поле `real_ip_networks` в `client_info` — используются вместо Antibot Networks в `set_real_ip_from`. Применяется для клиентов без антиддоса перед Angie.
|
||||||
|
|
||||||
|
Внутри контейнера nginx всегда использует `set_real_ip_from 172.16.0.0/12` (Docker bridge сеть Angie).
|
||||||
|
|
||||||
### SSL/TLS
|
### SSL/TLS
|
||||||
|
|
||||||
|
|
@ -753,8 +789,17 @@ UPDATE apps_settings SET balancing_method = 'least_conn' WHERE l7resourceid = 12
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Пропускать трафик если WAF недоступен
|
-- Пропускать трафик если WAF недоступен
|
||||||
UPDATE client_info SET ptaf_fallback_code = 'pass' WHERE client_uuid = 12345;
|
UPDATE client_info SET ptaf_fallback_code = 'pass' WHERE client_title = 'CLIENT001';
|
||||||
|
|
||||||
-- Вернуть 503 вместо 418
|
-- Вернуть 503 вместо 418
|
||||||
UPDATE client_info SET ptaf_fallback_code = '503' WHERE client_uuid = 12345;
|
UPDATE client_info SET ptaf_fallback_code = '503' WHERE client_title = 'CLIENT001';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Отключить антиддос для клиента (кастомные real_ip сети)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE client_info
|
||||||
|
SET sp_antiddos = false,
|
||||||
|
real_ip_networks = '{10.0.0.0/8, 192.168.1.0/24}'
|
||||||
|
WHERE client_title = 'CLIENT001';
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,9 @@ WHERE hostname = $1 AND instance = $2;
|
||||||
| `docker_image_download` | TEXT | URL для скачивания tar-архива образа | `https://...` |
|
| `docker_image_download` | TEXT | URL для скачивания tar-архива образа | `https://...` |
|
||||||
| `debug` | BOOLEAN | Режим отладки | `false` |
|
| `debug` | BOOLEAN | Режим отладки | `false` |
|
||||||
| `shm_size` | INTEGER | Размер /dev/shm в GB | `1` |
|
| `shm_size` | INTEGER | Размер /dev/shm в GB | `1` |
|
||||||
| `ptaf_fallback_code` | TEXT | Код ответа ptaf_fallback | `'418'` |
|
| `ptaf_fallback_code` | TEXT | Код ответа ptaf_fallback: число или 'pass' | `418` |
|
||||||
|
| `sp_antiddos` | BOOLEAN | Использовать антиддос сети в set_real_ip | `true` |
|
||||||
|
| `real_ip_networks` | INET[] | Кастомные сети для set_real_ip (если sp_antiddos=false) | `{10.0.0.0/8}` |
|
||||||
|
|
||||||
### Используемые запросы
|
### Используемые запросы
|
||||||
|
|
||||||
|
|
@ -114,7 +116,10 @@ SELECT
|
||||||
docker_image,
|
docker_image,
|
||||||
docker_image_download,
|
docker_image_download,
|
||||||
debug,
|
debug,
|
||||||
shm_size
|
shm_size,
|
||||||
|
ptaf_fallback_code,
|
||||||
|
sp_antiddos,
|
||||||
|
real_ip_networks
|
||||||
FROM client_info
|
FROM client_info
|
||||||
WHERE waf_instance = $1;
|
WHERE waf_instance = $1;
|
||||||
```
|
```
|
||||||
|
|
@ -133,7 +138,10 @@ SELECT
|
||||||
docker_image,
|
docker_image,
|
||||||
docker_image_download,
|
docker_image_download,
|
||||||
debug,
|
debug,
|
||||||
shm_size
|
shm_size,
|
||||||
|
ptaf_fallback_code,
|
||||||
|
sp_antiddos,
|
||||||
|
real_ip_networks
|
||||||
FROM client_info
|
FROM client_info
|
||||||
WHERE client_title = $1;
|
WHERE client_title = $1;
|
||||||
```
|
```
|
||||||
|
|
@ -154,6 +162,15 @@ ALTER TABLE client_info
|
||||||
|
|
||||||
ALTER TABLE client_info
|
ALTER TABLE client_info
|
||||||
ADD COLUMN IF NOT EXISTS shm_size INTEGER DEFAULT 1;
|
ADD COLUMN IF NOT EXISTS shm_size INTEGER DEFAULT 1;
|
||||||
|
|
||||||
|
ALTER TABLE client_info
|
||||||
|
ADD COLUMN IF NOT EXISTS ptaf_fallback_code TEXT DEFAULT '418';
|
||||||
|
|
||||||
|
ALTER TABLE client_info
|
||||||
|
ADD COLUMN IF NOT EXISTS sp_antiddos BOOLEAN DEFAULT TRUE;
|
||||||
|
|
||||||
|
ALTER TABLE client_info
|
||||||
|
ADD COLUMN IF NOT EXISTS real_ip_networks INET[] DEFAULT NULL;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ключевые поля
|
### Ключевые поля
|
||||||
|
|
@ -180,6 +197,28 @@ ALTER TABLE client_info
|
||||||
|
|
||||||
Размер разделяемой памяти `/dev/shm` для Docker-контейнера в гигабайтах. Результат в docker-compose: `shm_size: '2gb'`. Дефолт: 1.
|
Размер разделяемой памяти `/dev/shm` для Docker-контейнера в гигабайтах. Результат в docker-compose: `shm_size: '2gb'`. Дефолт: 1.
|
||||||
|
|
||||||
|
#### ptaf_fallback_code
|
||||||
|
|
||||||
|
Код HTTP ответа который PTAF возвращает когда WAF-модуль недоступен. Записывается в блок `http {}` файла `nginx.conf` — применяется глобально для всех ресурсов контейнера.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE client_info SET ptaf_fallback_code = 'pass' WHERE client_title = 'CLIENT001';
|
||||||
|
UPDATE client_info SET ptaf_fallback_code = '503' WHERE client_title = 'CLIENT001';
|
||||||
|
```
|
||||||
|
|
||||||
|
#### sp_antiddos / real_ip_networks
|
||||||
|
|
||||||
|
`sp_antiddos = true` (дефолт) — в Angie используются антиддос сети из таблицы `ips` для `set_real_ip_from`.
|
||||||
|
|
||||||
|
`sp_antiddos = false` + непустой `real_ip_networks` — вместо антиддос сетей используются кастомные сети из `real_ip_networks`. WAF сети из `ips` при этом всегда присутствуют.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE client_info
|
||||||
|
SET sp_antiddos = false,
|
||||||
|
real_ip_networks = '{10.0.0.0/8, 192.168.1.0/24}'
|
||||||
|
WHERE client_title = 'CLIENT001';
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Таблица: apps_settings
|
## Таблица: apps_settings
|
||||||
|
|
@ -248,8 +287,7 @@ SELECT
|
||||||
max_fails,
|
max_fails,
|
||||||
fail_timeout,
|
fail_timeout,
|
||||||
balancing_method,
|
balancing_method,
|
||||||
proxy_next_upstream_codes,
|
proxy_next_upstream_codes
|
||||||
ptaf_fallback_code
|
|
||||||
FROM apps_settings
|
FROM apps_settings
|
||||||
WHERE client_title = $1
|
WHERE client_title = $1
|
||||||
ORDER BY l7resourceid ASC;
|
ORDER BY l7resourceid ASC;
|
||||||
|
|
@ -268,9 +306,6 @@ ALTER TABLE apps_settings
|
||||||
|
|
||||||
ALTER TABLE apps_settings
|
ALTER TABLE apps_settings
|
||||||
ADD COLUMN IF NOT EXISTS proxy_next_upstream_codes TEXT DEFAULT '500,502,503,504';
|
ADD COLUMN IF NOT EXISTS proxy_next_upstream_codes TEXT DEFAULT '500,502,503,504';
|
||||||
|
|
||||||
ALTER TABLE client_info
|
|
||||||
ADD COLUMN IF NOT EXISTS ptaf_fallback_code TEXT DEFAULT '418';
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Кастомные поля (подробно)
|
### Кастомные поля (подробно)
|
||||||
|
|
@ -310,21 +345,6 @@ WHERE l7resourceid = 12345;
|
||||||
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
|
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
|
||||||
```
|
```
|
||||||
|
|
||||||
#### ptaf_fallback_code
|
|
||||||
|
|
||||||
Код HTTP ответа который PTAF возвращает когда WAF-модуль недоступен.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Пропускать трафик если WAF недоступен
|
|
||||||
UPDATE client_info SET ptaf_fallback_code = 'pass' WHERE client_uuid = 12345;
|
|
||||||
|
|
||||||
-- Вернуть 503 вместо 418
|
|
||||||
UPDATE client_info SET ptaf_fallback_code = '503' WHERE client_uuid = 12345;
|
|
||||||
|
|
||||||
-- Вернуть дефолт (418)
|
|
||||||
UPDATE client_info SET ptaf_fallback_code = NULL WHERE client_uuid = 12345;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### upstream_angie_custom / upstream_nginx_custom
|
#### upstream_angie_custom / upstream_nginx_custom
|
||||||
|
|
||||||
**Режим:** REPLACE (полная замена keepalive директив)
|
**Режим:** REPLACE (полная замена keepalive директив)
|
||||||
|
|
@ -563,6 +583,12 @@ SELECT client_title FROM client_info WHERE debug = true;
|
||||||
-- Клиенты с индивидуальным образом
|
-- Клиенты с индивидуальным образом
|
||||||
SELECT client_title, docker_image FROM client_info WHERE docker_image IS NOT NULL;
|
SELECT client_title, docker_image FROM client_info WHERE docker_image IS NOT NULL;
|
||||||
|
|
||||||
|
-- Клиенты без антиддоса
|
||||||
|
SELECT client_title, real_ip_networks FROM client_info WHERE sp_antiddos = false;
|
||||||
|
|
||||||
|
-- Клиенты с кастомным ptaf_fallback
|
||||||
|
SELECT client_title, ptaf_fallback_code FROM client_info WHERE ptaf_fallback_code != '418';
|
||||||
|
|
||||||
-- Ресурсы с кастомной балансировкой
|
-- Ресурсы с кастомной балансировкой
|
||||||
SELECT client_title, l7resourceid, balancing_method
|
SELECT client_title, l7resourceid, balancing_method
|
||||||
FROM apps_settings
|
FROM apps_settings
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo, resources []ResourceData, resourcePortMap map[int][]PortMapping) error {
|
func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo, resources []ResourceData, resourcePortMap map[int][]PortMapping, hostname string) error {
|
||||||
log.Printf("Настройка контейнеров для %s", clientTitle)
|
log.Printf("Настройка контейнеров для %s", clientTitle)
|
||||||
baseDir := filepath.Join("/home/install/ptaf", clientTitle)
|
baseDir := filepath.Join("/home/install/ptaf", clientTitle)
|
||||||
containerChanges := make(map[int]bool)
|
containerChanges := make(map[int]bool)
|
||||||
|
|
@ -19,11 +19,13 @@ func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo,
|
||||||
for containerNum := 1; containerNum <= clientInfo.ContainersCount; containerNum++ {
|
for containerNum := 1; containerNum <= clientInfo.ContainersCount; containerNum++ {
|
||||||
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
|
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
|
||||||
containerDir := filepath.Join(baseDir, containerName)
|
containerDir := filepath.Join(baseDir, containerName)
|
||||||
|
containerFullName := buildContainerFullName(clientTitle, containerNum, hostname)
|
||||||
log.Printf(" Обработка контейнера: %s", containerName)
|
log.Printf(" Обработка контейнера: %s", containerName)
|
||||||
|
|
||||||
|
confDDir := filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum), "conf.d")
|
||||||
dirs := []string{
|
dirs := []string{
|
||||||
containerDir,
|
containerDir,
|
||||||
filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum), "conf.d"),
|
confDDir,
|
||||||
}
|
}
|
||||||
for _, dir := range dirs {
|
for _, dir := range dirs {
|
||||||
existed := false
|
existed := false
|
||||||
|
|
@ -37,6 +39,10 @@ func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo,
|
||||||
log.Printf(" ✓ Создана директория: %s", dir)
|
log.Printf(" ✓ Создана директория: %s", dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// conf.d должна быть доступна пользователю ptaf внутри контейнера
|
||||||
|
if err := os.Chmod(confDDir, 0777); err != nil {
|
||||||
|
return fmt.Errorf("ошибка установки прав для conf.d %s: %w", confDDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
logDir := filepath.Join("/var/log/ptaf_nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum))
|
logDir := filepath.Join("/var/log/ptaf_nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum))
|
||||||
logDirExisted := false
|
logDirExisted := false
|
||||||
|
|
@ -151,7 +157,7 @@ func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo,
|
||||||
for idx, pm := range containerPorts {
|
for idx, pm := range containerPorts {
|
||||||
log.Printf(" PortMapping[%d]: HTTP=%v, HTTPS=%v", idx, pm.HTTPPorts, pm.HTTPSPorts)
|
log.Printf(" PortMapping[%d]: HTTP=%v, HTTPS=%v", idx, pm.HTTPPorts, pm.HTTPSPorts)
|
||||||
}
|
}
|
||||||
changed, err = generateDockerCompose(composeFile, config, clientTitle, clientInfo, containerNum, resources, containerPorts)
|
changed, err = generateDockerCompose(composeFile, config, clientTitle, clientInfo, containerNum, containerFullName, resources, containerPorts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ошибка генерации docker-compose.yml: %w", err)
|
return fmt.Errorf("ошибка генерации docker-compose.yml: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -159,8 +165,12 @@ func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo,
|
||||||
log.Printf(" ✓ docker-compose.yml изменен: %s", composeFile)
|
log.Printf(" ✓ docker-compose.yml изменен: %s", composeFile)
|
||||||
composeChanges[containerNum] = true
|
composeChanges[containerNum] = true
|
||||||
}
|
}
|
||||||
if err := saveContainerPorts(containerDir, containerNum, resources, containerPorts); err != nil {
|
// Сохраняем порты только если compose не изменился (нет пересоздания)
|
||||||
log.Printf(" ⚠ Ошибка сохранения портов: %v", err)
|
// Если compose изменился — сохраним после успешного запуска контейнера
|
||||||
|
if !composeChanges[containerNum] {
|
||||||
|
if err := saveContainerPorts(containerDir, containerNum, resources, containerPorts); err != nil {
|
||||||
|
log.Printf(" ⚠ Ошибка сохранения портов: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,12 +179,27 @@ func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo,
|
||||||
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
|
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
|
||||||
containerDir := filepath.Join(baseDir, containerName)
|
containerDir := filepath.Join(baseDir, containerName)
|
||||||
composeFile := filepath.Join(containerDir, "docker-compose.yml")
|
composeFile := filepath.Join(containerDir, "docker-compose.yml")
|
||||||
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientTitle, containerNum)
|
containerFullName := buildContainerFullName(clientTitle, containerNum, hostname)
|
||||||
containerExists, containerRunning := checkContainerStatus(containerFullName)
|
containerExists, containerRunning := checkContainerStatus(containerFullName)
|
||||||
|
|
||||||
|
// Собираем порты контейнера для сохранения после успешного запуска
|
||||||
|
containerPorts := []PortMapping{}
|
||||||
|
for _, res := range resources {
|
||||||
|
ports, ok := resourcePortMap[res.L7ResourceID]
|
||||||
|
if !ok || containerNum-1 >= len(ports) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
containerPorts = append(containerPorts, ports[containerNum-1])
|
||||||
|
}
|
||||||
|
|
||||||
if containerExists {
|
if containerExists {
|
||||||
if composeChanges[containerNum] {
|
if composeChanges[containerNum] {
|
||||||
recreateContainer(containerFullName, composeFile, containerDir, clientTitle, resources, resourcePortMap, containerNum)
|
success := recreateContainer(containerFullName, composeFile, containerDir, clientTitle, resources, resourcePortMap, containerNum)
|
||||||
|
if success {
|
||||||
|
if err := saveContainerPorts(containerDir, containerNum, resources, containerPorts); err != nil {
|
||||||
|
log.Printf(" ⚠ Ошибка сохранения портов: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !containerRunning {
|
if !containerRunning {
|
||||||
|
|
@ -312,6 +337,9 @@ func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo,
|
||||||
log.Printf(" ⚠ Контейнер %s не запустился после %d попыток. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, maxRetries, containerDir)
|
log.Printf(" ⚠ Контейнер %s не запустился после %d попыток. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, maxRetries, containerDir)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if err := saveContainerPorts(containerDir, containerNum, resources, containerPorts); err != nil {
|
||||||
|
log.Printf(" ⚠ Ошибка сохранения портов: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ func isPortBusyByAnyProcess(port int) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func recreateContainer(containerFullName, composeFile, containerDir, clientTitle string, resources []ResourceData, resourcePortMap map[int][]PortMapping, containerNum int) {
|
func recreateContainer(containerFullName, composeFile, containerDir, clientTitle string, resources []ResourceData, resourcePortMap map[int][]PortMapping, containerNum int) bool {
|
||||||
log.Printf(" docker-compose.yml изменился, пересоздаём контейнер %s...", containerFullName)
|
log.Printf(" docker-compose.yml изменился, пересоздаём контейнер %s...", containerFullName)
|
||||||
portsToFree := make([]int, 0)
|
portsToFree := make([]int, 0)
|
||||||
for _, res := range resources {
|
for _, res := range resources {
|
||||||
|
|
@ -110,7 +110,7 @@ func recreateContainer(containerFullName, composeFile, containerDir, clientTitle
|
||||||
log.Printf(" → Контейнер не остановился, принудительное удаление...")
|
log.Printf(" → Контейнер не остановился, принудительное удаление...")
|
||||||
if err := forceRemoveContainer(containerFullName); err != nil {
|
if err := forceRemoveContainer(containerFullName); err != nil {
|
||||||
log.Printf(" ⚠ Не удалось принудительно удалить контейнер: %v", err)
|
log.Printf(" ⚠ Не удалось принудительно удалить контейнер: %v", err)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +165,7 @@ func recreateContainer(containerFullName, composeFile, containerDir, clientTitle
|
||||||
if !allPortsFree {
|
if !allPortsFree {
|
||||||
log.Printf(" ⚠ Порты не освободились за 180 секунд!")
|
log.Printf(" ⚠ Порты не освободились за 180 секунд!")
|
||||||
log.Printf(" → Проверьте вручную: sudo lsof -i :%d", portsToFree[0])
|
log.Printf(" → Проверьте вручную: sudo lsof -i :%d", portsToFree[0])
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
@ -179,18 +179,19 @@ func recreateContainer(containerFullName, composeFile, containerDir, clientTitle
|
||||||
log.Printf(" ⚠ Порт всё ещё занят после ожидания освобождения!")
|
log.Printf(" ⚠ Порт всё ещё занят после ожидания освобождения!")
|
||||||
log.Printf(" → Проверьте: docker ps -a | grep %s", clientTitle)
|
log.Printf(" → Проверьте: docker ps -a | grep %s", clientTitle)
|
||||||
log.Printf(" → Найдите процесс: sudo lsof -i | grep LISTEN | grep 127.0.0.1")
|
log.Printf(" → Найдите процесс: sudo lsof -i | grep LISTEN | grep 127.0.0.1")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
_, isRunning := checkContainerStatus(containerFullName)
|
_, isRunning := checkContainerStatus(containerFullName)
|
||||||
if !isRunning {
|
if !isRunning {
|
||||||
log.Printf(" ⚠ Контейнер %s не запустился. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, containerDir)
|
log.Printf(" ⚠ Контейнер %s не запустился. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, containerDir)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
log.Printf(" ✓ Контейнер %s пересоздан с новыми портами", containerFullName)
|
log.Printf(" ✓ Контейнер %s пересоздан с новыми портами", containerFullName)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func startContainer(containerFullName, composeFile, containerDir string) {
|
func startContainer(containerFullName, composeFile, containerDir string) bool {
|
||||||
log.Printf(" Контейнер %s остановлен, запускаем...", containerFullName)
|
log.Printf(" Контейнер %s остановлен, запускаем...", containerFullName)
|
||||||
cmd := exec.Command("docker-compose", "-f", composeFile, "up", "-d")
|
cmd := exec.Command("docker-compose", "-f", composeFile, "up", "-d")
|
||||||
cmd.Dir = containerDir
|
cmd.Dir = containerDir
|
||||||
|
|
@ -200,9 +201,10 @@ func startContainer(containerFullName, composeFile, containerDir string) {
|
||||||
_, isRunning := checkContainerStatus(containerFullName)
|
_, isRunning := checkContainerStatus(containerFullName)
|
||||||
if !isRunning {
|
if !isRunning {
|
||||||
log.Printf(" ⚠ Контейнер %s не запустился. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, containerDir)
|
log.Printf(" ⚠ Контейнер %s не запустился. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, containerDir)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
log.Printf(" ✓ Контейнер %s запущен", containerFullName)
|
log.Printf(" ✓ Контейнер %s запущен", containerFullName)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// getContainerStartedAt возвращает время запуска контейнера
|
// getContainerStartedAt возвращает время запуска контейнера
|
||||||
|
|
@ -646,10 +648,11 @@ func generateMimeTypes(filePath string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateDockerCompose генерирует docker-compose.yml файл
|
// generateDockerCompose генерирует docker-compose.yml файл
|
||||||
func generateDockerCompose(filePath string, config Config, clientTitle string, clientInfo *ClientInfo, containerNum int, resources []ResourceData, portMappings []PortMapping) (bool, error) {
|
func generateDockerCompose(filePath string, config Config, clientTitle string, clientInfo *ClientInfo, containerNum int, containerFullName string, resources []ResourceData, portMappings []PortMapping) (bool, error) {
|
||||||
type ComposeData struct {
|
type ComposeData struct {
|
||||||
ClientTitle string
|
ClientTitle string
|
||||||
ContainerNum int
|
ContainerNum int
|
||||||
|
ContainerFullName string
|
||||||
DockerImage string
|
DockerImage string
|
||||||
PTAFConfig string
|
PTAFConfig string
|
||||||
WorkerProcesses int
|
WorkerProcesses int
|
||||||
|
|
@ -714,6 +717,7 @@ func generateDockerCompose(filePath string, config Config, clientTitle string, c
|
||||||
data := ComposeData{
|
data := ComposeData{
|
||||||
ClientTitle: clientTitle,
|
ClientTitle: clientTitle,
|
||||||
ContainerNum: containerNum,
|
ContainerNum: containerNum,
|
||||||
|
ContainerFullName: containerFullName,
|
||||||
DockerImage: dockerImage,
|
DockerImage: dockerImage,
|
||||||
PTAFConfig: "",
|
PTAFConfig: "",
|
||||||
WorkerProcesses: config.WorkerProcesses,
|
WorkerProcesses: config.WorkerProcesses,
|
||||||
|
|
@ -739,8 +743,8 @@ func generateDockerCompose(filePath string, config Config, clientTitle string, c
|
||||||
image: '{{.DockerImage}}'
|
image: '{{.DockerImage}}'
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
shm_size: '{{.ShmSize}}gb'
|
shm_size: '{{.ShmSize}}gb'
|
||||||
container_name: ptaf_{{.ClientTitle}}_{{printf "%03d" .ContainerNum}}
|
container_name: {{.ContainerFullName}}
|
||||||
hostname: ptaf_{{.ClientTitle}}_{{printf "%03d" .ContainerNum}}
|
hostname: {{.ContainerFullName}}
|
||||||
environment:
|
environment:
|
||||||
- CONNECTION_STRING={{.PTAFConfig}}
|
- CONNECTION_STRING={{.PTAFConfig}}
|
||||||
- WORKER_PROCESSES={{.WorkerProcesses}}
|
- WORKER_PROCESSES={{.WorkerProcesses}}
|
||||||
|
|
|
||||||
12
db.go
12
db.go
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
// connectToDB подключается к базе данных PostgreSQL
|
// connectToDB подключается к базе данных PostgreSQL
|
||||||
|
|
@ -98,7 +98,8 @@ func checkHostBelongsToInstance(db *sql.DB, hostname, instance string) (bool, er
|
||||||
func getClientInfoByInstance(db *sql.DB, instance string) ([]ClientInfo, error) {
|
func getClientInfoByInstance(db *sql.DB, instance string) ([]ClientInfo, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT containers_count, ptaf_config, client_title, fluent_bit_port, waf_instance,
|
SELECT containers_count, ptaf_config, client_title, fluent_bit_port, waf_instance,
|
||||||
docker_image, docker_image_download, debug, shm_size, ptaf_fallback_code
|
docker_image, docker_image_download, debug, shm_size, ptaf_fallback_code,
|
||||||
|
sp_antiddos, real_ip_networks
|
||||||
FROM client_info
|
FROM client_info
|
||||||
WHERE waf_instance = $1
|
WHERE waf_instance = $1
|
||||||
`
|
`
|
||||||
|
|
@ -127,6 +128,8 @@ func getClientInfoByInstance(db *sql.DB, instance string) ([]ClientInfo, error)
|
||||||
&ci.Debug,
|
&ci.Debug,
|
||||||
&ci.ShmSize,
|
&ci.ShmSize,
|
||||||
&ci.PtafFallbackCode,
|
&ci.PtafFallbackCode,
|
||||||
|
&ci.SpAntiddos,
|
||||||
|
pq.Array(&ci.RealIPNetworks),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ошибка сканирования строки: %w", err)
|
return nil, fmt.Errorf("ошибка сканирования строки: %w", err)
|
||||||
|
|
@ -212,7 +215,8 @@ func getAppsSettingsByClientTitle(db *sql.DB, clientTitle string) ([]AppsSetting
|
||||||
func getClientInfoByClientTitle(db *sql.DB, clientTitle string) (*ClientInfo, error) {
|
func getClientInfoByClientTitle(db *sql.DB, clientTitle string) (*ClientInfo, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT containers_count, ptaf_config, client_title, fluent_bit_port, waf_instance,
|
SELECT containers_count, ptaf_config, client_title, fluent_bit_port, waf_instance,
|
||||||
docker_image, docker_image_download, debug, shm_size, ptaf_fallback_code
|
docker_image, docker_image_download, debug, shm_size, ptaf_fallback_code,
|
||||||
|
sp_antiddos, real_ip_networks
|
||||||
FROM client_info
|
FROM client_info
|
||||||
WHERE client_title = $1
|
WHERE client_title = $1
|
||||||
`
|
`
|
||||||
|
|
@ -230,6 +234,8 @@ func getClientInfoByClientTitle(db *sql.DB, clientTitle string) (*ClientInfo, er
|
||||||
&clientInfo.Debug,
|
&clientInfo.Debug,
|
||||||
&clientInfo.ShmSize,
|
&clientInfo.ShmSize,
|
||||||
&clientInfo.PtafFallbackCode,
|
&clientInfo.PtafFallbackCode,
|
||||||
|
&clientInfo.SpAntiddos,
|
||||||
|
pq.Array(&clientInfo.RealIPNetworks),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "does not exist") {
|
if strings.Contains(err.Error(), "does not exist") {
|
||||||
|
|
|
||||||
37
drain.go
37
drain.go
|
|
@ -30,12 +30,22 @@ func getActualContainerCount(clientTitle string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Считаем по drain-маркерам в /tmp (контейнеры могли быть уже удалены с диска)
|
// Считаем по drain-маркерам в /tmp (контейнеры могли быть уже удалены с диска)
|
||||||
for num := max + 1; ; num++ {
|
// Ищем маркеры по паттерну /tmp/ptaf-drain-{clientTitle}_* независимо от формата имени
|
||||||
markerFile := fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_%03d", clientTitle, num)
|
matches, _ := filepath.Glob(fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle))
|
||||||
if _, err := os.Stat(markerFile); err != nil {
|
for _, match := range matches {
|
||||||
break
|
// Извлекаем номер агента из конца имени файла (формат: ..._a001 или ..._001)
|
||||||
|
base := filepath.Base(match)
|
||||||
|
base = strings.TrimPrefix(base, "ptaf-drain-")
|
||||||
|
// Убираем префикс clientTitle_
|
||||||
|
rest := strings.TrimPrefix(base, clientTitle+"_")
|
||||||
|
// Ищем номер в конце: _a001 или _001
|
||||||
|
parts := strings.Split(rest, "_")
|
||||||
|
lastPart := parts[len(parts)-1]
|
||||||
|
lastPart = strings.TrimPrefix(lastPart, "a")
|
||||||
|
var num int
|
||||||
|
if _, err := fmt.Sscanf(lastPart, "%d", &num); err == nil && num > max {
|
||||||
|
max = num
|
||||||
}
|
}
|
||||||
max = num
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return max
|
return max
|
||||||
|
|
@ -161,7 +171,8 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) {
|
||||||
hasExistingMarkers := false
|
hasExistingMarkers := false
|
||||||
actualCount := getActualContainerCount(clientTitle)
|
actualCount := getActualContainerCount(clientTitle)
|
||||||
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
||||||
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_%03d", clientTitle, containerNum)); err == nil {
|
containerFullName := buildContainerFullName(clientTitle, containerNum, hostname)
|
||||||
|
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName)); err == nil {
|
||||||
hasExistingMarkers = true
|
hasExistingMarkers = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +180,7 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) {
|
||||||
if !hasExistingMarkers {
|
if !hasExistingMarkers {
|
||||||
log.Printf(" → Помечаю контейнеры для миграции")
|
log.Printf(" → Помечаю контейнеры для миграции")
|
||||||
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
||||||
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientTitle, containerNum)
|
containerFullName := buildContainerFullName(clientTitle, containerNum, hostname)
|
||||||
exists, _ := checkContainerStatus(containerFullName)
|
exists, _ := checkContainerStatus(containerFullName)
|
||||||
if exists {
|
if exists {
|
||||||
if err := markContainerForDrain(containerFullName, true); err != nil {
|
if err := markContainerForDrain(containerFullName, true); err != nil {
|
||||||
|
|
@ -210,12 +221,10 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) {
|
||||||
|
|
||||||
func checkAndProcessExistingDrainMarkers(clientTitle string, containersCount int, processedCount *int) {
|
func checkAndProcessExistingDrainMarkers(clientTitle string, containersCount int, processedCount *int) {
|
||||||
hasAnyDrainMarkers := false
|
hasAnyDrainMarkers := false
|
||||||
actualCount := getActualContainerCount(clientTitle)
|
// Ищем маркеры по паттерну независимо от формата имени контейнера
|
||||||
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
matches, _ := filepath.Glob(fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle))
|
||||||
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_%03d", clientTitle, containerNum)); err == nil {
|
if len(matches) > 0 {
|
||||||
hasAnyDrainMarkers = true
|
hasAnyDrainMarkers = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !hasAnyDrainMarkers {
|
if !hasAnyDrainMarkers {
|
||||||
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle)); err == nil {
|
if _, err := os.Stat(fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle)); err == nil {
|
||||||
|
|
@ -244,7 +253,7 @@ func processDrainingContainers(clientTitle string, maxContainerNum int) {
|
||||||
actualCount := getActualContainerCount(clientTitle)
|
actualCount := getActualContainerCount(clientTitle)
|
||||||
for containerNum := maxContainerNum + 1; containerNum <= actualCount; containerNum++ {
|
for containerNum := maxContainerNum + 1; containerNum <= actualCount; containerNum++ {
|
||||||
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
|
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
|
||||||
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientTitle, containerNum)
|
containerFullName := buildContainerFullName(clientTitle, containerNum, hostname)
|
||||||
containerDir := filepath.Join(baseDir, containerName)
|
containerDir := filepath.Join(baseDir, containerName)
|
||||||
composeFile := filepath.Join(containerDir, "docker-compose.yml")
|
composeFile := filepath.Join(containerDir, "docker-compose.yml")
|
||||||
exists, _ := checkContainerStatus(containerFullName)
|
exists, _ := checkContainerStatus(containerFullName)
|
||||||
|
|
|
||||||
6
main.go
6
main.go
|
|
@ -345,7 +345,7 @@ func handleClientMigration(clientInfo ClientInfo, hostname string) {
|
||||||
|
|
||||||
actualCount := getActualContainerCount(clientInfo.ClientTitle)
|
actualCount := getActualContainerCount(clientInfo.ClientTitle)
|
||||||
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
||||||
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientInfo.ClientTitle, containerNum)
|
containerFullName := buildContainerFullName(clientInfo.ClientTitle, containerNum, hostname)
|
||||||
exists, _ := checkContainerStatus(containerFullName)
|
exists, _ := checkContainerStatus(containerFullName)
|
||||||
if exists {
|
if exists {
|
||||||
log.Printf(" → Помечаю контейнер %s для drain (миграция, таймаут %d сек)",
|
log.Printf(" → Помечаю контейнер %s для drain (миграция, таймаут %d сек)",
|
||||||
|
|
@ -373,14 +373,14 @@ func handleClientMigration(clientInfo ClientInfo, hostname string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanStaleDrainMarkers очищает устаревшие drain-маркеры если клиент вернулся на хост
|
// cleanStaleDrainMarkers очищает устаревшие drain-маркеры если клиент вернулся на хост
|
||||||
func cleanStaleDrainMarkers(clientInfo ClientInfo) {
|
func cleanStaleDrainMarkers(clientInfo ClientInfo, hostname string) {
|
||||||
log.Printf(" → Проверка и очистка устаревших маркеров drain для %s", clientInfo.ClientTitle)
|
log.Printf(" → Проверка и очистка устаревших маркеров drain для %s", clientInfo.ClientTitle)
|
||||||
cleanedContainers := 0
|
cleanedContainers := 0
|
||||||
cleanedAngieMarker := false
|
cleanedAngieMarker := false
|
||||||
|
|
||||||
actualCount := getActualContainerCount(clientInfo.ClientTitle)
|
actualCount := getActualContainerCount(clientInfo.ClientTitle)
|
||||||
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
for containerNum := 1; containerNum <= actualCount; containerNum++ {
|
||||||
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientInfo.ClientTitle, containerNum)
|
containerFullName := buildContainerFullName(clientInfo.ClientTitle, containerNum, hostname)
|
||||||
markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName)
|
markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName)
|
||||||
|
|
||||||
if _, err := os.Stat(markerFile); err == nil {
|
if _, err := os.Stat(markerFile); err == nil {
|
||||||
|
|
|
||||||
36
nginx.go
36
nginx.go
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateAngieConfigsWithoutReload генерирует конфиги Angie БЕЗ автоматического reload
|
// generateAngieConfigsWithoutReload генерирует конфиги Angie БЕЗ автоматического reload
|
||||||
func generateAngieConfigsWithoutReload(config Config, clientTitle string, resources []ResourceData, resourcePortMap map[int][]PortMapping, debug bool) (bool, error) {
|
func generateAngieConfigsWithoutReload(config Config, clientTitle string, clientInfo ClientInfo, resources []ResourceData, resourcePortMap map[int][]PortMapping, debug bool) (bool, error) {
|
||||||
log.Printf("Генерация конфигов Angie для %s", clientTitle)
|
log.Printf("Генерация конфигов Angie для %s", clientTitle)
|
||||||
|
|
||||||
configDir := "/etc/angie/http.d"
|
configDir := "/etc/angie/http.d"
|
||||||
|
|
@ -47,7 +47,7 @@ func generateAngieConfigsWithoutReload(config Config, clientTitle string, resour
|
||||||
return false, fmt.Errorf("не найдены порты для l7ResourceID %d", res.L7ResourceID)
|
return false, fmt.Errorf("не найдены порты для l7ResourceID %d", res.L7ResourceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := writeAngieResourceConfig(f, config, res, containerPorts, debug)
|
err := writeAngieResourceConfig(f, config, clientInfo, res, containerPorts, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove(tmpFile)
|
os.Remove(tmpFile)
|
||||||
return false, fmt.Errorf("ошибка генерации конфига для l7ResourceID %d: %w", res.L7ResourceID, err)
|
return false, fmt.Errorf("ошибка генерации конфига для l7ResourceID %d: %w", res.L7ResourceID, err)
|
||||||
|
|
@ -93,8 +93,8 @@ func generateAngieConfigsWithoutReload(config Config, clientTitle string, resour
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateAngieConfigs генерирует конфигурационные файлы Angie с автоматическим reload
|
// generateAngieConfigs генерирует конфигурационные файлы Angie с автоматическим reload
|
||||||
func generateAngieConfigs(config Config, clientTitle string, resources []ResourceData, resourcePortMap map[int][]PortMapping, debug bool) error {
|
func generateAngieConfigs(config Config, clientTitle string, clientInfo ClientInfo, resources []ResourceData, resourcePortMap map[int][]PortMapping, debug bool) error {
|
||||||
changed, err := generateAngieConfigsWithoutReload(config, clientTitle, resources, resourcePortMap, debug)
|
changed, err := generateAngieConfigsWithoutReload(config, clientTitle, clientInfo, resources, resourcePortMap, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +107,7 @@ func generateAngieConfigs(config Config, clientTitle string, resources []Resourc
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeAngieResourceConfig записывает конфигурацию для одного ресурса в Angie
|
// writeAngieResourceConfig записывает конфигурацию для одного ресурса в Angie
|
||||||
func writeAngieResourceConfig(f *os.File, config Config, res ResourceData, containerPorts []PortMapping, debug bool) error {
|
func writeAngieResourceConfig(f *os.File, config Config, clientInfo ClientInfo, res ResourceData, containerPorts []PortMapping, debug bool) error {
|
||||||
allDomains := []string{res.ServerName}
|
allDomains := []string{res.ServerName}
|
||||||
allDomains = append(allDomains, res.Aliases...)
|
allDomains = append(allDomains, res.Aliases...)
|
||||||
processedDomains := processDomainsWithWWW(allDomains)
|
processedDomains := processDomainsWithWWW(allDomains)
|
||||||
|
|
@ -271,7 +271,7 @@ func writeAngieResourceConfig(f *os.File, config Config, res ResourceData, conta
|
||||||
f.WriteString(fmt.Sprintf(" access_log /var/log/angie/%d_%s_access.log waf;\n", res.L7ResourceID, res.ServerName))
|
f.WriteString(fmt.Sprintf(" access_log /var/log/angie/%d_%s_access.log waf;\n", res.L7ResourceID, res.ServerName))
|
||||||
f.WriteString(fmt.Sprintf(" error_log /var/log/angie/%d_%s_error.log error;\n\n", res.L7ResourceID, res.ServerName))
|
f.WriteString(fmt.Sprintf(" error_log /var/log/angie/%d_%s_error.log error;\n\n", res.L7ResourceID, res.ServerName))
|
||||||
|
|
||||||
writeNetworkSettings(f, config)
|
writeNetworkSettings(f, config, clientInfo)
|
||||||
writeStandardServerSettings(f, getCustomServerDirectives(res.AppsSettings.ServerDirectivesAngieCustom), debug, getProxyNextUpstream(res.AppsSettings))
|
writeStandardServerSettings(f, getCustomServerDirectives(res.AppsSettings.ServerDirectivesAngieCustom), debug, getProxyNextUpstream(res.AppsSettings))
|
||||||
|
|
||||||
if hasCustomLocation {
|
if hasCustomLocation {
|
||||||
|
|
@ -324,7 +324,7 @@ func writeAngieResourceConfig(f *os.File, config Config, res ResourceData, conta
|
||||||
writeAngieSSLSettings(f, res.AppsSettings.CustomAngieSSL)
|
writeAngieSSLSettings(f, res.AppsSettings.CustomAngieSSL)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeNetworkSettings(f, config)
|
writeNetworkSettings(f, config, clientInfo)
|
||||||
writeStandardServerSettings(f, getCustomServerDirectives(res.AppsSettings.ServerDirectivesAngieCustom), debug, getProxyNextUpstream(res.AppsSettings))
|
writeStandardServerSettings(f, getCustomServerDirectives(res.AppsSettings.ServerDirectivesAngieCustom), debug, getProxyNextUpstream(res.AppsSettings))
|
||||||
|
|
||||||
if hasCustomLocation {
|
if hasCustomLocation {
|
||||||
|
|
@ -357,7 +357,7 @@ func writeAngieResourceConfig(f *os.File, config Config, res ResourceData, conta
|
||||||
}
|
}
|
||||||
log.Printf(" └─ Angie Server блок: порт %d, server_name %s%s",
|
log.Printf(" └─ Angie Server блок: порт %d, server_name %s%s",
|
||||||
block.Port, block.ServerName, aliasInfo)
|
block.Port, block.ServerName, aliasInfo)
|
||||||
generateAngieCustomServerBlock(f, config, res, block, containerPorts, customLocationStr, debug)
|
generateAngieCustomServerBlock(f, config, clientInfo, res, block, containerPorts, customLocationStr, debug)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -365,14 +365,22 @@ func writeAngieResourceConfig(f *os.File, config Config, res ResourceData, conta
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeNetworkSettings записывает WAF и Antibot networks для Angie
|
// writeNetworkSettings записывает WAF и Antibot networks для Angie
|
||||||
func writeNetworkSettings(f *os.File, config Config) {
|
func writeNetworkSettings(f *os.File, config Config, clientInfo ClientInfo) {
|
||||||
f.WriteString(" # waf networks\n")
|
f.WriteString(" # waf networks\n")
|
||||||
for _, net := range config.WAFNetworks {
|
for _, net := range config.WAFNetworks {
|
||||||
f.WriteString(fmt.Sprintf(" set_real_ip_from %s;\n", net))
|
f.WriteString(fmt.Sprintf(" set_real_ip_from %s;\n", net))
|
||||||
}
|
}
|
||||||
f.WriteString(" # antibot networks\n")
|
if !clientInfo.SpAntiddos && len(clientInfo.RealIPNetworks) > 0 {
|
||||||
for _, net := range config.AntibotNetworks {
|
// Кастомные сети вместо антиддос сетей
|
||||||
f.WriteString(fmt.Sprintf(" set_real_ip_from %s;\n", net))
|
f.WriteString(" # custom real_ip networks\n")
|
||||||
|
for _, net := range clientInfo.RealIPNetworks {
|
||||||
|
f.WriteString(fmt.Sprintf(" set_real_ip_from %s;\n", net))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.WriteString(" # antibot networks\n")
|
||||||
|
for _, net := range config.AntibotNetworks {
|
||||||
|
f.WriteString(fmt.Sprintf(" set_real_ip_from %s;\n", net))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
f.WriteString("\n real_ip_header X-Forwarded-For;\n")
|
f.WriteString("\n real_ip_header X-Forwarded-For;\n")
|
||||||
f.WriteString(" real_ip_recursive on;\n\n")
|
f.WriteString(" real_ip_recursive on;\n\n")
|
||||||
|
|
@ -500,7 +508,7 @@ func writeNginxCustomOrDefaultDirectives(f *os.File, customDirectives string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateAngieCustomServerBlock генерирует кастомный server блок для Angie
|
// generateAngieCustomServerBlock генерирует кастомный server блок для Angie
|
||||||
func generateAngieCustomServerBlock(f *os.File, config Config, res ResourceData, block CustomServerBlock, containerPorts []PortMapping, customLocationStr string, debug bool) {
|
func generateAngieCustomServerBlock(f *os.File, config Config, clientInfo ClientInfo, res ResourceData, block CustomServerBlock, containerPorts []PortMapping, customLocationStr string, debug bool) {
|
||||||
var upstreamName string
|
var upstreamName string
|
||||||
if block.IsHTTPS {
|
if block.IsHTTPS {
|
||||||
upstreamName = fmt.Sprintf("secure%d_%d_%s", res.L7ResourceID, block.Port, res.ServerName)
|
upstreamName = fmt.Sprintf("secure%d_%d_%s", res.L7ResourceID, block.Port, res.ServerName)
|
||||||
|
|
@ -538,7 +546,7 @@ func generateAngieCustomServerBlock(f *os.File, config Config, res ResourceData,
|
||||||
f.WriteString(fmt.Sprintf(" error_log /var/log/angie/%d_%s_port%d_error.log error;\n\n",
|
f.WriteString(fmt.Sprintf(" error_log /var/log/angie/%d_%s_port%d_error.log error;\n\n",
|
||||||
res.L7ResourceID, sanitizeServerName(block.ServerName), block.Port))
|
res.L7ResourceID, sanitizeServerName(block.ServerName), block.Port))
|
||||||
|
|
||||||
writeNetworkSettings(f, config)
|
writeNetworkSettings(f, config, clientInfo)
|
||||||
writeStandardServerSettings(f, getCustomServerDirectives(res.AppsSettings.ServerDirectivesAngieCustom), debug, getProxyNextUpstream(res.AppsSettings))
|
writeStandardServerSettings(f, getCustomServerDirectives(res.AppsSettings.ServerDirectivesAngieCustom), debug, getProxyNextUpstream(res.AppsSettings))
|
||||||
|
|
||||||
// Кастомные location блоки из location_angie_custom (перед location /)
|
// Кастомные location блоки из location_angie_custom (перед location /)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ func processPTAFClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Автоочистка устаревших маркеров drain
|
// Автоочистка устаревших маркеров drain
|
||||||
cleanStaleDrainMarkers(clientInfo)
|
cleanStaleDrainMarkers(clientInfo, hostname)
|
||||||
|
|
||||||
// Получение данных ресурсов
|
// Получение данных ресурсов
|
||||||
resourcesData := fetchPTAFResourcesData(db, config, appsSettingsList)
|
resourcesData := fetchPTAFResourcesData(db, config, appsSettingsList)
|
||||||
|
|
@ -95,14 +95,14 @@ func processPTAFClient(
|
||||||
// ════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
// 1. Генерация конфигурации Angie БЕЗ автоматического reload
|
// 1. Генерация конфигурации Angie БЕЗ автоматического reload
|
||||||
angieChanged, err := generateAngieConfigsWithoutReload(config, clientInfo.ClientTitle, resourcesData, resourcePortMap, clientInfo.Debug)
|
angieChanged, err := generateAngieConfigsWithoutReload(config, clientInfo.ClientTitle, clientInfo, resourcesData, resourcePortMap, clientInfo.Debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Ошибка генерации конфигов Angie для %s: %v", clientInfo.ClientTitle, err)
|
log.Printf("Ошибка генерации конфигов Angie для %s: %v", clientInfo.ClientTitle, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Создание директорий и конфигов для контейнеров (пересоздание если нужно)
|
// 2. Создание директорий и конфигов для контейнеров (пересоздание если нужно)
|
||||||
err = setupContainers(config, clientInfo.ClientTitle, &clientInfo, resourcesData, resourcePortMap)
|
err = setupContainers(config, clientInfo.ClientTitle, &clientInfo, resourcesData, resourcePortMap, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Ошибка настройки контейнеров для %s: %v", clientInfo.ClientTitle, err)
|
log.Printf("Ошибка настройки контейнеров для %s: %v", clientInfo.ClientTitle, err)
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
2
types.go
2
types.go
|
|
@ -53,6 +53,8 @@ type ClientInfo struct {
|
||||||
Debug bool // Режим отладки (влияет на лог-уровень и переменные окружения)
|
Debug bool // Режим отладки (влияет на лог-уровень и переменные окружения)
|
||||||
ShmSize int // Размер /dev/shm в GB (дефолт 1)
|
ShmSize int // Размер /dev/shm в GB (дефолт 1)
|
||||||
PtafFallbackCode sql.NullString // Код ответа ptaf_fallback: число или 'pass' (дефолт: 418)
|
PtafFallbackCode sql.NullString // Код ответа ptaf_fallback: число или 'pass' (дефолт: 418)
|
||||||
|
SpAntiddos bool // Использовать антиддос сети для set_real_ip (дефолт true)
|
||||||
|
RealIPNetworks []string // Кастомные сети для set_real_ip если SpAntiddos = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Структуры для API ответов
|
// Структуры для API ответов
|
||||||
|
|
|
||||||
49
utils.go
49
utils.go
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -335,3 +336,51 @@ func writeCustomLocationBlock(w stringWriter, customLocationStr string, correctU
|
||||||
}
|
}
|
||||||
w.WriteString("\n")
|
w.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseHostnameForContainer парсит hostname хоста и возвращает префикс для имени контейнера.
|
||||||
|
// Примеры:
|
||||||
|
//
|
||||||
|
// PTAF-TB-docker-AZ3-HW-node01 → AZ3_hn01
|
||||||
|
// PTAF-TB-docker-AZ1-node04 → AZ1_n04
|
||||||
|
func parseHostnameForContainer(hostname string) string {
|
||||||
|
// Извлекаем AZ номер
|
||||||
|
azPart := ""
|
||||||
|
nodePart := ""
|
||||||
|
isHW := false
|
||||||
|
|
||||||
|
parts := strings.Split(hostname, "-")
|
||||||
|
for i, part := range parts {
|
||||||
|
if strings.HasPrefix(strings.ToUpper(part), "AZ") {
|
||||||
|
azPart = strings.ToUpper(part) // например AZ3
|
||||||
|
}
|
||||||
|
if strings.ToUpper(part) == "HW" {
|
||||||
|
isHW = true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(strings.ToLower(part), "node") && i == len(parts)-1 {
|
||||||
|
// Берём только цифры после "node"
|
||||||
|
nodeNum := strings.TrimPrefix(strings.ToLower(part), "node")
|
||||||
|
if isHW {
|
||||||
|
nodePart = "hn" + nodeNum
|
||||||
|
} else {
|
||||||
|
nodePart = "n" + nodeNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if azPart == "" || nodePart == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return azPart + "_" + nodePart
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildContainerFullName формирует полное имя Docker-контейнера.
|
||||||
|
// Если hostname парсится корректно: {clientTitle}_{AZx}_{hn/n}{NN}_a{001}
|
||||||
|
// Иначе fallback на старый формат: ptaf_{clientTitle}_{001}
|
||||||
|
func buildContainerFullName(clientTitle string, containerNum int, hostname string) string {
|
||||||
|
prefix := parseHostnameForContainer(hostname)
|
||||||
|
if prefix == "" {
|
||||||
|
return fmt.Sprintf("ptaf_%s_%03d", clientTitle, containerNum)
|
||||||
|
}
|
||||||
|
// prefix = "AZ3_hn01", итог: "PSB_CFA_AZ3_hn01_a001"
|
||||||
|
return fmt.Sprintf("%s_%s_a%03d", clientTitle, prefix, containerNum)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue