最近,看到很多小伙伴在讨论开发栈,我也来分享下我的开发栈。
一开始我也是走过本地自建、PHP study、Homestead 这些路,但是一遇到要同时支持多个版本就变得很麻烦。后来就开始用 docker,一个项目一个环境,互不干扰,互不影响,配置好后,一键启动关闭,真的不要太省心。期间有不断适应调整,基于当前开发栈已经用了4年,公司项目加上自己的娱乐项目,有40+个项目,PHP 的版本从 5.6 到 8.3 每个主版本都有。
架构大概就这个样子:
PHP 镜像 php-nginx-alpine
最开始是用的 Laradock,但这玩意儿里面东西太多了,调整起来很麻烦。后来就想着自建镜像,于是基于 Laravel 项目要求,东学西凑的编写自己的 Dockerfile,编译自己的镜像。自己建就简单了,只需要能让项目跑起来的东西就够了。
这个镜像主要包含两部分,PHP 和 Nginx,具体内容详见: https://github.com/chuoke/php-nginx-alpine。
后来工作公司用的 ThinkPHP,于是将 Laravel 和 ThinkPHP 的默认 nginx 配置内置进来,添加了启动脚本。
反向代理 Treafik
项目多,端口绑定就会变得混乱,于是开始加反向代理,只需要绑定 80 端口就行。刚开始用的 Nginx Manager,但是吧,这个东西配置之后,要是某个项目没运行,就会报错,反正折腾了一个周就放弃了。然后就看到 Treafik,这家伙最开始好像是专为 docker 反向代理而生,是开源的。
Treafik 的配置超灵活,只要首次配置好,启动项目就可以开始代理,停止就注销,完全不用管其它的,感兴趣可以看它的文档 Traefik Proxy 文档。
这里给出我本地的配置:
# docker-compose.yml
services:
reverse-proxy:
image: traefik:v2.8
container_name: traefik
restart: unless-stopped
ports:
- target: 80
published: 80
mode: host
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml
- ./conf:/var/usr/traefik/conf
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik-dash.entrypoints=web"
- "traefik.http.routers.traefik-dash.rule=Host(`traefik.test`)"
- "traefik.http.routers.traefik-dash.service=api@internal"
# traefik.yml
# Docker configuration backend
providers:
docker:
exposedByDefault: false
file:
directory: "/var/usr/traefik/conf"
watch: true
# API and dashboard configuration
api:
# insecure: true
dashboard: true
## Static configuration
entryPoints:
web:
address: ":80"
forwardedHeaders:
insecure: true
web-secure:
address: ":443"
forwardedHeaders:
insecure: true
主要就两个文件,docker-compose.yml 和 traefik.yml,4年前写的配置就这样,中间就做了下版本升级,是不是超省心。
Acrylic DNS
我这项目是真的多,自带的 DNS 不支持通配符,加上 hosts 会经常莫名其妙的被重置,于是就找到了一个本地 DNS 服务–Acrylic DNS。可以设置开机启动,配置是在它独立 accrylic host 文件,除基础的接口,还支持通配符,不会和主机的 hosts 冲突。
我还体验过其它几个,但都没让我满意,就这个最方便好用,遗憾的是,它只支持 Windows 系统。
其它服务
另外项目基本都会用到 MySQL 、Redis 这些,也都是 docker 方式运行。
项目环境
docker 中的每个系统都是独立的,那么如何让他们串在一起相互访问呢?那就是通过网络,只要让他们在同一个网络,就可以相互访问到。以下展示我本地的一个项目案例说明:
networks:
traefik:
name: traefik_default
external: true
mysql:
name: mysql_default
external: true
redis:
name: redis_default
external: true
services:
app:
image: chuoke/php-nginx-alpine:8.2
container_name: ${COMPOSE_PROJECT_NAME}
environment:
- APP_CODE_PATH=${APP_CODE_PATH}
# NGINX_SERVER_TYPE: laravel(默认) thinkphp
- NGINX_SERVER_TYPE=laravel
# 这里是示例,NGINX_SERVER_ROOT 在 laravel 下默认就是这个
- NGINX_SERVER_ROOT=${APP_CODE_PATH}/public
working_dir:
${APP_CODE_PATH}
volumes:
# 这个可以提升下性能
- ${APP_CODE_PATH}/vendor
- ${APP_PATH_HOST}:${APP_CODE_PATH}
- ./nginx/logs:/var/log/nginx
# 如果需要,可以自定义 nginx 站点配置,就不需要上面的环境变量
# - ./nginx-site.conf:/etc/nginx/sites-available/site.conf
# 已默认启动 nginx 和 php-fpm,可以自定义
# - ./supervisord.conf:/etc/supervisor/conf.d/supervisord.d/site.conf
# 让该服务加入以下网络,就可以访问它们对应的服务啦
networks:
- traefik
- mysql
- redis
expose:
- 80
# 让 treafik 知道,我要加个代理转发服务
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_default"
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}-router.rule=HOST(`laravel-11.test`)"
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}-router.entrypoints=web"
supervisord.conf
中设置的是相关启动服务,主要启动并守护服务进程,这里主要有3个:php-fpm、nginx、scheduler:
[program:php-fpm]
command=php-fpm -F
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=3
[program:nginx]
command=nginx
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=3
[program:laravel-scheduler]
process_name=%(program_name)s_%(process_num)02d
command=/bin/sh -c "while [ true ]; do (php /var/www/site/artisan schedule:run --verbose --no-interaction &); sleep 60; done"
; command=/bin/sh -c "php /var/www/site/artisan schedule:work"
autostart=true
autorestart=true
numprocs=1
user=www-data
redirect_stderr=true
这样就可以启动项目了,另外要注意一点,在项目里访问其它服务,需要使用设置的对应网络的名称,比如 MySQL,根据这个示例,配置就得是这样: DB_HOST=mysql
,此处的 mysql
是 MySQL 服务在名为 mysql_default
网络中的名字,可以通过 docker network inspect mysql_default
查看,当然也可以把 MySQL 服务加入其它网络中。