이번에 회사에서 ESXi 위의 VM에 PTP에 준하는 정밀도를 가진 시간 동기화를 요구하여, 개념적으로만 알고 있었던 ESXi의 PTP 구현을 직접 테스트 해 볼 기회를 얻었다.
이번 포스팅에서는 ESXi의 PTP 구현체가 어떤 구성요소를 가지고 있는지, 또 어떻게 구성 파라미터를 조작할 수 있는지 문서화되지 않은 분석 결과를 포함하여 자세하게 다룰 것이다.
면책 조항: 이 문서의 워크어라운드는 VMware에서 공식적으로 지원하지 않으며, 이것을 사용함으로써 발생하는 모든 문제의 책임은 사용자에게 있음
시간 동기화 기술에 익숙하지 않은 사람들에게는 PTP (Precision Time Protocol)이 생소하게 들릴 수도 있을 것이다.
PTP는 일반적으로 많이 사용하는 NTP를 개선하여, 정밀하게 Path delay를 보정할 수 있게 만든 프로토콜로, IEEE 1588이라 불리기도 한다.
[그림 1. PTP의 Path Delay 계산 메커니즘]
PTP의 동작 메커니즘은 이 글의 작성 목적을 벗어나므로, 자세히 다루지는 않겠지만, 기본적으로 위와 같은 Delay Request - Delay Response 메커니즘을 통해 Master 와 Slave 사이의 Path delay를 계산하고, 정밀하게 시간을 맞출 수 있게 된다.
VMware에서는 Telco들이 VNF를 ESXi에 올리기 위해 PTP 기반 시간 동기화를 요구하면서, vSphere 7.0.2 부터 PTP 기반 시간 동기화 기술을 지원하게 되었다.
[그림 2. ESXi의 PTP 지원을 소개하는 공식 블로그 (출처)]
VMware에서는 ESXi에서 PTP를 사용하려면 Intel의 X710 또는 E810 NIC을 사용할 것을 요구한다. 나중에 살펴보겠지만, 이 제약사항에는 그럴만한 이유가 있으며, 문서화되지 않은 지원 NIC를 찾는 방법도 같이 알아볼 것이다.
VMware에서 공식적으로 지원하는 NIC의 목록은 Compatibility Guide에서 확인할 수 있다.
IO Devices > ESXi Hardware Timestamp based PTP를 선택하면, vSphere 8.0 U3 기준으로 3개의 NIC이 지원됨을 알 수 있다.
[그림 3. vSphere에서 공식적으로 지원하는 NIC]
세 NIC 모두 E810 칩셋을 사용하므로, vSphere 8에서 공식적으로 지원하는 NIC은 Intel E810만 있다고 받아들여야 할 것 같다.
한편, ESXi에서는 두 종류의 PTP 시간 동기화 방법을 제공한다.
하나는 VMKernel Adapter와 Software Timestamping을 사용하는 것이고, 다른 하나는 물리 NIC을 Passthrough 하여 Hardware Timestamping을 사용하는 것이다.
[그림 4. PCI-E Passthrough 기반 PTP 구성]
소프트웨어 타임스탬핑은 정밀 시간 동기화라는 PTP의 목표를 달성하기 어렵기 때문에, 일반적으로는 PCI-E Passthrough를 통한 PTP 구현을 사용하게 될 것이다.
좀 더 자세한 설정을 확인하기 위해 esxcli가 어떤 명령어를 제공하는지 확인해 보기로 했다.
ESXCLI는 esxcli system ptp
네임스페이스를 통해 PTP 관련 구성을 변경할 수 있다.
그러나 ESXCLI에서 변경할 수 있는 것 중 GUI에 없는 것은 domainNumber
와 calibration_delay
뿐이다.
좀 더 자세히 구성을 들여다보기 위해서는 configstorecli
를 사용할 수 있다. 이전 포스팅에서도 언급했듯이, vSphere 7.0.3부터 ESXi는 모든 구성 파일들을 Config Store라는 일종의 DB에 통합하기 시작했다. 어떤 파일들이 Config Store에 들어갔는지는 다음 링크에서 확인 가능하다.
ESXCLI는 VMOMI를 통해 Config Store를 수정하기 때문에, ESXi의 설정을 변경한다는 것은 'Config Store에 어떤 값을 집어넣을 수 있는가?' 라는 물음에 답하는 것이 된다.
configstorecli schema get -c esx -g system -k system_time
명령어를 통해 이 질문의 답을 얻을 수 있다.
이 명령어는 Config Store에 저장할 수 있는 NTP와 PTP 설정의 형식을 보여주는데, 8.0.2 기준 Config Store에 저장되는 PTP 관련 구성은 아래의 4개 뿐이다.
log_level, calibarion_delay, ports, domain_number
7.0.3과 달리 domain_number
와 calibration_delay
를 설정할 수 있는 것에 감사해야 할까?
안타깝지만, PTP 파라미터는 네트워크 구조와 사용 목적에 따라 크게 달라질 수 있기 때문에, 이것만으로는 PTP를 제대로 구성하기 어렵다.
7.0.2까지는 /etc/ptpd.conf
파일을 수정하는 것으로 대응할 수 있었던 것 같지만, 7.0.3 부터는 데몬이 Config Store의 구성을 참조하게 되어 그것도 불가능해졌다.
[그림 5. PTP Domain 설정을 변경할 수 없게 되어 화가 난 익명의 사용자]
실제로 이 문제로 불만을 토로하는 사람들을 찾을 수 있었고, vSphere 8에서 일부 불만사항은 해결되었지만 우리 회사의 요구사항에는 부합하지 못했다.
즉, VMware는 공식적으로 이 이상의 구성 변경을 지원하지 않는다.
나는 여기서 분석을 그만두는 대신, 더 깊게 파고들기로 했다.
ESXi의 PTP 구현체로 무엇을 할 수 있는지 파악하는 가장 좋은 방법은 로그를 보는 것이다.
vmkernel.log
와 hostd.log
를 잠깐 살펴본 뒤, PTP 서비스의 로그는 /var/run/log/trx.log
에 기록된다는 것을 알 수 있었다. 그리고 trx.log
의 내용을 통해 PTP 서비스가 CRX (Container Runtine for ESXi)에 의존한다는 것을 확인할 수 있었다.
CRX는 vSphere Pod를 위해 개발된 ESXi용 경량 리눅스 컨테이너 구현체이다. 자세한 설명은 여기에서 확인할 수 있으며, VMX 위에서 구동된다는 점이 특기할 만하다. 혹시 위에서 PTP를 설정할 때, pNIC을 Passthrough 해야 한다는 점에 위화감을 느꼈다면, 이제 그 궁금증이 해소되었을 것이다.
ESXi의 하드웨어 타임스탬프 PTP 구현체인 TRX는 VMX 위의 컨테이너에서 동작하며, VMX에 PCI-E 장치를 직접 연결하기 위해 Passthrough가 필요한 것이다.
이제 TRX가 CRX에 의존적이라는 것을 알았으니, 그걸로 무엇을 더 할 수 있을까?
crx-cli
는 ESXi에서 실행되는 컨테이너를 CLI로 관리할 수 있는 도구이다. 기본적으로 docker-cli
와 크게 다르지 않으며, 나는 이 도구를 이용해 실행중인 TRX 컨테이너 내부에서 어떤 일이 일어나는지를 직접 확인할 수 있었다.
약간의 시간과 노력을 투자하여 파악한 TRX의 실행 구조는 다음과 같다.
[그림 6. TRX의 실행 아키텍처]
CRX가 경량 리눅스 컨테이너이기 때문에, TRX 또한 리눅스의 ptp4l
과 phc2sys
를 그대로 사용할 수 있다.
추가적인 개발 없이 ESXi에서 PTP를 가장 편하게 지원할 수 있는 방법을 찾기 위해 vSphere 팀이 상당히 많은 고민을 했을 것임을 짐작할 수 있다.
그리고 위 파일들을 좀 더 들여다보니, 흥미로운 내용들을 찾을 수 있었다.
컨테이너의 /etc/trx
디렉토리에서 device.list
라는 파일을 찾을 수 있었고, 여기에서 TRX에서 지원되는 장치의 VID/DID와 드라이버 모듈을 확인할 수 있었다.
이 파일은 ESXi 호스트의 /usr/lib/vmware/trx/etc
디렉토리에서도 찾아볼 수 있다.
내용을 확인해 보면 공식 VMware Compatibility Guide와 달리 X710과 E810을 함께 지원한다는 것을 알 수 있다.
특히 이 파일에서 흥미로운 부분은 vmxnet3가 목록에 들어가 있다는 점인데, mock device 라는 언급을 보면 Nested 테스트베드에서 테스트 및 검증을 수행할 수 있도록 허용하기 위한 것으로 예상된다.
들어가기에 앞서, 이 구성은 VMware에 의해 공식적으로 지원되는 것이 아님을 밝힌다. 이 문단에서와 같이 VMware가 의도하지 않은 방식으로 ESXi를 사용하는 것은 예상치 못한 사이드 이펙트를 일으킬 수 있으며, VMware의 엔지니어는 그로 인해 발생한 장애의 서비스 요청을 거부할 수 있다.
trx-ctl
의 소스 코드를 들여다보다, 흥미로운 부분을 발견할 수 있었다.
trx-ctl
은 -c
옵션을 사용하면 trx-agent
에 임의의 문자열을 전달하게 되어 있었는데, 이건 디버깅 목적으로 내부 서비스에 임의의 설정을 적용하기에 딱 적당하지 않은가?
그래서 trx-ctl
과 trx-agent
의 코드를 좀 더 들여다 본 결과, 내가 필요로 하는 기능을 찾을 수 있었다.
이 파일은 기본적으로 존재하지 않지만, TRX가 실행될 때 이 파일이 존재한다면 trx-ctl
은 파일의 내용을 한 줄 씩 읽어들인 뒤 파싱하고, 그 값을 적절하게 trx-agent
로 전달한다.
이 파일의 문법은 다음과 같다.
<namespace>:<conf_key> <conf_value>
Namespace와 Config line은 :
로 구분되며, Config의 Key와 Value는 띄어쓰기로 구분된다.
8.0.2 기준, Namespace에는 3가지의 값이 들어갈 수 있다.
ptp4l, phc2sys, sys2vmw
실제로 /etc/vmware/trx.conf
에 문법에 맞게 설정값을 입력한 뒤, TRX 컨테이너를 실행해 보면, 다음과 같은 결과를 얻을 수 있다.
[그림 7-8. trx.conf를 이용해 TRX의 ptp4l에 임의의 설정값 적용하기]
그리고 PTP를 통한 동기화가 정상적으로 이루어지는 것도 확인할 수 있었다.
[그림 9. 임의의 설정값을 적용한 TRX의 실행 로그]
소스코드 분석 중에 발견한 문서화되지 않은 제약사항을 정리해 보았다.
이 분석 작업을 마무리하면서, 한 가지 아쉬움으로 남았던 부분이 바로 모든 ptp4l 옵션이 Global로 들어간다는 점이었다.
물론, TRX 컨테이너 내에는 네트워크 인터페이스가 하나밖에 없으므로 Global에 들어가도 별 상관은 없지만, 소스 코드에 Port별 구성을 파싱하는 루틴이 구현되어 있어 사용해 보니 초기화 중 설정 파싱 과정에서 에러가 발생했다.
[그림 10-11. TRX의 Config 파일 파싱 루틴]
trx-ctl
과 trx-agent
가 동일한 delimiter인 :
을 사용하는 것이 원인이었는데, trx-agent
에서 요구하는 문법을 맞춰주기 위해 ptp4l:port0:delay_mechanism P2P
와 같은 꼴로 파라미터를 입력하면, trx-ctl
에서 해당 라인을 파싱하는 과정에서 3개의 object를 반환하게 되어 예외가 발생하였다.
이 문제는 trx-ctl
에서 delimiter로 :
대신 =
을 사용하는 것으로 간단하게 해결할 수 있다.
디버깅 목적으로도 불필요하다고 생각되어 수정되지 않은 버그이겠지만, 다음 릴리즈에서 이 버그가 수정된다면 아주 재미있을 것 같다.