Aller au contenu

Déploiement sur GCP avec Terraform & Ansible

logo terraform

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.

Terminal window
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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
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.

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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
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 :

Terminal window
terraform init
terraform apply

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

Terminal window
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 :

Terminal window
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 :blush:

Terminal window
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"
}
Terminal window
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.

Terminal window
terraform apply

Vous devriez voir ces lignes :

Terminal window
...
# 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 _.

Terminal window
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 :

Terminal window
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 :

Terminal window
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