首页 > 其他 > 详细

laravel后端项目的Docker镜像打包

时间:2020-05-12 09:11:35      阅读:215      评论:0      收藏:0      [点我收藏+]

对读者的要求

  • 后端开发基础知识
  • 掌握Docker基础用法(有laradock使用经验为佳)
  • Laravel基础

简介

在第一篇文章《培训-纯前端项目的Docker镜像打包》中,提到后端项目的镜像打包方面比较复杂,需要独立一篇。我们的目标仍然是执行一条命令就要能完成应用的启动。纯前端项目使用的是docker run,后端项目需要用到镜像编排(docker-comopse),启动命令变成:

docker-compose up -d --renew-anon-volumes

docker-compose使用最新版本,否则--renew-anon-volumes参数可能不支持

概念

在谈论后端的镜像打包和镜像测试之前,首先要认识到Docker要用得好,需要满足两大特征:

  1. 无状态
  2. 单一职责

无状态是指容器不会保存任何东西,不论往容器里面写入任何东西,再次执行docker-compose up -d,启动后的容器是找不到原来写入的东西的。有些场景如果需要保存数据,比如数据库、队列和存储,它们需要通过挂载保证写入的数据不因为重启而丢失,这类容器称为有状态容器。

要保证应用的水平扩展快速迁移,所有的应用在启动时必须是无状态的。试想一下,你的应用原本在A机器上执行一句docker-comopse up -d就行了,现在要改到B机器上运行,就在B机器上执行一句docker-compose up -d。如果它是有状态的(比如A在启动应用的时候,将存储挂载到主机,以便用户上传的图片和应用产生的日志可以永久保留),那么迁移到B机器时,它的日志和图片仍然在A机器上,快速迁移受到了限制。
另一个更常见的场景是,A,B两台机器同时启动以便负载均衡,如果A和B是有状态的,就会出现跟迁移一样的问题,A和B都会遗漏用户上传的图片,这就限制了水平扩展。因此,应用必须是无状态的。

单一职责简单说就是只干一件事,反映到Docker上,就是最好只启动一个进程(取决于对职责的解释,也可能启动多个进程)。比如php-fpm镜像只能启动php-fpmnginx镜像只启动nginx。要做到将nginxphp-fpm都在同一个里容器里启动当然是可以的,但是它带来的代价往往比比好处更大。越多不同职责的功能塞到同一个容器里,代价就越大。以Laravel工程为例,有4个不同职责的进程:nginxphp-fpmphp队列cron定时任务。混在一起以后,第一个问题是:任何一块有更新,都要重新打包这个镜像,而通常这个镜像里面的内容比较多,通常会很大;第二个问题是职责的混合导致它很难与已有的系统结合,典型的是我们只需要php-fpmnginx由外部提供,如果打包到一起,nginx会产生干扰;另一个典型的场景是暂时不需要用到php队列cron定时任务,它们也必须开着;第三个则是日志信息的混乱,nginxphp-fpm的日志混在一起无法有效地提取。

后端应用要满足这2个特征,就带来2个直接的问题:

  • 后端访问的数据库、队列和存储放哪里?
  • nginx、php-fpm、php队列、定时任务怎么编排?

所有后端应用的配置都是比较敏感,比如数据库密码,是不能打包到镜像中的,于是就有了第3个问题:

  • 敏感信息怎么保护?

关键问题的分析

后端镜像的打包,重点在于思考清楚这3个问题。下面我们一个个问题的分析。

Q:后端要访问的数据库、队列和存储放哪里?
A:这三个需要与应用分开单独考虑,在生产环境下,一般使用一台独立的机器,或者直接使用云服务商提供的现成服务。比如数据库使用mysql或mariadb,队列使用redis或rabbitmq,存储使用对象存储(aws的s3,阿里的oss,或者自己搭建的minio)。不论应用迁移到哪里,都要使用同一份数据库、队列和存储。在演示的时候,我们将在本地搭建mariadb, redis, minio。

Q:nginx、php-fpm、php队列、php定时任务怎么编排?
A:单一职责在实践上是有争议的。如果是要求容器只启动一个进程的角度,将nginxlaravel php-fpmlaravel队列laravel定时任务独立成4个任务,如果要检查nginx证书是否过期,还得有certbot,共5个任务。一个简单的后台应用要有5个任务,听起来头就很大。

另外一种对单一职责的解释则是从就应用的范围去解释。nginxphp-fpm管的东西八竿子打不着,所以肯定是独立的两个镜像容器。但是上面的nginxcertbot要合在一起,而laravel php-fpmlaravel 队列laravel 定时任务也要合在一起,镜像编排时只启动2个容器。

这两种解释目前我们都接受,应用小的时候采用后一种方式,应用变大时就过度到第一种方式。

Q:敏感信息怎么保护?
A:通过环境变量的方式注入,。不论是docker-compose,还是云容器,都有提供配置环境的方式。

Laravel自身的问题

如果说前面的问题主要由Docker的特征导致,那么下面的这几个问题,则是由Laravel应用本身引起(其他语言的后台应用也有类似的问题),它们也使镜像打包变得复杂:

  • 升级版本时,数据库迁移怎么做?
  • 日志放哪里?
  • 性能数据的采集放哪里?

这些问题在这里抛出由读者思考,以后有机会我们专门讲讲,这里的镜像打包有对其中的一些问题做了处理。

目录结构

脚本文件比较多,要么将后端与脚本独立成2个项目,要么调整项目的总体目录结构

backend/
- Dockerfile 
- scripts/
    - start.sh
    - crontab
    - worker.conf
    - build.sh
    - push.sh
- ...其他Laravel文件直接忽略
scripts/
- nginx/ # nginx 镜像制作 
    - Dockerfile
    - app.conf
    - nginx.conf
    - build.sh
    - push.sh
- prod/ # 生产环境的镜像编排测试
    - docker-compose.yml
    - update.sh
    - app-backend.env
- prod-local/ # 本地镜像编排测试
    - docker-compose.yml
    - update.sh
    - app-backend.env

步骤

制作php-fpm镜像配置

先直接看Dockerfile,然后对这个文件做详细解释:

FROM pheye/php-fpm:latest

MAINTAINER LIUWENCAN <phenye@gmail.com>

# 将源码拷到镜像中
COPY . /var/www/backend
# 确保没有将.env打包进去
RUN rm .env

# 启动脚本,除了php-fpm还有一些额外的配置
COPY scripts/start.sh /start.sh
RUN chmod +x /start.sh
# 用于任务调度的任务
COPY scripts/crontab /etc/cron.d/www
# 用于支持worker的启动
ADD ./scripts/worker.conf /etc/supervisor/conf.d/worker.conf

# 修改属主,确保与php-fpm的用户一致
RUN chown -R www /var/www/backend

VOLUME /var/www/backend

CMD ["/start.sh"]

php-fpm的基础镜像,官方有提供,不过php-fpm的许多扩展需要自己配置,很容易出现遗漏,性能调优也需要自己配置,cronsupervisord等用于支持任务调度和队列的包也需要自己安装,整个配置过程是非常繁琐的,因此这里我使用自己在生产环境验证过的php-fpm作为基础镜像,有做调整时就更新该包即可。

Dockerfile中出现了start.shworker.confcrontab这3个文件,针对这3个文件做个详细解释。

start.sh是启动脚本,正常来讲,只需要启动php-fpm就能工作,但是默认情况没有考虑到迁移文件的需求,该脚本可以做更多必要的工作。

#!/bin/sh

# 用于启动性能采集
# nohup tideways-daemon &

# 执行migration
cd /var/www/backend
php artisan migrate --force
if [ ! -f "public/storage" ] ; then php artisan storage:link; fi

# 下面这2个被注释的命令有助于提高性能,但是可能导致应用不可用,根据需要自己启动
# php artisan optimize 
# php artisan api:cache 
if [ $? -eq 0 ] ; then
    # 启动php-fpm
    php-fpm
else
   exit 1
fi

worker.confsupersivord的配置文件,用于确保php队列的可靠启动和对队列的进程数量做精确控制

[program:worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/backend/artisan queue:work --sleep=3 --tries=3 --daemon
user=www
autostart=true
autorestart=true
numprocs=2
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0

crontab用于执行任务调度

* * * * * www php /var/www/backend/artisan schedule:run > /dev/null 2>&1

构建php-fpm镜像

docker build -t  app-backend:latest  .

制作nginx镜像配置

nginx的制作与《培训-纯前端项目的Docker镜像打包》几乎一样, 这里就不再赘述。只简单提下不同的几个小点:

  1. Dockerfile不需要添加任何应用的源码;
  2. app.conf与前端不一样,完整代码见下面;
  3. app.conf中的fastcgi_pass backend:9000;需要特别注意,backend是实际启动的php-fpm的容器名。

Dockerfile

FROM nginx:alpine

MAINTAINER LIUWENCAN <phenye@gmail.com>

RUN adduser -D -H -u 5000 -s /bin/sh www
RUN rm /etc/nginx/conf.d/default.conf
ADD nginx.conf /etc/nginx/
ADD backend.conf /etc/nginx/sites-available/

VOLUME /var/www

CMD ["nginx"]             

区别只在于app.conf的内容不一样:

server {
    listen 80;
    listen [::]:80;

    server_tokens off;
    server_name demo-app.store.codefriend.top;
    root /var/www/backend/public;
    index index.php index.html index.htm;

    location ~* .*\.(gif|jpg|jpeg|png|bmp|swf|js|css)$ {
        expires      30d;
        add_header Cache-Control "public";
    }

    location / {
         try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        internal;
        try_files $uri /index.php =404;
        fastcgi_pass app-backend:9000; # 这个名字需要注意
        fastcgi_index index.php;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt/;
        log_not_found off;
    }

}

构建nginx镜像

docker build -t  app-nginx:latest .

本地镜像编排

本地镜像编排docker-comopse.yml

version: ‘2‘
services:
  backend:
    image: app-backend:latest
    env_file: "./app-backend.env"
  cron:
    image: app-backend:latest
    env_file: "./app-backend.env"
    command: [‘cron‘, ‘-f‘]
  worker:
    image: app-backend:latest
    env_file: "./app-backend.env"
    command: [‘/usr/bin/supervisord‘, ‘-n‘, ‘-c‘, ‘/etc/supervisor/supervisord.conf‘]
  nginx:
    image: app-nginx:latest
    volumes_from:
      - backend
    depends_on:
      - backend
    ports:
      - "8888:80"

镜像编排中,出现了app-backend.env这个文件,这个文件就是Laravel.env,这些敏感一般放在保密的对象存储上,或者通过云容器的环境配置、或者放在vault这样的密钥管理里面。

测试镜像

docker-compose up -d --renew-anon-volumes

--renew-anon-volumes这个参数用于更新匿名挂载,非常重要。因为nginx里面本身没源码,源码是跟着php-fpm,如果没有这个参数,当php-fpm那边的代码更新以后,nginx这边得不到通知。

docker-compose ps看下各个任务是否正常启动,如果已经正常启动。直接访问http://localhost:8888应该能够正常进入。

如果不能正常进入,结合docker-compose logdocker-compose top以及通过docker-compose exec进到容器内部排查。

laravel后端项目的Docker镜像打包

原文:https://www.cnblogs.com/pheye/p/12873465.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!