Asal disiarkan di blog saya
Secara lalai, pengendali yang dibina menggunakan Kubebuilder dan masa jalan pengawal memproses satu permintaan penyelarasan pada satu masa. Ini adalah tetapan yang munasabah, kerana lebih mudah bagi pembangun pengendali untuk memikirkan dan menyahpepijat logik dalam aplikasi mereka. Ia juga mengekang daya pemprosesan daripada pengawal kepada sumber teras Kubernetes seperti ectd dan pelayan API.
Tetapi bagaimana jika baris gilir kerja anda mula membuat sandaran dan purata masa penyesuaian meningkat disebabkan permintaan yang dibiarkan duduk dalam baris gilir, menunggu untuk diproses? Nasib baik bagi kami, struct Pengawal masa larian pengawal termasuk medan MaxConcurrentReconciles (seperti yang saya nyatakan sebelum ini dalam artikel Petua Kubebuilder saya). Pilihan ini membolehkan anda menetapkan bilangan gelung selaras serentak yang berjalan dalam satu pengawal. Jadi dengan nilai di atas 1, anda boleh menyelaraskan berbilang sumber Kubernetes secara serentak.
Awal perjalanan pengendali saya, satu soalan yang saya ada ialah bagaimana kami boleh menjamin bahawa sumber yang sama tidak diselaraskan pada masa yang sama oleh 2 atau lebih goroutine? Dengan MaxConcurrentReconciles ditetapkan di atas 1, ini boleh membawa kepada semua jenis keadaan perlumbaan dan tingkah laku yang tidak diingini, kerana keadaan objek di dalam gelung perdamaian boleh berubah melalui kesan sampingan daripada sumber luaran (gelung perdamaian berjalan dalam utas yang berbeza) .
Saya memikirkan perkara ini seketika, malah melaksanakan penyegerakan.Pendekatan berasaskan peta yang akan membolehkan goroutine memperoleh kunci untuk sumber tertentu (berdasarkan ruang nama/namanya).
Ternyata semua usaha ini adalah sia-sia, kerana saya baru-baru ini mengetahui (dalam saluran slack k8s) bahawa baris gilir kerja pengawal sudah menyertakan ciri ini! Walaupun dengan pelaksanaan yang lebih mudah.
Ini ialah cerita ringkas tentang cara baris gilir kerja pengawal k8s menjamin bahawa sumber unik diselaraskan secara berurutan. Jadi walaupun MaxConcurrentReconciles ditetapkan di atas 1, anda boleh yakin bahawa hanya satu fungsi penyesuaian yang bertindak pada mana-mana sumber pada satu masa.
Controller-runtime menggunakan pustaka client-go/util/workqueue untuk melaksanakan baris gilir penyesuaian asasnya. Dalam fail doc.go pakej, ulasan menyatakan bahawa baris gilir kerja menyokong sifat ini:
Tunggu sebentar... Jawapan saya ada di sini dalam peluru kedua, sifat "Kedekut"! Menurut dokumen ini, baris gilir akan mengendalikan isu konkurensi ini secara automatik untuk saya, tanpa perlu menulis satu baris kod. Mari kita jalankan pelaksanaannya.
Struktur giliran kerja mempunyai 3 kaedah utama, Tambah, Dapatkan dan Selesai. Di dalam pengawal, pemberi maklumat akan Menambahkan permintaan yang mendamaikan (nama ruang nama sumber k8s generik) pada baris gilir kerja. Gelung mendamaikan berjalan dalam goroutine berasingan kemudiannya Dapatkan permintaan seterusnya daripada baris gilir (menyekat jika ia kosong). Gelung akan melaksanakan apa sahaja logik tersuai yang ditulis dalam pengawal, dan kemudian pengawal akan memanggil Selesai pada baris gilir, menghantar permintaan pendamaian sebagai hujah. Ini akan memulakan proses sekali lagi dan gelung pendamaian akan memanggil Dapatkan untuk mendapatkan semula item kerja seterusnya.
Ini serupa dengan memproses mesej dalam RabbitMQ, di mana pekerja mengeluarkan item dari baris gilir, memprosesnya dan kemudian menghantar "Ack" kembali kepada broker mesej yang menunjukkan bahawa pemprosesan telah selesai dan selamat untuk mengalih keluar item daripada beratur.
Namun, saya mempunyai pengendali yang sedang menjalankan pengeluaran yang memperkasakan infrastruktur QuestDB Cloud dan ingin memastikan baris gilir kerja berfungsi seperti yang diiklankan. Jadi a menulis ujian pantas untuk mengesahkan kelakuannya.
Berikut ialah ujian mudah yang mengesahkan sifat "Kedekut":
package main_test import ( "testing" "github.com/stretchr/testify/assert" "k8s.io/client-go/util/workqueue" ) func TestWorkqueueStingyProperty(t *testing.T) { type Request int // Create a new workqueue and add a request wq := workqueue.New() wq.Add(Request(1)) assert.Equal(t, wq.Len(), 1) // Subsequent adds of an identical object // should still result in a single queued one wq.Add(Request(1)) wq.Add(Request(1)) assert.Equal(t, wq.Len(), 1) // Getting the object should remove it from the queue // At this point, the controller is processing the request obj, _ := wq.Get() req := obj.(Request) assert.Equal(t, wq.Len(), 0) // But re-adding an identical request before it is marked as "Done" // should be a no-op, since we don't want to process it simultaneously // with the first one wq.Add(Request(1)) assert.Equal(t, wq.Len(), 0) // Once the original request is marked as Done, the second // instance of the object will be now available for processing wq.Done(req) assert.Equal(t, wq.Len(), 1) // And since it is available for processing, it will be // returned by a Get call wq.Get() assert.Equal(t, wq.Len(), 0) }
Memandangkan baris gilir kerja menggunakan mutex di bawah hud, tingkah laku ini selamat untuk benang. Jadi, walaupun saya menulis lebih banyak ujian yang menggunakan berbilang goroutin secara serentak membaca dan menulis daripada baris gilir pada kelajuan tinggi dalam usaha untuk memecahkannya, gelagat sebenar baris kerja akan sama seperti ujian satu benang kami.
Kubernetes 표준 라이브러리에는 이와 같은 작은 보석이 많이 숨겨져 있으며 그 중 일부는 그다지 눈에 띄지 않는 위치(예: go 클라이언트 패키지에 있는 컨트롤러 런타임 작업 대기열)에 있습니다. 이 발견과 과거에 제가 이룩한 다른 유사한 발견에도 불구하고 저는 여전히 이러한 문제를 해결하려는 이전의 시도가 완전한 시간 낭비가 아니라고 생각합니다. 이는 분산 시스템 컴퓨팅의 근본적인 문제에 대해 비판적으로 생각하게 만들고 내부에서 무슨 일이 일어나고 있는지 더 많이 이해하도록 도와줍니다. 그래서 "Kubernetes가 해냈다"는 사실을 알게 되었을 때 코드베이스를 단순화하고 불필요한 단위 테스트를 일부 제거할 수 있다는 사실에 안도감을 느꼈습니다.
Atas ialah kandungan terperinci Bagaimanakah Pengendali Kubernetes Mengendalikan Concurrency?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!