搭建Docker Registry私有仓库

一、概述

本文将详细介绍如何在本地搭建一个 Docker 私有 Registry 仓库,并实现镜像的上传和管理。通过私有仓库,可以方便地在本地网络中存储和分发 Docker 镜像,提高开发和部署效率。

二、搭建步骤

(一)下载Docker Registry镜像

1. 命令:

2. 解析:

  • docker pull 是 Docker 的命令,用于从 Docker Hub 下载指定的镜像。
  • registry 是 Docker 官方提供的 Registry 镜像,用于搭建私有仓库。
  • 默认情况下,docker pull 会下载最新版本的镜像(latest 标签)。
(二)启动Docker Registry容器

1. 命令:

2. 解析:

  • -d:表示以守护进程模式运行容器(后台运行)。
  • -p 5000:6000:将容器的 5000 端口映射到宿主机的 6000 端口,用于访问私有仓库。
  • --restart=always:设置容器在退出后自动重启,确保仓库服务始终可用。
  • --name registry:为容器指定名称 registry,便于后续管理。
  • registry:指定使用的镜像名称(即之前下载的 registry 镜像)。

3. 验证容器是否启动成功:

如果看到名为 registry 的容器正在运行,并且端口映射正确,说明启动成功。

(三)配置Docker客户端以信任私有仓库

由于私有仓库默认使用 HTTPS 协议,Docker 客户端需要配置为信任该仓库。

编辑 /etc/docker/daemon.json 文件:

添加以下内容:

insecure-registries:指定不安全的仓库地址,允许 Docker 客户端通过 HTTP 协议访问该地址。

重启 Docker 服务:

重启 Docker 服务后,配置生效。

如果使用的是 Docker Desktop 则参考下图修改:

(四)上传镜像到私有仓库

此部分功能暂时不能使用 Podman 替代

1. 标记镜像:

  • docker image tag:用于为镜像重新标记一个新的名称和标签。
  • nginx:latest:本地已有的镜像名称和标签。
  • 10.10.10.189:6000/docker.io/library/nginx:latest:目标仓库地址和镜像名称。

2. 推送镜像到私有仓库:

  • docker push:将标记后的镜像推送到指定的仓库地址。
  • 如果推送成功,会显示镜像层的上传进度和最终的摘要信息。
(五)验证镜像是否上传成功

通过 API 查看仓库中的镜像:

三、常见问题及解决方法

(一)无法连接到私有仓库

1. 问题描述:

在推送或拉取镜像时,可能会遇到以下错误:

2. 解决方法:

  • 确保 /etc/docker/daemon.json 文件中正确配置了 insecure-registries。
  • 确保 Docker 服务已重启。
  • 确保仓库地址正确,且防火墙允许访问 6000 端口。
(二)网络问题导致无法解析地址

1. 问题描述: 如果尝试访问 http://10.10.10.189:6000 或 https://10.10.10.189:6000/v2/ 时,可能会遇到解析失败的问题。

2. 解决方法:

  • 检查仓库地址是否正确,确保 IP 地址和端口无误。
  • 确保网络连接正常,可以尝试 ping 或 curl 测试连通性。
  • 如果问题仍然存在,可能是网络配置或防火墙限制,建议检查网络设置或联系网络管理员。
(三)是否可以使用 Podman 替代

目前测试 push 镜像功能是有问题的,在推送的时候会报错,其他功能都是正常的。

如下:

使用 docker 执行上述命令不会报错。

参考链接


通过共享内存优化Flutter外接纹理的渲染性能,实时渲染不是梦

前言

看了咸鱼这篇《万万没想到——flutter这样外接纹理》的文章,我们了解到 Flutter 提供一种机制,可以将 Native 的纹理共享给 Flutter 来进行渲染。但是,由于 Flutter 获取 Native 纹理的数据类型是 CVPixelBuffer,导致 Native 纹理需要经过 GPU->CPU->GPU 的转换过程消耗额外性能,这对于需要实时渲染的音视频类需求,是不可接受的。

闲鱼这边的解决方案是修改了 Flutter Engine 的代码,将 FlutterGL 环境和 nativeGL 环境通过 ShareGroup 来联通,避免2个环境的纹理传递还要去 CPU 内存绕一圈。此方案能够解决内存拷贝的性能问题,但暴露 FlutterGL 环境,毕竟是一个存在风险的操作,给以后的 Flutter 渲染问题定位也增加了复杂度。所以,有没有一个完美、简便的方案呢?答案就是利用 CVPixelBuffer 的共享内存机制。

Flutter外接纹理的原理

先回顾下前置知识,看看官方提供的外接纹理机制究竟是怎样运行的。

图中红色块,是我们自己要编写的 Native 代码,黄色是 Flutter Engine 的内部代码逻辑。整体流程分为注册纹理,和整体的纹理渲染逻辑。

注册纹理
  1. 创建一个对象,实现FlutterTexture协议,该对象用来管理具体的纹理数据
  2. 通过FlutterTextureRegistry来注册第一步的FlutterTexture对象,获取一个flutter纹理id
  3. 将该id通过channel机制传递给dart侧,dart侧就能够通过Texture这个widget来使用纹理了,参数就是id
纹理渲染
  1. dart侧声明一个Texture widget,表明该widget实际渲染的是native提供的纹理
  2. engine侧拿到layerTree,layerTree的TextureLayer节点负责外接纹理的渲染
  3. 首先通过dart侧传递的id,找到先注册的FlutterTexture,该flutterTexture是我们自己用native代码实现的,其核心是实现了copyPixelBuffer方法
  4. flutter engine调用copyPixelBuffer拿到具体的纹理数据,然后交由底层进行gpu渲染

CVPixelBuffer格式分析

一切问题的根源就在这里了:CVPixelBuffer。从上面flutter外接纹理的渲染流程来看,native纹理到flutter纹理的数据交互,是通过 copyPixelBuffer 传递的,其参数就是 CVPixelBuffer。而前面咸鱼文章里面说的性能问题,就来自于纹理与 CVPixelBuffer 之间的转换。

那么,如果 CVPixelBuffer 能够和OpenGL的纹理同享同一份内存拷贝,GPU -> CPU -> GPU的性能瓶颈,是否就能够迎刃而解了呢?其实我们看一下flutter engine里面利用CVPixelBuffer来创建纹理的方法,就能够得到答案:

Flutter Engine是使用 CVOpenGLESTextureCacheCreateTextureFromImage 这个接口来从 CVPixelBuffer 对象创建OpenGL纹理的,那么这个接口实际上做了什么呢?我们来看一下官方文档

This function either creates a new or returns a cached CVOpenGLESTextureRef texture object mapped to the CVImageBufferRef and associated parameters. This operation creates a live binding between the image buffer and the underlying texture object. The EAGLContext associated with the cache may be modified to create, delete, or bind textures. When used as a source texture or GL_COLOR_ATTACHMENT, the image buffer must be unlocked before rendering. The source or render buffer texture should not be re-used until the rendering has completed. This can be guaranteed by calling glFlush().

从文档里面,我们了解到几个关键点:

  1. 返回的纹理对象,是直接映射到了CVPixelBufferRef对象的内存的
  2. 这块buffer内存,其实是可以同时被CPU和GPU访问的,我们只需要遵循如下的规则:
    • GPU访问的时候,该 CVPixelBuffer ,不能够处于lock状态。
      使用过pixelbuffer的同学应该都知道,通常CPU操作pixelbuffer对象的时候,要先进行lock操作,操作完毕再unlock。所以这里也容易理解,GPU使用纹理的时候,其必然不能够同时被CPU操作。
    • CPU访问的时候,要保证GPU已经渲染完成,通常是指在 glFlush() 调用之后。
      这里也容易理解,CPU要读写这个buffer的时候,要保证关联的纹理不能正在被OpenGL渲染。

我们用instrument的allocation来验证一下:

instrument的结果,也能够印证文档中的结论。 只有在创建pixelBuffer的时候,才分配了内存,而映射到纹理的时候,并没有新的内存分配。

这里也能印证我们的结论,创建pixelBuffer的时候,才分配了内存,映射到纹理的时候,并没有新的内存分配。

共享内存方案

既然了解到CVPixelBuffer对象,实际上是可以桥接一个OpenGL的纹理的,那我们的整体解决方案就水到渠成了,可以看看下面这个图

关键点在于,首先需要创建pixelBuffer对象,并分配内存。然后在native gl环境和flutter gl环境里面分别映射一个纹理对象。这样,在2个独立的gl环境里面,我们都有各自的纹理对象,但实际上其内存都被映射到同一个CVPixelBuffer上。在实际的每一帧渲染流程里面,native环境做渲染到纹理,而flutter环境里面则是从纹理读取数据。

Demo演示

这里我写了个小demo来验证下实际效果,demo的主要逻辑是以60FPS的帧率,渲染一个旋转的三角形到一个pixelBuffer映射的纹理上。然后每帧绘制完成之后,通知 Flutter 侧来读取这个pixelBuffer对象去做渲染。

核心代码展示如下:

关键代码都添加了注释,这里就不分析了

我们从上面的gif图上可以看到整个渲染过程是十分流畅的,最后看displayLink的帧率也能够达到60FPS。该demo是可以套用到其他的需要CPU与GPU共享内存的场景的。

完整的demo代码在这里flutter_texture

参考链接


通过共享内存优化flutter外接纹理的渲染性能,实时渲染不是梦

ubuntu 24.04编译CEF(Chromium Embedded Framework)

前置条件

1. 已经通过 GitLab的替代者-轻量级Gitea安装与配置-Windows 11 配置过镜像服务

基础知识

CIPD 全称 Chrome Infrastructure Package Deployment (https://chromium.googlesource.com/infra/luci/luci-go/+/main/cipd/README.md),主要用于管理 Google 项目构建中用到的二进制文件(例如编译器之类的),你可以简单的认为是针对大文件的git系统。

CIPD管理的所有文件可以在(https://chrome-infra-packages.appspot.com)上查看。

手动下载( version 参数从 depot_tools/cipd_client_version 读取)

linux-x86-64:
https://chrome-infra-packages.appspot.com/client?platform=linux-amd64&version=git_revision:b1f414539ac10cc67a0250890a38712cc06cf102

windows-x86-64:
https://chrome-infra-packages.appspot.com/client?platform=windows-amd64&version=git_revision:b1f414539ac10cc67a0250890a38712cc06cf102

动手实践

外网构建

国内镜像构建
1. 使用镜像地址替换 Git 仓库

命令执行后的 .gitconfig 文件:

2. 配置HTTP代理服务器

由于 Chromium 项目不仅依赖了一系列的第三方 Git 项目,还依赖了一系列已经编译后的二进制文件,这些文件被托管在 CIPD (Chrome Infrastructure Package Deployment) / GCS(Google Cloud Storage) 服务器上,这部分服务器也是没办法正常访问下载文件的,因此我们把这些二进制文件依旧托管在刚刚搭建的 Gitea 服务器上面,作为一个独立的 Git 项目来维护,减少非必要的服务器。

但是如何更改这部分的下载地址呢?我们可以通过设置 http_proxy 的方式,使用 Python 脚本,在脚本中完成下载数据的重定向。

代理脚本如下:

参考配置文件如下:

3. 执行编译

参考链接


Windows 11系统怎么清理Windows.old文件夹?

Windows.old 就是指电脑上在重装系统的历程中,备份数据旧系统中关键文件的文件夹,Windows.old 一般而言用途并不大,可是它坐落于新系统的C盘中很占用室内空间,因而就需要将其删除, Windows.old有着一些独特的管理权限,促使它无法同时在資源管理器里被删除,可能是为了避免用户的误操作。该怎么删除呢?下面大家就一起来看看 Windows 11 删除Windows.old 文件夹的操作。

清理Windows.old文件夹的方法:进到开始菜单,开启设置,进到系统,进入存储,进到清理建议,勾选之前的 Windows 安装文件后,点击清理就可以。

进到开始菜单,点击任务栏四宫图标。

进入设置

进到系统

进入存储

进到清理建议。待扫描进行后,进到清理建议。

清理Windows.old文件夹

勾选之前的Windows安装文件后,点击清理就可以。

参考链接


win11系统,怎么清理Windows.old文件夹?win11清理文件夹操作步骤

GitLab的替代者-轻量级Gitea安装与配置-Windows 11

前置条件
  • Windows 11 专业版 24H2
  • podman desktop 1.17.2 x64
  • Gitea 1.23.6
动手实践

Windows 的本质是是安装一个 Linux 虚拟机,然后在虚拟机中安装 Gitea ,所以会出现后面需要进行端口数据转发。

后续如果想修改配置文件,那么需要通过 podman machine ssh 进入容器进行修改。

另外虚拟机整个被默认WSL2 挂载到  \\\\wsl.localhost\ 目录下,可以直接在 Windows 系统下访问,但是修改的时候可能会没有权限,还是需要通过命令行修改。

如果 Podman Desktop 没有开机自启动,确认如下如下配置是否已经勾选:

注意:如果对大型项目进行镜像部署,比如 chromium 。登录之前可以正常使用,登录特别慢,注意观察 PodmanLogs 项,如果观察到如下内容,特别是 [Slow SQL Query] 相关的日志输出:

上述的输出非常的不合理,简单的 SQL 语句执行耗时高达 26分钟,这个结果是非常不合理的。SQLite 的性能不可能这么差劲。

导出数据库文件,通过 SQLiteStudio 执行相同的 SQL 语句,在  0.1 毫秒执行结束。

并且 Docker 多次重启之后,高概率发生文件损坏。另一个常见现象是处理器占用长时间处于高位,某几个核心长时间满负荷,但是 DUMP 堆栈调用又一切正常,看不到用户态的高占用函数。

这个原因是由于 Windows 和 Linux 的跨系统文件兼容性问题无法得到很好的解决,WSL 2 下访问 Windows 文件性能会非常差。

具体解释参考  [wsl2] filesystem performance is much slower than wsl1 in /mnt #4197 

解决这个问题的方法就是不要进行映射,直接在存储在虚拟机的磁盘里

同时,建议内存不低于 32GB ,大型项目,如果内存不足的话,也可能诱发此类问题。

参考链接


GitLab的替代者-轻量级Gitea安装与配置-ubuntu 24.04.2

前置条件
    • ubuntu 24.04.2 LTS
    • Gitea 1.23.6
动手实践

访问配置,在地址栏输入 http://xxx.xxx.xxx.xxx:10890 进入配置页面,如下图:

后续如果需要修改配置,可以通过调整修改 /home/podman/.dockers/gitea/gitea/conf/app.ini 解决。

注意:如果对大型项目进行镜像部署,比如 chromium 。登录之前可以正常使用,登录特别慢,那么可能是内存不足导致的,大型项目建议内存不低于 32GB,否则会出现各种问题。

参考链接


GitLab的替代者-轻量级Gogs安装与配置

更建议参考使用 GitLab的替代者-轻量级Gitea安装与配置,功能更完善,尤其是作为大规模项目的镜像的情况。

Gogs项目开发进展比较缓慢,功能缺陷较多,Gitea 项目继承自 Gogs 项目。

前置条件
  • ubuntu 24.04.2 LTS
动手实践

访问配置,在地址栏输入 http://xxx.xxx.xxx.xxx:10880 进入配置页面,如下图:

后续如果需要修改配置,可以通过调整修改 /home/podman/.dockers/gogs/gogs/conf/app.ini 解决。

参考链接


使用 AndroidX 增强 WebView 的能力

在应用开发过程中,为了在多个平台上保持一致的用户体验和提高开发效率,许多应用程序选择使用 H5 技术。在 Android 平台上,通常使用 WebView 组件来承载 H5 内容以供展示。

WebView 存在的问题

自 Android Lollipop 起,WebView 组件的升级已经独立于 Android 平台。然而,控制 WebView 的 API(android.webkit) 仍然与平台升级相关。这意味着应用开发者只能使用当前平台所定义的接口,而无法充分利用 WebView 的全部能力。例如: WebView.startSafeBrowsing API 在 Android 8.1 上被添加,该 Feature 由 WebView 提供,即使我们在 Android 7.0 更新 WebView 拥有了该 Feature ,由于 Android 7.0 没有 WebView.startSafeBrowsing API ,我们也没办法使用该功能。

WebView 的实现基于 Chromium 开源项目,而 Android 则基于 AOSP 项目,这两个项目有着不同的发布周期,WebView 往往一个月就可以推出下一个版本,而 Android 则需要一年的时间,对于 WebView 新增的 Feature 我们最迟需要一年才能使用。

AndroidX Webkit 的出现

为了解决上面平台能力和 WebView 不匹配的问题,我们可以独立于平台之外定义一套 WebView API ,并让它随着 WebView 的 Feature 更新 API ,这样解决了现有的问题却导入了另一个问题——如何将新定义的 WebView API 和 WebView 进行衔接。

从应用开发的角度,系统 WebView 难以修改,自己编译定制一个 WebView 并随着 APK 提供是一个很好方案。这时候,我们可以轻松的解决衔接问题,并能够按照需求,任意增改 Feature 而不必等官方更新。同时解决了兼容问题和 WebView 内核碎片化的问题。腾讯 X5 ,UC U4 等都是这个方案。维护一份 WebView 并不是一件容易的事,需要投入更多的人力支持,因为将 WebView 打入包中,还伴随着包体积的急剧增加。

从 Android 官方的角度,可以推动 WebView 上游支持该 WebView API , 而这正是 AndroidX Webkit 的解决方案。Android 官方将定义的 WebView API 放置到 AndroidX Webkit 库,以支持频繁的更新,并在 WebView 上游增加“胶水层”与 AndroidX Webkit 进行衔接,这样在旧版的 Android 平台上,只要安装了拥有"胶水"层代码的 WebView ,也就拥有了新版平台的功能。

“胶水层” 是在某个版本之后才后才支持的,旧版本的 WebView 内核并不支持,这也是为什么在调用之前始终应该检查 isFeatureSupported 的原因。

AndroidX Webkit 的功能

初步了解了 AndroidX Webkit 的产生和实现原理,下面带领大家看一下它都提供了哪些新能力能够增强我们的 WebView 。

向下兼容

如上文分析,AndroidX Webkit 提供了向下的兼容,如下面代码所示,由 WebViewCompat 提供兼容的接口调用。

需要注意的是在调用之前对 WebViewFeature 的检查,对于每个 Feature ,AndroidX Webkit 会取平台和 WebView 所提供 Feature 的并集,在调用某个 API 之前必须进行检查,如果平台和 WebView 均不支持该API则将抛出 UnsupportedOperationException 异常。

如果我们扒开 WebViewCompat 的外衣查看他的源码(如下所示),会发现如果在当前版本 Platform API 提供了接口,就会直接调用 Platform API 的接口,而对于低版本,则由 AndroidX Webkit 和 WebView 的"通道"提供服务。

对比上面的代码,使用平台 API(old code)时仅可以支持 90% 的用户,而使用 AndroidX Webkit(new code) 则可以覆盖大约 99% 的用户。

代理功能支持

一直以来WebView 的代理设置异常繁琐,当遇到复杂的代理规则就无能为力了。在 AndroidX Webkit 中增加了 ProxyController API 用于为 WebView 设置代理。ProxyConfig.Builder 类提供了设置代理以及配置代理的绕过方式等方法,通过组合可以满足复杂的代理场景。

以上代码定义了一个复杂的代理场景,我们为 WebView 设置了两个代理服务器,localhost:1080 仅当 localhost:7890 失败的情况下启用,addDirect 声明了如果两个服务器都失败则直连服务器,addBypassRule 规定了 www.bing.com 和以 .so 结尾的域名始终不应该使用代理。

白名单代理

如果仅有少量的 URL 需要配置代理,我们可以使用 setReverseBypassEnabled(true) 方法将addBypassRule 添加的 URL 转变为使用代理服务器,而其他的 URL 则直连服务。

安全的 WebView 和 Native 通信支持

建立 WebView 和 Native 的双向通信是使用 Hybrid 混合开发模式的基础,在之前 Android 已经提供了一些机制能够让完成基本的通信,但是已有的接口都存在一些安全和性能问题,在 AndroidX 中增加了一个功能强大的接口 addWebMessageListener 兼顾了安全和性能等问题。

代码示例中将 JavaSript 对象 replyObject 注入到匹配 allowedOriginRules的上下文中,这样只有在可信的网站中才能被使用此对象,也就防止了不明来源的网络攻击者对该对象的利用。

调用上述方法之后,在 JavaScript 上下文中我们就可以访问 myObject ,调用 postMessage 就可以回调 Native 端的 onPostMessage 方法并自动切换到主线程执行,当 Native 端需要发送消息给 WebView 时,可以通过 JavaScriptReplyProxy.postMessage 发送到 WebView ,并将消息传递给 onmessage 闭包。

文件传递

在以往的通讯机制中,如果我们想传递一个图片只能将其转换为 base64 等进行传输,如果曾经使用过 shouldOverrideUrlLoading 拦截 url 大概率会遇见传输瓶颈,AndroidX Webkit 中很贴心的提供了字节流传递机制。

Native 传递文件给 WebView

WebView 传递文件给 Native

深色主题的支持

Android 10 提供了深色主题的支持,但是在 WebView 中显示的网页却不会自动显示深色主题, 这就表现出严重的割裂感,开发者只能通过修改 css 来达到目的,但这往往费时费力还存在兼容性问题,Android 官方为了改善这一用户体验,为 WebView 提供了深色主题的适配。

一个网页如何表现是和prefers-color-scheme and color-scheme 这两个 Web 标准互操作的。 Android官方提供了一张表阐述了他们之间的关系。

上面这张图比较复杂,简单来说如果你想让 WebView 的内容和应用的主题相匹配,你应该始终定义深色主题并实现 prefers-color-scheme ,而对于未定义 prefers-color-scheme 的页面,系统按照不同的策略选择算法生成或者显示默认页面。

以 Android 12 或更低版本为目标平台的应用 API 设计过于复杂,以 Android 13 或更高版本为目标平台的应用精简了 API ,具体变更请参考官方文档

JavaScript and WebAssembly 执行引擎支持

我们有时候我们会在程序中运行 JavaScript 而不显示任何 Web 内容,比如小程序的逻辑层,使用 WebView 本能够满足我们的要求但是浪费了过多的资源,我们都知道在 WebView 中真正负责执行 JavaScript 的引擎是 V8 ,但是我们又无法直接使用,所以我们的安装包中出现了各种各样的引擎:HermesJSCV8等。

Android 发现了这”群雄割据“的局面,推出了AndroidX JavascriptEngine,JavascriptEngine 直接使用了 WebView 的 V8 实现,由于不用分配其他 WebView 资源所以资源分配更低,并可以开启多个独立运行的环境,还针对传递大量数据做了优化。

代码展示了执行 JavaScript 和 WebAssembly 代码的使用:

更多支持

AndroidX Webkit 是一个功能强大的库,由于篇幅原因上文将开发者比较常用的功能进行了列举,AndroidX 还提供对 WebView 更精细化的控制,对 Cookie 的便捷访问、对 Web 资源的便捷访问,对 WebView 性能的收集,还有对大屏幕的支持等等强大的 API,大家可以查看发布页面查看最新的功能。

参考链接


Flutter格式化排除代码段

前置条件
  • ubuntu 24.04.2 LTS
  • Visual Studio Code 1.98.2
  • Android Studio Meerkat | 2024.3.1
  • Flutter 3.29.1
问题描述

在使用 Flutter 编写代码的时候,如下代码进行格式化

可以发现格式化后的结果变成了如下的样子:

这部分代码我们不希望格式化,尤其是编写加解密算法的时候,里面的置换数组格式化之后会完全乱掉。

可以使用

阻止格式化工具对具体代码块格式化。

比如如下代码:

参考链接


在Visual Studio Code中调试Flutter Dart代码报错未验证断点(Unverified Breakpoint)

前置条件
  • ubuntu 24.04.2 LTS
  • Visual Studio Code 1.98.2
  • Flutter 3.29.1
问题描述

在使用 Visual Studio Code 调试 Flutter 代码的时候,如果在依赖库的代码中设置断点,会无法断点,并且提示断点未验证,"Unverified Breakpoint"。

如下图:

继续阅读在Visual Studio Code中调试Flutter Dart代码报错未验证断点(Unverified Breakpoint)