我正在参加「金石计划5.0」
一、写在前面
在探索解决镜像问题的过程中,意外发现了在github上有一个docker-drag这样一个库。
利用docker-drag,可以直接使用Python进行Docker镜像的拉取,而无需安装Docker。
这个脚本就实现了,本地无需安装docker环境,也能拉取docker镜像了。
其实原理也是很简单:
一个docker镜像包,也是由一些文件组合而成,那我们就可以通过想办法去拿到这些文件,然后就合并成一个总文件,而最终的文件,就是我们的docker镜像包。
再通过docker load就可以将这个镜像包,load进docker环境下面了。
docker-drag源码,有兴趣的小伙伴可以直接去看,是使用python脚本编写的。
由于这个脚本拉取的是docker hub上面的镜像,如果想要改成拉取自己公司harbor仓库,估计得改改源码才行。
这里我就参考了这个脚本,改成可以拉取自己公司harbor仓库得镜像。有兴趣的小伙伴,可以接着往下看!!!
二、脚本实现
脚本内容比较多,这里我分开几块,好给大伙解析解析。(大家合并成一个文件即可)
docker_pull.py
,代码比较多,我分开几个模块展示,大家伙按顺序合并在一起即可。
1.harbor地址定义,python包依赖
python
import gzip
import hashlib
import json
import os
import shutil
import sys
import tarfile
import time
import requests
# Harbor 仓库地址
harbor_url = 'http://127.0.0.1'
harbor_username = 'llsydn'
harbor_password = 'llsydn'
harbor仓库地址和账号密码,改成自己公司的即可
2.拉取哪些镜像参数获取
python
# 命名空间、镜像名称和镜像标签
try:
print("请输入需要拉取的镜像:(例如:127.0.0.1/llsydn/nginx:1.21.4)")
input_str = input()
input_arr = input_str.split('/')
image_host = input_arr[0] # 镜像host
image_ns = input_arr[1] # 命名空间
image_name = input_arr[2].split(':')[0] # 镜像名称
image_tag = input_arr[2].split(':')[1] # 镜像标签
except Exception:
print("输入镜像格式有误,请重新操作。")
time.sleep(2)
exit(1)
print("开始拉取镜像:", input_arr[0] + '/' + image_ns + '/' + image_name + ':' + image_tag)
这里,是为了获取需要拉取的镜像,
例如:python docker_pull.py 127.0.0.1/llsydn/nginx:1.21.4
最后
127.0.0.1/llsydn/nginx:1.21.4
,就是我们要拉取的镜像,输入的参数
3.下载进度条方法定义
python
# 下载进度条
def progress_bar(ublob, nb_traits):
sys.stdout.write('\r' + ublob[7:19] + ': Downloading [')
for i in range(0, nb_traits):
if i == nb_traits - 1:
sys.stdout.write('>')
else:
sys.stdout.write('=')
for i in range(0, 49 - nb_traits):
sys.stdout.write(' ')
sys.stdout.write(']')
sys.stdout.flush()
这个只是一个下载进度显示,不是很重要。
4.下载docker镜像具体实现
python
# 拉取docker镜像
def docker_pull_image():
# 1.先get调harbor的login获取X-Harbor-Csrf-Token
auth_url = f'{harbor_url}/c/login'
auth_data = {'principal': harbor_username, 'password': harbor_password}
# 构造下次请求所需的header请求头
headers = {}
response = requests.get(auth_url)
csrf_token = response.headers.get('X-Harbor-Csrf-Token')
headers['X-Harbor-Csrf-Token'] = csrf_token
# 构造下次请求所需的cookies
_gorilla_csrf = response.cookies.get('_gorilla_csrf')
sid = response.cookies.get('sid')
cookies = {}
cookies['_gorilla_csrf'] = _gorilla_csrf
cookies['sid'] = sid
# 2.登录harbor获取token
auth_response = requests.post(auth_url, headers=headers, cookies=cookies, data=auth_data)
token = auth_response.cookies.get('sid')
# 构造下次请求所需的cookies
_gorilla_csrf = auth_response.cookies.get('_gorilla_csrf')
sid = auth_response.cookies.get('sid')
cookies = {}
cookies['_gorilla_csrf'] = _gorilla_csrf
cookies['sid'] = sid
# 构建请求头
headers = {
'Accept': '*/*',
'Authorization': f'Bearer {token}',
'Host': image_host
}
# 3.发送请求,获取镜像的manifests信息
pull_url = f'{harbor_url}/v2/{image_ns}/{image_name}/manifests/{image_tag}'
response = requests.get(pull_url, headers=headers, cookies=cookies)
layers = response.json()['layers']
# Create tmp folder that will hold the image
imgdir = 'tmp_{}_{}'.format(image_name, image_tag.replace(':', '@'))
os.mkdir(imgdir)
print('Creating image structure in: ' + imgdir)
# 4.发送请求,获取镜像的详细信息
config = response.json()['config']['digest']
pull_url = f'{harbor_url}/v2/{image_ns}/{image_name}/blobs/{config}'
confresp = requests.get(pull_url, headers=headers, cookies=cookies)
file = open('{}/{}.json'.format(imgdir, config[7:]), 'wb')
file.write(confresp.content)
file.close()
content = [{
'Config': config[7:] + '.json',
'RepoTags': [],
'Layers': []
}]
content[0]['RepoTags'].append(image_host + '/' + image_ns + "/" + image_name + ':' + image_tag)
empty_json = '{"created":"1970-01-01T00:00:00Z","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false, \
"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false, "StdinOnce":false,"Env":null,"Cmd":null,"Image":"", \
"Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null}}'
# 5.发送请求,循环下载layers对应的文件,并生成镜像.tar文件
parentid = ''
for layer in layers:
ublob = layer['digest']
# FIXME: Creating fake layer ID. Don't know how Docker generates it
fake_layerid = hashlib.sha256((parentid + '\n' + ublob + '\n').encode('utf-8')).hexdigest()
layerdir = imgdir + '/' + fake_layerid
os.mkdir(layerdir)
# Creating VERSION file
file = open(layerdir + '/VERSION', 'w')
file.write('1.0')
file.close()
# Creating layer.tar file
sys.stdout.write(ublob[7:19] + ': Downloading...')
sys.stdout.flush()
pull_url = f'{harbor_url}/v2/{image_ns}/{image_name}/blobs/{ublob}'
bresp = requests.get(pull_url, headers=headers, cookies=cookies)
# Stream download and follow the progress
bresp.raise_for_status()
unit = int(bresp.headers['Content-Length']) / 50
acc = 0
nb_traits = 0
progress_bar(ublob, nb_traits)
with open(layerdir + '/layer_gzip.tar', "wb") as file:
for chunk in bresp.iter_content(chunk_size=8192):
if chunk:
file.write(chunk)
acc = acc + 8192
if acc > unit:
nb_traits = nb_traits + 1
progress_bar(ublob, nb_traits)
acc = 0
sys.stdout.write("\r{}: Extracting...{}".format(ublob[7:19], " " * 50)) # Ugly but works everywhere
sys.stdout.flush()
with open(layerdir + '/layer.tar', "wb") as file: # Decompress gzip response
unzLayer = gzip.open(layerdir + '/layer_gzip.tar', 'rb')
shutil.copyfileobj(unzLayer, file)
unzLayer.close()
os.remove(layerdir + '/layer_gzip.tar')
print("\r{}: Pull complete [{}]".format(ublob[7:19], bresp.headers['Content-Length']))
content[0]['Layers'].append(fake_layerid + '/layer.tar')
# Creating json file
file = open(layerdir + '/json', 'w')
# last layer = config manifest - history - rootfs
if layers[-1]['digest'] == layer['digest']:
# FIXME: json.loads() automatically converts to unicode, thus decoding values whereas Docker doesn't
json_obj = json.loads(confresp.content)
del json_obj['history']
try:
del json_obj['rootfs']
except: # Because Microsoft loves case insensitiveness
del json_obj['rootfS']
else: # other layers json are empty
json_obj = json.loads(empty_json)
json_obj['id'] = fake_layerid
if parentid:
json_obj['parent'] = parentid
parentid = json_obj['id']
file.write(json.dumps(json_obj))
file.close()
# 6.生成manifest等文件
file = open(imgdir + '/manifest.json', 'w')
file.write(json.dumps(content))
file.close()
content = {image_host + '/' + image_ns + '/' + image_name: {image_tag: fake_layerid}}
file = open(imgdir + '/repositories', 'w')
file.write(json.dumps(content))
file.close()
# 7.将整个文件夹生成镜像的.tar文件,并清除临时文件
docker_tar = image_name + '_' + image_tag + '.tar'
sys.stdout.write("Creating archive...")
sys.stdout.flush()
tar = tarfile.open(docker_tar, "w")
tar.add(imgdir, arcname=os.path.sep)
tar.close()
shutil.rmtree(imgdir)
print('\rDocker image pulled: ' + docker_tar)
docker拉取镜像的具体实现,分了7个步骤:
1-2,两步是为了拿到登录harbor仓库所需要的token
3-4,两步是为了拿到镜像的manifests和详细的layers信息
5,是根据上面拿到的layers信息,去下载对应的layer.tar包
6,生成manifest等文件
7,将整个文件夹生成镜像的.tar文件,并清除临时文件
由此可见,一个docker镜像包,其实是由多个layer.tar包组合而成。
有兴趣的小伙伴,可以自己去观察一下,一个docker镜像包是由那些文件组合而成。
5.执行拉取方法
python
# 执行拉取方法
try:
docker_pull_image()
except Exception as e:
print("拉取镜像失败!")
print(e)
time.sleep(3)
三、代码运行
将以上所有代码合并成一个 docker_pull.py
文件,即可。
- 有python环境
直接执行文件docker_pull.py文件即可:
python
python docker_pull.py 127.0.0.1/llsydn/nginx:1.21.4
即可拉取127.0.0.1/llsydn/nginx:1.21.4
镜像拉取成功,会默认在当前目录生成镜像.tar文件,例如:nginx_1.21.4.tar
文件
- 没有python环境
没有python环境的,我们可以构建一个可运行的exe文件。
docker_pull.exe构建
python
pyinstaller -F docker_pull.py
即可帮我们生成一个可执行的exe文件,在dist文件夹下面docker_pull.exe
双击打开docker_pull.exe
文件,然后输入需要拉取的镜像,例如127.0.0.1/llsydn/nginx:1.21.4
(回车)
镜像拉取成功,会默认在当前目录生成镜像.tar文件,例如:nginx_1.21.4.tar
文件
生成的docker镜像tar文件,即可上传到服务器,然后使用docker load -i xxx.tar
命令,即可导入到服务器的docker环境中。
好了,以上就是我个人的实操了。可能有些不对,大家伙,轻点喷!!!
个人理解,可能也不够全面,班门弄斧了。
好了,今天就先到这里了!!!^_^
如果觉得有收获的,帮忙点赞、评论、收藏
一下,再走呗!!!