Kubernetes ConfigMaps: الفروق التي يجب معرفتها

ملاحظة : هذه ليست مقالة إرشادية كاملة ، بل هي تذكير / تلميح لأولئك الذين يستخدمون ConfigMap بالفعل في Kubernetes أو يقومون فقط بإعداد تطبيقهم للعمل فيه.



الخلفية: من rsync إلى ... Kubernetes


ماذا حدث من قبل؟ في عصر "الإدارة الكلاسيكية" ، في أبسط إصدار ، تم وضع ملف التكوين بجوار التطبيقات مباشرة (أو في المستودع ، إذا أردت). الأمر بسيط: نحن نجعل التسليم الأولي (CD) لرمزنا مع التكوين. حتى تطبيق rsync الشرطي يمكن أن يسمى أساسيات القرص المضغوط.

عندما نمت البنية التحتية ، كانت هناك حاجة إلى تكوينات مختلفة لبيئات مختلفة (dev / stage / production). تم تدريب التطبيق على فهم التكوين الذي سيتم استخدامه ، وتمريرها كوسيطة للبدء أو متغيرات البيئة المعينة في البيئة. حتى المزيد من القرص المضغوط معقد مع ظهور مثل هذا الشيف / العرائس / Ansible المفيد. تظهر الأدوار في الخوادم ، ويتوقف وصف البيئات في أماكن مختلفة - نأتي إلى IaC (البنية التحتية كرمز).

ما أعقب ذلك؟ إذا كان من الممكن أن نرى مزايا Kubernetes الحاسمة لنفسها وحتى تتصالح مع الحاجة إلى تعديل التطبيقات للعمل في هذه البيئة ، حدث الترحيل. في الطريق ، توقعت الكثير من الفروق الدقيقة والاختلافات في بناء الهندسة المعمارية ، ولكن عندما تمكنت من التعامل مع الجزء الرئيسي ، حصلت على التطبيق الذي طال انتظاره يعمل في K8s.

مرة واحدة هنا ، لا يزال بإمكاننا استخدام التكوينات المعدة في المستودع بجوار التطبيق أو تمرير ENV إلى الحاوية. ومع ذلك ، بالإضافة إلى هذه الأساليب ، تتوفر ConfigMaps أيضًا . يتيح لك K8s البدائي استخدام قوالب Go في التكوينات ، أي تقديمها مثل صفحات HTML وإعادة تحميل التطبيق عند تغيير التكوين دون إعادة التشغيل. مع ConfigMaps ، لم تعد هناك حاجة للحفاظ على 3+ تكوينات لبيئات مختلفة وتتبع مدى ملاءمة كل منها.

يمكن العثور على مقدمة عامة عن ConfigMaps ، على سبيل المثال ، هنا . وسأركز في هذه المقالة على بعض ميزات العمل معهم.

تكوينات بسيطة


كيف تبدو التكوينات في Kubernetes؟ ما الذي حصلوا عليه من قوالب الذهاب؟ على سبيل المثال ، فيما يلي ملف ConfigMap عادي لتطبيق تم نشره من مخطط Helm:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app
data:
  config.json: |
    {
      "welcome": {{ pluck .Values.global.env .Values.welcome | quote }},
      "name": {{ pluck .Values.global.env .Values.name | quote }}
    }

هنا القيم استبداله في .Values.welcomeو .Values.nameستتخذ من الملف values.yaml. لماذا بالضبط values.yaml؟ كيف يعمل محرك قالب Go؟ لقد تحدثنا بالفعل عن هذه التفاصيل بمزيد من التفاصيل هنا . تساعد

المكالمة pluckعلى تحديد الخط الضروري من الخريطة:

$ cat .helm/values.yaml 
welcome:
  production: "Hello"
  test: "Hey"
name:
  production: "Bob"
  test: "Mike"

علاوة على ذلك ، يمكنك أخذ كل من خطوط محددة وأجزاء كاملة من التكوين.

على سبيل المثال ، قد يكون ConfigMap كما يلي:

data:
  config.json: |
    {{ pluck .Values.global.env .Values.data | first | toJson | indent 4 }}

... وفي values.yaml- المحتويات التالية:

data:
  production:
    welcome: "Hello"
    name: "Bob"

المعني هنا global.envهو اسم البيئة. باستبدال هذه القيمة أثناء النشر ، يمكنك تقديم ConfigMaps بمحتوى مختلف. firstمطلوب هنا بسبب pluckتقوم بإرجاع قائمة ، يحتوي العنصر الأول منها على القيمة المطلوبة.

عندما يكون هناك الكثير من التكوينات


يمكن أن يحتوي ConfigMap واحد على العديد من ملفات التكوين:

data:
  config.json: |
    {
      "welcome": {{ pluck .Values.global.env .Values.welcome | first | quote }},
      "name": {{ pluck .Values.global.env .Values.name | first | quote }}
    }
  database.yml: |
    host: 127.0.0.1
    db: app
    user: app
    password: app

يمكنك حتى تثبيت كل تكوين على حدة:

        volumeMounts:
        - name: app-conf
          mountPath: /app/configfiles/config.json
          subPath: config.json
        - name: app-conf
          mountPath: /app/configfiles/database.yml
          subPath: database.yml

... أو احصل على جميع التكوينات مرة واحدة مع الدليل:

        volumeMounts:
        - name: app-conf
          mountPath: /app/configfiles

إذا قمت بتغيير وصف مورد النشر أثناء النشر ، فسيقوم Kubernetes بإنشاء ReplicaSet جديدة ، وتقليل القديم إلى 0 وزيادة الجديد إلى العدد المحدد من النسخ المتماثلة. (هذا صحيح بالنسبة لاستراتيجية النشر RollingUpdate.)

ستؤدي هذه الإجراءات إلى إعادة إنشاء لوحة مع وصف جديد. على سبيل المثال: كانت هناك صورة image:my-registry.example.com:v1لكنها أصبحت - image:my-registry.example.com:v2. ولا يهم على الإطلاق ما غيرناه تمامًا في وصف النشر لدينا: الشيء الرئيسي هو أن هذا تسبب في إعادة إنشاء النسخة المتماثلة (ونتيجة لذلك ، إعادة إنشاء البودرة). في هذه الحالة ، يتم تحميل الإصدار الجديد من ملف التكوين في الإصدار الجديد من التطبيق تلقائيًا ولن تكون هناك مشكلة.

استجابة تغيير ConfigMap


في حالة حدوث تغييرات في ConfigMap ، يمكن متابعة أربعة سيناريوهات للأحداث. اعتبرهم:

  1. : ConfigMap, subPath.
    : .
  2. : ConfigMap, pod.
    : pod .
  3. : ConfigMap Deployment -.
    : , ConfigMap’, Deployment, pod , — .
  4. : ConfigMap, .
    : pod’ / pod’.

سنحلل بمزيد من التفصيل.

السيناريو 1


نحن تصحيح تصحيح ConfigMap فقط ؟ لن يتم إعادة تشغيل التطبيق. في حالة التثبيت ، subPathلن تكون هناك تغييرات حتى يتم إعادة تشغيل اللوحة يدويًا.

كل شيء بسيط: تقوم Kubernetes بتثبيت ConfigMap الخاص بنا في جراب من إصدار محدد من المورد. نظرًا لأنه تم تثبيته مع subPath، لم يعد يتم توفير "تأثير" إضافي على التكوين.

السيناريو 2


لا يمكنك تحديث الملف دون إعادة إنشاء جراب؟ حسنًا ، لدينا 6 نسخ متماثلة في النشر ، حتى نتمكن من التناوب في القيام بكل شيء يدويًا delete pod. ثم عند إنشاء منصات جديدة ، سوف "يلتقطون" الإصدار الجديد من ConfigMap.

السيناريو 3


هل سئمت من إجراء مثل هذه العمليات يدويًا؟ يتم وصف حل هذه المشكلة في نصائح وحيل هيلم :

kind: Deployment
spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
[...]

وبالتالي ، تتم كتابة spec.templateتجزئة تكوين التعليقات التوضيحية المقدمة في قالب pod ( ).

التعليقات التوضيحية هي حقول قيم رئيسية عشوائية يمكنك تخزين قيمك فيها. إذا قمت بتسجيلها في قالب spec.templatepod المستقبلي ، ستندرج هذه الحقول في ReplicaSet و pod نفسها. ستلاحظ Kubernetes أن قالب pod قد تغير (منذ أن تم تغيير تهيئة sha256) وسيبدأ RollingUpdate، حيث لا يتغير شيء باستثناء هذا التعليق التوضيحي.

ونتيجة لذلك ، نحفظ نفس الإصدار من تطبيق ووصف النشر ، ونبدأ فقط في إعادة إنشاء الجهاز بواسطة الجهاز - على غرار الطريقة التي سنقوم بها يدويًا kubectl delete، ولكن بالفعل "بشكل صحيح": تلقائيًا وبواسطة RollingUpdate.

السيناريو 4


ربما يكون التطبيق يعرف بالفعل كيفية مراقبة التغييرات في التكوين وإعادة التحميل تلقائيًا؟ هنا تكمن ميزة مهمة في ConfigMaps ...

في Kubernetes ، إذا تم تحميل التكوين subPath، فلن يتم تحديثه حتى يتم إعادة تشغيل pod (راجع السيناريوهات الثلاثة الأولى التي نوقشت أعلاه) . ولكن إذا قمت بتثبيت ConfigMap كدليل ، بدون subPath، ثم داخل الحاوية سيكون هناك دليل مع تكوين محدث دون إعادة تشغيل pod.

هناك ميزات أخرى مفيدة للتذكر:

  • يتم تحديث ملف التكوين المحدث داخل الحاوية مع بعض التأخير. هذا يرجع إلى حقيقة أن الملف لم يتم تحميله بالضبط ، ولكن كائن Kubernetes.
  • الملف الموجود في الداخل هو ارتباط رمزي. مثال مع subPath:

    $ kubectl -n production exec go-conf-example-6b4cb86569-22vqv -- ls -lha /app/configfiles 
    total 20K    
    drwxr-xr-x    1 root     root        4.0K Mar  3 19:34 .
    drwxr-xr-x    1 app      app         4.0K Mar  3 19:34 ..
    -rw-r--r--    1 root     root          42 Mar  3 19:34 config.json
    -rw-r--r--    1 root     root          47 Mar  3 19:34 database.yml

    وماذا سيحدث بدون subPathتركيب الدليل؟

    $ kubectl -n production exec go-conf-example-67c768c6fc-ccpwl -- ls -lha /app/configfiles 
    total 12K    
    drwxrwxrwx    3 root     root        4.0K Mar  3 19:40 .
    drwxr-xr-x    1 app      app         4.0K Mar  3 19:34 ..
    drwxr-xr-x    2 root     root        4.0K Mar  3 19:40 ..2020_03_03_16_40_36.675612011
    lrwxrwxrwx    1 root     root          31 Mar  3 19:40 ..data -> ..2020_03_03_16_40_36.675612011
    lrwxrwxrwx    1 root     root          18 Mar  3 19:40 config.json -> ..data/config.json
    lrwxrwxrwx    1 root     root          19 Mar  3 19:40 database.yml -> ..data/database.yml

    قم بتحديث التكوين (عن طريق kubectl editالنشر أو ) ، وانتظر دقيقتين (وقت التخزين المؤقت apiserver) - وفويلا:

    $ kubectl -n production exec go-conf-example-67c768c6fc-ccpwl -- ls -lha --color /app/configfiles 
    total 12K    
    drwxrwxrwx    3 root     root        4.0K Mar  3 19:44 .
    drwxr-xr-x    1 app      app         4.0K Mar  3 19:34 ..
    drwxr-xr-x    2 root     root        4.0K Mar  3 19:44 ..2020_03_03_16_44_38.763148336
    lrwxrwxrwx    1 root     root          31 Mar  3 19:44 ..data -> ..2020_03_03_16_44_38.763148336
    lrwxrwxrwx    1 root     root          18 Mar  3 19:40 config.json -> ..data/config.json
    lrwxrwxrwx    1 root     root          19 Mar  3 19:40 database.yml -> ..data/database.yml

    لاحظ الطابع الزمني الذي تم تغييره في الدليل الذي تم إنشاؤه بواسطة Kubernetes.

تتبع التغيير


وأخيرًا - مثال بسيط على كيفية مراقبة التغييرات في التكوين.

سوف نستخدم تطبيق Go-application
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/fsnotify/fsnotify"
)

// Config fo our application
type Config struct {
	Welcome string `json:"welcome"`
	Name    string `json:"name"`
}

var (
	globalConfig *Config
)

// LoadConfig - load our config!
func LoadConfig(path string) (*Config, error) {
	configFile, err := os.Open(path)

	if err != nil {
		return nil, fmt.Errorf("Unable to read configuration file %s", path)
	}

	config := new(Config)

	decoder := json.NewDecoder(configFile)
	err = decoder.Decode(&config)
	if err != nil {
		return nil, fmt.Errorf("Unable to parse configuration file %s", path)
	}

	return config, nil
}

// ConfigWatcher - watches config.json for changes
func ConfigWatcher() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	defer watcher.Close()

	done := make(chan bool)
	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				log.Println("event:", event)
				if event.Op&fsnotify.Write == fsnotify.Write {
					log.Println("modified file:", event.Name)
				}
				globalConfig, _ = LoadConfig("./configfiles/config.json")
				log.Println("config:", globalConfig)
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("error:", err)
			}
		}
	}()

	err = watcher.Add("./configfiles/config.json")
	if err != nil {
		log.Fatal(err)
	}
	<-done
}

func main() {
	log.Println("Start")
	globalConfig, _ = LoadConfig("./configfiles/config.json")
	go ConfigWatcher()
	for {
		log.Println("config:", globalConfig)
		time.Sleep(30 * time.Second)
	}
}

... إضافته بمثل هذا التكوين:

$ cat configfiles/config.json 
{
  "welcome": "Hello",
  "name": "Alice"
}

إذا تم تشغيله ، فسيكون السجل:

2020/03/03 22:18:22 config: &{Hello Alice}
2020/03/03 22:18:52 config: &{Hello Alice}

والآن سنقوم بتثبيت هذا التطبيق في Kubernetes ، بعد أن تم تثبيت ConfigMap config في pod بدلاً من الملف من الصورة. تم إعداد مثال على مخطط Helm على GitHub :

helm install -n habr-configmap --namespace habr-configmap ./habr-configmap --set 'name.production=Alice' --set 'global.env=production'

وتغيير ConfigMap فقط:

-  production: "Alice"
+  production: "Bob"

قم بتحديث مخطط Helm في الكتلة ، على سبيل المثال ، مثل:

helm upgrade habr-configmap ./habr-configmap --set 'name.production=Bob' --set 'global.env=production'

ماذا سيحدث؟

  • لا يتم إعادة تشغيل التطبيقات v1 و v2 ، لأن بالنسبة لهم ، لم يحدث أي تغيير في النشر - ما زالوا يرحبون أليس.
  • أعيد تشغيل تطبيق v3 وأعد قراءة التكوين واستقبل بوب.
  • لم يتم إعادة تشغيل تطبيق v4. منذ تحميل ConfigMap كدليل ، لوحظت تغييرات في التكوين وتغيير التكوين على الطاير ، دون إعادة تشغيل جراب. نعم ، لاحظ التطبيق تغييرات في مثالنا البسيط - انظر رسالة الحدث من fsnotify :

    2020/03/03 22:19:15 event: "configfiles/config.json": CHMOD
    2020/03/03 22:19:15 config: &{Hello Bob}
    2020/03/03 22:19:22 config: &{Hello Bob}

يمكنك إلقاء نظرة على كيفية حل موقف مشابه - تتبع تغييرات ConfigMap - في مشروع أكثر الكبار (وببساطة "حقيقي") هنا .

مهم! من المفيد أيضًا أن نتذكر أن كل ما ورد أعلاه في المقالة ينطبق أيضًا على Secret'ov في Kubernetes ( kind: Secret): ليس لشيء يشبه إلى حد ما ConfigMap ...

علاوة! حلول الطرف الثالث


إذا كنت مهتمًا بموضوع تتبع التغييرات في التكوينات ، فهناك بالفعل أدوات مساعدة جاهزة لهذا:

  • jimmidyson / configmap-reload - يرسل طلب HTTP إذا تغير الملف. يخطط المطور أيضًا لتعليم SIGHUP للإرسال ، ولكن عدم وجود التزامات من أكتوبر 2019 يترك هذه الخطط محل تساؤل.
  • stakater / Reloader - يراقب ConfigMap / Secrets ويقوم بالترقية الدورية (كما يسميها مؤلفها) على الموارد المرتبطة بها.

سيكون من الملائم إطلاق مثل هذه التطبيقات مع حاوية جانبية للتطبيقات الموجودة. ومع ذلك ، إذا كنت تعرف ميزات Kubernetes / ConfigMap وتهيئتها للتحرير ليس "مباشرة" (من خلال edit) ، ولكن فقط كجزء من النشر ... فقد تبدو قدرات هذه الأدوات المساعدة غير ضرورية ، أي تكرار الوظائف الأساسية.

استنتاج


مع ظهور ConfigMap في Kubernetes ، انتقلت التكوينات إلى الجولة التالية من التطوير: جلب استخدام محرك القالب لهم مرونة مماثلة لعرض صفحات HTML. لحسن الحظ ، لم تحل مثل هذه المضاعفات محل الحلول القائمة ، ولكنها أصبحت مكملة لها . لذلك ، بالنسبة للمسؤولين (أو بالأحرى حتى المطورين) الذين يعتبرون الميزات الجديدة زائدة عن الحاجة ، لا تزال الملفات القديمة الجيدة متاحة.

بالنسبة لأولئك الذين يستخدمون ConfigMap'ami أو ينظرون إليهم ، تقدم المقالة نظرة عامة موجزة عن جوهرها والفروق الدقيقة في استخدامها. إذا كان لديك نصائح وحيل خاصة بك حول هذا الموضوع - يسعدني أن أرى في التعليقات.

ملاحظة


اقرأ أيضا في مدونتنا:


All Articles