Pergilah? Pesta! Temui shell-operator (review dan video laporan dari KubeCon EU'2020)

Tahun ini, konferensi Kubernetes Eropa utama - KubeCon + CloudNativeCon Europe 2020 - bersifat virtual. Namun, perubahan format tersebut tidak menghalangi kami untuk menyampaikan pembicaraan yang telah direncanakan sejak lama, “Ayo? Pesta! Perkenalkan Shell-operator ", yang didedikasikan untuk proyek shell-operator open source kami .



Terinspirasi oleh ceramah tersebut, artikel ini menyajikan pendekatan untuk menyederhanakan proses pembuatan operator untuk Kubernetes dan menunjukkan bagaimana Anda dapat membuatnya sendiri menggunakan operator shell dengan sedikit usaha.







Kami menyajikan video dengan laporan (~ 23 menit dalam bahasa Inggris, jauh lebih informatif daripada artikel) dan kutipan utama darinya dalam bentuk teks. Pergilah!



Di Flant, kami terus mengoptimalkan dan mengotomatiskan semuanya. Hari ini kita akan berbicara tentang konsep menarik lainnya. Perkenalkan skrip shell cloud-native !



Namun, mari kita mulai dengan konteks di mana ini semua terjadi - Kubernetes.



API dan Pengontrol Kubernetes



API di Kubernetes dapat direpresentasikan sebagai jenis server file dengan direktori untuk setiap jenis objek. Objek (sumber daya) di server ini diwakili oleh file YAML. Selain itu, server memiliki API dasar untuk melakukan tiga hal:



  • dapatkan sumber daya berdasarkan jenis dan namanya;
  • mengubah sumber daya (dalam hal ini, server hanya menyimpan objek yang "benar" - semua bentuk yang salah atau dimaksudkan untuk direktori lain akan dibuang);
  • ( / ).


Jadi, Kubernetes bertindak sebagai semacam server file (untuk manifes YAML) dengan tiga metode dasar (ya, sebenarnya, ada metode lain, tapi kami akan menghilangkannya untuk saat ini).







Masalahnya adalah server hanya dapat menyimpan informasi. Untuk membuatnya berfungsi, Anda memerlukan pengontrol - konsep terpenting dan fundamental kedua di dunia Kubernetes.



Ada dua jenis pengontrol utama. Yang pertama mengambil informasi dari Kubernetes, memprosesnya sesuai dengan logika bersarang, dan mengembalikannya ke K8s. Yang kedua mengambil informasi dari Kubernetes, tetapi, tidak seperti tipe yang pertama, mengubah status beberapa sumber daya eksternal.



Mari kita lihat lebih dekat proses pembuatan Deployment di Kubernetes:



  • Deployment Controller (termasuk dalam kube-controller-manager) menerima informasi tentang Deployment dan membuat ReplicaSet.
  • ReplicaSet membuat dua replika (dua pod) berdasarkan informasi ini, tetapi pod ini belum dijadwalkan.
  • Penjadwal menjadwalkan pod dan menambahkan informasi node ke YAML-nya.
  • Kubelet membuat perubahan pada sumber daya eksternal (katakanlah, Docker).


Kemudian seluruh urutan ini diulangi dalam urutan terbalik: kubelet memeriksa kontainer, menghitung status dari pod, dan mengirimkannya kembali. Pengontrol ReplicaSet mendapatkan status dan memperbarui status kumpulan replika. Hal yang sama terjadi dengan Deployment Controller, dan pengguna akhirnya mendapatkan status yang diperbarui (saat ini).







Operator shell



Ternyata Kubernetes didasarkan pada kolaborasi berbagai pengontrol (operator Kubernetes juga merupakan pengontrol). Muncul pertanyaan, bagaimana cara membuat operator Anda sendiri dengan sedikit usaha? Dan di sini operator shell yang kami kembangkan datang untuk menyelamatkan . Ini memungkinkan administrator sistem untuk membuat pernyataan mereka sendiri menggunakan metode yang sudah dikenal.







Contoh sederhana: menyalin rahasia



Mari kita lihat contoh sederhana.



Katakanlah kita memiliki cluster Kubernetes. Ini memiliki namespace defaultdengan beberapa Rahasia mysecret. Selain itu, ada ruang nama lain di cluster. Beberapa dari mereka memiliki label khusus. Tujuan kami adalah menyalin Rahasia ke dalam ruang nama dengan label.



Tugas ini diperumit oleh fakta bahwa namespace baru mungkin muncul di cluster, dan beberapa di antaranya mungkin memiliki label ini. Di sisi lain, saat menghapus label, Rahasia juga harus dihapus. Selain semuanya, Rahasia itu sendiri juga dapat berubah: dalam hal ini, Rahasia baru harus disalin ke semua ruang nama dengan label. Jika Rahasia tidak sengaja terhapus di namespace mana pun, operator kami harus segera memulihkannya.



Sekarang tugas telah dirumuskan, saatnya untuk mulai mengimplementasikannya menggunakan shell-operator. Tapi pertama-tama, ada baiknya mengatakan beberapa patah kata tentang shell-operator itu sendiri.



Bagaimana operator shell bekerja



Seperti beban kerja lain di Kubernetes, shell-operator berjalan di podnya. Pod ini /hooksberisi file yang dapat dijalankan di direktori . Ini bisa berupa skrip di Bash, Python, Ruby, dll. Kami menyebut hook yang dapat dieksekusi ini .







Shell-operator berlangganan event Kubernetes dan mengaktifkan hook ini sebagai tanggapan atas event apapun yang kita butuhkan.







Bagaimana operator shell mengetahui hook mana yang harus dijalankan dan kapan? Intinya adalah setiap kail memiliki dua tahap. Saat startup, shell-operator menjalankan semua hook dengan sebuah argumen --config- ini adalah tahap konfigurasi. Dan setelah itu pengait diluncurkan dengan cara biasa - sebagai tanggapan atas peristiwa yang menyambungkannya. Dalam kasus terakhir, hook menerima konteks yang mengikat) - data dalam format JSON, yang akan kita bahas lebih detail di bawah ini.



Menjadikan operator di Bash



Kami sekarang siap untuk implementasi. Untuk melakukan ini, kita perlu menulis dua fungsi (ngomong-ngomong, kami merekomendasikan pustaka shell_lib , yang sangat menyederhanakan penulisan kait di Bash):



  • yang pertama diperlukan untuk tahap konfigurasi - ini menampilkan konteks pengikatan;
  • yang kedua berisi logika utama pengait.


#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"


Langkah selanjutnya adalah memutuskan objek apa yang kita butuhkan. Dalam kasus kami, kami perlu melacak:



  • sumber rahasia untuk perubahan;
  • semua ruang nama dalam kluster, sehingga Anda tahu yang mana label dilampirkan;
  • rahasia target untuk memastikan semuanya sinkron dengan rahasia sumber.


Berlangganan ke sumber rahasia



Konfigurasi pengikatan cukup sederhana untuknya. Kami menunjukkan bahwa kami tertarik pada Rahasia dengan nama mysecretdi namespace default:







function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF


Akibatnya, hook akan berjalan saat source secret ( src_secret) berubah dan menerima konteks binding berikut:







Seperti yang Anda lihat, hook tersebut berisi nama dan seluruh objek.



Melacak ruang nama



Sekarang Anda perlu berlangganan namespace. Untuk melakukan ini, kami akan menentukan konfigurasi binding berikut:



- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false


Seperti yang Anda lihat, bidang baru bernama jqFilter telah muncul di konfigurasi . Seperti namanya, ini jqFiltermenyaring semua informasi yang tidak perlu dan membuat objek JSON baru dengan bidang yang menarik bagi kami. Sebuah hook dengan konfigurasi ini akan menerima konteks binding berikut:







Ini berisi array filterResultsuntuk setiap namespace di cluster. Variabel boolean yang hasLabelmenunjukkan apakah label dilampirkan ke namespace yang diberikan. Pemilih keepFullObjectsInMemory: falsemengatakan tidak perlu menyimpan objek lengkap di memori.



Melacak target rahasia



Kami berlangganan semua Rahasia yang memiliki anotasi managed-secret: "yes"(ini adalah target kami dst_secrets):



- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false


Dalam kasus ini, jqFilterfilter semua informasi kecuali untuk namespace dan parameter resourceVersion. Parameter terakhir diteruskan ke anotasi saat membuat rahasia: ini memungkinkan Anda membandingkan versi rahasia dan menjaganya tetap mutakhir.



Sebuah hook yang dikonfigurasi dengan cara ini akan menerima tiga konteks pengikatan yang dijelaskan di atas ketika dijalankan. Anggap saja sebagai semacam snapshot cluster.







Berdasarkan semua informasi ini, algoritma dasar dapat dikembangkan. Ini mengulangi semua ruang nama dan:



  • jika hasLabelrelevan trueuntuk namespace saat ini:
    • membandingkan rahasia global dengan rahasia lokal:
      • jika mereka sama, itu tidak melakukan apapun;
      • jika mereka berbeda, jalankan kubectl replaceatau create;
  • jika hasLabelrelevan falseuntuk namespace saat ini:

    • pastikan bahwa Secret tidak ada di namespace yang diberikan:
      • jika ada Rahasia lokal, hapus menggunakan kubectl delete;
      • jika Rahasia lokal tidak ditemukan, itu tidak melakukan apa-apa.






Anda dapat mengunduh implementasi algoritme di Bash di repositori kami dengan contoh .



Ini adalah bagaimana kami dapat membuat pengontrol Kubernetes sederhana menggunakan 35 baris konfigurasi YAML dan jumlah kode Bash yang kurang lebih sama! Tugas dari shell-operator adalah untuk merangkai keduanya.



Namun, menyalin rahasia bukanlah satu-satunya bidang penerapan utilitas. Berikut adalah beberapa contoh lagi untuk menunjukkan kemampuannya.



Contoh 1: membuat perubahan pada ConfigMap



Mari kita lihat Deployment tiga pod. Pod menggunakan ConfigMap untuk menyimpan beberapa konfigurasi. Saat pod diluncurkan, ConfigMap berada dalam beberapa status (sebut saja v.1). Karena itu, semua pod menggunakan versi ConfigMap khusus ini.



Sekarang misalkan ConfigMap telah berubah (v.2). Namun, pod akan menggunakan versi lama ConfigMap (v.1):







Bagaimana cara membuatnya bermigrasi ke ConfigMap (v.2) yang baru? Jawabannya sederhana: gunakan template. Mari tambahkan anotasi checksum ke bagian templatekonfigurasi Deployment:







Hasilnya, checksum ini akan didaftarkan di semua pod, dan akan sama seperti di Deployment. Sekarang Anda hanya perlu memperbarui anotasi saat ConfigMap berubah. Dan shell-operator sangat berguna dalam kasus ini. Yang perlu Anda lakukan adalah memprogram pengait yang akan berlangganan ConfigMap dan memperbarui checksum .



Jika pengguna membuat perubahan pada ConfigMap, operator-shell akan melihatnya dan menghitung ulang checksum. Kemudian keajaiban Kubernetes mulai bekerja: orkestrator akan mematikan pod, membuat yang baru, menunggu hingga menjadi Ready, dan melanjutkan ke pod berikutnya. Akibatnya, Deployment akan menyinkronkan dan bermigrasi ke ConfigMap versi baru.







Contoh 2: Bekerja dengan Definisi Sumber Daya Kustom



Seperti yang Anda ketahui, Kubernetes memungkinkan Anda membuat tipe (jenis) objek kustom. Misalnya, Anda bisa membuat jenis MysqlDatabase. Katakanlah tipe ini memiliki dua parameter metadata: namedannamespace.



apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar


Kami memiliki cluster Kubernetes dengan namespace berbeda di mana kami dapat membuat database MySQL. Dalam hal ini, shell-operator dapat digunakan untuk melacak sumber daya MysqlDatabase, menghubungkannya ke server MySQL, dan menyinkronkan status cluster yang diinginkan dan diamati.







Contoh 3: Memantau jaringan cluster



Seperti yang Anda ketahui, menggunakan ping adalah cara termudah untuk memantau jaringan. Dalam contoh ini, kami akan menunjukkan bagaimana mengimplementasikan pemantauan tersebut menggunakan operator shell.



Pertama-tama, Anda perlu berlangganan ke node. Operator shell membutuhkan nama dan alamat IP dari setiap node. Dengan bantuan mereka, itu akan melakukan ping ke node ini.



configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"


Parameter executeHookOnEvent: []mencegah peluncuran hook sebagai respons terhadap peristiwa apa pun (yaitu, sebagai respons terhadap perubahan, penambahan, penghapusan node). Namun, itu akan mulai (dan memperbarui daftar node) sesuai jadwal - setiap menit, seperti yang ditentukan oleh bidang schedule.



Sekarang pertanyaannya adalah, bagaimana tepatnya kita tahu tentang masalah seperti kehilangan paket? Mari kita lihat kodenya:



function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}


Kami mengulangi daftar node, mendapatkan nama dan alamat IP mereka, melakukan ping dan mengirim hasilnya ke Prometheus. Shell-operator dapat mengekspor metrik ke Prometheus , menyimpannya ke file yang terletak sesuai dengan jalur yang ditentukan dalam variabel lingkungan $METRICS_PATH.



Ini adalah bagaimana Anda dapat melakukan operator untuk pemantauan jaringan sederhana dalam sebuah cluster.



Mekanisme antrian



Artikel ini tidak akan lengkap tanpa menjelaskan mekanisme penting lainnya yang dibangun ke dalam operator shell. Bayangkan bahwa ia mengeksekusi hook sebagai respons terhadap suatu peristiwa di cluster.



  • Apa yang terjadi jika peristiwa lain terjadi di cluster pada waktu yang sama ?
  • Akankah operator shell memulai instance lain dari hook?
  • Tetapi bagaimana jika, katakanlah, lima peristiwa segera terjadi di cluster?
  • Akankah shell-operator menanganinya secara paralel?
  • Bagaimana dengan sumber daya yang dikonsumsi seperti memori dan CPU?


Untungnya, shell-operator memiliki mekanisme antrian built-in. Semua acara diantrekan dan diproses secara berurutan.



Mari kita ilustrasikan ini dengan contoh. Katakanlah kita memiliki dua pengait. Acara pertama menuju hook pertama. Setelah pemrosesannya selesai, antrean akan maju. Tiga peristiwa berikutnya diarahkan ke hook kedua - mereka dihapus dari antrian dan dimasukkan ke dalamnya dalam "batch". Artinya, hook menerima serangkaian peristiwa - atau lebih tepatnya, array konteks yang mengikat.



Selain itu, acara - acara ini dapat digabungkan menjadi satu acara besar . Parameter groupdalam konfigurasi binding bertanggung jawab untuk ini .







Anda dapat membuat sejumlah antrian / pengait dan berbagai kombinasinya. Misalnya, satu antrean dapat bekerja dengan dua kait, atau sebaliknya.







Yang perlu Anda lakukan adalah menyesuaikan bidang sesuai queuedi konfigurasi penjilidan. Jika tidak ada nama antrian yang ditentukan, hook berjalan pada queue default ( default). Mekanisme antrian ini memungkinkan Anda menyelesaikan semua masalah manajemen sumber daya sepenuhnya saat bekerja dengan pengait.



Kesimpulan



Kami berbicara tentang apa itu shell-operator, menunjukkan bagaimana ia dapat digunakan dengan cepat dan mudah membuat operator Kubernetes, dan memberikan beberapa contoh penggunaannya.



Informasi mendetail tentang shell-operator, serta panduan cepat untuk menggunakannya, tersedia di repositori terkait di GitHub . Jangan ragu untuk menghubungi kami jika ada pertanyaan: Anda dapat mendiskusikannya di grup Telegram khusus (dalam bahasa Rusia) atau di forum ini (dalam bahasa Inggris).



Dan jika Anda menyukainya, kami selalu senang dengan terbitan baru / PR / bintang di GitHub, di mana, Anda dapat menemukan proyek menarik lainnya . Di antara mereka, ada baiknya menyoroti operator addon , yang merupakan kakak dari operator shell... Utilitas ini menggunakan diagram Helm untuk menginstal add-on, dapat memberikan pembaruan dan memantau berbagai parameter / nilai diagram, mengontrol proses penginstalan diagram, dan juga dapat memodifikasinya sebagai respons terhadap peristiwa di cluster.







Video dan slide



Video dari pertunjukan (~ 23 menit):





Penyajian laporan:







PS



Baca juga di blog kami:






All Articles