关于基于Harbor的高可用私有镜像仓库,在个人博客里曾不止一次提到,在源创会2017沈阳站上,我还专门以此题目和你们作了分享。过后,不少人经过微博私信、我的公众号或博客评论问我是否能够在Kubernetes集群上安装高可用的Harbor仓库,今天我就用这篇文章来回答你们这个问题。node
首先,我能够确定给出一个回答:Harbor支持在Kubernetes部署。只不过Harbor官方的默认安装并不是是高可用的,而是“单点式”的。在《基于Harbor的高可用企业级私有容器镜像仓库部署实践》一文中,我曾谈到了一种在裸机或VM上的、基于Cephfs共享存储的高可用Harbor方案。在Kubernetes上部署,其高可用的思路也是相似的,可见下面这幅示意图:mysql
围绕这幅示意图,简单说明一下咱们的方案:linux
方案肯定后,接下来咱们就开始部署。nginx
在Harbor官方的对Kubernetes支持的说明中,提到当前的Harbor on kubernetes相关脚本和配置在Kubernetes v1.6.5和Harbor v1.2.0上验证测试经过了,所以在咱们的实验环境中,Kubernetes至少要准备v1.6.5及之后版本。下面是个人环境的一些信息:git
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的:github
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) 集群。redis
咱们在本地建立harbor-install-on-k8s目录,并将Harbor最新源码下载到该目录下:sql
# 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目录下:docker
# 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
下面我用一个示意图来形象地描述一下配置的生成过程以及各个文件在后续Harbor组件启动中的做用:数据库
因为使用external mysql db,Harbor自带的mysql组件咱们不会使用,对应的pv目录下的storage.pv.yaml和storage.pvc.yaml咱们也不会去关注和使用。
咱们先在共享分布式存储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
咱们须要在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;
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
MYSQL_HOST: {external_db_ip} MYSQL_USR: harbork8s MYSQL_DATABASE: registry_k8s RESET: "true"
注:adminserver.cm.yaml没有使用harbor.cfg中的有关数据库的配置项,而是须要单独再配置一遍,这块估计未来会fix掉这个问题。
rootcertbundle: /etc/registry/root.crt
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无需改变。
replicas: 3
不变。
不变。
replicas: 3
apiVersion: v1 kind: Service metadata: name: nginx spec: type: NodePort ports: - name: http port: 80 nodePort: 31777 protocol: TCP selector: name: nginx-apps
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目录。
不变。
replicas: 3
- name: _REDIS_URL valueFrom: configMapKeyRef: name: harbor-ui-config key: _REDIS_URL
执行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
# 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
# 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
# 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
因为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
咱们测试上传一个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
在浏览器中打开http://hub.tonybai.com:31777,用admin/Harbor12345登陆,若是看到下面页面,说明安装部署成功了: