Zarządzanie konfiguracją aplikacji: 10 typowych błędów w środowisku produkcyjnym
Niepoprawne limity czasu, tajne dane lub ograniczenia w konfiguracji powodują awarie szybciej niż błędy w logice aplikacji. W przeciwieństwie do kodu źródłowego, konfiguracje często omijają przeglądy kodu (code review), testy oraz potok CI. To tworzy ryzyko: usługa zapisuje dane do niewłaściwej bazy danych, wyczerpuje pulę połączeń lub ujawnia poufne dane. Analiza rzeczywistych projektów wykazała 10 błędów, które pozostają ukryte aż do wystąpienia incydentu.
Hardkodowanie limitów czasu: ponad 30 miejsc w kodzie
Limity czasu zakodowane w źródle wymagają ponownej kompilacji przy każdej zmianie. Wyszukiwanie frazy Duration.ofSeconds wykazało ponad 30 przypadków z różnymi wartościami: 10 s w NotificationService, 30 s w SearchService, 15 s w ApiRoutes.
private static final Duration TIMEOUT = Duration.ofSeconds(10); // NotificationService
private static final Duration TIMEOUT = Duration.ofSeconds(30); // SearchService
private static final Duration TIMEOUT = Duration.ofSeconds(10); // AuthFilter
private static final Duration TIMEOUT = Duration.ofSeconds(15); // ApiRoutes
Pod obciążeniem prawidłowa konfiguracja limitów czasu jest krytyczna, ale hardkod uniemożliwia elastyczność. Rozwiązanie: przeniesienie do pliku konfiguracyjnego z możliwością nadpisania przez zmienne środowiskowe.
Brak walidacji: kasowanie bazy danych produkcyjnej
Obiekt konfiguracyjny o długości 695 linii sprawdza tylko jeden parametr. Pozostałe są akceptowane bez żadnych ograniczeń: rozmiar puli = 0, limit czasu = -1, pusty adres URL bazy danych.
String authType = config.getString("authType");
if (!Set.of("kerberos", "jwt").contains(authType)) {
throw new IllegalArgumentException("Nieprawidłowy typ uwierzytelnienia. Dopuszczalne wartości to: kerberos, jwt");
}
Niebezpieczny przykład: http.server.parsing.max-content-length = 10g — pojedynczy żądanie zużywa 10 GB pamięci RAM. Flaga dropAll = ${?DROP_ALL} dla Liquibase kasuje całą schemę bazy danych bez potwierdzenia. Mechanizm fail-fast podczas uruchamiania aplikacji zapobiega incydentom.
Wartości domyślne maskują problemy
Wartości domyślne ukrywają brak kluczy konfiguracyjnych aż do momentu uruchomienia aplikacji. Przy włączaniu SSO:
Duration maxResponseDelay = ssoEnabled
? config.getDuration("sso.maxResponseDelay")
: Duration.ofMinutes(10);
long responseCacheSize = ssoEnabled
? config.getLong("sso.responseCacheSize")
: 10_000;
Brak kluczy powoduje awarię w środowisku produkcyjnym. Podobnie wartość tokenTtl: skonfigurowana jako 1 minuta nieoczekiwanie staje się 3 minutami bez wpisu w logach, co narusza zasady bezpieczeństwa.
- Problem wartości domyślnych: ukrywają błędne ustawienia aż do aktywacji funkcji.
- Rozwiązanie: walidacja podczas uruchamiania oraz ostrzeżenia przy użyciu wartości domyślnej.
Rozbieżności konfiguracji między środowiskiem testowym a produkcyjnym
Konfiguracja testowa różni się od produkcyjnej o kilka rzędów wielkości:
| Parametr | Produkcja | Test |
|----------|-----------|------|
| api.responseLimit | 10 MB | 1 GB |
| api.contextLimit | 10 MB | 1 MB |
Odpowiedź o rozmiarze 50 MB przejdzie testy, ale spowoduje awarię w produkcji. Odwrotna sytuacja: testy są zbyt restrykcyjne i generują fałszywe wyniki negatywne.
Chaos w nazewnictwie parametrów
Jeden plik application.conf miesza różne style:
fileStoragePath = "/opt/storage" # camelCase
retention-days = 7 # kebab-case
createdump-interval-seconds = 240 # hybryda
usage-threshold = 80 # kebab-case
compressHeapDump = true # camelCase
Flagi logiczne: sslValidationEnabled, smtpSslEnable, saml.enabled, isUseSecureCookieJWT. Nadpisywanie przez zmienne środowiskowe nie działa: smtpSslEnable → SMTP_SSL_ENABLED (różnica między Enable a Enabled).
- Mieszanie
camelCaseikebab-casew jednym pliku. - Niezgodność między składnią HOCON a konwencją nazewnictwa zmiennych środowiskowych.
- Cztery różne konwencje nazewnictwa flag logicznych.
Martwe flagi funkcji bez cyklu życia
16 flag funkcji w klasie konfiguracyjnej: ws-cache-enabled=false wszędzie, history-import-enabled=true zawsze. Brak dat, autorów ani uzasadnienia — rośnie obciążenie poznawcze.
- Dodaj: datę, autora, uzasadnienie i termin usunięcia.
- Usuń „nieśmiertelne” flagi: zawsze
true/false, nieużywane w kodzie.
Niepoprawna kolejność nadpisywania w HOCON
W HOCON decyduje ostatnia przypisana wartość. Poprawnie:
key = "default-value"
key = ${?ENV_OVERRIDE}
Błąd:
dump {
enabled = ${?DUMP_ENABLED}
enabled = false
directory = ${?DUMP_DIR}
directory = "opt/myapp/dumps/auto"
}
Zmienna środowiskowa zostaje nadpisana przez false — zrzuty nie są włączone.
Konfiguracja bez dokumentacji: ponad 100 parametrów
Plik docker.md zawiera listę zmiennych środowiskowych bez opisów: EMAIL_ENABLED=false/true. Klasa AppConfig.java ma 695 linii bez komentarzy Javadoc. Parametr sync-request-timeout-seconds=600 — do czego służy? workflowStepLimit=200 — jakie są konsekwencje ustawienia 0 lub 100 000?
Hardkod zewnętrznych adresów URL i poziomów logowania
Adresy URL API zakodowane w źródle wymagają ponownej kompilacji przy zmianie punktu końcowego. Poziom logowania w obrazie Docker wymaga montowania woluminu lub ponownej budowy obrazu, aby włączyć tryb DEBUG.
Na co zwrócić uwagę:
- Wszystkie parametry przenieść do konfiguracji z walidacją podczas uruchamiania.
- Jednolity styl nazewnictwa dla HOCON i zmiennych środowiskowych.
- Dokumentacja każdego parametru: typ, zakres dopuszczalnych wartości, wartość domyślna, cel użycia.
- Fail-fast zamiast cichego stosowania wartości domyślnej.
- Cykl życia flag funkcji: data dodania, uzasadnienie i termin usunięcia.
— Editorial Team
Brak komentarzy.