尧图精选

Docker实战指南:从安装、镜像容器到MySQL与Redis容器化部署

🕒 发布时间:2026/10/1 20:12:11 📁 来源:尧图网络
1. 安装Docker和Docker Desktop大多数人卡住的地方其实在启动之前我最早接触Docker是在一个需要同时跑MySQL、Redis、Nginx和两个Java服务的老项目上。当时机器环境乱得离谱Redis版本不兼容、MySQL权限混乱、Nginx配置被改得面目全非每次处理环境问题都要花掉半天时间。后来我直接在机器上装了Docker Desktop把所有中间件全部容器化从此环境问题基本在一分钟内解决。这也是我为什么一直建议身边的朋友只要你是做开发的无论后端、前端还是运维Docker都值得尽早用起来。不过很多人在安装这一步就栽了跟头。尤其是Windows用户最常见的就是启动Docker Desktop时报错提示virtualization support not detected或者是docker desktop failed to start because virtualization support is not enabled。这个报错看起来吓人其实原因非常集中——Docker Desktop依赖虚拟化技术来跑Linux容器你的机器没开虚拟化或者没装好相应的Windows功能。1.1 Windows下必须先确认的三件事先说结论在Windows上安装Docker Desktop本质上不是在安装一个软件而是在给Linux容器运行准备好完整的虚拟化环境。Docker Desktop底层借助WSL2或者Hyper-V来运行Linux内核这三样东西缺一不可。第一件事确认BIOS里的虚拟化开关。打开任务管理器切到性能标签看CPU那一栏右下角有没有虚拟化已启用。如果是已禁用就需要重启电脑进BIOS设置。不同的主板叫法不一样Intel平台一般叫Intel Virtualization Technology也叫VT-xAMD平台叫SVM Mode有的华为、联想、戴尔笔记本甚至会在默认状态下关闭。提示进BIOS的方法在台式机和笔记本上略有不同开机时反复按F2、Del或F10都能试。找不到选项的话直接搜自己主板型号加开启虚拟化。开启之后重启进系统再确认一下任务管理器里的虚拟化状态确实变成了已启用这一步经常有人漏掉。第二件事启用Windows功能。在Windows搜索框里输入启用或关闭Windows功能然后勾选这两项适用于Linux的Windows子系统也就是WSL2虚拟机平台Virtual Machine Platform勾完之后系统会提示你重启。注意这一步不是可选项。虽然Docker Desktop也支持用Hyper-V来跑但WSL2模式在启动速度和内存占用上明显更好而且已经是官方默认推荐的方式。我见过很多人只勾了WSL没勾虚拟机平台启动时依然报错所以两项都要勾。第三件事更新WSL内核。这一步很容易被忽略。Docker Desktop装好之后如果启动时始终卡在Engine starting十有八九是WSL内核太旧。在PowerShell里执行wsl --update更新完之后再重启Docker Desktop。如果系统里从来没用过WSL可以先执行wsl --status看看输出里有没有报错比如没有已安装的分发版。有报错的话执行wsl --install装一个默认的Ubuntu发行版再回来启动Docker Desktop。1.2 启动卡住、界面无反应时的排查思路我遇到过一个比较典型的场景一位同事装Docker Desktop双击图标之后界面长时间停留在Docker Engine starting左下角一直转圈。按照网上的说法他重新装了几遍都没用。后来我让他看了一下Docker Desktop的设置页面发现Use WSL 2 based engine处于开启状态但WSL里根本没有可用的发行版等于引擎的底层不存在。这种时候启动当然会失败。排查顺序很简单先确认WSL状态wsl --list --verbose看看有没有正在运行的发行版。没有的话去Microsoft Store装一个Ubuntu然后重新启动Docker Desktop。如果WSL状态正常但启动依然卡住打开任务管理器看内存占用Docker Desktop初始化时比较吃内存Memory不足也会一直卡在Starting状态。另外某些第三方安全软件会拦截Docker Desktop创建虚拟网卡表现也是启动失败或启动后镜像拉不下来。这个没有太好的办法只能尝试临时退出安全软件再启动。我自己用的做法是给Docker Desktop的核心进程加白名单。1.3 不想用Desktop的话命令行安装方式也很稳如果你用的是纯Linux服务器或者本机配置比较有限Desktop并不是唯一选择。Linux下的安装方式其实更干净curl -fsSL https://get.docker.com | bash这条命令会自动检测系统版本配置Docker官方源并安装。安装完成后sudo systemctl enable --now docker国内网络环境下如果这一步拉镜像特别慢需要配置镜像加速器。编辑/etc/docker/daemon.json写入{ registry-mirrors: [https://docker.m.daocloud.io] }然后重启Dockersudo systemctl restart dockermacOS用户如果不想装Desktop也可以直接用Homebrewbrew install docker brew install colima colima startColima是一个轻量级的Docker运行时口碑一直不错。它的用法和Desktop基本一致命令行完全兼容。2. 跑通第一个容器之前得先把这几个概念理顺装好Docker之后很多人的第一个动作就是去看教程复制粘贴跑一个nginx容器。跑完发现能访问然后就没然后了。过了几天想跑MySQL的时候照着网上的命令敲结果各种报错。问题出在哪里出在最基础的那几个概念没理顺——镜像和容器是什么关系端口映射是怎么工作的数据卷为什么必须挂。2.1 镜像是模板容器是运行实例把镜像类比成ISO安装盘容器就是装完系统跑起来的机器。你可以从同一个ISO安装出很多台机器每台机器互不影响同样你可以从一个镜像启动很多个容器它们之间彼此隔离。但和虚拟机不一样的是容器的开销小得多因为它不是完整模拟硬件而是直接复用宿主机的内核。实际操作中你会发现一条命令就够了docker run -d --name my-nginx -p 8080:80 nginx这条命令的拆解是这样的docker run从镜像创建并启动容器。-d后台运行不占用当前终端。--name my-nginx给容器起个名字之后操作都用这个名字。-p 8080:80把宿主机的8080端口映射到容器内的80端口。nginx要使用的镜像名。跑起来之后浏览器访问http://localhost:8080就能看到nginx的欢迎页。如果没看到先别急着怀疑端口映射90%的情况是容器压根没启动成功。执行docker ps -a看看状态再执行docker logs my-nginx看日志。2.2 端口映射的本质宿主机端口和容器端口是两回事很多新手在-p参数上犯迷糊觉得既然容器有端口为什么还要再映射一次。这里要明白一个关键点容器默认运行在一个独立的网络隔离环境里宿主机访问不到容器的IP。所以你必须告诉Docker帮我开一个宿主机端口把流量转发到容器的某个端口上。格式是-p 宿主机端口:容器端口。左边的端口如果被占用启动时Docker会直接报错提示端口已经被绑定。我经常看到有人问我的3306端口被本机MySQL占用了怎么办解决办法很简单左边换一个端口就行比如-p 3307:3306。容器内部依然是3306外部通过宿主机的3307访问两边互不干扰。2.3 数据卷不挂载的话删容器等于丢数据这是Docker使用里最惨痛的教训我身边至少三个人踩过。做个演示你就明白了docker run -d --name test-redis -p 6379:6379 redis docker exec -it test-redis redis-cli set name hello docker rm -f test-redis再重新docker run一个redis你会发现name这个键根本不存在。因为默认情况下容器内部的文件系统是临时的容器删除后里面所有数据一并消失。对于数据库类的容器这绝对是灾难。解决办法是挂载数据卷。常见做法是把宿主机目录映射到容器内目录docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDmy-secret-pw \ -v /data/mysql:/var/lib/mysql \ mysql:8.0这样MySQL的数据文件会落到宿主机的/data/mysql目录里以后无论容器怎么重建数据都还在。挂载目录这个动作在Docker里叫数据卷核心思想就是容器本身可丢弃但数据要留在宿主机上。我个人的习惯是凡是跑数据库、缓存、队列这类有状态的服务一律先把数据卷挂好再启动。2.4 进容器、看日志、拷文件的基础操作日常维护中这三条命令的使用频率最高# 进入容器的交互式shell docker exec -it my-nginx bash # 持续查看容器的实时日志 docker logs -f my-nginx # 从宿主机拷贝文件进容器 docker cp /local/file.txt my-nginx:/tmp/file.txtexec这条命令很重要排查容器内部的问题时几乎每次都靠它。比如容器能启动但服务报错你可以直接进去看配置文件、手动执行命令。需要注意的是有些容器基于Alpine系统里面没有bash只有sh那你就用docker exec -it my-container sh。3. 两个实战级例子MySQL 8.0和Redis主从的容器化搭建理论说再多不如亲手跑一遍。这一节我用两个最常见的使用场景——MySQL 8.0和Redis主从——来做实战演示。你如果在搜索引擎上搜过docker安装mysql8.0并使用和docker安装redis主从那你正在找的可能就是下边这些内容。3.1 用Docker跑起MySQL 8.0并顺利完成初始化先拉镜像docker pull mysql:8.0然后启动docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD你的密码 \ -e MYSQL_DATABASEapp_db \ -e MYSQL_USERapp \ -e MYSQL_PASSWORDapp密码 \ -v /data/mysql:/var/lib/mysql \ -v /data/mysql-conf:/etc/mysql/conf.d \ mysql:8.0这里有两个容易翻车的点。第一个点字符集。默认情况下MySQL 8.0拉起来后字符集可能还是latin1存中文容易乱码。建议在启动前先建好自定义配置文件。在宿主机的/data/mysql-conf目录下新建my.cnf写入[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci [client] default-character-setutf8mb4然后重启容器docker exec mysql8 mysql -uroot -p你的密码 -e SHOW VARIABLES LIKE character%;看到character_set_server的值是utf8mb4说明配置生效了。第二个点端口冲突。你本机如果已经装了MySQL3306端口就被占用了docker run会直接报错。这时候左边换一个宿主机端口就行比如-p 3307:3306。客户端连接时连3307完全不受影响。排查容器内MySQL状态的命令docker exec -it mysql8 mysql -uroot -p进去之后正常执行SQL。如果容器启动后两三秒就退出基本是配置文件写错或者挂载目录权限不对看docker logs mysql8的输出问题基本一目了然。3.2 搭建Redis主从两行命令搞定但容器间通信要先想清楚Redis主从的用处不用多说读写分离和高可用都靠它。用Docker搭建比手工装两个实例方便太多了。先建一个自定义网络让两个容器可以通过名字互相访问docker network create redis-net启动主节点docker run -d --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7 redis-server --appendonly yes启动从节点注意用--replicaof指定主节点的地址。在同一个自定义网络里容器名redis-master就是它的主机名Docker内置的DNS会自动解析docker run -d --name redis-slave \ --network redis-net \ -p 6380:6379 \ redis:7 redis-server --replicaof redis-master 6379测试验证一下docker exec -it redis-master redis-cli set foo bar docker exec -it redis-slave redis-cli get foo从节点返回bar说明主从同步正常。这里要提一个非常容易踩的坑如果你不自定义网络而是分别在默认bridge网络上跑主从然后从节点启动时填了主节点的IP看起来也能通但一旦容器重启IP变了从节点就再也连不上主节点了。而用自定义网络之后容器名解析是固定的重建容器只要同名从节点就能重新找到主节点。所以我建议凡是容器间需要通信的场景一律先docker network create建一个网络再用--network启动容器。4. Docker网络不通按这四层链路排查基本都能定位Docker相关的问题里网络问题大概占了四成。症状五花八门容器之间ping不通、宿主机访问不了容器端口、容器能启动但访问超时。很多人遇到这种问题就慌了开始盲目重启Docker和容器往往浪费大量时间。其实Docker的网络链路是层层叠加的只要按顺序排查大多数问题都能快速定位。4.1 第一层容器内的服务到底有没有在监听先问自己一个问题你访问不通的那个端口容器内部真的有服务在监听吗我见过很多次镜像拉下来了容器也起来了但访问不了的求助。进去一看容器里的进程压根没起来或者起来以后崩了。所以排查第一步不是查网络而是查容器状态docker ps -a docker logs 容器名如果容器处于Exited状态看日志找崩溃原因。如果还在运行可以进去手动验证docker exec -it 容器名 bash curl localhost:端口容器里自己访问自己通宿主机访问不通才进入第二层。容器里都不通那问题出在服务本身的配置上比如绑定地址是127.0.0.1还是0.0.0.0。不少应用默认只监听127.0.0.1这样即使端口映射配了外部也访问不到。配置文件里改成0.0.0.0即可。4.2 第二层端口映射是否正确容器内部通、宿主机外部不通优先检查映射关系。docker port 容器名这个命令会打印当前容器的端口映射列表。看到类似3306/tcp - 0.0.0.0:3306说明映射是存在的。如果映射正常但宿主机访问还是不通再看看是否有多个容器戴了帽子比如两个容器都映射了宿主机的3306端口后启动的那个其实没能绑定成功这也会导致访问异常。还有一个隐蔽问题Docker Desktop在Windows/macOS上端口映射和宿主机的回环地址绑定是比较特殊的。访问时应该用localhost而不是局域网IP如果非要用局域网IP访问需要在Docker Desktop设置里开启Allow user defined networks之类的相关选项不同版本名字略有差异。4.3 第三层容器之间的互通靠网络别用宿主机IP前面提到过容器和容器之间通信最好在同一自定义网络里用容器名访问。如果容器A访问容器B的宿主机映射端口有可能会通但这种依赖宿主机的转发链路很不稳定。更常见的问题是容器A ping 不通 容器B的IP但服务器上的防火墙没有拦安全组也放行了最后还是不通。这种时候要检查两个容器是不是在同一个网络里docker inspect 容器A | grep NetworkMode docker inspect 容器B | grep NetworkMode两边都是同一个自定义网络的话才能用容器名互访。如果一个是默认bridge、一个是自定义网络它们之间天然隔离开必须通过IP或端口映射才能通。注意docker inspect的输出很长建议用docker inspect --format{{.HostConfig.NetworkMode}} 容器名只提取网络模式看起来更清楚。4.4 第四层防火墙、云安全组和Docker的自作主张这一层的坑最隐蔽。现象是在宿主机本机用curl localhost:8080能通但用另一台机器访问服务器IP的8080就不通。一般有两个原因。第一个是云服务器的安全组策略没有放行该端口去云控制台看安全组规则添加入站规则。第二个是宿主机防火墙没放行Docker的端口sudo firewall-cmd --add-port8080/tcp --permanent sudo firewall-cmd --reload有些发行版还需要额外注意iptables的问题。Docker在启动时会修改宿主机的iptables规则如果你手动改过防火墙或iptables可能导致Docker的转发规则被清掉表现就是容器之间或者外部的流量进不来、出不去。遇到这种说不清原因的网络问题我试过最好的方法是重启Docker守护进程sudo systemctl restart docker这会重建Docker维护的网络规则不少临时性的网络异常能够直接恢复。5. docker compose把一堆命令变成一笔可复用的配置文件如果你只用docker run管理两三个中间件倒还好。一旦服务数量多起来比如MySQL、Redis、Nginx、后端、前端一起跑每次重启都要敲一长串命令而且容易记混参数。这就是docker compose存在的意义——把容器的启动配置写成YAML文件一条docker compose up全部搞定。5.1 为什么Compose值得专门学一下Docker Compose做的事情本质上就是把docker run的参数结构化写进一个docker-compose.yml文件里。好处有三点所有配置都可见、可维护、可提交到Git仓库。新同事接手项目时不用再翻文档猜端口看配置文件就一目了然。一组服务可以用一条命令统一启动、统一停止、统一查看状态。举一个实实在在的例子一个普通的Web项目需要MySQL和Redis做支撑。文件内容大致长这样version: 3.8 services: mysql8: image: mysql:8.0 container_name: app-mysql restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: app_db volumes: - /data/mysql:/var/lib/mysql - ./mysql-conf:/etc/mysql/conf.d redis: image: redis:7 container_name: app-redis restart: always ports: - 6379:6379 command: redis-server --appendonly yes volumes: - /data/redis:/data然后在同目录下执行docker compose up -d两条命令启动两个服务比写两条docker run加维护各自参数的体验好太多了。之后查看状态docker compose ps看日志docker compose logs -f进入某个服务docker compose exec mysql8 bash停止所有服务docker compose down注意down会删除容器但不会删除数据卷。如果你连数据卷一起清理需要加-v参数。生产环境中慎用down -v这命令会连库带文件一起清空。5.2 depends_on的坑它只管启动顺序不管服务是否就绪新手配置Compose时经常听到depends_on这个词以为用上它就能保证服务依赖可靠。实际上depends_on只保证容器启动的先后顺序不保证依赖服务已经可以接受请求。举个例子你定义一个后端服务依赖MySQLservices: backend: image: my_backend depends_on: - mysql8系统会先启动mysql8再启动backend但不会等MySQL初始化完成。MySQL初始化通常需要几秒到几十秒如果后端启动时立即去连数据库就会报连接失败。解决办法通常是在应用层做重试或者在启动脚本里等待端口可用。很多项目里后端代码自带数据库连接重试逻辑所以这种情况不算致命但你要心里清楚depends_on迁移的是启动顺序而不是就绪状态。5.3 把Compose文件拆成多个环境的技巧实际项目里开发环境和生产环境的配置往往不同。比较常见的做法是用.env文件保存差异变量然后在Compose文件里引用services: mysql8: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}在项目根目录放一个.env文件MYSQL_ROOT_PASSWORDdev_password_123这样同一个Compose文件开发环境用自己的.env生产环境用另外一份。注意.env文件不应该提交到Git仓库应该加入.gitignore数据库密码这种敏感信息留在仓库里是隐患。6. 日常使用中真正能提升效率的若干细节到了这个阶段基础的镜像、容器、网络、Compose你都能用起来了接下来要聊的是我在日常使用中积累的一些润物细无声的细节。每一个单看都不是惊天动地的大功能但组合起来能让你的Docker使用体验从及格变成舒服。6.1 定期清理垃圾别等磁盘满了再折腾容器跑久了宿主机上会积累大量无用的镜像层、停止状态的容器和悬空的匿名卷。看磁盘空间docker system df这个命令能直观展示你当前占用了多少空间。然后# 清理停止的容器、悬空网络镜像 docker system prune # 更彻底一些清理所有未使用的镜像和构建缓存 docker system prune -a --volumessystem prune是安全操作一般不会误删正在使用的东西。但-a --volumes会清理所有未被容器引用的镜像、数据卷和构建缓存执行前想清楚。6.2 给日志加上限防止日志文件把磁盘撑满容器日志默认会无限增长尤其是那种高频打印日志的应用几天就能吃掉几十GB。从源头控制更稳妥。启动容器时加上日志轮转参数docker run -d --name app \ --log-opt max-size10m \ --log-opt max-file3 \ my_app_image意思是最多保留3个日志文件每个最大10MB。Compose里对应services: app: image: my_app_image logging: driver: json-file options: max-size: 10m max-file: 3如果是已经跑起来的容器修改日志参数需要重建容器但你可以先通过truncate -s 0把当前日志文件清空再用docker logs确认应用没有因为日志文件被清空而报错。大多数容器日志驱动是json-file截断日志文件不会影响容器运行。6.3 调试容器内部环境时用临时的初始化命令排查问题时经常需要临时起一个容器做网络连通性测试或者环境检查比如看看特定端口能不能通。我常用的方式是docker run --rm -it --network app-network nicolaka/netshoot \ ping app-mysql--rm让容器在退出后自动删除不留垃圾。nicolaka/netshoot这个镜像里预装了tcpdump、ping、curl、nc等网络调试工具比在业务容器里现场装工具方便太多。这种用完即焚的临时容器是排查容器网络问题的利器。6.4 镜像体积越小拉取和启动越快这是老生常谈的话题但值得再次强调。同样的应用Java基础镜像可能上百MBAlpine版本可能只有几十MB多阶段构建可以把编译工具链只留在构建阶段最终镜像里只保留运行时的产物。构建镜像时的习惯直接影响日常使用体验尤其在网络环境一般的情况下小镜像带来的加速是立竿见影的。大致思路是# 构建阶段 FROM golang:1.22 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED0 go build -o app . # 运行阶段 FROM alpine:latest COPY --frombuilder /app/app /usr/local/bin/app CMD [app]最后跑出来的镜像不包含golang工具链基础层也很薄推拉速度会快很多。6.5 容器重建的通用套路无论改了什么环境变量、更换了镜像版本还是调整了挂载卷最终你都会面临容器重建。我的标准操作是五步经过长期验证基本不会出问题先备份如果是数据库容器先做一下数据导出或者确认数据卷里有完整数据。停旧容器docker stop 容器名。删除旧容器docker rm 容器名。注意这一步和上一步是两回事只知道stop不删除直接再跑同名的会报名字冲突。用新参数重新启动。确认状态docker ps看是否在运行docker logs --tail 50 容器名看是否有报错。这套流程看起来没有技术含量但比直接docker rm -f安全得多因为你有机会在删除前备份也有时间观察新容器是否正常。顺手分享一个我个人的习惯给每一个容器都固定名称不要用随机生成的容器名。有名字的容器在排错时可以直接看见业务语义日志、exec、inspect都不需要先查ID。用Docker时间久了会发现它的核心价值其实不是虚拟化或者隔离而是环境的可复制性——昨天在一台机器上验证过的环境今天在另一台机器上一模一样地跑起来。这种确定性省掉的环境折腾时间远超学习Docker本身花掉的时间。希望这篇文章里写到的安装细节、中间件搭建、网络排查和Compose使用能让你少走一些弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →