修复 GitHub Pages 推送后 CNAME 自动重置旧域名的问题

部署后 GitHub Pages 域名自动变成已经取消的实效的域名,而不是预期的 *.github.io

排查之后是source/CNAME 文件中配置了旧域名 airag.click

删除 CNAME 文件后重新部署就可以解决

1
rm source/CNAME
  • CNAME 文件会被 Hexo 复制到生成目录,告诉 GitHub Pages 使用自定义域名
  • 删除后将使用默认的 <username>.github.io 域名

使用 AWS SES + S3 发送 HTML 邮件

在营销、通知等场景中,我们经常需要发送格式丰富的 HTML 邮件。本文介绍如何用 Python + boto3,从 S3 读取 HTML 模板并通过 SES 发送邮件。

架构

S3 (HTML模板) → Python脚本 → SES → 收件人

前置条件

  1. AWS 账号已开通 SES 服务,且发件地址已验证
  2. S3 Bucket 中已上传 HTML 模板文件
  3. 本地已配置 AWS 凭证(aws configure 或 IAM Role)
  4. 安装依赖:pip install boto3

核心代码

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
#!/usr/bin/env python3
"""从 S3 读取 HTML 模板并通过 SES 发送邮件"""
import boto3

REGION = '<region>'
BUCKET = '<your-bucket-name>'
TEMPLATE_KEY = '<your-template-key>.html'

s3 = boto3.client('s3', region_name=REGION)
ses = boto3.client('ses', region_name=REGION)


def get_html_from_s3(bucket: str, key: str) -> str:
"""从 S3 读取 HTML 内容"""
response = s3.get_object(Bucket=bucket, Key=key)
return response['Body'].read().decode('utf-8')


def send_html_email(to: str, subject: str, html_content: str):
"""发送 HTML 邮件"""
ses.send_email(
Source='<sender-email>',
Destination={'ToAddresses': [to]},
Message={
'Subject': {'Data': subject, 'Charset': 'UTF-8'},
'Body': {'Html': {'Data': html_content, 'Charset': 'UTF-8'}}
}
)
print(f'邮件已发送至 {to}')


if __name__ == '__main__':
html = get_html_from_s3(BUCKET, TEMPLATE_KEY)
send_html_email('<recipient-email>', '测试邮件', html)

关键点说明

  • get_html_from_s3:通过 s3.get_object 拉取 HTML 文件内容,注意 decode(‘utf-8’) 确保中文正常显示
  • send_html_email:调用 ses.send_email,将 HTML 作为邮件 Body 发送,指定 Charset: UTF-8 避免乱码
  • Source 地址必须是 SES 中已验证的邮箱或域名

一次拿trace把langfuse打挂的修复

用 Langfuse 做 LLM 观测平台,拉 trace 数据时不小心把服务端打挂了。本文记录从发现 502 到定位 Node.js OOM,再到写脚本安全导出标注数据的完整过程。

Langfuse 是一个开源的 LLM 观测平台,用来追踪 LLM 应用的调用链路、记录 input/output、做人工标注评估等.

阅读更多

小抄们

git clone –depth 1 是浅克隆(shallow clone),只拉取最近一次提交的历史记录,而不是整个仓库的完整历史。

主要好处:

下载速度快,节省带宽和磁盘空间
适合只想获取最新代码、不关心历史记录的场景
比如一个有几千次提交的大仓库,完整克隆可能要几百 MB,用 –depth 1 可能只需要几十 MB。

常见用法:

只拉最新一次提交

git clone –depth 1 https://github.com/user/repo.git

拉最近 10 次提交

git clone –depth 10 https://github.com/user/repo.git
缺点是你看不到完整的 commit 历史,也没法切换到旧的分支或标签。如果后续需要完整历史,可以用 git fetch –unshallow 补全。

Amazon Bedrock AgentCore 开发实战(一):本地构建 AI Agent

在 AI Agent 开发过程中,快速迭代和即时反馈至关重要。Amazon Bedrock AgentCore 提供了完整的本地开发支持,让开发者能够在本地环境中构建、测试和调试 Agent,然后无缝部署到云端。

本文将带您从零开始,在本地搭建 AgentCore 开发环境,并通过详细的代码解析,帮助您深入理解每一个技术细节。

Amazon Bedrock AgentCore 简介

Amazon Bedrock AgentCore 是一套专为 AI Agent 设计的企业级基础设施服务。它解决了 Agent 从原型到生产过程中的核心挑战:

  • AgentCore Runtime:无服务器运行时环境,支持最长 8 小时的任务执行和 100MB 的请求负载
  • AgentCore Memory:提供短期和长期记忆管理,支持跨会话的上下文保持
  • AgentCore Gateway:统一的工具网关,支持 MCP 协议的工具发现和调用
  • AgentCore Identity:安全的身份认证和授权管理

本地开发的优势在于:开发者可以在熟悉的环境中快速验证想法,无需等待云端部署,同时保持与生产环境一致的代码结构。

阅读更多

Milvus Workshop Web 版上线:4 万 Star 项目的官方实战教程,在手机上也能直接学了

Milvus 作为全球最受欢迎的开源向量数据库,GitHub Star 数已突破 4 万

向量数据库已经成为 AI 应用的核心基础设施。RAG 需要它存储知识库,Agent 需要它实现记忆,推荐系统需要它计算相似度,多模态搜索需要它做特征检索。从实验室到生产环境,向量检索已经是 AI 应用的标配能力。

但从入门到真正用好 Milvus,这条路并不短。

分布式架构需要理解——Proxy、Coord、Node 各司其职,一条查询请求在系统内部如何流转?Schema 怎么设计、Chunk 策略怎么选、混合搜索怎么配?与 LangChain、LangGraph 怎么集成?生产环境更是另一个战场——内存优化、写入调优、慢查询排查,每一个都是实打实的工程问题。

为了系统性地解决这些问题,我们整理了这份 《Milvus Workshop:从入门到应用》

这份教程已经在多场线下 Workshop 中经过验证,帮助数百位开发者快速上手 Milvus。现在,我们上线了网页版——打开浏览器就能学,手机、平板、电脑无缝切换。

阅读更多

零代码改动!用 Docker 将 Flask 应用部署到 AWS Lambda

你有一个现成的 Flask API,想部署到 AWS Lambda 享受 Serverless 的好处,但又不想改代码?AWS Lambda Web Adapter 可以帮你实现。

本文将手把手教你如何使用 Docker + Gunicorn + Lambda Web Adapter,将 Flask 应用部署到 Lambda,并通过 API Gateway 对外提供服务。

为什么选择这个方案?

传统方式部署 Flask 到 Lambda 需要使用 Mangum、aws-wsgi 等第三方库,需要修改代码添加 handler。而 Lambda Web Adapter 是 AWS 官方方案,有以下优势:

  • 零代码改动:Flask 代码完全不用改
  • 生产级配置:可以使用 Gunicorn 作为 WSGI 服务器
  • 本地开发友好:同一个 Docker 镜像本地和 Lambda 都能跑
  • 框架无关:Flask、Django、FastAPI 都支持

架构概览

1
2
3
4
5
客户端 → API Gateway → Lambda (Docker 容器)

Lambda Web Adapter

Gunicorn + Flask

Lambda Web Adapter 作为 Lambda Extension 运行,负责将 API Gateway 事件转换为标准 HTTP 请求,Flask 应用完全感知不到自己运行在 Lambda 上。

准备工作

确保你已安装:

  • Docker
  • AWS CLI(已配置凭证)
  • Python 3.11+

第一步:创建 Flask 应用

创建项目目录和文件:

1
mkdir flask-lambda && cd flask-lambda

app.py - 一个简单的 Flask API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route('/')
def health():
return jsonify(status='healthy')

@app.route('/api/hello')
def hello():
name = request.args.get('name', 'World')
return jsonify(message=f'Hello, {name}!')

@app.route('/api/echo', methods=['POST'])
def echo():
data = request.get_json()
return jsonify(received=data)

requirements.txt

1
2
flask==3.0.0
gunicorn==21.2.0

第二步:配置 Gunicorn

gunicorn.conf.py - 针对 Lambda 优化的配置:

1
2
3
4
5
6
7
8
bind = '0.0.0.0:8080'
workers = 1 # Lambda 单实例,1 个 worker 足够
threads = 4 # 多线程处理并发
timeout = 30
keepalive = 2
accesslog = '-' # 输出到 stdout,方便 CloudWatch 收集
errorlog = '-'
loglevel = 'info'

第三步:编写 Dockerfile

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM python:3.11-slim

# 添加 Lambda Web Adapter
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["gunicorn", "-c", "gunicorn.conf.py", "app:app"]

核心就是这一行 COPY --from=...,它从 AWS 公共 ECR 仓库拉取 Lambda Web Adapter 二进制文件,放到 /opt/extensions/ 目录。Lambda 启动时会自动加载这个 Extension。

第四步:本地测试

构建前先登录 ECR Public:

1
2
aws ecr-public get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin public.ecr.aws

构建并运行:

1
2
docker build --platform linux/amd64 -t flask-lambda .
docker run -p 8080:8080 flask-lambda

测试:

1
2
3
4
5
6
7
8
9
10
curl http://localhost:8080/
# {"status":"healthy"}

curl "http://localhost:8080/api/hello?name=Lambda"
# {"message":"Hello, Lambda!"}

curl -X POST http://localhost:8080/api/echo \
-H "Content-Type: application/json" \
-d '{"msg":"hello"}'
# {"received":{"msg":"hello"}}

本地没问题,接下来部署到 AWS。

第五步:推送镜像到 ECR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 设置变量
AWS_REGION="ap-northeast-1"
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ECR_REPO="flask-lambda"

# 创建 ECR 仓库
aws ecr create-repository --repository-name $ECR_REPO --region $AWS_REGION

# 登录 ECR
aws ecr get-login-password --region $AWS_REGION | \
docker login --username AWS --password-stdin \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com

# 打标签并推送
docker tag flask-lambda:latest \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest

docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest

第六步:创建 Lambda 函数

首先创建 IAM 角色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建信任策略
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF

# 创建角色并附加权限
aws iam create-role \
--role-name flask-lambda-role \
--assume-role-policy-document file://trust-policy.json

aws iam attach-role-policy \
--role-name flask-lambda-role \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

sleep 10 # 等待角色生效

创建 Lambda 函数:

1
2
3
4
5
6
7
8
aws lambda create-function \
--function-name flask-api \
--package-type Image \
--code ImageUri=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest \
--role arn:aws:iam::$AWS_ACCOUNT_ID:role/flask-lambda-role \
--timeout 30 \
--memory-size 512 \
--region $AWS_REGION

第七步:配置 API Gateway

打开 API Gateway 控制台,创建 REST API:

  1. 点击「创建 API」→ 选择「REST API」→「构建」
  2. 输入 API 名称,如 flask-api
  3. 创建资源:
    • 点击「创建资源」
    • 勾选「代理资源」
    • 资源路径填 {proxy+}
    • 点击「创建资源」
  4. 设置集成:
    • 集成类型选「Lambda 函数」
    • 勾选「Lambda 代理集成」
    • 选择你的 Lambda 函数 flask-api
  5. 同样为根路径 / 创建 ANY 方法,集成到同一个 Lambda
  6. 点击「部署 API」,阶段名填 v1

部署完成后,你会得到一个调用 URL,类似:

1
https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1

第八步:测试 API

1
2
3
4
5
6
7
API_URL="https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1"

curl $API_URL/
# {"status":"healthy"}

curl "$API_URL/api/hello?name=Serverless"
# {"message":"Hello, Serverless!"}

🎉 大功告成!你的 Flask API 已经运行在 Lambda 上了。

进阶:IAM 认证

如果你的 API 需要认证,可以在 API Gateway 中启用 IAM 认证。调用时需要对请求进行 SigV4 签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import boto3
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
import requests

def signed_request(method, url, data=None, headers=None):
session = boto3.Session()
credentials = session.get_credentials()

headers = headers or {}
request = AWSRequest(method=method, url=url, data=data, headers=headers)
# 注意:API Gateway 用 execute-api,Function URL 用 lambda
SigV4Auth(credentials, "execute-api", "ap-northeast-1").add_auth(request)

return requests.request(
method=method,
url=url,
headers=dict(request.headers),
data=data
)

resp = signed_request("GET", f"{API_URL}/api/hello")
print(resp.json())

更新部署

代码更新后,只需重新构建镜像并更新 Lambda:

1
2
3
4
5
6
7
docker build --platform linux/amd64 -t flask-lambda .
docker tag flask-lambda:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest
docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest

aws lambda update-function-code \
--function-name flask-api \
--image-uri $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO:latest

冷启动优化

首次请求可能需要 1-2 秒(冷启动),可以通过以下方式优化:

  1. 启用 Provisioned Concurrency:预热实例,消除冷启动
  2. 减小镜像体积:使用 slim 基础镜像,减少依赖
  3. 启用异步初始化:设置环境变量 AWS_LWA_ASYNC_INIT=true

总结

使用 Lambda Web Adapter,你可以:

  • 保持 Flask 代码不变
  • 使用熟悉的 Gunicorn 生产配置
  • 同一镜像本地和云端都能运行
  • 享受 Serverless 的弹性伸缩和按需付费

参考资料

学云计算到底是在学什么?

个人感觉 AWS 甚至很多云计算平台都被过分神话了。以下是个人经验,不喜勿喷。

人话版本

学什么?

  1. 怎么启动虚拟机,以及如何进行远程管理,比如 SSH 和 DRP。
  2. 对象存储 S3。如果你能自建 MinIO 或者 RustFS 也没问题,如果你喜欢 OpenStack Swift 也行。
  3. Docker/K8S。这帮人天天吹云原生,其实主要还是容器充分可以利用云上的弹性。
  4. 网络基础,比如为什么我连不上某台机器,能从网络链路都排查一遍。
阅读更多

使用 OpenList 将 S3 转换为 WebDAV

Amazon S3 是一种高可扩展、低延迟的对象存储服务,广泛用于存储和管理数据。尽管目前有多个工具来将 S3 与其他存储解决方案(如 Storage Gateway,EMRFS 或者 S3FS 等)集成,但今天我们介绍一个新的方法,通过使用 OpenList,将 S3 存储转换为 WebDAV,简化文件管理和访问。

本文将引导你通过 Docker Compose 启动 OpenList,并将其与 Amazon S3 配置,以便通过 WebDAV 协议进行访问。

阅读更多
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×