Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5518e818be | |||
|
|
1cc53d145d | ||
| 5bc9ffceff | |||
| 007fc00710 | |||
| 9cc2501f89 | |||
|
|
5bd192ca3f | ||
| 00e333cbe7 | |||
|
|
451a01cc87 | ||
| 41a9a1e8e2 | |||
|
|
c8402fd5d8 | ||
| 110ba60513 | |||
| 22c0f9b2ab | |||
| b1d56d1cb7 | |||
| 5dab10b843 | |||
| 9435e9a915 | |||
| f367a0d032 | |||
| fd817b5334 | |||
| cbefc54227 | |||
| 35a5afc715 | |||
| 7e6521f3bf | |||
| 5a6d11c650 | |||
| 0d356fb85d | |||
| 51a6b38c6c | |||
| c155dc6b4d | |||
| f490012cec | |||
| f0d8a1d4da | |||
| 31bce27adf | |||
| 76d3359896 | |||
| 572e14d1dd | |||
| 08d0c269d0 |
112
.gitlab-ci.yml
Normal file
112
.gitlab-ci.yml
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
stages: [build, deploy]
|
||||||
|
|
||||||
|
variables:
|
||||||
|
IMAGE_REPO: "$CI_REGISTRY_IMAGE"
|
||||||
|
TAG: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
|
||||||
|
RELEASE: "open-meteo-service-gitlab"
|
||||||
|
NAMESPACE: "sandbox"
|
||||||
|
CHART_PATH: "./open-meteo-service"
|
||||||
|
VALUES_FILE: "./values.yaml"
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
tags: [homelab]
|
||||||
|
script:
|
||||||
|
- whoami
|
||||||
|
- set -euo pipefail
|
||||||
|
- unset DOCKER_HOST DOCKER_TLS_VERIFY DOCKER_CERT_PATH || true
|
||||||
|
- export DOCKER_HOST=unix:///var/run/docker.sock
|
||||||
|
- docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
|
||||||
|
- docker build -t "$IMAGE_REPO:$TAG" .
|
||||||
|
- docker push "$IMAGE_REPO:$TAG"
|
||||||
|
- docker tag "$IMAGE_REPO:$TAG" "$IMAGE_REPO:latest"
|
||||||
|
- docker push "$IMAGE_REPO:latest"
|
||||||
|
- echo "Built and pushed $IMAGE_REPO:$TAG and $IMAGE_REPO:latest"
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
stage: deploy
|
||||||
|
tags: [homelab]
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /^ci: bump image tag/'
|
||||||
|
when: never
|
||||||
|
- when: on_success
|
||||||
|
script: |-
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Configure kubectl for k3s cluster
|
||||||
|
export KUBECONFIG=/tmp/k3s.yaml
|
||||||
|
echo "Configured kubectl to use k3s cluster"
|
||||||
|
kubectl cluster-info || echo "Warning: Could not connect to cluster yet"
|
||||||
|
|
||||||
|
# Update values.yaml with the new tag using sed
|
||||||
|
sed -i "s/^ tag: .*/ tag: \"${TAG}\"/" "${VALUES_FILE}"
|
||||||
|
echo "Updated ${VALUES_FILE} with image.tag=${TAG}"
|
||||||
|
|
||||||
|
# Verify the change
|
||||||
|
grep -A 2 "^image:" "${VALUES_FILE}"
|
||||||
|
|
||||||
|
# Configure git identity
|
||||||
|
git config user.email "gitlab-ci@dvirlabs.com"
|
||||||
|
git config user.name "GitLab CI"
|
||||||
|
|
||||||
|
# Commit & push values bump (skip-ci to avoid loop)
|
||||||
|
git add "${VALUES_FILE}"
|
||||||
|
git commit -m "ci: bump image tag to ${TAG} [skip ci]" || echo "No changes to commit"
|
||||||
|
|
||||||
|
# Push using GITLAB_TOKEN (Project Access Token with api scope)
|
||||||
|
if [ -n "${GITLAB_TOKEN:-}" ]; then
|
||||||
|
git remote set-url origin "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
||||||
|
git push origin "HEAD:${CI_COMMIT_REF_NAME}" && echo "Pushed values.yaml update successfully" || echo "Failed to push, continuing deployment anyway"
|
||||||
|
else
|
||||||
|
echo "GITLAB_TOKEN not set, skipping git push (values.yaml is updated locally for this deployment)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create namespace if it doesn't exist
|
||||||
|
kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
# Create or update GitLab registry secret for pulling images
|
||||||
|
echo "Creating/updating image pull secret..."
|
||||||
|
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 with Helm..."
|
||||||
|
helm upgrade --install "${RELEASE}" "${CHART_PATH}" \
|
||||||
|
-f "${VALUES_FILE}" \
|
||||||
|
-n "${NAMESPACE}" \
|
||||||
|
--create-namespace \
|
||||||
|
--wait --timeout 10m \
|
||||||
|
--debug
|
||||||
|
|
||||||
|
echo "Waiting for deployment to be ready..."
|
||||||
|
kubectl -n "${NAMESPACE}" rollout status deploy -l app.kubernetes.io/name=open-meteo-service --timeout=180s
|
||||||
|
|
||||||
|
echo "Port-forward for tests..."
|
||||||
|
SERVICE_NAME="${RELEASE}-open-meteo-service"
|
||||||
|
kubectl -n "${NAMESPACE}" port-forward "svc/${SERVICE_NAME}" 8000:8000 >/tmp/pf.log 2>&1 &
|
||||||
|
PF_PID=$!
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
echo "Testing /healthz..."
|
||||||
|
curl -fsS http://localhost:8000/healthz >/dev/null
|
||||||
|
echo "OK /healthz"
|
||||||
|
|
||||||
|
echo "Testing /coordinates..."
|
||||||
|
COORDS_RESPONSE="$(curl -fsS http://localhost:8000/coordinates)"
|
||||||
|
echo "$COORDS_RESPONSE" | head -20
|
||||||
|
if ! echo "$COORDS_RESPONSE" | grep -q '"data"'; then
|
||||||
|
echo "ERROR: /coordinates response missing 'data' field"
|
||||||
|
kill $PF_PID || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "OK /coordinates"
|
||||||
|
|
||||||
|
echo "Testing /metrics..."
|
||||||
|
curl -fsS http://localhost:8000/metrics | head -20
|
||||||
|
echo "OK /metrics"
|
||||||
|
|
||||||
|
kill $PF_PID || true
|
||||||
|
echo "All tests passed!"
|
||||||
72
README.md
72
README.md
@ -99,6 +99,78 @@ helm install open-meteo-service ./helm \
|
|||||||
--set grafana.persistence.enabled=true
|
--set grafana.persistence.enabled=true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## GitLab CI Workflow
|
||||||
|
|
||||||
|
This project uses a GitLab CI/CD pipeline that automates building, versioning, deployment, and testing on an external k3s cluster (homelab).
|
||||||
|
|
||||||
|
### Pipeline Overview
|
||||||
|
|
||||||
|
The pipeline consists of two stages: **build** and **deploy**.
|
||||||
|
|
||||||
|
#### 1. Build Stage
|
||||||
|
- **Docker Image Build**: Builds the application Docker image using the project Dockerfile
|
||||||
|
- **Tagging Strategy**: Tags each image with a unique identifier combining branch and commit SHA:
|
||||||
|
- Format: `<branch>-<shortsha>` (e.g., `main-a1b2c3d`, `feature-api-xyz1234`)
|
||||||
|
- Uses `CI_COMMIT_REF_SLUG` (sanitized branch name) and `CI_COMMIT_SHORT_SHA`
|
||||||
|
- **Multi-Tag Push**: Pushes two tags to GitLab Container Registry:
|
||||||
|
- `$IMAGE_REPO:<branch>-<shortsha>` - Specific version tag
|
||||||
|
- `$IMAGE_REPO:latest` - Always points to the most recent build
|
||||||
|
- **Registry Authentication**: Uses GitLab's built-in CI variables (`CI_REGISTRY`, `CI_REGISTRY_USER`, `CI_REGISTRY_PASSWORD`)
|
||||||
|
|
||||||
|
#### 2. Deploy Stage
|
||||||
|
- **Values File Update**:
|
||||||
|
- Uses `yq` to update `open-meteo-service/values.yaml`
|
||||||
|
- Sets `image.tag` to the newly built tag (`<branch>-<shortsha>`)
|
||||||
|
- Keeps `image.repository` constant in the values file
|
||||||
|
- Commits the change with message: `ci: bump image tag to <tag> [skip ci]`
|
||||||
|
- Pushes back to the repository using `CI_JOB_TOKEN` for authentication
|
||||||
|
- **Helm Deployment**:
|
||||||
|
- Deploys to namespace: `sandbox`
|
||||||
|
- Release name: `open-meteo-service-gitlab`
|
||||||
|
- Uses the updated values.yaml file from the workspace
|
||||||
|
- Waits for deployment to be ready (5-minute timeout)
|
||||||
|
- **Automated Tests**:
|
||||||
|
- Port-forwards the service to localhost:8000
|
||||||
|
- `/healthz` - Checks service health
|
||||||
|
- `/coordinates` - Validates JSON response contains `"data"` field
|
||||||
|
- `/metrics` - Ensures metrics endpoint is accessible
|
||||||
|
- All tests must pass for the pipeline to succeed
|
||||||
|
|
||||||
|
### Preventing Infinite Loops
|
||||||
|
|
||||||
|
The pipeline includes safeguards to prevent infinite pipeline triggers:
|
||||||
|
- The deploy job includes a rule: `if: '$CI_COMMIT_MESSAGE =~ /^ci: bump image tag/'` with `when: never`
|
||||||
|
- The values bump commit includes `[skip ci]` to prevent triggering a new pipeline
|
||||||
|
- The deploy job uses the already-updated values.yaml in the current workspace
|
||||||
|
|
||||||
|
### Deployment Target
|
||||||
|
|
||||||
|
- **Cluster**: External k3s homelab cluster (not ephemeral)
|
||||||
|
- **Runner**: Self-hosted GitLab runner tagged with `homelab`
|
||||||
|
- **Namespace**: `sandbox` (separate from ArgoCD/Harbor setups)
|
||||||
|
- **Ingress**: Configured for `open-meteo-gitlab.dvirlabs.com` (see values.yaml)
|
||||||
|
|
||||||
|
### Viewing Pipeline Status
|
||||||
|
|
||||||
|
1. Navigate to **CI/CD > Pipelines** in your GitLab project
|
||||||
|
2. Click on any pipeline run to see job details
|
||||||
|
3. Expand jobs to view real-time logs
|
||||||
|
4. The deploy job shows Helm output, rollout status, and test results
|
||||||
|
|
||||||
|
### Manual Deployment
|
||||||
|
|
||||||
|
To deploy manually with a specific tag:
|
||||||
|
```bash
|
||||||
|
# Option 1: Deploy using updated values.yaml
|
||||||
|
helm upgrade --install open-meteo-service-gitlab ./open-meteo-service \
|
||||||
|
-n sandbox --create-namespace
|
||||||
|
|
||||||
|
# Option 2: Override tag via --set
|
||||||
|
helm upgrade --install open-meteo-service-gitlab ./open-meteo-service \
|
||||||
|
-n sandbox --create-namespace \
|
||||||
|
--set image.tag=main-a1b2c3d
|
||||||
|
```
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
### Endpoints
|
### Endpoints
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
repository: harbor.dvirlabs.com/my-apps/open-meteo-service
|
repository: registry.gitlab.com/dvirlabs/open-meteo-service
|
||||||
tag: "1.0.2"
|
tag: "latest"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: harbor-regcred
|
- name: gitlab-registry
|
||||||
|
|
||||||
service:
|
service:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
port: 8000
|
port: 8000
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
className: "traefik"
|
className: ""
|
||||||
hosts:
|
hosts:
|
||||||
- host: open-meteo.dvirlabs.com
|
- host: open-meteo-gitlab.dvirlabs.com
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
@ -53,10 +53,10 @@ prometheus:
|
|||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
port: 9090
|
port: 9090
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
className: "traefik"
|
className: ""
|
||||||
hosts:
|
hosts:
|
||||||
- host: open-meteo-prometheus.dvirlabs.com
|
- host: prometheus.local
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
@ -80,10 +80,10 @@ grafana:
|
|||||||
adminUser: admin
|
adminUser: admin
|
||||||
adminPassword: admin
|
adminPassword: admin
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
className: "traefik"
|
className: ""
|
||||||
hosts:
|
hosts:
|
||||||
- host: open-meteo-grafana.dvirlabs.com
|
- host: grafana.local
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
|
|||||||
97
values.yaml
Normal file
97
values.yaml
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: registry.gitlab.com/dvirlabs-group/open-meteo-service
|
||||||
|
tag: "main-5bc9ffce"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: gitlab-registry
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8000
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: "traefik"
|
||||||
|
hosts:
|
||||||
|
- host: open-meteo-gitlab.dvirlabs.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls: []
|
||||||
|
|
||||||
|
podAnnotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/path: /metrics
|
||||||
|
prometheus.io/port: "8000"
|
||||||
|
|
||||||
|
env:
|
||||||
|
cacheFile: /data/coordinates_cache.json
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
size: 1Gi
|
||||||
|
storageClassName: "nfs-client"
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
replicaCount: 1
|
||||||
|
image: prom/prometheus:latest
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 9090
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: "traefik"
|
||||||
|
hosts:
|
||||||
|
- host: open-meteo-prometheus-gitlab.dvirlabs.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls: []
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
size: 5Gi
|
||||||
|
storageClassName: "nfs-client"
|
||||||
|
resources: {}
|
||||||
|
extraArgs: []
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
enabled: true
|
||||||
|
replicaCount: 1
|
||||||
|
image: grafana/grafana:latest
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 3000
|
||||||
|
adminUser: admin
|
||||||
|
adminPassword: admin
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: "traefik"
|
||||||
|
hosts:
|
||||||
|
- host: open-meteo-grafana-gitlab.dvirlabs.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls: []
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
size: 5Gi
|
||||||
|
storageClassName: "nfs-client"
|
||||||
|
resources: {}
|
||||||
Loading…
x
Reference in New Issue
Block a user