Project 3 · Kubernetes Platform & GitOps

EKS GitOps Platform

A platform split across two repositories with a clean separation of concerns. eks-infrastructure (Terraform) provisions the cluster and bootstraps ArgoCD; eks-gitops-apps is the declarative source of truth for everything that runs inside the cluster. After bootstrap, ArgoCD continuously reconciles the cluster to Git.

This page is the visual overview. Full source code, deploy guides, and runbooks live in the two GitHub repositories below.

01 Why two repositories

Infrastructure provisioning and in-cluster desired state have different lifecycles, different blast radii, and different tools. Keeping them apart means a Terraform change can never accidentally alter a running workload, and an app change never touches cloud infrastructure.

flowchart LR
  subgraph infra["eks-infrastructure (Terraform)"]
    direction TB
    tf["VPC · EKS control plane · node group
IAM / IRSA · backend state"] argo["Installs ArgoCD"] root["Creates root ArgoCD app
bootstrap/root-app.yaml"] tf --> argo --> root end subgraph apps["eks-gitops-apps (Git = source of truth)"] direction TB kust["apps/kustomization.yaml"] a1["aws-load-balancer-controller"] a2["sample-app · flask-app"] a3["monitoring-chart"] a4["monitoring-resources"] a5["telegram-alerts"] kust --> a1 & a2 & a3 & a4 & a5 end root ==>|"points ArgoCD to apps/ path"| kust classDef i fill:#22303f,stroke:#4493f8,color:#e6edf3; classDef a fill:#12351f,stroke:#3fb950,color:#e6edf3; class tf,argo,root i; class kust,a1,a2,a3,a4,a5 a;
The handoff point: Terraform's root-app.yaml tells ArgoCD to watch the apps/ path in the GitOps repo. From there, Git is the source of truth — not manual kubectl.

02 Bootstrap & handoff sequence

The infrastructure repo is applied first. Its deploy script provisions AWS resources, wires up cluster access, installs ArgoCD (plus the Prometheus Operator CRDs the monitoring layer needs), and creates the root application — the moment control passes to GitOps.

sequenceDiagram
  actor Eng as Engineer
  participant TF as Terraform
  participant EKS as EKS cluster
  participant Argo as ArgoCD
  participant Git as eks-gitops-apps

  Eng->>TF: apply (VPC, EKS, node group, IAM)
  TF->>EKS: cluster provisioned
  Eng->>EKS: update kubeconfig + apply aws-auth.yaml
  Eng->>EKS: install ArgoCD + Prometheus Operator CRDs
  Eng->>Argo: apply bootstrap/root-app.yaml (root app)
  Argo->>Git: read apps/kustomization.yaml
  loop continuous reconciliation
    Argo->>Git: detect desired state
    Argo->>EKS: sync cluster to match Git
  end
  Note over Argo,EKS: From here, changes ship via git commit + push
        
Because the monitoring Helm chart is installed with skipCrds: true, the deploy script pre-installs the Prometheus Operator CRDs before ArgoCD reconciles — so ServiceMonitor, PrometheusRule, and AlertmanagerConfig apply cleanly without sync failures.

03 The GitOps reconciliation loop

Changes are never applied by hand. An engineer edits a manifest, commits, and pushes; ArgoCD detects the drift between Git and the cluster and reconciles. A CI pipeline validates manifests before they reach the cluster.

flowchart LR
  dev(["Edit manifest"]) --> commit["git commit + push"]
  commit --> ci{"CI validation"}
  ci -->|"kubectl kustomize
(render)"| render["Rendered manifests"] render -->|"kubeconform
(schema check)"| ok{"Valid?"} ok -->|no| fail["Blocked before cluster"] ok -->|yes| merged["Merged to Git"] merged --> argo["ArgoCD detects drift"] argo --> sync["Sync cluster to desired state"] classDef n fill:#1c2330,stroke:#ff9900,color:#e6edf3; class dev,commit,render,merged,argo,sync n;
Validation renders Kustomize output first, then schema-checks it — because in a Kustomize setup many errors only appear after overlays are composed. -ignore-missing-schemas lets valid CRDs pass while still strictly checking core Kubernetes resources.

04 Observability: platform vs. configuration split

Monitoring is deliberately split into two ArgoCD applications. monitoring-chart installs the kube-prometheus-stack platform (Prometheus, Alertmanager, Grafana) via Helm with skipCrds: true; monitoring-resources layers the project-specific ServiceMonitor, alert rules (PrometheusRule), and Alertmanager routing on top. This keeps the Helm chart clean while allowing custom observability to evolve independently.

flowchart LR
  m1["Flask /metrics endpoint"] --> sm["ServiceMonitor"]
  sm --> prom["Prometheus"]
  prom --> rule["PrometheusRule
(alert rules)"] rule --> am["Alertmanager"] am --> hook["telegram-alerts
webhook service"] hook --> tg(["Telegram"]) classDef plat fill:#22303f,stroke:#4493f8,color:#e6edf3; classDef res fill:#12351f,stroke:#3fb950,color:#e6edf3; class prom,am plat; class sm,rule,hook res;
The alerting path from a metric to a Telegram message. Blue = installed by the monitoring chart (platform). Green = defined in monitoring-resources (project configuration) and the Telegram webhook app.
Secrets stay out of Git. Sensitive values (e.g. the Telegram bot token) are never committed. They are created directly in the cluster as Kubernetes secrets and referenced by manifests, so the GitOps repo can safely remain public and auditable.

05 Design decisions & tradeoffs

Repository separation. Two repos add a small amount of coordination (the ALB controller needs VPC ID / IRSA ARN from the infra repo) in exchange for clean lifecycle isolation between infrastructure and application state.
PR checks exclude terraform plan/apply. Running plan in PR CI would require the remote S3 backend, live AWS credentials, and environment variables — coupling validation to real infrastructure. PR checks stay fast and deterministic (fmt, validate, tflint, tfsec); apply happens in controlled deployment pipelines.
Not every tfsec finding is remediated. The project intentionally surfaces security findings (e.g. VPC flow logs left off to show the observability-vs-cost tradeoff) rather than artificially hardening everything — demonstrating prioritization, which mirrors real platforms.
← Back to all projects