Penerapan Laravel Zero Downtime

Diterbitkan: 2022-03-11

Ketika datang untuk memperbarui aplikasi langsung, ada dua cara yang berbeda secara mendasar untuk melakukannya.

Pada pendekatan pertama, kami membuat perubahan bertahap pada status sistem kami. Misalnya, kami memperbarui file, memodifikasi properti lingkungan, menginstal kebutuhan tambahan, dan sebagainya. Pada pendekatan kedua, kami merobohkan seluruh mesin dan membangun kembali sistem dengan gambar baru dan konfigurasi deklaratif (misalnya, menggunakan Kubernetes).

Penerapan Laravel Menjadi Mudah

Artikel ini terutama membahas aplikasi yang relatif kecil, yang mungkin tidak dihosting di cloud, meskipun saya akan menyebutkan bagaimana Kubernetes dapat sangat membantu kami dengan penerapan di luar skenario "tanpa cloud". Kami juga akan membahas beberapa masalah umum dan tip untuk melakukan pembaruan yang berhasil yang mungkin berlaku dalam berbagai situasi yang berbeda, tidak hanya dengan penerapan Laravel.

Untuk keperluan demonstrasi ini, saya akan menggunakan contoh Laravel, tetapi perlu diingat bahwa aplikasi PHP apa pun dapat menggunakan pendekatan serupa.

Versi

Sebagai permulaan, sangat penting bagi kita untuk mengetahui versi kode yang saat ini digunakan pada produksi. Itu dapat dimasukkan dalam beberapa file atau setidaknya atas nama folder atau file. Adapun penamaan, jika kita mengikuti praktik standar versi semantik, kita dapat memasukkan lebih banyak informasi di dalamnya daripada hanya satu nomor.

Melihat dua rilis yang berbeda, informasi tambahan ini dapat membantu kami dengan mudah memahami sifat perubahan yang diperkenalkan di antara mereka.

Gambar menunjukkan penjelasan versi semantik.

Pembuatan versi rilis dimulai dengan sistem kontrol versi, seperti Git. Katakanlah kita telah menyiapkan rilis untuk penerapan, misalnya, versi 1.0.3. Saat mengatur rilis dan aliran kode ini, ada gaya pengembangan yang berbeda seperti pengembangan berbasis trunk dan aliran Git, yang dapat Anda pilih atau campur berdasarkan preferensi tim Anda dan spesifikasi proyek Anda. Pada akhirnya, kami kemungkinan besar akan berakhir dengan rilis kami yang ditandai dengan sesuai di cabang utama kami.

Setelah komit, kita dapat membuat tag sederhana seperti ini:

git tag v1.0.3

Dan kemudian kami menyertakan tag saat menjalankan perintah push:

git push <origin> <branch> --tags

Kami juga dapat menambahkan tag ke komit lama menggunakan hash mereka.

Mendapatkan File Rilis ke Tujuan Mereka

Penerapan Laravel membutuhkan waktu, meskipun hanya menyalin file. Namun, meskipun tidak memakan waktu terlalu lama, tujuan kami adalah mencapai waktu henti nol .

Oleh karena itu, kita harus menghindari menginstal pembaruan di tempat dan tidak boleh mengubah file yang disajikan secara langsung. Sebagai gantinya, kita harus menyebarkan ke direktori lain dan beralih hanya setelah instalasi benar-benar siap.

Sebenarnya, ada berbagai alat dan layanan yang dapat membantu kita dalam penerapan, seperti Envoyer.io (oleh desainer Laravel.com Jack McDade), Capistrano, Deployer, dll. Saya belum menggunakan semuanya dalam produksi, jadi saya tidak bisa membuat rekomendasi atau menulis perbandingan yang komprehensif, tetapi izinkan saya menunjukkan ide di balik produk ini. Jika beberapa (atau semua) tidak dapat memenuhi persyaratan Anda, Anda selalu dapat membuat skrip khusus untuk mengotomatiskan proses dengan cara terbaik yang Anda inginkan.

Untuk keperluan demonstrasi ini, misalkan aplikasi Laravel kita dilayani oleh server Nginx dari jalur berikut:

/var/www/demo/public

Pertama, kita memerlukan direktori untuk menempatkan file rilis setiap kali kita melakukan penerapan. Juga, kita membutuhkan symlink yang akan menunjuk ke rilis kerja saat ini. Dalam hal ini, /var/www/demo akan berfungsi sebagai symlink kami. Menetapkan ulang penunjuk akan memungkinkan kita mengubah rilis dengan cepat.

Penanganan file Laravel Deployment

Jika kita berurusan dengan server Apache, kita mungkin perlu mengizinkan symlink berikut dalam konfigurasi:

Options +FollowSymLinks

Struktur kita bisa seperti ini:

 /opt/demo/release/v0.1.0 /opt/demo/release/v0.1.1 /opt/demo/release/v0.1.2

Mungkin ada beberapa file yang perlu kita simpan melalui penerapan yang berbeda, misalnya, file log (jika kita tidak menggunakan Logstash, tentu saja). Dalam hal penerapan Laravel, kita mungkin ingin menyimpan direktori penyimpanan dan file konfigurasi .env. Kami dapat memisahkannya dari file lain dan menggunakan symlink mereka sebagai gantinya.

Untuk mengambil file rilis kami dari repositori Git, kami dapat menggunakan perintah klon atau arsip. Beberapa orang menggunakan git clone, tetapi Anda tidak dapat mengkloning komit atau tag tertentu. Itu berarti seluruh repositori diambil dan kemudian tag tertentu dipilih. Ketika repositori berisi banyak cabang atau sejarah besar, ukurannya jauh lebih besar daripada arsip rilis. Jadi, jika Anda tidak secara khusus membutuhkan git repo pada produksi, Anda dapat menggunakan git archive . Ini memungkinkan kita untuk mengambil hanya arsip file dengan tag tertentu. Keuntungan lain menggunakan yang terakhir ini adalah kita dapat mengabaikan beberapa file dan folder yang seharusnya tidak ada di lingkungan produksi, misalnya tes. Untuk ini, kita hanya perlu mengatur properti export-ignore di .gitattributes file . Dalam Daftar Periksa Praktik Pengodean Aman OWASP, Anda dapat menemukan rekomendasi berikut: “Hapus kode uji atau fungsi apa pun yang tidak dimaksudkan untuk produksi, sebelum penerapan.”

Jika kami mengambil rilis dari sistem kontrol versi sumber, arsip git dan export-ignore dapat membantu kami dengan persyaratan ini.

Mari kita lihat skrip yang disederhanakan (ini akan membutuhkan penanganan kesalahan yang lebih baik dalam produksi):

menyebarkan.sh

 #!/bin/bash # Terminate execution if any command fails set -e # Get tag from a script argument TAG=$1 GIT_REMOTE_URL='here should be a remote url of the repo' BASE_DIR=/opt/demo # Create folder structure for releases if necessary RELEASE_DIR=$BASE_DIR/releases/$TAG mkdir -p $RELEASE_DIR mkdir -p $BASE_DIR/storage cd $RELEASE_DIR # Fetch the release files from git as a tar archive and unzip git archive \ --remote=$GIT_REMOTE_URL \ --format=tar \ $TAG \ | tar xf - # Install laravel dependencies with composer composer install -o --no-interaction --no-dev # Create symlinks to `storage` and `.env` ln -sf $BASE_DIR/.env ./ rm -rf storage && ln -sf $BASE_DIR/storage ./ # Run database migrations php artisan migrate --no-interaction --force # Run optimization commands for laravel php artisan optimize php artisan cache:clear php artisan route:cache php artisan view:clear php artisan config:cache # Remove existing directory or symlink for the release and create a new one. NGINX_DIR=/var/www/public mkdir -p $NGINX_DIR rm -f $NGINX_DIR/demo ln -sf $RELEASE_DIR $NGINX_DIR/demo

Untuk menyebarkan rilis kami, kami hanya dapat menjalankan yang berikut ini:

deploy.sh v1.0.3

Catatan: Dalam contoh ini, v1.0.3 adalah tag git dari rilis kami.

Komposer di Produksi?

Anda mungkin telah memperhatikan bahwa skrip meminta Komposer untuk menginstal dependensi. Meskipun Anda melihat ini di banyak artikel, mungkin ada beberapa masalah dengan pendekatan ini. Umumnya, ini adalah praktik terbaik untuk membuat build lengkap aplikasi dan memajukan build ini melalui berbagai lingkungan pengujian infrastruktur Anda. Pada akhirnya, Anda akan memiliki build yang diuji secara menyeluruh, yang dapat digunakan dengan aman untuk produksi. Meskipun setiap build harus dapat direproduksi dari awal, itu tidak berarti bahwa kita harus membangun kembali aplikasi pada tahapan yang berbeda. Saat kami membuat composer install pada produksi, ini bukan build yang benar-benar sama dengan build yang diuji dan inilah yang bisa salah:

  • Kesalahan jaringan dapat mengganggu dependensi pengunduhan.
  • Vendor perpustakaan mungkin tidak selalu mengikuti SemVer.

Kesalahan jaringan dapat dengan mudah diketahui. Skrip kami bahkan akan berhenti dijalankan dengan kesalahan. Tetapi perubahan besar di perpustakaan mungkin sangat sulit untuk dijabarkan tanpa menjalankan tes, yang tidak dapat Anda lakukan dalam produksi. Saat memasang dependensi, Komposer, npm, dan alat serupa lainnya mengandalkan versi semantik–major.minor.patch. Jika Anda melihat ~1.0.2 di composer.json, itu berarti menginstal versi 1.0.2 atau versi patch terbaru, seperti 1.0.4. Jika Anda melihat ^1.0.2, itu berarti menginstal versi 1.0.2 atau versi minor atau patch terbaru, seperti 1.1.0. Kami memercayai vendor perpustakaan untuk menabrak nomor utama ketika ada perubahan yang melanggar diperkenalkan, tetapi terkadang persyaratan ini terlewatkan atau tidak diikuti. Ada kasus seperti itu di masa lalu. Bahkan jika Anda meletakkan versi tetap di composer.json Anda, dependensi Anda mungkin memiliki ~ dan ^ di composer.json mereka.

Jika dapat diakses, menurut saya, cara yang lebih baik adalah menggunakan repositori artefak (Nexus, JFrog, dll.). Rilis build, yang berisi semua dependensi yang diperlukan, akan dibuat satu kali, pada awalnya. Artefak ini akan disimpan dalam repositori dan diambil untuk berbagai tahap pengujian dari sana. Juga, itu akan menjadi build yang akan di-deploy ke produksi, alih-alih membangun kembali aplikasi dari Git.

Menjaga Kode dan Basis Data Kompatibel

Alasan saya jatuh cinta pada Laravel pada pandangan pertama adalah bagaimana penulisnya memperhatikan detail dengan cermat, memikirkan kenyamanan pengembang, dan juga memasukkan banyak praktik terbaik ke dalam kerangka kerja, seperti migrasi basis data.

Migrasi basis data memungkinkan kami untuk menyinkronkan basis data dan kode kami. Kedua perubahannya dapat dimasukkan dalam satu komit, karenanya rilis tunggal. Namun, ini tidak berarti bahwa perubahan apa pun dapat diterapkan tanpa waktu henti. Pada titik tertentu selama penerapan, akan ada versi berbeda dari aplikasi dan database yang berjalan. Dalam kasus masalah, titik ini bahkan mungkin berubah menjadi periode. Kita harus selalu mencoba untuk membuat keduanya kompatibel dengan versi sebelumnya dari rekan mereka: database lama-aplikasi baru, database baru-aplikasi lama.

Sebagai contoh, katakanlah kita memiliki kolom address dan perlu membaginya menjadi address1 dan address2 . Agar semuanya tetap kompatibel, kami mungkin memerlukan beberapa rilis.

  1. Tambahkan dua kolom baru di database.
  2. Ubah aplikasi untuk menggunakan bidang baru bila memungkinkan.
  3. Migrasikan data address ke kolom baru dan lepaskan.

Kasus ini juga merupakan contoh yang baik tentang bagaimana perubahan kecil jauh lebih baik untuk diterapkan. Rollback mereka juga lebih mudah. Jika kami mengubah basis kode dan basis data selama beberapa minggu atau bulan, mungkin mustahil untuk memperbarui sistem produksi tanpa waktu henti.

Beberapa Kehebatan Kubernetes

Meskipun skala aplikasi kita mungkin tidak memerlukan cloud, node, dan Kubernetes, saya masih ingin menyebutkan seperti apa penerapan di K8. Dalam hal ini, kami tidak melakukan perubahan pada sistem, melainkan menyatakan apa yang ingin kami capai dan apa yang harus dijalankan pada berapa banyak replika. Kemudian, Kubernetes memastikan bahwa keadaan sebenarnya sesuai dengan yang diinginkan.

Setiap kali kami memiliki rilis baru yang siap, kami membuat gambar dengan file baru di dalamnya, menandai gambar dengan versi baru, dan meneruskannya ke K8s. Yang terakhir akan dengan cepat memutar gambar kita di dalam sebuah cluster. Itu akan menunggu sebelum aplikasi siap berdasarkan pemeriksaan kesiapan yang kami berikan, lalu tanpa disadari mengarahkan lalu lintas ke aplikasi baru dan mematikan yang lama. Kami dapat dengan mudah menjalankan beberapa versi aplikasi kami yang memungkinkan kami melakukan penerapan biru/hijau atau kenari hanya dengan beberapa perintah.

Jika Anda tertarik, ada beberapa demonstrasi yang mengesankan dalam ceramah “9 Langkah untuk Mengagumkan dengan Kubernetes oleh Burr Sutter.”

Terkait: Otentikasi Pengguna Penuh dan Kontrol Akses – Tutorial Paspor Laravel, Pt. 1