Olá Habr! Neste artigo, quero compartilhar a experiência de implantar um servidor de teste para a equipe de desenvolvimento. Resumidamente, a essência do problema - há uma equipe de desenvolvimento e vários projetos em php. Enquanto éramos poucos e o projeto era essencialmente um, usamos um servidor de teste e, para mostrar a tarefa ao cliente, o desenvolvedor “colocou em coluna” o servidor por um certo tempo. Se não houvesse "janelas" a tempo, teríamos que esperar. Com o tempo, a equipe e a complexidade das tarefas aumentaram, respectivamente, o tempo de verificação e a ocupação do servidor de teste, o que afetou negativamente o lead time e o bônus. Portanto, eu tive que procurar uma solução e está sob o corte.
Introdutório
O que foi:
- Um servidor de teste
- Gitlab e redmine em outro servidor
- Desejo de resolver um problema
Todos os servidores estão em nossa rede local, o servidor de teste é inacessível do lado de fora.
O que foi necessário:
- Capacidade de testar vários projetos / filiais ao mesmo tempo
- O desenvolvedor pode ir ao servidor, configurá-lo e não quebrar nada dos outros
- Tudo deve ser o mais conveniente possível e feito em 1 botão, de preferência no gitlab (CI / CD).
Opções de decisão
1. Um servidor, muitos hosts
A opção mais fácil. Utilizamos o mesmo servidor de teste, apenas o desenvolvedor precisa criar um host para cada filial / projeto e adicioná-lo à configuração nginx / apache2.
Prós:
- Rapidamente e todos entendem
- Pode automatizar
Contras:
- A cláusula 2 dos requisitos não foi atendida - o desenvolvedor pode começar a atualizar o banco de dados e, em algumas circunstâncias, colocar tudo em (Oi Andrey!)
- Automação bastante complexa com vários arquivos de configuração
2. Para cada desenvolvedor no servidor!
Aloque para cada servidor e o desenvolvedor é responsável por sua economia.
Prós:
- O desenvolvedor pode personalizar completamente o servidor para seu projeto
Contras:
- A cláusula 2 dos requisitos não é cumprida
- Caros e recursos podem simplesmente ficar ociosos enquanto o desenvolvimento está em andamento, não testando
- A automação é ainda mais complicada do que no ponto 1 devido a diferentes servidores
3. Containerização - janela de encaixe, kubernetes
Essa tecnologia está cada vez mais penetrando em nossas vidas. Em casa, uso o docker para meus projetos há muito tempo.
O Docker é um software para automatizar a implantação e o gerenciamento de aplicativos em um ambiente de virtualização no nível do sistema operacional. Permite que você "empacote" o aplicativo com todos os seus arredores e dependências em um contêiner que possa ser portado para qualquer sistema Linux com suporte a cgroups no kernel e também forneça um ambiente de gerenciamento de contêiner.
Prós:
- Um servidor é usado
- Todos os requisitos são atendidos.
Contras:
- Às vezes, imagens e contêineres ocupam muito espaço; é necessário limpar as coroas que já estão desatualizadas para liberar espaço.
Implementação do Docker
Ao usar o gitlab, o AutoDevOps, as configurações do kubernetes frequentemente chamavam minha atenção. Além disso, caras barbudos em vários encontros contam o quão legal eles trabalham com os kubernetes. Portanto, foi decidido tentar implantar o cluster em suas instalações, o servidor foi solicitado (e você não pode tocar no teste, as pessoas estão testando lá) e começou!
Como tenho experiência com o kubernetes 0, tudo foi feito de acordo com o manual, com a tentativa de entender como todos esses clusters funcionam. Depois de algum tempo, consegui aumentar o cluster, mas houve problemas com certificados, chaves e, de fato, com a dificuldade de implantação. Eu precisava de uma solução mais simples para ensinar meus colegas a trabalhar com isso (por exemplo, não quero passar as mesmas férias sentado no Skype e ajudando na configuração). Portanto, o kubernetes foi deixado sozinho. O próprio Docker permaneceu e foi necessário encontrar uma solução para o roteamento de contêineres. Como eles podem ser detectados em portas diferentes, o mesmo nginx pode ser usado para redirecionamento interno. Isso é chamado de servidor proxy reverso.
Um servidor proxy reverso é um tipo de servidor proxy que retransmite solicitações de clientes de uma rede externa para um ou mais servidores localizados logicamente na rede interna. Ao mesmo tempo, parece ao cliente como se os recursos solicitados estivessem localizados diretamente no servidor proxy.
Proxy reverso
Para não reinventar a roda, comecei a procurar soluções prontas. E foi encontrado - isto é
traefik .
O Træfik é um moderno proxy reverso HTTP e balanceador de carga que simplifica a implantação de microsserviços. O Træfik se integra aos componentes de infraestrutura existentes (Docker, modo Swarm, Kubernetes, Maratona, Consul, Etcd, Rancher, Amazon ECS, ...) e é configurado automaticamente e dinamicamente. Para trabalhar com a janela de encaixe, basta especificar seu soquete e pronto, então o próprio Træfik encontra todos os contêineres e encaminhamento para eles (para obter mais detalhes, consulte “Empacotando aplicativos na janela de encaixe”).
Configuração de contêiner da TræfikEu inicio através do docker-compose.yml
version: '3' services: traefik: image: traefik:latest # The official Traefik docker image command: --api --docker # Enables the web UI and tells Træfik to listen to docker ports: - 443:443 - 80:80 # The HTTP port - 8080:8080 # The Web UI (enabled by --api) volumes: - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events - /opt/traefik/traefik.toml:/traefik.toml - /opt/traefik/certs/:/certs/ networks: - proxy container_name: traefik restart: always networks: proxy: external: true
Aqui, informamos ao proxy que precisamos ouvir as portas 80.443 e 8080 (a face da Web do proxy), monte o soquete do docker, o arquivo de configuração e a pasta do certificado. Para a conveniência de nomear sites de teste, decidimos criar uma zona de domínio local * .test. Ao acessar qualquer site, o usuário acessa nosso servidor de teste. Portanto, os certificados na pasta traefik são autoassinados, mas são compatíveis com o Let's Encrypt.
Geração de certificado
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout domain.key -out domain.crt
Antes de iniciar, você precisa criar uma rede proxy na janela de encaixe (você pode nomear como sua).
docker network create proxy
Essa será a rede para conectar o traefik com contêineres de sites php. Portanto, nós o especificamos no parâmetro de redes do serviço e nas redes de todo o arquivo, especificando no parâmetro external: true.
Arquivo Traefik.toml debug = false logLevel = "DEBUG" defaultEntryPoints = ["https","http"] # insecureSkipVerify = true # [entryPoints] [entryPoints.http] address = ":80" [entryPoints.https] address = ":443" [entryPoints.https.tls] [docker] endpoint = "unix:///var/run/docker.sock" domain = "docker.localhost" watch = true exposedbydefault = false
Tudo é bem simples aqui - especificamos os pontos de entrada do tráfego http e https, não se esqueça de definir insecureSkipVerify = true se os certificados forem locais. Na seção entryPoints.https.tls, você não pode especificar certificados; o traefik substituirá seu certificado.
Você pode iniciar o serviço
docker-compose up -d
Se você for ao
site.test , receberá um erro 404, pois esse domínio não está vinculado a nenhum contêiner.
Empacotamos aplicativos no docker
Agora você precisa configurar o contêiner com o aplicativo, a saber:
1. especifique uma rede proxy nas redes
2. adicione etiquetas com configuração traefik
Abaixo está a configuração de um dos aplicativos
aplicativos docker-compose.yml version: '3' services: app: build: data/docker/php # restart: always working_dir: /var/www/html/public volumes: - ./:/var/www/html # - /home/develop/site-files/f:/var/www/html/public/f # links: - mailcatcher - memcached - mysql labels: - traefik.enabled=true - traefik.frontend.rule=Host:TEST_DOMAIN,crm.TEST_DOMAIN,bonus.TEST_DOMAIN - traefik.docker.network=proxy - traefik.port=443 - traefik.protocol=https networks: - proxy - default mailcatcher: image: schickling/mailcatcher:latest restart: always memcached: image: memcached restart: always mysql: image: mysql:5.7 restart: always command: --max_allowed_packet=902505856 --sql-mode="" environment: MYSQL_ROOT_PASSWORD: 12345 MYSQL_DATABASE: site volumes: - ./data/cache/mysql-db:/var/lib/mysql # phpmyadmin: image: phpmyadmin/phpmyadmin restart: always links: - mysql environment: MYSQL_USERNAME: root MYSQL_ROOT_PASSWORD: 12345 PMA_ARBITRARY: 1 PMA_HOST: mysql_1 labels: - traefik.enabled=true - traefik.frontend.rule=Host:pma.TEST_DOMAIN - traefik.docker.network=proxy - traefik.port=80 - traefik.default.protocol=http networks: - proxy - default networks: proxy: external: true
No serviço de aplicativo, na seção de rede, é necessário especificar proxy e padrão, isso significa que ele estará disponível em duas redes, como pode ser visto na configuração, não encaminhar portas para fora, tudo passa na rede.
Em seguida, configure etiquetas
- traefik.enabled=true # traefik - traefik.frontend.rule=Host:TEST_DOMAIN,crm.TEST_DOMAIN,bonus.TEST_DOMAIN # traefik - traefik.docker.network=proxy # - traefik.port=443 #, ssl 80 http - traefik.protocol=https # # phpmyadmin http
Na seção de redes gerais, especifique external: true
A constante TEST_DOMAIN deve ser substituída por um domínio, por exemplo, site.test
Inicie o aplicativo
docker-compose up -d
Agora, se você acessar os domínios site.test, crm.site.test, bonus.site.test, poderá ver o site em funcionamento. E no domínio pma.site.test, haverá phpmyadmin para um trabalho conveniente com o banco de dados.
Configurar o GitLab
Criamos um manipulador de tarefas, para isso corremos
gitlab-runner register
Nós especificamos o URL do gitlab, o token e através do qual a tarefa será executada (executores). Como meu teste e gitlab estão em servidores diferentes, seleciono o ssh executor. Você precisará especificar o endereço do servidor e o login / senha para conectar via ssh.
O corredor pode ser anexado a um ou mais projetos. Como minha lógica de trabalho é a mesma em todos os lugares, um corredor compartilhado foi criado (comum para todos os projetos).
E o toque final é criar um arquivo de configuração de IC
.gitlab-ci.yml stages: - build - clear # develop build_develop: stage: build # build tags: # - ssh-develop environment: # , - name: review/$CI_BUILD_REF_NAME # url: https://site$CI_PIPELINE_ID.test #url on_stop: clear when: manual script: - cd ../ && cp -r $CI_PROJECT_NAME $CI_PIPELINE_ID && cd $CI_PIPELINE_ID # - cp -r /home/develop/site-files/.ssh data/docker/php/.ssh # ssh - sed -i -e docker-compose.yml # - docker-compose down # - docker-compose up -d --build # - script -q -c cd ../ && php composer.phar install --prefer-dist \ # - script -q -c cd ../ && php composer.phar first-install $CI_PIPELINE_ID\ # # production build_prod: stage: build tags: - ssh-develop environment: name: review/$CI_BUILD_REF_NAME url: https://site$CI_PIPELINE_ID.test on_stop: clear when: manual script: - cd ../ && cp -r $CI_PROJECT_NAME $CI_PIPELINE_ID && cd $CI_PIPELINE_ID - cp -r /home/develop/site-files/.ssh data/docker/php/.ssh # ssh - docker-compose down - docker-compose up -d --build - script -q -c cd ../ && php composer.phar install --prefer-dist --no-dev\ - script -q -c cd ../ && php composer.phar first-install $CI_PIPELINE_ID\ clear: stage: clear tags: - ssh-develop environment: name: review/$CI_BUILD_REF_NAME action: stop script: - cd ../ && cd $CI_PIPELINE_ID && docker-compose down && cd ../ && echo password | sudo -S rm -rf $CI_PIPELINE_ID # when: manual
Nesta configuração, são descritos 2 estágios - compilar e limpar. A fase de construção possui 2 opções - build_develop e build_prod

O Gitlab cria um diagrama de fluxo de processo compreensível. No meu exemplo, todos os processos iniciam manualmente (quando: parâmetro manual). Isso é feito para que o desenvolvedor, depois de implantar o site de teste, possa inserir suas edições no contêiner sem reconstruir o contêiner inteiro. Outro motivo é o nome do domínio - site $ CI_PIPELINE_ID.test, em que CI_PIPELINE_ID é o número do processo que iniciou a montagem. Ou seja, eles enviaram o site com o domínio site123.test para verificação e, para fazer edições quentes, as alterações são imediatamente lançadas no contêiner pelo desenvolvedor.
Um pequeno recurso do executor ssh. Quando conectado ao servidor, uma pasta do formulário é criada.
/home//builds/_runner/0/_/_
Portanto, uma linha foi adicionada
cd ../ && cp -r $CI_PROJECT_NAME $CI_PIPELINE_ID && cd $CI_PIPELINE_ID
Nele, subimos para a pasta acima e copiamos o projeto para a pasta com o número do processo. Assim, você pode implantar várias ramificações de um projeto. Mas nas configurações do manipulador, é necessário marcar Bloquear nos projetos atuais, para que o manipulador não tente expandir várias ramificações ao mesmo tempo.
O estágio de limpeza para os contêineres e exclui a pasta; talvez você precise de privilégios de root, por isso usamos a senha de eco | sudo -S rm em que senha é sua senha.
Coleta de lixo
De tempos em tempos, você precisa remover os contêineres não utilizados para não ocupar espaço; para isso, um script com esse conteúdo fica pendurado na coroa
realizada uma vez ao dia.
Conclusão
Essa solução nos ajudou a otimizar significativamente os testes e o lançamento de novos recursos. Pronto para responder a perguntas, críticas construtivas são aceitas.
Bônus
Para não coletar imagens do Dockerfile de cada vez, você pode armazená-las no registro da janela de encaixe local.
Arquivo docker-compose.yml registry: restart: always image: registry:2 ports: - 5000:5000 volumes: - /opt/docker-registry/data:/var/lib/registry
Esta opção não usa autenticação, não é uma maneira segura (!!!), mas é adequada para armazenar imagens não críticas.
Você pode configurar o gitlab para visualizar
gitlab_rails['registry_enabled'] = true gitlab_rails['registry_host'] = "registry.test" gitlab_rails['registry_port'] = "5000"
Depois disso, uma lista de imagens aparece no gitlab
