Avec cet article, nous ouvrons une série de publications avec des instructions pratiques sur la façon de nous faciliter la vie (l'opération) et les développeurs dans diverses situations qui se produisent littéralement tous les jours. Tous sont collectés à partir d'une expérience réelle dans la résolution des problèmes des clients et se sont améliorés au fil du temps, mais ne prétendent toujours pas être idéaux - considérez-les davantage comme des idées et des blancs.
Je vais commencer par une «astuce» dans la préparation de grands vidages de base de données comme MySQL et PostgreSQL pour leur déploiement rapide pour divers besoins - tout d'abord, sur les plates-formes pour les développeurs. Le contexte des opérations décrites ci-dessous est notre environnement typique, qui comprend un cluster Kubernetes fonctionnel et l'utilisation de GitLab (et
dapp ) pour CI / CD. C'est parti!

La principale difficulté de Kubernetes lors de l'utilisation de la branche de fonctionnalité est les grandes bases de données, lorsque les développeurs souhaitent tester / démontrer leurs modifications sur une base de données complète (ou presque complète) depuis la production. Par exemple:
- Il existe une application avec une base de données en MySQL pour 1 To et 10 développeurs qui développent leurs propres fonctionnalités.
- Les développeurs veulent des boucles de test individuelles et quelques boucles plus spécifiques pour les tests et / ou les démos.
- De plus, il est nécessaire de restaurer le vidage de nuit de la base de production dans son circuit de test pendant une durée raisonnable - pour reproduire le problème avec le client ou le bogue.
- Enfin, il est possible d'alléger la taille de la base de données d'au moins 150 Go - pas tant que cela, mais tout en économisant de l'espace. C'est-à -dire nous devons encore préparer en quelque sorte la décharge.
Remarque : Habituellement, nous sauvegardons les bases de données MySQL en utilisant innobackupex de Percona, ce qui nous permet de sauvegarder toutes les bases de données et les utilisateurs ... - en bref, tout ce qui peut être nécessaire. C'est un tel exemple qui est examiné plus loin dans l'article, bien que dans le cas général, la façon dont vous effectuez les sauvegardes n'a pas d'importance.Disons que nous avons une sauvegarde de base de données. Que faire ensuite?
Étape 1: préparation d'une nouvelle base de données à partir du vidage
Tout d'abord, nous créerons dans Kubernetes
Deployment , qui se composera de deux conteneurs d'initialisation
(c'est-à -dire de tels conteneurs spéciaux qui s'exécutent avant les foyers d'application et vous permettent d'effectuer la préconfiguration) et d'un foyer.
Mais où le placer? Nous avons une grande base de données (1 To) et nous voulons augmenter dix de ses instances - nous avons besoin d'un serveur avec un grand disque (10+ To). Nous le commandons séparément pour cette tâche et marquons le nœud avec ce serveur avec une
étiquette spéciale
dedicated: non-prod-db
. Dans le mĂŞme temps, nous utiliserons la
tache éponyme, que Kubernetes dira que seules les applications qui lui sont résistantes (ont des
tolérances ) peuvent rouler vers ce nœud, c'est-à -dire traduire Kubernetes dans le langage,
dedicated Equal non-prod-db
.
Ă€ l'aide de
nodeSelector
et des
tolerations
sélectionnez le nœud souhaité (situé sur un serveur avec un grand disque):
nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute"
... et reprenez la description du contenu de ce nœud.
Conteneurs init: get-bindump
Le premier conteneur d'initialisation que nous appellerons
get-bindump
. Il monte
emptyDir
(dans
/var/lib/mysql
), où le vidage de la base de données reçu du serveur de sauvegarde sera ajouté. Pour ce faire, le conteneur contient tout ce dont vous avez besoin: clés SSH, adresses de serveur de sauvegarde. Cette étape dans notre cas prend environ 2 heures.
La description de ce conteneur dans le
déploiement est la suivante:
- name: get-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/get_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: id-rsa mountPath: /root/.ssh
Le script
get_bindump.sh
utilisé dans le conteneur:
Conteneurs init: prepare-bindump
Après avoir téléchargé la sauvegarde, le deuxième conteneur d'initialisation est lancé -
prepare-bindump
. Il exécute
innobackupex --apply-log
(puisque les fichiers sont déjà disponibles dans
/var/lib/mysql
- grâce Ă
emptyDir
de
get-bindump
) et le serveur MySQL démarre.
C'est dans ce conteneur init que nous effectuons toutes les conversions nécessaires vers la base de données, la préparant pour l'application sélectionnée: nous effaçons les tables pour lesquelles elle est autorisée, modifions les accès à l'intérieur de la base de données, etc. Ensuite, nous éteignons le serveur MySQL et archivons simplement l'intégralité de
/var/lib/mysql
dans un fichier tar.gz. En conséquence, le vidage tient dans un fichier de 100 Go, ce qui est déjà un ordre de grandeur plus petit que le 1 To d'origine. Cette étape dure environ 5 heures.
Description du deuxième conteneur d'initialisation dans le
déploiement :
- name: prepare-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/prepare_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: debian-cnf mountPath: /etc/mysql/debian.cnf subPath: debian.cnf
Le script
prepare_bindump.sh
utilisé ressemble à ceci:
Sous
L'accord final est le lancement du foyer principal, qui se produit après l'exécution des conteneurs d'initialisation. Dans pod, nous avons un simple nginx, et via
emtpyDir
compressé et recadré de 100 Go est
emtpyDir
. La fonction de ce nginx est de donner ce vidage.
Configuration du foyer:
- name: nginx image: nginx:alpine resources: requests: memory: "1500Mi" cpu: "400m" lifecycle: preStop: exec: command: ["/usr/sbin/nginx", "-s", "quit"] livenessProbe: httpGet: path: /healthz port: 80 scheme: HTTP timeoutSeconds: 7 failureThreshold: 5 volumeMounts: - name: dump mountPath: /usr/share/nginx/html - name: nginx-config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf readOnly: false volumes: - name: dump emptyDir: {} - name: mysqlbindir emptyDir: {}
Voici à quoi ressemble tout le déploiement avec ses initContainers ... --- apiVersion: apps/v1beta1 kind: Deployment metadata: name: db-dumps spec: strategy: rollingUpdate: maxUnavailable: 0 revisionHistoryLimit: 2 template: metadata: labels: app: db-dumps spec: imagePullSecrets: - name: regsecret nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute" initContainers: - name: get-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/get_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: id-rsa mountPath: /root/.ssh - name: prepare-bindump image: db-dumps imagePullPolicy: Always command: [ "/bin/sh", "-c", "/prepare_bindump.sh" ] resources: limits: memory: "5000Mi" cpu: "1" requests: memory: "5000Mi" cpu: "1" volumeMounts: - name: dump mountPath: /dump - name: mysqlbindir mountPath: /var/lib/mysql - name: log mountPath: /var/log/mysql - name: debian-cnf mountPath: /etc/mysql/debian.cnf subPath: debian.cnf containers: - name: nginx image: nginx:alpine resources: requests: memory: "1500Mi" cpu: "400m" lifecycle: preStop: exec: command: ["/usr/sbin/nginx", "-s", "quit"] livenessProbe: httpGet: path: /healthz port: 80 scheme: HTTP timeoutSeconds: 7 failureThreshold: 5 volumeMounts: - name: dump mountPath: /usr/share/nginx/html - name: nginx-config mountPath: /etc/nginx/nginx.conf subPath: nginx.conf readOnly: false volumes: - name: dump emptyDir: {} - name: mysqlbindir emptyDir: {} - name: log emptyDir: {} - name: id-rsa secret: defaultMode: 0600 secretName: somedb-id-rsa - name: nginx-config configMap: name: somedb-nginx-config - name: debian-cnf configMap: name: somedb-debian-cnf --- apiVersion: v1 kind: Service metadata: name: somedb-db-dump spec: clusterIP: None selector: app: db-dumps ports: - name: http port: 80
Notes supplémentaires:
- Dans notre cas, nous préparons un nouveau vidage chaque nuit en utilisant le travail planifié dans GitLab. C'est-à -dire chaque nuit, ce déploiement se déroule automatiquement, ce qui génère un nouveau vidage et le prépare pour la distribution dans tous les environnements de développeur de test.
- Pourquoi lançons-nous également volume
/dump
dans des conteneurs init (et dans le script il y a une vérification de l'existence de /dump/version.txt
)? Cela se fait au cas où le serveur sous lequel il s'exécute est redémarré. Les conteneurs recommenceront et sans cette vérification, le vidage recommencera le téléchargement. Si nous avons déjà préparé un vidage une fois, puis au prochain démarrage (en cas de redémarrage du serveur), le /dump/version.txt
drapeau /dump/version.txt
en informera. - Qu'est
db-dumps
? Nous le collectons avec dapp et son Dappfile
ressemble Ă ceci:
dimg: "db-dumps" from: "ubuntu:16.04" docker: ENV: TERM: xterm ansible: beforeInstall: - name: "Install percona repositories" apt: deb: https://repo.percona.com/apt/percona-release_0.1-4.xenial_all.deb - name: "Add repository for borgbackup" apt_repository: repo="ppa:costamagnagianfranco/borgbackup" codename="xenial" update_cache=yes - name: "Add repository for mysql 5.6" apt_repository: repo: deb http://archive.ubuntu.com/ubuntu trusty universe state: present update_cache: yes - name: "Install packages" apt: name: "{{`{{ item }}`}}" state: present with_items: - openssh-client - mysql-server-5.6 - mysql-client-5.6 - borgbackup - percona-xtrabackup-24 setup: - name: "Add get_bindump.sh" copy: content: | {{ .Files.Get ".dappfiles/get_bindump.sh" | indent 8 }} dest: /get_bindump.sh mode: 0755 - name: "Add prepare_bindump.sh" copy: content: | {{ .Files.Get ".dappfiles/prepare_bindump.sh" | indent 8 }} dest: /prepare_bindump.sh mode: 0755
Étape 2: lancement de la base de données dans un environnement de développeur
Lors du déploiement de la base de données MySQL dans l'environnement de test du développeur, il a un bouton dans GitLab qui lance le redéploiement
du déploiement avec MySQL avec la stratégie
RollingUpdate.maxUnavailable: 0
:

Comment est-ce mis en œuvre?Dans GitLab, lorsque vous cliquez sur
reload db , le
déploiement avec la spécification suivante est déployé:
spec: strategy: rollingUpdate: maxUnavailable: 0
C'est-Ă -dire nous demandons Ă Kubernetes de mettre Ă jour le
déploiement (en créer un nouveau sous) et de nous assurer qu'au moins un sous est actif. Étant donné que lors de la création d'un nouveau foyer, il contient des conteneurs d'initialisation pendant qu'ils fonctionnent, le nouveau
ne passe pas en état de fonctionnement, ce qui signifie que l'ancien continue de fonctionner. Et seulement au moment où MySQL lui-même a démarré (et que la sonde de préparation a fonctionné), le trafic y bascule et l'ancienne (avec l'ancienne base de données) est supprimée.
Les détails sur ce programme peuvent être trouvés dans les documents suivants:
L'approche choisie nous permet d'attendre qu'un nouveau dump soit téléchargé, décompressé et lancé, et ce n'est qu'après que l'ancien sera supprimé de MySQL. Ainsi, pendant que nous préparons une nouvelle décharge, nous travaillons tranquillement avec l'ancienne base.
Le conteneur init de ce
déploiement utilise la commande suivante:
curl "$DUMP_URL" | tar -C /var/lib/mysql/ -xvz
C'est-à -dire nous téléchargeons le vidage de la base de données compressée qui a été préparé à l'étape 1, décompressons-le dans
/var/lib/mysql
, puis démarre sous
Déploiement , dans lequel MySQL est lancé avec les données déjà préparées. Tout cela prend environ 2 heures.
Et le déploiement est le suivant ... apiVersion: apps/v1beta1 kind: Deployment metadata: name: mysql spec: strategy: rollingUpdate: maxUnavailable: 0 template: metadata: labels: service: mysql spec: imagePullSecrets: - name: regsecret nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute" initContainers: - name: getdump image: mysql-with-getdump command: ["/usr/local/bin/getdump.sh"] resources: limits: memory: "6000Mi" cpu: "1.5" requests: memory: "6000Mi" cpu: "1.5" volumeMounts: - mountPath: /var/lib/mysql name: datadir - mountPath: /etc/mysql/debian.cnf name: debian-cnf subPath: debian.cnf env: - name: DUMP_URL value: "http://somedb-db-dump.infra-db.svc.cluster.local/mysql_bindump.tar.gz" containers: - name: mysql image: mysql:5.6 resources: limits: memory: "1024Mi" cpu: "1" requests: memory: "1024Mi" cpu: "1" lifecycle: preStop: exec: command: ["/etc/init.d/mysql", "stop"] ports: - containerPort: 3306 name: mysql protocol: TCP volumeMounts: - mountPath: /var/lib/mysql name: datadir - mountPath: /etc/mysql/debian.cnf name: debian-cnf subPath: debian.cnf env: - name: MYSQL_ROOT_PASSWORD value: "password" volumes: - name: datadir emptyDir: {} - name: debian-cnf configMap: name: somedb-debian-cnf --- apiVersion: v1 kind: Service metadata: name: mysql spec: clusterIP: None selector: service: mysql ports: - name: mysql port: 3306 protocol: TCP --- apiVersion: v1 kind: ConfigMap metadata: name: somedb-debian-cnf data: debian.cnf: | [client] host = localhost user = debian-sys-maint password = password socket = /var/run/mysqld/mysqld.sock [mysql_upgrade] host = localhost user = debian-sys-maint password = password socket = /var/run/mysqld/mysqld.sock
Résumé
Il s'avère que nous avons toujours le
déploiement , qui se déroule tous les soirs et fait ce qui suit:
- Obtient un nouveau vidage de base de données
- en quelque sorte, il le prépare à un fonctionnement correct dans un environnement de test (par exemple, trankeytit certaines tables, remplace les données réelles des utilisateurs, rend les utilisateurs nécessaires, etc.);
- donne à chaque développeur la possibilité de déployer une telle base de données préparée dans son espace de noms dans Deployment en appuyant sur un bouton dans CI - grâce au service qui y est disponible, la base de données sera disponible sur
mysql
(par exemple, il peut s'agir du nom du service dans l'espace de noms).
Pour l'exemple que nous avons examiné, la création d'un vidage à partir d'une réplique réelle prend environ 6 heures, la préparation d'une «image de base» prend 7 heures et la mise à jour de la base de données dans l'environnement du développeur prend 2 heures. Étant donné que les deux premières actions sont effectuées «en arrière-plan» et sont invisibles pour les développeurs, en fait, ils peuvent déployer une version de production de la base de données (d'une taille de 1 To)
pendant les mĂŞmes 2 heures .
Les questions, critiques et corrections du schéma proposé et de ses composantes sont les bienvenues dans les commentaires!
PS Bien sûr, nous comprenons que dans le cas de VMware et de certains autres outils, il serait possible de créer un instantané d'une machine virtuelle et de lancer un nouveau virusalka à partir d'un instantané (ce qui est encore plus rapide), mais cette option n'inclut pas la préparation de la base, compte tenu du fait qu'elle se révélera à peu près la même temps ... Sans oublier le fait que tout le monde n'a pas la possibilité ou le désir d'utiliser des produits commerciaux.
PPS
Autre du cycle de trucs et astuces K8:
Lisez aussi dans notre blog: