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 플립을 지원하므로 단계적 전환 가능).