기본적으로 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

 

credcheck는 PostgreSQL의 패스워드 보안을 강화하기 위해 사용하는 확장 모듈(Extension)이다. 중요한 점은 DB 재시작이 무조건 필요해서 운영하는 시스템에 적용하려면 다운타임이 발생한다는 것이다.

 

git에서 다운받을 수도 있지만, 인터넷 통신이 불가능한 사내망이라면 so 파일을 만들어내기 위해서 필요한 모든 의존성 패키지들을 RPM 파일로 전부 준비해야한다.(지옥이다!!) 그래서 credcheck.so 모듈로 이미 만들어진 RPM(완전체) 패키지를 다운받으면 정말 편하다. 

 

1. RPM 패키지 설치하기

Redhat 8 / PG 14 기준으로 아래 링크에서 RPM을 다운한다. OS와 PG 버전을 맞게 찾아야한다.

알아서 Postgresql 라이브러리 폴더로 so 파일을 넣어주기에 라이브러리(ex: /usr/psql-14/lib/) 에서 RPM을 설치할 필요가 없다.

원래라면 git에서 zip 파일로 서버로 넣어서 한다면 소스 코드 컴파일을 통해서 so 파일을 생성해줘야해서 인터넷이 불가하다면 gcc, make 같은 빌드 도구와 devel 패키지가 필요하고 의존 패키지들을 전부 일일히 RPM 파일로 다운받아서 설치해줘야한다.

그 외 필요한 의존성 패키지들도 전부 설치해줘야한다...

https://download.postgresql.org/pub/repos/yum/14/redhat/rhel-8-x86_64/

 

Index of /pub/repos/yum/14/redhat/rhel-8-x86_64/

 

download.postgresql.org

 

 

rgel8.10이라고 적혀있으면 제작할때 8.10 환경에서 만들었다는 뜻이다. 그러나 8.6인 환경에서 설치하는데 문제가 없던걸로 봐서 마이너 버전의 호환은 가능한 모양이다. 

sudo yum install -y ./credcheck_14-4.7-1PGDG.rhel8.10.x86_64.rpm

2. postgresql.conf의 위치를 찾아 config 파일을 수정한다.

# postgresql.conf 파일 경로를 찾는다.
SHOW config_file;

# config 파일안에서 해당 부분을 수정한다.
shared_preload_libraries = 'credcheck'

3. DB 재시작을 한다.

sudo systemctl restart postgresql-14

4. 모듈을 활성화하고 적용되었는지 확인한다.

# psql 접속 후
CREATE EXTENSION credcheck;

# 정상 적용 확인
SELECT * FROM pg_extension WHERE extname = 'credcheck';

5. 예외 적용

비밀번호 정책을 적용할 수 없는 시스템, 백업 계정 등에 예외처리가 가능하다.

ALTER SYSTEM SET credcheck.whitelist = 'admin, batch_user';

6. 규칙 설정

ALTER SYSTEM SET credcheck.password_length = 8;        -- 8글자 이상
ALTER SYSTEM SET credcheck.password_complexity = 2;    -- 영문/숫자/특수기호 중 2개 이상 조합
ALTER SYSTEM SET credcheck.password_age = 90;          -- 90일 후 변경 권고

7. 설정 반영을 위해 서버 설정 리로드해야한다. (재시작 없이 반영 가능)

리로드하지 않으면 예외처리와 규칙 설정이 적용되지 않으니 필수로 꼭 해야한다.

SELECT pg_reload_conf();

8. 예외 및 규칙 설정 정상 확인

SHOW credcheck.password_length;
SHOW credcheck.password_complexity;
SHOW credcheck.password_age;
SHOW credcheck.whitelist;

 

📌 규칙을 적용한다고 기존에 이미 등록된 계정의 비밀번호가 변경되는것은 아니다. 새로 계정을 생성하거나 비밀번호 변경주기가 도래하여 비밀번호를 새로 바꿀 때 정책 체크를 시작한다.

 

📌 예외와 규칙을 변경할땐 DB 재시작이 필요하지 않지만, 모듈을 활성화하기 위해서는 DB 재시작이 필수적이다.

가상화 환경에서 k8s 없이 docker 컨테이너를 띄울 때 아래와 같은 점을 고려해야한다.

 

# Docker demon process 활성화

host 서버 재시작 시에 자동으로 도커 엔진이 부팅되게 해야한다. 서버가 켜질 때 자동으로 Docker demon이 켜진다.

sudo systemctl enable docker

 

# Volume : 앱 로그 파일 보존

- app log 파일을 컨테이너 재기동에도 보존할 수 있도록 적절한 볼륨 설정을 하고 있는가?

 

컨테이너가 재기동 혹은 삭제되어도 로그를 영구적으로 보존하려면 호스트 서버의 특정 디렉토리에 컨테이너 내부 로그를 바인드 마운트 해야한다.

services:
  my-app:
    image: my-app:latest
    volumes:
      - /data/logs/my-app:/app/logs  # [호스트 경로]:[컨테이너 내부 경로]


# Daemon : /etc/docker/daemon.json 커스텀 설정

로그가 무제한으로 쌓여서 호스트 디스크가 꽉 차는 Disk Full를 막고 운영 안전성을 높이기 위해서 설정이 꼭 필요하다.

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  },
  "live-restore": true,
  "data-root": "/data/docker"
}

 

①log-opts (로그 롤링 및 제한)

컨테이너의 표준 출력(stdout, stderr) 로그 크기와 파일 개수를 제한하는 것이다.

- max-size: 로그 파일의 크기를 제한

- max-file: 최대 보관하는 파일의 갯수, 가장 오래된것부터 덮어씀

-> 해당 옵션은 Doker 데몬이 수집하는 표준 출력 로그(docker logs로 보는 로그)에만 적용된다. 

-> 앱 내부 자체 파일 로그는 Logback이나 logrotate를 통해서 별도 설정이 필요하다.

 

②live-restore (무중단 운영)

-> Docker 데몬 프로세스가 업데이트되거나 재시작될 때, 실행 중인 컨테이너들을 강제로 종료하지 않고 계속 살려두는 옵션이다.

 

③data-root (디스크 파티션 분리)

-> Docker는 기본적으로 모든 이미지, 볼륨, 컨테이너 데이터를 /var/lib/docker (OS Root 파티션) 에 저장한다.

-> 이미지가 쌓이거나 컨테이너 용량이 커지면 OS 영역의 디스크가 가득 차서 서버 자체가 다운될 수 있다. 그래서 별도의 데이터 파티션으로 저장 경로를 변경해주는 것이 중요하다.

 

# Docker Network Mode(Bridge VS host mode)

- default bridge mode 아닌, host mode가 더 적합하지 않은가?

 

 bridge mode

방식: 컨테이너마다 도커가 만들어준 가상의 독립된 네트워크 공간에서 격리하여 관리

특징: 컨테이너 통신 시 이름으로 통신 가능하며 외부와 통신할때는 호스트 서버의 포트를 빌려서 통신한다.

결론: 보안과 독립성이 중요한 일반적인 서비스용에서 사용한다.

단점: 컨테이너 내부 IP가 가상 IP라서 NAT가 필요하다. 

 

host mode

방식: 컨테이너가 호스트 서버의 네트워크를 그대로 공유한다. 

특징: 호스트와 똑같이 움직여 네트워크 성능 저하가 전혀 없지만, 포트가 겹치면 안된다.

결론: 격리보다는 성능이 최우선인 특수 환경용

 

-> default bridge mode로 진행했다. NAT로 인한 오버헤드는 서버들의 하드웨어 가속화와 리눅스 커널의 고성능 패킷 처리 기술로 인해서 미세하고 WEB/WAS/DB 구성에서 기본 모드로 사용을 주로하고 hosts 파일 수정없이 통신하여(port 충돌 해결) 컨테이너 격리 보장하기 위해서다.

 

Bridge 모드에서 주의할 점

NETWORK MODE가 host가 아닌 경우, docker-compose.yml 내 extra_hosts 추가해야 한다.

컨테이너 내부에 별도로 생긴 자신만의 /etc/hosts 파일을 가지고 있기에 host 서버의 /etc/hosts 레코드를 컨테이너는 인식 못한다. 또한 extra_hosts 가 추가되면 컨테이너 재시작이 필요하다. 이미 실행중인 컨테이너는 /etc/hosts가 생성된 상태라서 실행 중인 컨테이너의 파일 내부를 도커가 실시간으로 고쳐주지는 않는다. 그래서 서비스 중단 없이 적용하기 위해서는 무중단 배포를 사용하면 된다. docker-compose up -d --build (또는 --force-recreate) 사용하면 무중단 배포가 된다.

 

 

+ Recent posts