สวัสดีครับทุกคน วันนี้จะมาเล่าประสบการณ์การ 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
ตอนที่ 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
จาก 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
domains.conf
จะเป็นไฟล์ที่บอกว่าจะให้สร้าง SSL ของโดเมนไหนบ้าง โดยผมตั้งให้ทำ Wildcard SSL ให้กับ rayriffy.com และทุก subdomain ของ rayriffy.com
*.rayriffy.com rayriffy.com
คราวนี้ก็ต่อด้วย 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
คราวนี้ใน 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
services ที่มาเพิ่มจะมีอยู่ 2 อันคือ เว็บอันนึง และ PHP-FPM อีกอันนึง ซึ่ง PHP-FPM เรา build เอาเองสดๆ ไปดู Dockerfile เอาเอง จะไม่อธิบาย แต่ง่ายๆคือ php-fpm จะฟังอยู่ที่ port 9000 และอย่าลืม volume เว็บให้ path เหมือนกับ container เว็บด้วย
ส่วนตัวเว็บเราก็ Build เองสดๆเหมือนกัน โดยมีโครงสร้างแบบนี้
Dockerfile
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;'"
site-template.conf
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;
}
}
เรารับ 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 กับเว็บของคุณเองได้นะครับ ขอบคุณครับ