Drupal یک سیستم مدیریت محتوا (CMS) است که به زبان PHP نوشته شده و تحت مجوز عمومی منبع آزاد GNU توزیع می شود. مردم و سازمانهای مختلف در سراسر جهان از Drupal برای ایجاد سایتهای دولتی ، وبلاگ های شخصی ، کسب و کارها و موارد دیگر استفاده می کنند. آنچه Drupal را از سایر چارچوبهای CMS منحصر به فرد می کند ، جامعه در حال رشد آن و مجموعه ای از ویژگی هایی است که شامل فرآیندهای ایمن ، عملکرد قابل اعتماد ، مدولاریتی (پیمانه ای بودن) و انعطاف پذیری در انطباق است.
Drupal نیاز به نصب پشته LAMP (Linux ، Apache ، MySQLیا PHP) یا پشته LEMP (Linux، Nginx ، MySQL و PHP) دارد ، اما نصب تک تک مؤلفه ها یک کار زمان بر است. ما می توانیم از ابزارهایی مانند Docker و Docker Compose برای ساده کردن روند نصب Drupal استفاده کنیم. در این آموزش از تصاویر Docker برای نصب مولفه های جداگانه در کانتینر Docker استفاده خواهد شد. با استفاده از Docker Compose می توان چندین کانتینر را برای پایگاه داده ، برنامه و شبکه / ارتباط بین آنها تعریف و مدیریت کرد.
در این آموزش Drupal را با استفاده از Docker Compose نصب خواهیم کرد تا بتوانیم از کانتینرینگ استفاده کرده و وب سایت Drupal خود را روی سرورها مستقر کنیم. کانتینرهایی برای یک پایگاه داده MySQL ، وب سرور مجازی Nginx و Drupal اجرا خواهیم کرد. همچنین با بدست آوردن گواهینامه های TLS / SSL با Let’s Encrypt برای دامنه مورد نظر جهت پیوند با سایت خود ، نصب خود را ایمن خواهیم کرد. سرانجام ، یک فرآیند cron را برای تمدید گواهینامه های خود تنظیم خواهیم کرد تا دامنه مان ایمن بماند.
پیش نیازها
برای دنبال کردن این آموزش ، به موارد زیر نیاز خواهیم داشت:
⦁ سروری که اوبونتو 18.04 را اجرا می کند ، به همراه یک کاربر غیر ریشه با امتیازات sudo و یک فایروال فعال. برای راهنمایی در مورد نحوه تنظیم این موارد ، لطفاً به این راهنمای تنظیم اولیه سرور مجازی مراجعه کنید.
⦁ Docker که طبق مراحل 1 و 2 نحوه نصب و استفاده از Docker در اوبونتو 18.04 بر روی سرور مجازی نصب شده باشد. این آموزش بر روی نسخه 19.03.8 تست شده است.
⦁ Docker Compose که طبق مرحله 1 نحوه نصب Docker در اوبونتو 18.04 بر روی سرور مجازی نصب باشد. این آموزش بر روی نسخه 1.21.2 تست شده است.
⦁ نام دامنه ثبت شده. در سراسر این آموزش از your_domain استفاده خواهد کرد. می توانید یک نام دامنه به صورت رایگان در Freenom دریافت کنید ، و یا از ثبت کننده دامنه مورد نظر خود استفاده کنید.
⦁ هر دو فایل DNS زیر برای سرور مجازی شما تنظیم شده باشد.
⦁ یک رکورد A با your_domain که به آدرس IP عمومی سرور مجازی شما اشاره کند.
⦁ رکورد A با www.your_domain که به آدرس IP عمومی سرور مجازی شما نشان می دهد.
مرحله 1 – تعریف پیکربندی وب سرور
قبل از اجرای هر نوع کانتینر ، باید پیکربندی سرور مجازی وب Nginx خود را تعریف کنیم. فایل پیکربندی ما شامل برخی از بلوک های مکانی مخصوص Drupal ، به همراه بلوک موقعیت مکانی برای هدایت درخواست های تأیید Let’s Encrypt به کلاینت Certbot برای تمدید خودکار گواهینامه میباشد.
ابتدا ، اجازه دهید یک دایرکتوری پروژه برای مجموعه Drupal خود به نام drupal ایجاد کنیم:
⦁ $ mkdir drupal

به دیرکتوری جدید ایجاد شده بروید:
⦁ $ cd drupal

اکنون می توانیم یک دایرکتوری برای فایل پیکربندی خود تهیه کنیم:
⦁ $ mkdir nginx-conf

فایل را با nano یا ویرایشگر متن مورد علاقه خود باز کنید:
⦁ $ nano nginx-conf/nginx.conf

در این فایل ، یک بلوک سرور مجازی با دستورالعمل هایی برای نام سرور مجازی و ریشه اسناد ، و بلوک های مکان برای هدایت درخواست کلاینت Certbot برای صدور گواهینامه ها ، پردازش PHP و درخواست های دارایی استاتیک اضافه خواهیم کرد.
کد زیر را در فایل اضافه کنید. حتماً your_domain را با نام دامنه خود جایگزین کنید:
~/drupal/nginx-conf/nginx.conf
server {
listen 80;
listen [::]:80;

server_name your_domain www.your_domain;

index index.php index.html index.htm;

root /var/www/html;

location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}

location / {
try_files $uri $uri/ /index.php$is_args$args;
}

rewrite ^/core/authorize.php/core/authorize.php(.*)$ /core/authorize.php$1;

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass drupal:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}

location ~ /\.ht {
deny all;
}

location = /favicon.ico {
log_not_found off; access_log off;
}
location = /robots.txt {
log_not_found off; access_log off; allow all;
}
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}

بلوک سرور مجازی ما شامل اطلاعات زیر است:
دستورالعمل ها:
⦁ listen: به Nginx می گوید که به پورت 80 گوش دهد ، که به ما این امکان را می دهد تا از پلاگین webroot Certbot برای درخواست های گواهی خود استفاده کنیم. توجه داشته باشید که ما هنوز پورت 443 را دراختیار نداریم – پس از اینکه گواهینامه های خود را با موفقیت به دست آوردیم ، پیکربندی خود را برای شامل شدن SSL به روز خواهیم کرد.
⦁ server_name: نام سرور مجازی ما و بلوک سرور مجازی را که باید برای درخواست به سرور مجازی ما استفاده شود ، تعریف می کند. حتما your_domain را در این خط با نام دامنه خود جایگزین کنید.
⦁ index: فایل هایی را که هنگام پردازش درخواست به سرور مجازی ما به عنوان دیرکتوری استفاده می شوند ، تعریف می کند. ما ترتیب پیش فرض اولویت را در اینجا تغییر داده ایم ، index.php را در جلوی index.html قرار می دهیم تا Nginx در صورت امکان فایلهایی به نام index.php را در اولویت قرار دهد.
⦁ Root: دستورالعمل root دیرکتوری اصلی برای درخواست به سرور مجازی ما را نامگذاری می کند. این دیرکتوری ، / var / www / html ، به عنوان یک نقطه نصب در زمان ساخت با دستورالعمل هایی در Drupal Dockerfile ما ایجاد می شود. این دستورالعمل Dockerfile همچنین اطمینان حاصل می کند که فایل های موجود در نسخه Drupal روی این حجم نصب شده اند.
⦁ rewrite: اگر عبارت معمول و مشخص شده (^/core/authorize.php/core/authorize.php(.*)$) با یک URI درخواستی مطابقت داشته باشد ، URI همانطور که در رشته جایگزینی مشخص شده است تغییر می کند (/core/authorize.php$1)
بلوک های موقعیت مکانی:
⦁ location ~ /.well-known/acme-challenge : این بلوک موقعیت مکانی درخواست ها به دایرکتوری .well-known را مدیریت میکند که در آن Certbot یک فایل موقت قرار می دهد تا تأیید کند که DNS برای دامنه ما روی سرور مجازی مناسب میباشد. با استقرار این پیکربندی ، می توانیم از پلاگین webroot Certbot برای به دست آوردن گواهینامه های دامنه خود استفاده کنیم.
⦁ location / : در این بلوک موقعیت مکانی ، ما از یک دستورالعمل try_files برای بررسی فایل های مطابق با درخواست های URI استفاده خواهیم کرد. با این وجود ، به جای بازگشت یک وضعیت 404 Not Found به عنوان پیش فرض ، با آرگومان های درخواست ، کنترل را به فایل index.php Drupal خواهیم داد.
⦁ location ~ \.php$ : این بخش موقعیت مکانی پردازش PHP و پروکسی این درخواستها به کانتینر Drupal را مدیریت می کند. از آنجا که تصویر Drupal Docker ما مبتنی بر تصویر php: fpm خواهد بود ، همچنین گزینه های پیکربندی خاص را در پروتکل FastCGI در این بلوک قرار خواهیم داد. Nginx برای درخواست های PHP به یک پردازنده مستقل PHP احتیاج دارد: در مورد ما ، این درخواست ها توسط پردازنده php-fpm که همراه با تصویر php: fpm است ، انجام می شود. علاوه بر این ، این بلوک موقعیت مکانی شامل دستورالعمل ها ، متغیرها و گزینه های خاص FastCGI است که درخواست ها به برنامه Drupal را که در کانتینر Drupal ما اجرا می شود ، پروکسی میکند، و شاخص مورد نظر را برای URI درخواستی تجزیه شده و درخواست های URI تنظیم میکند.
⦁ location ~ /\.ht : این بلوک فایل های html دسترسی را از آنجایی که Nginx به آنها سرویس نمی دهد ، مدیریت خواهد کرد. دستورالعمل deny_all تضمین می کند که فایل های .htaccess هرگز در اختیار کاربران قرار نمی گیرد.
⦁ location = /favicon.ico, location = /robots.txt : این بلوک ها اطمینان حاصل می کنند که درخواست ها به /favicon.ico و /robots.txt وارد نشوند.
⦁ location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ : این بلوک ورود به سیستم برای درخواست های دارایی استاتیک را غیرفعال می کند و تضمین می کند که این دارایی ها بسیار قابل ذخیره هستند ، زیرا ارائه آن ها معمولاً گران است.
برای کسب اطلاعات بیشتر در مورد پراکسی FastCGI ، به راهنمای درک و اجرای Proxying FastCGI در Nginx مراجعه کنید. برای کسب اطلاعات در مورد بلوک های سرور و موقعیت مکانی ، به راهنمای درک سرور Nginx و الگوریتم های انتخاب بلوک موقعیت مراجعه کنید.
پس از پایان ویرایش ، فایل را ذخیره کنید و ببندید.
با تنظیم Nginx در محل خود ، می توانید به سراغ ایجاد متغیرهای محیط بروید تا در زمان اجرا به کانتینرها برنامه و پایگاه داده خود منتقل شوید.
مرحله 2 – تعیین متغیرهای محیط
برنامه Drupal ما برای ذخیره اطلاعات مربوط به سایت به یک پایگاه داده (MySQL ، PostgresSQL و غیره) نیاز دارد. برای دسترسی به کانتینر پایگاه داده (MySQL) ، کانتینر Drupal در زمان اجرا به برخی از متغیرهای محیطی نیاز دارد. این متغیرها حاوی اطلاعات حساسی مانند اعتبارات پایگاه داده هستند ، بنابراین نمی توانیم مستقیماً آنها را در فایل Docker Compose قرار دهیم – فایل اصلی که حاوی اطلاعاتی در مورد نحوه اجرای کانتینرهای ما است.
همیشه توصیه می شود مقادیر حساس را در فایل .env تنظیم کنید و گردش آن را محدود کنید. این کار مانع از کپی شدن این مقادیر در مخازن پروژه می شود و در معرض دید عموم قرار نمیگیرد.
در دیرکتوری اصلی پروژه ، ~ / drupal ، فایلی با نام .env ایجاد و آن را باز کنید:
⦁ $ nano .env

متغیرهای زیر را به فایل .env اضافه کنید و بخش هایلایت شده را با اعتبار مورد نظر خود جایگزین کنید:
~/drupal/.env
MYSQL_ROOT_PASSWORD=root_password
MYSQL_DATABASE=drupal
MYSQL_USER=drupal_database_user
MYSQL_PASSWORD=drupal_database_password

اکنون گذرواژه را برای حساب ریشه MySQL و همچنین نام کاربری و رمزعبور مورد نظر خود برای بانک اطلاعات برنامه خود اضافه کرده ایم.
فایل .env ما حاوی اطلاعات حساسی است ، بنابراین همیشه توصیه می شود آن را در فایل های .gitignore و .dockerignore یک پروژه قرار دهید تا به مخازن Git و تصاویر Docker اضافه نشود.
اگر می خواهید برای کنترل نسخه با Git کار کنید ، دیرکتوری کاری فعلی خود را به عنوان یک مخزن با git init مقداردهی کنید:
⦁ $ git init

فایل gitignore را باز کنید:
⦁ $ nano .gitignore

موارد زیر را اضافه کنید:
~/drupal/.gitignore
.env
فایل را ذخیره کنید و از آن خارج شوید.
به همین ترتیب ، فایل .dockerignore را باز کنید:
⦁ $ nano .dockerignore

سپس موارد زیر را اضافه کنید:
~/drupal/.dockerignore
.env
.git
فایل را ذخیره کنید و از آن خارج شوید.
اکنون که برای حفظ اعتبار خود به عنوان متغیرهای محیطی اقداماتی انجام داده ایم ، بیایید به مرحله بعدی تعریف خدمات خود در فایل docker-compose.yml برویم.
مرحله 3 – تعریف خدمات با Compose Docker
Docker Compose ابزاری برای تعریف و اجرای برنامه های چند کانتینری Docker است. ما برای پیکربندی خدمات برنامه خود ، یک فایل YAML تعریف می کنیم. سرویس در Docker Compose یک کانتینر در حال اجرا است و Compose به ما اجازه می دهد تا این سرویس ها را با حجم و شبکه های مشترک پیوند دهیم.
کانتینرهای مختلفی را برای برنامه Drupal ، پایگاه داده و سرور مجازی وب خود ایجاد خواهیم کرد. در کنار اینها ، همچنین یک کانتینر برای اجرای Certbot ایجاد خواهیم کرد تا بتوانیم گواهینامه هایی را برای سرور مجازی وب خود بدست آوریم.
یک فایل docker-compose.yml ایجاد کنید:
⦁ $ nano docker-compose.yml

برای تعریف نسخه فایل Compose و سرویس پایگاه داده mysql کد زیر را اضافه کنید:
~/drupal/docker-compose.yml
version: “3”

services:
mysql:
image: mysql:8.0
container_name: mysql
command: –default-authentication-plugin=mysql_native_password
restart: unless-stopped
env_file: .env
volumes:
– db-data:/var/lib/mysql
networks:
– internal

بیایید همه گزینه های پیکربندی سرویس mysql را یک به یک مرور کنیم:
⦁ image: تصویری را که برای ایجاد کانتینر استفاده یا واکشی خواهد شد، مشخص می کند. همیشه برای جلوگیری از مشکلات بعدی توصیه می شود از تصویر با برچسب نسخه مناسب به استثنای آخرین برچسب استفاده کنید. اطلاعات بیشتر در مورد بهترین روش های Dockerfile را از اسناد Docker بخوانید.
⦁ container_name:برای تعریف نام کانتینر.
⦁ Command: از این گزینه برای رونویسی دستور پیش فرض (دستورالعمل CMD) در تصویر استفاده می شود. MySQL از افزونه های مختلف تأیید اعتبار پشتیبانی کرده است ، اما mysql_native_password m روش معمول تأیید اعتبار است. از آنجا که PHP ، و از این رو Drupal ، از تأیید هویت MySQL جدیدتر پشتیبانی نمی کنند ، ما باید –default-authentication-plugin=mysql_native_password  را به عنوان مکانیزم پیش فرض تأیید اعتبار تنظیم کنیم.
⦁ restart: برای تعریف رویکرد ریستارت کانتینر استفاده می شود. رویکرد unless-stopped یک کانتینر را مجدداً راه اندازی می کند مگر اینکه به صورت دستی متوقف شود.
⦁ env_file: متغیرهای محیط را از یک فایل اضافه می کند. در مورد ما متغیرهای محیط را از فایل .env تعریف شده در مرحله قبل می خواند.
⦁ volumes: مسیرهای هاست یا والیوم های نامگذاری را به عنوان گزینه هایی برای یک سرویس مشخص می کند. ما یک والیوم نامگذاری شده به نام db-data را در دیرکتوری / var / lib / mysql روی کانتینر نصب می کنیم ، جایی که MySQL بصورت پیش فرض فایل های داده خود را خواهد نوشت.
⦁ networks: شبکه داخلی را که سرویس برنامه ما به آن ملحق می شود ، تعریف می کند. ما در انتهای فایل شبکه ها را تعریف خواهیم کرد.
تعریف سرویس mysql ما را انجام داده ایم ، بنابراین اکنون بیایید تعریف سرویس برنامه Drupal را به انتهای فایل اضافه کنیم:
~/drupal/docker-compose.yml

drupal:
image: drupal:8.7.8-fpm-alpine
container_name: drupal
depends_on:
– mysql
restart: unless-stopped
networks:
– internal
– external
volumes:
– drupal-data:/var/www/html

در این تعریف سرویس ، ما همانطور که با سرویس mysql انجام دادیم ، کانتینر خود را نامگذاری می کنیم و یک سیاست راه اندازی مجدد را تعریف می کنیم. ما همچنین گزینه های خاصی را برای این کانتینر اضافه می کنیم:
Image: در اینجا ، از تصویر 8.7.8-fpm-alpine استفاده می کنیم. این تصویر دارای پردازنده php-fpm است که سرور مجازی وب Nginx ما برای پردازش PHP نیاز دارد. علاوه بر این ، از تصویر alpine ، مشتق از پروژه Alpine Linux استفاده می کنیم ، که باعث کاهش سایز تصویر کلی می شود و در بهترین روش های Dockerfile توصیه می شود. Drupal نسخه های بیشتری از تصاویر دارد ، بنابراین آنها را در Dockerhub بررسی کنید.
depends_on : برای بیان وابستگی بین خدمات استفاده می شود. تعیین سرویس mysql به عنوان وابستگی به کانتینر Drupal ما ، اطمینان حاصل می کند که کانتینر Drupal ما پس از کانتینر mysql ایجاد می شود و برنامه ما را قادر می سازد تا یکنواخت شروع شود.
networks: در اینجا ، ما این کانتینر را به همراه شبکه داخلی به شبکه خارجی اضافه کرده ایم. این اطمینان حاصل می کند که سرویس mysql ما فقط از طریق کانتینر داخلی Drupal از طریق شبکه داخلی قابل دسترسی است در حالی که این کانتینرها را از طریق شبکه خارجی در دسترس سایر کانتینرها قرار می دهد.
volumes: یک والیوم نامگذاری شده به نام drupal-data را بر روی Mount / var / www / html قرار می دهیم که توسط تصویر Drupal ایجاد شده است. استفاده از یک والیوم مشخص از این طریق به ما امکان می دهد تا کد برنامه خود را با سایر کانتینرها به اشتراک بگذاریم.
سپس ، تعریف خدمات Nginx را بعد از تعریف سرویس Drupal اضافه خواهیم کرد:
~/drupal/docker-compose.yml

webserver:
image: nginx:1.17.4-alpine
container_name: webserver
depends_on:
– drupal
restart: unless-stopped
ports:
– 80:80
volumes:
– drupal-data:/var/www/html
– ./nginx-conf:/etc/nginx/conf.d
– certbot-etc:/etc/letsencrypt
networks:
– external

مجدداً ، برای شروع کار ، کانتینر خود را نامگذاری می کنیم و آن را به کانتینر Drupal وابسته می کنیم. همچنین از یک تصویر alpine– تصویر 1.17.4-alpine Nginx – استفاده می کنیم .
این تعریف خدمات نیز گزینه های زیر را شامل می شود:
⦁ ports: پورت 80 را برای فعال کردن گزینه های پیکربندی تعریف شده در فایل nginx.conf در مرحله 1 در معرض نمایش قرار می دهد.
⦁ volumes: در اینجا ، ما هم والیوم نامگذاری شده و هم مسیر هاست را تعریف می کنیم:
⦁ drupal-data:/var/www/html : کد برنامه Drupal ما را روی دیرکتوری / var / www / html سوار می کند ، که ما آن را به عنوان ریشه در بلوک سرور مجازی Nginx قرار داده ایم.
⦁ ./nginx-conf:/etc/nginx/conf.d : دایرکتوری پیکربندی Nginx را بر روی هاست برای دیرکتوری مربوطه در کانتینر سوار می کند ، و اطمینان حاصل می کند که هرگونه تغییری که در فایل ها ایجاد کنیم روی هاست در کانتینر منعکس می شود.
⦁ certbot-etc:/etc/letsencrypt : مجوزها و کلیدهای Let’s Encrypt برای دامنه ما را روی دایرکتوری مناسب موجود در کانتینر سوار می کند.
⦁ networks: ما شبکه خارجی را فقط برای این تعریف کرده ایم تا این کانتینر بتواند با کانتینر Drupal و نه با کانتینر mysql ارتباط برقرار کند.
سرانجام آخرین تعریف سرویس خود را برای سرویس certbot اضافه خواهیم کرد. حتماً sammy @ your_domain و your_domain را با ایمیل و نام دامنه خود جایگزین کنید:
~/drupal/docker-compose.yml

certbot:
depends_on:
– webserver
image: certbot/certbot
container_name: certbot
volumes:
– certbot-etc:/etc/letsencrypt
– drupal-data:/var/www/html
command: certonly –webroot –webroot-path=/var/www/html –email sammy@your_domain –agree-tos –no-eff-email –staging -d your_domain -d www.your_domain

این تعریف به Compose می گوید تا تصویر certbot / certbot را از Docker Hub دریافت کند. همچنین از این والیوم های نامگذاری شده برای به اشتراک گذاری منابع با کانتینر Nginx ، از جمله گواهینامه های دامنه و کلید در certbot-etcو کد برنامه در drupal-data استفاده می کند.
همچنین از depends_on استفاده کرده ایم تا مطمئن شویم که پس از اجرای سرویس وب سرور مجازی ، کانتینرهای certbot شروع می شوند.
ما هیچ شبکه ای را در اینجا مشخص نکرده ایم زیرا این کانتینر با هیچ سرویس دیگری از طریق شبکه ارتباط برقرار نخواهد کرد. تنها گواهی نامه های دامنه و کلید را اضافه میکند که ما با استفاده از والیوم نام گذاری شده نصب کرده ایم.
همچنین گزینه command  را گنجانده ایم که یک فرمان فرعی را برای اجرا با دستور certbot پیش فرض کانتینر مشخص می کند. کلاینت Certbot از پلاگین ها برای بدست آوردن و نصب گواهینامه ها پشتیبانی می کند. ما از پلاگین webroot برای بدست آوردن گواهی نامه با درج نام مستقل و – webroot در خط فرمان استفاده می کنیم. اطلاعات بیشتر در مورد افزونه و دستورات اضافی را از مستندات رسمی Certbot بخوانید.
پس از تعریف سرویس certbot ، تعریف شبکه و والیوم را اضافه کنید:
~/drupal/docker-compose.yml

networks:
external:
driver: bridge
internal:
driver: bridge

volumes:
drupal-data:
db-data:
certbot-etc:

کلید شبکه سطح بالا به ما امکان می دهد شبکه هایی که باید ایجاد شوند مشخص کنیم. شبکه ها امکان ارتباط بین سرویس ها و کانتینرهای موجود در کلیه پورت ها را از زمان ورود به سایت فراهم می کنند زیرا در همان هاست Docker daemon هستند. ما دو شبکه داخلی و خارجی تعریف کرده ایم تا ارتباط وب سرورها ، Drupal و mysql را تضمین کنیم.
از کلید volumes  برای تعریف والیوم های نامگذاری drupal-data ، db-data و certbot-etc استفاده می شود. هنگامی که Docker والیوم ها را ایجاد می کند ، محتوای والیوم در یک دیرکتوری در سیستم فایل هاست ، / var / lib / docker / volumes / ، که توسط Docker اداره می شود ، ذخیره می گردد. محتویات هر والیوم از این دیرکتوری روی هر کانتینر دیگری که از والیوم استفاده کند سوار می شود. به این ترتیب ، امکان اشتراک گذاری کد و داده ها بین کانتینرها وجود دارد.
فایل نهایی docker-compose.yml این گونه به نظر می رسد:
~/drupal/docker-compose.yml
version: “3”

services:
mysql:
image: mysql:8.0
container_name: mysql
command: –default-authentication-plugin=mysql_native_password
restart: unless-stopped
env_file: .env
volumes:
– db-data:/var/lib/mysql
networks:
– internal

drupal:
image: drupal:8.7.8-fpm-alpine
container_name: drupal
depends_on:
– mysql
restart: unless-stopped
networks:
– internal
– external
volumes:
– drupal-data:/var/www/html

webserver:
image: nginx:1.17.4-alpine
container_name: webserver
depends_on:
– drupal
restart: unless-stopped
ports:
– 80:80
volumes:
– drupal-data:/var/www/html
– ./nginx-conf:/etc/nginx/conf.d
– certbot-etc:/etc/letsencrypt
networks:
– external

certbot:
depends_on:
– webserver
image: certbot/certbot
container_name: certbot
volumes:
– certbot-etc:/etc/letsencrypt
– drupal-data:/var/www/html
command: certonly –webroot –webroot-path=/var/www/html –email sammy@your_domain –agree-tos –no-eff-email –staging -d your_domain -d www.your_domain

networks:
external:
driver: bridge
internal:
driver: bridge

volumes:
drupal-data:
db-data:
certbot-etc:

ما کار تعریف سرویس ها را انجام داده ایم. سپس ، بیایید کانتینر را شروع کنیم و درخواست های گواهینامه خود را آزمایش کنیم.
مرحله 4 – اخذ گواهینامه ها و اعتبارات SSL
ما می توانیم کانتینرهای خود را با دستور docker-compose up شروع کنیم ، که کانتینرهای ما را به ترتیبی که مشخص کرده ایم ، ایجاد و اجرا می کند. اگر درخواست های دامنه ما موفق باشد ، وضعیت خروجی صحیح در خروجی خود و گواهی های صحیح نصب شده در پوشه / etc / letsencrypt / live در کانتینر سرور مجازی وب را مشاهده خواهیم کرد.
برای اجرای کانتینرها در پس زمینه ، از دستور docker-compose up با پرچم -d استفاده کنید:
⦁ $ docker-compose up -d

خروجی مشابهی مشاهده خواهید کرد که تأیید می کند خدمات شما ایجاد شده اند:
Output

Creating mysql … done
Creating drupal … done
Creating webserver … done
Creating certbot … done

با استفاده از دستور docker-compose ps وضعیت خدمات را بررسی کنید:
⦁ $ docker-compose ps

خدمات mysql ، Drupal و وب سرور را با وضعیتUp مشاهده خواهیم کرد ، در حالی که certbot با یک پیام وضعیت 0 خارج می شود:
Output
Name Command State Ports
————————————————————————–
certbot certbot certonly –webroot … Exit 0
drupal docker-php-entrypoint php-fpm Up 9000/tcp
mysql docker-entrypoint.sh –def … Up 3306/tcp, 33060/tcp
webserver nginx -g daemon off; Up 0.0.0.0:80->80/tcp

اگر در ستون State برای خدمات mysql ، Drupal یا webserver چیز دیگری به غیر از up مشاهده میکنید و یا وضعیت خروج غیر از 0 برای کانتینر certbot مشاهده می کنید ، حتما ورود های خدمات را با دستور docker-comps logs بررسی نمایید:
⦁ $ docker-compose logs service_name

اکنون می توانیم بررسی کنیم که گواهینامه های ما با استفاده از دستور docker-compose exec در کانتینر webserver نصب شده است:
⦁ $ docker-compose exec webserver ls -la /etc/letsencrypt/live

خروجی زیر را می دهد:
Output
total 16
drwx—— 3 root root 4096 Oct 5 09:15 .
drwxr-xr-x 9 root root 4096 Oct 5 09:15 ..
-rw-r–r– 1 root root 740 Oct 5 09:15 README
drwxr-xr-x 2 root root 4096 Oct 5 09:15 your_domain
اکنون که همه چیز با موفقیت اجرا شد ، می توانیم تعریف سرویس certbot خود را ویرایش کنیم تا پرچم –staging را حذف کنیم.
فایل docker-compose.yml را باز کنید ، به تعریف سرویس certbot بروید و پرچم –staging را در گزینه فرمان با پرچم –force-renewal جایگزین کنید ، که به Certbot می گوید که می خواهید یک گواهی جدید را با دامنه های مشابه گواهی موجود درخواست دهید. تعریف certbot به روز شده به شرح زیر خواهد بود:
~/drupal/docker-compose.yml

certbot:
depends_on:
– webserver
image: certbot/certbot
container_name: certbot
volumes:
– certbot-etc:/etc/letsencrypt
– drupal-data:/var/www/html
command: certonly –webroot –webroot-path=/var/www/html –email sammy@your_domain –agree-tos –no-eff-email –force-renewal -d your_domain -d www.your_domain

برای ایجاد مجدد کانتینر certbot باید دوباره docker-compose up را اجرا کنیم. همچنین گزینه –no-deps را در اختیار میگیریم تا به Compose بگوییم که می تواند از شروع سرویس وب سرور مجازی صرفنظر کند ، زیرا در حال حاضر اجرا میشود:
⦁ $ docker-compose up –force-recreate –no-deps certbot

خروجی را نشان می دهد که تایید میکند درخواست گواهی ما موفق بوده است:
Output
Recreating certbot … done
Attaching to certbot
certbot | Saving debug log to /var/log/letsencrypt/letsencrypt.log
certbot | Plugins selected: Authenticator webroot, Installer None
certbot | Renewing an existing certificate
certbot | Performing the following challenges:
certbot | http-01 challenge for your_domain
certbot | http-01 challenge for www.your_domain
certbot | Using the webroot path /var/www/html for all unmatched domains.
certbot | Waiting for verification…
certbot | Cleaning up challenges
certbot | IMPORTANT NOTES:
certbot | – Congratulations! Your certificate and chain have been saved at:
certbot | /etc/letsencrypt/live/your_domain/fullchain.pem
certbot | Your key file has been saved at:
certbot | /etc/letsencrypt/live/your_domain/privkey.pem
certbot | Your cert will expire on 2020-01-03. To obtain a new or tweaked
certbot | version of this certificate in the future, simply run certbot
certbot | again. To non-interactively renew *all* of your certificates, run
certbot | “certbot renew”
certbot | – Your account credentials have been saved in your Certbot
certbot | configuration directory at /etc/letsencrypt. You should make a
certbot | secure backup of this folder now. This configuration directory will
certbot | also contain certificates and private keys obtained by Certbot so
certbot | making regular backups of this folder is ideal.
certbot | – If you like Certbot, please consider supporting our work by:
certbot |
certbot | Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
certbot | Donating to EFF: https://eff.org/donate-le
certbot |
certbot exited with code 0

اکنون که مجوزهای خود را با موفقیت تولید کردیم ، می توانیم پیکربندی Nginx خود را به روز کنیم تا SSL را شامل شود.
مرحله 5 – اصلاح تنظیمات وب سرور مجازی و تعریف سرویس
پس از نصب گواهینامه های SSL در Nginx ، باید کلیه درخواست های HTTP را به HTTPS تغییر مسیر دهیم. همچنین باید مجوز SSL و مکانهای کلیدی خود را مشخص کنیم و پارامترهای امنیتی و هدرها را اضافه کنیم.
از آنجا که می خواهید سرویس وب سرور مجازی را برای گنجاندن این موارد اضافه، بازتولید کنید ، می توانید اکنون آن را متوقف کنید:
⦁ $ docker-compose stop webserver

خروجی زیر را به دست می دهد:
Output
Stopping webserver … done
سپس ، فایل پیکربندی Nginx را که قبلاً ایجاد کردیم حذف خواهیم کرد:
⦁ $ rm nginx-conf/nginx.conf

نسخه دیگری از فایل را باز کنید:
⦁ $ nano nginx-conf/nginx.conf

برای هدایت HTTP به HTTPS و اضافه کردن اعتبار ، پروتکل و هدرهای امنیتی SSL ، کد زیر را به فایل اضافه کنید. به یاد داشته باشید که your_domain را با دامنه خود جایگزین کنید:
~/drupal/nginx-conf/nginx.conf
server {
listen 80;
listen [::]:80;

server_name your_domain www.your_domain;

location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}

location / {
rewrite ^ https://$host$request_uri? permanent;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name your_domain www.your_domain;

index index.php index.html index.htm;

root /var/www/html;

server_tokens off;

ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;

add_header X-Frame-Options “SAMEORIGIN” always;
add_header X-XSS-Protection “1; mode=block” always;
add_header X-Content-Type-Options “nosniff” always;
add_header Referrer-Policy “no-referrer-when-downgrade” always;
add_header Content-Security-Policy “default-src * data: ‘unsafe-eval’ ‘unsafe-inline'” always;

location / {
try_files $uri $uri/ /index.php$is_args$args;
}

rewrite ^/core/authorize.php/core/authorize.php(.*)$ /core/authorize.php$1;

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass drupal:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}

location ~ /\.ht {
deny all;
}

location = /favicon.ico {
log_not_found off; access_log off;
}
location = /robots.txt {
log_not_found off; access_log off; allow all;
}
location ~* \.(css|gif|ico|jpeg|jpg|js|png)$ {
expires max;
log_not_found off;
}
}

بلوک سرور مجازی HTTP افزونه webroot را برای درخواستهای تمدید Certbot به دایرکتوری .well-known/acme-challenge مشخص می کند. این برنامه همچنین شامل یک دستورالعمل rewrite  است که درخواست های HTTP را به دیرکتوری اصلی به HTTPS هدایت می کند.
بلوک سرور مجازی HTTPS ، ssl و http2 را فعال می کند. برای کسب اطلاعات بیشتر درباره نحوه تکرار HTTP / 2 در پروتکل های HTTP و فواید آن برای عملکرد وب سایت ، لطفاً به مقدمه نحوه تنظیم Nginx با پشتیبانی HTTP / 2 در اوبونتو 18.04 مراجعه کنید.
این بلوک ها SSL را فعال می کنند ، همانطور که گواهی SSL و مکان های کلیدی ما را همراه با هدرهای توصیه شده درج کرده ایم. این هدرها به ما این امکان را می دهند که در سایت های آزمایش سرور مجازی SSL Labs و Security Headers امتیاز A کسب کنیم.
دستورالعمل های root  و index  ما نیز در این بلوک قرار دارند ، مانند سایر قسمت های بلوک مکان ویژه Drupal که در مرحله 1 بحث شده است.
فایل پیکربندی Nginx به روز شده را ذخیره کنید و ببندید.
قبل از استفاده مجدد از کانتینر سرور مجازی ، نیاز به اضافه کردن نگاشت پورت 443 روی تعریف سرویس وب سرور مجازی خود خواهیم داشت زیرا مجوزهای SSL را فعال کرده ایم.
فایل docker-compose.yml را باز کنید:
⦁ $ nano docker-compose.yml

تغییرات زیر را در تعریف سرویس وب سرور مجازی ایجاد کنید:
~/drupal/docker-compose.yml

webserver:
image: nginx:1.17.4-alpine
container_name: webserver
depends_on:
– drupal
restart: unless-stopped
ports:
– 80:80
– 443:443
volumes:
– drupal-data:/var/www/html
– ./nginx-conf:/etc/nginx/conf.d
– certbot-etc:/etc/letsencrypt
networks:
– external

پس از فعال کردن گواهینامه های SSL ، docker-compose.yml به صورت زیر ظاهر می شود:
~/drupal/docker-compose.yml
version: “3”

services:
mysql:
image: mysql:8.0
container_name: mysql
command: –default-authentication-plugin=mysql_native_password
restart: unless-stopped
env_file: .env
volumes:
– db-data:/var/lib/mysql
networks:
– internal

drupal:
image: drupal:8.7.8-fpm-alpine
container_name: drupal
depends_on:
– mysql
restart: unless-stopped
networks:
– internal
– external
volumes:
– drupal-data:/var/www/html

webserver:
image: nginx:1.17.4-alpine
container_name: webserver
depends_on:
– drupal
restart: unless-stopped
ports:
– 80:80
– 443:443
volumes:
– drupal-data:/var/www/html
– ./nginx-conf:/etc/nginx/conf.d
– certbot-etc:/etc/letsencrypt
networks:
– external

certbot:
depends_on:
– webserver
image: certbot/certbot
container_name: certbot
volumes:
– certbot-etc:/etc/letsencrypt
– drupal-data:/var/www/html
command: certonly –webroot –webroot-path=/var/www/html –email sammy@your_domain –agree-tos –no-eff-email –force-renewal -d your_domain -d www.your_domain

networks:
external:
driver: bridge
internal:
driver: bridge

volumes:
drupal-data:
db-data:
certbot-etc:

فایل را ذخیره کنید و ببندید. بیایید سرویس وب سرور مجازی را با پیکربندی به روز شده دوباره بازیابی کنیم:
⦁ $ docker-compose up -d –force-recreate –no-deps webserver

خروجی زیر را ارائه می دهد:
Output
Recreating webserver … done
خدمات را با docker-compose ps بررسی کنید:
⦁ $ docker-compose ps

خدمات mysql ، Drupal و webserver را در حالی مشاهده خواهیم کرد که certbot با یک پیام وضعیت 0 خارج می شود:
Output
Name Command State Ports
————————————————————————–
certbot certbot certonly –webroot … Exit 0
drupal docker-php-entrypoint php-fpm Up 9000/tcp
mysql docker-entrypoint.sh –def … Up 3306/tcp, 33060/tcp
webserver nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

در حال حاضر ، تمام خدمات ما در حال اجرا است و بهتر است با نصب Drupal از طریق رابط وب پیش برویم.
مرحله 6 – تکمیل نصب از طریق رابط وب
بیایید نصب را از طریق رابط وب Drupal انجام دهیم.
در یک مرورگر وب ، به دامنه سرور بروید. به یاد داشته باشید که your_domain خود را در اینجا با نام دامنه خود جایگزین کنید:
https://your_domain

زبان مورد استفاده را انتخاب کنید:

روی Save and continue کلیک کنید . به صفحه نمایه نصب منتقل میشویم. Drupal دارای پروفایل های مختلف است ، بنابراین مشخصات Standard را انتخاب کرده و روی Save and continue کلیک کنید.

پس از انتخاب پروفایل ، به صفحه پیکربندی Database خواهیم رفت. نوع Database را به عنوان MySQL ، MariaDB ، Percona Server یا معادل آن انتخاب کنید و مقادیر مربوط به نام پایگاه داده ، نام کاربری و رمز عبور را از بین مقادیر مربوط به MYSQL_DATABASE ، MYSQL_USER و MYSQL_PASSWORD به ترتیب در فایل.env در مرحله 2 تعریف کردید، انتخاب نمایید. روی Advanced Options کلیک کرده و مقدار هاست را روی نام کانتینر سرویس mysql تنظیم کنید. روی Save and continue کلیک کنید.

پس از پیکربندی پایگاه داده ، نصب ماژول ها و تم های پیش فرض Drupal شروع می شود:

پس از نصب سایت ، به صفحه ستاپ سایت پیکربندی Drupal برای پیکربندی نام سایت ، ایمیل ، نام کاربری ، رمز عبور و تنظیمات ناحیه ای می رویم. اطلاعات را پر کنید و بر روی Save and continue کلیک کنید:

بعد از کلیک روی ذخیره و ادامه ، می توانیم صفحه Welcome to Drupal را مشاهده کنیم ، که نشان می دهد سایت Drupal ما با موفقیت شروع به کار کرده است.

اکنون که نصب Drupal ما تمام شد ، باید اطمینان حاصل کنیم که گواهینامه های SSL ما به صورت خودکار تمدید خواهد شد.
مرحله 7 – تمدید گواهینامه ها
گواهینامه های Let’s Encrypt به مدت 90 روز معتبر هستند ، بنابراین باید یک فرایند تمدید خودکار را تنظیم کنیم تا اطمینان حاصل شود که از بین نمی روند. یکی از راه های انجام این کار ایجاد اقدامی با ابزار برنامه ریزی cron است. در این حالت ، ما یک کار cron ایجاد خواهیم کرد تا به صورت دوره ای یک اسکریپت را اجرا کنیم که گواهینامه های ما را تمدید کرده و پیکربندی Nginx را مجدد لود کند.
بیایید فایل ssl_renew.sh را برای تمدید گواهینامه های خود ایجاد کنیم:
⦁ $ nano ssl_renew.sh

کد زیر را اضافه کنید. به یاد داشته باشید که نام دیرکتوری را با کاربر غیر ریشه خود جایگزین کنید:
~/drupal/ssl_renew.sh
#!/bin/bash

cd /home/sammy/drupal/
/usr/local/bin/docker-compose -f docker-compose.yml run certbot renew –dry-run && \
/usr/local/bin/docker-compose -f docker-compose.yml kill -s SIGHUP webserver

این اسکریپت در دیرکتوری پروژه ~ / drupal تغییر می کند و دستورات docker-compose زیر را اجرا می کند.
docker-compose run : یک کانتینر certbot را شروع می کند و فرمان ارائه شده در تعریف سرویس certbot ما را نادیده می گیرد. به جای استفاده از فرمان فرعی certonly ، ما در اینجا از فرمان فرعی renew  استفاده می کنیم ، که گواهینامه هایی را که نزدیک به انقضا هستند تجدید می کند. برای آزمایش اسکریپت خود گزینه –dry run را در اینجا گنجانده ایم.
docker-compose kill : یک سیگنال SIGHUP را به کانتینر وب سرور مجازی ارسال می کند تا پیکربندی Nginx را مجدد لود کند.
با اجرای دستور زیر فایل را ببندید و آن را قابل اجرا کنید:
⦁ $ sudo chmod +x ssl_renew.sh

در مرحله بعد ، فایل crontab ریشه را باز کنید تا اسکریپت تجدید در یک بازه مشخص اجرا شود:
⦁ $ sudo crontab -e

اگر این اولین بار است که این فایل را ویرایش می کنید ، از شما خواسته می شود ویرایشگر متن را انتخاب کنید تا فایل با آن باز شود:
Output
no crontab for root – using an empty one

Select an editor. To change later, run ‘select-editor’.
1. /bin/nano
2. /usr/bin/vim.basic
3. /usr/bin/vim.tiny
4. /bin/ed

Choose 1-4 [1]:

در انتهای فایل خط زیر را اضافه کنید و sammy را با نام کاربری خود جایگزین کنید:
crontab

*/5 * * * * /home/sammy/drupal/ssl_renew.sh >> /var/log/cron.log 2>&1

این فاصله زمانی را برای هر پنج دقیقه تعیین می کند ، بنابراین می توانیم آزمایش کنیم که آیا درخواست تجدید ما مطابق پیش بینی شده کار کرده است یا خیر. همچنین یک فایل ورود، cron.log را ایجاد کرده ایم تا خروجی مربوطه را ثبت کنیم.
پس از پنج دقیقه ، از دستور tail استفاده کنید تا cron.log را بررسی کنید و ببینید آیا درخواست تمدید موفقیت آمیز بوده است یا خیر:
⦁ $ tail -f /var/log/cron.log

خروجی تأیید موفقیت آمیز تجدید را مشاهده خواهید کرد:
Output
** DRY RUN: simulating ‘certbot renew’ close to cert expiry
** (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
/etc/letsencrypt/live/your_domain/fullchain.pem (success)
** DRY RUN: simulating ‘certbot renew’ close to cert expiry
** (The test certificates above have not been saved.)
– – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

CTRL + C را فشار دهید تا از روند tail خارج شود.
اکنون می توانیم فایل crontab را تغییر دهیم تا اسکریپت روز دوم هر هفته در ساعت 2 صبح اجرا شود. خط آخر crontab را به صورت زیر تغییر دهید:
crontab

* 2 * * 2 /home/sammy/drupal/ssl_renew.sh >> /var/log/cron.log 2>&1

خارج شوید و فایل را ذخیره کنید.
اکنون ، بیایید گزینه –dry run را از متن ssl_renew.sh حذف کنیم. ابتدا آن را باز کنید:
⦁ $ nano ssl_renew.sh

سپس محتوا را به شرح زیر تغییر دهید:
~/drupal/ssl_renew.sh
#!/bin/bash

cd /home/sammy/drupal/
/usr/local/bin/docker-compose -f docker-compose.yml run certbot renew && \
/usr/local/bin/docker-compose -f docker-compose.yml kill -s SIGHUP webserver

در حال حاضر کار cron از تمدید گواهینامه های SSL با تمدید آنها در صورت واجد شرایط بودن ، مراقبت می کند.
نتیجه
در این آموزش از Docker Compose برای ایجاد نصب Drupal با یک وب سرور مجازی Nginx استفاده کرده ایم. به عنوان بخشی از این گردش کار ، گواهینامه های TLS / SSL را برای دامنه مورد نظر خود با سایت Drupal در نظر گرفتیم و یک کار cron ایجاد کردیم تا در صورت لزوم این گواهینامه ها را تمدید کنیم.
اگر دوست دارید درباره Docker اطلاعات بیشتری کسب کنید ، به صفحه  Docker topic مراجعه کنید.

 

از این لینک ها زیر می توانید آمورش های بیشتری برای لینوکس پیدا کنید :

نصب و پیکربندی Ansible در اوبونتو 18.04 – نصب MySQL در اوبونتو 18.04

نظارت بر سلامت سرور با Checkmk در اوبونتو – اجرای چند نسخه PHP بر روی یک سرور

نصب Nginx، MySQL، PHP در CentOS 7 –  نصب و پیکربندی Elasticsearch در اوبونتو 18.04

راه اندازی سرور اولیه با اوبونتو 20.04 – تنظیم کلیدهای SSH در Ubuntu 20.04

نصب و استفاده از PostgreSQL در اوبونتو 20.0 – نحوه نصب MySQL در اوبونتو 20.04

نصب Python 3 روی سرور Ubuntu 18.04 –  نحوه نصب Python 3 روی سرور Ubuntu 20.04

نصب Linux،Apache،MySQL،PHP LAMP در اوبونتو 20.04 –  نصب وب سرور Apache در CentOS 8

نحوه نصب Drupal با Docker Compose –  نحوه نصب Nginx در اوبونتو 20.04

7 مورد از اقدامات امنیتی برای محافظت از سرورهای شما –  نحوه نصب و ایمن سازی Redis در اوبونتو 20.04

نحوه نصب و ایمن سازی Redis در اوبونتو 18.04اضافه کردن فضای Swap در اوبونتو 20.04

چگونه می توان فایروال را با UFW در اوبونتو 20.04 تنظیم کردنظارت بر اطلاعیه و مسیر BGP با BGPalerter اوبونتو 18.04

نصب و ایمن سازی phpMyAdmin در اوبونتو 20.04  –  نصب و استفاده از Composer در اوبونتو 20.04

ریست کردن رمز ورود ریشه MySQL یا MariaDB –  استفاده از نوع داده MySQL BLOB برای ذخیره تصاویر

نصب Nginx در اوبونتو 18.04نصب توزیع Anaconda پایتون در اوبونتو 20.04

نحوه نصب جاوا با Apt در اوبونتو 18.04 –  نحوه نصب Django و تنظیم محیط توسعه در اوبونتو 16.04

نصب جنگو و تنظیم محیط توسعه در اوبونتو 20.04 – نحوه ایجاد سرور Minecraft در اوبونتو 18.04

نحوه راه اندازی یک پروژه Node با Typescript –  نحوه نصب و پیکربندی VNC در اوبونتو 18.04

ایجاد یک برنامه Django و اتصال آن به یک بانک اطلاعاتینصب و پیکربندی VNC در اوبونتو 20.04

نصب MariaDB در اوبونتو 20.04  –  فعال سازی و اتصال رابط کاربری Django

تنظیم پلتفرم Cloud IDE کد سرور را در اوبونتو 18.04پیکربندی Apache HTTP با رویداد MPM و PHP-FPM

 

 

کلمات کلیدی خرید سرور

خرید vps – خرید سرور مجازی – خرید سرور – سرور هلند – فروش vps – سرور مجازی آمریکا – خریدvps – سرور مجازی هلند – فروش سرور مجازی – سرور آمریکا – vps – سرور مجازی انگلیس – سرور مجازی آلمان – سرور مجازی کانادا – خرید vps آمریکا – خرید وی پی اس – سرور – خرید سرور مجازی هلند – vps خرید – سرور مجازی فرانسه – سرور مجازی هلند – خرید vps آمریکاخرید سرور مجازی ارزان هلندvpsخرید vps هلندخرید سرور مجازی آمریکاخرید vps فرانسهتست vpsسرور مجازی تستسرور مجازی ویندوزارزانترین vpsخرید وی پی اسvps ارزان – 

 

 

 

برچسب‌ها:, , ,