离线环境神器:手把手用Docker打包全套Prometheus监控工具链

最近在帮一家金融机构做内部系统监控的迁移,他们的生产环境完全隔离,连个外网出口都没有。传统的在线安装、拉取镜像那一套根本行不通。折腾了几轮,总算把Prometheus、Grafana、各种Exporter这一大家子,用Docker完整地“搬”进了内网。这个过程里踩了不少坑,也总结出一套比较顺滑的离线部署方法论。今天就来聊聊,怎么在完全没有互联网连接的环境下,从零开始搭建一套企业级的监控系统。这不仅仅是把镜像拷进去那么简单,更涉及到依赖梳理、版本管理、私有仓库搭建和后续维护的完整链条。如果你也面临类似的内网、安全合规或者网络受限场景,希望这篇实战笔记能给你一些直接的参考。

1. 为什么离线部署是企业的刚需?

在很多人的印象里,离线部署似乎是个“古老”或“边缘”的需求。但真实的企业场景,尤其是金融、能源、军工、高端制造以及部分对数据主权有严格要求的行业,离线或强隔离网络是标准配置,甚至是合规的强制要求。在这些环境里,随意连接外网下载软件包或Docker镜像,是绝对不被允许的安全红线。

离线部署的核心价值,远不止“没网也能装”这么简单:

  • 安全与合规:这是首要驱动力。企业需要确保所有引入的软件组件都经过内部安全团队的漏洞扫描和代码审计。直接从互联网拉取未经审查的镜像,无异于在防火墙上开了一个不可控的口子。
  • 环境稳定性与一致性:在线安装受网络波动、镜像仓库服务可用性影响。离线部署意味着你将所有依赖固化在某个介质(如内部服务器、移动硬盘)上,确保了在任何时间、任何地点(如灾备中心)重建环境时,组件版本完全一致,避免了因版本漂移带来的意外问题。
  • 部署效率与可控性:在跨地域、多数据中心的场景下,通过内部网络分发一个打包好的离线包,速度远快于每个节点各自从外网缓慢拉取。同时,版本升级也完全由内部流程控制,可以规划统一的变更窗口。

所以,我们今天的任务,就是把一个典型的云原生监控栈——以Prometheus为核心,配合Node Exporter、cAdvisor、MySQL Exporter等数据采集器,以及Grafana可视化工具——完整地“离线化”。这不仅仅是执行几条docker savedocker load命令,而是一个系统工程。

提示:在开始动手前,强烈建议准备一台可以连接互联网的“构建机”(Jump Server/Build Machine),以及至少一台代表目标环境的“离线机”。所有在线操作都在构建机完成,最终产出是一个完整的离线部署包。

2. 构建离线包:从梳理依赖到批量导出

第一步,也是最容易出错的环节,就是搞清楚我们需要哪些镜像,以及它们之间的版本兼容关系。盲目地拉取最新版(latest tag)往往是灾难的开始。

2.1 规划你的监控栈与版本矩阵

一个基础的监控工具链通常包括以下组件,我建议在构建之初就确定好每个组件的具体版本号。

组件官方镜像名称(示例)推荐版本(示例)核心作用
Prometheus Serverprom/prometheusv2.45.0监控主服务器,负责拉取、存储时序数据。
Grafanagrafana/grafana9.5.2数据可视化与仪表盘平台。
Node Exporterprom/node-exporterv1.6.0采集主机硬件和操作系统指标。
cAdvisorgcr.io/cadvisor/cadvisorv0.47.0采集容器资源使用和性能指标。
MySQL Exporterprom/mysqld-exporterv0.14.0采集MySQL数据库性能指标。
Alertmanager (可选)prom/alertmanagerv0.25.0处理告警通知,去重、分组、路由。

为什么强调版本?因为Grafana的某些仪表盘模板可能只兼容特定版本的Exporter数据格式,Prometheus的配置文件语法也可能随版本升级有细微变化。锁定版本是保证离线环境可重复部署的基础。

在构建机上,使用docker pull命令拉取所有确定的镜像:

# 请将版本号替换为你实际确定的版本
docker pull prom/prometheus:v2.45.0
docker pull grafana/grafana:9.5.2
docker pull prom/node-exporter:v1.6.0
docker pull gcr.io/cadvisor/cadvisor:v0.47.0
docker pull prom/mysqld-exporter:v0.14.0
docker pull prom/alertmanager:v0.25.0

拉取完成后,使用docker images命令确认所有镜像都已就绪。

2.2 高级技巧:使用脚本批量导出与生成清单

手动一个个docker save既容易遗漏,也容易出错。这里分享一个我常用的Shell脚本,它能自动导出所有相关镜像,并生成一份详细的清单文件,这对于后续的维护和审计至关重要。

将以下脚本保存为 export_offline_images.sh

#!/bin/bash
# 定义需要导出的镜像列表,以空格分隔
IMAGE_LIST="prom/prometheus:v2.45.0 grafana/grafana:9.5.2 prom/node-exporter:v1.6.0 gcr.io/cadvisor/cadvisor:v0.47.0 prom/mysqld-exporter:v0.14.0 prom/alertmanager:v0.25.0"
# 定义输出目录
OUTPUT_DIR="./offline_prometheus_package_$(date +%Y%m%d)"
IMAGE_TAR_DIR="${OUTPUT_DIR}/images"
MANIFEST_FILE="${OUTPUT_DIR}/image_manifest.txt"

# 创建目录
mkdir -p ${IMAGE_TAR_DIR}

echo "开始导出镜像到目录: ${OUTPUT_DIR}"
echo "========================================" > ${MANIFEST_FILE}
echo "镜像导出清单 - 生成时间: $(date)" >> ${MANIFEST_FILE}
echo "========================================" >> ${MANIFEST_FILE}

for IMAGE in ${IMAGE_LIST}; do
    # 处理镜像名,将`/`和`:`替换为`_`作为文件名
    FILENAME=$(echo ${IMAGE} | sed 's[/[_]g' | sed 's[:][_]g').tar
    TAR_PATH="${IMAGE_TAR_DIR}/${FILENAME}"

    echo "正在导出: ${IMAGE} -> ${FILENAME}"
    docker save -o ${TAR_PATH} ${IMAGE}

    # 获取镜像的完整Digest (唯一ID),写入清单
    IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ${IMAGE} 2>/dev/null || echo "Digest Not Available")
    echo "镜像: ${IMAGE}" >> ${MANIFEST_FILE}
    echo "文件: ${FILENAME}" >> ${MANIFEST_FILE}
    echo "摘要(Digest): ${IMAGE_DIGEST}" >> ${MANIFEST_FILE}
    echo "导出时间: $(date)" >> ${MANIFEST_FILE}
    echo "---" >> ${MANIFEST_FILE}
done

# 将使用的脚本本身和可能用到的配置文件也打包(示例)
cp prometheus.yml ${OUTPUT_DIR}/ 2>/dev/null || echo "prometheus.yml 未找到,跳过。"
cp docker-compose-offline.yml ${OUTPUT_DIR}/ 2>/dev/null || echo "docker-compose文件未找到,跳过。"

echo "导出完成!"
echo "所有镜像已保存至: ${IMAGE_TAR_DIR}"
echo "详细清单请查看: ${MANIFEST_FILE}"

给脚本添加执行权限并运行:chmod +x export_offline_images.sh && ./export_offline_images.sh。执行后,你会得到一个以日期命名的文件夹(如offline_prometheus_package_20231027),里面包含了所有镜像的.tar文件和一个记录版本、摘要的文本清单。这个文件夹就是你的核心离线包

3. 传输与加载:在离线环境“解压”镜像

得到离线包后,通过安全的内部方式(如内部文件服务器、加密移动硬盘)将其传输到目标离线服务器。

3.1 单镜像加载与批量加载

在离线服务器上,首先需要安装好Docker引擎(同样需要通过离线安装包安装,此处不赘述)。然后进入存放镜像tar包的目录。

方法一:手动逐个加载 适用于镜像数量少或需要选择性加载的情况。

cd /path/to/offline_package/images
docker load -i prom_prometheus_v2.45.0.tar
docker load -i grafana_grafana_9.5.2.tar
# ... 依次加载其他镜像

方法二:使用循环批量加载 更高效,使用我们之前生成的清单目录。

cd /path/to/offline_package/images
for tar_file in *.tar; do
    echo "正在加载镜像文件: $tar_file"
    docker load -i "$tar_file"
done

加载完成后,运行 docker images,应该能看到所有导入的镜像。

3.2 配置文件的离线准备

镜像只是载体,配置才是灵魂。在离线环境下,你需要预先准备好所有组件的配置文件。最重要的就是Prometheus的 prometheus.yml。这个文件需要根据你离线环境的实际网络规划来编写,关键点在于targets中的IP地址必须使用离线环境内容器或主机的真实可达地址,不能照搬在线环境的例子。

一个适配我们上述组件、使用Docker Compose部署的 prometheus.yml 配置示例如下:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100'] # 使用Docker Compose服务名

  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

  - job_name: 'mysqld-exporter'
    static_configs:
      - targets: ['mysqld-exporter:9104']

  - job_name: 'alertmanager'
    static_configs:
      - targets: ['alertmanager:9093']

注意,这里我使用了服务名(如node-exporter)而非IP,这依赖于Docker Compose或自定义网络提供的DNS解析能力,是更优雅、更易维护的方式。你需要将这个配置文件也放入离线包,一并传输到离线服务器。

4. 超越简单加载:搭建私有镜像仓库实现长效管理

如果只是偶尔部署一次,docker load 勉强够用。但一旦面临多台服务器部署、版本更新、团队共享镜像的场景,维护一堆散落的.tar文件就变成了噩梦。搭建一个内网私有镜像仓库,是将离线部署从“手工活”升级为“工程化”的关键一步。

私有仓库(如Harbor、Docker Registry)就像一个内网的Docker Hub,你的离线机可以从这个内网仓库拉取镜像,享受和在线环境几乎相同的体验。

4.1 快速部署一个简易私有Registry

在离线环境中的某台服务器(可以作为仓库服务器)上,你可以用Docker快速启动一个官方的Registry v2容器。这虽然功能简单,但足以满足基本的镜像推送和拉取需求。

# 在仓库服务器上运行
mkdir -p /data/registry
docker run -d \
  --name private-registry \
  --restart=always \
  -p 5000:5000 \
  -v /data/registry:/var/lib/registry \
  registry:2

现在,一个私有仓库就在 http://<仓库服务器IP>:5000 运行了。

4.2 向私有仓库推送离线镜像

回到我们那台可以连接互联网的构建机,在导出镜像后,我们多做一个步骤:将其推送到这个新建的私有仓库。但首先,需要给镜像打上私有仓库的标签。

假设我们的私有仓库地址是 192.168.100.100:5000

# 1. 为每个镜像打上私有仓库标签
docker tag prom/prometheus:v2.45.0 192.168.100.100:5000/monitoring/prometheus:v2.45.0
docker tag grafana/grafana:9.5.2 192.168.100.100:5000/monitoring/grafana:9.5.2
# ... 为其他镜像执行类似操作

# 2. 由于Docker默认不允许非HTTPS仓库,需要修改构建机的Docker守护进程配置(临时方案,生产环境建议配置HTTPS)
# 编辑 /etc/docker/daemon.json,添加 insecure-registries 配置
{
  "insecure-registries": ["192.168.100.100:5000"]
}
# 然后重启docker服务: sudo systemctl restart docker

# 3. 推送镜像到私有仓库
docker push 192.168.100.100:5000/monitoring/prometheus:v2.45.0
docker push 192.168.100.100:5000/monitoring/grafana:9.5.2
# ... 推送其他镜像

4.3 离线环境从私有仓库部署

现在,在离线环境中的任何一台服务器上,只要它能访问 192.168.100.100:5000,并且在其Docker配置中同样添加了 insecure-registries,就可以像从Docker Hub拉取镜像一样,从私有仓库拉取。

# 在离线应用服务器上
docker pull 192.168.100.100:5000/monitoring/prometheus:v2.45.0
docker pull 192.168.100.100:5000/monitoring/grafana:9.5.2

之后,使用Docker Compose部署时,只需将image字段指向私有仓库地址即可。这种方式彻底摆脱了对物理介质传输的依赖,实现了内网环境下的标准化镜像分发。

5. 实战部署:使用Docker Compose编排离线监控栈

将所有组件用Docker Compose编排起来,是管理复杂多容器应用的最佳实践。它用一个声明式的YAML文件定义了所有服务、网络、卷的关系,一键启停,清晰明了。

以下是一个整合了私有仓库镜像和自定义配置的 docker-compose.yml 示例,将其放在离线应用服务器上,与 prometheus.yml 同目录:

version: '3.8'

services:
  prometheus:
    image: 192.168.100.100:5000/monitoring/prometheus:v2.45.0
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.retention.time=30d'
    networks:
      - monitoring

  grafana:
    image: 192.168.100.100:5000/monitoring/grafana:9.5.2
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123 # 首次登录后请务必修改!
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - monitoring

  node-exporter:
    image: 192.168.100.100:5000/monitoring/node-exporter:v1.6.0
    container_name: node-exporter
    restart: unless-stopped
    command:
      - '--path.procfs=/host/proc'
      - '--path.rootfs=/rootfs'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    ports:
      - "9100:9100"
    networks:
      - monitoring

  cadvisor:
    image: 192.168.100.100:5000/monitoring/cadvisor:v0.47.0
    container_name: cadvisor
    restart: unless-stopped
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker:/var/lib/docker:ro
      - /dev/disk/:/dev/disk:ro
    ports:
      - "8080:8080"
    networks:
      - monitoring

  mysqld-exporter:
    image: 192.168.100.100:5000/monitoring/mysqld-exporter:v0.14.0
    container_name: mysqld-exporter
    restart: unless-stopped
    environment:
      - DATA_SOURCE_NAME=exporter_user:password@(mysql-host:3306)/ # 替换为真实MySQL连接信息
    ports:
      - "9104:9104"
    networks:
      - monitoring

networks:
  monitoring:
    driver: bridge

volumes:
  prometheus_data:
  grafana_data:

在包含 docker-compose.yml 的目录下,执行 docker-compose up -d,所有服务就会在后台启动。通过 docker-compose ps 查看状态,docker-compose logs -f [服务名] 查看日志。

至此,一个完全离线、自主可控的Prometheus监控工具链就已经部署完成。你可以访问 http://<服务器IP>:3000 登录Grafana,添加数据源(地址为 http://prometheus:9090,注意这里用的是Compose服务名),然后导入对应的仪表盘模板ID(如8919、7362),开始你的监控之旅。

整个流程走下来,最初的“离线”限制,反而促使我们建立了一套更规范、更可控的软件分发与部署流程。从依赖管理、镜像归档、私有仓库搭建到编排部署,每一步都加深了对系统本身的理解。下次再遇到类似的环境,你手里握着的就不再是几个零散的镜像文件,而是一套成熟的方法论和可复用的工具链。

更多推荐