มาทำเว็บยุ่งๆ ให้สวยงามด้วย Docker กันครับ

Written by Phumrapee Limpianchop on 20 September, 2018

สวัสดีครับทุกคน วันนี้จะมาเล่าประสบการณ์การ renovate เว็บ rayriffy.com จากเดิมที่เป็น standalone กลายมาเป็น Docker กันนะครับ

ตอนที่ 1: ออกแบบโครงสร้างของระบบ

ระบบที่วางเอาไว้คือ จะให้ Traffic ทั้งหมดไปที่ container ที่ชื่อว่า proxy โดย proxy จะเป็นตัวกลางสื่อสารระหว่าง Network ภายนอกกับ Network ภายใน

ส่วนด้านในก็จะเป็นเว็บต่างๆ โดยจัดไว้แบบหนึ่งเว็บต่อ container แล้วก็มีของจุกจิกอื่นๆนิดหน่อยเช่น php-fpm คือ…ต้องเข้าใจนะว่าผมสาย Laravel ผสม Vue.js ซึ่ง Laravel มันก็ต้องใช้ php-fpm เหมือนกัน แล้วทั้งหมดนี้ก็จะอยู่ใน backend Network ที่ไม่สามารถเข้าถึงได้โดยตรงจากภายนอก

จากตรงนี้ง่ายๆเลยคือมีแค่ proxy เท่านั้นที่ออกคุยกับโลกภายนอกได้

ตอนที่ 2: เตรียมตัวก่อนเริ่มงาน

โปรเจคสุด Masterpiece นี้สามารถหาดูได้บน GitHub นะครับ จุ๊บๆ

แน่นอนว่าเราต้องลง Docker CE และ Docker Compose ให้เรียบร้อยก่อน โดยจะแปะ tutorial ไว้ให้

หลังลงเสร็จแล้วก็มาต่อกันได้เลย ไฟล์หลักที่เราจะทำงานกันคือ docker-compose.yml โดยเริ่มต้นเราก็จะมาสร้าง Network ให้กับระบบเราก่อน ซึ่งจะมีอยู่ 2 อันคือ frontend กับ backend

version: '3'
networks:
frontend:
driver: bridge
backend:
driver: bridge
view raw docker-compose.yml hosted with ❤ by GitHub

ตอนที่ 3: เริ่มสร้าง proxy อันน่ารักของเรากัน~

อย่างแรกก่อนที่จะมี proxy เลยคือเราต้องมี SSL Certificate ก่อน โดยผมก็ไปหา Images ที่สามารถสร้าง SSL ด้วย Let’s Encrypt ได้ แถมทำให้เองได้กับ Cloudflare API ด้วย!? เลยจัดซะเลย

services:
certbot:
image: adferrand/letsencrypt-dns:2.5.3
container_name: certbot
restart: unless-stopped
env_file:
- ./certbot/build/env
volumes:
- ./certbot/dist/domains.conf:/etc/letsencrypt/domains.conf
- ./tmp/letsencrypt:/etc/letsencrypt
networks:
- backend
view raw docker-compose.yml hosted with ❤ by GitHub

จาก config ผมได้ volume folder ของ /etc/letsencrypt ไปใส่ที่ ./tmp/letsencrypt อันนี้จะเอาไว้ให้ proxy เรียก SSL certificate มาใช้ และคราวนี้ก็จะสังเกตุเห็นว่าผมมีการอ้างอิงไฟล์ 2 ตัวคือ env กับ domains.conf โดยมันจะมีเหตุผลของมันอยู่

env จะเป็น Environment Variables ที่เอาไว้ใช้ Build โดยหลักๆที่ตั้งคือ Email และรายละเอียดของ Cloudflare

LETSENCRYPT_USER_MAIL=example@example.com
LEXICON_PROVIDER=cloudflare
LEXICON_CLOUDFLARE_USERNAME=example@example.com
LEXICON_CLOUDFLARE_TOKEN=example
view raw env hosted with ❤ by GitHub

domains.conf จะเป็นไฟล์ที่บอกว่าจะให้สร้าง SSL ของโดเมนไหนบ้าง โดยผมตั้งให้ทำ Wildcard SSL ให้กับ rayriffy.com และทุก subdomain ของ rayriffy.com

*.rayriffy.com rayriffy.com
view raw domains.conf hosted with ❤ by GitHub

คราวนี้ก็ต่อด้วย proxy โดยผมเลือกที่จะใช้ NGINX เป็น Web Server

version: '3'
networks:
frontend:
driver: bridge
backend:
driver: bridge
services:
certbot:
image: adferrand/letsencrypt-dns:2.5.3
container_name: certbot
restart: unless-stopped
env_file:
- ./certbot/build/env
volumes:
- ./certbot/dist/domains.conf:/etc/letsencrypt/domains.conf
- ./tmp/letsencrypt:/etc/letsencrypt
networks:
- backend
proxy:
image: nginx:1.15.3-alpine
container_name: proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf/web:/etc/nginx/conf.d
- ./nginx/conf/module:/etc/nginx/snippets
- ./tmp/letsencrypt:/etc/letsencrypt
depends_on:
- certbot
networks:
- frontend
- backend
view raw docker-compose.yml hosted with ❤ by GitHub

คราวนี้ใน volumes ก็จะมีพวกไฟล์ config ของแต่ละ domain อยู่ที่ ./nginx/conf/web โดยตัวอย่างง่ายๆจะประมาณนี้

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name blog.rayriffy.com;
include snippets/_ssl.conf;
location / {
proxy_pass http://web-blog-rayriffy-com;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
server {
listen 80;
listen [::]:80;
server_name blog.rayriffy.com;
return 301 https://blog.rayriffy.com$request_uri;
}

config ตัวนี้จะบังคับให้ redirect ทุก HTTP Request ไป HTTPS แล้วส่ง proxy ไปที่ http://web-blog-rayriffy-com โดย web-blog-rayriffy-com จะเป็นชื่อ container ที่จะ deploy ต่อค่อยเอาไว้มาอธิบาย แต่ concept คือไม่จำเป็นต้องกำหนด IP เพราะ Docker จะจัดการ DNS ทุกอย่างไว้ให้แล้วตามชื่อ container ขอแค่ให้เชื่ออยู่ใน Network เดียวกันก็พอ

ส่วนตั้งค่า SSL จะตั้งยังไงก็ตั้งกันเองเลยเต็มที่

อ่อลืมอธิบายเรื่องเกี่ยวกับ depends_on

ตอนที่ 4: ลงเว็บ (ของจริงล่ะๆ)

มาลงเว็บกันเลย เริ่มตั้งแต่ docker-compose.yml

version: '3'
networks:
frontend:
driver: bridge
backend:
driver: bridge
services:
certbot:
image: adferrand/letsencrypt-dns:2.5.3
container_name: certbot
restart: unless-stopped
env_file:
- ./certbot/build/env
volumes:
- ./certbot/dist/domains.conf:/etc/letsencrypt/domains.conf
- ./tmp/letsencrypt:/etc/letsencrypt
networks:
- backend
php-fpm-72:
build:
context: ./php-fpm/build/72
dockerfile: Dockerfile
container_name: php-fpm-72
networks:
- backend
volumes:
- ./web/data/html:/web
proxy:
image: nginx:1.15.3-alpine
container_name: proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf/web:/etc/nginx/conf.d
- ./nginx/conf/module:/etc/nginx/snippets
- ./tmp/letsencrypt:/etc/letsencrypt
depends_on:
- certbot
networks:
- frontend
- backend
web-blog-rayriffy-com:
build:
context: ./web/build/html
dockerfile: Dockerfile
container_name: web-blog-rayriffy-com
restart: unless-stopped
environment:
- SERVER_NAME=blog.rayriffy.com
- PHP_BACKEND=php-fpm-72
- ROOT=/web/blog.rayriffy.com
depends_on:
- php-fpm-72
volumes:
- ./web/data/html/blog.rayriffy.com:/web/blog.rayriffy.com
networks:
- backend
view raw docker-compose.yml hosted with ❤ by GitHub

services ที่มาเพิ่มจะมีอยู่ 2 อันคือ เว็บอันนึง และ PHP-FPM อีกอันนึง ซึ่ง PHP-FPM เรา build เอาเองสดๆ ไปดู Dockerfile เอาเอง จะไม่อธิบาย แต่ง่ายๆคือ php-fpm จะฟังอยู่ที่ port 9000 และอย่าลืม volume เว็บให้ path เหมือนกับ container เว็บด้วย

ส่วนตัวเว็บเราก็ Build เองสดๆเหมือนกัน โดยมีโครงสร้างแบบนี้

FROM nginx:1.15.3-alpine
COPY site-template.conf /etc/nginx/conf.d/site.template
CMD sh -c "envsubst '\$SERVER_NAME \$ROOT \$PHP_BACKEND' < /etc/nginx/conf.d/site.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
view raw Dockerfile hosted with ❤ by GitHub
server {
listen 80;
listen [::]:80;
index index.html index.php;
server_name ${SERVER_NAME};
root ${ROOT};
error_log /var/log/nginx/${SERVER_NAME}.error.log error;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass ${PHP_BACKEND}:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_intercept_errors off;
fastcgi_hide_header X-Powered-By;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
include fastcgi_params;
# Prevents URIs that include the front controller. This will 404:
# http://domain.tld/index.php/some-path
# Remove the internal directive to allow URIs like this
internal;
}
}
view raw site-template.conf hosted with ❤ by GitHub

เรารับ environment SERVER_NAME ROOT PHP_BACKEND จาก docker-compose.yml เพื่อเอามาวางในไฟล์ site-template.conf ข้อดีของการทำแบบนี้คือภายใน Dockerfile ตัวเดียว จะสามารถใช้กับ domain อื่นๆได้อีก ไม่จำเป็นต้องเขียนทีละอัน

เราก็ volume data เว็บทั้งหมดแล้วกำหนด ROOT ให้ถูก ตั้ง SERVER_NAME ให้ดี แล้วก็บอก PHP_BACKEND ที่ต้องการให้ fast_cgi ใช้

ตอนที่ 5: ลองรันมันดู

คำสั่งเดียวง่ายๆ

$ docker-compose up

แค่นี้ก็ได้ blog.rayriffy.com แล้วแบบง่ายๆ

สรุป

คราวนี้ก็จะได้มาแล้ว 1 domain ที่เหลือแค่ทำแบบเดิมคล้ายๆกันไปเรื่อยๆจนเสร็จตามที่ต้องการ :) ถ้าต้องการดูอะไรที่ละเอียดกว่านี้ก็ลองไปดู GitHub ผมแล้วขุดๆคุ้ยๆดู หวังว่าจะได้ concept การทำเว็บแบบ Dockerized ของผมกันนะครับ

ว่างๆก็ลองเอาไป Implement กับเว็บของคุณเองได้นะครับ ขอบคุณครับ



HomeCategoriesAuthors