├── 03-gitops ├── apps │ ├── 01-platform │ │ ├── argocd │ │ │ ├── kustomization.yaml │ │ │ └── argocd-ingress.yaml │ │ ├── longhorn │ │ │ ├── kustomization.yaml │ │ │ └── longhorn-ingress.yaml │ │ ├── cert-manager │ │ │ ├── kustomization.yaml │ │ │ ├── clusterissuer.yaml │ │ │ ├── issuer-ca.yaml │ │ │ └── certificate.yaml │ │ └── cilium │ │ │ ├── kustomization.yaml │ │ │ └── values.yaml │ ├── 02-monitoring │ │ ├── hubble │ │ │ ├── kustomization.yaml │ │ │ └── hubble-ui-ingress.yaml │ │ ├── loki │ │ │ ├── values.yaml │ │ │ ├── kustomization.yaml │ │ │ └── loki-ingress.yaml │ │ ├── jaeger │ │ │ ├── values.yaml │ │ │ ├── kustomization.yaml │ │ │ └── jaeger-ingress.yaml │ │ ├── tempo │ │ │ ├── kustomization.yaml │ │ │ ├── values.yaml │ │ │ └── tempo-ingress.yaml │ │ ├── otel-collector │ │ │ ├── kustomization.yaml │ │ │ ├── otel-collector-rbac.yaml │ │ │ └── values.yaml │ │ └── kube-prometheus-stack │ │ │ ├── grafana-ingress.yaml │ │ │ ├── prometheus-ingress.yaml │ │ │ ├── alertmanager-ingress.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── values.yaml │ │ │ └── otel-grafana-dashboards │ │ │ ├── exemplars-dashboard.json │ │ │ └── demo-dashboard.json │ └── 03-demo │ │ └── otel-demo │ │ ├── kustomization.yaml │ │ └── base │ │ ├── kustomization.yaml │ │ ├── namespace.yaml │ │ ├── ingress.yaml │ │ ├── accounting.yaml │ │ ├── fraud-detection.yaml │ │ ├── valkey-cart.yaml │ │ ├── email.yaml │ │ ├── currency.yaml │ │ ├── shipping.yaml │ │ ├── ad.yaml │ │ ├── image-provider.yaml │ │ ├── quote.yaml │ │ ├── payment.yaml │ │ ├── kafka.yaml │ │ ├── recommendation.yaml │ │ ├── cart.yaml │ │ ├── load-generator.yaml │ │ ├── checkout.yaml │ │ ├── frontend.yaml │ │ ├── frontend-proxy.yaml │ │ ├── flagd.yaml │ │ ├── opensearch.yaml │ │ └── product-catalog.yaml ├── applications │ ├── 03-otel-demo.yaml │ ├── 01-platform-bootstrap.yaml │ └── 02-monitoring-bootstrap.yaml └── README.md ├── .gitignore ├── assets ├── k9s.png ├── lens.png ├── tempo.png ├── argocd.png ├── grafana.png ├── hubble.png ├── jaeger.png ├── proxmox.png ├── load-gen.png ├── longhorn.png └── otel-demo.png ├── 01-infrastructure ├── patches │ ├── common │ │ ├── 04-disable-kubeproxy.yaml │ │ ├── 03-disable-network-cni.yaml │ │ ├── 00-enable-kubeprism.yaml │ │ ├── 01-enable-hostdns.yaml │ │ └── 02-enable-cluster-discovery.yaml │ ├── cilium-mode │ │ ├── common │ │ │ ├── 04-disable-kubeproxy.yaml │ │ │ └── 03-disable-network-cni.yaml │ │ └── controller │ │ │ ├── 02-lb-pool-manifest.yaml │ │ │ └── 01-install-cilium-job.yaml │ ├── worker │ │ └── 00-mount-longhorn-disk.yaml │ └── controller │ │ ├── 00-set-network-vip.yaml │ │ ├── 03-psa-longhorn-ns-manifest.yaml │ │ ├── 05-psa-monitoring-ns-manifest.yaml │ │ ├── 02-lb-pool-manifest.yaml │ │ ├── 04-prometheus-crd-job.yaml │ │ └── 01-install-cilium-job.yaml ├── terraform.tfvars ├── outputs.tf ├── providers.tf ├── locals.tf ├── README.md ├── talos_configs.tf ├── proxmox_nodes.tf └── variables.tf ├── 02-bootstrap ├── helmfile.yaml └── README.md ├── 00-prerequisite └── README.md └── README.md /03-gitops/apps/01-platform/argocd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - argocd-ingress.yaml -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/longhorn/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - longhorn-ingress.yaml -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/hubble/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - hubble-ui-ingress.yaml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | */.terraform/ 3 | .terraform.lock.hcl 4 | *.crt 5 | *.tfstate 6 | *.tfstate.backup -------------------------------------------------------------------------------- /assets/k9s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/k9s.png -------------------------------------------------------------------------------- /assets/lens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/lens.png -------------------------------------------------------------------------------- /assets/tempo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/tempo.png -------------------------------------------------------------------------------- /assets/argocd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/argocd.png -------------------------------------------------------------------------------- /assets/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/grafana.png -------------------------------------------------------------------------------- /assets/hubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/hubble.png -------------------------------------------------------------------------------- /assets/jaeger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/jaeger.png -------------------------------------------------------------------------------- /assets/proxmox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/proxmox.png -------------------------------------------------------------------------------- /assets/load-gen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/load-gen.png -------------------------------------------------------------------------------- /assets/longhorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/longhorn.png -------------------------------------------------------------------------------- /assets/otel-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SerhiiMyronets/terraform-talos-gitops-homelab/HEAD/assets/otel-demo.png -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - clusterissuer.yaml 3 | - certificate.yaml 4 | - issuer-ca.yaml -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - base -------------------------------------------------------------------------------- /01-infrastructure/patches/common/04-disable-kubeproxy.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to disable kube-proxy on Talos. 2 | cluster: 3 | proxy: 4 | disabled: true -------------------------------------------------------------------------------- /01-infrastructure/patches/cilium-mode/common/04-disable-kubeproxy.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to disable kube-proxy on Talos. 2 | cluster: 3 | proxy: 4 | disabled: true -------------------------------------------------------------------------------- /01-infrastructure/patches/common/03-disable-network-cni.yaml: -------------------------------------------------------------------------------- 1 | # This patch disables the CNI network plugin in Talos. 2 | cluster: 3 | network: 4 | cni: 5 | name: none 6 | -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/cert-manager/clusterissuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: selfsigned 5 | spec: 6 | selfSigned: {} -------------------------------------------------------------------------------- /01-infrastructure/terraform.tfvars: -------------------------------------------------------------------------------- 1 | proxmox_endpoint = "https://192.168.1.100:8006/" 2 | proxmox_username = "root@pam" 3 | proxmox_password = "changeme" 4 | proxmox_node_name = "proxmox" -------------------------------------------------------------------------------- /01-infrastructure/patches/cilium-mode/common/03-disable-network-cni.yaml: -------------------------------------------------------------------------------- 1 | # This patch disables the CNI network plugin in Talos. 2 | cluster: 3 | network: 4 | cni: 5 | name: none 6 | -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/cert-manager/issuer-ca.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: ingress 5 | spec: 6 | ca: 7 | secretName: ingress-tls -------------------------------------------------------------------------------- /01-infrastructure/patches/common/00-enable-kubeprism.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to enable kubePrism on Talos Linux. 2 | machine: 3 | features: 4 | kubePrism: 5 | enabled: true 6 | port: 7445 -------------------------------------------------------------------------------- /01-infrastructure/patches/common/01-enable-hostdns.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to enable host DNS in Talos. 2 | machine: 3 | features: 4 | hostDNS: 5 | enabled: true 6 | forwardKubeDNSToHost: true -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/loki/values.yaml: -------------------------------------------------------------------------------- 1 | loki: 2 | persistence: 3 | enabled: true 4 | storageClassName: longhorn 5 | size: 5Gi 6 | 7 | promtail: 8 | enabled: true 9 | 10 | grafana: 11 | enabled: false -------------------------------------------------------------------------------- /01-infrastructure/patches/worker/00-mount-longhorn-disk.yaml: -------------------------------------------------------------------------------- 1 | # This file is defined to mount the longhorn disk on the worker nodes. 2 | machine: 3 | disks: 4 | - device: /dev/sdb 5 | partitions: 6 | - mountpoint: /var/lib/longhorn -------------------------------------------------------------------------------- /01-infrastructure/patches/controller/00-set-network-vip.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to set the network VIP for the Talos controller nodes. 2 | machine: 3 | network: 4 | interfaces: 5 | - interface: eth0 6 | vip: 7 | ip: ${cluster_vip} 8 | -------------------------------------------------------------------------------- /01-infrastructure/patches/common/02-enable-cluster-discovery.yaml: -------------------------------------------------------------------------------- 1 | # This patch enables cluster discovery for Talos. 2 | cluster: 3 | discovery: 4 | enabled: true 5 | registries: 6 | kubernetes: 7 | disabled: false 8 | service: 9 | disabled: true 10 | -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/jaeger/values.yaml: -------------------------------------------------------------------------------- 1 | provisionDataStore: 2 | cassandra: false 3 | 4 | allInOne: 5 | enabled: true 6 | 7 | storage: 8 | type: memory 9 | 10 | agent: 11 | enabled: false 12 | 13 | collector: 14 | enabled: false 15 | 16 | query: 17 | enabled: false -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/tempo/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - tempo-ingress.yaml 3 | 4 | helmCharts: 5 | - name: tempo 6 | releaseName: tempo 7 | version: 1.21.0 8 | repo: https://grafana.github.io/helm-charts 9 | namespace: monitoring 10 | valuesFile: values.yaml -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/jaeger/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - jaeger-ingress.yaml 3 | 4 | helmCharts: 5 | - name: jaeger 6 | releaseName: jaeger 7 | version: 3.4.1 8 | repo: https://jaegertracing.github.io/helm-charts 9 | namespace: monitoring 10 | valuesFile: values.yaml -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/loki/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - loki-ingress.yaml 3 | 4 | helmCharts: 5 | - name: loki-stack 6 | releaseName: loki-stack 7 | version: 2.10.1 8 | repo: https://grafana.github.io/helm-charts 9 | namespace: monitoring 10 | valuesFile: values.yaml -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/tempo/values.yaml: -------------------------------------------------------------------------------- 1 | persistence: 2 | enabled: true 3 | storageClass: longhorn 4 | size: 10Gi 5 | 6 | tempo: 7 | search: 8 | enabled: true 9 | otlp: 10 | grpc: 11 | enabled: true 12 | http: 13 | enabled: true 14 | 15 | metricsGenerator: 16 | enabled: true -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/cilium/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: kube-system 5 | 6 | helmCharts: 7 | - name: cilium 8 | repo: https://helm.cilium.io 9 | version: 1.17.3 10 | releaseName: cilium 11 | namespace: kube-system 12 | valuesFile: values.yaml 13 | -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/otel-collector/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - otel-collector-rbac.yaml 3 | 4 | helmCharts: 5 | - name: opentelemetry-collector 6 | releaseName: otel-collector 7 | version: 0.120.0 8 | repo: https://open-telemetry.github.io/opentelemetry-helm-charts 9 | namespace: monitoring 10 | valuesFile: values.yaml -------------------------------------------------------------------------------- /01-infrastructure/patches/controller/03-psa-longhorn-ns-manifest.yaml: -------------------------------------------------------------------------------- 1 | cluster: 2 | inlineManifests: 3 | - name: longhorn-namespace 4 | contents: | 5 | apiVersion: v1 6 | kind: Namespace 7 | metadata: 8 | name: longhorn-system 9 | labels: 10 | pod-security.kubernetes.io/enforce: privileged 11 | pod-security.kubernetes.io/enforce-version: latest 12 | pod-security.kubernetes.io/audit: privileged 13 | pod-security.kubernetes.io/warn: privileged -------------------------------------------------------------------------------- /01-infrastructure/patches/controller/05-psa-monitoring-ns-manifest.yaml: -------------------------------------------------------------------------------- 1 | cluster: 2 | inlineManifests: 3 | - name: monitoring-namespace 4 | contents: | 5 | apiVersion: v1 6 | kind: Namespace 7 | metadata: 8 | name: monitoring 9 | labels: 10 | pod-security.kubernetes.io/enforce: privileged 11 | pod-security.kubernetes.io/enforce-version: latest 12 | pod-security.kubernetes.io/audit: privileged 13 | pod-security.kubernetes.io/warn: privileged -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/cert-manager/certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: ingress 5 | namespace: cert-manager 6 | spec: 7 | isCA: true 8 | commonName: Kubernetes Ingress 9 | secretName: ingress-tls 10 | privateKey: 11 | algorithm: ECDSA 12 | size: 256 13 | duration: 4320h 14 | subject: 15 | organizations: 16 | - cluster 17 | organizationalUnits: 18 | - Kubernetes 19 | issuerRef: 20 | name: selfsigned 21 | kind: ClusterIssuer 22 | group: cert-manager.io -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - namespace.yaml 3 | - accounting.yaml 4 | - ad.yaml 5 | - cart.yaml 6 | - checkout.yaml 7 | - currency.yaml 8 | - email.yaml 9 | - flagd.yaml 10 | - fraud-detection.yaml 11 | - frontend.yaml 12 | - frontend-proxy.yaml 13 | - image-provider.yaml 14 | - kafka.yaml 15 | - load-generator.yaml 16 | - payment.yaml 17 | - product-catalog.yaml 18 | - quote.yaml 19 | - recommendation.yaml 20 | - shipping.yaml 21 | - valkey-cart.yaml 22 | # - opensearch.yaml 23 | - ingress.yaml -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: otel-demo 5 | labels: 6 | # istio-injection: enabled 7 | pod-security.kubernetes.io/enforce: privileged 8 | pod-security.kubernetes.io/audit: privileged 9 | pod-security.kubernetes.io/warn: privileged 10 | --- 11 | apiVersion: v1 12 | kind: ServiceAccount 13 | metadata: 14 | name: otel-demo 15 | labels: 16 | helm.sh/chart: opentelemetry-demo-0.37.1 17 | 18 | app.kubernetes.io/instance: otel-demo 19 | app.kubernetes.io/version: "2.0.2" 20 | app.kubernetes.io/part-of: opentelemetry-demo 21 | app.kubernetes.io/managed-by: Helm -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/loki/loki-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: loki-ui 5 | namespace: monitoring 6 | annotations: 7 | cert-manager.io/cluster-issuer: ingress 8 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 9 | spec: 10 | ingressClassName: nginx 11 | tls: 12 | - hosts: 13 | - loki.cluster 14 | secretName: loki-tls 15 | rules: 16 | - host: loki.cluster 17 | http: 18 | paths: 19 | - path: / 20 | pathType: Prefix 21 | backend: 22 | service: 23 | name: loki-stack 24 | port: 25 | number: 3100 -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/tempo/tempo-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: tempo-ui 5 | namespace: monitoring 6 | annotations: 7 | cert-manager.io/cluster-issuer: ingress 8 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: tempo.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: tempo 20 | port: 21 | number: 3100 22 | # tls: 23 | # - hosts: 24 | # - tempo.cluster 25 | # secretName: tempo-tls -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/otel-collector/otel-collector-rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: otel-collector 5 | rules: 6 | - apiGroups: [""] 7 | resources: ["pods"] 8 | verbs: ["get", "list", "watch"] 9 | - apiGroups: ["apps"] 10 | resources: ["replicasets"] 11 | verbs: ["get", "list", "watch"] 12 | 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: ClusterRoleBinding 16 | metadata: 17 | name: otel-collector 18 | roleRef: 19 | apiGroup: rbac.authorization.k8s.io 20 | kind: ClusterRole 21 | name: otel-collector 22 | subjects: 23 | - kind: ServiceAccount 24 | name: otel-collector 25 | namespace: monitoring -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/hubble/hubble-ui-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: hubble-ui 5 | namespace: kube-system 6 | annotations: 7 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 8 | cert-manager.io/cluster-issuer: ingress 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: hubble.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: hubble-ui 20 | port: 21 | number: 80 22 | # tls: 23 | # - hosts: 24 | # - hubble.cluster 25 | # secretName: hubble-ui-tls -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/jaeger/jaeger-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: jaeger-ui 5 | namespace: monitoring 6 | annotations: 7 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 8 | cert-manager.io/cluster-issuer: ingress 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: jaeger.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: jaeger-query 20 | port: 21 | number: 16686 22 | # tls: 23 | # - hosts: 24 | # - jaeger.cluster 25 | # secretName: jaeger-ui-tls -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/argocd/argocd-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: argocd-server-ui 5 | namespace: argocd 6 | annotations: 7 | cert-manager.io/cluster-issuer: ingress 8 | nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: argocd.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: argocd-server 20 | port: 21 | number: 443 22 | # tls: 23 | # - hosts: 24 | # - argocd.cluster 25 | # secretName: argocd-server-tls 26 | -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/longhorn/longhorn-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: longhorn-ui 5 | namespace: longhorn-system 6 | annotations: 7 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 8 | cert-manager.io/cluster-issuer: ingress 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: longhorn.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: longhorn-frontend 20 | port: 21 | number: 80 22 | # tls: 23 | # - hosts: 24 | # - longhorn.cluster 25 | # secretName: longhorn-ui-tls -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/kube-prometheus-stack/grafana-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: grafana-ui 5 | namespace: monitoring 6 | annotations: 7 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 8 | cert-manager.io/cluster-issuer: ingress 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: grafana.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: kube-prometheus-stack-grafana 20 | port: 21 | number: 80 22 | # tls: 23 | # - hosts: 24 | # - grafana.cluster 25 | # secretName: grafana-ui-tls -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/kube-prometheus-stack/prometheus-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: prometheus-ui 5 | namespace: monitoring 6 | annotations: 7 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 8 | cert-manager.io/cluster-issuer: ingress 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: prometheus.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: kube-prometheus-stack-prometheus 20 | port: 21 | number: 9090 22 | # tls: 23 | # - hosts: 24 | # - prometheus.cluster 25 | # secretName: prometheus-ui-tls -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/kube-prometheus-stack/alertmanager-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: alertmanager-ui 5 | namespace: monitoring 6 | annotations: 7 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 8 | cert-manager.io/cluster-issuer: ingress 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: alertmanager.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: kube-prometheus-stack-alertmanager 20 | port: 21 | number: 9093 22 | # tls: 23 | # - hosts: 24 | # - alertmanager.cluster 25 | # secretName: alertmanager-ui-tls -------------------------------------------------------------------------------- /03-gitops/applications/03-otel-demo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: AppProject 3 | metadata: 4 | name: otel-demo 5 | namespace: argocd 6 | spec: 7 | sourceRepos: 8 | - '*' 9 | destinations: 10 | - namespace: '*' 11 | server: https://kubernetes.default.svc 12 | clusterResourceWhitelist: 13 | - group: '*' 14 | kind: '*' 15 | --- 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Application 18 | metadata: 19 | name: otel-demo 20 | namespace: argocd 21 | spec: 22 | project: otel-demo 23 | source: 24 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 25 | targetRevision: main 26 | path: 03-gitops/apps/03-demo/otel-demo 27 | destination: 28 | server: https://kubernetes.default.svc 29 | namespace: otel-demo 30 | syncPolicy: 31 | automated: {} 32 | syncOptions: 33 | - CreateNamespace=true -------------------------------------------------------------------------------- /01-infrastructure/outputs.tf: -------------------------------------------------------------------------------- 1 | // ============================================================================== 2 | // Talos Client Configuration Output 3 | // ============================================================================== 4 | 5 | output "talosconfig" { 6 | value = data.talos_client_configuration.talosconfig.talos_config 7 | sensitive = true 8 | } 9 | 10 | output "kubeconfig_command" { 11 | value = "terraform output -raw kubeconfig > ~/.kube/config" 12 | } 13 | 14 | // ============================================================================== 15 | // Kubernetes Kubeconfig Output 16 | // ============================================================================== 17 | 18 | output "kubeconfig" { 19 | value = talos_cluster_kubeconfig.kubeconfig.kubeconfig_raw 20 | sensitive = true 21 | } 22 | 23 | output "talosconfig_command" { 24 | value = "terraform output -raw talosconfig > ~/.talos/config" 25 | } 26 | -------------------------------------------------------------------------------- /01-infrastructure/patches/controller/02-lb-pool-manifest.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to set the load balancer IP pool for the Talos cluster. 2 | cluster: 3 | inlineManifests: 4 | - name: lb-pool-define 5 | contents: | 6 | --- 7 | apiVersion: cilium.io/v2alpha1 8 | kind: CiliumL2AnnouncementPolicy 9 | metadata: 10 | name: external 11 | spec: 12 | loadBalancerIPs: true 13 | interfaces: 14 | - eth0 15 | nodeSelector: 16 | matchExpressions: 17 | - key: node-role.kubernetes.io/control-plane 18 | operator: DoesNotExist 19 | --- 20 | apiVersion: cilium.io/v2alpha1 21 | kind: CiliumLoadBalancerIPPool 22 | metadata: 23 | name: external 24 | spec: 25 | blocks: 26 | - start: ${load_balancer_first_host} 27 | stop: ${load_balancer_last_host} -------------------------------------------------------------------------------- /01-infrastructure/patches/cilium-mode/controller/02-lb-pool-manifest.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to set the load balancer IP pool for the Talos cluster. 2 | cluster: 3 | inlineManifests: 4 | - name: lb-pool-define 5 | contents: | 6 | --- 7 | apiVersion: cilium.io/v2alpha1 8 | kind: CiliumL2AnnouncementPolicy 9 | metadata: 10 | name: external 11 | spec: 12 | loadBalancerIPs: true 13 | interfaces: 14 | - eth0 15 | nodeSelector: 16 | matchExpressions: 17 | - key: node-role.kubernetes.io/control-plane 18 | operator: DoesNotExist 19 | --- 20 | apiVersion: cilium.io/v2alpha1 21 | kind: CiliumLoadBalancerIPPool 22 | metadata: 23 | name: external 24 | spec: 25 | blocks: 26 | - start: ${load_balancer_first_host} 27 | stop: ${load_balancer_last_host} -------------------------------------------------------------------------------- /01-infrastructure/providers.tf: -------------------------------------------------------------------------------- 1 | // ============================================================================== 2 | // Terraform Settings 3 | // ============================================================================== 4 | 5 | terraform { 6 | required_providers { 7 | proxmox = { 8 | source = "bpg/proxmox" 9 | version = "0.73.0" 10 | } 11 | kubernetes = { 12 | source = "hashicorp/kubernetes" 13 | version = "~> 2.24" 14 | } 15 | talos = { 16 | source = "siderolabs/talos" 17 | version = "0.8.0-alpha.0" 18 | } 19 | } 20 | } 21 | 22 | // ============================================================================== 23 | // Proxmox Provider 24 | // ============================================================================== 25 | 26 | provider "proxmox" { 27 | endpoint = var.proxmox_endpoint 28 | username = var.proxmox_username 29 | password = var.proxmox_password 30 | insecure = true 31 | } -------------------------------------------------------------------------------- /03-gitops/apps/01-platform/cilium/values.yaml: -------------------------------------------------------------------------------- 1 | ipam: 2 | mode: kubernetes 3 | kubeProxyReplacement: true 4 | k8sServiceHost: localhost 5 | k8sServicePort: 7445 6 | 7 | cgroup: 8 | autoMount: 9 | enabled: false 10 | hostRoot: /sys/fs/cgroup 11 | 12 | devices: 13 | - eth0 14 | 15 | routingMode: tunnel 16 | tunnelProtocol: vxlan 17 | 18 | envoy: 19 | enabled: false 20 | 21 | l2announcements: 22 | enabled: true 23 | 24 | operator: 25 | replicas: 1 26 | prometheus: 27 | enabled: true 28 | 29 | prometheus: 30 | enabled: true 31 | 32 | securityContext: 33 | capabilities: 34 | ciliumAgent: 35 | - CHOWN 36 | - KILL 37 | - NET_ADMIN 38 | - NET_RAW 39 | - IPC_LOCK 40 | - SYS_ADMIN 41 | - SYS_RESOURCE 42 | - DAC_OVERRIDE 43 | - FOWNER 44 | - SETGID 45 | - SETUID 46 | cleanCiliumState: 47 | - NET_ADMIN 48 | - SYS_ADMIN 49 | - SYS_RESOURCE 50 | 51 | hubble: 52 | relay: 53 | enabled: true 54 | tls: 55 | auto: 56 | enabled: true 57 | ui: 58 | backend: 59 | enabled: true 60 | address: https://hubble-relay.kube-system.svc.cluster.local:443 61 | frontend: 62 | enabled: true 63 | enabled: true -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/kube-prometheus-stack/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - grafana-ingress.yaml 3 | - alertmanager-ingress.yaml 4 | - prometheus-ingress.yaml 5 | 6 | helmCharts: 7 | - name: kube-prometheus-stack 8 | releaseName: kube-prometheus-stack 9 | version: 72.5.2 10 | repo: https://prometheus-community.github.io/helm-charts 11 | namespace: monitoring 12 | valuesFile: values.yaml 13 | 14 | configMapGenerator: 15 | - name: demo-dashboard 16 | namespace: monitoring 17 | files: 18 | - otel-grafana-dashboards/demo-dashboard.json 19 | options: 20 | labels: 21 | grafana_dashboard: "1" 22 | 23 | - name: exemplars-dashboard 24 | namespace: monitoring 25 | files: 26 | - otel-grafana-dashboards/exemplars-dashboard.json 27 | options: 28 | labels: 29 | grafana_dashboard: "1" 30 | 31 | - name: otel-collector-dashboard 32 | namespace: monitoring 33 | files: 34 | - otel-grafana-dashboards/opentelemetry-collector.json 35 | options: 36 | labels: 37 | grafana_dashboard: "1" 38 | 39 | - name: spanmetrics-dashboard 40 | namespace: monitoring 41 | files: 42 | - otel-grafana-dashboards/spanmetrics-dashboard.json 43 | options: 44 | labels: 45 | grafana_dashboard: "1" 46 | 47 | generatorOptions: 48 | disableNameSuffixHash: true 49 | annotations: {} -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: otel-demo-ingress 5 | namespace: otel-demo 6 | annotations: 7 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 8 | cert-manager.io/cluster-issuer: ingress 9 | spec: 10 | ingressClassName: nginx 11 | rules: 12 | - host: otel-demo.cluster 13 | http: 14 | paths: 15 | - path: / 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: frontend-proxy 20 | port: 21 | number: 8080 22 | # tls: 23 | # - hosts: 24 | # - otel-demo.cluster 25 | # secretName: otel-demo-tls 26 | --- 27 | apiVersion: networking.k8s.io/v1 28 | kind: Ingress 29 | metadata: 30 | name: otel-demo-loadgen-ingress 31 | namespace: otel-demo 32 | annotations: 33 | nginx.ingress.kubernetes.io/backend-protocol: "HTTP" 34 | cert-manager.io/cluster-issuer: ingress 35 | spec: 36 | ingressClassName: nginx 37 | rules: 38 | - host: otel-demo-loadgen.cluster 39 | http: 40 | paths: 41 | - path: / 42 | pathType: Prefix 43 | backend: 44 | service: 45 | name: load-generator 46 | port: 47 | number: 8089 48 | # tls: 49 | # - hosts: 50 | # - otel-demo-loadgen.cluster 51 | # secretName: otel-demo-loadgen-tls -------------------------------------------------------------------------------- /02-bootstrap/helmfile.yaml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - name: jetstack 3 | url: https://charts.jetstack.io 4 | - name: ingress-nginx 5 | url: https://kubernetes.github.io/ingress-nginx 6 | - name: argo 7 | url: https://argoproj.github.io/argo-helm 8 | - name: longhorn 9 | url: https://charts.longhorn.io 10 | - name: metrics-server 11 | url: https://kubernetes-sigs.github.io/metrics-server 12 | 13 | releases: 14 | - name: metrics-server 15 | namespace: kube-system 16 | chart: metrics-server/metrics-server 17 | version: 3.12.1 18 | values: 19 | - args: 20 | - --kubelet-insecure-tls 21 | 22 | - name: cert-manager 23 | namespace: cert-manager 24 | chart: jetstack/cert-manager 25 | version: 1.16.2 26 | createNamespace: true 27 | values: 28 | - crds: 29 | enabled: true 30 | 31 | - name: ingress-nginx 32 | namespace: ingress-nginx 33 | chart: ingress-nginx/ingress-nginx 34 | version: 4.10.1 35 | createNamespace: true 36 | values: 37 | - controller: 38 | service: 39 | type: LoadBalancer 40 | 41 | - name: argocd 42 | namespace: argocd 43 | chart: argo/argo-cd 44 | version: 7.7.7 45 | createNamespace: true 46 | values: 47 | - configs: 48 | cm: 49 | kustomize.buildOptions: "--enable-helm" 50 | - server: 51 | extraArgs: 52 | - --disable-auth 53 | 54 | - name: longhorn 55 | namespace: longhorn-system 56 | chart: longhorn/longhorn 57 | version: 1.8.1 58 | createNamespace: false -------------------------------------------------------------------------------- /01-infrastructure/patches/controller/04-prometheus-crd-job.yaml: -------------------------------------------------------------------------------- 1 | cluster: 2 | inlineManifests: 3 | - name: prometheus-crds-install 4 | contents: | 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: prometheus-crds-install 10 | namespace: kube-system 11 | --- 12 | apiVersion: rbac.authorization.k8s.io/v1 13 | kind: ClusterRoleBinding 14 | metadata: 15 | name: prometheus-crds-install 16 | roleRef: 17 | apiGroup: rbac.authorization.k8s.io 18 | kind: ClusterRole 19 | name: cluster-admin 20 | subjects: 21 | - kind: ServiceAccount 22 | name: prometheus-crds-install 23 | namespace: kube-system 24 | --- 25 | apiVersion: batch/v1 26 | kind: Job 27 | metadata: 28 | name: prometheus-crds-install 29 | namespace: kube-system 30 | spec: 31 | backoffLimit: 10 32 | template: 33 | spec: 34 | serviceAccountName: prometheus-crds-install 35 | restartPolicy: OnFailure 36 | containers: 37 | - name: kubectl 38 | image: bitnami/kubectl:latest 39 | command: 40 | - /bin/sh 41 | - -c 42 | - | 43 | echo "Installing Prometheus CRDs..." 44 | kubectl apply -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.82.1/stripped-down-crds.yaml -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/kube-prometheus-stack/values.yaml: -------------------------------------------------------------------------------- 1 | crds: 2 | enabled: true 3 | 4 | kubeScheduler: 5 | enabled: true 6 | service: 7 | enabled: false 8 | serviceMonitor: 9 | enabled: false 10 | 11 | kubeControllerManager: 12 | enabled: true 13 | service: 14 | enabled: false 15 | serviceMonitor: 16 | enabled: false 17 | 18 | prometheus: 19 | prometheusSpec: 20 | storageSpec: 21 | volumeClaimTemplate: 22 | spec: 23 | storageClassName: longhorn 24 | accessModes: ["ReadWriteOnce"] 25 | resources: 26 | requests: 27 | storage: 5Gi 28 | 29 | alertmanager: 30 | alertmanagerSpec: 31 | storage: 32 | volumeClaimTemplate: 33 | spec: 34 | storageClassName: longhorn 35 | accessModes: ["ReadWriteOnce"] 36 | resources: 37 | requests: 38 | storage: 2Gi 39 | 40 | grafana: 41 | grafana.ini: 42 | auth: 43 | disable_login_form: true 44 | auth.anonymous: 45 | enabled: true 46 | org_role: Admin 47 | defaultDatasourceEnabled: false 48 | additionalDataSources: 49 | - name: Prometheus 50 | type: prometheus 51 | uid: webstore-metrics 52 | access: proxy 53 | url: http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090 54 | jsonData: 55 | exemplarTraceIdDestinations: 56 | - datasourceUid: webstore-traces 57 | name: trace_id 58 | - name: Jaeger 59 | type: jaeger 60 | uid: webstore-traces 61 | access: proxy 62 | url: http://jaeger-query.monitoring.svc.cluster.local:16686/jaeger/ui 63 | - name: Tempo 64 | type: tempo 65 | uid: tempo 66 | access: proxy 67 | url: http://tempo.monitoring.svc.cluster.local:3100 68 | - name: Loki 69 | type: loki 70 | uid: webstore-logs 71 | access: proxy 72 | url: http://loki-stack.monitoring.svc.cluster.local:3100 73 | sidecar: 74 | dashboards: 75 | enabled: true 76 | label: grafana_dashboard -------------------------------------------------------------------------------- /03-gitops/applications/01-platform-bootstrap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: AppProject 3 | metadata: 4 | name: platform 5 | namespace: argocd 6 | spec: 7 | sourceRepos: 8 | - '*' 9 | destinations: 10 | - namespace: '*' 11 | server: https://kubernetes.default.svc 12 | clusterResourceWhitelist: 13 | - group: '*' 14 | kind: '*' 15 | --- 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Application 18 | metadata: 19 | name: cert-manager 20 | namespace: argocd 21 | spec: 22 | project: platform 23 | source: 24 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 25 | targetRevision: main 26 | path: 03-gitops/apps/01-platform/cert-manager 27 | destination: 28 | server: https://kubernetes.default.svc 29 | namespace: argocd 30 | syncPolicy: 31 | automated: {} 32 | --- 33 | apiVersion: argoproj.io/v1alpha1 34 | kind: Application 35 | metadata: 36 | name: cilium 37 | namespace: argocd 38 | spec: 39 | project: platform 40 | source: 41 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 42 | targetRevision: main 43 | path: 03-gitops/apps/01-platform/cilium 44 | destination: 45 | server: https://kubernetes.default.svc 46 | namespace: argocd 47 | syncPolicy: 48 | automated: {} 49 | --- 50 | apiVersion: argoproj.io/v1alpha1 51 | kind: Application 52 | metadata: 53 | name: argocd 54 | namespace: argocd 55 | spec: 56 | project: platform 57 | source: 58 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 59 | targetRevision: main 60 | path: 03-gitops/apps/01-platform/argocd 61 | destination: 62 | server: https://kubernetes.default.svc 63 | namespace: argocd 64 | syncPolicy: 65 | automated: {} 66 | --- 67 | apiVersion: argoproj.io/v1alpha1 68 | kind: Application 69 | metadata: 70 | name: longhorn 71 | namespace: argocd 72 | spec: 73 | project: platform 74 | source: 75 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 76 | targetRevision: main 77 | path: 03-gitops/apps/01-platform/longhorn 78 | destination: 79 | server: https://kubernetes.default.svc 80 | namespace: argocd 81 | syncPolicy: 82 | automated: {} -------------------------------------------------------------------------------- /01-infrastructure/locals.tf: -------------------------------------------------------------------------------- 1 | // ============================================================================== 2 | // Local Definitions for Node IPs 3 | // ============================================================================== 4 | 5 | locals { 6 | controller_nodes = [ 7 | for i in range(var.controller_config.count) : { 8 | name = "controlplane-0${i + 1}" 9 | address = cidrhost(var.cluster_node_network, var.cluster_node_network_first_controller_hostnum + i) 10 | } 11 | ] 12 | 13 | worker_nodes = [ 14 | for i in range(var.worker_config.count) : { 15 | name = "worker-0${i + 1}" 16 | address = cidrhost(var.cluster_node_network, var.cluster_node_network_first_worker_hostnum + i) 17 | } 18 | ] 19 | } 20 | 21 | // ============================================================================== 22 | // Local Definitions for Patch Files 23 | // ============================================================================== 24 | 25 | locals { 26 | patch_base_path = "${path.module}/patches" 27 | 28 | common_patch_files = fileset("${local.patch_base_path}/common", "*.yaml") 29 | worker_patch_files = fileset("${local.patch_base_path}/worker", "*.yaml") 30 | controller_patch_files = fileset("${local.patch_base_path}/controller", "*.yaml") 31 | 32 | shared_patches = [ 33 | for f in local.common_patch_files : 34 | yamlencode(yamldecode(file("${local.patch_base_path}/common/${f}"))) 35 | ] 36 | 37 | worker_patches = [ 38 | for f in local.worker_patch_files : 39 | yamlencode(yamldecode(file("${local.patch_base_path}/worker/${f}"))) 40 | ] 41 | 42 | controller_patches = [ 43 | for f in local.controller_patch_files : 44 | yamlencode(yamldecode(templatefile("${local.patch_base_path}/controller/${f}", { 45 | cluster_vip = var.cluster_vip, 46 | load_balancer_first_host = cidrhost(var.cluster_node_network, var.load_balancer_ip_range.first), 47 | load_balancer_last_host = cidrhost(var.cluster_node_network, var.load_balancer_ip_range.last) 48 | }))) 49 | ] 50 | 51 | config_patches_worker = concat( 52 | local.shared_patches, 53 | local.worker_patches 54 | ) 55 | 56 | config_patches_controller = concat( 57 | local.shared_patches, 58 | local.controller_patches, 59 | ) 60 | } -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/accounting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: accounting 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: accounting 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: accounting 11 | app.kubernetes.io/name: accounting 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | replicas: 1 17 | revisionHistoryLimit: 10 18 | selector: 19 | matchLabels: 20 | 21 | opentelemetry.io/name: accounting 22 | template: 23 | metadata: 24 | labels: 25 | 26 | opentelemetry.io/name: accounting 27 | app.kubernetes.io/instance: otel-demo 28 | app.kubernetes.io/component: accounting 29 | app.kubernetes.io/name: accounting 30 | spec: 31 | serviceAccountName: otel-demo 32 | containers: 33 | - name: accounting 34 | image: 'ghcr.io/open-telemetry/demo:2.0.2-accounting' 35 | imagePullPolicy: IfNotPresent 36 | env: 37 | - name: OTEL_SERVICE_NAME 38 | valueFrom: 39 | fieldRef: 40 | apiVersion: v1 41 | fieldPath: metadata.labels['app.kubernetes.io/component'] 42 | - name: OTEL_COLLECTOR_NAME 43 | value: otel-collector.monitoring 44 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 45 | value: cumulative 46 | - name: KAFKA_ADDR 47 | value: kafka:9092 48 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 49 | value: http://$(OTEL_COLLECTOR_NAME):4318 50 | - name: OTEL_RESOURCE_ATTRIBUTES 51 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 52 | resources: 53 | limits: 54 | memory: 120Mi 55 | volumeMounts: 56 | initContainers: 57 | - command: 58 | - sh 59 | - -c 60 | - until nc -z -v -w30 kafka 9092; do echo waiting for kafka; sleep 2; done; 61 | image: busybox:latest 62 | name: wait-for-kafka 63 | volumes: -------------------------------------------------------------------------------- /02-bootstrap/README.md: -------------------------------------------------------------------------------- 1 | # 02-bootstrap 2 | 3 | This stage installs essential platform components into the Kubernetes cluster using Helmfile. 4 | 5 | It assumes that the cluster is already initialized and accessible using the generated `kubeconfig` from the previous stage (`01-infrastructure`). 6 | 7 | ## Purpose 8 | 9 | The components installed in this phase are required for certificate management, ingress routing, GitOps-based application delivery, persistent storage, and cluster metrics. 10 | 11 | ## Installed Components 12 | 13 | | Name | Purpose | 14 | | ---------------- | ---------------------------------------------- | 15 | | `metrics-server` | Enables resource metrics collection | 16 | | `cert-manager` | Manages TLS certificates via Kubernetes CRDs | 17 | | `ingress-nginx` | Provides ingress routing via NGINX controller | 18 | | `argo-cd` | GitOps controller for managing Kubernetes apps | 19 | | `longhorn` | Provides persistent storage for workloads | 20 | 21 | ## Usage 22 | 23 | Before running this stage, make sure you have: 24 | 25 | * Access to the cluster via `kubeconfig` 26 | * Talos cluster is fully bootstrapped and reachable 27 | * Helmfile and Helm installed on your machine 28 | 29 | To apply the bootstrap components: 30 | 31 | ```bash 32 | helmfile apply 33 | ``` 34 | 35 | This command installs all defined charts with their default or overridden configurations. 36 | 37 | Note: After applying, it may take 1–2 minutes for Longhorn to become fully ready as it initializes its internal components. 38 | 39 | You can check the status with: 40 | 41 | ```bash 42 | kubectl get deployments -n longhorn-system 43 | ``` 44 | 45 | Example output when ready: 46 | 47 | ``` 48 | NAME READY UP-TO-DATE AVAILABLE AGE 49 | csi-attacher 3/3 3 3 2m13s 50 | csi-provisioner 3/3 3 3 2m12s 51 | csi-resizer 3/3 3 3 2m12s 52 | csi-snapshotter 3/3 3 3 2m12s 53 | longhorn-driver-deployer 1/1 1 1 3m21s 54 | longhorn-ui 2/2 2 2 3m21s 55 | ``` 56 | 57 | ## Navigation 58 | 59 | [← Back to 01-infrastructure](../01-infrastructure/README.md) • [→ Continue to 03-gitops](../03-gitops/README.md) 60 | -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/fraud-detection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: fraud-detection 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: fraud-detection 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: fraud-detection 11 | app.kubernetes.io/name: fraud-detection 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | replicas: 1 17 | revisionHistoryLimit: 10 18 | selector: 19 | matchLabels: 20 | 21 | opentelemetry.io/name: fraud-detection 22 | template: 23 | metadata: 24 | labels: 25 | 26 | opentelemetry.io/name: fraud-detection 27 | app.kubernetes.io/instance: otel-demo 28 | app.kubernetes.io/component: fraud-detection 29 | app.kubernetes.io/name: fraud-detection 30 | spec: 31 | serviceAccountName: otel-demo 32 | containers: 33 | - name: fraud-detection 34 | image: 'ghcr.io/open-telemetry/demo:2.0.2-fraud-detection' 35 | imagePullPolicy: IfNotPresent 36 | env: 37 | - name: OTEL_SERVICE_NAME 38 | valueFrom: 39 | fieldRef: 40 | apiVersion: v1 41 | fieldPath: metadata.labels['app.kubernetes.io/component'] 42 | - name: OTEL_COLLECTOR_NAME 43 | value: otel-collector.monitoring 44 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 45 | value: cumulative 46 | - name: KAFKA_ADDR 47 | value: kafka:9092 48 | - name: FLAGD_HOST 49 | value: flagd 50 | - name: FLAGD_PORT 51 | value: "8013" 52 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 53 | value: http://$(OTEL_COLLECTOR_NAME):4318 54 | - name: OTEL_RESOURCE_ATTRIBUTES 55 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 56 | resources: 57 | limits: 58 | memory: 300Mi 59 | volumeMounts: 60 | initContainers: 61 | - command: 62 | - sh 63 | - -c 64 | - until nc -z -v -w30 kafka 9092; do echo waiting for kafka; sleep 2; done; 65 | image: busybox:latest 66 | name: wait-for-kafka 67 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/valkey-cart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: valkey-cart 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: valkey-cart 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: valkey-cart 11 | app.kubernetes.io/name: valkey-cart 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 6379 19 | name: valkey-cart 20 | targetPort: 6379 21 | selector: 22 | 23 | opentelemetry.io/name: valkey-cart 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: valkey-cart 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: valkey-cart 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: valkey-cart 35 | app.kubernetes.io/name: valkey-cart 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: valkey-cart 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: valkey-cart 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: valkey-cart 53 | app.kubernetes.io/name: valkey-cart 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: valkey-cart 58 | image: 'valkey/valkey:7.2-alpine' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 6379 63 | name: valkey-cart 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: OTEL_RESOURCE_ATTRIBUTES 75 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 76 | resources: 77 | limits: 78 | memory: 20Mi 79 | securityContext: 80 | runAsGroup: 1000 81 | runAsNonRoot: true 82 | runAsUser: 999 83 | volumeMounts: 84 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/email.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: email 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: email 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: email 11 | app.kubernetes.io/name: email 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: email 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: email 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: email 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: email 35 | app.kubernetes.io/name: email 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: email 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: email 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: email 53 | app.kubernetes.io/name: email 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: email 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-email' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: EMAIL_PORT 75 | value: "8080" 76 | - name: APP_ENV 77 | value: production 78 | - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT 79 | value: http://$(OTEL_COLLECTOR_NAME):4318/v1/traces 80 | - name: OTEL_RESOURCE_ATTRIBUTES 81 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 82 | resources: 83 | limits: 84 | memory: 100Mi 85 | volumeMounts: 86 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/currency.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: currency 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: currency 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: currency 11 | app.kubernetes.io/name: currency 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: currency 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: currency 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: currency 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: currency 35 | app.kubernetes.io/name: currency 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: currency 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: currency 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: currency 53 | app.kubernetes.io/name: currency 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: currency 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-currency' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: CURRENCY_PORT 75 | value: "8080" 76 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 77 | value: http://$(OTEL_COLLECTOR_NAME):4317 78 | - name: VERSION 79 | value: '2.0.2' 80 | - name: OTEL_RESOURCE_ATTRIBUTES 81 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 82 | resources: 83 | limits: 84 | memory: 20Mi 85 | volumeMounts: 86 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/shipping.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: shipping 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: shipping 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: shipping 11 | app.kubernetes.io/name: shipping 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: shipping 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: shipping 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: shipping 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: shipping 35 | app.kubernetes.io/name: shipping 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: shipping 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: shipping 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: shipping 53 | app.kubernetes.io/name: shipping 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: shipping 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-shipping' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: SHIPPING_PORT 75 | value: "8080" 76 | - name: QUOTE_ADDR 77 | value: http://quote:8080 78 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 79 | value: http://$(OTEL_COLLECTOR_NAME):4317 80 | - name: OTEL_RESOURCE_ATTRIBUTES 81 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 82 | resources: 83 | limits: 84 | memory: 20Mi 85 | volumeMounts: 86 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/ad.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: ad 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: ad 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: ad 11 | app.kubernetes.io/name: ad 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: ad 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: ad 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: ad 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: ad 35 | app.kubernetes.io/name: ad 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: ad 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: ad 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: ad 53 | app.kubernetes.io/name: ad 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: ad 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-ad' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: AD_PORT 75 | value: "8080" 76 | - name: FLAGD_HOST 77 | value: flagd 78 | - name: FLAGD_PORT 79 | value: "8013" 80 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 81 | value: http://$(OTEL_COLLECTOR_NAME):4318 82 | - name: OTEL_LOGS_EXPORTER 83 | value: otlp 84 | - name: OTEL_RESOURCE_ATTRIBUTES 85 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 86 | resources: 87 | limits: 88 | memory: 300Mi 89 | volumeMounts: 90 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/image-provider.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: image-provider 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: image-provider 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: image-provider 11 | app.kubernetes.io/name: image-provider 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8081 19 | name: tcp-service 20 | targetPort: 8081 21 | selector: 22 | 23 | opentelemetry.io/name: image-provider 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: image-provider 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: image-provider 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: image-provider 35 | app.kubernetes.io/name: image-provider 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: image-provider 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: image-provider 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: image-provider 53 | app.kubernetes.io/name: image-provider 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: image-provider 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-image-provider' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8081 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: IMAGE_PROVIDER_PORT 75 | value: "8081" 76 | - name: OTEL_COLLECTOR_PORT_GRPC 77 | value: "4317" 78 | - name: OTEL_COLLECTOR_HOST 79 | value: $(OTEL_COLLECTOR_NAME) 80 | - name: OTEL_RESOURCE_ATTRIBUTES 81 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 82 | resources: 83 | limits: 84 | memory: 50Mi 85 | volumeMounts: 86 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/quote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: quote 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: quote 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: quote 11 | app.kubernetes.io/name: quote 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: quote 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: quote 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: quote 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: quote 35 | app.kubernetes.io/name: quote 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: quote 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: quote 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: quote 53 | app.kubernetes.io/name: quote 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: quote 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-quote' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: QUOTE_PORT 75 | value: "8080" 76 | - name: OTEL_PHP_AUTOLOAD_ENABLED 77 | value: "true" 78 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 79 | value: http://$(OTEL_COLLECTOR_NAME):4318 80 | - name: OTEL_RESOURCE_ATTRIBUTES 81 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 82 | resources: 83 | limits: 84 | memory: 40Mi 85 | securityContext: 86 | runAsGroup: 33 87 | runAsNonRoot: true 88 | runAsUser: 33 89 | volumeMounts: 90 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/payment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: payment 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: payment 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: payment 11 | app.kubernetes.io/name: payment 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: payment 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: payment 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: payment 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: payment 35 | app.kubernetes.io/name: payment 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: payment 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: payment 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: payment 53 | app.kubernetes.io/name: payment 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: payment 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-payment' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: PAYMENT_PORT 75 | value: "8080" 76 | - name: FLAGD_HOST 77 | value: flagd 78 | - name: FLAGD_PORT 79 | value: "8013" 80 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 81 | value: http://$(OTEL_COLLECTOR_NAME):4317 82 | - name: OTEL_RESOURCE_ATTRIBUTES 83 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 84 | resources: 85 | limits: 86 | memory: 120Mi 87 | securityContext: 88 | runAsGroup: 1000 89 | runAsNonRoot: true 90 | runAsUser: 1000 91 | volumeMounts: 92 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/kafka.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kafka 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: kafka 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: kafka 11 | app.kubernetes.io/name: kafka 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 9092 19 | name: plaintext 20 | targetPort: 9092 21 | - port: 9093 22 | name: controller 23 | targetPort: 9093 24 | selector: 25 | 26 | opentelemetry.io/name: kafka 27 | --- 28 | apiVersion: apps/v1 29 | kind: Deployment 30 | metadata: 31 | name: kafka 32 | labels: 33 | helm.sh/chart: opentelemetry-demo-0.37.1 34 | 35 | opentelemetry.io/name: kafka 36 | app.kubernetes.io/instance: otel-demo 37 | app.kubernetes.io/component: kafka 38 | app.kubernetes.io/name: kafka 39 | app.kubernetes.io/version: "2.0.2" 40 | app.kubernetes.io/part-of: opentelemetry-demo 41 | app.kubernetes.io/managed-by: Helm 42 | spec: 43 | replicas: 1 44 | revisionHistoryLimit: 10 45 | selector: 46 | matchLabels: 47 | 48 | opentelemetry.io/name: kafka 49 | template: 50 | metadata: 51 | labels: 52 | 53 | opentelemetry.io/name: kafka 54 | app.kubernetes.io/instance: otel-demo 55 | app.kubernetes.io/component: kafka 56 | app.kubernetes.io/name: kafka 57 | spec: 58 | serviceAccountName: otel-demo 59 | containers: 60 | - name: kafka 61 | image: 'ghcr.io/open-telemetry/demo:2.0.2-kafka' 62 | imagePullPolicy: IfNotPresent 63 | ports: 64 | 65 | - containerPort: 9092 66 | name: plaintext 67 | - containerPort: 9093 68 | name: controller 69 | env: 70 | - name: OTEL_SERVICE_NAME 71 | valueFrom: 72 | fieldRef: 73 | apiVersion: v1 74 | fieldPath: metadata.labels['app.kubernetes.io/component'] 75 | - name: OTEL_COLLECTOR_NAME 76 | value: otel-collector.monitoring 77 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 78 | value: cumulative 79 | - name: KAFKA_ADVERTISED_LISTENERS 80 | value: PLAINTEXT://kafka:9092 81 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 82 | value: http://$(OTEL_COLLECTOR_NAME):4318 83 | - name: KAFKA_HEAP_OPTS 84 | value: -Xmx400M -Xms400M 85 | - name: OTEL_RESOURCE_ATTRIBUTES 86 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 87 | resources: 88 | limits: 89 | memory: 600Mi 90 | securityContext: 91 | runAsGroup: 1000 92 | runAsNonRoot: true 93 | runAsUser: 1000 94 | volumeMounts: 95 | volumes: -------------------------------------------------------------------------------- /01-infrastructure/README.md: -------------------------------------------------------------------------------- 1 | # 01-infrastructure 2 | 3 | This stage provisions the base infrastructure for the Kubernetes cluster using Talos Linux and Terraform on a Proxmox VE host. 4 | 5 | It creates control plane and worker node virtual machines, injects machine configurations, and applies environment-specific patches to customize Talos behavior. 6 | 7 | ## Purpose 8 | 9 | Supports single-node and HA configurations depending on variable overrides. 10 | 11 | * Provision VMs on Proxmox with static IPs 12 | * Generate and inject Talos machine configurations 13 | * Apply Talos patches for control plane, workers, and Cilium mode 14 | * Output all required data for the bootstrap phase 15 | 16 | ## Directory Structure 17 | 18 | * `providers.tf` – defines Terraform providers 19 | * `proxmox_nodes.tf` – VM resource definitions 20 | * `talos_configs.tf` – generation and injection of Talos machine configs 21 | * `variables.tf` – input variables 22 | * `terraform.tfvars` – example configuration 23 | * `outputs.tf` – exposed outputs (e.g., IPs, config paths) 24 | * `patches/` – Talos machine config patches grouped by role or function 25 | 26 | ## Usage 27 | 28 | Before you begin, add your Proxmox connection details to `terraform.tfvars`. 29 | 30 | Example: 31 | 32 | ```hcl 33 | proxmox_endpoint = "https://192.168.1.100:8006/" 34 | proxmox_username = "root@pam" 35 | proxmox_password = "your-password" 36 | proxmox_node_name = "proxmox" 37 | ``` 38 | 39 | Additional cluster settings (e.g., Talos version, VM resources, IPs) are defined in [`variables.tf`](./variables.tf) and can be overridden if needed. 40 | 41 | ```bash 42 | terraform init 43 | terraform apply 44 | 45 | # Save kubeconfig locally to access the cluster 46 | terraform output -raw kubeconfig > ~/.kube/config 47 | ``` 48 | 49 | Terraform will provision the VMs, generate Talos configurations, and return the required outputs for the next deployment stage. Wait until all nodes become `Ready` before proceeding. You can verify this using: 50 | 51 | ```bash 52 | kubectl get nodes 53 | ``` 54 | 55 | Expected output: 56 | 57 | ``` 58 | NAME STATUS ROLES AGE VERSION 59 | talos-controlplane-01 Ready control-plane 3m24s v1.32.0 60 | talos-worker-01 Ready 3m8s v1.32.0 61 | ``` 62 | 63 | ## Verification 64 | 65 | Once the cluster is up, you can visually confirm successful provisioning: 66 | 67 | ### Proxmox VM view 68 | 69 | This screenshot shows Talos VMs created in the Proxmox Virtual Environment, including control plane and worker nodes with their assigned IPs and resource allocations. 70 | 71 | > ⚠️ The screenshot shows a 5-node cluster. The default configuration provisions 2 nodes (1 control-plane, 1 worker). 72 | 73 | 74 | 75 | ### Talos Cluster Status 76 | 77 | Cluster node status as seen via `k9s`, a terminal-based UI for managing Kubernetes clusters. Make sure `k9s` is installed locally to use this view. 78 | 79 | 80 | 81 | ## Navigation 82 | 83 | [← Back to 00-prerequisite](../00-prerequisite/README.md) • [→ Continue to 02-bootstrap](../02-bootstrap/README.md) 84 | -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/recommendation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: recommendation 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: recommendation 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: recommendation 11 | app.kubernetes.io/name: recommendation 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: recommendation 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: recommendation 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: recommendation 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: recommendation 35 | app.kubernetes.io/name: recommendation 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: recommendation 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: recommendation 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: recommendation 53 | app.kubernetes.io/name: recommendation 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: recommendation 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-recommendation' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: RECOMMENDATION_PORT 75 | value: "8080" 76 | - name: PRODUCT_CATALOG_ADDR 77 | value: product-catalog:8080 78 | - name: OTEL_PYTHON_LOG_CORRELATION 79 | value: "true" 80 | - name: PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION 81 | value: python 82 | - name: FLAGD_HOST 83 | value: flagd 84 | - name: FLAGD_PORT 85 | value: "8013" 86 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 87 | value: http://$(OTEL_COLLECTOR_NAME):4317 88 | - name: OTEL_RESOURCE_ATTRIBUTES 89 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 90 | resources: 91 | limits: 92 | memory: 500Mi 93 | volumeMounts: 94 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/cart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: cart 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: cart 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: cart 11 | app.kubernetes.io/name: cart 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: cart 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: cart 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: cart 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: cart 35 | app.kubernetes.io/name: cart 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: cart 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: cart 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: cart 53 | app.kubernetes.io/name: cart 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: cart 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-cart' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: CART_PORT 75 | value: "8080" 76 | - name: ASPNETCORE_URLS 77 | value: http://*:$(CART_PORT) 78 | - name: VALKEY_ADDR 79 | value: valkey-cart:6379 80 | - name: FLAGD_HOST 81 | value: flagd 82 | - name: FLAGD_PORT 83 | value: "8013" 84 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 85 | value: http://$(OTEL_COLLECTOR_NAME):4317 86 | - name: OTEL_RESOURCE_ATTRIBUTES 87 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 88 | resources: 89 | limits: 90 | memory: 160Mi 91 | volumeMounts: 92 | initContainers: 93 | - command: 94 | - sh 95 | - -c 96 | - until nc -z -v -w30 valkey-cart 6379; do echo waiting for valkey-cart; sleep 2; 97 | done; 98 | image: busybox:latest 99 | name: wait-for-valkey-cart 100 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/load-generator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: load-generator 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: load-generator 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: load-generator 11 | app.kubernetes.io/name: load-generator 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8089 19 | name: tcp-service 20 | targetPort: 8089 21 | selector: 22 | 23 | opentelemetry.io/name: load-generator 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: load-generator 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: load-generator 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: load-generator 35 | app.kubernetes.io/name: load-generator 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: load-generator 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: load-generator 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: load-generator 53 | app.kubernetes.io/name: load-generator 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: load-generator 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-load-generator' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8089 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: LOCUST_WEB_HOST 75 | value: 0.0.0.0 76 | - name: LOCUST_WEB_PORT 77 | value: "8089" 78 | - name: LOCUST_USERS 79 | value: "1" 80 | - name: LOCUST_SPAWN_RATE 81 | value: "1" 82 | - name: LOCUST_HOST 83 | value: http://frontend-proxy:8080 84 | - name: LOCUST_HEADLESS 85 | value: "false" 86 | - name: LOCUST_AUTOSTART 87 | value: "true" 88 | - name: LOCUST_BROWSER_TRAFFIC_ENABLED 89 | value: "true" 90 | - name: PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION 91 | value: python 92 | - name: FLAGD_HOST 93 | value: flagd 94 | - name: FLAGD_OFREP_PORT 95 | value: "8016" 96 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 97 | value: http://$(OTEL_COLLECTOR_NAME):4317 98 | - name: OTEL_RESOURCE_ATTRIBUTES 99 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 100 | resources: 101 | limits: 102 | memory: 1500Mi 103 | volumeMounts: 104 | volumes: -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/checkout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: checkout 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: checkout 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: checkout 11 | app.kubernetes.io/name: checkout 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: checkout 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: checkout 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: checkout 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: checkout 35 | app.kubernetes.io/name: checkout 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: checkout 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: checkout 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: checkout 53 | app.kubernetes.io/name: checkout 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: checkout 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-checkout' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: CHECKOUT_PORT 75 | value: "8080" 76 | - name: CART_ADDR 77 | value: cart:8080 78 | - name: CURRENCY_ADDR 79 | value: currency:8080 80 | - name: EMAIL_ADDR 81 | value: http://email:8080 82 | - name: PAYMENT_ADDR 83 | value: payment:8080 84 | - name: PRODUCT_CATALOG_ADDR 85 | value: product-catalog:8080 86 | - name: SHIPPING_ADDR 87 | value: shipping:8080 88 | - name: KAFKA_ADDR 89 | value: kafka:9092 90 | - name: FLAGD_HOST 91 | value: flagd 92 | - name: FLAGD_PORT 93 | value: "8013" 94 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 95 | value: http://$(OTEL_COLLECTOR_NAME):4317 96 | - name: OTEL_RESOURCE_ATTRIBUTES 97 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 98 | resources: 99 | limits: 100 | memory: 20Mi 101 | volumeMounts: 102 | initContainers: 103 | - command: 104 | - sh 105 | - -c 106 | - until nc -z -v -w30 kafka 9092; do echo waiting for kafka; sleep 2; done; 107 | image: busybox:latest 108 | name: wait-for-kafka 109 | volumes: -------------------------------------------------------------------------------- /03-gitops/applications/02-monitoring-bootstrap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: AppProject 3 | metadata: 4 | name: monitoring 5 | namespace: argocd 6 | spec: 7 | sourceRepos: 8 | - '*' 9 | destinations: 10 | - namespace: '*' 11 | server: https://kubernetes.default.svc 12 | clusterResourceWhitelist: 13 | - group: '*' 14 | kind: '*' 15 | --- 16 | apiVersion: argoproj.io/v1alpha1 17 | kind: Application 18 | metadata: 19 | name: kube-prometheus-stack 20 | namespace: argocd 21 | spec: 22 | project: monitoring 23 | source: 24 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 25 | targetRevision: main 26 | path: 03-gitops/apps/02-monitoring/kube-prometheus-stack 27 | destination: 28 | server: https://kubernetes.default.svc 29 | namespace: monitoring 30 | syncPolicy: 31 | automated: {} 32 | --- 33 | apiVersion: argoproj.io/v1alpha1 34 | kind: Application 35 | metadata: 36 | name: jaeger 37 | namespace: argocd 38 | spec: 39 | project: monitoring 40 | source: 41 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 42 | targetRevision: main 43 | path: 03-gitops/apps/02-monitoring/jaeger 44 | destination: 45 | server: https://kubernetes.default.svc 46 | namespace: monitoring 47 | syncPolicy: 48 | automated: {} 49 | --- 50 | apiVersion: argoproj.io/v1alpha1 51 | kind: Application 52 | metadata: 53 | name: jaeger 54 | namespace: argocd 55 | spec: 56 | project: monitoring 57 | source: 58 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 59 | targetRevision: main 60 | path: 03-gitops/apps/02-monitoring/jaeger 61 | destination: 62 | server: https://kubernetes.default.svc 63 | namespace: monitoring 64 | syncPolicy: 65 | automated: {} 66 | --- 67 | apiVersion: argoproj.io/v1alpha1 68 | kind: Application 69 | metadata: 70 | name: hubble 71 | namespace: argocd 72 | spec: 73 | project: monitoring 74 | source: 75 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 76 | targetRevision: main 77 | path: 03-gitops/apps/02-monitoring/hubble 78 | destination: 79 | server: https://kubernetes.default.svc 80 | namespace: monitoring 81 | syncPolicy: 82 | automated: {} 83 | --- 84 | apiVersion: argoproj.io/v1alpha1 85 | kind: Application 86 | metadata: 87 | name: loki 88 | namespace: argocd 89 | spec: 90 | project: monitoring 91 | source: 92 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 93 | targetRevision: main 94 | path: 03-gitops/apps/02-monitoring/loki 95 | destination: 96 | server: https://kubernetes.default.svc 97 | namespace: monitoring 98 | syncPolicy: 99 | automated: {} 100 | --- 101 | apiVersion: argoproj.io/v1alpha1 102 | kind: Application 103 | metadata: 104 | name: tempo 105 | namespace: argocd 106 | spec: 107 | project: monitoring 108 | source: 109 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 110 | targetRevision: main 111 | path: 03-gitops/apps/02-monitoring/tempo 112 | destination: 113 | server: https://kubernetes.default.svc 114 | namespace: monitoring 115 | syncPolicy: 116 | automated: {} 117 | --- 118 | apiVersion: argoproj.io/v1alpha1 119 | kind: Application 120 | metadata: 121 | name: otel-collector 122 | namespace: argocd 123 | spec: 124 | project: monitoring 125 | source: 126 | repoURL: https://github.com/SerhiiMyronets/homelab-talos-kubernetes-terraform.git 127 | targetRevision: main 128 | path: 03-gitops/apps/02-monitoring/otel-collector 129 | destination: 130 | server: https://kubernetes.default.svc 131 | namespace: monitoring 132 | syncPolicy: 133 | automated: {} -------------------------------------------------------------------------------- /01-infrastructure/talos_configs.tf: -------------------------------------------------------------------------------- 1 | // ============================================================================== 2 | // Talos Machine Secrets and Client Configuration 3 | // ============================================================================== 4 | 5 | resource "talos_machine_secrets" "machine_secrets" {} 6 | 7 | data "talos_client_configuration" "talosconfig" { 8 | cluster_name = var.cluster_name 9 | client_configuration = talos_machine_secrets.machine_secrets.client_configuration 10 | endpoints = [for node in local.controller_nodes : node.address] 11 | } 12 | 13 | // ============================================================================== 14 | // Talos Cluster Kubeconfig 15 | // ============================================================================== 16 | 17 | resource "talos_cluster_kubeconfig" "kubeconfig" { 18 | depends_on = [talos_machine_bootstrap.bootstrap] 19 | client_configuration = talos_machine_secrets.machine_secrets.client_configuration 20 | node = local.controller_nodes[0].address 21 | } 22 | 23 | // ============================================================================== 24 | // Talos Controlplane Node Configuration 25 | // ============================================================================== 26 | 27 | data "talos_machine_configuration" "controller" { 28 | cluster_name = var.cluster_name 29 | cluster_endpoint = local.cluster_endpoint 30 | kubernetes_version = var.kubernetes_version 31 | machine_type = "controlplane" 32 | machine_secrets = talos_machine_secrets.machine_secrets.machine_secrets 33 | config_patches = local.config_patches_controller 34 | } 35 | 36 | resource "talos_machine_configuration_apply" "controller" { 37 | count = var.controller_config.count 38 | depends_on = [proxmox_virtual_environment_vm.control_plane] 39 | client_configuration = talos_machine_secrets.machine_secrets.client_configuration 40 | machine_configuration_input = data.talos_machine_configuration.controller.machine_configuration 41 | endpoint = local.controller_nodes[count.index].address 42 | node = local.controller_nodes[count.index].address 43 | } 44 | 45 | // ============================================================================== 46 | // Talos Worker Node Configuration 47 | // ============================================================================== 48 | 49 | data "talos_machine_configuration" "worker" { 50 | cluster_name = var.cluster_name 51 | cluster_endpoint = local.cluster_endpoint 52 | kubernetes_version = var.kubernetes_version 53 | machine_type = "worker" 54 | machine_secrets = talos_machine_secrets.machine_secrets.machine_secrets 55 | config_patches = local.config_patches_worker 56 | } 57 | 58 | resource "talos_machine_configuration_apply" "worker" { 59 | count = var.worker_config.count 60 | depends_on = [proxmox_virtual_environment_vm.talos_worker_01] 61 | client_configuration = talos_machine_secrets.machine_secrets.client_configuration 62 | machine_configuration_input = data.talos_machine_configuration.worker.machine_configuration 63 | endpoint = local.worker_nodes[count.index].address 64 | node = local.worker_nodes[count.index].address 65 | } 66 | 67 | // ============================================================================== 68 | // Talos Cluster Bootstrap 69 | // ============================================================================== 70 | 71 | resource "talos_machine_bootstrap" "bootstrap" { 72 | depends_on = [talos_machine_configuration_apply.controller] 73 | endpoint = local.controller_nodes[0].address 74 | client_configuration = talos_machine_secrets.machine_secrets.client_configuration 75 | node = local.controller_nodes[0].address 76 | } -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/otel-collector/values.yaml: -------------------------------------------------------------------------------- 1 | nameOverride: otel-collector 2 | fullnameOverride: otel-collector 3 | mode: deployment 4 | 5 | image: 6 | repository: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib 7 | tag: 0.120.0 8 | 9 | config: 10 | connectors: 11 | spanmetrics: { } 12 | extensions: 13 | health_check: 14 | endpoint: ${env:MY_POD_IP}:13133 15 | processors: 16 | batch: { } 17 | k8sattributes: 18 | extract: 19 | metadata: 20 | - k8s.namespace.name 21 | - k8s.deployment.name 22 | - k8s.statefulset.name 23 | - k8s.daemonset.name 24 | - k8s.cronjob.name 25 | - k8s.job.name 26 | - k8s.node.name 27 | - k8s.pod.name 28 | - k8s.pod.uid 29 | - k8s.pod.start_time 30 | passthrough: false 31 | pod_association: 32 | - sources: 33 | - from: resource_attribute 34 | name: k8s.pod.ip 35 | - sources: 36 | - from: resource_attribute 37 | name: k8s.pod.uid 38 | - sources: 39 | - from: connection 40 | memory_limiter: 41 | check_interval: 5s 42 | limit_percentage: 80 43 | spike_limit_percentage: 25 44 | resource: 45 | attributes: 46 | - action: insert 47 | from_attribute: k8s.pod.uid 48 | key: service.instance.id 49 | transform: 50 | error_mode: ignore 51 | trace_statements: 52 | - context: span 53 | statements: 54 | - replace_pattern(name, "\\?.*", "") 55 | - replace_match(name, "GET /api/products/*", "GET /api/products/{productId}") 56 | 57 | ############################################## 58 | 59 | receivers: 60 | httpcheck/frontend-proxy: 61 | targets: 62 | - endpoint: http://frontend-proxy:8080 63 | otlp: 64 | protocols: 65 | grpc: {} 66 | http: {} 67 | redis: 68 | collection_interval: 10s 69 | endpoint: valkey-cart.otel-demo.svc.cluster.local:6379 70 | 71 | ############################################## 72 | 73 | exporters: 74 | debug: {} 75 | otlp/jaeger: 76 | endpoint: jaeger-collector.monitoring:4317 77 | tls: 78 | insecure: true 79 | otlp/tempo: 80 | endpoint: tempo.monitoring.svc.cluster.local:4317 81 | tls: 82 | insecure: true 83 | loki: 84 | endpoint: http://loki-stack.monitoring.svc.cluster.local:3100/loki/api/v1/push 85 | prometheus: 86 | endpoint: "0.0.0.0:8889" 87 | 88 | ############################################## 89 | 90 | service: 91 | extensions: 92 | - health_check 93 | pipelines: 94 | logs: 95 | receivers: [ otlp ] 96 | processors: [ k8sattributes, memory_limiter, resource, batch ] 97 | exporters: [ loki ] 98 | metrics: 99 | receivers: [ httpcheck/frontend-proxy, redis, otlp, spanmetrics ] 100 | processors: [ k8sattributes, memory_limiter, resource, batch ] 101 | exporters: [ prometheus ] 102 | traces: 103 | receivers: [otlp] 104 | processors: [k8sattributes, memory_limiter, resource, transform, batch] 105 | exporters: [spanmetrics, otlp/jaeger, otlp/tempo] 106 | telemetry: 107 | metrics: 108 | address: "0.0.0.0:8888" 109 | 110 | 111 | ############################################## 112 | 113 | ports: 114 | metrics: 115 | enabled: true 116 | containerPort: 8888 117 | servicePort: 8888 118 | spanmetrics: 119 | enabled: true 120 | containerPort: 8889 121 | servicePort: 8889 122 | 123 | service: 124 | enabled: true 125 | type: ClusterIP 126 | 127 | 128 | serviceMonitor: 129 | enabled: true 130 | metricsEndpoints: 131 | - port: spanmetrics 132 | interval: 15s 133 | - port: metrics 134 | interval: 15s 135 | extraLabels: 136 | release: kube-prometheus-stack -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/frontend.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: frontend 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: frontend 11 | app.kubernetes.io/name: frontend 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: frontend 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: frontend 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: frontend 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: frontend 35 | app.kubernetes.io/name: frontend 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: frontend 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: frontend 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: frontend 53 | app.kubernetes.io/name: frontend 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: frontend 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-frontend' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: FRONTEND_PORT 75 | value: "8080" 76 | - name: FRONTEND_ADDR 77 | value: :8080 78 | - name: AD_ADDR 79 | value: ad:8080 80 | - name: CART_ADDR 81 | value: cart:8080 82 | - name: CHECKOUT_ADDR 83 | value: checkout:8080 84 | - name: CURRENCY_ADDR 85 | value: currency:8080 86 | - name: PRODUCT_CATALOG_ADDR 87 | value: product-catalog:8080 88 | - name: RECOMMENDATION_ADDR 89 | value: recommendation:8080 90 | - name: SHIPPING_ADDR 91 | value: shipping:8080 92 | - name: FLAGD_HOST 93 | value: flagd 94 | - name: FLAGD_PORT 95 | value: "8013" 96 | - name: OTEL_COLLECTOR_HOST 97 | value: $(OTEL_COLLECTOR_NAME) 98 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 99 | value: http://$(OTEL_COLLECTOR_NAME):4317 100 | - name: WEB_OTEL_SERVICE_NAME 101 | value: frontend-web 102 | - name: PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT 103 | value: http://localhost:8080/otlp-http/v1/traces 104 | - name: OTEL_RESOURCE_ATTRIBUTES 105 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 106 | resources: 107 | limits: 108 | memory: 250Mi 109 | securityContext: 110 | runAsGroup: 1001 111 | runAsNonRoot: true 112 | runAsUser: 1001 113 | volumeMounts: 114 | volumes: -------------------------------------------------------------------------------- /01-infrastructure/proxmox_nodes.tf: -------------------------------------------------------------------------------- 1 | // ============================================================================== 2 | // Talos Proxmox Image Download 3 | // ============================================================================== 4 | 5 | resource "proxmox_virtual_environment_download_file" "talos_nocloud_image" { 6 | content_type = "iso" 7 | datastore_id = "local" 8 | node_name = var.proxmox_node_name 9 | file_name = local.talos_image_filename 10 | url = local.talos_image_url 11 | decompression_algorithm = "gz" 12 | overwrite = true 13 | overwrite_unmanaged = true 14 | } 15 | 16 | // ============================================================================== 17 | // Talos Control Plane Virtual Machines 18 | // ============================================================================== 19 | 20 | resource "proxmox_virtual_environment_vm" "control_plane" { 21 | count = var.controller_config.count 22 | vm_id = count.index + 100 23 | name = "${var.prefix}-${local.controller_nodes[count.index].name}" 24 | tags = sort(["talos", "control_plane", "terraform"]) 25 | stop_on_destroy = true 26 | node_name = var.proxmox_node_name 27 | on_boot = true 28 | 29 | cpu { 30 | cores = var.controller_config.cpu 31 | type = "x86-64-v2-AES" 32 | } 33 | 34 | memory { 35 | dedicated = var.controller_config.memory 36 | } 37 | 38 | agent { 39 | enabled = true 40 | } 41 | 42 | network_device { 43 | bridge = var.proxmox_network_bridge 44 | } 45 | 46 | disk { 47 | datastore_id = var.controller_config.os_disk.datastore 48 | file_id = proxmox_virtual_environment_download_file.talos_nocloud_image.id 49 | file_format = "raw" 50 | interface = "virtio0" 51 | size = var.controller_config.os_disk.size 52 | } 53 | 54 | operating_system { 55 | type = "l26" 56 | } 57 | 58 | initialization { 59 | ip_config { 60 | ipv4 { 61 | address = "${local.controller_nodes[count.index].address}/24" 62 | gateway = var.cluster_node_network_gateway 63 | } 64 | } 65 | } 66 | } 67 | 68 | // ============================================================================== 69 | // Talos Worker Virtual Machines 70 | // ============================================================================== 71 | 72 | resource "proxmox_virtual_environment_vm" "talos_worker_01" { 73 | depends_on = [proxmox_virtual_environment_vm.control_plane] 74 | count = var.worker_config.count 75 | name = "${var.prefix}-${local.worker_nodes[count.index].name}" 76 | tags = sort(["talos", "worker", "terraform"]) 77 | node_name = var.proxmox_node_name 78 | on_boot = true 79 | 80 | cpu { 81 | cores = var.worker_config.cpu 82 | type = "x86-64-v2-AES" 83 | } 84 | 85 | memory { 86 | dedicated = var.worker_config.memory 87 | } 88 | 89 | agent { 90 | enabled = true 91 | } 92 | 93 | network_device { 94 | bridge = var.proxmox_network_bridge 95 | } 96 | 97 | # OS disk (bootable Talos image) 98 | disk { 99 | datastore_id = var.worker_config.os_disk.datastore 100 | interface = "scsi0" 101 | file_id = proxmox_virtual_environment_download_file.talos_nocloud_image.id 102 | file_format = "raw" 103 | size = var.worker_config.os_disk.size 104 | } 105 | # 106 | # Data disk for longhorn SCI 107 | disk { 108 | datastore_id = var.worker_config.longhorn_disk.datastore 109 | interface = "scsi1" 110 | file_format = "raw" 111 | size = var.worker_config.longhorn_disk.size 112 | } 113 | 114 | operating_system { 115 | type = "l26" 116 | } 117 | 118 | initialization { 119 | ip_config { 120 | ipv4 { 121 | address = "${local.worker_nodes[count.index].address}/24" 122 | gateway = var.cluster_node_network_gateway 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/frontend-proxy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend-proxy 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: frontend-proxy 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: frontend-proxy 11 | app.kubernetes.io/name: frontend-proxy 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: frontend-proxy 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: frontend-proxy 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: frontend-proxy 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: frontend-proxy 35 | app.kubernetes.io/name: frontend-proxy 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: frontend-proxy 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: frontend-proxy 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: frontend-proxy 53 | app.kubernetes.io/name: frontend-proxy 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: frontend-proxy 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-frontend-proxy' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: ENVOY_PORT 75 | value: "8080" 76 | - name: FLAGD_HOST 77 | value: flagd 78 | - name: FLAGD_PORT 79 | value: "8013" 80 | - name: FLAGD_UI_HOST 81 | value: flagd 82 | - name: FLAGD_UI_PORT 83 | value: "4000" 84 | - name: FRONTEND_HOST 85 | value: frontend 86 | - name: FRONTEND_PORT 87 | value: "8080" 88 | - name: GRAFANA_HOST 89 | value: grafana 90 | - name: GRAFANA_PORT 91 | value: "80" 92 | - name: IMAGE_PROVIDER_HOST 93 | value: image-provider 94 | - name: IMAGE_PROVIDER_PORT 95 | value: "8081" 96 | - name: JAEGER_HOST 97 | value: jaeger-query 98 | - name: JAEGER_PORT 99 | value: "16686" 100 | - name: LOCUST_WEB_HOST 101 | value: load-generator 102 | - name: LOCUST_WEB_PORT 103 | value: "8089" 104 | - name: OTEL_COLLECTOR_HOST 105 | value: $(OTEL_COLLECTOR_NAME) 106 | - name: OTEL_COLLECTOR_PORT_GRPC 107 | value: "4317" 108 | - name: OTEL_COLLECTOR_PORT_HTTP 109 | value: "4318" 110 | - name: OTEL_RESOURCE_ATTRIBUTES 111 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 112 | resources: 113 | limits: 114 | memory: 65Mi 115 | securityContext: 116 | runAsGroup: 101 117 | runAsNonRoot: true 118 | runAsUser: 101 119 | volumeMounts: 120 | volumes: -------------------------------------------------------------------------------- /00-prerequisite/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Prerequisites for Running Talos Kubernetes Cluster on Proxmox 3 | 4 | This document describes the prerequisites and initial environment setup required to deploy a Kubernetes cluster using Talos Linux, Terraform, and Proxmox VE. 5 | 6 | --- 7 | 8 | ## Minimum Hardware Requirements 9 | 10 | A physical server, mini PC, or spare laptop with virtualization support is sufficient. 11 | 12 | | Resource | Minimum (testing) | Recommended (production-like) | 13 | |-------------|-------------------|-------------------------------| 14 | | CPU Cores | 4 | 8+ | 15 | | RAM | 8 GB | 16 GB+ | 16 | 17 | > For running the full GitOps stack (observability, demo apps, load generation), at least 12 GB RAM is recommended. 18 | 19 | --- 20 | 21 | ## Required Software 22 | 23 | Install the following CLI tools on your **local workstation** (not inside Proxmox): 24 | 25 | ### Mandatory 26 | 27 | - `terraform`: Infrastructure provisioning 28 | - `kubectl`: Kubernetes control interface 29 | - `helmfile`: Declarative Helm release management 30 | - `helm`: Dependency of `helmfile` 31 | 32 | ### Optional 33 | 34 | - `talosctl`: Talos Linux management CLI 35 | - `cilium`: Cilium CLI for CNI diagnostics 36 | 37 | #### Installation (macOS / Ubuntu) 38 | 39 | **macOS (Homebrew):** 40 | ```bash 41 | brew install terraform kubectl helmfile helm 42 | brew install talosctl cilium 43 | ``` 44 | 45 | **Ubuntu/Debian (APT + manual binaries):** 46 | ```bash 47 | sudo apt update && sudo apt install -y terraform kubectl helmfile helm 48 | # talosctl and cilium must be downloaded manually 49 | ``` 50 | 51 | --- 52 | 53 | ## Proxmox VE Installation 54 | 55 | Proxmox must be installed directly on the host machine that will run the cluster. 56 | 57 | 1. Download ISO: https://www.proxmox.com/en/downloads 58 | 2. Flash to USB (e.g., with Balena Etcher) 59 | 3. Boot the machine and install Proxmox 60 | 4. Access the UI: `https://:8006` 61 | 62 | > Ensure virtualization support (VT-x / AMD-V) is enabled in BIOS/UEFI. 63 | 64 | --- 65 | 66 | ## Post-Install Configuration 67 | 68 | Run a standard setup script to configure repositories and base settings: 69 | 70 | ```bash 71 | bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pve-install.sh)" 72 | ``` 73 | 74 | This will: 75 | - Enable community repositories 76 | - Update package lists 77 | - Remove subscription notices 78 | - Apply default Proxmox tweaks 79 | 80 | --- 81 | 82 | ## Proxmox Network Configuration 83 | 84 | To isolate the Kubernetes cluster, configure a dedicated bridge (`vmbr1`) with NAT on the Proxmox host. This prevents interference with your home network. 85 | 86 | > Replace all instances of `enp3s0` with your actual network interface (check via `ip a` or `ip link`). 87 | 88 | Example `/etc/network/interfaces`: 89 | 90 | ```ini 91 | # loopback 92 | auto lo 93 | iface lo inet loopback 94 | 95 | # main interface 96 | auto enp3s0 97 | iface enp3s0 inet static 98 | address 192.168.1.100/24 99 | gateway 192.168.1.1 100 | 101 | # isolated bridge for cluster 102 | auto vmbr1 103 | iface vmbr1 inet static 104 | address 192.168.100.1/24 105 | bridge-ports none 106 | bridge-stp off 107 | bridge-fd 0 108 | 109 | post-up echo 1 > /proc/sys/net/ipv4/ip_forward 110 | post-up iptables -t nat -A POSTROUTING -s '192.168.100.0/24' -o enp3s0 -j MASQUERADE 111 | post-down iptables -t nat -D POSTROUTING -s '192.168.100.0/24' -o enp3s0 -j MASQUERADE 112 | 113 | source /etc/network/interfaces.d/* 114 | ``` 115 | 116 | Apply changes with: 117 | 118 | ```bash 119 | ifreload -a 120 | ``` 121 | 122 | --- 123 | 124 | ## Static Route (Local Machine) 125 | 126 | To allow your workstation to reach Talos nodes inside the isolated network, add a static route: 127 | 128 | ```bash 129 | sudo route -n add 192.168.100.0/24 192.168.1.100 130 | ``` 131 | 132 | Replace `192.168.1.100` with your Proxmox host's IP address. 133 | 134 | --- 135 | 136 | ## Navigation 137 | 138 | [← Back to Main project README](../README.md) • [→ Continue to 01-infrastructure](../01-infrastructure/README.md) 139 | 140 | -------------------------------------------------------------------------------- /01-infrastructure/patches/controller/01-install-cilium-job.yaml: -------------------------------------------------------------------------------- 1 | # https://www.talos.dev/v1.10/kubernetes-guides/network/deploying-cilium/ 2 | # This is a Talos patch to install Cilium on the Kubernetes cluster. 3 | # It creates a Job that runs the Cilium CLI to install Cilium on the cluster. 4 | # The Job is run as a ServiceAccount with cluster-admin permissions. 5 | cluster: 6 | inlineManifests: 7 | - name: cilium-install 8 | contents: | 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRoleBinding 12 | metadata: 13 | name: cilium-install 14 | roleRef: 15 | apiGroup: rbac.authorization.k8s.io 16 | kind: ClusterRole 17 | name: cluster-admin 18 | subjects: 19 | - kind: ServiceAccount 20 | name: cilium-install 21 | namespace: kube-system 22 | --- 23 | apiVersion: v1 24 | kind: ServiceAccount 25 | metadata: 26 | name: cilium-install 27 | namespace: kube-system 28 | --- 29 | apiVersion: batch/v1 30 | kind: Job 31 | metadata: 32 | name: cilium-install 33 | namespace: kube-system 34 | spec: 35 | backoffLimit: 10 36 | template: 37 | metadata: 38 | labels: 39 | app: cilium-install 40 | spec: 41 | restartPolicy: OnFailure 42 | tolerations: 43 | - operator: Exists 44 | - effect: NoSchedule 45 | operator: Exists 46 | - effect: NoExecute 47 | operator: Exists 48 | - effect: PreferNoSchedule 49 | operator: Exists 50 | - key: node-role.kubernetes.io/control-plane 51 | operator: Exists 52 | effect: NoSchedule 53 | - key: node-role.kubernetes.io/control-plane 54 | operator: Exists 55 | effect: NoExecute 56 | - key: node-role.kubernetes.io/control-plane 57 | operator: Exists 58 | effect: PreferNoSchedule 59 | affinity: 60 | nodeAffinity: 61 | requiredDuringSchedulingIgnoredDuringExecution: 62 | nodeSelectorTerms: 63 | - matchExpressions: 64 | - key: node-role.kubernetes.io/control-plane 65 | operator: Exists 66 | serviceAccount: cilium-install 67 | serviceAccountName: cilium-install 68 | hostNetwork: true 69 | containers: 70 | - name: cilium-install 71 | image: quay.io/cilium/cilium-cli-ci:latest 72 | env: 73 | - name: KUBERNETES_SERVICE_HOST 74 | valueFrom: 75 | fieldRef: 76 | apiVersion: v1 77 | fieldPath: status.podIP 78 | - name: KUBERNETES_SERVICE_PORT 79 | value: "6443" 80 | command: 81 | - cilium 82 | - install 83 | - --set 84 | - ipam.mode=kubernetes 85 | - --set 86 | - kubeProxyReplacement=true 87 | - --set 88 | - securityContext.capabilities.ciliumAgent={CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID} 89 | - --set 90 | - securityContext.capabilities.cleanCiliumState={NET_ADMIN,SYS_ADMIN,SYS_RESOURCE} 91 | - --set 92 | - cgroup.autoMount.enabled=false 93 | - --set 94 | - cgroup.hostRoot=/sys/fs/cgroup 95 | - --set 96 | - k8sServiceHost=localhost 97 | - --set 98 | - k8sServicePort=7445 99 | - --set 100 | - l2announcements.enabled=true 101 | - --set 102 | - devices={eth0} 103 | - --set 104 | - envoy.enabled=false -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Talos Kubernetes Cluster on Proxmox with Terraform 2 | 3 | This repository contains infrastructure-as-code configurations for deploying a minimal, production-grade Kubernetes cluster using Talos Linux and Terraform on Proxmox VE. This repository provides a fully declarative, script-free setup: Talos is configured and installed automatically during VM provisioning via Terraform. 4 | 5 | ## Overview 6 | 7 | This project is designed for enthusiasts, students, or professionals who want to gain hands-on experience with a production-grade GitOps Kubernetes cluster using modest hardware — such as an old laptop or mini PC. It offers a fully automated deployment pipeline without requiring cloud resources or expensive infrastructure. 8 | 9 | The Kubernetes cluster is composed of multiple control plane and worker nodes provisioned on a Proxmox host using Terraform. Talos Linux is injected and configured automatically as part of the VM provisioning step. The configuration supports high availability (HA) and uses a virtual IP for the control plane endpoint. The deployment includes core platform components (ingress, certificate management, GitOps), observability stack (metrics, logs, traces), and demo microservices applications for testing. 10 | 11 | ## Architecture 12 | 13 | The Kubernetes cluster operates in an isolated subnet (`192.168.100.0/24`) with virtual machines provisioned directly on a Proxmox VE host. A dedicated NAT bridge (`vmbr1`) is used to provide connectivity. Each node is assigned a static IP from this subnet. The control plane nodes are configured in high availability (HA) mode and share a virtual IP (`192.168.100.50`) for the Kubernetes API. 14 | 15 | ``` 16 | Proxmox VE (192.168.1.100) 17 | └─ vmbr1: 192.168.100.1 (NAT Gateway) 18 | ├─ controlplane-1: 192.168.100.60 19 | ├─ controlplane-2: 192.168.100.61 20 | ├─ worker-1: 192.168.100.70 21 | ├─ worker-2: 192.168.100.71 22 | └─ cluster VIP: 192.168.100.50 (Kubernetes API) 23 | ``` 24 | 25 | A static route to `192.168.100.0/24` must be configured on the developer workstation via the Proxmox host. 26 | 27 | ## Features 28 | 29 | * Support for high availability across control-plane nodes 30 | * Fully declarative setup (no shell scripts) 31 | * Talos Linux installed and configured via Terraform 32 | * Proxmox-native VM provisioning 33 | * GitOps with Argo CD and Helmfile 34 | * Cilium CNI with kube-proxy disabled 35 | * Longhorn for persistent volumes 36 | * Full observability stack with OpenTelemetry Collector (metrics, logs, traces via Tempo, Loki, Prometheus, Grafana) 37 | *Demo microservices instrumented for end-to-end tracing and performance metrics collection* 38 | 39 | ## Directory Structure 40 | 41 | | Path | Description | 42 | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ | 43 | | [`00-prerequisite/`](./00-prerequisite/README.md) | Environment preparation: hardware requirements, dependencies, Proxmox and networking setup | 44 | | [`01-infrastructure/`](./01-infrastructure/README.md) | Terraform configurations for Proxmox VM provisioning and Talos injection | 45 | | [`02-bootstrap/`](./02-bootstrap/README.md) | Installs base components (cert-manager, ingress, Argo CD, Longhorn, etc.) using Helmfile | 46 | | [`03-gitops/`](./03-gitops/README.md) | Deploys applications via Argo CD, including observability stack and demo workloads | 47 | 48 | 49 | ## UI Preview 50 | 51 | Below is a preview of the cluster after deployment. For a complete set of UI screenshots, see the [03-gitops UI Previews](./03-gitops/README.md#ui-previews). 52 | 53 | | Proxmox | Argocd | 54 | |:---------------------------------------------:|:--------------------------------------------:| 55 | | | | 56 | | HubbleUI | Tempo | 57 | | | | 58 | 59 | ## Getting Started 60 | 61 | To get started, begin with [00-prerequisite](./00-prerequisite/README.md), which walks through system setup, required dependencies, and network configuration. -------------------------------------------------------------------------------- /01-infrastructure/patches/cilium-mode/controller/01-install-cilium-job.yaml: -------------------------------------------------------------------------------- 1 | # https://www.talos.dev/v1.10/kubernetes-guides/network/deploying-cilium/ 2 | # This is a Talos patch to install Cilium on the Kubernetes cluster. 3 | # It creates a Job that runs the Cilium CLI to install Cilium on the cluster. 4 | # The Job is run as a ServiceAccount with cluster-admin permissions. 5 | cluster: 6 | inlineManifests: 7 | - name: cilium-install 8 | contents: | 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRoleBinding 12 | metadata: 13 | name: cilium-install 14 | roleRef: 15 | apiGroup: rbac.authorization.k8s.io 16 | kind: ClusterRole 17 | name: cluster-admin 18 | subjects: 19 | - kind: ServiceAccount 20 | name: cilium-install 21 | namespace: kube-system 22 | --- 23 | apiVersion: v1 24 | kind: ServiceAccount 25 | metadata: 26 | name: cilium-install 27 | namespace: kube-system 28 | --- 29 | apiVersion: batch/v1 30 | kind: Job 31 | metadata: 32 | name: cilium-install 33 | namespace: kube-system 34 | spec: 35 | backoffLimit: 10 36 | template: 37 | metadata: 38 | labels: 39 | app: cilium-install 40 | spec: 41 | restartPolicy: OnFailure 42 | tolerations: 43 | - operator: Exists 44 | - effect: NoSchedule 45 | operator: Exists 46 | - effect: NoExecute 47 | operator: Exists 48 | - effect: PreferNoSchedule 49 | operator: Exists 50 | - key: node-role.kubernetes.io/control-plane 51 | operator: Exists 52 | effect: NoSchedule 53 | - key: node-role.kubernetes.io/control-plane 54 | operator: Exists 55 | effect: NoExecute 56 | - key: node-role.kubernetes.io/control-plane 57 | operator: Exists 58 | effect: PreferNoSchedule 59 | affinity: 60 | nodeAffinity: 61 | requiredDuringSchedulingIgnoredDuringExecution: 62 | nodeSelectorTerms: 63 | - matchExpressions: 64 | - key: node-role.kubernetes.io/control-plane 65 | operator: Exists 66 | serviceAccount: cilium-install 67 | serviceAccountName: cilium-install 68 | hostNetwork: true 69 | containers: 70 | - name: cilium-install 71 | image: quay.io/cilium/cilium-cli-ci:latest 72 | env: 73 | - name: KUBERNETES_SERVICE_HOST 74 | valueFrom: 75 | fieldRef: 76 | apiVersion: v1 77 | fieldPath: status.podIP 78 | - name: KUBERNETES_SERVICE_PORT 79 | value: "6443" 80 | command: 81 | - cilium 82 | - install 83 | - --set 84 | - ipam.mode=kubernetes 85 | - --set 86 | - kubeProxyReplacement=true 87 | - --set 88 | - securityContext.capabilities.ciliumAgent={CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID} 89 | - --set 90 | - securityContext.capabilities.cleanCiliumState={NET_ADMIN,SYS_ADMIN,SYS_RESOURCE} 91 | - --set 92 | - cgroup.autoMount.enabled=false 93 | - --set 94 | - cgroup.hostRoot=/sys/fs/cgroup 95 | - --set 96 | - k8sServiceHost=localhost 97 | - --set 98 | - k8sServicePort=7445 99 | - --set 100 | - l2announcements.enabled=true 101 | - --set 102 | - devices={eth0} 103 | - --set 104 | - envoy.enabled=false 105 | - --set 106 | - hubble.relay.enabled=true 107 | - --set 108 | - hubble.ui.enabled=true 109 | - --set 110 | - prometheus.enabled=true 111 | - --set 112 | - operator.prometheus.enabled=true -------------------------------------------------------------------------------- /01-infrastructure/variables.tf: -------------------------------------------------------------------------------- 1 | // ============================================================================== 2 | // Proxmox Credentials 3 | // ============================================================================== 4 | 5 | variable "proxmox_endpoint" { 6 | description = "Proxmox API endpoint URL." 7 | type = string 8 | } 9 | 10 | variable "proxmox_username" { 11 | description = "Proxmox API username." 12 | type = string 13 | } 14 | 15 | variable "proxmox_password" { 16 | description = "Proxmox API password." 17 | type = string 18 | sensitive = true 19 | } 20 | 21 | // ============================================================================== 22 | // Global Cluster Settings 23 | // ============================================================================== 24 | 25 | variable "cluster_name" { 26 | description = "The name of the Kubernetes cluster. Used in resource naming." 27 | type = string 28 | default = "homelab" 29 | } 30 | 31 | variable "prefix" { 32 | description = "Prefix used for virtual machine names." 33 | type = string 34 | default = "talos" 35 | } 36 | 37 | // ============================================================================== 38 | // Proxmox Node Settings 39 | // ============================================================================== 40 | 41 | variable "proxmox_node_name" { 42 | description = "The name of the Proxmox node where virtual machines are created." 43 | type = string 44 | default = "proxmox" 45 | } 46 | 47 | variable "proxmox_network_bridge" { 48 | description = "The network bridge interface on Proxmox used by virtual machines." 49 | type = string 50 | default = "vmbr1" 51 | } 52 | 53 | // ============================================================================== 54 | // Talos Settings 55 | // ============================================================================== 56 | 57 | variable "talos_version" { 58 | description = "Talos Linux version." 59 | type = string 60 | default = "v1.9.5" 61 | } 62 | 63 | variable "talos_qemu_iscsi_hash" { 64 | description = "SHA256 hash of the Talos Linux image for QEMU/ISCSI." 65 | type = string 66 | default = "dc7b152cb3ea99b821fcb7340ce7168313ce393d663740b791c36f6e95fc8586" 67 | } 68 | 69 | locals { 70 | talos_image_url = "https://factory.talos.dev/image/${var.talos_qemu_iscsi_hash}/${var.talos_version}/nocloud-amd64.raw.gz" 71 | talos_image_filename = "talos-${var.talos_version}-nocloud-amd64.img" 72 | } 73 | 74 | variable "kubernetes_version" { 75 | type = string 76 | default = "1.32.0" 77 | } 78 | 79 | // ============================================================================== 80 | // Cluster Network Settings 81 | // ============================================================================== 82 | 83 | variable "cluster_node_network" { 84 | description = "The CIDR block for the Kubernetes nodes network." 85 | type = string 86 | default = "192.168.100.0/24" 87 | } 88 | 89 | variable "cluster_node_network_gateway" { 90 | description = "The gateway IP address for the Kubernetes nodes network." 91 | type = string 92 | default = "192.168.100.1" 93 | } 94 | 95 | variable "cluster_vip" { 96 | description = "The Virtual IP used by controller nodes for the Kubernetes API (should be in same subnet)." 97 | type = string 98 | default = "192.168.100.50" 99 | } 100 | 101 | locals { 102 | cluster_endpoint = "https://${var.cluster_vip}:6443" 103 | } 104 | 105 | variable "cluster_node_network_first_controller_hostnum" { 106 | description = "Host number for the first controlplane node (e.g. 192.168.100.60)." 107 | type = number 108 | default = 60 109 | } 110 | 111 | variable "cluster_node_network_first_worker_hostnum" { 112 | description = "Host number for the first worker node (e.g. 168.168.100.70)." 113 | type = number 114 | default = 70 115 | } 116 | 117 | variable "load_balancer_ip_range" { 118 | description = "Range of host numbers to allocate for LoadBalancer services." 119 | default = { 120 | first = 80 121 | last = 85 122 | } 123 | } 124 | 125 | // ============================================================================== 126 | // Node Resource Configuration 127 | // ============================================================================== 128 | 129 | variable "controller_config" { 130 | description = "Resources for control plane nodes." 131 | type = object({ 132 | count = number 133 | cpu = number 134 | memory = number 135 | os_disk = object({ 136 | size = number 137 | datastore = string 138 | }) 139 | }) 140 | default = { 141 | count = 1 142 | cpu = 2 143 | memory = 1024 * 4 144 | 145 | os_disk = { 146 | size = 20 147 | datastore = "local-lvm" 148 | } 149 | } 150 | } 151 | 152 | variable "worker_config" { 153 | description = "Resources for worker nodes." 154 | type = object({ 155 | count = number 156 | cpu = number 157 | memory = number 158 | os_disk = object({ 159 | size = number 160 | datastore = string 161 | }) 162 | longhorn_disk = object({ 163 | size = number 164 | datastore = string 165 | }) 166 | }) 167 | default = { 168 | count = 1 169 | cpu = 4 170 | memory = 1024 * 9 171 | 172 | os_disk = { 173 | size = 20 174 | datastore = "local-lvm" 175 | } 176 | 177 | longhorn_disk = { 178 | size = 40 179 | datastore = "local-lvm" 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /03-gitops/README.md: -------------------------------------------------------------------------------- 1 | # 03-gitops 2 | 3 | In this stage, your Kubernetes cluster becomes fully GitOps-driven with automated deployment of platform services, observability stack, and demo workloads. 4 | Through Argo CD, all components—including platform services, observability tools, and workloads—are deployed in a declarative and reproducible manner. 5 | 6 | ## Purpose 7 | 8 | This layer turns your cluster into a fully GitOps-managed platform, enabling reproducible deployments, real-time observability, and traceable demo applications. 9 | 10 | Argo CD Applications are bootstrapped in a controlled order to ensure service readiness and interdependency handling. This approach enables reproducible, declarative deployment of all Kubernetes workloads via Git. 11 | 12 | ## Structure 13 | 14 | | Layer | Path | Description | 15 | | ------------- | --------------------- | ---------------------------------------------- | 16 | | Applications | `applications/` | Argo CD Application definitions (per stage) | 17 | | Platform Apps | `apps/01-platform/` | Argo CD, cert-manager, Cilium, Longhorn | 18 | | Monitoring | `apps/02-monitoring/` | Prometheus, Grafana, Tempo, Loki, Jaeger, etc. | 19 | | Demo | `apps/03-demo/` | OpenTelemetry Demo (`otel-demo`) | 20 | 21 | ## Ingress Access 22 | 23 | All Ingress resources created in this stage are configured to work with the NGINX Ingress Controller. 24 | They receive static IPs from the Cilium LoadBalancer IP pool. By default, services are exposed via `192.168.100.80`. 25 | 26 | TLS configuration blocks are included in the manifests but commented out. To enable TLS for Ingress resources, uncomment the relevant sections in the Ingress manifests. Then extract the self-signed certificate and add it to your local trust store: 27 | 28 | ```bash 29 | kubectl get secret ingress-tls -n cert-manager -o jsonpath='{.data.tls\.crt}' | base64 -d > local-cluster-root-ca.crt 30 | ``` 31 | 32 | You can then add this certificate to your system trust store: 33 | 34 | * **macOS**: open Keychain Access → drag the file into "System" → set trust to "Always Trust" 35 | * **Linux**: copy to `/usr/local/share/ca-certificates/` and run `sudo update-ca-certificates`. 36 | 37 | To access services via domain names, you can: 38 | 39 | * Update your local `/etc/hosts` file (example below): 40 | 41 | ```bash 42 | 192.168.100.80 argocd.homelab.local grafana.homelab.local prometheus.homelab.local \ 43 | alertmanager.homelab.local loki.homelab.local tempo.homelab.local \ 44 | jaeger.homelab.local longhorn.homelab.local \ 45 | otel-demo.homelab.local otel-demo-loadgen.homelab.local 46 | ``` 47 | 48 | * Or configure wildcard DNS entries (e.g., \*.homelab.local) pointing to the Ingress IP. 49 | 50 | ## Usage 51 | 52 | > **Pre-requisite**: Ensure Argo CD is already running in your cluster. It was installed via Helm in the previous stage (`02-bootstrap`). 53 | 54 | It is recommended to apply the Argo CD Applications in order, as each layer builds upon the previous one (e.g., monitoring components depend on platform services such as cert-manager and ingress). 55 | 56 | ### Step-by-step deployment 57 | 58 | ```bash 59 | # 1. Apply platform components 60 | kubectl apply -f applications/01-platform-bootstrap.yaml 61 | 62 | # 2. Apply observability stack 63 | kubectl apply -f applications/02-monitoring-bootstrap.yaml 64 | 65 | # 3. Apply the OpenTelemetry demo 66 | kubectl apply -f applications/03-otel-demo.yaml 67 | ``` 68 | 69 | ### Application breakdown 70 | 71 | * `01-platform-bootstrap.yaml` 72 | 73 | * Adds Cilium to Argo CD management (already pre-installed) 74 | * Installs cert-manager with self-signed CA 75 | * Deploys Ingress resources for Argo CD and Longhorn UIs 76 | 77 | * `02-monitoring-bootstrap.yaml` 78 | 79 | * Installs kube-prometheus-stack, OpenTelemetry Collector, Loki, Tempo, Jaeger, and Hubble 80 | * Deploys Ingress resources for Hubble, Jaeger, Alertmanager, Grafana, Prometheus, Loki, and Tempo 81 | 82 | * `03-otel-demo.yaml` 83 | 84 | * Deploys the `otel-demo`, an OpenTelemetry example application representing a 21-microservice online store 85 | * Exposes the frontend and load generator via Ingress resources 86 | 87 | After applying all stages, you’ll have a fully observable, GitOps-driven cluster with traceable demo workloads ready for exploration. 88 | 89 | ## UI Previews 90 | 91 | Below are sample screenshots of key components that become available after deploying this layer. All of them are exposed via Ingress with optional TLS. 92 | 93 | ### Lens 94 | 95 | Cluster workloads and sync events visualized in Lens — a Kubernetes dashboard for developers and operators. 96 | 97 | 98 | 99 | ### Argo CD 100 | 101 | Argo CD web interface showing synced applications and their health/status. 102 | 103 | 104 | 105 | ### Grafana 106 | 107 | Observability dashboards with real-time service-level metrics and performance data. 108 | 109 | 110 | 111 | ### Longhorn 112 | 113 | Web UI displaying storage volumes, replicas, and system health status. 114 | 115 | 116 | 117 | ### Hubble 118 | 119 | Cilium-powered service map visualizing real-time network traffic flows. 120 | 121 | 122 | 123 | ### Jaeger 124 | 125 | UI for exploring distributed traces captured by the OpenTelemetry instrumentation. 126 | 127 | 128 | 129 | ### Tempo 130 | 131 | Trace timeline visualization inside Grafana using the Tempo datasource. 132 | 133 | 134 | 135 | ### OpenTelemetry Demo 136 | 137 | The main frontend page of the otel-demo microservices-based e-commerce application. 138 | 139 | 140 | 141 | ### Load Generator 142 | 143 | Locust UI that generates synthetic traffic to simulate real user behavior. 144 | 145 | 146 | 147 | All components shown above are deployed declaratively and updated automatically via Argo CD. 148 | 149 | ## Navigation 150 | 151 | [← 02-bootstrap](../02-bootstrap/README.md) • [↑ Main project README](../README.md) 152 | -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/flagd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: flagd 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: flagd 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: flagd 11 | app.kubernetes.io/name: flagd 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8013 19 | name: rpc 20 | targetPort: 8013 21 | - port: 8016 22 | name: ofrep 23 | targetPort: 8016 24 | - port: 4000 25 | name: tcp-service-0 26 | targetPort: 4000 27 | selector: 28 | 29 | opentelemetry.io/name: flagd 30 | --- 31 | apiVersion: apps/v1 32 | kind: Deployment 33 | metadata: 34 | name: flagd 35 | labels: 36 | helm.sh/chart: opentelemetry-demo-0.37.1 37 | 38 | opentelemetry.io/name: flagd 39 | app.kubernetes.io/instance: otel-demo 40 | app.kubernetes.io/component: flagd 41 | app.kubernetes.io/name: flagd 42 | app.kubernetes.io/version: "2.0.2" 43 | app.kubernetes.io/part-of: opentelemetry-demo 44 | app.kubernetes.io/managed-by: Helm 45 | spec: 46 | replicas: 1 47 | revisionHistoryLimit: 10 48 | selector: 49 | matchLabels: 50 | 51 | opentelemetry.io/name: flagd 52 | template: 53 | metadata: 54 | labels: 55 | 56 | opentelemetry.io/name: flagd 57 | app.kubernetes.io/instance: otel-demo 58 | app.kubernetes.io/component: flagd 59 | app.kubernetes.io/name: flagd 60 | spec: 61 | serviceAccountName: otel-demo 62 | containers: 63 | - name: flagd 64 | image: 'ghcr.io/open-feature/flagd:v0.11.1' 65 | imagePullPolicy: IfNotPresent 66 | command: 67 | - /flagd-build 68 | - start 69 | - --port 70 | - "8013" 71 | - --ofrep-port 72 | - "8016" 73 | - --uri 74 | - file:./etc/flagd/demo.flagd.json 75 | ports: 76 | 77 | - containerPort: 8013 78 | name: rpc 79 | - containerPort: 8016 80 | name: ofrep 81 | env: 82 | - name: OTEL_SERVICE_NAME 83 | valueFrom: 84 | fieldRef: 85 | apiVersion: v1 86 | fieldPath: metadata.labels['app.kubernetes.io/component'] 87 | - name: OTEL_COLLECTOR_NAME 88 | value: otel-collector.monitoring 89 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 90 | value: cumulative 91 | - name: FLAGD_METRICS_EXPORTER 92 | value: otel 93 | - name: FLAGD_OTEL_COLLECTOR_URI 94 | value: $(OTEL_COLLECTOR_NAME):4317 95 | - name: OTEL_RESOURCE_ATTRIBUTES 96 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 97 | resources: 98 | limits: 99 | memory: 75Mi 100 | volumeMounts: 101 | - name: config-rw 102 | mountPath: /etc/flagd 103 | - name: flagd-ui 104 | image: 'ghcr.io/open-telemetry/demo:2.0.2-flagd-ui' 105 | imagePullPolicy: IfNotPresent 106 | ports: 107 | 108 | - containerPort: 4000 109 | name: service 110 | env: 111 | - name: OTEL_SERVICE_NAME 112 | valueFrom: 113 | fieldRef: 114 | apiVersion: v1 115 | fieldPath: metadata.labels['app.kubernetes.io/component'] 116 | - name: OTEL_COLLECTOR_NAME 117 | value: my-otel-collector.opentelemetry-ns 118 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 119 | value: cumulative 120 | - name: FLAGD_METRICS_EXPORTER 121 | value: otel 122 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 123 | value: http://$(OTEL_COLLECTOR_NAME):4318 124 | - name: OTEL_RESOURCE_ATTRIBUTES 125 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 126 | resources: 127 | limits: 128 | memory: 100Mi 129 | volumeMounts: 130 | - mountPath: /app/data 131 | name: config-rw 132 | initContainers: 133 | - command: 134 | - sh 135 | - -c 136 | - cp /config-ro/demo.flagd.json /config-rw/demo.flagd.json && cat /config-rw/demo.flagd.json 137 | image: busybox 138 | name: init-config 139 | volumeMounts: 140 | - mountPath: /config-ro 141 | name: config-ro 142 | - mountPath: /config-rw 143 | name: config-rw 144 | volumes: 145 | - name: config-rw 146 | emptyDir: {} 147 | - configMap: 148 | name: flagd-config 149 | name: config-ro 150 | --- 151 | apiVersion: v1 152 | kind: ConfigMap 153 | metadata: 154 | name: flagd-config 155 | labels: 156 | helm.sh/chart: opentelemetry-demo-0.37.1 157 | 158 | app.kubernetes.io/instance: otel-demo 159 | app.kubernetes.io/version: "2.0.2" 160 | app.kubernetes.io/part-of: opentelemetry-demo 161 | app.kubernetes.io/managed-by: Helm 162 | data: 163 | 164 | demo.flagd.json: | 165 | { 166 | "$schema": "https://flagd.dev/schema/v0/flags.json", 167 | "flags": { 168 | "productCatalogFailure": { 169 | "description": "Fail product catalog service on a specific product", 170 | "state": "ENABLED", 171 | "variants": { 172 | "on": true, 173 | "off": false 174 | }, 175 | "defaultVariant": "off" 176 | }, 177 | "recommendationCacheFailure": { 178 | "description": "Fail recommendation service cache", 179 | "state": "ENABLED", 180 | "variants": { 181 | "on": true, 182 | "off": false 183 | }, 184 | "defaultVariant": "off" 185 | }, 186 | "adManualGc": { 187 | "description": "Triggers full manual garbage collections in the ad service", 188 | "state": "ENABLED", 189 | "variants": { 190 | "on": true, 191 | "off": false 192 | }, 193 | "defaultVariant": "off" 194 | }, 195 | "adHighCpu": { 196 | "description": "Triggers high cpu load in the ad service", 197 | "state": "ENABLED", 198 | "variants": { 199 | "on": true, 200 | "off": false 201 | }, 202 | "defaultVariant": "off" 203 | }, 204 | "adFailure": { 205 | "description": "Fail ad service", 206 | "state": "ENABLED", 207 | "variants": { 208 | "on": true, 209 | "off": false 210 | }, 211 | "defaultVariant": "off" 212 | }, 213 | "kafkaQueueProblems": { 214 | "description": "Overloads Kafka queue while simultaneously introducing a consumer side delay leading to a lag spike", 215 | "state": "ENABLED", 216 | "variants": { 217 | "on": 100, 218 | "off": 0 219 | }, 220 | "defaultVariant": "off" 221 | }, 222 | "cartFailure": { 223 | "description": "Fail cart service", 224 | "state": "ENABLED", 225 | "variants": { 226 | "on": true, 227 | "off": false 228 | }, 229 | "defaultVariant": "off" 230 | }, 231 | "paymentFailure": { 232 | "description": "Fail payment service charge requests n%", 233 | "state": "ENABLED", 234 | "variants": { 235 | "100%": 1, 236 | "90%": 0.95, 237 | "75%": 0.75, 238 | "50%": 0.5, 239 | "25%": 0.25, 240 | "10%": 0.1, 241 | "off": 0 242 | }, 243 | "defaultVariant": "off" 244 | }, 245 | "paymentUnreachable": { 246 | "description": "Payment service is unavailable", 247 | "state": "ENABLED", 248 | "variants": { 249 | "on": true, 250 | "off": false 251 | }, 252 | "defaultVariant": "off" 253 | }, 254 | "loadGeneratorFloodHomepage": { 255 | "description": "Flood the frontend with a large amount of requests.", 256 | "state": "ENABLED", 257 | "variants": { 258 | "on": 100, 259 | "off": 0 260 | }, 261 | "defaultVariant": "off" 262 | }, 263 | "imageSlowLoad": { 264 | "description": "slow loading images in the frontend", 265 | "state": "ENABLED", 266 | "variants": { 267 | "10sec": 10000, 268 | "5sec": 5000, 269 | "off": 0 270 | }, 271 | "defaultVariant": "off" 272 | } 273 | } 274 | } -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/opensearch.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Source: opentelemetry-demo/charts/opensearch/templates/configmap.yaml 3 | apiVersion: v1 4 | kind: ConfigMap 5 | metadata: 6 | name: opensearch-config 7 | labels: 8 | helm.sh/chart: opensearch-2.31.0 9 | app.kubernetes.io/name: opensearch 10 | app.kubernetes.io/instance: otel-demo 11 | app.kubernetes.io/version: "2.19.0" 12 | app.kubernetes.io/managed-by: Helm 13 | app.kubernetes.io/component: opensearch 14 | data: 15 | opensearch.yml: | 16 | cluster.name: opensearch-cluster 17 | 18 | # Bind to all interfaces because we don't know what IP address Docker will assign to us. 19 | network.host: 0.0.0.0 20 | 21 | # Setting network.host to a non-loopback address enables the annoying bootstrap checks. "Single-node" mode disables them again. 22 | # Implicitly done if ".singleNode" is set to "true". 23 | # discovery.type: single-node 24 | 25 | # Start OpenSearch Security Demo Configuration 26 | # WARNING: revise all the lines below before you go into production 27 | # plugins: 28 | # security: 29 | # ssl: 30 | # transport: 31 | # pemcert_filepath: esnode.pem 32 | # pemkey_filepath: esnode-key.pem 33 | # pemtrustedcas_filepath: root-ca.pem 34 | # enforce_hostname_verification: false 35 | # http: 36 | # enabled: true 37 | # pemcert_filepath: esnode.pem 38 | # pemkey_filepath: esnode-key.pem 39 | # pemtrustedcas_filepath: root-ca.pem 40 | # allow_unsafe_democertificates: true 41 | # allow_default_init_securityindex: true 42 | # authcz: 43 | # admin_dn: 44 | # - CN=kirk,OU=client,O=client,L=test,C=de 45 | # audit.type: internal_opensearch 46 | # enable_snapshot_restore_privilege: true 47 | # check_snapshot_restore_write_privileges: true 48 | # restapi: 49 | # roles_enabled: ["all_access", "security_rest_api_access"] 50 | # system_indices: 51 | # enabled: true 52 | # indices: 53 | # [ 54 | # ".opendistro-alerting-config", 55 | # ".opendistro-alerting-alert*", 56 | # ".opendistro-anomaly-results*", 57 | # ".opendistro-anomaly-detector*", 58 | # ".opendistro-anomaly-checkpoints", 59 | # ".opendistro-anomaly-detection-state", 60 | # ".opendistro-reports-*", 61 | # ".opendistro-notifications-*", 62 | # ".opendistro-notebooks", 63 | # ".opendistro-asynchronous-search-response*", 64 | # ] 65 | ######## End OpenSearch Security Demo Configuration ######## 66 | --- 67 | # Source: opentelemetry-demo/charts/opensearch/templates/poddisruptionbudget.yaml 68 | apiVersion: policy/v1 69 | kind: PodDisruptionBudget 70 | metadata: 71 | name: "opensearch-pdb" 72 | labels: 73 | helm.sh/chart: opensearch-2.31.0 74 | app.kubernetes.io/name: opensearch 75 | app.kubernetes.io/instance: otel-demo 76 | app.kubernetes.io/version: "2.19.0" 77 | app.kubernetes.io/managed-by: Helm 78 | app.kubernetes.io/component: opensearch 79 | spec: 80 | maxUnavailable: 1 81 | selector: 82 | matchLabels: 83 | app.kubernetes.io/name: opensearch 84 | app.kubernetes.io/instance: otel-demo 85 | --- 86 | # Source: opentelemetry-demo/charts/opensearch/templates/service.yaml 87 | kind: Service 88 | apiVersion: v1 89 | metadata: 90 | name: opensearch 91 | labels: 92 | helm.sh/chart: opensearch-2.31.0 93 | app.kubernetes.io/name: opensearch 94 | app.kubernetes.io/instance: otel-demo 95 | app.kubernetes.io/version: "2.19.0" 96 | app.kubernetes.io/managed-by: Helm 97 | app.kubernetes.io/component: opensearch 98 | annotations: 99 | {} 100 | spec: 101 | type: ClusterIP 102 | selector: 103 | app.kubernetes.io/name: opensearch 104 | app.kubernetes.io/instance: otel-demo 105 | ports: 106 | - name: http 107 | protocol: TCP 108 | port: 9200 109 | - name: transport 110 | protocol: TCP 111 | port: 9300 112 | - name: metrics 113 | protocol: TCP 114 | port: 9600 115 | --- 116 | # Source: opentelemetry-demo/charts/opensearch/templates/service.yaml 117 | kind: Service 118 | apiVersion: v1 119 | metadata: 120 | name: opensearch-headless 121 | labels: 122 | helm.sh/chart: opensearch-2.31.0 123 | app.kubernetes.io/name: opensearch 124 | app.kubernetes.io/instance: otel-demo 125 | app.kubernetes.io/version: "2.19.0" 126 | app.kubernetes.io/managed-by: Helm 127 | app.kubernetes.io/component: opensearch 128 | annotations: 129 | service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" 130 | spec: 131 | clusterIP: None # This is needed for statefulset hostnames like opensearch-0 to resolve 132 | # Create endpoints also if the related pod isn't ready 133 | publishNotReadyAddresses: true 134 | selector: 135 | app.kubernetes.io/name: opensearch 136 | app.kubernetes.io/instance: otel-demo 137 | ports: 138 | - name: http 139 | port: 9200 140 | - name: transport 141 | port: 9300 142 | - name: metrics 143 | port: 9600 144 | --- 145 | # Source: opentelemetry-demo/charts/opensearch/templates/statefulset.yaml 146 | apiVersion: apps/v1 147 | kind: StatefulSet 148 | metadata: 149 | name: opensearch 150 | labels: 151 | helm.sh/chart: opensearch-2.31.0 152 | app.kubernetes.io/name: opensearch 153 | app.kubernetes.io/instance: otel-demo 154 | app.kubernetes.io/version: "2.19.0" 155 | app.kubernetes.io/managed-by: Helm 156 | app.kubernetes.io/component: opensearch 157 | annotations: 158 | majorVersion: "2" 159 | spec: 160 | serviceName: opensearch-headless 161 | selector: 162 | matchLabels: 163 | app.kubernetes.io/name: opensearch 164 | app.kubernetes.io/instance: otel-demo 165 | replicas: 1 166 | podManagementPolicy: Parallel 167 | updateStrategy: 168 | type: RollingUpdate 169 | template: 170 | metadata: 171 | name: "opensearch" 172 | labels: 173 | helm.sh/chart: opensearch-2.31.0 174 | app.kubernetes.io/name: opensearch 175 | app.kubernetes.io/instance: otel-demo 176 | app.kubernetes.io/version: "2.19.0" 177 | app.kubernetes.io/managed-by: Helm 178 | app.kubernetes.io/component: opensearch 179 | annotations: 180 | configchecksum: d060047b0680a2ac958347eadaf18cabdae24703989987e14d75906e2d0a163 181 | spec: 182 | securityContext: 183 | fsGroup: 1000 184 | runAsUser: 1000 185 | automountServiceAccountToken: false 186 | affinity: 187 | podAntiAffinity: 188 | preferredDuringSchedulingIgnoredDuringExecution: 189 | - weight: 1 190 | podAffinityTerm: 191 | topologyKey: kubernetes.io/hostname 192 | labelSelector: 193 | matchExpressions: 194 | - key: app.kubernetes.io/instance 195 | operator: In 196 | values: 197 | - otel-demo 198 | - key: app.kubernetes.io/name 199 | operator: In 200 | values: 201 | - opensearch 202 | terminationGracePeriodSeconds: 120 203 | volumes: 204 | - name: config 205 | configMap: 206 | name: opensearch-config 207 | - emptyDir: {} 208 | name: config-emptydir 209 | enableServiceLinks: true 210 | initContainers: 211 | - name: configfile 212 | image: "opensearchproject/opensearch:2.19.0" 213 | imagePullPolicy: "IfNotPresent" 214 | command: 215 | - sh 216 | - -c 217 | - | 218 | #!/usr/bin/env bash 219 | cp -r /tmp/configfolder/* /tmp/config/ 220 | resources: 221 | {} 222 | volumeMounts: 223 | - mountPath: /tmp/config/ 224 | name: config-emptydir 225 | - name: config 226 | mountPath: /tmp/configfolder/opensearch.yml 227 | subPath: opensearch.yml 228 | containers: 229 | - name: "opensearch" 230 | securityContext: 231 | capabilities: 232 | drop: 233 | - ALL 234 | runAsNonRoot: true 235 | runAsUser: 1000 236 | 237 | image: "opensearchproject/opensearch:2.19.0" 238 | imagePullPolicy: "IfNotPresent" 239 | readinessProbe: 240 | failureThreshold: 3 241 | periodSeconds: 5 242 | tcpSocket: 243 | port: 9200 244 | timeoutSeconds: 3 245 | startupProbe: 246 | failureThreshold: 30 247 | initialDelaySeconds: 5 248 | periodSeconds: 10 249 | tcpSocket: 250 | port: 9200 251 | timeoutSeconds: 3 252 | ports: 253 | - name: http 254 | containerPort: 9200 255 | - name: transport 256 | containerPort: 9300 257 | - name: metrics 258 | containerPort: 9600 259 | resources: 260 | limits: 261 | memory: 1100Mi 262 | requests: 263 | cpu: 1000m 264 | memory: 100Mi 265 | env: 266 | - name: node.name 267 | valueFrom: 268 | fieldRef: 269 | fieldPath: metadata.name 270 | - name: discovery.seed_hosts 271 | value: "opensearch-cluster-master-headless" 272 | - name: cluster.name 273 | value: "demo-cluster" 274 | - name: network.host 275 | value: "0.0.0.0" 276 | - name: OPENSEARCH_JAVA_OPTS 277 | value: "-Xms300m -Xmx300m" 278 | - name: node.roles 279 | value: "master,ingest,data,remote_cluster_client," 280 | - name: discovery.type 281 | value: "single-node" 282 | - name: bootstrap.memory_lock 283 | value: "true" 284 | - name: DISABLE_INSTALL_DEMO_CONFIG 285 | value: "true" 286 | - name: DISABLE_SECURITY_PLUGIN 287 | value: "true" 288 | volumeMounts: 289 | - name: config-emptydir 290 | mountPath: /usr/share/opensearch/config/opensearch.yml 291 | subPath: opensearch.yml 292 | -------------------------------------------------------------------------------- /03-gitops/apps/03-demo/otel-demo/base/product-catalog.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: product-catalog 5 | labels: 6 | helm.sh/chart: opentelemetry-demo-0.37.1 7 | 8 | opentelemetry.io/name: product-catalog 9 | app.kubernetes.io/instance: otel-demo 10 | app.kubernetes.io/component: product-catalog 11 | app.kubernetes.io/name: product-catalog 12 | app.kubernetes.io/version: "2.0.2" 13 | app.kubernetes.io/part-of: opentelemetry-demo 14 | app.kubernetes.io/managed-by: Helm 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - port: 8080 19 | name: tcp-service 20 | targetPort: 8080 21 | selector: 22 | 23 | opentelemetry.io/name: product-catalog 24 | --- 25 | apiVersion: apps/v1 26 | kind: Deployment 27 | metadata: 28 | name: product-catalog 29 | labels: 30 | helm.sh/chart: opentelemetry-demo-0.37.1 31 | 32 | opentelemetry.io/name: product-catalog 33 | app.kubernetes.io/instance: otel-demo 34 | app.kubernetes.io/component: product-catalog 35 | app.kubernetes.io/name: product-catalog 36 | app.kubernetes.io/version: "2.0.2" 37 | app.kubernetes.io/part-of: opentelemetry-demo 38 | app.kubernetes.io/managed-by: Helm 39 | spec: 40 | replicas: 1 41 | revisionHistoryLimit: 10 42 | selector: 43 | matchLabels: 44 | 45 | opentelemetry.io/name: product-catalog 46 | template: 47 | metadata: 48 | labels: 49 | 50 | opentelemetry.io/name: product-catalog 51 | app.kubernetes.io/instance: otel-demo 52 | app.kubernetes.io/component: product-catalog 53 | app.kubernetes.io/name: product-catalog 54 | spec: 55 | serviceAccountName: otel-demo 56 | containers: 57 | - name: product-catalog 58 | image: 'ghcr.io/open-telemetry/demo:2.0.2-product-catalog' 59 | imagePullPolicy: IfNotPresent 60 | ports: 61 | 62 | - containerPort: 8080 63 | name: service 64 | env: 65 | - name: OTEL_SERVICE_NAME 66 | valueFrom: 67 | fieldRef: 68 | apiVersion: v1 69 | fieldPath: metadata.labels['app.kubernetes.io/component'] 70 | - name: OTEL_COLLECTOR_NAME 71 | value: otel-collector.monitoring 72 | - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE 73 | value: cumulative 74 | - name: PRODUCT_CATALOG_PORT 75 | value: "8080" 76 | - name: PRODUCT_CATALOG_RELOAD_INTERVAL 77 | value: "10" 78 | - name: FLAGD_HOST 79 | value: flagd 80 | - name: FLAGD_PORT 81 | value: "8013" 82 | - name: OTEL_EXPORTER_OTLP_ENDPOINT 83 | value: http://$(OTEL_COLLECTOR_NAME):4317 84 | - name: OTEL_RESOURCE_ATTRIBUTES 85 | value: service.name=$(OTEL_SERVICE_NAME),service.namespace=opentelemetry-demo,service.version=2.0.2 86 | resources: 87 | limits: 88 | memory: 20Mi 89 | volumeMounts: 90 | - name: product-catalog-products 91 | mountPath: /usr/src/app/products 92 | volumes: 93 | - name: product-catalog-products 94 | configMap: 95 | name: product-catalog-products 96 | --- 97 | apiVersion: v1 98 | kind: ConfigMap 99 | metadata: 100 | name: product-catalog-products 101 | labels: 102 | helm.sh/chart: opentelemetry-demo-0.37.1 103 | 104 | app.kubernetes.io/instance: otel-demo 105 | app.kubernetes.io/version: "2.0.2" 106 | app.kubernetes.io/part-of: opentelemetry-demo 107 | app.kubernetes.io/managed-by: Helm 108 | data: 109 | 110 | products.json: | 111 | { 112 | "products": [ 113 | { 114 | "id": "OLJCESPC7Z", 115 | "name": "National Park Foundation Explorascope", 116 | "description": "The National Park Foundation’s (NPF) Explorascope 60AZ is a manual alt-azimuth, refractor telescope perfect for celestial viewing on the go. The NPF Explorascope 60 can view the planets, moon, star clusters and brighter deep sky objects like the Orion Nebula and Andromeda Galaxy.", 117 | "picture": "NationalParkFoundationExplorascope.jpg", 118 | "priceUsd": { 119 | "currencyCode": "USD", 120 | "units": 101, 121 | "nanos": 960000000 122 | }, 123 | "categories": [ 124 | "telescopes" 125 | ] 126 | }, 127 | { 128 | "id": "66VCHSJNUP", 129 | "name": "Starsense Explorer Refractor Telescope", 130 | "description": "The first telescope that uses your smartphone to analyze the night sky and calculate its position in real time. StarSense Explorer is ideal for beginners thanks to the app’s user-friendly interface and detailed tutorials. It’s like having your own personal tour guide of the night sky", 131 | "picture": "StarsenseExplorer.jpg", 132 | "priceUsd": { 133 | "currencyCode": "USD", 134 | "units": 349, 135 | "nanos": 950000000 136 | }, 137 | "categories": [ 138 | "telescopes" 139 | ] 140 | }, 141 | { 142 | "id": "1YMWWN1N4O", 143 | "name": "Eclipsmart Travel Refractor Telescope", 144 | "description": "Dedicated white-light solar scope for the observer on the go. The 50mm refracting solar scope uses Solar Safe, ISO compliant, full-aperture glass filter material to ensure the safest view of solar events. The kit comes complete with everything you need, including the dedicated travel solar scope, a Solar Safe finderscope, tripod, a high quality 20mm (18x) Kellner eyepiece and a nylon backpack to carry everything in. This Travel Solar Scope makes it easy to share the Sun as well as partial and total solar eclipses with the whole family and offers much higher magnifications than you would otherwise get using handheld solar viewers or binoculars.", 145 | "picture": "EclipsmartTravelRefractorTelescope.jpg", 146 | "priceUsd": { 147 | "currencyCode": "USD", 148 | "units": 129, 149 | "nanos": 950000000 150 | }, 151 | "categories": [ 152 | "telescopes", 153 | "travel" 154 | ] 155 | }, 156 | { 157 | "id": "L9ECAV7KIM", 158 | "name": "Lens Cleaning Kit", 159 | "description": "Wipe away dust, dirt, fingerprints and other particles on your lenses to see clearly with the Lens Cleaning Kit. This cleaning kit works on all glass and optical surfaces, including telescopes, binoculars, spotting scopes, monoculars, microscopes, and even your camera lenses, computer screens, and mobile devices. The kit comes complete with a retractable lens brush to remove dust particles and dirt and two options to clean smudges and fingerprints off of your optics, pre-moistened lens wipes and a bottled lens cleaning fluid with soft cloth.", 160 | "picture": "LensCleaningKit.jpg", 161 | "priceUsd": { 162 | "currencyCode": "USD", 163 | "units": 21, 164 | "nanos": 950000000 165 | }, 166 | "categories": [ 167 | "accessories" 168 | ] 169 | }, 170 | { 171 | "id": "2ZYFJ3GM2N", 172 | "name": "Roof Binoculars", 173 | "description": "This versatile, all-around binocular is a great choice for the trail, the stadium, the arena, or just about anywhere you want a close-up view of the action without sacrificing brightness or detail. It’s an especially great companion for nature observation and bird watching, with ED glass that helps you spot the subtlest field markings and a close focus of just 6.5 feet.", 174 | "picture": "RoofBinoculars.jpg", 175 | "priceUsd": { 176 | "currencyCode": "USD", 177 | "units": 209, 178 | "nanos": 950000000 179 | }, 180 | "categories": [ 181 | "binoculars" 182 | ] 183 | }, 184 | { 185 | "id": "0PUK6V6EV0", 186 | "name": "Solar System Color Imager", 187 | "description": "You have your new telescope and have observed Saturn and Jupiter. Now you're ready to take the next step and start imaging them. But where do you begin? The NexImage 10 Solar System Imager is the perfect solution.", 188 | "picture": "SolarSystemColorImager.jpg", 189 | "priceUsd": { 190 | "currencyCode": "USD", 191 | "units": 175, 192 | "nanos": 0 193 | }, 194 | "categories": [ 195 | "accessories", 196 | "telescopes" 197 | ] 198 | }, 199 | { 200 | "id": "LS4PSXUNUM", 201 | "name": "Red Flashlight", 202 | "description": "This 3-in-1 device features a 3-mode red flashlight, a hand warmer, and a portable power bank for recharging your personal electronics on the go. Whether you use it to light the way at an astronomy star party, a night walk, or wildlife research, ThermoTorch 3 Astro Red’s rugged, IPX4-rated design will withstand your everyday activities.", 203 | "picture": "RedFlashlight.jpg", 204 | "priceUsd": { 205 | "currencyCode": "USD", 206 | "units": 57, 207 | "nanos": 80000000 208 | }, 209 | "categories": [ 210 | "accessories", 211 | "flashlights" 212 | ] 213 | }, 214 | { 215 | "id": "9SIQT8TOJO", 216 | "name": "Optical Tube Assembly", 217 | "description": "Capturing impressive deep-sky astroimages is easier than ever with Rowe-Ackermann Schmidt Astrograph (RASA) V2, the perfect companion to today’s top DSLR or astronomical CCD cameras. This fast, wide-field f/2.2 system allows for shorter exposure times compared to traditional f/10 astroimaging, without sacrificing resolution. Because shorter sub-exposure times are possible, your equatorial mount won’t need to accurately track over extended periods. The short focal length also lessens equatorial tracking demands. In many cases, autoguiding will not be required.", 218 | "picture": "OpticalTubeAssembly.jpg", 219 | "priceUsd": { 220 | "currencyCode": "USD", 221 | "units": 3599, 222 | "nanos": 0 223 | }, 224 | "categories": [ 225 | "accessories", 226 | "telescopes", 227 | "assembly" 228 | ] 229 | }, 230 | { 231 | "id": "6E92ZMYYFZ", 232 | "name": "Solar Filter", 233 | "description": "Enhance your viewing experience with EclipSmart Solar Filter for 8” telescopes. With two Velcro straps and four self-adhesive Velcro pads for added safety, you can be assured that the solar filter cannot be accidentally knocked off and will provide Solar Safe, ISO compliant viewing.", 234 | "picture": "SolarFilter.jpg", 235 | "priceUsd": { 236 | "currencyCode": "USD", 237 | "units": 69, 238 | "nanos": 950000000 239 | }, 240 | "categories": [ 241 | "accessories", 242 | "telescopes" 243 | ] 244 | }, 245 | { 246 | "id": "HQTGWGPNH4", 247 | "name": "The Comet Book", 248 | "description": "A 16th-century treatise on comets, created anonymously in Flanders (now northern France) and now held at the Universitätsbibliothek Kassel. Commonly known as The Comet Book (or Kometenbuch in German), its full title translates as “Comets and their General and Particular Meanings, According to Ptolomeé, Albumasar, Haly, Aliquind and other Astrologers”. The image is from https://publicdomainreview.org/collection/the-comet-book, made available by the Universitätsbibliothek Kassel under a CC-BY SA 4.0 license (https://creativecommons.org/licenses/by-sa/4.0/)", 249 | "picture": "TheCometBook.jpg", 250 | "priceUsd": { 251 | "currencyCode": "USD", 252 | "units": 0, 253 | "nanos": 990000000 254 | }, 255 | "categories": [ 256 | "books" 257 | ] 258 | } 259 | ] 260 | } -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/kube-prometheus-stack/otel-grafana-dashboards/exemplars-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "grafana", 8 | "uid": "-- Grafana --" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "type": "dashboard" 15 | } 16 | ] 17 | }, 18 | "editable": true, 19 | "fiscalYearStartMonth": 0, 20 | "graphTooltip": 0, 21 | "id": 5, 22 | "links": [], 23 | "panels": [ 24 | { 25 | "fieldConfig": { 26 | "defaults": {}, 27 | "overrides": [] 28 | }, 29 | "gridPos": { 30 | "h": 2, 31 | "w": 24, 32 | "x": 0, 33 | "y": 0 34 | }, 35 | "id": 8, 36 | "options": { 37 | "code": { 38 | "language": "plaintext", 39 | "showLineNumbers": false, 40 | "showMiniMap": false 41 | }, 42 | "content": "This dashboard shows the use of metric exemplars.\nExemplars can be used to look up a trace in Jaeger.\nOnly the most recent exemplars may still be available in Jaeger.\n
\nChart panels may require 5 minutes after the Demo is started before rendering data.", 43 | "mode": "html" 44 | }, 45 | "pluginVersion": "11.4.0", 46 | "title": "", 47 | "type": "text" 48 | }, 49 | { 50 | "collapsed": false, 51 | "gridPos": { 52 | "h": 1, 53 | "w": 24, 54 | "x": 0, 55 | "y": 2 56 | }, 57 | "id": 4, 58 | "panels": [], 59 | "title": "GetCart Exemplars", 60 | "type": "row" 61 | }, 62 | { 63 | "datasource": { 64 | "type": "prometheus", 65 | "uid": "webstore-metrics" 66 | }, 67 | "fieldConfig": { 68 | "defaults": { 69 | "custom": { 70 | "hideFrom": { 71 | "legend": false, 72 | "tooltip": false, 73 | "viz": false 74 | }, 75 | "scaleDistribution": { 76 | "type": "linear" 77 | } 78 | } 79 | }, 80 | "overrides": [] 81 | }, 82 | "gridPos": { 83 | "h": 9, 84 | "w": 24, 85 | "x": 0, 86 | "y": 3 87 | }, 88 | "id": 2, 89 | "interval": "2m", 90 | "options": { 91 | "calculate": false, 92 | "cellGap": 1, 93 | "color": { 94 | "exponent": 0.5, 95 | "fill": "dark-orange", 96 | "mode": "scheme", 97 | "reverse": false, 98 | "scale": "exponential", 99 | "scheme": "Spectral", 100 | "steps": 64 101 | }, 102 | "exemplars": { 103 | "color": "rgba(255,0,255,0.7)" 104 | }, 105 | "filterValues": { 106 | "le": 1e-9 107 | }, 108 | "legend": { 109 | "show": true 110 | }, 111 | "rowsFrame": { 112 | "layout": "auto" 113 | }, 114 | "tooltip": { 115 | "mode": "single", 116 | "showColorScale": false, 117 | "yHistogram": false 118 | }, 119 | "yAxis": { 120 | "axisPlacement": "left", 121 | "reverse": false 122 | } 123 | }, 124 | "pluginVersion": "11.4.0", 125 | "targets": [ 126 | { 127 | "disableTextWrap": false, 128 | "editorMode": "builder", 129 | "exemplar": true, 130 | "expr": "sum by(le) (rate(app_cart_get_cart_latency_bucket[$__rate_interval]))", 131 | "format": "heatmap", 132 | "fullMetaSearch": false, 133 | "includeNullMetadata": false, 134 | "instant": true, 135 | "legendFormat": "{{le}}", 136 | "range": true, 137 | "refId": "A", 138 | "useBackend": false 139 | } 140 | ], 141 | "title": "GetCart Latency Heatmap with Exemplars", 142 | "type": "heatmap" 143 | }, 144 | { 145 | "datasource": { 146 | "type": "prometheus", 147 | "uid": "webstore-metrics" 148 | }, 149 | "fieldConfig": { 150 | "defaults": { 151 | "color": { 152 | "mode": "palette-classic" 153 | }, 154 | "custom": { 155 | "axisBorderShow": false, 156 | "axisCenteredZero": false, 157 | "axisColorMode": "text", 158 | "axisLabel": "", 159 | "axisPlacement": "auto", 160 | "barAlignment": 0, 161 | "barWidthFactor": 0.6, 162 | "drawStyle": "line", 163 | "fillOpacity": 0, 164 | "gradientMode": "none", 165 | "hideFrom": { 166 | "legend": false, 167 | "tooltip": false, 168 | "viz": false 169 | }, 170 | "insertNulls": false, 171 | "lineInterpolation": "linear", 172 | "lineWidth": 1, 173 | "pointSize": 5, 174 | "scaleDistribution": { 175 | "type": "linear" 176 | }, 177 | "showPoints": "auto", 178 | "spanNulls": false, 179 | "stacking": { 180 | "group": "A", 181 | "mode": "none" 182 | }, 183 | "thresholdsStyle": { 184 | "mode": "off" 185 | } 186 | }, 187 | "mappings": [], 188 | "thresholds": { 189 | "mode": "absolute", 190 | "steps": [ 191 | { 192 | "color": "green", 193 | "value": null 194 | }, 195 | { 196 | "color": "red", 197 | "value": 80 198 | } 199 | ] 200 | } 201 | }, 202 | "overrides": [] 203 | }, 204 | "gridPos": { 205 | "h": 10, 206 | "w": 24, 207 | "x": 0, 208 | "y": 12 209 | }, 210 | "id": 5, 211 | "interval": "2m", 212 | "options": { 213 | "legend": { 214 | "calcs": [], 215 | "displayMode": "list", 216 | "placement": "bottom", 217 | "showLegend": true 218 | }, 219 | "tooltip": { 220 | "mode": "single", 221 | "sort": "none" 222 | } 223 | }, 224 | "pluginVersion": "11.4.0", 225 | "targets": [ 226 | { 227 | "datasource": { 228 | "type": "prometheus", 229 | "uid": "webstore-metrics" 230 | }, 231 | "disableTextWrap": false, 232 | "editorMode": "builder", 233 | "exemplar": true, 234 | "expr": "histogram_quantile(0.95, sum by(le) (rate(app_cart_get_cart_latency_bucket[$__rate_interval])))", 235 | "fullMetaSearch": false, 236 | "includeNullMetadata": false, 237 | "legendFormat": "p95 GetCart", 238 | "range": true, 239 | "refId": "A", 240 | "useBackend": false 241 | } 242 | ], 243 | "title": "95th Pct Cart GetCart Latency with Exemplars", 244 | "type": "timeseries" 245 | }, 246 | { 247 | "collapsed": false, 248 | "gridPos": { 249 | "h": 1, 250 | "w": 24, 251 | "x": 0, 252 | "y": 22 253 | }, 254 | "id": 3, 255 | "panels": [], 256 | "title": "AddItem Exemplars", 257 | "type": "row" 258 | }, 259 | { 260 | "datasource": { 261 | "type": "prometheus", 262 | "uid": "webstore-metrics" 263 | }, 264 | "fieldConfig": { 265 | "defaults": { 266 | "custom": { 267 | "hideFrom": { 268 | "legend": false, 269 | "tooltip": false, 270 | "viz": false 271 | }, 272 | "scaleDistribution": { 273 | "type": "linear" 274 | } 275 | } 276 | }, 277 | "overrides": [] 278 | }, 279 | "gridPos": { 280 | "h": 9, 281 | "w": 24, 282 | "x": 0, 283 | "y": 23 284 | }, 285 | "id": 6, 286 | "interval": "2m", 287 | "options": { 288 | "calculate": false, 289 | "cellGap": 1, 290 | "color": { 291 | "exponent": 0.5, 292 | "fill": "dark-orange", 293 | "mode": "scheme", 294 | "reverse": false, 295 | "scale": "exponential", 296 | "scheme": "Spectral", 297 | "steps": 64 298 | }, 299 | "exemplars": { 300 | "color": "rgba(255,0,255,0.7)" 301 | }, 302 | "filterValues": { 303 | "le": 1e-9 304 | }, 305 | "legend": { 306 | "show": true 307 | }, 308 | "rowsFrame": { 309 | "layout": "auto" 310 | }, 311 | "tooltip": { 312 | "mode": "single", 313 | "showColorScale": false, 314 | "yHistogram": false 315 | }, 316 | "yAxis": { 317 | "axisPlacement": "left", 318 | "reverse": false 319 | } 320 | }, 321 | "pluginVersion": "11.4.0", 322 | "targets": [ 323 | { 324 | "disableTextWrap": false, 325 | "editorMode": "builder", 326 | "exemplar": true, 327 | "expr": "sum by(le) (rate(app_cart_add_item_latency_bucket[$__rate_interval]))", 328 | "format": "heatmap", 329 | "fullMetaSearch": false, 330 | "includeNullMetadata": false, 331 | "instant": true, 332 | "legendFormat": "{{le}}", 333 | "range": true, 334 | "refId": "A", 335 | "useBackend": false 336 | } 337 | ], 338 | "title": "AddItem Latency Heatmap with Exemplars", 339 | "type": "heatmap" 340 | }, 341 | { 342 | "datasource": { 343 | "type": "prometheus", 344 | "uid": "webstore-metrics" 345 | }, 346 | "fieldConfig": { 347 | "defaults": { 348 | "color": { 349 | "mode": "palette-classic" 350 | }, 351 | "custom": { 352 | "axisBorderShow": false, 353 | "axisCenteredZero": false, 354 | "axisColorMode": "text", 355 | "axisLabel": "", 356 | "axisPlacement": "auto", 357 | "barAlignment": 0, 358 | "barWidthFactor": 0.6, 359 | "drawStyle": "line", 360 | "fillOpacity": 0, 361 | "gradientMode": "none", 362 | "hideFrom": { 363 | "legend": false, 364 | "tooltip": false, 365 | "viz": false 366 | }, 367 | "insertNulls": false, 368 | "lineInterpolation": "linear", 369 | "lineWidth": 1, 370 | "pointSize": 5, 371 | "scaleDistribution": { 372 | "type": "linear" 373 | }, 374 | "showPoints": "auto", 375 | "spanNulls": false, 376 | "stacking": { 377 | "group": "A", 378 | "mode": "none" 379 | }, 380 | "thresholdsStyle": { 381 | "mode": "off" 382 | } 383 | }, 384 | "mappings": [], 385 | "thresholds": { 386 | "mode": "absolute", 387 | "steps": [ 388 | { 389 | "color": "green" 390 | }, 391 | { 392 | "color": "red", 393 | "value": 80 394 | } 395 | ] 396 | } 397 | }, 398 | "overrides": [] 399 | }, 400 | "gridPos": { 401 | "h": 10, 402 | "w": 24, 403 | "x": 0, 404 | "y": 32 405 | }, 406 | "id": 1, 407 | "interval": "2m", 408 | "options": { 409 | "legend": { 410 | "calcs": [], 411 | "displayMode": "list", 412 | "placement": "bottom", 413 | "showLegend": true 414 | }, 415 | "tooltip": { 416 | "mode": "single", 417 | "sort": "none" 418 | } 419 | }, 420 | "pluginVersion": "11.4.0", 421 | "targets": [ 422 | { 423 | "datasource": { 424 | "type": "prometheus", 425 | "uid": "webstore-metrics" 426 | }, 427 | "disableTextWrap": false, 428 | "editorMode": "builder", 429 | "exemplar": true, 430 | "expr": "histogram_quantile(0.95, sum by(le) (rate(app_cart_add_item_latency_bucket[$__rate_interval])))", 431 | "fullMetaSearch": false, 432 | "includeNullMetadata": false, 433 | "legendFormat": "p95 AddItem", 434 | "range": true, 435 | "refId": "A", 436 | "useBackend": false 437 | } 438 | ], 439 | "title": "95th Pct Cart AddItem Latency with Exemplars", 440 | "type": "timeseries" 441 | } 442 | ], 443 | "preload": false, 444 | "schemaVersion": 40, 445 | "tags": ["otel-demo"], 446 | "templating": { 447 | "list": [] 448 | }, 449 | "time": { 450 | "from": "now-15m", 451 | "to": "now" 452 | }, 453 | "timepicker": {}, 454 | "timezone": "browser", 455 | "title": "Cart Service Exemplars", 456 | "uid": "ce6sd46kfkglca", 457 | "version": 3, 458 | "weekStart": "" 459 | } -------------------------------------------------------------------------------- /03-gitops/apps/02-monitoring/kube-prometheus-stack/otel-grafana-dashboards/demo-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "grafana", 8 | "uid": "-- Grafana --" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "target": { 15 | "limit": 100, 16 | "matchAny": false, 17 | "tags": [], 18 | "type": "dashboard" 19 | }, 20 | "type": "dashboard" 21 | } 22 | ] 23 | }, 24 | "editable": true, 25 | "fiscalYearStartMonth": 0, 26 | "graphTooltip": 1, 27 | "id": 1, 28 | "links": [], 29 | "liveNow": false, 30 | "panels": [ 31 | { 32 | "gridPos": { 33 | "h": 3, 34 | "w": 24, 35 | "x": 0, 36 | "y": 0 37 | }, 38 | "id": 21, 39 | "options": { 40 | "code": { 41 | "language": "plaintext", 42 | "showLineNumbers": false, 43 | "showMiniMap": false 44 | }, 45 | "content": "This dashboard shows RED metrics for the selected service, as generated by the spanmetrics connector in the OpenTelemetry Collector.\nIf the selected service emits logs, the logs will also be displayed.\nCustom metrics generated by some services are also displayed. \n
\nChart panels may require 5 minutes after the Demo is started before rendering data.", 46 | "mode": "html" 47 | }, 48 | "pluginVersion": "10.3.1", 49 | "type": "text" 50 | }, 51 | { 52 | "collapsed": false, 53 | "gridPos": { 54 | "h": 1, 55 | "w": 24, 56 | "x": 0, 57 | "y": 3 58 | }, 59 | "id": 14, 60 | "panels": [], 61 | "title": "Spanmetrics (RED metrics)", 62 | "type": "row" 63 | }, 64 | { 65 | "datasource": { 66 | "type": "prometheus", 67 | "uid": "webstore-metrics" 68 | }, 69 | "fieldConfig": { 70 | "defaults": { 71 | "color": { 72 | "mode": "palette-classic" 73 | }, 74 | "custom": { 75 | "axisBorderShow": false, 76 | "axisCenteredZero": false, 77 | "axisColorMode": "text", 78 | "axisLabel": "", 79 | "axisPlacement": "auto", 80 | "barAlignment": 0, 81 | "drawStyle": "line", 82 | "fillOpacity": 0, 83 | "gradientMode": "none", 84 | "hideFrom": { 85 | "legend": false, 86 | "tooltip": false, 87 | "viz": false 88 | }, 89 | "insertNulls": false, 90 | "lineInterpolation": "linear", 91 | "lineWidth": 1, 92 | "pointSize": 5, 93 | "scaleDistribution": { 94 | "type": "linear" 95 | }, 96 | "showPoints": "never", 97 | "spanNulls": false, 98 | "stacking": { 99 | "group": "A", 100 | "mode": "none" 101 | }, 102 | "thresholdsStyle": { 103 | "mode": "off" 104 | } 105 | }, 106 | "mappings": [], 107 | "thresholds": { 108 | "mode": "absolute", 109 | "steps": [ 110 | { 111 | "color": "green", 112 | "value": null 113 | }, 114 | { 115 | "color": "red", 116 | "value": 80 117 | } 118 | ] 119 | }, 120 | "unit": "reqps", 121 | "unitScale": true 122 | }, 123 | "overrides": [] 124 | }, 125 | "gridPos": { 126 | "h": 8, 127 | "w": 8, 128 | "x": 0, 129 | "y": 4 130 | }, 131 | "id": 12, 132 | "interval": "2m", 133 | "options": { 134 | "legend": { 135 | "calcs": [], 136 | "displayMode": "list", 137 | "placement": "bottom", 138 | "showLegend": true 139 | }, 140 | "tooltip": { 141 | "hideZeros": false, 142 | "mode": "multi", 143 | "sort": "desc" 144 | } 145 | }, 146 | "pluginVersion": "11.5.2", 147 | "targets": [ 148 | { 149 | "datasource": { 150 | "type": "prometheus", 151 | "uid": "webstore-metrics" 152 | }, 153 | "editorMode": "code", 154 | "expr": "sum by (span_name) (rate(traces_span_metrics_duration_milliseconds_count{service_name=\"${service}\"}[$__rate_interval]))", 155 | "legendFormat": "{{ span_name }}", 156 | "range": true, 157 | "refId": "A" 158 | } 159 | ], 160 | "title": "Requests Rate by Span Name", 161 | "type": "timeseries" 162 | }, 163 | { 164 | "datasource": { 165 | "type": "prometheus", 166 | "uid": "webstore-metrics" 167 | }, 168 | "description": "", 169 | "fieldConfig": { 170 | "defaults": { 171 | "color": { 172 | "mode": "palette-classic" 173 | }, 174 | "custom": { 175 | "axisBorderShow": false, 176 | "axisCenteredZero": false, 177 | "axisColorMode": "text", 178 | "axisLabel": "", 179 | "axisPlacement": "auto", 180 | "barAlignment": 0, 181 | "drawStyle": "line", 182 | "fillOpacity": 0, 183 | "gradientMode": "none", 184 | "hideFrom": { 185 | "legend": false, 186 | "tooltip": false, 187 | "viz": false 188 | }, 189 | "insertNulls": false, 190 | "lineInterpolation": "linear", 191 | "lineWidth": 1, 192 | "pointSize": 5, 193 | "scaleDistribution": { 194 | "type": "linear" 195 | }, 196 | "showPoints": "never", 197 | "spanNulls": false, 198 | "stacking": { 199 | "group": "A", 200 | "mode": "none" 201 | }, 202 | "thresholdsStyle": { 203 | "mode": "off" 204 | } 205 | }, 206 | "mappings": [], 207 | "thresholds": { 208 | "mode": "absolute", 209 | "steps": [ 210 | { 211 | "color": "green", 212 | "value": null 213 | }, 214 | { 215 | "color": "red", 216 | "value": 80 217 | } 218 | ] 219 | }, 220 | "unitScale": true 221 | }, 222 | "overrides": [] 223 | }, 224 | "gridPos": { 225 | "h": 8, 226 | "w": 8, 227 | "x": 8, 228 | "y": 4 229 | }, 230 | "id": 10, 231 | "interval": "2m", 232 | "options": { 233 | "legend": { 234 | "calcs": [], 235 | "displayMode": "list", 236 | "placement": "bottom", 237 | "showLegend": true 238 | }, 239 | "tooltip": { 240 | "hideZeros": false, 241 | "mode": "multi", 242 | "sort": "desc" 243 | } 244 | }, 245 | "pluginVersion": "11.5.2", 246 | "targets": [ 247 | { 248 | "datasource": { 249 | "type": "prometheus", 250 | "uid": "webstore-metrics" 251 | }, 252 | "editorMode": "code", 253 | "expr": "sum by (span_name) (rate(traces_span_metrics_calls_total{status_code=\"STATUS_CODE_ERROR\", service_name=\"${service}\"}[$__rate_interval]))", 254 | "interval": "", 255 | "legendFormat": "{{ span_name }}", 256 | "range": true, 257 | "refId": "A" 258 | } 259 | ], 260 | "title": "Error Rate by Span Name", 261 | "type": "timeseries" 262 | }, 263 | { 264 | "datasource": { 265 | "type": "prometheus", 266 | "uid": "webstore-metrics" 267 | }, 268 | "description": "", 269 | "fieldConfig": { 270 | "defaults": { 271 | "color": { 272 | "mode": "palette-classic" 273 | }, 274 | "custom": { 275 | "axisBorderShow": false, 276 | "axisCenteredZero": false, 277 | "axisColorMode": "text", 278 | "axisLabel": "", 279 | "axisPlacement": "auto", 280 | "barAlignment": 0, 281 | "drawStyle": "line", 282 | "fillOpacity": 0, 283 | "gradientMode": "none", 284 | "hideFrom": { 285 | "legend": false, 286 | "tooltip": false, 287 | "viz": false 288 | }, 289 | "insertNulls": false, 290 | "lineInterpolation": "linear", 291 | "lineWidth": 1, 292 | "pointSize": 5, 293 | "scaleDistribution": { 294 | "type": "linear" 295 | }, 296 | "showPoints": "never", 297 | "spanNulls": false, 298 | "stacking": { 299 | "group": "A", 300 | "mode": "none" 301 | }, 302 | "thresholdsStyle": { 303 | "mode": "off" 304 | } 305 | }, 306 | "mappings": [], 307 | "thresholds": { 308 | "mode": "absolute", 309 | "steps": [ 310 | { 311 | "color": "green", 312 | "value": null 313 | }, 314 | { 315 | "color": "red", 316 | "value": 80 317 | } 318 | ] 319 | }, 320 | "unit": "dtdurationms", 321 | "unitScale": true 322 | }, 323 | "overrides": [] 324 | }, 325 | "gridPos": { 326 | "h": 8, 327 | "w": 8, 328 | "x": 16, 329 | "y": 4 330 | }, 331 | "id": 2, 332 | "interval": "2m", 333 | "options": { 334 | "legend": { 335 | "calcs": [], 336 | "displayMode": "list", 337 | "placement": "bottom", 338 | "showLegend": true 339 | }, 340 | "tooltip": { 341 | "hideZeros": false, 342 | "mode": "multi", 343 | "sort": "desc" 344 | } 345 | }, 346 | "pluginVersion": "11.5.2", 347 | "targets": [ 348 | { 349 | "datasource": { 350 | "type": "prometheus", 351 | "uid": "webstore-metrics" 352 | }, 353 | "editorMode": "code", 354 | "exemplar": false, 355 | "expr": "histogram_quantile(0.50, sum(rate(traces_span_metrics_duration_milliseconds_bucket{service_name=\"${service}\"}[$__rate_interval])) by (le, span_name))", 356 | "legendFormat": "{{span_name}}", 357 | "range": true, 358 | "refId": "A" 359 | } 360 | ], 361 | "title": "Average Duration by Span Name", 362 | "type": "timeseries" 363 | }, 364 | { 365 | "collapsed": false, 366 | "gridPos": { 367 | "h": 1, 368 | "w": 24, 369 | "x": 0, 370 | "y": 12 371 | }, 372 | "id": 18, 373 | "panels": [], 374 | "title": "Application Metrics", 375 | "type": "row" 376 | }, 377 | { 378 | "datasource": { 379 | "type": "prometheus", 380 | "uid": "webstore-metrics" 381 | }, 382 | "fieldConfig": { 383 | "defaults": { 384 | "color": { 385 | "mode": "palette-classic" 386 | }, 387 | "custom": { 388 | "axisBorderShow": false, 389 | "axisCenteredZero": false, 390 | "axisColorMode": "text", 391 | "axisLabel": "", 392 | "axisPlacement": "auto", 393 | "barAlignment": 0, 394 | "drawStyle": "line", 395 | "fillOpacity": 0, 396 | "gradientMode": "none", 397 | "hideFrom": { 398 | "legend": false, 399 | "tooltip": false, 400 | "viz": false 401 | }, 402 | "insertNulls": false, 403 | "lineInterpolation": "linear", 404 | "lineWidth": 1, 405 | "pointSize": 5, 406 | "scaleDistribution": { 407 | "type": "linear" 408 | }, 409 | "showPoints": "never", 410 | "spanNulls": false, 411 | "stacking": { 412 | "group": "A", 413 | "mode": "none" 414 | }, 415 | "thresholdsStyle": { 416 | "mode": "off" 417 | } 418 | }, 419 | "mappings": [], 420 | "thresholds": { 421 | "mode": "absolute", 422 | "steps": [ 423 | { 424 | "color": "green", 425 | "value": null 426 | }, 427 | { 428 | "color": "red", 429 | "value": 80 430 | } 431 | ] 432 | }, 433 | "unit": "percent", 434 | "unitScale": true 435 | }, 436 | "overrides": [] 437 | }, 438 | "gridPos": { 439 | "h": 8, 440 | "w": 12, 441 | "x": 0, 442 | "y": 13 443 | }, 444 | "id": 6, 445 | "options": { 446 | "legend": { 447 | "calcs": [], 448 | "displayMode": "list", 449 | "placement": "bottom", 450 | "showLegend": true 451 | }, 452 | "tooltip": { 453 | "hideZeros": false, 454 | "mode": "multi", 455 | "sort": "desc" 456 | } 457 | }, 458 | "pluginVersion": "11.5.2", 459 | "targets": [ 460 | { 461 | "datasource": { 462 | "type": "prometheus", 463 | "uid": "webstore-metrics" 464 | }, 465 | "editorMode": "code", 466 | "expr": "rate(process_runtime_cpython_cpu_time_seconds_total{type=~\"system\"}[$__rate_interval])*100", 467 | "hide": false, 468 | "interval": "2m", 469 | "legendFormat": "{{job}} ({{type}})", 470 | "range": true, 471 | "refId": "A" 472 | }, 473 | { 474 | "datasource": { 475 | "type": "prometheus", 476 | "uid": "webstore-metrics" 477 | }, 478 | "editorMode": "code", 479 | "expr": "rate(process_runtime_cpython_cpu_time_seconds_total{type=~\"user\"}[$__rate_interval])*100", 480 | "hide": false, 481 | "interval": "2m", 482 | "legendFormat": "{{job}} ({{type}})", 483 | "range": true, 484 | "refId": "B" 485 | } 486 | ], 487 | "title": "Python services (CPU%)", 488 | "transformations": [ 489 | { 490 | "id": "renameByRegex", 491 | "options": { 492 | "regex": "opentelemetry-demo/(.*)", 493 | "renamePattern": "$1" 494 | } 495 | } 496 | ], 497 | "type": "timeseries" 498 | }, 499 | { 500 | "datasource": { 501 | "type": "prometheus", 502 | "uid": "webstore-metrics" 503 | }, 504 | "fieldConfig": { 505 | "defaults": { 506 | "color": { 507 | "mode": "palette-classic" 508 | }, 509 | "custom": { 510 | "axisBorderShow": false, 511 | "axisCenteredZero": false, 512 | "axisColorMode": "text", 513 | "axisLabel": "", 514 | "axisPlacement": "auto", 515 | "barAlignment": 0, 516 | "drawStyle": "line", 517 | "fillOpacity": 0, 518 | "gradientMode": "none", 519 | "hideFrom": { 520 | "legend": false, 521 | "tooltip": false, 522 | "viz": false 523 | }, 524 | "insertNulls": false, 525 | "lineInterpolation": "linear", 526 | "lineWidth": 1, 527 | "pointSize": 5, 528 | "scaleDistribution": { 529 | "type": "linear" 530 | }, 531 | "showPoints": "never", 532 | "spanNulls": false, 533 | "stacking": { 534 | "group": "A", 535 | "mode": "none" 536 | }, 537 | "thresholdsStyle": { 538 | "mode": "off" 539 | } 540 | }, 541 | "mappings": [], 542 | "thresholds": { 543 | "mode": "absolute", 544 | "steps": [ 545 | { 546 | "color": "green", 547 | "value": null 548 | }, 549 | { 550 | "color": "red", 551 | "value": 80 552 | } 553 | ] 554 | }, 555 | "unit": "bytes", 556 | "unitScale": true 557 | }, 558 | "overrides": [] 559 | }, 560 | "gridPos": { 561 | "h": 8, 562 | "w": 12, 563 | "x": 12, 564 | "y": 13 565 | }, 566 | "id": 8, 567 | "options": { 568 | "legend": { 569 | "calcs": [], 570 | "displayMode": "list", 571 | "placement": "bottom", 572 | "showLegend": true 573 | }, 574 | "tooltip": { 575 | "hideZeros": false, 576 | "mode": "multi", 577 | "sort": "desc" 578 | } 579 | }, 580 | "pluginVersion": "11.5.2", 581 | "targets": [ 582 | { 583 | "datasource": { 584 | "type": "prometheus", 585 | "uid": "webstore-metrics" 586 | }, 587 | "editorMode": "code", 588 | "expr": "process_runtime_cpython_memory_bytes{type=\"rss\"}", 589 | "legendFormat": "{{job}}", 590 | "range": true, 591 | "refId": "A" 592 | } 593 | ], 594 | "title": "Python services (Memory)", 595 | "transformations": [ 596 | { 597 | "id": "renameByRegex", 598 | "options": { 599 | "regex": "opentelemetry-demo/(.*)", 600 | "renamePattern": "$1" 601 | } 602 | } 603 | ], 604 | "type": "timeseries" 605 | }, 606 | { 607 | "datasource": { 608 | "type": "prometheus", 609 | "uid": "webstore-metrics" 610 | }, 611 | "fieldConfig": { 612 | "defaults": { 613 | "color": { 614 | "mode": "palette-classic" 615 | }, 616 | "custom": { 617 | "axisBorderShow": false, 618 | "axisCenteredZero": false, 619 | "axisColorMode": "text", 620 | "axisLabel": "", 621 | "axisPlacement": "auto", 622 | "barAlignment": 0, 623 | "drawStyle": "line", 624 | "fillOpacity": 0, 625 | "gradientMode": "none", 626 | "hideFrom": { 627 | "legend": false, 628 | "tooltip": false, 629 | "viz": false 630 | }, 631 | "insertNulls": false, 632 | "lineInterpolation": "linear", 633 | "lineWidth": 1, 634 | "pointSize": 5, 635 | "scaleDistribution": { 636 | "type": "linear" 637 | }, 638 | "showPoints": "never", 639 | "spanNulls": false, 640 | "stacking": { 641 | "group": "A", 642 | "mode": "none" 643 | }, 644 | "thresholdsStyle": { 645 | "mode": "off" 646 | } 647 | }, 648 | "mappings": [], 649 | "thresholds": { 650 | "mode": "absolute", 651 | "steps": [ 652 | { 653 | "color": "green", 654 | "value": null 655 | }, 656 | { 657 | "color": "red", 658 | "value": 80 659 | } 660 | ] 661 | }, 662 | "unitScale": true 663 | }, 664 | "overrides": [] 665 | }, 666 | "gridPos": { 667 | "h": 8, 668 | "w": 12, 669 | "x": 0, 670 | "y": 21 671 | }, 672 | "id": 4, 673 | "options": { 674 | "legend": { 675 | "calcs": [], 676 | "displayMode": "list", 677 | "placement": "bottom", 678 | "showLegend": false 679 | }, 680 | "tooltip": { 681 | "hideZeros": false, 682 | "mode": "multi", 683 | "sort": "desc" 684 | } 685 | }, 686 | "pluginVersion": "11.5.2", 687 | "targets": [ 688 | { 689 | "datasource": { 690 | "type": "prometheus", 691 | "uid": "webstore-metrics" 692 | }, 693 | "editorMode": "code", 694 | "expr": "rate(app_recommendations_counter_total{recommendation_type=\"catalog\"}[$__rate_interval])", 695 | "interval": "2m", 696 | "legendFormat": "recommendations", 697 | "range": true, 698 | "refId": "A" 699 | } 700 | ], 701 | "title": "Recommendations Rate", 702 | "type": "timeseries" 703 | }, 704 | { 705 | "datasource": { 706 | "type": "prometheus", 707 | "uid": "webstore-metrics" 708 | }, 709 | "fieldConfig": { 710 | "defaults": { 711 | "color": { 712 | "mode": "palette-classic" 713 | }, 714 | "custom": { 715 | "axisBorderShow": false, 716 | "axisCenteredZero": false, 717 | "axisColorMode": "text", 718 | "axisLabel": "", 719 | "axisPlacement": "auto", 720 | "barAlignment": 0, 721 | "drawStyle": "line", 722 | "fillOpacity": 0, 723 | "gradientMode": "none", 724 | "hideFrom": { 725 | "legend": false, 726 | "tooltip": false, 727 | "viz": false 728 | }, 729 | "insertNulls": false, 730 | "lineInterpolation": "linear", 731 | "lineWidth": 1, 732 | "pointSize": 5, 733 | "scaleDistribution": { 734 | "type": "linear" 735 | }, 736 | "showPoints": "never", 737 | "spanNulls": false, 738 | "stacking": { 739 | "group": "A", 740 | "mode": "none" 741 | }, 742 | "thresholdsStyle": { 743 | "mode": "off" 744 | } 745 | }, 746 | "mappings": [], 747 | "thresholds": { 748 | "mode": "absolute", 749 | "steps": [ 750 | { 751 | "color": "green", 752 | "value": null 753 | }, 754 | { 755 | "color": "red", 756 | "value": 80 757 | } 758 | ] 759 | }, 760 | "unitScale": true 761 | }, 762 | "overrides": [] 763 | }, 764 | "gridPos": { 765 | "h": 8, 766 | "w": 12, 767 | "x": 12, 768 | "y": 21 769 | }, 770 | "id": 16, 771 | "interval": "2m", 772 | "options": { 773 | "legend": { 774 | "calcs": [], 775 | "displayMode": "list", 776 | "placement": "bottom", 777 | "showLegend": true 778 | }, 779 | "tooltip": { 780 | "hideZeros": false, 781 | "mode": "multi", 782 | "sort": "desc" 783 | } 784 | }, 785 | "pluginVersion": "11.5.2", 786 | "targets": [ 787 | { 788 | "datasource": { 789 | "type": "prometheus", 790 | "uid": "webstore-metrics" 791 | }, 792 | "editorMode": "code", 793 | "expr": "rate(otel_trace_span_processor_spans{job=\"quote\"}[2m])*120", 794 | "interval": "2m", 795 | "legendFormat": "{{state}}", 796 | "range": true, 797 | "refId": "A" 798 | } 799 | ], 800 | "title": "Quote Service batch span processor", 801 | "type": "timeseries" 802 | } 803 | ], 804 | "refresh": "", 805 | "schemaVersion": 39, 806 | "tags": [ 807 | "otel-demo" 808 | ], 809 | "templating": { 810 | "list": [ 811 | { 812 | "current": { 813 | "selected": true, 814 | "text": "frontend", 815 | "value": "frontend" 816 | }, 817 | "datasource": { 818 | "type": "prometheus", 819 | "uid": "webstore-metrics" 820 | }, 821 | "definition": "traces_span_metrics_duration_milliseconds_bucket", 822 | "hide": 0, 823 | "includeAll": false, 824 | "label": "Service", 825 | "multi": false, 826 | "name": "service", 827 | "options": [], 828 | "query": { 829 | "query": "traces_span_metrics_duration_milliseconds_bucket", 830 | "refId": "PrometheusVariableQueryEditor-VariableQuery" 831 | }, 832 | "refresh": 1, 833 | "regex": "/.*service.name=\\\"([^\\\"]+)\\\".*/", 834 | "skipUrlSync": false, 835 | "sort": 1, 836 | "type": "query" 837 | } 838 | ] 839 | }, 840 | "time": { 841 | "from": "now-15m", 842 | "to": "now" 843 | }, 844 | "timepicker": {}, 845 | "timezone": "", 846 | "title": "Demo Dashboard", 847 | "uid": "W2gX2zHVk", 848 | "version": 1, 849 | "weekStart": "" 850 | } --------------------------------------------------------------------------------