Implantando Aplicações Serverless no Google Cloud Run
Cristiano Lemes
Posted on November 18, 2024
Introdução
Nesse guia vou implantar os contêineres do sistema do TeoMeWhy da Twitch que hoje está na AWS, e colocar na GCP.
Arquitetura na GCP
Não será usada nenhuma ferramenta de automação complexa, tudo será feita pela console, integrando com o Github e fazendo o deploy das imagens a cada commit na main.
Usaremos:
- Cloud Run - Para as aplicações web
- Cloud SQL - Para o banco de dado MySQL
- GCE - Para rodar o Teomebot
- Cloud Storage - Object Storage (S3)
- Cloud Build - Para criar o deploy das aplicações
- Secret Manager - Salvar as credencias da aplicação de forma segura.
Obtendo as aplicações
- Visite o GitHub do TeoMeWhy e faça o fork das aplicações relacionadas ao projeto.
- Na página do repositório, clique em Starred e depois em Fork.
- Na página de fork, dê um nome ao fork e clique em Create fork.
- Repita o processo para os outros repositórios do projeto caso queira replicar todo o ambiente.
- Faça o clone do fork para ajustar o necessário na aplicação antes de enviá-la para o GCP.
git clone git@github.com:cslemes/points-to-go.git
Criando o Dockerfile para criação da imagem de container
Navegue até a pasta do repositório clonado. O repositório já contém um Dockerfile pensado para o Docker. Vamos analisá-lo:
```
FROM golang:latest
WORKDIR /app/
COPY . .
RUN go build main.go
CMD ["./main"]
```
Esse Dockerfile está funcional, mas vamos otimizá-lo usando multi-stage builds para reduzir o tamanho da imagem final. Como o Go não requer dependências externas, podemos usar uma imagem base mínima como scratch.
-
Troque a imagem de build por uma versão mais leve e nomeie-a para referência em outros estágios.
FROM golang:1.23.1-alpine3.20 AS build
-
Adicione o comando
go mod download
para cachear as dependências ego mod verify
para garantir que elas correspondem aos checksums no arquivogo.sum
.
RUN go mod download && go mod verify
-
Para otimizar o binário para produção, adicione os parâmetros abaixo:
-
CGO_ENABLED=0
: Desativa o suporte a CGO. CGO é a funcionalidade do Go que permite chamar código em C, mas ao desativá-lo, você obtém um binário completamente estático, sem dependências de bibliotecas externas. -
GOARCH=amd64
: Define a arquitetura de destino para a compilação.amd64
é a arquitetura usada em máquinas com processadores de 64 bits, como a maioria dos servidores e desktops modernos. -
GOOS=linux
: Define o sistema operacional de destino para a compilação. Aqui está configurado paralinux
, o que significa que o binário gerado será executável em sistemas Linux. -
go build -o /app/points
: Compila o código e salva o binário no caminho especificado (/app/points
). -
-a
: Força a recompilação de todos os pacotes, incluindo dependências, mesmo que não tenham mudado. Pode ser útil para garantir que tudo seja recompilado com as novas flags e configurações. -
-ldflags="-s -w"
: Essas são flags de otimização de tamanho. -
-s
remove a tabela de símbolos de depuração do binário. -
-w
remove as informações de stack tracing (rastreamento de pilha). Essas opções reduzem o tamanho do binário final. -
-installsuffix cgo
: Adiciona um sufixo ao diretório de instalação para diferenciar binários com CGO desativado. Isso pode evitar conflitos com binários que usam CGO.
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o /app/points -a -ldflags="-s -w" -installsuffix cgo
-
-
(opcional) Para deixar a imagem ainda menor podemos comprimir o binário usando upx, O
upx
(Ultimate Packer for eXecutables) é uma ferramenta que comprime binários executáveis para reduzir o tamanho de arquivos, como binários Go. Os pontos negativos é que vai ter maior tempo de build, e um delay maior no startup do container, então deve ser avaliado o é mais benéfico para sua implantação. Como o objetivo e usar no cloud Run que já possui Cold Start, ele pausa o container quando está sem uso, não vamos utilizar porque vai aumentar o tempo de inicialização do container.
RUN apk add --no-cache curl upx RUN upx --ultra-brute -qq points && upx -t points
-
upx --ultra-brute -qq points
: Comprime o bináriopoints
de forma agressiva, usando todas as opções de compressão possíveis e sem exibir mensagens de saída. -
upx -t points
: Testa o binário comprimido para garantir que ele ainda funcione corretamente. -
Agora com o binário pronto vamos fazer o segundo estágio, que é copiar o binário para uma imagem limpa.
FROM scratch AS prod WORKDIR /app COPY --from=build /app/points / CMD ["./points"]
-
scratch
é uma imagem base especial no Docker que representa uma imagem completamente vazia, sem qualquer sistema operacional ou dependência. UsarFROM scratch
é comum em imagens minimalistas para aplicações Go, onde um binário estático é suficiente.
-
-
Arquivo completo
FROM golang:1.23.1-alpine3.20 AS build WORKDIR /app/ COPY . . RUN go mod download && go mod verify RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o /app/points -a -ldflags="-s -w" -installsuffix cgo ## Comprime o binário opcional RUN apk add --no-cache curl upx RUN upx --ultra-brute -qq points && upx -t points FROM scratch AS prod WORKDIR /app COPY --from=build /app/points / CMD ["./points"]
- Comparando builds
- A otimização do Dockerfile e o uso do upx resultam em uma imagem até 3 vezes menor.
- A análise da imagem original com Trivy mostra 904 CVEs, enquanto a imagem scratch é livre de CVEs.
Imagem | Tipo | Tamanho |
---|---|---|
points-to-go | Otimizado | 14MB |
points-to-go | Com upx | 5.77MB |
points-to-go | original | 1.76GB |
- Repita essas definições para os outros repositórios.
Implantando o banco de dados
- No console, acesse SQL no menu lateral.
- Ou busque "SQL" na barra de pesquisa e selecione SQL na lista.
- Na página do Cloud SQL, clique em Criar Instância e escolha MySQL.
- Selecione Enterprise em edição.
- Em "Predefinições da edição", escolha Sandbox.
- Deixe a versão do banco de dados em MySQL 8.0.
- Dê um nome à instância em ID da Instância e defina uma senha.
- você consegue especificar as politicas de senha expandindo política de senha
- Defina a zona e região, e vamos deixar em uma única zona.
- Em personalizar instância podemos ajustar o hardware de acordo com nossas necessidades, as opções vão variar de acordo com a edição que escolhemos.
- Ajuste a CPU para 1 e o disco para 10GB conforme necessário.
- Em conexões, desmarque IP público e marque IP particular.
- Em conexão de acesso privado clique em Configurar Conexão
- Selecione use um intervalo alocado automaticamente
- Clique em Continuar
- Clique em Criar conexão
- Clique em Criar Instância e aguarde a criação.
-
Para conectar à instância, ative o Cloud Shell e execute:
gcloud sql connect tmwdb --user=root
- Coloque a senha atribuída na etapa anterior.
- E então você vai receber um erro de conexão, porque o cloud shell não tem acesso a sua VPC privada.
- Para simplificar a conexão via Cloud shell, vamos editar a instância e marcar IP público, no apêndice vou mostrar como criar o VPC peering para acessar o DB pelo Cloud Shell
- Tente conectar novamente.
-
Crie o banco de dados da aplicação:
CREATE DATABASE points;
O Cloud Shell ainda tem um editor de texto baseado no VSCode, é possível fazer algumas atividades direto por ele, ele tem 5GB de volume persistente no seu /home, o hardware é um VM e2-small com 1vCPU e 1.7GB de RAM.
Criando containers no Cloud Run
- No console do Google Cloud, acesse o Cloud Run pelo menu lateral ou barra de pesquisa.
- Na página do Cloud Run, clique em Implantar Contêiner e escolha a opção Serviço.
- Escolhendo o Método de Implantação
- Existem três opções para implantar um serviço:
- Usar uma imagem de contêiner de um registry.
- Conectar diretamente a um repositório.
- Criar uma função (utilizando o Cloud Functions, integrado ao Run).
- Para este guia, selecione Implantação Contínua com GitHub, permitindo que o Google configure a pipeline de CI/CD automaticamente.
Configurando a Conexão com o GitHub
Clique em Autenticar para permitir a integração do Google com seu GitHub.
Autorize o acesso, escolhendo entre permitir acesso a todos os repositórios ou apenas um específico.
Escolhendo Tipo de Build
Na etapa de build, selecione entre usar um Dockerfile ou Aplicações com suporte e Buildpacks do GCP.
Opte por Dockerfile e ajuste o caminho/nome do arquivo, se necessário
Configurações de Serviço
-
Configure os seguintes parâmetros:
- Autenticação: Selecione Permitir chamadas não autenticadas.
- Alocação de CPU: Escolha A CPU é alocada somente durante o processamento da solicitação.
- Controle de entrada: Selecione Interno.
Ajuste a porta do container, conforme a necessário para a aplicação.
Na aba segurança, em conta de serviço, clique em criar Nova conta de serviço
-
Adicione as roles
- Administrador de objeto do Storage
- Administrador do Cloud Run
- Assessor de secret do Secret Manager
- Cliente do Cloud SQL
- Conta de serviço do Cloud Build
- Gravador do Artifact Registry
- Usuário da conta de serviço
-
Analisando o código da aplicação, precisamos passar as variáveis de ambiente para o banco de dados.
// db.go ... func OpenDBConnection() (*gorm.DB, error) { godotenv.Load(".env") HOST_DB := os.Getenv("HOST_DB") PORT_DB := os.Getenv("PORT_DB") USER_DB := os.Getenv("USER_DB") PASSWORD_DB := os.Getenv("PASSWORD_DB") ...
-
Vamos ter que alterar o código da aplicação para o padrão do Cloud Sql que usa Unix sockets, e o Cloud Run não acessa diretamente o DB ele usa o Cloud SQL Auth Proxy.
// db.go ... HOST_DB := os.Getenv("HOST_DB") //PORT_DB := os.Getenv("PORT_DB") USER_DB := os.Getenv("USER_DB") PASSWORD_DB := os.Getenv("PASSWORD_DB") //log.Println(PORT_DB) // UNIX dsn dsn := "%s:%s@unix(/cloudsql/%s)/%s? charset=utf8mb4&parseTime=True&loc=Local" //dsn := "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local" dsn = fmt.Sprintf(dsn, USER_DB, PASSWORD_DB, HOST_DB, "points") ...
Em contêiner, vamos na aba variáveis e secrets e clicar em adicionar variável.
Adicione as variáveis necessárias.
O endereço do banco segue o padrão, PROJECT_ID:REGION:INSTANCE_NAME, você também pode obter o nome mais abaixo, no item 16.
A senha do banco vamos botar em
REFERENCIAR UM SECRET
Se a opção
criar novo secret
estiver desativado é porque precisa ativar a Api.Em Conexões do Cloud SQL vamos adicionar a URL do banco que criamos
Para criação do schema da da aplicação, no código exige que passemos o argumento migrations=true.
Vamos adicionar
migrations=true
ao argumento da função, depois removemos na próxima revisão do container.
Testando a aplicação usando Thunder Client.
Criando serviço Teomebot
O chatbot não será implantado no Cloud Run, será instalando no Google Compute Engine (GCE). Diferente do Cloud Run, o Compute Engine é ideal porque o chatbot precisa estar ativo continuamente para interagir com o chat.
Além disso, abordaremos o uso de containers, gerenciamento de secrets e a configuração do Cloud Build para automação do deploy.
Criando uma VM no Google Compute Engine (GCE)
- Acesse o Compute Engine no menu lateral do Console do GCP.
- Clique em Criar Instância.
- Insira um nome para a instância (ex.:
teomebot-instance
). - Configure a região e a zona conforme necessário e anote essas informações para uso posterior.
- Em Configuração da máquina, escolha o tipo
E2
e, em Predefinição, selecionee2-micro
. - Clique na aba Contêiner e clique em Implantar Contêiner.
- Use temporariamente a imagem
nginx:latest
para concluir a configuração inicial. - Adicione as variáveis de ambiente:
-
TWITCH_BOT
: Nome do bot. -
TWITCH_CHANNEL
: Nome do canal da Twitch. -
HOST_DB
: IP privado do banco de dados Cloud SQL. -
USER_DB
: Usuário do banco. -
PORT_DB
: Porta do banco. -
URL_POINTS
: Endpoint do serviço Points-to-Go.
-
- Clique em
Selecionar
- Em Identidade e acesso à API, selecione a conta de serviço configurada para este projeto.
- Deixe o restante das configurações no padrão e clique em Criar.
Adicionando Secrets com o Secret Manager
- Você deve ter percebido que não passamos as secrets, no compute engine não tem um jeito simples de passar como no cloud run. Minha solução foi adicionar uma função no código da aplicação para ler a informação direto do secret manager.
- Acesse o Secret Manager no Console do GCP.
- Clique em Criar Secret
- Insira um nome descritivo (ex.:
twitch-token
) e adicione o valor correspondente. - Copie o caminho do secret gerado (ex.:
projects/123456/secrets/twitch-token/versions/latest
).- Crie um novo arquivo
utils/gcp.go
- Crie um novo arquivo
-
Altere o utils/db.go para chamar a função, passando o caminho do secret manager como parâmetro.
func OpenDBConnection() (*gorm.DB, error) { PASSWORD_DB := accessSecretVersion("projects/******/secrets/tmwdb-root/versions/latest") //godotenv.Load(".env") HOST_DB := os.Getenv("HOST_DB") PORT_DB := os.Getenv("PORT_DB") USER_DB := os.Getenv("USER_DB") //PASSWORD_DB := os.Getenv("PASSWORD_DB") dsn := "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local" dsn = fmt.Sprintf(dsn, USER_DB, PASSWORD_DB, HOST_DB, "teomebot") db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) return db, err }
-
Altere o
main.go
para pegar as credencias da Twitch
func main() {
migration := flag.Bool("migrations", false, "Realizar migrations do banco de dados")
flag.Parse()
godotenv.Load()
user := os.Getenv("TWITCH_BOT")
// alteração
oauth := utils.AccessSecretVersion("projects/551619572964/secrets/twitch-token/versions/latest")
channel := os.Getenv("TWITCH_CHANNEL")
```
Cloud Build
Configurando o Cloud Build
O Cloud Build será usado para automatizar a criação da imagem do container e sua implantação no Compute Engine.
- Crie um arquivo
cloudbuild.yaml
na raiz do repositório com o conteúdo abaixo: -
Substitutions
substitutions: _VERSION: "v1.0.${COMMIT_SHA}"
A variável _VERSION
é definida com um valor que combina v1.0.
com o hash do commit atual (${COMMIT_SHA}
). Isso cria uma versão única para cada build, garantindo que cada imagem seja identificável pela versão e pelo commit.
Steps
A seçãosteps
define as etapas que o Cloud Build deve executar. Aqui, temos quatro etapas: build, push (duas vezes) e update.-
Etapa 1: Build da Imagem Docker
- name: "gcr.io/cloud-builders/docker" args: - "build" - "--no-cache" - "-t" - "gcr.io/$PROJECT_ID/teomebot:$_VERSION" - "-t" - "gcr.io/$PROJECT_ID/teomebot:latest" - "." id: Build
Esta etapa executa um build da imagem Docker:
-
"--no-cache"
: força o build sem utilizar o cache. -
"-t"
: define tags para a imagem criada:-
gcr.io/$PROJECT_ID/teomebot:$_VERSION
: imagem com a tag que usa o hash do commit. -
gcr.io/$PROJECT_ID/teomebot:latest
: imagem com a taglatest
.
-
-
"."
: define o diretório atual como o contexto do build.
A tag id: Build
é um identificador opcional para a etapa, útil para referência e depuração.
- Etapa 2: Push da Imagem com Tag de Versão
- name: "gcr.io/cloud-builders/docker"
args:
- "push"
- "gcr.io/$PROJECT_ID/teomebot:$_VERSION"
id: Push
Essa etapa faz o push da imagem com a tag específica ($_VERSION
) para o Google Container Registry, permitindo que a versão gerada no build seja armazenada no repositório.
-
Etapa 3: Push da Imagem com Tag
latest
- name: "gcr.io/cloud-builders/docker" args: - "push" - "gcr.io/$PROJECT_ID/teomebot:$_VERSION" id: Push
Esta etapa faz o push da imagem com a tag latest
para o Google Container Registry, atualizando a imagem latest
com a versão mais recente.
-
Etapa 4: Atualização do Container em uma Instância GCE
- name: "gcr.io/cloud-builders/gcloud" args: - "compute" - "instances" - "update-container" - "teomebot-instance" - "--container-image=gcr.io/$PROJECT_ID/teomebot:latest" - "--zone=$_DEPLOY_ZONE" - "--container-restart-policy=always"
Essa etapa usa o comando gcloud
para atualizar o container em uma instância do Google Compute Engine:
-
"teomebot-instance"
: especifica o nome da instância que executa o container. -
--container-image
: define a imagem do container que a instância deve usar. Aqui, usa a versãolatest
da imagem. -
--zone=$_DEPLOY_ZONE
: usa uma variável para especificar a zona de implantação. -
--container-restart-policy=always
: define a política de reinicialização do container para sempre reiniciar em caso de falha. -
Options
options: logging: CLOUD_LOGGING_ONLY
A opção logging: CLOUD_LOGGING_ONLY
especifica que o Cloud Build deve registrar apenas no Cloud Logging, economizando dados e foco nos logs do GCP.
-
Arquivo final
substitutions: _VERSION: "v1.0.${COMMIT_SHA}" steps: - name: "gcr.io/cloud-builders/docker" args: - "build" - "--no-cache" - "-t" - "gcr.io/$PROJECT_ID/teomebot:$_VERSION" - "-t" - "gcr.io/$PROJECT_ID/teomebot:latest" - "." id: Build - name: "gcr.io/cloud-builders/docker" args: - "push" - "gcr.io/$PROJECT_ID/teomebot:$_VERSION" id: Push - name: "gcr.io/cloud-builders/docker" args: - "push" - "gcr.io/$PROJECT_ID/teomebot:latest" - name: "gcr.io/cloud-builders/gcloud" args: - "compute" - "instances" - "update-container" - "teomebot-instance" - "--container-image=gcr.io/$PROJECT_ID/teomebot:latest" - "--zone=$_DEPLOY_ZONE" - "--container-restart-policy=always" options: logging: CLOUD_LOGGING_ONLY
Criando Gatilho para Criação da Imagem de Container
Configurando a Conta de Serviço
- Acesse o Cloud Build no console do Google Cloud.
- Vá em Configurações.
- Clique em Permissões de Conta de Serviço.
- Localize a conta de serviço criada para o Cloud Run.
- Ative a opção Definir como conta de serviço preferida
- Habilite a função Administrador da Instância do Compute à conta de serviço. Criando o Gatilho
- No menu lateral, clique em Gatilhos e depois em Criar Gatilho.
- Insira um nome descritivo para o gatilho.
- Em Repositórios, clique em Conectar Repositório e selecione o repositório Teomebot.
- Em Configuração, selecione a opção Arquivo de Configuração do Cloud Build.
- Adicione a variável de substituição
_DEPLOY_ZONE
com o valor correspondente à zona em que a instância foi criada. - Em conta de serviço verifique a conta selecionada se está conforme configuramos no passo 6.
- Clique em Salvar. Executando o Gatilho
- Na tela de visão geral, na linha do gatilho recém criado, clique em executar para rodar o processo manualmente.
- Nos detalhes do processo, acompanhe os passos do build da imagem para verificar possíveis erros.
Testando a Aplicação
- No painel do Compute Engine, copie o comando de ssh para acessar a instancia, ou use o cliente web ssh, e connect a instância.
-
Conecte-se à instância e execute os comandos abaixo para verificar o estado do container:
docker ps docker container logs <container_id>
Resolvendo Problemas de Certificado
-
Caso ocorra um erro relacionado a certificados (causado pela imagem base
scratch
), substitua-a pela imagemdistroless
. No Dockerfile, altere a linha que define a imagem base de:
FROM scratch
para:
```
FROM gcr.io/distroless/static-debian12
```
Dockerfile atualizado:
```Dockerfile
FROM golang:1.23.1-alpine3.20 AS build
WORKDIR /app
COPY . .
RUN go mod download && go mod verify
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o /app/tmwbot -a -ldflags="-s -w" -installsuffix cgo
FROM gcr.io/distroless/static-debian12 AS prod
WORKDIR /app
COPY --from=build /app/tmwbot /
ENTRYPOINT ["/tmwbot"]
```
Ajustando Permissões para o Secret Manager
- Alterar o escopo da conta de serviço para acessar o Secret Manager.
- Acesse o Console do Google Cloud.
- No menu lateral, vá para Compute Engine > Instâncias de VM.
- Encontre e clique no nome da sua instância de VM.
- Na página de detalhes da VM, clique em Parar para desligar a instância (essa etapa é necessária, pois o escopo de conta de serviço só pode ser modificado com a instância parada).
- Depois que a instância for interrompida, clique em Editar na parte superior da página.
- Role até a seção Identidade e API de acesso.
- Em Conta de Serviço, selecione a conta de serviço que sua aplicação usa.
- Em Escopos de acesso da API, selecione Permitir acesso total a todas as APIs do Cloud ou clique em Definir escopos específicos de acesso da API e adicione o escopo
https://www.googleapis.com/auth/cloud-platform
. - Após ajustar o escopo, clique em Salvar para aplicar as mudanças.
- Reinicie a instância clicando em Iniciar.
-
Ou pela linha de comando, parando a instância, rodando o comando e iniciando depois.
gcloud compute instances set-service-account teomebot-instance --scopes=https://www.googleapis.com/auth/cloud-platform --zone "us-central1-a"
Adicionando mais containers
Os demais serviços seguem o mesmo processo do points-to-go, para os serviços que comunicam entre si crie variáveis de ambiente para configurar o endereço dos endpoints, que serão sempre https porta 443.
Para comunicação com outros serviços ajustei o código para receber mais uma variável de ambiente com a url do serviço, no points por exemplo ficou assim:
```go
...
var URL_POINTS = os.Getenv("URL_POINTS")
...
url := fmt.Sprintf("https://%s/customers/", URL_POINTS)
...
```
Testanto o bot
Testando a comunicação do bot com a Twitch.
Ajuste de Segurança na Rede
Após finalizar os testes, coloque os container para ser acessados somente internamente na VPC.
Conclusão
Com isso finalizamos a migração do sistema do TeoMeWhy, o guia serve de base para migrar os outros serviços do TeoMeWhy.
Os principais objetivos alcançados foram:
Realizações Técnicas
- Migração de aplicações conteinerizadas para o Cloud Run, permitindo escalabilidade automática e redução de custos
- Otimização de imagens Docker através de multi-stage builds, reduzindo significativamente o tamanho das imagens e vulnerabilidades
- Implementação de banco de dados gerenciado com Cloud SQL, garantindo alta disponibilidade e segurança
- Configuração de CI/CD automatizado usando Cloud Build, possibilitando deploys automáticos a partir do GitHub
- Gestão segura de credenciais utilizando Secret Manager
Melhorias na Arquitetura
- Separação clara de responsabilidades entre serviços
- Utilização de conexões privadas para maior segurança
- Implementação de padrões serverless para otimização de custos
- Automação de processos de build e deploy
- Integração contínua com repositórios do GitHub
Benefícios Obtidos
- Custos: Redução de custos através do modelo serverless e otimização de recursos
- Manutenibilidade: Facilidade de manutenção com deploys automatizados
- Segurança: Gestão apropriada de secrets e conexões privadas
- Escalabilidade: Capacidade de escalar automaticamente conforme a demanda
- Monitoramento: Melhor visibilidade da infraestrutura através das ferramentas nativas da GCP
Apêndice
Habilitar a API do Secret Manager
- No console do Google Cloud, pesquise por Secret Manager API.
- Clique na API nos resultados da pesquisa.
- Na tela de detalhes, clique em Ativar.
Referências
Posted on November 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.