hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

Harborのインストール方法



1 Harborとは?

Harborは、コンテナイメージを保存・管理するためのコンテナレジストリです。Docker Hubのような公開レジストリとは異なり、組織内に専用のレジストリを構築できるため、本番環境で使用するコンテナイメージを安全に一元管理できます。また、イメージの脆弱性スキャン、アクセス制御、イメージ署名、レプリケーションなど、コンテナを安全に運用するための機能を標準で提供しています。

goharbor.io

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@control ~]# kubectl version
Client Version: v1.36.2
Kustomize Version: v5.8.1
Server Version: v1.36.2

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードは 4コアのCPU(4 vCPU) を搭載しています。

[root@control ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 1:1:1:1          yes
  2    0      2    2 2:2:2:2          yes
  3    0      3    3 3:3:3:3          yes

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 事前準備

本検証ではCNI(Container Network Interface)にCalicoを使用しており、ノード間のPod通信にはIPIPカプセル化方式(tunl0インターフェース)を使用しています。firewalldを有効にした環境では、Calicoが使用するトンネルインターフェースやPodネットワークに対する通信がfirewalldによって許可されていない場合、Pod間通信が正常に行えず、Harborなどのアプリケーションが正常に動作しないことがあります。そのため、この後の手順(MetalLB、Harborのインストール)を正常に進めるため、事前に以下の設定を行います。3章の設定は全てのノード(コントロールノード、ワーカノード)で実施します。

(1) tunl0 の状態確認
tunl0が現在どのゾーンに属しているか確認します。no zoneと表示された場合、以降の設定が必要です。

[root@control ~]# firewall-cmd --get-zone-of-interface=tunl0
no zone

firewall-cmdコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

(2) trustedゾーンへの追加
firewalldのtrustedゾーンは、そのゾーンに属する通信を許可する、最も信頼度の高いゾーンです。今回は、Calicoが使用するトンネルインターフェース(tunl0)と、Podネットワーク(10.244.0.0/16)からの通信を許可するため、これらをtrustedゾーンへ追加します。

tunl0インターフェースをtrustedゾーンへ追加します。

[root@control ~]# firewall-cmd --permanent --zone=trusted --add-interface=tunl0
success

Podネットワーク(10.244.0.0/16)をtrustedゾーンへ追加します。この設定により、Calicoが構成するPodネットワークの通信がfirewalldによって遮断されることを防ぎます。

[root@control ~]# firewall-cmd --permanent --zone=trusted --add-source=10.244.0.0/16
success

クラスタを構成するノードが接続されているネットワーク(192.168.1.0/24)をtrustedゾーンへ追加します。

[root@control ~]# firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24
success

永続設定を反映するため、firewalldの設定をリロードします。

[root@control ~]# firewall-cmd --reload
success

(3) 設定確認
tunl0がtrustedゾーンに追加されたことを確認します。

[root@control ~]# firewall-cmd --get-zone-of-interface=tunl0
trusted

trustedゾーンの設定内容を確認します。interfacesにtunl0 が登録されていれば、設定は正しく反映されています。

[root@control ~]# firewall-cmd --zone=trusted --list-all
trusted (active)
  target: ACCEPT
  ingress-priority: 0
  egress-priority: 0
  icmp-block-inversion: no
  interfaces: tunl0
  sources: 10.244.0.0/16 192.168.1.0/24
  services:
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

4 ストレージ(Local Path Provisioner)のインストール

Harborは、コンテナイメージやデータベースなどのデータを永続的に保存するために、PersistentVolumeClaim(PVC)を使用します。ここでは、PVCの要求に応じてPersistentVolume(PV)を動的に作成するLocal Path Provisionerをインストールします。Local Path Provisionerは、ノード上のローカルディスクを利用して、ストレージを提供します。

curl コマンドを使用して、Local Path Provisionerのマニフェストファイルをダウンロードします。

[root@control ~]# curl -LO https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.36/deploy/local-path-storage.yaml
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3852  100  3852    0     0   1046      0  0:00:03  0:00:03 --:--:--  1046

curl コマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com


ダウンロードしたマニフェストのバックアップを作成します。

[root@control ~]# cp local-path-storage.yaml local-path-storage.yaml.org

テキストエディタを使って、StorageClassをクラスタのデフォルトのStorageClassとして使用するため、local-path-storage.yaml を編集します。

[root@control ~]# vi local-path-storage.yaml

diff コマンドを使用し、StorageClassをデフォルトとして指定するアノテーションが追加されたことを確認します。

[root@control ~]# diff -Nur local-path-storage.yaml.org local-path-storage.yaml
--- local-path-storage.yaml.org 2026-07-12 08:58:45.916523536 +0900
+++ local-path-storage.yaml     2026-07-12 09:00:47.781709321 +0900
@@ -116,6 +116,8 @@
 kind: StorageClass
 metadata:
   name: local-path
+  annotations:
+    storageclass.kubernetes.io/is-default-class: "true"
 provisioner: rancher.io/local-path
 volumeBindingMode: WaitForFirstConsumer
 reclaimPolicy: Delete

編集したマニフェストをKubernetesクラスタに適用し、Local Path Provisionerをインストールします。

[root@control ~]# kubectl apply -f local-path-storage.yaml
namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
role.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
rolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created

インストールが完了したら、Local Path ProvisionerのPodが正常に起動しているか確認します。STATUS が Running になっていればOKです。

[root@control ~]# kubectl get pods -n local-path-storage -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
local-path-provisioner-6d76485965-xq6s2   1/1     Running   0          24s   10.244.189.66   worker2   <none>           <none>

最後に、local-path がデフォルトのStorageClassとして正しく認識されているかを確認します。

[root@control ~]# kubectl get storageclass
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  42s

5 MetalLBのインストール

オンプレミス(自宅・社内インフラなど)では、クラウド環境とは異なり、LoadBalancer型のServiceを作成しても外部IPアドレス(EXTERNAL-IP)が自動で割り当てられません。MetalLBは、オンプレミス環境でもLoadBalancer型Serviceに対して外部IPアドレスを割り当てることができるソフトウェアです。今回は、後ほどHarborへのアクセスに使うIngress Controller(ingress-nginx)を外部公開するためにMetalLBを導入します。

5.1 MetalLBのインストール

MetalLBのリポジトリを追加します。

[root@control ~]# helm repo add metallb https://metallb.github.io/metallb
"metallb" has been added to your repositories

helm コマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

最新のチャート(パッケージ)情報を取得するため、Helmリポジトリの情報を更新します。

[root@control ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
Update Complete. ?Happy Helming!?

Helmを使ってMetalLBをインストールします。--create-namespace を付与することで、専用のNamespace(metallb-system)の作成とインストールを同時に行います。STATUS: deployed となれば成功です。

[root@control ~]# helm install metallb metallb/metallb --namespace metallb-system --create-namespace
I0712 09:11:29.887926   36180 warnings.go:107] "Warning: unrecognized format \"cidr\""
NAME: metallb
LAST DEPLOYED: Sun Jul 12 09:11:29 2026
NAMESPACE: metallb-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
MetalLB is now running in the cluster.

Now you can configure it via its CRs. Please refer to the metallb official docs
on how to use the CRs.

helm list コマンドを使い、指定したNamespaceにMetalLBが正常にデプロイされている状態か確認します。

[root@control ~]# helm list -n metallb-system
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
metallb metallb-system  1               2026-07-12 09:11:29.313813192 +0900 JST deployed        metallb-0.16.1  v0.16.1

MetalLBを構成する各Podの状態を確認します。すべてのPodが Running になっていれば正常に動作しています。

[root@control ~]# kubectl get pods -n metallb-system
NAME                                             READY   STATUS    RESTARTS   AGE
metallb-controller-bc9cbb54b-xsts9               1/1     Running   0          2m26s
metallb-frr-k8s-fcdvp                            5/5     Running   0          2m26s
metallb-frr-k8s-jtg94                            5/5     Running   0          2m26s
metallb-frr-k8s-s282w                            5/5     Running   0          2m26s
metallb-frr-k8s-statuscleaner-75b695f48d-dvtnc   1/1     Running   0          2m26s
metallb-speaker-fs475                            1/1     Running   0          2m26s
metallb-speaker-w9p7t                            1/1     Running   0          2m26s
metallb-speaker-wh8fd                            1/1     Running   0          2m26s

MetalLBのWebhook Serviceが作成されていることを確認します。

[root@control ~]# kubectl get svc -n metallb-system
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
frr-k8s-webhook-service   ClusterIP   10.96.250.168   <none>        443/TCP   4m12s
metallb-webhook-service   ClusterIP   10.111.129.74   <none>        443/TCP   4m12s

5.2 IPプールの設定

MetalLBがLoadBalancer型のServiceに対して、どのIPアドレスを割り当てるべきかを定義したマニフェストを作成します。 
- IPAddressPool:払い出すIPアドレスの範囲(今回は 192.168.1.200〜210)を指定します。
- L2Advertisement:定義したIPアドレスを、L2モード(ARP)を使ってネットワーク内に通知するための設定です。

[root@control ~]# vi metallb-pool.yaml
[root@control ~]# cat metallb-pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.200-192.168.1.210
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2advertisement
  namespace: metallb-system

作成したマニフェストファイルをクラスタに適用します。

[root@control ~]# kubectl apply -f metallb-pool.yaml
ipaddresspool.metallb.io/first-pool created
l2advertisement.metallb.io/l2advertisement created

設定したIPプール(IPAddressPool)が正しく認識され、指定したIPアドレス範囲が登録されているか確認します。

[root@control ~]# kubectl get ipaddresspool -n metallb-system
NAME         AUTO ASSIGN   AVOID BUGGY IPS   ADDRESSES
first-pool   true          false             ["192.168.1.200-192.168.1.210"]

同様に、L2モードの通知設定(L2Advertisement)が正しく登録されているか確認します。

[root@control ~]# kubectl get l2advertisement -n metallb-system
NAME              IPADDRESSPOOLS   IPADDRESSPOOL SELECTORS   INTERFACES
l2advertisement

5.3 動作確認

設定したIPプールから、実際にServiceへIPアドレスが自動で割り当てられるかを確認します。まずは動作確認用として、シンプルなNginxのDeploymentを作成(Podをデプロイ)します。

[root@control ~]# kubectl create deployment nginx-test --image=nginx
deployment.apps/nginx-test created

Deploymentの詳細は、以下のページをご覧ください。
hana-shin.hatenablog.com

作成したNginxのDeploymentを、LoadBalancer 型のServiceとして外部へ公開します。

[root@control ~]# kubectl expose deployment nginx-test --port=80 --type=LoadBalancer
service/nginx-test exposed

作成されたServiceのステータスを確認します。EXTERNAL-IP の欄に、先ほどプールとして指定した範囲内から 192.168.1.200 が自動的に割り当てられていることがわかります。

[root@control ~]# kubectl get svc nginx-test
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
nginx-test   LoadBalancer   10.110.52.162   192.168.1.200   80:31347/TCP   5s

curl コマンドを使い、割り当てられた外部IPアドレス(192.168.1.200)へHTTPリクエストを送信してみます。Serviceを介して背後のNginx Podへと正しくルーティングされ、Nginxから正常な応答が返ってきていることが確認できます。

[root@control ~]# curl http://192.168.1.200
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, nginx is successfully installed and working.
Further configuration is required for the web server, reverse proxy,
API gateway, load balancer, content cache, or other features.</p>

<p>For online documentation and support please refer to
<a href="https://nginx.org/">nginx.org</a>.<br/>
To engage with the community please visit
<a href="https://community.nginx.org/">community.nginx.org</a>.<br/>
For enterprise grade support, professional services, additional
security features and capabilities please refer to
<a href="https://f5.com/nginx">f5.com/nginx</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

動作確認が完了したため、テスト用に作成したServiceとDeploymentを削除して環境を元の状態に戻しておきます。

[root@control ~]# kubectl delete svc nginx-test
service "nginx-test" deleted from default namespace
[root@control ~]# kubectl delete deployment nginx-test
deployment.apps "nginx-test" deleted from default namespace

6 イングレスコントローラのインストール

ここでは、ingress-nginxというIngress Controllerをインストールします。
Ingressは、HTTP/HTTPSリクエストをホスト名やURLパスに応じてServiceへルーティングするためのルールを定義するオブジェクトです。しかし、Ingressを定義しただけではリクエストはルーティングされません。Ingressで定義したルールを読み取り、リバースプロキシとして動作し、リクエストを適切なServiceへ振り分ける役割を担うのがIngress Controllerです。

6.1 ingress-nginxのインストール

MetalLBのプールから払い出される、Ingress用のIP(例えば192.168.1.200)に対してホスト名を紐付けます。

  • 192.168.1.200 harbor.home.lab

ingress-nginxのHelmリポジトリを追加します。

[root@control ~]# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
"ingress-nginx" has been added to your repositories

Helmリポジトリの情報を最新の状態に更新します。

[root@control ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
...Successfully got an update from the "ingress-nginx" chart repository
Update Complete. ?Happy Helming!?

ingress-nginxをインストールします。--set controller.service.type=LoadBalancerを指定することで、ingress-nginxのServiceがLoadBalancer型で作成され、MetalLBによって外部IPアドレスが自動的に割り当てられます。

[root@control ~]# helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.service.type=LoadBalancer
NAME: ingress-nginx
LAST DEPLOYED: Sun Jul 12 10:38:10 2026
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
It may take a few minutes for the load balancer IP to be available.
You can watch the status by running 'kubectl get service --namespace ingress-nginx ingress-nginx-controller --output wide --watch'

An example Ingress that makes use of the controller:
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: example
    namespace: foo
  spec:
    ingressClassName: nginx
    rules:
      - host: www.example.com
        http:
          paths:
            - pathType: Prefix
              backend:
                service:
                  name: exampleService
                  port:
                    number: 80
              path: /
    # This section is only required if TLS is to be enabled for the Ingress
    tls:
      - hosts:
        - www.example.com
        secretName: example-tls

If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:

  apiVersion: v1
  kind: Secret
  metadata:
    name: example-tls
    namespace: foo
  data:
    tls.crt: <base64 encoded cert>
    tls.key: <base64 encoded key>
  type: kubernetes.io/tls

インストール後、Podの状態を確認します。

[root@control ~]# kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-5cd9869bf8-m584m   1/1     Running   0          49s

インストール後、Serviceの状態を確認します。ServiceのEXTERNAL-IPにMetalLBのプール範囲内(192.168.1.200〜192.168.1.210)のIPアドレスが割り当てられていれば成功です。

[root@control ~]# kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.97.221.138   192.168.1.200   80:32423/TCP,443:32058/TCP   55s
ingress-nginx-controller-admission   ClusterIP      10.98.231.61    <none>          443/TCP                      55s

7 証明書の作成

ここでは、公開認証局(Root CA)の代わりに、コントロールノード上でプライベートCAを構築します。具体的には、作業用ディレクトリを作成し、CAで使用する秘密鍵とCA証明書を作成します。続いて、作成したプライベートCAを使用してHarborのサーバ証明書に署名します。作成する証明書は以下のとおりです。

  • PCへインポートするCA証明書
  • HarborのHTTPS通信で使用するサーバ証明書

7.1 プライベートCAの証明書の作成

作業用ディレクトリを作成し、cdコマンドでharbor-certsディレクトリへ移動します。

[root@control ~]# mkdir -p ~/harbor-certs 
[root@control ~]# cd harbor-certs/

プライベートCAで使用する秘密鍵を作成します。

[root@control test]# openssl genpkey -algorithm RSA -out ca.key -pkeyopt rsa_keygen_bits:4096
[root@control harbor-certs]#

作成した秘密鍵(ca.key)が正常に生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 4
-rw-------. 1 root root 3268  7月 14 10:49 ca.key

プライベートCAのCA証明書(ca.crt)を作成します。このCA証明書は、Harborのサーバ証明書の検証に使用します。なお、検証環境のため、有効期限は10年(3650日)に設定しています。

[root@control harbor-certs]# openssl req -x509 -new -noenc -key ca.key -sha256 -days 3650 -out ca.crt -subj "/C=JP/ST=Tokyo/L=Machida/O=HomeLab/OU=CA/CN=HomeLab Root CA"

作成したCA証明書(ca.crt)と秘密鍵(ca.key)が正常に生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 8
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key

7.2 harborのサーバ証明書の作成

HarborとHTTPS通信を行うため、harbor.home.lab用のサーバー証明書を作成します。

まず、Harborで使用する4096ビットRSA秘密鍵を生成します。

[root@control harbor-certs]# openssl genpkey -algorithm RSA -out harbor.home.lab.key -pkeyopt rsa_keygen_bits:4096

作成したサーバー秘密鍵が生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 12
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key
-rw-------. 1 root root 3272  7月 14 10:51 harbor.home.lab.key

続いて、秘密鍵を使用してCSR(Certificate Signing Request:証明書署名要求)を作成します。CSRには、証明書へ埋め込むサーバー名などの情報が含まれます。

[root@control harbor-certs]# openssl req -new -key harbor.home.lab.key \
  -out harbor.home.lab.csr \
  -subj "/C=JP/ST=Tokyo/L=Machida/O=HomeLab/OU=Harbor/CN=harbor.home.lab"

CSR(harbor.home.lab.csr)とサーバー秘密鍵(harbor.home.lab.key)が生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 16
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key
-rw-r--r--. 1 root root 1704  7月 14 10:51 harbor.home.lab.csr
-rw-------. 1 root root 3272  7月 14 10:51 harbor.home.lab.key

サーバー証明書へSAN(Subject Alternative Name)を設定するための設定ファイルを作成します。Webブラウザでは、証明書のSANに接続先のホスト名やIPアドレスが登録されていることが必須となっています。

[root@control harbor-certs]# vi harbor.home.lab.ext
[root@control harbor-certs]# cat harbor.home.lab.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = harbor.home.lab
IP.1 = 192.168.1.200

作成したCSRをプライベートCAの秘密鍵で署名し、Harborのサーバー証明書を発行します。検証環境のため、有効期限は1年(365日)に設定しています。

[root@control harbor-certs]# openssl x509 -req -in harbor.home.lab.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out harbor.home.lab.crt -days 365 -sha256 -extfile harbor.home.lab.ext
Certificate request self-signature ok
subject=C=JP, ST=Tokyo, L=Machida, O=HomeLab, OU=Harbor, CN=harbor.home.lab

サーバー証明書(harbor.home.lab.crt)、CSR、秘密鍵などのファイルが生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 28
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key
-rw-r--r--. 1 root root   41  7月 14 10:52 ca.srl
-rw-r--r--. 1 root root 2126  7月 14 10:52 harbor.home.lab.crt
-rw-r--r--. 1 root root 1704  7月 14 10:51 harbor.home.lab.csr
-rw-r--r--. 1 root root  257  7月 14 10:52 harbor.home.lab.ext
-rw-------. 1 root root 3272  7月 14 10:51 harbor.home.lab.key

最後に、サーバー証明書へSANが正しく設定されていることを確認します。

[root@control harbor-certs]# openssl x509 -in harbor.home.lab.crt -text -noout | grep -A2 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
                DNS:harbor.home.lab, IP Address:192.168.1.200
            X509v3 Subject Key Identifier:

7.3 Kubernetes Secretへの登録

harabor Namespaceを作成します。

[root@control harbor-certs]# kubectl create namespace harbor
namespace/harbor created

HarborのIngressからTLS証明書を利用できるよう、作成したサーバー証明書と秘密鍵をKubernetes Secretとして登録します。

[root@control harbor-certs]# kubectl create secret tls harbor-tls \
  --cert=harbor.home.lab.crt \
  --key=harbor.home.lab.key \
  -n harbor
secret/harbor-tls created
[root@control harbor-certs]#

ConfigMap/Secretの詳細は、以下のページをご覧ください。
hana-shin.hatenablog.com

作成したTLS Secretが登録されていることを確認します。

[root@control harbor-certs]# kubectl get secret harbor-tls -n harbor
NAME         TYPE                DATA   AGE
harbor-tls   kubernetes.io/tls   2      30s

8 PC側の作業

PCのブラウザから、コントロールノード上で稼働するHarborへHTTPSでアクセスします。このとき、ブラウザはHarborから送られてくるサーバ証明書を検証します。サーバ証明書の検証には、CA証明書に含まれる公開鍵が使用されます。そのため、事前にCA証明書をPCへインポートしておく必要があります。ここでは、CA証明書をWindowsへインポートする手順を説明します。

8.1 hostsファイルの編集

PCからHarborへホスト名でアクセスできるよう、Windowsのhostsファイル(C:\Windows\System32\drivers\etc\hosts)に以下の内容を追記します。

192.168.1.200  harbor.home.lab

8.2 CA証明書のインポート

コントロールノードで作成したCA証明書(ca.crt)をPCへ転送します。次に、転送したCA証明書をWindowsの証明書ストア(信頼されたルート証明機関)へインポートします。ChromeやMicrosoft EdgeはWindowsの証明書ストアを利用するため、この設定を行うことで、ブラウザからHarborへHTTPSでアクセスすることができるようになります。

まず、[Windows]キー + [R] を押して 「ファイル名を指定して実行」 を開きます。そして、「certmgr.msc」 と入力し、「OK」 をクリックします。

証明書マネージャー(Certmgr)が起動したら、「信頼されたルート証明機関」→「証明書」 を右クリックし、「すべてのタスク」→「インポート」 を選択します。その後、ウィザードに従って転送したCA証明書を選択し、Windowsへインポートします。

「次へ」をクリックする。

作成したCA証明書(ca.crt)を指定して「次へ」をクリックします。

「証明書をすべて次のストアに配置する(P)」から「信頼されたルート証明機関」を選択して「次へ」をクリックする。

「完了」をクリックする。

「はい」をクリックする

「正しくインポートされました」のポップアップ画面を確認する。

9 Harborのインストール

Harbor用のNamespaceを作成します。

[root@control ~]# kubectl create namespace harbor
namespace/harbor created

Harborのリポジトリを追加します。

[root@control ~]# helm repo add harbor https://helm.goharbor.io
"harbor" has been added to your repositories

リポジトリの情報を最新の状態に更新します。

[root@control ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
...Successfully got an update from the "ingress-nginx" chart repository
...Successfully got an update from the "harbor" chart repository
Update Complete. ?Happy Helming!?

追加したリポジトリを確認します。

[root@control ~]#  helm repo list
NAME            URL
metallb         https://metallb.github.io/metallb
ingress-nginx   https://kubernetes.github.io/ingress-nginx
harbor          https://helm.goharbor.io

helm search repoコマンドを実行して、リポジトリに登録されているチャートを確認します。実行結果から、harborチャートが利用可能であることを確認できます。

[root@control ~]# helm search repo harbor
NAME            CHART VERSION   APP VERSION     DESCRIPTION
harbor/harbor   1.19.1          2.15.1          An open source trusted cloud native registry th...

Harborインストール用の作業ディレクトリを作成します。

[root@control ~]#  mkdir -p ~/harbor-install

HarborはHelmチャートを使用してインストールします。Helmでは、values.yamlに設定を記述することで、デフォルト設定を変更できます。ここでは、Harborへアクセスするホスト名やTLSの設定、永続ストレージの設定、管理者パスワードなどを指定するため、values.yamlを作成します。

[root@control ~]# vi ~/harbor-install/values.yaml
[root@control ~]# cat ~/harbor-install/values.yaml
expose:
  type: ingress
  tls:
    enabled: true
    certSource: secret
    secret:
      secretName: "harbor-tls"
  ingress:
    hosts:
      core: harbor.home.lab
    className: "nginx"

externalURL: https://harbor.home.lab

persistence:
  enabled: true
  persistentVolumeClaim:
    registry:
      storageClass: "local-path"
      size: 20Gi
    jobservice:
      jobLog:
        storageClass: "local-path"
        size: 1Gi
    database:
      storageClass: "local-path"
      size: 5Gi
    redis:
      storageClass: "local-path"
      size: 1Gi
    trivy:
      storageClass: "local-path"
      size: 5Gi

harborAdminPassword: "Harbor12345"

Harborをインストールします。-fオプションで指定したvalues.yamlの内容(Ingress・TLS・永続化ストレージの設定)が反映されます。

[root@control ~]# helm install harbor harbor/harbor -n harbor -f ~/harbor-install/values.yaml
NAME: harbor
LAST DEPLOYED: Sun Jul 12 11:24:12 2026
NAMESPACE: harbor
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
Please wait for several minutes for Harbor deployment to complete.
Then you should be able to visit the Harbor portal at https://harbor.home.lab
For more details, please visit https://github.com/goharbor/harbor

Harborがインストールされていることを確認します。

[root@control ~]# helm list -n harbor
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
harbor  harbor          1               2026-07-12 11:24:12.830505033 +0900 JST deployed        harbor-1.19.1   2.15.1

HarborのPodが正常に起動したことを確認します。

[root@control ~]# kubectl get pods -n harbor
NAME                                 READY   STATUS    RESTARTS      AGE
harbor-core-767d58bf54-6kh8f         1/1     Running   0             2m32s
harbor-database-0                    1/1     Running   0             2m32s
harbor-jobservice-84b7976678-l88l5   1/1     Running   4 (80s ago)   2m32s
harbor-portal-5b6bc45d5c-wtwd6       1/1     Running   0             2m32s
harbor-redis-0                       1/1     Running   0             2m32s
harbor-registry-54f749db94-x2spw     2/2     Running   0             2m32s
harbor-trivy-0                       1/1     Running   0             2m32s

Harborでは、コンテナイメージやデータベース、Redisなどの永続データをPersistentVolumeClaim(PVC)に保存します。すべてのPVCが Bound になっていれば、PersistentVolumeが正常に割り当てられています。

[root@control ~]# kubectl get pvc -n harbor
NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
data-harbor-redis-0               Bound    pvc-b5442603-8404-481e-8b93-938faf3e785e   1Gi        RWO            local-path     <unset>                 3m7s
data-harbor-trivy-0               Bound    pvc-b5fe7ad0-3510-44cc-a733-cb84a8f17eea   5Gi        RWO            local-path     <unset>                 3m7s
database-data-harbor-database-0   Bound    pvc-49907738-2af0-4864-b698-81f58eba1839   1Gi        RWO            local-path     <unset>                 3m7s
harbor-jobservice                 Bound    pvc-d236097f-637c-4525-b4bb-ecf4c7f9ed5e   1Gi        RWO            local-path     <unset>                 3m7s
harbor-registry                   Bound    pvc-f9800439-4cb1-4cca-b57b-a80b3ae96771   5Gi        RWO            local-path     <unset>                 3m7s

Harbor用のIngressリソースが作成されていることを確認します。HOSTSにharbor.home.lab、ADDRESSにMetalLBから割り当てられたIPアドレスが表示されていれば、Ingressが正しく構成されています。

[root@control ~]# kubectl get ingress -n harbor
NAME             CLASS   HOSTS             ADDRESS         PORTS     AGE
harbor-ingress   nginx   harbor.home.lab   192.168.1.200   80, 443   34h

10 Harbor管理画面へのログイン

ブラウザで https://harbor.home.lab/ にアクセスします。
ログイン画面が表示されたら、ユーザー名に 「admin」、パスワードに 「Harbor12345」 を入力してログインします。パスワードには、values.yaml の harborAdminPassword に設定した値を使用します。

ログインに成功すると、以下のようにHarborの管理画面が表示されます。

Z 参考図書

今回の記事執筆にあたり参考にした図書は以下のものです。

単行本

電子書籍