มาลองสร้าง LEMP Stack ง่ายๆ ด้วย Docker กัน (Part3) รัน Yii Framework

สวัสดีครับ พบกับตอนสุดท้าย หลังจากที่ได้ลองใช้ Docker Compose กันไปแล้วทั้ง 2 ตอน (ตอน1, ตอน2) ในบทความนี้ผมจะแนะนำวิธีการใช้งาน Docker กับ Yii Framework ตั้งแต่การเตรียมโครงสร้างไปจนถึงการใช้งาน Docker Compose ซึ่งทั้งหมดนี้ใช้เวลาในการติดตั้งแค่ไม่กี่นาที โดยเราจะแบ่งเป็น 5 service ดังนี้

  • MySQL
  • PHP-FPM
  • Nginx
  • PhpMyAdmin
  • Memcached

เตรียมโครงสร้าง

ให้ทำการสร้างโฟลเดอร์ชื่อ lemp_docker_yii และสร้างไฟล์โฟลเดอร์ไว้ด้านใน ตามโครงสร้างนี้ จริงๆ ไม่ได้มีกฎตายตัวอะไร เราจะสร้างโครงสร้างแบบใหนก็ได้ ขอแค่ระบุพาทไฟล์ให้ถูกต้องพอ

lemp_docker_yii
├── docker-compose.yml
├── logs
│ ├── mysql
│ ├── nginx
│ └── php-fpm
├── mysql
│ ├── backup
│ ├── backup.sh
│ ├── data
│ └── initdb
├── nginx
│ ├── conf
│ │ └── nginx.conf
│ └── conf.d
│ ├── backend.conf
│ ├── frontend.conf
│ └── phpmyadmin.conf
├── php
│ ├── Dockerfile
│ ├── php-fpm.conf
│ └── php.ini
└── www
  • logs เอาไว้เก็บ log จาก service ทั้งหมดเราจะนำมารวมไว้ที่นี่ (mysql ผมยังทำไม่เป็น ถ้าใครรู้แนะนำด้วยนะครับ ^^ )
  • mysql เอาไว้เก็บข้อมูล mysql และข้อมูล backup ต่างๆ
  • nginx เอาไว้เก็บไฟล์ config ของ nginx
  • php เอาไว้เก็บ Dockerfile เพื่อ build image และไฟล์คอนฟิก
  • www เอาไว้เก็บ source code ที่เราสร้างจาก Yii

สร้างไฟล์

ไฟล์ทั้งหมดนี้สามารถโหลดได้จากตัว demo ที่ผมทำไว้ที่นี่

docker-compose.yml

version: '2'
services:
db:
image: mariadb:10.1
container_name: lemp_mariadb
restart: always
volumes:
- ./mysql/initdb/:/docker-entrypoint-initdb.d
- ./mysql/data/:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=123132123
- MYSQL_DATABASE=lemp_db
- MYSQL_USER=lemp
- MYSQL_PASSWORD=123456
php:
build: ./php
container_name: lemp_php-fpm
restart: always
volumes:
- ./www/:/var/www/html
- ./php/php-fpm.conf:/usr/local/etc/php-fpm.conf
- ./php/php.ini:/usr/local/etc/php/php.ini
- ./logs/php-fpm:/var/log/php-fpm
expose:
- "9000"
nginx:
image: nginx:stable-alpine
container_name: lemp_nginx
restart: always
volumes:
- ./nginx/conf/nginx.conf:/etc/nginx/conf/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./logs/nginx:/var/log/nginx
volumes_from:
- php
ports:
- 80:80
memcached:
image: memcached
container_name: lemp_memcached
ports:
- "11211:11211"
pma:
image: phpmyadmin/phpmyadmin
container_name: lemp_phpmyadmin
restart: always
ports:
- "8000:80"

Dockerfile

เราจะทำการสร้าง image ใหม่โดยใช้ Dockerfile และติดตั้ง php extension ต่างๆ ที่ Yii จำเป็นต้องใช้ เช่น Intl, pdo_mysql, Gd, Memcached, Composer

FROM php:7-fpm-alpine
MAINTAINER Sathit Seethaphon <dixonsatit@gmail.com>
RUN apk upgrade --update && apk --no-cache add \
git autoconf file g++ gcc binutils isl libatomic libc-dev musl-dev make re2c libstdc++ libgcc libcurl binutils-libs mpc1 mpfr3 gmp libgomp coreutils freetype-dev libjpeg-turbo-dev libltdl libmcrypt-dev libpng-dev openssl-dev libxml2-dev expat-dev icu-dev libmemcached-dev \
&& docker-php-ext-install iconv mysqli pdo pdo_mysql curl bcmath mcrypt mbstring json xml zip bz2 opcache intl \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd \
&& git clone --branch php7 https://github.com/php-memcached-dev/php-memcached /usr/src/php/ext/memcached \
&& cd /usr/src/php/ext/memcached \
&& docker-php-ext-configure memcached --disable-memcached-sasl \
&& docker-php-ext-install memcached
# Install Composer && Setup the Composer installer
RUN php -r "copy('https://getcomposer.org/installer', '/tmp/composer-setup.php');" \
&& php -r "if (hash_file('SHA384', '/tmp/composer-setup.php') === 'e115a8dc7871f15d853148a7fbac7da27d6c0030b848d9b3dc09e2a0388afed865e6a3d6b3c0fad45c48e2b5fc1196ae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('/tmp/composer-setup.php'); } echo PHP_EOL;" \
&& php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer && rm -rf /tmp/composer-setup.php \
&& composer global require --no-progress "fxp/composer-asset-plugin:~1.2.0"
EXPOSE 9000
CMD ["php-fpm"]

#update ใช้เป็นด้านล่างแทนนะครับ เพิ่ม curl-dev

FROM php:7.1.0-fpm-alpine
MAINTAINER Sathit Seethaphon <dixonsatit@gmail.com>
RUN apk upgrade --update && apk --no-cache add \
autoconf tzdata openntpd file g++ gcc binutils isl libatomic libc-dev musl-dev make re2c libstdc++ libgcc libcurl curl-dev binutils-libs mpc1 mpfr3 gmp libgomp coreutils freetype-dev libjpeg-turbo-dev libltdl libmcrypt-dev libpng-dev openssl-dev libxml2-dev expat-dev \
&& docker-php-ext-install -j$(nproc) iconv mysqli pdo pdo_mysql curl bcmath mcrypt mbstring json xml zip opcache \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd
# TimeZone
RUN cp /usr/share/zoneinfo/Asia/Bangkok /etc/localtime \
&& echo "Asia/Bangkok" > /etc/timezone
# Install Composer && Assets Plugin
RUN php -r "readfile('https://getcomposer.org/installer');" | php -- --install-dir=/usr/local/bin --filename=composer \
&& composer global require --no-progress "fxp/composer-asset-plugin:~1.2" \
&& apk del tzdata \
&& rm -rf /var/cache/apk/*
EXPOSE 9000
CMD ["php-fpm"]

backup.sh

เป็นไฟล์ .sh เพื่อสั่ง backup ข้อมูลจาก db เก็บไว้

#!/bin/bash
NOW=$(date +"%m-%d-%Y_%H-%M")
FILE="backup-$NOW.sql"
docker exec lemp_mariadb sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --databases $MYSQL_DATABASE' > "./mysql/backup/$FILE"
echo "Backing up data complate."
สำหรับการใช้งาน backup.sh ผมอธิบายไว้แล้วในตอน2

php.ini

เราสามารถกำหนดค่า php.ini ได้ โดยทำผ่านไฟล์นี้แล้วนำเข้าไปที่ container อีกทีดูเต็มๆ ที่นี่

cgi.fix_pathinfo=0
file_uploads = On
memory_limit = 64M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600

php-fpm.conf

กำหนดค่า php-fpm ได้ที่ไฟล์นี้ ดูเต็มๆ ที่นี่

[www]
user = www-data
group = www-data
listen = [::]:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500

nginx.conf

เป็นไฟล์ตั้งค่าหลักๆ ของ nginx

worker_processes 1;
daemon off;
events {
worker_connections 1024;
}
error_log   /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
http {
include /etc/nginx/conf/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# tells the server to use on-the-fly gzip compression.
include /etc/nginx/conf.d/*.conf;
}

frontend.conf

เป็นไฟล์ตั้งค่าสำหรับโดเมน lemp-frontend.dev

server {
charset utf-8;
client_max_body_size 128M;
listen 80; ## listen for ipv4
#listen [::]:80 default_server ipv6only=on; ## listen for ipv6
server_name lemp-frontend.dev;
root /var/www/html/frontend/web;
index index.php;
access_log  /var/log/nginx/frontend-access.log;
error_log /var/log/nginx/frontend-error.log;
location / {
# Redirect everything that isn't a real file to index.php
try_files $uri $uri/ /index.php$is_args$args;
}
# uncomment to avoid processing of calls to non-existing static files by Yii
#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
# try_files $uri =404;
#}
#error_page 404 /404.html;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php:9000;
try_files $uri =404;
}
location ~ /\.(ht|svn|git) {
deny all;
}
}

backend.conf

เป็นไฟล์ตั้งค่าสำหรับโดเมน lemp-backend.dev

server {
charset utf-8;
client_max_body_size 128M;
listen 80; ## listen for ipv4
#listen [::]:80 default_server ipv6only=on; ## listen for ipv6
server_name lemp-backend.dev;
root /var/www/html/backend/web;
index index.php;
access_log  /var/log/nginx/backend-access.log;
error_log /var/log/nginx/backend-error.log;
location / {
# Redirect everything that isn't a real file to index.php
try_files $uri $uri/ /index.php$is_args$args;
}
# uncomment to avoid processing of calls to non-existing static files by Yii
#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
# try_files $uri =404;
#}
#error_page 404 /404.html;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php:9000;
try_files $uri =404;
}
location ~ /\.(ht|svn|git) {
deny all;
}
}

phpmyadmin.conf

#PhpMyAdmin
server {
listen 80;
server_name app-pma.dev;
location / {
proxy_pass http://app-pma.dev:8000;
}
}

สร้าง Yii App

เราจะทำการสร้าง app ด้วย Yii 2 Advanced Project Template ให้เรา cd เข้าไปที่โฟลเดอร์ www จากนั้นทำการ git clone Project Template ลงมา ด้วยคำสั่ง git clone https://github.com/yiisoft/yii2-app-advanced.git .

git clone https://github.com/yiisoft/yii2-app-advanced.git .
Cloning into '.'...
remote: Counting objects: 5370, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 5370 (delta 2), reused 0 (delta 0), pack-reused 5356
Receiving objects: 100% (5370/5370), 1.09 MiB | 301.00 KiB/s, done.
Resolving deltas: 100% (2857/2857), done.
Checking connectivity... done.
เราทำการ git clone ลงมาที่โฟลเดอร์ www โดยที่ไม่ให้ git มันสร้างโฟลเดอร์หลักให้ โดยใช้ . ตามท้าย git clone <url> .

จะได้ source code ดังนี้

www
├── LICENSE.md
├── README.md
├── Vagrantfile
├── backend
├── codeception.yml
├── common
├── composer.json
├── console
├── docs
├── environments
├── frontend
├── init
├── init.bat
├── requirements.php
├── vagrant
├── yii.bat
├── yii_test
└── yii_test.bat
7 directories, 11 files

ตั้งค่า Host เพื่อให้สามารถใช้ Domain local

ในขั้นตอนของการพัฒนาเราสามารถตั้งค่า domain local เพื่อใช้ได้ เช่น

โดยเราสามารถแก้ไขที่ไฟล์ hosts ของแต่ละเครื่องได้ ที่

  • Windows: c:\Windows\System32\Drivers\etc\hosts
  • Linux: /etc/hosts

เปิดไฟล์ขึ้นมา แล้วเพิ่ม Domain ที่ต้องการเข้าไปด้านล่างสุด ตั้งอะไรก็ได้ แต่ต้องตั้งให้ตรงกับที่ config ใน nginx

# lemp-docker
127.0.0.1 lemp-phpmyadmin.dev
127.0.0.1 lemp-frontend.dev
127.0.0.1 lemp-backend.dev
การแก้ไขไฟล์ Hosts นี้ทำเฉพาะตอน Development ในเครื่องเราเท่านั้นนะครับ ตอนขึ้น Production เราก็ใช้ Domain จริง แล้วแก้ไขเฉพาะไฟล์ใน nginx/conf.d ตรง server_name ก็พอ

รัน Docker Compose

สั่ง start และ build service

docker-compose up -d

เมื่อรันเสร็จให้เราตรวจสอบ service ของเราได้ start จริงหรือไม่ โดยใช้คำสั่ง ps

docker-compose ps

จะพบกับหน้าจอสถานะอันสวยงาม ให้ดูที่คอลัมน์ state ต้องเป็น Up เท่านั้น แสดงว่า service ของเราพร้อมแล้ว

Initialize Yii Advance Template

การใช้งาน yii advance template นั้นจำเป็นจะต้อง init เพื่อสร้างไฟล์ config ต่างๆ ก่อนถึงจะใช้งานได้ ซึ่งเราสามารถเลือกได้ 2 แบบคือ Development และ Production ในเบื้องต้นให้เลือก 0 เพราะเรายังอยู่ในขั้นตอนของการพัฒนาอยู่ หากนำขึ้น production ค่อยเลือกเป็น 1 อีกที

docker-compose run --rm php ./init
Yii Application Initialization Tool v1.0
Which environment do you want the application to be initialized in?
[0] Development
[1] Production
Your choice [0-1, or "q" to quit] 0

เมื่อได้ init เรียบร้อยแล้วให้เราทำการติดตั้ง packet ต่างๆ ที่จำเป็นในการพัฒนา yii framework โดยรันคำสั่ง composer install

docker-compose run --rm php composer install
หลังจากที่เรารันคำสั่ง composer install แล้ว composer จะทำการติดตั้ง packet ต่างๆ ให้เรา ช่วงเวลานี้เป็นช่วงเวลาที่พิเศษ ให้เราหากิจกรรมต่างๆ ทำ เช่น ชงกาแฟ, ดูทีวี, อ่านหนังสือ, พาแฟนไปดูหนัง หรือจะไปว่ายน้ำก็ตามแต่ ถุ้ยยย !….555 มันน้านนานจริงๆ นะ…

อธิบายเพิ่มเติม ปกติการ ssh เข้าไปใน container นั้นเราจะใช้คำสั่ง exec เพื่อจะไปทำอะไรบางอย่าง ข้อเสียคือเราต้องเข้าไปอยู่ใน containter เมื่อเสร็จก็ต้องออกมาอีก บางครั้งเราอยากรันคำสั่งบางอย่างเล็กๆ น้อย ก็สามารถใช้คำสั่ง run ได้ โดยที่เราไม่ต้อง ssh เข้าไปใน container

docker-compose run [options] SERVICE [COMMAND]

ตัวอย่าง

docker-compose run --rm php composer --version
Composer version 1.2.0 2016-07-19 01:28:52

ตั้งค่าการติดต่อฐานข้อมูล

หลังจากที่เราทำการ init Application ของเราแล้วมันจะสร้างไฟล์ config ไว้ที่ common/config/main-local.php เปิดไฟล์นี้ขึ้นมา ทำการตั้งค่า db, username, password, ส่วน host นั้นเราใช้ docker อยู่เราสามารถใช้ชื่อ service แทนได้เลยซึ่งตอนนี้ mysql เราตั้งชื่อ service ว่า db

ซึ่งเราได้ตั้งค่าไว้ใน docker-compose.yml ดังนี้

db:
image: mariadb:10.1
container_name: lemp_mariadb
restart: always
volumes:
- ./mysql/initdb/:/docker-entrypoint-initdb.d
- ./mysql/data/:/var/lib/mysql
- ./logs/mysql:/var/log/mysql
environment:
- MYSQL_ROOT_PASSWORD=123132123
- MYSQL_DATABASE=lemp_db
- MYSQL_USER=lemp
- MYSQL_PASSWORD=123456

เราสามารถตั้งค่าได้ดังนี้

<?php
return [
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=db;dbname=lemp_db',
'username' => 'lemp',
'password' => '123456',
'charset' => 'utf8',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mail',
// send all mails to a file by default. You have to set
// 'useFileTransport' to false and configure a transport
// for the mailer to send real emails.
'useFileTransport' => true,
],
],
];

จากนั้นรันคำสั่ง migrate เพื่อนำเข้าฐานข้อมูลของ yii-advance-templage ให้เราตอบ yes

docker-compose run --rm php ./yii migrate
Yii Migration Tool (based on Yii v2.0.10-dev)
Creating migration history table "migration"...Done.
Total 1 new migration to be applied:
m130524_201442_init
Apply the above migration? (yes|no) [no]:yes
*** applying m130524_201442_init
> create table {{%user}} ... done (time: 0.019s)
*** applied m130524_201442_init (time: 0.094s)
1 migration was applied.
Migrated up successfully.

เข้าใช้งาน

เมื่อติดตั้งเสร็จเรียบร้อย เราสามารถเข้าใช้งานตาม url ที่เรากำหนดไว้

สำหรับ Frontendhttp://lemp-frontend.dev

สำหรับ Backendhttp://lemp-backend.dev

ตั้งค่าเปิดการใช้งาน Memcached

ปกติค่า default data caching ของ yii จะเป็น FileCache เราสามารถเปลี่ยนมาใช้ memcached ได้ดังนี้

ไปที่ไฟล์ common/config/main.php

<?php
return [
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'components' => [
'cache' => [
'class' => 'yii\caching\FileCache',
],
],
];

เปลี่ยนให้เรียกเป็น memcached ดังนี้

<?php
return [
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'components' => [
'cache' => [
'class' => 'yii\caching\MemCache',
'servers' => [
[
'host' => 'memcached',
'port' => 11211,
'weight' => 100,
]
],

],
],
];

ในส่วนของเราตั้งค่า host เป็น memcached ได้เลยเพราะเป็นชื่อ service ที่เราตั้งใน docker-compose.yml มันจะมองเป็นเหมือนชื่อ host ตัวนี้เลย อ่านเพิ่มเติม data caching ได้ที่นี่

เข้าใช้งาน PhpMyAdmin

เราสามารถเข้าใช้งาน phpmyadmin ได้ที่

จริงๆ ควรจะเป็น http://lemp-phpmyadmin.dev แหละแต่ผมลองแล้วไม่ได้ ถ้าเป็น local นะ แต่ถ้าบน host จริง ได้ปกติ ถ้าใครรู้ต้องตั้งค่ายังไงแนะนำผมด้วยนะครับ

เพิ่มเติม!..แถม ^^

ในขึ้นตอนของการสร้าง App จาก yii-advance-templat นั้นมีอยู่ 4 ขั้นตอนหลักๆ คือ

  • Download Source code
  • init & config db
  • Install packet ด้วย composer
  • Migrate db

เราสามารถรวมคำสั่งขั้นตอน 3–4ให้ migrate db ด้วย composer หลังจากที่มัน install packet เสร็จแล้วให้มัน migrate db ต่อได้เลย ซึ่งเราสามารถใส่ command เข้าไปได้ดังตัวตามตัวอย่างในไฟล์ composer.json ได้ ในส่วน script หากมีหลายๆ คำสั่งก็เพิ่มเข้าไปหลายๆ บันทัดได้ครับ

"scripts": {
"post-install-cmd": "php init --env=Development --overwrite=n"
}

เปลี่ยนเป็น

"scripts": {
"post-install-cmd": [
"php init --env=Development --overwrite=n",
"php yii migrate/up --interactive=0",
]
}

หลังจากที่เรา Download Source code เสร็จและทำการ init เรียบร้อย เราสามารถรันคำสั่ง composer install ได้เลยเมื่อมันติดตั้ง packet เสร็จก็จะทำการ migrate db ต่อให้ทันทีอัตโนมัติครับ

ในการรันผ่าน Docker Compose เราใช้คำสั่งนี้

docker-compose run --rm php composer install

สรุป

หวังว่าบทความนี้ คงช่วยพอให้เรามองเห็นภาพการใช้งาน docker กับ yii นะครับ ในตัวอย่างนี้เราได้ลองสร้างตั้งแต่เตรียมโครงสร้าง, สร้าง docker-compose, Dockerfile, ไฟล์คอนฟิกต่างๆ , การติดตั้ง yii advance template และการ config สรุปคร่าวๆ ขั้นตอนการใช้งาน

  • สร้างโครงสร้างไฟล์ตามตัวอย่าง
  • สร้าง app: git clone https://github.com/yiisoft/yii2-app-advanced.git . มาไว้ที่ www
  • สั่งรัน: docker-compose up -d
  • สั่ง init ด้วย: docker-compose run — rm php ./init
  • ติดตั้ง packet ด้วยคำสั่ง: docker-compose run — rm php composer install
  • นำเข้าฐานข้อมูล: docker-compose run — rm php ./yii migrate
ในกรณีที่ทำตามหัวข้อเพิ่มเติม! ให้รันแค่ docker-compose run — rm php composer install ก็พอเดี่ยวมัน migrate db ต่อให้เอง

ส่วนตัวผมรู้สึกว่าสะดวกมากๆ ตั้งแต่ใช้ Docker มา แต่ตรงนี้อาจจะมีความยุ่งยากเล็กน้อยในช่วงแรก อาจจะด้วยความไม่คุ้นเลย ผมแนะนำว่าให้เราค่อยๆ ทำความเข้าใจ ไม่ต้องรีบ จริงๆ ผมว่าคนที่จะใช้ docker อย่างน้อยต้องเคยลองเล่น Linux มาบ้าง ถึงจะทำให้เข้าใจง่าย และก็ลืม concept เดิมๆ ที่เคยใช้มาด้วยนะครับ เพราะไม่งั้นเราจะพยายามทำให้มันเป็นแบบเดิม ยัดทุกๆ อย่างเข้าไปใน service เดียว

ส่วนการใช้งานผมว่า พอทำไปซักพัก เราจะเข้าใจเอง คำสั่งใหนที่สงสัยให้เปิดดูใน doc ของ docker มีคำตอบทั้งหมดครับ ขอให้สนุกกับ Docker ครับ ^^

Referent

Download Source code

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.