1
0
forked from baron/baron-sso
Files
baron-sso/docs/works-drive-docker-image-archive.md
2026-06-19 13:16:20 +09:00

229 lines
10 KiB
Markdown

# WORKS Drive Docker Image Archive
## 목적
WORKS Drive는 Docker Registry HTTP API v2 backend로 직접 사용하지 않는다. 대신 stage/production 공용 Docker 이미지를 `docker save` 결과물로 내보내고, zstd 압축 archive와 검증 파일을 WORKS Shared Drive에 보관하는 CLI 기반 이미지 산출물 저장소로 사용한다.
이 방식은 다음 상황을 목표로 한다.
- 공용 Registry 없이 WORKS Drive 접근 권한만으로 이미지 산출물 보관
- 작은 규모의 stage/production 배포 이미지 이관
- `docker load` 기반 오프라인 배포
Gitea Actions의 shared image publish workflow는 `baron_sso/<service>:<image_tag>` 형태의 로컬 이미지를 빌드한 뒤 WORKS Drive archive로 업로드한다. Harbor registry login/push/pull은 이 publish 흐름의 필수 조건이 아니다. staging/production은 같은 image tag 계약을 공유하며, WORKS Drive archive를 검증한 뒤 `docker load`로 배포 대상 호스트에 적재하는 흐름으로 확장한다.
## 현재 Gitea Actions 설정 상태
2026-06-19 기준 Docker image archive 업로드 단계는 `.gitea/workflows/image_publish.yml``Upload built images to WORKS Drive archive` step에서 실행된다. 이 workflow는 stage/production 공용 산출물을 만들며 `dev` branch의 commit hash 4자리로 immutable tag를 계산한다.
업로드를 실행하려면 최소한 다음 값을 등록해야 한다.
- variable `WORKS_SHAREDRIVE_DOCKER_IMAGE_DIR=docker-build-image`
- variable `WORKS_DRIVE_SHARED_DRIVE_ID`
- 선택 variable `WORKS_DRIVE_PARENT_FILE_ID`
- secret `WORKS_DRIVE_ACCESS_TOKEN`, 또는 variable `WORKS_DRIVE_ACCESS_TOKEN_FILE`, 또는 variable `WORKS_DRIVE_ACCESS_TOKEN_CMD`, 또는 refresh-token 방식의 secret `WORKS_DRIVE_OAUTH_REFRESH_TOKEN`
- refresh-token 방식을 쓸 경우 secret `WORKS_DRIVE_OAUTH_CLIENT_ID`, secret `WORKS_DRIVE_OAUTH_CLIENT_SECRET`
서비스 계정 JWT 방식은 upload script의 fallback으로 남아 있지만 shared image publish workflow의 기본 필수 인증값은 아니다.
## WORKS Drive 토큰 운영
WORKS OAuth의 Access Token은 Developer Console 설정에 따라 1시간 또는 24시간 동안 유효하고, Refresh Token은 90일 동안 유효하다. 따라서 Gitea secret에 `WORKS_DRIVE_ACCESS_TOKEN`만 고정해 두는 방식은 publish workflow가 장시간 중단된 뒤 재실행될 때 실패할 수 있다.
`image_publish.yml`은 업로드 직전에 `Resolve WORKS Drive access token` step을 실행한다.
- `WORKS_DRIVE_ACCESS_TOKEN`이 있으면 이를 마스킹한 뒤 해당 workflow run 안에서만 사용한다.
- `WORKS_DRIVE_ACCESS_TOKEN_FILE` 또는 `WORKS_DRIVE_ACCESS_TOKEN_CMD`가 있으면 그 결과를 같은 방식으로 사용한다.
- 위 값이 없고 `WORKS_DRIVE_OAUTH_REFRESH_TOKEN`이 있으면 `grant_type=refresh_token`으로 새 Access Token을 발급하고, 이후 다섯 개 이미지 업로드는 모두 이 Access Token을 공유한다.
Refresh Token Rotation이 켜져 있으면 WORKS가 refresh 응답에 새 Refresh Token을 포함할 수 있다. workflow는 이 값을 로그에 노출하지 않도록 마스킹하고 `${RUNNER_TEMP}/works-drive-rotated-refresh-token``0600` 권한으로 캡처한다. 다만 Gitea repository secret을 자동 갱신하려면 별도의 secret 쓰기 권한이 있는 Gitea token과 secret update 절차가 필요하므로, 기본 publish workflow는 repository secret을 직접 변경하지 않는다.
운영 권장값은 다음 중 하나다.
- Refresh Token Rotation을 끄고 `WORKS_DRIVE_OAUTH_REFRESH_TOKEN`으로 매 run마다 Access Token만 자동 발급한다.
- Rotation을 켠 경우 publish run에서 rotated refresh token 경고가 나오면 `WORKS_DRIVE_OAUTH_REFRESH_TOKEN` secret을 수동 갱신한다.
- secret 자동 갱신이 필요하면 Gitea secret write 전용 token을 별도 설계로 추가한다.
## 저장 구조
기본 최상위 디렉터리는 다음 환경 변수로 지정한다.
```dotenv
WORKS_SHAREDRIVE_DOCKER_IMAGE_DIR=docker-build-image
```
이미지는 WORKS Shared Drive에서 다음 구조로 저장한다.
```text
docker-build-image/<repository-path>/<tag>/
image.tar.zst
image.tar.zst.sha256
manifest.json
```
예시:
```text
docker-build-image/baron_sso/backend/v1.2606.ab12/
image.tar.zst
image.tar.zst.sha256
manifest.json
```
Registry hostname은 저장 경로에서 제외한다. 예를 들어 `registry.example/baron_sso/backend:v1.2606.ab12``baron_sso/backend/v1.2606.ab12` 아래에 저장한다.
## Manifest
`manifest.json`에는 다음 정보를 기록한다.
- archive format: `docker-save-zstd`
- 원본 `image_ref`
- repository path
- tag
- Docker image id
- Git commit
- archive 파일명, 크기, sha256
- WORKS Drive remote path
- 복원 명령 예시
복원은 다음 흐름으로 처리한다.
```bash
sha256sum -c image.tar.zst.sha256
zstd -d -c image.tar.zst | docker load
```
## 업로드 CLI
로컬 컨테이너를 먼저 이미지로 commit한 뒤 업로드하려면 다음처럼 실행한다.
```bash
WORKS_SHAREDRIVE_DOCKER_IMAGE_DIR=docker-build-image \
WORKS_DOCKER_COMMIT_CONTAINER=baron_backend \
DOCKER_IMAGE_REF=registry.example/baron_sso/backend:v1.2606.ab12 \
scripts/docker-image/upload_works_drive.sh
```
이미지가 이미 로컬에 있으면 `WORKS_DOCKER_COMMIT_CONTAINER`를 생략한다.
```bash
DOCKER_IMAGE_REF=registry.example/baron_sso/backend:v1.2606.ab12 \
scripts/docker-image/upload_works_drive.sh
```
실제 업로드에는 기존 백업 업로드와 같은 WORKS Drive 인증 변수를 사용한다.
- `WORKS_DRIVE_TARGET=sharedrive`
- `WORKS_DRIVE_SHARED_DRIVE_ID` 또는 `WORKS_SHAREDRIVE_ID`
- 선택: `WORKS_DRIVE_PARENT_FILE_ID`
- `WORKS_DRIVE_ACCESS_TOKEN`, `WORKS_DRIVE_ACCESS_TOKEN_FILE`, `WORKS_DRIVE_ACCESS_TOKEN_CMD`, `WORKS_DRIVE_OAUTH_REFRESH_TOKEN`, 또는 서비스 계정 OAuth 변수
업로드 전 packaging만 확인하려면 다음을 사용한다.
```bash
WORKS_DRIVE_DRY_RUN=true \
DOCKER_IMAGE_REF=registry.example/baron_sso/backend:v1.2606.ab12 \
scripts/docker-image/upload_works_drive.sh
```
dry-run도 실제 `docker save`, `zstd`, checksum, manifest 생성을 수행한다. WORKS Drive API 호출만 생략하므로 업로드 전 산출물 검증에 사용할 수 있다.
## 다운로드 및 복원 검증 CLI
WORKS Drive에서 다음 세 파일을 같은 로컬 디렉터리로 내려받은 뒤 검증한다.
```text
image.tar.zst
image.tar.zst.sha256
manifest.json
```
checksum, manifest, zstd stream 무결성만 확인하려면 다음을 실행한다.
```bash
scripts/docker-image/verify_archive.sh /path/to/downloaded/archive
```
`make` 타깃을 사용할 수도 있다.
```bash
make docker-image-verify-works \
WORKS_DOCKER_IMAGE_ARCHIVE_DIR=/path/to/downloaded/archive
```
실제 Docker image load까지 검증하려면 다음을 실행한다.
```bash
WORKS_DOCKER_VERIFY_LOAD=true \
scripts/docker-image/verify_archive.sh /path/to/downloaded/archive
```
검증은 다음 조건을 모두 확인한다.
- `image.tar.zst.sha256` checksum 성공
- `manifest.json``schema_version=1`, `format=docker-save-zstd`
- manifest의 archive 파일명, sha256, size와 실제 파일 일치
- `zstd -t` 무결성 성공
- 선택적으로 `docker load` 성공
현재 repo에는 WORKS Drive API에서 파일을 자동 다운로드하는 CLI는 없다. 따라서 자동 다운로드 스크립트를 만들기 전까지는 WORKS Drive UI 또는 운영자가 승인한 API 도구로 세 파일을 내려받고, 위 검증 CLI로 복원 가능성을 확인한다.
API로 다운로드할 때는 대상 archive 폴더의 children을 조회해 각 파일의 `fileId`를 얻은 뒤 다음 endpoint를 호출한다.
```text
GET /v1.0/sharedrives/<sharedDriveId>/files/<fileId>/download
```
검증 결과 이 endpoint는 `302 Location`을 반환한다. `curl -L`만 사용하면 리다이렉트 대상 요청에 인증 헤더가 유지되지 않아 `UNAUTHORIZED` JSON이 파일로 저장될 수 있다. 자동화할 때는 리다이렉트 대상에도 인증 헤더를 유지하도록 처리해야 한다. `curl` 기준으로는 다음 형태를 사용한다.
```bash
curl -sS -L --location-trusted \
-H "Authorization: Bearer <access-token>" \
-o image.tar.zst \
"https://www.worksapis.com/v1.0/sharedrives/<sharedDriveId>/files/<fileId>/download"
```
smoke 검증에는 Alpine 계열보다 운영 환경과 libc/패키지 계열 차이가 적은 Debian slim 계열을 사용한다.
```bash
docker create --name baron-works-image-smoke debian:trixie-slim \
sh -c 'printf works-drive-docker-image-smoke >/works-smoke.txt'
docker start -a baron-works-image-smoke
WORKS_DOCKER_COMMIT_CONTAINER=baron-works-image-smoke \
DOCKER_IMAGE_REF=registry.example/baron_sso/works-smoke:works-test-ab12 \
scripts/docker-image/upload_works_drive.sh
```
로컬 smoke 검증 예시는 다음과 같다.
```bash
WORKS_DRIVE_DRY_RUN=true \
WORKS_DOCKER_IMAGE_ARCHIVE_DIR=/tmp/baron-sso-works-verify-smoke \
DOCKER_IMAGE_REF=alpine:latest \
scripts/docker-image/upload_works_drive.sh
scripts/docker-image/verify_archive.sh \
/tmp/baron-sso-works-verify-smoke/alpine/latest
WORKS_DOCKER_VERIFY_LOAD=true \
scripts/docker-image/verify_archive.sh \
/tmp/baron-sso-works-verify-smoke/alpine/latest
```
## Staging/Production 계약
Action에서 `dev` 브랜치를 checkout한 뒤 한 번만 이미지를 빌드하고 immutable `image_tag`를 계산한다. staging과 production은 같은 image_tag를 입력받아 같은 image archive를 사용한다.
```text
dev branch -> publish image tag vX.YYMM.<commit4> -> staging deploy -> production deploy
```
WORKS Drive archive는 Action에서 로컬로 빌드된 이미지를 `docker save`로 내보내 생성한다. 따라서 WORKS archive, staging, production은 모두 같은 immutable image tag를 기준으로 한다.
## 제한
- 이 구조는 `docker push`/`docker pull`과 호환되는 Registry backend가 아니다.
- layer deduplication이 없으므로 같은 기반 이미지가 반복 저장된다.
- 배포 전에는 반드시 `image.tar.zst.sha256` 검증 후 `docker load`를 수행해야 한다.
- tag 없는 image ref와 digest-only image ref는 지원하지 않는다.