퍼블릭 의존성 제거부터 복제 정책, ArgoCD 연동까지 실전 가이드

이 문서는 aks 클러스터에서 Harbor OCI Registry를 구축하고, 내부망(Egress 제한) 환경에서 독립적인 컨테이너 생태계를 구축하는 방법을 다룹니다.

목차

  1. 아키텍처 및 개요
  2. 현재 구축된 Harbor 환경
  3. Helm 차트 내부화 전략
  4. 컨테이너 이미지 미러링
  5. 복제 정책 및 수명주기 관리
  6. CI/CD 파이프라인 자동화
  7. 보안 및 거버넌스
  8. 모니터링 및 운영
  9. 실전 배포 예시
  10. 트러블슈팅 및 베스트 프랙티스

아키텍처 및 개요

목표

  • 퍼블릭 Helm 레포지토리/이미지 의존성 제거
  • 내부망(Egress 제한)에서 Harbor(OCI)만을 소스로 하는 배포 파이프라인 구축
  • OpenTelemetry, Prometheus, Grafana, Fluent Bit, Tempo 등 독립적인 운영 환경 구성

전체 아키텍처

워크플로우

  1. 퍼블릭 → Harbor 미러링: 외부 Helm 차트와 이미지를 Harbor로 일괄 복사
  2. 차트 Push: Harbor charts 프로젝트에 OCI 형식으로 저장
  3. AKS Pull: AKS는 Harbor에서만 차트와 이미지를 가져옴
  4. 자동 배포: ArgoCD를 통한 GitOps 기반 자동 배포

현재 구축된 Harbor 환경

프로젝트 구조

Harbor Projects:
├── charts/           # Helm Charts (OCI)
│   ├── argo-cd      # ArgoCD 차트들
│   ├── monitoring   # Grafana, Prometheus, Tempo 등
│   ├── logging      # Fluent-bit 등
│   └── o11y         # OpenTelemetry 차트들
├── images/           # 컨테이너 이미지 (구축 중)
│   ├── monitoring/  # 모니터링 이미지들
│   ├── logging/     # 로깅 이미지들
│   └── base/        # 기본 이미지들
└── helm/             # Helm Repository (선택사항)

네트워크 구성

  • AKS Web App Routing IngressClass: webapprouting.kubernetes.azure.com
  • 외부 URL: https://harbor.xx.nip.io
  • TLS: 자체 서명 인증서 (SAN 포함)
  • 네트워크: 내부망 (Egress 제한)

인증 구성

  • 관리자 계정: admin / adminpass
  • 로봇 계정: 프로젝트별 push/pull 권한 (구성 중)
  • 프로젝트 범위: charts, images 프로젝트별 권한 분리

Helm 차트 내부화 전략

1. 퍼블릭에서 .tgz 받기

# Helm Repository 추가
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add argo    https://argoproj.github.io/argo-helm
helm repo add fluent  https://fluent.github.io/helm-charts
helm repo add o11y    https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo add prom    https://prometheus-community.github.io/helm-charts
helm repo update

# 차트 다운로드
helm pull grafana/tempo --version 1.15.0
helm pull grafana/grafana --version 8.5.11
helm pull argo/argo-cd --version 7.6.12
helm pull fluent/fluent-bit --version 0.47.10
helm pull prom/kube-prometheus-stack --version 65.4.0
helm pull o11y/opentelemetry-operator --version 0.63.2

2. Harbor(OCI)로 Push

export HARBOR=harbor.xx.nip.io
export PROJ=charts

# Harbor 로그인
helm registry login $HARBOR --username <user|robot> --password <token>

# 일괄 업로드
for TGZ in *.tgz; do
  echo "==> pushing $TGZ"
  helm push "$TGZ" oci://$HARBOR/$PROJ
done

3. ArgoCD에 Harbor(OCI) 등록

apiVersion: v1
kind: Secret
metadata:
  name: harbor-oci-charts
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  name: harbor-oci
  type: helm
  url: oci://harbor.xx.nip.io/charts
  username: <user|robot>
  password: <token>
  enableOCI: "true"

4. 애플리케이션 예시 (Tempo)

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: tempo
  namespace: argocd
spec:
  project: default
  source:
    repoURL: oci://harbor.xx.nip.io/charts
    chart: tempo
    targetRevision: 1.15.0
    helm:
      values: |
        tempo:
          metricsGenerator:
            enabled: true
  destination:
    server: https://kubernetes.default.svc
    namespace: observability
  syncPolicy:
    automated: { prune: true, selfHeal: true }

컨테이너 이미지 미러링

1. 차트가 참조하는 이미지 목록 추출

# 템플릿 렌더링
helm template tempo   grafana/tempo --version 1.15.0 > /tmp/tempo.yaml
helm template grafana grafana/grafana --version 8.5.11 > /tmp/grafana.yaml
helm template argo    argo/argo-cd --version 7.6.12 > /tmp/argocd.yaml
helm template fb      fluent/fluent-bit --version 0.47.10 > /tmp/fluent-bit.yaml
helm template otel    o11y/opentelemetry-operator --version 0.63.2 > /tmp/otel.yaml
helm template kps     prom/kube-prometheus-stack --version 65.4.0 > /tmp/kps.yaml

# image:와 repository:+tag: 두 패턴 모두 수집
grep -hoE 'image:\s*"?[^" ]+\S' /tmp/*.yaml | awk '{print $2}' | sed -e 's/^"//' -e 's/"$//' > /tmp/images_a.txt
awk '/repository:/{r=$2} /tag:/{t=$2; if(r!=""&&t!=""){print r":"t; r=""; t=""}}' /tmp/*.yaml \
 | sed -e 's/^"//' -e 's/"$//' > /tmp/images_b.txt
cat /tmp/images_a.txt /tmp/images_b.txt | sed 's/@.*$//' | sort -u > /tmp/images.txt

2. Harbor images로 미러링

export HARBOR=harbor.xx.nip.io
export IMGPROJ=images

# Harbor 로그인
docker login $HARBOR -u <robot> -p <token>

# 일괄 미러링
while read -r SRC; do
  [ -z "$SRC" ] && continue
  # docker.io 생략 보정
  if [[ "$SRC" != *"/"* ]]; then SRC="docker.io/library/$SRC"; fi
  DEST="$HARBOR/$IMGPROJ/$SRC"
  echo ">> $SRC -> $DEST"
  docker pull "$SRC"
  docker tag  "$SRC" "$DEST"
  docker push "$DEST"
done < /tmp/images.txt

3. 네임스페이스 Pull Secret

kubectl -n observability create secret docker-registry harbor-pull \
  --docker-server=$HARBOR --docker-username='<robot>' --docker-password='<token>' \
  --docker-email='devnull@example.com'

kubectl -n observability patch serviceaccount default \
  -p '{"imagePullSecrets":[{"name":"harbor-pull"}]}'

4. values 오버라이드 예시

kube-prometheus-stack (전역 레지스트리 사용)

global:
  imageRegistry: harbor.xx.nip.io/images
imagePullSecrets:
  - name: harbor-pull

Grafana

image:
  repository: harbor.xx.nip.io/images/docker.io/grafana/grafana
  tag: "11.3.0"
imagePullSecrets:
  - name: harbor-pull

Tempo

image:
  repository: harbor.xx.nip.io/images/docker.io/grafana/tempo
  tag: "2.6.1"
imagePullSecrets:
  - name: harbor-pull

복제 정책 및 수명주기 관리

Harbor Replication 설정

# Harbor Replication 규칙 예시
apiVersion: v1
kind: ConfigMap
metadata:
  name: harbor-replication-rules
  namespace: harbor
data:
  rules: |
    - name: "ecr-sync"
      description: "ECR에서 Harbor로 Pull-based 동기화"
      dest_namespace: "images"
      src_registry: "ecr"
      dest_registry: "harbor"
      filters:
        - type: "name"
          value: "monitoring/*"
      trigger:
        type: "manual"
        cron: "0 2 * * *"  # 매일 새벽 2시
      deletion: false
      override: true
      enabled: true

Retention 정책 (보존)

# Harbor Retention 정책 예시
apiVersion: v1
kind: ConfigMap
metadata:
  name: harbor-retention-policy
  namespace: harbor
data:
  policy: |
    - project: "charts"
      rules:
        - selector:
            kind: "chart"
            tag: "latest"
          retention:
            count: 5
            days: 90
        - selector:
            kind: "chart"
            tag: "v*"
          retention:
            count: 10
            days: 180

    - project: "images"
      rules:
        - selector:
            kind: "image"
            tag: "latest"
          retention:
            count: 3
            days: 30
        - selector:
            kind: "image"
            tag: "v*"
          retention:
            count: 5
            days: 90

Immutability 정책 (변경 불가)

# Harbor Immutability 정책 예시
apiVersion: v1
kind: ConfigMap
metadata:
  name: harbor-immutability-policy
  namespace: harbor
data:
  policy: |
    - project: "charts"
      rules:
        - selector:
            tag: "v*.*.*"  # 시맨틱 버전 태그
          immutable: true
        - selector:
            tag: "latest"
          immutable: false

    - project: "images"
      rules:
        - selector:
            tag: "v*.*.*"
          immutable: true
        - selector:
            tag: "latest"
          immutable: false

Trivy 취약점 스캔 정책

# Harbor Trivy 설정 예시
trivy:
  enabled: true
  image:
    repository: goharbor/trivy-adapter-photon
    tag: v2.5.4
  storageClass: "managed-premium"
  size: 10Gi

  # 스캔 정책
  scanPolicy:
    - project: "images"
      scanOnPush: true
      scanOnPull: false
      scanSchedule: "0 1 * * *"  # 매일 새벽 1시
      severityThreshold: "MEDIUM"
      allowList:
        - cve: "CVE-2023-1234"
          reason: "False positive"
          expires: "2024-12-31"

CI/CD 파이프라인 자동화

1. 승인 버전 목록 (charts.yaml)

# charts.yaml - 승인된 버전 관리
charts:
  - name: grafana/tempo
    version: 1.15.0
    approved: true
    approvedDate: "2025-08-17"
    approvedBy: "devops-team"
  - name: grafana/grafana
    version: 8.5.11
    approved: true
    approvedDate: "2025-08-17"
    approvedBy: "devops-team"
  - name: argo/argo-cd
    version: 7.6.12
    approved: true
    approvedDate: "2025-08-17"
    approvedBy: "devops-team"
  - name: fluent/fluent-bit
    version: 0.47.10
    approved: true
    approvedDate: "2025-08-17"
    approvedBy: "devops-team"
  - name: prom/kube-prometheus-stack
    version: 65.4.0
    approved: true
    approvedDate: "2025-08-17"
    approvedBy: "devops-team"
  - name: o11y/opentelemetry-operator
    version: 0.63.2
    approved: true
    approvedDate: "2025-08-17"
    approvedBy: "devops-team"

2. GitHub Actions 자동화

name: Mirror Helm Charts to Harbor
on: 
  workflow_dispatch: {}
  schedule: [{cron: '0 3 * * 1'}]  # 매주 월요일 새벽 3시

env:
  HARBOR_HOST: harbor.xx.nip.io
  HARBOR_USER: ${{ secrets.HARBOR_USER }}
  HARBOR_PASS: ${{ secrets.HARBOR_PASS }}

jobs:
  mirror:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: azure/setup-helm@v4

      - name: Setup Helm Repositories
        run: |
          helm repo add grafana https://grafana.github.io/helm-charts
          helm repo add argo    https://argoproj.github.io/argo-helm
          helm repo add fluent  https://fluent.github.io/helm-charts
          helm repo add o11y    https://open-telemetry.github.io/opentelemetry-helm-charts
          helm repo add prom    https://prometheus-community.github.io/helm-charts
          helm repo update

      - name: Login Harbor OCI
        run: |
          echo "$HARBOR_PASS" | helm registry login $HARBOR_HOST \
            --username "$HARBOR_USER" --password-stdin

      - name: Pull & Push Charts
        run: |
          # 승인된 차트만 다운로드
          yq '.charts[] | select(.approved==true) | .name + ":" + .version' charts.yaml -r | \
          while read ITEM; do
            NAME=${ITEM%%:*}; VER=${ITEM##*:}
            echo "Processing $NAME:$VER"
            helm pull "$NAME" --version "$VER"
          done

          # Harbor로 업로드
          for TGZ in *.tgz; do
            echo "Pushing $TGZ to Harbor..."
            helm push "$TGZ" oci://$HARBOR_HOST/charts
          done

      - name: Mirror Images
        run: |
          # 이미지 목록 추출 및 미러링
          for TGZ in *.tgz; do
            helm template temp-chart $TGZ > /tmp/temp.yaml
            grep -hoE 'image:\s*"?[^" ]+\S' /tmp/temp.yaml | \
            awk '{print $2}' | sed -e 's/^"//' -e 's/"$//' | \
            while read IMAGE; do
              if [[ -n "$IMAGE" ]]; then
                echo "Mirroring $IMAGE"
                skopeo copy docker://$IMAGE \
                  docker://$HARBOR_HOST/images/$IMAGE
              fi
            done
          done

보안 및 거버넌스

Robot 계정 관리

# Harbor Robot 계정 예시
apiVersion: v1
kind: Secret
metadata:
  name: harbor-robot-accounts
  namespace: harbor
type: Opaque
data:
  charts-push: <base64-encoded-token>    # charts 프로젝트 push 권한
  charts-pull: <base64-encoded-token>    # charts 프로젝트 pull 권한
  images-push: <base64-encoded-token>    # images 프로젝트 push 권한
  images-pull: <base64-encoded-token>    # images 프로젝트 pull 권한

NetworkPolicy 접근 제어

# Harbor 접근 제어 정책
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: harbor-access-policy
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: <HARBOR_IP>/32
    ports:
    - protocol: TCP
      port: 443
    - protocol: TCP
      port: 80
    - protocol: TCP
      port: 5000
  - to: [] # 다른 모든 아웃바운드 트래픽 차단

이미지 스캔 및 정책

# Harbor 보안 정책 예시
security:
  scanOnPush: true
  scanOnPull: false
  severityThreshold: "MEDIUM"

  # 허용 목록 (False Positive 처리)
  allowList:
    - cve: "CVE-2023-1234"
      reason: "False positive in our environment"
      expires: "2024-12-31"
      approvedBy: "security-team"

  # 차단 목록
  blockList:
    - cve: "CVE-2023-CRITICAL"
      reason: "Critical security vulnerability"
      action: "BLOCK"

모니터링 및 운영

Harbor 메트릭 수집

# prometheus-values.yaml에 Harbor 스크랩 추가
scrape_configs:
  - job_name: 'harbor'
    static_configs:
      - targets: ['harbor-core:8001', 'harbor-registry:8001', 'harbor-jobservice:8001']
    metrics_path: /metrics
    scheme: http
    tls_config:
      insecure_skip_verify: true

Grafana 대시보드

Harbor 메트릭을 시각화하는 Grafana 대시보드를 구성하여 다음을 모니터링:

  • 성능 메트릭: 이미지 push/pull 성능, API 응답 시간
  • 용량 메트릭: 저장소 사용량, 프로젝트별 사용량
  • 보안 메트릭: 스캔 결과, 취약점 통계
  • 운영 메트릭: 동시 사용자 수, 에러율, 백업 상태

백업 및 복구 전략

Harbor 데이터 백업

#!/bin/bash
# harbor-backup.sh

BACKUP_DIR="/backup/harbor/$(date +%Y%m%d)"
mkdir -p $BACKUP_DIR

# Harbor 데이터베이스 백업
kubectl exec -n harbor deployment/harbor-database -- pg_dump -U postgres harbor > $BACKUP_DIR/harbor-db.sql

# Harbor 설정 백업
kubectl get configmap -n harbor -o yaml > $BACKUP_DIR/harbor-configmaps.yaml
kubectl get secret -n harbor -o yaml > $BACKUP_DIR/harbor-secrets.yaml

# PVC 데이터 백업 (선택사항)
kubectl get pvc -n harbor -o yaml > $BACKUP_DIR/harbor-pvcs.yaml

echo "Backup completed: $BACKUP_DIR"

복구 절차

# 1. Harbor 삭제
kubectl delete -k harbor/

# 2. 데이터베이스 복구
kubectl apply -k harbor/
kubectl wait --for=condition=ready pod -l app=harbor-database -n harbor
kubectl exec -n harbor deployment/harbor-database -- psql -U postgres harbor < /backup/harbor/20241201/harbor-db.sql

# 3. 설정 복구
kubectl apply -f /backup/harbor/20241201/harbor-configmaps.yaml
kubectl apply -f /backup/harbor/20241201/harbor-secrets.yaml

실전 배포 예시

현재 등록된 차트 현황

차트명 버전 상태 승인일 승인자
tempo 1.15.0, 1.7.1 업로드 완료 2025-08-17 devops-team
grafana 8.5.11, 8.6.3 업로드 완료 2025-08-17 devops-team
argo-cd 5.51.6, 7.6.12 업로드 완료 2025-08-17 devops-team
fluent-bit 0.47.10 업로드 완료 2025-08-17 devops-team
kube-prometheus-stack 65.4.0, 65.5.1 업로드 완료 2025-08-17 devops-team
opentelemetry-collector 0.95.0 업로드 완료 2025-08-17 devops-team
opentelemetry-operator 0.63.1, 0.63.2 업로드 완료 2025-08-17 devops-team

ArgoCD Application 

Grafana 배포

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: grafana
  namespace: argocd
spec:
  project: default
  source:
    repoURL: oci://harbor.xx.nip.io/charts
    chart: grafana
    targetRevision: 8.5.11
    helm:
      values: |
        image:
          repository: harbor.xx.nip.io/images/docker.io/grafana/grafana
          tag: "8.5.11"
        imagePullSecrets:
          - name: harbor-pull
        persistence:
          enabled: true
          size: 10Gi
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring
  syncPolicy:
    automated: { prune: true, selfHeal: true }

Tempo 배포

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: tempo
  namespace: argocd
spec:
  project: default
  source:
    repoURL: oci://harbor.xx.nip.io/charts
    chart: tempo
    targetRevision: 1.15.0
    helm:
      values: |
        image:
          repository: harbor.xx.nip.io/images/docker.io/grafana/tempo
          tag: "1.15.0"
        imagePullSecrets:
          - name: harbor-pull
        tempo:
          metricsGenerator:
            enabled: true
        storage:
          trace:
            backend: local
            local:
              path: /var/tempo/traces
  destination:
    server: https://kubernetes.default.svc
    namespace: observability
  syncPolicy:
    automated: { prune: true, selfHeal: true }

트러블슈팅 및 베스트 프랙티스

자주 발생하는 문제

1. 인증서 문제

# 오류: x509: certificate is valid for ingress.local, not harbor....
# 해결: TLS Secret 및 Ingress 호스트 재설정

# 인증서 체인 확인
openssl s_client -showcerts -servername harbor.2xx.nip.io \
  -connect harbor.xx.nip.io:443

2. Helm 인증 문제

# 오류: unknown flag: --insecure-skip-tls-verify
# 해결: Helm은 --insecure (login 전용) 사용. push 시에는 OS 신뢰 스토어가 중요

3. 이미지 Pull 실패

# 오류: ImagePullBackOff
# 해결: 경로/태그/네임스페이스 imagePullSecrets/사설CA 신뢰 여부 점검

베스트 프랙티스

  1. OCI 차트 사용: Harbor의 레거시 ChartMuseum 대신 OCI 차트 관리 권장
  2. HTTPS 강제: HTTP 사용 시 각 노드 containerd의 insecure registry 설정 필요 → 운영 난이도 상승
  3. 정기 동기화: 승인된 버전만 화이트리스트로 관리하여 보안 강화
  4. 백업 자동화: 정기적인 백업 및 복구 테스트 수행

Harbor 상태 확인

# Harbor 카탈로그
curl -vk https://harbor.xx.nip.io/v2/_catalog

# 차트 메타 확인
helm show chart oci://harbor.xx.nip.io/charts/tempo --version 1.15.0

# Harbor 상태 확인
kubectl get pods -n harbor
kubectl get svc -n harbor
kubectl get ingress -n harbor

로그 확인

kubectl logs -n harbor deployment/harbor-core
kubectl logs -n harbor deployment/harbor-registry
kubectl logs -n harbor deployment/harbor-jobservice

스토리지 확인

kubectl get pvc -n harbor
kubectl get pv

참고 자료