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 :
- app : chạy container chứa source code và xử lí
- web : chạy container nginx
- database : chạy container mysql
- 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 :
- 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)
- 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.
- 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).
- 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 :
- 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.
- 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 :
- 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.
- 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 :
- Copy prod.docker-compose.yml vào môi trường prod.
- Khởi tạo swarm. docker swarm init
Deploy stack :
docker stack deploy --compose-file prod.docker-compose.yml mysrv-
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
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