Dari permintaan kolam renang untuk melepaskan. Laporkan Yandex.Taxi

Ada periode kritis dalam siklus rilis layanan - dari saat ketika versi baru disiapkan hingga saat ketika tersedia untuk pengguna. Tindakan tim antara kedua tonggak ini harus konsisten dari rilis ke rilis dan, jika mungkin, otomatis. Dalam laporannya, alberist Sergey Pomazanov menjelaskan proses yang mengikuti setiap permintaan Yandex.Taxi pool.


- Selamat sore! Nama saya Sergey, saya adalah kepala grup otomasi di Yandex.Taxi. Singkatnya, tugas utama grup kami adalah meminimalkan waktu yang dihabiskan pengembang untuk memecahkan masalah mereka. Ini mencakup semuanya, mulai dari CI hingga proses pengembangan dan pengujian.

Apa yang dilakukan pengembangan kami ketika kode ditulis?

Untuk menguji fungsionalitas baru, pertama-tama kami memeriksa semuanya secara lokal. Untuk pengujian lokal, kami memiliki serangkaian tes besar. Jika kode baru muncul, itu juga perlu ditutupi dengan tes.



Cakupan pengujian kami tidak sebaik yang kami inginkan, tetapi kami berusaha mempertahankannya pada tingkat yang memadai.

Untuk pengujian, kami menggunakan Google Test dan kerangka pytest yang ditulis sendiri, yang dengannya kami menguji tidak hanya bagian "python", tetapi juga bagian "plus". Kerangka kerja kami memungkinkan Anda untuk memulai layanan, mengunggah data ke basis data sebelum setiap pengujian, memperbarui cache, menghapus semua permintaan eksternal, dll. Kerangka kerja yang cukup fungsional yang memungkinkan Anda untuk menjalankan apa saja sesuka Anda, mengunci apa saja sehingga kami tidak secara tidak sengaja mendapatkan apa pun permintaan di luar.

Selain tes fungsional, kami memiliki tes integrasi. Mereka memungkinkan Anda untuk memecahkan masalah lain. Jika Anda tidak yakin bahwa layanan Anda akan berinteraksi dengan benar dengan layanan lain, maka Anda dapat menjalankan dudukan dan menjalankan serangkaian tes. Sejauh ini kami memiliki serangkaian tes dasar, tetapi tes ini perlahan berkembang.

Dudukan ini dibangun berdasarkan teknologi Docker dan Docker Compose, di mana setiap wadah memiliki layanannya sendiri, dan mereka semua berinteraksi satu sama lain. Ini terjadi di lingkungan yang terisolasi. Mereka memiliki jaringan sendiri yang terisolasi, basis data mereka sendiri, kumpulan data mereka sendiri. Dan tes berlangsung sedemikian rupa, seolah-olah seseorang meluncurkan aplikasi seluler, mengklik tombol, melakukan pemesanan. Pada saat ini, mobil virtual mengemudikan, mengantarkan penumpang, lalu uang didebit dari penumpang, dan sebagainya. Pada dasarnya, semua tes memerlukan interaksi banyak layanan dan komponen sekaligus.

Secara alami, kami hanya menguji layanan kami dan hanya komponen kami, karena kami tidak boleh menguji layanan eksternal, dan kami membasahi semuanya eksternal.

Dudukan cukup nyaman untuk dijalankan secara lokal dan mengeluarkan taksi saku. Anda dapat mengambil pendirian ini, menjalankannya di mesin lokal atau di mesin virtual atau mesin pengembangan lainnya. Setelah meluncurkan dudukan, Anda dapat mengambil aplikasi seluler yang diadaptasi untuk taksi saku, mengonfigurasinya di komputer, dan melakukan pemesanan. Semuanya persis sama seperti dalam produksi atau di tempat lain. Jika Anda perlu menguji fungsionalitas baru, Anda bisa memasukkan kode Anda, itu akan mengambil dan berjalan di seluruh lingkungan.

Sekali lagi, Anda bisa mengambil dan menjalankan layanan yang diinginkan. Untuk melakukan ini, Anda perlu meningkatkan basis data, mengisinya dengan konten yang diperlukan, atau mengambil basis dari lingkungan yang ada dan menghubungkannya ke layanan. Dan kemudian Anda bisa menghubunginya, melakukan beberapa pertanyaan, melihat apakah itu berfungsi dengan benar atau tidak.

Poin penting lainnya adalah pemeriksaan gaya. Jika semuanya sederhana untuk "plus", kami menggunakan format dentang dan memeriksa apakah kode cocok atau tidak, maka untuk Python kami menggunakan sebanyak empat analisis: Flake8, Pylint, Mypy dan, sepertinya, autopep8.

Kami menggunakan analisis ini terutama dalam pengiriman standar. Jika ada peluang untuk memilih beberapa gaya desain, maka kami menggunakan gaya Google. Satu-satunya hal yang kami koreksi dengan menambahkan milik kami adalah pemeriksaan untuk menyortir impor sehingga impor diurutkan dengan benar.

Setelah Anda membuat kode, diperiksa secara lokal, Anda dapat melakukan permintaan kumpulan. Permintaan kolam dibuat di GitHub.



Membuat permintaan kumpulan di GitHub memberikan banyak peluang yang diberikan TeamCity kepada kami. TeamCity secara otomatis menjalankan semua tes yang disebutkan di atas, memeriksanya secara otomatis, dan dalam kumpulan permintaan itu sendiri tertulis tentang status bagian, apakah tes lulus atau tidak. Artinya, tanpa mengunjungi TeamCity, Anda dapat melihat apakah sudah lulus atau belum, dan dengan mengklik tautan untuk memahami apa yang salah dan apa yang perlu diperbaiki.

Jika Anda tidak memiliki cukup taksi dan tes, Anda ingin memeriksa interaksi nyata dengan beberapa layanan nyata, kami memiliki lingkungan pengujian yang mengulangi produksi. Kami memiliki dua lingkungan pengujian ini. Satu untuk pengembangan penguji seluler, dan yang kedua untuk pengembang. Lingkungan pengujian sedekat mungkin dengan produksi, dan jika ada permintaan yang dibuat untuk layanan eksternal, mereka juga dibuat dari lingkungan pengujian. Satu-satunya batasan adalah bahwa lingkungan pengujian pergi ke pengujian sumber daya eksternal bila memungkinkan. Dan lingkungan produksi masuk ke produksi.

Lebih lanjut tentang lingkungan pengujian, kami melakukannya cukup sederhana melalui TeamCity. Penting untuk menempatkan label yang sesuai pada GitHub, dan setelah itu diatur, klik tombol "Kumpulkan kustom". Jadi kami menyebutnya. Maka semua permintaan gabungan dengan label ini akan bersaing, dan kemudian perakitan otomatis paket dengan pengelompokan akan dimulai.

Selain pengujian rutin, pengujian beban terkadang diperlukan. Jika Anda mengedit kode yang merupakan bagian dari layanan yang sangat dimuat, kami dapat melakukan tes untuk ini. Dalam Python, ada beberapa layanan yang sangat banyak dimuat, beberapa di antaranya kami tulis ulang dalam C ++, namun demikian, mereka tetap ada, terkadang ada tempatnya. Pengujian beban dilakukan melalui sistem Lunapark. Ini menggunakan Yandex. Terima kasih, tersedia secara gratis, Anda dapat mengunduh dan menontonnya. Tangki memungkinkan Anda menjalankan beberapa layanan, membuat grafik, melakukan berbagai metode pemuatan, dan menunjukkan beban apa yang saat ini ada di layanan dan sumber daya apa yang digunakan. Cukup mengklik tombol melalui TeamCity, paket akan dikumpulkan, dan kemudian dimungkinkan untuk menggulungnya jika perlu. Atau cukup isi dan jalankan secara manual di sana.



Saat Anda menguji kode Anda, salah satu pengembang mungkin saat ini mulai melihat kode Anda dan terlibat dalam ulasannya.

Apa yang kami perhatikan dalam proses:



Salah satu poin penting - fungsionalitas harus dinonaktifkan. Ini berarti bahwa apa pun kodenya, ada bug di dalamnya atau tidak, mungkin fungsi ini tidak berfungsi seperti yang semula dimaksudkan, mungkin para manajer menginginkan sesuatu yang lain, atau mungkin fungsi ini mencoba menempatkan layanan lain yang tidak siap untuk memuat baru, dan Anda perlu kemampuan untuk mematikannya dengan cepat dan meletakkan semuanya dalam keadaan normal.

Kami juga memiliki aturan bahwa ketika meluncurkan, fungsionalitas baru harus dimatikan, dan dihidupkan hanya setelah itu diluncurkan ke semua cluster dan semua pusat data.

Jangan lupa bahwa kami memiliki API yang digunakan oleh aplikasi seluler yang mungkin tidak diperbarui untuk waktu yang lama. Jika kami membuat beberapa perubahan yang tidak kompatibel ke belakang di API kami, maka beberapa aplikasi mungkin rusak dan kami tidak dapat memaksa semua aplikasi untuk hanya mengunduh dan memperbarui. Ini akan berdampak negatif pada reputasi kita. Oleh karena itu, semua fungsi baru harus kompatibel ke belakang. Ini berlaku tidak hanya untuk API eksternal, tetapi juga untuk yang internal, karena Anda tidak dapat secara bersamaan meluncurkan semua kode ke semua pusat data, ke semua mesin, ke semua cluster. Bagaimanapun, kode lama dan yang baru akan tinggal bersama kami pada saat yang sama. Akibatnya, kami mendapatkan beberapa kueri yang tidak dapat diproses di suatu tempat, dan kami akan memiliki kesalahan.

Anda juga harus memikirkan hal berikutnya: jika tiba-tiba kode Anda tidak berfungsi atau Anda menulis layanan mikro baru di mana ada potensi masalah, Anda harus siap dengan konsekuensinya dan dapat menurunkan. Rekan saya akan membicarakan hal ini pada presentasi berikutnya.

Jika Anda melakukan perubahan pada layanan yang sangat dimuat, dan Anda tidak harus menunggu akhir dari beberapa operasi, maka Anda dapat melakukan beberapa hal secara serempak di suatu tempat di latar belakang atau sebagai proses terpisah. Lebih baik melakukannya dengan cara ini, karena proses terpisah memiliki dampak yang lebih kecil pada produksi, dan sistem akan bekerja secara keseluruhan lebih stabil.



Penting juga bahwa semua data yang kita terima dari luar, kita tidak boleh mempercayai mereka, kita harus entah bagaimana memvalidasi, memverifikasi, dll. Semua data yang kita miliki harus dibagi menjadi kelompok-kelompok yang telah kita bentuk , atau data mentah yang tidak lulus validasi. Ini termasuk semua data yang berpotensi diperoleh dari beberapa layanan eksternal lain atau langsung dari pengguna, karena apa pun berpotensi datang. Mungkin seseorang secara khusus mengirim permintaan jahat, dan semuanya harus diperiksa bersama kami.



Masih ada beberapa kasus yang, berdasarkan permintaan, layanan mungkin tidak merespons pada waktu yang tepat. Mungkin koneksi terputus atau ada yang salah, mungkin ada banyak situasi. Aplikasi mobile tidak tahu apa yang akhirnya terjadi, hanya melakukan permintaan ulang.

Sangat penting bahwa dalam proses permintaan ulang ini, tidak peduli berapa banyak yang ada, pada akhirnya semuanya berfungsi seperti yang diharapkan semula dengan satu permintaan. Kita seharusnya tidak memiliki efek khusus. Perlu juga diingat bahwa kami memiliki lebih dari satu layanan, kami memiliki banyak mesin, banyak pusat data, kami telah mendistribusikan basis, dan balapan dimungkinkan untuk semua orang. Kode harus ditulis sehingga jika dijalankan di beberapa tempat pada saat yang sama, sehingga kita tidak memiliki balapan.

Poin yang sama pentingnya adalah kemampuan untuk mendiagnosis masalah. Masalah selalu ada, dalam segala hal, dan Anda perlu memahami di mana mereka terjadi. Dalam situasi yang ideal, keberadaan masalah tidak dipelajari melalui layanan dukungan, tetapi melalui pemantauan. Dan sementara mengurai beberapa situasi, kita akhirnya bisa memahami apa yang terjadi hanya dengan membaca log tanpa membaca kode. Bahkan orang yang belum pernah melihat kode, sehingga dengan log pada akhirnya bisa mendapatkannya.

Dan dalam kasus yang ideal, jika situasinya sangat rumit, Anda harus dapat memeriksa log ke arah mana program berjalan pada akhirnya, dan apa yang terjadi sangat menyederhanakan pembekalan. Karena situasinya berjalan sebagai hasil di masa lalu, dan sekarang tidak mungkin untuk mereproduksi, tidak ada data atau data lain atau situasi lain.

Jika Anda melakukan operasi baru dalam database atau membuat yang baru, Anda perlu mempertimbangkan bahwa mungkin ada banyak data. Mungkin Anda akan menulis jumlah rekaman yang tak terbatas ke basis data ini, dan jika Anda tidak berpikir tentang pengarsipan, maka mungkin ada masalah, basis data akan mulai tumbuh tanpa batas waktu, dan tidak akan ada lagi sumber daya, tidak ada disk, dan sharding. Penting untuk dapat mengarsipkan data, dan hanya menyimpan data operasional yang diperlukan saat ini. Dan juga perlu membuat kueri indeks ke semua basis data. Permintaan non-indeks dapat menempatkan seluruh produksi. Satu permintaan kecil ke pusat koleksi paling banyak dapat menempatkan semuanya. Anda harus sangat berhati-hati.

Kami tidak menerima optimisasi dini. Jika seseorang mencoba membuat semacam pabrik metode yang sangat universal yang berpotensi menangani kasus untuk masa depan, mungkin suatu hari nanti seseorang akan ingin mengembangkannya - ini bukan kebiasaan kami, karena ada kemungkinan bahwa itu akan berkembang itu akan sepenuhnya salah, dan mungkin kode ini pada akhirnya akan dikubur, atau mungkin itu semua tidak perlu, tetapi hanya mempersulit pembacaan dan pemahaman kode. Karena membaca dan memahami kode sangat penting. Penting bahwa kodenya sangat sederhana dan mudah.

Jika Anda menambahkan database baru dalam kode Anda atau membuat perubahan pada API, kami memiliki dokumentasi yang sebagian dihasilkan dari kode, sebagian dilakukan di Wiki. Informasi ini penting untuk selalu mendapat informasi terbaru. Jika tidak, itu dapat menyesatkan seseorang atau menyebabkan masalah bagi pengembang lain. Karena kodenya ditulis sendiri, tetapi didukung banyak.

Bagian penting adalah ketaatan terhadap gaya umum. Hal utama dalam hal ini adalah keseragaman. Ketika semua kode ditulis dengan cara yang seragam, mudah dipahami, mudah dibaca, dan Anda tidak perlu mempelajari semua detail dan nuansa. Kode tertulis yang seragam dapat mempercepat seluruh proses pengembangan secara potensial di masa depan.

Poin lain yang kami tidak secara khusus memeriksa ulasan adalah bahwa kami tidak mencari bug. Karena penulis harus terlibat dalam pencarian bug. Jika ada bug selama peninjauan, tentu saja, mereka akan menulis tentang itu, tetapi seharusnya tidak ada pencarian yang bertujuan, ini sepenuhnya menjadi tanggung jawab orang yang menulis kode.

Selanjutnya, ketika kode Anda ditulis, tinjauan selesai, Anda siap untuk membekukannya, tetapi sering kali Anda perlu melakukan tindakan tambahan, bermigrasi ke database.



Untuk migrasi, kami menulis skrip Python yang dapat berkomunikasi dengan backend. Backend, pada gilirannya, memiliki koneksi ke semua pangkalan kami dan dapat melakukan semua operasi yang diperlukan. Skrip diluncurkan melalui panel admin peluncuran skrip, lalu dijalankan, Anda dapat melihat log dan hasilnya. Dan jika Anda membutuhkan operasi balok jangka panjang, maka Anda tidak dapat memperbarui semuanya sekaligus, Anda perlu melakukannya dengan potongan 1000-10000 dengan beberapa jeda, agar tidak secara tidak sengaja meletakkan pangkalan dengan operasi ini.



Saat kode ditulis, ditinjau, diuji, semua migrasi dilakukan, Anda dapat menggabungkannya dengan aman di GitHub dan terus melepaskannya.

Untuk beberapa layanan, kami memiliki peraturan yang menurutnya harus kami luncurkan pada waktu tertentu, tetapi sebagian besar layanan yang dapat kami luncurkan kapan saja.

Ini semua dilakukan dengan TeamCity.



Semuanya dimulai dengan membangun paket. TeamCity tidak git flow atau kemiripannya. Kami perlahan-lahan beralih dari aliran git ke praktik terbaik kami, yang kami pikir lebih nyaman. TeamCity menghasilkan semua ini, mengumpulkan paket, mengisinya. Kami menunggu lebih lanjut ketika tes akan melewati paket-paket ini. Lulus tes diperlukan untuk meluncurkan rilis. Jika tes gagal, maka pertama-tama Anda harus mencari tahu dan melihat apa yang akhirnya salah. Tes yang digunakan sama, teratur dan terintegrasi. Mereka memeriksa paket yang sudah dirakit, siap, persis apa yang akan diproduksi. Ini hanya dalam kasus, tiba-tiba ada masalah dalam paket yang dirakit, tiba-tiba ada sesuatu yang tidak disalin, tiba-tiba ada sesuatu yang hilang.

Ada juga persyaratan bahwa kami membuat tiket rilis di pelacak kami, di mana setiap pengembang harus berhenti berlangganan bagaimana ia menguji kode ini, dan itu harus berisi semua tugas yang harus diselesaikan.

Ini juga dilakukan secara otomatis di TeamCity, yang melewati daftar commit. Kami memiliki persyaratan bahwa dalam setiap komit harus ada kata kunci "Relates" diikuti dengan nama tugas. Sebuah skrip yang ditulis dengan Python secara otomatis melewati semua ini, menyusun daftar tugas yang telah diselesaikan, membentuk daftar penulis dan membuat tiket rilis, mendesak semua penulis untuk berhenti berlangganan tentang pengujian mereka dan mengonfirmasi bahwa mereka siap untuk "pergi" dalam rilis.



Ketika semua orang siap, konfirmasi dikumpulkan, kemudian peluncuran, pertama - di pra-stabil. Ini adalah bagian kecil dari produksi. Untuk setiap layanan beberapa pusat data digunakan, di setiap pusat data dapat ada beberapa mesin. Salah satu mesin adalah pra-stabil, dan kode diluncurkan pertama hanya untuk satu atau beberapa mesin.

Ketika kode kempis, kita mengikuti grafik, log, dan apa yang terjadi pada akhirnya pada layanan. Jika semuanya baik-baik saja, jika grafik menunjukkan bahwa semuanya stabil, dan semua orang telah memeriksa bahwa fungsinya berfungsi sebagaimana mestinya, maka itu berguling ke seluruh lingkungan, yang kita sebut stabil. Ketika meluncur ke stabil, semuanya sama: kita melihat grafik, mencatat dan memeriksa bahwa semuanya baik-baik saja dengan kita.

Peluncuran telah berlalu, semuanya baik-baik saja. Dan jika ada yang salah, jika tiba-tiba ada masalah?



Kami mengumpulkan perbaikan terbaru. Hal ini dilakukan dengan prinsip yang sama dengan aliran git, yaitu cabang dari cabang master. Permintaan kumpulan terpisah dibuat dari master, yang membuat koreksi, dan kemudian skrip diluncurkan dari TeamCity membekukannya, melakukan semua operasi yang diperlukan, mengumpulkan semua paket dengan cara yang sama, dan melanjutkan.



Pada akhirnya, saya ingin berbicara tentang arah pergerakan kami. Kami bergerak menuju satu repositori, ketika banyak layanan tinggal dalam satu repositori sekaligus. Masing-masing memiliki perhitungan independen: dalam pengujian, dalam rilis. Untuk permintaan kumpulan, bahkan ketika TeamCity digunakan, kami memeriksa file mana yang terpengaruh, layanan mana yang mereka ikuti. Dengan menggunakan grafik dependensi, kami menentukan tes mana yang pada akhirnya perlu kami jalankan dan apa yang harus diperiksa. Kami berusaha untuk isolasi maksimum layanan dari satu sama lain. Sejauh ini, ini tidak berhasil dengan baik, tetapi kami berusaha keras untuk ini sehingga banyak layanan dapat hidup dalam satu repositori, memiliki beberapa kode umum, dan ini tidak menyebabkan masalah dan menyederhanakan kehidupan pengembangan. Itu saja, terima kasih semuanya.

Source: https://habr.com/ru/post/id439704/


All Articles