Kiat & trik Kubernetes: mempercepat bootstrap dari database besar

Dengan artikel ini, kami membuka serangkaian publikasi dengan instruksi praktis tentang bagaimana membuat hidup lebih mudah bagi kami (operasi) dan pengembang dalam berbagai situasi yang terjadi secara harfiah setiap hari. Semuanya dikumpulkan dari pengalaman nyata dalam memecahkan masalah dari klien dan telah meningkat dari waktu ke waktu, tetapi masih tidak mengklaim sebagai ideal - menganggapnya lebih sebagai ide dan kosong.

Saya akan mulai dengan "trik" dalam mempersiapkan dump database besar seperti MySQL dan PostgreSQL untuk penyebaran cepat mereka untuk berbagai kebutuhan - terutama, pada platform untuk pengembang. Konteks operasi yang dijelaskan di bawah ini adalah lingkungan khas kami, yang meliputi kluster Kubernet yang berfungsi dan penggunaan GitLab (dan dapp ) untuk CI / CD. Ayo pergi!



Kesulitan utama dalam Kubernetes ketika menggunakan fitur cabang adalah database besar, ketika pengembang ingin menguji / menunjukkan perubahan mereka pada database penuh (atau hampir lengkap) dari produksi. Sebagai contoh:

  • Ada aplikasi dengan database di MySQL untuk 1 TB dan 10 pengembang yang mengembangkan fitur mereka sendiri.
  • Pengembang menginginkan loop tes individual dan beberapa loop lebih spesifik untuk tes dan / atau demo.
  • Selain itu, ada kebutuhan untuk mengembalikan dump malam dari basis produksi di sirkuit pengujian untuk waktu yang waras - untuk mereproduksi masalah dengan klien atau bug.
  • Akhirnya, dimungkinkan untuk meringankan ukuran database setidaknya 150 GB - tidak begitu banyak, tetapi masih menghemat ruang. Yaitu kita masih perlu entah bagaimana mempersiapkan tempat sampah.

Catatan : Biasanya kita mencadangkan basis data MySQL menggunakan innobackupex Percona, yang memungkinkan kita menyimpan semua basis data dan pengguna ... - singkatnya, semua yang mungkin diperlukan. Ini adalah contoh yang dipertimbangkan lebih lanjut dalam artikel, meskipun dalam kasus umum tidak masalah bagaimana Anda membuat cadangan.

Jadi, katakanlah kita memiliki cadangan basis data. Apa yang harus dilakukan selanjutnya?

Langkah 1: Mempersiapkan database baru dari dump


Pertama-tama, kita akan membuat dalam Penyebaran Kubernetes, yang akan terdiri dari dua kontainer init (mis., Wadah khusus yang berjalan sebelum perapian aplikasi dan memungkinkan Anda untuk melakukan pra-konfigurasi) dan satu perapian.

Tapi di mana menempatkannya? Kami memiliki basis data besar (1 TB) dan kami ingin meningkatkan sepuluh instansnya - kami membutuhkan server dengan disk besar (10+ TB). Kami memesannya secara terpisah untuk tugas ini dan menandai simpul dengan server ini dengan label khusus yang dedicated: non-prod-db . Pada saat yang sama, kita akan menggunakan eponymous taint, yang akan dikatakan Kubernetes bahwa hanya aplikasi yang resisten (memiliki toleransi ) yang dapat bergulir ke simpul ini, mis., Menerjemahkan Kubernetes ke dalam bahasa, dedicated Equal non-prod-db .

Menggunakan nodeSelector dan tolerations pilih node yang diinginkan (terletak di server dengan disk besar):

  nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute" 

... dan ambil uraian isi simpul ini.

Kontainer Init: get-bindump


Wadah init pertama yang akan kita sebut get-bindump . emptyDir (in /var/lib/mysql ), di mana dump basis data yang diterima dari server cadangan akan ditambahkan. Untuk melakukan ini, wadah memiliki semua yang Anda butuhkan: kunci SSH, alamat server cadangan. Tahap ini dalam kasus kami membutuhkan waktu sekitar 2 jam.

Deskripsi wadah ini di Penempatan adalah sebagai berikut:

  - 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 

Skrip get_bindump.sh digunakan dalam wadah:

 #!/bin/bash date if [ -f /dump/version.txt ]; then echo "Dump file already exists." exit 0 fi rm -rf /var/lib/mysql/* borg extract --stdout user@your.server.net:somedb-mysql::${lastdump} stdin | xbstream -x -C /var/lib/mysql/ echo $lastdump > /dump/version.txt 

Kontainer Init: persiapan-bindump


Setelah mengunduh cadangan, wadah init kedua diluncurkan - prepare-bindump . Ini mengeksekusi innobackupex --apply-log (karena file sudah tersedia di /var/lib/mysql - berkat emptyDir dari get-bindump ) dan server MySQL mulai.

Dalam wadah init inilah kita melakukan semua konversi yang diperlukan ke basis data, menyiapkannya untuk aplikasi yang dipilih: kita membersihkan tabel yang diizinkan, mengubah akses di dalam basis data, dll. Kemudian kita mematikan server MySQL dan cukup mengarsipkan seluruh /var/lib/mysql ke file tar.gz. Sebagai hasilnya, dump cocok dengan file 100 GB, yang sudah menjadi urutan besarnya lebih kecil dari 1 TB asli. Tahap ini memakan waktu sekitar 5 jam.

Deskripsi wadah init kedua dalam Penerapan :

  - 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 

Skrip prepare_bindump.sh digunakan di dalamnya terlihat seperti ini:

 #!/bin/bash date if [ -f /dump/healthz ]; then echo "Dump file already exists." exit 0 fi innobackupex --apply-log /var/lib/mysql/ chown -R mysql:mysql /var/lib/mysql chown -R mysql:mysql /var/log/mysql echo "`date`: Starting mysql" /usr/sbin/mysqld --character-set-server=utf8 --collation-server=utf8_general_ci --innodb-data-file-path=ibdata1:200M:autoextend --user=root --skip-grant-tables & sleep 200 echo "`date`: Creating mysql root user" echo "update mysql.user set Password=PASSWORD('password') WHERE user='root';" | mysql -uroot -h 127.0.0.1 echo "delete from mysql.user where USER like '';" | mysql -uroot -h 127.0.0.1 echo "delete from mysql.user where user = 'root' and host NOT IN ('127.0.0.1', 'localhost');" | mysql -uroot -h 127.0.0.1 echo "FLUSH PRIVILEGES;" | mysql -uroot -h 127.0.0.1 echo "truncate somedb.somedb_table_one;" | mysql -uroot -h 127.0.0.1 -ppassword somedb /usr/bin/mysqladmin shutdown -uroot -ppassword cd /var/lib/mysql/ tar -czf /dump/mysql_bindump.tar.gz ./* touch /dump/healthz rm -rf /var/lib/mysql/* 

Di bawah


Akord terakhir adalah peluncuran perapian utama, yang terjadi setelah kontainer init dieksekusi. Di pod, kami memiliki nginx sederhana, dan melalui emtpyDir terkompresi dan terpangkas 100 GB emtpyDir . Fungsi nginx ini adalah untuk memberikan dump ini.

Konfigurasi perapian:

  - 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: {} 

Inilah tampilan seluruh Penempatan dengan 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 

Catatan tambahan:

  1. Dalam kasus kami, kami menyiapkan tempat pembuangan baru setiap malam menggunakan pekerjaan yang dijadwalkan di GitLab. Yaitu setiap malam, Penyebaran ini secara otomatis diluncurkan, yang menarik dump baru dan menyiapkannya untuk distribusi ke semua lingkungan pengembang pengujian.
  2. Mengapa kita juga membuang volume /dump ke dalam wadah init (dan di dalam skrip terdapat pemeriksaan untuk keberadaan /dump/version.txt )? Ini dilakukan jika server yang dijalankannya di-restart. Kontainer akan mulai lagi dan tanpa pemeriksaan ini, dump akan mulai mengunduh lagi. Jika kita sudah menyiapkan dump sekali, maka pada awal berikutnya (jika server reboot), /dump/version.txt flag /dump/version.txt akan menginformasikan tentang ini.
  3. Apa gambar db-dumps ? Kami mengumpulkannya dengan dapp dan Dappfile-nya terlihat seperti ini:

     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 

Langkah 2: Meluncurkan basis data di lingkungan pengembang


Saat meluncurkan basis data MySQL di lingkungan pengujian pengembang, ia memiliki tombol di GitLab yang meluncurkan penempatan kembali Penempatan dengan MySQL dengan RollingUpdate.maxUnavailable: 0 yang tersedia RollingUpdate.maxUnavailable: 0 :



Bagaimana ini diterapkan?
Di GitLab, ketika Anda mengklik pada reload db , Deployment dengan spesifikasi berikut digunakan:

 spec: strategy: rollingUpdate: maxUnavailable: 0 

Yaitu kami memberi tahu Kubernetes untuk memperbarui Penempatan (buat yang baru di bawah) dan pastikan bahwa setidaknya satu di bawah adalah siaran langsung. Karena ketika membuat perapian baru, ia memiliki wadah init saat mereka bekerja, yang baru tidak masuk ke status Running , yang berarti bahwa yang lama terus bekerja. Dan hanya pada saat MySQL itu sendiri mulai (dan probe kesiapan bekerja), lalu lintas beralih ke sana, dan yang lama (dengan database lama) dihapus.

Detail tentang skema ini dapat ditemukan dalam materi berikut:


Pendekatan yang dipilih memungkinkan kita untuk menunggu sampai dump baru diunduh, dibuka ritsleting dan diluncurkan, dan hanya setelah itu dump yang lama akan dihapus dari MySQL. Jadi, saat kami sedang mempersiapkan tempat pembuangan baru, kami bekerja dengan diam-diam dengan pangkalan lama.

Wadah init dari Penempatan ini menggunakan perintah berikut:

 curl "$DUMP_URL" | tar -C /var/lib/mysql/ -xvz 

Yaitu kami mengunduh dump database terkompresi yang disiapkan pada langkah 1, unzip ke /var/lib/mysql , dan kemudian mulai di bawah Penempatan , di mana MySQL diluncurkan dengan data yang sudah disiapkan. Semua ini memakan waktu sekitar 2 jam.

Dan Penempatan adalah sebagai berikut ...
 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 

Ringkasan


Ternyata kami selalu memiliki Penyebaran , yang diluncurkan setiap malam dan melakukan hal berikut:

  • Mendapat dump database baru
  • entah bagaimana ia mempersiapkannya untuk operasi yang benar dalam lingkungan pengujian (misalnya, trankeytit beberapa tabel, menggantikan data pengguna nyata, membuat pengguna yang diperlukan, dll.);
  • memberikan setiap pengembang kesempatan untuk meluncurkan basis data yang telah disiapkan ke namespace mereka di Deployment dengan menekan tombol di CI - berkat Layanan yang tersedia di dalamnya, basis data akan tersedia di mysql (misalnya, mungkin itu adalah nama layanan di namespace).

Sebagai contoh yang kami teliti, membuat dump dari replika asli membutuhkan waktu sekitar 6 jam, menyiapkan "gambar dasar" membutuhkan waktu 7 jam, dan memperbarui basis data di lingkungan pengembang membutuhkan waktu 2 jam. Karena dua tindakan pertama dilakukan "di latar belakang" dan tidak terlihat oleh pengembang, sebenarnya mereka dapat menggunakan versi produksi dari basis data (dengan ukuran 1 TB) selama 2 jam yang sama .

Pertanyaan, kritik dan koreksi terhadap skema yang diusulkan dan komponennya disambut dalam komentar!

PS Tentu saja, kami memahami bahwa dalam kasus VMware dan beberapa alat lainnya, adalah mungkin untuk membuat snapshot dari mesin virtual dan meluncurkan virusalka baru dari snapshot (yang bahkan lebih cepat), tetapi opsi ini tidak termasuk menyiapkan pangkalan, dengan mempertimbangkan yang akan berubah hampir sama waktu ... Belum lagi fakta bahwa tidak semua orang memiliki kesempatan atau keinginan untuk menggunakan produk komersial.

PPS


Lainnya dari siklus tips & trik K8:


Baca juga di blog kami:

Source: https://habr.com/ru/post/id417509/


All Articles