magos/containers_part1.go

307 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, hostname string) 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)
containerFullName := buildContainerFullName(clientTitle, containerNum, hostname)
log.Printf(" Обработка контейнера: %s", containerName)
confDDir := filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum), "conf.d")
dirs := []string{
containerDir,
confDDir,
}
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)
}
}
// 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))
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, getPtafFallbackCode(*clientInfo))
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, containerFullName, 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
}
// Сохраняем порты только если compose не изменился (нет пересоздания)
// Если compose изменился — сохраним после успешного запуска контейнера
if !composeChanges[containerNum] {
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 := buildContainerFullName(clientTitle, containerNum, hostname)
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 composeChanges[containerNum] {
updateLockStatus(fmt.Sprintf("recreating container: %s", containerFullName))
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
}
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)
updateLockStatus(fmt.Sprintf("starting container: %s", containerFullName))
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
}
if err := saveContainerPorts(containerDir, containerNum, resources, containerPorts); err != nil {
log.Printf(" ⚠ Ошибка сохранения портов: %v", err)
}
}
}
log.Printf(" Проверка лишних контейнеров для %s", clientTitle)
processDrainingContainers(clientTitle, clientInfo.ContainersCount)
return nil
}