80 lines
6.3 KiB
Markdown
80 lines
6.3 KiB
Markdown
---
|
||
id: devops.docker.interview
|
||
title: Docker 面试题
|
||
type: interview
|
||
domain: devops
|
||
stack: [docker]
|
||
tags: [Docker, 容器, Compose, 面试]
|
||
level: basic
|
||
related: [devops.docker.image-dockerfile, devops.docker.compose-network-volume]
|
||
week: [3, 14, 16]
|
||
---
|
||
|
||
# Docker 面试题
|
||
|
||
## 基础
|
||
|
||
### 【基础】容器和虚拟机有什么区别?
|
||
|
||
**参考答案**:虚拟机虚拟化硬件,每个虚拟机有自己完整的操作系统内核,隔离性强但启动慢、占用大;容器共享宿主机内核,靠 Linux 的 namespace 做隔离(进程、网络、文件系统等)、cgroups 限制资源,启动快、占用小,但隔离性比虚拟机弱。
|
||
|
||
**追问**:容器的隔离靠什么实现?→ Linux 的 namespace(隔离进程、网络、文件系统等视图)和 cgroups(限制 CPU、内存等资源);容器共享宿主机内核,隔离程度弱于虚拟机。
|
||
|
||
### 【基础】镜像和容器是什么关系?镜像为什么要分层?
|
||
|
||
**参考答案**:镜像是只读模板,容器是镜像运行起来的实例,在镜像之上加了一个可写层。分层让多个镜像可以共享相同的底层(节省磁盘和下载),构建时没有变化的层可以复用缓存。
|
||
|
||
**追问**:修改 Dockerfile 中间的一行,为什么后面的层都要重新构建?→ 每一层的缓存依赖它之前的所有层,前面的层变了,后面的缓存全部失效。所以把变化少的步骤(安装依赖)放在前面。
|
||
|
||
### 【基础】`CMD` 和 `ENTRYPOINT` 的区别?`COPY` 和 `ADD` 呢?
|
||
|
||
**参考答案**:`ENTRYPOINT` 指定容器启动时固定执行的程序;`CMD` 提供默认参数或默认命令,`docker run` 后面的参数会覆盖 `CMD`。两者都推荐使用 exec 形式(JSON 数组),程序作为 PID 1 才能收到信号。`COPY` 只复制本地文件;`ADD` 还能自动解压 tar 和下载 URL,行为不直观,一般用 `COPY`。
|
||
|
||
**追问**:`ENTRYPOINT` 用 shell 形式会有什么问题?→ shell 形式由 `/bin/sh -c` 启动,进程不是 PID 1,收不到 SIGTERM,无法优雅退出;应该使用 exec 形式(JSON 数组)。
|
||
|
||
### 【基础】容器里的数据怎么持久化?
|
||
|
||
**参考答案**:容器可写层的数据随容器删除而消失。持久化用数据卷(Docker 管理的命名卷,适合数据库)或绑定挂载(把宿主机目录挂进容器,适合开发时挂源码、挂配置文件)。
|
||
|
||
**追问**:命名卷和绑定挂载怎么选?→ 数据库数据用命名卷(由 Docker 管理,可移植);开发时挂载源代码用绑定挂载。
|
||
|
||
## 进阶
|
||
|
||
### 【进阶】怎么减小镜像体积、加快构建?
|
||
|
||
**参考答案**:多阶段构建,运行阶段只保留产物;选小的基础镜像(slim、alpine、distroless);把变化少的步骤(安装依赖)放前面,充分利用层缓存;合并 RUN 并在同一层清理缓存(如 `pip install --no-cache-dir`);用 `.dockerignore` 排除无关文件。
|
||
|
||
**追问**:多阶段构建为什么能减小体积?→ 编译工具和中间产物留在构建阶段,最终镜像只复制编译结果,基础镜像也可以用更小的 distroless / alpine。
|
||
|
||
### 【进阶】`docker stop` 时发生了什么?怎么做优雅退出?
|
||
|
||
**参考答案**:Docker 向容器的 PID 1 发送 SIGTERM,等待超时(默认 10 秒)后发送 SIGKILL。程序要监听 SIGTERM,停止接受新请求、处理完进行中的请求、关闭连接后退出。前提是程序本身是 PID 1(exec 形式启动),否则信号被 shell 吞掉。
|
||
|
||
**追问**:程序没有处理 SIGTERM 会怎样?→ 等待超时(默认 10 秒)后被 SIGKILL 强制杀掉,正在处理的请求会中断。
|
||
|
||
### 【进阶】Compose 中服务之间怎么通信?`depends_on` 能保证依赖服务已就绪吗?
|
||
|
||
**参考答案**:同一个 Compose 项目的服务在同一个网络里,用服务名作为主机名访问。`depends_on` 默认只保证启动顺序,不保证就绪;要配合 `healthcheck` 和 `condition: service_healthy`,应用本身也要有连接重试。
|
||
|
||
**追问**:怎么让应用等数据库真正就绪?→ 数据库配置 `healthcheck`,应用的 `depends_on` 使用 `condition: service_healthy`;应用本身也要有连接重试。
|
||
|
||
## 场景题
|
||
|
||
### 【场景题】容器启动后马上退出,怎么排查?
|
||
|
||
**参考答案**:`docker ps -a` 看退出码,`docker logs <容器>` 看输出。常见原因:主进程是后台进程(容器以前台进程为生命周期)、配置或环境变量缺失导致程序报错退出、二进制和基础镜像不兼容(CGO 程序放进 scratch)、权限问题(非 root 用户无权读文件)。可以用 `docker run -it --entrypoint sh 镜像` 进入容器手动执行排查。
|
||
|
||
**追问**:容器反复重启,日志一闪而过怎么办?→ 已退出的容器日志仍然可以用 `docker logs 容器ID`(`docker ps -a` 找到 ID)或 `docker compose logs 服务名` 查看;`docker inspect` 看退出码;也可以临时把启动命令改成 `sleep infinity` 进入容器手动启动程序排查。
|
||
|
||
### 【场景题】应用容器连不上数据库容器,怎么排查?
|
||
|
||
**参考答案**:确认连接地址用的是服务名而不是 localhost;两个容器是否在同一网络(`docker network inspect`);数据库是否真的就绪(健康检查、日志);端口是否是容器内端口而不是宿主机映射端口;用户名密码和数据库名是否和初始化时一致(数据卷已存在时环境变量不会重新生效)。
|
||
|
||
**追问**:容器里的 `localhost` 指的是什么?→ 指容器自己,不是宿主机也不是其他容器;连接其他容器要用服务名,连接宿主机的方式随平台不同(如 `host.docker.internal`)。
|
||
|
||
### 【场景题】怎么避免把密钥打包进镜像?
|
||
|
||
**参考答案**:不在 Dockerfile 里写密钥,不把 `.env` 等文件复制进镜像(`.dockerignore` 排除);运行时通过环境变量或密钥管理系统(Kubernetes Secret、云厂商的密钥服务)注入;构建时确实需要的密钥(如私有依赖仓库的 token)用 BuildKit 的 `--secret` 挂载,不会留在镜像层里;镜像推送前用扫描工具检查。
|
||
|
||
**追问**:构建时需要私有仓库的令牌怎么办?→ 使用 BuildKit 的 `--secret` 挂载,令牌只在构建步骤中可见,不会写入镜像层。
|