Déploiement sur GCP avec Terraform & Ansible
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.8pyenv virtualenv 3.9.8 ansiblepyenv local ansiblepip 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-srCreate 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 providerprovider "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.
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 = Falseinventory=gcp_compute.ymlinterpreter_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_computezones: # populate inventory with instances in these regions - europe-west1-bprojects: - test-terraform-srservice_account_file: ./service_account.jsonauth_kind: serviceaccountkeyed_groups: # Create groups from GCE labels - key: labels prefix: label - key: zone prefix: zonehostnames: - namecompose: 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 initterraform 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 applyansible-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:
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.218Warning: 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_devgitlab-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éé.