در این مطلب به پیاده سازی یک نمونه ساده از CICD برای یک پروژه Net Core. بر روی GitLab میپردازیم. عملا میخواهیم سرویسی را پیاده سازی کنیم تا آن کارهایی که به صورت دستی برای پابلیش پروژه انجام میدهیم را به صورت خودکار انجام دهد. برای پابلیش یک پروژه دات نتی بر روی IIS چه کارهایی انجام میدهیم؟

  1. Build گرفتن از پروژه
  2. Publish گرفتن از پروژه
  3. Stop کردن WebSite بر روی IIS
  4. Stop کردن Application Pool مربوط به WebSite
  5. انتقال فایل های پابلیش شده بر روی سرور
  6. Start کردن Application Pool مربوطه
  7. Start کردن WebSite مربوطه

میتوانیم کارهای نوشته شده را با استفاده از powershell انجام دهیم به صورت زیر:

dotnet build
dotnet publish -c Release  
Stop-WebSite -Name $application_pool_name
Stop-WebAppPool -Name $application_pool_name
Copy-Item $publish_path -Destination $iis_worker_path -Force
Start-WebAppPool -Name $application_pool_name
Start-WebSite -Name $application_pool_name

متغییر application_pool_name$ نام مربوط به برنامه بر روی IIS است که باید Stop شود. متغییر publish_path$ مسیر فایلهای پابلیش شده است که باید کپی شوند و متغییر iis_worker_path$ مسیر مربوط به برنامه اجرا شده بر روی IIS است که باید فایل ها به آن مسیر کپی شوند. باید کاری انجام دهیم تا این دستورات به صورت خودکار اجرا شوند.

GitLab شامل یک Pipeline است که از یک یا چند Job تشکیل شده است. Pipeline با استفاده از فایل gitlab-ci.yml. ساخته میشود که باید در روت پروژه قرار بگیرد در Repository مربوطه. در هرکدام از Job های مربوط به Pipeline میتوانیم دستورات خود را وارد کنیم و تمامی دستورات بالا را میتوانیم اجرا کنیم با استفاده از Pipeline. 

برای استفاده از Pipeline باید از GitLab-Runner استفاده کنیم. تمامی کارهای Pipeline توسط GitLab-Runner اجرا میشوند. در این مثال ما از یک سرور ویندوزی استفاده میکنیم برای همین باید Runner مربوط به ویندوز را نصب نمایید. برای نصب Runner باید فایل مناسب سیستم عامل خو را دانلود کنید از این لینک : https://docs.gitlab.com/runner/install/windows.html

سپس باید یک پوشه به نام  GitLab-Runner ایجاد کنید در روت درایور C. سپس فایل دانلود شده را درون این پوشه کپی کنید و نام فایل را به gitlab-runner.exe تغییر دهید. توجه داشته باشید که بر روی سروری که GitLab-Runner را نصب میکنید باید git نصب باشد و به پروژه شما دسترسی داشته باشد تا بتواند پروژه را clone کند.

در ادامه باید Runner را به GitLab معرفی کنیم تا بتوانیم دستورات مربوط به pipeline را اجرا کنیم. برای این کار باید در GitLab اطلاعات مربوط به Runner پروژه را دریافت کنیم. یک توکن و یک آدرس در سایت GitLab:

Settings > CICD > Runners > Specific runners

بعد از دریافت توکن و آدرس مربوطه باید این اطلاعات را به Runner ارسال کنیم. برای این کار باید CMD را در مسیر C:\Gitlab-Runner از طریق Admin اجرا کنید و دستورات زیر را وارد کنید:

  1. gitlab-runner.exe register
  2. https://gitlab.com/
  3. Your token
  4. DotNetRunner
  5. dotnet
  6. shell

در قسمت اول دستور رجیستر کردن را وارد میکنید.

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

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

در مرحله چهارم اسم Runner مورد نظر را وارد میکنید که در اینجا DotNetRunner وارد شده است.

در مرحله پنج تگ های مورد نظر خودرا وارد میکنید، برای این Runner که کار برنامه های دات نتی را انجام میدهد dotnet را نوشته ایم. (این نام ها انتخابی هستند)

در نهایت در قسمت شش باید یک executer معرفی کنید که مشخص شود دستورات شما در چه محیطی اجرا شود و چون Runner ما بر روی ویندوز قرار دارد و از دستورات powershell استفاده میکنیم گزینه shell را وارد کرده ایم.

در ادامه باید دستورات زیر را برای نصب و اجرای Runner اجرا کنیم با استفاده از CMD

gitlab-runner.exe install
gitlab-runner.exe start

 سپس باید در فایل config.toml در پوشه Gitlab-Runner، مقدار pwsh را به powershell تغییر دهید برای پارامتر shell:

[[runners]]
  name = "DotNetRunner"
  url = https://gitlab.com/
  token = "YourToken"
  executor = "shell"
  shell = "powershell"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]

اکنون Runner مورد نظر ایجاد شده است و میتوانید در صفحه مربوط به Runner ها آن را مشاهده کنید. با انجام این کار تمامی دستورات نوشته درون فایل .gitlab-ci.yml با استفاده از این Runner و از طریق powershell اجرا میشود.

اکنون اگر تغییری بر روی Repository اعمال شود، به صورت خودکار عملیات پابلیش آن بر روی IIS انجام میشود.

فایل yml ایجاد شده نهایی با اضافه شدن تغییرات جدید به صورت زیر است:

variables:
  publish_path: '.\DotNetDocs\publish\$CI_PIPELINE_ID\'
  published_data_path: '.\DotNetDocs\publish\$CI_PIPELINE_ID\*.*'
  application_pool_name: 'FarhadZamani'
  iis_worker_path: 'C:\Publish\FarhadZamani'
stages:
  - build
  - tests
  - deploy
  
build:
  stage: build
  script:
    - dotnet restore
    - dotnet build --no-restore
    - dotnet publish .\DotNetDocs\DotNetDocs.csproj -c Release -o $publish_path
  artifacts:
    paths:
      - $publish_path
    expire_in: '1 hrs'
  tags:
    - dotnet  
  only:
    - develop
    - master

unit-test:
  stage: tests
  script:
    - dotnet test --no-build --verbosity normal
  tags:
    - dotnet
  only:
    - master
    - develop
  needs: [build]

production:
  stage: deploy
  script:
    - |
      if((Get-WebSiteState -Name $application_pool_name).Value -ne 'Stopped'){
      Write-Output ('Stopping WebSite: {0}' -f $application_pool_name)
      Stop-WebSite -Name $application_pool_name
      }
    - |
      if((Get-WebAppPoolState -Name $application_pool_name).Value -ne 'Stopped'){
      Write-Output ('Stopping Application Pool: {0}' -f $application_pool_name)
      Stop-WebAppPool -Name $application_pool_name
      }
    - "Copy-Item $published_data_path -Destination $iis_worker_path -Force"
    - "Start-Sleep -s 5"
    - |
      if((Get-WebAppPoolState -Name $application_pool_name).Value -ne 'Started'){
      Write-Output ('Starting Application Pool: {0}' -f $application_pool_name)
      Start-WebAppPool -Name $application_pool_name
      }
    - |
      if((Get-WebSiteState -Name $application_pool_name).Value -ne 'Started'){
      Write-Output ('Starting WebSite: {0}' -f $application_pool_name)
      Start-WebSite -Name $application_pool_name
      }
  only:
    - master
  tags:
    - dotnet
  needs: [staging,build]
  when: manual
  dependencies:
    - build

فایل yml ارسال شده شامل سه stage میباشد.

1 – build: در این مرحله  ابتدا دستور dotnet restore اجرا میشود برای restore کردن پکیج ها. سپس دستور dotnet build اجرا میشود بدون restore کردن. سپس دستور پابلیش برنامه اجرا میشود و مسیر آن درون متغییر $publish_path مشخص شده است.

این عمل روی برنچ های master و develop اجرا میشود، یعنی هربار که عملیاتی روی هرکدام از برنچ ها انجام شود، این قسمت build اجرا میشود.

only:
  - develop
  - master

بعد از اینکه عملیات پابلیش تمام شد، فایل های پابلیش شده به صورت artifact آپلود میشوند.

همچنین میتوانید یک زمان برای expire شدن Artifact مشخص کنید، که در این قسمت روی 1 ساعت تنظیم شده است:

artifacts:
  paths:
    - $publish_path
  expire_in: '1 hrs'

در کد بالا تمامی فایل های پابلیش شده به صورت یک artifact ایجاد میشوند.

اگر artifact ایجاد شده، آخرین artifact باشد تا زمانی که artifact جدیدی ایجاد نشود، expire نمیشود حتی اگر زمان آن فرا رسیده باشد. با این کار همیشه یک artifact در دسترس میباشد.

2 – tests: در این مرحله تست های نوشته را برای بررسی درست بودن آنها اجرا میکنیم با دستور dotnet test.  با دستور زیر مشخص کرده ایم که job مربوط به unit-test در stage مربوط به tests قرار دارد:

unit-test:
  stage: tests

پیشنیاز این مرحله، مرحله build میباشد که با استفاده از دستور

needs: [build]

مشخص شده است.

3 – deploy: در این stage پابلیش پروژه بر روی سرور production به صورت دستی با یک کلیک انجام میشود. اسکریپت نوشته شده در قسمت production کارهای زیر را انجام میدهد:

  • ابتدا بررسی میکند که WebSite مورد نظر Stopped نباشد و وقتی که Stopped نباشد ابتدا آن را Stop میکند و به مرحله بعد میرود.
  • سپس بررسی Stop بودن AppPool را انجام میدهد که اگر Stopped نباشد، آنرا Stop میکند.
  • فایل های پابلیش شده در مرحله build را از طریق artifact ها دانلود میکند و به مسیری که پروژه بر روی IIS قرار دارد کپی میکند.
  • AppPool را در صورتی که Started نباشد Start میکند.
  • در نهایت WebSite را در صورت Started نباشد، آنرا Start میکند.

به دلیل اینکه ما از artifact ساخته شده در مرحله build استفاده میکنیم، باید dependency مربوط به آن را وارد کنیم:

dependencies:
  - build

با این دستور میتوانیم به artifact ساخته شده در مرحله build دسترسی داشته باشیم.

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

needs: [unit-test,build]

به دلیل dependency داشتن با مرحله  build، باید مرحله build را هم درون نیازمندی بنویسیم در غیر این صورت فقط به unit-test نیاز داریم.

این job فقط زمانی اجرا میشود که بر روی برنچ مستر تغییری ایجاد شود که با دستور زیر مشخص شده است:

only:
  - master

در جاب مربوط به production با دستور زیر مشخص کرده ایم که job باید به صورت دستی اجرا شود:

when: manual

:)

نظرات