All pages
1 of 9

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

OpenSQL 관리

오픈프록시(OpenProxy) 관리

본 문서에서는 OpenProxy의 Connection Pooling, 로드밸런싱, 가상 IP 및 이중화 구성에 대한 설명과 Configuration 파라미터를 통해 해당 기능을 관리하는 방법에 대하여 기술합니다.

사용법

시작

OpenSQL-Installer 를 통해 설치한 경우 OpenProxy는 아래 명령어로 기동된 상태입니다.

bash $OPENSQL_HOME/scripts/start_openproxy.sh
  • $OPENSQL_HOME/etc/openproxy.toml 설정 파일을 로드하여 백그라운드(nohup)로 프로세스를 실행합니다.

  • 프로세스 기동 확인 후 PID를 $OPENSQL_HOME/etc/openproxy.pid에 저장합니다.

  • 기동 대기 시간은 최대 10초이며, 이 안에 프로세스가 확인되지 않으면 오류와 함께 로그 마지막 20줄을 출력합니다.

  • 로그 레벨은 LOG_LEVEL 환경변수로 지정하며, 기본값은 info입니다.

  • $OPENSQL_HOME/etc/openproxy.pid에서 PID를 읽어 SIGTERM 신호를 보냅니다.

  • 프로세스가 완전히 종료될 때까지 대기한 후 PID 파일을 삭제합니다.

  • PID 파일이 없거나 해당 PID의 프로세스가 실행 중이지 않으면 오류 메시지를 출력합니다.

OpenProxy 프로세스를 재시작합니다.

  • stop_openproxy.sh → start_openproxy.sh 순서로 실행합니다.

  • 프로세스가 실행 중이지 않아도 stop 단계의 오류를 무시하고 start를 진행합니다.

  • 실행 중인 OpenProxy 프로세스에 SIGHUP 신호를 보내 프로세스를 재시작하지 않고 설정 파일을 재로드합니다.

  • openproxy.toml 수정 후 서비스 중단 없이 설정을 반영할 때 사용합니다.

  • PID 파일이 없거나 프로세스가 실행 중이지 않으면 오류를 반환합니다.

커넥션 풀(Connection Pool) 관리

OpenProxy의 Connection Pooling 기능과 openproxy.toml 설정파일을 변경하여 구성하는 방법에 대하여 기술합니다.

Connection Pool 을 정의하여 접근할 PostgreSQL 데이터베이스 및 사용자 설정을 관리합니다.

  • openproxy.toml 에 [pools.simple_db] 섹션 작성합니다.

  • 지정되지 않은 DB 이름 - 사용자 이름 쌍에 대한 연결을 처리할 Global Default 풀을 [general.default_pool] 에 정의할 수 있습니다.

종료

재시작

리로딩

  • SCRAM-SHA-256 인증 방식을 [general] 설정 혹은 Pool 설정에 사용하면서 auth_query 를 함께 사용하는 경우 OpenProxy -> PostgreSQL 연결은 서버의 pg_hba.conf 설정에서 반드시 trust 로 설정되어야 합니다. auth_query 를 통해 PostgreSQL 서버에서 가져온 사용자 비밀번호 해시는 OpenProxy 에 접근하고자 하는 클라이언트에 대한 인증에는 활용할 수 있으나 PostgreSQL 에 접속할 때 사용자 패스워드로 활용할 수 없기 때문입니다.

    • openproxy.toml 에 [pools.simple_db.users.0] 섹션 작성합니다.

    • 해당 풀에 대해 정의되지 않은 사용자 이름을 가진 클라이언트 요청을 처리할 Wildcard 사용자를 아래와 같이 정의할 수 있습니다.

    • openproxy.toml 에 [pools.simple_db.shard.0] 섹션 작성합니다.


    생성할 connection pool 을 설정한 openproxy.toml 파일로 OpenProxy 를 수행합니다.


    psql -h 127.0.0.1 -p 6432 -d openproxy -U postgres 명령어를 사용하여 생성된 Connection Pool을 확인합니다.

    개요

    Connection Pool 정의

    Connection Pool 기본 설정

    Connection Pool 사용자 정보 설정

    접속할 cluster 주소 및 database 기재

    simple_db connection pool 생성 설정 파일 전체 예시

    OpenProxy 실행

    Connection Pool 확인

    Starting OpenProxy with config: /opt/opensql/etc/openproxy.toml
    OpenProxy started with PID 18472
    bash $OPENSQL_HOME/scripts/stop_openproxy.sh
    Stopping OpenProxy (PID: 18472)
    OpenProxy stopped
    bash $OPENSQL_HOME/scripts/restart_openproxy.sh
    Restarting OpenProxy...
    Stopping OpenProxy (PID: 18472)
    OpenProxy stopped.
    Starting OpenProxy with config: /opt/opensql/etc/openproxy.toml
    OpenProxy started with PID 18531
    bash $OPENSQL_HOME/scripts/reload_openproxy.sh
    Reloading OpenProxy configuration (PID: 18531)...
    OpenProxy configuration reloaded successfully.
    ## Patroni 설정 예시
    postgresql:
      pg_hba:
        ## OpenProxy 대역에 대해서 인증 없이 접속을 허용하는 trust 옵션을 적용합니다.
        - host    all      all          172.18.65.0/24  trust
    [pools.simple_db.users.0]
    username = "simple_user"
    password = "simple_user"
    pool_size = 5
    statement_timeout = 30000
    [pools.simple_db.users.1]
    username = "*"
    pool_size = 3
    statement_timeout = 5000
    [pools.simple_db.shards.0]
    servers = [
      [ "opensql1", 5432, "Auto", ],
      [ "opensql2", 5432, "Auto", ],
      [ "opensql3", 5432, "Auto", ],
    ]
    database = "some_db"
    use_patroni = true
    [pools.simple_db]
    pool_mode = "session"
    query_parser_enabled = true
    query_parser_read_write_splitting = true
    primary_reads_enabled = true
    sharding_function = "pg_bigint_hash"
    [general.default_pool]
    pool_mode = "transaction"
    query_parser_enabled = true
    query_parser_read_write_splitting = true
    primary_reads_enabled = true
    auth_query = "SELECT usename, passwd FROM pg_shadow WHERE usename = '$1'"
    auth_query_user = "myuser"
    auth_query_password = "mypassword"
    [pools.simple_db]
    pool_mode = "session"
    default_role = "primary"
    query_parser_enabled = true
    query_parser_read_write_splitting = true
    primary_reads_enabled = true
    sharding_function = "pg_bigint_hash"
    prepared_statements_cache_size = 500
    
    [pools.simple_db.users.0]
    username = "simple_user"
    password = "simple_user"
    pool_size = 5
    statement_timeout = 30000
    
    [pools.simple_db.shards.0]
    servers = [
      [ "opensql1", 5432, "Auto", ],
      [ "opensql2", 5432, "Auto", ],
      [ "opensql3", 5432, "Auto", ],
    ]
    database = "some_db"
    use_patroni = true
    openproxy=> show pools;
      database  |     user      |  pool_mode  | cl_idle | cl_active | cl_waiting | cl_cancel_req | sv_active | sv_idle | sv_used | sv_tested | sv_login | maxwait | maxwait_us 
    ------------+---------------+-------------+---------+-----------+------------+---------------+-----------+---------+---------+-----------+----------+---------+------------
     simple_db  | simple_user   | session     |       0 |         0 |          0 |             0 |         0 |       0 |       0 |         0 |        0 |       0 |          0
    (1 rows)
    
    openproxy=> show databases;
                 name             |    host     | port | database |  force_user   | pool_size | min_pool_size | reserve_pool |  pool_mode  | max_connections | current_connections | paused | disabled 
    ------------------------------+-------------+------+----------+---------------+-----------+---------------+--------------+-------------+-----------------+---------------------+--------+----------
     simple_db_shard_0_replica_0  | 178.176.0.4 | 5432 | some_db  | simple_user   |         5 |             0 |            0 | session     |               5 |                   0 |      0 |        0
     simple_db_shard_0_replica_1  | 178.176.0.2 | 5432 | some_db  | simple_user   |         5 |             0 |            0 | session     |               5 |                   0 |      0 |        0
     simple_db_shard_0_primary    | 178.176.0.3 | 5432 | some_db  | simple_user   |         5 |             0 |            0 | session     |               5 |                   0 |      0 |        0
     (3 rows)

    가상 IP 및 이중화 구성 관리

    OpenProxy 에서 제공하는 가상 라우터 다중화 프로토콜 (VRRP) 기반의 가상 IP (Virtual IP) 설정 및 관제 기능에 대한 설명과 구성 방법에 대하여 서술합니다.

    OpenProxy 에서는 가상 라우터 다중화 프로토콜 (VRRP) 기반으로 Virtual IP 를 관리하여 한 OpenProxy 노드가 예기치 않게 종료되어도 같은 가상 IP를 이용해 서비스 고가용성을 유지할 수 있습니다. 이때 PostgreSQL 커넥션 Pooling 및 로드밸런싱 기능에 영향을 미치지 않도록 별도의 비동기 런타임을 통하여 가상 라우터 이벤트를 처리합니다.

    지정한 네트워크 인터페이스의 Multicast 주소로 Advertisement Packet을 보내 노드 간 통신하는 Multicast 방식, 혹은 다른 모든 Peer 노드 (OpenProxy가 구성된 다른 노드) 들의 IPv4 주소를 설정하여 통신하는Unicast 방식으로 동작합니다.

    • Advertisement Packet의 송/수신은 Linux Raw (L3; IP) 소켓을 열고 네트워크의 Multicast 주소 224.0.0.18 에 바인딩 혹은 자신이 구동중인 노드의 IPv4 주소에 바인딩 (Unicast 옵션을 활성화한 경우) 함으로써 이루어집니다.

    가상 IP 점유 / 해제는 Linux NetLink 소켓을 열고 커널에 RTM_NEWADDR 혹은 RTM_DELADDR 메세지를 직접 보내는 방식으로 이루어집니다.

    가상 IP 기능 활성화를 위해서는 OpenProxy 프로세스에 Linux 시스템 권한 cap_net_admin 과 cap_net_raw 설정이 필요합니다.

    Patroni를 이용해 구성된 PostgreSQL 클러스터에 대해 OpenProxy에서 접속할 PostgreSQL 서버의 Role을 정의할 필요 없이 Patroni의 REST API를 통한 Topology Discovery를 수행하도록 지정할 수 있습니다.

    • 각 Pool의 Shard 마다 use_patroni (Boolean) 키를 true로 설정해 활성화할 수 있으며 정의되어 있지 않은 경우는 해당 기능이 비활성화됩니다.

    • Shard 마다 필요한 경우 patroni_port 변수를 추가로 설정해 기본 Port 8008 이 아닌 다른 Port를 리스닝하고 있는 Patroni 서버와도 연동할 수 있습니다.

    • Patroni 서버와 연동해 클러스터 토폴로지를 가져오는 경우 Shard의 Server 마다 설정한 PostgreSQL에 접속하기 위한 Port 번호와 PostgreSQL 노드 Role (Primary 혹은 Replica) 은 무시되며 Patroni REST API 서버가 응답한 값이 사용됩니다.

    Patroni와 연동되도록 설정된 Pool은 설정된 renew_interval (밀리초 단위, 기본값: 5000) 주기마다 Patroni의 REST API 서버에 요청을 보내, PostgreSQL 서버의 접속 정보와 Primary/Replica 역할(Role) 정보를 가져옵니다.

    • Patroni 서버로 보내는 HTTP 요청은 기본적으로 1초의 타임아웃을 가집니다. Shard 내 서버에 요청을 보낸 후, 타임아웃 시간 내에 응답을 받지 못하거나 응답 파싱(Parsing)에 실패할 경우, 다음 서버로 요청을 보내는 방식으로 동작합니다.

    • 모든 서버에 질의했음에도 응답을 정상적으로 처리하지 못한 경우, 서버의 역할(Role)을 업데이트하지 않습니다. 이로 인해 트랜잭션 풀링 모드에서의 쿼리 파싱(Query Parsing) 및 읽기-쓰기 분리(Read-Write Splitting) 기능이 정상적으로 동작하지 않을 수 있습니다.


    OpenProxy 설정파일인 openproxy.toml 에 [general.virtual_router] 항목을 설정하여 가상 라우터 기능을 활성화할 수 있습니다.

    해당 항목이 정의되면 OpenProxy 프로세스 시작 시 별도 런타임을 통해 가상 라우터 상태 머신 (State Machine) 이 활성화되어 가상 IP 관련 이벤트를 처리합니다.

    • interface 항목은 다른 OpenProxy 노드들과 통신할 수 있는(즉 실제 물리 네트워크 인터페이스 카드를 통하여 연결된) 이 노드의 네트워크 인터페이스 이름을 지정합니다.

    • router_id 항목은 이 네트워크에서 가상 라우터 클러스터를 구분할 식별자로 1 ~ 255 사이의 값을 가집니다. 가상 IP MASTER 선출에 참여할 다른 OpenProxy 노드들과 같은 값을 가져야 합니다.

    • priority 항목은 MASTER 가상 라우터 선출에 있어 이 노드가 가질 우선순위 값으로 1 ~ 255 사이의 값을 가집니다. 우선순위 값이 255 인 노드는 시작과 동시에 BACKUP 상태가 아닌 MASTER 상태로 가상 IP 점유를 시도하게 됩니다. 이 외에는 Advertisement 패킷을 통해 전달되는 우선순위 값을 인식하여 가장 높은 노드가 MASTER 상태가 됩니다. 노드마다 다르게 설정하는 것을 권장합니다.

    • advert_int 항목은 MASTER 노드가 자신의 상태 및 우선순위 값을 네트워크 내에 전파하는 Advertisement 패킷을 전달할 주기로 단위는 초 (second) 이며 1 ~ 255 사이의 값을 가집니다. 클러스터 내의 모든 노드들이 같은 값을 가져야 하며 Advertisement 패킷을 3번 연속으로 수신하지 못하면 다른 BACKUP 노드들이 MASTER 선출을 시작합니다.

    • vip_addresses 는 MASTER 노드가 점유할 가상 IP들의 목록으로 쉼표 , 로 구분되는 IPv4 주소 및 네트워크의 비트마스크 길이를 포함한 형태로 주어져야 합니다.

    • pre_promote_script (Optional) 은 BACKUP → MASTER 승격이 일어날 경우 이 노드에서 실행할 명령어를 지정하는 항목입니다. 특정 클라우드 벤더 환경에서는 노드의 네트워크 인터페이스가 가상화 되어 있어 가상 IP 등록을 위해서는 노드의 Secondary IP 등록 이외에 추가적인 작업이 필요할 수 있습니다.

    • pre_demote_script (Optional) 은 MASTER → BACKUP 강등이 일어날 경우 이 노드에서 실행할 명령어를 지정하는 항목입니다.

    • unicast_peers (Optional) 은 Multicast 방식이 아닌 Unicast 방식으로 다른 OpenProxy 노드에 Advertisement 패킷을 전달하기 위한 옵션입니다. 가상 IP MASTER 선출에 참여할 다른 OpenProxy 노드들의 IPv4 주소를 쉼표 , 로 구분되는 배열로 지정합니다.

    OpenProxy 설정파일인 openproxy.toml 에 Patroni REST API와 연동하고자 하는 Pool이 정의된 섹션의 Shard 정의에 use_patroni 키를 설정하여 토폴로지 Discovery 기능을 활성화합니다.

    • [general] 섹션의 renew_interval 값을 설정하여 openproxy.toml 파일을 읽어오거나 Patroni 서버에 질의하여 PostgreSQL 서버의 Role을 업데이트 할 주기를 조정할 수 있습니다.

    • Port 번호와 Role 값은 OpenProxy Connection 생성 시에 실제로 참조되는 값은 아니지만 하위 호환성을 위해 입력되어야 합니다.


    가상 라우터 기능을 활성화하기 위해서는 OpenProxy 프로세스를 root 사용자 권한으로 실행하거나 cap_net_raw, cap_net_admin 권한이 부여되어야 합니다.

    • cap_net_raw 는 RAW 타입 네트워크 소켓을 열기 위해 필요합니다.

    • cap_net_admin 은 네트워크 인터페이스에 Secondary IP 추가 / 삭제를 위해 필요합니다.

    아래와 같이 Linux setcap 을 이용해 실행 바이너리 openproxy 에 권한을 부여합니다.

    systemd 서비스로 설정하여 구동하는 경우 .service 파일에 아래 옵션을 추가하여 권한을 부여합니다.

    개요

    가상 IP 관리 기능

    Patroni 연동 기능

    구성

    가상 라우터

    Patroni REST API 연동

    실행

    [general.virtual_router]
    interface = "eno1"
    router_id = 50
    priority = 150
    advert_int = 3
    vip_addresses = [ "192.168.0.200/24" ]
    pre_promote_script = "/home/opensql/startup.sh"
    pre_demote_script = "/home/opensql/cleanup.sh"
    unicast_peers = [ "192.168.0.7", "192.168.0.8" ]
    [general]
    renew_interval = 5000  ## 설정파일을 읽어 Config을 업데이트하거나, Patroni 서버에 질의하여
                           ## Postgres 서버 Role을 업데이트 할 주기를 설정할 수 있습니다.
                           ## 단위는 밀리초 (milliseconds) 이며 기본값은 5000 입니다.
    
    [pools.my_pool]
    
    [pools.my_pool.shards.0]
    servers = [
      [
        "192.168.0.8",    ## Patroni REST API 서버가 구동중인 호스트들의 IPv4 주소를 입력합니다.
        5432,             ## PostgreSQL 서버의 Port 번호로 Patroni 연동 시에는 무시됩니다.
        "Auto",           ## PostgreSQL 노드의 Role 값으으로 Patroni 연동 시에는 무시됩니다.
      ],
      [
        "192.168.0.9",
        5432,
        "Auto",
      ],
      [
        "192.168.0.10",
        5432,
        "Auto",
      ]
    ]
    database = "postgres"
    use_patroni = true
    patroni_port = "8008"    ## Patroni의 HTTP REST API 서버의 Port 번호를 지정합니다.
                           ## 비어있는 경우 기본값 8008 이 사용됩니다.
    $ sudo setcap 'cap_net_raw=eip cap_net_admin=eip' openproxy
    [Service]
    AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN

    로드밸런싱 관리

    개요

    OpenProxy의 PostgreSQL 서버 로드 밸런싱 및 쿼리 라우팅 기능에 대한 전반적인 설명과 구성 방법에 대하여 기술합니다.


    기능

    Read-Only Load Balancing

    PostgreSQL의 Streaming Replication 구성에서 Primary가 아닌 Replica 인스턴스는 Read-Only 쿼리만을 처리할 수 있습니다. OpenProxy는 구성에 따라 Read-Only 쿼리를 Replica 인스턴스들 중 하나로 보내어 처리하고 결과를 반환하는 로드밸런싱 기능을 제공합니다.

    OpenProxy에서는 기본적으로 Pool (OpenProxy에 접속 시 Database 이름으로 구분되는 PostgreSQL 서버군) 단위로 로드밸런싱 옵션을 관리하며 Pool마다 다른 설정을 부여할 수 있습니다.


    구성

    Query Parser 활성화

    Read-Only 쿼리를 식별해 Replica 인스턴스로 보내기 위해서는 openproxy.toml 구성에서 아래 옵션을 활성화해야 합니다.

    • query_parser_enabled : Query Parsing 기능을 활성화합니다. Read / Write Splitting 외에도 Plugin 기능 활용을 위해서는 해당 옵션이 활성화되어 있어야 합니다.

    • query_parser_read_write_splitting : Parsing한 쿼리를 기반으로 Replica로 보낼 수 있는 (Read-Only) 쿼리인지 Primary에서만 처리할 수 있는 쿼리인지 판단하는 Infer 기능을 활성화합니다.

    Pool의 PostgreSQL 구성에 따라 위 옵션으로 동작하는 OpenProxy는 사용자 쿼리를 파싱하여 Read-Only 트랜잭션을 Replica Role을 가진 서버로 사용자 요청을 보내 처리할 수 있습니다.

    • Simple Query 프로토콜로 SELECT 쿼리를 처리하거나 Extended 프로토콜로 명시적인 Transaction Block 없이 SELECT 만 파싱하여 처리하는 경우는 Replica로 라우팅할 수 있습니다.

    • 명시적인 Transaction 내에서 1개 혹은 여러 개의 Statement를 처리하는 경우는 Primary로 라우팅합니다.

    • 이 외에 Primary 노드도 Read-Only 쿼리를 보내는 대상에 포함하고자 하는 경우는 아래와 같이 primary_reads_enabled

    session 풀링 모드 (하나의 클라이언트가 하나의 PostgreSQL 서버 연결에서 처리되는 방식) 에서도 해당 옵션은 동작하지만 클라이언트의 첫 쿼리를 infer() 한 결과로 얻은 PostgreSQL 서버 연결이 클라이언트 세션 종료시까지 유지되므로, SELECT 쿼리를 먼저 처리하고 이후 Primary에서만 처리되어야 하는 쿼리를 보내는 경우 에러가 발생할 수 있습니다.

    • Query 파싱 및 로드밸런싱 기능을 사용하고자 하는 경우에는 transaction 풀링 모드 (하나의 트랜잭션이 하나의 PostgreSQL 서버 연결에서 처리되는 방식) 를 사용하는 것을 권장합니다.

    Pool 단위로 Read-Only 쿼리를 처리할 Replica를 선택하는 방식을 설정할 수 있습니다.

    • random 방식은 PostgreSQL 서버 중 랜덤하게 하나의 서버를 선택하여 요청을 라우팅하는 방식입니다.

    • loc 방식은 처리중인 Connection 수가 가장 적은 PostgreSQL 서버로 요청을 라우팅하는 방식입니다.

    Prepared Statement는 PostgreSQL Extended Protocol에서 쿼리를 실행하는 방식의 하나로, 사용자 지정 쿼리를 전처리한 (Pre-compiled) 쿼리 실행 Plan 형태로 DBMS 서버에 오브젝트 형태로 저장하여 사용하는 방식입니다.

    PostgreSQL에서는 클라이언트 세션 단위로 Prepared Statement 생성 및 처리를 지원합니다.

    OpenProxy의 session 풀링 모드에서는 하나의 Client가 종료시까지 하나의 PostgreSQL 서버 연결에서만 처리되므로 별도의 작업이 필요 없으나, transaction 풀링 모드에서는 하나의 Client에서의 요청이 Transaction 단위로 여러 개의 PostgreSQL 서버 연결에서 처리가 될 수 있으므로 Prepared Statement 처리 시 문제가 발생할 수 있습니다.

    • 이를 위해 OpenProxy는 transaction 풀링 모드로 동작 시 글로벌한 Prepared Statement 캐시 기능을 제공합니다.

    • Client들이 선언한 모든 Prepared Statement는 캐시에 저장되고, Client가 Prepared Statement를 실행하려는 경우 해당 Prepared Statement를 캐시에서 가져와 현재 PostgreSQL 서버 세션에 선언되었는지 확인하고, 선언되어 있지 않은 경우 실행에 앞서 선언합니다.

    • 해당 글로벌 Cache를 활성화하기 위해서는 Pool의 prepared_statements_cache_size 값을 지정해주어야 합니다. 기본값은 0

    해당 글로벌 Cache는 LRU (Least Recently Used) 방식으로 유지되며, 값이 너무 작은 경우 Prepared Statement를 많이 선언하는 클라이언트 요청 처리 시 문제가 발생할 수 있습니다.


    transaction 풀링 모드에서 Query Parser를 활성화하는 경우 아래 SQL 명령어들의 처리가 제한됩니다.

    • 이름을 가진 (Named) Prepared Statement를 사용자가 명시적으로 SQL 레벨에서 생성하고 실행을 지시하는 명령어입니다. transaction 풀링 모드에서는 각 Transaction이 서로 다른 서버 Connection에서 실행될 수 있으므로 정상적으로 동작하지 않습니다.

    • session 풀링 모드에서는 Client와 Server 사이의 Connection이 세션이 유지되는 동안 동일하므로 사용할 수 있습니다.

    • CREATE FUNCTION 구문을 이용해 사용자 지정 함수를 Database에 생성하는 경우, 해당 함수가 Primary 노드에서만 실행되어야 하는 DDL 혹은 DML 구문을 포함한다면 SELECT 쿼리를 이용한 해당 함수의 실행이 OpenProxy 구성 설정에 따라 Replica 노드로 라우팅되어 에러가 발생할 수 있습니다.

    • 특정 구문의 실행을 명시적 Transaction Block BEGIN … COMMIT 으로 감싸 Primary 노드로만 라우팅하는 방향으로 우회할 수 있습니다.

    옵션을 활성화해야 합니다.
    으로 글로벌 Cache를 비활성화하는 설정입니다.
  • 글로벌 Cache가 비활성화된 경우, 각 Client 내에서 별도의 메모리 공간을 할당하여 Prepared Statements를 저장하고 서버에 요청합니다.

  • Load Balancing 설정

    Prepared Statement Cache 설정

    주의사항

    지원되지 않는 기능

    PREPARE, EXECUTE

    사용자 지정 함수의 실행

    [pools.my_pool]
    pool_mode = "transaction"
    query_parser_enabled = true
    query_parser_read_write_splitting = true
    [pools.my_pool]
    pool_mode = "transaction"
    query_parser_enabled = true
    query_parser_read_write_splitting = true
    primary_reads_enabled = true
    [pools.my_pool]
    load_balancing_mode = "random" ## "random", "loc"
    [pools.my_pool]
    pool_mode = "transaction"
    prepared_statements_cache_size = 1000

    TLS 인증 구성하기

    개요

    본 문서에서는 사설 TLS 인증서를 발급하여 해당 TLS 인증서를 가진 클라이언트만 ETCD3 클러스터에 접근하여 데이터 CRUD를 수행할 수 있도록 구성하는 방법에 대하여 설명합니다.


    인증서 생성

    필요한 Tool 설치

    사설 인증서 설치 및 관리를 위해 Cloudflare의 cfssl, cfssljson 을 설치합니다.

    #!/bin/bash
    CFSSL_VERSION=1.6.5
    CFSSL_PATH=/usr/local/bin
    ARCH=amd64
    
    curl -L "https://github.com/cloudflare/cfssl/releases/download/v${CFSSL_VERSION}/cfssl_${CFSSL_VERSION}_linux_${ARCH}" -o cfssl
    curl -L "https://github.com/cloudflare/cfssl/releases/download/v${CFSSL_VERSION}/cfssljson_${CFSSL_VERSION}_linux_${ARCH}" -o cfssljson
    curl -L "https://github.com/cloudflare/cfssl/releases/download/v${CFSSL_VERSION}/cfssl-certinfo_${CFSSL_VERSION}_linux_${ARCH}" -o cfssl-certinfo
    
    chmod +x cfssl cfssljson cfssl-certinfo
    sudo cp cfssl cfssljson cfssl-certinfo ${CFSSL_PATH}/

    인증서 발급하기

    참고

    ETCD3 클러스터를 위한 인증서 발급 예시를 참조합니다.

    Makefile을 필요에 따라 아래와 같이 수정합니다.

    • 해당 예시의 경우, 파일 관리 편의성을 위해 cfssljson 명령어로 export 하여 생성하는 .pem 파일의 이름 템플릿을 변경하였습니다.

    인증서 CSR (Certificate Signing Request) 를 필요에 따라 아래와 같이 수정합니다.

    • “CN” 항목은 삭제합니다. 현 Patroni에서 ETCD에 접근하기 위해 클라이언트로 이용하는 Python gRPC gateway가 TLS Common Name이 적용된 인증서를 지원하지 않습니다.

    • host 항목에 구성할 ETCD 클러스터의 IP 주소 및 호스트 이름 (필요시) 을 배열로 입력합니다.

    인증기관 (CA) CSR 을 필요에 따라 아래와 같이 수정합니다.

    • “CN” 항목은 삭제합니다.

    • 필요에 따라 names 항목을 아래와 같이 수정합니다.

    make 를 실행하여 인증서를 생성합니다.

    • 설정한 infra0, infra1, infra2 환경변수 값은 생성된 .pem 인증서의 파일 이름으로 사용됩니다.


    • ETCD 실행 시 환경변수 파일 $OPENSQL_HOME/etc/etcd.env 혹은 명령줄 인자를 통해 https 연결과 인증서를 설정합니다.

      • 생성한 인증기관 (CA) 인증서 etcd-ca, etcd-ca-key 를 이용해 서명된 인증서를 가지고 있는 클라이언트만 이 ETCD 인스턴스에 접근할 수 있게 됩니다.

    $OPENSQL_HOME/etc/etcd.env 파일 혹은 ETCD 실행 시 명령줄 인자를 통해 ADVERTISE_CLIENT_URLS, LISTEN_CLIENT_URLS 변경합니다.

    • http://127.0.0.1:2379 는 Local 환경에서의 사용을 위한 것으로 불필요하면 삭제 가능합니다.

    • #Certs 항목은 위 과정을 통해 발급한 인증서들을 등록합니다. peer 는 Client, 나머지는 Server 사이드 TLS 인증서로 활용됩니다.

      • ETCD_TRUSTED_CA_FILE

    : 서버가 신뢰할 TLS 인증서의 인증 기관 (CA) 인증서 경로입니다. 유효한 인증서가 구성된 경우 ETCD 서버는 모든 클라이언트의 인증서를 검증하게 됩니다. 별도 인증 기관을 설정하지 않고 클라이언트 인증을 활용하는 경우
    ETCD_CLIENT_CERT_AUTH=true
    옵션을 활용해야 합니다.
  • ETCD_CERT_FILE : Client - Sever 통신에 사용할 TLS 인증서 경로입니다.

  • ETCD_KEY_FILE : Client - Server 통신에 사용할 TLS Key 경로입니다.

  • ETCD_PEER_TRUSTED_CA_FILE : ETCD Peer간 통신에 사용할 TLS 인증서의 인증 기관 (CA) 인증서 경로입니다.

  • ETCD_PEER_CERT_FILE : ETCD Peer간 통신에 사용할 TLS 인증서 경로입니다.

  • ETCD_PEER_KEY_FILE : ETCD Peer간 통신에 사용할 TLS Key 경로입니다.

  • ETCD 연동

    https://github.com/etcd-io/etcd/tree/main/hack/tls-setup
    $ vim Makefile
    .PHONY: cfssl ca req clean
    
    CFSSL   = @env PATH=$(GOPATH)/bin:$(PATH) cfssl
    JSON    = env PATH=$(GOPATH)/bin:$(PATH) cfssljson
    
    all:  ca req
    
    cfssl:
            HTTPS_PROXY=127.0.0.1:12639 go get -u -tags nopkcs11 github.com/cloudflare/cfssl/cmd/cfssl
            HTTPS_PROXY=127.0.0.1:12639 go get -u github.com/cloudflare/cfssl/cmd/cfssljson
            HTTPS_PROXY=127.0.0.1:12639 go get -u github.com/mattn/goreman
    
    ca:
            mkdir -p certs
            $(CFSSL) gencert -initca config/ca-csr.json | $(JSON) -bare certs/etcd-ca
    
    req:
            $(CFSSL) gencert \
              -ca certs/etcd-ca.pem \
              -ca-key certs/etcd-ca-key.pem \
              -config config/ca-config.json \
              config/req-csr.json | $(JSON) -bare certs/${infra0}
            $(CFSSL) gencert \
              -ca certs/etcd-ca.pem \
              -ca-key certs/etcd-ca-key.pem \
              -config config/ca-config.json \
              config/req-csr.json | $(JSON) -bare certs/${infra1}
            $(CFSSL) gencert \
              -ca certs/etcd-ca.pem \
              -ca-key certs/etcd-ca-key.pem \
              -config config/ca-config.json \
              config/req-csr.json | $(JSON) -bare certs/${infra2}
            $(CFSSL) gencert \
              -ca certs/etcd-ca.pem \
              -ca-key certs/etcd-ca-key.pem \
              -config config/ca-config.json \
              config/req-csr.json | $(JSON) -bare certs/${infra0}-peer
            $(CFSSL) gencert \
              -ca certs/etcd-ca.pem \
              -ca-key certs/etcd-ca-key.pem \
              -config config/ca-config.json \
              config/req-csr.json | $(JSON) -bare certs/${infra1}-peer
            $(CFSSL) gencert \
              -ca certs/etcd-ca.pem \
              -ca-key certs/etcd-ca-key.pem \
              -config config/ca-config.json \
              config/req-csr.json | $(JSON) -bare certs/${infra2}-peer
    
    clean:
            rm -rf certs
    $ vim config/req-csr.json
    {
      "CN": "etcd",               # 삭제
      "hosts": [
        "localhost",
        "127.0.0.1",
        "node1",
        "node2",
        "node3",
        "172.18.0.2",
        "172.18.0.3",
        "172.18.0.4"
      ],
      "key": {
        "algo": "ecdsa",
        "size": 384
      },
      "names": [
        {
          "O": "autogenerated",
          "OU": "etcd cluster",
          "L": "the internet"
        }
      ]
    }
    $ vim config/ca-csr.json
    {
      "CN": "Autogenerated CA",               # 삭제
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names": [
        {
          "O": "TmaxTibero",
          "OU": "OpenSQL",
          "L": "Seongnam-si",
          "ST": "Gyeonggi-do",
          "C": "KR"
        }
      ]
    }
    $ infra0=node1 infra1=node2 infra2=node3 make
    $ ls -l
    total 84
    -rw-r--r-- 1 opensql opensql  985  1월  6 18:10 etcd-ca.csr
    -rw------- 1 opensql opensql 1679  1월  6 18:10 etcd-ca-key.pem
    -rw-rw-r-- 1 opensql opensql 1281  1월  6 18:10 etcd-ca.pem
    -rw-r--r-- 1 opensql opensql  623  1월  6 18:10 node1.csr
    -rw------- 1 opensql opensql  288  1월  6 18:10 node1-key.pem
    -rw-rw-r-- 1 opensql opensql 1196  1월  6 18:10 node1.pem
    -rw-r--r-- 1 opensql opensql  623  1월  6 18:10 node2.csr
    -rw------- 1 opensql opensql  288  1월  6 18:10 node2-key.pem
    -rw-rw-r-- 1 opensql opensql 1196  1월  6 18:10 node2.pem
    -rw-r--r-- 1 opensql opensql  623  1월  6 18:10 node3.csr
    -rw------- 1 opensql opensql  288  1월  6 18:10 node3-key.pem
    -rw-rw-r-- 1 opensql opensql 1196  1월  6 18:10 node3.pem
    -rw-r--r-- 1 opensql opensql  623  1월  6 18:10 node1-peer.csr
    -rw------- 1 opensql opensql  288  1월  6 18:10 node1-peer-key.pem
    -rw-rw-r-- 1 opensql opensql 1196  1월  6 18:10 node1-peer.pem
    -rw-r--r-- 1 opensql opensql  623  1월  6 18:10 node2-peer.csr
    -rw------- 1 opensql opensql  288  1월  6 18:10 node2-peer-key.pem
    -rw-rw-r-- 1 opensql opensql 1196  1월  6 18:10 node2-peer.pem
    -rw-r--r-- 1 opensql opensql  623  1월  6 18:10 node3-peer.csr
    -rw------- 1 opensql opensql  288  1월  6 18:10 node3-peer-key.pem
    -rw-rw-r-- 1 opensql opensql 1196  1월  6 18:10 node3-peer.pem
    $ vim $OPENSQL_HOME/etc/etcd.env
    #$OPENSQL_HOME/etc/etcd.env
    ## 아래 내용을 추가하여 허용할 인증서의 인증기관과 공개키 - 사설키를 각각 등록합니다.
    ## 위치한 .pem 인증서는 etcd 프로세스를 시작할 사용자가 읽기 권한을 가진 파일이어야 합니다.
    #Cert
    ETCD_TRUSTED_CA_FILE=$OPENSQL_HOME/etc/pki/etcd-ca.pem
    ETCD_CERT_FILE=$OPENSQL_HOME/etc/pki/node3.pem
    ETCD_KEY_FILE=$OPENSQL_HOME/etc/pki/node3-key.pem
    ETCD_PEER_TRUSTED_CA_FILE=$OPENSQL_HOME/etc/pki/etcd-ca.pem
    ETCD_PEER_CERT_FILE=$OPENSQL_HOME/etc/pki/node3-peer.pem
    ETCD_PEER_KEY_FILE=$OPENSQL_HOME/etc/pki/node3-peer-key.pem
    #$OPENSQL_HOME/etc/etcd.env
    # ...
    ETCD_ADVERTISE_CLIENT_URLS=https://172.18.0.2:2379
    ETCD_LISTEN_CLIENT_URLS=https://172.18.0.2:2379,http://127.0.0.1:2379
    # ...
    #Certs
    ETCD_TRUSTED_CA_FILE=$OPENSQL_HOME/etc/pki/etcd-ca.pem
    ETCD_CERT_FILE=$OPENSQL_HOME/etc/pki/node1.pem
    ETCD_KEY_FILE=$OPENSQL_HOME/etc/pki/node1-key.pem
    ETCD_PEER_TRUSTED_CA_FILE=$OPENSQL_HOME/etc/pki/etcd-ca.pem
    ETCD_PEER_CERT_FILE=$OPENSQL_HOME/etc/pki/node1-peer.pem
    ETCD_PEER_KEY_FILE=$OPENSQL_HOME/etc/pki/node1-peer-key.pem

    Etcd 관리

    개요

    본 문서에서는 ETCD3와 함께 설치되는 명령줄 도구인 etcdctl 을 이용한 ETCD3 클러스터의 멤버와 엔드포인트 Health 등 상태를 확인하거나 특정 조건을 만족하는 Key들의 값을 조회하고 클러스터의 읽기 / 쓰기 성능을 테스트하는 방법에 대하여 기술합니다.


    명령줄 도구 etcdctl 설치 확인

    ETCD3 클러스터를 관리하기 위한 명령줄 도구인 etcdctl 은 etcd 서버 바이너리와 함께 제공됩니다. 노드에 etcdctl 이 설치되어 있는지 여부와 버전을 확인할 수 있습니다.

    $ which etcdctl
    /usr/local/bin/etcdctl
    
    $ etcdctl version
    etcdctl version: 3.5.6
    API version: 3.5

    etcdctl 의 API에는 v2 와 v3 두 개의 버전이 있으며 etcdctl 실행 시 환경변수 ETCDCTL_API 를 각각 2 혹은 3 으로 설정하여 어떤 버전의 API를 호출할 지 명시할 수 있습니다.

    v3.4.0 이상부터는 v3 버전의 API가 기본으로 사용되며 본 문서에서도 v3 버전의 API를 기준으로 사용법을 서술합니다.

    etcdctl 명령어를 실행할 때 지정할 수 있는 공통 옵션에 대하여 설명합니다.

    • --endpoints : etcdctl 이 접근할 ETCD3 gRPC 서버의 Endpoint URL들을 쉼표 , 로 구분된 목록으로 지정합니다. 지정되지 않은 경우 실행되는 환경의 localhost 127.0.0.1:2379 를 기본 gRPC Endpoint URL로 인지하여 통신을 시도합니다.

      • 유효하지 않은 Endpoint URL은 아래와 같이 에러를 반환합니다.


    클러스터의 멤버 및 Endpoint URL의 목록과 상태를 조회합니다.

    etcdctl member list 로 클러스터 노드의 이름, Advertise 된 Peer 통신을 위한 URL 목록과 Client 통신을 위한 URL 목록을 확인할 수 있습니다.

    • ID : 해당 노드의 고유한 식별자로 ETCD 리더 노드를 선출하는 Raft 알고리즘에 이용됩니다.

    • STATUS : 해당 노드가 부팅되어 클러스터의 리더 선출에 성공적으로 참여 하였는지를 나타내는 값으로 started 혹은 unstarted 값을 가질 수 있습니다. 아직 시작된 적이 없는 ETCD3 노드의 경우 unstarted 값을 가집니다.

    etcdctl endpoint status 로 클러스터의 모든 멤버 노드들의 접속 URL, 데이터베이스 크기, Leader 여부 및 Raft 정보를 확인할 수 있습니다.

    • ENDPOINT : 해당 노드의 Advertise 된 Client 통신을 위한 URL의 목록입니다.

    • ID : ENDPOINT 와 동일합니다.

    • VERSION


    ETCD3 클러스터에 저장된 데이터를 직접 조회합니다.

    etcdctl get 명령어 인자로 --prefix 옵션을 지정해 선행하는 특정 문자열과 매칭되는 Key들을 조회할 수 있습니다.

    etcdctl get 명령어 인자로 특정 Key 값 key 와 다른 Key range_end 를 지정하여 Key 인덱스 공간의 두 지점 사이, 정확히는 [key, range_end) 에 해당하는 Key의 목록을 조회할 수 있습니다.

    • ETCD3에 저장되는 모든 Key들은 Byte의 배열로 치환되어 Index 공간에 저장되며 대소를 비교하여 색인됩니다.

      • aa < ab

      • a\xff < b


    ETCD3는 동시성 있는 클라이언트 접근을 제어하며 데이터 일관성을 유지하기 위해 Revision 정보를 기반으로 한 MVCC (Multi-Version Concurrency Control) 를 이용합니다. 클러스터가 오랜 기간 유지되며 Patroni Switchover / Failover 등의 동작이 많이 일어난 경우 실제 Patroni에서 ETCD3 클러스터에 저장하는 데이터 사이즈가 작음에도 DB 사이즈가 계속해서 증가하는 문제가 발생할 수 있습니다.

    etcdctl compact 명령어로 특정 Revision을 지정해 그 이전 시점의 Revision을 더 이상 참조되지 않는 상태로 정의합니다. 해당 동작은 클러스터 단위의 작업이므로 별도의 endpoint를 지정하지 않고 실행할 수 있습니다.

    etcdctl defrag 명령어로 참조되지 않는 Revision을 DB File에서 삭제해 디스크 공간을 확보합니다. 별도 --endpoints 옵션이 없으면 로컬 노드의 디스크 공간만을 확보합니다. 클러스터의 모든 Endpoint URL을 --endpoints 옵션으로 지정하면 각 호스트들의 디스크 공간을 모두 확보합니다.

    etcdctl check perf 명령어로 1분 동안 클러스터의 쓰기 전체 처리량 (Throughput)을 테스트 할 수 있습니다.

    --load 옵션으로 요청하는 클라이언트 수 및 초당 요청 횟수를 다르게 지정할 수 있습니다.

    • s : 50 Clients, 초당 쓰기 작업 최대 150 건

    • m : 200 Clients, 초당 쓰기 작업 최대 1000 건

    • l : 500 Clients, 초당 쓰기 작업 최대 8000 건

    출력되는 결과에는 PASS / FAIL Criteria가 존재합니다.

    • 생성된 요청의 90% 이상 Throughput이 나와야 합니다.

    • 모든 요청은 500 ms 안에 처리되어야 합니다.

    • 요청의 처리에 걸린 시간의 표준 편차 (stddev) 가 100 ms 이하여야 합니다.

    성능 테스트로 가해진 부하가 많은 경우 DB Revision History로 인해 ETCD 데이터베이스 사이즈가 크게 증가할 수 있으므로 compact 및 defrag 작업을 수행할 것을 권장합니다.


    ETCD3 는 클러스터 노드의 접속 URL, 클러스터 토큰, Listen URL 등 자신을 포함한 클러스터의 모든 구성을 데이터베이스 스냅샷과 같이 관리합니다. 이미 구성되어 있는 ETCD3 클러스터의 노드의 환경설정을 변경하여 Key-Value 데이터만 보존한 채로 재시작하는 경우에는 일반적으로 동작하지 않습니다.

    서비스 중단으로 인해 발생한 장애로 Scale-In이 요구되거나, 새로운 노드를 추가해 Scale-Out을 진행하거나, 호스트의 IPv4 주소가 변경되어 클러스터를 재설정해야 하는 등 이미 구성된 ETCD3 클러스터의 환경 구성을 변경하여 재시작하고자 하는 경우, 명령줄 도구 etcdctl 을 이용해 복구를 진행해야 합니다.

    복구를 위해서는 가져오고자 하는 Key-Value 데이터가 포함된 ETCD3 노드의 스냅샷 (Snapshot) 이 필요합니다. ETCD3의 스냅샷은 관계형 데이터베이스의 데이터 파일에 대응되며, 특정 시점에 ETCD3 클러스터에 저장된 Key-Value 데이터뿐만 아니라 클러스터의 구성 정보 및 상태 (Raft State) 를 같이 저장합니다. 파일 시스템에 저장된 ETCD3의 db 파일을 가져오거나, 구동 중인 ETCD3 클러스터로부터 생성할 수 있습니다.

    ETCD3 데이터 경로 ETCD_DATA_DIR 하부 경로 member/snap/db 에 위치합니다.

    명령줄 도구 etcdctl 의 snapshot save 명령어를 이용해 ETCD3 서버로부터 현재 시점의 Snapshot을 생성할 수 있습니다.

    • 여러 노드로 구성된 클러스터가 동작 중이어도 ENDPOINT 인자로는 하나의 ETCD3 서버 URL만 주어져야 합니다.

    명령줄 도구 etcdctl 의 snapshot restore 명령어를 이용해 Snapshot으로부터 ETCD3 클러스터 데이터를 생성할 수 있습니다.

    • ETCD3 클러스터를 초기 구성할 때와 마찬가지로 새롭게 구성할 클러스터의 초기 설정 중 Database Snapshot에 포함되는 인자들이 명령줄 인자로 모두 주어져야 합니다.

      • --name : 복구하여 새로 생성할 ETCD3 서버의 노드 이름을 지정합니다.

      • --initial-cluster : 클러스터 내 모든 ETCD3 서버의 노드 이름 및 Peer 통신을 위한 Endpoint URL 을 지정합니다.

    • 데이터가 성공적으로 복구 된 경우 ${ETCD_NAME}.etcd 이름의 디렉토리 안에 새로운 클러스터가 구성됩니다.

    • 복구 된 데이터 디렉토리를 기반으로 ETCD3 서비스를 재시작합니다. 데이터 복구 시 사용한 인자와 서비스 재시작 시 사용하는 인자가 일치해야 합니다.

      • 생성된 ${ETCD_NAME}.etcd 디렉토리는 기존 ETCD3 데이터 경로 밑의 member/ 서브디렉토리와 대응됩니다.

    -w, --write-out : 출력값의 Format을 설정하는 옵션으로 fields, json, protobuf, simple, table 을 허용합니다.
  • --key : https 엔드포인트로 접근하려는 경우 TLS 인증에 사용할 Client Key 파일 경로를 지정합니다.

  • --cert : TLS 인증에 사용할 Client 인증서 파일 경로를 지정합니다.

  • --cacert : TLS 인증에 사용할 인증기관 (CA) 인증서 파일 경로를 지정합니다.

  • NAME : 해당 노드의 고유한 이름으로 ETCD3 서버 프로세스 시작 시 --name 인자로 지정한 값입니다.

  • PEER ADDRS : 해당 노드의 Advertise 된 Peer 통신을 위한 URL의 목록입니다.

  • CLIENT ADDRS : 해당 노드의 Advertise 된 Client 통신을 위한 URL의 목록입니다.

  • IS LEARNER : 해당 멤버가 ETCD 클러스터로부터 Snapshot 및 WAL 복제 상태를 유지하지만 리더 선출 Quorum 에는 참여하지 않는 Learner 노드인지를 나타냅니다.

  • : 해당 노드에서 구동중인 ETCD3 서버의 버전을 나타냅니다.
  • DB SIZE : 디스크에 저장된 ETCD3 데이터베이스의 크기를 나타냅니다.

  • IS LEADER : 해당 노드가 이 클러스터의 Raft Leader 인지 여부를 나타냅니다.

  • IS LEARNER : IS LEADER 와 동일합니다.

  • RAFT TERM : Raft 알고리즘에 따른 현재 임기 (Term). 새로운 Leader 선출이 이루어질 때마다 값이 1씩 늘어납니다.

  • RAFT INDEX : 해당 노드에 저장된 쓰기 작업의 Log Entry 위치를 나타냅니다. Leader 선출 시 참조하며 Raft Index가 가장 높은 (즉 가장 최신 상태를 유지하고 있는) 노드가 Leader 선출 시 우선권을 갖습니다.

  • RAFT APPLIED INDEX : 해당 노드의 로컬 환경 Key-Value Store에 적용되어 (Applied) 읽기 가능한 상태의 Log Entry 위치를 나타냅니다. 노드의 CPU 및 디스크 환경에 따라 Raft Applied Index가 Raft Index 보다 작은 값을 갖는 (즉 데이터 쓰기 작업이 지연되는) 상황이 발생할 수 있으며 이 경우 클라이언트 설정에 따라 Data Consistency가 유지되지 않을 수 있습니다.

    • etcdctl get 플래그 --consistency 값에 따라 동작이 달라집니다. --consistency=s (Serializable, default) 인 경우 과거 시점의 데이터를 불러옵니다. --consistency=l (Linearizable) 인 경우 질의하는 서버가 최신 Raft Index를 따라잡을 때까지 대기한 후 최신 시점의 데이터를 불러옵니다.

  • ERRORS : 해당 노드의 Endpoint에서 감지된 문제를 메세지 형태로 표현합니다.

  • range_end 와 일치하는 Key는 포함되지 않습니다. (Exclusive)

  • xl : 1000 Clients, 초당 쓰기 작업 최대 15000 건

    --initial-cluster-token : 클러스터에 참여하기 위한 Initialize Token 을 지정합니다.

  • --initial-advertise-peer-urls : 클러스터의 다른 노드에 알릴 이 노드의 Peer 통신을 위한 Endpoint URL 을 지정합니다.

  • --skip-hash-check 옵션은 복구될 원본 스냅샷의 데이터 무결성을 검증하는 과정을 생략하기 위한 옵션입니다.

    • snapshot save 명령어로 구동중인 ETCD3 서버에서 생성한 스냅샷의 경우 데이터 무결성이 유지되므로 해당 옵션 없이도 정상적으로 복구할 수 있습니다.

    • 파일 시스템에서 복사해 온 ETCD3 데이터 db 파일의 경우 실행 시점의 클러스터 메타 정보를 포함하므로 해당 옵션 없이는 정상적으로 복구할 수 없습니다. --skip-hash-check 옵션을 부여해 데이터 무결성 검증을 생략합니다.

  • 공통 옵션

    예시

    참고

    https://github.com/etcd-io/etcd/issues/9600

    클러스터 상태 조회

    클러스터 멤버 조회

    클러스터 엔드포인트 조회

    데이터 조회

    Prefix로 조회

    Range로 조회

    데이터베이스 유지 보수

    정리할 대상 Revision 지정

    데이터 조각모음

    쓰기 성능 테스트

    복구

    스냅샷 확인

    파일시스템에서 확인

    구동중인 ETCD3로부터 생성

    스냅샷으로부터 데이터 복구

    참고

    https://github.com/etcd-io/etcd/blob/v3.4.19/Documentation/op-guide/recovery.md

    $ etcdctl member list
    Error:  dial tcp 127.0.0.1:2379: connect: connection refused
    $ ETCDCTL_API=3 etcdctl --endpoints="https://192.168.0.10:2379,https://192.168.0.11:2379" \
      --key="./etcd-client-key.pem" \
      --cert="./etcd-client-crt.pem" \
      --cacert="./etcd-ca-crt.pem" \
      member list
    $ etcdctl member list
    670b863301943618, started, node1, http://192.168.0.10:2380, http://192.168.0.10:2379, false
    7825d7b04510b842, started, node3, http://192.168.0.11:2380, http://192.168.0.11:2379, false
    c8245114d55ec576, started, node2, http://192.168.0.12:2380, http://192.168.0.12:2379, false
    
    $ etcdctl member list -w table
    +------------------+---------+-------+--------------------------+--------------------------+------------+
    |        ID        | STATUS  | NAME  |        PEER ADDRS        |       CLIENT ADDRS       | IS LEARNER |
    +------------------+---------+-------+----------------------------+------------------------+------------+
    | 670b863301943618 | started | node1 | http://192.168.0.10:2380 | http://192.168.0.10:2379 |      false |
    | 7825d7b04510b842 | started | node3 | http://192.168.0.11:2380 | http://192.168.0.11:2379 |      false |
    | c8245114d55ec576 | started | node2 | http://192.168.0.12:2380 | http://192.168.0.12:2379 |      false |
    +------------------+---------+-------+----------------------------+------------------------+------------+
    $ etcdctl endpoint status -w table
    +--------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
    |         ENDPOINT         |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
    +--------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
    | http://192.168.0.10:2379 | 670b863301943618 |  3.5.21 |  168 kB |      true |      false |         6 |        520 |                520 |        |
    | http://192.168.0.11:2379 | c8245114d55ec576 |  3.5.21 |  168 kB |     false |      false |         6 |        520 |                520 |        |
    | http://192.168.0.12:2379 | 7825d7b04510b842 |  3.5.21 |  168 kB |     false |      false |         6 |        520 |                520 |        |
    +--------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
    $ etcdctl get --prefix "/opensql/opensql/members" -w simple
    /opensql/opensql/members/pg-1
    {"conn_url":"postgres://192.168.131.12:5432/postgres","api_url":"http://192.168.131.12:8008/patroni","state":"running","role":"replica","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"replication_state":"streaming","timeline":3}
    /opensql/opensql/members/pg-2
    {"conn_url":"postgres://192.168.131.13:5432/postgres","api_url":"http://192.168.131.13:8008/patroni","state":"running","role":"primary","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"timeline":3}
    /opensql/opensql/members/pg-3
    {"conn_url":"postgres://192.168.131.14:5432/postgres","api_url":"http://192.168.131.14:8008/patroni","state":"running","role":"replica","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"replication_state":"streaming","timeline":3}
    $ etcdctl get "/opensql/opensql/members/pg-1" "/opensql/opensql/members/pg-3"
    /opensql/opensql/members/pg-1
    {"conn_url":"postgres://192.168.131.12:5432/postgres","api_url":"http://192.168.131.12:8008/patroni","state":"running","role":"replica","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"replication_state":"streaming","timeline":3}
    /opensql/opensql/members/pg-2
    {"conn_url":"postgres://192.168.131.13:5432/postgres","api_url":"http://192.168.131.13:8008/patroni","state":"running","role":"primary","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"timeline":3}
    
    $ etcdctl get "/opensql/opensql/failover" "/opensql/opensql/historz"
    /opensql/opensql/failover
    {}
    /opensql/opensql/history
    [[1,223438192,"no recovery target specified","2025-04-18T14:27:03.269756+09:00","pg-1"],[2,223438824,"no recovery target specified","2025-04-21T15:13:37.632010+09:00","pg-2"]]
    ## Endpoint Status를 확인해 각 노드에서 유지하고 있는 가장 최신 Revision 정보를 가져온다.
    $ etcdctl endpoint status -w json | jq | grep 'revision'
            "revision": 447871,
            "revision": 447871,
            "revision": 447871,
    
    ## 해당 Revision 이전 시점의 Revision들을 더 이상 참조되지 않는 상태로 지정한다.
    $ rev=447871
    $ etcdctl compact $rev
    compacted revision 447871
    $ etcdctl defrag --endpoints "http://192.168.0.10:2379,http://192.168.0.11:2379,http://192.168.0.12:2379"
    Finished defragmenting etcd member[http://192.168.0.10:2379]
    Finished defragmenting etcd member[http://192.168.0.11:2379]
    Finished defragmenting etcd member[http://192.168.0.12:2379]
    
    $ etcdctl endpoint status --endpoints "..."
    http://192.168.0.10:2379, 670b863301943618, 3.5.21, 25 kB, true, false, 8, 448000, 448000, 
    http://192.168.0.11:2379, c8245114d55ec576, 3.5.21, 25 kB, false, false, 8, 448000, 448000, 
    http://192.168.0.12:2379, 7825d7b04510b842, 3.5.21, 25 kB, false, false, 8, 448000, 448000,
    $ etcdctl check perf
     60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooom! 100.00% 1m0s
    PASS: Throughput is 150 writes/s
    PASS: Slowest request took 0.291186s
    PASS: Stddev is 0.029467s
    PASS
    $ etcdctl check perf
     60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooom! 100.00% 1m0s
    PASS: Throughput is 150 writes/s
    Slowest request took too long: 0.535645s
    PASS: Stddev is 0.079037s
    FAIL
    
    $ etcdctl check perf --load="xl"
     60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooom! 100.00% 1m0s
    FAIL: Throughput too low: 3668 writes/s
    Slowest request took too long: 0.645609s
    Stddev too high: 0.105516s
    FAIL
    $ ls -l $ETCD_DATA_DIR/member/snap/
    total 168
    -rw-------. 1 opensql opensql 16805888 Apr 18 14:29 db
    
    $ cp $ETCD_DATA_DIR/member/snap/db ./mysnapshot.db
    $ ETCDCTL_API=3 etcdctl --endpoints=${ENDPOINT} snapshot save mysnapshot.db
    
    {"level":"info","ts":"2025-04-21T12:18:30.406283+0900","caller":"snapshot/v3_snapshot.go:65","msg":"created temporary db file","path":"mysnapshot.db.part"}
    {"level":"info","ts":"2025-04-21T12:18:30.407142+0900","logger":"client","caller":"v3@v3.5.21/maintenance.go:212","msg":"opened snapshot stream; downloading"}
    {"level":"info","ts":"2025-04-21T12:18:30.407162+0900","caller":"snapshot/v3_snapshot.go:73","msg":"fetching snapshot","endpoint":"192.168.131.12:2379"}
    {"level":"info","ts":"2025-04-21T12:18:30.432538+0900","logger":"client","caller":"v3@v3.5.21/maintenance.go:220","msg":"completed snapshot read; closing"}
    {"level":"info","ts":"2025-04-21T12:18:30.480983+0900","caller":"snapshot/v3_snapshot.go:88","msg":"fetched snapshot","endpoint":"192.168.131.12:2379","size":"168 kB","took":"now"}
    {"level":"info","ts":"2025-04-21T12:18:30.481039+0900","caller":"snapshot/v3_snapshot.go:97","msg":"saved","path":"mysnapshot.db"}
    Snapshot saved at mysnapshot.db
    $ ETCDCTL_API=3 etcdctl snapshot restore ./mysnapshot.db \
      --name node3 \
      --initial-cluster node1=http://192.168.0.8:2380,node2=http://192.168.0.9:2380,node3=http://192.168.0.10:2380 \
      --initial-cluster-token new-etcd-cluster \
      --initial-advertise-peer-urls http://192.168.0.10:2380 \
      --skip-hash-check \
      # ...
    
    Deprecated: Use `etcdutl snapshot restore` instead.
    
    snapshot/v3_snapshot.go:248	restoring snapshot	{"path": "member/snap/db", "wal-dir": "node3.etcd/member/wal", "data-dir": "node3.etcd", "snap-dir": "node3.etcd/member/snap", "stack": "go.etcd.io/..." }
    membership/store.go:141	Trimming membership information from the backend...
    membership/cluster.go:421	added member	{"cluster-id": "154dfe96307df6f0", "local-member-id": "0", "added-peer-id": "3dfe6fc7fff49d22", "added-peer-peer-urls": ["http://192.168.0.8:2380"]}
    membership/cluster.go:421	added member	{"cluster-id": "154dfe96307df6f0", "local-member-id": "0", "added-peer-id": "7f846315e3b9872d", "added-peer-peer-urls": ["http://192.168.0.9:2380"]}
    membership/cluster.go:421	added member	{"cluster-id": "154dfe96307df6f0", "local-member-id": "0", "added-peer-id": "c0ea9022befd3eaa", "added-peer-peer-urls": ["http://192.168.0.10:2380"]}
    snapshot/v3_snapshot.go:269	restored snapshot	{"path": "member/snap/db", "wal-dir": "node3.etcd/member/wal", "data-dir": "node3.etcd", "snap-dir": "node3.etcd/member/snap"}
    $ ls -l
    total 0
    drwx------. 3 root root  20 Mar 17 14:48 node1.etcd
    
    $ ls -l node1.etcd/
    drwx------. 2 root root 246 Mar 17 14:41 snap
    drwx------. 2 root root 257 Mar 17 14:41 wal
    $ rm -rf $ETCD_DATA_DIR/member
    
    $ cp -r node1.etcd $ETCD_DATA_DIR/member
    
    ## Service 정의에 참조된 etcd.env 파일 내용 확인
    $ vi $OPENSQL_HOME/etc/etcd/etcd.env
    ETCD_NAME=node1
    
    ETCD_INITIAL_CLUSTER=node1=http://192.168.0.8:2380,node2=http://192.168.0.9:2380,node3=http://192.168.0.10:2380
    ETCD_INITIAL_CLUSTER_TOKEN=new-etcd-cluster
    ETCD_INITIAL_CLUSTER_STATE=new
      --initial-cluster-token new-etcd-cluster \
      --initial-advertise-peer-urls http://192.168.0.8:2380 \
    
    $ systemctl restart etcd.service

    Patroni 관리

    patronictl은 Patroni 패키지와 함께 설치되는 Python 3로 작성된 CLI (커맨드라인 인터페이스)로, Patroni 클러스터가 제공하는 REST API를 이용하여 클러스터를 관제 하거나 DCS에 접근하기 위한 기능을 제공합니다.

    PostgreSQL 클러스터의 관리와 상태 체크, 설정 값 등을 확인하기 위해 사용합니다.

    설치 확인

    patronictl은 기본적으로 Python 3 패키지 patroni 와 함께 제공됩니다. 아래와 같이 노드에 patronictl이 설치되어 있는지 여부와 버전을 확인할 수 있습니다.

    $ which patronictl 
    /usr/local/bin/patronictl
    
    $ patronictl version
    patronictl version 4.0.5


    Local Configuration File 설정하기

    Patroni 프로세스 실행 시 매개변수로 입력 받는 경로에 위치한 yml 파일로부터 읽어오는 설정 값들에 대해 설명합니다.

    Local Configuration 항목값들은 Patroni 프로세스에 SIGHUP 시그널을 보내거나 REST API 서버에 POST /reload 요청을 보내 설정 파일을 새로 읽어오도록 함으로써 갱신할 수 있습니다. 기본 템플릿 환경 구성 파일의 경로는 $OPENSQL_HOME/etc/patroni/patroni.yml 를 참조합니다. 해당 경로에 yml 파일을 생성하고 해당 파일의 내용을 수정하여 구성하고자 하는 환경에 맞게 변경합니다.

    • Patroni 클러스터의 메타 정보, etcd 연결 정보, 로깅 구성, REST API 서버 구성 및 PostgreSQL 파라미터 정보를 정의할 수 있습니다.

    • PostgreSQL 파라미터 셋은 Local Configuration 및 Global Dynamic Configuration로 설정할 수 있습니다. 중복되는 키가 있는 경우 Local Configuration의 값이 우선합니다.

    • bootstrap.dcs 항목을 정의해 아래의 Global Dynamic Configuration의 초기 구성 셋을 설정할 수 있습니다.

    • scope: 구성하고자 하는 Patroni 클러스터의 이름으로 PostgreSQL 파라미터 cluster_name 에 적용됩니다.

    • namespace: Configuration Store 내에서 사용할 키의 접두어입니다.

    • name: 해당 인스턴스 (노드) 의 이름으로 클러스터 내에서 Unique 해야하며 설정하지 않는 경우 호스트네임이 사용됩니다.

    • log.type: 로그 형식을 지정하는 항목으로 plain 과 json 두 가지 옵션을 지원합니다. json 타입 사용을 위해서는 Python 패키지 patroni[jsonlogger] 설치가 추가로 필요합니다.

    • log.format: 로그 메세지 형식을 지정하는 항목으로 Python logging 패키지의 LogRecord 모듈에서 지정하는 포맷 문자열 규칙을 따릅니다.

    • restapi.listen: Patroni REST API 서버가 바인딩 될 IPv4 주소와 포트 번호를 지정합니다.

    • restapi.connect_address: Patroni 멤버 간 통신을 위해 사용할 이 노드의 외부 식별 가능한 Rest API 서버 주소를 IPv4 주소 : 포트번호 형식으로 입력합니다. 클러스터 멤버를 조회하는 API 호출 시에도 이 값이 Parsing 되어 Host 주소로 사용됩니다.

    etcd v3 인 경우의 예시

    • etcd3.protocol: etcd3 클러스터에 접근 시 사용할 프로토콜로 http 혹은 https 를 지원합니다. http 가 기본 값으로 사용되며 https 인 경우 etcd3.cacert, etcd3.cert, etcd3.key 항목 설정이 추가로 필요합니다.

    Patroni 시작 시 노드에 PostgreSQL 데이터베이스가 초기화되지 않은 경우 이 섹션의 내용을 참조하여 데이터베이스 인스턴스를 초기화합니다. 이미 구성된 PostgreSQL 데이터베이스가 노드에 있는 경우 이 섹션의 내용 또는 추가되는 변경사항은 Patroni에 반영되지 않습니다.

    하위 항목 bootstrap.dcs 의 내용은 Patroni 클러스터를 초기화하며 DCS에 Global Dynamic Configuration 으로 저장할 값들입니다.

    • bootstrap.dcs: Patroni 클러스터 환경설정으로 초기화 시 DCS의 /<namespace>/<scope>/config 에 저장되는 Global Dynamic Configuration 셋입니다.

    • bootstrap.initdb: 데이터베이스 초기화 방법으로 initdb (기본값) 를 설정한 경우 initdb 실행 시 넘겨줄 파라미터의 배열입니다.

    PostgreSQL 데이터베이스의 시스템 파라미터, 기본 사용자, Host Based Authentication 규칙 및 데이터베이스 파라미터 등을 정의합니다. Patroni 설정값 세팅 중 Local Configuration에 해당합니다.

    지정할 수 있는 항목들은 Global Dynamic Configuration의 키 postgresql 로 지정하는 항목과 동일합니다. 같은 키가 이 파일과 Global Dynamic Configuration에 중복으로 정의되는 경우 이 파일 (즉 Local Configuration File) 에 정의되는 값이 우선합니다.

    • postgresql.listen: 해당 노드의 Patroni가 실행할 PostgreSQL 서버가 Listen할 주소를 <IP주소>:<Port번호> 형태로 입력합니다.

    • postgresql.connect_address: 다른 노드 혹은 클라이언트 어플리케이션에서 참조할 PostgreSQL의 접속 URL을 입력합니다. 클러스터 정보 및 DSN을 조회할 때 반환되는 값입니다.

    • postgresql.proxy_address: PostgreSQL 서버에 접근하기 위한 Proxy 서버가 있는 경우 필요에 따라 서비스 디스커버리를 위해 그 Proxy 서버의 URL을 입력할 수 있으며 이 값은 DCS의 클러스터 정보에 같이 저장됩니다.


    patronictl은 별도의 구성 설정을 저장하지 않으며, 매 실행 시 클러스터의 정보를 가져오기 위해 DCS 접속 URL 혹은 Patroni 접속 URL이 주어져야 합니다.

    클러스터 구성에 사용한 Configuration .yml 파일을 인자로 주어 아래와 같이 사용합니다.

    아래와 같이 Linux alias로 등록해 사용할 수도 있습니다.

    클러스터의 구성 노드와 각 노드의 접속 정보, 상태 정보를 출력합니다.

    클러스터 노드의 DSN (Data Source Name) 을 출력합니다. 별도 옵션이 주어지지 않으면 Leader 노드 접속 정보를 출력합니다.

    특정 Role을 가진 멤버에 대한 접속 정보를 출력하거나 이름으로 특정 멤버에 대한 접속 정보를 출력할 수도 있습니다.

    클러스터의 멤버 노드 중 하나의 PostgreSQL 프로세스를 재시작합니다. 클러스터 이름 (메타 정보에서 설정) 이 인자로 주어져야 하며 추가로 멤버 노드의 이름을 옵션으로 넣을 수 있습니다. 멤버 이름이 지정되지 않은 경우 모든 노드들이 한번씩 재시작됩니다.

    대화형 프롬프트를 통해 재시작 일시 (바로 재시작하는 옵션과 Timestamp를 지정하여 재시작을 스케쥴하는 기능을 제공) 를 입력하며 재시작할 PostgreSQL 서버의 버전을 확인하여 필터링하는 기능을 제공합니다.

    클러스터의 멤버 노드 중 하나의 PostgreSQL 서버를 재시작하지 않고 Configuration을 다시 불러오는 기능입니다. 클러스터 이름이 인자로 주어져야 하며 추가로 멤버 노드의 이름을 옵션으로 넣을 수 있습니다.

    대화형 프롬프트를 통해 클러스터 멤버 리로딩을 스케쥴할 지 여부를 확인합니다.

    Context 값이 internal, postmaster 인 PostgreSQL 변수 (GUC)는 Reloading 기능으로 변경할 수 없습니다. internal 변수는 서버 프로그램을 컴파일할 때 혹은 initdb 명령어로 데이터베이스를 초기화할 때 결정되는 변수로 데이터베이스 재설치 없이 변경할 수 없으며 postmaster 변수는 PostgreSQL 프로세스를 재시작해야 변경할 수 있습니다.

    클러스터에서 발생한 Failover / Switchover 이력을 조회합니다.

    특정 Role을 가진 PostgreSQL 노드에 데이터베이스 쿼리를 실행하여 결과값을 확인할 수 있습니다.

    클러스터에 발생한 장애로 Leader 노드가 없는 경우 수동으로 Failover를 실행할 수 있습니다.

    정상 동작중인 클러스터에서도 patronictl failover 명령어로 수동 Failover를 수행할 수 있습니다. 다만 정상 동작중인 클러스터에서 Leader 인스턴스를 변경하고자 하는 경우 patronictl switchover 명령어를 사용하는 것이 권장됩니다.

    PostgreSQL / Patroni Leader 노드를 Replica로 전환하고, 다른 Replica 노드 중 하나를 Leader로 승격시키는 동작입니다.

    Patroni 클러스터의 자동 Failover 기능을 중단시키고 유지보수 모드 (Maintenance Mode) 로 클러스터를 전환합니다.

    Resume 명령어로 유지보수 모드를 종료하고 클러스터의 자동 Failover 기능을 다시 활성화합니다.

    DCS를 조회하여 현재 Patroni 클러스터에 적용된 설정값들을 확인할 수 있습니다.

    DCS에 저장된 Patroni 클러스터의 동적 환경설정 (Dynamic Configuration) 값을 수정할 수 있습니다.

    로컬 사용자의 EDITOR 환경변수로 지정된 텍스트 에디터 또는 vi 를 서브프로세스로 실행하여 TTY 형태로 Configuration을 수정 후 저장할 수 있습니다.

  • 로그 타입이 plain 인 경우 위 예시와 같은 문자열로 주어져야 합니다.

  • 로그 타입이 json 인 경우 로깅 하고자 하는 항목의 배열로 주어질 수 있습니다.

  • 참고

  • log.dir: Patroni 로그를 작성할 디렉토리 경로이며, 로그 파일의 기본 보존 (Retention) 사이즈는 425 MB 입니다.

  • 언급되지 않은 항목은 아래 링크의 문서를 참조합니다.

    • 참고

  • etcd3.host: 단일 노드 etcd3 클러스터를 구성한 경우 그 노드의 etcd3 엔드포인트를 입력합니다.
  • etcd3.hosts: etcd3 클러스터의 각 노드별 엔드포인트 주소를 입력합니다.

  • postgresql.data_dir: PostgreSQL 서버의 데이터 경로. Patroni 프로세스를 실행하는 사용자가 해당 경로에 대한 접근 권한을 가지고 있어야 합니다. 해당 경로가 비어있으면 Patroni 프로세스 실행 시 initdb 동작이 같이 실행됩니다.

  • postgresql.bin_dir: PostgreSQL 실행 바이너리 pg_ctl, initdb, postgres 등이 위치한 경로를 지정합니다.

  • postgresql.config_dir: PostgreSQL 설정 파일 postgresql.conf 을 보관할 디렉토리 경로. 기본값은 data_dir 값과 동일합니다.

  • postgresql.pg_hba: Patroni가 생성할 pg_hba.conf (PostgreSQL의 기본 호스트 기반 인증 설정) 파일에 작성할 아이템들을 입력합니다. PostgreSQL 파라미터 hba_file 이 사용자 지정 값으로 설정되어 있으면 이 항목은 무시됩니다.

  • postgresql.parameters: PostgreSQL 데이터베이스 파라미터입니다. 키-값 형태로 입력하며 postgresql.conf 파일을 생성할 때 이용됩니다.

  • scope: batman
    #namespace: /service/
    name: postgresql0
    log:
      type: plain
      format: "[%(asctime)s] [%(module)s] [%(levelname)s]: %(message)s"
      dir: /etc/patroni/logs
    restapi:
      listen: 0.0.0.0:8008
      connect_address: 192.168.0.100:8008
    etcd3:
      protocol: http
      # host: 192.168.0.100:2379
      hosts:
      - 192.168.0.1:2379
      - 192.168.0.2:2379
      - 192.168.0.3:2379
    bootstrap:
      # This section will be written into Etcd:/<namespace>/<scope>/config after initializing new cluster
      # and all other cluster members will use it as a `global configuration`.
      # WARNING! If you want to change any of the parameters that were set up
      # via `bootstrap.dcs` section, please use `patronictl edit-config`!
      dcs:
        ttl: 30
        loop_wait: 10
        retry_timeout: 10
        maximum_lag_on_failover: 1048576
    #    primary_start_timeout: 300
    #    synchronous_mode: false
        #standby_cluster:
          #host: 127.0.0.1
          #port: 1111
          #primary_slot_name: patroni
        slots:
          barman:
            type: physical
        postgresql:
          use_pg_rewind: true
          use_slots: true
          parameters:
    #        wal_level: hot_standby
    #        hot_standby: "on"
            max_connections: 100
            max_worker_processes: 8
    #        wal_keep_segments: 8
    #        max_wal_senders: 10
    #        max_replication_slots: 10
    #        max_prepared_transactions: 0
    #        max_locks_per_transaction: 64
    #        wal_log_hints: "on"
    #        track_commit_timestamp: "off"
    #        archive_mode: "on"
    #        archive_timeout: 1800s
    #        archive_command: mkdir -p ../wal_archive && test ! -f ../wal_archive/%f && cp %p ../wal_archive/%f
    #      recovery_conf:
    #        restore_command: cp ../wal_archive/%f %p
    
      # some desired options for 'initdb'
      initdb:  # Note: It needs to be a list (some options need values, others are switches)
      - encoding: UTF8
      - data-checksums
    postgresql:
      listen: 0.0.0.0:5432
      connect_address: 192.168.0.100:5432
      proxy_address: 127.0.0.1:6432  # The address of connection pool (e.g., pgbouncer) running next to Patroni/Postgres. Only for service discovery.
      #data_dir: data/postgresql0
      data_dir: /var/lib/pgsql/16/data
      bin_dir: /usr/pgsql-16/bin
    #  config_dir:
      pgpass: /tmp/pgpass0
      authentication:
        replication:
          username: patroni_repl
          password: patroni_repl
        superuser:
          username: postgres
          password: zalando
        rewind:  # Has no effect on postgres 10 and lower
          username: patroni_rewind
          password: patroni_rewind
      pg_hba:
      # For kerberos gss based connectivity (discard @.*$)
      - local all all trust
      - host replication patroni_repl 192.168.0.0/24 trust
      - host replication patroni_repl 127.0.0.1/32 trust
      - host all all 0.0.0.0/0 md5
      - host all barman 192.168.0.0/24 trust
      - host replication streaming_barman 192.168.0.0/24 trust
      parameters:
        log_line_prefix: '%m [%r] [%u] [%a]'
        archive_command: 'barman-wal-archive node4 pg %p'
        archive_mode: 'true'
        wal_level: 'replica'
    $ patronictl list
    2024-10-29 17:20:56,603 - WARNING - Listing members: No cluster names were provided 
    
    $ patronictl list opensql
    Error: Can not find suitable configuration of distributed configuration store
    Available implementations: etcd, etcd3, kubernetes
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml list
    
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+ 
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   | 
    +-------------+-------------+---------+-----------+----+-----------+------------------------+ 
    | postgresql0 | 192.1.1.218 | Replica | streaming | 16 |         0 |                        | 
    +-------------+-------------+---------+-----------+----+-----------+------------------------+ 
    | postgresql1 | 192.1.1.236 | Replica | streaming | 16 |         0 | failover_priority: 150 | 
    |             |             |         |           |    |           | nofailover: false      | 
    +-------------+-------------+---------+-----------+----+-----------+------------------------+ 
    | postgresql2 | 192.1.1.238 | Leader  | running   | 16 |           |                        | 
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    $ alias pctl='patronictl -c $OPENSQL_HOME/etc/patroni.yml'
    
    $ echo 'alias pctl="patronictl -c $OPENSQL_HOME/etc/patroni.yml"' >> ~/.bashrc
    
    $ pctl list
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | streaming | 16 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Replica | streaming | 16 |         0 | failover_priority: 150 |
    |             |             |         |           |    |           | nofailover: false      |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Leader  | running   | 16 |           |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    $ patronictl list
    2024-10-29 15:36:15,282 - WARNING - Listing members: No cluster names were provided
    
    ## 테이블 형태로 조회 (기본 옵션)
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml list
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | streaming | 16 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Replica | streaming | 16 |         0 | failover_priority: 150 |
    |             |             |         |           |    |           | nofailover: false      |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Leader  | running   | 16 |           |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    
    ## JSON 형태로 조회
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml list -f json
    [{"Cluster": "opensql", "Member": "postgresql0", "Host": "192.1.1.218", "Role": "Leader", "State": "running", "TL": 17}, {"Cluster": "opensql", "Member": "postgresql1", "Host": "192.1.1.236", "Role": "Replica", "State": "streaming", "TL": 17, "Lag in MB": 0, "Tags": {"nofailover": false, "failover_priority": 150}}, {"Cluster": "opensql", "Member": "postgresql2", "Host": "192.1.1.238", "Role": "Replica", "State": "streaming", "TL": 17, "Lag in MB": 0}]
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml topology
    + Cluster: opensql (7364637789542980847) +-----------+----+-----------+---------------------------------------------+
    | Member        | Host        | Role    | State     | TL | Lag in MB | Tags                                        |
    +---------------+-------------+---------+-----------+----+-----------+---------------------------------------------+
    | postgresql2   | 192.1.1.238 | Leader  | running   | 21 |           |                                             |
    | + postgresql0 | 192.1.1.218 | Replica | streaming | 21 |         0 |                                             |
    | + postgresql1 | 192.1.1.236 | Replica | streaming | 21 |         0 | {failover_priority: 150, nofailover: false} |
    +---------------+-------------+---------+-----------+----+-----------+---------------------------------------------+
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml dsn
    host=192.1.1.238 port=5432
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml dsn -r replica
    host=192.1.1.218 port=5432
    
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml dsn -m postgresql1
    host=192.1.1.236 port=5432
    $ pctl restart <cluster_name>
    $ pctl restart opensql postgresql0
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | streaming | 16 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Replica | streaming | 16 |         0 | failover_priority: 150 |
    |             |             |         |           |    |           | nofailover: false      |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Leader  | running   | 16 |           |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    When should the restart take place (e.g. 2024-10-31T12:16)  [now]:
    ## now를 입력하면 바로 재시작
    
    Are you sure you want to restart members postgresql0? [y/N]:
    
    Restart if the PostgreSQL version is less than provided (e.g. 9.5.2)  []:
    
    Success: restart on member postgresql0
    $ pctl reload <cluster_name>
    $ pctl reload opensql
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | streaming | 16 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Replica | streaming | 16 |         0 | failover_priority: 150 |
    |             |             |         |           |    |           | nofailover: false      |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Leader  | running   | 16 |           |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    Are you sure you want to reload members postgresql0, postgresql1, postgresql2? [y/N]:
    Reload request received for member postgresql0 and will be processed within 10 seconds
    Reload request received for member postgresql1 and will be processed within 10 seconds
    Reload request received for member postgresql2 and will be processed within 10 seconds
    $ pctl history
    +----+------------+------------------------------+----------------------------------+-------------+
    | TL |        LSN | Reason                       | Timestamp                        | New Leader  |
    +----+------------+------------------------------+----------------------------------+-------------+
    |  1 |   26875256 | no recovery target specified | 2024-05-03T14:20:28.841738+09:00 | postgresql2 |
    |  2 |  213072680 | no recovery target specified | 2024-05-03T14:45:37.945208+09:00 | postgresql1 |
    |  3 |  213101064 | no recovery target specified | 2024-05-03T14:46:14.686504+09:00 | postgresql2 |
    |  4 |  805306528 | no recovery target specified | 2024-05-28T17:44:02.808722+09:00 | postgresql1 |
    |  5 |  855638176 | no recovery target specified | 2024-05-28T17:54:12.358175+09:00 | postgresql2 |
    |  6 | 1879048352 | no recovery target specified | 2024-07-30T15:58:10.527327+09:00 | postgresql0 |
    |  7 | 1895825568 | no recovery target specified | 2024-07-30T15:58:52.408275+09:00 | postgresql2 |
    |  8 | 2013266080 | no recovery target specified | 2024-08-12T10:00:38.641449+09:00 | postgresql2 |
    |  9 | 2030043296 | no recovery target specified | 2024-08-12T10:04:16.370771+09:00 | postgresql2 |
    | 10 | 2046820512 | no recovery target specified | 2024-08-12T10:05:07.178679+09:00 | postgresql2 |
    | 11 | 2063597728 | no recovery target specified | 2024-08-12T10:47:06.368795+09:00 | postgresql2 |
    | 12 | 2080374944 | no recovery target specified | 2024-08-12T10:50:59.596850+09:00 | postgresql2 |
    | 13 | 2332033184 | no recovery target specified | 2024-10-28T13:41:28.936064+09:00 | postgresql1 |
    | 14 | 2348810400 | no recovery target specified | 2024-10-28T16:04:27.587725+09:00 | postgresql0 |
    | 15 | 2365587616 | no recovery target specified | 2024-10-28T16:16:32.842442+09:00 | postgresql2 |
    +----+------------+------------------------------+----------------------------------+-------------+
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml query -U postgres --password -c "SELECT VERSION();"
    Password: 
    version
    PostgreSQL 14.13 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44), 64-bit
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml failover
    
    Current cluster topology
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | streaming | 20 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Leader  | running   | 20 |           | failover_priority: 150 |
    |             |             |         |           |    |           | nofailover: false      |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Replica | streaming | 20 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    Candidate ['postgresql0', 'postgresql2'] []: postgresql2
    Are you sure you want to failover cluster opensql, demoting current leader postgresql1? [y/N]: y
    2024-11-04 16:36:12.20700 Successfully failed over to "postgresql2"
    + Cluster: opensql (7364637789542980847) --------+----+-----------+------------------------+
    | Member      | Host        | Role    | State   | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | running | 20 |         0 |                        |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Replica | stopped |    |   unknown | failover_priority: 150 |
    |             |             |         |         |    |           | nofailover: false      |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Leader  | running | 20 |           |                        |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml switchover
    
    Current cluster topology
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Leader  | running   | 19 |           |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Replica | streaming | 19 |         0 | failover_priority: 150 |
    |             |             |         |           |    |           | nofailover: false      |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Replica | streaming | 19 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    Primary [postgresql0]: postgresql0
    Candidate ['postgresql1', 'postgresql2'] []: postgresql1
    When should the switchover take place (e.g. 2024-11-04T17:34 )  [now]: now
    Are you sure you want to switchover cluster opensql, demoting current leader postgresql0? [y/N]: y
    2024-11-04 16:35:07.34291 Successfully switched over to "postgresql1"
    + Cluster: opensql (7364637789542980847) --------+----+-----------+------------------------+
    | Member      | Host        | Role    | State   | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | stopped |    |   unknown |                        |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Leader  | running | 19 |           | failover_priority: 150 |
    |             |             |         |         |    |           | nofailover: false      |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Replica | running | 19 |         0 |                        |
    +-------------+-------------+---------+---------+----+-----------+------------------------+
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml pause
    Success: cluster management is paused
    
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml list
    + Cluster: opensql (7364637789542980847) ----------+----+-----------+------------------------+
    | Member      | Host        | Role    | State     | TL | Lag in MB | Tags                   |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql0 | 192.1.1.218 | Replica | streaming | 25 |         0 |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql1 | 192.1.1.236 | Replica | streaming | 25 |         0 | failover_priority: 150 |
    |             |             |         |           |    |           | nofailover: false      |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
    | postgresql2 | 192.1.1.238 | Leader  | running   | 25 |           |                        |
    +-------------+-------------+---------+-----------+----+-----------+------------------------+
     Maintenance mode: on
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml resume
    Success: cluster management is resumed
    $ patronictl -c $OPENSQL_HOME/etc/patroni.yml show-config
    loop_wait: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      parameters:
        archive_command: barman-wal-archive node4 pg %p
        archive_mode: 'true'
        authentication_timeout: '200'
        log_line_prefix: '%m [%r] [%u] [%a]'
        max_connections: '250'
        wal_level: replica
        wal_receiver_timeout: '30000'
      pg_hba:
      - local all all trust
      - host replication patroni_repl 192.1.1.218/26 trust
      - host replication patroni_repl 127.0.0.1/32 trust
      - host all all 0.0.0.0/0 md5
      - host all barman 192.1.1.218/26 trust
      - host replication streaming_barman 192.1.1.218/26 trust
      use_pg_rewind: true
      use_slots: true
    retry_timeout: 10
    slots:
      barman:
        type: physical
    ttl: 30
    $ pctl edit-config
    loop_wait: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      parameters:
        archive_command: barman-wal-archive node4 pg %p
        archive_mode: 'false'
        authentication_timeout: '500'
        log_line_prefix: '%m [%r] [%u] [%a]'
        max_connections: 500
        wal_level: replica
        wal_receiver_timeout: '30000'
      pg_hba:
      - local all all trust
      - host replication patroni_repl 192.1.1.218/26 trust
      - host replication patroni_repl 127.0.0.1/32 trust
      - host all all 0.0.0.0/0 md5
      - host all barman 192.1.1.218/26 trust
      - host replication streaming_barman 192.1.1.218/26 trust
      use_pg_rewind: true
      use_slots: true
    retry_timeout: 10
    slots:
      barman:
        type: physical
    ttl: 45
    ~
    ~
    ~
    ~
    "/tmp/opensql-config-m332oce3.yaml" 25L, 683C

    클러스터 메타 정보

    로깅

    Rest API

    DCS (etcd)

    Bootstrapping

    PostgreSQL

    사용법

    클러스터 정보 확인

    토폴로지 출력

    DSN 출력

    재시작

    리로딩

    이력 조회

    쿼리

    Failover

    Switchover

    Pause / Resume

    Config 확인

    Config 수정

    https://docs.python.org/3.6/library/logging.html#logrecord-attributes
    https://patroni.readthedocs.io/en/latest/ENVIRONMENT.html#log