在Kubernetes集群上部署高可用Harbor镜像仓库
- 这里主要介绍使用kubectl部署Harbor镜像仓库到Kubernetes集群中。
- 使用 ofollow,noindex" target="_blank">Helm 部署,参考:
关于 基于Harbor的高可用私有镜像仓库 ,在我的博客里 曾不止一次提到 ,在 源创会2017沈阳站 上,我还专门 以此题目和大家做了分享 。事后,很多人通过微博私信、 个人公众号 或博客评论问我是否可以在 Kubernetes集群 上安装高可用的 Harbor 仓库,今天我就用这篇文章来回答大家这个问题。
一、Kubernetes上的高可用Harbor方案
首先,我可以肯定给出一个回答:Harbor支持在Kubernetes部署。只不过Harbor官方的默认安装并非是高可用的,而是“单点式”的。在 《基于Harbor的高可用企业级私有容器镜像仓库部署实践》 一文中,我曾谈到了一种在裸机或VM上的、基于 Cephfs 共享存储的高可用Harbor方案。在Kubernetes上部署,其高可用的思路也是类似的,可见下面这幅示意图:

围绕这幅示意图,简单说明一下我们的方案:
- 通过在Kubernetes上启动Harbor内部各组件的多个副本的方式实现Harbor服务的计算高可用;
- 通过挂载CephFS共享存储的方式实现镜像数据高可用;
- Harbor使用的配置数据和关系数据放在外部(External)数据库集群中,保证数据高可用和实时一致性;
- 通过外部 Redis 集群实现UI组件的session共享。
方案确定后,接下来我们就开始部署。
二、环境准备
在Harbor官方的 对Kubernetes支持的说明 中,提到当前的Harbor on kubernetes相关脚本和配置在Kubernetes v1.6.5和Harbor v1.2.0上验证测试通过了,因此在我们的实验环境中, Kubernetes 至少要准备v1.6.5及以后版本。下面是我的环境的一些信息:
Kubernetes使用v1.7.3版本: # kubelet --version Kubernetes v1.7.3 Docker使用17.03.2版本: # docker version Client: Version: 17.03.2-ce API version: 1.27 Go version: go1.7.5 Git commit: f5ec1e2 Built: Tue Jun 27 03:35:14 2017 OS/Arch: linux/amd64 Server: Version: 17.03.2-ce API version: 1.27 (minimum version 1.12) Go version: go1.7.5 Git commit: f5ec1e2 Built: Tue Jun 27 03:35:14 2017 OS/Arch: linux/amd64 Experimental: false
关于Harbor的相关脚本,我们直接用master branch中的,而不是v1.2.0这个release版本中的。 切记 !否则你会发现v1.2.0版本源码中的相关kubernetes支持脚本根本就没法工作,甚至缺少adminserver组件的相关脚本。不过Harbor相关组件的image版本,我们使用的还是 v1.2.0 的:
Harbor源码的版本: commit 82d842d77c01657589d67af0ea2d0c66b1f96014 Merge pull request #3741 from wy65701436/add-tc-concourse on Dec 4, 2017 Harbor各组件的image的版本: REPOSITORY TAG IMAGE ID vmware/harbor-jobservice v1.2.0 1fb18427db11 vmware/harbor-ui v1.2.0 b7069ac3bd4b vmware/harbor-adminserver v1.2.0 a18331f0c1ae vmware/registry 2.6.2-photon c38af846a0da vmware/nginx-photon 1.11.13 2971c92cc1ae
除此之外,高可用Harbor使用外部的DB cluster和redis cluster,DB cluster我们采用MySQL,对于MySQL cluster,可以使用 mysql galera cluster 或MySQL5.7以上版本自带的Group Replication (MGR) 集群。
三、探索harbor on k8s部署脚本和配置
我们在本地创建harbor-install-on-k8s目录,并将Harbor最新源码下载到该目录下:
# mkdir harbor-install-on-k8s # cd harbor-install-on-k8s # wget -c https://github.com/vmware/harbor/archive/master.zip # unzip master.zip # cd harbor-master # ls -F AUTHORS CHANGELOG.md contrib/ CONTRIBUTING.md docs/ LICENSE make/ Makefile NOTICE partners.md README.md ROADMAP.md src/ tests/ tools/ VERSION
将Harbor部署到k8s上的脚本就在make/kubernetes目录下:
# cd harbor-master/make # tree kubernetes kubernetes ├── adminserver │ ├── adminserver.rc.yaml │ └── adminserver.svc.yaml ├── jobservice │ ├── jobservice.rc.yaml │ └── jobservice.svc.yaml ├── k8s-prepare ├── mysql │ ├── mysql.rc.yaml │ └── mysql.svc.yaml ├── nginx │ ├── nginx.rc.yaml │ └── nginx.svc.yaml ├── pv │ ├── log.pvc.yaml │ ├── log.pv.yaml │ ├── registry.pvc.yaml │ ├── registry.pv.yaml │ ├── storage.pvc.yaml │ └── storage.pv.yaml ├── registry │ ├── registry.rc.yaml │ └── registry.svc.yaml ├── templates │ ├── adminserver.cm.yaml │ ├── jobservice.cm.yaml │ ├── mysql.cm.yaml │ ├── nginx.cm.yaml │ ├── registry.cm.yaml │ └── ui.cm.yaml └── ui ├── ui.rc.yaml └── ui.svc.yaml 8 directories, 25 files
- k8s-prepare脚本:根据templates下的模板文件以及harbor.cfg中的配置生成各个组件,比如registry等的最终configmap配置文件。它的作用类似于用docker-compose工具部署Harbor时的prepare脚本;
- templates目录:templates目录下放置各个组件的配置模板文件(configmap文件模板),将作为k8s-prepare的输入;
- pv目录:Harbor组件所使用的存储插件的配置,默认情况下使用hostpath,对于高可用Harbor而言,我们这里将使用cephfs;
- 其他组件目录,比如:registry:这些目录中存放这各个组件的service yaml和rc yaml,用于在Kubernetes cluster启动各个组件时使用。
下面我用一个示意图来形象地描述一下配置的生成过程以及各个文件在后续Harbor组件启动中的作用:
由于使用external mysql db,Harbor自带的mysql组件我们不会使用,对应的pv目录下的storage.pv.yaml和storage.pvc.yaml我们也不会去关注和使用。
四、部署步骤
1、配置和创建挂载Cephfs的pv和pvc
我们先在共享分布式存储CephFS上为Harbor的存储需求创建目录:apps/harbor-k8s,并在harbor-k8s下创建两个子目录:log和registry,分别满足jobservice和registry的存储需求:
# cd /mnt // CephFS的根目录挂载到了/mnt下面 # mkdir -p apps/harbor-k8s/log # mkdir -p apps/harbor-k8s/registry # tree apps/harbor-k8s apps/harbor-k8s ├── log └── registry
关于CephFS的挂载等具体操作步骤,可以参见我的 《Kubernetes集群跨节点挂载CephFS》 一文。
接下来,创建用于k8s pv挂载cephfs的ceph-secret,我们编写一个ceph-secret.yaml文件:
//ceph-secret.yaml apiVersion: v1 data: key: {base64 encoding of the ceph admin.secret} kind: Secret metadata: name: ceph-secret type: Opaque
创建ceph-secret:
# kubectl create -f ceph-secret.yaml secret "ceph-secret" created
最后,我们来修改pv、pvc文件并创建对应的pv和pvc资源,要修改的文件包括pv/log.xxx和pv/registry.xxx,我们的目的就是用cephfs替代原先的hostPath:
//log.pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: log-pv labels: type: log spec: capacity: storage: 1Gi accessModes: - ReadWriteMany cephfs: monitors: - {ceph-mon-node-ip}:6789 path: /apps/harbor-k8s/log user: admin secretRef: name: ceph-secret readOnly: false persistentVolumeReclaimPolicy: Retain //log.pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: log-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi selector: matchLabels: type: log // registry.pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: registry-pv labels: type: registry spec: capacity: storage: 5Gi accessModes: - ReadWriteMany cephfs: monitors: - 10.47.217.91:6789 path: /apps/harbor-k8s/registry user: admin secretRef: name: ceph-secret readOnly: false persistentVolumeReclaimPolicy: Retain //registry.pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: registry-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 5Gi selector: matchLabels: type: registry
创建pv和pvc:
# kubectl create -f log.pv.yaml persistentvolume "log-pv" created # kubectl create -f log.pvc.yaml persistentvolumeclaim "log-pvc" created # kubectl create -f registry.pv.yaml persistentvolume "registry-pv" created # kubectl create -f registry.pvc.yaml persistentvolumeclaim "registry-pvc" created # kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE log-pvc Bound log-pv 1Gi RWX 31s registry-pvc Bound registry-pv 5Gi RWX 2s # kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE log-pv 1Gi RWX Retain Bound default/log-pvc 36s registry-pv 5Gi RWX Retain Bound default/registry-pvc 6s
2、创建和初始化Harbor用的数据库
我们需要在External DB中创建Harbor访问数据库所用的user(harbork8s/harbork8s)以及所使用的数据库(registry_k8s):
mysql> create user harbork8s identified by 'harbork8s'; Query OK, 0 rows affected (0.03 sec) mysql> GRANT ALL PRIVILEGES ON *.* TO 'harbork8s'@'%' IDENTIFIED BY 'harbork8s' WITH GRANT OPTION; Query OK, 0 rows affected, 1 warning (0.00 sec) # mysql> create database registry_k8s; Query OK, 1 row affected (0.00 sec) mysql> grant all on registry_k8s.* to 'harbork8s' identified by 'harbork8s'; Query OK, 0 rows affected, 1 warning (0.00 sec)
由于目前Harbor还不支持自动init数据库,因此我们需要为新建的registry_k8s数据库做初始化,具体的方案就是先使用docker-compose工具在本地启动一个harbor,通过mysqldump将harbor-db container中的数据表dump出来,再导入到external db中的registry_k8s中,具体操作步骤如下:
# wget -c http://harbor.orientsoft.cn/harbor-1.2.0/harbor-offline-installer-v1.2.0.tgz # tar zxvf harbor-offline-installer-v1.2.0.tgz 进入harbor目录,修改harbor.cfg中的hostname: hostname = hub.tonybai.com:31777 # ./prepare # docker-compose up -d 找到harbor_db的container id: 77fde71390e7,进入容器,并将数据库registry dump出来: # docker exec -i -t 77fde71390e7 bash # mysqldump -u root -pxxx --databases registry > registry.dump 离开容器,将容器内导出的registry.dump copy到本地: # docker cp 77fde71390e7:/tmp/registry.dump ./ 修改registry.dump为registry_k8s.dump,修改其内容中的registry为registry_k8s,然后导入到external db: # mysqldump -h external_db_ip -P 3306 -u harbork8s -pharbork8s mysql> source ./registry_k8s.dump;
3、配置make/harbor.cfg
harbor.cfg是整个配置生成的重要输入,我们在k8s-prepare执行之前,先要根据我们的需要和环境对harbor.cfg进行配置:
// make/harbor.cfg hostname = hub.tonybai.com:31777 db_password = harbork8s db_host = {external_db_ip} db_user = harbork8s
4、对templates目录下的configmap配置模板(*.cm.yaml)进行配置调整
- templates/adminserver.cm.yaml:
MYSQL_HOST: {external_db_ip} MYSQL_USR: harbork8s MYSQL_DATABASE: registry_k8s RESET: "true"
注:adminserver.cm.yaml没有使用harbor.cfg中的有关数据库的配置项,而是需要单独再配置一遍,这块估计将来会fix掉这个问题。
- templates/registry.cm.yaml:
rootcertbundle: /etc/registry/root.crt
- templates/ui.cm.yaml:
ui组件需要添加session共享。ui组件读取_REDIS_URL环境变量:
//vmware/harbor/src/ui/main.go ... .. redisURL := os.Getenv("_REDIS_URL") if len(redisURL) > 0 { beego.BConfig.WebConfig.Session.SessionProvider = "redis" beego.BConfig.WebConfig.Session.SessionProviderConfig = redisURL } ... ... 而redisURL的格式在beego的源码中有说明: // beego/session/redis/sess_redis.go // SessionInit init redis session // savepath like redis server addr,pool size,password,dbnum // e.g. 127.0.0.1:6379,100,astaxie,0 func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {...}
因此,我们在templates/ui.cm.yaml中添加一行:
_REDIS_URL: {redis_ip}:6379,100,{redis_password},11
jobservice.cm.yaml和nginx.cm.yaml无需改变。
5、对各组件目录下的xxx.rc.yaml和xxx.svc.yaml配置模板进行配置调整
- adminserver/adminserver.rc.yaml
replicas: 3
- adminserver/adminserver.svc.yaml
不变。
- jobservice/jobservice.rc.yaml、jobservice/jobservice.svc.yaml
不变。
- nginx/nginx.rc.yaml
replicas: 3
- nginx/nginx.svc.yaml
apiVersion: v1 kind: Service metadata: name: nginx spec: type: NodePort ports: - name: http port: 80 nodePort: 31777 protocol: TCP selector: name: nginx-apps
- registry/registry.rc.yaml
replicas: 3 mountPath: /etc/registry
这里有一个严重的 bug ,即registry.rc.yaml中configmap的默认mount路径:/etc/docker/registry与registry的docker image中的registry配置文件的路径/etc/registry不一致,这将导致我们精心配置的registry的configmap根本没有发挥作用,数据依然在memory中,而不是在我们配置的Cephfs中。这样一旦registry container退出,仓库的image数据就会丢失。同时也无法实现数据的高可用。因此,我们将mountPath都改为与registry image的一致,即:/etc/registry目录。
- registry/registry.svc.yaml
不变。
- ui/ui.rc.yaml
replicas: 3
- ui/ui.svc.yaml
- name: _REDIS_URL valueFrom:configMapKeyRef:name: harbor-ui-configkey: _REDIS_URL
6、执行k8s-prepare
执行k8s-prepare,生成各个组件的configmap文件:
# ./k8s-prepare # git status ... ... adminserver/adminserver.cm.yaml jobservice/jobservice.cm.yaml mysql/mysql.cm.yaml nginx/nginx.cm.yaml registry/registry.cm.yaml ui/ui.cm.yaml
7、启动Harbor组件
- 创建configmap
# kubectl apply -f jobservice/jobservice.cm.yaml configmap "harbor-jobservice-config" created # kubectl apply -f nginx/nginx.cm.yaml configmap "harbor-nginx-config" created # kubectl apply -f registry/registry.cm.yaml configmap "harbor-registry-config" created # kubectl apply -f ui/ui.cm.yaml configmap "harbor-ui-config" created # kubectl apply -f adminserver/adminserver.cm.yaml configmap "harbor-adminserver-config" created # kubectl get cm NAME DATA AGE harbor-adminserver-config 42 14s harbor-jobservice-config 8 16s harbor-nginx-config 3 16s harbor-registry-config 2 15s harbor-ui-config 9 15s
- 创建harbor各组件对应的k8s service
# kubectl apply -f jobservice/jobservice.svc.yaml service "jobservice" created # kubectl apply -f nginx/nginx.svc.yaml service "nginx" created # kubectl apply -f registry/registry.svc.yaml service "registry" created # kubectl apply -f ui/ui.svc.yaml service "ui" created # kubectl apply -f adminserver/adminserver.svc.yaml service "adminserver" created # kubectl get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) adminserver 10.103.7.8 <none> 80/TCP jobservice 10.104.14.178 <none> 80/TCP nginx 10.103.46.129 <nodes> 80:31777/TCP registry 10.101.185.42 <none> 5000/TCP,5001/TCP ui 10.96.29.187 <none> 80/TCP
- 创建rc,启动各个组件pods
# kubectl apply -f registry/registry.rc.yaml replicationcontroller "registry-rc" created # kubectl apply -f jobservice/jobservice.rc.yaml replicationcontroller "jobservice-rc" created # kubectl apply -f ui/ui.rc.yaml replicationcontroller "ui-rc" created # kubectl apply -f nginx/nginx.rc.yaml replicationcontroller "nginx-rc" created # kubectl apply -f adminserver/adminserver.rc.yaml replicationcontroller "adminserver-rc" created #kubectl get pods NAMESPACE NAME READY STATUS RESTARTS AGE default adminserver-rc-9pc78 1/1 Running 0 3m default adminserver-rc-pfqtv 1/1 Running 0 3m default adminserver-rc-w55sx 1/1 Running 0 3m default jobservice-rc-d18zk 1/1 Running 1 3m default nginx-rc-3t5km 1/1 Running 0 3m default nginx-rc-6wwtz 1/1 Running 0 3m default nginx-rc-dq64p 1/1 Running 0 3m default registry-rc-6w3b7 1/1 Running 0 3m default registry-rc-dfdld 1/1 Running 0 3m default registry-rc-t6fnx 1/1 Running 0 3m default ui-rc-0kwrz 1/1 Running 1 3m default ui-rc-kzs8d 1/1 Running 1 3m default ui-rc-vph6d 1/1 Running 1 3m
五、验证与Troubleshooting
1、docker cli访问
由于harbor默认使用了http访问,因此在docker login前先要将我们的仓库地址加到/etc/docker/daemon.json的insecure-registries中:
///etc/docker/daemon.json { "insecure-registries": ["hub.tonybai.com:31777"] }
systemctl daemon-reload and restart后,我们就可以通过docker login登录新建的仓库了(初始密码:Harbor12345):
docker login hub.tonybai.com:31777 Username (admin): admin Password: Login Succeeded
2、docker push & pull
我们测试上传一个busybox image:
# docker pull busybox Using default tag: latest latest: Pulling from library/busybox 0ffadd58f2a6: Pull complete Digest: sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0 Status: Downloaded newer image for busybox:latest # docker tag busybox:latest hub.tonybai.com:31777/library/busybox:latest # docker push hub.tonybai.com:31777/library/busybox:latest The push refers to a repository [hub.tonybai.com:31777/library/busybox] 0271b8eebde3: Preparing 0271b8eebde3: Pushing [==================================================>] 1.338 MB 0271b8eebde3: Pushed latest: digest: sha256:179cf024c8a22f1621ea012bfc84b0df7e393cb80bf3638ac80e30d23e69147f size: 527
下载刚刚上传的busybox:
# docker pull hub.tonybai.com:31777/library/busybox:latest latest: Pulling from library/busybox 414e5515492a: Pull complete Digest: sha256:179cf024c8a22f1621ea012bfc84b0df7e393cb80bf3638ac80e30d23e69147f Status: Downloaded newer image for hub.tonybai.com:31777/library/busybox:latest
3、访问Harbor UI
在浏览器中打开http://hub.tonybai.com:31777,用admin/Harbor12345登录,如果看到下面页面,说明安装部署成功了:
本文转自开源中国- 在Kubernetes集群上部署高可用Harbor镜像仓库