colima 자동 기동: brew services가 10초마다 재실행되는 이유
개발기에 도커를 새로 깔 일이 생겼습니다. 컨테이너 하나만 돌리면 되는 일이라 가볍게 끝날 줄 알았는데, 설치 자체는 5분이었고 그 뒤에 두 가지를 더 고쳐야 했습니다. 하나는 깃 저장소가 더러워진 것이고, 하나는 코어 하나의 2.8%가 계속 타고 있던 것입니다.
Docker Desktop 대신 colima를 고른 이유
먼저 확인해 보니 이 맥에는 도커가 정말 아무것도 없었습니다. Docker Desktop도 OrbStack도 없고 /var/run/docker.sock도 없었습니다.
맥에서 리눅스 컨테이너를 돌리려면 어떤 방법을 쓰든 리눅스 VM이 하나 떠야 합니다. 리눅스 커널이 있어야 컨테이너가 돌고 맥에는 그게 없기 때문입니다. Docker Desktop도 앱 아이콘 뒤에서 정확히 이 일을 합니다. 세 선택지의 차이는 그 VM 위에 무엇을 더 얹느냐입니다.
| 구성 | 설치 | |
|---|---|---|
| Docker Desktop | VM + 데몬 + GUI 대시보드 + 자동 업데이트 | cask, 관리자 암호 |
| OrbStack | VM + 데몬 + GUI (더 가볍고 빠름) | cask, 관리자 암호 |
| colima | VM + 데몬 | formula, sudo 불필요 |
colima를 고른 이유는 성능이 아니라 설치 경로였습니다. 이 기계에서 도커가 할 일은 postgres 컨테이너 하나인데, 그걸 위해 GUI 앱을 깔고 특권 헬퍼에 관리자 암호를 주고 싶지 않았습니다. colima는 홈브루 formula라 그 과정이 전부 없습니다.
brew install colima docker docker-compose
colima start --cpu 4 --memory 4 --disk 60 --vm-type vz --mount-type virtiofsdocker와 docker compose 명령은 표준 CLI를 따로 깝니다. colima는 그 CLI가 붙을 소켓을 제공할 뿐입니다. 그래서 기존 프로젝트의 컴포즈 파일이 글자 하나 안 바뀌고 그대로 돌았습니다. 밑바탕은 Lima라는 VM 도구고 colima는 거기에 도커를 얹어 주는 껍데기입니다. vz는 애플의 Virtualization.framework를 쓰라는 뜻으로, QEMU 에뮬레이션이 아니라 네이티브 가상화입니다.
여기까지는 순조로웠습니다. 컨테이너가 떴고 DB가 붙었습니다.
colima start가 고친 파일, ~/.ssh/config
작업을 마치고 다른 저장소의 상태를 보다가 이걸 발견했습니다.
M modules/ssh/config건드린 기억이 없는 파일이었습니다. diff를 보니 맨 위에 한 줄이 추가돼 있었습니다.
+Include ~/.colima/ssh_configcolima가 넣은 것입니다. colima는 기본값(sshConfig: true)으로 ~/.ssh/config에 자기 VM 접속 설정을 Include하는 줄을 넣습니다. colima ssh 없이 ssh colima로 VM에 바로 들어가라는 배려입니다.
문제는 이 기계의 ~/.ssh/config가 파일이 아니라는 데 있었습니다.
~/.ssh/config -> <설정 저장소>/modules/ssh/configdotfile을 깃으로 관리하면서 심링크로 걸어 둔 상태였습니다. colima는 홈 디렉터리의 설정 파일을 고친다고 생각하고 썼는데, 그 쓰기는 심링크를 따라가서 추적 중인 파일에 떨어졌습니다. 수정 시각이 colima start가 끝난 그 초와 같았습니다.
심링크된 dotfile이 만드는 사각지대
이건 colima의 잘못이라고 보기 어렵습니다. ~/.ssh/config를 고치는 것은 흔하고 문서화된 동작이고, 그 파일이 어디로 링크돼 있는지는 colima가 알 바가 아닙니다.
그래서 더 문제입니다. 설정 파일을 "친절하게" 고쳐 주는 도구는 colima만이 아닙니다. 클라우드 CLI, VPN 클라이언트, VM 도구 여럿이 같은 일을 합니다. dotfile을 저장소에 두고 심링크로 걸어 둔 이상, 그 도구들 전부가 잠재적으로 커밋 대상을 건드립니다. 그리고 그 사실은 git status를 우연히 볼 때까지 아무도 알려 주지 않습니다.
고치는 순서가 중요했습니다. 그냥 되돌리면 다음 colima start에 또 씁니다.
colima stop
# ~/.colima/default/colima.yaml 에서 sshConfig: true -> false
git checkout -- modules/ssh/config
colima start # 다시 쓰는지 확인되돌리고 나서 다시 시작해 봤고, 이번엔 git status가 비어 있었습니다. 확인하지 않고 "고쳤다"고 말할 수 있는 종류의 변경이 아니었습니다.
Include를 지워도 도커는 멀쩡합니다. docker는 유닉스 소켓으로 붙지 ssh를 타지 않고, VM 안에 들어갈 일이 있어도 colima ssh가 그 설정 없이 동작합니다.
더 나은 구조는 따로 있습니다. 저장소의 ssh config 맨 위에 Include ~/.ssh/config.local 한 줄을 두고 그 파일을 gitignore하면, 이런 도구들이 쓸 자리가 생겨서 저장소가 안 더러워집니다. 지금 당장 필요한 건 아니라 손대지 않았지만, 도구가 하나 더 늘면 그때 하겠습니다.
brew services가 붙여 준 KeepAlive
다음 문제는 자동 기동에서 나왔습니다. colima는 로그인할 때 자동으로 뜨지 않아서 재부팅하면 손으로 colima start를 쳐야 합니다. 홈브루가 서비스를 제공하니 그걸 쓰면 될 일이었습니다.
brew services start colima성공했다고 나왔습니다. 그런데 상태를 보니 Running: false였습니다. 로그를 열어 봤습니다.
10:27:28 already running, ignoring
10:27:38 already running, ignoring
10:27:49 already running, ignoring10초 간격입니다. plist를 보니 이유가 있었습니다.
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<true/>
</dict>
<key>ProgramArguments</key>
<array>
<string>.../colima</string>
<string>start</string>
<string>-f</string>
</array>KeepAlive의 SuccessfulExit가 true면 성공적으로 끝났을 때 다시 띄우라는 뜻입니다. 실패했을 때가 아니라 성공했을 때입니다. 그런데 colima start -f는 VM이 이미 떠 있으면 "already running"을 찍고 0으로 끝납니다. 성공이니까 launchd가 또 띄웁니다. 또 성공합니다. 또 띄웁니다. launchd가 10초로 스로틀링하는 덕분에 무한 루프가 아니라 10초 주기 루프가 된 것뿐입니다.
10초마다 0.28초, 코어 하나의 2.8%
여기서 그냥 넘어갈 뻔했습니다. VM은 잘 떠 있고 컨테이너도 잘 돌고 로그 한 줄이 늘 뿐이니까요. 얼마인지 재 보고 생각이 바뀌었습니다.
$ /usr/bin/time -p colima start -f
already running, ignoring
real 0.52
user 0.15
sys 0.13한 번에 0.28초의 CPU를 씁니다. 10초마다니까 분당 여섯 번, 계산하면 코어 하나의 2.8%를 상시로 태웁니다. 노트북에서 아무 일도 안 하면서 내는 값으로는 비쌉니다. 로그도 항목당 79바이트로 회전 없이 쌓여서 하루 683KB, 1년이면 250MB입니다.
재기 전에는 "로그 한 줄"로 보였고, 재고 나니 "배터리를 계속 깎는 것"이 됐습니다. 값을 모르는 채로 괜찮다고 판단하고 있었던 셈입니다.
한 가지 덧붙이면, 이 KeepAlive가 의도된 것일 가능성도 있습니다. VM이 죽었을 때 10초 안에 되살아나는 감시 효과가 실제로 있습니다. 다만 제 경우엔 컨테이너가 이미 컴포즈의 restart: unless-stopped로 스스로 돌아오고 VM이 죽는 일은 드물어서, 그 보험료로 2.8%는 비싸다고 봤습니다. 상시 가동 서버라면 반대로 판단할 수도 있는 부분입니다.
RunAtLoad만 남긴 자체 LaunchAgent
plist를 직접 고치는 방법도 있지만 그건 홈브루가 소유한 파일이라 다음 업그레이드나 brew services restart에 조용히 되돌아갑니다. 눈에 안 보이게 원복되는 수정은 안 한 것보다 나쁩니다.
그래서 홈브루 서비스를 내리고 LaunchAgent를 직접 만들었습니다. 핵심은 RunAtLoad만 두고 KeepAlive를 아예 안 넣는 것입니다.
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/colima</string>
<string>start</string>
</array>
<key>RunAtLoad</key>
<true/>
<!-- KeepAlive 없음. 한 번 돌고 끝난다. -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>EnvironmentVariables의 PATH는 빼먹기 쉬운 부분입니다. colima는 limactl을 PATH에서 찾는데 launchd는 셸의 PATH를 물려주지 않습니다. 이게 없으면 로그인할 때만 조용히 실패합니다.
로그인을 재현해서 확인했습니다. VM이 뜨고 컨테이너가 따라 올라오고 데이터도 그대로였습니다. 45초를 기다려 로그를 다시 보니 증가분이 0바이트였고, launchctl list는 - 0으로 나왔습니다. 한 번 돌고 끝났다는 뜻입니다.
끄고 켜는 것도 확인해 뒀습니다.
# 켜기
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/co.duolabs.colima.plist
# 끄기
launchctl bootout gui/$(id -u)/co.duolabs.colima끄면 자동 기동만 멈추고 이미 떠 있는 VM은 안 건드립니다. 이것도 재 봤습니다.
colima가 Docker Desktop보다 빠르다는 오해
정리하면서 "그럼 Docker Desktop보다 빠르겠네"라는 생각이 들었는데, 이건 반만 맞습니다.
컨테이너가 도는 속도는 사실상 같습니다. 둘 다 리눅스 VM을 띄우고 그 안에서 같은 도커 엔진을 돌립니다. Docker Desktop도 애플 실리콘에서는 이미 Virtualization.framework와 VirtioFS를 기본으로 씁니다. 제가 --vm-type vz --mount-type virtiofs로 고른 것과 같은 것입니다. 같은 커널에 같은 엔진이니 차이가 날 구석이 없습니다.
다른 것은 상주 비용입니다. Docker Desktop은 VM 말고도 대시보드와 자동 업데이트와 백그라운드 헬퍼가 함께 돕니다. colima 쪽 호스트 상주를 재 보니 limactl 두 프로세스와 ssh 포워딩을 합쳐 149MB였습니다. 다만 이 기계에 Docker Desktop이 없어서 맞대고 재지는 못했습니다. 한쪽만 잰 숫자입니다.
그리고 맥에서 도커가 느리다는 말은 거의 항상 바인드 마운트 이야기라는 점도 짚어 둘 만합니다. 호스트 파일시스템을 VM 안으로 넘기는 지점이라 node_modules처럼 파일이 수만 개인 것을 마운트하면 확 느려집니다. 제 경우는 postgres가 도커 볼륨을 쓰기 때문에 그 경계를 한 번도 안 건넙니다. 유명한 차이가 아예 해당되지 않는 것이고, 그래서 마운트 방식 논쟁을 따라갈 필요도 없었습니다.
굳이 순위를 매기면 셋 중 가장 빠른 것은 OrbStack입니다. 그걸 안 고른 이유는 성능이 아니라 앞에서 말한 설치 경로였고, 바인드 마운트를 크게 쓰는 일이 생기면 그때 갈아탈 만합니다. 컴포즈 파일은 그대로 쓰면 됩니다.
아무 에러도 내지 않는 것들
이번 일에서 남는 건 colima 자체보다 그 주변입니다. 컨테이너가 뜬 시점에 저는 설치가 끝났다고 생각했는데, 실제로는 저장소 하나가 더러워져 있었고 자동 기동을 걸자 배터리가 깎이고 있었습니다. 둘 다 아무 에러도 내지 않았습니다.
둘을 발견한 경위도 비슷합니다. 하나는 다른 일을 하다 git status를 우연히 봤고, 하나는 Running: false가 이상해서 로그를 열었습니다. 찾으려고 찾은 게 아닙니다.
그래서 도구를 새로 깔고 나면 두 가지를 습관으로 하려고 합니다. 홈 디렉터리의 설정 파일 중 저장소로 링크된 것들의 git status를 한 번 보는 것, 그리고 백그라운드에 뭔가를 등록했으면 그게 몇 초에 한 번 무엇을 하는지 로그로 확인하는 것입니다. 둘 다 1분이 안 걸리는데, 이번엔 둘 다 안 해서 한참 뒤에 발견했습니다.
함께 읽기
- 드라이런과 멱등성: 사고를 막는 시점의 차이배포 스크립트에 안전장치를 넣는다고 할 때 두 가지가 자주 같이 나옵니다.
- 새 맥북 개발 환경 세팅: git clone이 가져오지 않는 것들맥북을 새로 받고 하던 프로젝트를 이어서 하려 했습니다. 리포를 클론하고 npm install 을 돌린 다음 개발 서버를 띄웠는데, 브라우저에 이게 떴습니다.
- xcode-select와 DEVELOPER_DIR: 어느 쪽이 정석일까?Expo 앱을 iOS 시뮬레이터에 띄우려고 도구부터 점검했는데, 결과가 앞뒤가 안 맞았습니다.
- prisma generate 를 어디서 돌릴까: postinstall 과 도커 빌드 캐시새 맥에 프로젝트들을 하나씩 세우는 중이었습니다. 클론하고, 의존성을 깔고, 개발 서버를 띄우면 이 화면이 떴습니다.
- 환경변수로 둔 CLI 기본값: 비대화형 셸에서 사라지는 이유문서를 저장하는 CLI를 하나 만들었습니다. 저장할 때마다 그 문서가 어디로 떨어졌는지 찾는 일이 반복돼서, 새 문서는 인박스로 가도록 기본값을 정했습니다. 터미널에서 쳐 보니 의도한 자리에 들어갔습니다.