Bạn có chắc chắn muốn xóa bài viết này không ?
Bạn có chắc chắn muốn xóa bình luận này không ?
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 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 filevhost
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óaenviroment
: 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 jsontty
: 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






