00:00:00이것은 모든 서버에 설치 가능한 Tailscale의 무료 오픈 소스 버전인 Headscale입니다.
00:00:06인터넷 연결이 끊기거나 Tailscale이 갑자기 가격을 올릴 경우를 대비해, 암호화된 네트워크를 완벽하게 제어할 수 있게 해주죠.
00:00:13그런데 흥미로운 점은 Headscale을 만든 사람이 바로 Tailscale의 직원이라는 사실입니다.
00:00:18왜 자기들의 경쟁 모델을 만드는 사람에게 월급을 주고 있는 걸까요?
00:00:22구독 버튼을 누르고 그 이유를 함께 알아봅시다.
00:00:25자세한 내용을 살펴보기 전에 Headscale이 실제로 어떻게 작동하는지 빠르게 확인해 보죠.
00:00:30현재 세 대의 Hetzner 서버가 있습니다. 하나는 메인 Headscale 컨트롤 플레인용이고, 나머지 두 노드를 서로 연결해 보려고 합니다.
00:00:40이 두 노드는 현재 제 암호화된 네트워크의 일부입니다.
00:00:44모든 것이 Docker에서 실행 중이므로, 이 명령어를 입력하면 현재 활성화된 노드인 Ubuntu Test와 Ubuntu Test 2를 확인할 수 있습니다.
00:00:53그리고 이 주소들이 노드에 접속할 때 사용할 수 있는 IP 주소입니다.
00:00:56이제 이 두 노드 사이를 서로 연결해 보겠습니다.
00:00:59먼저 이 IP 주소를 복사합니다.
00:01:02그리고 Ubuntu Test 2 서버에서 root 계정으로 SSH 접속을 시도해 보겠습니다.
00:01:07원래 root 접속은 지양해야 하지만, 테스트 목적이니까요.
00:01:09방금 복사한 Headscale의 IP 주소를 입력합니다.
00:01:11엔터를 치고 화면을 정리하면, 보시는 것처럼 Ubuntu Test 1에 접속되었습니다.
00:01:17Ubuntu Test 2에서 1로 들어갔고, 다시 exit를 입력해 2로 돌아올 수 있습니다.
00:01:24반대로 Ubuntu Test 1에서도 Ubuntu Test 2의 IP를 붙여넣어 SSH 접속을 할 수 있죠.
00:01:31화면을 보시면 Ubuntu Test 1에서 Ubuntu Test 2로 성공적으로 접속된 것을 볼 수 있습니다.
00:01:36하지만 테일넷(tailnet) 외부인 제 맥(Mac)에서 새 탭을 열어 Ubuntu Test 2의 IP로 접속을 시도하면 어떨까요?
00:01:48보시다시피 네트워크 바깥에 있기 때문에 연결되지 않고 멈춰버립니다.
00:01:52방금 보신 것처럼 Headscale은 네트워크에 대한 완전한 권한을 부여합니다.
00:01:56모든 세부 사항을 직접 제어할 수 있고, 특정 업체에 종속되지 않고 원하는 만큼 노드를 추가할 수 있습니다.
00:02:03구형 라즈베리 파이나 NAS에 연결하면 인터넷 없이도 작동 가능합니다.
00:02:08다만 설정 과정이 조금 복잡합니다.
00:02:11실제로 어떻게 구축하는지 보여드리겠습니다.
00:02:13지금 Ubuntu Test 3라는 새 서버를 만들었는데요, 두 노드를 연결할 새 사용자와 함께 새로운 테일넷, 즉 암호화된 서버를 생성할 겁니다.
00:02:24테스트를 위해 Ubuntu Test 3와 Ubuntu Test 1을 연결해 보죠.
00:02:29제 Headscale 컨트롤 플레인에는 이미 설치가 되어 있지만, 어떤 과정을 거쳤는지 훑어보겠습니다.
00:02:37현재 세 개의 Docker 컨테이너가 실행 중입니다.
00:02:40Headscale UI, Headscale 본체, 그리고 Caddy입니다.
00:02:45Docker Compose 파일을 보면 Headscale 공식 문서에 있는 예제와 매우 유사합니다.
00:02:56다만 Headscale 버전이나 디렉토리 경로 같은 몇 가지 사항만 수정했습니다.
00:03:03Headscale에서 사용할 수 있는 여러 오픈 소스 웹 UI 중 하나인 Headscale UI 컨테이너도 추가했습니다.
00:03:11이건 잠시 후에 보여드릴게요.
00:03:13그리고 리버스 프록시 역할을 할 Caddy가 있습니다.
00:03:16이 Docker Compose 파일 링크는 영상 설명란에 남겨두겠습니다.
00:03:21이 디렉토리에는 lib 폴더와 config 폴더가 있습니다.
00:03:26lib 폴더는 사용자, 노드, DNS 정보 등을 관리하는 SQLite 데이터베이스를 보관하는 곳입니다.
00:03:36원한다면 설정에서 SQLite 대신 Postgres 데이터베이스로 변경할 수도 있습니다.
00:03:43config 디렉토리에는 Tailscale 설정과 JSON 형식의 액세스 제어 목록(ACL) 정책 파일이 들어 있습니다.
00:03:52이건 나중에 더 설명하겠지만, Tailscale 문서에서 가져와 서버로 바로 내려받을 수 있는 파일입니다.
00:04:01GitHub의 예제 설정을 보면 서버 주소부터 접두사, 정책 파일 위치까지 모든 것을 변경할 수 있음을 알 수 있습니다.
00:04:11제 설정도 거의 비슷한데, 네트워크 내 노드들이 서로를 찾을 수 있도록 서버 URL을 Cloudflare 도메인으로 연결해 주었습니다.
00:04:23정책 파일 경로를 추가한 것 외에는 크게 건드린 게 없습니다.
00:04:28도메인은 일반적인 Cloudflare 설정이며, headscale 서브도메인의 A 레코드를 컨트롤 플레인의 IP 주소로 연결했습니다.
00:04:37이렇게 설정한 뒤 Caddy 파일을 열어보면, UI용 URL(/web)과 프록시용 URL 두 개가 사용 중인 것을 볼 수 있습니다.
00:04:49설정이 끝났다면 첫 번째로 할 일은 사용자를 만드는 것입니다. 이 명령어를 입력하면 됩니다.
00:04:56사용자 이름을 “Tom”으로 정해볼까요? 이름은 무엇이든 상관없습니다.
00:05:00사용자가 생성되었습니다. 리스트를 확인해 보면 ID 6번으로 새 사용자가 추가된 것을 볼 수 있습니다.
00:05:08하지만 아직 이 사용자에게 할당된 노드가 없습니다.
00:05:11이제 Tom에게 노드를 연결해 봅시다.
00:05:13새로 만든 Ubuntu Test 3 서버에서 가장 먼저 할 일은 이 명령어를 실행해 Tailscale 클라이언트를 추가하는 것입니다.
00:05:23이제 Tailscale 바이너리를 사용할 수 있게 되었습니다.
00:05:27root 사용자가 아니라면 명령어 앞에 sudo를 붙여야 할 수도 있습니다.
00:05:31하지만 서비스를 시작하거나 로그인하기 전에, Headscale과 연동되도록 특정 방식으로 실행해야 합니다.
00:05:38그러기 위해 사전 인증 키(pre-auth keys)를 생성해야 하는데, 관련 명령어는 Headscale 문서의 시작 가이드에서 찾을 수 있습니다.
00:05:46스크롤을 내려서 이 명령어를 복사하겠습니다.
00:05:49Headscale 컨트롤 플레인에서 'docker exec' 뒤에 방금 복사한 명령어를 붙여넣습니다.
00:05:54사용자 ID 부분에는 Tom의 ID인 6을 입력합니다.
00:05:59컨테이너 내부로 명령을 보내는 것이므로 'headscale'을 한 번 더 써줘야 합니다.
00:06:04이제 사전 인증 키가 생성되었습니다.
00:06:07이 명령어를 복사해서 Ubuntu Test 3 서버에 붙여넣습니다.
00:06:11그다음 방금 만든 사전 인증 키를 여기에 입력합니다.
00:06:15마지막으로 로그인 서버 주소를 우리가 설정한 headscale.pandor.css로 변경해 줍니다.
00:06:23Pandora 도메인은 이미 누가 쓰고 있더군요. 아니면 그걸 썼을 텐데요.
00:06:26엔터를 치면 Headscale 네트워크에 새 노드가 성공적으로 추가될 겁니다.
00:06:31다시 컨트롤 플레인으로 돌아와 노드 리스트를 확인하면, Tom 소속의 Ubuntu Test 3가 IP 주소와 함께 나타납니다.
00:06:44이제 Ubuntu Test 1도 Tom에게 할당해 보겠습니다.
00:06:47우선 ID 1번인 기존 노드를 삭제하겠습니다. 확인 절차를 생략하기 위해 강제 삭제 옵션을 사용합니다.
00:06:56이제 새 인증 키를 만들고 Ubuntu Test 서버에 등록합니다. SSH 기능을 사용 중이니 SSH 플래그도 잊지 말고 넣어줍니다.
00:07:06Ubuntu Test 3 서버에서도 SSH 접속이 가능하도록 설정을 마무리해 줍니다.
00:07:13노드 목록을 다시 보면 Ubuntu Test 1과 3 모두 Tom의 계정 아래에 있습니다.
00:07:20이제 두 서버를 서로 연결해 보겠습니다.
00:07:21Ubuntu Test 3의 IP를 복사해 Ubuntu Test 1로 가서 SSH 접속을 시도합니다.
00:07:30키를 승인하니 Ubuntu Test 1에서 Ubuntu Test 3로 접속이 잘 됩니다.
00:07:35반대로 Ubuntu Test 3에서 1로 접속하는 것도 가능합니다.
00:07:41제 단계를 그대로 따라 하셨더라도 작동하지 않을 확률이 매우 높은데, 그건 액세스 제어 정책(ACL)을 설정해야 하기 때문입니다.
00:07:49제가 사용하는 정책을 보여드릴게요.
00:07:51Headscale 디렉토리의 config 폴더를 보면 사람이 읽을 수 있는 JSON 형식의 ACL 파일이 있습니다.
00:08:01내용을 보면 네트워크 내 누구든 어떤 목적지나 포트로든 접속을 허용하는 매우 간단한 설정입니다.
00:08:11또한 테일넷 내부의 모든 노드가 별도의 승인 없이 root 계정으로 SSH 접속을 할 수 있게 되어 있죠.
00:08:24물론 실제 환경에서는 보안을 위해 특정 노드가 특정 포트로만 접속할 수 있게 훨씬 엄격하게 설정해야 합니다.
00:08:33하지만 이건 시작을 돕기 위한 아주 기초적인 구성입니다.
00:08:37마찬가지로 이 설정 파일의 링크도 설명란에 적어두겠습니다.
00:08:40그럼 “Headscale UI를 쓰면 이 모든 과정이 쉬워지나?”라는 의문이 드실 겁니다.
00:08:43제 대답은 “그럴 수도 있고 아닐 수도 있다”입니다.
00:08:45보여드리죠.
00:08:47이것이 제 Headscale UI 주소입니다. 현재 Tess와 Tom 두 사용자의 정보가 보입니다.
00:08:48연결된 키와 노드 정보 등을 확인할 수 있습니다.
00:08:57이걸 쓰려면 먼저 명령어를 실행해 생성한 Headscale API 키를 등록해야 합니다.
00:09:01그런데 제가 겪은 문제는 장치 목록(device view) 화면입니다.
00:09:06보시다시피 정보가 거의 표시되지 않는데, 콘솔을 보면 TypeScript 타입 오류가 납니다. 소스 코드를 건드리지 않았으니 제 잘못은 아닐 겁니다.
00:09:09이 지점에서 앱이 멈춰버려서 아무것도 할 수가 없습니다.
00:09:22원래는 여기서 새 장치를 추가하거나 설정을 변경할 수 있어야 하거든요.
00:09:26버그가 너무 많아서 아직은 큰 도움이 되지 않는 것 같습니다.
00:09:32액세스 정책 설정을 더 쉽게 해주는 다른 UI들도 시중에 많이 나와 있습니다.
00:09:35하지만 제 개인적인 경험상, 아직 Headscale UI가 아주 유용하다고 보긴 어렵네요.
00:09:42네, 설정 과정이 확실히 복잡합니다.
00:09:46정말 많이 복잡하죠.
00:09:49기술적 배경이 없거나 주니어 개발자라면 혼자 설정하는 데 꽤 애를 먹을 겁니다.
00:09:51아마 그래서 Tailscale 측에서도 직원이 이 프로젝트를 하도록 놔두는 것 같습니다. 당장은 회사 수익에 큰 위협이 되지 않으니까요.
00:09:57누군가 Claude Opus 같은 AI를 활용해 아주 깔끔한 UI를 입힌다면 멋진 서비스가 되겠지만, 라이선스 문제에 부딪힐 수도 있겠죠.
00:10:07하지만 일단 모든 설정을 마치고 나면, Headscale은 정말 놀라울 정도로 잘 작동합니다.
00:10:18물론 서버를 외부에 공개하는 Funnel이나 Serve 기능 같은 일부 미지원 기능이 있긴 합니다.
00:10:22다중 테일넷, 일시적 노드(ephemeral nodes), 네이티브 네트워크 로그 기능 등도 아직은 지원하지 않습니다.
00:10:32그럼에도 오픈 소스 도구치고는 굉장히 인상적입니다.
00:10:38나중에 제 개인 AI(OpenClaw)를 로컬 LLM을 통해 완전히 오프라인으로 돌리고 싶어지면, Headscale 도입을 진지하게 고려해 볼 것 같습니다.
00:10:42And if I ever get to the stage where I want my OpenClaw to run completely offline with a local LLM, then I may consider it.