PHP Application Modernization with Container

โพสต์นี้แอดอยากชวนคุยแนวทางในการปรับปรุงแอปพลิเคชันให้ทันสมัยด้วย 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 คืออะไร ???

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' &gt; "./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
PHP Application Modernization with Container

แหล่งข้อมูลเพื่อเรียนรู้เพิ่มเติม:
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/

Scroll to top