Computação em nuvem: taxas de transferência de dados podem ser enormes

As taxas de transferência de dados são um dos itens mais irritantes em uma fatura de nuvem. Quando dois serviços se comunicam pela internet pública, você paga por cada byte trafegado. O suporte a múltiplos containers do Google Cloud Run muda esse cenário para arquiteturas no estilo sidecar: containers na mesma aplicação Cloud Run compartilham um namespace de rede e se comunicam via localhost, portanto nenhum desse tráfego é contabilizado como transferência externa de dados.

Entendendo o Suporte a Múltiplos Containers do Google Cloud Run

O Cloud Run sempre foi direto ao ponto para deployments com container único. O suporte a múltiplos containers adiciona a capacidade de executar containers sidecar junto ao principal, todos compartilhando o mesmo ambiente. O detalhe-chave: por compartilharem um namespace de rede, requisições entre containers nunca saem do host. Isso elimina o custo de transferência de dados e reduz a latência em comparação com chamar um serviço separado.

Benefícios de Aplicações Multi-Container no Cloud Run

  • Redução de custos: tráfego entre serviços via localhost não incorre em tarifas de transferência de dados pela internet.
  • Arquitetura simplificada: serviços auxiliares como agentes de logging ou processadores específicos de linguagem rodam junto ao container principal sem um deployment separado.
  • Superfície de ataque reduzida: menos chamadas de rede externas.
  • Menor latência na comunicação entre processos.

Reduzindo Custos de Transferência de Dados

Em uma configuração padrão de microsserviços, cada chamada de serviço cruza a rede, gerando latência e custos de transferência. Deployments multi-container eliminam ambos para o tráfego entre containers na mesma aplicação.

Como Deployments Multi-Container Reduzem Custos

Os containers compartilham a mesma interface localhost. O tráfego entre eles nunca atravessa a internet pública, portanto não aparece na sua fatura de transferência de dados. Recursos compartilhados também significam menor overhead de memória e CPU em comparação com serviços Cloud Run separados.

Construindo uma Aplicação Multi-Container: Exemplo com Node.js e Python

O cenário: um container Node.js trata requisições HTTP dos usuários e chama um container Python para processamento de dados. Por estarem co-localizados, essa chamada interna não gera nenhum custo de transferência.

Visão Geral da Aplicação

O container Node.js serve a interface web e processa as requisições dos usuários. O container Python realiza o processamento de dados, como inferências de machine learning ou análise de dados.

Configurando o Container Node.js

// server.js
const express = require('express');
const axios = require('axios');
const app = express();
const PORT = process.env.PORT || 8080;

app.get('/', async (req, res) => {
  try {
    // Call the Python service
    const response = await axios.get('http://localhost:5000/process');
    res.send(`Python Service Response: ${response.data}`);
  } catch (error) {
    res.status(500).send('Error communicating with Python service');
  }
});

app.listen(PORT, () => {
  console.log(`Node.js server listening on port ${PORT}`);
});

Dockerfile para o Container Node.js:

# Node.js Dockerfile
FROM node:14

# Create app directory
WORKDIR /usr/src/app

# Install app dependencies
COPY package*.json ./
RUN npm install

# Bundle app source
COPY . .

# Expose the port
EXPOSE 8080

# Run the application
CMD [ "node", "server.js" ]

package.json:

{
  "name": "nodejs-container",
  "version": "1.0.0",
  "description": "Node.js container for multi-container Cloud Run application",
  "main": "server.js",
  "dependencies": {
    "axios": "^0.21.1",
    "express": "^4.17.1"
  }
}

Configurando o Container Python

# app.py
from flask import Flask
app = Flask(__name__)

@app.route('/process')
def process():
    # Perform data processing
    return 'Data processed successfully by Python service'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Dockerfile para o Container Python:

# Python Dockerfile
FROM python:3.8-slim

# Install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application
COPY . /app
WORKDIR /app

# Expose the port
EXPOSE 5000

# Run the application
CMD [ "python", "app.py" ]

requirements.txt:

flask

Configurando o Serviço Cloud Run

Defina um arquivo service.yaml que lista ambos os containers:

# service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: multi-container-app
spec:
  template:
    spec:
      containers:
        - image: gcr.io/your-project-id/nodejs-container
          ports:
            - containerPort: 8080
        - image: gcr.io/your-project-id/python-container
          ports:
            - containerPort: 5000

Substitua your-project-id pelo ID real do seu projeto no Google Cloud. Ambos os containers compartilham o mesmo namespace de rede, portanto o servidor Node.js consegue alcançar o serviço Python em http://localhost:5000.

Fazendo Deploy no Google Cloud Run

1. Build e Push dos Containers

Build e push do container Node.js:

docker build -t gcr.io/your-project-id/nodejs-container .
docker push gcr.io/your-project-id/nodejs-container

Build e push do container Python:

docker build -t gcr.io/your-project-id/python-container .
docker push gcr.io/your-project-id/python-container

2. Deploy do Serviço

gcloud run services replace service.yaml

Esse comando cria ou substitui o serviço Cloud Run definido em service.yaml.

Verificando a Aplicação

Após o deploy, acesse a URL do serviço Cloud Run. A aplicação Node.js chama o container Python internamente e retorna:

Python Service Response: Data processed successfully by Python service

Vantagens Desta Abordagem

  • Menor latência entre containers em comparação com chamar um serviço Cloud Run separado pela internet.
  • Nenhuma cobrança de transferência de dados externa para a comunicação entre containers.
  • Uma única unidade de deployment para gerenciar em vez de dois serviços separados.

Casos de Uso para Aplicações Multi-Container

Deployments multi-container no Cloud Run são bem adequados para padrões sidecar (agentes de logging, proxies de autenticação, coletores de monitoramento), processamento em linguagens mistas (um modelo de ML em Python junto a uma API Node.js) e decomposição gradual de um monólito onde se deseja co-localização sem o overhead total de microsserviços.

Boas Práticas

  • Defina limites de recursos para cada container a fim de evitar que um consuma os recursos do outro.
  • Implemente health checks em ambos os containers para que o Cloud Run detecte falhas com precisão.
  • Use um formato de logging unificado para que os logs de ambos os containers apareçam de forma coerente no Cloud Logging.

Possíveis Desafios

Configurações multi-container adicionam complexidade ao deployment. A ordem de inicialização dos containers não é garantida, portanto o servidor Node.js precisa lidar com o caso em que o serviço Python ainda não está pronto. As atribuições de porta não podem conflitar. E se ambos os containers precisarem da mesma variável de ambiente com valores diferentes, você precisará ser explícito no service.yaml.

Conclusão

O Cloud Run multi-container elimina o custo de transferência para serviços que de outra forma se comunicariam pela rede. O exemplo com Node.js e Python acima é simples de fazer deploy e elimina imediatamente as cobranças de transferência de dados entre serviços. Para arquiteturas em que dois serviços se comunicam com frequência, essa é uma economia real, e o modelo de rede compartilhada escala para padrões sidecar mais complexos sem alterar a forma como os containers se comunicam.