[{"data":1,"prerenderedAt":2848},["ShallowReactive",2],{"navigation-de":3,"content-urls":13,"pagesContext-de-2":153,"pagesContext-de-3":179,"authorsContext-de-jeroenbach":199,"postsContext-de-4-undefined":216},[4,7,10],{"label":5,"to":6},"Blog","\u002Fposts",{"label":8,"to":9},"Portfolio","\u002Fde\u002Fcontent\u002F20-portfolio",{"label":11,"to":12},"Über mich","\u002Fde\u002Fcontent\u002F30-uber-mich",{"page":14,"blogPost":76,"blog":152},{"1":15,"10":32,"20":42,"30":52,"404":63},{"en":16,"nl":26,"de":29},{"title":17,"path":18,"stem":19,"contentId":20,"url":21,"dateModified":22,"slug":23,"locale":24,"type":25},"Home","\u002Fcontent\u002Fhome","content\u002F1.home",1,"","2025-11-02T18:00:00",null,"en","page",{"title":17,"path":18,"stem":19,"contentId":20,"url":27,"dateModified":22,"slug":23,"locale":28,"type":25},"\u002Fnl","nl",{"title":17,"path":18,"stem":19,"contentId":20,"url":30,"dateModified":22,"slug":23,"locale":31,"type":25},"\u002Fde","de",{"en":33,"nl":38,"de":40},{"title":5,"path":34,"stem":35,"contentId":36,"url":6,"dateModified":37,"slug":23,"locale":24,"type":25},"\u002Fcontent\u002Fposts","content\u002F10.posts",10,"2025-10-26T18:00:00",{"title":5,"path":34,"stem":35,"contentId":36,"url":39,"dateModified":37,"slug":23,"locale":28,"type":25},"\u002Fnl\u002Fposts",{"title":5,"path":34,"stem":35,"contentId":36,"url":41,"dateModified":37,"slug":23,"locale":31,"type":25},"\u002Fde\u002Fposts",{"en":43,"nl":49,"de":51},{"title":8,"path":44,"stem":45,"contentId":46,"url":47,"dateModified":48,"slug":23,"locale":24,"type":25},"\u002Fcontent\u002Fportfolio","content\u002F20.portfolio",20,"\u002Fcontent\u002F20-portfolio","2026-07-12T18:00:00",{"title":8,"path":44,"stem":45,"contentId":46,"url":50,"dateModified":48,"slug":23,"locale":28,"type":25},"\u002Fnl\u002Fcontent\u002F20-portfolio",{"title":8,"path":44,"stem":45,"contentId":46,"url":9,"dateModified":48,"slug":23,"locale":31,"type":25},{"en":53,"nl":59,"de":62},{"title":54,"path":55,"stem":56,"contentId":57,"url":58,"dateModified":48,"slug":23,"locale":24,"type":25},"About","\u002Fcontent\u002Fabout","content\u002F30.about",30,"\u002Fcontent\u002F30-about",{"title":60,"path":55,"stem":56,"contentId":57,"url":61,"dateModified":48,"slug":23,"locale":28,"type":25},"Over","\u002Fnl\u002Fcontent\u002F30-over",{"title":11,"path":55,"stem":56,"contentId":57,"url":12,"dateModified":48,"slug":23,"locale":31,"type":25},{"en":64,"nl":70,"de":73},{"title":65,"path":66,"stem":67,"contentId":68,"url":69,"dateModified":22,"slug":23,"locale":24,"type":25},"Page not found","\u002Fcontent\u002Fpage-not-found","content\u002F404.page-not-found",404,"\u002Fcontent\u002F404-page-not-found",{"title":71,"path":66,"stem":67,"contentId":68,"url":72,"dateModified":22,"slug":23,"locale":28,"type":25},"Pagina niet gevonden","\u002Fnl\u002Fcontent\u002F404-pagina-niet-gevonden",{"title":74,"path":66,"stem":67,"contentId":68,"url":75,"dateModified":22,"slug":23,"locale":31,"type":25},"Seite nicht gefunden","\u002Fde\u002Fcontent\u002F404-seite-nicht-gefunden",{"1":77,"2":91,"3":106,"4":123,"5":138},{"en":78,"nl":85,"de":88},{"title":79,"path":80,"stem":81,"contentId":20,"dateModified":82,"slug":23,"url":83,"locale":24,"type":84},"Mastering Conditional Property Types with Vue 3.3 Generics","\u002Fposts\u002F1","posts\u002F1","2025-05-27T11:30:00","\u002Fposts\u002F1-mastering-conditional-property-types-with-vue-3_3-generics","blogPost",{"title":86,"path":80,"stem":81,"contentId":20,"dateModified":82,"slug":23,"url":87,"locale":28,"type":84},"Conditionele Property Types beheersen met Vue 3.3 Generics","\u002Fnl\u002Fposts\u002F1-conditionele-property-types-beheersen-met-vue-3_3-generics",{"title":89,"path":80,"stem":81,"contentId":20,"dateModified":82,"slug":23,"url":90,"locale":31,"type":84},"Bedingte Property-Typen mit Vue 3.3 Generics meistern","\u002Fde\u002Fposts\u002F1-bedingte-property-typen-mit-vue-3_3-generics-meistern",{"en":92,"nl":99,"de":102},{"title":93,"path":94,"stem":95,"contentId":96,"dateModified":97,"slug":23,"url":98,"locale":24,"type":84},"Ditching the Cookie Banners: Run Plausible Analytics on Azure Kubernetes","\u002Fposts\u002F2","posts\u002F2",2,"2025-10-04T13:00:00","\u002Fposts\u002F2-ditching-the-cookie-banners:-run-plausible-analytics-on-azure-kubernetes",{"title":100,"path":94,"stem":95,"contentId":96,"dateModified":97,"slug":23,"url":101,"locale":28,"type":84},"Afscheid van Cookie Banners: Draai Plausible Analytics op Azure Kubernetes","\u002Fnl\u002Fposts\u002F2-afscheid-van-cookie-banners:-draai-plausible-analytics-op-azure-kubernetes",{"title":103,"path":94,"stem":95,"contentId":96,"dateModified":97,"slug":104,"url":105,"locale":31,"type":84},"Abschied von Cookie-Bannern: Plausible Analytics auf Azure Kubernetes betreiben","plausible-analytics-azure-kubernetes-ohne-cookie-banner","\u002Fde\u002Fposts\u002F2-plausible-analytics-azure-kubernetes-ohne-cookie-banner",{"en":107,"nl":115,"de":119},{"title":108,"path":109,"stem":110,"contentId":111,"dateModified":112,"slug":113,"url":114,"locale":24,"type":84},"Track how many people read your articles, using Plausible.io, Vue.js and Azure functions","\u002Fposts\u002F3","posts\u002F3",3,"2025-08-03T15:45:00","track-article-reads-plausible-vue-azure-functions","\u002Fposts\u002F3-track-article-reads-plausible-vue-azure-functions",{"title":116,"path":109,"stem":110,"contentId":111,"dateModified":112,"slug":117,"url":118,"locale":28,"type":84},"Volg hoeveel mensen je artikelen lezen, met Plausible.io, Vue.js en Azure functions","artikel-lezingen-volgen-plausible-vue-azure-functions","\u002Fnl\u002Fposts\u002F3-artikel-lezingen-volgen-plausible-vue-azure-functions",{"title":120,"path":109,"stem":110,"contentId":111,"dateModified":112,"slug":121,"url":122,"locale":31,"type":84},"Verfolgen Sie, wie viele Menschen Ihre Artikel lesen, mit Plausible.io, Vue.js und Azure Functions","artikel-aufrufe-verfolgen-plausible-vue-azure-functions","\u002Fde\u002Fposts\u002F3-artikel-aufrufe-verfolgen-plausible-vue-azure-functions",{"en":124,"nl":131,"de":134},{"title":125,"path":126,"stem":127,"contentId":128,"dateModified":129,"slug":23,"url":130,"locale":24,"type":84},"Deploy a production-ready Kubernetes Cluster on Azure with Terraform","\u002Fposts\u002F4","posts\u002F4",4,"2025-12-06T21:00:00","\u002Fposts\u002F4-deploy-a-production-ready-kubernetes-cluster-on-azure-with-terraform",{"title":132,"path":126,"stem":127,"contentId":128,"dateModified":129,"slug":23,"url":133,"locale":28,"type":84},"Implementeer een productie-klaar Kubernetes Cluster op Azure met Terraform","\u002Fnl\u002Fposts\u002F4-implementeer-een-productie-klaar-kubernetes-cluster-op-azure-met-terraform",{"title":135,"path":126,"stem":127,"contentId":128,"dateModified":129,"slug":136,"url":137,"locale":31,"type":84},"Einen produktionsreifen Kubernetes-Cluster auf Azure mit Terraform bereitstellen","produktionsreifer-kubernetes-cluster-azure-terraform","\u002Fde\u002Fposts\u002F4-produktionsreifer-kubernetes-cluster-azure-terraform",{"en":139,"nl":146,"de":149},{"title":140,"path":141,"stem":142,"contentId":143,"dateModified":144,"slug":23,"url":145,"locale":24,"type":84},"Array to Map conversion in Typescript, with type safety","\u002Fposts\u002F5","posts\u002F5",5,"2025-09-29T21:00:00","\u002Fposts\u002F5-array-to-map-conversion-in-typescript-with-type-safety",{"title":147,"path":141,"stem":142,"contentId":143,"dateModified":144,"slug":23,"url":148,"locale":28,"type":84},"Array naar Map conversie in Typescript, met type veiligheid","\u002Fnl\u002Fposts\u002F5-array-naar-map-conversie-in-typescript-met-type-veiligheid",{"title":150,"path":141,"stem":142,"contentId":143,"dateModified":144,"slug":23,"url":151,"locale":31,"type":84},"Array zu Map Konvertierung in TypeScript, mit Typsicherheit","\u002Fde\u002Fposts\u002F5-array-zu-map-konvertierung-in-typescript-mit-typsicherheit",{},{"id":154,"title":155,"body":156,"canonicalUrl":23,"company":23,"contentId":96,"dateModified":23,"datePublished":23,"description":162,"draft":165,"enableProse":165,"excludeFromNavigation":165,"extension":166,"imageAlt":23,"imageUrl":23,"meta":167,"navigation":173,"partial":173,"path":174,"seo":175,"slug":23,"stem":176,"url":177,"__hash__":178},"pages_de\u002Fcontent\u002F_footer.md","Über Jeroen Bach",{"type":157,"value":158,"toc":163},"minimark",[159],[160,161,162],"p",{},"Entworfen in Figma und erstellt mit Vue.js, Nuxt.js und Tailwind CSS.\nBereitgestellt über Cloudflare Pages und Cloudflare Workers, wobei die API auf Azure Kubernetes Service läuft.\nWebsite-Analysen werden von Plausible Analytics betrieben, ebenfalls bereitgestellt mit Azure Kubernetes Service.",{"title":21,"searchDepth":96,"depth":96,"links":164},[],false,"md",{"readingTime":168},{"text":169,"minutes":170,"time":171,"words":172},"1 min read",0.19,11400,38,true,"\u002Fcontent\u002F_footer",{"title":155,"description":162},"content\u002F_footer","\u002Fde\u002Fcontent\u002F2-uber-jeroen-bach","BLLqEovxxJ3L4C_b_KvMwzx1PlCKy3d9WRuX4SHlvHU",{"id":180,"title":181,"body":182,"canonicalUrl":23,"company":23,"contentId":111,"dateModified":23,"datePublished":23,"description":186,"draft":165,"enableProse":165,"excludeFromNavigation":165,"extension":166,"imageAlt":23,"imageUrl":23,"meta":189,"navigation":173,"partial":173,"path":194,"seo":195,"slug":23,"stem":196,"url":197,"__hash__":198},"pages_de\u002Fcontent\u002F_footer-about.md","Footer About",{"type":157,"value":183,"toc":187},[184],[160,185,186],{},"Ich bin Engineering Manager mit 15 Jahren Erfahrung in der Kombination von praktischer Ingenieursarbeit und Menschenführung.\nDie größte Zufriedenheit finde ich darin, die Bedingungen zu schaffen, unter denen ein Team seine beste Arbeit leisten kann.\nDieser Blog ist der Ort, an dem ich Techniken und Erkenntnisse zum Erstellen großartiger Software teile, inspiriert von realen Projekten.",{"title":21,"searchDepth":96,"depth":96,"links":188},[],{"readingTime":190},{"text":169,"minutes":191,"time":192,"words":193},0.275,16500,55,"\u002Fcontent\u002F_footer-about",{"description":186},"content\u002F_footer-about","\u002Fde\u002Fcontent\u002F3-footer-about","JvPnDX3b8uiLVBqSdJwhE9Pd7OW34Wst9X3a36HtVTs",{"id":200,"company":201,"extension":205,"fullName":206,"github":207,"homePage":208,"imageUrl":204,"linkedIn":209,"meta":210,"role":211,"stem":212,"twitter":213,"userName":214,"__hash__":215},"authors_de\u002Fautoren\u002Fjeroenbach.yaml",{"name":202,"url":203,"imageUrl":204},"Bach.Software","https:\u002F\u002Fbach.software","\u002FJEROEN-4238-SQUARE-zoom.jpeg","yaml","Jeroen Bach","https:\u002F\u002Fgithub.com\u002Fjeroenbach","https:\u002F\u002Fbach.software\u002Fpages\u002Fabout","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjeroenbach\u002F",{},"Engineering Manager","autoren\u002Fjeroenbach","https:\u002F\u002Fx.com\u002Fjeroenbach","jeroenbach","LAWAl6OxkIQenUN8NiVc-wx8Lal_pDP10NTrZa6s78o",{"id":217,"title":135,"authorName":214,"body":218,"canonicalUrl":23,"category":261,"contentId":128,"dateModified":129,"datePublished":2823,"description":2824,"draft":165,"excerpt":2825,"extension":166,"imageAlt":2831,"imagePosition":2832,"imageUrl":2833,"keywords":2834,"meta":2837,"navigation":173,"path":126,"readingTime":2838,"seo":2843,"slug":136,"stem":127,"url":137,"__hash__":2844,"author":2845},"posts_de\u002Fposts\u002F4.md",{"type":157,"value":219,"toc":2803},[220,228,248,267,277,282,316,319,345,349,378,388,392,401,404,518,525,528,546,551,566,572,648,667,670,673,705,708,719,723,743,753,759,763,766,778,782,785,1081,1141,1145,1148,1156,1402,1410,1584,1588,1591,1799,1803,1806,1809,1812,2148,2152,2155,2344,2348,2351,2529,2532,2535,2538,2544,2547,2575,2578,2585,2588,2607,2610,2619,2744,2747,2751,2757,2780,2784,2790,2796,2799],[160,221,222,223,227],{},"In diesem Leitfaden erfahren Sie, wie Sie eine vollständig modulare und wiederverwendbare Terraform-Lösung erstellen,\ndie Ressourcen über ",[224,225,226],"strong",{},"Azure, Kubernetes und Cloudflare"," hinweg bereitstellt.",[229,230,231],"blockquote",{},[160,232,233,236,237,242,243,247],{},[224,234,235],{},"Update (Dez 2025):"," Nach dem Betrieb dieser Plausible-Bereitstellung für einige Monate in der minimalen Azure-Umgebung\nbin ich auf Speicherengpässe und Festplattenprobleme gestoßen, die dazu führten, dass die Container wiederholt abstürzten.\nDie beiden zusätzlichen Abschnitte ",[238,239,241],"a",{"href":240},"#h%C3%BCrde-speicherengpass-f%C3%BCr-clickhouse-container","Hürde: Speicherengpass für clickhouse Container","\nund ",[238,244,246],{"href":245},"#h%C3%BCrde-festplatte-voll-aufgrund-von-systemlogs","Hürde: Festplatte voll aufgrund von Systemlogs"," beschreiben die Grundursachen und die Konfigurationsänderungen, die diese Stabilitätsprobleme behoben haben.",[160,249,250,251,254,255,258,259,262,263,266],{},"In meinem ",[238,252,253],{"href":98},"vorherigen Artikel"," haben Sie gelernt, wie Sie einen ",[224,256,257],{},"Kubernetes-Cluster"," einrichten und Plausible Analytics mit einer Reihe von CLI-Befehlen ausführen.\nDieser Ansatz funktioniert zwar, ist aber nicht ideal. Eine bessere, nachhaltigere Lösung ist die Verwendung von ",[224,260,261],{},"Terraform",".\nMit Terraform beschreiben Sie Ihre Infrastruktur in ihrem ",[224,264,265],{},"gewünschten Zustand",", und Terraform findet heraus, welche Schritte erforderlich sind, um dorthin zu gelangen.",[160,268,269,270,276],{},"Ich habe früher auch ",[238,271,275],{"href":272,"rel":273},"https:\u002F\u002Fgithub.com\u002Fhelmfile\u002Fhelmfile",[274],"nofollow","helmfile"," verwendet, das sich hervorragend für die Verwaltung von Helm-Releases eignet.\nWas mir an Terraform gefällt, ist jedoch, dass es noch weiter geht. Es hört nicht bei Kubernetes auf, sondern bietet Ihnen eine einheitliche Lösung zur Definition aller Ressourcen in Ihrem gesamten Stack.",[278,279,281],"h2",{"id":280},"hauptvorteile","Hauptvorteile",[283,284,285,292,298,304,310],"ul",{},[286,287,288,291],"li",{},[224,289,290],{},"Gewünschten Zustand definieren"," - Keine manuellen CLI-Skripte oder Konfigurationsabweichungen mehr (Ihre Umgebung repräsentiert immer Ihren Code)",[286,293,294,297],{},[224,295,296],{},"Wiederherstellbar"," - Da Sie Ihren gewünschten Zustand definiert haben, ist es einfach, eine gesamte Umgebung mit allen Einstellungen wiederherzustellen",[286,299,300,303],{},[224,301,302],{},"Modulares Design"," - Erstellen Sie Module, die in verschiedenen Umgebungen wiederverwendet werden können",[286,305,306,309],{},[224,307,308],{},"Umgebungsunabhängig"," - Bereitstellung auf Azure, Kubernetes, Cloudflare und vielen weiteren Plattformen",[286,311,312,315],{},[224,313,314],{},"Versionskontrolle"," - Verfolgen Sie Infrastrukturänderungen in der Versionskontrolle und pflegen Sie einen vollständigen Prüfpfad",[160,317,318],{},"Die Einrichtung Ihrer Lösung wie in diesem Artikel beschrieben erweitert diese Vorteile um:",[283,320,321,327,333,339],{},[286,322,323,326],{},[224,324,325],{},"Automatisches HTTPS"," - Let's Encrypt-Zertifikate für alle Ihre exponierten Anwendungen",[286,328,329,332],{},[224,330,331],{},"Kostenoptimiert"," - Nur eine einzige öffentliche IP, eine kosteneffiziente VM-Konfiguration und keine zusätzlichen VM-Festplatten",[286,334,335,338],{},[224,336,337],{},"Keine Konfigurationsabweichungen"," - Infrastrukturzustand zentral gespeichert",[286,340,341,344],{},[224,342,343],{},"Datensicherheit"," - Azure-Festplatten speichern Ihre Daten und ermöglichen einfaches Backup und Wiederherstellung über Snapshots oder Backup-Vault",[278,346,348],{"id":347},"voraussetzungen","Voraussetzungen",[160,350,351,352,355,356,358,359,362,363,366,367,369,370,358,374,377],{},"Stellen Sie sicher, dass die folgenden Tools auf Ihrem Rechner installiert sind: ",[224,353,354],{},"Azure CLI",", ",[224,357,261],{}," & ",[224,360,361],{},"Kubernetes-Tools",".\nAlternativ können Sie die ",[224,364,365],{},"Cloud Shell"," im Azure-Portal verwenden, wo alle benötigten Tools vorinstalliert sind. Bei Verwendung der ",[224,368,365],{}," sollten Sie das clouddrive mounten, um alles über Shell-Sitzungen hinweg zu persistieren: ",[371,372,373],"code",{},"clouddrive mount",[371,375,376],{},"cd clouddrive",".",[160,379,380,381,383,384,387],{},"Wenn Sie Windows verwenden, nutzen Sie bitte die ",[224,382,365],{}," oder ",[224,385,386],{},"WSL",", da alle Skripte in Bash geschrieben sind.",[278,389,391],{"id":390},"erste-schritte","Erste Schritte",[160,393,394,395,400],{},"Es gibt einen kleinen manuellen Schritt, bevor wir in Terraform eintauchen können.\nUnsere Lösung benötigt ein Backend (Speicher) für die State-Datei, daher müssen wir zunächst ein Storage Account mit einem Container in Azure erstellen.\nEs gibt ein praktisches Skript in unserem ",[238,396,399],{"href":397,"rel":398},"https:\u002F\u002Fgithub.com\u002Fjeroenbach\u002Fbach.software\u002Ftree\u002Fmain\u002Fsrc\u002Fapp\u002Fexamples\u002Fpost4\u002Fterraform",[274],"Terraform-Projekt",", das dies für Sie erledigt. Lassen Sie uns also zuerst unser Terraform-Projekt holen.",[160,402,403],{},"Sie können dies schnell durchführen, indem Sie das folgende Shell-Skript ausführen:",[405,406,407],"code-group",{},[408,409,414],"pre",{"className":410,"code":411,"filename":412,"language":413,"meta":21,"style":21},"language-bash shiki shiki-themes github-light github-dark","#!\u002Fusr\u002Fbin\u002Fenv bash\n# Terraform-Projekt aus dem Repository herunterladen und extrahieren\ncurl -L \"https:\u002F\u002Fgithub.com\u002Fjeroenbach\u002Fbach.software\u002Farchive\u002Frefs\u002Fheads\u002Fmain.zip\" -o \"bach.software-terraform.zip\"\nunzip -q \"bach.software-terraform.zip\" \"bach.software-main\u002Fsrc\u002Fapp\u002Fexamples\u002Fpost4\u002Fterraform\u002F*\"\n\n# Extrahierten Ordner ins aktuelle Verzeichnis verschieben und Zip sowie extrahierten Ordner entfernen\nmv \"bach.software-main\u002Fsrc\u002Fapp\u002Fexamples\u002Fpost4\u002Fterraform\" \".\u002Fterraform\"\nrm -rf \"bach.software-terraform.zip\" \"bach.software-main\"\n\n# Zum Terraform-Verzeichnis navigieren\ncd \".\u002Fterraform\"\n","download-terraform-project.sh","bash",[371,415,416,424,429,449,463,468,474,486,500,505,510],{"__ignoreMap":21},[417,418,420],"span",{"class":419,"line":20},"line",[417,421,423],{"class":422},"sJ8bj","#!\u002Fusr\u002Fbin\u002Fenv bash\n",[417,425,426],{"class":419,"line":96},[417,427,428],{"class":422},"# Terraform-Projekt aus dem Repository herunterladen und extrahieren\n",[417,430,431,435,439,443,446],{"class":419,"line":111},[417,432,434],{"class":433},"sScJk","curl",[417,436,438],{"class":437},"sj4cs"," -L",[417,440,442],{"class":441},"sZZnC"," \"https:\u002F\u002Fgithub.com\u002Fjeroenbach\u002Fbach.software\u002Farchive\u002Frefs\u002Fheads\u002Fmain.zip\"",[417,444,445],{"class":437}," -o",[417,447,448],{"class":441}," \"bach.software-terraform.zip\"\n",[417,450,451,454,457,460],{"class":419,"line":128},[417,452,453],{"class":433},"unzip",[417,455,456],{"class":437}," -q",[417,458,459],{"class":441}," \"bach.software-terraform.zip\"",[417,461,462],{"class":441}," \"bach.software-main\u002Fsrc\u002Fapp\u002Fexamples\u002Fpost4\u002Fterraform\u002F*\"\n",[417,464,465],{"class":419,"line":143},[417,466,467],{"emptyLinePlaceholder":173},"\n",[417,469,471],{"class":419,"line":470},6,[417,472,473],{"class":422},"# Extrahierten Ordner ins aktuelle Verzeichnis verschieben und Zip sowie extrahierten Ordner entfernen\n",[417,475,477,480,483],{"class":419,"line":476},7,[417,478,479],{"class":433},"mv",[417,481,482],{"class":441}," \"bach.software-main\u002Fsrc\u002Fapp\u002Fexamples\u002Fpost4\u002Fterraform\"",[417,484,485],{"class":441}," \".\u002Fterraform\"\n",[417,487,489,492,495,497],{"class":419,"line":488},8,[417,490,491],{"class":433},"rm",[417,493,494],{"class":437}," -rf",[417,496,459],{"class":441},[417,498,499],{"class":441}," \"bach.software-main\"\n",[417,501,503],{"class":419,"line":502},9,[417,504,467],{"emptyLinePlaceholder":173},[417,506,507],{"class":419,"line":36},[417,508,509],{"class":422},"# Zum Terraform-Verzeichnis navigieren\n",[417,511,513,516],{"class":419,"line":512},11,[417,514,515],{"class":437},"cd",[417,517,485],{"class":441},[160,519,520,521,524],{},"Bevor Sie Terraform oder eines der nächsten Skripte ausführen, stellen Sie immer sicher, dass Sie bei der richtigen Azure-Subscription angemeldet sind, indem Sie ",[371,522,523],{},"az login"," verwenden.",[160,526,527],{},"Lassen Sie uns das Storage Account und den Container für unseren Terraform-State erstellen:",[408,529,531],{"className":410,"code":530,"language":413,"meta":21,"style":21},"az login\n.\u002Fscripts\u002Fcreate-tfstate-storage.sh\n",[371,532,533,541],{"__ignoreMap":21},[417,534,535,538],{"class":419,"line":20},[417,536,537],{"class":433},"az",[417,539,540],{"class":441}," login\n",[417,542,543],{"class":419,"line":96},[417,544,545],{"class":433},".\u002Fscripts\u002Fcreate-tfstate-storage.sh\n",[547,548,550],"h3",{"id":549},"terraform-ausführen","Terraform ausführen",[160,552,553,554,557,558,561,562,565],{},"Bei der Ausführung von ",[371,555,556],{},"terraform apply"," fordert Terraform einige Eingabevariablen an. Die benötigten Werte finden Sie in der Datei ",[224,559,560],{},"input-output.tf"," im selben Ordner.\nSie können auch eine Datei ",[224,563,564],{},"terraform.tfvars"," mit den benötigten Werten erstellen, sodass Sie diese nicht jedes Mal eingeben müssen.",[160,567,568,571],{},[224,569,570],{},"Hinweis",": Stellen Sie sicher, dass Sie Ihre .tfvars-Dateien nicht in die Versionskontrolle einchecken",[405,573,574],{},[408,575,579],{"className":576,"code":577,"filename":564,"language":578,"meta":21,"style":21},"language-hcl shiki shiki-themes github-light github-dark","azure_subscription_id = \"\u003Cazure-subscription-id>\"\nazure_cluster_name    = \"aks-westeu-prod\"\ncloudflare_api_token  = \"\u003Ccloudflare-api-token>\"\ncloudflare_zone_id    = \"\u003Ccloudflare-zone-id>\"\nplausible_dns         = \"plausible.example.com\"\nletsencrypt_email     = \"admin@example.com\"\n","hcl",[371,580,581,594,605,616,626,637],{"__ignoreMap":21},[417,582,583,587,591],{"class":419,"line":20},[417,584,586],{"class":585},"sVt8B","azure_subscription_id",[417,588,590],{"class":589},"szBVR"," =",[417,592,593],{"class":441}," \"\u003Cazure-subscription-id>\"\n",[417,595,596,599,602],{"class":419,"line":96},[417,597,598],{"class":585},"azure_cluster_name",[417,600,601],{"class":589},"    =",[417,603,604],{"class":441}," \"aks-westeu-prod\"\n",[417,606,607,610,613],{"class":419,"line":111},[417,608,609],{"class":585},"cloudflare_api_token",[417,611,612],{"class":589},"  =",[417,614,615],{"class":441}," \"\u003Ccloudflare-api-token>\"\n",[417,617,618,621,623],{"class":419,"line":128},[417,619,620],{"class":585},"cloudflare_zone_id",[417,622,601],{"class":589},[417,624,625],{"class":441}," \"\u003Ccloudflare-zone-id>\"\n",[417,627,628,631,634],{"class":419,"line":143},[417,629,630],{"class":585},"plausible_dns",[417,632,633],{"class":589},"         =",[417,635,636],{"class":441}," \"plausible.example.com\"\n",[417,638,639,642,645],{"class":419,"line":470},[417,640,641],{"class":585},"letsencrypt_email",[417,643,644],{"class":589},"     =",[417,646,647],{"class":441}," \"admin@example.com\"\n",[160,649,650,651,656,657,661,662,666],{},"Bevor Sie beginnen, stellen Sie sicher, dass Sie die erforderlichen Informationen zur Verfügung haben. Sie können ein kostenloses Cloudflare-Konto erstellen und es mit einer DNS verknüpfen, die Sie besitzen, oder eine neue DNS erstellen.\nSie können ein API-Token erstellen, indem Sie ",[238,652,655],{"href":653,"rel":654},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Ffundamentals\u002Fapi\u002Fget-started\u002Fcreate-token\u002F",[274],"diesen Anweisungen"," folgen und die Vorlage \"Edit zone DNS\" verwenden.\nIhre Zone-ID finden Sie, indem Sie ",[238,658,655],{"href":659,"rel":660},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Ffundamentals\u002Faccount\u002Ffind-account-and-zone-ids\u002F",[274]," folgen.\nIhre Azure-Subscription-ID finden Sie, indem Sie ",[238,663,655],{"href":664,"rel":665},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fazure-portal\u002Fget-subscription-tenant-id",[274]," folgen.",[160,668,669],{},"Wenn Sie die Cloudflare-Variablen nicht angeben, wird das DNS nicht aktualisiert, aber alles andere funktioniert weiterhin und Sie erhalten am Ende die IP-Adresse angezeigt (um auf Plausible zuzugreifen).\nSie müssen selbst einen DNS-Eintrag mit dieser IP-Adresse erstellen, da der Zertifikatsaussteller diesen benötigt, um den DNS-Eintrag zu validieren, bevor er ein gültiges Zertifikat ausstellen kann.",[160,671,672],{},"Lassen Sie uns nun Ihre Umgebung bereitstellen:",[408,674,676],{"className":410,"code":675,"language":413,"meta":21,"style":21},"# Umgebungsname: Azure Kubernetes Service - Western Europe - Production\ncd aks-westeu-prod\nterraform init\nterraform apply\n",[371,677,678,683,690,698],{"__ignoreMap":21},[417,679,680],{"class":419,"line":20},[417,681,682],{"class":422},"# Umgebungsname: Azure Kubernetes Service - Western Europe - Production\n",[417,684,685,687],{"class":419,"line":96},[417,686,515],{"class":437},[417,688,689],{"class":441}," aks-westeu-prod\n",[417,691,692,695],{"class":419,"line":111},[417,693,694],{"class":433},"terraform",[417,696,697],{"class":441}," init\n",[417,699,700,702],{"class":419,"line":128},[417,701,694],{"class":433},[417,703,704],{"class":441}," apply\n",[160,706,707],{},"Terraform wird:",[283,709,710,713,716],{},[286,711,712],{},"Den AKS-Cluster bereitstellen",[286,714,715],{},"Plausible über Helm installieren",[286,717,718],{},"Cloudflare DNS aktualisieren",[278,720,722],{"id":721},"backup-wiederherstellung-optional","Backup & Wiederherstellung (Optional)",[160,724,725,726,729,730,358,733,736,737,742],{},"In Ihrer Ressourcengruppe ",[224,727,728],{},"rg-nodes-aks-westeu-prod"," finden Sie die beiden Azure-Festplatten, die alle Daten der Plausible-Lösung enthalten:\n",[224,731,732],{},"pv-disk-plausible-analytics-v3-clickhouse-0",[224,734,735],{},"pv-disk-plausible-analytics-v3-postgresql-0",".\nSie können stündliche, tägliche oder wöchentliche Backups dieser Festplatten mit ",[238,738,741],{"href":739,"rel":740},"https:\u002F\u002Flearn.microsoft.com\u002Fen-us\u002Fazure\u002Fbackup\u002Fbackup-managed-disks",[274],"Azure Backup Vault"," erstellen.",[160,744,745,746,749,750,377],{},"Um ein Backup wiederherzustellen, erstellen Sie einen Snapshot des spezifischen Backups in einer Ressourcengruppe und tragen Sie die Snapshot-IDs in die folgenden Variablen in der Datei aks-westeu-prod\u002Finput-output.tf ein: ",[224,747,748],{},"postgresql_restore_snapshot_id"," und ",[224,751,752],{},"clickhouse_restore_snapshot_id",[160,754,755,756,758],{},"Beim nächsten Ausführen von ",[371,757,556],{}," wird Plausible mit den Backups wiederhergestellt.",[278,760,762],{"id":761},"die-umgebung-zerstören","Die Umgebung zerstören",[160,764,765],{},"Um die Umgebung und alle zugehörigen Ressourcen zu zerstören, können Sie den folgenden Befehl ausführen:",[408,767,769],{"className":410,"code":768,"language":413,"meta":21,"style":21},"terraform destroy\n",[371,770,771],{"__ignoreMap":21},[417,772,773,775],{"class":419,"line":20},[417,774,694],{"class":433},[417,776,777],{"class":441}," destroy\n",[278,779,781],{"id":780},"lösungsstruktur","Lösungsstruktur",[160,783,784],{},"Um die Lösung von Anfang bis Ende zum Laufen zu bringen, gab es einige Hürden zu überwinden. In diesem Kapitel werde ich diese Hürden und deren Lösung untersuchen, aber lassen Sie mich zunächst einen allgemeinen Überblick über die Lösung geben.",[408,786,788],{"className":410,"code":787,"language":413,"meta":21,"style":21},"terraform\u002F\n├── aks-westeu-prod\u002F\n│   ├── app-plausible.tf\n│   ├── aks-cluster.tf\n│   └── provider.tf\n├── helm-charts\u002F\n│   ├── letsencrypt-cert-issuer\u002F\n│   │   ├── templates\u002F\n│   │   │   ├── letsencrypt-cluster-issuer-staging.yaml\n│   │   │   └── letsencrypt-cluster-issuer.yaml\n│   │   ├── Chart.yaml\n│   │   └── values.yaml\n├── modules\u002F\n│   ├── aks-cluster\u002F\n│   │   ├── aks-cluster.tf\n│   │   ├── ingress-and-certificates.tf\n│   │   └── input-output.tf\n│   ├── persistent-azure-disk-volume\u002F\n│   │   ├── input.tf\n│   │   └── persistent-azure-disk-volume.tf\n│   └── plausible\u002F\n│   │   ├── disks.tf\n│   │   ├── input.tf\n│   │   ├── namespace.tf\n│   │   └── plausible.tf\n├── scripts\u002F\n│   ├── create-tfstate-storage.sh\n│   └── download-terraform-project.sh\n",[371,789,790,795,803,814,823,833,840,849,861,874,887,898,910,918,928,939,951,963,973,985,996,1006,1018,1029,1041,1053,1061,1071],{"__ignoreMap":21},[417,791,792],{"class":419,"line":20},[417,793,794],{"class":433},"terraform\u002F\n",[417,796,797,800],{"class":419,"line":96},[417,798,799],{"class":433},"├──",[417,801,802],{"class":441}," aks-westeu-prod\u002F\n",[417,804,805,808,811],{"class":419,"line":111},[417,806,807],{"class":433},"│",[417,809,810],{"class":441},"   ├──",[417,812,813],{"class":441}," app-plausible.tf\n",[417,815,816,818,820],{"class":419,"line":128},[417,817,807],{"class":433},[417,819,810],{"class":441},[417,821,822],{"class":441}," aks-cluster.tf\n",[417,824,825,827,830],{"class":419,"line":143},[417,826,807],{"class":433},[417,828,829],{"class":441},"   └──",[417,831,832],{"class":441}," provider.tf\n",[417,834,835,837],{"class":419,"line":470},[417,836,799],{"class":433},[417,838,839],{"class":441}," helm-charts\u002F\n",[417,841,842,844,846],{"class":419,"line":476},[417,843,807],{"class":433},[417,845,810],{"class":441},[417,847,848],{"class":441}," letsencrypt-cert-issuer\u002F\n",[417,850,851,853,856,858],{"class":419,"line":488},[417,852,807],{"class":433},[417,854,855],{"class":441},"   │",[417,857,810],{"class":441},[417,859,860],{"class":441}," templates\u002F\n",[417,862,863,865,867,869,871],{"class":419,"line":502},[417,864,807],{"class":433},[417,866,855],{"class":441},[417,868,855],{"class":441},[417,870,810],{"class":441},[417,872,873],{"class":441}," letsencrypt-cluster-issuer-staging.yaml\n",[417,875,876,878,880,882,884],{"class":419,"line":36},[417,877,807],{"class":433},[417,879,855],{"class":441},[417,881,855],{"class":441},[417,883,829],{"class":441},[417,885,886],{"class":441}," letsencrypt-cluster-issuer.yaml\n",[417,888,889,891,893,895],{"class":419,"line":512},[417,890,807],{"class":433},[417,892,855],{"class":441},[417,894,810],{"class":441},[417,896,897],{"class":441}," Chart.yaml\n",[417,899,901,903,905,907],{"class":419,"line":900},12,[417,902,807],{"class":433},[417,904,855],{"class":441},[417,906,829],{"class":441},[417,908,909],{"class":441}," values.yaml\n",[417,911,913,915],{"class":419,"line":912},13,[417,914,799],{"class":433},[417,916,917],{"class":441}," modules\u002F\n",[417,919,921,923,925],{"class":419,"line":920},14,[417,922,807],{"class":433},[417,924,810],{"class":441},[417,926,927],{"class":441}," aks-cluster\u002F\n",[417,929,931,933,935,937],{"class":419,"line":930},15,[417,932,807],{"class":433},[417,934,855],{"class":441},[417,936,810],{"class":441},[417,938,822],{"class":441},[417,940,942,944,946,948],{"class":419,"line":941},16,[417,943,807],{"class":433},[417,945,855],{"class":441},[417,947,810],{"class":441},[417,949,950],{"class":441}," ingress-and-certificates.tf\n",[417,952,954,956,958,960],{"class":419,"line":953},17,[417,955,807],{"class":433},[417,957,855],{"class":441},[417,959,829],{"class":441},[417,961,962],{"class":441}," input-output.tf\n",[417,964,966,968,970],{"class":419,"line":965},18,[417,967,807],{"class":433},[417,969,810],{"class":441},[417,971,972],{"class":441}," persistent-azure-disk-volume\u002F\n",[417,974,976,978,980,982],{"class":419,"line":975},19,[417,977,807],{"class":433},[417,979,855],{"class":441},[417,981,810],{"class":441},[417,983,984],{"class":441}," input.tf\n",[417,986,987,989,991,993],{"class":419,"line":46},[417,988,807],{"class":433},[417,990,855],{"class":441},[417,992,829],{"class":441},[417,994,995],{"class":441}," persistent-azure-disk-volume.tf\n",[417,997,999,1001,1003],{"class":419,"line":998},21,[417,1000,807],{"class":433},[417,1002,829],{"class":441},[417,1004,1005],{"class":441}," plausible\u002F\n",[417,1007,1009,1011,1013,1015],{"class":419,"line":1008},22,[417,1010,807],{"class":433},[417,1012,855],{"class":441},[417,1014,810],{"class":441},[417,1016,1017],{"class":441}," disks.tf\n",[417,1019,1021,1023,1025,1027],{"class":419,"line":1020},23,[417,1022,807],{"class":433},[417,1024,855],{"class":441},[417,1026,810],{"class":441},[417,1028,984],{"class":441},[417,1030,1032,1034,1036,1038],{"class":419,"line":1031},24,[417,1033,807],{"class":433},[417,1035,855],{"class":441},[417,1037,810],{"class":441},[417,1039,1040],{"class":441}," namespace.tf\n",[417,1042,1044,1046,1048,1050],{"class":419,"line":1043},25,[417,1045,807],{"class":433},[417,1047,855],{"class":441},[417,1049,829],{"class":441},[417,1051,1052],{"class":441}," plausible.tf\n",[417,1054,1056,1058],{"class":419,"line":1055},26,[417,1057,799],{"class":433},[417,1059,1060],{"class":441}," scripts\u002F\n",[417,1062,1064,1066,1068],{"class":419,"line":1063},27,[417,1065,807],{"class":433},[417,1067,810],{"class":441},[417,1069,1070],{"class":441}," create-tfstate-storage.sh\n",[417,1072,1074,1076,1078],{"class":419,"line":1073},28,[417,1075,807],{"class":433},[417,1077,829],{"class":441},[417,1079,1080],{"class":441}," download-terraform-project.sh\n",[283,1082,1083,1093,1107],{},[286,1084,1085,1088,1089,1092],{},[371,1086,1087],{},"aks-westeu-prod",": Eine Produktionsumgebungskonfiguration für die Bereitstellung in Azure West Europe. Sie können diesen Ordner als Vorlage verwenden, um weitere Umgebungen zu erstellen. Die mit ",[371,1090,1091],{},"app-"," präfixierten Dateien zeigen die verschiedenen im Cluster installierten Anwendungen.",[286,1094,1095,1098,1099],{},[371,1096,1097],{},"helm-charts",": Benutzerdefinierte Helm-Charts\n",[283,1100,1101],{},[286,1102,1103,1106],{},[371,1104,1105],{},"letsencrypt-cert-issuer",": Anstatt die ClusterIssuer-Ressourcen separat bereitzustellen, habe ich sie in einem Helm-Chart verpackt",[286,1108,1109,1112,1113],{},[371,1110,1111],{},"modules",": Jedes Modul kapselt eine spezifische Verantwortlichkeit\n",[283,1114,1115,1121,1135],{},[286,1116,1117,1120],{},[371,1118,1119],{},"aks-cluster",": Stellt einen AKS-Cluster mit Let's Encrypt-Zertifikatsaussteller, nginx ingress als Load Balancer bereit und wartet darauf, dass die öffentliche IP verfügbar ist",[286,1122,1123,1126,1127,749,1131,1134],{},[371,1124,1125],{},"persistent-azure-disk-volume",": Erstellt eine Azure-Festplatte oder stellt eine mit einem Snapshot wieder her und erstellt dann ein ",[1128,1129,1130],"em",{},"persistent volume",[1128,1132,1133],{},"persistent volume claim"," in Kubernetes",[286,1136,1137,1140],{},[371,1138,1139],{},"plausible",": Installiert Plausible und seine Abhängigkeiten über Helm",[547,1142,1144],{"id":1143},"hürde-verbindungsdetails-des-neuen-clusters-noch-nicht-verfügbar","Hürde: Verbindungsdetails des neuen Clusters noch nicht verfügbar",[160,1146,1147],{},"Nach dem Erstellen des Kubernetes-Clusters möchten wir Ressourcen darin bereitstellen können. Aber in der Terraform-Planungsphase sind die Informationen zur Verbindung mit dieser neuen Umgebung noch nicht verfügbar. Daher mussten wir zwei Schritte unternehmen, um eine nahtlose Bereitstellung zu ermöglichen.",[283,1149,1150],{},[286,1151,1152,1155],{},[224,1153,1154],{},"Dynamische Provider-Konfiguration",": Die Informationen des AKS-Clusters werden für die Helm- und Kubernetes-Provider dynamisch festgelegt, indem die Verbindungsinformationen vom neu erstellten Cluster abgerufen werden:",[405,1157,1158],{},[408,1159,1162],{"className":576,"code":1160,"filename":1161,"language":578,"meta":21,"style":21},"provider \"helm\" {\n  kubernetes = {\n    # Dynamische Provider-Konfiguration verwenden, um den neu erstellten Cluster direkt zu nutzen\n    host                   = module.aks_cluster.kube_config.host\n    client_certificate     = base64decode(module.aks_cluster.kube_config.client_certificate)\n    client_key             = base64decode(module.aks_cluster.kube_config.client_key)\n    cluster_ca_certificate = base64decode(module.aks_cluster.kube_config.cluster_ca_certificate)\n  }\n}\n\nprovider \"kubernetes\" {\n  # Dynamische Provider-Konfiguration verwenden, um den neu erstellten Cluster direkt zu nutzen\n  host                   = module.aks_cluster.kube_config.host\n  client_certificate     = base64decode(module.aks_cluster.kube_config.client_certificate)\n  client_key             = base64decode(module.aks_cluster.kube_config.client_key)\n  cluster_ca_certificate = base64decode(module.aks_cluster.kube_config.cluster_ca_certificate)\n}\n","aks-westeu-prod\u002Fprovider.tf",[371,1163,1164,1175,1184,1189,1200,1228,1252,1276,1281,1286,1290,1299,1304,1328,1351,1375,1398],{"__ignoreMap":21},[417,1165,1166,1169,1172],{"class":419,"line":20},[417,1167,1168],{"class":433},"provider",[417,1170,1171],{"class":437}," \"helm\"",[417,1173,1174],{"class":585}," {\n",[417,1176,1177,1180,1182],{"class":419,"line":96},[417,1178,1179],{"class":585},"  kubernetes",[417,1181,590],{"class":589},[417,1183,1174],{"class":585},[417,1185,1186],{"class":419,"line":111},[417,1187,1188],{"class":422},"    # Dynamische Provider-Konfiguration verwenden, um den neu erstellten Cluster direkt zu nutzen\n",[417,1190,1191,1194,1197],{"class":419,"line":128},[417,1192,1193],{"class":585},"    host                   ",[417,1195,1196],{"class":589},"=",[417,1198,1199],{"class":585}," module.aks_cluster.kube_config.host\n",[417,1201,1202,1205,1207,1210,1213,1215,1218,1220,1223,1225],{"class":419,"line":143},[417,1203,1204],{"class":585},"    client_certificate     ",[417,1206,1196],{"class":589},[417,1208,1209],{"class":437}," base64decode",[417,1211,1212],{"class":585},"(module",[417,1214,377],{"class":589},[417,1216,1217],{"class":585},"aks_cluster",[417,1219,377],{"class":589},[417,1221,1222],{"class":585},"kube_config",[417,1224,377],{"class":589},[417,1226,1227],{"class":585},"client_certificate)\n",[417,1229,1230,1233,1235,1237,1239,1241,1243,1245,1247,1249],{"class":419,"line":470},[417,1231,1232],{"class":585},"    client_key             ",[417,1234,1196],{"class":589},[417,1236,1209],{"class":437},[417,1238,1212],{"class":585},[417,1240,377],{"class":589},[417,1242,1217],{"class":585},[417,1244,377],{"class":589},[417,1246,1222],{"class":585},[417,1248,377],{"class":589},[417,1250,1251],{"class":585},"client_key)\n",[417,1253,1254,1257,1259,1261,1263,1265,1267,1269,1271,1273],{"class":419,"line":476},[417,1255,1256],{"class":585},"    cluster_ca_certificate ",[417,1258,1196],{"class":589},[417,1260,1209],{"class":437},[417,1262,1212],{"class":585},[417,1264,377],{"class":589},[417,1266,1217],{"class":585},[417,1268,377],{"class":589},[417,1270,1222],{"class":585},[417,1272,377],{"class":589},[417,1274,1275],{"class":585},"cluster_ca_certificate)\n",[417,1277,1278],{"class":419,"line":488},[417,1279,1280],{"class":585},"  }\n",[417,1282,1283],{"class":419,"line":502},[417,1284,1285],{"class":585},"}\n",[417,1287,1288],{"class":419,"line":36},[417,1289,467],{"emptyLinePlaceholder":173},[417,1291,1292,1294,1297],{"class":419,"line":512},[417,1293,1168],{"class":433},[417,1295,1296],{"class":437}," \"kubernetes\"",[417,1298,1174],{"class":585},[417,1300,1301],{"class":419,"line":900},[417,1302,1303],{"class":422},"  # Dynamische Provider-Konfiguration verwenden, um den neu erstellten Cluster direkt zu nutzen\n",[417,1305,1306,1309,1312,1315,1317,1319,1321,1323,1325],{"class":419,"line":912},[417,1307,1308],{"class":585},"  host",[417,1310,1311],{"class":589},"                   =",[417,1313,1314],{"class":585}," module",[417,1316,377],{"class":589},[417,1318,1217],{"class":585},[417,1320,377],{"class":589},[417,1322,1222],{"class":585},[417,1324,377],{"class":589},[417,1326,1327],{"class":585},"host\n",[417,1329,1330,1333,1335,1337,1339,1341,1343,1345,1347,1349],{"class":419,"line":920},[417,1331,1332],{"class":585},"  client_certificate",[417,1334,644],{"class":589},[417,1336,1209],{"class":437},[417,1338,1212],{"class":585},[417,1340,377],{"class":589},[417,1342,1217],{"class":585},[417,1344,377],{"class":589},[417,1346,1222],{"class":585},[417,1348,377],{"class":589},[417,1350,1227],{"class":585},[417,1352,1353,1356,1359,1361,1363,1365,1367,1369,1371,1373],{"class":419,"line":930},[417,1354,1355],{"class":585},"  client_key",[417,1357,1358],{"class":589},"             =",[417,1360,1209],{"class":437},[417,1362,1212],{"class":585},[417,1364,377],{"class":589},[417,1366,1217],{"class":585},[417,1368,377],{"class":589},[417,1370,1222],{"class":585},[417,1372,377],{"class":589},[417,1374,1251],{"class":585},[417,1376,1377,1380,1382,1384,1386,1388,1390,1392,1394,1396],{"class":419,"line":941},[417,1378,1379],{"class":585},"  cluster_ca_certificate",[417,1381,590],{"class":589},[417,1383,1209],{"class":437},[417,1385,1212],{"class":585},[417,1387,377],{"class":589},[417,1389,1217],{"class":585},[417,1391,377],{"class":589},[417,1393,1222],{"class":585},[417,1395,377],{"class":589},[417,1397,1275],{"class":585},[417,1399,1400],{"class":419,"line":953},[417,1401,1285],{"class":585},[283,1403,1404],{},[286,1405,1406,1409],{},[224,1407,1408],{},"Lokalen kubectl-Kontext setzen",": Nach dem Erstellen des AKS-Clusters schreiben wir die neue kube config und setzen den kubectl-Kontext auf dem lokalen Rechner, sodass local-exec-Befehle sich sofort mit dem neuen Cluster verbinden können.",[405,1411,1412],{},[408,1413,1416],{"className":576,"code":1414,"filename":1415,"language":578,"meta":21,"style":21},"resource \"null_resource\" \"set_kube_context\" {\n  provisioner \"local-exec\" {\n    command = \u003C\u003CEOT\n      # Wir holen es aus dem Terraform-State und fügen es zur kubeconfig hinzu\n      echo '${azurerm_kubernetes_cluster.aks_cluster.kube_config_raw}' > ~\u002F.kube\u002Fconfig\n      export KUBECONFIG=~\u002F.kube\u002Fconfig\n      kubectl config use-context ${azurerm_kubernetes_cluster.aks_cluster.name}\n    EOT\n  }\n\n  \u002F\u002F Kube-Kontext immer beim Ausführen von apply setzen, auch wenn keine Änderungen am Cluster vorgenommen wurden\n  triggers = {\n    always_run = \"${timestamp()}\"\n  }\n\n  depends_on = [azurerm_kubernetes_cluster.aks_cluster]\n}\n","modules\u002Faks-cluster\u002Faks-cluster.tf",[371,1417,1418,1431,1441,1451,1456,1482,1487,1507,1512,1516,1520,1525,1534,1557,1561,1565,1580],{"__ignoreMap":21},[417,1419,1420,1423,1426,1429],{"class":419,"line":20},[417,1421,1422],{"class":433},"resource",[417,1424,1425],{"class":437}," \"null_resource\"",[417,1427,1428],{"class":437}," \"set_kube_context\"",[417,1430,1174],{"class":585},[417,1432,1433,1436,1439],{"class":419,"line":96},[417,1434,1435],{"class":433},"  provisioner",[417,1437,1438],{"class":437}," \"local-exec\"",[417,1440,1174],{"class":585},[417,1442,1443,1446,1448],{"class":419,"line":111},[417,1444,1445],{"class":585},"    command",[417,1447,590],{"class":589},[417,1449,1450],{"class":589}," \u003C\u003CEOT\n",[417,1452,1453],{"class":419,"line":128},[417,1454,1455],{"class":441},"      # Wir holen es aus dem Terraform-State und fügen es zur kubeconfig hinzu\n",[417,1457,1458,1461,1464,1467,1469,1471,1473,1476,1479],{"class":419,"line":143},[417,1459,1460],{"class":441},"      echo '",[417,1462,1463],{"class":589},"${",[417,1465,1466],{"class":585},"azurerm_kubernetes_cluster",[417,1468,377],{"class":589},[417,1470,1217],{"class":585},[417,1472,377],{"class":589},[417,1474,1475],{"class":585},"kube_config_raw",[417,1477,1478],{"class":589},"}",[417,1480,1481],{"class":441},"' > ~\u002F.kube\u002Fconfig\n",[417,1483,1484],{"class":419,"line":470},[417,1485,1486],{"class":441},"      export KUBECONFIG=~\u002F.kube\u002Fconfig\n",[417,1488,1489,1492,1494,1496,1498,1500,1502,1505],{"class":419,"line":476},[417,1490,1491],{"class":441},"      kubectl config use-context ",[417,1493,1463],{"class":589},[417,1495,1466],{"class":585},[417,1497,377],{"class":589},[417,1499,1217],{"class":585},[417,1501,377],{"class":589},[417,1503,1504],{"class":585},"name",[417,1506,1285],{"class":589},[417,1508,1509],{"class":419,"line":488},[417,1510,1511],{"class":589},"    EOT\n",[417,1513,1514],{"class":419,"line":502},[417,1515,1280],{"class":585},[417,1517,1518],{"class":419,"line":36},[417,1519,467],{"emptyLinePlaceholder":173},[417,1521,1522],{"class":419,"line":512},[417,1523,1524],{"class":422},"  \u002F\u002F Kube-Kontext immer beim Ausführen von apply setzen, auch wenn keine Änderungen am Cluster vorgenommen wurden\n",[417,1526,1527,1530,1532],{"class":419,"line":900},[417,1528,1529],{"class":585},"  triggers",[417,1531,590],{"class":589},[417,1533,1174],{"class":585},[417,1535,1536,1539,1541,1544,1546,1549,1552,1554],{"class":419,"line":912},[417,1537,1538],{"class":585},"    always_run ",[417,1540,1196],{"class":589},[417,1542,1543],{"class":441}," \"",[417,1545,1463],{"class":589},[417,1547,1548],{"class":437},"timestamp",[417,1550,1551],{"class":441},"()",[417,1553,1478],{"class":589},[417,1555,1556],{"class":441},"\"\n",[417,1558,1559],{"class":419,"line":920},[417,1560,1280],{"class":585},[417,1562,1563],{"class":419,"line":930},[417,1564,467],{"emptyLinePlaceholder":173},[417,1566,1567,1570,1572,1575,1577],{"class":419,"line":941},[417,1568,1569],{"class":585},"  depends_on",[417,1571,590],{"class":589},[417,1573,1574],{"class":585}," [azurerm_kubernetes_cluster",[417,1576,377],{"class":589},[417,1578,1579],{"class":585},"aks_cluster]\n",[417,1581,1582],{"class":419,"line":953},[417,1583,1285],{"class":585},[547,1585,1587],{"id":1586},"hürde-load-balancer-ip-noch-nicht-verfügbar","Hürde: Load Balancer IP noch nicht verfügbar",[160,1589,1590],{},"Beim Bereitstellen eines Helm-Release ist Terraform fertig, bevor das Release vollständig bereitgestellt ist. Es liefert auch keine Load Balancer IP-Informationen. Daher habe ich zwei lokale Skripte implementiert, die auf die nginx ingress-Bereitstellung warten und die Load Balancer IP sammeln, die für die Aktualisierung Ihres DNS benötigt wird.",[405,1592,1593],{},[408,1594,1597],{"className":576,"code":1595,"filename":1596,"language":578,"meta":21,"style":21},"# Warten, bis das ingress-nginx Helm-Release bereitgestellt ist\nresource \"null_resource\" \"wait_for_ingress_nginx\" {\n  provisioner \"local-exec\" {\n    command = \u003C\u003CEOT\n      for i in {1..30}; do\n        kubectl get svc -n ingress-nginx ${helm_release.ingress_nginx.name}-controller && sleep 30 && break || sleep 30;\n      done\n    EOT\n  }\n\n  depends_on = [helm_release.ingress_nginx]\n}\n\n# Externe IP mit kubectl abrufen\ndata \"external\" \"ingress_external_ip\" {\n  program = [\"bash\", \"-c\", \u003C\u003CEOT\n    EXTERNAL_IP=$(kubectl get svc -n ingress-nginx ${helm_release.ingress_nginx.name}-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>\u002Fdev\u002Fnull || echo \"\")\n    echo \"{\\\"ip\\\":\\\"$EXTERNAL_IP\\\"}\"\n  EOT\n  ]\n\n  depends_on = [null_resource.wait_for_ingress_nginx]\n}\n","modules\u002Faks-cluster\u002Fingress-and-certificates.tf",[371,1598,1599,1604,1615,1623,1631,1636,1660,1665,1669,1673,1677,1691,1695,1699,1704,1717,1740,1762,1767,1772,1777,1781,1795],{"__ignoreMap":21},[417,1600,1601],{"class":419,"line":20},[417,1602,1603],{"class":422},"# Warten, bis das ingress-nginx Helm-Release bereitgestellt ist\n",[417,1605,1606,1608,1610,1613],{"class":419,"line":96},[417,1607,1422],{"class":433},[417,1609,1425],{"class":437},[417,1611,1612],{"class":437}," \"wait_for_ingress_nginx\"",[417,1614,1174],{"class":585},[417,1616,1617,1619,1621],{"class":419,"line":111},[417,1618,1435],{"class":433},[417,1620,1438],{"class":437},[417,1622,1174],{"class":585},[417,1624,1625,1627,1629],{"class":419,"line":128},[417,1626,1445],{"class":585},[417,1628,590],{"class":589},[417,1630,1450],{"class":589},[417,1632,1633],{"class":419,"line":143},[417,1634,1635],{"class":441},"      for i in {1..30}; do\n",[417,1637,1638,1641,1643,1646,1648,1651,1653,1655,1657],{"class":419,"line":470},[417,1639,1640],{"class":441},"        kubectl get svc -n ingress-nginx ",[417,1642,1463],{"class":589},[417,1644,1645],{"class":585},"helm_release",[417,1647,377],{"class":589},[417,1649,1650],{"class":585},"ingress_nginx",[417,1652,377],{"class":589},[417,1654,1504],{"class":585},[417,1656,1478],{"class":589},[417,1658,1659],{"class":441},"-controller && sleep 30 && break || sleep 30;\n",[417,1661,1662],{"class":419,"line":476},[417,1663,1664],{"class":441},"      done\n",[417,1666,1667],{"class":419,"line":488},[417,1668,1511],{"class":589},[417,1670,1671],{"class":419,"line":502},[417,1672,1280],{"class":585},[417,1674,1675],{"class":419,"line":36},[417,1676,467],{"emptyLinePlaceholder":173},[417,1678,1679,1681,1683,1686,1688],{"class":419,"line":512},[417,1680,1569],{"class":585},[417,1682,590],{"class":589},[417,1684,1685],{"class":585}," [helm_release",[417,1687,377],{"class":589},[417,1689,1690],{"class":585},"ingress_nginx]\n",[417,1692,1693],{"class":419,"line":900},[417,1694,1285],{"class":585},[417,1696,1697],{"class":419,"line":912},[417,1698,467],{"emptyLinePlaceholder":173},[417,1700,1701],{"class":419,"line":920},[417,1702,1703],{"class":422},"# Externe IP mit kubectl abrufen\n",[417,1705,1706,1709,1712,1715],{"class":419,"line":930},[417,1707,1708],{"class":433},"data",[417,1710,1711],{"class":437}," \"external\"",[417,1713,1714],{"class":437}," \"ingress_external_ip\"",[417,1716,1174],{"class":585},[417,1718,1719,1722,1724,1727,1730,1732,1735,1737],{"class":419,"line":941},[417,1720,1721],{"class":585},"  program",[417,1723,590],{"class":589},[417,1725,1726],{"class":585}," [",[417,1728,1729],{"class":441},"\"bash\"",[417,1731,355],{"class":585},[417,1733,1734],{"class":441},"\"-c\"",[417,1736,355],{"class":585},[417,1738,1739],{"class":589},"\u003C\u003CEOT\n",[417,1741,1742,1745,1747,1749,1751,1753,1755,1757,1759],{"class":419,"line":953},[417,1743,1744],{"class":441},"    EXTERNAL_IP=$(kubectl get svc -n ingress-nginx ",[417,1746,1463],{"class":589},[417,1748,1645],{"class":585},[417,1750,377],{"class":589},[417,1752,1650],{"class":585},[417,1754,377],{"class":589},[417,1756,1504],{"class":585},[417,1758,1478],{"class":589},[417,1760,1761],{"class":441},"-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>\u002Fdev\u002Fnull || echo \"\")\n",[417,1763,1764],{"class":419,"line":965},[417,1765,1766],{"class":441},"    echo \"{\\\"ip\\\":\\\"$EXTERNAL_IP\\\"}\"\n",[417,1768,1769],{"class":419,"line":975},[417,1770,1771],{"class":589},"  EOT\n",[417,1773,1774],{"class":419,"line":46},[417,1775,1776],{"class":585},"  ]\n",[417,1778,1779],{"class":419,"line":998},[417,1780,467],{"emptyLinePlaceholder":173},[417,1782,1783,1785,1787,1790,1792],{"class":419,"line":1008},[417,1784,1569],{"class":585},[417,1786,590],{"class":589},[417,1788,1789],{"class":585}," [null_resource",[417,1791,377],{"class":589},[417,1793,1794],{"class":585},"wait_for_ingress_nginx]\n",[417,1796,1797],{"class":419,"line":1020},[417,1798,1285],{"class":585},[547,1800,1802],{"id":1801},"hürde-die-daten-in-unserer-lösung-sind-nicht-sicher","Hürde: Die Daten in unserer Lösung sind nicht sicher",[160,1804,1805],{},"Bei Verwendung des Plausible Helm-Charts werden zwei Datenbanken erstellt: PostgreSQL und ClickHouse. Standardmäßig verwenden diese Datenbanken flüchtigen Speicher, was bedeutet, dass alle Daten verloren gehen, wenn der Pod gelöscht oder neu geplant wird.\nUm sicherzustellen, dass unsere Daten sicher sind, müssen wir persistenten Speicher verwenden. In einer Cloud-Umgebung wie Azure können wir dafür Azure-Festplatten verwenden.",[160,1807,1808],{},"Ich habe ein Modul erstellt, um eine Azure-Festplatte zu erstellen oder wiederherzustellen und sie in Kubernetes zu integrieren, indem ein persistent volume und ein persistent volume claim erstellt werden.",[160,1810,1811],{},"So können Sie das Modul verwenden und es in Ihre Plausible Helm-Bereitstellung einbinden.",[405,1813,1814,1987],{},[408,1815,1818],{"className":576,"code":1816,"filename":1817,"language":578,"meta":21,"style":21},"module \"create_pv_postgresql\" {\n  source                    = \"..\u002Fpersistent-azure-disk-volume\"\n  snapshot_id               = var.postgresql_restore_snapshot_id\n  azure_location            = var.azure_disk_location\n  pvc_namespace             = var.namespace\n  pv_name                   = \"pv-disk-${var.name}-postgresql-0\"\n  pvc_name                  = \"pvc-disk-${var.name}-postgresql-0\"\n  azure_resource_group_name = var.azure_disk_resource_group_name\n  disk_size_gb              = var.plausible_config_disk_size # Diese Größe muss gleich der im Plausible Helm-Chart definierten Größe sein\n\n  depends_on = [kubernetes_namespace.plausible_analytics]\n}\n","modules\u002Fplausible\u002Fdisks.tf",[371,1819,1820,1830,1841,1857,1872,1886,1910,1933,1947,1965,1969,1983],{"__ignoreMap":21},[417,1821,1822,1825,1828],{"class":419,"line":20},[417,1823,1824],{"class":433},"module",[417,1826,1827],{"class":437}," \"create_pv_postgresql\"",[417,1829,1174],{"class":585},[417,1831,1832,1835,1838],{"class":419,"line":96},[417,1833,1834],{"class":585},"  source",[417,1836,1837],{"class":589},"                    =",[417,1839,1840],{"class":441}," \"..\u002Fpersistent-azure-disk-volume\"\n",[417,1842,1843,1846,1849,1852,1854],{"class":419,"line":111},[417,1844,1845],{"class":585},"  snapshot_id",[417,1847,1848],{"class":589},"               =",[417,1850,1851],{"class":585}," var",[417,1853,377],{"class":589},[417,1855,1856],{"class":585},"postgresql_restore_snapshot_id\n",[417,1858,1859,1862,1865,1867,1869],{"class":419,"line":128},[417,1860,1861],{"class":585},"  azure_location",[417,1863,1864],{"class":589},"            =",[417,1866,1851],{"class":585},[417,1868,377],{"class":589},[417,1870,1871],{"class":585},"azure_disk_location\n",[417,1873,1874,1877,1879,1881,1883],{"class":419,"line":143},[417,1875,1876],{"class":585},"  pvc_namespace",[417,1878,1358],{"class":589},[417,1880,1851],{"class":585},[417,1882,377],{"class":589},[417,1884,1885],{"class":585},"namespace\n",[417,1887,1888,1891,1893,1896,1898,1901,1903,1905,1907],{"class":419,"line":470},[417,1889,1890],{"class":585},"  pv_name",[417,1892,1311],{"class":589},[417,1894,1895],{"class":441}," \"pv-disk-",[417,1897,1463],{"class":589},[417,1899,1900],{"class":585},"var",[417,1902,377],{"class":589},[417,1904,1504],{"class":585},[417,1906,1478],{"class":589},[417,1908,1909],{"class":441},"-postgresql-0\"\n",[417,1911,1912,1915,1918,1921,1923,1925,1927,1929,1931],{"class":419,"line":476},[417,1913,1914],{"class":585},"  pvc_name",[417,1916,1917],{"class":589},"                  =",[417,1919,1920],{"class":441}," \"pvc-disk-",[417,1922,1463],{"class":589},[417,1924,1900],{"class":585},[417,1926,377],{"class":589},[417,1928,1504],{"class":585},[417,1930,1478],{"class":589},[417,1932,1909],{"class":441},[417,1934,1935,1938,1940,1942,1944],{"class":419,"line":488},[417,1936,1937],{"class":585},"  azure_resource_group_name",[417,1939,590],{"class":589},[417,1941,1851],{"class":585},[417,1943,377],{"class":589},[417,1945,1946],{"class":585},"azure_disk_resource_group_name\n",[417,1948,1949,1952,1955,1957,1959,1962],{"class":419,"line":502},[417,1950,1951],{"class":585},"  disk_size_gb",[417,1953,1954],{"class":589},"              =",[417,1956,1851],{"class":585},[417,1958,377],{"class":589},[417,1960,1961],{"class":585},"plausible_config_disk_size ",[417,1963,1964],{"class":422},"# Diese Größe muss gleich der im Plausible Helm-Chart definierten Größe sein\n",[417,1966,1967],{"class":419,"line":36},[417,1968,467],{"emptyLinePlaceholder":173},[417,1970,1971,1973,1975,1978,1980],{"class":419,"line":512},[417,1972,1569],{"class":585},[417,1974,590],{"class":589},[417,1976,1977],{"class":585}," [kubernetes_namespace",[417,1979,377],{"class":589},[417,1981,1982],{"class":585},"plausible_analytics]\n",[417,1984,1985],{"class":419,"line":900},[417,1986,1285],{"class":585},[408,1988,1991],{"className":576,"code":1989,"filename":1990,"language":578,"meta":21,"style":21},"# der existingClaim wird auf den pvc_name sowohl für postgresql als auch für clickhouse gesetzt\npostgresql:\n  primary:\n    persistence:\n      enabled: true\n      existingClaim: pvc-disk-${var.name}-postgresql-0\n      size: ${var.plausible_config_disk_size}Gi # Diese Datenbank wird nur für Einstellungen und Benutzerdaten verwendet, muss also nicht sehr groß sein\n\n...\n\nclickhouse:\n  persistence:\n    enabled: true\n    existingClaim: pvc-disk-${var.name}-clickhouse-0\n    size: ${var.plausible_data_disk_size}Gi # Diese Datenbank wird zum Speichern aller Analysedaten verwendet, muss also größer sein\n\n","modules\u002Fplausible\u002Fplausible.tf",[371,1992,1993,1998,2006,2013,2020,2031,2061,2074,2078,2083,2087,2094,2101,2110,2135],{"__ignoreMap":21},[417,1994,1995],{"class":419,"line":20},[417,1996,1997],{"class":422},"# der existingClaim wird auf den pvc_name sowohl für postgresql als auch für clickhouse gesetzt\n",[417,1999,2000,2003],{"class":419,"line":96},[417,2001,2002],{"class":585},"postgresql",[417,2004,2005],{"class":589},":\n",[417,2007,2008,2011],{"class":419,"line":111},[417,2009,2010],{"class":585},"  primary",[417,2012,2005],{"class":589},[417,2014,2015,2018],{"class":419,"line":128},[417,2016,2017],{"class":585},"    persistence",[417,2019,2005],{"class":589},[417,2021,2022,2025,2028],{"class":419,"line":143},[417,2023,2024],{"class":585},"      enabled",[417,2026,2027],{"class":589},":",[417,2029,2030],{"class":437}," true\n",[417,2032,2033,2036,2038,2041,2044,2047,2049,2052,2054,2056,2058],{"class":419,"line":470},[417,2034,2035],{"class":585},"      existingClaim",[417,2037,2027],{"class":589},[417,2039,2040],{"class":585}," pvc",[417,2042,2043],{"class":589},"-",[417,2045,2046],{"class":585},"disk",[417,2048,2043],{"class":589},[417,2050,2051],{"class":585},"${var.name}",[417,2053,2043],{"class":589},[417,2055,2002],{"class":585},[417,2057,2043],{"class":589},[417,2059,2060],{"class":437},"0\n",[417,2062,2063,2066,2068,2071],{"class":419,"line":476},[417,2064,2065],{"class":585},"      size",[417,2067,2027],{"class":589},[417,2069,2070],{"class":585}," ${var.plausible_config_disk_size}Gi ",[417,2072,2073],{"class":422},"# Diese Datenbank wird nur für Einstellungen und Benutzerdaten verwendet, muss also nicht sehr groß sein\n",[417,2075,2076],{"class":419,"line":488},[417,2077,467],{"emptyLinePlaceholder":173},[417,2079,2080],{"class":419,"line":502},[417,2081,2082],{"class":589},"...\n",[417,2084,2085],{"class":419,"line":36},[417,2086,467],{"emptyLinePlaceholder":173},[417,2088,2089,2092],{"class":419,"line":512},[417,2090,2091],{"class":585},"clickhouse",[417,2093,2005],{"class":589},[417,2095,2096,2099],{"class":419,"line":900},[417,2097,2098],{"class":585},"  persistence",[417,2100,2005],{"class":589},[417,2102,2103,2106,2108],{"class":419,"line":912},[417,2104,2105],{"class":585},"    enabled",[417,2107,2027],{"class":589},[417,2109,2030],{"class":437},[417,2111,2112,2115,2117,2119,2121,2123,2125,2127,2129,2131,2133],{"class":419,"line":920},[417,2113,2114],{"class":585},"    existingClaim",[417,2116,2027],{"class":589},[417,2118,2040],{"class":585},[417,2120,2043],{"class":589},[417,2122,2046],{"class":585},[417,2124,2043],{"class":589},[417,2126,2051],{"class":585},[417,2128,2043],{"class":589},[417,2130,2091],{"class":585},[417,2132,2043],{"class":589},[417,2134,2060],{"class":437},[417,2136,2137,2140,2142,2145],{"class":419,"line":930},[417,2138,2139],{"class":585},"    size",[417,2141,2027],{"class":589},[417,2143,2144],{"class":585}," ${var.plausible_data_disk_size}Gi ",[417,2146,2147],{"class":422},"# Diese Datenbank wird zum Speichern aller Analysedaten verwendet, muss also größer sein\n",[547,2149,2151],{"id":2150},"hürde-das-plausible-helm-release-ist-nicht-exponiert","Hürde: Das Plausible Helm Release ist nicht exponiert",[160,2153,2154],{},"Bei der Bereitstellung von Plausible über Helm wird der Service standardmäßig nicht exponiert. Um ihn aus dem Internet zugänglich zu machen, müssen wir eine Ingress-Ressource konfigurieren.\nBei der Konfiguration des Ingress können wir auch die cert-manager-Annotation angeben, um sicherzustellen, dass das Zertifikat erstellt wird.",[405,2156,2157],{},[408,2158,2160],{"className":576,"code":2159,"filename":1990,"language":578,"meta":21,"style":21},"ingress:\n  enabled: true\n  annotations:\n    cert-manager.io\u002Fcluster-issuer: \"letsencrypt-production\"\n    kubernetes.io\u002Fingress.class: nginx\n    kubernetes.io\u002Ftls-acme: \"true\"\n  className: nginx\n  hosts:\n    - ${var.plausible_dns}\n  path: \u002F\n  pathType: Prefix\n  tls:\n    - secretName: letsencrypt-production\n      hosts:\n        - ${var.plausible_dns}\n\n",[371,2161,2162,2169,2178,2185,2216,2239,2262,2271,2278,2286,2296,2306,2313,2330,2337],{"__ignoreMap":21},[417,2163,2164,2167],{"class":419,"line":20},[417,2165,2166],{"class":585},"ingress",[417,2168,2005],{"class":589},[417,2170,2171,2174,2176],{"class":419,"line":96},[417,2172,2173],{"class":585},"  enabled",[417,2175,2027],{"class":589},[417,2177,2030],{"class":437},[417,2179,2180,2183],{"class":419,"line":111},[417,2181,2182],{"class":585},"  annotations",[417,2184,2005],{"class":589},[417,2186,2187,2190,2192,2195,2197,2200,2203,2206,2208,2211,2213],{"class":419,"line":128},[417,2188,2189],{"class":585},"    cert",[417,2191,2043],{"class":589},[417,2193,2194],{"class":585},"manager",[417,2196,377],{"class":589},[417,2198,2199],{"class":585},"io",[417,2201,2202],{"class":589},"\u002F",[417,2204,2205],{"class":585},"cluster",[417,2207,2043],{"class":589},[417,2209,2210],{"class":585},"issuer",[417,2212,2027],{"class":589},[417,2214,2215],{"class":441}," \"letsencrypt-production\"\n",[417,2217,2218,2221,2223,2225,2227,2229,2231,2234,2236],{"class":419,"line":143},[417,2219,2220],{"class":585},"    kubernetes",[417,2222,377],{"class":589},[417,2224,2199],{"class":585},[417,2226,2202],{"class":589},[417,2228,2166],{"class":585},[417,2230,377],{"class":589},[417,2232,2233],{"class":585},"class",[417,2235,2027],{"class":589},[417,2237,2238],{"class":585}," nginx\n",[417,2240,2241,2243,2245,2247,2249,2252,2254,2257,2259],{"class":419,"line":470},[417,2242,2220],{"class":585},[417,2244,377],{"class":589},[417,2246,2199],{"class":585},[417,2248,2202],{"class":589},[417,2250,2251],{"class":585},"tls",[417,2253,2043],{"class":589},[417,2255,2256],{"class":585},"acme",[417,2258,2027],{"class":589},[417,2260,2261],{"class":441}," \"true\"\n",[417,2263,2264,2267,2269],{"class":419,"line":476},[417,2265,2266],{"class":585},"  className",[417,2268,2027],{"class":589},[417,2270,2238],{"class":585},[417,2272,2273,2276],{"class":419,"line":488},[417,2274,2275],{"class":585},"  hosts",[417,2277,2005],{"class":589},[417,2279,2280,2283],{"class":419,"line":502},[417,2281,2282],{"class":589},"    -",[417,2284,2285],{"class":585}," ${var.plausible_dns}\n",[417,2287,2288,2291,2293],{"class":419,"line":36},[417,2289,2290],{"class":585},"  path",[417,2292,2027],{"class":589},[417,2294,2295],{"class":589}," \u002F\n",[417,2297,2298,2301,2303],{"class":419,"line":512},[417,2299,2300],{"class":585},"  pathType",[417,2302,2027],{"class":589},[417,2304,2305],{"class":585}," Prefix\n",[417,2307,2308,2311],{"class":419,"line":900},[417,2309,2310],{"class":585},"  tls",[417,2312,2005],{"class":589},[417,2314,2315,2317,2320,2322,2325,2327],{"class":419,"line":912},[417,2316,2282],{"class":589},[417,2318,2319],{"class":585}," secretName",[417,2321,2027],{"class":589},[417,2323,2324],{"class":585}," letsencrypt",[417,2326,2043],{"class":589},[417,2328,2329],{"class":585},"production\n",[417,2331,2332,2335],{"class":419,"line":920},[417,2333,2334],{"class":585},"      hosts",[417,2336,2005],{"class":589},[417,2338,2339,2342],{"class":419,"line":930},[417,2340,2341],{"class":589},"        -",[417,2343,2285],{"class":585},[547,2345,2347],{"id":2346},"hürde-kann-plausible-nicht-aus-einem-backup-wiederherstellen-wenn-die-umgebung-bereits-existiert","Hürde: Kann Plausible nicht aus einem Backup wiederherstellen, wenn die Umgebung bereits existiert",[160,2349,2350],{},"Wenn Sie die Snapshot-IDs in der Datei terraform.tfvars ändern, erstellt Terraform das Plausible Helm-Release oder die persistent volume claims nicht neu, da es keine Änderungen darin sieht.\nDies verhindert das Löschen und Neuerstellen der Azure-Festplatten und persistent volumes, da sie niemals ungebunden werden.\nIch habe daher eine null_resource hinzugefügt, die einen Austausch des Plausible-Release und der persistent volume claims auslöst, wenn sich die Snapshot-IDs ändern.\nAuf diese Weise können Sie neue Snapshot-IDs angeben und die Ressourcen ohne manuelle Eingriffe neu erstellen lassen.",[405,2352,2353,2449],{},[408,2354,2356],{"className":576,"code":2355,"filename":1990,"language":578,"meta":21,"style":21},"resource \"null_resource\" \"snapshot_trigger\" {\n  triggers = {\n    postgresql_snapshot = var.postgresql_restore_snapshot_id\n    clickhouse_snapshot = var.clickhouse_restore_snapshot_id\n  }\n}\n...\n\n  lifecycle {\n    replace_triggered_by = [\n      null_resource.snapshot_trigger\n    ]\n  }\n",[371,2357,2358,2369,2377,2387,2397,2401,2405,2409,2413,2420,2430,2440,2445],{"__ignoreMap":21},[417,2359,2360,2362,2364,2367],{"class":419,"line":20},[417,2361,1422],{"class":433},[417,2363,1425],{"class":437},[417,2365,2366],{"class":437}," \"snapshot_trigger\"",[417,2368,1174],{"class":585},[417,2370,2371,2373,2375],{"class":419,"line":96},[417,2372,1529],{"class":585},[417,2374,590],{"class":589},[417,2376,1174],{"class":585},[417,2378,2379,2382,2384],{"class":419,"line":111},[417,2380,2381],{"class":585},"    postgresql_snapshot ",[417,2383,1196],{"class":589},[417,2385,2386],{"class":585}," var.postgresql_restore_snapshot_id\n",[417,2388,2389,2392,2394],{"class":419,"line":128},[417,2390,2391],{"class":585},"    clickhouse_snapshot ",[417,2393,1196],{"class":589},[417,2395,2396],{"class":585}," var.clickhouse_restore_snapshot_id\n",[417,2398,2399],{"class":419,"line":143},[417,2400,1280],{"class":585},[417,2402,2403],{"class":419,"line":470},[417,2404,1285],{"class":585},[417,2406,2407],{"class":419,"line":476},[417,2408,2082],{"class":589},[417,2410,2411],{"class":419,"line":488},[417,2412,467],{"emptyLinePlaceholder":173},[417,2414,2415,2418],{"class":419,"line":502},[417,2416,2417],{"class":433},"  lifecycle",[417,2419,1174],{"class":585},[417,2421,2422,2425,2427],{"class":419,"line":36},[417,2423,2424],{"class":585},"    replace_triggered_by",[417,2426,590],{"class":589},[417,2428,2429],{"class":585}," [\n",[417,2431,2432,2435,2437],{"class":419,"line":512},[417,2433,2434],{"class":585},"      null_resource",[417,2436,377],{"class":589},[417,2438,2439],{"class":585},"snapshot_trigger\n",[417,2441,2442],{"class":419,"line":900},[417,2443,2444],{"class":585},"    ]\n",[417,2446,2447],{"class":419,"line":912},[417,2448,1280],{"class":585},[408,2450,2453],{"className":576,"code":2451,"filename":2452,"language":578,"meta":21,"style":21},"resource \"null_resource\" \"snapshot_trigger\" {\n  triggers = {\n    snapshot = var.snapshot_id\n  }\n}\n...\n\n  lifecycle {\n    replace_triggered_by = [\n      null_resource.snapshot_trigger\n    ]\n  }\n","modules\u002Fpersistent-...\u002Fpersistent-azure-disk-volume.tf",[371,2454,2455,2465,2473,2483,2487,2491,2495,2499,2505,2513,2521,2525],{"__ignoreMap":21},[417,2456,2457,2459,2461,2463],{"class":419,"line":20},[417,2458,1422],{"class":433},[417,2460,1425],{"class":437},[417,2462,2366],{"class":437},[417,2464,1174],{"class":585},[417,2466,2467,2469,2471],{"class":419,"line":96},[417,2468,1529],{"class":585},[417,2470,590],{"class":589},[417,2472,1174],{"class":585},[417,2474,2475,2478,2480],{"class":419,"line":111},[417,2476,2477],{"class":585},"    snapshot ",[417,2479,1196],{"class":589},[417,2481,2482],{"class":585}," var.snapshot_id\n",[417,2484,2485],{"class":419,"line":128},[417,2486,1280],{"class":585},[417,2488,2489],{"class":419,"line":143},[417,2490,1285],{"class":585},[417,2492,2493],{"class":419,"line":470},[417,2494,2082],{"class":589},[417,2496,2497],{"class":419,"line":476},[417,2498,467],{"emptyLinePlaceholder":173},[417,2500,2501,2503],{"class":419,"line":488},[417,2502,2417],{"class":433},[417,2504,1174],{"class":585},[417,2506,2507,2509,2511],{"class":419,"line":502},[417,2508,2424],{"class":585},[417,2510,590],{"class":589},[417,2512,2429],{"class":585},[417,2514,2515,2517,2519],{"class":419,"line":36},[417,2516,2434],{"class":585},[417,2518,377],{"class":589},[417,2520,2439],{"class":585},[417,2522,2523],{"class":419,"line":512},[417,2524,2444],{"class":585},[417,2526,2527],{"class":419,"line":900},[417,2528,1280],{"class":585},[547,2530,241],{"id":2531},"hürde-speicherengpass-für-clickhouse-container",[160,2533,2534],{},"Der ClickHouse-Container verwendete zu viel Speicher, was dazu führte, dass die Plausible-Lösung alle paar Stunden abstürzte.\nDies war auf die interne Konfiguration von ClickHouse zurückzuführen, es prüft den verfügbaren Speicher und setzt seine eigenen Limits automatisch auf 75% davon.",[160,2536,2537],{},"Das Problem war, dass ClickHouse den gesamten Speicher des Knotens (4GB) betrachtete und versuchte, 75% davon (3GB) zu verwenden, was zu Speicherproblemen für die anderen Pods führte, die auf demselben Knoten liefen.",[160,2539,2540,2543],{},[224,2541,2542],{},"Die Lösung:"," Setzen Sie ein Kubernetes-Speicherlimit für den ClickHouse-Container. Wenn Kubernetes ein Speicherlimit setzt, wird dieses über cgroups an den Container übergeben. ClickHouse liest dieses cgroup-Limit anstelle des gesamten Knotenspeichers und berechnet 75% des Container-Limits anstatt 75% des Knotenspeichers.",[160,2545,2546],{},"Durch das Setzen eines Limits von 1229Mi (ungefähr 1.2Gi) sieht ClickHouse jetzt nur noch diese Menge und verwendet 75% von 1229Mi (≈922Mi), was es gut innerhalb akzeptabler Grenzen hält und gleichzeitig genug Speicher für andere Pods übrig lässt.",[405,2548,2549],{},[408,2550,2553],{"className":2551,"code":2552,"filename":1990,"language":205,"meta":21,"style":21},"language-yaml shiki shiki-themes github-light github-dark","clickhouse:\n  resources:\n    limits:\n      memory: 1229Mi  # 1.2Gi max um innerhalb der Knotenkapazität zu bleiben\n",[371,2554,2555,2560,2565,2570],{"__ignoreMap":21},[417,2556,2557],{"class":419,"line":20},[417,2558,2559],{},"clickhouse:\n",[417,2561,2562],{"class":419,"line":96},[417,2563,2564],{},"  resources:\n",[417,2566,2567],{"class":419,"line":111},[417,2568,2569],{},"    limits:\n",[417,2571,2572],{"class":419,"line":128},[417,2573,2574],{},"      memory: 1229Mi  # 1.2Gi max um innerhalb der Knotenkapazität zu bleiben\n",[547,2576,246],{"id":2577},"hürde-festplatte-voll-aufgrund-von-systemlogs",[160,2579,2580,2581,2584],{},"Nach einigen Monaten Betrieb von Plausible stellte ich fest, dass die ClickHouse-Datenbank ",[224,2582,2583],{},"7.5GB Festplattenspeicher"," allein für Systemlogs verbraucht hatte. Dies füllte schnell die zugewiesene Festplatte und führte zu Abstürzen der Container.",[160,2586,2587],{},"ClickHouse aktiviert standardmäßig mehrere umfangreiche Systemlogs:",[283,2589,2590,2596,2602],{},[286,2591,2592,2595],{},[224,2593,2594],{},"trace_log",": Verwendete 2.6GB",[286,2597,2598,2601],{},[224,2599,2600],{},"metric_log",": Verwendete 1.9GB",[286,2603,2604,2601],{},[224,2605,2606],{},"asynchronous_metric_log",[160,2608,2609],{},"Für ein kleines Analytics-Setup wie unseres sind diese detaillierten Systemlogs unnötig und verschwenderisch.",[160,2611,2612,2614,2615,2618],{},[224,2613,2542],{}," Die umfangreichen Systemlogs vollständig deaktivieren und nur wesentliche Logs mit kurzen Aufbewahrungszeiten behalten. Ich verwendete die ",[371,2616,2617],{},"extraOverrides","-Konfiguration im ClickHouse Helm-Chart, um eine benutzerdefinierte XML-Konfiguration einzufügen:",[405,2620,2621],{},[408,2622,2624],{"className":2551,"code":2623,"filename":1990,"language":205,"meta":21,"style":21},"clickhouse:\n  logLevel: warning\n  extraOverrides: |\n    \u003Cclickhouse>\n      \u003C!-- trace_log deaktivieren (verwendete 2.6GB) -->\n      \u003Ctrace_log remove=\"1\"\u002F>\n      \u003C!-- metric_log deaktivieren (verwendete 1.9GB) -->\n      \u003Cmetric_log remove=\"1\"\u002F>\n      \u003C!-- asynchronous_metric_log deaktivieren (verwendete 1.9GB) -->\n      \u003Casynchronous_metric_log remove=\"1\"\u002F>\n      \u003C!-- query_log behalten, aber mit kurzer 7-Tage-Aufbewahrung -->\n      \u003Cquery_log>\n        \u003Cdatabase>system\u003C\u002Fdatabase>\n        \u003Ctable>query_log\u003C\u002Ftable>\n        \u003Cpartition_by>toYYYYMM(event_date)\u003C\u002Fpartition_by>\n        \u003Cttl>event_date + INTERVAL 7 DAY\u003C\u002Fttl>\n      \u003C\u002Fquery_log>\n      \u003C!-- text_log behalten, aber mit kurzer 3-Tage-Aufbewahrung -->\n      \u003Ctext_log>\n        \u003Cdatabase>system\u003C\u002Fdatabase>\n        \u003Ctable>text_log\u003C\u002Ftable>\n        \u003Cttl>event_date + INTERVAL 3 DAY\u003C\u002Fttl>\n      \u003C\u002Ftext_log>\n    \u003C\u002Fclickhouse>\n",[371,2625,2626,2630,2635,2640,2645,2650,2655,2660,2665,2670,2675,2680,2685,2690,2695,2700,2705,2710,2715,2720,2724,2729,2734,2739],{"__ignoreMap":21},[417,2627,2628],{"class":419,"line":20},[417,2629,2559],{},[417,2631,2632],{"class":419,"line":96},[417,2633,2634],{},"  logLevel: warning\n",[417,2636,2637],{"class":419,"line":111},[417,2638,2639],{},"  extraOverrides: |\n",[417,2641,2642],{"class":419,"line":128},[417,2643,2644],{},"    \u003Cclickhouse>\n",[417,2646,2647],{"class":419,"line":143},[417,2648,2649],{},"      \u003C!-- trace_log deaktivieren (verwendete 2.6GB) -->\n",[417,2651,2652],{"class":419,"line":470},[417,2653,2654],{},"      \u003Ctrace_log remove=\"1\"\u002F>\n",[417,2656,2657],{"class":419,"line":476},[417,2658,2659],{},"      \u003C!-- metric_log deaktivieren (verwendete 1.9GB) -->\n",[417,2661,2662],{"class":419,"line":488},[417,2663,2664],{},"      \u003Cmetric_log remove=\"1\"\u002F>\n",[417,2666,2667],{"class":419,"line":502},[417,2668,2669],{},"      \u003C!-- asynchronous_metric_log deaktivieren (verwendete 1.9GB) -->\n",[417,2671,2672],{"class":419,"line":36},[417,2673,2674],{},"      \u003Casynchronous_metric_log remove=\"1\"\u002F>\n",[417,2676,2677],{"class":419,"line":512},[417,2678,2679],{},"      \u003C!-- query_log behalten, aber mit kurzer 7-Tage-Aufbewahrung -->\n",[417,2681,2682],{"class":419,"line":900},[417,2683,2684],{},"      \u003Cquery_log>\n",[417,2686,2687],{"class":419,"line":912},[417,2688,2689],{},"        \u003Cdatabase>system\u003C\u002Fdatabase>\n",[417,2691,2692],{"class":419,"line":920},[417,2693,2694],{},"        \u003Ctable>query_log\u003C\u002Ftable>\n",[417,2696,2697],{"class":419,"line":930},[417,2698,2699],{},"        \u003Cpartition_by>toYYYYMM(event_date)\u003C\u002Fpartition_by>\n",[417,2701,2702],{"class":419,"line":941},[417,2703,2704],{},"        \u003Cttl>event_date + INTERVAL 7 DAY\u003C\u002Fttl>\n",[417,2706,2707],{"class":419,"line":953},[417,2708,2709],{},"      \u003C\u002Fquery_log>\n",[417,2711,2712],{"class":419,"line":965},[417,2713,2714],{},"      \u003C!-- text_log behalten, aber mit kurzer 3-Tage-Aufbewahrung -->\n",[417,2716,2717],{"class":419,"line":975},[417,2718,2719],{},"      \u003Ctext_log>\n",[417,2721,2722],{"class":419,"line":46},[417,2723,2689],{},[417,2725,2726],{"class":419,"line":998},[417,2727,2728],{},"        \u003Ctable>text_log\u003C\u002Ftable>\n",[417,2730,2731],{"class":419,"line":1008},[417,2732,2733],{},"        \u003Cttl>event_date + INTERVAL 3 DAY\u003C\u002Fttl>\n",[417,2735,2736],{"class":419,"line":1020},[417,2737,2738],{},"      \u003C\u002Ftext_log>\n",[417,2740,2741],{"class":419,"line":1031},[417,2742,2743],{},"    \u003C\u002Fclickhouse>\n",[160,2745,2746],{},"Diese Konfiguration reduzierte die Festplattennutzung erheblich, während die wesentlichen Abfragelogs (mit 7-tägiger Aufbewahrung) und Textlogs (mit 3-tägiger Aufbewahrung) für Debugging-Zwecke bei Bedarf erhalten blieben.",[547,2748,2750],{"id":2749},"hürde-unnötige-kosten-in-unserem-aks-cluster","Hürde: Unnötige Kosten in unserem AKS-Cluster",[160,2752,2753,2754,2756],{},"In meinem vorherigen Artikel ",[238,2755,93],{"href":98}," haben Sie einige Tricks gelernt, um die Kosten Ihres AKS-Clusters zu reduzieren. Diese sind auch in dieser Lösung integriert.",[283,2758,2759,2768,2774],{},[286,2760,2761,2764,2765,377],{},[224,2762,2763],{},"Ephemere Festplatten verwenden",": Diese werden direkt auf dem lokalen Speicher der VM gespeichert und verursachen ",[224,2766,2767],{},"keine zusätzlichen Kosten",[286,2769,2770,2773],{},[224,2771,2772],{},"Standard_B2s-Konfiguration",": Die kostengünstigste verfügbare VM-Konfiguration",[286,2775,2776,2779],{},[224,2777,2778],{},"Anzahl der Pods pro Node erhöhen",": Um mehr Workloads auf der Standard_B2s-Instanz zu ermöglichen",[278,2781,2783],{"id":2782},"abschließende-gedanken","Abschließende Gedanken",[160,2785,2786,2787,2789],{},"Wir haben die Bash-Skripte aus unserem ",[238,2788,253],{"href":98}," erfolgreich in eine produktionsreife Kubernetes-Bereitstellung auf Azure transformiert.\nDurch die Nutzung von Terraforms deklarativem Ansatz und der verwalteten Infrastruktur von AKS verfügen Sie nun über eine Plausible Analytics-Instanz, die nicht nur läuft – sie ist skalierbar, wartbar und bereit für den Einsatz unter realen Bedingungen.",[160,2791,2792,2793,2795],{},"Das Schöne an diesem Infrastructure as Code-Ansatz liegt in seiner Wiederholbarkeit. Benötigen Sie eine Staging-Umgebung?\nDuplizieren Sie einfach den Ordner ",[224,2794,1087],{}," mit unterschiedlichen Variablen.\nMöchten Sie in einer anderen Region bereitstellen? Ändern Sie einen einzigen Parameter.\nJede Infrastrukturentscheidung ist im Code dokumentiert, wird durch Pull Requests überprüft und kann bei Bedarf zurückgesetzt werden.",[160,2797,2798],{},"Obwohl diese Einrichtung für ein einfaches Analyse-Tool übertrieben erscheinen mag, werden Ihnen die hier erlernten Muster (modularisiertes Terraform,\ncert-manager-Integration, ordnungsgemäße Secret-Verwaltung) bei jeder produktiven Kubernetes-Workload gute Dienste leisten.",[2800,2801,2802],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":21,"searchDepth":96,"depth":96,"links":2804},[2805,2806,2807,2810,2811,2812,2822],{"id":280,"depth":96,"text":281},{"id":347,"depth":96,"text":348},{"id":390,"depth":96,"text":391,"children":2808},[2809],{"id":549,"depth":111,"text":550},{"id":721,"depth":96,"text":722},{"id":761,"depth":96,"text":762},{"id":780,"depth":96,"text":781,"children":2813},[2814,2815,2816,2817,2818,2819,2820,2821],{"id":1143,"depth":111,"text":1144},{"id":1586,"depth":111,"text":1587},{"id":1801,"depth":111,"text":1802},{"id":2150,"depth":111,"text":2151},{"id":2346,"depth":111,"text":2347},{"id":2531,"depth":111,"text":241},{"id":2577,"depth":111,"text":246},{"id":2749,"depth":111,"text":2750},{"id":2782,"depth":96,"text":2783},"2025-09-20T16:34:55","Erfahren Sie, wie Sie eine kostenoptimierte, vollautomatische Kubernetes-Infrastruktur auf Azure mit Terraform aufbauen, komplett mit HTTPS-Zertifikaten und Backup-Strategien",{"type":157,"value":2826},[2827],[160,2828,222,2829,227],{},[224,2830,226],{},"Plausible.io, Terraform, Kubernetes und Azure Logos","object-top","\u002Fposts\u002F4\u002Fcover.jpeg",[2835,2836,261],"Kubernetes","Azure",{},{"text":2839,"minutes":2840,"time":2841,"words":2842},"12 min read",11.83,709800,2366,{"title":135,"description":2824},"Iz80QP_0ZxrsnLL4Lw-Bw7nRTw4h1hU6cWLZxbA2r6g",{"id":200,"company":2846,"extension":205,"fullName":206,"github":207,"homePage":208,"imageUrl":204,"linkedIn":209,"meta":2847,"role":211,"stem":212,"twitter":213,"userName":214,"__hash__":215},{"name":202,"url":203,"imageUrl":204},{},1786173071323]