너무 저렴해서 품질이 낮다고요? 다시 생각해보세요.
요약
CyberPanel의 무료 기능 결함과 유료 유도 방식에 실망한 저자가 aaPanel로 전환했으나, 제어권 상실 문제로 인해 Caddy 기반의 새로운 관리 도구 필요성을 느낍니다. 결국 직접 셸 스크립트와 FastAPI로 경량화된 관리 시스템을 구축하기로 결심하며 AI 코딩 도구 활용을 고려합니다.
핵심 포인트
- CyberPanel의 무료 기능 버그 및 유료 전환 유도 문제 지적
- aaPanel의 편리함과 OpenLiteSpeed 제어권 상실 사이의 트레이드오프
- Caddy 서버의 간결함과 자동 HTTPS 기능의 장점
- 경량화된 자체 관리 도구(Shell + FastAPI) 구축 필요성
- GitHub Copilot CLI 또는 Claude Code를 통한 개발 고려
수년 동안 저는 OpenLiteSpeed에서 WordPress 사이트들을 운영해 왔습니다. 빠른 서버이며, LSCache는 진정으로 인상적이고, OLS/WordPress 조합은 순수 성능 면에서 따라오기 힘듭니다. 제어판(Control Panel)의 경우, CyberPanel로 시작했습니다. Microsoft 제품보다 버그가 많았고, 팀은 사용자들이 유료 플랜으로 넘어가도록 무료 기능들을 의도적으로 방해하는 것처럼 보였습니다. 저는 고칠 수 없는 버그를 말하는 것이 아닙니다. 무료 티어 기능이 어떤 동작도 완료하지 못하도록 설계된 것처럼 보이는 버그들을 말하는 것입니다.
두 가지 사례가 있습니다. 첫 번째는 WordPress 설치입니다. 수년 동안 문제없이 사용했습니다. 하지만 CyberPanel v2.4.x 버전부터는 SQL 에러가 마지막 단계를 차단합니다. 파일은 완전히 다운로드되어 존재하지만, 데이터베이스를 수동으로 생성하고 직접 설치를 실행해야 합니다. 완곡하게 표현해도 매우 직관적이지 않은 상황입니다.
두 번째 사례는 Let's Encrypt SSL 인증서 생성입니다. 생성된 설정 파일(config files)이 올바르지 않아 지속적으로 실패합니다. 그리고 두 경우 모두, 유료인 "강화된(enhanced)" 버전을 사용할 수 있습니다. 당연하게도 말이죠.
제 입장은 간단합니다. 만약 어떤 기능이 수년 동안 잘 작동하다가 이제 작동하지 않는다면, 유료 버전이 작동할 것이라는 보장도 없으며, 내일 약관이 바뀌지 않으리라는 보장도 없습니다. 이것이 미끼 상술(bait-and-switch)인가요? 명시적으로 그렇게 말하지는 않겠습니다. 하지만 무료 기능이 수년 동안 작동하다가 여러 연속된 버전에서 작동을 멈추고, 유료 대안이 동일한 영역을 커버한다면, 질문에 대한 답은 스스로 나타납니다. 저는 질문을 던졌고, 결론을 내렸으며, 해당 벤더를 블랙리스트에 올렸습니다.
그래서 저는 aaPanel로 옮겼습니다. 더 쾌적하고, 더 안정적이며, 더 가볍습니다. 하지만 OLS 관리에 있어서는 완전히 경로를 벗어난(off-rails) 접근 방식을 취합니다. OpenLiteSpeed를 직접 구성할 수 없으며, 모든 것이 aaPanel의 추상화 계층(abstraction layer)을 통해 이루어지기 때문에 자신의 스택에 대한 제어권을 잃게 됩니다. 7080 포트에 직접 접속하면 모든 것을 망가뜨릴 위험이 있습니다. 오직 aaPanel 대시보드만 사용해야 합니다. 그것이 전부입니다.
그 후 저의 사용 패턴이 바뀌었습니다. Astro를 더 많이 사용하고, 준정적 사이트 (quasi-static sites) 비중이 늘어났으며, PHP가 필요 없는 프로젝트가 많아졌습니다. WordPress의 경계를 벗어나는 순간 OLS의 매력은 사라집니다. 반면 Caddy는 HTTPS를 자동으로 처리하고, 설정 (config)이 몇 줄의 읽기 쉬운 코드로 구성되며, OLS 특유의 재작성 (rewrite) 기벽도 없습니다.
질문은 이것이었습니다: aaPanel/OLS를 Caddy와 컨트롤 패널 (control panel)로 대체할 수 있을까? GitHub에 하나가 있긴 합니다 — CaddyManager인데, 별(star)은 1.1k 개이고, 기여자는 단 한 명이며, 영원히 "초기 개발 (early development)" 단계에 머물러 있습니다. 8시간 만에 만들어진 Caddyfile 생성기인 CaddyGen도 있지만, 이는 완성된 제품이라기보다 개념 증명 (proof-of-concept)에 가깝습니다. 프로덕션 환경에 바로 사용할 수 있는 것은 아무것도 없었습니다.
결론은 명확했습니다: 잘 작성된 셸 스크립트 (shell scripts)와 최소한의 FastAPI 인터페이스라면 충분히 제 역할을 할 것이며, 무한히 더 유지보수하기 쉬울 것이라는 점입니다. 누군가 그것을 작성하기만 하면 됩니다.
제가 직접 하는 대신, GitHub Copilot CLI에 사양 (spec)을 전달할까 생각했습니다. 혹은 Claude Code를 써볼까도 생각했죠. 하지만 청구서가 날아오기도 전에 입술만 겨우 적실 수 있을 정도로 비싸진 Copilot의 새로운 가격 정책을 고려했을 때... 저는 DeepInfra나 OpenRouter에 연결된 OpenCode와 Kilo CLI에 흥미가 생겼습니다. 그리고 이것을 하나의 벤치마크 (benchmark)로 만들기로 결심했습니다.
📋 요약 (TL;DR): 실제 VPS 프로젝트에서 8가지 도구/모델 조합을 테스트했습니다. 두 단계로 진행되었습니다 — 아키텍처 (architecture) 설계 후 코드 (code) 작성. 승부를 가리기 위한 독립적인 외부 리뷰까지 포함됩니다. 프로덕션 환경에 적합하다고 판단된 유일한 툴킷의 총비용은 1.94달러였습니다. 우승 모델은? 아마 일반적인 비교 분석에서는 보지 못했을 모델일 것입니다.
💡 읽기 참고 사항: 섹션 5 전까지, 코드 단계에서 선택된 네 가지 구현체는 A, B, C, D로 식별됩니다. 모델 이름은 외부 리뷰 판결 후에 공개됩니다 — 배심원단의 이름을 익명 처리하는 것과 같은 이유입니다: 라벨을 읽기 전에 코드를 먼저 읽으십시오.
1. 테스트 프로젝트
요구 사항은 의도적으로 구체적이었습니다: Ubuntu 24.04를 위한 최소한의 VPS 관리 툴킷입니다. 웹 서버로는 Caddy, PHP-FPM은 두 가지 버전(현재 버전 및 폴백(fallback) 버전), MariaDB 및 PostgreSQL, 객체 캐싱(object caching)을 위한 Valkey를 사용합니다. 모든 작업에는 셸 스크립트(Shell scripts)를 사용하며, 자동화를 위한 FastAPI 인터페이스를 제공합니다. Docker, 컨트롤 패널, 불필요한 추상화(abstraction)는 배제합니다.
처리해야 할 사이트 유형은 네 가지입니다: 정적(static, HTML/에셋만 포함, PHP 및 데이터베이스 없음), PHP(커스텀 앱, 선택적 데이터베이스), WordPress(WP-CLI를 통한 전체 설치, 데이터베이스 필수), 그리고 리버스 프록시(reverse proxy)입니다. 마지막 유형은 별도의 설명이 필요합니다. 이는 단순히 동일한 서버에서 실행되는 Node.js, FastAPI 또는 Go 애플리케이션으로 요청을 전달하는 Caddy vhost일 뿐입니다. Caddy가 HTTPS와 도메인을 처리하므로 애플리케이션은 이를 신경 쓸 필요가 없습니다. PHP-FPM도, 데이터베이스도 필요 없습니다. 단지 reverse_proxy 블록과 포트 번호만 있으면 됩니다.
예상되는 작업은 전체 라이프사이클을 다룹니다: 서버 부트스트랩(bootstrap), 사이트 프로비저닝(provisioning), 파괴적인 작업 전 자동 백업을 포함한 삭제, 온디맨드(on-demand) 데이터베이스 생성, rsync를 통한 정적 배포, 백업, 그리고 서비스 관리입니다.
왜 합성 벤치마크(synthetic benchmark) 대신 실제 프로젝트를 선택했을까요? 합성 벤치마크는 모델이 이상적인 조건에서 무엇을 할 수 있는지를 테스트하기 때문입니다. 실제 프로젝트는 보안, 멱등성(idempotency), 파일 간 일관성, 셸(shell)과 Python 레이어 간의 에러 처리와 같이 제약 조건이 쌓였을 때 모델이 어떻게 작동하는지를 테스트합니다. 바로 이 지점에서 차이가 드러납니다.
전체 기능 요구 사항은 프로젝트의 GitHub 저장소에서 확인할 수 있습니다.
2. 방법론 (Methodology)
프로토콜은 인간의 검증을 통해 분리된 두 가지 별도의 단계로 진행됩니다.
1단계 — 아키텍처 (Architecture)
각 도구/모델 조합에 동일한 기능 요구 사항이 제출됩니다. 추가적인 컨텍스트, 설정 파일, 또는 예상되는 솔루션에 대한 힌트는 제공되지 않습니다. 도구는 아키텍처, 프로젝트 구조, 각 역할이 명시된 스크립트 목록, API 경로 맵(route map)을 제안합니다. 그리고 설계가 잘 되었다면, 무언가를 생성하기 전에 질문을 던질 것입니다.
2단계 — 구현 (Implementation)
계획이 검증되고 결정이 내려지면, 단일 개발 프롬프트 (development prompt)가 모든 도구에 제출됩니다. 여기에는 검증된 아키텍처 (architecture), 확정된 10가지 기술적 결정 사항, script→API 종료 코드 (exit code) 컨벤션, 그리고 단 하나의 명확한 지시 사항이 포함됩니다: 요약이나 지름길 없이, 순서대로 30개의 파일을 디스크에 저장할 것.
테스트된 조합 (Combinations tested)
| 도구 (Tool) | 모델 (Model) |
|---|---|
| Claude Code | Haiku 4.5 |
| ... |
Devstral 2 (123B)가 계획되었습니다. 불행히도 이 모델은 OpenCode나 Kilo CLI의 모델 선택기 (model selector)에 나타나지 않습니다. 두 도구 모두 models.dev에서 카탈로그를 가져오는데, OpenRouter에서 사용 가능함에도 불구하고 아직 인덱싱되지 않았기 때문입니다. OpenRouter 플레이그라운드 (playground)를 통한 테스트 결과 API를 통해 모델에 접근할 수 있음을 확인했지만, 코딩 에이전트 (coding agent) 외부에서는 우리가 측정하려는 요소의 대부분을 상실하게 됩니다. Devstral 2가 제외된 것은 순전히 기술적인 이유 때문이며, 품질 때문이 아닙니다.
Haiku 4.5는 세 가지 서로 다른 도구에서 세 번 등장합니다. 이는 의도적인 것입니다. 모델과 독립적으로 도구의 영향력을 격리하여 측정할 수 있게 해주는 바로 그 요소이기 때문입니다.
코드 단계 (code phase)는 5절에서 공개될 때까지 A, B, C, D로 라벨링된 네 가지 대표적인 구현체에서 실행되었습니다.
외부 검토 (External review)
네 가지 구현체에 의해 생성된 코드는 벤치마크에 포함되지 않은 모델에 제출되었으며, 보안 (security), 정확성 (correctness), 멱등성 (idempotency), 코드 품질 (code quality), 완결성 (completeness)이라는 고정된 평가 그리드 (evaluation grid)를 적용받았습니다. 구현체당 5개의 대표 파일을 선정하여 25점 만점으로 점수를 매겼습니다.
3. 계획 단계 (Planning phase) — 실제로 생각하는 것은 누구인가?
기능 명세서 (functional brief)는 각 도구에 암묵적인 질문을 던집니다: 미리 만들어진 해결책이 없는 개방형 프로젝트를 넘겨받았을 때 당신은 무엇을 하는가?
가장 먼저 눈에 띄는 — 그리고 매우 놀라운 점은 — 테스트된 모델 중 그 어떤 모델도 계획을 생성하기 전에 질문을 던지지 않는다는 것입니다. 단 하나도 없습니다. 모든 모델이 먼저 완전한 아키텍처를 전달한 다음, 마지막에 명확한 설명을 요구합니다. 이는 무언가를 그리기 전에 모호한 부분에서 작업을 멈추는 인간 아키텍트 (human architect)가 하는 행동과는 정반대입니다.
이것은 중요한 문제입니다. 사후에 제기된 여러 질문들은 만약 사전에 질문되었다면 아키텍처 결정(architectural decisions)을 바꾸었을 것입니다. 한 모델은 "디스크에 비밀 정보가 없음(no secrets on disk)"과 자격 증명(credentials)이 정당하게 필요한 애플리케이션 설정 파일(application config files) 사이의 긴장 관계를 식별해냈습니다. wp-config.php가 명백한 예시입니다. 이는 진정으로 작업을 차단할 수 있는(blocking) 질문입니다. 계획이 수립된 후에 질문되면, 이는 그저 각주(footnote)로 전락하고 맙니다.
계획이 드러내는 것
질문의 품질은 첫 번째 식별 신호입니다. 두 모델은 옵션과 권장 사항을 포함하여 구성된, 진정으로 작업을 차단할 수 있는 4~5개의 질문을 던졌습니다. 또 다른 모델은 아카이브 형식(archive format), 로그 로테이션(log rotation)과 같이 아키텍처 측면에서 아무것도 바꾸지 못할 8개의 일반적인 질문을 던졌습니다.
제안된 구조는 두 번째 신호입니다. 오직 한 모델만이 스크립트로 분기(dispatch)하는 통합 CLI 진입점(entry point)인 bin/vpsmgr을 자발적으로 제안했습니다. 이는 단순한 스크립트 모음을 일관된 도구(coherent tool)로 바꾸는 디테일입니다. 다른 모델들은 이를 생각하지 못했습니다.
한 모델은 계획 단계부터 정규화되고 문서화된 종료 코드(exit code) 컨벤션을 제안한 유일한 모델이었습니다:
| 코드 | 의미 | HTTP |
|---|---|---|
| 0 | 성공 | 200 |
| ... |
이것은 외관상의 문제가 아닙니다. 이는 셸 스크립트(shell scripts)와 FastAPI 레이어 사이의 계약(contract)입니다. 이것이 없다면 HTTP 매핑은 임의적이게 되며, 각 라우트(route)가 이를 서로 다르게 구현하게 됩니다.
계획 단계 비용
| 도구 + 모델 | 토큰 (Tokens) | 비용 (Cost) |
|---|---|---|
| BigPickle | ~35k | $0 |
| ... |
Gemini 3.1 Pro는 가장 간결한 출력물을 생성했습니다 — 품질 높은 계획을 위해 27k 토큰을 사용했습니다. OpenCode의 Haiku 4.5는 더 낮은 품질을 위해 69k 토큰을 소비했습니다. 토큰 양이 품질을 예측하지는 않습니다.
4. 코드 단계 — 실제로 누가 결과물을 내놓는가?
코드 단계는 선택된 네 가지 구현체에 제출된 단 하나의 개발 프롬프트(development prompt)와 함께 시작됩니다. 여기에는 검증된 아키텍처, 확인된 10가지 기술적 결정, 종료 코드 컨벤션, 그리고 하나의 명확한 지침이 포함됩니다: 요약이나 지름길 없이, 의존성 순서(dependency order)에 따라 30개의 파일을 디스크에 전달할 것.
이 지점에서 모델 간의 차이가 구체화됩니다.
common.sh가 드러내는 것
공통 라이브러리(shared library)는 가장 먼저 전달되는 파일입니다. 이는 로깅(logging), 비밀 정보 처리(secret handling), 사이트 상태 관리(site state management), 비밀번호 생성(password generation) 등 다른 모든 것이 의존하는 토대입니다. 결함이 있는 common.sh는 이를 소싱(source)하는 모든 스크립트를 오염시킵니다.
모델 A는 98줄의 간결한 코드를 전달합니다. 비밀 정보 마스킹(Secret redaction)은 솔트(salts), 인증 키(authentication keys)를 포함한 10가지 WordPress 패턴을 명시적으로 모두 다룹니다. 이 특정 지점에서는 가장 완벽합니다. 도메인 검증(domain validation), require_cmd(), 원자적 상태 파일 쓰기(atomic state file writes)는 없습니다.
모델 B는 310줄을 전달합니다. readonly를 사용한 명명된 상수(Named constants), RFC-1035 정규 표현식(regex)을 사용한 normalize_domain(), 동시성 잠금(concurrency locks), mktemp+mv를 이용한 원자적 쓰기(atomic writes)를 포함합니다. 가장 풍부한 시스템 유틸리티 라이브러리입니다. 하지만 비밀 정보 마스킹에서 WordPress 솔트를 놓쳤습니다.
모델 C는 366줄을 전달합니다. 마스킹 패턴은 하드코딩되지 않고 환경 변수(environment variable)를 통해 설정 가능합니다. jq가 없을 경우 Python으로 대체되는 순수 쉘(pure-shell) JSON 헬퍼를 제공합니다. 프롬프트에 명시된 대로 출력 내용을 <<>> 마커로 감싸는 print_credentials()를 포함합니다. Jinja 의존성 없이 설정 파일을 위한 render_template()를 제공합니다. 모호한 문자(0/O/1/l/I)를 제외한 비밀번호 생성을 지원합니다. 개발 프롬프트에 문서화된 모든 예외 상황(edge case)을 예측한 유일한 구현입니다.
모델 D는 184줄을 전달합니다. 벤치마크에서 가장 독창적인 아이디어인 '명명된 함수에 캡슐화된 종료 코드(exit codes)'를 보여줍니다 — exit_input_error(), exit_conflict() 등은 단순한 exit 3 호출보다 가독성이 좋습니다. 또한 common.sh 내에 직접 json_output()을 구현하여 쉘에서 API용 JSON을 생성합니다. 원자적 쓰기나 require_cmd()는 없습니다.
당신이 직접 발견하게 될 버그 — 혹은 발견하지 못할 버그
Model C는 세션 중에 자신의 코드를 테스트합니다. schemas.py를 작성한 후, 테스트 케이스로 이를 실행하여 두 개의 버그를 찾아내고 즉시 수정합니다. 하나는 잘못 구현된 Pydantic v2 검증기(필드 간 검증을 위해 model_validator 대신 field_validator를 사용함)이고, 다른 하나는 스키마 수준에서 강제되지 않은 상호 배제(mutual exclusion)입니다. 또한 render_template()에서 경로의 / 문자로 인해 발생하는 sed 치환 문제를 발견하여, 이를 순수 bash 매개변수 확장(parameter expansion)으로 교체하여 수정합니다.
세션이 끝날 때, Model C는 다음과 같은 검증 요약(verification summary)을 제공합니다: 모든 스크립트에 대한 bash -n 실행, 모든 파일에 대한 Python AST 검사, OpenAPI 명세(spec)를 통한 19/19개 API 라우트 검증, 18/18개 bash 헬퍼 테스트 완료, PHP 폴백(fallback) 규칙 검증(8.5→8.4, 8.4→없음, 7.x 거부).
Model A는 작업을 마치기 전에 shebang을 확인합니다. Model B는 문제 해결(troubleshooting), curl 예시, 빠른 시작(quick start)을 포함한 다듬어진 사용자 문서를 제공합니다. Model D는 bash 및 Python 구문을 검증합니다. 세 모델 중 어느 것도 기능적 로직(functional logic)을 테스트하지는 않습니다.
코드 단계 비용 (Code phase costs)
| 모델 (Model) | 토큰 (Tokens) | 시간 (Time) | 코드 비용 (Code cost) | 총계 (Total) |
|---|---|---|---|---|
| A | — | 2m58s | $0 | $0 |
| ... |
Model D는 Model C가 23분 37초 동안 수행한 결과물을 9분 42초 만에 내놓지만, 기능 테스트(functional tests)는 포함되어 있지 않습니다. Model C는 세션 중에 코드를 실행하고 매 반복(iteration)마다 컨텍스트를 다시 불러오기 때문에 3.4배 더 많은 토큰을 소비합니다.
5. 외부 검토 — 그리고 공개
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기