Kubernetes ConfigMaps: Nuansa untuk Tahu

Catatan : ini bukan artikel panduan lengkap, melainkan pengingat / petunjuk bagi mereka yang sudah menggunakan ConfigMap di Kubernetes atau hanya menyiapkan aplikasi mereka untuk bekerja di dalamnya.



Latar Belakang: Dari rsync ke ... Kubernetes


Apa yang terjadi sebelumnya? Di era "administrasi klasik", dalam versi yang paling sederhana, file konfigurasi ditempatkan tepat di sebelah aplikasi (atau di repositori, jika Anda mau). Sederhana: kami membuat pengiriman dasar (CD) untuk kode kami bersama dengan konfigurasi. Bahkan implementasi rsync bersyarat dapat disebut sebagai dasar CD.

Ketika infrastruktur tumbuh, konfigurasi yang berbeda diperlukan untuk lingkungan yang berbeda (dev / stage / produksi). Aplikasi dilatih untuk memahami konfigurasi mana yang akan digunakan, meneruskannya sebagai argumen untuk memulai atau variabel lingkungan yang ditetapkan di lingkungan. Bahkan lebih banyak CD yang rumit dengan munculnya Chef / Wayang / Beraneka yang sangat berguna. Peran muncul di server, dan lingkungan tidak lagi dijelaskan di tempat yang berbeda - kita sampai pada IaC (Infrastruktur sebagai kode).

Apa yang terjadi selanjutnya? Jika memungkinkan untuk melihat manfaat penting Kubernet untuk dirinya sendiri dan bahkan dapat menyesuaikan diri dengan kebutuhan untuk memodifikasi aplikasi agar berfungsi di lingkungan ini, migrasi akan terjadi. Dalam perjalanan, saya mengharapkan banyak nuansa dan perbedaan dalam konstruksi arsitektur, tetapi ketika saya berhasil mengatasi bagian utama, saya menjalankan aplikasi yang telah lama ditunggu-tunggu berjalan di K8s.

Setelah di sini, kita masih bisa menggunakan konfigurasi yang disiapkan dalam repositori di sebelah aplikasi atau meneruskan ENV ke wadah. Namun, selain metode ini, ConfigMaps juga tersedia . Primitif K8 ini memungkinkan Anda untuk menggunakan templat Go di konfigurasi, mis. membuat mereka seperti halaman HTML dan memuat ulang aplikasi saat mengubah konfigurasi tanpa restart. Dengan ConfigMaps, tidak perlu lagi menyimpan 3+ konfigurasi untuk lingkungan yang berbeda dan melacak relevansi masing-masing.

Pengantar umum untuk ConfigMaps dapat ditemukan, misalnya, di sini . Dan dalam artikel ini saya akan fokus pada beberapa fitur bekerja dengan mereka.

ConfigMaps Sederhana


Seperti apa konfigurasi pada Kubernetes? Apa yang mereka dapatkan dari templat go? Misalnya, berikut adalah ConfigMap biasa untuk aplikasi yang digunakan dari grafik 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 }}
    }

Di sini nilai yang diganti .Values.welcomedan .Values.nameakan diambil dari file values.yaml. Kenapa tepatnya dari mana values.yaml? Bagaimana cara kerja mesin templat Go? Kami sudah membicarakan detail ini lebih terinci di sini .

Panggilan pluckmembantu untuk memilih jalur yang diperlukan dari peta:

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

Selain itu, Anda dapat mengambil kedua garis khusus dan seluruh fragmen konfigurasi.

Misalnya, ConfigMap mungkin seperti ini:

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

... dan di values.yaml- isi berikut:

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

Yang terlibat di sini global.envadalah nama lingkungan. Mengganti nilai ini selama penyebaran, Anda dapat merender ConfigMaps dengan konten yang berbeda. firstdiperlukan di sini karena pluckmengembalikan daftar, elemen pertama yang berisi nilai yang diinginkan.

Ketika ada banyak konfigurasi


Satu ConfigMap dapat berisi beberapa file konfigurasi:

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

Anda bahkan dapat memasang setiap konfigurasi secara terpisah:

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

... atau ambil semua konfigurasi sekaligus dengan direktori:

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

Jika Anda mengubah deskripsi sumber daya Penempatan selama penyebaran, Kubernetes akan membuat ReplicaSet baru, mengurangi yang lama ke 0 dan meningkatkan yang baru ke jumlah replika yang ditentukan. (Ini berlaku untuk strategi penyebaran RollingUpdate.)

Tindakan semacam itu akan mengarah pada penciptaan kembali pod dengan deskripsi baru. Misalnya: ada gambar image:my-registry.example.com:v1, tetapi menjadi - image:my-registry.example.com:v2. Dan sama sekali tidak masalah apa yang sebenarnya kita ubah dalam deskripsi Penempatan kami: yang utama adalah hal ini menyebabkan replikaSet (dan, sebagai hasilnya, pod) diciptakan kembali. Dalam hal ini, versi baru file konfigurasi di versi baru aplikasi secara otomatis dipasang dan tidak akan ada masalah.

ConfigMap Change Response


Dalam hal perubahan dalam ConfigMap, empat skenario acara dapat mengikuti. Pertimbangkan mereka:

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

Kami akan menganalisis lebih detail.

skenario 1


Kami hanya memperbaiki ConfigMap? Aplikasi tidak akan memulai ulang. Dalam hal pemasangan oleh, subPathtidak akan ada perubahan sampai pod dimulai ulang secara manual.

Semuanya sederhana: Kubernet me-mount ConfigMap kami di pod versi sumber daya yang spesifik. Karena sudah di-mount dengan subPath, tidak ada "pengaruh" tambahan pada konfigurasi tidak lagi disediakan.

Skenario 2


Tidak dapat memperbarui file tanpa membuat ulang pod? Oke, kami memiliki 6 replika di Deployment, sehingga kami dapat bergantian melakukan semuanya secara manual delete pod. Kemudian ketika membuat pod baru, mereka akan “mengambil” versi baru ConfigMap.

Skenario 3


Bosan melakukan operasi seperti itu secara manual? Solusi untuk masalah ini dijelaskan dalam tips dan trik Helm :

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

Dengan demikian, spec.templatehash dari konfigurasi anotasi yang diberikan hanya ditulis ke template pod ( ).

Anotasi adalah bidang nilai kunci yang berubah-ubah tempat Anda dapat menyimpan nilai-nilai Anda. Jika Anda mendaftarkannya di templat spec.templatepod masa depan, bidang ini akan jatuh ke dalam ReplicaSet dan pod itu sendiri. Kubernetes akan melihat bahwa template pod telah berubah (karena konfigurasi sha256 telah berubah) dan akan mulai RollingUpdate, di mana tidak ada yang berubah kecuali penjelasan ini.

Sebagai hasilnya, kami menyimpan versi aplikasi dan uraian Deployment yang sama dan pada dasarnya hanya memicu pembuatan ulang pod oleh mesin - mirip dengan cara kami melakukannya secara manual kubectl delete, tetapi sudah “benar”: secara otomatis dan dengan RollingUpdate.

Skenario 4


Mungkin aplikasi sudah tahu cara memantau perubahan dalam konfigurasi dan secara otomatis memuat ulang? Di sinilah letak fitur penting dari ConfigMaps ...

Di Kubernetes, jika config di-mount dengannya subPath, itu tidak akan diperbarui sampai pod dimulai ulang (lihat tiga skenario pertama yang dibahas di atas) . Tetapi jika Anda memasang ConfigMap sebagai direktori, tanpa subPath, maka di dalam wadah akan ada direktori dengan konfigurasi yang diperbarui tanpa memulai ulang pod.

Ada fitur lain yang berguna untuk diingat:

  • File konfigurasi yang diperbarui di dalam wadah diperbarui dengan penundaan. Ini karena fakta bahwa file tersebut tidak di-mount dengan tepat, tetapi objek Kubernetes.
  • File di dalamnya adalah symlink. Contoh dengan 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

    Dan apa yang akan terjadi tanpa subPathketika dipasang oleh direktori?

    $ 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

    Perbarui konfigurasi (via deploy atau kubectl edit), tunggu 2 menit (waktu caching apiserver) - dan voila:

    $ 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

    Perhatikan cap waktu yang diubah di direktori yang dibuat oleh Kubernetes.

Ubah pelacakan


Dan akhirnya - contoh sederhana tentang bagaimana Anda dapat memantau perubahan dalam konfigurasi.

Kami akan menggunakan aplikasi-Go semacam itu
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)
	}
}

... menambahkannya dengan konfigurasi seperti itu:

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

Jika dijalankan, lognya adalah:

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

Dan sekarang kita akan menginstal aplikasi ini di Kubernetes, setelah memasang konfigurasi ConfigMap di pod alih-alih file dari gambar. Contoh grafik Helm telah disiapkan di GitHub :

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

Dan ubah hanya ConfigMap:

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

Perbarui grafik Helm di cluster, misalnya, seperti ini:

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

Apa yang akan terjadi?

  • Aplikasi v1 dan v2 tidak restart, karena bagi mereka, tidak ada perubahan yang terjadi dalam Penempatan - mereka masih menyambut Alice.
  • Aplikasi v3 restart, baca kembali konfigurasi, dan sambut Bob.
  • Aplikasi v4 tidak memulai ulang. Karena ConfigMap di-mount sebagai direktori, perubahan dalam konfigurasi diketahui dan konfigurasi diubah dengan cepat, tanpa memulai ulang pod. Ya, aplikasi memperhatikan perubahan dalam contoh sederhana kami - lihat pesan acara dari 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}

Anda dapat melihat bagaimana situasi serupa - melacak perubahan ConfigMap - diselesaikan dalam proyek yang lebih dewasa (dan hanya "nyata"), di sini .

Penting! Penting juga untuk mengingat bahwa semua hal di atas dalam artikel ini juga berlaku untuk Secret'ov di Kubernetes ( kind: Secret): bukan tidak ada artinya mereka sangat mirip dengan ConfigMap ...

Bonus! Solusi Pihak Ketiga


Jika Anda tertarik dengan topik pelacakan perubahan di konfigurasi, sudah ada utilitas siap pakai untuk ini:

  • jimmidyson / configmap-reload - Mengirim permintaan HTTP jika file telah berubah. Pengembang juga berencana untuk mengajarkan SIGHUP untuk mengirim, tetapi kurangnya komitmen sejak Oktober 2019 membuat rencana ini dipertanyakan;
  • stakater / Reloader - memonitor ConfigMap / Secrets dan melakukan upgrade bergulir (sebagaimana penulisnya menyebutnya) pada sumber daya yang terkait dengannya.

Akan lebih mudah untuk meluncurkan aplikasi seperti itu dengan wadah sespan untuk aplikasi yang sudah ada. Namun, jika Anda mengetahui fitur Kubernetes / ConfigMap dan konfigurasi untuk diedit tidak "langsung" (melalui edit), tetapi hanya sebagai bagian dari penyebaran ... maka kemampuan utilitas tersebut mungkin tampak berlebihan, mis. menduplikasi fungsi dasar.

Kesimpulan


Dengan munculnya ConfigMap di Kubernetes, konfigurasi pindah ke babak pengembangan selanjutnya: penggunaan mesin template memberi mereka fleksibilitas yang sebanding dengan rendering halaman HTML. Untungnya, komplikasi seperti itu tidak menggantikan solusi yang ada, tetapi menjadi pelengkap mereka . Oleh karena itu, untuk administrator (atau lebih tepatnya, bahkan pengembang) yang menganggap fitur baru berlebihan, file lama yang baik masih tersedia.

Bagi mereka yang sudah menggunakan ConfigMaps atau hanya melihatnya, artikel ini memberikan gambaran singkat tentang esensi mereka dan nuansa penggunaan. Jika Anda memiliki tips & trik Anda sendiri pada topik - Saya akan senang melihat di komentar.

PS


Baca juga di blog kami:


All Articles