Escalando Scala: Como Dockerizar Usando Kubernetes

Publicados: 2022-03-11

O Kubernetes é o novo garoto no bloco, prometendo ajudar a implantar aplicativos na nuvem e escalá-los mais rapidamente. Hoje, ao desenvolver para uma arquitetura de microsserviços, é bastante padrão escolher Scala para criar servidores de API.

Os microsserviços estão substituindo os servidores de back-end monolíticos clássicos por vários serviços independentes que se comunicam entre si e têm seus próprios processos e recursos.

Se houver um aplicativo Scala em seus planos e você quiser escalá -lo em uma nuvem, você está no lugar certo. Neste artigo, mostrarei passo a passo como usar um aplicativo Scala genérico e implementar o Kubernetes com o Docker para iniciar várias instâncias do aplicativo. O resultado final será um único aplicativo implantado como várias instâncias e balanceado por carga pelo Kubernetes.

Tudo isso será implementado simplesmente importando o kit de origem do Kubernetes em seu aplicativo Scala. Observe que o kit esconde muitos detalhes complicados relacionados à instalação e configuração, mas é pequeno o suficiente para ser legível e fácil de entender se você quiser analisar o que ele faz. Para simplificar, implantaremos tudo em sua máquina local. No entanto, a mesma configuração é adequada para uma implantação de nuvem do Kubernetes no mundo real.

Dimensione seu aplicativo Scala com Kubernetes

Seja inteligente e durma bem, dimensione seu Docker com o Kubernetes.
Tweet

O que é Kubernetes?

Antes de entrar nos detalhes sangrentos da implementação, vamos discutir o que é o Kubernetes e por que é importante.

Você já deve ter ouvido falar do Docker. De certa forma, é uma máquina virtual leve.

O Docker oferece a vantagem de implantar cada servidor em um ambiente isolado, muito semelhante a uma máquina virtual autônoma, sem a complexidade de gerenciar uma máquina virtual completa.

Por esses motivos, já é uma das ferramentas mais utilizadas para implantação de aplicativos em nuvens. Uma imagem do Docker é muito fácil e rápida de construir e duplicável, muito mais fácil do que uma máquina virtual tradicional como VMWare, VirtualBox ou XEN.

O Kubernetes complementa o Docker, oferecendo um ambiente completo para gerenciar aplicativos dockerizados. Ao usar o Kubernetes, você pode implantar, configurar, orquestrar, gerenciar e monitorar facilmente centenas ou até milhares de aplicativos Docker.

O Kubernetes é uma ferramenta de código aberto desenvolvida pelo Google e adotada por muitos outros fornecedores. O Kubernetes está disponível nativamente na plataforma de nuvem do Google, mas outros fornecedores também o adotaram para seus serviços de nuvem OpenShift. Ele pode ser encontrado na Amazon AWS, Microsoft Azure, RedHat OpenShift e ainda mais tecnologias de nuvem. Podemos dizer que está bem posicionado para se tornar um padrão para implantação de aplicativos em nuvem.

Pré-requisitos

Agora que abordamos o básico, vamos verificar se você tem todos os softwares obrigatórios instalados. Primeiro de tudo, você precisa do Docker. Se você estiver usando Windows ou Mac, precisará do Docker Toolbox. Se você estiver usando Linux, precisará instalar o pacote específico fornecido pela sua distribuição ou simplesmente seguir as instruções oficiais.

Vamos codificar em Scala, que é uma linguagem JVM. Você precisa, é claro, do Java Development Kit e da ferramenta scala SBT instaladas e disponíveis no caminho global. Se você já é um programador Scala, é provável que já tenha essas ferramentas instaladas.

Se você estiver usando Windows ou Mac, o Docker criará, por padrão, uma máquina virtual chamada default com apenas 1 GB de memória, que pode ser muito pequena para executar o Kubernetes. Na minha experiência, tive problemas com as configurações padrão. Eu recomendo que você abra a GUI do VirtualBox, selecione o default da sua máquina virtual e altere a memória para pelo menos 2048 MB.

Configurações de memória do VirtualBox

O aplicativo para clusterizar

As instruções neste tutorial podem ser aplicadas a qualquer aplicativo ou projeto Scala. Para que este artigo tenha um pouco de “carne” para trabalhar, escolhi um exemplo usado com muita frequência para demonstrar um microsserviço REST simples em Scala, chamado Akka HTTP. Eu recomendo que você tente aplicar o kit de origem ao exemplo sugerido antes de tentar usá-lo em seu aplicativo. Testei o kit com o aplicativo de demonstração, mas não posso garantir que não haverá conflitos com seu código.

Então, primeiro, começamos clonando o aplicativo de demonstração:

 git clone https://github.com/theiterators/akka-http-microservice

Em seguida, teste se tudo funciona corretamente:

 cd akka-http-microservice sbt run

Em seguida, acesse http://localhost:9000/ip/8.8.8.8 , e você deverá ver algo como na imagem a seguir:

O microsserviço HTTP Akka está em execução

Adicionando o Kit de Origem

Agora, podemos adicionar o kit de origem com alguma mágica do Git:

 git remote add ScalaGoodies https://github.com/sciabarra/ScalaGoodies git fetch --all git merge ScalaGoodies/kubernetes

Com isso, você tem a demonstração incluindo o kit de origem e está pronto para experimentar. Ou você pode até copiar e colar o código de lá em seu aplicativo.

Depois de mesclar ou copiar os arquivos em seus projetos, você estará pronto para começar.

Iniciando o Kubernetes

Depois de baixar o kit, precisamos baixar o binário kubectl necessário, executando:

 bin/install.sh

Este instalador é inteligente o suficiente (espero) para baixar o binário kubectl correto para OSX, Linux ou Windows, dependendo do seu sistema. Observe que o instalador trabalhou nos sistemas que possuo. Por favor, reporte quaisquer problemas, para que eu possa consertar o kit.

Depois de instalar o binário kubectl , você pode iniciar todo o Kubernetes em seu Docker local. Apenas corra:

 bin/start-local-kube.sh

Na primeira vez que for executado, este comando baixará as imagens de toda a pilha do Kubernetes e um registro local necessário para armazenar suas imagens. Pode levar algum tempo, então, por favor, seja paciente. Observe também que ele precisa de acesso direto à internet. Se você estiver atrás de um proxy, será um problema, pois o kit não suporta proxies. Para resolvê-lo, você precisa configurar as ferramentas como Docker, curl e assim por diante para usar o proxy. É complicado o suficiente para que eu recomende obter um acesso irrestrito temporário.

Supondo que você conseguiu baixar tudo com sucesso, para verificar se o Kubernetes está funcionando bem, você pode digitar o seguinte comando:

 bin/kubectl get nodes

A resposta esperada é:

 NAME STATUS AGE 127.0.0.1 Ready 2m

Observe que a idade pode variar, é claro. Além disso, como iniciar o Kubernetes pode levar algum tempo, talvez seja necessário invocar o comando algumas vezes antes de ver a resposta. Se você não obtiver erros aqui, parabéns, você tem o Kubernetes instalado e funcionando em sua máquina local.

Dockerizando seu aplicativo Scala

Agora que você tem o Kubernetes em execução, pode implantar seu aplicativo nele. Antigamente, antes do Docker, você precisava implantar um servidor inteiro para executar seu aplicativo. Com o Kubernetes, tudo o que você precisa fazer para implantar seu aplicativo é:

  • Crie uma imagem do Docker.
  • Empurre-o em um registro de onde ele pode ser iniciado.
  • Inicie a instância com o Kubernetes, que irá retirar a imagem do registro.

Felizmente, é muito menos complicado do que parece, especialmente se você estiver usando a ferramenta de construção SBT como muitos fazem.

No kit, incluí dois arquivos contendo todas as definições necessárias para criar uma imagem capaz de rodar aplicações Scala, ou pelo menos o necessário para rodar a demonstração HTTP Akka. Não posso garantir que funcionará com qualquer outro aplicativo Scala, mas é um bom ponto de partida e deve funcionar para muitas configurações diferentes. Os arquivos a serem procurados para construir a imagem do Docker são:

 docker.sbt project/docker.sbt

Vamos dar uma olhada no que há neles. O arquivo project/docker.sbt contém o comando para importar o plugin sbt-docker :

 addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.4.0")

Este plugin gerencia a construção da imagem do Docker com o SBT para você. A definição do Docker está no arquivo docker.sbt e se parece com isso:

 imageNames in docker := Seq(ImageName("localhost:5000/akkahttp:latest")) dockerfile in docker := { val jarFile: File = sbt.Keys.`package`.in(Compile, packageBin).value val classpath = (managedClasspath in Compile).value val mainclass = mainClass.in(Compile, packageBin).value.getOrElse(sys.error("Expected exactly one main class")) val jarTarget = s"/app/${jarFile.getName}" val classpathString = classpath.files.map("/app/" + _.getName) .mkString(":") + ":" + jarTarget new Dockerfile { from("anapsix/alpine-java:8") add(classpath.files, "/app/") add(jarFile, jarTarget) entryPoint("java", "-cp", classpathString, mainclass) } }

Para entender completamente o significado desse arquivo, você precisa conhecer o Docker bem o suficiente para entender esse arquivo de definição. No entanto, não vamos entrar nos detalhes do arquivo de definição do Docker, porque você não precisa entendê-lo completamente para construir a imagem.

A beleza de usar o SBT para construir a imagem do Docker é que
o SBT se encarregará de recolher todos os arquivos para você.

Observe que o caminho de classe é gerado automaticamente pelo seguinte comando:

 val classpath = (managedClasspath in Compile).value

Em geral, é bastante complicado reunir todos os arquivos JAR para executar um aplicativo. Usando SBT, o arquivo Docker será gerado com add(classpath.files, "/app/") . Dessa forma, o SBT coleta todos os arquivos JAR para você e constrói um Dockerfile para executar sua aplicação.

Os outros comandos reúnem as peças que faltam para criar uma imagem do Docker. A imagem será construída usando um APT de imagem existente para executar programas Java ( anapsix/alpine-java:8 , disponível na internet no Docker Hub). Outras instruções estão adicionando os outros arquivos para executar seu aplicativo. Finalmente, especificando um ponto de entrada, podemos executá-lo. Observe também que o nome começa com localhost:5000 de propósito, porque localhost:5000 é onde instalei o registro no script start-kube-local.sh .

Construindo a imagem do Docker com SBT

Para construir a imagem do Docker, você pode ignorar todos os detalhes do Dockerfile. Você só precisa digitar:

 sbt dockerBuildAndPush

O plug-in sbt-docker criará uma imagem do Docker para você, baixando da Internet todas as peças necessárias e, em seguida, enviará para um registro do Docker que foi iniciado antes, junto com o aplicativo Kubernetes em localhost . Então, tudo que você precisa é esperar um pouco mais para ter sua imagem cozida e pronta.

Observe que, se você tiver problemas, a melhor coisa a fazer é redefinir tudo para um estado conhecido executando os seguintes comandos:

 bin/stop-kube-local.sh bin/start-kube-local.sh

Esses comandos devem parar todos os contêineres e reiniciá-los corretamente para deixar seu registro pronto para receber a imagem criada e enviada por sbt .

Iniciando o serviço no Kubernetes

Agora que o aplicativo está empacotado em um contêiner e enviado para um registro, estamos prontos para usá-lo. O Kubernetes usa a linha de comando e os arquivos de configuração para gerenciar o cluster. Como as linhas de comando podem se tornar muito longas e também podem replicar as etapas, estou usando os arquivos de configuração aqui. Todas as amostras do kit de origem estão na pasta kube .

Nosso próximo passo é lançar uma única instância da imagem. Uma imagem em execução é chamada, na linguagem Kubernetes, de pod . Então, vamos criar um pod invocando o seguinte comando:

 bin/kubectl create -f kube/akkahttp-pod.yml

Agora você pode inspecionar a situação com o comando:

 bin/kubectl get pods

Você deveria ver:

 NAME READY STATUS RESTARTS AGE akkahttp 1/1 Running 0 33s k8s-etcd-127.0.0.1 1/1 Running 0 7d k8s-master-127.0.0.1 4/4 Running 0 7d k8s-proxy-127.0.0.1 1/1 Running 0 7d

O status na verdade pode ser diferente, por exemplo, “ContainerCreating”, pode levar alguns segundos antes de se tornar “Running”. Além disso, você pode obter outro status como “Erro” se, por exemplo, esquecer de criar a imagem antes.

Você também pode verificar se seu pod está em execução com o comando:

 bin/kubectl logs akkahttp

Você deve ver uma saída terminando com algo assim:

 [DEBUG] [05/30/2016 12:19:53.133] [default-akka.actor.default-dispatcher-5] [akka://default/system/IO-TCP/selectors/$a/0] Successfully bound to /0:0:0:0:0:0:0:0:9000

Agora você tem o serviço funcionando dentro do contêiner. No entanto, o serviço ainda não está disponível. Esse comportamento faz parte do design do Kubernetes. Seu pod está em execução, mas você precisa expô-lo explicitamente. Caso contrário, o serviço deve ser interno.

Criando um serviço

Criar um serviço e verificar o resultado é uma questão de executar:

 bin/kubectl create -f kube/akkahttp-service.yaml bin/kubectl get svc

Você deve ver algo assim:

 NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE akkahttp-service 10.0.0.54 9000/TCP 44s kubernetes 10.0.0.1 <none> 443/TCP 3m

Observe que a porta pode ser diferente. O Kubernetes alocou uma porta para o serviço e o iniciou. Se estiver usando Linux, você pode abrir diretamente o navegador e digitar http://10.0.0.54:9000/ip/8.8.8.8 para ver o resultado. Se você estiver usando Windows ou Mac com Docker Toolbox, o IP é local para a máquina virtual que está executando o Docker e, infelizmente, ainda está inacessível.

Quero ressaltar aqui que isso não é um problema do Kubernetes, mas sim uma limitação do Docker Toolbox, que por sua vez depende das restrições impostas por máquinas virtuais como o VirtualBox, que agem como um computador dentro de outro computador. Para superar essa limitação, precisamos criar um túnel. Para facilitar as coisas, incluí outro script que abre um túnel em uma porta arbitrária para acessar qualquer serviço que implantamos. Você pode digitar o seguinte comando:

 bin/forward-kube-local.sh akkahttp-service 9000

Observe que o túnel não será executado em segundo plano, você deve manter a janela do terminal aberta enquanto precisar e fechar quando não precisar mais do túnel. Enquanto o túnel está rodando, você pode abrir: http://localhost:9000/ip/8.8.8.8 e finalmente ver a aplicação rodando no Kubernetes.

Toque final: escala

Até agora, “simplesmente” colocamos nossa aplicação no Kubernetes. Embora seja uma conquista empolgante, não agrega muito valor à nossa implantação. Somos poupados do esforço de carregar e instalar em um servidor e configurar um servidor proxy para ele.

Onde o Kubernetes brilha é no dimensionamento. Você pode implantar duas, dez ou cem instâncias do nosso aplicativo alterando apenas o número de réplicas no arquivo de configuração. Então, vamos fazê-lo.

Vamos parar o pod único e iniciar uma implantação. Então vamos executar os seguintes comandos:

 bin/kubectl delete -f kube/akkahttp-pod.yml bin/kubectl create -f kube/akkahttp-deploy.yaml

Em seguida, verifique o status. Novamente, você pode tentar algumas vezes porque a implantação pode levar algum tempo para ser executada:

 NAME READY STATUS RESTARTS AGE akkahttp-deployment-4229989632-mjp6u 1/1 Running 0 16s akkahttp-deployment-4229989632-s822x 1/1 Running 0 16s k8s-etcd-127.0.0.1 1/1 Running 0 6d k8s-master-127.0.0.1 4/4 Running 0 6d k8s-proxy-127.0.0.1 1/1 Running 0 6d

Agora temos dois pods, não um. Isso porque no arquivo de configuração que forneci, existe o valor replica: 2 , com dois nomes diferentes gerados pelo sistema. Não vou entrar em detalhes dos arquivos de configuração, porque o escopo do artigo é simplesmente uma introdução para os programadores Scala começarem a usar o Kubernetes.

De qualquer forma, agora existem dois pods ativos. O interessante é que o serviço é o mesmo de antes. Configuramos o serviço para balancear a carga entre todos os pods rotulados como akkahttp . Isso significa que não precisamos reimplantar o serviço, mas podemos substituir a instância única por uma replicada.

Podemos verificar isso iniciando o proxy novamente (se você estiver no Windows e o tiver fechado):

 bin/forward-kube-local.sh akkahttp-service 9000

Então, podemos tentar abrir duas janelas de terminal e ver os logs de cada pod. Por exemplo, no primeiro tipo:

 bin/kubectl logs -f akkahttp-deployment-4229989632-mjp6u

E no segundo tipo:

 bin/kubectl logs -f akkahttp-deployment-4229989632-s822x

Claro, edite a linha de comando de acordo com os valores que você tem em seu sistema.

Agora, tente acessar o serviço com dois navegadores diferentes. Você deve esperar ver as solicitações a serem divididas entre os vários servidores disponíveis, como na imagem a seguir:

Kubernets em ação

Conclusão

Hoje mal arranhamos a superfície. O Kubernetes oferece muito mais possibilidades, incluindo dimensionamento e reinicialização automatizados, implantações incrementais e volumes. Além disso, o aplicativo que usamos como exemplo é muito simples, sem estado, com as várias instâncias não precisando se conhecer. No mundo real, os aplicativos distribuídos precisam se conhecer e precisam alterar as configurações de acordo com a disponibilidade de outros servidores. De fato, o Kubernetes oferece um keystore distribuído ( etcd ) para permitir que diferentes aplicativos se comuniquem quando novas instâncias são implantadas. No entanto, este exemplo é propositalmente pequeno o suficiente e simplificado para ajudá-lo a continuar, concentrando-se nas funcionalidades principais. Se você seguir o tutorial, poderá obter um ambiente de trabalho para seu aplicativo Scala em sua máquina sem se confundir com um grande número de detalhes e se perder na complexidade.

Relacionado:
  • Desenvolvendo para a Nuvem na Nuvem: Desenvolvimento de BigData com Docker na AWS
  • K8s/Kubernetes: AWS vs. GCP vs. Azure
  • Uma comparação de malha de serviço do Kubernetes