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 的 CMDENTRYPOINT 调用 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。已老实,求放过。