Harbor

쿠버네티스에서 파드를 생성할 때 필수적으로 필요로 하는 것이 바로 컨테이너 이미지 입니다. 컨테이너 이미지는 로컬에 저장할 수도 있고 도커 허브와 같은 원격 환경을 이용할 수도 있습니다. 원격 환경이 다른 사람들에게 이미지를 공유하거나 할 때 편리하지만, 문제는 도커 허브 이용에 제한이 생겼다는 것 입니다. 특정 시간 동안 특정 횟수의 다운로드 제한이 있습니다.

유료 모델을 사용하는 것도 하나의 방법이지만, 그보다 무료 이미지 저장소를 로컬에 설치 하는 것이 더 바람직할 듯 합니다. Harbor는 그 목적에 아주 적합 합니다.

Installation

쿠버네티스에서 HA하게 하버를 이용하기 위해, Helm을 통해 설치하는 것 권장하고 있습니다.

미리 준비 해두어야 할 조건은 다음과 같습니다.

  • Kubernetes cluster 1.10+ ⇒ 이번 테스트에서는 1.27 버전을 사용하고 있습니다.
  • Helm 2.8.0+ ⇒ Helm 3을 사용하고 있습니다.
  • High available ingress controller (Harbor does not manage the external endpoint) ⇒ 노드 포트를 사용할 수 있습니다. nginx 인그레스 컨트롤러 앞에 proxy를 붙혔을 때 왠지 모를 ssl 인증오류가 생겨서, 노드 포트를 열고 이를 proxy에 등록해줄 것 입니다.
  • High available PostgreSQL 9.6+ (Harbor does not handle the deployment of HA of database) ⇒ 파드로 만들어 놓아야 하는 걸까요?
  • High available Redis (Harbor does not handle the deployment of HA of Redis) ⇒ 위와 동일한 질문이 있습니다. 답을 먼저 말씀 드리자면, 꼭 미리 준비해 둘 필요는 없습니다.
  • PVC that can be shared across nodes or external object storage ⇒ 준비 된 PVC가 필요한데, 저희는 동적 프로비저닝 기능을 사용할 것입니다.
# 헬름 리포를 등록하고, 폴더를 내려 받습니다.
helm repo add harbor https://helm.goharbor.io
helm fetch harbor/harbor --untar

명령어 실행한 경로에 /harbor 폴더가 생성되어 있습니다.

# cd ./harbor && ls -al

rwxr-xr-x   4 root root    123  7월 31 18:03 .
dr-xr-x---. 64 root root   4096  7월 31 18:03 ..
-rw-r--r--   1 root root     57  7월 31 18:03 .helmignore
-rw-r--r--   1 root root    567  7월 31 18:03 Chart.yaml
-rw-r--r--   1 root root  11357  7월 31 18:03 LICENSE
-rw-r--r--   1 root root 192242  7월 31 18:03 README.md
drwxr-xr-x   2 root root     58  7월 31 18:03 conf
drwxr-xr-x  15 root root   4096  7월 31 18:03 templates
-rw-r--r--   1 root root  33874  7월 31 18:03 values.yaml

values.yaml 파일을 수정해야 합니다. Ingress rule, External URL, External PostgreSQL, External Redis, Storage 부분들에 변화가 필요하다고 합니다. 허나, 위에서 언급했듯 저는 PostgreSQL와 Redis도 내부 저장소 사용할 예정입니다. 결국 따로 필요로 하는 것은 Storage 설정입니다.

Dynamic provisioning : pvc with NFS

기본적인 PV / PVC 사용 순서는 미리 PV를 만들어 놓고, “저 PV를 쓸거야"라는 선언의 목적으로 PVC를 만들어 놓아야 합니다. 하지만 동적 프로비저닝을 사용하면 PVC 하나만 적용시켜도 자동으로 PV가 생성되게 됩니다. 참고로 설치에 사용한 볼륨은 13G 정도 입니다.

  1. NFS 서버 시작하기

    # nfs-server를 다운 받습니다.
    systemctl start nfs-server
    systemctl enable nfs-server
    
    # nfs 저장소로 사용할 폴더를 생성해 줍니다.
    # 경로는 자유입니다. 저는 nfs 폴더를 생성하였습니다.
    mkdir nfs
    
    # /etc/exports 파일에 경로를 등록합니다.
    # [path] = 파일 경로
    # [cidr] = ex) 192.168.15.0/24 
    echo '[path] [cidr](rw,sync,no_root_squash)' >> /etc/exports
    
    #설정을 적용하고 결과를 확인합니다.
    exportfs -r
    showmount -e
    
  2. 동적 프로비저닝 설정하기

    참고자료 → Kubernetes k8s Volume 동적 프로비저닝 with NFS 기본 스토리지 클래스

    ⇒ 쿠버네티스 노드 모두에 nfs 클라이언트를 받아놓아야합니다. 위의 블로그에 과정이 자세하게 나와 있습니다. 굳이 디폴트 스토리지 클래스 까지 등록해주실 필요는 없습니다.

    궁금한 점 중 하나는 깃허브 사이트에 보면 헬름으로도 이를 만들어줄 수 있는 듯 한데, 만약 여러 경로를 사용하려면, 여러 번 헬름 install을 수행하면 되는 것인가,, 라는 생각이 듭니다.

values.yaml

/harbor 경로에 있는 폴더에 들어가보면 values.yaml 이라는 파일이 존재합니다. 하버 뿐만이 아니라, 여러 헬름 프로젝트에서는 설치에 필요한 각종 정보들은 위의 파일로 관리 합니다.

파일을 열어보면 1000줄 가까이의 무지막지한 라인들이 저를 반겨줍니다. 이 설정들을 하나하나 뜯어봐야하는 공포에 사로잡히지만, 다행이 수정할 부분은 그리 많지 않습니다.

가장 먼저 expose.type을 nodePort로 바꿔봅시다. 인그레스를 활용한 케이스는 이 블로그를 살펴보시면 좋을 것 같습니다. 저도 처음에 nginx ingress 컨트롤러에 연결해 보았다가, 프록시와의 이유 모를 통신 이슈로 nodePort로 선회 했습니다.

인그레스 이외의 설정(“clusterIP”, “nodePort” or “loadBalancer”)을 사용한다면, tls 인증을 위해 expose.tls.auto.commonName을 입력해 줍시다. 저는 프록시에서 사용할 도메인을 입력해 주었습니다. tls 인증이 harbor CA 라는 곳에서 이뤄지던데, 정확이 어떤 곳인지는 잘 모르겠습니다.

또한 externalURL 쪽에도 https://[domain]을 입력해 주었습니다.

그 다음 볼륨 설정이 필요합니다. persistence.persistentVolumeClaim 쪽에서 registry, jobservice, database, redis, trivy 부분 아래 공통적으로 storageClass을 동적 프로비저닝 스토리지의 클래스로 지정해줘야 합니다.

설정이 끝났습니다.

Helm install

helm을 이용한 설치는 언제나 편리합니다. 다만, 명령어 실행 부분이 harbor 폴더를 바라보고 있어야 합니다.

# 네임스페이스 생성
kubectl create namespace harbor

# harbor 네임스페이스에 harbor 폴더의 설정 파일을 이용해 harbor를 설치
helm install -n harbor harbor harbor/

설정이 바뀌었을 때 적용 방법입니다.

helm upgrade -n harbor harbor harbor/

설치를 확인 해봅시다.

$ k get all,pv,pvc -n harbor 
NAME                                        READY   STATUS    RESTARTS       AGE
pod/harbor-core-7db5b74c86-nctk2            1/1     Running   0              148m
pod/harbor-database-0                       1/1     Running   0              15h
pod/harbor-jobservice-865b66687b-mg9dr      1/1     Running   3 (148m ago)   148m
pod/harbor-nginx-5cb465f58f-fmmrz           1/1     Running   0              148m
pod/harbor-notary-server-854944cd7d-kvsnf   1/1     Running   1 (143m ago)   148m
pod/harbor-notary-signer-76b6978d97-h7gfl   1/1     Running   0              148m
pod/harbor-portal-d7dbcc46c-94rxr           1/1     Running   0              15h
pod/harbor-redis-0                          1/1     Running   0              15h
pod/harbor-registry-d7c5dbf6f-7pzxc         2/2     Running   0              148m
pod/harbor-trivy-0                          1/1     Running   0              15h

NAME                           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                     AGE
service/harbor                 NodePort    10.96.115.165    <none>        80:30002/TCP,443:30003/TCP,4443:30004/TCP   148m
service/harbor-core            ClusterIP   10.96.117.194    <none>        80/TCP                                      15h
service/harbor-database        ClusterIP   10.104.0.73      <none>        5432/TCP                                    15h
service/harbor-jobservice      ClusterIP   10.102.37.222    <none>        80/TCP                                      15h
service/harbor-notary-server   ClusterIP   10.108.64.69     <none>        4443/TCP                                    15h
service/harbor-notary-signer   ClusterIP   10.111.161.104   <none>        7899/TCP                                    15h
service/harbor-portal          ClusterIP   10.101.185.44    <none>        80/TCP                                      15h
service/harbor-redis           ClusterIP   10.98.124.114    <none>        6379/TCP                                    15h
service/harbor-registry        ClusterIP   10.100.29.39     <none>        5000/TCP,8080/TCP                           15h
service/harbor-trivy           ClusterIP   10.107.210.23    <none>        8080/TCP                                    15h

NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/harbor-core            1/1     1            1           15h
deployment.apps/harbor-jobservice      1/1     1            1           15h
deployment.apps/harbor-nginx           1/1     1            1           148m
deployment.apps/harbor-notary-server   1/1     1            1           15h
deployment.apps/harbor-notary-signer   1/1     1            1           15h
deployment.apps/harbor-portal          1/1     1            1           15h
deployment.apps/harbor-registry        1/1     1            1           15h

NAME                                              DESIRED   CURRENT   READY   AGE
replicaset.apps/harbor-core-7db5b74c86            1         1         1       148m
replicaset.apps/harbor-jobservice-865b66687b      1         1         1       148m
replicaset.apps/harbor-nginx-5cb465f58f           1         1         1       148m
replicaset.apps/harbor-notary-server-854944cd7d   1         1         1       148m
replicaset.apps/harbor-notary-signer-76b6978d97   1         1         1       148m
replicaset.apps/harbor-portal-d7dbcc46c           1         1         1       15h
replicaset.apps/harbor-registry-d7c5dbf6f         1         1         1       148m

NAME                               READY   AGE
statefulset.apps/harbor-database   1/1     15h
statefulset.apps/harbor-redis      1/1     15h
statefulset.apps/harbor-trivy      1/1     15h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                    STORAGECLASS   REASON   AGE
persistentvolume/pvc-24fc670f-e4f2-435d-8205-55e8681cdaa2   5Gi        RWO            Delete           Bound    harbor/data-harbor-trivy-0               nfs-client              15h
persistentvolume/pvc-54c51073-e463-4337-bce7-4bf218e58e1d   1Gi        RWO            Delete           Bound    harbor/harbor-jobservice                 nfs-client              15h
persistentvolume/pvc-9b0f92ee-439f-4e5b-bb4c-46c51c6a6317   1Gi        RWO            Delete           Bound    harbor/data-harbor-redis-0               nfs-client              15h
persistentvolume/pvc-c3926a2b-eecb-4758-aabf-3d32aeb0dd52   1Gi        RWO            Delete           Bound    harbor/database-data-harbor-database-0   nfs-client              15h
persistentvolume/pvc-eee40456-6920-472a-827c-b9d58179e092   5Gi        RWO            Delete           Bound    harbor/harbor-registry                   nfs-client              15h

NAME                                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/data-harbor-redis-0               Bound    pvc-9b0f92ee-439f-4e5b-bb4c-46c51c6a6317   1Gi        RWO            nfs-client     15h
persistentvolumeclaim/data-harbor-trivy-0               Bound    pvc-24fc670f-e4f2-435d-8205-55e8681cdaa2   5Gi        RWO            nfs-client     15h
persistentvolumeclaim/database-data-harbor-database-0   Bound    pvc-c3926a2b-eecb-4758-aabf-3d32aeb0dd52   1Gi        RWO            nfs-client     15h
persistentvolumeclaim/harbor-jobservice                 Bound    pvc-54c51073-e463-4337-bce7-4bf218e58e1d   1Gi        RWO            nfs-client     15h
persistentvolumeclaim/harbor-registry                   Bound    pvc-eee40456-6920-472a-827c-b9d58179e092   5Gi        RWO            nfs-client     15h

노드 포트를 사용했기 때문에 [쿠버네티스 노드 ip]:30003 경로를 https를 이용해 들어가보면 다음과 같이 대시보드 화면이 나타나는 것을 확인하실 수 있게 됩니다. 다만, 인증서가 신뢰할 수 없다고는 뜹니다.

저는 Envoy proxy 서버를 두었기 때문에 제가 가진 도메인과 잘 엮어서 외부에서도 접속 할 수 있게 설정을 마쳤습니다. 하버의 초기 로그인 정보는 ID : admin, PW : Harbor12345 입니다.

How to use?

하버를 설치 해보았으니, 사용법도 익혀봅시다. 공식 문서에서는 Working with project 부분 입니다. 눈치 빠르신 분들은 Harbor administration 파트를 넘어 갔다는 걸 아셨을 텐데, 왠지 양이 많이 뛰어 넘고 싶은 느낌 입니다. 그래도 언젠가 한 번 심도 있게 어떤 기능이 있는지 살펴보긴 해야겠죠? 언젠간 말이에요

로그인을 하고 나면 다음과 같은 화면이 나타납니다.

Project

프로젝트가 생성되지 않으면 이미지를 하버에 저장할 수 없습니다. 프로젝트는 유저의 역할에 따라 사용할 수 있는 기능이 다르다고 하네요. (Role-Based Access Control : RBAC) 또 크게 Public과 Private로 구분된다고 합니다. 디폴트로 library 프로젝트가 만들어져 있습니다.

저는 지금 관리자의 권한을 가지고 있습니다. 프로젝트를 만들어봅시다. 프로젝트 네임과 quota limit이 필수 기입 조건인데, -1이 의미하는 것은 용량 제한을 두지 않겠다 라는 말입니다.

위의 상태로 생성을 해보면, 프로젝트가 추가된 게 확인 됩니다. Private로 만들었습니다.

안에 들어와 보니 summary, repositories, helm charts, members, labels, scanner, p2p preheat, policy, robot accounts, logs 그리고 configuration 탭이 존재 합니다. 기능이 많습니다.

Configuration 쪽을 클릭해보면, Public 전환 버튼과 취약점이 있을지도 모르는 이미지(Vulnerability image)에 관련된 버튼들이 존재 합니다.

이제 프로젝트에 유저를 추가해봅시다. 유저를 먼저 만들어 봐야겠죠? Administration에 NEW USER를 클릭합니다. 저는 openstack 이라는 이름으로 하나 만들어 보았습니다. 생성 후 project로 돌아와 Members에서 openstack을 추가 해줍시다. 선택 버튼이 아니라 입력인 것이 조금 이외입니다.

추후 Role을 바꾼다던가 유저를 프로젝트에서 없애려면 Action 버튼을 활용하면 될 것 같습니다.

openstack 유저로 로그인 해서 확인해보면, kaps 프로젝트가 리스트에 보이게 됩니다.

LDAP(?) server을 하버에 연결 시켜 두었다면, 유저에 Group을 형성할 수 있는 듯 한데, 제 하버에는 그런 설정이 되어 있지 않습니다.

이외에도 프로젝트에서는, Robot 계정을 만들수도 있고, 웹훅 알람을 보낼 수도 있습니다. 자세한 내용은 문서를 참고해봅시다.

Image

하버를 HTTP로 사용하거나, 인증서가 unknown CA certificate 일 때 도커 클라이언트에서 이미지를 보낼 수 없다고 합니다. 현재 제 서버가 어느 정도까지 허용 될지 모르겠으니, 우선 image push를 시도해봅시다.

$ sudo docker image list
REPOSITORY         TAG            IMAGE ID       CREATED        SIZE
envoyproxy/envoy   v1.27-latest   511f8ff2a1f9   5 days ago     147MB
hello-world        latest         9c7a54a9a43c   2 months ago   13.3kB

엔보이 프록시 이미지를 하버에 넣어봅시다. 저는 문제 없이 로그인 했습니다.

# docker login <harbor_address>
Username: admin
Password:

만약 문제가 생긴다면 이 방법을 시도 해봅시다.

다운 받아두었던 이미지를 tag 명령으로 다음과 같이 바꿔줍시다.

# docker tag envoyproxy/envoy:v1.27-latest <harbor_address>/kaps/envoyproxy/envoy:v1.27-latest
# docker image list
REPOSITORY                                TAG            IMAGE ID       CREATED        SIZE
envoyproxy/envoy                          v1.27-latest   511f8ff2a1f9   5 days ago     147MB
<harbor_address>/kaps/envoyproxy/envoy   v1.27-latest   511f8ff2a1f9   5 days ago     147MB
hello-world                               latest         9c7a54a9a43c   2 months ago   13.3kB

그리고 바꾼 이미지를 push 합니다.

docker push <harbor_address>/kaps/envoyproxy/envoy:v1.27-latest
The push refers to repository [<harbor_address>/kaps/envoyproxy/envoy]
5f70bf18a086: Pushed 
34049bcf227d: Pushed 
7f9bdb71d8f3: Pushed 
d44ad2051d84: Pushed 
d4dab6f2fcc7: Pushed 
dde1e8ed09f5: Pushed 
c04c5616301c: Pushed 
928c1abc363c: Pushed 
f5bb4f853c84: Pushed 
v1.27-latest: digest: sha256:fdc35b806570985ff51db9736017b76508873581223e5d8df92b3f8ad9c9c489 size: 2195

결과는 성공입니다!!!

가장 근본적이고, 중요한 기능을 동작 해보았습니다.

문서를 보면, 이미지를 다루는 다양한 기능들이 존재하니 언젠가 꼭 살펴 봐야겠습니다.

API V2.0

대시보드 좌측 하단에 API 문서 링크가 있습니다. 이 역시 언젠가 마음을 다 잡고 한 번 살펴보는 것이 좋아 보입니다.