Skopeo 컨테이너 유틸리티: 데몬리스 이미지 검사 및 복사

💡 핵심 요약 (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/load 100% 호환 단일 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 웹 콘솔에서 해당 프로젝트를 사전 생성한 후 스크립트 재실행

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *