๐ ๊ฐ์ - Harbor - K8S
๐ณ๏ธโ๐ [๊ถ๊ธํ์ ]
๐[๋ชฉ์ฐจ]
- 1๏ธโฃ ์ค๋น
- 2๏ธโฃ ์ค์
- 3๏ธโฃ ์ค์น
- 4๏ธโฃ ๊ฒ์ฆ
- ์ฐธ๊ณ
- Trivy
์ด์ ํ๊ฒฝ์์๋ CSI๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
apiVersion: storage.k8s.io/v1
kind: StorageClass
reclaimPolicy: Delete
metadata:
name: dev-harbor-local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
k apply -f dev-harbor-sc.yaml
persistentvolume/dev-harbor-pv1 created
sudo mkdir /tmp/dev-harbor-data{1..3}
sudo chown -R 1001:1001 /tmp/dev-harbor-data{1..3}
ls -ld /tmp/dev-harbor-data{1..3}
drwxr-xr-x 2 1001 1001 4096 Jun 7 03:08 /tmp/dev-harbor-data1
drwxr-xr-x 2 1001 1001 4096 Jun 7 03:08 /tmp/dev-harbor-data2
drwxr-xr-x 2 1001 1001 4096 Jun 7 03:08 /tmp/dev-harbor-data3
apiVersion: v1
kind: PersistentVolume
metadata:
name: dev-harbor-pv1
labels:
type: local
spec:
storageClassName: dev-harbor-local-storage
persistentVolumeReclaimPolicy: Delete
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/dev-harbor-data1"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- {key: kubernetes.io/hostname, operator: In, values: [slave1]}
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: dev-harbor-pv2
labels:
type: local
spec:
storageClassName: dev-harbor-local-storage
persistentVolumeReclaimPolicy: Delete
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/dev-harbor-data2"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- {key: kubernetes.io/hostname, operator: In, values: [slave1]}
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: dev-harbor-pv3
labels:
type: local
spec:
storageClassName: dev-harbor-local-storage
persistentVolumeReclaimPolicy: Delete
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/tmp/dev-harbor-data3"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- {key: kubernetes.io/hostname, operator: In, values: [slave1]}
k apply -f dev-harbor-pv.yaml
persistentvolume/dev-harbor-pv1 created
k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
dev-harbor-pv1 2Gi RWO Delete Bound my-dev/my-harbor-registry dev-harbor-local-storage 38m
dev-harbor-pv2 2Gi RWO Delete Bound my-dev/data-my-harbor-trivy-0 dev-harbor-local-storage 38m
dev-harbor-pv3 2Gi RWO Delete Bound my-dev/my-harbor-jobservice dev-harbor-local-storage 41m
dev-psql-pv1 1Gi RWO Delete Bound my-dev/data-my-psql-postgresql-0 dev-psql-local-storage 82m
dev-redis-pv1 2Gi RWO Delete Bound my-dev/redis-data-my-redis-master-0 dev-redis-local-storage 9m20s
helm repo add harbor https://helm.goharbor.io
helm repo update
helm pull harbor/harbor --untar
cp values.yaml dev-harbor-values.yaml
expose:
type: ingress
tls:
enabled: false
ingress:
hosts:
core: myharbor.local
controller: default
kubeVersionOverride: ""
className: "nginx"
externalURL: http://myharbor.local:32091
persistence:
enabled: true
persistentVolumeClaim:
registry:
storageClass: "dev-harbor-local-storage"
accessMode: ReadWriteOnce
size: 2Gi
jobservice:
jobLog:
storageClass: "dev-harbor-local-storage"
accessMode: ReadWriteOnce
size: 2Gi
harborAdminPassword: "Harbor12345"
portal:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
core:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
jobservice:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
registry:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
trivy:
enabled: false
database:
type: external
external:
host: "my-psql-postgresql-0.my-psql-postgresql-hl.my-dev.svc.cluster.local"
port: "5432"
username: "admin"
password: "psql"
coreDatabase: "psql"
redis:
type: external
external:
addr: "my-redis-master-0.my-redis-headless.my-dev.svc.cluster.local:6379"
username: ""
password: ""
helm install my-harbor . -n my-dev --create-namespace -f dev-harbor-values.yaml
NAME: my-harbor
LAST DEPLOYED: Sat Jun 7 12:56:05 2025
NAMESPACE: my-dev
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: harbor
CHART VERSION: 26.7.2
APP VERSION: 2.13.1
Did you know there are enterprise versions of the Bitnami catalog? For enhanced secure software supply chain features, unlimited pulls from Docker, LTS support, or application customization, see Bitnami Premium or Tanzu Application Catalog. See https://www.arrow.com/globalecs/na/vendors/bitnami for more information.
** Please be patient while the chart is being deployed **
1. Get the Harbor URL:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
Watch the status with: 'kubectl get svc --namespace my-dev -w my-harbor'
export SERVICE_IP=$(kubectl get svc --namespace my-dev my-harbor --template "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}")
echo "Harbor URL: http://$SERVICE_IP/"
2. Login with the following credentials to see your Harbor application
echo Username: "admin"
echo Password: $(kubectl get secret --namespace my-dev my-harbor-core-envvars -o jsonpath="{.data.HARBOR_ADMIN_PASSWORD}" | base64 -d)
WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
- exporter.resources
- nginx.resources
- registry.controller.resources
- registry.server.resources
+info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
k get po -n my-dev -l app.kubernetes.io/instance=my-harbor
NAME READY STATUS RESTARTS AGE
my-harbor-core-68959d694-5nsqx 1/1 Running 0 21m
my-harbor-jobservice-6d6894fcfb-v8hqv 1/1 Running 2 (21m ago) 21m
my-harbor-portal-74c8fb9b8-xdhwb 1/1 Running 0 21m
my-harbor-registry-6567555d84-67pxz 2/2 Running 0 21m
k get pvc -n my-dev -l app.kubernetes.io/instance=my-harbor
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-harbor-jobservice Bound dev-harbor-pv1 2Gi RWO dev-harbor-local-storage 25h
my-harbor-registry Bound dev-harbor-pv2 2Gi RWO dev-harbor-local-storage 25h
helm ls -n my-dev -l name=my-harbor
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-harbor my-dev 1 2025-06-08 16:36:57.6176201 +0900 KST deployed harbor-1.17.1 2.13.1
k get svc -n my-dev -l app.kubernetes.io/instance=my-harbor
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-harbor-core ClusterIP 10.102.252.254 <none> 80/TCP 23m
my-harbor-jobservice ClusterIP 10.98.218.182 <none> 80/TCP 23m
my-harbor-portal ClusterIP 10.109.209.234 <none> 80/TCP 23m
my-harbor-registry ClusterIP 10.106.229.13 <none> 5000/TCP,8080/TCP 23m
192.168.56.10 myharbor.local
ping myharbor.local
Ping myexample.local [192.168.56.10] 32๋ฐ์ดํธ ๋ฐ์ดํฐ ์ฌ์ฉ:
192.168.56.10์ ์๋ต: ๋ฐ์ดํธ=32 ์๊ฐ<1ms TTL=64
192.168.56.10์ ์๋ต: ๋ฐ์ดํธ=32 ์๊ฐ<1ms TTL=64
k get ing -n my-dev -l app.kubernetes.io/instance=my-harbor
NAME CLASS HOSTS ADDRESS PORTS AGE
my-harbor-ingress nginx myharbor.local 192.168.56.102 80 2m47s
k get ing my-harbor-ingress -n my-dev -o yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/proxy-body-size: "0"
ingress.kubernetes.io/ssl-redirect: "true"
meta.helm.sh/release-name: my-harbor
meta.helm.sh/release-namespace: my-dev
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
creationTimestamp: "2025-06-08T07:36:59Z"
generation: 1
labels:
app: harbor
app.kubernetes.io/instance: my-harbor
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: harbor
app.kubernetes.io/part-of: harbor
app.kubernetes.io/version: 2.13.1
chart: harbor
heritage: Helm
release: my-harbor
name: my-harbor-ingress
namespace: my-dev
resourceVersion: "732625"
uid: 0bd48ad5-19e6-46f5-a07b-5dc8faff525b
spec:
ingressClassName: nginx
rules:
- host: myharbor.local
http:
paths:
- backend:
service:
name: my-harbor-core
port:
number: 80
path: /api/
pathType: Prefix
- backend:
service:
name: my-harbor-core
port:
number: 80
path: /service/
pathType: Prefix
- backend:
service:
name: my-harbor-core
port:
number: 80
path: /v2/
pathType: Prefix
- backend:
service:
name: my-harbor-core
port:
number: 80
path: /c/
pathType: Prefix
- backend:
service:
name: my-harbor-portal
port:
number: 80
path: /
pathType: Prefix
status:
loadBalancer:
ingress:
- ip: 192.168.56.102
k get svc -n my-dev -l app.kubernetes.io/instance=my-harbor
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-harbor-core ClusterIP 10.109.125.130 <none> 80/TCP 3m12s
my-harbor-jobservice ClusterIP 10.111.215.2 <none> 80/TCP 3m12s
my-harbor-portal ClusterIP 10.111.178.74 <none> 80/TCP 3m12s
my-harbor-registry ClusterIP 10.110.53.193 <none> 5000/TCP,8080/TCP 3m12s
k get svc -n my-dev -l app.kubernetes.io/instance=my-nic
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nic-nginx-ingress-controller NodePort 10.107.24.79 <none> 80:32091/TCP,443:31971/TCP 81m
my-nic-nginx-ingress-controller-default-backend ClusterIP 10.97.1.245 <none> 80/TCP 81m
k exec -it my-harbor-portal-74c8fb9b8-xdhwb -n my-dev -- bash
nginx [ / ]$ cat /etc/resolv.conf
search my-dev.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
nginx [ / ]$ curl my-harbor-portal.my-dev.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Harbor</title>
<base href="/"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" type="image/x-icon" href="favicon.ico?v=2"/>
<link rel="stylesheet" href="styles.ac415221c96d2bef.css"></head>
<body>
<harbor-app>
<div class="spinner spinner-lg app-loading app-loading-fixed">
Loading...
</div>
</harbor-app>
<script src="runtime.4eab865dc31b6057.js" type="module"></script><script src="polyfills.d87db3092ff69ed9.js" type="module"></script><script src="scripts.3846d86d42cdb753.js" defer></script><script src="main.a86461846fd53284.js" type="module"></script></body>
</html>
k exec -it my-harbor-portal-74c8fb9b8-jmsb9 -n my-dev -- bash
nginx [ / ]$ curl http://my-harbor-core/api/v2.0/health
{"components":[{"name":"core","status":"healthy"},{"name":"database","status":"healthy"},{"name":"jobservice","status":"healthy"},{"name":"portal","status":"healthy"},{"name":"redis","status":"healthy"},{"name":"registry","status":"healthy"},{"name":"registryctl","status":"healthy"}],"status":"healthy"}
curl my-harbor-portal.my-dev.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Harbor</title>
<base href="/"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" type="image/x-icon" href="favicon.ico?v=2"/>
<link rel="stylesheet" href="styles.ac415221c96d2bef.css"></head>
<body>
<harbor-app>
<div class="spinner spinner-lg app-loading app-loading-fixed">
Loading...
</div>
</harbor-app>
<script src="runtime.4eab865dc31b6057.js" type="module"></script><script src="polyfills.d87db3092ff69ed9.js" type="module"></script><script src="scripts.3846d86d42cdb753.js" defer></script><script src="main.a86461846fd53284.js" type="module"></script></body>
</html>
kubectl exec -it my-harbor-core-68959d694-5nsqx -n my-dev -- curl http://localhost:8080/api/v2.0/health
{"components":[{"name":"core","status":"healthy"},{"name":"database","status":"healthy"},{"name":"jobservice","status":"healthy"},{"name":"portal","status":"healthy"},{"name":"redis","status":"healthy"},{"name":"registry","status":"healthy"},{"name":"registryctl","status":"healthy"}],"status":"healthy"}
I have no name!@my-nic-nginx-ingress-controller-669797d9f5-kn5hm:/etc/nginx$ curl my-harbor-portal.my-dev.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Harbor</title>
<base href="/"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" type="image/x-icon" href="favicon.ico?v=2"/>
<link rel="stylesheet" href="styles.ac415221c96d2bef.css"></head>
<body>
<harbor-app>
<div class="spinner spinner-lg app-loading app-loading-fixed">
Loading...
</div>
</harbor-app>
<script src="runtime.4eab865dc31b6057.js" type="module"></script><script src="polyfills.d87db3092ff69ed9.js" type="module"></script><script src="scripts.3846d86d42cdb753.js" defer></script><script src="main.a86461846fd53284.js" type="module"></script></body>
</html>

# helm ์ญ์ ํ ์ฌ ์์ฑ
helm delete my-harbor -n my-dev
helm install my-harbor . -n my-dev -f dev-harbor-values.yaml --create-namespace
# persistentVolumeReclaimPolicy: Retain ์ผ ๊ฒฝ์ฐ - Released ์ํ์ pv Available๋ก ๋ณ๊ฒฝ
rm -rf /tmp/dev-harbor-data{1..3}/*
kubectl patch pv dev-harbor-pv{1..3} -p '{"spec":{"claimRef": null}}'
# persistentVolumeReclaimPolicy: Retain ์ผ ๊ฒฝ์ฐ - PV ์์ฑ
k apply -f dev-harbor-pv.yaml
# persistentVolumeReclaimPolicy: Retain ์ผ ๊ฒฝ์ฐ - pv ์ ์ฒด ์ญ์
k delete -f dev-harbor-pv.yaml
# PVC ์ญ์
k delete pvc -n my-dev data-my-harbor-postgresql-0
k delete pvc -n my-dev data-my-harbor-trivy-0
k delete pvc -n my-dev redis-data-my-harbor-redis-master-0
k delete pvc -n my-dev my-harbor-registry
k delete pvc -n my-dev my-harbor-jobservice
| ์ปดํฌ๋ํธ | ๋ชฉ์ | ๋ํดํธ mount ๊ฒฝ๋ก |
|---|---|---|
| database | Harbor DB(PostgreSQL) | /var/lib/postgresql/data |
| registry | ์ด๋ฏธ์ง ์ ์ฅ์ | /storage |
| jobservice | ์ด๋ฏธ์ง ์ฒ๋ฆฌ ๊ฒฐ๊ณผ ์ ์ฅ์ | /var/jobservice |
| redis | ์บ์/ํ ์ ์ฅ์ | /data |
| trivy | ์ทจ์ฝ์ DB ์ ์ฅ์ | /home/scanner/.cache |
| ํญ๋ชฉ | ์ค๋ช |
|---|---|
| Ingress | ํธ๋ํฝ์ Harbor Core์ ์ ๋ฌํจ |
| externalURL | Harbor ์์ฒด๊ฐ "๋ด ์ฃผ์๋ ์ด๊ฒ์ด๋ค"๋ผ๊ณ ์ธ์ํ๊ธฐ ์ํ ๊ฐ |
Harbor ๋ด๋ถ ์ปดํฌ๋ํธ์ ํด๋ผ์ด์ธํธ๊ฐ ํต์ ํ ๋ Harbor์ ๋๋ฉ์ธ์ ๋ช
ํํ ์ธ์ํ๊ธฐ ์ํ ๊ธฐ์ค ์ฃผ์๋ก ์ฌ์ฉ๋๋ค.
externalURL์ ๋จ์ ๋
ธ์ถ ์ฃผ์๊ฐ ์๋๋ผ, Harbor ์์ฒด ๊ธฐ๋ฅ ๋์์ ํต์ฌ์ ์ธ ๋ฉํ๋ฐ์ดํฐ์ด๋ค.
| ๊ธฐ๋ฅ | externalURL ์ํฅ ์ฌ๋ถ | ์ค๋ช |
|---|---|---|
| Harbor UI ๋งํฌ | ์ํฅ ์์ | Harbor UI ๋ด๋ถ ๋งํฌ๋ค์ด ์ด URL ๊ธฐ์ค์ผ๋ก ์์ฑ๋จ |
| Docker CLI ๋ก๊ทธ์ธ | ์ํฅ ์์ | docker login harbor.example.com ์ ํ์ํ ์ฃผ์ |
| Replication | ์ํฅ ์์ | ๋ค๋ฅธ Harbor ๊ฐ ๋๊ธฐํ ์ ์๊ธฐ ์ฃผ์ ์๋ณ์ฉ |
| Notary | ์ํฅ ์์ | ์๋ช ๊ธฐ๋ฅ ํ์ฑํ ์ ์๊ธฐ ๋๋ฉ์ธ ํ์ |
| Webhook | ์ํฅ ์์ | ์ธ๋ถ ์์คํ ์ ์ด๋ฒคํธ ์ ์ก ์ ์ฃผ์ ๊ธฐ์ค |
| ์ํฅ ์์ | ๋ฉ์ผ ๋ด์ฉ ๋ด "๋ก๊ทธ์ธ ๋งํฌ" ๋ฑ ์ฃผ์ ๊ธฐ์ค | |
| Token ๊ธฐ๋ฐ ์ธ์ฆ | ์ํฅ ์์ | Harbor Core โ Registry โ Token ์ ๋ฌ ์ ์ฃผ์ ํ์ |