今さら聞けないHarborのレジストリ以外の機能

本記事は「TUNA-JP Advent Calendar 2022」の11日目のエントリとして、Harborのコンテナレジストリ以外の機能について解説します。\
Harborは非常に多機能なので、ただのコンテナレジストリと思ってる方に是非読んでもらえればと思います。

## Harborとは
元々はVMwareが2014年頃から開発を始め、2016年にオープンソース化したコンテナレジストリソフトウェアです。
2018年にCNCFのプロジェクトとして寄付され、2020年にはGraduated Projectとなった、コンテナレジストリとしてはデファクトと言っても良いOSSです。

![1669593448174.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/8511981e-3986-3b30-8071-dc51b4a219d9.png)

[Harborのオフィシャルページ](https://github.com/goharbor/harbor#features)には以下のような機能があると記載されています。

- `Cloud native registry`: コンテナイメージとHelmチャートの両方をサポート
- `Role based access control`: ユーザーは「プロジェクト」を通じて異なるリポジトリにアクセスし、プロジェクトの下でイメージやHelmチャートに異なる権限を持つことができる
- `Policy based replication`: リポジトリ、タグ、ラベルの各フィルターを使用し、ポリシーに基づいて複数のレジストリインスタンス間でイメージとチャートをレプリケート（同期）することができる
- `Vulnerability Scanning`: イメージの脆弱性を定期的にスキャンし、脆弱性のあるイメージがデプロイされないようにするためのポリシーチェックを実施
- `LDAP/AD support`: 既存のエンタープライズLDAP/ADと統合し、LDAPグループをHarborにインポートして、特定のプロジェクトに権限を付与する
- `OIDC support`: 外部の認証サーバーやIDプロバイダーから認証されたユーザーの身元を確認し、SSOを有効化する
- `Image deletion & garbage collection`: ガベージコレクションジョブを実行し、イメージを削除して、定期的にスペースを解放する
- `Notary`:　コンテナイメージに署名し、出所を保証し、また、署名されていないイメージのデプロイを防止するポリシーも有効にする
- `Graphical user portal`: リポジトリの閲覧、検索、プロジェクトの管理を容易に行う
- `Auditing`: リポジトリに対するすべての操作は、ログによって追跡可能
- `RESTful API`: 管理操作を容易にするためにRESTful APIが提供されている
- `Easy deployment`: Docker composeやHelm Chartでデプロイ可能で、最近Harbor Operatorも追加された

こんな感じで非常に多機能なHarborなんですが、今回はこの中でも特に商用環境で運用する上で有用な
- レプリケーション
- 脆弱性スキャン
- ガベージコレクション
  
にフォーカスして解説します。\
なお、イメージへの署名については過去に[こちら](https://qiita.com/ipppppei/items/ce9c20bb04cbcfb47f4e)で解説記事を書いているので、必要な方はそちらをご参照いただければと思います。

ちなみに余談ですが、Helm Chartの管理機能であるChart MuseumについてはTanzu Kubernetes Gridに付属するHarborだとサポートされません。また、[将来的に廃止になる](https://github.com/goharbor/harbor/discussions/15057)ようです。
Chart管理目的で利用される方は気をつけてください。(Chartの[OCI baseの使い方](https://helm.sh/docs/topics/registries/)向けのレジストリとしては利用可能なので、こちらに置き換えていくと良さそうです）

## レプリケーション
レプリケーション機能はHarborと別のコンテナレジストリ間でコンテナイメージやChartを同期する機能です。
AdministrationのReplicationsから利用することが出来ます。
![1670117975229.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/cc932cc1-1a09-4ffb-e4a8-29f59c217c2c.png)


レプリケーションに失敗した場合は数分後にリトライするようになっており、ちょっとしたバックアップ感覚や冗長化目的で利用することも可能です。（ただし、世代管理等は出来ないのでバックアップとイコールではないですし、冗長化観点でも転送が必ず必要なため同期ずれのタイミングがあること、containerdの向き先変更が必要だったりガチ目的では適していない点は注意が必要です。あくまでも「ちょっとした」になります。）

設定画面は以下のようになっており、レプリケーションモードとしてPush-based（相手のコンテナレジストリにイメージをpushする)か、Pull-based（相手のコンテナレジストリのイメージをHarborにpullする）
が選べるようになっています。
![1670118624590.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/6941211b-b573-a581-1ccf-e54f83b34872.png)

今回はPull-modeで試しに設定してみます。設定画面は以下になります。
![1670118720565.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/0f87cc77-31e1-96f5-24e5-d30c3c04a55c.png)

設定項目は以下が用意されています。
- `Source registry`：Pullしてくるレジストリを指定します。
- `Source resource filter`：Pullするイメージを絞り込みます。指定がない場合は全てPullします。
- `Destination`：転送先Projectのことです（なぜNamespaceという名前なのかは不明）。指定したProjectがない場合は自動で生成されます。指定しない場合はSource resource filterの名前が指定されたことになります。
- `Flatting`：ネスト構造(例：harbor.info/myproj/ngin/ngin)の場合、どこまでネストを再現するかの指定です。
- `Trigger Mode`：手動でSyncするか、スケジュール(cron形式)でSyncするかを選べます。
- `Bandwidth`：帯域幅を制限する場合に指定します。量が多い場合など、業務に影響する可能性がある場合は絞ると良さそうです。

なお、Trigger Modeに関してはレプリケーションモードが`Push-based`の場合は`Event Based`というPush契機でSyncさせる機能も選ぶことが出来ます。イベントドリブンでレプリケーションを実施したい人はこちらを利用するとよいでしょう。

Source registryは予め登録しておく必要があります。作っていない場合は`Endpoints`のリンクをクリックすると作成できます。
レジストリの登録時、Providerを聞かれるのでPullしてくる元のレジストリを指定します。

![1670119782562.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/02fef3d9-a436-d7b9-b06a-e1f4ea5f47f2.png)

今回は動作確認用にGHCRを利用し、Access IDにGitHubのアカウント、Access SecretにGitHubのTokenを指定しています。

GHCR上に置いた[nginx](https://github.com/users/imurata/packages/container/package/nginx%2Fnginx)をレプリケーションする設定は以下のようになります。

![1670129733377.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/0610bc4c-eee1-a0b0-e67b-0fc2f3f01b47.png)

今回は実験用にFlatteningを`Flatten All Levels`にしています。キレイにネスト構造を残したい人は`No Flatting`を選択してください。

SAVEを押して保存すると、このようにレプリケーションの設定が表示されるので、ラジオボタンをクリックして`REPLICATE`を選択します。
![1670129857201.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/b1f9fd2b-a471-acec-1ca5-1b90624aefea.png)

しばらくすると、下のExecutionsのStatusがSucceedに変わります。

![1670129914793.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/b43d5c16-0f27-2fda-74a7-e3811a934685.png)

Project側にnginxのイメージが転送されました。

![1670129961841.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/9a479bf6-c41b-ea18-b7e0-3b490496e013.png)

## 脆弱性スキャン
Harborの構築時に[Trivyを有効化](https://goharbor.io/docs/main/install-config/run-installer-script/#installation-with-trivy)している場合、コンテナイメージの脆弱性が簡単に検査できます。

Trivyが有効になっている場合、イメージ名をチェックして、`SCAN`を実行すると実行できます。
![1670131063574.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/cb6554be-1d3f-7f1b-0d3d-59ce6f00ef60.png)

イメージのpush時にスキャンすることも出来ます。Projectの`Configuration`から`Vulnerability scanning`の`Automatically scan images on push`にチェックを入れます。

![1670131431377.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/60f716f1-ea96-0cee-2a51-414ba5eccad5.png)

そうすることで、pushしたものが自動でスキャンされます。
試しにCentOSのlatestのイメージをpushしてみます。
![1670131551337.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/b7ef1561-bc94-9079-053e-efe0c0edc7a7.png)

上記のようにスキャン結果が表示されるようになりました。（カーソルを合わせるとグラフが表示されます）
アーティファクト(sha256...部分)をクリックすると脆弱性の詳細も確認できます。

![1670132128542.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/2d324385-1b97-0e1f-2d03-2509e62f78d8.png)



余談ですが、このCentOSのスキャン結果を見ても分かりますが世の中のイメージはlatestを使っても脆弱性が残っていることが多いです。コンテナイメージを作る際は安易にCentOSなどをベースイメージとはせず、最小限のパッケージ構成としたコンテナイメージを作るようにしましょう。難しい場合は[Cloud Native Buildpacks](https://buildpacks.io/)やTanzu Build Serviceを使うようにしましょう。

また、インフラ管理者などは安易に脆弱性が含まれるイメージを利用されたくない、といったことがあると思います。その際はHarborの機能でpullを制限することが出来ます。
Projectの`Configuration`から`Deployment security`を選択し、`Prevent vulnerable images from running`のチェックを有効化することでpullの制限を有効化出来ます。
![1670131886677.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/06a2f957-aeda-4557-0279-70f00b8306c2.png)

プルダウン部分ではどこのSeverityから制限をかけるかが選べるようになっています。実運用だと`Low`, `Medium`くらいは許容しないと使い物にならないことも考えられるので、`High`ぐらいに設定し、下の`CVE allowlist`を活用して、`High`以上でも許容する脆弱性のCVEの番号を列挙して穴を開けておくとよいかと思います。

なお、Fixed Versionがリリースされているものだけ制限する、という使い方は出来ないため、そのような使い方がしたい場合はHarborの機能を使わずにCI/CDパイプライン上でtrivyコマンドを呼び出してガードする方がよいかと思います。

## ガベージコレクション
非常に重要なことなのですが、Harborではイメージを削除しても空き容量は回復しません。Harborのトラブルで一番多いのが容量不足なのですが、ガベージコレクションを知らないとトラブルが長期化してしまうので、この点を知らない人は是非覚えておいてください。\
まず、この事実を確認したいと思います。\
今、全体で41.14GiB利用しています。

![1670132814282.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/fe36f353-8a71-7191-725d-64d8da393fe2.png)

この時、全体で604MiB利用しているcode-serverのイメージを削除してみます。

![1670132894100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/b21f6aca-441f-9d5b-0ee8-7334d741fa93.png)


削除後、利用量を確認しますが変化していない事が分かります。

![1670132814282.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/fe36f353-8a71-7191-725d-64d8da393fe2.png)
ここで、ガベージコレクションを実行してみます。`Administration`の`Garbage Collection`を選択し、`GC NOW`を選択してガベージコレクションを実行します。

![1670133004507.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/47ba2010-b023-a932-58e4-e562569155c5.png)


実行後、Historyのタブを確認しStatusがSUCCESSになっていれば成功しています

![1670133085652.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/9b049d90-41f1-5ed1-7c89-91394219008f.png)

`Logs`の項目をクリックするとファイルが実際に削除されているログが確認できます。
ただ、今回の場合、実はほとんど削除されていません。ログから削除された領域は2MBだったことが分かります。
```
2022-12-04T05:50:14Z [INFO] [/jobservice/job/impl/gc/garbage_collection.go:356]: 4 blobs and 2 manifests are actually deleted
2022-12-04T05:50:14Z [INFO] [/jobservice/job/impl/gc/garbage_collection.go:357]: The GC job actual frees up 2 MB space.
2022-12-04T05:50:14Z [INFO] [/jobservice/job/impl/gc/garbage_collection.go:174]: success to run gc in job.
```
これはHarborが直近2時間でpushされたイメージを保護するよう出来ているためで、今回検証用のイメージは2時間以内にpushしたもののため、削除対象とならなかったからでした。
ということで、2時間待って再実行してみます。
```
2022-12-04T08:21:35Z [INFO] [/jobservice/job/impl/gc/garbage_collection.go:356]: 16 blobs and 2 manifests are actually deleted
2022-12-04T08:21:35Z [INFO] [/jobservice/job/impl/gc/garbage_collection.go:357]: The GC job actual frees up 855 MB space.
2022-12-04T08:21:35Z [INFO] [/jobservice/job/impl/gc/garbage_collection.go:174]: success to run gc in job.
```
今度は855MB削除されたので成功したようです。なお削除された量が少し多いのは、他のイメージも削除したためでした。（先程試したCentOS等)
WebUIの表示でも削除されたことが分かります。
![1670142229504.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/1b6222f0-f3d9-cfc2-a978-c0452f6bae68.png)

なお、ガベージコレクションはスケジュールすることも可能であり、ガベージコレクション実行中もHarborの機能は通常通り利用可能なので、空き容量が気になる人はスケジュールで定期的に実行しておくとよいでしょう。

## 終わりに
コンテナレジストリは様々存在しますが、現時点でここまで商用環境向けの機能が揃っているものはHarborくらいしかないのではと思います。\
また、Harborはまだまだ開発が活発で[Issues](https://github.com/goharbor/harbor/issues)を見る感じ今後も様々な機能が追加されそうなので、他の機能が気になる方はGitHubの方をwatchして動向を追ってみてください。