Skip to content

Commit

Permalink
feat(kubernetes): add health endpoints (#1602)
Browse files Browse the repository at this point in the history
* feat(backend): add health endpoint

* feat(helm): health probe path

* feat(frontend): add health endpoints

* feat(backend): remove unused app state fields

* fix(active): rewrote active boards logic

* feat(backend): add endpoint for resetting active boards
  • Loading branch information
lindtvedtsebastian authored Aug 1, 2024
1 parent 8f97d7f commit e188bc0
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 65 deletions.
89 changes: 45 additions & 44 deletions backend/helm/backend/values.yaml
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
common:
app: backend
shortname: backend
team: selvbetjent
ingress:
enabled: true
trafficType: public
service:
internalPort: 3000
container:
image: <+artifacts.primary.image>
cpu: 0.3
memory: 512
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis
key: redis-password
- name: BACKEND_API_KEY
valueFrom:
secretKeyRef:
name: backend
key: api-key
probes:
enabled: true
spec:
startupProbe:
tcpSocket:
port: 3000
periodSeconds: 1
timeoutSeconds: 1
failureThreshold: 300
livenessProbe:
tcpSocket:
port: 3000
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
tcpSocket:
port: 3000
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
app: backend
shortname: backend
team: selvbetjent
ingress:
enabled: true
trafficType: public
service:
internalPort: 3000
container:
image: <+artifacts.primary.image>
cpu: 0.3
memory: 512
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis
key: redis-password
- name: BACKEND_API_KEY
valueFrom:
secretKeyRef:
name: backend
key: api-key
probes:
enabled: true
spec:
startupProbe:
tcpSocket:
port: 3000
periodSeconds: 1
timeoutSeconds: 1
failureThreshold: 300
livenessProbe:
tcpSocket:
port: 3000
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
path: "/health"
readinessProbe:
tcpSocket:
port: 3000
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
46 changes: 29 additions & 17 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::time::Duration;
use axum::{
body::Body,
extract::{Path, State},
http::{HeaderValue, Method, StatusCode},
response::Response,
http::{HeaderValue, Method, Response, StatusCode},
routing::{get, post},
Json, Router,
};
Expand All @@ -13,7 +12,7 @@ use axum_auth::AuthBearer;
use serde_json::{from_str, Value};
use tokio::{net::TcpListener, time};

use redis::AsyncCommands;
use redis::{AsyncCommands, ConnectionLike};

use tokio_stream::StreamExt;
use tokio_util::{sync::CancellationToken, task::TaskTracker};
Expand All @@ -25,10 +24,12 @@ use tower_http::cors::CorsLayer;
use types::{AppError, AppState, Message};
use utils::{graceful_shutdown, setup_redis};

use crate::types::Guard;

#[tokio::main]
async fn main() {
let host = std::env::var("HOST").unwrap_or("0.0.0.0".to_string());
let port = std::env::var("PORT").unwrap_or("3000".to_string());
let port = std::env::var("PORT").unwrap_or("3001".to_string());
let key = std::env::var("BACKEND_API_KEY").expect("Expected to find api key");

let listener = TcpListener::bind(format!("{}:{}", host, port))
Expand All @@ -43,8 +44,6 @@ async fn main() {
let redis_clients = AppState {
master,
replicas,
runtime_status: runtime_status.clone(),
task_tracker: task_tracker.clone(),
key,
};

Expand All @@ -62,6 +61,8 @@ async fn main() {
.route("/subscribe/:bid", get(subscribe))
.route("/refresh/:bid", post(trigger))
.route("/update", post(update))
.route("/health", get(check_health))
.route("/reset", post(reset_active))
.with_state(redis_clients)
.layer(cors);

Expand All @@ -71,6 +72,25 @@ async fn main() {
.unwrap()
}

async fn reset_active(
AuthBearer(token): AuthBearer,
State(mut state): State<AppState>,
) -> Result<StatusCode, AppError> {
if token != state.key {
return Ok(StatusCode::UNAUTHORIZED);
}

state.master.set("active_boards", 0).await?;
Ok(StatusCode::OK)
}

async fn check_health(State(mut state): State<AppState>) -> StatusCode {
if state.replicas.check_connection() {
return StatusCode::OK;
}
StatusCode::INTERNAL_SERVER_ERROR
}

async fn active_boards(
AuthBearer(token): AuthBearer,
State(mut state): State<AppState>,
Expand Down Expand Up @@ -111,21 +131,18 @@ async fn update(

async fn subscribe(
Path(bid): Path<String>,
State(mut state): State<AppState>,
State(state): State<AppState>,
) -> Result<Message, AppError> {
let mut pubsub = state.replicas.get_async_pubsub().await?;
pubsub.subscribe(bid).await?;
pubsub.subscribe("update").await?;

state
.master
.incr::<&str, i32, i32>("active_boards", 1)
.await?;
let _guard = Guard::new(state.master.clone());

let mut msg_stream = pubsub.on_message();
let res = tokio::select! {
Some(msg) = msg_stream.next() => {
let channel = msg.get_channel_name();
let channel = msg.get_channel_name();
if channel == "update" {
Message::Update
} else {
Expand All @@ -139,10 +156,5 @@ async fn subscribe(
}
};

state
.master
.decr::<&str, i32, i32>("active_boards", 1)
.await?;

Ok(res)
}
28 changes: 24 additions & 4 deletions backend/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ use axum::{
http::{Response, StatusCode},
response::IntoResponse,
};
use redis::{aio::MultiplexedConnection, Client};
use redis::{aio::MultiplexedConnection, AsyncCommands, Client};
use serde::Serialize;
use serde_json::{to_string, Value};
use tokio_util::{sync::CancellationToken, task::TaskTracker};

#[derive(Clone)]
pub struct AppState {
pub master: MultiplexedConnection,
pub replicas: Client,
pub runtime_status: CancellationToken,
pub task_tracker: TaskTracker,
pub key: String,
}

Expand All @@ -25,6 +22,29 @@ pub enum Message {
Timeout,
}

pub struct Guard {
pub master: MultiplexedConnection,
}

impl Guard {
pub fn new(master: MultiplexedConnection) -> Self {
let mut m = master.clone();
tokio::spawn(async move {
let _ = m.incr::<&str, i32, i32>("active_boards", 1).await;
});
Guard { master }
}
}

impl Drop for Guard {
fn drop(&mut self) {
let mut m = self.master.clone();
tokio::spawn(async move {
let _ = m.decr::<&str, i32, i32>("active_boards", 1).await;
});
}
}

impl IntoResponse for Message {
fn into_response(self) -> axum::response::Response {
if let Ok(json_string) = to_string(&self) {
Expand Down
5 changes: 5 additions & 0 deletions next-tavla/app/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NextResponse } from 'next/server'

export function GET() {
return NextResponse.json({ status: 'OK' })
}
1 change: 1 addition & 0 deletions next-tavla/helm/tavla/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ common:
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
path: '/health'
readinessProbe:
tcpSocket:
port: 3000
Expand Down

0 comments on commit e188bc0

Please sign in to comment.