Die toelatingswebhook wat ja gesê het en ons tog platgetrek het
Ons bedryf 'n verifikasiediens, en daarom is die vraag "is die houer wat pas begin het werklik die houer wat ons gebou het?" nie akademies nie. Ons antwoord was handtekeninge: teken elke beeld in CI, verifieer voor ontplooiing, en sit dan, as 'n tweede laag, 'n webhook in die kluster sodat selfs 'n handgetikte kubectl nie iets ongetekend kon laat loop nie. Dit is op 'n Dinsdagmiddag ingevoeg. Teen die aand het ons verifikasie-Deployment op die dev-kluster 2 242 ReplicaSets opgehoop, het dit ongeveer elke drie sekondes 'n nuwe een geskep, het Available=False gerapporteer, en niks bedien nie.
Die voor die hand liggende hipotese is dat die nuwe webhook ons beelde verwerp het. Dit het nie. Sy logs het, vir elke versoek, allowed: true gesê. Dit het alles wat ons daarheen gestuur het toegelaat, die hele aand lank, terwyl die Deployment wat dit toegelaat het uitmekaar geval het.
Die laag wat ons besig was om by te voeg
Die pyplyn het reeds geteken en geverifieer. Elke beeld kry 'n sleutellose handtekening tydens boutyd, gekoppel aan die GitHub Actions OIDC-identiteit, en die ontplooiingstaak voer 'n verifikasie teen daardie identiteit uit voordat enigiets die kluster bereik. Daardie hek is fail-closed en dit is die werklike beheer.
Die kluster-kant laag is verdediging in diepte: sigstore se policy-controller, wat as 'n toelatingswebhook loop, met twee beleide. Een sê dat beelde wat by ons eie register-pad pas 'n sleutellose handtekening van ons werkvloei-identiteit moet dra. Die ander is 'n vang-alles wat alles anders laat deurgaan, want "geen beleid het gepas nie" beteken weier, en sonder die vang-alles verloor die kluster sy Vault-agente, sy CSI-drywers, en elke sidecar wat dit nie gebou het nie. Installeer, etiketteer die naamruimte, klaar. Die etiket is die skakelaar: geen etiket, geen afdwinging nie.
Die eerste onderbreking, wat vervelig was
Om dit met failurePolicy: Fail en die chart se verstektydslimiet van tien sekondes aan te skakel, het dev byna onmiddellik platgelê, op die manier waarop almal verwag dat 'n toelatingswebhook jou sal platlê. 'n Koue beheerder moet Fulcio en Rekor bereik om 'n handtekening te verifieer wat dit nog nie voorheen gesien het nie. Koud voltooi daardie werk nie binne tien sekondes nie. Fail-closed plus 'n gemiste sperdatum beteken dat pod-skepping geweier word, wat beteken die uitrol kan nie pods plaas nie, wat beteken die diens het geen replikas nie.
Daardie faalmodus is goed gedokumenteer en die oplossing is die gedokumenteerde een: verhoog die webhook se tydslimiet na dertig sekondes en stel failurePolicy: Ignore. Ignore klink soos om op te gee, en in 'n enkellaag-ontwerp sou dit wees. In ons s'n is die pyplyn-hek die fail-closed primêre beheer, en die kluster-laag bestaan om te vang wat glad nie deur die pyplyn gegaan het nie. 'n Webhook wat die kluster nooit kan platlê nie, is vir ons meer werd as 'n webhook wat die laaste een persent vang, want die laaste een persent is reeds stroomop gevang.
Ons het dit om 17:40 uitgerol. Dit het die eerste onderbreking heeltemal reggemaak. Dit het ook die tweede een geskep, en dit is die deel wat die moeite werd is om te lees.
Muterend, nie net validerend nie
Almal dink aan 'n toelatingswebhook as 'n uitsmyter: dit inspekteer die objek en gee ja of nee terug. Policy-controller is nie net dit nie. Dit is 'n muterende webhook, en wat dit muteer is die ding wat dit pas geverifieer het. Wanneer dit 'n pod-spesifikasie toelaat wat 'n beeld volgens sy tag verwys, herskryf dit daardie verwysing om die digest in te sluit wat dit opgelos en gekontroleer het. myregistry.io/authagonal-auth:abc123 gaan in. myregistry.io/authagonal-auth:abc123@sha256:... kom uit.
Dit is 'n werklik goeie idee. 'n Tag is 'n veranderlike wyser, so om 'n tag te verifieer en dan die kubelet dit later weer te laat oplos, laat 'n venster waar die twee kan verskil. Om die digest tydens toelating vas te pen, maak dit toe. Die objek wat loop, is die objek wat geverifieer is.
Sit dit nou langs hoe 'n Deployment werk. Die Deployment-beheerder hash jou pod-sjabloon, en daardie hash is wat die ReplicaSet identifiseer wat die pods besit. Dit hash die sjabloon wat jy verklaar het. Die webhook herskryf die sjabloon op die ReplicaSet wat dit toelaat. So as jou Deployment se sjabloon :abc123 sê, sê sy eie kind-ReplicaSet :abc123@sha256:..., en die twee stem nie meer ooreen nie.
Die beheerder rekonsilieer, hash die sjabloon, soek 'n ReplicaSet met daardie hash, en vind een waarvan die sjabloon verskil. Daar is presies een ding wat dit in Kubernetes beteken: 'n hash-botsing, twee verskillende sjablone wat op dieselfde hash beland. Die beheerder hanteer botsings soos dit veronderstel is om te doen. Dit verhoog collisionCount, wat die hash versteur, en skep 'n nuwe ReplicaSet. Drie sekondes later rekonsilieer dit weer, en die nuwe ReplicaSet is ook gemuteer. Die lus konvergeer nie, want die meningsverskil wat dit probeer oplos, word deur die webhook herskep elke keer wat dit probeer.
Twee duisend twee honderd twee en veertig ReplicaSets is hoe 'n oneindige lus lyk wanneer jy dit in die aand eerder as die oggend vang.
Waarom net een Deployment gebreek het
Vier werkladings loop in daardie naamruimte. Een het in 'n spiraal beland. Drie was heeltemal reg, wat die besonderheid is wat ons in die verkeerde plek laat soek het, want 'n sistemiese wankonfigurasie behoort nie selektief te wees nie.
failurePolicy: Ignore is die rede. Wanneer die webhook stadig of koud is en 'n versoek se tyd verstryk, beteken Ignore dat die objek ongemuteerd toegelaat word. Of 'n gegewe apply teruggekom het met 'n digest ingebak of met die kaal tag waarmee dit ingegaan het, het afgehang of die webhook betyds geantwoord het. Die drie gesonde Deployments is toegepas terwyl dit warm was, so hul sjablone het reeds digests gedra, en die webhook se herskrywing was 'n no-op wat ooreengestem het met wat reeds daar was. Die een wat gespiraal het, is toegepas terwyl die webhook koud was, het sy kaal tag in die sjabloon behou, en toe is elke kind-ReplicaSet onder dit uit gemuteer.
So die sneller was 'n wedloop, per apply besleg, wat enige van die vier op enige ontplooiing kon verloor het. Ignore het nie die fout veroorsaak nie. Dit het 'n deterministiese fout in 'n wisselvallige een verander en dit agter drie gesonde werkladings weggesteek.
Die oplossing is 'n vaste punt
Die instink is om die webhook te keer om te muteer. 'n Beter instink: gee dit niks om te muteer nie. As die pod-sjabloon wat ons indien reeds presies is wat die webhook sou produseer, verander die herskrywing niks, bly die hash stabiel, en kan die lus nie begin nie.
So die ontplooiingstaak los nou self die digest op voordat dit toepas. Vir elke beeld vra dit die register waarheen die tag tans wys, en skryf dan die ten volle vasgepende verwysing in die overlay:
digest=$(az acr repository show --name "$acr" \
--image "authagonal-${img}:${tag}" --query digest -o tsv)
kustomize edit set image "${ref}=${ref}:${tag}@${digest}"
Wat uitgestuur word, is :tag@sha256:..., tag en digest saam. Die tag bly vir menslike leesbaarheid, die digest is wat werklik oplos. Die webhook verifieer dit, vind niks om te herskryf nie, en gee die objek onveranderd terug. collisionCount is sedertdien plat.
Daar is 'n klein slaggat aan die stert hiervan. Omdat CI dit is wat die digest skryf, stuur die overlay met die hand van 'n skootrekenaar af eerder die basismanifeste se plekhouer-tag, en die beleid weier dit nou tereg met "must be an image digest." Die kluster vertel jou die waarheid: daardie ding wat jy pas getik het, is nooit geverifieer nie.
'n Muterende webhook is 'n tweede skrywer, nie 'n kontrolepunt nie
'n Muterende toelatingswebhook is nie 'n kontrolepunt nie. Dit is 'n skrywer binne 'n beheerlus wat reeds vergelyk wat jy gevra het teenoor wat bestaan. Twee skrywers met verskillende opinies oor dieselfde veld is nie 'n beleidsvraag nie, dit is 'n verspreide-stelsels-vraag, en die een ding wat dit veilig maak, is idempotensie: die toestand wat jy verklaar, moet 'n vaste punt van die mutasie wees, sodat om die mutasie daarop toe te pas dit weer produseer.
Daardie herformulering veralgemeen verby sigstore. Enigiets wat jou spesifikasies tydens toelating herskryf, of dit sidecars inspuit, verstek-hulpbronlimiete byvoeg, of beeldverwysings normaliseer, is in dieselfde posisie, en dieselfde vraag geld. As jy jou eie manifes deur hierdie ding gehardloop het, sou jy jou eie manifes terugkry? Indien nie, gaan iets aanhou om die verskil raak te sien. Dit sal geduldig wees, en dit sal baie vinniger as jy wees.
En die kleiner les, die een wat ons werklik daardie nag moes afleer: allowed: true beteken nie dat die webhook nie die probleem is nie. Ons het 'n uur spandeer om daardie logs as 'n alibi te behandel. Die webhook het die hele tyd die waarheid vertel. Ons het dit die verkeerde vraag gevra, want ons het nog altyd net aan dit gedink as iets wat nee sê.
As jy verkies dat jou identiteitsverskaffer reeds met sy voorsieningsketting uitgepluis kom, Authagonal is 'n gehoste verifikasiediens wie se beelde in CI geteken word, voor ontplooiing geverifieer word, en volgens digest by die punt van toelating vasgepen word, wat 'n sin is wat ons kan skryf omdat ons reeds 'n aand verloor het om dit te verdien.