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)
设置
考虑这个相对简单的 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)