Create cicd pipline
This commit is contained in:
parent
08d0c269d0
commit
572e14d1dd
106
.gitlab-ci.yml
Normal file
106
.gitlab-ci.yml
Normal file
@ -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
|
||||||
65
README.md
65
README.md
@ -99,6 +99,71 @@ helm install open-meteo-service ./helm \
|
|||||||
--set grafana.persistence.enabled=true
|
--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
|
## API Documentation
|
||||||
|
|
||||||
### Endpoints
|
### Endpoints
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
repository: harbor.dvirlabs.com/my-apps/open-meteo-service
|
repository: harbor.dvirlabs.com/library/open-meteo-service
|
||||||
tag: "1.0.2"
|
tag: "1.0.3"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
@ -13,7 +13,7 @@ service:
|
|||||||
port: 8000
|
port: 8000
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
className: "traefik"
|
className: "traefik"
|
||||||
hosts:
|
hosts:
|
||||||
- host: open-meteo.dvirlabs.com
|
- host: open-meteo.dvirlabs.com
|
||||||
@ -35,7 +35,7 @@ persistence:
|
|||||||
accessModes:
|
accessModes:
|
||||||
- ReadWriteOnce
|
- ReadWriteOnce
|
||||||
size: 1Gi
|
size: 1Gi
|
||||||
storageClassName: ""
|
storageClassName: "standard"
|
||||||
|
|
||||||
resources: {}
|
resources: {}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ prometheus:
|
|||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
port: 9090
|
port: 9090
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
className: "traefik"
|
className: "traefik"
|
||||||
hosts:
|
hosts:
|
||||||
- host: open-meteo-prometheus.dvirlabs.com
|
- host: open-meteo-prometheus.dvirlabs.com
|
||||||
@ -66,7 +66,7 @@ prometheus:
|
|||||||
accessModes:
|
accessModes:
|
||||||
- ReadWriteOnce
|
- ReadWriteOnce
|
||||||
size: 5Gi
|
size: 5Gi
|
||||||
storageClassName: ""
|
storageClassName: "standard"
|
||||||
resources: {}
|
resources: {}
|
||||||
extraArgs: []
|
extraArgs: []
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ grafana:
|
|||||||
adminUser: admin
|
adminUser: admin
|
||||||
adminPassword: admin
|
adminPassword: admin
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
className: "traefik"
|
className: "traefik"
|
||||||
hosts:
|
hosts:
|
||||||
- host: open-meteo-grafana.dvirlabs.com
|
- host: open-meteo-grafana.dvirlabs.com
|
||||||
@ -93,5 +93,5 @@ grafana:
|
|||||||
accessModes:
|
accessModes:
|
||||||
- ReadWriteOnce
|
- ReadWriteOnce
|
||||||
size: 5Gi
|
size: 5Gi
|
||||||
storageClassName: ""
|
storageClassName: "standard"
|
||||||
resources: {}
|
resources: {}
|
||||||
Loading…
x
Reference in New Issue
Block a user