Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Docker——访问官方镜像库-代理配置


终端科学上网说明

  • 配置好科学上网后,网页能科学上网

  • 为了在终端科学上网,需要配置代理(修改环境变量为 VPN 的指定端口)

    1
    2
    export HTTP_PROXY="http://127.0.0.1:7897/"
    export HTTPS_PROXY="http://127.0.0.1:7897/"
  • 确认一下 Ubuntu 终端已经科学上网,即已经配置下面的情况

    1
    2
    3
    4
    # echo $HTTP_PROXY
    http://127.0.0.1:7897/
    # echo $HTTPS_PROXY
    http://127.0.0.1:7897/
    • 从输出可以看到,终端已经配置了代理(HTTP_PROXY 和 HTTPS_PROXY 均指向 http://127.0.0.1:7897/)
  • 此时在终端执行 curl 能成功

    1
    curl https://www.google.com
    • 注意此时用 ping 命令不一定能成功,因为 ping 使用的是 ICMP 协议
  • 综上已经确定了终端可以科学上网

  • 特别注意:一般来说终端的代理配置仅对当前终端会话生效 ,除非把环境变量配置添加到 ~/.bashrc 文件


docker 的问题

  • 通过网页访问 docker 核心库没问题(有返回没验证的说明就是 OK 的)
  • Docker 服务默认不会继承终端的代理环境变量 ,这常常是 Docker 拉取镜像仍失败的关键原因
  • 需要将代理配置同步给 Docker 服务,确保 Docker 能通过代理访问镜像仓库

为 Docker 配置代理(确保与终端代理一致)

第一步:创建 Docker 代理配置文件

  • Docker 服务的代理配置需通过专门的系统目录(/etc/systemd/system/docker.service.d/)生效,执行以下命令创建配置文件:
    1
    2
    3
    4
    5
    # 创建配置目录(若不存在)
    sudo mkdir -p /etc/systemd/system/docker.service.d

    # 创建代理配置文件
    sudo nano /etc/systemd/system/docker.service.d/proxy.conf

第二步:写入代理配置(与终端代理保持一致)

  • 在 proxy.conf 文件中添加以下内容(注意代理地址 127.0.0.1:7897 需与你终端的 HTTP_PROXY 完全一致,包括结尾是否带 /):

    1
    2
    3
    4
    [Service]
    Environment="HTTP_PROXY=http://127.0.0.1:7897/"
    Environment="HTTPS_PROXY=http://127.0.0.1:7897/"
    Environment="NO_PROXY=localhost,127.0.0.1,docker-registry.local.golang.org,.docker.internal"
    • NO_PROXY:指定无需代理的地址(避免本地服务/容器间通信走代理,防止冲突),无需修改

第三步:重启 Docker 服务,使代理生效

1
2
3
4
5
6
7
8
# 重新加载 systemd 配置(识别新的 proxy.conf)
sudo systemctl daemon-reload

# 重启 Docker 服务
sudo systemctl restart docker

# 验证 Docker 是否加载了代理配置
sudo systemctl show --property=Environment docker
* 若输出中包含 `HTTP_PROXY=http://127.0.0.1:7897/` 和 `HTTPS_PROXY=http://127.0.0.1:7897/`,说明 Docker 代理配置成功

再次尝试拉取 Docker 镜像

  • 现在 Docker 已通过代理访问外部仓库,执行拉取命令:
    1
    2
    3
    4
    5
    # 先测试小镜像 hello-world,验证代理是否生效
    sudo docker pull hello-world

    # 若 hello-world 成功,再拉取目标镜像
    sudo docker pull xxx

Docker——镜像深入理解


整体说明

  • TLDR:镜像的本质是静态的只读模板
  • 镜像(Image)是一个静态的、只读的二进制文件集合 ,包含运行应用所需的代码、 runtime、库、环境变量、配置文件等所有依赖
  • 镜像的核心作用是:
    • 作为容器的”模板”:容器是镜像的运行实例(镜像 + 可写层)
    • 保证环境一致性:无论在哪个宿主机器上,基于同一镜像创建的容器都能运行相同的应用(”一次构建,到处运行”)
  • 镜像的核心特性包括
    • 只读性 :所有层不可修改,保证安全性和可复用性
    • 分层存储 :基于 UnionFS,层可共享,减少冗余
    • 轻量高效 :仅包含应用依赖,体积远小于虚拟机镜像
    • 可移植性 :镜像内容与宿主环境无关,实现”一次构建,到处运行”

镜像的分层结构:UnionFS 与 Copy-on-Write

  • Docker 镜像最核心的设计是分层存储 ,基于 Union File System(联合文件系统) 实现
  • 这种分层结构让镜像具备了”可复用、轻量、高效”的特性

分层的本质

  • 每个镜像由多个只读层(Layer) 叠加而成,每层对应镜像构建过程中的一个操作(如 RUN、COPY 等 Dockerfile 指令)
  • 层与层之间通过哈希值唯一标识(如 sha256:a1b2c3...),相同的层会被不同镜像共享(避免重复存储)
  • 例如,一个 nginx 镜像可能包含以下层:
    • 基础层:ubuntu:20.04 的底层文件系统(如 /bin、/etc 等)
    • 依赖层:安装 nginx 所需的库(如 libpcre3 等)
    • 应用层:nginx 二进制文件和配置文件(如 /usr/sbin/nginx、/etc/nginx/)

联合挂载(Union Mount)

  • 当镜像被用于创建容器时,Docker 会将所有只读层联合挂载为一个统一的文件系统,对容器来说,这些层看起来是一个完整的目录(透明化分层细节)

Copy-on-Write(写时复制)机制

  • Copy-on-Write(写时复制)机制是镜像分层与容器交互的核心机制
  • 镜像的所有层都是只读的,容器启动时,Docker 会在镜像顶层添加一个可写层(Writable Layer)
  • 当容器需要修改文件时:
    • 若文件在底层(镜像层),会先将文件复制到可写层 ,再修改可写层的副本(底层文件不变)
    • 若文件是新创建的,直接写入可写层
  • 这种机制保证了:
    • 镜像层不会被容器修改(只读),可安全复用
    • 容器的修改仅保存在自己的可写层,不影响其他容器或镜像

Dockerfile 构建镜像与分层

  • 镜像的构建通常通过 Dockerfile 定义(而非 docker commit,后者不推荐)

  • Dockerfile 中的每一条指令都会生成一个新的只读层 ,指令与层的对应关系是理解镜像体积和优化的关键

  • 下面是一个 Dockerfile 与分层对应的示例:

    1
    2
    3
    4
    5
    6
    FROM ubuntu:20.04       # 基础层(复用 ubuntu:20.04 的所有层)
    RUN apt-get update # 层 1:执行 update 后的文件变化
    RUN apt-get install -y nginx # 层 2:安装 nginx 后的变化
    COPY nginx.conf /etc/nginx/ # 层 3:复制配置文件的变化
    EXPOSE 80 # 元数据(不生成层,仅记录信息)
    CMD ["nginx", "-g", "daemon off;"] # 元数据(容器启动命令)
  • EXPOSE、CMD、ENV 等指令仅修改镜像的元数据(保存在镜像的配置层),不生成新的文件层

  • 多条指令会生成多个层,层越多,镜像体积可能越大(需优化)


附录:镜像 ID 与 Digest的区别

  • 镜像 ID是镜像的唯一标识符(64 位哈希,通常显示前 12 位),由镜像的所有层和元数据共同计算得出
    • f9c8f87e172b(完整 ID 为 f9c8f87e172b2a4e41a73e2d685c8f...)
  • Digest(摘要) :镜像内容的哈希值(基于所有层的内容计算),用于验证镜像的完整性(避免篡改)
    • 拉去影响完成时,给出的 Digest: sha256:abc123... 就是摘要,相同摘要的镜像内容一定相同

附录:Storage Driver

  • Docker 通过存储驱动管理镜像层和容器可写层的存储与联合挂载
  • 不同的存储驱动实现方式不同,主流的是 overlay2(Linux 推荐,性能最优)
  • overlay2 驱动的核心结构如下:
    • 镜像层 :存储在 /var/lib/docker/overlay2/ 下,每层对应一个目录(以层哈希命名)
    • 可写层 :容器启动时,overlay2 会创建一个新目录作为可写层,并通过”上下层”关系关联镜像层
    • 合并视图 :通过 overlay2 的联合挂载,将所有层合并为容器内看到的统一文件系统

附录:镜像的体积优化

  • 镜像体积过大会导致存储、传输和启动效率下降,优化核心是减少层数、删除冗余文件 :

  • 合并指令(减少层数) :多条 RUN 指令可合并为一条(用 && 连接),并清理缓存(如 apt-get clean):

    1
    2
    3
    4
    5
    6
    7
    8
    # 优化前(2 层)
    RUN apt-get update
    RUN apt-get install -y nginx

    # 优化后(1 层,且清理缓存)
    RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/* # 删除 apt 缓存
  • 多阶段构建(丢弃无用层) :用于编译型应用(如 Go、Java),仅保留运行时所需文件(下面的代码最终镜像仅包含 alpine 基础层 + 二进制文件,体积大幅减小):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 阶段 1:编译(包含编译器等冗余工具)
    FROM golang:1.20 AS builder
    WORKDIR /app
    COPY . .
    RUN go build -o myapp .

    # 阶段 2:运行(仅保留编译产物)
    FROM alpine:3.18
    COPY --from=builder /app/myapp /myapp # 仅复制编译好的二进制文件
    CMD ["/myapp"]
  • 优先选择 alpine(几 MB)、slim 版本,而非完整版(如 ubuntu 完整版约 200MB,alpine 约 5MB)

  • 删除临时文件、日志、包管理缓存(如 yum clean all、npm cache clean)


附录:执行 docker pull 时在发生什么?

  • 当执行 docker pull [镜像名] 时,终端会显示的多行输出,这是 Docker 拉取镜像过程的详细日志,每一行对应镜像的一个 层(Layer) 的下载或处理状态,包含层的信息
  • 如果本地已经有的层,不会再下载,且不同镜像是可以共享同一个层的(通过 ID 唯一识别)
  • 层 ID :每个层的唯一标识符(如 a1b2c3d4...)
  • 操作类型 :
    • Pull complete:该层已成功下载并解压
    • Already exists:本地已存在该层,无需重复下载
    • Downloading:正在下载该层,会显示进度(如 50% [=====>])
    • Verifying Checksum:验证文件完整性
    • Extracting:解压下载的层文件
  • 为什么会有这么多层?
    • Docker 镜像是由多个 只读层(Layer) 叠加而成的
    • 每个层对应镜像构建过程中的一个操作(如 RUN、COPY 等指令)
    • 层具有 可复用性 :不同镜像可能共享相同的层,避免重复存储和下载
    • 层的设计让镜像更新更高效(只需更新变化的层)

附录:镜像与容器的关系:动态 vs 静态

  • 镜像 :静态、只读、多分层,是容器的”模板”
  • 容器 :动态、可写,是镜像的”运行实例”(= 镜像所有只读层 + 容器独有的可写层)
  • 可以理解为:容器 = 镜像(只读层) + 可写层(容器私有) + 容器元数据(如网络配置、环境变量等)
  • 当容器被删除时,其可写层和元数据会被清理 ,但镜像的只读层不受影响(可继续用于创建新容器)

服务器——内存扩展


服务器内存通道说明

  • 一般来说两个槽一个通道,由黑白不同的两个插槽组成
  • 某些特殊的产品也会有单个插槽就是一个通道的情况
  • 大部分 CPU 一般有4个通道,有些会有6个通道

一般服务器内存扩展原则

  • 先插满白色口,再插黑色口
  • 也就是充分利用通道,每个通道插一个是速度最快的最优选择

对于特定产品

DELL Power Edge

  • 第一个 CPU 通道数:6个

    • [A3, A5]分别为一个一个单独的通道
    • 其余的每双槽构成一个通道
    • 一共10个插槽
  • 第二个 CPU 通道数:3个

    • 每个通道两个插槽
    • 6个插槽组成3通道
  • 对于一个 CPU 而言,插内存的组合方式

    内存条数量 插槽对应位置
    1 A1
    2 A1, A2
    4 A1, A2, A4, A5
    6 A1, A2, A3, A4, A5, A6
    8 A1, A2, A3, A4, A5, A6, A7, A8
    10 A1, A2, A3, A4, A5, A6, A7, A8, A9, A10

浏览器——设置网页为可编辑模式

用一行代码将网页设置成可修改模式,方便修改


修改网页为可编辑模式

  • 打开 console
  • 执行以下代码
    1
    document.body.contentEditable = 'true'

Python——修改图片背景颜色

利用Python的skimage包实现修改图片背景色等


修改签名背景,按照灰度图像处理

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from skimage import io, color
import matplotlib.pyplot as plt


def change_grey_to_white(origin, new):
"""
Change background color for image, such as signature
:param origin: the path of original image
:param new: the path of new image
:return: None
"""
# img = io.imread('./origin.jpeg')
img = io.imread(origin)
io.imshow(img)
plt.show()
img_gray = color.rgb2gray(img)
rows, cols = img_gray.shape
for i in range(rows):
for j in range(cols):
if img_gray[i, j] <= 0.5:
img_gray[i, j] = 0
else:
img_gray[i, j] = 1
io.imshow(img_gray)
plt.show()
io.imsave(new, img_gray)

# example
change_grey_to_white('./origin.jpeg', 'new.jpeg')

说明

  • 这里可接受彩色图片,比如拍照片得到的原始图片
  • 函数会将其转换为灰度图片然后处理,最终输出也是灰度图片
  • 如果需要处理指定灰度,可通过修改判断语句中0.5这个值从而实现(比如修改为区间等)

修改证件照背景,按彩色图像处理

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from skimage import io
import numpy as np


def change_pixel_color(pixel, old_pixel, new_pixel=None, error=60):
"""
change color for pixel if pixel in range [old_pixel-error, old_pixel+error]
:param pixel:
:param old_pixel:
:param new_pixel:
:param error:
:return: the new pixel
"""
if new_pixel is None:
new_pixel = [255, 255, 255]
similar = True
for i in abs(pixel - old_pixel):
if i > error:
similar = False
break
if similar:
return new_pixel
else:
return pixel


def change_background(origin_path, new_path, new_color, error=60):
"""
change background, auto detect background
:param origin_path:
:param new_path:
:param new_color: target background color
:param error: the error for change color
:return: None
"""
img = io.imread(origin_path)
bg = img[10:30, 10:30, :]
bg_pixel = [1.0 * np.sum(bg[:, :, channel])/bg[:, :, channel].size for channel in range(3)]
print("background color: %s" % bg_pixel)
new_img = np.array([[change_pixel_color(pixel, [0, 160, 234], new_color, error) for pixel in row] for row in img])
io.imshow(new_img)
# import matplotlib.pyplot as plt
# plt.show()
io.imsave(new_path, new_img)


# example
change_background(origin_path="origin.jpeg",
new_path='new.jpeg',
new_color=[0, 255, 255])

说明

  • 自动读取图片的左上角部分像素的平均值作为背景颜色
  • 允许差范围在合适的范围内,可通过error参数调节,该参数不宜过大也不宜过小,可测试多次选择比较合适的

ML——缺失值处理

missing值处理


对于数值类型的特征

中位数填充

  • 用当前特征所有未缺失的值的中位数(median)填充当前特征的缺失值

均值填充

  • 用当前特征所有未缺失的值的均值填充当前特征的缺失值

加权填充

  • 引入相似性矩阵,评估缺失样本与其他未缺失样本的相似性,按照相似性分配权重
  • 效果更好,但是需要更多时间

对于Category类型

特殊字符填充

  • 使用某种未出现过的特殊字符填充
  • 等价于将缺失值看成是个特殊类别

填充众数

  • 寻找所有未缺失数据中样本最多的类别,然后将缺失值填充为该众数类别

什么情况下不用填充

树模型

普通树模型
  • ID3不支持缺失值处理(也可能可以,但是我没看到具体介绍)
  • C4.5和CART都使用下面的方法进行缺失值处理
  • 结点分裂时:
    • 参考自周志华机器学习书籍中
    • 先按照无缺失的数据正常划分(缺失数据不参与计算)
    • 对于无缺失值的样本,正常分配到对应叶子节点
    • 对于缺失值的样本,每个样本以不同的概率分配到各个叶子节点, 概率值为: 子节点中无缺失样本的数量 / 无缺失样本的总数
  • 问题: 如果是训练时没有缺失,预测时有缺失怎么办?
    • 一种可能的方式是直接放到某个结点中
    • [待更新]
GBDT
  • 如果使用的是树模型(CART)作为基分类器是否可以直接借用树模型对缺失值的处理方法?
  • 如果使用的是逻辑回归模型作为基分类器,需要自己对缺失值进行处理
XGBoost
  • 寻找分裂点时(split point):
    • 不遍历缺失值对应的样本,只使用无缺失的样本确定分裂点(节省开销)
    • 尝试将缺失值分配到左叶子结点或者右叶子结点,分别计算增益(保证完备性)
    • 选择增益大的点即可
  • 如果训练时没有缺失值,预测时有缺失值:
    • 将缺失值自动放到右子树中即可

不同模型对缺失值的敏感度总结

不敏感模型

  • 树模型
  • 神经网络的鲁棒性强, 数据量够的话不敏感?
    • 神经网络的输入必须是没有缺失值的,需要我们使用特征工程的方法填充缺失值
    • 其实我觉得自己需要填充数据的其实都有点敏感吧, 所以神经网络在使用时有时候感觉并不敏感,因为缺失值被我们填充后总能得到不错的效果

敏感模型

  • 距离度量模型: KNN, SVM等
    • 在尽量保证缺失值是随机的前提下使用基于统计分布的填充方法可能降低缺失值造成的负面影响
    • 但是SVM这样的模型对缺失值的抗性非常差,不恰当的非随机缺失值可能导致模型出现意外
  • 线性模型的损失函数往往也涉及到距离的计算?

Chrome——常用插件总结

Chrome是最好用的浏览器没有之一,本文将总结Chrome中功能强大的插件


Markdown Viewer

  • 用于浏览markdown文件内容
  • 自动为markdown文件生成HTML源码
  • 安装后记得设置允许该插件访问文件URL

油猴插件(Tampermonkey)

  • 脚本引擎,用于管理其他脚本
  • 功能强大,里面有形形色色黑科技油猴脚本

Postman

  • 程序员必备,测试API首选
  • 各种http接口(如GET, POST)和数据格式(如表格)

AdBlock

  • 能屏蔽大部分的广告

SwitchyOmega

  • shadowsocks的伴侣
  • 特别是Ubuntu必备

Conference——各种会议知识总结

NIPS和Advances in Neural Information Processing Systems的关系

  • NIPS 会议全称为:Annual Conference on Neural Information Processing Systems,年度神经信息处理系统大会
  • NIPS 收录的会议论文会以“Advances in Neural Information Processing Systems”为名出版成书,一般由 mit 和 Morgan Kaufmann 出版社出版
  • 引用时一般会以“Advances in Neural Information Processing Systems”命名

ICLR不在CCF列表中

  • ICLR(International Conference on Learning Representations) 是近年来的会议,也是很好的会议,甚至可以比拟 NIPS,但是因为国内发的不多,所以没有列到 CCF列表 中
  • ICLR 在清华的列表中是 A 类

SIGKDD vs KDD

  • 这两者指的是同一个会议,一般正式叫做 SIGKDD
  • 会议全称:ACM Knowledge Discovery and Data Mining

AI 顶会 & 顶刊排名

  • AI顶会&顶刊
  • 人工智能方面顶级会议(转)

FS——文件系统总结


FAT32

常用于闪存

优点

  • 通用格式,任何USB都会预装FAT32,任何操作系统平台上都能读写
  • 节约空间

缺点

  • 单个文件的大小限制为4GB
  • 无读写日志,不能记录磁盘上文件的修改记录?

ExFAT

微软自家创建的用来取代FAT32的新型文件格式类型

优点

  • 跨平台
  • 能支持4GB以上的单个文件

缺点

  • 无读写日志,不能记录磁盘上文件的修改记录

NTFS

New Technology File System
微软为硬盘和固态硬盘创建的默认新型文件系统
几乎集成了所有文件系统的优点

优点

  • 日志功能
  • 无文件大小限制
  • 支持文件压缩和长文件名
  • 服务器文件管理权限

缺点

  • MacOS不能写,只能读(严格来说并不能算是NTFS的问题,是苹果自己不适配吧)

MacOS日志式

优点

  • 没啥缺点也是一种优点

缺点


总结

  • 如果要在MacOS上访问使用就用ExFAT,否则一律使用NTFS
  • 对于移动硬盘可以考虑部分分区格式化为ExFAT,部分分区格式化为NTFS

IDEA——一些常见的问题解决方案

本文总结一些IDEA使用过程中遇到的问题和解决方案


模块导入问题

  • 同一目录下Python模块导入有红色波浪线,但是可以运行
    • 解决方案:
      • 将当前文件夹标记成Source Root
      • 如果是使用的from,可使用当前文档标记符来解决问题
        1
        from .test_module import test_function
1…454647…64
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

631 posts
53 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4