在Docker中运行MySQL / MariaDB时django.db.utils.OperationalError:在'reading initial communication packet'



在Docker容器中运行MySQL/MariaDB:

docker run -p 3306:3306 --name $(DATABASE_NAME) -v /tmp/mysql:/var/lib/mysql -e MYSQL_DATABASE=$(DATABASE_NAME) -e MYSQL_USER=$(DATABASE_USER) -e MYSQL_ROOT_PASSWORD=$(DATABASE_PASSWORD) -d mariadb:latest > /dev/null

然后在本地运行Django version 4:

manage.py runserver 127.0.0.1:8000

错误
django.db.utils.OperationalError: (2013, "Lost connection to MySQL server at 'reading initial communication packet', system error: 2")

我可以成功地用MySQL工作台连接到数据库,以及命令:

mysql -h 127.0.0.1 -P 3306 -u root -p <database>

我正在从Makefile启动Django和MySQL/MariaDB Docker容器。

Makefile

SHELL := /bin/bash
.PHONY: dj-start-local
dj-start-local: start-mysql
PYTHONPATH=. django_project/src/manage.py runserver 127.0.0.1:8000
.PHONY: start-mysql
start-mysql:
docker run -p 3306:3306 --name $(DATABASE_NAME) 
-v /tmp/mysql:/var/lib/mysql 
-e MYSQL_DATABASE=$(DATABASE_NAME) 
-e MYSQL_USER=$(DATABASE_USER) 
-e MYSQL_ROOT_PASSWORD=$(DATABASE_PASSWORD) 
-d mariadb:latest > /dev/null

使用容器中的healthcheck.sh。使用MARIADB_MYSQL_LOCALHOST_USER=1创建一个mysql@localhost用户,脚本可以使用该用户访问数据库,

无论何时,健康检查都将等待完全启动。

Makefile:

.PHONY: start-mariadb
start-mariadb:
docker run -p 3306:3306 --name $(DATABASE_NAME) 
-e MARIADB_DATABASE=$(DATABASE_NAME) 
-e MARIADB_USER=$(DATABASE_USER) 
-e MARIADB_PASSWORD=$(DATABASE_PASSWORD) 
-e MARIADB_ROOT_PASSWORD=$(DATABASE_PASSWORD) 
-e MARIADB_MYSQL_LOCALHOST_USER=1 
-v /tmp/mysql:/var/lib/mysql 
-d mariadb:latest
while ! docker exec $(DATABASE_NAME) healthcheck.sh --su=mysql --connect --innodb_initialized; do sleep 1; done
docker exec --user mysql $(DATABASE_NAME) mariadb -e 'select "hello world"'

测试运行:

$ make start-mariadb 
docker run -p 3306:3306 --name dd 
-e MARIADB_DATABASE=dd 
-e MARIADB_USER=dd 
-e MARIADB_PASSWORD=dd 
-e MARIADB_ROOT_PASSWORD=dd 
-e MARIADB_MYSQL_LOCALHOST_USER=1 
-d mariadb:latest
53066fffa293ed061743024e387bd7fb6f1c664efd603c7a657ba88e307be308
while ! docker exec dd healthcheck.sh --su=mysql --connect --innodb_initialized; do sleep 1; done
healthcheck connect failed
healthcheck connect failed
healthcheck connect failed
healthcheck connect failed
docker exec --user mysql dd mariadb -e 'select "hello world"'
hello world
hello world

注意:添加了MARIADB_PASSWORD,否则无法创建数据库/用户。

这个问题可能是由于一个竞争条件,Django试图在数据库准备好之前连接到数据库。请尝试在启动Docker容器后等待几秒钟。

简单解决方案

Makefile

.PHONY: start-mysql
start-mysql:
docker run -p 3306:3306 --name $(DATABASE_NAME) 
-v /tmp/mysql:/var/lib/mysql 
-e MYSQL_DATABASE=$(DATABASE_NAME) 
-e MYSQL_USER=$(DATABASE_USER) 
-e MYSQL_ROOT_PASSWORD=$(DATABASE_PASSWORD) 
-d mariadb:latest
sleep 4

使用healthcheck.sh的更好解决方案

Dan Black的回答,他指出MariaDB docker容器包含一个脚本healthcheck.sh.

如果您只想检查MariaDB容器是否可以接收连接,那么可以不指定用户(避免授权复杂性)而运行healthcheck.sh

下面的Makefile将每秒钟检查一次数据库连接,直到数据库准备好。

Makefile(高级版)

.PHONY: mysql-start
mysql-start: mysql-stop
docker run -p 3306:3306 --name $(DATABASE_NAME) 
-v /tmp/mysql:/var/lib/mysql 
-e MYSQL_DATABASE=$(DATABASE_NAME) 
-e MYSQL_USER=$(DATABASE_USER) 
-e MYSQL_ROOT_PASSWORD=$(DATABASE_PASSWORD) 
-d mariadb:latest
while ! docker exec $(DATABASE_NAME) healthcheck.sh --connect 2> /dev/null; do sleep 1; done