
Esta postagem foi escrita a pedido dos trabalhadores que perguntam periodicamente sobre "Como executar o aplicativo Illuminate / Symfony / MyOwn Psr7 na janela de encaixe". Não tenho vontade de fornecer um link para uma postagem escrita anteriormente , porque minhas opiniões sobre como resolver o problema mudaram bastante.
Tudo o que será escrito abaixo é uma experiência subjetiva, que (como sempre) não afirma ser considerada a única decisão correta, mas algumas abordagens e soluções podem parecer interessantes e úteis para você.
Como aplicativo, também utilizarei o Laravel, pois ele é mais familiar para mim e bastante difundido. É possível adaptar-se a outras estruturas / componentes baseados em PSR-7 , mas essa história não é sobre isso.
Tratamento de erros
Gostaria de começar com o que acabou por ser "não as melhores práticas" no contexto do artigo anterior :
- A necessidade de alterar a estrutura dos arquivos no repositório
- Usando o FPM. Se queremos desempenho de nossos aplicativos, talvez uma das melhores soluções, mesmo no estágio de seleção de tecnologia, seja abandoná-lo em favor de algo mais rápido e "adaptado" ao fato de que a memória pode vazar. O RoadRunner da lachezis está aqui como nunca antes
- Uma imagem separada com origem e ativos. Apesar do uso dessa abordagem, podemos reutilizar a mesma imagem para criar um roteamento mais complexo de solicitações recebidas (nginx na frente para retornar estática; solicitações de dinâmica são atendidas por outro contêiner no qual o volume com as mesmas fontes será enviado - para melhor escala) - esse esquema provou ser bastante complicado na operação do produto. Além disso, o próprio RR renderiza estática com perfeição e, se houver muitas estatísticas (ou o recurso pode carregar e exibir conteúdo gerado pelo usuário) - levamos para CDN (o pacote S3 + CloudFront + CloudFlare funciona bem) e esquecemos esse problema em princípio
- IC complexo. Isso se tornou um problema real quando o período de "construção de carne" ativo começou nos estágios de montagem e teste automático. Um cara que não apoiava esse IC antes, fica muito difícil fazer alterações sem medo de quebrar nada.
Agora, sabendo quais problemas precisam ser corrigidos e com uma compreensão de como fazer isso, proponho prosseguir com sua eliminação. O conjunto de "ferramentas de desenvolvedor" não mudou - é o mesmo docker-ce
, docker-compose
e o poderoso Makefile
.
Como resultado, obtemos:
- Um contêiner autônomo com um aplicativo sem a necessidade de montar volume adicional
- Um exemplo de uso do git-hooks - colocaremos as dependências necessárias após o
git pull
automaticamente e proibiremos o envio do código se os testes falharem (os hooks serão armazenados sob o git, naturalmente) - O RoadRunner processará solicitações HTTP (s)
- Os desenvolvedores ainda poderão executar
dd(..)
e dump(..)
para depuração, enquanto nada trava no navegador - Os testes podem ser executados diretamente a partir do PHPStorm IDE, enquanto serão executados no contêiner com o aplicativo
- O IC coletará imagens para nós ao publicar uma nova tag de versão do aplicativo
- Vamos seguir a regra estrita de manter os arquivos
CHANGELOG.md
e ENVIRONMENT.md
Introdução visual de uma nova abordagem
Para uma demonstração visual, dividirei todo o processo em várias etapas, cujas mudanças serão feitas como RMs separadas (após a fusão, todos os brunches permanecerão em seus lugares; referências à RM nos cabeçalhos das "etapas"). O ponto de partida é o esqueleto do Laravel de um aplicativo criado usando o composer create-project laravel/laravel
:
$ docker run \ --rm -i \ -v "$(pwd):/src" \ -u "$(id -u):$(id -g)" \ composer composer create-project --prefer-dist laravel/laravel \ /src/laravel-in-docker-with-rr "5.8.*"
O primeiro passo é ensinar o aplicativo a ser executado no contêiner. Para fazer isso, precisamos de um Dockerfile
, docker-compose.yml
para a descrição de "como elevar e vincular contêineres" e um Makefile
para reduzir um processo já simplificado a um ou dois comandos.
Dockerfile
A imagem básica que eu uso php:XXX-alpine
como a mais fácil e contém o que você precisa para executar. Além disso, todas as atualizações subseqüentes no intérprete são reduzidas a simplesmente alterar o valor nessa linha (atualizar o PHP agora é mais fácil do que nunca).
O Composer e o arquivo binário RoadRunner são entregues no contêiner usando vários estágios e COPY --from=...
- isso é muito conveniente, e todos os valores associados às versões não são "dispersos", mas estão no início do arquivo. Isso funciona rápido e sem dependências do curl
/ git clone
/ make build
. Imagens de 512k / roadrunner são suportadas por mim, se você quiser - você mesmo pode montar o arquivo binário.
Uma história interessante aconteceu com a variável de ambiente PS1
(responsável pelo prompt no shell) - acontece que você pode usar emoji nele e tudo funciona localmente, mas se você tentar iniciar a imagem com uma variável contendo emoji em, digamos, rancheiro, ela falhará (no enxame de tudo funciona sem problemas).
No Dockerfile
começo a gerar um certificado SSL autoassinado para usá-lo para solicitações HTTPS recebidas. Naturalmente - nada impede o uso de um certificado "normal".
Eu também gostaria de dizer sobre:
COPY ./composer.* /app/ RUN set -xe \ && composer install --no-interaction --no-ansi --no-suggest --prefer-dist \ --no-autoloader --no-scripts \ && composer install --no-dev --no-interaction --no-ansi --no-suggest \ --prefer-dist --no-autoloader --no-scripts
Aqui, o significado é o seguinte - os arquivos composer.json
e composer.json
são entregues em uma camada separada para a imagem, após a qual todas as dependências descritas neles são instaladas. Isso é feito para que durante as compilações subsequentes da imagem usando --cache-from
, se a composição e as versões das dependências instaladas não tiverem sido alteradas, a composer install
não composer install
executada, retirando essa camada do cache, economizando tempo e tráfego de compilação (obrigado pela ideia jetexe ).
composer install
é executada duas vezes (na segunda vez com --no-dev
) para "aquecer" o cache das dependências de dev, de modo que quando colocamos todas as dependências no IC para executar os testes, elas são colocadas no cache do compositor, que já está na imagem, e não esticado de galáxias distantes.
Com a última instrução RUN
, exibimos as versões do software instalado e a composição dos módulos PHP, tanto para o histórico nos logs de construção quanto para garantir que "ele esteja lá e, de alguma forma, seja iniciado".
Também uso meu Entrypoint, porque antes de iniciar o aplicativo em algum lugar do cluster, quero realmente verificar a disponibilidade de serviços dependentes - DB, redis, rabbit e outros.
Roadrunner
Para integrar o RoadRunner a um aplicativo Laravel, foi escrito um pacote que reduz toda a integração a alguns comandos no shell (executando o docker-compose run app sh
):
$ composer require avto-dev/roadrunner-laravel "^2.0" $ ./artisan vendor:publish --provider='AvtoDev\RoadRunnerLaravel\ServiceProvider' --tag=rr-config
Adicione APP_FORCE_HTTPS=true
ao arquivo ./docker/docker-compose.env
e especifique o caminho para o certificado SSL no contêiner nos .rr*.yaml
.
Para poder usar dump(..)
e dd(..)
e tudo funcionaria, existe outro pacote - avto-dev/stacked-dumper-laravel
. Tudo o que é necessário é adicionar um pefix a esses auxiliares, a saber \dev\dd(..)
e \dev\dump(..)
respectivamente. Sem isso, você observará um erro como:
worker error: invalid data found in the buffer (possible echo)
Depois de todas as manipulações, docker-compose up -d
e voila:

Os funcionários do banco de dados PostgeSQL, redis e RoadRunner foram executados com êxito em contêineres.
Como escrevi anteriormente, um Makefile é um item muito subestimado. Metas dependentes, seu próprio açúcar sintático, 99% de probabilidade de ele já estar na máquina linux / mac do desenvolvedor, preenchem automaticamente “out of the box” - uma pequena lista de suas vantagens.
Adicionando -o ao nosso projeto e fazendo make
sem parâmetros, podemos observar:

Para executar testes de unidade, podemos fazer um make test
ou colocar um shell dentro do contêiner com o aplicativo ( make shell
), executar o composer phpunit
. Para obter o relatório de cobertura, basta fazer make test-cover
e, antes de executar os testes, o xdebug com suas dependências será entregue ao contêiner e os testes serão iniciados (já que esse procedimento geralmente não é realizado e não pelo CI - essa solução parece ser melhor do que manter uma imagem separada com todos dev-loções).
Ganchos Git
Ganchos, no nosso caso, desempenharão duas funções importantes - não permitindo a inserção de código na origem cujos testes não são bem-sucedidos; e automaticamente coloque todas as dependências necessárias se, puxando as alterações para a sua máquina, acontecer que o composer.lock
mudou. No Makefile
, há um destino separado para isso:
cwd = $(shell pwd) git-hooks: ## Install (reinstall) git hooks (required after repository cloning) -rm -f "$(cwd)/.git/hooks/pre-push" "$(cwd)/.git/hooks/pre-commit" "$(cwd)/.git/hooks/post-merge" ln -s "$(cwd)/.gitlab/git-hooks/pre-push.sh" "$(cwd)/.git/hooks/pre-push" ln -s "$(cwd)/.gitlab/git-hooks/pre-commit.sh" "$(cwd)/.git/hooks/pre-commit" ln -s "$(cwd)/.gitlab/git-hooks/post-merge.sh" "$(cwd)/.git/hooks/post-merge"
Fazer make git-hooks
simplesmente tira os hooks existentes e os coloca no .gitlab/git-hooks
em seu lugar. A fonte deles pode ser visualizada neste link .
Executando testes do PhpStorm
Apesar de ser bastante simples e conveniente - usei-o por um longo tempo ./vendor/bin/phpunit --group=foo
vez de simplesmente pressionar a tecla de atalho diretamente enquanto escrevia um teste ou código associado a ela.
Clique em File > Settings > Languages & Frameworks > PHP > CLI interpreter > [...] > [+] > From Docker, Vargant, VM, Remote
. Selecione Composição do Docker e nome do serviço do aplicativo .

O segundo passo é dizer ao phpunit para usar o intérprete no contêiner: File > Settings > Test frameworks > [+] > PHPUnit by remote interpreter
e selecione o intérprete remoto criado anteriormente. No campo Path to script
, especifique /app/vendor/autoload.php
e, em Path mappings
especificamos o diretório raiz do projeto, conforme montado em /app
.

E agora podemos executar testes diretamente do IDE usando o intérprete dentro da imagem com o aplicativo, pressionando (por padrão, Linux) Ctrl + Shift + F10.
Tudo o que resta a fazer é automatizar o processo de execução de testes e montagem da imagem. Para fazer isso, crie o .gitlab-ci.yml
no diretório raiz do aplicativo, preenchendo-o com o seguinte conteúdo . A idéia principal dessa configuração é ser o mais simples possível, mas não perder a funcionalidade ao mesmo tempo.
A imagem é montada a cada brunch, a cada confirmação. Usando --cache-from
montar uma imagem após reconfigurar é muito rápido. A necessidade de reconstrução se deve ao fato de que em cada brunch temos uma imagem com as alterações feitas como parte desse brunch e, como resultado, nada nos impede de implementá-lo no swarm / k8s / etc, a fim de garantir a "vida" que tudo funciona, e funciona como deveria antes mesmo da fusão com a luz- master
.
Após a montagem, execute testes de unidade e verifique o lançamento do aplicativo no contêiner, enviando solicitações de curvatura para o terminal de verificação de integridade (essa ação é opcional, mas várias vezes esse teste me ajudou muito).
Para o "release of the release" - basta publicar uma tag no formato vX.XX
(se você ainda vX.XX
versionamento semântico - será muito legal) - o CI coletará a imagem, executará os testes e executará as ações que você especificar na deploy to somewhere
.
Não se esqueça, nas configurações do projeto (se possível), de limitar a capacidade de publicar tags apenas para pessoas com permissão para "liberar liberações".
CHANGELOG.md
e ENVIRONMENT.md
Antes de aceitar um ou outro MR, o inspetor deve, sem falhar, verificar os arquivos CHANGELOG.md
e ENVIRONMENT.md
CHANGELOG.md
ENVIRONMENT.md
. Se o primeiro for cada vez menos claro , o segundo relativo darei uma explicação. Este arquivo é usado para descrever todas as variáveis de ambiente às quais o contêiner com o aplicativo responde. I.e. se o desenvolvedor adicionar ou remover o suporte para uma ou outra variável de ambiente, isso deve ser refletido neste arquivo. E no momento em que surge a pergunta “Precisamos urgentemente redefinir isso e aquilo” - ninguém começa a investigar freneticamente a documentação ou os códigos-fonte - mas procura em um único arquivo. Muito confortável
Conclusão
Neste artigo, examinamos o processo bastante simples de transferir o desenvolvimento e o lançamento de aplicativos para o ambiente Docker, o RoadRunner integrado e o uso de um script de IC simples automatizou a montagem e o teste da imagem com nosso aplicativo.
Após a clonagem do repositório, os desenvolvedores precisam make git-hooks && make install && make up
e começar a escrever um código útil. Camaradas * ops-am - pegue a imagem com a tag desejada e role-a em seus cachos.
Naturalmente - esse esquema também é simplificado e, nos projetos de "combate", estou encerrando muito mais, mas se a abordagem descrita no artigo ajudar alguém, saberei que perdi meu tempo.