magos/containers_part1.go
2026-04-09 15:41:54 +03:00

321 lines
13 KiB
Go
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.

package main
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func setupContainers(config Config, clientTitle string, clientInfo *ClientInfo, resources []ResourceData, resourcePortMap map[int][]PortMapping) error {
log.Printf("Настройка контейнеров для %s", clientTitle)
baseDir := filepath.Join("/home/install/ptaf", clientTitle)
containerChanges := make(map[int]bool)
composeChanges := make(map[int]bool)
for containerNum := 1; containerNum <= clientInfo.ContainersCount; containerNum++ {
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
containerDir := filepath.Join(baseDir, containerName)
log.Printf(" Обработка контейнера: %s", containerName)
dirs := []string{
containerDir,
filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum), "conf.d"),
}
for _, dir := range dirs {
existed := false
if _, err := os.Stat(dir); err == nil {
existed = true
}
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("ошибка создания директории %s: %w", dir, err)
}
if !existed {
log.Printf(" ✓ Создана директория: %s", dir)
}
}
logDir := filepath.Join("/var/log/ptaf_nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum))
logDirExisted := false
if _, err := os.Stat(logDir); err == nil {
logDirExisted = true
}
if err := os.MkdirAll(logDir, 0777); err != nil {
return fmt.Errorf("ошибка создания директории логов %s: %w", logDir, err)
}
if err := os.Chmod(logDir, 0777); err != nil {
return fmt.Errorf("ошибка установки прав для директории логов %s: %w", logDir, err)
}
if !logDirExisted {
log.Printf(" ✓ Создана директория логов: %s (права: 0777)", logDir)
}
nginxConfPath := filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum), "nginx.conf")
existingPodIP := ""
if info, err := os.Stat(nginxConfPath); err == nil {
if info.IsDir() {
log.Printf(" ⚠ nginx.conf является директорией, удаляем...")
os.RemoveAll(nginxConfPath)
} else {
existingPodIP = extractPodIPFromConfig(nginxConfPath)
}
}
ptafConfig := ""
if clientInfo.PTAFConfig.Valid {
ptafConfig = clientInfo.PTAFConfig.String
}
changed, err := generateNginxConf(nginxConfPath, config, ptafConfig, existingPodIP, clientInfo.Debug)
if err != nil {
return fmt.Errorf("ошибка генерации nginx.conf: %w", err)
}
if changed {
log.Printf(" ✓ nginx.conf изменен: %s", nginxConfPath)
containerChanges[containerNum] = true
} else {
log.Printf(" ○ nginx.conf не изменился: %s", nginxConfPath)
}
mimeTypesPath := filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum), "mime.types")
if info, err := os.Stat(mimeTypesPath); err == nil {
if info.IsDir() {
log.Printf(" ⚠ mime.types является директорией, удаляем...")
os.RemoveAll(mimeTypesPath)
}
}
changed, err = generateMimeTypes(mimeTypesPath)
if err != nil {
return fmt.Errorf("ошибка генерации mime.types: %w", err)
}
if changed {
log.Printf(" ✓ mime.types изменен: %s", mimeTypesPath)
containerChanges[containerNum] = true
}
confDPath := filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum), "conf.d")
// Удаляем конфиги ресурсов которых больше нет в БД
activeConfFiles := make(map[string]bool)
for _, res := range resources {
activeConfFiles[fmt.Sprintf("%s_%d.conf", clientTitle, res.L7ResourceID)] = true
}
if entries, err := os.ReadDir(confDPath); err == nil {
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".conf") {
if !activeConfFiles[entry.Name()] {
stalePath := filepath.Join(confDPath, entry.Name())
if err := os.Remove(stalePath); err == nil {
log.Printf(" → Удалён устаревший конфиг ресурса: %s", entry.Name())
containerChanges[containerNum] = true
} else {
log.Printf(" ⚠ Ошибка удаления устаревшего конфига %s: %v", entry.Name(), err)
}
}
}
}
}
containerPorts := []PortMapping{}
for _, res := range resources {
ports, ok := resourcePortMap[res.L7ResourceID]
if !ok || containerNum-1 >= len(ports) {
return fmt.Errorf("не найдены порты для l7ResourceID %d контейнер %d", res.L7ResourceID, containerNum)
}
containerPorts = append(containerPorts, ports[containerNum-1])
confFile := filepath.Join(confDPath, fmt.Sprintf("%s_%d.conf", clientTitle, res.L7ResourceID))
oldHash := ""
if _, err := os.Stat(confFile); err == nil {
oldHash, _ = getFileHash(confFile)
}
err := generateNginxResourceConfig(confFile, config, res, ports[containerNum-1])
if err != nil {
return fmt.Errorf("ошибка генерации конфига для ресурса: %w", err)
}
newHash, _ := getFileHash(confFile)
if oldHash != "" && oldHash != newHash {
log.Printf(" ✓ Конфиг ресурса изменен: %s", confFile)
containerChanges[containerNum] = true
} else if oldHash == "" {
log.Printf(" ✓ Создан конфиг ресурса: %s", confFile)
containerChanges[containerNum] = true
} else {
log.Printf(" ○ Конфиг ресурса не изменился: %s", confFile)
}
}
composeFile := filepath.Join(containerDir, "docker-compose.yml")
log.Printf(" → DEBUG порты контейнера %d:", containerNum)
for idx, pm := range containerPorts {
log.Printf(" PortMapping[%d]: HTTP=%v, HTTPS=%v", idx, pm.HTTPPorts, pm.HTTPSPorts)
}
changed, err = generateDockerCompose(composeFile, config, clientTitle, clientInfo, containerNum, resources, containerPorts)
if err != nil {
return fmt.Errorf("ошибка генерации docker-compose.yml: %w", err)
}
if changed {
log.Printf(" ✓ docker-compose.yml изменен: %s", composeFile)
composeChanges[containerNum] = true
}
if err := saveContainerPorts(containerDir, containerNum, resources, containerPorts); err != nil {
log.Printf(" ⚠ Ошибка сохранения портов: %v", err)
}
}
log.Printf(" Запуск и обновление контейнеров для %s", clientTitle)
for containerNum := 1; containerNum <= clientInfo.ContainersCount; containerNum++ {
containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
containerDir := filepath.Join(baseDir, containerName)
composeFile := filepath.Join(containerDir, "docker-compose.yml")
containerFullName := fmt.Sprintf("ptaf_%s_%03d", clientTitle, containerNum)
containerExists, containerRunning := checkContainerStatus(containerFullName)
if containerExists {
if composeChanges[containerNum] {
recreateContainer(containerFullName, composeFile, containerDir, clientTitle, resources, resourcePortMap, containerNum)
continue
}
if !containerRunning {
startContainer(containerFullName, composeFile, containerDir)
} else {
// Проверяем порты только если контейнер запущен недавно (менее 5 минут)
// Это позволяет исправить ситуацию когда контейнер поднялся без портов
// но не трогает давно работающие контейнеры
startedAt, err := getContainerStartedAt(containerFullName)
if err == nil && time.Since(startedAt) < 5*time.Minute {
portsOK := true
containerPorts := []PortMapping{}
for _, res := range resources {
ports, ok := resourcePortMap[res.L7ResourceID]
if !ok || containerNum-1 >= len(ports) {
continue
}
containerPorts = append(containerPorts, ports[containerNum-1])
}
for _, pm := range containerPorts {
for _, dockerPort := range pm.HTTPPorts {
if !checkPortListening("127.0.0.1", dockerPort) {
log.Printf(" ⚠ Контейнер %s запущен недавно но порт %d не слушает — пересоздаю", containerFullName, dockerPort)
portsOK = false
break
}
}
if !portsOK {
break
}
for _, dockerPort := range pm.HTTPSPorts {
if !checkPortListening("127.0.0.1", dockerPort) {
log.Printf(" ⚠ Контейнер %s запущен недавно но порт %d не слушает — пересоздаю", containerFullName, dockerPort)
portsOK = false
break
}
}
if !portsOK {
break
}
}
if !portsOK {
recreateContainer(containerFullName, composeFile, containerDir, clientTitle, resources, resourcePortMap, containerNum)
continue
}
}
}
if containerChanges[containerNum] {
log.Printf(" Конфигурация изменилась, проверяем и перезагружаем nginx в %s", containerFullName)
if err := testNginxConfig(containerFullName); err != nil {
log.Printf(" ⚠ Ошибка проверки конфигурации в %s: %v", containerFullName, err)
continue
}
if err := reloadNginx(containerFullName); err != nil {
log.Printf(" ⚠ Ошибка перезагрузки nginx в %s: %v", containerFullName, err)
continue
}
log.Printf(" ✓ Nginx успешно перезагружен в контейнере %s", containerFullName)
} else {
log.Printf(" ○ Конфигурация не изменилась, проверка и перезагрузка не требуются для %s", containerFullName)
}
} else {
log.Printf(" Запуск нового контейнера %s", containerName)
// Собираем порты которые должен занять контейнер
portsToCheck := []int{}
for _, res := range resources {
ports, ok := resourcePortMap[res.L7ResourceID]
if !ok || containerNum-1 >= len(ports) {
continue
}
pm := ports[containerNum-1]
for _, dockerPort := range pm.HTTPPorts {
portsToCheck = append(portsToCheck, dockerPort)
}
for _, dockerPort := range pm.HTTPSPorts {
portsToCheck = append(portsToCheck, dockerPort)
}
}
// Ждём освобождения портов перед запуском
if len(portsToCheck) > 0 {
portsReady := false
for attempt := 0; attempt < 30; attempt++ {
busy := false
for _, port := range portsToCheck {
if isPortBusyByAnyProcess(port) {
if attempt == 0 {
log.Printf(" → Порт %d занят, ожидаем освобождения...", port)
}
busy = true
break
}
}
if !busy {
portsReady = true
if attempt > 0 {
log.Printf(" ✓ Порты освободились (попытка %d)", attempt+1)
}
break
}
time.Sleep(1 * time.Second)
}
if !portsReady {
log.Printf(" ⚠ Порты не освободились за 30 секунд, пробуем запустить всё равно")
}
}
const maxRetries = 3
started := false
for attempt := 1; attempt <= maxRetries; attempt++ {
cmd := exec.Command("docker-compose", "-f", composeFile, "up", "-d")
cmd.Dir = containerDir
output, err := cmd.CombinedOutput()
log.Printf(" → Вывод docker-compose: %s", string(output))
if err != nil && strings.Contains(string(output), "address already in use") {
log.Printf(" ⚠ Попытка %d/%d: порт занят, жду 3 секунды и повторяю...", attempt, maxRetries)
time.Sleep(3 * time.Second)
continue
}
time.Sleep(5 * time.Second)
_, isRunning := checkContainerStatus(containerFullName)
if !isRunning {
log.Printf(" ⚠ Попытка %d/%d: контейнер %s не запустился", attempt, maxRetries, containerFullName)
if attempt < maxRetries {
time.Sleep(3 * time.Second)
}
continue
}
log.Printf(" ✓ Контейнер %s успешно запущен (попытка %d)", containerName, attempt)
started = true
break
}
if !started {
log.Printf(" ⚠ Контейнер %s не запустился после %d попыток. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, maxRetries, containerDir)
continue
}
}
}
log.Printf(" Проверка лишних контейнеров для %s", clientTitle)
processDrainingContainers(clientTitle, clientInfo.ContainersCount)
return nil
}