Bunchee
Back to Blog

Deploy Saleor Production แบบ ไม่ใช้ Docker และวิธีติดตั้ง Plugins

บทความนี้พูดถึงการ Deploy Saleor Production บน Ubuntu แบบไม่ใช้ Docker จะมีวิธีการ Deploy อย่างไร พร้อมอธิบายวิธีติดตั้ง Plugins

Deploy Saleor Production แบบ ไม่ใช้ Docker และวิธีติดตั้ง Plugins

ในบทความที่แล้ว ได้พูดถึงการ Deploy Saleor Production บน Ubuntu แบบใช้ Docker ไปแล้ว แต่ถ้าเราไม่อยากใช้ Docker จะมีวิธีการ Deploy อย่างไร แล้วถ้าอยากติดตั้ง Plugins บน Saleor จะมีวิธีติดตั้งอย่างไร

สิ่งที่ต้องเตรียมก่อน

สร้างบัญชีผู้ใช้บน Linode

ถ้ายังไม่มีบัญชีผู้ใช้งานบน Linode ให้กลับไปสร้างบัญชีผู้ใช้งานก่อนครับ โดยดูวิธีการจาก บทความ นี้

สร้างฐานข้อมูล PostgreSQL

สำหรับคนที่อาจใช้ Server ที่อื่น หรือใช้ Server ส่วนตัว และยังไม่ได้ติดตั้ง PostgreSQL ให้รันคำสั่งต่อไปนี้ครับ

sudo apt update && sudo apt upgrade -y
sudo apt install postgresql postgresql-contrib -y

ตรวจดูว่า PostgreSQL ทำงานหรือยัง

sudo systemctl status postgresql

เข้าใช้งาน PostgreSQL โดยเริ่มจากเปลี่ยน User เป็น postgres

sudo su - postgres

ลองพิมพ์คำสั่ง

psql

ในทางปฏิบัติเราควรสร้างบัญชีใหม่ขึ้นมาทำงาน ด้วยคำสั่ง

CREATE USER your-user WITH PASSWORD 'your-password';

ในที่นี้ขอตั้งชื่อผู้ใช้ว่า saleoradmin และรหัสผ่านเป็น saleor12345 จะได้คำสั่งดังนี้

CREATE USER saleoradmin WITH PASSWORD 'saleor12345';

ถ้าทำสำเร็จระบบจะตอบกลับมาว่า

CREATE ROLE

และให้สร้างฐานข้อมูลใหม่เพื่อใช้กับ Saleor API

CREATE DATABASE your-db-name OWNER your-user

ในที่นี้จะเป็น

CREATE DATABASE saleordb OWNER saleoradmin

ถ้าติดปัญหาสร้าง User หรือฐานข้อมูลด้วยคำสั่งข้างบนไม่ได้ ให้พิมพ์ \q แล้ว Enter เพื่อออกไปที่ User postgres แล้วพิมพ์คำสั่ง

1. สร้าง User

createuser --interactive --pwprompt

2. สร้างฐานข้อมูล

createdb saleordb

จะได้ผลลัพธ์ดังนี้

postgres-# \q
postgres@localhost:~$ createuser --interactive --pwprompt
Enter name of role to add: your-username
Enter password for new role: 
Enter it again: 
Shall the new role be a superuser? (y/n) 
Please answer "y" or "n".
Shall the new role be a superuser? (y/n) y
...
postgres@localhost:~$ createdb your-db-name
postgres@localhost:~$ psql
psql (14.10 (Ubuntu 14.10-0ubuntu0.22.04.1))
Type "help" for help.

เมื่อได้ บัญชีผู้ใช้ใหม่ และ ฐานข้อมูลใหม่แล้ว ลองเข้าไปใช้ดูครับ โดย

Enable บัญชีชื่อผู้ใช้ใหม่ โดยออกไปที่ Ubuntu ก่อน แล้วจึงเพิ่ม User

sudo su -
adduser saleoradmin

และตามด้วยคำสั่ง

sudo su - saleoradmin
psql -d saleordb

ลองทดสอบด้วยคำสั่งง่ายๆ โดยขอดูลิสต์รายชื่อฐานข้อมูลทั้งหมด

\l

deploy saleor


Saleor ใช้ Poetry แทน PIP

ให้ดาวน์โหลด Repo Saleor Core จาก releases

คลิ๊กขวาตรง Source code (zip) แล้วเลือก Copy Link Address แล้วดาวน์โหลดด้วย wget ก็ได้ครับ ดูรายละเอียดได้ที่ บทความ นี้

เมื่อเราแตกไฟล์ออกมาแล้ว เปลี่ยนชื่อโฟลเดอร์เป็น saleor-api แล้วเข้าไปที่โฟลเดอร์นี้

ให้สังเกตว่า เราไม่พบไฟล์ requirements.txt แล้วเราจะติดตั้ง Python Packages อย่างไรล่ะ

เนื่องจาก Saleor ใช้ Poetry แทน PIP ดังนั้นเราจะไม่สามารถมองเห็นไฟล์ requirements.txt ใน repo แต่จะเห็นไฟล์ pyproject.toml แทนครับ

ติดตั้ง Saleor Packages

ถ้าต้องการ Deploy Saleor ใน Mode Production และต้องการติดตั้ง Plugins เช่น Stripe, Slack, MailChimp, EmailServer เราต้องติดตั้ง Packages ของ Saleor ให้ครบถ้วน ให้ดำเนินการตามขั้นตอนต่อไปนี้ครับ

1. สร้าง Virtural Environment

python -m venv env
source env/bin/activate

2. เนื่องจาก psycopg2 มีปัญหา เราจะแก้ด้วยการติดตั้ง psycopg2-binary แทน พร้อมทั้งติดตั้ง django-environ เพื่อจัดการไฟล์ .env ครับ ให้ติดตั้ง Python Packages ด้วยคำสั่งเหล่านี้

poetry add psycopg2-binary
poetry add django-environ
poetry install

3. เปลี่ยน Django Default Port จาก 8000 เป็น 800X ในที่นี้จะเปลี่ยนเป็น Port 8009 โดยเพิ่มโค้ด 2 บรรทัดนี้เข้าไปครับ

from django.core.management.commands.runserver import Command as runserver
runserver.default_port = "8009"

4. สร้างไฟล์ .env ที่โฟลเดอร์ saleor-api ใส่ข้อมูลตามนี้ครับ

สำหรับ SECRET_KEY ให้ไปที่ randomkeygen แล้วเลือก SECRET KEY ตามชอบครับ

*https://xxx-xxx-xxxx.ngrok-free.app สำหรับ ngrok ถ้าใครไม่ได้ใช้งาน ngrok ก็ไม่ต้องใส่ก็ได้ครับ

*STOREFRONT_URL="https://www.your-domain.com/" หรือ

*STOREFRONT_URL="https://saleor-shop.your-domain.com/"

DEBUG=False
SECRET_KEY="your-secret-key"
ALLOWED_GRAPHQL_ORIGINS="https://saleor-dashboard.your-domain.com, https://www.your-domain.com, https://saleor-shop.your-domain.com, https://stripe.saleor.app, https://saleor-smtp.your-domain.com, https://saleor-crm.your-domain.com, https://saleor-slack.your-domain.com, https://xxx-xxx-xxxx.ngrok-free.app"
ALLOWED_HOSTS=saleor-dashboard.your-domain.com, www.your-domain.com, .your-domain.com, 127.0.0.1, saleor-api.your-domain.com, stripe.saleor.app, xxx-xxx-xxxx.ngrok-free.app
ALLOWED_CLIENT_HOSTS="localhost, 127.0.0.1, .your-domain.com, saleor-dashboard.your-domain.com, www.your-domain.com, stripe.saleor.app, saleor-slack.your-domain.com, saleor-crm.your-domain.com, saleor-smtp.your-domain.com, xxx-xxx-xxxx.ngrok-free.app"
PUBLIC_URL="https://saleor-api.your-domain.com/"
DASHBOARD_URL="https://saleor-dashboard.your-domain.com/dashboard/"
STOREFRONT_URL="https://www.your-domain.com/"

DATABASE_URL="postgres://saleoradmin:saleor12345@localhost:5432/saleordb"

DEFAULT_COUNTRY=TH
DEFAULT_CURRENCY=THB

AWS_ACCESS_KEY_ID=your-AWS_ACCESS_KEY_ID
AWS_S3_REGION_NAME='ap-southeast-1'
AWS_DEFAULT_ACL="public-read"
AWS_MEDIA_BUCKET_NAME="your-AWS_MEDIA_BUCKET_NAME"
AWS_STORAGE_BUCKET_NAME=AWS_MEDIA_BUCKET_NAME
AWS_MEDIA_CUSTOM_DOMAIN='your-AWS_MEDIA_BUCKET_NAME.s3.ap-southeast-1.amazonaws.com'
AWS_QUERYSTRING_AUTH=False
AWS_SECRET_ACCESS_KEY=your-AWS_SECRET_ACCESS_KEY
AWS_STATIC_CUSTOM_DOMAIN=AWS_MEDIA_CUSTOM_DOMAIN
AWS_S3_FILE_OVERWRITE=True
AWS_S3_CUSTOM_DOMAIN=AWS_MEDIA_CUSTOM_DOMAIN
STATIC_URL='https://your-AWS_MEDIA_BUCKET_NAME.s3.ap-southeast-1.amazonaws.com/static/'
MEDIA_URL='https://your-AWS_MEDIA_BUCKET_NAME.s3.ap-southeast-1.amazonaws.com/media/'

5. เพิ่มโค้ดนี้เข้าไปในไฟล์ settings.py เพื่อ Enable django-environ

env = environ.Env(
    # set casting, default value
    DEBUG=(bool, False)
)
# Set the project base directory
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

พร้อมทั้งเปลี่ยนโค้ด SECRET_KEY และ DEBUG ดังนี้ โดยแก้จาก

SECRET_KEY = os.environ.get("SECRET_KEY")
DEBUG = get_bool_from_env("DEBUG", True)

เป็น

SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG')

6. ระบุฐานข้อมูล ที่เราเพิ่งสร้างให้กับ Saleor ผ่านไฟล์ settings.py

เข้าไปที่โฟลเดอร์ saleor-api/saleor ค้นหา DATABASES

DATABASES = {
    DATABASE_CONNECTION_DEFAULT_NAME: dj_database_url.config(
        # default="postgres://saleoradmin:saleor12345@localhost:5432/saleordb",
        default=env("DATABASE_URL"),
        conn_max_age=DB_CONN_MAX_AGE,
    ),
    DATABASE_CONNECTION_REPLICA_NAME: dj_database_url.config(
        # default="postgres://saleoradmin:saleor12345@localhost:5432/saleordb",
        default=env("DATABASE_URL"),
        conn_max_age=DB_CONN_MAX_AGE,
    ),
}

7. ที่ไฟล์ settings.py เช่นเดียวกัน ให้หาคำว่า RSA_PRIVATE_KEY

เปลี่ยน

RSA_PRIVATE_KEY = os.environ.get("RSA_PRIVATE_KEY", None)

เป็น

RSA_PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwq4CiHZeAfLY4Bj25XwRavq9M2Zz2j1yvTwLuhoCK7UVR1RG
MWnHTXncNOL6uUXZdvQJpUrix3oMVcWW2l+7HDzw/crdCpIMUsirAU1biddeG6UI
LKIlPw1NKM+SMKo5SIhiZTw0FklP1mh20voGCbs1rpmRtk5Mvu45t1/VMciCkot2
1ldSZ9QmG4tnSEOZT3Flv/zreF01vq70qjQ/xBq4dDUnw3JoeJEmLGjudM2fG/+I
qwH3cbH8Ems/EbcXFaYXrm1VTJ1k5rQnH0tcTTnIzcbX3+t+xs0FFGSfLzfv6y9C
sl2uIblJVpUPTJBVqXgkME1MB58xvP4DS/M8xQIDAQABAoIBAAQ5La1jsfzlVY6h
BAQs+ZzCRUX+8D75C8ruqUt3gnoLwuMqAR7TzmLQJLaSAQHxcbsKpsXq9roAnBFl
SLVCk+bULJ843iw7SGCoYUtVMAnwveYoIaIEP34bbgPXYvLC0pzP9qB/GpssKnr6
h69ihKyD3vGDe92CW9hdhyuC/PdIOlfOZ3Xf2C+PB2hiR0Z6oG05Ka+TKg7RXsUF
bse2nSfSzb0KTajKwlWIHkaJTK9MvZ+tCLntPAKp1JGUzQdgF8viUkueXI2CdR8L
SJZqt806Avu9Tf9MjRtXu4MAV2CgVsFeY7ImzXG5bLeUFdmy7tQWIfJrnqK4PASv
19obCFkCgYEA61n2oqJPxPPi+9Wks1+11MmwDyA8KUtkL5hMye+DBVhM4xW4RgO7
xt/zir++CBECJhKq88ZBU0ndJB7tEQEuTcKCV1GTPfpRD7Myh7pyKOA3st1O4Uxg
S76xFeim2FTYbUKg/XCS3pvH4Q/JUBwi/qAHdsalMbcsqq/SC8uR+7kCgYEA08KN
fQyWJik5PXcRqdLfzUhLMMTIQIktIrV+m2uewCv67WPh0H2w78eerVuMYpF0EUrA
80CAAtcl9rZXT+3Z09FAFBnuc7chOt3+vZAZCalzqmX+6555QV2TkK/6B1H73lyL
YMD1sVA4AFYi9B7dgyXlh7nFcURcm4CdcUaxB20CgYALO6gB6y1TgTB8RJ4v0Ymk
Nlwo3KkCb47AlsxTdxMR1j0VOZwp+1OjEl1VagFv8R/hIVL3f6buir/7UV6PSTck
jvwZntMgSipETZFD2SpJuSnvZ5C0QCj4dImPOiN8f9A0ptF4Rz87UMQhgddh83XY
IVs52BFaZhvDqdCkr3qwQQKBgFX1VI/dSxnkg/rCWaYxFm3zGaqLRqqDxJGhUOpw
Djn94Fb6w5BpZSiARJYkYmEkoBPg32Ae35fHk/6I1/p3F4QXHcbLG/NW9CM8OArk
8nTslyolSwyEAL6a6KrD9F+CVRZXRLCaw2Edqg3g6UFlQg/Zk0m8DDzFPj5VQBPa
WUQlAoGBANCAAyqQe8LuruDZ22kJFg3qr55qeFUDCMrmXz6R+4qRnCHqQXCnfZEX
T4Xlzsm2UqVzqtTT32KMVoIuL1frRpVHVFDqoq1hXOs93CNq10aToweM0opwWBa4
yveNltcElLVK+n7ZjtL/ruF9EbEYYKTinLKhvqOowfmjnVZ5L1YS
-----END RSA PRIVATE KEY-----"""

8. เสร็จแล้วรันคำสั่ง

poetry run python manage.py migrate

9. ติดตั้ง JavaScript Dependencies

pnpm i

10. สร้าง superuser

poetry run python manage.py createsuperuser

Gunicorn และ Uvicorn

Saleor ใช้ Uvicorn ทำงานร่วมกับ Gunicorn ดังนี้ครับ

ให้สร้างไฟล์ gunicorn.socket ด้วยคำสั่ง

sudo nano /etc/systemd/system/gunicorn.socket

แล้วคัดลอกโค้ดนี้เข้าไปครับ

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

ต่อไปสร้างไฟล์ gunicorn.service ด้วยคำสั่ง

sudo nano /etc/systemd/system/gunicorn.service

คัดลอกโค้ดนี้เข้าไปครับ

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=root
Group=www-data
WorkingDirectory=/root/saleor-api
Environment="PATH=/root/saleor-api/env/bin"
ExecStart=/root/saleor-api/env/bin/gunicorn \
          --access-logfile - \
          --workers 4 \
          --bind unix:/run/gunicorn.sock \
          -k uvicorn.workers.UvicornWorker \
 saleor.asgi:application

[Install]
WantedBy=multi-user.target

Nginx (Reverse Proxy)

สร้างไฟล์ saleor-api ใน Nginx ด้วยคำสั่ง

sudo nano /etc/nginx/sites-available/saleor-api

คัดลอกโค้ดนี้เข้าไปครับ

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /root/saleor-api;
    }

    location /media/ {
        root /root/saleor-api;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

}

ไฟล์ที่เราต้องใช้มีทั้งหมดแค่นี้ครับ

  • gunicorn.socket

  • gunicorn.service

  • saleor-api

เมื่อมีข้อมูลครบทั้ง 3 ไฟล์ ให้รันคำสั่งทั้งหมดนี้พร้อมกันเลยครับ

sudo systemctl daemon-reload
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo ln -s /etc/nginx/sites-available/saleor-api /etc/nginx/sites-enabled/
sudo systemctl reload nginx

วิธีแก้ปัญหาเบื้องต้น

ถ้า Gunicorn มีปัญหา

sudo journalctl -u gunicorn

ถ้า Nginx มีปัญหา

sudo nginx -t
sudo ufw allow 'Nginx Full'

Enable HTTPS สำหรับ Saleor Production

ให้รันคำสั่ง

sudo certbot --nginx

สามารถดูรายละเอียดได้ที่ บทความ นี้ครับ

สรุปการ Deploy Saleor Production

1. แก้ไขไฟล์ settings.py

2. เพิ่มไฟล์ .env

3. สร้างไฟล์สำหรับ Gunicorn & Uvicorn

4. และไฟล์สำหรับ Nginx

Saleor Plugins

การติดตั้ง Plugins มีข้อสังเกต ถ้าไม่ Enable Production หรือ Debug=False จะไม่สามารถติดตั้งได้ แม้ติดตั้งได้ แต่ทำงานไม่ได้ เพราะมันจะมองหา localhost:8000 ซึ่งถ้าให้ถูกต้องมันต้องมองหา saleor-api.your-domain.com/graphql/

Stripe Payment Plugins

เมื่อเราเปิดร้าน E-Commerce เราจะต้องมีระบบรับเงินจากลูกค้า ซึ่ง Saloer รองรับทั้ง

  • Adyen และ

  • Stripe

ดังนั้นเพื่อให้ระบบหลังบ้านของเราทำงานได้ครบวงจร เราจะติดตั้ง Stripe กันครับ

ไปที่ https://stripe.saleor.app

แล้วใส่ Saleor URL เข้าไป เช่น https://saleor-api.your-domain.com

install Stripe Plugins

หรือเราสามารถติดตั้งผ่าน Graphql ก็ได้ครับ ให้ไปที่ saleor-api.your-domain.com/graphql/

ใส่โค้ดนี้เข้าไปใน Playground ครับ

mutation stripeInstall {
  appInstall(
    input: {appName: "Stripe App", manifestUrl: "https://stripe.saleor.app/api/manifest", permissions: [HANDLE_PAYMENTS]}
  ) {
    appInstallation {
      id
      status
      appName
      manifestUrl
    }
    errors {
      field
      message
      code
      permissions
    }
  }
}

โดยต้องมี Token ด้วยครับ ตามรูป

Stripe Plugins

วิธีหา Token

จำได้มั๊ยครับตอนเราใช้คำสั่ง createsuperuser

poetry run python manage.py createsuperuser

เพื่อสร้าง Super User ให้เราใช้ข้อมูลตรงนั้นแหละครับ มาขอ Token

ไปที่ Graphql Playground เปิดแท็บใหม่ครับ

ใส่โค้ดนี้เข้าไปครับ

mutation createToken {
  tokenCreate(email: "your-email", password: "your-password") {
    token
    refreshToken
    errors {
      field
      message
    }
  }
}

จะได้ Token ตามรูปข้างล่างครับ ให้คัดลอกข้อมูลทั้งหมดที่อยู่ระหว่างเครื่องหมายคำพูด หรือ "" แล้วนำไปใส่ใน Headers ตอนเราติดตั้ง Stripe App

Deploy Saleor Production Plugins


วิธีแก้ปัญหา Saleor Plugins

1. App permissions

Pain Point: เนื่องจาก Saleor Plugins เป็น App แยกออกมาจาก Saloer Core (API) ตอนติดตั้งแต่ละ App มันจะไปลงทะเบียน /api/register App กับ Saleor Core (API) และเก็บ JWT Auth ไว้บน APL โดยอ้างอิง App ID ทีนี้ถ้าเราลง App ตัวอื่นซึ่งใช้ App ID เดียวกันทำให้ JWT Auth ของ App เก่าโดนเขียนทับ ทำให้เมื่อลง App ใหม่ ส่งผลให้ App ที่เคยลงไว้ก่อนทำงานไม่ได้เพราะ JWT Auth ถูกเขียนทับไปแล้ว ซึ่งปัญหานี้เกิดกับกรณีที่เราใช้ APL เป็น Upstash

Solution: ให้แก้ปัญหาเบื้องต้นโดยการเปิดใช้ Upstash หลาย Accounts อย่าใช้ Upstash ซ้ำกัน หรือ แก้ App ID ให้แตกต่างกัน อ่านเพิ่มเติมจาก ลิงค์

2. วิธีทดสอบ AWS SES (SMTP)

ตอนสร้าง AWS SES (SMTP) ตรวจสอบให้แน่ใจว่าเราสร้างไว้ที่ สิงคโปร์ (ap-southeast-1) แล้ว ให้รันคำสั่ง

openssl s_client -crlf -quiet -starttls smtp -connect email-smtp.ap-southeast-1.amazonaws.com:587

ถ้าขึ้น Status OK ก็แสดงว่า AWS SES (SMTP) Endpoint ทำงานเป็นปกติ