diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1ec9e50..9e15b28 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,6 +1,5 @@ name: CI/CD Pipeline - -on: +'on': push: branches: - main @@ -9,51 +8,55 @@ on: branches: - main - master - jobs: test: name: Run Django Tests runs-on: ubuntu-latest - + services: + postgres: + image: 'postgres:14' + env: + POSTGRES_DB: '${{ secrets.DB_NAME }}' + POSTGRES_USER: '${{ secrets.DB_USER }}' + POSTGRES_PASSWORD: '${{ secrets.DB_PASSWORD }}' + ports: + - '5432:5432' + options: >- + --health-cmd pg_isready --health-interval 10s --health-timeout 5s + --health-retries 5 steps: - name: Checkout Repository uses: actions/checkout@v3 - - name: Set up Python 3.8 uses: actions/setup-python@v4 with: - python-version: "3.8" - + python-version: '3.8' - name: Install Dependencies run: | python -m venv venv source venv/bin/activate pip install -r requirements.txt - - name: Decode and set environment variables - run: | - echo "AT_USERNAME=$(echo '${{ secrets.AT_USERNAME }}' | base64 -d)" >> $GITHUB_ENV - echo "AT_SHORT_CODE=$(echo '${{ secrets.AT_SHORT_CODE }}' | base64 -d)" >> $GITHUB_ENV - echo "AT_API_KEY=$(echo '${{ secrets.AT_API_KEY }}' | base64 -d)" >> $GITHUB_ENV + run: > + echo "AT_USERNAME=$(echo '${{ secrets.AT_USERNAME }}' | base64 -d)" >> + $GITHUB_ENV + + echo "AT_SHORT_CODE=$(echo '${{ secrets.AT_SHORT_CODE }}' | base64 + -d)" >> $GITHUB_ENV + echo "AT_API_KEY=$(echo '${{ secrets.AT_API_KEY }}' | base64 -d)" >> + $GITHUB_ENV - name: Run Tests env: - DB_NAME: ${{ secrets.DB_NAME }} - DB_USER: ${{ secrets.DB_USER }} - DB_PASSWORD: ${{ secrets.DB_PASSWORD }} - DB_HOST: ${{ secrets.DB_HOST }} - DB_PORT: ${{ secrets.DB_PORT }} - SECRET_KEY: ${{ secrets.SECRET_KEY }} + DB_NAME: '${{ secrets.DB_NAME }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASSWORD: '${{ secrets.DB_PASSWORD }}' + DB_HOST: localhost + DB_PORT: '5432' + SECRET_KEY: '${{ secrets.SECRET_KEY }}' run: | source venv/bin/activate - # Debug: Check if variables are set (without exposing values) - echo "Checking database configuration..." - [ -z "$DB_NAME" ] && echo "DB_NAME is not set" || echo "DB_NAME is set" - [ -z "$DB_USER" ] && echo "DB_USER is not set" || echo "DB_USER is set" - [ -z "$DB_HOST" ] && echo "DB_HOST is not set" || echo "DB_HOST is set" - [ -z "$DB_PORT" ] && echo "DB_PORT is not set" || echo "DB_PORT is set" - pytest -v - + pytest -v build-and-push: name: Build and Push Docker Image runs-on: ubuntu-latest @@ -61,52 +64,183 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v3 - - name: Log in to Docker Hub uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - + username: '${{ secrets.DOCKER_USERNAME }}' + password: '${{ secrets.DOCKER_PASSWORD }}' - name: Build and Push Docker Image - run: | - docker build -t ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest . - docker push ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest + run: > + docker build -t ${{ secrets.DOCKER_USERNAME + }}/e-commerce-service:latest . + docker push ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest deploy: - name: Deploy to Kubernetes + name: Deploy to Minikube runs-on: ubuntu-latest - needs: build-and-push - + needs: test steps: - name: Checkout Repository uses: actions/checkout@v3 + - name: Check Minikube and Kubectl Installation + id: check-tools + run: | + MINIKUBE_INSTALLED=$(which minikube || echo "false") + KUBECTL_INSTALLED=$(which kubectl || echo "false") + + echo "Minikube installed: $MINIKUBE_INSTALLED" + echo "Kubectl installed: $KUBECTL_INSTALLED" + + echo "minikube_exists=$MINIKUBE_INSTALLED" >> $GITHUB_OUTPUT + echo "kubectl_exists=$KUBECTL_INSTALLED" >> $GITHUB_OUTPUT + - name: Install Minikube (if needed) + if: steps.check-tools.outputs.minikube_exists == 'false' + run: > + curl -LO + https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 + + sudo install minikube-linux-amd64 /usr/local/bin/minikube + - name: Install Kubectl (if needed) + if: steps.check-tools.outputs.kubectl_exists == 'false' + run: > + curl -LO "https://dl.k8s.io/release/$(curl -L -s + https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" + + sudo install -o root -g root -m 0755 kubectl + /usr/local/bin/kubectl + + - name: Reset Kubernetes Cluster + run: | + # Stop any running Minikube instance + minikube stop || true + + # Delete existing Minikube cluster + minikube delete || true + + # Restart Minikube with optimized resources + minikube start \ + --driver=docker \ + --kubernetes-version=v1.27.1 \ + --cpus=2 \ + --memory=4g \ + --wait=all \ + --force + + # Wait for cluster to be fully ready + kubectl wait --for=condition=Ready nodes --all --timeout=300s - - name: Set Up Kubectl - uses: azure/setup-kubectl@v3 - with: - version: 'latest' + - name: Clean Up Existing Resources + run: | + # Remove all existing resources + kubectl delete all --all || true + kubectl delete pvc --all || true + kubectl delete secrets --all || true + + # Wait for resources to be terminated + sleep 15 - - name: Validate KUBECONFIG Secret + - name: Configure Kubectl Context run: | - if [ -z "${{ secrets.KUBECONFIG }}" ]; then - echo "Error: KUBECONFIG secret is missing!" - exit 1 - fi + kubectl config use-context minikube + kubectl config set-cluster minikube --insecure-skip-tls-verify=true + + - name: Deploy Configuration + run: | + # Apply ConfigMaps + kubectl apply -f configmap.yaml + + # Apply Secrets + kubectl apply -f secrets.yaml + + # Verify resources + kubectl get configmaps + kubectl get secrets + + + - name: Verify Minikube Setup + run: > + minikube status + + minikube ip + + kubectl cluster-info - - name: Configure Kubeconfig + kubectl get nodes + + kubectl version || true # Use || true to prevent failure if version + command is incompatible + + - name: Create Kubernetes Secrets + env: + DB_NAME: '${{ secrets.DB_NAME }}' + DB_USER: '${{ secrets.DB_USER }}' + DB_PASSWORD: '${{ secrets.DB_PASSWORD }}' + DB_HOST: postgres-service + DB_PORT: '5432' + SECRET_KEY: '${{ secrets.SECRET_KEY }}' run: | - echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig.yaml - export KUBECONFIG=$(pwd)/kubeconfig.yaml - echo "KUBECONFIG=$(pwd)/kubeconfig.yaml" >> $GITHUB_ENV + # Create secret only if it doesn't exist + kubectl get secret django-secrets || \ + kubectl create secret generic django-secrets \ + --from-literal=DB_NAME="$DB_NAME" \ + --from-literal=DB_USER="$DB_USER" \ + --from-literal=DB_PASSWORD="$DB_PASSWORD" \ + --from-literal=DB_HOST="$DB_HOST" \ + --from-literal=DB_PORT="$DB_PORT" \ + --from-literal=SECRET_KEY="$SECRET_KEY" + - name: Log in to Docker Hub + run: > + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ + secrets.DOCKER_PASSWORD }} - - name: Deploy Postgres to Kubernetes + - name: Deploy Postgres to Minikube + run: | + kubectl apply -f k8s/postgres.yaml --validate=false + echo "Checking Postgres Deployment Details:" + kubectl get deployments + kubectl get pods + kubectl describe deployment postgres + kubectl get events --sort-by='.metadata.creationTimestamp' + kubectl rollout status deployment/postgres --timeout=120s + + - name: Check Postgres Deployment Logs run: | - kubectl apply -f k8s/postgres.yaml - kubectl rollout status statefulset/postgres --timeout=120s + sleep 10 + POSTGRES_PODS=$(kubectl get pods -l app=postgres -o jsonpath='{.items[*].metadata.name}') + for pod in $POSTGRES_PODS; do + echo "Logs for pod: $pod" + kubectl logs $pod || true + echo "---" + done + + - name: Verify Persistent Volume and Claims + run: | + kubectl get pvc + kubectl describe pvc postgres-pvc + kubectl get storageclass + + - name: Wait for Postgres to be Ready + run: > + kubectl wait --for=condition=Ready pod -l app=postgres --timeout=180s || { + echo "Postgres pod did not become ready in time" + kubectl get pods + kubectl describe pods + exit 1 + } - - name: Deploy Application to Kubernetes + - name: Pull and Deploy Application to Minikube run: | + eval $(minikube docker-env) + docker pull ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest + minikube image load ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest + minikube image ls | grep e-commerce-service + sed -i 's|image: .*|image: ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest|' k8s/deployment.yaml kubectl apply -f k8s/deployment.yaml - kubectl set image deployment/ecommerce-app ecommerce-app=${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest - kubectl rollout status deployment/ecommerce-app --timeout=120s + + - name: Check Deployment Status + run: | + kubectl get pods + kubectl get services + kubectl describe deployment/ecommerce-app + kubectl describe pods + diff --git a/entrypoint.sh b/entrypoint.sh index 4448ab9..203baca 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/sh echo "Waiting for database to be ready..." -while ! nc -z "$DATABASE_HOST" "$DATABASE_PORT"; do +while ! nc -z "$DB_HOST" "$DB_PORT"; do sleep 2 done diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 65868a2..e32d0c8 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -5,7 +5,10 @@ metadata: spec: replicas: 1 strategy: - type: Recreate + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 selector: matchLabels: app: ecommerce-app @@ -17,11 +20,20 @@ spec: initContainers: - name: wait-for-db image: busybox - command: ['sh', '-c', 'until nc -z postgres-service 5432; do echo waiting for db; sleep 2; done;'] + command: + - 'sh' + - '-c' + - | + echo "Checking PostgreSQL connection..." + until nc -z -v -w5 postgres-service 5432; do + echo "Waiting for PostgreSQL to be ready..."; + sleep 5; + done + echo "PostgreSQL is ready!" containers: - name: ecommerce-app image: ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest - imagePullPolicy: Always + imagePullPolicy: Always ports: - containerPort: 8000 envFrom: @@ -31,16 +43,16 @@ spec: name: django-secrets resources: requests: - memory: "128Mi" - cpu: "100m" - limits: memory: "256Mi" - cpu: "500m" + cpu: "200m" + limits: + memory: "512Mi" + cpu: "1000m" readinessProbe: httpGet: path: /api/v1/health/ port: 8000 - initialDelaySeconds: 30 + initialDelaySeconds: 60 periodSeconds: 10 failureThreshold: 3 timeoutSeconds: 5 diff --git a/k8s/postgres.yaml b/k8s/postgres.yaml index 9860daf..09d607e 100644 --- a/k8s/postgres.yaml +++ b/k8s/postgres.yaml @@ -8,6 +8,8 @@ spec: resources: requests: storage: 1Gi + + storageClassName: standard --- apiVersion: apps/v1 @@ -29,9 +31,23 @@ spec: image: postgres:14 ports: - containerPort: 5432 - envFrom: - - secretRef: - name: django-secrets + env: + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: django-secrets + key: DATABASE_NAME + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: django-secrets + key: DATABASE_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: django-secrets + key: DATABASE_PASSWORD + volumeMounts: - mountPath: /var/lib/postgresql/data name: postgres-storage @@ -58,4 +74,4 @@ spec: ports: - protocol: TCP port: 5432 - targetPort: 5432 + targetPort: 5432 \ No newline at end of file