---
# System prepended metadata

title: 在 k8s 安裝 harbor，並透過 cert-manager 自簽憑證透過 istio ingressgateway 存取
tags: [工具]

---

# 在 k8s 安裝 harbor，並透過 cert-manager 自簽憑證透過 istio ingressgateway 存取


## 環境準備

1. 已安裝好 istio 和 cert-manager

```
$ kubectl -n istio-system get pod
NAME                                    READY   STATUS    RESTARTS   AGE
istio-cni-node-5kmfh                    1/1     Running   0          19m
istio-cni-node-975fn                    1/1     Running   0          19m
istio-cni-node-9jqsq                    1/1     Running   0          19m
istio-cni-node-9n8s6                    1/1     Running   0          19m
istio-cni-node-d5d4z                    1/1     Running   0          19m
istio-ingressgateway-5b6548c6d6-n6x64   1/1     Running   0          11m
istiod-d498fb64c-d9w5x                  1/1     Running   0          19m
jaeger-76444674f-s2nqj                  1/1     Running   0          10m
kiali-6d774d8bb8-6n6zj                  1/1     Running   0          16m
ztunnel-dc56s                           1/1     Running   0          18m
ztunnel-f4t5s                           1/1     Running   0          18m
ztunnel-mfxxn                           1/1     Running   0          18m
ztunnel-vrbs2                           1/1     Running   0          18m
ztunnel-w4xkp                           1/1     Running   0          18m

$ kubectl get pods --namespace cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-58dd99f969-w2tz2              1/1     Running   0          20m
cert-manager-cainjector-55cd9f77b5-5lkh8   1/1     Running   0          20m
cert-manager-webhook-7987476d56-j8r8z      1/1     Running   0          20m
```

2. 已安裝好 csi driver
```
$ kubectl get sc
NAME      PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-csi   nfs.csi.k8s.io   Retain          Immediate           false                  4s
```
## 建立 harbor namespace

* 在 `harbor` namespace 設定 `istio.io/dataplane-mode=ambient` label
```
$ kubectl create ns harbor

$ kubectl label namespace harbor istio.io/dataplane-mode=ambient
```


## 使用 cert-manager 自簽憑證
### 1. 先建立 SelfSigned Issuer → 再用它簽一個 CA → 用 CA 建一個 ClusterIssuer

```
$ nano cert.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
  namespace: cert-manager
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: root-ca
  namespace: cert-manager
spec:
  isCA: true
  duration: 87600h            # 10 years
  renewBefore: 720h           # 30d
  commonName: root-ca
  secretName: root-ca-secret
  issuerRef:
    name: selfsigned-issuer
    kind: Issuer
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: istio-ca-clusterissuer
spec:
  ca:
    secretName: root-ca-secret
```

* 部署以及檢查資源
```
$ kubectl apply -f cert.yaml

$ kubectl -n cert-manager get Issuer
NAME                READY   AGE
selfsigned-issuer   True    32s

$ kubectl -n cert-manager get Certificate
NAME      READY   SECRET           AGE
root-ca   True    root-ca-secret   8s

$ kubectl get ClusterIssuer
NAME                     READY   AGE
istio-ca-clusterissuer   True    13s

$ kubectl -n cert-manager get secret root-ca-secret
NAME             TYPE                DATA   AGE
root-ca-secret   kubernetes.io/tls   3      32s
```
### 2. 建立 Harbor 憑證
* 使用 `istio-ca-clusterissuer` 這個 ClusterIssuer 來簽發憑證，憑證簽發後會把 公鑰和私鑰放在 `nginx-cert-tls` secret。
* `dnsNames:` 就是 憑證的 主體名稱（SANs），也就是 Harbor 服務對外的 domain，也需要將名稱解析到 istio ingressgateway 的 svc ip。
```
$ nano harbor-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: harbor-cert
  namespace: istio-system
spec:
  duration: 8760h             # 1 year
  renewBefore: 720h           # 30d
  secretName: harbor-cert-tls
  dnsNames:
    - "*.myk8s.com"
  issuerRef:
    name: istio-ca-clusterissuer
    kind: ClusterIssuer

$ kubectl apply -f harbor-cert.yaml

$ kubectl -n istio-system get Certificate harbor-cert
NAME          READY   SECRET            AGE
harbor-cert   True    harbor-cert-tls   24s

$ kubectl -n istio-system get secret harbor-cert-tls
NAME              TYPE                DATA   AGE
harbor-cert-tls   kubernetes.io/tls   3      32
```

## 安裝 harbor
```
$ helm repo add harbor https://helm.goharbor.io

$ helm search repo harbor/harbor --versions

# 指定 helm chart 版本
$ helm fetch harbor/harbor --version 1.17.1 --untar
```
* 編輯 harbor chart 的 values.yaml
  - `expose.type` 修改為 clusterIP。
  - `expose.type.tls.enabled` 修改為 false。
  - `externalURL` 設定 harbor 對外的 domain。
  - 在 `persistence` 以下都需設定自己的 storageclass。
```
$ nano harbor/values.yaml
expose:
  # Set how to expose the service. Set the type as "ingress", "clusterIP", "nodePort" or "loadBalancer"
  # and fill the information in the corresponding section
  type: clusterIP
......
  tls:
    enabled: false
......
externalURL: https://harbor.myk8s.com
......
persistence:
  enabled: true
  # Setting it to "keep" to avoid removing PVCs during a helm delete
  # operation. Leaving it empty will delete PVCs after the chart deleted
  # (this does not apply for PVCs that are created for internal database
  # and redis components, i.e. they are never deleted automatically)
  resourcePolicy: "keep"
  persistentVolumeClaim:
    registry:
      # Use the existing PVC which must be created manually before bound,
      # and specify the "subPath" if the PVC is shared with other components
      existingClaim: ""
      # Specify the "storageClass" used to provision the volume. Or the default
      # StorageClass will be used(the default).
      # Set it to "-" to disable dynamic provisioning
      storageClass: "nfs-csi"
      subPath: ""
      accessMode: ReadWriteOnce
      size: 300Gi
    chartmuseum:
      existingClaim: ""
      storageClass: "nfs-csi"
      subPath: ""
      accessMode: ReadWriteOnce
      size: 5Gi
    jobservice:
      existingClaim: ""
      storageClass: "nfs-csi"
      subPath: ""
      accessMode: ReadWriteOnce
      size: 1Gi
    # If external database is used, the following settings for database will
    # be ignored
    database:
      existingClaim: ""
      storageClass: "nfs-csi"
      subPath: ""
      accessMode: ReadWriteOnce
      size: 1Gi
    # If external Redis is used, the following settings for Redis will
    # be ignored
    redis:
      existingClaim: ""
      storageClass: "nfs-csi"
      subPath: ""
      accessMode: ReadWriteOnce
      size: 1Gi
    trivy:
      existingClaim: ""
      storageClass: "nfs-csi"
      subPath: ""
      accessMode: ReadWriteOnce
      size: 5Gi
```
```
$ helm install harbor harbor/ -n harbor
```
```
$ kubectl -n harbor get pod
NAME                                 READY   STATUS    RESTARTS      AGE
harbor-core-848b4d75dd-9xskw         1/1     Running   3 (97s ago)   2m4s
harbor-database-0                    1/1     Running   0             2m4s
harbor-jobservice-778d59bd67-lpdjq   1/1     Running   0             28s
harbor-nginx-8549449f48-9fgsz        1/1     Running   0             2m4s
harbor-portal-5b6b5f7494-9n7pd       1/1     Running   0             2m4s
harbor-redis-0                       1/1     Running   0             2m4s
harbor-registry-584776c6dd-kslvc     2/2     Running   0             2m4s
harbor-trivy-0                       1/1     Running   0             2m4s
```



## 設定 istio 
### 建立 Istio Gateway 與 VirtualService
* 設定 Istio 的流量路由，目標是讓外部流量透過 HTTPS 進入 Istio Ingress Gateway，然後轉發到內部 harbor 的 `harbor-portal` Service。


```
$ nano harbor-istio.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: harbor-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: harbor-cert-tls  # 對應憑證 Secret
    hosts:
    - harbor.myk8s.com
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: harbor-vs
  namespace: istio-system
spec:
  hosts:
  - harbor.myk8s.com
  gateways:
  - harbor-gateway
  http:
  - match:
    - uri:
        prefix: /api/
    route:
    - destination:
        host: harbor-core.harbor.svc.cluster.local
        port:
          number: 80
  - match:
    - uri:
        prefix: /service/
    route:
    - destination:
        host: harbor-core.harbor.svc.cluster.local
        port:
          number: 80
  - match:
    - uri:
        prefix: /v2/
    route:
    - destination:
        host: harbor-core.harbor.svc.cluster.local
        port:
          number: 80
  - match:
    - uri:
        prefix: /c/
    route:
    - destination:
        host: harbor-core.harbor.svc.cluster.local
        port:
          number: 80
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: harbor-portal.harbor.svc.cluster.local
        port:
          number: 80


$ kubectl apply -f harbor-istio.yaml
```

```
$ kubectl -n istio-system get gateway.networking
NAME             AGE
harbor-gateway   14s

$ kubectl -n istio-system get virtualservice
NAME        GATEWAYS             HOSTS                  AGE
harbor-vs   ["harbor-gateway"]   ["harbor.myk8s.com"]   30s
```

## 將 ca.pem 匯入到每一個 client 上


* 將 `ca.pem` 匯入到瀏覽器
```
$ kubectl -n cert-manager get secret root-ca-secret -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.pem
```

* ssh 進入到 client 並且把 ca.pem 複製到 OS
```
$ sudo cp ca.pem /usr/local/share/ca-certificates/harbor.crt

$ sudo update-ca-certificates

# 把憑證塞入 docker 就可以使用 ca login
$ sudo mkdir /etc/docker/certs.d

$ sudo cp ca.pem /etc/docker/certs.d/

$ sudo systemctl daemon-reload

$ sudo systemctl restart docker
```