Harbor镜像迁移
背景说明
在早期生产环境尝试使用docker的时候,虽然使用了harbor作为镜像仓库,但是并没有做好相关存储规划,所有的镜像都直接存储到了harbor本地。随着业务发展,本地存储已无法满足镜像存储需求。
解决方案有两种:
- 使用共享文件系统存储,比如glusterfs,直接挂载本地的harbor存储目录当中。在此之前,只需要先把harbor本地目录中的文件拷贝到glusterfs当中即可。
- 部署一套新的harbor,直接使用共享存储作为镜像后端存储。将现有harbor中的所有镜像全量同步到新的harbor当中。
这两种方式操作起来从复杂度上来讲,都还好。但我们线上glusterfs面临下线。所以选择了第二种方式。
第二种方式,其实就是找一台新的机器,部署一套新的harbor,直接使用新的存储,无论是文件系统也好,对象存储也罢。然后使用harbor自带的主从复制即可完成镜像的全量同步。在此过程中,甚至不用停机。
但是,第二种方式,要求新部署的harbor版本与原harbor版本一致。我们原来的harbor版本比较低,而且很久没升级了,我这人有强迫症,觉得既然要搞,就干脆一步到位。直接使用最新版本的harbor部署了新的节点。这样一来,就没办法再使用原生的主从同步方式来完成镜像的同步了。
于是只好自己写脚本,基于harbor的rest api来完成镜像的导出与导入。
方案实现
先简单说下脚本的整执行流程:
- 先实现一个request来完成harbor的登录,获取session
- 获取所有的project
- 循环所有的project,获取所有的repositories
- 获取repositories的所有tag
- 根据repositories和tag拼接完整的镜像名称
- 连接两边的harbor,通过docker pull的方式从原harbor中拉取镜像,再通过docker push的方式将镜像推送到新harbor当中,然后删除本地镜像。
- 在上面的过程中,还做了个事情,每个镜像推送之后,都会将其镜像名称作为key,将其状态作为value保存到redis中。以备事后处理推送失败的镜像。
依赖组件:
- redis: 上面说了,依赖redis保存其推送状态
后续改进:
- 因为我这是在内网中跑,没有对http请求作任何校验,默认直接认为其成功,没做异常处理。
- 当前脚本可以开多个进程同时跑,以提供高好的性能。依赖redis对当前正在执行的镜像加锁。更好的方式,是在脚本中,直接以多进程的方式来实现。
下面直接上代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import subprocess
import json
import redis
import sys
class RequestClient(object):
def __init__(self, login_url, username, password):
self.username = username
self.password = password
self.login_url = login_url
self.session = requests.Session()
self.login()
def login(self):
self.session.post(self.login_url, params={"principal": self.username, "password": self.password})
class HarborRepos(object):
def __init__(self, harbor_domain, harbor_new_domain, password, new_password, schema="https", new_schema="https",
username="admin", new_username="admin"):
self.schema = schema
self.harbor_domain = harbor_domain
self.harbor_new_domain = harbor_new_domain
self.harbor_url = self.schema + "://" + self.harbor_domain
self.login_url = self.harbor_url + "/login"
self.api_url = self.harbor_url + "/api"
self.pro_url = self.api_url + "/projects"
self.repos_url = self.api_url + "/repositories"
self.username = username
self.password = password
self.client = RequestClient(self.login_url, self.username, self.password)
self.new_schema = new_schema
self.harbor_new_url = self.new_schema + "://" + self.harbor_new_domain
self.login_new_url = self.harbor_new_url + "/c/login"
self.api_new_url = self.harbor_new_url + "/api"
self.pro_new_url = self.api_new_url + "/projects"
self.new_username = new_username
self.new_password = new_password
self.new_client = RequestClient(self.login_new_url, self.new_username, self.new_password)
def __fetch_pros_obj(self):
# TODO
self.pros_obj = self.client.session.get(self.pro_url).json()
return self.pros_obj
def fetch_pros_id(self):
self.pros_id = []
# TODO
pro_res = self.__fetch_pros_obj()
for i in pro_res:
self.pros_id.append(i['project_id'])
return self.pros_id
def fetch_pro_name(self, pro_id):
# TODO
pro_res = self.__fetch_pros_obj()
for i in pro_res:
if i["project_id"] == pro_id:
self.pro_name = i["name"]
return self.pro_name
# def judge_pros(self,pro_name):
# res = self.new_client.session.head(self.pro_new_url,params={"project_name": pro_name})
# print(res.status_code)
# if res.status_code == 404:
# return False
# else:
# return True
def create_pros(self, pro_name):
'''
{
"project_name": "string",
"public": 1
}
'''
pro_res = self.__fetch_pros_obj()
pro_obj = {}
pro_obj["metadata"]={}
public = "false"
for i in pro_res:
if i["name"] == pro_name:
pro_obj["project_name"] = pro_name
if i["public"]:
public = "true"
pro_obj["metadata"]["public"] = public
# pro_obj["metadata"]["enable_content_trust"] = i["enable_content_trust"]
# pro_obj["metadata"]["prevent_vul"] = i["prevent_vulnerable_images_from_running"]
# pro_obj["metadata"]["severity"] = i["prevent_vulnerable_images_from_running_severity"]
# pro_obj["metadata"]["auto_scan"] = i["automatically_scan_images_on_push"]
headers = {"content-type": "application/json"}
print(pro_obj)
res = self.new_client.session.post(self.pro_new_url, headers=headers, data=json.dumps(pro_obj))
if res.status_code == 409:
print("\033[32m 项目 %s 已经存在!\033[0m" % pro_name)
return True
elif res.status_code == 201:
# print(res.status_code)
print("\033[33m 创建项目%s成功!\033[0m" % pro_name)
return True
else:
print(res.status_code)
print("\033[35m 创建项目%s失败!\033[0m" % pro_name)
return False
def fetch_repos_name(self, pro_id):
self.repos_name = []
repos_res = self.client.session.get(self.repos_url, params={"project_id": pro_id})
# TODO
for repo in repos_res.json():
self.repos_name.append(repo['name'])
return self.repos_name
def fetch_repos(self, repo_name):
self.repos = {}
tag_url = self.repos_url + "/" + repo_name + "/tags"
# TODO
for tag in self.client.session.get(tag_url).json():
full_repo_name = self.harbor_domain + "/" + repo_name + ":" + tag["name"]
full_new_repo_name = self.harbor_new_domain + "/" + repo_name + ":" + tag["name"]
self.repos[full_repo_name] = full_new_repo_name
return self.repos
def migrate_repos(self, full_repo_name, full_new_repo_name, redis_conn):
# repo_cmd_dict = {}
if redis_conn.exists(full_repo_name) and redis_conn.get(full_repo_name) == "1":
print("\033[32m镜像 %s 已经存在!\033[0m" % full_repo_name)
return
else:
cmd_list = []
pull_old_repo = "docker pull " + full_repo_name
tag_repo = "docker tag " + full_repo_name + " " + full_new_repo_name
push_new_repo = "docker push " + full_new_repo_name
del_old_repo = "docker rmi -f " + full_repo_name
del_new_repo = "docker rmi -f " + full_new_repo_name
cmd_list.append(pull_old_repo)
cmd_list.append(tag_repo)
cmd_list.append(push_new_repo)
cmd_list.append(del_old_repo)
cmd_list.append(del_new_repo)
# repo_cmd_dict[full_repo_name] = cmd_list
sum = 0
for cmd in cmd_list:
print("\033[34m Current command: %s\033[0m" % cmd)
ret = subprocess.call(cmd, shell=True)
sum += ret
if sum == 0:
print("\033[32m migrate %s success!\033[0m" % full_repo_name)
redis_conn.set(full_repo_name, 1)
else:
print("\033[33m migrate %s faild!\033[0m" % full_repo_name)
redis_conn.set(full_repo_name, 0)
return
if __name__ == "__main__":
harbor_domain = "hub.test.com"
harbor_new_domain = "hub-new.test.com"
re_pass = "xxxxxxx"
re_new_pass = "xxxxxxx"
pool = redis.ConnectionPool(host='localhost', port=6379,
decode_responses=True) # host是redis主机,需要redis服务端和客户端都起着 redis默认端口是6379
redis_conn = redis.Redis(connection_pool=pool)
res = HarborRepos(harbor_domain, harbor_new_domain, re_pass, re_new_pass)
# pros_id = res.fetch_pro_id()
for pro_id in res.fetch_pros_id():
#pro_id = 13
pro_name = res.fetch_pro_name(pro_id)
# print(pro_name)
# ret = res.judge_pros(pro_name)
# print(ret)
res.create_pros(pro_name)
#sys.exit()
for pro_id in res.fetch_pros_id():
repos_name = res.fetch_repos_name(pro_id=pro_id)
for repo_name in repos_name:
repos = res.fetch_repos(repo_name=repo_name)
for full_repo_name, full_new_repo_name in repos.items():
res.migrate_repos(full_repo_name, full_new_repo_name, redis_conn)
Harbor镜像迁移的更多相关文章
- 使用nodejs+ harbor rest api 进行容器镜像迁移
最近因为基础设施调整,需要进行harbor 镜像仓库的迁移,主要是旧版本很老了,不想使用,直接 打算部署新的,原以为直接使用复制功能就可以,但是发现版本差异太大,直接失败,本打算使用中间 版本过度进行 ...
- 阿里云开源 image-syncer 工具,容器镜像迁移同步的终极利器
为什么要做这个工具? 由于阿里云上的容器服务 ACK 在使用成本.运维成本.方便性.长期稳定性上大大超过公司自建自维护 Kubernets 集群,有不少公司纷纷想把之前自己维护 Kubernetes ...
- harbor镜像仓库-02-https访问配置
harbor镜像仓库-02-https访问配置 harbordockerhttps harbor搭建部署参考上一章节 harbor镜像仓库-01-搭建部署 Harbor默认使用http,给harbor ...
- 如何把Composer镜像迁移到Laravel China 维护的镜像?
今天在更新Laravel-admin:1.6.0提示没有对应的包,后面才发现需要使用官方或者 Laravel-China 的 composer 镜像,phpcomposer 镜像已经停止维护了.怎么从 ...
- harbor镜像仓库-01-搭建部署
harbor镜像仓库-01-搭建部署 dockerregistryharbor安装部署docker-compose harbor的https配置参考另一章节harbor镜像仓库-02-https访问配 ...
- 在Linux主机使用命令行批量删除harbor镜像
在Linux主机使用命令行批量删除harbor镜像 脚本使用说明: 此脚本不是万能脚本,根据自身环境要调整很多 能用harbor的域名就不要用IP 脚本前半部分可以套用,后半部分需一步一步试错,结合 ...
- Harbor镜像漏洞扫描
Harbor镜像漏洞扫描 闲聊:我们知道 镜像安全也是容器化建设中一个很重要的环节,像一些商业软件如:Aqua就很专业但是收费也是很昂贵的,今天我们介绍下Harbor自带的镜像扫描器. 一.安装最新版 ...
- Harbor镜像删除回收?只看这篇
最近,公司的技术平台,运维的破事儿颇多.Jira无法访问,ES堆内存不足,Jenkins频繁不工作..等等等,让我这个刚入门的小兵抓心脑肝,夜不能寐,关键时刻方恨经验薄弱呀!!一波未平,一波又起,这不 ...
- Harbor镜像仓库
Harbor镜像仓库 作者 刘畅 时间 2020-7-11 微信 目录 1.下载离线安装包 1 2.安装docker 1 3.安装docker-compose 2 4.自签TLS证书 2 4.1.创建 ...
随机推荐
- #个人博客作业week2——关于代码规范的个人观点
对于这一讨论的前提我们首先要知道什么是代码规范. 在这个问题上我同意一篇参考文章的观点——代码规范不仅只编码风格.编码风格仅是代码规范的一个方面,除了编码风格,代码规范还包括函数返回值等其他方面.在我 ...
- Linux内核分析——进程的描述和进程的创建
进程的描述和进程的创建 一. 进程的描述 (一)进程控制块PCB——task_struct 1.操作系统的三大管理功能包括: (1)进程管理 (2)内存管理 (3)文件系统 2.PCB task_st ...
- Linux内核期中
Linux内核期中总结 一.计算机是如何工作的 个人理解:计算机就是通过和用户进行交互,执行用户的指令,这些指令存放在内存中,通过寄存器存储,堆栈变化,来一步步顺序执行. 二.存储程序计算机工作模型 ...
- 20135323符运锦期中总结----Linux系统的理解及学习心得
一.网易云课堂 1.各章节总结 第一周:计算机是如何工作的http://www.cnblogs.com/20135323fuyunjin/p/5222787.html 第二周:操作系统是如何工作的ht ...
- 20135327郭皓--Linux内核分析第九周 期中总结
Linux内核分析第九周 期中总结 一.知识概要 1. 计算机是如何工作的 存储程序计算机工作模型:冯诺依曼体系结构 X86汇编基础 会变一个简单的C程序分析其汇编指令执行过程 2. 操作系统是如何工 ...
- 2017-2018-2 1723《程序设计与数据结构》第九周作业 & 第二周结对编程 总结
作业地址 第九次作业:https://edu.cnblogs.com/campus/besti/CS-IMIS-1723/homework/1878 (作业界面已评分,可随时查看,如果对自己的评分有意 ...
- 快速简化Android截屏工作
1.安装Notepad++v6.9 2.插件管理器里Plugin Manager安装AndroidLogger 3.AndroidLogger里的capture功能抓取Android的当前屏幕截图到w ...
- [转帖]2016年时的新闻:ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0
ASP.NET Core 1.0.ASP.NET MVC Core 1.0和Entity Framework Core 1.0 http://www.cnblogs.com/webapi/p/5673 ...
- SQLserver 使用网络驱动器恢复数据库
1. 公司内有多台虚拟机,因为公司提供出来的机器 硬盘总是不够大...所以想到了使用 映射网络驱动器的方式进行备份恢复工作. 学到的方法主要如下: 0. 首先打开sqlcmd 启动命令行界面 1. ...
- 关于一个常用的CheckBox样式
我们在使用CheckBox的时候,原始的样式有时不能满足我们的需求,这是我们就需要更改其模板,比如我们常用的一种,在播放器中“播放”.“暂停”按钮,其实这也是一种CheckBox,只不过我们只是修改了 ...