下午好,在本文中,我将展示如何在AWS上部署Symfony 4应用程序。 官方文档中有这样一个过程的示例,但是我的版本并不像使用应用程序下载zip存档那样简单。 在2019年的院子里,以docker模式,微服务架构和CI / CD实践终于开始被包括在DevOps工程师和普通
凡人开发人员的工具中。 为了使文章更有趣,我在React.JS上添加了前面板,以满足广大人群的需求,如果您的应用程序不使用Encore,没关系,我将向您说明如何更改Docker文件,对React.JS的支持只会影响到它。 谁会对本教程感兴趣? 首先,它面向希望更改其部署实践的PHP开发人员-摆脱通常的规范,并使用docker打包其应用程序并布置图像。 但是您可以更深入一点,进一步的叙述将旨在通过CI / CD平台从Git自动部署应用程序(将使用CircleCI,但是如果您对Gitlab配置感兴趣,请在注释中写上我的附件)。 实际上,对于React / PHP来说,无论您是拥有应用程序还是.NET Core,这都不是绝对重要的,对于开发人员来说,获得通用的部署自动化技能将是很有趣的。 源代码位于github存储库中,位于文章结尾的链接。 好吧,走吧!
我假设您有自己的Symfony应用程序,但是出于演示目的,我草绘了“ hello,world!”,其中包含以下软件包:
`symfony/webpack-encore-bundle symfony/form symfony/orm-pack symfony/profiler-pack symfony/security-bundle symfony/twig-bundle symfony/validator symfony/phpunit-bridge`
是一个最小的绅士组合。 目前,文件夹结构应如下所示:

现在,您需要配置您的云基础架构。 我将不专注于注册和激活AWS的试用期,在这一阶段,我们需要创建2个数据库实例-我将使用2种类型的环境:STG(分段)来测试新“功能”的实现以及PROD(生产)直接作为“战斗”的实现伺服器 关于托管服务数据库的好处,已经写了很多文章,此外,在本指南中,我们主要是为开发人员寻求便利,因此我们使用RDS,而不是开发自己的独立数据库服务器。 作为该示例的DBMS,我使用了PostgreSQL,您可以自由选择适合您的任何一种,转到RDS服务并创建所需容量和容量的2个实例。 由于
.env
文件可在Symfony中“开箱即用”地使用,因此我们将使用它(例如,对于PROD),对于STG,我们将创建
.env.stg
的副本,
.env.stg
和
APP_ENV=dev
更改为
APP_ENV=stg
APP_ENV=dev
APP_ENV=prod
APP_ENV=dev
APP_ENV=prod
,并且还为每个创建的实例输入
.env
连接参数。
太好了,已经开始了! 如您所知,symfony依赖项是通过composer安装的,要安装它,请使用composer.sh文件,该文件位于项目的根目录中:
作曲家 #!/bin/sh EXPECTED_SIGNATURE="$(wget -q -O - https://composer.imtqy.com/installer.sig)" php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ] then >&2 echo 'ERROR: Invalid installer signature' rm composer-setup.php exit 1 fi php composer-setup.php --quiet RESULT=$? rm composer-setup.php exit $RESULT
这
是composer的软件安装指南。
现在,对于每种环境,在项目的根目录中创建自己的Dockerfile:
Dockerfile.stg(暂存) FROM php:7.2.19-apache EXPOSE 80 RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && a2enmod rewrite RUN sed -ri -e 's!memory_limit = 128M!memory_limit = 256M!g' "$PHP_INI_DIR/php.ini" RUN apt-get update && apt-get install -y \ wget \ curl \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libzip-dev \ zip \ libpq-dev \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-configure zip --with-libzip \ && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ && docker-php-ext-install -j$(nproc) gd \ && docker-php-ext-install zip \ && docker-php-ext-install pdo pdo_pgsql pgsql WORKDIR /var/www ENV APACHE_DOCUMENT_ROOT /var/www/public RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf COPY ./composer.sh ./ RUN chmod +x ./composer.sh && ./composer.sh && mv composer.phar /usr/local/bin/composer RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update -qq \ && apt-get install -y yarn COPY ./ ./ COPY ./.env.stg ./.env RUN composer install && yarn && yarn run build
和
Dockerfile(生产) FROM php:7.2.19-apache EXPOSE 80 RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && a2enmod rewrite RUN sed -ri -e 's!memory_limit = 128M!memory_limit = 256M!g' "$PHP_INI_DIR/php.ini" RUN apt-get update && apt-get install -y \ wget \ curl \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libzip-dev \ zip \ libpq-dev \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-configure zip --with-libzip \ && docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql \ && docker-php-ext-install -j$(nproc) gd \ && docker-php-ext-install zip \ && docker-php-ext-install pdo pdo_pgsql pgsql WORKDIR /var/www ENV APACHE_DOCUMENT_ROOT /var/www/public RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf COPY ./composer.sh ./ RUN chmod +x ./composer.sh && ./composer.sh && mv composer.phar /usr/local/bin/composer RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ && apt-get install -y nodejs RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update -qq \ && apt-get install -y yarn COPY ./ ./ RUN composer install && yarn && yarn run build
文件可以“按原样”使用,没有宏用于更改。 让我们遍历Dockerfile的内容以消除“魔幻”的联系。 作为“基础”,我们将官方的PHP 7.2.19映像与集成的Apache Web服务器一起使用(您可以随意使用任何选择,与Nginx配置捆绑包,依此类推,在本例中,我认为以上示例最多)方便)。 目前,公开行对我们并不重要,它本身什么也不做,但是将来它将由ElasticBeanstalk使用,需要它正确部署。 以下构造使用制造商推荐的PHP优化生产设置,激活Apache的mod_rewrite并将PHP脚本的最大内存从128 mb增加到256 mb,这对于作曲家正常工作是必需的。 接下来,我们安装必要的应用程序,PHP依赖项和扩展,并立即对其进行配置。 我们将文件夹/ var / www分配给应用程序的工作目录-应用程序的源代码将复制到该目录中。 由于默认情况下,apache使用/ var / www作为其主机的入口点,并且symfony索引文件位于/ var / www / public中,因此我们使用以下结构更改apache文档的根目录。 然后,我们依次安装composer,nodejs和yarn(如果您在应用程序中不使用encore / react.js,则不需要最后两点)。 最后,我们复制源代码,并通过composer来安装symfony的依赖关系,并通过yarn来进行react.js的依赖关系的安装。 用于STG的单独Dockerfile的含义在于倒数第二条指令,用于将docker-将.env.stg复制到.env,因此STG映像中的.env文件将包含与此环境相关的参数。 您可以在本地(当然安装了docker)收集图像,运行它并确保应用程序正在运行,并且不需要其他任何工作:
docker build -t tmp:stg -f Dockerfile.stg . docker run -p 80:80 tmp:stg
对于STG和
docker build -t tmp:prod . docker run -p 80:80 tmp:prod
用于PROD。
我们可以使用EC2,配置ELB / ASG等,或使用ElasticBeanstalk,这只是给我们带来的便利。 转到ElasticBeanstalk部分,并使用其名称和描述创建一个新应用程序。 然后创建两个前面提到的环境:STG和PROD,将这两个环境都创建为Web服务器环境,将“ Docker”指定为平台,并将Sample应用程序保留为应用程序代码。 部署到ElasticBeanstalk的方法是上传项目文件或说明,通常是在zip存档中。 在我们的例子中,流程将是这样的:我们收集应用程序的Docker映像,将其加载到存储库中并加载指令,而不是源归档或Docker映像,这告诉ElasticBeanstalk从远程服务器获取该映像并进行部署。 所有这一切都是自动的。
让我们从创建用于存储docker映像的存储库开始。 有2个选项:
1-您的项目是私有的,其代码已关闭,并且存储库也必须分别关闭。 在这种情况下,您可以将自己的图像注册保存在某个地方,也可以使用私有云。 AWS具有用于这些目的的ECR,您可以在此处创建存储库,但是没有人强迫您这样做。
2-您有一个开源项目,可以使用dockerhub。
在我们的示例中,代码是开放的,但是我将展示如何使用封闭的存储库,在理解了此过程之后,从dockerhub连接映像并不困难。 我们需要做的第一件事是创建存储库本身,之后您将获得其唯一的URI。 进一步的叙述将针对第三方(而非AWS ECR存储库及其集成),对于ECR,我将在此之后编写。
创建存储库后,我们需要登录到此服务,并且有一个小技巧...转到本地安装的Docker的设置,并检查是否可以选择将密码保存在已删除的外部存储中(对于macOS用户:“安全存储docker登录名在macOS Keychain中),否则我们需要的配置文件将为空。 因此,我们授权所选服务存储您的图像寄存器:
docker login -u LOGIN -p PASSWORD REGISTRY
成功通过身份验证后,以下构造将出现在〜/ .docker / config.json配置文件中:
"REGISTRY" : { "auth" : "BASE64_ENCODED_TOKEN" }
如果未出现,请再次检查上述docker配置。
现在一切准备就绪,可以为ElasticBeanstalk-Dockerrun.aws.json准备指令文件,其代码将如下所示:
Dockerrun.aws.json { "AWSEBDockerrunVersion": "1", "Authentication": { "Bucket": "BUCKET_ID", "Key": "KEY_PATH" }, "Image": { "Name": "IMAGE_URL", "Update": "true" }, "Ports": [ { "ContainerPort": "80" } ] }
通常,该指令如下所示:使用KEY_PATH位于S3存储区BUCKET_ID中的密钥登录,通过IMAGE_URL覆盖已保存的图像加载图像,通过将端口80转发到容器上的相同端口来启动它。 现在关于使用的常量:
BUCKET_ID是在S3服务中自动为您创建的“背包”,格式为elasticbeanstalk-REGION-HASH,在这里系统可以找到ElasticBeanstalk的服务文件,包括使用“上传和部署”按钮下载的应用程序文件。
KEY_PATH-图像存储库授权文件的路径,我使用APP_NAME / cr.json格式,即在我的应用程序名称下BUCKET_ID内的文件夹中(我创建,如果尚未创建),我将包含授权后收到的代码的cr.json文件放入寄存器中本地图像:
BUCKET_ID / APP_NAME / cr.json { "REGISTRY" : { "auth" : "BASE64_ENCODED_TOKEN" } }
IMAGE_URL是图像寄存器的唯一URI +图像本身的标签,此处应清除所有内容。
就是这样,现在我们可以在ElasticBeanstalk中将该文件下载为应用程序的版本,他将提取指定的映像并进行部署。
仍然可以自动执行此过程。 绝对有趣的是,我将为下一个流程实现步骤序列:对于不在master分支中的所有提交,映像将被收集并部署在STG环境中,如果我们推送到master中,或者更好的是,将其关闭并用合并请求填充,则代码将部署在PROD上。 因此,我们在PROD中获得了一个最新的向导,该向导中的所有内容均应正常运行,并具有用于在STG中开发和测试新代码的分支。 对于此实现,我们需要有关上传非最新图像的说明,将Dockerrun.aws.json复制到Dockerrun.aws.stg.json,并将Dockerrun.aws.json重命名为Dockerrun.aws.prod.json(仅为方便起见)。
将Dockerrun.aws.stg.json与Dockerrun.aws.prod.json区别开的唯一东西是IMAGE_URL:
Dockerrun.aws.stg.json { "AWSEBDockerrunVersion": "1", "Authentication": { "Bucket": "BUCKET_ID", "Key": "KEY_PATH" }, "Image": { "Name": "IMAGE_URL:dev", "Update": "true" }, "Ports": [ { "ContainerPort": "80" } ] }
正如我在文章开头所说,我将使用CircleCI作为CI / CD,根据我的个人感觉,如果使用免费的SaaS版本,它的速度将比GitlabCI快。 Free Travis可以做到,但是由于它不适用于私人git存储库,因此我没有专门进行演示,因此在需要这种机会时不会感到失望。 我将把CircleCI中项目的设置留给读者进行独立研究,我自己给出部署所需的说明-在我们项目的根目录中,我们将在.config文件中创建.circleci文件夹,其中config.yml具有以下内容:
.circleci /配置文件 version: 2 jobs: build: machine: true steps: - checkout - run: echo "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:dev -f Dockerfile.stg . - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:dev build-master: machine: true steps: - checkout - run: echo "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:latest . - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:latest deploy-stg: docker: - image: circleci/python:latest steps: - checkout - run: sudo pip install awsebcli --upgrade - run: | mkdir ~/.aws touch ~/.aws/config chmod 600 ~/.aws/config echo "[profile eb-cli]" > ~/.aws/config echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config - run: eb init --region EB_REGION --platform Docker EB_APP - run: cp Dockerrun.aws.stg.json Dockerrun.aws.json - run: eb use EB_ENV_STG --region EB_REGION - run: eb deploy -v --staged --profile eb-cli deploy-prod: docker: - image: circleci/python:latest steps: - checkout - run: sudo pip install awsebcli --upgrade - run: | mkdir ~/.aws touch ~/.aws/config chmod 600 ~/.aws/config echo "[profile eb-cli]" > ~/.aws/config echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config - run: eb init --region EB_REGION --platform Docker EB_APP - run: cp Dockerrun.aws.prod.json Dockerrun.aws.json - run: eb use EB_ENV_STG --region EB_REGION - run: eb deploy -v --staged --profile eb-cli workflows: version: 2 build: jobs: - build: filters: branches: ignore: - master - deploy-stg: requires: - build filters: branches: ignore: - master build-deploy: jobs: - build-master: filters: branches: only: - master - deploy-prod: requires: - build-master filters: branches: only: - master
我早些时候画了流程本身,在这里它被翻译成CircleCI的yaml指令,让我们看一下具体步骤的实现。 重要的是要注意存在为CI定义的环境变量,该变量将由他在工作期间使用:
需要CI_REGISTRY,CI_REGISTRY_USER和CI_REGISTRY_PASSWORD来访问Docker映像存储-与我们在cr.json中放入的内容相同,只不过没有base64
CI_REGISTRY / CI_REGISTRY_ID组成一个唯一的图像URL,不带标签
AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY-名称可以说明一切,这些是为CircleCI代表其部署的用户的AWS积分。 转到AWS IAM并创建一个用户,将其添加到管理员组中,并仅提供编程访问权限。 请记住,AWS_SECRET_ACCESS_KEY仅可查看/复制一次,在您单击“显示”链接后,您将不会再次看到它。
返回CircleCI配置步骤。 什么是魔术? Checkout将源代码从git分支加载到当前工作目录中,此过程在每个作业中都重复进行。 在构建过程中,我们顺序登录到存储库,基于Dockerfile.stg在XXX:dev标签下收集代码,并将其发送到存储库。 build-master会执行相同的操作,仅对于其构建使用标签“ XXX:Latest”下的“ normal” Dockerfile。
deploy-stg将安装AWS EB CLI并在〜/ .aws / config文件中创建一个授权配置文件,该文件是CLI正常工作所必需的,然后初始化CLI的变量-您将需要指定选择的区域,平台-始终是Docker和您的应用程序名称。 接下来,我们将Dockerrun.aws.stg.json的内容复制到新的Dockerrun.aws.json文件,并使用特定的环境和区域,给出命令以使用创建的授权配置文件部署我们的应用程序。 默认情况下,作为此命令的结果,受监视分支的所有代码都将最终存储在一个zip归档文件中,该文件将下载到ElasticBeanstalk并在那里解压缩,但是此操作相对昂贵,因此我们创建了一个新的Dockerrun.aws.json文件,该文件足以部署我们创建的文件远程映像,实际上我们只需要上传它。 为此,请在项目根目录中创建一个.ebignore文件:
该文件使用.gitignore语法,并且是.gitignore,但不是用于Git CLI,而是用于AWS EB CLI。 在此文件中,我告诉CLI跳过除Dockerrun.aws.json外的所有文件。 就是这样,现在当您在ElasticBeanstalk中运行deploy-stg作业时,将仅发送我们创建的文件。 deploy-prod做同样的事情,仅将Dockerrun.aws.prod.json文件的内容复制到Dockerrun.aws.json,最后一个以CircleCI格式指示工作顺序(构建后为ploy-stg,构建后为deploy-prod -master),以及要在哪个分支上查找数据(忽略:-master,仅:-master)。
正如我所承诺的,AWS ECR的问题稍有不同,我们将继续讨论。 您不需要远程登录ECR并创建cr.json文件,因为ElasticBeanstalk“亲自结识了一个兄弟”。 因此,Dockerrun.aws.json看起来会有所不同-根本就不会有身份验证块:
Dockerrun.aws.json(AWS ECR) { "AWSEBDockerrunVersion": "1", "Image": { "Name": "IMAGE_URL", "Update": "true" }, "Ports": [ { "ContainerPort": "80" } ] }
但是身份验证将如何发生? 事实是,访问ECR的服务具有一定的权限集,而这些权限又基于某些安全策略。 在我们的案例中,当通过AWS CLI从第三方服务器(从CI)启动部署时,将使用“ aws-elasticbeanstalk-ec2-role”角色,在AWS IAM的角色部分中找到该角色,并将附加策略“ AmazonEC2ContainerRegistryReadOnly”附加到该角色。 现在,从私有存储库下载到其“邻居”将成功,不会出现错误。
但这完全是从同一VPC加载的,通过CLI,docker login命令也不是“没有技巧”:您需要通过AWS CLI获得(只是获取)docker登录的信用,为此
aws ecr get-login --region REGION --no-include-email
此命令将以docker login ...形式返回一行,简而言之,您需要在控制台中执行
eval $(aws ecr get-login --region EB_REGION --no-include-email)
该命令将首先接收用于身份验证的字符串,然后启动相应的过程。 鉴于AWS ECR的这些规则,CircleCI的指令文件如下所示:
.circleci / config.yml(适用于AWS ECR) version: 2 jobs: build: docker: - image: circleci/python:latest steps: - checkout - run: sudo pip install awscli --upgrade - run: | mkdir ~/.aws touch ~/.aws/config chmod 600 ~/.aws/config echo "[profile eb-cli]" > ~/.aws/config echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config - setup_remote_docker - run: eval $(aws ecr get-login --region EB_REGION --no-include-email) - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:dev -f Dockerfile.stg . - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:dev build-master: docker: - image: circleci/python:latest steps: - checkout - run: sudo pip install awscli --upgrade - run: | mkdir ~/.aws touch ~/.aws/config chmod 600 ~/.aws/config echo "[profile eb-cli]" > ~/.aws/config echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config - setup_remote_docker - run: eval $(aws ecr get-login --region EB_REGION --no-include-email) - run: docker build -t $CI_REGISTRY/$CI_REGISTRY_ID:latest . - run: docker push $CI_REGISTRY/$CI_REGISTRY_ID:latest deploy-stg: docker: - image: circleci/python:latest steps: - checkout - run: sudo pip install awsebcli --upgrade - run: | mkdir ~/.aws touch ~/.aws/config chmod 600 ~/.aws/config echo "[profile eb-cli]" > ~/.aws/config echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config - run: eb init --region EB_REGION --platform Docker EB_APP - run: cp Dockerrun.aws.stg.json Dockerrun.aws.json - run: eb use EB_ENV_STG --region EB_REGION - run: eb deploy -v --staged --profile eb-cli deploy-prod: docker: - image: circleci/python:latest steps: - checkout - run: sudo pip install awsebcli --upgrade - run: | mkdir ~/.aws touch ~/.aws/config chmod 600 ~/.aws/config echo "[profile eb-cli]" > ~/.aws/config echo "aws_access_key_id=$AWS_ACCESS_KEY_ID" >> ~/.aws/config echo "aws_secret_access_key=$AWS_SECRET_ACCESS_KEY" >> ~/.aws/config - run: eb init --region EB_REGION --platform Docker EB_APP - run: cp Dockerrun.aws.prod.json Dockerrun.aws.json - run: eb use EB_ENV_STG --region EB_REGION - run: eb deploy -v --staged --profile eb-cli workflows: version: 2 build: jobs: - build: filters: branches: ignore: - master - deploy-stg: requires: - build filters: branches: ignore: - master build-deploy: jobs: - build-master: filters: branches: only: - master - deploy-prod: requires: - build-master filters: branches: only: - master
为了支持docker-in-docker,我们在组装阶段添加了setup_remote_docker,您应该已经从本文的内容中了解了其余内容。就是这样,现在我们的项目结构如下:
尝试更改代码,将其压入任务分支,然后向主服务器发出合并(拉)请求并接受它。没有更多的“动作”来发布更新。可能(并且有人需要)走得更远,为滚动迁移编写自己的工作台,采取强制性的中间步骤以通过自动测试,依此类推,等等,我希望本文将为此类实验以及随后的高质量和适当内容交付的实现奠定基础。GitHub源代码:tutorial-aws-symfony-ci