Harbor折腾升级记
Harbor是vmware中国开发的一款企业级的DockerRegistry服务器,我们内部也是有搭建了一个Harbor,但是版本是0.5,对于当前最新的release版本1.5.2而言已经太老了,确实也有一些问题,比如不支持多级的镜像名称,某些情况下会触发bug导致panic。
所以需要升级一下,既然考虑升级了,就干脆升级到最新的版本1.5.2了。首先说一下目前的Harbor,官方提供的离线安装包里,默认是本地启动一个MySQL,将Harbor需要的一些数据存储在本地的MySQL中的,这个是不能接受的,所以在之前的部署中,是使用了外部的一个MySQL,同样,registry的存储在线上也是使用了共享存储,保证可用性。
不过Harbor的1.5.2版本对于0.5版本变化还是比较大的,首先是增加了adminserver这个角色,将所有的配置都拿到adminserver中存储,ui组件通过http请求定期向adminserver请求当前最新的配置信息,其次是数据库结构,新版本和旧版本相比数据库结构发生了很大的变化。
对于升级操作,官方也提供了解决方案,可以参考migration_guide进行升级,升级工具的镜像官方也是提供了,但是这其中存在一个问题,就是升级工具依赖本地的MySQL,也就是说,这个工具只能工作在MySQL是Harbor离线安装包启动的情况下,如果使用了外部的MySQL,这个升级工具就无法直接使用了。
所以呢,最终还是需要去看一下官方的升级工具是如何实现的,看能否通过其他办法手动升级,于是就花了点时间看了一下代码,找到了最后的实现方式,具体的代码在alembic/mysql这个目录下,原理也很简单,官方使用了一个Python的工具alembic
实现了数据库结构的版本管理。
手动运行数据库升级,首先需要安装alembic
工具,可以通过pip
安装,或者针对不同的发行版找对应的软件包。
下面开始操作:
目录下有一个alembic.tpl
文件,这个文件是alembic运行所需要配置文件的模板,我们可以通过source alembic.tpl > alembic.ini
实例化一个配置文件,然后打开配置文件,可以看到最关键的两个配置:
script_location = /harbor-migration/db/alembic/mysql/migration_harbor
sqlalchemy.url = mysql://:@localhost:3306/registry?unix_socket=/var/run/mysqld/mysqld.sock
分别是脚本路径和连接MySQL的地址,默认的在工具镜像中脚本路径是放在/harbor-migration/
这个目录下,这个需要根据机器上的情况修改到对应目录,然后sqlalchemy.url
配置也很明显,需要修改为实际线上数据库的连接地址。
修改完成后,就可以调用alembic
工具升级数据库了:
export PYTHONPATH=`pwd` # 添加PYTHONPATH
alembic -c alembic.ini current # 查看当前数据库版本
alembic -c alembic.ini upgrade 1.5 # 升级到1.5版本
alembic -c alembic.ini current # 查看升级后数据库版本
脚本运行完成后,数据库结构就能升级成功了。需要提醒的是,建议先导出线上数据到单独的一个数据库中做一下测试,确认没有问题后再进行线上操作,另外,做好备份!
数据库升级完成,该升级各个组建了,新版Harbor的配置也有比较大的变化,默认安装时会有个harbor.cfg
,官方也提供了一个工具去做harbor.cfg
的版本迁移,但是我们也不需要通过这个工具了,在安装时,prepare
脚本会根据harbor.cfg
文件生成对应组件所需要的配置和env文件,所以我们直接使用新版的默认harbor.cfg
生成配置文件,再对比老的配置文件,然后直接修改最后的配置文件保持一致。
需要注意的是,新版Harbor大部分的配置都集中在common/config/adminserver/env
文件中,而adminserver是老版本没有的角色,老版本大部分配置都集中在common/config/ui/app.conf
和common/config/ui/env
中。
最终我们采取的办法是,先用默认配置生成一份实际的配置文件,特别是common/config/adminserver/env
文件,再根据老版本的线上配置文件比对,直接修改生成后的配置文件而不是harbor.cfg
,相当于新旧配置文件做一次merge。等配置文件准备完成,直接调用docker-compose up -d
以新配置文件启动Harbor容器。
总体来说,升级还是很顺利的,具体的步骤如下:
1. 备份数据库
2. 停止并删除老Harbor容器
3. 通过alembic升级数据库版本
4. 通过docker-compose up -d启动新版本Harbor容器
升级过程很顺利,容器启动后工作正常,但是遇到一个小问题,就是Harbor管理平台里显示的日志的时间差了8个小时,这个其实是在容器化中很容易遇到的一个情况,容器中的时区和宿主机不一致,毕竟我们在中国嘛,还是应该用北京时间,虽然问题挺常见,但是在Harbor解决这个问题还是费了一些力气。
正常的思路,就是修改一下docker-compose.yaml
将/usr/share/zoneinfo/Asia/Shanghai
挂载到/etc/localtime
,这样容器内部时区就正确了。测试了一下,确实如此,在容器中执行date命令已经可以正常返回正确的时间。
但是日志的时间并没有变化,这个确实令人费解,由于Harbor的镜像相关日志都是写到数据库中的,所以还是需要看一下具体插入日志的代码,看是否能够发现一些问题:
// 需要记录日志的地方调用dao.AddAccessLog插入日志,可以看到OpTime就是time.Now()。
go func() {
if err = dao.AddAccessLog(
models.AccessLog{
Username: p.SecurityCtx.GetUsername(),
ProjectID: projectID,
RepoName: pro.Name + "/",
RepoTag: "N/A",
Operation: "create",
OpTime: time.Now(),
}); err != nil {
log.Errorf("failed to add access log: %v", err)
}
}()
// AddAccessLog具体实现
// AddAccessLog persists the access logs
func AddAccessLog(accessLog models.AccessLog) error {
// the max length of username in database is 255, replace the last
// three charaters with "..." if the length is greater than 256
if len(accessLog.Username) > 255 {
accessLog.Username = accessLog.Username[:252] + "..."
}
o := GetOrmer()
_, err := o.Insert(&accessLog)
return err
}
看到这边,基本可以确定和ORM实现没有关系了,因为用的mysql驱动是go-sql-driver/mysql
,而这个驱动可以单独设置时区,而且驱动默认的时区是UTC
,所以需要修改,具体可以参考mysql#loc,于是问题就变成了如何将这个参数传到驱动了,继续看代码:
// NewMySQL returns an instance of mysql
func NewMySQL(host, port, usr, pwd, database string) Database {
return &mysql{
host: host,
port: port,
usr: usr,
pwd: pwd,
database: database,
}
}
// Register registers MySQL as the underlying database used
func (m *mysql) Register(alias ...string) error {
if err := utils.TestTCPConn(m.host+":"+m.port, 60, 2); err != nil {
return err
}
if err := orm.RegisterDriver("mysql", orm.DRMySQL); err != nil {
return err
}
an := "default"
if len(alias) != 0 {
an = alias[0]
}
// 关键代码!根据配置组合一个MySQL URI,初始化驱动。
conn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", m.usr,
m.pwd, m.host, m.port, m.database)
return orm.RegisterDataBase(an, "mysql", conn)
}
也就是说,可以在database名字后面加上?loc=Local
来让MySQL驱动使用本地的时区设置。于是就简单了,直接修改common/config/adminserver/env
文件,将其中的MYSQL_DATABASE=registry
改成MYSQL_DATABASE=registry?loc=Local
就行了,虽然有点hack,但是,起码不用修改代码再编译打镜像了吧~
一波hack操作,重启对应组建,启动完成,果然时区也正常了~
PS:Harbor 1.6为了保持和Clair数据库的一致性,将MySQL迁移到PostgreSQL了,所以上面的做法可能就不适用于1.6了,由于暂时内部还没有PostgreSQL的支持,也不需要Clair,所以短期内没有考虑升级1.6,等哪天需要升级了,再看相应的解决方案吧。