teleport mig
Hermaeus Mora · · Devops
Teleport 클러스터를 다른 k8s 클러스터로 옮겼는데 SSH 노드가 전부 떨어짐
노드가 안 붙는 이유는 노드 레코드가 아니라 Host CA 다.
-
엣지 노드
/var/lib/teleport의 host cert 는 구 클러스터 Host CA 서명 -
새 auth 는 빈 backend 로 뜨면서 CA 를 새로 생성 → 상호 인증 실패
-
backend 를 통째로 이식하면 CA/유저/롤/커넥터가 승계되어 노드가 스스로 재연결한다
-
backend 안의
/nodes/레코드는 heartbeat TTL 로 이미 만료돼 있어도 무관하다 (캐시성 데이터)
Teleport 의 상태는 두 군데에 있다:
| 경로 | 내용 |
|---|---|
| /var/lib/teleport/backend/sqlite.db | CA, 유저, 롤, 토큰 |
| /var/lib/teleport/proc/sqlite.db | auth 프로세스 자신의 인스턴스 identity 캐시 |
backend 만 갈아끼우면 CA 는 구 CA인데 proc/ 엔 신규 CA 서명 identity 가 남아, auth 가 127.0.0.1:3025 자기 자신에게 붙다가 x509: certificate signed by unknown authority 로 죽는다. 둘 다 처리해야 한다.
3. 사전 확인 (전부 통과해야 진행)
# 구 backend 살아있나 (PVC Bound, 파드 미점유)
kubectl --context $OLD -n teleport-cluster get pvc teleport-cluster
# 양쪽 clusterName 동일한가 — backend 에 박혀 있어 불변. 다르면 이관 불가
grep clusterName charts/teleport_cluster/values.yaml
# 양쪽 차트/teleport 버전 동일한가 — sqlite 스키마 skew 방지
grep -A3 dependencies charts/teleport_cluster/Chart.yaml
# 새 auth 도 sqlite/PVC 인가 (teleport.yaml 에 storage 스탠자 없으면 기본값 = sqlite)
kubectl --context $NEW -n teleport-cluster get cm teleport-cluster-auth -o jsonpath='{.data.teleport\.yaml}'
4. 절차
Phase A — 구 backend 반출
RWO PVC 를 read-only 로 마운트하는 파드를 띄운다. auth 가 이미 내려가 있으므로 안전하다.
kubectl --context $NCP -n teleport-cluster apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: tel-backend-inspect
spec:
restartPolicy: Never
containers:
- name: sh
image: busybox:1.36
command: ["sleep","86400"]
volumeMounts:
- {name: data, mountPath: /data, readOnly: true}
volumes:
- name: data
persistentVolumeClaim: {claimName: teleport-cluster, readOnly: true}
EOF
kubectl --context $OLD -n teleport-cluster wait --for=condition=Ready pod/tel-backend-inspect --timeout=180s
kubectl --context $OLD -n teleport-cluster exec tel-backend-inspect -- sha256sum /data/backend/sqlite.db
kubectl --context $OLD -n teleport-cluster cp tel-backend-inspect:/data/backend/sqlite.db ./sqlite.db
shasum -a 256 ./sqlite.db # 위 값과 일치 확인
반출본 무결성 + 내용 검증:
sqlite3 ./sqlite.db "pragma integrity_check;" # ok
sqlite3 ./sqlite.db "select key from kv where key like '/authorities/%';" # CA 12종
sqlite3 ./sqlite.db "select count(*) from kv where key like '/nodes/%';" # 노드 수 = 복구 목표
/nodes/ 의 hostname/UUID 를 뽑아두면 Phase E 검증 대조표가 된다.
백업본을 S3 등 내구성 있는 곳에 한 벌 더 둘 것. 세션 스크래치패드는 휘발된다.
Phase B — ArgoCD 동결 + auth 정지
selfHeal이 켜져있는 경우 (대다수의 경우 켜져있겠지만) application-controller 를 내리는 것이 CR 변형 없이 가장 깔끔하다.
kubectl --context $NEW -n argocd scale sts argocd-application-controller --replicas=0
kubectl --context $NEW -n teleport-cluster scale deploy teleport-cluster-auth --replicas=0
# auth 파드가 완전히 사라질 때까지 대기 (RWO PVC detach 필요)
Phase C — backend 교체
PVC 를 rw 로 마운트하는 파드(runAsUser: 0)를 띄우고:
# 1. 롤백본 확보 — 반드시 먼저
cp -a /data/backend/sqlite.db /data/backend/sqlite.db.pre-restore-$(date +%Y%m%d-%H%M%S)
# 2. 기존 소유/권한 기록
stat -c '%u:%g' /data/backend/sqlite.db # 예: 0:0
stat -c '%a' /data/backend/sqlite.db # 예: 600
# 3. 반입 후 파드 안에서 sha256 재대조 (불일치면 교체 전 중단)
# 4. stale WAL/SHM 제거 — 남기면 새로 넣은 db 를 오염시킨다
rm -f /data/backend/sqlite.db-wal /data/backend/sqlite.db-shm
# 5. 교체 + 소유/권한 복원
mv /data/backend/sqlite.db.new /data/backend/sqlite.db
chown <OWNER> /data/backend/sqlite.db && chmod <MODE> /data/backend/sqlite.db
# 6. proc/ 비우기 — 이거 빠뜨리면 auth 가 unknown authority 로 죽는다
rm -rf /data/proc/*
host_uuid 는 보존한다. auth 가 복원된 CA 로 자기 identity 만 재발급하면 된다.
파드 정리 후 auth 기동:
kubectl --context $NEW -n teleport-cluster scale deploy teleport-cluster-auth --replicas=1
kubectl --context $NEW -n teleport-cluster rollout status deploy/teleport-cluster-auth --timeout=300s
kubectl --context $NEW -n teleport-cluster logs deploy/teleport-cluster-auth --tail=200 | grep -E 'ERRO|unknown authority'
unknown authority 0건이어야 한다. 나오면 proc/ 가 안 비워진 것.
# CA 복원 확인 — 전부 "never rotated" 로 나와야 원본
kubectl --context $NEW -n teleport-cluster exec deploy/teleport-cluster-auth -- tctl status
tctl 은 auth 파드 안에서 로컬 admin 소켓으로 붙는다. 클라이언트 인증서/CA 신뢰와 무관하게 항상 동작하는 break-glass 경로다.
Phase D — 구성요소 재조인 (순서 강제)
각 컴포넌트가 신규 CA 서명 identity 를 캐시하고 있어 폐기가 필요하다.
1. proxy + operator
kubectl --context $NEW -n teleport-cluster rollout restart deploy/teleport-cluster-proxy deploy/teleport-cluster-operator
토큰 수동 부트스트랩은 불필요하다. auth 의 --apply-on-startup=/etc/teleport/apply-on-startup.yaml 이 매 기동마다 <release>-proxy / teleport-operator 토큰을 현재 릴리스의 SA 바인딩으로 재적용한다. 구 backend 에 남은 teleport-cluster-proxy 는 무해한 잔재.
operator 가 붙으면 roles / tokens(cert-tbot-token, teleport-kube-agent-token, …) / bots / github connector / databases CR 을 전부 리컨사일한다. 로그로 확인:
kubectl --context $NEW -n teleport-cluster logs deploy/teleport-cluster-operator --tail=30 | grep 'upsert object in Teleport'
Phase E — 해동 + 검증
kubectl --context $NEW -n argocd scale sts teleport-cluster-argocd-application-controller --replicas=1
kubectl --context $NEW -n teleport-cluster exec deploy/teleport-cluster-auth -- tctl get nodes --format=text
kubectl --context $NEW -n teleport-cluster exec deploy/teleport-cluster-auth -- tctl get kube_server --format=text
kubectl --context $NEW -n teleport-cluster exec deploy/teleport-cluster-auth -- tctl get db_server --format=text
노드 UUID 가 Phase A 에서 뽑아둔 목록과 일치해야 한다. 일치하면 재등록이 아니라 원래 host identity 로 재연결된 것이다.
추가 확인:
# ArgoCD 앱 상태
kubectl --context $NEW -n argocd get app | grep -E 'teleport|tbot|db-ca'
tsh login (GitHub connector) 은 별도로 직접 확인할 것. 위 명령들은 전부 로컬 admin 소켓 경로라 SSO 경로를 검증하지 못한다.
5. 롤백
Phase C 에서 만든 sqlite.db.pre-restore-<타임스탬프> 를 되돌리고 auth 재기동. proc/ 도 함께 비운다(반대 방향으로 같은 문제가 생긴다). 이후 Phase D 를 동일 순서로 반복.
6. 함정 정리
| 함정 | 증상 | 대응 |
|---|---|---|
| proc/ 미제거 | auth 가 127.0.0.1:3025 에 x509: unknown authority | rm -rf /data/proc/* 후 재기동 |
| stale WAL/SHM | sqlite 오염, 원인 불명 동작 | 교체 전 rm -f *-wal *-shm |
| ArgoCD selfHeal | scale 0 이 자동 복구됨 | application-controller 를 내린다 |
| tbot 을 operator 보다 먼저 | cert-tbot-token 없음 → 조인 실패 | 순서 준수 |
| kube-agent 를 exporter 보다 먼저 | db 접속 계속 거부 | 순서 준수 |
| helper 파드 sleep 짧게 | cannot exec into a completed pod | 넉넉히 (86400) |
8. 근본 해결 (예정)
이 런북이 필요했던 이유는 CA 가 backend 에 있다는 사실이 아니라 backend 가 클러스터 안 PVC 에 갇혀 있다는 것이다. backend 를 DynamoDB 로 빼면:
-
이관이 "새 auth 를 같은 테이블로 가리키기" 로 끝난다 (다운타임/노드 재연결/proc 사고 없음)
-
auth HA 확보 — 현재
replicas=1+strategy=Recreate+ RWO gp3 라 버전 업그레이드마다 전면 다운타임 -
PITR 로 실질적 백업 확보 (현재 "백업" = auth 내리고 PVC 에서 파일 긁기)
세션 녹화는 S3, audit 은 Athena(차트가 DynamoDB 와의 dual-write + auditLogPrimaryBackend 플립을 지원하므로 단계적 전환 가능).