💡 핵심 요약 (Executive Summary)
본 가이드는 oci skopeo 핵심 가이드 및 실전 아키텍처의 핵심 개념, 실전 구성 명령어 및 아키텍처 베스트 프랙티스를 다룹니다. 클라우드 네이티브 및 인프라 엔지니어링 실무에 즉시 활용 가능하도록 체계적으로 정리했습니다.
📑 Table of Contents
Installation
RHEL / Rocky Linux (8/9)
bash
sudo dnf install -y skopeo
skopeo --version
Ubuntu / Debian (20.04/22.04/24.04)
bash
sudo apt-get update && sudo apt-get install -y skopeo
skopeo --version
macOS
bash
brew install skopeo
폐쇄망(에어갭) 오프라인 패키징 & 로컬 설치
bash
# [온라인 머신] RHEL/Rocky 의존성 RPM 번들링
mkdir -p /opt/k8s-airgap-bundle/rpms/skopeo && cd /opt/k8s-airgap-bundle/rpms/skopeo
dnf download --resolve --alldeps skopeo
# [폐쇄망 호스트] RHEL 로컬 오프라인 즉시 설치
sudo dnf --disablerepo="*" -y localinstall /opt/k8s-airgap-bundle/rpms/skopeo/*.rpm || sudo rpm -Uvh --replacepkgs /opt/k8s-airgap-bundle/rpms/skopeo/*.rpm
# [온라인 머신] Ubuntu 재귀 의존성 DEB 번들링
mkdir -p /opt/k8s-airgap-bundle/debs/skopeo && cd /opt/k8s-airgap-bundle/debs/skopeo
apt-get download $(apt-cache depends --recurse --no-recommends --no-suggests \
--no-conflicts --no-breaks --no-replaces --no-enhances "skopeo" | grep "^\w" | sort -u) 2>/dev/null || apt-get download skopeo
# [폐쇄망 호스트] Ubuntu 로컬 오프라인 즉시 설치
sudo dpkg -i /opt/k8s-airgap-bundle/debs/skopeo/*.deb
Completion
bash
# bash 자동완성 활성화
skopeo completion bash | sudo tee /etc/bash_completion.d/skopeo > /dev/null
source /etc/bash_completion.d/skopeo
Exec
bash
# 1. 원격 이미지 메타데이터 검사 (로컬 풀 없이 Manifest 확인)
skopeo inspect docker://{{ image }}
skopeo inspect --raw docker://{{ image }} | jq .
# 2. 이미지 태그 목록 조회
skopeo list-tags docker://{{ repository }}
# 3. 레지스트리 ➔ 단일 tar 아카이브로 저장 (docker load 및 폐쇄망 반입 호환)
skopeo copy docker://{{ registry }}/{{ image }}:{{ tag }} docker-archive:{{ file.tar }}:{{ registry }}/{{ image }}:{{ tag }}
# 4. 단일 tar 아카이브 ➔ 사설 레지스트리(Harbor) 푸시
skopeo copy --dest-tls-verify=false --dest-creds "{{ user }}:{{ pass }}" docker-archive:{{ file.tar }} docker://{{ harbor_url }}/{{ project }}/{{ image }}:{{ tag }}
# 5. 레지스트리 간 직접 복제 (Direct Streaming, 로컬 디스크 I/O 낭비 제로)
skopeo copy --dest-tls-verify=false --dest-creds "{{ user }}:{{ pass }}" docker://{{ src_registry }}/{{ image }}:{{ tag }} docker://{{ dest_registry }}/{{ project }}/{{ image }}:{{ tag }}
# 6. 특정 아키텍처 강제 지정 다운로드
skopeo copy --override-os linux --override-arch amd64 docker://{{ image }} docker-archive:{{ file.tar }}:{{ image }}
# 7. 원격 레지스트리 이미지 태그 삭제
skopeo delete --tls-verify=false --creds "{{ user }}:{{ pass }}" docker://{{ registry }}/{{ project }}/{{ image }}:{{ tag }}
copy
bash
# 원격 ➔ tar 아카이브 (단일 파일 내 태그 메타데이터 기록 보장)
skopeo copy docker://registry.k8s.io/pause:3.9 docker-archive:/tmp/pause_3.9.tar:registry.k8s.io/pause:3.9
# tar 아카이브 ➔ 사설 Harbor 일괄 푸시
skopeo copy --dest-tls-verify=false --dest-creds admin:HarborPassword \
docker-archive:/tmp/pause_3.9.tar docker://harbor.internal.net/k8s/pause:3.9
# 레지스트리 to 레지스트리 직통 복제 (dockerd 불필요, 로컬 디스크 미사용)
skopeo copy --dest-tls-verify=false --dest-creds admin:HarborPassword \
docker://docker.io/calico/cni:v3.28.2 docker://harbor.internal.net/k8s/cni:v3.28.2
inspect
bash
# 원격 Manifest, 레이어 해시, 아키텍처, 엔트리포인트 조회
skopeo inspect docker://registry.k8s.io/kube-apiserver:v1.30.10
# 원시 Manifest JSON 조회
skopeo inspect --raw docker://registry.k8s.io/kube-apiserver:v1.30.10 | jq .
# 사설 Harbor 인증 및 TLS 우회 검사
skopeo inspect --tls-verify=false --creds "admin:HarborPassword" \
docker://harbor.internal.net/k8s/kube-apiserver:v1.30.10
list-tags
bash
# 원격 리포지토리 전체 태그 목록 출력
skopeo list-tags docker://docker.io/calico/node | jq .Tags
# 특정 버전 태그 필터링
skopeo list-tags docker://docker.io/calico/node | jq -r '.Tags[] | select(startswith("v3.28"))'
Features
지원 Transport 접두사 규격 (Supported Transports)
docker://: Docker Registry v2 프로토콜 (Harbor, Docker Hub, Quay, ECR, GCR 등)docker-archive::docker save/load100% 호환 단일 tar 아카이브oci:: OCI(Open Container Initiative) 이미지 레이아웃 표준 디렉터리oci-archive:: OCI 이미지 레이아웃 tar 파일dir:: 압축 해제된 레이어 및 manifest 파일이 그대로 노출되는 디렉터리 (디버깅용)containers-storage:: Podman / CRI-O 로컬 스토리지 (/var/lib/containers/storage)docker-daemon:: 로컬 구동 중인 dockerd 데몬 스토리지 직접 접근
폐쇄망 에어갭용 4스레드 병렬 번들링 쉘 스크립트
bash
#!/usr/bin/env bash
set -euo pipefail
DEST_DIR="/opt/k8s-airgap-bundle/images"
mkdir -p "${DEST_DIR}"
IMAGE_LIST=(
"registry.k8s.io/kube-apiserver:v1.30.10"
"registry.k8s.io/kube-controller-manager:v1.30.10"
"registry.k8s.io/kube-scheduler:v1.30.10"
"registry.k8s.io/kube-proxy:v1.30.10"
"registry.k8s.io/pause:3.9"
"docker.io/calico/cni:v3.28.2"
"docker.io/calico/node:v3.28.2"
"docker.io/calico/kube-controllers:v3.28.2"
)
# 단일 이미지 다운로드 헬퍼 함수
download_img() {
local IMG="$1"
local OUT_DIR="$2"
local BASENAME=$(echo "$IMG" | awk -F'/' '{print $NF}')
local FILENAME=$(echo "${BASENAME}" | tr ':' '_')
local TAR_PATH="${OUT_DIR}/${FILENAME}.tar"
if [ -f "${TAR_PATH}" ]; then
echo " [건너뜀] 이미 존재: ${FILENAME}.tar"
else
echo " ⬇️ [다운로드] ${IMG} ➔ ${FILENAME}.tar"
skopeo copy --override-os linux --override-arch amd64 \
"docker://${IMG}" "docker-archive:${TAR_PATH}:${IMG}"
fi
}
export -f download_img
# xargs -P 4 멀티프로세스 병렬 다운로드 (소요시간 70% 단축)
printf "%s\n" "${IMAGE_LIST[@]}" | sort -u | xargs -n 1 -P 4 -I {} bash -c 'download_img "$@"' _ {} "${DEST_DIR}"
echo "🎉 전체 이미지 오프라인 번들링 완료!"
폐쇄망 배스천 ➔ 번들 SHA-256 검증, 압축 해제 및 Harbor 일괄 푸시 올인원 스크립트
bash
#!/usr/bin/env bash
set -euo pipefail
BUNDLE_DIR="/opt/k8s-airgap-bundle"
IMAGE_DIR="${BUNDLE_DIR}/images"
# [0/3] 반입 번들 아카이브 SHA-256 무결성 검증 및 /opt 자동 압축 해제
ARCHIVE_FILE=$(ls -1 k8s-airgap-bundle-*.tar.gz /opt/k8s-airgap-bundle-*.tar.gz 2>/dev/null | head -n 1 || true)
if [ -n "${ARCHIVE_FILE}" ] && [ -f "${ARCHIVE_FILE}" ]; then
ARCHIVE_DIR=$(dirname "${ARCHIVE_FILE}")
ARCHIVE_NAME=$(basename "${ARCHIVE_FILE}")
SHA_FILE="${ARCHIVE_FILE}.sha256"
echo ">>> [1단계: 무결성 검증] 반입 번들 아카이브 감지: ${ARCHIVE_FILE}"
if [ -f "${SHA_FILE}" ]; then
echo " SHA-256 체크섬 대조 검증 중..."
(cd "${ARCHIVE_DIR}" && sha256sum -c "${ARCHIVE_NAME}.sha256")
echo " ✅ SHA-256 무결성 검증 완료: 번들 파일이 손상 없이 온전합니다."
else
echo " ℹ️ .sha256 파일이 없어 체크섬 검증을 건너뜁니다."
fi
if [ ! -d "${BUNDLE_DIR}/images" ]; then
echo ">>> [2단계: 압축 해제] 번들 아카이브 압축 해제 중 ➔ /opt ..."
sudo tar -xzvf "${ARCHIVE_FILE}" -C /opt
echo " ✅ 압축 해제 완료: ${BUNDLE_DIR}"
else
echo " ℹ️ 이미 ${BUNDLE_DIR} 디렉터리가 존재하여 압축 해제를 생략합니다."
fi
fi
if [ ! -d "${BUNDLE_DIR}" ]; then
echo "❌ Error: 번들 디렉터리(${BUNDLE_DIR})를 찾을 수 없습니다. 번들 압축 파일을 먼저 반입해주세요."
exit 1
fi
# [1/3] 폐쇄망 환경 skopeo 설치 여부 검사 및 번들 내 오프라인 자동 설치
if ! command -v skopeo >/dev/null 2>&1; then
echo "⚠️ 호스트에 skopeo가 없습니다. 오프라인 로컬 설치를 시작합니다..."
if [ -d "${BUNDLE_DIR}/rpms/skopeo" ]; then
sudo dnf --disablerepo="*" -y localinstall "${BUNDLE_DIR}/rpms/skopeo"/*.rpm 2>/dev/null || \
sudo rpm -Uvh --replacepkgs "${BUNDLE_DIR}/rpms/skopeo"/*.rpm 2>/dev/null || true
elif [ -d "${BUNDLE_DIR}/debs/skopeo" ]; then
sudo dpkg -i "${BUNDLE_DIR}/debs/skopeo"/*.deb 2>/dev/null || true
fi
fi
HARBOR_URL="harbor.internal.net"
HARBOR_PROJECT="k8s"
HARBOR_USER="admin"
HARBOR_PASSWORD="YourHarborPassword"
export SKOPEO_DEST_CREDS="${HARBOR_USER}:${HARBOR_PASSWORD}"
echo "=== [2/3] Harbor 일괄 푸시 시작: https://${HARBOR_URL}/${HARBOR_PROJECT} ==="
find "${IMAGE_DIR}" -type f -name "*.tar" | while read -r ARCHIVE; do
BNAME=$(basename "$ARCHIVE" .tar)
# 파일명 규칙: [이미지명]_[태그].tar ➔ 마지막 언더스코어(_)를 콜론(:)으로 복원
REPO_NAME=$(echo "$BNAME" | sed 's/\(.*\)_\([^:]*\)$/\1:\2/')
DEST_IMG="docker://${HARBOR_URL}/${HARBOR_PROJECT}/${REPO_NAME}"
echo ">>> Pushing: [${ARCHIVE}] ➔ [${DEST_IMG}]"
skopeo copy --dest-tls-verify=false --dest-creds "${SKOPEO_DEST_CREDS}" \
"docker-archive:${ARCHIVE}" "${DEST_IMG}"
done
echo "🎉 모든 이미지가 사설 Harbor로 일괄 푸시되었습니다!"
Troubleshooting
x509: certificate signed by unknown authority
- 원인: 사설 Harbor/Nexus가 사설 인증서(Self-signed SSL/TLS)를 사용할 때 발생
- 해결:
--src-tls-verify=false또는--dest-tls-verify=false플래그 추가
docker load 시 \ 태그 생성 방지
- 원인:
docker-archive:지정 시 끝에 대상 이미지 식별자를 생략했을 때 발생 - 해결:
bash
# ❌ 태그 누락
skopeo copy docker://pause:3.9 docker-archive:/tmp/pause.tar
# ⭕ 정상 (아카이브 메타데이터에 태그 기록됨)
skopeo copy docker://pause:3.9 docker-archive:/tmp/pause.tar:registry.k8s.io/pause:3.9
manifest unknown (404 Not Found)
- 원인: 푸시 대상 Harbor에 프로젝트(Project, 예:
k8s)가 사전 생성되어 있지 않음 - 해결: Harbor 웹 콘솔에서 해당 프로젝트를 사전 생성한 후 스크립트 재실행
Leave a Reply