diff --git a/script/cd.sh b/script/cd.sh new file mode 100644 index 0000000..2eeddb0 --- /dev/null +++ b/script/cd.sh @@ -0,0 +1,222 @@ +#!/bin/bash + +# Continuous Deployment Script for Kubernetes +# This script deploys your application to a Kubernetes cluster + +# ============================================================================= +# Environment Variables (with default values) +# ============================================================================= + +# Kubernetes Configuration +KUBECONFIG_DATA="${KUBECONFIG_DATA:-}" +KUBERNETES_URL="${KUBERNETES_URL:-https://kubernetes.default.svc}" +KUBERNETES_NAMESPACE="${KUBERNETES_NAMESPACE:-default}" +KUBERNETES_INGRESS_HOST="${KUBERNETES_INGRESS_HOST:-example.com}" + +# Container Registry (inherited from ci.sh) +CONTAINER_REGISTRY_URL="${CONTAINER_REGISTRY_URL:-127.0.0.1}" +CONTAINER_REGISTRY_USERNAME="${CONTAINER_REGISTRY_USERNAME:-username}" +CONTAINER_REGISTRY_NAMESPACE="${CONTAINER_REGISTRY_NAMESPACE:-username}" +CONTAINER_REGISTRY_PASSWORD="${CONTAINER_REGISTRY_PASSWORD:-password}" +CONTAINER_IMAGE_NAME="${CONTAINER_IMAGE_NAME:-image-name}" +CONTAINER_IMAGE_TAG="${CONTAINER_IMAGE_TAG:-latest}" + +# ============================================================================= +# Functions +# ============================================================================= + +# Print help message +print_help() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " deploy Deploy application to Kubernetes" + echo " help Show this help message (default)" + echo "" + echo "Environment Variables:" + echo " KUBECONFIG_DATA Base64 encoded kubeconfig file" + echo " KUBERNETES_URL Kubernetes API server URL" + echo " KUBERNETES_NAMESPACE Deploy namespace (default: default)" + echo " KUBERNETES_INGRESS_HOST Ingress host (default: example.com)" + echo "" + echo " Container Registry Variables (from ci.sh):" + echo " CONTAINER_REGISTRY_URL" + echo " CONTAINER_REGISTRY_USERNAME" + echo " CONTAINER_REGISTRY_NAMESPACE" + echo " CONTAINER_REGISTRY_PASSWORD" + echo " CONTAINER_IMAGE_NAME" + echo " CONTAINER_IMAGE_TAG" + echo "" + echo "Examples:" + echo " $0 deploy" + echo " KUBERNETES_NAMESPACE=production $0 deploy" +} + +# Setup Kubernetes configuration +setup_kubeconfig() { + echo "==========================================" + echo "Setting up Kubernetes Configuration" + echo "==========================================" + + # Check if KUBECONFIG_DATA is provided + if [ -z "$KUBECONFIG_DATA" ]; then + echo "Warning: KUBECONFIG_DATA not provided, using existing kubectl config" + return 0 + fi + + # Create temporary kubeconfig file from base64 encoded data + echo "Decoding kubeconfig..." + echo "$KUBECONFIG_DATA" | base64 -d > /tmp/kubeconfig + + # Set KUBECONFIG environment variable to use the temporary file + export KUBECONFIG=/tmp/kubeconfig + + echo "✓ Kubeconfig configured" + echo "" +} + +# Create Docker registry secret for pulling private images +create_registry_secret() { + echo "==========================================" + echo "Creating Docker Registry Secret" + echo "==========================================" + + # Create or update the docker registry secret in the namespace + kubectl create secret docker-registry regcred \ + --docker-server="${CONTAINER_REGISTRY_URL}" \ + --docker-username="${CONTAINER_REGISTRY_USERNAME}" \ + --docker-password="${CONTAINER_REGISTRY_PASSWORD}" \ + --namespace="${KUBERNETES_NAMESPACE}" \ + --dry-run=client -o yaml | kubectl apply -f - + + if [ $? -eq 0 ]; then + echo "✓ Registry secret created/updated" + echo "" + else + echo "✗ Failed to create registry secret" + exit 1 + fi +} + +# Deploy application to Kubernetes +deploy_application() { + # Construct the full image name + FULL_IMAGE_NAME="${CONTAINER_REGISTRY_URL}/${CONTAINER_REGISTRY_NAMESPACE}/${CONTAINER_IMAGE_NAME}:${CONTAINER_IMAGE_TAG}" + + echo "==========================================" + echo "Deploying Application to Kubernetes" + echo "==========================================" + echo "Namespace: ${KUBERNETES_NAMESPACE}" + echo "Image: ${FULL_IMAGE_NAME}" + echo "Ingress Host: ${KUBERNETES_INGRESS_HOST}" + echo "" + + # Setup kubeconfig + setup_kubeconfig + + # Test kubectl connection + echo "Testing Kubernetes connection..." + kubectl cluster-info > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "✗ Failed to connect to Kubernetes cluster" + exit 1 + fi + echo "✓ Connected to Kubernetes cluster" + echo "" + + # Create namespace if it doesn't exist + echo "Creating namespace..." + kubectl apply -f script/k8s/namespace.yaml + echo "" + + # Create registry secret for pulling images + create_registry_secret + + # Export environment variables for envsubst (template substitution) + export KUBERNETES_NAMESPACE + export KUBERNETES_INGRESS_HOST + export FULL_IMAGE_NAME + + # Apply Kubernetes manifests with environment variable substitution + echo "Applying Kubernetes manifests..." + + # Apply deployment + echo "- Applying deployment..." + envsubst < script/k8s/deployment.yaml | kubectl apply -f - + + # Apply service + echo "- Applying service..." + envsubst < script/k8s/service.yaml | kubectl apply -f - + + # Apply ingress + echo "- Applying ingress..." + envsubst < script/k8s/ingress.yaml | kubectl apply -f - + + echo "" + + # Wait for deployment to be ready + echo "Waiting for deployment to be ready..." + kubectl rollout status deployment/${CONTAINER_IMAGE_NAME} \ + -n ${KUBERNETES_NAMESPACE} \ + --timeout=300s + + if [ $? -eq 0 ]; then + echo "" + echo "==========================================" + echo "✓ Deployment Successful!" + echo "==========================================" + echo "Application: ${CONTAINER_IMAGE_NAME}" + echo "Namespace: ${KUBERNETES_NAMESPACE}" + echo "URL: http://${KUBERNETES_INGRESS_HOST}" + echo "" + + # Show pod status + echo "Pod Status:" + kubectl get pods -n ${KUBERNETES_NAMESPACE} -l app=${CONTAINER_IMAGE_NAME} + echo "" + + # Show service status + echo "Service Status:" + kubectl get svc -n ${KUBERNETES_NAMESPACE} -l app=${CONTAINER_IMAGE_NAME} + echo "" + + # Show ingress status + echo "Ingress Status:" + kubectl get ingress -n ${KUBERNETES_NAMESPACE} + else + echo "" + echo "✗ Deployment failed or timed out" + exit 1 + fi + + # Cleanup temporary kubeconfig if it was created + if [ -f /tmp/kubeconfig ]; then + rm -f /tmp/kubeconfig + fi +} + +# ============================================================================= +# Main Script Logic +# ============================================================================= + +# If no arguments provided, show help +if [ $# -eq 0 ]; then + print_help + exit 0 +fi + +# Process command line arguments +case "$1" in + deploy) + deploy_application + ;; + help|--help|-h) + print_help + ;; + *) + echo "Error: Unknown option '$1'" + echo "" + print_help + exit 1 + ;; +esac diff --git a/script/k8s/deployment.yaml b/script/k8s/deployment.yaml new file mode 100644 index 0000000..23b0868 --- /dev/null +++ b/script/k8s/deployment.yaml @@ -0,0 +1,75 @@ +# Kubernetes Deployment Configuration +# Deployment manages a set of identical pods and ensures desired state + +apiVersion: apps/v1 +kind: Deployment +metadata: + # Name of the deployment + name: ${CONTAINER_IMAGE_NAME} + # Namespace where deployment will be created + namespace: ${KUBERNETES_NAMESPACE} + labels: + app: ${CONTAINER_IMAGE_NAME} +spec: + # Number of pod replicas to run + replicas: 2 + + # Label selector to identify pods managed by this deployment + selector: + matchLabels: + app: ${CONTAINER_IMAGE_NAME} + + # Pod template definition + template: + metadata: + labels: + app: ${CONTAINER_IMAGE_NAME} + spec: + # Secret to pull images from private registry + imagePullSecrets: + - name: regcred + + # Container specification + containers: + - name: ${CONTAINER_IMAGE_NAME} + # Docker image to use + image: ${FULL_IMAGE_NAME} + # Always pull the latest version of the image + imagePullPolicy: Always + + # Container port that the app listens on + ports: + - name: http + containerPort: 80 + protocol: TCP + + # Health check to know when container is ready to serve traffic + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + # Health check to know when to restart container + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + + # Resource limits and requests + resources: + # Minimum resources guaranteed to container + requests: + memory: "64Mi" + cpu: "100m" + # Maximum resources container can use + limits: + memory: "128Mi" + cpu: "200m" diff --git a/script/k8s/ingress.yaml b/script/k8s/ingress.yaml new file mode 100644 index 0000000..d8f90be --- /dev/null +++ b/script/k8s/ingress.yaml @@ -0,0 +1,45 @@ +# Kubernetes Ingress Configuration for Traefik +# Ingress exposes HTTP routes from outside the cluster to services within + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + # Name of the ingress + name: ${CONTAINER_IMAGE_NAME} + # Namespace where ingress will be created + namespace: ${KUBERNETES_NAMESPACE} + + # Annotations specific to Traefik ingress controller + annotations: + # Specify Traefik as the ingress controller + kubernetes.io/ingress.class: "traefik" + + # Traefik router configuration + # Entry point for HTTP traffic (not using HTTPS as per requirements) + traefik.ingress.kubernetes.io/router.entrypoints: web + + # Optional: Add middleware for common headers + # traefik.ingress.kubernetes.io/router.middlewares: default-headers@kubernetescrd + + labels: + app: ${CONTAINER_IMAGE_NAME} + +spec: + # Define routing rules + rules: + # Host-based routing + - host: ${KUBERNETES_INGRESS_HOST} + http: + paths: + # Route all paths to the service + - path: / + # PathType defines how path matching is done + # Prefix matches any path starting with / + pathType: Prefix + backend: + service: + # Name of the service to route traffic to + name: ${CONTAINER_IMAGE_NAME} + port: + # Service port to route to + number: 80 diff --git a/script/k8s/namespace.yaml b/script/k8s/namespace.yaml new file mode 100644 index 0000000..134aeee --- /dev/null +++ b/script/k8s/namespace.yaml @@ -0,0 +1,10 @@ +# Kubernetes Namespace Configuration +# A namespace provides a scope for names and is used to divide cluster resources + +apiVersion: v1 +kind: Namespace +metadata: + # Name of the namespace where your application will be deployed + name: ${KUBERNETES_NAMESPACE} + labels: + name: ${KUBERNETES_NAMESPACE} diff --git a/script/k8s/service.yaml b/script/k8s/service.yaml new file mode 100644 index 0000000..1cd4c8c --- /dev/null +++ b/script/k8s/service.yaml @@ -0,0 +1,29 @@ +# Kubernetes Service Configuration +# Service exposes pods to network traffic within the cluster + +apiVersion: v1 +kind: Service +metadata: + # Name of the service + name: ${CONTAINER_IMAGE_NAME} + # Namespace where service will be created + namespace: ${KUBERNETES_NAMESPACE} + labels: + app: ${CONTAINER_IMAGE_NAME} +spec: + # Type of service - ClusterIP is internal only + type: ClusterIP + + # Service will route traffic to pods with these labels + selector: + app: ${CONTAINER_IMAGE_NAME} + + # Port mapping + ports: + - name: http + # Port that service listens on + port: 80 + # Port on the pod that receives traffic + targetPort: http + # Protocol to use + protocol: TCP