feat(k8s): add Kubernetes Helm chart for emailks service

- Create Helm chart structure with Chart.yaml and values.yaml
- Add deployment template with container configuration and environment variables
- Implement service template for gRPC port exposure
- Add service account template with security configuration
- Include horizontal pod autoscaler template for scaling capabilities
- Add helper templates for naming and label management
- Configure SMTP settings as configurable parameters in values.yaml
- Set up resource limits and requests for container performance
- Implement liveness and readiness probes for health checks
- Add support for existing secrets and custom configurations
This commit is contained in:
zhenyi
2026-06-07 22:59:06 +08:00
parent d0852ad131
commit c4824ef261
17 changed files with 353 additions and 33 deletions
+23 -3
View File
@@ -8,6 +8,8 @@ use tonic::{Request, Response, Status};
use tracing::warn;
const STREAM_STATUS_POLL_INTERVAL: Duration = Duration::from_millis(300);
/// Maximum lifetime of a single streaming batch-status RPC.
/// Protects against leaked streams when jobs never reach a terminal state.
const STREAM_STATUS_TIMEOUT: Duration = Duration::from_secs(10 * 60);
use crate::{
@@ -75,12 +77,12 @@ impl EmailService for EmailServiceImpl {
request: Request<BatchSendEmailRequest>,
) -> Result<Response<BatchSendEmailResponse>, Status> {
let req = request.into_inner();
let total = req.emails.len();
let total = req.emails.len() as i32;
let mut success = 0i32;
let mut failures = 0i32;
let mut results = Vec::with_capacity(total);
let mut results = Vec::with_capacity(total as usize);
for email in req.emails {
for (i, email) in req.emails.into_iter().enumerate() {
match self.queue.enqueue(email) {
Ok(id) => {
success += 1;
@@ -90,6 +92,8 @@ impl EmailService for EmailServiceImpl {
failures += 1;
warn!(%e, "batch enqueue failed for one email");
if req.fail_fast {
// Count remaining unprocessed emails as failures too.
failures += total - (i as i32) - 1;
warn!(
successful = success,
failed = failures,
@@ -152,6 +156,7 @@ impl EmailService for EmailServiceImpl {
let id_set: std::collections::HashSet<u64> = ids.iter().copied().collect();
let store = self.store.clone();
let mut missing_streak: std::collections::HashMap<u64, u32> = std::collections::HashMap::new();
let (tx, rx) = mpsc::channel(ids.len().saturating_add(immediate_results.len()).max(1));
tokio::spawn(async move {
@@ -186,6 +191,7 @@ impl EmailService for EmailServiceImpl {
continue;
}
if let Some(entry) = store.get(*id) {
missing_streak.remove(id);
match entry.status {
SendStatus::Sent => {
if tx
@@ -209,6 +215,20 @@ impl EmailService for EmailServiceImpl {
}
_ => {}
}
} else {
// Status entry may have been evicted under memory pressure.
// Report as failed after a few consecutive misses.
let streak = missing_streak.entry(*id).and_modify(|c| *c += 1).or_insert(1);
if *streak >= 5 {
if tx.send(Ok(build_failed_response(
Some(*id),
"status entry evicted before terminal state".into(),
))).await.is_err() {
return;
}
reported.insert(*id);
missing_streak.remove(id);
}
}
}