08. HA 구성과 장애 전환 검증

1. 검증 범위
단일 인스턴스를 3개로 확장하고 복제·장애 복구를 확인합니다.
| 항목 | 검증 내용 |
|---|---|
| 인스턴스 | Primary 1개·Replica 2개 |
| 배치 | 노드 분산 |
| 복제 | Replica 상태·복제 지연 |
| 장애 | Primary Pod 삭제 |
| 연결 | Primary Service 경유 쓰기 재개 |
| 복귀 | 삭제한 인스턴스의 역할·복제 상태 |
Pod 삭제는 노드 장애·네트워크 단절과 다릅니다. 이번 결과를 전체 HA 검증으로 간주하지 않습니다.
백업·복구는 제외합니다.
2. 사전 확인
2.1. 변수 설정
export K8S_CONTEXT="$(kubectl config current-context)"
export DB_NAMESPACE="postgres"
export DB_CLUSTER="demo-db-yaml"
export DB_SERVICE="$DB_CLUSTER"
클러스터·노드를 확인합니다.
kubectl --context "$K8S_CONTEXT" get sgcluster "$DB_CLUSTER" \
--namespace "$DB_NAMESPACE"
kubectl --context "$K8S_CONTEXT" get nodes -o wide
2.2. 테스트 조건
- 추가 인스턴스의 CPU·메모리·볼륨 확보
- Replica 준비 후 장애 발생
- 쓰기는 Primary Service 사용
- 애플리케이션 재접속은 별도 검증
3개 인스턴스를 분산하려면 배치 조건을 충족하는 노드도 3개 필요합니다.
3. 인스턴스 확장
SGCluster YAML의 인스턴스 수를 변경합니다.
spec:
instances: 3
기존 버전·스토리지·설정 참조는 유지합니다.
kubectl --context "$K8S_CONTEXT" apply \
-f 02-cluster.yaml
UI에서는 클러스터 편집 화면에서 변경합니다. 관리 YAML이 있으면 함께 반영합니다.
Pod·PVC를 확인합니다.
kubectl --context "$K8S_CONTEXT" get pods \
--namespace "$DB_NAMESPACE" \
-o wide
kubectl --context "$K8S_CONTEXT" get pvc \
--namespace "$DB_NAMESPACE"
기본 Pod 이름 예시입니다.
demo-db-yaml-0
demo-db-yaml-1
demo-db-yaml-2
Pod 번호로 Primary를 판단하지 않습니다.
4. 노드 분산과 복제 확인
4.1. 노드 분산
Pod의 NODE를 확인합니다.
kubectl --context "$K8S_CONTEXT" get pods \
--namespace "$DB_NAMESPACE" \
-o wide
같은 노드의 인스턴스는 노드 장애 시 함께 영향을 받습니다.
StackGres의 production 프로파일은 분산용 anti-affinity를 자동 구성합니다. 실제 프로파일·Pod 배치 조건을 확인합니다.
kubectl --context "$K8S_CONTEXT" get sgcluster "$DB_CLUSTER" \
--namespace "$DB_NAMESPACE" \
-o jsonpath='{.spec.profile}{"\n"}'
kubectl --context "$K8S_CONTEXT" get pod "${DB_CLUSTER}-0" \
--namespace "$DB_NAMESPACE" \
-o yaml
인스턴스 확장과 분산 배치는 별도로 확인합니다.
4.2. Patroni 상태
준비된 Pod에서 실행합니다.
kubectl --context "$K8S_CONTEXT" exec \
--namespace "$DB_NAMESPACE" \
"${DB_CLUSTER}-0" \
--container patroni \
-- patronictl list
| 항목 | 확인 내용 |
|---|---|
| Role | Leader 1개·Replica |
| State | 실행·복제 상태 |
| Lag | 복제 지연 |
| Timeline | 장애 전후 타임라인 |
현재 Leader를 기록합니다.
export PRIMARY_POD="<현재-Leader-Pod>"
Replica가 비정상이거나 지연이 크면 장애 테스트를 중단합니다.
5. 쓰기 테스트 준비
5.1. 테이블 생성
06번 문서의 방법으로 appuser·appdb에 접속합니다.
CREATE TABLE app.ha_check (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
recorded_at timestamptz NOT NULL DEFAULT clock_timestamp(),
message text NOT NULL
);
INSERT INTO app.ha_check (message)
VALUES ('before failure');
SELECT * FROM app.ha_check;
5.2. 반복 접속과 쓰기
클러스터 내부의 별도 클라이언트에서 실행합니다. psql과 DB 접근 경로가 필요합니다.
export PGHOST="demo-db-yaml.postgres"
export PGPORT="5432"
export PGDATABASE="appdb"
export PGUSER="appuser"
export PGCONNECT_TIMEOUT="3"
read -r -s -p "DB password: " PGPASSWORD
printf '\n'
export PGPASSWORD
외부 PC는 PGHOST·PGPORT를 실제 NodePort 또는 LoadBalancer 접속값으로 바꿉니다.
while true; do
printf '%s ' "$(date -Is)"
if psql -X -w \
-v ON_ERROR_STOP=1 \
-c "INSERT INTO app.ha_check (message) VALUES ('connection probe');" \
>/dev/null 2>&1; then
echo "WRITE_OK"
else
echo "WRITE_FAIL"
fi
sleep 1
done
매번 새 연결을 생성합니다. 기존 연결·애플리케이션 커넥션 풀 복구는 검증하지 않습니다.
6. Primary Pod 삭제
6.1. 대상 재확인
삭제 직전에 Leader를 확인합니다.
kubectl --context "$K8S_CONTEXT" exec \
--namespace "$DB_NAMESPACE" \
"${DB_CLUSTER}-0" \
--container patroni \
-- patronictl list
현재 Leader와 PRIMARY_POD가 일치하는지 확인합니다.
printf '삭제 대상: %s\n' "$PRIMARY_POD"
6.2. 장애 발생
다른 터미널에서 실행합니다.
date -Is
kubectl --context "$K8S_CONTEXT" delete pod "$PRIMARY_POD" \
--namespace "$DB_NAMESPACE"
강제 삭제 옵션은 사용하지 않습니다.
Pod 상태와 반복 쓰기 결과를 관찰합니다.
kubectl --context "$K8S_CONTEXT" get pods \
--namespace "$DB_NAMESPACE" \
-o wide \
--watch
6.3. 결과 해석
| 결과 | 의미 |
|---|---|
| 다른 Pod가 Leader | Replica 승격·역할 전환 |
| 기존 Pod가 다시 Leader | 빠른 재생성으로 기존 역할 유지 가능 |
리더 잠금 만료 전에 재생성되면 기존 Primary가 유지될 수 있습니다. Pod 삭제가 반드시 Replica 승격으로 이어지지는 않습니다.
7. 복구 상태 확인
7.1. 역할과 복제
준비된 Pod에서 조회합니다.
kubectl --context "$K8S_CONTEXT" exec \
--namespace "$DB_NAMESPACE" \
<READY_POD> \
--container patroni \
-- patronictl list
다음을 확인합니다.
- Leader 1개
- 쓰기 재개
- 삭제한 Pod 재생성
- Replica 상태·지연 정상화
Leader가 바뀌었다면 이전 Primary의 Replica 복귀를 확인합니다.
7.2. 데이터 확인
Primary Service로 접속합니다.
SELECT * FROM app.ha_check
ORDER BY id DESC
LIMIT 10;
INSERT INTO app.ha_check (message)
VALUES ('after recovery');
조회 성공만으로 무손실을 증명하지 않습니다. 복제 모드·장애 시점의 쓰기 결과를 함께 평가합니다.
7.3. 연결 회복 시간
반복 쓰기 로그에서 시점을 기록합니다.
| 시점 | 기록 내용 |
|---|---|
| Pod 삭제 시작 | 명령 실행 시간 |
| 최초 WRITE_FAIL | 연결·쓰기 실패 |
| WRITE_OK 재개 | 연결·쓰기 회복 |
| Replica 정상화 | 복제 상태 회복 |
테스트 간격·연결 제한 시간이 포함되므로 정밀한 장애 전환 시간으로 표현하지 않습니다.
실패가 없으면 “이번 샘플링에서 실패 미관찰”로 기록합니다.
8. 애플리케이션 재접속 확인
반복 psql과 실제 애플리케이션 검증을 구분합니다.
| 항목 | 확인 내용 |
|---|---|
| 기존 연결 | 끊긴 연결 폐기 |
| 커넥션 풀 | 새 연결 확보 |
| 재시도 | 중복 쓰기 방지 |
| 트랜잭션 | 실패·결과 불명 처리 |
| 접속 주소 | Primary Service 사용 |
장애 전환 성공과 애플리케이션 연결 회복은 별도로 확인합니다.
9. 결과 기록과 정리
9.1. 검증 기록
| 항목 | 실제 결과 |
|---|---|
| 인스턴스 수 | |
| 노드 분산 | |
| 장애 전 Leader | |
| 장애 후 Leader | |
| 역할 전환 여부 | |
| 쓰기 실패 구간 | |
| 기존 인스턴스 복귀 | |
| 애플리케이션 재접속 |
Pod 재시작과 Replica 승격을 구분해 기록합니다.
9.2. 테스트 정리
반복 쓰기를 Ctrl+C로 중단합니다.
unset PGPASSWORD
테스트 테이블을 삭제합니다.
DROP TABLE app.ha_check;
인스턴스 축소·PVC 삭제 전 역할과 볼륨 처리 정책을 확인합니다.