برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

سلام به همه در این وبلاگ! این سومین پست از مجموعه‌ای است که در آن نحوه استقرار برنامه‌های وب مدرن در Red Hat OpenShift را نشان می‌دهیم.

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

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

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

OpenShift به عنوان یک محیط توسعه

گردش کار توسعه

همانطور که قبلاً در اولین پست، فرآیند توسعه معمول برای برنامه های کاربردی وب مدرن به سادگی نوعی "سرور توسعه" است که تغییرات فایل های محلی را ردیابی می کند. هنگامی که آنها رخ می دهند، ساخت برنامه راه اندازی می شود و سپس به مرورگر به روز می شود.

در اکثر چارچوب های مدرن، چنین "سرور توسعه" در ابزارهای خط فرمان مربوطه تعبیه شده است.

نمونه محلی

ابتدا، بیایید ببینیم این کار هنگام اجرای برنامه‌ها به صورت محلی چگونه کار می‌کند. بیایید برنامه را به عنوان مثال در نظر بگیریم واکنش نشان می دهند از مقالات قبلی، اگرچه تقریباً همان مفاهیم گردش کار در تمام چارچوب های مدرن دیگر اعمال می شود.
بنابراین، برای راه اندازی "dev server" در مثال React خود، دستور زیر را وارد می کنیم:

$ npm run start

سپس در پنجره ترمینال چیزی شبیه به این را خواهیم دید:

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

و برنامه ما در مرورگر پیش فرض باز می شود:

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

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

خوب، همه چیز با توسعه در حالت محلی روشن است، اما چگونه می‌توان در OpenShift به همین نتیجه رسید؟

سرور توسعه در OpenShift

اگر به خاطر دارید، در پست قبلی، ما به فاز اجرا شده تصویر S2I نگاه کردیم و دیدیم که به طور پیش فرض ماژول سرویس وظیفه سرویس دهی به برنامه وب ما را بر عهده دارد.

با این حال، اگر از نزدیک نگاه کنید اجرای اسکریپت از آن مثال، شامل متغیر محیطی $NPM_RUN است که به شما امکان می دهد دستور خود را اجرا کنید.

به عنوان مثال، ما می توانیم از ماژول nodeshift برای استقرار برنامه خود استفاده کنیم:

$ npx nodeshift --deploy.env NPM_RUN="yarn start" --dockerImage=nodeshift/ubi8-s2i-web-app

نکته: مثال بالا برای نشان دادن ایده کلی به اختصار آمده است.

در اینجا ما متغیر محیطی NPM_RUN را به استقرار خود اضافه کرده‌ایم، که به زمان اجرا می‌گوید دستور yarn start را اجرا کند، که سرور توسعه React را در OpenShift pod ما راه‌اندازی می‌کند.

اگر به گزارش یک غلاف در حال اجرا نگاه کنید، چیزی شبیه به این خواهد بود:

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

البته تا زمانی که نتوانیم کد محلی را با کد همگام سازی کنیم، همه اینها هیچ نخواهد بود، که برای تغییرات نیز نظارت می شود، اما روی یک سرور راه دور زندگی می کند.

همگام سازی ریموت و کد محلی

خوشبختانه، nodeshift به راحتی می تواند به همگام سازی کمک کند و می توانید از دستور watch برای پیگیری تغییرات استفاده کنید.

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

$ npx nodeshift watch

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

بنابراین، اگر اکنون فایل src/App.js را به‌روزرسانی کنیم، سیستم به این تغییرات واکنش نشان می‌دهد، آنها را در خوشه راه دور کپی می‌کند و سرور توسعه را راه‌اندازی می‌کند، که سپس برنامه ما را در مرورگر به‌روزرسانی می‌کند.

برای تکمیل تصویر، اجازه دهید نشان دهیم که کل این دستورات چگونه هستند:

$ npx nodeshift --strictSSL=false --dockerImage=nodeshift/ubi8-s2i-web-app --build.env YARN_ENABLED=true --expose --deploy.env NPM_RUN="yarn start" --deploy.port 3000

$ npx nodeshift watch --strictSSL=false

فرمان watch یک انتزاع در بالای دستور oc rsync است، می‌توانید درباره نحوه کارکرد آن بیشتر بدانید. اینجا.

این یک مثال برای React بود، اما دقیقاً همان روش را می‌توان با سایر فریم‌ورک‌ها استفاده کرد، فقط متغیر محیطی NPM_RUN را در صورت لزوم تنظیم کنید.

خطوط لوله Openshift

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

در ادامه در مورد ابزاری مانند OpenShift Pipelines و نحوه استفاده از آن به عنوان جایگزینی برای ساخت های زنجیره ای صحبت خواهیم کرد.

خطوط لوله OpenShift چیست؟

OpenShift Pipelines یک سیستم ادغام و تحویل پیوسته CI/CD بومی ابری است که برای سازماندهی خطوط لوله با استفاده از Tekton طراحی شده است. Tekton یک چارچوب CI/CD بومی Kubernetes منبع باز منعطف است که به شما امکان می دهد با انتزاع از لایه زیرین، استقرار را بر روی پلتفرم های مختلف (Kubernetes، بدون سرور، ماشین های مجازی و غیره) خودکار کنید.

درک این مقاله مستلزم دانشی در مورد Pipelines است، بنابراین اکیداً توصیه می کنیم ابتدا مطالعه کنید کتاب درسی رسمی.

محیط کار خود را تنظیم کنید

برای بازی با مثال های این مقاله، ابتدا باید محیط کاری خود را آماده کنید:

  1. یک کلاستر OpenShift 4 را نصب و پیکربندی کنید. نمونه‌های ما برای این کار از CodeReady Containers (CRD) استفاده می‌کنند که دستورالعمل‌های نصب آن را می‌توانید پیدا کنید. اینجا.
  2. پس از آماده شدن کلاستر، باید Pipeline Operator را روی آن نصب کنید. نترسید، آسان است، دستورالعمل نصب اینجا.
  3. دانلود تکتون CLI (tkn) اینجا.
  4. ابزار خط فرمان create-react-app را اجرا کنید تا برنامه ای ایجاد کنید که سپس آن را مستقر خواهید کرد (این یک برنامه ساده است واکنش نشان می دهند).
  5. (اختیاری) برای اجرای برنامه نمونه به صورت محلی با نصب npm و سپس شروع npm مخزن را کلون کنید.

مخزن برنامه همچنین دارای یک پوشه k8s است که حاوی Kubernetes/OpenShift YAML های مورد استفاده برای استقرار برنامه است. Tasks، ClusterTasks، Resources و Pipelines وجود خواهد داشت که در این مورد ایجاد خواهیم کرد مخازن.

بیا شروع کنیم

اولین قدم برای مثال ما ایجاد یک پروژه جدید در خوشه OpenShift است. بیایید این پروژه را webapp-pipeline صدا کنیم و با دستور زیر آن را ایجاد کنیم:

$ oc new-project webapp-pipeline

این نام پروژه بعداً در کد ظاهر می شود، بنابراین اگر تصمیم دارید نام آن را با نام دیگری انتخاب کنید، فراموش نکنید که کد نمونه را مطابق با آن ویرایش کنید. از این نقطه شروع می کنیم، نه از بالا به پایین، بلکه از پایین به بالا: یعنی ابتدا تمام اجزای نوار نقاله و تنها پس از آن خود نوار نقاله را ایجاد می کنیم.

پس اول از همه ...

وظایف

بیایید چند کار ایجاد کنیم، که سپس به استقرار برنامه در خط لوله ما کمک می کند. اولین وظیفه - application_manifests_task - مسئول اعمال YAML منابع Kubernetes (سرویس، استقرار و مسیر) است که در پوشه k8s برنامه ما قرار دارند. وظیفه دوم - update_deployment_task - مسئول به روز رسانی تصویری است که قبلاً مستقر شده است به تصویر ایجاد شده توسط خط لوله ما.

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

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/tasks/update_deployment_task.yaml
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/tasks/apply_manifests_task.yaml

سپس با استفاده از دستور tkn CLI بررسی می کنیم که وظایف ایجاد شده اند:

$ tkn task ls

NAME                AGE
apply-manifests     1 minute ago
update-deployment   1 minute ago

توجه: اینها وظایف محلی برای پروژه فعلی شما هستند.

وظایف خوشه ای

وظایف کلاستر اساساً همان وظایف ساده هستند. یعنی مجموعه ای از مراحل قابل استفاده مجدد است که در هنگام اجرای یک کار خاص به یک روش ترکیب می شوند. تفاوت این است که یک کار کلاستر در همه جای خوشه موجود است. برای مشاهده لیستی از وظایف کلاستر که به صورت خودکار هنگام افزودن Pipeline Operator ایجاد می شوند، دوباره از دستور tkn CLI استفاده می کنیم:

$ tkn clustertask ls

NAME                       AGE
buildah                    1 day ago
buildah-v0-10-0            1 day ago
jib-maven                  1 day ago
kn                         1 day ago
maven                      1 day ago
openshift-client           1 day ago
openshift-client-v0-10-0   1 day ago
s2i                        1 day ago
s2i-go                     1 day ago
s2i-go-v0-10-0             1 day ago
s2i-java-11                1 day ago
s2i-java-11-v0-10-0        1 day ago
s2i-java-8                 1 day ago
s2i-java-8-v0-10-0         1 day ago
s2i-nodejs                 1 day ago
s2i-nodejs-v0-10-0         1 day ago
s2i-perl                   1 day ago
s2i-perl-v0-10-0           1 day ago
s2i-php                    1 day ago
s2i-php-v0-10-0            1 day ago
s2i-python-3               1 day ago
s2i-python-3-v0-10-0       1 day ago
s2i-ruby                   1 day ago
s2i-ruby-v0-10-0           1 day ago
s2i-v0-10-0                1 day ago

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

ایجاد و ارسال تصویر

هنگام ایجاد اولین وظیفه، آنچه را که قبلاً در مقاله قبلی درباره مجموعه های پیوندی انجام دادیم، تکرار می کنیم. به یاد بیاورید که ما از تصویر S2I (ubi8-s2i-web-app) برای "ساخت" برنامه خود استفاده کردیم و در نهایت به یک تصویر ذخیره شده در رجیستری داخلی OpenShift رسیدیم. اکنون ما از این تصویر برنامه وب S2I برای ایجاد یک DockerFile برای برنامه خود استفاده می کنیم و سپس از Buildah برای انجام ساخت واقعی استفاده می کنیم و تصویر حاصل را به رجیستری داخلی OpenShift فشار می دهیم، زیرا این دقیقاً همان کاری است که OpenShift هنگام استقرار برنامه های خود با استفاده از NodeShift انجام می دهد. .

می پرسی ما از کجا همه اینها را می دانستیم؟ از جانب نسخه رسمی Node.js رسمی، ما فقط آن را کپی کردیم و برای خودمان اصلاح کردیم.

بنابراین، اکنون بیایید وظیفه کلاستر s2i-web-app را ایجاد کنیم:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/clustertasks/s2i-web-app-task.yaml

ما این را با جزئیات تجزیه و تحلیل نخواهیم کرد، بلکه فقط بر روی پارامتر OUTPUT_DIR تمرکز خواهیم کرد:

params:
      - name: OUTPUT_DIR
        description: The location of the build output directory
        default: build

به طور پیش فرض، این پارامتر برابر با build است، جایی که React محتوای اسمبل شده را قرار می دهد. فریمورک های دیگر از مسیرهای مختلفی استفاده می کنند، به عنوان مثال، در Ember آن dist است. خروجی اولین کار کلاستر ما یک تصویر حاوی HTML، جاوا اسکریپت و CSS خواهد بود که جمع آوری کرده ایم.

یک تصویر بر اساس NGINX بسازید

در مورد دومین وظیفه کلاستر ما، باید با استفاده از محتوای برنامه‌ای که قبلاً ساخته‌ایم، یک تصویر مبتنی بر NGINX برای ما بسازد. در اصل، این بخشی از بخش قبلی است که در آن به ساخت های زنجیره ای نگاه کردیم.

برای انجام این کار، ما - دقیقاً مشابه همان بالا - یک کار کلاستر webapp-build-runtime ایجاد می کنیم:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/clustertasks/webapp-build-runtime-task.yaml

اگر به کد این وظایف کلاستر نگاه کنید، می بینید که مخزن Git که با آن کار می کنیم یا نام تصاویری که در حال ایجاد آن هستیم را مشخص نمی کند. ما فقط مشخص می کنیم که دقیقاً چه چیزی را به Git منتقل می کنیم، یا یک تصویر خاص که در آن تصویر نهایی باید خروجی شود. به همین دلیل است که می توان از این وظایف خوشه ای هنگام کار با برنامه های دیگر استفاده مجدد کرد.

و در اینجا با آرامش به نکته بعدی می رویم ...

منابع

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

# This resource is the location of the git repo with the web application source
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: web-application-repo
spec:
  type: git
  params:
    - name: url
      value: https://github.com/nodeshift-starters/react-pipeline-example
    - name: revision
      value: master

در اینجا PipelineResource از نوع git است. کلید url در بخش params به یک مخزن خاص اشاره می کند و شاخه اصلی را مشخص می کند (این اختیاری است، اما ما آن را برای کامل شدن می نویسیم).

اکنون باید یک منبع برای تصویر ایجاد کنیم که در آن نتایج وظیفه s2i-web-app ذخیره می شود، این کار به این صورت انجام می شود:

# This resource is the result of running "npm run build",  the resulting built files will be located in /opt/app-root/output
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: built-web-application-image
spec:
  type: image
  params:
    - name: url
      value: image-registry.openshift-image-registry.svc:5000/webapp-pipeline/built-web-application:latest

در اینجا PipelineResource از نوع image است، و مقدار پارامتر url به رجیستری تصویر داخلی OpenShift اشاره می کند، به ویژه رجیستری که در فضای نام webapp-pipeline قرار دارد. اگر از فضای نام دیگری استفاده می کنید، فراموش نکنید که این تنظیم را تغییر دهید.

و در نهایت، آخرین منبعی که ما نیاز داریم نیز از نوع image خواهد بود و این تصویر نهایی NGINX خواهد بود که در حین استقرار استفاده خواهد شد:

# This resource is the image that will be just the static html, css, js files being run with nginx
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: runtime-web-application-image
spec:
  type: image
  params:
    - name: url
      value: image-registry.openshift-image-registry.svc:5000/webapp-pipeline/runtime-web-application:latest

باز هم توجه داشته باشید که این منبع تصویر را در رجیستری OpenShift داخلی در فضای نام webapp-pipeline ذخیره می کند.

برای ایجاد همه این منابع به طور همزمان، از دستور create استفاده می کنیم:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/resources/resource.yaml

می توانید مطمئن شوید که منابع به این صورت ایجاد شده اند:

$ tkn resource ls

خط لوله نقاله

اکنون که همه اجزای لازم را داریم، بیایید یک خط لوله از آنها با ایجاد آن با دستور زیر جمع آوری کنیم:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/pipelines/build-and-deploy-react.yaml

اما قبل از اجرای این دستور، اجازه دهید این اجزا را بررسی کنیم. اولی نام است:

apiVersion: tekton.dev/v1alpha1
kind: Pipeline
metadata:
  name: build-and-deploy-react

سپس در بخش مشخصات، نشانه ای از منابعی را که قبلا ایجاد کرده بودیم می بینیم:

spec:
  resources:
    - name: web-application-repo
      type: git
    - name: built-web-application-image
      type: image
    - name: runtime-web-application-image
      type: image

سپس وظایفی را که خط لوله ما باید تکمیل کند ایجاد می کنیم. اول از همه، باید وظیفه s2i-web-app را که قبلاً ایجاد کرده ایم، اجرا کند:

tasks:
    - name: build-web-application
      taskRef:
        name: s2i-web-app
        kind: ClusterTask

این کار پارامترهای ورودی (منبع gir) و خروجی (ساخت وب-برنامه-منبع تصویر) را می گیرد. ما همچنین یک پارامتر خاص به آن می‌دهیم تا TLS را تأیید نکند زیرا از گواهی‌های خودامضا استفاده می‌کنیم:

resources:
        inputs:
          - name: source
            resource: web-application-repo
        outputs:
          - name: image
            resource: built-web-application-image
      params:
        - name: TLSVERIFY
          value: "false"

کار بعدی تقریباً یکسان است، فقط در اینجا وظیفه کلاستر webapp-build-runtime که قبلاً ایجاد کرده ایم نامیده می شود:

name: build-runtime-image
    taskRef:
      name: webapp-build-runtime
      kind: ClusterTask

مانند کار قبلی، ما یک منبع را ارسال می کنیم، اما اکنون آن را ساخته شده-web-application-image (خروجی وظیفه قبلی ما) است. و به عنوان خروجی دوباره تصویر را تنظیم می کنیم. از آنجایی که این وظیفه باید بعد از وظیفه قبلی اجرا شود، فیلد runAfter را اضافه می کنیم:

resources:
        inputs:
          - name: image
            resource: built-web-application-image
        outputs:
          - name: image
            resource: runtime-web-application-image
        params:
        - name: TLSVERIFY
          value: "false"
      runAfter:
        - build-web-application

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

راه اندازی نوار نقاله

بنابراین، تمام قسمت های خط لوله ما ایجاد می شود و ما آن را با دستور زیر اجرا می کنیم:

$ tkn pipeline start build-and-deploy-react

در این مرحله، خط فرمان به صورت تعاملی استفاده می شود و شما باید منابع مناسب را در پاسخ به هر یک از درخواست های آن انتخاب کنید: برای منبع git، web-application-repo، سپس برای اولین منبع تصویر، ساخته شده وب-برنامه را انتخاب کنید. -image و در نهایت برای منبع تصویر دوم -runtime-web-application-image:

? Choose the git resource to use for web-application-repo: web-application-repo (https://github.com/nodeshift-starters/react-pipeline-example)
? Choose the image resource to use for built-web-application-image: built-web-application-image (image-registry.openshift-image-registry.svc:5000/webapp-pipeline/built-web-
application:latest)
? Choose the image resource to use for runtime-web-application-image: runtime-web-application-image (image-registry.openshift-image-registry.svc:5000/webapp-pipeline/runtim
e-web-application:latest)
Pipelinerun started: build-and-deploy-react-run-4xwsr

حال بیایید وضعیت خط لوله را با استفاده از دستور زیر بررسی کنیم:

$ tkn pipeline logs -f

پس از شروع خط لوله و استقرار برنامه، می توانیم مسیر منتشر شده را با دستور زیر درخواست کنیم:

$ oc get route react-pipeline-example --template='http://{{.spec.host}}'

برای تجسم بیشتر، می توانید خط لوله ما را در حالت توسعه دهنده کنسول وب در بخش مشاهده کنید خطوط لوله، همانطور که در شکل نشان داده شده است. 1.

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

عکس. 1. بررسی خطوط لوله در حال اجرا

همانطور که در شکل 2 نشان داده شده است، با کلیک بر روی خط لوله در حال اجرا، جزئیات بیشتری نمایش داده می شود.

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

برنج. 2. اطلاعات اضافی در مورد خط لوله.

پس از کسب اطلاعات بیشتر، می توانید برنامه های در حال اجرا را در نمای مشاهده کنید توپولوژیهمانطور که در شکل 3 نشان داده شده است.

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

شکل 3. غلاف راه اندازی شد.

با کلیک بر روی دایره در گوشه سمت راست بالای نماد برنامه ما باز می شود، همانطور که در شکل 4 نشان داده شده است.

برنامه های مدرن در OpenShift، قسمت 3: OpenShift به عنوان یک محیط توسعه و OpenShift Pipelines

برنج. 4. اجرای برنامه React.

نتیجه

بنابراین، ما نشان دادیم که چگونه یک سرور توسعه را برای برنامه خود در OpenShift اجرا کنید و آن را با سیستم فایل محلی همگام کنید. ما همچنین نحوه شبیه سازی یک الگوی ساخت زنجیره ای را با استفاده از OpenShift Pipelines بررسی کردیم. همه کدهای نمونه از این مقاله را می توان یافت اینجا.

منابع اضافی (EN)

اطلاعیه های وبینارهای آتی

ما در حال شروع یک سری وبینارهای جمعه درباره تجربه بومی با استفاده از Red Hat OpenShift Container Platform و Kubernetes هستیم:

منبع: www.habr.com

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