整理自实战,涵盖:进入容器、查询容器、文件复制、时间戳行为差异,以及 Kubernetes 对应操作。
适合日常运维查阅,重点标注了容易踩的坑。
一、进入容器的常见命令
1. docker exec —— 最常用
进入正在运行的容器,开一个新进程,退出不影响容器。
# 进入交互式 bash
docker exec -it container_name bash
# 精简镜像没有 bash,用 sh
docker exec -it container_name sh
# 指定用户(如以 root 身份进入)
docker exec -it -u root container_name bash
# 指定工作目录
docker exec -it -w /app container_name bash
# 不进终端,直接执行单条命令
docker exec container_name ls /app
-i= 保持标准输入打开,-t= 分配伪终端。交互式必须两个一起用。
2. docker attach —— 连接主进程(慎用)
连到容器的 1 号主进程的输入输出。
docker attach container_name
⚠️ 退出(Ctrl+C)可能杀死容器。安全退出用 Ctrl+P 再 Ctrl+Q。一般不用它进 shell。
3. docker run -it —— 新建并进入容器
注意这是新建容器,不是进入已有容器。
docker run -it --rm ubuntu bash # 退出即删除
docker run -it --rm alpine sh
4. Docker Compose
用服务名而非容器名,更方便。
docker compose exec service_name bash # 新版
docker-compose exec service_name bash # 旧版
5. nsenter —— 无 shell 镜像的底层进入(高级)
distroless 等镜像里没有任何 shell 时,从宿主机进命名空间:
pid=$(docker inspect -f '{{.State.Pid}}' container_name)
nsenter -t $pid -m -u -i -n -p sh
二、容器名称是否会重复
同一个 Docker daemon 内,容器名强制唯一。 创建同名容器会直接报错:
Conflict. The container name "/myapp" is already in use by container "xxxx".
所以 docker exec -it 完整名 bash 永远只匹配一个,不存在歧义。
但以下情况会让你”看起来”有多个:
| 情况 | 说明 |
|---|---|
| 过滤器子串匹配(最常见的坑) | --filter "name=web" 会匹配 web、web1、my_web 等多个 |
| Compose 多副本(scale) | --scale web=3 生成 project-web-1/2/3,前缀相同名字不同 |
| 不同 daemon / 主机 | 不同 Docker 环境间名字可重复,互不冲突 |
精确匹配写法(用正则锚定,避免子串误伤):
docker ps -a --filter "name=^myapp$" --format "{{.Names}}"
三、多个容器时如何处理
docker exec -it 一次只能连一个容器的交互终端。多容器场景:
# 1. 逐个进入(多开终端,各进一个,互不干扰)
docker exec -it container_a bash
# 2. 批量执行同一非交互命令
for c in $(docker ps --filter "name=web" --format "{{.Names}}"); do
docker exec "$c" echo "hello from $c"
done
# 3. 先查再进
docker ps --format "{{.Names}}"
docker ps --filter "name=api" --format "{{.Names}}"
# 4. 同一容器开多个 bash 会话(在不同终端分别 exec 即可)
docker exec -it my_app bash
四、复制文件:docker cp
格式永远是 源 → 目标,容器内路径写成 容器名:路径。
# 容器 → 宿主机
docker cp container_name:/app/log.txt ./log.txt
docker cp container_name:/app/logs ./logs # 复制整个目录
# 宿主机 → 容器(源和目标对调)
docker cp ./config.json container_name:/app/config.json
关键细节:
# 用容器 ID 也行
docker cp a1b2c3d4:/app/data.db ./data.db
# 末尾 /. 的区别
docker cp container:/app/logs ./logs # 把 logs 目录整个拷过来
docker cp container:/app/logs/. ./logs # 只拷 logs 里的内容到本地 logs
# 容器停止状态也能 cp(这是 docker cp 的一大优势,适合捞日志/数据)
docker cp stopped_container:/app/file.txt ./
⚠️ 两个方向都不会自动创建多级父目录,目标父目录不存在会报错。
其他场景:
# 挂载卷(提前规划时最优,根本不用 cp)
docker run -v /home/ryan/data:/app/data myimage
# Compose: 先查容器名再 docker cp
docker compose ps
docker cp project-web-1:/app/log.txt ./
# 大量文件先打包再拷
docker exec container tar czf /tmp/backup.tar.gz /app/data
docker cp container:/tmp/backup.tar.gz ./
五、两个复制方向的差异(属主 + 时间戳)
命令对称,但属主和时间戳两个方向行为不同,这是最容易踩坑的地方。
5.1 文件属主(ownership)
| 方向 | 文件属主 |
|---|---|
| 容器 → 宿主机 | 执行 docker cp 的宿主机用户 |
| 宿主机 → 容器 | 默认 root(UID 0),除非用 --chown |
宿主机 → 容器最常见的坑:拷进去的文件属于 root,但应用以非 root 用户运行 → 没权限读写。
# 指定容器内属主(较新版本 Docker 支持)
docker cp --chown=appuser:appuser ./config.json container:/app/config.json
# 老版本: 拷进去后再 docker exec container chown ...
文件权限位(rwx,如 644/755)两个方向都会保留,但属主变了,能否访问就不同。
5.2 时间戳(实测结论,方向不对称!)
⚠️ 这是经实测验证的真实行为,且反直觉:
| 方向 | mtime(修改时间)结果 |
|---|---|
| 容器 → 宿主机 | ✅ 保留原始时间 |
| 宿主机 → 容器 | ❌ 变成复制时的当前时间 |
为什么不对称?
两个方向底层都用 tar 流传输,tar 头本身带 mtime。差别在于哪一端解包、解包时有没有还原 tar 头里的时间:
- 容器 → 宿主机:由 docker 客户端(CLI) 在宿主机侧解包,会还原 tar 头里的 mtime → 时间不变 ✅
- 宿主机 → 容器:tar 流送进 daemon,由它写入容器联合文件系统,落地的 mtime 是写入时刻,没有把原始 mtime 设回去 → 变成”现在” ❌
简单说:读出端(CLI)保时间,写入容器端(daemon)不保。
此外,ctime 和创建时间(birthtime) 任何复制都无法保留——复制本质是在目标端新建文件,对文件系统而言就是个”刚出生”的新文件。
想让 宿主机→容器 也保留时间,绕过 docker cp,用 tar 在两端各自解包:
# tar 自己应用 mtime,不受 docker cp 限制
tar cf - -C /host/dir myfile.txt | docker exec -i container tar xf - -C /target/dir
或直接用挂载卷——根本不发生复制,所有时间戳天然一致。
六、Kubernetes 对应操作
K8s 最小单位是 Pod,一个 Pod 可能跑多个容器。工具是 kubectl。
6.1 查询容器(先找 Pod)
kubectl get pods # 当前命名空间
kubectl get pods -n my-namespace # 指定命名空间
kubectl get pods -A # 所有命名空间
kubectl get pods -o wide # 显示节点、IP
kubectl get pods | grep web # 模糊查找
# 看一个 Pod 里有哪些容器
kubectl describe pod pod_name
kubectl get pod pod_name -o jsonpath='{.spec.containers[*].name}'
6.2 进入容器
# 单容器 Pod
kubectl exec -it pod_name -- bash
kubectl exec -it pod_name -- sh
kubectl exec -it pod_name -n my-namespace -- bash
# 多容器 Pod 用 -c 指定容器(和 docker 最大的区别)
kubectl exec -it pod_name -c container_name -- bash
⚠️ 那个 -- 必须写,用于把 kubectl 参数和容器内命令分开。
6.3 直接执行命令
kubectl exec pod_name -- ls /app
kubectl exec pod_name -c container_name -- cat /etc/hostname
6.4 复制文件
# 容器 → 本地
kubectl cp pod_name:/app/log.txt ./log.txt
# 本地 → 容器
kubectl cp ./config.json pod_name:/app/config.json
# 多容器 Pod 用 -c
kubectl cp pod_name:/app/log.txt ./log.txt -c container_name
# 跨命名空间用 namespace/pod 格式
kubectl cp my-namespace/pod_name:/app/log.txt ./log.txt
kubectl cp底层依赖容器里有tar命令,distroless 等无 tar 镜像会失败。
七、总表速查
Docker 常用
| 操作 | 命令 |
|---|---|
| 列出运行容器 | docker ps |
| 列出全部容器 | docker ps -a |
| 进入容器 | docker exec -it 名 bash(无 bash 用 sh) |
| 精确匹配名 | docker ps --filter "name=^名$" |
| 容器→宿主机 | docker cp 名:/路径 ./本地 |
| 宿主机→容器 | docker cp ./本地 名:/路径(注意属主变 root、时间变 now) |
| 看详情 | docker inspect 名 |
Docker vs Kubernetes 对照
| 操作 | Docker | Kubernetes |
|---|---|---|
| 列出 | docker ps | kubectl get pods |
| 进入 | docker exec -it 名 bash | kubectl exec -it pod -- bash |
| 多容器指定 | (一容器一名) | -c container_name |
| 执行命令 | docker exec 名 cmd | kubectl exec pod -- cmd |
| 复制文件 | docker cp | kubectl cp |
| 看详情 | docker inspect | kubectl describe pod |
最常用两三句
# Docker
docker ps
docker exec -it container_name bash
docker cp container_name:/容器内路径 ./宿主机路径
# K8s
kubectl get pods
kubectl exec -it pod_name -- bash # 多容器加 -c 容器名
kubectl cp pod_name:/app/log.txt ./
重点提醒(容易遗忘的坑)
- 容器名唯一,”多个同名”基本是过滤器子串匹配或 compose 多副本造成的,用
^名$精确锚定。 docker cp时间戳方向不对称:容器→宿主机保留时间,宿主机→容器变成当前时间。要保真用tar管道或挂载卷。- 宿主机→容器属主默认 root,非 root 应用会没权限,记得
--chown或事后 chown。 - K8s 多容器 Pod 必须用
-c指定容器,且kubectl exec命令前要有--。 docker cp对停止的容器也有效;kubectl cp依赖容器内有 tar。