From 2c0b6b5ea98a6522a5725e8a99752bb789097a1a Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Wed, 18 Mar 2026 22:41:12 +0100 Subject: [PATCH 001/113] authn: public paths --- cluster-resources/policies/auth-sidecar-injector.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index 3259d9d..a680ccc 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -134,6 +134,8 @@ spec: value: ":8080" - name: AUTH_UPSTREAM_URL value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}" + - name: AUTH_PUBLIC_PATHS + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}" - name: AUTH_TOKEN_FILE value: "/etc/auth/tokens" - name: AUTH_MODE @@ -225,6 +227,8 @@ spec: value: "{{ regex_replace_all('https?://[^/]*', request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-callback-path\", '') }}" - name: AUTH_OIDC_SCOPES value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-scopes\" || 'openid,profile,email' }}" + - name: AUTH_PUBLIC_PATHS + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}" - name: AUTH_OIDC_COOKIE_SECRET valueFrom: secretKeyRef: @@ -307,6 +311,8 @@ spec: value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-resource\" }}" - name: AUTH_MCP_AUTHORIZATION_SERVERS value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-authority\" }}" + - name: AUTH_PUBLIC_PATHS + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}" - name: AUTH_MCP_SCOPES_SUPPORTED value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'read,write' }}" resources: From 0de4e381c773d4eb65dd0545b1bfc99a14b1fcbe Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 19 Mar 2026 10:38:02 +0100 Subject: [PATCH 002/113] mm secret --- secrets/musicman-auth-oidc-sealed.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 secrets/musicman-auth-oidc-sealed.yaml diff --git a/secrets/musicman-auth-oidc-sealed.yaml b/secrets/musicman-auth-oidc-sealed.yaml new file mode 100644 index 0000000..ba36df7 --- /dev/null +++ b/secrets/musicman-auth-oidc-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: auth-oidc + namespace: music-man +spec: + encryptedData: + client-secret: AgAKQ24qP43Dopfb6LfH0o7RIQjK3IMjII8knmdpfL5ldXJRGaYYYQDjZJcIE5HiLoEFXKkLHm39k83l6sJ1fn1RCbbskSGmosYNenJgxOqTZdlyC911km1PVMu890N2JEqUVRQb4YTn8+1dYUglWPL+zaAki3wJ84G5uM6ZY0R7mMEp/5J8RFEeByzdTiHLtB9U2fPnBJiGdKfFN/ysnoZOLN0nAHvvnTEa4pkO2BXNrrhVl7Nje/y9IDmQntCNIHomF9Fy4bo1SpChj1vRXWbLETlMdFkRINHMVWAvemoIDv+CaHVRaaoy/XtTngqA5lyZZwiwiix+HBIDPE5HAnGE/Zqgc2L+ByR3ak33H6O6iGJoVdR79uu9PiW0iaWJwruXgSfm7Fq0KuOHNg0QzfTqsZf0jgT5RhT4apSGHZOl2g4QUkMUee6BPuFM435ZZFdOfDZw1YRdqdFvI6OJgx7+G+kwwnEa125LuBgbJzqLRVxH0LbD9HoBoEqZUB4H0V+0PUY7uVSKcpZUi0RwiLjcoHJzaJlgJdaEc9GrDhh8OvDXOxsBJcRrJDmAH5WbGLXbq7+HUknfbJFkcl6rg9bPWDah0DDf98weqUdafpn0TTt7qoOGW1z27yVApGn9bm9B7ykCa/i9QtX/6G9RTaoslTU45pYzA1Clw9ccRNxv7O+2afU5KZORZPYNXCho4K7tpR7Hw788Rj68x1GYxrZh82+s0fs/SYpvzvN6EiFxKY8R+f1azefD + cookie-secret: AgAPJomNWVV2m8izVg8CQ78mnXP9qQaXh1WaIELv4JzwyUIcMOddDDq8PWzmAiWfV24iYj/oy9QB0jruI8+78H5nU6QsF5KB3DlZiDjzwaUeNOs1q4wPsQ/aU9JygYpWD2dYUn4pBYqQQxJyf/gJ1u6jbGMbjsO5J3kkK4RaFiTNt+A3151nmYVgWToknTPPcTaFgpH0xumhQHicO9MyaNsSKOJ1c2AeUiWHqc/0a39tuchCgq4Gm1FOKjXXfevcZw/IamnRiEegoAr5qgSPl7e1xfZeG49FJacsfpWEuQhmKGsaePTHok141VnkKHhlmFdewe3Sn+kSWJbtAx6jky0sqClUKqopVjxfj5IKwcucE0NjxxX2HXmQb/e9nY3BLQ1CRjQQVCNa+JJy6DvAbcERHs7t9T7ATXm4S5lawp9fp8UB1ySy++q4Qvmc+4/16tgugqVEths/n+gSYLWAM8Ibd42RhNXlWqjzvpAUXmHX3Dt/26wNy+5ZnRlcwOIotPBdDqQKlp3fGKc2loC2JHcyN1PH31EwY5+h4pdU/sGSU+EsPd43hALUHq3XzvibHVDQTzyerJgC77NX6kvu2W+V5iWN9LYAAVjMh5yzLYjfgoC/zzIiOIe9yRf6zJzW0nhSkdcoC8EH0lcwEEDp0d/CuO0A6AwWOBaer+tnQtjHeZse9RdKEfyisJDDa7xUNj4T9e8Z+shdkU94vladQpVP1lyRd4auXF7B+T0+1Pkmvac8wDHDk67iomi+1BpS+/2jdJUGJO+91+qIdCFLPi1Y + template: + metadata: + creationTimestamp: null + name: auth-oidc + namespace: music-man From 2b71f63740654ac0af9f9cb882e168b9acdfa39e Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 19 Mar 2026 12:46:51 +0100 Subject: [PATCH 003/113] dot-ai --- secrets/dot-ai-secrets-sealed.yaml | 18 ++++++++++++++++++ secrets/dot-ai-secrets.yaml | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 secrets/dot-ai-secrets-sealed.yaml delete mode 100644 secrets/dot-ai-secrets.yaml diff --git a/secrets/dot-ai-secrets-sealed.yaml b/secrets/dot-ai-secrets-sealed.yaml new file mode 100644 index 0000000..df92dd5 --- /dev/null +++ b/secrets/dot-ai-secrets-sealed.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: dot-ai-secrets + namespace: dot-ai +spec: + encryptedData: + anthropic-api-key: AgAVvbLxqVVQzYnBNl+FQ/+L4dcF6JkDWczore04ZRzE3XpCjf48cTjB6lnRgibVI7Soyj2lJ3k9lZBMwaxLOVa5o2t3vnMxvrtCNQGTqBHSQrdGFiFES1wmAwnrhSzgvQNdCKFF4e3pCk9LTzLVr5W+yDrEjZ4ykIr8WH5n/YzGgnFxV6s467h7DvmKR2z5PdWVdqmMSIetSYwk+JGKGKLZ64rgLT9qRKvnRt8krLqTpEpBrZjQ5vJeQVaUPuT1e6BepBHUHFtPyCKTyJGAirH3TGbU56TZSgqwew2MQ5xzrtqoBulrevEqDKIECTJnlHHV0MiJQ081Ap1e+MLMYR0dAqyqWiguiObQPPM3+LsRWvEtorvhqKMtm15WrvyDV7kRx09E296sbHcD4V4gD2LQrwJcsfq57mTqzRMxFqKPrqzIGQUJTMloCrK9gApLvF3WkKkq6gUqupklkXBycgthKVc42WWOfS5yvDqr23+yNSMGmiV++aVBL+8xRKdaGnz9fW5/t9PtaUJ+2X3iz1LEOiIVAUQjDqeseO4Q7N+Fe+Dhm8g0hdkiHmw9HY2pq1jjAqAXBoZaVZvbA5/2E07nlBI2xazixuAiXRLtCHXczFT0JdJLXdAhZYyULTnqWELkCspknjJYw73gQmUtjr09BstTFKufo4wIaG/26Oo/xEqkHzmRZT5/k1gorx0T7OVmyCQK8d5q8nTPweYdLWHnqqoAO6IAzfFWie5NgvRbUP+ucJT7gMnvBgFRKu1sPTfMebPT2SQtkj2N/Jsn0s8ExGiQQ7/Hc39pwOWGppDgO1WXnwcrzmngzL7lxOK3L9jIwnJMSwvZV5WQDzk= + auth-token: AgB1EePlDQOtWknzGD6XZ5mL5uKv2ndcPC59SoEAOepNlPx2dZGe1oXCrk45MmAnhx/8iPRQjaCR5yz/Wz4kFQIgYESRg+lWAer+Rh3u61uHIzFZwl2KUU90cVET2pgoo1mFwT/XGESwqaA/+5nT8UmOA3LxuL7BeklPqUJp/IMMUobSfJJLjZY1exueLmY/Oeffs2LamdF24+ByR0yhlCp0EgpM2+Oh4u4NZAjsMFWlJsknw+L7L/SlUjj7OCsb9r6Sl4JrjNDyOhfnM7ngSvb8BFtkGbgQAPyRwc+uOENJ9A4XnyHePANrONDtgepaj6j7gLMv4zEyVVLKdLsVcS2NzlsrHCxP/tytl6bxmoWZ58ARd9gyhBrEWEsHj5eO/vymuoFAF/7kF0CkoYwQ61aeO1o3bFu+a0TPzNGl5Kh67qQU6xNQLZvI7u3jbYph01Lp0kiZtp9vkO714P6N0SB30dTAiKYkEIL8UPqAuKsUjm+sUlEst4eAHxUhmG1j67DX+iToPbI7kFRHQ9r349hyugQJOEPo5CkUXnoR+HtuG4NaH1ADM5PF1aYMHsC0D825d1wVJvL60h08uwwMsSGOASBj25bELhlV4u9rfgL/pJacyYurvu+YC++S0vkv6QaexRQojIzf2lrMRxIX6byuZZ0awmdLA/uKP3hjtf2sX4S4zce6Hp86kFRo/yEAk6sct2cZ4mUsqjgoFJKvKVqJXMVHdTNHhTyJrpkoBbPzeSjYvZZAof091nrFDg== + openai-api-key: AgAko+W3hgncmmupjZuiWofsKP1zTfoSxLPHyO6tzESQ85pQCMl/hYs7RGsCa7xF/fZSrxeIDWp/WtzeNxeOQIX2RS2MdBeYlf4txLTs/xwaHbKYYxz3IxMCOdtj+4Y15jMoOv7L5/Hh5HWDebZeSMIRVBKtnOTCsGNunaWF3tYqiY9ghDjotF7JiQ4kMWmyL0VtH5ihqDGcKnL8NWEe+I84kAoqte7K/9BxUYzKPQ6TCl3DUTXeaQDAtVbT8OyiiC/VdjT0Sf1frI/GmeRVatKLkK4DqJS/E/IOEY9zPeOqwRgciQvB7nZLOa10eu072CVz8h38EmvVhel9PAhFkMfbKQ85k5RJaKJd2rtqiE1qSbsj3WISwvfyb3UGMa/jGUa0v7WTf9N9cDY6SBx2iq/lcrkf6d7qHxqh783yHKivtz7uwb52AaEC1w8DnlURp1AWdHUk85/v1prwTfJdd9bhx+St4c08GxaZUMOMzebtwAi4nXk7Qgp53tXTbGPVSDJuSdV+FnJ0i0KfH/0cGaabQNQG7lgBvEIxMmnN9Tac+RznLTfKpVMNwte9JY6TzG3MyB3Kw1nEwB7MWqKJc+/SuZtQzYjN27p8oeMj+NUNdShydyX+3QLuQth+LUl0YzXdkguMCpWtdf7FU9riO3/fVOQHG+OyDLMJ3WiP4cHULKCBMqBVSR+yFcFzdigoLJREc4K+kYG/T0ApKMBmBtZs970yDMUULbzuLTVkE2NPGHrqoJTcpqXeopCzdAaixblOSr+yi9BaTr9krxmSzLOYTUD4cQX1HPRFuIAlWFIS79sEjeRNjIpjmWQDn7BL/VuHWJwsAl7MNxa9xQ9avChf8MDJJrCNUrjvb+bbw5RWxuJT9gUxBPblc+u+G6csL1bYG/lHsmp4DvlhunEG0AFx6u07KA== + ui-auth-token: AgCZfcMfCg0OS2EdgZi06+4VuU6VdoBAvUUhuRvq3VDw3MnbaGXnWe8x9V6xlzbx7bnsJFLEgKHZeEe17Fgx7+riZUisOOJgd2p3PE05Uum2OkDZkIEZdjPe1OW86b8z7Q09qirMRkIqcxIlZMkF2i7PUPL0rMtzMTe/h0siaEXB2ywXx0QMVDK3ffeNyzKFPuPyQsOozcTXxNiS3CVxmXx2HoIbWl02we6rBxSHEnI4z7Wc/lo4EpeherjJpTRgAJYDKrPj6Xuj/pO7sz/KhEfJXo2RtjfFunk/2nDTJcdXStnP54jsAA99PONeC8/hjzkLu+QJB6uXB0uIMWM40vyHqCk1yzXUlwmtgUajEk75QlygpHxluOrSIyVyuR+tTxa/RsRlcEXi4lwFD/yzVsc2xVRLynE22+rhHXhzjFQAMGyzItoOEB6JfiV2zDXQg04JsIYx0m7VUC+DQG0Li88EXQQLaWCi+84TiQZWHNdaZz9qFtd0OugyNmdx9FUjHy2xkFO5Mc/AKyMnNB2aN6R9eLPqFhgZ5jyDSaFNHwMfToH0ABZ0qbkq5BOPu5+qVbEIUobFALwX0r/+peEKegwH4xc5GSnN8Pj0PVLccRabZvDHvWXo4zatFSsucHD8BQIE/Yb2uFx7TPLTtwLaNPOr8yJoyYXtRLxcc02N6Kw6l0EG3x+XQiJf6Ty7TvvTI+HvewZK+29qiyQ87j09gE0DaC0seXdYq4gm3B/FvKerM75BqsOihNO1ORonMA== + template: + metadata: + creationTimestamp: null + name: dot-ai-secrets + namespace: dot-ai diff --git a/secrets/dot-ai-secrets.yaml b/secrets/dot-ai-secrets.yaml deleted file mode 100644 index 7f6f113..0000000 --- a/secrets/dot-ai-secrets.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# SealedSecret created after namespace (sync-wave: 0) -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - creationTimestamp: null - name: dot-ai-secrets - namespace: dot-ai -spec: - encryptedData: - anthropic-api-key: AgASIRVNs7kIvZ/XwFJ4j9TJ05TW4YlFNzi2lx+6URlvzjTkMignK+y4/HD3wTK37BkIonnOXf/DGJcMgqgxOrHBVu9tLkSamve5qgf+SOZ8jxaqpLX2e5hdmHrgMp7rVWKiOoxM+bvu8Gdve3eMwXX8Eazj7W7vi430LsvW4BmPrQp+A9SOP+hwMlV5r+P5pUogC3hddt5KoSwpgnpOb8fRGlLDpc548eDNBJrhsAZiiFqc8+CycHX6idxWzlWOTPR05LBs7YrTIP/LUXWdyq6UG8yB5D0c9JNndnGo6kSfJxdaxCNO7GhMAOmo4CitzYn/zhICwuHPLKwUYQBcEHVpFxgub0U9fY7s4i0SAH9aF1kk8yqHWaCicHzi6B3AxMZYr+knS5vVNB+uGZmSKgZ7QNE1bF6E4Gg5bXRwk7OVQhuJSBzxVNDoXoJ6stL8ejU+MP5FtI792FxZbNDImu3kqor/t9wB49ZeB9Ngks2yvtlSrQ3G2lxJaOgNBPmKYhAX95JFI3xUSHOMa18ftn+aXmEQTXOHtM/IT8A95gUvIKz8Hrt03CwgZ8E+3lcsjAVsfNjqn/erqV+xi93OYOL6o9ivmTIem3MzsqjKuQ57HKaghnd+Ygdt/WfotRBSgZtCweOkZBV0XKxI1RmCQXk6dce8x4T7FDEeaSQwkc4yZ77LfQJnyVgU1rds9Hqv0RbymHK8JCQEMMb6PcK+Ow068fdj4NP27HkuYMB5JdPp682KIRwGznN2d3+QPXRd/ZHshnixudY7CLirlZl9xD0HssfSBI+uCrFEAln8D93UYbn7trF5gkKWYQsQ07CVZ5Z1qXieqwJWjHRY+oY= - auth-token: AgB65rUY3yJuTTLbGyrCtJPZp8UyEOr+kznSMGvUaZ9PoHq+kIEhazRdVKHa/WGvMWuc4x+XYRuIGj+2KiooyilUPrLcE3bz9fUOAFbRw3+iUh3WAgwK+f3eBUG6/0OoFw+GJ583IRidDW1t2BchMxSM1m+3vmQoF24qj7k9j/lWPsnX2IX3DhM0SomD1xmG+LsQMo2e4vQXB0BxhVDIQ621JTrvUPYzYx0NlPqZO/MtR1JWYS7WBTvegvgeBKLxfy9KLnqsuzu7rc2t9BYt7TRqvBg/prrKPdSV6Ei4GOZq+AcG15iOKXhsj8SxejPpM0QDemFTRQkdfz+k8ms4/SM0eylr5fEaa99TMTvjYGCfjJJyRcVm5Ef/XdmXiM3OI1u+9QNPiqXh1zmtp0yQMZ7nRE70kKQ2MHVhEmSUkBjovybIzLk4OL1v0FGDDqa1BN9KmNEBlo8H7DqXzfamVoNPyuuO625B3HgSeR3Udq8hngy9W/wiolmMNSc9C6vBA0HzE7Mkq79fHh/ruTi5zOJLfyEP7KQQqlNKfEtDFQfabaV9ERthQjuUs8ZIrdfCTOyQkrmmGY/sH5yodLRSYSEpHFrhPepwHS7lGzbVucW3Fn34D4OxJXyXQQObZKybLV7g6Oglf2Pdn11CEu/4+19RlykuOlm1dlj852a5NWWmmX319laLGlEd1BpG4cZyc9UZud68dGumwzrUupUMTLlJx8bR5rLgqaMpQMluwvq8MQbBXm4ySOcQ2jQhjw== - openai-api-key: AgBc4cSrd0riSxKPrp7AqrNYMDAZJTe3aCjTOwEb5pS/KARkgoEGz+0ZhLXa2bcCsPlQzqRzZ1c1cUCnBACkp1xoMfhSOoIJUWJQHRgZx5kEDhNIE/M8PQe8es7jYsW7/ui6iK8uj3Ljk21r8l/ctnP/KiKyML4553el28Ya7XsPQynRvpVexFC4BoJw50nkauLnvl3in0cKmEVAkMWorUP3Etapaj4DWkCQQ22RPN0xGNo9yiIUEAVE+uTRFNKGEHMIvJzgB291FJQtG/WoN/Sy/DeoZkUYndA7CbfFgiz9sq3GqJwyqTM+Ikzw6VCQ3TVWYO/6+yaEzT1NZ/rw00YhDyFoH+yGTkX5lrSUo8lGf3T9OODTdSS1203xtj4dhGbY70sPGMxDfucnO6piVcuRfh9bWg1VX+MTjqpBQE1J0DKJanhoh6lKbPOJhRbi0TIoCz1a3btbbKLDq2YJl7FXA3QNdBsR4qVJ3xmgWlH/eTUskCE2YxDzHmkxgZN2mL22SfeUJYtDRswRG0UGc6pvg2xrR0iEDB0UbH4FQK46fWv8aiOejteLlAAyA9HNA8yi73rTEjq55wpO19/MYj+6oUGEHpFaJje77INTHAKpdbfkp8iKotNXFYLM2hsjyau+x/AFjg87kEdIUVVHHLVMDhOq6NO+foM0nFOIv4lr2Yjl15+ImMSTEfcBr+nWaomnb9G8i5ptqz9bMHbxAcHpzWUBH9SZhzrCWZNDzOm2X2K6mpXhg4WX7VJMoIJk/f+bxTTKnWBawdnpCdbdG5GYQh7usqjPuELFYRTsx+6Tqoddp4KIEyMMxO81XObiN5vEBg74ygunxrjKNg5FoOkUA79YvcGBVVU1FNRl3d8IslXFqhEOT35nxGBB0T7ZORii34tZ2E551NkIo1WbCwilD3Dlgw== - ui-auth-token: AgDStP4jHhMehx/SAv5x2aQrJwChnWq8WhV/LVTSuuCSSy9kFQGa3Izyl21sMhLKUgrlS030GH50lbku+IY90S+MSBfaCq0Xb8qwohfH+qJulMRgljoU8r58/3ZsaBzbGJjiNMgBCwGlvuK85t3R3662ftwikahWqLfwAahi12L0llUDNyG9UB0Os3oR2CVAt83+YB07YspCWRzwuOz9D1SgOL/g/JF2hoL43pDD65BqYKdEuLFInJZx1Ul68V/FPmJK1gmJb/Kv8pgtk0sGTzxbbTdIQ1Ugf6+8//CWEq6GsMeEZG9VwExL/KYqobnbqd02FRgSSvWybhWLjiALLQvAFtce6FR6bur0rsariyogGjYCuXYdRnhf7QnB6NopwplhFP37Qf6E4q2pJTJ6Fzv0xq+XlfzNkn9Jvebrdkk/5CThW+wPAfx8r1VIySqBsYnPO/ZQDkfQ5ocX6fmzZ1iCg+0NI24k2YwCeMZtEAGpKEtejS0BLvqrilUG6Oz9sHCGHro4Bv5B/jzUjGbye0DSDj1f6c2RcpqMiUxfPvydJIcJGhrTx8sZS3qhEWME7kwjJCZpHEzfdv0weSJbgVGBSh9e1wjZxxeJGXuZKdFzdhpNEWP4uScGW0UnRDwxZzHMsLjS/W30mQmwmgJVTKdPl6VQrvdq7m4AC6+/d7iXlHazieC1KpBme4hWzO+/h7qRw1P5Va/ZWZtnGs8476J8hIojRtOJ/CdWwVLUa0gHo4CYnempIMwCIS3GtA== - template: - metadata: - creationTimestamp: null - name: dot-ai-secrets - namespace: dot-ai From 4c0ec63ec31f2876276ccc88d77b3250dc42e98a Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 19 Mar 2026 12:51:07 +0100 Subject: [PATCH 004/113] apikey --- secrets/dot-ai-secrets-sealed.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/secrets/dot-ai-secrets-sealed.yaml b/secrets/dot-ai-secrets-sealed.yaml index df92dd5..3b4df09 100644 --- a/secrets/dot-ai-secrets-sealed.yaml +++ b/secrets/dot-ai-secrets-sealed.yaml @@ -7,10 +7,10 @@ metadata: namespace: dot-ai spec: encryptedData: - anthropic-api-key: AgAVvbLxqVVQzYnBNl+FQ/+L4dcF6JkDWczore04ZRzE3XpCjf48cTjB6lnRgibVI7Soyj2lJ3k9lZBMwaxLOVa5o2t3vnMxvrtCNQGTqBHSQrdGFiFES1wmAwnrhSzgvQNdCKFF4e3pCk9LTzLVr5W+yDrEjZ4ykIr8WH5n/YzGgnFxV6s467h7DvmKR2z5PdWVdqmMSIetSYwk+JGKGKLZ64rgLT9qRKvnRt8krLqTpEpBrZjQ5vJeQVaUPuT1e6BepBHUHFtPyCKTyJGAirH3TGbU56TZSgqwew2MQ5xzrtqoBulrevEqDKIECTJnlHHV0MiJQ081Ap1e+MLMYR0dAqyqWiguiObQPPM3+LsRWvEtorvhqKMtm15WrvyDV7kRx09E296sbHcD4V4gD2LQrwJcsfq57mTqzRMxFqKPrqzIGQUJTMloCrK9gApLvF3WkKkq6gUqupklkXBycgthKVc42WWOfS5yvDqr23+yNSMGmiV++aVBL+8xRKdaGnz9fW5/t9PtaUJ+2X3iz1LEOiIVAUQjDqeseO4Q7N+Fe+Dhm8g0hdkiHmw9HY2pq1jjAqAXBoZaVZvbA5/2E07nlBI2xazixuAiXRLtCHXczFT0JdJLXdAhZYyULTnqWELkCspknjJYw73gQmUtjr09BstTFKufo4wIaG/26Oo/xEqkHzmRZT5/k1gorx0T7OVmyCQK8d5q8nTPweYdLWHnqqoAO6IAzfFWie5NgvRbUP+ucJT7gMnvBgFRKu1sPTfMebPT2SQtkj2N/Jsn0s8ExGiQQ7/Hc39pwOWGppDgO1WXnwcrzmngzL7lxOK3L9jIwnJMSwvZV5WQDzk= - auth-token: AgB1EePlDQOtWknzGD6XZ5mL5uKv2ndcPC59SoEAOepNlPx2dZGe1oXCrk45MmAnhx/8iPRQjaCR5yz/Wz4kFQIgYESRg+lWAer+Rh3u61uHIzFZwl2KUU90cVET2pgoo1mFwT/XGESwqaA/+5nT8UmOA3LxuL7BeklPqUJp/IMMUobSfJJLjZY1exueLmY/Oeffs2LamdF24+ByR0yhlCp0EgpM2+Oh4u4NZAjsMFWlJsknw+L7L/SlUjj7OCsb9r6Sl4JrjNDyOhfnM7ngSvb8BFtkGbgQAPyRwc+uOENJ9A4XnyHePANrONDtgepaj6j7gLMv4zEyVVLKdLsVcS2NzlsrHCxP/tytl6bxmoWZ58ARd9gyhBrEWEsHj5eO/vymuoFAF/7kF0CkoYwQ61aeO1o3bFu+a0TPzNGl5Kh67qQU6xNQLZvI7u3jbYph01Lp0kiZtp9vkO714P6N0SB30dTAiKYkEIL8UPqAuKsUjm+sUlEst4eAHxUhmG1j67DX+iToPbI7kFRHQ9r349hyugQJOEPo5CkUXnoR+HtuG4NaH1ADM5PF1aYMHsC0D825d1wVJvL60h08uwwMsSGOASBj25bELhlV4u9rfgL/pJacyYurvu+YC++S0vkv6QaexRQojIzf2lrMRxIX6byuZZ0awmdLA/uKP3hjtf2sX4S4zce6Hp86kFRo/yEAk6sct2cZ4mUsqjgoFJKvKVqJXMVHdTNHhTyJrpkoBbPzeSjYvZZAof091nrFDg== - openai-api-key: AgAko+W3hgncmmupjZuiWofsKP1zTfoSxLPHyO6tzESQ85pQCMl/hYs7RGsCa7xF/fZSrxeIDWp/WtzeNxeOQIX2RS2MdBeYlf4txLTs/xwaHbKYYxz3IxMCOdtj+4Y15jMoOv7L5/Hh5HWDebZeSMIRVBKtnOTCsGNunaWF3tYqiY9ghDjotF7JiQ4kMWmyL0VtH5ihqDGcKnL8NWEe+I84kAoqte7K/9BxUYzKPQ6TCl3DUTXeaQDAtVbT8OyiiC/VdjT0Sf1frI/GmeRVatKLkK4DqJS/E/IOEY9zPeOqwRgciQvB7nZLOa10eu072CVz8h38EmvVhel9PAhFkMfbKQ85k5RJaKJd2rtqiE1qSbsj3WISwvfyb3UGMa/jGUa0v7WTf9N9cDY6SBx2iq/lcrkf6d7qHxqh783yHKivtz7uwb52AaEC1w8DnlURp1AWdHUk85/v1prwTfJdd9bhx+St4c08GxaZUMOMzebtwAi4nXk7Qgp53tXTbGPVSDJuSdV+FnJ0i0KfH/0cGaabQNQG7lgBvEIxMmnN9Tac+RznLTfKpVMNwte9JY6TzG3MyB3Kw1nEwB7MWqKJc+/SuZtQzYjN27p8oeMj+NUNdShydyX+3QLuQth+LUl0YzXdkguMCpWtdf7FU9riO3/fVOQHG+OyDLMJ3WiP4cHULKCBMqBVSR+yFcFzdigoLJREc4K+kYG/T0ApKMBmBtZs970yDMUULbzuLTVkE2NPGHrqoJTcpqXeopCzdAaixblOSr+yi9BaTr9krxmSzLOYTUD4cQX1HPRFuIAlWFIS79sEjeRNjIpjmWQDn7BL/VuHWJwsAl7MNxa9xQ9avChf8MDJJrCNUrjvb+bbw5RWxuJT9gUxBPblc+u+G6csL1bYG/lHsmp4DvlhunEG0AFx6u07KA== - ui-auth-token: AgCZfcMfCg0OS2EdgZi06+4VuU6VdoBAvUUhuRvq3VDw3MnbaGXnWe8x9V6xlzbx7bnsJFLEgKHZeEe17Fgx7+riZUisOOJgd2p3PE05Uum2OkDZkIEZdjPe1OW86b8z7Q09qirMRkIqcxIlZMkF2i7PUPL0rMtzMTe/h0siaEXB2ywXx0QMVDK3ffeNyzKFPuPyQsOozcTXxNiS3CVxmXx2HoIbWl02we6rBxSHEnI4z7Wc/lo4EpeherjJpTRgAJYDKrPj6Xuj/pO7sz/KhEfJXo2RtjfFunk/2nDTJcdXStnP54jsAA99PONeC8/hjzkLu+QJB6uXB0uIMWM40vyHqCk1yzXUlwmtgUajEk75QlygpHxluOrSIyVyuR+tTxa/RsRlcEXi4lwFD/yzVsc2xVRLynE22+rhHXhzjFQAMGyzItoOEB6JfiV2zDXQg04JsIYx0m7VUC+DQG0Li88EXQQLaWCi+84TiQZWHNdaZz9qFtd0OugyNmdx9FUjHy2xkFO5Mc/AKyMnNB2aN6R9eLPqFhgZ5jyDSaFNHwMfToH0ABZ0qbkq5BOPu5+qVbEIUobFALwX0r/+peEKegwH4xc5GSnN8Pj0PVLccRabZvDHvWXo4zatFSsucHD8BQIE/Yb2uFx7TPLTtwLaNPOr8yJoyYXtRLxcc02N6Kw6l0EG3x+XQiJf6Ty7TvvTI+HvewZK+29qiyQ87j09gE0DaC0seXdYq4gm3B/FvKerM75BqsOihNO1ORonMA== + anthropic-api-key: AgAkI0wvqHmk3lQBXDuHOnyoK9kuIO6ArNrmlYdJaAOjV/pWlUDbWHWUkfe1tCAEb4B5qoIFZCYY0ZUVe+AVM2O0zk62qW9VLYtIAN5YnOyUJdRZBs2ctAzIQD9nmjR1fkGO4UYUqfTnoPuhbVwqiYn5mYVIxqP95JBRlo6rFI66qXsmcgyAGSw8WCJfQMvE4S65BFUdu58iYjoYmTGtL1WxTALV/2aXglfBQu+bJzM0U+ckaOYI0xMhtBwO16pGR16xuGQW3KGUYV5QH2Gs/qljigZGaN1VLSjr8t9WqseGx/pqIvQbljy30xsqfOCm0ZyK8I4c85gBP1W3Jjo7G001v91emCZACatxGaZSaEzNvxWXh+spN/I5vxHnMkQ5S/PVDu61fO8z+UTnVHe97PCx9DzhKdwAaYU/RvuGlNHG8pvn+IrlLp0S7mob3cuKfJzxvYCOjZx8kbBNHkCZnoxMkVPIlrnWtSW8+QcnSoXKyXdiucLuQUfq6mBiAZ4VeTeFFChhVRJixEogoottfEJQfDq82xUO86OP7AL4bNTkb98zBHGYE2YdEYrds8CMKmRYCGdYN3vVxcAwfkE/QC3MHN5BD/D6a4YyzZllUtIORxB0Ke2gqc2ITMkHcDcDauyKmVZtO93jAQMqyhqd/RQIm3lFdAAzrlMb4QkAwvOf4Js/ABbQAhADpW/dXw6bNfiK9uqluhsokyqlxPK7Yiw1oSmgbDCjXhMkrEB//3xL2A8Hl0AGwwf+DZPZKgsIFokEwc+D0XzrRYQXVbaVb/AkuUZrCgtgzlO/WmM58eLeESjgiaiPHnIbeOzj+R2iH6BsxPLwJqdqlnIENpw= + auth-token: AgAiqOYKxMmx4q9TYoFiz0kV7USMPQ7f4idBSlJmfUnZlm1XKI9RgkDmQCeK2L9v/NwVs5TEQ2hjvMv5KarowGOeqd8y8qdM0GLhfzHX9oN1ybN80BkwQJqYy4LgIMNXiMLUkjagAHAf5DEChBOUheOulsAZTFY+n1usKk++GfO8HqDf9mDfYkHmi7QIXTuoVyYbmkmR3GEEypDUbX77mRKKCwqZ/IEgE2mm4niUcrY0duReuv0mq46SRYhMneylbb/fXrKmGu1epZW2S4YiVrRgntYjF1X/e61BS41D/8OjybUPgVGssKB1lY+9OzQvCRc42GVxJPymSHMONhMrnumLUeWSJEVJCy+ODYZ+/KDwVXxDHsu+pMhclyHRcRwkpQtzBRCehTpCx3elzfd9UA2GevDDoQeZ9AlD4cMM93GD8i+ZxadV1WvwPjE7BNqAiiM/f0XkYxDTj4wRNqPMibb76kh5l6NOVFjta44FXiVf67jxMXKGAL1wiRiuTE6GhrN0i2tzPd9GpVV3r/PsT5x+gpcNk1n4+XpWVvTvcsSsZY0ljMZ4KbSOPq/jUrQVhErrglo2EadWmjnoZ+VyC/tFDIe9hLq85CXmRxNYwwtK2JBuhf0UguT7iRj964Pnkh8FK7KEGYEpfsmGSF8H7z+yPRadISYbvseLGkzAbyrHOUFOtS73iSrwbQLXBg9qcX+4s9bwfkL88efxXJN/IrFqaWz5v5eYFh/5XffKzeTpJhir7fPi6spkxIGkUA== + openai-api-key: AgAtobul09GMkKLRKkOP6B0tPjU7EhUn+SpOz+JMMZMg8EdHOwWCFqfp2f12CPG7+ScnwD/2iZ97tNGv045jswwGaMxcs7R518/3mGjcBWu73K9q3RyW3r2sslgUJbUpUP7h4EUVjucYdP3MyTDWyTi/zQDyHG5jwBkeF/Lbb6eMWPRvJxhzUlhLgkCWJOQNuYKqliitMeryLfGn0Qkmwk3r/deI65WK9LXbVuyFp+Qh/FSOibPnEd5lxvWGAWu8lEDbWRdVQZoGI0Q05uO+bo92Sg46p0rRcsgvQutMR9u/Dm2hVbLT8pMk52LzWWdvtMr2UTIZoOb/W52s4sgQ00MHWx77zdkWbVfmCRZKQM53Ryitw94MlQp37Dv7VJTVtIf2zC8qlBSZ887vH0iq1bI03zb+uC5UjYarJkVtE/aMd8qDAaqCBl6W8ocsf9tpc8du9NJj4uSq10XGnL4wz8aoRCuth1/uJrI8jOVQQHdzzUs1+Ui16HX/ZtXxwgC1QD0q/iFdrQJ9f6T2n/boZ+jKvXILPZ7F9iMRFDm6P55esHeGezg1/1f3gO/X/OrmBQliPw70VgOFoGOGHM+toa+cgwYpDR3Ir6jTC0od8gIBGwLy0xZrIv48TWv3m5XZV60daWTorqrMK+WEzG8KGIG6NCEzltPKF5+SL9wLmY2KzFL9fPibePKyiBc2t18cosW2BhiM29UlGqsGKZjd+AkMLqTBAWf3gTSpX/FSlNCwSaU9RQ9DmoilbyCEhgJ+LIzyaK2H6ZqsiYvbv0W/QHXP0rQPLBzTFZ+eGLvN9s7u79deLJvmmqte9pouWV17rl9tOb+fvtGZt7NxrivWRTnVxWgpdwXFaCuHI69d/w99RmHdb6XWPGZ7fueZykYh/hGh4wcLBCP/pUZH6kbrke1mvWvfIg== + ui-auth-token: AgAuV6cyAro248xsXITZZLmsPfYjk5jCSrCeEDDjzMVXnnuZEx67ig9G/SHuPyNRV3mms9/29jsQlTiLkmiduS8DGmZ1evSnhCIOcpVHeVFX1H5KYYyb6zA0xrkjBRetRccQPSTb6vjvqEFTq8nFwbFUJrBXrXvpM8BVXLFCYSuTqz8teu6eNfNKtkqvN/E4WzjsPT50RswPOliEtz4s44fhkHtreLvo1lBHTFBbicMTGlG3M+zvs+GOm4OOr4XhEQyPbAzpNRxdpiGfa4saan0AND1YfQFpvj5jYDAm8agGN8W74WRUW0kwd5RLDCbRCOY9LgD4xfzguN0UKQWfNX9Gieb1LyN7YrCD9kdzUJWA8M8cc+K8vG38cTW6GQYOVsoGxzLCtBxYfMl2aJ4fJM9fqcBhYnMgPgATSgN8Yee4qikmGw6/Rkap80b6ydP5U2hHWuq4/TGEg/39ovdpf5zTl9dwjEVHqgY8/jtRmjNnSJ/3kahAmc5sVcxaXYWEiuaiBLztVW4CPhYgfG3n10gShy0+HPv9RQfrbrxql+z32sX5+dMkxEw6ImE5WPv/jLimDiRIUnwI6iZBvESLASaraASoeQ9h8EBf7xPeyeJ8Cphxx37CC2zdE0939oq4hvIxahBod0aDDroV3O35mCNUaNhSu9CkrlbiPP/PUqlaUEeP8aQI6d1PHRTM/ttqlBQ+qLii+uXfldFwny0KDomPNHN/By6omlgWJR5/hOKhkHl/JPjo3Ex3SG+U+Q== template: metadata: creationTimestamp: null From 36460b5cac62fde606efa111433558f6f1f4f956 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 19 Mar 2026 20:28:42 +0100 Subject: [PATCH 005/113] kcprom --- infra/values/keycloak-values.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index 4e0e61f..1aa6e02 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -21,6 +21,12 @@ ingress: annotations: cert-manager.io/cluster-issuer: letsencrypt-prod +metrics: + enabled: true + prometheusRule: + namespace: monitoring + enabled: true + resources: requests: cpu: 250m From 3264f879b0a320c64272f8c91c97ef19500eaf9c Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 09:42:32 +0100 Subject: [PATCH 006/113] fortedigital/forte-helm --- README.md | 4 ++-- apps/argo-mcp.yaml | 2 +- apps/mcp10x.yaml | 2 +- apps/mcpcoder.yaml | 2 +- apps/musicman.yaml | 2 +- docs/DEVELOPER-GUIDE.md | 8 ++++---- docs/GITOPS-ARCHITECTURE.md | 4 ++-- docs/REFERENCE.md | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 15af8a7..95a6a0e 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ This repository contains the complete GitOps configuration for our Kubernetes cl | Repository | Purpose | Who Edits | How Often | |------------|---------|-----------|-----------| | **[sturdy-adventure](https://github.com/fortedigital/sturdy-adventure.git)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often | -| **[forte-helm](https://github.com/snothub/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely | +| **[forte-helm](https://github.com/fortedigital/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely | | **[helm-values](git@github.com:fortedigital/helm-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes | ### GitOps Workflow @@ -473,7 +473,7 @@ Documentation lives in `docs/`. To update: - [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) ### Related Repositories -- [forte-helm](https://github.com/snothub/forte-helm) - Helm chart templates +- [forte-helm](https://github.com/fortedigital/forte-helm) - Helm chart templates - [helm-values](git@github.com:fortedigital/helm-values.git) - Application values --- diff --git a/apps/argo-mcp.yaml b/apps/argo-mcp.yaml index f9eac29..b0fffe4 100644 --- a/apps/argo-mcp.yaml +++ b/apps/argo-mcp.yaml @@ -16,7 +16,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/apps/mcp10x.yaml b/apps/mcp10x.yaml index 3ec00d5..60bb9b6 100644 --- a/apps/mcp10x.yaml +++ b/apps/mcp10x.yaml @@ -17,7 +17,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/apps/mcpcoder.yaml b/apps/mcpcoder.yaml index cb98beb..ee16292 100644 --- a/apps/mcpcoder.yaml +++ b/apps/mcpcoder.yaml @@ -17,7 +17,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/apps/musicman.yaml b/apps/musicman.yaml index 08578cc..05697c4 100644 --- a/apps/musicman.yaml +++ b/apps/musicman.yaml @@ -17,7 +17,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index 364d1dd..e4982b7 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -103,7 +103,7 @@ You'll need read/write access to these repositories: 3. **forte-helm** (Chart repo - read-only for most developers) ```bash - git clone https://github.com/snothub/forte-helm.git + git clone https://github.com/fortedigital/forte-helm.git cd forte-helm ``` @@ -134,7 +134,7 @@ cd ~/dev/k8s # Clone repositories git clone https://github.com/fortedigital/sturdy-adventure.git launchpad git clone git@github.com:fortedigital/helm-values.git helm-prod-values -git clone https://github.com/snothub/forte-helm.git forte-helm +git clone https://github.com/fortedigital/forte-helm.git forte-helm # Your folder structure: # ~/dev/k8s/ @@ -247,7 +247,7 @@ metadata: namespace: argocd spec: sources: - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp helm: valueFiles: @@ -411,7 +411,7 @@ spec: sources: # Source 1: Helm chart templates - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/docs/GITOPS-ARCHITECTURE.md b/docs/GITOPS-ARCHITECTURE.md index f126421..91a7f3e 100644 --- a/docs/GITOPS-ARCHITECTURE.md +++ b/docs/GITOPS-ARCHITECTURE.md @@ -188,7 +188,7 @@ sturdy-adventure/ --- ### 2. **Helm Charts Repository** -**Repository**: `https://github.com/snothub/forte-helm` +**Repository**: `https://github.com/fortedigital/forte-helm` **Purpose**: Reusable Helm chart templates for Forte applications **Location**: `C:\dev\k8s\forte-helm` @@ -343,7 +343,7 @@ Applications like `mcp10x` and `musicman` use multiple sources: ```yaml spec: sources: - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp # Helm chart templates helm: valueFiles: diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index c556c88..5c23d05 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -177,7 +177,7 @@ spec: ### Helm Charts Repository: `forte-helm` -**URL**: `https://github.com/snothub/forte-helm` +**URL**: `https://github.com/fortedigital/forte-helm` #### Chart: `forteapp` @@ -513,7 +513,7 @@ spec: # Multi-source configuration sources: - - repoURL: https://github.com/snothub/forte-helm + - repoURL: https://github.com/fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: From 29e644510c0349dd7eb8379a3744146e35164a95 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 10:57:18 +0100 Subject: [PATCH 007/113] traefik tracing --- infra/traefik-application.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index 5369608..51efd71 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -28,6 +28,9 @@ spec: helm: values: | + tracing: + otlp: + enabled: true providers: kubernetesIngress: publishedService: # Fixes ArgoCD health checks for LoadBalancer services From d50b790082427ba058240a60838cee726aa3a8e8 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 11:08:04 +0100 Subject: [PATCH 008/113] otel --- infra/traefik-application.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index 51efd71..fc794a7 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -28,9 +28,18 @@ spec: helm: values: | + metrics: + addInternals: true + otlp: + enabled: true + http: + enabled: true tracing: otlp: - enabled: true + enabled: true + http: + enabled: true + providers: kubernetesIngress: publishedService: # Fixes ArgoCD health checks for LoadBalancer services From ec4082de93499519da2c81eaffaae8c90b892747 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 11:09:42 +0100 Subject: [PATCH 009/113] otel --- infra/traefik-application.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index fc794a7..1243f1a 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -32,13 +32,9 @@ spec: addInternals: true otlp: enabled: true - http: - enabled: true tracing: otlp: enabled: true - http: - enabled: true providers: kubernetesIngress: From 8b1931fa9d76ee2b1d4d01c8a4d27970db6d2929 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 11:12:48 +0100 Subject: [PATCH 010/113] traefik access logging --- infra/traefik-application.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index 1243f1a..010a447 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -35,6 +35,15 @@ spec: tracing: otlp: enabled: true + logs: + general: + level: DEBUG + oltp: + enabled: true + access: + enabled: true + oltp: + enabled: true providers: kubernetesIngress: From 016e70a998f79e4e945f855b4d5d4fb0ac608e02 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 12:59:13 +0100 Subject: [PATCH 011/113] argocd repo secret --- docs/OPERATIONS-RUNBOOK.md | 87 +++++--------------- secrets/argocd-forte-helm-secret-sealed.yaml | 20 +++++ 2 files changed, 41 insertions(+), 66 deletions(-) create mode 100644 secrets/argocd-forte-helm-secret-sealed.yaml diff --git a/docs/OPERATIONS-RUNBOOK.md b/docs/OPERATIONS-RUNBOOK.md index 0425b0b..83eaaca 100644 --- a/docs/OPERATIONS-RUNBOOK.md +++ b/docs/OPERATIONS-RUNBOOK.md @@ -169,78 +169,33 @@ This creates two files: Add the private key to ArgoCD as a repository secret: +Save the following file in private/ (gitignored) folder as secret.yaml ```bash -# Create secret for sturdy-adventure repository -kubectl create secret generic repo-sturdy-adventure \ - --from-file=sshPrivateKey=argocd-deploy-key \ - --namespace=argocd \ - --dry-run=client -o yaml | kubectl apply -f - - -# Label it for ArgoCD to recognize -kubectl label secret repo-sturdy-adventure \ - -n argocd \ - argocd.argoproj.io/secret-type=repository - -# Add repository annotations -kubectl annotate secret repo-sturdy-adventure \ - -n argocd \ - managed-by=argocd.argoproj.io + apiVersion: v1 + kind: Secret + metadata: + name: forte-helm-repo + namespace: argocd + labels: + argocd.argoproj.io/secret-type: repository + stringData: + type: git + url: git@github.com:fortedigital/forte-helm.git + sshPrivateKey: | + + project: default ``` - -Alternatively, create a complete repository secret with all metadata: - +Seal the secret using `kubeseal` command ```bash -kubectl apply -f - < secrets/forte-helm-repo-secret-sealed.yaml ``` **Step 4: Register Repository in ArgoCD** -Add the repository to ArgoCD's configuration: - -```bash -# Via kubectl (recommended for GitOps) -kubectl apply -f - < Date: Fri, 20 Mar 2026 13:02:48 +0100 Subject: [PATCH 012/113] repo url fix --- apps/argo-mcp.yaml | 2 +- apps/mcp10x.yaml | 2 +- apps/mcpcoder.yaml | 2 +- apps/musicman.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/argo-mcp.yaml b/apps/argo-mcp.yaml index b0fffe4..ca12650 100644 --- a/apps/argo-mcp.yaml +++ b/apps/argo-mcp.yaml @@ -16,7 +16,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/fortedigital/forte-helm + - repoURL: git@github.com:fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/apps/mcp10x.yaml b/apps/mcp10x.yaml index 60bb9b6..e487f85 100644 --- a/apps/mcp10x.yaml +++ b/apps/mcp10x.yaml @@ -17,7 +17,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/fortedigital/forte-helm + - repoURL: git@github.com:fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/apps/mcpcoder.yaml b/apps/mcpcoder.yaml index ee16292..c8b92bc 100644 --- a/apps/mcpcoder.yaml +++ b/apps/mcpcoder.yaml @@ -17,7 +17,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/fortedigital/forte-helm + - repoURL: git@github.com:fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: diff --git a/apps/musicman.yaml b/apps/musicman.yaml index 05697c4..da6377d 100644 --- a/apps/musicman.yaml +++ b/apps/musicman.yaml @@ -17,7 +17,7 @@ metadata: spec: project: default sources: - - repoURL: https://github.com/fortedigital/forte-helm + - repoURL: git@github.com:fortedigital/forte-helm path: forteapp targetRevision: HEAD helm: From b665faaa7b717104b679e4287beebf4a70a7187d Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 13:06:11 +0100 Subject: [PATCH 013/113] sidecar image ref --- .../policies/auth-sidecar-injector.yaml | 6 +++--- docs/DEVELOPER-GUIDE.md | 2 +- docs/REFERENCE.md | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index a680ccc..6b41047 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -124,7 +124,7 @@ spec: spec: containers: - name: authn - image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" ports: - containerPort: 8080 name: auth @@ -202,7 +202,7 @@ spec: spec: containers: - name: authn - image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - containerPort: 8080 @@ -292,7 +292,7 @@ spec: spec: containers: - name: authn - image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - containerPort: 8080 diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index e4982b7..685aa99 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -1025,7 +1025,7 @@ policies.forteapps.io/auth-upstream-url: "http://localhost:3000" #### Sidecar Configuration The auth sidecar container: -- **Image**: `ghcr.io/snothub/stunning-memory:latest` +- **Image**: `ghcr.io/fortedigital/auth-sidecar:latest` - **Port**: 8080 - **Resources**: 10m CPU / 32Mi memory (requests), 50m CPU / 64Mi memory (limits) - **Health checks**: `/healthz` endpoint diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 5c23d05..61091b6 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -853,7 +853,7 @@ policies.forteapps.io/auth-token-secret-name: "auth-tokens" policies.forteapps.io/auth-upstream-url: "http://localhost:3000" # Optional customization -policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory" +policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar" policies.forteapps.io/auth-image-version: "latest" ``` @@ -869,7 +869,7 @@ policies.forteapps.io/auth-oidc-client-id: "myapp" policies.forteapps.io/auth-oidc-callback-path: "/auth/callback" policies.forteapps.io/auth-oidc-scopes: "openid,profile,email" policies.forteapps.io/auth-upstream-url: "http://localhost:3000" -policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory" +policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar" policies.forteapps.io/auth-image-version: "latest" ``` @@ -885,7 +885,7 @@ policies.forteapps.io/auth-mcp-authority: "https://auth.example.com" policies.forteapps.io/auth-mcp-scopes: "read,write" policies.forteapps.io/auth-upstream-url: "http://localhost:3000" policies.forteapps.io/auth-log-level: "info" -policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory" +policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar" policies.forteapps.io/auth-image-version: "latest" ``` @@ -894,7 +894,7 @@ policies.forteapps.io/auth-image-version: "latest" **Token Mode**: ```yaml name: authn -image: ghcr.io/snothub/stunning-memory:latest +image: ghcr.io/fortedigital/auth-sidecar:latest ports: - containerPort: 8080 name: auth @@ -929,7 +929,7 @@ securityContext: **OIDC Mode**: ```yaml name: authn -image: ghcr.io/snothub/stunning-memory:latest +image: ghcr.io/fortedigital/auth-sidecar:latest ports: - containerPort: 8080 name: auth @@ -976,7 +976,7 @@ securityContext: **MCP Mode**: ```yaml name: authn -image: ghcr.io/snothub/stunning-memory:latest +image: ghcr.io/fortedigital/auth-sidecar:latest ports: - containerPort: 8080 name: auth From 3c81fd1e3a75940b3398456feea3fa9415a0a920 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 13:11:38 +0100 Subject: [PATCH 014/113] cleanup --- .../snothub-repo-credentials-sealed.yaml | 20 ------------------- docs/GITOPS-ARCHITECTURE.md | 4 +--- docs/REFERENCE.md | 1 - infra/values/argocd-values.yaml | 4 ---- 4 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 cluster-resources/snothub-repo-credentials-sealed.yaml diff --git a/cluster-resources/snothub-repo-credentials-sealed.yaml b/cluster-resources/snothub-repo-credentials-sealed.yaml deleted file mode 100644 index aba4b5f..0000000 --- a/cluster-resources/snothub-repo-credentials-sealed.yaml +++ /dev/null @@ -1,20 +0,0 @@ ---- -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - creationTimestamp: null - name: snothub-repo-creds - namespace: argocd -spec: - encryptedData: - sshPrivateKey: AgCBd+i8jXBDwvWM0YC9OWvNTKyynVpW6hF0F3aH0GXBIYxzFo1g9kMajE/Ce3bTl8DiVs7VGzPdI6lmJtSC3+fVMm4wavDGrUUbhUHSR/HnEqq51NVjxU1Uj+VRz550z9r6gB0VLAcYqN0WLKd/4Qn7tvQOmKsaXMd5jsIYpSB8nZK87awZ5niFmT8DIKu2DOzoDBeN7Yqrf1aELq3m3kaDxGcDMYSglB0xRU3fF8FYr5inCic1eTrCUdfIwnrBSublNxI9FrGnlylRC40XCPxNpy6zyoQt7yeJTNgRSvtfceqXAwh25mcvga+BfDBTLPQ0EDbCoNDnE9EyMUa4kWcXBTFZ1Qx5NGRz8HAjWoYDbuNOonl9ik5dvv9UKKA+/04ZEuPjsbdkBq9xKuowx0zLL1IVv/jeuViFdvNY6LKNu/hLwMn8aMlTLlIkB+1fsIqV7Vkva9Vk7IXNz575lMwIGUTo1dsK9FQ5+uIe2bsRnY8RJ3lpyndZQ1HDPh7P7KZLZPH8fUuAHH1UF74njMQyve79zCRcisBAewpXdq4UsYAYUQOluS1Ak+sFcIdQ0jHjfklGCcJnTvMyO7obIsPQSv39/bsCqQX6uisrTzcb0s7wnbzhcxf1gm7IyZMIhi2Vub3GoLCIMnb6ViO8k+itLUa5eZpoEeg+BpP1mgL7O2nVfrZYgueULgMSvN89ct/THITsFAR8614An3DCHSnZLv5ZmY7yC6rmO405IlrnjvfFqEt9MdqTgR7uTYDSdI7UjIFJp3rchzikF1pSDMu/siKmD/Vi9+S0KqBcSENz7EUppVuYIk0aRBqHZ9Awoe0qPIjp1AMg947FjIYXzkGk3Kz0P85fGwkktyfHNZdIrXvpDAIP2739Mr7Hde+EHpuajrhuBgozfless5PqLFfGWFnt5COW2HrdSrvrY56MXuJUfiV0nq3eEhCju9f/cA41VbxfO0Hj+KMCjxiL+MCgIt1eWD24P7GQHb0D7+JiuwqgcH0ZqXRaKFYSOJ3/U1o9RRX9v5yebNTm8ErQTSnIN5bNRgE+t8CrTulGVcpaL/rW+XW8cO8MXLC/R4eeNwVjoiK4yXSHpSjf5dF9MZ6bS6SSZPMOa2besEzzQIj0c/EkUsp/GmIF1utDbJVI2VZLFGklwRo92OJs3URXNCqbzzgte43Y9dJp2VdWyi3Zx32pXVWaNdOzeifVOASP7ags7Kbdfvoaa5CmTu17iRUHCOv6164/KFeDCfY= - type: AgCIKe+SzLHIp+6gjOVbD7wcpZkeg5UwgXabFjtonavkPbzX+txHWN7IH2HbDmd7TdpgGFiqMGFSQC8mOVXnj+Pw1XnI8trH6wavvKjQ1SsRWteB8o7lGe2PcG2h2v5yW2vk5rrmuB0ehogeJez3ynlk508HguRtidzxnKdZyc6SHOh2hbWq1clspJicREsHlz3Rfn7upOSUyFmx+Tilfnjuom5FFYGBNZt0aEjaW5S7fjcYJBTEerjGjTz3vUs2DK3C5ymyCKKasA628fVZ20uIZhmn5NUGGK6bKDusFOQhYjRnxRYwS2fToBHiVfC8wo7bWW8ZwOrbguvedJU2q9pwjKvXy9upw8Ra3EcYzXASqwI6rqUQv6htGqzTYtVkRsdVxaQqITc3FYrhKIroru4Iup4xYtcVojppz0+HQxiv5WtxosVtfXXX4Zj4spr9ThoZgzLe3ebILEIK3wJNmK/AxV80NXq7JOI6gaiLK/fbm7gd9G7oW9VuM50OJELDmR4jU2k1KSx6sD5lB+c+Ajp1iw+iS9ETD/je93+eyBRKGM9fqP0+DpNsJBBlVLuWK/BwidBN186pFi4DlMzo13Wd0zYKQce4NVrf5s4f7T01KeBU+nnvvcuI+rW7tsY3R18zgP9D9uX2E5066qJQdrFov2YuXwovS4hCQDttIfz9YoGUKKQ3PObWUVynhtBptoGbhRw= - url: AgCjVnAWNarZplxbAurz++eBiKXIuYUcoleDcsrMdwdflBOOf6ayguCr7CDHzUbuKFnlNKoFvIvjUOqFb1v5Yy4iWju3ajvlo+ncEIezxetYOmQSft3nSeOD3+RCZn4Qf4K2C1D113IyPCo7T2h01KuVLWOItfuWuVwFDpLTvmKfTRAs8eP1L4ohvAcY0/J5mAmi7tXV2hN1r9R2MEuvHOusujtrRqewXHhzBksSb9/wxilruU/BHQAhYKeKHzi7QoIOeXJTnGEYxuTvskwKQhschIOIBPAOLaUbgKkuHuDIf8y1Gv2b8ENu5uNvTb/ZD43jtmx9P4pS+Hwc7OW411TrkRO7XbV7qo/PqYGYpkKYDK4g7ONGnzrsmXbSmip34vXll/jAknY61QQ6D6JbbONw/psX72p+ZFvOedhlKbHRuUnDYXyQgKFWBODLb63RaMYai79qbv8mJcwDLJaPXYXpwLumHeZX91uPPjahxfvOe8VoryTpvHIbxJO85VJJ1q+7uyFy6h7LVLhifYSbb+M55p/e5Ds5gNcgUv6npPUHdbf0yjYbT1tXGjhaqk4Tx93WoOKPQ/j+nPB41akooY0YfL/ZTDzr2iCMByrx/uPQz3JE+m7VrH5BZyYjj9sSASVsULabJGFiSuGpD+u/lcUNJW+WHQetMEU7+wNo/WB3E5iG9J8qPSPFcTiTslEa8cvGPNtNgyCQ2PphlinscQsvcAQ+ALtEd1dWXPhIGbF8udK6Wx6NYt8= - template: - metadata: - creationTimestamp: null - labels: - argocd.argoproj.io/secret-type: repository - name: snothub-repo-creds - namespace: argocd - type: Opaque diff --git a/docs/GITOPS-ARCHITECTURE.md b/docs/GITOPS-ARCHITECTURE.md index 91a7f3e..1b81a88 100644 --- a/docs/GITOPS-ARCHITECTURE.md +++ b/docs/GITOPS-ARCHITECTURE.md @@ -53,7 +53,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where ┌────────────────────────────────┐ │ Config Repository │ │ (ArgoCD Applications) │ - │ github.com/snothub/ │ + │ github.com/fortedigital/ │ │ sturdy-adventure │ └────────────────────────────────┘ │ @@ -150,7 +150,6 @@ sturdy-adventure/ │ ├── letsencrypt-issuer.yaml # Let's Encrypt ClusterIssuer │ ├── kyverno-config.yaml │ ├── argocd-notifications-secret-sealed.yaml -│ ├── snothub-repo-credentials-sealed.yaml │ ├── forte10x-repo-credentials-sealed.yaml │ ├── mcp10x-repo-credentials-sealed.yaml │ └── policies/ # Kyverno policies @@ -489,7 +488,6 @@ git commit -m "Add app credentials" **Private Repository Credentials** stored as SealedSecrets: ```yaml -# cluster-resources/snothub-repo-credentials-sealed.yaml # cluster-resources/forte10x-repo-credentials-sealed.yaml ``` diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 61091b6..243dd09 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -104,7 +104,6 @@ sturdy-adventure/ │ ├── letsencrypt-issuer.yaml │ ├── kyverno-config.yaml │ ├── argocd-notifications-secret-sealed.yaml -│ ├── snothub-repo-credentials-sealed.yaml │ ├── forte10x-repo-credentials-sealed.yaml │ ├── mcp10x-repo-credentials-sealed.yaml │ └── policies/ diff --git a/infra/values/argocd-values.yaml b/infra/values/argocd-values.yaml index fe5976e..135b9fa 100644 --- a/infra/values/argocd-values.yaml +++ b/infra/values/argocd-values.yaml @@ -8,10 +8,6 @@ configs: application.resourceTrackingMethod: annotation timeout.reconciliation: 60s admin.enabled: "true" - repositories: | - - type: git - url: https://github.com/snothub - name: github-repo params: "server.insecure": true server: From 2ecd0c8a445edfa7b07ba5c759c0fac71053303e Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 13:32:06 +0100 Subject: [PATCH 015/113] traefik metrics --- infra/traefik-application.yaml | 9 +++++---- infra/values/fluent-bit-values.yaml | 3 ++- infra/values/loki-values.yaml | 4 ++-- infra/values/prometheus-values.yaml | 19 +++++++++++++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index 010a447..b2e08c1 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -38,12 +38,13 @@ spec: logs: general: level: DEBUG - oltp: + otlp: enabled: true - access: + access: + format: json + enabled: true + otlp: enabled: true - oltp: - enabled: true providers: kubernetesIngress: diff --git a/infra/values/fluent-bit-values.yaml b/infra/values/fluent-bit-values.yaml index 0d00154..ee70788 100644 --- a/infra/values/fluent-bit-values.yaml +++ b/infra/values/fluent-bit-values.yaml @@ -48,7 +48,8 @@ config: Match kube.* Host loki-gateway.monitoring.svc.cluster.local Port 80 - Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name'] + Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name'], stream=$stream + Auto_Kubernetes_Labels Off Line_Format json [OUTPUT] diff --git a/infra/values/loki-values.yaml b/infra/values/loki-values.yaml index 648d00c..a235593 100644 --- a/infra/values/loki-values.yaml +++ b/infra/values/loki-values.yaml @@ -20,8 +20,8 @@ loki: limits_config: reject_old_samples: true reject_old_samples_max_age: 168h - ingestion_rate_mb: 10 - ingestion_burst_size_mb: 20 + ingestion_rate_mb: 15 + ingestion_burst_size_mb: 30 max_line_size: 512KB chunksCache: enabled: false diff --git a/infra/values/prometheus-values.yaml b/infra/values/prometheus-values.yaml index 6b1307e..bc5a0bd 100644 --- a/infra/values/prometheus-values.yaml +++ b/infra/values/prometheus-values.yaml @@ -56,5 +56,24 @@ extraScrapeConfigs: | - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance] target_label: instance + - job_name: traefik + scrape_interval: 15s + metrics_path: /metrics + kubernetes_sd_configs: + - role: endpoints + namespaces: + names: + - traefik-system + relabel_configs: + - source_labels: [__meta_kubernetes_endpoint_port_name] + regex: metrics + action: keep + - source_labels: [__meta_kubernetes_service_name] + target_label: service + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + alertmanager: enabled: false From e4f8f2c0717310f970f61477e6be3d90d0ad725b Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 13:46:13 +0100 Subject: [PATCH 016/113] traefik grafana dash --- infra/values/grafana-values.yaml | 1163 ++++++++++++++++++++++++++++++ 1 file changed, 1163 insertions(+) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index a83bff9..d7042f6 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -978,6 +978,1169 @@ dashboards: "version": 7, "weekStart": "" } + traefik-loki: + json: | + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Loki version 2 showcase using JSON Traefik access logs.", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 13713, + "graphTooltip": 0, + "id": 12, + "links": [], + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 32, + "interval": "", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n \n Traefik Dashboard\n
", + "mode": "html" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "loki_build_info", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 2 + }, + "id": 4, + "interval": "1m", + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "code", + "expr": "sum(count_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" [$__interval]))", + "legendFormat": "", + "queryType": "range", + "refId": "A" + } + ], + "timeFrom": "1h", + "title": "Total requests ", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-blue" + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 6, + "y": 2 + }, + "id": 5, + "interval": "30s", + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "code", + "expr": "sum by (OriginStatus) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))", + "legendFormat": "HTTP Status: {{OriginStatus}}", + "queryType": "range", + "refId": "A" + } + ], + "timeFrom": "3h", + "title": "Requests per status code", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 15, + "y": 2 + }, + "id": 19, + "interval": "5m", + "maxDataPoints": 1, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": " sum(rate({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval])) / (sum(rate({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | __error__=\"\"[$__interval])) / 100)", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": "1h", + "title": "% of 4/5xx ", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsNull", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "hidden" + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 19, + "y": 2 + }, + "id": 36, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": " sum by (OriginStatus,ServiceName) (count_over_time({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval]))", + "legendFormat": " {{ServiceName}} / {{OriginStatus}} ", + "refId": "A" + } + ], + "timeFrom": "3h", + "title": " 4/5xx Services", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple" + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 6 + }, + "id": 22, + "interval": "5m", + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "count(sum by (ClientHost) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval])))", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": "5m", + "title": "Users right now", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple" + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 8, + "x": 15, + "y": 6 + }, + "id": 8, + "interval": "1m", + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "sum_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | OriginStatus=200 | unwrap DownstreamContentSize | __error__=\"\" [$__interval])", + "legendFormat": "Bytes sent", + "refId": "A" + } + ], + "timeFrom": "5m", + "title": "Total Bytes Sent", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "sum" + ] + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "Total": "Bytes Sent" + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 33, + "interval": "5m", + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "7.3.4", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "sum by (RouterName) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))", + "legendFormat": "{{RouterName}}", + "refId": "A" + } + ], + "timeFrom": "1h", + "title": "Requests Route", + "transparent": true, + "type": "piechart" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 11, + "interval": "", + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\"| json | line_format \"Status:{{.OriginStatus}} Client From {{.ClientAddr}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} Route To {{.ServiceAddr}}\"", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": "5m", + "title": "Logs", + "transparent": true, + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 90, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent" + }, + { + "color": "red", + "value": 0.3 + } + ] + }, + "unit": "ns" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "95th percentile" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 16, + "interval": "30s", + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "quantile_over_time(0.95,{namespace=\"traefik-system\"} |= \"RequestProtocol\"| json | unwrap Duration | __error__=\"\" [$__interval]) by (ServiceName)", + "hide": false, + "legendFormat": " {{ ServiceName }}", + "refId": "C" + } + ], + "title": "95th percentile of Request Time", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 90, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent" + }, + { + "color": "red", + "value": 0.3 + } + ] + }, + "unit": "ns" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "95th percentile" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 18 + }, + "id": 34, + "interval": "30s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "max by (ServiceName) (max_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" |json | unwrap Duration | __error__=\"\" [$__interval]))", + "hide": false, + "legendFormat": "{{ ServiceName}}", + "refId": "D" + } + ], + "title": "Max Age of Request Time", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 90, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent" + }, + { + "color": "red", + "value": 0.3 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "95th percentile" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 18 + }, + "id": 35, + "interval": "30s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "sum by (ServiceName) (sum_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" |json | unwrap RequestContentSize | __error__=\"\" [$__interval]))", + "hide": false, + "legendFormat": "{{ ServiceName}}", + "refId": "D" + } + ], + "title": "Requests Size", + "transparent": true, + "type": "timeseries" + } + ], + "refresh": false, + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Traefik Via Loki", + "uid": "fJarCeaGk", + "version": 2, + "weekStart": "" + } dot-ai-logs: json: | { From afb39f99a72395893a91b003ae775982c1be1173 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 14:04:20 +0100 Subject: [PATCH 017/113] Grafana Tempo --- infra/tempo.yaml | 42 ++++++++++++++++++++++++++++++++ infra/traefik-application.yaml | 3 +++ infra/values/grafana-values.yaml | 21 ++++++++++++++++ infra/values/tempo-values.yaml | 25 +++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 infra/tempo.yaml create mode 100644 infra/values/tempo-values.yaml diff --git a/infra/tempo.yaml b/infra/tempo.yaml new file mode 100644 index 0000000..5b47059 --- /dev/null +++ b/infra/tempo.yaml @@ -0,0 +1,42 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: tempo + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" + labels: + app.kubernetes.io/name: tempo + app.kubernetes.io/part-of: monitoring + app.kubernetes.io/managed-by: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + sources: + - repoURL: https://grafana.github.io/helm-charts + chart: tempo + targetRevision: "1.24.4" + helm: + releaseName: tempo + valueFiles: + - $values/infra/values/tempo-values.yaml + + - repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + ref: values + + destination: + server: https://kubernetes.default.svc + namespace: monitoring + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - Validate=true + - ServerSideApply=true diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index b2e08c1..00fb839 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -35,6 +35,9 @@ spec: tracing: otlp: enabled: true + grpc: + endpoint: "tempo.monitoring.svc.cluster.local:4317" + insecure: true logs: general: level: DEBUG diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index d7042f6..7a91c60 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -27,6 +27,27 @@ datasources: uid: loki url: http://loki-gateway.monitoring.svc.cluster.local access: proxy + - name: Tempo + type: tempo + uid: tempo + url: http://tempo.monitoring.svc.cluster.local:3200 + access: proxy + jsonData: + tracesToLogsV2: + datasourceUid: loki + tags: + - key: namespace + - key: pod + - key: container + tracesToMetrics: + datasourceUid: Prometheus + tags: + - key: service.name + value: service + nodeGraph: + enabled: true + serviceMap: + datasourceUid: Prometheus dashboardProviders: dashboardproviders.yaml: apiVersion: 1 diff --git a/infra/values/tempo-values.yaml b/infra/values/tempo-values.yaml new file mode 100644 index 0000000..c32fbc5 --- /dev/null +++ b/infra/values/tempo-values.yaml @@ -0,0 +1,25 @@ +tempo: + storage: + trace: + backend: local + local: + path: /var/tempo/traces + receivers: + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:4318" + +persistence: + enabled: true + size: 10Gi + +resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 200m + memory: 512Mi From 7522b88cfb980ff7f991e5d397ac672d0c38dff7 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 14:19:55 +0100 Subject: [PATCH 018/113] fix tempo --- infra/traefik-application.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index 00fb839..9532d14 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -35,9 +35,6 @@ spec: tracing: otlp: enabled: true - grpc: - endpoint: "tempo.monitoring.svc.cluster.local:4317" - insecure: true logs: general: level: DEBUG @@ -49,6 +46,10 @@ spec: otlp: enabled: true + env: + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: "http://tempo.monitoring.svc.cluster.local:4318" + providers: kubernetesIngress: publishedService: # Fixes ArgoCD health checks for LoadBalancer services From f728f9dbd34033ecd1148d73c788860a54501233 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 14:22:14 +0100 Subject: [PATCH 019/113] Tempo doc --- README.md | 9 ++++-- docs/GITOPS-ARCHITECTURE.md | 11 +++++-- docs/OPERATIONS-RUNBOOK.md | 27 ++++++++++++++++++ docs/REFERENCE.md | 57 +++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 95a6a0e..0d48f54 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,10 @@ This repository contains the complete GitOps configuration for our Kubernetes cl ### What's Inside -- **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Sealed Secrets +- **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Sealed Secrets - **Business Applications**: MCP10X, MusicMan, Dot-AI Stack, ArgoCD MCP - **Policies**: Kyverno security policies for secret management, namespace controls, pod verification -- **Monitoring**: Full observability stack with metrics, logs, and alerting +- **Monitoring**: Full observability stack with metrics, logs, traces, and alerting - **Secrets**: Sealed Secrets for secure Git storage ### Key Features @@ -72,7 +72,7 @@ This repository contains the complete GitOps configuration for our Kubernetes cl ✅ **Policy Enforcement**: Kyverno ensures security and compliance ✅ **Authentication**: Automatic sidecar injection (token & OIDC support) ✅ **TLS Everywhere**: Automatic Let's Encrypt certificates -✅ **Full Observability**: Prometheus, Grafana, Loki integration +✅ **Full Observability**: Prometheus, Grafana, Loki, Tempo integration --- @@ -91,6 +91,7 @@ This repository contains the complete GitOps configuration for our Kubernetes cl │ ├── prometheus.yaml │ ├── grafana.yaml │ ├── loki.yaml +│ ├── tempo.yaml │ ├── fluent-bit.yaml │ ├── trivy.yaml │ ├── sealedsecrets.yaml @@ -331,6 +332,7 @@ kubectl patch application myapp -n argocd \ | **Prometheus** | Metrics | `monitoring` | 1 | | **Grafana** | Dashboards | `monitoring` | 1 | | **Loki** | Logs | `monitoring` | 1 | +| **Tempo** | Distributed tracing | `monitoring` | 1 | | **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet | | **Trivy** | Vulnerability scanning | `trivy-system` | 1 | @@ -470,6 +472,7 @@ Documentation lives in `docs/`. To update: - [Kyverno Documentation](https://kyverno.io/docs/) - [Traefik Documentation](https://doc.traefik.io/traefik/) - [Cert-Manager Documentation](https://cert-manager.io/docs/) +- [Grafana Tempo Documentation](https://grafana.com/docs/tempo/) - [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) ### Related Repositories diff --git a/docs/GITOPS-ARCHITECTURE.md b/docs/GITOPS-ARCHITECTURE.md index 1b81a88..2fa1794 100644 --- a/docs/GITOPS-ARCHITECTURE.md +++ b/docs/GITOPS-ARCHITECTURE.md @@ -21,7 +21,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where - **Deployment Pattern**: App-of-Apps - **Secret Management**: Sealed Secrets (kubeseal) - **Ingress**: Traefik with Let's Encrypt TLS -- **Monitoring**: Prometheus + Grafana + Loki + Fluent-Bit +- **Monitoring**: Prometheus + Grafana + Loki + Tempo + Fluent-Bit - **Policy Engine**: Kyverno - **Notifications**: Slack integration for sync status @@ -83,6 +83,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where │ │ - Prometheus │ │ │ │ - Grafana │ │ │ │ - Loki │ │ + │ │ - Tempo │ │ │ │ - Fluent-Bit │ │ │ └──────────────────────────┘ │ │ │ @@ -127,6 +128,7 @@ sturdy-adventure/ │ ├── prometheus.yaml │ ├── grafana.yaml │ ├── loki.yaml +│ ├── tempo.yaml │ ├── fluent-bit.yaml │ ├── trivy.yaml │ ├── sealedsecrets.yaml @@ -136,6 +138,7 @@ sturdy-adventure/ │ ├── prometheus-values.yaml │ ├── grafana-values.yaml │ ├── loki-values.yaml +│ ├── tempo-values.yaml │ └── fluent-bit-values.yaml │ ├── apps/ # Business Application ArgoCD manifests @@ -301,6 +304,7 @@ _app-of-apps.yaml (Root) │ ├── kyverno │ ├── prometheus │ ├── grafana + │ ├── tempo │ └── ... (other infra apps) │ └── enterprise-apps (manages apps/) @@ -526,8 +530,9 @@ annotations: 1. **Prometheus**: Metrics collection and storage 2. **Grafana**: Metrics visualization and dashboards 3. **Loki**: Log aggregation -4. **Fluent-Bit**: Log shipping from pods to Loki -5. **Trivy**: Container vulnerability scanning +4. **Tempo**: Distributed tracing (OTLP) +5. **Fluent-Bit**: Log shipping from pods to Loki +6. **Trivy**: Container vulnerability scanning ### Slack Notifications diff --git a/docs/OPERATIONS-RUNBOOK.md b/docs/OPERATIONS-RUNBOOK.md index 83eaaca..aa29eff 100644 --- a/docs/OPERATIONS-RUNBOOK.md +++ b/docs/OPERATIONS-RUNBOOK.md @@ -954,6 +954,33 @@ curl -G -s 'http://localhost:3100/loki/api/v1/query_range' \ --data-urlencode 'start=1h' | jq ``` +### Tempo Traces + +```bash +# Port forward to Tempo query API +kubectl port-forward -n monitoring svc/tempo 3200:3200 + +# Access: http://localhost:3200 +``` + +**Query traces via Grafana:** +1. Open Grafana → Explore +2. Select Tempo datasource +3. Use TraceQL or search by service name + +**Verify Traefik is sending traces:** +```bash +# Check Traefik logs for OTLP export errors +kubectl logs -n traefik-system -l app.kubernetes.io/name=traefik | grep -i "traces export" + +# Check Tempo is receiving data +kubectl logs -n monitoring -l app.kubernetes.io/name=tempo | grep "receiver" +``` + +**Trace-to-log correlation:** +- Click a trace span in Grafana → linked Loki logs appear (by namespace, pod, container) +- Trace-to-metrics links to Prometheus by service name + ### Fluent-Bit Log Shipping Verify Fluent-Bit is shipping logs: diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 243dd09..14fdee8 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -29,6 +29,7 @@ | **Secret Management** | Sealed Secrets (Bitnami) | | **Monitoring** | Prometheus + Grafana | | **Logging** | Loki + Fluent-Bit | +| **Tracing** | Tempo (OTLP) | | **Container Scanning** | Trivy | ### Network Architecture @@ -81,6 +82,7 @@ sturdy-adventure/ │ ├── prometheus.yaml │ ├── grafana.yaml │ ├── loki.yaml +│ ├── tempo.yaml │ ├── fluent-bit.yaml │ ├── trivy.yaml │ ├── sealedsecrets.yaml @@ -90,6 +92,7 @@ sturdy-adventure/ │ ├── prometheus-values.yaml │ ├── grafana-values.yaml │ ├── loki-values.yaml +│ ├── tempo-values.yaml │ └── fluent-bit-values.yaml │ ├── apps/ # Business applications @@ -703,6 +706,7 @@ kubeStateMetrics: **Datasources**: - Prometheus - Loki +- Tempo ### Loki @@ -720,6 +724,45 @@ promtail: enabled: false # Using Fluent-Bit instead ``` +### Tempo + +**Chart**: `grafana/tempo` +**Version**: 1.24.4 +**Namespace**: `monitoring` + +**Purpose**: Distributed tracing backend receiving OTLP traces from Traefik and other instrumented services. + +**Configuration**: +```yaml +tempo: + storage: + trace: + backend: local + local: + path: /var/tempo/traces + receivers: + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:4318" + +persistence: + enabled: true + size: 10Gi +``` + +**Endpoints**: +- gRPC OTLP receiver: `:4317` +- HTTP OTLP receiver: `:4318` +- Query API: `:3200` + +**Grafana Integration**: +- Trace-to-logs correlation with Loki (by namespace, pod, container) +- Trace-to-metrics correlation with Prometheus (by service name) +- Service graph and node graph visualization + ### Fluent-Bit **Chart**: `fluent/fluent-bit` @@ -1184,6 +1227,19 @@ GET /api/v1/query_range?query={promql}&start={time}&end={time}&step={duration} GET /api/v1/label/__name__/values ``` +### Tempo API + +``` +# Search traces +GET /api/search?q={traceql} + +# Get trace by ID +GET /api/traces/{traceID} + +# Service tag values +GET /api/v2/search/tag/resource.service.name/values +``` + ### Loki API ``` @@ -1315,6 +1371,7 @@ team: platform | **Prometheus** | 2.47.0+ | Latest | | **Grafana** | 10.0.0+ | Latest | | **Loki** | 2.9.0+ | Latest | +| **Tempo** | 2.6.0+ | 1.24.4 | | **Fluent-Bit** | 2.1.0+ | Latest | | **PostgreSQL** | 16-alpine | N/A | | **Trivy** | Latest | Latest | From d394dfd55e58f7a6aef125d010c0b61dc5db0a78 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 14:23:46 +0100 Subject: [PATCH 020/113] host fix --- infra/traefik-application.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index 9532d14..703b5a4 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -46,9 +46,8 @@ spec: otlp: enabled: true - env: - - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://tempo.monitoring.svc.cluster.local:4318" + additionalArguments: + - "--tracing.otlp.http.endpoint=http://tempo.monitoring.svc.cluster.local:4318/v1/traces" providers: kubernetesIngress: From 7aa69f6a7fe2893e3a01e59f00d54ea5be2b1610 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 14:29:32 +0100 Subject: [PATCH 021/113] cleanup --- infra/traefik-application.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index 703b5a4..f239ce2 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -30,21 +30,15 @@ spec: values: | metrics: addInternals: true - otlp: - enabled: true tracing: otlp: enabled: true logs: general: level: DEBUG - otlp: - enabled: true access: format: json enabled: true - otlp: - enabled: true additionalArguments: - "--tracing.otlp.http.endpoint=http://tempo.monitoring.svc.cluster.local:4318/v1/traces" From b4ffae5078ab3de18588143bc3aaa0474c909971 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 14:31:52 +0100 Subject: [PATCH 022/113] service graph --- infra/values/prometheus-values.yaml | 1 + infra/values/tempo-values.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/infra/values/prometheus-values.yaml b/infra/values/prometheus-values.yaml index bc5a0bd..8a65223 100644 --- a/infra/values/prometheus-values.yaml +++ b/infra/values/prometheus-values.yaml @@ -13,6 +13,7 @@ server: memory: 1Gi enableLifecycle: true + enableRemoteWriteReceiver: true extraScrapeConfigs: | - job_name: kyverno diff --git a/infra/values/tempo-values.yaml b/infra/values/tempo-values.yaml index c32fbc5..aee124a 100644 --- a/infra/values/tempo-values.yaml +++ b/infra/values/tempo-values.yaml @@ -1,4 +1,7 @@ tempo: + metricsGenerator: + enabled: true + remoteWriteUrl: "http://prometheus-server.monitoring.svc.cluster.local/api/v1/write" storage: trace: backend: local From 2f88b2d16c8e77e9b4a5621467e09579ca92438d Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 20 Mar 2026 14:41:10 +0100 Subject: [PATCH 023/113] svc graph fix --- infra/values/prometheus-values.yaml | 3 ++- infra/values/tempo-values.yaml | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/infra/values/prometheus-values.yaml b/infra/values/prometheus-values.yaml index 8a65223..da797e5 100644 --- a/infra/values/prometheus-values.yaml +++ b/infra/values/prometheus-values.yaml @@ -13,7 +13,8 @@ server: memory: 1Gi enableLifecycle: true - enableRemoteWriteReceiver: true + extraFlags: + - web.enable-remote-write-receiver extraScrapeConfigs: | - job_name: kyverno diff --git a/infra/values/tempo-values.yaml b/infra/values/tempo-values.yaml index aee124a..9952fce 100644 --- a/infra/values/tempo-values.yaml +++ b/infra/values/tempo-values.yaml @@ -2,6 +2,12 @@ tempo: metricsGenerator: enabled: true remoteWriteUrl: "http://prometheus-server.monitoring.svc.cluster.local/api/v1/write" + overrides: + defaults: + metrics_generator: + processors: + - service-graphs + - span-metrics storage: trace: backend: local From 258ece5f85743f56a5878f96fd68b9caeb3fef0c Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Sat, 21 Mar 2026 23:32:32 +0100 Subject: [PATCH 024/113] dot ai secrets --- secrets/dot-ai-secrets-sealed.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/secrets/dot-ai-secrets-sealed.yaml b/secrets/dot-ai-secrets-sealed.yaml index 3b4df09..3728d71 100644 --- a/secrets/dot-ai-secrets-sealed.yaml +++ b/secrets/dot-ai-secrets-sealed.yaml @@ -7,10 +7,10 @@ metadata: namespace: dot-ai spec: encryptedData: - anthropic-api-key: AgAkI0wvqHmk3lQBXDuHOnyoK9kuIO6ArNrmlYdJaAOjV/pWlUDbWHWUkfe1tCAEb4B5qoIFZCYY0ZUVe+AVM2O0zk62qW9VLYtIAN5YnOyUJdRZBs2ctAzIQD9nmjR1fkGO4UYUqfTnoPuhbVwqiYn5mYVIxqP95JBRlo6rFI66qXsmcgyAGSw8WCJfQMvE4S65BFUdu58iYjoYmTGtL1WxTALV/2aXglfBQu+bJzM0U+ckaOYI0xMhtBwO16pGR16xuGQW3KGUYV5QH2Gs/qljigZGaN1VLSjr8t9WqseGx/pqIvQbljy30xsqfOCm0ZyK8I4c85gBP1W3Jjo7G001v91emCZACatxGaZSaEzNvxWXh+spN/I5vxHnMkQ5S/PVDu61fO8z+UTnVHe97PCx9DzhKdwAaYU/RvuGlNHG8pvn+IrlLp0S7mob3cuKfJzxvYCOjZx8kbBNHkCZnoxMkVPIlrnWtSW8+QcnSoXKyXdiucLuQUfq6mBiAZ4VeTeFFChhVRJixEogoottfEJQfDq82xUO86OP7AL4bNTkb98zBHGYE2YdEYrds8CMKmRYCGdYN3vVxcAwfkE/QC3MHN5BD/D6a4YyzZllUtIORxB0Ke2gqc2ITMkHcDcDauyKmVZtO93jAQMqyhqd/RQIm3lFdAAzrlMb4QkAwvOf4Js/ABbQAhADpW/dXw6bNfiK9uqluhsokyqlxPK7Yiw1oSmgbDCjXhMkrEB//3xL2A8Hl0AGwwf+DZPZKgsIFokEwc+D0XzrRYQXVbaVb/AkuUZrCgtgzlO/WmM58eLeESjgiaiPHnIbeOzj+R2iH6BsxPLwJqdqlnIENpw= - auth-token: AgAiqOYKxMmx4q9TYoFiz0kV7USMPQ7f4idBSlJmfUnZlm1XKI9RgkDmQCeK2L9v/NwVs5TEQ2hjvMv5KarowGOeqd8y8qdM0GLhfzHX9oN1ybN80BkwQJqYy4LgIMNXiMLUkjagAHAf5DEChBOUheOulsAZTFY+n1usKk++GfO8HqDf9mDfYkHmi7QIXTuoVyYbmkmR3GEEypDUbX77mRKKCwqZ/IEgE2mm4niUcrY0duReuv0mq46SRYhMneylbb/fXrKmGu1epZW2S4YiVrRgntYjF1X/e61BS41D/8OjybUPgVGssKB1lY+9OzQvCRc42GVxJPymSHMONhMrnumLUeWSJEVJCy+ODYZ+/KDwVXxDHsu+pMhclyHRcRwkpQtzBRCehTpCx3elzfd9UA2GevDDoQeZ9AlD4cMM93GD8i+ZxadV1WvwPjE7BNqAiiM/f0XkYxDTj4wRNqPMibb76kh5l6NOVFjta44FXiVf67jxMXKGAL1wiRiuTE6GhrN0i2tzPd9GpVV3r/PsT5x+gpcNk1n4+XpWVvTvcsSsZY0ljMZ4KbSOPq/jUrQVhErrglo2EadWmjnoZ+VyC/tFDIe9hLq85CXmRxNYwwtK2JBuhf0UguT7iRj964Pnkh8FK7KEGYEpfsmGSF8H7z+yPRadISYbvseLGkzAbyrHOUFOtS73iSrwbQLXBg9qcX+4s9bwfkL88efxXJN/IrFqaWz5v5eYFh/5XffKzeTpJhir7fPi6spkxIGkUA== - openai-api-key: AgAtobul09GMkKLRKkOP6B0tPjU7EhUn+SpOz+JMMZMg8EdHOwWCFqfp2f12CPG7+ScnwD/2iZ97tNGv045jswwGaMxcs7R518/3mGjcBWu73K9q3RyW3r2sslgUJbUpUP7h4EUVjucYdP3MyTDWyTi/zQDyHG5jwBkeF/Lbb6eMWPRvJxhzUlhLgkCWJOQNuYKqliitMeryLfGn0Qkmwk3r/deI65WK9LXbVuyFp+Qh/FSOibPnEd5lxvWGAWu8lEDbWRdVQZoGI0Q05uO+bo92Sg46p0rRcsgvQutMR9u/Dm2hVbLT8pMk52LzWWdvtMr2UTIZoOb/W52s4sgQ00MHWx77zdkWbVfmCRZKQM53Ryitw94MlQp37Dv7VJTVtIf2zC8qlBSZ887vH0iq1bI03zb+uC5UjYarJkVtE/aMd8qDAaqCBl6W8ocsf9tpc8du9NJj4uSq10XGnL4wz8aoRCuth1/uJrI8jOVQQHdzzUs1+Ui16HX/ZtXxwgC1QD0q/iFdrQJ9f6T2n/boZ+jKvXILPZ7F9iMRFDm6P55esHeGezg1/1f3gO/X/OrmBQliPw70VgOFoGOGHM+toa+cgwYpDR3Ir6jTC0od8gIBGwLy0xZrIv48TWv3m5XZV60daWTorqrMK+WEzG8KGIG6NCEzltPKF5+SL9wLmY2KzFL9fPibePKyiBc2t18cosW2BhiM29UlGqsGKZjd+AkMLqTBAWf3gTSpX/FSlNCwSaU9RQ9DmoilbyCEhgJ+LIzyaK2H6ZqsiYvbv0W/QHXP0rQPLBzTFZ+eGLvN9s7u79deLJvmmqte9pouWV17rl9tOb+fvtGZt7NxrivWRTnVxWgpdwXFaCuHI69d/w99RmHdb6XWPGZ7fueZykYh/hGh4wcLBCP/pUZH6kbrke1mvWvfIg== - ui-auth-token: AgAuV6cyAro248xsXITZZLmsPfYjk5jCSrCeEDDjzMVXnnuZEx67ig9G/SHuPyNRV3mms9/29jsQlTiLkmiduS8DGmZ1evSnhCIOcpVHeVFX1H5KYYyb6zA0xrkjBRetRccQPSTb6vjvqEFTq8nFwbFUJrBXrXvpM8BVXLFCYSuTqz8teu6eNfNKtkqvN/E4WzjsPT50RswPOliEtz4s44fhkHtreLvo1lBHTFBbicMTGlG3M+zvs+GOm4OOr4XhEQyPbAzpNRxdpiGfa4saan0AND1YfQFpvj5jYDAm8agGN8W74WRUW0kwd5RLDCbRCOY9LgD4xfzguN0UKQWfNX9Gieb1LyN7YrCD9kdzUJWA8M8cc+K8vG38cTW6GQYOVsoGxzLCtBxYfMl2aJ4fJM9fqcBhYnMgPgATSgN8Yee4qikmGw6/Rkap80b6ydP5U2hHWuq4/TGEg/39ovdpf5zTl9dwjEVHqgY8/jtRmjNnSJ/3kahAmc5sVcxaXYWEiuaiBLztVW4CPhYgfG3n10gShy0+HPv9RQfrbrxql+z32sX5+dMkxEw6ImE5WPv/jLimDiRIUnwI6iZBvESLASaraASoeQ9h8EBf7xPeyeJ8Cphxx37CC2zdE0939oq4hvIxahBod0aDDroV3O35mCNUaNhSu9CkrlbiPP/PUqlaUEeP8aQI6d1PHRTM/ttqlBQ+qLii+uXfldFwny0KDomPNHN/By6omlgWJR5/hOKhkHl/JPjo3Ex3SG+U+Q== + anthropic-api-key: AgChQtSYNH1g3kI1w00X2MWGDBVB7W/UAggTkjWxPYBRqHkxoCgGITCWQAk1m8v8r6Ckg6UAV9EbC7yzvDZx+lLvEfCKv2IB7md+yxW0JbIoJa8mQHrshERnbut5M9NB3J/6gG+iB8QCjb47r5SRSvsqH38vkXAhYwZB7dUD77sLj6vIt4bm/IRsocCqGORwDVbFGFKNzAcrrIHlxmljDj+phE29kM0x/Aq8fo+xrxN8DQ4WyNvSHFJaezoAfJ0a+fSyQ42dbp/LlwfN+Xh+R5SA5+Y42KPG0i9Hozvhqv+grkiGbteuKGh8mHE7L0jsfS9kNOO6Ag/9B2OqIz71Te7/vCC/UUuP1rK0IdlobPtBQfFplz2lQpBO/9QPVm3GFJioeiv56qWXKg+u3yYLcE/3Pqpd0iqpngsRXzrFLr2yZssTcloEgDGMxixAUSwbEkYf1aCrPLv3sjptRy7sAcsmgFLVhtNp0AnGur47myzbmmcjY2yAYaB2QMxxa2rwWjN2lsAMWQA+Xp3U7v3u4OBa/SN20UB60K0nmHvbTWq7+4VEBJ9FqD+051GXk02nk2jhZDPJx8vQRWEBS3FCWnBrgKl8RJuPawhVJWhhC+xuV+TW7olbJXwJQ8wrhxsSh7NXSib8EHfe2yuUJB0AX8jXzHcexJGa0UaW9D9x8C/9VQrKiGJKdHc1PdykJDOcaVxgCjNFHBOYnkQ1Jfv+FjMxuA61litOssnjVyZ+GFbZD8GgN+EZkiCn9h5MdGzOV3y2Txv7XocFHBUMezTeL3xkgv56LWv6fLcmB6zbqxCnBvew1VCsyjaiM966dFs7DWujRjyis7u/IEQaxVY= + auth-token: AgCTd2O7yYp145PXa7flMMTupvbz+UtBrYhLFTjRRW6iK/zj4LBdPUTXF0V678QcGMjb34lGHToXM8ginbMskBPAW2Iqo3mZ61z2RQMIE02gQTCirBU7b5MwIWi+OzrMboYdnqd2Rb9fapXBX0n+dnlk5Nc0P11tEazh0kVG8yDeeHg/ogpYn0/KmQH7hweUY/IK3w5WKRtRNxKti7ToSROt/156r6qSjoxgB3cuAS2uP8jTs31uBUbReuHIRKotA0ipiRsHBh4phykIF2iERluakRIu1gAl05Xkuku9bdhpHBrdIxnqI04cLirToIyCPGJPFO+o3ZZeeh3oL9T7fpgVY8YsV9n9vmFLt5uCyvR0CFBhHjC6SfkJbUOOFyEchOuv5W1q0TqoP9Q+fDfJxLLI6JS1QbxF8v3ebe4B3C8G/GWF1LpsngG1hgzyyipUNbWlsfeFGzqxcVM0lfFvrVE+bqc10wgO6miQBM4Aryv0KCUfspKvzwphD3RsJVK5lLwnSqlYXn4sbd2u4SCaratMFfMd4xjghg/h+WeT9vQLfm/B9SgI/5nhA1n4VabC4N9wHqUe51S33/EwZ5IHrMXJXXX+RXmROfHhGFc+AAxjP6a7JCG7Qo+WQ5uISHtjryfHwkLNYHWhi5AP0NAUKH74KT5abs/chPTfVJAROUYVJiCUCaezocpmaSBIlApKwISffhpi0UNXnIAhOk/IuNH8Uv1O6eapfwYWQKCn9vFJXdICUVCELlB92wXd2Q== + openai-api-key: AgAIUFytcKh+1X/1ownK4513Q5dkCYonZDghDg+YBMuBadvdo6nqAJPav3uhojKRqWdoTa4u/oT52woekXKncB35+5O/vrRN0OQ7l+td78CbFx66uv68oT6hCC5kHgtTtaEP0e+g1kofLXTpImM1h7FGqD4YZtXNnPK2BgcT/+viZPsS9PP2jB3dkvGkWhsW1JugJacIp4gw7cCmZv32ISkXfiOnYGLOpoQCH3vcVMKTq1QGAqKaGRuVY6YxH/Kjz6bd4a8+Fr3di7pXgMiLFuUop5nQmZMqt6sQdWLCoY2G8zpfO3u4bcIG33nMZYykCkeO1QUGPwEhWYVLUyQdPmET64lO8rNGgScpm46wKXfFH7fsgWpuN3HkMAfSs89WIepLaqyi6/3psbh0Xn7+yn0+7UY/E0fBJhN3KQ478mKoMPGZDzKJFIs8GidzmoXDUFo/O9wrywVMKgXbenfewvHOVRHKQyazN/Tc393n7jmNsS+OAF76miq9CqegGF69JuH14p8Kt/4LcPLRdQXVnBBHmprUnLFwQeMFOYbh3SA0Zpb90rSlB+TLYew/5Dtfev9Zgjn/3rXQyUU9LwNAKV16L7WRoXHj4ZwGvOyLlQI8W6nMkHB4c/Moqt1noqokb01LD1qQPcFLuWwMfDQdNTsN72sRlAyMDKck6p+s2RBXV6H0yW2fOC/+3zr+xw5OvePb0ttiNh39xJCYQ5lBaq1ezPwtRdQU0xDYq9D0pnUn7pXbjNo6zQb2bZItIB2v6plu3MeVPFto5aZbM5b2KlbYH2tzJPn0wq5MMMghitnEaL2T+AnAbXYkCq09/EpY7HEtULpLZdAE8BEoo40JGkS3/9zQvv9FB5pFdwt0iCKbk1ebrJH02qsUq5ky83I1D2XAFx3XpbT/2xmkHXP5E23I90GojA== + ui-auth-token: AgBHWnZM0pf5FKfAmkEdjKbX5mOkioSPdUdxRNCGcX7qf0PXfoY00vwp85ttIb2Kg8R18nwHK2ViMt3xlxZ9kf93Z6nBE0NMpW/9dv/MvhJVxhmHtlrVumTHv6IjmE2rBlcvseAzTD+R7236W9D/ZLD3TACY2vGfBdt35IaaQQ/8Lh7AP6t0yyJaSXFDhQ1lAF7om+WrNNakX+m8RSdkfErpF95kyIPJu1ZumlCVYDTmf4bqLSGJFMCL8C86Z/GD9arZfHIZHZ8avvJ09h990pTWsH50O/sMN74olsP0R7cPpCKAPYA2ErfgySm9/W2acRDlBFY7+77T03kAyO9LjVO+6OO9LrINnpOS3ZFMeb1Ks7mT4ML7JMtBE6kv0NXhJd9p/3xQCCf6+nluJ6uX56MJDA7/XgFJAKLYs3h+EacESt5qBOZc8UjZURWuiYneq/yUd2Nc7gvIpPVwi9oa3u3jArvKFRo43YCh5KNEUvJ9WXNmce9Rmi97pbk26ZzV9Cuyd5tk4P2ooRh5pQcXjxmyJOoSSREbWy+K8er2gb0Wo8YybA7sjkf0K0PeRJWw0jSgQUXqZpdXy1B03v2FsVQwe3ljMBhJ0r8YXmXzM8WpIJwDipT/lX4bajbEn89vTWyF6gyWQtJaKxm43QkeVpU3/Li4nuIPhnK/+ngpkD0/rrKW1kUN9aV9SdLsscFZlZpzBp94tkCG3ZgfogV0o6WRaX0kaquRJJ7qA+Q6dZtR6GoNLyVBXnh1HsYD3Q== template: metadata: creationTimestamp: null From 5dc12cfaa2077e1b08842667f38038a7c4f93479 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Sat, 21 Mar 2026 23:36:33 +0100 Subject: [PATCH 025/113] new api token --- secrets/dot-ai-secrets-sealed.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/secrets/dot-ai-secrets-sealed.yaml b/secrets/dot-ai-secrets-sealed.yaml index 3728d71..5dd9590 100644 --- a/secrets/dot-ai-secrets-sealed.yaml +++ b/secrets/dot-ai-secrets-sealed.yaml @@ -7,10 +7,10 @@ metadata: namespace: dot-ai spec: encryptedData: - anthropic-api-key: AgChQtSYNH1g3kI1w00X2MWGDBVB7W/UAggTkjWxPYBRqHkxoCgGITCWQAk1m8v8r6Ckg6UAV9EbC7yzvDZx+lLvEfCKv2IB7md+yxW0JbIoJa8mQHrshERnbut5M9NB3J/6gG+iB8QCjb47r5SRSvsqH38vkXAhYwZB7dUD77sLj6vIt4bm/IRsocCqGORwDVbFGFKNzAcrrIHlxmljDj+phE29kM0x/Aq8fo+xrxN8DQ4WyNvSHFJaezoAfJ0a+fSyQ42dbp/LlwfN+Xh+R5SA5+Y42KPG0i9Hozvhqv+grkiGbteuKGh8mHE7L0jsfS9kNOO6Ag/9B2OqIz71Te7/vCC/UUuP1rK0IdlobPtBQfFplz2lQpBO/9QPVm3GFJioeiv56qWXKg+u3yYLcE/3Pqpd0iqpngsRXzrFLr2yZssTcloEgDGMxixAUSwbEkYf1aCrPLv3sjptRy7sAcsmgFLVhtNp0AnGur47myzbmmcjY2yAYaB2QMxxa2rwWjN2lsAMWQA+Xp3U7v3u4OBa/SN20UB60K0nmHvbTWq7+4VEBJ9FqD+051GXk02nk2jhZDPJx8vQRWEBS3FCWnBrgKl8RJuPawhVJWhhC+xuV+TW7olbJXwJQ8wrhxsSh7NXSib8EHfe2yuUJB0AX8jXzHcexJGa0UaW9D9x8C/9VQrKiGJKdHc1PdykJDOcaVxgCjNFHBOYnkQ1Jfv+FjMxuA61litOssnjVyZ+GFbZD8GgN+EZkiCn9h5MdGzOV3y2Txv7XocFHBUMezTeL3xkgv56LWv6fLcmB6zbqxCnBvew1VCsyjaiM966dFs7DWujRjyis7u/IEQaxVY= - auth-token: AgCTd2O7yYp145PXa7flMMTupvbz+UtBrYhLFTjRRW6iK/zj4LBdPUTXF0V678QcGMjb34lGHToXM8ginbMskBPAW2Iqo3mZ61z2RQMIE02gQTCirBU7b5MwIWi+OzrMboYdnqd2Rb9fapXBX0n+dnlk5Nc0P11tEazh0kVG8yDeeHg/ogpYn0/KmQH7hweUY/IK3w5WKRtRNxKti7ToSROt/156r6qSjoxgB3cuAS2uP8jTs31uBUbReuHIRKotA0ipiRsHBh4phykIF2iERluakRIu1gAl05Xkuku9bdhpHBrdIxnqI04cLirToIyCPGJPFO+o3ZZeeh3oL9T7fpgVY8YsV9n9vmFLt5uCyvR0CFBhHjC6SfkJbUOOFyEchOuv5W1q0TqoP9Q+fDfJxLLI6JS1QbxF8v3ebe4B3C8G/GWF1LpsngG1hgzyyipUNbWlsfeFGzqxcVM0lfFvrVE+bqc10wgO6miQBM4Aryv0KCUfspKvzwphD3RsJVK5lLwnSqlYXn4sbd2u4SCaratMFfMd4xjghg/h+WeT9vQLfm/B9SgI/5nhA1n4VabC4N9wHqUe51S33/EwZ5IHrMXJXXX+RXmROfHhGFc+AAxjP6a7JCG7Qo+WQ5uISHtjryfHwkLNYHWhi5AP0NAUKH74KT5abs/chPTfVJAROUYVJiCUCaezocpmaSBIlApKwISffhpi0UNXnIAhOk/IuNH8Uv1O6eapfwYWQKCn9vFJXdICUVCELlB92wXd2Q== - openai-api-key: AgAIUFytcKh+1X/1ownK4513Q5dkCYonZDghDg+YBMuBadvdo6nqAJPav3uhojKRqWdoTa4u/oT52woekXKncB35+5O/vrRN0OQ7l+td78CbFx66uv68oT6hCC5kHgtTtaEP0e+g1kofLXTpImM1h7FGqD4YZtXNnPK2BgcT/+viZPsS9PP2jB3dkvGkWhsW1JugJacIp4gw7cCmZv32ISkXfiOnYGLOpoQCH3vcVMKTq1QGAqKaGRuVY6YxH/Kjz6bd4a8+Fr3di7pXgMiLFuUop5nQmZMqt6sQdWLCoY2G8zpfO3u4bcIG33nMZYykCkeO1QUGPwEhWYVLUyQdPmET64lO8rNGgScpm46wKXfFH7fsgWpuN3HkMAfSs89WIepLaqyi6/3psbh0Xn7+yn0+7UY/E0fBJhN3KQ478mKoMPGZDzKJFIs8GidzmoXDUFo/O9wrywVMKgXbenfewvHOVRHKQyazN/Tc393n7jmNsS+OAF76miq9CqegGF69JuH14p8Kt/4LcPLRdQXVnBBHmprUnLFwQeMFOYbh3SA0Zpb90rSlB+TLYew/5Dtfev9Zgjn/3rXQyUU9LwNAKV16L7WRoXHj4ZwGvOyLlQI8W6nMkHB4c/Moqt1noqokb01LD1qQPcFLuWwMfDQdNTsN72sRlAyMDKck6p+s2RBXV6H0yW2fOC/+3zr+xw5OvePb0ttiNh39xJCYQ5lBaq1ezPwtRdQU0xDYq9D0pnUn7pXbjNo6zQb2bZItIB2v6plu3MeVPFto5aZbM5b2KlbYH2tzJPn0wq5MMMghitnEaL2T+AnAbXYkCq09/EpY7HEtULpLZdAE8BEoo40JGkS3/9zQvv9FB5pFdwt0iCKbk1ebrJH02qsUq5ky83I1D2XAFx3XpbT/2xmkHXP5E23I90GojA== - ui-auth-token: AgBHWnZM0pf5FKfAmkEdjKbX5mOkioSPdUdxRNCGcX7qf0PXfoY00vwp85ttIb2Kg8R18nwHK2ViMt3xlxZ9kf93Z6nBE0NMpW/9dv/MvhJVxhmHtlrVumTHv6IjmE2rBlcvseAzTD+R7236W9D/ZLD3TACY2vGfBdt35IaaQQ/8Lh7AP6t0yyJaSXFDhQ1lAF7om+WrNNakX+m8RSdkfErpF95kyIPJu1ZumlCVYDTmf4bqLSGJFMCL8C86Z/GD9arZfHIZHZ8avvJ09h990pTWsH50O/sMN74olsP0R7cPpCKAPYA2ErfgySm9/W2acRDlBFY7+77T03kAyO9LjVO+6OO9LrINnpOS3ZFMeb1Ks7mT4ML7JMtBE6kv0NXhJd9p/3xQCCf6+nluJ6uX56MJDA7/XgFJAKLYs3h+EacESt5qBOZc8UjZURWuiYneq/yUd2Nc7gvIpPVwi9oa3u3jArvKFRo43YCh5KNEUvJ9WXNmce9Rmi97pbk26ZzV9Cuyd5tk4P2ooRh5pQcXjxmyJOoSSREbWy+K8er2gb0Wo8YybA7sjkf0K0PeRJWw0jSgQUXqZpdXy1B03v2FsVQwe3ljMBhJ0r8YXmXzM8WpIJwDipT/lX4bajbEn89vTWyF6gyWQtJaKxm43QkeVpU3/Li4nuIPhnK/+ngpkD0/rrKW1kUN9aV9SdLsscFZlZpzBp94tkCG3ZgfogV0o6WRaX0kaquRJJ7qA+Q6dZtR6GoNLyVBXnh1HsYD3Q== + anthropic-api-key: AgAdnPQzl2SNSqyAZkoBdEEGEjXfpMJUNMg4e040vxri4jbjoTnmwQ/DjmmIGtoInjhQA3NZRUKcIuDrO7nGMXFOPY9tSfhpdaAMWTji1CVZvmdJnrQwZY1LFBwgbozq3RSZZQQcZd2EPrhhsU5UlmacKk2WpuQdq76twfdU5WVD8xalxbt0H5UDvihuQa3sYX/rUIDZK2Th5IfaFqYZk5JvaxR8PFwBh9IOooGewWXWrsxVT510lxHRotSDIgKt/jl8ymsVA7IpddnbQHr6K77it7BxR6fLvDGaTQeQW0RtPZwsLvB268OTwp338RQ2sjmVuUDErWViLuk1V6UFmj5vhRR0X2dy/EipkWEJwR0Zf8JCW+vAXJ4NFiVFjmOsiTXoLHCYp0k01EwFcPQpnswKSdaQ3tuCEa4Q+rHu2DiXyfSuZ1MnWnTyLSx9H51VYUgIPwQa+BUVoVnAswypw4yLQHrfLu9TSvsNl/OonP0dSmgSGaByTc1RzwpvHBUAaOYII2LuuvkF90oA4cChGkGe1KuYKHFqiBdaT9jrsfeBUpgqFAH0uH9DNjBjU5EjlUtV3bdO7wXDHct9EOBZniKMH7cIbXfT2TBaormeHNqFtXxSRXy1szDpXiDD1QDoAJx0ylrwwqix7XPBlqSt91OZ8N1VPSUPvfnTQf7zirVWJNckU9fW6tsnQ+GDOOEwERPEAhqxWuw6V0HMab4VzpbGgWuvtQY5CsI2bFUYWSZAIJ6H8E5ip3lb8ilWtYadhx8QZD3aKi7k5h4bFWd6M0rg0uj7bfbW8YXauTS6Uz4b9FlvgrAPkmcWG84um/jsmP6a0YhvJDGKHMHoUmY= + auth-token: AgBq/MM/+fqi1H6L5o/xFeKYMfcn6+bmbkepbq97O0Ob+dFKWuyfXmzqM2Dha+NKpXcu47qSBbLpaOdLS5ONq7JDIcZ2byW0ae9khBuL7k2mCa5ZPh96LS46c+v1qNJb6F4NQe+jWSx2H9LIShoihom+lRueBT1/uthW3hnUUUgQMgXU3NYimDNAg6JK6VgX3yKkI7ePLPAet7+ykaL1aPXwcfCAldcobPmls0vxMQDtgd+o4nLqx2sKArFplnwZ9G1SEz2dRXNFm8HI4HhgtOBdv1ISjXO8XGRZWnFqgX3s3BqcwFcPqVuFGHo+2ZvAUW+mdkuAEJOloJEoZXJkFCP+l7/yMV7/FmFAhntqfdTwijEJ+rP/sUbUNSzn6BKM2ITRGXEJkOeNq4xfpgS16AOs+O0DWkIAr0kWCJG+mkn94U7ALCGYQXtKLy0g/87MJ3IwaUU7gowVawHoDmpY0QoAYDJwpbIa9yMnlPOzJANkc99dl85sbLeQMvkkb4RqQlbMc80gkFFhbU5/0kKX483ylqhRi0b4ZjGHgl72s/8+FhxTDkSXJQ2HbrNPGlna+B7TAgxYZ9IS1cpw4wSNzSCk23Rt3STHnXuu/QXRLeMijXGBDFNJgU0JNKLp63JC1gftHcNhqRL6lxK1tLyo5+0Cnh/KJN64b1DpPXZnzdaeR25GQFSDxsymjLm2BFATtufRPJqeIT7tn5TRGTr97fH6LQv/LuJ4VnjS6WLsQoFdUhDuNug2LDuyLHjpKg== + openai-api-key: AgCQR+HezEHwiwFD1pIkla1Byzz91SBoUyaWVTTuBrBDq201KvlLmqahjQwpKWs4YvqhmKkbF/mXKuMSyFut72IwH025tfmzzmTryOD6bOQDxI0Ws5NndnztzKv2qw2Js0c+6lAOJR+lEuo1ynGlG3hfS+bGShShvLZIDcD/hDa8IfhQBh7fKyFslJ52KxKK+zeEFDVQcx6Lrq/zP9IPH2QXz0bVQYlRyrQ4vqONO9h2pjxNAhDxuJ5FPk+Hb4ClGjyRPHFT34ZYsYdvHUgkjL3nOeaOUrc0GGJOKYZChfsK/JrqX8iOqgbE+0vQvMX+q8Tr6Ynr0KRKMcGPr6ak3eCNknJaZ1EABtkyUS2u5TzKqMzYq5DF4kpvPApIvaQW+VS05zD8piTQSZEFNRwwXDpyru/R4MmPvwt7OdSM0SJpQ8kR7tsuRgBPFY2Eduk6fhrDmMVgHze3AoRaqyh87CeqR3Z7/XofbdgZBqEp2vwiY425ArB6yMLpVvO6eB+yoTgCJ/UpfOxBDHE1M6U3goJhk/YeOy9UpTfRD1VyA2OuzJliDNtC784wxnGAsOk3qKk1bjjBqUzwITK41mOWqNLKCn4Ol9lXZtbbqLIiU4lXv4Pl93mVSuPLOIXINLGMRFl0rlvDICEjrrfE+JmwKx6i45sqsk3/hz1Fb+/bKFo7pl9JnAXvr5WfDW/rflwBrokfJVeYsSm6R2yxGWlosrKZIwIRctOut1EbbylurI5pIPp6vHguyIvZDDxwj3Qk3ewU/WVAcI9989aLR++UrZ5FGXt3apMk8BY9bq7DIQOUtgfPO98+cVPdoZSIh5bhQpCHAVPMGcGTdIPkuQbxSai0FsP8ipdHx0fluI6p/4v1Rv/BqZH0X8dnwPQoqUhSD+C575w511Fc8PEzgaG63S9hHx5Azw== + ui-auth-token: AgBgKRwErv5MtWIdqSN5BSQ/deHFE4469gzqL6gwp/3JidtBEiAAyuOIbc9dRCWuJNcdtixKOZT1rKuI8O/e6oJ6ABGAx+ckDfH468VKsR4AJhdZGcxub5j79NKccbNA5MKTnqxlU05zukUOkgw3wdsOyuQr3RnfL31GFbEVyDMej+Xr7Rs0fWrfqpasCi45/PEi8fkofMg45kGc+LMfDgovNtkKla2MAXwYlUk24SA+lgCFoG2DHB50GdTK2byCks4Px4J3jQqqHdLUfC+/gyqimPoVUsVGb5SY0xeNepKfTz7lHEkmDOB9R1wfv7pUaLMapc+eVrVN7reLHKOA47IEedDpnlLm/fyEfPe7ktkejc59tPyIyxBubrN+Vb/+6bDgi7xsXy/N5uD/2sPqWVgI2D7veWOJEjM+GYOYeb2iRbf7KlrokCXD/gRMiQBKqYyUdjWx40MjrhxzPArOa+cL0kb1CUgwuyjmqq5hNIUO2wo6/kkeNtUeT7r067LE5rtMoZG2EAfw/xrL1CI2F+3c72JzRd//aClt1MsSfPPPoFz0TlWSSc7qNDhYHuQf//G/Zd29Nk/Ac52HVwdkl2AXJE/GIFdwi+ypqK5mjmSUTn/JVE0ZaA3OpHOF/01meISuqBo+HuAWCV8sw6en0F8AF+DF8rw8t5hDdexScXep5QuJmm4CYgqThyjz2V5xME9oMmGA+Yiu/MJ4AlkLowijpXkKEWpEOBRpRRhu2q2szhZqmx1oKJw+qJ40CQ== template: metadata: creationTimestamp: null From 1983c80f15b8d88619a62c2dd851ec77cec98814 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Sat, 21 Mar 2026 23:55:33 +0100 Subject: [PATCH 026/113] opencost --- infra/opencost.yaml | 42 +++++++++++++++++++++++++++++++ infra/values/grafana-values.yaml | 4 +++ infra/values/opencost-values.yaml | 16 ++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 infra/opencost.yaml create mode 100644 infra/values/opencost-values.yaml diff --git a/infra/opencost.yaml b/infra/opencost.yaml new file mode 100644 index 0000000..5beeda5 --- /dev/null +++ b/infra/opencost.yaml @@ -0,0 +1,42 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: opencost + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" + labels: + app.kubernetes.io/name: opencost + app.kubernetes.io/part-of: monitoring + app.kubernetes.io/managed-by: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + sources: + - repoURL: https://opencost.github.io/opencost-helm-chart + chart: opencost + targetRevision: "1.42.0" + helm: + releaseName: opencost + valueFiles: + - $values/infra/values/opencost-values.yaml + + - repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + ref: values + + destination: + server: https://kubernetes.default.svc + namespace: monitoring + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - Validate=true + - ServerSideApply=true diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 7a91c60..839de03 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -2260,3 +2260,7 @@ dashboards: "title": "dot-ai Logs", "uid": "dot-ai-logs" } + opencost: + gnetId: 15714 + revision: 1 + datasource: Prometheus diff --git a/infra/values/opencost-values.yaml b/infra/values/opencost-values.yaml new file mode 100644 index 0000000..b427777 --- /dev/null +++ b/infra/values/opencost-values.yaml @@ -0,0 +1,16 @@ +opencost: + exporter: + defaultClusterId: launchpad + extraEnv: + EMIT_KSM_V1_METRICS: "false" + EMIT_KSM_V1_METRICS_ONLY: "true" + prometheus: + internal: + enabled: true + serviceName: prometheus-server + namespaceName: monitoring + port: 80 + customPricing: + enabled: true + ui: + enabled: true From c6bc723b8ad0925f7d8de860e61a153f650ee234 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Sun, 22 Mar 2026 00:17:12 +0100 Subject: [PATCH 027/113] opencost scrapes --- infra/values/prometheus-values.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/infra/values/prometheus-values.yaml b/infra/values/prometheus-values.yaml index da797e5..dfda28a 100644 --- a/infra/values/prometheus-values.yaml +++ b/infra/values/prometheus-values.yaml @@ -77,5 +77,28 @@ extraScrapeConfigs: | - source_labels: [__meta_kubernetes_namespace] target_label: namespace + - job_name: opencost + scrape_interval: 1m + scrape_timeout: 10s + metrics_path: /metrics + kubernetes_sd_configs: + - role: endpoints + namespaces: + names: + - monitoring + relabel_configs: + - source_labels: [__meta_kubernetes_service_name] + regex: opencost + action: keep + - source_labels: [__meta_kubernetes_endpoint_port_number] + regex: "9003" + action: keep + - source_labels: [__meta_kubernetes_service_name] + target_label: service + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + alertmanager: enabled: false From fd0e5781319252cd028f62665f003e4a531d3133 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Sun, 22 Mar 2026 15:51:11 +0100 Subject: [PATCH 028/113] opencost scraping --- infra/values/opencost-values.yaml | 4 ++++ infra/values/prometheus-values.yaml | 23 ----------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/infra/values/opencost-values.yaml b/infra/values/opencost-values.yaml index b427777..43ab24d 100644 --- a/infra/values/opencost-values.yaml +++ b/infra/values/opencost-values.yaml @@ -14,3 +14,7 @@ opencost: enabled: true ui: enabled: true +service: + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9003" diff --git a/infra/values/prometheus-values.yaml b/infra/values/prometheus-values.yaml index dfda28a..da797e5 100644 --- a/infra/values/prometheus-values.yaml +++ b/infra/values/prometheus-values.yaml @@ -77,28 +77,5 @@ extraScrapeConfigs: | - source_labels: [__meta_kubernetes_namespace] target_label: namespace - - job_name: opencost - scrape_interval: 1m - scrape_timeout: 10s - metrics_path: /metrics - kubernetes_sd_configs: - - role: endpoints - namespaces: - names: - - monitoring - relabel_configs: - - source_labels: [__meta_kubernetes_service_name] - regex: opencost - action: keep - - source_labels: [__meta_kubernetes_endpoint_port_number] - regex: "9003" - action: keep - - source_labels: [__meta_kubernetes_service_name] - target_label: service - - source_labels: [__meta_kubernetes_pod_name] - target_label: pod - - source_labels: [__meta_kubernetes_namespace] - target_label: namespace - alertmanager: enabled: false From d0ab490eb58994e4bc5878ec3d43955c79065eea Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Sun, 22 Mar 2026 16:01:04 +0100 Subject: [PATCH 029/113] datasource --- infra/values/grafana-values.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 839de03..c495e10 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -36,14 +36,14 @@ datasources: tracesToLogsV2: datasourceUid: loki tags: - - key: namespace - - key: pod - - key: container + - key: namespace + - key: pod + - key: container tracesToMetrics: datasourceUid: Prometheus tags: - - key: service.name - value: service + - key: service.name + value: service nodeGraph: enabled: true serviceMap: @@ -2263,4 +2263,4 @@ dashboards: opencost: gnetId: 15714 revision: 1 - datasource: Prometheus + datasource: ${datasource} From c2aa680a0f9040dc3a879916095a7c999601a084 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 09:23:16 +0100 Subject: [PATCH 030/113] opencost grafana json --- infra/values/grafana-values.yaml | 2102 +++++++++++++++++++++++++++++- 1 file changed, 2099 insertions(+), 3 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index c495e10..a963edf 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -2261,6 +2261,2102 @@ dashboards: "uid": "dot-ai-logs" } opencost: - gnetId: 15714 - revision: 1 - datasource: ${datasource} + json: | + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Cluster cost overview for Kubecost running Grafana Cloud's Managed Prometheus in the backend.", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 15714, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 27, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "Note: this dashboard requires Kubecost metrics to be available in your Prometheus deployment. [Learn more](https://github.com/kubecost/cost-model/blob/master/PROMETHEUS.md)", + "mode": "markdown" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly run rate of CPU + GPU costs based on currently provisioned resources.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 2 + }, + "hideTimeOverride": true, + "id": 2, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Monthly CPU Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly run rate of memory costs based on currently provisioned expenses.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 2 + }, + "hideTimeOverride": true, + "id": 3, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Monthly Memory Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly run rate of attached storage and PV costs based on currently provisioned resources.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "N/A": { + "index": 1, + "text": "1" + } + }, + "type": "value" + }, + { + "options": { + "match": "null", + "result": { + "index": 0, + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 2 + }, + "hideTimeOverride": true, + "id": 4, + "interval": "15", + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Monthly Storage Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Sum of compute, memory, storage and network costs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 2 + }, + "hideTimeOverride": true, + "id": 11, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Total Monthly Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Current CPU use from applications divided by allocatable CPUs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 5 + }, + "hideTimeOverride": true, + "id": 13, + "maxDataPoints": 100, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "(\n sum(\n count(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n * on (instance) \n sum(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n ) \n / \n (sum (kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}))\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "CPU Utilization", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Current CPU reservation requests from applications vs allocatable CPU", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 5 + }, + "id": 15, + "maxDataPoints": 100, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / SUM(kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "title": "CPU Requests", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Current RAM use vs RAM available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 5 + }, + "hideTimeOverride": true, + "id": 17, + "interval": "15", + "maxDataPoints": 100, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "SUM(container_memory_working_set_bytes{namespace!=\"\"}) / SUM(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"}) * 100", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + } + ], + "timeFrom": "", + "title": "RAM Utilization", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Current RAM requests vs RAM available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 5 + }, + "id": 19, + "maxDataPoints": 100, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"})\n /\n sum(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "title": "RAM Requests", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "This gauge shows the current standard storage use, including cluster storage, vs storage available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 5 + }, + "hideTimeOverride": true, + "id": 21, + "maxDataPoints": 100, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(pod_pvc_allocation) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) /\nsum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "Storage Utilization", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly run rate of CPU + GPU costs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 6, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.3.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "compute cost", + "refId": "A" + } + ], + "title": "Compute Cost", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly run rate of memory costs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 9 + }, + "id": 9, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.3.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "memory cost", + "refId": "A" + } + ], + "title": "Memory Cost", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly run rate of attached disk + PV storage costs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 9 + }, + "id": 10, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.3.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "storage cost", + "refId": "A" + } + ], + "title": "Storage Cost", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Sum of compute, memory, and storage costs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 9 + }, + "id": 22, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.3.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "total cost", + "refId": "A" + } + ], + "title": "Total Cost", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cost of by resource class of currently provisioned nodes", + "fieldConfig": { + "defaults": { + "custom": { + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "decimals": 2, + "displayName": "", + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.hidden", + "value": true + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "Compute Cost" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #A" + }, + "properties": [ + { + "id": "displayName", + "value": "CPU Cost" + }, + { + "id": "unit", + "value": "currencyUSD" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #B" + }, + "properties": [ + { + "id": "displayName", + "value": "Mem Cost" + }, + { + "id": "unit", + "value": "currencyUSD" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #C" + }, + "properties": [ + { + "id": "displayName", + "value": "Total" + }, + { + "id": "unit", + "value": "currencyUSD" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "instance" + }, + "properties": [ + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.hidden", + "value": true + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #D" + }, + "properties": [ + { + "id": "displayName", + "value": "GPU" + }, + { + "id": "unit", + "value": "short" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 8, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": false, + "expr": "# CPU \navg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100) +\n# GPU\navg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n# Memory\navg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n", + "format": "table", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "C" + } + ], + "title": "Monthly Cost by Node", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly run rate of attached disk + PV storage costs based on currently provisioned resources.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 25, + "interval": "1m", + "options": { + "displayMode": "lcd", + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "text": {}, + "valueMode": "color" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "cpu", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "memory", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\nsum(avg(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "storage", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "SUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $percentEgress * $egressCost ", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "network", + "refId": "D" + } + ], + "title": "Monthly Cost by Resource", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 29, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true, + "values": [] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(sum(container_memory_allocation_bytes) by (container,namespace, instance) * on(instance) group_left() (\n\t\t\t\tnode_ram_hourly_cost{} / 1024 / 1024 / 1024\n\t\t\t\t+ on(node,instance_type) group_left()\n\t\t\t\t\tlabel_replace\n\t\t\t\t\t(\n\t\t\t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t\t\t\t) * 0\n\t\t\t)\n + \n sum(container_cpu_allocation) by (container,namespace, instance) * on(instance) group_left() (\n\t \t\t\tnode_cpu_hourly_cost{} + on(node,instance_type) group_left()\n\t\t \t\t\tlabel_replace\n\t\t \t\t\t(\n\t\t \t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t \t\t\t) * 0\n\t\t \t)) by (namespace, container)", + "interval": "", + "legendFormat": "{{namespace}} / {{container}}", + "refId": "A" + } + ], + "title": "Hourly Cost by Container", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 25 + }, + "id": 31, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true, + "values": [] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(sum(container_memory_allocation_bytes) by (namespace,instance) * on(instance) group_left() (\n\t\t\t\tnode_ram_hourly_cost{} / 1024 / 1024 / 1024\n\t\t\t\t+ on(node,instance_type) group_left()\n\t\t\t\t\tlabel_replace\n\t\t\t\t\t(\n\t\t\t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t\t\t\t) * 0\n\t\t\t)\n + \n sum(container_cpu_allocation) by (namespace,instance) * on(instance) group_left() (\n\t \t\t\tnode_cpu_hourly_cost{} + on(node,instance_type) group_left()\n\t\t \t\t\tlabel_replace\n\t\t \t\t\t(\n\t\t \t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t \t\t\t) * 0\n\t\t \t)) by (namespace)", + "interval": "", + "legendFormat": "{{namespace}}", + "refId": "A" + } + ], + "title": "Hourly Cost by Namespace", + "type": "piechart" + } + ], + "refresh": false, + "schemaVersion": 39, + "tags": [ + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "auto": true, + "auto_count": 1, + "auto_min": "1m", + "current": { + "selected": false, + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + "hide": 2, + "name": "timeRange", + "options": [ + { + "selected": true, + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + }, + { + "selected": false, + "text": "90d", + "value": "90d" + } + ], + "query": "1h,6h,12h,1d,7d,14d,30d,90d", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + }, + { + "hide": 2, + "label": "Cost per Gb hour for attached disks", + "name": "localStorageGBCost", + "query": "${VAR_LOCALSTORAGEGBCOST}", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "selected": true, + "text": "0", + "value": "0" + }, + "hide": 0, + "label": "Sustained Use Discount %", + "name": "useDiscount", + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], + "query": "0", + "skipUrlSync": false, + "type": "textbox" + }, + { + "hide": 2, + "name": "percentEgress", + "query": "${VAR_PERCENTEGRESS}", + "skipUrlSync": false, + "type": "constant" + }, + { + "hide": 2, + "name": "egressCost", + "query": "${VAR_EGRESSCOST}", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_GRAFANACLOUD-PROM}" + }, + "definition": "query_result(sum(container_memory_working_set_bytes{namespace!=\"\"}) by (namespace))", + "hide": 0, + "includeAll": false, + "label": "ns", + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "query_result(sum(container_memory_working_set_bytes{namespace!=\"\"}) by (namespace))", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "/namespace=\\\"(.*?)(\\\")/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_GRAFANACLOUD-PROM}" + }, + "definition": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (pod))", + "hide": 0, + "includeAll": false, + "label": "pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (pod))", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "/pod=\\\"(.*?)(\\\")/", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_GRAFANACLOUD-PROM}" + }, + "definition": "cluster", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "cluster", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "/cluster=\\\"(.*?)(\\\")/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_GRAFANACLOUD-PROM}" + }, + "definition": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (container))", + "hide": 0, + "includeAll": false, + "label": "conatiner", + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (container))", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "/container=\\\"(.*?)(\\\")/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_GRAFANACLOUD-PROM}" + }, + "definition": "node", + "hide": 0, + "includeAll": false, + "label": "node", + "multi": false, + "name": "node", + "options": [], + "query": { + "query": "node", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Kubecost Dashboard for Grafana Cloud", + "uid": "JOUdHGZZz", + "version": 1, + "weekStart": "" + } From b1b75c77c5a5daa34d98ebc9d27163a5616aeaec Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 09:37:44 +0100 Subject: [PATCH 031/113] cost values --- infra/values/grafana-values.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index a963edf..2403b3c 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -4152,7 +4152,7 @@ dashboards: "hide": 2, "label": "Cost per Gb hour for attached disks", "name": "localStorageGBCost", - "query": "${VAR_LOCALSTORAGEGBCOST}", + "query": "0.04", "skipUrlSync": false, "type": "constant" }, @@ -4179,14 +4179,14 @@ dashboards: { "hide": 2, "name": "percentEgress", - "query": "${VAR_PERCENTEGRESS}", + "query": "0.1", "skipUrlSync": false, "type": "constant" }, { "hide": 2, "name": "egressCost", - "query": "${VAR_EGRESSCOST}", + "query": "0.12", "skipUrlSync": false, "type": "constant" }, @@ -4212,7 +4212,7 @@ dashboards: "current": {}, "datasource": { "type": "prometheus", - "uid": "${DS_GRAFANACLOUD-PROM}" + "uid": "${datasource}" }, "definition": "query_result(sum(container_memory_working_set_bytes{namespace!=\"\"}) by (namespace))", "hide": 0, @@ -4235,7 +4235,7 @@ dashboards: "current": {}, "datasource": { "type": "prometheus", - "uid": "${DS_GRAFANACLOUD-PROM}" + "uid": "${datasource}" }, "definition": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (pod))", "hide": 0, @@ -4258,7 +4258,7 @@ dashboards: "current": {}, "datasource": { "type": "prometheus", - "uid": "${DS_GRAFANACLOUD-PROM}" + "uid": "${datasource}" }, "definition": "cluster", "hide": 0, @@ -4280,7 +4280,7 @@ dashboards: "current": {}, "datasource": { "type": "prometheus", - "uid": "${DS_GRAFANACLOUD-PROM}" + "uid": "${datasource}" }, "definition": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (container))", "hide": 0, @@ -4303,7 +4303,7 @@ dashboards: "current": {}, "datasource": { "type": "prometheus", - "uid": "${DS_GRAFANACLOUD-PROM}" + "uid": "${datasource}" }, "definition": "node", "hide": 0, From f8ecc54b86b32c90164fdca9285906af091a9790 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 09:39:34 +0100 Subject: [PATCH 032/113] panel --- infra/values/grafana-values.yaml | 34 -------------------------------- 1 file changed, 34 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 2403b3c..6ae467a 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -2293,40 +2293,6 @@ dashboards: "links": [], "liveNow": false, "panels": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "gridPos": { - "h": 2, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 27, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "Note: this dashboard requires Kubecost metrics to be available in your Prometheus deployment. [Learn more](https://github.com/kubecost/cost-model/blob/master/PROMETHEUS.md)", - "mode": "markdown" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, { "datasource": { "type": "prometheus", From 8f71d159ff222ca341b3ad17ee19c46e62b0f7d3 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 10:36:02 +0100 Subject: [PATCH 033/113] currency and rates --- infra/values/grafana-values.yaml | 24 ++++++++++++------------ infra/values/opencost-values.yaml | 8 ++++++++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 6ae467a..72335d4 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -2329,7 +2329,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -2415,7 +2415,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -2511,7 +2511,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -2598,7 +2598,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -3167,7 +3167,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -3267,7 +3267,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -3367,7 +3367,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -3469,7 +3469,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, @@ -3605,7 +3605,7 @@ dashboards: }, { "id": "unit", - "value": "currencyUSD" + "value": "currencyEUR" }, { "id": "decimals", @@ -3628,7 +3628,7 @@ dashboards: }, { "id": "unit", - "value": "currencyUSD" + "value": "currencyEUR" }, { "id": "decimals", @@ -3651,7 +3651,7 @@ dashboards: }, { "id": "unit", - "value": "currencyUSD" + "value": "currencyEUR" }, { "id": "decimals", @@ -3824,7 +3824,7 @@ dashboards: } ] }, - "unit": "currencyUSD" + "unit": "currencyEUR" }, "overrides": [] }, diff --git a/infra/values/opencost-values.yaml b/infra/values/opencost-values.yaml index 43ab24d..e261cec 100644 --- a/infra/values/opencost-values.yaml +++ b/infra/values/opencost-values.yaml @@ -12,6 +12,14 @@ opencost: port: 80 customPricing: enabled: true + costModel: + CPU: "6.07" + RAM: "1.52" + GPU: "0" + storage: "0.03" + zoneNetworkEgress: "0" + regionNetworkEgress: "0" + internetNetworkEgress: "0" ui: enabled: true service: From 4266327d35a9f9ff134023e80d4d77651adc6144 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 10:41:21 +0100 Subject: [PATCH 034/113] rates fix --- infra/values/opencost-values.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/values/opencost-values.yaml b/infra/values/opencost-values.yaml index e261cec..73cd36a 100644 --- a/infra/values/opencost-values.yaml +++ b/infra/values/opencost-values.yaml @@ -13,10 +13,10 @@ opencost: customPricing: enabled: true costModel: - CPU: "6.07" - RAM: "1.52" + CPU: "0.00832" + RAM: "0.00208" GPU: "0" - storage: "0.03" + storage: "0.00004" zoneNetworkEgress: "0" regionNetworkEgress: "0" internetNetworkEgress: "0" From f50f03d8e044e3c19503a6aca5a8c7f220825fa6 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 10:44:46 +0100 Subject: [PATCH 035/113] typo --- infra/values/grafana-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 72335d4..2e95591 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -4251,7 +4251,7 @@ dashboards: "definition": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (container))", "hide": 0, "includeAll": false, - "label": "conatiner", + "label": "container", "multi": false, "name": "container", "options": [], From 76b39241c1fc08be90d1535f014c07d69ad65d32 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 10:50:44 +0100 Subject: [PATCH 036/113] oc yaml indenting --- infra/values/opencost-values.yaml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/infra/values/opencost-values.yaml b/infra/values/opencost-values.yaml index 73cd36a..f9332fb 100644 --- a/infra/values/opencost-values.yaml +++ b/infra/values/opencost-values.yaml @@ -10,16 +10,18 @@ opencost: serviceName: prometheus-server namespaceName: monitoring port: 80 - customPricing: - enabled: true - costModel: - CPU: "0.00832" - RAM: "0.00208" - GPU: "0" - storage: "0.00004" - zoneNetworkEgress: "0" - regionNetworkEgress: "0" - internetNetworkEgress: "0" + customPricing: + enabled: true + provider: custom + costModel: + description: "UpCloud 4-node cluster pricing" + CPU: "6.07" + RAM: "1.52" + GPU: "0" + storage: "0.03" + zoneNetworkEgress: "0" + regionNetworkEgress: "0" + internetNetworkEgress: "0" ui: enabled: true service: From 161dc52d4a88a476e3f214e89949c670941cc1d2 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 11:07:52 +0100 Subject: [PATCH 037/113] refresh --- infra/values/grafana-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 2e95591..bb4b16a 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -4042,7 +4042,7 @@ dashboards: "type": "piechart" } ], - "refresh": false, + "refresh": "30s", "schemaVersion": 39, "tags": [ "cost", From fca94cde94ee42298ba42d9e3e08490483befe9d Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 11:41:13 +0100 Subject: [PATCH 038/113] fix --- infra/values/grafana-values.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index bb4b16a..0815bc6 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -3884,8 +3884,9 @@ dashboards: "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\nsum(avg(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) +\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", "format": "time_series", + "instant": true, "intervalFactor": 1, "legendFormat": "storage", "refId": "C" From 7269eb3121c2f4cd0a77cc7ccbccc08fdf69ba13 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 11:42:25 +0100 Subject: [PATCH 039/113] title --- infra/values/grafana-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 0815bc6..1cb28be 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -4322,7 +4322,7 @@ dashboards: ] }, "timezone": "", - "title": "Kubecost Dashboard for Grafana Cloud", + "title": "Opencost Dashboard", "uid": "JOUdHGZZz", "version": 1, "weekStart": "" From bdfada0838310389a9e93da2220237cfbb04a20b Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 11:49:30 +0100 Subject: [PATCH 040/113] opencost disable ui --- infra/values/opencost-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/opencost-values.yaml b/infra/values/opencost-values.yaml index f9332fb..cee1465 100644 --- a/infra/values/opencost-values.yaml +++ b/infra/values/opencost-values.yaml @@ -23,7 +23,7 @@ opencost: regionNetworkEgress: "0" internetNetworkEgress: "0" ui: - enabled: true + enabled: false service: annotations: prometheus.io/scrape: "true" From 7a204e367c6a446c526ee8cab00ec9ffdb678e09 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 11:55:00 +0100 Subject: [PATCH 041/113] time ranges --- infra/values/grafana-values.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 1cb28be..0be4d0d 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -1126,7 +1126,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "1h", "title": "Total requests ", "transparent": true, "type": "stat" @@ -1190,7 +1189,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "3h", "title": "Requests per status code", "transparent": true, "type": "stat" @@ -1260,7 +1258,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "1h", "title": "% of 4/5xx ", "transparent": true, "type": "stat" @@ -1414,7 +1411,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "3h", "title": " 4/5xx Services", "transparent": true, "type": "timeseries" @@ -1475,7 +1471,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "5m", "title": "Users right now", "transparent": true, "type": "stat" @@ -1537,7 +1532,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "5m", "title": "Total Bytes Sent", "transformations": [ { @@ -1632,7 +1626,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "1h", "title": "Requests Route", "transparent": true, "type": "piechart" @@ -1671,7 +1664,6 @@ dashboards: "refId": "A" } ], - "timeFrom": "5m", "title": "Logs", "transparent": true, "type": "logs" From db8fb09fe1ee17bf123970ae23a71e6f0d6c1b06 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 12:10:31 +0100 Subject: [PATCH 042/113] sm --- infra/values/grafana-values.yaml | 1963 +++++++++++++++++++++++++++++- 1 file changed, 1960 insertions(+), 3 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 0be4d0d..d623c1c 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -63,9 +63,1966 @@ dashboardProviders: dashboards: default: kubernetes: - gnetId: 15758 - revision: 1 - datasource: Prometheus + json: | + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "This is a modern 'Namespaces View' dashboard for your Kubernetes cluster(s). Made for kube-prometheus-stack and take advantage of the latest Grafana features (8.x.x). GitHub repository: https://github.com/dotdc/grafana-dashboards-kubernetes", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 15758, + "graphTooltip": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "tempo", + "uid": "tempo" + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 39, + "options": { + "edges": {}, + "nodes": {} + }, + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "tempo" + }, + "limit": 20, + "queryType": "serviceMap", + "refId": "A", + "tableType": "traces" + } + ], + "title": "Service Graph", + "type": "nodeGraph" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 38, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "refId": "A" + } + ], + "title": "Resources", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 0, + "y": 11 + }, + "id": 17, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Running Pods", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 2, + "y": 11 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_service_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Services", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 4, + "y": 11 + }, + "id": 22, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_endpoint_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Endpoints", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 6, + "y": 11 + }, + "id": 26, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_ingress_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Ingress", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 8, + "y": 11 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_deployment_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Deployments", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 10, + "y": 11 + }, + "id": 16, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "expr": "sum(kube_statefulset_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Statefulsets", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 11 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_status_running{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Running Pods", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_service_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Services", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_ingress_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Ingresses", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_deployment_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Deployments", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_statefulset_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Statefulsets", + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_daemonset_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Daemonsets", + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_persistentvolumeclaim_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Persistent Volume Claims", + "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_hpa_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Horizontal Pod Autoscalers", + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_configmap_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Configmaps", + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_secret_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Secrets", + "refId": "J" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_networkpolicy_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Network Policies", + "refId": "K" + } + ], + "title": "Kubernetes Ressource Count", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 0, + "y": 15 + }, + "id": 18, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_daemonset_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Daemonsets", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 2, + "y": 15 + }, + "id": 20, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_persistentvolumeclaim_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "PVC", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 4, + "y": 15 + }, + "id": 23, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_hpa_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "HPA", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 6, + "y": 15 + }, + "id": 21, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_configmap_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Configmaps", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 8, + "y": 15 + }, + "id": 24, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_secret_info{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Secrets", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 10, + "y": 15 + }, + "id": 25, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_networkpolicy_labels{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Network Policies", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 19 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_status_ready{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Ready", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_status_running{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Running", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_status_waiting{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Waiting", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_status_restarts_total{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Restarts Total", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_status_terminated{namespace=\"$namespace\"})", + "interval": "", + "legendFormat": "Terminated", + "refId": "E" + } + ], + "title": "Nb of pods by state", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 19 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_info{namespace=\"$namespace\"}) by (pod)", + "interval": "", + "legendFormat": "{{ pod }}", + "refId": "A" + } + ], + "title": "Nb of replicas by pod", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "CPU Cores", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 29, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(rate(container_cpu_usage_seconds_total{namespace=\"$namespace\", image!=\"\", service=~\"$service\"}[$__rate_interval])) by (pod)", + "interval": "$resolution", + "legendFormat": "{{ pod }}", + "refId": "A" + } + ], + "title": "CPU usage by Pod", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 30, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(container_memory_working_set_bytes{namespace=\"$namespace\", image!=\"\", service=~\"$service\"}) by (pod)", + "interval": "$resolution", + "legendFormat": "{{ pod }}", + "refId": "A" + } + ], + "title": "Memory usage by Pod", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 35 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(kube_deployment_status_replicas_available{namespace=\"$namespace\"}) by (deployment)", + "interval": "", + "legendFormat": "{{ deployment }}", + "refId": "A" + } + ], + "title": "Replicas available by deployment", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 35 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_deployment_status_replicas_unavailable{namespace=\"$namespace\"}) by (deployment)", + "interval": "", + "legendFormat": "{{ deployment }}", + "refId": "A" + } + ], + "title": "Replicas unavailable by deployment", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(kubelet_volume_stats_used_bytes{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim) / sum(kubelet_volume_stats_capacity_bytes{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim)", + "interval": "", + "legendFormat": "{{ persistentvolumeclaim }}", + "refId": "A" + } + ], + "title": "Persistent Volumes - Capacity", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 43 + }, + "id": 27, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.3.3", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "exemplar": true, + "expr": "sum(kubelet_volume_stats_inodes_used{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim) / sum(kubelet_volume_stats_inodes{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim) * 100", + "interval": "", + "legendFormat": "{{ persistentvolumeclaim }}", + "refId": "A" + } + ], + "title": "Persistent Volumes - Inodes", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": [ + "Kubernetes", + "Prometheus" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "kube-system", + "value": "kube-system" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_info, namespace)", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_pod_info, namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "1m", + "value": "1m" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "resolution", + "options": [ + { + "selected": true, + "text": "1m", + "value": "1m" + }, + { + "selected": false, + "text": "2m", + "value": "2m" + }, + { + "selected": false, + "text": "3m", + "value": "3m" + }, + { + "selected": false, + "text": "5m", + "value": "5m" + } + ], + "query": "1m, 2m, 3m, 5m", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "isNone": true, + "selected": false, + "text": "None", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(machine_cpu_cores, service)", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "service", + "options": [], + "query": { + "query": "label_values(machine_cpu_cores, service)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "", + "title": "Kubernetes / Views / Namespaces", + "uid": "k8s_views_ns", + "version": 1, + "weekStart": "" + } trivy: json: | { From 684b35c0094940937a6a2510312fb4e3376d2ad3 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 12:16:44 +0100 Subject: [PATCH 043/113] service graph --- infra/values/grafana-values.yaml | 2076 ++---------------------------- 1 file changed, 79 insertions(+), 1997 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index d623c1c..219c1a0 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -63,1966 +63,9 @@ dashboardProviders: dashboards: default: kubernetes: - json: | - { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "description": "This is a modern 'Namespaces View' dashboard for your Kubernetes cluster(s). Made for kube-prometheus-stack and take advantage of the latest Grafana features (8.x.x). GitHub repository: https://github.com/dotdc/grafana-dashboards-kubernetes", - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": 15758, - "graphTooltip": 1, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": { - "type": "tempo", - "uid": "tempo" - }, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 39, - "options": { - "edges": {}, - "nodes": {} - }, - "targets": [ - { - "datasource": { - "type": "tempo", - "uid": "tempo" - }, - "limit": 20, - "queryType": "serviceMap", - "refId": "A", - "tableType": "traces" - } - ], - "title": "Service Graph", - "type": "nodeGraph" - }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 10 - }, - "id": 38, - "panels": [], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "refId": "A" - } - ], - "title": "Resources", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 0, - "y": 11 - }, - "id": 17, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Running Pods", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 2, - "y": 11 - }, - "id": 15, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_service_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Services", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 4, - "y": 11 - }, - "id": 22, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_endpoint_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Endpoints", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 6, - "y": 11 - }, - "id": 26, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_ingress_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Ingress", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 8, - "y": 11 - }, - "id": 14, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_deployment_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Deployments", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 10, - "y": 11 - }, - "id": 16, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "expr": "sum(kube_statefulset_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Statefulsets", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 11 - }, - "id": 32, - "options": { - "legend": { - "calcs": [ - "mean", - "lastNotNull", - "max", - "min" - ], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_status_running{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Running Pods", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_service_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Services", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_ingress_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Ingresses", - "refId": "C" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_deployment_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Deployments", - "refId": "D" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_statefulset_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Statefulsets", - "refId": "E" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_daemonset_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Daemonsets", - "refId": "F" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_persistentvolumeclaim_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Persistent Volume Claims", - "refId": "G" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_hpa_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Horizontal Pod Autoscalers", - "refId": "H" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_configmap_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Configmaps", - "refId": "I" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_secret_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Secrets", - "refId": "J" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_networkpolicy_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Network Policies", - "refId": "K" - } - ], - "title": "Kubernetes Ressource Count", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 0, - "y": 15 - }, - "id": 18, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_daemonset_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Daemonsets", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 2, - "y": 15 - }, - "id": 20, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_persistentvolumeclaim_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "PVC", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 4, - "y": 15 - }, - "id": 23, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_hpa_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "HPA", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 6, - "y": 15 - }, - "id": 21, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_configmap_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Configmaps", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 8, - "y": 15 - }, - "id": 24, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_secret_info{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Secrets", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "blue", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 2, - "x": 10, - "y": 15 - }, - "id": 25, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_networkpolicy_labels{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Network Policies", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 19 - }, - "id": 5, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_status_ready{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Ready", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_status_running{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Running", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_status_waiting{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Waiting", - "refId": "C" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_status_restarts_total{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Restarts Total", - "refId": "D" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_status_terminated{namespace=\"$namespace\"})", - "interval": "", - "legendFormat": "Terminated", - "refId": "E" - } - ], - "title": "Nb of pods by state", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 0, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 19 - }, - "id": 2, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_pod_container_info{namespace=\"$namespace\"}) by (pod)", - "interval": "", - "legendFormat": "{{ pod }}", - "refId": "A" - } - ], - "title": "Nb of replicas by pod", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "CPU Cores", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 27 - }, - "id": 29, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum(rate(container_cpu_usage_seconds_total{namespace=\"$namespace\", image!=\"\", service=~\"$service\"}[$__rate_interval])) by (pod)", - "interval": "$resolution", - "legendFormat": "{{ pod }}", - "refId": "A" - } - ], - "title": "CPU usage by Pod", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "bytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 27 - }, - "id": 30, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum(container_memory_working_set_bytes{namespace=\"$namespace\", image!=\"\", service=~\"$service\"}) by (pod)", - "interval": "$resolution", - "legendFormat": "{{ pod }}", - "refId": "A" - } - ], - "title": "Memory usage by Pod", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 35 - }, - "id": 7, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum(kube_deployment_status_replicas_available{namespace=\"$namespace\"}) by (deployment)", - "interval": "", - "legendFormat": "{{ deployment }}", - "refId": "A" - } - ], - "title": "Replicas available by deployment", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 35 - }, - "id": 8, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(kube_deployment_status_replicas_unavailable{namespace=\"$namespace\"}) by (deployment)", - "interval": "", - "legendFormat": "{{ deployment }}", - "refId": "A" - } - ], - "title": "Replicas unavailable by deployment", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 2, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 43 - }, - "id": 12, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum(kubelet_volume_stats_used_bytes{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim) / sum(kubelet_volume_stats_capacity_bytes{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim)", - "interval": "", - "legendFormat": "{{ persistentvolumeclaim }}", - "refId": "A" - } - ], - "title": "Persistent Volumes - Capacity", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 2, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 43 - }, - "id": 27, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.3.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum(kubelet_volume_stats_inodes_used{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim) / sum(kubelet_volume_stats_inodes{namespace=\"$namespace\", service=~\"$service\"}) by (persistentvolumeclaim) * 100", - "interval": "", - "legendFormat": "{{ persistentvolumeclaim }}", - "refId": "A" - } - ], - "title": "Persistent Volumes - Inodes", - "type": "timeseries" - } - ], - "refresh": "30s", - "schemaVersion": 39, - "tags": [ - "Kubernetes", - "Prometheus" - ], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "Prometheus", - "value": "PBFA97CFB590B2093" - }, - "hide": 0, - "includeAll": false, - "multi": false, - "name": "datasource", - "options": [], - "query": "prometheus", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - }, - { - "current": { - "selected": false, - "text": "kube-system", - "value": "kube-system" - }, - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "definition": "label_values(kube_pod_info, namespace)", - "hide": 0, - "includeAll": false, - "multi": false, - "name": "namespace", - "options": [], - "query": { - "query": "label_values(kube_pod_info, namespace)", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "tagValuesQuery": "", - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "current": { - "selected": false, - "text": "1m", - "value": "1m" - }, - "hide": 0, - "includeAll": false, - "multi": false, - "name": "resolution", - "options": [ - { - "selected": true, - "text": "1m", - "value": "1m" - }, - { - "selected": false, - "text": "2m", - "value": "2m" - }, - { - "selected": false, - "text": "3m", - "value": "3m" - }, - { - "selected": false, - "text": "5m", - "value": "5m" - } - ], - "query": "1m, 2m, 3m, 5m", - "queryValue": "", - "skipUrlSync": false, - "type": "custom" - }, - { - "current": { - "isNone": true, - "selected": false, - "text": "None", - "value": "" - }, - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "definition": "label_values(machine_cpu_cores, service)", - "hide": 0, - "includeAll": false, - "multi": false, - "name": "service", - "options": [], - "query": { - "query": "label_values(machine_cpu_cores, service)", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "type": "query" - } - ] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": {}, - "timezone": "", - "title": "Kubernetes / Views / Namespaces", - "uid": "k8s_views_ns", - "version": 1, - "weekStart": "" - } + gnetId: 15758 + revision: 1 + datasource: Prometheus trivy: json: | { @@ -2980,7 +1023,6 @@ dashboards: "fiscalYearStartMonth": 0, "gnetId": 13713, "graphTooltip": 0, - "id": 12, "links": [], "panels": [ { @@ -3037,7 +1079,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "purple" + "color": "purple", + "value": null } ] }, @@ -3100,7 +1143,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "light-blue" + "color": "light-blue", + "value": null } ] }, @@ -3166,7 +1210,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "purple" + "color": "purple", + "value": null }, { "color": "red", @@ -3270,7 +1315,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "purple" + "color": "purple", + "value": null }, { "color": "red", @@ -3385,7 +1431,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "purple" + "color": "purple", + "value": null } ] } @@ -3445,7 +1492,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "purple" + "color": "purple", + "value": null } ] }, @@ -3455,7 +1503,7 @@ dashboards: }, "gridPos": { "h": 4, - "w": 8, + "w": 9, "x": 15, "y": 6 }, @@ -3589,41 +1637,34 @@ dashboards: }, { "datasource": { - "type": "loki", - "uid": "loki" + "type": "tempo", + "uid": "tempo" }, "gridPos": { - "h": 8, + "h": 17, "w": 16, "x": 8, "y": 10 }, - "id": 11, - "interval": "", + "id": 37, "options": { - "dedupStrategy": "none", - "enableLogDetails": true, - "prettifyLogMessage": false, - "showCommonLabels": false, - "showLabels": false, - "showTime": true, - "sortOrder": "Descending", - "wrapLogMessage": false + "edges": {}, + "nodes": {} }, "targets": [ { "datasource": { - "type": "loki", - "uid": "loki" + "type": "tempo", + "uid": "tempo" }, - "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\"| json | line_format \"Status:{{.OriginStatus}} Client From {{.ClientAddr}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} Route To {{.ServiceAddr}}\"", - "legendFormat": "", - "refId": "A" + "limit": 20, + "queryType": "serviceMap", + "refId": "A", + "tableType": "traces" } ], - "title": "Logs", - "transparent": true, - "type": "logs" + "title": "Service graph", + "type": "nodeGraph" }, { "datasource": { @@ -3673,7 +1714,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "transparent" + "color": "transparent", + "value": null }, { "color": "red", @@ -3826,7 +1868,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "transparent" + "color": "transparent", + "value": null }, { "color": "red", @@ -3894,8 +1937,8 @@ dashboards: "gridPos": { "h": 9, "w": 8, - "x": 8, - "y": 18 + "x": 0, + "y": 27 }, "id": 34, "interval": "30s", @@ -3929,6 +1972,44 @@ dashboards: "transparent": true, "type": "timeseries" }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "gridPos": { + "h": 16, + "w": 16, + "x": 8, + "y": 27 + }, + "id": 11, + "interval": "", + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\"| json | line_format \"Status:{{.OriginStatus}} Client From {{.ClientAddr}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} Route To {{.ServiceAddr}}\"", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Logs", + "transparent": true, + "type": "logs" + }, { "datasource": { "type": "loki", @@ -3977,7 +2058,8 @@ dashboards: "mode": "absolute", "steps": [ { - "color": "transparent" + "color": "transparent", + "value": null }, { "color": "red", @@ -4045,8 +2127,8 @@ dashboards: "gridPos": { "h": 9, "w": 8, - "x": 16, - "y": 18 + "x": 0, + "y": 36 }, "id": 35, "interval": "30s", @@ -4108,7 +2190,7 @@ dashboards: "timezone": "", "title": "Traefik Via Loki", "uid": "fJarCeaGk", - "version": 2, + "version": 1, "weekStart": "" } dot-ai-logs: From 5d8437bd01119bf2e5704c7d850cd78b06c08498 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 12:24:27 +0100 Subject: [PATCH 044/113] filter logs --- infra/values/grafana-values.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 219c1a0..d573602 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -1993,7 +1993,8 @@ dashboards: "showLabels": false, "showTime": true, "sortOrder": "Descending", - "wrapLogMessage": false + "wrapLogMessage": true, + "displayedFields": ["OriginStatus", "RequestMethod", "RequestAddr", "RequestPath", "ClientAddr", "ServiceAddr", "ServiceName", "Duration"] }, "targets": [ { @@ -2001,7 +2002,7 @@ dashboards: "type": "loki", "uid": "loki" }, - "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\"| json | line_format \"Status:{{.OriginStatus}} Client From {{.ClientAddr}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} Route To {{.ServiceAddr}}\"", + "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\" | json", "legendFormat": "", "refId": "A" } From c914498590448a860622a59914c099fada861c2d Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 12:27:58 +0100 Subject: [PATCH 045/113] line format --- infra/values/grafana-values.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index d573602..1ee661a 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -1993,8 +1993,7 @@ dashboards: "showLabels": false, "showTime": true, "sortOrder": "Descending", - "wrapLogMessage": true, - "displayedFields": ["OriginStatus", "RequestMethod", "RequestAddr", "RequestPath", "ClientAddr", "ServiceAddr", "ServiceName", "Duration"] + "wrapLogMessage": false }, "targets": [ { @@ -2002,7 +2001,7 @@ dashboards: "type": "loki", "uid": "loki" }, - "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\" | json", + "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\" | json | line_format \"{{.OriginStatus}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} -> {{.ServiceAddr}}\"", "legendFormat": "", "refId": "A" } From 279bc8b273b7962e52f59a590b02a25a5d8b3957 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 23 Mar 2026 13:11:28 +0100 Subject: [PATCH 046/113] traefik default time span --- infra/values/grafana-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 1ee661a..2033c7c 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -2170,7 +2170,7 @@ dashboards: "list": [] }, "time": { - "from": "now-1h", + "from": "now-12h", "to": "now" }, "timeRangeUpdatedDuringEditOrView": false, From b9d8470a52cbc3c7a8e11391be8b56cbf85f65e2 Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 11:48:28 +0100 Subject: [PATCH 047/113] oauth env sidecar --- .../policies/auth-sidecar-injector.yaml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index 6b41047..2445a8c 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -340,6 +340,96 @@ spec: capabilities: drop: - ALL + - name: inject-sidecar-oauth + skipBackgroundRequests: true + match: + any: + - resources: + kinds: + - Pod + annotations: + policies.forteapps.io/auth: "true" + policies.forteapps.io/auth-type: "oauth" + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + - argocd + - cert-manager + - monitoring + context: + - name: appPort + variable: + jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` + mutate: + patchStrategicMerge: + spec: + containers: + - name: authn + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: auth + protocol: TCP + env: + - name: AUTH_MODE + value: "oauth" + - name: AUTH_LISTEN_ADDR + value: ":8080" + - name: AUTH_LOG_LEVEL + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" + - name: AUTH_UPSTREAM_URL + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}" + - name: AUTH_OAUTH_AUTHORITY + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-authority\" }}" + - name: AUTH_OAUTH_CLIENT_ID + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-client-id\" }}" + - name: AUTH_OAUTH_SCOPES + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-scopes\" || 'openid,profile,email' }}" + - name: AUTH_OAUTH_DELEGATION_ENABLED + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-enabled\" || 'false' }}" + - name: AUTH_OAUTH_DELEGATION_CLIENT_ID + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-client-id\" || '' }}" + - name: AUTH_OAUTH_DELEGATION_SCOPES + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-scopes\" || '' }}" + - name: AUTH_OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: auth-oauth + key: client-secret + - name: AUTH_OAUTH_DELEGATION_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: auth-oauth + key: delegation-client-secret + resources: + limits: + cpu: 50m + memory: 64Mi + requests: + cpu: 10m + memory: 32Mi + readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 2 + periodSeconds: 5 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL - name: generate-auth-network-policy skipBackgroundRequests: true match: From 5640a5ca4ac289c72468f6737e1bc0374290ef4b Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 13:24:06 +0100 Subject: [PATCH 048/113] sidecar port --- .../policies/auth-sidecar-injector.yaml | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index 2445a8c..1eb9e2e 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -10,7 +10,7 @@ metadata: policies.kyverno.io/severity: medium policies.kyverno.io/subject: Pod policies.kyverno.io/description: >- - Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. A NetworkPolicy is generated to restrict ingress to the sidecar port only. + Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. The sidecar port defaults to 8080 and can be overridden via the policies.forteapps.io/auth-sidecar-port annotation. A NetworkPolicy is generated to restrict ingress to the sidecar port only. spec: background: false rules: @@ -119,6 +119,9 @@ spec: - name: appPort variable: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` + - name: sidecarPort + variable: + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') mutate: patchStrategicMerge: spec: @@ -126,12 +129,12 @@ spec: - name: authn image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" ports: - - containerPort: 8080 + - containerPort: "{{ sidecarPort }}" name: auth protocol: TCP env: - name: AUTH_LISTEN_ADDR - value: ":8080" + value: ":{{ sidecarPort }}" - name: AUTH_UPSTREAM_URL value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}" - name: AUTH_PUBLIC_PATHS @@ -154,13 +157,13 @@ spec: readinessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 2 periodSeconds: 5 livenessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 5 periodSeconds: 10 securityContext: @@ -197,6 +200,9 @@ spec: - name: appPort variable: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` + - name: sidecarPort + variable: + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') mutate: patchStrategicMerge: spec: @@ -205,14 +211,14 @@ spec: image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - - containerPort: 8080 + - containerPort: "{{ sidecarPort }}" name: auth protocol: TCP env: - name: AUTH_MODE value: "oidc" - name: AUTH_LISTEN_ADDR - value: ":8080" + value: ":{{ sidecarPort }}" - name: AUTH_LOG_LEVEL value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" - name: AUTH_UPSTREAM_URL @@ -249,13 +255,13 @@ spec: readinessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 2 periodSeconds: 5 livenessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 5 periodSeconds: 10 securityContext: @@ -287,6 +293,9 @@ spec: - name: appPort variable: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` + - name: sidecarPort + variable: + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') mutate: patchStrategicMerge: spec: @@ -295,14 +304,14 @@ spec: image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - - containerPort: 8080 + - containerPort: "{{ sidecarPort }}" name: auth protocol: TCP env: - name: AUTH_MODE value: "mcp" - name: AUTH_LISTEN_ADDR - value: ":8080" + value: ":{{ sidecarPort }}" - name: AUTH_LOG_LEVEL value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" - name: AUTH_UPSTREAM_URL @@ -325,13 +334,13 @@ spec: readinessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 2 periodSeconds: 5 livenessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 5 periodSeconds: 10 securityContext: @@ -363,6 +372,9 @@ spec: - name: appPort variable: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` + - name: sidecarPort + variable: + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') mutate: patchStrategicMerge: spec: @@ -371,14 +383,14 @@ spec: image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - - containerPort: 8080 + - containerPort: "{{ sidecarPort }}" name: auth protocol: TCP env: - name: AUTH_MODE value: "oauth" - name: AUTH_LISTEN_ADDR - value: ":8080" + value: ":{{ sidecarPort }}" - name: AUTH_LOG_LEVEL value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" - name: AUTH_UPSTREAM_URL @@ -415,13 +427,13 @@ spec: readinessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 2 periodSeconds: 5 livenessProbe: httpGet: path: /healthz - port: 8080 + port: "{{ sidecarPort }}" initialDelaySeconds: 5 periodSeconds: 10 securityContext: @@ -454,6 +466,10 @@ spec: operator: In value: - CREATE + context: + - name: sidecarPort + variable: + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') generate: synchronize: false apiVersion: networking.k8s.io/v1 @@ -472,5 +488,5 @@ spec: - Ingress ingress: - ports: - - port: 8080 + - port: "{{ sidecarPort }}" protocol: TCP From 8029b7816d55f87f0c962664147aed582d5e0c6c Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 13:25:53 +0100 Subject: [PATCH 049/113] rename annotation --- .../policies/auth-sidecar-injector.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index 1eb9e2e..e0535d8 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -10,7 +10,7 @@ metadata: policies.kyverno.io/severity: medium policies.kyverno.io/subject: Pod policies.kyverno.io/description: >- - Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. The sidecar port defaults to 8080 and can be overridden via the policies.forteapps.io/auth-sidecar-port annotation. A NetworkPolicy is generated to restrict ingress to the sidecar port only. + Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. The sidecar port defaults to 8080 and can be overridden via the policies.forteapps.io/auth-port annotation. A NetworkPolicy is generated to restrict ingress to the sidecar port only. spec: background: false rules: @@ -121,7 +121,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') mutate: patchStrategicMerge: spec: @@ -202,7 +202,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') mutate: patchStrategicMerge: spec: @@ -295,7 +295,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') mutate: patchStrategicMerge: spec: @@ -374,7 +374,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') mutate: patchStrategicMerge: spec: @@ -469,7 +469,7 @@ spec: context: - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-sidecar-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') generate: synchronize: false apiVersion: networking.k8s.io/v1 From c33abcc357017541f69988abb463ac28a1227391 Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 13:26:31 +0100 Subject: [PATCH 050/113] default port --- cluster-resources/policies/auth-sidecar-injector.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index e0535d8..ee6bb70 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -121,7 +121,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001') mutate: patchStrategicMerge: spec: @@ -202,7 +202,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001') mutate: patchStrategicMerge: spec: @@ -295,7 +295,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001') mutate: patchStrategicMerge: spec: @@ -374,7 +374,7 @@ spec: jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001') mutate: patchStrategicMerge: spec: @@ -469,7 +469,7 @@ spec: context: - name: sidecarPort variable: - jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '8080') + jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001') generate: synchronize: false apiVersion: networking.k8s.io/v1 From 1609f6afde01ca93469576b1257a0fc979a3ca8f Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 13:34:00 +0100 Subject: [PATCH 051/113] exclude trivy-system from kyverno policies --- cluster-resources/policies/deployment-verifier.yaml | 2 ++ cluster-resources/policies/label-checker.yaml | 1 + 2 files changed, 3 insertions(+) diff --git a/cluster-resources/policies/deployment-verifier.yaml b/cluster-resources/policies/deployment-verifier.yaml index 8e827cc..9770218 100644 --- a/cluster-resources/policies/deployment-verifier.yaml +++ b/cluster-resources/policies/deployment-verifier.yaml @@ -23,6 +23,7 @@ spec: - monitoring - argocd - traefik-system + - trivy-system context: - name: ownerReplicaSet apiCall: @@ -59,6 +60,7 @@ spec: - monitoring - argocd - traefik-system + - trivy-system skipBackgroundRequests: true validate: allowExistingViolations: true diff --git a/cluster-resources/policies/label-checker.yaml b/cluster-resources/policies/label-checker.yaml index 129007a..8a8efd3 100644 --- a/cluster-resources/policies/label-checker.yaml +++ b/cluster-resources/policies/label-checker.yaml @@ -26,6 +26,7 @@ spec: - monitoring - secrets - kyverno + - trivy-system match: any: - resources: From ca8127802b7dcbc8db2a2cf058c08022b848d84f Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 15:25:14 +0100 Subject: [PATCH 052/113] doc --- cluster-resources/policies/auth-sidecar-injector.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index ee6bb70..f774c12 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -10,7 +10,7 @@ metadata: policies.kyverno.io/severity: medium policies.kyverno.io/subject: Pod policies.kyverno.io/description: >- - Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. The sidecar port defaults to 8080 and can be overridden via the policies.forteapps.io/auth-port annotation. A NetworkPolicy is generated to restrict ingress to the sidecar port only. + Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. The sidecar port defaults to 9001 and can be overridden via the policies.forteapps.io/auth-port annotation. A NetworkPolicy is generated to restrict ingress to the sidecar port only. spec: background: false rules: From e938bf2467181547c6fbb0225347fcd22657fcd8 Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 16:04:32 +0100 Subject: [PATCH 053/113] new grafana dash --- infra/values/grafana-values.yaml | 2376 ++++++------------------------ 1 file changed, 474 insertions(+), 1902 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 2033c7c..765af6b 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -2298,1902 +2298,576 @@ dashboards: "list": [ { "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, + "datasource": { "type": "datasource", "uid": "grafana" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, + "target": { "limit": 100, "matchAny": false, "tags": [], "type": "dashboard" }, "type": "dashboard" } ] }, - "description": "Cluster cost overview for Kubecost running Grafana Cloud's Managed Prometheus in the backend.", + "description": "UpCloud 4-node cluster cost monitoring powered by OpenCost with custom pricing.", "editable": true, "fiscalYearStartMonth": 0, - "gnetId": 15714, - "graphTooltip": 0, + "graphTooltip": 1, "links": [], "liveNow": false, "panels": [ { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Monthly run rate of CPU + GPU costs based on currently provisioned resources.", + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, + "id": 100, + "title": "Monthly Cost Overview", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Monthly CPU cost based on provisioned capacity and OpenCost custom pricing.", "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, + "color": { "fixedColor": "green", "mode": "fixed" }, "decimals": 2, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, "unit": "currencyEUR" }, "overrides": [] }, - "gridPos": { - "h": 3, - "w": 6, - "x": 0, - "y": 2 - }, - "hideTimeOverride": true, - "id": 2, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", - "format": "time_series", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": " {{ node }}", - "refId": "A" - } - ], - "timeFrom": "15m", + "gridPos": { "h": 4, "w": 6, "x": 0, "y": 1 }, + "id": 1, + "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, "title": "Monthly CPU Cost", - "type": "stat" + "type": "stat", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100))", + "legendFormat": "CPU Cost", + "refId": "A" + } + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Monthly run rate of memory costs based on currently provisioned expenses.", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Monthly memory cost based on provisioned capacity and OpenCost custom pricing.", "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, + "color": { "fixedColor": "green", "mode": "fixed" }, "decimals": 2, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, "unit": "currencyEUR" }, "overrides": [] }, - "gridPos": { - "h": 3, - "w": 6, - "x": 6, - "y": 2 - }, - "hideTimeOverride": true, - "id": 3, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", - "format": "time_series", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": " {{ node }}", - "refId": "A" - } - ], - "timeFrom": "15m", + "gridPos": { "h": 4, "w": 6, "x": 6, "y": 1 }, + "id": 2, + "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, "title": "Monthly Memory Cost", - "type": "stat" + "type": "stat", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100))", + "legendFormat": "Memory Cost", + "refId": "A" + } + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Monthly run rate of attached storage and PV costs based on currently provisioned resources.", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Monthly storage cost from PV hourly costs and local disk at $localStorageGBCost/GB.", "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, + "color": { "fixedColor": "green", "mode": "fixed" }, "decimals": 2, - "mappings": [ - { - "options": { - "N/A": { - "index": 1, - "text": "1" - } - }, - "type": "value" - }, - { - "options": { - "match": "null", - "result": { - "index": 0, - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, "unit": "currencyEUR" }, "overrides": [] }, - "gridPos": { - "h": 3, - "w": 6, - "x": 12, - "y": 2 - }, - "hideTimeOverride": true, - "id": 4, - "interval": "15", - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", - "format": "time_series", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": " {{ node }}", - "refId": "A" - } - ], - "timeFrom": "15m", + "gridPos": { "h": 4, "w": 6, "x": 12, "y": 1 }, + "id": 3, + "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, "title": "Monthly Storage Cost", - "type": "stat" + "type": "stat", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", + "legendFormat": "Storage Cost", + "refId": "A" + } + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Sum of compute, memory, storage and network costs.", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Total monthly infrastructure cost (CPU + Memory + Storage).", "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, + "color": { "fixedColor": "green", "mode": "fixed" }, "decimals": 2, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, "unit": "currencyEUR" }, "overrides": [] }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 2 - }, - "hideTimeOverride": true, - "id": 11, - "maxDataPoints": 100, - "options": { - "colorMode": "none", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", - "format": "time_series", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": " {{ node }}", - "refId": "A" - } - ], - "timeFrom": "15m", + "gridPos": { "h": 4, "w": 6, "x": 18, "y": 1 }, + "id": 4, + "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, "title": "Total Monthly Cost", - "type": "stat" + "type": "stat", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)) + sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)) + sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", + "legendFormat": "Total Cost", + "refId": "A" + } + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Current CPU use from applications divided by allocatable CPUs", + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 }, + "id": 101, + "title": "Resource Utilization", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Percentage of total CPU capacity currently in use.", "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], + "color": { "mode": "thresholds" }, + "decimals": 1, "max": 100, "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": null - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 30 - }, - { - "color": "#c15c17", - "value": 80 - } - ] - }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, "unit": "percent" }, "overrides": [] }, - "gridPos": { - "h": 4, - "w": 3, - "x": 0, - "y": 5 - }, - "hideTimeOverride": true, - "id": 13, - "maxDataPoints": 100, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "(\n sum(\n count(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n * on (instance) \n sum(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n ) \n / \n (sum (kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}))\n) * 100", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "A", - "step": 10 - } - ], - "timeFrom": "", + "gridPos": { "h": 6, "w": 6, "x": 0, "y": 6 }, + "id": 5, + "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, "title": "CPU Utilization", - "type": "gauge" + "type": "gauge", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(rate(container_cpu_usage_seconds_total{image!=\"\"}[5m])) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) * 100", + "legendFormat": "CPU Utilization", + "refId": "A" + } + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Current CPU reservation requests from applications vs allocatable CPU", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Percentage of total CPU capacity reserved by resource requests.", "fieldConfig": { "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], + "color": { "mode": "thresholds" }, + "decimals": 1, "max": 100, "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": null - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 30 - }, - { - "color": "#c15c17", - "value": 80 - } - ] - }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, "unit": "percent" }, "overrides": [] }, - "gridPos": { - "h": 4, - "w": 3, - "x": 3, - "y": 5 - }, - "id": 15, - "maxDataPoints": 100, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / SUM(kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}) * 100", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "refId": "A", - "step": 10 - } - ], - "title": "CPU Requests", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Current RAM use vs RAM available", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": null - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 30 - }, - { - "color": "#c15c17", - "value": 80 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 3, - "x": 6, - "y": 5 - }, - "hideTimeOverride": true, - "id": 17, - "interval": "15", - "maxDataPoints": 100, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "SUM(container_memory_working_set_bytes{namespace!=\"\"}) / SUM(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"}) * 100", - "format": "time_series", - "instant": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "A", - "step": 10 - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "", - "format": "time_series", - "instant": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "B" - } - ], - "timeFrom": "", - "title": "RAM Utilization", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Current RAM requests vs RAM available", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": null - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 30 - }, - { - "color": "#c15c17", - "value": 80 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 3, - "x": 9, - "y": 5 - }, - "id": 19, - "maxDataPoints": 100, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"})\n /\n sum(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"})\n) * 100", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "refId": "A", - "step": 10 - } - ], - "title": "RAM Requests", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "This gauge shows the current standard storage use, including cluster storage, vs storage available", - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "decimals": 2, - "mappings": [ - { - "options": { - "match": "null", - "result": { - "text": "N/A" - } - }, - "type": "special" - } - ], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": null - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 30 - }, - { - "color": "#c15c17", - "value": 80 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 12, - "y": 5 - }, - "hideTimeOverride": true, - "id": 21, - "maxDataPoints": 100, - "options": { - "minVizHeight": 75, - "minVizWidth": 75, - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showThresholdLabels": false, - "showThresholdMarkers": true, - "sizing": "auto" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(pod_pvc_allocation) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) /\nsum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) * 100", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "A", - "step": 10 - } - ], - "timeFrom": "", - "title": "Storage Utilization", - "type": "gauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Monthly run rate of CPU + GPU costs", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 9 - }, + "gridPos": { "h": 6, "w": 6, "x": 6, "y": 6 }, "id": 6, - "interval": "1m", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "8.3.5", + "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, + "title": "CPU Requests", + "type": "gauge", "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "compute cost", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) * 100", + "legendFormat": "CPU Requests", "refId": "A" } - ], - "title": "Compute Cost", - "type": "timeseries" + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Monthly run rate of memory costs", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Percentage of total memory capacity currently in use.", "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], + "color": { "mode": "thresholds" }, + "decimals": 1, + "max": 100, "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "currencyEUR" + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, + "unit": "percent" }, "overrides": [] }, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 9 - }, - "id": 9, - "interval": "1m", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "8.3.5", + "gridPos": { "h": 6, "w": 6, "x": 12, "y": 6 }, + "id": 7, + "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, + "title": "RAM Utilization", + "type": "gauge", "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "memory cost", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(container_memory_working_set_bytes{image!=\"\"}) / sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) * 100", + "legendFormat": "RAM Utilization", "refId": "A" } - ], - "title": "Memory Cost", - "type": "timeseries" + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Monthly run rate of attached disk + PV storage costs", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Percentage of total memory capacity reserved by resource requests.", "fieldConfig": { "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], + "color": { "mode": "thresholds" }, + "decimals": 1, + "max": 100, "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "currencyEUR" + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, + "unit": "percent" }, "overrides": [] }, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 9 - }, - "id": 10, - "interval": "1m", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "8.3.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "legendFormat": "storage cost", - "refId": "A" - } - ], - "title": "Storage Cost", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Sum of compute, memory, and storage costs", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 9 - }, - "id": 22, - "interval": "1m", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "8.3.5", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", - "format": "time_series", - "instant": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "total cost", - "refId": "A" - } - ], - "title": "Total Cost", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Cost of by resource class of currently provisioned nodes", - "fieldConfig": { - "defaults": { - "custom": { - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "decimals": 2, - "displayName": "", - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Time" - }, - "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.hidden", - "value": true - }, - { - "id": "custom.align" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Value" - }, - "properties": [ - { - "id": "displayName", - "value": "Compute Cost" - }, - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Value #A" - }, - "properties": [ - { - "id": "displayName", - "value": "CPU Cost" - }, - { - "id": "unit", - "value": "currencyEUR" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Value #B" - }, - "properties": [ - { - "id": "displayName", - "value": "Mem Cost" - }, - { - "id": "unit", - "value": "currencyEUR" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Value #C" - }, - "properties": [ - { - "id": "displayName", - "value": "Total" - }, - { - "id": "unit", - "value": "currencyEUR" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "instance" - }, - "properties": [ - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.hidden", - "value": true - }, - { - "id": "custom.align" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Value #D" - }, - "properties": [ - { - "id": "displayName", - "value": "GPU" - }, - { - "id": "unit", - "value": "short" - }, - { - "id": "decimals", - "value": 2 - }, - { - "id": "custom.align" - } - ] - } - ] - }, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 16 - }, + "gridPos": { "h": 6, "w": 6, "x": 18, "y": 6 }, "id": 8, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "11.0.0", + "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, + "title": "RAM Requests", + "type": "gauge", "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100)", - "format": "table", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": "", + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\"}) / sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) * 100", + "legendFormat": "RAM Requests", "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", - "format": "table", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", - "format": "table", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "D" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": false, - "expr": "# CPU \navg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100) +\n# GPU\navg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n# Memory\navg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n", - "format": "table", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "C" } - ], - "title": "Monthly Cost by Node", - "transformations": [ - { - "id": "merge", - "options": { - "reducers": [] - } - } - ], - "type": "table" + ] }, { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "description": "Monthly run rate of attached disk + PV storage costs based on currently provisioned resources.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-GrYlRd" + "collapsed": true, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 12 }, + "id": 102, + "title": "Cost Trends", + "type": "row", + "panels": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Hourly cost trend stacked by resource type.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "normal" } }, + "decimals": 2, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "currencyEUR" + }, + "overrides": [] }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 }, + "id": 9, + "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, + "title": "Total Cost Over Time", + "type": "timeseries", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node))", + "legendFormat": "CPU", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node))", + "legendFormat": "Memory", + "refId": "B" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost / 730 OR on() vector(0))", + "legendFormat": "Storage", + "refId": "C" + } + ] + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Hourly cost trend broken down by namespace.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 15, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "normal" } }, + "decimals": 4, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 }, + "id": 10, + "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, + "title": "Cost by Namespace Over Time", + "type": "timeseries", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(container_cpu_allocation{namespace=~\"$namespace\"} * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes{namespace=~\"$namespace\"} / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (namespace)", + "legendFormat": "{{namespace}}", + "refId": "A" + } + ] + } + ] + }, + { + "collapsed": true, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 13 }, + "id": 103, + "title": "Cost Breakdown", + "type": "row", + "panels": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Monthly cost breakdown per node showing CPU and memory costs.", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "custom": { "align": "auto", "cellOptions": { "type": "auto" }, "inspect": false }, + "decimals": 2, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "currencyEUR" + }, + "overrides": [ + { "matcher": { "id": "byName", "options": "Node" }, "properties": [{ "id": "custom.width", "value": 250 }] } + ] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 14 }, + "id": 11, + "options": { "cellHeight": "sm", "footer": { "countRows": false, "enablePagination": false, "fields": "", "reducer": ["sum"], "show": true }, "showHeader": true, "sortBy": [{ "desc": true, "displayName": "CPU Cost" }] }, + "title": "Monthly Cost by Node", + "type": "table", + "transformations": [ + { "id": "seriesToColumns", "options": { "byField": "node" } }, + { "id": "organize", "options": { "excludeByName": { "Time 1": true, "Time 2": true }, "renameByName": { "Value #A": "CPU Cost", "Value #B": "Memory Cost", "node": "Node" } } } + ], + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730", + "format": "table", + "instant": true, + "legendFormat": "CPU Cost", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730", + "format": "table", + "instant": true, + "legendFormat": "Memory Cost", + "refId": "B" + } + ] + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Monthly cost split by resource type.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "decimals": 2, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 14 }, + "id": 12, + "options": { "displayMode": "gradient", "minVizHeight": 10, "minVizWidth": 0, "namePlacement": "auto", "orientation": "horizontal", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showUnfilled": true, "valueMode": "color" }, + "title": "Monthly Cost by Resource", + "type": "bargauge", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730)", + "legendFormat": "CPU", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730)", + "legendFormat": "Memory", + "refId": "B" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", + "legendFormat": "Storage", + "refId": "C" + } + ] + } + ] + }, + { + "collapsed": true, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 14 }, + "id": 104, + "title": "Namespace & Container Costs", + "type": "row", + "panels": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Hourly cost distribution across namespaces.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "decimals": 4, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 15 }, + "id": 13, + "options": { "displayLabels": ["name", "percent"], "legend": { "displayMode": "table", "placement": "right", "values": ["value", "percent"] }, "pieType": "donut", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "tooltip": { "mode": "single", "sort": "none" } }, + "title": "Hourly Cost by Namespace", + "type": "piechart", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(container_cpu_allocation * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (namespace)", + "legendFormat": "{{namespace}}", + "refId": "A" + } + ] + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Hourly cost distribution across containers in the selected namespace(s).", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "decimals": 4, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 15 }, + "id": 14, + "options": { "displayLabels": ["name", "percent"], "legend": { "displayMode": "table", "placement": "right", "values": ["value", "percent"] }, "pieType": "donut", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "tooltip": { "mode": "single", "sort": "none" } }, + "title": "Hourly Cost by Container", + "type": "piechart", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(container_cpu_allocation{namespace=~\"$namespace\"} * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes{namespace=~\"$namespace\"} / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (container)", + "legendFormat": "{{container}}", + "refId": "A" + } + ] + } + ] + }, + { + "collapsed": true, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 15 }, + "id": 105, + "title": "Cost Efficiency", + "type": "row", + "panels": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Compare CPU requests against actual usage to identify over-provisioning.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "CPU Cores", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "none" } }, + "decimals": 2, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "short" + }, + "overrides": [ { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 + "matcher": { "id": "byRegexp", "options": "/Requested/" }, + "properties": [{ "id": "custom.lineStyle", "value": { "dash": [10, 10], "fill": "dash" } }, { "id": "custom.fillOpacity", "value": 0 }] } ] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 16 - }, - "id": 25, - "interval": "1m", - "options": { - "displayMode": "lcd", - "maxVizHeight": 300, - "minVizHeight": 16, - "minVizWidth": 8, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showUnfilled": true, - "sizing": "auto", - "text": {}, - "valueMode": "color" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "cpu", - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "memory", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) +\n(sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost OR on() vector(0))", - "format": "time_series", - "instant": true, - "intervalFactor": 1, - "legendFormat": "storage", - "refId": "C" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "expr": "SUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $percentEgress * $egressCost ", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "network", - "refId": "D" - } - ], - "title": "Monthly Cost by Resource", - "type": "bargauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 }, + "id": 15, + "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, + "title": "CPU Request vs Actual Usage by Namespace", + "type": "timeseries", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace=~\"$namespace\"}) by (namespace)", + "legendFormat": "{{namespace}} - Requested", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\", image!=\"\"}[5m])) by (namespace)", + "legendFormat": "{{namespace}} - Actual", + "refId": "B" } - }, - "mappings": [] + ] }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 25 - }, - "id": 29, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "list", - "placement": "right", - "showLegend": true, - "values": [] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "targets": [ { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "description": "Compare memory requests against actual usage to identify over-provisioning.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "Memory", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "none" } }, + "decimals": 2, + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { "id": "byRegexp", "options": "/Requested/" }, + "properties": [{ "id": "custom.lineStyle", "value": { "dash": [10, 10], "fill": "dash" } }, { "id": "custom.fillOpacity", "value": 0 }] + } + ] }, - "exemplar": true, - "expr": "sum(sum(container_memory_allocation_bytes) by (container,namespace, instance) * on(instance) group_left() (\n\t\t\t\tnode_ram_hourly_cost{} / 1024 / 1024 / 1024\n\t\t\t\t+ on(node,instance_type) group_left()\n\t\t\t\t\tlabel_replace\n\t\t\t\t\t(\n\t\t\t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t\t\t\t) * 0\n\t\t\t)\n + \n sum(container_cpu_allocation) by (container,namespace, instance) * on(instance) group_left() (\n\t \t\t\tnode_cpu_hourly_cost{} + on(node,instance_type) group_left()\n\t\t \t\t\tlabel_replace\n\t\t \t\t\t(\n\t\t \t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t \t\t\t) * 0\n\t\t \t)) by (namespace, container)", - "interval": "", - "legendFormat": "{{namespace}} / {{container}}", - "refId": "A" - } - ], - "title": "Hourly Cost by Container", - "type": "piechart" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 }, + "id": 16, + "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, + "title": "Memory Request vs Actual Usage by Namespace", + "type": "timeseries", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace=~\"$namespace\"}) by (namespace)", + "legendFormat": "{{namespace}} - Requested", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "expr": "sum(container_memory_working_set_bytes{namespace=~\"$namespace\", image!=\"\"}) by (namespace)", + "legendFormat": "{{namespace}} - Actual", + "refId": "B" } - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 25 - }, - "id": 31, - "options": { - "displayLabels": [ - "percent" - ], - "legend": { - "displayMode": "list", - "placement": "right", - "showLegend": true, - "values": [] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" + ] } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "exemplar": true, - "expr": "sum(sum(container_memory_allocation_bytes) by (namespace,instance) * on(instance) group_left() (\n\t\t\t\tnode_ram_hourly_cost{} / 1024 / 1024 / 1024\n\t\t\t\t+ on(node,instance_type) group_left()\n\t\t\t\t\tlabel_replace\n\t\t\t\t\t(\n\t\t\t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t\t\t\t) * 0\n\t\t\t)\n + \n sum(container_cpu_allocation) by (namespace,instance) * on(instance) group_left() (\n\t \t\t\tnode_cpu_hourly_cost{} + on(node,instance_type) group_left()\n\t\t \t\t\tlabel_replace\n\t\t \t\t\t(\n\t\t \t\t\t\tkube_node_labels{}, \"instance_type\", \"$1\", \"label_node_kubernetes_io_instance_type\", \"(.*)\"\n\t\t \t\t\t) * 0\n\t\t \t)) by (namespace)", - "interval": "", - "legendFormat": "{{namespace}}", - "refId": "A" - } - ], - "title": "Hourly Cost by Namespace", - "type": "piechart" + ] } ], "refresh": "30s", - "schemaVersion": 39, - "tags": [ - "cost", - "utilization", - "metrics" - ], + "schemaVersion": 38, + "tags": ["cost", "opencost", "upcloud"], "templating": { "list": [ { - "auto": true, - "auto_count": 1, - "auto_min": "1m", - "current": { - "selected": false, - "text": "auto", - "value": "$__auto_interval_timeRange" - }, - "hide": 2, - "name": "timeRange", - "options": [ - { - "selected": true, - "text": "auto", - "value": "$__auto_interval_timeRange" - }, - { - "selected": false, - "text": "1h", - "value": "1h" - }, - { - "selected": false, - "text": "6h", - "value": "6h" - }, - { - "selected": false, - "text": "12h", - "value": "12h" - }, - { - "selected": false, - "text": "1d", - "value": "1d" - }, - { - "selected": false, - "text": "7d", - "value": "7d" - }, - { - "selected": false, - "text": "14d", - "value": "14d" - }, - { - "selected": false, - "text": "30d", - "value": "30d" - }, - { - "selected": false, - "text": "90d", - "value": "90d" - } - ], - "query": "1h,6h,12h,1d,7d,14d,30d,90d", - "refresh": 2, - "skipUrlSync": false, - "type": "interval" - }, - { - "hide": 2, - "label": "Cost per Gb hour for attached disks", - "name": "localStorageGBCost", - "query": "0.04", - "skipUrlSync": false, - "type": "constant" - }, - { - "current": { - "selected": true, - "text": "0", - "value": "0" - }, - "hide": 0, - "label": "Sustained Use Discount %", - "name": "useDiscount", - "options": [ - { - "selected": true, - "text": "0", - "value": "0" - } - ], - "query": "0", - "skipUrlSync": false, - "type": "textbox" - }, - { - "hide": 2, - "name": "percentEgress", - "query": "0.1", - "skipUrlSync": false, - "type": "constant" - }, - { - "hide": 2, - "name": "egressCost", - "query": "0.12", - "skipUrlSync": false, - "type": "constant" - }, - { - "current": { - "selected": false, - "text": "Prometheus", - "value": "PBFA97CFB590B2093" - }, + "current": { "selected": false, "text": "Prometheus", "value": "Prometheus" }, "hide": 0, "includeAll": false, "multi": false, @@ -4207,153 +2881,51 @@ dashboards: "type": "datasource" }, { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "definition": "query_result(sum(container_memory_working_set_bytes{namespace!=\"\"}) by (namespace))", + "allValue": ".*", + "current": { "selected": true, "text": ["All"], "value": ["$__all"] }, + "datasource": { "type": "prometheus", "uid": "${datasource}" }, + "definition": "label_values(namespace)", "hide": 0, - "includeAll": false, - "label": "ns", - "multi": false, + "includeAll": true, + "multi": true, "name": "namespace", "options": [], - "query": { - "query": "query_result(sum(container_memory_working_set_bytes{namespace!=\"\"}) by (namespace))", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "/namespace=\\\"(.*?)(\\\")/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "definition": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (pod))", - "hide": 0, - "includeAll": false, - "label": "pod", - "multi": false, - "name": "pod", - "options": [], - "query": { - "query": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (pod))", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "/pod=\\\"(.*?)(\\\")/", + "query": { "query": "label_values(namespace)", "refId": "StandardVariableQuery" }, + "refresh": 2, + "regex": "", "skipUrlSync": false, "sort": 1, "type": "query" }, { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "definition": "cluster", + "current": { "selected": false, "text": "0", "value": "0" }, "hide": 0, - "includeAll": false, - "multi": false, - "name": "cluster", - "options": [], - "query": { - "query": "cluster", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "/cluster=\\\"(.*?)(\\\")/", + "label": "Sustained Use Discount %", + "name": "useDiscount", + "options": [{ "selected": true, "text": "0", "value": "0" }], + "query": "0", "skipUrlSync": false, - "sort": 0, - "type": "query" + "type": "textbox" }, { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "definition": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (container))", - "hide": 0, - "includeAll": false, - "label": "container", - "multi": false, - "name": "container", - "options": [], - "query": { - "query": "query_result(sum(container_memory_working_set_bytes{namespace=\"$namespace\"}) by (container))", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "/container=\\\"(.*?)(\\\")/", + "current": { "selected": false, "text": "0.03", "value": "0.03" }, + "hide": 2, + "label": "Local Storage GB Cost", + "name": "localStorageGBCost", + "options": [{ "selected": true, "text": "0.03", "value": "0.03" }], + "query": "0.03", "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "definition": "node", - "hide": 0, - "includeAll": false, - "label": "node", - "multi": false, - "name": "node", - "options": [], - "query": { - "query": "node", - "refId": "StandardVariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" + "type": "constant" } ] }, - "time": { - "from": "now-7d", - "to": "now" - }, - "timeRangeUpdatedDuringEditOrView": false, + "time": { "from": "now-7d", "to": "now" }, "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h"], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] }, "timezone": "", - "title": "Opencost Dashboard", + "title": "Cluster Cost Overview", "uid": "JOUdHGZZz", "version": 1, "weekStart": "" From 87cd2401c5cf2b3120675f16439eedbd390ddc10 Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 18:31:28 +0100 Subject: [PATCH 054/113] resource lowering on monitoring --- infra/values/grafana-values.yaml | 4 ++-- infra/values/loki-values.yaml | 8 ++++---- infra/values/prometheus-values.yaml | 6 +++--- infra/values/tempo-values.yaml | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index 765af6b..b8cd033 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -4,10 +4,10 @@ ingress: - grafana.127.0.0.1.nip.io resources: requests: - cpu: 100m + cpu: 50m memory: 128Mi limits: - cpu: 200m + cpu: 100m memory: 256Mi adminUser: admin diff --git a/infra/values/loki-values.yaml b/infra/values/loki-values.yaml index a235593..0cd3fa0 100644 --- a/infra/values/loki-values.yaml +++ b/infra/values/loki-values.yaml @@ -29,11 +29,11 @@ singleBinary: replicas: 1 resources: requests: - cpu: 100m - memory: 512Mi + cpu: 50m + memory: 256Mi limits: - cpu: 200m - memory: 2Gi + cpu: 100m + memory: 1Gi read: replicas: 0 backend: diff --git a/infra/values/prometheus-values.yaml b/infra/values/prometheus-values.yaml index da797e5..5fa1e1e 100644 --- a/infra/values/prometheus-values.yaml +++ b/infra/values/prometheus-values.yaml @@ -6,15 +6,15 @@ server: resources: requests: - cpu: 250m + cpu: 150m memory: 512Mi limits: - cpu: 500m + cpu: 300m memory: 1Gi enableLifecycle: true extraFlags: - - web.enable-remote-write-receiver + - web.enable-remote-write-receiver extraScrapeConfigs: | - job_name: kyverno diff --git a/infra/values/tempo-values.yaml b/infra/values/tempo-values.yaml index 9952fce..0ba785d 100644 --- a/infra/values/tempo-values.yaml +++ b/infra/values/tempo-values.yaml @@ -6,8 +6,8 @@ tempo: defaults: metrics_generator: processors: - - service-graphs - - span-metrics + - service-graphs + - span-metrics storage: trace: backend: local @@ -27,8 +27,8 @@ persistence: resources: requests: - cpu: 100m + cpu: 50m memory: 256Mi limits: - cpu: 200m + cpu: 100m memory: 512Mi From 875db6721ebac5571cd137eba7b2f23a6e492dd0 Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 26 Mar 2026 18:34:10 +0100 Subject: [PATCH 055/113] keycloak resource lowering --- infra/values/keycloak-values.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index 1aa6e02..fd58d00 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -30,10 +30,10 @@ metrics: resources: requests: cpu: 250m - memory: 512Mi + memory: 256Mi limits: - cpu: "1" - memory: 1Gi + cpu: 500m + memory: 512Mi postgresql: enabled: true From 5e8448cfd255d8a4edcdaac0b5e4aa99a27320b8 Mon Sep 17 00:00:00 2001 From: snothub Date: Fri, 27 Mar 2026 08:56:56 +0100 Subject: [PATCH 056/113] dashboards json --- infra/cert-manager-application.yaml | 4 +- infra/dashboards/dot-ai-logs.json | 148 ++ infra/dashboards/kustomization.yaml | 22 + infra/dashboards/opencost.json | 1510 ++++++++++++++ infra/dashboards/traefik-loki.json | 1192 +++++++++++ infra/dashboards/trivy.json | 931 +++++++++ infra/grafana-dashboards.yaml | 34 + infra/kyverno.yaml | 4 + infra/values/grafana-values.yaml | 2874 +-------------------------- 9 files changed, 3853 insertions(+), 2866 deletions(-) create mode 100644 infra/dashboards/dot-ai-logs.json create mode 100644 infra/dashboards/kustomization.yaml create mode 100644 infra/dashboards/opencost.json create mode 100644 infra/dashboards/traefik-loki.json create mode 100644 infra/dashboards/trivy.json create mode 100644 infra/grafana-dashboards.yaml diff --git a/infra/cert-manager-application.yaml b/infra/cert-manager-application.yaml index 8580416..3adddf6 100644 --- a/infra/cert-manager-application.yaml +++ b/infra/cert-manager-application.yaml @@ -48,10 +48,10 @@ spec: resources: requests: cpu: 50m - memory: 64Mi + memory: 128Mi limits: cpu: 100m - memory: 128Mi + memory: 256Mi # Service account serviceAccount: diff --git a/infra/dashboards/dot-ai-logs.json b/infra/dashboards/dot-ai-logs.json new file mode 100644 index 0000000..ae23276 --- /dev/null +++ b/infra/dashboards/dot-ai-logs.json @@ -0,0 +1,148 @@ +{ + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "title": "Log Volume", + "type": "timeseries", + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "datasource": { + "type": "loki", + "uid": "loki" + }, + "targets": [ + { + "expr": "sum(count_over_time({namespace=\"dot-ai\"} [1m])) by (pod)", + "legendFormat": "{{pod}}", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "bars", + "fillOpacity": 50, + "stacking": { + "mode": "normal" + } + } + }, + "overrides": [] + } + }, + { + "title": "Logs by Pod", + "type": "logs", + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 6 + }, + "datasource": { + "type": "loki", + "uid": "loki" + }, + "targets": [ + { + "expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | line_format `{{.pod}} |{{or .message .msg .log}}`", + "refId": "A" + } + ], + "options": { + "showTime": true, + "showLabels": false, + "showCommonLabels": false, + "wrapLogMessage": true, + "prettifyLogMessage": false, + "enableLogDetails": true, + "sortOrder": "Descending", + "dedupStrategy": "none", + "displayedFields": [ + "pod", + "level" + ] + } + }, + { + "title": "Errors & Warnings", + "type": "logs", + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 22 + }, + "datasource": { + "type": "loki", + "uid": "loki" + }, + "targets": [ + { + "expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | level=~`error|warn|warning|fatal|panic` | line_format `{{.pod}} |{{or .message .msg .log}}`", + "refId": "A" + } + ], + "options": { + "showTime": true, + "showLabels": false, + "showCommonLabels": false, + "wrapLogMessage": true, + "prettifyLogMessage": false, + "enableLogDetails": true, + "sortOrder": "Descending", + "dedupStrategy": "none", + "displayedFields": [ + "pod", + "level" + ] + } + } + ], + "schemaVersion": 39, + "tags": [ + "dot-ai", + "logs", + "loki" + ], + "templating": { + "list": [ + { + "name": "pod", + "type": "query", + "datasource": { + "type": "loki", + "uid": "loki" + }, + "query": { + "label": "pod", + "stream": "{namespace=\"dot-ai\"}", + "type": 1 + }, + "includeAll": true, + "multi": true, + "current": { + "selected": true, + "text": "All", + "value": "$__all" + } + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "title": "dot-ai Logs", + "uid": "dot-ai-logs" +} diff --git a/infra/dashboards/kustomization.yaml b/infra/dashboards/kustomization.yaml new file mode 100644 index 0000000..ecf5662 --- /dev/null +++ b/infra/dashboards/kustomization.yaml @@ -0,0 +1,22 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: monitoring + +generatorOptions: + disableNameSuffixHash: true + labels: + grafana_dashboard: "1" + +configMapGenerator: +- name: grafana-dashboard-trivy + files: + - trivy.json +- name: grafana-dashboard-traefik-loki + files: + - traefik-loki.json +- name: grafana-dashboard-dot-ai-logs + files: + - dot-ai-logs.json +- name: grafana-dashboard-opencost + files: + - opencost.json diff --git a/infra/dashboards/opencost.json b/infra/dashboards/opencost.json new file mode 100644 index 0000000..aa34b31 --- /dev/null +++ b/infra/dashboards/opencost.json @@ -0,0 +1,1510 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "UpCloud 4-node cluster cost monitoring powered by OpenCost with custom pricing.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "title": "Monthly Cost Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly CPU cost based on provisioned capacity and OpenCost custom pricing.", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "title": "Monthly CPU Cost", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100))", + "legendFormat": "CPU Cost", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly memory cost based on provisioned capacity and OpenCost custom pricing.", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "title": "Monthly Memory Cost", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100))", + "legendFormat": "Memory Cost", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly storage cost from PV hourly costs and local disk at $localStorageGBCost/GB.", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "title": "Monthly Storage Cost", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", + "legendFormat": "Storage Cost", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Total monthly infrastructure cost (CPU + Memory + Storage).", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "green", + "mode": "fixed" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "title": "Total Monthly Cost", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)) + sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)) + sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", + "legendFormat": "Total Cost", + "refId": "A" + } + ] + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 101, + "title": "Resource Utilization", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Percentage of total CPU capacity currently in use.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 60 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 6 + }, + "id": 5, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "title": "CPU Utilization", + "type": "gauge", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(rate(container_cpu_usage_seconds_total{image!=\"\"}[5m])) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) * 100", + "legendFormat": "CPU Utilization", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Percentage of total CPU capacity reserved by resource requests.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 60 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 6 + }, + "id": 6, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "title": "CPU Requests", + "type": "gauge", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) * 100", + "legendFormat": "CPU Requests", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Percentage of total memory capacity currently in use.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 60 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 6 + }, + "id": 7, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "title": "RAM Utilization", + "type": "gauge", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(container_memory_working_set_bytes{image!=\"\"}) / sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) * 100", + "legendFormat": "RAM Utilization", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Percentage of total memory capacity reserved by resource requests.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 60 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 6 + }, + "id": 8, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "title": "RAM Requests", + "type": "gauge", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\"}) / sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) * 100", + "legendFormat": "RAM Requests", + "refId": "A" + } + ] + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 102, + "title": "Cost Trends", + "type": "row", + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Hourly cost trend stacked by resource type.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "stacking": { + "group": "A", + "mode": "normal" + } + }, + "decimals": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 13 + }, + "id": 9, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "Total Cost Over Time", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node))", + "legendFormat": "CPU", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node))", + "legendFormat": "Memory", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost / 730 OR on() vector(0))", + "legendFormat": "Storage", + "refId": "C" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Hourly cost trend broken down by namespace.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "stacking": { + "group": "A", + "mode": "normal" + } + }, + "decimals": 4, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 13 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "Cost by Namespace Over Time", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(container_cpu_allocation{namespace=~\"$namespace\"} * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes{namespace=~\"$namespace\"} / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (namespace)", + "legendFormat": "{{namespace}}", + "refId": "A" + } + ] + } + ] + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 103, + "title": "Cost Breakdown", + "type": "row", + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly cost breakdown per node showing CPU and memory costs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "decimals": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Node" + }, + "properties": [ + { + "id": "custom.width", + "value": 250 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 11, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": true + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "CPU Cost" + } + ] + }, + "title": "Monthly Cost by Node", + "type": "table", + "transformations": [ + { + "id": "seriesToColumns", + "options": { + "byField": "node" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time 1": true, + "Time 2": true + }, + "renameByName": { + "Value #A": "CPU Cost", + "Value #B": "Memory Cost", + "node": "Node" + } + } + } + ], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730", + "format": "table", + "instant": true, + "legendFormat": "CPU Cost", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730", + "format": "table", + "instant": true, + "legendFormat": "Memory Cost", + "refId": "B" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly cost split by resource type.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "decimals": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 12, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "valueMode": "color" + }, + "title": "Monthly Cost by Resource", + "type": "bargauge", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730)", + "legendFormat": "CPU", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730)", + "legendFormat": "Memory", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", + "legendFormat": "Storage", + "refId": "C" + } + ] + } + ] + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 104, + "title": "Namespace & Container Costs", + "type": "row", + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Hourly cost distribution across namespaces.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "decimals": 4, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 15 + }, + "id": 13, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "title": "Hourly Cost by Namespace", + "type": "piechart", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(container_cpu_allocation * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (namespace)", + "legendFormat": "{{namespace}}", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Hourly cost distribution across containers in the selected namespace(s).", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "decimals": 4, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "currencyEUR" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 15 + }, + "id": 14, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "title": "Hourly Cost by Container", + "type": "piechart", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(container_cpu_allocation{namespace=~\"$namespace\"} * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes{namespace=~\"$namespace\"} / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (container)", + "legendFormat": "{{container}}", + "refId": "A" + } + ] + } + ] + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 105, + "title": "Cost Efficiency", + "type": "row", + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Compare CPU requests against actual usage to identify over-provisioning.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisLabel": "CPU Cores", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "stacking": { + "group": "A", + "mode": "none" + } + }, + "decimals": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Requested/" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 15, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "CPU Request vs Actual Usage by Namespace", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace=~\"$namespace\"}) by (namespace)", + "legendFormat": "{{namespace}} - Requested", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\", image!=\"\"}[5m])) by (namespace)", + "legendFormat": "{{namespace}} - Actual", + "refId": "B" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Compare memory requests against actual usage to identify over-provisioning.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisLabel": "Memory", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "showPoints": "never", + "stacking": { + "group": "A", + "mode": "none" + } + }, + "decimals": 2, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Requested/" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "custom.fillOpacity", + "value": 0 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 16, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "Memory Request vs Actual Usage by Namespace", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace=~\"$namespace\"}) by (namespace)", + "legendFormat": "{{namespace}} - Requested", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(container_memory_working_set_bytes{namespace=~\"$namespace\", image!=\"\"}) by (namespace)", + "legendFormat": "{{namespace}} - Actual", + "refId": "B" + } + ] + } + ] + } + ], + "refresh": "30s", + "schemaVersion": 38, + "tags": [ + "cost", + "opencost", + "upcloud" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": ".*", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(namespace)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "0", + "value": "0" + }, + "hide": 0, + "label": "Sustained Use Discount %", + "name": "useDiscount", + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], + "query": "0", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": false, + "text": "0.03", + "value": "0.03" + }, + "hide": 2, + "label": "Local Storage GB Cost", + "name": "localStorageGBCost", + "options": [ + { + "selected": true, + "text": "0.03", + "value": "0.03" + } + ], + "query": "0.03", + "skipUrlSync": false, + "type": "constant" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Cluster Cost Overview", + "uid": "JOUdHGZZz", + "version": 1, + "weekStart": "" +} diff --git a/infra/dashboards/traefik-loki.json b/infra/dashboards/traefik-loki.json new file mode 100644 index 0000000..113ddfe --- /dev/null +++ b/infra/dashboards/traefik-loki.json @@ -0,0 +1,1192 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Loki version 2 showcase using JSON Traefik access logs.", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 13713, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 32, + "interval": "", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
\n \n Traefik Dashboard\n
", + "mode": "html" + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "loki_build_info", + "format": "table", + "instant": false, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 2 + }, + "id": 4, + "interval": "1m", + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "code", + "expr": "sum(count_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" [$__interval]))", + "legendFormat": "", + "queryType": "range", + "refId": "A" + } + ], + "title": "Total requests ", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "light-blue", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 6, + "y": 2 + }, + "id": 5, + "interval": "30s", + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "code", + "expr": "sum by (OriginStatus) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))", + "legendFormat": "HTTP Status: {{OriginStatus}}", + "queryType": "range", + "refId": "A" + } + ], + "title": "Requests per status code", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 15, + "y": 2 + }, + "id": 19, + "interval": "5m", + "maxDataPoints": 1, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": " sum(rate({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval])) / (sum(rate({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | __error__=\"\"[$__interval])) / 100)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "% of 4/5xx ", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsNull", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "hidden" + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 19, + "y": 2 + }, + "id": 36, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": " sum by (OriginStatus,ServiceName) (count_over_time({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval]))", + "legendFormat": " {{ServiceName}} / {{OriginStatus}} ", + "refId": "A" + } + ], + "title": " 4/5xx Services", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 6 + }, + "id": 22, + "interval": "5m", + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "count(sum by (ClientHost) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval])))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Users right now", + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 9, + "x": 15, + "y": 6 + }, + "id": 8, + "interval": "1m", + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "sum_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | OriginStatus=200 | unwrap DownstreamContentSize | __error__=\"\" [$__interval])", + "legendFormat": "Bytes sent", + "refId": "A" + } + ], + "title": "Total Bytes Sent", + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "sum" + ] + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "Total": "Bytes Sent" + } + } + } + ], + "transparent": true, + "type": "stat" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "decimals": 0, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 10 + }, + "id": 33, + "interval": "5m", + "maxDataPoints": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "7.3.4", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "sum by (RouterName) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))", + "legendFormat": "{{RouterName}}", + "refId": "A" + } + ], + "title": "Requests Route", + "transparent": true, + "type": "piechart" + }, + { + "datasource": { + "type": "tempo", + "uid": "tempo" + }, + "gridPos": { + "h": 17, + "w": 16, + "x": 8, + "y": 10 + }, + "id": 37, + "options": { + "edges": {}, + "nodes": {} + }, + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "tempo" + }, + "limit": 20, + "queryType": "serviceMap", + "refId": "A", + "tableType": "traces" + } + ], + "title": "Service graph", + "type": "nodeGraph" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 90, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "red", + "value": 0.3 + } + ] + }, + "unit": "ns" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "95th percentile" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 16, + "interval": "30s", + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "quantile_over_time(0.95,{namespace=\"traefik-system\"} |= \"RequestProtocol\"| json | unwrap Duration | __error__=\"\" [$__interval]) by (ServiceName)", + "hide": false, + "legendFormat": " {{ ServiceName }}", + "refId": "C" + } + ], + "title": "95th percentile of Request Time", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 90, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "red", + "value": 0.3 + } + ] + }, + "unit": "ns" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "95th percentile" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 27 + }, + "id": 34, + "interval": "30s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "max by (ServiceName) (max_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" |json | unwrap Duration | __error__=\"\" [$__interval]))", + "hide": false, + "legendFormat": "{{ ServiceName}}", + "refId": "D" + } + ], + "title": "Max Age of Request Time", + "transparent": true, + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "gridPos": { + "h": 16, + "w": 16, + "x": 8, + "y": 27 + }, + "id": 11, + "interval": "", + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\" | json | line_format \"{{.OriginStatus}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} -> {{.ServiceAddr}}\"", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Logs", + "transparent": true, + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 90, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "transparent", + "value": null + }, + { + "color": "red", + "value": 0.3 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "95th percentile" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max latency" + }, + "properties": [ + { + "id": "custom.fillOpacity", + "value": 30 + }, + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 36 + }, + "id": 35, + "interval": "30s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.3.6", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "expr": "sum by (ServiceName) (sum_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" |json | unwrap RequestContentSize | __error__=\"\" [$__interval]))", + "hide": false, + "legendFormat": "{{ ServiceName}}", + "refId": "D" + } + ], + "title": "Requests Size", + "transparent": true, + "type": "timeseries" + } + ], + "refresh": false, + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-12h", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Traefik Via Loki", + "uid": "fJarCeaGk", + "version": 1, + "weekStart": "" +} diff --git a/infra/dashboards/trivy.json b/infra/dashboards/trivy.json new file mode 100644 index 0000000..79d80eb --- /dev/null +++ b/infra/dashboards/trivy.json @@ -0,0 +1,931 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Monitore Trivy Image Vulnerabilities.\r\n\r\nBased on https://grafana.com/grafana/dashboards/17080-trivy-image-vulnerabilities/ and https://grafana.com/grafana/dashboards/16652-trivy-operator-reports/.", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 17214, + "graphTooltip": 1, + "id": 8, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 43, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Image Vulnerabilities", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "purple", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 52, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "text": {}, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{severity=\"Unknown\", namespace=~\"$namespace\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "UNKNOWN", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "blue", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 60, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{severity=\"Low\", namespace=~\"$namespace\"})", + "format": "time_series", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "LOW", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 49, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{severity=\"Medium\", namespace=~\"$namespace\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "MEDIUM", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 50, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{severity=\"High\", namespace=~\"$namespace\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "HIGH", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 51, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{severity=\"Critical\", namespace=~\"$namespace\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "CRITICAL", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "text", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 39, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"})", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "TOTAL", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "blue", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "kube-system" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 4 + }, + "id": 58, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.5.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"}) by (namespace)", + "instant": false, + "interval": "", + "legendFormat": "{{namespace}}", + "range": true, + "refId": "A" + } + ], + "title": "Image Vulnerabilities by namespace", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "blue", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 4 + }, + "id": 61, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "8.5.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"}) by (severity)", + "instant": false, + "interval": "", + "legendFormat": "{{severity}}", + "range": true, + "refId": "A" + } + ], + "title": "Image Vulnerabilities by severity", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "transparent", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "mode": "basic", + "type": "color-background" + }, + "filterable": true, + "inspect": false + }, + "links": [ + { + "targetBlank": true, + "title": "Go to CVE", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=${__data.fields.vuln_id}" + } + ], + "mappings": [ + { + "options": { + "CRITICAL": { + "color": "dark-red", + "index": 0 + }, + "HIGH": { + "color": "yellow", + "index": 1 + }, + "LOW": { + "color": "dark-blue", + "index": 3 + }, + "MEDIUM": { + "color": "dark-orange", + "index": 2 + }, + "UNKNOWN": { + "color": "super-light-blue", + "index": 4 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "namespace" + }, + "properties": [ + { + "id": "custom.width" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "pod" + }, + "properties": [ + { + "id": "custom.width" + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 67, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 2, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "severity" + } + ] + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"}) without (namespace, last_modified_date, instance, job, endpoint, service, container, image_digest, resource, resource_name)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Image by CVE", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "instance": true, + "job": true + }, + "indexByName": { + "Time": 10, + "Value": 9, + "image_digest": 7, + "image_registry": 4, + "image_repository": 5, + "image_tag": 6, + "name": 8, + "namespace": 2, + "pod": 3, + "severity": 0, + "vuln_id": 1 + }, + "renameByName": { + "image_digest": "", + "name": "source workload" + } + } + } + ], + "type": "table" + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": [ + "Prometheus", + "Addons", + "Trivy" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "kube-system" + ], + "value": [ + "kube-system" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(trivy_vulnerability_id{job=\"trivy-operator\"}, namespace)", + "hide": 0, + "includeAll": false, + "label": "namespace", + "multi": true, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(trivy_vulnerability_id{job=\"trivy-operator\"}, namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timeRangeUpdatedDuringEditOrView": false, + "timepicker": {}, + "timezone": "", + "title": "Trivy Operator / Image Vulnerability", + "uid": "trivy_starboard_operator", + "version": 7, + "weekStart": "" +} diff --git a/infra/grafana-dashboards.yaml b/infra/grafana-dashboards.yaml new file mode 100644 index 0000000..782d8bf --- /dev/null +++ b/infra/grafana-dashboards.yaml @@ -0,0 +1,34 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: grafana-dashboards + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "2" + labels: + app.kubernetes.io/name: grafana-dashboards + app.kubernetes.io/part-of: monitoring + app.kubernetes.io/managed-by: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: infra/dashboards + + destination: + server: https://kubernetes.default.svc + namespace: monitoring + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - Validate=true + - ServerSideApply=true diff --git a/infra/kyverno.yaml b/infra/kyverno.yaml index 77e3ac8..ebc2aa5 100644 --- a/infra/kyverno.yaml +++ b/infra/kyverno.yaml @@ -39,6 +39,10 @@ spec: targetRevision: v3.7.0 # Update to latest stable version helm: releaseName: kyverno + valuesObject: + grafana: + enabled: true + namespace: monitoring destination: server: https://kubernetes.default.svc diff --git a/infra/values/grafana-values.yaml b/infra/values/grafana-values.yaml index b8cd033..d6acbcf 100644 --- a/infra/values/grafana-values.yaml +++ b/infra/values/grafana-values.yaml @@ -48,6 +48,16 @@ datasources: enabled: true serviceMap: datasourceUid: Prometheus + +sidecar: + dashboards: + enabled: true + label: grafana_dashboard + labelValue: "1" + folder: /tmp/dashboards + provider: + foldersFromFilesStructure: false + dashboardProviders: dashboardproviders.yaml: apiVersion: 1 @@ -66,2867 +76,3 @@ dashboards: gnetId: 15758 revision: 1 datasource: Prometheus - trivy: - json: | - { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { - "limit": 100, - "matchAny": false, - "tags": [], - "type": "dashboard" - }, - "type": "dashboard" - } - ] - }, - "description": "Monitore Trivy Image Vulnerabilities.\r\n\r\nBased on https://grafana.com/grafana/dashboards/17080-trivy-image-vulnerabilities/ and https://grafana.com/grafana/dashboards/16652-trivy-operator-reports/.", - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": 17214, - "graphTooltip": 1, - "id": 8, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 43, - "panels": [], - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "refId": "A" - } - ], - "title": "Image Vulnerabilities", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "purple", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 0, - "y": 1 - }, - "id": 52, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{severity=\"Unknown\", namespace=~\"$namespace\"})", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "UNKNOWN", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "blue", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 4, - "y": 1 - }, - "id": 60, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{severity=\"Low\", namespace=~\"$namespace\"})", - "format": "time_series", - "hide": false, - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "LOW", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "yellow", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 8, - "y": 1 - }, - "id": 49, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{severity=\"Medium\", namespace=~\"$namespace\"})", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "MEDIUM", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "orange", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 12, - "y": 1 - }, - "id": 50, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{severity=\"High\", namespace=~\"$namespace\"})", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "HIGH", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 16, - "y": 1 - }, - "id": 51, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{severity=\"Critical\", namespace=~\"$namespace\"})", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "CRITICAL", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "text", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 20, - "y": 1 - }, - "id": 39, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"})", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "TOTAL", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineStyle": { - "fill": "solid" - }, - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "blue", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "kube-system" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 4 - }, - "id": 58, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.5.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"}) by (namespace)", - "instant": false, - "interval": "", - "legendFormat": "{{namespace}}", - "range": true, - "refId": "A" - } - ], - "title": "Image Vulnerabilities by namespace", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 15, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 2, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "blue", - "value": 1 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 4 - }, - "id": 61, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "desc" - } - }, - "pluginVersion": "8.5.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"}) by (severity)", - "instant": false, - "interval": "", - "legendFormat": "{{severity}}", - "range": true, - "refId": "A" - } - ], - "title": "Image Vulnerabilities by severity", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "fixedColor": "transparent", - "mode": "fixed" - }, - "custom": { - "align": "left", - "cellOptions": { - "mode": "basic", - "type": "color-background" - }, - "filterable": true, - "inspect": false - }, - "links": [ - { - "targetBlank": true, - "title": "Go to CVE", - "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=${__data.fields.vuln_id}" - } - ], - "mappings": [ - { - "options": { - "CRITICAL": { - "color": "dark-red", - "index": 0 - }, - "HIGH": { - "color": "yellow", - "index": 1 - }, - "LOW": { - "color": "dark-blue", - "index": 3 - }, - "MEDIUM": { - "color": "dark-orange", - "index": 2 - }, - "UNKNOWN": { - "color": "super-light-blue", - "index": 4 - } - }, - "type": "value" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "namespace" - }, - "properties": [ - { - "id": "custom.width" - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "pod" - }, - "properties": [ - { - "id": "custom.width" - } - ] - } - ] - }, - "gridPos": { - "h": 10, - "w": 24, - "x": 0, - "y": 12 - }, - "id": 67, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "frameIndex": 2, - "showHeader": true, - "sortBy": [ - { - "desc": false, - "displayName": "severity" - } - ] - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(trivy_vulnerability_id{namespace=~\"$namespace\"}) without (namespace, last_modified_date, instance, job, endpoint, service, container, image_digest, resource, resource_name)", - "format": "table", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Image by CVE", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "Value": true, - "instance": true, - "job": true - }, - "indexByName": { - "Time": 10, - "Value": 9, - "image_digest": 7, - "image_registry": 4, - "image_repository": 5, - "image_tag": 6, - "name": 8, - "namespace": 2, - "pod": 3, - "severity": 0, - "vuln_id": 1 - }, - "renameByName": { - "image_digest": "", - "name": "source workload" - } - } - } - ], - "type": "table" - } - ], - "refresh": "30s", - "schemaVersion": 39, - "tags": [ - "Prometheus", - "Addons", - "Trivy" - ], - "templating": { - "list": [ - { - "current": { - "selected": true, - "text": [ - "kube-system" - ], - "value": [ - "kube-system" - ] - }, - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "definition": "label_values(trivy_vulnerability_id{job=\"trivy-operator\"}, namespace)", - "hide": 0, - "includeAll": false, - "label": "namespace", - "multi": true, - "name": "namespace", - "options": [], - "query": { - "query": "label_values(trivy_vulnerability_id{job=\"trivy-operator\"}, namespace)", - "refId": "StandardVariableQuery" - }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "type": "query" - } - ] - }, - "time": { - "from": "now-3h", - "to": "now" - }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": {}, - "timezone": "", - "title": "Trivy Operator / Image Vulnerability", - "uid": "trivy_starboard_operator", - "version": 7, - "weekStart": "" - } - traefik-loki: - json: | - { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "description": "Loki version 2 showcase using JSON Traefik access logs.", - "editable": true, - "fiscalYearStartMonth": 0, - "gnetId": 13713, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "gridPos": { - "h": 2, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 32, - "interval": "", - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "
\n \n Traefik Dashboard\n
", - "mode": "html" - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "loki_build_info", - "format": "table", - "instant": false, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "transparent": true, - "type": "text" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "purple", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 2 - }, - "id": 4, - "interval": "1m", - "options": { - "colorMode": "background", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "editorMode": "code", - "expr": "sum(count_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" [$__interval]))", - "legendFormat": "", - "queryType": "range", - "refId": "A" - } - ], - "title": "Total requests ", - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "light-blue", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 9, - "x": 6, - "y": 2 - }, - "id": 5, - "interval": "30s", - "options": { - "colorMode": "background", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "editorMode": "code", - "expr": "sum by (OriginStatus) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))", - "legendFormat": "HTTP Status: {{OriginStatus}}", - "queryType": "range", - "refId": "A" - } - ], - "title": "Requests per status code", - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "max": 100, - "min": 0, - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "purple", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 4, - "x": 15, - "y": 2 - }, - "id": 19, - "interval": "5m", - "maxDataPoints": 1, - "options": { - "colorMode": "background", - "graphMode": "none", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": " sum(rate({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval])) / (sum(rate({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | __error__=\"\"[$__interval])) / 100)", - "legendFormat": "", - "refId": "A" - } - ], - "title": "% of 4/5xx ", - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "decimals": 0, - "mappings": [], - "min": 0, - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "purple", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [ - { - "matcher": { - "id": "byValue", - "options": { - "op": "gte", - "reducer": "allIsZero", - "value": 0 - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": true, - "tooltip": true, - "viz": false - } - } - ] - }, - { - "matcher": { - "id": "byValue", - "options": { - "op": "gte", - "reducer": "allIsNull", - "value": 0 - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": true, - "tooltip": true, - "viz": false - } - } - ] - }, - { - "matcher": { - "id": "byType", - "options": "time" - }, - "properties": [ - { - "id": "custom.axisPlacement", - "value": "hidden" - } - ] - } - ] - }, - "gridPos": { - "h": 4, - "w": 5, - "x": 19, - "y": 2 - }, - "id": 36, - "interval": "1m", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "7.3.6", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": " sum by (OriginStatus,ServiceName) (count_over_time({namespace=\"traefik-system\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval]))", - "legendFormat": " {{ServiceName}} / {{OriginStatus}} ", - "refId": "A" - } - ], - "title": " 4/5xx Services", - "transparent": true, - "type": "timeseries" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "purple", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 6, - "x": 0, - "y": 6 - }, - "id": 22, - "interval": "5m", - "options": { - "colorMode": "background", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "count(sum by (ClientHost) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval])))", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Users right now", - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "purple", - "value": null - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 9, - "x": 15, - "y": 6 - }, - "id": 8, - "interval": "1m", - "options": { - "colorMode": "background", - "graphMode": "none", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "sum" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "value", - "wideLayout": true - }, - "pluginVersion": "11.0.0", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "sum_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | OriginStatus=200 | unwrap DownstreamContentSize | __error__=\"\" [$__interval])", - "legendFormat": "Bytes sent", - "refId": "A" - } - ], - "title": "Total Bytes Sent", - "transformations": [ - { - "id": "reduce", - "options": { - "reducers": [ - "sum" - ] - } - }, - { - "id": "organize", - "options": { - "excludeByName": {}, - "indexByName": {}, - "renameByName": { - "Total": "Bytes Sent" - } - } - } - ], - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - } - }, - "decimals": 0, - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 8, - "x": 0, - "y": 10 - }, - "id": 33, - "interval": "5m", - "maxDataPoints": 3, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true, - "values": [ - "value", - "percent" - ] - }, - "pieType": "pie", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "tooltip": { - "maxHeight": 600, - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "7.3.4", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "sum by (RouterName) (count_over_time({namespace=\"traefik-system\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))", - "legendFormat": "{{RouterName}}", - "refId": "A" - } - ], - "title": "Requests Route", - "transparent": true, - "type": "piechart" - }, - { - "datasource": { - "type": "tempo", - "uid": "tempo" - }, - "gridPos": { - "h": 17, - "w": 16, - "x": 8, - "y": 10 - }, - "id": 37, - "options": { - "edges": {}, - "nodes": {} - }, - "targets": [ - { - "datasource": { - "type": "tempo", - "uid": "tempo" - }, - "limit": 20, - "queryType": "serviceMap", - "refId": "A", - "tableType": "traces" - } - ], - "title": "Service graph", - "type": "nodeGraph" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 90, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "line+area" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "transparent", - "value": null - }, - { - "color": "red", - "value": 0.3 - } - ] - }, - "unit": "ns" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "95th percentile" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "max latency" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "max latency" - }, - "properties": [ - { - "id": "custom.fillOpacity", - "value": 30 - }, - { - "id": "custom.lineStyle", - "value": { - "dash": [ - 10, - 10 - ], - "fill": "dash" - } - } - ] - } - ] - }, - "gridPos": { - "h": 9, - "w": 8, - "x": 0, - "y": 18 - }, - "id": 16, - "interval": "30s", - "options": { - "legend": { - "calcs": [ - "max" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "7.3.6", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "quantile_over_time(0.95,{namespace=\"traefik-system\"} |= \"RequestProtocol\"| json | unwrap Duration | __error__=\"\" [$__interval]) by (ServiceName)", - "hide": false, - "legendFormat": " {{ ServiceName }}", - "refId": "C" - } - ], - "title": "95th percentile of Request Time", - "transparent": true, - "type": "timeseries" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 90, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "line+area" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "transparent", - "value": null - }, - { - "color": "red", - "value": 0.3 - } - ] - }, - "unit": "ns" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "95th percentile" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "max latency" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "max latency" - }, - "properties": [ - { - "id": "custom.fillOpacity", - "value": 30 - }, - { - "id": "custom.lineStyle", - "value": { - "dash": [ - 10, - 10 - ], - "fill": "dash" - } - } - ] - } - ] - }, - "gridPos": { - "h": 9, - "w": 8, - "x": 0, - "y": 27 - }, - "id": 34, - "interval": "30s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "7.3.6", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "max by (ServiceName) (max_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" |json | unwrap Duration | __error__=\"\" [$__interval]))", - "hide": false, - "legendFormat": "{{ ServiceName}}", - "refId": "D" - } - ], - "title": "Max Age of Request Time", - "transparent": true, - "type": "timeseries" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "gridPos": { - "h": 16, - "w": 16, - "x": 8, - "y": 27 - }, - "id": 11, - "interval": "", - "options": { - "dedupStrategy": "none", - "enableLogDetails": true, - "prettifyLogMessage": false, - "showCommonLabels": false, - "showLabels": false, - "showTime": true, - "sortOrder": "Descending", - "wrapLogMessage": false - }, - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "{namespace=\"traefik-system\"} |= \"RequestProtocol\" | json | line_format \"{{.OriginStatus}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} -> {{.ServiceAddr}}\"", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Logs", - "transparent": true, - "type": "logs" - }, - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 90, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "line+area" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "transparent", - "value": null - }, - { - "color": "red", - "value": 0.3 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "95th percentile" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "max latency" - }, - "properties": [ - { - "id": "color", - "value": { - "fixedColor": "super-light-blue", - "mode": "fixed" - } - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "max latency" - }, - "properties": [ - { - "id": "custom.fillOpacity", - "value": 30 - }, - { - "id": "custom.lineStyle", - "value": { - "dash": [ - 10, - 10 - ], - "fill": "dash" - } - } - ] - } - ] - }, - "gridPos": { - "h": 9, - "w": 8, - "x": 0, - "y": 36 - }, - "id": 35, - "interval": "30s", - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "maxHeight": 600, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "7.3.6", - "targets": [ - { - "datasource": { - "type": "loki", - "uid": "loki" - }, - "expr": "sum by (ServiceName) (sum_over_time({namespace=\"traefik-system\"} |= \"RequestProtocol\" |json | unwrap RequestContentSize | __error__=\"\" [$__interval]))", - "hide": false, - "legendFormat": "{{ ServiceName}}", - "refId": "D" - } - ], - "title": "Requests Size", - "transparent": true, - "type": "timeseries" - } - ], - "refresh": false, - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-12h", - "to": "now" - }, - "timeRangeUpdatedDuringEditOrView": false, - "timepicker": { - "refresh_intervals": [ - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "", - "title": "Traefik Via Loki", - "uid": "fJarCeaGk", - "version": 1, - "weekStart": "" - } - dot-ai-logs: - json: | - { - "annotations": { "list": [] }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "title": "Log Volume", - "type": "timeseries", - "gridPos": { "h": 6, "w": 24, "x": 0, "y": 0 }, - "datasource": { "type": "loki", "uid": "loki" }, - "targets": [ - { - "expr": "sum(count_over_time({namespace=\"dot-ai\"} [1m])) by (pod)", - "legendFormat": "{{pod}}", - "refId": "A" - } - ], - "fieldConfig": { - "defaults": { - "custom": { - "drawStyle": "bars", - "fillOpacity": 50, - "stacking": { "mode": "normal" } - } - }, - "overrides": [] - } - }, - { - "title": "Logs by Pod", - "type": "logs", - "gridPos": { "h": 16, "w": 24, "x": 0, "y": 6 }, - "datasource": { "type": "loki", "uid": "loki" }, - "targets": [ - { - "expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | line_format `{{.pod}} |{{or .message .msg .log}}`", - "refId": "A" - } - ], - "options": { - "showTime": true, - "showLabels": false, - "showCommonLabels": false, - "wrapLogMessage": true, - "prettifyLogMessage": false, - "enableLogDetails": true, - "sortOrder": "Descending", - "dedupStrategy": "none", - "displayedFields": ["pod", "level"] - } - }, - { - "title": "Errors & Warnings", - "type": "logs", - "gridPos": { "h": 10, "w": 24, "x": 0, "y": 22 }, - "datasource": { "type": "loki", "uid": "loki" }, - "targets": [ - { - "expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | level=~`error|warn|warning|fatal|panic` | line_format `{{.pod}} |{{or .message .msg .log}}`", - "refId": "A" - } - ], - "options": { - "showTime": true, - "showLabels": false, - "showCommonLabels": false, - "wrapLogMessage": true, - "prettifyLogMessage": false, - "enableLogDetails": true, - "sortOrder": "Descending", - "dedupStrategy": "none", - "displayedFields": ["pod", "level"] - } - } - ], - "schemaVersion": 39, - "tags": ["dot-ai", "logs", "loki"], - "templating": { - "list": [ - { - "name": "pod", - "type": "query", - "datasource": { "type": "loki", "uid": "loki" }, - "query": { "label": "pod", "stream": "{namespace=\"dot-ai\"}", "type": 1 }, - "includeAll": true, - "multi": true, - "current": { "selected": true, "text": "All", "value": "$__all" } - } - ] - }, - "time": { "from": "now-1h", "to": "now" }, - "title": "dot-ai Logs", - "uid": "dot-ai-logs" - } - opencost: - json: | - { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { "type": "datasource", "uid": "grafana" }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "target": { "limit": 100, "matchAny": false, "tags": [], "type": "dashboard" }, - "type": "dashboard" - } - ] - }, - "description": "UpCloud 4-node cluster cost monitoring powered by OpenCost with custom pricing.", - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 1, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, - "id": 100, - "title": "Monthly Cost Overview", - "type": "row" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Monthly CPU cost based on provisioned capacity and OpenCost custom pricing.", - "fieldConfig": { - "defaults": { - "color": { "fixedColor": "green", "mode": "fixed" }, - "decimals": 2, - "mappings": [], - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 4, "w": 6, "x": 0, "y": 1 }, - "id": 1, - "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, - "title": "Monthly CPU Cost", - "type": "stat", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100))", - "legendFormat": "CPU Cost", - "refId": "A" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Monthly memory cost based on provisioned capacity and OpenCost custom pricing.", - "fieldConfig": { - "defaults": { - "color": { "fixedColor": "green", "mode": "fixed" }, - "decimals": 2, - "mappings": [], - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 4, "w": 6, "x": 6, "y": 1 }, - "id": 2, - "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, - "title": "Monthly Memory Cost", - "type": "stat", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100))", - "legendFormat": "Memory Cost", - "refId": "A" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Monthly storage cost from PV hourly costs and local disk at $localStorageGBCost/GB.", - "fieldConfig": { - "defaults": { - "color": { "fixedColor": "green", "mode": "fixed" }, - "decimals": 2, - "mappings": [], - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 4, "w": 6, "x": 12, "y": 1 }, - "id": 3, - "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, - "title": "Monthly Storage Cost", - "type": "stat", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", - "legendFormat": "Storage Cost", - "refId": "A" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Total monthly infrastructure cost (CPU + Memory + Storage).", - "fieldConfig": { - "defaults": { - "color": { "fixedColor": "green", "mode": "fixed" }, - "decimals": 2, - "mappings": [], - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 4, "w": 6, "x": 18, "y": 1 }, - "id": 4, - "options": { "colorMode": "value", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "textMode": "auto" }, - "title": "Total Monthly Cost", - "type": "stat", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)) + sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)) + sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", - "legendFormat": "Total Cost", - "refId": "A" - } - ] - }, - { - "collapsed": false, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 }, - "id": 101, - "title": "Resource Utilization", - "type": "row" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Percentage of total CPU capacity currently in use.", - "fieldConfig": { - "defaults": { - "color": { "mode": "thresholds" }, - "decimals": 1, - "max": 100, - "min": 0, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { "h": 6, "w": 6, "x": 0, "y": 6 }, - "id": 5, - "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, - "title": "CPU Utilization", - "type": "gauge", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(rate(container_cpu_usage_seconds_total{image!=\"\"}[5m])) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) * 100", - "legendFormat": "CPU Utilization", - "refId": "A" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Percentage of total CPU capacity reserved by resource requests.", - "fieldConfig": { - "defaults": { - "color": { "mode": "thresholds" }, - "decimals": 1, - "max": 100, - "min": 0, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { "h": 6, "w": 6, "x": 6, "y": 6 }, - "id": 6, - "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, - "title": "CPU Requests", - "type": "gauge", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) * 100", - "legendFormat": "CPU Requests", - "refId": "A" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Percentage of total memory capacity currently in use.", - "fieldConfig": { - "defaults": { - "color": { "mode": "thresholds" }, - "decimals": 1, - "max": 100, - "min": 0, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { "h": 6, "w": 6, "x": 12, "y": 6 }, - "id": 7, - "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, - "title": "RAM Utilization", - "type": "gauge", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(container_memory_working_set_bytes{image!=\"\"}) / sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) * 100", - "legendFormat": "RAM Utilization", - "refId": "A" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Percentage of total memory capacity reserved by resource requests.", - "fieldConfig": { - "defaults": { - "color": { "mode": "thresholds" }, - "decimals": 1, - "max": 100, - "min": 0, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 60 }, { "color": "red", "value": 80 }] }, - "unit": "percent" - }, - "overrides": [] - }, - "gridPos": { "h": 6, "w": 6, "x": 18, "y": 6 }, - "id": 8, - "options": { "minVizHeight": 75, "minVizWidth": 75, "orientation": "auto", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showThresholdLabels": false, "showThresholdMarkers": true }, - "title": "RAM Requests", - "type": "gauge", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\"}) / sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) * 100", - "legendFormat": "RAM Requests", - "refId": "A" - } - ] - }, - { - "collapsed": true, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 12 }, - "id": 102, - "title": "Cost Trends", - "type": "row", - "panels": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Hourly cost trend stacked by resource type.", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 30, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "normal" } }, - "decimals": 2, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 }, - "id": 9, - "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, - "title": "Total Cost Over Time", - "type": "timeseries", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node))", - "legendFormat": "CPU", - "refId": "A" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node))", - "legendFormat": "Memory", - "refId": "B" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost / 730 OR on() vector(0))", - "legendFormat": "Storage", - "refId": "C" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Hourly cost trend broken down by namespace.", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 15, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "normal" } }, - "decimals": 4, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 }, - "id": 10, - "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, - "title": "Cost by Namespace Over Time", - "type": "timeseries", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(container_cpu_allocation{namespace=~\"$namespace\"} * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes{namespace=~\"$namespace\"} / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (namespace)", - "legendFormat": "{{namespace}}", - "refId": "A" - } - ] - } - ] - }, - { - "collapsed": true, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 13 }, - "id": 103, - "title": "Cost Breakdown", - "type": "row", - "panels": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Monthly cost breakdown per node showing CPU and memory costs.", - "fieldConfig": { - "defaults": { - "color": { "mode": "thresholds" }, - "custom": { "align": "auto", "cellOptions": { "type": "auto" }, "inspect": false }, - "decimals": 2, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [ - { "matcher": { "id": "byName", "options": "Node" }, "properties": [{ "id": "custom.width", "value": 250 }] } - ] - }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 14 }, - "id": 11, - "options": { "cellHeight": "sm", "footer": { "countRows": false, "enablePagination": false, "fields": "", "reducer": ["sum"], "show": true }, "showHeader": true, "sortBy": [{ "desc": true, "displayName": "CPU Cost" }] }, - "title": "Monthly Cost by Node", - "type": "table", - "transformations": [ - { "id": "seriesToColumns", "options": { "byField": "node" } }, - { "id": "organize", "options": { "excludeByName": { "Time 1": true, "Time 2": true }, "renameByName": { "Value #A": "CPU Cost", "Value #B": "Memory Cost", "node": "Node" } } } - ], - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730", - "format": "table", - "instant": true, - "legendFormat": "CPU Cost", - "refId": "A" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730", - "format": "table", - "instant": true, - "legendFormat": "Memory Cost", - "refId": "B" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Monthly cost split by resource type.", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "decimals": 2, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 14 }, - "id": 12, - "options": { "displayMode": "gradient", "minVizHeight": 10, "minVizWidth": 0, "namePlacement": "auto", "orientation": "horizontal", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "showUnfilled": true, "valueMode": "color" }, - "title": "Monthly Cost by Resource", - "type": "bargauge", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730)", - "legendFormat": "CPU", - "refId": "A" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024/1024/1024 * avg(node_ram_hourly_cost) by (node) * 730)", - "legendFormat": "Memory", - "refId": "B" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024/1024/1024) + (sum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024/1024/1024) * $localStorageGBCost OR on() vector(0))", - "legendFormat": "Storage", - "refId": "C" - } - ] - } - ] - }, - { - "collapsed": true, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 14 }, - "id": 104, - "title": "Namespace & Container Costs", - "type": "row", - "panels": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Hourly cost distribution across namespaces.", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "decimals": 4, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 15 }, - "id": 13, - "options": { "displayLabels": ["name", "percent"], "legend": { "displayMode": "table", "placement": "right", "values": ["value", "percent"] }, "pieType": "donut", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "tooltip": { "mode": "single", "sort": "none" } }, - "title": "Hourly Cost by Namespace", - "type": "piechart", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(container_cpu_allocation * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (namespace)", - "legendFormat": "{{namespace}}", - "refId": "A" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Hourly cost distribution across containers in the selected namespace(s).", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "decimals": 4, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "currencyEUR" - }, - "overrides": [] - }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 15 }, - "id": 14, - "options": { "displayLabels": ["name", "percent"], "legend": { "displayMode": "table", "placement": "right", "values": ["value", "percent"] }, "pieType": "donut", "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, "tooltip": { "mode": "single", "sort": "none" } }, - "title": "Hourly Cost by Container", - "type": "piechart", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(container_cpu_allocation{namespace=~\"$namespace\"} * on(node) group_left() node_cpu_hourly_cost + container_memory_allocation_bytes{namespace=~\"$namespace\"} / 1024/1024/1024 * on(node) group_left() node_ram_hourly_cost) by (container)", - "legendFormat": "{{container}}", - "refId": "A" - } - ] - } - ] - }, - { - "collapsed": true, - "gridPos": { "h": 1, "w": 24, "x": 0, "y": 15 }, - "id": 105, - "title": "Cost Efficiency", - "type": "row", - "panels": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Compare CPU requests against actual usage to identify over-provisioning.", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "CPU Cores", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "none" } }, - "decimals": 2, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "short" - }, - "overrides": [ - { - "matcher": { "id": "byRegexp", "options": "/Requested/" }, - "properties": [{ "id": "custom.lineStyle", "value": { "dash": [10, 10], "fill": "dash" } }, { "id": "custom.fillOpacity", "value": 0 }] - } - ] - }, - "gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 }, - "id": 15, - "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, - "title": "CPU Request vs Actual Usage by Namespace", - "type": "timeseries", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace=~\"$namespace\"}) by (namespace)", - "legendFormat": "{{namespace}} - Requested", - "refId": "A" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\", image!=\"\"}[5m])) by (namespace)", - "legendFormat": "{{namespace}} - Actual", - "refId": "B" - } - ] - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "description": "Compare memory requests against actual usage to identify over-provisioning.", - "fieldConfig": { - "defaults": { - "color": { "mode": "palette-classic" }, - "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisLabel": "Memory", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 5, "showPoints": "never", "stacking": { "group": "A", "mode": "none" } }, - "decimals": 2, - "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, - "unit": "bytes" - }, - "overrides": [ - { - "matcher": { "id": "byRegexp", "options": "/Requested/" }, - "properties": [{ "id": "custom.lineStyle", "value": { "dash": [10, 10], "fill": "dash" } }, { "id": "custom.fillOpacity", "value": 0 }] - } - ] - }, - "gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 }, - "id": 16, - "options": { "legend": { "calcs": ["mean", "lastNotNull"], "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "multi", "sort": "desc" } }, - "title": "Memory Request vs Actual Usage by Namespace", - "type": "timeseries", - "targets": [ - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace=~\"$namespace\"}) by (namespace)", - "legendFormat": "{{namespace}} - Requested", - "refId": "A" - }, - { - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "expr": "sum(container_memory_working_set_bytes{namespace=~\"$namespace\", image!=\"\"}) by (namespace)", - "legendFormat": "{{namespace}} - Actual", - "refId": "B" - } - ] - } - ] - } - ], - "refresh": "30s", - "schemaVersion": 38, - "tags": ["cost", "opencost", "upcloud"], - "templating": { - "list": [ - { - "current": { "selected": false, "text": "Prometheus", "value": "Prometheus" }, - "hide": 0, - "includeAll": false, - "multi": false, - "name": "datasource", - "options": [], - "query": "prometheus", - "queryValue": "", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - }, - { - "allValue": ".*", - "current": { "selected": true, "text": ["All"], "value": ["$__all"] }, - "datasource": { "type": "prometheus", "uid": "${datasource}" }, - "definition": "label_values(namespace)", - "hide": 0, - "includeAll": true, - "multi": true, - "name": "namespace", - "options": [], - "query": { "query": "label_values(namespace)", "refId": "StandardVariableQuery" }, - "refresh": 2, - "regex": "", - "skipUrlSync": false, - "sort": 1, - "type": "query" - }, - { - "current": { "selected": false, "text": "0", "value": "0" }, - "hide": 0, - "label": "Sustained Use Discount %", - "name": "useDiscount", - "options": [{ "selected": true, "text": "0", "value": "0" }], - "query": "0", - "skipUrlSync": false, - "type": "textbox" - }, - { - "current": { "selected": false, "text": "0.03", "value": "0.03" }, - "hide": 2, - "label": "Local Storage GB Cost", - "name": "localStorageGBCost", - "options": [{ "selected": true, "text": "0.03", "value": "0.03" }], - "query": "0.03", - "skipUrlSync": false, - "type": "constant" - } - ] - }, - "time": { "from": "now-7d", "to": "now" }, - "timepicker": { - "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h"], - "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] - }, - "timezone": "", - "title": "Cluster Cost Overview", - "uid": "JOUdHGZZz", - "version": 1, - "weekStart": "" - } From ce5094c1c8dce51111acc11317d2e76a1a5a8bc2 Mon Sep 17 00:00:00 2001 From: snothub Date: Fri, 27 Mar 2026 11:49:03 +0100 Subject: [PATCH 057/113] egress --- .../network/deny-external-egress-trivy.yaml | 37 +++++++++++++++++++ infra/cluster-resources-application.yaml | 2 + infra/network-policies-application.yaml | 33 +++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 cluster-resources/network/deny-external-egress-trivy.yaml create mode 100644 infra/network-policies-application.yaml diff --git a/cluster-resources/network/deny-external-egress-trivy.yaml b/cluster-resources/network/deny-external-egress-trivy.yaml new file mode 100644 index 0000000..939aa11 --- /dev/null +++ b/cluster-resources/network/deny-external-egress-trivy.yaml @@ -0,0 +1,37 @@ +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: deny-external-egress + namespace: trivy-system + labels: + app.kubernetes.io/managed-by: argocd + app.kubernetes.io/part-of: network-policies +spec: + endpointSelector: {} + egress: + # Allow DNS resolution + - toEndpoints: + - matchLabels: + io.kubernetes.pod.namespace: kube-system + k8s-app: kube-dns + toPorts: + - ports: + - port: "53" + protocol: UDP + - port: "53" + protocol: TCP + + # Allow cluster-internal traffic (RFC1918) + - toCIDR: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + + # Allow Trivy vulnerability DB downloads (ghcr.io OCI registry) + - toFQDNs: + - matchName: ghcr.io + - matchName: pkg-containers.githubusercontent.com + toPorts: + - ports: + - port: "443" + protocol: TCP diff --git a/infra/cluster-resources-application.yaml b/infra/cluster-resources-application.yaml index 4072f08..72886b4 100644 --- a/infra/cluster-resources-application.yaml +++ b/infra/cluster-resources-application.yaml @@ -18,6 +18,8 @@ spec: repoURL: git@github.com:fortedigital/sturdy-adventure.git targetRevision: HEAD path: cluster-resources + directory: + exclude: 'network' destination: server: https://kubernetes.default.svc diff --git a/infra/network-policies-application.yaml b/infra/network-policies-application.yaml new file mode 100644 index 0000000..08ec050 --- /dev/null +++ b/infra/network-policies-application.yaml @@ -0,0 +1,33 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: network-policies + namespace: argocd + labels: + app.kubernetes.io/name: network-policies + app.kubernetes.io/part-of: platform + app.kubernetes.io/managed-by: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: cluster-resources/network + + destination: + server: https://kubernetes.default.svc + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + + syncOptions: + - Validate=true + - ServerSideApply=true From e199b00137a8d1989811bce544908d7abe71270f Mon Sep 17 00:00:00 2001 From: snothub Date: Fri, 27 Mar 2026 14:25:34 +0100 Subject: [PATCH 058/113] dash opt --- infra/dashboards/trivy.json | 918 +++++++++++++++++++++++++++++++++++- 1 file changed, 914 insertions(+), 4 deletions(-) diff --git a/infra/dashboards/trivy.json b/infra/dashboards/trivy.json index 79d80eb..ddc241d 100644 --- a/infra/dashboards/trivy.json +++ b/infra/dashboards/trivy.json @@ -873,6 +873,915 @@ } ], "type": "table" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 22 }, + "id": 70, + "panels": [], + "title": "Config Audits", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 0, "y": 23 }, + "id": 71, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_configaudits{severity=\"Critical\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "CRITICAL", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "orange", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 6, "y": 23 }, + "id": 72, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_configaudits{severity=\"High\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "HIGH", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 12, "y": 23 }, + "id": 73, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_configaudits{severity=\"Medium\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "MEDIUM", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "blue", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 18, "y": 23 }, + "id": 74, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_configaudits{severity=\"Low\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "LOW", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 26 }, + "id": 75, + "options": { + "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, + "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" } + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "expr": "sum(trivy_resource_configaudits{namespace=~\"$namespace\"}) by (namespace)", + "legendFormat": "{{namespace}}", + "range": true, + "refId": "A" + }], + "title": "Config Audits by Namespace", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 26 }, + "id": 76, + "options": { + "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, + "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" } + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "expr": "sum(trivy_resource_configaudits{namespace=~\"$namespace\"}) by (severity)", + "legendFormat": "{{severity}}", + "range": true, + "refId": "A" + }], + "title": "Config Audits by Severity", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "fixedColor": "transparent", "mode": "fixed" }, + "custom": { "align": "left", "cellOptions": { "mode": "basic", "type": "color-background" }, "filterable": true, "inspect": false }, + "links": [], + "mappings": [{ + "options": { + "Critical": { "color": "dark-red", "index": 0 }, + "High": { "color": "yellow", "index": 1 }, + "Medium": { "color": "dark-orange", "index": 2 }, + "Low": { "color": "dark-blue", "index": 3 } + }, + "type": "value" + }], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { "h": 10, "w": 24, "x": 0, "y": 34 }, + "id": 77, + "options": { + "cellHeight": "sm", + "footer": { "countRows": false, "fields": "", "reducer": ["sum"], "show": false }, + "showHeader": true, + "sortBy": [{ "desc": false, "displayName": "severity" }] + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_configaudits{namespace=~\"$namespace\"}) by (namespace, severity, config_audit_id, config_audit_title)", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "Config Audit Findings", + "transformations": [{ + "id": "organize", + "options": { + "excludeByName": { "Time": true, "Value": true }, + "indexByName": { "severity": 0, "config_audit_id": 1, "config_audit_title": 2, "namespace": 3 }, + "renameByName": { "config_audit_id": "Audit ID", "config_audit_title": "Description" } + } + }], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 44 }, + "id": 80, + "panels": [], + "title": "Exposed Secrets", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 0, "y": 45 }, + "id": 81, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_image_exposedsecrets{severity=\"Critical\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "CRITICAL", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "orange", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 6, "y": 45 }, + "id": 82, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_image_exposedsecrets{severity=\"High\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "HIGH", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 12, "y": 45 }, + "id": 83, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_image_exposedsecrets{severity=\"Medium\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "MEDIUM", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "blue", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 18, "y": 45 }, + "id": 84, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_image_exposedsecrets{severity=\"Low\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "LOW", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 48 }, + "id": 85, + "options": { + "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, + "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" } + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "expr": "sum(trivy_image_exposedsecrets{namespace=~\"$namespace\"}) by (namespace)", + "legendFormat": "{{namespace}}", + "range": true, + "refId": "A" + }], + "title": "Exposed Secrets by Namespace", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 48 }, + "id": 86, + "options": { + "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, + "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" } + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "expr": "sum(trivy_image_exposedsecrets{namespace=~\"$namespace\"}) by (severity)", + "legendFormat": "{{severity}}", + "range": true, + "refId": "A" + }], + "title": "Exposed Secrets by Severity", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "fixedColor": "transparent", "mode": "fixed" }, + "custom": { "align": "left", "cellOptions": { "mode": "basic", "type": "color-background" }, "filterable": true, "inspect": false }, + "links": [], + "mappings": [{ + "options": { + "Critical": { "color": "dark-red", "index": 0 }, + "High": { "color": "yellow", "index": 1 }, + "Medium": { "color": "dark-orange", "index": 2 }, + "Low": { "color": "dark-blue", "index": 3 } + }, + "type": "value" + }], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { "h": 10, "w": 24, "x": 0, "y": 56 }, + "id": 87, + "options": { + "cellHeight": "sm", + "footer": { "countRows": false, "fields": "", "reducer": ["sum"], "show": false }, + "showHeader": true, + "sortBy": [{ "desc": false, "displayName": "severity" }] + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_image_exposedsecrets{namespace=~\"$namespace\"}) by (namespace, severity, secret_category, secret_title, image_registry, image_repository, image_tag)", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "Exposed Secrets Detail", + "transformations": [{ + "id": "organize", + "options": { + "excludeByName": { "Time": true, "Value": true }, + "indexByName": { "severity": 0, "secret_category": 1, "secret_title": 2, "namespace": 3, "image_registry": 4, "image_repository": 5, "image_tag": 6 }, + "renameByName": { "secret_category": "Category", "secret_title": "Secret", "image_registry": "Registry", "image_repository": "Repository", "image_tag": "Tag" } + } + }], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 66 }, + "id": 90, + "panels": [], + "title": "RBAC Assessments", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 0, "y": 67 }, + "id": 91, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_rbacassessments{severity=\"Critical\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "CRITICAL", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "orange", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 6, "y": 67 }, + "id": 92, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_rbacassessments{severity=\"High\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "HIGH", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "yellow", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 12, "y": 67 }, + "id": 93, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_rbacassessments{severity=\"Medium\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "MEDIUM", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }, { "color": "blue", "value": 1 }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 6, "x": 18, "y": 67 }, + "id": 94, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_rbacassessments{severity=\"Low\", namespace=~\"$namespace\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "LOW", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 70 }, + "id": 95, + "options": { + "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, + "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" } + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "expr": "sum(trivy_resource_rbacassessments{namespace=~\"$namespace\"}) by (namespace)", + "legendFormat": "{{namespace}}", + "range": true, + "refId": "A" + }], + "title": "RBAC Assessments by Namespace", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 70 }, + "id": 96, + "options": { + "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, + "tooltip": { "maxHeight": 600, "mode": "multi", "sort": "desc" } + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "expr": "sum(trivy_resource_rbacassessments{namespace=~\"$namespace\"}) by (severity)", + "legendFormat": "{{severity}}", + "range": true, + "refId": "A" + }], + "title": "RBAC Assessments by Severity", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "fieldConfig": { + "defaults": { + "color": { "fixedColor": "transparent", "mode": "fixed" }, + "custom": { "align": "left", "cellOptions": { "mode": "basic", "type": "color-background" }, "filterable": true, "inspect": false }, + "links": [], + "mappings": [{ + "options": { + "Critical": { "color": "dark-red", "index": 0 }, + "High": { "color": "yellow", "index": 1 }, + "Medium": { "color": "dark-orange", "index": 2 }, + "Low": { "color": "dark-blue", "index": 3 } + }, + "type": "value" + }], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { "h": 10, "w": 24, "x": 0, "y": 78 }, + "id": 97, + "options": { + "cellHeight": "sm", + "footer": { "countRows": false, "fields": "", "reducer": ["sum"], "show": false }, + "showHeader": true, + "sortBy": [{ "desc": false, "displayName": "severity" }] + }, + "pluginVersion": "11.0.0", + "targets": [{ + "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(trivy_resource_rbacassessments{namespace=~\"$namespace\"}) by (namespace, severity, rbac_assessment_id, rbac_assessment_title)", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "A" + }], + "title": "RBAC Assessment Findings", + "transformations": [{ + "id": "organize", + "options": { + "excludeByName": { "Time": true, "Value": true }, + "indexByName": { "severity": 0, "rbac_assessment_id": 1, "rbac_assessment_title": 2, "namespace": 3 }, + "renameByName": { "rbac_assessment_id": "Assessment ID", "rbac_assessment_title": "Description" } + } + }], + "type": "table" } ], "refresh": "30s", @@ -880,7 +1789,8 @@ "tags": [ "Prometheus", "Addons", - "Trivy" + "Trivy", + "Security" ], "templating": { "list": [ @@ -898,7 +1808,7 @@ "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "definition": "label_values(trivy_vulnerability_id{job=\"trivy-operator\"}, namespace)", + "definition": "label_values({job=\"trivy-operator\", __name__=~\"trivy_vulnerability_id|trivy_resource_configaudits|trivy_image_exposedsecrets|trivy_resource_rbacassessments\"}, namespace)", "hide": 0, "includeAll": false, "label": "namespace", @@ -906,7 +1816,7 @@ "name": "namespace", "options": [], "query": { - "query": "label_values(trivy_vulnerability_id{job=\"trivy-operator\"}, namespace)", + "query": "label_values({job=\"trivy-operator\", __name__=~\"trivy_vulnerability_id|trivy_resource_configaudits|trivy_image_exposedsecrets|trivy_resource_rbacassessments\"}, namespace)", "refId": "StandardVariableQuery" }, "refresh": 2, @@ -924,7 +1834,7 @@ "timeRangeUpdatedDuringEditOrView": false, "timepicker": {}, "timezone": "", - "title": "Trivy Operator / Image Vulnerability", + "title": "Trivy Operator / Security Overview", "uid": "trivy_starboard_operator", "version": 7, "weekStart": "" From 9edbe3d0ef22c2afc8a6aedf9a2434eeb2133aac Mon Sep 17 00:00:00 2001 From: snothub Date: Sun, 29 Mar 2026 14:53:55 +0200 Subject: [PATCH 059/113] argocd status sync update --- infra/values/argocd-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/argocd-values.yaml b/infra/values/argocd-values.yaml index 135b9fa..c4db122 100644 --- a/infra/values/argocd-values.yaml +++ b/infra/values/argocd-values.yaml @@ -67,7 +67,7 @@ notifications: - when: app.status.operationState.phase in ['Running'] send: [app-syncing] trigger.on-sync-succeeded: | - - when: app.status.operationState.phase in ['Succeeded'] + - when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy' send: [app-sync-succeeded] trigger.on-sync-failed: | - when: app.status.operationState.phase in ['Failed'] From ede14d9ec6c9616441415b8fd6e3066c02234e29 Mon Sep 17 00:00:00 2001 From: snothub Date: Sun, 29 Mar 2026 14:57:30 +0200 Subject: [PATCH 060/113] degraded message fix --- infra/values/argocd-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/argocd-values.yaml b/infra/values/argocd-values.yaml index c4db122..192e309 100644 --- a/infra/values/argocd-values.yaml +++ b/infra/values/argocd-values.yaml @@ -58,7 +58,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n💬 Message: {{ .app.status.health.message }}" + "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" } # Define notification triggers From 38433f62ce356a2cea0ca1e44797e23cf73fa8e4 Mon Sep 17 00:00:00 2001 From: snothub Date: Sun, 29 Mar 2026 15:20:27 +0200 Subject: [PATCH 061/113] del mcpcoder --- apps/mcpcoder.yaml | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 apps/mcpcoder.yaml diff --git a/apps/mcpcoder.yaml b/apps/mcpcoder.yaml deleted file mode 100644 index c8b92bc..0000000 --- a/apps/mcpcoder.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: mcpcoder - namespace: argocd - annotations: - argocd.argoproj.io/sync-wave: "1" - notifications.argoproj.io/subscribe.on-sync-succeeded.slack: "" - notifications.argoproj.io/subscribe.on-sync-failed.slack: "" - notifications.argoproj.io/subscribe.on-degraded.slack: "" - labels: - app.kubernetes.io/name: mcpcoder - app.kubernetes.io/part-of: apps - app.kubernetes.io/managed-by: argocd - finalizers: - - resources-finalizer.argocd.argoproj.io -spec: - project: default - sources: - - repoURL: git@github.com:fortedigital/forte-helm - path: forteapp - targetRevision: HEAD - helm: - valueFiles: - - $values/mcpcoder/values.yaml - - - repoURL: git@github.com:fortedigital/helm-values.git - targetRevision: HEAD - ref: values - - destination: - server: https://kubernetes.default.svc - namespace: mcpcoder - syncPolicy: - automated: - prune: true - selfHeal: true - syncOptions: - - CreateNamespace=true From 212dc66fab874d4b1882f3a30ecfd67b14ced309 Mon Sep 17 00:00:00 2001 From: snothub Date: Sun, 29 Mar 2026 16:20:48 +0200 Subject: [PATCH 062/113] PSS dash --- infra/dashboards/kustomization.yaml | 3 + infra/dashboards/pod-security.json | 399 ++++++++++++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 infra/dashboards/pod-security.json diff --git a/infra/dashboards/kustomization.yaml b/infra/dashboards/kustomization.yaml index ecf5662..98af3cf 100644 --- a/infra/dashboards/kustomization.yaml +++ b/infra/dashboards/kustomization.yaml @@ -20,3 +20,6 @@ configMapGenerator: - name: grafana-dashboard-opencost files: - opencost.json +- name: grafana-dashboard-pod-security + files: + - pod-security.json diff --git a/infra/dashboards/pod-security.json b/infra/dashboards/pod-security.json new file mode 100644 index 0000000..8e8fd4e --- /dev/null +++ b/infra/dashboards/pod-security.json @@ -0,0 +1,399 @@ +{ + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "links": [], + "panels": [ + { + "title": "Enforced Denials", + "description": "Pods rejected by Pod Security Standards (enforce mode)", + "type": "stat", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 5, "w": 6, "x": 0, "y": 0 }, + "targets": [ + { + "expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[$__range])) or vector(0)", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { "value": null, "color": "green" }, + { "value": 1, "color": "red" } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { "calcs": ["lastNotNull"] }, + "colorMode": "background", + "textMode": "auto" + } + }, + { + "title": "Audit Violations", + "description": "Pods that violate audit-level policy (allowed but logged)", + "type": "stat", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 5, "w": 6, "x": 6, "y": 0 }, + "targets": [ + { + "expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"audit\"}[$__range])) or vector(0)", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { "value": null, "color": "green" }, + { "value": 1, "color": "orange" } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { "calcs": ["lastNotNull"] }, + "colorMode": "background", + "textMode": "auto" + } + }, + { + "title": "Warnings", + "description": "Pods that triggered warn-level policy (allowed with warning)", + "type": "stat", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 5, "w": 6, "x": 12, "y": 0 }, + "targets": [ + { + "expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"warn\"}[$__range])) or vector(0)", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { "value": null, "color": "green" }, + { "value": 1, "color": "yellow" } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { "calcs": ["lastNotNull"] }, + "colorMode": "background", + "textMode": "auto" + } + }, + { + "title": "Total Evaluations", + "description": "All pod security evaluations across all modes", + "type": "stat", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 5, "w": 6, "x": 18, "y": 0 }, + "targets": [ + { + "expr": "sum(increase(pod_security_evaluations_total[$__range])) or vector(0)", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { "value": null, "color": "blue" } + ] + } + }, + "overrides": [] + }, + "options": { + "reduceOptions": { "calcs": ["lastNotNull"] }, + "colorMode": "background", + "textMode": "auto" + } + }, + { + "title": "Violation Rate by Mode", + "description": "Rate of policy violations over time, grouped by enforcement mode", + "type": "timeseries", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 }, + "targets": [ + { + "expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[5m]))", + "legendFormat": "enforce (denied)", + "refId": "A" + }, + { + "expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"audit\"}[5m]))", + "legendFormat": "audit", + "refId": "B" + }, + { + "expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"warn\"}[5m]))", + "legendFormat": "warn", + "refId": "C" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 15, + "pointSize": 5, + "showPoints": "auto" + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "enforce (denied)" }, + "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byName", "options": "audit" }, + "properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byName", "options": "warn" }, + "properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }] + } + ] + } + }, + { + "title": "Violations by Policy Level", + "description": "Violation rate grouped by the PSS level that was violated", + "type": "timeseries", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 }, + "targets": [ + { + "expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\"}[5m])) by (policy_level)", + "legendFormat": "{{ policy_level }}", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 15, + "pointSize": 5, + "showPoints": "auto" + }, + "unit": "ops" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "restricted" }, + "properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byName", "options": "baseline" }, + "properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byName", "options": "privileged" }, + "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }] + } + ] + } + }, + { + "title": "Enforced Denials by Namespace", + "description": "Pods blocked per namespace (enforce mode only)", + "type": "timeseries", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 }, + "targets": [ + { + "expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[5m])) by (resource_namespace)", + "legendFormat": "{{ resource_namespace }}", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "bars", + "lineWidth": 1, + "fillOpacity": 80, + "stacking": { "mode": "normal" } + }, + "unit": "ops" + }, + "overrides": [] + } + }, + { + "title": "Audit + Warn Violations by Namespace", + "description": "Non-enforced violations per namespace — candidates for tightening", + "type": "timeseries", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 }, + "targets": [ + { + "expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=~\"audit|warn\"}[5m])) by (resource_namespace)", + "legendFormat": "{{ resource_namespace }}", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "bars", + "lineWidth": 1, + "fillOpacity": 80, + "stacking": { "mode": "normal" } + }, + "unit": "ops" + }, + "overrides": [] + } + }, + { + "title": "Violations Breakdown", + "description": "Detailed breakdown of all policy violations", + "type": "table", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 10, "w": 24, "x": 0, "y": 21 }, + "targets": [ + { + "expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\"}[$__range])) by (resource_namespace, policy_level, mode, request_operation) > 0", + "format": "table", + "instant": true, + "refId": "A" + } + ], + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { "Time": true }, + "renameByName": { + "resource_namespace": "Namespace", + "policy_level": "Policy Level", + "mode": "Mode", + "request_operation": "Operation", + "Value": "Violations" + }, + "indexByName": { + "resource_namespace": 0, + "policy_level": 1, + "mode": 2, + "request_operation": 3, + "Value": 4 + } + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { "field": "Violations", "desc": true } + ] + } + } + ], + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Mode" }, + "properties": [ + { + "id": "mappings", + "value": [ + { "type": "value", "options": { "enforce": { "text": "Enforce", "color": "red" }, "audit": { "text": "Audit", "color": "orange" }, "warn": { "text": "Warn", "color": "yellow" } } } + ] + } + ] + }, + { + "matcher": { "id": "byName", "options": "Violations" }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { "type": "color-background", "mode": "gradient" } + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { "value": null, "color": "transparent" }, + { "value": 1, "color": "orange" }, + { "value": 100, "color": "red" } + ] + } + } + ] + } + ] + } + }, + { + "title": "Exemptions", + "description": "Pods exempted from policy evaluation", + "type": "timeseries", + "datasource": { "type": "prometheus" }, + "gridPos": { "h": 8, "w": 24, "x": 0, "y": 31 }, + "targets": [ + { + "expr": "sum(rate(pod_security_exemptions_total[5m])) by (request_namespace)", + "legendFormat": "{{ request_namespace }}", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineWidth": 2, + "fillOpacity": 10 + }, + "unit": "ops" + }, + "overrides": [] + } + } + ], + "schemaVersion": 39, + "tags": [ + "security", + "pod-security", + "pss", + "compliance" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "title": "Pod Security Violations", + "uid": "pod-security-violations" +} From 369d5453e0624705b6329331dc7c117d65a89d13 Mon Sep 17 00:00:00 2001 From: snothub Date: Sun, 29 Mar 2026 21:07:26 +0200 Subject: [PATCH 063/113] notification fix --- bootstrap.sh | 2 +- infra/values/argocd-values.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 88c4473..23558fe 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -10,7 +10,7 @@ echo "running $0..." Bootstrap() { ArgoCd - Github +# Github } diff --git a/infra/values/argocd-values.yaml b/infra/values/argocd-values.yaml index 192e309..b20b9e9 100644 --- a/infra/values/argocd-values.yaml +++ b/infra/values/argocd-values.yaml @@ -34,7 +34,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}" + "payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}" } template.app-sync-succeeded: | webhook: @@ -42,7 +42,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" + "payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" } template.app-sync-failed: | webhook: @@ -50,7 +50,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}\n⚠️ Message: {{ .app.status.operationState.message }}" + "payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}\n⚠️ Message: {{ .app.status.operationState.message }}" } template.app-degraded: | webhook: @@ -58,7 +58,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" + "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" } # Define notification triggers From 010d29ff11834e82e4595876e85695ea89d31ef4 Mon Sep 17 00:00:00 2001 From: snothub Date: Sun, 29 Mar 2026 21:44:25 +0200 Subject: [PATCH 064/113] argo notifications --- infra/values/argocd-values.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infra/values/argocd-values.yaml b/infra/values/argocd-values.yaml index b20b9e9..e1e1f8e 100644 --- a/infra/values/argocd-values.yaml +++ b/infra/values/argocd-values.yaml @@ -34,7 +34,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}" + "payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}" } template.app-sync-succeeded: | webhook: @@ -42,7 +42,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" + "payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" } template.app-sync-failed: | webhook: @@ -50,7 +50,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}\n⚠️ Message: {{ .app.status.operationState.message }}" + "payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}\n⚠️ Message: {{ .app.status.operationState.message }}" } template.app-degraded: | webhook: @@ -58,7 +58,7 @@ notifications: method: POST body: | { - "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n📦 Revision: {{ .app.status.sync.revision | default \"n/a\" | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" + "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷️ Image: {{ . }}{{ end }}" } # Define notification triggers From b2815568086ca748b37cf27293c18cf151455b9c Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 2 Apr 2026 22:45:15 +0200 Subject: [PATCH 065/113] mcp def scope --- cluster-resources/policies/auth-sidecar-injector.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index f774c12..fd2af7f 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -323,7 +323,7 @@ spec: - name: AUTH_PUBLIC_PATHS value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}" - name: AUTH_MCP_SCOPES_SUPPORTED - value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'read,write' }}" + value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'profile' }}" resources: limits: cpu: 50m From f7897bc2bf16f03950fd789f7db2f8c27bf82823 Mon Sep 17 00:00:00 2001 From: snothub Date: Thu, 2 Apr 2026 22:56:18 +0200 Subject: [PATCH 066/113] kc resources --- infra/values/keycloak-values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index fd58d00..bed8038 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -30,10 +30,10 @@ metrics: resources: requests: cpu: 250m - memory: 256Mi + memory: 512Mi limits: cpu: 500m - memory: 512Mi + memory: 1Gi postgresql: enabled: true From 97aeba82756ec1c324481f8eadc0b988b9bebafe Mon Sep 17 00:00:00 2001 From: snothub Date: Sat, 4 Apr 2026 17:30:16 +0200 Subject: [PATCH 067/113] docs --- .github/workflows/docs.yml | 35 +++++++++++++++++++++++++++++++++++ .gitignore | 1 - .project-standards.yaml | 6 ++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docs.yml create mode 100644 .project-standards.yaml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..23f98f4 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,35 @@ +name: Deploy GitHub Pages + +on: + push: + branches: [main] + paths: + - 'docs/**' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/configure-pages@v5 + + - uses: actions/upload-pages-artifact@v3 + with: + path: './docs' + + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index c10a0fd..61960a7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -.github/ private/ .helm/ temp/ diff --git a/.project-standards.yaml b/.project-standards.yaml new file mode 100644 index 0000000..371611a --- /dev/null +++ b/.project-standards.yaml @@ -0,0 +1,6 @@ +standards_version: "2025.1" +last_configured: "2026-04-04" +components: + github-pages: "2025.1" + github-pages-generator: "static" + github-pages-source: "docs/" From 43699b9bbd762685b37da635963a1a85c5af6f8c Mon Sep 17 00:00:00 2001 From: snothub Date: Sat, 4 Apr 2026 17:46:08 +0200 Subject: [PATCH 068/113] MkDocs --- .github/workflows/docs.yml | 24 ++++++++++++++++----- .project-standards.yaml | 3 ++- mkdocs.yml | 43 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 mkdocs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 23f98f4..ebfb00a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,7 @@ on: branches: [main] paths: - 'docs/**' + - 'mkdocs.yml' workflow_dispatch: permissions: @@ -17,19 +18,32 @@ concurrency: cancel-in-progress: false jobs: - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install mkdocs mkdocs-material + + - run: mkdocs build + - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v3 with: - path: './docs' + path: './site' + deploy: + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: - id: deployment uses: actions/deploy-pages@v4 diff --git a/.project-standards.yaml b/.project-standards.yaml index 371611a..d7c9612 100644 --- a/.project-standards.yaml +++ b/.project-standards.yaml @@ -2,5 +2,6 @@ standards_version: "2025.1" last_configured: "2026-04-04" components: github-pages: "2025.1" - github-pages-generator: "static" + github-pages-generator: "mkdocs" github-pages-source: "docs/" + github-pages-theme: "material" diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..d7230c5 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,43 @@ +site_name: K8s Launchpad +site_description: Documentation for the GitOps-managed Kubernetes cluster +repo_url: https://github.com/fortedigital/sturdy-adventure +repo_name: fortedigital/sturdy-adventure + +theme: + name: material + palette: + - scheme: default + primary: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.instant + - navigation.sections + - navigation.top + - search.highlight + - content.code.copy + +nav: + - Home: README.md + - GitOps Architecture: GITOPS-ARCHITECTURE.md + - Developer Guide: DEVELOPER-GUIDE.md + - Operations Runbook: OPERATIONS-RUNBOOK.md + - Technical Reference: REFERENCE.md + +markdown_extensions: + - tables + - toc: + permalink: true + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - admonition + - pymdownx.details From dcfa104948cb50ad47d001f2308572045c3c1020 Mon Sep 17 00:00:00 2001 From: snothub Date: Tue, 7 Apr 2026 10:26:27 +0200 Subject: [PATCH 069/113] disable results cache --- infra/values/loki-values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infra/values/loki-values.yaml b/infra/values/loki-values.yaml index 0cd3fa0..3a5bd26 100644 --- a/infra/values/loki-values.yaml +++ b/infra/values/loki-values.yaml @@ -25,6 +25,8 @@ loki: max_line_size: 512KB chunksCache: enabled: false +resultsCache: + enabled: false singleBinary: replicas: 1 resources: From 2e725ffcdd1b9775e2cc2261c77a2163243f2cf3 Mon Sep 17 00:00:00 2001 From: snothub <12095691+snothub@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:00:15 +0200 Subject: [PATCH 070/113] gitea --- docs/REFERENCE.md | 50 ++++++++- infra/gitea.yaml | 42 +++++++ infra/values/gitea-values.yaml | 152 ++++++++++++++++++++++++++ infra/values/keycloak-values.yaml | 18 ++- secrets/gitea-credentials-sealed.yaml | 18 +++ 5 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 infra/gitea.yaml create mode 100644 infra/values/gitea-values.yaml create mode 100644 secrets/gitea-credentials-sealed.yaml diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 14fdee8..6db029f 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -31,6 +31,7 @@ | **Logging** | Loki + Fluent-Bit | | **Tracing** | Tempo (OTLP) | | **Container Scanning** | Trivy | +| **Version Control** | Gitea | ### Network Architecture @@ -85,6 +86,7 @@ sturdy-adventure/ │ ├── tempo.yaml │ ├── fluent-bit.yaml │ ├── trivy.yaml +│ ├── gitea.yaml │ ├── sealedsecrets.yaml │ ├── secrets.yaml │ └── values/ @@ -93,6 +95,7 @@ sturdy-adventure/ │ ├── grafana-values.yaml │ ├── loki-values.yaml │ ├── tempo-values.yaml +│ ├── gitea-values.yaml │ └── fluent-bit-values.yaml │ ├── apps/ # Business applications @@ -121,6 +124,7 @@ sturdy-adventure/ ├── secrets/ # Application secrets (sealed) │ ├── argocd-mcp-credentials.yaml │ ├── dot-ai-secrets.yaml +│ ├── gitea-credentials-sealed.yaml │ ├── mcp10x-credentials-sealed.yaml │ └── musicman-credentials.yaml │ @@ -770,6 +774,49 @@ persistence: **Output**: Loki +### Gitea + +**Chart**: `gitea/gitea` +**Version**: 12.5.0 (app v1.25.4) +**Namespace**: `gitea` + +**Purpose**: Self-hosted Git repository hosting with pull requests, issues, CI/CD (Gitea Actions), container registry, and package registry. + +**Configuration**: +```yaml +# infra/gitea.yaml + infra/values/gitea-values.yaml +ingress: + host: git.forteapps.net + tls: cert-manager (letsencrypt-prod) + +gitea: + admin: + existingSecret: gitea-credentials + config: + service: + DISABLE_REGISTRATION: true + ALLOW_ONLY_EXTERNAL_REGISTRATION: true + actions: + ENABLED: true + packages: + ENABLED: true + metrics: + ENABLED: true + +postgresql: + enabled: true + persistence: 8Gi (upcloud-block-storage-maxiops) +``` + +**Authentication**: Keycloak OIDC via `forte` realm (client ID: `gitea`) + +**Endpoints**: +- Web UI: `https://git.forteapps.net` +- SSH: port 22 (ClusterIP) +- Metrics: `/metrics` (Prometheus scrape) + +**Secrets**: `gitea-credentials` (SealedSecret) containing `admin-password`, `postgres-password`, `secret` (OIDC client secret) + --- ## Kyverno Policies @@ -1373,6 +1420,7 @@ team: platform | **Loki** | 2.9.0+ | Latest | | **Tempo** | 2.6.0+ | 1.24.4 | | **Fluent-Bit** | 2.1.0+ | Latest | +| **Gitea** | 1.25.4 | 12.5.0 | | **PostgreSQL** | 16-alpine | N/A | | **Trivy** | Latest | Latest | @@ -1384,6 +1432,6 @@ team: platform --- -**Last Updated**: 2026-03-16 +**Last Updated**: 2026-04-08 **Maintained By**: Platform Team **Version**: 1.0.0 diff --git a/infra/gitea.yaml b/infra/gitea.yaml new file mode 100644 index 0000000..317193f --- /dev/null +++ b/infra/gitea.yaml @@ -0,0 +1,42 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: gitea + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" + labels: + app.kubernetes.io/name: gitea + app.kubernetes.io/part-of: platform + app.kubernetes.io/managed-by: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + sources: + - repoURL: https://dl.gitea.com/charts + chart: gitea + targetRevision: "12.5.0" + helm: + releaseName: gitea + valueFiles: + - $values/infra/values/gitea-values.yaml + + - repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + ref: values + + destination: + server: https://kubernetes.default.svc + namespace: gitea + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - Validate=true + - ServerSideApply=true diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml new file mode 100644 index 0000000..de2490f --- /dev/null +++ b/infra/values/gitea-values.yaml @@ -0,0 +1,152 @@ +# Gitea Helm Chart Values +# Host: git.forteapps.net +# Chart: gitea v12.5.0 (app v1.25.4) +# Repo: https://dl.gitea.com/charts + +# -- Admin account (password from sealed secret) +gitea: + admin: + existingSecret: gitea-credentials + email: admin@forteapps.net + + # -- Gitea app.ini configuration + config: + APP_NAME: "Forte Git" + + server: + DOMAIN: git.forteapps.net + ROOT_URL: https://git.forteapps.net + SSH_DOMAIN: git.forteapps.net + SSH_PORT: 22 + LFS_START_SERVER: true + + service: + DISABLE_REGISTRATION: true + REQUIRE_SIGNIN_VIEW: false + ALLOW_ONLY_EXTERNAL_REGISTRATION: true + + openid: + ENABLE_OPENID_SIGNIN: true + ENABLE_OPENID_SIGNUP: true + + oauth2: + ENABLED: true + + session: + PROVIDER: db + + cache: + ADAPTER: memory + + database: + DB_TYPE: postgres + + metrics: + ENABLED: true + + repository: + DEFAULT_BRANCH: main + DEFAULT_PRIVATE: last + + actions: + ENABLED: true + + packages: + ENABLED: true + + indexer: + ISSUE_INDEXER_TYPE: bleve + REPO_INDEXER_ENABLED: true + + # -- OIDC authentication via Keycloak + oauth: + - name: "Keycloak" + provider: "openidConnect" + existingSecret: gitea-credentials + key: gitea + autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" + scopes: "openid email profile" + groupClaimName: "" + adminGroup: "" + restrictedGroup: "" + + # -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed) + metrics: + enabled: true + serviceMonitor: + enabled: false + +# -- Ingress via Traefik with Let's Encrypt TLS +ingress: + enabled: true + className: traefik + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: git.forteapps.net + paths: + - path: / + pathType: Prefix + tls: + - secretName: gitea-tls + hosts: + - git.forteapps.net + +# -- Git repository storage +persistence: + enabled: true + size: 10Gi + accessModes: + - ReadWriteOnce + storageClass: upcloud-block-storage-maxiops + +# -- Pod resources +resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +# -- Embedded PostgreSQL (Bitnami subchart) +# Password auto-generated by the subchart; Gitea chart auto-wires the connection. +postgresql: + enabled: true + auth: + existingSecret: gitea-credentials + secretKeys: + adminPasswordKey: postgres-password + userPasswordKey: postgres-password + username: gitea + database: gitea + primary: + persistence: + enabled: true + size: 8Gi + storageClass: upcloud-block-storage-maxiops + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +# -- Disable PostgreSQL HA (using single-instance postgresql above) +postgresql-ha: + enabled: false + +# -- Disable Redis cluster (use in-memory cache instead) +redis-cluster: + enabled: false + +# -- Disable test pod +test: + enabled: false + +# -- SSH service (ClusterIP for now; enable NodePort if SSH access needed) +service: + ssh: + type: ClusterIP + port: 22 diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index bed8038..c3eaa8d 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -64,5 +64,21 @@ keycloakConfigCli: "registrationAllowed": false, "loginWithEmailAllowed": true, "resetPasswordAllowed": true, - "rememberMe": true + "rememberMe": true, + "clients": [ + { + "clientId": "gitea", + "name": "Gitea", + "enabled": true, + "protocol": "openid-connect", + "clientAuthenticatorType": "client-secret", + "secret": "382ed413580cb79d0f54813e5da87007b28fe766a8903d378b9e1c266405a784", + "standardFlowEnabled": true, + "directAccessGrantsEnabled": false, + "publicClient": false, + "redirectUris": ["https://git.forteapps.net/*"], + "webOrigins": ["https://git.forteapps.net"], + "defaultClientScopes": ["openid", "email", "profile"] + } + ] } diff --git a/secrets/gitea-credentials-sealed.yaml b/secrets/gitea-credentials-sealed.yaml new file mode 100644 index 0000000..e13a349 --- /dev/null +++ b/secrets/gitea-credentials-sealed.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: gitea-credentials + namespace: gitea +spec: + encryptedData: + key: AgAN7QteEqfzTrlCRR4Hi50nPJ6GGktFqOJIMgYvAL+es3ELUy9KLGtcyaC+Bf+b9lmG5+UgiCdz8jWbnVI7xEhDKXx8+3KzLw3nPAhYPO3j4XSi3FhbHwE0p5IGlZ0/XdXP5SRr/X0P6wQewapLSClGoya/ErY549ORfL0KgbcUrPE4ReSKbER6R/biRzSpkngxljf0rw4uruhNTlmBvnL/0nRSdZGtww7fLhcQceARutGihKHjOTkzmv0d39hHJA+4xNTi4PCbfOUJBkKwriy7NTi6AZkGRH2b06m+/IGfYYvTp/2gdtf/omS0ZhIoEVeRLhupiwkRAlclOSk3EXRsCcMiH8Gp5Mp4lUuc6yYHwM9ONAbq+P2OlUTjNDOntgH4Z6p02D3pIUf6nterSTuga7haGr5Ph0OCYBk/V6KkswwuTsSBAv7EI5TXrZoALp0xxXBXS459sc21UCgp2K5Xdhb3K92KwtcQDND7qAdIVZXAgf1wahm2svjRWhvjnRVLqChbb8MvkEQC+queHvQvIW+/5mCEb/hAEY/nAHJqTqQhsHnpPxzMhLvxN+7S2zvqSC48zSVAbKvwNjJcaiZ3s4dTjgCaryT307Gg4FI9bHixtk30fB9ZUkMu1cm51QC8ld5CBVKLMi8mHSezshiZ1n2PVYMiOivbghDZQxJ+uutZVOlrF65uAtgnQgmm9q/fTD7FWQ== + password: AgBdnjk041bBX2rwz+q1G6QDk+QrHfvuzoTo1pCRE1w7NLACFgGwzeSTS7gjPXwtjIkgganpZYgT9ZwWK8+gcT6Sae/CBTcDPgE6Y7FtfpG244wgp+mn891TDQXONW/OBAYk+706YDbHfqqz5AQxKAEorJ74RZuLTAo6kB85A/eveEnQqq4cByJaInz0eprB1A4hGfBLF3XArjiVdL7f4X33mR4scH80GozJiqUnG6ruVv5jcEBXnzySVrnCaq4VgmgJTu5Lsjz+AyH7niGTGFIsVBeOnVyeodKwgDk4N3HeSj4w9No3ileZELD5nLZh+/OqdQHw2niBz5/hPjDyXw/p96PfLM3CFygXfs/Au1DqdDf8ccZufhF9OK9CCQqlIgCWK3W7lhsdhcgK65XisRG+hh37Vcxke+A9ZoLGMjIT43KejTqwE8r3wRxPrmejBUcVHohiD7L60dFbcUp64hSBktcIGwEAWn89gmD7Ar1gCoCUmFCBCTVD8c5LQ2W2xeeZ1OrtDpMkOustzXUkzqkqiCB6TPMqDQ4J5FXCyoTuHrBnieLWaKL82kdUohF1+Jak3UGe0rSpOxBmoNC/17yVXyiwBGOUZZP+DJyvwiJu1jc7j3USQ0HRCcR7af96IcwGxge4yWbR268oI53yiJlwh46JFcc8RkxQQaq3Nh3RoiGJohqx/or8JOIFsXEtUW4WSLNQa2tZ4dxRI8F83JnBCo2W+zvL2yXKRP/LLMXfF7nAOuXPrYKrRcdnnRhP1cdDUSIQk8c6gzvKJrsEkt/j + secret: AgAR/5Tj79Rk4fgvPwvRU63q2xF0fokEe0/KWQG9CGiYWwut8wB8PlST5WEcKWIjwNfGpSsyHRQHywIVwDT3m+3/wxfQ5eOtlHIfMuglSymqeLHvj9tuM2EWabd4o9frKHHJigNojXS42JZ1jVSW+PeEfNrGFBARWWaDYSA5586/M8U5qi6UOBIkdyG+796wqqIeUM52d+/lwHHcETQOdhwa5/B4IrbRB33CSv0UHmfM+MeNtUWEnza5+8tm+1mPFyBJ2N9isXxMeyHXWutwOJVsAjEe+X35mC1C03eIsDjpmpjQ+hweUhinu6zse7CsIwcBDuq4/+1xd8xaQ/lmYc01FophuAcVRqDZIhlaAjCLI7gCfX2fliluVuaUbZ/A4DY8Q/psOteODeI0kVEMCaL13T1j6tZZWyDqDAB91xKFh2ne4DOOTwkZLP2StMt7rAENy/eMld+VNrfNliHJUVJGMoPTzCxPaZQxJfRYIbpE5uGGA2fWkteuX5VJRqZ/8JshdBAUVG1XOSXlvM1zlW9KlyvO3j3KhekKG7bY+NndIwFMNhujG3FGWCA+k3MYQfAks3FWhW+ZXA0vYZpnPOJuYO5tqqDO4GLsrdJc5GaXMC7kcey90PqkwzkwhpxX7/X414w3bHWoYttmFCcQwSyA+S029gSFBIleNTKkjpXsNl8qB2NDJR1lf2kufO016WP07PB1NK0hGjvI0isz0uHZILrAD0i55t858/EjWsD2gg7jK2ww69vVxi2c2o6k370YVv92qoZCayTuYsXF0Ay6 + username: AgAmO891YOV8ZtWTNjOF+ZA+6yG69Jdvozq49/XCQabuVXSqoESWA/V/6RPL8+up8dFa+c/u0J+s+Pu1cJL5zjZmJtXStOIuhoIGBTkWBPgHQ11hp/5apkR4nt0gWR3fWQtz1v/073WsaQGhEyKHB4ZsxAXbwEQ41f+TQzdkvxu2DT07Kc4O0jN+QsAl/YtPLk2YW2WaiY/Q7dDq5+LKAbKVRR4Aa03OuMVDapPGOEogu3wC8vl9lkYMhPBrIosq9ycqk4roenKbtmFSIkzytLOLRKSNEmPUSklDMrO8HEGSByASxJP10jgUZW9wY4lE/DmsjuS2CQsGLXhZAye2S3hgGyewnIA1ZfsnJTb7jSsnQ0BVxm6HL9lKy/1PqDzMuE0Ou+LHiIt5DLbCrDmn0F+n8jhNlpCbtiZXVL/Tvo7SAruBe858CYXc2StFHNi5hA1dRP2CCGklY2Wmi8u5JeOdi8RXdhw8deZtI0G+I1NltqzhXdDR55kMWahy2BTImJrGj8owrdg5bFz1Ne63P7jiS80BeUfDKffYAWuslDEiKkhO8YSoVj7ixeWIfXvZRyaecaCMcZ+VuNlZy3R5CbFTcIyZkfotK0+aA9S/q+iM6K36ghTxpQv6WSecnHpeR8mkVckCGVo+iiCPIFfQw2VwOHn+BtvbH8pfr0McKoGHAvoDGGH9sjgDbjAGwCYFYr7qHTKaFc1vbVcUNw== + template: + metadata: + creationTimestamp: null + name: gitea-credentials + namespace: gitea From 118cae656a03a07822cb2f019d6924bc8c7590ab Mon Sep 17 00:00:00 2001 From: snothub Date: Wed, 8 Apr 2026 12:04:13 +0200 Subject: [PATCH 071/113] gitea pg --- infra/values/gitea-values.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index de2490f..159c24a 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -114,10 +114,6 @@ resources: postgresql: enabled: true auth: - existingSecret: gitea-credentials - secretKeys: - adminPasswordKey: postgres-password - userPasswordKey: postgres-password username: gitea database: gitea primary: From 463a96054d94a85f2d69b2b594a37fca2602f2b2 Mon Sep 17 00:00:00 2001 From: snothub Date: Wed, 8 Apr 2026 12:12:09 +0200 Subject: [PATCH 072/113] kyverno policy remove --- .../policies/deployment-verifier.yaml | 73 ------------------- 1 file changed, 73 deletions(-) delete mode 100644 cluster-resources/policies/deployment-verifier.yaml diff --git a/cluster-resources/policies/deployment-verifier.yaml b/cluster-resources/policies/deployment-verifier.yaml deleted file mode 100644 index 9770218..0000000 --- a/cluster-resources/policies/deployment-verifier.yaml +++ /dev/null @@ -1,73 +0,0 @@ -apiVersion: kyverno.io/v1 -kind: ClusterPolicy -metadata: - name: require-deployment-owner -spec: - validationFailureAction: Audit - background: false - rules: - - name: check-pod-owner-is-replicaset-from-deployment - skipBackgroundRequests: true - match: - any: - - resources: - kinds: - - Pod - exclude: - any: - - resources: - namespaces: - - kube-system - - kyverno - - cert-manager - - monitoring - - argocd - - traefik-system - - trivy-system - context: - - name: ownerReplicaSet - apiCall: - method: GET - urlPath: "/apis/apps/v1/namespaces/{{request.namespace}}/replicasets/{{request.object.metadata.ownerReferences[0].name}}" - jmesPath: "@" - preconditions: - all: - - key: "{{ request.object.metadata.ownerReferences || `[]` | [?kind=='ReplicaSet'] | length(@) }}" - operator: GreaterThanOrEquals - value: 1 - validate: - allowExistingViolations: true - message: "Pods must be created through a Deployment resource." - deny: - conditions: - any: - - key: "{{ownerReplicaSet.metadata.ownerReferences[0].kind}}" - operator: NotEquals - value: Deployment - - name: deny-pods-without-replicaset-owner - match: - any: - - resources: - kinds: - - Pod - exclude: - any: - - resources: - namespaces: - - kube-system - - kyverno - - cert-manager - - monitoring - - argocd - - traefik-system - - trivy-system - skipBackgroundRequests: true - validate: - allowExistingViolations: true - message: "Direct pod creation is not allowed. Pods must come from a Deployment managed by ArgoCD." - deny: - conditions: - all: - - key: "{{ request.object.metadata.ownerReferences || `[]` | [?kind=='ReplicaSet'] | length(@) }}" - operator: LessThan - value: 1 From 5e205944c6d3ef18923fc4965cf2de5a1383dd61 Mon Sep 17 00:00:00 2001 From: snothub Date: Wed, 8 Apr 2026 12:21:37 +0200 Subject: [PATCH 073/113] kc creds --- secrets/keycloak-credentials-sealed.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/secrets/keycloak-credentials-sealed.yaml b/secrets/keycloak-credentials-sealed.yaml index d5bc82a..28e8543 100644 --- a/secrets/keycloak-credentials-sealed.yaml +++ b/secrets/keycloak-credentials-sealed.yaml @@ -7,9 +7,9 @@ metadata: namespace: keycloak spec: encryptedData: - admin-password: AgCbGhJduOlTEmA9OLys+ZmuXLU5T1dmdm+fRlrNnPtvjpDCmfKYnOP4rbvBs+Kvf1Y1TZ8fbR2UTISg96pXfY/TsSoJlU024NpBWSZ4e7HxJgHtq0ZwaNi+ruw41JiQ01LrGJ23q34WaPVFV/VfCDaWMlcIJ4gfRb9+lb8jXB6/sr9zSVNWtfrPoYdECcXvr3sOIz9Y5D1eiV/uuDuDQqm+vKpZp8a3bvWO5sB/uQ6a+ECCI/NUFJHtEP8sGLsCGoJMrO1UotXCvKLpIflcxMwW3NlzHd2e7vEPRdelpc3tlT88wd33lQ2LuXMJpQuMI6eHIcVA72UtTqD5bePU1HVWb6d8Dlg6UdebUDIphuJ5rsV3uKYNMgOxn+mvBVXhu5xRBsyanwHoUIKcvvuXaN93YtXHk4khgmt6GNNFdLFEZ69wDB5tPLrlpepvKzNzFYwEIRngYNQlBT3rn8677XgBhUltoq4vYoEqYredVCrh5N7R+DrYD2OYmfsB4WWoMrmE1+Kn/HA/Nu6i5M3mAYXg3NP6Yv9dI5ynMQKVBtwGnFn/hDym1cixmxdQs2FQ3ZaD62zzcXQ0Sc2CuiYVtKg8MZjLCGQoV6ZG7UrnuZ0cyzKE2SGmOaWwFHkRysXoRZiQ/eBybUWwU4DXigczSJ3zBEg57pjEsvloHqAEcGjMxqV1VqXpty/6tdFKkRvCgO6pH5wIQA== - password: AgBeVrYU5CDY8yBA0huXHEJuxykggruULsQB/Q/2sKOZTGZ9KyF1WSB2qJXG9CPIuwBz1YR/hlg4ezF7dQRaWNJspLXeTQgAmjwvgsa3XrfQEEoSFbt9KuN2T28W1S32YbQQJOQHMiMjn8+6YtIWTVM/14MWMGIw8J54Js1cWMsOS4BDT7qe059YMbivxOuPi+J0duL28doowmy5A393804LjyBTzWynB1XqmeBd9V5dJWeQSRl7jpvORR+LKGm/JXF9dX97kyeews0o89qa7VnPqxb9ZGUXhSufMEC8qmm2tXgyj80HaP+PE2aZ0M69o7Xmn1FkEaIxQ8stTyepG4+c7iySbdeqIWPWf0LpgPlkdEuQ7hXAtyNd2TLnD0AxNBiN8bKl2DGLIxaOpoa2PvVHqU08fFQuyHGB3uCvAhgO69Pvb6q62Og2hQHgNM4WWAgu1h9K/F3R4DyzxC0JPYAjfw+XIZCiuYX/UYpjEFXIw4pFU/2SCDGbbPNMKFVxppuBU8h8m7/QTawkJZ0y9ivv0SuIG1z2ExjX73dWk+h1my732wyaoGV+os4R5io5O+aznyvFUsH23N+jNj6WzMSromdYxHKIdmBakd7FiNqsXFy0vm7+aY+tie9WnB/ysC9zN13UpHTCkG5mn2E0X+ctzL9RM7ylQMMesfmk5VCnzewEQloGtEby334CCc/HH1X8+Ws9AO6okEMGplVI53cI2rXwXncFis37qO9yMeV5EZ18wfjXeaKxouiv/zZRuY4wnaojqJKIh0wnbwcOK1f8 - postgres-password: AgBlBR9F4xBm5B31+dwptBjxH4LvjOBd/kDr5AJ2CRpjBZOsEqZa2jnFjUujlNFKfRDWDdzt3FQ6E7VW8Qt2hsrngDgRcIH6aPoZBOSZ0xVMpqLiY+Ek8YlZXH86Oh3+m4wUFNtcM9lZwPGU09/byC5W5zWhpmsypd+waXHF6VJZmweayupvbCwHOL2af58y3HXdl7uqX2/VyTR6mVZKcLVePO/QHNTMBlkPlfw+HKjDuluPcS3MirZU42k6E5OGo3v9XheF4HoiiY5Z76YkBI9k1shYzQYk2w48iPg7QV4heZxUDgwcrkRdJA/eEAUQejakTM4m2yx84ef1xzW2vnnkxz/OTO4KCZtcM7LddBOuO6TS9LSb7JEcfx8SJ7+oQTVhEk0e7nEAMGQQrcOE8TOX122ejBnMkhJra57VnHQ73Xb6fEjQy9dhz6pEnukagIvKKsZ1gZwJkY6qBfq4MtE7nGRCL5OFdXjEFwjdHG1pyR57sYqwvmX5ufE0YZkymdxdmFl/Wigh8uIeZAkfwYgH+8bWg/1Z5yLYzXxhx4S+YjOfLO4ncy2zILw8LnIZvnrOCWgogiiYOJM32E78jZadmZOGVVXbgRvOPLZpw8458Frwsk9oaXMjL7TEryXsH8U8NzVeUBOnnmW9Z6w25a755Jkb2AwnLYbfxMoHhJm7DTTERNM4ZM4umPjUEsXhlTRo/rJ8oE4El54sdSEHIuau0U+ZDbfE0V0y2YhVK1gx2iKeDdvPmGp2LGRURT6AQNc/a5c3/S9MXLEgVSiaSUO5 + admin-password: AgCPuUHRhzPEau3aH9UKq9gp+k5Pu+lPKizST30OWEOW9ozwdcHbq90eb3b5Sd+R/fRPkqta/8TrhZx3uzqNhVnMUSSwfcnxG14Nfq9yl/CAIuXBrPH1C+SghLWrGcv/CaKwT1arTk/ECjtz87vfpGXwqjjRc3rnH1fRjMTsIL6vwOLKIad6dXREzviUGoyxWp6U7A9QPUQqAApRY3zF5cSH603TYiSd8+EIEVViaAAQDg/2xaknbGsMi6M9Ei74IQIFoYRHarMnpkdn7AZnbX5u/bpIOmjMloUazVg8DhPHlc72QiRzqq95YFwfjMqJumOZwS/Fo4rQXoyKM5/aUe0j6zx0wgWk+MNHq7JUjdsJcGIrlgFW4Um9oDhZbYiexDmwG6cR/68fKLOoFpVx4gyZH7Is3+v6l/EYm+ssARU0e+28xA6ZVGm7YhsrgjLmPd3z3YmVafgFWpyEvIGkAKqHNmWD0txIZ3LWptISqwjcr7Yk63kPFjnuXS9t3nWOzSF/RXnLgM90ZSkoytZU+CwyjAHFf30qRWeMvnXB6Ry4g7Koz54jpoMj0Gk9Fqb0v60DgOd4yxhTbT91Z83x2SvKeucnbDB8IK4xMK+QNfKy20BbCHLGfY5kCdYxIUMNlB0LUsjDOnGjqCrAUZjkeHNI04jNvHYMItgP4/EgTU7NB1f/GsP6cwwdwbKmpHoFKGBPIPo5iMUKK51/U/da/1Zm3f9Qr1HrKQ== + password: AgACslplMgI3ZXPpx7BivIM8LIhNDD5j38wbzd4H6IKqZab/nTNpmz2Hc2hCD96H4ljhCIiw1mETzCWshu3vwvCVtJnH2pXiVY0nHpJcSH1C/HqBPOXWVEuEcm2EgvKnf8koKdtkHtAaT6egWrpvNEHCnapo1pa53qB1cZk0PbPQ+cIcedVTa6yF7VzFcOQp56mbtu8ez/iww+K/SdiXtp/IkVCaiOsC7j5ZUwZVmPB/w/nEjgPPETezy3OsOaAokelKq77EUfrj4mh2CEtK9bHBRkbmZkTc9pVkKC/SuWRud5S6xV/VcLRdy/EA/wuqj+Z/XF3hOqyHUpNblbntZ60lqwxoju0ZK9hgZWwZyNvzz04/gv0TkX3/W5JTxNwemJ32AjQ8dQZOz6gdkTsX68lT5P4YlR58Dxsb3WxQxG1ishkDlxGCBA2oQSyyIMFULETuu47V32N8FXOy5fgWHO5ZSer5yFjIIjgU7GyywiSesvZ1m9G7lB5EX56ay1djEmUPiKP0KDyb3QSgsaVbUEdTDi0bXXyDgoVBZEgQNS6vttd5b4qV/IGDqDB4g2hw5xLiff2vkF/h0QPGEz61O+FDyYP5m2rR37o5lD6yWJGI6cB/epoFTKVANPyyGdrIDKtVlgEE30w/fciq+FdDRZob7Yg4uzXi/kGvAYfBmi0Ek04EIU1FLdEq8p+5yZhDSBy4TWjTuH5JTvYlwHjFhnLrCHC/zCBSgkOPgUkAGNOHdTya2ElqEGSSfcRrrvMhnY6ZlZubhGFSdPSrZSXTauSo + postgres-password: AgB/qqkiqoQ1d5g+ZMF5Za0ZQ0kpD+6sh/ds9/uc5XBD4VkaX2+1EDrEcq776x/thbngR1Fi3HNBafRtflfptO64N0r3GBJiakVIT+fEdp7tJs6VKjqcP9HFAEObAVEdlcJzOm9h8iPvhi+kxGgkJYSP3ojJ/lfoWoMLtZjsOJtlQH8yw0uPd8xlgVukssxUzFKkT2RkihLwlK/Zn/8MX8BNJT3W9Li8ruK7pm0+qKqsPKcq0hpKHVRDyYpWubAmiGtj8+Oe3qSYoQxepIdUvtEgEXqHJ5DFggxYB4VzVErdKcyn2tK8JraofcOYpR+JM+8VRH/HWTp0/zNJzY8LCgN/V+KkPkCghVDryW+8ZS2FSP/bcqhPWVbQE3s9EblUny8htsOu2GikbbQcF4LzBKDhIJS6hc9SUbw5sfT7CtREcU7XCfq4VyX7dQgOdiGotO8OTmpXFZtceX9E8ClGTQ3V9tKZkzlpILkEip3PjbkkLshNTtJaRsFgRu37PDddUHaVczBoPKBelONC+KpKBQDoiTfB08BylNxdROsic2ag3upR5avOuqf0YDU6tDKBP3S0FPhPoZOmisUsiZNc8/QqV590gSUvqNBRDWWbqLtYsHmgeYRKDH02kiEzIBQuARryOjK8gOxmv0oo1SVdOd33Q1smU/W5YQNqtoVcqRQOtn0lR/cfQjtHJ3OSSNBZuvEx0BMUsLkH5Oud6nmnVITxT6gxLOS6K/BgV8WyUwgaO0r1sFdjiArCQcaDTjLHkAopYjgWsjEkV04XdjxssEJB template: metadata: creationTimestamp: null From b3d4a26a079ca23a929853aa5063301256338385 Mon Sep 17 00:00:00 2001 From: snothub Date: Wed, 8 Apr 2026 12:40:13 +0200 Subject: [PATCH 074/113] gitea runners --- docs/REFERENCE.md | 51 ++++++++++++++++++++++++++ infra/gitea-actions.yaml | 42 +++++++++++++++++++++ infra/values/gitea-actions-values.yaml | 37 +++++++++++++++++++ infra/values/gitea-values.yaml | 9 +++-- 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 infra/gitea-actions.yaml create mode 100644 infra/values/gitea-actions-values.yaml diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 6db029f..6b5a7d9 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -87,6 +87,7 @@ sturdy-adventure/ │ ├── fluent-bit.yaml │ ├── trivy.yaml │ ├── gitea.yaml +│ ├── gitea-actions.yaml │ ├── sealedsecrets.yaml │ ├── secrets.yaml │ └── values/ @@ -96,6 +97,7 @@ sturdy-adventure/ │ ├── loki-values.yaml │ ├── tempo-values.yaml │ ├── gitea-values.yaml +│ ├── gitea-actions-values.yaml │ └── fluent-bit-values.yaml │ ├── apps/ # Business applications @@ -125,6 +127,7 @@ sturdy-adventure/ │ ├── argocd-mcp-credentials.yaml │ ├── dot-ai-secrets.yaml │ ├── gitea-credentials-sealed.yaml +│ ├── gitea-runner-token-sealed.yaml │ ├── mcp10x-credentials-sealed.yaml │ └── musicman-credentials.yaml │ @@ -817,6 +820,53 @@ postgresql: **Secrets**: `gitea-credentials` (SealedSecret) containing `admin-password`, `postgres-password`, `secret` (OIDC client secret) +### Gitea Actions Runners + +**Chart**: `actions` (from `https://dl.gitea.com/charts`) +**Namespace**: `gitea` +**Sync Wave**: 2 (deploys after Gitea) + +**Purpose**: Act runners execute Gitea Actions CI/CD workflows. Deployed as a StatefulSet with a Docker-in-Docker sidecar for container-based job execution. + +**Configuration**: +```yaml +# infra/gitea-actions.yaml + infra/values/gitea-actions-values.yaml +replicaCount: 3 + +runner: + labels: + - "ubuntu-latest:docker://node:20-bookworm" + - "ubuntu-22.04:docker://node:20-bookworm" + existingSecret: gitea-runner-token + +gitea: + instance: + url: http://gitea-http.gitea.svc.cluster.local:3000 + +dind: + enabled: true # Docker-in-Docker sidecar (privileged) +``` + +**Resources**: + +| Container | CPU Request | Memory Request | CPU Limit | Memory Limit | +|-----------|-------------|----------------|-----------|--------------| +| Runner | 250m | 256Mi | 1 | 1Gi | +| DinD sidecar | 250m | 256Mi | 1 | 1Gi | + +**Secrets**: `gitea-runner-token` (SealedSecret) containing `token` (instance-level runner registration token from `/admin/runners`) + +**Setup Steps**: +1. Get runner registration token from Gitea admin panel (`/admin/runners`) +2. Fill in `private/gitea-runner-token.yaml` with the token +3. Seal: `kubeseal --format yaml < private/gitea-runner-token.yaml > secrets/gitea-runner-token-sealed.yaml` +4. Commit and push — ArgoCD deploys runners automatically + +**Verification**: +- `kubectl get statefulset -n gitea` — 3/3 runners ready +- Gitea admin panel (`/admin/runners`) — runners show as Online +- Create test workflow in `.gitea/workflows/test.yml` — job executes + --- ## Kyverno Policies @@ -1421,6 +1471,7 @@ team: platform | **Tempo** | 2.6.0+ | 1.24.4 | | **Fluent-Bit** | 2.1.0+ | Latest | | **Gitea** | 1.25.4 | 12.5.0 | +| **Gitea Act Runner** | Latest | Latest | | **PostgreSQL** | 16-alpine | N/A | | **Trivy** | Latest | Latest | diff --git a/infra/gitea-actions.yaml b/infra/gitea-actions.yaml new file mode 100644 index 0000000..f9d5ce0 --- /dev/null +++ b/infra/gitea-actions.yaml @@ -0,0 +1,42 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: gitea-actions + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "2" + labels: + app.kubernetes.io/name: gitea-actions + app.kubernetes.io/part-of: platform + app.kubernetes.io/managed-by: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + sources: + - repoURL: https://dl.gitea.com/charts + chart: actions + targetRevision: "*" + helm: + releaseName: gitea-actions + valueFiles: + - $values/infra/values/gitea-actions-values.yaml + + - repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + ref: values + + destination: + server: https://kubernetes.default.svc + namespace: gitea + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - Validate=true + - ServerSideApply=true diff --git a/infra/values/gitea-actions-values.yaml b/infra/values/gitea-actions-values.yaml new file mode 100644 index 0000000..5f68173 --- /dev/null +++ b/infra/values/gitea-actions-values.yaml @@ -0,0 +1,37 @@ +## Gitea Act Runner - Helm values +## Chart: actions (https://dl.gitea.com/charts) + +replicaCount: 3 + +runner: + config: | + log: + level: info + runner: + labels: + - "ubuntu-latest:docker://node:20-bookworm" + - "ubuntu-22.04:docker://node:20-bookworm" + + existingSecret: gitea-runner-token + + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + +gitea: + instance: + url: http://gitea-http.gitea.svc.cluster.local:3000 + +dind: + enabled: true + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index 159c24a..528f7f7 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -21,16 +21,18 @@ gitea: LFS_START_SERVER: true service: - DISABLE_REGISTRATION: true + DISABLE_REGISTRATION: false REQUIRE_SIGNIN_VIEW: false ALLOW_ONLY_EXTERNAL_REGISTRATION: true openid: - ENABLE_OPENID_SIGNIN: true - ENABLE_OPENID_SIGNUP: true + ENABLE_OPENID_SIGNIN: false + ENABLE_OPENID_SIGNUP: false oauth2: ENABLED: true + ENABLE_AUTO_REGISTRATION: true + USERNAME: email session: PROVIDER: db @@ -69,7 +71,6 @@ gitea: groupClaimName: "" adminGroup: "" restrictedGroup: "" - # -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed) metrics: enabled: true From ab136ea8f27d1db9adb87ffb401f5e8035212b3d Mon Sep 17 00:00:00 2001 From: snothub Date: Wed, 8 Apr 2026 12:44:56 +0200 Subject: [PATCH 075/113] gitea recreate --- infra/values/gitea-values.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index 528f7f7..58f3620 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -101,6 +101,10 @@ persistence: - ReadWriteOnce storageClass: upcloud-block-storage-maxiops +# -- Recreate strategy to avoid Multi-Attach errors with RWO volumes +strategy: + type: Recreate + # -- Pod resources resources: requests: From 9f130a8dc49897eaa00d811928f4c2da733de826 Mon Sep 17 00:00:00 2001 From: snothub Date: Wed, 8 Apr 2026 12:51:11 +0200 Subject: [PATCH 076/113] gitea runner token --- secrets/gitea-runner-token-sealed.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 secrets/gitea-runner-token-sealed.yaml diff --git a/secrets/gitea-runner-token-sealed.yaml b/secrets/gitea-runner-token-sealed.yaml new file mode 100644 index 0000000..41e2594 --- /dev/null +++ b/secrets/gitea-runner-token-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: gitea-runner-token + namespace: gitea +spec: + encryptedData: + token: AgBZkU9AgXmFoBURd4WImEFFY4k3LTvGmMJ0ZEZETYzQNS9ICyM0TZfK9nF/bSY48y9t2Vd+VNNW2WJhqec05HE9rLe/vQmo2L8OlBLbCpFL9vCEFJ/h8AiWdHGsLJtCH6tOJwBQ667KPfAGFeYZzKf1XF5HA8O6G1+v7kO3mtSNSIYWq7S/swe5QO+bgtclkeDjg3xgOhjYTzlOsg+rcTknhwGa2YpL22rFN6Tbf94oTK6xVBYrkly1nhiALGYyGnROSv/Ua8+M4xEF5tr0QslwjRmzaQTC+Skt1HKXogsWVzE03RZHWQpmcz1ltaKfikl8WQwBmOBDeg9xNRfBu5n5jli9NvdKZgMEIyQQZtp27QCsIk+FLxbAb2Zy/hG3G93Jvk9mRzgryXyi0te8w+iEP1WtVOYHSyLmk0AMCahn2hPIfzQvlOXJxoSFhLbwm5eSwtqYTQkQ2WFKmwaznSZBUmk1a+LvxV5pOhPMvc52w0v8u13ng/YzDDnyjY/6WdYX5pJtYOGUFDt79yd2Bd0Hc4CH9y50dy84YfdlC/prgTVeVfoJFW48qhE4sNkBhL28PjgkcYmIOb9fskP1o3RuHIoR28y9ipZMkzdLimCRzIQxf4GmdOlGsoEEvujgiGr1ta8ilRoAjjDAD593PuYELFRfGYjk4KGNkz6MP3t4UDRlG8umXMQIqhEWUaRGytqMnfrPGiGPAGCBfAAes3mooDNO/NO+WwNq09fd1OWFeDvzJMOLxWLy + template: + metadata: + creationTimestamp: null + name: gitea-runner-token + namespace: gitea From cb548ee09a81cafd43b7fd5511b8e30020525e6c Mon Sep 17 00:00:00 2001 From: snothub Date: Wed, 8 Apr 2026 12:56:07 +0200 Subject: [PATCH 077/113] gitea actions --- .gitignore | 3 +- infra/gitea-actions.yaml | 2 +- infra/values/gitea-actions-values.yaml | 46 +++++++++++++------------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 61960a7..6f744e3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ CLAUDE.md .claude/ devbox.d/ devbox.lock -.devbox/ \ No newline at end of file +.devbox/ +bash.exe.stackdump \ No newline at end of file diff --git a/infra/gitea-actions.yaml b/infra/gitea-actions.yaml index f9d5ce0..0f763cf 100644 --- a/infra/gitea-actions.yaml +++ b/infra/gitea-actions.yaml @@ -17,7 +17,7 @@ spec: sources: - repoURL: https://dl.gitea.com/charts chart: actions - targetRevision: "*" + targetRevision: "0.0.5" helm: releaseName: gitea-actions valueFiles: diff --git a/infra/values/gitea-actions-values.yaml b/infra/values/gitea-actions-values.yaml index 5f68173..288fda5 100644 --- a/infra/values/gitea-actions-values.yaml +++ b/infra/values/gitea-actions-values.yaml @@ -1,18 +1,15 @@ ## Gitea Act Runner - Helm values -## Chart: actions (https://dl.gitea.com/charts) +## Chart: actions v0.0.5 (https://dl.gitea.com/charts) -replicaCount: 3 +enabled: true -runner: - config: | - log: - level: info - runner: - labels: - - "ubuntu-latest:docker://node:20-bookworm" - - "ubuntu-22.04:docker://node:20-bookworm" +giteaRootURL: http://gitea-http.gitea.svc.cluster.local:3000 - existingSecret: gitea-runner-token +existingSecret: gitea-runner-token +existingSecretKey: token + +statefulset: + replicas: 3 resources: requests: @@ -22,16 +19,19 @@ runner: cpu: "1" memory: 1Gi -gitea: - instance: - url: http://gitea-http.gitea.svc.cluster.local:3000 + actRunner: + config: | + log: + level: info + cache: + enabled: false + container: + require_docker: true + docker_timeout: 300s + runner: + labels: + - "ubuntu-latest:docker://node:20-bookworm" + - "ubuntu-22.04:docker://node:20-bookworm" -dind: - enabled: true - resources: - requests: - cpu: 250m - memory: 256Mi - limits: - cpu: "1" - memory: 1Gi + dind: + rootless: false From 84698ab74384abf1638c06f8dc092b01bef57caa Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Thu, 9 Apr 2026 08:54:15 +0000 Subject: [PATCH 078/113] Update .github/workflows/docs.yml --- .github/workflows/docs.yml | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ebfb00a..0fc98c6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,4 @@ -name: Deploy GitHub Pages +name: Deploy Gitea Pages on: push: @@ -8,17 +8,8 @@ on: - 'mkdocs.yml' workflow_dispatch: -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - jobs: - build: + build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -32,18 +23,12 @@ jobs: - run: mkdocs build - - uses: actions/configure-pages@v5 - - - uses: actions/upload-pages-artifact@v3 - with: - path: './site' - - deploy: - needs: build - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - id: deployment - uses: actions/deploy-pages@v4 + - name: Deploy to Gitea Pages + run: | + cd site + git init + git config user.name "github-actions" + git config user.email "actions@forteapps.net" + git add . + git commit -m "Deploy docs" + git push --force "https://x-token:${{ secrets.PAGES_TOKEN }}@git.forteapps.net/Forte/launchpad.git" HEAD:gitea-pages From 61184f6fdfdcfccec06ba3de14a75e54f5fda65a Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Thu, 9 Apr 2026 08:58:00 +0000 Subject: [PATCH 079/113] Update .github/workflows/docs.yml --- .github/workflows/docs.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0fc98c6..000744a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,13 +11,10 @@ on: jobs: build-and-deploy: runs-on: ubuntu-latest + container: python:3.12-slim steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Install dependencies run: pip install mkdocs mkdocs-material @@ -27,7 +24,7 @@ jobs: run: | cd site git init - git config user.name "github-actions" + git config user.name "gitea-actions" git config user.email "actions@forteapps.net" git add . git commit -m "Deploy docs" From 643c0aaf9bb3022681dfe8d19d0713e74e3c9b02 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Thu, 9 Apr 2026 09:00:24 +0000 Subject: [PATCH 080/113] Update .github/workflows/docs.yml --- .github/workflows/docs.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 000744a..51841d1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,12 +11,11 @@ on: jobs: build-and-deploy: runs-on: ubuntu-latest - container: python:3.12-slim steps: - uses: actions/checkout@v4 - name: Install dependencies - run: pip install mkdocs mkdocs-material + run: pip3 install mkdocs mkdocs-material - run: mkdocs build From 665e4020ba08925a45703674fbd5c228fe33eaf3 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Thu, 9 Apr 2026 09:01:37 +0000 Subject: [PATCH 081/113] Update .github/workflows/docs.yml --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 51841d1..bd8c265 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - name: Install dependencies - run: pip3 install mkdocs mkdocs-material + run: python3 -m pip install mkdocs mkdocs-material - run: mkdocs build From 1c6a0a1b2feebd211e7cfcd9b42d03d122b16c06 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Thu, 9 Apr 2026 09:02:58 +0000 Subject: [PATCH 082/113] Update .github/workflows/docs.yml --- .github/workflows/docs.yml | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bd8c265..fbf6b2a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,29 +2,33 @@ name: Deploy Gitea Pages on: push: - branches: [main] + branches: [ main ] paths: - - 'docs/**' - - 'mkdocs.yml' + - 'docs/**' + - 'mkdocs.yml' workflow_dispatch: + jobs: build-and-deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Install dependencies - run: python3 -m pip install mkdocs mkdocs-material + - name: Install dependencies + run: | + apt-get update -qq + apt-get install -y -qq python3-pip + pip3 install mkdocs mkdocs-material - - run: mkdocs build + - run: mkdocs build - - name: Deploy to Gitea Pages - run: | - cd site - git init - git config user.name "gitea-actions" - git config user.email "actions@forteapps.net" - git add . - git commit -m "Deploy docs" - git push --force "https://x-token:${{ secrets.PAGES_TOKEN }}@git.forteapps.net/Forte/launchpad.git" HEAD:gitea-pages + - name: Deploy to Gitea Pages + run: | + cd site + git init + git config user.name "gitea-actions" + git config user.email "actions@forteapps.net" + git add . + git commit -m "Deploy docs" + git push --force "https://x-token:${{ secrets.PAGES_TOKEN }}@git.forteapps.net/Forte/launchpad.git" HEAD:gitea-pages From f90833711d11efceac4a03bf4c722dc58d0ec942 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Thu, 9 Apr 2026 09:05:12 +0000 Subject: [PATCH 083/113] Update .github/workflows/docs.yml --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fbf6b2a..8649b21 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,7 +19,7 @@ jobs: run: | apt-get update -qq apt-get install -y -qq python3-pip - pip3 install mkdocs mkdocs-material + pip3 install --break-system-packages mkdocs mkdocs-material - run: mkdocs build From 02d5b3eb5a1d0c4211cc760d3432576ce68d4678 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Thu, 9 Apr 2026 09:11:16 +0000 Subject: [PATCH 084/113] Update .github/workflows/docs.yml --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8649b21..4d9dbf1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -31,4 +31,4 @@ jobs: git config user.email "actions@forteapps.net" git add . git commit -m "Deploy docs" - git push --force "https://x-token:${{ secrets.PAGES_TOKEN }}@git.forteapps.net/Forte/launchpad.git" HEAD:gitea-pages + git push --force "https://x-token:${{ secrets.GITEA_TOKEN }}@git.forteapps.net/Forte/launchpad.git" HEAD:gitea-pages From 827213c8836bc40e0d4382e17aec478e6522c587 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 13 Apr 2026 15:54:14 +0200 Subject: [PATCH 085/113] migration --- README.md | 1 + apps/argo-mcp.yaml | 4 +- apps/mcp10x.yaml | 4 +- apps/musicman.yaml | 4 +- cluster-resources/gitea-backup-cronjob.yaml | 88 ++++++++++ cluster-resources/gitea-ssh-ingressroute.yaml | 13 ++ .../policies/auth-sidecar-injector.yaml | 8 +- docs/OPERATIONS-RUNBOOK.md | 2 +- infra/gitea-actions.yaml | 6 + infra/gitea.yaml | 6 + infra/traefik-application.yaml | 14 ++ infra/values/gitea-actions-values.yaml | 7 +- infra/values/gitea-values.yaml | 12 +- infra/values/opencost-values.yaml | 6 +- scripts/gitea-backup.sh | 91 ++++++++++ scripts/gitea-restore.sh | 165 ++++++++++++++++++ secrets/gitea-backup-s3-sealed.yaml | 19 ++ 17 files changed, 428 insertions(+), 22 deletions(-) create mode 100644 cluster-resources/gitea-backup-cronjob.yaml create mode 100644 cluster-resources/gitea-ssh-ingressroute.yaml create mode 100644 scripts/gitea-backup.sh create mode 100644 scripts/gitea-restore.sh create mode 100644 secrets/gitea-backup-s3-sealed.yaml diff --git a/README.md b/README.md index 0d48f54..befa9b2 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,7 @@ kubectl patch application myapp -n argocd \ | **Loki** | Logs | `monitoring` | 1 | | **Tempo** | Distributed tracing | `monitoring` | 1 | | **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet | +| **OpenCost** | Cost monitoring | `monitoring` | 1 | | **Trivy** | Vulnerability scanning | `trivy-system` | 1 | **Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components) diff --git a/apps/argo-mcp.yaml b/apps/argo-mcp.yaml index ca12650..30b2874 100644 --- a/apps/argo-mcp.yaml +++ b/apps/argo-mcp.yaml @@ -16,14 +16,14 @@ metadata: spec: project: default sources: - - repoURL: git@github.com:fortedigital/forte-helm + - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git path: forteapp targetRevision: HEAD helm: valueFiles: - $values/argocd-mcp/values.yaml - - repoURL: git@github.com:fortedigital/helm-values.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git targetRevision: HEAD ref: values diff --git a/apps/mcp10x.yaml b/apps/mcp10x.yaml index e487f85..984c63a 100644 --- a/apps/mcp10x.yaml +++ b/apps/mcp10x.yaml @@ -17,14 +17,14 @@ metadata: spec: project: default sources: - - repoURL: git@github.com:fortedigital/forte-helm + - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git path: forteapp targetRevision: HEAD helm: valueFiles: - $values/mcp10x/values.yaml - - repoURL: git@github.com:fortedigital/helm-values.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git targetRevision: HEAD ref: values diff --git a/apps/musicman.yaml b/apps/musicman.yaml index da6377d..c29b9a2 100644 --- a/apps/musicman.yaml +++ b/apps/musicman.yaml @@ -17,14 +17,14 @@ metadata: spec: project: default sources: - - repoURL: git@github.com:fortedigital/forte-helm + - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git path: forteapp targetRevision: HEAD helm: valueFiles: - $values/musicman/values.yaml - - repoURL: git@github.com:fortedigital/helm-values.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git targetRevision: HEAD ref: values diff --git a/cluster-resources/gitea-backup-cronjob.yaml b/cluster-resources/gitea-backup-cronjob.yaml new file mode 100644 index 0000000..d05ec17 --- /dev/null +++ b/cluster-resources/gitea-backup-cronjob.yaml @@ -0,0 +1,88 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: gitea-backup + namespace: gitea +spec: + schedule: "0 3 * * *" # daily at 03:00 UTC + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 1 + activeDeadlineSeconds: 1800 + template: + spec: + restartPolicy: Never + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + # Must run on the same node as Gitea to share the RWO volume + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/name: gitea + topologyKey: kubernetes.io/hostname + initContainers: + - name: gitea-dump + image: gitea/gitea:1.25.4 + command: + - sh + - -c + - | + gitea dump \ + -c /data/gitea/conf/app.ini \ + -f /backup/gitea-dump.zip \ + -t /tmp/gitea-dump && \ + echo "Dump completed: $(ls -lh /backup/gitea-dump.zip)" + volumeMounts: + - name: data + mountPath: /data + readOnly: true + - name: backup + mountPath: /backup + - name: tmp + mountPath: /tmp/gitea-dump + containers: + - name: upload + image: minio/mc:latest + env: + - name: HOME + value: /tmp + command: + - sh + - -c + - | + mc alias set upcloud "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}" + + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + KEY="gitea-dump-${TIMESTAMP}.zip" + echo "Uploading ${KEY}..." + mc cp /backup/gitea-dump.zip "upcloud/${S3_BUCKET}/${KEY}" && \ + echo "Upload complete." + + # Prune backups older than 7 days + echo "Pruning backups older than 7 days..." + mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true + echo "Pruning complete." + envFrom: + - secretRef: + name: gitea-backup-s3 + volumeMounts: + - name: backup + mountPath: /backup + readOnly: true + volumes: + - name: data + persistentVolumeClaim: + claimName: gitea-shared-storage + - name: backup + emptyDir: + sizeLimit: 5Gi + - name: tmp + emptyDir: + sizeLimit: 5Gi diff --git a/cluster-resources/gitea-ssh-ingressroute.yaml b/cluster-resources/gitea-ssh-ingressroute.yaml new file mode 100644 index 0000000..fb68b90 --- /dev/null +++ b/cluster-resources/gitea-ssh-ingressroute.yaml @@ -0,0 +1,13 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteTCP +metadata: + name: gitea-ssh + namespace: gitea +spec: + entryPoints: + - giteassh + routes: + - match: HostSNI(`*`) + services: + - name: gitea-ssh + port: 22 diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index fd2af7f..6d47dc8 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -127,7 +127,7 @@ spec: spec: containers: - name: authn - image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" ports: - containerPort: "{{ sidecarPort }}" name: auth @@ -208,7 +208,7 @@ spec: spec: containers: - name: authn - image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - containerPort: "{{ sidecarPort }}" @@ -301,7 +301,7 @@ spec: spec: containers: - name: authn - image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - containerPort: "{{ sidecarPort }}" @@ -380,7 +380,7 @@ spec: spec: containers: - name: authn - image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/fortedigital/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" + image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" imagePullPolicy: Always ports: - containerPort: "{{ sidecarPort }}" diff --git a/docs/OPERATIONS-RUNBOOK.md b/docs/OPERATIONS-RUNBOOK.md index aa29eff..e222165 100644 --- a/docs/OPERATIONS-RUNBOOK.md +++ b/docs/OPERATIONS-RUNBOOK.md @@ -180,7 +180,7 @@ Save the following file in private/ (gitignored) folder as secret.yaml argocd.argoproj.io/secret-type: repository stringData: type: git - url: git@github.com:fortedigital/forte-helm.git + url: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git sshPrivateKey: | project: default diff --git a/infra/gitea-actions.yaml b/infra/gitea-actions.yaml index 0f763cf..adb95e3 100644 --- a/infra/gitea-actions.yaml +++ b/infra/gitea-actions.yaml @@ -40,3 +40,9 @@ spec: - CreateNamespace=true - Validate=true - ServerSideApply=true + + ignoreDifferences: + - group: apps + kind: StatefulSet + jsonPointers: + - /spec/volumeClaimTemplates diff --git a/infra/gitea.yaml b/infra/gitea.yaml index 317193f..b219a72 100644 --- a/infra/gitea.yaml +++ b/infra/gitea.yaml @@ -40,3 +40,9 @@ spec: - CreateNamespace=true - Validate=true - ServerSideApply=true + + ignoreDifferences: + - group: apps + kind: StatefulSet + jsonPointers: + - /spec/volumeClaimTemplates diff --git a/infra/traefik-application.yaml b/infra/traefik-application.yaml index f239ce2..1fb13ab 100644 --- a/infra/traefik-application.yaml +++ b/infra/traefik-application.yaml @@ -76,6 +76,10 @@ spec: { "name": "websecure", "mode": "tcp" + }, + { + "name": "giteassh", + "mode": "tcp" } ], "backends": [ @@ -90,6 +94,9 @@ spec: "properties": { "outbound_proxy_protocol": "v2" } + }, + { + "name": "giteassh" } ] } @@ -129,6 +136,13 @@ spec: metrics: true tracing: true + giteassh: + port: 2222 + expose: + default: true + exposedPort: 2222 + protocol: TCP + destination: server: https://kubernetes.default.svc namespace: traefik-system diff --git a/infra/values/gitea-actions-values.yaml b/infra/values/gitea-actions-values.yaml index 288fda5..c659ec1 100644 --- a/infra/values/gitea-actions-values.yaml +++ b/infra/values/gitea-actions-values.yaml @@ -3,7 +3,7 @@ enabled: true -giteaRootURL: http://gitea-http.gitea.svc.cluster.local:3000 +giteaRootURL: https://git.forteapps.net existingSecret: gitea-runner-token existingSecretKey: token @@ -30,8 +30,7 @@ statefulset: docker_timeout: 300s runner: labels: - - "ubuntu-latest:docker://node:20-bookworm" - - "ubuntu-22.04:docker://node:20-bookworm" - + - "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04" + - "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04" dind: rootless: false diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index 58f3620..bf580bb 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -17,13 +17,17 @@ gitea: DOMAIN: git.forteapps.net ROOT_URL: https://git.forteapps.net SSH_DOMAIN: git.forteapps.net - SSH_PORT: 22 + SSH_PORT: 2222 LFS_START_SERVER: true + ENABLE_GITEA_PAGES: true service: DISABLE_REGISTRATION: false + DEFAULT_ALLOW_CREATE_ORGANIZATION: false REQUIRE_SIGNIN_VIEW: false ALLOW_ONLY_EXTERNAL_REGISTRATION: true + ENABLE_BASIC_AUTHENTICATION: true + ENABLE_PASSWORD_SIGNIN_FORM: false openid: ENABLE_OPENID_SIGNIN: false @@ -67,8 +71,8 @@ gitea: existingSecret: gitea-credentials key: gitea autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" - scopes: "openid email profile" - groupClaimName: "" + scopes: "openid email profile organization" + groupClaimName: "groups" adminGroup: "" restrictedGroup: "" # -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed) @@ -146,7 +150,7 @@ redis-cluster: test: enabled: false -# -- SSH service (ClusterIP for now; enable NodePort if SSH access needed) +# -- SSH service (ClusterIP, exposed externally via Traefik TCP IngressRoute on port 2222) service: ssh: type: ClusterIP diff --git a/infra/values/opencost-values.yaml b/infra/values/opencost-values.yaml index cee1465..39d73cc 100644 --- a/infra/values/opencost-values.yaml +++ b/infra/values/opencost-values.yaml @@ -15,10 +15,10 @@ opencost: provider: custom costModel: description: "UpCloud 4-node cluster pricing" - CPU: "6.07" - RAM: "1.52" + CPU: "5.86" + RAM: "1.46" GPU: "0" - storage: "0.03" + storage: "0.34" zoneNetworkEgress: "0" regionNetworkEgress: "0" internetNetworkEgress: "0" diff --git a/scripts/gitea-backup.sh b/scripts/gitea-backup.sh new file mode 100644 index 0000000..397a6fa --- /dev/null +++ b/scripts/gitea-backup.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Gitea backup helper — interacts with the S3 bucket via a temporary pod +# Uses the gitea-backup-s3 secret in the gitea namespace +# +# Usage: +# ./scripts/gitea-backup.sh list # list all backups +# ./scripts/gitea-backup.sh download # download a backup to current dir +# ./scripts/gitea-backup.sh download latest # download the most recent backup + +NAMESPACE="gitea" +SECRET="gitea-backup-s3" +IMAGE="minio/mc:latest" +POD_NAME="gitea-backup-helper" +ALIAS_CMD='mc alias set upcloud ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null' + +cleanup() { + kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true +} + +mc_run() { + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"${ALIAS_CMD}; $1\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1 + kubectl -n "$NAMESPACE" logs "$POD_NAME" + cleanup +} + +case "${1:-help}" in + list) + echo "Listing backups..." + mc_run 'mc ls upcloud/${S3_BUCKET}/' + ;; + + download) + FILE="${2:?Usage: $0 download }" + + if [ "$FILE" = "latest" ]; then + echo "Finding latest backup..." + FILE=$(mc_run 'mc ls upcloud/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]') + if [ -z "$FILE" ]; then + echo "No backups found." + exit 1 + fi + echo "Latest: $FILE" + fi + + echo "Downloading $FILE..." + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"sleep 300\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1 + + echo "Saving to ./$FILE ..." + kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat upcloud/\${S3_BUCKET}/$FILE" > "./$FILE" + cleanup + + echo "Downloaded: ./$FILE" + ;; + + *) + echo "Gitea backup helper" + echo "" + echo "Usage:" + echo " $0 list List all backups in S3" + echo " $0 download Download a specific backup" + echo " $0 download latest Download the most recent backup" + ;; +esac diff --git a/scripts/gitea-restore.sh b/scripts/gitea-restore.sh new file mode 100644 index 0000000..34c9ac7 --- /dev/null +++ b/scripts/gitea-restore.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Gitea restore helper — restores a gitea-dump zip into a running Gitea deployment +# +# Prerequisites: +# - Gitea deployed on the target cluster (Helm chart via ArgoCD) +# - kubectl context pointing to the target cluster +# - A gitea-dump zip file (from gitea-backup.sh download or CronJob) +# +# Usage: +# ./scripts/gitea-restore.sh +# +# What it does: +# 1. Scales Gitea down to 0 +# 2. Restores the PostgreSQL database from gitea-db.sql +# 3. Restores Git repositories to the data PVC +# 4. Scales Gitea back up + +NAMESPACE="gitea" +DEPLOYMENT="gitea" +PG_STATEFULSET="gitea-postgresql" +PVC="gitea-shared-storage" +HELPER_POD="gitea-restore-helper" +HELPER_IMAGE="alpine:3.20" +PG_USER="gitea" +PG_DB="gitea" + +DUMP_FILE="${1:?Usage: $0 }" + +if [ ! -f "$DUMP_FILE" ]; then + echo "Error: file not found: $DUMP_FILE" + exit 1 +fi + +echo "=== Gitea Restore ===" +echo "Dump file: $DUMP_FILE" +echo "" + +# --- Safety prompt --- +read -r -p "This will OVERWRITE the Gitea database and repositories on the current cluster. Continue? [y/N] " confirm +if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 0 +fi + +cleanup() { + kubectl -n "$NAMESPACE" delete pod "$HELPER_POD" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true +} +trap cleanup EXIT + +# --- Step 1: Extract dump locally --- +RESTORE_DIR=$(mktemp -d) +echo "" +echo "[1/5] Extracting dump..." +unzip -q "$DUMP_FILE" -d "$RESTORE_DIR" + +# Detect SQL dump file (gitea-db.sql or gitea-dump-*.sql) +SQL_FILE=$(find "$RESTORE_DIR" -maxdepth 1 -name '*.sql' | head -1) +if [ -z "$SQL_FILE" ]; then + echo "Error: no .sql file found in dump." + rm -rf "$RESTORE_DIR" + exit 1 +fi + +# Detect repos — either a "repos" folder or gitea-repo.zip +if [ -d "$RESTORE_DIR/repos" ]; then + REPOS_SOURCE="dir" + REPOS_PATH="$RESTORE_DIR/repos" +elif [ -f "$RESTORE_DIR/gitea-repo.zip" ]; then + REPOS_SOURCE="zip" + REPOS_PATH="$RESTORE_DIR/gitea-repo.zip" +else + echo "Error: no repos/ directory or gitea-repo.zip found in dump." + rm -rf "$RESTORE_DIR" + exit 1 +fi + +echo " Found: $(basename "$SQL_FILE") ($(du -h "$SQL_FILE" | cut -f1))" +echo " Found: repos ($REPOS_SOURCE)" +[ -d "$RESTORE_DIR/data" ] && echo " Found: data/" +[ -f "$RESTORE_DIR/app.ini" ] && echo " Found: app.ini (managed by Helm, skipping)" + +# --- Step 2: Scale down Gitea --- +echo "" +echo "[2/5] Scaling down Gitea..." +kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=0 +kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=60s 2>/dev/null || true +echo " Waiting for pods to terminate..." +kubectl -n "$NAMESPACE" wait --for=delete pod -l app.kubernetes.io/name=gitea --timeout=120s 2>/dev/null || true +echo " Gitea is down." + +# --- Step 3: Restore database --- +echo "" +echo "[3/5] Restoring database..." +# Drop and recreate to ensure clean state +kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \ + psql -U "$PG_USER" -d postgres -c "DROP DATABASE IF EXISTS ${PG_DB};" 2>/dev/null +kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \ + psql -U "$PG_USER" -d postgres -c "CREATE DATABASE ${PG_DB} OWNER ${PG_USER};" 2>/dev/null +kubectl -n "$NAMESPACE" exec -i "${PG_STATEFULSET}-0" -- \ + psql -U "$PG_USER" -d "$PG_DB" < "$SQL_FILE" > /dev/null +echo " Database restored." + +# --- Step 4: Restore repositories --- +echo "" +echo "[4/5] Restoring repositories..." + +# Need a helper pod on the same node as the PVC (RWO) +cleanup +kubectl -n "$NAMESPACE" run "$HELPER_POD" --restart=Never \ + --image="$HELPER_IMAGE" \ + --overrides="{ + \"spec\":{ + \"containers\":[{ + \"name\":\"$HELPER_POD\", + \"image\":\"$HELPER_IMAGE\", + \"command\":[\"sleep\",\"3600\"], + \"volumeMounts\":[{\"name\":\"data\",\"mountPath\":\"/data\"}] + }], + \"volumes\":[{ + \"name\":\"data\", + \"persistentVolumeClaim\":{\"claimName\":\"$PVC\"} + }] + } + }" > /dev/null 2>&1 + +kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$HELPER_POD" --timeout=120s > /dev/null 2>&1 + +# Clear existing repos +kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- sh -c "rm -rf /data/gitea/repositories/*" 2>/dev/null || true + +# Upload repos — tar via stdin since kubectl cp needs tar in the container +echo " Uploading repositories..." +if [ "$REPOS_SOURCE" = "dir" ]; then + # repos/ directory — tar and stream into the PVC + tar -cf - -C "$REPOS_PATH" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/repositories/" +else + # gitea-repo.zip — stream and extract + cat "$REPOS_PATH" | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "cat > /tmp/gitea-repo.zip && unzip -q -o /tmp/gitea-repo.zip -d /data/gitea/repositories/ && rm /tmp/gitea-repo.zip" +fi + +# Restore data/ directory (avatars, attachments, LFS, etc.) if present +if [ -d "$RESTORE_DIR/data" ]; then + echo " Uploading data (avatars, attachments, LFS)..." + tar -cf - -C "$RESTORE_DIR/data" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/" +fi + +# Fix ownership +kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- chown -R 1000:1000 /data/gitea/ +echo " Repositories restored." + +# --- Step 5: Scale back up --- +echo "" +echo "[5/5] Scaling Gitea back up..." +kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=1 +kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=120s + +# Cleanup temp dir +rm -rf "$RESTORE_DIR" + +echo "" +echo "=== Restore complete ===" +echo "Gitea should be back online with restored data." +echo "Verify at your Gitea URL that repos and users are present." diff --git a/secrets/gitea-backup-s3-sealed.yaml b/secrets/gitea-backup-s3-sealed.yaml new file mode 100644 index 0000000..0e185e3 --- /dev/null +++ b/secrets/gitea-backup-s3-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: gitea-backup-s3 + namespace: gitea +spec: + encryptedData: + AWS_ACCESS_KEY_ID: AgByl2/ljFIHzTD0Vy7srQoXRfdJRLG+WukgeLMJeiJm9MOFJBNEkr5ju2DemDNdRcViXQLN3yxqT/L0fG0rz+kaPQtLeVFToqr58vokxDasHw4WVIUOosi6wE+yaI16H6vxvdV8dck09nHE3fdBcwctlvjqsY7mKvyx4tYKdGRDoeJ/C7shYoDTl4E/ZtsSRkfOQ4Ojm6M6FU10zn03OOKrzaOUczxnqbAyGNFrZvCUGG38QQVnyYm/2HLofQgheSSQx8p0w5IgPRRhBM3IAyCLGkyEA8qHXSO3J5Y2m1izAoU9RVsHAVUMVTfYEdtUMADkDcywKzi0bi0ehBxDs2PuC89Uz4s6rKQ/v8xU+jUf3KogrZxsbuCh1iFVO/NCOLvQhLY5wU/946wl1WmS76/HxkM/D9Iq+KF0VsP0pJAA+SIyJQ3Bh0a9GnKRsJjCfO8qX0M0WSXhOpjw+4DvBoe653mV7n+LOEjQy7LJURFaz1HzQColelhhlQ5tHCN8J1jtjscFNiHZqyzBBm226X3oxr0cAC6e/l53ohNkKS3NA5aM/wouBrscs1/CfmDYKxujLyGqontFRQc3rtCZ29829u/RmuwietIVGeu+ooCacSM73zDqGYKM7HRr/Y7QYxuW0TiSaJhYMZQqsC4uo7ebZhRa2bWbCTHiVCs3jDdpSRyPgEECOvnOJbkTsh0e02HtrUEx7HBjLZua9FD2sskr8C9XJQ== + AWS_SECRET_ACCESS_KEY: AgCYxgmto8ytLz8QMm25/nIqqezlWWennhbPSPMB/aDYR+zW45LvAZbjwVp6wwSR/U5iXwfdZg5/k8+8CzGAKDjxc3Nwygih3cUpqVBOl+uOzD3W3oNDsyQckhmNA4jidwIbJF6ecV8O+GVuU19+E4QrkHTIP9lN5pnhkfIR7nMRVj4jdcNahH2O75huadGQII4GG+rmnGX1012IAhknq20CiOCbby3a2yHaU3om0srO1TkW/67jioQX2IvgUh7jVl6c4r1Y6b+glwV4bHc9GecDqNEF6uj6uy8ChNh1khRfUYVysIQRM9m1pV/qlKiUW/wjDZOjoW88IAg4wl2MMOFQby27jVwQWSe+kUPwRMf7HSNWoq/DaE/z71cMsdeEnAXtQMGwNzOr4EGM1n/faPGDWkj306l1xjoXNO2hLCNX8BspSBxQDWWADBGClC6C1AQX0HlsZLV0G18VCEkjTwvPRmPqigxzHganxWiWM0q3DfGrc+JvnGFW0r7waoKI5vIzxwzCbb3I042+3z2vsvo7ZW2mez+eKgeD0MvhRW2SlBMiE62MGJQL2BvTew1iU0Xean+19WGZO7PPysnOH6kU098kTJ5GjQpxlI2C+w6QC18q8eQeIvVyd/7wH+k7RMCDC+No6MCcYhDlcQNbIir6JJ7vIOd3n5NKXdg7Sy3SnjkyDPTOjXTwyn2hHkATMzUxgn/0frNZYSsEMTuoNlOfcZLr7UbFv+Qlr49rkAEMo3deohfGiQSD + S3_BUCKET: AgA6ulIpP/DrYOQ7iqo7CSeaSj0L/+PXDPZ7SxPmdu/wrqbXw7nlxAyp+7QHqkUub5XgVhwrk3KPmqUPcECPDbHdt93+nlM3PVD0yPNkVaijncEPRVccGu/VhE6Nae5lXI9U3pnAVAXn+7z8iwRpF/vr1qIGyQwiizsKyfBQhvRSupzOvY8sypbItDjyjttxlwMRorGI94GObeUS89kSx/MB7BWZJMtUuRRSG6YwgH/XIkIWbo2p2LD90EhNtaT9rYa1PcGP9BVcgHf/9zVCI4+1LWbfmZSgobwAEKLhzZfhzCM2DhN31CsVWhpp0x0gYmspNbtQQvoosKmeBPBT+BLkTabAhjx00rvVX3J48Er3PaVAjw6JxT1KSdaUuCmcIzX3O8ys/8PNacaEgEiqmeuIPgID8YHSXSfs9RIUkjKBWGydjE90lMQPgnqOBkPWTd1BNRqHj60D2pFp0/h7+j8/OfBj7doDp9ECwcdQqjwzX4pNi7WQiGd+Ri0/7DK1xSAOL0lwgg8VSrqCOIdasAZRVQuHhuWwKMyhdQyQCr+zCOQ/bLQaPeF1m7tKxFfU4lNz5tRiC8AOQI5aHX2gRrkpfugD3G9qFFQMl9EPCdNeBh/ezVWxSxekWvQTuGJ2WnLD33BhsZVLKjXa+tHjD/BsQjQgdCiqv8J9gPgtngx/pAFf0NaQezVU4tBfaYD4tetcrZDz5UtW5tTHaF4= + S3_ENDPOINT: AgBfKbxdU9hZrxIpbM6b+hDthXQ+uYrjWHGmxSdjGvxgHB2P7E+HqblPAHjIpiAsGiPESV75ZKs5/BdEBOoZpbvneAuRgVLZ5mxkWiQ35q7sJpWaUg47icnlEPFoFj8oxYbi1NRAYB5hbc3AU0s11mw7wre0pRZu0pgSidHk/lyuSXOHKQzuhXxKYmV61LjMxCQGwDNwbiDNuSZyU7AZ2r+vr2W7Tzu6G+tJctwQd3HOnYbOLMV4tBv93nc7EkU4tbdvdIvkGHEmKf4r4F+nGvKZ3fZie1QKyQvG/4+i8OKqby9XJtviEEfBqfrk5qb1dNQlqCfA4ThQ11MmRiP8VoaUp/yoUHHYACNY9HLBp+N5Cgbbcxo044U1c8b97I6ZOZJ2waZ9XkrBpYPPXWJRKxLeNgYoJqn3yMZV/U561DO1jLZ2cwQXXaFrm1WT7VjcB0czdJHW3FcOg9lzYKMCCTTX+cD4M1oK992931eECQxBecrtlQYD+NlJng8ARm7myTACOZGYMQo2gjdM4ZBh9KqoCT2jrFC6E29YwfRAIXrhiWdZZxOW6Bu9Txt8FgxnIlSz9iZ1hvbfdvrSZTilJbAAULKFqLUgNpQbdgYHtGXQkzFHqYmbdZ0vJ6taIli7y+/Rz6xKcql8uJLxnuncLvLvXHxXl+rWeKrAMn+jPvnuCcdq6yVPsI0Nz/B4EQRL7Nzl9XYQxSybAJACrrCjgEuHsquoPpuznlGuk2scuakXdWOzMg6i/MEk + template: + metadata: + creationTimestamp: null + name: gitea-backup-s3 + namespace: gitea + type: Opaque From 4abd528b19c63938e71fe2f8e6d4982c842021e3 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 13 Apr 2026 15:55:25 +0200 Subject: [PATCH 086/113] launchpad --- infra/enterprise-apps.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/enterprise-apps.yaml b/infra/enterprise-apps.yaml index 0cce61e..1d3db4a 100644 --- a/infra/enterprise-apps.yaml +++ b/infra/enterprise-apps.yaml @@ -16,7 +16,7 @@ metadata: spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD path: apps destination: From 18fb0ca3da18a8a9cfce6b4ae9e2157812be284e Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 13 Apr 2026 16:08:01 +0200 Subject: [PATCH 087/113] repo names fix --- _app-of-apps.yaml | 2 +- docs/DEVELOPER-GUIDE.md | 22 ++++---- docs/GITOPS-ARCHITECTURE.md | 12 ++--- docs/OPERATIONS-RUNBOOK.md | 40 +++++++-------- docs/REFERENCE.md | 8 +-- infra/cluster-resources-application.yaml | 2 +- infra/fluent-bit.yaml | 2 +- infra/gitea-actions.yaml | 2 +- infra/gitea.yaml | 2 +- infra/grafana-dashboards.yaml | 2 +- infra/grafana.yaml | 2 +- infra/keycloak.yaml | 2 +- infra/kyverno-policies.yaml | 2 +- infra/loki.yaml | 2 +- infra/network-policies-application.yaml | 2 +- infra/opencost.yaml | 2 +- infra/prometheus.yaml | 2 +- infra/secrets.yaml | 2 +- infra/tempo.yaml | 2 +- mkdocs.yml | 64 ++++++++++++------------ 20 files changed, 88 insertions(+), 88 deletions(-) diff --git a/_app-of-apps.yaml b/_app-of-apps.yaml index 3e4fb9e..d2fa207 100644 --- a/_app-of-apps.yaml +++ b/_app-of-apps.yaml @@ -18,7 +18,7 @@ metadata: spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD path: infra destination: diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index 685aa99..dc06e3e 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -89,21 +89,21 @@ If you do need cluster access, install: You'll need read/write access to these repositories: -1. **sturdy-adventure** (Config repo) +1. **launchpad** (Config repo) ```bash - git clone https://github.com/fortedigital/sturdy-adventure.git - cd sturdy-adventure + git clone https://git.forteapps.net/Forte/launchpad.git + cd launchpad ``` 2. **helm-values** (Values repo) ```bash - git clone git@github.com:fortedigital/helm-values.git + git clone https://git.forteapps.net/Forte/helm-prod-values.git cd helm-values ``` 3. **forte-helm** (Chart repo - read-only for most developers) ```bash - git clone https://github.com/fortedigital/forte-helm.git + git clone https://git.forteapps.net/Forte/forte-helm.git cd forte-helm ``` @@ -132,9 +132,9 @@ mkdir -p ~/dev/k8s cd ~/dev/k8s # Clone repositories -git clone https://github.com/fortedigital/sturdy-adventure.git launchpad -git clone git@github.com:fortedigital/helm-values.git helm-prod-values -git clone https://github.com/fortedigital/forte-helm.git forte-helm +git clone https://git.forteapps.net/Forte/launchpad.git launchpad +git clone https://git.forteapps.net/Forte/helm-prod-values helm-prod-values +git clone https://git.forteapps.net/Forte/forte-helm forte-helm # Your folder structure: # ~/dev/k8s/ @@ -201,7 +201,7 @@ Our setup uses three repositories: |------------|---------|-----------|-----------| | **forte-helm** | Helm chart templates (generic, reusable) | Platform engineers | ❌ Rarely | | **helm-values** | Application configuration (image tag, env vars) | Developers / CI pipelines | ✅ Sometimes | -| **sturdy-adventure** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app | +| **launchpad** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app | ### Example: Deploying "myapp" @@ -236,7 +236,7 @@ app: value: https://api.example.com ``` -#### Repository: `sturdy-adventure` (ArgoCD Application) +#### Repository: `launchpad` (ArgoCD Application) ```yaml # apps/myapp.yaml # Tells ArgoCD to deploy your app @@ -386,7 +386,7 @@ git push ### Step 3: Create ArgoCD Application Manifest -In the `sturdy-adventure` repository, create `apps/hello-world.yaml`: +In the `launchpad` repository, create `apps/hello-world.yaml`: ```yaml apiVersion: argoproj.io/v1alpha1 diff --git a/docs/GITOPS-ARCHITECTURE.md b/docs/GITOPS-ARCHITECTURE.md index 2fa1794..e663979 100644 --- a/docs/GITOPS-ARCHITECTURE.md +++ b/docs/GITOPS-ARCHITECTURE.md @@ -53,8 +53,8 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where ┌────────────────────────────────┐ │ Config Repository │ │ (ArgoCD Applications) │ - │ github.com/fortedigital/ │ - │ sturdy-adventure │ + │ git.forteapps.net/Forte/ │ + │ launchpad │ └────────────────────────────────┘ │ │ @@ -109,12 +109,12 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where ## Repository Structure ### 1. **Config Repository** (Current Repo) -**Repository**: `https://github.com/fortedigital/sturdy-adventure.git` +**Repository**: `https://git.forteapps.net/Forte/launchpad` **Purpose**: GitOps configuration - ArgoCD Applications and cluster resources **Location**: `C:\dev\k8s\launchpad` ``` -sturdy-adventure/ +launchpad/ ├── bootstrap.sh # Cluster initialization script ├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern) │ @@ -405,7 +405,7 @@ jobs: 1. **Config Repo Change**: - Developer updates `apps/myapp.yaml` - - Pushes to `sturdy-adventure` repo + - Pushes to `launchpad` repo - ArgoCD detects change (60s reconciliation) - Syncs application to cluster @@ -561,7 +561,7 @@ Notifications include: **Rebuild Process**: 1. Provision new Kubernetes cluster -2. Clone `sturdy-adventure` repository +2. Clone `launchpad` repository 3. Run `./bootstrap.sh` 4. ArgoCD installs and syncs all applications 5. Manually recreate unsealed secrets and seal them diff --git a/docs/OPERATIONS-RUNBOOK.md b/docs/OPERATIONS-RUNBOOK.md index e222165..9ff6f9f 100644 --- a/docs/OPERATIONS-RUNBOOK.md +++ b/docs/OPERATIONS-RUNBOOK.md @@ -51,8 +51,8 @@ kubectl get nodes ```bash # 1. Clone config repository -git clone https://github.com/fortedigital/sturdy-adventure.git -cd sturdy-adventure +git clone https://git.forteapps.net/Forte/launchpad +cd launchpad # 2. Set cluster name (optional) export CLUSTER_NAME="prod-cluster-01" @@ -130,10 +130,10 @@ Generate a dedicated SSH key for ArgoCD without a passphrase (required for autom ```bash # Generate ED25519 key (recommended - smaller and more secure) -ssh-keygen -t ed25519 -C "argocd-deploy-key-sturdy-adventure" -f argocd-deploy-key -N "" +ssh-keygen -t ed25519 -C "argocd-deploy-key-launchpad" -f argocd-deploy-key -N "" # Or RSA key if ED25519 is not supported -ssh-keygen -t rsa -b 4096 -C "argocd-deploy-key-sturdy-adventure" -f argocd-deploy-key -N "" +ssh-keygen -t rsa -b 4096 -C "argocd-deploy-key-launchpad" -f argocd-deploy-key -N "" ``` This creates two files: @@ -148,7 +148,7 @@ This creates two files: ``` 2. Go to GitHub repository settings: - - Navigate to: `https://github.com/fortedigital/sturdy-adventure/settings/keys` + - Navigate to: `https://git.forteapps.net/Forte/launchpad/settings/keys` - Or: Repository → Settings → Deploy keys 3. Click **"Add deploy key"** @@ -227,7 +227,7 @@ metadata: spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: main path: cluster-resources destination: @@ -273,7 +273,7 @@ rm /tmp/test-repo-access.yaml # Add new public key to GitHub (keep old key for now) # Update Kubernetes secret - kubectl create secret generic repo-sturdy-adventure \ + kubectl create secret generic repo-launchpad \ --from-file=sshPrivateKey=argocd-new-key \ --namespace=argocd \ --dry-run=client -o yaml | kubectl apply -f - @@ -290,7 +290,7 @@ rm /tmp/test-repo-access.yaml kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository # Review deploy keys in GitHub - # Visit: https://github.com/fortedigital/sturdy-adventure/settings/keys + # Visit: https://git.forteapps.net/Forte/launchpad/settings/keys ``` 4. **Use Different Keys per Repository** @@ -304,16 +304,16 @@ rm /tmp/test-repo-access.yaml ```bash # Check if secret exists -kubectl get secret repo-sturdy-adventure -n argocd +kubectl get secret repo-launchpad -n argocd # Verify secret has correct label -kubectl get secret repo-sturdy-adventure -n argocd -o yaml | grep argocd.argoproj.io/secret-type +kubectl get secret repo-launchpad -n argocd -o yaml | grep argocd.argoproj.io/secret-type # Check ArgoCD application controller logs kubectl logs -n argocd deployment/argocd-application-controller | grep -i "permission denied" # Verify deploy key is added to GitHub -# Visit: https://github.com/fortedigital/sturdy-adventure/settings/keys +# Visit: https://git.forteapps.net/Forte/launchpad/settings/keys ``` **Issue: "Host key verification failed"** @@ -324,7 +324,7 @@ kubectl exec -n argocd deployment/argocd-repo-server -- \ ssh-keyscan github.com >> ~/.ssh/known_hosts # Or disable strict host key checking (less secure) -kubectl patch secret repo-sturdy-adventure -n argocd \ +kubectl patch secret repo-launchpad -n argocd \ --type merge \ -p '{"stringData":{"insecure":"true"}}' ``` @@ -336,7 +336,7 @@ kubectl patch secret repo-sturdy-adventure -n argocd \ kubectl logs -n argocd deployment/argocd-repo-server # Refresh repository connection -kubectl delete secret repo-sturdy-adventure -n argocd +kubectl delete secret repo-launchpad -n argocd # Recreate secret (see Step 3 above) # Restart ArgoCD components @@ -346,12 +346,12 @@ kubectl rollout restart deployment argocd-application-controller -n argocd #### Multiple Repository Setup -For the three-repository pattern (sturdy-adventure, forte-helm, helm-values): +For the three-repository pattern (launchpad, forte-helm, helm-values): ```bash -# 1. sturdy-adventure (main config repo) -ssh-keygen -t ed25519 -C "argocd-sturdy-adventure" -f key-sturdy -N "" -# Add key-sturdy.pub to: https://github.com/fortedigital/sturdy-adventure/settings/keys +# 1. launchpad (main config repo) +ssh-keygen -t ed25519 -C "argocd-launchpad" -f key-sturdy -N "" +# Add key-sturdy.pub to: https://git.forteapps.net/Forte/launchpad/settings/keys # 2. helm-values (private values repo) ssh-keygen -t ed25519 -C "argocd-helm-values" -f key-helm-values -N "" @@ -360,7 +360,7 @@ ssh-keygen -t ed25519 -C "argocd-helm-values" -f key-helm-values -N "" # 3. forte-helm (private helm charts repo) # Create secrets -kubectl create secret generic repo-sturdy-adventure \ +kubectl create secret generic repo-launchpad \ --from-file=sshPrivateKey=key-sturdy \ --namespace=argocd --dry-run=client -o yaml | \ kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \ @@ -385,9 +385,9 @@ If you're currently using HTTPS and want to switch to SSH: # 2. Update all Application manifests # Change from: -# repoURL: https://github.com/fortedigital/sturdy-adventure.git +# repoURL: https://git.forteapps.net/Forte/launchpad # To: -# repoURL: git@github.com:fortedigital/sturdy-adventure.git +# repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git # 3. Update and commit find . -name "*.yaml" -type f -exec sed -i 's|https://github.com/fortedigital/|git@github.com:fortedigital/|g' {} + diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 6b5a7d9..fed299c 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -62,14 +62,14 @@ Internet ## Repository Reference -### Config Repository: `sturdy-adventure` +### Config Repository: `launchpad` -**URL**: `https://github.com/fortedigital/sturdy-adventure.git` +**URL**: `https://git.forteapps.net/Forte/launchpad` #### Directory Structure ``` -sturdy-adventure/ +launchpad/ ├── bootstrap.sh # Cluster initialization script ├── _app-of-apps.yaml # Root ArgoCD Application │ @@ -171,7 +171,7 @@ metadata: spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git path: infra destination: server: https://kubernetes.default.svc diff --git a/infra/cluster-resources-application.yaml b/infra/cluster-resources-application.yaml index 72886b4..6c6ce01 100644 --- a/infra/cluster-resources-application.yaml +++ b/infra/cluster-resources-application.yaml @@ -15,7 +15,7 @@ spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD path: cluster-resources directory: diff --git a/infra/fluent-bit.yaml b/infra/fluent-bit.yaml index 462665f..b90ccab 100644 --- a/infra/fluent-bit.yaml +++ b/infra/fluent-bit.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/fluent-bit-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/gitea-actions.yaml b/infra/gitea-actions.yaml index adb95e3..1531d69 100644 --- a/infra/gitea-actions.yaml +++ b/infra/gitea-actions.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/gitea-actions-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/gitea.yaml b/infra/gitea.yaml index b219a72..cdec1b3 100644 --- a/infra/gitea.yaml +++ b/infra/gitea.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/gitea-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/grafana-dashboards.yaml b/infra/grafana-dashboards.yaml index 782d8bf..2784c00 100644 --- a/infra/grafana-dashboards.yaml +++ b/infra/grafana-dashboards.yaml @@ -15,7 +15,7 @@ spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD path: infra/dashboards diff --git a/infra/grafana.yaml b/infra/grafana.yaml index 43d87c7..1238fbc 100644 --- a/infra/grafana.yaml +++ b/infra/grafana.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/grafana-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/keycloak.yaml b/infra/keycloak.yaml index b5e00a0..66b38fa 100644 --- a/infra/keycloak.yaml +++ b/infra/keycloak.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/keycloak-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/kyverno-policies.yaml b/infra/kyverno-policies.yaml index 028813a..e00e063 100644 --- a/infra/kyverno-policies.yaml +++ b/infra/kyverno-policies.yaml @@ -15,7 +15,7 @@ spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD path: cluster-resources/policies diff --git a/infra/loki.yaml b/infra/loki.yaml index 6d53444..f7cf2f0 100644 --- a/infra/loki.yaml +++ b/infra/loki.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/loki-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/network-policies-application.yaml b/infra/network-policies-application.yaml index 08ec050..16ae239 100644 --- a/infra/network-policies-application.yaml +++ b/infra/network-policies-application.yaml @@ -15,7 +15,7 @@ spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD path: cluster-resources/network diff --git a/infra/opencost.yaml b/infra/opencost.yaml index 5beeda5..7e4ef10 100644 --- a/infra/opencost.yaml +++ b/infra/opencost.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/opencost-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/prometheus.yaml b/infra/prometheus.yaml index b17436c..d36cb5d 100644 --- a/infra/prometheus.yaml +++ b/infra/prometheus.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/prometheus-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/infra/secrets.yaml b/infra/secrets.yaml index ad1d8e9..d0a99a1 100644 --- a/infra/secrets.yaml +++ b/infra/secrets.yaml @@ -17,7 +17,7 @@ metadata: spec: project: default source: - repoURL: git@github.com:fortedigital/sturdy-adventure.git + repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git path: secrets destination: server: https://kubernetes.default.svc diff --git a/infra/tempo.yaml b/infra/tempo.yaml index 5b47059..1ce4fbb 100644 --- a/infra/tempo.yaml +++ b/infra/tempo.yaml @@ -23,7 +23,7 @@ spec: valueFiles: - $values/infra/values/tempo-values.yaml - - repoURL: git@github.com:fortedigital/sturdy-adventure.git + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD ref: values diff --git a/mkdocs.yml b/mkdocs.yml index d7230c5..57a01e4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,43 +1,43 @@ site_name: K8s Launchpad site_description: Documentation for the GitOps-managed Kubernetes cluster -repo_url: https://github.com/fortedigital/sturdy-adventure -repo_name: fortedigital/sturdy-adventure +repo_url: https://git.forteapps.net/Forte/launchpad +repo_name: Forte/launchpad theme: name: material palette: - - scheme: default - primary: indigo - toggle: - icon: material/brightness-7 - name: Switch to dark mode - - scheme: slate - primary: indigo - toggle: - icon: material/brightness-4 - name: Switch to light mode + - scheme: default + primary: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode features: - - navigation.instant - - navigation.sections - - navigation.top - - search.highlight - - content.code.copy + - navigation.instant + - navigation.sections + - navigation.top + - search.highlight + - content.code.copy nav: - - Home: README.md - - GitOps Architecture: GITOPS-ARCHITECTURE.md - - Developer Guide: DEVELOPER-GUIDE.md - - Operations Runbook: OPERATIONS-RUNBOOK.md - - Technical Reference: REFERENCE.md +- Home: README.md +- GitOps Architecture: GITOPS-ARCHITECTURE.md +- Developer Guide: DEVELOPER-GUIDE.md +- Operations Runbook: OPERATIONS-RUNBOOK.md +- Technical Reference: REFERENCE.md markdown_extensions: - - tables - - toc: - permalink: true - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.superfences - - pymdownx.tabbed: - alternate_style: true - - admonition - - pymdownx.details +- tables +- toc: + permalink: true +- pymdownx.highlight: + anchor_linenums: true +- pymdownx.superfences +- pymdownx.tabbed: + alternate_style: true +- admonition +- pymdownx.details From f5166b3797dada08f287582e26ea34c5e86e6609 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Mon, 13 Apr 2026 16:08:30 +0200 Subject: [PATCH 088/113] readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index befa9b2..585d9fa 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ ### For New Developers ```bash # 1. Clone repositories -git clone https://github.com/fortedigital/sturdy-adventure.git -git clone git@github.com:fortedigital/helm-values.git +git clone https://git.forteapps.net/Forte/launchpad.git +git clone ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git # 2. Read the guides # - Start: docs/GITOPS-ARCHITECTURE.md @@ -137,9 +137,9 @@ This repository contains the complete GitOps configuration for our Kubernetes cl | Repository | Purpose | Who Edits | How Often | |------------|---------|-----------|-----------| -| **[sturdy-adventure](https://github.com/fortedigital/sturdy-adventure.git)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often | -| **[forte-helm](https://github.com/fortedigital/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely | -| **[helm-values](git@github.com:fortedigital/helm-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes | +| **[launchpad](https://git.forteapps.net/Forte/launchpad)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often | +| **[forte-helm](https://git.forteapps.net/Forte/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely | +| **[helm-values](ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes | ### GitOps Workflow From e74a8cb9d843005188a11865439a9d162ece0270 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Tue, 14 Apr 2026 08:23:09 +0000 Subject: [PATCH 089/113] Update infra/values/gitea-values.yaml ENABLE_BASIC_AUTH_CHALLENGE: true --- infra/values/gitea-values.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index bf580bb..8f04cb6 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -20,6 +20,7 @@ gitea: SSH_PORT: 2222 LFS_START_SERVER: true ENABLE_GITEA_PAGES: true + ENABLE_BASIC_AUTH_CHALLENGE: true service: DISABLE_REGISTRATION: false From 2b7d441803b906eb92fccd94ce033ad5720b6b66 Mon Sep 17 00:00:00 2001 From: gitea_admin Date: Tue, 14 Apr 2026 08:33:47 +0000 Subject: [PATCH 090/113] Update infra/gitea.yaml ignoreDifferences --- infra/gitea.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infra/gitea.yaml b/infra/gitea.yaml index cdec1b3..f0c5209 100644 --- a/infra/gitea.yaml +++ b/infra/gitea.yaml @@ -46,3 +46,7 @@ spec: kind: StatefulSet jsonPointers: - /spec/volumeClaimTemplates + - group: v1 + kind: Secret + jsonPointers: + - /data/postgres-password From 08d870d44c49506d96393500584f43a1d1cbcd00 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Tue, 14 Apr 2026 10:53:45 +0200 Subject: [PATCH 091/113] oauth fix --- infra/values/gitea-values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index bf580bb..fc688c0 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -64,9 +64,9 @@ gitea: ISSUE_INDEXER_TYPE: bleve REPO_INDEXER_ENABLED: true - # -- OIDC authentication via Keycloak + # -- OIDC authentication via Forte oauth: - - name: "Keycloak" + - name: "Forte" provider: "openidConnect" existingSecret: gitea-credentials key: gitea From 0e8524b84a0c8252e228a9b53ad392b766de6346 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Tue, 14 Apr 2026 12:05:14 +0200 Subject: [PATCH 092/113] renovate --- README.md | 2 ++ docs/REFERENCE.md | 49 +++++++++++++++++++++++++++++-- infra/renovate.yaml | 42 ++++++++++++++++++++++++++ infra/values/renovate-values.yaml | 28 ++++++++++++++++++ secrets/renovate-env-sealed.yaml | 19 ++++++++++++ 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 infra/renovate.yaml create mode 100644 infra/values/renovate-values.yaml create mode 100644 secrets/renovate-env-sealed.yaml diff --git a/README.md b/README.md index 585d9fa..1dea06c 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ This repository contains the complete GitOps configuration for our Kubernetes cl │ ├── fluent-bit.yaml │ ├── trivy.yaml │ ├── sealedsecrets.yaml +│ ├── renovate.yaml │ └── values/ # Helm value overrides │ ├── apps/ # Business Applications @@ -335,6 +336,7 @@ kubectl patch application myapp -n argocd \ | **Tempo** | Distributed tracing | `monitoring` | 1 | | **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet | | **OpenCost** | Cost monitoring | `monitoring` | 1 | +| **Renovate** | Dependency updates | `renovate` | CronJob | | **Trivy** | Vulnerability scanning | `trivy-system` | 1 | **Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components) diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index fed299c..222d042 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -90,6 +90,7 @@ launchpad/ │ ├── gitea-actions.yaml │ ├── sealedsecrets.yaml │ ├── secrets.yaml +│ ├── renovate.yaml │ └── values/ │ ├── argocd-values.yaml │ ├── prometheus-values.yaml @@ -98,7 +99,8 @@ launchpad/ │ ├── tempo-values.yaml │ ├── gitea-values.yaml │ ├── gitea-actions-values.yaml -│ └── fluent-bit-values.yaml +│ ├── fluent-bit-values.yaml +│ └── renovate-values.yaml │ ├── apps/ # Business applications │ ├── mcp10x.yaml @@ -867,6 +869,48 @@ dind: - Gitea admin panel (`/admin/runners`) — runners show as Online - Create test workflow in `.gitea/workflows/test.yml` — job executes +### Renovate + +**Chart**: `renovate` (OCI: `ghcr.io/renovatebot/charts`) +**Version**: 46.109.0 (app v43.113.0) +**Namespace**: `renovate` +**Sync Wave**: 2 + +**Purpose**: Automated dependency update bot. Runs as a CronJob that scans Gitea repositories for outdated dependencies and creates pull requests with updates. + +**Configuration**: +```yaml +# infra/renovate.yaml + infra/values/renovate-values.yaml +cronjob: + schedule: "@hourly" + concurrencyPolicy: Forbid + +renovate: + config: + platform: gitea + endpoint: https://git.forteapps.net + autodiscover: true + gitAuthor: "Renovate Bot " + +resources: + requests: { cpu: 250m, memory: 512Mi } + limits: { cpu: "1", memory: 1Gi } +``` + +**Secrets**: `renovate-env` (SealedSecret in `secrets` namespace, cloned by Kyverno) containing: +- `RENOVATE_TOKEN` — Gitea PAT with repo write + issue write permissions +- `RENOVATE_GITHUB_COM_TOKEN` — GitHub PAT (public_repo read-only) for changelog fetching + +**Setup Steps**: +1. Fill in `private/renovate-env.yaml` with tokens +2. Seal: `kubeseal --format yaml < private/renovate-env.yaml > secrets/renovate-env-sealed.yaml` +3. Commit and push — ArgoCD deploys the CronJob, Kyverno clones the secret + +**Verification**: +- `kubectl get cronjob -n renovate` — CronJob exists +- `kubectl create job --from=cronjob/renovate renovate-test -n renovate` — manual trigger +- `kubectl logs -n renovate job/renovate-test` — check logs + --- ## Kyverno Policies @@ -1472,6 +1516,7 @@ team: platform | **Fluent-Bit** | 2.1.0+ | Latest | | **Gitea** | 1.25.4 | 12.5.0 | | **Gitea Act Runner** | Latest | Latest | +| **Renovate** | v43.113.0 | 46.109.0 | | **PostgreSQL** | 16-alpine | N/A | | **Trivy** | Latest | Latest | @@ -1483,6 +1528,6 @@ team: platform --- -**Last Updated**: 2026-04-08 +**Last Updated**: 2026-04-14 **Maintained By**: Platform Team **Version**: 1.0.0 diff --git a/infra/renovate.yaml b/infra/renovate.yaml new file mode 100644 index 0000000..bc1d34e --- /dev/null +++ b/infra/renovate.yaml @@ -0,0 +1,42 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: renovate + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "2" + labels: + app.kubernetes.io/name: renovate + app.kubernetes.io/part-of: platform + app.kubernetes.io/managed-by: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + sources: + - repoURL: ghcr.io/renovatebot/charts + chart: renovate + targetRevision: "46.109.0" + helm: + releaseName: renovate + valueFiles: + - $values/infra/values/renovate-values.yaml + + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git + targetRevision: HEAD + ref: values + + destination: + server: https://kubernetes.default.svc + namespace: renovate + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - Validate=true + - ServerSideApply=true diff --git a/infra/values/renovate-values.yaml b/infra/values/renovate-values.yaml new file mode 100644 index 0000000..0feb572 --- /dev/null +++ b/infra/values/renovate-values.yaml @@ -0,0 +1,28 @@ +cronjob: + schedule: "@hourly" + concurrencyPolicy: Forbid + +renovate: + config: | + { + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "platform": "gitea", + "endpoint": "https://git.forteapps.net", + "autodiscover": true, + "gitAuthor": "Renovate Bot " + } + +envFrom: +- secretRef: + name: renovate-env + +env: + LOG_LEVEL: debug + +resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi diff --git a/secrets/renovate-env-sealed.yaml b/secrets/renovate-env-sealed.yaml new file mode 100644 index 0000000..b03051b --- /dev/null +++ b/secrets/renovate-env-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: renovate-env + namespace: secrets +spec: + encryptedData: + RENOVATE_GITHUB_COM_TOKEN: AgBpci12KEAs8A5ujnMyoE5/0V5r8EU0MKBrd/465FUIv4n80veSEvpD10k3oA9I6Cyieb0O4uKIE3efx2FdtqnB3sDu8Zb+++hNahxCTN2CPHkr1ULBf/8oD8HpHh1+ajQNX3O5CcZ0mYumyQzGNg30RcvTo0gSq4d/TFFdRDMJSi5w/J7EZWE/Zanl8g6q+ShVSNPSwLoe6mfZkL65nlL057wlWiVEkgu82rTHB27ssRGptnyM32dYNg0i7Z1+jMqqK6pIMRth13HhxCNITtBKa9wjg30rnyNdHZ6O+++jB2vP4c6mRuF/mIUVPr6SCkHqYthqgJQRj1VTVn07jSgWkKxe3g/tBtUglq8nItoRAiXH9AuhDG6Y6UxG6YFC3KCO5/Hjp6pHVMsbZWBLzmUGlHvDR9nNtDeQGndt/R+LHSA6hlxE7lzrip87x9pD2dSgoBbDwOOb19D99+88rkeTiRYg8Z8UUJ2ty+UjcyLb8RgseuBQEKrYM1nV/QzykEVaD5CtUeMTR4yrULDgPWuOznYm+nbj4KN6cfeRIQ9qcRjdlv9qEyVQZHMBjE971cBDbnacrknB8oZhCzUdVvNKxSfer+D4jTP1DQwd/ePHBsT1epdypfwi63kArqIUIx8HcU0uy29jUYr4FIoI9O+WcSiZ9YxRlCrXzIGEHBe57pXzcCsejQnymztZzXUiUItRjIxLxIdXMReYyMgUFlPLr0n70JallREfpBSR71kZdEC4yu1R6g7gcdI7AX1WvuRm2EYNtF4oP2IlOUMGZsU7LsR6oucSLSKPGEB/qSS1YUu5hm6q5C3mLQMu2hk= + RENOVATE_TOKEN: AgBZX1CBmvBz+1bDFfkfjK3c3UISoCHPGfJxCs3GboH+uBAo+foZP59JzlLIxY7qcMnPtesMVcQlVe3MXnSdPy9R/fkYrLLAmbKi8wpGgmZx/+pHu5ZTcZ8wRgszNCamLjB23Sk+xObNbeECo6PTDVSCSvpgUy4UVH7yVdFP/iFZjMDyd8GZyW/jf7kroQlliRSFm9eNupUC4BE2Qr3O5pkY0k48KXJIeTtoTuI525NUUSHmkoq8xKX7mA/Qe8HA7hLIBZy3iZvwUm9QdyrbsxQbLHip0EEG/44PyTQ5q9y4ufpLpbbkpMBqDbLlUCrZZmYP2anzrrXtqAx81tpjwBTOV+3ko+WwiZJhEouG22Y7EfDs56g8Zue7FBGqS6HdIRh5oet7LhKLUo8ZfXYIS+qMfNwKV1aOobUQ75U4rH73iOAexeHtLZBnCETCYK+iK7ea6nHNyJhvStpquWb0m/05MZBBJw/hG1/ShOm2+2v99qNXLG+bKE2NXX56JMr0S5eVlPRUq7gHgmzxQtoWWCV7Tz2R5ej/3c2QniD4Cz8Rxsej3FLQwZInICM5AS5u03jQTehzy+wLZ8TJJW9VApc6Z/lkfJixPOKzD+B6/NBEq5PuAatINtqj9Lzh/OzcbHV9qUj1Q+8Wydny6afYdjqrcx0+N15WPMI5tMglMpsggYd26MQ2aAaIa/vY/O4aRAIepmbFlzy6LwsGxd1ZfkeSP3nGc25zEbt7H9tOLA5nwuq9kCRl5vb0 + template: + metadata: + creationTimestamp: null + labels: + allowedToBeCloned: "true" + name: renovate-env + namespace: secrets + type: Opaque From 8b403736a9445e667781d3a181f9b79ef99f1c96 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Tue, 14 Apr 2026 12:14:43 +0200 Subject: [PATCH 093/113] secret renovate --- secrets/renovate-env-sealed.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/secrets/renovate-env-sealed.yaml b/secrets/renovate-env-sealed.yaml index b03051b..6ae41ba 100644 --- a/secrets/renovate-env-sealed.yaml +++ b/secrets/renovate-env-sealed.yaml @@ -4,16 +4,16 @@ kind: SealedSecret metadata: creationTimestamp: null name: renovate-env - namespace: secrets + namespace: renovate spec: encryptedData: - RENOVATE_GITHUB_COM_TOKEN: AgBpci12KEAs8A5ujnMyoE5/0V5r8EU0MKBrd/465FUIv4n80veSEvpD10k3oA9I6Cyieb0O4uKIE3efx2FdtqnB3sDu8Zb+++hNahxCTN2CPHkr1ULBf/8oD8HpHh1+ajQNX3O5CcZ0mYumyQzGNg30RcvTo0gSq4d/TFFdRDMJSi5w/J7EZWE/Zanl8g6q+ShVSNPSwLoe6mfZkL65nlL057wlWiVEkgu82rTHB27ssRGptnyM32dYNg0i7Z1+jMqqK6pIMRth13HhxCNITtBKa9wjg30rnyNdHZ6O+++jB2vP4c6mRuF/mIUVPr6SCkHqYthqgJQRj1VTVn07jSgWkKxe3g/tBtUglq8nItoRAiXH9AuhDG6Y6UxG6YFC3KCO5/Hjp6pHVMsbZWBLzmUGlHvDR9nNtDeQGndt/R+LHSA6hlxE7lzrip87x9pD2dSgoBbDwOOb19D99+88rkeTiRYg8Z8UUJ2ty+UjcyLb8RgseuBQEKrYM1nV/QzykEVaD5CtUeMTR4yrULDgPWuOznYm+nbj4KN6cfeRIQ9qcRjdlv9qEyVQZHMBjE971cBDbnacrknB8oZhCzUdVvNKxSfer+D4jTP1DQwd/ePHBsT1epdypfwi63kArqIUIx8HcU0uy29jUYr4FIoI9O+WcSiZ9YxRlCrXzIGEHBe57pXzcCsejQnymztZzXUiUItRjIxLxIdXMReYyMgUFlPLr0n70JallREfpBSR71kZdEC4yu1R6g7gcdI7AX1WvuRm2EYNtF4oP2IlOUMGZsU7LsR6oucSLSKPGEB/qSS1YUu5hm6q5C3mLQMu2hk= - RENOVATE_TOKEN: AgBZX1CBmvBz+1bDFfkfjK3c3UISoCHPGfJxCs3GboH+uBAo+foZP59JzlLIxY7qcMnPtesMVcQlVe3MXnSdPy9R/fkYrLLAmbKi8wpGgmZx/+pHu5ZTcZ8wRgszNCamLjB23Sk+xObNbeECo6PTDVSCSvpgUy4UVH7yVdFP/iFZjMDyd8GZyW/jf7kroQlliRSFm9eNupUC4BE2Qr3O5pkY0k48KXJIeTtoTuI525NUUSHmkoq8xKX7mA/Qe8HA7hLIBZy3iZvwUm9QdyrbsxQbLHip0EEG/44PyTQ5q9y4ufpLpbbkpMBqDbLlUCrZZmYP2anzrrXtqAx81tpjwBTOV+3ko+WwiZJhEouG22Y7EfDs56g8Zue7FBGqS6HdIRh5oet7LhKLUo8ZfXYIS+qMfNwKV1aOobUQ75U4rH73iOAexeHtLZBnCETCYK+iK7ea6nHNyJhvStpquWb0m/05MZBBJw/hG1/ShOm2+2v99qNXLG+bKE2NXX56JMr0S5eVlPRUq7gHgmzxQtoWWCV7Tz2R5ej/3c2QniD4Cz8Rxsej3FLQwZInICM5AS5u03jQTehzy+wLZ8TJJW9VApc6Z/lkfJixPOKzD+B6/NBEq5PuAatINtqj9Lzh/OzcbHV9qUj1Q+8Wydny6afYdjqrcx0+N15WPMI5tMglMpsggYd26MQ2aAaIa/vY/O4aRAIepmbFlzy6LwsGxd1ZfkeSP3nGc25zEbt7H9tOLA5nwuq9kCRl5vb0 + RENOVATE_GITHUB_COM_TOKEN: AgBU+ZlwMh/VfDR+5rxjzPRiDgfLtA45IJBONRw7LDjCcgGoct68yZhnuDEasItnbpy8LxsmafqG7mTK6d/vTIy5rxnQKfeT+6RjKAvfKtH8eb6BtOCbZoP7Q9l9jE0Ba8afRSH9rs6908E6Fc1KCqBVrJiEeJxgi55YpXN6THubj5DjLdbqJxIUMj2vgA3me/TdMTyzIKobwMBDXH5RmR8MAdFgzuBXrnUwpqtVISija5T70jHaPDHZLTlZNJFHfBu5nSEazVxoL+a+xKsATJLH9JrQ6Ssxv/REaotqpRMR42N2aynAS793LE9pDXZZJlI/VWBJAlP/IFoqjKyRRlr07NNpaKRE/EstaSIBjb1MczOya18Dp9SYaWU68QF5BrfPP8T+og/FRAB/MvVijejl8S53qi+yo7DwUOF68lmT43ymuisaHjQz7Kuj/UPDg9F/HBQ9srq63+vMN/x+7ld/z8lpHb85hUPEN69RBCrahCFR6QQrqzJkWYZsLWBTkNg9eTivcX4BPG9C6bv5zaWsvEmvp+Pe88WUdDk6nNK3F5iAqD4a/yFhtYdJnJiSmax8msFJ/IOLszT2Tij6OYPvl+uUDgf3TyMZ4aKWvsoT12+Wr3/Rr2Kt4qrx7QIWDcNuMIjwiBk1/441l7EQ/96h7zXzhRuxw7UE2yFdmKHMcy6w2JgrA12tz8AkeWhyoQGc30kk+b1XWINQVg1o/vd5uNMEA0n9kvuaGpKuj+DF9AOS9EQKgP2weRpNm7LVRkYeKPoCv+x+p2Su+OvPEOFnNyqsS9CT/x55eV+h0UfMfGwasefhPRWbK27FOmw= + RENOVATE_TOKEN: AgAGQjwkrGT2sEgyBTsBgamLaAu16ZTnpazUhyBeCDIPDqF5kPEdqvbqiCuhV09nH7uvz63tzoz1vYlt/P3BAgltMutqu67smYwKwK4Qfsbz8L4qbqz2KRpxqtyGkcAGcUrd3nvtaD6OWXV561Kn4Ob+LCj+YrdLMJz4iDnqzYno6P0EGeJ8Hbt0OC+i7RPM25chTV8+cMLDRPRP1IXAP6wqjRrlmhbAb6K9l1zSmz22mM4NjhPPVO9fpDkGXy5TijNgKSeFWy5grmgfFR26dwcDULgOz9oQmBqTvH+R7jXEoR6oNQMTJulSHvH2jvQ/AtcRXjRqi7zeeFURfW2KyS2qtymZtQOwDZvaLjhERCyVO++T++nigR22hfA4/iI3Bj/QhlEkoHI48Lbr7PVtfhNRzMjbI/w+6oPQm7fMAtmSdu724TwmW6YVhcMtfpo+eq3IKLwDjpy8ewtUU9ALfebG6Yx7I3exrVQT9F2f5L6ef+V+G8VqchhcnP8UjJfc+ptrHEtkVN0YgmTGslOXhLi2m/yh3Xbuwfae/CaiJnaCfIQxGsL5Ux7f4Ny7du3Af2ch8n+nIGGEzER0l/LR8LxkCONKJKuQSk0P2yVtUoxBoWOJbZVpr99tLjZzXbMkxd1wXcgi+Ul1oofJpQSI2X6EMuWZpJz0n5h0tumoWPKDFvcsq5L/mjwEG7+q23Vjp5TrN7vEU6SjO2PjDjWpy838nXLowgxUkIb9Mr7zdW5ZpBHhR26kCjHE template: metadata: creationTimestamp: null labels: allowedToBeCloned: "true" name: renovate-env - namespace: secrets + namespace: renovate type: Opaque From a702a1615528e436f0da931ad6446d28335d2bdf Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Tue, 14 Apr 2026 12:17:53 +0200 Subject: [PATCH 094/113] renovate token --- secrets/renovate-env-sealed.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/secrets/renovate-env-sealed.yaml b/secrets/renovate-env-sealed.yaml index 6ae41ba..7ba928c 100644 --- a/secrets/renovate-env-sealed.yaml +++ b/secrets/renovate-env-sealed.yaml @@ -7,8 +7,8 @@ metadata: namespace: renovate spec: encryptedData: - RENOVATE_GITHUB_COM_TOKEN: AgBU+ZlwMh/VfDR+5rxjzPRiDgfLtA45IJBONRw7LDjCcgGoct68yZhnuDEasItnbpy8LxsmafqG7mTK6d/vTIy5rxnQKfeT+6RjKAvfKtH8eb6BtOCbZoP7Q9l9jE0Ba8afRSH9rs6908E6Fc1KCqBVrJiEeJxgi55YpXN6THubj5DjLdbqJxIUMj2vgA3me/TdMTyzIKobwMBDXH5RmR8MAdFgzuBXrnUwpqtVISija5T70jHaPDHZLTlZNJFHfBu5nSEazVxoL+a+xKsATJLH9JrQ6Ssxv/REaotqpRMR42N2aynAS793LE9pDXZZJlI/VWBJAlP/IFoqjKyRRlr07NNpaKRE/EstaSIBjb1MczOya18Dp9SYaWU68QF5BrfPP8T+og/FRAB/MvVijejl8S53qi+yo7DwUOF68lmT43ymuisaHjQz7Kuj/UPDg9F/HBQ9srq63+vMN/x+7ld/z8lpHb85hUPEN69RBCrahCFR6QQrqzJkWYZsLWBTkNg9eTivcX4BPG9C6bv5zaWsvEmvp+Pe88WUdDk6nNK3F5iAqD4a/yFhtYdJnJiSmax8msFJ/IOLszT2Tij6OYPvl+uUDgf3TyMZ4aKWvsoT12+Wr3/Rr2Kt4qrx7QIWDcNuMIjwiBk1/441l7EQ/96h7zXzhRuxw7UE2yFdmKHMcy6w2JgrA12tz8AkeWhyoQGc30kk+b1XWINQVg1o/vd5uNMEA0n9kvuaGpKuj+DF9AOS9EQKgP2weRpNm7LVRkYeKPoCv+x+p2Su+OvPEOFnNyqsS9CT/x55eV+h0UfMfGwasefhPRWbK27FOmw= - RENOVATE_TOKEN: AgAGQjwkrGT2sEgyBTsBgamLaAu16ZTnpazUhyBeCDIPDqF5kPEdqvbqiCuhV09nH7uvz63tzoz1vYlt/P3BAgltMutqu67smYwKwK4Qfsbz8L4qbqz2KRpxqtyGkcAGcUrd3nvtaD6OWXV561Kn4Ob+LCj+YrdLMJz4iDnqzYno6P0EGeJ8Hbt0OC+i7RPM25chTV8+cMLDRPRP1IXAP6wqjRrlmhbAb6K9l1zSmz22mM4NjhPPVO9fpDkGXy5TijNgKSeFWy5grmgfFR26dwcDULgOz9oQmBqTvH+R7jXEoR6oNQMTJulSHvH2jvQ/AtcRXjRqi7zeeFURfW2KyS2qtymZtQOwDZvaLjhERCyVO++T++nigR22hfA4/iI3Bj/QhlEkoHI48Lbr7PVtfhNRzMjbI/w+6oPQm7fMAtmSdu724TwmW6YVhcMtfpo+eq3IKLwDjpy8ewtUU9ALfebG6Yx7I3exrVQT9F2f5L6ef+V+G8VqchhcnP8UjJfc+ptrHEtkVN0YgmTGslOXhLi2m/yh3Xbuwfae/CaiJnaCfIQxGsL5Ux7f4Ny7du3Af2ch8n+nIGGEzER0l/LR8LxkCONKJKuQSk0P2yVtUoxBoWOJbZVpr99tLjZzXbMkxd1wXcgi+Ul1oofJpQSI2X6EMuWZpJz0n5h0tumoWPKDFvcsq5L/mjwEG7+q23Vjp5TrN7vEU6SjO2PjDjWpy838nXLowgxUkIb9Mr7zdW5ZpBHhR26kCjHE + RENOVATE_GITHUB_COM_TOKEN: AgBliRXcjCrWeZKa3A0RuQatvluoF30R8HEAt7aVJwSf9EbMtB/ozbtsbHxYTsyfpdnaewD+BMRbaDdQOTpmlPxfK5eqKFuLXJGCXmiiMEHvK3GG0y0STjfgC2GyrXvtmh/lg/Awod9ZneJ2+Xoi0X7ZIngWwHXmgvsRfczKtwZAZm/YVDLGHRW2BCwBgzStZquopxYGQRa58WSx9XkE/cV8UOb6Kw+4fGMKggJPr8ERUQyermCZ0HBOpkQwPpWoPhRFdCMj2qasVLttkK/jLG2romy3JZ59RGrQ7bj2p9IdJujqtteWb4vDdOc4jvplKWlB8QptZ872a2MPeyvopqkCsmnsfi6tup6uBaUzE8Hym0olpqK73dCjc4RP7x64KwsdGOyi2aJgEz1/Ro7GMu7sam5sdpTjpdA3EbyC8tZrnlC51mUlq4ghP5XELGj6DjvLWZ3dk0UOSQ4vQ/KLGyA29VBgvV3MdabpGNhs5VlU69p4RofxoFT9n9CrtJN8FPOnMUK6L58Zf3w+eYJQMuyttgf/h/stQUx6OU7R4d/4xA1j4YCFRxw2mXaYO9Tjsxsq2KB1v3TzyZKtZfYLeiNhv/rAfPs7krACs1ncNXVtS7sP3G14+Asvu6EdnlM86LThBmIeKRW8lSry5pO3IMOIzNGrqulFnuTGP9fy9cs5d58q/E4sUXoY0Liiod92+1RO8dpyn+xf9afCAei8lG6TFNPWdLzcl8WOJgZLWmRdRNhezOmSZHxnh8P/Ul2oxhrVZSYGLhvIfBVU9oY+s7lgw8FpM08RLdP3OsKigs5/uiE/56qHZHvj2YZf6mg= + RENOVATE_TOKEN: AgCV8DmDz1CT+wjtghW9ERy9tY/GCZfehPbd3vqikGx8tsZzxEoKukJzkeRJmsHqDTXylFMZHB+saYuk+vWOeMT7oGs/6wIC7eC08GN4OcBYpihrTSrzoAkp1WwLJV0KoNGbtbgoQXdlPJOWBjs6X90mrFmpwRewcaADks/uS/ONZw3MhiSbukYN3L45uL6b6gUp1Yl4nDyCyvCDoRpr1FQt2MMltyChimUtLXoMrnV3iO0oFlFMITJRjvauYrKXdD0xyWxji3Rbrowbgr/iFkz/0fTpg6AwhP/ISRWtO+NDB3kZ6/CvcK2a1phTIgf2eiHfvMQIwnpfRauyUPtNHkCInC7OOtn0eyHZmAqkSaJTN1rJN3X8lRIgjhtZr9upE6ZCLK5kMdq87oDjtvwc0X0AyeTxV3EGXh87Zfda97KhG5LSC3uShzc8AYPA7SGxMTuoF4wON0Qa7AdAeGvtL+2GuhRkHhmyOeY4iORrVo8TlJsN1DsZmrbRArJWzvMcb31gefxTmg8tSZhuLOo4RHsUayCNIPmW/AvpcbGJS1pPXS8nPkDqSoeL6UW/G8CoEEisr50+9ztGOCmurobbadHcs4LhwAC2s9SuYuCTTSK4c98ke901ks4Iho8TuG3WmBkgJlEsNMigMsnW6pwlx/Xnij06csRY8Xcfh77MjluQXx833oKRxffwC9NuIgXcBG0cqGraEF6RFxKlu3DsWRIElt5Rnju/WSNhDsWubKrMVtKXuYX0tTBn template: metadata: creationTimestamp: null From 1d43ecddad0c2be73f8ee0befabbbf65eaebe00e Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Tue, 14 Apr 2026 12:26:46 +0200 Subject: [PATCH 095/113] renovate daily and more mem --- infra/values/renovate-values.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/infra/values/renovate-values.yaml b/infra/values/renovate-values.yaml index 0feb572..a627de0 100644 --- a/infra/values/renovate-values.yaml +++ b/infra/values/renovate-values.yaml @@ -1,5 +1,5 @@ cronjob: - schedule: "@hourly" + schedule: "@daily" concurrencyPolicy: Forbid renovate: @@ -21,8 +21,8 @@ env: resources: requests: - cpu: 250m - memory: 512Mi - limits: - cpu: "1" + cpu: 500m memory: 1Gi + limits: + cpu: "2" + memory: 4Gi From c63a9242f0162eea02bbb9ed99d13a51c7cf9999 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Tue, 14 Apr 2026 12:44:47 +0200 Subject: [PATCH 096/113] renovate loglevel --- infra/values/renovate-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/renovate-values.yaml b/infra/values/renovate-values.yaml index a627de0..ce4b785 100644 --- a/infra/values/renovate-values.yaml +++ b/infra/values/renovate-values.yaml @@ -17,7 +17,7 @@ envFrom: name: renovate-env env: - LOG_LEVEL: debug + LOG_LEVEL: info resources: requests: From 177150e069d48972e9b4fbb95d10c7716f277c9d Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Wed, 15 Apr 2026 13:27:14 +0200 Subject: [PATCH 097/113] gitea protocol mapper --- docs/REFERENCE.md | 2 +- infra/values/keycloak-values.yaml | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 222d042..715e554 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -813,7 +813,7 @@ postgresql: persistence: 8Gi (upcloud-block-storage-maxiops) ``` -**Authentication**: Keycloak OIDC via `forte` realm (client ID: `gitea`) +**Authentication**: Keycloak OIDC via `forte` realm (client ID: `gitea`). Protocol mapper: `email_verified` hardcoded claim (`true`, boolean) on ID token, Access token, and Userinfo. **Endpoints**: - Web UI: `https://git.forteapps.net` diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index c3eaa8d..3f07394 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -78,7 +78,22 @@ keycloakConfigCli: "publicClient": false, "redirectUris": ["https://git.forteapps.net/*"], "webOrigins": ["https://git.forteapps.net"], - "defaultClientScopes": ["openid", "email", "profile"] + "defaultClientScopes": ["openid", "email", "profile"], + "protocolMappers": [ + { + "name": "email_verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "config": { + "claim.name": "email_verified", + "claim.value": "true", + "jsonType.label": "boolean", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] } ] } From db8a1de7977adb4ea5ffc2f26396d531faeef11d Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Wed, 15 Apr 2026 13:46:13 +0200 Subject: [PATCH 098/113] 10x repo PRs --- infra/values/renovate-values.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/infra/values/renovate-values.yaml b/infra/values/renovate-values.yaml index ce4b785..f329b48 100644 --- a/infra/values/renovate-values.yaml +++ b/infra/values/renovate-values.yaml @@ -9,7 +9,13 @@ renovate: "platform": "gitea", "endpoint": "https://git.forteapps.net", "autodiscover": true, - "gitAuthor": "Renovate Bot " + "gitAuthor": "Renovate Bot ", + "packageRules": [ + { + "matchRepositories": ["**/10x"], + "assignees": ["edvard.unsvag"] + } + ] } envFrom: From 87ee0588a777eeeab0a86bd36f63a9d1ff2f3cbc Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Wed, 15 Apr 2026 16:33:58 +0200 Subject: [PATCH 099/113] renovate pr targets --- infra/values/renovate-values.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/infra/values/renovate-values.yaml b/infra/values/renovate-values.yaml index f329b48..11e2391 100644 --- a/infra/values/renovate-values.yaml +++ b/infra/values/renovate-values.yaml @@ -14,6 +14,14 @@ renovate: { "matchRepositories": ["**/10x"], "assignees": ["edvard.unsvag"] + }, + { + "matchRepositories": ["**/auth-sidecar"], + "assignees": ["danijel.simeunovic"] + }, + { + "matchRepositories": ["**/forte-helm"], + "assignees": ["danijel.simeunovic"] } ] } From 88c29565b63319b4a0926abdbf462454b174c918 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 10:42:35 +0200 Subject: [PATCH 100/113] smtp --- infra/values/gitea-values.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index d5a33cf..ed14c49 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -65,6 +65,26 @@ gitea: ISSUE_INDEXER_TYPE: bleve REPO_INDEXER_ENABLED: true + mailer: + ENABLED: true + PROTOCOL: smtps + SMTP_ADDR: smtp.forteapps.net + SMTP_PORT: 465 + FROM: "noreply@forteapps.net" + + # -- SMTP credentials injected from secret (USER and PASSWD) + additionalConfigFromEnvs: + - name: GITEA__mailer__USER + valueFrom: + secretKeyRef: + name: gitea-smtp-secret + key: username + - name: GITEA__mailer__PASSWD + valueFrom: + secretKeyRef: + name: gitea-smtp-secret + key: password + # -- OIDC authentication via Forte oauth: - name: "Forte" From 7e10954a8f9a57ef8bbeac9b435d328047aa8df4 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 13:55:13 +0200 Subject: [PATCH 101/113] client secret bootstrapping --- docs/DEVELOPER-GUIDE.md | 132 +++++++++++++++++- docs/REFERENCE.md | 61 ++++++++- infra/keycloak.yaml | 6 + infra/values/gitea-values.yaml | 4 +- infra/values/keycloak-values.yaml | 213 +++++++++++++++++++++++++++++- 5 files changed, 411 insertions(+), 5 deletions(-) diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index dc06e3e..f812c5b 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -9,6 +9,7 @@ - [Updating an Existing Application](#updating-an-existing-application) - [Working with Secrets](#working-with-secrets) - [Enabling Authentication for Applications](#enabling-authentication-for-applications) +- [Adding a New Keycloak Client](#adding-a-new-keycloak-client) - [Troubleshooting](#troubleshooting) - [Best Practices](#best-practices) @@ -1247,6 +1248,135 @@ kubectl logs -n myapp -c authn --- +## Adding a New Keycloak Client + +When you need an application to authenticate via Keycloak (OIDC), you can add a client definition to the realm config. The secret syncer automatically extracts the Keycloak-generated client secret into a Kubernetes Secret that your application can reference — no manual secret management needed. + +### How It Works + +1. You define a client in `forte-realm.json` (inside `keycloak-values.yaml`) **without** a `secret` field +2. Keycloak auto-generates a cryptographically strong secret on first creation +3. An ArgoCD **PostSync Job** (`keycloak-secret-syncer`) runs after each Keycloak sync: + - Authenticates to the Keycloak Admin API + - Finds clients with `k8s.secret.sync: "true"` in their attributes + - Extracts the auto-generated secret for each client + - Creates/updates a K8s Secret in the target namespace with `client-id` and `client-secret` keys +4. Your application references the syncer-created Secret + +### Step 1: Add Client to Realm Config + +In `infra/values/keycloak-values.yaml`, add a new entry to the `clients` array in `forte-realm.json`: + +```json +{ + "clientId": "myapp", + "name": "My Application", + "enabled": true, + "protocol": "openid-connect", + "clientAuthenticatorType": "client-secret", + "standardFlowEnabled": true, + "directAccessGrantsEnabled": false, + "publicClient": false, + "redirectUris": ["https://myapp.forteapps.net/*"], + "webOrigins": ["https://myapp.forteapps.net"], + "defaultClientScopes": ["openid", "email", "profile"], + "attributes": { + "k8s.secret.sync": "true", + "k8s.secret.namespace": "myapp", + "k8s.secret.name": "myapp-oidc-credentials" + } +} +``` + +**Important**: +- Do **NOT** include a `"secret"` field — Keycloak generates one automatically +- The `attributes` block tells the syncer where to create the K8s Secret +- The target namespace must exist before the syncer runs (ArgoCD creates it via `CreateNamespace=true`) + +### Step 2: Reference the Secret in Your Application + +In your application's Helm values, reference the syncer-created secret: + +```yaml +# In helm-values/myapp/values.yaml (or inline in values file) +# The secret will have keys: client-id, client-secret +existingSecret: myapp-oidc-credentials +key: client-secret +``` + +For Gitea-style oauth config: +```yaml +oauth: +- name: "Forte" + provider: "openidConnect" + existingSecret: myapp-oidc-credentials + key: client-secret + autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" +``` + +### Step 3: Commit and Push + +```bash +cd ~/dev/k8s/launchpad +git add infra/values/keycloak-values.yaml +git commit -m "Add myapp Keycloak client with auto-sync" +git push +``` + +ArgoCD will: +1. Sync the Keycloak config (keycloakConfigCli creates the client) +2. Run the PostSync syncer Job +3. The syncer creates `myapp-oidc-credentials` in the `myapp` namespace + +### Step 4: Verify + +```bash +# Check the syncer job ran successfully +kubectl get jobs -n keycloak +kubectl logs -n keycloak job/keycloak-secret-syncer + +# Verify the secret was created +kubectl get secret myapp-oidc-credentials -n myapp -o yaml + +# Check the secret has the expected keys +kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-id}' | base64 -d +kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-secret}' | base64 -d +``` + +### Sync Attribute Reference + +| Attribute | Required | Description | +|-----------|----------|-------------| +| `k8s.secret.sync` | Yes | Set to `"true"` to enable syncing | +| `k8s.secret.namespace` | Yes | Target K8s namespace for the secret | +| `k8s.secret.name` | Yes | Name of the K8s Secret to create | + +### Retrieving Secrets for External Deployments + +The syncer always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster: + +```bash +# View the central copy +kubectl get secret gitea-oidc-credentials -n secrets -o yaml + +# Extract the client secret for use elsewhere +kubectl get secret myapp-oidc-credentials -n secrets \ + -o jsonpath='{.data.client-secret}' | base64 -d +``` + +This is useful when an application runs on a separate cluster or external infrastructure and needs the Keycloak-generated OIDC credentials provisioned manually (e.g., via a SealedSecret on the remote side). + +### Syncer Behavior Notes + +- The syncer runs as an ArgoCD **PostSync hook** — it executes after all Keycloak resources are healthy +- `BeforeHookCreation` delete policy ensures old Job is cleaned up before each run +- If the target namespace doesn't exist, the target write is skipped with a warning (the central copy still happens) +- A central copy is **always** written to the `secrets` namespace for every synced client +- The syncer uses the `keycloak-credentials` secret for admin authentication +- Created secrets have the label `app.kubernetes.io/managed-by: keycloak-secret-syncer` + +--- + ## Troubleshooting ### Application Not Deploying @@ -1579,4 +1709,4 @@ Now that you understand the basics: - Docs: [Full documentation index](README.md) - Help: Contact platform team -**Last Updated**: 2026-03-16 +**Last Updated**: 2026-04-16 diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 715e554..3cee384 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -869,6 +869,65 @@ dind: - Gitea admin panel (`/admin/runners`) — runners show as Online - Create test workflow in `.gitea/workflows/test.yml` — job executes +### Keycloak Secret Syncer + +**Type**: ArgoCD PostSync Job (deployed via Keycloak Helm chart `extraDeploy`) +**Namespace**: `keycloak` + +**Purpose**: Automatically extracts Keycloak-generated client secrets and syncs them into Kubernetes Secrets in target namespaces. Eliminates the need to manually manage OIDC client secrets. + +**How It Works**: +1. Runs as an ArgoCD PostSync hook after Keycloak resources are healthy +2. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret +3. Queries all clients in the `forte` realm +4. Filters clients with `k8s.secret.sync: "true"` attribute +5. For each matching client, retrieves the auto-generated secret via Keycloak Admin API +6. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute) +7. Always writes a central copy to the `secrets` namespace (for external deployment retrieval) + +**Resources**: +- `ServiceAccount`: `keycloak-secret-syncer` (namespace: `keycloak`) +- `ClusterRole`: `keycloak-secret-syncer` (secrets: get/create/update/patch; namespaces: get/list) +- `ClusterRoleBinding`: `keycloak-secret-syncer` +- `Job`: `keycloak-secret-syncer` (PostSync hook) + +**Client Attributes** (set in `forte-realm.json`): + +| Attribute | Description | +|-----------|-------------| +| `k8s.secret.sync` | Set to `"true"` to enable syncing | +| `k8s.secret.namespace` | Target K8s namespace | +| `k8s.secret.name` | Name of the K8s Secret | + +**Created Secret Format**: +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: + namespace: + labels: + app.kubernetes.io/managed-by: keycloak-secret-syncer +type: Opaque +data: + client-id: + client-secret: +``` + +**Verification**: +```bash +# Check job status +kubectl get jobs -n keycloak + +# View syncer logs +kubectl logs -n keycloak job/keycloak-secret-syncer + +# Verify created secret +kubectl get secret -n -o yaml +``` + +**See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client) + ### Renovate **Chart**: `renovate` (OCI: `ghcr.io/renovatebot/charts`) @@ -1528,6 +1587,6 @@ team: platform --- -**Last Updated**: 2026-04-14 +**Last Updated**: 2026-04-16 **Maintained By**: Platform Team **Version**: 1.0.0 diff --git a/infra/keycloak.yaml b/infra/keycloak.yaml index 66b38fa..be9d5ef 100644 --- a/infra/keycloak.yaml +++ b/infra/keycloak.yaml @@ -40,3 +40,9 @@ spec: - CreateNamespace=true - Validate=true - ServerSideApply=true + + ignoreDifferences: + - group: batch + kind: Job + jsonPointers: + - /spec/template/spec/containers/0/args diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index d5a33cf..b20f588 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -69,8 +69,8 @@ gitea: oauth: - name: "Forte" provider: "openidConnect" - existingSecret: gitea-credentials - key: gitea + existingSecret: gitea-oidc-credentials + key: client-secret autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" scopes: "openid email profile organization" groupClaimName: "groups" diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index 3f07394..d709513 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -72,13 +72,17 @@ keycloakConfigCli: "enabled": true, "protocol": "openid-connect", "clientAuthenticatorType": "client-secret", - "secret": "382ed413580cb79d0f54813e5da87007b28fe766a8903d378b9e1c266405a784", "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "publicClient": false, "redirectUris": ["https://git.forteapps.net/*"], "webOrigins": ["https://git.forteapps.net"], "defaultClientScopes": ["openid", "email", "profile"], + "attributes": { + "k8s.secret.sync": "true", + "k8s.secret.namespace": "gitea", + "k8s.secret.name": "gitea-oidc-credentials" + }, "protocolMappers": [ { "name": "email_verified", @@ -97,3 +101,210 @@ keycloakConfigCli: } ] } + +extraDeploy: +# -- ServiceAccount for the secret syncer Job +- apiVersion: v1 + kind: ServiceAccount + metadata: + name: keycloak-secret-syncer + namespace: keycloak + +# -- ClusterRole granting access to secrets and namespaces +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: keycloak-secret-syncer + rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create", "update", "patch"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] + +# -- ClusterRoleBinding for the syncer ServiceAccount +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: keycloak-secret-syncer + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: keycloak-secret-syncer + subjects: + - kind: ServiceAccount + name: keycloak-secret-syncer + namespace: keycloak + +# -- PostSync Job: extracts Keycloak client secrets into K8s Secrets +- apiVersion: batch/v1 + kind: Job + metadata: + name: keycloak-secret-syncer + namespace: keycloak + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + spec: + backoffLimit: 3 + template: + spec: + serviceAccountName: keycloak-secret-syncer + restartPolicy: Never + containers: + - name: syncer + image: alpine:3.20 + command: ["/bin/sh", "-c"] + args: + - | + set -e + apk add --no-cache curl jq > /dev/null 2>&1 + + KEYCLOAK_URL="http://keycloak:80" + REALM="forte" + + # Read admin credentials from the keycloak-credentials secret + ADMIN_USER="admin" + ADMIN_PASS=$(cat /secrets/admin-password) + + # Authenticate to Keycloak Admin API + echo "Authenticating to Keycloak..." + TOKEN=$(curl -sf -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \ + -d "client_id=admin-cli" \ + -d "username=${ADMIN_USER}" \ + -d "password=${ADMIN_PASS}" \ + -d "grant_type=password" | jq -r '.access_token') + + if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then + echo "ERROR: Failed to authenticate to Keycloak" + exit 1 + fi + + # Get all clients in the realm + echo "Fetching clients from realm '${REALM}'..." + CLIENTS=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients") + + # Filter clients with k8s.secret.sync=true + SYNC_CLIENTS=$(echo "$CLIENTS" | jq -c '[.[] | select(.attributes["k8s.secret.sync"] == "true")]') + COUNT=$(echo "$SYNC_CLIENTS" | jq 'length') + echo "Found ${COUNT} client(s) with sync enabled" + + K8S_API="https://kubernetes.default.svc" + SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + CENTRAL_NS="secrets" + + # Upsert a K8s Secret: try PUT (update), fall back to POST (create) + upsert_secret() { + local ns="$1" name="$2" manifest="$3" + local code + code=$(curl -sf -o /dev/null -w "%{http_code}" \ + --cacert "$CA_CERT" \ + -H "Authorization: Bearer ${SA_TOKEN}" \ + -H "Content-Type: application/json" \ + -X PUT -d "$manifest" \ + "${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}") + if [ "$code" = "200" ]; then + echo " Updated secret '${ns}/${name}'" + elif [ "$code" = "404" ]; then + code=$(curl -sf -o /dev/null -w "%{http_code}" \ + --cacert "$CA_CERT" \ + -H "Authorization: Bearer ${SA_TOKEN}" \ + -H "Content-Type: application/json" \ + -X POST -d "$manifest" \ + "${K8S_API}/api/v1/namespaces/${ns}/secrets") + if [ "$code" = "201" ]; then + echo " Created secret '${ns}/${name}'" + else + echo " ERROR: Failed to create secret '${ns}/${name}' (HTTP ${code})" + return 1 + fi + else + echo " ERROR: Failed to update secret '${ns}/${name}' (HTTP ${code})" + return 1 + fi + } + + # Build a Secret JSON manifest + build_manifest() { + local ns="$1" name="$2" b64_id="$3" b64_secret="$4" + cat < secret '${TARGET_NS}/${TARGET_NAME}'" + + # Get the client secret from Keycloak + SECRET_VALUE=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \ + | jq -r '.value') + + if [ -z "$SECRET_VALUE" ] || [ "$SECRET_VALUE" = "null" ]; then + echo " WARNING: No secret found for client '${CLIENT_ID}', skipping" + continue + fi + + B64_CLIENT_ID=$(printf '%s' "$CLIENT_ID" | base64 | tr -d '\n') + B64_SECRET=$(printf '%s' "$SECRET_VALUE" | base64 | tr -d '\n') + + # 1. Write to target namespace (if it exists) + NS_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \ + --cacert "$CA_CERT" \ + -H "Authorization: Bearer ${SA_TOKEN}" \ + "${K8S_API}/api/v1/namespaces/${TARGET_NS}") + + if [ "$NS_STATUS" = "200" ]; then + MANIFEST=$(build_manifest "$TARGET_NS" "$TARGET_NAME" "$B64_CLIENT_ID" "$B64_SECRET") + upsert_secret "$TARGET_NS" "$TARGET_NAME" "$MANIFEST" || exit 1 + else + echo " WARNING: Namespace '${TARGET_NS}' does not exist, skipping target" + fi + + # 2. Always write a central copy to the secrets namespace + CENTRAL_MANIFEST=$(build_manifest "$CENTRAL_NS" "$TARGET_NAME" "$B64_CLIENT_ID" "$B64_SECRET") + upsert_secret "$CENTRAL_NS" "$TARGET_NAME" "$CENTRAL_MANIFEST" || exit 1 + done + + echo "Secret sync complete" + volumeMounts: + - name: keycloak-credentials + mountPath: /secrets + readOnly: true + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + volumes: + - name: keycloak-credentials + secret: + secretName: keycloak-credentials + items: + - key: admin-password + path: admin-password From 020dfeffd4e53a97930b01c88c848e25ae3351c6 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 15:04:27 +0200 Subject: [PATCH 102/113] client secret fixes --- docs/DEVELOPER-GUIDE.md | 19 +++++++++++++------ docs/REFERENCE.md | 18 ++++++++++-------- infra/values/gitea-values.yaml | 2 +- infra/values/keycloak-values.yaml | 21 ++++++++++++++------- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index f812c5b..d9b13c3 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -1283,7 +1283,9 @@ In `infra/values/keycloak-values.yaml`, add a new entry to the `clients` array i "attributes": { "k8s.secret.sync": "true", "k8s.secret.namespace": "myapp", - "k8s.secret.name": "myapp-oidc-credentials" + "k8s.secret.name": "myapp-oidc-credentials", + "k8s.secret.client-id-key": "key", + "k8s.secret.client-secret-key": "secret" } } ``` @@ -1292,6 +1294,7 @@ In `infra/values/keycloak-values.yaml`, add a new entry to the `clients` array i - Do **NOT** include a `"secret"` field — Keycloak generates one automatically - The `attributes` block tells the syncer where to create the K8s Secret - The target namespace must exist before the syncer runs (ArgoCD creates it via `CreateNamespace=true`) +- Set `client-id-key` / `client-secret-key` to match what the consuming app expects (defaults: `client-id` / `client-secret`) ### Step 2: Reference the Secret in Your Application @@ -1345,11 +1348,15 @@ kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-se ### Sync Attribute Reference -| Attribute | Required | Description | -|-----------|----------|-------------| -| `k8s.secret.sync` | Yes | Set to `"true"` to enable syncing | -| `k8s.secret.namespace` | Yes | Target K8s namespace for the secret | -| `k8s.secret.name` | Yes | Name of the K8s Secret to create | +| Attribute | Required | Default | Description | +|-----------|----------|---------|-------------| +| `k8s.secret.sync` | Yes | — | Set to `"true"` to enable syncing | +| `k8s.secret.namespace` | Yes | — | Target K8s namespace for the secret | +| `k8s.secret.name` | Yes | — | Name of the K8s Secret to create | +| `k8s.secret.client-id-key` | No | `client-id` | Field name for the client ID in the K8s Secret | +| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for the client secret in the K8s Secret | + +**Note on key names:** Different applications expect different field names. For example, the Gitea Helm chart expects `key` and `secret`, while a generic OIDC consumer might expect `client-id` and `client-secret`. Use the optional key attributes to match what the consuming application expects. ### Retrieving Secrets for External Deployments diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 3cee384..837a0d4 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -893,13 +893,15 @@ dind: **Client Attributes** (set in `forte-realm.json`): -| Attribute | Description | -|-----------|-------------| -| `k8s.secret.sync` | Set to `"true"` to enable syncing | -| `k8s.secret.namespace` | Target K8s namespace | -| `k8s.secret.name` | Name of the K8s Secret | +| Attribute | Required | Default | Description | +|-----------|----------|---------|-------------| +| `k8s.secret.sync` | Yes | — | Set to `"true"` to enable syncing | +| `k8s.secret.namespace` | Yes | — | Target K8s namespace | +| `k8s.secret.name` | Yes | — | Name of the K8s Secret | +| `k8s.secret.client-id-key` | No | `client-id` | Field name for client ID in the Secret | +| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for client secret in the Secret | -**Created Secret Format**: +**Created Secret Format** (key names configurable via attributes): ```yaml apiVersion: v1 kind: Secret @@ -910,8 +912,8 @@ metadata: app.kubernetes.io/managed-by: keycloak-secret-syncer type: Opaque data: - client-id: - client-secret: + : + : ``` **Verification**: diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index b20f588..4391799 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -70,7 +70,7 @@ gitea: - name: "Forte" provider: "openidConnect" existingSecret: gitea-oidc-credentials - key: client-secret + key: gitea autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" scopes: "openid email profile organization" groupClaimName: "groups" diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index d709513..c151afe 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -81,7 +81,9 @@ keycloakConfigCli: "attributes": { "k8s.secret.sync": "true", "k8s.secret.namespace": "gitea", - "k8s.secret.name": "gitea-oidc-credentials" + "k8s.secret.name": "gitea-oidc-credentials", + "k8s.secret.client-id-key": "key", + "k8s.secret.client-secret-key": "secret" }, "protocolMappers": [ { @@ -228,8 +230,9 @@ extraDeploy: } # Build a Secret JSON manifest + # Args: namespace, name, id-key, secret-key, b64-id, b64-secret build_manifest() { - local ns="$1" name="$2" b64_id="$3" b64_secret="$4" + local ns="$1" name="$2" id_key="$3" secret_key="$4" b64_id="$5" b64_secret="$6" cat < secret '${TARGET_NS}/${TARGET_NAME}'" + # Configurable key names (defaults: client-id, client-secret) + ID_KEY=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.client-id-key"] // "client-id"') + SECRET_KEY=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.client-secret-key"] // "client-secret"') + + echo "Processing client '${CLIENT_ID}' -> secret '${TARGET_NS}/${TARGET_NAME}' (keys: ${ID_KEY}, ${SECRET_KEY})" # Get the client secret from Keycloak SECRET_VALUE=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ @@ -278,14 +285,14 @@ extraDeploy: "${K8S_API}/api/v1/namespaces/${TARGET_NS}") if [ "$NS_STATUS" = "200" ]; then - MANIFEST=$(build_manifest "$TARGET_NS" "$TARGET_NAME" "$B64_CLIENT_ID" "$B64_SECRET") + MANIFEST=$(build_manifest "$TARGET_NS" "$TARGET_NAME" "$ID_KEY" "$SECRET_KEY" "$B64_CLIENT_ID" "$B64_SECRET") upsert_secret "$TARGET_NS" "$TARGET_NAME" "$MANIFEST" || exit 1 else echo " WARNING: Namespace '${TARGET_NS}' does not exist, skipping target" fi # 2. Always write a central copy to the secrets namespace - CENTRAL_MANIFEST=$(build_manifest "$CENTRAL_NS" "$TARGET_NAME" "$B64_CLIENT_ID" "$B64_SECRET") + CENTRAL_MANIFEST=$(build_manifest "$CENTRAL_NS" "$TARGET_NAME" "$ID_KEY" "$SECRET_KEY" "$B64_CLIENT_ID" "$B64_SECRET") upsert_secret "$CENTRAL_NS" "$TARGET_NAME" "$CENTRAL_MANIFEST" || exit 1 done From 4486279eabb226719ccef2bab8f88c6cb315033e Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 15:13:18 +0200 Subject: [PATCH 103/113] smtp config --- infra/values/gitea-values.yaml | 7 +++---- secrets/gitea-smtp-secret-sealed.yaml | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 secrets/gitea-smtp-secret-sealed.yaml diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index ed14c49..fa07c42 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -68,9 +68,9 @@ gitea: mailer: ENABLED: true PROTOCOL: smtps - SMTP_ADDR: smtp.forteapps.net - SMTP_PORT: 465 - FROM: "noreply@forteapps.net" + SMTP_ADDR: smtp.office365.com + SMTP_PORT: 587 + FROM: "noreply@fortedigital.com" # -- SMTP credentials injected from secret (USER and PASSWD) additionalConfigFromEnvs: @@ -84,7 +84,6 @@ gitea: secretKeyRef: name: gitea-smtp-secret key: password - # -- OIDC authentication via Forte oauth: - name: "Forte" diff --git a/secrets/gitea-smtp-secret-sealed.yaml b/secrets/gitea-smtp-secret-sealed.yaml new file mode 100644 index 0000000..3fecd0b --- /dev/null +++ b/secrets/gitea-smtp-secret-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: gitea-smtp-secret + namespace: gitea +spec: + encryptedData: + password: AgBMuISQeA41mtBIOo686sND2EO12Jv7BIogL5G7xxt7wKfIk88dQrU74vs+fv3OtPce2Ra63QkR6po31M9fZkoiYba5yqJtEOc6em3y0ZxM/UzavbRvHwrvsWEYqmHBnkCUjcJijdGbSX+rHyGsXLfZO0gOjqXO379Zl1fzmV4p3F5REXFQm6HorVtWX/LOSRj9GDW76l6KgPR/A+CbtAx8Cq0m3D3fyiaVLEReP3uOgBOo70APHj9Yp56EcgOtVa1pEgCxR4ctXeS4t/EliWcHc/JT4TBdRBRDYPKLfME6FvJxjLjaSZcWxtJrJzCv3+vA5LlfObuHY31aSDRqYwO4VBCPhf3Aa6Z5UXgUnmAtJRhHa9pKSSjW48jgNb1jDPIkQn5XgB2/twJ+gX3inAkrTQ82JJ75Rz7XWC8KmYkOtkgXgU2buCa4nIfPeXOr5qvutyywxV1Ge1nK0fQYneQZVFXlHTbAQXBJMpVvJoJ+G3xGjm1904/iBGkVKmNrQwaABUsGBC6ZIHGOTa45GBqrg3ODU2Gr61SCYxv6m3pMU1msR7QYne0oqLCVD8mLDaeSeiQI4ZY9u4ddsVwM6l2BFrT6+3IQuYPBgOoodzDVlCgmA7hoekhpak9vZ0loSHaWDXdNt75SemAjsQfwCO5sSEkr+wbCJEQpXh5p38RMZKTuOh3nYEGQEx/MQNl3VD4FarK/zOJM9EO9IkqdM4LnqVo3zPX4KAPosS1PPKS8 + username: AgBF6MiaI1x2xQOUoF4NUh4MeFF64Db3vywcEO0FdJ0U9EirVFMsBSSiqJLy8ok43ha72s+/RLBNHiSSRKX1UMWwwCsfs+LQJNh9EetgHRxoyqkHiqRMX5V2acU2scdPE/FCFQFOYzAjweup+kP8xNu1WKuDtPBRiAgBNDfW59ihFi9TgOJQ2AnDHottjm5CNaWsbTOSgZrXqzCEChfHu5K0W9cty9ENHYqnYDfcm/zPLYeUPW0gVN5GJq3lPo9vZjM7T2JnwryjOkBaPCRCzHOpRF3bMrArFrjbUlH6gdI0APf4CzGLMMKol/jTMG2tLBseaNQHfbz6p1vFYExCL60gSN46fzh10zIWaIC2O+SgoLLOizkQZWf/v86cdRerBSl6PFmbRUO18XUQ4SyR/WPM71HD1jeLnUZjKtkOu+fqQKlv8kBSELHGqiURNYDnbmUA1LQpdNkDnMkRS+uzQ3XwWCSQBAn+u9wh69kg1oPVEN60Nc4KpNwFIg25aycGkP3cMklfl3/u9nr5KruwtJe+hl2ynSk8zeEFWWQrBki7+88CH9aWVW/GTA8Ho7Fz+gp4ZUdUA0WhH2LRAQIN945pvJIkHm/AYAhH6pZiXdBzYeguPY5VEf6hDVM1sa39aSZzs81cj0YHxbjR/BoBxbUAa9xW7JYH2rcIqXDhJB4zq2H++8e0eABsdQC3tMmE1eQA51d0yg8+2fX+CRkcvMCmI3VjS/mdirrtnctv + template: + metadata: + creationTimestamp: null + labels: + allowedToBeCloned: "true" + name: gitea-smtp-secret + namespace: gitea + type: Opaque From 61c2801e0aa14303929c47499fea4a50f4fcfad2 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 15:32:10 +0200 Subject: [PATCH 104/113] smtp --- infra/values/gitea-values.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index 74e88ac..3ff8450 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -67,11 +67,14 @@ gitea: mailer: ENABLED: true - PROTOCOL: smtps + PROTOCOL: smtp+starttls SMTP_ADDR: smtp.office365.com SMTP_PORT: 587 FROM: "noreply@fortedigital.com" + admin: + DEFAULT_EMAIL_NOTIFICATIONS: enabled + # -- SMTP credentials injected from secret (USER and PASSWD) additionalConfigFromEnvs: - name: GITEA__mailer__USER From 3e1029a557df4c63ac8ac3a9a7b0aa83930c5b75 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 15:39:51 +0200 Subject: [PATCH 105/113] mail notification --- infra/values/gitea-values.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index 3ff8450..7793dc7 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -29,6 +29,7 @@ gitea: ALLOW_ONLY_EXTERNAL_REGISTRATION: true ENABLE_BASIC_AUTHENTICATION: true ENABLE_PASSWORD_SIGNIN_FORM: false + ENABLE_NOTIFY_MAIL: true openid: ENABLE_OPENID_SIGNIN: false @@ -70,7 +71,7 @@ gitea: PROTOCOL: smtp+starttls SMTP_ADDR: smtp.office365.com SMTP_PORT: 587 - FROM: "noreply@fortedigital.com" + FROM: "svc-noreply@fortedigital.com" admin: DEFAULT_EMAIL_NOTIFICATIONS: enabled From 0eccd2d43977ca33bc54fc8c633268234e04c66d Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 15:43:10 +0200 Subject: [PATCH 106/113] smtp auth --- infra/values/gitea-values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index 7793dc7..8a46028 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -68,10 +68,10 @@ gitea: mailer: ENABLED: true - PROTOCOL: smtp+starttls + PROTOCOL: smtps SMTP_ADDR: smtp.office365.com SMTP_PORT: 587 - FROM: "svc-noreply@fortedigital.com" + FROM: "noreply@fortedigital.com" admin: DEFAULT_EMAIL_NOTIFICATIONS: enabled From 439b8516f028566542e2fed36ac1296ba9fb3dd7 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 15:46:54 +0200 Subject: [PATCH 107/113] smtps auth --- infra/values/gitea-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index 8a46028..cf89dc5 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -70,7 +70,7 @@ gitea: ENABLED: true PROTOCOL: smtps SMTP_ADDR: smtp.office365.com - SMTP_PORT: 587 + SMTP_PORT: 465 FROM: "noreply@fortedigital.com" admin: From 4485731ab5a692049ff0ca5720739d516e591457 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 16 Apr 2026 15:57:59 +0200 Subject: [PATCH 108/113] smtp+starttls --- infra/values/gitea-values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infra/values/gitea-values.yaml b/infra/values/gitea-values.yaml index cf89dc5..e34f256 100644 --- a/infra/values/gitea-values.yaml +++ b/infra/values/gitea-values.yaml @@ -68,9 +68,9 @@ gitea: mailer: ENABLED: true - PROTOCOL: smtps + PROTOCOL: smtp+starttls SMTP_ADDR: smtp.office365.com - SMTP_PORT: 465 + SMTP_PORT: 587 FROM: "noreply@fortedigital.com" admin: From 6639d0e3ffdbdb86e15c7504438ccdcea05175bf Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 17 Apr 2026 09:58:52 +0200 Subject: [PATCH 109/113] renovate prs --- docs/REFERENCE.md | 18 +++++++++++++++--- infra/values/renovate-values.yaml | 11 +++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 837a0d4..22bdfc4 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -943,7 +943,7 @@ kubectl get secret -n -o yaml ```yaml # infra/renovate.yaml + infra/values/renovate-values.yaml cronjob: - schedule: "@hourly" + schedule: "@daily" concurrencyPolicy: Forbid renovate: @@ -952,12 +952,24 @@ renovate: endpoint: https://git.forteapps.net autodiscover: true gitAuthor: "Renovate Bot " + packageRules: + - matchRepositories: ["**/10x"] + assignees: ["edvard.unsvag"] + reviewers: ["edvard.unsvag"] + - matchRepositories: ["**/auth-sidecar"] + assignees: ["danijel.simeunovic"] + reviewers: ["danijel.simeunovic"] + - matchRepositories: ["**/forte-helm"] + assignees: ["danijel.simeunovic"] + reviewers: ["danijel.simeunovic"] resources: - requests: { cpu: 250m, memory: 512Mi } - limits: { cpu: "1", memory: 1Gi } + requests: { cpu: 500m, memory: 1Gi } + limits: { cpu: "2", memory: 4Gi } ``` +**Note**: Assignees and reviewers are only applied at PR creation time. Existing PRs must be closed and recreated for new assignment rules to take effect. + **Secrets**: `renovate-env` (SealedSecret in `secrets` namespace, cloned by Kyverno) containing: - `RENOVATE_TOKEN` — Gitea PAT with repo write + issue write permissions - `RENOVATE_GITHUB_COM_TOKEN` — GitHub PAT (public_repo read-only) for changelog fetching diff --git a/infra/values/renovate-values.yaml b/infra/values/renovate-values.yaml index 11e2391..1525383 100644 --- a/infra/values/renovate-values.yaml +++ b/infra/values/renovate-values.yaml @@ -13,15 +13,18 @@ renovate: "packageRules": [ { "matchRepositories": ["**/10x"], - "assignees": ["edvard.unsvag"] + "assignees": ["edvard.unsvag"], + "reviewers": ["edvard.unsvag"] }, { "matchRepositories": ["**/auth-sidecar"], - "assignees": ["danijel.simeunovic"] + "assignees": ["danijel.simeunovic"], + "reviewers": ["danijel.simeunovic"] }, { "matchRepositories": ["**/forte-helm"], - "assignees": ["danijel.simeunovic"] + "assignees": ["danijel.simeunovic"], + "reviewers": ["danijel.simeunovic"] } ] } @@ -31,7 +34,7 @@ envFrom: name: renovate-env env: - LOG_LEVEL: info + LOG_LEVEL: debug resources: requests: From f8b17cc030fd51c932830a51f651378dec1ca3c3 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 17 Apr 2026 10:59:52 +0200 Subject: [PATCH 110/113] log level info renovate --- infra/values/renovate-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/values/renovate-values.yaml b/infra/values/renovate-values.yaml index 1525383..1ec12eb 100644 --- a/infra/values/renovate-values.yaml +++ b/infra/values/renovate-values.yaml @@ -34,7 +34,7 @@ envFrom: name: renovate-env env: - LOG_LEVEL: debug + LOG_LEVEL: info resources: requests: From b2f601e9501a64e468bba6f04979e7bc3c9547f6 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 17 Apr 2026 11:42:46 +0200 Subject: [PATCH 111/113] doc --- docs/DEVELOPER-GUIDE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index d9b13c3..9068b23 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -1312,8 +1312,7 @@ For Gitea-style oauth config: oauth: - name: "Forte" provider: "openidConnect" - existingSecret: myapp-oidc-credentials - key: client-secret + existingSecret: myapp-oidc-credentials # Gitea expects "key" and "secret" as fields autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" ``` From 44fc242ae8dd2f4e7a9a1201345249c863ff70b1 Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 17 Apr 2026 11:43:50 +0200 Subject: [PATCH 112/113] doc --- docs/DEVELOPER-GUIDE.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index 9068b23..bec5109 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -1304,7 +1304,6 @@ In your application's Helm values, reference the syncer-created secret: # In helm-values/myapp/values.yaml (or inline in values file) # The secret will have keys: client-id, client-secret existingSecret: myapp-oidc-credentials -key: client-secret ``` For Gitea-style oauth config: From 72a65f0e065da60629171cdd7a5e5d77a7232d4c Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Fri, 17 Apr 2026 13:42:44 +0000 Subject: [PATCH 113/113] client cloner (#3) Reviewed-on: https://git.forteapps.net/Forte/launchpad/pulls/3 Reviewed-by: gitea_admin Co-authored-by: Danijel Simeunovic Co-committed-by: Danijel Simeunovic --- .../policies/auth-sidecar-injector.yaml | 8 +- .../policies/keycloak-client-cloner.yaml | 37 ++ docs/DEVELOPER-GUIDE.md | 178 ++++--- docs/REFERENCE.md | 112 ++++- infra/keycloak.yaml | 4 +- infra/values/keycloak-values.yaml | 457 ++++++++++++------ 6 files changed, 551 insertions(+), 245 deletions(-) create mode 100644 cluster-resources/policies/keycloak-client-cloner.yaml diff --git a/cluster-resources/policies/auth-sidecar-injector.yaml b/cluster-resources/policies/auth-sidecar-injector.yaml index 6d47dc8..0babdc2 100644 --- a/cluster-resources/policies/auth-sidecar-injector.yaml +++ b/cluster-resources/policies/auth-sidecar-injector.yaml @@ -243,8 +243,8 @@ spec: - name: AUTH_OIDC_CLIENT_SECRET valueFrom: secretKeyRef: - name: auth-oidc - key: client-secret + name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}" + key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}" resources: limits: cpu: 50m @@ -410,8 +410,8 @@ spec: - name: AUTH_OAUTH_CLIENT_SECRET valueFrom: secretKeyRef: - name: auth-oauth - key: client-secret + name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret\" || 'auth-oauth' }}" + key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret-key\" || 'client-secret' }}" - name: AUTH_OAUTH_DELEGATION_CLIENT_SECRET valueFrom: secretKeyRef: diff --git a/cluster-resources/policies/keycloak-client-cloner.yaml b/cluster-resources/policies/keycloak-client-cloner.yaml new file mode 100644 index 0000000..d83c43c --- /dev/null +++ b/cluster-resources/policies/keycloak-client-cloner.yaml @@ -0,0 +1,37 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: keycloak-client-config-cloner +spec: + rules: + - name: clone-client-config-to-keycloak + skipBackgroundRequests: false + match: + any: + - resources: + kinds: + - Secret + selector: + matchLabels: + keycloak.forteapps.net/client-config: "true" + exclude: + any: + - resources: + namespaces: + - keycloak + generate: + apiVersion: v1 + kind: Secret + name: "{{request.object.metadata.name}}" + namespace: keycloak + synchronize: true + data: + metadata: + labels: + keycloak.forteapps.net/client-config: "true" + keycloak.forteapps.net/source-namespace: "{{request.object.metadata.namespace}}" + annotations: + keycloak.forteapps.net/source-name: "{{request.object.metadata.name}}" + keycloak.forteapps.net/source-namespace: "{{request.object.metadata.namespace}}" + data: "{{request.object.data}}" + type: "{{request.object.type}}" diff --git a/docs/DEVELOPER-GUIDE.md b/docs/DEVELOPER-GUIDE.md index bec5109..27b7ddc 100644 --- a/docs/DEVELOPER-GUIDE.md +++ b/docs/DEVELOPER-GUIDE.md @@ -1250,20 +1250,119 @@ kubectl logs -n myapp -c authn ## Adding a New Keycloak Client -When you need an application to authenticate via Keycloak (OIDC), you can add a client definition to the realm config. The secret syncer automatically extracts the Keycloak-generated client secret into a Kubernetes Secret that your application can reference — no manual secret management needed. +There are two ways to add an OIDC client, depending on your use case: -### How It Works +| Method | Best for | Who edits the infra repo? | +|--------|----------|--------------------------| +| **Self-service** (recommended) | New apps that deploy their own resources | App developer — no infra changes needed | +| **Legacy (realm JSON)** | Existing clients already defined in forte-realm.json (e.g., Gitea) | Platform engineer | -1. You define a client in `forte-realm.json` (inside `keycloak-values.yaml`) **without** a `secret` field -2. Keycloak auto-generates a cryptographically strong secret on first creation -3. An ArgoCD **PostSync Job** (`keycloak-secret-syncer`) runs after each Keycloak sync: - - Authenticates to the Keycloak Admin API - - Finds clients with `k8s.secret.sync: "true"` in their attributes - - Extracts the auto-generated secret for each client - - Creates/updates a K8s Secret in the target namespace with `client-id` and `client-secret` keys -4. Your application references the syncer-created Secret +Both methods are served by the **Keycloak Client Registrar** CronJob, which runs every 2 minutes. -### Step 1: Add Client to Realm Config +### Self-Service OIDC Client Registration + +This is the recommended flow for new applications. Your app deploys a labeled config Secret in its own namespace; the platform handles everything else. + +#### How It Works + +1. You deploy a Secret with label `keycloak.forteapps.net/client-config: "true"` containing a `client.json` definition +2. A **Kyverno ClusterPolicy** (`keycloak-client-config-cloner`) clones it to the `keycloak` namespace +3. The **Client Registrar CronJob** picks it up within 2 minutes: + - Registers (or updates) the client in Keycloak + - Fetches the auto-generated client secret + - Creates a credential Secret in your app's namespace + - Annotates the config Secret with sync status + +#### Step 1: Create the Config Secret + +Deploy this Secret in your application's namespace (e.g., as part of your Helm chart or Kustomize overlay): + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: keycloak-client-myapp + namespace: myapp + labels: + keycloak.forteapps.net/client-config: "true" +stringData: + client.json: | + { + "clientId": "myapp", + "name": "My Application", + "redirectUris": ["https://myapp.forteapps.net/*"], + "webOrigins": ["https://myapp.forteapps.net"], + "defaultClientScopes": ["openid", "email", "profile"], + "protocolMappers": [], + "secret": { + "namespace": "myapp", + "name": "myapp-oidc-credentials", + "keys": { "clientId": "client-id", "clientSecret": "client-secret" } + } + } +``` + +**`client.json` fields**: + +| Field | Required | Description | +|-------|----------|-------------| +| `clientId` | Yes | Keycloak client ID | +| `name` | Yes | Display name in Keycloak | +| `redirectUris` | Yes | Allowed redirect URIs | +| `webOrigins` | Yes | Allowed web origins (CORS) | +| `defaultClientScopes` | No | Scopes (default: `["openid", "email", "profile"]`) | +| `protocolMappers` | No | Custom claim mappers (default: `[]`) | +| `secret.namespace` | No | Namespace for the credential Secret (default: source namespace) | +| `secret.name` | No | Name of the credential Secret (default: `-oidc-credentials`) | +| `secret.keys.clientId` | No | Key name for client ID in credential Secret (default: `client-id`) | +| `secret.keys.clientSecret` | No | Key name for client secret in credential Secret (default: `client-secret`) | + +#### Step 2: Reference the Credential Secret + +In your application's deployment config, reference the credential Secret that the registrar creates: + +```yaml +env: +- name: OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: myapp-oidc-credentials + key: client-id +- name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: myapp-oidc-credentials + key: client-secret +``` + +#### Step 3: Deploy and Wait + +Commit and push your changes. The credential Secret will appear within 2 minutes: + +```bash +# Watch for the credential Secret to be created +kubectl get secret myapp-oidc-credentials -n myapp -w + +# Check registrar logs +kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}') + +# Check sync status on the config Secret +kubectl get secret keycloak-client-myapp -n keycloak -o jsonpath='{.metadata.annotations}' +``` + +#### Change Detection + +The registrar computes a SHA-256 hash of `client.json` and stores it as an annotation. On subsequent runs, it skips processing if: +- The hash hasn't changed, AND +- The credential Secret already exists in the target namespace + +To force a re-sync, update any field in `client.json` (e.g., add a trailing space to `name`). + +### Legacy Method: Realm JSON + +Existing clients (like Gitea) are defined directly in `forte-realm.json` inside `keycloak-values.yaml`. The registrar syncs their secrets via client attributes. + +#### Step 1: Add Client to Realm Config In `infra/values/keycloak-values.yaml`, add a new entry to the `clients` array in `forte-realm.json`: @@ -1292,30 +1391,16 @@ In `infra/values/keycloak-values.yaml`, add a new entry to the `clients` array i **Important**: - Do **NOT** include a `"secret"` field — Keycloak generates one automatically -- The `attributes` block tells the syncer where to create the K8s Secret -- The target namespace must exist before the syncer runs (ArgoCD creates it via `CreateNamespace=true`) +- The `attributes` block tells the registrar where to create the K8s Secret - Set `client-id-key` / `client-secret-key` to match what the consuming app expects (defaults: `client-id` / `client-secret`) -### Step 2: Reference the Secret in Your Application - -In your application's Helm values, reference the syncer-created secret: +#### Step 2: Reference the Secret in Your Application ```yaml -# In helm-values/myapp/values.yaml (or inline in values file) -# The secret will have keys: client-id, client-secret existingSecret: myapp-oidc-credentials ``` -For Gitea-style oauth config: -```yaml -oauth: -- name: "Forte" - provider: "openidConnect" - existingSecret: myapp-oidc-credentials # Gitea expects "key" and "secret" as fields - autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration" -``` - -### Step 3: Commit and Push +#### Step 3: Commit and Push ```bash cd ~/dev/k8s/launchpad @@ -1324,27 +1409,9 @@ git commit -m "Add myapp Keycloak client with auto-sync" git push ``` -ArgoCD will: -1. Sync the Keycloak config (keycloakConfigCli creates the client) -2. Run the PostSync syncer Job -3. The syncer creates `myapp-oidc-credentials` in the `myapp` namespace +ArgoCD will sync the Keycloak config, and the registrar CronJob will pick up the new client within 2 minutes. -### Step 4: Verify - -```bash -# Check the syncer job ran successfully -kubectl get jobs -n keycloak -kubectl logs -n keycloak job/keycloak-secret-syncer - -# Verify the secret was created -kubectl get secret myapp-oidc-credentials -n myapp -o yaml - -# Check the secret has the expected keys -kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-id}' | base64 -d -kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-secret}' | base64 -d -``` - -### Sync Attribute Reference +#### Legacy Sync Attribute Reference | Attribute | Required | Default | Description | |-----------|----------|---------|-------------| @@ -1354,11 +1421,9 @@ kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-se | `k8s.secret.client-id-key` | No | `client-id` | Field name for the client ID in the K8s Secret | | `k8s.secret.client-secret-key` | No | `client-secret` | Field name for the client secret in the K8s Secret | -**Note on key names:** Different applications expect different field names. For example, the Gitea Helm chart expects `key` and `secret`, while a generic OIDC consumer might expect `client-id` and `client-secret`. Use the optional key attributes to match what the consuming application expects. - ### Retrieving Secrets for External Deployments -The syncer always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster: +The registrar always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster: ```bash # View the central copy @@ -1369,16 +1434,13 @@ kubectl get secret myapp-oidc-credentials -n secrets \ -o jsonpath='{.data.client-secret}' | base64 -d ``` -This is useful when an application runs on a separate cluster or external infrastructure and needs the Keycloak-generated OIDC credentials provisioned manually (e.g., via a SealedSecret on the remote side). +### Registrar Behavior Notes -### Syncer Behavior Notes - -- The syncer runs as an ArgoCD **PostSync hook** — it executes after all Keycloak resources are healthy -- `BeforeHookCreation` delete policy ensures old Job is cleaned up before each run +- The registrar runs as a CronJob every 2 minutes (`concurrencyPolicy: Forbid`) - If the target namespace doesn't exist, the target write is skipped with a warning (the central copy still happens) - A central copy is **always** written to the `secrets` namespace for every synced client -- The syncer uses the `keycloak-credentials` secret for admin authentication -- Created secrets have the label `app.kubernetes.io/managed-by: keycloak-secret-syncer` +- The registrar uses the `keycloak-credentials` secret for admin authentication +- Created secrets have the label `app.kubernetes.io/managed-by: keycloak-client-registrar` --- diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 22bdfc4..5d72b57 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -123,6 +123,7 @@ launchpad/ │ ├── replicaset-cleaner.yaml │ ├── default-ns-blocker.yaml │ ├── secret-cloner.yaml +│ ├── keycloak-client-cloner.yaml │ └── auth-sidecar-injector.yaml │ ├── secrets/ # Application secrets (sealed) @@ -869,29 +870,44 @@ dind: - Gitea admin panel (`/admin/runners`) — runners show as Online - Create test workflow in `.gitea/workflows/test.yml` — job executes -### Keycloak Secret Syncer +### Keycloak Client Registrar -**Type**: ArgoCD PostSync Job (deployed via Keycloak Helm chart `extraDeploy`) +**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`) **Namespace**: `keycloak` +**Schedule**: `*/2 * * * *` (every 2 minutes) -**Purpose**: Automatically extracts Keycloak-generated client secrets and syncs them into Kubernetes Secrets in target namespaces. Eliminates the need to manually manage OIDC client secrets. +**Purpose**: Handles two responsibilities: +1. **Legacy sync** — extracts secrets from Keycloak clients with `k8s.secret.sync: "true"` attribute (same as former PostSync syncer) +2. **Self-service registration** — processes config Secrets (cloned by Kyverno) to register new OIDC clients and sync their credentials **How It Works**: -1. Runs as an ArgoCD PostSync hook after Keycloak resources are healthy -2. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret -3. Queries all clients in the `forte` realm -4. Filters clients with `k8s.secret.sync: "true"` attribute -5. For each matching client, retrieves the auto-generated secret via Keycloak Admin API -6. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute) -7. Always writes a central copy to the `secrets` namespace (for external deployment retrieval) + +*Legacy path (existing clients like Gitea):* +1. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret +2. Queries all clients in the `forte` realm +3. Filters clients with `k8s.secret.sync: "true"` attribute +4. For each matching client, retrieves the auto-generated secret via Keycloak Admin API +5. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute) +6. Always writes a central copy to the `secrets` namespace + +*Self-service path (new clients):* +1. Lists Secrets in `keycloak` namespace with label `keycloak.forteapps.net/client-config=true` +2. For each config Secret, parses `client.json` and computes a config hash +3. Skips if hash matches annotation and credential Secret already exists +4. Creates or updates the Keycloak client via Admin API +5. Fetches the generated client secret +6. Upserts credential Secret in target namespace + central `secrets` namespace +7. Annotates config Secret with sync status, config hash, and timestamp **Resources**: -- `ServiceAccount`: `keycloak-secret-syncer` (namespace: `keycloak`) -- `ClusterRole`: `keycloak-secret-syncer` (secrets: get/create/update/patch; namespaces: get/list) -- `ClusterRoleBinding`: `keycloak-secret-syncer` -- `Job`: `keycloak-secret-syncer` (PostSync hook) +- `ServiceAccount`: `keycloak-client-registrar` (namespace: `keycloak`) +- `ClusterRole`: `keycloak-client-registrar` (secrets: get/list/create/update/patch; namespaces: get/list) +- `ClusterRoleBinding`: `keycloak-client-registrar` +- `CronJob`: `keycloak-client-registrar` -**Client Attributes** (set in `forte-realm.json`): +**Kyverno Policy**: `keycloak-client-config-cloner` — clones labeled Secrets from app namespaces to `keycloak` namespace (see [Kyverno Policies](#kyverno-policies)) + +**Legacy Client Attributes** (set in `forte-realm.json`): | Attribute | Required | Default | Description | |-----------|----------|---------|-------------| @@ -901,31 +917,68 @@ dind: | `k8s.secret.client-id-key` | No | `client-id` | Field name for client ID in the Secret | | `k8s.secret.client-secret-key` | No | `client-secret` | Field name for client secret in the Secret | -**Created Secret Format** (key names configurable via attributes): +**Self-Service Config Secret Schema**: ```yaml apiVersion: v1 kind: Secret metadata: - name: - namespace: + name: keycloak-client- + namespace: labels: - app.kubernetes.io/managed-by: keycloak-secret-syncer + keycloak.forteapps.net/client-config: "true" +stringData: + client.json: | + { + "clientId": "", + "name": "", + "redirectUris": ["https://.forteapps.net/*"], + "webOrigins": ["https://.forteapps.net"], + "defaultClientScopes": ["openid", "email", "profile"], + "protocolMappers": [], + "secret": { + "namespace": "", + "name": "-oidc-credentials", + "keys": { "clientId": "client-id", "clientSecret": "client-secret" } + } + } +``` + +**Created Credential Secret Format**: +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: + namespace: + labels: + app.kubernetes.io/managed-by: keycloak-client-registrar type: Opaque data: : : ``` +**Config Secret Annotations** (set by registrar): + +| Annotation | Description | +|-----------|-------------| +| `keycloak.forteapps.net/config-hash` | SHA-256 hash of client.json for change detection | +| `keycloak.forteapps.net/sync-status` | `synced` or `error` | +| `keycloak.forteapps.net/last-sync` | ISO 8601 timestamp of last successful sync | + **Verification**: ```bash -# Check job status -kubectl get jobs -n keycloak +# Check CronJob status +kubectl get cronjobs -n keycloak -# View syncer logs -kubectl logs -n keycloak job/keycloak-secret-syncer +# View latest registrar logs +kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}') # Verify created secret kubectl get secret -n -o yaml + +# Check config Secret annotations (self-service) +kubectl get secret keycloak-client- -n keycloak -o jsonpath='{.metadata.annotations}' ``` **See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client) @@ -1020,6 +1073,19 @@ spec: **Label Requirement**: Secrets must have `allowedToBeCloned: "true"` +### Keycloak Client Config Cloner + +**File**: `cluster-resources/policies/keycloak-client-cloner.yaml` + +**Purpose**: Clones Secrets labeled `keycloak.forteapps.net/client-config: "true"` from app namespaces to the `keycloak` namespace. This allows apps to declare their OIDC client configuration in their own namespace, which the [Keycloak Client Registrar](#keycloak-client-registrar) then processes. + +**Trigger**: Any Secret with label `keycloak.forteapps.net/client-config: "true"` created outside the `keycloak` namespace. + +**Behavior**: +- Generates a copy of the Secret in the `keycloak` namespace with the same name +- Adds source tracking annotations (`keycloak.forteapps.net/source-namespace`, `keycloak.forteapps.net/source-name`) +- `synchronize: true` — changes to the source Secret are reflected in the clone + ### Default Namespace Blocker **File**: `cluster-resources/policies/default-ns-blocker.yaml` diff --git a/infra/keycloak.yaml b/infra/keycloak.yaml index be9d5ef..4b448f2 100644 --- a/infra/keycloak.yaml +++ b/infra/keycloak.yaml @@ -43,6 +43,6 @@ spec: ignoreDifferences: - group: batch - kind: Job + kind: CronJob jsonPointers: - - /spec/template/spec/containers/0/args + - /spec/jobTemplate/spec/template/spec/containers/0/args diff --git a/infra/values/keycloak-values.yaml b/infra/values/keycloak-values.yaml index c151afe..f1d7c4e 100644 --- a/infra/values/keycloak-values.yaml +++ b/infra/values/keycloak-values.yaml @@ -105,213 +105,354 @@ keycloakConfigCli: } extraDeploy: -# -- ServiceAccount for the secret syncer Job +# -- ServiceAccount for the client registrar CronJob - apiVersion: v1 kind: ServiceAccount metadata: - name: keycloak-secret-syncer + name: keycloak-client-registrar namespace: keycloak # -- ClusterRole granting access to secrets and namespaces - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: keycloak-secret-syncer + name: keycloak-client-registrar rules: - apiGroups: [""] resources: ["secrets"] - verbs: ["get", "create", "update", "patch"] + verbs: ["get", "list", "create", "update", "patch"] - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "list"] -# -- ClusterRoleBinding for the syncer ServiceAccount +# -- ClusterRoleBinding for the registrar ServiceAccount - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: keycloak-secret-syncer + name: keycloak-client-registrar roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: keycloak-secret-syncer + name: keycloak-client-registrar subjects: - kind: ServiceAccount - name: keycloak-secret-syncer + name: keycloak-client-registrar namespace: keycloak -# -- PostSync Job: extracts Keycloak client secrets into K8s Secrets +# -- CronJob: registers Keycloak clients and syncs secrets - apiVersion: batch/v1 - kind: Job + kind: CronJob metadata: - name: keycloak-secret-syncer + name: keycloak-client-registrar namespace: keycloak - annotations: - argocd.argoproj.io/hook: PostSync - argocd.argoproj.io/hook-delete-policy: BeforeHookCreation spec: - backoffLimit: 3 - template: + schedule: "*/2 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + jobTemplate: spec: - serviceAccountName: keycloak-secret-syncer - restartPolicy: Never - containers: - - name: syncer - image: alpine:3.20 - command: ["/bin/sh", "-c"] - args: - - | - set -e - apk add --no-cache curl jq > /dev/null 2>&1 + backoffLimit: 3 + template: + spec: + serviceAccountName: keycloak-client-registrar + restartPolicy: Never + containers: + - name: registrar + image: alpine:3.20 + command: ["/bin/sh", "-c"] + args: + - | + set -e + apk add --no-cache curl jq > /dev/null 2>&1 - KEYCLOAK_URL="http://keycloak:80" - REALM="forte" + KEYCLOAK_URL="http://keycloak:80" + REALM="forte" + K8S_API="https://kubernetes.default.svc" + SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) + CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + CENTRAL_NS="secrets" - # Read admin credentials from the keycloak-credentials secret - ADMIN_USER="admin" - ADMIN_PASS=$(cat /secrets/admin-password) + # --- Authenticate to Keycloak Admin API --- + ADMIN_USER="admin" + ADMIN_PASS=$(cat /secrets/admin-password) - # Authenticate to Keycloak Admin API - echo "Authenticating to Keycloak..." - TOKEN=$(curl -sf -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \ - -d "client_id=admin-cli" \ - -d "username=${ADMIN_USER}" \ - -d "password=${ADMIN_PASS}" \ - -d "grant_type=password" | jq -r '.access_token') + echo "Authenticating to Keycloak..." + TOKEN=$(curl -sf -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \ + -d "client_id=admin-cli" \ + -d "username=${ADMIN_USER}" \ + -d "password=${ADMIN_PASS}" \ + -d "grant_type=password" | jq -r '.access_token') - if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then - echo "ERROR: Failed to authenticate to Keycloak" - exit 1 - fi + if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then + echo "ERROR: Failed to authenticate to Keycloak" + exit 1 + fi - # Get all clients in the realm - echo "Fetching clients from realm '${REALM}'..." - CLIENTS=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ - "${KEYCLOAK_URL}/admin/realms/${REALM}/clients") + # --- Helper functions --- - # Filter clients with k8s.secret.sync=true - SYNC_CLIENTS=$(echo "$CLIENTS" | jq -c '[.[] | select(.attributes["k8s.secret.sync"] == "true")]') - COUNT=$(echo "$SYNC_CLIENTS" | jq 'length') - echo "Found ${COUNT} client(s) with sync enabled" + # Upsert a K8s Secret: try PUT (update), fall back to POST (create) + upsert_secret() { + local ns="$1" name="$2" manifest="$3" + local code + code=$(curl -sf -o /dev/null -w "%{http_code}" \ + --cacert "$CA_CERT" \ + -H "Authorization: Bearer ${SA_TOKEN}" \ + -H "Content-Type: application/json" \ + -X PUT -d "$manifest" \ + "${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}") + if [ "$code" = "200" ]; then + echo " Updated secret '${ns}/${name}'" + elif [ "$code" = "404" ]; then + code=$(curl -sf -o /dev/null -w "%{http_code}" \ + --cacert "$CA_CERT" \ + -H "Authorization: Bearer ${SA_TOKEN}" \ + -H "Content-Type: application/json" \ + -X POST -d "$manifest" \ + "${K8S_API}/api/v1/namespaces/${ns}/secrets") + if [ "$code" = "201" ]; then + echo " Created secret '${ns}/${name}'" + else + echo " ERROR: Failed to create secret '${ns}/${name}' (HTTP ${code})" + return 1 + fi + else + echo " ERROR: Failed to update secret '${ns}/${name}' (HTTP ${code})" + return 1 + fi + } - K8S_API="https://kubernetes.default.svc" - SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) - CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" - CENTRAL_NS="secrets" + # Build a credential Secret JSON manifest + build_credential_secret() { + local ns="$1" name="$2" id_key="$3" secret_key="$4" b64_id="$5" b64_secret="$6" + cat < '${TARGET_NS}/${TARGET_NAME}' (keys: ${ID_KEY}, ${SECRET_KEY})" + sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$TARGET_NS" "$TARGET_NAME" "$ID_KEY" "$SECRET_KEY" + done + + # ============================================= + # NEW PATH — self-service config Secrets + # ============================================= + echo "" + echo "=== Self-service: config Secrets with label keycloak.forteapps.net/client-config=true ===" + + CONFIG_SECRETS=$(curl -sf \ --cacert "$CA_CERT" \ -H "Authorization: Bearer ${SA_TOKEN}" \ - -H "Content-Type: application/json" \ - -X POST -d "$manifest" \ - "${K8S_API}/api/v1/namespaces/${ns}/secrets") - if [ "$code" = "201" ]; then - echo " Created secret '${ns}/${name}'" - else - echo " ERROR: Failed to create secret '${ns}/${name}' (HTTP ${code})" - return 1 - fi - else - echo " ERROR: Failed to update secret '${ns}/${name}' (HTTP ${code})" - return 1 - fi - } + "${K8S_API}/api/v1/namespaces/keycloak/secrets?labelSelector=keycloak.forteapps.net/client-config=true") - # Build a Secret JSON manifest - # Args: namespace, name, id-key, secret-key, b64-id, b64-secret - build_manifest() { - local ns="$1" name="$2" id_key="$3" secret_key="$4" b64_id="$5" b64_secret="$6" - cat < secret '${TARGET_NS}/${TARGET_NAME}' (keys: ${ID_KEY}, ${SECRET_KEY})" + CLIENT_ID=$(echo "$CLIENT_JSON" | jq -r '.clientId') + echo "Processing self-service client '${CLIENT_ID}' from config '${CONFIG_NAME}'" - # Get the client secret from Keycloak - SECRET_VALUE=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ - "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \ - | jq -r '.value') + # Compute config hash for change detection + CONFIG_HASH=$(printf '%s' "$CLIENT_JSON" | sha256sum | cut -d' ' -f1) + EXISTING_HASH=$(echo "$CONFIG_SECRET" | jq -r '.metadata.annotations["keycloak.forteapps.net/config-hash"] // ""') - if [ -z "$SECRET_VALUE" ] || [ "$SECRET_VALUE" = "null" ]; then - echo " WARNING: No secret found for client '${CLIENT_ID}', skipping" - continue - fi + # Extract secret delivery config from client.json + CRED_NS=$(echo "$CLIENT_JSON" | jq -r '.secret.namespace // "'"${SOURCE_NS}"'"') + CRED_NAME=$(echo "$CLIENT_JSON" | jq -r '.secret.name // "'"${CLIENT_ID}"'-oidc-credentials"') + CRED_ID_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientId // "client-id"') + CRED_SECRET_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientSecret // "client-secret"') - B64_CLIENT_ID=$(printf '%s' "$CLIENT_ID" | base64 | tr -d '\n') - B64_SECRET=$(printf '%s' "$SECRET_VALUE" | base64 | tr -d '\n') + # Check if credential Secret already exists in target namespace + CRED_EXISTS=$(curl -sf -o /dev/null -w "%{http_code}" \ + --cacert "$CA_CERT" \ + -H "Authorization: Bearer ${SA_TOKEN}" \ + "${K8S_API}/api/v1/namespaces/${CRED_NS}/secrets/${CRED_NAME}") - # 1. Write to target namespace (if it exists) - NS_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \ - --cacert "$CA_CERT" \ - -H "Authorization: Bearer ${SA_TOKEN}" \ - "${K8S_API}/api/v1/namespaces/${TARGET_NS}") + # Skip if hash matches and credential Secret exists + if [ "$CONFIG_HASH" = "$EXISTING_HASH" ] && [ "$CRED_EXISTS" = "200" ]; then + echo " No changes detected, skipping" + continue + fi - if [ "$NS_STATUS" = "200" ]; then - MANIFEST=$(build_manifest "$TARGET_NS" "$TARGET_NAME" "$ID_KEY" "$SECRET_KEY" "$B64_CLIENT_ID" "$B64_SECRET") - upsert_secret "$TARGET_NS" "$TARGET_NAME" "$MANIFEST" || exit 1 - else - echo " WARNING: Namespace '${TARGET_NS}' does not exist, skipping target" - fi + # Build Keycloak client representation (strip our secret delivery config) + KC_CLIENT=$(echo "$CLIENT_JSON" | jq '{ + clientId: .clientId, + name: .name, + enabled: true, + protocol: "openid-connect", + clientAuthenticatorType: "client-secret", + standardFlowEnabled: true, + directAccessGrantsEnabled: false, + publicClient: false, + redirectUris: .redirectUris, + webOrigins: .webOrigins, + defaultClientScopes: .defaultClientScopes, + protocolMappers: (.protocolMappers // []) + }') - # 2. Always write a central copy to the secrets namespace - CENTRAL_MANIFEST=$(build_manifest "$CENTRAL_NS" "$TARGET_NAME" "$ID_KEY" "$SECRET_KEY" "$B64_CLIENT_ID" "$B64_SECRET") - upsert_secret "$CENTRAL_NS" "$TARGET_NAME" "$CENTRAL_MANIFEST" || exit 1 - done + # Check if client already exists + EXISTING=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \ + | jq -r '.[0].id // empty') - echo "Secret sync complete" - volumeMounts: - - name: keycloak-credentials - mountPath: /secrets - readOnly: true - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 200m - memory: 128Mi - volumes: - - name: keycloak-credentials - secret: - secretName: keycloak-credentials - items: - - key: admin-password - path: admin-password + if [ -n "$EXISTING" ]; then + echo " Updating existing Keycloak client (uuid: ${EXISTING})" + HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -X PUT -d "$KC_CLIENT" \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${EXISTING}") + if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "200" ]; then + echo " ERROR: Failed to update client '${CLIENT_ID}' (HTTP ${HTTP_CODE})" + annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "error" + continue + fi + CLIENT_UUID="$EXISTING" + else + echo " Creating new Keycloak client '${CLIENT_ID}'" + HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + -X POST -d "$KC_CLIENT" \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients") + if [ "$HTTP_CODE" != "201" ]; then + echo " ERROR: Failed to create client '${CLIENT_ID}' (HTTP ${HTTP_CODE})" + annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "error" + continue + fi + # Fetch the newly created client's UUID + CLIENT_UUID=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \ + "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \ + | jq -r '.[0].id') + fi + + # Sync credentials to target namespace + sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$CRED_NS" "$CRED_NAME" "$CRED_ID_KEY" "$CRED_SECRET_KEY" + + # Annotate config Secret with hash and sync status + annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/config-hash" "$CONFIG_HASH" + annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "synced" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/last-sync" "$TIMESTAMP" + echo " Synced successfully" + done + + echo "" + echo "Client registrar run complete" + volumeMounts: + - name: keycloak-credentials + mountPath: /secrets + readOnly: true + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + volumes: + - name: keycloak-credentials + secret: + secretName: keycloak-credentials + items: + - key: admin-password + path: admin-password