본문으로 건너뛰기
02 개발환경 — 브라우저와 아이패드

개발환경 — 터미널 하나를 어디서든 같은 자리로 연다

앞 편은 클러스터 쪽 이야기였습니다. 이번엔 그 클러스터 위에서 제가 실제로 코드를 만지는 자리를 다룹니다. 한 문장으로 줄이면 hub 클러스터에 code-server를 pod로 띄워 두고 노트북이든 데스크탑이든 브라우저로 들어가 그 터미널에 앉는 구조입니다. 같은 터미널에 아이패드로도 붙습니다. 이 글도 그 터미널 안에서 쓰고 있습니다.

클러스터 배치와 NAS, 그리고 Keycloak 한 곳으로 로그인을 모으는 원칙은 hub / edge 구조에 있습니다. 여기서는 그 둘 위에 사람의 작업 자리를 어떻게 마련했는지만 봅니다.

전체 그림

전체 오버뷰 — 왼쪽이 사용자 기기, 오른쪽이 개발환경입니다. 노트북이든 데스크탑이든 브라우저로 웹 VS Code(code-server)에 들어가고, 편집기와 터미널을 한 화면에서 씁니다. 그 요청은 hub 클러스터의 관문에서 Keycloak 인증을 지나 code-server pod에 닿습니다. pod는 홈서버 여러 대 중 한 노드에 뜨는데 어느 노드든 상관없습니다 — 작업 디렉토리가 NAS에 있으니까요. 아이패드는 관문을 거치지 않고 Anthropic 릴레이로 그 pod 안의 Claude Code에 붙습니다. 두 길은 같은 tmux 세션에서 만납니다.
도식 텍스트
  • 사용자 기기 — 브라우저
  • hub 클러스터 — Kubernetes
  • 홈서버 중 한 노드
  • 노트북 — 웹 VS Code
  • 데스크탑 — 웹 VS Code
  • iPad · 폰 — Remote Control
  • 관문 — Keycloak 인증
  • Anthropic 릴레이 — 인바운드 포트 X
  • code-server pod
  • tmux 터미널 — main 세션
  • Claude Code — /rc
  • Storage — PVC 작업 디렉토리
  • HTTPS
  • HTTPS
  • 인증 후
  • attach
  • claude
  • PVC
  • HTTPS
  • /rc 연결

왼쪽이 사용자 기기, 오른쪽이 클러스터입니다. 노트북이든 데스크탑이든 브라우저로 웹 VS Code에 들어가면 편집기와 터미널이 한 화면에 있습니다. 그 요청은 관문에서 Keycloak 인증을 지나 code-server pod에 닿고, 터미널은 tmux 위에서 Claude Code를 띄웁니다. pod는 홈서버 여러 대 중 한 노드에 뜨는데 어느 노드든 상관없습니다. 작업 디렉토리가 NAS에 있으니까요. 아이패드는 관문을 거치지 않고 Anthropic 릴레이를 통해 같은 Claude Code에 붙습니다. 어느 쪽으로 들어와도 같은 pod의 같은 tmux 세션에서 만납니다. 클러스터 안쪽과 아이패드 쪽은 아래에서 하나씩 폅니다.

호스팅 구조 — 관문과 pod

호스팅 구조 — 클러스터 안쪽을 편 그림입니다. 브라우저는 공인 도메인으로 istio gateway에 닿고, 앞에 선 oauth2-proxy가 Keycloak에 로그인을 위임합니다. 통과한 요청만 code-server pod에 들어갑니다. pod는 홈서버 여러 대 중 한 노드에 뜨고 홈 디렉토리는 NAS의 NFS PVC라 pod가 다른 노드로 옮겨 떠도 작업물은 그대로입니다. 테두리 색은 시리즈 공통 — 파랑 배포 · 보라 상태 · 주황 접근.
도식 텍스트
  • hub 클러스터 — Kubernetes
  • 홈서버 중 한 노드
  • 브라우저 — 노트북 · 데스크탑
  • istio gateway — 공인 도메인
  • oauth2-proxy — 로그인 위임
  • Keycloak — SSO
  • code-server pod — auth=none
  • tmux main — 창 유지
  • Storage — PVC 작업 디렉토리
  • HTTPS
  • 라우팅
  • OIDC
  • 인증 후
  • 터미널
  • PVC

왼쪽에서 오른쪽으로 읽으면 됩니다. 브라우저가 공인 도메인으로 들어와 istio gateway에 닿고 앞에 선 oauth2-proxy가 로그인 쿠키를 검사합니다. 쿠키가 없으면 Keycloak으로 보냅니다. 쿠키가 있으면 code-server pod로 넘깁니다. pod의 홈 디렉토리는 노드 디스크가 아니라 NAS에 있습니다.

pod가 어느 노드에 뜨는지는 신경 쓰지 않습니다. 홈서버 여러 대가 노드로 묶여 있고 스케줄러가 고르는 대로 뜹니다. 그래도 되는 건 홈 디렉토리가 스토리지 PVC이기 때문입니다. 관문을 지나면 pod의 터미널이 나오고 그 터미널의 파일은 NAS에 있습니다. 여기까지만 잡으면 됩니다.

구성실제 값
도메인공인 도메인 하나 — istio gateway가 받아 pod로 넘김
인증oauth2-proxy (OIDC) → Keycloak SSO, 그룹 code-server-users
code-server--auth=none. 인증은 앞단이 끝냈으니 본체는 믿고 받음
홈 디렉토리스토리지 PVC — 노드에는 남길 것이 없음
배치hub 클러스터 안 pod — 홈서버 중 한 노드, 상태는 NAS

pod 안의 컨테이너 이름은 code-server이고 istio-proxy 사이드카가 하나 더 붙습니다. 실제로 도는 프로세스는 이렇습니다.

code-server --bind-addr 0.0.0.0:8080 --auth=none /home/mont
항목어디서 오나
이미지monthouse-workspace-code-server — base codercom/code-server:4.106.3Deployment
--bind-addr 0.0.0.0:8080고정이미지 entrypoint.sh
--auth=none인증 끔pod args (Dockerfile CMD 기본값과 동일)
/home/mont열 디렉토리pod args
containerPort8080pod spec
env WORKSPACE_USERentrypoint가 gosu로 이 유저로 강등pod spec
/home/mont스토리지 PVCvolumeMounts
/etc/workspace-initConfigMap — init-script 카탈로그volumeMounts
resourcesrequests 100m / 200Mi, limits 12 CPU / 24Gipod spec
securityContextfsGroup: 1000, fsGroupChangePolicy: OnRootMismatchpod spec
lxcfs/proc/cpuinfo·meminfo 등 hostPath 마운트admission webhook 주입

entrypoint는 부팅 때마다 플랫폼이 관리하는 settings.json 키를 사용자 설정에 deep-merge하고 tmux.conf를 배치한 뒤 gosu로 code-server를 실행합니다. ~/.config/code-server/config.yamlauth: password가 남아 있어도 CLI 플래그 --auth=none이 우선이라 무시됩니다.

인증을 code-server 밖으로 뺀 이유

code-server 자체에도 비밀번호 인증이 있습니다. 처음엔 그걸 썼습니다. 호스트 한 대에 systemd로 띄우고 설정 파일에 password: … 한 줄을 넣은 채 공인 도메인에서 프록시만 걸어둔 형태였습니다. 비밀번호 하나를 브라우저마다 돌려 쓰다 보니 어디서 로그인했는지 알 수 없고 사람을 빼려면 비밀번호를 바꿔야 했습니다.

지금은 code-server가 인증을 하지 않습니다. auth: none으로 두고 클러스터 안의 oauth2-proxy가 Keycloak에 로그인을 위임합니다. 콜백 주소는 인증 전용 호스트 하나로 모았습니다. 쿠키를 상위 도메인에 걸어 두어 한 번 로그인하면 3000- 같은 포트 접두 서브도메인의 개발 서버에도 그대로 들어갑니다. pod 안에는 인증 관련 설정이 한 줄도 없습니다.

hub / edge 구조에서 ArgoCD 로그인을 Keycloak으로 모은 것과 같은 원칙입니다. 사람을 넣고 빼는 곳은 한 군데여야 합니다. 개발환경이라고 예외를 두면 그 예외가 제일 먼저 관리에서 빠집니다.

대가도 같습니다. Keycloak이 죽으면 code-server에 못 들어갑니다. 이미 열린 세션은 쿠키가 살아 있는 동안 유지되고 터미널 안에서 돌던 것들은 tmux가 붙들고 있습니다. 로그인 관문이 잠깐 닫히는 것과 작업이 날아가는 것은 다른 일입니다.

터미널은 tmux 위에 앉는다

code-server의 통합 터미널은 탭을 닫으면 셸이 죽습니다. 브라우저 탭이 날아가도, 노트북을 덮어도 마찬가지입니다. 그래서 터미널이 열릴 때 바로 셸을 주지 않고 tmux에 붙입니다.

main이라는 세션 하나가 pod 안에서 계속 살아 있고 터미널을 열 때마다 그 세션에 묶인 grouped session을 하나 만들어 새 window를 골라 앉힙니다. 터미널을 닫으면 그 window가 빈 bash였을 때만 정리하고 claude나 ssh처럼 뭔가 돌고 있으면 main에 남깁니다. 다음에 어느 브라우저에서 들어와도 prefix+w로 어제 돌려 둔 것을 그대로 잡을 수 있습니다.

처음엔 “비어 있는 window를 재사용"하는 쪽이었는데, 그러면 새 터미널을 열었을 때 어제의 cwd와 scrollback이 딸린 셸에 떨어지는 일이 잦아서 지금은 무조건 새 window를 만듭니다. 새 터미널은 항상 깨끗한 프롬프트이고 살아남아야 하는 셸은 살아남습니다.

아이패드에서 같은 Claude Code에 붙는다

두 개의 입구 — 위 줄은 브라우저가 Keycloak을 거쳐 code-server 터미널에 앉는 길, 아래 줄은 아이패드가 Anthropic 릴레이를 거쳐 그 터미널 안의 Claude Code에 붙는 길입니다. 두 길은 같은 pod의 같은 tmux 세션에서 만납니다. 릴레이 연결은 pod가 밖으로 여는 아웃바운드라 집 쪽에 포트를 하나도 더 열지 않습니다.
도식 텍스트
  • 사용자 기기
  • 연결 경로
  • code-server pod
  • 브라우저 — 웹 VS Code
  • Keycloak — oauth2-proxy → OIDC
  • code-server — 통합 터미널
  • tmux window — /rc
  • Claude Code — 같은 tmux 세션
  • iPad · 폰 — Remote Control
  • Anthropic 릴레이 — 인바운드 포트 X
  • 로그인
  • 세션 쿠키
  • attach
  • 실행
  • HTTPS
  • 릴레이 · pod가 밖으로 연 연결

위 줄이 지금까지 설명한 길입니다. 아래 줄이 이 글을 쓰게 된 이유입니다.

터미널에서 claude를 띄우고 /rc를 치면 그 세션이 Anthropic 릴레이에 등록됩니다. 아이패드의 Claude 앱에서 그 세션을 고르면 pod 안에서 돌고 있는 바로 그 Claude Code가 화면에 뜹니다. 별도의 code-server 로그인도, VPN도 없습니다. 연결은 pod가 밖으로 여는 아웃바운드 HTTPS라 집 쪽에 포트를 하나도 더 열지 않습니다.

이 그림에서 두 길은 결국 한 곳에서 만납니다. 브라우저로 들어간 터미널과 아이패드가 보는 Claude Code는 같은 pod의 같은 tmux window이고 같은 NAS 디렉토리를 봅니다. 아이패드에서 “이 파일 고쳐"라고 한 결과가 NAS에 쓰이고 나중에 노트북 브라우저로 들어가면 그 변경이 그대로 있습니다. 기기가 바뀌어도 작업 자리는 한 곳입니다.

그래서 실제로는 이렇게 씁니다. 책상에서는 브라우저로 code-server를 열어 편집기와 터미널을 같이 씁니다. 자리를 뜰 땐 tmux가 붙들고 있으니 아무것도 정리하지 않습니다. 소파나 밖에서는 아이패드로 같은 Claude Code 세션을 이어서 시킵니다. 돌아와 브라우저를 열면 아이패드에서 한 일이 그 터미널의 scrollback에 남아 있습니다.

대가

  • 무거운 편집 작업은 아이패드로 못 합니다. 릴레이로 붙는 건 Claude Code 세션이지 편집기가 아니니 아이패드에서 할 수 있는 일은 “시키고 확인하는 것"까지입니다.
  • 관문(Keycloak·gateway)과 NAS가 전부 hub에 있어 hub가 통째로 내려가면 새 로그인도 홈 디렉토리도 없습니다. 이 개발환경은 hub의 일부로 두고 그 대가를 받아들였습니다.
  • 릴레이 경로는 Anthropic 쪽 서비스에 의존합니다. 이게 막히면 아이패드 길만 닫히고 브라우저 길은 그대로입니다.
마지막 수정 일자