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

Trần Mỹ viết ngày 29/11/2017

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-webmyrepo/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-applaravel-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 updocker 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

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Trần Mỹ

9 bài viết.
85 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
38 9
XIn chào mọi người. Thời gian gần đây mình có tìm hiểu về blockchain và golang. Mình viết bài viết này với mục đích chia sẻ và tổng hợp những kiến...
Trần Mỹ viết 6 tháng trước
38 9
White
28 5
Xin chào mọi người. Thời gian ngắn gần đây mình có tìm hiểu 1 chút về Bitcoin và Blockchain, và để củng cố kiến thức thu nạp được mình quyết định ...
Trần Mỹ viết 9 tháng trước
28 5
White
15 1
Xin chào mọi người. Đây là phần 2 trong bài viết về xây dựng blockchain đơn giản với golang của mình. Ở (Link) mình đã trình bày về việc xây dựng...
Trần Mỹ viết 6 tháng trước
15 1
Bài viết liên quan
White
8 0
Một trong những trường hợp build docker image là thừa hưởng từ một image mẹ Lấy một ví dụ sau: Dockefile FROM my_repo/my_image ENTRYPOINT ech...
Lơi Rệ viết gần 3 năm trước
8 0
White
16 0
Công cụ này dành riêng cho những người lười muốn muốn tiết kiệm thời gian thao tác với docker bằng dòng lệnh với các lợi ích sau: + Tiết kiệm thời...
Phí Ngọc Chi viết hơn 1 năm trước
16 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
9 bài viết.
85 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!