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.
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_rsa.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-rsa ... = 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éé.