Pengujian pada Prod: Canary Deployment

Burung kenari adalah burung kecil yang berkicau terus-menerus. Burung-burung ini sensitif terhadap metana dan karbon monoksida. Bahkan konsentrasi kecil gas berlebih di udara menyebabkan mereka kehilangan kesadaran atau mati. Penambang emas dan penambang membawa burung ke tambang: sambil burung kenari berkicau, Anda bisa bekerja, jika diam, ada gas di tambang dan saatnya berangkat. Para penambang mengorbankan seekor burung kecil untuk keluar dari tambang hidup-hidup.

Pengujian pada Prod: Canary Deployment

Praktik serupa juga terjadi di bidang TI. Misalnya, dalam tugas standar menerapkan versi baru layanan atau aplikasi ke produksi dengan pengujian sebelumnya. Lingkungan pengujian bisa jadi terlalu mahal, pengujian otomatis tidak mencakup semua yang kita inginkan, dan tidak menguji serta mengorbankan kualitas adalah hal yang berisiko. Dalam kasus seperti itulah pendekatan Canary Deployment membantu, ketika sedikit lalu lintas produksi nyata dikirim ke versi baru. Pendekatan ini membantu dengan aman periksa versi baru untuk produksi, mengorbankan sedikit demi tujuan besar. Dia akan memberi tahu Anda secara lebih rinci bagaimana pendekatan ini bekerja, mengapa pendekatan ini berguna dan bagaimana menerapkannya. Andrey Markelov (Andrey_V_Markelov), berdasarkan contoh implementasi di Infobip.

Andrey Markelov — Insinyur perangkat lunak terkemuka di Infobip, telah mengembangkan aplikasi Java di bidang keuangan dan telekomunikasi selama 11 tahun. Mengembangkan produk Open Source, berpartisipasi aktif dalam Komunitas Atlassian dan menulis plugin untuk produk Atlassian. Prometheus, penginjil Docker dan Redis.

Putar video

Tentang Infobip

Ini adalah platform telekomunikasi global yang memungkinkan bank, ritel, toko online, dan perusahaan transportasi mengirim pesan ke pelanggan mereka menggunakan SMS, push, surat, dan pesan suara. Dalam bisnis seperti itu, stabilitas dan keandalan penting agar pelanggan menerima pesan tepat waktu.

Infrastruktur TI Infobip dalam jumlah:

  • 15 pusat data di seluruh dunia;
  • 500 layanan unik yang beroperasi;
  • 2500 contoh layanan, lebih dari sekadar perintah;
  • lalu lintas bulanan 4,5 TB;
  • 4,5 miliar nomor telepon;

Bisnis ini berkembang, dan seiring dengan itu, jumlah rilisnya pun meningkat. Kami melaksanakan 60 rilis per hari, karena pelanggan menginginkan lebih banyak fitur dan kekuatan. Tapi ini sulit - ada banyak layanan, tetapi sedikit tim. Anda harus dengan cepat menulis kode yang seharusnya berfungsi dalam produksi tanpa kesalahan.

Rilis

Rilisan tipikal berjalan seperti ini. Misalnya ada layanan A, B, C, D dan E yang masing-masing dikembangkan oleh tim tersendiri.

Pengujian pada Prod: Canary Deployment

Pada titik tertentu, tim layanan A memutuskan untuk menerapkan versi baru, namun tim layanan B, C, D, dan E tidak mengetahuinya. Ada dua pilihan apa yang akan dilakukan tim layanan A.

Akan melakukan rilis tambahan: pertama-tama akan mengganti satu versi, lalu versi kedua.

Pengujian pada Prod: Canary Deployment

Tapi ada pilihan kedua: perintah akan menemukan kapasitas dan mesin tambahan, akan menerapkan versi baru, lalu mengganti router, dan versi tersebut akan mulai berfungsi untuk produksi.

Pengujian pada Prod: Canary Deployment

Dalam versi apa pun, masalah hampir selalu muncul setelah penerapan, meskipun versi tersebut telah diuji. Anda dapat mengujinya secara manual, Anda dapat melakukannya secara otomatis, Anda tidak perlu mengujinya - masalah akan tetap muncul. Cara paling sederhana dan paling benar untuk menyelesaikannya adalah dengan mengembalikan ke versi yang berfungsi. Hanya dengan begitu Anda dapat mengatasi kerusakan, penyebabnya, dan memperbaikinya.

Jadi apa yang kita inginkan?

Kami tidak membutuhkan masalah. Jika pelanggan menemukannya lebih cepat dari kami, reputasi kami akan rusak. Oleh karena itu kita harus menemukan masalah lebih cepat dari klien. Dengan bekerja secara proaktif, kami meminimalkan kerusakan.

Pada saat yang sama, kami ingin mempercepat penerapansehingga hal ini terjadi dengan cepat, mudah, alami dan tanpa ketegangan di pihak tim. Insinyur, insinyur DevOps, dan pemrogram harus dilindungi - merilis versi baru merupakan hal yang menegangkan. Tim ini tidak bisa dibuang, kami berusaha penggunaan sumber daya manusia secara rasional.

Masalah penerapan

Lalu lintas klien tidak dapat diprediksi. Tidak mungkin untuk memprediksi kapan lalu lintas klien akan minimal. Kami tidak tahu di mana atau kapan klien akan memulai kampanye mereka - mungkin malam ini di India, besok di Hong Kong. Mengingat perbedaan waktu yang besar, penerapan bahkan pada jam 2 pagi tidak menjamin bahwa klien tidak akan dirugikan.

Masalah penyedia. Messenger dan penyedia adalah mitra kami. Terkadang mereka mengalami gangguan yang menyebabkan kesalahan selama penerapan versi baru.

Tim Terdistribusi. Tim yang mengembangkan sisi klien dan backend berlokasi di zona waktu yang berbeda. Karena itu, mereka seringkali tidak bisa sepakat satu sama lain.

Pusat data tidak dapat direplikasi di panggung. Ada 200 rak dalam satu pusat data - bahkan tidak mungkin mengulanginya di kotak pasir.

Waktu hentitidak dapat diterima! Kami memiliki tingkat ketersediaan yang dapat diterima (Anggaran Kesalahan), ketika kami bekerja 99,99% dari waktu, misalnya, dan persentase sisanya adalah “hak untuk kesalahan”. Tidak mungkin mencapai keandalan 100%, namun penting untuk terus memantau kerusakan dan waktu henti.

Solusi klasik

Tulis kode tanpa bug. Ketika saya masih seorang pengembang muda, manajer mendekati saya untuk meminta rilis bebas bug, tetapi hal ini tidak selalu memungkinkan.

Tes tulis. Pengujian berhasil, namun terkadang tidak berjalan sesuai keinginan bisnis. Menghasilkan uang bukanlah tugas ujian.

Tes di atas panggung. Selama 3,5 tahun saya bekerja di Infobip, saya belum pernah melihat keadaan panggung, setidaknya sebagian, bertepatan dengan produksi.

Pengujian pada Prod: Canary Deployment

Kami bahkan mencoba mengembangkan ide ini: pertama kami punya panggung, lalu praproduksi, lalu praproduksi praproduksi. Tapi ini juga tidak membantu - mereka bahkan tidak memiliki kekuatan yang setara. Dengan sebuah panggung, kami dapat menjamin fungsionalitas dasar, tetapi kami tidak tahu bagaimana cara kerjanya saat dimuat.

Rilis ini dibuat oleh orang yang mengembangkannya. Ini adalah praktik yang baik: meskipun seseorang mengubah nama komentar, komentar tersebut akan segera ditambahkan ke produksi. Ini membantu mengembangkan tanggung jawab dan mengingat perubahan yang dilakukan.

Ada komplikasi tambahan juga. Sangat menegangkan bagi pengembang untuk menghabiskan banyak waktu memeriksa semuanya secara manual.

Rilis yang disepakati. Opsi ini biasanya ditawarkan oleh manajemen: “Setujui bahwa Anda akan menguji dan menambahkan versi baru setiap hari.” Ini tidak berhasil: selalu ada tim yang menunggu orang lain atau sebaliknya.

Tes asap

Cara lain untuk menyelesaikan masalah kita adalah dengan penerapan. Mari kita lihat cara kerja pengujian asap pada contoh sebelumnya, ketika tim A ingin menerapkan versi baru.

Pertama, tim menyebarkan satu contoh ke produksi. Pesan ke instance dari tiruan lalu lintas nyata disimulasikan, sehingga bertepatan dengan lalu lintas normal sehari-hari. Jika semuanya baik-baik saja, tim akan mengalihkan versi baru ke lalu lintas pengguna.

Pengujian pada Prod: Canary Deployment

Opsi kedua adalah menyebarkan dengan tambahan zat besi. Tim mengujinya dalam produksi, lalu menggantinya, dan semuanya berfungsi.

Pengujian pada Prod: Canary Deployment

Kerugian dari tes asap:

  • Tes tidak bisa dipercaya. Di mana saya bisa mendapatkan lalu lintas yang sama seperti untuk produksi? Anda dapat menggunakan kemarin atau seminggu yang lalu, tetapi tidak selalu bersamaan dengan yang sekarang.
  • Sulit untuk dipertahankan. Anda harus memelihara akun pengujian dan terus-menerus mengatur ulangnya sebelum setiap penerapan, ketika catatan aktif dikirim ke penyimpanan. Ini lebih sulit daripada menulis tes di sandbox Anda sendiri.

Satu-satunya bonus di sini adalah Anda dapat memeriksa kinerjanya.

Rilis kenari

Karena kekurangan tes asap, kami mulai menggunakan pelepasan kenari.

Sebuah praktik yang mirip dengan bagaimana para penambang menggunakan burung kenari untuk menunjukkan tingkat gas telah diterapkan di bidang TI. Kami mengizinkan masuk beberapa lalu lintas produksi nyata untuk versi baru, saat mencoba memenuhi Perjanjian Tingkat Layanan (SLA). SLA adalah “hak untuk melakukan kesalahan” yang dapat kami gunakan setahun sekali (atau untuk jangka waktu lain). Jika semuanya berjalan dengan baik, kami akan menambahkan lebih banyak lalu lintas. Jika tidak, kami akan mengembalikan versi sebelumnya.

Pengujian pada Prod: Canary Deployment

Implementasi dan nuansa

Bagaimana kami menerapkan pelepasan kenari? Misalnya, sekelompok klien mengirimkan pesan melalui layanan kami.

Pengujian pada Prod: Canary Deployment

Penerapannya berjalan seperti ini: kami menghapus satu node dari bawah penyeimbang (1), mengubah versi (2) dan secara terpisah membiarkan beberapa lalu lintas masuk (3).

Pengujian pada Prod: Canary Deployment

Secara keseluruhan, semua orang di grup akan senang, meskipun salah satu pengguna tidak senang. Jika semuanya baik-baik saja, kami mengubah semua versi.

Pengujian pada Prod: Canary Deployment

Saya akan menunjukkan kepada Anda secara skematis seperti apa layanan mikro dalam banyak kasus.

Ada Service Discovery dan dua layanan lagi: S1N1 dan S2. Layanan pertama (S1N1) memberi tahu Service Discovery saat dimulai, dan Service Discovery mengingatnya. Layanan kedua dengan dua node (S2N1 dan S2N2) juga memberi tahu Service Discovery saat startup.

Pengujian pada Prod: Canary Deployment

Layanan kedua bertindak sebagai server untuk layanan pertama. Yang pertama meminta Service Discovery untuk informasi tentang servernya, dan ketika menerimanya, ia mencari dan memeriksanya (“pemeriksaan kesehatan”). Ketika dia memeriksa, dia akan mengirimi mereka pesan.

Ketika seseorang ingin menerapkan versi baru dari layanan kedua, mereka memberi tahu Service Discovery bahwa node kedua akan menjadi node canary: lebih sedikit lalu lintas yang akan dikirim ke sana, karena penerapan sekarang akan dilakukan. Kami menghapus node canary dari bawah penyeimbang dan layanan pertama tidak mengirimkan lalu lintas ke sana.

Pengujian pada Prod: Canary Deployment

Kami mengubah versi dan Service Discovery mengetahui bahwa node kedua sekarang adalah canary - Anda dapat mengurangi bebannya (5%). Jika semuanya baik-baik saja, kami mengubah versinya, mengembalikan muatannya dan melanjutkan.

Untuk mengimplementasikan semua ini, kita membutuhkan:

  • menyeimbangkan;
  • pemantauan, karena penting untuk mengetahui secara detail apa yang diharapkan setiap pengguna dan cara kerja layanan kami;
  • analisis versiuntuk memahami seberapa baik versi baru akan bekerja dalam produksi;
  • otomatisasi — tulis urutan penerapan (pipa penerapan).

Pengujian pada Prod: Canary Deployment

Menyeimbangkan

Ini adalah hal pertama yang harus kita pikirkan. Ada dua strategi penyeimbangan.

Pilihan paling sederhana adalah kapan satu node selalu kenari. Node ini selalu menerima lebih sedikit lalu lintas dan kami memulai penerapan dari sana. Jika ada masalah, kami akan membandingkan pekerjaannya sebelum dan selama penerapan. Misal errornya 2 kali lebih banyak, maka damagenya menjadi dua kali lipat.

Node Canary ditentukan selama proses penerapan. Ketika penerapan selesai dan kami menghapus status node canary darinya, keseimbangan lalu lintas akan dipulihkan. Dengan lebih sedikit mobil kita akan mendapatkan distribusi yang adil.

Pemantauan

Landasan pelepasan burung kenari. Kami harus memahami dengan tepat alasan kami melakukan hal ini dan metrik apa yang ingin kami kumpulkan.

Contoh metrik yang kami kumpulkan dari layanan kami.

  • Jumlah kesalahan, yang ditulis ke log. Ini adalah indikator yang jelas bahwa semuanya berjalan sebagaimana mestinya. Secara keseluruhan ini adalah metrik yang bagus.
  • Waktu eksekusi kueri (latensi). Semua orang memantau metrik ini karena semua orang ingin bekerja dengan cepat.
  • Ukuran antrian (throughput).
  • Jumlah respons yang berhasil per detik.
  • Waktu penyelesaian untuk 95% dari semua permintaan.
  • Metrik bisnis: berapa banyak uang yang dihasilkan suatu bisnis dalam waktu atau churn pengguna tertentu. Metrik untuk versi baru kami ini mungkin lebih penting dibandingkan metrik yang ditambahkan oleh teknisi.

Contoh metrik dalam sistem pemantauan paling populer.

Melawan. Ini adalah beberapa peningkatan nilai, misalnya jumlah kesalahan. Sangat mudah untuk menginterpolasi metrik ini dan mempelajari grafiknya: kemarin ada 2 kesalahan, dan hari ini ada 500, yang berarti ada yang tidak beres.

Jumlah kesalahan per menit atau per detik merupakan indikator terpenting yang dapat dihitung menggunakan Counter. Data ini memberikan gambaran yang jelas tentang kinerja sistem dari jarak jauh. Mari kita lihat contoh grafik jumlah error per detik untuk dua versi sistem produksi.

Pengujian pada Prod: Canary Deployment

Versi pertama memiliki sedikit kesalahan, mungkin audit tidak berhasil. Di versi kedua, semuanya jauh lebih buruk. Kami dapat mengatakan dengan pasti bahwa ada masalah, jadi kami harus mengembalikan versi ini.

Mengukur. Metrik mirip dengan Penghitung, tetapi kami mencatat nilai yang dapat bertambah atau berkurang. Misalnya, waktu eksekusi permintaan atau ukuran antrian.

Grafik menunjukkan contoh waktu respons (latensi). Grafik menunjukkan bahwa versinya serupa dan Anda dapat menggunakannya. Namun jika Anda perhatikan lebih dekat, Anda akan melihat bagaimana nilainya berubah. Jika waktu eksekusi kueri bertambah saat menambahkan pengguna, maka langsung terlihat jelas bahwa ada masalah - ini belum pernah terjadi sebelumnya.

Pengujian pada Prod: Canary Deployment

Ringkasan. Salah satu indikator terpenting untuk bisnis adalah persentil. Metrik menunjukkan hal itu 95% kasus sistem kami bekerja sesuai keinginan kami. Kami bisa menerima jika ada masalah di suatu tempat, karena kami memahami tren umum, baik atau buruknya segala sesuatunya.

Alat

Tumpukan ELK. Anda dapat mengimplementasikan canary menggunakan Elasticsearch - kami mencatat kesalahan di dalamnya saat peristiwa terjadi. Dengan panggilan API sederhana Anda bisa mendapatkan jumlah kesalahan kapan saja dan membandingkannya dengan periode sebelumnya: GET /applg/_cunt?q=level:errr.

Prometheus. Dia menunjukkan dirinya dengan baik di Infobip. Hal ini memungkinkan penerapan metrik multidimensi karena label digunakan.

Kita bisa gunakan level, instance, service, gabungkan mereka dalam satu sistem. Dengan bantuan offset Anda dapat melihat, misalnya, nilai suatu nilai seminggu yang lalu hanya dengan satu perintah GET /api/v1/query?query={query}Dimana {query}:

rate(logback_appender_total{ 
    level="error",  
    instance=~"$instance" 
}[5m] offset $offset_value)

Analisis Versi

Ada beberapa strategi untuk analisis versi.

Lihat metrik hanya untuk node canary. Salah satu opsi paling sederhana: terapkan versi baru dan pelajari pekerjaannya saja. Tetapi jika seorang insinyur saat ini mulai mempelajari log, terus-menerus memuat ulang halaman dengan gugup, maka solusi ini tidak berbeda dengan solusi lainnya.

Node Canary dibandingkan dengan node lainnya. Ini adalah perbandingan dengan contoh lain yang berjalan pada lalu lintas penuh. Misalnya, jika lalu lintas kecil keadaannya lebih buruk atau tidak lebih baik dari keadaan sebenarnya, maka ada sesuatu yang salah.

Node Canary dibandingkan dengan dirinya sendiri di masa lalu. Node yang dialokasikan ke canary dapat dibandingkan dengan data historis. Misalnya, jika seminggu yang lalu semuanya baik-baik saja, maka kita dapat mengandalkan data ini untuk memahami situasi saat ini.

Otomasi

Kami ingin membebaskan para insinyur dari perbandingan manual, jadi penting untuk menerapkan otomatisasi. Alur penerapan biasanya terlihat seperti ini:

  • mari kita mulai;
  • hapus simpul dari bawah penyeimbang;
  • instal simpul kenari;
  • kami mengaktifkan penyeimbang dengan jumlah lalu lintas terbatas;
  • membandingkan.

Pengujian pada Prod: Canary Deployment

Pada tahap ini kami menerapkan perbandingan otomatis. Mari kita lihat tampilannya dan mengapa ini lebih baik daripada memeriksa setelah penerapan menggunakan contoh dari Jenkins.

Ini adalah saluran pipa ke Groovy.

while (System.currentTimeMillis() < endCanaryTs) {
    def isOk = compare(srv, canary, time, base, offset, metrics)
    if (isOk) {
        sleep DEFAULT SLEEP
    }   else {
        echo "Canary failed, need to revert"  
        return false
    }
}

Di sini, di loop kami menetapkan bahwa kami akan membandingkan node baru dalam waktu satu jam. Jika proses canary belum menyelesaikan prosesnya, panggil fungsinya. Dia melaporkan apakah semuanya baik-baik saja atau tidak: def isOk = compare(srv, canary, time, base, offset, metrics).

Jika semuanya baik-baik saja - sleep DEFAULT SLEEP, misalnya, sebentar, dan lanjutkan. Jika tidak, keluar - penerapan gagal.

Deskripsi metrik. Mari kita lihat seperti apa fungsinya compare menggunakan DSL sebagai contoh.

metric(
    'errorCounts',
    'rate(errorCounts{node=~"$canaryInst"}[5m] offset $offset)',
    {   baseValue, canaryValue ->
        if (canaryValue > baseValue * 1.3) return false 
        return true
    }
)

Katakanlah kita membandingkan jumlah kesalahan dan ingin mengetahui jumlah kesalahan per detik selama 5 menit terakhir.

Kami memiliki dua nilai: node dasar dan node canary. Nilai dari node canary adalah nilai saat ini. Dasar - baseValue — ini adalah nilai dari node non-canary lainnya. Kami membandingkan nilai satu sama lain menggunakan rumus yang kami tetapkan berdasarkan pengalaman dan pengamatan kami. Jika nilainya canaryValue buruk, maka penerapannya gagal, dan kami melakukan roll back.

Mengapa semua ini diperlukan?

Seseorang tidak dapat memeriksa ratusan dan ribuan metrik, terutama untuk melakukannya dengan cepat. Perbandingan otomatis membantu memeriksa semua metrik dan dengan cepat memberi tahu Anda jika ada masalah. Waktu peringatan sangat penting: jika sesuatu terjadi dalam 2 detik terakhir, kerusakannya tidak akan sebesar jika terjadi 15 menit yang lalu. Sampai seseorang mengetahui adanya masalah, menulis surat ke dukungan, dan mengirimkan dukungan kepada kami untuk mengembalikannya, Anda bisa kehilangan pelanggan.

Jika proses selesai dan semuanya baik-baik saja, kami menyebarkan semua node lainnya secara otomatis. Selama ini, para insinyur tidak melakukan apa pun. Hanya ketika mereka meluncurkan canary barulah mereka memutuskan metrik mana yang akan diambil, berapa lama melakukan perbandingan, dan strategi apa yang akan digunakan.

Pengujian pada Prod: Canary Deployment

Jika masalah muncul, kami secara otomatis memutar kembali node canary, mengerjakan versi sebelumnya dan memperbaiki kesalahan yang kami temukan. Dengan menggunakan metrik, mudah untuk menemukannya dan melihat kerusakan dari versi baru.

Hambatan

Tentu saja hal ini tidak mudah untuk diterapkan. Pertama-tama, Anda perlu sistem pemantauan umum. Insinyur memiliki metriknya sendiri, dukungan dan analis memiliki metrik lainnya, dan bisnis memiliki metrik lainnya. Sistem umum adalah bahasa umum yang digunakan oleh bisnis dan pembangunan.

Perlu diuji dalam praktek stabilitas metrik. Pengujian membantu Anda memahami berapa set metrik minimum yang diperlukan untuk memastikan kualitas?.

Bagaimana ini bisa dicapai? Gunakan layanan canary bukan pada saat penerapan. Kami menambahkan layanan tertentu pada versi lama yang kapan saja dapat menggunakan node khusus mana pun dan mengurangi lalu lintas tanpa penerapan. Lalu kita bandingkan: kita mempelajari kesalahan dan mencari garis itu ketika kita mencapai kualitas.

Pengujian pada Prod: Canary Deployment

Bagaimana kami mendapat manfaat dari pelepasan burung kenari

Meminimalkan persentase kerusakan akibat bug. Sebagian besar kesalahan penerapan terjadi karena ketidakkonsistenan beberapa data atau prioritas. Kesalahan seperti itu jauh lebih sedikit karena kami dapat menyelesaikan masalah dalam beberapa detik pertama.

Mengoptimalkan kerja tim. Pemula memiliki “hak untuk membuat kesalahan”: mereka dapat diterapkan ke produksi tanpa takut membuat kesalahan, mereka memiliki inisiatif tambahan dan insentif untuk bekerja. Jika mereka merusak sesuatu, itu tidak kritis, dan orang yang melakukan kesalahan tidak akan dipecat.

Penerapan otomatis. Ini bukan lagi proses manual seperti sebelumnya, melainkan proses yang benar-benar otomatis. Tapi itu membutuhkan waktu lebih lama.

Menyoroti metrik penting. Seluruh perusahaan, mulai dari bisnis dan insinyur, memahami apa yang sebenarnya penting dalam produk kami, metrik apa, misalnya arus keluar dan masuk pengguna. Kami mengontrol prosesnya: kami menguji metrik, memperkenalkan metrik baru, melihat cara kerja metrik lama, untuk membangun sistem yang akan menghasilkan uang dengan lebih efisien.

Kami memiliki banyak praktik dan sistem keren yang membantu kami. Meskipun demikian, kami berusaha untuk bersikap profesional dan melakukan pekerjaan kami dengan baik, terlepas dari apakah kami memiliki sistem untuk membantu kami atau tidak.

Pendekatan dan praktik teknik - fokus utama TechLead Conf. Jika Anda telah mencapai kesuksesan dalam perjalanan menuju keunggulan teknis dan siap memberi tahu Anda apa yang membantu Anda dalam hal ini, - mengajukan permohonan laporan.

Kami berencana untuk mengadakannya Konferensi TechLead 8 Juni. Kami memahami bahwa saat ini sulit untuk mengambil keputusan mengenai partisipasi dalam konferensi. Namun pada saat yang sama, kami percaya bahwa karantina bukanlah alasan untuk menghentikan komunikasi dan pengembangan profesional. Oleh karena itu, bagaimanapun juga, kami akan menemukan cara untuk mendiskusikan tugas pimpinan teknis dan pendekatan untuk menyelesaikannya - jika perlu, kami akan online dan mulai berjejaring di sana!

Sumber: www.habr.com

Beli hosting yang andal untuk situs dengan perlindungan DDoS, server VPS VDS 🔥 Beli hosting website andal dengan perlindungan DDoS, server VPS VDS | ProHoster