Harbor高可用理论及实践(汇聚篇)

1、理论概述

什么是harbor

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,能够用来构建企业内部的Docker镜像仓库。html

harbor是基于docker registry进行了相应的企业级扩展,从而得到了更加普遍的应用,新特性包括:管理用户界面,基于角色的访问控制 ,AD/LDAP集成以及审计日志等。mysql

harbor要解决的问题

以Docker为表明的容器技术的出现,改变了传统的交付方式。经过把业务及其依赖的环境打包进Docker镜像,解决了开发环境和生产环境的差别问题,提高了业务交付的效率。如何高效地管理和分发Docker镜像?是众多企业须要考虑的问题。linux

有了docker自带的registry为何还要用harbor

  1. harbor的安全机制

能够根据角色灵活的进行权限控制,如访客只需给pull权限便可nginx

  1. harbor的镜像同步机制

为何须要镜像同步git

1. 对系统稳定性要求高,须要多个仓库保证高可用性

2. 更经常使用的场景是,在企业级软件环境中,会在软件开发的不一样阶段存在不一样的镜像仓库

传统镜像同步方式是采用RSYNC服务来定义两个仓库之间的镜像数据同步!!github

harbor同步机制web

1. 采用用Harbor本身的API来进行镜像下载和传输,做到与底层存储环境解耦。 

2. 利用任务调度和监控机制进行复制任务的管理,保障复制任务的健壮性。在同步过程当中,若是源镜像已删除,Harbor会自动同步删除远端的镜像。在镜像同步复制的过程当中,Harbor会监控整个复制过程,遇到网络等错误,会自动重试。 


3. 提供复制策略机制保证项目级的复制需求。在Harbor中,能够在项目中建立复制策略,来实现对镜像的同步。与Docker Registry的不一样之处在于,Harbor的复制是推(PUSH)的策略,由源端发起,而Docker Registry的复制是拉(PULL)的策略,由目标端发起。
  1. 可利用图形界面进行镜像等等的管理

参考文章redis

  1. 提供分层传输机制,优化网络传输

Docker镜像是是分层的,而若是每次传输都使用全量文件(因此用FTP的方式并不适合),显然不经济。必须提供识别分层传输的机制,以层的UUID为标识,肯定传输的对象。sql

harbor的架构组件

  • Harbor在架构上主要由五个组件构成:
1. Proxy:Harbor的registry,UI, token等服务,经过一个前置的反向代理统一接收浏览器、Docker客户端的请求,并将请求转发给后端不一样的服务。

2. Registry: 负责储存Docker镜像,并处理docker push/pull 命令。因为咱们要对用户进行访问控制,即不一样用户对Dockerimage有不一样的读写权限,Registry会指向一个token服务,强制用户的每次docker pull/push请求都要携带一个合法的token,Registry会经过公钥对token 进行解密验证。

3. Core services: 这是Harbor的核心功能,主要提供如下服务:

    UI:提供图形化界面,帮助用户管理registry上的镜像(image), 并对用户进行受权。

    webhook:为了及时获取registry 上image状态变化的状况, 在Registry上配置webhook,把状态变化传递给UI模块。

    token 服务:负责根据用户权限给每一个docker push/pull命令签发token. Docker 客户端向Regiøstry服务发起的请求,若是不包含token,会被重定向到这里,得到token后再从新向Registry进行请求。

4. Database:为core services提供数据库服务,负责储存用户权限、审计日志、Dockerimage分组信息等数据。

5. Log collector:为了帮助监控Harbor运行,负责收集其余组件的log,供往后进行分析。
  • Harbor每一个组件实现方式

Harbor的每一个组件都是以Docker容器的形式构建的,所以很天然地,咱们使用Docker Compose来对它进行部署。docker

在源代码中(https://github.com/vmware/harbor), 用于部署Harbor的Docker Compose 模板位于/Deployer/docker-compose.yml. 打开这个模板文件,会发现Harbor由5个容器组成:

1. proxy:由Nginx 服务器构成的反向代理。

2. registry:由Docker官方的开源registry 镜像构成的容器实例。

3. ui: 即架构中的coreservices, 构成此容器的代码是Harbor项目的主体。

4. mysql: 由官方MySql镜像构成的数据库容器。

5. log: 运行着rsyslogd的容器,经过log-driver的形式收集其余容器的日志。

这几个容器经过Docker link的形式链接在一块儿,这样,在容器之间能够经过容器名字互相访问。对终端用户而言,只须要暴露proxy (即Nginx)的服务端口。

Harbor工做原理

因为只说干货比较抽象,咱们用具体的命令做为表现形式研究工做流程

  • docker login登陆命令

假设咱们将Harbor部署在IP 为192.168.1.10的机器上。用户经过docker login命令向这个Harbor服务发起登陆请求:

docker login 192.168.1.10

当用户输入所需信息并点击回车后,Docker 客户端会向地址 “192.168.1.10/v2/” 发出HTTP GET请求。 Harbor的各个容器会经过如下步骤处理:

1.. 1.10机器上收到该请求,会有映射到宿主机80端口的容器接收到。根据匹配规则,容器中Nginx将请求转发给registry容器;

2.. registry容器,因为基于token认证,registry返回错误代码401,提示docker客户端访问token去访问token服务绑定的URL。在harbor中,这个URL指向的是CoreServices(核心服务组件);

3.. Docker  客户端在接到这个错误代码后,会向token服务的URL发出请求,并根据HTTP协议的BasicAuthentication规范,将用户名密码组合并编码,放在请求头部(header);

4.. 这个请求经过1.10:80发送到proxy容器后,Nginx根据规则吧请求转发给UI容器,UI容器监听token服务网址的处理程序,接收到请求后,将请求头解码,获得了用户名和密码;

5.. 获得用户名、密码后,UI容器中的代码会查询数据库,将用户名、密码与mysql容器中的数据进行比对。比对成功的话,UI容器返回表示成功状态吗,用秘钥生成token,放在响应体中返回给docker客户端

docker push 192.168.1.10/library/hello-word

1.. docker客户端重复login的过程,首先发送请求到registry,以后获得token服务的地址;

2.. 以后,docker客户端在访问UI容器的token服务时会提供额外的信息,指明它要申请一个对library/hello-word进行push操做的token;

3.. token服务在通过Nginx转发获得了这个请求后,访问数据库合适当前用户是否有权限对该image进行push操做。若是有权限,会把image信息以及push动做进行编码,而且用私钥签名,生成token返回给docker客户端

4.. 获得token以后docker客户端将token放在请求头部,向registry发出请求,师徒开始推送image。registry收到请求后会用公钥解码token而且进行核对,一切成功后,image传输就开始了

参考文章

harbor几个高可用方案参考文章——理论

2、部署harbor及其主从复制

环境

主机名 IP地址 角色
harbor1 192.168.111.3 harbor仓库
harbor2 192.168.111.4 harbor备份仓库
client 192.168.111.5 docker客户端

本案例搭建harbor镜像仓库的高可用,可是因为harbor并无相应官方方案推荐,本案例只是简单采用主主高可用,而且基于keepalived的VIP实现

  • 部署docker
  • 三台机器部署社区版docker
[root@localhost ~]# yum -y install yum-utils device-mapper-persistent-data lvm2
#安装依赖

[root@localhost ~]# wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo
#下载docker的repo

[root@localhost ~]# yum -y install docker-ce

[root@localhost ~]# mkdir /etc/docker
[root@localhost ~]# vim /etc/docker/daemon.json

{
        "registry-mirrors":["https://*******.mirror.aliyuncs.com"]
}
#阿里云镜像加速

#systemctl start docker

地址须要我的前往阿里云得到,参考这篇文档

在本案例中,一开始我初心是想要部署一个基于https的高可用harbor镜像仓库,可是后来生成证书时发现应该是必需要用域名才能够,可是若是用域名的话,个人高可用VIP如何实现,故去除https的配置,用普通链接

  • 部署harbor
  • 两个harbor部署

从 github harbor 官网 release 页面下载指定版本的安装包。

一、在线安装包
    $ wget https://github.com/vmware/harbor/releases/download/v1.1.2/harbor-online-installer-v1.1.2.tgz
    $ tar xvf harbor-online-installer-v1.1.2.tgz
二、离线安装包
    $ wget https://github.com/vmware/harbor/releases/download/v1.1.2/harbor-offline-installer-v1.1.2.tgz
    $ tar xvf harbor-offline-installer-v1.1.2.tgz

[root@harbor1 ~]# mv harbor /usr/local/
[root@harbor1 ~]# vim /usr/local/harbor/harbor.cfg
hostname = 192.168.111.3
ui_url_protocol = http


[root@harbor2 cert]# vim /usr/local/harbor/harbor.cfg
hostname = 192.168.111.4
ui_url_protocol = http
  • 两个harbor部署docker-compose
curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m`
[root@harbor2 ~]# mv docker-compose /usr/local/bin/docker-compose
[root@harbor2 ~]# chmod +x !$
chmod +x /usr/local/bin/docker-compose
  • 启动配置
[root@harbor2 ~]# sh /usr/local/harbor/install.sh 
[root@harbor1 ~]# sh /usr/local/harbor/install.sh
#此过程比较慢

浏览器访问测试http://192.168.111.3/http://192.168.111.4/

  • 测试
[root@localhost anchors]# docker pull cirros

[root@localhost anchors]# docker login 192.168.111.3
Username: admin
Password: 

[root@localhost anchors]# docker tag cirros:latest 192.168.111.3/joinbest1/cirros:test1
[root@localhost anchors]# docker push 192.168.111.3/joinbest1/cirros:test1
The push refers to repository [192.168.111.3/joinbest1/cirros]
abbd6d6ac643: Pushed 
75b99987219d: Pushed 
0cc237193a30: Pushed 
test1: digest: sha256:96137d51e0e46006243fa2403723eb47f67818802d1175b5cde7eaa7f19446bd size: 943

  • 部署主主复制(实现任何一个镜像仓库有改动,都要同步到另外一侧)

两端都是如此

这时,在对端harbor已经能够看到刚才建立的测试镜像了。

因为部署是主主,对端harbor仓库也要进行以上操做

  • 小测试
[root@localhost anchors]# docker tag cirros:latest 192.168.111.4/joinbest1/cirros:test2
[root@localhost anchors]# docker push 192.168.111.4/joinbest1/cirros:test2 
The push refers to repository [192.168.111.4/joinbest1/cirros]
abbd6d6ac643: Layer already exists 
75b99987219d: Layer already exists 
0cc237193a30: Layer already exists 
test2: digest: sha256:96137d51e0e46006243fa2403723eb47f67818802d1175b5cde7eaa7f19446bd size: 943
#上面的部分输出含义是该层数据已经存在,可是不影响,由于在harbor中并非每一个镜像都要全量上传,而是分层存储,更利于节省空间,以层的UUID为标识

这时两个仓库上都有了该镜像

也就是说,不管我再任何一个仓库操做,都会同步给其它的任何仓库

  • 部署keepalived高可用实现

本案例太多因素出自笔者主观,也许可靠,也许不可靠

两台harbor安装keepalived
#yum -y install keepalived

[root@harbor1 harbor]# cat /etc/keepalived/keepalived.conf 
! Configuration File for keepalived

global_defs {
   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   script_user root
   #须要制定脚本运行用户
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id LVS_DEVEL
   vrrp_skip_check_adv_addr
   vrrp_garp_interval 0
   vrrp_gna_interval 0
}
vrrp_script check_harbor {
    script "/opt/harbor.sh"
    interval 2
    weight 20

}
#使用监控脚原本监控自身80端口,由于他是整个harbor的访问入口

vrrp_instance VI_1 {
    state MASTER
    interface ens32
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.111.100/32 dev ens32 label ens32:2
    }
    track_script {
    check_harbor
    } 
}

--------------注意修改关键配置项------------
[root@harbor2 harbor]# cat /etc/keepalived/keepalived.conf 
! Configuration File for keepalived

global_defs {
   notification_email {
     acassen@firewall.loc
     failover@firewall.loc
     sysadmin@firewall.loc
   }
   script_user root
   #须要制定脚本运行用户
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   router_id LVS_DEVEL1
   vrrp_skip_check_adv_addr
   vrrp_garp_interval 0
   vrrp_gna_interval 0
}
vrrp_script check_harbor {
    script "/opt/harbor.sh"
    interval 2
    weight 20

}

vrrp_instance VI_1 {
    state BACKUP
    interface ens32
    virtual_router_id 51
    priority 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.111.100/32 dev ens32 label ens32:2
    }
    track_script {
    check_harbor
    } 
}


----------故障切换脚本-----------
[root@harbor1 harbor]# vim /opt/harbor.sh 

#!/bin/bash

sum=`netstat -lnpt | grep -wo 80 | wc -l`

if [ $sum -eq 0 ]; then
        pkill -9 keepalived
fi

[root@harbor1 harbor]# chmod +x /opt/harbor.sh 


[root@localhost anchors]# vim /usr/lib/systemd/system/docker.service 
#主机3

ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock  --insecure-registry 192.168.111.3 --insecure-registry 192.168.111.4 --insecure-registry 192.168.111.100
#将VIP添加入可不安全访问

[root@localhost anchors]# docker login 192.168.111.100
Authenticating with existing credentials...
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
#目前是能够访问

[root@localhost anchors]# docker images
REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
192.168.111.4/joinbest1/cirros   test2               bc94bceaae77        5 months ago        10.3MB
cirros                           latest              bc94bceaae77        5 months ago        10.3MB
192.168.111.3/joinbest1/cirros   test1               bc94bceaae77        5 months ago        10.3MB
#镜像也正常

[root@harbor1 harbor]# docker-compose stop
Stopping harbor-jobservice  ... done
Stopping nginx              ... done
Stopping harbor-ui          ... done
Stopping harbor-adminserver ... done
Stopping redis              ... done
Stopping registry           ... done
Stopping harbor-db          ... done
Stopping harbor-log         ... done
#测试故障切换

[root@harbor2 harbor]# ip a | grep ens32
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.111.4/24 brd 192.168.111.255 scope global noprefixroute ens32
    inet 192.168.111.100/32 scope global ens32:2
#ip切过来了
  • 测试keepalived的VIP的写入是否同步其他仓库
[root@localhost anchors]# docker tag cirros:latest 192.168.111.100/joinbest1/cirros:test3
[root@localhost anchors]# docker push 192.168.111.100/joinbest1/cirros:test3 
The push refers to repository [192.168.111.100/joinbest1/cirros]
abbd6d6ac643: Layer already exists 
75b99987219d: Layer already exists 
0cc237193a30: Layer already exists 
test3: digest: sha256:96137d51e0e46006243fa2403723eb47f67818802d1175b5cde7eaa7f19446bd size: 943
#目前正常写入

#经过web界面对111.3和111.4的joinbest1仓库看看镜像是否同步
#这里先把刚才测试关闭的harbor1仓库给启动
Starting log         ... done
Starting registry    ... done
Starting mysql       ... done
Starting adminserver ... done
Starting ui          ... done
Starting redis       ... done
Starting jobservice  ... done
Starting proxy       ... done
#好,我这里是所有能够正常同步,就算是刚才客户端上传时,harbor1是宕机的,可是从新启动后,仍是会进行同步,不过应该是有触发机制,我后来又建立了一个test4,刚开始harbor1是没有test3的,我上传test4以后出发了同步机制,这时harbor1的镜像仓库也是正常工做了,便将3.4一块儿同步过去了

3、总结

  • 1.本高可用方案,生产环境有待考量
  • 2.刚开始是尝试着作基于https的链接,可是由于诸多因素:ssl工具或许不支持基于ip的加密只支持域名,可是要配合keepalived确定是须要ip的,故舍弃https;通过思考,若是只是作私有仓库使用的话,那么不使用https或许也是很是可行的。
  • 3.keepalived很好用,颇有感触
  • 4.本案例环境都是在测试环境,在生产环境的相应压力中,并不知道会有怎么样的后果

报错

raise Exception("Error: the protocol must be https when Harbor is deployed with Notary")
Exception: Error: the protocol must be https when Harbor is deployed with Notary
#我输入得命令是sh install.sh --with-notary --with-clair
#其中使用--with-notary含义是启用镜像签名;必须是https才能够,把该选项去掉便可·




[root@localhost anchors]# docker login 192.168.111.3
Username: admin
Password: 
Error response from daemon: Get https://192.168.111.3/v2/: dial tcp 192.168.111.3:443: connect: connection refused
#客户端链接报错,默认使用的是https我须要修改成可使用http来进行链接

[root@localhost anchors]# vim /usr/lib/systemd/system/docker.service 
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock  --insecure-registry 192.168.111.3 --insecure-registry 192.168.111.4