[PYTHON] Mémo de la migration de la base de données de Django de SQLite3 vers MySQL sur Docker sur Raspberry Pi 4B

But de cette fois

C'est un peu brouillon.

  1. Migrez votre propre projet Django qui fonctionnait (directement) sur Raspberry Pi 3B + vers Raspberry Pi 4B
  2. La base de données SQLite3 est devenue gonflée (vide d'environ 120 Mo) et déplacée vers MySQL pour assurer le parallélisme.
  3. Assurez-vous d'exécuter le projet Django avec docker-compose.
  4. Lançons MySQL avec docker-compose
  5. Lançons un script d'exécution périodique qui met automatiquement à jour la base de données avec docker-compose

1. Configuration initiale du Raspberry Pi 4B

Tout d'abord, configurez le Raspberry Pi 4B.

J'ai entendu dire que Stretch qui fonctionnait sur 3B + ne fonctionnait pas sur 4B, j'ai donc apporté un nouveau Raspbian Buster de Télécharger Raspbian pour Raspberry Pi. Gravez sur une carte Micro SD. Buster est Lite au lieu de Desktop car il semble économiser de la mémoire (bien qu'il ait augmenté) (pour une utilisation serveur de toute façon).

~~ Je n'avais pas de câble / adaptateur mini HDMI (c'est dur, même si c'est Micro ...), donc ~~ (PostScript 20/03/07: faux. Micro HDMI. Mini est un gars mince?) Monitorless installer. Il se vend 100 yens, alors procurez-vous-le en cas de besoin. Insérez la carte Micro SD dans l'unité principale et connectez-la au routeur avec un câble LAN. Connectez une alimentation 5V 3A Type-C (dur, remplacez par Micro) et allumez l'alimentation (j'ai un peu peur si je ne veux pas acheter une nouvelle alimentation car la conversion entre Micro USB et Type-C semble être d'environ 100).

Vérifiez l'adresse IP attribuée par DHCP à partir du routeur et établissez une connexion SSH (mot de passe initial) sur le LAN.

Quand il dit Trop d'échecs d'authentification ou est joué avec publickey, il ne parvient pas à s'authentifier avec la clé publique, alors définissez -o PreferredAuthentications = password ou -o PubkeyAuthentication = no comme option de la commande ssh Ou ajoutez PreferredAuthentications password ou PubkeyAuthentication no à ~ / .ssh / config.

Changer le mot de passe / changer le nom d'utilisateur

Changez le mot de passe, puis changez le nom d'utilisateur en même temps. Vous ne pouvez pas changer le nom d'utilisateur lorsque vous êtes connecté à l'utilisateur pi, alors créez un nouvel utilisateur sudoer et connectez-vous à nouveau (cette fois, c'est dans le LAN, vous pouvez donc définir un mot de passe root temporaire).

Créez un tmpuser en exécutant la commande suivante en tant qu'utilisateur pi.

#Le répertoire de base n'est pas créé pour useradd
sudo useradd tmpuser
sudo passwd tmpuser

Ajoutez ensuite tmpuser aux sudoers.

sudo adduser tmpuser sudo

Si vous voulez un petit détour, modifiez / etc / sudoers et ajoutez tmpuser. D'une manière ou d'une autre, / etc / sudoers et d'autres sont en lecture seule (chmod convient parfaitement), alors créez /etc/sudoers.d/011_tmpuser (vous pouvez l'ajouter au groupe sudo).

# /etc/sudoers.d/011_tmpuser
tmpuser ALL=(ALL:ALL) ALL

Déconnectez-vous une fois, reconnectez-vous en tant qu'utilisateur tmpuser, changez le nom de l'utilisateur pi avec la commande suivante, changez le nom du groupe pi et enfin déplacez le répertoire de base.

sudo usermod -l NEW_NAME pi
sudo groupmod -n NEW_NAME pi
sudo usermod -m -d /home/NEW_NAME NEW_NAME

Déconnectez-vous de l'utilisateur tmpuser, reconnectez-vous en tant qu'utilisateur NEW_NAME et supprimez l'utilisateur tmpuser. Si vous faites un détour, supprimez également /etc/sudoers.d/011_tmpuser. Par défaut, l'utilisateur pi appartient au groupe sudo, il n'est donc pas nécessaire d'ajouter à nouveau l'utilisateur NEW_NAME aux sudoers (devrait).

sudo userdel tmpuser
# sudo rm /etc/sudoers.d/011_tmpuser

Changement de nom d'hôte

# /etc/hostname
NEW_HOSTNAME

# /etc/hosts
...
127.0.1.1 NEW_HOSTNAME

Authentification par clé publique

Enregistrez la clé publique auprès de l'utilisateur NEW_NAME et modifiez / etc / ssh / sshd_config pour que l'authentification SSH soit uniquement la clé publique.

côté pi

mkdir ~/.ssh
chmod 700 ~/.ssh

Côté hôte

cd ~/.ssh
ssh-keygen -f KEY_NAME
scp KEY_NAME.pub RPI4_HOST:.ssh/

côté pi

cd ~/.ssh
cat KEY_NAME.pub >> authorized_keys
chmod 600 authorized_keys

Après cela, spécifiez ʻIdentity File` dans ~ / .ssh / config. Lorsqu'il est toujours appelé «Trop d'échecs d'authentification», ajoutez «Identités uniquement oui».

Modifiez / etc / ssh / sshd_config pour définir PasswordAuthentication no si nécessaire.

2. Introduction de Docker / docker-compose sur Raspberry Pi 4B

sudo curl -fsSL https://get.docker.com/ | sh
sudo apt install python3-pip
sudo apt install libffi-dev
sudo pip3 install docker-compose

3. Migrez le projet Django vers Docker / docker-compose

Puisque le projet Django était géré par git, j'ai migré le programme vers le nouveau serveur avec git clone. DB (SQLite3) est migré avec scp.

Étant donné que l'environnement était géré par virtualenv sur l'ancien serveur, requirements.txt est généré à partir d'ici.

pip3 freeze > requirements.txt

Comme c'est un gros problème, nous allons créer l'environnement pour le nouveau serveur avec Docker / docker-compose. C'était une configuration de django: wsgi-gunicorn-nginx, mais avant tout, c'était un test de fonctionnement en soi.

# Dockerfile
FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
# docker-compose.yml
version: '3'

services:
    web:
        build: .
        command: python manage.py runserver 0.0.0.0:8000
        volumes:
            - .:/code
        ports:
            - "127.0.0.1:8000:8000"
        environment:
            - ENVIRONMENT=production
sudo docker-compose up

4. Exécutez MySQL sur Docker (docker-compose / Raspberry Pi 4)

Exécutez MySQL (MariaDB) avec docker-compose sur Raspberry Pi. Utilisez jsurf / rpi-mariadb.

...
    db:
        # image: mariadb
        image: jsurf/rpi-mariadb
        command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
        volumes:
            - DATABASE_DIRECTORY:/var/lib/mysql
        environment:
            - MYSQL_ROOT_PASSWORD=ROOT_PASSWORD
            - MYSQL_DATABASE=DATABASE_NAME
            - MYSQL_USER=USER
            - MYSQL_PASSWORD=PASSWORD
    web:
...

5. Changez les paramètres DB de Django de SQLite3 à MySQL

Je le retournerai plus tard, donc tout en facilitant le retour, je jouerai avec settings.py de Django de manière appropriée et spécifierai la base de données à partir de la variable d'environnement.

DATABASE_ENGINE = os.environ.get('DATABASE_ENGINE', 'django.db.backends.sqlite3')
DATABASE_OPTIONS = {}
if DATABASE_ENGINE == 'django.db.backends.mysql':
    DATABASE_OPTIONS = {
        'charset': os.environ.get('DATABASE_CHARSET'),
    }

DATABASES = {
    'default': {
        'ENGINE': DATABASE_ENGINE,
        'HOST': os.environ.get('DATABASE_HOST'),
        'PORT': os.environ.get('DATABASE_PORT'),
        'NAME': os.environ.get('DATABASE_NAME', os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': os.environ.get('DATABASE_USER'),
        'PASSWORD': os.environ.get('DATABASE_PASSWORD'),
        'OPTIONS': DATABASE_OPTIONS,
    },
}

Modifiez docker-compose.yml comme suit. Mettez en commentaire toute la partie DATABASE de l'environnement, ou créez un autre docker-compose.yml pour que la base de données puisse être retournée à SQLite3.

    web:
...
        environment:
            - ENVIRONMENT=production
            - DATABASE_ENGINE=django.db.backends.mysql
            - DATABASE_HOST=db
            - DATABASE_PORT=3306
            - DATABASE_NAME=DATABASE_NAME
            - DATABASE_USER=USER
            - DATABASE_PASSWORD=PASSWORD
            - DATABASE_CHARSET=utf8mb4
        depends_on:
            - db

Ajoutez PyMySQL à requirements.txt et ajoutez ce qui suit en haut de manage.py.

if os.environ.get('DATABASE_ENGINE') == 'django.db.backends.mysql':
    import pymysql
    pymysql.install_as_MySQLdb()

Si Django accède avant que l'initialisation de MySQL ne soit terminée, Django supprimera une erreur, alors réexécutez docker-compose up à la première exécution. Si Django démarre en premier lors de la deuxième migration, gérez-le en insérant un sleep approprié ou en créant un script d'attente. Après avoir pris en sandwich gunicorn dans la 9e section, même si Django (gunicorn) commence en premier, l'erreur disparaît (comme), vous n'aurez peut-être pas trop à vous inquiéter.

        command: bash -c "sleep 5 && python manage.py runserver 0.0.0.0:8000"

6. Élimination des erreurs de migration

Selon la définition du modèle de base de données, sudo docker-compose up -d et sudo docker-compose exec web python3 manage.py migrate donneront une erreur. Par exemple, si vous avez un TextField avec une contrainte unique et que vous ne spécifiez pas max_length. Cette fois, j'ai changé l'URL de TextField au lieu d'URLField en URLField, et j'ai spécifié max_length (255 ou moins) pour TextField, qui est connue pour être une courte chaîne de caractères, et je l'ai résolue (cependant, sur Raspberry Pi, ce n'est pas suffisant. Cela n'a pas fonctionné, j'ai donc fini par supprimer la contrainte unique plus tard).

Dans ce domaine, j'ai déplacé le projet et la base de données vers la machine principale pour accélérer et mener des expériences. Cette fois, il faut changer l'image de MySQL, mais le bon point de Docker est qu'il prépare automatiquement le même environnement (généralement) et ne pollue / n'affecte pas l'environnement hôte (compatible sans image officielle) Je suis troublé par le sexe ...?).

7. Exportez les données Django DB vers json

Après avoir éliminé l'erreur de migration, renvoyez la base de données vers SQLite3, migrez-la, puis videz les données dans json.

sudo docker-compose run web bash
python3 manage.py makemigrations
python3 manage.py migrate
# python3 manage.py dumpdata > dump.json
python3 manage.py dumpdata --natural-foreign --natural-primary -e contenttypes -e auth.Permission > dump.json

8. Réécrire les données Django DB depuis json

python3 manage.py migrate
python3 manage.py loaddata dump.json
django.db.utils.IntegrityError: Problem installing fixture '/code/dump.json': Could not load APP.MODELNAME(pk=PK_NUM): (1062, "Duplicate entry 'ONE_FIELD_NUM' for key 'ONE_FIELD'")

Il semble que ce n'était pas bon de mettre la contrainte unique_together sur OneToOneField (OneToOne ne peut pas être utilisé plusieurs-à-un), alors je l'ai changé en ForeignKey. De plus, à ce stade, même si cela fonctionnait avec mariadb, cela ne fonctionnait pas car l'erreur autour de la longueur de la clé n'a probablement pas disparu parce qu'elle était définie sur utf8mb4 dans jsurf / rpi-mariadb, j'ai donc supprimé la contrainte unique de toutes les chaînes de caractères. En plus de cela, j'ai dû réécrire directement les fichiers sous migrations car la migration s'est arrêtée à mi-chemin ici. Même si j'ai envoyé la base de données traitée par un autre PC directement, cela n'a pas fonctionné, donc je suis toujours inquiet pour la compatibilité. Après de nombreux essais et erreurs, j'ai enfin pu charger des données.

9. Placez gunicorn entre le serveur Web hôte (proxy inverse) et Django

Ajoutez gunicorn à requirements.txt.

Modifiez docker-compose.yml. Puisqu'il consomme de la mémoire (je pense), ajustez le nombre de travailleurs -w si nécessaire.

        # command: /usr/local/bin/gunicorn -w 4 -b 0.0.0.0:8000 MY_PROJECT.wsgi -t 300
        command: bash -c "sleep 5 && gunicorn -w 4 -b 0.0.0.0:8000 MY_PROJECT.wsgi -t 300"

Comme pour manage.py, ajoutez ce qui suit en haut de wsgi.py.

if os.environ.get('DATABASE_ENGINE') == 'django.db.backends.mysql':
    import pymysql
    pymysql.install_as_MySQLdb()

10. Exécutez le script d'exécution périodique sur Docker

(Post-scriptum du 20/02/15)

J'ai défini les paramètres en utilisant busybox crond ci-dessous, mais il semble que le script de production n'ait pas bien fonctionné en raison des inconvénients liés au journal, j'ai donc écrit un script d'exécution périodique en Python et un conteneur avec la même configuration que le conteneur Django J'ai décidé d'en faire un autre et de l'exécuter. Cependant, comme il est redondant, il peut être préférable de créer un point de terminaison pour une exécution périodique du côté du conteneur Django et d'en faire un conteneur qui ignore simplement les requêtes HTTP.


(Ancienne version)

Cette fois, exécutez une exécution régulière dans le même conteneur que Django.

Jusqu'à présent, le script d'exécution périodique était généralement écrit en python ou exécuté par la minuterie de systemd. Cette fois, c'était systemd / timer, j'ai donc essayé de le déplacer dans le conteneur Docker, mais bien que je puisse exécuter le script dans le conteneur Docker à partir de l'hôte avec ʻexec`, j'exécute systemd / timer dans le conteneur Docker Je ne suis pas sûr.

Quoi qu'il en soit, le système d'exploitation de base de python: 3 est Debian et il ne semble pas y avoir de systemd (init.d), donc je vais l'exécuter régulièrement avec cron.

C'était la première fois que j'utilisais cron, donc je me suis finalement perdu.

Je veux hériter des variables d'environnement spécifiées par Docker, j'utilise donc crond inclus dans busybox.

Tout d'abord, créez le fichier suivant crontab dans le répertoire d'exécution.

# * * * * * cd /code && echo `env` >> env.txt
0 */6 * * * cd /code && /usr/local/bin/python3 AUTORUN_SCRIPT.py

Le haut est le paramètre pour écrire des variables d'environnement dans un fichier toutes les minutes (pour le débogage), et le bas est le paramètre pour exécuter automatiquement /code/AUTORUN_SCRIPT.py dans l'utilisateur root et le répertoire de travail / code toutes les 6 heures. Le temps est bien avec JST.

Ensuite, définissez l'installation de crond et l'ajout du fichier de configuration dans le Dockerfile. La bonne réponse est que / var / spool / cron / crontabs / root est un fichier plutôt qu'un répertoire.

# Dockerfile
...
RUN apt update && apt install -y \
  busybox-static
ENV TZ Asia/Tokyo
COPY crontab /var/spool/cron/crontabs/root
...

Assurez-vous ensuite que crond démarre au démarrage du conteneur Docker. Notez que CMD dans Dockerfile n'est pas exécuté car nous utilisons cette fois-ci docker-compose. À la place, ajoutez la commande start pour crond à command dans docker-compose.yml. Puisque crond est exécuté en arrière-plan, gunicorn sera lancé automatiquement.

# docker-compose.yml
...
        # command: bash -c "busybox crond && gunicorn -w 4 -b 0.0.0.0:8000 MY_PROJECT.wsgi -t 300"
        command: bash -c "sleep 5 && busybox crond && gunicorn -w 4 -b 0.0.0.0:8000 MY_PROJECT.wsgi -t 300"
...

Depuis que je joue avec la base de données de Django, j'ai ajouté l'installation de pymysql dans ʻAUTORUN_SCRIPT.py (identique à manage.py, wsgi.py`), et j'ai confirmé que le script expérimental fonctionne. .. Supprimez les paramètres cron pour le débogage et complétez les paramètres.

11. Exécution automatique au démarrage et exécution en arrière-plan

Ajoutez restart: always à docker-compose.yml pour qu'il démarre automatiquement au démarrage de l'hôte et démarrez-le en arrière-plan avec sudo docker-compose up -d. Après cela, vous pouvez redémarrer l'hôte (sudo docker-compose ps, sudo docker ps).

résultat

Nous avons réussi à migrer le matériel (Raspberry Pi), à migrer la base de données, à migrer le moteur de base de données, à migrer vers Docker et à le rendre persistant.

Les performances se sont améliorées (semble être) en raison des améliorations des spécifications matérielles et de la migration vers MySQL, et les opérations de base de données peuvent maintenant être effectuées en parallèle (comme), donc lors de l'accès à la base de données en même temps L'erreur «La base de données est verrouillée» qui se produisait n'est plus visible.

C'est un projet personnel, alors j'essaie de casser les paramètres du journal ou de le couper de manière unique ... J'ai abandonné parce que cela m'a pris du temps. Cependant, après le chargement des données, il peut être possible de retourner des données uniques par migration.

Après cela, j'ai pensé qu'il serait préférable de séparer cron dans un autre conteneur, mais comme il a exactement la même dépendance que le projet Django, je l'ai mis ensemble sans le diviser. Comment divisez-vous cela ...?

Recommended Posts

Mémo de la migration de la base de données de Django de SQLite3 vers MySQL sur Docker sur Raspberry Pi 4B
Connectez-vous à MySQL avec Python sur Raspberry Pi
Mémo de déploiement de Django × Postgresql sur Docker vers Heroku
Installation de Docker sur Raspberry Pi et L Chika
Portez FreeRTOS vers Raspberry Pi 4B
raspberry pi 4 centos7 installer sur docker
Installez ghoto2 sur Raspberry Pi (Remarque)
Sortie du Raspberry Pi vers la ligne
Remarques sur la connexion Bluetooth d'un smartphone / PC à Raspeye 4
Démarrage USB sur Raspberry Pi 4 modèle B
Connexion de python à MySQL sur CentOS 6.4
Construire un environnement OpenCV-Python sur Raspberry Pi B +
Comment installer NumPy sur Raspeye
Pourquoi detectMultiScale () est lent sur Raspberry Pi B +
Construire un environnement Django sur Raspai (MySQL)
Rendre DHT11 disponible avec Raspeye + python (Remarque)
Impossible de se connecter à MySQL depuis l'environnement Docker (Debian)
Introduction de Ceph avec Kubernetes sur Raspberry Pi 4B (ARM64)
Proxy inverse d'Apache sur GCP vers Raspeye Apache local (Wake on LAN over NAT [3])
De la configuration du Raspberry Pi à l'installation de l'environnement Python
Exécutez la matrice LED de manière interactive avec Raspberry Pi 3B + sur Slackbot
Mettre en œuvre des cadres photo optimisés personnellement avec Raspberry Pi
Contrôler la mise sous / hors tension du port USB du Raspberry Pi
Sortie sur "LED 7 segments" en utilisant python avec Raspberry Pi 3!
Jouez pour informer Slack des données environnementales de SensorTag à l'aide d'AWS PaaS via Raspberry Pi3