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
爲例來爲大家說明下認證流程:
-
docker client 接收到用戶輸入的 docker login 命令,將命令轉化爲調用 engine api 的 RegistryLogin 方法
-
在 RegistryLogin 方法中通過 http 調用 registry 服務中的 auth 方法
-
因爲我們這裏使用的是 v2 版本的服務,所以會調用 loginV2 方法,在 loginV2 方法中會進行 /v2/ 接口調用,該接口會對請求進行認證
-
此時的請求中並沒有包含 token 信息,認證會失敗,返回 401 錯誤,同時會在 header 中返回去哪裏請求認證的服務器地址
-
registry client 端收到上面的返回結果後,便會去返回的認證服務器那裏進行認證請求,向認證服務器發送的請求的 header 中包含有加密的用戶名和密碼
-
認證服務器從 header 中獲取到加密的用戶名和密碼,這個時候就可以結合實際的認證系統進行認證了,比如從數據庫中查詢用戶認證信息或者對接 ldap 服務進行認證校驗
-
認證成功後,會返回一個 token 信息,client 端會拿着返回的 token 再次向 registry 服務發送請求,這次需要帶上得到的 token,請求驗證成功,返回狀態碼就是 200 了
-
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,也符合生產環境的部署方式。在安裝高可用的版本之前,我們需要如下先決條件:
-
Kubernetes 集羣 1.10+ 版本
-
Helm 2.8.0+ 版本
-
高可用的 Ingress 控制器
-
高可用的 PostgreSQL 9.6+(Harbor 不進行數據庫 HA 的部署)
-
高可用的 Redis 服務(Harbor 不處理)
-
可以跨節點或外部對象存儲共享的 PVC
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 文件即可:
-
Ingress 配置通過
expose.ingress.hosts.core
和expose.ingress.hosts.notary
-
外部 URL 通過配置
externalURL
-
外部 PostgreSQL 通過配置
database.type
爲external
,然後補充上database.external
的信息。需要我們手動創建 3 個空的數據:Harbor core
、Notary server
以及Notary signer
,Harbor 會在啓動時自動創建表結構 -
外部 Redis 通過配置
redis.type
爲external
,並填充redis.external
部分的信息。Harbor 在 2.1.0 版本中引入了 redis 的Sentinel
模式,你可以通過配置sentinel_master_set
來開啓,host 地址可以設置爲<host_sentinel1>:<port_sentinel1>,<host_sentinel2>:<port_sentinel2>,<host_sentinel3>:<port_sentinel3>
。還可以參考文檔 https://community.pivotal.io/s/article/How-to-setup-HAProxy-and-Redis-Sentinel-for-automatic-failover-between-Redis-Master-and-Slave-servers 在 Redis 前面配置一個 HAProxy 來暴露單個入口點。 -
存儲,默認情況下需要一個默認的
StorageClass
在 K8S 集羣中來自動生成 PV,用來存儲鏡像、Charts 和任務日誌。如果你想指定StorageClass
,可以通過persistence.persistentVolumeClaim.registry.storageClass
、persistence.persistentVolumeClaim.chartmuseum.storageClass
以及persistence.persistentVolumeClaim.jobservice.storageClass
進行配置,另外還需要將 accessMode 設置爲ReadWriteMany
,確保 PV 可以跨不同節點進行共享存儲。此外我們還可以通過指定存在的 PVCs 來存儲數據,可以通過existingClaim
進行配置。如果你沒有可以跨節點共享的 PVC,你可以使用外部存儲來存儲鏡像和 Chart(外部存儲支持:azure,gcs,s3 swift 和 oss),並將任務日誌存儲在數據庫中。將設置爲persistence.imageChartStorage.type
爲你要使用的值並填充相應部分並設置jobservice.jobLogger
爲database
-
副本:通過設置
portal.replicas
,core.replicas
,jobservice.replicas
,registry.replicas
,chartmuseum.replicas
,notary.server.replicas
和notary.signer.replicas
爲 n(n> = 2)
比如這裏我們將主域名配置爲 harbor.k8s.local
,通過前面的 NFS 的 StorageClass 來提供存儲(生產環境不建議使用 NFS),又因爲前面我們在安裝 GitLab 的時候就已經單獨安裝了 postgresql 和 reids 兩個數據庫,所以我們也可以配置 Harbor 使用這兩個外置的數據庫,這樣可以降低資源的使用(我們可以認爲這兩個數據庫都是 HA 模式)。但是使用外置的數據庫我們需要提前手動創建數據庫,比如我們這裏使用的 GitLab 提供的數據庫,則進入該 Pod 創建 harbor
、notary_server
、notary_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