想象一下你在一家电商公司工作,每天需要处理上百万商品页面的截图生成、PDF导出和自动化测试。如果每张截图都要手动打开浏览器操作,那估计整个技术团队都得加班到天亮。这就是为什么我们需要Headless Chrome——没有图形界面的浏览器,能通过命令行批量执行网页操作。
但单机部署的Headless Chrome很快就会遇到瓶颈。我去年做过一个项目,单台服务器跑Chrome实例超过20个就开始频繁崩溃,内存直接飙到90%以上。这时候就需要用Docker容器化封装单个实例,再通过Kubernetes集群实现动态扩容。实测下来,同样的硬件资源,K8s集群方案能稳定支撑200+并发请求。
官方browserless/chrome镜像开箱即用,但默认配置有几个大坑:
这是我优化后的Dockerfile:
dockerfile复制FROM browserless/chrome:latest
RUN apt-get update && apt-get install -y \
fonts-wqy-microhei \
libxss1 \
--no-install-recommends
ENV DISABLE_GPU=false \
CHROME_PATH=/usr/bin/google-chrome
在docker run时务必加上这些参数:
bash复制docker run -d \
-e MAX_CONCURRENT_SESSIONS=10 \ # 单容器最大会话数
-e MAX_QUEUE_LENGTH=50 \ # 队列积压预警
-e PREBOOT_CHROME=true \ # 预启动加速
-p 3000:3000 \
my-chrome-image
注意:
MAX_CONCURRENT_SESSIONS不要超过主机内存/1GB,否则会频繁OOM
这是经过生产验证的配置模板:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: chrome-cluster
spec:
replicas: 3
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 确保零停机更新
template:
spec:
affinity:
podAntiAffinity: # 分散部署到不同节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: chrome
topologyKey: kubernetes.io/hostname
containers:
- name: chrome
resources:
limits:
cpu: "2"
memory: "2Gi"
requests:
cpu: "0.5"
memory: "1Gi"
lifecycle:
preStop: # 优雅终止
exec:
command: ["/bin/sh", "-c", "pkill chrome"]
结合HPA和自定义指标实现智能扩缩:
bash复制# 安装metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 创建HPA规则
kubectl autoscale deployment chrome-cluster \
--cpu-percent=60 \
--min=3 \
--max=20
我在实际使用中发现,单纯依赖CPU指标不够准确,最好配合Prometheus采集Chrome实例的队列深度指标。
直接上干货——这是我封装的连接池工具类:
java复制public class ChromePool {
private static final int MAX_POOL_SIZE = 50;
private static final Duration TIMEOUT = Duration.ofSeconds(30);
private static final GenericObjectPool<Browser> pool;
static {
PooledObjectFactory<Browser> factory = new BasePooledObjectFactory<>() {
@Override
public Browser create() {
return Puppeteer.connect(
new ConnectOptions(),
"ws://chrome-service:3000"
);
}
};
pool = new GenericObjectPool<>(factory);
pool.setMaxTotal(MAX_POOL_SIZE);
pool.setBlockWhenExhausted(true);
pool.setMaxWaitMillis(TIMEOUT.toMillis());
}
public static Page borrowPage() {
try {
Browser browser = pool.borrowObject();
return browser.newPage();
} catch (Exception e) {
throw new RuntimeException("获取Chrome实例失败", e);
}
}
}
当集群节点宕机时,需要自动重试其他节点。这里分享一个WS连接的重试策略:
java复制public Browser connectWithRetry(String[] endpoints, int retries) {
for (int i = 0; i < retries; i++) {
for (String endpoint : endpoints) {
try {
return Puppeteer.connect(options, endpoint);
} catch (IOException e) {
log.warn("连接{}失败: {}", endpoint, e.getMessage());
}
}
Thread.sleep(1000 * (i + 1)); // 指数退避
}
throw new RuntimeException("所有节点均不可用");
}
这些Grafana面板是必须配置的:
Chrome常驻内存容易泄漏,我的排查三板斧:
bash复制kubectl exec chrome-pod -- kill -SIGUSR1 $(pidof chrome)
yaml复制env:
- name: DISABLE_FONTS
value: "true"
- name: BLOCK_ADS
value: "true"
去年双十一大促期间,我们的集群突然出现大规模超时。后来发现是因为:
现在的解决方案是:
yaml复制env:
- name: SINGLE_PROCESS
value: "true" # 每个tab独立进程
- name: TIMEOUT
value: "30000" # 30秒超时强制终止