ChatOps — 텔레그램 봇 16개 명령어로 K3s 관리하기
K3s 클러스터를 5 노드 굴리다 보니 매번 SSH 들어가서 kubectl get pods --all-namespaces 치는 게 귀찮아졌습니다. 그래서 텔레그램 봇 하나로 K3s 핵심 명령들을 다 호출할 수 있게 만들었습니다. 시작은 7 개 명령이었는데 지금은 16 개.
이 글에서 다루는 것
- 봇 아키텍처 — Go + SSH 한 다리로 모든 노드 접근
- 16 개 명령어 분류 (조회 / 운영 / 리포트)
- /디스크 텍스트 바 그래프 트릭
- /파드 노드별 그룹핑
- 자동 알림과 수동 호출의 분리
1. 왜 ChatOps 인가
서버 모니터링은 Grafana + AlertManager 로 충분히 됩니다. 그런데 “새벽에 알림 받고 즉시 확인” 시나리오 에서 문제가 생겨요.
- 휴대폰 화면에서 Grafana 대시보드 열기 → VPN 연결 → 로그인 → 패널 찾기 → 약 2 분
- 텔레그램 봇
/파드→ 약 3 초
알림 → 진단 → 1 차 액션 까지 한 화면에서 끝나는 게 ChatOps 의 진짜 가치입니다.
2. 아키텍처 — 한 다리만 건너면 다 됨
┌─────────────┐
│ Telegram │ 사용자 메시지
└──────┬──────┘
↓
┌─────────────┐
│ server- │ Go 봇 (Mac mini 에서 상시 실행)
│ monitor │
└──────┬──────┘
↓ SSH (key-based)
┌─────────────────────────────────────┐
│ 르무엘 (control-plane) │
│ kubectl / docker 직접 실행 │
└─────────────────────────────────────┘
봇은 르무엘 에 SSH 로 접속해서 kubectl 을 직접 호출합니다. 다른 노드 정보가 필요해도 르무엘 의 kubectl 이 다 가져옴 (control-plane 은 모든 노드 상태를 앎). 그래서 5 노드 × 5 SSH 연결 같은 복잡함 없이 단 한 통로로 끝.
// internal/bot/bot.go
func (b *Bot) sshLemuel(cmd string) string {
out, err := exec.Command("ssh",
"-p", "2652",
"-i", "/Users/lms/.ssh/id_ed25519",
"iamipro@192.168.219.101",
cmd,
).Output()
if err != nil {
return fmt.Sprintf("❌ %v", err)
}
return string(out)
}
3. 16 개 명령어 — 분류표
조회 (read-only)
| 명령 | 단축 | 무엇을 |
|---|---|---|
/상태 |
/s |
5 노드 가동 / 사이트 응답 시간 |
/파드 |
/p |
K3s 모든 namespace pod 노드별 그룹 |
/노드 |
/n |
kubectl get nodes -o wide + load |
/도커 |
/d |
docker ps 노드별 |
/이미지 |
/i |
ghcr 최근 push 된 이미지 |
/디스크 |
df -h 5 노드 텍스트 바 그래프 | |
/도움 |
/h |
전체 명령어 + 설명 |
운영 (action)
| 명령 | 무엇을 |
|---|---|
/마이그 |
옛 docker → K3s 마이그 자동화 스크립트 호출 |
/백업 |
velero backup create + 진행률 |
/argocd |
sync, refresh, app list |
/재시작 <svc> |
systemctl restart 또는 kubectl rollout restart |
리포트 / 알림
| 명령 | 무엇을 |
|---|---|
/알람 |
현재 발화중인 AlertManager alerts |
/오늘 |
오늘 깃 커밋 + Trivy 결과 + uptime |
/주간 |
주간 활동 요약 (PR / 배포 / 알람) |
/가격 |
비트코인 / 이더 / LMUL 시세 (잡탕) |
4. /디스크 — 텍스트 바 그래프
화면 좁은 휴대폰에서 5 노드 디스크를 한 눈에 보이게 하려고 ASCII 바를 직접 그렸습니다.
func renderBar(pct int) string {
width := 20
filled := pct * width / 100
bar := strings.Repeat("█", filled) + strings.Repeat("░", width-filled)
return fmt.Sprintf("%s %d%%", bar, pct)
}
func (b *Bot) cmdDisk(chatID int64) {
nodes := []string{"르무엘", "루이스", "데이비드", "일원", "솔로몬"}
msg := "💾 디스크 사용\n\n"
for _, n := range nodes {
pct := getDiskPct(n) // SSH df -h 호출
msg += fmt.Sprintf("%s\n%s\n\n", n, renderBar(pct))
}
b.send(chatID, msg)
}
결과 (텔레그램 메시지):
💾 디스크 사용
르무엘
██████░░░░░░░░░░░░░░ 30%
루이스
████░░░░░░░░░░░░░░░░ 20%
데이비드
███████████░░░░░░░░░ 55%
일원
█░░░░░░░░░░░░░░░░░░░ 4%
솔로몬
█░░░░░░░░░░░░░░░░░░░ 4%
이게 그라파나 대시보드 한 패널보다 읽기 쉬워요. 모바일에서 특히.
5. /파드 — 노드별 그룹핑
kubectl get pods --all-namespaces -o wide 의 출력을 그대로 보내면 너무 길어서 휴대폰에서 안 보입니다. 노드별로 그룹핑 + 한 줄 요약.
type podInfo struct {
namespace, name, status, node string
}
func (b *Bot) cmdPods(chatID int64) {
raw := b.sshLemuel("sudo k3s kubectl get pods -A -o wide --no-headers")
pods := parsePods(raw)
byNode := make(map[string][]podInfo)
for _, p := range pods {
byNode[p.node] = append(byNode[p.node], p)
}
var msg strings.Builder
msg.WriteString("🧊 K3s pods (노드별)\n\n")
for _, node := range []string{"lemuel", "louise", "david", "ilwon", "solomon"} {
ps := byNode[node]
running, total := countRunning(ps)
msg.WriteString(fmt.Sprintf("%s — %d/%d\n", node, running, total))
for _, p := range ps {
icon := iconByStatus(p.status) // ✅ ⚠️ ❌
msg.WriteString(fmt.Sprintf(" %s %s/%s\n",
icon, p.namespace, p.name))
}
msg.WriteString("\n")
}
b.send(chatID, msg.String())
}
결과:
🧊 K3s pods (노드별)
lemuel — 7/7
✅ kube-system/coredns-xxx
✅ kube-system/metrics-server-xxx
✅ argocd/argocd-server-xxx
...
louise — 12/13
✅ velero/node-agent-xxx
⚠️ academy-staging/admin-xxx (ImagePullBackOff)
...
solomon — 9/9
✅ jen-prod/jen-postgres-0
✅ dart-prod/dart-postgres-0
...
6. 자동 알림 vs 수동 호출
봇은 두 종류 메시지 를 보냅니다:
자동 (5 분 주기)
# config.yml
interval_seconds: 300
threshold:
cpu_load: 0.8
memory_pct: 85
disk_pct: 80
response_ms: 5000
이 임계 넘으면 자동 푸시. 알림 받았을 때만 보고 평소엔 조용 한 게 ChatOps 의 핵심 — 너무 자주 메시지 오면 무시하게 됨.
수동 (사용자가 명령 입력)
/파드, /상태, /디스크 등. 언제든 현재 상태를 확인 가능.
이 둘이 같은 포맷을 공유하면 좋습니다 — 자동 알림이 /상태 출력의 한 단락 같아 보이게.
7. 봇 추가하면서 배운 것
- 명령은 한국어로.
/파드가/pods보다 빨라요. 한글 IME 안 켜고 그냥 한국어로 칠 수 있음 (자동완성 + 메모리에 박힘) - 단축키는 한 글자로 (
/p,/d,/n). 새벽 3시 졸린 상태로 칠 수 있어야 함 - 출력은 짧게. 휴대폰 한 화면에 못 들어가면 아무도 안 봄. 페이지네이션 절대 X
- 게이트는 봇이 아니라 K3s 가. RBAC 으로 봇이 destructive 명령 못 치게 막아두는 게 안전 (봇 탈취 시나리오)
8. 다음 단계
/논스명령 — Cloudflare Tunnel 로 ArgoCD UI 노출 시 OTP 발급/롤백 <서비스>— 이미지 한 단계 이전으로 (kubectl rollout undo)- /긴급
/패닉— 모든 staging 정지, 운영 PriorityClass 만 살리기
홈랩 K3s 기준 텔레그램 봇 하나로 50% 운영이 가능합니다. Grafana 가 “지표” 라면 봇은 “조작” 의 영역. 둘이 합쳐져야 진짜 운영 화면이 됩니다.