Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion projects/03-eks-gitops-argocd/.github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
#placeholder for now
# GitOps CI — validates K8s manifests on push.
# ArgoCD handles the actual deploy by watching this repo and auto-syncing.

name: Validate K8s Manifests

on:
push:
branches: [main]
paths:
- "projects/03-eks-gitops-argocd/k8s/**"
pull_request:
branches: [main]
paths:
- "projects/03-eks-gitops-argocd/k8s/**"
workflow_dispatch:

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate manifests (dry-run)
run: |
kubectl apply --dry-run=client -f projects/03-eks-gitops-argocd/k8s/

- name: Lint YAML
run: |
pip install yamllint
yamllint projects/03-eks-gitops-argocd/k8s/
121 changes: 121 additions & 0 deletions projects/03-eks-gitops-argocd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Project 03 – EKS GitOps with ArgoCD

Manual kubectl deployments create configuration drift, lack audit trails, and don't scale across teams. This project implements a GitOps delivery pipeline where Git is the single source of truth — every change is version-controlled, peer-reviewed, and automatically reconciled to the cluster by ArgoCD.

## Overview

A production-style GitOps pipeline on Amazon EKS (Fargate) using ArgoCD for continuous delivery. Terraform provisions the infrastructure (VPC, EKS cluster, Fargate profiles, ArgoCD via Helm), and ArgoCD watches this repository for Kubernetes manifest changes and auto-syncs them to the cluster. A sample nginx application demonstrates the full push-to-deploy workflow.

## Architecture

```mermaid
flowchart LR
Dev[Developer] -->|git push| GH[GitHub Repo]
GH -->|webhook / poll| Argo[ArgoCD]
Argo -->|sync| EKS[EKS Cluster - Fargate]

subgraph EKS
direction TB
NS1[argocd namespace]
NS2[app namespace]
NS2 --> Pod1[nginx pod]
NS2 --> Pod2[nginx pod]
NS2 --> Svc[ClusterIP Service]
end
```

## Key Features

- **GitOps delivery** — Git push triggers automatic cluster sync via ArgoCD
- **Self-healing** — ArgoCD reverts manual cluster changes to match the Git state
- **Automated pruning** — Deleted manifests are automatically removed from the cluster
- **Fargate-native** — No EC2 nodes to manage; pods run on serverless compute
- **Infrastructure as Code** — Full VPC, EKS, and ArgoCD provisioned with Terraform
- **CI validation** — GitHub Actions validates K8s manifests on every push

## Tech Stack

- **Cloud/IaC:** Terraform, AWS VPC, Amazon EKS, Fargate, IAM
- **GitOps:** ArgoCD (Helm-deployed), Application CRD
- **App:** nginx on Kubernetes (Deployment, Service, ConfigMap)
- **CI:** GitHub Actions (manifest validation + YAML lint)

## Prerequisites

- AWS CLI configured with appropriate credentials
- Terraform >= 1.9.0
- kubectl
- ArgoCD CLI (`brew install argocd` or [releases](https://github.com/argoproj/argo-cd/releases))
- Helm 3

## Quick Start

1. **Provision infrastructure**
```bash
cd projects/03-eks-gitops-argocd/infra/terraform
terraform init
terraform apply
```

2. **Configure kubectl**
```bash
aws eks update-kubeconfig --region us-east-1 --name gitops-demo
```

3. **Initialize ArgoCD**
```bash
./scripts/init-argocd.sh
```

4. **Verify the GitOps workflow**
- Edit a file in `k8s/` (e.g., change replica count in `deployment.yaml`)
- Commit and push
- Watch ArgoCD sync: `argocd app get nginx-demo --watch --grpc-web`

## GitOps Workflow

```
1. Developer pushes manifest change to GitHub
2. GitHub Actions validates the YAML (dry-run + lint)
3. ArgoCD detects the new commit (polls every ~3 min)
4. ArgoCD compares desired state (Git) vs live state (cluster)
5. ArgoCD applies the diff — pods roll, services update
6. Self-heal: any manual cluster drift is reverted automatically
```

## Project Structure

```
03-eks-gitops-argocd/
infra/terraform/ # VPC, EKS, Fargate, ArgoCD Helm release
argocd/
application.yaml # ArgoCD Application CR
helm-chart/values.yaml # ArgoCD Helm overrides
k8s/ # App manifests (ArgoCD sync source)
namespace.yaml
deployment.yaml
service.yaml
configmap.yaml
scripts/
init-argocd.sh # Post-deploy setup helper
.github/workflows/
deploy.yml # CI manifest validation
```

## Cleanup

```bash
cd projects/03-eks-gitops-argocd/infra/terraform
terraform destroy
```

## Interview Talking Points

- **Why GitOps over CI/CD push?** GitOps provides a declarative, auditable, self-healing delivery model. The cluster always converges to what's in Git — no imperative scripts that can partially fail.
- **How does ArgoCD detect drift?** It polls the Git repo (configurable interval) and compares the rendered manifests against the live cluster state using a 3-way diff.
- **Why Fargate?** Eliminates node management overhead. Each pod runs in its own micro-VM, providing workload isolation without managing EC2 instances or AMI updates.
- **What happens if someone kubectl-edits a resource?** ArgoCD's self-heal reverts it within the next sync cycle, enforcing Git as the single source of truth.

## Why This Project Matters

GitOps is the standard delivery model for Kubernetes-native organizations. This project demonstrates the end-to-end pattern — IaC provisioning, Helm-managed platform components, declarative app delivery, and automated drift correction — the exact workflow used by platform teams at scale.
24 changes: 24 additions & 0 deletions projects/03-eks-gitops-argocd/argocd/application.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-demo
namespace: argocd
spec:
project: default

source:
# Update this URL to your fork/clone of this repo
repoURL: https://github.com/TerminalsandCoffee/aws-devops-portfolio.git
targetRevision: main
path: projects/03-eks-gitops-argocd/k8s

destination:
server: https://kubernetes.default.svc
namespace: app

syncPolicy:
automated:
prune: true # Remove resources deleted from Git
selfHeal: true # Revert manual cluster changes to match Git
syncOptions:
- CreateNamespace=true
50 changes: 49 additions & 1 deletion projects/03-eks-gitops-argocd/argocd/helm-chart/values.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
#placeholder for now
# ArgoCD Helm values for EKS Fargate deployment

server:
service:
type: LoadBalancer
# Run insecure — TLS terminates at the load balancer
extraArgs:
- --insecure
resources:
requests:
cpu: 256m
memory: 256Mi
limits:
cpu: 512m
memory: 512Mi

controller:
resources:
requests:
cpu: 256m
memory: 512Mi
limits:
cpu: 512m
memory: 1Gi

repoServer:
resources:
requests:
cpu: 256m
memory: 256Mi
limits:
cpu: 512m
memory: 512Mi

redis:
resources:
requests:
cpu: 128m
memory: 128Mi
limits:
cpu: 256m
memory: 256Mi

# Disable components not needed for this demo
dex:
enabled: false

notifications:
enabled: false
20 changes: 20 additions & 0 deletions projects/03-eks-gitops-argocd/docs/architecture.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
flowchart LR
Dev[Developer] -->|git push| GH[GitHub Repo]
GH -->|webhook / poll| Argo[ArgoCD]
Argo -->|sync manifests| EKS[EKS Cluster]

subgraph EKS[EKS Cluster - Fargate]
direction TB
ArgoNS[argocd namespace<br/>ArgoCD Server + Controller]
AppNS[app namespace]

subgraph AppNS[app namespace]
Dep[Deployment: nginx x2]
Svc[Service: ClusterIP:80]
CM[ConfigMap: custom HTML]
Dep --> Svc
CM -.->|mounted| Dep
end
end

GH -.->|GitHub Actions<br/>validates YAML| CI[CI Check]
22 changes: 8 additions & 14 deletions projects/03-eks-gitops-argocd/infra/terraform/argocd.tf
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
resource "helm_release" "argocd" {
name = "argocd"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
version = "5.52.1" # latest stable at time of writing
namespace = "argocd"
name = "argocd"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
version = "5.52.1"
namespace = "argocd"
create_namespace = true

set {
name = "server.service.type"
value = "LoadBalancer"
}
values = [file("${path.module}/../../argocd/helm-chart/values.yaml")]

set {
name = "server.extraArgs"
value = "{--insecure}"
}
}
depends_on = [module.eks]
}
3 changes: 0 additions & 3 deletions projects/03-eks-gitops-argocd/infra/terraform/backend.tf

This file was deleted.

2 changes: 1 addition & 1 deletion projects/03-eks-gitops-argocd/infra/terraform/eks.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module "eks" {
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
cluster_endpoint_private_access = true
cluster_endpoint_public_access = false
cluster_endpoint_public_access = true

# Enable Container Insights
cluster_enabled_log_types = ["api", "audit", "authenticator", "controllerManager", "scheduler"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
data "aws_eks_cluster_auth" "this" {
name = module.eks.cluster_id
name = module.eks.cluster_name
}

provider "kubernetes" {
Expand Down
24 changes: 9 additions & 15 deletions projects/03-eks-gitops-argocd/infra/terraform/main.tf
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
output "cluster_endpoint" {
value = module.eks.cluster_endpoint
}

output "cluster_name" {
value = module.eks.cluster_id
}

output "configure_kubectl" {
value = "aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_id}"
}

output "argocd_url" {
value = "http://${helm_release.argocd.status[0].load_balancer_ingress[0].hostname}"
}
# Root module — resources are organized in dedicated files:
# vpc.tf – VPC and networking
# eks.tf – EKS cluster and Fargate profiles
# argocd.tf – ArgoCD Helm release
# helm_providers.tf – Kubernetes/Helm provider config
# outputs.tf – All outputs
# variables.tf – All variables
# versions.tf – Terraform/provider versions + backend
# provider.tf – AWS provider
14 changes: 11 additions & 3 deletions projects/03-eks-gitops-argocd/infra/terraform/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
output "cluster_name" {
value = module.eks.cluster_name
description = "EKS cluster name"
value = module.eks.cluster_name
}

output "cluster_endpoint" {
value = module.eks.cluster_endpoint
description = "EKS cluster API endpoint"
value = module.eks.cluster_endpoint
}

output "configure_kubectl" {
description = "Command to configure kubectl"
value = "aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_name}"
}

output "argocd_url" {
value = kubernetes_service.argocd_server.metadata[0].name
description = "ArgoCD server URL (available after Helm release completes)"
value = "Run: kubectl get svc argocd-server -n argocd -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'"
}
30 changes: 1 addition & 29 deletions projects/03-eks-gitops-argocd/infra/terraform/provider.tf
Original file line number Diff line number Diff line change
@@ -1,31 +1,3 @@
terraform {
required_version = ">= 1.9.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
}

# Remote S3 backend – create the bucket once (see script at end)
backend "s3" {
bucket = "yourname-devops-portfolio-tfstate"
key = "eks-gitops/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}

provider "aws" {
region = var.aws_region
}
}
Loading