TLS는 Transport Layer Security로 네트워크 통신 구간에서 인증과 기밀성을 보장하는 보안 프로토콜이다.

 

핸드셰이크란?

아래 두 가지를 확립하는 과정을 핸드셰이크라고 한다.

 

  • 인증(Authentication): 지금 통신하는 상대가 진짜 그 서버가 맞는지 확인
  • 기밀성/무결성(Confidentiality/Integrity): 오가는 데이터를 암호화해서 도청·변조 방지

 

TLS는 어디에 위치할까?

Transport(전송) 계층 바로 위, Application(응용) 계층 아래에 위치한다. 그래서 그래서 상위에 HTTP를 얹으면 HTTPS, SMTP를 얹으면 SMTPS가 되는 것처럼, TLS 자체는 어떤 상위 프로토콜과도 조합 가능한 범용 계층이다.

 

핵심 구성요소 세 가지

  • Public Key Infrastructure (공개키 기반구조): root CA → intermediate CA → leaf 인증서로 이어지는 신뢰 체계. 이걸 통해 "이 공개키가 진짜 이 도메인 것이다"를 CA가 전자서명으로 보증
  • 인증서(X.509): 그 보증 결과를 담은 문서. PEM은 그걸 텍스트로 인코딩하는 표준 포맷
  • 핸드셰이크: 클라이언트가 서버 인증서 체인을 자기 로컬 trust store의 root까지 검증하고, 그다음 세션키를 안전하게 교환하는 절차

SSL과의 관계

TLS는 SSL의 후속 버전이에요. SSL은 설계 결함(POODLE, DROWN 등)으로 전부 폐기됐고, 지금 쓰이는 건 사실상 TLS 1.2/1.3뿐인데 이름만 관습적으로 "SSL"이라 부르는 경우가 많아요 (ssl_protocols, SSLHandshakeException 등).

 

PKI의 기본구조

PKI (전체 인프라)
├── CA (인증서 발급/서명 기관)
│     ├── Root CA
│     ├── Intermediate CA
│     └── Leaf 인증서
├── 인증서 (구조/포맷 자체 - 공개키 + 신원정보를 CA가 서명한 문서)
├── CRL/OCSP (폐기 체계 - 인증서 도난/유출 시 무효화하는 메커니즘)
└── 신뢰 저장소 (OS나 애플리케이션이 어떤 root를 신뢰할지 관리하는 저장소)

 

 

CA 계층구조

  • Root CA: 자기 자신을 자기 개인키로 서명(self-signed). OS/브라우저에 미리 내장돼 있어서 별도 검증 없이 신뢰됨. 개인키는 오프라인으로 엄격히 보관.
  • Intermediate CA: Root가 서명한 하위 CA. 실제 서버 인증서는 root가 직접 서명하지 않고 이 중간 CA가 서명함. 이렇게 하는 이유는 root 키를 노출시키지 않으면서, 사고 발생 시 root는 그대로 두고 intermediate만 폐기(revoke)하면 되기 때문.
  • Leaf(end-entity) 인증서: *.aa.co.kr 실제 도메인용. intermediate가 서명.

인증서 인코딩 방식

  • .pem: 보통 PEM(Base64 텍스트) 형식이 맞음. 확장자 자체가 "PEM 형식이다"를 의도하고 붙인 이름이라 신뢰도가 높음.
  • .crt: PEM(텍스트)일 수도, DER(바이너리)일 수도 있음. 특히 Windows/IIS 환경에서는 .crt가 DER 바이너리인 경우가 흔하고, Linux/nginx 환경에서는 .crt가 PEM 텍스트인 경우가 흔함. .crt는 그냥 "이건 인증서 파일이다"라는 뜻만 담고 있지, 인코딩 방식은 명시 안 함. 그래서 열어서 확인해봐야함.
file mycert.crt
# "PEM certificate" 또는 "data" (바이너리 DER인 경우 이렇게 뜸)

head -c 30 mycert.crt
# 텍스트로 -----BEGIN CERTIFICATE----- 가 보이면 PEM
# 이상한 바이너리 문자가 보이면 DER

 

Sectigo은 체인이 왜 4개일까?

Sectigo 인증기관에서 발급한 SSL을 사용하는데 레거시 호환성으로 인해서 체인이 4개였다.

 

옛날 Windows XP나 구형 클라이언트들은 최신 root인 USERTrust RSA CA를 신뢰 저장소에 안 갖고 있는 경우가 있어서, 예전부터 신뢰돼 있던 AddTrust External CA Root로 cross-sign 해둔 게 번들에 같이 들어있는 것. (이 AddTrust 루트는 2020년에 만료돼서, 지금은 사실상 3단만 실질적으로 동작하지만 번들 파일에는 호환용으로 여전히 포함되는 경우가 많음)

 

 

TLS 핸드셰이크 자세히 보기

두 컴퓨터가 실시간으로 주고받는 통신 절차로써, 시간 순서대로 메시지를 주고받는 프로토콜 흐름이다.

핸드셰이크는 이 두 가지를 확립하는 협상 과정이고, 흥미로운 점은 인증서(비대칭키)는 핸드셰이크 단계에서만 쓰이고, 실제 데이터 전송은 훨씬 빠른 대칭키(세션키)로 한다는 것이다.

 

1. ClientHello: 클라이언트가 지원 가능한 TLS 버전, 암호 스위트 목록, random 값을 보냄

2. ServerHello: 서버가 그중 하나를 골라서 응답 (버전/암호 스위트 확정), 자기 random 값도 같이 보냄

3. Certificate: 서버가 자기 인증서 체인을 보냄 — nginx가 star_cert.pem에 있는 leaf+intermediate들을 이 시점에 클라이언트로 쏘는 것. (root는 안 보냄, 클라이언트가 이미 OS 저장소에 갖고 있으니까)

4. ServerHelloDone: 서버 쪽 메시지 끝

 

  • 체인 검증: 클라이언트가 받은 인증서 체인을 자기 OS/브라우저 신뢰 저장소의 root까지 서명 검증. 여기서 실패하면 "이 연결은 안전하지 않습니다" 뜨는 거예요.

5. ClientKeyExchange: 클라이언트가 pre-master secret을 만들어서 서버의 공개키(인증서 안에 들어있음)로 암호화해서 보냄. 이건 서버의 개인키로만 복호화 가능 — 이 지점이 진짜 인증(누가 개인키를 갖고 있나)이 실질적으로 일어나는 곳

6. Finished: 양쪽이 각자 계산한 세션키로 암호화 통신 시작. 이후 모든 데이터는 대칭키(AES 등)로 암호화됨

 

PKI 에서 사용하는 전자서명 메커니즘

인증서 자체가 어떻게 만들어지고 검증되는가에 대한 알고리즘적 원리이다. 수학적 절차에 해당한다. 

이미 인증서를 발급할 때 한번 일어나고 검증할때마다 재사용되는거다. 실시간 대화가 아니라 이미 만들어진 문서의 진위를 확인하는 방법이다.

 

1. 원본 데이터를 해시해서 고정 길이 값으로 만든다.

2. 그 해시값을 개인키로 암호화한다. 이 과정을 서명이라고 부른다.

3. 받는 쪽은 같은 데이터를 다시 해시하고 서명을 공개키로 복호화해서 나온 값과 비교한다.

4. 둘이 같으면 원본이 위변조가 안됐고 그 개인키를 가진 사람이 서명한게 맞다는 게 증명된다.

 

1,2번은 CA가 인증서로 미리 발급해둔것이고 3,4번은 클라이언트다 핸드셰이크 중 체인 검증 단계에서 하는것이다.

 

CA가 자기 개인키로 이 공개키는 .aa.co.kr 도메인것이다. 라는 정보에 전자서명을 하고 그 인증서를 열어보면 안에 CA의 서명값이 들어있고 클라이언트는 CA 공개키로 그 서명을 검증한다.

'Network' 카테고리의 다른 글

네트워크 OSI 7계층 설명  (1) 2026.03.01

이중복제 발생 : Minio + Ceph RBD 

k8s에서 Minio를 특정 노드에 고정돼서 사용하고 싶지 않으면, Ceph를 사용해서 Ceph의 RBD 볼륨을 PVC로 사용해야한다.

그래서 Minio + local PVC가 아니라 이중복제가 발생한다. Minio도 자체적으로 EC를 진행해서 PVC에 데이터 조각을 분산해서 저장하고 패리티 분산도 진행한다. 그런데 minio는 Ceph가 데이터를 복제하는 것을 모르기에 Ceph가 또 물리 디스크의 데이터를 3중 복제하면서 이중복제가 발생한다.

 

Minio 공식문서에서도 direct-attached 로컬 디스크(JBOD) 위에 배포하는것을 권장한다. 물리 디스크를 그대로 물려서 Minio만의 복제 로직만 동작하게 하는 것이다.

 

그래서 이것을 해결하고자 Minio의 Statfulset + local PV를 사용할 수 있지만, 이러면 노드가 죽었을 때 S3의 패리티 수 이상만큼 장애 발생 시 서비스가 중단되며, 빠르게 노드를 수동으로 복구해줘야한다는 단점이 있다. 즉, k8s의 stateless의 장점을 충분히 활용하지 못하는 것이다.

 

이중복제 문제점 : Minio + Ceph RBD

 

1. 디스크 낭비 : MinIO가 1.5배(4+2 EC) 복제를 하고 Ceph 3중 복제를 하기에 실질 저장 공간 배율은 3 × 1.5 = 4.5TB가 필요하다. 1TB의 데이터를 저장하기 위해서 물리 디스크가 4.5TB 필요한 것이다. 만약 Minio EC 복제 설정 값을 더 크게하면 오버헤드가 더 커진다. 예를 들어서 8+4EC도 있다.

 

2. 네트워크 오버헤드 및 레이어 과다 : Ceph 자체도 RADOS → OSD → PG 매핑 등 내부 레이어가 이미 많은 시스템인데, 그 위에 MinIO의 분산 로직까지 얹히니 전체 I/O 경로가 불필요하고, 하나의 쓰기 요청이 두 번의 독립적인 분산 복제 프로토콜을 거치게된다.

 

3. 설계 철학 충돌

Minio와 Ceph 둘 다 내가 최종 스토리지 계층으로 데이터 내구성을 책임진다는 전제여서 서로의 위/아래에 쌓이는 걸 권장하지 않음 (Minio 공식문서에 적혀 있는 내용이랑 동일한 말)

이중복제 해결 : Longhorn + SeaweedFS

Minio는 분산 모드로 띄우면 무조건 EC 복제를 하기 때문에 끌 수가 없다. 그런데 SeaweedFS는 볼륨(collection) 단위로 replication 설정을 조절할 수 있어서 000으로 설정하면 데이터 복제를 끌 수 있다. 그러면 Longhorn 한 곳에서만 3중 복제를 하기 때문에 오버헤드를 줄일 수 있다. 그러면서 k8s의 stateless인 장점인 PVC를 그대로 사용할 수 있다. 노드가 죽어도 pod가 다른 노드로 띄울 수 있고 PVC도 그대 마운트하면 되기 때문이다. 그래서 노드 장애 시 어느 정도 시간을 벌 수 있다.

 

정리하자면, Minio + Ceph RDB가 각각 자체 복제를 하기에, SeaweedFS 쪽 복제를 의도적으로 끄고 Longhorn 3중 복제만 유지한다.

 

 

'Kubernetes' 카테고리의 다른 글

🚀 k8s 클러스터 백업 방법 (Velero, etcd)  (0) 2026.01.10

 

 

 

기본적으로 SSL Termination과 3-Tier Architecture 구조로 구성하였다. DB는 성능과 안정성을 위해 호스트 프로세스로 직접 운영하는 하이브리드 구성을 택했다.

 

서버 이중화 시 고려한 점

1. Sticky Session과 Session Clustering

Sticky Session은 LB가 특정 사용자의 요청을 처음 접속했던 서버로 보내주는 것으로 사용자의 브라우저 쿠키나 IP 주소를 확인한다. 서버가 죽었을 때 해당 서버에 있는 사용자 세션이 전부 날라가 버린다. 그래서 WAS 서버에서 로직으로 failover가 가능한 Session Clustering도 같이 고려해야한다. 보통 속도를 위해서 redis에 세션을 저장해서 클러스터링한다. Redis Sentinel 구조로 고가용성을 확보해야하고 굉장히 인프라적으로 복잡하다. 그런데 JWT를 사용하면 Stateless 방식이라서 둘 다 필요없다.

 

Sticky Session을 구성하는 방식

1. ip_hash

 

nginx가 HTTP 레벨(L7)에서 동작하지만, 클라이언트의 IP(L3 정보)를 기준으로 해싱(hash 알고리즘)해 항상 같은 서버로 보낸다.

L4의 알고리즘 방식과 관계없이 L7의 알고리즘 방식을 hash를 사용해 Client IP가 같으면 항상 Target WAS를 같게 해주는 것이다. 특정 서버로 트래픽이 몰릴 수 있다.

upstream backend_servers {
    ip_hash;
    server was1.example.com;
    server was2.example.com;
}

 

2. Cookie Insertion - Nginx Plus(유료) 버전만 가능

Nginx에서 들어오는 요청의 쿠키값을 본다. 서버에 고르게 트래픽이 분산되지만 유료 버전만 가능하여 인프라 종속성이 생긴다.

upstream backend_servers {
    server was1.example.com;
    server was2.example.com;

    # 로드밸런서가 'route'라는 이름의 쿠키를 삽입함
    sticky cookie route expires=1h domain=.example.com path=/;
}

 

결론은 JWT를 사용해서 인프라 관리 복잡성을 낮추는게 좋다!!!!!

 

메커니즘 역할
proxy_next_upstream 매 요청마다 실행됨. 현재 서버가 실패하면 그 요청 안에서 즉시 다음 서버로 재시도
max_fails / fail_timeout 실패 횟수가 누적되면, 이후 새로운 요청들이 아예 그 죽은 서버를 시도조차 안 하게 미리 건너뛰게 함 (일종의 캐시)

트러블 슈팅

서버 shutdown 시, 재시도를 하면서 무한로딩 발생한다. Docker 다운이나 HTTP 오류일때는 무한로딩이 발생하지 않고, 서버 다운일 경우에만 발생했다. 해당 문제는 타임아웃일 경우에만 발생하는 것으로 error나 http일때는 상관없다.

-> timeout 발생한 서버를 헬스체크 한번 발생했다면 DB의 Active-Stanby 형식처럼 down된 서버에 접속이 안되는지 찾아봤는데 이건 Nginx Plus(유료)에서 지원하는 Active Health Check 기능이다.

 

특히 이건 내가 proxy_connect_timeout + max_fails  + fail_timeout 의 정말 알맞은 값을 찾기 위해서 여러 시나리오를 생각하고 작성한 것이다. 무료버전에서 서버 shutdown으로 타임아웃 발생 시 웹 페이지에서 크롬으로 따지면 맨 위에서 빙글빙글 돌면서 무한 로딩이 발생하는 이유는 타임아웃 시간만큼 기다렸다가 upstream을 하기 때문이다. 그래서 fail_timeout 시간 내에 max_fails 만큼 오류가 나야되는데 proxy_connect_timeout을 잘 조절해야한다. 즉 30초 안에 3번 오류가 날때 타임아웃 오류일 경우엔 바로 다음 서버로 넘기는게 아니라 proxy_connect_timeout 시간만큼 기다렸다가 넘긴다. 그 모든게 fail_timeout 시간 안에 이루어져야한다. 30초 안에 사용자 3명이 동시에 접속할 경우 3명의 사용자(병렬처리)가 모두 proxy_connect_timeout 만큼 무한로딩을 겪고 다음 4번째 사용자부터 죽은 서버를 제외하고 바로 upstream된다.

최악의 경우는 1명의 사용자가 홀로 접속해서 30초 내에 3번의 클릭을 했는데 proxy_connect_timeout 시간을 너무 길게 잡아서 fail_timeout 시간을 넘어 max_fails 가 초기화되는 경우이다. 이러면 정상 서버로 넘어갈 때 계속 비정상 서버를 체크하면서 proxy_connect_timeout 만큼 기다리기에 계속 로딩이 오래걸린다고 느끼게 된다.

또 다른 최악의 상황은 3명의 사용자가 순차적으로 접속하는 경우이다. 이런 경우엔 upstream되면서 fail_timeout이 초기화되지 않고 첫번째 요청을 기준으로 한다. 즉 fail_timeout - proxy_connect_timeout 시간 만큼만 죽은 서버를 제외해서 fail_timeout보다 훨씬 빠르게 죽은 서버를 다시 찔러본다.

 

1. 동시 접속 3명 (병렬)

t=0: 3명 동시 요청 시작
t=30: 3명 다 거의 동시에 타임아웃 → 실패 카운트 1,2,3이 "거의 동시"에 쌓임 (창은 t=30에 막 시작)
      → 이 순간 마킹됨. 창이 "막 시작"했으니 fail_timeout(30초)이 거의 통째로 남아있음
t=30~60: 이 구간(약 30초) 동안 죽은 서버 스킵됨 ✅

 

2. 1명 순차 클릭 (= 3. 3명 순차 접속과 동일)

t=0:  요청1 → 죽은 서버 시도 → 30초 대기
t=30: 실패 카운트 1 (창 시작: t=30~60) → 정상 서버로 재시도 → 응답
      (요청2가 바로 이어서 들어옴, 1명이 다시 클릭하든 다른 사용자든 동일)
t=30: 요청2 → 죽은 서버 시도 → 30초 대기
t=60: 실패 카운트 시도... 근데 창(t=30~60)이 "지금 막" 끝나는 시점!
      → fail_timeout(30초)이 이미 다 지나버려서 카운터가 리셋됨
      → 이 실패가 "카운트 2"가 아니라 새로운 창의 "카운트 1"이 됨 (창 재시작: t=60~90)
t=60: 요청3 → 죽은 서버 시도 → 30초 대기
t=90: 또다시 창(t=60~90)이 끝나는 시점에 걸림 → 다시 리셋, 카운트 1로 재시작

결과: max_fails=3에 영원히 도달하지 못함 ❌
      → 서버는 절대 unavailable로 마킹되지 않음
      → 모든 요청이 각각 30초씩 무한 로딩을 반복

 

결론은 max_fails × proxy_connect_timeout이 fail_timeout보다 충분히 작아야 한다. 이상적으로는 fail_timeout ≥ max_fails × proxy_connect_timeout × 2 정도의 여유를 두면, 어떤 시나리오(동시/순차)에서도 안정적인 스킵 구간이 확보된다.


proxy_next_upstream + proxy_connect_timeout

Nginx가 WAS 1번으로 보냈는데 타임아웃이나 에러가 났을 때, 사용자에게 에러를 주지 않고 즉시 WAS 2번으로 재시도하게 만드는 설정이다. 오픈소스에서 사용할 수 있는 옵션이다. 둘 다 정상적으로 서버가 살아있다면 해당 옵션은 무시된다.

upstream backend_servers {
    server was1:8080 max_fails=3 fail_timeout=30s;
    server was2:8080 max_fails=3 fail_timeout=30s;
}

server {
    location / {
        proxy_pass http://backend_servers;
        
        # 타임아웃, 에러, HTTP 500번대 발생 시 다음 서버로 즉시 넘김
        # error timeout은 기본값이라서 http_500 http_502 http_503 http_504를 적어야지 의미가 있다.
        # 그렇다고 error timeout를 빼고 http만 적는다면 error timeout일때 upstream이 안되니 주의필요
        proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
        
        # 타임아웃 발생 시 해당 시간만큼 빙글빙글이 발생하게 된다.
        proxy_connect_timeout 15s;
        
        # 다음 서버로 넘길 때의 타임아웃 제한 (너무 오래 걸리면 안 되니까)
        proxy_next_upstream_timeout 10s;
        proxy_next_upstream_tries 2;
    }
}

 

결론적으로는 무료 nginx에서 proxy_next_upstream과 proxy_connect_timeout의 값을 조절하여 정상 서버로 넘기는 것을 보장할 수 있다.

 

Nginx Plus(유료)에서 지원하는 Active Health Check 기능을 구현할 수 있는 무료 nginx에서의 2가지 방법

운영중인 서비스에 적용하기에는 리스크가 커서 적용하지는 못했다. 시간상 여유가 있다면 개발 혹은 스테이징 서버에서 충분한 테스트 후에 적용해야한다.

 

1. nginx_upstream_check_module로 Nginx Plus(유료)에서 지원하는 Active Health Check 기능을 구현할 수 있는데, nginx 코어의 upstream 라운드로빈 로직 자체를 수정해야해서 nginx를 처음부터 통째로 다시 빌드해야하고 2014년 이후로 사실상 유지보수가 멈춘 프로젝트라서 추천하지 않는다. 기존 yum으로 설치된 nginx는 삭제하고 소스 재빌드해야한다.

 

2. OpenResty(Nginx+Lua 통합 배포판)를 쓰면 순수 Lua 스크립트로 active health check를 구현할 수 있다. 그러나 config 파일 경로 이동도 필수적이고 Nginx 서비스 다운타임도 필수적이라 영향도 파악에 너무 많은 시간이 소요될거같아서 따로 더 이상 찾아보지는 않았다.

 

pigsty는 Ansible로 온프레미스에 HA·백업·모니터링까지 갖춘 PostgreSQL을 RDS처럼 구축해주는 오픈소스 배포판이다. PGPOOL-II+Watchdog로 HA를 구성하고 pg_dump로 백업하는 현 시스템을 대체할 수 있는 PaaS급 Postgre인 것이다.

Patroni+etcd+HAProxy 기반 HA, pgBackRest 기반 PITR 백업, Prometheus+Grafana 기반 모니터링을 Ansible 플레이북으로 한 번에 구성해주고 확장 플러그인을 포함해서 제공한다는 것이 장점이다. 전부 설치되어있고 CREATE EXTENSION XXX로 켜서 사용만 하면 된다.

 

공식 문서는 아래 URL에서 확인 가능하다.

https://pigsty.io/docs/concept/

 

Concepts

Understand Pigsty's core concepts, architecture design, learn how high availability, backup recovery, iac, security works

pigsty.io

 

아키텍처를 더 자세히 살펴보도록 하겠다. 우선 모듈별로 의존성이 있기 때문에 INFRA -> NODE -> ETCD -> PGSQL 모듈순으로 설치를 해야한다. Active-Standby 서버로 구성할 시 총 3대의 서버가 필요하고 추가적으로 중앙서버가 하나 필요하다. 그래서 총 4대의 서버가 필요하다. 중앙 서버는 Ansible을 통해서 나머지 모든 서버들을 원격으로 제어하고 배포하는 구조이다. 관리 노드인 중앙서버에는 Infra 노드만 깔고 나머지 PGSQL 노드들에 NODE -> ETCD -> PGSQL를 설치하면 된다.

 

구조를 하나씩 뜯어보자.

 

먼저 가장 중요한 HA 이중화를 가능하게 하는 것은 haproxy, etcd 때문이다.

 

pgbouncer는 연결 풀링에 최적화된 도구로써 클라이언트 연결할때 미리 준비해 놓은 연결 풀링을 이용함으로써 클라이언트가 접속할때 마다 생성하는 OS 프로세스를 미리 준비해두어 속도를 향상시킨다. 그래서 접속할 때는 5432가 아니라 미들웨어인 pgbouncer의 Port로 접속해야한다.

 

patroni는 노드가 죽었을 때 자동 failover부터 죽은 노드를 복구시키는 online recovery까지 전부 자동으로 관리해주는 에이전트이다. postgresql 자체는 고가용성이 없기 때문에 파이썬 코드로 되어있고 이 코드가 돌면서 고가용성을 책임진다.

 

etcd는 patroni가 모여서 정보를 공유하고 합의하는 분산 저장소이다. 현재 primary가 누구인지, 노드 응답이 없어 etcd에 주기적으로 신호를 보내지 못하면 patroni가 승격을 하는 것이다.

 

 

그 다음 백업을 위해서 존재하는 것은 pgBackRest와 Minio다.

 

pgBackRest의 가장 큰 장점은 증분 백업이 가능하다는 것이다. pg_dump는 전체 백업만 가능하여 시간이 오래 걸리지만 증분 백업이 가능하기에 백업 시간과 저장 공간을 줄일 수 있다. 그리고 PITR (Point-in-Time Recovery)이 가능해서 특정 시간대의 DB로 되돌릴 수 있다. 아키텍처 그림에서 patroni와 협업하는 방식은 Patroni는 Primary 노드가 죽으면 Replica를 Primary로 승격시키는데, 이때 승격된 새 Primary는 pgBackRest와 연동되어 데이터 동기화(복구)를 수행할 수 있다. Streaming Replication가 끊겨있거나 WAL 로그 갭이 심해서 따라갈 수 없을때 진행한다. 

 

 

다음은 네트워크 계층을 살펴보겠다.

 

VIP-Manager는 네트워크 L2 계층으로써 VIP를 실제로 옮기고 서버가 하나의 IP를 바라볼 수 있도록 묶어주는 역할을 한다.

HAProxy는 L4 계층으로써 누가 현재 Primary인지 판단하여 하나의 VIP로 요청이 오더라도 실제 Primary 서버로 라우팅해주는 역할을 하고 포트를 보고 replica로 Select를 보내고 primary로는 Insert, Update, Delete를 보낸다.

 

 

빠르게 설치해보기

./deploy.yml을 입력하면 전체 모듈이 한번에 순서대로 설치된다. 그래서 특정 노드만 설치하고 싶다면 ansible-playbook infra.yml 이런식으로 특정 모듈의 yml을 지정해서 설치해야한다.

curl -fsSL https://repo.pigsty.io/get | bash  # Install Pigsty and dependencies
cd ~/pigsty; ./configure -g                   # Generate config (with 1-node template, -g generates random passwords)
./deploy.yml                                  # Execute deployment playbook

PostgreSQL은 우선 Active- Active로 HA 구성이 불가능하며, Active- Active 운영 난이도가 매우 높아서 보통 Active-Stanby로 HA를 구성한다.
 
Pgpool-II -+ watchdog는 PostgreSQL을 고가용성(HA)으로 운영하기 위한 구성이다.

 

Pgpool-II은 PostgreSQL 앞단에 놓이는 미들웨어로, DB 연결 관리·로드밸런싱·실시간 복제 감지를 담당한다. Pgpool-II에 내장된  watchdog는 pgpool 노드끼리 서로 생존 여부를 감시하고, 장애 발생 시 자동 Failover와 VIP 전환을 수행한다.

Pgpool-II의 주요 기능

읽기/쓰기 분리(Read/Write Splitting): SQL을 파싱하여 쓰기 작업은 Primary로, 읽기 작업은 Replica로 자동 라우팅한다.

SQL를 스스로 까보고 알아서 Select는 Replica로 그 외 Insert, Delete, Update는 Primary로 보낸다. 다만 이 과정에서 SQL을 직접 파싱하기에 오버헤드가 발생할 수 있다.

 

쿼리 캐시(Query Cache): 동일한 쿼리의 결과를 메모리에 캐싱하여 응답 속도를 높인다.

 

고가용성(HA) 지원: Health check 기반의 자동 Failover(Standby 승격)와, Watchdog 기반의 가상 IP(VIP)/active pgpool 관리를 통해 HA를 구성한다.

 

Streaming Replication

Primary(DB 메인)가 생성한 WAL 로그를 Standby(DB 대기)가 실시간으로 받아 적용해 데이터를 계속 동일하게 유지하는 복제 방식이다. 이것은 Streaming replication은 Postgresql 자체 기능으로써, Pgpool-II 미들웨어를 사용하지 않아도 가능하다.

Pgpool-II 미들웨어는 Standby 서버가 여러대일 경우 복제 지연 상태를 감지해서 어느 Standby 서버로 보낼지 라우팅한다.
 

Failover

Primary 서버가 장애로 죽었을 때, Standby를 자동으로 Primary로 승격해서 서비스가 계속되도록 하는 동작이다.
Failover는 pgpool + watchdog이 수행한다. watchdog은 pgpool 미들웨어 자체의 장애 감지하고 리더를 선출해서 Active pgpool로 VIP를 이동시킨다. pgpool Health check가 Primary 장애를 감지하면, pgpool은 failover_command에 등록된 스크립트를 실행해 특정 Standby를 Primary로 승격시키고, 내부 backend status 테이블을 갱신한다. failover_command는 한번 작성해두면 자동화 가능하다.

 

Online Recovery

죽었던 Active DB 노드를 최신 데이터로 재동기화해 Standby로 복귀시키는 작업으로 수동으로 스크립트( recovery_1st_stage_command, pgpool_remote_start)를 실행해주어야한다.

pgpool의 health check가 죽었던 Active DB 노드가 살아났다는 것은 감지할 수 있지만, 결국 관리자가 pcp_recovery_node 명령으로 복구 스크립트를 직접 실행하여 Standby로 복구시키고 죽었던 동안 격차가 벌어진 데이터를 동기화해줘야한다. 즉 온라인 복구는 자동화가 불가능하다.

 
 
⭐ HA 용어 정의

용어 역할 목적
Streaming Replication 데이터 실시간 복제 Standby를 최신 상태로 유지
Failover Standby → Primary 승격 장애 시 서비스 지속
Online Recovery 복구된 노드 재동기화, 재편입 클러스터 구조 복원

 
⭐ 기능별 수행 주체

기능 실제 수행 주체
Streaming Replication PostgreSQL 자체 기능
Failover pgpool (스크립트 실행 - 자동화 가능)
Online Recovery pgpool (스크립트 실행 - 수동 실행)
pgpool 미들웨어 생존 감시 및 VIP 이동 watchdog

 

⭐ 감시 판단 주체

감시 대상 판단 주체 메커니즘
PostgreSQL DB 노드 생존 pgpool (health check) 다중 pgpool일 경우 watchdog 통신망으로 합의(quorum) 가능
pgpool 미들웨어 자체 생존 watchdog watchdog 간 heartbeat

 

내가 설치와 설정을 하면서 참고한 사이트이다.

 

추후에 pigsty라는 local aws rds postgre 오픈소스를 알게되었는데 이걸로 구성하면 Online Recovery를 포함하여 모든 설정을 자동화 할 수 있다. 그건 나중에 블로그에서 더 자세히 설명하고자한다.

Streaming Replication 설정 및 Failover 스크립트 생성 및 테스트

Online Recovery 부분에 대해서는 나와있지 않다.

https://tech.osci.kr/postgresql-ha-%EA%B5%AC%EC%84%B1-pgpool-ii%EB%A1%9C-%EC%9E%90%EB%8F%99-failover%EA%B9%8C%EC%A7%80-%EA%B5%AC%ED%98%84%ED%95%98%EB%8A%94-%EC%8B%A4%EC%A0%84-%EA%B0%80%EC%9D%B4%EB%93%9C/

 

해당 블로그에 들어가면 모든 부분을 확인할 수 있다. 아래 블로그에서 postresql 이중화 #1~#6번까지 보는걸 추천한다.

나는 Streaming Replication랑 Failover는 위 사이트 참고해서 하고 Online Recovery 부분만 아래 사이트를 참고했는데 그냥 처음부터 아래 블로그를 참고하는걸 추천한다.

https://berasix.tistory.com/entry/PostgreSQL-%EC%9D%B4%EC%A4%91%ED%99%94-6-PostgreSQL-Pgpool2Watchdog-%EC%9D%B4%EC%A4%91%ED%99%94-Failover%EC%99%80-Failback?category=1056014

 

[PostgreSQL 이중화] #6. PostgreSQL Pgpool2+Watchdog 이중화 Failover와 Failback

■ PostgreSQL 이중화(Pgpool2+Watchdog) Failover와 Failback ▶ 지난 포스팅에 이어 Auto Failover 테스트와 Failback 작업을 진행해 보겠습니다. ▶ pgpool-II version 4.1.4 버전을 사용하였습니다.(Ubuntu 22.04 Repository에

berasix.tistory.com

 

공식문서 참고자료: https://www.pgpool.net/docs/4.1/en/html/example-cluster.html

 

Pgpool-II + Watchdog Setup Example

Specify failover.sh script to be executed after failover in failover_command parameter. If we use 3 PostgreSQL servers, we need to specify follow_master_command to run after failover on the primary node failover. In case of two PostgreSQL servers, follow_m

www.pgpool.net

 

kube-prometheus-stack helm chart는 Grafana 설치시에 가장 많이 이용하는 helm 이다.

배포를 하면 총 6가지의 POD가 올라온다. Alertmanager는 옵션이지만 나머지는 필수적으로 설치해야한다.

Pod 역할 배포방식
Prometheus 시계열 DB, 메트릭 저장 StatefulSet
Prometheus Operator Prometheus 자동 관리 Deployment
Node Exporter 노드 레벨 메트릭(CPU, Memory, Disk) 수집 DaemonSet
Grafana 대시보드 시각화 Deployment
kube-state-metrics K8s 리소스 레벨 상태 메트릭 Deployment
Alertmanager 알림 관리/발송 (enabled 시) StatefulSet

 

📌 Prometheus Operator 역할 보충설명

1. Prometheus 설정 자동 갱신
2. ServiceMonitor 추가하면 자동 반영
3. PrometheusRule 추가하면 알림 규칙 자동 반영
4. Prometheus Pod 장애 시 자동 복구
5. 버전 업그레이드 관리

 

📌 kube-state-metrics 역할 보충설명

1. Pod 상태 (Running, Pending, Failed)
2. Deployment replica 수
3. Service 정보
4. PVC 상태
5. Node 상태 (Ready, NotReady)

 

 

helm이 설치되어있지않다면 helm 설치부터 진행하면 된다. 난 helm 설치는 되어있어서 건너뛰었다. promethues repo를 추가하고 압축을 해제하면 구조를 확인할 수 있다.

# 레포 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 다운로드
helm pull prometheus-community/kube-prometheus-stack

# 압축 해제
tar -xzf kube-prometheus-stack-*.tgz

# 구조 확인
ls kube-prometheus-stack/

# values.yaml 파일 수정
kube-prometheus-stack/
├── values.yaml
├── charts/
│   ├── grafana/
│   ├── ...

# install (kube-prometheus-stack 폴더 안에서 진행)
helm upgrade --install prometheus . \
  --namespace monitoring \
  --create-namespace \
  -f ./values.yaml

 

values.yaml 파일 내에서 꼭 수정이 필요한 부분들을 정리하고자 한다.

 

1. Prometheus PV, PVC 및 보관주기 설정 

Prometheus는 기본적으로 Pod가 재시작되거나 노드가 교체되어도 메트릭 데이터를 보존하려면 PV,PVC 설정이 꼭 필요하다.

재시작이 없다면 기본적으로 로그를 보관주기 설정에 맞게 보관하지만 재시작이 되거나 노드가 교체된다면 로그 보관주기와 상관없이 데이터가 날라간다. 그래서 retention을 유지하려면 PV, PVC 설정이 필수이다.

# 로그 보관주기 변경
prometheus:
  prometheusSpec:
    retention: 20d    <- 수정
    
# prometheus PV, PVC 설정 
prometheus:
  prometheusSpec:
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: nfs-sc    <- 환경에 맞게 변경
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 10Gi
          selector: {} <- 미지원 시 삭제 필요 (AWS EBS는 미지원)

 

2. Grafana PV, PVC 및 admin ID/PW 설정

kube-prometheus-stack은 Grafana 대시보드를 ConfigMap으로 관리해서 PV,PVC 설정이 필수는 아니지만, 해당 설정을 안해주면 Grafana pod가 재시작되거나 노드가 교체되었을때 Grafana 커스텀 대시보드가 날라간다. Grafana 커스텀 대시보드를 사용한다면 PV 설정은 필수이다.

storageSpec: {}  이 기본으로 되어있는데 {} 삭제하고 작성해주면 된다. {} 삭제하지 않고 storageSpec: 설정 시에는 Pod 오류가 발생한다. 기본적으로 아래 코드가 주석처리 되어있어서 주석을 풀고 storageClassName과 storage만 작성하면 된다.

# sc명 확인
kubectl get storageclass

# grafana pv, pvc 설정
grafana:
  persistence:
    enabled: true
    type: pvc
    storageClassName: nfs-sc    # 환경에 맞게 변경
    accessModes: ["ReadWriteOnce"]
    size: 5Gi
          
# admin ID/PW 설정
grafana:
  adminUser: admin
  adminPassword: "your-password"

3. Grafana ingress.yaml 작성

grafana 대시보드는 외부에서 접속이 필요하기 때문에 ingress 리소스가 필요하다. values.yaml 내에서 추가하는 방식도 있지만 추가적으로 yaml 파일을 작성해서 관리하는것이 편해서 그렇게 했다. 나는 nginx ingress controller와 HTTPS를 사용하여 그 기준으로 작성하였다.

# values.yaml 파일 외 별도로 ingress.yaml 파일 작성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: grafana-dashboard
  namespace: monitoring
spec:
  rules:
  - host: grafana.co.kr <- 수정필요
    http:
      paths:
      - backend:
          service:
            name: kube-prometheus-stack-grafana
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - grafana.co.kr <- 수정필요
    secretName: tls

 

+ Recent posts