Built 时的服务器-DB 通信问题

Server-DB Communication Issue at Built

设置

考虑这个相对简单的 docker-app,使用 fastapi 作为服务器,使用 couchdb 作为数据库。

这是项目结构

├── app.py
├── compose
│   ├── couchdb.Dockerfile
│   └── server.Dockerfile
├── docker-compose.yml
├── .env  # Global vars e.g. DB credentials
├── Pipfile  # For local dev
├── Pipfile.lock  # Same
└── requirements.txt

这是我的app.py

import couchdb
from fastapi import FastAPI, HTTPException
import os


def _load_db_client():
    _user = os.environ["COUCHDB_USER"]
    _password = os.environ["COUCHDB_PASSWORD"]
    _host = os.environ["COUCHDB_HOST"]
    _port = os.environ["COUCHDB_PORT"]
    _client = couchdb.Server(f"http://{_user}:{_password}@{_host}:{_port}")
    del _user, _password, _host, _port
    return _client


def _get_or_create_db(_client: couchdb.Server, db_name: str) -> couchdb.Database:
    if db_name in _client:
        return _client[db_name]
    print("Creating DB", db_name)
    return _client.create(db_name)


couch: couchdb.Server = _load_db_client()
db_user = _get_or_create_db(_client=couch, db_name="user")

app = FastAPI()

这是我的 docker 个文件:

### docker-compose ###
version: "3.4"

# This help to avoid routing conflict within virtual machines:
networks:
  default:
    ipam:
      driver: default
      config:
        - subnet: 192.168.112.0/24

services:

  couchdb:
    restart: unless-stopped
    build:
      context: .
      dockerfile: compose/couchdb.Dockerfile
    expose:
      - 5984
    ports:
      - "59840:5984"
    env_file:
      - .env

  server:
    restart: unless-stopped
    build:
      context: .
      dockerfile: compose/server.Dockerfile
    expose:
      - 8080
    ports:
      - "8080:8080"
    env_file:
      - .env

### couchdb.Dockerfile ###
FROM couchdb:latest

### server.Dockerfile ###
FROM python:3.8

RUN apt-get update && apt-get -y install tmux nano

ADD ./requirements.txt /srv
WORKDIR /srv
RUN pip install --upgrade pip
RUN pip install -r ./requirements.txt
ADD . /srv

CMD uvicorn app:app --host 0.0.0.0 --port 8080 --reload

问题

构建 docker 时,在 docker (`uvicorn app:app ...`) 中启动服务器的命令因行 `db_user = ...` 而失败。

这是完整的错误:

INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
INFO:     Started reloader process [6] using statreload
Creating DB user
Process SpawnProcess-1:
Traceback (most recent call last):
File "/usr/local/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
File "/usr/local/lib/python3.8/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
File "/usr/local/lib/python3.8/site-packages/uvicorn/subprocess.py", line 62, in subprocess_started
    target(sockets=sockets)
File "/usr/local/lib/python3.8/site-packages/uvicorn/main.py", line 390, in run
    loop.run_until_complete(self.serve(sockets=sockets))
File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
File "/usr/local/lib/python3.8/site-packages/uvicorn/main.py", line 397, in serve
    config.load()
File "/usr/local/lib/python3.8/site-packages/uvicorn/config.py", line 278, in load
    self.loaded_app = import_from_string(self.app)
File "/usr/local/lib/python3.8/site-packages/uvicorn/importer.py", line 20, in import_from_string
    module = importlib.import_module(module_str)
File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
File "", line 1014, in _gcd_import  File "", line 991, in _find_and_load
File "", line 975, in _find_and_load_unlocked
File "", line 671, in _load_unlocked
File "", line 783, in exec_module
File "", line 219, in _call_with_frames_removed
File "./app.py", line 24, in
     db_user = _get_or_create_db(_client=couch, db_name="user")
 File "./app.py", line 20, in _get_or_create_db
     return _client.create(db_name)
 File "/usr/local/lib/python3.8/site-packages/couchdb/client.py", line 221, in create
     self.resource.put_json(name)
 File "/usr/local/lib/python3.8/site-packages/couchdb/http.py", line 577, in put_json
     return self._request_json('PUT', path, body=body, headers=headers,
File "/usr/local/lib/python3.8/site-packages/couchdb/http.py", line 595, in _request_json
    status, headers, data = self._request(method, path, body=body,
File "/usr/local/lib/python3.8/site-packages/couchdb/http.py", line 590, in _request
    return self.session.request(method, url, body=body,
File "/usr/local/lib/python3.8/site-packages/couchdb/http.py", line 295, in request
    conn = self.connection_pool.get(url)
File "/usr/local/lib/python3.8/site-packages/couchdb/http.py", line 515, in get
    conn.connect()
File "/usr/local/lib/python3.8/http/client.py", line 921, in connect
    self.sock = self._create_connection(
File "/usr/local/lib/python3.8/socket.py", line 808, in create_connection
    raise err
File "/usr/local/lib/python3.8/socket.py", line 796, in create_connection
    sock.connect(sa)ConnectionRefusedError:
[Errno 111] Connection refused

我的猜测

此错误仅在构建时发生(`docker-compose up --build -d`):构建后,我可以手动进入我的服务器容器执行相同的命令而不会出现任何错误。然后创建用户数据库并启动我的服务器 运行。

考虑到这一点,我怀疑可能是当服务器容器尝试创建新数据库时我的 couchdb 容器未完全创建而产生错误。

关于如何解决这个问题有什么想法吗?

编辑

# .env
COUCHDB_USER=admin
COUCHDB_PASSWORD=superSECRET!
COUCHDB_HOST=couchdb
COUCHDB_PORT=5984

Python 尝试连接时,您的数据库可能尚未启动。像这样在服务器中添加依赖项:

depends_on: 
  - couchdb

@CyrilG. 的回答虽然不幸没有完全解决问题,但为我指明了正确的方向:

depends_on 中曾经有一个 condition 参数可以传递 service_healthy。不幸的是,从 v.3 开始,这已被弃用。

可能的解决方案是:

  • 重新启动服务几次,直到它像
  • 一样工作
  • 修改服务本身来处理
  • 等问题

我选择了第二个选项。 app.py 的这个细微修改解决了问题:

...
import time
...

def _get_or_create_db(
        _client: couchdb.Server, 
        db_name: str, 
        max_retries: int = 3, 
        retry_wait: int = 10

) -> couchdb.Database:
    retry = 0
    while retry <= max_retries:
        try:
            if db_name in _client:
                return _client[db_name]
            print("Creating DB", db_name)
            return _client.create(db_name)
        except ConnectionRefusedError:
            retry += 1
            time.sleep(retry_wait)