HarborでPrivate Registryをつくってみる


# はじめに
[Kubernetes Advent Calendar](https://qiita.com/advent-calendar/2018/kubernetes) 22日目です。
Harborの概要と，手元のKubernetes上で試せることを目標とします。
(※ネットワークがしばらく無いので検証結果やスクリーンショット等は後ほど載せますっ！)

# What's Harbor?
- 2014年にVMwareが開発
- 現在はCNCFに移管されてIncubationプロジェクトの位置付け
- オープンソース Apache2.0
- コンテナレジストリ，Helm Chartのプライベートレジストリとして使用できる
- イメージの脆弱性スキャン
- イメージ署名 (Notaryを使っている)
- LDAP/AD連携とRBACでコンテンツ保護
- Web UI
- Harbor自体がコンテナ上で動作

# Harborを構築しよう
Helm Chartが用意されているので，[GitHubの導入手順](https://github.com/goharbor/harbor-helm)に沿ってKubernetesクラスターにデプロイします。

- 前提
    - Kubernetes クラスター v1.10+
    - Helm v2.8.0+

## 0) Helm Chartをクローン
```
$ git clone https://github.com/goharbor/harbor-helm
$ cd harbor-helm
$ git checkout -b myharbor <== 任意のブランチ名
```


## 1) PVを準備
Helm Chartでは5つのPVC(Persistent Volume Claim)の定義が用意されています。
今回はhostPathで明示的に5つ用意することにします。

- 1GiのPVを3つ
    - jobservice
    - database
    - redis
- 5GiのPVを2つ
    - registry
    - chartmuseum

```
以下は1Giの例

...
"spec": {
    "capacity": {
      "storage": "1Gi"
    },
    "hostPath": {
      "path": "/xxxxx",
      "type": ""
    },
    "accessModes": [
      "ReadWriteOnce"
    ],
...
```

## 2) values.yamlを編集
今回は外部アクセスはNodePortで実施してみます。

- expose: typeを `nodePort`に変更
- expose: commonNameに `nodePort`を指定
- externalURL: に `https://169.62.99.180/harbor` を指定 (今回はシングルVMにK8sクラスターを構成済)

```values.yaml
expose:
  # Set the way how to expose the service. Set the type as "ingress",
  # "clusterIP" or "nodePort" and fill the information in the corresponding
  # section
  type: nodePort
　　　　...
  ...
  ...
  commonName: "nodePort"
...
...
externalURL: https://169.62.99.180/harbor
...
```
※もちろんvalues.yamlを編集せずに helm install時に `--set`フラグをつけて直接指定しても構いません。


## 3) helm installを実行
```
$ helm install --name myharbor --tls

$ helm install --name myharbor . --tls

NAME:   myharbor
LAST DEPLOYED: Sat Dec 22 08:12:57 2018
NAMESPACE: kube-system
STATUS: DEPLOYED

RESOURCES:
==> v1/Deployment
NAME                           DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
myharbor-harbor-adminserver    1        1        1           0          1s
myharbor-harbor-chartmuseum    1        0        0           0          1s
myharbor-harbor-clair          1        0        0           0          1s
myharbor-harbor-core           1        0        0           0          1s
myharbor-harbor-jobservice     1        0        0           0          1s
myharbor-harbor-notary-server  1        0        0           0          1s
myharbor-harbor-notary-signer  1        0        0           0          1s
myharbor-harbor-portal         1        0        0           0          1s
myharbor-harbor-registry       1        0        0           0          1s

==> v1/StatefulSet
NAME                      DESIRED  CURRENT  AGE
myharbor-harbor-database  1        1        1s
myharbor-harbor-redis     1        1        1s

==> v1beta1/Ingress
NAME                     HOSTS                                    ADDRESS  PORTS  AGE
myharbor-harbor-ingress  core.harbor.domain,notary.harbor.domain  80, 443  1s

==> v1/Pod(related)
NAME                                            READY  STATUS             RESTARTS  AGE
myharbor-harbor-adminserver-78458f9bdb-ctqtv    0/1    ContainerCreating  0         1s
myharbor-harbor-chartmuseum-6775b948c7-4crvg    0/1    Pending            0         1s
myharbor-harbor-clair-78c4c75949-j6z7n          0/1    ContainerCreating  0         1s
myharbor-harbor-core-5db7d4474-t82fw            0/1    ContainerCreating  0         1s
myharbor-harbor-jobservice-75865bb84b-p4tck     0/1    Pending            0         1s
myharbor-harbor-notary-server-65d7b56fd5-ghchj  0/1    ContainerCreating  0         1s
myharbor-harbor-notary-signer-dc7cf48b8-8hw45   0/1    ContainerCreating  0         1s
myharbor-harbor-portal-5499d76f78-glv68         0/1    Pending            0         1s
myharbor-harbor-database-0                      0/1    Pending            0         1s
myharbor-harbor-redis-0                         0/1    Pending            0         1s

==> v1/Secret
NAME                         TYPE               DATA  AGE
myharbor-harbor-adminserver  Opaque             4     1s
myharbor-harbor-chartmuseum  Opaque             1     1s
myharbor-harbor-core         Opaque             4     1s
myharbor-harbor-database     Opaque             1     1s
myharbor-harbor-ingress      kubernetes.io/tls  3     1s
myharbor-harbor-jobservice   Opaque             1     1s
myharbor-harbor-registry     Opaque             1     1s

==> v1/ConfigMap
NAME                           DATA  AGE
myharbor-harbor-adminserver    39    1s
myharbor-harbor-chartmuseum    24    1s
myharbor-harbor-clair          1     1s
myharbor-harbor-core           1     1s
myharbor-harbor-jobservice     1     1s
myharbor-harbor-notary-server  5     1s
myharbor-harbor-registry       2     1s

==> v1/PersistentVolumeClaim
NAME                         STATUS   VOLUME  CAPACITY  ACCESS MODES  STORAGECLASS  AGE
myharbor-harbor-chartmuseum  Pending  1s
myharbor-harbor-jobservice   Pending  1s
myharbor-harbor-registry     Pending  1s

==> v1/Service
NAME                           TYPE       CLUSTER-IP  EXTERNAL-IP  PORT(S)            AGE
myharbor-harbor-adminserver    ClusterIP  10.0.0.68   <none>       80/TCP             1s
myharbor-harbor-chartmuseum    ClusterIP  10.0.0.4    <none>       80/TCP             1s
myharbor-harbor-clair          ClusterIP  10.0.0.43   <none>       6060/TCP           1s
myharbor-harbor-core           ClusterIP  10.0.0.89   <none>       80/TCP             1s
myharbor-harbor-database       ClusterIP  10.0.0.212  <none>       5432/TCP           1s
myharbor-harbor-jobservice     ClusterIP  10.0.0.224  <none>       80/TCP             1s
myharbor-harbor-notary-server  ClusterIP  10.0.0.65   <none>       4443/TCP           1s
myharbor-harbor-notary-signer  ClusterIP  10.0.0.107  <none>       7899/TCP           1s
myharbor-harbor-portal         ClusterIP  10.0.0.164  <none>       80/TCP             1s
myharbor-harbor-redis          ClusterIP  10.0.0.108  <none>       6379/TCP           1s
myharbor-harbor-registry       ClusterIP  10.0.0.6    <none>       5000/TCP,8080/TCP  1s
```

pvを用意し忘れているとpendingになったり，K8sクラスター環境によってはセキュアに構成されていてイメージプルできない場合があります。
その場合はimagePolicyの定義を変更して`docker.io/harbor*`を許可したりなど，適宜対応してください。


# WebUIでログイン
ブラウザで`https://<IP>:30003/harbor`にアクセス
デフォルトのログイン情報は`admin/Harbor12345`

![image.png](https://qiita-image-store.s3.amazonaws.com/0/69447/27f3b7b9-5134-4947-476a-a3f38c1866a6.png)

ログイン後，以下のような画面に遷移すればOKです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/69447/3f1b9e58-e7af-cacc-0297-0de56e6d27d4.png)

# Harborの機能を試してみよう
[TBD]()


# リンク集
- [goharbor.io](https://goharbor.io/)
- [goharbor/harbor](https://github.com/goharbor/harbor)
- [Helm Chart](https://github.com/goharbor/harbor-helm)