انتشار werf 1.1: بهبودهایی برای سازنده امروز و برنامه‌هایی برای آینده

انتشار werf 1.1: بهبودهایی برای سازنده امروز و برنامه‌هایی برای آینده

ورف ابزار منبع باز GitOps CLI ما برای ساخت و ارائه برنامه ها به Kubernetes است. همانطور که قول داده شده بود، انتشار نسخه v1.0 آغازی برای افزودن ویژگی های جدید به werf و تجدید نظر در رویکردهای سنتی بود. اکنون ما خوشحالیم که نسخه 1.1 را ارائه می کنیم که گامی بزرگ در توسعه و پایه ای برای آینده است گردآورنده ورف نسخه در حال حاضر در دسترس است کانال 1.1 ea.

اساس انتشار، معماری جدید ذخیره سازی صحنه و بهینه سازی کار هر دو مجموعه (برای Stapel و Dockerfile) است. معماری جدید ذخیره‌سازی امکان پیاده‌سازی مجموعه‌های توزیع‌شده از چندین میزبان و مجموعه‌های موازی روی یک میزبان را باز می‌کند.

بهینه سازی کار شامل خلاص شدن از محاسبات غیر ضروری در مرحله محاسبه امضاهای مرحله و تغییر مکانیسم های محاسبه جمع های چک فایل به موارد کارآمدتر است. این بهینه سازی میانگین زمان ساخت پروژه با استفاده از werf را کاهش می دهد. و بیلدهای بیکار، زمانی که تمام مراحل در حافظه پنهان وجود دارند مراحل-ذخیره سازی، اکنون واقعا سریع هستند. در بیشتر موارد، راه اندازی مجدد ساخت کمتر از 1 ثانیه طول می کشد! این همچنین در مورد رویه هایی برای تأیید مراحل در روند کار تیم ها اعمال می شود. werf deploy и werf run.

همچنین در این نسخه، یک استراتژی برای برچسب گذاری تصاویر بر اساس محتوا ظاهر شد - برچسب گذاری مبتنی بر محتوا، که اکنون به طور پیش فرض فعال است و تنها مورد توصیه شده است.

بیایید نگاهی دقیق‌تر به نوآوری‌های کلیدی werf v1.1 بیندازیم و در عین حال در مورد برنامه‌های آینده به شما بگوییم.

چه چیزی در werf v1.1 تغییر کرده است؟

قالب و الگوریتم جدید نامگذاری مرحله برای انتخاب مراحل از کش

قانون جدید تولید نام صحنه. اکنون هر مرحله ساخت یک نام مرحله منحصر به فرد ایجاد می کند که از 2 قسمت تشکیل شده است: یک امضا (همانطور که در نسخه 1.0 بود) به اضافه یک شناسه موقت منحصر به فرد.

به عنوان مثال، نام تصویر مرحله کامل ممکن است به شکل زیر باشد:

werf-stages-storage/myproject:d2c5ad3d2c9fcd9e57b50edd9cb26c32d156165eb355318cebc3412b-1582656767835

یا به طور کلی:

werf-stages-storage/PROJECT:SIGNATURE-TIMESTAMP_MILLISEC

در اینجا:

  • SIGNATURE یک امضای مرحله است که نشان دهنده شناسه محتوای مرحله است و به تاریخچه ویرایش هایی در Git که منجر به این محتوا شده است بستگی دارد.
  • TIMESTAMP_MILLISEC یک شناسه تصویر منحصر به فرد تضمین شده است که در زمان ساخت یک تصویر جدید تولید می شود.

الگوریتم انتخاب مراحل از حافظه نهان بر اساس بررسی رابطه تعهدات Git است:

  1. ورف امضای مرحله خاصی را محاسبه می کند.
  2. В مراحل-ذخیره سازی ممکن است چندین مرحله برای یک امضای مشخص وجود داشته باشد. Werf تمام مراحلی را که با امضا مطابقت دارند انتخاب می کند.
  3. اگر مرحله فعلی به Git مرتبط باشد (git-archive، مرحله سفارشی با وصله های Git: install, beforeSetup, setup; یا git-latest-patch)، سپس werf فقط مراحلی را انتخاب می کند که با یک commit مرتبط هستند که اجداد commit فعلی است (که build برای آن فراخوانی می شود).
  4. از میان مراحل مناسب باقیمانده، یکی انتخاب می شود - قدیمی ترین آنها بر اساس تاریخ ایجاد.

یک مرحله برای شاخه های مختلف Git می تواند دارای امضای یکسان باشد. اما werf از استفاده از کش مرتبط با شاخه های مختلف بین این شاخه ها جلوگیری می کند، حتی اگر امضاها مطابقت داشته باشند.

→ مستندات.

الگوریتم جدید برای ایجاد و ذخیره مراحل در ذخیره سازی مرحله

اگر هنگام انتخاب مراحل از کش، werf مرحله مناسبی پیدا نکرد، فرآیند مونتاژ مرحله جدید آغاز می شود.

توجه داشته باشید که چندین فرآیند (روی یک یا چند هاست) می توانند تقریباً در یک زمان شروع به ساخت همان مرحله کنند. Werf از یک الگوریتم مسدودسازی خوش بینانه استفاده می کند مراحل-ذخیره سازی در لحظه ذخیره تصویر تازه جمع آوری شده در مراحل-ذخیره سازی. به این ترتیب وقتی ساخت مرحله جدید آماده شد، ورف بلوک می شود مراحل-ذخیره سازی و یک تصویر تازه جمع آوری شده را تنها در صورتی ذخیره می کند که تصویر مناسب دیگر در آنجا وجود نداشته باشد (با امضا و سایر پارامترها - الگوریتم جدید برای انتخاب مراحل از کش را ببینید).

یک تصویر تازه مونتاژ شده دارای یک شناسه منحصر به فرد توسط تضمین می شود TIMESTAMP_MILLISEC (قالب نامگذاری مرحله جدید را ببینید). در صورت در مراحل-ذخیره سازی یک تصویر مناسب پیدا می شود، werf تصویر تازه کامپایل شده را دور می اندازد و از تصویر موجود در حافظه پنهان استفاده می کند.

به عبارت دیگر: اولین فرآیندی که ساخت تصویر را به پایان می‌رساند (سریع‌ترین فرآیند) حق ذخیره آن را در مراحل ذخیره می‌کند (و سپس این تصویر واحد است که برای همه ساخت‌ها استفاده می‌شود). یک فرآیند ساخت آهسته هرگز مانع از ذخیره نتایج ساخت مرحله فعلی و رفتن به ساخت بعدی توسط یک فرآیند سریعتر نمی شود.

→ مستندات.

بهبود عملکرد سازنده Dockerfile

در حال حاضر، خط لوله مراحل برای یک تصویر ساخته شده از یک Dockerfile شامل یک مرحله است - dockerfile. هنگام محاسبه امضا، چک جمع پرونده ها محاسبه می شود context، که در هنگام مونتاژ استفاده خواهد شد. قبل از این بهبود، werf به صورت بازگشتی در تمام فایل‌ها قدم زد و با جمع‌بندی زمینه و حالت هر فایل، یک چک‌جمع به دست آورد. با شروع نسخه 1.1، werf می تواند از چک جمع های محاسبه شده ذخیره شده در یک مخزن Git استفاده کند.

الگوریتم بر اساس git ls-tree. الگوریتم رکوردهای موجود را در نظر می گیرد .dockerignore و درخت فایل را فقط در صورت لزوم به صورت بازگشتی طی می کند. بنابراین، ما از خواندن سیستم فایل و وابستگی الگوریتم به اندازه جدا شده ایم. context قابل توجه نیست

این الگوریتم همچنین فایل های ردیابی نشده را بررسی می کند و در صورت لزوم، آنها را در چک جمع در نظر می گیرد.

بهبود عملکرد هنگام وارد کردن فایل ها

نسخه‌های werf v1.1 از سرور rsync استفاده می‌کنند وارد کردن فایل ها از مصنوعات و تصاویر. قبلاً، وارد کردن در دو مرحله با استفاده از mount دایرکتوری از سیستم میزبان انجام می شد.

عملکرد واردات در macOS دیگر محدود به حجم Docker نیست و واردات در همان زمان لینوکس و ویندوز انجام می شود.

برچسب گذاری بر اساس محتوا

Werf v1.1 از به اصطلاح برچسب گذاری توسط محتوای تصویر پشتیبانی می کند - برچسب گذاری مبتنی بر محتوا. برچسب‌های تصاویر Docker به محتوای آن تصاویر بستگی دارد.

هنگام اجرای دستور werf publish --tags-by-stages-signature یا werf ci-env --tagging-strategy=stages-signature تصاویر منتشر شده از به اصطلاح امضای صحنه تصویر هر تصویر با امضای مراحل این تصویر مشخص می شود که طبق قوانین امضای معمولی هر مرحله به طور جداگانه محاسبه می شود اما یک شناسه کلی تصویر است.

امضای مراحل تصویر به موارد زیر بستگی دارد:

  1. محتویات این تصویر؛
  2. تاریخچه تغییرات Git که منجر به این محتوا شد.

یک مخزن Git همیشه دارای commit های ساختگی است که محتویات فایل های تصویری را تغییر نمی دهد. به عنوان مثال، commits فقط با نظرات یا commit های ادغام، یا commit هایی که فایل هایی را در Git تغییر می دهد که به تصویر وارد نمی شوند.

هنگام استفاده از برچسب گذاری مبتنی بر محتوا، مشکلات راه اندازی مجدد غیر ضروری پادهای برنامه در Kubernetes به دلیل تغییر در نام تصویر حل می شود، حتی اگر محتوای تصویر تغییر نکرده باشد. به هر حال، این یکی از دلایلی است که مانع از ذخیره بسیاری از میکروسرویس های یک برنامه در یک مخزن Git می شود.

همچنین، برچسب‌گذاری مبتنی بر محتوا، روش برچسب‌گذاری مطمئن‌تری نسبت به برچسب‌گذاری در شاخه‌های Git است، زیرا محتوای تصاویر به‌دست‌آمده به ترتیب اجرای خطوط لوله در سیستم CI برای مونتاژ چند commit از یک شاخه بستگی ندارد.

این مهم است: از الان شروع میشه مراحل - امضا - آیا تنها استراتژی برچسب گذاری توصیه شده. به طور پیش فرض در دستور استفاده خواهد شد werf ci-env (مگر اینکه به صراحت طرح برچسب گذاری متفاوتی را مشخص کنید).

→ مستندات. یک نشریه جداگانه نیز به این ویژگی اختصاص داده خواهد شد. به روز شد (3 آوریل): مقاله با جزئیات منتشر شده.

سطوح ورود به سیستم

کاربر اکنون این فرصت را دارد که خروجی را کنترل کند، سطح گزارش را تنظیم کند و با اطلاعات اشکال زدایی کار کند. گزینه ها اضافه شد --log-quiet, --log-verbose, --log-debug.

به طور پیش فرض، خروجی حاوی حداقل اطلاعات است:

انتشار werf 1.1: بهبودهایی برای سازنده امروز و برنامه‌هایی برای آینده

هنگام استفاده از خروجی پرمخاطب (--log-verbose) می توانید ببینید که werf چگونه کار می کند:

انتشار werf 1.1: بهبودهایی برای سازنده امروز و برنامه‌هایی برای آینده

خروجی دقیق (--log-debug، علاوه بر اطلاعات اشکال زدایی werf، شامل لاگ های کتابخانه های استفاده شده نیز می باشد. به عنوان مثال، می‌توانید نحوه تعامل با رجیستری Docker را مشاهده کنید و همچنین مکان‌هایی را که زمان قابل توجهی در آن سپری شده است را ثبت کنید:

انتشار werf 1.1: بهبودهایی برای سازنده امروز و برنامه‌هایی برای آینده

برنامه های آینده

اخطار! گزینه های شرح داده شده در زیر مشخص شده اند v1.1 در این نسخه در دسترس خواهد بود، بسیاری از آنها در آینده نزدیک. به روز رسانی ها از طریق به روز رسانی خودکار ارائه می شود هنگام استفاده از Multiwerf. این ویژگی‌ها بر بخش پایدار توابع v1.1 تأثیر نمی‌گذارند؛ ظاهر آنها نیازی به مداخله دستی کاربر در پیکربندی‌های موجود ندارد.

پشتیبانی کامل از پیاده سازی های مختلف Docker Registry (NEW)

  • نسخه: v1.1
  • تاریخ: اسفند
  • موضوع

هدف این است که کاربر هنگام استفاده از werf از پیاده سازی سفارشی بدون محدودیت استفاده کند.

در حال حاضر، مجموعه‌ای از راه‌حل‌های زیر را شناسایی کرده‌ایم که می‌خواهیم پشتیبانی کامل از آنها را تضمین کنیم:

  • پیش فرض (کتابخانه/رجیستری)*،
  • AWS ECR
  • لاجوردی*،
  • داکر هاب
  • GCR*،
  • بسته های GitHub
  • GitLab Registry*,
  • بندرگاه*،
  • اسکله.

راه حل هایی که در حال حاضر به طور کامل توسط werf پشتیبانی می شوند با یک ستاره مشخص می شوند. برای دیگران پشتیبانی وجود دارد، اما با محدودیت.

دو مشکل اصلی را می توان شناسایی کرد:

  • برخی راه‌حل‌ها از حذف برچسب با استفاده از Docker Registry API پشتیبانی نمی‌کنند و کاربران را از استفاده از پاک‌سازی خودکار werf باز می‌دارد. این برای بسته‌های AWS ECR، Docker Hub و GitHub صادق است.
  • برخی راه‌حل‌ها از مخازن به اصطلاح تو در تو (Docker Hub، بسته‌های GitHub و Quay) پشتیبانی نمی‌کنند یا پشتیبانی نمی‌کنند، اما کاربر باید آنها را به صورت دستی با استفاده از UI یا API (AWS ECR) ایجاد کند.

ما می خواهیم این مشکلات و مشکلات دیگر را با استفاده از API های بومی راه حل ها حل کنیم. این وظیفه همچنین شامل پوشش چرخه کامل عملیات werf با آزمایشات برای هر یک از آنها است.

ساخت تصویر توزیع شده (↑)

  • نسخه: v1.2 v1.1 (اولویت اجرای این ویژگی افزایش یافته است)
  • تاریخ: مارس-آوریل مارس
  • موضوع

در حال حاضر، werf v1.0 و v1.1 را می توان تنها بر روی یک میزبان اختصاصی برای عملیات ساخت و انتشار تصاویر و استقرار برنامه در Kubernetes استفاده کرد.

برای باز کردن امکانات کار توزیع‌شده werf، زمانی که ساخت و استقرار برنامه‌ها در Kubernetes بر روی چندین میزبان دلخواه راه‌اندازی می‌شوند و این میزبان‌ها وضعیت خود را بین ساخت‌ها (راننده‌های موقت) ذخیره نمی‌کنند، werf برای پیاده‌سازی قابلیت استفاده لازم است. Docker Registry به عنوان فروشگاه صحنه.

قبلاً وقتی پروژه werf هنوز dapp نامیده می شد، چنین فرصتی داشت. با این حال، ما با تعدادی از مسائل مواجه شده‌ایم که باید در هنگام پیاده‌سازی این قابلیت در werf مورد توجه قرار گیرد.

یادداشت. این ویژگی نیازی به کار کلکتور در داخل غلاف های Kubernetes ندارد، زیرا برای انجام این کار، باید از وابستگی به سرور محلی Docker خلاص شوید (در غلاف Kubernetes به سرور محلی Docker دسترسی ندارید، زیرا خود فرآیند در یک ظرف در حال اجرا است و werf پشتیبانی نمی کند و نخواهد کرد. کار با سرور Docker از طریق شبکه). پشتیبانی از اجرای Kubernetes به طور جداگانه اجرا خواهد شد.

پشتیبانی رسمی از GitHub Actions (جدید)

  • نسخه: v1.1
  • تاریخ: اسفند
  • موضوع

شامل مستندات werf (بخش‌های مرجع и راهنمایی) و همچنین اکشن رسمی GitHub برای کار با werf.

علاوه بر این، به werf اجازه می دهد تا روی دونده های زودگذر کار کند.

مکانیک تعامل کاربر با سیستم CI مبتنی بر قرار دادن برچسب‌ها بر روی درخواست‌های کششی برای شروع اقدامات خاصی برای ساخت/تولید برنامه است.

توسعه محلی و استقرار برنامه های کاربردی با werf (↓)

  • نسخه: v1.1
  • تاریخ: ژانویه تا فوریه آوریل
  • موضوع

هدف اصلی دستیابی به یک پیکربندی یکپارچه برای استقرار برنامه‌ها هم به صورت محلی و هم در تولید، بدون اعمال پیچیده، خارج از جعبه است.

werf همچنین باید حالت عملیاتی داشته باشد که در آن ویرایش کد برنامه و دریافت فوری بازخورد از برنامه در حال اجرا برای اشکال زدایی راحت باشد.

الگوریتم تمیز کردن جدید (جدید)

  • نسخه: v1.1
  • تاریخ: فروردین
  • موضوع

در نسخه فعلی werf v1.1 در روش cleanup هیچ تمهیدی برای تمیز کردن تصاویر برای طرح برچسب‌گذاری مبتنی بر محتوا وجود ندارد - این تصاویر جمع می‌شوند.

همچنین، نسخه فعلی werf (v1.0 و v1.1) از سیاست‌های پاکسازی متفاوتی برای تصاویر منتشر شده تحت طرح‌های برچسب‌گذاری استفاده می‌کند: شاخه Git، تگ Git یا Git commit.

یک الگوریتم جدید برای تمیز کردن تصاویر بر اساس تاریخچه commit ها در Git، یکپارچه برای همه طرح های برچسب گذاری، اختراع شده است:

  • برای هر git HEAD (شاخه ها و برچسب ها) بیش از N1 تصاویر مرتبط با آخرین commit های N2 را نگه ندارید.
  • برای هر git HEAD (شاخه ها و برچسب ها) بیش از N1 تصاویر مرحله مرتبط با آخرین commit های N2 را ذخیره نکنید.
  • همه تصاویری که در هر منبع خوشه Kubernetes استفاده می‌شوند را ذخیره کنید (همه زمینه‌های Kube فایل پیکربندی و فضاهای نام اسکن می‌شوند؛ می‌توانید این رفتار را با گزینه‌های خاص محدود کنید).
  • تمام تصاویری که در مانیفست های پیکربندی منابع ذخیره شده در نسخه های Helm استفاده می شوند را ذخیره کنید.
  • اگر تصویری با هیچ HEAD از git مرتبط نباشد (مثلاً چون خود HEAD مربوطه حذف شده است) و در هیچ مانیفست در خوشه Kubernetes و در نسخه های Helm استفاده نشود، می توان آن را حذف کرد.

ساختمان تصویر موازی (↓)

  • نسخه: v1.1
  • تاریخ: ژانویه تا فوریه آوریل *

نسخه فعلی werf تصاویر و مصنوعات شرح داده شده در آن را جمع آوری می کند werf.yaml، به ترتیب موازی سازی روند مونتاژ مراحل مستقل تصاویر و مصنوعات و همچنین ارائه خروجی راحت و آموزنده ضروری است.

* توجه: مهلت به دلیل افزایش اولویت برای اجرای مونتاژ توزیع شده تغییر یافته است که قابلیت های مقیاس افقی بیشتری را اضافه می کند و همچنین استفاده از werf با GitHub Actions. مونتاژ موازی مرحله بعدی بهینه سازی است که مقیاس پذیری عمودی را هنگام مونتاژ یک پروژه فراهم می کند.

انتقال به Helm 3 (↓)

  • نسخه: v1.2
  • تاریخ: فوریه تا مارس می*

شامل مهاجرت به پایگاه کد جدید است هلم 3 و یک روش ثابت و راحت برای انتقال تاسیسات موجود.

* توجه: تغییر به Helm 3 ویژگی های قابل توجهی را به werf اضافه نمی کند، زیرا تمام ویژگی های کلیدی Helm 3 (3-way-merge و no tiler) قبلاً در werf پیاده سازی شده اند. علاوه بر این، werf دارد ویژگی های اضافی علاوه بر موارد ذکر شده با این حال، این انتقال در برنامه های ما باقی مانده و اجرا خواهد شد.

Jsonnet برای توصیف پیکربندی Kubernetes (↓)

  • نسخه: v1.2
  • تاریخ: ژانویه - فوریه آوریل - مه

Werf از توضیحات پیکربندی Kubernetes در قالب Jsonnet پشتیبانی می کند. در عین حال، werf با Helm سازگار خواهد ماند و امکان انتخاب فرمت توضیحات وجود خواهد داشت.

دلیل آن این است که قالب های Go به گفته بسیاری از افراد دارای مانع ورودی بالایی هستند و قابل درک بودن کد این قالب ها نیز آسیب می بیند.

امکان معرفی سایر سیستم های توصیف پیکربندی Kubernetes (به عنوان مثال Kustomize) نیز در حال بررسی است.

کار در Kubernetes (↓)

  • نسخه: v1.2
  • تاریخ: آوریل - مه - ژوئن

هدف: اطمینان حاصل کنید که تصاویر ساخته شده اند و برنامه با استفاده از runners در Kubernetes ارائه می شود. آن ها تصاویر جدید را می توان مستقیماً از پادهای Kubernetes ساخته، منتشر کرد، پاک کرد و مستقر کرد.

برای پیاده سازی این قابلیت ابتدا باید بتوانید تصاویر توزیع شده بسازید (نگاه کنید به نکته بالا).

همچنین نیاز به پشتیبانی از حالت عملکرد سازنده بدون سرور Docker دارد (یعنی ساخت یا ساخت شبیه Kaniko در فضای کاربری).

Werf از ساخت بر روی Kubernetes نه تنها با Dockerfile، بلکه با سازنده Stapel خود با بازسازی های تدریجی و Ansible پشتیبانی می کند.

گامی به سوی توسعه باز

ما جامعه خود را دوست داریم (GitHub, تلگرام) و ما از افراد بیشتر و بیشتری می خواهیم که به بهتر شدن ورف کمک کنند، مسیری که در آن حرکت می کنیم را درک کنند و در توسعه مشارکت کنند.

اخیراً تصمیم گرفته شد که به آن سوئیچ کنیم تابلوهای پروژه GitHub به منظور آشکار کردن روند کار تیم ما. اکنون می توانید برنامه های فوری و همچنین کارهای جاری را در زمینه های زیر مشاهده کنید:

کارهای زیادی با مسائل زیر انجام شده است:

  • موارد نامربوط را حذف کرد.
  • موارد موجود به یک قالب واحد، با تعداد کافی جزئیات و جزئیات آورده شده است.
  • موضوعات جدید با ایده ها و پیشنهادات اضافه شده است.

نحوه فعال کردن نسخه v1.1

نسخه در حال حاضر در دسترس است کانال 1.1 ea (در کانال ها پایدار и جامد با این حال، با وقوع تثبیت، انتشار ظاهر می شود ea خود به اندازه کافی برای استفاده پایدار است، زیرا از طریق کانال ها گذشت آلفا и بتا). فعال شد از طریق مولتی ورف به روش زیر:

source $(multiwerf use 1.1 ea)
werf COMMAND ...

نتیجه

معماری جدید ذخیره سازی مرحله و بهینه سازی های سازنده برای سازندگان Stapel و Dockerfile امکان پیاده سازی ساخت های توزیع شده و موازی در werf را باز می کند. این ویژگی ها به زودی در همان نسخه نسخه 1.1 ظاهر می شوند و به طور خودکار از طریق مکانیسم به روز رسانی خودکار (برای کاربران) در دسترس خواهند بود چند وجهی).

در این نسخه، یک استراتژی برچسب گذاری بر اساس محتوای تصویر اضافه شده است - برچسب گذاری مبتنی بر محتوا، که به استراتژی پیش فرض تبدیل شده است. لاگ فرمان اصلی نیز دوباره کار شده است: werf build, werf publish, werf deploy, werf dismiss, werf cleanup.

گام مهم بعدی اضافه کردن مجموعه های توزیع شده است. ساخت‌های توزیع‌شده از نسخه 1.0 به اولویت بالاتری نسبت به ساخت‌های موازی تبدیل شده‌اند، زیرا ارزش بیشتری به werf می‌افزایند: مقیاس‌بندی عمودی سازنده‌ها و پشتیبانی از سازنده‌های زودگذر در سیستم‌های مختلف CI/CD، و همچنین توانایی پشتیبانی رسمی از GitHub Actions. . بنابراین، مهلت های اجرایی برای مجامع موازی جابه جا شد. با این حال، ما در تلاش هستیم تا هر دو احتمال را در اسرع وقت اجرا کنیم.

اخبار را دنبال کنید! و فراموش نکنید که به ما سر بزنید GitHubبرای ایجاد یک مشکل، پیدا کردن یک مورد موجود و اضافه کردن یک امتیاز مثبت، ایجاد روابط عمومی، یا به سادگی توسعه پروژه را تماشا کنید.

PS

در وبلاگ ما نیز بخوانید:

منبع: www.habr.com

اضافه کردن نظر