퍼블릭 의존성 제거부터 복제 정책, ArgoCD 연동까지 실전 가이드
이 문서는 aks 클러스터에서 Harbor OCI Registry를 구축하고, 내부망(Egress 제한) 환경에서 독립적인 컨테이너 생태계를 구축하는 방법을 다룹니다.
목차
- 아키텍처 및 개요
- 현재 구축된 Harbor 환경
- Helm 차트 내부화 전략
- 컨테이너 이미지 미러링
- 복제 정책 및 수명주기 관리
- CI/CD 파이프라인 자동화
- 보안 및 거버넌스
- 모니터링 및 운영
- 실전 배포 예시
- 트러블슈팅 및 베스트 프랙티스
아키텍처 및 개요
목표
- 퍼블릭 Helm 레포지토리/이미지 의존성 제거
- 내부망(Egress 제한)에서 Harbor(OCI)만을 소스로 하는 배포 파이프라인 구축
- OpenTelemetry, Prometheus, Grafana, Fluent Bit, Tempo 등 독립적인 운영 환경 구성
전체 아키텍처

워크플로우
- 퍼블릭 → Harbor 미러링: 외부 Helm 차트와 이미지를 Harbor로 일괄 복사
- 차트 Push: Harbor charts 프로젝트에 OCI 형식으로 저장
- AKS Pull: AKS는 Harbor에서만 차트와 이미지를 가져옴
- 자동 배포: 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 신뢰 여부 점검
베스트 프랙티스
- OCI 차트 사용: Harbor의 레거시 ChartMuseum 대신 OCI 차트 관리 권장
- HTTPS 강제: HTTP 사용 시 각 노드 containerd의 insecure registry 설정 필요 → 운영 난이도 상승
- 정기 동기화: 승인된 버전만 화이트리스트로 관리하여 보안 강화
- 백업 자동화: 정기적인 백업 및 복구 테스트 수행
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
참고 자료
'Kubernetes' 카테고리의 다른 글
| kubeadm 기반 바닐라 Kubernetes 클러스터를 Terraform + Cloud-init 자동화로 설치/운영 (2) | 2025.08.12 |
|---|---|
| Kubernetes에서 외부로부터 들어오는 요청의 Source IP 파악하기 (0) | 2025.07.30 |
| ArgoCD Multi Cluster Setup Guide (GitOps 기반 자동 배포) (3) | 2025.07.29 |
| KubeVirt VM 로그 수집 및 시각화 구성 (Promtail → Loki → Grafana) (1) | 2025.07.02 |
| FastAPI 애플리케이션을 AKS에 배포하고 Prometheus + Grafana로 모니터링 하기 (0) | 2025.06.09 |