본문으로 건너뛰기

업그레이드

scripts/upgrade.sh 래퍼는 동작 중인 설치를 in-place로 업그레이드합니다. 어떠한 작업 전에도 사전 백업을 먼저 수행하며, docker-compose pull + up -d 흐름을 사용해 이미지 해시가 변경된 서비스만 재생성합니다.

대상 독자

포털 호스트의 sudo 권한 운영자. docker-compose ps/logs에 익숙해야 합니다.

호환성 및 정책​

  • Forward-only Alembic 마이그레이션. 본 포털은 alembic downgrade를 지원하지 않습니다. 되돌리려면 사전 백업을 복원하세요(롤백).
  • 마이너·패치 업그레이드는 동일 메이저 버전 내에서 항상 in-place 지원됩니다. 메이저 업그레이드(예: 2.x → 3.x)는 별도 릴리스 노트로 안내되며 scripts/upgrade.sh를 무작정 사용하면 안 됩니다.
  • 다운타임 예상: 이미지가 변경된 서비스가 재생성되는 동안 포털이 잠시 중단됩니다. 일반적으로 30초 이내.

사전 요구사항​

  • 직전 정상 설치 상태 (즉, docker-compose -f docker-compose.yml ps 출력에서 모든 서비스가 healthy).
  • PATH의 docker-compose (V1).
  • 새 이미지 레이어와 사전 백업을 위해 5 GB 이상 여유 디스크.
  • 의도한 IMAGE_TAG가 .env에 명시 (혹은 마법사 기본값 0.11.0 사용). 사설 레지스트리를 운영한다면 IMAGE_TAG는 그곳에 게시된 매니페스트와 일치해야 합니다.

1단계 — 업그레이드 시점 점검​

조용한 시점을 선택합니다(진행 중인 스캔 없는 시점). 대시보드 /scans에서 전역 큐가 모두 비기를 기다리세요.

docker-compose -f docker-compose.yml ps

모든 행이 Up (healthy)여야 합니다. 재시작 중이거나 unhealthy인 서비스가 있으면 먼저 그것을 해결하세요. 깨진 설치 위에 업그레이드를 쌓지 마세요.

2단계 — 업그레이드 래퍼 실행​

bash scripts/upgrade.sh

흐름:

  1. 사전 백업: bash scripts/backup.sh (필수, 건너뛰는 플래그 없음).
  2. docker-compose pull: 새 이미지 가져오기.
  3. docker-compose up -d: 이미지 해시가 변경된 서비스만 재생성.
  4. alembic upgrade head: 새로운 마이그레이션 적용.
  5. Health 프로브: /health를 최대 60초간 폴링.

성공 시 출력:

✓ backend is healthy
Upgrade complete
If something looks off, restore the pre-upgrade backup:
bash scripts/restore.sh $(ls -td backups/* | head -1)

3단계 — 업그레이드 검증​

  1. 포털에 로그인.
  2. /admin/health 방문. 모든 컴포넌트가 녹색이어야 합니다.
  3. 알려진 프로젝트(혹은 가장 최근 스캔된 프로젝트)에 작은 스캔을 실행. WebSocket 진행률이 Completed까지 이동하는지 확인.
  4. 릴리스 노트가 새 admin 화면이나 설정을 안내한다면 한 번씩 점검합니다.

롤백​

업그레이드 후 포털이 망가졌다면 사전 백업을 복원합니다.

bash scripts/restore.sh "$(ls -td backups/* | head -1)"

scripts/restore.sh 동작:

  1. 파괴적 작업을 대화형으로 확인 (y 입력).
  2. 애플리케이션 컨테이너(backend, frontend, worker, beat) 중지. PostgreSQL과 Redis는 유지.
  3. PostgreSQL 덤프(postgres.sql.gz) 복원.
  4. workspace tar(workspace.tar.gz)이 있다면 복원.
  5. 애플리케이션 컨테이너 재시작.
  6. Alembic head가 백업의 manifest.json과 일치하는지 검증.

manifest.json이 없거나 head가 일치하지 않으면 경고를 출력하며 alembic upgrade head를 수동 실행해야 합니다.

데이터 손실

restore.sh는 라이브 데이터베이스 내용과 WORKSPACE_HOST_PATH 디렉터리를 교체합니다. 되돌릴 수 없습니다. 가리키는 백업이 올바른지(ls -td backups/*는 최신 순) 반드시 확인하세요.

버전 점프​

각 중간 버전이 forward-only 마이그레이션 경로를 가지는 한, 한 번의 upgrade.sh 실행으로 여러 버전 점프가 가능하며 한 번에 2.0.0 → 2.0.5는 지원됩니다. CI는 빈 데이터베이스뿐 아니라, 최신 배포 릴리스에서 개발 최신본으로 올릴 때 API로 데이터를 채운 데이터베이스에서도 마이그레이션 체인을 검증합니다. 옮길 행이 있을 때만 어긋나는 데이터 마이그레이션을 릴리스 전에 잡기 위해서입니다.

래퍼는 다음 두 경우를 아무것도 바꾸기 전에 거부합니다.

  • 다운그레이드. 마이그레이션은 forward-only이므로 IMAGE_TAG를 이전 릴리스로 되돌려도 되돌아가지 않습니다. 더 새로운 스키마 위에서 더 오래된 코드가 뜰 뿐입니다. 되돌리려면 백업을 복원하세요.
  • 메이저 건너뛰기. 예를 들어 1.x에서 3.x로 바로 가는 경우입니다. 각 메이저의 수동 절차는 그 메이저의 릴리스 노트에 있고, 래퍼 자체의 마이그레이션 사전 작업도 메이저 한 단계씩을 전제합니다.

두 검사 모두 실행 중인 백엔드 컨테이너의 이미지 태그와 옮겨 갈 IMAGE_TAG를 비교합니다. 둘 중 하나를 X.Y.Z로 읽을 수 없으면(사용자 지정 태그이거나 스택이 내려가 있는 경우) 무엇을 봤는지 알리고 업그레이드를 막지 않고 진행합니다. 의도적으로 거부를 넘기려면 UPGRADE_ALLOW_VERSION_SKIP=true를 설정하세요.

메이저 점프는 래퍼 호출 전에 릴리스 노트의 "Migration steps" 섹션을 따르세요.

컨테이너 스캔 식별자 교정 이후​

지금까지 컨테이너 스캔은 이미지에 무엇이 들어 있든 모든 패키지를 Alpine 패키지로 기록했습니다. pkg:apk/{이름}@{버전}에 패키지 타입 apk 였습니다. Rocky 이미지의 rpm도, Debian 이미지의 deb도, Python 이미지 안의 pip도 모두 잘못된 생태계로 목록에 올랐습니다. 취약점을 놓치지는 않았기 때문에 개수는 맞고 값만 틀린 상태였습니다.

이제는 스캐너가 알려 주는 생태계를 그대로 기록합니다. rpm이나 deb 기반 이미지를 스캔한 적이 있는 배포에 한해 두 가지가 달라집니다.

  • 기존에 저장된 행은 다시 쓰지 않습니다. 지난 스캔은 그때 저장한 식별자를 그대로 유지합니다. 그 스캔이 실제로 본 값이기 때문입니다. 교정된 목록을 보려면 이미지를 다시 스캔하세요.
  • 다시 스캔하면 그 프로젝트의 취약점 판정이 초기화됩니다. 최초 발견 시각과 분석가가 내린 판정(수용, 오탐 등)은 컴포넌트를 기준으로 이어지는데, 교정된 패키지는 다른 컴포넌트이기 때문입니다. rpm·deb 이미지를 다시 스캔하면 취약점이 새로 발견된 것으로 나오고 판정을 다시 입력해야 합니다.

Alpine 이미지만 스캔해 온 배포는 영향이 없습니다. 그 값은 원래 맞았습니다.

흔한 문제​

alembic upgrade head가 제약 조건 위반으로 실패​

대개 실제 데이터 문제입니다. 기본값보다 앞선 행이 있는데 NOT NULL 컬럼이 추가된 경우 등입니다. 백업을 복원하고 문제 행을 분석한 후 이슈 트래커에 마이그레이션 문제를 보고하세요.

Health 프로브가 60초 후 타임아웃​

풀이 더 긴 워밍업이 필요한 서비스를 다운시켰을 수 있습니다(예: JRE 업그레이드 후 워커). 로그를 확인하세요.

docker-compose -f docker-compose.yml logs --tail=200 backend worker

backend는 떠 있는데 worker가 떠 있지 않으면 스캔이 큐에 쌓이기만 합니다. 워커를 수동으로 재시작:

docker-compose -f docker-compose.yml restart worker beat

이미지 풀 거부 (403 / 401)​

레지스트리 자격증명이 만료되었습니다. 재인증:

docker login <your-registry>

이후 bash scripts/upgrade.sh를 다시 실행합니다.

함께 보기​