懒猫微服进阶心得(十五):超越systemd,不用每次开机都再安装软件~

前言

懒猫微服是分层文件系统,所以在之前的文章里前面我们使用用 systemctl --user 在开机时自动安装软件,解决重启丢包的问题。但说实话,每次开机都要跑一遍 apt install,当面对软件包过多以及网络延迟的问题的时候,使用dkpg会卡住,这不够优雅,所以才有了这个方案。

后来我换了个思路:既然懒猫微服天生就是为 Docker 而生的,为什么不直接用容器来跑这些工具呢?

想想看,htop、iotop、glances 这些运维神器,本质上就是读取 /proc/sys 里的系统信息。只要让容器能访问宿主机的这些文件,不就能正常工作了吗?

这篇文章就来聊聊怎么用 Docker 容器跑系统监控工具。不用装软件,不怕重启丢失,一条命令就能用,用完自动清理。这让我们既享受了分层文件系统的优势,也不用再为软件持久化烦恼了。

阅读更多

零代码改动!用 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 的弹性伸缩和按需付费

参考资料

懒猫微服开发篇(八):反向代理与自签名 SSL 的优雅共存之道

在分布式系统中,HTTPS 是默认的通信规范。但当你在内部环境中部署服务时,特别是很多以容器方式运行的组件,但是很多时候
它们往往只默认开启了 HTTPS 接口,却附带了一个 自签名证书(Self-signed certificate)

这意味着,只要一层反向代理(reverse proxy)去转发 HTTPS 请求,TLS 校验就会报错
这不是配置错误,而是 SSL 的“安全特性”在起作用。

阅读更多

Mac 上 Orbstack的Docker容器访问微服 Wordpress IPv6 解析问题记录

今天在 Mac 上的 Docker 容器访问微服里的 Wordpress 时,遇到了 IPv6 无法正常访问的问题。
现象是:dig 能解析出 IPv6 地址,但容器内网络不可达。

https://appstore.lazycat.cloud/#/shop/detail/dev.beiyu.wordpress

问题现象

  • dig 查询正常,能返回 IPv6 结果。
  • 但容器内访问(curlping6)失败,提示网络不可达。
阅读更多

懒猫微服开发篇(七): 解析 Docker Compose Override

看过很多的 Docker 教程,也都不曾提到过 compose override,第一次接触到这个是在懒猫微服上解开 LPK 看到的,用来注入 docker 引擎的环境变量。但是还以为是懒猫微服的小技巧,今天整理笔记才发现原来的 Docker compose 用来做多环境部署的配置文件,比如用来给开发和生产分别注入不同的环境变量和配置文件。

参考文档:Docker Compose Override - LazyCat Developer Guide

阅读更多

懒猫微服进阶心得(十二):不登陆机器,如何使用 Docker Context 玩转微服容器?

平时管理远端的 Docker 容器,大多数人第一反应是通过 SSH 登录到服务器再执行命令。

其实 Docker 本身是 C/S 架构,只要配置好连接方式,就能在本地直接管理远程容器,甚至用 VS Code 图形化界面操作,完全不必反复登录。

下面我就用管理微服容器的例子,把实现方法和使用体验记录下来

1. 准备免密登录

为了避免奇怪的认证问题,可以先将 SSH Key 复制到远端,实现免密登录。
(/root 目录重启不会丢失 SSH Key)

1
ssh-copy-id root@name.heiyu.space

2. 最简单的方式:设置 DOCKER_HOST

直接在本地设置 Docker 引擎的环境变量,指向远端的 docker.sock 文件:

1
export DOCKER_HOST=ssh://root@name.heiyu.space

在 Warp 中(非直接 SSH 登录),执行 docker ps 后,就可以看到远端的容器了。

不过仔细看,这里其实是系统组件,没有必要随便动,而且千万不要随便操作系统组件容器

image-20250814200633245

3. 使用 Docker Context 管理非标准路径的 docker.sock

之前介绍过,playground 和 appstore 的 Docker 配置文件在其他目录。
这种情况下可以通过 docker context 引用非标准路径的 docker.sock:

1
2
3
4
docker context create my-remote-sock \
--docker "host=ssh://root@name.heiyu.space:/data/playground/docker.sock"

docker context use my-remote-sock

playground 的 Docker 也可以用 Dockge 管理,不过当 Dockge 的功能不够用时,就可以用这个作为备用方案。

4. 管理商店容器

商店有时需要执行 lzc-docker execlzc-docker restart,可以先创建对应的 Context:

1
2
docker context create lzc-remote-sock \
--docker "host=ssh://root@name.heiyu.space:/lzcsys/run/lzc-docker/docker.sock"

然后切换 Docker 引擎:

1
docker context use lzc-remote-sock

查看 Docker 信息:

1
2
3
4
5
6
7
8
9
Kernel Version: 6.5.0-0.deb12.4-amd64
Operating System: Debian GNU/Linux 12 (bookworm) (containerized)
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 31.12GiB
Name: lzcbox-029c588e
ID: 0726989e-2e3e-46d1-89ba-753f7dd1a600
Docker Root Dir: /lzcsys/run/data/system/docker

5. 本地 Warp 记录

image-20250814195406282

到这里,你会发现本地和远端几乎没有区别

6. 图形化管理:VS Code Docker 插件

在 VS Code 中安装 Microsoft 官方 Docker 插件,即可在界面中查看远端的 Image、Container、Logs,并且可以直接 exec 进入容器。

3398ac4853bb5a0a3be0a5a38d84e1b3

Docker Context 面板可以直接切换上下文(相当于 docker context use lzc-remote-sock),然后就能在 VS Code 中操作对应容器和镜像,无需手动敲命令。

image-20250814195227594

7. 查看已创建的 Docker Context

在命令行查看所有 Context:

1
docker context ls

输出示例:

1
2
3
4
5
6
7
8
NAME              DESCRIPTION                               DOCKER ENDPOINT
default * Current DOCKER_HOST based configuration ssh://root@name.heiyu.space
lzc-remote-sock ssh://root@name.heiyu.space:/lzcsys/run/lzc-docker/docker.sock
my-remote-sock ssh://root@name.heiyu.space:/data/playground/docker.sock
orbstack OrbStack unix:///Users/.orbstack/run/docker.sock

Warning: DOCKER_HOST environment variable overrides the active context.
To use a context, either set the global --context flag, or unset DOCKER_HOST environment variable.

总结

通过 docker context 配合 VS Code Docker 插件,不仅能用命令行直接操作远端微服容器,还能图形化查看容器状态、镜像和日志。
这种方式的好处是:

  • 免 SSH 登录,管理更高效;
  • 可切换多个 docker.sock,适合同时维护多个服务环境;
  • 配合 VS Code,操作体验接近本地容器。

对于习惯 GUI 操作的人来说,这几乎就是远程 Docker 的“丝滑”管理方式。下次维护微服时,你也可以试试这一套。

懒猫微服进阶心得(十三): WebVirtCloud 安装 Windows 使用 virtIO 的注意事项

在使用 WebVirtCloud 部署 Windows 虚拟机时,如果想要获得更高的磁盘与网络性能,建议使用 virtIO 半虚拟化驱动。不过,Windows 并不像大部分 Linux 发行版那样自带 virtIO 驱动,因此安装过程中需要额外设置。本文结合社区已有教程与实测经验,整理了安装 Windows 时使用 virtIO 的注意事项,方便大家快速上手。

社区原教程参考:

阅读更多

懒猫微服进阶心得(九):商店 App 如何接管 Docker 引擎?

在之前的内容中,我们提到过懒猫微服采用三套独立的 Docker 环境来隔离系统组件、Playground Docker 与商店 App 的 Docker 实例。那么问题来了:如何让商店中上架的 App 操作 Playground 中的 Docker 引擎?

答案是:通过挂载 docker.sock 文件来实现跨容器控制。

所以我们可以在商店的 APP 中操作 playground docker,其实也就是 Docker 面板或者轻量 Docker 面板做的事情。

为什么不操作其他两个 Docker 引擎?

  • 系统组件 Docker 无需干预,重启之后可以复原。
  • 应用商店有自己的生命周期,也无需干预。

一、在 build.yml 中挂载 Playground 路径

首先,在打包配置 build.yml 中新增 services 字段,用于将宿主机中的 /data/playground 挂载到容器内部:

阅读更多

懒猫微服进阶心得(八):如何在 ARM 机器上下载并运行X86镜像以及在X86上运行ARM镜像

之前写好应用做好镜像想扔到懒猫微服上打包,都是先使用 buildx 打包双架构镜像,push 到 dockerhub 上,然后再用微服的 copy image 转成国内的镜像地址,这过程很麻烦。

因为在 Apple Silicon(如 M1/M2 芯片)设备上,默认运行的是 ARM 架构镜像(linux/arm64)。但有些镜像或依赖只支持 X86(linux/amd64)架构。

本文将介绍如何在 ARM 设备上拉取并运行 X86 镜像,以及如何保存和加载镜像。

🐳 拉取 X86 架构的 Docker 镜像

使用 --platform=amd64 参数即可拉取 X86 架构镜像:

1
docker pull --platform=amd64 nginx:latest
  • docker pull:从远程仓库拉取镜像
  • --platform=amd64:显式指定拉取 x86_64 架构的镜像
  • nginx:latest:镜像名与标签

适用于在 M 系列 Mac 上使用 X86 镜像进行兼容性测试或运行仅支持 x86 的应用。

阅读更多

我用Amazon Q写了一个Docker客户端,并上架了懒猫微服商店

https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.containly

自从被种草了 Amazon Q,我陆陆续续写了不少小软件,其中这个 Docker 客户端是一个典型的例子,比较符合自己平时使用的习惯,也分享给一些朋友和 NAS 爱好者来用。

image-20250606190108571

故事还要用上次折腾黑群晖说起,本意想把 NAS 和打印机共享二合一的,所以把闲着的软路由做了改装。顺便使用 Docker 跑一些服务,有老本行的 ES 集群,也有自己写的一些工具类型的服务。

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

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

×