나의 k3s 구성 둘러보기
개인 서버 운영의 역사 (TMI 대방출)
개인 서버를 운영해온지도 거의 10여년이 넘어가고 있다. 몇몇 취미로 운영하는 서비스를 돌리거나 RSS 리더, Mastodon 인스턴스 등 개인적인 서비스를 띄우는 데에 사용하고 있다. 무료 크레딧을 받거나 해서 저렴하게 운영할 수 있는 옵션이 생길 때마다 여러 호스팅 서비스를 옮겨다니곤 했다. (생각나는 것만: 카페24, DigitalOcean, Linode, GCE, EC2) 지금은 Lightsail에 정착했다.
이렇다보니 언제든 호스팅 서비스를 옮길 수 있게 해둘 필요를 느꼈다. 단순히 설정 방법을 기록해 두는 건 실수의 여지가 많고, 초기 설정 이후에 바꾼 내용 업데이트를 잊기 쉽다. 직접 서버에 들어가서 뭔가 수정하기보다는 형상 관리가 가능한 방식을 사용하고 싶어서 처음에는 당시에 익숙했던 Ansible로 관리를 했다. 하지만 시스템 전역에 설치한 패키지에 의존하도록 하다보니 OS 버전이 바뀌거나 하면 제대로 작동하지 않는 경우가 많았다.
결국 환경의 영향을 덜 받게 하려면 Docker를 사용해야겠다 생각했다. 물론 Ansible로 Docker 컨테이너를 관리하는 게 불가능하지는 않다. 그렇지만 중단 없이 서비스를 재시작(롤링 업데이트)하는 등 조금 복잡한 작업을 하기는 어렵다. 당시 Kubernetes는 단일 서버에서 사용하기에는 설정이 쉽지 않았기 때문에 Docker Swarm을 잠시 시도해봤으나 여러 가지로 아쉬운 점이 많아서 대충 방치해놓고 시간이 흘렀다.
그러던 중 k3s라는 가볍고 단일 서버에 설치 가능한 Kubernetes 배포판이 나왔다는 소식을 접하고 시험해봤는데 꽤나 만족스러웠다. 설치도 간단해서 좋았다. (명령어 하나면 충분: curl -sfL https://get.k3s.io | sh -
) 대기 상태에서도 머신 자원을 생각보다 많이 사용하는 문제는 있지만 그 정도는 편리함과 타협할 수 있는 부분이라고 보고 지금까지 정착해서 사용하고 있다.
물론 직접 서버를 운영하는 게 Vercel이나 fly.io 같은 PaaS를 사용하는 것에 비해 효율이 떨어지는 일인 건 맞다. 그래도 인프라 공부도 되고 취미 생활로는 나쁘지 않은 것 같다.
인그레스 컨트롤러와 TLS 인증서
Kubernetes에서 웹 서비스는 일반적으로 인그레스로 외부에 노출하게 되고 인그레스 컨트롤러를 설치해야 한다. k3s에는 Traefik이 인그레스 컨트롤러로 기본 설치되는데 나는 익숙한 ingress-nginx를 대신 사용하고 있다. Traefik을 설치하지 않으려면 k3s을 처음 설치할 때 --disable traefik
옵션을 줘야 한다.
ingress-nginx는 LoadBalancer
타입의 서비스를 만들고 k3s의 ServiceLB에 의해 호스트 외부에서 접속할 수 있게 된다. 물론 Lightsail의 방화벽 설정에서도 80, 443 포트를 열어줘야 한다. Lightsail 인스턴스가 바뀌어도 IP가 그대로이도록 고정 IP를 할당하고, DNS는 직접 서버 IP를 가리키게 한다.
AWS의 로드 밸런서를 사용하지 않기 때문에 TLS 인증서는 직접 발급해야 한다. cert-manager를 이용해서 Let's Encrypt 인증서를 자동 발급, 자동 갱신하고 있다. 새 서브도메인에 대한 인증서가 필요하면 Ingress에 어노테이션만 달아주면 된다.
모니터링
처음에 별 생각 없이 익숙한 Prometheus Operator를 설치했다가 리소스 낭비가 심해서 삭제하고 그냥 살고 있었는데, 최근 실행하는 서비스가 많아지니 다시 필요성을 느꼈다.
회사 인프라에서 쓰는 걸 보고 알게 된 VictoriaMetrics를 한 번 깔아봤는데 가볍고 잘 작동한다! VictoriaMetrics도 오퍼레이터를 사용하는 게 권장하는 방식 같지만, 오퍼레이터 자체의 오버헤드도 줄이고 싶어서 단일 프로세스인 VictoriaMetrics Single을 띄우는 방향으로 해보았다.
VictoriaMetrics는 딱 Prometheus와 같이 지표 수집, 저장만 하는 역할이어서 지표를 보고 싶으면 Grafana도 설치해야 한다. 이 역시 튜토리얼 문서에 나온대로 하면 된다. 외부에서 Grafana에 접속하게 하려면 인증을 붙이든지 해야 하는데 귀찮아서 일단 Tailscale 프록시를 달아놓고 Tailscale VPN 내에서만 노출해 두었다.
로그도 오래 보관하면 좋긴 하겠지만 당장은 필요가 없어서 그냥 두고 있다. 만약 필요하다면 Loki를 써볼지도...
데이터베이스와 퍼시스턴트 볼륨
내가 돌리는 대부분의 서비스가 PostgreSQL에 의존하고 있기 때문에 k3s에 하나를 설치해서 공유하고 있다. 파드가 재시작되어도 데이터가 날아가면 안되니 퍼시스턴트 볼륨을 마운트해야 하는데, 이 또한 k3s에서 기본 제공하는 Local Storage Provider를 사용한다. (예전에는 기본 탑재가 아니었는데 언젠가부터 기본적으로 설치된다. 여기서 또 TMI, CPU를 많이 잡아먹는 버그를 수정한 적이 있다 😎)
호스트 머신 파일시스템의 /opt/local-path-provisioner
디렉토리 밑에 파일이 저장되는 매우 단순한 구조이므로 파드가 다른 머신으로 옮겨다닐 수 없는 문제가 있지만 어차피 노드를 한 개만 사용할 것이므로 전혀 문제가 되지 않는다. 노드를 추가하더라도 컨트롤 플레인이 있는 노드에서만 DB를 돌리면 오케이.
DB 백업은 별도로 하지 않고, Lightsail의 백업 기능을 켜두었다. 매일 디스크 스냅샷을 떠주고 일주일 간 유지한다.
인프라 형상 관리
위에서 설명한 시스템 구성 요소는 Helm 차트로 설치하고 있다. 개인적으로 Helm을 썩 좋아하진 않지만 (YAML을 템플리팅 할 미친 생각을 대체 누가 했을까?) Kubernetes 환경의 사실상 표준이기 때문에 어쩔 수 없이 타협을 해야 한다. 내가 직접 패키징해야 하는 경우는 거의 Kustomize를 사용하고 있다.
아무튼 여러 개의 Helm 차트를 설치해야 하고, 매번 helm
명령어를 입력할 수는 없기 때문에 Helmfile로 Helm 차트의 목록과 파라미터를 관리한다. 가능하면 직접 helm
이나 kubectl
로 조작하는 건 지양하고 Helmfile만으로 관 리하려고 노력한다. 모든 설정은 나의 Git 저장소에 잘 올려두고 있다.
아직 깔끔하게 정리하지 못한 부분이 DB 암호 등 Helmfile에 주입할 비밀 정보를 관리하는 것이다. 일단은 Git에는 커밋하지 않는 내 로컬 머신에만 있는 파일에 넣어두고 있다. 만약 내 로컬 머신이 불의의 사고로 날아가더라도 k8s Secret에는 남아있어서 복구 가능하므로 큰 문제는 없을 것으로 생각한다.