Skip to content

Commit

Permalink
新增镜像回滚功能
Browse files Browse the repository at this point in the history
  • Loading branch information
llody55 committed Jul 2, 2024
1 parent b631533 commit 2048dd9
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 32 deletions.
8 changes: 3 additions & 5 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions:

env:
PYTHON_VERSION: '3.9.13'
RELEASE_VERSION: 'v1.1.3' # 发布版本
RELEASE_VERSION: 'v1.1.4' # 发布版本
DOCKER_IMAGE_NAME: 'udocker'
DOCKER_NAMESPACE: 'llody'
DOCKER_REGISTRY_HUAWEI: 'swr.cn-southwest-2.myhuaweicloud.com'
Expand Down Expand Up @@ -116,10 +116,8 @@ jobs:
body: |
- **新版本发布**: ${{ env.RELEASE_VERSION }}
- **更新内容**:
- * 镜像仓库-新增备注信息。
- * BUG - 优化镜像过多时加载慢的情况。
- * 系统信息 - 新增主机使用率展示。
- * 镜像仓库-新增dockerhub代理,(docker.llody.cn)。
- * 容器管理 - 新增镜像回滚功能,可以对容器正在使用的镜像进行镜像回滚,切换镜像并重新创建容器。
- * 镜像仓库 - 新增dockerhub代理,(docker.llody.cn)。
- * 页面优化。
- * 流水线优化 - 新增同步国内镜像仓库镜像。
draft: false
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
**如果此项目对你有用,请给一个**:star:

## 资源
> 只需要:1核1G即可,暂时只有X86版本
> 1核1G即可轻松运行,支持只有 **X86****arm64**架构
## 快速了解
> udocker 是一个轻量且好用的docker管理面板,并且自带一个webssh终端管理工具,可以很方便的管理服务器和上传下载文件。
Expand All @@ -22,6 +22,7 @@

- 镜像管理
- 容器管理
- * 镜像回滚:可以对容器正在使用的镜像进行回滚版本,建议使用 **latest** 版本号时使用。
- 网络管理
- 存储管理
- 事件中心
Expand Down
1 change: 1 addition & 0 deletions apps/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
re_path("docker_image_info/",views.docker_image_info,name="docker_image_info"), # 远程docker镜像管理
re_path("docker_images_api/",views.docker_images_api,name="docker_images_api"), # 远程docker镜像管理API
re_path("docker_images_pull/",views.docker_images_pull,name="docker_images_pull"), # 远程docker镜像管理拉取镜像
re_path("docker_rollback_api/",views.docker_rollback_api,name="docker_rollback_api"),

re_path("get_images_list/",views.get_images_list,name="get_images_list"),
re_path("get_registries_list/",views.get_registries_list,name="get_registries_list"),
Expand Down
133 changes: 127 additions & 6 deletions apps/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,7 @@ def docker_images_api(request):
# 获取所有容器的镜像ID
container_image_ids = {container.image.id for container in containers}
def process_image(image):
image_id = image.id
image_id = image.short_id
image_tags = image.tags
image_size = humanize.naturalsize(image.attrs['Size'], binary=True)
time_str = image.attrs['Created']
Expand Down Expand Up @@ -1140,7 +1140,7 @@ def get_volumes_list(request):
def get_historicalmirror_list(request):
mirror_data = []
image_info = request.GET.get("image")
print("传递的镜像:",image_info)
container_info = request.GET.get("name")
try:
#容器管理模块API
success, client = docker_mod.connect_to_docker()
Expand All @@ -1161,19 +1161,140 @@ def get_historicalmirror_list(request):
historical_mirror_data = []
for image in specific_images:
tag_version = image.tags[0].split(':')[-1] if image.tags else '<none>'
created_time = parser.isoparse(image.attrs['Created'])
formatted_created_time = created_time.strftime('%Y-%m-%d %H:%M:%S')
size_mb = round(image.attrs['VirtualSize'] / 1024 / 1024)
historical_mirror_data.append({
'container_info':container_info,
'current_mirror': image_info,
'REPOSITORY': image.tags[0] if image.tags else '<none>',
'TAG': tag_version,
'IMAGE ID': image.short_id,
'CREATED': image.attrs['Created'],
'SIZE': image.attrs['VirtualSize']/1024/1024,
'IMAGE_ID': image.short_id,
'CREATED': formatted_created_time,
'SIZE': size_mb,
})
print("数据:",historical_mirror_data)
return render(request, 'container/docker_mirror.html',{"historical_mirror_data":historical_mirror_data})
except DockerException as e:
logger.error(e)

# 回滚方法
@csrf_exempt
@login_required
def docker_rollback_api(request):
if request.method == 'POST':
request_data = QueryDict(request.body)
rollback_name = request_data.get("container_info")
rollback_image = request_data.get("rollbackImageId")
print(f"回滚容器名称:{rollback_name},回滚镜像ID:{rollback_image}")
try:
#容器管理模块API
success, client = docker_mod.connect_to_docker()
if not success:
return JsonResponse({'code': 1, 'msg': '无法连接到Docker'})
# 获取指定容器
container = client.containers.get(rollback_name)
# 获取当前容器正在使用的镜像
current_image_name = container.attrs['Config']['Image']
# 记录回滚前的镜像ID,为报错回滚事务做准备
original_image_id = container.attrs['Image']

# 备份原始容器配置
original_config = {
'Config': container.attrs['Config'],
'HostConfig': container.attrs['HostConfig'],
'NetworkSettings': container.attrs['NetworkSettings']
}

# 重新打TAG
client.images.get(rollback_image).tag(current_image_name)

# 提取原容器的配置信息
config = container.attrs['Config']
network_config = container.attrs['NetworkSettings']

# 生成新的host_config
host_config = client.api.create_host_config(
binds=container.attrs['HostConfig'].get('Binds'),
port_bindings=container.attrs['HostConfig'].get('PortBindings'),
links=container.attrs['HostConfig'].get('Links'),
publish_all_ports=container.attrs['HostConfig'].get('PublishAllPorts', False),
privileged=container.attrs['HostConfig'].get('Privileged', False),
dns=container.attrs['HostConfig'].get('Dns'),
dns_search=container.attrs['HostConfig'].get('DnsSearch'),
volumes_from=container.attrs['HostConfig'].get('VolumesFrom'),
network_mode=container.attrs['HostConfig'].get('NetworkMode'),
restart_policy=container.attrs['HostConfig'].get('RestartPolicy'),
cap_add=container.attrs['HostConfig'].get('CapAdd'),
cap_drop=container.attrs['HostConfig'].get('CapDrop'),
devices=container.attrs['HostConfig'].get('Devices'),
ulimits=container.attrs['HostConfig'].get('Ulimits'),
log_config=container.attrs['HostConfig'].get('LogConfig'),
)

# 检查网络配置
if len(network_config['Networks']) > 1:
result = {'code': 1, 'msg': '原容器连接到多个网络,请手动处理网络配置'}
return JsonResponse(result)

# 停止并删除当前容器
container.stop()
container.remove()

# 生成新的端口映射
ports = {}
exposed_ports = {}
if 'Ports' in network_config:
for port, mappings in network_config['Ports'].items():
if mappings is not None:
for mapping in mappings:
ports[port] = mapping['HostPort']
exposed_ports[port] = {}

# 重新创建容器
new_container = client.api.create_container(
image=current_image_name,
name=rollback_name,
command=config['Cmd'],
environment=config.get('Env', []), # 确保Env存在
host_config=host_config,
networking_config=client.api.create_networking_config({
list(network_config['Networks'].keys())[0]: network_config['Networks'][list(network_config['Networks'].keys())[0]]
}),
volumes=config['Volumes'],
ports=exposed_ports
)

# 启动新容器
client.api.start(new_container['Id'])

result = {'code': 0, 'msg': '回滚成功,可返回容器管理查看!'}
return JsonResponse(result)
except DockerException as e:
logger.error(e)
# 回滚镜像tag到原始状态
try:
client.images.get(current_image_name).tag(original_image_id)
print(f"镜像TAG因容器创建失败进行回滚!!!")
except Exception as rollback_err:
print(f"镜像回滚失败 tag: {rollback_err}")

# 恢复原始容器
try:
if 'Config' in original_config and 'HostConfig' in original_config and 'NetworkSettings' in original_config:
new_container = client.containers.create(
image=original_config['Config']['Image'],
name=rollback_name,
command=original_config['Config']['Cmd'],
environment=original_config['Config'].get('Env', []),
host_config=original_config['HostConfig'],
networking_config=original_config['NetworkSettings']
)
client.api.start(new_container.id)
except Exception as restore_err:
print(f"Error restoring container: {restore_err}")

result = {'code': 1, 'msg': str(e)}
return JsonResponse(result)
# 网络管理
@login_required
def docker_network_info(request):
Expand Down
34 changes: 19 additions & 15 deletions templates/container/docker_container_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -622,23 +622,27 @@
var checkStatus = table.checkStatus(obj.config.id)
var data = checkStatus.data;
if(data.length == 0 ) {
layer.msg("请至少选择一行")
layer.msg("请至少选择一行", { icon: 5 })
} else if (data.length > 1){
layer.msg("不支持多选")
layer.msg("不支持多选", { icon: 5 })
} else {
layer.open({
type: 2,
area: ['50%', '70%'],
closeBtn: true, // 1或者2表示开启关闭按钮,0表示不开启
title: "文件管理器",
shade: 0.1,
shift: 2,
shadeClose: false,
content: '{% url "get_historicalmirror_list" %}?image=' + data[0].image,
move: false, // 禁止拖动
resize: false, // 禁止调整大小
skin: 'white-background' // 应用自定义的背景颜色类
});
layer.confirm('镜像回滚会<span style="color: red">重新创建</span>容器,请<span style="color: red">测试</span>并<span style="color: red">确认</span>要这么做?</br><span style="color: red">请注意,此操作不可逆</span>', function (index) {
layer.open({
type: 2,
area: ['50%', '70%'],
closeBtn: true, // 1或者2表示开启关闭按钮,0表示不开启
title: "容器镜像回滚",
shade: 0.1,
shift: 2,
shadeClose: false,
content: '{% url "get_historicalmirror_list" %}?image=' + data[0].image + '&name=' + data[0].name,
move: false, // 禁止拖动
resize: false, // 禁止调整大小
skin: 'white-background' // 应用自定义的背景颜色类
});
layer.close(index);
})

}
break
}
Expand Down
68 changes: 63 additions & 5 deletions templates/container/docker_mirror.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</style>
</head>
<body>
{% csrf_token %}
<div class="layui-card">
<div class="layui-card-body">
<div class="layui-row">
Expand All @@ -33,17 +34,31 @@
</colgroup>
<tbody>
<tr>
<td>REPOSITORY</td>
<td>TAG</td>
<td>CREATED</td>
<td>SIZE</td>
<td>仓库</td>
<td>版本</td>
<td>镜像ID</td>
<td>创建时间</td>
<td>大小</td>
<td>状态</td>
<td>操作</td>
</tr>
{% for mirror in historical_mirror_data %}
<tr>
<td>{{ mirror.REPOSITORY }}</td>
<td>{{ mirror.TAG }}</td>
<td>{{ mirror.IMAGE_ID }}</td>
<td>{{ mirror.CREATED }}</td>
<td>{{ mirror.SIZE }}</td>
<td>{{ mirror.SIZE }} MB</td>
<td>
{% if mirror.REPOSITORY == mirror.current_mirror %}
<span class="layui-badge layui-bg-green">UP</span>
{% else %}
-
{% endif %}
</td>
<td>
<button class="layui-btn layui-btn-xs layui-btn-danger rollback-btn" data-image-id="{{ mirror.IMAGE_ID }}" data-container-info="{{ mirror.container_info }}">回滚</button>
</td>
</tr>
{% endfor %}
</tbody>
Expand All @@ -55,5 +70,48 @@
</div>
</div>
<script src="/static/layui/layui.js"></script>
<script>
layui.use(['layer', 'util', 'jquery'], function () {
var layer = layui.layer;
var util = layui.util;
var $ = layui.$;

$('.rollback-btn').on('click', function () {
var csrf_token = $('[name="csrfmiddlewaretoken"]').val();
var rollbackImageId = $(this).data('image-id');
var containerInfo = $(this).data('container-info');

//console.log('回滚镜像ID:', rollbackImageId);
//console.log('回滚容器:', containerInfo);

$.ajax({
url: '{% url "docker_rollback_api" %}',
method: 'POST',
data: {
rollbackImageId: rollbackImageId,
container_info: containerInfo
},
headers: { 'X-CSRFToken': csrf_token },
success: function (result) {
if (result.code == '0') {

layer.msg(result.msg, { icon: 6, time: 2000 }, function () {
// 重新加载页面
window.location.reload()
});
} else {
layer.msg(result.msg, { icon: 5 })
}

},
error: function(xhr, status, error) {
console.error('Error:', error);
// 处理错误响应
}
})
})
})

</script>
</body>
</html>

0 comments on commit 2048dd9

Please sign in to comment.