O que é Virtualização?

Publicado em 16 Dec 2021 por André Felipe

Atualmente, com o surgimento da computação em nuvem, as aplicações tiveram que se adequar as novas necessidades. A principal delas é a disponibilidade, que com o uso crescente da tecnologia pelas organizações, se tornou de vital importância.

Alguns fatores que afetam a disponibilidade de uma aplicação são o alto número de usuários, assim como tecnologias, como bancos de dados, linguagens de programação, frameworks e a arquitetura de software escolhida para desenvolvê-la. Outro fator vital é o hardware, que não tem evoluído tão rapidamente quanto as aplicações, exigindo soluções mais complexas a nível de software.

Uma delas é a virtualização, criada visando reduzir os custos com hardware, permitindo um melhor gerenciamento de recursos de hardware, como memória RAM, processamento e armazenamento. Existem cinco níveis de virtualização:

Conjunto de instruções

Nesse tipo, as instruções são executadas via software ao invés de hardware, em tempo real. Usado para emulação e tradução de instruções de hardware, possui flexibilidade a nível de hardware muito alta e alto grau de isolamento, porém com desempenho muito baixo e alta complexidade de implementação. Utilizada para depuração. Alguns exemplos são o QEMU, Bochs e Crusoe.

Hardware

Nesse tipo, componentes como processador, memória e armazenamento são virtualizados, através de estruturas conhecidas como máquinas virtuais, permitindo um isolamento total em relação ao hardware e melhor compatibilidade. Possui desempenho e nível de isolamento muito altos, porém com complexidade de implementação muito alta. Amplamente utilizado para depuração, testes e migração de aplicações, com foco em compatibilidade. Existem dois tipos:

  • Standalone de nível 1: quando não há o auxílio de um sistema operacional hospedeiro.
  • Hosted ou nível 2: quando há o auxílio de um sistema operacional hospedeiro.

Alguns exemplos são o VMware Workstation (Hosted), o VirtualBox (Hosted), o Hyper-V (Hosted) e o VMware ESXi (Standalone).

Sistema Operacional

Nesse tipo, componentes como processador, memória e armazenamento são compartilhados entre os contêiners, estruturas que armazenam uma estrutura mais simples de execução do que uma máquina virtual, e o próprio sistema operacional hospedeiro.

Assim como o anterior, também é bastante utilizado para depuração, testes e migração de aplicações, porém o seu foco está no melhor uso de recursos de hardware ao invés da compatibilidade. Possui desempenho muito alto e uma maior facilidade de implementação que o anterior, porém com grau de isolamento menor que os dois anteriores. Um grande exemplo desse tipo é o Docker.

Biblioteca

São voltadas para a execução de aplicações de diferentes sistemas operacionais de forma nativa, ou seja, sem estruturas como contêiners e máquinas virtuais. Seu funcionamento consiste em reimplementar a API do sistema operacional alvo, usando recursos do sistema operacional em execução. Possui bom desempenho, porém inferior aos dois anteriores, além de um menor isolamento e menor facilidade de desenvolvimento. Alguns exemplos são o WINE, o WSL e os subsistemas Win32, POSIX e OS/2.

Linguagem de Programação

São voltadas para a execução de aplicações desenvolvidas em linguagens híbridas como Java e C#, onde a execução é feita através de uma máquina virtual desenvolvida para um sistema operacional específico. Permite um maior grau de isolamento mas com pior desempenho que o anterior.

O que é Docker e Docker Compose?

O Docker é uma ferramenta de virtualização a nível de sistema operacional que permite a execução de um conjunto de aplicações de forma isolada. Esse isolamento é feito através de estruturas conhecidas como contêiners. Como cada contêiner é, por natureza, independente dos demais, problemas que afetem as aplicações contidas nele não afetam os demais contêiners daquele servidor, exceto quando onde existe uma dependência entre aplicações de contêiners diferentes, como entre um servidor web e um serviço de banco de dados.

Além disso, é possível criar instâncias do mesmo contêiner de forma automatizada, conforme a demanda de usuários, também chamado de escalamento horizontal, frequentemente aplicado em aplicações em nuvem, além de replicar o mesmo contêiner em vários tipos de ambiente sem a necessidade de configurar as aplicações contidas nele manualmente, como em instalações feitas diretamente em um sistema operacional.

A criação de um contêiner Docker se dá através de uma imagem de contêiner, criada a partir de um arquivo chamado Dockerfile, responsável por definir as aplicações e as configurações de cada uma delas, bem como outras tarefas adicionais. Após isso, o arquivo Dockerfile deve ser copiado para uma máquina com o Docker instalado e executado. Ao final do processo, a imagem estará instalada na instância do Docker e pronta para execução. Esse processo está representado no diagrama abaixo:

image info

Toda imagem de contêiner é desenvolvida utilizando uma imagem base, armazenada em algum dos repositórios públicos ou privados, também conhecidos como serviços de registro de contêiners. Alguns exemplos são:

  • Docker Hub
  • Amazon ECR
  • Azure CR
  • Google CR
  • JFrog Artifactory
  • Red Hat Quay

Durante a execução do arquivo Dockerfile, é feito o download dessa imagem base de algum desses repositórios, e feita as modificações necessárias, adição de novos arquivos, alterações em arquivos de configuração e instalação, atualização ou remoção de pacotes. Alguns comandos usados no arquivo Dockerfile são:

  • FROM, para selecionar uma imagem base de um repositório específico.
  • RUN, para executar um comando shell. É interessante utilizar um arquivo shell, caso sejam muitos comandos.
  • CMD, para executar um comando shell, porém somente na inicialização do contêiner. É interessante utilizar um arquivo shell, caso sejam muitos comandos.
  • ADD, para copiar um arquivo ou pasta do máquina para o contêiner.
  • ENV, para criar uma nova variável de ambiente.
  • EXPOSE, para abrir uma porta de rede para alguma aplicação.
  • WORKDIR, para mudar de diretório.

Um exemplo de arquivo Dockerfile pode ser visualizado abaixo:

FROM ubuntu                         # Utilizando a imagem base do ubuntu, disponível no docker hub
                                    # Caso eu queira uma imagem de outro repositório, basta utilizar a sintaxe abaixo:
                                    # nome_dominio_repositorio:porta/nome_imagem:versao

WORKDIR /                           # Muda para a raiz do volume
RUN apt-get update                  # Atualiza a lista de pacotes
RUN apt-get install -y python3      # Instala o python3

WORKDIR /usr           # Muda para o diretório /usr
RUN mkdir /scripts     # Cria um diretório chamado scripts


WORKDIR /usr/scripts      # Muda para o diretório /usr/scripts

RUN touch main.py                             # Cria um arquivo em branco, chamado main.py
RUN echo "print('Ola Mundo!')" > main.py      # Escreve print('Ola Mundo') no arquivo main.py	
				
RUN chmod +x main.py      # Adiciona permissão de execução
RUN python3 main.py       # Executa o arquivo main.py. Deve aparecer 'Ola Mundo!' no console.

Para criar uma imagem de contêiner a partir do arquivo Dockerfile, no terminal da máquina com o Docker instalado, utiliza-se o comando docker build, juntamente com o diretório do arquivo Dockerfile e o nome da imagem. Caso o comando não seja reconhecido, é necessário adicionar o Docker ao path do sistema operacional.

docker build "diretório do arquivo dockerfile" -t "nome da imagem"

Para visualizar todos os contêiners instalados, bem como suas configurações e status de execução, se utiliza o comando:

docker ps -a

E para visualizar todas as imagens instaladas e propriedades como o tamanho, se utiliza o comando:

docker images

Para iniciar um contêiner a partir de uma imagem de contêiner, basta utilizar o comando abaixo, passando o nome da imagem, as portas que serão acessadas no host e as que foram abertas usando o comando EXPOSE, no arquivo Dockerfile.

docker run -dp porta_host:porta_container "nome da imagem"

Para terminar a execução de um contêiner, se utiliza o comando abaixo:

docker stop "id container"

Já para excluir um contêiner, se utiliza o comando abaixo. O nome do contêiner pode ser visualizado usando o comando docker ps -a e o contêiner não deve estar em execução.

docker rm "id container"

Para excluir uma imagem de contêiner, se utiliza o comando abaixo. O nome da imagem pode ser visualizado usando o comando docker images e para excluí-la, não pode haver nenhum contêiner, executando ou não, utilizando ela.

docker rmi "id container"

Com o Docker Compose, é possível centralizar e organizar as configurações de um conjunto de imagens de contêiners em um arquivo docker-compose.yml. Nesse arquivo, são importados os arquivos Dockerfile e as suas configurações de inicialização como nome da imagem e portas. Após o seu desenvolvimento, através do terminal, deve-se navegar até o diretório do arquivo docker-compose.yml e executar o comando docker-compose up. Durante a execução, será feita a execução de cada um dos arquivos Dockerfile e as configurações deles na instalação do Docker.

Um modelo básico de arquivo docker-compose.yml pode ser visualizado abaixo:

version: '3'

services:
  python:                        # Um rótulo para a imagem apenas para agrupar as informações
	container_name: python   # O nome da imagem
  
	image: ubuntu            # Importando uma imagem pronta para uso, existente em um repositório público (nesse caso, Docker Hub). 
	                         # Caso precise de alterações, desenvolva um arquivo Dockerfile e o importe usando a sintaxe abaixo, usando o comando build.
  
    build:                       # Importando um arquivo Dockerfile, ao invés de uma imagem pronta. A imagem será gerada utilizando esse arquivo.
      context: .                 # Define o diretório do arquivo Dockerfile
      dockerfile: Dockerfile     # Define o nome do arquivo em si, caso esteja com outro nome

    restart: always	   # Indica que os contêiners serão reiniciados ao executar o script, caso já existam na instalação.
	
    ports:                 # Indica as portas que serão abertas no Docker e as que estão expostas na imagem/arquivo Dockerfile, respectivamente. Sempre em pares
      - "8000:8000"        # Uma par de portas por linha
      - "8001:8001"	
      - "8002:8002"

Para mais detalhes sobre o Docker e do Docker Compose, além de outros comandos, segue alguns links úteis:

Dockerlabs - The Ultimate Docker Cheat Sheet

Docker Labs on GitHub

Docker Reference