استقرار خودکار پروژه های Laravel با Gitlab CI
آخرین مرحله از روند توسعه یک پروژه مربوط به مباحث استقرار اون میشه. خودکارسازی این کار کمک زیادی به صرفه جویی در وقت میکنه و باعث میشه نیروهای فنی مجموعه تمرکز خودشون رو روی موارد دیگه ای قرار بدن. در این پست به بررسی چنین کاری می پردازیم.
اگر پست قبلی رو مطالعه کرده باشید ، با استفاده از Gitlab یکسری Pipeline برای امور مختلف ساختیم و بخش اول کار یعنی CI انجام شد.
حالا نوبت به استقرار پروژه میرسه. پس از اینکه تمامی تست ها انجام شد و طبق نیازمندی های خودمون مطمئن شدیم آخرین تغییرات Commit شده شرایط مناسب برای استقرار نهایی رو داره ، این کار رو انجام میدیم.
توجه کنید که در این پست ساده ترین روش خودکارسازی استقرار رو توضیح میدم. استفاده از ابزارهایی مانند Kubernets و ادغام اون با CI/CD یکی از بهترین کارهاست ولی در عین حال پیچیدگی های خودش رو داره و هزینه های مازادی ممکنه برای تیم های کوچیک ایجاد کنه.
برای استقرار پروژه های Laravel کتابخانه های متعددی نوشته شده که با جستجوی عبارت Laravel Deployer
به لیست بلندی از اون ها بر میخورید. در این پست از این گزینه استفاده می کنیم :
نصب
قبل از تنظیم Pipeline باید کتابخانه deployer رو نصب و تنظیم کنیم. برای این کار مسلما از composer استفاده می کنیم :
composer require deployer/deployer deployer/recipes
پس از اینکه پکیج ها نصب شد با استفاده از دستور زیر عملیات initialize را انجام دهید تا فایل deploy.php
مربوط به تنظیمات هم برای شما ساخته شود :
./vendor/bin/dep init
به صورت پیش فرض deployer از Git برای استقرار و بروزرسانی پروژه ها استفاده می کنه. به این صورت که به سرور شما از طریق SSH متصل میشه و سپس git pull
انجام میده. از اونجایی که ما این عملیات رو در خود Gitlab انجام میدیم پس Pipeline ما مستقیما آخرین تغییرات رو بررسی کرده و build میکنه و در نهایت یک پروژه آماده برای استقرار داریم و در نتیجه نیاز نیست از روش پیش فرض استفاده کنیم. راه جایگزین استفاده از rsync
برای کپی کردن مستقیم فایل هاست.
فایل deploy.php
را باز کرده و کدهای زیر را به ترتیب قرار بدید :
<?php
namespace Deployer;
require 'recipe/laravel.php';
require 'recipe/rsync.php';
set('application', getenv('CI_PROJECT_NAME'));
set('ssh_multiplexing', true);
set('rsync_src', function () {
return __DIR__;
});
ابتدا کتابخانه های مورد نظرمون رو وارد می کنیم. سپس مقادیری را باید تنظیم کنیم :
- application : اینجا باید نام پروژه قرار داده بشه که با استفاده از متغیر
CI_PROJECT_NAME
این کار انجام میشه. - ssh_multiplexing : با فعال کردن این گزینه سرعت کار افزایش پیدا میکنه بیشتر بخوانید
- rsync_src : اینجا باید مسیر root پروژه تنظیم بشه. اگر ریپازیتوری مستقیما به پروژه اشاره میکنه لازم به تغییر نیست.
add('rsync', [
'exclude' => [
'.git',
'/.env',
'/storage/',
'/vendor/',
'/node_modules/',
'.gitlab-ci.yml',
'deploy.php',
],
]);
این بخش مانند .gitignore
عمل میکنه و تمامی دایرکتوری ها و فایل هایی که نیاز به sync شدن ندارن رو مشخص می کنیم.
// Copy secrets
task('deploy:secrets', function () {
file_put_contents(__DIR__ . '/.env', getenv('DOT_ENV'));
upload('.env', get('deploy_path') . '/shared');
});
در این قسمت یک task برای تنظیم env ها نیاز داریم. چرا ؟ خب مسلما پروژه لاراول به فایل .env
نیاز داره و از اونجایی که اطلاعات مهمی در این فایل ذخیره میشه امکان قرار دادن اون در ریپازیتوری وجود نداره. پس تمام اطلاعاتش رو به صورت Environment در Gitlab ذخیره کرده و با استفاده از این بخش اون رو به صورت فایل در میاریم.
// Hosts
host('production.app.com') // Name
->hostname('165.22.242.104') // Hostname or IP
->stage('production') // Deployment stage
->user('deploy') // User
->set('deploy_path', '/var/www'); // Path
در این بخش باید اطلاعات سرور داده بشه :
- hostname : شامل آدرس آی پی یا دامنه برای اتصال به سرور
- stage : با توجه به پروژه استیج مورد نظر رو قرار بدید مثلا production, staging, dev یا هر چیز دیگه
- user : نام کاربری که از طریق SSH بهش متصل میشید
- set('deploy_path', ...) : تنظیم دایرکتوری مقصد که باید پروژه قرار بگیره و sync بشه
توجه کنید که اتصال با SSH از طریق کلمه عبور انجام نمیشه و باید کلید های مورد نظر ساخته بشه که در ادامه در موردش صحبت می کنیم
after('deploy:failed', 'deploy:unlock');
task('deploy', [
'deploy:info',
'deploy:prepare',
'deploy:lock',
'deploy:release',
'rsync', // Deploy code
'deploy:secrets', // Deploy secrets
'deploy:shared',
'deploy:vendors',
'deploy:writable',
'artisan:storage:link', // |
'artisan:view:cache', // |
'artisan:config:cache', // | Laravel commands
'artisan:optimize', // |
'artisan:migrate', // |
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);
در نهایت برای اجرای عملیات deploy از این بخش استفاده میشه. با تنظیم deploy:failed
که همانند یک رخداد عمل میکنه می تونیم عملیات های مختلفی رو در صورت بروز خطا انجام بدیم.
در آخرین task دستوراتی اجرا میشن که ترتیب اونها مهمه :
- ابتدا دستورات مربوط به خود deployer اجرا میشن
- با استفاده از دستور rsync کل فایل ها کپی میشن
- با دستور
deploy:secrets
تسکی که در بالا تنظیم کرده بودیم اجرا میشه - در ادامه تمامی دستورات لازم برای لاراول ( با استفاده از artisan ) اجرا میشه
فایل deploy.php
ما در نهایت به این صورت خواهد بود :
<?php
namespace Deployer;
require 'recipe/laravel.php';
require 'recipe/rsync.php';
set('application', getenv('CI_PROJECT_NAME'));
set('ssh_multiplexing', true);
set('rsync_src', function () {
return __DIR__;
});
// Rsync
add('rsync', [
'exclude' => [
'.git',
'/.env',
'/storage/',
'/vendor/',
'/node_modules/',
'.gitlab-ci.yml',
'deploy.php',
],
]);
// Copy secrets
task('deploy:secrets', function () {
file_put_contents(__DIR__ . '/.env', getenv('DOT_ENV'));
upload('.env', get('deploy_path') . '/shared');
});
// Hosts
host('production.app.com') // Name
->hostname('165.22.242.104') // Hostname or IP
->stage('production') // Deployment stage
->user('deploy') // User
->set('deploy_path', '/var/www'); // Path
after('deploy:failed', 'deploy:unlock');
task('deploy', [
'deploy:info',
'deploy:prepare',
'deploy:lock',
'deploy:release',
'rsync', // Deploy code
'deploy:secrets', // Deploy secrets
'deploy:shared',
'deploy:vendors',
'deploy:writable',
'artisan:storage:link', // |
'artisan:view:cache', // |
'artisan:config:cache', // | Laravel commands
'artisan:optimize', // |
'artisan:migrate', // |
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);
تنظیم سرور
همونطور که در بالا مشاهده کردید روند اتصال با SSH با کلمه عبور انجام نمیشه و در نتیجه ما نیاز داریم تا کلید های خصوصی و عمومی خودمون رو برای این کار داشته باشیم. الان که دارید بهش فکر می کنید شاید بگید کار سختیه ولی اصلا اینطور نیست ... پیاده سازی چنین چیزی به راحتی در Gitlab امکان پذیره.
ابتدا کلید های عمومی و خصوصی خودتون رو با استفاده از دستور ssh-keygen
بسازید و کلید عمومی رو به سرور معرفی کنید :
ssh-copy-id -i ~/.ssh/mykey user@host
محتوای کلید خصوصی رو برای بعدا نیاز داریم.
یکی دیگه از مواردی که بهش نیاز داریم fingerprint سرور میشه. اگه دقت کرده باشید اولین بار که میخواید به سروری متصل بشید چنین پیغامی برای شما میاد :
با تایید کردن ، آدرس آی پی سرور به فایل ssh/known_hosts./~
اضافه میشه و در واقع شما این سرور رو به لیست سرور های آشنا و مطمئن اضافه می کنید. حالا سوال اینجاست که ما در Gitlab CI چه کنیم ؟ اونجا هم به چنین فایلی نیاز داریم !
برای این مورد هم میتونیم این کار رو از طریق Env انجام بدیم و fingerprint سرور رو به صورت متغیر ذخیره کنیم. برای این کار دستور زیر رو اجرا کنید :
ssh-keyscan -t rsa <server IP>
خروجی این دستور همان fingerprint سرور شماست که باید برای استفاده های بعدی اون رو ذخیره کنید.
تنظیم Gitlab CI
حالا نوبت به آخرین بخش میرسه. برای استفاده از Gitlab CI به فایل gitlab-ci.yml.
نیاز داریم که در پست قبلی اون رو ایجاد کردیم :
اینجا فقط کافیه تا stage جدیدی برای بحث deploy به اون اضافه بشه :
stages:
- preparation
- building
- testing
- security
- deploy
...
deploy:
stage: deploy
only:
- master
dependencies:
- phpunit
before_script:
- mkdir -p ~/.ssh
- eval $(ssh-agent -s)
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- echo "${SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - > /dev/null
script:
- dep deploy production --tag=$CI_COMMIT_REF_NAME -vvv
این job مسلما باید پس از اجرای موفق مراحل قبل اجرا بشه. تمامی پیش نیاز ها رو میتونید با توجه به نیازتون قرار بدید ، برای مثال اینجا من فقط phpunit
رو تنظیم کردم که قبل از استقرار مطمئن بشم تست ها با موفقیت پشت سر گذاشته شده.
این job مسلما باید روی branch اصلی مخزن اجرا بشه تا احیانا کدهای مشکل دار deploy نشن. حتما این مورد رو مد نظر قرار بدید ( البته خب بستگی به ساختار پروژه و روش استفاده شما از git داره ولی خب راه اصولیش اینه که مستقیم به branch اصلی تغییرات مهم فرستاده نشه ).
خب حالا نوبت به اجرای دستورات مورد نظرمون میرسه تا روند deploy اتفاق بیوفته. اول باید پیش نیازهامون رو داشته باشیم :
- از اونجایی که همش در مورد SSH صحبت کردیم ، دایرکتوری پیش فرض اون رو میسازیم
- سپس ssh-agent رو اجرا می کنیم
- مقدار fingerprint سرور که به صورت Env ذخیره شده رو در فایل مورد نظر قرار میدیم
- دسترسی ها تنظیم میشه
- با استفاده از
ssh-add
کلید خصوصی به gitlab اضافه میشه
و در نهایت با اجرای دستور dep deploy production
عملیات deploy آغاز میشه.
ما تا اینجا 3 متغیر تعریف کردیم که باید به صورت ENV به Gitlab اضافه بشن :
- DOT_ENV : شامل اطلاعات فایل
env.
مربوط به Laravel میشه که در فایلdeploy.php
ازش استفاده می کنیم - SSH_PRIVATE_KEY : کلید خصوصی ساخته شده جهت اتصال به سرور
- SSH_KNOWN_HOSTS : داده مربوط به fingerprint سرور مقصد
حالا تمام تغییرات رو Push کنید و از اجرای درست Pipeline مطمئن بشید. با اتمام این بخش یک روند کامل CI/CD برای پروژه لاراول پیاده سازی میشه و نتیجه اش خودکار سازی کلیه امور مربوط به کار شماست. از اجرای تست ها گرفته تا بررسی Code Style و استقرار نهایی پروژه بدون لحظه ای Downtime
توجه کنید که مطالب آموزش داده شده در این 2 پست معیار مناسبی برای تمام پروژه ها نیست و لازمه تا با در نظر گرفتن شرایط ، تغییراتی رو اعمال کنید. شاید نیاز به stage های دیگه ای داشته باشید ، دستورات artisan متفاوتی اجرا کنید و غیره.