Post 4: Harbor Container Registry

Harbor Log

What is Harbor?

Harbor is a container registry, i.e. a system which manages container images which can then be used by Kubernetes. It is a self-hosted alternative to a cloud based service like Docker Hub.

Why do we want to use Harbor (vs a cloud registry like Docker Hub)?

A few concrete reasons stick out:

  • If your cluster restarts (say due to a power outage or hardware failure) then on reboot Kubernetes may try to pull many images at once from the internet, which could clog up your internet connection. Your download speed from a local hard disk is probably faster then your internet connection, especially at home.
  • Cloud registries are known to be vectors for malware. Docker Hub integrates with scanning tools to allow you to first pull images, scan them for malware and vulnerabilities, and then choose whether or not to deploy.
  • As with any cloud based service, it is subject to change. While Docker Hub is free, it is rate limited, and it could be not free tomorrow.

Installation

🔐
Note: I'm going to be deploying Harbor with trusted certs from Let's Encrypt. This requires that you own a domain name, and that the DNS provider for that domain is one that is supported by cert-manager's DNS01 challenge. This doesn't mean you have to have bought your domain from one of these providers (I bought mine from Namecheap and registered the DNS for free with Cloudflare). If you don't have a domain there is a way you can get Tanzu (and any other k8s distribution I assume) to trust self-signed certificates.

Before we can even think about installing Harbor there's a small sized list of dependencies we need to go through (an American small size, that is).

Command line tools

Install imgpkg:

wget https://carvel.dev/install.sh -O install.sh

# Inspect install.sh before running...
sudo bash install.sh
imgpkg version

Install yq:

wget https://github.com/mikefarah/yq/releases/download/${VERSION}/yq_linux_amd64 -O yq && chmod +x yq && sudo mv yq /usr/bin/yq
Replace ${VERSION} with the latest version of yq, e.g. v4.27.2

Kubernetes Packages

Install cert-manager:

# Get package versions
tanzu package available list cert-manager.community.tanzu.vmware.com
# Install
tanzu package install cert-manager \
   --package-name cert-manager.community.tanzu.vmware.com \
   --version ${CERT_MANAGER_PACKAGE_VERSION}
Replace ${CERT_MANAGER_PACKAGE_VERSION} with the latest version of cert-manager from the first command.

Install Contour (note: to install Contour your cluster must have a service LoadBalancer. See post 2 for more info:

# Get package versions
tanzu package available list contour.community.tanzu.vmware.com
# Install
tanzu package install contour \
   --package-name contour.community.tanzu.vmware.com \
   --version ${CONTOUR_PACKAGE_VERSION}
Replace ${CONTOUR_PACKAGE_VERSION} with latest version of Contour from the first command.

Harbor

Creating Certificates for Harbor

In order to issue certificates cert-manager is going to use a DNS challenge. This challenge creates temporary DNS records to prove your ownership of your domain to Let's Encrypt. Since this challenge runs each time the certificate renews (every 60 days), cert-manager requires an automatic way to create DNS records for our domain, e.g. by providing cert-manager with an API key or other credentials by which it can access the DNS provider. This will vary a bit depending on the DNS provider for your domain. I'll be covering Cloudflare, see the cert-manager docs for other providers.

On the Cloudflare web dashboard navigate to User Profile API Tokens API Tokens. Create an API token with the following settings:

  • Permissions: Zone - DNS - Edit, Zone - Zone - Read
  • Resources: Include - All Zones

Next create a Kubernetes secret to store your API token:

cat <<EOF > cloudflare-api-dns-token-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-dns-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  api-token: YOUR_API_TOKEN
EOF

kubectl apply -f cloudflare-api-dns-token-secret.yaml
Replace YOUR_API_TOKEN with your token from Cloudflare

Create a ClusterIssuer resource. This will issue certificates.

💡
Kubernetes also has a Issuer resource. These can only issue certificates in one namespace, while ClusterIssuer can issue certs in all namespaces on a cluster.
cat <<EOF > letsencrypt-prod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: cert-manager
spec:
  acme:
    email: youremail@example.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-dns-token-secret
            key: api-token
EOF

kubectl apply -f letsencrypt-prod.yaml
Replace youremail@example.com with your email. Let's Encrypt may use this email to contact you about your certificate.

Finally create a Certificate resource for Harbor to use:

cat <<EOF > harbor-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: harbor-external-cert
  namespace: harbor
  labels:
    app: harbor
spec:
  dnsNames:
  - harbor.private.tcehomelab.com
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: letsencrypt-prod
  secretName: harbor-external-cert-tls
  usages:
  - digital signature
  - key encipherment

If everything went well kubectl get certificates -n harbor should show the following:

kubectl get certificates -n harbor
NAME                   READY   SECRET                           
harbor-external-cert   True    harbor.private.tcehomelab.com-tls   7m

It may take a few minutes to become ready. You can run kubectl describe certificate -n harbor harbor-external-cert to see Certificate details. If you have waited a few minutes and the certificate is still not ready, follow the troubleshooting steps listed here.

Installing Harbor

We are finally ready to install harbor.

Download the values file and password-generation scripts.

# harbor-values.yaml
wget https://raw.githubusercontent.com/vmware-tanzu/community-edition/main/addons/packages/harbor/2.4.2/bundle/config/values.yaml -O harbor-values.yaml
# generate-passwords.sh
wget https://raw.githubusercontent.com/vmware-tanzu/community-edition/main/addons/packages/harbor/2.4.2/bundle/config/scripts/generate-passwords.sh -O generate-passwords.sh

Run the password generation script on the values file. This will modify the file to include the passwords and keys that Harbor needs.

bash generate-passwords.sh harbor-values.yaml

Open harbor-values.yaml in an editor of your choice. Change the hostname to the FQDN where you plan to host Harbor and should match the DNS name of the Certificate created earlier (in my case harbor.private.tcehomelab.com). Set the  tlsCertificateSecretName to harbor-external-cert-tls. Save the file.

Run the following to create a copy of the values file with comments removed (it uses the yq tool we installed earlier):

cp harbor-values.yaml harbor-values-clean.yaml && yq -i eval '... comments=""' harbor-values-clean.yaml

Install harbor:

tanzu package install harbor -p harbor.community.tanzu.vmware.com -v 2.4.2 -f harbor-values-no-comment.yaml

Finally, create a Kubernetes ingress resource:

cat <<EOF > harbor-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: harbor
  labels:
    app: harbor
  namespace: harbor
spec:
  rules:
  - host: harbor.private.tcehomelab.com
    http:
      paths:
      - backend:
          service:
            name: harbor-portal
            port:
              number: 443
        path: "/"
        pathType: ImplementationSpecific
EOF

kubectl apply -f harbor-ingress.yaml
Change host to your hostname.

If you run kubectl get ing -n harbor you should see an output similar to the following:

kubectl get ing -n harbor        
NAME     CLASS    HOSTS                           ADDRESS          PORTS   AGE
harbor   <none>   harbor.private.tcehomelab.com   192.168.30.212   80      10m

Add a DNS entry on your local network DNS (e.g. pfSense) that points the harbor host to the IP address of the ingress.

Finally you should be able to go the Harbor webpage at whatever your hostname is. You can login with the username admin and password from harbor-values.yaml (harborAdminPassword). See the usage section of the TCE harbor docs or the official Harbor docs for more info, such as instructions for uploading an image to Harbor.