- Published on
폐쇄망 이미지 반입 파이프라인 설계 — skopeo, Harbor, 그리고 썩지 않는 재반입 런북
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 한 번의 반입은 반드시 썩습니다
- 반입 목록을 코드로 관리한다
- 외부 수집 — skopeo sync로 떠내기
- 검사 구간 — Trivy DB를 손으로 들여오기
- 서명과 SBOM — 투명성 로그가 없는 곳의 cosign
- 내부 배포 — 이중 레지스트리 패턴과 Harbor
- 정기 재반입 런북
- 마치며 — 파이프라인의 수명은 목록 파일의 수명입니다
- 참고 자료
들어가며 — 한 번의 반입은 반드시 썩습니다
폐쇄망 설치 가이드는 많습니다. 그런데 대부분 첫 설치까지만 다룹니다. 실제로 팀을 갉아먹는 건 그다음입니다.
3개월 뒤 새 서비스가 배포되면서 이미지 열두 개가 추가로 필요해집니다. 6개월 뒤 취약점 점검 결과가 내려오는데, 사내 스캐너의 취약점 DB가 반년 전 것이라 결과를 믿을 수 없습니다. 9개월 뒤 base 이미지를 올려야 하는데 그때 누가 어떤 태그를 어떤 경로로 들여왔는지 아무도 재구성하지 못합니다. 최초 반입을 한 사람은 이미 다른 팀입니다.
이 글은 그 문제를 다룹니다. 한 번 들여오는 방법이 아니라, 반복해서 들여오는 구조를 설계하는 방법입니다. 아래 도구들을 기준으로 확인했습니다.
| 도구 | 확인한 버전 또는 기준 | 확인 시점 | 출처 |
|---|---|---|---|
| skopeo | skopeo-sync 매뉴얼 (main 브랜치 문서) | 2026-07-31 | skopeo-sync.1.md |
| oras | 1.3 | 2026-07-31 | oras push |
| Trivy DB | trivy-db 태그 2, trivy-java-db 태그 1 | 2026-07-31 | Trivy Self-Hosting |
| Harbor | 2.14.0 | 2026-07-31 | Harbor Docs |
| Helm OCI | 문서 페이지 기준 Helm 4.2.3 | 2026-07-31 | Helm Registries |
반입 목록을 코드로 관리한다
파이프라인의 첫 단추는 도구가 아니라 목록입니다. 목록이 사람 머릿속이나 위키 페이지에 있으면 반년 뒤 반드시 실제와 어긋납니다. Git 저장소에 두고, 반입 작업은 그 파일의 커밋으로만 시작하게 만드십시오.
skopeo가 읽을 수 있는 형식을 그대로 정본으로 쓰는 것이 가장 실용적입니다. 별도 변환 스크립트가 필요 없기 때문입니다.
# images.yaml — 반입 목록 정본. 이 파일의 diff 가 곧 반입 신청서입니다.
docker.io:
images:
library/postgres:
- '16.4'
- '16.6'
library/redis:
- '7.4.1'
images-by-tag-regex:
library/busybox: ^1\.36.*$
registry.k8s.io:
images:
ingress-nginx/controller:
- 'v1.12.0'
metrics-server/metrics-server:
- 'v0.7.2'
quay.io:
tls-verify: true
images:
prometheus/prometheus:
- 'v3.1.0'
prometheus/node-exporter:
- 'v1.8.2'
ghcr.io:
images:
aquasecurity/trivy:
- '0.58.1'
이 형식은 skopeo sync 매뉴얼에 정의된 YAML 소스 형식 그대로입니다. images, images-by-tag-regex, images-by-semver, credentials, tls-verify, cert-dir 키를 지원합니다. 태그를 정규식이나 semver 범위로 지정할 수 있다는 점이 중요한데, 폐쇄망에서는 범위 지정을 최대한 피하고 태그를 못 박는 편이 낫습니다. 범위를 쓰면 반입할 때마다 무엇이 들어왔는지 달라지고, 그러면 목록 파일이 정본 역할을 못 합니다.
목록에 반드시 함께 적어야 할 것이 하나 더 있습니다. 다이제스트입니다.
# 태그가 가리키는 다이제스트를 목록과 함께 고정합니다
skopeo inspect docker://docker.io/library/postgres:16.6 \
| jq -r '.Digest' \
| tee -a DIGESTS.txt
태그는 움직입니다. 3개월 뒤 같은 태그를 다시 뜨면 다른 내용물이 올 수 있습니다. 폐쇄망 감사에서 "지난번에 반입한 것과 같은 것입니까"라는 질문에 답하려면 다이제스트 기록이 있어야 합니다.
외부 수집 — skopeo sync로 떠내기
수집 단계는 인터넷이 되는 DMZ 장비에서 수행합니다. 여기서 도커 데몬을 쓰지 않는 것이 핵심입니다. skopeo는 데몬 없이 레지스트리와 직접 대화하므로, 반입용 스테이징 장비를 최소 구성으로 유지할 수 있습니다.
#!/usr/bin/env bash
# collect.sh — DMZ 수집 장비
set -euo pipefail
BATCH="$(date +%Y%m%d)"
OUT="/staging/inbound/${BATCH}"
mkdir -p "${OUT}"
# 목록 파일 하나로 여러 레지스트리를 한 번에 떠냅니다.
# --scoped 를 붙이면 원본 레지스트리 경로가 접두어로 붙어 이름 충돌이 사라집니다.
skopeo sync \
--src yaml \
--dest dir \
--scoped \
--all \
--keep-going \
images.yaml "${OUT}"
du -sh "${OUT}"
find "${OUT}" -maxdepth 3 -type d | head -30
각 플래그의 의미는 매뉴얼에 정의된 그대로입니다.
--scoped— "여러 이미지가 같은 이름을 가질 수 있으므로 목적지에 저장할 때 원본 이미지 경로를 접두어로 붙인다". 여러 레지스트리에서 동명의 이미지를 받을 때 필수입니다.--all— 소스가 이미지 목록(멀티 아키텍처 매니페스트)을 가리키는 경우, 현재 OS와 아키텍처에 맞는 것만이 아니라 전부 복사합니다. amd64와 arm64 노드가 섞인 폐쇄망이라면 반드시 필요합니다.--keep-going— 복사 중 오류가 나도 로그만 남기고 계속 진행합니다. 200개 이미지를 뜨는데 세 번째에서 멈추는 상황을 막습니다.--preserve-digests— 다이제스트를 보존하고, 보존할 수 없으면 실패합니다. 반입 추적성이 중요하면 켜십시오.
목적지 전송(--dest)은 매뉴얼 기준 docker와 dir 두 가지입니다. 매체로 나를 것이므로 dir을 씁니다. 매체에 담기 전에 아카이브로 묶고 체크섬을 붙입니다.
# 매체 반입용 아카이브와 매니페스트
cd /staging/inbound
tar -cf "images-${BATCH}.tar" "${BATCH}"
sha256sum "images-${BATCH}.tar" > "images-${BATCH}.sha256"
# 반입 신청서에 첨부할 목록 스냅샷
cp images.yaml "images-${BATCH}.manifest.yaml"
cp DIGESTS.txt "images-${BATCH}.digests.txt"
이미지가 아닌 것들 — oras
폐쇄망에 들여야 할 것은 이미지만이 아닙니다. Helm 차트, SBOM, 정책 번들, 취약점 DB가 모두 필요합니다. 이것들은 OCI 아티팩트로 취급하면 이미지와 완전히 같은 경로로 나를 수 있습니다. 도구는 oras입니다.
# 임의 파일을 OCI 아티팩트로 밀어 넣기 (oras 1.3 기준)
oras push --artifact-type application/vnd.example.policy.v1+tar \
registry.internal.example:5000/policies/kyverno:2026.07 \
policies.tar.gz
# 파일별로 미디어 타입을 지정할 수도 있습니다
oras push registry.internal.example:5000/bundles/edge:2026.07 \
bundle.tar:application/vnd.example.bundle \
README.md:text/markdown
# OCI 레이아웃 디렉터리로 떠내기 — 매체로 나르기에 적합한 형태
oras push --oci-layout /staging/inbound/20260731/oci:policies-2026.07 policies.tar.gz
레지스트리 간 복사에는 oras cp, 매니페스트 확인에는 oras manifest fetch가 있습니다. 다만 이번 확인 시점에 oras push 문서 페이지 본문에서 확인한 것은 push 계열 문법과 OCI 레이아웃 옵션까지입니다. oras cp와 oras pull의 정확한 플래그는 해당 명령의 문서 페이지에서 확인한 뒤 스크립트에 넣으십시오.
검사 구간 — Trivy DB를 손으로 들여오기
수집과 배포 사이에 검사 구간이 있어야 합니다. 폐쇄망에서 검사가 어려운 이유는 스캐너 자체가 아니라 스캐너의 데이터가 인터넷에서 갱신되기 때문입니다. Trivy는 취약점 DB를 OCI 아티팩트로 배포하므로, 이미지와 같은 방식으로 들여올 수 있습니다.
#!/usr/bin/env bash
# collect-trivy-db.sh — DMZ 수집 장비
set -euo pipefail
BATCH="$(date +%Y%m%d)"
mkdir -p "/staging/inbound/${BATCH}/trivy" && cd "/staging/inbound/${BATCH}/trivy"
# 공식 문서가 명시한 저장소와 태그
oras pull ghcr.io/aquasecurity/trivy-db:2
oras pull ghcr.io/aquasecurity/trivy-java-db:1
oras pull ghcr.io/aquasecurity/trivy-checks:latest
ls -la
sha256sum ./* > TRIVY-DB.sha256
매체가 안쪽으로 들어오면 사내 레지스트리에 올립니다. 여기서 미디어 타입이 중요합니다. Trivy는 커스텀 미디어 타입으로 계층을 식별하므로, 일반 파일처럼 밀어 넣으면 Trivy가 못 읽습니다.
| 아티팩트 | 미디어 타입 |
|---|---|
| trivy-db | application/vnd.aquasec.trivy.db.layer.v1.tar+gzip |
| trivy-java-db | application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip |
| trivy-checks | application/vnd.oci.image.manifest.v1+json |
# 폐쇄망 내부 — 사내 레지스트리로 밀어 넣기 (공식 문서의 예시 형태)
oras push registry.internal.example:5000/trivy/trivy-db:2 db.tar.gz
oras push registry.internal.example:5000/trivy/trivy-java-db:1 javadb.tar.gz
oras push registry.internal.example:5000/trivy/trivy-checks:latest ./checks/
# 스캔 — 사내 저장소를 바라보게 지정합니다
trivy image \
--db-repository registry.internal.example:5000/trivy/trivy-db \
--java-db-repository registry.internal.example:5000/trivy/trivy-java-db \
--checks-bundle-repository registry.internal.example:5000/trivy/trivy-checks \
registry.internal.example:5000/apps/api:1.4.2
여기서 정직하게 밝혀 둘 부분이 있습니다. --skip-db-update, --skip-java-db-update, --offline-scan 같은 플래그는 널리 쓰이지만, 이번 확인 시점에 Trivy 에어갭 문서 페이지 본문에서 이 플래그들의 정확한 이름과 동작을 확인하지 못했습니다. 캐시 디렉터리 기본 경로도 마찬가지입니다. 파이프라인에 넣기 전에 반입한 Trivy 바이너리로 직접 확인하십시오.
# 반입한 바이너리에서 실제 플래그 이름을 확인합니다 — 추측하지 마십시오
trivy image --help | grep -iE 'db|offline|cache'
trivy --version
한 가지 더. Trivy 문서는 검사 번들이 빌드 시점에 Trivy 바이너리에 내장되어 있어 외부 DB를 못 쓸 때 폴백으로 사용된다고 설명합니다. 즉 설정 오류로 미스컨피그 검사가 조용히 낡은 내장 번들로 돌아갈 수 있다는 뜻입니다. 스캔 결과가 이상하게 깨끗하면 이 경로를 의심하십시오.
서명과 SBOM — 투명성 로그가 없는 곳의 cosign
폐쇄망에서 서명 검증은 반쪽이 됩니다. cosign의 기본 동작은 서명을 투명성 로그와 대조하는 것인데, 폐쇄망에서는 그 로그에 닿을 수 없습니다. 그래서 전략을 다르게 잡아야 합니다.
가장 확실한 방법은 자체 키 쌍을 쓰고 공개 키를 반입물에 포함시키는 것입니다.
# DMZ 구간 — 검사를 통과한 이미지에 사내 키로 서명
cosign sign --key /secure/cosign.key \
registry.dmz.example:5000/apps/api@sha256:abc123...
# 공개 키를 반입 매니페스트에 함께 넣습니다
cp /secure/cosign.pub "/staging/inbound/${BATCH}/cosign.pub"
# 폐쇄망 내부 — 로컬 공개 키로 검증
cosign verify --key /etc/cosign/cosign.pub \
registry.internal.example:5000/apps/api:1.4.2
원본 공급자가 서명한 것을 검증해야 하는 경우라면 사정이 다릅니다. 공식 문서는 로컬에 내려받은 이미지를 검증하는 경로와, 투명성 로그 대조를 건너뛰는 옵션을 제시합니다.
# 로컬로 내려받은 이미지 검증
cosign verify --key cosign.pub --local-image /staging/inbound/20260731/apps-api
# 투명성 로그 대조를 건너뛰고 키와 페이로드만 검증
cosign verify --check-claims=false --key cosign.pub registry.internal.example:5000/apps/api:1.4.2
다만 --insecure-ignore-tlog, --private-infrastructure, 그리고 오프라인 TUF 루트를 미러로 초기화하는 절차는 이번 확인 시점에 sigstore 검증 문서 본문에서 확인하지 못했습니다. 이 세 가지가 파이프라인에 필요하다면 반입한 cosign 바이너리의 도움말과 sigstore 문서에서 직접 확인한 뒤 적용하십시오. 검증 절차를 추측으로 채우면 검증을 안 하는 것보다 나쁩니다. "검증했다"는 기록만 남고 실제로는 아무것도 확인하지 않은 상태가 되기 때문입니다.
SBOM은 서명보다 우선순위가 높습니다. 폐쇄망에서 SBOM이 없으면, 반년 뒤 새 취약점이 공개되었을 때 "우리 클러스터에 그 패키지가 있는가"에 답할 방법이 없습니다. 이미지를 다시 스캔하려면 스캐너 DB를 또 반입해야 하고, 이미 삭제된 이미지는 조사조차 못 합니다.
# 수집 시점에 SBOM 을 함께 생성해 매체에 담습니다
trivy image --format cyclonedx \
--output "sbom/apps-api-1.4.2.cdx.json" \
registry.dmz.example:5000/apps/api:1.4.2
# SBOM 을 OCI 아티팩트로 함께 반입
oras push --artifact-type application/vnd.cyclonedx+json \
registry.internal.example:5000/sbom/apps-api:1.4.2 \
"sbom/apps-api-1.4.2.cdx.json"
SBOM을 이미지에 참조로 붙이는 방식(referrer)도 있지만, 레지스트리가 referrers API를 지원해야 합니다. 지원 여부가 확실하지 않으면 위처럼 별도 저장소 경로에 태그로 올려 두는 편이 안전합니다. 조회가 단순하고 어느 레지스트리에서나 동작합니다.
내부 배포 — 이중 레지스트리 패턴과 Harbor
반입 파이프라인의 구조는 두 개의 레지스트리와 그 사이의 검사 구간으로 정리됩니다.
| 구간 | 위치 | 역할 | 여기서 하지 말아야 할 것 |
|---|---|---|---|
| 수집 레지스트리 | DMZ | 외부에서 받은 원본 보관, 다이제스트 기록, 서명 | 운영 클러스터가 여기를 직접 바라보게 하기 |
| 검사 구간 | DMZ 또는 중계 | 취약점 스캔, SBOM 생성, 정책 검사, 승인 기록 | 실패한 아티팩트를 수동으로 통과시키기 |
| 반입 경로 | 매체 또는 단방향 게이트웨이 | 체크섬 검증, 심의 기록 | 검증 없이 내부로 넣기 |
| 배포 레지스트리 | 폐쇄망 내부 | 클러스터가 바라보는 유일한 출처 | 검사 없이 개별 개발자가 직접 푸시하게 두기 |
가장 흔한 설계 실수는 배포 레지스트리에 개발자가 직접 푸시할 수 있게 열어 두는 것입니다. 그 순간 이중 레지스트리 패턴이 무너지고, 검사 구간을 우회한 이미지가 클러스터에 들어옵니다. 배포 레지스트리에 쓰기 권한을 가진 주체는 반입 파이프라인 계정 하나여야 합니다.
Harbor를 내부 배포 레지스트리로 쓰는 경우 확인 시점 최신 릴리스는 2.14.0입니다. Harbor를 폐쇄망에 설치할 때는 오프라인 인스톨러를 받아야 하고, Harbor 자체의 컨테이너 이미지들이 그 인스톨러에 포함되어 있습니다. 반입 목록에 Harbor 인스톨러를 넣는 것을 잊지 마십시오.
여기서 반드시 짚고 넘어갈 것이 있습니다. Harbor의 프록시 캐시 기능은 완전 폐쇄망에서 쓸모가 없습니다. 프록시 캐시는 상위 레지스트리로 요청을 넘겨 받아 오는 구조이므로, 상위 레지스트리로 가는 네트워크 경로가 전제입니다. 경로가 없으면 캐시 미스가 곧 실패입니다. 이 기능이 유용한 경우는 "인터넷은 되지만 통제하고 싶은" 준폐쇄망이지, 라우팅 자체가 없는 환경이 아닙니다.
같은 이유로 Harbor의 복제(replication) 기능도 폐쇄망 경계를 넘지 못합니다. 복제는 DMZ 안쪽에서 수집 레지스트리들 사이를 정리하는 데 쓰고, 경계를 넘는 이동은 매체나 단방향 게이트웨이로만 하십시오. 이번 확인 시점에 Harbor 복제 설정과 프록시 캐시의 개별 문서 페이지는 URL이 바뀌어 본문을 확인하지 못했으므로, 지원 소스 레지스트리 종류와 트리거 방식 같은 세부 사항은 Harbor 2.14 문서에서 직접 확인하십시오.
Helm 차트를 OCI 아티팩트로 나르기
차트를 별도 차트 저장소로 관리하면 반입 경로가 하나 더 늘어납니다. OCI 아티팩트로 통일하면 이미지와 같은 레지스트리, 같은 인증, 같은 반입 절차를 씁니다.
# DMZ 수집 — 외부 차트를 tgz 로 받아 둡니다
helm pull oci://registry-1.docker.io/bitnamicharts/postgresql --version 16.4.5 -d ./charts
helm pull https://prometheus-community.github.io/helm-charts/prometheus-25.27.0.tgz -d ./charts
sha256sum ./charts/*.tgz > CHARTS.sha256
# 폐쇄망 내부 — 사내 레지스트리로 푸시
helm registry login registry.internal.example:5000
helm push ./charts/postgresql-16.4.5.tgz oci://registry.internal.example:5000/charts
helm push ./charts/prometheus-25.27.0.tgz oci://registry.internal.example:5000/charts
# 설치 — oci 참조에는 버전 지정이 필요합니다
helm show all oci://registry.internal.example:5000/charts/postgresql --version 16.4.5
helm template pg oci://registry.internal.example:5000/charts/postgresql --version 16.4.5
helm install pg oci://registry.internal.example:5000/charts/postgresql --version 16.4.5
차트만 반입하고 끝내면 안 됩니다. 차트가 참조하는 이미지가 별도로 반입되어야 합니다. 이 누락이 폐쇄망에서 가장 자주 나오는 사고입니다. 차트를 받은 즉시 이미지 참조를 뽑아 목록 파일에 반영하는 단계를 파이프라인에 넣으십시오.
# 차트가 참조하는 이미지 목록 추출 — 반입 목록에 반영할 근거
helm template tmp ./charts/postgresql-16.4.5.tgz \
| grep -E '^\s+image:' \
| awk '{print $2}' \
| tr -d '"' \
| sort -u
values 파일에 따라 참조 이미지가 달라진다는 점에 주의하십시오. 실제 배포에 쓸 values를 그대로 적용해서 추출해야 정확합니다. 조건부로 켜지는 사이드카나 init 컨테이너가 빠지면, 배포 당일에야 발견합니다.
helm template tmp ./charts/postgresql-16.4.5.tgz -f values-prod.yaml \
| grep -E '^\s+image:' | awk '{print $2}' | tr -d '"' | sort -u
Helm 문서 페이지는 확인 시점에 Helm 4.2.3 기준이며, 해당 페이지에 Helm 4용으로 완전히 갱신되지 않았다는 경고가 붙어 있습니다. 반입할 Helm 버전과 문서 버전이 다르면 명령 동작을 스테이징에서 먼저 확인하십시오.
정기 재반입 런북
여기까지가 구조이고, 여기부터가 이 글의 실제 결론입니다. 위 파이프라인을 아무리 잘 만들어도 주기적으로 돌지 않으면 6개월 뒤 쓸모없어집니다. 자산마다 유효 기간이 다르기 때문에 하나의 주기로 묶으면 안 됩니다.
| 자산 | 재반입 주기 | 근거 | 방치했을 때의 증상 |
|---|---|---|---|
| Trivy 취약점 DB | 주 1회 | 취약점 정보가 가장 빨리 낡음 | 스캔은 통과인데 실제로는 알려진 취약점이 그대로 존재 |
| Trivy 검사 번들 | 월 1회 | 미스컨피그 규칙 갱신 | 내장 폴백으로 조용히 되돌아가 낡은 규칙으로 검사 |
| base 이미지 | 월 1회 | OS 패키지 보안 패치 | 모든 파생 이미지가 같은 취약점을 공유 |
| 애플리케이션 이미지 | 배포 주기에 맞춤 | 서비스 릴리스와 연동 | 반입 심의 대기로 릴리스가 밀림 |
| 쿠버네티스 배포판 아티팩트 | 분기 1회 | 패치 릴리스 누적 | 인증서·CVE 대응이 늦어지고 업그레이드 폭이 커짐 |
| Helm 차트와 참조 이미지 | 차트 변경 시 | 차트만 올리면 이미지가 없음 | 배포 당일 ImagePullBackOff |
| 사내 CA와 서명 공개 키 | 만료 90일 전 | 키 회전 주기 | 레지스트리 TLS 실패로 전 클러스터 이미지 pull 중단 |
| SBOM | 이미지 반입 시 동반 | 사후 조사의 유일한 근거 | 신규 CVE 공개 시 영향 범위를 산정할 수 없음 |
이 표를 캘린더에 넣고 담당자를 지정하십시오. 폐쇄망에서 "필요할 때 하겠다"는 계획은 언제나 "필요해진 다음에 3주 걸린다"로 끝납니다.
재반입을 자동화하는 스크립트의 골격은 다음과 같습니다. 핵심은 직전 반입과의 차이를 먼저 계산하는 것입니다. 매번 전량을 다시 나르면 매체 용량과 심의 시간이 감당이 안 됩니다.
#!/usr/bin/env bash
# reimport.sh — DMZ 수집 장비에서 주기 실행
set -euo pipefail
BATCH="$(date +%Y%m%d)"
PREV="$(ls -1d /staging/inbound/20* | sort | tail -1)"
OUT="/staging/inbound/${BATCH}"
mkdir -p "${OUT}"
echo "== 1. 현재 태그의 다이제스트 산출"
: > "${OUT}/DIGESTS.txt"
while read -r ref; do
[ -z "${ref}" ] && continue
d=$(skopeo inspect "docker://${ref}" 2>/dev/null | jq -r '.Digest') || d="ERROR"
echo "${ref} ${d}" >> "${OUT}/DIGESTS.txt"
done < refs.txt
echo "== 2. 직전 반입과의 차이"
if [ -f "${PREV}/DIGESTS.txt" ]; then
diff "${PREV}/DIGESTS.txt" "${OUT}/DIGESTS.txt" > "${OUT}/CHANGES.diff" || true
CHANGED=$(grep -c '^>' "${OUT}/CHANGES.diff" || true)
echo "변경된 참조: ${CHANGED}건"
if [ "${CHANGED}" -eq 0 ]; then
echo "변경 없음 — 이번 회차 반입 생략"
exit 0
fi
fi
echo "== 3. 변경분만 수집"
skopeo sync --src yaml --dest dir --scoped --all --keep-going images.yaml "${OUT}/images"
echo "== 4. 스캔 및 SBOM"
mkdir -p "${OUT}/sbom" "${OUT}/scan"
while read -r ref _; do
name=$(echo "${ref}" | tr '/:' '__')
trivy image --format cyclonedx --output "${OUT}/sbom/${name}.cdx.json" "${ref}" || true
trivy image --severity HIGH,CRITICAL --format json \
--output "${OUT}/scan/${name}.json" "${ref}" || true
done < "${OUT}/DIGESTS.txt"
echo "== 5. 심의 제출용 아카이브"
cd /staging/inbound
tar -cf "batch-${BATCH}.tar" "${BATCH}"
sha256sum "batch-${BATCH}.tar" > "batch-${BATCH}.sha256"
echo "제출 준비 완료: batch-${BATCH}.tar"
내부 반입 쪽에도 같은 수준의 스크립트가 필요합니다. 체크섬 검증, 배포 레지스트리 푸시, 반입 대장 기록까지 한 번에 끝나야 사람이 단계를 건너뛰지 않습니다.
#!/usr/bin/env bash
# ingest.sh — 폐쇄망 내부
set -euo pipefail
BATCH="$1"
SRC="/media/inbound/batch-${BATCH}.tar"
sha256sum -c "/media/inbound/batch-${BATCH}.sha256"
mkdir -p "/opt/inbound" && tar -xf "${SRC}" -C /opt/inbound
# 디렉터리 형태로 들어온 이미지를 배포 레지스트리로
skopeo sync --src dir --dest docker \
"/opt/inbound/${BATCH}/images" registry.internal.example:5000/mirror/
# 반입 대장에 기록 — 나중에 "언제 무엇이 들어왔는가"의 유일한 근거
{
echo "batch=${BATCH} at=$(date -Iseconds) by=${USER}"
cat "/opt/inbound/${BATCH}/DIGESTS.txt"
} >> /var/log/airgap-ingest.log
마치며 — 파이프라인의 수명은 목록 파일의 수명입니다
반입 파이프라인에서 가장 오래 살아남는 것은 스크립트가 아니라 목록 파일입니다. 스크립트는 도구 버전이 바뀌면 다시 쓰게 되지만, "우리 클러스터가 필요로 하는 아티팩트는 이것들이다"라는 목록은 몇 년을 갑니다. 그 목록이 Git에 있고 다이제스트가 함께 기록되어 있으면, 담당자가 세 번 바뀌어도 파이프라인은 굴러갑니다.
그리고 프록시 캐시나 복제 같은 기능을 검토할 때는 항상 같은 질문을 먼저 하십시오. 이 기능은 상위로 가는 네트워크 경로를 전제하는가. 전제한다면 폐쇄망에서는 성립하지 않습니다. 이 한 가지 질문이 아키텍처 회의 두 시간을 아껴 줍니다.