k8s上にプライベートなコンテナイメージレジストリをデプロイする(Harbor on k8s)

Kubernetes上にプライベートなコンテナリポジトリを建てる時の自分用メモ

- https対応
    - Ingressでssl/tlsの終端を行う
- 自己署名証明書を使用
    - 証明書はcert-managerで発行&管理
- コンテナリポジトリはHarborを使用

# 前提
- 作業端末にhelmがインストール済み
- k8sクラスタ内にcert-managerがデプロイ済み
- k8sクラスタ内にIngress Controller(筆者環境ではNGINX Ingress Controllerを使用)がデプロイ済み

# 手順
## namespace作成
コンテナリポジトリ(Harbor)をデプロイするnamespaceを準備しておく
```
kubectl create ns harbor
```

## 自己署名証明書の作成
ここではcert-managerを使って作成
cert-managerを使わない場合は、opensslコマンド等でCA証明書作成 → k8s secret化という流れでも同じことができる

まずは自己署名用の認証局(CA)を作成する
cert-managerはIssuer(またはcluster-issuer)というカスタムリソースによって認証局を管理する
```bash
# 認証局(自己署名用)のマニフェスト
$ cat issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
 name: harbor-issuer
 namespace: harbor
spec:
 selfSigned: {}

# 認証局作成
$ kubectl apply -f issuer.yaml
```

つぎに作成した認証局を使ってCA証明書を発行する
cert-managerはCertificateというカスタムリソースによって証明書を管理する
```bash
# CA証明書のマニフェスト
$ cat cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: harbor-cert
  namespace: harbor
spec:
  secretName: harbor-tls
  commonName: core.harbor.yourdomain.com #後ほど作成するingressのホスト名と合わせておく
  dnsNames:
    - core.harbor.yourdomain.com #後ほど作成するingressのホスト名と合わせておく
  issuerRef:
    name: harbor-issuer # 前の手順で作成した認証局の名前を指定
    kind: Issuer
    group: cert-manager.io

# 証明書作成
$ kubectl apply -f cert.yaml
```

Certificateリソースのspec内で指定した名前で、CA証明書を含むsecretリソースが作成される
```bash
$ kubectl get secret -n harbor harbor-tls
NAME         TYPE                DATA   AGE
harbor-tls   kubernetes.io/tls   3      65m
```
## Harborのデプロイ
ここではhelmを使ってデプロイする。[こちらのドキュメント](https://goharbor.io/docs/1.10/install-config/harbor-ha-helm/)を参照。

```bash
# helmチャートをダウンロードして展開
$ helm repo add harbor https://helm.goharbor.io
$ helm fetch harbor/harbor --untar
$ cd harbor

# パラメータファイルを編集
$ cp values.yaml values-custom.yaml
$ vim values-custom.yaml
$ diff values.yaml values-custom.yaml
19c19
<     certSource: auto
---
>     certSource: secret # 前の手順にてcert-managerで作成した証明書(secretリソース)を利用するため、"secret"を指定
28c28
<       secretName: ""
---
>       secretName: "harbor-tls" #Certificateリソースによって作成されたsecretリソース名
36,37c36,37
<       core: core.harbor.domain
<       notary: notary.harbor.domain
---
>       core: core.harbor.yourdomain.com #自環境のドメイン名に書き換え(CertificateリソースのdnsNameに合わせる)
>       notary: notary.harbor.yourdomain.com　#自環境のドメイン名に書き換え
47c47
<     className: ""
---
>     className: "nginx" #自環境におけるIngress Classの名称に書き換え
127c127
< externalURL: https://core.harbor.domain
---
> externalURL: https://core.harbor.yourdomain.com #自環境のドメイン名に書き換え(CertificateリソースのdnsNameに合わせる)

# helmを使用してHarborをインストール
$ helm install my-harbor . -n harbor -f values-custom.yaml

# 各podの稼働確認
$ kubectl get pod -n harbor
NAME                                       READY   STATUS    RESTARTS       AGE
my-harbor-chartmuseum-765c5f5845-vq6v8     1/1     Running   0              143m
my-harbor-core-7f6fbdcb7d-82jkm            1/1     Running   0              143m
my-harbor-database-0                       1/1     Running   0              143m
my-harbor-jobservice-6f5dc8655b-drkv9      1/1     Running   3 (143m ago)   143m
my-harbor-notary-server-697b5d79d9-m4kfm   1/1     Running   1 (143m ago)   143m
my-harbor-notary-signer-788cf6c74c-tjq9t   1/1     Running   1 (143m ago)   143m
my-harbor-portal-97c89d9b4-tfp5r           1/1     Running   0              143m
my-harbor-redis-0                          1/1     Running   0              143m
my-harbor-registry-5886497567-gnltv        2/2     Running   0              143m
my-harbor-trivy-0                          1/1     Running   0              143m

# ingressリソースの確認
$ kubectl get ingress -n harbor
NAME                       CLASS   HOSTS                         ADDRESS          PORTS     AGE
my-harbor-ingress          nginx   core.harbor.yourdomain.com     10.128.213.144   80, 443   144m
my-harbor-ingress-notary   nginx   notary.harbor.yourdomain.com   10.128.213.144   80, 443   144m
```

デプロイ完了後、https://(my-harbor-ingressのホスト名)にアクセスすると、HarborのGUIが表示される。初期パスはadmin/Harbor12345。
![スクリーンショット 2023-02-20 13.37.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436161/3e14f675-c5cc-73ab-6627-7a4d9d6791a6.png)


## 証明書のインストール
この時点でdockerコマンドでのリポジトリへのアクセスや、k8sノードからのイメージプルを試みるとX509エラーが出力される。
```bash
$ sudo docker login core.harbor.yourdomain.com
Username: admin
Password:
Error response from daemon: Get "https://core.harbor.yourdomain.com/v2/": x509: certificate signed by unknown authority
```

回避するために下記のマシンにCA証明書をインストールする。

- docker pushを実行するクライアント
- k8sクラスタ内の各ノード

手順はOSによって異なるが今回の環境では全てUbuntu 20.04 LTSを使用している。

まずはcert-managerで管理されているCA証明書をエクスポートする

``` bash
# k8s secretからCA証明書をエクスポート
$ kubectl get secret -n harbor harbor-tls -o jsonpath='{.data.ca\.crt}' | base64 -d > harbor.crt
```

エクスポートしたCA証明書をSCP等で各マシンにコピーした上で、以下を実施
```bash
# 証明書を格納
# 格納先は/usr/share/ca-certificatesではないことに注意
$ cp harbor.crt /usr/local/share/ca-certificates

# 証明書を適用
$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.

# dockerデーモン再起動
$ sudo snap stop docker
$ sudo snap start docker
```

これで設定は完了。あとは適宜リポジトリへのアクセス確認などを行う。
```bash
$ sudo docker login core.harbor.yourdomain.com
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /root/snap/docker/2343/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
```