Construindo pipeline CI/CD de Jobs Spark usando Kubernetes, ArgoCD e GitLab

Ricardo Junior
Data Hackers
Published in
8 min readOct 13, 2023

Neste artigo, exploraremos como construir um pipeline robusto usando tecnologias como Kubernetes, ArgoCD e GitLab, permitindo uma orquestração e deploy eficiente de jobs Spark. A implementação de um pipeline para jobs Spark é essencial para otimizar o desenvolvimento e implantação de aplicações de processamento de dados em larga escala.

Arquitetura CICD

1. Introdução

Em um mundo cada vez mais orientado por dados, a capacidade de processar, analisar e derivar insights a partir de grandes volumes de informações é essencial. O Apache Spark, uma das estruturas de processamento distribuído mais poderosas, desempenha um papel fundamental nessa jornada. No entanto, ao enfrentar a complexidade dos fluxos de dados, a escalabilidade e a automação se tornam cruciais.

É aqui que entram em cena os Pipelines CI/CD (Integração Contínua e Entrega Contínua) e as tecnologias de orquestração. Este artigo explora como construir um pipeline robusto de Jobs Spark, usando tecnologias de ponta, como Kubernetes, ArgoCD e GitLab, para automatizar a implantação, execução e monitoramento de seus fluxos de trabalho de dados. Ao fazer isso, conseguimos acelerar o ciclo de desenvolvimento, aprimorar a qualidade e a confiabilidade dos Jobs Spark e, em última análise, capacitar sua organização a tomar decisões mais informadas e oportunas com base em dados.

Ao decorrer desse post, vamos configurar cada componente desse pipeline, explorando suas funcionalidades e como eles se combinam para criar um ambiente de desenvolvimento de dados altamente eficiente e escalável. Além disso, todos os códigos desse post estão no GitLab.

2. Criando o Pipeline

2.1 Configurando o nosso cluster Kubernetes

Antes de começar a construção do nosso pipeline CI/CD para Jobs Spark, precisaremos criar o nosso ambiente Kubernetes, que servirá como a base para nossos fluxos de trabalho de processamento de dados.

Kubernetes, uma plataforma de orquestração de contêineres de código aberto, oferece a flexibilidade e escalabilidade necessárias para gerenciar recursos de computação de maneira eficiente.

Subimos um cluster Kubernetes na plataforma da Digital Ocean , onde eles disponibilizam 200 dólares durante dois meses para gastarmos como quiser com os nossos cluster spark. Para criar o cluster seguimos a documentação bem intuitiva da plataforma.

Painel de Controle da Digital Ocean

2.2 Instalando o ArgoCD

Com o cluster criado, o primeiro passo que vamos dar é criar os namespaces , onde são um mecanismo usado para criar divisões virtuais e isoladas em um cluster. Eles são uma forma de organizar e segmentar recursos e objetos dentro de um cluster Kubernetes. Cada recurso dentro de um cluster Kubernetes pertence a um namespace. Vamos ter namespaces, cicd e processing. No cicd vamos fazer o deploy do ArgoCD e no namespace de processing será destino para os nossos jobs spark.

$ kubectl create namespace processing
$ kubectl create namespace cicd

Depois que criamos as nossas divisões lógicas, vamos instalar o ArgoCD usando o helm, que é o gerenciador de pacotes do kuberetes. Usamos os seguintes comandos:

$ helm repo add argo https://argoproj.github.io/argo-helm
$ helm repo update
$ helm install argocd argo/argo-cd --namespace cicd

Por fim, para conseguimos acessar o ArgoCD localmente, precisaremos criar um “túnel” entre o nosso computador e o serviço chamado “argocd-server” no cluster Kubernetes e depois pegaremos a senha do usuário padrão (o login do usuário padrão é admin):

$ kubectl port-forward svc/argocd-server -n cicd 8080:443
$ kubectl get secret argocd-initial-admin-secret -n cicd -o jsonpath="{.data.password}" | base64 -d

Depois desses passos, vamos ter o nosso ArgoCD disponível no endereço 127.0.0.1:8080

ArgoCD

2.3 Configurando o nosso Spark Operator

Após a configuração da nossa ferramenta de integração contínua, vamos preparar o nosso cluster para conseguir rodar jobs spark. Para isso vamos usar o Spark Operator desenvolvido pela google.

  • O que é um operador? Um operador no Kubernetes é um padrão de automação que usa código para simplificar o gerenciamento de aplicativos complexos, estendendo as capacidades do Kubernetes. Ele automatiza tarefas como implantação, escalonamento, atualização e monitoramento, tornando o gerenciamento de aplicativos mais eficiente e consistente. Os operadores usam Custom Resource Definitions (CRDs) para definir recursos personalizados e lógica personalizada para controlar o ciclo de vida desses recursos.

Então, o Spark Operator é uma ferramenta no Kubernetes que simplifica a execução de cargas de trabalho do Apache Spark no cluster Kubernetes, permitindo que o usuário defina, implante e gerencie aplicativos Spark usando recursos personalizados no Kubernetes, como SparkApplications, de forma eficiente e escalável. Ele automatiza o gerenciamento de aplicativos Spark, facilitando a orquestração e o monitoramento.

Para adicionar o Spark Operator ao nosso namespace vamos executar os seguintes comandos:

$ helm repo add spark-operator https://googlecloudplatform.github.io/spark-on-k8s-operator
$ helm repo update
$ helm install spark spark-operator/spark-operator --namespace processing --set image.tag=v1beta2-1.3.8-3.1.1

Depois de tudo instalado, conseguimos visualizar os status do nosso operador:

Status deployment Spark Operator

2.4 Preparando nossa integração contínua(CI) com GitLab

Depois de preparar nossa infraestrutura no Kuberentes, vamos preparar o nosso pipeline no GitLab. Além de ser uma ferramenta que armazena e versiona os códigos para um projeto, o GitLab oferece várias features para gerenciamento de código, integração contínua, entrega contínua (CI/CD), rastreamento de problemas, teste automatizados e muito mais. Então, o GitLab é ferramenta ideal para fazer a integração contínua do nosso projeto.

Para usar o GitLab CI/CD, precisamos adicionar o arquivo .gitlab-ci.ymla raiz do projeto. Neste arquivo, a gente especifica a lista de etapas que desejamos fazer, como testar e implantar um aplicativo. Além disso, vamos usar a pasta jobs para armazenar nossos jobs Spark. Nosso projeto, portanto, terá a seguinte estrutura de pastas:

Estrutura de Pastas

O nosso fluxo de CI serão basicamente dois estágios: Detectar jobs novos e alterados, e fazer o build e o push das imagens desses jobs. Quando olhamos no GitLab, temos o seguinte fluxo:

Pipeline CI Gitlab

Detectar jobs novos e alterados

detect_changed_jobs:
stage: detect_changed_jobs
script:
- touch changed_jobs_$CI_COMMIT_SHA_repeated.txt
- set -x
- changed_files=$(git diff-tree --no-commit-id --name-only -r $CI_COMMIT_SHA | tr ' ' '\n')
- changed_jobs=$(echo $changed_files | { grep -o "jobs\/[A-z1-9_\-]*\/" || true; })
- for job_name in $changed_jobs ; do
- basename $job_name >> changed_jobs_$CI_COMMIT_SHA_repeated.txt
- done
- cat changed_jobs_$CI_COMMIT_SHA_repeated.txt | uniq > changed_jobs_${CI_COMMIT_SHA}.txt
- echo changed_jobs_${CI_COMMIT_SHA}.txt
- cat changed_jobs_${CI_COMMIT_SHA}.txt
artifacts:
paths:
- changed_jobs_${CI_COMMIT_SHA}.txt
expire_in: 5 minutes
needs: []

Esse estágio do pipeline serve para verificar quais jobs foram inseridos ou atualizados, acima temos o trecho de código dessa etapa. Começamos criando um arquivo temporário para armazenar quais jobs foram alterados e inseridos, para descobrir isso usamos o comando git diff-tree para listar os nomes dos arquivos que foram alterados no commit atual e usando o comando grepconseguimos filtrar pelas pastas que estão dentro da nossa pastas de jobs. Para garantir que não tenhamos trabalhos repetidos, lemos os nomes dos jobs do arquivo temporário e usamos o comando uniq para remover duplicatas. O resultado é salvo em um arquivo com o nome do commit.

Build e o push das imagens dos jobs novos e alterados

build_and_push_all_changed_job_images:
stage: build_and_push_all_changed_job_images
image:
name: gcr.io/kaniko-project/executor:v1.14.0-debug
entrypoint: [""]
script:
- changed_jobs=$(cat changed_jobs_$CI_COMMIT_SHA.txt | tr '\n' ' ' )
- mkdir -p /kaniko/.docker
- set -x
- cd ${CI_PROJECT_DIR}/jobs
- echo "{\"auths\":{\"${DOCKER_HUB_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
- for job_name in $changed_jobs ; do
- echo $job_name
- cd $job_name
- >-
/kaniko/executor
--context .
--dockerfile "./Dockerfile"
--destination "${DOCKER_HUB_IMAGE}:${job_name}"
--cleanup
- cd ..
- done
artifacts:
paths:
- changed_jobs_${CI_COMMIT_SHA}.txt
expire_in: 5 minutes
needs: ["detect_changed_jobs"]

Por fim, a última etapa do nosso CI é buildar e dar push nas imagens Docker para o Docker Hub dos jobs que foram identificados na etapa anterior. Acima temos o trecho de código que consiste essa etapa. Primeiro começamos lendo o arquivo da estágio anterior. Em seguida, configuramos o ambiente para construção de imagens Docker usando o Kaniko — que é uma ferramenta para construir imagens de contêiner a partir de um Dockerfile. Então, iteramos por cada linha do arquivo de jobs alterados, para, a partir do arquivo Dockerfile, construir a imagem Docker, onde o nome da imagem é o nome da pasta do job, e , por fim, enviamos a imagem para o Docker Hub. Cada job Spark vai ter sua própria imagem.

2.3 Configurando o ArgoCD

Depois que todo o nosso pipeline CI foi construído , vamos construir agora a nossa entrega contínua (CD) usando o ArgoCD. Queremos garantir que , após algum job seja alterado ou adicionado , eles estajam sempre atualizados.

ArgoCD é uma ferramenta de código aberto que é usada para implantar e gerenciar aplicativos em um ambiente Kubernetes. Ela é classificada como uma ferramenta de “GitOps”, o que significa que o estado desejado da infraestrutura e dos aplicativos é definido em repositórios Git, e o ArgoCD se encarrega de garantir que o ambiente real no Kubernetes reflita fielmente esse estado desejado. O ArgoCD permite que o usuário defina o estado desejado de seus aplicativos no Kubernetes usando arquivos YAML e mantenha essas definições em repositórios Git. Ele usa essas definições para garantir que o ambiente esteja sempre alinhado com o estado desejado.

Então , para adicionar o nosso repositório é bem simples. Clica em “Manage your repositories, projects, settings” e depois em “Repositories”, conforme a imagem a seguir:

Configurando o sincronismo de repositório

Após isso, podemos se conectar ao nosso repositório usando SSH, HTTPS ou GitHub APP. Como essa configuração feita, o nosso repositório aparecerá:

Configurando o sincronismo de repositório

Com a nossa conexão estabelecida, conseguimos visualizar que o sincronismo dos nossos jobs do nosso repositório será feita automaticamente.

Sincronização com o repositório

Portanto, quando houver alguma alteração nos jobs, o ArgoCD irá fazer o deploy no kubernetes automaticamente.

3. Conclusão

Nesse arigo construimos um pipeline CI/CD para Jobs Spark usando Kubernetes, ArgoCD e GitLab. Ao longo deste percurso, exploramos os componentes fundamentais de um pipeline de engenharia de dados moderno e eficiente, desde a configuração de um cluster Kubernetes até a automação de implantação e atualização de aplicativos. Não entramos muito no mérito de como configuramos os nossos jobs, focamos mais no pipeline. Todos os comandos e códigos estão no nosso GitLab

Espero que tenha sido útil para você esse artigo! Caso desejem enviar sugestões , queiram outros temas de post, feedbacks ou apenas trocar um papo legal podem falar comigo no meu Linkedin :) . Se gostou , curte e compartilha para chegar em mais pessoas :).

--

--

Ricardo Junior
Data Hackers

Data Engineer - Sênior | Ml Engineer | Python | AWS | Azure | LinkedIn: shorturl.at/GPY35