작업 공간 배포
원천 · framework/evomind/web/workflow-project-evo-service기준 · 릴리스 v0.12.0커밋 · 2e32c6cc파일 · docs/operations/workspace-deploy.md기준선 · 2026-11-12
웹 workflow 작업 공간(~170)을 compose 로 배포하는 구성이다. 기본은 꺼짐이고, 켜지 않으면 기존 채팅·API 동작은 그대로다. 실행기는 workspace-runner.md, 도구·승인은 workspace-tools.md, 사용자 창은 workspace-user-window.md 를 따른다.
1. 무엇을 띄우나
섹션 제목: “1. 무엇을 띄우나”deploy/docker-compose.yml 의 workspace 프로필에 세 서비스가 있다. docker compose up -d 는 프로필이 없어
이들을 건드리지 않는다 — 켤 때만 --profile workspace 로 띄운다.
| 서비스 | 하는 일 | 외부 포트 |
|---|---|---|
workspace-runner |
사용자별 uid 로 git·glab·evo-workflow 를 실행하는 실행기. root 로 시작해 SETUID·SETGID·CHOWN 만 갖는다 | 없음 |
workspace-gateway |
evo-api → 실행기 방향 TCP 전달(8090). evo-api 와 실행기가 같은 네트워크에 붙지 않게 사이에 둔다 |
없음 |
workspace-egress |
실행기 도구의 유일한 외부 경로. 허용 목록의 호스트 :443 만 통과하는 Squid 프록시 |
없음 |
| 네트워크 | 붙는 서비스 | 성질 |
|---|---|---|
default |
postgres·app·web | 기존 |
workspace-api |
app·gateway | 내부 전용 — 밖으로 나가는 경로 없음 |
workspace-home |
runner·gateway·egress | 내부 전용 — 실행기가 닿을 수 있는 곳은 게이트웨이와 프록시뿐 |
workspace-egress |
egress | 인터넷에 닿는 유일한 곳(프록시만) |
사용자 공간은 볼륨 workspaces(/work)에 쌓인다. 지워도 계정·대화·토큰은 남는다(공간만 초기화).
2. 격리
섹션 제목: “2. 격리”- 실행기 컨테이너는 내부 네트워크에만 붙어 인터넷·호스트로 가는 기본 경로가 없다. 닿는 것은
workspace-gateway:8090(TCP 전달)과workspace-egress:3128(프록시)뿐이다. - 실행기 안에서 evo-api·postgres 이름은 해석되지 않는다(다른 네트워크). 같은 호스트의 게시 포트로도 나갈 수 없다.
- 도구가 밖으로 나가는 길은 프록시 하나이고, 프록시는
allowed-hosts.txt의 호스트:443만 통과시킨다 (http_access deny all). clone·push·glab·evo-workflow 가 그 호스트만 본다. - 실행기가 만든 도구 프로세스는 그 컨테이너의 네트워크를 그대로 쓰므로 같은 제약을 받는다.
설치 뒤 실행기 컨테이너에서 확인한다(예: .env 의 WORKSPACE_ALLOWED_GIT_HOSTS 가 <GitLab 호스트> 일 때):
P=<compose 프로젝트> # 예: evomind-chat# 1) 앱·DB 로 가는 경로가 없다 — 이름이 해석되지 않아야 한다.docker compose -p "$P" exec workspace-runner nslookup app || echo "app 해석 불가(정상)"docker compose -p "$P" exec workspace-runner nslookup postgres || echo "postgres 해석 불가(정상)"# 2) 게이트웨이·프록시는 닿는다.docker compose -p "$P" exec workspace-runner nslookup workspace-gatewaydocker compose -p "$P" exec workspace-runner nslookup workspace-egress# 3) 허용 호스트는 프록시로만, 직접으로는 닿지 않는다.docker compose -p "$P" exec workspace-runner sh -c \ 'git ls-remote https://<GitLab 호스트>/<그룹>/<프로젝트>.git >/dev/null' && echo "직접 접속이 열려 있음(설정 확인)"docker compose -p "$P" exec workspace-runner sh -c \ 'HTTPS_PROXY=http://workspace-egress:3128 git ls-remote https://<GitLab 호스트>/<그룹>/<프로젝트>.git >/dev/null' \ && echo "프록시 경유 접속 정상"- 3)의 첫 명령이 성공하면 격리가 깨진 것이다(예: 실행기를
default네트워크에 붙였다). 즉시 서비스를 내리고docker-compose.yml의networks를 본다. - 허용 목록에 없는 호스트는 프록시 경유도 403 이다(
allowed-hosts.txt확인 — §4).
3. 설정 (.env)
섹션 제목: “3. 설정 (.env)”| 변수 | 기본 | 설명 |
|---|---|---|
WORKSPACE_ENABLED |
false |
기능 전체 스위치. 켜야 도구·창이 보인다(사용자별 workspace 기능 권한도 있어야 한다) |
WORKSPACE_GITLAB_HOST |
— | clone·push 대상(https). 계정 메뉴·인증 상태에 쓰인다 |
WORKSPACE_TOKEN_KEY |
— | 사용자별 GitLab 토큰 암호화 키(base64, 32바이트, 비밀). 비우면 사용자별 등록을 열지 않는다 — workspace-token.md |
WORKSPACE_GITLAB_TOKEN |
— | 사용자 등록을 쓰지 않을 때의 로컬(공용) GitLab 토큰(비밀,.env 에만) |
WORKSPACE_ALLOWED_GIT_HOSTS |
— | clone 허용 호스트(스킴 없이, 쉼표 구분, :443 만). 비우면 clone 모두 거절. egress 허용 목록과 같은 값 |
WORKSPACE_RUNNER_URL |
— | evo-api 가 부르는 실행기 주소. compose 구성에서는 http://workspace-gateway:8090 |
WORKSPACE_RUNNER_TOKEN |
— | 서비스 토큰(32자 이상, 비밀). 실행기의 RUNNER_TOKEN 과 같은 값 |
WORKSPACE_HTTP_PROXY |
— | 실행 도구가 쓰는 프록시. compose 구성에서는 http://workspace-egress:3128 |
EVOMIND_WORKSPACE_IMAGE |
evo-workspace:latest |
실행기 이미지. 번들 설치·개발서버는 install/수동으로 채운다 |
WORKSPACE_USER_QUOTA_BYTES |
10737418240 |
사용자당 디스크 할당량(0 = 검사 안 함) |
WORKSPACE_EXEC_TIMEOUT_SEC |
120 |
실행 시간 상한(초) |
WORKSPACE_MAX_OUTPUT_BYTES |
262144 |
실행 출력 상한(바이트) |
WORKSPACE_MAX_PROCESSES |
128 |
실행당 프로세스·스레드 수 |
WORKSPACE_MAX_FILE_BYTES |
1073741824 |
실행이 쓸 수 있는 파일 하나의 크기 |
WORKSPACE_MEMORY_LIMIT·WORKSPACE_CPUS·WORKSPACE_PIDS_LIMIT |
2g·2·512 |
컨테이너 전체 상한(실행별 상한과 별개) |
WORKSPACE_PROTECTED_BRANCHES |
main,master,develop,release/* |
push 를 거절할 보호 브랜치 |
WORKSPACE_RUNNER_TOKEN이 비었거나 32자 미만이면 실행기 컨테이너가 기동하지 않고 로그에 이유만 남긴다(값은 남기지 않는다).- 호스트 목록에는 호스트 이름만 쓴다(스킴·포트·경로·IP·와일드카드 없이). 형식이 틀리면 목록 생성이 실패한다.
- 프록시는
:443만 연다 — GitLab 이 다른 포트를 쓰면 이 배포 구성으로는 clone 할 수 없다.
4. 설치
섹션 제목: “4. 설치”번들(폐쇄망)
섹션 제목: “번들(폐쇄망)”install.sh/upgrade.sh 가 하는 일은 그대로이고, 다음이 더해진다.
- 번들에
images/evo-workspace.tar가 있으면docker load하고.env의EVOMIND_WORKSPACE_IMAGE를 채운다. 없으면 기능을 켤 수 없다(이미지가 필요하다). .env로deploy/workspace-egress/allowed-hosts.txt를 만든다(workspace-egress/render.sh).docker compose up -d— 프로필이 없으므로 앱·화면·DB 만 뜬다(기존과 같다).
작업 공간을 켜려면 .env 를 채운 뒤:
cd <설치 폴더>sh workspace-egress/render.sh .env # 호스트 목록을 고쳤을 때마다docker compose --env-file .env --profile workspace up -d개발서버
섹션 제목: “개발서버”개발서버의 두 인스턴스(조직용·일반용)는 작업 공간이 켜져 있다(2026-10-31 격리·실행 확인). 서버에는 상주 .env 가
없고 배포는 표준 입력으로 env 를 넘기므로, 실행기를 올리거나 갱신할 때는 배포에 쓰는 CI File 변수
(DEPLOY_DEV_ORG_ENV_FILE·DEPLOY_DEV_PERSONAL_ENV_FILE)의 내용을 서버에서 권한 600 임시 파일로 받아 쓴다.
cd <서버 배포 폴더> # deploy/docker-compose.yml 이 있는 폴더umask 077; cat > /tmp/ws-<프로젝트>.env # CI File 변수 내용을 붙여 넣는다(셸 기록·저장소에 남기지 않는다)# compose 는 파일 전체를 해석하므로 지금 도는 앱·화면 이미지 태그도 넘겨야 한다(값은 컨테이너에서 읽는다).export EVOMIND_IMAGE=$(docker inspect -f '{{.Config.Image}}' <프로젝트>-app-1)export EVOMIND_WEB_IMAGE=$(docker inspect -f '{{.Config.Image}}' <프로젝트>-web-1)# 1) 허용 목록을 지금 호스트로 다시 만든다(호스트를 바꿀 때마다).sh deploy/workspace-egress/render.sh /tmp/ws-<프로젝트>.env# 2) 실행기 이미지를 받아 올린다(태그는 develop 의 dev_workspace 가 게시한 것을 쓴다).docker compose -p <프로젝트> --env-file /tmp/ws-<프로젝트>.env -f deploy/docker-compose.yml \ --profile workspace pull workspace-runnerdocker compose -p <프로젝트> --env-file /tmp/ws-<프로젝트>.env -f deploy/docker-compose.yml \ --profile workspace up -drm -f /tmp/ws-<프로젝트>.env- 앱·화면 배포(
deploy)는 프로필 없이up -d하므로 작업 공간 서비스를 건드리지 않는다. 실행기를 올릴 때만 위처럼--profile workspace로 올린다(서비스 하나만 갱신할 때는up -d workspace-runner). - 실행기 이미지 태그는 서버 파일이 아니라 위 CI File 변수의
EVOMIND_WORKSPACE_IMAGE를 바꾼다. 실행기 이미지는 개발판 evo·glab 을 담으므로, evo 개발판이 올라가면dev_workspace가 게시한 새 태그로 바꿔 다시 올린다. - 허용 목록(
render.sh)과 실행기 이미지 태그는 사람이 갱신한다. evo-infra 는 배포 스크립트가 이 둘을 자동으로 처리하도록 하지 않기로 했다(2026-10-31) — root 로 도는 실행기의 입력 범위를 넓히지 않기 위해서다. - 배포 경로가 둘이다. 키가 붙은 러너는 원격 경로로 서버 사본(
/opt/evo-deploy/…)의 compose 를 쓰고, 서버 자신에서 도는 러너는 CI 체크아웃의 compose 를 쓴다. 그래서 서버 사본이 낡으면 원격 경로 배포에서만 설정이 빠진다 (의WORKSPACE_TOKEN_KEY누락 사례). 배포 뒤에는 사본과 이 저장소 develop 머리의 파일 해시·SOURCE_COMMIT이 같은지 확인한다.
서버 사본에 있어야 하는 파일 (evo-infra)
섹션 제목: “서버 사본에 있어야 하는 파일 (evo-infra)”| 파일 | 바뀐 것 |
|---|---|
deploy/docker-compose.yml |
workspace 프로필의 세 서비스, workspace-api·workspace-home·workspace-egress 네트워크, workspaces 볼륨, app 의 WORKSPACE_HTTP_PROXY·WORKSPACE_TOKEN_KEY |
deploy/workspace-egress/squid.conf |
새 파일 — egress 프록시 설정 |
deploy/workspace-egress/render.sh |
새 파일 — .env 로 allowed-hosts.txt 생성 |
/opt/evo-deploy는 evo-infra 소유다. 바꾸기 전에 evo-infra 에 요청서로 먼저 알린다(root 로 불리는 실행기 파일이다). 불가피하게 직접 바꿀 때는 백업한 뒤에 하고, 바꾼 뒤 사본의SOURCE_COMMIT을 develop 머리로 맞춘다.render.sh자동 실행과 실행기 이미지 태그 전달은 사본의deploy에 들어 있지 않다 — 둘 다 사람이 한다(위 ‘개발서버’).
install.sh·upgrade.sh·build-bundle.sh 는 이 저장소의 스크립트라 서버 사본과 무관하지만, 번들을 다시 만들어야
evo-workspace.tar·workspace-egress/ 가 들어간다.
5. 끄기
섹션 제목: “5. 끄기”기능을 끄면 채팅·API 는 예전과 같다(도구·계정 메뉴 항목이 사라진다).
# 1) 앱에서 기능을 끈다 — 실행 인증·도구·창이 모두 꺼진다.# .env: WORKSPACE_ENABLED=falsedocker compose --env-file .env up -d app# 2) 실행기·게이트웨이·프록시를 내린다(볼륨은 남긴다).docker compose --env-file .env --profile workspace stop- 다시 켤 때:
.env를 되돌리고 4절의 두 명령을 다시 실행한다. - 공간을 완전히 지우려면(사용자 파일·프로젝트·worktree): 실행기를 내린 뒤
docker compose --env-file .env --profile workspace down하고docker volume rm <프로젝트>_workspaces. 이때 대화·계정은 남는다(DB 는 다른 볼륨). - 기능 권한도 함께 본다:
WORKSPACE_ENABLED=true여도 사용자·그룹에workspace기능 권한이 없으면 도구·창이 보이지 않는다(workspace-tools.md).
6. 장애 대응
섹션 제목: “6. 장애 대응”| 증상 | 보는 곳 | 대응 |
|---|---|---|
| 실행기 컨테이너가 계속 다시 시작 | --profile workspace logs workspace-runner |
RUNNER_TOKEN 누락·32자 미만(값은 로그에 없다). .env 확인 |
| 프록시가 기동하지 않음 | logs workspace-egress |
allowed-hosts.txt 가 없거나 비었다 — render.sh .env 를 돌리고 다시 올린다 |
clone 이 403 host_not_allowed |
실행기 로그(JSON 한 줄), .env |
WORKSPACE_ALLOWED_GIT_HOSTS 에 그 호스트가 없다(스킴 없이, 443) |
| clone 이 프록시에서 403 | logs workspace-egress |
같은 호스트가 목록에 없다 — render.sh 다시 |
| 앱이 실행기에 502 | app 로그 | 게이트웨이·실행기가 떠 있는지(ps), WORKSPACE_RUNNER_URL 이 게이트웨이인지 |
409 busy |
— | 그 사용자의 실행이 도는 중이다(삭제·초기화만 막힌다) |
507 disk_quota |
사용자 창(사용량) | 사용자가 정리하거나 초기화한다. 상한은 WORKSPACE_USER_QUOTA_BYTES |
- 실행기는 감사를 표준 출력 한 줄 JSON 으로 남긴다(인자·환경 변수·파일 이름·경로 없음). 필요한 동안만
docker compose logs로 보고, 오래 보관하지 않는다. - 볼륨이 커지면 사용량이 많은 사용자를 찾아 관리자 화면에서 초기화한다(workspace-admin.md).
7. 시험
섹션 제목: “7. 시험”sh scripts/release/test-workspace-egress.sh # 허용 목록 생성(형식·중복·비움) — 네트워크 없이 sh 로 실행sh scripts/release/test-deploy.sh # 기존 배포 순서 회귀(프로필 서비스는 건드리지 않는다)cd backend && ./gradlew :app:test --tests '*WorkspaceAuthServiceTest' # 프록시 환경 변수- compose 문법은
docker compose -f deploy/docker-compose.yml config --profiles로 확인한다(프로필 목록에workspace). - 실제 격리는 §2 의 명령으로 설치 뒤 확인한다(CI 는 docker 데몬이 없어 실행하지 않는다).
답변은 문서를 근거로 만들지만 틀릴 수 있습니다. 출처 문서를 함께 확인하세요.