• 渐进式图片加载

    摘自 2023/7/25 笔记,很多图片失效了。。。后续要补充

    渐进式图片加载(Progressive Loading)可以在展示图片的时候让图片从一张模糊的图片过渡到一张清晰的图片。

    • 非渐进式:像扫描仪一样从上往下逐渐显示
    • 渐进式:先显示大致的轮廓,在加载z过程中再逐渐变清晰

    渐进式加载不能从根本上提高图片的加载速度,但因为用户看到图片的时间提前了,可以给用户一种“看上去加载得比较快”的错觉,从而提升使用体验。实际开发中,渐进式图片可以配合懒加载一起使用,提升网站性能。

    图片的渐进式解码

    渐进式图片加载依赖编码/解码。

    渐进式解码是从不完整的图像文件增量解码图像部分的功能。解码完整图像所需的传递数取决于图像文件格式和用于创建图像的编码过程。(传递数的意思就是经过几次模糊的轮廓到清晰的图片这个过程)

    图像满足渐进式解码条件:

    • 图像格式必须支持渐进式解码:JPEG、PNG 和GIF
    • 图像文件必须编码为渐进式图像
    • 编解码器必须支持渐进式解码

    如何判断图片是否为渐进式

    图片编码

    以JPEG图片格式为例,JPEG图片编码以FFD8开头,FFD9结尾,FFDA代表一个帧的开头,传统编码只有一个FFDA,渐进式编码则有多个FFDA

    参考:https://en.wikipedia.org/wiki/JPEG

    Linux

    执行命令,输出为Plane则为渐进式图片

    identify -verbose filename.jpg | grep Interlace

    保存/转换为渐进式图片

    PhotoShop

    存储为“web所用格式”,选择“连续”就是渐进式 JPEG

    PHP

    <?php
        $im = imagecreatefromjpeg('pic.jpg');
        imageinterlace($im, 1);
        imagejpeg($im, './php_interlaced.jpg', 100);
        imagedestroy($im);
    ?>

    Python

    import PIL
    from exceptions import IOError
    img = PIL.Image.open("./test.jpg")
    destination = "./dest.jpeg"
    try:
        img.save(destination, "JPEG", quality=80, optimize=True, progressive=True)
    except IOError:
        PIL.ImageFile.MAXBLOCK = img.size[0] * img.size[1]
        img.save(destination, "JPEG", quality=80, optimize=True, progressive=True)

    参考文档:https://cloudinary.com/blog/progressive_jpegs_and_green_martians


  • 理解浏览器network stalled状态

    摘自 2023/11/07 笔记

    最近在回归直播插件状态上报情况时,发现时不时会出现一次耗时6分钟的请求,让人匪夷所思

    一开始以为是接口问题,联系接口负责同学排查。重启服务后还是多次出现这种情况。查看请求 `timeline` 发现阻塞发生在”stalled”状态

    学习一下官方文档中对network请求各个阶段的解释

    Stalled. The request could be stalled after connection start for any of the reasons described in Queueing.

    翻译一下:由于排队中描述的任何原因,请求可能会在连接启动后停滞,似懂非懂,在搜一下 stack overflow 怎么说的:

    Time the request spent waiting before it could be sent. This time is inclusive of any time spent in proxy negotiation. Additionally, this time will include when the browser is waiting for an already established connection to become available for re-use, obeying Chrome’s maximum six TCP connection per origin rule.


    翻译一下:Stalled 是请求在发送前等待所花费的时间(也就是说跟接口一点关系都没有)。 该时间包括协商代理所花费的任何时间(检查本地代理!)。

    此外,这个时间还包括浏览器等待已建立的连接可供重复使用的时间,遵守 Chrome 的每个源最多 6 个 TCP 连接的规则。(理解为连接池,如果6个连接都被占满了,就只能等待资源释放)。

    如果是网络不稳定造成的等待,就不会每次都是相同的6分钟,每次都是等待6分钟,对应Chrome 6个TCP连接规则。破案了!

    解决

    为每个请求设置超时时间,当超过设置等待时间则cancel请求,减少资源占用。

    const xhr = new XMLHttpRequest();
    xhr.timeout = 60 * 1000;     // 设置超时时间
    xhr.open('POST', url);

  • Windows Server Cheat Sheet

    raw 镜像转换至 vhdx

    linux – How do I convert an .img file to vhd? – Super User

    直接简单粗暴扔给 qemu-utils 处理就可以了。

    qemu-img convert ./debian-12-nocloud-amd64-20250428-2096.raw -O vhdx ./debian-12-nocloud-amd64-20250428-2096.vhdx

    可以添加 -o subformat=fixed-o subformat=dynamic 来配置目标 vhdx 是否为动态大小。

    创建类似 VMware 中 NAT 类型的虚拟交换机

    Set up a NAT network | Microsoft Learn

    Get-NetNat (NetNat) | Microsoft Learn

    Hyper-V NAT 网络设置固定 IP / DHCP – wswind – 博客园

    创建虚拟网卡和配置其静态 IP 可以在可视化界面中完成,也可以用 PowerShell:

    New-VMSwitch -SwitchName "SwitchName" -SwitchType Internal
    PS C:\> Get-NetAdapter
    
    Name                  InterfaceDescription               ifIndex Status       MacAddress           LinkSpeed
    ----                  --------------------               ------- ------       ----------           ---------
    vEthernet (intSwitch) Hyper-V Virtual Ethernet Adapter        24 Up           00-15-5D-00-6A-01      10 Gbps
    Wi-Fi                 Marvell AVASTAR Wireless-AC Net...      18 Up           98-5F-D3-34-0C-D3     300 Mbps
    Bluetooth Network ... Bluetooth Device ...                    21 Disconnected 98-5F-D3-34-0C-D4       3 Mbps
    New-NetIPAddress -IPAddress <NAT Gateway IP> -PrefixLength <NAT Subnet Prefix Length> -InterfaceIndex <ifIndex>

    创建 NAT,没找到对应的可视化工具。

    New-NetNat -Name <NATOutsideName> -InternalIPInterfaceAddressPrefix <NAT subnet prefix>

    创建 DNAT 规则:

    Add-NetNatStaticMapping -ExternalIPAddress "0.0.0.0/0" -ExternalPort 6881 -Protocol TCP -InternalIPAddress "192.168.1.100" -InternalPort 6881 -NatName Default

    启用 CIFS/SMB 在 Linux 中挂载

    samba – How do I mount a CIFS share via FSTAB and give full RW to Guest – Ask Ubuntu

    How to mount a windows/samba windows share under Linux? – Unix & Linux Stack Exchange

    安装依赖,然后挂载测试:

    apt install cifs-utils
    mount -t cifs -o "username=debian" -o "password=password" //192.168.1.1/assets /mnt/assets

    确认无误后写入 /etc/fstab

    //192.168.1.1/assets /mnt/assets cifs username=username,password=password,uid=1000,gid=1000 0 0

    别忘了验证,防止影响操作系统启动:

    umount /mnt/assets
    systemctl daemon-reload
    mount -a


  • 腾讯云轻量对象存储的简单体验

    腾讯云给轻量服务器锐驰型的用户发了相同时长的轻量对象存储 50G 免费额度,正好来体验一下。

    控制台上挂载到轻量服务器上使用

    控制台上面非常简洁,看起来基本上就是只希望客户使用这种方式了。使用门槛很低,只需要配置相同地域的服务器(不一定非要锐驰型)和路径就会自动帮忙挂载。

    底层也是 cosfs 实现的,应该是用 Agent 自动下发的挂载命令,对新手很友好。搭配 200M 的机器,对于个人开发者来说可以省下一笔不少的费用。

    这种方式没有什么更多要说的了,唯一可能要注意的事情是:部分修改文件可能会导致整个文件重传。更多类似限制,可以查阅 cosfs 的相关文档。另外,cosfs 也有了支持 posix 的后继者 GooseFS-Lite,参见这里。但未来趋势可能还是存储网关。

    当然了,对象存储更多的使用场景还是得通过 API 来进行存取,下面简单来尝试一下。还是有所要注意的地方的。

    通过 API/SDK 调用 – 以 Loki 为例

    看轻量对象存储的文档的话,会发现其实它还是支持 API/SDK 调用的,Endpoint 和标准的 COS 是一样的:

    那么我们就先来创建子账号和对应所需的权限。经过尝试,如果按照 lhcos(轻量对象存储)进行授权的话,通过 S3 兼容接口调用还是会报错 403,这里按 cos(标准对象存储)授权就可以了。这里也吐槽一下,由于这个 appid 不是用户主账号 uid,就没法使用对象存储的诊断工具了。

    {
        "statement": [
            {
                "action": [
                    "cos:GetBucket",
                    "cos:GetObject",
                    "cos:PutObject",
                    "cos:DeleteObject"
                ],
                "effect": "allow",
                "resource": [
                    "qcs::cos:<region>:uid/<uid>:<bucketName>-<uid>/*"
                ]
            }
        ],
        "version": "2.0"
    }

    配置 Loki 使用轻量对象存储,这里给出关键的配置片段:

    loki:
      storage:
        type: s3
        s3:
          endpoint: cos.ap-hongkong.myqcloud.com
          region: ap-hongkong
          accessKeyId: <ak>
          secretAccessKey: <sk>
        bucketNames:
          chunks: <bucketName>-<uid>
          ruler: <bucketName>-<uid>
          admin: <bucketName>-<uid>
      schemaConfig:
        configs:
          - from: 2024-04-01
            object_store: s3
            store: tsdb
            schema: v13
            index:
              prefix: index_
              period: 24h

    由于我这里是跨云的集群,如果 CoreDNS 不在内网会导致解析对象存储的 Endpoint 是公网 IP,会把账单打爆,所以这里还需要配置 DNS:(说明一下,这里不知道什么毛病,dnsConfig 居然用了 tpl 方法但是 values.yaml 里面又是个 map,所以只能喂个 string,然后部署的时候会有警告)

    singleBinary:
      replicas: 1
      nodeSelector:
        kubernetes.io/hostname: hkg-qcloud
      dnsConfig: |-
        nameservers:
          - 183.60.83.19
          - 183.60.82.98

    启动后发现 Loki 有报错,日志没有持久化:

    level=error ts=2025-04-23T08:34:33.088568269Z caller=flush.go:261 component=ingester loop=29 org_id=xxx msg="failed to flush" retries=1 err="failed to flush chunks: store put chunk: InvalidArgument: invalid x-cos-storage-class for role mode bucket, only support intellingent tiering or default\n\tstatus code: 400, request id: xxx, host id: , num_chunks: 1, labels: {app=\"loki\", component=\"gateway\", container=\"nginx\", filename=\"/var/log/pods/monitoring_loki-gateway-6fb5686c6f-42wcc_ce7d81e7-a5fb-4436-b2e8-51abf866f056/nginx/0.log\", instance=\"loki\", job=\"monitoring/loki\", namespace=\"monitoring\", node_name=\"hkg-qcloud\", pod=\"loki-gateway-6fb5686c6f-42wcc\", service_name=\"loki\", stream=\"stderr\"}"

    看起来是 Loki 在调用上传的时候,加了对象的预期存储类型,轻量对象存储可能刚好简化了这个类型。尝试从代码里面找的话, 可以发现确实支持进行配置:

    文档中也给出了相应说明:Grafana Loki configuration parameters | Grafana Loki documentation。所以我们可以新增配置:

    loki:
      storage_config:
        aws:
          storage_class: INTELLIGENT_TIERING

    问题解决。Loki 日志显示开始正常存取日志块,并且轻量对象存储控制台上也能看见相应的文件了。

    附录

    完整的 Loki values.yaml

    deploymentMode: SingleBinary
    loki:
      commonConfig:
        replication_factor: 1
      storage:
        type: s3
        s3:
          endpoint: cos.ap-hongkong.myqcloud.com
          region: ap-hongkong
          accessKeyId: <ak>
          secretAccessKey: <sk>
        bucketNames:
          chunks: <bucketName>-<uid>
          ruler: <bucketName>-<uid>
          admin: <bucketName>-<uid>
      storage_config:
        aws:
          storage_class: INTELLIGENT_TIERING
      schemaConfig:
        configs:
          - from: 2024-04-01
            object_store: s3
            store: tsdb
            schema: v13
            index:
              prefix: index_
              period: 24h
      limits_config:
        retention_period: 4320h
      compactor:
        retention_enabled: true
        delete_request_store: s3
    singleBinary:
      replicas: 1
      nodeSelector:
        kubernetes.io/hostname: hkg-qcloud
      dnsConfig: |-
        nameservers:
          - 183.60.83.19
          - 183.60.82.98
    chunksCache:
      enabled: false
    gateway:
      nodeSelector:
        kubernetes.io/hostname: hkg-qcloud
    resultsCache:
      enabled: false
    read:
      replicas: 0
    backend:
      replicas: 0
    write:
      replicas: 0

  • domain 的自言自语 2504

    Linux

    深入理解Linux Netlink机制:进程间通信的关键 – 知乎

    The proc/net/tcp and proc/net/tcp6 variables — The Linux Kernel documentation

    kernel.org/doc/Documentation/networking/proc_net_tcp.txt

    How to increase swap space? – Ask Ubuntu

    GitHub – canonical/cloud-utils: This package provides a useful set of utilities for interacting with a cloud.

    apt install cloud-guest-utils -y
    growpart /dev/sda 1
    e2fsck -f /dev/sda1
    resize2fs /dev/sda1

    读书

    大教堂与集市 (豆瓣)

    网络

    HTTPS 温故知新(四) —— 直观感受 TLS 握手流程(下)

    Submarine Cable Map

    杂项

    calculator-app – Chad Nauseam Home
    搞个计算器是真的不容易。

    Site Names in Google Search | Google Search Central  |  Documentation  |  Google for Developers
    发现搜索的时候,站点名称没有展示,而是显示为域名。

    找到了从哪里获取的就比较好办了,这段可以直接在 functions.php 里面注入:

    add_action('wp_head', function () {
        echo '<script type="application/ld+json">{"@context":"http://schema.org","@type":"WebSite","name":"我们的笔记","url":"' . home_url() . '"}</script>';
    });

  • linuxserver/openssh-server mods

    最近希望给一个服务器启动一个独立的 openssh-server 容器用于安全转发内部接口请求,发现 linuxserver/openssh-server 镜像非常适合这一场景。

    不过这个镜像默认是不开启 TCP 转发的,官方提供了一个较为优雅的模块插件机制来实现类似功能,模块可以在这里找到:Linuxserver Container Mods。只要配置环境变量 DOCKER_MODS=linuxserver/mods:openssh-server-ssh-tunnel 就可以自动加载了。

    看起来模块只是一些脚本,具体的实现在这里:docker-mods/root/etc/s6-overlay/s6-rc.d/init-mod-openssh-server-ssh-tunnel-setup/run at f7fc561d103d6832bb75a4cb4f575b1166180430 · linuxserver/docker-mods · GitHub

    那么模块是怎么被拉取的呢,离线的私有化场景中怎么处理?可以在这里找到:docker-mods/docker-mods.v3 at cac9e7450a0698f19d750b67db61c4aa214d5290 · linuxserver/docker-mods

    也就是说,模块实际上都是 Docker 镜像,并且这个脚本已经支持了自定义制品库拉取,我们应该可以编写自己的模块,放到私仓,然后在离线环境中拉取并加载使用了。有空的时候来测试一下这个场景。

    引用

    For the ones who don’t know about the existence of Linuxserver Docker mods : r/selfhosted


  • webpack loader

    webpack 只能理解 JavaScript 和 JSON 文件。

    在 webpack 中,loader 用于对模块的源代码进行转换。可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。

    配置

    module.rules 允许你在 webpack 配置中指定多个 loader。

    loader 从右到左(或从下到上)地取值(evaluate)/执行(execute)

    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              { loader: 'style-loader' },
              {
                loader: 'css-loader',
                options: {
                  modules: true,
                },
              },
              { loader: 'sass-loader' },
            ],
          },
        ],
      },
    };

    在以上例子中:

    1. 执行 sass-loader :Sass 或 SCSS 文件编译成 CSS 代码。
    2. 执行 css-loader:解析第一步生成的 CSS 文件,将 CSS 代码转换成 JavaScript 模块;options: { modules: true } 启用 CSS 模块化功能,这意味着每个 CSS 文件都会被视为一个独立的模块,可以避免样式冲突。
    3. 执行 style-loader :将 css-loader 处理后的 CSS 代码动态地插入到 HTML 页面的 <style> 标签中。

    特性

    • 链式调用:一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript。
    • loader 可以是同步的,也可以是异步的
    • loader 运行在 Node.js 中,并且能够执行任何操作
    • loader 可以通过 options 对象配置
    • 除了常见的通过 package.json 的 main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块。
    • 插件(plugin)可以为 loader 带来更多特性。
    • loader 能够产生额外的任意文件。

    类型

    • loader: 核心的转换器,负责将源文件转换成 webpack 可以理解的模块。
    • preLoader: 在普通 loader 之前执行的 loader,主要用于预处理文件,例如代码检查(linting)。
    • postLoader: 在普通 loader 之后执行的 loader,主要用于后处理文件,例如优化或添加额外功能。

    执行顺序

    webpack 在处理模块时,loader 的执行顺序如下:

    1. preLoader
    2. loader
    3. postLoader

    举个例子

    preLoader 示例:ESLint 代码检查

    module: {
      rules: [
        {
          enforce: 'pre', // 指定为 preLoader
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'eslint-loader',
          options: {
            // eslint 配置选项
          },
        },
        // 其他 loader 配置
      ],
    },

    postLoader 示例:添加版权信息

    • 创建一个自定义的 postLoader,在代码处理完成后,自动添加版权信息到 JavaScript 文件的顶部。
    //copyright-loader.js
    module.exports = function(source) {
      const copyright = '// Copyright (c) 2024 Your Company\n';
      return copyright + source;
    };
    module: {
      rules: [
        {
          enforce: 'post',  // 指定为 postLoader
          test: /\.js$/,
          exclude: /node_modules/,
          use:{
            loader:path.resolve(__dirname,'copyright-loader.js')
          }
        },
        // 其他 loader 配置
      ],
    },


  • 新版 K3s CNI 目录变化导致 Istio 安装失败

    最近在研究 Istio,在最新正式版的 K3s v1.31.5+k3s1 版本下,安装最新正式版 Istio 1.24.3 时,一直卡在 ztunnel 安装这里,于是来发个牢骚。

    现象

    使用 Helm 安装 ztunnel 之后,发现 ztunnel 无法调度,报错 IP 池不足:failed to allocate for range 0: no IP addresses available in range set。事实上每个节点上调度的 Pod 相当少,IP 不足是不可能的。而且在这之后,其它 Pod 如果重新调度,也会报同样错误,说明集群网络可能出现问题了。

    调查

    任选一个节点,发现 /var/lib/cni/networks/cbr0 目录下已经整个 IP 池被占用,没有释放。参考一个 Issue:pod creation failure: “no IP addresses available in range set” · Issue #4682 · k3s-io/k3s,尝试将 IP 释放,释放后发现,如果再次尝试安装 ztunnel 就会导致 IP 快速被耗尽,而且 ztunnel 的报错信息中有 CNI 组件相关内容(忘记截图),因此考虑 K3s 中是否有其它不同的因素导致其工作异常。

    经过一番搜寻之后发现 K3s 还真的有一个相关的变化,CNI bin dir changes with K3s version · Issue #10869 · k3s-io/k3s 这里引入了一个 CNI 路径的变化,从 /var/lib/rancher/k3s/data/current/bin/ 变为了 /var/lib/rancher/k3s/data/cni/

    一开始我看到 Rancher 当下的文档 Additional Steps for Installing Istio on RKE2 and K3s Clusters | Rancher 中提到的路径都是前者,就没有怀疑,看来问题就恰好出在这里。

    注意到 Istio 的 Helm Chart 中,有一个提交对此做出了适配:fix: fix k3s cniBinDir to static path (#54112) · istio/istio@7693f57 · GitHub,但是不幸的是还没有正式版的 Release,也就是说,当前安装的 Istio 1.24.3 仍然是旧的 CNI 路径,因此和较新版本的 K3s 不兼容。不过,我发现当下已经有 RC 版本(1.25.0-rc.0)的 Helm Chart 带上了这个变更,所以尝试清理 IP 池后,将 Istio 组件都更新到 1.25.0-rc.0,果然 ztunnel 成功运行了。

    引用

    Istio / Platform-Specific Prerequisites

    kubenet/kubelet leaks ips on docker restart · Issue #34278 · kubernetes/kubernetes

    kubenet ip泄漏 – xiaoqing blog

    「Bug」K8s 节点的 IP 地址泄漏,导致 IP 被耗尽 – 於清樂 – 博客园

    failed to allocate for range 0: no IP addresses available in range set: 172.20.xx.1-172.20.xx.254 – yuxiaoba – 博客园

    Containerd IP leakage · Issue #5768 · containerd/containerd

    Kubernetes-cni issue with 1.9.0 – no ip address available in range · Issue #57280 · kubernetes/kubernetes