فرایند استقرار یک گام اساسی در چرخه چرخه توسعه نرم افزار است.
وقتی به طور ویژه به جهان Node.js می آید ، دو رویکرد اصلی پدیدار شده است. یکی از آنها سبک "git push heroku" سریع و بدون درز است. مورد دوم ، که ما در این مقاله توضیح خواهیم داد ، عموما خطوط لوله استقرار پیشرفته تری را هدف قرار می دهد – آنهایی که از ده ها سرویس دهنده برنامه تشکیل شده و اغلب به خط مشی خرابی صفر نیاز دارند.
"با استفاده از روش ارائه شده ، ما به یک صفر رسیدیم. زمان استقرار که در طول روز به طور مرتب بر روی 50 – 120 سرور اجرا می شود ، اما می تواند تعداد بیشتری از آن را رقم بزند. "
این رویکرد فراتر از یک لاینر دستی است و خواستار کد سفارشی است. بنابراین ، هدف ما این است که به شما نشان دهیم که چگونه یک اسکریپت استقرار آسان را با کمک کم (نه چندان کم) از پرواز در اختیار شما قرار دهید.
اجازه دهید در وهله اول درباره استقرار به سبک هرووک صحبت کنیم. یک سلب مسئولیت: ما از تحریک هرووک و خانواده دور نیستیم. ما خودمان از آن استفاده کرده ایم و فکر می کنیم که آنها مکان مناسبی را در نقشه DevOps دارند. ما فقط می خواهیم روشی جایگزین برای مدیریت استقرار در محیط های پر چالش تر ارائه دهیم ، چالش برانگیز از نظر زیرساخت ها و مسئولیت قابلیت اطمینان سایت که روی شانه های شما قرار دارد.
بنابراین ، "جایگزین جدید شما چیست؟" . رویکرد جدید واقعاً جدید نیست. این اسکریپت پوسته قدیمی است. تنها نکته جدید این است که آن را با یک رابط کاربری خوب JS تست شده از نبرد و ابر آماده شده است. با planeplan.js ملاقات کنید. اکنون ، بنشینید و در مورد سفر خود به یک اسکریپت استقرار قابل تنظیم و قابل اطمینان گوش دهید که به ما امکان می دهد علاوه بر استقرار کامل خرابی صفر ، به کارگیری قطعات جزئی ، بازگرداندن سریع و اتحاد نسخه های همه سرورها بپردازیم.
INFRASTRUCTURE [19659009] قبل از اینکه شیرجه بزنیم ، بیایید لحظه ای توضیح دهیم که چگونه سرورها و سایر موارد در پرونده ما به هم وصل می شوند. این تصویر بزرگ است:
در یک مرور کلی ، ما سرورهای کاربردی زیادی در پشت تعادل بار AWS ELB داریم. فلش در بالا نشان دهنده درخواست هایی است که از جهان خارج وارد برنامه ما می شود. بعد از اینکه از طریق ELB مسیریابی شدند ، آنها به یکی از سرورهای BE ما که سرورهای با پسوند در اختیار برنامه قرار دارد ، پایان می دهند. تعادل بار در این معماری برای تقسیم کم و بیش بار در بین تمام موارد موجود ضروری است. 70 درصد از سرویس دهنده ها قرار است درخواست ها را انجام دهند و در حد متوسط CPU مطلوب بمانند. جادوی مناسب سحر و جادو نظارت را به همراه اتوکالینگ فراهم می کند. پس از اتمام هر نمونه ، عروسکی به ما کمک می کند تا آن را با حداقل وابستگی های نرم افزاری لازم پر کنیم و برنامه خود را در نسخه مورد نظر راه اندازی کنیم.
هم ELB و هم کنسول به ما امکان می دهند لیستی از سرورها را در دست بگیریم و سلامت آنها را بررسی کنیم. علاوه بر این ، کنسول یک حافظه با ارزش کلیدی را برای پیکربندی در زمان واقعی برای ذخیره جنبه های سطح پایین و تنظیمات خاص خاص دامنه از برنامه ارائه می دهد. Consul یک بستر کشف خدمات است که به ما کمک می کند تا همه موارد (IP ، سلامت) را که براساس نوع خدماتی که یک نمونه خاص ارائه می دهد ، ردیابی کنیم. از منظر DevOps در حال اجرا ، اسکریپت ما از تمام ویژگی های کنسول ذکر شده از طریق API آن استفاده می کند ، از دستورات خارجی به نام aws-cli از پیش نصب شده و پیکربندی شده و قابلیت داخلی Flightplan برای استفاده از عوامل SSH استفاده می کند. aws-cli ابزاری برای خط فرمان از AWS است که عملیات بسیاری را برای برخی از خدمات آنها ارائه می دهد. ما از آن برای کنترل ELB استفاده می کنیم و از معماری S3 اطلاعاتی به دست می آوریم. این امکان را برای ما فراهم می کند تا صدها سرور را اندازه گیری کنیم (همانطور که در نمودار زیر نشان داده شده است) و هنوز راهی برای به روزرسانی ناخواسته کد در هر زمان داریم.
SCRIPT
بیایید قسمت خسته کننده را ببندیم و خلاصه کاری را که می توانیم با پرواز انجام دهیم ، مرور کنیم.
{OD CODE}
"استفاده از سخت" ؛
// ثبت برگشت پاسخ تمیز قبل از نیاز به پرواز.
process.on ("SIGINT" ، interchedCleanup) ؛
ثبت نام callback repup قبل از نیاز به پرواز. "Use") ،
moment = need ("moment")،
_ = need ("lodash")،
plan = need ("پرواز") ،
درخواست = درخواست ("درخواست -فرهنگ ") ؛
/ *
موارد استفاده:
پرواز [deploy:] dev (مستقر شعبه فعلی در DEV)
پرواز [deploy:] dev –branch = تست (مستقر شاخه تست ، این اختیاری است پارام می تواند برای همه اهداف مورد استفاده قرار گیرد)
پرواز [deploy:] dev –branch = 23af9e8 (استقرار 23af9e8 را انجام می دهد ، می تواند برای همه اهداف مورد استفاده قرار گیرد)
پرواز [deploy:] canary –msg = "خلاصه" (استقرار شاخه اصلی در قناری ، msg لازم است برای اهداف قناری و تولید پارامتر باشد]
پرواز [deploy:] production10 –msg = "خلاصه" (استقرار استاد سبزه ساعت در 10٪ از سرورهای تولید)
پرواز [deploy:] production25 –msg = "خلاصه" (شاخه کارشناسی ارشد را روی 25٪ از سرورهای تولید مستقر می کند)
پرواز [deploy:] prodhimin –msg = "خلاصه" ( شاخه کارشناسی ارشد را روی همه سرورهای تولید مستقر می کند)
پرواز [deploy:] production –msg = "خلاصه" –force = "172.11.22.333"
پرواز [deploy:] تولید –msg = "خلاصه" – force = "172.11.22.333،172.22.33.444"
(پارامتر نیرو اجازه می دهد تا در انتظار معاینه های بهداشتی برخی از موارد بگذرد و مجبور به مجدد کار اجباری در آنجا شود)
پرواز [deploy:] canary –msg = "Msg" – بی صدا (حالت خاموش خاموش و اعلان ها و وقایع Slack را خاموش می کند)
برگشت پرواز: قناری (ساخت و سازهای قدیمی در قناری)
پرواز برگشت پرواز: تولید (ساخت بک ترک های قدیمی در تمام سرورهای تولید)
پرواز متحد: تولید ( نسخه ساخت را برای همه سرورهای تولید متحد می کند ، و برای "بازگشت" لایک های جزئی استفاده می کند e 10٪)
{{ENDCODE}
نظرات بعدی در مورد هر دستور و یک بررسی اجمالی از فرآیند که به زودی می خواهیم آن را بدست آوریم این است که من همین الان می توانم به شما بدهم تا شما را برای کد آینده آماده کنم قطعه قطعه بنابراین اگر هنوز لازم نیست ، لطفاً سریعاً از آن فراتر بروید. و اگر اکنون تعجب می کنید که آیا ایده استقرار جزئی ، بازگرداندن ها و اتحاد را به درستی می دانید ، اجازه دهید نمودار زیر با نشان دادن نحوه تغییر نشانگرهای نسخه با مرور زمان ، همه این موارد را روشن کند. این نشان می دهد که چگونه ورودی های دارای ارزش کلیدی با نسخه های برنامه در حین اعزام به DEV ، قناری و تولید در محدوده کامل یا جزئی به روز می شوند. این مقادیر سپس هنگام بازیابی برنامه روی سرور گرفته می شوند. نمودار نمای را ساده می کند ، زیرا در واقعیت مقادیر گیت gys هستند.
EXER EMERGENCY
اکنون ، بعد از اینکه مقدمه را گذرانیدیم ، وقت آن رسیده است که به سؤال واقعی پاسخ دهیم: چه چیزی در قطع شده است. و چه زمانی لازم است؟ این بیشتر در شرایطی است که توسعه دهنده می خواهد اجرای اسکریپت را ترک کند زیرا فکر می کند چیزی اشتباه است. در اکثر موارد ، هیچ سرور در آن زمان پردازش نمی شود ، اما ، برای مثال ، وقتی اسکریپت به طور تصادفی متوقف می شود ، ما آخرین فرصت را داریم که با داده های ذخیره شده در حافظه کاری مفید انجام دهیم.
اگر می خواهید این کار را داشته باشید ، باید آن را در اسرع وقت ، حتی قبل از "برنامه پرواز" اضافه کنید. (توجه: علاوه بر یک Flightplan که به طور محلی نصب شده است برای کار با اسکریپت به احتیاج دارد ، ما باید آن را نیز در سطح جهانی نصب کنیم) npm i -g پرواز برنامه ای برای اینکه بتوانیم از آن استفاده کنیم ابزار خط فرمان.
{{CODE}}
// ثبت برگشت پاسخ تمیز قبل از نیاز به هواپیما پرواز.
دستگیرنده فقط درحال ثبت چه دستوراتی است. باید مورد استفاده قرار گیرد تا نمونه هایی که در حال حاضر از ELB حذف شده اند را بازگرداند. وقتی فیلمنامه قطع شد ، بهترین ایده برای تلاش برای ایجاد یک فرآیند جدید برای انجام این کار نیست زیرا در بسیاری از موارد به سادگی از بین می رود. d در حال حاضر بهترین کاری است که می توانیم در چنین مواردی انجام دهیم.
برخی از متغیرهای نشان داده شده باید فوراً برای شما حس کنند ، مانند نسخهKeyPostfix ، oldVersionKeyPostfix ، آوردهBackPrevVersion ، canUnify یا FFELELB را حذف کرد ، و برخی ممکن است توضیحات بیشتری بخواهند. برای مثال lbTakeout را بگیرید. این بدان معناست که اگر اهداف را باید از EL خارج کرده و دوباره به ELB برگردانید. LockTarget متغیری است که در صورت تنظیم ، اسکریپت را از قفل ساده برای جلوگیری از استقرار موازی روی سرورهای مهم استفاده می کند. این مقدار نیز در داخل قفل در کنسول ذخیره می شود ، فقط به این دلیل که می توانیم.
همچنین ، یک کلمه در مورد PARALLEL_DEPLOYS_FRACTION . این فقط بخشی از چند سرور می تواند به طور همزمان اصلاح شود.
بگذارید تأکید کنم که مهمترین متغیر BRANCH نسخه برنامه ای را که باید مستقر شود ذخیره می کند. به طور پیش فرض ، به [استاد] اشاره می کند ، اما می تواند به راحتی توسط مبدل پارامترهای داخلی نادیده گرفته شود. اگر – branch = "مقدار" را در خط فرمان مشخص كنيم ، اين فوراً در داخل شيء گزينه ها موجود خواهد بود كه بعداً قابل مشاهده خواهد بود.
در مرحله بعد ما دو قلاب هدف بسيار مشابه براي DEV و Canary داريم. از کجا مشخص می کنیم (با استفاده از یک تابع) از چه سرورهایی استفاده می شود.
{{CODE}
// برنامه هدف برای سرور DEV.
plan.target ("dev" ، انجام شده => {[19659003] BRANCH = "HEAD" ؛ // استفاده از شعبه فعلی به عنوان پیش فرض.
getServersList ()
.catch (err => انجام شده (خطای جدید (use.format ("دریافت لیست سرورها انجام نشد – پیام:٪ s ، خطا:٪ j "، خطا ، خطا))))
اول ، بگذارید در اینجا منظور را مشخص کنیم. همانطور که احتمالاً حدس زده اید ، هر آنچه در اینجا به عنوان پارامتر دوم ارائه می شود (یک شی ، آرایه یا عملکردی که با استفاده از یک تماس برگشتی با جسم یا آرایه ای مانند پرونده ما تماس می گیرد) به عنوان ماشین های هدف که Flightplan برای ما کنترل خواهد کرد ، انجام می شود. مفهوم مهم دوم یک کار است. وظایف را به ترتیب کد می کشد اگر فقط نام آنها مطابق پارامتر اول باشد که می تواند یک رشته یا یک آرایه باشد.
محتوای روش هایی مانند getServersList را می توانید در قسمت اصلی ضمیمه پیدا کنید. پایان این پست در حال حاضر ، بیایید فرض کنیم که سرورها از فهرست کنسول نمونه های ثبت شده گرفته شده اند و به همین ترتیب می خواهیم چک های بهداشتی را از آن بعداً بخوانیم.
بعد ، بگذارید اهداف 25٪ و کامل تولید را بررسی کنیم (بیایید 10 را بگذاریم. ٪ مورد برای تخیل یا می توانید آن را در قسمت اصلی بررسی کنید.
{{CODE}
// برنامه هدف برای 25٪ از سرورهای تولید.
plan.target ("تولید 25" ، انجام شده = > {
getServersList ()
.catch (err => انجام شده (خطای جدید (Use.format) ("دریافت لیست سرورها انجام نشد – پیام:٪ s ، خطا:٪ j" ، خطا ، خطا)))
همانطور که می بینید در مواردی که تولیدات زیادی را برمی گردیم. به عنوان مثال ، آدرس های IP با الفبای طبقه بندی شده اند. این بدان معناست که ، به عنوان مثال ، هنگام استقرار 10٪ و سپس 25٪ ، فقط 15٪ سرورهای بیشتر با کد جدید به روز می شوند. توجه کنید که maxParallelDeploys کسری از تمام سرورها برای استقرار کامل است اما برابر با همه سرورهای هدف برای استقرار جزئی است. بنابراین ، بدون تغییر مناسب این متغیر ، از این کد برای کسری بالاتر از 25٪ استفاده نکنید.
فیلترهای زیر نشان داده شده بر اساس برچسب های کنسول که در اصل از پیکربندی AMI هر نمونه گرفته شده اند ، موردی را پیدا می کنند. ثبت نام در کنسول.
{{کد}}
تابع isNonProduction (سرور) {
Return isDev (سرور) || isCanary (سرور)؛
function
تابع isDev (سرور) {
Return _.includes (server.ServiceTags، "dev")؛
function
function isCanary (سرور) {
] _ _includes (server.ServiceTags، "قناری")؛
function
تابع toHost (آدرس) {
Return {
میزبان: آدرس ،
نام کاربری: "کاربر" ، [19659003] عامل: process.env.SSH_AUTH_SOCK
}؛
}
{{ENDCODE}}
در بالا می توانید ببینید که چگونه باید هر میزبان در انتها قالب بندی شود – علاوه بر ارائه IP ما باید (یا می تواند) نام کاربری ، یک نماینده محلی ssh را منتقل کند ، یا حتی مدارکی را برای اتصال SSH ارائه دهد.
خوب ، بگذارید یک استراحت بزنیم ، اگر در حال جابجایی هستید ، زیرا در حال حاضر ما وارد یک سراشیبی کاملاً تند هستیم.
رکورد سریع
آنچه تاکنون نشان داده ایم؟ قسمت اول این اسکریپت مربوط به تنظیم قلاب خروجی ، پیکربندی اولیه و دستیابی به اطلاعات در مورد سرورها است. Flightplan سپس "قسمت از راه دور" متن زیر را روی آنها اجرا خواهد کرد. ما می توانیم فقط توضیحات یک شیء ، مجموعه ای از چنین اشیاء یا عملکردی را ارائه دهیم که یکی از این دو را بازگرداند. هر یک از این موارد کلیه جزئیات مربوط به عامل محلی SSH را مشخص می کند. متغیر آرایه (یا اشیاء) به عنوان تعریف هدف ، باید در اسکریپت سخت کد شده باشد یا به طور همزمان همزمان پیش از فراخوانی قلاب هدف به دست آمده باشد.
وظایف تعریف ها
پس از تعریف همه اهداف ، زمان آن رسیده است. وظایف خود را به قسمت های محلی یا از راه دور متصل کنید و موارد را در اینجا ترتیب دهید.
ابتدا اگر بخواهیم برنامه را مستقر کنیم یا اگر اصلاً کار را مشخص نکنیم قلاب محلی آتش می رود. [) ، به عنوان مثال هنگام اجرا پرواز dev .
{{CODE}
plan.local ([“default”, “deploy”]، local => {
localMachine = local؛
const options = plan.runtime.options؛
if (options.force) {
detyr = options.force.trim (). split ("،")؛
}
if (گزینه ها). شاخه) {
BRANCH = options.branch؛
}
if (BRANCH === "HEAD") {
newRev = local.exec ("git rev-parse HEAD") stdout. trim ()؛
if (options.event && typof options.msg! == "رشته") {
plan.abort ("لطفا خلاصه استقرار را ارائه دهید. مثال" پرواز استقرار: تولید –msg = "خلاصه استقرار " ")) ؛
بازگشت ؛
} [19659003] if (! buildReady ()) {
plan.abort ("ساخت آماده نیست")؛
}
})؛
{{ENDCODE}
چرا چنین است؟ بسیاری از صخره ها در جاده؟ بیایید قدم به قدم برویم. در ردیف اول ، ما مرجع دستگاه محلی را در متغیر اسکریپت ذخیره می کنیم. ما این کار را انجام می دهیم تا در روش های کمکی دسترسی آسانتر داشته باشیم (کد کمتری) ، اما در درجه اول امکان تماس با کارکردهای خود را در حالی که Flightplan در حال اجرا قلاب از راه دور است ، انجام می دهیم.
خط دوم برای ذخیره گزینه ها در متغیرهای دستی بیشتر است. . این گزینه ها هم گزینه های هدف پیش فرض را دارند و هم گزینه های پویا ای که در خط فرمان ارائه می دهید. این در دو عبارت شرطی زیر استفاده می شود: اگر مقداری ارائه شده است ، آنرا تجزیه کرده و از آن استفاده کنید. اگر چیزی برای یک کلید خاص در خط فرمان فراهم نشده باشد ، مقدار در اسکریپت برابر با true خواهد بود.
بعدی جالب ترین قسمت وجود دارد: جایی که ویرایش نهایی به دست می آید. اگر فقط شعبه برابر باشد "HEAD" ما git sha شعبه محلی را بدست می آوریم. این بسیار مفید است وقتی می خواهیم یک ویژگی را به سرعت روی سرور DEV تست کنیم (در کد هدف این مقدار به صورت پیش فرض اختصاص داده شده است ؛ می توانید در بالا بررسی کنید). اگر تنها بخش BRANCH هنوز "استاد" " یا هر برچسب شعبه دیگری باشد ، متغیر remoteRev در حال ذخیره به گیت sha در مخزن از راه دور است. ، بعد از ادغام روابط عمومی ، حتی بدون وجود محلی git pull مستقر می شود. اگر شاخه ای پیدا نشود ، مقدار مورد نظر به عنوان یک git sha کوتاه مورد آزمایش قرار می گیرد و به شکل کامل آن گسترش می یابد.
سرانجام ، اسکریپت بررسی می کند که آیا هدف فعلی پیام استقرار را می خواهد یا اگر در دستور کار باشد خط سپس ، اسکریپت با بررسی اینکه آیا ظرف Docker با نام مربوطه در AWS S3 ذخیره شده است ، تأیید می کند که آیا ساخت مورد نظر آماده است. اعتراف می کنم این یک راه حل سفارشی است ، اما همیشه می توانید راه خود را پیدا کنید. همانطور که می بینید ، اگر فقط با برنامه plan.abort تماس بگیرید ، پرواز پرواز اجرای آن را کاهش می دهد. به یاد داشته باشید که این عملکرد خطایی را که باید هنگام استفاده در زمینه Flightplan گرفت ، گرفتار می کند ؛ اگر اینطور نیست ، ما یک استثناء حل نشده خواهیم داشت.
دستورات در FLIGHTPLAN
بیایید همچنین بررسی کنیم که چگونه ساخت آماده است.
{{CODE function
function buildReady () 19 [19659003] localMachine.exec را برگردانید ("aws s3 ls s3: //url.to.docker.registry/docker/registry/v2/repositories/product/_manifests/tags/" + newRev، {failsafe: true code) کد == = 0؛
}
{COD ENDCODE}
ما می توانیم برخی از جنبه های خاص Flightplan را ببینیم. اسکریپت با تابع را به صورت محلی اجرا می کند که فرآیندی را برای هر دستور سطح سیستم مورد نظر ما ایجاد می کند. به طور پیش فرض ، در صورت عدم موفقیت این خطا ، خطایی به وجود می آید تا کل اسکریپت قطع شود. تنظیم پارامتر failsafe در true این رفتار را سرکوب می کند و می توانیم کد حاصل تماس فرمان را بررسی کنیم. علاوه بر این ، ما به stdout و خروجی stderr دسترسی داریم. پارامتر مفید دیگر خاموش است که می تواند خروجی برخی از دستورات پر سر و صدا را از روی پرونده های Flightplan پنهان کند.
HOOKS MORE
دو قلاب زیر فقط برای تنظیم محلی محلی Machine و بررسی کنید که آیا می توانید این کار را بر روی هدف مشخص شده اجرا کنید.
{{CODE}
plan.local ("rollback"، local =>
localMachine = local؛
const options = plan. runtime.options؛
if (! options.oldVersionKeyPostfix) {
plan.abort ("هدف برگشت پذیر نیست")؛
}
؛)؛
plan.local ("وحدت" ، محلی => {
localMachine = محلی؛
const options = plan.runtime.options؛
if (! options.canUnify) {
plan. سقط ("هدف نمی تواند متحد شود")؛
}
})؛
// HACK: اتصال به هر سرور قبل از قفل کردن ، ارسال به Slack و غیره. plan.remote ([“default”, “deploy”, “rollback”, “unify”]، remote => remote.log ("متصل")) ؛
{{ENDCODE}
در بالا می توانیم اولین مورد قلاب از راه دور را ببینیم. آنچه که با محلی متفاوت است این است که ما در آنجا مرجع نمونه ای داریم که از لپ تاپ ما فاصله دارد. درک این نکته ضروری است که کد به طور معمول در اینجا در یک موضوع یک گره اجرا می شود ، و در عین حال دستورات آتش ناهمزمان (طبق طبیعت وب) را انجام می دهد و نتایج به دست آمده را مانند یک عمل همزمان همزمان می کند. به این ترتیب ، عملیات از راه دور طولانی در حال اجرا به طور همزمان بر روی تمام اهداف انتخاب شده انجام می شود.
در قطعه فوق ، به نظر می رسد که ما فقط یک کلمه را ثبت می کنیم ، اما آیا حتی این مورد نیاز است؟ این عملکرد ساده فقط در صورت بروز برخی مشکلات در اتصال به هر سرور BE ، اجرای اسکریپت را متوقف می کند. این کار به طور خاص قبل از قلاب محلی دیگر انجام می شود ، جایی که ما چندین عملیات اولیه را انجام می دهیم که فقط برای یک دستگاه اجرا می شوند ، مانند قفل کردن یا ارسال برخی اعلان ها. این گزینه ها می توانند به قلاب از راه دور نیز بروند ، اما در این صورت ما باید موارد دیگر از راه دور را همگام سازی کنیم تا منتظر یک امتیاز ممتاز باشیم.
ACTION HOOKS
اگر به این مرحله رسیدید ، من خبرهایی برای شما دارم. اول از همه ، فقط چهار قطعه کد برای رفتن وجود دارد. با این حال ، دو نفر اول کار اصلی را انجام می دهند ، بنابراین بیایید از آنها بگذریم و خلاصه اقدامات اصلی دقیقاً زیر هر قطعه کد قرار داشته باشد. در پایان ، دو قطعه آخر برای کشش پاهای شما قبل از تمام مبارزاتی که هنوز پیش روی شماست وجود دارد. کفش های خود را خوب ببندید و درست بعد از آن تپه بعدی یکدیگر را ببینیم.
Nice! بیایید به طور خلاصه آنچه را که در بالا اتفاق می افتد را یادآوری کنیم. اول ، همه اعدام ها به سرعت به زنجیره وعده هایی که در تماس [local[صور local.waitFor قرار دارند ، منتقل می شوند. این امر باعث می شود تا این شیء حمل و نقل (از طریق راه دور فراخوانی شود) صبر کند تا زمانی که تماس تلفنی ارائه شده به روش خود را صدا کنید.
مراحل مورد نظر را طی کنیم. در ابتدا سلامت سرور (فقط در مورد محصول) بررسی می شود تا اگر چیزی در حال حاضر خوب نباشد ، به سرعت از بین برید. پس از آن قفل را بررسی می کنیم و در صورت رایگان بودن (قناری و تولیدی) آن را به دست می آوریم. در مرحله بعد ، استقرار جزئی و کامل نسخه فعلی را دریافت می کند. استقرار کامل دیگر مانند قناری و تولید نسخه قدیمی را نسخه فعلی قرار می دهد. توجه کنید که آخرین نسخه را در یک مورد خاص جست و جو می کنیم ، هنگامی که نسخه قبلی برابر با نسخه جدید است. این می تواند رخ دهد اگر ما یک استقرار جزئی را بشکنیم یا به جای انجام یک کار متحد سازی ، استقرار کامل را تکرار کنیم.
درست قبل از آخرین دستور ، که نسخه جدید و مورد نظر خود را تنظیم می کند ، اسکریپت یک اعلان Slack ارسال می کند و یک رویداد را در ایجاد می کند. یکی از سیستم های مانیتورینگ ما این آتش سوزی فقط در صورت هدف در یک رویداد و حالت خاموش فعال نمی شود.
و این برای این قسمت می باشد. اگر همه چیز موفقیت آمیز باشد ، اسکریپت به اهداف از راه دور می رود ، کد زیر را "به طور موازی" با یکدیگر اجرا می کنید ، و سپس به آخرین قلاب می رود که دوباره به صورت محلی شلیک می شود.
جالب در قسمت قبل اتفاق می افتد. کد در اینجا در موضوع گره اجرا شده است ، که در هر زمان اجرای دستور روی یک هدف هدف ، زمان اجرای آن را بین همه زمینه های از راه دور مبادله می کند. پس از اتمام دستور ، یک زمینه از راه دور فراخوانی می تواند شروع به تکرار بیشتر تکرار قلاب ما کند. متغیرهای محلی یک حافظه مشترک ایجاد می کنند (خوشبختانه با تغییر فقط یک موضوع) برای همه زمینه های از راه دور که می توانیم برای همگام سازی کار آنها به روشی که می خواهیم استفاده کنیم. ابتدا ، از instId با استفاده از برخی ترفندهای AWS و نمونه Host از داده های Flightplan استفاده می کنیم. سپس ، اگر فقط هدف تولید باشد ، ما با هر سرور صبر می کنیم تا همه موارد سالم باشند. در ادامه ، پیشخوان parallelDeploysCount را افزایش می دهیم که هنگام گزارش پیشرفت قابل مشاهده است. بعد ، هنگامی که هدف آن را بخواهد ، ما آن را از ELB خارج می کنیم و 20 ثانیه صبر می کنیم تا امیدوارم که تمام درخواست ها تمام شود. سپس ، مرحله اصلی انجام می شود ، که مجدداً ظرف Docker را شروع می کند. بعد از اتمام ، نمونه را در ELB قرار می دهیم و بلافاصله آنرا انجام می دهیم. It’s because ELB will not enable this instance unless it’s not healthy, so we should not worry about this part. Some counters are then decreased (current parallel deploys count) or increased (progress) and the remote hook is finished at this point.
After each remote is finished the script goes into last local hook that sets back previous version (partial deploy) and/or removes the lock if needed.
A PIECE OF HISTORY
When talking about what must be triggered on the remote to restart the app, what we do now is restart a service that’s supposed to query for its specific version from the Consul, downloading a corresponding Docker image and running it. However, it was not always like that. One of the strong points of that kind of deploy scripts is the liberty that you gain when it comes to transporting layer of your app. In the very beginning we had neither Docker images nor any CI to build them. Our deploy procedure pushed us to run tests locally after merging the PR to check if all was still ok, and the script was simply pulling fresh code and restarting the guardian pm2 process. So you see, if you want to shift from git-push-repo deploy style it does not have to be such a big jump in the first iteration. Then we switched to .tar balls that were built on a CI and fetched into the remote machine by our deploy script, and now we have Docker images. Nobody at this point can predict if we won’t do any more switches in the future, but in all cases we have the elasticity.
SYNCHRONIZING THE WORK
As the last, hardest code sample I’m giving you, the waitUntilServersHealthy method matters only for the full production deploy. It’s orchestrating our remotes against maximum allowed parallel deploys limit and Consul’s health checks. If this is not the time to break the loop for particular remote (start the deploy), remote executes a sleep command, releasing our thread for other work. Worth noting is the way we use the local variable w postponing progress logging to not overwhelm the reader.
console.log("Waiting for all Backend servers in Consul failed – Message: %s, Error: %j", err, err);
done(false);
});
}
{{ENDCODE}}
Finally, the following is how we log the deploy progress. Besides calling it when servers are added or removed, we also use it in the loop above. A fragment of the final effect can be seen just after the code.
{{CODE}}
function logProgress() {
console.log("Progress: %d/%d, Removed from ELB: %j, Parallel deploys: %d/%d",
By using the presented method, we achieved a zero-downtime deploy that is regularly executed on 50 – 120 servers during the workday, but can scale up to some bigger numbers. It will not work perfectly yet on numbers bigger by order of magnitude, because servers sometimes fail to start and block our script in endless loop rechecking server’s health. But this is most likely some next issue we will solve here when we feel the urge.
In closing the story of possible choices to solve the deploy problem, let me stress out that the presented list is not exhaustive in any way. Let’s place git push deployments on one end of the spectrum, where all hard work is taken from us but the language of choice simply must be supported. On the other end place locally executed deploy scripts such as this one, with all that freedom and responsibility they mix in. When looking at this picture, it’s hard to forget there are plenty of choices in the middle. If we squint at just the containers’ deployments some good options like the Kubernetes, Docker swarm mode, and Apache Mesos arise on the horizon. Any of them might be the best answer for you, but with each option you gain and lose something. Dedicated for that job, products could provide right away most of the basic functions that we had to implement, like handling partial deploys or different deploy policies; yet achieving some of them as well as extending the process with these little custom steps, like Slack notifications, might be tricky at some point. Scripts maintained by us can also perform some project-specific actions on each instance, from cleaning old logs to parallel processing of some big amount of data. The final choice is up to you and as always should depend mostly on your project’s needs.
As a final thought, I can say that besides saving a lot of time for repeatable steps, like creating a monitoring event, the presented way is not free from a few negatives. First, it’s not always that easy in the beginning to write it more or less correctly. An already mentioned example is that we cannot stop the whole script directly from a waitFor indent and it might be tempting to use the abort in that code block. Also, the fact that we can synchronize remotes as freely as we want to can end up in more complex code than we initially envisaged. Another factor could be that locally executed script can be modified too easily. However, in good hands it can provide more good than possible damage, and you can always run it from the CI platform.
Yet, I hope with that kind of help you’ll be able to finish your flightplan.js much quicker now. As promised, a link to the full script is provided, especially for those of you interested in how exactly we use the Consul’s API or aws-cli commands. Please leave a comment if you have any thoughts or want to share your way of doing the deploys.