This commit is contained in:
Magnus Root 2026-06-09 17:12:38 +03:00
parent 1e0690b061
commit f01944f474
4 changed files with 110 additions and 48 deletions

View file

@ -30,12 +30,18 @@ func getActualContainerCount(clientTitle string) int {
} }
// Считаем по drain-маркерам в /tmp (контейнеры могли быть уже удалены с диска) // Считаем по drain-маркерам в /tmp (контейнеры могли быть уже удалены с диска)
// Ищем маркеры по паттерну /tmp/ptaf-drain-{clientTitle}_* независимо от формата имени // Ищем маркеры по двум паттернам — с и без префикса ptaf_
matches, _ := filepath.Glob(fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle)) patterns := []string{
fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle), // новый формат: {clientTitle}_AZ1_n01_a001
fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_*", clientTitle), // fallback формат: ptaf_{clientTitle}_001
}
for _, pattern := range patterns {
matches, _ := filepath.Glob(pattern)
for _, match := range matches { for _, match := range matches {
// Извлекаем номер агента из конца имени файла (формат: ..._a001 или ..._001)
base := filepath.Base(match) base := filepath.Base(match)
base = strings.TrimPrefix(base, "ptaf-drain-") base = strings.TrimPrefix(base, "ptaf-drain-")
// Убираем возможный префикс ptaf_
base = strings.TrimPrefix(base, "ptaf_")
// Убираем префикс clientTitle_ // Убираем префикс clientTitle_
rest := strings.TrimPrefix(base, clientTitle+"_") rest := strings.TrimPrefix(base, clientTitle+"_")
// Ищем номер в конце: _a001 или _001 // Ищем номер в конце: _a001 или _001
@ -47,6 +53,7 @@ func getActualContainerCount(clientTitle string) int {
max = num max = num
} }
} }
}
return max return max
} }
@ -142,6 +149,12 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) {
continue continue
} }
clientTitle := entry.Name() clientTitle := entry.Name()
// Пропускаем блочные директории — они управляются через родительский клиент
if isBlockClientTitle(clientTitle) {
continue
}
log.Printf(" → Проверка клиента: %s", clientTitle) log.Printf(" → Проверка клиента: %s", clientTitle)
clientInfo, err := getClientInfoByClientTitle(db, clientTitle) clientInfo, err := getClientInfoByClientTitle(db, clientTitle)
@ -204,7 +217,7 @@ func processAllDrainingContainersGlobally(db *sql.DB, hostname string) {
processedClients++ processedClients++
} else { } else {
log.Printf(" ✓ Клиент должен быть здесь, проверяю устаревшие drain-маркеры") log.Printf(" ✓ Клиент должен быть здесь, проверяю устаревшие drain-маркеры")
checkAndProcessExistingDrainMarkers(clientTitle, clientInfo.ContainersCount, &processedClients) checkAndProcessExistingDrainMarkers(clientTitle, 0, &processedClients)
} }
} }
@ -221,10 +234,16 @@ 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
// Ищем маркеры по паттерну независимо от формата имени контейнера // Ищем маркеры по двум паттернам — новый и fallback формат
matches, _ := filepath.Glob(fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle)) for _, pattern := range []string{
fmt.Sprintf("/tmp/ptaf-drain-%s_*", clientTitle),
fmt.Sprintf("/tmp/ptaf-drain-ptaf_%s_*", clientTitle),
} {
matches, _ := filepath.Glob(pattern)
if len(matches) > 0 { 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 {

19
main.go
View file

@ -127,11 +127,6 @@ func main() {
log.Fatalf("Ошибка загрузки конфигурации: %v", err) log.Fatalf("Ошибка загрузки конфигурации: %v", err)
} }
// Проверяем доступность Docker daemon
if err := checkDockerAvailable(); err != nil {
log.Fatalf("❌ Docker daemon недоступен: %v", err)
}
instanceNames, err := getInstancesByHostname(db, hostname) instanceNames, err := getInstancesByHostname(db, hostname)
if err != nil { if err != nil {
log.Fatalf("Ошибка получения instances для hostname %s: %v", hostname, err) log.Fatalf("Ошибка получения instances для hostname %s: %v", hostname, err)
@ -146,8 +141,12 @@ func main() {
// Проверка наличия Docker образов для каждого PTAF-клиента отдельно // Проверка наличия Docker образов для каждого PTAF-клиента отдельно
ptafClients := getPTAFClients(db, instanceNames) ptafClients := getPTAFClients(db, instanceNames)
if len(ptafClients) == 0 { if len(ptafClients) == 0 {
log.Printf(" PTAF-клиентов на хосте нет, проверка Docker образов пропускается") log.Printf(" PTAF-клиентов на хосте нет, проверка Docker пропускается")
} else { } else {
// Проверяем Docker только если есть PTAF-клиенты
if err := checkDockerAvailable(); err != nil {
log.Fatalf("❌ Docker daemon недоступен: %v", err)
}
checkedImages := make(map[string]bool) // не проверяем один образ дважды checkedImages := make(map[string]bool) // не проверяем один образ дважды
for _, clientInfo := range ptafClients { for _, clientInfo := range ptafClients {
image, url := getClientDockerImage(clientInfo, config) image, url := getClientDockerImage(clientInfo, config)
@ -437,7 +436,9 @@ func cleanStaleDrainMarkers(clientInfo ClientInfo, hostname string) {
cleanedContainers++ cleanedContainers++
} }
} else if found && !isMigration { } else if found && !isMigration {
if containerNum <= clientInfo.ContainersCount { // Не удаляем маркер если клиент в блочном режиме —
// контейнер помечен на drain из-за перехода one_container: true → false
if clientInfo.OneContainer && containerNum <= clientInfo.ContainersCount {
if err := os.Remove(markerFile); err == nil { if err := os.Remove(markerFile); err == nil {
log.Printf(" ✓ Удалён устаревший маркер drain для контейнера %s (масштабирование отменено)", containerFullName) log.Printf(" ✓ Удалён устаревший маркер drain для контейнера %s (масштабирование отменено)", containerFullName)
cleanedContainers++ cleanedContainers++
@ -449,11 +450,15 @@ func cleanStaleDrainMarkers(clientInfo ClientInfo, hostname string) {
angieMarkerFile := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientInfo.ClientTitle) angieMarkerFile := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientInfo.ClientTitle)
if _, err := os.Stat(angieMarkerFile); err == nil { if _, err := os.Stat(angieMarkerFile); err == nil {
// Не удаляем маркер Angie если клиент в блочном режиме —
// конфиг помечен на drain из-за перехода one_container: true → false
if clientInfo.OneContainer {
if err := os.Remove(angieMarkerFile); err == nil { if err := os.Remove(angieMarkerFile); err == nil {
log.Printf(" ✓ Удалён устаревший маркер drain для конфига Angie (миграция отменена)") log.Printf(" ✓ Удалён устаревший маркер drain для конфига Angie (миграция отменена)")
cleanedAngieMarker = true cleanedAngieMarker = true
} }
} }
}
if cleanedContainers > 0 || cleanedAngieMarker { if cleanedContainers > 0 || cleanedAngieMarker {
log.Printf(" ✓ Очищено устаревших маркеров: контейнеры=%d, конфиг Angie=%v", cleanedContainers, cleanedAngieMarker) log.Printf(" ✓ Очищено устаревших маркеров: контейнеры=%d, конфиг Angie=%v", cleanedContainers, cleanedAngieMarker)

View file

@ -73,7 +73,10 @@ func processPTAFClient(
} }
// Автоочистка устаревших маркеров drain // Автоочистка устаревших маркеров drain
// При блочном режиме не очищаем — маркеры управляются через cleanupOneContainerContainers
if clientInfo.OneContainer {
cleanStaleDrainMarkers(clientInfo, hostname) cleanStaleDrainMarkers(clientInfo, hostname)
}
// Получение данных ресурсов // Получение данных ресурсов
resourcesData := fetchPTAFResourcesData(db, config, appsSettingsList) resourcesData := fetchPTAFResourcesData(db, config, appsSettingsList)
@ -441,12 +444,16 @@ func cleanupStaleBlocks(clientTitle string, activeBlocks map[string]bool, hostna
log.Printf(" → Удаление устаревшего блока %s для %s", blockName, clientTitle) log.Printf(" → Удаление устаревшего блока %s для %s", blockName, clientTitle)
blockClientTitle := fmt.Sprintf("%s_%s", clientTitle, blockName) blockClientTitle := fmt.Sprintf("%s_%s", clientTitle, blockName)
// Помечаем контейнеры на drain // Помечаем контейнеры на drain (только если маркер ещё не существует)
actualCount := getActualContainerCount(blockClientTitle) actualCount := getActualContainerCount(blockClientTitle)
for containerNum := 1; containerNum <= actualCount; containerNum++ { for containerNum := 1; containerNum <= actualCount; containerNum++ {
containerFullName := buildContainerFullName(blockClientTitle, containerNum, hostname) containerFullName := buildContainerFullName(blockClientTitle, containerNum, hostname)
exists, _ := checkContainerStatus(containerFullName) exists, _ := checkContainerStatus(containerFullName)
if exists { if exists {
markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName)
if _, err := os.Stat(markerFile); err == nil {
continue // маркер уже есть — не перезаписываем
}
if err := markContainerForDrain(containerFullName, false); err != nil { if err := markContainerForDrain(containerFullName, false); err != nil {
log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err) log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err)
} else { } else {
@ -455,7 +462,9 @@ func cleanupStaleBlocks(clientTitle string, activeBlocks map[string]bool, hostna
} }
} }
// Помечаем Angie конфиг для drain // Помечаем Angie конфиг для drain (только если маркер ещё не существует)
angieMarker := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", blockClientTitle)
if _, err := os.Stat(angieMarker); err != nil {
if err := markAngieConfigForDrain(blockClientTitle, false); err != nil { if err := markAngieConfigForDrain(blockClientTitle, false); err != nil {
log.Printf(" ⚠ Ошибка пометки Angie конфига для %s: %v", blockClientTitle, err) log.Printf(" ⚠ Ошибка пометки Angie конфига для %s: %v", blockClientTitle, err)
} else { } else {
@ -463,6 +472,7 @@ func cleanupStaleBlocks(clientTitle string, activeBlocks map[string]bool, hostna
} }
} }
} }
}
// cleanupBlockContainers дренирует все блочные контейнеры клиента // cleanupBlockContainers дренирует все блочные контейнеры клиента
// Используется при переходе one_container: false → true // Используется при переходе one_container: false → true
@ -485,6 +495,12 @@ func cleanupOneContainerContainers(clientTitle string, hostname string) {
containerFullName := buildContainerFullName(clientTitle, containerNum, hostname) containerFullName := buildContainerFullName(clientTitle, containerNum, hostname)
exists, _ := checkContainerStatus(containerFullName) exists, _ := checkContainerStatus(containerFullName)
if exists { if exists {
// Проверяем что маркер ещё не существует чтобы не сбрасывать таймер
markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerFullName)
if _, err := os.Stat(markerFile); err == nil {
// Маркер уже есть — не перезаписываем
continue
}
hasNonBlock = true hasNonBlock = true
if err := markContainerForDrain(containerFullName, false); err != nil { if err := markContainerForDrain(containerFullName, false); err != nil {
log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err) log.Printf(" ⚠ Ошибка пометки контейнера %s: %v", containerFullName, err)
@ -494,6 +510,11 @@ func cleanupOneContainerContainers(clientTitle string, hostname string) {
} }
} }
if hasNonBlock { if hasNonBlock {
angieMarker := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle)
if _, err := os.Stat(angieMarker); err == nil {
// Маркер Angie уже есть — не перезаписываем
return
}
if err := markAngieConfigForDrain(clientTitle, false); err != nil { if err := markAngieConfigForDrain(clientTitle, false); err != nil {
log.Printf(" ⚠ Ошибка пометки Angie конфига: %v", err) log.Printf(" ⚠ Ошибка пометки Angie конфига: %v", err)
} }

View file

@ -424,3 +424,20 @@ func parseSidBlocks(sidBlock string) ([][]int, error) {
} }
return blocks, nil return blocks, nil
} }
// isBlockClientTitle проверяет является ли clientTitle блочным виртуальным именем
// вида {clientTitle}_block{N} которые создаются при one_container=false
func isBlockClientTitle(clientTitle string) bool {
// Ищем паттерн _block{число} в конце строки
parts := strings.Split(clientTitle, "_block")
if len(parts) < 2 {
return false
}
lastPart := parts[len(parts)-1]
for _, c := range lastPart {
if c < '0' || c > '9' {
return false
}
}
return len(lastPart) > 0
}