feat: billing — usage tracking per SQS operation in PostgreSQL

- New package app/billing: Init/RecordUsage/Close with auto-migrate
- Integration in actionHandler: record tenant_id, operation, msg_count, msg_bytes
- Helm chart: billing section in values.yaml, secret-billing.yaml, env vars in deployment
- Optional: billing disabled by default (BILLING_PG_HOST not set = no-op)
- Table: sqs_usage_records with index on (tenant_id, recorded_at)
This commit is contained in:
Naeel
2026-04-12 11:51:27 +03:00
parent d1af612b48
commit 1272388673
13 changed files with 448 additions and 4 deletions
@@ -0,0 +1,97 @@
# deployment.yaml — Deployment shared-sqs с RollingUpdate
# Created: 2026-04-11
apiVersion: apps/v1
kind: Deployment
metadata:
name: shared-sqs
namespace: {{ .Values.namespace }}
labels:
{{- include "shared-sqs.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
strategy:
type: {{ .Values.strategy.type }}
{{- if eq .Values.strategy.type "RollingUpdate" }}
rollingUpdate:
maxSurge: {{ .Values.strategy.rollingUpdate.maxSurge }}
maxUnavailable: {{ .Values.strategy.rollingUpdate.maxUnavailable }}
{{- end }}
selector:
matchLabels:
{{- include "shared-sqs.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "shared-sqs.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: shared-sqs
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
name: http
env:
- name: SHARED_SQS_ADMIN_TOKEN
valueFrom:
secretKeyRef:
name: shared-sqs-admin
key: token
- name: SHARED_SQS_SEED_DEMO
value: {{ .Values.seedDemo | quote }}
- name: REDIS_ADDR
valueFrom:
secretKeyRef:
name: shared-sqs-redis
key: addr
- name: REDIS_USER
valueFrom:
secretKeyRef:
name: shared-sqs-redis
key: user
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: shared-sqs-redis
key: password
- name: NUBES_ENDPOINT
value: {{ .Values.nubesEndpoint | quote }}
{{- if .Values.billing.enabled }}
- name: BILLING_PG_HOST
value: {{ .Values.billing.postgres.host | quote }}
- name: BILLING_PG_PORT
value: {{ .Values.billing.postgres.port | quote }}
- name: BILLING_PG_DATABASE
value: {{ .Values.billing.postgres.database | quote }}
- name: BILLING_PG_USER
value: {{ .Values.billing.postgres.user | quote }}
- name: BILLING_PG_SSLMODE
value: {{ .Values.billing.postgres.sslmode | quote }}
- name: BILLING_PG_PASSWORD
valueFrom:
secretKeyRef:
{{- if .Values.billing.postgres.existingSecret }}
name: {{ .Values.billing.postgres.existingSecret }}
{{- else }}
name: shared-sqs-billing-pg
{{- end }}
key: {{ .Values.billing.postgres.secretKey | default "password" }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: {{ .Values.service.port }}
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: {{ .Values.service.port }}
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
@@ -0,0 +1,15 @@
# secret-billing.yaml — Secret с PostgreSQL credentials для billing
# Создаётся только если billing.enabled=true и existingSecret не указан
# Created: 2026-04-12
{{- if and .Values.billing.enabled (not .Values.billing.postgres.existingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: shared-sqs-billing-pg
namespace: {{ .Values.namespace }}
labels:
{{- include "shared-sqs.labels" . | nindent 4 }}
type: Opaque
stringData:
password: {{ required "billing.postgres.password is required when billing is enabled" .Values.billing.postgres.password | quote }}
{{- end }}
+97
View File
@@ -0,0 +1,97 @@
# values.yaml — параметры деплоя shared-sqs
# Created: 2026-04-11
# Updated: 2026-04-12 09:57 MSK — image tag v0.1.22 для demo UI showcase mode
# Все значения — переменные, меняются при деплое через --set или -f override.yaml
# -- Namespace для деплоя
namespace: shared-sqs
# -- Количество реплик
replicaCount: 1
# -- Образ приложения
image:
repository: naeel/shared-sqs
tag: "v0.1.22"
pullPolicy: IfNotPresent
# -- imagePullSecrets (имя Secret'а с credentials к registry)
imagePullSecrets:
- name: sless-registry-auth
# -- Redis: эндпоинт, credentials
# addr — полный host:port (можно internal K8s DNS или внешний IP)
redis:
addr: "rfrm-redisk8s.54467f74-67f5-4ca7-9b4a-bdd50e8c23d6.svc:6379"
user: "default"
password: "quiY2oovugaiChiejah9"
# -- Admin токен для /admin/* API
admin:
token: "sqs-admin-7a7d8bd0c060a75c198d48680f34077a"
# -- Seed demo tenant при старте
seedDemo: true
# -- Nubes API endpoint
nubesEndpoint: "https://deck-api-test.ngcloud.ru/api/v1"
# -- Ресурсы контейнера
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "500m"
# -- Service
service:
type: ClusterIP
port: 4100
# -- Ingress
ingress:
enabled: true
className: nginx
host: qu.kube5s.ru
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
nginx.ingress.kubernetes.io/proxy-send-timeout: "30"
tls:
enabled: true
secretName: shared-sqs-tls
# -- Probes
probes:
liveness:
path: /health
initialDelaySeconds: 5
periodSeconds: 10
readiness:
path: /health
initialDelaySeconds: 3
periodSeconds: 5
# -- Strategy
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# -- Billing: PostgreSQL для учёта использования SQS-операций.
# Если enabled=false — billing отключён, сервис работает без PostgreSQL.
billing:
enabled: false
postgres:
host: ""
port: "5432"
database: "sqsdb"
user: ""
sslmode: "require"
# Имя существующего K8s Secret с ключом "password"
existingSecret: ""
secretKey: "password"