diff --git a/kubernetes/k3d-config.yaml b/kubernetes/k3d-config.yaml new file mode 100644 index 0000000..fba1536 --- /dev/null +++ b/kubernetes/k3d-config.yaml @@ -0,0 +1,12 @@ +apiVersion: k3d.io/v1alpha5 +kind: Simple +metadata: + name: study-app-cluster +servers: 1 +agents: 1 +options: + k3s: + extraArgs: + - arg: --disable=traefik + nodeFilters: + - server:* diff --git a/kubernetes/manifests/base/backend/deployment.yaml b/kubernetes/manifests/base/backend/deployment.yaml new file mode 100644 index 0000000..6317a16 --- /dev/null +++ b/kubernetes/manifests/base/backend/deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: study-app +spec: + replicas: 1 + selector: + matchLabels: + component: backend + template: + metadata: + labels: + component: backend + spec: + containers: + - name: backend + image: backend:latest + ports: + - containerPort: 22112 + resources: + limits: + memory: "512Mi" + requests: + cpu: "100m" + memory: "128Mi" diff --git a/kubernetes/manifests/base/backend/kustomization.yaml b/kubernetes/manifests/base/backend/kustomization.yaml new file mode 100644 index 0000000..5b98e94 --- /dev/null +++ b/kubernetes/manifests/base/backend/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml diff --git a/kubernetes/manifests/base/backend/service.yaml b/kubernetes/manifests/base/backend/service.yaml new file mode 100644 index 0000000..94674cc --- /dev/null +++ b/kubernetes/manifests/base/backend/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: backend + namespace: study-app +spec: + selector: + component: backend + ports: + - port: 22112 + type: LoadBalancer + diff --git a/kubernetes/manifests/base/frontend/deployment.yaml b/kubernetes/manifests/base/frontend/deployment.yaml new file mode 100644 index 0000000..a4d7cab --- /dev/null +++ b/kubernetes/manifests/base/frontend/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + namespace: study-app +spec: + replicas: 1 + selector: + matchLabels: + component: frontend + template: + metadata: + labels: + component: frontend + spec: + containers: + - name: frontend + image: frontend:latest + ports: + - containerPort: 22111 + env: + - name: API_URL + value: "http://backend:22112" + resources: + limits: + memory: "512Mi" + requests: + cpu: "100m" + memory: "128Mi" + diff --git a/kubernetes/manifests/base/frontend/kustomization.yaml b/kubernetes/manifests/base/frontend/kustomization.yaml new file mode 100644 index 0000000..5b98e94 --- /dev/null +++ b/kubernetes/manifests/base/frontend/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - deployment.yaml + - service.yaml diff --git a/kubernetes/manifests/base/frontend/service.yaml b/kubernetes/manifests/base/frontend/service.yaml new file mode 100644 index 0000000..eabab1d --- /dev/null +++ b/kubernetes/manifests/base/frontend/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: frontend + namespace: study-app +spec: + selector: + component: frontend + ports: + - port: 22111 + type: LoadBalancer + diff --git a/kubernetes/manifests/base/kustomization.yaml b/kubernetes/manifests/base/kustomization.yaml new file mode 100644 index 0000000..cf40911 --- /dev/null +++ b/kubernetes/manifests/base/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - backend + - frontend diff --git a/kubernetes/manifests/dev/backend/kustomization.yaml b/kubernetes/manifests/dev/backend/kustomization.yaml new file mode 100644 index 0000000..e1ea66e --- /dev/null +++ b/kubernetes/manifests/dev/backend/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base/backend diff --git a/kubernetes/manifests/dev/frontend/kustomization.yaml b/kubernetes/manifests/dev/frontend/kustomization.yaml new file mode 100644 index 0000000..61ca572 --- /dev/null +++ b/kubernetes/manifests/dev/frontend/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base/frontend + +# Patch for the frontend deployment +patches: + - target: + kind: Deployment + name: frontend + namespace: study-app + patch: |- + - op: replace + path: /spec/template/spec/containers/0/env + value: + - name: DEBUG + value: "true" + - name: API_URL + value: "http://dev-backend:22112" diff --git a/kubernetes/manifests/dev/kustomization.yaml b/kubernetes/manifests/dev/kustomization.yaml new file mode 100644 index 0000000..875ec64 --- /dev/null +++ b/kubernetes/manifests/dev/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml + - backend + - frontend + +namespace: study-app +namePrefix: dev- + +images: + - name: backend + newTag: dev + - name: frontend + newTag: dev diff --git a/kubernetes/manifests/dev/namespace.yaml b/kubernetes/manifests/dev/namespace.yaml new file mode 100644 index 0000000..e6a1ebf --- /dev/null +++ b/kubernetes/manifests/dev/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: study-app \ No newline at end of file diff --git a/kubernetes/setup_cluster_local b/kubernetes/setup_cluster_local new file mode 100755 index 0000000..24e0367 --- /dev/null +++ b/kubernetes/setup_cluster_local @@ -0,0 +1,142 @@ +#!/bin/bash +set -e + +# Colors for better output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_DIR="$(dirname "$SCRIPT_DIR")" +CLUSTER_NAME="study-app-cluster" + +echo -e "${GREEN}DevOps Study App - Kubernetes Deployment Helper${NC}" +echo -e "${YELLOW}This script will set up a k3d cluster and deploy the study app.${NC}" +echo "" + +# Check for required tools +check_dependency() { + if ! command -v "$1" &>/dev/null; then + echo -e "${RED}Error: $1 is not installed. Please install it before proceeding.${NC}" + exit 1 + fi +} + +echo "Checking dependencies..." +check_dependency k3d +check_dependency kubectl +check_dependency docker +echo -e "${GREEN}All dependencies are installed.${NC}" +echo "" + +# Check if cluster exists +if k3d cluster list | grep -q "$CLUSTER_NAME"; then + echo -e "${YELLOW}Cluster $CLUSTER_NAME already exists.${NC}" + read -p "Do you want to delete and recreate it? (y/n): " -r + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Deleting existing cluster..." + k3d cluster delete "$CLUSTER_NAME" + else + echo "Using existing cluster." + fi +fi + +# Create cluster if it doesn't exist +if ! k3d cluster list | grep -q "$CLUSTER_NAME"; then + echo "Creating k3d cluster using config file..." + k3d cluster create --config "$SCRIPT_DIR/k3d-config.yaml" + echo -e "${GREEN}Cluster created successfully!${NC}" +fi + +# Configure kubectl to use the cluster +echo "Configuring kubectl to use the cluster..." +kubectl config use-context k3d-"$CLUSTER_NAME" + +# Build Docker images +echo "Building Docker images..." +echo "Building backend image..." +docker build -t backend:dev -f "$BASE_DIR/src/backend/Dockerfile" "$BASE_DIR/src/backend" + +echo "Building frontend image..." +docker build -t frontend:dev -f "$BASE_DIR/src/frontend/Dockerfile" "$BASE_DIR/src/frontend" + +# Import images into k3d +echo "Importing images into k3d..." +k3d image import backend:dev -c "$CLUSTER_NAME" +k3d image import frontend:dev -c "$CLUSTER_NAME" + +# Deploy the application using kustomize +echo "Deploying application using kustomize..." +kubectl apply -k "$SCRIPT_DIR/manifests/dev" + +# Wait for pods to be ready +echo "Waiting for pods to be ready..." +kubectl wait --for=condition=Ready pods --all -n study-app --timeout=120s + +# Get service info +echo -e "${GREEN}Application deployed successfully!${NC}" +echo "Getting service information..." +kubectl get services -n study-app + +# Function to wait for LoadBalancer IP assignment and get URL +get_service_url() { + local service_name=$1 + local max_attempts=30 + local attempt=1 + + echo -e "Waiting for $service_name external IP..." + + while [ $attempt -le $max_attempts ]; do + local ip + local port + + ip=$(kubectl get svc "$service_name" -n study-app -o=jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null) + port=$(kubectl get svc "$service_name" -n study-app -o=jsonpath='{.spec.ports[0].port}' 2>/dev/null) + + if [ -n "$ip" ] && [ -n "$port" ]; then + echo "http://$ip:$port" + return 0 + fi + + echo -n "." + sleep 2 + ((attempt++)) + done + + echo "" + echo -e "${RED}Could not get external IP for $service_name after $max_attempts attempts${NC}" + return 1 +} + +# Get frontend and backend service URLs +FRONTEND_URL=$(get_service_url "dev-frontend") +BACKEND_URL=$(get_service_url "dev-backend") + +echo -e "\n${GREEN}Access your application via LoadBalancer external IPs:${NC}" +if [ -n "$FRONTEND_URL" ] && [ -n "$BACKEND_URL" ]; then + echo -e "Frontend: ${YELLOW}$FRONTEND_URL${NC}" + echo -e "Backend API: ${YELLOW}$BACKEND_URL${NC}" + echo -e "Backend health check: ${YELLOW}$BACKEND_URL/health${NC}" +else + echo -e "\n${RED}Could not determine service URLs. Please check the service status:${NC}" + echo -e "${YELLOW}kubectl get svc -n study-app${NC}" +fi + +# Extract service ports for port-forwarding instructions +FRONTEND_PORT=$(kubectl get svc dev-frontend -n study-app -o=jsonpath='{.spec.ports[0].port}' 2>/dev/null) +BACKEND_PORT=$(kubectl get svc dev-backend -n study-app -o=jsonpath='{.spec.ports[0].port}' 2>/dev/null) + +echo -e "\n${GREEN}Alternative access via port-forwarding (for local development):${NC}" +echo -e "To access via port-forwarding, run these commands in separate terminals:" +echo -e "${YELLOW}kubectl port-forward svc/dev-frontend -n study-app $FRONTEND_PORT:$FRONTEND_PORT${NC}" +echo -e "${YELLOW}kubectl port-forward svc/dev-backend -n study-app $BACKEND_PORT:$BACKEND_PORT${NC}" +echo -e "Then access the application at:" +echo -e "Frontend: ${YELLOW}http://localhost:$FRONTEND_PORT${NC}" +echo -e "Backend API: ${YELLOW}http://localhost:$BACKEND_PORT${NC}" +echo -e "Backend health check: ${YELLOW}http://localhost:$BACKEND_PORT/health${NC}" + +echo -e "\nTo delete the cluster when finished:" +echo -e "${YELLOW}k3d cluster delete $CLUSTER_NAME${NC}" + diff --git a/mise.toml b/mise.toml index a92fdf7..ca5ebfc 100644 --- a/mise.toml +++ b/mise.toml @@ -1,4 +1,6 @@ [tools] +k3d = "latest" +kubectl = "latest" pre-commit = "latest" python = "3.13" ruff = "latest" @@ -13,5 +15,9 @@ experimental = true APP_REPO = 'devops' GITOPS_REPO = 'devops-gitops' +[tasks.k8s-setup-local] +description = "Set up a basic Kubernetes cluster for development that builds the local images and applies them" +run = "bash ./kubernetes/setup_cluster_local" + [hooks] enter = "bash ./scripts/setup_project"