Harbor Container Registry - HA Architecture Setup

Key Advantages of Harbor

  • Security: Strong authentication, RBAC, and security scanning features

  • Integration: Easy integration with Kubernetes and Docker

  • Performance: Fast image distribution and management

  • Scalability: High availability and replication support

  • Management: User-friendly web interface and project-based organization

By setting up our system on virtual machines instead of Kubernetes, we minimized the risk of downtime and simplified management.

Why Do We Use Separate Servers?

  • Uninterrupted Service: Operates independently from Kubernetes cluster maintenance

  • Resource Management: Dedicated resource allocation and optimization

  • Simple Management: Less complex infrastructure

  • Security: Isolated security layers

This setup ensures Harbor operates more stably and reliably while also making management easier.

Setup

Requirements:

  1. Load Balancer - 2 Servers (HAProxy and Keepalived)

    • 192.168.10.101 LoadBalancer-1

    • 192.168.10.102 LoadBalancer-2

    • 192.168.10.100 (Keepalived IP)

  2. Main - 2 Harbor installations (same configurations)

    • 192.168.10.111 Main-1

    • 192.168.10.112 Main-2

  3. NFS Cluster (Existing or new NFS, GlusterFS, CephFS Cluster)

    • 192.168.10.120
  4. PostgreSQL Cluster (An existing setup can be used)

    • 192.168.10.121
  5. Redis Cluster (An existing setup can be used)

    • 192.168.10.122

An example server architecture configuration is shown above.

Load Balancer

As seen in the image, there are 2 Load Balancers in the system, and they operate in an active-passive structure.

Load Balancer-1

The HAProxy configuration is as follows. The only difference between Load Balancer-1 and Load Balancer-2 is the load balancer IP in the monitor-stats section.

  • Directs the Harbor main servers in an active-passive manner.

  • SSL control is done through /etc/haproxy/cert.pem. You can use it by assigning a domain to the IP 192.168.10.100.

cat /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    maxconn 10000
    tune.ssl.default-dh-param 2048

listen monitor-stats
    mode http
    bind 192.168.10.101:7000
    stats enable
    stats uri /

defaults
    log global
    option httplog
    option dontlognull
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms
    timeout http-request 10s
    timeout http-keep-alive 10s
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

frontend harbor_frontend
    bind *:443 ssl crt /etc/haproxy/cert.pem
    mode http
    default_backend harbor_backend

backend harbor_backend
    mode http
    #option httpchk GET /
    server harbor1 192.168.10.111:443 ssl verify none check
    server harbor2 192.168.10.112:443 ssl verify none check backup

Keepalived

  • High availability between two load balancers:

    • Primary server: 192.168.10.101

    • Backup server: 192.168.10.102

  • The HAProxy service is checked every 2 seconds and automatically switches over in case of an issue

  • Provides uninterrupted access to the Harbor registry with virtual IP 192.168.10.100

cat /etc/keepalived/keepalived.conf
# Global Settings for notifications
global_defs {

}

# Define the script used to check if haproxy is still working
vrrp_script chk_haproxy {
    script "/usr/bin/killall -0 haproxy"
    interval 2
    weight 2
}

# Configuration for Virtual Interface
vrrp_instance LB_VIP {
    interface ens192
    state MASTER        # set to BACKUP on the peer machine
    priority 101        # set to  99 on the peer machine
    virtual_router_id 20

    smtp_alert          # Enable Notifications Via Email

    authentication {
        auth_type PASS
        auth_pass MYP@ssword    # Password for accessing vrrpd. Same on all devices
    }
    unicast_src_ip 192.168.10.101 # Private IP address of master
    unicast_peer {
        192.168.10.102
   }

    # The virtual ip address shared between the two loadbalancers
    virtual_ipaddress {
        192.168.10.100
    }

    # Use the Defined Script to Check whether to initiate a fail over
    track_script {
        chk_haproxy
    }
}

Loadbalancer-2

Loadbalancer-2 operates as a backup server. The HAProxy configuration is the same as Loadbalancer-1, with the only difference being the monitor-stats IP is 192.168.10.102.

In the Keepalived configuration, there are these important differences:

  • The state is set to BACKUP

  • The priority is lowered to 100 (lower than the main server)

  • The unicast_src_ip is its own IP, 192.168.10.102

global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    maxconn 10000
    tune.ssl.default-dh-param 2048

listen monitor-stats
    mode http
    bind 192.168.10.102:7000
    stats enable
    stats uri /

defaults
    log global
    option httplog
    option dontlognull
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms
    timeout http-request 10s
    timeout http-keep-alive 10s
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

frontend harbor_frontend
    bind *:443 ssl crt /etc/haproxy/cert.pem
    mode http
    default_backend harbor_backend

backend harbor_backend
    mode http
    #option httpchk GET /
    server harbor1 192.168.10.111:443 ssl verify none check
    server harbor2 192.168.10.112:443 ssl verify none check backup

Keepalived configuration:

# Global Settings for notifications
global_defs {

}

# Define the script used to check if haproxy is still working
vrrp_script chk_haproxy {
    script "/usr/bin/killall -0 haproxy"
    interval 2
    weight 2
}

# Configuration for Virtual Interface
vrrp_instance LB_VIP {
    interface ens192
    state BACKUP        # set to BACKUP on the peer machine
    priority 100        # set to  99 on the peer machine
    virtual_router_id 20

    smtp_alert          # Enable Notifications Via Email

    authentication {
        auth_type PASS
        auth_pass MYP@ssword    # Password for accessing vrrpd. Same on all devices
    }
    unicast_src_ip 192.168.10.102 # Private IP address of master
    unicast_peer {
        192.168.10.101
   }

    # The virtual ip address shared between the two loadbalancers
    virtual_ipaddress {
        192.168.10.100
    }

    # Use the Defined Script to Check whether to initiate a fail over
    track_script {
        chk_haproxy
    }
}

Harbor Main Servers

For Harbor installation, you first need to install the requirements specified in the https://goharbor.io/docs/1.10/install-config/installation-prereqs/ document and perform system updates.

NFS Connection

Let's mount the /data directory to the NFS cluster on the Main-1 and Main-2 servers:

vi /etc/fstab
192.168.10.120:/mnt/harbor-data /data nfs4 rsize=1048576,wsize=1048576,noatime,nodiratime 0 0
mount -a

With this configuration, data will be stored on the NFS server. If there is an issue with the Main-1 server, the Main-2 (backup) server will automatically take over. Since the data is kept on the NFS cluster, it will be stored redundantly.

Harbor Installation (Same for Main-1 and Main-2)

wget <https://github.com/goharbor/harbor/releases/download/v2.11.1/harbor-online-installer-v2.11.1.tgz>
tar xfv harbor-online-installer-v2.11.1.tgz
cd harbor
mv harbor.yml.tmpl harbor.yml
./prepare
mv /serdarcanb.cert /data/cert/registry.serdarcanb.com.cert
mv /serdarcanb.key /data/cert/registry.serdarcanb.com.key
vi harbor.yml

./install.sh --with-trivy

Harbor Configuration

The content of the harbor.yml file for both servers:

http: 
  port: 80

hostname: registry.serdarcanb.com
https:
  port: 443
  certificate: /data/cert/registry.serdarcanb.com.cert
  private_key: /data/cert/registry.serdarcanb.com.key

harbor_admin_password: Serdarcanb!23
data_volume: /data

trivy:
  ignore_unfixed: true
  skip_update: false
  skip_java_db_update: false
  offline_scan: false
  security_check: vuln
  insecure: false
  timeout: 5m0s

jobservice:  
  max_job_workers: 10
  job_loggers:
    - STD_OUTPUT
    - FILE
  logger_sweeper_duration: 1 

notification:
  webhook_job_max_retry: 3
  webhook_job_http_client_timeout: 3 

log:
  level: info
  local:
    rotate_count: 50
    rotate_size: 200M
    location: /var/log/harbor
_version: 2.11.0

proxy:
  http_proxy:
  https_proxy:
  no_proxy:
  components:
    - core
    - jobservice
    - trivy

metric:
  enabled: true
  port: 9090
  path: /metrics

upload_purging:
  enabled: true
  age: 168h
  interval: 24h
  dryrun: false

cache:
  enabled: true
  expire_hours: 2

external_database:
  harbor:
    host: 192.168.10.121
    port: 5432
    db_name: harbor
    username: devops
    password: 1Serdarcan123
    ssl_mode: disable
    max_idle_conns: 10
    max_open_conns: 100
external_redis:
  host: 192.168.10.122
  port: 6379
  password: 1Serdarcan123
  registry_db_index: 1
  jobservice_db_index: 2
  chartmuseum_db_index: 3

This Harbor configuration file (harbor.yml) includes the following basic settings:

  • HTTP and HTTPS port settings (80 and 443)

  • SSL certificate configuration

  • Trivy security scanner settings

  • Job service configuration

  • Notification and logging settings

  • Metrics and cache configuration

  • External database connection (PostgreSQL - 192.168.10.121)

  • External Redis connection (192.168.10.122)

With this configuration, the Harbor container registry is set to use external database and Redis services for high availability.

For PostgreSQL and Redis redundancy, it can be set up in a cluster structure. For more detailed configuration options, you can check the https://goharbor.io/docs/2.12.0/install-config/configure-yml-file/ page.

1
Subscribe to my newsletter

Read articles from Serdarcan Büyükdereli directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Serdarcan Büyükdereli
Serdarcan Büyükdereli

Senior DevOps Engineer | Building scalable, reliable infrastructures | Automation, Cloud, CI/CD | Performance & security-focused 🚀