วิธีติดตั้ง VPS และ Let’s Encrypt ด้วย Docker Container แบบง่ายๆ

VPS and Let’s Encrypt with 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 ด้วย

Forward Proxy

ในทางตรงกันข้าม Reverse Proxy จะถูกใช้โดย Server ซึ่งเมื่อรับ Request จาก Client แล้วมันจะส่งต่อให้ Server ภายใน Internal Network ดังภาพด้านล่าง

Reverse Proxy

ในการส่งต่อ 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
|__ html/
|   |__ index.php
|__ nginx
|  |__ conf/
|  |  |__ nginx.conf
|  |__ conf.d/
|     |__ default.conf
|__ mariadb/
|  |__ data/
|  |__ initdb/
|  |  |__ tinanic.sql
|  |__ backup/
|__ php/
   |__ Dockerfile
โครงสร้างของ LEMP Stack Project
  • แก้ไข 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 กันครับ

ขอขอบคุณ Nipa.Cloud ที่ให้การสนับสนุน Environment ในการเรียนการสอน
รายวิชา Dev-Ops and Cloud Engineering 101