整理自实战,涵盖:进入容器、查询容器、文件复制、时间戳行为差异,以及 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+PCtrl+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" 会匹配 webweb1my_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 对照

操作DockerKubernetes
列出docker pskubectl get pods
进入docker exec -it 名 bashkubectl exec -it pod -- bash
多容器指定(一容器一名)-c container_name
执行命令docker exec 名 cmdkubectl exec pod -- cmd
复制文件docker cpkubectl cp
看详情docker inspectkubectl 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 ./

重点提醒(容易遗忘的坑)

  1. 容器名唯一,”多个同名”基本是过滤器子串匹配或 compose 多副本造成的,用 ^名$ 精确锚定。
  2. docker cp 时间戳方向不对称:容器→宿主机保留时间,宿主机→容器变成当前时间。要保真用 tar 管道或挂载卷。
  3. 宿主机→容器属主默认 root,非 root 应用会没权限,记得 --chown 或事后 chown。
  4. K8s 多容器 Pod 必须用 -c 指定容器,且 kubectl exec 命令前要有 --
  5. docker cp停止的容器也有效;kubectl cp 依赖容器内有 tar