Ai时代的Blog换脸与字体压缩

不得不感叹,ai时代的强大,博主的本职是一个运维,对前端和CSS懂的不多,用的软件是Hugo,其实对这个也一窍不通,就是选了一个自己看得顺眼的theme,然后就是用来写文章,期间也尝试去做些改变,均已失败告终,主要是对hugo、theme、css不清楚,也不知道怎么改。 现在有了AI的加持,这个一直用了好多年的blog的样子也看烦了,准备换掉。 旧的界面: 新的界面: 用了gemini,花了一天时间,主要是嫌弃底部的前一页、后一页看起来永无止境,于是让ai搓了一个底部导航条。也定制了css。确实惊艳一枪 但是过程中吧,用到了一个开源的字体:OPPOSans-Regular.ttf ,OPPO手机的字体,这个文件居然有9兆之巨 完犊子了,加载字体都要花半天时间,这也太恐怖了,于是搜优化方案 那就是压缩字体,把博客中出现的所有字扫描一遍,然后生成小字体文件,这样就好了 那就要用到 glyphhanger 了 注意:这个软件用起来及其麻烦,一定要在Windows下装,Linux下简直是垃圾成堆 本体是一个nodejs的软件,用到了python的fonttools,还装了puppyteer进行网页的抓取,简直是杂烩大集合 # 要压缩成woff2,所以要装python的2个包 npm install -g glyphhanger pip install fonttools pip install brotli #打开hugo的预览,http://localhost:1313/ hugo serve # 当前目录下的OPPOSans-Regular.ttf,生成压缩过的字体文件,格式是woff2 glyphhanger --subset OPPOSans-Regular.ttf --formats=woff2 --output ./ http://localhost:1313/posts/ ttf的完整字体经过压缩成woff2,从9兆变成了4.52兆,然后扫描网页后又压缩到了最终的75.5KB. 这会生成一个OPPOSans-Regular-subset.woff2的新字体文件,75.5KB 没完呢,我们还要在CSS里指定字体范围 # 压缩字体文件,同时生成CSS样式 glyphhanger --css --subset fonts/OPPOSans-Regular.woff2 --formats=woff2 --output newfonts http://localhost:1313/posts/ 会生成CSS文件,我们把它放进定制的custom.css中即可 @font-face { font-family: OPPO Sans; src: local("OPPO Sans"), url(OPPOSans-Regular-subset.woff2) format("woff2"); unicode-range: U+A,U+20-3E,U+40-7D,U+A9,U+B7,U+BB,U+2013,U+25BA,U+2705,U+3001,U+3002,U+4E00,U+4E09-4E0B,U+4E0D,U+4E14,U+4E1A,U+4E1C,U+4E24,U+4E2A,U+4E2D,U+4E34,U+4E3A,U+4E3B,U+4E48,U+4E49,U+4E4B,U+4E4E,U+4E50,U+4E5F,U+4E66,U+4E71,U+4E86,U+4E89,U+4E8B,U+4E8C,U+4E8E,U+4E94,U+4E9B,U+4EA6,U+4EA7,U+4EAB,U+4EBA,U+4ECA,U+4ECE,U+4ED3,U+4EE3-4EE5,U+4EEC,U+4EF6,U+4EFB,U+4EFD,U+4F01,U+4F1A,U+4F20,U+4F3C,U+4F46,U+4F53,U+4F55,U+4F59,U+4F5C,U+4F60,U+4F73,U+4F7F,U+4FBF,U+4FDD,U+4FE1,U+4FEE,U+5019,U+503C,U+5047,U+504F,U+505A,U+50CF,U+513F,U+5143,U+5148,U+5165,U+5168,U+516B,U+516C,U+5171,U+5173,U+5176,U+5177,U+517C,U+5185,U+518D,U+5197,U+5199,U+51B3,U+51B5,U+51C0,U+51C6,U+51FA,U+51FB,U+51FD,U+5206,U+5212,U+5219-521B,U+521D,U+5220,U+522B,U+5230,U+524D,U+526F,U+529E-52A1,U+52A8,U+52B2,U+5305,U+5316,U+5355,U+535A,U+5360,U+5361,U+5373,U+5386,U+53BB,U+53C2,U+53C8,U+53CA,U+53D1,U+53D6,U+53D8,U+53E3,U+53E4,U+53EA,U+53EF,U+53F0,U+53F7,U+53F8,U+5404,U+540C-540E,U+5411,U+5426,U+5427,U+542F,U+544A,U+5462,U+547D,U+548C,U+54EA,U+554A,U+5565,U+5668,U+56DB,U+56DE,U+56E0,U+56E7,U+56FD,U+56FE,U+5728,U+5730,U+573A,U+5740,U+5757,U+578B,U+57DF,U+585E,U+5883,U+589E,U+5904,U+5907,U+590D,U+5916,U+591A,U+5927,U+5929,U+592A,U+5931,U+5948,U+597D,U+5982,U+59CB,U+5B50,U+5B57,U+5B58,U+5B83,U+5B89,U+5B8C,U+5B98,U+5B9A,U+5B9E,U+5BA2,U+5BB6,U+5BB9,U+5BBD,U+5BC6,U+5BCC,U+5BF9,U+5BFC,U+5C01,U+5C06,U+5C31,U+5C3D,U+5C45,U+5DDE,U+5DF1,U+5DF2,U+5E02,U+5E03,U+5E38,U+5E72-5E74,U+5E76,U+5E78,U+5E7F,U+5E8F,U+5E93,U+5E94,U+5EFA,U+5F00,U+5F04,U+5F0F,U+5F20,U+5F31,U+5F39,U+5F52,U+5F53,U+5F55,U+5F85,U+5F88,U+5F97,U+5FAA,U+5FC3,U+5FC5,U+5FD7,U+5FEB,U+6000,U+6001,U+600E,U+601D,U+6027,U+603B,U+6062,U+606F,U+6089,U+60A8,U+60C5,U+60F3,U+6109,U+610F,U+613F,U+6210-6212,U+6216,U+6237,U+623F,U+6240,U+624B,U+624D,U+6253,U+6254,U+6258,U+6267,U+6269,U+627E,U+6280,U+628A,U+62A5,U+62C9,U+62DF,U+62E9,U+62EC,U+62FF,U+6301,U+6307,U+6309,U+636E,U+6377,U+6389,U+6392,U+63A5,U+63A8,U+641C,U+642D,U+6446,U+6447,U+64CD,U+652F,U+6539,U+653E,U+6545,U+6570,U+6574,U+6587,U+65AD,U+65B0,U+65B9,U+65E0,U+65E5,U+65E9,U+65F6,U+660E,U+6613,U+662F,U+663E,U+666F,U+66B4,U+66F4,U+6700,U+6708,U+6709,U+670D,U+671F,U+672A,U+672C,U+672F,U+673A,U+6743,U+6765,U+677E,U+677F,U+6790,U+679C,U+67D0,U+67E5,U+6807,U+6837-6839,U+683C,U+6848,U+6863,U+68AD,U+68C0,U+6A21,U+6B21,U+6B4C,U+6B62,U+6B63,U+6B65,U+6B7B,U+6BB5,U+6BCF,U+6BD4,U+6C42,U+6C89,U+6CA1,U+6CD5,U+6CE8,U+6D3B,U+6D4B,U+6D88,U+6DFB,U+6E05,U+6E90,U+6EE5,U+6F0F,U+706B,U+70B9,U+70C2,U+70E6,U+7130,U+7136,U+7167,U+719F,U+723D,U+7248,U+7279,U+72B6,U+72EC,U+73A9,U+73AF,U+73B0,U+7406,U+751F,U+7528,U+7531,U+754C,U+7591,U+7684,U+76D6,U+76EE,U+76F4,U+76F8,U+7701,U+770B,U+771F,U+77E5,U+77ED,U+7801,U+7814,U+786E,U+793A,U+79BB,U+79CD,U+79D2,U+79DF,U+79F0,U+79FB,U+7A00,U+7A0B,U+7A76,U+7A7A,U+7ACB,U+7AD9,U+7ADE-7AE0,U+7AEF,U+7B2C,U+7B49,U+7B54,U+7B7E,U+7B80,U+7B97,U+7BA1,U+7C7B,U+7CFB,U+7D22,U+7E41,U+7EA7,U+7EAF,U+7EC6,U+7ECF,U+7ED3,U+7ED9,U+7EDC,U+7EDF,U+7EED,U+7F3A,U+7F51,U+7F6E,U+7F72,U+7FA4,U+8003,U+8005,U+800C,U+800D,U+8017,U+804C,U+8054,U+80AF,U+80CC,U+80FD,U+8106,U+8111,U+811A,U+817E,U+81EA,U+81F4,U+8272,U+8282,U+8350,U+83B7,U+83DC,U+85CF,U+8651,U+865A,U+884C,U+88AB,U+88C2,U+88C5,U+88D5,U+897F,U+8981,U+8986,U+89C9,U+89D2,U+89E3,U+8A00,U+8B66,U+8BA1,U+8BA2,U+8BAF,U+8BB2,U+8BBA,U+8BBE,U+8BBF,U+8BC1,U+8BD5,U+8BDD,U+8BE2,U+8BE5,U+8BE6,U+8BED,U+8BEF,U+8BF4,U+8BF7,U+8C03,U+8C37,U+8C61,U+8D25,U+8D44,U+8D70,U+8D77,U+8DD1,U+8DDF,U+8DEF,U+8DF3,U+8DF5,U+8F7B,U+8F7D,U+8F83,U+8F93,U+8FC1,U+8FC7,U+8FD0,U+8FD4,U+8FD8,U+8FD9,U+8FDB,U+8FDE,U+9000-9002,U+9009,U+901A,U+903C,U+9047,U+904D,U+9053,U+9075,U+90A3,U+90E8,U+90FD,U+914D,U+91CA,U+91CC,U+91CD,U+91CF,U+94FE,U+9501,U+9519,U+952E,U+955C,U+957F,U+95EE,U+95F4,U+9632,U+963B,U+9645,U+964B,U+9650,U+9664,U+968F,U+9690,U+969C,U+96BE,U+96C6,U+9700,U+975E,U+9762,U+9875,U+9876,U+987B,U+9884,U+9891,U+9898,U+9996,U+9AA4,U+9AD8,U+9EBB,U+FF01,U+FF0C,U+FF1A,U+FF1F; font-style: normal; font-display: swap; font-weight: 300 700; } 把压缩后的字体和CSS再次提交后博客的速度就变得飞快了。 ...

2025年12月13日

LibreChat如何配置接入Azure

Ai的时代已经来临,现在主用的是谷歌的antigravity,然后平时对话询问,用的是LibreChat,给个链接 https://github.com/danny-avila/LibreChat 然后这个东西吧,配起来没那么简单,还是 docker compose 的部署方法快捷 那有openrouter的api,这个接入简单。那同时之前还有用Azure gpt-4o的api,用的是Javis,直接接入Azure Javis偏弱,现在想统一用到LibreChat,接入还真的是一言难尽 来详细说说Azure的配法,背景:Azure是同事在2024年开的,只给了我三个参数 AZURE_OPENAI_API_BASE="https://pppqqq-eus.openai.azure.com/" OPENAI_API_KEY="12xxxaaabbbccccccccccccgggggggggggggggggggggggggggCOGuy6M" AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-abc" 然后呢,同事还离职了,然后其它参数就都问不到了 LibreChat呢,文档简直稀烂,而且变动还特别大 LibreChat主要的思路呢,就是在.env中定义变量,然后在librechat.yaml中使用变量,我们就省了,都扔到librechat.yaml中好了 endpoints: azureOpenAI: titleModel: "gpt-4o" plugins: true groups: - group: "azure" apiKey: "12xxxaaabbbccccccccccccgggggggggggggggggggggggggggCOGuy6M" instanceName: "pppqqq-eus" version: "2024-05-01-preview" # 推荐使用新版API,以支持GPT-4o # --- Model-Level Mapping (重点) --- models: # LibreChat显示的名称: 实际Azure部署的名称 gpt-4o: # <--- 这个名称会显示在LibreChat的下拉菜单中 deploymentName: "gpt-4o-abc" # <--- 您的实际Azure部署名称 version: "2024-05-01-preview" # 可覆盖group的version gpt-4-turbo-2024-04-09: # LibreChat显示的名称 deploymentName: "gpt-4o-abc" # 您的实际Azure部署名称 version: "2024-02-15-preview" 上面的配置是问了gemini给出的,看得出来变量AZURE_OPENAI_API_BASE中的xxx.openai.azure.com前面的xxx,就是instanceName OPENAI_API_KEY就是apiKey AZURE_OPENAI_DEPLOYMENT_NAME就是deploymentName 模型的gpt-4o还有gpt-4-turbo-2024-04-09都是geimini给出的,version也很重要,不对会报错 然后就可以了 ...

2025年12月10日

Milvus生产环境搭建

Milvus 是个向量数据库,可以在kubernetes里搭建,也可以独立出来搭建 helm的方式先不说,用docker-compose独立搭建的时候,居然自己跑了etcd和minio,还大摇大摆的把minio给暴漏了出来,minio的密码太简陋了,放在生产是肯定不行的。 那生产搭建的具体步骤如下,可以把minio的9091端口也给取消了: 一、准备好docker-compose.yml文件 services: etcd: container_name: milvus-etcd image: quay.io/coreos/etcd:v3.5.18 environment: - ETCD_AUTO_COMPACTION_MODE=revision - ETCD_AUTO_COMPACTION_RETENTION=1000 - ETCD_QUOTA_BACKEND_BYTES=4294967296 - ETCD_SNAPSHOT_COUNT=50000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd restart: unless-stopped healthcheck: test: ["CMD", "etcdctl", "endpoint", "health"] interval: 30s timeout: 20s retries: 3 minio: container_name: milvus-minio image: minio/minio:RELEASE.2024-12-18T13-15-44Z environment: MINIO_ACCESS_KEY: rendoumi MINIO_SECRET_KEY: 12345abcde volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data command: minio server /minio_data --console-address ":9001" restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3 standalone: container_name: milvus-standalone image: milvusdb/milvus:v2.6.0 command: ["milvus", "run", "standalone"] restart: unless-stopped security_opt: - seccomp:unconfined environment: ETCD_ENDPOINTS: etcd:2379 MINIO_ADDRESS: minio:9000 MQ_TYPE: woodpecker volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"] interval: 30s start_period: 90s timeout: 20s retries: 3 ports: - "19530:19530" depends_on: - "etcd" - "minio" networks: default: name: milvus 注意上面的文件,MINIO_ACCESS_KEY和MINIO_SECRET_KEY已经被改掉了。 ...

2025年12月3日

Minecraft自建服务器的搭建

儿子天天玩minecraft玩得不亦乐乎,就想自己搭个服务器,然后当家做主人 于是乎,那就搭个吧,没想到还真不太容易, 找到个网站:https://docker-minecraft-server.readthedocs.io/en/latest/ 用docker run吧死活跑不起来,大无语 改用docker compose就一下起来了: docker-compose.yaml services: mc: image: itzg/minecraft-server:latest pull_policy: always tty: true stdin_open: true ports: - "25565:25565" environment: EULA: "TRUE" volumes: # attach the relative directory 'data' to the container's /data path - ./data:/data 注意:关键的一步来了,启动后,到docker compose的当前目录 vi data/server.properties online-mode=false 否则客户端无法联上服务器。 那客户端呢,直接下载这个启动器,然后不用下载任何java的东西 https://webstatic.ctfile.com/assets/download/url94.ctfile.com/008854/HMCL-3.7.3.zip 打开后会自动下载java和客户端,然后连接服务器的25565端口,就可以愉快的玩耍了。

2025年11月24日

生产环境Flink的搭建

生产环境用到了Flink,那其实无论腾讯的flink还是aws的flink,都是运行在容器中的 所以我们自建,也用容器,docker-compose.yaml一把梭 services: jobmanager: image: flink:1.17.1 ports: - "8081:8081" command: jobmanager environment: - | FLINK_PROPERTIES= jobmanager.memory.process.size: 4gb jobmanager.rpc.address: jobmanager taskmanager: image: flink:1.17.1 depends_on: - jobmanager command: taskmanager environment: - | FLINK_PROPERTIES= taskmanager.numberOfTaskSlots: 1 taskmanager.memory.process.size: 8gb jobmanager.rpc.address: jobmanager deploy: replicas: 1 注意啊,上面节点是16GB内存,所以job分了4G,然后task给了8G,TaskSlot给了1,副本也是1。内存富裕4G。是这么算的 我们再看一个配置,是测试环境的,节点是32G: services: jobmanager: image: flink:1.17.1 ports: - "8081:8081" command: jobmanager environment: - | FLINK_PROPERTIES= jobmanager.memory.process.size: 2gb jobmanager.rpc.address: jobmanager taskmanager: image: flink:1.17.1 depends_on: - jobmanager command: taskmanager environment: - | FLINK_PROPERTIES= taskmanager.numberOfTaskSlots: 4 taskmanager.memory.process.size: 8gb jobmanager.rpc.address: jobmanager deploy: replicas: 1 测试环境的内存就囧多了,jobmanager只给了2G,但是任务多且大,8G和Slot给了4,这个值是不断失败然后修改得到的 ...

2025年11月21日

Java进程cpu高排查

没办法了,被连续问到这个问题,必须讲一下了 java程序写得不好,要么cpu高,要么内存高,内存可以不断扩大,我们的某个pod,已经扩到8G了,node总共才16G,无语啊。 那究竟用啥玩意来排查呢? 那必须推荐async-profiler了 使用也很简单,运行排查即可 mkdir -p /opt/app/profiler wget https://github.com/async-profiler/async-profiler/releases/download/v4.2/async-profiler-4.2-linux-x64.tar.gz tar zxvf async-profiler-4.2-linux-x64.tar.gz -C /opt/app/profiler --strip-components=1 命令很简单,关键是参数-e的选择: 高 CPU 占用 (CPU-Bound)?缺省参数 首选: -e cpu 或 -e cycles 目标: 查看火焰图顶端最宽的方法,即消耗 CPU 最多的计算代码。 I/O 阻塞、锁等待或应用假死 (Latency/Blocking-Bound)? 首选: -e wall 目标: 查找火焰图顶端长时间处于 BLOCKED 或 I/O 相关的代码块,找出阻塞的根源。 频繁 GC 或内存消耗过快? 首选: -e alloc 目标: 找出哪些方法在短时间内创建了大量对象,导致年轻代 GC 频繁。 明确怀疑锁竞争? 首选: -e lock 目标: 准确找出哪个锁是主要的竞争点。 那确定目标,cpu是缺省参数,所以可以直接运行 ./asprof -d 30 -f 1.html 1 解释,-d 分析实践30秒,-f输出的文件,最后的1是java的进程号,这样就生成了一个1.html的文件 打开看看: ...

2025年11月17日

Kubernetes下各种资源的每日定期备份

这个问题其实比较有意思,同事早上用k9s修改Deployment的时候误删了一个deployment,这下麻烦了。那其实自己也有过类似操作,迁移的时候误删过influxdb,最后考古才弄回来,也误删过一个ns的secret,都是大麻烦! 其实系统是有velero备份的,但是从那里面去弄出单独一个ns的deploy进行恢复,怎么也来不及,那幸好有每日资源备份,直接拿过来重建即可。分享一下脚本,那注意一下,因为主节点的etcd也是非常要命的东西,也必须备一下,防止主节点脑裂,到时候可以用备份进行恢复。 #!/bin/bash export KUBECONFIG=/root/.kube/config export PATH=/usr/local/bin:$PATH # K8s 资源备份脚本 # 目标:遍历所有命名空间,将 Deployment, Secret, Ingress, 和 Service 导出为 YAML 文件。 # 设置输出目录 OUTPUT_DIR="/root/k8s_deployments_backup/$(date +%Y%m%d)" # 要备份的资源类型及其对应的 kubectl 别名 # 注意:Secret 资源会备份所有类型,包括自动生成的 Service Account Tokens。 RESOURCE_TYPES=("deployments" "secrets" "ingresses" "services") # 确保脚本在遇到错误时立即退出 set -e echo "--- 正在创建输出目录: $OUTPUT_DIR ---" mkdir -p "$OUTPUT_DIR" # ---------------------------------------------------- # 核心函数:备份指定类型的资源 # 参数: $1 = 资源类型 (如 deployment), $2 = 命名空间, $3 = 输出目录 # ---------------------------------------------------- backup_resource() { local TYPE=$1 local NAMESPACE=$2 local DIR=$3 # 获取当前命名空间中所有该类型资源的名称 # 使用 2>/dev/null 隐藏找不到资源时的错误信息 local RESOURCES=$(kubectl get "$TYPE" -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null) if [ -z "$RESOURCES" ]; then echo " [信息] 命名空间 $NAMESPACE 中未找到 $TYPE。" return fi echo " -> 找到 ${#RESOURCES[@]} 个 $TYPE,正在导出..." # 遍历每个资源并保存 YAML for RESOURCE in $RESOURCES; do # 文件名格式: namespace-resource_type-resourcename.yaml local FILE_NAME="${NAMESPACE}-${TYPE}-${RESOURCE}.yaml" local FILE_PATH="$DIR/$FILE_NAME" # 使用 kubectl get 获取 YAML,并通过管道和 sed 清理元数据字段 kubectl get "$TYPE" "$RESOURCE" -n "$NAMESPACE" -o yaml --ignore-not-found | # 清理元数据字段,使 YAML 更干净,便于重新应用 sed '/^ creationTimestamp:/d' | sed '/^ resourceVersion:/d' | sed '/^ uid:/d' | sed '/^ selfLink:/d' | sed '/^ status:/d' \ > "$FILE_PATH" # 检查 Secret 是否为 Service Account Token,如果是,则发出警告 if [ "$TYPE" == "secrets" ] && grep -q 'kubernetes.io/service-account.name' "$FILE_PATH"; then echo " [警告] Secret $RESOURCE 是 Service Account Token,通常不需要备份。" fi done } # 1. 获取所有命名空间 (Namespace) NAMESPACES=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}') if [ -z "$NAMESPACES" ]; then echo "错误:未找到任何命名空间。请检查 kubectl 配置和集群连接。" exit 1 fi echo "找到以下命名空间: $NAMESPACES" echo "----------------------------------------------------" # 2. 遍历每个命名空间和资源类型 for NAMESPACE in $NAMESPACES; do echo "--- 处理命名空间: $NAMESPACE ---" for TYPE in "${RESOURCE_TYPES[@]}"; do backup_resource "$TYPE" "$NAMESPACE" "$OUTPUT_DIR" done echo "--- 命名空间 $NAMESPACE 处理完成 ---" done ETCDCTL_API=3 etcdctl \ --endpoints=https://10.10.240.3:2379 \ --cacert=/etc/ssl/etcd/ca.pem \ --cert=/etc/ssl/etcd/etcd-client.pem \ --key=/etc/ssl/etcd/etcd-client-key.pem \ snapshot save $OUTPUT_DIR/etcd-`date +%Y%m%d`-snapshot.db echo "====================================================" echo "✅ 所有指定资源已成功备份到目录: $OUTPUT_DIR" echo "====================================================" exit 0

2025年11月17日

AWS 放错在Private子网的机器如何用公网IP连进去

今天面试了一下,面试官问到AWS的子网问题,来说说AWS的最佳实践: 首先是VPC的划定,然后就是三个public子网,三个private子网,都是一个zone分布在a、b、c三个不同机房 来保证最大冗余性,然后pub子网通过IGW来出公网,那Private子网就通过NAT出公网。 但是,但是,但是: 如果你把一台EC2服务器一开始就放错了子网,放到了private子网里,然后上面又跑了重要的服务,无法迁移,无法重启,而且整个vpc里只有这一台ec2,其它东西都是aws的服务或者市场的服务又或者fargate、lambda之类的无服务器,这时候你想进去调试,那就麻烦大了 那能不能给这台private的机器加上公网ip来当作跳板机直接访问呢? 答案是肯定的,可以。 但是又来了,如果这么配置了,你要对路由非常的熟悉,因为随后发生错乱的情况可能需要你手动添加路由,最麻烦的不是配置网卡,而是配置路由! 做法如下: 一、动态添加弹性ip到这个ec2的第二个网卡 二、配置网络 # vi /etc/netplan/50-cloud-init.yaml # This file is generated from information provided by the datasource. Changes # to it will not persist across an instance reboot. To disable cloud-init's # network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} network: ethernets: enX0: addresses: - 10.0.132.169/20 #The private IP address of primary ENI nameservers: addresses: - 10.0.0.2 routes: - to: 0.0.0.0/0 via: 10.0.128.1 # Default gateway, you can find it using** ip r** command table: 1001 - to: 10.0.132.169 via: 0.0.0.0 scope: link table: 1001 routing-policy: - from: 10.0.132.169 table: 1001 dhcp4: no dhcp6: false match: macaddress: 06:36:82:ef:39:39 set-name: enX0 enX1: addresses: - 10.0.3.176/20 #The private IP address of primary ENI gateway4: 10.0.0.1 nameservers: addresses: - 8.8.8.8 - 1.1.1.1 routes: - to: 0.0.0.0/0 via: 10.0.0.1 # Default gateway, you can find it using** ip r** command table: 1002 - to: 10.0.3.176 via: 0.0.0.0 scope: link table: 1002 routing-policy: - from: 10.0.3.176 table: 1002 - from: 120.116.111.99 table: 1002 dhcp4: no dhcp6: false match: macaddress: 06:29:c3:b2:c5:f9 set-name: enX1 version: 2 netplan apply 详细解释一下,enX0是private子网的网卡,enX1是弹性IP的网卡,注意,即使是弹性IP,也是个内网地址,这两个IP呢,都有各自独立的网关,第二个网卡还有自己的DNS。这就容易发生错乱了,因为缺省路由走第一个网卡,那如果从第二个网卡的公网IP入,出的时候走第一张网卡,那就有意思了。 ...

2025年11月16日

安装轻量级的Docker registry:zot

服务器在HK,但是去拉取腾讯广州Docker镜像仓的时候总是失败,还是企业版,大无语了。 没办法,被逼无奈,干脆在HK建一个镜像仓,然后推送到那里,这样就方便了。 刚开始的想法是用Harbor,但是Harbor实在太沉重了,而且Harbor的最大问题是过期镜像清理,如果过期镜像出错了,那是清理不掉的,除非重新打包上传覆盖掉错误的镜像,然后再删除才可以,但问题是如果有700、800个错的镜像,那是真没有那个劲去清理了。 那这次就想建个轻量级的,干脆连WEB界面都不要,只要docker login后能push和pull即可! 这样的话zot是个不错的选择,zot是兼容oci标准的,某些地方跟docker是不兼容的: 下载zot wget https://github.com/project-zot/zot/releases/download/v2.1.10/zot-linux-amd64 准备配置文件 cat >/usr/local/bin/config.json << EOF { "distSpecVersion": "1.0.0-dev", "http": { "address": "127.0.0.1", "port": "5000", "compat": ["docker2s2"], "auth": { "htpasswd": { "path": "/usr/local/bin/htpasswd" } } }, "storage": { "rootDirectory": "/data/registry" } } EOF 注意上面compat,如果不配置,就会出现push的时候出现manifest invalid错误,这是因为docker和oci的标准不同,zot是遵循oci标准的 准备htpasswd,必须用bcrypt的方式,zot不支持其它的! -B Force bcrypt hashing of the password (very secure) htpasswd -B -c /usr/local/bin/htpasswd user01 准备zot.service ...

2025年11月11日

Caddy自动签发任意域名证书

公司的SAAS平台,本来多租户的证书管理模式预计是这样的: 给每个租户单独设立一个 xyz.alibaba.com 的子域名,然后用cert-manager管理证书,只要annouce一个 xyz.alibaba.com 的ingress出来,就会自动签发证书。 然而研发觉得这样也是很不爽,连annouce ingress也不愿意,就想客户的域名直接解析一个 xyz.客户.com 的 cname 过来,然后就自动签发证书。 这个需求真的是很有意思,也只有caddy能很轻松的这么实现,方法如下: 一、做好一个caddy的deploy和service,前面直接顶一个Loadbalance 二、配置caddy的配置 /etc/caddy/Caddyfile 的内容 { debug on_demand_tls { ask http://localhost:5555/ } } https:// { tls { on_demand } handle { reverse_proxy https://alibaba.com { header_up Host alibaba.com } } respond "Welcome to dynamic certificate signing!" 200 } http://localhost:5555 { respond 200 } 注意上面,Caddy对这种随便的域名,是要求去问一下后端的服务是否对这个域名进行签发的,这是为了防止滥用。 我们直接做个虚拟服务,回应200即可 这样的做法比较直接了当,当然只适用于国外,国内就麻烦了,未知的域名解析到你的ip上,没备案直接就会被封站。

2025年11月10日