En Kinsta, tenemos proyectos de todos los tamaños para alojamiento de aplicaciones, alojamiento de bases de datos y alojamiento de WordPress administrado.
Con las soluciones de alojamiento en la nube de Kinsta, puede implementar aplicaciones en varios lenguajes y marcos, como NodeJS, PHP, Ruby, Go, Scala y Python. Con un Dockerfile, puede implementar cualquier aplicación. Puede conectar su repositorio Git (alojado en GitHub, GitLab o Bitbucket) para implementar su código directamente en Kinsta.
Puede alojar bases de datos MariaDB, Redis, MySQL y PostgreSQL listas para usar, lo que le ahorra tiempo para concentrarse en desarrollar sus aplicaciones en lugar de sufrir con las configuraciones de alojamiento.
Y si elige nuestro hospedaje administrado de WordPress, experimentará el poder de las máquinas Google Cloud C2 en su red de nivel Premium y la seguridad integrada de Cloudflare, lo que hace que sus sitios web de WordPress sean los más rápidos y seguros del mercado.
Superar el desafío de desarrollar aplicaciones nativas de la nube en un equipo distribuido
Uno de los mayores desafíos de desarrollar y mantener aplicaciones nativas de la nube a nivel empresarial es tener una experiencia consistente durante todo el ciclo de vida del desarrollo. Esto es aún más difícil para las empresas remotas con equipos distribuidos que trabajan en diferentes plataformas, con diferentes configuraciones y comunicación asincrónica. Necesitamos proporcionar una solución consistente, confiable y escalable que funcione para:
- Los desarrolladores y los equipos de control de calidad, independientemente de sus sistemas operativos, crean una configuración sencilla y mínima para desarrollar y probar funciones.
- Equipos de DevOps, SysOps e Infra, para configurar y mantener entornos de ensayo y producción.
En Kinsta, confiamos mucho en Docker para esta experiencia consistente en cada paso, desde el desarrollo hasta la producción. En esta publicación, lo guiamos a través de:
- Cómo aprovechar Docker Desktop para aumentar la productividad de los desarrolladores.
- Cómo creamos imágenes de Docker y las enviamos a Google Container Registry a través de canalizaciones de CI con CircleCI y GitHub Actions.
- Cómo usamos las canalizaciones de CD para promover cambios incrementales en la producción mediante imágenes de Docker, Google Kubernetes Engine y Cloud Deploy.
- Cómo el equipo de control de calidad utiliza sin problemas imágenes de Docker preconstruidas en diferentes entornos.
Uso de Docker Desktop para mejorar la experiencia del desarrollador
Ejecutar una aplicación localmente requiere que los desarrolladores preparen meticulosamente el entorno, instalen todas las dependencias, configuren servidores y servicios y se aseguren de que estén configurados correctamente. Cuando ejecuta múltiples aplicaciones, esto puede ser engorroso, especialmente cuando se trata de proyectos complejos con múltiples dependencias. Cuando presenta esta variable a múltiples colaboradores con múltiples sistemas operativos, se instala el caos. Para evitarlo, usamos Docker.
Con Docker, puede declarar las configuraciones del entorno, instalar las dependencias y crear imágenes con todo lo que debería estar. Cualquiera, en cualquier lugar, con cualquier sistema operativo puede usar las mismas imágenes y tener exactamente la misma experiencia que los demás.
Declare su configuración con Docker Compose
Para comenzar, cree un archivo Docker Compose, docker-compose.yml
. Es un archivo de configuración declarativo escrito en formato YAML que le dice a Docker cuál es el estado deseado de su aplicación. Docker utiliza esta información para configurar el entorno de su aplicación.
Los archivos de Docker Compose resultan muy útiles cuando se ejecuta más de un contenedor y existen dependencias entre contenedores.
para crear tu docker-compose.yml
archivo:
- Comience eligiendo un
image
como base para nuestra aplicación. Busque en Docker Hub e intente encontrar una imagen de Docker que ya contenga las dependencias de su aplicación. Asegúrate de usar una etiqueta de imagen específica para evitar errores. Utilizando ellatest
etiqueta puede causar errores imprevistos en su aplicación. Puede usar varias imágenes base para varias dependencias. Por ejemplo, uno para PostgreSQL y otro para Redis. - Usar
volumes
para conservar los datos en su host si es necesario. La persistencia de datos en la máquina host lo ayuda a evitar la pérdida de datos si se eliminan los contenedores acoplables o si tiene que volver a crearlos. - Usar
networks
para aislar su configuración para evitar conflictos de red con el host y otros contenedores. También ayuda a que sus contenedores se encuentren y se comuniquen fácilmente entre sí.
Reuniéndolos todos, tenemos un docker-compose.yml
que se parece a esto:
version: '3.8'services:
db:
image: postgres:14.7-alpine3.17
hostname: mk_db
restart: on-failure
ports:
- ${DB_PORT:-5432}:5432
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USER:-user}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
POSTGRES_DB: ${DB_NAME:-main}
networks:
- mk_network
redis:
image: redis:6.2.11-alpine3.17
hostname: mk_redis
restart: on-failure
ports:
- ${REDIS_PORT:-6379}:6379
networks:
- mk_network
volumes:
db_data:
networks:
mk_network:
name: mk_network
Contenerice la aplicación
Cree una imagen de Docker para su aplicación
Primero, necesitamos construir una imagen de Docker usando un Dockerfile
y luego llamarlo desde docker-compose.yml
.
para crear tu Dockerfile
archivo:
- Comience eligiendo una imagen como base. Utilice la imagen base más pequeña que funcione para la aplicación. Por lo general, las imágenes alpinas son mínimas con casi ningún paquete adicional instalado. Puede comenzar con una imagen alpina y construir sobre eso:
FROM node:18.15.0-alpine3.17
- A veces es necesario utilizar una arquitectura de CPU específica para evitar conflictos. Por ejemplo, suponga que utiliza un
arm64-based
procesador pero necesita construir unamd64
imagen. Puede hacerlo especificando el-- platform
enDockerfile
:FROM --platform=amd64 node:18.15.0-alpine3.17
- Defina el directorio de la aplicación e instale las dependencias y copie la salida a su directorio raíz:
WORKDIR /opt/app COPY package.json yarn.lock ./ RUN yarn install COPY . .
- Llama a
Dockerfile
dedocker-compose.yml
:services: ...redis ...db app: build: context: . dockerfile: Dockerfile platforms: - "linux/amd64" command: yarn dev restart: on-failure ports: - ${PORT:-4000}:${PORT:-4000} networks: - mk_network depends_on: - redis - db
- Implemente la recarga automática para que cuando cambie algo en el código fuente, pueda obtener una vista previa de sus cambios inmediatamente sin tener que reconstruir la aplicación manualmente. Para hacer eso, primero crea la imagen y luego ejecútala en un servicio separado:
services: ... redis ... db build-docker: image: myapp build: context: . dockerfile: Dockerfile app: image: myapp platforms: - "linux/amd64" command: yarn dev restart: on-failure ports: - ${PORT:-4000}:${PORT:-4000} volumes: - .:/opt/app - node_modules:/opt/app/node_modules networks: - mk_network depends_on: - redis - db - build-docker volumes: node_modules:
Consejo profesional: Tenga en cuenta que node_modules
también se monta explícitamente para evitar problemas específicos de la plataforma con los paquetes. Significa que en lugar de usar el node_modules
en el host, el contenedor docker usa el suyo propio pero lo mapea en el host en un volumen separado.
Cree de forma incremental las imágenes de producción con integración continua
La mayoría de nuestras aplicaciones y servicios utilizan CI/CD para su implementación. Docker juega un papel importante en el proceso. Cada cambio en la rama principal desencadena inmediatamente una canalización de compilación a través de GitHub Actions o CircleCI. El flujo de trabajo general es muy simple: instala las dependencias, ejecuta las pruebas, crea la imagen de la ventana acoplable y la envía a Google Container Registry (o Artifact Registry). La parte que discutimos en este artículo es el paso de compilación.
Construyendo las imágenes de Docker
Utilizamos compilaciones de varias etapas por motivos de seguridad y rendimiento.
Etapa 1: Constructor
En esta etapa, copiamos todo el código base con todo el código fuente y la configuración, instalamos todas las dependencias, incluidas las dependencias de desarrollo, y construimos la aplicación. Crea un dist/
carpeta y copia la versión compilada del código allí. Pero esta imagen es demasiado grande con un gran conjunto de huellas para usarla en la producción. Además, como usamos registros privados de NPM, usamos nuestro privado NPM_TOKEN
en esta etapa también. Por lo tanto, definitivamente no queremos que este escenario esté expuesto al mundo exterior. Lo único que necesitamos de esta etapa es dist/
carpeta.
Etapa 2: Producción
La mayoría de las personas usan esta etapa para el tiempo de ejecución, ya que es muy similar a lo que necesitamos para ejecutar la aplicación. Sin embargo, todavía necesitamos instalar dependencias de producción y eso significa que dejamos huellas y necesitamos la NPM_TOKEN
. Así que esta etapa todavía no está lista para ser expuesta. Además, preste atención a yarn cache clean
en la línea 19. Ese pequeño comando reduce el tamaño de nuestra imagen hasta en un 60%.
Etapa 3: tiempo de ejecución
La última etapa debe ser lo más delgada posible con un mínimo de huellas. Así que simplemente copiamos la aplicación completamente horneada de producción y seguimos adelante. Ponemos todos esos hilos y NPM_TOKEN
cosas detrás y solo ejecuta la aplicación.
esta es la final Dockerfile.production
:
# Stage 1: build the source code
FROM node:18.15.0-alpine3.17 as builder
WORKDIR /opt/app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
RUN yarn build
# Stage 2: copy the built version and build the production dependencies FROM node:18.15.0-alpine3.17 as production
WORKDIR /opt/app
COPY package.json yarn.lock ./
RUN yarn install --production && yarn cache clean
COPY --from=builder /opt/app/dist/ ./dist/
# Stage 3: copy the production ready app to runtime
FROM node:18.15.0-alpine3.17 as runtime
WORKDIR /opt/app
COPY --from=production /opt/app/ .
CMD ["yarn", "start"]
Tenga en cuenta que, para todas las etapas, empezamos a copiar package.json
y yarn.lock
primero, instalando las dependencias y luego copiando el resto del código base. La razón es que Docker crea cada comando como una capa encima del anterior. Y cada compilación podría usar las capas anteriores si están disponibles y solo compilar las nuevas capas con fines de rendimiento.
Digamos que ha cambiado algo en src/services/service1.ts
sin tocar los paquetes. Significa que las primeras cuatro capas de la etapa de construcción están intactas y se pueden reutilizar. Eso hace que el proceso de construcción sea increíblemente más rápido.
Envío de la aplicación al registro de contenedores de Google a través de las canalizaciones de CircleCI
Hay varias formas de crear una imagen de Docker en las canalizaciones de CircleCI. En nuestro caso, optamos por utilizar circleci/gcp-gcr orbs
:
executors:
docker-executor:
docker:
- image: cimg/base:2023.03
orbs:
gcp-gcr: circleci/[email protected]
jobs:
...
deploy:
description: Build & push image to Google Artifact Registry
executor: docker-executor
steps:
...
- gcp-gcr/build-image:
image: my-app
dockerfile: Dockerfile.production
tag: ${CIRCLE_SHA1:0:7},latest
- gcp-gcr/push-image:
image: my-app
tag: ${CIRCLE_SHA1:0:7},latest
Se necesita una configuración mínima para compilar y enviar nuestra aplicación, gracias a Docker.
Envío de la aplicación al registro de contenedores de Google a través de acciones de GitHub
Como alternativa a CircleCI, podemos usar GitHub Actions para implementar la aplicación de forma continua. configuramos gcloud
y crea y envía la imagen de Docker a gcr.io
:
jobs:
setup-build:
name: Setup, Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get Image Tag
run: |
echo "TAG=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- uses: google-github-actions/setup-gcloud@master
with:
service_account_key: ${{ secrets.GCP_SA_KEY }}
project_id: ${{ secrets.GCP_PROJECT_ID }}
- run: |-
gcloud --quiet auth configure-docker
- name: Build
run: |-
docker build
--tag "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:$TAG"
--tag "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:latest"
.
- name: Push
run: |-
docker push "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:$TAG"
docker push "gcr.io/${{ secrets.GCP_PROJECT_ID }}/my-app:latest"
Con cada pequeño cambio enviado a la rama principal, construimos e insertamos una nueva imagen de Docker en el registro.
Implementación de cambios en Google Kubernetes Engine mediante canalizaciones de entrega de Google
Tener imágenes de Docker listas para usar para todos y cada uno de los cambios también facilita la implementación en producción o la reversión en caso de que algo salga mal. Usamos Google Kubernetes Engine para administrar y servir nuestras aplicaciones y usamos Google Cloud Deploy y Delivery Pipelines para nuestro proceso de implementación continua.
Cuando la imagen de Docker se crea después de cada pequeño cambio (con la canalización de CI que se muestra arriba), damos un paso más e implementamos el cambio en nuestro clúster de desarrollo usando gcloud
. Echemos un vistazo a ese paso en la canalización de CircleCI:
- run:
name: Create new release
command: gcloud deploy releases create release-${CIRCLE_SHA1:0:7} --delivery-pipeline my-del-pipeline --region $REGION --annotations commitId=$CIRCLE_SHA1 --images my-app=gcr.io/${PROJECT_ID}/my-app:${CIRCLE_SHA1:0:7}
Esto desencadena un proceso de lanzamiento para implementar los cambios en nuestro clúster de desarrollo de Kubernetes. Después de probar y obtener las aprobaciones, promovemos el cambio a la puesta en escena y luego a la producción. Todo esto es posible porque tenemos una imagen de Docker delgada y aislada para cada cambio que tiene casi todo lo que necesita. Solo necesitamos decirle a la implementación qué etiqueta usar.
Cómo se beneficia el equipo de control de calidad de este proceso
El equipo de control de calidad necesita principalmente una versión en la nube de preproducción de las aplicaciones que se van a probar. Sin embargo, a veces necesitan ejecutar una aplicación preconstruida localmente (con todas las dependencias) para probar una función determinada. En estos casos, no quieren ni necesitan pasar por todo el dolor de clonar todo el proyecto, instalar paquetes npm, compilar la aplicación, enfrentar errores del desarrollador y revisar todo el proceso de desarrollo para poner la aplicación en funcionamiento. Ahora que todo ya está disponible como una imagen de Docker en Google Container Registry, todo lo que necesitan es un servicio en el archivo de composición de Docker:
services:
...redis
...db
app:
image: gcr.io/${PROJECT_ID}/my-app:latest
restart: on-failure
ports:
- ${PORT:-4000}:${PORT:-4000}
environment:
- NODE_ENV=production
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://${DB_USER:-user}:${DB_PASSWORD:-password}@db:5432/main
networks:
- mk_network
depends_on:
- redis
- db
Con este servicio, pueden activar la aplicación en sus máquinas locales usando contenedores Docker ejecutando:
docker compose up
Este es un gran paso hacia la simplificación de los procesos de prueba. Incluso si el control de calidad decide probar una etiqueta específica de la aplicación, pueden cambiar fácilmente la etiqueta de la imagen en la línea 6 y volver a ejecutar el comando de composición de Docker. Incluso si deciden comparar diferentes versiones de la aplicación simultáneamente, pueden lograrlo fácilmente con algunos ajustes. El mayor beneficio es mantener a nuestro equipo de control de calidad alejado de los desafíos de los desarrolladores.
Ventajas de usar Docker
- Casi cero huellas para las dependencias: Si alguna vez decide actualizar la versión de Redis o Postgres, solo puede cambiar 1 línea y volver a ejecutar la aplicación. No es necesario cambiar nada en su sistema. Además, si tiene dos aplicaciones que necesitan Redis (quizás incluso con versiones diferentes), puede hacer que ambas se ejecuten en su propio entorno aislado sin conflictos entre sí.
- Múltiples instancias de la aplicación: Hay muchos casos en los que necesitamos ejecutar la misma aplicación con un comando diferente. Como inicializar la base de datos, ejecutar pruebas, ver cambios en la base de datos o escuchar mensajes. En cada uno de estos casos, dado que ya tenemos lista la imagen compilada, simplemente agregamos otro servicio al archivo de composición de Docker con un comando diferente y listo.
- Entorno de prueba más fácil: La mayoría de las veces, solo necesita ejecutar la aplicación. No necesita el código, los paquetes ni ninguna conexión de base de datos local. Solo desea asegurarse de que la aplicación funcione correctamente o necesita una instancia en ejecución como servicio de back-end mientras trabaja en su propio proyecto. Ese también podría ser el caso de los revisores de QA, Pull Request o incluso de UX que quieren asegurarse de que su diseño se haya implementado correctamente. La configuración de nuestra ventana acoplable hace que sea muy fácil para todos ellos poner las cosas en marcha sin tener que lidiar con demasiados problemas técnicos.
Este artículo se publicó originalmente en Docker.