Na Kinsta, temos projetos de todos os tamanhos para hospedagem de aplicativos, hospedagem de banco de dados e hospedagem gerenciada de WordPress.
Com as soluções de hospedagem na nuvem da Kinsta, você pode implantar aplicativos em várias linguagens e frameworks, como NodeJS, PHP, Ruby, Go, Scala e Python. Com um Dockerfile, você pode implementar qualquer aplicativo. Você pode conectar seu repositório Git (hospedado no GitHub, GitLab ou Bitbucket) para implantar seu código diretamente na Kinsta.
Você pode hospedar bancos de dados MariaDB, Redis, MySQL e PostgreSQL prontos para uso, economizando tempo para se concentrar no desenvolvimento de seus aplicativos em vez de se preocupar com configurações de hospedagem.
E se você escolher nossa Hospedagem Gerenciada de WordPress, você experimentará o poder das máquinas do Google Cloud C2 em sua rede de nível Premium e a segurança integrada do Cloudflare, tornando seus sites WordPress os mais rápidos e seguros do mercado.
Resolvendo o desafio de criar aplicativos nativos da nuvem com uma equipe remota
Um dos maiores desafios do desenvolvimento e da manutenção de aplicativos nativos da nuvem de nível empresarial é ter uma experiência consistente durante todo o ciclo de vida do desenvolvimento. Isso é ainda mais difícil para empresas remotas com equipes distribuídas que trabalham em plataformas diferentes, com configurações diferentes e comunicação assíncrona. Precisamos fornecer uma solução consistente, confiável e escalável que funcione para:
- Desenvolvedores e equipes de garantia de qualidade, independentemente de seus sistemas operacionais, criam uma configuração simples e mínima para desenvolver e testar recursos.
- Equipes de DevOps, SysOps e Infra, para configurar e manter ambientes de teste e produção.
Na Kinsta, confiamos muito no Docker para que você tenha uma experiência consistente em cada etapa, do desenvolvimento à produção. Neste artigo, mostraremos a você:
- Como aproveitar o Docker Desktop para aumentar a produtividade dos desenvolvedores.
- Como criamos imagens do Docker e as enviamos para o Google Container Registry por meio de pipelines CI com CircleCI e GitHub Actions.
- Como usamos pipelines CI para promover alterações incrementais na produção usando imagens do Docker, o Google Kubernetes Engine e o Cloud Deploy.
- Como a equipe de controle de qualidade usa perfeitamente imagens pré-construídas do Docker em diferentes ambientes.
Usando o Docker Desktop para melhorar a experiência do desenvolvedor
A execução de um aplicativo localmente exige que os desenvolvedores preparem meticulosamente o ambiente, instalem todas as dependências, configurem servidores e serviços e certifiquem-se de que estejam configurados corretamente. Quando você executa vários aplicativos, isso pode ser complicado, especialmente quando se trata de projetos complexos com várias dependências. Quando você introduz nessa variável vários colaboradores com vários sistemas operacionais, o caos está instalado. Para evitar isso, usamos o Docker.
Com o Docker, você pode declarar as configurações de ambiente, instalar as dependências e criar imagens com tudo onde deveria estar. Qualquer pessoa, em qualquer lugar, com qualquer sistema operacional, pode usar as mesmas imagens e ter a mesma experiência que todos os outros.
Declare sua configuração com o Docker Compose
Para começar, crie um arquivo Docker Compose, docker-compose.yml
. É um arquivo de configuração declarativo escrito no formato YAML que informa ao Docker qual é o estado desejado do seu aplicativo. O Docker usa essas informações para configurar o ambiente para o seu aplicativo.
Os arquivos Docker Compose são muito úteis quando você tem mais de um contêiner em execução e há dependências entre os contêineres.
Para criar seu arquivo docker-compose.yml
:
- Comece escolhendo um arquivo
image
como base para o nosso aplicativo. Pesquise no Docker Hub e tente encontrar uma imagem do Docker que já contenha as dependências do seu aplicativo. Certifique-se de usar uma tag de imagem específica para evitar erros. O uso da taglatest
pode causar erros imprevistos em seu aplicativo. Você pode usar várias imagens de base para várias dependências. Por exemplo, uma para o PostgreSQL e outra para Redis. - Use
volumes
para manter os dados em seu host, se você precisar. A persistência de dados no computador host ajuda a evitar a perda de dados se os contêineres do docker forem excluídos ou se você precisar recriá-los. - Use o
networks
para isolar sua configuração e evitar conflitos de rede com o host e outros contêineres. Isso também ajuda os contêineres a se localizarem e se comunicarem facilmente uns com os outros.
Juntando tudo, temos um docker-compose.yml
que se parece com isto:
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
Containerização do aplicativo
Crie uma imagem do Docker para o seu aplicativo
Primeiro, precisamos criar uma imagem do Docker usando Dockerfile
e, em seguida, chamá-lo a partir do docker-compose.yml
.
Para criar seu arquivo Dockerfile
:
- Comece escolhendo uma imagem como base. Use a menor imagem de base que funcione para o aplicativo. Normalmente, as imagens alpine são mínimas, com quase nenhum pacote extra instalado. Você pode começar com uma imagem alpine e construir em cima dela:
FROM node:18.15.0-alpine3.17
- Às vezes, você precisa usar uma arquitetura de CPU específica para evitar conflitos. Por exemplo, suponha que você use um processador
arm64-based
, mas precise criar uma imagemamd64
. Você pode fazer isso especificando o-- platform
emDockerfile
:FROM --platform=amd64 node:18.15.0-alpine3.17
- Defina o diretório do aplicativo, instale as dependências e copie a saída para o diretório raiz:
WORKDIR /opt/app COPY package.json yarn.lock ./ RUN yarn install COPY . .
- Chame o
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 o recarregamento automático para que, ao alterar algo no código-fonte, você possa visualizar as alterações imediatamente sem precisar reconstruir o aplicativo manualmente. Para fazer isso, crie a imagem primeiro e, em seguida, execute em um serviço 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:
Dica profissional: Observe que o node_modules
também está montado explicitamente para evitar problemas específicos da plataforma com os pacotes. Isso significa que, em vez de usar o node_modules
no host, o contêiner Docker usa o seu próprio, mas o mapeia no host em um volume separado.
Otimizando a criação de imagens de produção com Integração Contínua
A maioria dos nossos aplicativos e serviços usa CI/CD para implantação. O Docker desempenha um papel importante no processo. Cada alteração na branch principal aciona imediatamente um pipeline de criação por meio do GitHub Actions ou do CircleCI. O fluxo de trabalho geral é muito simples: ele instala as dependências, executa os testes, cria a imagem do Docker e a envia para o Google Container Registry (ou Artifact Registry). A parte que discutiremos neste artigo é a etapa de build.
Criação das imagens do Docker
Usamos builds de múltiplos estágios por razões de segurança e desempenho.
Etapa 1: Builder
Nesta etapa, copiamos toda a base de código com todo o código-fonte e a configuração, instalamos todas as dependências, inclusive as dependências de desenvolvimento, e criamos o aplicativo. Você cria uma pasta dist/
e copia a versão construída do código para lá. Mas essa imagem é muito grande, com um grande conjunto de rastros para ser usado em produção. Além disso, como usamos registros NPM privados, também usamos nosso NPM_TOKEN
privado nesse estágio. Portanto, definitivamente não queremos que esse estágio seja exposto ao mundo externo. A única coisa de que precisamos nesse estágio é a pasta dist/
.
Estágio 2: Produção
A maioria das pessoas usa esse estágio para o tempo de execução, por estar muito próximo do que precisamos para executar o aplicativo. No entanto, ainda precisamos instalar as dependências de produção, o que significa que deixamos rastros e precisamos do NPM_TOKEN
. Portanto, esse estágio ainda não está pronto para ser exposto. Além disso, preste atenção em yarn cache clean
na linha 19. Esse pequeno comando reduz o tamanho da nossa imagem em até 60%.
Estágio 3: Tempo de execução
O último estágio precisa ser o mais fino possível, com o mínimo de rastros. Então, copiamos o aplicativo totalmente pronto da produção e seguimos em frente. Deixamos de lado todas as coisas do yarn e do NPM_TOKEN
e executamos apenas o aplicativo.
Esta é a versão final do 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"]
Observe que, em todas as etapas, começamos a copiar os arquivos package.json
e yarn.lock
primeiro, instalamos as dependências e depois copiamos o restante da base de código. O motivo é que o Docker constrói cada comando como uma camada sobre a anterior. E cada build pode usar as camadas anteriores, se disponíveis, e apenas construir as novas camadas para fins de desempenho.
Digamos que você tenha alterado algo em src/services/service1.ts
sem tocar nos pacotes. Isso significa que as primeiras quatro camadas do construtor não foram tocadas e podem ser reutilizadas. Isso torna o processo de build incrivelmente mais rápido.
Enviando o aplicativo para o Google Container Registry por meio de pipelines CircleCI
Há várias maneiras de criar uma imagem do Docker nos pipelines do CircleCI. Em nosso caso, optamos por usar 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
Graças ao Docker, você precisa de uma configuração mínima para criar e enviar nosso aplicativo.
Enviando o aplicativo para o Google Container Registry por meio do GitHub Actions
Como alternativa ao CircleCI, podemos usar o GitHub Actions para implantar o aplicativo continuamente. Configuramos o site gcloud
, criamos e enviamos a imagem do Docker para 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"
A cada pequena alteração enviada para a branch principal, criamos e enviamos uma nova imagem do Docker para o registro.
Implantando mudanças no Google Kubernetes Engine usando os pipelines de entrega do Google
Ter imagens do Docker prontas para uso para cada alteração também facilita a implantação na produção ou a reversão caso algo dê errado. Usamos o Google Kubernetes Engine para gerenciar e servir nossos aplicativos e usamos o Google Cloud Deploy e o Pipelines de entrega para nosso processo de implantação contínua.
Quando a imagem do Docker é criada após cada pequena alteração (com o pipeline de CI mostrado acima), damos um passo adiante e implantamos a alteração em nosso cluster de desenvolvimento usando gcloud
. Vamos dar uma olhada nessa etapa do pipeline do 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}
Isso aciona um processo de lançamento para implementar as alterações em nosso cluster Kubernetes de desenvolvimento. Depois de testar e obter as aprovações, promovemos a alteração para o ambiente de teste e, em seguida, para o ambiente de produção. Tudo isso é possível porque temos uma imagem do Docker fina e isolada para cada alteração que tem quase tudo o que precisa. Só precisamos informar à implantação qual tag você deve usar.
Como a equipe de garantia de qualidade se beneficia desse processo
A equipe de controle de qualidade precisa principalmente de uma versão de pré-produção na nuvem dos aplicativos a serem testados. No entanto, às vezes eles precisam executar um aplicativo pré-criado localmente (com todas as dependências) para testar um determinado recurso. Nesses casos, eles não querem ou não precisam passar por todo o trabalho de clonar o projeto inteiro, instalar pacotes npm, criar o aplicativo, enfrentar erros de desenvolvedor e revisar todo o processo de desenvolvimento para colocar o aplicativo em funcionamento. Agora que tudo já está disponível como uma imagem do Docker no Google Container Registry, tudo o que eles precisam é de um serviço no arquivo Docker compose:
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
Com esse serviço, eles podem ativar o aplicativo em suas máquinas locais usando contêineres do Docker, executando:
docker compose up
Esse é um grande passo para simplificar os processos de teste. Mesmo que o controle de qualidade decida testar uma tag específica do aplicativo, ele poderá alterar facilmente a tag da imagem na linha 6 e executar novamente o comando Docker compose. Mesmo que você decida comparar diferentes versões do aplicativo simultaneamente, poderá fazer isso facilmente com alguns ajustes. A maior vantagem é manter nossa equipe de controle de qualidade longe dos desafios do desenvolvedor.
Vantagens de usar o Docker
- Quase zero de rastros para dependências: Se você decidir atualizar a versão do Redis ou do Postgres, basta alterar uma linha e executar o aplicativo novamente. Não há necessidade de mudar nada em seu sistema. Além disso, se você tiver dois aplicativos que precisam do Redis (talvez até com versões diferentes), você pode ter ambos rodando em seu próprio ambiente isolado, sem conflitos entre eles.
- Várias instâncias do aplicativo: Há muitos casos em que precisamos executar o mesmo aplicativo com um comando diferente. Por exemplo, inicializar o banco de dados, executar testes, monitorar alterações no banco de dados ou ouvir mensagens. Em cada um desses casos, como já temos a imagem construída pronta, basta adicionar outro serviço ao arquivo de composição do Docker com um comando diferente e pronto.
- Ambiente de teste mais simples: Na maioria das vezes, você só precisa executar o aplicativo. Você não precisa do código, dos pacotes ou de nenhuma conexão com o banco de dados local. Você só quer ter certeza de que o aplicativo funciona corretamente ou precisa de uma instância em execução como um serviço de backend enquanto trabalha em seu próprio projeto. Esse também pode ser o caso de QA, revisores de Pull Request ou até mesmo pessoal de UX que queira ter certeza de que seu design foi implementado corretamente. Nossa configuração com Docker facilita muito para todos eles prosseguirem sem ter que lidar com muitos problemas técnicos.
Este artigo foi originalmente publicado no Docker.
Deixe um comentário