Laravel + Docker 第 2 部分-准备生产
这是两部分的第 2 部分,我们将介绍如何使用 Docker 在生产环境下运行 Laravel应用程序。在学习之前,请先了解 第1部分。
第二部分主要介绍我们用来构建应用程序的开发环境与实际运行的应用生产环境之间工作流的区别。
生产环境下的不同?
本文只涉及单服务器设置。 我们将以手动的方式完成所有操作,以便您查看所需要的所有细节。
源文件:
在开发环境下,我们通常把源文件的工作目录配置到容器中,以便对源文件进行修改且结果实时反映在正在运行的程序中。但是在生产环境下,我们则需要直接将源文件复制到镜像中,以便他们可以独立于主机运行(即:在服务器上的某处)。
端口暴露:
在开发环境下,有时我们会将主机上的端口绑定到容器上以简化调试过程。但是在生产环境下这不是必须的,因为 docker-compose
可以自动为我们创建服务器的网络通信。
数据持久化:
在开发中,我们为 MySql 设置了一个命名卷,以允许写入其中的数据得以持久(即使我们的容器停止并重新启动时)—但我们无法处理应用程序可能要写入磁盘的情况(以 Laravel 为例,这种情况会在视图中发生)-因此,在生产环境中,我们需要创建一个卷,该卷将允许写入磁盘以保持数据持久化。
环境变量
在开发中,我们只有一个.env
文件,该文件与我们的所有其他源代码一起可用于容器。 在生产中,我们将动态加载另一个
SSL & Nginx配置
我们需要配置我们的网络服务器以启用安全连接,但是我们想在运行时动态加载证书的路径,以便在本地测试生产设置时可以将实时证书交换为自签名。
步骤 1 — 准备‘app’ 镜像
在第一篇文章中,我们创建了一个适用于运行 Laravel 应用程序的基于 PHP-FPM 的镜像。 然后,我们需要做的就是将当前目录装载到该容器中,然后该应用程序开始运行。 对于生产,我们需要以不同的方式考虑它,这些步骤是:
- 首先使用支持 laravel 的 php 镜像作为基础
- 将源文件复制到镜像中(不包括第三方扩展文件夹)
- 下载并安装 composer 管理工具
- 在镜像中使用 composer 来安装依赖项
- 改变被应用写入的文件夹目录的所有者
- 运行
PHP artisan optimize
创建框架所需的类映射
因此,让我们开始吧。首先,你要创建用来完成这些操作的 app.dockerfile
。
FROM shakyshane/laravel-php:latest
COPY composer.lock composer.json /var/www/
COPY database /var/www/database
WORKDIR /var/www
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
&& php -r "if (hash_file('SHA384', 'composer-setup.php') === '55d6ead61b29c7bdee5cccfb50076874187bd9f21f65d8991d46ec5cc90518f447387fb9f76ebae1fbbacf329e583e30') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
&& php composer-setup.php \
&& php -r "unlink('composer-setup.php');" \
&& php composer.phar install --no-dev --no-scripts \
&& rm composer.phar
COPY . /var/www
RUN chown -R www-data:www-data \
/var/www/storage \
/var/www/bootstrap/cache
RUN php artisan optimize
注意:
-在第1行,我们正在从我发布到 Docker 集线器的映像进行重建,该映像包含的内容仅足以运行 Laravel 应用程序。
-因为在第3行和第5行,我们使用 COPY
命令,因此每当我们尝试构建时,Docker将检查复制内容的内容。如果没有任何更改,它可以使用该层的缓存版本-但如果发生了更改,即使是任何文件中的单个字节,整个缓存都会被丢弃,并且所有后续命令都将再次执行。在我们的示例中,这意味着每次我们尝试构建此映像时,如果我们的规则没有更改(因为 compose.lock 文件相同),则Docker将不会执行包含 Composer Install
和 run
的命令,而我们的构建将是又好又快!
步骤 2 — 创建 .dockerignore文件
在 app.dockerfile
中有这样一条 COPY . /var/www
意思是这将引入每个文件 (包括隐藏文件如 .git ) 从而生成一个巨大的镜像. 为了解决这个问题,我们可以在根目录创引入 .dockerignore
文件.
该文件的编写方式与 .gitignore
类似, 至少包含以下内容.
.git
.idea
.env
node_modules
vendor
storage/framework/cache/**
storage/framework/sessions/**
storage/framework/views/***
注意:
- 最后三行是确保开发中不包含 Laravel 的这三个文件,但是我们需要保留目录结构.
步骤3 — 构建 ‘app’ 镜像
创建 app.dockerfile
和 .dockerignore
文件后, 我们可以继续构建自定义镜像。
docker build -t shakyshane/laravel-app .
注意:
- 这个过程可能需要几分钟,因为
composer
需要下载相关依赖包. -t
在这里表示给当前 Docker 镜像指定一个标签, 你可以取任意你喜欢的名称, 但是在取名时需要考虑镜像的位置. 例如在这个例子中,我将这个镜像使用我的用户名发布在 docker hub (shakyshane
) 上,并且将laravel-app
作为我的镜像存储仓库。 你可以在这里非常直观的看到我所表达的意思 hub.docker.com/r/shakyshane/larave... :
镜像构建成功后你可以使用 docker images
命令来验证是否标记.
Step 4— 添加 ‘app’ 服务到 docker-compose.prod.yml
现在我们可以从我们的app
服务开始构建docker-compose.prod.yml
文件。. (与第一篇文章一样,它们全部都在‘services’关键字下面, 但请放心,因为稍后会有完整的摘要)。
# The Application
app:
image: shakyshane/laravel-app
volumes:
- /var/www/storage
env_file: '.env.prod'
environment:
- "DB_HOST=database"
- "REDIS_HOST=cache"
注意:
- 我们使用
image: shakyshane/laravel-app
指向我们最后一步中构建的镜像.。请记住,其中包含我们所有的源代码,因此我们不需要从主机挂载任何目录。 - 我们需要一种方法,该方法可以让应用程序在它运行的环境中持久化文件存储. 以
/var/www/storage
这种卷定义的方式将会导致 Docker 创建一个持久化的卷.该持久卷将在任何容器停止/启动后都不会丢失。
- 我们将Redis设置为会话和缓存驱动程序,但我还没有找到一种方法来阻止 Laravel 将视图缓存写入磁盘,这就是为什么需要这个单个卷。
- 我们使用
env_file: '.env.prod'
将Laravel环境文件挂载到容器中。这一部分可以通过使用专用的秘密处理解决方案来改进,但我没有足够的开发能力来做到这一点,并且在我们只使用单服务器设置的情况下,我认为这种方法是可以的。(请各位安全专家正确/为我指出正确的方向
Step 5 — 准备web 镜像
因此, 既然我们正在构建一个包含PHP环境,我们所有的源代码和应用程序依赖的自定义镜像, 是时候对web服务器做同样的事了.
这个简单得多。我们将构建 Nginx,将 vhost.conf
复制到适当位置(适用于 Laravel 应用),然后复制到整个 public 目录中。这将使 nginx 可以提供不需要应用程序处理的静态文件(例如图像,css,js 等)
所以现在继续创建 web.dockerfile 。只需要以下内容:
FROM nginx:1.10-alpine
ADD vhost.conf /etc/nginx/conf.d/default.conf
COPY public /var/www/public
注意:
- 这次我们使用的是
nginx:1.10-alpine
— 最后的-alpine
意味着此基本映像是由一个很小的linux基本映像构建的。 - 且 alpine 镜像默认支持 HTTP2
步骤 6 — 创建 NGINX 配置
将文件名保存为 vhost.conf
— 与在 web.dockerfile
的名称对应
server {
server_name localhost;
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
add_header Strict-Transport-Security max-age=15768000;
index index.php index.html;
root /var/www/public;
location / {
try_files $uri /index.php?$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
server {
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}
注意:
- 当你看到
/etc/letsencrypt/live/example.com
这个文件时, 可将其改为 example.com 你自己的地址, 该网站 www.selfsignedcertificate.com/ 提供签名证书, 可自行下载(或者使用你知道的方式生成证书 如 阿里云提供的免费证书) 将生成好的证书放到该目录下certs/live/example.com
— 以下是该目录的结构.
- 在本地测试时,您可以传入类似于
LE_DIR=certs
—引用本地目录,但是随后在服务器上可以传递LE_DIR=/etc/letsencrypt
— which is where the certbot 将在此处转储您的证书。这将由于自签名证书而在本地创建安全警告,但您可以单击忽略警告以完全测试HTTP2+通过https连接的所有链接。
步骤7 —建立 web 镜像
创建文件 web.dockerfile
和 vhost.conf
后,我们可以继续构建第二个自定义镜像。
docker build -t shakyshane/laravel-web .
笔记:
-与前一个映像一样,我们标记此映像以匹配它以后将使用的 repo 名称。
步骤8— 将 web 服务添加到 docker-compose.prod.yml
# The Web Server
web:
image: shakyshane/laravel-web
volumes: - "${LE_DIR}:/etc/letsencrypt"
ports:
- 80:80
- 443:443
注意:
- 我们使用环境装载包含证书的目录变量。
Docker compose
将用我们在运行时提供的值替换${LE_DIR}
该值允许我们在实时/本地证书之间进行切换。 - 我们从主机->容器绑定两个端口
80
&443
。这样我们就可以处理不安全流量和安全流量-您可以在上述vhost.conf
文件的第二个server
块中看到此配置信息。
步骤9 —将MySql和Redis服务添加到 docker-compose.prod.yml
最后,需要将MySql和Redis的配置放置在我们的 docker-compose.prod.yml
文件中。但对于这些文件,我们不需要构建任何自定义镜像。
version: '2'
services: # The Database database:
image: mysql:5.6
volumes:
- dbdata:/var/lib/mysql
environment:
- "MYSQL_DATABASE=homestead"
- "MYSQL_USER=homestead"
- "MYSQL_PASSWORD=secret"
- "MYSQL_ROOT_PASSWORD=secret"
# redis cache:
image: redis:3.0-alpine
volumes:
dbdata:
注意:
-我们为MySql使用了一个命名卷,以确保数据可以在主机上保存,但是对于 Redis,我们不需要这样做,因为镜像中已配置。
步骤10 —创建.env.prod
与任何Laravel应用程序一样,您将需要一个包含应用程序秘钥的文件,该文件通常因环境而异。现在,因为我们想在本地计算机上以 production
模式运行,我们只需复制/粘贴默认的Laravel .env.example
示例文件并将其重命名为.env.prod
- 当此应用程序最终部署到服务器上时,我们可以创建正确的环境文件并使用它。
步骤11 –在您的机器上以生产模式进行测试!
这是开始变得非常酷。我们已经将我们的源代码和依赖项直接构建到镜像中,允许它们在安装了Docker 的任何主机上运行,这包括您的本地开发机器!
此时我们只有一个最后的命令要运行,但值得您简要介绍一下到目前为止您应该做的事情。
- 创建了一个
app.dockerfile
,用它构建了一个映像,并在docker.compose.prod.yml
中做配置 - 同样的处理了
web.dockerfile
- 创建了
dockerignore
以从COPY
中排除文件和文件夹 - 使用 NGINX 配置创建了
vhost.conf
, 创建用于本地测试的自签名证书并在docker.compose.prod.yml
中对其做配置 - 添加了 Redis 和 MySQL 配置
- 创建了
env.prod
配置文件
docker-compose.prod.yml
文件里的内容:
version: '2'
services: # The Application app:
image: shakyshane/laravel-app
working_dir: /var/www
volumes:
- /var/www/storage
env_file: '.env'
environment:
- "DB_HOST=database"
- "REDIS_HOST=cache"
# The Web Server web:
image: shakyshane/laravel-web
volumes:
- "${LE_DIR}:/etc/letsencrypt"
ports:
- 80:80
- 443:443
# The Database database:
image: mysql:5.6
volumes:
- dbdata:/var/lib/mysql
environment:
- "MYSQL_DATABASE=homestead"
- "MYSQL_USER=homestead"
- "MYSQL_PASSWORD=secret"
- "MYSQL_ROOT_PASSWORD=secret"
# redis cache:
image: redis:3.0-alpine
volumes:
dbdata:
剩下要做的就是运行一个命令:
LE_DIR=./certs docker-compose -f docker-compose.prod.yml up
过几分钟,您的应用程序将可以通过 0.0.0.0 进行访问
恭喜您,您现在运行应用程序的方式在生产服务器上是100%可复制的-不是开玩笑!一旦我们构建的两个映像发布到 Docker Hub之类的注册表中,您所需要做的就是在服务器上放置 docker-compose.prod.yml
&一个.env
文件,您的应用程序将使用您已经在本地计算机上测试过的系统运行。
下一步。
我只能在这里介绍这么多内容,而此博文的主要重点是填补我发现其他帖子在开发和生产中使用 Docker 之间的差异方面所缺乏的部分。
正如我在开始时提到的,此设置在单服务器环境中很好地满足了我的需求,我在该环境中执行了以下步骤以使其在生产中运行:
- 创建了一个预先安装了 Docker 的 Digital Ocean Droplet。
- 遵循他们的指南之一 撰写(它不像 Docker for Mac/Windows 上那样预先安装在 Linux 上)
- Docker Hub 中的安装程序自动构建,以便在推送Github时自动构建两个映像。
- 使用 certbot 在服务器上生成ssl证书
- 运行上面看到的相同的
docker-compose
命令,但这一次交换服务器上证书路径的LE_DIR
部分。例如:LE_DIR=/etc/letsencrypt docker-compose -f docker-compose.prod.yml up
关于 Docker还有更多内容可以讲,但是,我在这里只是向您展示了最初的步骤,以便您可以掌握有关如何容器您的应用程序的一些概念。
这里是本次项目的 代码仓库
接下来要看的东西:
- CI: 本文中的许多步骤都可以自动化,例如,您可以使用CI服务来构建映像并将其发布到注册机构,以及运行测试等。我建议您首先熟悉手动构建/推送映像,只是为了让你在进入完全自动化的流程之前充分了解工作流程的各个部分。
- 安全证书管理: 整个应用程序及其运行环境整齐地打包到容器中感觉就像正确的做法,但是由于我缺乏 devops 和系统管理员的技能,老实说,我不知道如何在运行时从安装中删除这两项。我听说 Docker目前正在开发自己的安全证书管理解决方案,这很让人期待。诸如 Kubernetes 已经为它提供了自带的解决方案,然后还有专门的方案如 Vault…. 好吧。要学的…太多了:)
- 群集模式,伸缩等:· Docker支持使用简单的CLI命令跨多个主机伸缩应用程序。非常令人期待。我已经尝试过使用
docker-machine
在 Digital Ocean 上启动云服务器,现在我可以告诉您,这是一种令人兴奋的体验-尤其是当您意识到所有常规的 docker 命令,包括docker-compose
之类的东西,都可以通过网络工作时更是如此酷毙了。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
这代码部分, 惨不忍睹啊.