oralce ubuntu minimal 初始化定制

目标与现状

把一台全新安装的 Oracle Ubuntu 24.04 minimal 实例改造成纯 docker 宿主机:不做别的事,不装监控,rootful 与 rootless 双栈并存,并由它接管原站点的全部容器与固定公网 IP。全过程脚本化为 optimize-ubuntu.sh,三种模式(--check 只读预检 / 无参数实跑 / --verify 只断言),幂等可反复执行。

终态:一个 optimize-<机名>.ini 描述目标机与两把钥匙,一条命令完成「精简 → docker 全栈 → rootless 前置 → 家目录命令集 → 13 项验收」,任何一步失败即 FATAL 退出,不做兜底。

前置事实(实测)

项实测值
发行版Ubuntu 24.04.5 LTS minimal(Oracle 官方镜像)
机型VM.Standard.E2.1.Micro,1 核 / 952M 内存 / 50G 引导卷
内核级限制kernel.apparmor_restrict_unprivileged_userns = 1(Ubuntu 23.10+ 默认,rootless 命门)
用户命名空间上限user.max_user_namespaces = 1212
镜像自带防火墙/etc/iptables/rules.v4:INPUT 仅放行 22,其余 -j REJECT
自带 snap3 个:core18 / snapd / OCI 云代理,其中 core18 被云代理占用
运维账号两个:特权账号(免密 sudo,只干 root 活)+ 日常账号(sudo 要密码,rootless 跑它名下)
安装后占用docker 全栈约 3.5G;迁入整站数据后 13G / 48G

实现过程

实例创建

  • 镜像选 Ubuntu 24.04 Minimal,引导卷 50G,与既有实例同一子网——安全列表随子网继承,不另建规则。
  • cloud-init user-data 建两个账号:特权账号 sudo: ALL=(ALL) NOPASSWD:ALL;日常账号 sudo: ALL=(ALL) ALL(要密码)。两账号各写自己的 ssh_authorized_keys,不给日常账号免密提权。
  • 公网 IP 先分配临时 IP 供安装期使用,站点验证通过后再把保留 IP 转过来。

系统精简

  • snap 整体清除(含云代理快照):先按轮次删应用快照、再删 base 快照,最后 apt-get purge snapd + 删 /var/lib/snapd、/snap。
  • 关服务:unattended-upgrades、apport、ModemManager、udisks2、rpcbind(含 socket)、getty@tty1。
  • 关计时器:apt-daily*、motd-news、fwupd-refresh、e2scrub_all、dpkg-db-backup。
  • 保留:iscsid(云引导卷)、serial-getty(串口救援)、fstrim(SSD)、cloud-init 网络。
  • journald SystemMaxUse=100M;swapfile 2G(已有活动 swap 则整段跳过);收尾 autoremove --purge + clean。

内核参数与宿主防火墙

键 / 端口值为什么
kernel.apparmor_restrict_unprivileged_userns0不关掉 AppArmor 对非特权用户命名空间的限制,rootless daemon 直接起不来
vm.swappiness30小内存机型别急着换出
net.ipv4.ip_forward1容器网络转发
fs.inotify.max_user_watches / max_user_instances524288 / 8192多容器同时监听文件不撞上限
INPUT 放行 TCP80 / 443 / 62222 / 63306边缘转发与端口直通;云安全列表放行 ≠ 能访问,宿主这一层必须同步放行

sysctl 落 /etc/sysctl.d/99-docker-host.conf;端口写进镜像自带的 /etc/iptables/rules.v4(iptables-persistent 开机复原),并同时用 iptables -I INPUT 1 立即生效,不 flush 现网规则。

docker 宿主化

  • 官方源 + 全栈:docker-ce / docker-ce-cli / containerd.io / buildx / compose-plugin / docker-ce-rootless-extras / slirp4netns / fuse-overlayfs / uidmap / dbus-user-session。
  • daemon.json:json-file 日志 10m×3 + live-restore + overlay2(与既有机型一致,日志不会吃满小盘)。
  • apt 全部带 -o DPkg::Lock::Timeout=600:新机首启 cloud-init 会占 dpkg 锁。

rootless 改造(四个硬前提)

前提做法失败表现
uid 映射uidmap 包 + 日常账号在 /etc/subuid 有行(useradd 建的才有)setuptool 报缺 newuidmap
常驻loginctl enable-linger <账号>无人登录时 rootless daemon 不起
cgroup 下放/etc/systemd/system/user@<UID>.service.d/delegate.conf 写 Delegate=yes带 cpus: 的容器报 NanoCPUs can not be set
安装dockerd-rootless-setuptool.sh install --forcerootful 在跑时 setuptool 默认拒绝

之后断言 /sys/fs/cgroup/user.slice/user-<UID>.slice/cgroup.controllers 含 cpu;不含就 systemctl restart user@<UID> 一次(linger 会抢在 drop-in 之前把 user manager 拉起,导致 Delegate 不生效——这是竞态,不是偶发)。再建 rootless 网络、把 CLI context 复位回 default(setuptool 会偷偷切成 rootless,裸 docker 行为因此漂移)。

bashrc 改造

  • 把跨机命令集整篇写入日常账号的 ~/.bashrc(原文件备份为 ~/.bashrc.bak-optimize):d/dc 走 rootless socket,rd/rdc 走 rootful socket,严格不互相回落。
  • 文件顶部先 unalias d dc rd rdc reload:旧 alias 残留会在函数定义解析时把 d() { 展开成语法错误——unalias 必须在任何复合命令之前,写在 if 体内没用(bash 先整块解析后执行)。
  • 配套脚本落家目录并加执行位:mem.sh(单文件双模式:source 得函数 / 执行跑主流程)、dockersave.sh。
  • 脚本用 ssh ... 'bash -s' < 本地文件 送远端执行,不走多层引号拼接(本地 set -u 会把远端 $VAR 提前吞掉)。

站点迁移与固定 IP 交接

  • 数据:同 VCN 私网直传 tar(--acls --xattrs --same-owner 保留 subuid 属主),实测 38MB/s,5.4G 用 2 分 49 秒;文件数与源机逐一相等。跨公网中转实测不到 0.7MB/s,不要用。
  • 镜像:不重新 pull,docker save | ssh | docker load(用户裁定:ghcr 等源太慢)。
  • 固定 IP 转移三步(顺序保证每步后仍能远控):① 删新机临时公网 IP → ② 把保留 IP 从旧机 private-ip 改绑新机 → ③ 给旧机补一个新临时 IP。同子网安全列表自动继承。
  • 边缘转发:纯端口转发容器改 network_mode: host 直连 127.0.0.1,不挂在网桥上——bridge 形态下 host.docker.internal 指向 docker0(NO-CARRIER),在新系统上会 EHOSTUNREACH。
  • DNS 切换、六域名验收通过后才销毁旧机(/www 先删,再连引导卷一起销毁,释放额度)。

实测证据

判据实测
rootful daemon29.9.3(server 版本与 CLI 一致)
rootless daemon29.9.3
rootless 功能docker run --rm hello-world 通过(userns 真跑通,不是只看 socket)
apparmor userns0
cgroup 控制器cpuset cpu io memory pids(cpu 已下放)
snap0 个,snap 命令不存在
内存used 384M / available 567M(952M 机器,双 daemon 常驻)
磁盘安装完成 3.5G/48G;整站迁入后 13G/48G
站点验收笔记 302 / 代码库 200 / 任务 404 / 文件 401 / 静态 200 / 首页 200
端口直通62222 / 63306 均为 OPEN
重启韧性整机重启后 13 项验收全自动恢复(rootless 由 linger 自起,不依赖登录)

管理命令

用途命令
只读预检(两账号连通性 + 远端现状)./optimize-ubuntu.sh --check optimize-<机名>.ini
实跑(幂等,可反复)./optimize-ubuntu.sh optimize-<机名>.ini
只断言 13 项验收./optimize-ubuntu.sh --verify optimize-<机名>.ini
看实跑日志tail -30 logs/optimize-<机名>-<时间>.log
扩新机cp optimize-<机名>.ini optimize-<新机>.ini 改五键(host / user / user_key / sudo_user / sudo_key)
换命令集来源目录BASHRC_DIR=<目录> ./optimize-ubuntu.sh <ini>
rootless daemon 状态systemctl --user is-active docker.service
区分两条栈ss -tlnp | grep -E ':80 |:443 |:8080 |:8443 ';rootful 的 docker-proxy 与 rootless 的 rootlesskit 会同时出现
宿主放行端口是否在iptables -S INPUT | grep dport

命令为什么这样写:脚本只认 sudo 账号干 root 活、日常账号干 rootless,两条身份不混;所有远端执行走 ssh ... bash -s 的 stdin,避免引号层数与变量提前展开;验收一律断言而不是打印「完成」。

踩坑记录

现象(原文)成因处置
rootless daemon 起不来 / userns 相关报错Ubuntu 23.10+ 默认 apparmor_restrict_unprivileged_userns=1置 0 并落 sysctl.d
cannot remove "core18": snap is being used by ...base 快照被应用快照依赖,按字母序删必失败多轮重试到删不动,再 apt purge snapd
Aborting because rootful Docker is running and accessiblesetuptool 检测到 rootfulinstall --force
docker.socket 不存在导致 enable 失败rootless setuptool 只建 docker.servicesocket 有才 enable,不硬启
rootful 探测结果为空特权账号不在 docker 组凡探 rootful 一律 sudo docker ...
NanoCPUs can not be set, as your kernel does not support CPU CFS scheduleruser@UID 只拿到 memory/pids 控制器Delegate=yes drop-in + 断言 cgroup.controllers 含 cpu
外部 443 表现为 TLS 握手 EOF,宿主自己 curl 正常镜像自带 rules.v4 只放行 22,INPUT REJECTrules.v4 补端口 + 立即 iptables -I
connect(...): Host is unreachable(边缘容器 → 宿主)bridge 形态下 host-gateway 指向 NO-CARRIER 的 docker0边缘容器改 network_mode: host
VERSION_CODENAME: 未绑定的变量(本地脚本报)本地 set -u 提前展开远端变量远端脚本走 stdin 传输,不在命令行里拼变量
TMP_USER: 未绑定的变量(trap 里)EXIT trap 引用尚未赋值的变量先声明为空再 trap,用时写 ${X:-}
重复 source 报 syntax error near unexpected token '('旧 alias 在函数定义解析时被展开unalias 放文件顶部、复合命令之前;验证必须用真实旧 alias + shopt -s expand_aliases 复现

不做的事(用户裁定)

  • 不定 alias:d/dc/rd/rdc 由用户自己写进 .bashrc,脚本只负责把命令集文件送到位。
  • 不装监控:不装任何资源监控、探针、采集代理;只保留 systemd 自身的 journald 配额。
  • 不动 cloud-init 网络:systemd-networkd / resolved 原样保留,避免重启丢网。
  • 镜像不重新 pull:迁移统一 docker save | ssh | docker load。
  • 终端命令不进 nix 包装:命令收敛在 bashrc 一套文件里,不新造系统级 wrapper。

边界

  • 只针对 Ubuntu(Oracle 云镜像);换成 alpine 等发行版时只替换 SYS 段(包管理器与 init 系统),USER / BASHRC / 验收三段原样复用。
  • rootless 数据与 rootful 数据不共享(data-root 不同),迁移后必须重新拉起容器,不能只拷镜像。
  • 数据属主依赖各机 /etc/subuid 起始值一致;换机若 subuid 区间不同,容器内 1001 → 宿主 UID 会变,需重新 chown。
  • 本笔记不含任何密码、私钥内容与具体公网 IP;主机名、域名、路径一律占位符(笔记树带公开分享属性)。
  • 性能预期仅为「1 核 952M 小机可跑双栈 + 六容器」,不是跑分;双 daemon 常驻本身即固定开销。