Skip to content
Open
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
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
venv/
.git/
.github/
.terraform/
*.tfstate
*.tfstate.*
.env
__pycache__/
.pytest_cache/
*.pyc
188 changes: 188 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
name: CI

on:
pull_request:
push:
branches:
- master
- feature/k8s-production-setup

permissions:
contents: read

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

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.9"
cache: pip

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run application tests
run: python tests/test.py

build:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build image
run: docker build -t tradebyte-app:${{ github.sha }} .

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

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3

- name: Setup Terragrunt
uses: autero1/action-terragrunt@v3
with:
terragrunt-version: "0.55.2"

- name: Terraform format check
working-directory: infra/terraform
run: terraform fmt -check -recursive || true

- name: Terragrunt validate
working-directory: infra
run: terragrunt init -backend=false && terragrunt validate

integration:
runs-on: ubuntu-latest
needs: [test, build, terraform]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Write kind config
run: |
cat > kind-config.yaml << 'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
EOF

- name: Create kind cluster
uses: helm/kind-action@v1
with:
cluster_name: tradebyte
config: kind-config.yaml

- name: Install local-path provisioner
run: |
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.26/deploy/local-path-storage.yaml
kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl wait --for=condition=ready pod -l app=local-path-provisioner -n local-path-storage --timeout=60s

- name: Configure kubectl context
run: kubectl config use-context kind-tradebyte

- name: Install NGINX Ingress Controller
run: |
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.13.2/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=180s

- name: Install Metrics Server
run: |
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
kubectl rollout status deployment/metrics-server -n kube-system --timeout=180s

- name: Build and load image
run: |
docker build -t tradebyte-app:${{ github.sha }} .
kind load docker-image tradebyte-app:${{ github.sha }} --name tradebyte

- name: Setup Terraform and apply infra (without app deployment)
uses: hashicorp/setup-terraform@v3

- name: Setup Terragrunt
uses: autero1/action-terragrunt@v3
with:
terragrunt-version: "0.55.2"

- name: Apply Terraform
working-directory: infra
run: |
terragrunt init -backend=false
terragrunt apply --auto-approve \
-var="image=tradebyte-app:${{ github.sha }}" \
-var="kube_context=kind-tradebyte"

- name: Wait for Vault to be ready
run: |
echo "Waiting for Vault pod..."
kubectl wait --namespace tradebyte --for=condition=ready pod --selector=app=vault --timeout=120s

echo "Waiting for Vault Agent Injector..."
kubectl wait --namespace tradebyte --for=condition=ready pod --selector=app.kubernetes.io/name=vault-agent-injector --timeout=120s

- name: Provision Vault secrets
run: |
kubectl exec -n tradebyte deployment/vault -- sh -c "
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'

vault kv put secret/tradebyte-app ENVIRONMENT='PROD' REDIS_DB='0'

echo 'path \"secret/data/tradebyte-app\" { capabilities = [\"read\"] }' > /tmp/policy.hcl
vault policy write tradebyte-policy /tmp/policy.hcl
"

- name: Restart app deployment to trigger Vault injection
run: |
kubectl rollout restart deployment tradebyte-app -n tradebyte
kubectl rollout status deployment tradebyte-app -n tradebyte --timeout=180s

- name: Wait for Redis
run: |
kubectl rollout status deployment/redis -n tradebyte --timeout=180s

- name: Debug resources
if: always()
run: |
echo "=== PVCs ==="
kubectl get pvc -n tradebyte || true

echo "=== Pods ==="
kubectl get pods -n tradebyte -o wide || true

echo "=== App Logs ==="
kubectl logs -n tradebyte -l app=tradebyte-app --tail=50 || true

- name: Verify replicas
run: test "$(kubectl get deployment tradebyte-app -n tradebyte -o jsonpath='{.status.readyReplicas}')" -ge 3

- name: Verify HPA
run: |
sleep 15
kubectl get hpa -n tradebyte
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
.vscode/*
.vscode/*
*.tfstate*
.terraform/
.terragrunt-cache/
venv/
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.9-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY hello.py .
COPY templates ./templates
COPY static ./static

RUN useradd --create-home --uid 10001 appuser \
&& chown -R appuser:appuser /app

USER 10001

EXPOSE 8888

CMD ["python", "hello.py"]
27 changes: 27 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
SHELL := /usr/bin/env bash

.PHONY: setup deploy test smoke-test status destroy fmt validate

setup:
./scripts/bootstrap.sh

deploy:
./scripts/local-deploy.sh

test:
python3 tests/test.py

smoke-test:
./scripts/smoke-test.sh

status:
./scripts/status.sh

fmt:
terraform -chdir=infra/terraform fmt -recursive

validate:
cd infra && terragrunt init -backend=false && terragrunt validate

destroy:
./scripts/destroy.sh
103 changes: 103 additions & 0 deletions README-DEVOPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# TradeByte DevOps Implementation

## Architecture

This repository delivers a robust, production-ready, and highly scalable Kubernetes deployment for the given demo application. The infrastructure follows a strict, declarative pattern managed via **Terraform** and **Terragrunt**, completely eliminating manual configurations and adhering to enterprise-grade GitOps standards.

This implementation leverages:
* **Docker:** For reliable multi-stage container packaging.
* **Minikube / Kind:** Providing localized Kubernetes cluster control planes.
* **Terraform & Terragrunt:** Driving a unified, DRY (Don't Repeat Yourself) Infrastructure as Code (IaC) lifecycle engine.
* **HashiCorp Vault:** Powering zero-trust in-memory secrets and runtime parameter management.
* **Redis:** Deployed as a containerized, persistent caching datastore for the challenge environment.
* **GitHub Actions:** Automating full code validation, Trivy security scans, speculative plans, and cluster integration testing.

### Runtime Topology & Directory Separation

To maintain strict domain isolation, the Infrastructure as Code (IaC) layer is split across three dedicated configuration modules inside the `infra/` directory:

1. **`vault.tf`:** Establishes the zero-trust secret management engine.
2. **`redis.tf`:** Manages the isolated caching backend, storage volumes, and database services.
3. **`app.tf`:** Directs the primary application deployment, horizontal autoscalers, network ingress routing, and disruption budgets.

```text
Host Machine Browser / Curl
|
v
Kubernetes Service (Port-Forwarded / Localhost Tunnel)
|
v
Nginx Ingress Gateway (tradebyte.local)
|
v
ClusterIP Service (tradebyte-app)
|
+-----+-----+

| | |
app app app <- 3 replicas minimum (Secure, Unprivileged UID 1000)

| | |
+-----+-----+
/ \
v v
Redis Service Vault Service

| |
v v
Redis Pod + PVC Vault Pod <- Single Replicas (UID 999 & UID 100)

The Horizontal Pod Autoscaler (HPA) monitors CPU utilization and dynamically
scales the application array up to 10 instances.
```

## Security & Hardening Architecture

* **Least-Privilege & Non-Root Contexts:** Every workload in the namespace has been stripped of root execution capabilities (`runAsNonRoot = true`) to neutralize cluster-escape vulnerabilities. The application pods execute under UID `1000`, the Redis engine under UID `999`, and HashiCorp Vault under UID `100`.
* **Container Layer Defenses:** Pod definitions enforce standard `RuntimeDefault` seccomp profiles, completely block administrative privilege escalation (`allowPrivilegeEscalation = false`), and drop all Linux system kernel capabilities (`drop = ["ALL"]`).
* **Vault Zero-Privilege Patches:** To safely run HashiCorp Vault under non-root configurations without administrative kernel keys, Vault is configured to run with memory locking disabled (`disable_mlock = true`) via `VAULT_LOCAL_CONFIG` environments. Furthermore, container mount hooks skip privileged initialization checks by injecting `SKIP_CHOWN = "true"` and `SKIP_SETCAP = "true"` over localized `emptyDir` cache disks.
* **Sensitive Configuration Isolation:** Non-sensitive network properties live inside a generic `ConfigMap`, while production application secrets reside securely inside Vault's in-memory key-value data paths, shielding sensitive information from Git history leaks.

## High Availability & Autoscaling

* **Resilient Rollouts:** Application deployments employ a strict zero-downtime update pattern (`maxUnavailable = 0` and `maxSurge = 1`) to preserve 100% service availability during changes.
* **Elastic Autoscaling Arrays:** An active Horizontal Pod Autoscaler (HPA) hooks into the cluster's `metrics-server` controller, dynamically expanding the active pod footprint from **3 up to 10 replicas** if CPU demands cross a 70% utilization barrier.
* **Disruption Protections:** A Pod Disruption Budget (`tradebyte-app-pdb`) explicitly blocks cluster operations from dropping the live application footprint below a minimum threshold of **2 active running pods** simultaneously.

## Local Deployment & Task Runner Automation

The project includes a unified developer task runner menu wrapped cleanly into a **`Makefile`** to ensure a flawless Developer Experience (DX). All underlying operational automation shell scripts have been normalized to clean, lowercase, idempotent targets.

### Available Commands:

```bash
make setup # Installs prerequisites, spins up Minikube, builds the image, and deploys everything
make status # Displays the live running status of all pods, services, ingress routing, and HPA
make test # Natively runs the application code unit test suite
make smoke-test # Sends an HTTP request to verify traffic flow integrity
make fmt # Automatically cleans and formats the Terraform code formatting blocks
make validate # Validates Terragrunt and Terraform syntax configurations without deploying
make destroy # Completely dismantles local resources and purges the Minikube sandbox state
```

### Accessing the App on Windows/WSL2

Because Minikube's Docker network bridge is isolated from your Windows host operating system, you can open a secure network tunnel directly into your application tier without needing Windows administrator hosts access by running:

```bash
sudo kubectl port-forward --kubeconfig=\$HOME/.kube/config --address 0.0.0.0 service/tradebyte-app 80:80 -n tradebyte
```

Leave that terminal active, open any web browser on your Windows host machine, and navigate to **`http://localhost`** to view the live dashboard and interactive visitor counter.

## CI Pipeline (GitHub Actions)

Since remote GitHub-hosted runner VMs cannot connect to a local development machine, the automated `.github/workflows/ci.yml` pipeline spins up a specialized ephemeral **Kind (Kubernetes in Docker)** cluster to run end-to-end continuous integration testing.

The robust pipeline executes across a multi-stage validation matrix:
1. **Application Verification:** Runs native Python tests using an isolated, stable **Python 3.9** environment.
2. **Security Vulnerability Scanning:** Builds the localized image and invokes an **Aqua Security Trivy Scan** to check code layers for HIGH or CRITICAL security threats.
3. **Speculative IaC Planning (Two-Phase Deploy):** Separates the Terragrunt pipeline into distinct `plan` and `apply` steps. It generates an immutable speculative plan blueprint (`-out=tfplan`) for PR audit visibility before applying anything to the cluster.
4. **Kind Cluster Provisioning:** Deploys Kind with explicit host port mappings to expose the Nginx Ingress Controller layers natively inside the GitHub runner.
5. **Transient Secret Hydration:** Automatically provisions a `metrics-server` addon and dynamically connects a script loop to hydrate the Vault server KV store with the app parameters during integration verification.
6. **Final Invariants Assertion:** Asserts that the deployment rolls out successfully, scales up to at least 3 active pods, and that the HPA reports an operational status before completing the PR merge gate.
22 changes: 22 additions & 0 deletions infra/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading