Aller au contenu principal

Migrer depuis Virtualmin

Ce guide vous accompagne dans la migration de sites PHP+MySQL, d'un serveur Virtualmin vers Kuploy. La démarche s'appuie sur la boîte à outils kuploy-migrate, qui automatise la copie des fichiers, les exports de bases de données et la génération des Dockerfile.

Vue d'ensemble​

Le déroulé de la migration :

  1. Exporter — lancez le script de migration sur votre serveur Virtualmin, pour pousser les fichiers des sites et les exports de bases vers un dépôt GitHub
  2. Déployer — créez dans Kuploy les projets, les bases et les applications, en les faisant pointer sur ce dépôt
  3. Basculer le DNS — mettez à jour vos enregistrements A vers votre cluster Kuploy

Chaque site Virtualmin devient un projet Kuploy, avec sa propre base MariaDB et son déploiement applicatif.

Prérequis​

  • Un serveur Virtualmin, avec un accès root
  • git, curl, rsync, mysql et mysqldump installés sur le serveur
  • Un jeton d'accès personnel GitHub, avec la portée repo
  • Une instance Kuploy, avec un fournisseur GitHub connecté
  • Un registre de conteneurs configuré dans Kuploy (Settings → Registry)

Phase 0 : préparer l'image de base​

kuploy-migrate utilise une image Docker de base commune à tous les sites PHP. Construisez-la et poussez-la une fois pour toutes :

git clone https://github.com/kuploy/kuploy-migrate
cd kuploy-migrate

docker build --platform linux/amd64 -t votreregistre/php-legacy:8.1 -f base-Dockerfile .
docker push votreregistre/php-legacy:8.1

L'image de base comporte PHP 8.1, Apache avec mod_rewrite et AllowOverride All, les extensions mysqli et PDO, Composer, et des valeurs par défaut raisonnables dans php.ini : 64 Mo de téléversement, 256 Mo de mémoire, 300 s d'exécution.

astuce

Si votre cluster tourne sur des nœuds ARM, construisez pour plusieurs plateformes :

docker buildx build --platform linux/amd64,linux/arm64 --push -t votreregistre/php-legacy:8.1 -f base-Dockerfile .

Phase 1 : exporter les sites depuis Virtualmin​

Lancer le script de migration​

Sur votre serveur Virtualmin :

export GITHUB_PAT=ghp_votre_jeton
cd /chemin/vers/kuploy-migrate
chmod +x virtualmin/migrate.sh

# D'abord à blanc
./virtualmin/migrate.sh \
--pat $GITHUB_PAT \
--repo votreorg/virtualmin-sites \
--sites "monsite" \
--dry-run

# Puis pour de vrai
./virtualmin/migrate.sh \
--pat $GITHUB_PAT \
--repo votreorg/virtualmin-sites \
--sites "monsite"

Ce que fait le script​

Pour chaque site, le script :

  1. analyse /etc/webmin/virtual-server/domains/*, pour en tirer les identifiants de base de données, les noms de domaine et le mode PHP ;
  2. copie public_html/, avec des exclusions : journaux, archives, courriels, phpMyAdmin ;
  3. détecte les applications PHP situées hors de public_html/, en cherchant artisan ou vendor/autoload.php ;
  4. copie la racine de l'application, en préservant le nom de répertoire d'origine ;
  5. exporte la base MySQL, avec les identifiants propres au site ;
  6. génère un Dockerfile minimal ;
  7. enregistre les identifiants dans un fichier local, jamais versionné dans Git ;
  8. pousse le tout vers le dépôt GitHub.

La structure du dépôt​

Le script crée cette arborescence dans votre dépôt GitHub :

virtualmin-sites/
├── monsite/
│ ├── Dockerfile
│ ├── public_html/
│ ├── monsite/ ← la racine de l'application PHP, si détectée
│ ├── db/monsite.sql ← l'export MySQL
│ └── site.json ← les métadonnées
└── .gitignore

La détection des applications PHP​

Le script cherche des répertoires de code hors de public_html/, sous ~/<site>/<site>/, ~/<site>/app/ ou ~/<site>/laravel/. Il y guette soit artisan (Laravel), soit vendor/autoload.php (n'importe quelle application Composer).

Le nom de répertoire d'origine est préservé, pour que les chemins des require() de votre index.php continuent de fonctionner. Ainsi, si votre index.php fait require('../monsite/vendor/autoload.php'), le Dockerfile copie vers /var/www/monsite/.

Un répertoire d'application personnalisé : si la racine de votre application PHP porte un nom qui sort de l'ordinaire — sita au lieu du nom du site, par exemple —, utilisez l'option --app-dir :

./virtualmin/migrate.sh \
--pat $GITHUB_PAT \
--repo votreorg/virtualmin-sites \
--sites "monsite" \
--app-dir "monsite:sita"

Le script cherchera alors l'application sous ~/<site>/sita/, en plus des emplacements par défaut. Le nom du répertoire est préservé dans le Dockerfile, pour la compatibilité des chemins.

La gestion des dépendances :

  • si l'application a un composer.json, le répertoire vendor est exclu de Git et le Dockerfile exécute composer install --no-dev à la construction ;
  • s'il n'y a pas de composer.json, vendor est versionné tel quel.

Phase 2 : déployer dans Kuploy​

Vous pouvez déployer vos sites soit par l'API d'import de sites — recommandé pour les migrations en lot —, soit à la main, depuis le tableau de bord.

L'API d'import de sites crée le projet, la base, l'application, les variables d'environnement et le domaine en un seul appel par site.

Mise en place :

  1. Générez une clé d'API depuis Account → API Keys (voir Les clés d'API). Basculez sur l'organisation cible avant de créer la clé : elle est liée à l'organisation active au moment de sa création.
  2. Notez votre identifiant d'organisation et l'identifiant de votre fournisseur Git GitHub, depuis le tableau de bord

Importer tous les sites :

export API_KEY="votre-clé-d-api"
export KUPLOY_URL="https://console.example.com"
export ORG_ID="org_xxx"
export GITHUB_ID="gh_xxx"
export REPO="votreorg/virtualmin-sites"

for site_dir in ${REPO_DIR}/*/; do
site=$(basename "$site_dir")
domain=$(jq -r '.domain' "$site_dir/site.json")
db_name=$(jq -r '.db_mysql' "$site_dir/site.json")
db_user=$(jq -r '.mysql_user' "$site_dir/site.json")
db_pass=$(grep "^$site " "$CREDS_FILE" | awk '{print $4}')

curl -s -X POST "$KUPLOY_URL/api/site-import" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"organizationId\": \"$ORG_ID\",
\"name\": \"$site\",
\"domain\": \"$domain\",
\"database\": {
\"type\": \"mariadb\",
\"name\": \"$db_name\",
\"user\": \"$db_user\",
\"password\": \"$db_pass\"
},
\"source\": {
\"type\": \"github\",
\"repo\": \"$REPO\",
\"branch\": \"main\",
\"buildPath\": \"/$site\",
\"buildType\": \"dockerfile\",
\"githubId\": \"$GITHUB_ID\"
},
\"port\": 80
}" | jq .
done

L'API définit automatiquement DB_HOST, DB_DATABASE, DB_USERNAME et DB_PASSWORD sur l'application. Pour les sites Laravel, ajoutez les variables supplémentaires par le champ envVars :

"envVars": {
"APP_KEY": "base64:xxx",
"APP_ENV": "production",
"APP_DEBUG": "false"
}
astuce

Récupérez l'APP_KEY depuis le serveur d'origine :

grep APP_KEY /home/monsite/monsite/.env

Importer l'export de base de données​

Une fois que l'API d'import de sites a créé le conteneur de base de données et qu'il tourne, importez l'export SQL :

# Trouver le nom du pod
kubectl get pods -n project-monsite

# Importer l'export (utilisez -i, et non -it, quand vous alimentez l'entrée standard)
kubectl exec -i <pod-mariadb> -n project-monsite -- \
mariadb -u root -p'<mot_de_passe_root>' monsite < monsite/db/monsite.sql

# Vérifier que les tables ont bien été créées
kubectl exec -i <pod-mariadb> -n project-monsite -- \
mariadb -u root -p'<mot_de_passe_root>' monsite -e "SHOW TABLES;"
info

Les conteneurs MariaDB utilisent la commande mariadb, et non mysql. Récupérez le mot de passe root depuis l'environnement du pod :

kubectl exec -i <pod-mariadb> -n project-monsite -- env | grep MARIADB_ROOT

Option B : la mise en place à la main (tableau de bord)​

Si vous préférez configurer chaque site vous-même :

  1. Créez un projet — cliquez sur New Project, et nommez-le d'après le site
  2. Créez une base MariaDB — Add Resource → Database → MariaDB, puis renseignez le nom, l'utilisateur et le mot de passe
  3. Créez une application — Add Resource → Application, source GitHub, dépôt votreorg/virtualmin-sites, branche main, chemin de construction /<site>, type Dockerfile, port 80
  4. Définissez les variables d'environnement — DB_HOST, DB_DATABASE, DB_USERNAME, DB_PASSWORD, plus APP_KEY et consorts pour Laravel
  5. Ajoutez le domaine — allez dans l'onglet Domains, et ajoutez votre domaine

Ajouter un domaine personnalisé​

Si vous êtes passé par l'API, le domaine est déjà configuré. Si vous procédez à la main :

  1. Allez dans l'onglet Domains
  2. Ajoutez votre domaine (monsite.com, par exemple)
  3. Kuploy met en service un certificat TLS automatiquement, par cert-manager

Tester avant de basculer le DNS​

Vérifiez que le déploiement fonctionne avant de toucher au DNS :

# À ajouter dans /etc/hosts, sur votre machine
<ip-ingress-k8s> monsite.com

Récupérez l'adresse de l'ingress :

kubectl get svc -n ingress-nginx

L'EXTERNAL-IP du service ingress-nginx-controller est l'adresse de votre répartiteur de charge.

La stratégie de migration DNS​

Important

N'indiquez pas de domaine dans l'appel à l'API d'import de sites si le DNS pointe encore vers votre ancien serveur. La création d'un domaine déclenche l'émission d'un certificat SSL (le défi HTTP-01 de Let's Encrypt), qui échouera si le domaine ne résout pas vers l'adresse de votre ingress Kuploy.

Procédez plutôt ainsi :

  1. Importez le site sans domaine — déployez et vérifiez d'abord qu'il fonctionne
  2. Mettez le DNS à jour, vers l'adresse de l'ingress Kuploy
  3. Ajoutez le domaine depuis le tableau de bord Kuploy, une fois le DNS propagé

Récupérer l'adresse de votre ingress Kuploy :

kubectl get svc -n ingress-nginx
# Repérez l'EXTERNAL-IP de ingress-nginx-controller

Tester avant de basculer le DNS​

Vérifiez que le déploiement fonctionne avant de toucher au DNS :

# À ajouter dans /etc/hosts, sur votre machine
<ip-ingress-k8s> monsite.com

Mettre le DNS à jour​

Une fois la vérification faite, mettez l'enregistrement A de votre domaine à jour, vers l'adresse de l'ingress Kuploy.

Si le DNS est géré par un bureau d'enregistrement externe (Enom, Namecheap, etc.), modifiez l'enregistrement A depuis son interface.

Si le DNS est géré par Virtualmin, son interface web vérifie que les enregistrements A correspondent à l'adresse du serveur lui-même, et refusera votre modification. Deux contournements :

Option A — décocher la validation (interface web) :

  1. Allez dans Server Configuration → DNS Records, pour le domaine
  2. Changez l'enregistrement A du domaine nu (sitagroupgn.com., par exemple) vers l'adresse de votre ingress Kuploy
  3. Décochez « Validate new records? », en bas
  4. Cliquez sur Save

Option B — la ligne de commande, qui contourne entièrement la validation :

KUPLOY_IP="<ip-ingress-kuploy>"

virtualmin modify-dns --domain monsite.com --remove-record "monsite.com. A"
virtualmin modify-dns --domain monsite.com --add-record "monsite.com. A $KUPLOY_IP"
virtualmin modify-dns --domain monsite.com --remove-record "www.monsite.com. A"
virtualmin modify-dns --domain monsite.com --add-record "www.monsite.com. A $KUPLOY_IP"
astuce

Ne modifiez que les enregistrements A du domaine nu et de www. Laissez tous les autres en place — en particulier MX, mail, webmail, SPF et DMARC, si le serveur Virtualmin continue de gérer la messagerie.

attention

Laissez les enregistrements MX, mail, webmail, SPF et autodiscover inchangés : ils doivent continuer de pointer vers le serveur Virtualmin, s'il gère encore la messagerie.

Ajouter le domaine dans Kuploy​

Une fois le DNS propagé — vérifiez avec dig +short monsite.com A :

  1. Allez dans Projects → monsite → Domains
  2. Ajoutez monsite.com, avec HTTPS activé
  3. Kuploy met en service un certificat TLS automatiquement, par Let's Encrypt

Autre possibilité, si vous êtes passé par l'API d'import de sites et que l'import est partial ou failed :

  1. Allez dans Import Sites
  2. Cliquez sur l'icône de crayon, sur la carte de l'import, pour ajouter le domaine
  3. Cliquez sur Retry — l'import reprend là où il s'était arrêté

Phase 3 : traiter le reste des sites en lot​

Lancez le script de migration sur plusieurs sites à la fois :

./virtualmin/migrate.sh \
--pat $GITHUB_PAT \
--repo votreorg/virtualmin-sites \
--sites "site1 site2 site3 site4 site5"

Puis répétez la mise en place côté Kuploy — projet, base, application, variables d'environnement, domaine — pour chaque site.

Dépannage​

L'import est partial, avec « domain_creation: HTTP request failed »​

L'étape du domaine a échoué, probablement parce que le DNS ne pointe pas encore vers le cluster Kuploy. Le projet, la base et l'application du site ont bien été créés ; le domaine et le déploiement, non.

Le correctif :

  1. Allez dans Import Sites
  2. Cliquez sur l'icône de crayon, sur la carte de l'import
  3. Videz le champ Domain
  4. Cliquez sur Save, puis sur Retry

L'import est alors relancé sans création de domaine. Vous ajouterez celui-ci plus tard, depuis le tableau de bord, après la migration DNS.

L'import est success, mais le site est injoignable​

success signifie que toutes les ressources ont été créées et la construction déclenchée ; mais la construction et le déploiement sont asynchrones. Vérifiez :

  1. Allez dans Projects → monsite → Application → Deployments, pour voir l'état de la construction
  2. Si elle a échoué, cherchez les erreurs dans les journaux de déploiement
  3. Si elle a réussi mais qu'aucun pod ne tourne, cliquez sur Deploy pour relancer un déploiement

Le bouton Retry tourne sans avancer​

L'import attend une étape longue : une construction, la création de ressources Kubernetes. Cela peut arriver si l'API de votre cluster est lente.

  • Patientez — une construction peut prendre jusqu'à une demi-heure sur un gros site
  • Si elle reste coincée, rechargez la page pour réinitialiser l'interface, puis vérifiez l'état de l'import

La construction échoue avec « failed to read dockerfile: is a directory »​

Le buildPath de l'import pointe sur un sous-répertoire (/monsite, par exemple), mais le constructeur n'y a pas trouvé le Dockerfile. Assurez-vous que :

  • le Dockerfile existe bien sous <buildPath>/Dockerfile, dans votre dépôt ;
  • le chemin de construction commence par / — /monsite, et non monsite.

L'import de l'export de base échoue​

Si kubectl exec reste bloqué ou renvoie des erreurs :

# Vérifier que le pod tourne
kubectl get pods -n project-monsite

# Vérifier le mot de passe root de MariaDB
kubectl exec -i <pod-mariadb> -n project-monsite -- env | grep MARIADB_ROOT

# Pensez à utiliser -i (et non -it) quand vous alimentez l'entrée standard
kubectl exec -i <pod-mariadb> -n project-monsite -- \
mariadb -u root -p'<mot_de_passe_root>' monsite < monsite/db/monsite.sql

Quelques réserves​

La messagerie​

Virtualmin gère la messagerie (Postfix, Dovecot) domaine par domaine. Kuploy ne s'en occupe pas. Vos options :

  • garder Virtualmin en service pour la seule messagerie — retirez l'hébergement web, conservez les enregistrements MX ;
  • migrer vers un prestataire de messagerie externe : Google Workspace, Zoho, Mailgun.

phpMyAdmin​

Le script de migration retire le phpMyAdmin embarqué dans chaque site. Déployez au besoin une seule instance de phpMyAdmin partagée, comme application Kuploy distincte.

Le mode PHP​

Virtualmin utilise php_mode=fcgid (FastCGI). L'image de base utilise mod_php, le module Apache. Cela convient à la très grande majorité des sites. Si l'un des vôtres repose sur des pools PHP par utilisateur, il demandera peut-être un ajustement.

Les tâches planifiées​

Les tâches cron de Virtualmin doivent être migrées à la main. Inspectez les crontabs existantes :

crontab -u monsite -l

Recréez-les comme planifications Kuploy, ou comme CronJob Kubernetes.

Les gros sites​

GitHub applique une limite indicative d'environ 1 Go par dépôt, et de 100 Mo par fichier. Si un site comporte de très gros fichiers multimédias, envisagez de les servir depuis S3 ou un stockage objet, plutôt que de les inclure dans le dépôt Git.

Les options du script​

OptionObligatoireDescription
--patOuiLe jeton d'accès personnel GitHub
--repoOuiLe dépôt cible, au format propriétaire/nom — créé s'il n'existe pas
--sitesOuiLa liste, séparée par des espaces, des répertoires de sites sous /home/
--site-pathNonAssocie un site à un chemin de départ personnalisé (format site:/chemin, répétable)
--app-dirNonAssocie un site à un nom de répertoire d'application PHP personnalisé (format site:répertoire, répétable)
--base-imgNonL'image Docker de base (par défaut : ceduth/php-legacy:8.1)
--dry-runNonPrévisualise sans exécuter

Le script enregistre les identifiants de bases de données dans /tmp/virtualmin-migrate/credentials.txt — en local seulement, jamais versionné. Servez-vous-en lors de la création de vos bases dans Kuploy.