Konfiguration, hemligheter och hälsokontroller
Hoppa till
Var vi är
Trafiken hittar in i klustret och poddarna svarar. Kvar finns två frågor som förra delen lämnade orörda, och båda handlar om vad som händer inuti en pod.
Den första: hur kör man samma applikation i test och produktion när inställningarna skiljer sig? Den andra: introduktionen nämnde att Kubernetes “frågar löpande varje pod om den mår bra” — men vad frågar den, och vad händer vid ett nej?
Varför konfiguration inte hör hemma i imagen
Lisa behöver köra API:et mot testdatabasen i test och mot den riktiga i produktion. Adressen ligger i
en miljövariabel som bakas in vid bygget, så hon bygger två images: searchapi:v3 och
searchapi:v3-test.
Det fungerar i en vecka. Sedan upptäcker teamet en bugg i produktion som inte går att återskapa i test, och ingen kan längre säga säkert om de två imagerna innehåller exakt samma kod.
Poängen med en image är att den ska vara testad en gång och körd överallt. Bygger du en image per miljö har du inte längre testat det som körs i produktion — du har testat något som liknar det.
Imagen innehåller applikationen, miljön levererar inställningarna. Samma searchapi:v3 går hela
vägen från test till produktion, och det enda som skiljer är vad den matas med vid start.
Vad en ConfigMap är
En ConfigMap är ett objekt i klustret som håller inställningar som nyckel/värde-par.
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
LOG_LEVEL: "info"
FEATURE_SEARCH: "true"
DB_HOST: "db"
Filen är vanlig YAML och hör hemma i Git, granskad som all annan kod. Värdena står i klartext, eftersom de inte är hemliga.
En ConfigMap har en storleksgräns på ungefär en megabyte. Behöver du mer är det inte konfiguration längre utan data, och hör hemma någon annanstans.
Vad en Secret är
En Secret är samma sorts objekt, avsett för värden som inte ska stå i klartext.
apiVersion: v1
kind: Secret
metadata:
name: api-secrets
type: Opaque
stringData:
DB_PASSWORD: "hemligt"
type: Opaque betyder “godtyckliga nyckel/värde-par” och är det vanliga. Det finns särskilda typer
för TLS-certifikat och registry-inloggningar, men de fungerar likadant.
ConfigMap vs Secret
De två objekten är förvånansvärt lika, och skillnaden är mindre än namnen antyder.
En ConfigMap lagrar värdet som du skrev det. Den är avsedd för sådant som får synas i en merge request: loggnivåer, funktionsflaggor, värdnamn.
En Secret lagrar värdet base64-kodat och markeras som känslig. kubectl get secret api-secrets
visar en sammanfattning, inte värdena.
Ber du om YAML eller JSON syns de base64-kodade värdena. En Secret markerar känslighet, men döljer inte värdet för någon som får läsa objektet.
Båda kan lämnas till containern som miljövariabler eller monterade filer. Men manifest, åtkomst och
hantering av känsliga värden skiljer sig, så byt inte bara kind: ConfigMap mot kind: Secret.
stringData vs data
Två fält i samma Secret som gör samma sak från olika håll.
stringData låter dig skriva värdet i klartext. Kubernetes kodar om det till base64 när objektet
sparas.
data kräver att du själv anger värdet redan base64-kodat.
Läser du tillbaka en Secret ser du alltid data — stringData finns bara vid skrivning och
försvinner ur objektet. Det är därför en Secret du precis skapat “inte ser ut som” filen du skickade
in.
En Secret är inte krypterad
Att värdet blir base64 är lätt att tolka som säkerhet. Det är det inte — base64 är kodning, inte kryptering, och vem som helst med läsrättighet kan avkoda värdet på en sekund:
kubectl get secret api-secrets \
-o jsonpath='{.data.DB_PASSWORD}' | base64 -dSom standard lagras Secrets dessutom okrypterade i klustrets databas.
Det som faktiskt skyddar dem är tre saker: behörigheter (RBAC), att kryptering i vila slås på av den som driftar klustret, och att YAML-filen med hemligheten aldrig committas.
Skillnaden mot en ConfigMap är alltså i huvudsak att en Secret markerar avsikten och hanteras varsammare av verktygen — inte att den är säker i sig.
Vill du inte lägga hemligheter i klustret alls är nästa steg att hämta dem från en extern valvtjänst vid start. Det ligger utanför den här serien, men det är dit resan går i de flesta team.
Hur värdena når containern
I Deploymenten hämtas båda objekten med envFrom, som gör varje nyckel till en miljövariabel:
spec:
containers:
- name: api
image: registry.example.com/searchapi:v3
envFrom:
- configMapRef:
name: api-config
- secretRef:
name: api-secrets
Nu har containern LOG_LEVEL, FEATURE_SEARCH, DB_HOST och DB_PASSWORD som helt vanliga
miljövariabler. Applikationskoden behöver inte ändras en rad, och vet ingenting om Kubernetes.
Vill du bara ha en enskild nyckel finns env med valueFrom i stället för envFrom.
Miljövariabel vs monterad fil
Det finns två sätt att leverera värdena, och valet får en konsekvens som förvånar.
Som miljövariabler är enklast. Applikationen läser dem som vanligt, och det fungerar i varje språk utan bibliotek.
Som monterad fil lägger Kubernetes värdena som filer i en katalog i containern. Applikationen läser filen i stället.
Skillnaden märks vid ändring: miljövariabler sätts en gång vid start och läses aldrig om. En ConfigMap eller Secret som monteras som vanlig volym uppdateras efter en stund, men applikationen måste läsa om filen.
Monteras filen med subPath uppdateras den inte automatiskt.
Miljövariabler är enklare. Monterade filer är mer flexibla, och nödvändiga för värden som är för stora eller för strukturerade för en variabel, som ett certifikat.
När ändringen slår igenom
Ändrar du en ConfigMap händer ingenting med poddar som redan kör.
De fick sina miljövariabler när de startade och fortsätter med de gamla värdena tills de byts ut. Ingen varning, inget felmeddelande — bara ett värde som envist är kvar.
kubectl rollout restart deployment/api
Kommandot startar en ny utrullning. Hur många poddar som byts samtidigt beror på Deploymentens strategi. De nya poddarna plockar upp de nya värdena när de startar.
Vad en probe är
En probe är en kontroll Kubernetes kör mot en container med jämna mellanrum för att avgöra hur den mår.
Kontrollen är oftast ett HTTP-anrop mot en sökväg du bestämmer, men kan också vara ett kommando som körs inuti containern eller en TCP-anslutning mot en port.
Det finns tre sorter, och de gör helt olika saker vid ett nej. Att blanda ihop dem är den vanligaste allvarliga missen i Kubernetes-konfiguration.
Vad livenessProbe gör
livenessProbe frågar: är den här containern bortom räddning?
Vid nej startas containern om. Podden behåller sitt namn, men processen inuti dödas och startas på nytt.
Sonden ska därför bara svara på något den egna processen rår över — att den inte hängt sig, låst sig eller slutat svara.
Vad readinessProbe gör
readinessProbe frågar: kan den här podden ta emot ett anrop just nu?
Vid nej tas podden ur trafik men får leva. Servicen slutar skicka anrop till den, och börjar igen så fort svaret blir ja.
Här är det däremot rimligt att ta hänsyn till beroenden. En pod som tappat kontakten med databasen kan gott sluta ta emot anrop en stund.
Liveness vs readiness
Samma sorts anrop, helt olika konsekvens vid nej.
Frågar: är den här containern bortom räddning?
Vid nej: containern startas om.
Ska svara på något som bara den egna processen rår över — att den inte hängt sig.
Frågar: kan den här podden ta emot ett anrop nu?
Vid nej: podden tas ur trafik, men fortsätter köra.
Får gärna ta hänsyn till att beroenden tillfälligt är otillgängliga.
Tumregeln: är det något en omstart skulle laga? Ja → liveness. Nej → readiness.
Vad startupProbe löser
startupProbe finns för applikationer som tar lång tid att komma igång.
Så länge den kör är de andra två pausade. Först när den lyckats tar readiness och liveness vid.
Utan den skulle du behöva ge livenessProbe en lång tolerans, så att en app som startar på tre
minuter inte dödas i onödan. Den toleransen skulle sedan gälla för alltid, även när appen verkligen
hängt sig.
Sonderna i ordning
startupProbe:
httpGet:
path: /health
port: 5000
failureThreshold: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 5000
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 5000
periodSeconds: 10
startupProbe tillåter här 30 misslyckanden med tio sekunders mellanrum — alltså ungefär fem
minuter. Därefter startar kubelet om containern. Det är inte ett permanent stopp; nästa start får
ett nytt försök.
Fallgropen: omstartsstormen
Den vanligaste allvarliga missen: låta livenessProbe peka på en endpoint som kontrollerar
databasen.
Blir databasen seg svarar alla poddar nej samtidigt. Kubernetes gör då exakt som den blivit tillsagd och startar om samtliga — vilket ger en anstormning av nya anslutningar mot en databas som redan har det svårt. Systemet går från långsamt till nere, och orsaken var övervakningen.
Regeln: livenessProbe ska bara svara på om processen lever. Beroenden hör hemma i
readinessProbe, där konsekvensen är att podden pausas i stället för att dödas.
Vad det här betyder i ditt arbete
Du kommer ofta att använda envFrom och probes i Deployment-manifest. De få raderna påverkar hur
systemet beter sig en dålig dag, så välj dem utifrån tjänstens faktiska behov.
Vid felsökning kan en ändrad ConfigMap som inte slagit igenom kräva en ny utrullning. En pod som
startar om i loop kan också bero på en felinställd livenessProbe, men kontrollera poddens händelser
och loggar innan du väljer åtgärd.
RBAC, kryptering i vila och externa valvtjänster blir dina den dag du äger klustret. Det som inte kan vänta är att veta att en Secret inte är krypterad, för det påverkar vad du får lägga i den redan i dag.
Vanliga nybörjarförväxlingar
En Secret är inte krypterad. Base64 är kodning, och vem som helst med läsrätt kan avkoda den.
ConfigMap och Secret når containern på samma sätt. Skillnaden är avsikt och hur verktygen och behörigheterna hanterar dem.
stringData finns inte kvar i objektet. Läser du tillbaka Secreten ser du data, base64-kodat.
En ändrad ConfigMap uppdaterar inte körande poddar som läser den som miljövariabler. De sätts vid start.
livenessProbe och readinessProbe är inte utbytbara. Den ena startar om, den andra tar ur trafik.
En livenessProbe som kontrollerar databasen är inte grundlig. Den är en risk.
Prova själv
Det här kräver inget kluster. --dry-run=client betyder att kubectl bygger objektet lokalt och
skriver ut det utan att kontakta något.
Skapa först en ConfigMap och titta på resultatet:
kubectl create configmap api-config \
--from-literal=LOG_LEVEL=info \
--dry-run=client -o yaml
Under data: står LOG_LEVEL: info — precis som du skrev det. Gör nu samma sak som en Secret:
kubectl create secret generic api-secrets \
--from-literal=DB_PASSWORD=hemligt \
--dry-run=client -o yaml
Nu står det DB_PASSWORD: aGVtbGlndA==. Värdet ser skyddat ut. Avkoda det:
echo "aGVtbGlndA==" | base64 -d
Du får tillbaka hemligt. Det tog ett kommando och ingen nyckel — vilket är hela poängen med
avsnittet om att en Secret inte är krypterad.
Lägg märke till att de två utskrifterna är nästan identiska. Enda skillnaden är kind och att värdet
kodats om. Ingen av dem är skyddad av sig själv.
Tre saker som kan förvirra: --dry-run=client skickar ingenting till ett kluster, så kommandot
fungerar även om kubectl get pods skulle klaga på att den inte når någon server. Utskriften saknar
stringData trots att du angav ett klartextvärde — det fältet finns bara vid skrivning. Och på äldre
macOS heter avkodningsflaggan -D i stället för -d.
- Konfiguration hör inte hemma i imagen. Samma image ska gå från test till produktion.
ConfigMapför vanliga inställningar,Secretför känsliga — men base64 är inte kryptering.stringDataskrivs i klartext och lagras somdatai base64.- Miljövariabler sätts vid start. En ändrad ConfigMap som läses som miljövariabler kräver
kubectl rollout restart. - Monterade filer uppdateras efter en stund, men appen måste läsa om dem.
subPathuppdateras inte. livenessProbestartar om,readinessProbetar ur trafik,startupProbeger starten tid.- Låt aldrig
livenessProbebero på en databas — det förvandlar en långsam dag till ett avbrott.
Varför är det ett problem att bygga en image per miljö?
En kollega säger att lösenordet är säkert eftersom det ligger i en Secret. Har hen rätt?
Du ändrar ett värde i en ConfigMap. Poddarna fortsätter använda det gamla. Varför?
kubectl rollout restart deployment/api så startar en ny utrullning och nya poddar läser värdet. Monterade filer uppdateras efter en stund, men applikationen måste läsa om dem. subPath uppdateras inte.Vad är den praktiska skillnaden mellan livenessProbe och readinessProbe?
Din applikation tar tre minuter att starta. Varför är startupProbe bättre än att bara ge livenessProbe lång tolerans?
startupProbe pausar de andra sonderna under starten och lämnar sedan över, så att liveness kan vara strikt i normaldrift.Varför ska livenessProbe inte kontrollera databasanslutningen?
Snabb sammanfattning
- Imagen bär applikationen, klustret bär inställningarna. En image genom alla miljöer.
- En ConfigMap håller inställningar som nyckel/värde-par, i klartext, i Git.
- En Secret är samma sak för känsliga värden, lagrade base64-kodade.
- De når containern på samma sätt. Skillnaden är avsikt och hur verktygen och behörigheterna hanterar dem.
stringDataskrivs i klartext och sparas somdatai base64. Fältet finns inte vid läsning.- Base64 är kodning, inte kryptering. RBAC, kryptering i vila och
.gitignoreär skyddet. envFromgör varje nyckel till en miljövariabel.envmedvalueFromför en enskild.- Miljövariabler sätts vid start. Monterade filer uppdateras men måste läsas om.
- En ändrad ConfigMap som läses som miljövariabler kräver
kubectl rollout restartför att slå igenom. - En probe är en återkommande kontroll: HTTP-anrop, kommando eller TCP-anslutning.
- Liveness startar om, readiness tar ur trafik, startup ger starten tid och pausar de andra.
- En
livenessProbesom kontrollerar beroenden förvandlar en långsam dag till ett avbrott.
Nästa del
Nu vet Kubernetes vad podden ska matas med och om den mår bra. En sak till behöver klustret veta innan podden är fullt beskriven: hur mycket den får ta.
Nästa del handlar om resurser — hur mycket minne och processorkraft en pod får begära, och vad som händer när den tar mer. Svaret är olika för de två, och skillnaden avgör om podden blir långsam eller dör.