Files
obsidian/infra/network/smtp-relay.md

5.4 KiB

title, updated, tags
title updated tags
smtp-relay (K3s 내부 SMTP 게이트웨이 via Mailgun) 2026-04-08
k3s
mail
postfix
mailgun
smtp
relay

코드: gitea.inouter.com/kaffa/smtp-relay (private) ArgoCD App: argocd/smtp-relay (auto-sync + self-heal)

smtp-relay

K3s 클러스터 내부 애플리케이션이 표준 SMTP로 메일을 보내면 mailgun을 통해 실제 발송되는 릴레이. k3s-backup 안에서만 접근 가능.

아키텍처

K3s 앱 (pod, 임의 네임스페이스)
  └─> smtp-relay.mail.svc.cluster.local:25
        └─> smtp.mailgun.org:587 (SASL auth + STARTTLS)

배치

항목
클러스터 incus-kr1/kr2/hp2 단일 K3s (v1.34.5)
Namespace mail
Deployment smtp-relay 1 replica, strategy=Recreate
이미지 boky/postfix:v4.2.0-alpine (github)
Service ClusterIP, port 25 → 587, port 587 → 587
Resources req 20m CPU / 64Mi, limit 200m / 256Mi
Probe TCP 587 readiness + liveness

DNS / 접근

용도 주소
내부 앱 smtp-relay.mail.svc.cluster.local:25
같은 네임스페이스(mail) smtp-relay:25
외부 / incus 컨테이너 접근 불가 (ClusterIP only)

외부 접근이 필요해지면 NodePort 또는 Gateway TCP route 추가 고려.

Secret 처리

Mailgun SMTP 인증 정보는 Gitea 저장소에 커밋하지 않는다. Vault secret/messaging/mailgun/smtp 에서 읽어 클러스터에 out-of-band로 apply:

USERNAME=$(vault kv get -field=username secret/messaging/mailgun/smtp)
PASSWORD=$(vault kv get -field=password secret/messaging/mailgun/smtp)

kubectl -n mail create secret generic smtp-relay-mailgun \
  --from-literal=username="$USERNAME" \
  --from-literal=password="$PASSWORD" \
  --dry-run=client -o yaml | kubectl apply -f -

Secret 회전 시 동일 명령 재실행 후 kubectl -n mail rollout restart deploy smtp-relay.

ConfigMap (smtp-relay-config)

postfix 동작을 결정하는 주요 환경변수:

변수 의미
RELAYHOST [smtp.mailgun.org]:587 상위 릴레이
RELAYHOST_TLS_LEVEL encrypt STARTTLS 필수
RELAYHOST_USERNAME/_PASSWORD Secret에서 주입 Mailgun SASL
ALLOWED_SENDER_DOMAINS inouter.com 발신 도메인 화이트리스트
POSTFIX_myhostname smtp-relay.mail.svc.cluster.local HELO 이름
POSTFIX_mynetworks 10/8 127/8 172.16/12 192.168/16 릴레이 허용 네트워크
POSTFIX_message_size_limit 25 MiB Mailgun 메시지 한계
POSTFIX_smtpd_helo_required yes HELO 강제
POSTFIX_maximal_queue_lifetime / bounce_queue_lifetime 1h 큐 체류 상한

사용법 (내부 앱)

# Python 예시
import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "noreply@inouter.com"
msg["To"] = "kaffa@inouter.com"
msg["Subject"] = "hello from k3s"
msg.set_content("본문")

with smtplib.SMTP("smtp-relay.mail.svc.cluster.local", 25) as s:
    s.send_message(msg)

인증 불필요. From 도메인은 반드시 inouter.com (아니면 postfix가 reject).

검증 절차

# TCP 접속
kubectl -n mail run probe --rm -it --restart=Never --image=busybox -- \
  sh -c 'echo -e "EHLO test\nQUIT" | nc smtp-relay 25'

# 실제 발송 (swaks 사용)
kubectl -n mail run swaks-test --restart=Never --image=nicolaka/netshoot --command -- \
  swaks --server smtp-relay:25 \
        --from claude@inouter.com \
        --to you@example.com \
        --h-Subject '테스트' \
        --body '본문'
sleep 4
kubectl -n mail logs swaks-test
kubectl -n mail delete pod swaks-test

# postfix 큐에서 처리 내역 확인
kubectl -n mail logs deploy/smtp-relay --tail=30 | grep -E 'status=|relay='

성공 시 로그에 status=sent (250 Great success)relay=smtp.mailgun.org[...]:587 가 나와야 한다.

배포 검증 기록 (2026-04-08 초기)

  • Pod 1/1 Running, readiness/liveness OK
  • swaks 테스트 성공: queue ID 6895722DB89to=<kaffa@inouter.com>, relay=smtp.mailgun.org[34.160.157.95]:587, status=sent (250 Great success)
  • 지연 2.2초 (DNS lookup + STARTTLS 포함)

운영 주의사항

  • DKIM 서명: postfix 자체 DKIM 설정 안 함. Mailgun이 자체 DKIM으로 재서명하므로 수신 측 SPF/DKIM 검증은 통과. 자체 도메인 DKIM 원하면 DKIM_AUTOGENERATE=yes 설정 + DNS TXT 추가.
  • Rate limit: postfix 자체 제한 없음. 폭주 시 Mailgun throttle 걸림 → 큐에 쌓이다 1h 후 bounce. 앱 레벨에서 throttle 권장.
  • sender spoofing 차단: ALLOWED_SENDER_DOMAINS=inouter.com 으로 제한. 다른 도메인 발신이 필요해지면 ConfigMap에서 쉼표로 추가.
  • NetworkPolicy: 현재 없음. 필요 시 특정 네임스페이스만 발송 가능하도록 제한 권장.
  • Monitoring / 알림: 실패 메일 bounce 처리 알림 없음. Mailgun 대시보드로 확인하거나 Discord webhook 추가 필요.

관련 리소스

  • Vault: secret/messaging/mailgun/smtp (SMTP creds), secret/messaging/mailgun/api-key (API 키)
  • Mailgun 도메인: inouter.com
  • ArgoCD App 등록은 infra-hostsargocd 네임스페이스에 있음
  • 유사 GitOps 패턴 앱: kaffa/bunnycdn-mcp, kaffa/cf-bouncer-manager, kaffa/namecheap-api, kaffa/vultr-api