Docker这几年越来越火,容器化部署已经成为了主流,而Docker Compose是Docker官方的容器编排工具,可以用一个配置文件定义和运行多个容器,非常适合开发和测试环境,也可以用在简单的生产环境。我这两年用Docker Compose做了不少项目,踩了不少坑,也积累了一些实战经验。今天就来分享一下Docker Compose编排实战中踩过的那些坑,以及对应的解决方案。

一、配置文件的坑

第一个坑是配置文件的问题,Docker Compose的配置文件是YAML格式的,YAML看起来简单,但是坑很多。

第一个坑是缩进。YAML对缩进非常敏感,必须用空格缩进,不能用Tab,而且缩进的数量要正确,多一个少一个都会导致解析错误。我刚开始用的时候,经常因为缩进不对,配置文件解析失败,查了半天才发现是缩进的问题。建议大家用支持YAML语法高亮和校验的编辑器,比如VS Code,能及时发现缩进错误。

第二个坑是版本号。Docker Compose的配置文件有版本号,不同的版本支持的功能不一样,比如version 2和version 3的语法就有一些区别,有些配置在不同版本里的写法不一样。很多人复制网上的配置文件,不注意版本号,结果运行的时候报错,或者某些配置不生效。建议大家用最新的稳定版本,而且写配置文件的时候,先看一下对应版本的官方文档,不要盲目复制网上的旧配置。

第三个坑是端口映射。端口映射的格式是"宿主机端口:容器端口",很多人写反了,或者只写了一个端口,导致端口映射不对。还有就是,多个容器不能映射同一个宿主机端口,不然会冲突,启动失败。建议大家写端口映射的时候,注意格式,而且规划好端口,不要冲突。

第四个坑是命令和入口点。很多人在配置文件里用command或者entrypoint覆盖容器默认的命令,但是写法不对,导致容器启动失败。比如command应该是一个数组或者字符串,如果是数组的话,每个元素是一个参数,不要把整个命令写成一个字符串,不然会被当成一个参数。还有就是,如果用了entrypoint,command会被当成entrypoint的参数,而不是覆盖默认命令,这点要注意。

二、网络的坑

第二个大坑是网络的问题,Docker Compose的网络看起来简单,但是坑很多。

第一个坑是容器之间的通信。很多人刚开始用Docker Compose的时候,以为容器之间可以用localhost或者127.0.0.1互相访问,结果发现不行。因为每个容器都是独立的网络命名空间,localhost指的是容器自己,不是其他容器。正确的做法是用服务名来访问,Docker Compose会自动做DNS解析,服务名就是容器的主机名,比如你有一个服务叫web,一个服务叫db,web容器里就可以用db:3306来访问数据库。

第二个坑是网络模式。Docker Compose默认会创建一个桥接网络,所有服务都在这个网络里,可以互相访问。但是有时候你需要用其他网络模式,比如host模式,或者none模式,或者连接到外部网络,这时候要注意配置。特别是host模式,容器和宿主机共享网络,这时候端口映射就没用了,而且容器里的服务会直接占用宿主机的端口,要注意端口冲突。

第三个坑是端口暴露和端口映射的区别。很多人搞不清expose和ports的区别,expose只是声明容器暴露了哪些端口,不会映射到宿主机,只有在同一个网络里的其他容器可以访问;ports是把容器的端口映射到宿主机,外部可以访问。如果你只是容器之间通信,不需要外部访问,用expose就行,不要用ports,这样更安全,也不会占用宿主机端口。

第四个坑是外部网络的连接。有时候你需要把Docker Compose的服务连接到已经存在的外部网络,比如另一个Docker Compose项目创建的网络,这时候要在配置文件里声明外部网络,而且要注意网络名要正确。我之前踩过一个坑,外部网络名写错了,结果Docker Compose自动创建了一个新的网络,服务连不到外部网络的容器,查了半天才发现是网络名写错了。

三、数据卷的坑

第三个大坑是数据卷的问题,特别是有状态的服务,比如数据库,数据卷配置不对,数据就丢了。

第一个坑是数据卷的类型。Docker的数据卷有几种类型,最常用的是命名卷和绑定挂载。命名卷是Docker管理的,存在Docker的目录里,不需要指定宿主机路径;绑定挂载是把宿主机的某个目录挂载到容器里,需要指定宿主机路径。很多人搞不清这两种的区别,随便用,结果出问题。我的建议是,数据库这种需要持久化的数据,用命名卷,Docker管理,更安全,权限也不容易出问题;配置文件、日志这种需要在宿主机直接访问的,用绑定挂载,方便查看和修改。

第二个坑是权限问题。绑定挂载的时候,经常会遇到权限问题,容器里的进程没有权限读写挂载的目录,因为宿主机的目录权限和容器里的用户ID不匹配。比如MySQL容器,它的进程是用mysql用户运行的,用户ID是999,如果你挂载的宿主机目录是root用户的,mysql用户就没有权限写,容器就启动失败。解决方案是,要么把宿主机目录的权限改成和容器里的用户一致,要么用命名卷,Docker会自动处理权限,要么在Dockerfile里指定用户ID,和宿主机一致。

第三个坑是数据卷的删除。很多人用docker-compose down停服务的时候,加了-v参数,会把数据卷也删掉,结果数据库的数据就没了,哭都来不及。所以一定要注意,docker-compose down -v会删除数据卷,除非你真的想删数据,不然不要加-v参数。还有就是,重要的数据一定要定期备份,不要只靠数据卷,数据卷也可能出问题。

第四个坑是容器重启后数据丢失。很多人发现,容器重启之后,数据没了,查了半天,发现是没有配置数据卷,数据存在容器的可写层里,容器删除或者重建的时候,可写层就没了,数据就丢了。所以有状态的服务,一定要配置数据卷,把数据存在数据卷里,不要存在容器里。

四、环境变量和配置的坑

第四个坑是环境变量和配置的问题,特别是应用的配置,很多人喜欢用环境变量传配置,但是用不好就会出问题。

第一个坑是环境变量的传递。Docker Compose里可以用environment配置环境变量,也可以用envfile从文件里读取环境变量,但是很多人搞不清这两种的优先级,或者变量名写错了,导致环境变量没传进去。还有就是,环境变量在容器启动的时候就固定了,修改环境变量必须重启容器才能生效,不要以为改了envfile容器里的变量就变了。

第二个坑是敏感信息的处理。很多人把数据库密码、API密钥这些敏感信息直接写在docker-compose.yml里,然后提交到Git仓库,结果敏感信息就泄露了。正确的做法是,敏感信息不要写在配置文件里,用环境变量或者envfile传递,而且envfile不要提交到Git,要加到.gitignore里。更安全的做法是用Docker的secrets或者专门的配置管理工具。

第三个坑是配置文件的热更新。很多人希望修改配置文件之后,容器里的服务能自动加载新配置,不用重启容器。但是Docker Compose默认是不支持热更新的,你修改了配置文件,必须重启容器才能生效。如果需要热更新,可以用一些工具,比如watchtower自动更新镜像,或者用配置中心,服务主动拉取配置。或者对于Nginx这种支持reload的服务,可以进入容器手动reload,不用完全重启。

第四个坑是多个环境的配置。很多项目有开发、测试、生产多个环境,每个环境的配置不一样,很多人复制多份docker-compose.yml,然后手动改,结果改着改着就乱了,某个环境的配置忘了改,出问题。正确的做法是用多个配置文件,基础配置写在docker-compose.yml里,环境相关的配置写在docker-compose.override.yml或者docker-compose.prod.yml里,启动的时候用-f参数指定多个配置文件,Docker Compose会自动合并。

五、部署和运维的坑

第五个坑是部署和运维的问题,Docker Compose用在生产环境的时候,有很多需要注意的地方。

第一个坑是容器的重启策略。生产环境一定要配置restart: always或者unless-stopped,这样容器挂了会自动重启,服务器重启之后容器也会自动启动,保证服务高可用。很多人忘了配置重启策略,结果容器挂了就挂了,服务就断了,还要手动去重启。

第二个坑是日志的问题。Docker默认的日志驱动是json-file,会把容器的stdout和stderr存成JSON文件,但是默认没有大小限制,时间长了日志文件会越来越大,把磁盘占满。我之前就踩过这个坑,服务器磁盘满了,查了半天发现是Docker的日志文件有几十G。解决方案是,配置日志驱动的大小限制,比如max-size和max-file,限制单个日志文件的大小和数量,或者用其他日志驱动,比如syslog、fluentd,把日志集中管理。

第三个坑是资源限制。生产环境一定要给容器配置资源限制,比如cpus和memory,限制容器最多能用多少CPU和内存,不然某个容器出问题,比如内存泄漏,会把整个服务器的资源占满,影响其他容器。很多人忘了配置资源限制,结果一个容器出问题,整个服务器都挂了。

第四个坑是健康检查。Docker Compose支持healthcheck,可以配置健康检查命令,定期检查容器是否健康,不健康的话会标记为unhealthy,可以配合重启策略自动重启。生产环境建议给重要的服务配置健康检查,这样容器出问题了能自动发现,自动重启,提高可用性。

第五个坑是更新部署。更新服务的时候,不要直接docker-compose up -d,这样会先停掉旧容器,再启动新容器,中间会有 downtime。如果是多实例的服务,可以用滚动更新,先启动新容器,确认健康了再停旧容器,实现零停机更新。Docker Compose本身对滚动更新的支持有限,复杂的部署建议用Docker Swarm或者Kubernetes。

六、写在最后

Docker Compose编排实战:那些年我踩过的坑。

以上就是我这两年来用Docker Compose踩过的一些坑,以及对应的解决方案,包括配置文件、网络、数据卷、环境变量、部署运维等几个方面。当然,Docker Compose的坑远不止这些,还有很多细节需要在实际使用中慢慢积累。

总的来说,Docker Compose是一个非常好用的容器编排工具,简单、灵活、功能强大,非常适合开发、测试环境,以及简单的生产环境。但是它也不是银弹,复杂的微服务编排、大规模集群管理,还是建议用Kubernetes。不过对于大部分中小项目来说,Docker Compose完全够用了,而且更简单,运维成本更低。

技术是踩坑踩出来的,每个人都是在不断踩坑、不断填坑的过程中成长的。希望我的这些踩坑经验能帮到大家,让大家少走弯路,用好Docker Compose。

最后用一句话结尾:"Docker虽好,可不要贪杯哦。"容器化是趋势,但是也要根据实际需求选择合适的工具,不要为了用Docker而用Docker,适合自己的才是最好的。