2024년 8월 21일 수요일

ZFS만으로 우분투를 구동하는 모험

 1. 저지르기

우분투가 설치 때부터 ZFS를 쓸 수 있게 지원하지만, 나는 그 한참 전부터 시스템을 굴려왔었고, ZFS를 도입할 때도 단순히 데이터 저장용으로만 ZFS를 구성했다. 중간에 OS 디스크를 교체하는 경우는 종종 있었지만 Clonezilla 같은 걸로 OS 디스크끼리만 복사하면 충분했기 때문에 ZFS 디스크들은 계속 데이터 저장용으로만 남아 있었다.

 

여차저차한 이유로, 퇴역시켰던 HP 마이크로서버를 다시 활용하겠다는 생각이 들었다. 하지만 그 사이 OS는 NVMe로 넘어온 상태였고 마이크로서버에는 NVMe는 없기 때문에 SATA로 디스크를 복사하는 게 가장 흔한 선택이었겠지만 NVMe로 넘어가면서 1테라 용량을 선택했는데 애석하게도 가지고 있는 SATA 디스크 중에는 그런 용량이 없었기 때문에 다른 방법을 찾아야 했다.

 

가장 먼저 떠올린 건 그냥 돈을 써서 SATA 디스크를 하나 사는 거였지만 채택하지 않았다. 추가 지출은 피하고 싶은 상황이기도 했고 그렇게 바꾸고 나면 저용량 디스크는 용도가 애매해져서 결국 디지털 쓰레기에 일조하게 되는 것도 싫었다.

다음으로는 어떻게든 사용 용량을 줄여서 쓰던 SATA로 되돌아가는 거였지만 이리저리 줄여봐도 이미 방만해진 사용량을 아주 홀-쭉하게 줄이는 건 어려웠다. 어지간한 건 다 ZFS 쪽으로 빼내서 SATA에는 최소한만 남기는 식이 되어야 했는데 이럴 바에는 ZFS로 다 옮기는 게 맞다.

그렇다면 본격적으로 ZFS를 OS로도 쓰는 모험을 감행할 차례였다. 실은 우분투가 정식으로 ZFS 설치를 지원하기 시작한 때부터 옮겨갈까 생각하긴 했었다.

 

하지만 막상 결심은 쉽지 않았다. ZFS를 도입한 뒤 충분한 용량 때문에 관리의 필요를 거의 못 느꼈고 지금에 와서는 자잘한 파일들이 잔뜩 쌓여서 혹시라도 사고가 생기는 날에는 뭐가 날아갔는지 파악조차 어려울 지경이었다. 사고를 피하려면 ZFS 전체 내용을 백업해둬야 했지만 지금에 와서 그만한 용량의 디스크를 따로 마련하자니 추가 지출이 발생하고, S3로 백업하는 것도 거기에 들어가는 시간은 둘째 치고 S3 용량과 전송량을 생각해봤을 때 AWS 비용면에서 같은 용량의 디스크를 사는 것과 별반 차이가 없어 보였다.

그래서 그냥 저질렀다. (https://github.com/openzfs/zfs/discussions/16120 코멘트에 따라 snapshot은 만들어두었지만 정말 큰 사고가 생긴다면 이것도 도움이 안 될 거라고 생각했다)

 

2. 어떻게 저질러야 하나

처음에는 우분투의 설치 과정을 따라가다가 보면 "이미 있는 ZFS 풀"에 설치하는 방법도 제시하지 않을까 하는 기대를 하고 라이브 USB를 만들어서 켜봤지만 그런 옵션은 없었다. 디스크를 다 지우고 진행하는 선택지만 있었다. OpenZFS에서 제공하는 문서를 봤지만 https://openzfs.github.io/openzfs-docs/Getting%20Started/Ubuntu/Ubuntu%2022.04%20Root%20on%20ZFS.html 이것도 "이미 있는" 뭔가를 다루는 게 아니라 새로 설치하는 방법을 안내하는 거였다. 아마 우분투가 이 흐름을 따를 터였다. 이걸 따라갈 수는 없었다.

https://www.complete.org/converting-an-existing-linux-installation-to-zfs/ 이것저것 찾다가 본 이 문서는 꽤 그럴듯해 보였다. 하지만 여전히 어떻게 흘러가야 하는지 잘 눈에 들어오지 않았다. https://stackoverflow.com/a/61506953 에서 간명하게 세 단계로 진행된다고 하는 편이 차라리 안심하는 데 도움이 되었다.

 

무엇보다 필요한 건 어쨌든 ZFS로 OS 내용을 옮기는 거였다. OS가 ZFS로 넘어가 있어야 ZFS로 부팅한다는 행위가 성립할 테니까.

그래서 우선 떠있던 데몬들은 다 멈추고 라이브 USB로 부팅한 다음 rsync로 ZFS에 만든 풀로 루트 파티션 전체를 옮겨줬다. 하지만 왜인지 df, du로 확인한 용량은 ext4와 zfs가 서로 꽤 달랐고, FS에 따라서 다를 수 있다는 건 알고 있지만 그래도 너무 달라서 find로 출력한 전체 파일의 상대경로 비교를 해봤지만 find에는 있거나 없다고 나오는 파일이 실제로는 양쪽 다 있어서 find를 믿을 수 없었고, rsync 옵션을 av, avH, avc 같이 이것저것 줘봤는데 checksum이 너무 오래 걸려서 중도에 멈추고 결국 에러 없이 종료하는 rsync를 믿기로 했다.

그 상태로 fstab 파일에서 / 경로만 zfs로 바꿔서 열면 어떻게 될까 싶어 파일을 바꾸고 재부팅했지만 zfs로는 부팅되지 않았다. ext4로 루트가 잡힌 채로 부팅이 되었는데 뭔가 이상해서 살펴보니 ro 상태였다. 뭔가 에러 때문에 ro로 넘어온 걸까 의심했지만 dmesg에는 의심할만한 구석은 안 보였다. 망한 건가 싶었지만 이미 진창에 발은 디뎠고 진흙도 잔뜩 묻었으니 돌파하는 것 밖에는 다른 수가 없었다.


3. OS를 부팅한다는 것

루트 파티션이 준비되었으니, 설치 과정에 견주어 생각해보면 다음은 부트로더가 새 루트 파티션 기반으로 돌아가게 할 차례였다.

그리고 여기서 생각이 막혔다. 부트로더라 하면 BIOS가 부팅 장치로 인식하는 어떤 디스크를 구조적으로 바꿔야 하는 건데, ZFS는 그냥 디스크 전체가 ZFS로 잡혀있고 여러 디스크가 똑같은 구조이기 때문에 어느 하나만 부팅용으로 만든다는 개념이 와닿지 않았다. 깊게 들어간다면 MBR이니 GPT니 EFI니 하는 단어가 나와야 하지만 어쨌든 ZFS 디스크를 건드리고 싶지 않았다.

그래서 ZFSBootMenu를 검토했다. 사실 검색 초기부터 이 이름이 나오긴 했는데 가뜩이나 전체 그림이 안 잡히는 상황에서 뭔가 새로운 걸 다 파악하고 싶지 않아서 지나쳤다.

https://docs.zfsbootmenu.org/en/v2.3.x/guides/ubuntu/noble-uefi.html#install-zfsbootmenu 문서를 읽어보니 앞부분의 신규 설치 과정들은 다 했다고 치고 넘어갈 수 있어 보였다. 중요한 단계는 짚어보자면 이렇게 되는 거였다.

  1. ZBM은 빈 vfat에 EFI/ZBM/VMLINUZ.EFI 경로로 파일을 넣어준다.
  2. ZFS는 미리 zfs set 명령으로 ZBM이 인식하는 정보를 넣어준다.
  3. efivars 정보에 접근해서 efibootmgr 명령으로 ZBM을 EFI 부팅 항목으로 인식시킨다.

ESXi를 USB로 만들어서 부팅용으로만 쓰는 건 알고 있었기 때문에, ZBM도 빈 vfat이 꼭 기존 디스크의 파티션 중 하나일 필요는 없이 USB를 하나 만들어서 넣어주면 되겠다 싶었고, 실제로도 USB로 부팅에 성공했다.


4. 시작이 반이다. 아니 반이 시작이다.

부팅 후 확인했을 때 NVMe 디스크는 더 이상 시스템에 사용되지 않았다. 원하던 결과였다.

하지만 이건 이제 시작이고, 원래 하려던 HP 마이크로서버로의 귀환까지 마무리가 되어야 한다. 아마 ZBM을 EFI에 인식시키느라 재부팅을 몇 번 해야 할 것 같다. 그리고 기왕에 GRUB의 역할을 ZBM에 넘겼으니 그동안 GRUB으로 처리했던 부분도 ZBM에선 어떻게 하는지도 차차 알아봐야 할 것이다. (HP 마이크로서버에서 부팅해보니 EFI 쉘에서 직접 실행하는 걸론 안 되었지만 라이브 USB를 통해 efibootmgr로 등록해준 다음에는 라이브 USB를 제거해도 ZBM으로 정상 부팅됐다)

그 와중에 예전에 쓰던 OS를 부팅하면서 생긴 tailscale 겹침 증상 때문에 여차저차 tailscale 전체 노드를 새로 로그인해야 하는 번거로움이 있었다. rsync가 잘 되었는지도 약간 불안하기 때문에 debsums 명령으로 전체를 확인해서 일단 패키지 범위에서는 문제가 없음을 확인했다. docker는 의외로 이미지가 멀쩡하게 살아있는지 검사해주는 명령이 없어보여서 그냥 컨테이너가 떴으면 정상이려니 하고 넘어가기로 했다.

Dropbox가 zfs 같은 거 쓰지 말고 평범하게 ext4로 쓰라고 안내하는 건 이미 예전에 zvol로 반영해둔 상태라서 괜찮다. qemu를 NVMe 대신 zfs에서 굴리면 얼마나 속도 저하를 역체감할지는 걱정이다.

부디 아무 사고 없이 이 모험이 끝나기를.

2024년 8월 20일 화요일

고대 문명이 흔적도 없이 사라진 이유 (How Atlantis Vanished)

 그 시대의 ISIS가 모두 부숴버렸다.


The ISIS of their age have destroyed them all.

2024년 8월 15일 목요일

난 단지 k8s 클러스터를 클라우드 비용 안 내고 구축해보고 싶었을 뿐이었다

업무 환경에서 AWS EKS로 k8s를 접했고 그 앞에 CI/CD도 구성되어서 꽤 편리한 경험이었다. 이걸 직접 만들어보고 싶었다. 돈 들이지 않고.

 

GCP, Azure, AWS 다 써봤고 무료 단계라는 것도 다 손대봤지만 OCI에서 제공하는 무료 VM만큼 제약 없이 완전 무료인 경우는 없었다.

그래서 생각했다. VM을 4개나 쓸 수 있고 집에도 서버를 켜놓고 있는데 여러 노드짜리 k8s 클러스터로 묶어 쓰면 되지 않을까? 이게 기나긴 모험의 시작이었다.

k8s 구성요소를 패키징해서 설치해주는 게 몇 가지 있다는 건 알고 있었다. k8s 사이트에서 minikube라는 걸 안내하는 것도 봤었고, 우분투에 쉘로 들어가면 microk8s를 써보라고 안내가 나오기도 했으니까.

 

microk8s를 우분투가 기본으로 지원하기 때문에 첫 타자로 써봤지만 여러 노드에 설치했을 때 서로 연동이 잘 되지 않았다. microk8s 자체가 snap 기반으로 돌아가기도 했기 때문에 어렴풋하게 네트워크 문제인 것 같다고 의심했다. 하지만 로그를 확인하기도 쉽지 않았다. 몇 번의 시도를 해보다가 결국 포기했다.


다음은 k3s였다. Rancher에서 k3s와 rke2를 제공하는데 https://www.suse.com/c/rancher_blog/when-to-use-k3s-and-rke2/ 비교에 따르면 rke2는 보안에 좀 더 힘을 줬고, k3s는 가볍게 성능을 챙기고 싶으면 쓰라고 되어 있다.

이것도 여러차례 삽질을 했다. k8s가 이런저런 부품으로 이루어졌다는 건 알고 있었지만 각 역할을 충분히 이해하지 않은 상태에서 https://github.com/superseb/multipass-k3s/blob/master/multipass-k3s.sh 이런 성공사례가 있고 공식문서에도 착착 따라하면 다 된다고 적혀 있는데 왜 나는 안 되는지 다양한 시도를 했다.

로그를 살펴봐도 단순히 결과로만 CA 인증이 실패했다거나 접속이 안 됐다거나 하는 식으로만 나올 경우도 많아서, 도대체 뭘 잘못 넣었길래 그런 결과가 나오는지 여러 부분을 의심하면서 옵션을 빼고 넣고를 반복해야 했다.

 

그리고 마침내 뭐가 문제였는지 충분히 이해했다.

  • 일반 OS라면 /etc 경로에 파일로 저장했을 각종 상태들을 etcd라는 서비스로 저장하는데, HA를 유지하기 위해 3개 노드에 etcd를 사용하길 권장한다. 여러 노드에 있는 각각의 etcd는 서로 통신을 해야 하고, k8s 안에서 운용되는 다른 부품들도 etcd에 접속해서 내용을 읽고 쓸 수 있어야 한다.
  • k3s 설치 과정에서 서버와 에이전트로 노드가 접속하는 것만이 아니라 각 노드 안에 흩어져서 실행되는 pod 등의 요소들도 이런저런 이유로 서로 통신할 수 있어야 한다. 다시 말하면 10.x.y.z로 부여된 k8s 내부 IP를 라우터를 거쳐서 어느 노드로 들어가야 하는지 파악할 수 있어야 한다는 것. 오라클 VM과 홈서버로 구성된 이질적인 네트워크에서는 이게 뚝딱 되는 게 아니었다.
  • 오라클 VM에서 x86_64와 aarch64 서버를 모두 제공하는데 x86 쪽은 성능이 k8s의 모든 부품을 다 띄울 수 있는 상황이 되지 않는다. 무료로 쓰면서 불평하는 건 아니지만 처음에 성능 한계가 어느 정도인지 잘 인지하지 못해서 고부하를 일으키고 그것 때문에 k8s 클러스터에 노드가 들어왔다 끊기다가 하는 것처럼 보이는 상황이 생긴 건 문제였다.

 

해결은 이렇게 했다.

  • k3s에서 각 노드의 역할(role)을 제어할 수 있게 되어 있어서, etcd는 오라클 VM 안에서만 뜨게 했다. k3s 구동 자체가 etcd는 내부IP 값을 따르게 되어 있었고, 실제로도 빈번하게 접속을 하는 성격상 외부 네트워크를 경유하게 만드는 건 적절하지 않기 때문이다. 특히 x86 VM에는 다른 부품 없이 etcd만 들어가게 해서 부하가 높아지지 않게 했다.
    • multipass-k3s.sh 같은 간단한 성공사례는 애초에 동일 네트워크 안에 VM을 잔뜩 만들어서 구성하는 방식이기 때문에 이런 네트워크 부분에 특이사항이 나오지 않았던 것이다.
  • 노드 내부의 pod 등 통신을 위해서는 tailscale을 도입했다. https://docs.k3s.io/kr/networking/distributed-multicloud#integration-with-the-tailscale-vpn-provider-experimental 에서 안내하는 --vpn-auth 옵션을 붙이고 나면 kubectl get nodes -o wide 명령으로 나오는 노드의 내부IP가 모두 tailscale로 잡히면서 실제 노드가 서로 다른 네트워크에 속하는 상황을 해결해준다. 이미 tailscale을 사용하고 있었기 때문에 도입에 문제는 없었고, 오라클 VM 같이 자체 내부망이 있는 경우에는 tailscale status를 해보면 direct라는 표시가 따로 붙었기 때문에 tailscale IP를 쓴다고 통신에 지연이 생기지는 않을 것으로 생각한다.

 

결과물은 이렇게 나왔다.

  • k3s로 5개 호스트에 etcd는 3개를 띄우고 에이전트 전용은 2개 노드인 클러스터를 만들었다.
  • openfaas를 helm로 설치해서 nodeinfo라는 함수를 모든 노드에 다 퍼트린 다음, 반복해서 실행했을 때 각 노드의 정보가 번갈아 나오는 것을 확인했다.

 

2024년 6월 1일 토요일

3년 1개월이 지난 회고

 https://blog.keizie.net/2018/07/resume-or-postmortem.html


8년 하고도 3개월짜리 회고를 한 것도 벌써 6년이 지났다.

 

1. 해온 일

  • 개발조직이 주가 아닌 업체에 잠깐 있었는데 인하우스인데도 외주처럼 다뤄지는 느낌이라 금방 손을 뗐다.
  • 쇼핑몰과 풀필먼트를 모두 보유한 회사에 잠깐 있었는데 개발조직 구성이 생각보다 안 좋아서 일찌감치 발을 뺐다. 실물을 다루는 커머스는 엮이는 게 정말 많다는 걸 새삼 맛볼 수 있었다.
  • PHP 기반의 레거시를 가진 콘텐츠 판매 업체에서 1년 3개월 정도 있었다. 나름 재미있는 경험이었고 회사 안에 가족관계가 존재하면 정말 문제가 된다는 걸 크게 느꼈다. 이 다음에 한참동안 나라가 주는 월급으로 생활하면서 회복기를 가졌는데 이것도 아주 즐거운 시간이었다.
  • 그동안의 주력이었던 PHP로 된 레거시를 Node.js로 전환하려는 업체를 만나 좋은 경험을 할 수 있었다. 도메인 자체는 인터넷 방송이라서 상당히 거리감이 느껴지는 분야였고 실제로도 그 분위기에 적응하기까지 꽤 시간이 걸렸다. 기술적으로도 단순한 웹페이지가 아니라 꾸준히 떠 있어야 하는 싱글페이지 앱에 가까운 걸 제공해야 했고 웹소켓이라는 실시간 통신도 다뤄야해서 손이 많이 가는만큼 새로운 분야의 경험을 할 수 있었다.
  • 팀 리드 역할을 맡았다. 경력이 있는만큼 자연스럽다고도 할 수 있겠는데 특히 이 회사에는 관리직에 큰 회사 경험이 있는 사람이 있다보니 그 전에 했었던 "기술팀을 두루 아우르는" 뭉뚱그린 역할이 아니라 몇 가지 명확한 요구사항이 존재했고 한동안 이를 충족시키기 위해 시간과 노력을 꽤 들여야 했다. 매니징 트랙으로 완전히 전환하지 않았지만 앞으로 매니징 역할을 또 하게 될 때 참고가 될만한 경험치였다고 느낀다.
  • 역할 분장이 뚜렷했어서인지 리드 역할로 팀원과 주로 접촉했기 때문인지 회사 내의 다른 팀 인력들과의 복합적인 의사소통은 아주 많지는 못했던 것 같다. 아주 가끔 있었던 조인트 회식, 종종 참여했던 필참은 아닌 회의들에서의 의사소통, 혹은 진척이 막힌다고 느낄 때 이해당사자와 직접 작업상황을 확인하는 등의 시도는 이어가려고 노력했다.

 

2. 다뤄본 것

  • AWS EKS를 통해 K8s를 겪어볼 수 있었던 게 가장 큰 수확이었다. 어지간한 규모가 아니면 효율을 뽑기 힘들 것 같고 앞으로 또 써볼 수 있을지도 의문스럽다.
  • 웹소켓 관련 경험도 좋았다. 간단하게 맛보는 건 정말 간단하지만 깊이 파고들자면 고려할 게 꽤 많아지는 것들이라 실무에서 깊게 다뤄볼 수 있어 좋았다.
  • PHP를 Node.js로 옮기는 건 결과적으로는 깔끔하게 딱 떨어지는 결과를 내지 못했다. 상단에 API Gateway 역할을 두고 하위에 레거시와 신규를 두는 방식이 가장 좋았겠지만 인프라와의 업무분장이나 프론트엔드와의 접근법 차이 등을 고려해 하나의 방식을 밀어붙이지는 않았다. 다음에 또 비슷한 상황이 생기면 좀 더 조율에 시간을 할애하는 게 좋지 않을까?
  • Redis가 캐시만이 아니라 pub/sub을 통해 MSA 느낌으로 시스템 전반에 관여하고 있었다. 너무 예전에 구축되고 멈춘 레거시여서 pub/sub을 큐 용도로 쓰는 건 좀 비틀어진 느낌이었어서 개선하고 싶었지만 여의치 못했다.
  • Nest.js는 정말 좋은 프레임워크였다. PHP를 다루면서 개념만 접하고 말로만 들었던 DI나 서비스 레이어, DTO 같은 것들이 실제로 구현체로 다가오고 동작시켜볼 수 있다는 게 좋았다. TDD도 테스트 먼저 작성한다는 게 어떤 것인지 머릿속으로만 상상했었는데 어느 시점이 되니 내가 그걸 하고 있어서 놀라웠다.
  • CI/CD라고 하는 걸 좀 더 본격적으로 접할 수 있었다. 인프라 담당자가 따로 있다는 건 매우 좋은 상황이었다.
  • 충전 캐시 개념을 다루었다. 기존 PG 경험은 단건결제 단건취소(혹은 부분취소)까지였는데 충전을 하고 충전된 캐시를 사용하고 정산하는 등의 로직은 또 다른 체험이었다. 정산만을 위한 하위 앱이 따로 있어서 나중에는 회계 관련도 조금 더 이해해야 하는 상황이 되었다.

 

3. 다뤘어야 하는 것

  • 6년 전에는 이 부분에 기술적인 아쉬움을 주로 적었는데 이번에는 기술적으로는 떠오르는 것이 별로 없다. 기술적인 노력이 부족했기 때문에 아쉬움도 없는 것일까? 아니면 하고 싶은 건 다 해봤기 때문일까?
  • 서비스에 유튜브 도입을 더 빨리 했다면 어땠을까 하는 생각은 든다. 3년 조금 넘는 정도의 재직기간에서 가장 큰 후회를 꼽자면 이것이다. 흔히 말하는 "급한 일과 중요한 일" 중에서 급한 일들을 처리하다 보니 밀렸던 건데 결과적으로 보면 먼저 처리해야 하는 중요한 일이었다.

 

4. 다루고 싶은 것

  • BI를 강화하고 싶었지만 맡은 JD에서는 벗어나는 부분이라 선뜻 의제화를 하지 못했다. 자체적으로 데이터를 보유하고 있어도 분석해서 활용하지 않으면 쓸모가 없다.
  • 깃허브 코파일럿을 시작으로 AI가 두드러지게 등장하는 기간이었다. AI 관련을 업으로 삼기는 어렵지 않을까 생각했지만 개인 프로젝트를 위해 찾다보니 모델 자체를 연구하는 것 외에 모델을 응용해서 이런저런 결과물을 내는 것도 손이 많이 필요한 부분이고 내 지난 이력들과도 많이 다르지는 않을 것 같았다. 기회가 되면 ML 등을 접목한 작업도 해보고 싶다.