Hola Habr! En este artículo quiero compartir la experiencia de implementar un servidor de prueba para el equipo de desarrollo. Brevemente, la esencia del problema es que hay un equipo de desarrollo y varios proyectos en PHP. Si bien éramos pocos y el proyecto era esencialmente uno, usamos 1 servidor de prueba y para mostrar la tarea al cliente, el desarrollador "columnó" el servidor durante un tiempo determinado. Si no hubo "ventanas" a tiempo, entonces tuvimos que esperar. Con el tiempo, el equipo y la complejidad de las tareas crecieron, respectivamente, el tiempo de verificación y la ocupación del servidor de prueba aumentaron, lo que afectó negativamente el tiempo de entrega y la bonificación. Por lo tanto, tuve que buscar una solución y está debajo del corte.
Introductorio
Lo que fue:
- Un servidor de prueba
- Gitlab y Redmine en otro servidor
- Deseo de resolver un problema
Todos los servidores están en nuestra red local, el servidor de prueba es inaccesible desde el exterior.
Lo que se requería:
- Capacidad para probar múltiples proyectos / sucursales al mismo tiempo
- El desarrollador puede ir al servidor, configurarlo y no romper nada de los demás.
- Todo debe ser lo más conveniente posible y hacerse con 1 botón, preferiblemente desde gitlab (CI / CD).
Opciones de decisión
1. Un servidor, muchos hosts
La opción más fácil. Utilizamos el mismo servidor de prueba, solo el desarrollador necesita crear un host para cada rama / proyecto y agregarlo a la configuración nginx / apache2.
Pros:
- Rápido y fácil para todos
- Puede automatizar
Contras:
- La cláusula 2 de los requisitos no se cumple: el desarrollador puede comenzar a actualizar la base de datos y, en algunas circunstancias, colocar todo (¡Hola Andrey!)
- Automatización bastante compleja con un montón de archivos de configuración
2. ¡A cada desarrollador en el servidor!
Asigne a cada servidor y el desarrollador es responsable de su economía.
Pros:
- El desarrollador puede personalizar completamente el servidor para su proyecto
Contras:
- La cláusula 2 de los requisitos no se cumple
- Los costosos y los recursos pueden simplemente permanecer inactivos mientras el desarrollo está en marcha, no probando
- La automatización es aún más complicada que en el punto 1 debido a los diferentes servidores
3. Contenedorización - acoplador, kubernetes
Esta tecnología está penetrando cada vez más en nuestras vidas. En casa, he estado usando Docker para mis proyectos durante mucho tiempo.
Docker: software para automatizar la implementación y administración de aplicaciones en un entorno de virtualización a nivel del sistema operativo. Le permite "empaquetar" la aplicación con todo su entorno y dependencias en un contenedor que se puede portar a cualquier sistema Linux con soporte de cgroups en el núcleo, y también proporciona un entorno de administración de contenedores.
Pros:
- Se usa un servidor
- Se cumplen todos los requisitos.
Contras:
- Las imágenes y los contenedores a veces ocupan mucho espacio, tienes que limpiar las coronas que ya están desactualizadas para liberar espacio.
Implementación de Docker
Al usar gitlab, AutoDevOps, la configuración de kubernetes a menudo me llamó la atención. Además, los hombres con barba en varias reuniones cuentan cuán geniales trabajan con kubernetes. Por lo tanto, se decidió intentar implementar el clúster en sus instalaciones, se solicitó el servidor (y no se puede tocar el de prueba, la gente está probando allí) y comenzó.
Como tengo experiencia con kubernetes 0, todo se hizo de acuerdo con el manual con el intento de comprender cómo funcionan todos estos grupos. Después de un tiempo, logré elevar el clúster, pero luego hubo problemas con los certificados, las claves y, de hecho, con la dificultad de la implementación. Necesitaba una solución más simple para enseñarles a mis colegas cómo trabajar con esto (por ejemplo, no quiero pasar las mismas vacaciones sentado en Skype y ayudando con la configuración). Por lo tanto, Kubernetes se quedó solo. Docker permaneció y fue necesario encontrar una solución para el enrutamiento de contenedores. Como se pueden recoger en diferentes puertos, se puede usar el mismo nginx para la redirección interna. Esto se llama un servidor proxy inverso.
Un servidor proxy inverso es un tipo de servidor proxy que retransmite las solicitudes de los clientes desde una red externa a uno o más servidores ubicados lógicamente en la red interna. Al mismo tiempo, al cliente le parece que los recursos solicitados se encuentran directamente en el servidor proxy.
Proxy inverso
Para no reinventar la rueda, comencé a buscar soluciones listas para usar. Y fue encontrado, esto es
traefik .
Træfik es un proxy inverso HTTP moderno y equilibrador de carga que simplifica la implementación de microservicios. Træfik se integra con los componentes de infraestructura existentes (Docker, modo Swarm, Kubernetes, Marathon, Consul, Etcd, Rancher, Amazon ECS, ...) y se configura de forma automática y dinámica. Para trabajar con Docker, solo necesita especificar su socket y listo, entonces Træfik mismo encuentra todos los contenedores y enruta hacia ellos (para obtener más detalles, consulte "Empaquetado de aplicaciones en Docker").
Configuración del contenedor TræfikLo lanzo a través de 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
Aquí le informamos al proxy que necesitamos escuchar los puertos 80,443 y 8080 (la cara web del proxy), montar el zócalo del acoplador, el archivo de configuración y la carpeta del certificado. Para la conveniencia de nombrar sitios de prueba, decidimos hacer una zona de dominio local * .test. Al acceder a cualquier sitio en él, el usuario accede a nuestro servidor de prueba. Por lo tanto, los certificados en la carpeta traefik son autofirmados, pero son compatibles con Let's Encrypt.
Generación de certificados
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout domain.key -out domain.crt
Antes de comenzar, debe crear una red proxy en la ventana acoplable (puede nombrarla como suya).
docker network create proxy
Esta será la red para conectar traefik con contenedores de sitios php. Por lo tanto, lo especificamos en el parámetro de redes del servicio y en las redes de todo el archivo especificando en el parámetro externo: verdadero.
Archivo 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
Aquí todo es bastante simple: especificamos los puntos de entrada del tráfico http y https, no olvide establecer insecureSkipVerify = true si los certificados son locales. En la sección entryPoints.https.tls, no puede especificar certificados, luego traefik sustituirá su certificado.
Puedes comenzar el servicio
docker-compose up -d
Si va a
site.test , obtendrá un error 404, ya que este dominio no está vinculado a ningún contenedor.
Empaquetamos aplicaciones en docker
Ahora necesita configurar el contenedor con la aplicación, a saber:
1. especificar una red proxy en las redes
2. agregar etiquetas con configuración traefik
A continuación se muestra la configuración de una de las aplicaciones.
aplicaciones 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
En el servicio de aplicaciones, en la sección de red, debe especificar proxy y predeterminado, esto significa que estará disponible en dos redes, como se puede ver en la configuración, no reenvío puertos al exterior, todo va en la red.
Luego, configure las 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
En la sección de redes generales, especifique external: true
La constante TEST_DOMAIN debe reemplazarse con un dominio, por ejemplo, site.test
Inicia la aplicación
docker-compose up -d
Ahora, si va a los dominios site.test, crm.site.test, bonus.site.test, puede ver el sitio de trabajo. Y en el dominio pma.site.test habrá phpmyadmin para un trabajo conveniente con la base de datos.
Configurar GitLab
Creamos un manejador de tareas, para esto ejecutamos
gitlab-runner register
Especificamos la url de gitlab, el token y a través del cual se ejecutará la tarea (ejecutores). Como mi prueba y gitlab están en diferentes servidores, selecciono ssh ejecutor. Deberá especificar la dirección del servidor y el nombre de usuario / contraseña para conectarse a través de ssh.
El corredor se puede adjuntar a uno o más proyectos. Como mi lógica de trabajo es la misma en todas partes, se creó un corredor compartido (común para todos los proyectos).
Y el toque final es crear un archivo de configuración de CI
.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
En esta configuración, se describen 2 etapas: construir y borrar. La fase de compilación tiene 2 opciones: build_develop y build_prod

Gitlab crea un diagrama de flujo de proceso comprensible. En mi ejemplo, todos los procesos comienzan manualmente (cuando: parámetro manual). Esto se hace para que el desarrollador, después de implementar el sitio de prueba, pueda realizar sus ediciones en el contenedor sin reconstruir todo el contenedor. Otra razón es el nombre de dominio: sitio $ CI_PIPELINE_ID.test, donde CI_PIPELINE_ID es el número del proceso que inició el ensamblado. Es decir, enviaron el sitio con el dominio site123.test para la verificación, y para realizar ediciones en caliente, los cambios son vertidos inmediatamente en el contenedor por el desarrollador.
Una pequeña característica del ejecutor ssh. Cuando se conecta al servidor, se crea una carpeta del formulario.
/home//builds/_runner/0/_/_
Por lo tanto, se agregó una línea
cd ../ && cp -r $CI_PROJECT_NAME $CI_PIPELINE_ID && cd $CI_PIPELINE_ID
En él, subimos a la carpeta de arriba y copiamos el proyecto en la carpeta con el número de proceso. Por lo tanto, puede implementar varias ramas de un proyecto. Pero en la configuración del controlador, debe marcar Bloquear en proyectos actuales, por lo que el controlador no intentará expandir varias ramas al mismo tiempo.
La etapa clear detiene los contenedores y elimina la carpeta, es posible que necesite privilegios de root, por lo que usamos la contraseña echo | sudo -S rm donde contraseña es su contraseña.
Recolección de basura
De vez en cuando, debe eliminar los contenedores no utilizados para no ocupar espacio, para esto, un script con dicho contenido se cuelga en la corona
realizado una vez al día.
Conclusión
Esta solución nos ayudó a optimizar significativamente las pruebas y el lanzamiento de nuevas funciones. Listo para responder preguntas, se aceptan críticas constructivas.
Bono
Para no recopilar imágenes del Dockerfile cada vez, puede almacenarlas en el registro local de Docker.
Archivo docker-compose.yml registry: restart: always image: registry:2 ports: - 5000:5000 volumes: - /opt/docker-registry/data:/var/lib/registry
Esta opción no utiliza autenticación, esta no es una forma segura (!!!), pero es adecuada para almacenar imágenes no críticas.
Puede configurar gitlab para ver
gitlab_rails['registry_enabled'] = true gitlab_rails['registry_host'] = "registry.test" gitlab_rails['registry_port'] = "5000"
Después de eso, aparece una lista de imágenes en gitlab
