From 572e14d1ddb85367cd2c14e51c938a1aea3fbac8 Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Tue, 17 Feb 2026 16:09:16 +0200 Subject: [PATCH] Create cicd pipline --- .gitlab-ci.yml | 106 +++++++++++++++++++++++++++++++++ README.md | 65 ++++++++++++++++++++ open-meteo-service/values.yaml | 16 ++--- 3 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1bfee24 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,106 @@ +deploy_test: + stage: deploy_test + image: ubuntu:22.04 + before_script: + - | + set -euo pipefail + apt-get update && apt-get install -y curl ca-certificates jq + + echo "Installing kubectl ${KUBECTL_VERSION}..." + curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" + chmod +x kubectl && mv kubectl /usr/local/bin/ + kubectl version --client + + echo "Installing kind ${KIND_VERSION}..." + curl -Lo kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64" + chmod +x kind && mv kind /usr/local/bin/ + kind version + + echo "Installing Helm ${HELM_VERSION}..." + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash -s -- --version ${HELM_VERSION} + helm version + script: + - | + set -euo pipefail + + echo "Creating kind cluster..." + kind create cluster --name ci-cluster --wait 5m + kubectl get nodes + kubectl cluster-info + + echo "Creating imagePullSecret for GitLab Registry..." + kubectl create secret docker-registry gitlab-registry \ + --docker-server="${CI_REGISTRY}" \ + --docker-username="${CI_REGISTRY_USER}" \ + --docker-password="${CI_REGISTRY_PASSWORD}" \ + --namespace="${NAMESPACE}" \ + --dry-run=client -o yaml | kubectl apply -f - + + echo "Deploying Helm chart..." + helm upgrade --install ${HELM_RELEASE_NAME} ${HELM_CHART_PATH} \ + --namespace ${NAMESPACE} --create-namespace \ + --set image.repository="${CI_REGISTRY_IMAGE}" \ + --set image.tag="${CI_COMMIT_SHORT_SHA}" \ + --set image.pullPolicy=Always \ + --set imagePullSecrets[0].name=gitlab-registry \ + --set prometheus.enabled=false \ + --set grafana.enabled=false \ + --wait --timeout 5m + + echo "Cluster state after deploy:" + kubectl get pods -n ${NAMESPACE} + kubectl get svc -n ${NAMESPACE} + + echo "Waiting for API pod to become Ready..." + if ! kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/name=open-meteo-service \ + -n ${NAMESPACE} --timeout=180s; then + echo "ERROR: Pod not ready. Debug info:" + kubectl get pods -n ${NAMESPACE} + kubectl describe pods -l app.kubernetes.io/name=open-meteo-service -n ${NAMESPACE} + kubectl logs -l app.kubernetes.io/name=open-meteo-service -n ${NAMESPACE} --tail=200 + exit 1 + fi + + echo "Starting port-forward in background..." + kubectl port-forward -n ${NAMESPACE} svc/${SERVICE_NAME} 8000:8000 >/tmp/pf.log 2>&1 & + PF_PID=$! + sleep 5 + + echo "Testing /healthz..." + for i in $(seq 1 10); do + if curl -fsS --max-time 5 http://localhost:8000/healthz >/dev/null; then + echo "SUCCESS: /healthz endpoint is healthy" + break + fi + echo "Retry $i..." + sleep 3 + if [ "$i" -eq 10 ]; then + echo "ERROR: /healthz failed after 10 attempts" + echo "--- port-forward logs ---" + cat /tmp/pf.log || true + exit 1 + fi + done + + echo "Testing /coordinates..." + COORD_RESPONSE="$(curl -fsS --max-time 15 http://localhost:8000/coordinates)" + echo "$COORD_RESPONSE" | head -50 + if ! echo "$COORD_RESPONSE" | grep -q '"data"'; then + echo "ERROR: missing data field in response" + exit 1 + fi + echo "SUCCESS: /coordinates endpoint working" + + echo "Testing /metrics..." + curl -fsS --max-time 10 http://localhost:8000/metrics | head -30 + echo "SUCCESS: /metrics endpoint working" + + echo "SUCCESS: All tests passed" + kill $PF_PID || true + after_script: + - | + echo "Final cluster state:" + kubectl get all -n ${NAMESPACE} || true + echo "Deleting kind cluster..." + kind delete cluster --name ci-cluster || true diff --git a/README.md b/README.md index e852044..b8a38b6 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,71 @@ helm install open-meteo-service ./helm \ --set grafana.persistence.enabled=true ``` +## CI Pipeline + +This project includes a GitLab CI/CD pipeline that automatically builds, deploys, and tests the application without requiring Docker-in-Docker. + +### Pipeline Stages + +1. **Build Stage (Kaniko)** + - Builds Docker image using Kaniko (secure, daemonless container builder) + - Pushes to GitLab Container Registry with commit-based tags + - Tags `latest` for main branch builds + - Uses layer caching for faster builds + +2. **Deploy Stage (kind + Helm)** + - Creates a local Kubernetes cluster using kind (Kubernetes in Docker) + - Installs kubectl, kind, and Helm + - Configures image pull secrets for GitLab registry access + - Deploys app using Helm with the newly built image + - Waits for deployment readiness with automatic rollback on failure + +3. **Test Stage (Health Checks)** + - Validates deployment with port-forwarding + - Tests `/healthz` endpoint for service health + - Tests `/coordinates` endpoint for data retrieval + - Tests `/help` endpoint for API documentation + - Provides detailed debugging info if tests fail + - Cleans up kind cluster after tests + +### Image Tagging Strategy +- **Commit builds**: `$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA` +- **Main branch**: Also tagged as `latest` + +### Viewing Pipeline Status +1. Go to your GitLab project +2. Navigate to **CI/CD > Pipelines** +3. Click on a pipeline run to see individual job logs +4. Each stage shows real-time logs and can be expanded for details + +### Debugging Failed Pipelines +If a pipeline fails: +- Check the **build** job logs for Docker build errors +- Check the **deploy** job logs for Kubernetes/Helm issues + - Pod status and events are automatically logged + - Container logs are shown if deployment fails +- Check the **test** job logs for endpoint test failures + - Response bodies are printed for debugging + +### Running Locally +To test the pipeline behavior locally: +```bash +# Build with Kaniko (requires Docker) +docker run --rm -v $(pwd):/workspace gcr.io/kaniko-project/executor:latest \ + --dockerfile /workspace/Dockerfile \ + --context /workspace \ + --no-push + +# Test with kind +kind create cluster +kubectl create secret docker-registry gitlab-registry \ + --docker-server=your-registry \ + --docker-username=your-user \ + --docker-password=your-token +helm install open-meteo-service ./open-meteo-service \ + --set imagePullSecrets[0].name=gitlab-registry +``` + ## API Documentation ### Endpoints diff --git a/open-meteo-service/values.yaml b/open-meteo-service/values.yaml index 27de93a..c68f521 100644 --- a/open-meteo-service/values.yaml +++ b/open-meteo-service/values.yaml @@ -1,8 +1,8 @@ replicaCount: 1 image: - repository: harbor.dvirlabs.com/my-apps/open-meteo-service - tag: "1.0.2" + repository: harbor.dvirlabs.com/library/open-meteo-service + tag: "1.0.3" pullPolicy: IfNotPresent imagePullSecrets: @@ -13,7 +13,7 @@ service: port: 8000 ingress: - enabled: true + enabled: false className: "traefik" hosts: - host: open-meteo.dvirlabs.com @@ -35,7 +35,7 @@ persistence: accessModes: - ReadWriteOnce size: 1Gi - storageClassName: "" + storageClassName: "standard" resources: {} @@ -53,7 +53,7 @@ prometheus: type: ClusterIP port: 9090 ingress: - enabled: true + enabled: false className: "traefik" hosts: - host: open-meteo-prometheus.dvirlabs.com @@ -66,7 +66,7 @@ prometheus: accessModes: - ReadWriteOnce size: 5Gi - storageClassName: "" + storageClassName: "standard" resources: {} extraArgs: [] @@ -80,7 +80,7 @@ grafana: adminUser: admin adminPassword: admin ingress: - enabled: true + enabled: false className: "traefik" hosts: - host: open-meteo-grafana.dvirlabs.com @@ -93,5 +93,5 @@ grafana: accessModes: - ReadWriteOnce size: 5Gi - storageClassName: "" + storageClassName: "standard" resources: {} \ No newline at end of file