• LINKS 1.0.5 — host Watchtower와의 배포 충돌 차단

    LINKS 1.0.5

    이 patch 릴리스는 이전 릴리스 1.0.4(f9171bfa75)부터 최종 소스 604d5ffec1까지를 묶었습니다. 루트 패키지는 1.0.5이고 ARCA pin은 exact 0.1.18로 그대로입니다. 1.0.3·1.0.4 배포 실패의 원인인 host Watchtower와의 충돌을 막았습니다. 모든 LINKS container에 Watchtower 제외 label을 붙였으므로, host의 다른 프로젝트용 Watchtower가 LINKS container를 교체하지 않습니다. application 코드는 바뀌지 않았습니다(Compose·test·문서). schema는 바뀌지 않았습니다. DB 초기화와 migration이 필요 없습니다.

    업그레이드 주의: 1.0.4 → 1.0.5는 sh update.sh만 실행합니다(초기화 없음)

    1. 갱신 방법과 실행 시각: 1.0.4에서는 배포 디렉터리에서 sh update.sh만 실행합니다. host에 Watchtower가 있으면 이번 첫 갱신은 Watchtower 실행 시각과 겹치지 않게 실행합니다. label이 없는 1.0.4 container는 update.sh가 다시 만들기 전까지 아직 Watchtower 대상입니다. 실행 주기는 docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' <watchtower container> | grep WATCHTOWER_SCHEDULE로 확인합니다. 6필드 cron의 첫 칸은 초이므로 30 4 * * * *는 매시 4분 30초입니다. 이번 갱신 뒤부터는 Watchtower가 LINKS container를 건드리지 않으며, Watchtower 설정은 바꾸지 않습니다. schema는 바뀌지 않았고(infra/migrations/base.sha256이 1.0.4와 같고 migration은 0개) reset.sh를 실행하지 않습니다. 갱신 전 backup(sh backup.sh)은 선택입니다. 1.0.2 이하에서 올 때는 1.0.3과 같은 1회 curl 전환을 거칩니다(운영 가이드 '최초 설치와 이전 release에서의 전환').
    2. Watchtower 제외 label: compose.yaml의 모든 service와 backup restore drill(ops/backup-verify.compose.yaml의 verify·verify-postgres)에 com.centurylinklabs.watchtower.enable: "false"가 붙었습니다. compose run으로 만드는 one-off(package worker·stager, backup, migration)와 Builder container도 이 label을 물려받습니다.
    3. 1.0.3·1.0.4 배포 실패의 원인과 복구: 1.0.3·1.0.4 배포에서 No such container, 임시 이름 <id>_links-* container, worker의 backend network 누락(ENOTFOUND postgres)이 나타났습니다. 원인은 host의 다른 프로젝트용 Watchtower였습니다. 이 Watchtower는 label-enable 없이 모든 container를 감시하고 cleanup을 켠 상태로, update.sh 실행 중에 LINKS :latest container를 교체했습니다. 이런 실패가 다시 보이면 운영 가이드 '실패와 되돌리기'의 절차를 따릅니다. 먼저 실패 시각의 Watchtower log에 Found new image·Started new container container=links-…가 있는지 확인합니다. 있으면 LINKS project의 Created 잔여 container만 확인한 뒤 지우고 sh update.sh를 다시 실행합니다. volume·bind data·원본은 지우지 않습니다.
    4. container 재생성: label이 새로 추가됐으므로 이번 갱신에서 모든 LINKS container(postgres·gateway 포함)가 한 번 다시 만들어집니다. data는 bind·volume에 그대로 남습니다.
    5. NAS 조건 gate: 릴리스 전에 격리 docker:24.0.2-dind에서 이 갱신을 검증했습니다. host Compose는 공식 Docker Compose v2.20.1 binary(release의 게시 checksum으로 확인)였고, stager는 1026:100, 원본 library 최상위는 0:0 000에 1026만 여는 ACL이었습니다. dind 안에 nickfedor/watchtower(1.23.0)를 운영과 같은 env로 띄웠습니다(label-enable 없음, WATCHTOWER_CLEANUP=true, schedule 30 4 * * * *). 이 상태에서 설치된 1.0.4 update.sh로 1.0.5 갱신, registry latest 재게시 뒤 Watchtower 갱신 실행, VX Ace 준비, 재-update까지 모두 기대대로 끝났습니다(아래 검증). Synology build(v2.20.1-6047-g6817716)와 실제 NAS의 Watchtower 일정은 gate와 다를 수 있으므로 실제 NAS 결과는 확인 전입니다.

    NAS 절차(1.0.4에서, Watchtower 실행 시각을 피해서):

    cd /volume1/docker/links
    sh update.sh
    

    성공하면 단계 표시 6줄 뒤에 Updated to 1.0.5 (catalog 8cc47b4ca0a0), runtime.package ready.가 출력됩니다. 실패하면 원인 한 줄을 출력하고 nonzero로 끝납니다. 갱신 뒤 docker inspect --format '{{index .Config.Labels "com.centurylinklabs.watchtower.enable"}}' <links container>는 false입니다. docker logs --since 1h <watchtower container> 2>&1 | grep links-에는 Found new image·Started new container가 없어야 합니다.

    원본 game library는 읽기 전용으로 유지합니다. 이번 게시 작업에서 개발·운영 DB 초기화나 NAS 배포는 실행하지 않았습니다.

    Watchtower 제외 label

    • compose.yaml의 x-app-base가 label을 갖고, anchor를 쓰지 않는 service(byedpi, gateway, runtime-arca-builder, runtime-package-coordinator, postgres)는 같은 label을 직접 둡니다. 운영 렌더링의 20개 service 모두에 붙습니다.
    • backup restore drill의 임시 project(verify·verify-postgres)와 test 조합(compose.test.yaml)이 더하는 service에도 붙습니다.
    • nickfedor/watchtower는 label-enable 설정과 관계없이 enable=false container를 갱신 대상에서 뺍니다. host Watchtower 설정은 바꾸지 않아도 됩니다.
    • 검사 script tests/scripts/compose-watchtower-label-check.mjs가 렌더링된 Compose에서 label 없는 service를 찾으면 실패합니다. 대상은 운영, 운영+개발, 운영+test의 전체 profile과 backup restore drill입니다.

    문서

    • 운영 가이드: label 설명, 첫 갱신 시각 확인, 갱신 뒤 label·Watchtower log 확인, 실패와 되돌리기의 Watchtower 진단 조건과 Created 잔여 container 복구 절차
    • 완료 기준선(제공 범위, Compose·registry 행), tests/README(단독 Compose 검사·변경별 검증)

    알려진 제한(후속 과제)

    • 실제 NAS(Synology Compose build, Synology ACL·btrfs)에서의 갱신·VX/VX Ace package, PowerShell coordinator 전체 실행은 NOT RUN입니다.
    • gate는 Watchtower 갱신을 한 번 직접 실행했습니다. 실제 NAS의 schedule(매시 4분 30초)과 겹친 갱신은 실행하지 않았습니다.
    • 1.0.4 이하의 label 없는 container가 Watchtower 실행과 겹치면 이번 첫 갱신도 같은 방식으로 실패할 수 있습니다. 위 업그레이드 주의 1을 따릅니다.
    • update.sh는 network가 빠진 container를 스스로 감지하지 않습니다. 운영 가이드 '실패와 되돌리기'로 복구합니다.
    • 1.0.4와 같은 제한:
      • 재확인→게시 간격이 128 MiB에서 약 1.2초 늘어납니다(봉인 사본 검증은 게임 크기에 비례).
      • /artifacts 쓰기 거부는 기존 분류를 따르고, load 뒤 권한 변경은 ARTIFACT_INVALID로 끝납니다.
      • 소유 계정이 섞인 sealed store는 지원하지 않습니다.
      • Docker 24 graphdriver에서는 Builder config digest를 pin과 직접 대조하지 않습니다(ARCA 후속).
      • 3단계 이후 실패하면 coordinator가 정지된 채 남습니다. 원인을 고친 뒤 다음 sh update.sh가 다시 기동합니다.
      • Player 세이브 관리 listener(3005)는 catalog caller token으로만 보호되며, edge network에서 TCP로 닿을 수 있습니다.
      • catalog·Player의 일반 egress는 network로 제한하지 않습니다.
      • update.sh는 opt-in profile container(translation-import 등)를 갱신하지 않습니다.

    검증과 남은 범위

    • PASS: wt-optout 브랜치(병합 c6df686)
      • label 검사 RED → GREEN: 수정 전에는 운영 전체 profile의 20개 service 모두 누락이었습니다. 수정 뒤 운영 20, 운영+개발 20, 운영+test 32, backup restore drill 2가 모두 exit 0입니다.
      • 렌더링 diff는 label 추가뿐입니다. 격리 dind Watchtower 확인(label 붙은 service·one-off는 그대로, label 없는 control만 교체)을 마쳤습니다.
      • ops-backup restore drill, reset 기록기 30·update.sh 기록기 93, static, 문서 검사, git diff --check
      • 리뷰 최종 판정: 병합 가능(P1~P3 없음)
    • PASS: 릴리스 gate(Compose v2.20.1, NAS 조건, host Watchtower)
      • 환경: 격리 docker:24.0.2-dind(overlay2), host Compose는 공식 v2.20.1 linux-x86_64 binary(release의 게시 checksum과 일치). 합성 CA의 HTTPS release 서버가 Gitea를, 합성 TLS registry가 git.2ex.dev를 대신했습니다(dind daemon과 Watchtower container에만 적용). registry에는 실제 1.0.4 image와 이 릴리스의 1.0.5 image를 넣었습니다. 배포 파일은 1.0.4 tag와 이 릴리스 커밋의 Git blob입니다.
      • 운영과 같은 계정과 원본 권한: scanner·stager 1026:100, ./data bind store, 원본 library 최상위 0:0 000 + ACL u:1026:r-x.
      • 1.0.4 설치 → reset.sh → update.sh: exit 0(106초). 12개 service, network 불일치 0, label 없음.
      • nickfedor/watchtower 1.23.0을 운영 env(label-enable 없음, cleanup, 30 4 * * * *)로 기동하고, label 없는 다른 프로젝트 control container를 함께 띄웠습니다.
      • 1.0.5 게시(registry latest = 1.0.5, asset 없음) 뒤 설치된 1.0.4 update.sh: exit 0(59초), Updated to 1.0.5 .... 7개 파일이 release와 같고 .tmp는 0입니다. 12개 container가 모두 다시 만들어져 label false를 가졌고, network 불일치 0, unhealthy 0, package·runtime readiness exit 0, coordinator 재시작 0이었습니다.
      • registry latest를 새 digest로 다시 게시(같은 1.0.5 내용에 label 하나 추가)하고 control image도 새 digest로 게시한 뒤, 같은 Watchtower container에서 갱신을 한 번 실행했습니다. Update session completed failed=0 scanned=4 updated=1이었습니다. 검사 대상은 LINKS 밖 container 4개였고, 교체된 것은 label 없는 control뿐이었습니다. LINKS container 12개는 모두 enable=false로 제외됐고 container ID·StartedAt이 바뀌지 않았습니다. scheduled Watchtower log에도 links- 줄이 0입니다. 그 뒤 latest를 게시된 1.0.5 digest로 되돌렸습니다.
      • VX Ace 준비: seal(runtime-package-stager, 1026:100) → stage → Builder → verify-source(runtime-package-stager, 1026:100) → publish → build ready. attempt 1에 성공했고 모든 one-shot이 exit 0이며 label false였습니다. sealed 항목은 모두 1026:100이고, Builder 계정의 읽기·탐색 실패는 0입니다.
      • 설치 상태에서 재-update: exit 0(14초), 배포 파일 변화 없음, container 재생성 없음(coordinator·migration one-shot만 재기동), 준비 상태 ready 유지, network 불일치 0, readiness 0/0.
    • PASS: 최종 1.0.5 소스. 릴리스 커밋 두 개(root version, 완료 기준선의 version 문구) 뒤 다음을 다시 통과했습니다: 문서 검사(760개 파일 / Markdown 32개), static(Prettier·ESLint·typecheck·docs-static), git diff --check.
    • 게시 이미지: 같은 최종 소스에서 build했고, gate도 이 image로 실행했습니다. image 안에서 root version 1.0.5와 ARCA pin·설치본 0.1.18을 확인했습니다.
    • PARTIAL: 전체 UI/제품 acceptance. 화면·fixture·package/build 성공을 실제 browser play PASS로 기록하지 않습니다.
    • NOT RUN:
      • Synology NAS 실제 환경(Compose v2.20.1 Synology build, Synology ACL, Docker 24 btrfs)에서의 1.0.4 → 1.0.5 갱신과 실제 Watchtower 일정
      • PowerShell coordinator 전체 실행
      • 실제 Gitea release·registry를 대상으로 한 전체 stack 갱신(gate는 합성 release 서버·registry)
      • 실제 library/private game과 실제 scan
      • 입력·오디오·save/reload의 실제 게임 acceptance
      • live provider
      • 개발 rebuild

    배포 이미지와 파일

    • Git annotated tag는 1.0.5이고 최종 소스는 604d5ffec1입니다.
    • 단일 linux/amd64 이미지: git.2ex.dev/hanyul/links:1.0.5 = git.2ex.dev/hanyul/links:latest
    • 원격 digest: sha256:8cc47b4ca0a023cff1795731a9940b0250d0fad10511297ecfaee4542a8b8036
    • release asset은 없습니다. update.sh가 tag 1.0.5의 7개 배포 파일을 HTTPS로 받습니다. 대응은 update.sh=ops/deploy-registry.sh, reset.sh=ops/reset-registry.sh, backup.sh=ops/backup-registry.sh이고, 나머지는 compose.yaml·infra/docker/Caddyfile·ops/backup-verify.compose.yaml·ops/runtime-package-coordinator.sh입니다.
    • 1.0.4 대비 바뀐 배포 파일은 두 개입니다. compose.yaml과 ops/backup-verify.compose.yaml(Watchtower 제외 label)입니다. 나머지 5개는 같습니다.
    Downloads 2