一台VPS自托管日志管理:journald、Loki与OpenSearch
一台VPS上先用journald和logrotate,何时升级到Loki或OpenSearch?本文比较最低内存、全文搜索限制、日志保留规则及重启后丢失日志的配置陷阱。
在一台 VPS 上自托管日志管理的实际成本
在一台 VPS(虚拟专用服务器)上自托管日志管理,归根结底只有一个问题:您需要搜索集群,还是只需要日志轮转和 grep?大多数厂商指南会在发送第一行日志之前,就从 3 个节点和 12 GB 内存开始规划。对于单台服务器,这种方案没有参考价值。下面的比较依据是:每种方案在存储任何日志之前,对小型服务器的最低要求是什么。
如果您只运行 1 台或 2 台服务器,并且只想知道上周二发生了什么,systemd-journald 和 logrotate 已经可以完成这项工作。您可以阅读下一节后停止配置。如果必须将多台机器的日志集中到一个位置,并支持按周进行搜索,Grafana Loki 适合小型服务器,因为它索引的是标签,而不是日志行中的文本。Elasticsearch 和 OpenSearch 提供真正的全文搜索,但会消耗更多内存,因为 JVM(Java 虚拟机)堆有一个无法继续降低的最低容量。
从 journald 开始,因为大多数人到这里就够了
在任何当前版本的 Ubuntu 或 Debian 服务器上,systemd-journald 都已在运行。它会捕获每个服务单元的标准输出、内核消息,以及发送到 syslog 的所有内容。4 条命令可以覆盖大多数故障。
journalctl -u nginx.service --since "2026-08-14 09:00" --until "2026-08-14 10:00"
journalctl -p err -b
journalctl -f -u ssh.service
journalctl --disk-usage最后一条命令会输出类似 Archived and active journals take up 1.1G in the file system. 的一行。这就是决定是否还需要执行其他操作的数值。如果显示几百 MB,并且可以通过 -u 和 --since 找到所需内容,就不需要再做其他处理。
日志是否在重启后保留,取决于 Storage=,以及 /var/log/journal 是否存在。在常见的 Storage=auto 设置下,如果该目录存在,journald 会写入 /var/log/journal;如果不存在,则写入 /run/log/journal。/run 位于内存中,因此没有该目录的服务器会在重启时清除所有日志,而这正是您需要读取日志的时刻。Ubuntu 镜像会提供该目录。精简镜像和基于容器的镜像通常不会提供。
ls -d /var/log/journal
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald
journalctl --disk-usage重启后,journalctl --disk-usage 应报告低于 /var/log/journal 的大小,而不是 /run。默认值本身已经有限制,这是 journald 能作为可靠方案而不是备用方案的主要原因。journald.conf 手册页将 SystemMaxUse= 设置为文件系统大小的 10%,将 SystemKeepFree= 设置为 15%,并将每个计算出的默认值限制为 4G。SystemMaxFileSize= 默认为 SystemMaxUse= 的八分之一,并限制为 128M,因此通常会保留 7 个轮换文件。MaxRetentionSec= 默认为 0,这会关闭基于时间的删除。请再看一遍最后一个默认值:开箱即用时,日志只受大小限制,不受时间限制。
[Journal]
Storage=persistent
SystemMaxUse=2G
MaxRetentionSec=30day将其写入 /etc/systemd/journald.conf.d/99-size.conf,重启 journald,然后检查 journalctl --disk-usage 是否接近新的上限。要立即回收空间,而不是等待下一次轮换,请运行 sudo journalctl --vacuum-size=500M 或 sudo journalctl --vacuum-time=14d。这两条命令都会输出所删除的每个文件,因此命令无输出表示没有可删除的内容。
日志之外的所有内容,例如 /var/log/nginx/access.log,都由 logrotate 负责,并通过 systemd timer 每天运行。有一个故障值得了解,因为它看起来像是 df 中的错误。轮换后,旧文件会从目录列表中消失,但 daemon 仍保持该文件打开,因此 df -h 会报告磁盘已满,而 du -sh /var/log 报告的空间占用要小得多。只有进程重新打开日志后,空间才会释放;配置中的 postrotate reload 行就是为此准备的。sudo lsof -nP +L1 会列出已删除但仍保持打开状态的文件,并显示持有每个文件的进程。使用 sudo logrotate -d /etc/logrotate.d/nginx 可以在不执行任何更改的情况下测试规则。
将多台服务器的日志发送到一个收集器
当服务器不止一台时,将日志集中到一个位置可以简化同时管理多台 Linux 服务器。大多数发行版已经安装了 rsyslog,因此成本最低的集中式收集方式,是在每个发送端配置一个文件。
*.* action(type="omfwd" target="logs.example.com" port="514" protocol="tcp")将其保存为 /etc/rsyslog.d/50-forward.conf,使用 sudo rsyslogd -N1 检查。该命令会验证配置,但不会启动任何服务。然后重启 rsyslog。在收集器上启用 TCP 输入。
module(load="imtcp")
input(type="imtcp" port="514")需要注意两点,都是机制本身导致的问题。普通 syslog 不提供加密或身份验证,因此任何能够访问 514 端口的主机,都可以注入看起来与您的日志完全相同的日志行。应将其绑定到专用网络或 VPN,并在防火墙中限制该端口。其次,默认操作队列位于内存中。收集器无法访问时,队列会被填满,随后消息会被丢弃,且不会保留副本。rsyslog 在其可靠转发教程中介绍了适用于此场景的磁盘辅助队列。
为什么 ELK 堆栈不适合小型 VPS
ELK 包括用于存储和搜索的 Elasticsearch、用于日志采集管道的 Logstash,以及用于提供界面的 Kibana。最低内存需求来自 JVM 堆,而且在任何日志到达之前就已经确定。
Elastic 的文档要求将堆设置为每个 Elasticsearch 节点可用总内存的 50% 或更少,因为该进程还会使用堆外缓冲区,并依赖操作系统文件缓存快速读取索引文件。因此,2 GB 堆意味着至少需要一台 4 GB 的机器,还没有计算 Kibana,也没有计算服务器实际要运行的其他服务。Elastic 还说明,Elasticsearch 会根据节点角色和总内存自动设置堆大小。这意味着小型机器只能获得较小的堆,然后长期消耗资源进行垃圾回收。
Logstash 才是会直接打破小型 VPS 预算的组件。Elastic 自己的 JVM 设置页面建议,典型日志采集场景使用不低于 4GB 且不高于 8GB 的堆。这已经占满一台 4 GB VPS 的全部内存,而这还只是管道中间的一个进程。
The data behind this chart
[
{
"label": "Loki plus Alloy (no JVM)",
"documented_heap_mb": 0
},
{
"label": "OpenSearch demo compose",
"documented_heap_mb": 512
},
{
"label": "OpenSearch production example",
"documented_heap_mb": 2048
},
{
"label": "Logstash recommended minimum",
"documented_heap_mb": 4096
}
]这些数值来自各个项目自己的文档。它们不是在测试机器上测得的结果,实际工作负载会改变这些数值。OpenSearch 的示例 compose 文件为演示环境中的每个节点设置 512 MB,并在生产示例中设置 2048 MB;Logstash 建议的下限是 4096 MB。Loki 和 Alloy 的堆列显示为 0,因为它们是 Go 程序,不需要预留 JVM 堆。一个数字就说明了全部差异:JVM 组件无论是否有日志到达,都会预留这部分内存。
如果您仍想在一台小型服务器上运行 Elastic 堆栈,可以移除 Logstash,改用轻量级采集器直接将日志发送到 Elasticsearch。Logstash 用于大规模解析和转换日志;在单台服务器上,您可以将这些工作放到边缘节点执行,也可以跳过。
Elasticsearch 和 OpenSearch 还需要将 vm.max_map_count 提高到 262144,因为它们会使用内存映射读取索引文件,而 Linux 的默认限制过低。全新服务器上的容器启动几秒后退出,通常就是这个限制导致的。
OpenSearch 还是 Elasticsearch:您可以部署哪一个?
先看简短的许可证历史,因为这决定您获准运行什么。2021 年 1 月,Elastic 将 Elasticsearch 和 Kibana 从 Apache 2.0 切换为 SSPL(服务器端公共许可证)和 Elastic License 2.0 双许可证模式。AWS 将最后一版 Apache 2.0 代码分叉为 OpenSearch,后者继续采用 Apache 2.0。2024 年 9 月,Elastic 又加入 AGPLv3(GNU Affero 通用公共许可证第 3 版),作为免费源代码的另一种许可证选项。对于个人在一台 VPS 上自托管的场景,这些许可证都允许您这样使用。只有当您向其他人提供托管服务时,许可证限制才会产生实际影响。
在小型服务器上,两者的实际差异没有许可证历史看起来那么大,因为它们底层使用的是同一类引擎。名称有所不同:OpenSearch 将索引生命周期称为 ISM(索引状态管理),Elasticsearch 将其称为 ILM(索引生命周期管理)。截至 2026 年 8 月,OpenSearch 2.12 及更高版本在首次运行时未设置管理员密码就会拒绝启动。
sudo sysctl -w vm.max_map_count=262144
printf 'vm.max_map_count = 262144\n' | sudo tee /etc/sysctl.d/99-opensearch.conf
docker run -d -p 9200:9200 -p 9600:9600 -e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=<custom-admin-password>" \
opensearchproject/opensearch:latestsysctl -w 所在的行会立即应用设置,/etc/sysctl.d/ 中的文件则负责保存设置,使其在重启后仍然生效。使用 curl -k -u admin:<password> https://localhost:9200 确认容器已启动。它会使用演示证书通过 https 响应,因此 -k 会跳过证书验证;正常响应是一个简短的 JSON 块,其中包含集群名称和版本。OpenSearch 安装页面还提示 Docker Desktop 用户至少为主机分配 4 GB 内存。这也能大致说明该进程所需的资源量。
Loki 如何保持轻量:使用标签而不是完整的文本索引
Loki 只为标签维护一个索引,并将日志行存储为压缩块。查询会先选择日志流,再过滤文本。{unit="ssh.service"} |= "Failed password" 根据标签选择日志流,然后在这些数据块中扫描指定字符串。日志行正文不会建立索引,因此日志写入成本较低,也无需在内存中维护倒排索引。成本转移到了查询时,这种取舍适用于通常明确要查看哪个服务的场景。
Grafana 文档将单体模式(即 Loki 的所有组件在一个进程中运行,并使用 -target=all)的适用范围定为每天约 20GB 以内的小型读写量。单台 VPS 的规模远低于这一范围。
需要注意的是标签基数。每种不同的标签值组合都对应一个日志流,日志流数量会影响 Loki 的内存和索引大小。存储客户端 IP 地址或请求标识符的标签会为每个值创建一个日志流,因此繁忙的 Web 服务器一天内可能产生数万个日志流,进程会持续增长,直到内核将其终止。标签应只包含可以手工统计的值:unit、host、job、level。将可变的详细信息放在日志行本身中,在查询时通过过滤表达式查找这些内容。
在一台 VPS 上安装 Loki 和 Alloy
两个进程负责完成这项工作。Loki 存储日志并处理查询。Grafana Alloy 读取日志并将其推送到 Loki。过去负责发送日志的是 Promtail,但它已于 2 March 2026 结束生命周期,因此新安装应使用 Alloy;Loki 自带的 Docker 示例现在也提供 Alloy 配置。
wget https://raw.githubusercontent.com/grafana/loki/v3.7.0/cmd/loki/loki-local-config.yaml -O loki-config.yaml使用前先阅读该文件。它将 path_prefix: /tmp/loki 设置为使用 /tmp/loki/chunks 下的 chunks。这适用于演示环境,但不适用于服务器:容器的 /tmp 下没有任何内容会在容器重新创建后保留,因此下一次更新镜像时,历史数据会消失。请将其指向一个已挂载的路径。
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemorydocker volume create loki-data
docker run --name loki -d \
-v $(pwd):/mnt/config -v loki-data:/loki \
-p 127.0.0.1:3100:3100 \
grafana/loki:3.7.0 -config.file=/mnt/config/loki-config.yaml
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3100/ready最后一条命令应输出 200,因为 Loki 准备好接受流量后,/ready 会返回 HTTP 200。其他结果表示进程仍在启动,或配置被拒绝;docker logs loki 会说明具体原因。运行命令中的两个细节是有意这样设置的。端口只发布到 127.0.0.1,因为示例配置包含 auth_enabled: false,而 Loki 自身不提供用户身份验证。因此,任何能够访问 3100 端口的对象都可以读取所有日志,也可以写入伪造日志。请将其保持在回环地址上,或放在 VPN 或需要身份验证的反向代理之后。命名卷也很重要,因为该镜像以用户 loki(UID 10001)运行,因此由 root 所有的主机目录在 bind mount 后无法由容器写入。
sudo apt-get update && sudo apt-get install -y gpg wget
sudo mkdir -p /etc/apt/keyrings/
sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" \
| sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install -y alloyAlloy 读取 /etc/alloy/config.alloy。该配置读取系统 journal 和一组文件,并将两者推送到本地 Loki。
loki.write "local" {
endpoint {
url = "http://127.0.0.1:3100/loki/api/v1/push"
}
}
loki.relabel "journal" {
forward_to = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
}
loki.source.journal "read" {
forward_to = [loki.write.local.receiver]
relabel_rules = loki.relabel.journal.rules
labels = {job = "systemd-journal", host = "app-01"}
}
local.file_match "nginx" {
path_targets = [{"__path__" = "/var/log/nginx/*.log", "job" = "nginx", "host" = "app-01"}]
}
loki.source.file "nginx" {
targets = local.file_match.nginx.targets
forward_to = [loki.write.local.receiver]
}该 relabel 规则将 journal 字段 __journal__systemd_unit 复制到名为 unit 的 label 中,这正是后续 {unit="ssh.service"} 能够正常工作的原因。没有这条规则,单元名称会位于日志条目内部,而不是 label 中,因此无法按该名称筛选,每次查询都必须扫描所有内容。
sudo systemctl reload alloy
systemctl show -p User alloy
sudo journalctl -n 5
sudo -u alloy journalctl -n 5大多数配置都会在这里卡住。Alloy 以自己的服务账户运行,而不是以 root 运行。读取系统 journal 需要加入 systemd-journal 组;在 Debian 和 Ubuntu 上,/var/log/nginx 下的文件属于 adm 组。将最后一条命令中 systemctl show 输出的账户替换进去。如果该命令返回的条目远少于以 root 运行时的结果,说明该账户无法读取系统 journal。无论配置多么正确,Loki 都会保持为空。使用 sudo usermod -aG systemd-journal,adm alloy 添加这些组,然后使用 sudo systemctl restart alloy 重启。
curl -G -s "http://127.0.0.1:3100/loki/api/v1/query_range" \
--data-urlencode 'query={job="systemd-journal"}' \
--data-urlencode 'limit=5' | jq '.data.result | length'大于 0 的数值表示存在带有该 label 的 streams,并且其中包含日志条目。0 表示该 label 下尚未收到任何内容。一个默认设置经常会造成误报:loki.source.journal 将 max_age 设置为 7h,因此全新启动时只读取 journal 最近七小时的内容,不会读取更早的内容。若需要图形界面,请在同一台服务器上运行 Grafana,并将 Loki 数据源指向 http://127.0.0.1:3100.。容器日志需要使用不同的数据源:Alloy 会发现正在运行的 Docker 容器并读取其日志,这正是 Loki 自带入门示例的做法;在 VPS 上的单节点 k3s 集群中,该任务则会改为读取 kubelet 写入的 pod 日志目录。
保留期:确定日志何时删除
几乎没有人会在磁盘空间耗尽之前设置保留期。等到磁盘已满时,通常只能在凌晨 3 点、服务停机的情况下设置。应在第一天就根据两个问题确定保留期:实际需要查看多早以前的日志,以及下个月进行事件复盘时必须保留哪些日志。对于单台服务器,14 到 30 天通常可以满足这两个要求。
Loki 在启用 compactor 之前不会删除任何内容。默认情况下不会启用保留策略。很多人发现卷空间已耗尽时,retention_period 仍在配置中,但并未生效。
limits_config:
retention_period: 744h
compactor:
working_directory: /loki/retention
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
delete_request_store: filesystem744h 等于 31 天。该配置块受以下 4 条规则约束:
- 保留策略由 compactor 执行,Grafana 文档建议以单实例运行 compactor。在单台 VPS 上无需额外配置即可满足这一点。
- 最短保留期为 24h,并且只有在索引周期为 24h 时保留策略才会生效。示例中的
schema_config已使用period: 24h,保持不变即可。 - 当
retention_enabled为 true 时,必须设置delete_request_store。它指定存储删除请求的存储位置,因此对于基于文件系统的单节点部署,应与架构中已有的object_store: filesystem保持一致。 - Loki 会先标记数据块,经过
retention_delete_delay后才删除;这里的值为 2h,因此释放磁盘空间的时间会晚于策略规定的时间。重新加载配置 5 分钟后,不要通过df判断设置是否生效。
OpenSearch 删除的是整个索引,而不是单条日志,因此日志索引按天创建。ISM policy 会使索引依次经过不同状态,并在索引达到指定存续时间后将其删除;ism_template 会将该策略附加到新建索引,这样无需手动记忆。
14 天后删除日志索引的 ISM policy
{
"policy": {
"description": "delete log indexes after 14 days",
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [],
"transitions": [
{ "state_name": "delete", "conditions": { "min_index_age": "14d" } }
]
},
{
"name": "delete",
"actions": [ { "delete": {} } ],
"transitions": []
}
],
"ism_template": { "index_patterns": ["logs-*"], "priority": 100 }
}
}使用 PUT 请求将其创建到 _plugins/_ism/policies/logs-retention。该模板仅适用于策略创建后生成的索引,因此磁盘中已有的索引需要手动附加该策略。
无论使用哪种系统,保留天数的效果都取决于配套的可用空间检查。如果卷中已有 10 天的日志并且已经占满空间,那么设置 14 天后删除也无法解决问题。因此,应将该策略与 VPS 磁盘健康监控 配合使用,并在磁盘使用率达到 80% 时触发告警。
每 GB 日志需要多少磁盘空间
准确数值取决于日志行和字段,因此应使用自己的数据进行测量,不要盲目采用公开的比例。两种机制的差异足以判断存储空间的变化方向。OpenSearch 和 Elasticsearch 会为每个已索引字段写入倒排索引,并同时存储文档,因此磁盘上的数据会大于原始文本;每个副本还会进一步放大占用量。在单节点环境中,应将副本数设置为 0,因为同一节点上的副本分片无法在该节点故障后继续提供保护:保持为 1 会使磁盘占用翻倍,并导致集群健康状态永久停留在 yellow。Loki 写入压缩后的 chunk,并维护较小的标签索引,因此其占用空间主要取决于日志行的压缩后大小。
sudo du -sh /var/lib/docker/volumes/loki-data/_data
curl -k -u admin:<password> "https://localhost:9200/_cat/indices?v&h=index,docs.count,store.size"根据实际使用的系统,在连续两天分别执行测量。两次结果的差值就是每日增长量。将其乘以保留天数,再为 compaction 和 merge 预留约 30% 的余量,然后与卷容量比较。如果容量不足,应先缩短保留时间,再购买磁盘,因为更大的卷只会将同一个问题推迟几周。
小型主机最先会在哪里出问题
内存最先耗尽。内核的 OOM(内存不足)终止程序会选择一个占用内存较大的进程,而日志主机上占用内存最大的进程通常是 JVM。journalctl -k | grep -i "killed process" 显示了终止事件,并在方括号中标出进程名称。被终止的进程不一定是日志组件,也可能是 sshd 或数据库。因此,日志实验可能会连带停止你原本要采集日志的应用。为容器设置明确的内存上限,让故障发生在你指定的容器中,这正是 Docker Compose 中的内存限制 的用途。
磁盘通常随后耗尽,搜索引擎会以一种明确且容易识别的方式失败。Elasticsearch 和 OpenSearch 会在多个级别监控磁盘使用率。低水位为 85%,高水位为 90%。达到 95% 的洪水水位后,该节点上包含分片的每个索引都会收到阻止写入的标记 index.blocks.read_only_allow_delete,随后写入会因 blocked by: [FORBIDDEN/12/index read-only / allow delete (api)] 失败。磁盘使用率降回高水位以下后,该阻止标记会自动解除。先释放磁盘空间;只有阻止标记仍未解除时,才手动清除。
curl -k -u admin:<password> -X PUT "https://localhost:9200/_all/_settings" \
-H 'Content-Type: application/json' \
-d '{"index.blocks.read_only_allow_delete": null}'Loki 的失败过程更不明显。它没有可切换到的只读模式,因此卷空间耗尽时,发送端会出现推送失败,查询结果中也会出现缺口;基数问题则会表现为内存缓慢增长,而不是直接报错。应按计划监控 chunks 目录的大小,不要等到发生故障后才检查。
最后一种故障是写入了错误的数据。日志系统不是指标系统:每 10 秒采样一次 CPU 负载并以文本形式存储,既占用大量存储空间,也不便于绘图;这项工作应交给类似 Ubuntu 24.04 上的 Zabbix 监控服务器 的系统。应用异常需要分组、去重和堆栈跟踪视图,这属于 自托管错误跟踪系统 的职责。判断站点是否宕机则是另一项工作,应使用 类似 Uptime Kuma 的运行时间和状态页面。日志系统应只保存人员会阅读的文本行。
FAQ
我需要 Elasticsearch 来搜索服务器日志吗?
对于一两台服务器,不需要。journalctl 已按单元、优先级、启动、时间范围进行过滤,轮换后的文件也能响应 grep 和 zgrep。当您有许多机器、需要同时搜索所有机器上的全文,或需要多人共享一个界面时,搜索集群才值得占用内存。在此规模以下,为 journald 设置大小限制和保留时间即可完成相同工作,而且不需要额外的 RAM。
自托管日志管理需要多少 RAM?
请以各项目发布的数据为准,不要套用经验值。Loki 和 Alloy 是 Go 程序,不需要预先保留堆内存;Grafana 文档说明,单体 Loki 每天最多约处理 20GB。OpenSearch 的示例 compose 为演示环境设置了 512 MB 堆内存,并在生产示例中设置了 2 GB;Elastic 表示堆内存必须保持在总内存的 50% 或以下,因此 2 GB 堆内存意味着在运行 Kibana 之前就需要一台 4 GB 机器。Logstash 文档建议其自身的堆内存不少于 4GB。这些是文档记录的设置,不是基准测试结果,因此请先测量您自己的负载,再确定容量方案。
Loki 和 OpenSearch 用于日志时,实际区别是什么?
区别在索引模型。Loki 只为标签建立索引,并将日志正文保存为压缩块,在查询时扫描,因此写入成本低,但范围较大的查询成本更高。OpenSearch 为字段内容建立索引,因此任意全文搜索速度快,但索引会同时消耗内存和磁盘。确定服务和时间窗口后再搜索时,选择 Loki。需要搜索无法预先确定的文本时,选择 OpenSearch。
VPS 上的日志应保留多长时间?
请在磁盘替您决定之前确定保留天数。每个系统只能在一个位置设置保留策略:journald 使用 MaxRetentionSec= 和 SystemMaxUse=,Loki 在启用 compactor 后使用 retention_period,OpenSearch 使用带 min_index_age 的 ISM 策略。对于大多数单服务器环境,14 到 30 天足以支持故障排查和事件复盘。必须保留更长时间的日志,应复制到服务器之外进行存储,因为只保存在发生故障的服务器上的日志不能作为可靠记录。
Promtail 仍然是将日志发送到 Loki 的方案吗?
不是。Promtail 已于 2 March 2026 结束生命周期,Grafana Alloy 将替代它。Loki 自带的 Docker 安装示例现在提供 Alloy 配置,Grafana 也提供转换器,可将现有 Promtail 配置转换为 Alloy 语法。现有的 Promtail 安装仍会运行,但不会再获得修复,因此应将迁移视为维护工作,而不是可以无限期推迟的升级。