مع هذه المقالة ، نفتح سلسلة من المنشورات مع تعليمات عملية حول كيفية جعل الحياة أسهل لأنفسنا (العملية) والمطورين في مواقف مختلفة تحدث حرفيا كل يوم. يتم جمعهم جميعًا من الخبرة الحقيقية في حل المشكلات من العملاء وقد تحسنوا بمرور الوقت ، ولكن لا يزالون لا يدعون أنهم مثاليون - اعتبرهم أكثر كأفكار وفراغات.
سأبدأ بـ "خدعة" في إعداد مقالب قاعدة البيانات الكبيرة مثل MySQL و PostgreSQL لنشرها السريع لتلبية الاحتياجات المختلفة - أولاً وقبل كل شيء ، على الأنظمة الأساسية للمطورين. سياق العمليات الموضحة أدناه هو بيئتنا النموذجية ، والتي تتضمن مجموعة Kubernetes عاملة واستخدام GitLab (و
dapp ) لـ CI / CD. دعنا نذهب!

الألم الرئيسي في Kubernetes عند استخدام فرع الميزة هو قواعد البيانات الكبيرة ، عندما يرغب المطورون في اختبار / إظهار تغييراتهم في قاعدة بيانات كاملة (أو شبه كاملة) من الإنتاج. على سبيل المثال:
- يوجد تطبيق بقاعدة بيانات في MySQL لـ 1 تيرابايت و 10 مطورين يقومون بتطوير ميزاتهم الخاصة.
- يريد المطورون حلقات اختبار فردية وزوجان أكثر تحديدًا للاختبارات و / أو العروض التوضيحية.
- بالإضافة إلى ذلك ، هناك حاجة إلى استعادة التفريغ الليلي لقاعدة الإنتاج في دارة الاختبار الخاصة بها لفترة معقولة - لإعادة إنتاج المشكلة مع العميل أو الخطأ.
- أخيرًا ، من الممكن تفتيح حجم قاعدة البيانات بما لا يقل عن 150 جيجابايت - ليس كثيرًا ، ولكن لا يزال يوفر مساحة. على سبيل المثال ما زلنا بحاجة إلى إعداد تفريغ بطريقة أو بأخرى.
ملاحظة : عادة ما نقوم بعمل نسخة احتياطية لقواعد بيانات MySQL باستخدام innobackupex الخاص بـ Percona ، والذي يسمح لنا بحفظ جميع قواعد البيانات والمستخدمين ... - باختصار ، كل ما قد يكون مطلوبًا. إنه مثل هذا المثال الذي يتم النظر فيه بشكل أكبر في المقالة ، على الرغم من أنه في الحالة العامة لا يهم بالضبط كيفية إجراء النسخ الاحتياطية.لذا ، لنفترض أن لدينا نسخة احتياطية لقاعدة البيانات. ماذا تفعل بعد ذلك؟
الخطوة 1: تحضير قاعدة بيانات جديدة من التفريغ
بادئ ذي بدء ، سننشئ في Kubernetes
Deployment ، والتي ستتألف من حاويتين تهيئة
(على سبيل المثال ، حاويات خاصة يتم تشغيلها قبل جلسات استماع
التطبيق وتسمح لك بإجراء التهيئة المسبقة) وموقد واحد.
لكن أين نضعها؟ لدينا قاعدة بيانات كبيرة (1 تيرابايت) ونريد رفع عشرة من مثيلاتها - نحن بحاجة إلى خادم بقرص كبير (10 تيرابايت). نطلبها بشكل منفصل لهذه المهمة ونضع علامة على العقدة مع هذا الخادم
بتسمية خاصة
dedicated: non-prod-db
. في الوقت نفسه ، سنستخدم
العيب المسمى ، والذي سيقول Kubernetes أنه فقط التطبيقات المقاومة (التي لديها
تحمل ) لها يمكن أن تتدحرج إلى هذه العقدة ، أي ترجمة Kubernetes إلى اللغة ،
dedicated Equal non-prod-db
.
باستخدام
nodeSelector
tolerations
حدد العقدة المطلوبة (الموجودة على خادم به قرص كبير):
nodeSelector: dedicated: non-prod-db tolerations: - key: "dedicated" operator: "Equal" value: "non-prod-db" effect: "NoExecute"
... وتناول وصف محتويات هذه العقدة.
الحاويات الأولية: get-bindump
أول حاوية init
get-bindump
. يتم
emptyDir
(في
/var/lib/mysql
) ، حيث سيتم إضافة تفريغ قاعدة البيانات المستلمة من خادم النسخ الاحتياطي. للقيام بذلك ، تحتوي الحاوية على كل ما تحتاجه: مفاتيح SSH وعناوين خادم النسخ الاحتياطي. تستغرق هذه المرحلة في حالتنا حوالي ساعتين.
وصف هذه الحاوية في
النشر هو كما يلي:
- 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
البرنامج النصي
get_bindump.sh
المستخدم في الحاوية:
حاويات التهيئة الأولية: تحضير- bindump
بعد تنزيل النسخة الاحتياطية ، يتم إطلاق حاوية التهيئة الثانية -
prepare-bindump
. ينفذ
innobackupex --apply-log
(حيث أن الملفات متاحة بالفعل في
/var/lib/mysql
- بفضل
emptyDir
من
get-bindump
) ويبدأ خادم MySQL.
في حاوية التهيئة هذه نقوم بكل التحويلات اللازمة لقاعدة البيانات ، ونجهزها للتطبيق المحدد: نمسح الجداول المسموح بها ، ونغير الوصول داخل قاعدة البيانات ، وما إلى ذلك. ثم نقوم بإيقاف تشغيل خادم MySQL وأرشفت الملف
/var/lib/mysql
بالكامل إلى ملف tar.gz. ونتيجة لذلك ، يلائم التفريغ ملف 100 غيغابايت ، وهو بالفعل حجم أصغر من 1 تيرابايت الأصلي. تستغرق هذه المرحلة حوالي 5 ساعات.
وصف حاوية التهيئة الثانية في
النشر :
- 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
prepare_bindump.sh
البرنامج النصي
prepare_bindump.sh
المستخدم فيه كالتالي:
تحت
الوتر النهائي هو إطلاق الموقد الرئيسي ، والذي يحدث بعد تنفيذ حاويات التهيئة. في pod ، لدينا nginx بسيط ، ومن خلال
emtpyDir
تفريغ مضغوط
emtpyDir
من 100 غيغابايت. وظيفة nginx هذه هي إعطاء هذا التفريغ.
تكوين الموقد:
- 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: {}
إليك ما يبدو عليه النشر بالكامل مع 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
ملاحظات إضافية:
- في حالتنا ، نقوم بإعداد مكب جديد كل ليلة باستخدام الوظيفة المجدولة في GitLab. على سبيل المثال كل ليلة ، يتم نشر هذا النشر تلقائيًا ، مما يسحب تفريغًا جديدًا ويجهزه للتوزيع على جميع بيئات اختبار المطورين.
- لماذا نقوم أيضًا بإلقاء المجلد
/dump
في حاويات التهيئة (وفي البرنامج النصي هناك تحقق من وجود /dump/version.txt
)؟ يتم ذلك في حالة إعادة تشغيل الخادم الذي يعمل تحته. ستبدأ الحاويات من جديد وبدون هذا الفحص ، سيبدأ تنزيل التفريغ مرة أخرى. إذا قمنا بالفعل بإعداد ملف تفريغ مرة واحدة ، في البداية التالية (في حالة إعادة تشغيل الخادم) ، سيتم إبلاغ /dump/version.txt
بهذا. - ما هي
db-dumps
؟ نجمعها مع Dappfile
ويبدو Dappfile
الخاص به كما يلي:
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
الخطوة 2: تشغيل قاعدة البيانات في بيئة مطور
عند طرح قاعدة بيانات MySQL في بيئة اختبار المطور ، فإنه يحتوي على زر في GitLab يشغل إعادة
نشر النشر باستخدام MySQL باستخدام استراتيجية
RollingUpdate.maxUnavailable: 0
:

كيف يتم تنفيذ ذلك؟في GitLab ، عند النقر فوق
إعادة تحميل ديسيبل ،
يتم نشر النشر بهذه المواصفات:
spec: strategy: rollingUpdate: maxUnavailable: 0
على سبيل المثال نقول Kubernetes لتحديث
النشر (إنشاء واحدة جديدة تحتها) والتأكد من أن واحدًا على الأقل تحت البث المباشر. منذ إنشاء موقد جديد يحتوي على حاويات أولية أثناء العمل ،
لا تدخل الحالة الجديدة في حالة
التشغيل ، مما يعني أن القديمة تستمر في العمل. وفقط في اللحظة التي بدأ فيها MySQL نفسه (وعمل مسبار الاستعداد) ، تحولت حركة المرور إليه ، وتم حذف القديم (مع قاعدة البيانات القديمة).
يمكن العثور على تفاصيل حول هذا المخطط في المواد التالية:
يسمح لنا النهج المختار بالانتظار حتى يتم تنزيل ملف تفريغ جديد وفك ضغطه وتشغيله ، وبعد ذلك فقط سيتم حذف الملف القديم من MySQL. وهكذا ، بينما نقوم بإعداد مكب جديد ، نحن نعمل بهدوء مع القاعدة القديمة.
تستخدم حاوية التهيئة لهذا
النشر الأمر التالي:
curl "$DUMP_URL" | tar -C /var/lib/mysql/ -xvz
على سبيل المثال نقوم بتنزيل ملف تفريغ قاعدة البيانات المضغوطة الذي تم تحضيره في الخطوة 1 ، وفك ضغطه إلى
/var/lib/mysql
، ثم يبدأ تحت
النشر ، حيث يتم إطلاق MySQL مع البيانات المعدة بالفعل. كل هذا يستغرق حوالي ساعتين.
والانتشار على النحو التالي ... 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
الملخص
اتضح أن لدينا دائمًا
نشر ، يتم نشره كل ليلة ويقوم بما يلي:
- يحصل على تفريغ قاعدة بيانات جديدة
- بطريقة ما تقوم بإعدادها للتشغيل الصحيح في بيئة اختبار (على سبيل المثال ، trankeytit بعض الجداول ، واستبدال بيانات المستخدم الحقيقية ، وجعل المستخدمين الضروريين ، وما إلى ذلك) ؛
- يوفر لكل مطور فرصة طرح قاعدة بيانات معدة على مساحة الاسم الخاصة بهم في النشر عن طريق الضغط على زر في CI - بفضل الخدمة المتوفرة فيه ، ستكون قاعدة البيانات متاحة على
mysql
(على سبيل المثال ، قد يكون اسم الخدمة في مساحة الاسم).
بالنسبة للمثال الذي فحصناه ، يستغرق إنشاء ملف تفريغ من نسخة متماثلة حقيقية حوالي 6 ساعات ، وإعداد "الصورة الأساسية" يستغرق 7 ساعات ، وتحديث قاعدة البيانات في بيئة المطور يستغرق ساعتين. نظرًا لأنه يتم تنفيذ الإجراءين الأولين "في الخلفية" وغير مرئي للمطورين ، في الواقع يمكنهم نشر إصدار إنتاج من قاعدة البيانات (بحجم 1 تيرابايت)
لنفس الساعتين .
الأسئلة والنقد والتصحيحات على المخطط المقترح ومكوناته موضع ترحيب في التعليقات!
PS بالطبع ، نحن نتفهم أنه في حالة VMware وبعض الأدوات الأخرى ، قد يكون من الممكن إنشاء لقطة افتراضية وإطلاق فيروس جديد من اللقطة (وهو أسرع) ، ولكن هذا الخيار لا يتضمن إعداد القاعدة ، مع الأخذ في الاعتبار أنه سيظهر حول نفس الشيء الوقت ... ناهيك عن حقيقة أنه ليس لدى الجميع الفرصة أو الرغبة في استخدام المنتجات التجارية.
PPS
أخرى من دورة النصائح والحيل K8s:
اقرأ أيضا في مدونتنا: