容器化部署OpenVAS(GVM)实战:从零搭建漏洞扫描平台 1. 为什么我建议用容器方案部署 OpenVASGVMOpenVAS 这套漏洞扫描系统在圈内混过几年的人基本都听说过早期叫 OpenVAS后来改名成 Greenbone Vulnerability ManagementGVM现在官方更推荐直接叫 Greenbone 系列。很多人第一次接触它是在 Kali 里敲一句apt install openvas然后开始折腾结果十有八九会碰到依赖冲突、服务起不来、Web 界面打不开、NVT 库版本对不上这类问题。我早期也被这套折磨过后来换成官方推荐的容器方案也就是 Greenbone Community ContainersGCC才算是真正“一次装成、长期省心”。这个容器方案解决的核心痛点是环境一致性。GVM 是一个由多个服务组成的大系统包括 PostgreSQL 数据库、gvmd 管理进程、ospd-openvas 扫描器、gsad Web 服务以及一大套漏洞库 feed 数据。用传统方式安装任何一个组件版本和系统库不匹配整条链路就卡住。容器方案把每个组件封装成独立镜像由官方统一构建和发布依赖关系在镜像内部已经处理好主机上只需要有 Docker 或者 Podman 环境就能把整套系统跑起来。这篇文章适合谁看刚接触漏洞扫描、想在实验室搭建一套自用漏洞管理平台的人被传统安装方式劝退、想换个干净方案的老手以及需要在离线或内网环境评估扫描能力的安全测试人员。接下来我按自己的实际操作过程把 GCC 从环境准备到跑起来扫出第一份报告的完整链路讲一遍里面包含踩过的坑和排查思路照着我这个流程走基本能避开我自己当初走的那些弯路。2. 部署前必须搞懂的架构与选型逻辑2.1 容器方案与传统包安装的对比很多人会纠结一件事既然 Kali 或者 Ubuntu 里有现成的gvm包为什么要绕一圈去用容器我自己的对比经验是传统安装的可控性差在“版本锁”上。系统源里的 gvmd、ospd-openvas 和 feed 数据经常不是同一时间发布的而 GVM 各组件之间有严格的版本匹配关系尤其是 gvmd 与 feed 里的数据格式以及 ospd-openvas 与 gvmd 之间的通信协议版本相差稍大就会出现“连上了但扫不了”的诡异状态。容器方案用镜像 tag 把组件版本固定在一起官方发布 GCC 时已经做过集成测试。举个例子当前发布周期里greenbone/gvmd:stable、greenbone/ospd-openvas:stable、greenbone/gsa:stable这几个镜像的版本是配套的你按官方推荐的组合拉取就不太会撞上版本错位。而且容器天然支持多实例并存我可以在同一台服务器上用不同端口映射跑一套测试版和一套稳定版这在传统安装方式下几乎是灾难。资源占用方面GCC 相比传统方式会额外多一点开销主要是容器运行时和镜像层占用的磁盘但换来的是宿主机的整洁。所有配置文件、日志、数据库都集中在容器数据卷里删掉容器和数据卷之后系统里几乎不留残留文件对于需要频繁重建环境的场景特别友好。2.2 GCC 的整体架构与核心镜像组GCC 不是一个大而全的镜像而是拆分成了几个职责单一的服务镜像docker-compose 把它们编排到一起。理解这层架构后面遇到问题才知道去哪查日志、重启哪个服务。大概分成四块PostgreSQL存放配置、任务、扫描结果等结构化数据是 gvmd 的存储后端。gvmdGreenbone Vulnerability Manager Daemon核心管理服务负责任务调度、结果汇总、与 feed 数据交互。ospd-openvas把 OpenVAS 扫描器封装成 OSP 协议的守护进程gvmd 通过它下发扫描任务、接收扫描结果。GSAGreenbone Security Assistant也就是 gsad提供 Web 界面和 API用户在浏览器里操作的就是它。这四块的启动顺序有讲究PostgreSQL 要先起来并完成初始化gvmd 才能连接数据库gvmd 起来之后ospd-openvas 才能向 gvmd 注册最后 gsad 才能把前端界面对接上。docker-compose 里的depends_on只能保证容器启动顺序不能保证服务内部就绪所以官方方案里通常会配合健康检查或者等待脚本我在实际部署时也遇到过一次 gvmd 启动过快导致连不上数据库的情况后面会讲解决办法。2.3 Docker 还是 Podman官方文档里两种都支持我实际测试下来Docker 的体验更顺滑主要是 Docker Compose 插件的支持最成熟网上能搜到的资料也最多。如果你的环境只能用 Podman注意要用podman-compose或者 Docker Compose 的兼容模式有些网络配置细节会和 Docker 不太一样。我个人的建议是干净环境直接用 Docker Engine Docker Compose Plugin。安装命令各发行版有差异Debian/Ubuntu 系可以直接用 Docker 官方仓库装CentOS/RHEL 系也一样。装完之后记得把当前用户加入docker组否则每次敲命令都要加sudo很影响体验。提示如果之前装过老版本的 gvm 相关包建议先彻底卸载干净否则容器内端口映射到宿主机时可能和残留服务抢端口。我第一次部署时就因为宿主机 5432 端口上还跑着一个旧 PostgreSQL导致容器端口映射失败排查了半天。3. 完整安装实操从拉镜像到 Web 界面可访问3.1 写一份干净可用的 docker-compose.yaml我推荐用 Docker Compose 来管理整套环境原因很简单重启、升级、查看状态都方便而且配置可以固化下来换机器时直接带走。官方仓库提供了 compose 文件但默认配置里有些细节需要根据自己的环境调整。下面是我在 Ubuntu 22.04 上验证过的版本基于官方 compose 做了少量调整主要是固定镜像 tag、增加数据卷生命周期管理。version: 3.9 services: pg-gvm: image: greenbone/pg-gvm:stable volumes: - pg_data:/var/lib/postgresql environment: POSTGRES_PASSWORD: postgres restart: unless-stopped gvmd: image: greenbone/gvmd:stable volumes: - gvmd_data:/var/lib/gvm - pg_data:/var/lib/postgresql environment: POSTGRES_PASSWORD: postgres depends_on: - pg-gvm restart: unless-stopped openvas: image: greenbone/ospd-openvas:stable volumes: - gvmd_data:/var/lib/gvm - openvas_data:/var/lib/openvas - scripts_data:/var/lib/gvm/scripts environment: POSTGRES_PASSWORD: postgres depends_on: - pg-gvm restart: unless-stopped gsa: image: greenbone/gsa:stable ports: - 8080:80 volumes: - gvmd_data:/var/lib/gvm depends_on: - gvmd restart: unless-stopped volumes: pg_data: gvmd_data: openvas_data: scripts_data:这里关键点有几个。端口映射我选了宿主机8080因为80端口经常被系统里的 Nginx 或者其他 Web 服务占掉你如果确定 80 可以用改回80:80也没问题。PostgreSQL 的密码我用的是官方默认的postgres如果你的机器有外网暴露风险强烈建议改成一个强密码同时注意容器环境变量里的密码要和 compose 文件里保持一致。创建好docker-compose.yaml之后在文件所在目录执行docker compose pull这个命令会一次性拉取所有镜像。镜像体积不小尤其是gvmd和ospd-openvas里面包含了漏洞库和扫描器程序建议预留 15GB 以上的磁盘空间。拉取时间取决于网络状况我当时用了大概十几分钟。3.2 启动容器并验证各服务状态镜像拉完先别急着docker compose up -d一把梭我建议分两步走方便定位问题。docker compose up -d pg-gvm sleep 20 docker compose up -d gvmd openvas sleep 30 docker compose up -d gsa为什么分步因为在首次启动时pg-gvm需要初始化 PostgreSQL 数据目录这个过程比较慢如果同时启动gvmd它可能在数据库还没就绪时就尝试连接然后报错退出。虽然restart: unless-stopped会让它自动重启但多次无效重启会拖慢整体启动速度日志里也全是连接失败的记录。启动过程中随时可以用下面的命令观察状态docker compose ps正常情况下四个服务都应该是running状态。如果某个容器反复重启Restarting马上看日志docker compose logs gvmd我第一次部署时gvmd一直报无法连接数据库。原因就是分步启动时pg-gvm还没完全就绪我后来在depends_on之外又加了 healthcheck或者干脆手动等待足够时间问题就解决了。这里分享一个经验不要完全信任depends_on它只保证容器启动了不保证服务就绪了重要服务之间还是要手动确认或者写健康检查。3.3 同步漏洞库 feed这一步最容易翻车容器全部跑起来界面可能已经能打开了但这时候直接建扫描任务你会发现能扫出来结果但是漏洞插件NVT数据可能是空的或者版本号不匹配导致扫描报告没有内容。原因是没有同步 feed。GVM 的 feed 数据分成几类NVTNetwork Vulnerability Tests是扫描器核心插件库CERT 数据是德国计算机紧急响应组发布的漏洞公告GVMD 数据是管理端用于匹配扫描结果和漏洞信息的。这些数据由官方定期更新必须通过greenbone-feed-sync工具来拉取。容器方案的做法是进入gvmd容器执行同步docker compose exec gvmd greenbone-feed-sync这个命令会自动检测当前容器里 gvmd 的版本然后通过网络下载匹配版本的 feed 数据。首次同步的数据量很大NVT 库加 CERT 数据加起来有几个 GB而且下载完成后还要导入数据库整个过程可能长达半小时甚至更久。我在实际同步时中途断过一次网重新执行命令会继续下载官方工具设计得还算健壮。同步完成后可以进入容器确认版本docker compose exec gvmd gvmd --version这个命令会显示 gvmd 版本、数据库 schema 版本以及 feed 的版本号。如果 feed 版本显示为 0 或者时间戳很旧说明同步没成功。注意feed 版本必须和 gvmd 的 schema 版本匹配否则打开 Web 界面时会在 Feed 状态区域看到红色警示。最常见的报错是 “Initializing feed” 卡住或者 “Feed is out of date”。出现这种情况不要慌重新执行一次同步命令或者重启gvmd容器后再同步基本能解决。3.4 首次登录、改密码与基础配置默认情况下Web 界面在浏览器里访问http://你的服务器IP:8080。首次打开是登录页默认账号是admin默认密码是admin。需要注意新版 GCC 在首次启动时可能要求你立即修改密码这是正常的。你也可以用命令行修改密码这样更方便docker compose exec gvmd gvmd --useradmin --new-password你的新密码除了改密码我建议登录之后先做两件事。第一在右上角用户设置里看一遍当前的 feed 状态确认三个类型的 feed 都是绿色或者显示为最新日期第二打开“管理”菜单下的“扫描配置”看一下是否有可用的配置模板比如 “Full and fast”、“Base”、“Discovery” 等如果能看到这些说明 NVT 数据正常加载进来了。这里还有一个很容易被忽略的点新版 gvmd 默认启用了用户角色和权限控制admin 用户可以创建新用户并分配不同角色。如果你是要给团队用建议给每个成员建独立账号不要共用一个 admin否则审计日志里没法区分谁操作了什么这在正式一点的团队协作里是个隐患。3.5 不使用 Compose 的命令行启动方式如果你对 Docker Compose 不太感冒或者觉得日志方式不够直接也可以直接用 Docker 命令行启动。官方文档里给过一套命令但我不推荐日常使用因为参数太多容易记错尤其是数据卷映射那部分一旦写错容器启动后数据就丢了。不过有一种场景必须了解命令行方式单容器调试。比如你想单独启动一个openvas容器看看它输出什么日志或者想手动测试某个镜像是否可以正常启动用docker run比写 compose 文件更直接。我自己在排查ospd-openvas的 socket 通信问题时就单独启动过它逐步排查问题。但如果你是正常部署给业务用还是老老实实用 Compose理由很简单可维护性。半年后你再来看这个环境一条docker compose ps就能看清所有服务的状态一份docker-compose.yaml就能知道当时是怎么配的比翻 shell history 高效得多。4. Web 界面初次使用创建目标并跑通第一个扫描任务4.1 创建扫描目标时的关键设置环境跑起来最重要的事情是用它扫出一个真实结果验证全链路是通的。登录 Web 界面后按照“扫描 - 任务 - 新建任务”的顺序创建一个扫描任务。创建任务之前先要在“配置 - 目标”里添加一个目标。目标设置里有一个字段很关键“排除主机”。如果你扫描的是一个网段比如192.168.1.0/24而你的 Gateways、打印机、网络摄像头这些设备不想被扫可以在这里排除。还有一个“SSH 凭据”选项如果要对目标做 Authenticated Scan认证扫描可以选择配置好的 SSH 或 SMB 凭据这样扫描结果会更深能看到需要登录才能发现的漏洞。我建议第一次调试时目标选一台自己可控的测试虚拟机IP 直接写单个地址不要一上来就扫一个网段。原因是扫描器默认配置是全端口扫描加服务识别网段稍大一点任务运行时间会很长而且大量扫描流量可能触发目标侧的网络监控告警。先用单台机器验证流程跑通之后再扩展范围。4.2 调度配置与扫描时间估算创建任务时有几项参数需要根据自己的场景调整扫描配置第一次建议选Base它只做基础的端口扫描和服务识别速度快确认链路没问题后再用Full and fast这个会调用所有匹配的 NVT 插件更全面但时间数倍增长。目标选择刚才建好的目标。调度如果是定期扫描需求可以创建调度规则。GCC 支持 Cron 风格配置例如每天凌晨 2 点扫描一次。如果没有定期需求选“手动启动”即可。扫描开始后可以在任务列表里看到进度百分比。扫描时间取决于目标数量、端口范围和网络延迟。单台 Linux 主机用Full and fast通常 5 到 20 分钟能跑完Windows 主机如果没有认证凭据可能因为防火墙策略导致部分端口不可达耗时会更长。第一次跑任务时我建议盯一下“扫描状态”日志里面会实时输出当前正在检查的目标地址和端口可以帮助你判断扫描器是否正常工作。4.3 查看扫描报告并定位高危漏洞扫描完成后任务列表里会出现一份报告点击进去可以看到结果概览按危险等级分类严重、高危、中危、低危以及日志信息。报告里每一项漏洞都会显示受影响的主机、端口、漏洞描述、解决方案和参考链接比如 CVE 编号。我自己拿到第一份报告时看到里面有一堆“可能导致信息泄露”的中危漏洞起初很慌后来发现其中很多是扫描器对服务版本的误判或者目标默认返回的 banner 信息被过高评估。这里给新手一个建议不要只看危险等级要结合目标实际业务判断。比如报告提示 SSH 版本较旧但如果这台机器只在内网且无法直接对外访问就可以把风险等级调低或者标记为已接受风险。GCC 的报告还支持导出格式包括 PDF、XML、CSV 等。如果团队里有上级或者客户要求提供扫描报告直接导出 PDF 就很方便。导出的 PDF 上会带上 Greenbone 的标识如果你需要做品牌定制官方企业版本有相关功能社区版没有这个是正常的。5. 常见安装与运行问题排查实录5.1 Feed 版本不匹配或显示过时这是社区版用户遇到最多的问题。现象是 Web 界面顶部的 Feed 状态显示红色或在任务创建时提示 “Feed is out of date” / “Initializing feed”。原因通常是首次安装后没有同步 feed或者 gvmd 容器版本更新后之前同步的数据格式不兼容。我的排查顺序是先在容器内执行gvmd --version查看 gvmd 版本和 feed 版本如果 feed 时间为空或很旧执行greenbone-feed-sync同步完成后重启gvmd容器。如果依然不行可以进入 PostgreSQL 里查表feed_update_status或者meta相关表确认数据库里的 feed 数据是否写入成功。这个步骤对新手有点深但遇到顽固问题时会很有用。5.2 Web 界面打不开或一直转圈打开不了界面先确认容器状态docker compose ps看 gsa 是否在运行。然后确认端口映射是否生效docker compose port gsa 80这个命令会显示宿主机实际映射的地址和端口。如果端口没问题用 curl 从宿主机测试curl -I http://127.0.0.1:8080能得到 HTTP 200 响应说明 gsad 正常问题可能出在浏览器侧或者服务器防火墙。如果 curl 不通查看 gsa 容器日志docker compose logs gsa我遇到过一次 gsa 日志里报 “Failed to connect to gvmd”原因是 gvmd 容器先挂了gsad 自然无法工作。重启整个编排即可docker compose restart5.3 gvmd 反复重启且日志报数据库连接失败这个问题的根因通常是 PostgreSQL 容器还没完全就绪而 gvmd 已经尝试连接。我自己测试时发现即使官方 compose 里写了depends_on也没有真正解决这个问题。解决办法之一是在gvmd启动前增加等待逻辑。用 Compose 的健康检查来实现比较优雅pg-gvm: healthcheck: test: [CMD-SHELL, pg_isready -U postgres] interval: 5s timeout: 3s retries: 10然后在gvmd的depends_on里加上条件gvmd: depends_on: pg-gvm: condition: service_healthy这样 gvmd 会等数据库健康检查通过后才启动彻底解决启动顺序问题。5.4 扫描任务启动后立即失败任务启动即失败最常见的原因是ospd-openvas容器内没有可用 socket 文件或者/var/run/ospd目录权限不对。检查顺序如下docker compose exec openvas ls -la /var/run/ospd docker compose logs openvas正常情况下openvas 容器启动后会启动一个 OSPd 服务并监听/var/run/ospd/ospd.sock。如果这个文件不存在gvmd 没法与扫描器通信任务当然起不来。解决方法是重启 openvas 容器让 OSPd 重新注册。5.5 常见问题速查表现象可能原因处理动作Feed 状态红色首次安装未同步或版本过期执行greenbone-feed-sync后重启 gvmdWeb 界面打不开端口映射错误或 gsa 未就绪docker compose ps检查状态检查防火墙端口gvmd 反复重启数据库未就绪增加 pg-gvm 健康检查并依赖等待扫描任务启动即失败ospd socket 文件缺失重启 openvas 容器检查容器日志扫描结果为空NVT 数据未同步同步 feed 后重新扫描修改 admin 密码失败密码长度或复杂度不足使用至少 8 位、包含字符和数字的密码磁盘空间不足feed 和报告数据累积定期清理旧报告为 Docker 数据卷扩容其实很多问题都是“服务启动顺序”和“feed 同步”这两个最基础的环节出的岔子。把这两个环节搞稳了GCC 的稳定性比我预想的要高很多。6. 一些我实测后的心得与建议6.1 定期更新与备份策略GCC 官方大约每个月会发布一次新版本容器镜像漏洞库 feed 则更新得频繁得多。我建议给服务器设一个计划任务每周执行一次docker compose pull docker compose up -d docker compose exec gvmd greenbone-feed-sync这样能保证漏洞库不会落后太多。更新前先看一眼当前运行的版本和镜像仓库里最新 tag如果跨度太大建议先备份数据卷。备份数据卷最直接的方式是用 Docker 卷的 tar 导出docker run --rm -v gvm_pg_data:/data -v $(pwd):/backup alpine tar czf /backup/pg_data.tar.gz -C /data .这套命令会把pg_data卷打成 tar 包放在当前目录。恢复时解压到对应数据卷即可。数据库是整套系统里最核心的数据报告和配置丢了还能重新扫描历史扫描结果丢了就没法补救了。6.2 资源限制与大规模扫描的调优社区版默认没有对扫描资源做太严格的限制但如果你在一台只有 8GB 内存的机器上运行 GCC同时跑多个扫描任务内存可能吃紧。我测试中观察到单任务扫描时 openvas 进程内存占用大约在 1GB 到 2GB 之间同步 feed 期间会更高。建议生产环境至少 8GB 内存16GB 以上更稳妥。另外扫描调度时要注意并发数。Web 界面创建任务时有“并发扫描数”和“最大同时检查的主机数”之类的高级参数社区版默认可同时执行的任务数量有限制。大规模扫描场景下建议把网段拆成多个 /24 的子网分批扫描既避免目标侧防护策略触发也降低扫描器自身负载。6.3 容器方案的局限性与什么时候该绕开容器方案虽然省心但不是万能的。首先GCC 里的ospd-openvas通常运行在 Linux 容器中对目标做认证扫描时部分 Windows 专属测试可能会受到协议限制效果不如在 Windows 宿主机上直接跑官方企业版。其次容器网络默认走 NAT 模式如果目标位于独立的 VLAN 或者有特殊路由要求可能需要用host网络模式重新配置容器网络这会增加不少复杂度。如果你只需要快速跑一次漏洞扫描GCC 绝对够用。但如果你要做大规模分布式扫描、需要细粒度的 RBAC 权限控制或者要和内部的工单系统做深度集成社区版会有些吃力这时候可能需要评估商业版产品线。从我个人的经验来看GCC 的价值在于它让 GVM 这套功能完整的漏洞管理系统变得“开箱即用”了。对于那些还想再折腾一下的朋友我后来习惯把 gvmd 的数据目录单独挂载到宿主机某个路径比如/srv/gvm-data这样即使容器全部删掉重建数据还能保留。另外给 gsa 配一个 HTTPS 反向代理也很值得做毕竟带着明文密码访问管理界面在稍微正式一点的环境里多少有点说不过去。总之先把这套环境跑起来、扫出第一份报告后面再根据实际需求去调优这个路线是最务实的。