โพสต์นี้แอดอยากชวนคุยแนวทางในการปรับปรุงแอปพลิเคชันให้ทันสมัยด้วย Container technology ครับ (ไม่ใช่เรื่องใหม่อะไร แต่อยากเขียน 555++)
หลายทานที่ทำงานด้านไอที ปกติก็อยู่กับเซิร์ฟเวอร์แบบ bare metal ที่ติดตั้งระบบปฏิบัติการทั้ง Windows และ Linux รวมไปถึงเครื่อง Storage และ virtual machine
ประสบการณ์ทั้งหมดนี้มัดรวม ๆ กันแล้ว แอดเรียกว่าสาย IT infrastructure (มัดรวม Software และ Network hardware) หรือหากแบ่งตามหน้าที่ความรับผิดชอบก็เรียกว่า IT Generalist ล่ะกัน
ส่วนใหญ่หน้าที่การงานเราก็จะ operate ทั้ง on-premise และ cloud พร้อมเรียนรู้ทักษะใหม่ container platform และ DevOps ครับ (Containerized + Kubernetes environment)
หากถามว่ามีอะไรเป็น common principle ไหม?
ก็คงต้องตอบว่าอยู่ที่ workspaces และ projects ใน environment องค์กรครับ แต่ส่วนตัวมองว่าแนวทางการอัพสกิลจากข้างนอกเป็นสิ่งที่ทำให้คุณได้เรียนรู้ทักษะใหม่ ๆ
อาจมากกว่าใน organization environment ครับ
(ย้ำ ๆๆๆ จากประสบการณ์ส่วนตัวของผมเองนะครับ)
ไม่มีเกริ่นนำ…มาเริ่มกันเลย
(แล้วลากยาวมา 8-9 บรรทัดไม่ได้เรียกว่า “เกริ่นนำ”
เหรอแอดดด 55++)
อธิบายไม่ได้ลึกมากนะครับ ทั้งหมดนี้เพื่อเป็นแนวทางพัฒนาแอปพลิเคชันให้ทันสมัยด้วย Container technology เพราะหากเราต้องการ streamline ให้นักพัฒนาใช้คอนเทนเนอร์และไมโครเซอร์วิส คนที่ทำ IT infrastructure ก็ต้องเข้าใจเทคโนโลยี (อาจจะแค่รู้ เข้าใจ ไม่ถึงกับต้อง experts)
เกริ่นจริง …
หากเราต้องการปรับปรุงประสบการณ์และเติมเต็มเร่งความเร็วของ developer ด้วยแพลตฟอร์มแอปพลิเคชันอัตโนมัติ ไม่ว่าจะเป็น Kubernetes-based container
เทคโนโลยี Container เป็นโซลูชันที่เหมาะสำหรับคุณ …
สำหรับเนื้อหาโพสต์ข้างล่างนี้ เป็นตัวอย่างการติดตั้ง LEMP Stack สำหรับเว็บแอปพลิเคชัน Point of Sale (PoS software) ที่พัฒนาด้วยภาษา PHP
โดยเราจะใช้งาน NGINX เป็น Web Server หลัก และสำหรับการรัน Point of Sale เราจะใช้ NGINX ส่งต่อให้ php7-fpm โดยใช้ fastcgi_pass และฐานข้อมูลเราใช้ MariaDB ครับ
จะไม่ลงรายละเอียดการติดตั้ง Docker เครื่องเซิร์ฟเวอร์นะครับ
Docker is an open-source containerization platform that allows you to quickly build, test, and deploy applications as portable containers that can run virtually anywhere.
ดึง image จาก Docker Hub registry มาใช้งาน
Docker Hub คือที่รวบรวม docker image repository มีทั้งที่เป็น official และ docker image ที่คนอื่น ๆ สร้างขึ้นมา ซึ่งเราสามารถเรียกมาใช้งานได้เลย ตัวอย่าง docker image official สังเกตง่าย ๆ จะมีคำว่า Docker Official Images หรือ Docker Certified ครับ
ซึ่ง docker image จะถูกสร้างมาจาก Dockerfile อีกทีครับ และตัว Dockerfile จะเป็นที่เก็บคำสั่งสำหรับการติดตั้ง packet ต่าง ๆ ที่เกี่ยวข้องกับ image นั้น ๆ เป็นต้น
เตรียมโครงสร้างโดยให้เราสร้างโฟลเดอร์และไฟล์ดังนี้
storage
├── 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
www: เก็บไฟล์ php ที่ใช้รัน (project Point of Sale)
mariadb: โฟลเดอร์ที่เก็บข้อมูลของ mysql ทั้งหมด ไฟล์เดอร์แรก backup เก็บข้อมูลที่ backup เป็น .sql data ข้อมูลจริงที่เป็น Binary ทั้งหมด เราจะ Volume ออกมาเก็บไว้ที่นี่initdb เก็บไฟล์ .sql หากเราต้องการให้มัน import เข้าให้ในครั้งแรก
nginx: โฟลเดอร์ที่เก็บไฟล์คอนฟิกของ nginx ทั้งหมด
สร้าง Docker Compose ไฟล์

สร้างไฟล์ docker-compose.yml
version: '2'
services:
db:
image: mariadb:latest
container_name: phppos_mariadb
restart: always
volumes:
- ./mariadb/initdb/:/docker-entrypoint-initdb.d
- ./mariadb/data/:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=root123
- MYSQL_DATABASE=pos
- MYSQL_USER=pos
- MYSQL_PASSWORD=pos123
php:
build: ./php
container_name: phppos_php-fpm
restart: always
volumes:
- ./www/:/var/www/html
expose:
- "9000"
nginx:
image: nginx:stable-alpine
container_name: phppos_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:
- "8080:80"
memcache:
image: memcached
container_name: phppos_memcached
ports:
- "11211:11211"
pma:
image: phpmyadmin/phpmyadmin
container_name: phppos_phpmyadmin
restart: always
ports:
- "8000:80"
อธิบายคือสำหรับ image ที่เราเลือกใช้ในบทความชุดนี้คือ
nginx ใช้ nginx:alpine
php ใช้ php:7-fpm-alpine
Mariadb ใช้ mariadb:latest
สร้างไฟล์ Dockerfile เพื่อติดตั้ง extension php เพิ่มเติม
ก่อนที่จะได้ container มา docker จะเริ่มจากสร้าง Dockerfile จากนั้นนำไป build เป็น image และนำไปรันเป็น container เพื่อใช้งานต่อไป
FROM php:7.4-fpm
RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
&& docker-php-ext-configure gd mysqli --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd
# TimeZone
RUN cp /usr/share/zoneinfo/Asia/Bangkok /etc/localtime \
&& echo "Asia/Bangkok" > /etc/timezone
EXPOSE 9000
CMD ["php-fpm"]
สร้างไฟล์ www/index.php
<?
php phpinfo();
?>
สร้างไฟล์สำหรับ config nginx
ไฟล์แรก nginx/conf/nginx.conf
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;
}
สร้างไฟล์ nginx/conf.d/default.conf
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 app-frontend.dev;
root /var/www/html;
index index.php;
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;
}
}
ไฟล์ nginx/conf.d/phpmyadmin.conf
#PhpMyAdmin
server {
listen 80;
server_name phpmyadmin.domain.biz;
location / {
proxy_pass http://127.0.0.1:8000;
}
}
ใช้งาน Docker Compose
Docker Compose คืออะไร ???

เป็นไฟล์ YAML ที่เอาไว้ตั้งค่าคอนฟิกต่าง ๆ ซึ่งหากเรา Compose เอง ก็เขียนคอนฟิกเป็นไฟล์นามสกุล YMAL (.yml) ในการเขียนคอนฟิกควบคุม container
หลังจากที่เราได้เตรียมข้อมูลไฟล์ โฟลเดอร์ต่าง ๆ ครบแล้ว ถัดมาเราสามารถใช้งานได้เลย ให้เรา cd เข้าไปที่ไดเรกทอรี storage ที่เราสร้างไว้ จากนั้นรันคำสั่ง build และ start service
docker-compose up -d
จะได้ผลลัพธ์ดังนี้ รอซักครู่ให้มันสร้าง image
Recreating posserver_mariadb ... done
Recreating posserver_php-fpm ... done
Creating posserver_nginx ... done
docker ps
ให้เข้าไปที http://127.0.0.1 จะได้หน้าตา php
ตรวจสอบ services เราสามารถตรวจสอบ services ที่รันแล้วด้วยคำสั่ง
docker-compose ps
เราจะเห็นรายการ service แต่ละตัวสถานะเป็นยังไง และรันที่ port อะไร ชื่อ container อะไร
วิธีการ ssh เข้าไปที่ Web server Container
docker exec -it <container NAME> sh
วิธีการสั่ง start service ทั้งหมด*
docker-compose start

วิธีการสั่งให้หยุดทำงานทั้งหมด
docker-compose stop
วิธีการสั่งให้ stop/start service ตามชื่อที่เราระบุ
docker-compose stop nginx
docker-compose start nginx
วิธีการดู logs ทั้งหมด
docker-compose logs
วิธีการดู logs แยกตามชื่อ service
docker-compose logs php
คำสั่ง down*
จะเป็นการสั่ง stop container และ ลบ containers, networks, volumes และ images ที่สร้างโดย docker-compose up
docker-compose down
ถ้า down จะต้องรันสั่ง build และ start service ใหม่
docker-compose up -d
วิธีการ Import ฐานข้อมูล Mariadb
ให้เราเอาไฟล์ .sql ไปวางในพาท mysql/initdb
ซึ่งในตอนรันครั้งแรกมันจะ import ให้อัตโนมัติ
เพียงแค่คุณทำ volume โฟลเดอร์ที่เก็บไฟล์ .sql แล้วชี้ไปที่ /docker-entrypoint-initdb.d ใน container ครับ
หากดูที่ไฟล์ docker-compose.yml จะเห็นได้ว่าได้ทำ volumes ให้กับ service db ไว้ 2 ไดร์เรกทอรีดังนี้
volumes:
- ./mariadb/initdb/:/docker-entrypoint-initdb.d
- ./mariadb/data/:/var/lib/mysql
mariadb/initdb สำหรับเก็บไฟล์ sql ที่ต้องการให้ import ในตอนแรก
mariadb/data ที่เก็บไฟล์ฐานข้อมูลที่เราทำ volume เข้าไปใน container
เมื่อรันครั้งแรกมันจะเช็คว่าใน mariadb/data ที่เราทำ volume มีข้อมูลหรือยัง ถ้ายังไม่มีจะ import ไฟล์ .sql มาจาก mariadb/initdbให้อัตโนมัติ เพียงแค่นำไฟล์ .sql มาไว้ที่นี่
วิธีการเข้าถึง Database container
docker exec -it <container NAME> sh
เขียนสคริปต์ Backup ฐานข้อมูล Mariadb
สร้างไฟล์ storage/backup.sql ขึ้นมา 1 ไฟล์
แต่เพื่อความสะดวกให้เราเขียนเป็นสคริปต์ไฟล์
สร้างไฟล์ backup.sh
#!/bin/bash
NOW=$(date +"%m-%d-%Y_%H-%M")
FILE="backup-$NOW.sql"
docker exec pos_mariadb sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --databases $MYSQL_DATABASE' > "./mariadb/backup/$FILE"
echo "Backing up data complate."
วิธีรันสคริปต์ Backup
. mysql/backup.sh
ติดตั้ง PhpMyAdmin (หากต้องการใช้งาน)
ให้แก้ไขไฟล์ docker-compose.yml (เพิ่มต่อท้ายไฟล์) แล้วตั้งชื่อ service ใหม่ว่า pma และระบุให้รันที่ port 8000
pma:
image: phpmyadmin/phpmyadmin
container_name: posserver-phpmyadmin
restart: always
ports:
- "8000:80"
จากนั้นรันคำสั่ง up อีกครั้ง
docker-compose up -d
ลองเข้าไปที่ http://127.0.0.1:8000 จะพบหน้าจอของ PhpMyAdmin จากนั้นให้เรากรอกข้อมูล username กับ password ที่เรากำหนดไว้ใน docker-compose ไฟล์ ก็จะสามารถใช้งานได้ปกติ
วิธีการเพิ่ม domain ให้กับ PhpMyAdmin
สร้างไฟล์ชื่อว่า nginx/conf.d/pma.conf
#PhpMyAdmin
server {
listen 80;
server_name phpmyadmin.domina.com;
location / {
proxy_pass http://phpmyadmin.domina.com:8000;
}
}
จากนั้นสั่ง restart service nginx เป็นอันสำเร็จ
docker-compose restart <container NAME>
วิธีการสร้าง Network
ให้แก้ไขไฟล์ docker-compose.yml
ดูรายละเอีดยเพิ่มเติม Network in Compose
จากนั้นรันคำสั่ง up อีกครั้ง
docker-compose up -d
คำสั่ง Docker เบื้องต้น
– port : List port mappings or a specific mapping for the container
– images : แสดงรายละเอียดของ image ที่มีบนเครื่อง server
– ps : แสดงรายละเอียดของ container ที่มีการ start การทำงาน
– pull : ดึง image จาก registry มาใช้งาน
– port : กำหนด port การเชื่อมต่อ ระหว่าง container และเครื่อง server
– search : ตรวจสอบหา image ที่ต้องการใช้งานจาก docker hub
– start : สั่งให้ container เริ่มทำงาน
– stop : สั่งให้ container หยุดทำงาน
– run : ใช้ในการสร้าง container ใหม่ กรณีที่ยัง ไม่มี docker image นั้นๆ บนเครื่อง server คำสั่งนี้จะ pull image ลงมายังเครื่องโดยอัตโนมัติ
– rm : ลบ container ที่ไม่ต้องการใช้งาน
– rmi : ลบ image ที่ไม่ต้องการใช้งาน <IMAGE ID>
- ดูไอพีแอดเดรสบนคอนเทนเนอร์
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <container ID>
- เข้าถึงคอนเทนเนอร์
docker exec -it <container NAME> bash
- ติดตั้งแพคเกจ apt-get update
apt-get install -y vimapt-get install -y iputils-pingapt-get install -y telnet
- ติดตั้งแพคเกจ mysqli extension
docker-php-ext-install mysqli
docker restart phppos_php-fpm
- Import Database
docker exec -i phppos_mariadb mysql --user=phppos --password=pos123 phppos < www/database/database.sql
- เชื่อมต่อ MariaDB จาก Outside เข้ามายังคอนเทนเนอร์
mysql -h <Container_IP_Address> -P 3306 --protocol=TCP -u root -p

แหล่งข้อมูลเพื่อเรียนรู้เพิ่มเติม:
docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
medium.com/yii2-learning
github.com/deenseth/PHP-Point-Of-Sale
linuxize.com/post/how-to-remove-docker-images-containers-volumes-and-networks/