分类目录归档:技术笔记

Tailscale 自建 DERP 并配置 SSL 完整教程

Tailscale 在很多场景有着广泛应用,k3s 天然有针对它的支持,最近在基于这个方案构建遍布全球的分布式私有网络。官方的 DERP 服务质量并不稳定, 自建后稳定很多,本文记录详细的过程。

申请 ssl 证书

install acme.sh

这里给出了两种源,国内源为个人自用,不保证可用性。

# global
$ curl https://get.acme.sh | sh -s email=ca@frytea.com
# install acme.sh in china
git clone https://ghproxy.mirror.skybyte.me/https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install -m ca@frytea.com

因为我的服务器 80/443 都被占用,无法采用 HTTP 验证,故示例 CloudflareDNSPod 两家 DNS 验证方法,根据自己实际情况选择即可,其他用法请查阅官方文档 dnsapi

CloudFlare DNS

# derper.xxx.xxx.com 是你的域名,需要解析到你的服务器
$ CF_Token="xxxxxx" CF_Zone_ID="xxxxxx" acme.sh --dns dns_cf --issue -d derper.xxx.xxx.com 
...
[Tue Apr 22 04:54:20 AM PDT 2025] The domain key is here: /root/.acme.sh/derper.xxx.xxx.com_ecc/derper.xxx.xxx.com.key
...
$ mkdir -p /opt/derper/certs
$ acme.sh --install-cert -d derper.xxx.xxx.com --ecc --key-file /opt/derper/certs/derper.xxx.xxx.com.key --fullchain-file /opt/derper/certs/derper.xxx.xxx.com.crt

DndPod DNS Check

# derper.xxx.xxx.com 是你的域名,需要解析到你的服务器
$ DP_Id="xxxxxx" DP_Key=xxxxxx acme.sh --dns dns_dp --issue -d derper.xxx.xxx.com
$ mkdir -p /opt/derper/certs
$ acme.sh --install-cert -d derper.xxx.xxx.com --ecc --key-file /opt/derper/certs/derper.xxx.xxx.com.key --fullchain-file /opt/derper/certs/derper.xxx.xxx.com.crt

配置 Derper

install go

$ GOVERSION=1.23.4 GOARCH=amd64 rm -rf go${GOVERSION}.linux-${GOARCH}.tar.gz && wget https://mirrors.nju.edu.cn/golang/go${GOVERSION}.linux-${GOARCH}.tar.gz -O go${GOVERSION}.linux-${GOARCH}.tar.gz
$ rm -rf /usr/local/go && tar -C /usr/local -xzf go${GOVERSION}.linux-${GOARCH}.tar.gz
$ export PATH=$PATH:/usr/local/go/bin
$ go version

build derper

go install tailscale.com/cmd/derper@main
cp /root/go/bin/derper /usr/local/bin/

script

/opt/derper/runderper

#!/bin/sh
cd /usr/local/bin/
nohup ./derper -hostname derper.xxx.xxx.com -c=derper.conf -a :1214  -http-port -1 -certdir /opt/derper/certs -certmode manual -stun-port 1214 -verify-clients -stun > console.log 2>&1 &
echo $! > app.pid

/opt/derper/stopderper

#!/bin/sh
kill `cat app.pid`
rm -rf app.pid

/etc/systemd/system/derper.service

[Unit]
Description=Derper service
After=network.target

[Service]
Type=forking
ExecStart=/opt/derper/runderper
ExecStop=/opt/derper/stopderper

[Install]
WantedBy=multi-user.target

Usage

# 开机自启并立即启动
systemctl enable --now derper.service

配置到 Tailscale

在你的 Tailscale 管理界面找到 Access Controls (直达 )

{
    "acls": [
    // ...
    ],
    "ssh": [
        // ...
    ],
    // ...
    "derpMap": {
        "OmitDefaultRegions": true, // true 表示不使用官方节点,仅使用自建,默认为 false,按需配置
        "Regions": {
            "900": {
                "RegionID":   900, // 900以上
                "RegionCode": "cn-gz", // 区域代码,会在 `tailscale netcheck` 显示
                "RegionName": "中国-广州", // 区域名称,会在 `tailscale netcheck` 显示
                "Nodes": [
                    {
                        "Name":     "1",
                        "RegionID": 900, // 对应上方ID
                        "HostName": "xxxxx.xxx", // 填写你的DERP服务域名
                        "DERPPort": 12345, // 你的 DERP 服务端口
                        "STUNPort": 1214, // 你的 STUN UDP 服务端口
                    },
                ],
            },
            // 更多DERP节点
        },
    },
    // ...
}

检查可用性

# 检查 DERP 连接情况
$ tailscale netcheck
Report:
        * Time: 2025-04-22T13:25:12.257986394Z
        * UDP: true
        * IPv4: yes, xx.xx.xx.xx:59494
        * IPv6: no, but OS has support
        * MappingVariesByDestIP: false
        * PortMapping: 
        * CaptivePortal: false
        * Nearest DERP: xx xx
        * DERP latency:
                - xx: 9.2ms   (xx xx)
                - xx: 26.9ms  (xx xx)
                - xx: 152.7ms (xx xx)
# 检查 Peer 连接情况
$ tailscale status
100.xx.xx.xx xx-node1          YOURNAME@ linux   -
100.xx.xx.xx xx-node2             YOURNAME@ linux   active; relay "xx", tx 43624652 rx 4118764

References

OpenManus 使用记录

安装运行

# 安装 uv(一个快速的 Python 包管理器):
$ curl -LsSf https://astral.sh/uv/install.sh | sh
# 克隆仓库:
$ git clone https://github.com/mannaandpoem/OpenManus.git
$ cd OpenManus
# 创建并激活虚拟环境:
$ uv venv --python 3.12
$ source .venv/bin/activate  # Unix/macOS 系统
# Windows 系统使用:
# .venv\Scripts\activate
# 安装依赖:
$ uv pip install -r requirements.txt
# 浏览器自动化工具(可选)
$ playwright install
# 在 config 目录创建 config.toml 文件(可从示例复制):
$ cp config/config.example.toml config/config.toml
$ vim config/config.toml
# 一行命令运行 OpenManus:
$ python main.py

实际效果

开始执行:

开始执行截图

执行过程

执行过程

执行结果:

执行结果

准确性:

中国黄金官网同一时刻截图

似乎不是很准确。

References

解决 Nginx Ingress returns 413 Entity Too Large

TL;DR

配置 ingress 服务时调整一下大小即可:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cafe-ingress-with-annotations
  annotations:
    nginx.org/proxy-connect-timeout: "30s"
    nginx.org/proxy-read-timeout: "20s"
    nginx.org/client-max-body-size: "4m"
    nginx.org/server-snippets: |
      location / {
        return 302 /coffee;
      }      
spec:
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /tea
        pathType: Prefix
        backend:
          service:
            name: tea-svc
            port:
              number: 80
      - path: /coffee
        pathType: Prefix
        backend:
          service:
            name: coffee-svc
            port:
              number: 80

References

绘图模型效果对比之城市气象

Promot

A highly detailed and photorealistic image of Akmenė, Pemagatshel, Lithuania, during a cloudy day with a temperature feel of 7.0°C. The scene captures the historic Church of St. John the Baptist, its intricate brickwork and architectural features glistening from recent rain. The wet pavement reflects the overcast sky, which is 76.0% cloud covered, creating a soft, diffused light that highlights the textures of the buildings and surrounding lush greenery. The foreground includes puddles forming on the cobblestone streets, while the background features dense, misty forests framing the town. The composition employs the rule of thirds, with the church positioned to the right, leading lines from the street guiding the viewer’s eye through the scene. The lighting enhances the mood, maintaining clarity and brightness, with details visible in the shadows. The overall atmosphere is tranquil and inviting, embodying the rich cultural heritage and natural beauty of Akmenė.

Refs: Akmenė, Lithuania Weather Art

black-forest-labs/FLUX.1-schnell

Lithuania-akmene-20250421-040215 By FLUX.1-schnell

ChatGPT 4o

Lithuania-akmene-20250421-040215 By ChatGPT 4o

MJ V6.1

Lithuania-akmene-20250421-040215 By MJ V6.1

Lithuania-akmene-20250421-040215 By MJ V6.1

【转】k8s 认知路线

From V

以下内容转载自 https://www.v2ex.com/t/968514#r_13557021

k8s 这个东西真的内容太多了,没有啥系统性的资料,里面各种知识点真的没法说,太多了,最好的就是看官方文档,并且结合工作当中的实践慢慢积累,才能由浅入深,只是看文档想掌握深点,个人感觉很困难。

如果你不是专做这行,只是把它当作你应用部署的底层平台的话可以给你个简单的流程做参考:
1 、云服务商买一套,或者自己搭建个 k8s 环境
2 、运行起来一套简单的前后端分离服务,这里主要练习的是多个镜像启动多个不同的 workload ,然后怎么能互相访问对接,这个环节能掌握清楚 workload service ingress 都是干嘛的,该怎么用,怎么关联
3 、你会发现当你测试环境发生重启,或者 pod 重建后,数据库数据都没了,这会你就应该研究数据持久化了,pv pvc 的概念就出来了
5 、然后你又创建了个前端服务,想修改个前端页面的配置文件参数,比如网页的 title ,其他都一模一样,但是每个服务一个镜像,太麻烦了,容器里直接修改,重建就没了,配置文件放 pv pvc ,太小题大做,这会 configmap 出现了。连接数据库的配置文件,密钥明文,太 low 了,secret 出现了
6 、前端页面镜像有 bug ,必须要重打镜像了,cicd 出现了,你是选择 docker build 还是 jenkins ,新的知识又增加了
7 、更新 workload 的镜像,问题又来了,服务会不会受损,多副本就不会受损吗?如何优雅终止,健康检查,无损更新?
8 、服务高峰期怎么应对,手动扩副本数太傻,hpa 来了

如果你想深点,做些 k8s 运维或者技术支持的,那么除了上面的必须熟悉,下面的东西必知必会
1 、清楚 k8s 的工作逻辑,master 的三大件是干嘛的,kubelet ,kube-proxy ,coredns 都是干嘛的,比如执行个创建或查询一个 workload ,系统组件之间怎么通讯的,创建一个 pod 后,容器网络和外界是怎么打通的,k8s 资源调度和分配逻辑是什么
2 、自己搭建一套 k8s 集群,多 master 的最好,master 和 worker 节点分开(一定是自己搭建,不要购买云服务商现成的容器服务,自己搭建过程你会收获不少东西)
3 、熟练使用 kubectl 命令进行各种查询分析
4 、清楚 rbac 的功能和使用

剩下的太多太多了,说不完了,
等你发现 k8s 了解差不多了,发现总得有个监控吧,prometheus 出现了
流量治理,服务分析也得有吧,istio 来了
日志得持久化保存下吧,els 来了
学了那么多了,得给老板展示下漂亮帅气的监控吧,得研究 grafana 了。
服务都上 k8s 了,集群越来越重要,万一哪天 etcd 崩了怎么办,备份总得有吧,etcd 原理和备份恢复得研究下吧。
想自建镜像仓库了? harbor 研究下。
这个云平台太贵了,业务想换个云平台,集群要迁移,velero 走起
想给 pod 限速了,想给集群加审计了。。。。

References

OpenFOAM 两大分支的详细比较

OpenFOAM 的两个主要分支源自同一项目,但在 2011 年后走上了不同的发展道路。下面详细比较这两个版本的历史、版本发布情况、技术差异和适用场景。

历史背景

分叉原因

  • OpenFOAM 最初由 Henry Weller 和他的团队在 20 世纪 90 年代开发
  • 2004 年成立了 OpenCFD Ltd 公司商业化 OpenFOAM
  • 2011 年,SGI (Silicon Graphics International) 收购了 OpenCFD Ltd
  • 2012 年,ESI Group 从 SGI 购买了 OpenCFD Ltd
  • 这一系列收购后,原开发团队的一部分成立了 OpenFOAM Foundation,分叉了代码库

版本发布历史

OpenFOAM Foundation 版本

  • 命名规则:使用版本号(如 v2106, v2212)
  • 发布频率:通常每年 1-2 次大版本
  • 版本轨迹
    • v2.0.0(2011 年,首个独立版本)
    • v2.3.0(2014 年)
    • v4.0(2016 年)
    • v7(2019 年)
    • v9(2021 年)
    • v10(2022 年)
    • v11(2023 年)

OpenFOAM+ (plus) 版本

  • 命名规则:使用年份+月份(如 v1906, v2206)
  • 发布频率:每年 2 次,通常在 6 月和 12 月
  • 版本轨迹
    • v3.0+(2015 年)
    • v1606+(2016 年)
    • v1806+(2018 年)
    • v2006(2020 年)
    • v2206(2022 年)
    • v2312(2023 年)

技术差异

1. 代码结构与架构

Foundation 版本:

  • 更倾向于学术风格的代码组织
  • 更保守的代码重构,保持架构稳定性
  • 强调基础功能和核心求解器的可靠性

OpenFOAM+:

  • 更多工业应用导向的架构调整
  • 积极重构代码以提高性能
  • 增加了更多用户友好的界面和工具

2. 功能差异

功能领域 Foundation 版本 OpenFOAM+
网格生成 基础 snappyHexMesh 增强版 snappyHexMesh 和额外的网格工具
并行化 基础 MPI 支持 更好的负载平衡和分区技术
湍流模型 全面但更侧重基础模型 更多工业验证的高级模型
动态网格 基础功能 增强的动态网格功能和FSI(流固耦合)能力
多相流 VOF 等基本方法 增强的多相流模型和界面捕捉方法
预处理 基础工具 扩展的预处理工具和导入选项
后处理 基础 ParaView 集成 增强的可视化和数据分析工具
GPU 支持 有限 一些商业版本中有更好的支持

3. 具体技术特点差异

Foundation 版本独有或更强的特点:

  • 更纯粹的开源哲学
  • 更严格的代码审查流程
  • 更面向基础研究的算法实现

OpenFOAM+ 独有或更强的特点:

  • 扩展的几何处理功能
  • 商业CAD系统接口
  • 更广泛的物理建模能力
  • “Catalyst” 实时可视化
  • 针对云计算的一些优化

开发社区与发行形式

OpenFOAM Foundation 版本

  • 维护方式:由 OpenFOAM Foundation 维护,是一个非营利组织
  • 社区特点:更多学术和研究人员参与
  • 许可证:GPL v3
  • 发行形式:完全开源
  • 文档:开源文档,社区支持

OpenFOAM+

  • 维护方式:由 ESI Group 拥有的 OpenCFD Ltd 维护
  • 社区特点:更多工业用户和开发者
  • 许可证:GPL v3(核心代码),某些工具可能有不同许可
  • 发行形式:开源核心,加上一些专有扩展
  • 文档:更全面的官方文档,一些商业培训和支持

适用场景对比

OpenFOAM Foundation 版本适合

  • 学术研究和基础科学应用
  • 需要彻底理解和可能修改核心算法的用户
  • 教育环境和学习 CFD 的学生
  • 预算有限的小型组织和个人用户

OpenFOAM+ 更适合

  • 工业应用和生产环境
  • 需要现成解决方案的工程师
  • 有商业支持需求的用户
  • 处理复杂几何和多物理场问题

安装和使用差异

OpenFOAM Foundation 版本

  • Linux 为主要平台
  • 通过源代码构建或官方打包的二进制包安装
  • 较少的依赖项,更简单的安装流程
  • 更灵活的构建选项

OpenFOAM+

  • 更广泛的平台支持(Linux, Windows, macOS)
  • 提供更多预编译二进制包
  • Docker 镜像和虚拟机模板
  • 一些接口需要额外许可的商业软件

互操作性

两个版本之间的案例通常可以互相运行,但可能需要一些调整:

  • 某些字典文件的语法可能略有不同
  • 有些高级功能在另一个版本中可能不可用
  • 编译的自定义代码可能需要修改才能在另一版本上运行

总结建议

选择 OpenFOAM Foundation 版本的理由:

  • 追求纯粹开源体验
  • 主要用于学术研究
  • 希望更深入理解 CFD 原理和实现
  • 有能力自己扩展功能

选择 OpenFOAM+ 的理由:

  • 需要更多工业优化的功能
  • 重视用户友好性和易用工具
  • 需要商业级支持选项
  • 处理复杂工程应用

两个版本都在继续积极开发,并且相互借鉴一些想法。很多用户根据具体项目需求在两个版本间切换,或者同时安装两个版本以获取各自的优势。

第一个 CUDA 程序之矩阵运算计算效能对比

这是一个使用 CUDA 进行编程的实际例子,对比 CPU 和 GPU 在执行矩阵乘法时的性能差异。

运行效果

(base) root@gpu-1095cf160ec353b4e35a9-1-zqa76jnvthlx:~/data/CUDA/first# ./gpu_matrix_mult
GPU 执行时间: 0.000475046 秒
(base) root@gpu-1095cf160ec353b4e35a9-1-zqa76jnvthlx:~/data/CUDA/first# ./cpu_matrix_mult 
CPU 执行时间: 14.3784 秒

程序实例

示例:矩阵乘法

矩阵乘法是一个非常适合用 GPU 加速的计算密集型任务。我们将实现一个简单的矩阵乘法,分别在 CPU 和 GPU 上运行,并比较它们的执行时间。

1. CPU 实现 (C++)

#include 
<iostream>
#include 
<vector>
#include 
<chrono>

// CPU 矩阵乘法
std::vector
<float> cpuMatrixMult(const std::vector<float>& a, const std::vector<float>& b, int n) {
    std::vector
<float> c(n * n, 0.0f);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < n; ++k) {
                c[i * n + j] += a[i * n + k] * b[k * n + j];
            }
        }
    }
    return c;
}

int main() {
    int n = 1024; // 矩阵大小
    std::vector
<float> a(n * n, 1.0f);
    std::vector
<float> b(n * n, 1.0f);

    // 计时开始
    auto start = std::chrono::high_resolution_clock::now();
    std::vector
<float> c = cpuMatrixMult(a, b, n);
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration
<double> duration = end - start;

    std::cout << "CPU 执行时间: " << duration.count() << " 秒" << std::endl;

    return 0;
}

编译和运行 CPU 代码:

g++ cpu_matrix_mult.cpp -o cpu_matrix_mult
./cpu_matrix_mult

2. GPU 实现 (CUDA)

#include 
<iostream>
#include 
<vector>
#include 
<chrono>
#include <cuda_runtime.h>

// CUDA 核函数
__global__ void gpuMatrixMultKernel(const float* a, const float* b, float* c, int n) {
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;

    if (row < n && col < n) {
        float sum = 0.0f;
        for (int k = 0; k < n; ++k) {
            sum += a[row * n + k] * b[k * n + col];
        }
        c[row * n + col] = sum;
    }
}

// GPU 矩阵乘法
std::vector
<float> gpuMatrixMult(const std::vector<float>& a, const std::vector<float>& b, int n) {
    std::vector
<float> c(n * n, 0.0f);
    float *dev_a, *dev_b, *dev_c;

    // 分配 GPU 内存
    cudaMalloc((void**)&dev_a, n * n * sizeof(float));
    cudaMalloc((void**)&dev_b, n * n * sizeof(float));
    cudaMalloc((void**)&dev_c, n * n * sizeof(float));

    // 将数据从 CPU 复制到 GPU
    cudaMemcpy(dev_a, a.data(), n * n * sizeof(float), cudaMemcpyHostToDevice);
    cudaMemcpy(dev_b, b.data(), n * n * sizeof(float), cudaMemcpyHostToDevice);

    // 定义线程块和网格大小
    int blockSize = 32;
    dim3 blockDim(blockSize, blockSize);
    dim3 gridDim((n + blockSize - 1) / blockSize, (n + blockSize - 1) / blockSize);

    // 计时开始
    auto start = std::chrono::high_resolution_clock::now();

    // 调用 CUDA 核函数
    gpuMatrixMultKernel<<<gridDim, blockDim>>>(dev_a, dev_b, dev_c, n);

    // 等待 GPU 完成
    cudaDeviceSynchronize();

    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration
<double> duration = end - start;

    // 将结果从 GPU 复制回 CPU
    cudaMemcpy(c.data(), dev_c, n * n * sizeof(float), cudaMemcpyDeviceToHost);

    // 释放 GPU 内存
    cudaFree(dev_a);
    cudaFree(dev_b);
    cudaFree(dev_c);

    std::cout << "GPU 执行时间: " << duration.count() << " 秒" << std::endl;

    return c;
}

int main() {
    int n = 1024; // 矩阵大小
    std::vector
<float> a(n * n, 1.0f);
    std::vector
<float> b(n * n, 1.0f);

    gpuMatrixMult(a, b, n);

    return 0;
}

编译和运行 CUDA 代码:

  1. 安装 CUDA Toolkit: 确保你已经安装了 CUDA Toolkit,并且设置了正确的环境变量。
  2. 编译:
    nvcc gpu_matrix_mult.cu -o gpu_matrix_mult
  3. 运行:
    ./gpu_matrix_mult

    3. 性能对比分析

通常情况下,你会发现 GPU 的执行时间远小于 CPU 的执行时间,尤其是在矩阵大小 n 较大时。这是因为 GPU 具有大量的并行处理单元,可以同时处理矩阵中的多个元素。

影响性能的因素:

  • 矩阵大小: 矩阵越大,GPU 的优势越明显。
  • GPU 架构: 不同的 GPU 架构具有不同的性能。
  • 线程块大小: 合适的线程块大小可以提高 GPU 的利用率。
  • 内存传输: CPU 和 GPU 之间的数据传输会影响整体性能。

总结

这个例子展示了如何使用 CUDA 加速矩阵乘法。通过将计算任务转移到 GPU 上,可以显著提高程序的性能。请注意,实际的加速效果取决于你的硬件和具体的实现方式。 5

希望这个例子能帮助你入门 CUDA 编程!

Archlinux KDE Apache JMeter 配置高分屏缩放

ArchLinux 下安装 jmeter 很简单:

yay -S jmeter

但是在现代高分辨率显示器下 GUI 的字体和图标都很小 :

JMeter 缩放前

TL;DR

查阅文档后发现只需带有一个参数运行即可:

JVM_ARGS="-Dsun.java2d.uiScale=200%" jmeter

也可以把这个参数写入启动脚本中:

➜  which jmeter    
/usr/bin/jmeter  
➜  sudo vi /usr/bin/jmeter
#! /bin/sh
...
JVM_ARGS="-Dsun.java2d.uiScale=200%"
...

再启动就可以了:

JMeter 缩放后

KDE 下效果还不错。

其他平台处理方法类似,注入变量启动即可。

References

解决 gitlab-runner 移除残留文件 permission denied

最近使用遇到一些问题:

Running with gitlab-runner 17.10.0 (67b2b2db)
  on wz-arm64-host-runner8 Q3NRHTCy, system ID: s_a7d2f872e9b4
Preparing the "shell" executor 00:00
Using Shell (bash) executor...
Preparing environment 00:00
Running on xxx-runner8...
Getting source from Git repository 00:01
Fetching changes with git depth set to 50...
重新初始化已存在的 Git 仓库于 /home/gitlab-runner/builds/Q3NRHTCy/0/cloud/xxx-top/.git/
Checking out 2356a4c2 as detached HEAD (ref is release/2.6)...
warning: 删除 xxx/xxx/xxx/xxx/xxx-1.0/Makefile 失败: 权限不够
Cleaning up project directory and file based variables 00:00
ERROR: Job failed: exit status 1

最后发现是某一级路径的所有者被改变为 root 导致无法删除,造成该现象的原因未知。通过查阅资料和文档,在 gitlab-runner 的配置文件增加一行 pre_get_sources_script 解决:

[root@wz-kylin-v10-gfb-runner8 ~]# cat /etc/gitlab-runner/config.toml 
...

[session_server]
  session_timeout = 1800

[[runners]]
  ...
  pre_get_sources_script = "sudo chown -R gitlab-runner:gitlab-runner ."
  ...

References

nginx-ingress 配置路由 302

demo ingress

这个实例中,实现将 访问 https://image.frytea.com/Avatar.jpg 请求302到 https://image.frytea.com/i/Avatar.jpg

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  namespace: imagehost
  annotations:
    cert-manager.io/cluster-issuer: "dnspod-cluster-issuer"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      location = /Avatar.jpg {
        return 301 https://image.frytea.com/i/Avatar.jpg$is_args$args;
      }
spec:
  ingressClassName: nginx
  tls:
  - hosts:
      - image.frytea.com
      - imagehost-cdn.frytea.com
      - cdn-imagehost.frytea.com
    secretName: image-frytea-com-tls
  rules:
  - host: image.frytea.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app
            port:
              name: web

直接配置会提示报错:

➜  imagehost git:(main) ✗ kubectl apply -f ingress.yaml  
Error from server (BadRequest): error when applying patch:  
{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"networking.k8s.io/v1\",\"kind\":\"Ingress\",\"metadata\":{\"annotati  
ons\":{\"cert-manager.io/cluster-issuer\":\"dnspod-cluster-issuer\",\"nginx.ingress.kubernetes.io/configuration-snippet\":\"location = /Avatar.jpg {\\n  return 301 ht  
tps://image.frytea.com/i/Avatar.jpg$is_args$args;\\n}\\n\"},\"name\":\"app\",\"namespace\":\"imagehost\"},\"spec\":{\"ingressClassName\":\"nginx\",\"rules\":[{\"host\  
":\"image.frytea.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"app\",\"port\":{\"name\":\"web\"}}},\"path\":\"/\",\"pathType\":\"Prefix\"}]}},{\"ho  
st\":\"imagehost-cdn.frytea.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"app\",\"port\":{\"name\":\"web\"}}},\"path\":\"/\",\"pathType\":\"Prefix\  
"}]}},{\"host\":\"cdn-imagehost.frytea.com\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"app\",\"port\":{\"name\":\"web\"}}},\"path\":\"/\",\"pathType\  
":\"Prefix\"}]}}],\"tls\":[{\"hosts\":[\"image.frytea.com\",\"imagehost-cdn.frytea.com\",\"cdn-imagehost.frytea.com\"],\"secretName\":\"image-frytea-com-tls\"}]}}\n",  
"nginx.ingress.kubernetes.io/configuration-snippet":"location = /Avatar.jpg {\n  return 301 https://image.frytea.com/i/Avatar.jpg$is_args$args;\n}\n","nginx.ingress.k  
ubernetes.io/rewrite-rule":null}}}  
to:  
Resource: "networking.k8s.io/v1, Resource=ingresses", GroupVersionKind: "networking.k8s.io/v1, Kind=Ingress"  
Name: "app", Namespace: "imagehost"  
for: "ingress.yaml": error when patching "ingress.yaml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: annotation group ConfigurationSn  
ippet contains risky annotation based on ingress configuration

这个错误来自于 Nginx Ingress Controller 自带的一个叫做 “Admission Webhook” 的安全校验机制。它的作用是在你创建或更新 Ingress 资源时进行检查,防止应用不安全或可能导致问题的配置。 很多 Nginx Ingress Controller 的默认安装配置或者管理员策略禁用限制 configuration-snippetserver-snippet 这类强大的注解

开启 Snippet 注释

使用 helm 部署的 nginx-ingress ,首先修改 Values.yaml 中的内容,启动

controller:
  allowSnippetAnnotations: true

将配置应用到集群:

helm upgrade --install ingress-nginx ingress-nginx  \
    --repo https://kubernetes.github.io/ingress-nginx  \
    --namespace ingress-nginx --create-namespace -f vaules.yaml

之后调整 ConfigMap

kubectl -n ingress-nginx edit cm ingress-nginx-controller

增加两行:

...
apiVersion: v1
data:
  allow-snippet-annotations: "true"
  annotations-risk-level: Critical
  use-forwarded-headers: "true"
kind: ConfigMap
...

其中 :

  • annotations-risk-level: Critical: 设置 Webhook 接受的最高风险门槛,确保 Snippets(作为 Critical 风险注解)在被评估时不会因为风险等级过高而被直接拒绝,从而让 allow-snippet-annotations 的设置能够生效
  • use-forwarded-headers: "true": 告诉 Nginx Ingress Controller 信任并使用由其上游代理(通常是你的云服务商提供的负载均衡器,如 AWS ELB/ALB/NLB, GCP Load Balancer, Azure Load Balancer 等)设置的 X-Forwarded-*Forwarded HTTP 头部信息,来确定原始客户端的真实信息

再重启所有 nginx controller

kubectl rollout restart -n ingress-nginx daemonset ingress-nginx-controller

之后在尝试 apply 上面的 demo 就可以成功了。

References