DEVOXX UK. Kubernetes dalam produksi: penyebaran Biru / Hijau, autoscaling, dan otomatisasi penyebaran. Bagian 2

Kubernetes adalah alat yang hebat untuk menjalankan kontainer Docker di lingkungan produksi yang terkelompok. Namun, ada tugas-tugas yang tidak dapat diselesaikan Kubernetes. Dengan penyebaran yang sering di lingkungan produksi, kami membutuhkan penyebaran Biru / Hijau yang sepenuhnya otomatis untuk menghindari downtime dalam proses ini, yang juga memerlukan permintaan HTTP eksternal dan pengunggahan SSL. Ini membutuhkan integrasi dengan load balancer seperti ha-proxy. Tugas lain adalah penskalaan semi-otomatis dari kluster Kubernetes itu sendiri ketika bekerja di cloud, misalnya, pengurangan sebagian dari skala kluster di malam hari.

Meskipun Kubernetes tidak memiliki fitur-fitur ini di luar kotak, ia menyediakan API yang dapat digunakan untuk memecahkan masalah tersebut. Alat bantu skala dan penyebaran skala biru / hijau Kubernetes dikembangkan sebagai bagian dari proyek open source Cloud RTI.

Transkrip video ini menjelaskan cara mengkonfigurasi Kubernetes bersama dengan komponen open source lainnya untuk mendapatkan lingkungan siap-produksi yang menerima kode dari git commit perubahan komit tanpa downtime dalam produksi.



DEVOXX UK. Kubernetes dalam produksi: penyebaran Biru / Hijau, autoscaling, dan otomatisasi penyebaran. Bagian 1

Jadi, setelah Anda mengakses aplikasi Anda dari dunia luar, Anda dapat mulai mengkonfigurasi otomatisasi sepenuhnya, yaitu, membawanya ke tahap di mana Anda dapat mengeksekusi git commit dan memastikan bahwa git commit ini berakhir dalam produksi. Secara alami, dalam implementasi langkah-langkah ini, dalam implementasi penyebaran, kami tidak ingin menghadapi downtime. Jadi, otomatisasi apa pun di Kubernetes dimulai dengan API.



Kubernetes bukanlah alat yang dapat digunakan "di luar kotak" secara produktif. Tentu saja, Anda bisa melakukan ini, gunakan kubectl dan sebagainya, tetapi tetap saja API adalah hal yang paling menarik dan berguna tentang platform ini. Menggunakan API sebagai kumpulan fitur, Anda dapat mengakses hampir semua yang ingin Anda lakukan di Kubernetes. Kubectl sendiri juga menggunakan REST API.

Ini REST, jadi Anda bisa menggunakan bahasa dan alat apa saja untuk bekerja dengan API ini, tetapi perpustakaan pengguna akan sangat memudahkan hidup Anda. Tim saya menulis 2 perpustakaan seperti itu: satu untuk Java / OSGi dan satu untuk Go. Yang kedua tidak sering digunakan, tetapi dalam hal apa pun, hal-hal berguna ini siap membantu Anda. Mereka adalah proyek sumber terbuka berlisensi sebagian. Ada banyak perpustakaan untuk berbagai bahasa, sehingga Anda dapat memilih yang paling cocok.



Jadi, sebelum memulai otomasi penyebaran, Anda perlu memastikan bahwa proses ini tidak mengalami downtime. Misalnya, tim kami melakukan penyebaran produksi di tengah hari, ketika orang membuat sebagian besar aplikasi mereka, jadi sangat penting untuk menghindari keterlambatan dalam proses ini. Untuk menghindari downtime, 2 metode digunakan: penyebaran biru / hijau atau pembaruan pembaruan bergulir bergulir. Dalam kasus terakhir, jika Anda memiliki 5 replika aplikasi yang sedang berjalan, mereka diperbarui secara berurutan. Metode ini berfungsi dengan baik, tetapi tidak berfungsi jika Anda menjalankan versi aplikasi yang berbeda pada saat yang sama selama proses penyebaran. Dalam hal ini, Anda dapat memperbarui antarmuka pengguna sementara backend akan bekerja dengan versi lama dan aplikasi akan berhenti berfungsi.Oleh karena itu, dari sudut pandang pemrograman, bekerja dalam kondisi seperti itu agak sulit.

Ini adalah salah satu alasan kami memilih untuk menggunakan penyebaran biru / hijau untuk mengotomatiskan penerapan aplikasi kami. Dengan metode ini, Anda harus memastikan bahwa pada titik waktu tertentu hanya satu versi aplikasi yang aktif.

Mekanisme penyebaran biru / hijau adalah sebagai berikut. Kami mendapatkan lalu lintas untuk aplikasi kami melalui ha-proxy, yang mengarahkannya untuk menjalankan replika aplikasi dari versi yang sama.

Ketika penyebaran baru dilakukan, kami menggunakan Deployer, yang dilengkapi dengan komponen baru, dan menyebarkan versi baru. Menyebarkan versi baru aplikasi berarti bahwa set replika baru "naik", setelah itu replika versi baru ini diluncurkan di pod baru yang terpisah. Namun, ha-proxy tidak tahu apa-apa tentang mereka dan sejauh ini belum mengirimkan mereka beban kerja.

Karena itu, pertama-tama, perlu memeriksa kesehatan versi baru cheking kesehatan untuk memastikan bahwa replika siap untuk melayani beban.



Semua komponen penyebaran harus mendukung beberapa bentuk chek kesehatan. Ini bisa berupa cek HTTP yang sangat sederhana dengan panggilan ketika Anda menerima kode dengan status 200, atau cek lebih dalam di mana Anda memeriksa koneksi replika dengan database dan layanan lain, stabilitas koneksi lingkungan dinamis, apakah semuanya dimulai dan bekerja dengan benar. Proses ini bisa sangat rumit.



Setelah sistem memverifikasi bahwa semua replika yang diperbarui operasional, Deployer akan memperbarui konfigurasi dan memberikan confd yang benar, yang akan mengkonfigurasi ulang ha-proxy.



Hanya setelah itu lalu lintas akan diarahkan ke bawah dengan replika versi baru, dan yang lama akan hilang.



Mekanisme ini bukan fitur Kubernetes. Konsep penyebaran Biru / hijau telah ada selama beberapa waktu, dan selalu menggunakan penyeimbang beban. Pertama, Anda mengarahkan semua lalu lintas ke versi lama aplikasi, dan setelah upgrade, transfer sepenuhnya ke versi baru. Prinsip ini digunakan tidak hanya di Kubernetes.

Sekarang saya akan memperkenalkan Anda ke komponen penyebaran baru - Deployer, yang melakukan pemeriksaan kesehatan, mengkonfigurasi ulang proxy, dan sebagainya. Ini adalah konsep yang tidak berlaku untuk dunia luar dan ada di dalam Kubernetes. Saya akan menunjukkan bagaimana Anda dapat membuat konsep Deployer Anda sendiri menggunakan alat open-source.

Jadi, hal pertama yang dilakukan Deployer adalah membuat pengendali replikasi RC menggunakan API Kubernetes. API ini membuat pod dan layanan untuk penyebaran lebih lanjut, yaitu, ia menciptakan cluster yang sama sekali baru untuk aplikasi kami. Setelah RC memverifikasi bahwa replika sudah dimulai, itu akan memeriksa pemeriksaan kesehatan mereka. Untuk melakukan ini, Deployer menggunakan perintah GET / kesehatan. Ini meluncurkan komponen verifikasi yang sesuai dan memverifikasi semua elemen yang memastikan operasi cluster.



Setelah semua pod melaporkan "kesehatan" mereka, Deployer membuat item konfigurasi baru - etcd penyimpanan terdistribusi, yang digunakan di dalam Kubernetes, termasuk untuk menyimpan konfigurasi penyeimbang beban. Kami menulis data ke etcd, dan alat kecil, confd, memonitor etcd untuk data baru.

Jika dia menemukan perubahan pada konfigurasi awal, dia membuat file pengaturan baru dan meneruskannya ke ha-proxy. Dalam hal ini, ha-proxy reboot tanpa kehilangan koneksi dan mengatasi beban dengan layanan baru yang menyediakan versi baru dari aplikasi kami.



Seperti yang Anda lihat, meskipun banyak komponen, tidak ada yang rumit. Anda hanya perlu lebih memperhatikan API dan sebagainya. Saya ingin memberi tahu Anda tentang penggunaan sumber terbuka yang kami gunakan sendiri - ini adalah Amdatu Kubernetes Deployer.



Ini adalah alat orkestra penyebaran Kubernetes dengan fitur berikut:

  • Penyebaran Biru / Hijau
  • menyiapkan penyeimbang muatan eksternal;
  • Manajemen deskriptor penyebaran
  • Manajemen penyebaran aktual
  • Pemeriksaan kesehatan selama penyebaran
  • implementasi variabel lingkungan di pod.

Dibuat di atas API Kubernetes, Deployer ini menyediakan REST API untuk mengelola deskriptor dan penyebaran, serta API Websocket untuk stream log selama penyebaran.

Ini menempatkan data konfigurasi penyeimbang beban di etcd, sehingga Anda tidak dapat menggunakan ha-proxy dengan dukungan "di luar kotak", tetapi mudah untuk menggunakan file konfigurasi penyeimbang Anda sendiri. Amdatu Deployer ditulis dalam Go, seperti halnya Kubernetes sendiri, dan dilisensikan oleh Apache.

Sebelum menggunakan versi ini, saya menggunakan deskriptor penyebaran berikut, yang menentukan parameter yang saya butuhkan.



Salah satu parameter penting dari kode ini adalah untuk mengaktifkan bendera "useHealthCheck". Kami perlu menunjukkan bahwa pemeriksaan kesehatan diperlukan selama proses penyebaran. Opsi ini dapat dimatikan saat penyebaran menggunakan wadah pihak ketiga yang tidak perlu diverifikasi. Deskriptor ini juga menunjukkan jumlah replika dan URL frontend yang dibutuhkan oleh proxy. Pada akhirnya adalah bendera spesifikasi untuk podspec pod, yang memanggil Kubernetes untuk informasi tentang konfigurasi port, gambar, dll. Ini adalah deskriptor yang cukup sederhana dalam format JSON.

Alat lain yang merupakan bagian dari proyek open-source Amdatu adalah Deploymentctl. Ini memiliki antarmuka antarmuka pengguna untuk mengonfigurasi penyebaran, menyimpan riwayat penggunaan, dan berisi pengait web untuk panggilan balik oleh pengguna dan pengembang pihak ketiga. Anda tidak dapat menggunakan UI, karena Amdatu Deployer sendiri adalah REST API, tetapi antarmuka ini dapat mempermudah Anda untuk menggunakan tanpa melibatkan API apa pun. Deploymentctl ditulis dalam OSGi / Vertx menggunakan Angular 2.

Sekarang saya akan menunjukkan di atas pada layar menggunakan rekaman yang sudah dibuat sebelumnya, jadi Anda tidak perlu menunggu. Kami akan menyebarkan aplikasi sederhana di Go. Jangan khawatir, jika Anda belum pernah menemui Go sebelumnya, ini adalah aplikasi yang sangat sederhana, jadi Anda harus memahami segalanya.



Di sini kita membuat server HTTP yang hanya menanggapi / kesehatan, jadi aplikasi ini hanya memeriksa pemeriksaan kesehatan dan tidak ada yang lain. Jika pemeriksaan lolos, struktur JSON yang ditunjukkan di bawah ini dipanggil. Ini berisi versi aplikasi yang akan digunakan oleh deployer, pesan yang Anda lihat di bagian atas file, dan tipe data logis boolean - apakah aplikasi kita berfungsi atau tidak.

Saya sedikit curang dengan baris terakhir, karena saya menempatkan nilai boolean tetap di bagian atas file, yang di masa depan akan membantu saya menggunakan aplikasi yang “tidak sehat”. Kami akan mengatasinya nanti.

Jadi mari kita mulai. Pertama, kami memeriksa semua pod yang sedang berjalan menggunakan perintah ~ kubectl get pods, dan jika tidak ada respons dari URL frontend, kami memastikan bahwa saat ini tidak ada penyebaran yang dilakukan.



Selanjutnya, pada layar, Anda melihat antarmuka Deploymentctl yang saya sebutkan, di mana parameter penyebaran ditetapkan: namespace, nama aplikasi, versi penyebaran, jumlah replika, URL frontend, nama wadah, gambar, batas sumber daya, nomor port untuk memeriksa pemeriksaan kesehatan, dll. . Batas sumber daya sangat penting, karena memungkinkan Anda untuk menggunakan jumlah "besi" maksimum. Anda juga dapat melihat Deployment log deployment di sini.



Jika Anda mengulangi perintah ~ kubectl get pods sekarang, Anda dapat melihat bahwa sistem “macet” selama 20 detik, di mana terjadi konfigurasi ulang proxy-ha. Setelah itu, mulai di bawah, dan replika kami dapat dilihat di log penempatan.



Saya memotong 20 detik menunggu dari video, dan sekarang Anda melihat di layar bahwa versi pertama dari aplikasi ini digunakan. Semua ini dilakukan hanya dengan bantuan UI.



Sekarang mari kita coba versi kedua. Untuk melakukan ini, saya mengubah pesan aplikasi dengan "Halo, Kubernetes!" untuk "Hello, Deployer!", sistem membuat gambar ini dan menempatkannya di registri Docker, setelah itu kita cukup klik tombol "Deploy" di jendela Deploymentctl lagi. Dalam kasus ini, log penempatan diluncurkan secara otomatis dengan cara yang sama seperti ketika versi pertama aplikasi dikerahkan.



Perintah ~ kubectl get pods menunjukkan bahwa 2 versi aplikasi sedang berjalan, tetapi front-end menunjukkan bahwa kita masih menjalankan versi 1.



Load balancer menunggu hingga pemeriksaan kesehatan dilakukan, dan kemudian mengalihkan lalu lintas ke versi yang baru. Setelah 20 detik, kami beralih ke curl dan melihat bahwa sekarang kami telah menggunakan versi 2 aplikasi, dan yang pertama dihapus.



Itu adalah penyebaran aplikasi "sehat" - sehat -. Mari kita lihat apa yang terjadi jika untuk versi baru aplikasi saya mengubah nilai parameter Healthy dari true ke false, yaitu, saya akan mencoba untuk menggunakan aplikasi yang tidak sehat yang belum lulus pemeriksaan kesehatan. Ini dapat terjadi jika pada tahap pengembangan beberapa kesalahan konfigurasi dibuat dalam aplikasi, dan itu mulai diproduksi dalam bentuk ini.

Seperti yang Anda lihat, penyebaran melewati semua langkah di atas, dan ~ kubectl mendapatkan pod menunjukkan bahwa kedua pod sedang berjalan. Tetapi tidak seperti penyebaran sebelumnya, log menunjukkan keadaan batas waktu. Artinya, karena pemeriksaan kesehatan tidak lulus, versi baru aplikasi tidak dapat digunakan. Akibatnya, Anda melihat bahwa sistem kembali menggunakan versi aplikasi yang lama, dan versi yang baru dihapus.



Hal yang baik tentang ini adalah bahwa bahkan jika Anda memiliki sejumlah besar permintaan simultan yang datang ke aplikasi, mereka bahkan tidak akan melihat downtime selama implementasi prosedur penempatan. Jika Anda menguji aplikasi ini menggunakan kerangka kerja Gatling, yang mengirimkannya sebanyak mungkin jumlah permintaan, maka tidak satu pun dari permintaan ini akan dibatalkan. Ini berarti bahwa pengguna kami bahkan tidak akan melihat pembaruan versi waktu-nyata. Jika gagal, pekerjaan akan dilanjutkan pada versi lama, jika berhasil, pengguna akan beralih ke versi baru.

Hanya ada satu hal yang dapat menyebabkan kegagalan - jika pemeriksaan kesehatan berhasil, dan aplikasi macet segera setelah menerima beban kerja, yaitu, kehancuran akan terjadi hanya setelah penyebaran selesai. Dalam hal ini, Anda harus memutar kembali ke versi lama secara manual. Jadi, kami melihat cara menggunakan Kubernetes dengan alat open-source-nya. Proses penyebaran akan jauh lebih sederhana jika Anda menyematkan alat ini di pipa saluran Build / Deploy kreasi / penyebaran. Pada saat yang sama, untuk memulai penyebaran, Anda dapat menggunakan antarmuka pengguna dan mengotomatisasi proses ini sepenuhnya, menerapkan, misalnya, komit untuk dikuasai.



Build server build Server kami akan membuat gambar Docker, menempelkannya ke Docker Hub, atau registri lain yang Anda gunakan. Hub Docker mendukung webhook, sehingga kami dapat memulai penyebaran jarak jauh melalui Deployer seperti yang ditunjukkan di atas. Dengan demikian, Anda dapat sepenuhnya mengotomatiskan penyebaran aplikasi dalam potensi produksi.

Mari kita beralih ke topik berikutnya - menskalakan klaster Kubernetes. Saya perhatikan bahwa perintah kubectl adalah perintah penskalaan. Dengan bantuan orang lain, Anda dapat dengan mudah meningkatkan jumlah replika di kluster kami. Namun, dalam praktiknya, kami biasanya ingin menambah jumlah node, bukan node.



Pada saat yang sama, selama jam kerja, Anda mungkin perlu meningkatkan, dan pada malam hari, untuk mengurangi biaya layanan Amazon, mengurangi jumlah instance aplikasi yang berjalan. Ini tidak berarti bahwa hanya jumlah pod yang cukup, karena meskipun salah satu node tidak sibuk, Anda masih harus membayar Amazon untuk itu. Artinya, bersama dengan menskalakan tungku, Anda perlu mengukur jumlah mesin yang digunakan.

Ini bisa rumit karena terlepas dari apakah kita menggunakan Amazon atau layanan cloud lain, Kubernetes tidak tahu apa-apa tentang jumlah mesin yang digunakan. Tidak memiliki alat yang memungkinkan Anda untuk skala sistem di tingkat node.



Jadi kita harus merawat simpul dan polong. Kita dapat dengan mudah mengatur peluncuran node baru menggunakan AWS API dan mesin Scaling group untuk mengkonfigurasi jumlah node kerja Kubernetes. Anda juga dapat menggunakan cloud-init atau skrip serupa untuk mendaftarkan node di cluster Kubernetes.

Mesin baru dimulai dalam grup Scaling, memulai dirinya sebagai sebuah simpul, mendaftar di registri wizard dan mulai bekerja. Setelah itu, Anda dapat menambah jumlah replika untuk digunakan pada simpul yang dihasilkan. Mengurangi skala membutuhkan lebih banyak usaha, karena Anda perlu memastikan bahwa langkah seperti itu tidak akan mengarah pada penghancuran aplikasi yang sudah berjalan setelah mematikan mesin "yang tidak perlu". Untuk mencegah skenario ini, Anda perlu membawa node ke status "tidak terjadwal". Ini berarti bahwa penjadwal default saat menjadwalkan pod DaemonSet akan mengabaikan node ini. Penjadwal tidak akan menghapus apa pun dari server ini, tetapi juga tidak akan meluncurkan wadah baru di sana. Langkah selanjutnya adalah memindahkan node drain, yaitu, untuk memindahkan perapian kerja dari itu ke mesin lain, atau node lain yang memiliki kapasitas yang cukup untuk ini.Setelah memverifikasi bahwa tidak ada lagi wadah pada node ini, Anda dapat menghapusnya dari Kubernetes. Setelah itu, untuk Kubernetes, mereka tidak ada lagi. Selanjutnya, Anda perlu menggunakan AWS API untuk menonaktifkan node, atau mesin yang tidak perlu.
Anda dapat menggunakan Amdatu Scalerd, alat penskalaan open-source lain yang mirip dengan AWS API. Ini memberikan CLI untuk menambah atau menghapus node dalam sebuah cluster. Fitur yang menarik adalah kemampuan untuk mengkonfigurasi scheduler menggunakan file json berikut.



Kode yang ditampilkan membagi dua kapasitas cluster di malam hari. Ini dikonfigurasi sebagai jumlah replika yang tersedia, dan kapasitas yang diinginkan dari kluster Amazon. Menggunakan penjadwal ini akan secara otomatis mengurangi jumlah node di malam hari dan meningkatkannya di pagi hari, sehingga menghemat biaya menggunakan node dari layanan cloud seperti Amazon. Fitur ini tidak dibangun di Kubernetes, tetapi menggunakan Scalerd akan memungkinkan Anda untuk skala platform ini yang Anda inginkan.

Saya ingin menarik perhatian Anda pada fakta bahwa banyak orang mengatakan kepada saya: "Semua ini baik, tetapi bagaimana dengan basis data saya, yang biasanya dalam keadaan statis?" Bagaimana saya bisa menjalankan sesuatu seperti ini di lingkungan yang dinamis seperti Kubernetes? Menurut pendapat saya, Anda tidak harus melakukan ini, jangan mencoba untuk mengatur operasi data warehouse di Kubernetes. Secara teknis, ini mungkin, dan ada manual di Internet tentang hal ini, tetapi ini akan sangat menyulitkan hidup Anda.

Ya, konsep penyimpanan persisten ada di Kubernetes, dan Anda dapat mencoba menjalankan gudang data seperti Mongo atau MySQL, tetapi ini adalah tugas yang agak memakan waktu. Ini disebabkan oleh fakta bahwa gudang data tidak sepenuhnya mendukung interaksi dengan lingkungan yang dinamis. Sebagian besar database memerlukan penyetelan yang signifikan, termasuk mengkonfigurasi kluster secara manual, tidak suka autoscaling dan hal-hal serupa lainnya.
Karena itu, jangan menyulitkan hidup Anda ketika mencoba memulai data warehouse di Kubernetes. Atur pekerjaan mereka dengan cara tradisional menggunakan layanan yang sudah dikenal dan berikan Kubernetes kesempatan untuk menggunakannya.



Di akhir topik saya ingin memperkenalkan Anda ke platform Cloud RTI berdasarkan Kubernetes, yang sedang dikerjakan oleh tim saya. Ini menyediakan logging terpusat, aplikasi pemantauan dan cluster, dan memiliki banyak fitur berguna lainnya yang berguna bagi Anda. Ini menggunakan berbagai alat sumber terbuka seperti Grafana untuk menampilkan pemantauan.





Pertanyaan muncul, mengapa menggunakan load balancer ha-proxy dengan Kubernetes. Pertanyaan bagus, karena saat ini ada 2 level load balancing. Layanan Kubernetes masih berada di alamat IP virtual. Anda tidak dapat menggunakannya untuk port host eksternal, karena jika Amazon mem-boot ulang host cloud-nya, alamatnya akan berubah. Itu sebabnya kami menempatkan layanan proxy-ha di depan layanan - untuk membuat struktur yang lebih statis untuk interaksi lalu lintas tanpa batas dengan Kubernetes.

Pertanyaan bagus lainnya adalah bagaimana saya bisa mengurus perubahan skema database selama penyebaran biru / hijau? Faktanya adalah bahwa terlepas dari penggunaan Kubernetes, mengubah skema basis data adalah tugas yang kompleks. Anda perlu memastikan kompatibilitas skema lama dan baru, setelah itu Anda dapat memperbarui database dan kemudian memperbarui aplikasi sendiri. Anda dapat melakukan hot swap database dan kemudian meningkatkan aplikasi. Saya tahu orang-orang yang mengunduh cluster database yang sama sekali baru dengan skema baru, ini merupakan opsi jika Anda memiliki database schemeless seperti Mongo, tetapi bagaimanapun ini bukanlah tugas yang mudah. Jika tidak ada pertanyaan lagi, terima kasih atas perhatian Anda!


Sedikit iklan :)


Terima kasih untuk tetap bersama kami. Apakah Anda suka artikel kami? Ingin melihat materi yang lebih menarik? Dukung kami dengan melakukan pemesanan atau merekomendasikan kepada teman Anda VPS berbasis cloud untuk pengembang mulai $ 4,99 , analog unik dari server entry-level yang diciptakan oleh kami untuk Anda: Seluruh kebenaran tentang VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps mulai dari $ 19 atau cara membagi server? (opsi tersedia dengan RAID1 dan RAID10, hingga 24 core dan hingga 40GB DDR4).

Dell R730xd 2 kali lebih murah di pusat data Equinix Tier IV di Amsterdam? Hanya kami yang memiliki 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV dari $ 199 di Belanda!Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - mulai dari $ 99! Baca tentang Cara Membangun Infrastruktur Bldg. kelas c menggunakan server Dell R730xd E5-2650 v4 seharga 9.000 euro untuk satu sen?

All Articles