콘텐츠로 이동






Kubernetes Helm 기반 Harbor 설치 및 운영 가이드

대상: Kubernetes 환경에 Helm Chart로 Harbor를 설치하는 경우
범위: Ingress 기반 HTTPS, 사설 CA, StorageClass/PVC, Trivy 온라인·오프라인 DB 관리
설치 전 Harbor Helm Chart와 Chart가 설치하는 Harbor App Version을 확인한다.

1. 개요 및 사전 준비

Harbor는 OCI/Docker 이미지, Helm OCI Chart, SBOM 등의 클라우드 네이티브 아티팩트를 저장·배포하는 컨테이너 레지스트리다.

Kubernetes 환경에서는 Harbor 공식 Helm Chart를 사용하여 설치할 수 있다. 다중 노드 고가용성 환경에서는 고가용성 Ingress Controller, 공유 가능한 PVC 또는 Object Storage, 외부 PostgreSQL 및 외부 Redis 구성을 권장한다.

1.1 단일 노드와 HA 구성 비교

구분 단일 노드 구성 HA 구성
Ingress 단일 Ingress Controller 가능 고가용성 Ingress Controller 필요
Registry Storage RWO PVC 가능 RWX PVC 또는 Object Storage 권장
PostgreSQL Harbor 내부 DB 가능 외부 HA PostgreSQL 권장
Redis Harbor 내부 Redis 가능 외부 HA Redis 또는 Sentinel 권장
Harbor Replica 기본값 1 Core, Portal, Registry 등 2 이상
Job Log PVC 또는 DB DB 저장 권장

Harbor Helm 기반 HA 구성에서는 외부 Endpoint, PostgreSQL, Redis, 공유 스토리지 또는 Object Storage를 사용자가 제공해야 한다. Harbor Chart가 외부 인프라의 고가용성을 자동 구성하지는 않는다.

1.2 사전 요구사항

항목 권장 사항
Kubernetes 지원 중인 Kubernetes 버전
Helm Helm v3
Ingress Controller ingress-nginx, Envoy Gateway, ALB 등
DNS Harbor FQDN이 Ingress 또는 LoadBalancer IP를 가리켜야 함
StorageClass 동적 PVC Provisioning 가능해야 함
인증서 공인 CA 또는 사설 CA 인증서
Container Runtime containerd 또는 CRI-O 권장
모니터링 Prometheus, Grafana 연동 권장

1.3 네임스페이스 및 Helm Repository 준비

export HARBOR_NAMESPACE="harbor"
export HARBOR_RELEASE="harbor"
export HARBOR_FQDN="harbor.example.internal"

kubectl create namespace "${HARBOR_NAMESPACE}"

helm repo add harbor https://helm.goharbor.io
helm repo update

helm search repo harbor/harbor --versions | head -n 10

현재 Chart의 기본 설정 파일을 내려받는다.

mkdir -p ~/harbor-helm
cd ~/harbor-helm

helm show values harbor/harbor > values.yaml

운영 환경에서는 latest 성격의 무고정 설치보다 Chart Version을 명시해 설치하는 것을 권장한다.

helm search repo harbor/harbor --versions

2. 사설 CA 인증서와 trust-ca 구성

운영 환경에서는 HTTPS를 사용한다. 여기서는 10년 유효기간의 사설 Root CA를 만들고, Harbor FQDN에 대한 서버 인증서를 생성한다.

Root CA 인증서는 Harbor Portal 접속 사용자, Docker/Podman 클라이언트, Kubernetes 노드의 containerd 또는 CRI-O에 배포해야 한다.

2.1 환경 변수 설정

export HARBOR_FQDN="harbor.example.internal"
export HARBOR_IP="10.10.10.20"

export CA_DAYS=3650
export SERVER_DAYS=3650

mkdir -p ~/harbor-pki
cd ~/harbor-pki

2.2 10년 사설 Root CA 생성

openssl genrsa -out harbor-root-ca.key 4096
chmod 600 harbor-root-ca.key

openssl req -x509 -new -nodes \
  -key harbor-root-ca.key \
  -sha256 \
  -days "${CA_DAYS}" \
  -out harbor-root-ca.crt \
  -subj "/C=KR/ST=Seoul/L=Seoul/O=Example/OU=Platform/CN=Example Harbor Root CA"

CA 인증서 정보를 확인한다.

openssl x509 -in harbor-root-ca.crt -noout \
  -subject \
  -issuer \
  -dates

harbor-root-ca.key는 Root CA 개인 키다. Kubernetes Secret이나 Harbor Namespace에 저장하지 않고 별도 PKI 서버 또는 보안 저장소에 보관한다.

2.3 SAN 포함 Harbor 서버 인증서 생성

TLS 클라이언트는 CN만이 아니라 SAN(Subject Alternative Name)을 검증한다. FQDN과 필요 시 Ingress/LoadBalancer IP를 SAN에 포함한다.

cat > "${HARBOR_FQDN}.cnf" <<EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no

[req_distinguished_name]
C = KR
ST = Seoul
L = Seoul
O = Example
OU = Platform
CN = ${HARBOR_FQDN}

[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${HARBOR_FQDN}
IP.1 = ${HARBOR_IP}
EOF

서버 개인 키와 CSR을 생성한다.

openssl genrsa -out "${HARBOR_FQDN}.key" 4096
chmod 600 "${HARBOR_FQDN}.key"

openssl req -new \
  -key "${HARBOR_FQDN}.key" \
  -out "${HARBOR_FQDN}.csr" \
  -config "${HARBOR_FQDN}.cnf"

Root CA로 서버 인증서를 서명한다.

openssl x509 -req \
  -in "${HARBOR_FQDN}.csr" \
  -CA harbor-root-ca.crt \
  -CAkey harbor-root-ca.key \
  -CAcreateserial \
  -out "${HARBOR_FQDN}.crt" \
  -days "${SERVER_DAYS}" \
  -sha256 \
  -extensions v3_req \
  -extfile "${HARBOR_FQDN}.cnf"

인증서의 SAN과 CA 체인을 검증한다.

openssl x509 \
  -in "${HARBOR_FQDN}.crt" \
  -noout \
  -text | grep -A1 -E "Subject:|Subject Alternative Name"

openssl verify \
  -CAfile harbor-root-ca.crt \
  "${HARBOR_FQDN}.crt"

2.4 Harbor Ingress TLS Secret 생성

Harbor Namespace에 TLS Secret을 생성한다.

kubectl -n "${HARBOR_NAMESPACE}" create secret tls harbor-ingress-tls \
  --cert="${HARBOR_FQDN}.crt" \
  --key="${HARBOR_FQDN}.key"

Secret 내용을 확인한다.

kubectl -n "${HARBOR_NAMESPACE}" get secret harbor-ingress-tls

2.5 Harbor 내부 Component용 CA Bundle Secret

사설 CA 인증서가 필요한 내부 연동, 외부 Object Storage, 프록시, 복제 대상 Registry 등을 위해 CA Bundle Secret을 생성한다.

kubectl -n "${HARBOR_NAMESPACE}" create secret generic harbor-ca-bundle \
  --from-file=ca.crt=harbor-root-ca.crt

확인한다.

kubectl -n "${HARBOR_NAMESPACE}" describe secret harbor-ca-bundle

2.6 Rocky Linux/RHEL trust-ca 등록

Harbor에 Push/Pull하는 모든 Linux 노드에 적용한다.

sudo cp harbor-root-ca.crt \
  /etc/pki/ca-trust/source/anchors/harbor-root-ca.crt

sudo update-ca-trust extract

등록 여부를 확인한다.

trust list | grep -i -A2 "Example Harbor Root CA"

2.7 Docker Client CA 등록

Docker를 사용하는 노드에는 FQDN별 Registry CA도 등록한다.

sudo mkdir -p "/etc/docker/certs.d/${HARBOR_FQDN}"

sudo cp harbor-root-ca.crt \
  "/etc/docker/certs.d/${HARBOR_FQDN}/ca.crt"

sudo systemctl restart docker

검증한다.

docker login "${HARBOR_FQDN}"

2.8 containerd 노드 CA 등록

Kubernetes 노드의 Runtime이 containerd라면 모든 Worker Node에 적용한다.

sudo mkdir -p "/etc/containerd/certs.d/${HARBOR_FQDN}"

sudo cp harbor-root-ca.crt \
  "/etc/containerd/certs.d/${HARBOR_FQDN}/ca.crt"

sudo tee "/etc/containerd/certs.d/${HARBOR_FQDN}/hosts.toml" > /dev/null <<EOF
server = "https://${HARBOR_FQDN}"

[host."https://${HARBOR_FQDN}"]
  capabilities = ["pull", "resolve", "push"]
  ca = "/etc/containerd/certs.d/${HARBOR_FQDN}/ca.crt"
EOF

sudo systemctl restart containerd

RKE2, k3s, OpenShift, EKS, GKE, AKS 등은 별도 Private Registry CA 배포 방식을 제공할 수 있으므로, 해당 배포판의 공식 방법을 우선 적용한다.


3. Helm 기반 Harbor 설치

3.1 StorageClass 확인

Harbor는 기본적으로 PVC를 사용한다. 먼저 기본 StorageClass를 확인한다.

kubectl get storageclass

예시 출력:

NAME                   PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE
rook-ceph-block        rook-ceph.rbd.csi.ceph.com     Delete          Immediate
nfs-client (default)   cluster.local/nfs-client        Delete          Immediate

운영 환경에서는 StorageClass와 데이터 특성에 따라 다음을 검토한다.

Harbor 구성 요소 권장 스토리지
Registry 이미지 데이터 Object Storage 또는 고성능 PVC
PostgreSQL 저지연 Block Storage
Redis 저지연 Block Storage
Jobservice 로그 PVC 또는 Database
Trivy Cache/DB PVC
HA Registry RWX PVC 또는 Object Storage

3.2 values.yaml 예시

다음은 ingress-nginx, 사설 CA TLS Secret, 내부 DB/Redis, PVC, Trivy 활성화 기준의 예시다.

expose:
  type: ingress

  tls:
    enabled: true
    certSource: secret
    secret:
      secretName: harbor-ingress-tls

  ingress:
    className: nginx

    annotations:
      nginx.ingress.kubernetes.io/ssl-redirect: "true"
      nginx.ingress.kubernetes.io/proxy-body-size: "0"
      nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
      nginx.ingress.kubernetes.io/proxy-send-timeout: "600"

    hosts:
      core: harbor.example.internal

externalURL: https://harbor.example.internal

harborAdminPassword: "CHANGE_THIS_ADMIN_PASSWORD"

secretKey: "CHANGE_ME_16CHAR"

caSecretName: harbor-ca-bundle
caBundleSecretName: harbor-ca-bundle

persistence:
  enabled: true

  # helm uninstall 이후 PVC 자동 삭제 방지
  resourcePolicy: "keep"

  persistentVolumeClaim:
    registry:
      storageClass: "nfs-client"
      accessMode: ReadWriteOnce
      size: 200Gi

    jobservice:
      jobLog:
        storageClass: "nfs-client"
        accessMode: ReadWriteOnce
        size: 10Gi

    database:
      storageClass: "nfs-client"
      accessMode: ReadWriteOnce
      size: 20Gi

    redis:
      storageClass: "nfs-client"
      accessMode: ReadWriteOnce
      size: 10Gi

    trivy:
      storageClass: "nfs-client"
      accessMode: ReadWriteOnce
      size: 20Gi

  imageChartStorage:
    type: filesystem
    filesystem:
      rootdirectory: /storage

trivy:
  enabled: true

  resources:
    requests:
      cpu: 200m
      memory: 512Mi
    limits:
      cpu: "1"
      memory: 1Gi

  severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL"
  ignoreUnfixed: false
  vulnType: "os,library"

  securityCheck: "vuln,config,secret"

  skipUpdate: false
  skipJavaDBUpdate: false
  offlineScan: false

  dbRepository:
    - "mirror.gcr.io/aquasec/trivy-db"
    - "ghcr.io/aquasecurity/trivy-db"

  javaDBRepository:
    - "mirror.gcr.io/aquasec/trivy-java-db"
    - "ghcr.io/aquasecurity/trivy-java-db"

proxy:
  httpProxy: ""
  httpsProxy: ""
  noProxy: "127.0.0.1,localhost,.local,.internal,.svc,.cluster.local"
  components:
    - core
    - jobservice
    - trivy

metrics:
  enabled: true

  serviceMonitor:
    enabled: false

cache:
  enabled: false
  expireHours: 24

database:
  type: internal

  internal:
    password: "CHANGE_THIS_DATABASE_PASSWORD"
    shmSizeLimit: 1Gi

redis:
  type: internal

jobservice:
  maxJobWorkers: 10

  jobLoggers:
    - file

logLevel: info

secretKey는 16자 문자열이어야 한다. 운영 환경에서는 harborAdminPassword, database.internal.password, secretKey를 Git 저장소에 평문으로 보관하지 말고 External Secrets, Sealed Secrets, SOPS 등을 사용한다.

3.3 Secret 기반 관리자 비밀번호 설정

관리자 비밀번호를 values.yaml에서 분리하려면 Secret을 생성한다.

kubectl -n "${HARBOR_NAMESPACE}" create secret generic harbor-admin-password \
  --from-literal=HARBOR_ADMIN_PASSWORD='CHANGE_THIS_ADMIN_PASSWORD'

values.yaml에서 다음처럼 설정한다.

existingSecretAdminPassword: harbor-admin-password
existingSecretAdminPasswordKey: HARBOR_ADMIN_PASSWORD

3.4 Helm Install

먼저 렌더링 결과를 확인한다.

helm template "${HARBOR_RELEASE}" harbor/harbor \
  --namespace "${HARBOR_NAMESPACE}" \
  --values values.yaml \
  > harbor-rendered.yaml

차트를 설치한다.

helm upgrade --install "${HARBOR_RELEASE}" harbor/harbor \
  --namespace "${HARBOR_NAMESPACE}" \
  --create-namespace \
  --values values.yaml \
  --wait \
  --timeout 15m

특정 Chart Version을 고정하려면 다음처럼 설치한다.

helm upgrade --install "${HARBOR_RELEASE}" harbor/harbor \
  --namespace "${HARBOR_NAMESPACE}" \
  --create-namespace \
  --version "<CHART_VERSION>" \
  --values values.yaml \
  --wait \
  --timeout 15m

3.5 설치 상태 확인

kubectl get pods -n "${HARBOR_NAMESPACE}"

kubectl get svc -n "${HARBOR_NAMESPACE}"

kubectl get ingress -n "${HARBOR_NAMESPACE}"

kubectl get pvc -n "${HARBOR_NAMESPACE}"

helm list -n "${HARBOR_NAMESPACE}"

Pod 상태가 모두 Running 또는 Completed인지 확인한다.

kubectl get pod -n "${HARBOR_NAMESPACE}" -o wide

문제가 발생하면 Helm Release 상태와 Pod 이벤트를 확인한다.

helm status "${HARBOR_RELEASE}" \
  -n "${HARBOR_NAMESPACE}"

kubectl get events \
  -n "${HARBOR_NAMESPACE}" \
  --sort-by='.lastTimestamp'

kubectl describe pod \
  -n "${HARBOR_NAMESPACE}" \
  <POD_NAME>

3.6 접속 및 Push/Pull 테스트

Harbor Portal 접속 주소:

https://harbor.example.internal

로그인 정보:

ID: admin
Password: harborAdminPassword 또는 existingSecretAdminPassword의 값

Portal에서 Projects → + New Project로 이동하여 platform 프로젝트를 생성한다.

Docker Push/Pull 테스트:

export HARBOR_FQDN="harbor.example.internal"

docker login "${HARBOR_FQDN}"

docker tag nginx:1.27 \
  "${HARBOR_FQDN}/platform/nginx:1.27"

docker push \
  "${HARBOR_FQDN}/platform/nginx:1.27"

docker pull \
  "${HARBOR_FQDN}/platform/nginx:1.27"

Kubernetes Pod에서 Harbor 이미지를 Pull하는 예시:

apiVersion: v1
kind: Pod
metadata:
  name: harbor-pull-test
  namespace: default
spec:
  containers:
    - name: nginx
      image: harbor.example.internal/platform/nginx:1.27
  imagePullSecrets:
    - name: harbor-registry-credential

Docker Registry 인증 Secret 생성:

kubectl create secret docker-registry harbor-registry-credential \
  --namespace default \
  --docker-server="${HARBOR_FQDN}" \
  --docker-username="<HARBOR_USER>" \
  --docker-password="<HARBOR_PASSWORD>"

4. Trivy 온라인·오프라인 DB 관리

Trivy의 취약점 탐지 결과는 Vulnerability DB 최신성에 따라 달라진다.

데이터 용도 필요 여부
Vulnerability DB OS 및 패키지 CVE 탐지 필수
Java DB JAR 기반 Java 패키지 탐지 Java 스캔 시 권장
Checks Bundle IaC 및 설정 오류 검사 config 검사 시 필요

4.1 온라인 환경 Trivy 설정

온라인 환경에서는 skipUpdate: false를 유지한다.

trivy:
  enabled: true
  skipUpdate: false
  skipJavaDBUpdate: false
  offlineScan: false

  securityCheck: "vuln,config,secret"

  dbRepository:
    - "mirror.gcr.io/aquasec/trivy-db"
    - "ghcr.io/aquasecurity/trivy-db"

  javaDBRepository:
    - "mirror.gcr.io/aquasec/trivy-java-db"
    - "ghcr.io/aquasecurity/trivy-java-db"

GitHub API Rate Limit이 문제되는 환경에서는 GitHub Token을 설정한다.

trivy:
  gitHubToken: "<GITHUB_TOKEN>"

Token은 values.yaml에 직접 기록하기보다 Secret 또는 Helm Secret 관리 도구를 통해 주입한다.

온라인 환경에서 Trivy 로그를 확인한다.

kubectl logs \
  -n "${HARBOR_NAMESPACE}" \
  deployment/"${HARBOR_RELEASE}"-trivy \
  --tail=300

Pod 이름을 직접 확인하려면 다음을 사용한다.

kubectl get pods \
  -n "${HARBOR_NAMESPACE}" \
  -l component=trivy

4.2 온라인 환경 DB 사전 다운로드

인터넷 연결이 가능한 중계 서버에서 Trivy DB만 미리 받을 수 있다.

mkdir -p ~/trivy-cache

trivy image \
  --cache-dir ~/trivy-cache \
  --download-db-only

Java JAR 스캔이 필요하면 Java DB도 받는다.

trivy image \
  --cache-dir ~/trivy-cache \
  --download-java-db-only

캐시 파일을 확인한다.

find ~/trivy-cache -maxdepth 3 -type f

일반적으로 확인해야 할 파일은 다음과 같다.

db/trivy.db
db/metadata.json
java-db/trivy-java.db

4.3 오프라인 환경 values.yaml 설정

폐쇄망에서는 Trivy DB의 외부 다운로드를 차단한다.

trivy:
  enabled: true

  skipUpdate: true
  skipJavaDBUpdate: true
  offlineScan: true

  securityCheck: "vuln,config,secret"

persistence:
  enabled: true

  persistentVolumeClaim:
    trivy:
      existingClaim: harbor-trivy-cache
      accessMode: ReadWriteOnce
      size: 20Gi

offlineScan: true는 Trivy가 Java dependency 식별 등을 위해 외부 API에 접근하는 동작을 줄인다. 이 값은 DB 다운로드 자체를 막지 않으므로, 폐쇄망에서는 skipUpdate: true도 함께 설정해야 한다.

4.4 오프라인 Trivy PVC 생성

NFS StorageClass 기준의 PVC 예시다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: harbor-trivy-cache
  namespace: harbor
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs-client
  resources:
    requests:
      storage: 20Gi

적용한다.

kubectl apply -f harbor-trivy-cache-pvc.yaml

kubectl get pvc \
  -n "${HARBOR_NAMESPACE}" \
  harbor-trivy-cache

4.5 인터넷 구간에서 DB Package 생성

인터넷 연결 구간의 서버에서 실행한다.

export TRIVY_EXPORT_DIR="$HOME/trivy-offline"

rm -rf "${TRIVY_EXPORT_DIR}"
mkdir -p "${TRIVY_EXPORT_DIR}"

trivy image \
  --cache-dir "${TRIVY_EXPORT_DIR}" \
  --download-db-only

trivy image \
  --cache-dir "${TRIVY_EXPORT_DIR}" \
  --download-java-db-only

tar czvf "trivy-db-$(date +%F).tar.gz" \
  -C "${TRIVY_EXPORT_DIR}" .

sha256sum "trivy-db-$(date +%F).tar.gz" \
  > "trivy-db-$(date +%F).tar.gz.sha256"

생성 파일:

trivy-db-YYYY-MM-DD.tar.gz
trivy-db-YYYY-MM-DD.tar.gz.sha256

4.6 폐쇄망 Kubernetes에 Trivy DB 적재

폐쇄망 환경으로 tarball과 checksum 파일을 반입한다.

sha256sum -c trivy-db-YYYY-MM-DD.tar.gz.sha256

Trivy PVC에 DB를 복사할 임시 Pod를 실행한다.

apiVersion: v1
kind: Pod
metadata:
  name: trivy-db-loader
  namespace: harbor
spec:
  restartPolicy: Never
  containers:
    - name: loader
      image: busybox:1.36
      command:
        - sh
        - -c
        - sleep 36000
      volumeMounts:
        - name: trivy-cache
          mountPath: /trivy-cache
  volumes:
    - name: trivy-cache
      persistentVolumeClaim:
        claimName: harbor-trivy-cache
kubectl apply -f trivy-db-loader.yaml

kubectl get pod \
  -n "${HARBOR_NAMESPACE}" \
  trivy-db-loader

반입한 DB 파일을 Loader Pod로 전송한다.

kubectl cp \
  trivy-db-YYYY-MM-DD.tar.gz \
  "${HARBOR_NAMESPACE}/trivy-db-loader:/tmp/trivy-db.tar.gz"

Pod 내부에서 DB를 풀고 Trivy Cache 경로에 배치한다.

kubectl exec \
  -n "${HARBOR_NAMESPACE}" \
  trivy-db-loader \
  -- sh -c '
    mkdir -p /trivy-cache/db
    mkdir -p /trivy-cache/java-db
    mkdir -p /tmp/trivy-import

    tar xzvf /tmp/trivy-db.tar.gz -C /tmp/trivy-import

    find /tmp/trivy-import -name trivy.db \
      -exec cp -f {} /trivy-cache/db/trivy.db \;

    find /tmp/trivy-import -name metadata.json \
      -exec cp -f {} /trivy-cache/db/metadata.json \;

    find /tmp/trivy-import -name trivy-java.db \
      -exec cp -f {} /trivy-cache/java-db/trivy-java.db \;

    find /trivy-cache -maxdepth 3 -type f
  '

작업이 끝나면 Loader Pod를 삭제한다.

kubectl delete pod \
  -n "${HARBOR_NAMESPACE}" \
  trivy-db-loader

Harbor 설치 또는 업그레이드를 수행한다.

helm upgrade --install "${HARBOR_RELEASE}" harbor/harbor \
  --namespace "${HARBOR_NAMESPACE}" \
  --values values.yaml \
  --wait \
  --timeout 15m

4.7 정기 Trivy DB 업데이트

온라인 환경

온라인 환경은 skipUpdate: false를 유지한다.

DB 업데이트 상태를 확인한다.

kubectl logs \
  -n "${HARBOR_NAMESPACE}" \
  deployment/"${HARBOR_RELEASE}"-trivy \
  --tail=300 | egrep -i "download|update|db|error"

Trivy Adapter를 재시작하려면 다음을 실행한다.

kubectl rollout restart deployment \
  -n "${HARBOR_NAMESPACE}" \
  "${HARBOR_RELEASE}"-trivy

kubectl rollout status deployment \
  -n "${HARBOR_NAMESPACE}" \
  "${HARBOR_RELEASE}"-trivy

기존 이미지의 취약점 결과를 최신 DB 기준으로 갱신하려면 Harbor Portal에서 전체 재스캔 일정을 설정한다.

Administration
→ Interrogation Services
→ Vulnerability
→ Schedule to scan all

오프라인 환경

폐쇄망은 다음 주기를 권장한다.

작업 권장 주기 설명
인터넷 구간 DB 다운로드 매일 신규 CVE 반영
반입 파일 SHA-256 검증 매 반입 무결성 확인
Trivy PVC DB 교체 매일 또는 주 1회 보안 정책에 맞춤
전체 이미지 재스캔 매일 또는 매주 새 DB 기준 재평가

폐쇄망 DB 갱신 순서:

  1. 인터넷 구간에서 최신 DB를 내려받는다.
  2. tarball과 SHA-256 파일을 폐쇄망으로 반입한다.
  3. 무결성을 검증한다.
  4. trivy-db-loader Pod를 실행한다.
  5. PVC의 db/trivy.db, db/metadata.json, java-db/trivy-java.db를 교체한다.
  6. Trivy Deployment를 재시작한다.
  7. Harbor Portal에서 전체 재스캔을 수행하거나 스케줄을 실행한다.

Trivy Pod 재시작:

kubectl rollout restart deployment \
  -n "${HARBOR_NAMESPACE}" \
  "${HARBOR_RELEASE}"-trivy

kubectl rollout status deployment \
  -n "${HARBOR_NAMESPACE}" \
  "${HARBOR_RELEASE}"-trivy

Appendix #1. HA 구성과 외부 서비스 설정

운영 규모가 크거나 Harbor 중단을 줄여야 한다면 내부 PostgreSQL/Redis 대신 외부 고가용성 서비스를 사용한다.

A.1 외부 PostgreSQL 설정

database:
  type: external

  external:
    host: "postgresql-ha.example.internal"
    port: "5432"
    username: "harbor"
    password: "CHANGE_THIS_PASSWORD"
    coreDatabase: "registry"
    notaryServerDatabase: "notary_server"
    notarySignerDatabase: "notary_signer"
    sslmode: "require"

Harbor Core Database는 사전에 생성한다.

CREATE USER harbor WITH ENCRYPTED PASSWORD 'CHANGE_THIS_PASSWORD';
CREATE DATABASE registry OWNER harbor;

A.2 외부 Redis 설정

redis:
  type: external

  external:
    addr: "redis-ha.example.internal:6379"
    password: "CHANGE_THIS_PASSWORD"
    databaseIndex: "0"

Redis Sentinel 구성 예시:

redis:
  type: external

  external:
    addr: "redis-sentinel-1.example.internal:26379,redis-sentinel-2.example.internal:26379,redis-sentinel-3.example.internal:26379"
    sentinelMasterSet: "mymaster"
    password: "CHANGE_THIS_PASSWORD"
    databaseIndex: "0"

A.3 Object Storage 기반 Registry 설정

HA 환경에서 Registry 이미지를 Object Storage에 저장하는 S3 예시다.

persistence:
  imageChartStorage:
    type: s3

    s3:
      region: "ap-northeast-2"
      bucket: "harbor-registry"
      regionendpoint: "https://s3.ap-northeast-2.amazonaws.com"
      secure: true
      v4auth: true
      rootdirectory: "/harbor"
      existingSecret: "harbor-s3-credential"

S3 인증 Secret에는 다음 Key가 필요하다.

REGISTRY_STORAGE_S3_ACCESSKEY
REGISTRY_STORAGE_S3_SECRETKEY

Secret 생성 예시:

kubectl -n "${HARBOR_NAMESPACE}" create secret generic harbor-s3-credential \
  --from-literal=REGISTRY_STORAGE_S3_ACCESSKEY='<ACCESS_KEY>' \
  --from-literal=REGISTRY_STORAGE_S3_SECRETKEY='<SECRET_KEY>'

A.4 Harbor Helm Upgrade

업그레이드 전 현재 설정과 데이터를 백업한다.

helm get values "${HARBOR_RELEASE}" \
  -n "${HARBOR_NAMESPACE}" \
  -a > harbor-values-backup.yaml

kubectl get pvc \
  -n "${HARBOR_NAMESPACE}"

kubectl get secret \
  -n "${HARBOR_NAMESPACE}"

Repository를 업데이트하고 변경 내용을 확인한다.

helm repo update

helm diff upgrade "${HARBOR_RELEASE}" harbor/harbor \
  --namespace "${HARBOR_NAMESPACE}" \
  --values values.yaml

업그레이드를 수행한다.

helm upgrade "${HARBOR_RELEASE}" harbor/harbor \
  --namespace "${HARBOR_NAMESPACE}" \
  --values values.yaml \
  --wait \
  --timeout 30m

업그레이드 상태를 확인한다.

helm status "${HARBOR_RELEASE}" \
  -n "${HARBOR_NAMESPACE}"

kubectl get pods \
  -n "${HARBOR_NAMESPACE}"

Harbor 업그레이드 전에는 대상 Chart Version의 릴리스 노트, 지원 Kubernetes 버전, DB Migration 요구사항, Trivy 호환성을 반드시 확인한다.