Como automatizar a implantação de aplicativos no cluster do Kubernetes usando GitHub Actions

No último artigo dessa série ensinei como configurar um cluster Kubernetes gratuito com os recursos de nível sempre gratuito da Oracle Cloud Infrastructure, hoje você irá aprender como automatizar a implantação de suas aplicações a partir de um repositório git no GitHub diretamente para o cluster Kubernetes usando as GitHub Actions, realizando o build e a publicação de uma imagem Docker no OCI Container Registry permitindo assim que você saia de uma vez por todas de serviços como os da Vercel.

Eu vou te mostrar como configurar políticas de segurança no console da OCI, configurar seu repositório no GitHub para suportar builds e implementações automatizadas, e claro, implementar sua aplicação conteinerizada no seu cluster OKE.

Antes de começarmos

Para seguir esse guia eu presumo que você já tenha:

  • Um cluster Kubernetes gratuito rodando na OCI (siga os passos do artigo anterior para configurar um), também todas as credenciais necessárias para interagir com esse cluster.
  • Conhecimento básico de conceitos de Docker e Kubernetes.
  • Uma conta no GitHub.

Também gostaria de te apresentar a arquitetura da infraestrutura que foi implementada neste guia:

kubernetes-github-actions.webp
OCI architecture and GitHub workflow to automate deployments

Vamos começar!

1. Protegendo seu cluster OKE com os Grupos de Segurança de Rede (NSGs)

A OCI recomenda gerenciar as regras de segurança do seu cluster OKE usando os Grupos de Segurança de Rede (NSGs). Esses grupos permitem que você defina controle de acesso granulares para as aplicações rodando no cluster OKE.

1.1 Configurando políticas de segurança para permitir que o cluster gerencie NSGs.

No console da OCI navegue até Identidade e Segurança > Políticas.

  • Clique em Criar Política.
  • Nomeie sua política ("OKE-Security", por exemplo) e forneça uma descrição.
  • Selecione a opção Mostrar editor manual.
  • Adicione as seguintes declarações de políticas ao construtor de políticas:
ALLOW any-user to manage network-security-groups in compartment <compartment-name> where request.principal.type = 'cluster'
ALLOW any-user to manage vcns in compartment <compartment-name> where request.principal.type = 'cluster'

Substitua pelo nome do compartimento do seu cluster OKE (se você criou um).

Caso seu cluster tenha sido criado na raíz do compartimento da sua tenancy, use compartment id e o OCID da tenancy: ... in compartment id <tenancy-OCID>

1.2 Criando um NSG padrão para os backends dos Network Load Balancers

O próximo passo é criar um Grupo de Segurança de Rede (NSG) para gerir as regras de segurança dos backends dos Network Load Balancers. No console da OCI navegue até Redes Virtuais na Nuvem > selecione a VCN onde o cluster OKE está rodando.

  • Em resources, selecione Grupos de Segurança de Rede.
  • Clique em Criar Grupo de Segurança de Rede.
  • Nomeie seu novo grupo e clique em Próximo.
  • Já em Regras de Segurança não adicione nenhuma e clique em Criar.

1.3 Adicionando o NSG criado as instâncias dos nodes do cluster OKE.

Para surtir efeito e o oci-cloud-controller-mananger fazer sua mágica orquestrando as regras de segurança na VCN do cluster OKE e expor corretamente à internet os serviços Kubernetes do tipo LoadBalancer, precisamos atrelar o grupo recém criado à todas os nós do cluster.

No console OCI navegue até Serviços ao Desenvolvedor > Contêineres e Artefatos > Clusters do Kubernetes (OKE).

  • Selecione o cluster criado anteriormente.
  • Em recursos, selecione Pools de nós e clique no nome da pool de nós ("pool1", por exemplo).
  • Em nós, clique no nome do nó, uma página com informações da instância daquele nó deverá aparecer.
  • Em VNICS, procure por Network Security Groups e clique em editar.
  • Selecione o NSG criado anteriormente, repita esse processo para todas as instâncias de nós do cluster.

2. Configurando seu repositório do GitHub.

Para Dockerizar nossa aplicação e criar um workflow válido para as GitHub Actions, precisamos configurar algumas coisas em nosso repositório.

  • Crie um novo diretório no repositório para os arquivos do workflow:
mkdir .github/workflows
  • Crie um Dockerfile na raiz do repositório para definir as instruções de build da aplicação.
  • Dockerfile de exemplo para uma aplicação Go Lang:
FROM golang:1.22.5-alpine3.19 AS build
WORKDIR /app
COPY go.mod ./
RUN go mod download
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o hello main.go

FROM gcr.io/distroless/static-debian11:nonroot
COPY --from=build /app/hello /hello
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/hello"]
  • Crie um diretório para armazenar os arquivos de manifestos do Kubernetes (opcional):
mkdir kubernetes

2.1 Preparando um manifesto YAML para implantação.

Imagine que seu repositório hospeda o código de uma aplicação Go Lang, vamos preparar um arquivo YAML que descreva um deployment e service que serão implementados no cluster Kubernetes.

  • Exemplo do manifesto YAML do Deployment kind:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-hello-world
  labels:
    app: go-hello-world
spec:
  replicas: 2
  selector:
    matchLabels:
      app: go-hello-world
  template:
    metadata:
      labels:
        app: go-hello-world
    spec:
      containers:
      - name: go-hello-world
        image: $IMAGE_NAME
        imagePullPolicy: Always
        ports:
          - containerPort: 8080
            protocol: TCP
      imagePullSecrets:
        - name: $DOCKER_REGISTRY_SECRET_NAME

O deployment kind define os detalhes da aplicação "go-hello-world", incluindo as configurações dos pods, veja que estamos referenciando uma variável de ambiente para definir a imagem que será utilizada nos pods e uma outra para definir o nome do docker-registry secret a ser utilizado quando o pull da imagem for realizado em nosso OCI Container Registry. Essas variáveis serão criadas e terão valores atribuídos dinamicamente no ambiente do runner no momento em que rodarmos o workflow da GitHub Action que criaremos logo mais.

  • Exemplo do manifesto YAML do Service kinkd:
---
apiVersion: v1
kind: Service
metadata:
  name: go-hello-world-lb
  labels:
    app: go-hello-world
  annotations:
    oci.oraclecloud.com/load-balancer-type: "nlb"
    oci-network-load-balancer.oraclecloud.com/backend-policy: "THREE_TUPLE"
    oci.oraclecloud.com/security-rule-management-mode: "NSG"
    oci-network-load-balancer.oraclecloud.com/security-list-management-mode:  "None"
    oci.oraclecloud.com/oci-backend-network-security-group: $OCI_NETWORK_SG_BACKEND
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  ports:
    - name: http
      port: 80
      targetPort: 8080
  selector:
    app: go-hello-world

O service kind é o responsável por expor a aplicação go-hello-world à internet através de um LoadBalancer service type, escutando na porta 80 e redirecionando o tráfego para as portas correspondentes nos pods (nodePort:containerPort). Como visto no arquivo YAML, há anotações específicas da OCI usadas para definir o tipo de load balancer (NLB para OCI Network Load Balancer) e lidar com as políticas de segurança da rede, com destaque para a anotação "oci.oraclecloud.com/oci-backend-network-security-group" que referencia para uma variável de ambiente contendo o OCID do Grupo de Segurança de Rede para backends dos NLB que criamos e adicionamos às instâncias de nós do cluster anteriormente, e de novo, quando o workflow da nossa GitHub Action, definiremos o valor para essa variável de forma dinâmica a partir dos secrets que definiremos em nosso repositório do GitHub no próximo passo.

Assim fica a estrutura de um repositório de exemplo após todas as mudanças feitas:

.dockerignore
.github
   |-- workflows
   |   |-- main.yml
.gitignore
Dockerfile
go.mod
kubernetes
   |-- main.yml
main.go

3. Configurando Secrets para as GitHub Actions

Para automatizar as implementações de maneira segura vamos utilizar os Secrets das GitHub Actions para armazenar de maneira segura informações sensíveis necessárias em nossa aplicação ou até mesmo credenciais de acesso, como por exemplo, as credencias da OCI, tokens de acesso e detalhes do cluster.

  • No seu repositório no GitHub navegue até repository's Settings > Secrets & variables > Actions.
  • Crie os seguintes repository's secrets, substituindo a descrição abaixo com os valores dos secrets correspondentes:
    • OKE_CLUSTER_OCID: O id do cluster OKE, disponível na página de detalhes do cluster.
    • OCI_CLI_REGION: A região da conta OCI (por exemplo, sa-saopaulo-1).
    • OCI_CLI_TENANCY: A OCID da tenancy da conta OCI.
    • OCI_CLI_USER: A OCID do seu usuário de acesso à conta OCI, disponível em seus detalhes de perfil.
    • OCI_CLI_FINGERPRINT: A impressão digital da chave pública do seu usuário.
    • OCI_CLI_KEY_CONTENT: O conteúdo da chave privada do seu usuário.
    • OCIR_AUTH_TOKEN: O token de autenticação (usado para interação com o OCI Registry, pode ser gerado no seu perfil de usuário no console OCI).
    • OCIR_REGISTRY: O domínio da OCI Registry da região da sua conta (por exemplo, gru.ocir.io).
    • OCIR_USERNAME: Usuário para acesso ao OCI Registry (composto pelo namespace da tenancy + endereço de email usado para acessar sua conta OCI, por exemplo, sbataw3dd/jhon@acme.com).
    • OCIR_USERNAME_EMAIL: O email de acesso à conta OCI
    • OCIR_REPO_NAME: O nome desejado para o repositório na OCI Registry (por exemplo, ronilsonalves/meu-repositorio).
    • OCI_NETWORK_SG_BACKEND: A OCID do Grupo de Segurança de Rede criada e adicionada às instâncias dos nós do cluster.
    • OCI_TENANCY_NAMESPACE: O namespace de armazenamento de objetos criado automaticamente para sua OCI tenancy. Disponível nos detalhes de sua tenancy.

4. Criando o GitHub Action Workflow.

Crie um arquivo chamado main.yml dentro do diretório .github/workflows. Esse arquivo irá definir os passos para automatizar nosso processo de implementação de aplicações. O arquivo que iremos criar como todos outros criados até aqui estão no repositório criado de exemplo para esse artigo, link no final do artigo.

Antes de prosseguirmos, vamos voltar ao console da OCI e nos certificarmos de que é possível criar um repositório no Container Registry no primeiro envio no compartimento raiz.

Navegue até Serviços ao Desenvolvedor > Contêineres & Artefatos > Registro de Contêiner

  • Clique em Definições, dê check na opção de Criar repositório no primeiro envio no compartimento raiz.

Certo, vamos finalmente para o GitHub Actions Workflow:

name: Build, push and deploy example

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build-and-publish-docker-image:
    name: Build and publish Docker image
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Login to Oracle Cloud Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.OCIR_REGISTRY }}
          username: ${{ secrets.OCIR_USERNAME }}
          password: ${{ secrets.OCIR_AUTH_TOKEN }}
      - name: Build Docker image
        uses: docker/build-push-action@v6
        with:
          platforms: linux/arm64
          context: .
          file: Dockerfile
          tags: ${{ secrets.OCIR_REGISTRY }}/${{ secrets.OCI_TENANCY_NAMESPACE }}/${{ secrets.OCIR_REPO_NAME }}:latest
          push: false
          load: true
          cache-from: type=gha
          cache-to: type=gha,mode=max
      - name: Push Docker Image to Oracle Cloud Registry
        run: |
          docker push ${{ secrets.OCIR_REGISTRY }}/${{ secrets.OCI_TENANCY_NAMESPACE }}/${{ secrets.OCIR_REPO_NAME }}:latest

  deploy-to-oke:
    needs: build-and-publish-docker-image
    name: Deploy container image to an OKE Cluster
    runs-on: ubuntu-latest 
    env:
      OCI_CLI_USER: ${{ secrets.OCI_CLI_USER }}
      OCI_CLI_TENANCY: ${{ secrets.OCI_CLI_TENANCY }}
      OCI_CLI_FINGERPRINT: ${{ secrets.OCI_CLI_FINGERPRINT }}
      OCI_CLI_KEY_CONTENT: ${{ secrets.OCI_CLI_KEY_CONTENT }}
      OCI_CLI_REGION: ${{ secrets.OCI_CLI_REGION }}
      OCI_NETWORK_SG_BACKEND: ${{ secrets.OCI_NETWORK_SG_BACKEND }}
      IMAGE_NAME: ${{ secrets.OCIR_REGISTRY }}/${{ secrets.OCI_TENANCY_NAMESPACE }}/${{ secrets.OCIR_REPO_NAME }}:latest
      DOCKER_REGISTRY_SECRET_NAME: 'name-of-your-secret'
      NAMESPACE: 'name-of-desired-kubernetes-namespace'
    steps:
      - name: Configure Kubectl
        uses: oracle-actions/configure-kubectl-oke@v1.5.0
        with:
          cluster: ${{ secrets.OKE_CLUSTER_OCID }}
      - name: Checkout
        uses: actions/checkout@v4
      - name: Create namespace if not exists
        run: |
          kubectl create namespace ${{ env.NAMESPACE }} || echo "namespace already exists"
      - name: Create Docker registry secret
        run: | 
          kubectl -n preview create secret docker-registry ${{ env.DOCKER_REGISTRY_SECRET_NAME }} \
          --docker-server=${{ secrets.OCIR_REGISTRY}} \
          --docker-username=${{ secrets.OCIR_USERNAME}} \
          --docker-password='${{ secrets.OCIR_AUTH_TOKEN }}' \
          --docker-email=${{ secrets.OCIR_USERNAME_EMAIL}} || echo "secret already exists"
      - name: Deploy to Kubernetes Cluster
        run: |
          envsubst < kubernetes/main.yml | kubectl apply -f - -n ${{ env.NAMESPACE }}

Esse fluxo de trabalho é desencadeado em eventos push e pull request na branch main. Vamos mergulhar fundo nesse workflow e entender melhor os jobs e passos desse fluxo:

build-and-publish-docker-image

Esse trabalho é responsável por realizar o build e publicar a imagem Docker para o OCI Container Registry.

1. Checkout :

Esse passo é o responsável por fazer o checkout do código do repositório do GitHub dentro do ambiente do runner. A ação usada para isso é a oficial do GitHub, actions/checkout@v4.

2. Set up QEMU :

Esse passo configura o QEMU, um emulador de VM, necessário para realizar a criação e testar as imagens Docker. A ação usada nesse passo é a oficial do Docker, docker/setup-qemu-action@v3.

3. Set up Docker Buildx :

Nesse passo configuramos a ferramenta Docker Buildx, utilizada para realizar o build e publicar imagens Docker. Também é uma ação oficial do Docker docker/setup-buildx-action@v3.

4. Login to Oracle Cloud Registry :

Nesse passo é realizado a autenticação nos servidores da OCI Registry usando as credenciais que configuramos nos secrets do repositório. Essa ação também é oficial Docker, docker/login-action@v3.

5. Build Docker image :

Esse passo realiza o build da imagem Docker usando a ação oficial docker/build-push-action@v6. O build context é diretório atual (.), o Dockerfile é usado como script de build, a imagem recebe a tag definida pela URL do Registro de Contêineres da OCI/<OCIR_USERNAME>/<OCIR_REPO_NAME>:latest. A opção push é definida como falsa para evitar que a imagem seja enviada para o registro imediatamente, por alguma razão estranha, quando enviando direto da ação a imagem é criada e enviada dividida em layers.

6. Push Docker Image to Oracle Cloud Registry :

Esse passo envia a imagem Docker criada para o Registro de Contêineres da OCI usando o comando docker push.

deploy-to-oke

Esse trabalho é responsável por implementar a imagem Docker criada no passo anterior para o cluster Kubernetes da Oracle (OKE).

1. Needs :

Essa diretiva especifica que o trabalho build-and-publish-docker-image deve ter sido concluído com sucesso para que então este trabalho possa começar.

2. Configure Kubectl :

Esse passo configura a ferramenta cli kubectl para se conectar ao cluster OKE especificado e usa uma ação oficial da Oracle, oracle-actions/configure-kubectl-oke@v1.5.0.

3. Checkout :

Esse passo é o responsável por fazer o checkout do código do repositório do GitHub dentro do ambiente do runner.

4. Create namespace if not exists :

Nesse passo é criado um namespace dentro do cluster Kubernetes caso ele ainda não exista usando o comando kubectl create namespace.

5. Create Docker registry secret :

Já nesse passo é criado um secret do tipo docker-registry dentro do namespace especificado e criado no passo anterior, usando o comando kubectl create secret.

6. Deploy to Kubernetes Cluster :

Nesse passo finalmente fazemos a implementação da imagem Docker buildada e publicada para o Registro de Contêineres da OCI no cluster Kubernetes usando o comando kubectl apply. A configuração da impantação é lista do arquivo nomeado kubernetes/main.yml e o comando envsubst é usado para substituir as variáveis de ambiente presentes dentro do arquivo.

Se tudo ocorrer bem, os próximos eventos push e pull request para a branch main do repositório desencadearam a ação e serão criada e publicada para o Registro de Contêineres da OCI a imagem Docker da nossa aplicação Dockerizada para o cluster Kubernetes.

O próprio cluster OKE irá provisionar um Network Load Balancer e irá configurá-lo, incluindo as regras de segurança da rede. Ao final, você terá um NLB com endereço IPV4 publico que você pode apontar o domínio/subdomínio para. É possível definir um endereço de IP reservado no manifesto do serviço Kubernetes de tipo LoadBalancer, para mais detalhes e outras formas de implementação, leia a documentação da OCI.

GitHub Actions workflow executado com sucesso:

actions-running.webp
GitHub Actions workflow executed successfully

Pods rodando:

NAME                              READY   STATUS    RESTARTS   AGE
go-hello-world-6cbdd98797-hs7lv   1/1     Running   0          7m10s
go-hello-world-6cbdd98797-t4d5f   1/1     Running   0          7m10s

Network Load Balancer provisionado pelo cluster OKE:

nlb-oke-provisioned.webp
NLB provisioned by OKE Cluster

Conclusão

Esse é um guia simples, porém, esclarecedor de como automatizar a implementação de aplicativos para um cluster Kubernetes e lhe dá conhecimento para mudar de qualquer PaaS com abordagem Docker para uma nuvem pública. Você pode incluir trabalhos em seus workflows para testar seu código antes de buildar, publicá-lo e implementá-lo em um cluster Kubernetes, os workflows das GitHub Actions podem ser utilizados para melhorar seus processos de CI/CD, gerenciando múltiplos ambientes de desenvolvimento (produção, homologação, testes).

Espero que este artigo possa ter sido útil para você, me deixe saber se você tem comentários ou algo a acrescentar sobre isso.

Até a próxima!

Links & referências

Veja também