TGViewer
Make. Build. Break. Reflect. Make. Build. Break. Reflect. @makebreakreflect · 1.37K subscribers
Post #287 1.05K
#kubernetes #golang #troubleshooting

Снова про операторы.

У ресурса Secret в Kubernetes есть два поля для данных:
- Data map[string][]byte - бинарные данные, в YAML выглядят как base64
- StringData map[string]string - строковые данные, plain text в YAML

Как это работает на самом деле:
StringData - write-only поле для удобства.
Когда ты создаёшь или обновляешь секрет, API-сервер мержит StringData в Data (ключи из StringData перезаписывают Data при совпадении).
В etcd хранилке и в ответе API сохраняется только Data.
При чтении секрета из кластера поле StringData всегда пустое - его там просто нет.
Поэтому StringData - для людей и YAML-манифестов, а Data - для кода операторов.


А теперь типичнейшая ошибка, на которую я снова попался. 😭

Код оператора с ошибкой:
func BuildCredentialsSecret(instance *v1alpha1.MyApp) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: instance.Name + "-credentials",
Namespace: instance.Namespace,
},
Type: corev1.SecretTypeOpaque,
StringData: map[string]string{
"API_HOST": instance.Spec.Config.API.Host,
"API_TOKEN": instance.Spec.Config.API.Token,
},
}
}

В реконсайлере проверка "изменился ли секрет?":
func secretDataEqual(a, b map[string][]byte) bool {
if len(a) != len(b) {
return false
}
for k, v := range a {
if !bytes.Equal(v, b[k]) {
return false
}
}
return true
}

// в reconcile:
if !secretDataEqual(found.Data, secret.Data) {
// обновляем секрет
found.Data = secret.Data // ВОТ ТУТ ПРОБЛЕМА
return r.Update(ctx, found)
}

Видите проблему?

При создании desired-секрета мы используем StringData.
Но в Go-структуре этого объекта поле Data равно nil, потому что мы его не заполняли!!!

Что происходит:
- оператор создаёт секрет с StringData > API сервер конвертирует в Data, в кластере всё ок
- при следующем reconcile оператор читает секрет - там Data заполнен
- оператор строит desired-секрет - там Data = nil, StringData заполнен
- сравнение видит разницу:
len(found.Data) != 0, а len(secret.Data) == 0 

(потому что nil)
- оператор решает обновить секрет и делает
found.Data = secret.Data

записывая nil в поле данных!
- теперь в кластере секрет с
data: {}

(0 ключей)
- в следующем reconcile сравнение
secretDataEqual(found.Data, nil)

оба пустые? Да!
(len(nil) == 0, len(map{}) == 0)

- оператор думает: "секрет актуален", и никогда не восстанавливает данные 🤡

Результат: секрет живёт, ownerReference на месте, лейблы есть, а данных - ноль.
Поды падают с:
MountVolume.SetUp failed for volume "credentials": 
references non-existent secret key: API_TOKEN

Фикс очевиден: использовать Data в коде оператора:
func BuildCredentialsSecret(instance *v1alpha1.MyApp) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: instance.Name + "-credentials",
Namespace: instance.Namespace,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"API_HOST": []byte(instance.Spec.Config.API.Host),
"API_TOKEN": []byte(instance.Spec.Config.API.Token),
},
}
}


Теперь secret.Data заполнен, сравнение работает корректно, и ты случайно не затираешь данные nil-ом.
🎉🎉🎉

Мораль:
- stringData - для YAML и kubectl, где удобно писать plain text
- data - для кода операторов на Go
- в client-go структуре StringData и Data - разные поля; API сервер не синхронизирует их при чтении
- если используешь StringData в коде, помни: secret.Data == nil в этой структуре
- сравнивай и применяй то, что реально используешь

Потерял на этом баге часа два, пока не дошло. Сука.
Секрет создавался, лейблы были, ownerReference был, а данных - ноль.

Классика - симптомы в одном месте (падающие поды), причина в другом (nil вместо мапы в билдере).
  • ❤ 6
  • 👍 6
More from @makebreakreflect
  1. Sep 23, 2026В удивительнейшее время живём. - https://typesafe.ai/blog/introducing-system-one-models-an…
  2. Sep 22, 2026#aws #elasticache #redis #kubernetes #troubleshooting #devops #sre #longread Ничего не пре…
  3. Sep 18, 2026Вся эта неделя была очень странной. Опус отупел до уровня 3 модели. Фейбл сжигает токены б…
  4. Sep 16, 2026Post #382
  5. Sep 15, 2026Apple наконец слила beta и release в один продукт и избавились от лишнего шага в релизном…
  6. Sep 14, 2026#мысли #devops #aws Куча людей перешли на искусственный интеллект, так и не освоив собстве…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →