generated from hjess/PythonTemplateProject
This commit is contained in:
@@ -1,63 +1,150 @@
|
|||||||
name: Build, Push, and Deploy to Nomad
|
name: Build and Deploy LifeFAQ
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker-nomad:
|
build-image:
|
||||||
runs-on: self-hosted
|
runs-on: debian-host
|
||||||
|
|
||||||
|
env:
|
||||||
|
PATH: /usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/sbin:/bin:/snap/bin
|
||||||
|
DOCKER_HOST: unix:///var/run/docker.sock
|
||||||
|
BUILDX_CONFIG: /tmp/buildx
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Log in to Container Registry
|
- name: System info
|
||||||
run: echo ${{ secrets.password }} | docker login registry.i80.dk -u ${{ secrets.username }} --password-stdin
|
|
||||||
|
|
||||||
- name: Build Docker Image
|
|
||||||
run: |
|
run: |
|
||||||
COMMIT_HASH=$(git rev-parse --short HEAD)
|
uname -a
|
||||||
docker build -t registry.i80.dk/gitea/lifefaq:latest -t registry.i80.dk/gitea/lifefaq:${COMMIT_HASH} .
|
whoami
|
||||||
|
|
||||||
|
- name: Set up Docker Context for Buildx
|
||||||
- name: Push Docker Image
|
id: buildx-context
|
||||||
run: |
|
run: |
|
||||||
COMMIT_HASH=$(git rev-parse --short HEAD)
|
export DOCKER_HOST=tcp://docker:2376/
|
||||||
echo "registry.i80.dk/gitea/lifefaq:latest"
|
export DOCKER_TLS_VERIFY=0
|
||||||
echo "registry.i80.dk/gitea/lifefaq:${COMMIT_HASH}"
|
docker context rm builders || true
|
||||||
docker push registry.i80.dk/gitea/lifefaq:${COMMIT_HASH}
|
docker context create builders
|
||||||
docker push registry.i80.dk/gitea/lifefaq:latest
|
|
||||||
|
|
||||||
|
- name: Verify Docker
|
||||||
|
run: docker --version
|
||||||
|
|
||||||
- name: Validate Nomad Job
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
env:
|
env:
|
||||||
NOMAD_ADDR: https://nomad.i80.dk
|
PATH: /usr/bin:/usr/local/bin:/bin:/sbin:/usr/sbin
|
||||||
run: nomad job validate .gitea/workflows/nomad-job.hcl
|
|
||||||
|
|
||||||
- name: Stop old deployment
|
- name: Log in to Docker Registry
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.HARBOR_ROBOT_TOKEN }}" | docker login registry.i80.dk -u "robot\$gitserver" --password-stdin
|
||||||
env:
|
env:
|
||||||
NOMAD_ADDR: https://nomad.i80.dk
|
PATH: /usr/bin:/usr/local/bin:/bin:/sbin:/usr/sbin
|
||||||
run: nomad job stop -purge -no-shutdown-delay lifefaq
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: changes
|
||||||
|
uses: dorny/paths-filter@v2
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
docker:
|
||||||
|
- 'Dockerfile'
|
||||||
|
- 'app/**'
|
||||||
|
- 'requirements.txt'
|
||||||
|
|
||||||
- name: Apply Nomad Job
|
- name: Build and push Docker image
|
||||||
|
if: steps.changes.outputs.docker == 'true'
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
env:
|
env:
|
||||||
NOMAD_ADDR: https://nomad.i80.dk
|
PATH: /usr/bin:/usr/local/bin:/bin:/sbin:/usr/sbin
|
||||||
run: nomad job run .gitea/workflows/nomad-job.hcl
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
registry.i80.dk/gitea/lifefaq:latest
|
||||||
|
|
||||||
- name: Update Nginx Configuration
|
- name: Test container health
|
||||||
run: ssh runner@nomad sudo /opt/nginx_updater/venv/bin/python3 /opt/nginx_updater/nginx_updater.py lifefaq
|
run: |
|
||||||
|
echo "=== Starting container for health check ==="
|
||||||
|
|
||||||
- name: Update Forwarder Configuration
|
docker pull registry.i80.dk/gitea/lifefaq:latest
|
||||||
run: ssh runner@nomad sudo /opt/nginx_updater/venv/bin/python3 /opt/nginx_updater/update_forwarder.py --subdomain lifefaq
|
|
||||||
|
|
||||||
|
CONTAINER_ID=$(docker run -d \
|
||||||
|
-p 8000:8000 \
|
||||||
|
-e PORT=8000 \
|
||||||
|
-e APP_ENV=production \
|
||||||
|
--name lifefaq-test \
|
||||||
|
registry.i80.dk/gitea/lifefaq:latest)
|
||||||
|
|
||||||
# - name: Restart Nomad Job
|
echo "Container started: ${CONTAINER_ID}"
|
||||||
# env:
|
|
||||||
# NOMAD_ADDR: https://nomad.i80.dk
|
echo "Waiting for /health endpoint..."
|
||||||
# run: |
|
SUCCESS=false
|
||||||
# nomad job stop lifefaq
|
for i in {1..90}; do
|
||||||
# sleep 5 # Optional: Wait to ensure the old allocation is stopped
|
if curl -f -s http://localhost:8000/health > /dev/null 2>&1; then
|
||||||
# nomad job run .gitea/workflows/nomad-job.hcl
|
echo "✓ Health check passed after ${i} seconds"
|
||||||
|
curl -s http://localhost:8000/health | jq '.' || echo "Health endpoint returned OK"
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Attempt ${i}/90 - waiting..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== Container Logs ==="
|
||||||
|
docker logs lifefaq-test
|
||||||
|
|
||||||
|
docker stop lifefaq-test
|
||||||
|
docker rm lifefaq-test
|
||||||
|
|
||||||
|
if [ "$SUCCESS" = false ]; then
|
||||||
|
echo "✗ Health check failed after 90 seconds"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Container health check passed - safe to deploy"
|
||||||
|
env:
|
||||||
|
PATH: /usr/bin:/usr/local/bin:/bin:/sbin:/usr/sbin
|
||||||
|
|
||||||
|
- name: Deploy to Nomad
|
||||||
|
run: |
|
||||||
|
nomad job validate lifefaq.nomad
|
||||||
|
nomad job run lifefaq.nomad
|
||||||
|
env:
|
||||||
|
NOMAD_ADDR: "https://nomad.i80.dk:4646"
|
||||||
|
|
||||||
|
- name: Wait for deployment
|
||||||
|
run: |
|
||||||
|
echo "Checking deployment status..."
|
||||||
|
nomad job status lifefaq
|
||||||
|
|
||||||
|
echo "=== Allocation Details ==="
|
||||||
|
nomad job allocs lifefaq
|
||||||
|
|
||||||
|
echo "=== Getting logs from allocations ==="
|
||||||
|
for alloc in $(nomad job allocs -all lifefaq | tail -n +2 | awk '{print $1}'); do
|
||||||
|
echo "Logs for allocation $alloc:"
|
||||||
|
|
||||||
|
timeout=250
|
||||||
|
SECONDS=0
|
||||||
|
until nomad alloc logs "$alloc" 2>/dev/null || [ $SECONDS -gt $timeout ]; do
|
||||||
|
echo "Waiting for allocation to start... ($SECONDS/$timeout seconds)"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
[ $SECONDS -gt $timeout ] && echo "Timeout for $alloc"
|
||||||
|
echo "---"
|
||||||
|
done
|
||||||
|
env:
|
||||||
|
NOMAD_ADDR: "https://nomad.i80.dk:4646"
|
||||||
|
|
||||||
|
- name: Notify deployment status
|
||||||
|
run: |
|
||||||
|
echo "✅ Deployment completed!"
|
||||||
|
echo "LifeFAQ should be available at: https://lifefaq.i80.dk"
|
||||||
|
echo "Health check endpoint: https://lifefaq.i80.dk/health"
|
||||||
13
Dockerfile
13
Dockerfile
@@ -1,20 +1,13 @@
|
|||||||
# Base image with Python 3.11
|
|
||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
# Set the working directory in the container
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy the requirements file to the working directory
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
|
|
||||||
# Install Python dependencies
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Copy the rest of the application code
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Expose the port the FastAPI app runs on (default Uvicorn port)
|
# Port will be set via environment variable
|
||||||
EXPOSE 9210
|
EXPOSE 8000
|
||||||
|
|
||||||
# Command to run the FastAPI application
|
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "${PORT:-8000}", "--workers", "1"]
|
||||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "9210", "--workers", "1"]
|
|
||||||
9
app.py
9
app.py
@@ -1,5 +1,12 @@
|
|||||||
|
import os
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from app.main import app
|
from app.main import app
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run("app.main:app", host="0.0.0.0", port=9210, reload=False)
|
port = int(os.getenv("PORT", 8000))
|
||||||
|
uvicorn.run(
|
||||||
|
"app.main:app",
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=port,
|
||||||
|
reload=False
|
||||||
|
)
|
||||||
@@ -2,6 +2,7 @@ from fastapi import FastAPI
|
|||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
|
import app
|
||||||
from app.controllers.route_to_web import RouteToWeb
|
from app.controllers.route_to_web import RouteToWeb
|
||||||
from app.services.markdown_processor import MarkdownProcessor
|
from app.services.markdown_processor import MarkdownProcessor
|
||||||
from app.services.metadata_processor import MetadataProcessor
|
from app.services.metadata_processor import MetadataProcessor
|
||||||
@@ -68,6 +69,10 @@ class Application:
|
|||||||
"""Return the FastAPI app instance."""
|
"""Return the FastAPI app instance."""
|
||||||
return self.app
|
return self.app
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health(self):
|
||||||
|
return {"status": "healthy"}
|
||||||
|
|
||||||
|
|
||||||
application = Application()
|
application = Application()
|
||||||
app = application.get_app()
|
app = application.get_app()
|
||||||
|
|||||||
Reference in New Issue
Block a user