11/08/2018, 20:56

Deploy Laravel với Docker lên môi trường Production

Xin chào mọi người. Đây là bài viết thứ 2 trong chuỗi bài viết nói về phát triển project Laravel với Docker của mình. Ở bài viết thứ nhất mình đã nói về việc tạo môi trường Development Laravel với Docker ở đây : https://kipalog.com/posts/Thu-cai-dat-moi-truong-docker-cho-laravel Môi ...

Xin chào mọi người.

Đây là bài viết thứ 2 trong chuỗi bài viết nói về phát triển project Laravel với Docker của mình.

Ở bài viết thứ nhất mình đã nói về việc tạo môi trường Development Laravel với Docker ở đây :
https://kipalog.com/posts/Thu-cai-dat-moi-truong-docker-cho-laravel

Môi trường Production mà mình dự định thực hiện sẽ có 1 chút thay đổi so với môi trường development, nhìn qua thì mình nghĩ khác nhau cơ bản ở việc không dùng mount trực tiếp với folder source code, và các service sẽ được phân tán ra các node. Cụ thể mình sẽ trình bày ở dưới đây.

Mình vẫn còn là nhập môn nên hẳn bài viết sẽ có nhiều thiếu sót, hi vọng nhận được góp ý từ mọi người.

Môi trường production

Môi trường production mà mình thực hiện có cấu hình và những phần mềm liên quan mà mình cài trước như sau.

Phần mềm :

  • OS : alphine 3.5.2
  • Docker-Client : 17.06.0-ce
  • Docker-Server: 17.06.0-ce
  • docker-compose : 1.15.0

Phần cứng :
Mình dùng dịch vụ của aws, 3 instance, mỗi instance là t2.micro. Các bạn có thể tìm hiểu thêm trên mạng thông tin về phần này.
Mình đã thử và thấy deploy ổn, đủ để "chạy được" hiện lên dòng chữ "Laravel" khởi đầu.

Mục tiêu sau khi deploy hoàn thành

  • Cài đặt server ở chế độ swarm mode
  • Đưa lên swarm 4 service :

    1. app : chạy container chứa source code và xử lí
    2. web : chạy container nginx
    3. database : chạy container mysql
    4. redis : chạy container redis
  • Dùng browser gõ địa chỉ http đến và thấy giao diện mặc định "Laravel" hiện lên.

Với mục tiêu như vậy mình tiến hành các bước như sau. Đầu tiên mình xin trình bày về cấu trúc thư mục của project :

1. Cấu trúc thư mục

Đầu tiên sau khi xem xét về hoạt động của docker mình tạm thời xây dựng cấu trúc thư mục project như sau :

projectname/
    /www
        /* source code */
    /conf
        web/
            prod.vhost.conf
            dev.vhost.conf
    prod.web.dockerfile
    prod.app.dockerfile
    prod.docker-compose.yml
    dev.web.dockerfile
    dev.app.dockerfile
    dev.docker-compose.yml

Mình không chắc cấu trúc này là tốt nhưng với bối cảnh hiện tại của project mình thấy "đủ dùng" và dễ để diễn giải những gì mình làm dưới đây :

  • /www : chứa source code của project, trong đó sẽ là cây thư mục chuẩn của 1 project laravel như app, resources, database ...
  • /conf : chứa các file config cho các service. Ví dụ hiện tại đang chứa file vhost config cho service web chạy nginx
  • prod.<service-name>.dockerfile : các dockerfile dùng để build service cho môi trường product.
  • prod.docker-compose.yml : docker-compose cho môi trường product.
  • dev.<service-name>.dockerfile : các dockerfile dùng để build service cho môi trường development.
  • dev.docker-compose.yml : docker-compose cho môi trường development

Có 1 số lý do để mình xây dựng cấu trúc trên như sau :

  1. Các file Dockerfile cần ở trên các resource mà nó tham chiếu tới, nên được đặt ngoài cùng. (Nếu không, ta có thể sẽ gặp lỗi tham chiếu ngoài context khi run Dockerfile)
  2. Mình muốn source code độc lập về mặt tính năng liên quan đến laravel, không chứa các file về docker, nên được đặt riêng biệt trong www. Cây thư mục trong www giống với cây thư mục chuẩn của laravel.
  3. docker-compose.yml khác nhau giữa môi trường dev và prod : Môi trường prod khi thực thi ta sẽ sử dụng docker stack deploy, khi ở chế độ này ta sẽ không thể dùng những đặc tả như build , thay vào đó là image (Phải sử dụng 1 image có sẵn thay vì run dockerfile).
  4. Các dockerfile để build các service khác nhau giữa môi trường dev và prod : lý do tương tự như trên, nhưng thêm 1 lý do nữa là các config cho 2 môi trường nhiều khả năng sẽ khác nhau để thuận tiện cho việc phát triển. Ví dụ như ta muốn dùng ssl certificate cho môi trường prod nhưng dev thì không.

Về cấu trúc thư mục như vậy có 1 vấn đề mà mình cũng chưa nghĩ ra cách giải quyết :

  1. Số lượng file nằm ở phân cấp ngoài cùng tỉ lệ thuận với số services, dễ trở nên lộn xộn.
  2. Cấu trúc đơn giản, chưa bao gồm nhiều module liên quan như phpadmin, xdebug..

Với cấu trúc phân cấp như vậy, tiếp theo mình sẽ trình bày theo hướng diễn giải nội dung của từng file đặc tả cho môi trường production. (Với môi trường development, tên file có khác nhưng nội dung thì không khác ở bài 1 mình đã trình bày)

2. prod.docker-compose.yml

Mình sẽ diễn giải từng phần theo từng service, với những mục quan trọng.

2.1. Service app :

version: '3'
services:

  # The Application
  app:
    image: myrepo/laravel-app
    working_dir: /var/www
    volumes:
      - /var/www/storage
    environment:
      - "DB_HOST=database"
      - "REDIS_HOST=cache"
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: "10"
    tty: true
    ports:
      - "9000:9000"
  • version : '3' : Version docker-compose. Version 3 support tốt hơn cho việc deploy lên môi trường production cũng như swarm mode và hiện là version mới nhất hiện nay. Mình nghĩ nên sử dụng version này. Đa số các tutorial trên internet mình tìm thấy verison đang hơi cũ là version 2.

  • image: myrepo/laravel-app : Sử dụng image myrepo/laravel-app cho service. So với môi trường dev tham chiếu đến sourcecode được mount trên host, môi trường prod gần như bắt buộc ta phải sử dụng image. Ta sẽ tạo 1 docker image cho service app này. Chi tiết mình sẽ trình bày ở dưới.

  • volumes : - /var/www/storage : Khai báo rằng ta thư mục /var/www/storage sẽ tồn tại cho dù container tương ứng với nó bị xóa

  • enviroment : Khai báo các biến môi trường, phần này có thể viết vào 1 config option docker-compose cung cấp là env_file

  • env_file : Khai báo địa chỉ biến môi trường. .env.prod mình copy từ .env.example

  • logging : Khai báo cơ chế logging cho service. Mình chọn là kiểu json

  • tty : Cấp tty cho cho ontainer process. Mình bị lỗi container bị dừng, trả về exit code ngay lập tức ngay khi khởi động nếu không khai báo trường này.

  • ports : Khai báo cổng mà service sẽ lắng nghe (từ service web)

2.2. Service web :

# The Web Server
  web:
    image: myrepo/laravel-web
    ports:
      - 80:80
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: "10"
    depends_on:
      - app

image: myrepo/laravel-web : Khai báo container sẽ sử dụng image myrepo/laravel-web. Mình sẽ trình bày cách tạo image này ở phần dưới.
depends_on : Khai báo chỉ ra rằng service này nên được khởi động sau service app (phụ thuộc vào app)

2.3. Service database :

   # The Database
  database:
    image: mysql:latest
    volumes:
      - dbdata:/var/lib/mysql
    environment:
      - "MYSQL_DATABASE=homestead"
      - "MYSQL_USER=homestead"
      - "MYSQL_PASSWORD=secret"
      - "MYSQL_ROOT_PASSWORD=secret"
    ports:
        - "33061:3306"
    logging:
      driver: "json-file"
      options:
        max-size: "200k"
        max-file: 10

Tương tự như môi trường phát triển, cài đặt cho service này trên production cũng không khác ngoài việc mình thêm cấu hình logging

2.4. Service redis :

  # redis
  cache:
    image: redis:3.0-alpine

Ta sẽ dùng service cache với image được build sẵn. Không cần phải thêm config cho service này.

Tiếp theo mình sẽ nói về việc build các image cho các service trên.

3. Build image

Trên môi trường production ta sẽ không up code trực tiếp lên để xử lý tham chiếu đến. Thay vào đó ta sẽ up code vào docker image trên môi trường phát triển, và các image này sẽ được pull về để sử dụng trên môi trường production.

MÌnh sẽ trình bày về cách build 2 image myrepo/laravel-web và myrepo/laravel-app của mình :

myrepo ở đây tên tài khoản của mình trên https://hub.docker.com/. Các bạn sẽ cần tạo tài khoản để quản lý các image nếu chưa có để tiếp tục các bước sau.

3.1. Build image myrepo/laravel-app

3.1.1. Dockerfile để build image :

prod.app.dockerfile

FROM php:7.0.4-fpm

COPY ./www /var/www

WORKDIR /var/www

RUN apt-get update -y && apt-get install -y zip unzip

RUN docker-php-ext-install pdo mbstring

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 
    && php composer-setup.php 
    && php -r "unlink('composer-setup.php');" 
    && php composer.phar install --no-dev --no-scripts 
    && rm composer.phar

RUN chmod -R 777 /var/www/storage 
        /var/www/bootstrap/cache

RUN php artisan key:generate
RUN php artisan config:clear
RUN php artisan config:cache
RUN php artisan optimize
  • Image gốc là php:7.0.4-fpm
  • * Để chạy trên môi trường production không cần toàn bộ source code. Nhưng ta sẽ vẫn sẽ chạy lệnh COPY ./www /var/www và đặt những phần không cần copy vào .dockerignore
  • zip, upzip, pdo, mbstring là những thư viện mình nghĩ cần trong quá trình cài đặt.
  • Tải về và chạy composer để tải thư viện
  • chmod -R 777 /var/www/storage /var/www/bootstrap/cache Set quyền cho phép laravel thao tác với thư mục như tạo logs vào storage
  • php artisan : tạo key.. setup cơ bản của laravel

3.1.2. Chạy lệnh build image :
docker build -f prod.app.dockerfile -t myrepo/laravel-app .

Ta sẽ nhận được thông báo nếu build thành công
Successfully tagged myrepo/laravel-app:latest

(myrepo là account mình trên docker hub)

3.1.3. Push lên docker hub

docker login
docker push myrepo/laravel-app

Như vậy image đã sẵn sàng để pull trên production.
Tiếp theo là image web

3.2 Build image myrepo/laravel-web

3.1.1. Dockerfile để build image :
prod.web.dockerfile

FROM nginx:1.10-alpine

ADD conf/web/prod.vhost.conf /etc/nginx/conf.d/default.conf

COPY ./www/public /var/www/public

copy thư mục public vào image để server tham chiếu các file static nhanh hơn.

/conf/web/prod.vhost.conf tương tự như file vhost trong môi trường phát triển mình đã viết ở bài 1.

3.1.2. Chạy lệnh build image :
docker build -f prod.web.dockerfile -t myrepo/laravel-web .

3.1.3. Push lên docker hub

docker push myrepo/laravel-web

Vậy là mình đã có 2 image laravel-app và laravel-web sẵn sàng để sử dụng trên production.

4. Deploy trên môi trường development

Tiếp theo ta sẽ thử chạy trên giả lập môi trường production trên môi trường developement :

docker swarm init  #init swarm mode
docker stack deploy --compose-file prod.docker-compose.yml mysrv 

deploy thành công, ta có thể check với các service đang chạy với docker service ls và các tiến trình đang chạy với docker ps

my@mymy:~/Projects/mysrv$ docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                       NAMES
811e818e83b9        myrepo/laravel-web:latest       "nginx -g 'daemon ..."   3 minutes ago       Up 3 minutes        80/tcp, 443/tcp             mysrv_web.1.hogvz22nxo19b1w92ev3pu5xz
426b99609e97        myrepo/laravel-app:latest       "php-fpm"                3 minutes ago       Up 3 minutes        9000/tcp                    mysrv_app.1.n763hd569tu5hxd3u6m3ee02e
6a02ce819570        mysql:latest                    "docker-entrypoint..."   3 minutes ago       Up 3 minutes        3306/tcp                    mysrv_database.1.sc8j9xvqmkaotyjpotol9nucf
5238474eba93        redis:3.0-alpine                "docker-entrypoint..."   3 minutes ago       Up 3 minutes        6379/tcp                    mysrv_cache.1.qbtraxvu6kcchiwm6w5sjkgsj

Nếu deploy thành công, giao diện mặc định Laravel nên được hiển thị khi truy cập tới địa chỉ http://0.0.0.0/

Note :
Trong quá trình phát triển ta thường chạy bằng câu lệnh sau :

docker-compose up -f dev.docker-compose.yml

Theo mình có 1 số điểm khác biệt đáng chú ý giữa docker-compose up và docker stack deploy như sau :

  1. Khi dùng docker stack tức là ta đang dùng ở chế độ docker swarm. Các services cũng như container sẽ không chỉ chạy trên 1 máy (node) đủ để phát triển (docker-compose up) mà được phân tán ra nhiều node quản lý bởi swarm.
  2. docker stack hỗ trợ rất nhiều cho deploy trên production. Ví dụ giả sử khi ta dùng docker-compose up, 1 container bị chết sau 5 phút, container này sẽ không được restart lại cho đến khi ta up lại lần nữa. Trong khi docker stack sẽ thường xuyên theo dõi trạng thái các service và có thể restart lại service nếu phát hiện nó đang die tại 1 node nào đó.

1 số điểm khác biệt các bạn có thể tham khảo tại đây

5. Deploy trên môi trường production

Deploy trên môi trường production giống với bước 4 trên môi trường development. Ở đây mình chỉ trình bày 1 số bước ban đầu và tóm tắt lại như sau :

  1. Copy prod.docker-compose.yml vào môi trường prod.
  2. Khởi tạo swarm. docker swarm init
  3. Deploy stack :
    docker stack deploy --compose-file prod.docker-compose.yml mysrv

  4. Kiểm tra các process và các service hoạt động bình thường hay không

    ~/myproject $ docker service ls
    ID                  NAME                        MODE                REPLICAS            IMAGE                           PORTS
    fvoyz57upgxr        mysrv_app                   replicated          1/1                 myrepo/laravel-app:latest   *:9000->9000/tcp
    jq491ivl1cid        mysrv_database              replicated          1/1                 redis:3.0-alpine                *:33061->3306/tcp
    ltr1yqrny80b        mysrv_web                   replicated          1/1                 myrepo/laravel-web:latest   *:80->80/tcp
    s4laek150xv7        dockercloud-server-proxy    global              1/1                 dockercloud/server-proxy        *:2376->2376/tcp
    

Như ở trên mình check được đã 3 service app, database, web đã được docker chạy như trên.

Nếu địa chỉ ip của server là x.x.x.x thì lúc này ta có thể thấy giao diện Laravel mặc định trên http://x.x.x.x

alt text

Tắt stack : docker stack rm mysrv

Bài viết về deploy laravel lên môi trường production của mình kết thúc tại đây. Hi vọng cung cấp được nhiều kinh nghiệm có ích cho các bạn.

Mình đã up source code lên github. Các bạn có thể tham khảo tại đây

Tổng kết

Sử dụng docker cho thao tác deploy lên production mình nghĩ là về mặt cài đặt tiện hơn rất nhiều so với việc phải thao tác thủ công tải thư viện, cài đặt môi trường trên server.

Tham khảo :
https://medium.com/@shakyShane/laravel-docker-part-2-preparing-for-production-9c6a024e9797

TranMy 09-08-2017

0