Let's Go Further استقرار و میزبانی › اجرای API به عنوان یک سرویس پس‌زمینه
قبلی · فهرست مطالب · بعدی
فصل ۲۰.۴.

اجرای API به عنوان یک سرویس پس‌زمینه

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

ابزارهای مختلفی وجود دارد که می‌توانیم برای این کار استفاده کنیم، اما در این کتاب از systemd استفاده خواهیم کرد — مجموعه‌ای از ابزارها برای مدیریت سرویس‌ها که با Ubuntu (و بسیاری از توزیع‌های دیگر Linux) عرضه می‌شود.

برای اجرای برنامه API ما به عنوان یک سرویس پس‌زمینه، اولین کاری که باید انجام دهیم ایجاد یک فایل واحد است که به systemd می‌گوید چگونه و چه زمانی سرویس را اجرا کند.

اگر در حال دنبال کردن هستید، به پنجره ترمینال در ماشین محلی خود بازگردید و یک فایل جدید remote/production/api.service در پروژه خود ایجاد کنید:

$ mkdir remote/production
$ touch remote/production/api.service

و سپس کد زیر را اضافه کنید:

فایل: remote/production/api.service
[Unit]
# Description is a human-readable name for the service.
Description=Greenlight API service

# Wait until PostgreSQL is running and the network is "up" before starting the service.
After=postgresql.service
After=network-online.target
Wants=network-online.target

# Configure service start rate limiting. If the service is (re)started more than 5 times 
# in 600 seconds then don't permit it to start anymore.
StartLimitIntervalSec=600
StartLimitBurst=5

[Service]
# Execute the API binary as the greenlight user, loading the environment variables from
# /etc/environment and using the working directory /home/greenlight.
Type=exec
User=greenlight
Group=greenlight
EnvironmentFile=/etc/environment
WorkingDirectory=/home/greenlight
ExecStart=/home/greenlight/api -port=4000 -db-dsn=${GREENLIGHT_DB_DSN} -env=production

# Automatically restart the service after a 5-second wait if it exits with a non-zero 
# exit code. If it restarts more than 5 times in 600 seconds, then the rate limit we
# configured above will be hit and it won't be restarted anymore.
Restart=on-failure
RestartSec=5

[Install]
# Start the service automatically at boot time (the 'multi-user.target' describes a boot
# state when the system will accept logins).
WantedBy=multi-user.target

اکنون که فایل واحد را تنظیم کرده‌ایم، مرحله بعدی نصب این فایل واحد روی droplet و راه‌اندازی سرویس است. در اصل باید سه کار انجام دهیم:

  1. برای نصب فایل واحد، باید آن را به پوشه /etc/systemd/system/ در droplet خود کپی کنیم. از آنجا که این پوشه توسط کاربر root در droplet ما مالکیت شده است، باید این کار را به دو مرحله تقسیم کنیم: اول فایل واحد را به پوشه خانه کاربر greenlight کپی کنیم و دوم از دستور sudo mv برای انتقال آن به مکان نهایی استفاده کنیم.
  2. سپس باید دستور systemctl enable api را روی droplet خود اجرا کنیم تا systemd از فایل واحد جدید مطلع شود و سرویس را به طور خودکار هنگام ریبوت شدن droplet فعال کند.
  3. در نهایت، باید systemctl restart api را اجرا کنیم تا سرویس را شروع (یا ری‌استارت) کنیم.

همه این سه کار باید با sudo اجرا شوند.

بیایید قاعده makefile production/deploy/api را به روز کنیم تا این کارها را برای ما خودکار کند. به این صورت:

فایل: Makefile
...

# ==================================================================================== #
# PRODUCTION
# ==================================================================================== #

production_host_ip = '161.35.71.158'

...

## production/deploy/api: deploy the api to production
.PHONY: production/deploy/api
production/deploy/api:
	rsync -P ./bin/linux_amd64/api greenlight@${production_host_ip}:~
	rsync -rP --delete ./migrations greenlight@${production_host_ip}:~
	rsync -P ./remote/production/api.service greenlight@${production_host_ip}:~
	ssh -t greenlight@${production_host_ip} '\
		migrate -path ~/migrations -database $$GREENLIGHT_DB_DSN up \
		&& sudo mv ~/api.service /etc/systemd/system/ \
		&& sudo systemctl enable api \
		&& sudo systemctl restart api \
	'

فایل makefile را ذخیره کنید، سپس قاعده production/deploy/api را دوباره اجرا کنید. هنگام اجرا باید رمز عبور کاربر greenlight را وارد کنید و قاعده باید بدون خطا تکمیل شود. خروجی که می‌بینید باید شبیه به این باشد:

$ make production/deploy/api 
rsync -P ./bin/linux_amd64/api greenlight@"161.35.71.158":~
api
      7,700,480 100%    2.39GB/s    0:00:00 (xfr#1, to-chk=0/1)
rsync -rP --delete ./migrations greenlight@"161.35.71.158":~
sending incremental file list
migrations/000001_create_movies_table.down.sql
             28 100%    0.00kB/s    0:00:00 (xfr#1, to-chk=11/13)
migrations/000001_create_movies_table.up.sql
            286 100%  279.30kB/s    0:00:00 (xfr#2, to-chk=10/13)
migrations/000002_add_movies_check_constraints.down.sql
            198 100%  193.36kB/s    0:00:00 (xfr#3, to-chk=9/13)
migrations/000002_add_movies_check_constraints.up.sql
            289 100%  282.23kB/s    0:00:00 (xfr#4, to-chk=8/13)
migrations/000003_add_movies_indexes.down.sql
             78 100%   76.17kB/s    0:00:00 (xfr#5, to-chk=7/13)
migrations/000003_add_movies_indexes.up.sql
            170 100%  166.02kB/s    0:00:00 (xfr#6, to-chk=6/13)
migrations/000004_create_users_table.down.sql
             27 100%   26.37kB/s    0:00:00 (xfr#7, to-chk=5/13)
migrations/000004_create_users_table.up.sql
            294 100%  287.11kB/s    0:00:00 (xfr#8, to-chk=4/13)
migrations/000005_create_tokens_table.down.sql
             28 100%   27.34kB/s    0:00:00 (xfr#9, to-chk=3/13)
migrations/000005_create_tokens_table.up.sql
            203 100%  198.24kB/s    0:00:00 (xfr#10, to-chk=2/13)
migrations/000006_add_permissions.down.sql
             73 100%   71.29kB/s    0:00:00 (xfr#11, to-chk=1/13)
migrations/000006_add_permissions.up.sql
            452 100%  441.41kB/s    0:00:00 (xfr#12, to-chk=0/13)
rsync -P ./remote/production/api.service greenlight@"161.35.71.158":~
api.service
          1,266 100%    0.00kB/s    0:00:00 (xfr#1, to-chk=0/1)
ssh -t greenlight@"161.35.71.158" '\
        migrate -path ~/migrations -database $GREENLIGHT_DB_DSN up \
        && sudo mv ~/api.service /etc/systemd/system/ \
        && sudo systemctl enable api \
        && sudo systemctl restart api \
'
no change
[sudo] password for greenlight: 
Created symlink /etc/systemd/system/multi-user.target.wants/api.service → /etc/systemd/system/api.service.
Connection to 161.35.71.158 closed.

در مرحله بعد، به droplet متصل شوید و وضعیت سرویس جدید api را با استفاده از دستور sudo systemctl status api بررسی کنید:

$ make production/connect 
greenlight@greenlight-production:~$ sudo systemctl status api
● api.service - Greenlight API service
     Loaded: loaded (/etc/systemd/system/api.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-02-23 17:28:18 CET; 1min 7s ago
   Main PID: 2717 (api)
      Tasks: 5 (limit: 512)
     Memory: 2.9M
        CPU: 10ms
     CGroup: /system.slice/api.service
             └─2717 /home/greenlight/api -port=4000 -db-dsn=postgres://greenlight:pa55word1234@localhost/greenlight -env=production

Feb 23 17:28:18 greenlight-production systemd[1]: Starting Greenlight API service...
Feb 23 17:28:18 greenlight-production systemd[1]: Started Greenlight API service.
Feb 23 17:28:18 greenlight-production api[2717]: time=2023-02-23T17:28:18.722+02:00 level=INFO msg="database connection pool established"
Feb 23 17:28:18 greenlight-production api[2717]: time=2023-02-23T17:28:18.722+02:00 level=INFO msg="starting server" ...

عالی! این تأیید می‌کند که سرویس api ما با موفقیت در پس‌زمینه در حال اجرا است و در مورد من، شناسه فرآیند (PID) 2717 را دارد.

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

greenlight@greenlight-production:~$ ps -U greenlight
   PID TTY          TIME CMD
   2717 ?        00:00:00 api
   2749 ?        00:00:00 systemd
   2750 ?        00:00:00 (sd-pam)
   2807 ?        00:00:00 sshd
   2808 pts/0    00:00:00 bash
   2859 pts/0    00:00:00 ps

این با هم همخوانی دارد — می‌توانیم فرآیند api را با شناسه فرآیند 2717 اینجا ببینیم، که تأیید می‌کند کاربر greenlight ما در حال اجرای فایل اجرایی api است.

در نهایت، از دیدگاه خارجی بررسی کنیم و دوباره درخواستی به endpoint healthcheck خود بفرستیم، چه در مرورگر و چه با curl. باید پاسخ موفقی مانند این دریافت کنید:

$ curl 161.35.71.158:4000/v1/healthcheck
{
    "status": "available",
    "system_info": {
        "environment": "production",
        "version": "1c9b6ff48ea800acdf4f5c6f5c3b62b98baf2bd7"
    }
}

ری‌استارت پس از ریبوت

با اجرای دستور systemctl enable api در فایل makefile پس از کپی کردن فایل واحد systemd، سرویس API ما باید به طور خودکار پس از ریبوت شدن droplet شروع شود.

اگر می‌خواهید، می‌توانید خودتان این را تأیید کنید. در حالی که از طریق SSH به droplet متصل هستید، آن را ریبوت کنید (مطمئن شوید که به droplet متصل هستید و به طور تصادفی ماشین محلی خود را ریبوت نکنید!).

greenlight@greenlight-production:~$ sudo reboot
greenlight@greenlight-production:~$ Connection to 161.35.71.158 closed by remote host.
Connection to 161.35.71.158 closed.
make: *** [Makefile:91: production/connect] Error 255

یک دقیقه صبر کنید تا ریبوت کامل شود و droplet دوباره آنلاین شود. سپس باید بتوانید دوباره متصل شوید و از systemctl status استفاده کنید تا بررسی کنید سرویس دوباره در حال اجرا است. به این صورت:

$ make production/connect 
greenlight@greenlight-production:~$ sudo systemctl status api.service 
[sudo] password for greenlight: 
● api.service - Greenlight API service
        Loaded: loaded (/etc/systemd/system/api.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-02-23 17:34:55 CET; 1min 33s ago
   Main PID: 895 (api)
      Tasks: 5 (limit: 512)
     Memory: 8.8M
        CPU: 15ms
     CGroup: /system.slice/api.service
             └─895 /home/greenlight/api -port=4000 -db-dsn=postgres://greenlight:pa55word1234@localhost/greenlight -env=production

Feb 23 17:28:18 greenlight-production systemd[1]: Starting Greenlight API service...
Feb 23 17:28:18 greenlight-production systemd[1]: Started Greenlight API service.
Feb 23 17:28:18 greenlight-production api[2717]: time=2023-02-23T17:28:18.722+02:00 level=INFO msg="database connection pool established"
Feb 23 17:28:18 greenlight-production api[2717]: time=2023-02-23T17:28:18.722+02:00 level=INFO msg="starting server" ...

غیرفعال کردن پورت 4000

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

greenlight@greenlight-production:~$ sudo ufw delete allow 4000/tcp
Rule deleted
Rule deleted (v6)
greenlight@greenlight-production:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22                         ALLOW       Anywhere                  
80/tcp                     ALLOW       Anywhere                  
443/tcp                    ALLOW       Anywhere                  
22 (v6)                    ALLOW       Anywhere (v6)             
80/tcp (v6)                ALLOW       Anywhere (v6)             
443/tcp (v6)               ALLOW       Anywhere (v6) 

اطلاعات تکمیلی

گوش دادن روی یک پورت محدود

اگر قصد ندارید برنامه خود را پشت یک reverse proxy اجرا کنید و می‌خواهید مستقیماً روی پورت 80 یا 443 درخواست‌ها را گوش دهید، باید فایل واحد خود را طوری تنظیم کنید که سرویس قابلیت CAP_NET_BIND_SERVICE داشته باشد (که به آن اجازه اتصال به یک پورت محدود را می‌دهد). به عنوان مثال:

[Unit]
Description=Greenlight API service

After=postgresql.service
After=network-online.target
Wants=network-online.target

StartLimitIntervalSec=600
StartLimitBurst=5

[Service]
Type=exec
User=greenlight
Group=greenlight
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
EnvironmentFile=/etc/environment
WorkingDirectory=/home/greenlight
ExecStart=/home/greenlight/api -port=80 -db-dsn=${GREENLIGHT_DB_DSN} -env=production

Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

مشاهده لاگ‌ها

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

$ sudo journalctl -u api
-- Logs begin at Sun 2021-04-18 14:55:45 EDT, end at Mon 2021-04-19 03:39:55 EDT. --
Apr 19 03:24:35 greenlight-production systemd[1]: Starting Greenlight API service...
Apr 19 03:24:35 greenlight-production systemd[1]: Started Greenlight API service.
Apr 19 03:24:35 greenlight-production api[6997]: time=2023-02-23T17:28:18.722+02:00 level=INFO msg="database connection pool established"
Apr 19 03:24:35 greenlight-production api[6997]: time=2023-02-23T17:28:18.722+02:00 level=INFO msg="starting server" ...

دستور journalctl واقعاً قدرتمند است و طیف گسترده‌ای از پارامترها را ارائه می‌دهد که می‌توانید از آنها برای فیلتر کردن پیام‌های لاگ و سفارشی کردن قالب‌بندی استفاده کنید. این مقاله مقدمه عالی برای جزئیات journalctl ارائه می‌دهد و ارزش خواندن دارد.

پیکربندی ارائه‌دهنده SMTP

فایل واحد ما در حال حاضر برای شروع API ما با دستور زیر تنظیم شده است:

ExecStart=/home/greenlight/api -port=4000 -db-dsn=${GREENLIGHT_DB_DSN} -env=production

مهم است که به یاد داشته باشید که به جز پرچم‌های port، db-dsn و env که اینجا مشخص می‌کنیم، برنامه ما همچنان از مقادیر پیش‌فرض برای تنظیمات دیگر استفاده می‌کند که در فایل cmd/api/main.go کدنویسی شده است — از جمله اعتبارنامه‌های SMTP برای صندوق Mailtrap شما. در شرایط عادی، باید اعتبارنامه‌های SMTP تولیدی خود را به عنوان بخشی از این دستور در فایل واحد تنظیم کنید.

گزینه‌های اضافی فایل واحد

فایل‌های واحد Systemd طیف گسترده‌ای از گزینه‌های پیکربندی را ارائه می‌دهند و ما فقط سطح را در این فصل خراش داده‌ایم. برای اطلاعات بیشتر، مقاله فهم واحدها و فایل‌های واحد Systemd یک مرور واقعاً خوب است و می‌توانید اطلاعات جامع (هرچند متراکم) را در صفحات راهنما پیدا کنید.

همچنین این gist وجود دارد که گزینه‌های امنیتی مختلفی را که می‌توانید برای تقویت سرویس خود استفاده کنید، توضیح می‌دهد.