이더리움 밸리데이터 유연하고 안전하게 배포하기

Darron Park
A41 Tech Blog
Published in
30 min readJul 19, 2024

A41의 이더리움 인프라 전환 기술 포스트는 총 5편으로 구성되어 있습니다. 각 글은 빠르게 증가하는 이더리움 검증인의 수에 대응하며 2024년 상반기에 A41이 마주한 문제들과 이를 해결한 전략들에 대해 소개합니다.

Author: Darron Park (@darron1217)

확장 가능한 블록체인 인프라를 위한 A41 테크팀의 고민과 선택들에 대한 기술 포스트 목록입니다. 다른 편을 읽고 싶으시다면 아래의 리스트를 확인해주세요.

  1. 쿠버네티스로 확장 가능한 이더리움 밸리데이터 인프라 구축하기
  2. 이더리움 밸리데이터 유연하고 안전하게 배포하기
  3. 효율적인 모니터링과 신속한 장애 대응이 곧 퍼포먼스다
  4. 밸리데이터 인프라의 외부 모니터링 시스템 구축하기
  5. 이더리움 밸리데이터 키, 리워드와 패널티 추적하기
이더리움 밸리데이터 유연하고 안전하게 배포하기

들어가며

이번 포스트에서는 이더리움 밸리데이터 노드의 안전하고 유연한 배포를 위한 전략을 다룹니다. 베어메탈 쿠버네티스 클러스터를 성공적으로 구축하였지만, 안정적이고 효율적인 노드 운영을 위해서는 배포 과정에서 달성해야 할 다양한 목표들이 있습니다. 특히, 더블사이닝 방지와 자동화된 배포 과정에서의 인적 실수 최소화는 중요한 과제입니다.

이번 글에서는 Helm과 ArgoCD를 활용한 배포 전략과 슬래싱 방지 전략을 소개하며, 이를 통해 어떻게 유연하면서도 안전한 배포 환경을 구축했는지 소개하겠습니다.

풀고자 하는 문제

앞선 글에서 언급된 쿠버네티스로 해결할 수 있는 문제들 중 배포 파이프라인 구축을 통해 해결하는 문제는 다음과 같습니다.

확장성과 유지보수의 문제

운용해야하는 이더리움 밸리데이터 키가 빠르게 늘어나며 서버 자원 또한 급격히 증가했습니다. 머신에 직접 접근하는 기존 방식으로는 수많은 서버에 보안 정책 또는 소프트웨어 업그레이드를 적용하는 등 유지보수 작업에 오랜 시간이 소요되고 휴먼에러가 발생할 수 있는 가능성도 자연스레 높아졌습니다.

쿠버네티스로 전환을 결정하며 선택할 수 있는 기술의 폭이 넓어졌고 Infrastrucure as Code(IaC)를 도입하여 배포 정의 파일 수정/추가만으로 유지보수 및 확장을 가능하게 만들고자 했습니다.

배포 리소스 템플릿화 및 의존성 관리를 위한 Helm

쿠버네티스로 IaC를 달성하려면 yaml 형식의 리소스 정의 파일을 많이 작성해야 합니다. 리소스 정의 파일을 템플릿으로 관리하고, 배포 의존성을 명확히 표현하기 위해 Helm을 활용하기로 결정했습니다.

Helm은 쿠버네티스를 위한 패키지 매니징 소프트웨어 입니다. Chart 형태로 애플리케이션을 패키징하여 쿠버네티스 리소스를 템플릿으로 관리할 수 있게 해줍니다. 배포시에는 values.yaml로 리소스를 정의하며, 하나의 values 파일에서 여러 Chart를 활용할 수 있기 때문에 의존성 관리가 수월해집니다.

Continuous Delivery(CD) 및 배포 가시성 개선을 위한 ArgoCD

인프라 규모가 커졌을 때 가장 직접적으로 와닿는 문제는 배포 가시성 확보의 어려움입니다. 예를 들어 직접 작업자가 서버에 SSH로 접근하여 작업한다면 커맨드라인 히스토리로만 변경 기록이 남습니다. Helm으로 배포하는 경우에도 Chart를 만들고 values 파일을 git으로 관리해도 git 코드가 항상 실제 인프라 상태와 동기화되어있다는 보장이 없으면 현재 상태를 정확히 알기 어렵습니다. 그렇기에 ArgoCD를 활용해 배포 상태를 언제나 코드와 동기화 하기로 결정하였습니다.

서버 접근 권한 (과다부여) 문제

최소 권한 원칙 설명 (출처: Cloudflare Learning)

‘최소 권한 원칙’은 말 그대로 작업자가 역할을 수행하면서 필요한 항목에만 접근할 수 있어야 한다는 개념입니다. 예를 들어 소프트웨어 버전 업그레이드만 수행하고자 할 때, 서버 전체에 접근을 허용한다면 업그레이드 수행을 위한 권한 외에 머신 및 컨테이너의 설정에도 접근할 수 있어 필요 이상의 권한을 갖게 되는 것입니다.

이 문제는 쿠버네티스의 Role-Based Access Control(RBAC)과 Infrastructure as Code(IaC)로 해결할 수 있습니다. 이 중 배포 프로세스에서는 IaC로 서버 접근 권한 문제를 해결합니다.

모든 이더리움 노드는 Helm 차트로 작성되고 values로 배포되기 때문에 작업자는 Git에서 values 파일을 수정하거나 추가하여 Pull Request(PR)를 보내야 합니다. PR을 운영 브랜치에 병합하는 것은 Continuous Integration(CI) 파이프라인과 내부 승인 절차를 거쳐 엄격하게 테스트 및 검토 후 실행됩니다.

유연성이 필요한 아키텍처

이더리움 밸리데이터 키 1개를 안정적으로 운영하기 위해 최소 5개의 컨테이너가 필요합니다. Execution Client(EC), Beacon Client(BC), Validator Client(VC), Remote Signer, MEV-Boost가 기본적으로 필요하며, Lido 또는 Mantle과 같은 LSP의 오퍼레이터는 LSP에서 요구하는 써드파티 컴포넌트들을 추가로 운영해야 합니다. 각각의 클라이언트는 상호 의존성을 가지고 있으며, 한 지점이라도 실패하게 된다면 밸리데이터는 정상적으로 의무를 수행할 수 없습니다. 그렇기에 장애 상황에서 빠르게 의존하는 컴포넌트를 변경할 수 있는 유연성이 필요합니다.

저희는 앞서 언급한 3가지 문제를 Helm, ArgoCD를 적용하여 개선하였습니다. 이제 Helm 차트를 어떻게 설계하였는지, 배포 과정에서 고려한 점은 무엇인지, ArgoCD를 통해 어떻게 CD를 도입하였는지 소개하겠습니다.

Helm 아키텍처 오버뷰

Helm 차트를 구성하는 과정 전반은 이더리움 프로토콜의 특성에 많은 영향을 받았습니다. 아키텍처를 소개하기 전 이더리움 밸리데이터로써 고려해야 할 사항들을 살펴보겠습니다.

이더리움 클라이언트 다변화가 필요한 이유

이더리움 밸리데이터를 운영할때 어떤 클라이언트를 사용할지 고르는 것은 중요한 문제입니다.

이더리움 클라이언트 종류 (출처: https://clientdiversity.org/)

프로토콜 관점에서, 버그가 있는 밸리데이터 클라이언트의 비율이 66%를 넘어가게 되면 포크가 발생하고 정직한 밸리데이터들이 슬래싱되기에 하나의 클라이언트가 66% 이상의 점유율을 갖지 못하게 하는 것은 중요합니다. (클라이언트 다변화의 중요성에 관한 자세한 내용은 이더리움 재단의 Client diversity 포스트를 참고해 주세요)

밸리데이터 관점에서, 하나의 클라이언트에 의존하면 심각한 버그가 발생했을때 리워드 패널티를 받거나 심각하게는 슬래싱 패널티를 받게 될 수 있습니다. 일어날 확률이 매우 희박한 일이지만 이러한 리스크의 영향범위를 제한하기 위해 하나의 클라이언트에만 의존하지 않도록 구성이 필요합니다.

A41 클라이언트 비율 현황 (출처: 2024 Q1 Lido Validator Metrics)

그렇기에 A41은 Lido, Mantle 오퍼레이터로서 클라이언트 다변화를 기본 설계 원칙으로 삼고 있습니다. 이는 곧 여러 클라이언트 구현체간 호환성을 고려해야한다는 뜻이기도 합니다. 아래 글에선 Execution-Beacon 조합 간 의존성 문제 및 Beacon-VC-Signer 간 의존성 문제는 왜 생기며 어떻게 해결하였는지 소개하겠습니다.

BN-VC간 의존성이 생기는 이유

A41은 합의 레이어에 Lighthouse와 Teku를 사용 중이며 두 클라이언트는 호환성 문제가 종종 발생합니다. Lighthouse와 Teku 클라이언트를 서로 조합하여 사용했을 때 겪었던 대표적 이슈들을 간략하게 적어보겠습니다.

Lighthouse BN + Teku VC : SSZ 블록 전송 헤더 누락 문제

SSZ 블록 전송 헤더 누락 문제는 Beacon Spec에 정의된대로 구현한 Lighthouse와 그렇지 못한 Teku간 발생했던 호환성 버그입니다. Block에 SSZ encoding을 사용할 때 Eth-Consensus-Version 헤더를 포함하여 전송해야 하지만, Teku VC는 해당 헤더를 누락하여 발생한 문제였습니다. 현재는 고쳐진 버그입니다.

Teku BN + Lighthouse VC : Time discrepancy 에러

Time discrepancy 에러는 Teku BN에 Lighthouse VC를 연결하면 간헐적으로 발생합니다.

Feb 04 17:45:10.003 ERRO Time discrepancy with beacon node       endpoint: <http://beacon-node:5052/>, local_slot: 7546725,...

슬롯이 연속으로 비어있을때 Teku BN과 Lighthouse VC간 현재 슬롯넘버를 처리하는 기준이 달라서 발생하는 false-positive 에러입니다. 실제 합의 과정에 영향을 주는 오류는 아니지만, 오탐은 실제 장애 여부를 판단하기 어렵게 만듭니다.

Lighthouse, Teku repo에 등록된 호환성 이슈들 (출처: github)

이 외에도 도플갱어 탐지 호환성 이슈 등 Beacon Spec이 변경/추가되면서 발생하는 호환성 문제가 종종 발견됩니다. Spec이 고정되지 않고 지속적으로 변경/추가되는 프로토콜의 특성상 클라이언트 구현체간 스펙 불일치 및 버그는 쉽게 일어날 수 있습니다. 그렇기에 BN과 VC는 동일한 구현체로 구성하는 것이 높은 업타임을 유지하고, 예기치 못한 장애를 예방하는 데 유리합니다.

조합을 쉽게, Umbrella Chart 적용

Umbrella Chart 구조 설명 그림

Umbrella Chart는 최상위 차트의 values.yaml 에 global configuration을 정의하고, charts/ 서브 디렉토리에 차트를 정의하는 구조입니다.

Umbrella Chart 구조의 장점 몇 가지를 소개드리자면:

  1. 모듈화와 재사용성: Umbrella Chart는 여러 하위 차트(subcharts)를 포함할 수 있어 모듈화된 구성을 지원합니다. 이를 통해 각 하위 차트를 재사용 가능하게 하고, 유지보수가 용이해집니다.
  2. 의존성 관리: 각 하위 차트의 의존성을 명확하게 정의하고 관리할 수 있습니다. 이는 의존성 충돌을 방지하고, 배포 시 일관성을 유지하는 데 유리합니다.
  3. 확장성과 유연성: 새로운 하위 차트를 추가하거나 기존 하위 차트를 수정하기 쉽습니다. 이를 통해 시스템을 유연하게 확장할 수 있습니다.
  4. 통합 테스트와 배포: 통합된 차트로 구성되어 있어, 배포 전에 전체 애플리케이션을 통합 테스트할 수 있습니다. 이는 배포의 신뢰성을 높이는 데 기여합니다.
  5. 구성 관리: 전체 애플리케이션의 구성을 중앙에서 관리할 수 있어, 일관된 설정을 유지하고 변경 사항을 쉽게 반영할 수 있습니다.

앞서 살펴봤듯 BN과 VC가 긴밀한 의존성을 가지고 있기에 Umbrella Chart 구조를 적용하여 EC & BN & MEV-Boost 조합, BN & VC 조합, VC & Signer 조합 등 조합별 배포 및 통합 테스트가 가능하게 되었습니다.

이제 실제로 Umbrella Chart를 적용한 저희 Ethereum Chart의 디렉토리 구조를 살펴보겠습니다.

.
├── Chart.lock
├── Chart.yaml
├── charts
│ ├── geth
│ ├── lighthouse
│ ├── mev-boost
│ ├── nethermind
│ ├── teku
│ └── web3signer
├── values
│ ├── lido-holesky
│ ├── mantle-holesky
└── values.yaml

charts/ 디렉토리에는 클라이언트별 미리 정의된 리소스 템플릿이 들어있습니다. Lighthouse와 Teku의 경우 Beacon Client면서 Validator Client 역할을 하기 때문에 mode 값을 통해 하나의 차트에서validatorbeacon 모드를 스위칭 가능하게 하여 재사용성을 높였습니다.

values/ 디렉토리에는 최상위 values.yaml 을 상속받을 values를 정의합니다. 여기에 작성된 values 파일은 ArgoCD를 통해 실제 인프라에 배포됩니다. 배포가 많아질수록 values 파일도 늘어나게 되어 일정한 규칙에 따라 values 파일을 묶고, 관리하는 것 또한 중요하게 되었습니다.

운영 환경을 안전하게, 네임스페이스 격리와 네이밍 규칙

밸리데이터는 주로 LSP, 네트워크 단위로 묶입니다. 각 LSP는 고유한 컴포넌트들을 가지고 있고, 네트워크에 따라 보안 수준 또한 다르기 때문에 환경을 분리시킬 필요가 있습니다. LSP 및 네트워크간 영역을 명확히 구분하기 위해 저희는 namespace를 활용하였습니다. 네임스페이스는 {lsp_name}-{network_id} 형태로 네이밍하여 배포 시 혼선이 없도록 하였습니다. 예) lido-holesky

values 디렉토리는 ./values/{namespace}/{client_set}-{number}.yaml 패턴으로 네임스페이스 디렉토리 하위에 실제 배포를 위한 values 파일이 위치하게 됩니다.

./values
├── lido-holesky
│ ├── geth-lighthouse-01.yaml
│ ├── validator-01.yaml
│ └── validator-02.yaml
└── mantle-holesky
└── nethermind-teku-01.yaml
├── validator-01.yaml
└── validator-02.yaml

네임스페이스와 배포 네이밍 규칙에 따라 values 파일을 생성하였습니다. 디렉토리를 살펴보는 것 만으로도 어떤 클라이언트가 얼마나 배포되었는지를 쉽게 유추할 수 있습니다.

일괄작업을 쉽게, Values 상속

values 상속 구조

helm은 ‘차트 value’ — ‘루트 디렉토리 value’ — ‘타겟 value’ 순서로 value를 읽어들입니다. (여기서 타겟 value는 helm install시 --values= 파라미터로 지정하는 value를 뜻합니다)

예를 들어 geth-lighthouse-01.yaml을 helm install로 배포한다면 아래 명령어를 실행하게 됩니다.

helm install lido-holesky-geth-lighthouse-01 . --values=./values/lido-holesky/geth-lighthouse-01.yaml -n lido-holesky

위 명령어를 실행하면 helm은 ./charts/geth/values.yaml./values.yaml./values/lido-holesky/geth-lighthouse-01.yaml 순서로 value를 읽어들여 앞서 정의된 value들을 오버라이딩하며 최종 병합된 value 값을 릴리즈로 적용합니다. 이러한 상속 구조를 활용하여 클라이언트 버전 업그레이드 등 일괄 작업이 필요한 변수는 하위 차트의 values.yaml 또는 루트 디렉토리 values.yaml 에 정의하여 인프라 변경 사항을 일괄 적용할 수 있게 됩니다.

Execution-Beacon을 Stateful 하게

Execution Client(EC)와 Beacon Node(BN)는 의존관계입니다. BN은 EC 엔진에 의존하며 jwt 토큰 인증을 활용합니다. Execution-Beacon 조합은 대용량의 State DB를 유지하고 동기화 해야하기 때문에 스토리지에 강한 의존성을 가지고 있습니다. 따라서 Deployment가 아닌 StatefulSet으로 배포하여 최초에 배치된 머신에 인스턴스가 유지될 수 있도록 하였습니다.

최초 배포 시에는 용도에 따라 알맞은 배포가 이루어질 수 있도록 nodeSelector를 활용합니다. 새로운 머신이 딜리버리되면 클러스터 관리자는 머신을 새로운 워커노드로 추가합니다. 이때 워커노드에 라벨이 부여되는데, 아래와 같이 데이터센터 정보, 프로젝트, 클라이언트 종류 등이 명시됩니다. 각 클라이언트 구현체의 특성에 맞게 알맞은 리소스를 할당하기 위함입니다.

a41.io/datacenter: seocho
a41.io/project: lido-holesky
a41.io/client: geth-lighthouse

Execution과 Beacon 클라이언트를 띄울때 위 정보를 nodeSelector 로 활용하여 Execution-Beacon 클라이언트가 원하는 노드에 항상 고정적으로 배치될 수 있도록 합니다.

Helm 릴리즈 배포 예시

4. 밸리데이터를 유연하게

밸리데이터 리워드 & 패널티 기준

밸리데이터는 다른 밸리데이터들과 일치하는 투표를 할 때, 블록을 제안할 때, sync committee에 참여할 때 리워드를 받습니다. 밸리데이터 리워드 가중치는 크게 5가지로 구성되어 있습니다.

  1. Source Vote: 밸리데이터가 올바른 소스 체크포인트에 적시에 투표했는지
  2. Target Vote: 밸리데이터가 올바른 타겟 체크포인트에 적시에 투표했는지
  3. Head Vote: 밸리데이터가 올바른 헤드 블록에 대해 적시에 투표했는지
  4. Sync Committee Reward: 밸리데이터가 Sync Committee에 참여했는지
  5. Proposer Reward: 밸리데이터가 올바른 슬롯에 블록을 제안했는지

이러한 가중치들은 밸리데이터가 단순히 의무를 이행하는 것 이상으로 적시에 정확하게 의무를 이행하도록 장려합니다. 그렇기에 가중치 값을 높게 유지할 수 있도록 가용성을 높이는 것 또한 중요합니다.

높은 가용성이 필요한 실제 환경

Execution, Beacon Node는 네트워크 상태에 따라 종종 싱크 문제가 발생합니다. 또한 만에 하나 하드웨어 문제로 워커노드가 다운된다면 Execution, Beacon, 그리고 Validator Client까지 다운타임이 발생할 수 있습니다. 워커노드의 예기치 못한 장애로 인해 VC가 다운되면 다른 워커노드로 파드가 재배치되는데, 이때 VC가 어느 노드에 재배치 되어도 문제없게 설계하는 것이 중요합니다. VC가 재배치 될 때 발생할 수 있는 더블 사이닝과 VC 설정 유실 방지 대책을 마련해야 한다는 뜻이기도 합니다.

유연한 설계의 복병, Validator Definition

대부분의 Validator Client는 Validator Definition 파일(Teku는 key-manager config로 지칭)을 가지고 있습니다. 이 Validator Definition 파일은 밸리데이터 키별 Fee Recipient, Signing Method 등을 정의하기 위한 파일입니다.

Validator Definition 관리는 크게 2가지 어려움이 있습니다.

첫째는, 설정파일 및 API 인터페이스의 차이가 존재한다는 점입니다. A41은 Consensus 레이어에 Lighthouse와 Teku를 사용 중이며 두 구현체간 차이가 존재합니다. 그렇기에 Validator Definition을 일관된 포멧으로 관리하는 것은 배포 효율화의 중요한 과제였습니다.

둘째는, 이 파일(들)은 Client가 실행되는 동안 동적으로 변경될 수 있다는 점 입니다. 모든 Validator Client는 Key Manager API를 통해 Key를 Import 할 수 있게 구현되었기 때문입니다. Key를 Import 한다는 것은 앞서 언급한 Validator Definition 파일이 생성/변경된다는 뜻입니다.

컨테이너에서 파일을 수정하고 유지해야한다는 건, 곧 스토리지와 의존성이 생긴다는 뜻이고 local-path 스토리지를 사용해야하는 현 클러스터 구조상 Validator Client가 처음 떴던 워커노드에 다시 떠야만 Validator Definition 파일을 유지할 수 있다는 뜻이기도 합니다. 우리는 컨테이너 외부에 Validator Definition 정보를 저장하고 컨테이너 시작시 Validator Definition을 불러오는 방식으로 이 문제를 해결하였습니다.

기존 방식, init script

기존에는 일관된 포멧을 위해 Key Manager API를 활용했습니다. Key Manager API는 Validator Definition을 하나의 인터페이스로 관리할 수 있게 해주는 스펙입니다. 덕분에 우리는 Client 종류에 구애받지 않고 하나의 스크립트로 Validator Definition을 POST 요청으로 설정해 왔습니다.

init script 동작 흐름

그러나 이 구조는 2가지 한계가 있었습니다.

첫째로, 발행된 API token 관리의 어려움입니다. Key Manager API는 VC의 서명 경로를 수정할수도 있는 보안 측면에서 매우 중요한 엔드포인트입니다. 따라서 VC 초기화 때 생성되는 api-token을 헤더에 담고 있는 요청만을 허용합니다. 이 말인즉슨, init script에서 POST 요청을 보내기 위해선 필연적으로 VC의 스토리지 접근이 필요해진다는 뜻입니다.

둘째로, 현재 Validator Definition의 상태를 명확하게 보장할 수 없다는 것입니다. Validator Definition은 Mutable하기 때문에 IaC를 달성하기 어렵게 만들었습니다.

Keymanager API를 사용하지 않으면서도 Validator Definition 형상 관리 가능한 구조가 필요하게 되었고, helm의 템플릿 기능을 활용하여 이를 직접 구현하기로 했습니다.

Immutable Validator Definition

현재 사용 중인 방식은 Validator Definition을 Immtuable 한 부분과 Mutable한 부분으로 나누어 Immutable 한 부분은 helm value로 관리하고, 동적으로 변경이 필요한 경우 변경 사항을 DB에 기록하여 컨테이너 재시작 이후에도 상태를 유지하는 방식입니다.

이 방식은 앞서 언급한 2가지 문제를 해결함과 동시에 아래와 같은 부가적인 이점을 얻게 됩니다.

  • Validator Client 구현체의 Validator Definition 파일 형식에 의존하지 않을 수 있게 됩니다. 즉, Validator Client 종류를 변경할 때 별도의 마이그레이션 작업이 필요하지 않게 되어 클라이언트 버그에 더 빠르게 대응할 수 있게 됩니다.
  • Validator Client에 정의된 pubkey 목록을 values 파일에서 확인하여 pubkey 중복 여부를 코드 레벨에서 검증할 수 있습니다.

Validator Definition을 정의하고 배포하는 과정은 다음과 같습니다. 아래는 helm value로 정의한 validator config 예시입니다. validator config는 대상 클라이언트의 Validator Definition 형식으로 변환되기 전 필요한 값들을 정의하는 value입니다.

validatorConfig:
type: web3signer
externalSignerUrl: "http://{{ .Release.Name }}-signer-0:9020"
defaultFeeRecipient: "0x634b31F29C3724F39A0FC67e5ddc536E04454136"
graffiti: "A41"
keys:
- pubkey: "9295e87d46772da6e236ed0630367a8fecbfff8064da795d092ae79c7b569a2e4e74abc4c26610a9aed86a37e284e72f"
feeRecipient: "0x634b31F29C3724F39A0FC67e5ddc536E04454136"
- pubkey: "b9b914b929095f733bea8e577ae96b63a17e013242e0399335a94fd0a7cdede512afada0955300f68b444ce8cd08d966"
feeRecipient: "0x634b31F29C3724F39A0FC67e5ddc536E04454136"
...

Helm의 강력한 템플릿 기능으로 validatorConfig 를 읽어 대상 클라이언트 종류에 맞는 Validator Definition 파일형식으로 변환합니다.

Lighthouse 템플릿 예시:

{{- range .Values.validatorConfig.keys }}
- enabled: true
voting_public_key: "0x{{ .pubkey }}"
suggested_fee_recipient: {{ default $.Values.validatorConfig.defaultFeeRecipient .feeRecipient | quote }}
graffiti: {{ $.Values.validatorConfig.graffiti }}
type: {{ $.Values.validatorConfig.type }}
url: {{ tpl $.Values.validatorConfig.externalSignerUrl $ | quote }}
{{- end }}

Lighthouse의 Validator Definition 기본 경로는 .lighthouse/{network}/validators/validator_definition.yml 이며, 아래와 같은 형태로 저장됩니다.

- enabled: true
voting_public_key: "0x9295e87d46772da6e236ed0630367a8fecbfff8064da795d092ae79c7b569a2e4e74abc4c26610a9aed86a37e284e72f"
suggested_fee_recipient: "0x634b31F29C3724F39A0FC67e5ddc536E04454136"
graffiti: A41
type: web3signer
url: "<http://external-signer:9020>"

클라이언트 구현체의 특성에 맞게 템플릿을 수정하면 Lighthouse, Teku 외에도 다른 Validator Client로의 전환이 가능합니다. Validator Client의 버그로 인해 의무를 수행할 수 없는 상황에 대처할 수 있는 또 다른 옵션이 생긴 것입니다.

5. 밸리데이터를 안전하게

A41 인프라팀은 Can’t be evil 원칙에 기반하여 인프라를 설계합니다.

이더리움 PoS에서 슬래싱 패널티는 아래와 같은 상황에서 적용됩니다.

  • 동일한 슬롯에 대해 서로 다른 두 개의 블록을 제안하고 서명하는 경우
  • 다른 블록을 “둘러싸고 있는” 블록을 증명하는 경우
  • 동일한 블록에 대해 두 명의 후보를 증명하는 “이중 투표”의 경우

각 기준은 ‘이더리움 밸리데이터 키, 리워드와 패널티 추적하기’ 글에서 자세히 다루고 있습니다.

슬래싱의 위험성

슬래싱 패널티가 발생하면 지분의 1/32 만큼이 즉시 소각되며, 밸리데이터의 상태는 slashed_exiting 으로 전환됩니다. 또한 약 36일간 밸리데이터는 액티브 셋에서 제외되며 exit queue에 들어갑니다. 이 기간동안 밸리데이터는 리워드를 받지 못하지만, 매 에포크마다 의무에 참여하지 못하여 발생하는 패널티(8,000 GWei)는 부과받습니다. ~0.07ETH 의 패널티가 추가로 발생하는 셈입니다.

슬래싱은 자산의 손실이라는 점에서 위험하기도 하지만, 밸리데이터의 평판에 치명적이기 때문에 더더욱 발생하면 안 되는 이벤트입니다. 그렇기에 배포 프로세스 설계 과정에서 슬래싱을 철저히 방지하는 것은 가장 중요했습니다.

슬래싱 방지 전략

슬래싱 방지 전략을 수립하는 과정에서 Kiln의 Ethereum anti-slashing strategies 포스트의 많은 영감을 받아 스위스 치즈 모델에 기반한 멀티레이어 슬래싱 방지 전략을 적용하였습니다.

스위스 치즈 모델은 여러 방어층을 치즈 슬라이스에 비유하여, 각 방어층이 결함과 취약점을 가질 수 있으며, 이 치즈의 구멍들이 일직선으로 맞물릴 때 사고가 발생한다는 위험 관리 모델입니다. 슬래싱 방지 시스템을 여러 레이어로 구성하여 일부 레이어가 슬래싱 방지에 실패하더라도 슬래싱을 막을 수 있게 설계 하였습니다.

슬래싱 발생 가능한 경로와 대비책

1단계: Gitops 검증

1단계는 Gitops 검증 단계로 코드를 브랜치에 병합하는 과정에서 슬래싱 패널티로 이어질 수 있는 가능성을 탐지하고 차단합니다. IaC를 적용하여 모든 인프라 변경 사항은 PR로 제출되고 승인되는 과정을 거칩니다. CI 파이프라인을 통해 PR 단계에서 pubkey 및 signer 접근이 중복되었는지 검증할 수 있습니다.

CI/CD 파이프라인 예시

Gitops 레이어에서는 validator definition에 기재된 pubkey의 중복 여부와 서명을 담당하는 Signer 접근 중복을 검증합니다.

2단계: Doppelganger Detection

INFO Listening for doppelgangers     doppelganger_detecting_validators: 1, service: notifier

대부분의 VC는 도플갱어 탐지 기능을 지원하며, A41 인프라팀은 도플갱어 탐지 기능을 지원하는 클라이언트만을 사용 중입니다. 도플갱어 탐지 방식은 클라이언트 구현체마다 약간의 차이는 있지만, 보통 2~3 epoch 동안 동일한 밸리데이터키가 제출한 서명이 있는지를 확인합니다. 이 기간에 서명이 발견되지 않으면 다음 epoch부터 메세지에 서명을 시작합니다. 도플갱어 탐지 기능은 아래 파라미터를 통해 활성화됩니다.

  • Lighthouse: --enable-doppelganger-protection
  • Teku: --doppelganger-detection-enabled=true

도플갱어 탐지 기능에 온전히 의지하는 것은 좋은 패턴이 아닙니다. BN 네트워크 동기화 문제로 인해 도플갱어를 탐지하지 못하게 될 가능성도 있기 때문입니다. (라이트하우스 문서 참고)

3단계: Local Slashing Protection (VC)

Lighthouse와 Teku를 포함한 대부분의 VC는 slashing protection이 기본으로 활성되어 있습니다. Lighthouse는 sqlite 형식으로, Teku는 yaml 형식으로 서명 정보를 local 스토리지에 저장하고 활용합니다.

이러한 local slashing DB는 단일 VC 인스턴스에서 발생하는 더블 사이닝을 막아줍니다. 하지만 여러 VC 간 더블 사이닝이 발생하는 것을 막을 수 없기 때문에 또 하나의 레이어를 필요로 하게 되었습니다.

4단계: Global Slashing Protection (Signer)

Global Slashing Protection DB

인프라팀이 운영하는 모든 키를 대상으로 슬래싱 보호를 하는 것은 어쩌면 가장 중요한 일입니다. 앞선 단계들은 모두 장애 복구 작업 같은 특수한 상황에서는 서명 중복을 탐지하기 어렵기 때문입니다.

web3signer는 슬래싱 보호 데이터베이스 연결을 지원합니다. 가용성이 높은 단일 DB에 연결하여 모든 web3signer 인스턴스가 하나의 DB를 조회하여 서명이 중복되지 않았는지 검증할 수 있게 구성하였습니다.

밸리데이터 키 접근 제어

벨리데이터 키를 안전하게 지키는 것은 보안적으로 매우 중요합니다. 키가 한번 외부에 유출되면 해당 키는 더블사이닝 공격에 노출되는 것이므로, 더 이상 사용할 수 없게 됩니다. 그렇기에 벨리데이터키에 접근하는 컨테이너는 외부와 철저히 격리된 환경에서 구동되는것이 이상적입니다. 저희는 이를 달성하기 위해 서명을 담당하는 Remote Signer 인스턴스를 별도로 운영하고 있으며, 이는 밸리데이터 키에 접근하는 유일한 컨테이너입니다.

밸리데이터 키의 생성, 주입, 폐기 과정은 전적으로 최고 보안 담당자만 수행해야 하는 작업입니다. 클러스터 내에서 안전하게 오퍼레이션을 수행하기 위해 아래와 같은 정책으로 키를 생성하고 관리하고 있습니다.

  • 최고 보안 담당자만 밸리데이터 키를 생성, 접근할 수 있습니다.
  • 최고 보안 담당자 외엔 Remote Signer 컨테이너에 직접 접근할 수 없습니다.

6. 지속적 배포를 위한 ArgoCD

IaC를 통해 얻을 수 있는 큰 이점 중 하나는 배포 자동화입니다. 저희 팀은 운영 브랜치의 코드를 인프라의 실제 상태와 지속적으로 동기화하기 위해 ArgoCD를 활용하였습니다.

ArgoCD 웹 인터페이스 예시 (출처: ArgoCD docs)

ArgoCD는 쿠버네티스를 위한 선언적 Gitops Continuous Delivery(CD) 도구 입니다. Git 저장소를 단일 진실의 출처로 사용하여 인프라 및 애플리케이션 구성 관리를 자동화하는 GitOps 패턴에 따라 설계된 배포 도구입니다. 앞서 작성한 Helm 차트를 Git에서 읽어와 쿠버네티스 클러스터에 반영하고, 지속적으로 변경 사항을 추적하는 역할을 수행합니다. Git에 정의한 리소스가 잘 동기화되었는지, 동기화에 실패하였다면 어떤 컴포넌트가 왜 실패하였는지 시각적으로 보여줍니다.

저희 팀은 CD를 도입하면서도 실제 운영환경에 코드가 즉시 배포되는 부담을 완화하기 위해 배포 시점은 제어하고자 했습니다. 따라서 CD를 통해 대부분 자동화하되, 실질적인 반영은 제어할 수 있게 아래와 같이 요구사항을 명확히 하였습니다.

ArgoCD 요구사항은 아래와 같이 정의하였습니다.

  1. 새로운 Application 리소스 배포하려면 명시적인 행동(커밋)이 수반되어야 한다
  2. 의도적으로 Application 리소스와 배포된 리소스를 삭제하려면 명시적인 행동(커밋)이 수반되어야 한다
  3. ArgoCD가 삭제되면 ArgoCD의 리소스는 삭제되더라도 클러스터에 배포된 리소스는 남아야 한다
  4. AppProject 삭제는 연관된 Application 리소스에 영향이 없어야 한다
  5. ApplicationSet이 삭제되면 Application은 삭제되더라도 클러스터에 배포된 리소스는 남아야 한다

ArgoCD는 AppProject, ApplicationSet 리소스를 정의하여 Git 저장소에 있는 파일을 스캔하고 App으로 등록하는 Sync 기능을 제공합니다.

  • ApplicationSet에 반영되는 옵션
kind: ApplicationSet
spec:
syncPolicy:
applicationsSync: sync
preserveResourcesOnDeletion: true
  • Application에 반영되는 옵션
kind: ApplicationSet
spec:
template:
syncPolicy:
automated:
prune: true
syncOptions:
- ApplyOutOfSyncOnly=true

마치며

Helm Chart 구성과 배포 설계를 중심으로 이더리움 밸리데이터 노드 운영 시 고려한 지점을 소개하였습니다. 새 배포 시스템 도입 이후 휴먼에러가 현저히 줄어들고, 멀티레이어 슬래싱 보호 도입 이후 인프라 변경 및 업그레이드 작업에 대한 스트레스가 줄었습니다.

이제껏 배포를 효율적이고 안전하게 만들었다면, 배포된 자원을 꼼꼼히 모니터링 하고 장애 상황을 빠르고 정확하게 팀에게 전달하는 것 또한 중요합니다. 다음 포스트에서는 모니터링 시스템 구축하여 모든 자원을 효율적인 모니터링하고 대응할 수 있게 된 과정을 소개하겠습니다.

--

--