Harbor 結合 Traefik 的 HA 安裝配置

Harbor 是一個 CNCF 基金會託管的開源的可信的雲原生 docker registry 項目,可以用於存儲、簽名、掃描鏡像內容,Harbor 通過添加一些常用的功能如安全性、身份權限管理等來擴展 docker registry 項目,此外還支持在 registry 之間複製鏡像,還提供更加高級的安全功能,如用戶管理、訪問控制和活動審計等,在新版本中還添加了 Helm 倉庫託管的支持。

Harbor 最核心的功能就是給 docker registry 添加上一層權限保護的功能,要實現這個功能,就需要我們在使用 docker login、pull、push 等命令的時候進行攔截,先進行一些權限相關的校驗,再進行操作,其實這一系列的操作 docker registry v2 就已經爲我們提供了支持,v2 集成了一個安全認證的功能,將安全認證暴露給外部服務,讓外部服務去實現。

Harbor 認證原理

上面我們說了 docker registry v2 將安全認證暴露給了外部服務使用,那麼是怎樣暴露的呢?我們在命令行中輸入 docker login https://registry.qikqiak.com 爲例來爲大家說明下認證流程:

  1. docker client 接收到用戶輸入的 docker login 命令,將命令轉化爲調用 engine api 的 RegistryLogin 方法

  2. 在 RegistryLogin 方法中通過 http 調用 registry 服務中的 auth 方法

  3. 因爲我們這裏使用的是 v2 版本的服務,所以會調用 loginV2 方法,在 loginV2 方法中會進行 /v2/ 接口調用,該接口會對請求進行認證

  4. 此時的請求中並沒有包含 token 信息,認證會失敗,返回 401 錯誤,同時會在 header 中返回去哪裏請求認證的服務器地址

  5. registry client 端收到上面的返回結果後,便會去返回的認證服務器那裏進行認證請求,向認證服務器發送的請求的 header 中包含有加密的用戶名和密碼

  6. 認證服務器從 header 中獲取到加密的用戶名和密碼,這個時候就可以結合實際的認證系統進行認證了,比如從數據庫中查詢用戶認證信息或者對接 ldap 服務進行認證校驗

  7. 認證成功後,會返回一個 token 信息,client 端會拿着返回的 token 再次向 registry 服務發送請求,這次需要帶上得到的 token,請求驗證成功,返回狀態碼就是 200 了

  8. docker client 端接收到返回的 200 狀態碼,說明操作成功,在控制檯上打印 Login Succeeded 的信息 至此,整個登錄過程完成,整個過程可以用下面的流程圖來說明:

要完成上面的登錄認證過程有兩個關鍵點需要注意:怎樣讓 registry 服務知道服務認證地址?我們自己提供的認證服務生成的 token 爲什麼 registry 就能夠識別?

對於第一個問題,比較好解決,registry 服務本身就提供了一個配置文件,可以在啓動 registry 服務的配置文件中指定上認證服務地址即可,其中有如下這樣的一段配置信息:

......
auth:
  token:
    realm: token-realm
    service: token-service
    issuer: registry-token-issuer
    rootcertbundle: /root/certs/bundle
......

其中 realm 就可以用來指定一個認證服務的地址,下面我們可以看到 Harbor 中該配置的內容。

關於 registry 的配置,可以參考官方文檔:https://docs.docker.com/registry/configuration/

第二個問題,就是 registry 怎麼能夠識別我們返回的 token 文件?如果按照 registry 的要求生成一個 token,是不是 registry 就可以識別了?所以我們需要在我們的認證服務器中按照 registry 的要求生成 token,而不是隨便亂生成。那麼要怎麼生成呢?我們可以在 docker registry 的源碼中可以看到 token 是通過 JWT(JSON Web Token) 來實現的,所以我們按照要求生成一個 JWT 的 token 就可以了。

對 golang 熟悉的同學可以去 clone 下 Harbor 的代碼查看下,Harbor 採用 beego 這個 web 開發框架,源碼閱讀起來不是特別困難。我們可以很容易的看到 Harbor 中關於上面我們講解的認證服務部分的實現方法。

安裝

Harbor 涉及的組件比較多,我們可以使用 Helm 來安裝一個高可用版本的 Harbor,也符合生產環境的部署方式。在安裝高可用的版本之前,我們需要如下先決條件:

Harbor 的大部分組件都是無狀態的,所以我們可以簡單增加 Pod 的副本,保證組件儘量分佈到多個節點上即可,在存儲層,需要我們自行提供高可用的 PostgreSQL、Redis 集羣來存儲應用數據,以及存儲鏡像和 Helm Chart 的 PVC 或對象存儲。

首先添加 Chart 倉庫地址:

# 添加 Chart 倉庫
helm repo add harbor https://helm.goharbor.io
# 更新
helm repo update
# 拉取1.6.2版本並解壓
helm pull harbor/harbor --untar --version 1.6.2

在安裝 Harbor 的時候有很多可以配置的參數,可以在 harbor-helm 項目上進行查看,在安裝的時候我們可以通過 --set 指定參數或者 values.yaml 直接編輯 Values 文件即可:

比如這裏我們將主域名配置爲 harbor.k8s.local,通過前面的 NFS 的 StorageClass 來提供存儲(生產環境不建議使用 NFS),又因爲前面我們在安裝 GitLab 的時候就已經單獨安裝了 postgresql 和 reids 兩個數據庫,所以我們也可以配置 Harbor 使用這兩個外置的數據庫,這樣可以降低資源的使用(我們可以認爲這兩個數據庫都是 HA 模式)。但是使用外置的數據庫我們需要提前手動創建數據庫,比如我們這裏使用的 GitLab 提供的數據庫,則進入該 Pod 創建 harbornotary_servernotary_signer 這 3 個數據庫:

$ kubectl get pods -n kube-ops -l name=postgresql
NAME                          READY   STATUS    RESTARTS   AGE
postgresql-566846fd86-9kps9   1/1     Running   1          2d
$ kubectl exec -it postgresql-566846fd86-9kps9 /bin/bash -n kube-ops
root@postgresql-566846fd86-9kps9:/var/lib/postgresql# sudo su - postgres
postgres@postgresql-566846fd86-9kps9:~$ psql
psql (12.3 (Ubuntu 12.3-1.pgdg18.04+1))
Type "help" for help.

postgres=# CREATE DATABASE harbor OWNER postgres;  # 創建 harbor 數據庫
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE harbor to postgres;  # 授權給 postgres 用戶
GRANT
postgres=# GRANT ALL PRIVILEGES ON DATABASE harbor to gitlab;  # 授權給 gitlab 用戶
GRANT
# Todo: 用同樣的方式創建其他兩個數據庫:notary_server、notary_signer
......
postgres-# \q  # 退出

數據庫準備過後,就可以使用我們自己定製的 values 文件來進行安裝了,完整的定製的 values 文件如下所示:

# values-prod.yaml
externalURL: https://harbor.k8s.local
harborAdminPassword: Harbor12345
logLevel: debug

expose:
  type: ingress
  tls:
    enabled: true
  ingress:
    hosts:
      core: harbor.k8s.local
      notary: notary.k8s.local
    annotations:
      # 因爲我們使用的 Traefik2.x 作爲 Ingress 控制器
      kubernetes.io/ingress.class: traefik
      traefik.ingress.kubernetes.io/router.entrypoints: websecure
      traefik.ingress.kubernetes.io/router.tls: "true"

persistence:
  enabled: true
  resourcePolicy: "keep"
  persistentVolumeClaim:
    registry:
      # 如果需要做高可用,多個副本的組件則需要使用支持 ReadWriteMany 的後端
      # 這裏我們使用nfs,生產環境不建議使用nfs
      storageClass: "nfs-storage"
      # 如果是高可用的,多個副本組件需要使用 ReadWriteMany,默認爲 ReadWriteOnce
      accessMode: ReadWriteMany
      size: 5Gi
    chartmuseum:
      storageClass: "nfs-storage"
      accessMode: ReadWriteMany
      size: 5Gi
    jobservice:
      storageClass: "nfs-storage"
      accessMode: ReadWriteMany
      size: 1Gi
    trivy:
      storageClass: "nfs-storage"
      accessMode: ReadWriteMany
      size: 2Gi

database:
  type: external
  external:
    host: "postgresql.kube-ops.svc.cluster.local"
    port: "5432"
    username: "gitlab"
    password: "passw0rd"
    coreDatabase: "harbor"
    notaryServerDatabase: "notary_server"
    notarySignerDatabase: "notary_signer"

redis:
  type: external
  external:
    addr: "redis.kube-ops.svc.cluster.local:6379"

# 默認爲一個副本,如果要做高可用,只需要設置爲 replicas >= 2 即可
portal:
  replicas: 1
core:
  replicas: 1
jobservice:
  replicas: 1
registry:
  replicas: 1
chartmuseum:
  replicas: 1
trivy:
  replicas: 1
notary:
  server:
    replicas: 1
  signer:
    replicas: 1

由於我們這裏使用的 Ingress 控制器是 traefik2.x 版本,在配置 Ingress 的時候,我們需要重新配置 annotations(如果你使用的是其他 Ingress 控制器,請參考具體的使用方式)。這些配置信息都是根據 Harbor 的 Chart 包默認的 values 值進行覆蓋的,現在我們直接安裝即可:

cd harbor
$ helm upgrade --install harbor . -f values-prod.yaml -n kube-ops
Release "harbor" does not exist. Installing it now.
NAME: harbor
LAST DEPLOYED: Sat May 29 16:22:13 2021
NAMESPACE: kube-ops
STATUS: deployed
REVISION: 1
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.k8s.local
For more details, please visit https://github.com/goharbor/harbor

正常情況下隔一會兒就可以安裝成功了:

$ helm ls -n kube-ops
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
harbor  kube-ops        1               2021-05-29 16:22:13.495394 +0800 CST    deployed        harbor-1.6.2    2.2.2
$ kubectl get pods -n kube-ops -l app=harbor
NAME                                           READY   STATUS    RESTARTS   AGE
harbor-harbor-chartmuseum-6996f677d4-dhkgd     1/1     Running   0          91s
harbor-harbor-core-84b8db479-rpwml             1/1     Running   0          91s
harbor-harbor-jobservice-5584dccd6-gpv79       1/1     Running   0          91s
harbor-harbor-notary-server-7d79b7d46d-dlgt7   1/1     Running   0          91s
harbor-harbor-notary-signer-69d8fdd476-7pt44   1/1     Running   0          91s
harbor-harbor-portal-559c4d4bfd-8x2pp          1/1     Running   0          91s
harbor-harbor-registry-758f67dbbb-nl729        2/2     Running   0          91s
harbor-harbor-trivy-0                          1/1     Running   0          50s

安裝完成後,我們就可以將域名 harbor.k8s.local 解析到 Ingress Controller 所在的節點,我們這裏使用的仍然是 Traefik,由於我們開啓了 KubernetesIngress 支持的,所以我們只需要將域名解析到 Traefik 的 Pod 所在節點即可,然後就可以通過該域名在瀏覽器中訪問了:

$ kubectl get ingress -n kube-ops
NAME                           CLASS    HOSTS              ADDRESS   PORTS     AGE
harbor-harbor-ingress          <none>   harbor.k8s.local             80, 443   115s
harbor-harbor-ingress-notary   <none>   notary.k8s.local             80, 443   115s

用戶名使用默認的 admin,密碼則是上面配置的默認 Harbor12345,需要注意的是要使用 https 進行訪問,否則登錄可能提示用戶名或密碼錯誤:

但是這裏也需要注意的是,由於我們這裏使用的 traefik2.x 版本的 Ingress 控制器,所以對於 Ingress 資源的支持不是很友好,由於我們添加了 traefik.ingress.kubernetes.io/router.tls: "true" 這個註解,導致我們的 http 服務又失效了,爲了解決這個問題,我們這裏手動來創建一個 http 版本的 Ingress 對象:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    meta.helm.sh/release-name: harbor
    meta.helm.sh/release-namespace: kube-ops
    traefik.ingress.kubernetes.io/router.entrypoints: web
    traefik.ingress.kubernetes.io/router.middlewares: kube-system-redirect-https@kubernetescrd
  labels:
    app: harbor
    app.kubernetes.io/managed-by: Helm
    chart: harbor
    heritage: Helm
    release: harbor
  name: harbor-harbor-ingress-http
  namespace: kube-ops
spec:
  rules:
  - host: harbor.k8s.local
    http:
      paths:
      - backend:
          serviceName: harbor-harbor-portal
          servicePort: 80
        path: /
        pathType: Prefix
      - backend:
          serviceName: harbor-harbor-core
          servicePort: 80
        path: /api
        pathType: Prefix
      - backend:
          serviceName: harbor-harbor-core
          servicePort: 80
        path: /service
        pathType: Prefix
      - backend:
          serviceName: harbor-harbor-core
          servicePort: 80
        path: /v2
        pathType: Prefix
      - backend:
          serviceName: harbor-harbor-core
          servicePort: 80
        path: /chartrepo
        pathType: Prefix
      - backend:
          serviceName: harbor-harbor-core
          servicePort: 80
        path: /c
        pathType: Prefix
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: traefik
    meta.helm.sh/release-name: harbor
    meta.helm.sh/release-namespace: kube-ops
    traefik.ingress.kubernetes.io/router.entrypoints: web
    traefik.ingress.kubernetes.io/router.middlewares: kube-system-redirect-https@kubernetescrd
  labels:
    app: harbor
    app.kubernetes.io/managed-by: Helm
    chart: harbor
    heritage: Helm
    release: harbor
  name: harbor-harbor-ingress-notary-http
  namespace: kube-ops
spec:
  rules:
  - host: notary.k8s.local
    http:
      paths:
      - backend:
          serviceName: harbor-harbor-notary-server
          servicePort: 4443
        path: /
        pathType: Prefix

爲了讓能夠跳轉到 https,我們還需要創建如下所示的一個 Middleware(如果你使用的是其他 Ingress 控制器,請參考具體的使用方式):

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: kube-system
spec:
  redirectScheme:
    scheme: https

需要注意的是在 Ingress 的 annotations 中配置中間件的格式爲 <namespace>-redirect-https@kubernetescrd

登錄過後即可進入 Harbor 的 Dashboard 頁面:

我們可以看到有很多功能,默認情況下會有一個名叫 library 的項目,該項目默認是公開訪問權限的,進入項目可以看到裏面還有 Helm Chart 包的管理,可以手動在這裏上傳,也可以對該項目裏面的鏡像進行一些其他配置。

推送鏡像

現在我們來測試下使用 docker cli 來進行 pull/push 鏡像,直接使用 docker login 命令登錄:

$ docker login harbor.k8s.local
Username: admin
Password:
Error response from daemon: Get https://harbor.k8s.local/v2/: x509: certificate signed by unknown authority

可以看到會登錄失敗,這是因爲在使用 docker login 登錄的時候會使用 https 的服務,而我們這裏是自簽名的證書,所以就報錯了,我們可以將使用到的 CA 證書文件複製到 /etc/docker/certs.d/harbor.k8s.local 目錄下面來解決這個問題(如果該目錄不存在,則創建它)。ca.crt 這個證書文件我們可以通過 Ingress 中使用的 Secret 資源對象來提供:

$ kubectl get secret harbor-harbor-ingress -n kube-ops -o yaml
apiVersion: v1
data:
  ca.crt: <ca.crt>
  tls.crt: <tls.crt>
  tls.key: <tls.key>
kind: Secret
metadata:
  ......
  name: harbor-harbor-ingress
  namespace: kube-ops
  resourceVersion: "450460"
  selfLink: /api/v1/namespaces/kube-ops/secrets/harbor-harbor-ingress
  uid: 0c44425c-8258-407a-a0a7-1c7e50d29404
type: kubernetes.io/tls

其中 data 區域中 ca.crt 對應的值就是我們需要證書,不過需要注意還需要做一個 base64 的解碼,這樣證書配置上以後就可以正常訪問了。

不過由於上面的方法較爲繁瑣,所以一般情況下面我們在使用 docker cli 的時候是在 docker 啓動參數後面添加一個 --insecure-registry 參數來忽略證書的校驗的,在 docker 啓動配置文件 /usr/lib/systemd/system/docker.service 中修改 ExecStart 的啓動參數:

ExecStart=/usr/bin/dockerd --insecure-registry harbor.k8s.local

或者在 Docker Daemon 的配置文件中添加:

$ cat /etc/docker/daemon.json
{
  "insecure-registries" : [
    "harbor.k8s.local"
  ],
  "registry-mirrors" : [
    "https://ot2k4d59.mirror.aliyuncs.com/"
  ]
}

然後保存重啓 docker,再使用 docker cli 就沒有任何問題了:

$ docker login harbor.k8s.local
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

比如我們本地現在有一個名爲 busybox:1.28.4 的鏡像,現在我們想要將該鏡像推送到我們的私有倉庫中去,應該怎樣操作呢?首先我們需要給該鏡像重新打一個具有 harbor.k8s.local 前綴的鏡像,然後推送的時候就可以識別到推送到哪個鏡像倉庫:

$ docker tag busybox:1.28.4 harbor.k8s.local/library/busybox:1.28.4
$ docker push harbor.k8s.local/library/busybox:1.28.4
The push refers to repository [harbor.k8s.local/library/busybox]
432b65032b94: Pushed
1.28.4: digest: sha256:74f634b1bc1bd74535d5209589734efbd44a25f4e2dc96d78784576a3eb5b335 size: 527

推送完成後,我們就可以在 Portal 頁面上看到這個鏡像的信息了:

鏡像 push 成功,同樣可以測試下 pull:

$ docker rmi harbor.k8s.local/library/busybox:1.28.4
Untagged: harbor.k8s.local/library/busybox:1.28.4
Untagged: harbor.k8s.local/library/busybox@sha256:74f634b1bc1bd74535d5209589734efbd44a25f4e2dc96d78784576a3eb5b335
$ docker rmi busybox:1.28.4
Untagged: busybox:1.28.4
Untagged: busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
Deleted: sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472a
Deleted: sha256:432b65032b9466b4dadcc5c7b11701e71d21c18400aae946b101ad16be62333a
$ docker pull harbor.k8s.local/library/busybox:1.28.4
1.28.4: Pulling from library/busybox
07a152489297: Pull complete
Digest: sha256:74f634b1bc1bd74535d5209589734efbd44a25f4e2dc96d78784576a3eb5b335
Status: Downloaded newer image for harbor.k8s.local/library/busybox:1.28.4
harbor.k8s.local/library/busybox:1.28.4

$ docker images |grep busybox
harbor.k8s.local/library/busybox                       1.28.4     8c811b4aec35   3 years ago     1.15MB

到這裏證明上面我們的私有 docker 倉庫搭建成功了,大家可以嘗試去創建一個私有的項目,然後創建一個新的用戶,使用這個用戶來進行 pull/push 鏡像,Harbor 還具有其他的一些功能,比如鏡像複製,Helm Chart 包託管等等,大家可以自行測試,感受下 Harbor 和官方自帶的 registry 倉庫的差別。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/LGI7ZWSK8YGzPVddJxcAUA