一、理論概述
什么是harbor
Harbor是一個用於存儲和分發Docker鏡像的企業級Registry服務器,可以用來構建企業內部的Docker鏡像倉庫。
harbor是基於docker registry進行了相應的企業級擴展,從而獲得了更加廣泛的應用,新特性包括:管理用戶界面,基於角色的訪問控制 ,AD/LDAP集成以及審計日志等。
harbor要解決的問題
以Docker為代表的容器技術的出現,改變了傳統的交付方式。通過把業務及其依賴的環境打包進Docker鏡像,解決了開發環境和生產環境的差異問題,提升了業務交付的效率。如何高效地管理和分發Docker鏡像?是眾多企業需要考慮的問題。
有了docker自帶的registry為什么還要用harbor
- harbor的安全機制
可以根據角色靈活的進行權限控制,如訪客只需給pull權限即可
- harbor的鏡像同步機制
為什么需要鏡像同步
1. 對系統穩定性要求高,需要多個倉庫保證高可用性
2. 更常用的場景是,在企業級軟件環境中,會在軟件開發的不同階段存在不同的鏡像倉庫
傳統鏡像同步方式是采用RSYNC服務來定義兩個倉庫之間的鏡像數據同步!!
harbor同步機制
1. 采用用Harbor自己的API來進行鏡像下載和傳輸,作到與底層存儲環境解耦。
2. 利用任務調度和監控機制進行復制任務的管理,保障復制任務的健壯性。在同步過程中,如果源鏡像已刪除,Harbor會自動同步刪除遠端的鏡像。在鏡像同步復制的過程中,Harbor會監控整個復制過程,遇到網絡等錯誤,會自動重試。
3. 提供復制策略機制保證項目級的復制需求。在Harbor中,可以在項目中創建復制策略,來實現對鏡像的同步。與Docker Registry的不同之處在於,Harbor的復制是推(PUSH)的策略,由源端發起,而Docker Registry的復制是拉(PULL)的策略,由目標端發起。
- 可利用圖形界面進行鏡像等等的管理
- 提供分層傳輸機制,優化網絡傳輸
Docker鏡像是是分層的,而如果每次傳輸都使用全量文件(所以用FTP的方式並不適合),顯然不經濟。必須提供識別分層傳輸的機制,以層的UUID為標識,確定傳輸的對象。
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來對它進行部署。
在源代碼中(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及其主從復制
環境
主機名 | 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 頁面下載指定版本的安裝包。
1、在線安裝包
$ 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
2、離線安裝包
$ 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一起同步過去了
三、總結
- 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