Terraform | Versionando infraestrutura

Terraform | Versionando infraestrutura

Versionando a sua infraestrutura

Aqui iremos falar sobre como versionar sua infraestrutura utilizando Git no GitLab.

Primeira coisa que devemos fazer é destruir toda sua infraestrutura (caso ainda não tenha feito) com o comando:

$ terraform destroy -auto-approve

Quando criamos software, temos várias pessoas trabalhando neste desenvolvimento e para que todos consigam enviar seu código para o repositório, onde fica a versão estável do projeto, precisamos realizar o processo de Continuous Integration – CI (ou no Português, Integração Contínua) toda vez que enviamos um novo código ao nosso repositório.

Para todo o desenvolvimento de código existe um fluxo, porém este fluxo pode variar dependendo da empresa/pessoa/projeto, mas é basicamente temos:

  • Desenvolvimento de código local.
  • Build do código enviado para o repositório.
  • Testes são realizados.
  • Em caso de falha, o desenvolvedor ficará sabendo do problema para resolver os problemas.
  • Em caso de sucesso, o código é mesclado com o branch principal para todos terem acesso ao código mais recente.
  • Não iremos abordar instalação de Git em sua máquina ou criar uma conta no GitLab, ambos os processos são simples e certamente você saberá fazer o processo.

Para os exemplos foi criado um repositório chamado gcp-instances.

Ao criar o projeto, aparecerá uma nova tela com alguns passos que devemos configurar para Git. Todos os passos também serão realizados via linha de comando.

Neste podemos configurar de duas maneiras distintas, clonando o repositório vazio ou criando estrutura diretamente na sua máquina, mas iremos prosseguir apenas clonando o repositório vazio.

Faça o clone do seu repositório.

$ git clone git@gitlab.com:rd-public/blog-tf-modules/4linux/gcp-instances.git

Você receberá uma mensagem parecida com esta abaixo:

Cloning into 'gcp-instances'...
warning: You appear to have cloned an empty repository.

Para enviarmos nosso código fonte para este projeto precisaremos configurar nossa chave SSH.

Criando chaves SSH Chaves públicas estabelecem de forma confiável a comunicação de sua máquina com o seu repositório, o GitLab é o método mais utilizado para enviar/receber arquivos de um repositório.

Para quem já utiliza chaves públicas, é possível utilizar a mesma, ou criar outra.

O comando para criar está abaixo. Quando solicitado para inserir senhas, apenas pressione ENTER :

$ ssh-keygen -t rsa -b 4096

Para aqueles que queiram criar outra chave:

$ ssh-keygen -t rsa -b 4096 -f ${HOME}/.ssh/ebook

Para ambos os processos, a saída será similar.

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/rafael/.ssh/ebook.
Your public key has been saved in /home/rafael/.ssh/ebook.pub.
The key fingerprint is:
SHA256:mtABqryIABsxIw4YmnKBL55mqC2rREHIhfFRpuotTqI rafael@nazgul
The key\'s randomart image is:
+---[RSA 4096]----+
|@==ooo           |
|XB.o+.           |
|*+oo  .          |
|==+  . .         |
|*=. . . S        |
|B=o  . o         |
|BB .  o          |
|O o              |
|E+               |
+----[SHA256]-----+

Adicionando as chaves na sua conta do GitLab Precisamos copiar o conteúdo desta chave gerada para comunicar com o GitLab. Este processo além de utilizar criptografia, nos permite realizar commits sem que seja necessário a cada momento informar usuário e senha ao sistema.

Copie o conteúdo da chave pública:

$ cat ${HOME}/.ssh/ebook.pub

Vá até o GitLab e clique em https://gitlab.com/profile e no menu esquerdo selecione SSH Keys colando o conteúdo da chave no formulário.

Para aqueles que estão utilizando uma segunda chave: deverá ser criado um arquivo dentro do seu ${HOME} para suportar a outra chave.

Crie um arquivo chamado de config em ${HOME}/.ssh:

$ vim ${HOME}/.ssh/config

Adicione o seguinte conteúdo abaixo:


  Host gitlab.com
  HostName gitlab.com
  User git
  IdentityFile ~/.ssh/ebook
  IdentitiesOnly yes
* Entre no diretório do módulo que foi criado no post anterior.
* Faça um teste criando um arquivo chamado README.md.
$ echo "Módulo para gerenciamento de instâncias para Google Cloud Platform" > README.md

Verifique o arquivo com o comando:

$ git status

Você deverá receber um resultado com o seguinte resultado:

On branch master

No commits yet

Untracked files:
  (use "git add ..." to include in what will be committed)

    README.md
    main.tf
    variables.tf

nothing added to commit but untracked files present (use "git add" to track)

Adicione o arquivo com:

$ git add README.md

Faça o commit do arquivo:

$ git commit -m 'Envio do arquivo README.md para teste de autenticação'

Talvez aparecerá a seguinte mensagem no seu terminal. Esta mensagem irá aparecer caso você não tenha em modo global dizendo quem você é:

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account\'s default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got 'rafael@nazgul.(none)')

Preste atenção no parâmetro --global do Git, aqui é configurado para todo o seu sistema utilizar sempre os mesmos valores, porém não é aconselhado caso tenha mais de uma credencial. Este caso de ter mais de uma credencial acontece quando você faz commit com mais de uma conta de email, como por exemplo projetos pessoais e projetos da empresa no mesmo computador, fique atento!

$ git config user.name "Nome"
$ git config user.email "email@dominio"

Uma vez configurado informado os valores para o seu nome e email repita o comando de commit.

$ git commit -m 'Envio do arquivo README.md para teste de autenticação'

Com o seguinte resultado:

On branch master
Your branch is based on 'origin/master', but the upstream is gone.
  (use "git branch --unset-upstream" to fixup)

nothing to commit, working tree clean

Faça o envio do arquivo:

$ git push origin master

Com o seguinte resultado:

Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 296 bytes | 296.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To gitlab.com:rd-public/blog-tf-modules/4linux/gcp-instances.git
 * [new branch]      master -> master

Neste último comando, origin é o nome padrão do git para uma conexão e master é o nome do branch padrão de qualquer repositório git, porém tudo isto é configurável no seu repositório, caso seja necessário.

Verifique no GitLab o envio de seu código.

Envie agora o arquivo main.tf e variables.tf

$ git add main.tf variables.tf

Faça o commit:

$ git commit -m 'Envio de projeto'

Faça o envio para o repositório:

$ git push origin master

Com os comandos acima você irá enviar todos os arquivos para o repositório e assim ele poderá ser consumido por todos.

Agora é necessário alterar o código do capítulo anterior para consumir o novo módulo enviado.

Altere o arquivo instances.tf com o seguinte conteúdo:

  module "instances" {
  source = "git@gitlab.com:rd-public/blog-tf-modules/4linux/gcp-instances.git"

  amount = 2
  name   = "linux-vm-1"
}

module "group-web" {
  source = "git@gitlab.com:rd-public/blog-tf-modules/4linux/gcp-instances.git"

  amount = 3
  name   = "linux-web"
  image  = "centos-cloud/centos-8"
}

module "group-gitlab" {
  source = "git@gitlab.com:rd-public/blog-tf-modules/4linux/gcp-instances.git"

  amount = 2
  name   = "linux-gitlab"
  image  = "centos-cloud/centos-7"
}

Reinicialize o Terraform:

$ terraform init
Initializing modules...
Downloading git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git for group-gitlab...
- group-gitlab in .terraform/modules/group-gitlab
Downloading git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git for group-web...
- group-web in .terraform/modules/group-web
Downloading git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git for instances...
- instances in .terraform/modules/instances

Initializing the backend...
......
......
......
......

Faça o plano de execução:

$ terraform plan

Nesta saída, vamos deixar apenas os recursos que serão criados omitindo todo o restante:

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

-------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

# module.group-gitlab.google_compute_instance.this[0] will be created
......
......
# module.group-gitlab.google_compute_instance.this[1] will be created
......
......
# module.group-web.google_compute_instance.this[0] will be created
......
......
# module.group-web.google_compute_instance.this[1] will be created
......
......
# module.group-web.google_compute_instance.this[2] will be created
......
......
# module.instances.google_compute_instance.this[0] will be created
......
......
......
......
Plan: 7 to add, 0 to change, 0 to destroy.
......
......

Aplique a infraestrutura:

$ terraform apply -auto-approve

Com o seguinte resultado:

module.group-gitlab.google_compute_instance.this[0]: Creating...
module.group-web.google_compute_instance.this[3]: Creating...
module.instances.google_compute_instance.this[0]: Creating...
module.group-web.google_compute_instance.this[2]: Creating...
module.group-gitlab.google_compute_instance.this[1]: Creating...
module.group-web.google_compute_instance.this[1]: Creating...
module.instances.google_compute_instance.this[1]: Creating...
module.group-web.google_compute_instance.this[0]: Creating...
module.group-gitlab.google_compute_instance.this[0]: Still creating... [10s elapsed]
module.instances.google_compute_instance.this[0]: Still creating... [10s elapsed]
module.group-web.google_compute_instance.this[1]: Still creating... [10s elapsed]
module.group-web.google_compute_instance.this[1]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-web]
module.group-gitlab.google_compute_instance.this[0]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-gitlab]
module.instances.google_compute_instance.this[0]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-vm-1]

Error: Error creating instance: googleapi: Error 409: The resource 'projects/projeto-1-265222/zones/us-central1-a/instances/linux-gitlab' already exists, alreadyExists

  on .terraform/modules/group-gitlab/main.tf line 1, in resource "google_compute_instance" "this":
   1: resource "google_compute_instance" "this" {
......
......
......
......

Oops, temos um problema. Precisamos colocar uma forma de que o nome da máquina não se repita, por esse motivo o erro acima aconteceu, portanto devemos alterar um detalhe em nosso módulo.

Adicione no arquivo main.tf a seguinte linha no atributo name:

name = format("%s-%d", var.name, count.index)

Adicione o arquivo main.tf:

$ git add main.tf

Faça o commit:

$ git commit -m 'Correçao para suportar multiplas maquinas sem duplicar nomes'

Com o seguinte resultado:

[master e6bf395] Correçao para suportar multiplas maquinas sem duplicar nomes
 1 file changed, 1 insertion(+), 1 deletion(-)

Envie a correção para o repositório:

$ git push origin master

Com o seguinte resultado:

Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 969 bytes | 969.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git
   0ac9bb5..e6bf395  master -> master

Para começar de fato a versionar um módulo de maneira apropriada, devemos criar uma tag utilizando git para fazer uma marcação no tempo.

$ git tag -a v1.0 -m 'Versão 1.0 que realiza a criação de múltiplas máquinas'

Confirme sua tag com o comando:

$ git tag

Com a saída:

v1.0

Envie a tag para o repositório:

$ git push --tags

Com o seguinte resultado:

Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 205 bytes | 205.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git
 * [new tag]         v1.0 -> v1.0

Devemos alterar nosso código que consome este módulo para utilizar a nova tag:

Altere o arquivo instances.tf

  module "instances" {
  source = "git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git?ref=v1.0"

  amount = 2
  name   = "linux-vm-1"
}

module "group-web" {
  source = "git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git?ref=v1.0"

  amount = 3
  name   = "linux-web"
  image  = "centos-cloud/centos-8"
}

module "group-gitlab" {
  source = "git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git?ref=v1.0"

  amount = 2
  name   = "linux-gitlab"
  image  = "centos-cloud/centos-7"
}

Verifique no final de cada chamada do módulo o argumento ?ref=v1.0. Com este argumento você irá dizer em qual versão o seu módulo está. Desta forma você está garantindo que sua infraestrutura funcionará com este módulo na versão 1.0

Reinicialize o Terraform:

$ terraform init

Verifique a saída:

Initializing modules...
Downloading git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git?ref=v1.0 for group-gitlab...
- group-gitlab in .terraform/modules/group-gitlab
Downloading git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git?ref=v1.0 for group-web...
- group-web in .terraform/modules/group-web
Downloading git@gitlab.com:rd-public/4linux/blog-tf-modules/gcp-instances.git?ref=v1.0 for instances...
- instances in .terraform/modules/instances

Initializing the backend...
......
......
......
......

Antes de executar o plan e apply, destrua a infraestrutura que foi criada anteriormente, pois mesmo retornando um erro, algumas máquinas foram criadas até o erro ser disparado.

$ terraform destroy -auto-approve

Agora faça o plano de execução e em seguida crie a infraestrutura. Vou pular aqui o plan, vou diretamente para o apply para mostrar o funcionamento do módulo.

$ terraform apply -auto-approve

Com o seguinte resultado esperado desta vez:

module.group-web.google_compute_instance.this[0]: Creating...
module.instances.google_compute_instance.this[0]: Creating...
module.group-gitlab.google_compute_instance.this[0]: Creating...
module.group-web.google_compute_instance.this[2]: Creating...
module.group-gitlab.google_compute_instance.this[1]: Creating...
module.instances.google_compute_instance.this[1]: Creating...
module.group-web.google_compute_instance.this[1]: Creating...
module.group-web.google_compute_instance.this[0]: Still creating... [10s elapsed]
module.instances.google_compute_instance.this[0]: Still creating... [10s elapsed]
module.group-gitlab.google_compute_instance.this[0]: Still creating... [10s elapsed]
module.group-web.google_compute_instance.this[2]: Still creating... [10s elapsed]
module.group-gitlab.google_compute_instance.this[1]: Still creating... [10s elapsed]
module.instances.google_compute_instance.this[1]: Still creating... [10s elapsed]
module.group-web.google_compute_instance.this[1]: Still creating... [10s elapsed]
module.group-web.google_compute_instance.this[2]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-web-2]
module.instances.google_compute_instance.this[1]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-vm-1-1]
module.group-web.google_compute_instance.this[1]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-web-1]
module.instances.google_compute_instance.this[0]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-vm-1-0]
module.group-gitlab.google_compute_instance.this[1]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-gitlab-1]
module.group-web.google_compute_instance.this[0]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-web-0]
module.group-gitlab.google_compute_instance.this[0]: Creation complete after 12s [id=projects/projeto-1-265222/zones/us-central1-a/instances/linux-gitlab-0]

Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

Perfeito, módulo funcionando perfeitamente desta vez sem repetições de nomes.

Este módulo está disponível em: gitlab.com/rd-public/4linux/blog-tf-modules..

Para nosso próximo capítulo, iremos criar um módulo para gerenciamento de rede, outro módulo para gerenciamento de subredes na GCP e fazer sua integração com nosso módulo de gerenciamento de instâncias.