做 Linux 运维和开发这些年,我见过太多人被两个东西劝退:一个是 vi,进去之后不知道怎么退出;另一个是 Docker,装完不知道容器和镜像到底啥关系。vi 是 Linux 环境里最底层的编辑工具,Docker 是现在部署应用绕不开的容器引擎,表面上一个管文件、一个管运行环境,实际干活的时候两者天天配合。这篇文章就把这两块按我的实战经验串起来讲,从 vi 的模式、高频操作、寄存器,到 Docker 的安装、镜像管理、容器生命周期、数据卷、Compose 编排,最后再落几个真实场景:怎么在容器里改配置、怎么写 Dockerfile、怎么用 Docker 跑 MySQL 和 Redis 主从。适合刚接触 Linux 的同学照着抄,也适合已经入行但有些细节一直没搞透的兄弟查漏补缺。
1. 为什么把 vi 和 Docker 放在一起学
1.1 一个运维老兵的日常写照
我平时的工作流大概是这样的:本地用 VS Code 写代码,提交到服务器之后,要么直接 ssh 上去用 vi 改配置文件,要么写个 Dockerfile 让 CI 去构建镜像。无论哪条路,vi 和 Docker 命令都会出现在同一个画面里。
举个例子,线上 MySQL 容器需要改一下 my.cnf 的字符集参数。我先docker ps找到容器,然后docker exec -it mysql bash进到容器里,结果发现镜像里根本没有 vim,只有最基础的 vi。这时候你要是不会 vi 的基础操作,连个文件都打不开,更别提改参数了。反过来,你用 vi 改完了配置文件,如果不了解docker cp和容器重启的机制,改完等于白改,容器一重启数据又回去了。
这两个技能是互相成就的:vi 解决“在 Linux 环境里怎么高效改文本”的问题,Docker 解决“改完之后怎么让它可靠地跑起来”的问题。我甚至面试别人的时候,也会先问 vi 的几个命令,再问 Docker 的基础命令,这两关过了,基本能判断这人有没有真在命令行环境下干过活。
1.2 两者结合最典型的三个场景
第一类是手动运维场景。服务器上出了告警,你要快速看一眼容器日志,改个环境变量,重启下容器。整个过程里docker logs、vi、docker restart三个命令循环出现。
第二类是镜像构建场景。写Dockerfile本身就是纯文本编辑工作,你要在 vi 里起草,然后用docker build构建,构建失败又得回到 vi 里改。改的过程中还得规律性检查RUN、COPY、ENTRYPOINT这些指令的写法,稍有差池镜像就会报错。
第三类是故障排查场景。容器起不来,最常见的操作就是用 vi 改挂载出来的配置文件,或者docker exec进容器看一眼进程状态。你可以没有花哨的 K8s 集群,但不能连 vi 和 Docker 命令都不熟。
明白了为什么两者要一起学,下面就把命令掰开了讲。
2. vi 命令精讲:从卡住到顺手
2.1 三种模式是理解 vi 的第一道门
很多新手第一次用 vi,敲了几个字母发现屏幕上什么都没出来,再一敲,文字开始乱跳,整个人就慌了。这是因为 vi 分三种模式,而你大概率处在“普通模式”下不知道自己在干嘛。
普通模式(Normal Mode)是刚打开文件时的状态。这个模式下你敲的每个字母都是命令,不是输入的文字。比如x表示删除当前字符,dd表示删除当前行,按i才进入插入模式去写字。
插入模式(Insert Mode)是你真正打字的状态。进入方式有几种:i在当前光标前插入,a在当前光标后插入,o在当前行下方新建一行并插入,O在当前行上方新建一行。很多人误以为打开 vi 就应该直接打字,其实不是。这个模式最大的问题就是“怎么退出去”,答案是按Esc键。
可视化模式(Visual Mode)用于选中文本。按v进入字符选中,按V进入按行选中,按Ctrl+v进入块状选中,可以一次选中多行做列编辑。选中后配合y复制、d删除、>缩进,效率比鼠标高得多。
| 模式 | 作用 | 进入方式 | 退出方式 |
|---|---|---|---|
| 普通模式 | 执行删除、复制、跳转等命令 | 打开文件默认进入 | 按v进入可视,按i进入插入 |
| 插入模式 | 输入文本内容 | iaoO等 | 按Esc |
| 可视化模式 | 选中文本做批量操作 | vVCtrl+v | 按Esc |
这个“模式”设计初看很反人类,但用顺手了就会发现它极其高效:你的手不用离开键盘去摸鼠标,编辑和命令切换就在几个按键之间完成。
2.2 高频移动与编辑命令清单
vi 的移动命令是效率的核心,记住下面这组就够了:
hjkl:左、下、上、右,不用多说。w/b:按单词向后 / 向前跳,适合长行文本。0/$:跳到行首 / 行尾。gg/G:跳到文件第一行 / 最后一行,前面加数字表示跳转指定行,比如10G跳到第 10 行。Ctrl+d/Ctrl+u:向下 / 向上翻半页,读日志文件时特别有用。f加字符:在当前行向后找到该字符并跳过去,比如fx跳到下一个x的位置。
编辑命令方面,我实际使用频率最高的是:
x:删除当前字符。dd:删除当前行;dG删除光标到文件末尾;dw删除一个单词。yy:复制当前行;p在光标后粘贴;P在光标前粘贴。u:撤销;Ctrl+r:重做。这句话值得默念三遍,vi 的撤销不是 Ctrl+z,是按小写u。ciw:删掉当前单词并进入插入模式,适合改代码里的变量名。r加字符:把当前字符替换成指定字符,不用进入插入模式。
我看到过不少面试题会出现“如何快速删除 5 行”的题,答案是5dd,数字加命令的组合几乎适用于所有 vi 命令,比如5yy复制 5 行、5加j往下跳 5 行。这个逻辑一旦掌握,效率立刻上一个台阶。
2.3 查找、替换与全局命令
查找是日常排查日志和定位错误的利器。在普通模式下按/,输入关键词,回车后按n跳到下一个匹配项,按N跳到上一个。比如排查服务报错,直接/ERROR就能快速定位。
替换命令是 vi 里最能体现“不装插件也能办公”的功能。基本语法是:
:s/old/new # 替换当前行的第一个匹配 :s/old/new/g # 替换当前行的全部匹配 :%s/old/new/g # 替换整个文件的全部匹配 :%s/old/new/gc # 全局替换,每次询问确认%表示整个文件,g表示全局,c表示确认。加c这个选项在小范围替换时特别稳,不会误伤不想改的地方。
很多人搜“vi g”,其实就是在问全局命令。vi 里真正的全局命令是:g,它能在匹配某个模式的所有行上执行指定命令。举个例子,你想删除所有包含DEBUG的行:
:g/DEBUG/d这个命令的意思是:找到所有匹配DEBUG的行并删除。比手动一行行dd快得多。还有更进阶的用法,比如把所有包含TODO的行前面加上注释符号,可以这样写:
:g/TODO/s/^/#/^表示行首,s/^/#/就是在行首插入#。:g命令配合替换,能省下大量重复劳动。
2.4 寄存器、宏与可视化操作
寄存器是 vi 里最被低估的高级功能。Vi 支持编号寄存器"0到"9,命名寄存器"a到"z,还有系统剪贴板寄存器"+。
默认的复制粘贴用的是无名寄存器,你复制了新的内容,旧的就被覆盖了。如果你复制一段内容后马上又复制另一段,第一段想粘贴就找不回来了。解决方法是使用命名寄存器:
"ayy # 把当前行复制到寄存器 a "ap # 粘贴寄存器 a 的内容当你在多个文件之间搬运内容时,这个功能简直救命。系统剪贴板可以用"+y复制、"+p粘贴,这样 vi 里面复制的内容可以直接粘到浏览器或其它编辑器里,反过来也行。
宏功能更适合批量处理重复操作。按q加一个字母开始录制,比如qa,然后正常执行一系列操作,再按q结束录制,最后用@a重放,5@a重放五次。我遇到过要批量给 100 行配置加前缀的场景,录一个宏然后重放 100 次,十几秒搞定。
可视化模式配合块操作也非常实用。按Ctrl+v进入列模式,用j/k选择多行,按I输入内容,再按Esc,你会看到输入的内容一次性插到了所有选中行的同一位置。比如给多行配置统一加注释,这是最顺手的办法。
2.5 退出 vi 的正确姿势与常见翻车现场
“退出 vi”是每个 Linux 新手都绕不过去的坎,也是最容易成为玩笑梗的环节。退出方式其实很固定:
:wq:保存并退出。:q:直接退出,文件没改动时可用。:q!:不保存强制退出。:x:保存并退出,和:wq功能类似。ZZ:普通模式下直接保存退出,不需要输入冒号。
还有一种特殊场景,文件只读或想放弃所有修改时,ZQ可以不保存直接退出。
我见过不少新手在 vi 里按下Ctrl+s,然后整个终端就没反应了,以为死机。其实Ctrl+s在终端里是“暂停输出”的快捷键,这时候画面冻住但 vi 还活着,按Ctrl+q就能恢复。这个坑特别隐蔽,网上搜“vi 卡死”有一半是这个原因。
另一个经典报错是E37: No write since last change (use ! to override)。意思是文件有改动,你没有保存就想退出。这时候要么先:w保存,要么用:q!强行放弃。
还有一个情况是在某些精简系统里,默认安装的是vi而不是vim。老版本的 vi 功能不全,方向键在插入模式下会输出 ABCD 字符,体验很差。遇到这种环境,先用vi --version看一下,装不了 vim 就尽量用h j k l移动,别依赖方向键。这个细节在嵌入式 Linux 设备上特别常见,学会基础移动键位,到哪儿都不慌。
3. Docker 命令实战:从装好到会用
3.1 安装与启动 Docker 的完整流程
Docker 的安装方式因发行版而异,但核心逻辑一致。Debian/Ubuntu 系用apt,CentOS/RHEL 系用yum或dnf。这里分享我最常用的一套方式,以 Ubuntu 为例:
sudo apt update sudo apt install -y docker.io sudo systemctl enable --now docker docker versionenable --now的作用是设置开机自启并立刻启动服务,这比“启一次忘一次”靠谱得多。安装完成后,用docker version确认 Client 和 Server 都在,如果只显示 Client 而 Server 报错,说明守护进程没起来。
装完后有个必经步骤:把当前用户加入docker组,避免每条命令都带sudo:
sudo usermod -aG docker $USER执行完必须重新登录或重启终端才生效。我不止一次看到有人加完组不重新登录,然后一直报permission denied,还以为是权限设置错了。
检查 Docker 是否完全正常,可以用两个命令:
sudo systemctl status docker docker infodocker info会输出容器数量、镜像数量、存储驱动、运行引擎等关键信息,也是排查问题第一手信息来源。启动命令本身也要记牢:systemctl start docker手动启动、systemctl enable docker设置开机启动,这两个是运维面试高频题。
3.2 镜像管理:拉取、查看、删减
镜像(Image)是容器的模板,容器(Container)是镜像的运行实例。这个概念如果没建立起来,后面所有命令都会学得很混乱。打个生活中的比方:镜像是做菜的菜谱,容器是按照菜谱做出来的一盘菜,你可以按同一个菜谱做无数盘菜,每盘菜都是独立的。
拉取镜像用docker pull,比如:
docker pull nginx:alpine docker pull mysql:8.0查看本地镜像用docker images,这个命令会列出仓库名、标签、镜像 ID、创建时间和大小。docker image是完整管理子命令,但日常直接用docker images就够了。
删除镜像用docker rmi,比如docker rmi nginx:alpine。删除前要确保没有容器在用这个镜像,否则会报冲突。清理悬空镜像(没有标签、被遗弃的中间层镜像)可以用:
docker image prune网络上有种说法是“镜像少用 latest 标签,尽量用明确版本号”。这个我非常认同,尤其是 MySQL、Redis 这类数据敏感的中间件,latest 一升级,数据格式和行为都可能变,踩了坑都不知道怎么回退。
镜像仓库方面,默认是 Docker Hub。如果拉取速度不理想,可以给 Docker 配置 registry mirror(镜像加速器)。不同云厂商都有自己的加速地址,具体配置写在/etc/docker/daemon.json里:
sudo vi /etc/docker/daemon.json改完重启 Docker:sudo systemctl restart docker。这个文件是 JSON 格式,写错一个标点都会导致 Docker 启动失败,所以改完后第一时间docker info验证一下。我自己就吃过亏,当时一个多余的逗号让整个守护进程起不来,排查了半天才发现是配置文件的锅。
3.3 容器生命周期:从 run 到 rm
容器操作是 Docker 的核心。docker run是最常用也最复杂的命令,需要在心里拆成几部分来理解:镜像、名称、后台运行、端口映射、数据卷、重启策略、环境变量。
一个典型的运行命令:
docker run -d \ --name nginx-web \ -p 8080:80 \ -v /opt/html:/usr/share/nginx/html \ --restart=always \ nginx:alpine拆开讲一下:
-d表示后台运行。如果不加,容器会以前台模式跑,终端关了容器就停。--name给容器起名字,方便后续docker exec和docker logs引用。-p 8080:80把宿主机的 8080 端口映射到容器的 80 端口。访问宿主机 IP 的 8080,就相当于访问容器内的 nginx。-v挂载数据卷,把宿主机目录映射到容器目录,目的是让数据存在宿主机上。--restart=always设置容器异常退出后自动重启。
查看运行中的容器用docker ps,查看所有容器(包括已停止的)用docker ps -a。每次看到一堆容器时,我都会先用docker ps -a排查,因为很多故障现场是“容器已经退出了”,只查运行中的容器会漏掉重要信息。
进入容器内部的命令是:
docker exec -it nginx-web bash-i表示交互,-t表示分配终端,bash是进入后要执行的 shell。如果容器里没有 bash,可以换成sh。这个细节等会儿还会再提,确实是高频坑。
看日志是排查问题的第一动作:
docker logs nginx-web docker logs -f --tail 100 nginx-web-f是跟随输出,--tail 100表示只要最后 100 行。容器崩溃后第一件事永远是看日志,不要瞎猜。
停止和删除:
docker stop nginx-web docker rm nginx-web如果要强制删除一个正在运行的容器,直接docker rm -f nginx-web。查看容器资源占用用docker stats,它跟 top 类似,能实时看 CPU 和内存消耗,排查性能问题很有用。
最后还有一个进出自由的问题:docker attach可以附着到容器的标准输入输出上,但使用起来很容易把终端卡住,我现在基本只用docker exec -it。attach再按Ctrl+c有时候会直接把容器进程杀掉,而exec只是开个新进程,不会影响主进程。这个差别在运维时要特别注意。
3.4 数据卷与网络:持久化和通信
容器是“用完即弃”的,容器一删,内部数据跟着没。要让数据留下来,必须用数据卷(Volume)或绑定挂载(Bind Mount)。
数据卷的几种用法:
docker volume create mydata docker run -d -v mydata:/var/lib/mysql mysql:8.0这里mydata:/var/lib/mysql的含义是:把名为mydata的卷挂载到容器内的/var/lib/mysql路径。卷由 Docker 管理,存在宿主机特定目录下。
绑定挂载则是直接把宿主机的某个绝对路径映射进容器:
docker run -d -v /home/user/mysql-data:/var/lib/mysql mysql:8.0好处是你能用 vi 直接查看宿主机路径下的文件,方便排查和备份。我个人在生产环境更推荐绑定挂载,因为一切都在明面上,vi直接就能看配置和日志。
挂载的时候还可以加:ro表示只读,比如:
docker run -d -v /opt/app/config:/app/config:ro nginx:alpine这种配置适合只给容器提供配置、不让容器反向修改宿主机文件的场景,能防止应用乱写文件。
网络方面,Docker 默认创建了三个网络:bridge、host和none。最常见的bridge网络里,容器通过 IP 通信,默认使用 NAT 访问外网。想让多个容器互相访问,最好创建一个自定义网络:
docker network create mynet docker run -d --network=mynet --name=web1 nginx:alpine docker run -d --network=mynet --name=web2 nginx:alpine在同一个自定义网络里,容器可以直接用容器名互访,比如web1里访问http://web2:80。这个特性在后面讲 Redis 主从的时候会直接用到,值得记住。
3.5 docker compose 一键编排
单容器用docker run还应付得来,一旦涉及多个容器,比如应用 + 数据库 + 缓存,命令又长又容易忘,这时候就该上docker compose。
Compose 的核心是一个 YAML 文件,把容器的配置声明式地写进去。一个最小可用的示例:
version: "3.8" services: web: image: nginx:alpine ports: - "8080:80" volumes: - ./html:/usr/share/nginx/html restart: always mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb volumes: - mysql-data:/var/lib/mysql ports: - "3306:3306" volumes: mysql-data:启动整个项目只需要:
docker compose up -d docker compose ps docker compose logs -f docker compose down注意新版本 Docker 推荐用docker compose(中间有空格)而不是旧的docker-compose。当然老机器上旧命令还能用,但新环境建议直接用新语法。
docker compose down会停止并删除所有相关容器,但卷不会自动删,数据还在。如果真想连数据一起清理,用docker compose down -v。这个命令我每次都要默念一遍:-v是删卷,慎用。
Compose 最大的价值是把“跑一套服务”这件事变成可复制的文件,不用再到处翻历史命令。把这个文件放仓库里,同事拉下来up -d就能起一样的服务。
3.6 典型部署案例:MySQL 8.0 与 Redis 主从
纸上谈兵不如实操一组典型案例。先看 MySQL 8.0:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=my-secret-pw \ -e MYSQL_DATABASE=testdb \ -v /opt/mysql8/data:/var/lib/mysql \ --restart=always \ mysql:8.0-e是设置环境变量,MySQL 镜像必需的两个是MYSQL_ROOT_PASSWORD和MYSQL_DATABASE。没有 root 密码镜像会直接报错退出,启动失败先用docker logs mysql8看输出,十有八九是某个环境变量没设置。
MySQL 8.0 默认使用caching_sha2_password认证插件。如果你用的是老版本的客户端工具连接,会报Authentication plugin 'caching_sha2_password' cannot be loaded。解决办法有两个:一是升级客户端驱动;二是在启动时加参数修改默认认证方式:
-e MYSQL_ROOT_HOST=% docker run ... -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql:8.0 --default-authentication-plugin=mysql_native_password镜像后面跟的--default-authentication-plugin=mysql_native_password会作为 MySQL 服务启动参数传进去,不需要进容器手动改配置文件。
再看 Redis 主从。建立主从有两种方式:命令行slaveof或配置文件。用 Docker 跑,最稳妥的方式是启动两个容器,让从节点连主节点。假设主容器叫redis-master,映射 6379 端口,从容器启动后进入容器执行:
docker exec -it redis-slave redis-cli slaveof redis-master 6379如果用容器名作为主机名,两个容器必须在同一个自定义网络里:
docker network create redis-net docker run -d --name redis-master --network redis-net -p 6379:6379 redis:7 docker run -d --name redis-slave --network redis-net redis:7 docker exec redis-slave redis-cli -p 6379 slaveof redis-master 6379验证主从状态:
docker exec redis-slave redis-cli info replication这个场景是网上搜“docker安装redis主从”最常遇到的需求,只要理解了“同网络 + 容器名互访”这两件事,配置过程其实非常短。
4. vi 和 Docker 的组合实操
4.1 在容器里改配置文件的几种靠谱方式
进了容器发现没有 vi 或者不太好用,这事我遇见太多次了。极简镜像比如alpine默认甚至没有 bash,只有/bin/sh。这时候改容器内文件有几种思路,按推荐程度排序:
第一,用挂载目录的方式,在宿主机直接改。最推荐的方案。如果你的容器启动时就挂载了/opt/app/config:/app/config,那根本不用进容器,直接在宿主机用 vi 改/opt/app/config下的文件,改完docker restart 容器名即可。数据在宿主机,文件操作也在宿主机,两边都舒服。
第二,临时进容器安装 vim。适合调试场景,比如基于 Ubuntu 的容器:
docker exec -it mysql bash apt update && apt install -y vimAlpine 系列则是:
apk add vim改完配置后重启容器或重启进程,容器一旦被删除,安装的 vim 也没了,所以这只适合临时处理,不该作为常规手段。
第三,用 docker cp 把文件拷出来改。把容器内文件复制到宿主机,用 vi 改完再复制回去:
docker cp mysql:/etc/mysql/my.cnf ./my.cnf.bak vi ./my.cnf.bak docker cp ./my.cnf.bak mysql:/etc/mysql/my.cnf docker restart mysql这种方式不需要容器里有编辑器,宿主机上有什么用什么,文件也能留下备份。但注意docker cp是重新拷贝文件,容器内原来的文件属主、软链接关系可能受影响。改配置文件后的属主最好确认一下,必要时chown修正。
4.2 用 vi 写 Dockerfile 的实战经验
Dockerfile 的写作质量直接决定镜像的构建成功率。我在 vi 里写 Dockerfile 时积累了几个习惯。
首先,Dockerfile 的关键指令就那几个:FROM指定基础镜像,RUN构建时执行命令,COPY复制本地文件进镜像,WORKDIR设置工作目录,EXPOSE声明端口,CMD或ENTRYPOINT指定启动命令。
CMD和ENTRYPOINT的区别是高频面试题。简单说,ENTRYPOINT定义容器启动时的主命令,CMD提供默认参数,docker run后面跟的参数会覆盖CMD而不是ENTRYPOINT。举个例子:
ENTRYPOINT ["nginx"] CMD ["-g", "daemon off;"]容器启动时实际执行的是nginx -g daemon off;。
用 vi 写 Dockerfile 时,我必然会开行号,这样构建报错时能迅速定位是哪一行:
:set nu还要养成分层阅读的习惯。Docker 构建时每一步RUN都会生成新层,把apt install之类需要缓存的步骤写在前面,把经常变动的COPY写在后面,这样改代码重新构建时能命中缓存,构建速度差好几倍。一个典型的优化写法:
FROM node:18 WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD ["npm", "start"]先复制package.json、安装依赖、再复制源码,每次改代码后依赖层不用重装。
另外我习惯在项目目录下建.dockerignore,写出哪些文件不要打进构建上下文,比如node_modules、.git、*.log。这能显著减小构建上下文体积,尤其是大项目,效果立竿见影。
4.3 Docker Desktop 在 Windows 上的启动问题
提到 Docker Desktop,是因为不少朋友在 Windows 上用它,结果碰到 “Docker Desktop failed to start because virtualization support wasn't detected” 的提示直接卡住。这个报错的意思是:你的系统没有满足虚拟化要求,Docker Desktop 需要虚拟化支持来运行 Linux 容器。
排查步骤一般是这样的:
第一步,进入 BIOS,确认 Intel VT-x 或 AMD-V 已经开启。不同品牌主板路径不同,但关键词就是 Virtualization / Virtual Machine。
第二步,在 Windows 功能里勾选“虚拟机平台”和“适用于 Linux 的 Windows 子系统”,也就是 WSL2 支持。Docker Desktop 现在默认基于 WSL2 后端,没有它肯定起不来。
第三步,打开“任务管理器-性能-CPU”,看右下角有没有“虚拟化:已启用”。如果是“已禁用”,问题大概率出现在 BIOS 或安全软件拦截。
第三步是很多人忽略的:检查是否有其它虚拟机软件或旧版超管理器占用了虚拟化资源。装了某些第三方虚拟化软件会和 Docker Desktop 抢资源,解决办法是把冲突的软件卸载或关闭其相关服务,只用 Docker Desktop 搭配 WSL2。
启动成功后,网络和卷的性能在 Windows 上有时会偏慢,尤其是大量小文件读写。尽量把项目代码放到 WSL2 虚拟磁盘里,而不是放在 Windows 的 NTFS 分区下,性能差距经常是成倍的。我刚从 Windows 迁移到纯 Linux 环境时,同样的 Docker 项目构建时间从五分钟缩短到一分钟,这种差异值得留意。
5. 高频报错与排查实录
5.1 vi 场景的经典坑
坑一:swap 文件存在。打开文件时提示Swap file ".my.cnf.swp" already exists!,说明之前有别的进程在编辑,或者上次异常退出留下了残留文件。处理方式是先看是谁在编辑,如果确定没人占用,直接按 D 删除 swap 文件或rm .my.cnf.swp。
坑二:Ctrl+s 导致界面冻结。前面提过,这是终端流控而不是 vi 的问题,按Ctrl+q恢复。这个坑在远程 ssh 和本地终端都会遇到,记住一次后面就很省事。
坑三:Backspace 在插入模式下删不了字符。多出现在老版本的 vi 或终端类型设置不对时。按:set backspace=indent,eol,start能缓解,或者干脆切换到普通模式用x删除再重新插入。
坑四:中文乱码。有时候打开 gbk 编码的文件,界面上一片乱码。先:set encoding=utf-8,再:e ++enc=gbk重新加载。实在不行就在外面用iconv转码后再打开。
5.2 Docker 场景的经典坑
坑一:Can't connect to Docker daemon。报错内容通常是permission denied或者Cannot connect to the Docker daemon at unix:///var/run/docker.sock。先sudo systemctl status docker看服务状态,如果没启动就启动;如果权限问题就检查是否加入了 docker 组,加入后是否重新登录。
坑二:端口被占用。启动容器时报Bind for 0.0.0.0:3306 failed: port is already allocated。用ss -lntp | grep 3306或lsof -i:3306查看占用进程,改映射端口或停掉占用进程。
坑三:exec 进入容器失败,提示executable file not found。常见于 Alpine 镜像没有 bash,你敲了docker exec -it 容器名 bash找不到 bash。改成sh就好,或者先docker exec -it 容器名 /bin/sh确认 shell 类型。
坑四:容器启动后立刻退出。先用docker logs看输出。比如 MySQL 没设置密码直接退出,Redis 没有配置文件直接退出。容器短暂存活本身就是问题信号,不要反复 restart,先把日志读明白。
坑五:docker compose 命令不存在。老版本可能只有docker-compose,新版本推荐用docker compose。执行前先docker compose version确认,别两个命令混着用。
我把这些问题整理成一张速查表,方便大家直接对号入座:
| 现象 | 排查命令 | 常见原因 |
|---|---|---|
| 容器启动失败 | docker logs 容器名 | 环境变量缺失、配置错误 |
| 端口冲突 | ss -lntp | grep 端口 | 宿主机端口已被占用 |
| exec 找不到 shell | docker exec ... /bin/sh | 镜像只有 sh 没有 bash |
| 连不上 Docker daemon | systemctl status docker | 服务未启动或用户无权限 |
| 数据丢失 | 检查挂载卷配置 | 未使用卷或绑定挂载 |
| 镜像拉取失败 | docker pull重试 | 仓库地址或网络问题 |
5.3 从面试题视角看这些命令的原理
现在不少 Linux 面试题喜欢考“为什么”而不是“是什么”。比如问你为什么容器能隔离?底层依赖 Linux 的 namespace 和 cgroup,进程、网络、文件系统都被隔离,资源占用受到限制。这些问题看起来跟命令无关,但理解了原理,用起命令来心里更有底。
再比如问你为什么docker exec -it 容器 bash能在容器里开 shell?因为 exec 会在容器的命名空间里启动一个新进程,指向容器内的文件系统。这也解释了为什么容器里没装的东西,exec 进去照样找不到。
vi 相关的面试题更多是考察操作熟练度。常见的有:如何退不出 vi、如何在 100 行文本里批量加注释(:1,100s/^/#/)、如何删除包含特定关键字的行(:g/关键字/d)。本质上考的都是模式切换和命令组合能力。
我自己面试时还会问一个问题:容器里的进程是 PID 1,为什么 PID 1 的退出信号处理和普通进程不同?这是个延伸题,能答上来的人通常对 Linux 进程模型理解比较深。用 Docker 跑 Nginx 或 MySQL 时,为什么有些镜像的CMD要特意写成nginx -g "daemon off;",就是因为容器要求前台进程保持 PID 1 存活。这些原理层面的内容,配合 vi 和 Docker 命令一起消化,知识体系才完整。
6. 几个让我少踩坑的日常习惯
最后分享几个我实际操作中保留至今的习惯,不一定写进文档,但确实帮我省了不少事。
第一个习惯是拿到一台新服务器,先顺手确认 vi 和 Docker 的基础状态。vi --version看一眼版本,docker version看 Client 和 Server 是否齐全。这两个动作加起来不到十秒,却能避免后面一堆莫名其妙的问题。
第二个习惯是给容器打名字。所有docker run我都强制带--name。没有名字的容器,docker logs、docker exec、docker rm都得去查容器 ID,一次两次还好,容器多了真的会乱。
第三个习惯是重要配置全部走挂载,不进容器内部改。容器内部改配置文件,版本升级、容器重建后改动就丢了,而且镜像本身应该保持“不可变”。把配置放在宿主机目录里,用 vi 改宿主机文件,然后docker restart,这套流程既安全又可追溯。
第四个习惯是删除操作前三思。docker rm -f、docker compose down -v、docker rmi这类命令,我通常在敲回车前会再用docker ps -a或docker volume ls确认一遍目标。尤其是卷的删除,数据没了就是真没了。
第五个习惯是遇到问题先看日志再动手。容器问题看docker logs,vi 问题看:set和文件属性,不要一上来就重启再重启。重启能暂时掩盖问题,但下次遇到同样的故障还是会花时间排查。
vi 和 Docker 看着是两个独立的知识域,实际用起来是同一套工作方式的左右手。把命令记熟只是第一步,更重要的是形成“在命令行环境下解决问题”的思路。你不需要背下所有参数,但至少要知道哪几个命令能救你于水火,以及出了问题第一眼该看哪里。把这些基础打牢,后面的容器编排、自动化部署学起来都会顺很多。