首页 / 资讯中心 / 文章详情

Linux服务器Docker部署实战:从环境检查到排障完整指南

Linux服务器Docker部署实战:从环境检查到排障完整指南 ★ FEATURED ARTICLE
手里刚拿到一台 Linux 服务器大多数人的第一反应都是“先装个 Docker 吧”。这个想法本身没错但我见过太多人卡在这条看似简单的路上安装命令从网上抄了一堆结果镜像拉不下来容器起一个挂一个磁盘没几天就爆满3306 端口冲突改来改去还是连不上数据库。问题不在于 Docker 难而在于网上那些零散的教程只告诉你怎么敲命令没告诉你为什么这么敲、踩坑了怎么查。这篇文章我按自己真实部署的顺序来写从拿到一台 Linux 服务器到 Docker 稳定跑起业务再到能自己排查故障、管理多容器完整走一遍。每步都会讲清楚背后的理由也会把我踩过的坑原原本本摆出来。适合两类人看一是刚买服务器准备部署第一个应用的新手可以照着走二是已经装过 Docker 但总在运维环节打转的初学者排查思路这部分应该能帮上忙。1. 动手前先查四样东西发行版、内核、磁盘、系统时间1.1 发行版决定你后续用什么命令很多人拿了服务器就直接搜“docker 安装教程”根本没看自己的系统是什么。等你把 CentOS 7 的命令贴到 Ubuntu 上执行必然报错。Docker 官方对不同发行版提供了不同的软件仓库安装方式差距很大所以第一步永远是确认发行版。cat /etc/os-release看NAME和VERSION_ID字段就行。常见场景我列个对照表方便你对号入座发行版包管理器安装源常见版本情况CentOS 7yumdocker-ce.repo内核 3.10Docker 可用但特性有限CentOS Stream / Rocky / AlmaLinux 9dnfdocker-ce.repo内核较新适合生产Ubuntu 22.04 / 24.04aptDocker 官方 apt 源教程最多坑最少Debian 12aptDocker 官方 apt 源与 Ubuntu 同源基本一致openEuler / 麒麟 / 统信dnf 或 yumDocker 官方源或发行版自带源部分版本需要适配 old kernel顺带提一句现在国产 Linux 在服务器领域用得越来越多。这类系统如果是基于 CentOS 或 openEuler 改造的安装 Docker 的思路和标准发行版差别不大重点看内核版本是否满足要求。实在不确定的就用下面这个命令看内核。1.2 内核版本和容器运行机制的关系Docker 容器能跑起来依赖 Linux 内核提供的几个底层机制namespaces隔离进程和文件、cgroups限制资源、overlayfs镜像分层存储。这些在 2.6.x 时代的内核上要么没有、要么不完整。老教程里说“内核 3.10 就能装 Docker”这话没错但那是针对 2015 年前后的 Docker 1.x 版本说的。现在跑 Docker Engine 24我建议内核至少 4.18 以上越新越省心。uname -r lsmod | grep overlayuname -r看版本号lsmod | grep overlay看内核有没有加载 overlay 存储驱动模块。如果 grep 结果为空不代表不能装Docker 会自动尝试其他存储驱动但性能和功能会有损失。真在旧内核上遇到存储驱动问题可以看docker info里Storage Driver那行正常情况下应该是overlay2。1.3 磁盘分区给 /var/lib/docker 留足空间Docker 的镜像、容器可写层、日志和数据卷默认全放在/var/lib/docker下面。很多云服务器默认系统盘只有 40G装几个镜像、跑两三个带数据库的容器就满了。所以动手前务必看一下分区情况df -h lsblkdf -h看现有文件系统使用率lsblk看有没有独立数据盘。如果/或/var所在分区不大先别急着装参考后面第 3 节的>x509: certificate has expired or is not yet valid新手看到这个报错十有八九会以为网络出了问题又是换源又是重启服务折腾半天结果发现是时间不对。所以装 Docker 之前先看一眼时间date如果偏差明显装个时间同步服务。CentOS/Rocky 上用 chronyDebian/Ubuntu 上系统默认一般带 systemd-timesyncd也可以用 chronyyum install -y chrony systemctl enable --now chronyd # 或者 apt install -y chrony systemctl enable --now chrony同步完成后date再确认一次。这一步花不了两分钟但能帮你避免无数个“莫名其妙”的证书报错。2. 安装 Docker 的两条路线软件仓库安装与离线包安装2.1 在线安装装的是 docker-ce不是旧版 docker在线安装是绝大多数人的选择。这里有一个关键提醒不要直接执行yum install docker或apt install docker。系统自带源里的docker包是很老版本的容器引擎功能落后不说和现代镜像的兼容性也差。我们要装的是 Docker 官方维护的docker-ceCommunity Edition。CentOS / Rocky 系列的操作yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginUbuntu / Debian 系列的操作apt-get update apt-get install -y ca-certificates curl install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release echo $VERSION_CODENAME) stable /etc/apt/sources.list.d/docker.list apt-get update apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin注意我多装了两个东西docker-buildx-plugin和docker-compose-plugin。前者是现代化镜像构建工具后者是第 6 节要用的 Compose 插件。别省后面都需要。如果你所在网络访问download.docker.com比较慢可以把软件源地址替换成国内公开的镜像站点这属于常规操作。替换前先用curl -I测一下原地址通不通不通再换。2.2 离线安装内网服务器的标准姿势总有一批服务器在内网没法直接访问外网。这时候在线安装走不通但 Docker 依然能装而且有两种成熟方案。方案一是搞一台有外网的机器下载好 rpm 或 deb 包再拷进内网。用yumdownloader或dnf download把依赖包全部拉下来然后yum localinstall安装。这个方案的优点是能拿到完整的依赖树缺点是版本被锁死后续升级要重复整套操作。方案二是直接用 Docker 官方发布的静态二进制包不依赖包管理器。去官方 release 页面下载docker-xx.x.x.tgz这个过程可以在有外网的机器上完成然后把压缩包传到内网服务器tar -xzf docker-xx.x.x.tgz cp docker/* /usr/bin/然后手动创建 systemd 服务文件cat /etc/systemd/system/docker.service EOF [Unit] DescriptionDocker Application Container Engine Afternetwork-online.target firewalld.service Wantsnetwork-online.target [Service] Typenotify ExecStart/usr/bin/dockerd ExecReload/bin/kill -s HUP $MAINPID LimitNOFILEinfinity LimitNPROCinfinity LimitCOREinfinity TasksMaxinfinity Delegateyes KillModeprocess Restarton-failure [Install] WantedBymulti-user.target EOF systemctl daemon-reload systemctl enable --now docker静态二进制方案在内网和离线场景非常稳没有依赖地狱拷过去就能跑。我建议准备一台“跳板机”专门干这种下载打包的活能省很多事。2.3 启动服务与验证装好之后先启动再设置开机自启systemctl start docker systemctl enable docker验证一下安装是否完整docker version docker infodocker version会同时显示 Client 和 Server 两部分信息如果 Server 部分正常显示版本号说明 daemon 已经跑起来了。docker info能看到存储驱动、镜像加速器、数据目录这些关键配置。有个常见的困惑我必须提一下很多新手会在 Linux 服务器上搜索“Docker Desktop”然后看到docker desktop failed to start because virtualisation support wasnt detected之类的报错以为自己安装出了问题。其实 Docker Desktop 是 Windows/macOS 上的方案依赖本机虚拟化组件Linux 服务器上直接跑原生容器引擎根本不需要 Desktop 那套东西。你在 Linux 服务器上要装的就是我上面说的docker-ce装完直接命令行操作完事。如果你systemctl start docker失败不要慌先看日志journalctl -u docker --no-pager -n 50大部分启动失败问题日志里都能看到明确原因。按我的经验排在前几位的无非是systemd 服务文件缺失静态二进制没建服务、/var/lib/docker权限不对、防火墙或者 SELinux 拦截。看到具体报错再对症下药比盲目重装有效得多。3. 服务起来之后的三件套镜像加速、数据目录、权限与日志3.1 配置镜像加速拉取速度立刻不一样装好 Docker 后第一件事我建议先配镜像加速。默认情况下 Docker 从 Docker Hub 官方仓库拉镜像网络环境不同速度差异很大。这不是 Docker 本身的问题而是物理链路的问题。解决办法是让 Docker 优先从能快速访问的镜像站点拉取。配置文件是/etc/docker/daemon.json没有就自己新建。一个典型的配置长这样{ registry-mirrors: [ https://docker.m.daocloud.io, https://hub-mirror.c.163.com ] }公共加速地址变动比较频繁上面只是示例实际使用前你可以先用curl测一下哪个通、哪个快。阿里云容器镜像服务会给每个账号分配专属加速地址登录控制台能看到那个是相对稳定的选择。改完配置文件重启 Dockersystemctl daemon-reload systemctl restart docker docker info | grep -A 5 Registry Mirrorsdocker info里能看到当前生效的加速器列表确认配置加载了再开始拉镜像。3.2 数据目录迁移别等磁盘满了再搬前面说过/var/lib/docker默认在系统盘。如果你的服务器有独立数据盘强烈建议一开始就把 Docker 的数据目录迁过去。迁移本身不复杂关键是别丢数据、别改错配置。完整步骤如下systemctl stop docker mkdir -p /data/docker rsync -avzP /var/lib/docker/ /data/docker/然后编辑/etc/docker/daemon.json加上>{ data-root: /data/docker }重新启动并验证systemctl start docker docker info | grep Docker Root Dir看到输出Docker Root Dir: /data/docker就说明切换成功。旧目录/var/lib/docker确认无误后再删。为什么不建议用软链接把/var/lib/docker指到别处我试过多数情况下也能跑但 Docker 升级、systemd 环境清理等场景下软链接容易引发权限、SELinux 上下文之类的隐性故障。直接改>usermod -aG docker $USER newgrp docker重新登录后执行docker ps不再需要 sudo 就说明成功了。注意docker 组权限很大基本等同于 root。这主要是因为 docker CLI 要和 daemon 通信而 daemon 本身有极高的系统权限。所以这个操作只给可信的用户做别图省事给一堆人加尤其是服务器上有非技术同事共用账号的场景一定要慎重。日志滚动是另一个容易被忽略但极其重要的配置。Docker 默认把容器日志完整记录在/var/lib/docker/containers/容器ID/下不设上限的话一个日志量大的容器几天就能吃掉几十 GB 磁盘。在daemon.json里加上这个配置{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }意思就是单个日志文件最多 10 MB保留最近 3 个文件轮转删除旧的。这个配置只对新建容器生效改完要记得docker rm旧容器重新docker run或者在 Compose 文件里全局配置。我遇到过不止一例“服务器磁盘莫名其妙满了”的运维事故最后查到都是容器日志在作怪这一条配置能直接帮你把这个事故类型消灭在萌芽阶段。4. 第一个业务容器完整跑通Nginx 起手再接一个 MySQL4.1 镜像拉取别只盯着 latest配置好环境后来点实际的。先从最简单的 Nginx 开始热身docker pull nginx:stable-alpine注意我特意没有用latest标签。latest虽然是默认标签但它是“最近一次被推送的版本”语义上不可控。生产环境里镜像标签一定要精确到具体的版本或变体。stable-alpine表示稳定版基于 Alpine Linux体积小很多几百 MB 和几十 MB 的差距在日常拉取和部署时体感非常明显。拉取完成后可以用docker images查看本地镜像列表会看到镜像的仓库名、标签、镜像 ID、大小和创建时间。我再多一句嘴镜像来源一定要小心。尽量只从 Docker Hub 官方镜像或其他可信仓库拉取不要为了“某个功能好用”就去某些未知来源下载镜像容器里跑的东西对主机有极高权限来源不明的镜像可能直接让你服务器沦陷。4.2 启动 Nginx逐行拆解 docker run 参数镜像拉好了运行容器docker run -d \ --name web \ -p 8080:80 \ -v /srv/nginx/html:/usr/share/nginx/html:ro \ --restart unless-stopped \ nginx:stable-alpine这行命令每个参数都有讲究-d后台运行终端不会被占据。--name web给容器起固定名字。不起名字的话Docker 会随机生成一个让人头大的名字后续管理非常麻烦。-p 8080:80把宿主机的 8080 端口映射到容器的 80 端口。宿主端口和容器端口不需要一致这里用 8080 是故意避开和其他服务冲突。-v /srv/nginx/html:/usr/share/nginx/html:ro把宿主机的 HTML 目录挂载进容器:ro表示只读。这样你可以直接在宿主机改网页文件不用进容器也防止容器内进程篡改文件。--restart unless-stopped容器异常退出时自动重启除非是你手动 stop 的。这是生产环境最常用的重启策略。启动后用curl http://localhost:8080看看能不能返回 Nginx 欢迎页。能的话你的第一个容器就跑通了。这里有个隐藏问题挂在到容器的宿主机目录如果权限不对容器内进程可能读不到。而在启用了 SELinux 的系统上挂载目录还需要修正上下文chcon -Rt svirt_sandbox_file_t /srv/nginx/html不加这句你可能会看到容器正常启动但访问页面报 403。这种问题光看容器状态是看不出来的一定要学会看日志后面详细讲。4.3 MySQL 8.0 容器环境变量、数据卷和连接不上的完整排查跑通了 Nginx再来一个有“生产级要求”的MySQL。这一步很多人栽跟头我把常见问题一次讲清楚。启动 MySQL 8.0 容器的标准姿势docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourPassword \ -e MYSQL_DATABASEappdb \ -v /srv/mysql/data:/var/lib/mysql \ mysql:8.0这里有两个关键点第一MYSQL_ROOT_PASSWORD环境变量必须显式设置。官方 MySQL 镜像有个行为如果不设置这个变量它会随机生成一个 root 密码并且打印到容器日志里。你要是没注意到后面怎么连都连不上还以为是网络问题。显式设了密码至少不会有这种“隐藏变量”问题。第二-v /srv/mysql/data:/var/lib/mysql是保命配置。MySQL 的数据文件在容器内如果不挂数据卷容器一旦删除所有数据库数据全部消失。这不是假设我第一次不挂数据卷部署 MySQL后来因为调整参数删了容器重建直接丢了一整套测试数据那种感觉不想再体验第二次。容器起来后进容器执行 SQLdocker exec -it mysql8 mysql -uroot -p如果这台服务器上同时装了系统自带的 MariaDB 或旧版 MySQL3306 端口很可能被占用容器启动会直接报错Bind for 0.0.0.0:3306 failed: port is already allocated排查方式ss -lntp | grep 3306看到底是谁占用了端口。要么停掉占用的服务要么把映射改成别的端口比如-p 33060:3306。如果你在宿主机上能连 MySQL但其他机器连不上按这个顺序查宿主机防火墙是否放行firewall-cmd --list-all看 3306 端口没有就firewall-cmd --add-port3306/tcp --permanent firewall-cmd --reload。云服务器安全组阿里云、腾讯云等控制台里3306 入方向规则是否添加了。这一步最容易被忽略因为防火墙查完没问题但安全组在更外层。MySQL 用户的主机权限执行select user, host from mysql.user;确认你的连接用户 host 不是localhost如果是需要授权时带上%或指定客户端 IP。补充一个 MySQL 8.0 特有的兼容问题8.0 默认身份认证插件是caching_sha2_password一些老版本的客户端甚至部分旧语言的驱动不支持这个插件。如果你发现“Docker 里 MySQL 起来了程序却连不上”报错里有caching_sha2_password字样可以在容器内给业务账号改成传统认证方式ALTER USER appuser% IDENTIFIED WITH mysql_native_password BY YourPassword;当然更长远的选择是升级客户端驱动但应急切换认证方式也没毛病。5. 运维排障容器秒退、端口冲突和磁盘告警的排查链路5.1 容器启动后秒退按这个顺序查不要瞎试我在新手阶段犯过最大的错就是容器一挂立刻删了重新 run反复几次还是挂最后束手无策。正确做法是有一套固定的排查顺序从外到内一步步缩小范围。第一步看容器状态和退出码docker ps -a状态列会显示Exited (1)或Exited (137)之类。退出码很重要137 通常表示被 SIGKILL 杀死往往是内存超限或者被 OOM killer 处理了1 通常是应用自身报错比如启动参数不对、配置文件缺失。第二步看应用日志docker logs 容器名 --tail 200这是最重要的信息源。容器里跑的任何服务启动失败时基本都会往日志输出原因这里往往比任何猜测都靠谱。第三步用 inspect 看容器配置细节docker inspect 容器名重点看ExitCode、Mounts、RestartPolicy、Cmd、Entrypoint。比如挂载的主机目录权限有问题、启动命令写错、重启策略没设对这些在 inspect 输出里一目了然。第四步看宿主机资源层面有没有问题docker stats --no-stream journalctl -xe dmesg | tail -50如果容器反复被 OOM killer 杀掉宿主机内存又很小dmesg里通常有Out of memory相关记录。讲一个我真实踩过的 MySQL 容器秒退案例完整还原排查链路我在docker run时挂载了/srv/mysql/data:/var/lib/mysql容器启动后一两秒就退出。docker ps -a显示Exited (1)。docker logs mysql8里看到[ERROR] [MY-010262] ... Cant create/write to file /var/lib/mysql/ibdata1 (OS errno 13 - Permission denied)OS errno 13 是权限拒绝。原因很典型MySQL 容器内部进程是以 uid 999 的用户运行的而宿主机上/srv/mysql/data目录属于 root容器内用户没有写权限。修复chown -R 999:999 /srv/mysql/data同时如果这台机器开了 SELinux还要修正目录上下文chcon -Rt svirt_sandbox_file_t /srv/mysql/data然后删掉失败的容器重新docker run一次成功。整个过程不到五分钟但如果不按日志线索排查靠重装猜原因可能一晚上都耗进去。5.2 磁盘占用分析掌握这组命令不用天天心惊胆战容器跑多了以后磁盘告警会成为你最大的“夜间骚扰源”。预警比补救重要先学会看空间被谁吃了docker system df这条命令专门查看 Docker 自身的磁盘使用情况镜像占多少、容器可写层占多少、数据卷占多少、构建缓存占多少一目了然。再配合传统的du命令看明细du -sh /var/lib/docker/containers/*/ # 看容器日志目录大小 du -sh /var/lib/docker/overlay2/ # 看镜像分层数据日常清理用两个命令就够了。保守一点只想清理没有用的悬空镜像和无效数据docker system prune激进一点把没在用的镜像、停止的容器、无用网络、构建缓存全部清掉docker system prune -a -f注意-a会把所有不被运行的容器引用的镜像都删掉包括你可能以后要用的。我现在已经养成了习惯docker system prune -a之前先docker images看一眼哪些镜像要保留或者干脆用docker image prune -a --filter until72h这种带时间过滤的删法。5.3 给容器上资源限额防止一台容器拖死宿主机容器技术最让人放松警惕的一点是它默认不限制资源。一个容器可以把宿主机的 CPU 和内存吃满然后触发 Linux 内核的 OOM killer把宿主机关键进程干掉。这在生产环境是重大事故。所以给每个容器设资源上限应该成为一种肌肉记忆docker run -d \ --name web \ -m 512m \ --cpus 1 \ -p 8080:80 \ nginx:stable-alpine-m 512m限制容器最多用 512 MB 内存--cpus 1限制最多用 1 个 CPU 核心。启动后用docker stats观察实际占用如果业务确实需要更多再逐步调高。这里有个细节-m设置后容器内进程如果超过这个值会被内核杀掉启动参数里最好配合--oom-kill-disable一起考虑。但我不建议一上来就禁用 OOM kill因为这意味着容器内存会一直涨到宿主机不可用。通常我会先限制内存观察容器内应用的稳定性再决定要不要调整限制值。6. 多容器联动直接用 Compose一个文件描述一整套服务6.1 为什么单容器命令在项目化之后不够用当你手上只有一个 Nginx 或者一个 MySQL一条docker run就能应付。但真实项目往往是“前端容器 后端容器 数据库容器 缓存容器”的组合这时候再用散落的docker run命令管理你会发现几个问题启动顺序要自己控制、容器之间网络要手动建、参数依赖要靠脑子记。我经历过一次服务器重启后手动按顺序起七八个容器重启错一个整个环境就废了。Compose 的解决思路非常简单用一个 YAML 文件描述整个服务栈包括镜像、端口、环境变量、数据卷、网络、依赖关系然后一条命令全部拉起。文件可以放进 Git 管理换台机器部署就是一份文件的事。顺带说一句网上经常有人搜“docker 安装 redis 主从”这个需求在 Compose 里特别适合做——定义两个 redis 服务配好密码和depends_on关系主从关系很快就起来。6.2 docker compose 插件与 docker-compose 命令的区别如果你在 2023 年以后的 Docker 版本上操作直接用docker compose即可这是 Docker 官方的 Compose v2 插件安装 docker-ce 时通过docker-compose-plugin一并装好。而老教程里常见的docker-compose是独立二进制用 pip 或单独下载安装目前官方推荐的是前者。两个命令的参数基本一致但注意中间有没有横杠docker compose是两个词docker-compose是一个带横杠的词。看教程时注意版本不要混着抄。6.3 一个 nginx mysql redis 的生产雏形文件下面这个 Compose 文件是我日常项目起步常用的骨架你直接抄过去改改就能用services: web: image: nginx:stable-alpine ports: - 8080:80 volumes: - /srv/web/html:/usr/share/nginx/html:ro restart: unless-stopped db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: YourPassword MYSQL_DATABASE: appdb volumes: - db_data:/var/lib/mysql restart: unless-stopped redis: image: redis:7-alpine command: [redis-server, --appendonly, yes] volumes: - redis_data:/data restart: unless-stopped volumes: db_data: redis_data:和docker run对比着看ports对应-pvolumes对应-venvironment对应-erestart对应--restart。不同的地方在于db_data和redis_data是命名的数据卷由 Docker 管理比直接挂宿主机目录更干净也不容易受 SELinux 上下文影响。在这个文件所在目录执行docker compose up -dDocker 会自动创建 Compose 项目专属的网络三个容器在同一个网络里互相直接用服务名访问。比如你的后端应用连接数据库主机名写db端口写3306连接 Redis 写redis:6379不需要关心容器的 IP 是多少。其他常用命令docker compose ps # 查看项目内所有容器状态 docker compose logs -f web # 跟踪某个服务的日志 docker compose down # 停止并删除容器但数据卷保留 docker compose down -v # 连同数据卷一起删除谨慎使用docker compose up -d之后如果改了 YAML 文件再执行一次同样的命令Compose 会自动对比并重建有变更的服务这是它特别好用的地方。文件校验用docker compose config能检查 YAML 语法和字段合法性。6.4 一个容易踩的格式坑YAML 缩进和 TabCompose 文件的语法是 YAML缩进就是语法本身。我见过不少新手把复制来的配置文件缩进改乱或者无意间用了 Tab 字符结果docker compose up直接报mapping values are not allowed in this context之类的错误。解决办法很简单用两个空格做缩进统一用空格不要用 Tab。我所有 Compose 文件都养成了一个检查习惯先docker compose config校验确认没问题再up。还有一个小技巧生产环境不要把明文密码写在 Compose 文件里提交到 Git。可以用.env文件然后在 YAML 里引用environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}.env文件加入.gitignore密码就不会泄露到版本历史里了。最后说点个人体会。我部署 Docker 这几年最大的教训不是安装命令记不熟而是对“容器随时可能被删”这件事缺乏敬畏。数据卷、日志滚动、资源限额、重启策略这些配置看起来是“锦上添花”实际都是保命条款。如果你刚开始部署就把daemon.json和 Compose 骨架一次配好后面每加一个服务都是复制粘贴改改端口的事。真等磁盘报警、数据丢失以后再回头补配置代价就大了。希望这篇流程能让你少走我走过的弯路。
阅读完成 · 觉得有帮助?
咨询建站