본문 바로가기

DevOps

[Harbor] EKS 환경에서 Harbor 고가용성(HA)으로 구성하기

반응형

1. Harbor 구성요소

Harbor는 OIC와 호환되는 Cloud-native 아티팩트 레지스트리이다.

이 말은 즉 Harbor는 OCI 이미지 정의를 준수하는 이미지, Helm 차트, CNAB, OPA 등을 관리할 수 있다는 것을 의미하며 이러한 종류의 Artifact를 Pulling, Pushing, Deleting, Tagging, Replicating, Scanning 등을 모두 지원한다.

 

 

[1] Redis : cache 데이터를 저장하며 jobservice를 위한 job metadata 또한 관리한다.

[2] Database : Postgres를 사용하며 Harbor project, user, role, replication policy, tag retention policy, image, chart와 관련된 메타데이터를 관리하는 목적으로 PostgreSQL을 사용한다. PostgreSQL은 내장형 PostgreSQL을 사용할 수도 있으며 외장형 PostgreSQL 또한 사용할 수 있다.

[3] Nginx : Harbor 컴포넌트 앞에 구성되어 Reverse-Proxy 역할을 수행한다.

[4] Core : Harbor의 핵심 서비스로써 API 서버, 설정, 프로젝트, Quota(할당량), Retention(저장기간), Replication(복제), Notification(알람), OCI Artifact, Registry Driver 등에 대한 역할을 수행한다.

[5] Registry : 이미지 및 헬름 차트 등을 저장하는 Registry이며 기본적으로는 filesystem에 저장하지만 s3, gcs, minio 등도 지원한다.

[6] Jobservice : 주기적인 작업(가비지 컬렉션, 이미지 복제, 이미지 삭제 등) 등을 수행한다.

[7] Portal : Harbor의 Web UI를 담당한다.

 

 


 

2. Harbor HA(고가용성) 구성 방법

EKS 환경을 기준으로 설명한다.

Harbor에서 Persistence(영속성)이 필요한 구성 요소들은 다음과 같다.

[1] registry : Container Image, Helm Chart 등을 저장한다.

[2] database : Postgres를 사용하여 여러가지 메타데이터를 관리한다.

[3] redis : Harbor 내에서 사용하는 캐싱 데이터를 관리한다.

[4] jobservice : 작업과 관련된 메타데이터들을 관리한다.

 

2.1. HA를 위한 Persistence 구성 요소

그럼 여기서 HA를 구성하기 위해 어떤 식으로 구성하면 좋을지 생각해보자.

Database(postgres)는 RDS를 사용하면 별도의 걱정없이 메타데이터들을 관리할 수 있을 것이라고 생각한다. 즉 Harbor에서는 external DB에 RDS 설정을 적용하면 될 것이다.

Redis는 독립 혹은 센티널 설정만 지원하는데 필자는 Harbor에서의 internal(Harbor 내장 Redis)로 구성한다.

물론 이는 Redis가 죽으면 일시적으로 harbor-core 서비스에 장애가 발생하지만 큰 이슈가 없다면 redis 파드가 죽을 일은 크게 없으니 괜찮다고 생각한다.

Redis는 rdb 파일을 저장해야 하기 때문에 EBS를 볼륨으로 사용하며 1대로 구성한다.

Registry는 기존 filesystem을 사용하는게 아니라 s3를 사용한다.

다만 s3 인증 방식이 irsa는 아직 지원하지 않는 것으로 보이니 harbor bucket에 대한 권한이 부여된 IAM User를 생성하여 s3에 대한 access_key, secret_key를 발급받아둔다.

주의해야 할 점으로 Git에는 Credential이 노출되면 안되기 때문에 필자는 Helm secret을 활용하여 Access key를 암호화할 예정이다.

jobservice는 2대 이상 구성하기 위해서는 어쩔수없이 EFS를 활용한다. EFS는 비용이 비싸 가급적으로 사용하지 않으려 하지만 jobservice는 큰 용량을 필요로 하는게 아니기 때문에 그냥 사용하고자 함

 

2.2. HA Harbor를 위한 아키텍처

간단히 그려서 표현하자면 아래와 같다.

 

 

Redis도 ElastiCache를 쓰려면 단일 ElastiCache로 구성하여 사용하면 되긴 하지만 필자는 그냥 internal redis로 구성했다.

 

 

3. 사전 준비

  • RDS Postgres 생성
  • S3 Bucket 생성
    • S3 Bucket에 대한 권한이 부여된 IAM User 생성
      • registry에서 사용
  • EFS 생성
    • jobservice에서 사용

 

[1] Postgres 데이터베이스 및 계정 생성

RDS Postgres에는 Harbor에서 사용할 데이터베이스 및 계정을 준비해둔다.

# registry 데이터베이스 생성
create database registry;

# 계정 생성
create user harbor with ENCRYPTED PASSWORD 'xxxxxxxx';

# 포괄적으로 권한 부여
GRANT ALL PRIVILEGES ON DATABASE registry TO harbor;
GRANT ALL ON SCHEMA public TO harbor;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO harbor;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO harbor;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO harbor;

 

 

[2] Network 연동 방식 고민

그리고 외부에서 Harbor에 접근해야 하기 때문에 Network는 어떤식으로 서빙할지 정해야 한다.

 

Harbor는 기본적으로 Nginx를 reverse-proxy로 사용한다.

 

필자는 expose type을 clusterIP로 하는것을 선호한다.

 

필자가 테스트한 방법은 다음과 같으며 물론 ingress 등의 방식을 선호한다면 그렇게 해도 무방하다.

  • ALB → Istio Ingressgateway → Nginx(reverse-proxy) → Harbor components
    • relativeurls를 true로 해줘야 docker push가 가능해진다.
    • 만약 Istio virtualService에서 바로 Harbor components로 프록싱을 해준다면 nginx는 굳이 필요없게 될 수도 있다.
  • ALB → Nginx(reverse-proxy) → Harbor components
    • AWS Load Balancer Controller의 TargetGroupBinding을 통해 ALB TargetGroup과 Nginx Service를 바인딩하여 통신

 

reverse proxy 때문에 unknown blob 에러가 발생한다면 아래의 issue를 확인해보자.

https://github.com/goharbor/harbor-helm/issues/174

 

"unknown blob" error while pushing images to harbor · Issue #174 · goharbor/harbor-helm

I have successfully deployed harbor-helm on my openshift cluster. I am able to login to harbor. However, when i tried to push image to harbor repository. The "unknown blob" appears after completing...

github.com

 

 

 

[3] EFS StorageClass 생성

EFS CSI Driver Controller를 활용하여 EFS를 쓸 수 있도록 Storage Class를 구성해둔다.

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: harbor-efs-storage
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  fileSystemId: fs-xxxxxxxxx
  directoryPerms: "700"

 

 

4. Helm으로 Harbor HA 구성

모든 준비가 다 되었다면 Helm을 통해 Harbor를 구성하면 된다.

 

Helm values를 기준으로 각각의 값들을 설명한다.

 

[1] expose

expose는 clusterIP로 구성한다.

또한 externalURL은 Route53에서 레코드로 등록할 도메인을 설정해준다.

expose:
  # Set how to expose the service. Set the type as "ingress", "clusterIP", "nodePort" or "loadBalancer"
  # and fill the information in the corresponding section
  **type: clusterIP**
  tls:
    # Enable TLS or not.
    # Delete the "ssl-redirect" annotations in "expose.ingress.annotations" when TLS is disabled and "expose.type" is "ingress"
    # Note: if the "expose.type" is "ingress" and TLS is disabled,
    # the port must be included in the command when pulling/pushing images.
    # Refer to <https://github.com/goharbor/harbor/issues/5291> for details.
    enabled: false
...
...

  clusterIP:
    # The name of ClusterIP service
    name: harbor
    # The ip address of the ClusterIP service (leave empty for acquiring dynamic ip)
    staticClusterIP: ""
    # Annotations on the ClusterIP service
    annotations: {}
    ports:
      # The service port Harbor listens on when serving HTTP
      httpPort: 80
      # The service port Harbor listens on when serving HTTPS
      httpsPort: 443

...
...

externalURL: <https://harbor-ha.xxxxx.xxxxx>

 

 

[2] internalTLS

ALB에서 TLS Termination을 할 예정이기에 internalTLS는 비활성화 해주었다.

internalTLS:
  # If internal TLS enabled
  enabled: false

 

 

[3] Persistence

jobservice는 efs sc를 활용하고 redis는 1ea로 구성하기 때문에 ebs sc를 활용한다.

 

imageChartStorage는 type: s3를 활용하고 bucketName을 지정해준다.

 

추가로 IRSA를 지원하지 않는 것으로 보이기에 access key와 secret key를 명시해줘야 한다.

 

이는 필자의 경우 helm secret을 활용했다.

persistence:
  enabled: true

...
...

## jobservice : efs 사용
    jobservice:
      jobLog:
        existingClaim: ""
        storageClass: "harbor-efs-storage"
        subPath: ""
        accessMode: ReadWriteMany
        size: 30Gi
        annotations: {}
        
## Redis
    redis:
      existingClaim: ""
      storageClass: "gp3-sc"
      subPath: ""
#      accessMode: ReadWriteMany
      size: 30Gi
      annotations: {}

...
...

  imageChartStorage:
...
...

    # Specify the type of storage: "filesystem", "azure", "gcs", "s3", "swift",
    # "oss" and fill the information needed in the corresponding section. The type
    # must be "filesystem" if you want to use persistent volumes for registry
    type: s3

...
...

    s3:
      # Set an existing secret for S3 accesskey and secretkey
      # keys in the secret should be REGISTRY_STORAGE_S3_ACCESSKEY and REGISTRY_STORAGE_S3_SECRETKEY for registry
      #existingSecret: ""
      region: ap-northeast-2
      bucket: my-harbor-bucket
#      accesskey: 
#      secretkey: 

 

 

[4] update전략

만약 1ea로 구성했다면 jobservice와 registry pod를 Recreate로 해야하지만 2ea 이상 구성할 예정이기에 그대로 둔다.

 

ebs는 multiAttach를 기본적으로 지원하지 않기 때문이다.

 

(물론 1ea라 할지라도 imageChartStorage가 s3와 같은 objectStorage일 경우 registry는 RollingUpdate여도 상관없다.)

# The update strategy for deployments with persistent volumes(jobservice, registry): "RollingUpdate" or "Recreate"
# Set it as "Recreate" when "RWM" for volumes isn't supported
updateStrategy:
  type: RollingUpdate

 

 

[5] 컴포넌트들 공통 설정

필자가 기본적으로 모든 컴포넌트들에 추가한 설정은 다음과 같다.

 

podAntiAffinity : 같은 노드에 뜨지 않도록 하기 위한 설정이다.

 

nodeSelector : 특정 노드에 뜰 수 있도록 설정한다.

 

resource 설정

 

replicas : 2 (redis 제외)

nginx:
  image:
    repository: goharbor/nginx-photon
    tag: v2.10.2
  # set the service account to be used, default if left empty
  serviceAccountName: ""
  # mount the service account token
  automountServiceAccountToken: false
  replicas: 2
  revisionHistoryLimit: 10
  resources:
    requests:
      memory: 100Mi
      cpu: 20m
    limits:
      memory: 200Mi
      cpu: 100m
  extraEnvVars: []
  nodeSelector:
    type: harbor
    ...
    ...
    
  tolerations: []
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: component
            operator: In
            values:
            - nginx
        topologyKey: "kubernetes.io/hostname"

 

 

[6] registry.relativeurls: true 변경

relative URLs을 Header에 붙여서 응답해주도록 하는 설정이며 이를 하지 않을 경우 unknown blob 이슈를 잠재적으로 겪을 수 있기에 true로 해주는게 좋을 것으로 보인다.

 

 

[7] trivy 비활성화

image scanning은 필요없어서 비활성화 했다.

 

 

[8] database external 설정

database password 또한 helm secret으로 관리하기에 빈칸으로 넣어두었다.

추가로 sslmode가 기본적으로 disable인데 만약 connection error가 발생할경우 required로 변경하면 된다.

database:
  # if external database is used, set "type" to "external"
  # and fill the connection information in "external" section
  type: external

...
...

  #PostgreSQL 연결
  external:
    host: "xxxx.xxxx.xxxxxx.ap-northeast-2.rds.amazonaws.com"
    port: "5432"
    username: "harbor"
    # password : helm secret으로 관리
    password:
    coreDatabase: "registry"
    # if using existing secret, the key must be "password"
    existingSecret: ""
    # "disable" - No SSL
    # "require" - Always SSL (skip verification)
    # "verify-ca" - Always SSL (verify that the certificate presented by the
    # server was signed by a trusted CA)
    # "verify-full" - Always SSL (verify that the certification presented by the
    # server was signed by a trusted CA and the server host name matches the one
    # in the certificate)
    sslmode: "require"

 

 

[9] Redis

redis는 internal로 사용하여 replica는 1ea로 구성한다.

redis:
  # if external Redis is used, set "type" to "external"
  # and fill the connection information in "external" section
  type: internal

 

 

[10] cache 활성화

# cache layer configurations
# if this feature enabled, harbor will cache the resource
# `project/project_metadata/repository/artifact/manifest` in the redis
# which help to improve the performance of high concurrent pulling manifest.
cache:
  # default is not enabled.
  enabled: true
  # default keep cache for one day.
  expireHours: 24

위와 같은 설정으로 Harbor를 HA 모드로 구성할 수 있다.

 

 

jobservice는 core가 뜨기전에 붙으려하면 crashLoopBack이 뜨는데 Restart는 크게 상관쓰지 않아도 된다.

 

 

Harbor에 붙기 위한 Domain 설정 등에 대한 설명은 생략한다.

 

 

 

반응형