Utilo

把 docker run 收成 Compose,并看清哪些参数会丢

端口、环境变量、卷、重启策略和网络怎样从 docker run 进入 compose.yaml,哪些标志不能靠机械转换保住,以及环境变量何时该变成 Kubernetes Secret。

· 6 分钟阅读

容器常常从一行命令开始。有人先 docker run 一个端口,再加环境变量,再加卷,再加 --restart unless-stopped,因为进程在午饭时退出了。这一行已经是一次部署,却只活在 shell 历史里。Compose 是把它变成可以评审的文件的那一步。

docker run 与 Compose 转换器 在本地解析命令并写出服务,也可以读 Compose 文件、写回你要单独试一个服务时用的 docker run。这篇讲的是哪些标志有诚实的对应,哪些不该指望机械转换替你发明。

一行命令应当变成什么样的服务

拿一条已经在干活的命令:

docker run -d --name billing \
  -p 8080:8080 \
  -e DATABASE_URL=postgres://billing:secret@db:5432/billing \
  -e LOG_LEVEL=info \
  -v billing-data:/var/lib/billing \
  --restart unless-stopped \
  ghcr.io/example/billing:1.4.2

Compose 服务不是把字符串换个写法。它是另一种数据:

  • 镜像是 ghcr.io/example/billing:1.4.2。标签是镜像的一部分,不是可以忘掉的另一个字段。
  • 8080:8080 是发布端口。Compose 里写成 ports: ["8080:8080"]。左边是主机,右边是容器。写反了,服务在网络内部看起来正常,你的笔记本却连不上。
  • --restart unless-stopped 变成 restart: unless-stopped。always 和 on-failure 是不同策略。on-failure 还可以带重试次数。转换器没认出这个标志就把它丢掉,进程在重启后不会回来,而这正是你写这个标志要防的事。
  • 命名卷 billing-data 既要出现在服务上,若要保住名字,也要出现在顶层的 volumes: 里。./data:/var/lib/billing 这种是绑定主机路径。命名卷是 Docker 管理的目录。把两者混成一件事,数据的位置就变了。
  • -d 和 --name 说的是客户端这次怎么调用。分离模式没有 Compose 字段,因为 docker compose up -d 是客户端的选择。容器名可以设,但 Compose 已经会加上项目名前缀。写死 container_name: billing 会让这份文件无法在同一台机器上跑两份。

环境变量是敏感的部分。例子里的 DATABASE_URL 含有密码。转换器看见它不需要服务器,你也不应该需要。密码不要写进要提交的 Compose 文件,放到 env 文件里。

机械转换会说错的标志

--rm 是进程退出后删掉容器。Compose 没有这个开关。转换时悄悄丢掉 --rm,容器会留下来。对长期服务这通常是你想要的,对一次性迁移容器则是错的。在文件里用注释或 profiles 写清楚,不要假设这个标志还在。

-it 分配终端并保持标准输入。Compose 可以设 stdin_open 和 tty。Web 服务两者都不需要。交互式 shell 需要。把一条调试命令(docker run -it --rm ubuntu)收成长期服务,结果是容器马上退出,重启策略再把它拉起来。

--network host 不是一个叫 host 的网络名,而是一种模式。Compose 写成 network_mode: host。若把它当成用户网络 host,进程会绑在桥接网络上,而不是主机网卡。端口发布又开始起作用,这和当初加 --network host 的原因相反。

--link 已经过时。新文件应当用 Compose 网络和 DNS,服务名就是主机名。把 --link 抄进新文件,是在复制 Docker 正在拆掉的功能。

健康检查(--health-cmd、--health-interval)在 Compose 里是一块配置。如果转换器留下了命令、丢掉了间隔,你得到的是 Docker 的默认值。启动慢的进程可能被标成不健康,再叠上重启策略,就会循环被杀掉。转换之后要读健康检查,不要假设默认值等于你敲过的那些标志。

Compose 里 deploy.resources 的限制面向 Swarm 和部分实现。docker run 上的 --memory 和 --cpus 作用于本机容器。这些数字若只写在 deploy 下面,开发机上的 docker compose up 可能根本不看。转换结果是草稿。进程会泄漏内存时,限制要读两遍。

环境变量是另一个工具的事

服务有了之后,变量是会泄漏的那一块。Compose 写 env_file: .env,只要 .env 被 git 忽略,提交的文件里就没有秘密。Kubernetes 不读这个 .env。它要的是 Secret 或 ConfigMap。

这一步是 .env 转换器:把 .env 写成 Secret 清单,或写成 JSON、YAML,并对比两份 env,让生产环境缺的键在 Pod 崩溃前现形。Compose 转换器不知道哪个键是秘密。env 转换器不知道你的服务名叫什么。按这个顺序用:先得到服务,把变量移出要提交的文件,同一批变量要上 Kubernetes 时再生成清单。

Secret 清单里的值是 Base64。这不是锁。能 kubectl get secret -o yaml 的人都能解开。在本地生成清单,是为了不必把值贴进一个写着「env 转 Kubernetes」的网页。集群仍然会保存这份 Secret。那是另一条信任边界,和转换这一步不是一回事。

反向:从 Compose 回到 docker run

当你想在项目之外重现一个服务时,把 Compose 贴进去要 docker run。反向映射会丢掉项目级网络、depends_on 的顺序,以及大多数 profile。depends_on 变不成标志。依赖要你自己先启动。转出来的 docker run 没有数据库,不是解析器坏了。Compose 表达的是单条 docker run 表达不了的顺序。

多个服务变成多条命令。文件顶部声明的卷要先建好,或者让 Docker 在命令里见到这个名字时自己建。相对路径的绑定挂载是相对 Compose 文件所在目录,不是相对你后来粘贴 docker run 时所在的目录。转出来的命令里若有 ./data,就在那个目录里运行,或把路径改掉。

提交之前要读的几处

读镜像标签。读每个发布端口以及它绑在哪块接口上。读环境变量,把秘密移到 env 文件。把重启策略和健康检查放在一起读。若 network_mode 写着 host,读它。然后对一个可以扔掉的数据库跑这份文件,或跑生成的 docker run。能解析不等于数据目录还在原来的地方。

两个工具都留在浏览器里。你粘贴的命令里可能有镜像仓库密码、云令牌或数据库 URL。改写它的页面不需要留一份副本。

相关指南