标签 Ceph 下的文章

在Kubernetes集群上部署高可用Harbor镜像仓库

关于基于Harbor的高可用私有镜像仓库,在我的博客里曾不止一次提到,在源创会2017沈阳站上,我还专门以此题目和大家做了分享。事后,很多人通过微博私信个人公众号或博客评论问我是否可以在Kubernetes集群上安装高可用的Harbor仓库,今天我就用这篇文章来回答大家这个问题。

一、Kubernetes上的高可用Harbor方案

首先,我可以肯定给出一个回答:Harbor支持在Kubernetes部署。只不过Harbor官方的默认安装并非是高可用的,而是“单点式”的。在《基于Harbor的高可用企业级私有容器镜像仓库部署实践》一文中,我曾谈到了一种在裸机或VM上的、基于Cephfs共享存储的高可用Harbor方案。在Kubernetes上部署,其高可用的思路也是类似的,可见下面这幅示意图:

img{512x368}

围绕这幅示意图,简单说明一下我们的方案:

  • 通过在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组件启动中的作用:

img{512x368}

由于使用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-config
                 key: _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登录,如果看到下面页面,说明安装部署成功了:

img{512x368}

六、参考资料


微博:@tonybai_cn
微信公众号:iamtonybai
github.com: https://github.com/bigwhite

微信赞赏:
img{512x368}

源创会2017沈阳站讲稿:基于Harbor的高可用企业级私有容器镜像仓库部署实践

上周六开源中国源创会在沈阳举办了一次技术活动,很荣幸以本地讲师的身份和大家交流了一个topic: “基于Harbor的高可用企业级私有容器镜像仓库部署实践”。之所以选择这个topic,是因为这是我们团队的项目实践心得。很多企业和组织在深入使用Docker之后,都会有类似的高可用私有容器仓库搭建的需求,于是我就把我们摸索的实践和填坑过程拿出来,用30分钟与大家分享一下。另外这算是一个入门级的分享,并未深入过多原理。以下就是本次分享的内容讲稿整理。如有不妥或不正确的地方,欢迎交流指正。

img{512x368}

大家下午好,欢迎各位来到源创会沈阳站。在这里我也代表沈阳的IT人欢迎源创会来到沈阳,希望能有更多的像源创会这样的组织到沈阳举办技术活动。非常高兴能有这个机会在源创会这个平台上做分享, 今天和大家一起探讨的题目是:“基于Harbor的高可用企业级私有容器镜像仓库部署实践”。题目有些长,简单来说就是如何搭建一个好用的镜像仓库。

img{512x368}

首先做个简单的自我介绍。我叫白明,东软(注:源创会这次活动的会场在东软沈阳园区)是我的主场,在这里工作很多年,目前就职东软云科技;Gopher一枚,近两年主要使用Go语言开发;技术译者,曾参与翻译过《七周七语言》一书;并且参与过智慧城市架构系列丛书的编著工作;GopherChina大会讲师,这里顺便说一下GopherChina大会,它是目前中国地区规模最大、水平最高的Go语言技术大会,一般每年4月份在北京或上海举行。希望有志于Go语言开发的开发者积极参与;Blogger,写博10多年,依旧笔耕不倦;目前主要从事Docker&kubernetes的研究和实践。

当今,IT技术发展飞快。五年前, IT从业者口中谈论最多的技术是Virtual Machine,即虚拟化技术,人们经常争论的是到底是vmware的技术好,还是原生kvm技术稳定,又或是xen的技术完美。转眼间五年过去了,大家口中经常讨论的技术词汇发生了变化,越来越多的技术人在谈论Docker,谈论容器。

Docker是什么? Docker这门技术非常热,但我们要透过现象看其本质:

Docker技术并不是新技术,而是将已有技术进行了更好的整合和包装

内核容器技术以一种完整形态最早出现在Sun公司Solaris操作系统上,Solaris是当时最先进的服务器操作系统。2005年Solaris发布Solaris Container技术,从此开启了内核容器之门。

IT技术发展的趋势就是这样:商业有的,开源也要有。三年后,即2008年,以Google公司开发人员为主导的Linux Container,LXC功能在被merge到Linux内核。LXC是一种内核级虚拟化技术,主要基于namespacescgroup技术,实现共享一个os kernel前提下的进程资源隔离,为进程提供独立的虚拟执行环境,这样的一个虚拟的执行环境就是一个容器。本质上说,LXC容器与现在的Docker所提供容器是一样的。但是,当时LXC处于早期阶段,开发人员可能更为关注LXC的技术实现,而对开发体验方面有所忽略,导致LXC技术使用门槛较高,普通应用开发者学习、理解和使用它的心智负担较高,因此应用并不广泛。

这一情况一直持续到2013年,当时美国一家名不见经传的公司dotCloud发布了一款平台工具Docker,对外宣称可以实现:“build,ship and run any app and anywhere”。Docker实质上也是基于namespaces和cgroup技术的,Docker的创新之处在于其基于union fs技术定义了一套应用打包规范,真正将应用及其运行的所有依赖都封装到一个特定格式的文件中,这种文件就被称为image,即镜像文件。同时,Docker还提供了一套抽象层次更高的工具集,这套工具对dev十分友好,具有良好的开发体验(Developer eXperience),开发者无需关心namespace, cgroups之类底层技术,即可很easy的启动一个承载着其应用的容器:

Docker run ubuntu echo hello

因此, 从2013发布以来,Docker项目就像坐上了火箭,发展迅猛,目前已经是github上最火爆的开源项目之一。这里还要提一点就是:Docker项目是使用go语言开发的,Docker项目的成功,也或多或少得益于Go优异的开发效率和执行效率。

Docker技术的出现究竟给我们带来了哪些好处呢,个人觉得至少有以下三点:

  • 交付标准化:Docker使得应用程序和依赖的运行环境真正绑定结合为一体,得之即用。这让开发人员、测试和运维实现了围绕同一交付物,保持开发交付上下文同步的能力,即“test what you write, ship what you test”;
  • 执行高效化:应用的启动速度从原先虚拟机的分钟级缩短到容器的秒级甚至ms级,使得应用可以支持快速scaling伸缩;
  • 资源集约化:与vm不同的是,Container共享一个内核,这使得一个container的资源消耗仅为进程级别或进程组级别。同时,容器的镜像也因为如此,其size可以实现的很小,最小可能不足1k,平均几十M。与vm动辄几百兆的庞大身段相比,具有较大优势。

有了image文件后,自然而言我们就有了对image进行存取和管理的需求,即我们需要一个镜像仓库,于是Docker推出了Docker registry这个项目。Docker Registry就是Docker image的仓库,用来存储、管理和分发image的;Docker registry由Docker公司实现,项目名为distribution,其实现了Docker Registr 2.0协议,与早前的Registry 1.x协议版本相比,Distribution采用Go语言替换了Python,在安全性和性能方面都有了大幅提升;Docker官方运行着一个世界最大的公共镜像仓库:hub.docker.com,最常用的image都在hub上,比如反向代理nginx、redis、ubuntu等。鉴于国内访问hub网速不佳,多使用国内容器服务厂商提供的加速器。Docker官方还将Registry本身打入到了一个image中,方便开发人员快速以容器形式启动一个Registry:

docker run -d -p 5000:5000 --restart=always --name registry registry:2

不过,这样启动的Registry更多仅仅是一个Demo级别或满足个体开发者自身需要的,离满足企业内部开发流程或生产需求还差了许多。

既然Docker官方运行着免费的镜像仓库,那我们还需要自己搭建吗?实际情况是,对Docker的使用越深入,对私有仓库的需求可能就越迫切。我们先来看一组Docker 2016官方的调查数据,看看Docker都应用在哪些场合。 从Docker 2016官方调查来看,Docker 更多用于dev、ciDevOps等环节,这三个场合下的应用占据了半壁江山。而相比于公共仓库,私有镜像仓库能更好的满足开发人员在这些场合对镜像仓库的需求。理由至少有四点:

  • 便于集成到内部CI/Cd
    以我司内部为例,由于公司内部办公需要使用正向代理访问外部网络,要想将Public Registry集成到你的内部CI中,技术上就会有很多坎儿,整个搭建过程可能是非常痛苦的;

  • 对镜像可以更全面掌控
    一般来说,外部Public Registry提供的管理功能相对单一,往往无法满足企业内部的开发和交付需求;

  • 内部网络,网络传输性能更好
    内部开发运维流水线很多环节是有一定的时间敏感性的,比如:一次CI如果因为network问题导致image pull总是timeout,会让dev非常闹心,甚至影响整体的开发和交付效率。

  • 出于安全考虑
    总是有企业不想将自己开发的软件或数据放到公网上,因此在企业内部选择搭建一个private registry更会让这些企业得到满足;另外企业对仓库的身份验证可能还有LDAP支持的需求,这是外部registry无法满足的。

一旦企业决定搭建自己的private仓库,那么就得做一个private仓库的技术选型。商业版不在我们讨论范围内,我们从开源软件中挑选。不过开源的可选的不多,Docker 官方的Registry更聚焦通用功能,没有针对企业客户需求定制,开源领域我们大致有两个主要候选者:SUSEPortus和Vmware的Harbor。针对开源项目的技术选型,我个人的挑选原则最简单的就是看社区生态,落实到具体的指标上包括:

  • 项目关注度(即star数量)
  • 社区对issue的反馈数量和积极性
  • 项目维护者对issue fix的积极程度以及是否有远大的roadmap

对比后,我发现在这三个指标上,目前Harbor都暂时领先portus一段距离,于是我们选择Harbor。

Harbor是VMware中国团队开源的企业级镜像仓库项目,聚焦镜像仓库的企业级需求,这里从其官网摘录一些特性,大家一起来看一下:

– 支持基于角色的访问控制RBAC;
– 支持镜像复制策略(PUSH);
– 支持无用镜像数据的自动回收和删除; – 支持LDAP/AD认证;
– Web UI;
– 提供审计日志功能;
– 提供RESTful API,便于扩展;
– 支持中文&部署Easy。

不过,Harbor默认安装的是单实例仓库,并非是高可用的。对于接纳和使用Docker的企业来说,镜像仓库已经企业内部开发、交付和运维流水线的核心,一旦仓库停掉,流水线将被迫暂停,对开发交付的效率会产生重要影响;对于一些中大型企业组织,单实例的仓库性能也无法满足需求,为此高可用的Harbor势在必行。在设计Harbor HA方案之前,我们简单了解一下Harbor组成架构。

一个Harbor实例就是一组由docker-compose工具启动的容器服务,主要包括四个主要组件:

  • proxy
    实质就是一个反向代理nginx,负责流量路由分担到ui和registry上;

  • registry
    这里的registry就是原生的docker官方的registry镜像仓库,Harbor在内部内置了一个仓库,所有仓库的核心功能均是由registry完成的;

  • core service
    包含了ui、token和webhook服务;

  • job service
    主要用于镜像复制供。

同时,每个Harbor实例还启动了一个MySQL数据库容器,用于保存自身的配置和镜像管理相关的关系数据。

高可用系统一般考虑三方面:计算高可用、存储高可用和网络高可用。在这里我们不考虑网络高可用。基于Harbor的高可用仓库方案,这里列出两个。

img{512x368}

两个方案的共同点是计算高可用,都是通过lb实现的多主热运行,保证无单点;存储高可用则各有各的方案。一个使用了分布式共享存储,数据可靠性由共享存储provider提供;另外一个则需要harbor自身逻辑参与,通过镜像相互复制的方式保持数据的多副本。

两种方案各有优缺点,就看哪种更适合你的组织以及你手里的资源是否能满足方案的搭建要求。

方案1是Harbor开发团队推荐的标准方案,由于基于分布式共享存储,因此其scaling非常好;同样,由于多Harbor实例共享存储,因此可以保持数据是实时一致的。方案1的不足也是很明显的,第一:门槛高,需要具备共享存储provider;第二搭建难度要高于第二个基于镜像复制的方案。

方案2的优点就是首次搭建简单。不足也很多:scaling差,甚至是不能,一旦有三个或三个以上节点,可能就会出现“环形复制”;镜像复制需要时间,因此存在多节点上数据周期性不一致的情况;Harbor的镜像复制规则以Project为单位配置,因此一旦新增Project,需要在每个节点上手工维护复制规则,非常繁琐。因此,我们选择方案1。

我们来看一下方案1的细节: 这是一幅示意图。

  • 每个安放harbor实例的node都mount cephfs。ceph是目前最流行的分布式共享存储方案之一;
  • 每个node上的harbor实例(包含组件:ui、registry等)都volume mount node上的cephfs mount路径;
  • 通过Load Balance将request流量负载到各个harbor实例上;
  • 使用外部MySQL cluster替代每个Harbor实例内部自维护的那个MySQL容器;对于MySQL cluster,可以使用mysql galera cluster或MySQL5.7以上版本自带的Group Replication (MGR) 集群。
  • 通过外部Redis实现访问Harbor ui的session共享,这个功能是Harbor UI底层MVC框架-beego提供的。

接下来,我们就来看具体的部署步骤和细节。

环境和先决条件:

  • 三台VM(Ubuntu 16.04及以上版本);
  • CephFS、MySQL、Redis已就绪;
  • Harbor v1.1.0及以上版本;
  • 一个域名:hub.tonybai.com:8070。我们通过该域名和服务端口访问Harbor,我们可以通过dns解析多ip轮询实现最简单的Load balance,虽然不完美。

第一步:挂载cephfs

每个安装Harbor instance的节点都要mount cephfs的相关路径,步骤包括:

#安装cephfs内核驱动
apt install ceph-fs-common

# 修改/etc/fstab,添加挂载指令,保证节点重启依旧可以自动挂载cephfs
xx.xx.xx.xx:6789:/apps/harbor /mnt/cephfs/harbor ceph name=harbor,secretfile=/etc/ceph/a dmin.secret,noatime,_netdev 0 2

这里涉及一个密钥文件admin.secret,这个secret文件可以在ceph集群机器上使用ceph auth tool生成。

img{512x368}

前面提到过每个Harbor实例都是一组容器服务,这组容器启动所需的配置文件是在Harbor正式启动前由prepare脚本生成的,Prepare脚本生成过程的输入包括:harbor.cfg、docker-compose.yml和common/templates下的配置模板文件。这也是部署高可用Harbor的核心步骤,我们逐一来看。

第二步:修改harbor.cfg

我们使用域名访问Harbor,因此我们需要修改hostname配置项。注意如果要用域名访问,这里一定填写域名,否则如果这里使用的是Harbor node的IP,那么在后续会存在client端和server端仓库地址不一致的情况;

custom_crt=false 关闭 crt生成功能。注意:三个node关闭其中两个,留一个生成一套数字证书和私钥。

第三步:修改docker-compose.yml

docker-compose.yml是docker-compose工具标准配置文件,用于配置docker-compose即将启动的容器服务。针对该配置文件,我们主要做三点修改:

  • 修改volumes路径
    由/data/xxx 改为:/mnt/cephfs/harbor/data/xxx
  • 由于使用外部Mysql,因此需要删除mysql service以及其他 service对mysql service的依赖 (depends_on)
  • 修改对proxy外服务端口 ports: 8070:80

第四步:配置访问external mysql和redis

external mysql的配置在common/templates/adminserver/env中,我们用external Mysql的访问方式覆盖下面四项配置:

MYSQL_HOST=harbor_host
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password

还有一个关键配置,那就是将RESET由false改为true。只有改为true,adminserver启动时,才能读取更新后的配置

RESET=true

Redis连接的配置在common/templates/ui/env中,我们需要新增一行:

_REDIS_URL=redis_ip:6379,100,password,0

第五步:prepare并启动harbor

执行prepare脚本生成harbor各容器服务的配置;在每个Harbor node上通过下面命令启动harbor实例:

docker-compose up -d

启动后,可以通过docker-compose ps命令查看harbor实例中各容器的启动状态。如果启动顺利,都是”Up”状态,那么我们可以在浏览器里输入:http://hub.tonybai.com:8070,不出意外的话,我们就可以看到Harbor ui的登录页面了。

至此,我们的高可用Harbor cluster搭建过程就告一段落了。

Troubleshooting

不过,对Harbor的认知还未结束,我们在后续使用Harbor的过程中遇到了一些问题,这里举两个例子。

问题1: docker login hub.tonybai.com:8070 failed

现象日志:

Error response from daemon: Get https://hub.tonybai.com:8070/v1/users/: http: server gave HTTP response to HTTPS client

通过错误日志分析应该是docker daemon与镜像仓库所用协议不一致导致。docker engine默认采用https协议访问仓库,但之前我们搭建的Harbor采用的是http协议提供服务,两者不一致。

解决方法有两种,这里列出第一种:让docker引擎通过http方式访问harbor仓库:

在/etc/docker/daemon.json中添加insecure-registry:

{
    "insecure-registries": ["hub.tonybai.com:8070"]
}

重启docker service生效

第二种方法就是让Harbor支持https,需要为harbor的proxy配置私钥和证书,位置:harbor.cfg中

#The path of cert and key files for nginx, they are applied only the protocol is set to https
ssl_cert = /data/cert/server.crt
ssl_cert_key = /data/cert/server.key

这里就不细说了。

问题2:docker login hub.tonybai.com:8070 有时成功,有时failed

现象日志:

第一次登录成功:
# docker login -u user -p passwd http://hub.tonybai.com:8070 Login Succeeded

第二次登录失败:
# docker login -u user -p passwd http://hub.tonybai.com:8070
Error response from daemon: login attempt to http://hub.tonybai.com:8070/v2/ failed with status: 401 Unauthorized

这个问题的原因在于对docker registry v2协议登录过程理解不够透彻。docker registry v2是一个两阶段登录的过程:

  • 首先:docker client会到registry去尝试登录,registry发现request中没有携带token,则返回失败应答401,并告诉客户端到哪里去获取token;
  • 客户端收到应答后,获取应答中携带的token service地址,然后到harbor的core services中的token service那里获取token(使用user, password进行校验)。一旦token service校验ok,则会使用private_key.pem生成一个token;
  • 客户端拿到token后,再次到registry那里去登录,这次registry用root.crt去校验客户端携带的token,校验通过,则login成功。

由于我们是一个harbor cluster,如果docker client访问的token service和registry是在一个harbor实例中的,那么login就会ok;否则docker client就会用harbor node1上token service生成的token到harbor node2上的registry去登录,由于harbor node2上root.crt与harbor node1上private_key.pem并非一对,因此登录失败

解决方法:将所有节点上使用同一套root.crt和private_key.pem。即将一个harbor node(harbor.cfg中custom_crt=true的那个)上的 common/config/ui/private_key.pem和 common/config/registry/root.crt复制到其他harbor node;然后重建各harbor实例中的容器。

至此,我们的高可用Harbor仓库部署完了。针对上面的配置过程,我还做了几个录屏文件,由于时间关系,这里不能播放了,大家可以在下面这个连接下载并自行播放收看。

Harbor install 录屏: https://pan.baidu.com/s/1o8JYKEe

谢谢大家!

讲稿slide可以在这里获取到。

微博:@tonybai_cn
微信公众号:iamtonybai
github.com: https://github.com/bigwhite

基于Harbor和CephFS搭建高可用Private Registry

我们有给客户搭建私有容器仓库的需求。开源的私有容器registry可供选择的不多,除了docker官方的distribution之外,比较知名的是VMware China出品的Harbor,我们选择了harbor。

harbor在docker distribution的基础上增加了一些安全、访问控制、管理的功能以满足企业对于镜像仓库的需求。harbor以docker-compose的规范形式组织各个组件,并通过docker-compose工具进行启停。

不过,harbor默认的安装配置是针对single node的,要想做得可靠性高一些,我们需要自己探索一些可行的方案。本文将结合harbor和CephFS搭建一个满足企业高可用性需求的private registry。

一、实验环境

这里用两台阿里云ECS作为harbor的工作节点:

node1:  10.47.217.91
node2:  10.28.61.30

两台主机运行的都是Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-58-generic x86_64),使用root用户。

docker版本与docker-compose的版本如下:

# docker version
Client:
 Version:      1.12.5
 API version:  1.24
 Go version:   go1.6.4
 Git commit:   7392c3b
 Built:        Fri Dec 16 02:42:17 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.12.5
 API version:  1.24
 Go version:   go1.6.4
 Git commit:   7392c3b
 Built:        Fri Dec 16 02:42:17 2016
 OS/Arch:      linux/amd64

# docker-compose -v
docker-compose version 1.12.0, build b31ff33

ceph版本如下:

# ceph -v
ceph version 10.2.7

ceph的安装和配置可参考这里

二、方案思路

首先,从部署上说,我们需要的Private Registry是独立于k8s cluster存在的,即在k8s cluster外部,其存储和管理的镜像供k8s cluster 组件以及运行于k8s cluster上的应用使用。

其次,企业对registry有高可用需求,但我们也要有折中,我们的目标并不是理想的完全高可用,那样投入成本可能有些高。一般企业环境下更注重数据安全。因此首要保证harbor的数据安全,这样即便harbor实例宕掉,保证数据依然不会丢失即可。并且生产环境下registry的使用很难称得上高频,对镜像仓库的性能要求也没那么高。这种情况下,harbor的高可用至少有两种方案:

  • 多harbor实例共享后端存储
  • 多harbor实例相互数据同步(通过配置两个harbor相互复制镜像数据)

harbor原生支持双实例的镜像数据同步。不过这里我们采用第一种方案:即多harbor实例共享后端存储,因为我们有现成的cephfs供harbor使用。理想的方案示意图如下:

img{512x368}

  • 每个安放harbor实例的node都mount cephfs;
  • 每个node上的harbor实例(包含组件:ui、db、registry等)都volume mount node上的cephfs mount路径;
  • 通过Load Balance将request流量负载到各个harbor实例上。

但这样做可行么?如果这么做,Harbor实例里的mysql container就会“抱怨”:

May 17 22:45:45 172.19.0.1 mysql[12110]: 2017-05-17 14:45:45 1 [ERROR] InnoDB: Unable to lock ./ibdata1, error: 11
May 17 22:45:45 172.19.0.1 mysql[12110]: 2017-05-17 14:45:45 1 [Note] InnoDB: Check that you do not already have another mysqld process using the same InnoDB data or log files.

MySQL多个实例无法共享一份mysql数据文件。

那么,我们会考虑将harbor连接的mysql放到外面来,使用external database;同时考虑到session共享,我们还需要增加一个存储session信息的redis cluster,这样一来,方案示意图变更如下:

img{512x368}

图中的mysql、redis你即可以用cluster,也可以用单点,还是看你的需求和投入。如果你具备现成的mysql cluster和redis cluster,那么直接用就好了。但是如果你没有,并且你还不想投入这么多(尤其是搞mysql cluster),那么用单点就好了。考虑到数据安全,可以将单点mysql的数据存储在cephfs上,如果你已经有了现成的cephfs。

三、在一个node上安装Harbor

1、初装步骤

以一个node上的Harbor安装为例,harbor提供了详细的安装步骤文档,我们按照步骤逐步进行即可(这里我使用的是1.1.0版本,截至目前为止的最新稳定版本为1.1.1版本):

~/harbor-install# wget -c https://github.com/vmware/harbor/releases/download/v1.1.0/harbor-offline-installer-v1.1.0.tgz

~/harbor-install# tar zxvf harbor-offline-installer-v1.1.0.tgz

~/harbor-install/harbor# ls -F
common/  docker-compose.notary.yml  docker-compose.yml  harbor.cfg  harbor.v1.1.0.tar.gz  install.sh*  LICENSE  NOTICE  prepare*

~/harbor-install/harbor./install.sh

[Step 0]: checking installation environment ...

Note: docker version: 1.12.5
Note: docker-compose version: 1.12.0
[Step 1]: loading Harbor images ...
... ...
[Step 2]: preparing environment ...
Generated and saved secret to file: /data/secretkey
Generated configuration file: ./common/config/nginx/nginx.conf
Generated configuration file: ./common/config/adminserver/env
Generated configuration file: ./common/config/ui/env
Generated configuration file: ./common/config/registry/config.yml
Generated configuration file: ./common/config/db/env
Generated configuration file: ./common/config/jobservice/env
Generated configuration file: ./common/config/jobservice/app.conf
Generated configuration file: ./common/config/ui/app.conf
Generated certificate, key file: ./common/config/ui/private_key.pem, cert file: ./common/config/registry/root.crt
The configuration files are ready, please use docker-compose to start the service.

[Step 3]: checking existing instance of Harbor ...
[Step 4]: starting Harbor ...

Creating network "harbor_harbor" with the default driver
Creating harbor-log
Creating harbor-db
Creating registry
Creating harbor-adminserver
Creating harbor-ui
Creating nginx
Creating harbor-jobservice

ERROR: for proxy  Cannot start service proxy: driver failed programming external connectivity on endpoint nginx (fdeb3e538d5f8d714ea5c79a9f3f127f05f7ba5d519e09c4c30ef81f40b2fe77): Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use

harbor实例默认的监听端口是80,但一般node上的80口都会被占用,因此我们需要修改一个端口号。注意:此时harbor仅启动成功了一些container而已,尚无法正常工作。

2、修改harbor proxy组件的listen端口

harbor的proxy组件就是一个nginx,通过nginx这个反向代理,将不同的服务请求分发到内部其他组件中去。nginx默认监听node的80端口,我们用8060端口替代80端口需要进行两处配置修改:

1、harbor.cfg

hostname = node_public_ip:8060

2、docker-compose.yml

proxy:
    image: vmware/nginx:1.11.5-patched
    container_name: nginx
    restart: always
    volumes:
      - ./common/config/nginx:/etc/nginx:z
    networks:
      - harbor
    ports:
      - 8060:80   <--- 修改端口映射
      - 443:443
      - 4443:4443

由于我们修改了harbor.cfg文件,我们需要重新prepare一下,执行下面命令:

# docker-compose down -v
Stopping harbor-jobservice ... done
Stopping nginx ... done
Stopping harbor-ui ... done
Stopping harbor-db ... done
Stopping registry ... done
Stopping harbor-adminserver ... done
Stopping harbor-log ... done
Removing harbor-jobservice ... done
Removing nginx ... done
Removing harbor-ui ... done
Removing harbor-db ... done
Removing registry ... done
Removing harbor-adminserver ... done
Removing harbor-log ... done
Removing network harbor_harbor

# ./prepare
Clearing the configuration file: ./common/config/nginx/nginx.conf
Clearing the configuration file: ./common/config/ui/env
Clearing the configuration file: ./common/config/ui/app.conf
Clearing the configuration file: ./common/config/ui/private_key.pem
Clearing the configuration file: ./common/config/adminserver/env
Clearing the configuration file: ./common/config/jobservice/env
Clearing the configuration file: ./common/config/jobservice/app.conf
Clearing the configuration file: ./common/config/db/env
Clearing the configuration file: ./common/config/registry/config.yml
Clearing the configuration file: ./common/config/registry/root.crt
loaded secret from file: /mnt/cephfs/harbor/data/secretkey
Generated configuration file: ./common/config/nginx/nginx.conf
Generated configuration file: ./common/config/adminserver/env
Generated configuration file: ./common/config/ui/env
Generated configuration file: ./common/config/registry/config.yml
Generated configuration file: ./common/config/db/env
Generated configuration file: ./common/config/jobservice/env
Generated configuration file: ./common/config/jobservice/app.conf
Generated configuration file: ./common/config/ui/app.conf
Generated certificate, key file: ./common/config/ui/private_key.pem, cert file: ./common/config/registry/root.crt
The configuration files are ready, please use docker-compose to start the service.

# docker-compose up -d

Creating network "harbor_harbor" with the default driver
Creating harbor-log
Creating harbor-adminserver
Creating registry
Creating harbor-db
Creating harbor-ui
Creating harbor-jobservice
Creating nginx

我们可以通过docker-compose ps命令查看harbor组件的状态:

# docker-compose ps
       Name                     Command               State                                 Ports
--------------------------------------------------------------------------------------------------------------------------------
harbor-adminserver   /harbor/harbor_adminserver       Up
harbor-db            docker-entrypoint.sh mysqld      Up      3306/tcp
harbor-jobservice    /harbor/harbor_jobservice        Up
harbor-log           /bin/sh -c crond && rm -f  ...   Up      127.0.0.1:1514->514/tcp
harbor-ui            /harbor/harbor_ui                Up
nginx                nginx -g daemon off;             Up      0.0.0.0:443->443/tcp, 0.0.0.0:4443->4443/tcp, 0.0.0.0:8060->80/tcp
registry             /entrypoint.sh serve /etc/ ...   Up      5000/tcp

如果安全组将8060端口打开,通过访问:http://node_public_ip:8060,你将看到如下harbor的web页面:

img{512x368}

我们可以通过harbor内置的默认用户名和密码admin/Harbor12345登录harbor ui。当然,我们更重要的是通过cmdline访问harbor,push和pull image。如果这时你直接尝试docker login harbor_url,你可能会得到如下错误日志:

# docker login -u admin -p Harbor12345 node_public_ip:8060
Error response from daemon: Get https://node_public_ip:8060/v1/users/: http: server gave HTTP response to HTTPS client

这是因为docker默认采用https访问registry,因此我们需要在docker engine的配置中,添加–insecure-registry option。关于ubuntu 16.04下docker配置的问题,请参考这里

DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 --registry-mirror=https://xxxxx.mirror.aliyuncs.com --insecure-registry=node_public_ip:8060"

重启docker engine后尝试再次登录harbor:

docker login -u admin -p Harbor12345 node_public_ip:8060
Login Succeeded

一旦docker client login ok,我们就可以通过docker client对harbor中的相关repository进行操作了。

四、挂载路径修改

默认情况下,harbor将数据volume挂载到主机的/data路径下面。但由于我们采用ceph共享存储保证数据的高可用,需要修改harbor组件内容器的挂载路径,将其mount到共享存储挂载node上的路径:/mnt/cephfs/harbor/data/。对比两个路径,可以看出前缀由”/”变为了”/mnt/cephfs/harbor/”,我们需要修改docker-compose.yml和harbor.cfg两个文件。

由于docker-compose.yml文件较长,这里将原始文件改名为docker-compose.yml.orig,并将其与修改后的docker-compose.yml做对比:

# diff  docker-compose.yml.orig docker-compose.yml
8c8
<       - /var/log/harbor/:/var/log/docker/:z
---
>       - /mnt/cephfs/harbor/log/:/var/log/docker/:z
20c20
<       - /data/registry:/storage:z
---
>       - /mnt/cephfs/harbor/data/registry:/storage:z
40c40
<       - /data/database:/var/lib/mysql:z
---
>       - /mnt/cephfs/harbor/data/database:/var/lib/mysql:z
59,61c59,61
<       - /data/config/:/etc/adminserver/config/:z
<       - /data/secretkey:/etc/adminserver/key:z
<       - /data/:/data/:z
---
>       - /mnt/cephfs/harbor/data/config/:/etc/adminserver/config/:z
>       - /mnt/cephfs/harbor/data/secretkey:/etc/adminserver/key:z
>       - /mnt/cephfs/harbor/data/:/data/:z
80,81c80,81
<       - /data/secretkey:/etc/ui/key:z
<       - /data/ca_download/:/etc/ui/ca/:z
---
>       - /mnt/cephfs/harbor/data/secretkey:/etc/ui/key:z
>       - /mnt/cephfs/harbor/data/ca_download/:/etc/ui/ca/:z
100c100
<       - /data/job_logs:/var/log/jobs:z
---
>       - /mnt/cephfs/harbor/data/job_logs:/var/log/jobs:z
102c102
<       - /data/secretkey:/etc/jobservice/key:z
---
>       - /mnt/cephfs/harbor/data/secretkey:/etc/jobservice/key:z

harbor.cfg文件需要修改的地方不多:

// harbor.cfg

#The path of cert and key files for nginx, they are applied only the protocol is set to https
ssl_cert = /mnt/cephfs/harbor/data/cert/server.crt
ssl_cert_key = /mnt/cephfs/harbor/data/cert/server.key

#The path of secretkey storage
secretkey_path = /mnt/cephfs/harbor/data

配置修改完毕后,执行如下命令:

# docker-compose down -v
# prepare
# docker-compose up -d

新的harbor实例就启动起来了。注意:这一步我们用cephfs替换了本地存储,主要的存储变动针对log、database和registry三个输出数据的组件。你也许会感受到cephfs给harbor ui页面加载带来的影响,实感要比之前的加载慢一些。

五、使用外部数据库(external database)

前面提到了挂载ceph后,多个node上harbor实例中的db组件将出现竞争问题,导致只有一个node上的harbor db组件可以工作。因此,我们要使用外部数据库(或db集群)来解决这个问题。但是harbor官方针对如何配置使用外部DB很是“讳莫如深”,我们只能自己探索。

假设我们已经有了一个external database,并且建立了harbor这个user,并做了相应的授权。由于harbor习惯了独享database,在测试环境下可以考虑

GRANT ALL ON *.* TO 'harbor'@'%';

1、迁移数据

如果此时镜像库中已经有了数据,我们需要做一些迁移工作。

attach到harbor db组件的container中,将registry这张表dump到registry.dump文件中:

#docker exec -i -t  6e1e4b576315  bash

在db container中:
# mysqldump -u root -p --databases registry > registry.dump

回到node,将dump文件从container中copy出来:

#docker cp 6e1e4b576315:/root/registry.dump ./

再mysql login到external Database,将registry.dump文件导入:

# mysql -h external_db_ip -P 3306 -u harbor -p
# mysql> source ./registry.dump;

2、修改harbor配置,使得ui、jobservice组件连接external db

根据当前harbor architecture图所示:

img{512x368}

与database“有染”的组件包括ui和jobservice,如何通过配置修改来让这两个组件放弃老db,访问新的external db呢?这要从挖掘配置开始。harbor的组件配置都在common/config下:

~/harbor-install/harbor# tree -L 3 common
common
├── config
│   ├── adminserver
│   │   └── env
│   ├── db
│   │   └── env
│   ├── jobservice
│   │   ├── app.conf
│   │   └── env
│   ├── nginx
│   │   └── nginx.conf
│   ├── registry
│   │   ├── config.yml
│   │   └── root.crt
│   └── ui
│       ├── app.conf
│       ├── env
│       └── private_key.pem
└── templates
 ... ...

在修改config之前,我们先docker-compose down掉harbor。接下来,我们看到ui和jobservice下都有env文件,这里想必就是可以注入新db的相关访问信息的地方,我们来试试!

// common/config/ui/env
LOG_LEVEL=debug
CONFIG_PATH=/etc/ui/app.conf
UI_SECRET=$ui_secret
JOBSERVICE_SECRET=$jobservice_secret
GODEBUG=netdns=cgo
MYSQL_HOST=new_db_ip
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password

// common/config/jobservice/env
LOG_LEVEL=debug
CONFIG_PATH=/etc/jobservice/app.conf
UI_SECRET=$ui_secret
JOBSERVICE_SECRET=$jobservice_secret
GODEBUG=netdns=cgo
MYSQL_HOST=new_db_ip
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password

同时,由于不再需要harbor_db组件,因此切记:要将其从docker-compose.yml中剔除!。docker-compose up -d重新创建harbor各组件容器并启动!Harbor的日志可以在挂载的ceph路径: /mnt/cephfs/harbor/log下查找到:

/mnt/cephfs/harbor/log# tree 2017-06-09
2017-06-09
├── adminserver.log
├── anacron.log
├── CROND.log
├── jobservice.log
├── mysql.log
├── proxy.log
├── registry.log
├── run-parts.log
└── ui.log

我们以ui.log为例,我们发现harbor启动后,ui.log输出如下错误日志(jobservice.log也是相同):

Jun  9 11:00:17 172.19.0.1 ui[16039]: 2017-06-09T03:00:17Z [INFO] initializing database: type-MySQL host-mysql port-3306 user-root database-registry
Jun  9 11:00:18 172.19.0.1 ui[16039]: 2017-06-09T03:00:18Z [ERROR] [utils.go:94]: failed to connect to tcp://mysql:3306, retry after 2 seconds :dial tcp: lookup mysql: no such host

我们明明注入了新的db env,为何ui还是要访问“tcp://mysql:3306”呢?我们docker inspect一下ui的container,看看env是否包含我们添加的那些:

# docker inspect e91ab20e1dcb
... ...
            "Env": [
                "DATABASE_TYPE=mysql",
                "MYSQL_HOST=database_ip",
                "MYSQL_PORT=3306",
                "MYSQL_PWD=harbor_password",
                "MYSQL_USR=harbor",
                "MYSQL_DATABASE=registry",
            ],
.... ...

env已经注入,那么为何ui、jobservice无法连接到external database呢?要想搞清楚这点,我们只能去“啃代码”了。还好harbor代码并非很难啃。我们发现基于beego实现的ui、jobservice两个组件并未直接通过os.Getenv去获取这些env变量,而是调用了adminserver组件的服务。adminserver在初始化时,在RESET环境变量为true的情况下,读取了common/config/adminserver/env下的所有环境变量。

搞清楚原理后,我们知道了要修改的是common/config/adminserver/env,而不是common/config/ui/env和common/config/jobservice/env。我们将后两个文件还原。修改common/config/adminserver/env文件:

//common/config/adminserver/env
... ...
MYSQL_HOST=new_db_ip
MYSQL_PORT=3306
MYSQL_USR=harbor
MYSQL_PWD=harbor_password
... ...
RESET=true    <--- 改为true,非常关键

重新up harbor服务后,我们发现ui, jobservice与新database的连接成功了!打开harbor web页面,登录进去,我们看到了之前已经添加的用户、项目和镜像文件。

3、一劳永逸

如果你重新执行prepare,那么上面对config目录下的配置修改将被重新覆盖。如果要一劳永逸,那么需要修改的是common/templates下面的同位置同名配置文件。

六、安装其他节点上的harbor实例

前面,我们只搭建了一个节点,为的是验证方案的可行性。要实现高可用,我们还需要在其他节点上安装harbor实例。由于多个节点上harbor实例共同挂载ceph的同一目录,因此考虑到log的分离,在部署其他节点上的harbor时,最好对docker-compose.yml下log组件的volumes映射路径进行调整,以在多个节点间做隔离,便于日志查看,比如:

volumes:
      - /mnt/cephfs/harbor/log1/:/var/log/docker/:z

除此之外,各个节点上的harbor配置与上述配置完全一致。

七、共享session设置

到harbor的请求被负载均衡分发到多个node上的harbor实例上,这样就有了session共享的需求。Harbor对此已经给予了支持。在ui组件的代码中,我们发现ui在初始化时使用Getenv获取”_REDIS_URL”这个环境变量的值,因此我们只需要将_REDIS_URL这个环境变量配置到各个节点harbor ui组件的env文件中即可:

// common/config/adminserver/env

LOG_LEVEL=debug
CONFIG_PATH=/etc/ui/app.conf
UI_SECRET=LuAwkKUtYjF4l0mQ
JOBSERVICE_SECRET=SmsO1kVo4SrmgOIp
GODEBUG=netdns=cgo
_REDIS_URL=redis_ip:6379,100,redis_password,0

重新up harbor后,session共享生效。

不过光有一个外部redis存储共享session还不够,请求在多个harbor实例中的registry组件中进行鉴权需要harbor各个实例share相同的key和certificate。好在,我们的多harbor实例通过ceph共享存储,key和cert本就是共享的,都存放在目录:/mnt/cephfs/harbor/data/cert/的下边,因此也就不需要在各个harbor实例间同步key和cert了。

八、更换为域名访问

我们有通过域名访问docker registry的需求,那么直接通过域名访问harbor ui和registry是否可行呢?这要看harbor nginx的配置:

# docker ps |grep nginx
fa92765e8871        vmware/nginx:1.11.5-patched   "nginx -g 'daemon off"   3 hours ago
Up 3 hours          0.0.0.0:443->443/tcp, 0.0.0.0:4443->4443/tcp, 0.0.0.0:8060->80/tcp               nginx

# docker exec fa92765e8871 cat /etc/nginx/nginx.conf

... ...
http {
   server {
    listen 80;
   ... ...

}

nginx在http server block并未对域名或ip进行匹配,因此直接将域名A地址设置为反向代理的地址或直接解析为Harbor暴露的公网ip地址都是可以正常访问harbor服务的,当然也包括image push和pull服务。

注意:如果使用域名访问harbor服务,那么就将harbor.cfg中的hostname赋值为你的”域名+端口”,并重新prepare。否则你可能会发现通过harbor域名上传的image无法pull,因为其pull的地址为由ip组成的地址,以docker push hub.tonybai.com:8989/myrepo/foo:latest为例,push成功后,docker pull hub.tonybai.com:8989/myrepo/foo:latest可能提示你找不到该image,因为harbor中该imag
e的地址可能是my_ip_address:8989/myrepo/foo:latest。

九、统一registry的证书和token service的私钥

这是在本篇文章发表之后发现的问题,针对该问题,我专门写了一篇文章:《解决登录Harbor Registry时鉴权失败的问题》,请移步这篇文章,完成HA Harbor的搭建。

十、参考资料


微博:@tonybai_cn
微信公众号:iamtonybai
github.com: https://github.com/bigwhite

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:


如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:


以太币:


如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多