chore: add /status /ready best practices for Docker & K8S
This commit is contained in:
+2
-2
@@ -67,10 +67,10 @@ WORKDIR /app
|
|||||||
# Expose application port
|
# Expose application port
|
||||||
EXPOSE 8086
|
EXPOSE 8086
|
||||||
|
|
||||||
# Basic health check — verifies the HTTP server responds on the main port.
|
# Liveness probe — verifies the HTTP server is up (no DB check, fast).
|
||||||
# Docker / Compose / Swarm will mark the container unhealthy after 3 failures.
|
# Docker / Compose / Swarm will mark the container unhealthy after 3 failures.
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
CMD wget -qO- http://localhost:8086/api/version || exit 1
|
CMD wget -qO- http://localhost:8086/health || exit 1
|
||||||
|
|
||||||
# Entrypoint fixes volume permissions then drops to oxicloud user.
|
# Entrypoint fixes volume permissions then drops to oxicloud user.
|
||||||
# The container starts as root so it can chown mounted volumes,
|
# The container starts as root so it can chown mounted volumes,
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
- storage_data:/app/storage
|
- storage_data:/app/storage
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:8086/ready || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
start_period: 30s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
oxicloud:
|
oxicloud:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pub mod handlers;
|
|||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
pub use routes::create_api_routes;
|
pub use routes::create_api_routes;
|
||||||
|
pub use routes::create_health_routes;
|
||||||
pub use routes::create_public_api_routes;
|
pub use routes::create_public_api_routes;
|
||||||
|
|
||||||
use utoipa::OpenApi;
|
use utoipa::OpenApi;
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use crate::application::services::batch_operations::BatchOperationService;
|
|||||||
use crate::common::di::AppState;
|
use crate::common::di::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::DefaultBodyLimit,
|
extract::{DefaultBodyLimit, State},
|
||||||
response::Json as AxumJson,
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Json as AxumJson},
|
||||||
routing::{delete, get, post, put},
|
routing::{delete, get, post, put},
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -11,6 +12,31 @@ use std::sync::Arc;
|
|||||||
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
|
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
|
||||||
use utoipa::OpenApi;
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
/// Liveness probe — returns 200 if the process is running, no DB check.
|
||||||
|
async fn health() -> impl IntoResponse {
|
||||||
|
(StatusCode::OK, AxumJson(json!({"status": "ok"})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Readiness probe — returns 200 if the DB pool can serve queries, 503 otherwise.
|
||||||
|
async fn ready(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||||
|
match &state.db_pool {
|
||||||
|
Some(pool) => match sqlx::query("SELECT 1").execute(pool.as_ref()).await {
|
||||||
|
Ok(_) => (
|
||||||
|
StatusCode::OK,
|
||||||
|
AxumJson(json!({"status": "ok", "db": "ok"})),
|
||||||
|
),
|
||||||
|
Err(_) => (
|
||||||
|
StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
AxumJson(json!({"status": "error", "db": "error"})),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
None => (
|
||||||
|
StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
AxumJson(json!({"status": "error", "db": "not configured"})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the application version from Cargo.toml (compile-time constant)
|
/// Returns the application version from Cargo.toml (compile-time constant)
|
||||||
async fn get_version() -> AxumJson<serde_json::Value> {
|
async fn get_version() -> AxumJson<serde_json::Value> {
|
||||||
AxumJson(json!({
|
AxumJson(json!({
|
||||||
@@ -45,6 +71,18 @@ use crate::interfaces::api::handlers::search_handler::{
|
|||||||
};
|
};
|
||||||
use crate::interfaces::api::handlers::trash_handler;
|
use crate::interfaces::api::handlers::trash_handler;
|
||||||
|
|
||||||
|
/// Creates root-level health check routes — mounted directly at `/`, not under `/api/`.
|
||||||
|
/// (follow docker/kubernetes best practices)
|
||||||
|
///
|
||||||
|
/// - `GET /health` — liveness probe, no DB check, always 200 if process is up.
|
||||||
|
/// - `GET /ready` — readiness probe, pings DB pool, returns 503 if unreachable.
|
||||||
|
pub fn create_health_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||||
|
Router::new()
|
||||||
|
.route("/health", get(health))
|
||||||
|
.route("/ready", get(ready))
|
||||||
|
.with_state(app_state.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates public API routes that should NOT require authentication.
|
/// Creates public API routes that should NOT require authentication.
|
||||||
pub fn create_public_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
pub fn create_public_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||||
let share_service = app_state.share_service.clone();
|
let share_service = app_state.share_service.clone();
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ pub mod nextcloud;
|
|||||||
pub mod web;
|
pub mod web;
|
||||||
|
|
||||||
pub use api::create_api_routes;
|
pub use api::create_api_routes;
|
||||||
|
pub use api::create_health_routes;
|
||||||
pub use api::create_public_api_routes;
|
pub use api::create_public_api_routes;
|
||||||
|
|||||||
+8
-1
@@ -50,7 +50,9 @@ use oxicloud::interfaces;
|
|||||||
|
|
||||||
use common::di::AppServiceFactory;
|
use common::di::AppServiceFactory;
|
||||||
use infrastructure::db::create_database_pools;
|
use infrastructure::db::create_database_pools;
|
||||||
use interfaces::{create_api_routes, create_public_api_routes, web::create_web_routes};
|
use interfaces::{
|
||||||
|
create_api_routes, create_health_routes, create_public_api_routes, web::create_web_routes,
|
||||||
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@@ -115,6 +117,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// Build application router
|
// Build application router
|
||||||
let api_routes = create_api_routes(&app_state);
|
let api_routes = create_api_routes(&app_state);
|
||||||
let public_api_routes = create_public_api_routes(&app_state);
|
let public_api_routes = create_public_api_routes(&app_state);
|
||||||
|
let health_routes = create_health_routes(&app_state);
|
||||||
let web_routes = create_web_routes();
|
let web_routes = create_web_routes();
|
||||||
|
|
||||||
let mut app;
|
let mut app;
|
||||||
@@ -319,6 +322,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
));
|
));
|
||||||
|
|
||||||
app = Router::new()
|
app = Router::new()
|
||||||
|
// Health / readiness probes — no auth, mounted at root
|
||||||
|
.merge(health_routes)
|
||||||
// Rate-limited auth endpoints (login, register, refresh)
|
// Rate-limited auth endpoints (login, register, refresh)
|
||||||
.nest("/api/auth", auth_login)
|
.nest("/api/auth", auth_login)
|
||||||
.nest("/api/auth", auth_register)
|
.nest("/api/auth", auth_register)
|
||||||
@@ -375,6 +380,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// Auth disabled — no middleware applied
|
// Auth disabled — no middleware applied
|
||||||
tracing::warn!("Authentication is DISABLED — all API routes are publicly accessible");
|
tracing::warn!("Authentication is DISABLED — all API routes are publicly accessible");
|
||||||
app = Router::new()
|
app = Router::new()
|
||||||
|
// Health / readiness probes — no auth, mounted at root
|
||||||
|
.merge(health_routes)
|
||||||
.nest("/api", public_api_routes)
|
.nest("/api", public_api_routes)
|
||||||
.nest("/api", api_routes)
|
.nest("/api", api_routes)
|
||||||
// RFC 6764 well-known discovery (just redirects)
|
// RFC 6764 well-known discovery (just redirects)
|
||||||
|
|||||||
Reference in New Issue
Block a user