วิธีติดตั้ง VPS และ Let’s Encrypt ด้วย Docker Container แบบง่ายๆ
บทความโดย ผศ.ดร.ณัฐโชติ พรหมฤทธิ์
ภาควิชาคอมพิวเตอร์
คณะวิทยาศาสตร์
มหาวิทยาลัยศิลปากร
ในสมัยก่อนหากใครเคย Config Virtual Private Server (VPS) และติดตั้ง SSL Certificate เพื่อรันเว็บไซต์มาบ้าง คงทราบดีว่าไม่ใช่เรื่องง่ายนัก แต่ในปัจจุบันการ Config VPS และติดตั้ง SSL Certificate (โดยเฉพาะ Let’s Encrypt Certificate) บน Docker Container นั้นทำได้ง่ายๆ ใช้เวลาไม่นาน ซึ่งหลักๆ เราจะใช้ Feature Reverse proxy ของ Nginx เพื่อรับ Request จาก Client และ Forward ไปให้ Web Server Container ตามที่เรา Config ไว้
Forward Proxy vs Reverse Proxy
เวลาพูดถึง Proxy เรามักจะหมายถึง Forward Proxy ที่ใช้ในการทำให้ Client "มุด" หรือ Bypass Firewall (ที่มีการ Block IP address ของ Client) ไปถึง Website ได้ นอกจากนี้ Forward Proxy ยังสามารถทำตัวเป็น Cache Server ที่ช่วยลดเวลาในการเข้าถึง Content และลด Internet Bandwidth ด้วย
ในทางตรงกันข้าม Reverse Proxy จะถูกใช้โดย Server ซึ่งเมื่อรับ Request จาก Client แล้วมันจะส่งต่อให้ Server ภายใน Internal Network ดังภาพด้านล่าง
ในการส่งต่อ Client Request นั้น Reverse Proxy จะคอยเช็ค URL จาก HTTP Request Header แล้ว Forward ต่อไปยังปลายทาง
เราสามารถใช้คำสั่ง curl -v เพื่อดู HTTP Header ได้ดังตัวอย่าง
curl -v http://gitlab.cpsudevops.com/
Nginx-Proxy
ด้วยความเร็วในการรองรับ Client Request จำนวนมากพร้อมกันของ Nginx มันจึงเป็นตัวเลือกอันดับต้นๆ สำหรับชาว DevOps ในการทำ Reverse Proxy
เพื่อความสะดวกในการ Config และประหยัด Memory ของ Server เราจะใช้ jwilder/nginx-proxy:alpine เป็น Image เพื่อสร้าง Reverse Proxy Container ครับ
โดยเราจะ Config docker-compose ตามขั้นตอนต่อไปนี้
- Remote Login ไปยัง Cloud Server โดยใช้ ssh
ssh nc-user@labxx.cpsudevops.com
- สร้าง Project ชื่อ nginx_proxy_dock ภายใน Folder มีไฟล์ docker-compose.yml
.
|__ docker-compose.yml
- แก้ไข docker-compose.yml ตามตัวอย่างด้านล่าง
version: '2'
services:
nginx-proxy:
image: jwilder/nginx-proxy:alpine
container_name: nginx-proxy
restart: always
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
networks:
default:
external:
name:
webproxy
- สร้าง Bridge network โดยตั้งชื่อเป็น webproxy (ถ้ายังไม่มี) ด้วยคำสั่ง docker network create
docker network create webproxy
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ตรวจสอบขนาดของ Image
docker images
จะพบว่า jwilder/nginx-proxy Images มีขนาดเพียง 54.6 MB
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
- เมื่อเปิดดู Website โดยระบุ URL เป็นชื่อ Server เช่น http://lab20.cpsudevops.com เราจะไม่สามารถเข้าถึง URL เดิมได้ เพราะยังไม่มีการ Config Domain Name ใดๆ เป็น VPS ครับ
LEMP Stack Container
ทีนี้เราจะ Config LEMP Stack Container โดยกำหนด Virtual Host เป็น www.labxx.cpsudevops.com ตามชื่อเครื่องที่ Add ไว้ใน DNS Server
ซึ่งภายใน Docker จะประกอบด้วย Network 2 วง ได้แก่ "webproxy" Network สำหรับ Reverse Proxy Container และ "web_network" Network สำหรับ LEMP Stack Container
โดย Nginx Web Server ใน LEMP Stack Container ที่เป็นตัวกลางระหว่าง Reverse Proxy และ PHP Container จะต้องทำงานบน Network ได้ทั้ง 2 วง
เราจะ Config LEMP Stack จาก Code ที่ Clone มาจาก Gitlab ตามขั้นตอนดังต่อไปนี้
- Clone LEMP Project จาก Gitlab ตั้งชื่อ Folder เป็น website1
git clone https://gitlab.com/nuttachot_promrit/lemp_dock.git website1
- แก้ไข docker-compose.yml โดยกำหนด VIRTUAL_HOST เท่ากับ www.lab20.cpsudevops.com และกำหนด Network ตามตัวอย่างด้านล่าง
version: '3'
services:
php:
container_name: lemp_php
build: php/
restart: unless-stopped
volumes:
- ./html/:/var/www/html
expose:
- "9000"
depends_on:
- db
nginx:
container_name: lemp_nginx
image: nginx:stable-alpine
restart: unless-stopped
networks:
- webproxy
- default
environment:
VIRTUAL_HOST: www.lab20.cpsudevops.com
volumes:
- ./html/:/var/www/html
- ./nginx/conf/nginx.conf:/etc/nginx/conf/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
expose:
- "80"
db:
container_name: lemp_mariadb
image: mariadb:latest
restart: unless-stopped
volumes:
- ./mariadb/initdb/:/docker-entrypoint-initdb.d
- ./mariadb/data/:/var/lib/mysql/
environment:
- MYSQL_ROOT_PASSWORD=devops101
- MYSQL_DATABASE=devops_db
- MYSQL_USER=devops
- MYSQL_PASSWORD=devops101
networks:
default:
external:
name:
web_network
webproxy:
external:
name: webproxy
- แก้ไขไฟล์ index.php
<?php
$servername = "db";
$username = "devops";
$password = "devops101";
$dbhandle = mysqli_connect($servername,$username,$password);
$selected = mysqli_select_db($dbhandle, "titanic");
echo "Hello : from www.lab20.cpsudevops.com<br>";
echo "Connected database server<br>";
echo "Selected database";
?>
- สร้าง Bridge Network โดยตั้งชื่อเป็น web_network (ถ้ายังไม่มี) ด้วยคำสั่ง docker network create
docker network create web_network
- สร้าง PHP Image ด้วยคำสั่ง docker-compose build
docker-compose build
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
- เปิดดู Website ที่รันบน LEMP Stack Container โดยระบุ URL ที่เป็นชื่อ VPS ของเราเอง เช่น http://www.lab20.cpsudevops.com
Multiple Websites on Docker Container
Config LAMP Stack Container เพิ่มอีก 1 Website โดยกำหนด Virtual Host เป็น service.labxx.cpsudevops.com ตามชื่อเครื่องที่ Add ไว้ใน DNS server
- Clone LEMP Project จาก Gitlab ตั้งชื่อ Folder เป็น website2
git clone https://gitlab.com/nuttachot_promrit/lemp_dock.git website2
- แก้ไข docker-compose.yml โดยกำหนด VIRTUAL_HOST เท่ากับ service.lab20.cpsudevops.com และกำหนด Network ตามตัวอย่างด้านล่าง
version: '3'
services:
php:
container_name: lemp_php2
build: php/
restart: unless-stopped
volumes:
- ./html/:/var/www/html
expose:
- "9000"
depends_on:
- db
nginx:
container_name: lemp_nginx2
image: nginx:stable-alpine
restart: unless-stopped
networks:
- webproxy
- default
environment:
VIRTUAL_HOST: service.lab20.cpsudevops.com
volumes:
- ./html/:/var/www/html
- ./nginx/conf/nginx.conf:/etc/nginx/conf/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
expose:
- "80"
db:
container_name: lemp_mariadb2
image: mariadb:latest
restart: unless-stopped
volumes:
- ./mariadb/initdb/:/docker-entrypoint-initdb.d
- ./mariadb/data/:/var/lib/mysql/
environment:
- MYSQL_ROOT_PASSWORD=devops101
- MYSQL_DATABASE=devops_db
- MYSQL_USER=devops
- MYSQL_PASSWORD=devops101
networks:
default:
external:
name:
web_network2
webproxy:
external:
name: webproxy
- แก้ไขไฟล์ index.php
<?php
$servername = "db";
$username = "devops";
$password = "devops101";
$dbhandle = mysqli_connect($servername,$username,$password);
$selected = mysqli_select_db($dbhandle, "titanic");
echo "Hello : from service.lab20.cpsudevops.com<br>";
echo "Connected database server<br>";
echo "Selected database";
?>
- สร้าง Bridge Network โดยตั้งชื่อเป็น web_network2 ด้วยคำสั่ง docker network create
docker network create web_network2
- ดู Docker Network ทั้งหมด
docker network ls
- สร้าง php Image ด้วยคำสั่ง docker-compose build
docker-compose build
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
- เปิดดู Website ที่รันบน LEMP Stack Container โดยระบุ URL ที่เป็นชื่อ VPS ของเราเอง เช่น http://service.lab20.cpsudevops.com
Let’s Encrypt
Let’s Encrypt เป็นโครงการของ Internet Security Research Group (ISRG) ที่ทำให้เราสามารถออก SSL Certificate ได้ฟรีโดยไม่มีค่าใช้จ่าย
เพื่อจะ Config Let’s Encrypt บน Docker Container เราจะต้องจัดเตรียมองค์ประกอบต่างๆ ได้แก่
- Domain Name
- jwilder/nginx-proxy Container
- Web Server Container
- jrcs/letsencrypt-nginx-proxy-companion Container (ทำงานร่วมกับ Reverse Proxy Container ในการสร้าง การ Renew และใช้งาน Let's Encrypt Certificates)
และจะต้อง Config nginx-proxy และ letsencrypt-nginx-proxy-companion ให้มีการแชร์ Volume ร่วมกัน 4 Volume
/etc/nginx/vhost.d
/usr/share/nginx/html
/etc/nginx/certs
/etc/nginx/dhparam
โดยมีขั้นตอนดังต่อไปนี้
- Stop/Delete Container ใน Project nginx_proxy_dock ด้วยคำสั่ง docker-compose down และลบ image ทั้งหมดด้วย parameter --rmi all
docker-compose down --rmi all
- แก้ไข docker-compose.yml ใน Project nginx_proxy_dock ตามตัวอย่างด้านล่าง
version: '2'
services:
nginx-proxy:
image: jwilder/nginx-proxy:alpine
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- certs:/etc/nginx/certs:ro
- dhparam:/etc/nginx/dhparam
- /var/run/docker.sock:/tmp/docker.sock:ro
letsencrypt:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: nginx-proxy-lets
volumes_from:
- nginx-proxy
volumes:
- certs:/etc/nginx/certs:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- nginx-proxy
volumes:
vhost:
html:
certs:
dhparam:
networks:
default:
external:
name:
webproxy
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
- Stop Container ใน Project website1 ด้วยคำสั่ง docker-compose stop
docker-compose stop
- แก้ไข docker-compose.yml ใน Project website1 โดยเพิ่ม Environment Variable ตามตัวอย่างด้านล่าง
environment:
VIRTUAL_HOST: www.lab20.cpsudevops.com
LETSENCRYPT_HOST: www.lab20.cpsudevops.com
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Containers ที่รันทั้งหมด ตามที่ docker-compose.yml ดูแล
docker-compose ps
- เปิดดู Website ที่รันบน LEMP Stack Container โดยระบุ URL ที่เป็นชื่อ VPS ของเราเอง เช่น https://www.lab20.cpsudevops.com
- กดที่รูปกุญแแจเพื่อดู Let’s Encrypt Certificate
- Stop Container ใน Project website2 ด้วยคำสั่ง docker-compose stop
docker-compose stop
- แก้ไข docker-compose.yml ใน Project website2 โดยเพิ่ม Environment Variable ตามตัวอย่างด้านล่าง
environment:
VIRTUAL_HOST: service.lab20.cpsudevops.com
LETSENCRYPT_HOST: service.lab20.cpsudevops.com
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Containers ที่รันทั้งหมด ตามที่ docker-compose.yml ดูแล
docker-compose ps
- เปิดดู Website ที่รันบน LEMP Stack Container โดยระบุ URL ที่เป็นชื่อ VPS ของเราเอง เช่น https://service.lab20.cpsudevops.com
Bonus!
เพื่อให้การเข้าถึงฐานข้อมูลทำได้ง่ายขึ้น เราจะ Config phpMyAdmin ใน LEMP Stack Container ขึ้นเป็น VPS ที่ใช้ Let's Encrypt Certificate ตามขั้นตอนต่อไปนี้
- Stop Container ใน Project website1 ด้วยคำสั่ง docker-compose stop
docker-compose stop
- แก้ไข docker-compose.yml ใน Project website1 โดยเพิ่ม phpMyAdmin Container ตามตัวอย่างด้านล่าง
pma:
container_name: lemp-phpmyadmin
image: phpmyadmin/phpmyadmin
restart: always
networks:
- webproxy
- default
environment:
VIRTUAL_HOST: mydb.lab20.cpsudevops.com
LETSENCRYPT_HOST: mydb.lab20.cpsudevops.com
expose:
- "80"
- รัน Container ด้วย docker-compose และกลับไปที่ Terminal โดยใช้ parameter -d
docker-compose up -d
- ดู Container ที่กำลังรันทั้งหมดที่ docker-compose.yml ดูแล
docker-compose ps
- เปิดดู Website ที่รัน phpMyAdmin โดยระบุ URL ที่เป็นชื่อ VPS ของเราเอง เช่น https://mydb.lab20.cpsudevops.com แล้ว Login เป็น root ด้วย Password ที่กำหนดไว้ในไฟล์ docker-compose.yml
- ดู Titanic Database
สรุป
- ในบทความนี้เราได้ทำความเข้าใจแแนวคิดของ Forward Proxy และ Reverse Proxy
- การ Config Nginx-proxy Container
- การสร้าง VPS ด้วย LEMP Stack Container แบบหลาย Website
- การ Config Let’s Encrypt
- รวมทั้งการ Config phpMyAdmin
เนื้อหาในบทความตอนต่อไปเราจะได้ทดลองทำ Microservice บน Docker Container กันครับ
รายวิชา Dev-Ops and Cloud Engineering 101