Aller au contenu principal

Déploiement sud GCP avec Terraform & Ansible

· 9 minutes de lecture
Stéphane ROBERT
Consultant DevOps

L'objectif de ce billet est de mettre en place ce qu'il faut pour qu'à la fin, vous soyez capable de provisionner une machine sur le Cloud de Google avec Terraform et de la configurer avec Ansible.

Donc, nous verrons :

  • comment créer un projet sur Google Cloud Platform (GCP)
  • provisionner une machine avec Terraform
  • créer un compte de service pour l'inventaire dynamique Ansible
  • et configurer le serveur avec un simple playbook Ansible

Si à un moment vous perdez le fil dans les ajouts sur les fichiers terraform, je vous ai mis le code source sur gitlab.

Installation des prérequis

Si vous ne possédez pas encore de compte GCP, vous pouvez en créer un de test. Ce compte permet de découvrir gratuitement ce service pendant 90 jours avec un crédit alloué de 300$. Ensuite, on peut basculer sur la version gratuite mais il faudra bien faire attention de ne pas dépasser les limites.

Installation du SDK cloud gcloud

Dans un premier temps, nous allons installer la CLI gcloud. Il suffit de se rendre sur cette page pour récupérer la ligne de commande.

Installation de Terraform

De la même manière procéder à l'installation de Terraform.

Installation d'Ansible

Ansible est très simple à installer avec pyenv.

pyenv install 3.9.8
pyenv virtualenv 3.9.8 ansible
pyenv local ansible
pip install ansible google-auth requests

Initialisation du projet GCP

Dans un premier temps, il faut récupérer les informations de connexion de votre compte google sur votre machine. Il faudra un navigateur, mais il est possible de s'en passer avec l'option --console-only. Ici, il suffit de copier/coller le code générer dans le navigateur :

gcloud init

Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id ....

Enter verification code: 4/1AX4.....

Il vous demandera ensuite de créer un nouveau projet. Choisissez un nom de projet assez complexe pour éviter qu'il ne soit déjà pris. Si c'est le cas vous pouvez relancer la création du projet avec la commande suivante :

gcloud projects create test-terraform-sr
Create in progress for [https://cloudresourcemanager.googleapis.com/v1/projects/test-terraform-sr].
...
Operation "operations/acf.p2-...." finished successfully.

Notre projet est créé. Ensuite, il faut récupérer les informations de connexion :

gcloud auth application-default login

Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth

Enter verification code: 4/1AX4X...
Credentials saved to file: [/home/vagrant/.config/gcloud/application_default_credentials.json]
WARNING: Cannot find a quota project to add to ADC. You might receive a "quota exceeded" or "API not enabled" error.

Création de notre configuration Terraform

Nous allons simplement indiquer à Terraform que nous allons provisionner nos machines sur GCP. Pour cela il suffit de lui indiquer le provider suivant google. Mais au préalable créons un fichier pour y stocker nos variables : variables.tf. Modifier en fonction de vos besoins, moi j'utilise la zone europe-west1 puisque j'habite très très près de la Belgique (200m) :

variable "gcp_project" {
  type        = string
  default     = "test-terraform-sr"
  description : "The GCP project to deploy the runner into."
}
variable "gcp_zone" {
  type        = string
  default     = "europe-west1-b"
  description : "The GCP zone to deploy the runner into."
}

variable "gcp_region" {
  type        = string
  default     = "europe-west1"
  description : "The GCP region to deploy the runner into."
}

Maintenant créons le fichier principal de notre configuration Terraform :

// Configure the Google Cloud provider
provider "google" {
  project     = var.gcp_project
  region      = var.gcp_region
  zone        = var.gcp_zone
}

Il faut initialiser la configuration pour installer le provider google :

terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/google...
- Installing hashicorp/google v4.0.0...
- Installed hashicorp/google v4.0.0 (signed by HashiCorp)

Dans un premier temps créons une simple VM. Dans le fichier variable, ajoutez ces deux variables :

variable "ci_runner_instance_type" {
  type        = string
  default     = "e2-micro"
}
variable "hostname" {
  type        = string
  default     = "gitlab-runner.example.com"
}

Ensuite ajouter à la suite de votre fichier main.tf ceci :

resource "google_compute_instance" "gitalb-ci-runner" {
  name                      = "gitlab-ci-runner"
  hostname                  = var.hostname
  machine_type              = var.ci_runner_instance_type
  project                   = var.gcp_project
  zone                      = var.gcp_zone

  scheduling {
    preemptible       = true
    automatic_restart = false
  }

  boot_disk {
    initialize_params {
      image = "rocky-linux-cloud/rocky-linux-8"
      size  = 20
      type  = "pd-standard"
    }
  }
  network_interface {
    network = "default"
    access_config {
      // Include this section to give the VM an external ip address
    }
  }
}

Pour éviter de consommer trop sur votre compte, j'ai ajouté l'option preemptible au scheduling.

remarque

Les instances de VM préemptives sont disponibles à un prix nettement inférieur (une remise de 60 à 91 %) par rapport au prix des VM standards. Cependant, Compute Engine peut arrêter (préempter) ces instances s'il a besoin de récupérer ces ressources pour d'autres tâches

Vous pouvez vérifier la configuration avec la commande terraform plan. Normalement il va vous dire que l'API Compute engine n'est pas activé sur le projet. Suivez le lien et activez cette API. Il faudra également sélectionner votre compte de facturation. Patientez quelques minutes et relancez le calcul du plan Terraform. Cette fois-ci, ça devrait passer.

Lançons le provisionnement :

terraform apply

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_compute_instance.gitalb-ci-runner: Creating...
google_compute_instance.gitalb-ci-runner: Still creating... [10s elapsed]
google_compute_instance.gitalb-ci-runner: Still creating... [20s elapsed]
google_compute_instance.gitalb-ci-runner: Creation complete after 24s [id=projects/test-terraform-sr/zones/europe-west1-b/instances/gitlab-ci-runner]

Ajoutons une variable de sortie pour récupérer son adresse IP.

A la fin du fichier main.tf ajoutez ces lignes :

output "ip" {
  value = google_compute_instance.gitalb-ci-runner.network_interface.0.access_config.0.nat_ip
}

On peut relancer terraform plan. Vous devriez voir ceci apparaître dans le plan :

Changes to Outputs:
  + ip = "35.187.109.112"

Appliquez la configuration.

Maintenant attaquons la partie Ansible.

Configuration de l'inventaire Ansible

L'objectif est de pouvoir utiliser le plugin d'inventaire Ansible gcp_compute pour récupérer les machines provisionnées sur ce service.

Configurons le plugin ansible

Créer le fichier ansible.cfg pour ajouter le plugin d'inventaire gcp_compute :

[defaults]
host_key_checking = False
inventory=gcp_compute.yml
interpreter_python=auto_silent
[inventory]
enable_plugins = gcp_compute, auto, host_list, yaml, ini, toml, script

Il faut ensuite créer le fichier gcp_compute.yml avec ce contenu :

plugin: gcp_compute
zones: # populate inventory with instances in these regions
  - europe-west1-b
projects:
  - test-terraform-sr
service_account_file: ./service_account.json
auth_kind: serviceaccount
keyed_groups:
  # Create groups from GCE labels
  - key: labels
    prefix: label
  - key: zone
    prefix: zone
hostnames:
  - name
compose:
  ansible_host: networkInterfaces[0].accessConfigs[0].natIP

Lançons la commande d'inventaire Ansible :

ansible-inventory --list

 [Errno 2] No such file or directory: './service_account.json'

Vous voyez qu'il faut qu'on récupère le fichier de connexion d'un compte de service.

Création d'un compte de service

Nous allons voir comment créer un compte de service. Toujours au-dessus bloc output ajouter ces lignes :

resource "google_service_account" "service_account" {
  account_id   = "terraform"
  display_name = "terraform"
}
resource "google_service_account_key" "service_account" {
  service_account_id = google_service_account.service_account.name
  public_key_type    = "TYPE_X509_PEM_FILE"
}
resource "local_file" "service_account" {
    content  = base64decode(google_service_account_key.service_account.private_key)
    filename = "./service_account.json"
}

Appliquez :

terraform init
terraform apply

Attendez quelques instants et on relance la commande d'inventaire :

ansible-inventory --list

'compute.instances.list' permission for 'projects/test-terraform-sr'", 'domain': 'global', 'reason': 'forbidden'}]

Mince, il faut lui ajouter les droits de lister des VM.

Ajout d'un rôle à un compte de service

Ajoutons-lui simplement un rôle existant contenant celui nécessaire à ansible-inventory, un rôle avec des droits de lecture seule :

resource "google_project_iam_binding" "project" {
  project = var.gcp_project
  role    = "roles/viewer"

  members = [
    "serviceAccount:${google_service_account.service_account.email}",
  ]
}

On applique et on relance :

terraform apply
ansible-inventory --list
{
    "_meta": {
        "hostvars": {
            "gitlab-ci-runner": {
                "ansible_host": "35.189.194.218",
                "canIpForward": false,
....
    "all": {
        "children": [
            "ungrouped",
            "zone_europe_west1_b"
        ]
    },
    "zone_europe_west1_b": {
        "hosts": [
            "gitlab-ci-runner"
        ]
    }
}

Ça fonctionne 😊

ansible-inventory --graph
@all:
  |--@ungrouped:
  |--@zone_europe_west1_b:
  |  |--gitlab-ci-runner

Si vous voulez ajouter des groupes il suffit d'ajouter des labels à vos VM :

  labels = {
    environment = "dev"
  }
ansible-inventory --graph
@all:
  |--@label_environment_dev:
  |  |--gitlab-ci-runner
  |--@ungrouped:
  |--@zone_europe_west1_b:
  |  |--gitlab-ci-runner

Utilisez l'inventaire pour lancer des playbooks

On arrive à récupérer notre inventaire, mais nous n'avons toujours pas le droit de nous connecter à nos VM. Pourtant, indispensable pour lancer des playbooks.

Autorisez les connections à votre VM avec votre clé SSH

Dans le bloc de google_compute_instance il faut ajouter ces lignes pour autoriser les connexions en SSH mettez le, en dessous de celui du scheduling :

  metadata = {
    enable-oslogin = "TRUE"
  }

Avant le bloc output ajoutez aussi ces deux blocs :

data "google_client_openid_userinfo" "me" {
}

resource "google_os_login_ssh_public_key" "add_my_key" {
  project = var.gcp_project
  user =  data.google_client_openid_userinfo.me.email
  key = file("~/.ssh/id_ed25519.pub")
}

Le premier va récupérer les informations de connexion dans une source de données. Le second va simplement y ajouter votre clé SSH utilisateur. Si elle n'existe pas, créer la avec la commande ssh-keygen.

Appliquez la configuration.

terraform apply

Vous devriez voir ces lignes :

...
 # google_os_login_ssh_public_key.cache will be created
  + resource "google_os_login_ssh_public_key" "add_my_key" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + key         = <<-EOT
            ssh-ed25519 ... = vagrant@test
        EOT
      + project     = "test-terraform-sr"
      + user        = "robert.stephane.28@gmail.com"
    }

...

google_os_login_ssh_public_key.cache: Creation complete after 0s [id=users/robert.stephane.28@gmail.com/sshPublicKeys/...]

A la fin, vous devez voir l'adresse email qui est en fait le compte utilisateur créer sur la machine. Il suffit de remplacer les . et @ par des _.

ssh robert_stephane_28_gmail_com@35.189.194.218
Warning: Permanently added '35.189.194.218' (ECDSA) to the list of known hosts.
Creating directory '/home/robert_stephane_28_gmail_com'.

Cool

Configuration du remote user ansible

Il suffit d'ajouter à votre fichier ansible.cfg cette ligne dans la section [default] avec votre user :

remote_user=robert_stephane_28_gmail_com

et on teste :

ansible -m ping label_environment_dev
gitlab-ci-runner | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

Allez pour finir on tente l'installation d'un package sur notre VM. On crée un playbook ansible avec ce contenu :

---
- hosts: label_environment_dev
  gather_facts: false
  check_mode: false
  become: true
  tasks:
    - name: install epel-release
      ansible.builtin.package:
        name: epel-release
        state: present
    - name: install htop
      ansible.builtin.package:
        name: htop
        state: present
...

Et on lance :

ansible-playbook test.yml

PLAY [label_environment_dev]  ******************************

TASK [install epel-release] ********************************
changed: [gitlab-ci-runner]

TASK [install htop] ****************************************
changed: [gitlab-ci-runner]

PLAY RECAP **************************************************
gitlab-ci-runner           : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Voilà objectif atteint ! Mais on peut encore l'améliorer en créant un role dédié, en autorisant les connexions avec le compte de service créé.

Le code source de ce billet