docker镜像拉取工具(无需安装docker环境)

我正在参加「金石计划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环境中。


好了,以上就是我个人的实操了。可能有些不对,大家伙,轻点喷!!!

个人理解,可能也不够全面,班门弄斧了。

好了,今天就先到这里了!!!^_^

如果觉得有收获的,帮忙点赞、评论、收藏一下,再走呗!!!

相关推荐
2401_882727571 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
沐霜枫叶1 小时前
解决pycharm无法识别miniconda
ide·python·pycharm
cdg==吃蛋糕1 小时前
docker代理配置
docker·容器·eureka
途途途途2 小时前
精选9个自动化任务的Python脚本精选
数据库·python·自动化
蓝染然2 小时前
jax踩坑指南——人类早期驯服jax实录
python
许野平2 小时前
Rust: enum 和 i32 的区别和互换
python·算法·rust·enum·i32
问道飞鱼2 小时前
【Python知识】Python进阶-什么是装饰器?
开发语言·python·装饰器
追逐时光者2 小时前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
web135085886352 小时前
使用docker compose安装gitlab
docker·容器·gitlab
IT机器猫3 小时前
Docker完整技术汇总
运维·docker·容器