원클릭 homelab 구축 1 - 목적과 사연

Byeonghoon Yoo·2022년 8월 4일
2

homelab 구축

목록 보기
1/3
post-thumbnail

이 시리즈는 개인적으로 구축한 Kubenetes cluster의 구축 과정과 대략적인 구조를 소개하고, 어떻게 원클릭으로 클러스터를 구축했는지 간단하게 설명할 예정입니다. 구현 디테일은 레포지토리에 점진적으로 문서화할 예정이며, 이 시리즈에서는 멀리서 봤을 때의 구조와 그렇게 구현된 이유, 제가 고민했던 포인트들을 설명할 예정입니다.

이 포스트에서는 homelab의 최종 목적을 나열하고 각 목적들이 생기게된 사연을 설명합니다.

  • 1편: 목적과 사연
  • 2편: 구조
  • 3편: 원클릭/돌아보기

homelab이란?

구체적인 정의는 없는 것 같다. 개인이 실험을 위해서 직접 구축하고 운영하는 모든 시스템을 총칭한다고 생각한다. 예를 들어서 남는 구형 컴퓨터에 서버를 설치하거나 안드로이드 폰에 ssh 서버나 웹 서버를 띄워놓으면 그것도 homelab이라 부를 수 있다고 생각한다.

최종 목적

아래의 조건을 충족하는 homelab을 만들고 싶었다. 우선 조건을 나열하고 하나씩 그 이유를 설명하겠다. 순서는 중요도를 의미한다.

  1. (상용에 근접하는) 아주 높은 가용성
  2. Kubernetes 클러스터
  3. 구축 / 재구축이 버튼 하나 수준으로 간단
  4. 계정과 권한만 있으면 특정 기기에 종속되지 않은 배포
  5. 상태 모니터링 / 알림 가능
  6. 신뢰성있는 저장소
  7. (상용에 근접하는) 보안

거의 작은 회사의 인프라 요구사항으로 보일 수 있는데, 그게 맞다. 반복적인 작업을 용납하지 않기 때문에 안정적이면서 자동으로 굴러가는 homelab을 만들고 싶었다. 이 조건들은 경험에의해 만들어졌는데 그 경험들을 아래서 설명하겠다.

목적 1 - 배포는 새벽에만

지금 돌아보면 homelab의 첫 시작은 pi-hole 서버를 띄우는 것이었다. 당시 자주 보던 Linus Tech Tips 채널의 영상을 보고 결심했고, Raspberry Pi 4를 하나 구입해서 Linux를 설치하고 host OS에 서버를 구축했다.
DNS 서버이다보니 무엇보다 중요한게 가용성이었다. 가족들 기기에 모두 해당 DNS를 등록했기 때문에 만약 서버가 꺼지면 모든 기기가 먹통이 된다. 서버가 꺼질일이 없을 것 같지만 생각보다 쉽게 발생한다. homelab은 DNS 전용이 아니라 파일서버 혹은 docker 서버 역할도 했기 때문에 하나라도 버전 업그레이드 이후에 리붓하면 바로 DNS도 먹통이다. 혹은 누군가 서버가 연결된 플러그가 선풍기 플러그인줄 알고 뽑기라도 하면, 직접가서 다시 꼽기 전까지 켜지지 않는다.
한번은 집 밖에 있을 때 알 수 없는 이유로 서버가 죽은적이 있었는데, DNS를 public으로 열어놓고 가족 모두 집 밖에서도 사용했기 때문에 문자와 전화를 제외하면 아무런 인터넷 기능을 사용할 수 없던적이 있었다. 나야 원인을 알기 때문에 DNS를 해제하면 되지만, 가족들은 원인도 모른체 집으로 돌아오기 전까지 문자로만 소통했었다.
이걸 한번 겪고나니 모든 변경이 부담을 동반하게 되면서 웬만하면 새벽에만 배포하게됐고, 자연스럽게 사용성이 떨어지기 시작했다. 이 때 가용성이 중요한걸 체감했고 절대 중단되지 않는 서버를 구축하고 싶었다.

목적 2 - 그 중 제일은 Kubernetes이라

바로 위에서 설명한것처럼 가장 처음엔 host OS에 직접 설치해서 운용했지만, 가용성을 챙기려니 노드의 redundancy가 필수였다. 이걸 찾아보니 많은 글에서 "소규모 서버에는 Docker Swarm을, 규모가 조금있다면 Kubernetes를 사용해라"라는 의견이었다. 생각보다 많은 글에서 동일한 의견을 보여서 이걸 믿고 Docker Swarm을 선택했다. 한달 씨름하면서 두대의 노드를 연결했서 배포했는데 오히려 한대일때보다 느렸다. 이건 service mesh 설정을 바꿔서 최적화했는데, 이것보다 더 큰 문제 2개가 있었다. 첫번째로 문서가 너무 적다. 간단한 튜토리얼 수준의 글이야 많지만, 조금만 일반적이지 않은 문제를 검색하면 동일한 문제를 Kubernetes에서 해결하는 글밖에 보이지 않는다. 두번째로 자동화가 어렵다. 안쓴지 너무 오래돼서 정확히 기억나지 않지만, Kubernetes로치면 StatefulSet같이 노드마다 식별이 필요한 경우에 배포가 까다로웠던 기억이 있다. 클러스터를 처음 익혀서 겪었던 문제일수 있다고 생각하는데, 첫번째 문제와 엮여서 그런 경우에대한 문서를 찾기 어렵다. (지금은 버려진 Swarm cluster 프로젝트)
찾고 찾아서 코드까지 읽다보면 통달할 수 있는 문제들이라고 생각되지만, 서버 개발자로 일하면서 앞으로 쓰지도 않을 기술을 그렇게까지 파야할 이유가 있을까? 라는 의문이 들었고 (문서 찾다 현타왔던 것 같다), 결국 Kubernetes를 배우기 시작했다. 이러한 학습을했기 때문에 의미는 있었다고 생각하지만, 지금 누군가 클러스터를 만든다면 크건 작건, 노드가 한개건 여러개건 처음부터 Kubernetes를 사용하는게 맞다고 생각한다.

목적 3 - 재구축: THE BIG RED BUTTON

크게 두가지 이유로 재구축의 편의를 원했다.

일부 노드 변경

원래도 Linux를 main OS로 사용하기 때문에 노드 고장/추가의 이유로 다시 노드를 초기화 하는것은 전혀 어렵지 않다. 하지만 번거로움이 크다. 초기화 과정이 정말 귀찮아서 데스크탑/랩탑 OS조차 자동 초기화 스크립트를 만들어놓고 재활용하기 때문에, 노드 또한 자동화하고 싶었다. (OS 초기화 스크립트: Archlinux, Manjaro)

빈 클러스터에 서비스 배포

사실 Kubernetes를 사용할 경우 신규 노드에 네트워크 설정과 kubelet 설치 이후 클러스터에 연결만 해주면 추가로 자동화할건 거의 없다. 때문에 위 보다는 아래의 자동화가 더 필요했다. k8s cluster가 구축됐다면 보통 kubectl apply 혹은 helm install로 클러스터에 배포하는데, 만약 cluster를 다시 설치했다면 이 과정을 하나하나 다시 해야한다. 위의 OS 초기화 스크립트로 비유하면, 내가 원하는 최종 상태를 위해서 apt install 같은 커맨드를 이어서 하는것이라고 볼 수 있다. 그 많은 실행줄을 모두 기록하기도 어려울 뿐더러, 빠르게 테스트하려고 apply해놓고 기록하는것을 까먹으면 나중에 다시 찾기가 정말 어렵다. 특히 외부 서비스의 API Key 같은 것들은 발급 받는 과정을 다시 해야한다.


결국 초기화 스크립트처럼 실행만하면 노드들을 k8s cluster로 연결하고 그 위에 내가 원하는 서비스들이 자동으로 배포되는 자동화가 필요했다. 목적 1을 위해서라도 자동화 되어있어야하는데, 만약 자동화가 되어있지 않은데 아주 오래된 클러스터가 망가졌을 경우엔, 기억력을 믿을 수 없을게 분명하기 때문에 각 서비스들의 설치&설정 문서를 하나씩 뒤져가면서 순서에 맞게 재현해야한다. 이 시간동안 서버는 동작하지 않기 때문에 가용성을 크게 떨어트린다.

목적 4 - 배포는 내 방 책상에서만

Swarm을 썼을 때 겼었던 일인데, secret들을 .env 파일에 저장해놓고 배포할 때 환경변수로 주입해주기 때문에 배포를 하려면 .env가 있는 머신에서 해야했다. Kubernetes로 넘어왔을땐 이것보단 제약이 심하진 않았지만, 결국 cluster에 접근할 수 있어야 kubectl을 실행할 수 있기 때문에 .kube/config가 설정되어있는 머신에서 배포해야했다. 당시 회사에서는 Slack 명령줄로 간단하게 배포할 수 있는 환경이었기 때문에 kubectl 설정조차 제약이라고 생각했다.

목적 5 - 모니터링: 모르면 털린다

(내가 알기론) DNS는 인증이 없기 때문에 public으로 열어놓으면 아무나 응답을 받아갈 수 있다. AdGuardHome을 public으로 열어놓고 사용 중, 뭔가 느려졌다 싶어서 오랜만에 대시보드에 들어가보니 몇억건의 요청이 처리되고있었다. 지금도 이유는 모르겠지만 특정 IP에서 대량의 요청을 꾸준히 보내고있었고, 아무리 DNS라도 요청 볼륨 때문에 노드 리소스를 많이 먹어서 latency가 증가했었다. 바로 IP blocklist에 추가했고 이 때 모니터링과 알림이 필수라는것을 깨달았다.

목적 6 - 신뢰성있는 저장소

재사용성을 가장 중요하게 생각하기 때문에 OS 환경설정 이외에도 코드와 기타 문서들을 별도로 백업중이다. BorgBackup를 기반으로하는 데스크탑 클라이언트 Vorta를 사용중이다. 2015년부터 지금까지 총합 1.5TB의 데이터를 포함하는데, borg를 사용하면 dedup과 compression으로 42.2GB만 저장소에 저장하면서도 각 백업 시점의 snapshot을 그대로 유지할 수 있다. 근데 문제가 있는데 "이렇게 중요하고 압축된 정보를 어디에 저장하는가"가 핵심이다. borg가 어느정도 보정해줄거라고 기대하지만, 엄청난 dedup + compression이 적용되어있기 때문에 몇 bit만 손상돼도 1.5TB가 통으로 사라질 위험이 있다.
자동화가 번거롭겠지만 문서는 42.2GB니까 여기저기 복사할 수 있다. 하지만 사진과 음성, 영상은 이게 불가능하다. 모든 사진과 동영상은 원본을 해치지 않고 추억 그대로 저장하는것을 선호해서 손실 없이 저장하면 금새 용량을 차지하는데, 한번 HDD가 죽어서 (당시) 거액의 복구 비용을 지불한 이후에 다른 HDD에 복사해서 유지했다. 그 때 겪었던 문제가 있는데, 이렇게하면 항상 큰 HDD를 추가로 사야한다. 어렸을 때 250GB HDD의 백업으로 또 다른 250GB를 사용중이었는데 그게 꽉 차서 500GB를 샀다. 500GB에 데이터를 저장하자니 이걸 백업할 수 없는 문제가 생긴다. 그래서 1TB를 샀는데 사고보니 다음엔 더 비싼 2TB를 사야한다는 문제를 깨달았다.
때문에 HDFS처럼 redundancy가있는 저장소의 필요성을 체감했다.

목적 7 - 보안

과거 ssh 서버를 public으로 열어놓고 사용했던 노드가 어느날 100% CPU 사용률을 보여서 확인해보니 docker에 블록체인 마이너로 보이는 컨테이너가 실행되고 있었다. 뭔가 이상하다 싶어서 바로 docker daemon을 죽여서 CPU 사용률은 낮췄지만, 네트워크 대역폭을 최대로 사용하고있었다. 프로세스 목록을 보니 scp로 파일들이 업로드 되고있었다. 바로 네트워크를 끊고 확인할 수 있는 다양한 방법으로 누가 어떤 명령을 실행했고, 어떤 데이터를 빼갔는지 확인하려했다. 분명 누군가 ssh로 접근했고 docker로 커맨드를 실행했는데 w, ps, journalctl, .bash_history, .sh_history, /var/log 어딜봐도 기록이 전혀 없었다. 이 때 이후로 항상 ssh 보안 수준을 올려놓고, iptables로 외부 접근을 최소화한다. homelab의 경우 또한 개인 데이터가 저장되기 때문에 최대한 외부로부터 유입을 막아야한다.

레퍼런스

homelab이 생각보다 유명하기 때문에 이미 많은 프로젝트들이 이미 만들어졌다. 그 중에서도 직접 참고했던 예시는 아래의 2개이다.


다음편에서는 원클릭 구축을 설명하기 전에, 현재 클러스터가 어떠한 구조로 구성되어있는지 설명할 예정입니다.

2개의 댓글

comment-user-thumbnail
2022년 8월 7일

재미있게 잘 읽었네요! 저도 따라 해보고 싶어졌습니다 ㅎㅎ

1개의 답글