Docker踩坑记录:多服务容器化部署实践
最近,我花了不少时间在一个项目上,想着用 Docker 一键搞定 Elasticsearch、Kafka、MongoDB 和 PostgreSQL,这样听起来是不是超简单?
我也是这么想的,直到我真的动手了,才发现这个过程简直就是个....无底坑啊。
项目介绍
这个项目的环境需要用到很多服务:
- Elasticsearch:主要用于存储和处理数据。
- Kafka:消息队列,用于服务之间的数据传输。
- MongoDB:作为 NoSQL 数据库。
- PostgreSQL:作为关系型数据库。
这些服务之间还得用 Docker 容器相互连接,理论上 Docker Compose 一起启动就可以了——理论上。实际上各种问题一个接一个出现,一个比一个傻逼...(当然可能是我傻逼)。
1. 服务启动顺序问题
一开始我觉得应该没啥难度,直接用 Docker Compose 启动所有服务。但是很快我就发现,服务之间有依赖。。。比如我的主应用 app
要连接 ES,但 ES 启动慢,导致 app
先启动,结果连不上 ES,就提前跑了。
Docker 的 depends_on
选项只能保证容器启动顺序,没办法保证服务已经准备好。
最后我只能给每个服务都加上 healthcheck
,确保服务 真启动 后再继续启动下一个服务。比如 ES 的健康检查大概是这样:
elasticsearch:
...
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
interval: 30s
timeout: 10s
retries: 5
这样,app
只有在 Elasticsearch 完全准备好之后才会启动
2. Docker 网络搞不清楚
一个很容易忽略的问题是,在 Docker 里 localhost
不是你以为的那个"本地"。容器内部的 localhost
只指向容器自己,其他容器可不在这个范围内。我一开始全都用 localhost:9200
去连 Elasticsearch,结果根本连不上!CNXM!
解决办法是:别用 localhost
,用容器的服务名来做网络别名,比如 elasticsearch:9200
。容器互相通过服务名进行通信,这样就解决了网络连接的问题。
networks:
my-network:
driver: bridge
services:
app:
networks:
- my-network
elasticsearch:
networks:
- my-network
3. Elasticsearch 的安全配置
Elasticsearch 8.x 版本默认启用了 X-Pack 安全特性,这就意味着需要设置用户认证、启用 SSL 等等。
emmm最开始我没管它,结果 Elasticsearch 启动时各种报错,说必须开启 Transport SSL
Transport SSL must be enabled if security is enabled. Please set [xpack.security.transport.ssl.enabled] to [true]
由于这是本地开发环境,安全配置其实没那么必要。
所以我在开发环境中 直接禁用了安全功能,避免了很多不必要的配置麻烦:
elasticsearch:
environment:
- xpack.security.enabled=false
4. PostgreSQL 和 MongoDB 的启动问题
相对来说,PostgreSQL 和 MongoDB 稳定得多。但也有一些坑,比如 PostgreSQL 容器刚启动时要初始化数据表,有时候这个过程很慢,导致 app
连接时数据库还没准备好。为此,我也给 PostgreSQL 加了健康检查:
postgres:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 10s
retries: 5
这样,应用就不会在 PostgreSQL 完全准备好之前就去连它了。
5. Elasticsearch 索引创建问题
我们的项目需求是在 Docker 首次加载 时创建 long_term_memory_v2
的索引结构,因为这是一个嵌套性结构,如果给ES自动创建时会出错。
因此我之前就尝试了各种方法来搞定它:
- sh 脚本:用
sh
来调用 Python 脚本创建索引,失败。 - docker 脚本:通过 Docker 的
CMD
或ENTRYPOINT
调用 Python 脚本,失败。
每次尝试都挂了,甚至最后的某些方法搞得整个项目里的其他模块都无法正确启动,让我头疼不已。
最终,我用了一个相对稳定的方法:.env
文件配合 es_init
脚本来完成索引的初始化。
我在 docker-compose.yml
里为 Elasticsearch 配置了一个额外的容器:
es-init:
image: python:3.8-slim
environment:
- ES_HOST=http://elasticsearch:9200
volumes:
- ./init_es.py:/app/init_es.py
- ./config/long_term_memory_v2_mapping.json:/app/config/long_term_memory_v2_mapping.json
command: ["python", "/app/init_es.py"]
depends_on:
- elasticsearch
但这个方法也有个坑,如果 .env
文件没配置好,环境变量就会报错。鬼知道当时我是怎么解决的,总之就是各种环境变量要配合得天衣无缝,稍有不慎就会出错……
一开始索引创建还老是报错,错误信息类似:
Failed to create index 'long_term_memory_v2': {"error":{"root_cause":[{"type":"parse_exception","reason":"unknown key [long_term_memory_v2] for create index"}]}}
后来才发现问题出在索引结构的 JSON 文件上。
我以为可以在 JSON 里直接把索引名包进去,但实际上不行,Elasticsearch 只认直接从 "mappings"
开始的结构。
错误的格式:
{
"long_term_memory_v2": {
"mappings": { ... }
}
}
修复后:
{
"mappings": { ... }
}
最终修正了 JSON 文件的结构,索引才终于被正确创建。
经过这一轮折腾,项目总算可以正常工作了。
评价一下
emm 你要说docker方便的话,到也挺方便。但是在国内这种网络环境情况下,而且镜像站差不多都关完了,连依赖都要在其他电脑上打包好挂到服务器docker load。只能说工具是好工具,但是在这种网络环境下特别难用,直说了就是还不如直接跑项目,用个吊的DockerCompose。已老实,求放过。