ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Docker入门实战:从环境搭建到容器编排的避坑指南

Docker入门实战:从环境搭建到容器编排的避坑指南 实习第一天带我的同事丢给我一台机器说“先熟悉一下 Docker把测试环境搭起来”。当时我连 Docker 和虚拟机的区别都说不清硬着头皮装环境光是 Docker Desktop 就折腾了一下午。后来泡在文档、社区帖子和一次次“删了重来”里总算把每天要用的 Docker 常用操作摸透了。回头看新人在实习阶段真正用到的操作并不复杂无非是环境安装、镜像拉取、容器生命周期、数据卷和网络、再加一个 compose 编排。这篇文章就围绕这几条线展开所有命令我都按“自己在工位上会怎么敲”来写顺便把容易踩的坑也标出来。1. 装好 Docker 再谈其他Windows 和 Linux 环境实战Docker 本身是一套客户端加服务端的架构你的日常工作大部分是在和docker命令打交道。但在敲第一条命令之前得先把环境搞对。实习阶段最常见的两种场景一个是自己电脑上装 Docker Desktop 做开发调试一个是公司服务器上装 Docker Engine 跑测试环境两边我都折腾过不少。1.1 先解决 Windows 上的 Docker Desktop 启动问题在 Windows 上装 Docker Desktop很多人以为下载安装包点下一步就行结果装完双击图标一直转圈最后弹一个virtualization support not detected。这个问题的根源不是 Docker 安装包出了问题而是 Windows 下跑 Linux 容器需要虚拟化支持。安装之前先做两件事第一打开“启用或关闭 Windows 功能”确认“适用于 Linux 的 Windows 子系统”和“虚拟机平台”这两项已经勾选第二打开任务管理器切到“性能”标签页看 CPU 那一栏的“虚拟化”是不是“已启用”。如果显示未启用就需要重启进 BIOS找到 Intel Virtualization Technology 或 AMD SVM Mode把它设置为 Enabled。公司电脑 BIOS 如果有密码锁就不要自己硬搞了直接找 IT 帮忙这不是靠软件能绕过的。Docker Desktop 现在默认用 WSL2 作为后端相比老牌 Hyper-V 方案WSL2 启动更快、内存占用更灵活和 Windows 文件系统交互也更自然。所以装完之后最好顺手设一下默认版本终端执行wsl --set-default-version 2再跑一次wsl --update把 WSL 内核更新到最新。第一次启动 Docker Desktop会有一个让你选 Linux 容器还是 Windows 容器的提示日常开发基本上都选 Linux 容器Windows 容器主要面向 Windows 工作负载别在这里选错。如果 Docker Desktop 已经装好启动也点了但执行docker version一直报failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen这类错误多半是 Desktop 自己没起来或者 WSL 卡住了。不要急着卸载重装先把 Docker Desktop 退掉然后执行wsl --shutdown等几秒再重新打开 Docker Desktop。我碰到过好几次“Docker 连不上 API”的问题绝大多数都是这一招解决的真正需要重装的情况极少。1.2 Linux 服务器下用 apt 安装 Docker Engine到了公司服务器上图形界面就没有了这时候装的是 Docker Engine不是 Desktop。实习第一天我在一台 Ubuntu 22.04 上操作最省事的安装方式是走 Docker 官方 apt 源。先清理旧版本避免系统里残留不兼容的包sudo apt-get remove docker docker-engine docker.io containerd runc然后安装证书相关工具并且把 Docker 官方 GPG key 导入系统sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg接着添加 apt 源文件echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null源配好后再更新索引安装 Docker 全家桶sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin装完之后用sudo systemctl enable --now docker设置开机自启sudo systemctl status docker看状态最后docker version确认客户端和服务端都显示正常版本号。这里要说一个点为什么服务器上不需要 Docker Desktop因为 Desktop 是把 Docker Engine、Kubernetes、图形管理界面打包在一起的桌面软件服务器上我们只需要 Engine 和命令行工具open 容器、看日志都靠 ssh 加终端图形界面反而是多余资源。国内好多教程会建议你装完立刻改镜像源这个思路是对的但前提是先把 Docker 本身跑起来再改否则 daemon 配置出错会直接导致服务起不来。另外如果你的服务器在网络受限的内网环境访问不了 Docker 官方源不要瞎改源地址硬试先问问运维同事有没有内部 apt 镜像地址或离线安装包团队内部通常早有标准方案。2. 镜像和容器每天高频使用的那些 Docker 命令环境终于通了接下来是真正的工作。实习阶段最常见的任务就是“把某个中间件跑起来”。比如测试环境要一个 Nginx、一个 MySQL、一个 Redis这时候你用到的就是镜像和容器两样东西。2.1 先理清镜像与容器的关系网上喜欢说“镜像就是模板容器就是实例”我在实习初期的理解是镜像是一个只读的文件系统快照里面装好了程序、依赖、配置和启动脚本容器是镜像被运行起来后形成的动态进程它有自己的读写层、网络命名空间和资源限制。有个挺贴切的类比镜像像烤饼干的模具容器像模具压出来的饼干。同一个模具可以压出无数块饼干同一个镜像可以启动多个容器容器可以被停止、删除、重新创建但镜像是不会因为容器删除而消失的。这也是我刚接触 Docker 时反复绕不清的地方明明docker rmi删不掉一个镜像提示“被容器使用”就是因为那个镜像已经起过容器哪怕容器已经停在那里。解决办法是先docker rm删除容器再docker rmi删镜像。另外要理解docker run不是单纯“创建容器”它同时完成了三件事检查本地有没有镜像没有就自动 pull用镜像创建一个容器启动这个容器。所以你第一次docker run nginx看到它在“拉镜像”并不是出了什么错。2.2 高频命令速查与参数详解实习期间我每天敲得最多的命令一张表就能列完操作命令示例说明查看本地镜像docker images列出所有已下载镜像拉取镜像docker pull nginx:stable指定 tag 可避免拉到 unexpected 版本删除镜像docker rmi nginx需要先删除依赖该镜像的容器运行容器docker run -d --name web -p 8080:80 nginx后台运行并映射端口查看运行中容器docker ps只显示 up 状态的容器查看所有容器docker ps -a包含停止的容器停止容器docker stop web停止进程但容器还在启动已存在的容器docker start web重新启动之前 stop 的容器删除容器docker rm -f web-f 表示强制删除运行中的容器实时看日志docker logs -f web调试容器时最常用的命令进入容器终端docker exec -it web bash如果镜像没装 bash 就换成 sh查看容器资源占用docker stats类似宿主机 top看 CPU/内存docker run的参数看着多但实习里翻来覆去就那么几个。-d是后台运行不加的话日志会一直刷屏而且一关终端容器就停了。--name给容器起个容易记的名字后面 stop、logs、exec 都能直接用名字比随机生成的 ID 好记太多。-p 8080:80表示把宿主机的 8080 端口映射到容器内的 80 端口冒号左边是宿主机右边是容器方向千万别搞反。-it常和exec配合表示分配一个交互式终端方便在里面执行命令。踩过的一个坑是很多官方镜像默认不是 root 用户进去之后目录权限可能不够。遇到这种情况不要慌先看镜像文档或环境变量大部分服务镜像都提供了-e参数来指定初始用户和密码不是非得进容器里去改文件。2.3 镜像拉不下来或太慢怎么办在公司网络环境下docker pull卡半天是非常常见的事。表现形式不一样有的卡在Waiting有的报 timeout有的干脆一直转圈。核心原因是默认的 Docker Hub 镜像仓库对我们来说网络延迟偏高。解决办法不是去下载安装包而是配置 registry-mirrors。Linux 上编辑/etc/docker/daemon.jsonWindows 上可以在 Docker Desktop 的 Settings - Docker Engine 里直接改 JSON配置文件格式是一样的{ registry-mirrors: [https://docker.example.com] }改完执行systemctl restart dockerWindows 上重启 Docker Desktop再用docker info查看 Registry Mirrors 字段是否生效。这里要提醒一句不要随便在网上去复制来路不明的镜像源地址尤其是要求你关掉安全校验的那种。优先用公司内部镜像仓库如果个人开发就选你本机网络环境测下来比较稳的几个公共镜像源配置好之后拉个nginx验证速度。还有一类场景是目标机器完全没有外网这时候镜像源也白搭。可以在一台能联网的机器上先把镜像打包出来迁移过去docker save -o nginx.tar nginx:stable docker load -i nginx.tarsave和load是打包镜像的标准姿势适合离线机房如果只是想导出容器里的文件系统可以用export和import但这两个概念不同实习面试时经常被问到别搞混。3. 数据卷、端口与网络让容器没那么“流浪”刚用 Docker 那几天我最大的错觉是“容器里的东西会一直还在”。直到有一次我在容器里改完配置重启容器发现改动全没了才真正理解“容器是无状态的”。这份代价教会我三件事数据要挂数据卷对外要映射端口容器之间通信要进自定义网络。3.1 数据不丢-v 和具名卷的正确用法Docker 容器的读写层是临时的容器删除后写在这个层上的数据也就没了。所以凡是需要持久化的数据比如数据库文件、日志文件、应用上传的文件都必须放到宿主机或者专门的数据卷里。最简单的写法是绑定挂载docker run -d --name mysql8 -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -v /data/mysql:/var/lib/mysql \ mysql:8.0这条命令里-v /data/mysql:/var/lib/mysql把宿主机目录/data/mysql挂到容器的/var/lib/mysqlMySQL 的数据库文件就落在宿主机上。容器删了之后数据还在/data/mysql里。还有一种写法是具名卷docker volume create mysql_data docker run -d --name mysql8 -v mysql_data:/var/lib/mysql mysql:8.0具名卷由 Docker 管理物理位置在/var/lib/docker/volumes/mysql_data/_data。它的好处是不依赖宿主机某个特定目录适合需要真正“卷起来”的场景。新手容易犯的错是把两者混用启动失败时去宿主机目录找数据发现是空的其实数据在 volume 里。我的建议是数据库这种核心数据要么用绑定挂载并写清楚宿主机路径要么统一用具名卷别一会这一会那。3.2 端口映射宿主机和容器之间的关系默认情况下你在容器里启动的 MySQL 监听的是容器的 3306 端口宿主机访问不到。要让外部能连上必须做端口映射-p 宿主机端口:容器端口。比如 MySQL 在容器内监听 3306你在命令里写-p 3306:3306外部就可以通过宿主机的 3306 访问容器里的 MySQL如果改成-p 3307:3306外部就要连宿主机的 3307 才能进到容器内的 3306。刚入门容易理解成“容器会占用宿主机的端口”其实更准确的理解是“你把宿主机上的某个端口‘接通’到容器的某个端口”。端口映射只是宿主机和外界之间的事情容器和容器之间的通信不需要-p因为它们在同一个 Docker 网络里可以直接用对方容器名或 IP 访问。验证端口映射是否生效可以先用docker ps看 PORTS 列如果显示0.0.0.0:3306-3306/tcp说明映射成功。然后从宿主机执行curl 127.0.0.1:3306或telnet 127.0.0.1 3306如果通了问题大概率出在业务层面而不是 Docker 层面。3.3 用自定义网络搞定 Redis 主从服务实习第三周我的任务是搭一套 Redis 主从。按照传统思路主节点和从节点肯定要互相通信但要是靠容器 IP一重启容器 IP 就变了脚本直接报废。正确做法是创建一个自定义 bridge 网络让容器在同一个网络里用容器名互相访问。先建网络docker network create redis-net然后分别启动主节点和从节点docker run -d --name redis-master --network redis-net -p 6379:6379 redis:7 redis-server --appendonly yes docker run -d --name redis-slave --network redis-net -p 6380:6379 redis:7 redis-server --slaveof redis-master 6379这条命令里两个容器都指定了--network redis-net所以从节点容器内部访问redis-master时Docker 的 DNS 会解析成主节点容器的 IP。主节点不需要给从节点暴露宿主机端口因为容器在同一网络里通信不经过宿主机。从节点映射6380:6379只是方便我们自己在宿主机上调试用。启动完成后从节点容器里执行docker exec -it redis-slave redis-cli info replication看到master_link_status:up就表示主从关系正常。这里有个很重要的经验如果你把两个容器放进默认的 bridge 网络互相用 IP 访问有时能通但用容器名解析基本不行因为默认 bridge 网络没有内置 DNS 解析。所以不要为了省事跳过自定义网络这个习惯会在以后部署微服务时帮你大忙。4. 从单容器到多服务docker-compose 整理启动参数命令越来越长维护越来越痛苦。尤其是要同时跑 MySQL、Redis、后端服务的时候每次都要敲好几条超长的docker run漏一个环境变量排查半天。实习第二周带我的同事给我看了一个 compose 文件我才意识到容器的启动配置不应该靠人脑记应该像代码一样放进仓库里管理。4.1 一个 compose 文件搞定开发环境docker-compose其实就是把docker run的参数用 YAML 写下来。比如我想在本地起一个 MySQL 加 Redis再跑一个后端应用可以建一个docker-compose.ymlservices: mysql: image: mysql:8.0 container_name: dev-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: app ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql networks: - dev-net healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s timeout: 3s retries: 10 redis: image: redis:7 container_name: dev-redis ports: - 6379:6379 networks: - dev-net app: build: ./app container_name: dev-app depends_on: mysql: condition: service_healthy redis: condition: service_started ports: - 8080:8080 environment: DB_HOST: mysql REDIS_HOST: redis networks: - dev-net volumes: mysql_data: networks: dev-net:这个文件对应了之前docker run里的几大核心参数image对应镜像ports对应-pvolumes对应-vnetworks对应--networkenvironment对应-e。如果你本地有 Dockerfilebuild: ./app可以让 compose 先帮你构建镜像再把构建出来的镜像启动成容器。我特别想说的是depends_on。没经验的人以为它代表“等前一个服务完全可用后再启动下一个”其实它默认只是控制启动顺序即“先启动 mysql再启动 app”。但 MySQL 可能启动到一半还没监听 3306app 起来连不上还是白搭。所以上面的示例里我给 MySQL 加了healthcheck然后让app的depends_on等待service_healthy这样才真正做到了“数据库就绪后再启动应用”。这个细节在生产环境里非常有用。4.2 compose 常用命令与排错compose 文件写好后启动整个环境只需要一条命令docker compose up -d后面跟着的是实习生最常用的一批命令docker compose ps # 查看所有服务状态 docker compose logs -f # 实时看所有服务日志 docker compose logs app # 只看 app 服务日志 docker compose exec app bash # 进入某个服务容器 docker compose down # 停止并删除容器数据卷保留 docker compose down -v # 连数据卷一起删慎用如果不小心改了 compose 文件里的端口或镜像再执行一次docker compose up -dcompose 会自动对比当前运行状态和期望状态把变化的容器重建。这是它比手写docker run舒服的地方配置是声明式的系统会自动收敛到最终状态。排错的时候第一看docker compose ps里容器状态是不是Up第二看docker compose logs 服务名。如果端口被占用日志里会出现bind: address already in use这时候去查宿主机是谁占了端口而不是改 compose 里的 container_name。新版 Docker 默认支持docker compose这条命令老项目里可能出现docker-compose带横杠那是独立 Python 包装一下也能用不过新环境优先用内置插件。5. 实习期最容易踩的 6 个坑操作命令背得再熟不等于部署时不出问题。我整理了一下实习期间自己和其他同事踩过的坑大部分都是环境或配置层面的写成速查表能省不少时间。5.1 先记住一个排查原则Docker 出问题先别慌把故障分成三层来分析。第一层是环境层也就是 Docker Engine、WSL2、虚拟化、系统服务状态第二层是容器层包括启动命令、镜像配置、环境变量、容器日志第三层是网络层包括端口映射、防火墙、自定义网络、DNS 解析。大多数报错都可以归到这三层里先定位问题在哪一层再动手改效率高得多。5.2 六个高频问题速查表现象可能原因处理方式Docker Desktop 启动失败提示 virtualization support not detectedBIOS 没开虚拟化或 WSL2 未启用开机进 BIOS 开启 SVM/VT-x开启 Windows 功能里的“虚拟机平台”和“适用于 Linux 的 Windows 子系统”docker 命令报 failed to connect to the docker api at npipe...Docker Desktop 没完全启动或 WSL 卡住先重启 Docker Desktop不行就执行wsl --shutdown再重新打开Linux 下 docker 命令提示 permission denied当前用户不在 docker 组执行sudo usermod -aG docker $USER重新登录后生效容器启动后立刻退出exit code 为 1启动命令错误或缺少必要环境变量先用docker logs 容器名看报错再检查环境变量宿主机访问不到容器端口没有-p映射或防火墙拦截docker ps查看 PORTS 列确认映射再从宿主机检查防火墙容器之间 ping 不通容器不在同一网络或使用了默认 bridge创建自定义网络启动容器时都加--network 网络名表格只是线索排错时一定要看具体日志。举一个我自己的实例实习生第一次部署 MySQL端口映射和环境变量都写了但容器一直退出状态码 1。当时我直接重新 pull 镜像反复试了几次都没用。后来冷静下来执行docker logs mysql8看到的报错是宿主机挂载目录/data/mysql权限不足容器内 MySQL 进程没有写入权限。原因是绑定挂载的目录是 root 创建的容器里 mysql 用户 uid 是 999自然不能写。解决方案是chown -R 999:999 /data/mysql或者干脆换成具名卷。这个案例说明容器起不来第一步永远是看日志不是重装。5.3 镜像拉取慢的补充排查镜像拉取慢不止是网络延迟的问题。有时是 DNS 解析异常明明配置了镜像源也没用。遇到这种情况先用docker info确认配置是否生效再在宿主机上ping一下镜像仓库域名看通不通。如果公司的网络环境特殊可能还需要问运维同事要公司内部的镜像仓库地址这个比公共镜像源更稳定。另外内存不足也会导致启动异常。用 Docker Desktop 跑 MySQL默认给虚拟机的内存可能只有 2GBMySQL 加上系统本身很容易 OOM现象是容器退出状态码 137。这时候去 Docker Desktop Settings 里把内存调到 4GB 以上问题通常会缓解。docker stats是观察资源占用的好工具养成习惯出问题前就能发现苗头。6. 写在最后两个习惯让我少踩了很多坑实习这段时间我最大的体会是Docker 操作不是靠背命令而是靠建模和执行习惯。每次敲docker run之前我都会先在脑子里过一遍这个容器的数据落在哪里端口映射是给谁访问的容器之间靠什么通信要不要自启动和健康检查。这个“先想后敲”的习惯帮我避免了很多次“容器跑了但数据没了”的返工。另一个习惯是保留现场。遇到容器启动失败我不会急着把容器删了重来而是先执行docker logs和docker inspect把报错记下来再操作。很多问题的答案就在日志的最后几行里重来了反而失去排查线索。如果你也想在实习里快速成长我建议从今天开始把碰到的每一次报错都当成一个考点而不是一个麻烦记录下来的排查过程就是最宝贵的经验。
返回列表