Merkle 트리 손상 확인하기

Merkle 트리 손상 확인하기

Vault Enterprise 복제는 Merkle 트리를 사용해 클러스터 상태를 유지하고, 클러스터 상태를 Merkle 루트 해시로 굴립니다. 클러스터에서 데이터가 업데이트되거나 제거되면 Merkle 트리도 업데이트됩니다. 이 문서에서 나중에 설명하는 특정 상황에서 Merkle 트리가 손상될 수 있습니다.

출처: 문서

본문

손상 유형 (Types of corruption)

Merkle 트리 손상은 트리의 여러 지점에서 발생할 수 있습니다.

  • 복합 루트 손상 (Composite root corruption)
  • 하위 트리 루트 손상 (Subtree root corruption)
  • 페이지 및 하위 페이지 손상 (Page and subpage corruption)

Merkle 트리 손상 진단

Vault Enterprise 1.15.0+, 1.14.3+ 또는 1.13.7+ 버전을 실행 중이라면 /sys/replication/merkle-check API 엔드포인트를 사용해 클러스터가 Merkle 트리 손상을 겪고 있는지 확인하는 데 도움을 받을 수 있습니다. 다음 섹션에서 merkle-check 엔드포인트가 감지할 수 있는 증상과 손상 원인의 일부 세부사항을 배우게 됩니다.

참고: merkle-check 엔드포인트는 Merkle 트리가 손상될 수 있는 모든 방식을 감지할 수는 없다는 점을 유의하세요.

또한 merkle-check 엔드포인트를 조회하고 출력을 해석하는 방법, 그리고 손상 진단에 도움이 되는 일부 Vault CLI 명령에 대해 배울 것입니다.

연속 Merkle 차이와 동기화 루프

Merkle 트리 손상의 한 징후는 Vault 로그가 WAL(스트리밍 write-ahead log)에 대한 지속적이고 해결되지 않는 상태로 연속적인 Merkle 차이(merkle-diff)와 동기화(merkle-sync) 작업을 표시할 때 발생합니다.

이 증상의 알려진 원인은 HA(고가용성) Vault 클러스터 내에서 스플릿 브레인(split brain) 상황이 발생할 때입니다. 이 경우 리더가 데이터를 스토리지에 기록하는 동안 리더십을 잃습니다. 한편 새 리더가 선출되어 이전 리더가 변경 중인 데이터를 읽거나 씁니다. 리더십 이전 중 이전 리더가 쓴 데이터는 손실되어 일관성 없는 Merkle 트리 상태가 될 수 있습니다.

스플릿 브레인 문제를 잠재적으로 나타낼 수 있는 가장 일반적인 증상 두 가지가 다음 섹션에 자세히 설명되어 있습니다.

Merkle 차이가 델타를 생성하지 않음

델타(delta)가 없는 merkle-diff 작업은 충돌하는 Merkle 트리 페이지를 나타냅니다. 두 클러스터가 두 트리에서 정확히 같은 데이터를 보유하고 있음에도 루트 해시가 일치하지 않습니다.

다음 예시 로그는 손상된 트리로 인한 문제와 이전 merkle-sync 작업 이후 프라이머리에서 쓰기가 없음을 나타내는 성능 복제 세컨더리의 항목을 보여줍니다.

vault [INFO]  .perf-sec.core0.core: non-matching guard, exiting
vault [TRACE] .perf-sec.core0.core: finished client WAL streaming
vault [INFO]  .perf-sec.core0.replication: no matching WALs available
vault [DEBUG] .perf-sec.core0.replication: starting merkle diff
vault [TRACE] .perf-sec.core0.core: wal context done
vault [TRACE] .perf-sec.core0.core: checking conflicting pages
vault [TRACE] .perf-pri.core0.core: serving conflicting pages
vault [DEBUG] .perf-pri.core0.replication.index.perf: creating merkle state snapshot: generation=4
vault [DEBUG] .perf-pri.core0.replication.index.perf: removing state snapshot from cache: generation=4
vault [INFO]  .perf-sec.core0.replication: requesting WAL stream: guard=8acf94ac
vault [TRACE] .perf-sec.core0.core: starting client WAL streaming
vault [TRACE] .perf-sec.core0.core: receiving WALs
vault [TRACE] .perf-pri.core0.core: starting serving WALs: clientID=e16930a6-7d24-6924-41fe-aa8beb90b1b2
vault [TRACE] .perf-pri.core0.core: streaming from log shipper done: clientID=e16930a6-7d24-6924-41fe-aa8beb90b1b2
vault [TRACE] .perf-pri.core0.core: internal wal stream stop channel fired: clientID=e16930a6-7d24-6924-41fe-aa8beb90b1b2
vault [TRACE] .perf-pri.core0.core: stopping serving WALs: clientID=e16930a6-7d24-6924-41fe-aa8beb90b1b2
vault [INFO]  .perf-sec.core0.core: non-matching guard, exiting
vault [TRACE] .perf-sec.core0.core: finished client WAL streaming
vault [INFO]  .perf-sec.core0.replication: no matching WALs available
vault [TRACE] .perf-sec.core0.core: wal context done
vault [DEBUG] .perf-sec.core0.replication: starting merkle diff
vault [TRACE] .perf-sec.core0.core: checking conflicting pages
vault [TRACE] .perf-pri.core0.core: serving conflicting pages

예시 로그 출력에서 성능 세컨더리 클러스터의 FSM(유한 상태 머신)은 merkle-diff 모드로 들어가며, 프라이머리 클러스터에서 충돌하는 페이지를 가져오려고 시도합니다. diff 결과는 비어 있으며, 이는 즉시 stream-wals 모드로 전환되고 merkle-sync 작업을 건너뛰는 것으로 나타납니다.

로그에서 더 나아가 성능 세컨더리 클러스터는 Merkle 트리의 불일치를 프라이머리 클러스터와 조정하려고 다시 즉시 merkle-diff 모드로 들어갑니다. 이 루프는 Merkle 트리 손상으로 인해 해결되지 않은 채 계속됩니다.

해결되지 않는 merkle-sync

diff 작업이 충돌하는 데이터를 드러내고 sync 작업이 이를 가져오지만, 이후에도 Vault가 지속적인 스트리밍 WAL 모드로 들어갈 수 없으면 일치하지 않는 Merkle 루트 상태(non-matching Merkle roots) 를 나타냅니다.

다음 서버 로그 스니펫에서 merkle-diff는 비어 있지 않은 페이지 충돌 목록을 반환하고 merkle-sync는 해당 키를 가져옵니다. 그러면 FSM은 stream-wals 상태로 전환합니다. 이 전환 직후 FSM은 다시 merkle-diff로 전환하고 일치하지 않는 가드 오류를 반환합니다.

vault [INFO]  perf-sec.core0.core: non-matching guard, exiting
vault [TRACE] perf-sec.core0.core: finished client WAL streaming
vault [INFO]  perf-sec.core0.replication: no matching WALs available
vault [TRACE] perf-sec.core0.core: wal context done
vault [DEBUG] perf-sec.core0.replication: transitioning state: state=merkle-diff
vault [DEBUG] perf-sec.core0.replication: starting merkle diff
vault [TRACE] perf-sec.core0.core: checking conflicting pages
vault [TRACE] perf-pri.core0.core: serving conflicting pages
vault [DEBUG] perf-pri.core0.replication.index.perf: creating merkle state snapshot: generation=3
vault [TRACE] perf-sec.core0.core: fetching subpage hashes
vault [TRACE] perf-pri.core0.core: serving subpage hashes
vault [DEBUG] perf-pri.core0.replication.index.perf: removing state snapshot from cache: generation=3
vault [DEBUG] perf-sec.core0.replication: transitioning state: state=merkle-sync
vault [DEBUG] perf-sec.core0.replication: waiting for operations to complete before merkle sync
vault [DEBUG] perf-sec.core0.replication: starting merkle sync: num_conflict_keys=4
vault [DEBUG] perf-sec.core0.replication: merkle sync debug info: local_keys=[] remote_keys=[] conflicting_keys=["logical/67bf7b33-734e-f909-86e5-a7e69af0979f/junk9", "logical/67bf7b33-734e-f909-86e5-a7e69af0979f/junk7", "logical/67bf7b33-734e-f909-86e5-a7e69af0979f/junk8", "logical/67bf7b33-734e-f909-86e5-a7e69af0979f/junk6"]
vault [DEBUG] perf-sec.core0.replication: transitioning state: state=stream-wals
vault [INFO]  perf-sec.core0.replication: requesting WAL stream: guard=0c556858
vault [TRACE] perf-sec.core0.core: starting client WAL streaming
vault [TRACE] perf-sec.core0.core: receiving WALs
vault [TRACE] perf-pri.core0.core: starting serving WALs: clientID=6afbce30-67c5-bb15-6eda-001140d33275
vault [TRACE] perf-pri.core0.core: streaming from log shipper done: clientID=6afbce30-67c5-bb15-6eda-001140d33275
vault [TRACE] perf-pri.core0.core: internal wal stream stop channel fired: clientID=6afbce30-67c5-bb15-6eda-001140d33275
vault [TRACE] perf-pri.core0.core: stopping serving WALs: clientID=6afbce30-67c5-bb15-6eda-001140d33275
vault [INFO]  perf-sec.core0.core: non-matching guard, exiting
vault [TRACE] perf-sec.core0.core: finished client WAL streaming
vault [INFO]  perf-sec.core0.replication: no matching WALs available
vault [DEBUG] perf-sec.core0.replication: transitioning state: state=merkle-diff
vault [DEBUG] perf-sec.core0.replication: starting merkle diff
vault [TRACE] perf-sec.core0.core: wal context done
vault [TRACE] perf-sec.core0.core: checking conflicting pages
vault [TRACE] perf-pri.core0.core: serving conflicting pages

merkle-check 엔드포인트 사용하기

다음 예시는 curl로 merkle-check 엔드포인트를 조회하는 방법을 보여줍니다.

참고: merkle-check 엔드포인트는 인증됩니다. 엔드포인트를 조회하려면 엔드포인트 문서에 자세히 설명된 기능(capability)을 가진 Vault 토큰이 필요합니다.

프라이머리 클러스터 확인

$ curl $VAULT_ADDR/v1/sys/replication/merkle-check

예시 출력:

{
  "request_id": "d4b2ad1a-6e5f-7f9e-edfe-558eb89a40e6",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "merkle_corruption_report": {
      "corrupted_root": false,
      "corrupted_tree_map": {
        "1": {
          "corrupted_index_tuples_map": {
            "5": {
              "corrupted": false,
              "subpages": [
                28
              ]
            }
          },
          "corrupted_subtree_root": false,
          "root_hash": "DyGc6rQTV9XgyNSff3zimhi3FJM=",
          "tree_type": "replicated"
        },
        "2": {
          "corrupted_index_tuples_map": null,
          "corrupted_subtree_root": false,
          "root_hash": "EXmRTdfYCZTm5i9wLef9RQqyLCw=",
          "tree_type": "local"
        }
      },
      "last_corruption_check_epoch": "2023-09-11T11:25:59.44956-07:00"
    }
  }
}

merkle_corruption_report 스탠자는 Merkle 트리 손상에 대한 정보를 제공합니다. 복합 트리 또는 하위 트리 루트 해시가 손상되면 Vault는 corrupted_rootcorrupted_subtree_root 필드 값을 true로 설정합니다. Vault는 복합 트리와 하위 트리 둘 다의 루트 해시에서 손상을 감지하면 필드 값을 true로 설정합니다.

corrupted_tree_map 필드는 복제(고 replicated) 및 로컬 하위 트리를 포함한 하위 트리의 손상을 식별합니다. 복제 트리는 맵에서 숫자 1로, 로컬 트리는 숫자 2로 인덱싱됩니다. tree_type 하위 필드도 어떤 트리에 손상된 페이지가 있는지 보여줍니다. 복제 하위 트리는 재해 복구 또는 성능 복제 세컨더리 클러스터에 복제되는 정보를 저장합니다. Vault 구성, 시크릿 엔진·인증 메서드 구성, KV 시크릿 같은 복제 항목을 포함합니다. 로컬 하위 트리는 로컬 마운트, 임대, 토큰 같은 로컬 클러스터와 관련된 정보를 저장합니다.

트리의 페이지 또는 하위 페이지 내 손상이 발생하면 corrupted_index_tuples_map에 페이지 번호와 함께 손상된 하위 페이지 번호 목록이 포함됩니다. 페이지 해시가 손상되면 corrupted 필드가 true로 설정되고, 그렇지 않으면 false로 설정됩니다.

{
  "request_id": "d4b2ad1a-6e5f-7f9e-edfe-558eb89a40e6",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "merkle_corruption_report": {
      "corrupted_root": false,
      "corrupted_tree_map": {
        "1": {
          "corrupted_index_tuples_map": {
            "5": {
              "corrupted": false,
              "subpages": [
                28
              ]
            }
          },
          "corrupted_subtree_root": false,
          "root_hash": "DyGc6rQTV9XgyNSff3zimhi3FJM=",
          "tree_type": "replicated"
        },
        "2": {
          "corrupted_index_tuples_map": null,
          "corrupted_subtree_root": false,
          "root_hash": "EXmRTdfYCZTm5i9wLef9RQqyLCw=",
          "tree_type": "local"
        }
      },
      "last_corruption_check_epoch": "2023-09-11T11:25:59.44956-07:00"
    }
  }
}

예시 출력에서 복제 트리는 5페이지의 28번 하위 페이지에만 손상되었습니다. 이는 페이지 해시, 복제 트리 해시, 복합 트리 해시가 모두 올바르다는 것을 의미합니다.

세컨더리 클러스터 확인

Merkle check 엔드포인트는 재해 복구(DR) 세컨더리 클러스터의 Merkle 트리 손상 상태에 대한 정보를 출력합니다. 이 엔드포인트에 접근하려면 DR 운영 토큰이 필요합니다. 다음은 엔드포인트를 조회하는 예시 curl 명령입니다.

$ curl $VAULT_ADDR/v1/sys/replication/dr/secondary/merkle-check

손상 진단용 CLI 명령

잠재적 Merkle 트리 손상에 대해 복합 루트, 하위 트리 루트, 페이지 해시, 하위 페이지 해시 네 계층을 확인해야 합니다. jq 도구와 함께 다음 예시 Vault CLI 명령을 사용해 손상을 진단할 수 있습니다.

복합 루트 손상

복합 트리 루트가 손상되었는지 확인하려면 다음 같은 Vault 명령을 사용하세요.

$ vault write sys/replication/merkle-check \
    -format=json | jq -r '.data.merkle_corruption_report.corrupted_root'

응답이 true이면 Merkle 트리가 복합 루트에서 손상된 것입니다.

하위 트리 루트 손상

하위 트리 루트가 손상되었는지 확인하려면 다음 같은 Vault 명령을 사용하세요.

$ vault write sys/replication/merkle-check -format=json \
    | jq -r '.data.merkle_corruption_report.corrupted_tree_map[] | select(.corrupted_subtree_root==true)'

응답이 true이면 하위 트리가 손상된 것입니다.

페이지 또는 하위 페이지 손상

페이지 또는 하위 페이지가 손상되었는지 확인하려면 다음 같은 Vault 명령을 사용하세요.

$ vault write sys/replication/merkle-check -format=json \
    | jq -r '.data.merkle_corruption_report.corrupted_tree_map[] | select(.corrupted_index_tuples_map!=null)'

응답이 비어 있지 않은 맵이면 페이지 또는 하위 페이지가 하나 이상 손상된 것입니다. 명령 출력의 비어 있지 않은 맵 예시는 다음과 같습니다.

{
  "corrupted_index_tuples_map": {
    "23": {
      "corrupted": false,
      "subpages": [
        234
      ]
    }
  },
  "corrupted_subtree_root": false,
  "root_hash": "A5uW54VXDM4jUryDkxN8Vauk8kE=",
  "tree_type": "replicated"
}

이 예시에서는 페이지 번호 23이 반환되었지만 corrupted 필드가 false이므로 페이지는 손상되지 않았습니다. 그러나 234번 하위 페이지가 손상된 하위 페이지로 나열되어 있습니다.

손상된 하위 페이지를 찾으려면 Vault가 해당 부모 페이지도 기록해야 합니다. 각 페이지는 256개의 하위 페이지를 포함하고 이러한 인덱스는 페이지마다 반복되기 때문입니다. 손상된 하위 페이지 없이 전체 페이지만 손상되는 것도 가능합니다. 이 경우 페이지 맵의 corrupted 필드가 true이고 하위 페이지는 나열되지 않습니다.

주의사항 및 고려사항

일반적으로 재인덱싱 전에 merkle-check 엔드포인트를 참고할 것을 권장합니다. 재인덱싱은 시간이 많이 걸리고 다운타임을 유발할 수 있으므로 그 과정이 유용할 것인지 확인해야 합니다.

더 알아보기 (Learn more)