从 systemd 定时器到 podman — 真正搭建服务与网络
目标
systemd 服务与定时器、nginx 反向代理、iptables DNAT/DROP、dummy 接口上的桥接与 bond 以及 netplan、chrony 与时区、git 合并、无需注册表的 podman 镜像、永久生效的 sysctl、负载指标——这些在 LFCS 的 Operations 和 Networking 领域中被 Pod 禁用的内容,将在 Ubuntu 24.04 VM 上真实运行。
为什么重要
LFCS 看的是“结果”,而不是“说明”。单元文件写得再好,只要 systemctl is-enabled 的结果是 disabled,就是 0 分;nginx 配置语法即使正确,只要默认站点占着 80 端口,代理就无法工作。本实验的评分器不只检查文件,还会用 systemctl is-active、curl、iptables -S、/sys/class/net、sysctl -n、podman inspect 查看系统当前的状态。
有三条原则会反复出现。第一,enable 与 start 是不同的问题(是否要在开机时启用 / 现在是否已在运行)。第二,写了文件与内核、守护进程知道这件事是两回事(sysctl --system、daemon-reload、nginx reload)。第三,要留出退路——如果把 iptables 的 INPUT 默认策略改成 DROP,或者封掉 8899、22,评分代理就会断开,从下一步开始会出现找不出原因的失败。
会话从 60 分钟开始,最长可延长到 180 分钟,会话结束后 VM 会消失。本实验预计 85 分钟,所以开始时请先延长一次。
步骤
- 编写
/usr/local/bin/lfcs-heartbeat.sh,使其把当前时间追加一行到/var/log/lfcs-heartbeat.log,并在/etc/systemd/system/中把它写成Type=oneshot的lfcs-heartbeat.service,以及每分钟调用该服务的lfcs-heartbeat.timer。定时器必须同时完成 enable 和 start,并且要手动启动一次服务,使日志中至少有一行。 - 在
/srv/backend/index.html中写入backend ok这段文字,把python3 -m http.server 8000 --directory /srv/backend做成 systemd 服务lfcs-backend.service,并 enable、start。然后创建/etc/nginx/sites-available/lfcs-proxy,让 nginx 在 80 端口反向代理到这个后端,并启用它(停用默认站点)。curl http://127.0.0.1/必须返回backend ok。 - 用 iptables 添加两条规则。(1)在
nat表的PREROUTING中,把进入 tcp 8080 的流量转发到 80 的 DNAT(或 REDIRECT)。(2)在filter表的INPUT中,对所有接口的 tcp 9999 执行DROP。INPUT 的默认策略保持 ACCEPT 不变,不要动 8899 和 22(评分代理和 SSH)。最后把iptables-save的输出保存到/root/lfcs/net/rules.v4。 - 创建 dummy 接口
dummy0、dummy1、dummy2。把dummy0加入桥接br0,并给br0分配192.0.2.1/24。以active-backup模式创建 bondbond0,并把dummy1与dummy2加入其中(不要动 eth0 之类的真实接口)。用 netplan 语法把同样的配置写入/root/lfcs/net/99-lab.yaml,但不要应用它。 - 在
/etc/chrony/chrony.conf中添加server time.cloudflare.com iburst这一行,并重启 chrony。把时区改为Asia/Seoul。然后把chronyc sources保存到/root/lfcs/time/sources.txt,把chronyc tracking保存到/root/lfcs/time/tracking.txt,把timedatectl保存到/root/lfcs/time/timedatectl.txt。(这台 VM 的 UDP 123 被屏蔽,同步本身可能无法完成——评分的是配置和状态。) - 在
/root/lfcs/repo中创建 git 仓库(默认分支main),并提交README.md。在分支feature上添加feature.txt并提交,然后回到main,以保留合并提交的方式(--no-ff)进行合并。工作树必须保持干净。 - 把只包含
/bin/busybox(busybox-static)的根文件系统打成 tar,用podman import导入,创建localhost/lfcs-busybox:1镜像(不从注册表拉取)。用该镜像以--network none运行名为bb1的容器,让它输出hello-from-busybox,并把输出保存到/root/lfcs/bb/run.txt。不要删除容器(退出码必须为 0)。 - 在
/etc/sysctl.d/90-lfcs.conf中写入net.ipv4.ip_forward = 1、vm.swappiness = 10、net.core.somaxconn = 1024三个值,并同时应用到当前内核。应用之后,用sysctl读取这三个值,保存到/root/lfcs/sysctl/runtime.txt。 - 把
uptime的输出保存到/root/lfcs/perf/uptime.txt,把top -bn1的前 15 行保存到/root/lfcs/perf/top.txt。然后把/root/lfcs/perf/load.txt写成两行:NPROC=<이 VM 의 CPU 개수>、LOAD1=<1분 로드 평균(/proc/loadavg 첫 값)>(占位符依次为本 VM 的 CPU 数量、1 分钟平均负载,即 /proc/loadavg 的第一个值)。
参考
- 编写或修改单元之后,要执行
systemctl daemon-reload。用systemctl list-timers --all查看定时器是否已被识别。 - nginx:
nginx -t→systemctl reload nginx。如果不删除sites-enabled/default,default_server就会出现两个。 - iptables:
iptables -t nat -S PREROUTING、iptables -S INPUT。可以用-C查询规则是否已经存在。 - bond 的从属接口要在
ip link set dummy1 down之后再执行master bond0。用cat /sys/class/net/bond0/bonding/mode确认模式。 - 这台 VM 对外只开放了 DNS 和 80/443。
chronyc sources中的?是正常的,而且 podman 不会从注册表拉取。 - 常见错误 1:对定时器只执行
start,漏掉enable——重启之后它就会消失。 - 常见错误 2:像
lvextend那样,sysctl 也只写了文件,漏掉sysctl --system,导致内核中的值保持不变。 - 常见错误 3:用 fast-forward 方式合并,结果没有合并提交。
服务单元与定时器
编写 /usr/local/bin/lfcs-heartbeat.sh,使其把当前时间追加一行到 /var/log/lfcs-heartbeat.log,并在 /etc/systemd/system/ 中把它写成 Type=oneshot 的 lfcs-heartbeat.service,以及每分钟调用该服务的 lfcs-heartbeat.timer。定时器必须同时完成 enable 和 start,并且要手动启动一次服务,使日志中至少有一行。
定时器需要 [Timer] 中的 OnBootSec/OnUnitActiveSec,以及 [Install] 中的 WantedBy=timers.target。写完单元文件之后,要先执行 daemon-reload。is-enabled 和 is-active 是不同的问题。
nginx 反向代理
在 /srv/backend/index.html 中写入 backend ok 这段文字,把 python3 -m http.server 8000 --directory /srv/backend 做成 systemd 服务 lfcs-backend.service,并 enable、start。然后创建 /etc/nginx/sites-available/lfcs-proxy,让 nginx 在 80 端口反向代理到这个后端,并启用它(停用默认站点)。curl http://127.0.0.1/ 必须返回 backend ok。
关键是 server 块中的 listen 80 default_server,以及 location / { proxy_pass http://127.0.0.1:8000; }。如果不删除 sites-enabled/default,两个 default_server 会冲突。用 nginx -t 确认之后再 reload。
用 iptables 实现 DNAT 与 DROP
用 iptables 添加两条规则。(1)在 nat 表的 PREROUTING 中,把进入 tcp 8080 的流量转发到 80 的 DNAT(或 REDIRECT)。(2)在 filter 表的 INPUT 中,对所有接口的 tcp 9999 执行 DROP。INPUT 的默认策略保持 ACCEPT 不变,不要动 8899 和 22(评分代理和 SSH)。最后把 iptables-save 的输出保存到 /root/lfcs/net/rules.v4。
DNAT 用 -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination <本 VM 的 IP>:80(或 -j REDIRECT --to-ports 80)。DROP 用 -A INPUT -p tcp --dport 9999 -j DROP。发给自己的数据包不会经过 PREROUTING,所以 DNAT 通过规则是否存在来确认,DROP 则通过访问 127.0.0.1:9999 得到的是“超时”而不是“拒绝”来确认。
dummy 上的桥接与 bond,以及 netplan
创建 dummy 接口 dummy0、dummy1、dummy2。把 dummy0 加入桥接 br0,并给 br0 分配 192.0.2.1/24。以 active-backup 模式创建 bond bond0,并把 dummy1 与 dummy2 加入其中(不要动 eth0 之类的真实接口)。用 netplan 语法把同样的配置写入 /root/lfcs/net/99-lab.yaml,但不要应用它。
ip link add <名称> type dummy|bridge|bond。从属接口用 ip link set master 。bond 的从属接口在加入之前必须处于 down 状态。用 /sys/class/net/bond0/bonding/mode 确认 bond 模式。
chrony 与时区
在 /etc/chrony/chrony.conf 中添加 server time.cloudflare.com iburst 这一行,并重启 chrony。把时区改为 Asia/Seoul。然后把 chronyc sources 保存到 /root/lfcs/time/sources.txt,把 chronyc tracking 保存到 /root/lfcs/time/tracking.txt,把 timedatectl 保存到 /root/lfcs/time/timedatectl.txt。(这台 VM 的 UDP 123 被屏蔽,同步本身可能无法完成——评分的是配置和状态。)
timedatectl set-timezone Asia/Seoul。Ubuntu 中 chrony 的服务名称是 chrony。chronyc sources 的第一列为 ? 表示还没有收到响应,在这里是正常的。
git 基础——分支与合并
在 /root/lfcs/repo 中创建 git 仓库(默认分支 main),并提交 README.md。在分支 feature 上添加 feature.txt 并提交,然后回到 main,以保留合并提交的方式(--no-ff)进行合并。工作树必须保持干净。
git init -b main。提交需要 user.name/user.email(git config)。合并提交可以用 git log --merges 看到。如果以 fast-forward 方式合并,就不会有合并提交。
不用注册表,创建并运行 podman 镜像
把只包含 /bin/busybox(busybox-static)的根文件系统打成 tar,用 podman import 导入,创建 localhost/lfcs-busybox:1 镜像(不从注册表拉取)。用该镜像以 --network none 运行名为 bb1 的容器,让它输出 hello-from-busybox,并把输出保存到 /root/lfcs/bb/run.txt。不要删除容器(退出码必须为 0)。
复制 rootfs/bin/busybox,然后 tar -C rootfs -cf bb.tar . → podman import bb.tar localhost/lfcs-busybox:1。运行用 podman run --name bb1 --network none localhost/lfcs-busybox:1 /bin/busybox echo hello-from-busybox。本实验中的 podman 以 root 运行。
让内核参数永久生效
在 /etc/sysctl.d/90-lfcs.conf 中写入 net.ipv4.ip_forward = 1、vm.swappiness = 10、net.core.somaxconn = 1024 三个值,并同时应用到当前内核。应用之后,用 sysctl 读取这三个值,保存到 /root/lfcs/sysctl/runtime.txt。
sysctl --system 会按顺序读取并应用 /etc/sysctl.d/*.conf。只写文件的话,在重启之前内核并不知道。用 sysctl -n <键> 查看当前值。
留下负载指标
把 uptime 的输出保存到 /root/lfcs/perf/uptime.txt,把 top -bn1 的前 15 行保存到 /root/lfcs/perf/top.txt。然后把 /root/lfcs/perf/load.txt 写成两行:NPROC=<이 VM 의 CPU 개수>、LOAD1=<1분 로드 평균(/proc/loadavg 첫 값)>(占位符依次为本 VM 的 CPU 数量、1 分钟平均负载,即 /proc/loadavg 的第一个值)。
top -bn1 以批处理模式只输出一次。/proc/loadavg 的第一个字段是 1 分钟平均值,CPU 数量用 nproc 获取。平均负载必须与 CPU 数量对比才有意义。