EXPIRE_SESSIONS_JOB - zero1ne-26/doc GitHub Wiki

expire-sessions ๋ฐฐ์น˜ ์žก โ€” ๋นŒ๋“œ & ๋ฐฐํฌ ๊ฐ€์ด๋“œ

orchestration-server์˜ ๋น„ํ™œ์„ฑ ์„ธ์…˜(24์‹œ๊ฐ„ ์ด์ƒ last_activity_at ๊ฐฑ์‹  ์—†์Œ)์„ ์ž๋™ ์ข…๋ฃŒ(users.ended_at ์„ธํŒ…)ํ•˜๋Š” K8s CronJob.

์†Œ์Šค: orchestration-server/cmd/expire-sessions, Dockerfile.expire-sessions ์ฐจํŠธ: devops/charts/job


1. ๋ฐฐ๊ฒฝ

orchestration-server๋Š” ์„ธ์…˜(=์‚ฌ์šฉ์ž, users ํ…Œ์ด๋ธ”) ํ™œ์„ฑ ์ƒํƒœ๋ฅผ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.

๋ฐฉ์‹ ์œ„์น˜ ์ฃผ๊ธฐ ๋น„๊ณ 
์ธ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ ๋ฃจํ”„ internal/mqttx/session_store.go SessionStore.StartCleanup 1๋ถ„ ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋–  ์žˆ๋Š” ๋™์•ˆ๋งŒ ๋™์ž‘
K8s CronJob (์ด ๋ฌธ์„œ) cmd/expire-sessions ์Šค์ผ€์ค„ ์„ค์ •๊ฐ’ (๊ธฐ๋ณธ ๋งค์‹œ 13๋ถ„) ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์งํ›„ ๊ณต๋ฐฑ ๊ตฌ๊ฐ„, ์ธ๋ฉ”๋ชจ๋ฆฌ ์ƒํƒœ ์œ ์‹ค ๋“ฑ์„ ์ปค๋ฒ„ํ•˜๋Š” ์ด์ค‘ ์•ˆ์ „๋ง

๋‘ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๋ชจ๋‘ ๊ฐ™์€ ๊ทœ์น™์„ ์“ด๋‹ค: ended_at IS NULL AND last_activity_at < now - TTL ์ธ ์‚ฌ์šฉ์ž๋ฅผ ended_at = now๋กœ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ.

์ฃผ์˜: ended_at์„ ์„ธํŒ…ํ•ด๋„ MQTT ํ•ธ๋“ค๋Ÿฌ(task/start, task/complete, task/skip ๋“ฑ)๋Š” ํ˜„์žฌ ended_at์„ ์ฒดํฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋””๋ฐ”์ด์Šค๊ฐ€ ๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ID๋กœ ๊ณ„์† ๋ฐœํ–‰ํ•˜๋ฉด ์„œ๋ฒ„๋Š” ์—ฌ์ „ํžˆ ์ฒ˜๋ฆฌํ•œ๋‹ค โ€” ๋ณ„๋„ ์ด์Šˆ๋กœ ํŠธ๋ž˜ํ‚น ์ค‘, ์•„์ง ๋ฏธํ•ด๊ฒฐ.


2. ๋นŒ๋“œ (CI)

orchestration-server/.github/workflows/ci.yml์˜ expire-sessions-docker-build ์žก์ด main/master/develop ๋ธŒ๋žœ์น˜ ํ‘ธ์‹œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ:

  1. Dockerfile.expire-sessions๋กœ ์ด๋ฏธ์ง€ ๋นŒ๋“œ
  2. ghcr.io/<repository_owner>/orchestration-server-expire-sessions:latest๋กœ ํƒœ๊น…
  3. GHCR์— ํ‘ธ์‹œ (๋ฒ„์ „ ํƒœ๊ทธ ์—†์Œ, latest ๋‹จ์ผ ํƒœ๊ทธ โ€” ์šฉ๋Ÿ‰ ์ ˆ์•ฝ์„ ์œ„ํ•ด backend/backoffice ์ด๋ฏธ์ง€์™€ ๋™์ผํ•œ ์ •์ฑ…)

์ˆ˜๋™์œผ๋กœ ๋กœ์ปฌ ๋นŒ๋“œํ•˜๋ ค๋ฉด:

cd orchestration-server
docker build -f Dockerfile.expire-sessions -t orchestration-server-expire-sessions:local .

3. ๋ฐฐํฌ (Helm)

์ฐจํŠธ: devops/charts/job โ€” ์ƒ์‹œ ์„œ๋น„์Šค(infra, backoffice)์™€ ๋ถ„๋ฆฌ๋œ ๋ฐฐ์น˜ ์ „์šฉ chart. ๊ธฐ๋ณธ๊ฐ’์€ expireSessions.enabled: false์ด๋ฏ€๋กœ ํ•„์š”ํ•œ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ์ผ ๋‹ค.

3.1 DB ์ ‘์† ์ •๋ณด

์ด ๋ฐฐ์น˜๋Š” ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด๋ถ€ infra chart์˜ DB๋ฅผ ๋ณด์ง€ ์•Š๋Š”๋‹ค. ์™ธ๋ถ€(์šด์˜) DB ์ฃผ์†Œ๋ฅผ ์ง์ ‘ ๋„˜๊ฒจ์•ผ ํ•œ๋‹ค. ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•:

A. ์ด๋ฏธ ์กด์žฌํ•˜๋Š” Secret ์ฐธ์กฐ (๊ถŒ์žฅ โ€” values์— ํ‰๋ฌธ ๋…ธ์ถœ ์—†์Œ)

kubectl create secret generic expire-sessions-db -n oc \
  --from-literal=database-url='postgres://user:[email protected]:5432/orchestration?sslmode=require'

helm install job ./charts/job -n oc --create-namespace \
  --set expireSessions.enabled=true \
  --set database.existingSecret.name=expire-sessions-db \
  --set database.existingSecret.key=database-url

B. URL ๊ฐ’์„ ๋ฐ”๋กœ ๋„˜๊ฒจ์„œ chart๊ฐ€ Secret ์ƒ์„ฑ

helm install job ./charts/job -n oc --create-namespace \
  --set expireSessions.enabled=true \
  --set database.url='postgres://user:[email protected]:5432/orchestration?sslmode=require'

-n oc๋Š” ์˜ˆ์‹œ namespace๋‹ค. ์–ด๋–ค namespace๋ฅผ ์“ฐ๋“  Secret๊ณผ ์ด chart๋Š” ๋ฐ˜๋“œ์‹œ ๊ฐ™์€ namespace์— ์žˆ์–ด์•ผ ํ•œ๋‹ค. namespace๋Š” ์„ค์น˜ ํ›„์—” ๋ชป ์˜ฎ๊ธฐ๋‹ˆ(5.4 ์ฐธ๊ณ ) ์ฒ˜์Œ๋ถ€ํ„ฐ ์ •ํ•ด์„œ ์‹œ์ž‘ํ•˜๋Š” ๊ฒŒ ์ข‹๋‹ค.

3.2 ์„ค์ •๊ฐ’ (values.yaml ๋ฐœ์ทŒ)

ํ‚ค ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
expireSessions.enabled false CronJob ์ƒ์„ฑ ์—ฌ๋ถ€
expireSessions.image.repository ghcr.io/zero1ne-26/orchestration-server-expire-sessions ์ด๋ฏธ์ง€
expireSessions.image.tag latest ํƒœ๊ทธ
expireSessions.schedule "13 * * * *" ๋งค์‹œ 13๋ถ„ (์ •๊ฐ ๋ถ€ํ•˜ ํšŒํ”ผ)
expireSessions.sessionTtlHours 24 ๋น„ํ™œ์„ฑ ํ—ˆ์šฉ ์‹œ๊ฐ„
expireSessions.concurrencyPolicy Forbid ์ด์ „ ์‹คํ–‰์ด ์•ˆ ๋๋‚ฌ์œผ๋ฉด ์ƒˆ ์‹คํ–‰ ์Šคํ‚ต
expireSessions.activeDeadlineSeconds 120 ์žก์ด ์ด ์‹œ๊ฐ„ ๋„˜๊ฒŒ ๋Œ๋ฉด ๊ฐ•์ œ ์ข…๋ฃŒ
expireSessions.backoffLimit 2 ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ ํšŸ์ˆ˜
database.url / database.existingSecret.* "" ์œ„ 3.1 ์ฐธ๊ณ 

๊ฐ’์„ ๋ฐ”๊พธ๊ณ  ์‹ถ์œผ๋ฉด --set key=value๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ -f my-values.yaml๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œํ•œ๋‹ค.


4. ๋ฐฐํฌ ํ›„ ํ™•์ธ

# CronJob ๋“ฑ๋ก ํ™•์ธ
kubectl get cronjob -n oc

# ์Šค์ผ€์ค„์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์ฆ‰์‹œ 1ํšŒ ์‹คํ–‰ํ•ด๋ณด๊ธฐ
kubectl create job --from=cronjob/job-expire-sessions manual-test-run -n oc

# ์‹คํ–‰ ๋กœ๊ทธ ํ™•์ธ (JSON ๊ตฌ์กฐํ™” ๋กœ๊ทธ, expired ํ•„๋“œ๋กœ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜ ํ™•์ธ)
kubectl logs job/manual-test-run -n oc

# ๊ณผ๊ฑฐ ์‹คํ–‰ ์ด๋ ฅ (successfulJobsHistoryLimit/failedJobsHistoryLimit ๋งŒํผ ๋ณด๊ด€)
kubectl get jobs -n oc -l app.kubernetes.io/name=expire-sessions

์ •์ƒ ์ข…๋ฃŒ ์‹œ ๋กœ๊ทธ ์˜ˆ:

{"level":"INFO","msg":"expire-sessions done","expired":3,"ttl_hours":24,"cutoff":"2026-08-07T13:13:00Z"}

5. ๊ฐ’ ๋ณ€๊ฒฝ & ์šด์˜ ๊ด€๋ฆฌ

5.1 ๊ฐ’ ํ•˜๋‚˜๋งŒ ๋น ๋ฅด๊ฒŒ ๋ฐ”๊พธ๊ธฐ

helm upgrade job ./charts/job -n oc --reuse-values \
  --set expireSessions.schedule="5 * * * *"
  • --reuse-values: ์ด๋ฒˆ์— ์•ˆ ๊ฑด๋“œ๋ฆฐ ๊ฐ’(DB Secret ๋“ฑ)์€ ์ด์ „ ์„ค์น˜ ๋•Œ ๊ฐ’ ๊ทธ๋Œ€๋กœ ์œ ์ง€
  • -n oc: ์„ค์น˜ํ•  ๋•Œ ์“ด namespace์™€ ๋ฐ˜๋“œ์‹œ ๋™์ผํ•ด์•ผ ํ•จ (๋‹ค๋ฅด๋ฉด ์ƒˆ release๋กœ ์ทจ๊ธ‰๋˜๊ฑฐ๋‚˜ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ชป ์ฐพ์Œ)

5.2 ๊ฐ’ ํŒŒ์ผ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ (๊ถŒ์žฅ)

--set์„ ๊ณ„์† ์ด์–ด๋ถ™์ด๋Š” ๋Œ€์‹ , ๋ฐ”๊ฟ€ ๊ฐ’์„ ํŒŒ์ผ ํ•˜๋‚˜์— ๋ชจ์•„๋‘๊ณ  ๊ทธ ํŒŒ์ผ๋กœ upgradeํ•˜๋Š” ๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๋‹ค:

# values-oc.yaml
expireSessions:
  enabled: true
  schedule: "5 * * * *"
database:
  existingSecret:
    name: expire-sessions-db
helm upgrade job ./charts/job -n oc -f values-oc.yaml

์ด ํŒŒ์ผ์„ git์— ์ปค๋ฐ‹ํ•ด๋‘๋ฉด "์ง€๊ธˆ ์„ค์ •์ด ๋ญ์˜€๋Š”์ง€"๋ฅผ ํŒŒ์ผ ํ•˜๋‚˜๋กœ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ , ๋‹ค์Œ์— ๊ฐ’์„ ๋˜ ๋ฐ”๊ฟ€ ๋•Œ๋„ ์ด ํŒŒ์ผ๋งŒ ๊ณ ์ณ์„œ ๊ฐ™์€ ๋ช…๋ น์„ ๋ฐ˜๋ณตํ•˜๋ฉด ๋œ๋‹ค.

5.3 Headlamp์—์„œ ์ง์ ‘ ๊ณ ์น˜๋ฉด ์•ˆ ๋˜๋Š” ์ด์œ  (drift)

Headlamp(๋˜๋Š” kubectl edit)๋กœ ์ด๋ฏธ ๋–  ์žˆ๋Š” CronJob ๋ฆฌ์†Œ์Šค์˜ spec.schedule์„ ์ง์ ‘ ๊ณ ์น˜๋ฉด ๊ทธ ์ˆœ๊ฐ„์—” ์ ์šฉ๋œ๋‹ค. ํ•˜์ง€๋งŒ Helm์€ ๊ทธ ๋ณ€๊ฒฝ์„ ๋ชจ๋ฅธ ์ฑ„ "์ด release์˜ ๊ฐ’์€ ์—ฌ์ „ํžˆ 13๋ถ„"์ด๋ผ๊ณ  ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด์„œ, ๋‚˜์ค‘์— (์Šค์ผ€์ค„๊ณผ ์ „ํ˜€ ์ƒ๊ด€์—†๋Š” ๊ฐ’์„ ๋ฐ”๊พธ๋ ค๋Š” ๊ฒƒ์ด์—ˆ์–ด๋„) helm upgrade๋ฅผ ํ•œ ๋ฒˆ์ด๋ผ๋„ ์‹คํ–‰ํ•˜๋ฉด Helm์ด ๊ธฐ์–ตํ•˜๋Š” ๊ฐ’์œผ๋กœ ์กฐ์šฉํžˆ ๋˜๋Œ์•„๊ฐ„๋‹ค.

  • ์ž ๊น ํ…Œ์ŠคํŠธ๋งŒ ํ•˜๊ณ  ๋ฒ„๋ฆด ๊ฑฐ๋ฉด โ†’ Headlamp ์ง์ ‘ ์ˆ˜์ • OK
  • ๊ณ„์† ์“ธ ๊ฐ’์ด๋ฉด โ†’ ๋ฐ˜๋“œ์‹œ 5.1/5.2์ฒ˜๋Ÿผ Helm์„ ํ†ตํ•ด ๋ฐ”๊ฟ”์„œ Helm์ด ๊ทธ ๊ฐ’์„ "๊ธฐ์–ต"ํ•˜๊ฒŒ ํ•ด์•ผ ํ•จ

5.4 ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋Š” ๋‚˜์ค‘์— ๋ชป ๋ฐ”๊พผ๋‹ค

Helm/K8s์—๋Š” ์ด๋ฏธ ์„ค์น˜๋œ release์˜ namespace๋ฅผ ์˜ฎ๊ธฐ๋Š” ๊ธฐ๋Šฅ์ด ์—†๋‹ค. ์ž˜๋ชป๋œ namespace์— ์„ค์น˜ํ–ˆ๋‹ค๋ฉด ์ง€์šฐ๊ณ  ๋‹ค์‹œ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค:

# 1. ๊ธฐ์กด namespace์—์„œ ์ œ๊ฑฐ
helm uninstall job -n default

# 2. DB Secret์„ ๋ชฉํ‘œ namespace์— ์ƒˆ๋กœ ์ƒ์„ฑ (Secret์€ namespace ์•ˆ์—์„œ๋งŒ ๋ณด์ž„)
kubectl create secret generic expire-sessions-db -n oc \
  --from-literal=database-url='postgres://...'

# 3. ๋ชฉํ‘œ namespace์— ์žฌ์„ค์น˜
helm install job ./charts/job -n oc --create-namespace \
  --set expireSessions.enabled=true \
  --set database.existingSecret.name=expire-sessions-db

5.5 ์ œ๊ฑฐ

helm uninstall job -n oc