Saya Menguji di Prod

Saya Menguji di Prod

“Saya tidak selalu menguji kode saya,” renung Orang Paling Menarik di Dunia dalam salah satu meme teknologi paling kokoh sepanjang masa, “tetapi ketika saya melakukannya, saya menguji dalam produksi.”

Saya sudah menertawakan meme itu sejak pertama kali melihatnya di . . . 2013? Ini lebih dari lucu. Ini lucu!

Sejak itu, “test in prod” telah menjadi singkatan untuk semua cara kami yang tidak bertanggung jawab berinteraksi dengan layanan produksi atau mengambil jalan pintas dalam terburu-buru kami untuk mengirim, dan untuk ini saya menyalahkan Yang Paling Menarik Manusia dan meme-nya yang tak tertahankan.

Karena, sejujurnya, kita semua selalu menguji produksi. (Setidaknya, para insinyur yang baik melakukannya.) Ini pada dasarnya tidak buruk atau tanda kelalaian—sebenarnya merupakan hal yang baik bagi para insinyur untuk berinteraksi dengan produksi setiap hari, mengamati kode yang mereka tulis saat berinteraksi dengan infrastruktur dan pengguna di cara yang tidak pernah mereka duga.

Pengujian dalam produksi adalah kekuatan super. Ketidakmampuan kami untuk mengakui bahwa kami melakukannya, dan kemudian berinvestasi dalam peralatan dan pelatihan untuk melakukannya dengan aman, itu membunuh kami.

Pada intinya, pengujian adalah tentang mengurangi ketidakpastian dengan memeriksa kegagalan yang diketahui, kegagalan masa lalu, dan kegagalan yang dapat diprediksi—seolah-olah hal yang tidak diketahui yang Anda ketahui. Jika saya menjalankan sepotong kode deterministik di lingkungan tertentu, saya berharap hasilnya berhasil atau gagal dengan cara yang berulang, dan ini memberi saya kepercayaan pada kode itu untuk lingkungan itu. Keren.

Sistem modern dibangun dari blok bangunan yang dapat diuji ini, tetapi juga:

    Banyak koneksi bersamaan

    Stack jaringan tertentu dengan tunable, firmware, dan NIC tertentu

  • Kemampuan serial yang rapuh atau tidak ada dalam koneksi

  • Kondisi balapan

    Layanan digabungkan secara longgar melalui jaringan

    Kelemahan jaringan

    Waktu proses singkat

    CPU tertentu dan bug mereka; multiprosesor

    RAM perangkat keras tertentu dan bug memori

    Distro, kernel, dan versi OS tertentu

    Versi pustaka khusus untuk semua dependensi

    Build environment

    Kode dan proses penerapan

    Waktu proses dimulai ulang

    Cache hits atau misses

    Container atau VM tertentu dan bugnya

  • Penjadwal khusus dan kebiasaannya

  • Klien dengan back-off, percobaan ulang, dan time-out khusus mereka sendiri

    Internet pada umumnya

    Waktu tertentu dalam sehari, minggu, bulan, tahun, dan dekade

    Tetangga yang bising

  • Kawanan petir

  • Antrian

    Operator dan debugger manusia

    Pengaturan lingkungan

  • Kematian, cobaan, dan peristiwa dunia nyata lainnya

Ketika kami mengatakan “produksi”, kami biasanya mengartikan konstelasi dari semua hal ini dan banyak lagi. Terlepas dari upaya terbaik kami untuk mengabstraksi detail tingkat rendah yang mengganggu seperti versi firmware pada kartu eth0 Anda pada instance EC2, saya di sini untuk mengecewakan: Anda masih harus peduli tentang hal-hal itu pada kesempatan yang tidak terduga.

Dan jika pengujian adalah tentang ketidakpastian, Anda “menguji” setiap kali Anda menerapkan ke produksi. Bagaimanapun, setiap penerapan adalah kombinasi artefak, lingkungan, infra, dan waktu yang unik dan tidak akan pernah direplikasi. Pada saat Anda mengujinya, itu telah berubah.

Setelah Anda menerapkan, Anda tidak menguji kode lagi, Anda sedang menguji sistem—sistem kompleks yang terdiri dari pengguna, kode, lingkungan, infrastruktur, dan titik waktu. Sistem ini memiliki interaksi yang tidak dapat diprediksi, tidak memiliki keteraturan yang waras, dan mengembangkan sifat yang muncul yang terus-menerus dan selamanya menentang kemampuan Anda untuk menguji secara deterministik.

Ungkapan “Saya tidak selalu menguji, tetapi ketika saya melakukannya, Saya menguji dalam produksi” tampaknya menyindir bahwa Anda hanya dapat melakukan satu atau yang lain: uji sebelum produksi atau uji dalam produksi. Tapi itu dikotomi palsu. Semua tim yang bertanggung jawab melakukan kedua jenis pengujian.

Namun kami hanya mengakui jenis pengujian pertama, jenis “bertanggung jawab”. Tidak ada yang mengakui yang kedua, apalagi berbicara tentang bagaimana kita bisa melakukannya dengan lebih baik dan lebih aman. Tidak ada yang berinvestasi dalam alat “test in prod” mereka. Dan itulah salah satu alasan kami melakukannya dengan sangat buruk.

“Bekerja dengan baik di dev; masalah ops sekarang.”

Bagi kebanyakan dari kita, sumber daya paling langka di dunia adalah siklus rekayasa. Setiap kali kita memilih untuk melakukan sesuatu dengan waktu kita, kita secara implisit memilih untuk tidak melakukan ratusan hal lainnya. Memilih untuk menghabiskan waktu kita yang berharga adalah salah satu hal tersulit yang dapat dilakukan oleh tim mana pun. Itu dapat membuat atau menghancurkan perusahaan.

Sebagai sebuah industri, kami secara sistematis kurang berinvestasi dalam peralatan untuk sistem produksi. Cara kita berbicara tentang pengujian dan cara kita benar-benar bekerja dengan perangkat lunak secara eksklusif berpusat pada pencegahan masalah agar tidak pernah mencapai produksi. Mengakui bahwa beberapa bug akan berhasil tidak peduli apa yang kita lakukan telah menjadi kenyataan yang tak terkatakan. Karena itu, kami menemukan diri kami kekurangan cara untuk memahami, mengamati, dan menguji kode kami secara ketat dalam fase perkembangan luteal yang paling penting.

Biarkan saya menceritakan sebuah kisah. Pada Mei 2019, kami memutuskan untuk memutakhirkan Ubuntu untuk seluruh infrastruktur produksi Honeycomb. Ubuntu 14.04 AMI akan segera kehabisan dukungan, dan belum diluncurkan secara sistematis sejak saya pertama kali menyiapkan infra kami pada tahun 2015. Kami melakukan semua hal yang bertanggung jawab: Kami mengujinya, kami menulis skrip, kami meluncurkannya ke server pementasan dan dogfood terlebih dahulu. Kemudian kami memutuskan untuk meluncurkannya ke prod. Hal-hal tidak berjalan seperti yang direncanakan. (Apakah mereka pernah?)

Ada masalah dengan pekerjaan cron yang berjalan pada jam saat bootstrap masih berjalan. (Kami menggunakan ekstensif grup penskalaan otomatis, dan node penyimpanan data kami melakukan bootstrap satu sama lain.) Ternyata, kami hanya menguji bootstrap selama 50 dari 60 menit dalam satu jam. Secara alami, sebagian besar masalah yang kami lihat adalah dengan node penyimpanan kami, karena tentu saja demikian.

Ada masalah dengan data yang kedaluwarsa saat rsync-ing selesai. Rsync panik ketika tidak melihat metadata untuk file segmen, dan sebaliknya. Ada masalah seputar instrumentasi, restart yang anggun, dan penspasian nama. Biasa. Tapi mereka semua adalah contoh bagus dari pengujian yang bertanggung jawab di prod.

Kami melakukan jumlah pengujian yang sesuai di lingkungan palsu. Kami melakukan sebanyak yang kami bisa di lingkungan nonproduksi. Kami membangun perlindungan. Kami mempraktikkan pengembangan yang didorong oleh observabilitas. Kami menambahkan instrumentasi sehingga kami dapat melacak kemajuan dan menemukan kegagalan. Dan kami meluncurkannya sambil mengamatinya secara dekat dengan mata manusia untuk mencari perilaku yang tidak biasa atau masalah menakutkan.

Bisakah kami memperbaiki semua bug sebelum menjalankannya dalam prod? Tidak. Anda tidak akan pernah bisa menjamin bahwa Anda telah menyelesaikan semua bug. Kami tentu saja dapat menghabiskan lebih banyak waktu untuk mencoba meningkatkan kepercayaan diri bahwa kami telah mengatasi semua kemungkinan bug, tetapi Anda dengan cepat mencapai titik pengembalian yang berkurang dengan cepat.

Kami adalah startup. Startup tidak cenderung gagal karena mereka bergerak terlalu cepat. Mereka cenderung gagal karena terobsesi dengan hal-hal sepele yang sebenarnya tidak memberikan nilai bisnis. Sangatlah penting bagi kami untuk mencapai tingkat kepercayaan yang wajar, menangani kesalahan, dan memiliki beberapa tingkat fail-safe (yaitu, cadangan).

“Apa yang kita katakan kepada dewa waktu henti? Tidak hari ini.”

Kami melakukan eksperimen dalam manajemen risiko setiap hari, seringkali tanpa disadari. Setiap kali Anda memutuskan untuk menggabungkan untuk menguasai atau menyebarkan ke prod, Anda mengambil risiko. Setiap kali Anda memutuskan untuk tidak menggabungkan atau menyebarkan, Anda mengambil risiko. Dan jika Anda berpikir terlalu keras tentang semua risiko yang Anda ambil, itu sebenarnya bisa melumpuhkan.

Risiko untuk tidak menerapkannya bisa lebih kecil daripada risikonya, tetapi bukan itu masalahnya. Ini hanya jenis risiko yang berbeda. Anda berisiko tidak mengirimkan barang yang dibutuhkan atau diinginkan pengguna Anda; Anda mengambil risiko budaya penyebaran yang lamban; Anda berisiko kalah dari pesaing Anda. Lebih baik sering mempraktikkan hal-hal yang berisiko dan dalam porsi kecil, dengan radius ledakan terbatas, daripada menghindari hal-hal yang berisiko sama sekali.

Organisasi akan berbeda dalam selera risikonya. Dan bahkan di dalam sebuah organisasi, mungkin ada berbagai toleransi terhadap risiko. Toleransi cenderung terendah dan paranoia tertinggi semakin dekat Anda dengan meletakkan bit pada disk, terutama data pengguna atau data penagihan. Toleransi cenderung lebih tinggi terhadap sisi alat pengembang atau dengan layanan offline atau tanpa kewarganegaraan, di mana kesalahan kurang terlihat oleh pengguna atau permanen. Banyak insinyur, jika Anda bertanya kepada mereka, akan menyatakan penolakan mutlak mereka terhadap semua risiko. Mereka dengan penuh semangat percaya bahwa kesalahan apa pun adalah kesalahan yang terlalu banyak. Namun para insinyur ini entah bagaimana berhasil meninggalkan rumah setiap pagi dan kadang-kadang bahkan mengendarai mobil. (Horor!) Risiko melingkupi semua yang kita lakukan.

Risiko tidak bertindak kurang terlihat, tetapi tidak kalah mematikan. Mereka lebih sulit untuk diinternalisasi ketika mereka diamortisasi dalam jangka waktu yang lebih lama atau dirasakan oleh tim yang berbeda. Disiplin teknik yang baik terdiri dari memaksa diri sendiri untuk mengambil risiko kecil setiap hari dan tetap dalam praktik yang baik.

“Seseorang tidak hanya meluncurkan perangkat lunak tanpa bug.”

Faktanya adalah, sistem terdistribusi ada dalam keadaan degradasi parsial yang berkelanjutan. Kegagalan adalah satu-satunya yang konstan. Kegagalan sedang terjadi pada sistem Anda saat ini dalam ratusan cara yang tidak Anda sadari dan mungkin tidak pernah Anda pelajari. Terobsesi dengan kesalahan individu, paling banter, akan mendorong Anda ke dasar botol wiski terdekat dan membuat Anda terjaga sepanjang malam. Ketenangan pikiran (dan tidur nyenyak) hanya dapat diperoleh kembali dengan merangkul anggaran kesalahan melalui tujuan tingkat layanan (SLO) dan indikator tingkat layanan (SLI), berpikir kritis tentang seberapa banyak kegagalan yang dapat ditoleransi pengguna, dan menghubungkan loop umpan balik untuk memberdayakan insinyur perangkat lunak untuk memiliki sistem mereka dari ujung ke ujung.

Ketahanan sistem tidak ditentukan oleh kurangnya kesalahan; itu ditentukan oleh kemampuannya untuk bertahan dari banyak, banyak, banyak kesalahan. Kami membangun sistem yang lebih ramah bagi manusia dan pengguna bukan dengan mengurangi toleransi kami terhadap kesalahan, tetapi dengan meningkatkannya. Kegagalan bukan untuk ditakuti. Kegagalan harus dirangkul, dipraktikkan, dan dijadikan teman baik Anda.

Ini berarti kita perlu saling membantu mengatasi ketakutan dan paranoia kita di sekitar sistem produksi. Anda harus sampai siku Anda di prod setiap hari. Prod adalah tempat tinggal pengguna Anda. Prod adalah tempat pengguna berinteraksi dengan kode Anda di infrastruktur Anda.

Dan karena kami secara sistematis kurang berinvestasi dalam perkakas terkait prod, kami telah memilih untuk melarang orang dari prod langsung daripada membangun pagar pembatas yang dengan default membantu mereka melakukan hal yang benar dan membuatnya sulit untuk melakukan hal yang salah. Kami telah menugaskan alat penerapan untuk pekerja magang, bukan untuk teknisi kami yang paling senior. Kami telah membangun sebuah kastil kaca di mana kami seharusnya memiliki taman bermain.

“Beberapa orang suka melakukan debug hanya dalam pementasan. Saya juga suka hidup berbahaya.”

Bagaimana kita keluar dari kekacauan ini? Ada tiga aspek dari jawaban ini: teknis, budaya, dan manajerial.

Teknis

Ini adalah permainan instrumentasi. Kami berada di belakang di mana kami seharusnya sebagai industri dalam hal mengembangkan dan menyebarkan konvensi waras seputar observabilitas dan instrumentasi karena kami telah membangun terlalu lama agar sesuai dengan batasan format data bodoh. Alih-alih melihat instrumentasi sebagai upaya terakhir dari string dan metrik, kita harus berpikir tentang menyebarkan konteks penuh permintaan dan memancarkannya secara teratur. Permintaan tarik tidak boleh diterima kecuali insinyur dapat menjawab pertanyaan, “Bagaimana saya tahu jika ini rusak?”

Budaya

Insinyur harus siap untuk kode mereka sendiri. Saat menyebarkan, kita harus secara refleks melihat dunia melalui lensa instrumentasi kita. Apakah itu bekerja seperti yang kita harapkan? Apakah ada yang tampak aneh? Meskipun kabur dan tidak tepat, itu satu-satunya cara Anda akan menangkap hal-hal yang tidak diketahui yang berbahaya itu, masalah yang tidak pernah Anda harapkan.

Manajerial

Pendekatan tanpa toleransi yang tidak sehat terhadap kesalahan ini paling sering datang dari tekanan manajemen yang gila-kontrol. Manajer perlu belajar berbicara dalam bahasa kesalahan anggaran, SLO, dan SLI, untuk berorientasi pada hasil daripada menyelami detail tingkat rendah dengan cara yang membawa bencana dan tidak dapat diprediksi. Adalah tugas manajemen untuk mengatur nada (dan menahan garis) bahwa kesalahan dan kegagalan adalah teman dan hum guru, bukan sesuatu yang harus ditakuti dan dihindari. Tenang. Tenang. Pujilah perilaku yang ingin Anda lihat lebih banyak.

Ini juga tugas manajemen untuk mengalokasikan sumber daya pada tingkat tinggi. Manajer perlu menyadari bahwa 80 persen bug ditangkap dengan 20 persen upaya, dan setelah itu Anda mendapatkan hasil yang menurun tajam. Sistem perangkat lunak modern membutuhkan lebih sedikit investasi dalam pengerasan pra-prod dan lebih banyak investasi dalam ketahanan pasca-prod.

Saya menguji di prod.

Ada banyak waktu antara hanya melemparkan kode Anda ke dinding dan menunggu untuk mendapatkan halaman dan mengawasi kode Anda saat dikirimkan, mengawasi instrumentasi Anda, dan secara aktif melenturkan fitur-fitur baru. Ada banyak ruang untuk variasi sesuai dengan kebutuhan keamanan, persyaratan produk, dan bahkan perbedaan sosiokultural antar tim. Namun, satu-satunya konstanta adalah ini: Pekerjaan seorang insinyur perangkat lunak modern tidak selesai sampai mereka melihat pengguna menggunakan kode mereka dalam produksi.

Kita sekarang tahu bahwa satu-satunya cara untuk membangun sistem berkualitas tinggi adalah berinvestasi dalam kepemilikan perangkat lunak, membuat insinyur yang menulis layanan bertanggung jawab atas mereka hingga produksi. Kami juga tahu bahwa kami hanya dapat mengharapkan orang-orang siap siaga dalam jangka panjang jika kami membayar sejumlah besar gangguan dan insiden paging yang dihadapi sebagian besar tim. Ketahanan berjalan seiring dengan kepemilikan, yang sejalan dengan kualitas hidup. Ketiganya saling memperkuat dalam siklus yang baik. Anda tidak dapat mengerjakan satu dalam isolasi: Budaya eksperimen dan pengujian yang sehat dalam produksi menyatukan ketiganya.

Menghapus beberapa siklus dari yang tidak diketahui yang diketahui dan mengalokasikannya ke yang tidak diketahui—yang benar-benar masalah sulit—adalah satu-satunya cara untuk menutup loop dan membangun sistem yang benar-benar matang yang menawarkan layanan berkualitas tinggi kepada pengguna dan kualitas hidup yang tinggi untuk tender manusia mereka.

Ya, Anda harus menguji sebelum dan di prod. Tetapi jika saya harus memilih—dan untungnya saya tidak—saya akan memilih kemampuan untuk melihat kode saya dalam produksi daripada semua pengujian pra-prod di dunia. Hanya satu yang mewakili kenyataan. Hanya satu yang memberi Anda kekuatan dan fleksibilitas untuk menjawab pertanyaan apa pun. Itu sebabnya saya menguji di prod.

Baca selengkapnya