Dans l’univers dynamique du DevOps et de l’infrastructure en tant que code (IaC), l’automatisation et la gestion efficace des environnements de développement sont devenues primordiales. Parmi les outils de pointe qui facilitent cette transformation digitale, ArgoCD émerge comme un acteur clé. Cet article se propose de détailler la mise en œuvre d’une « usine à VM » à visée bureautique. En illustrant comment une instance d’ArgoCD, une solution GitOps native pour Kubernetes, pourrait permettre la création et la maintenance de “VM” dans des environnements cloud ou sur site.
ArgoCD embrasse le concept GitOps, intégrant étroitement les pratiques de développement logiciel dans la gestion des infrastructures, permettant ainsi une automatisation poussée, une sécurité renforcée et une traçabilité des déploiements. En adoptant une approche déclarative, ArgoCD facilite la définition de l’état désiré de votre infrastructure directement dans des dépôts Git. Cette méthodologie assure que l’état réel de votre système est continuellement synchronisé avec celui défini dans votre code, réduisant ainsi les risques d’erreurs humaines et accélérant le cycle de vie de déploiement.
L’approche de cette « usine à VM » avec ArgoCD est particulièrement séduisante pour les équipes souhaitant optimiser leurs workflows, améliorer leur efficacité opérationnelle, et garantir la cohérence et la fiabilité de leurs environnements de développement et de production. À travers cet article, nous dévoilerons comment configurer et exploiter ArgoCD pour orchestrer la création et la gestion de pseudos VMs avec une mise en pratique via des images préconfigurées.
Table of Contents
Prérequis
Avant de plonger dans l’installation d’ArgoCD, assurez-vous de satisfaire aux prérequis suivants :
- Un cluster Kubernetes en cours d’exécution.
- kubectl configuré pour communiquer avec votre cluster Kubernetes.
- Accès à un compte avec des privilèges administratifs sur le cluster.
Étape 1 : Déploiement d’ArgoCD sur Kubernetes
Le moyen le plus simple de déployer ArgoCD est d’utiliser son manifeste YAML officiel. Exécutez la commande suivante pour appliquer ce manifeste :
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Cette commande crée un nouvel espace de noms appelé argocd et déploie ArgoCD dans cet espace de noms.
Étape 2 : Accès à l’interface utilisateur d’ArgoCD
Vous devez créer un objet Ingress pour exposer le service argocd-server. Voici un exemple de fichier YAML pour un objet Ingress qui expose ArgoCD sur un sous-domaine (par exemple, argocd.mondomaine.com). Adaptez les valeurs selon votre configuration spécifique, notamment le domaine et, si nécessaire, les annotations spécifiques à votre Ingress Controller :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server-ingress
namespace: argocd
spec:
rules:
- host: argocd.mondomaine.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 80
# Si vous utilisez cert-manager pour la gestion des certificats SSL, ajoutez la section suivante :
# tls:
# - hosts:
# - argocd.mondomaine.com
# secretName: argocd-secret-tls
L’application ArgoCD sera alors accessible sur http://argocd.mondomaine.com
Étape 3 : Configuration d’ArgoCD pour Scruter un Dépôt Git
Prérequis
- Avoir créé un dépôt Git sous gitlab ou github
- Avoir configuré un compte d’accès au dépôt Git pour ArgoCD
- Avoir indiqué ce dépôt et ce compte dans l’interface d’administration d’ArgoCD
ArgoCD brille par sa capacité à automatiser le déploiement et la synchronisation des applications Kubernetes en se basant sur l’état déclaré dans un dépôt Git. Dans cette section, nous intégrerons une configuration spécifique pour que ArgoCD surveille un dépôt Git contenant les configurations d’infrastructure, notamment les définitions de machines virtuelles (VM), et les déploie automatiquement dans un cluster Kubernetes.
Création d’un projet ArgoCD
Un projet ArgoCD, tel que défini dans l’objet AppProject, sert à grouper des applications et à définir des politiques de sécurité, telles que les dépôts Git autorisés, les destinations de déploiement, et les ressources Kubernetes permises.
Créez un fichier nommé infrastructure-project.yaml avec le contenu suivant pour définir le projet :
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: infrastructure
namespace: argocd
spec:
description: "Projet d'infrastructure"
sourceRepos:
- "*"
sourceNamespaces:
- "*"
destinations:
- namespace: "*"
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: "*"
kind: "*"
Appliquez cette configuration dans votre cluster :
kubectl apply -f infrastructure-project.yaml
Définir l’application
Une application ArgoCD définit la source du code (dépôt Git, chemin, révision), la destination (cluster, namespace) et la politique de synchronisation. L’objet Application spécifie comment et où déployer les configurations d’infrastructure à partir du dépôt Git.
Créez un fichier nommé infrastructure-apps-application.yaml avec la configuration suivante :
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
spec:
project: infrastructure
source:
repoURL: 'https://gitlab.mondomaine.com/kubernetes/k8s.git'
path: argocd/apps
targetRevision: HEAD
directory:
recurse: true
destination:
server: 'https://kubernetes.default.svc'
namespace: infrastructure
syncPolicy:
automated:
selfHeal: true
prune: false
Cette configuration crée une application nommée infrastructure-apps qui surveille le chemin argocd/apps dans le dépôt spécifié. La politique de synchronisation automatique est activée, permettant à ArgoCD de rétablir automatiquement l’état désiré sans suppression des ressources non désirées (prune: false).
Appliquez cette configuration :
kubectl apply -f infrastructure-apps-application.yaml
Validation et Suivi
Après l’application des configurations, ArgoCD commencera à surveiller le dépôt Git spécifié. Toute modification apportée aux fichiers dans le chemin argocd/apps (et tous les sous-dossiers présents dans apps) déclenchera une synchronisation pour s’assurer que l’état de votre cluster correspond à celui défini dans le dépôt.
Vous pouvez vérifier le statut de l’application via l’interface utilisateur d’ArgoCD ou en utilisant la commande suivante :
argocd app get infrastructure-apps
Pour voir les logs de synchronisation ou les éventuelles erreurs, utilisez :
argocd app logs infrastructure-apps
Étape 4 : Déploiement de MariaDB avec ArgoCD
Afin de permettre à vos équipes d’accéder aux VMs, nous allons déployer guacamole dans votre environnement, une application de bureau à distance basée sur le web.
Le déploiement de Guacamole nécessite une base MariaDB ou PostgreSQL. Nous avons choisi mariadb dans cet exemple mais vous pouvez très bien appliquer la même logique pour déployer PostgreSQL.
Configuration de MariaDB
Définition de l’application MariaDB dans ArgoCD : Commencez par créer un fichier mariadb.yaml dans le dossier argocd/apps de votre dépôt Git, qui servira à déployer MariaDB. Cette configuration inclut la création d’un espace de noms mariadb et définit une application ArgoCD qui déploie MariaDB en utilisant un chart Helm.
Contenu de mariadb.yaml :
apiVersion: v1
kind: Namespace
metadata:
name: mariadb
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mariadb
namespace: argocd
spec:
project: infrastructure
source:
path: mariadb
repoURL: https://gitlab.mondomaine.com/kubernetes/k8s.git
targetRevision: HEAD
destination:
server: 'https://kubernetes.default.svc'
namespace: mariadb
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
selfHeal: true
prune: true
allowEmpty: true
Chart et valeurs pour MariaDB : Dans un nouveau dossier mariadb à la racine de votre dépôt git, créez deux fichiers, Chart.yaml et values.yaml, pour spécifier le chart Helm de MariaDB et les paramètres de configuration désirés, notamment le mot de passe root, le nom de la base de données, l’utilisateur et le mot de passe.
Chart.yaml
Ce fichier définit les métadonnées du chart Helm que vous créez pour le déploiement de MariaDB. Il spécifie le nom du chart, sa version, et les dépendances, notamment le chart MariaDB officiel à utiliser.
apiVersion: v2
name: mariadb
version: 1.0.0
description: "A Helm chart for Kubernetes"
# Dépendances : Spécifie le chart MariaDB et la version souhaitée.
dependencies:
- name: mariadb
version: "18.*"
repository: "oci://registry-1.docker.io/bitnamicharts"
Dans cet exemple, le chart mariadb dépend du chart MariaDB disponible dans le registre Bitnami. La version « 18.* » indique que n’importe quelle version majeure 18 sera sélectionnée. Vous devez vous assurer que la version spécifiée est compatible avec votre déploiement.
values.yaml
Ce fichier contient les configurations spécifiques pour le déploiement de MariaDB, telles que les paramètres de persistance, la configuration de l’authentification, et d’autres options spécifiques à l’application.
# Paramètres spécifiques à MariaDB
mariadb:
primary:
persistence:
enabled: true # Active la persistance pour garantir que les données survivent au redémarrage des pods
storageClass: longhorn # Utilise une classe de stockage spécifique pour le volume persistant
accessMode: ReadWriteOnce # Le volume peut être monté en écriture par un seul nœud
size: 8Gi # Définit la taille du volume de stockage
auth:
rootPassword: "root" # Mot de passe root pour l'accès à la base de données
database: "guacamole" # Nom de la base de données à créer automatiquement
username: "guacamole" # Nom de l'utilisateur de la base de données
password: "password" # Mot de passe pour l'utilisateur spécifié
Les paramètres sous mariadb.primary.persistence permettent de configurer la persistance des données. C’est crucial pour un service de base de données, car vous voudriez éviter de perdre vos données lorsque le pod de la base de données est redéployé ou supprimé.
Sous mariadb.auth, vous spécifiez les informations d’authentification pour la base de données, y compris le mot de passe root, le nom de la base de données par défaut à créer (dans ce cas, pour Guacamole), et les identifiants pour un utilisateur spécifique de cette base de données.
Lorsque vous poussez ces changements vers votre dépôt Git surveillé par ArgoCD, il détectera les changements et procédera au déploiement de MariaDB en utilisant les configurations spécifiées. ArgoCD facilite la gestion des déploiements en assurant que l’état de votre cluster Kubernetes correspond à l’état déclaré dans votre dépôt Git, permettant ainsi un déploiement et une mise à jour continus et automatisés de vos applications et services.
Étape 5 : Déploiement de guacamole avec ArgoCD
Pour le déploiement de Guacamole, créez un fichier guacamole.yaml dans le même dossier argocd/apps de votre dépôt Git. Ce fichier définit les ressources nécessaires pour déployer Guacamole, y compris le service Guacd et le service web Guacamole lui-même, ainsi qu’une configuration pour initialiser la base de données MariaDB.
# Création de l'espace de noms pour Guacamole
apiVersion: v1
kind: Namespace
metadata:
name: guacamole
---
# Déploiement du service guacd
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacd
namespace: guacamole
spec:
selector:
matchLabels:
app: guacd
template:
metadata:
labels:
app: guacd
spec:
containers:
- name: guacd
image: guacamole/guacd
ports:
- containerPort: 4822
# Probes pour surveiller l'état du service guacd
livenessProbe:
tcpSocket:
port: 4822
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 4822
initialDelaySeconds: 30
periodSeconds: 10
---
# Déploiement de l'application web Guacamole
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacamole
namespace: guacamole
spec:
selector:
matchLabels:
app: guacamole
template:
metadata:
labels:
app: guacamole
spec:
containers:
- name: guacamole
image: guacamole/guacamole
# Configuration de l'environnement pour se connecter à guacd et à la base de données
env:
- name: GUACD_HOSTNAME
value: guacd.guacamole.svc.cluster.local
- name: MYSQL_HOSTNAME
value: mariadb.mariadb.svc.cluster.local
- name: MYSQL_DATABASE
value: guacamole
- name: MYSQL_USER
value: guacamole
- name: MYSQL_PASSWORD
value: password
ports:
- containerPort: 8080
# Probes pour surveiller l'état de l'application web
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
---
# Service pour exposer guacd
apiVersion: v1
kind: Service
metadata:
name: guacd
namespace: guacamole
spec:
ports:
- port: 4822
targetPort: 4822
selector:
app: guacd
---
# Service pour exposer l'application web Guacamole
apiVersion: v1
kind: Service
metadata:
name: guacamole
namespace: guacamole
spec:
ports:
- port: 8080
targetPort: 8080
selector:
app: guacamole
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: guacamole
namespace: guacamole
spec:
rules:
- host: guacamole.mondomaine.com
http:
paths:
- backend:
service:
name: guacamole
port:
number: 8080
path: /
pathType: Prefix
tls:
- hosts:
- guacamole.mondomaine.com
---
# ConfigMap contenant le script d'initialisation de la base de données
apiVersion: v1
kind: ConfigMap
metadata:
name: guacamole-db-init-script
namespace: guacamole
data:
init.sql: |
CREATE TABLE IF NOT EXISTS `guacamole_connection_group` ( `connection_group_id` int(11) NOT NULL AUTO_INCREMENT, `parent_id` int(11), `connection_group_name` varchar(128) NOT NULL, `type` enum('ORGANIZATIONAL', 'BALANCING') NOT NULL DEFAULT 'ORGANIZATIONAL', `max_connections` int(11), `max_connections_per_user` int(11), `enable_session_affinity` boolean NOT NULL DEFAULT 0, PRIMARY KEY (`connection_group_id`), UNIQUE KEY `connection_group_name_parent` (`connection_group_name`, `parent_id`), CONSTRAINT `guacamole_connection_group_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_connection` ( `connection_id` int(11) NOT NULL AUTO_INCREMENT, `connection_name` varchar(128) NOT NULL, `parent_id` int(11), `protocol` varchar(32) NOT NULL, `proxy_port` integer, `proxy_hostname` varchar(512), `proxy_encryption_method` enum('NONE', 'SSL'), `max_connections` int(11), `max_connections_per_user` int(11), `connection_weight` int(11), `failover_only` boolean NOT NULL DEFAULT 0, PRIMARY KEY (`connection_id`), UNIQUE KEY `connection_name_parent` (`connection_name`, `parent_id`), CONSTRAINT `guacamole_connection_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_entity` ( `entity_id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, `type` enum('USER', 'USER_GROUP') NOT NULL, PRIMARY KEY (`entity_id`), UNIQUE KEY `guacamole_entity_name_scope` (`type`, `name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_user` ( `user_id` int(11) NOT NULL AUTO_INCREMENT, `entity_id` int(11) NOT NULL, `password_hash` binary(32) NOT NULL, `password_salt` binary(32), `password_date` datetime NOT NULL, `disabled` boolean NOT NULL DEFAULT 0, `expired` boolean NOT NULL DEFAULT 0, `access_window_start` TIME, `access_window_end` TIME, `valid_from` DATE, `valid_until` DATE, `timezone` VARCHAR(64), `full_name` VARCHAR(256), `email_address` VARCHAR(256), `organization` VARCHAR(256), `organizational_role` VARCHAR(256), PRIMARY KEY (`user_id`), UNIQUE KEY `guacamole_user_single_entity` (`entity_id`), CONSTRAINT `guacamole_user_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_user_group` ( `user_group_id` int(11) NOT NULL AUTO_INCREMENT, `entity_id` int(11) NOT NULL, `disabled` boolean NOT NULL DEFAULT 0, PRIMARY KEY (`user_group_id`), UNIQUE KEY `guacamole_user_group_single_entity` (`entity_id`), CONSTRAINT `guacamole_user_group_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_user_group_member` ( `user_group_id` int(11) NOT NULL, `member_entity_id` int(11) NOT NULL, PRIMARY KEY (`user_group_id`, `member_entity_id`), CONSTRAINT `guacamole_user_group_member_parent_id` FOREIGN KEY (`user_group_id`) REFERENCES `guacamole_user_group` (`user_group_id`) ON DELETE CASCADE, CONSTRAINT `guacamole_user_group_member_entity_id` FOREIGN KEY (`member_entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_sharing_profile ( `sharing_profile_id` int(11) NOT NULL AUTO_INCREMENT, `sharing_profile_name` varchar(128) NOT NULL, `primary_connection_id` int(11) NOT NULL, PRIMARY KEY (`sharing_profile_id`), UNIQUE KEY `sharing_profile_name_primary` (sharing_profile_name, primary_connection_id), CONSTRAINT `guacamole_sharing_profile_ibfk_1` FOREIGN KEY (`primary_connection_id`) REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_connection_parameter` ( `connection_id` int(11) NOT NULL, `parameter_name` varchar(128) NOT NULL, `parameter_value` varchar(4096) NOT NULL, PRIMARY KEY (`connection_id`,`parameter_name`), CONSTRAINT `guacamole_connection_parameter_ibfk_1` FOREIGN KEY (`connection_id`) REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_sharing_profile_parameter ( `sharing_profile_id` integer NOT NULL, `parameter_name` varchar(128) NOT NULL, `parameter_value` varchar(4096) NOT NULL, PRIMARY KEY (`sharing_profile_id`, `parameter_name`), CONSTRAINT `guacamole_sharing_profile_parameter_ibfk_1` FOREIGN KEY (`sharing_profile_id`) REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_user_attribute ( `user_id` int(11) NOT NULL, `attribute_name` varchar(128) NOT NULL, `attribute_value` varchar(4096) NOT NULL, PRIMARY KEY (user_id, attribute_name), KEY `user_id` (`user_id`), CONSTRAINT guacamole_user_attribute_ibfk_1 FOREIGN KEY (user_id) REFERENCES guacamole_user (user_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_user_group_attribute ( `user_group_id` int(11) NOT NULL, `attribute_name` varchar(128) NOT NULL, `attribute_value` varchar(4096) NOT NULL, PRIMARY KEY (`user_group_id`, `attribute_name`), KEY `user_group_id` (`user_group_id`), CONSTRAINT `guacamole_user_group_attribute_ibfk_1` FOREIGN KEY (`user_group_id`) REFERENCES `guacamole_user_group` (`user_group_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_connection_attribute ( `connection_id` int(11) NOT NULL, `attribute_name` varchar(128) NOT NULL, `attribute_value` varchar(4096) NOT NULL, PRIMARY KEY (connection_id, attribute_name), KEY `connection_id` (`connection_id`), CONSTRAINT guacamole_connection_attribute_ibfk_1 FOREIGN KEY (connection_id) REFERENCES guacamole_connection (connection_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_connection_group_attribute ( `connection_group_id` int(11) NOT NULL, `attribute_name` varchar(128) NOT NULL, `attribute_value` varchar(4096) NOT NULL, PRIMARY KEY (connection_group_id, attribute_name), KEY `connection_group_id` (`connection_group_id`), CONSTRAINT guacamole_connection_group_attribute_ibfk_1 FOREIGN KEY (connection_group_id) REFERENCES guacamole_connection_group (connection_group_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_sharing_profile_attribute ( `sharing_profile_id` int(11) NOT NULL, `attribute_name` varchar(128) NOT NULL, `attribute_value` varchar(4096) NOT NULL, PRIMARY KEY (sharing_profile_id, attribute_name), KEY `sharing_profile_id` (`sharing_profile_id`), CONSTRAINT guacamole_sharing_profile_attribute_ibfk_1 FOREIGN KEY (sharing_profile_id) REFERENCES guacamole_sharing_profile (sharing_profile_id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_connection_permission` ( `entity_id` int(11) NOT NULL, `connection_id` int(11) NOT NULL, `permission` enum('READ', 'UPDATE', 'DELETE', 'ADMINISTER') NOT NULL, PRIMARY KEY (`entity_id`,`connection_id`,`permission`), CONSTRAINT `guacamole_connection_permission_ibfk_1` FOREIGN KEY (`connection_id`) REFERENCES `guacamole_connection` (`connection_id`) ON DELETE CASCADE, CONSTRAINT `guacamole_connection_permission_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_connection_group_permission` ( `entity_id` int(11) NOT NULL, `connection_group_id` int(11) NOT NULL, `permission` enum('READ', 'UPDATE', 'DELETE', 'ADMINISTER') NOT NULL, PRIMARY KEY (`entity_id`,`connection_group_id`,`permission`), CONSTRAINT `guacamole_connection_group_permission_ibfk_1` FOREIGN KEY (`connection_group_id`) REFERENCES `guacamole_connection_group` (`connection_group_id`) ON DELETE CASCADE, CONSTRAINT `guacamole_connection_group_permission_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_sharing_profile_permission ( `entity_id` integer NOT NULL, `sharing_profile_id` integer NOT NULL, `permission` enum('READ', 'UPDATE', 'DELETE', 'ADMINISTER') NOT NULL, PRIMARY KEY (`entity_id`, `sharing_profile_id`, `permission`), CONSTRAINT `guacamole_sharing_profile_permission_ibfk_1` FOREIGN KEY (`sharing_profile_id`) REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE CASCADE, CONSTRAINT `guacamole_sharing_profile_permission_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_system_permission` ( `entity_id` int(11) NOT NULL, `permission` enum('CREATE_CONNECTION', 'CREATE_CONNECTION_GROUP', 'CREATE_SHARING_PROFILE', 'CREATE_USER', 'CREATE_USER_GROUP', 'ADMINISTER') NOT NULL, PRIMARY KEY (`entity_id`,`permission`), CONSTRAINT `guacamole_system_permission_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_user_permission` ( `entity_id` int(11) NOT NULL, `affected_user_id` int(11) NOT NULL, `permission` enum('READ', 'UPDATE', 'DELETE', 'ADMINISTER') NOT NULL, PRIMARY KEY (`entity_id`,`affected_user_id`,`permission`), CONSTRAINT `guacamole_user_permission_ibfk_1` FOREIGN KEY (`affected_user_id`) REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE, CONSTRAINT `guacamole_user_permission_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_user_group_permission` ( `entity_id` int(11) NOT NULL, `affected_user_group_id` int(11) NOT NULL, `permission` enum('READ', 'UPDATE', 'DELETE', 'ADMINISTER') NOT NULL, PRIMARY KEY (`entity_id`, `affected_user_group_id`, `permission`), CONSTRAINT `guacamole_user_group_permission_affected_user_group` FOREIGN KEY (`affected_user_group_id`) REFERENCES `guacamole_user_group` (`user_group_id`) ON DELETE CASCADE, CONSTRAINT `guacamole_user_group_permission_entity` FOREIGN KEY (`entity_id`) REFERENCES `guacamole_entity` (`entity_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `guacamole_connection_history` ( `history_id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `username` varchar(128) NOT NULL, `remote_host` varchar(256) DEFAULT NULL, `connection_id` int(11) DEFAULT NULL, `connection_name` varchar(128) NOT NULL, `sharing_profile_id` int(11) DEFAULT NULL, `sharing_profile_name` varchar(128) DEFAULT NULL, `start_date` datetime NOT NULL, `end_date` datetime DEFAULT NULL, PRIMARY KEY (`history_id`), KEY `user_id` (`user_id`), KEY `connection_id` (`connection_id`), KEY `sharing_profile_id` (`sharing_profile_id`), KEY `start_date` (`start_date`), KEY `end_date` (`end_date`), KEY `connection_start_date` (`connection_id`, `start_date`), CONSTRAINT `guacamole_connection_history_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `guacamole_user` (`user_id`) ON DELETE SET NULL, CONSTRAINT `guacamole_connection_history_ibfk_2` FOREIGN KEY (`connection_id`) REFERENCES `guacamole_connection` (`connection_id`) ON DELETE SET NULL, CONSTRAINT `guacamole_connection_history_ibfk_3` FOREIGN KEY (`sharing_profile_id`) REFERENCES `guacamole_sharing_profile` (`sharing_profile_id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_user_history ( `history_id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `username` varchar(128) NOT NULL, `remote_host` varchar(256) DEFAULT NULL, `start_date` datetime NOT NULL, `end_date` datetime DEFAULT NULL, PRIMARY KEY (history_id), KEY `user_id` (`user_id`), KEY `start_date` (`start_date`), KEY `end_date` (`end_date`), KEY `user_start_date` (`user_id`, `start_date`), CONSTRAINT guacamole_user_history_ibfk_1 FOREIGN KEY (user_id) REFERENCES guacamole_user (user_id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS guacamole_user_password_history ( `password_history_id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `password_hash` binary(32) NOT NULL, `password_salt` binary(32), `password_date` datetime NOT NULL, PRIMARY KEY (`password_history_id`), KEY `user_id` (`user_id`), CONSTRAINT `guacamole_user_password_history_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `guacamole_user` (`user_id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO guacamole_entity (name, type) VALUES ('guacadmin', 'USER') ON DUPLICATE KEY UPDATE name = VALUES(name), type = VALUES(type); INSERT IGNORE INTO guacamole_user (entity_id, password_hash, password_salt, password_date) SELECT entity_id, x'CA458A7D494E3BE824F5E1E175A1556C0F8EEF2C2D7DF3633BEC4A29C4411960', x'FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264', NOW() FROM guacamole_entity WHERE name = 'guacadmin'; INSERT IGNORE INTO guacamole_system_permission (entity_id, permission) SELECT entity_id, permission FROM ( SELECT 'guacadmin' AS username, 'CREATE_CONNECTION' AS permission UNION SELECT 'guacadmin' AS username, 'CREATE_CONNECTION_GROUP' AS permission UNION SELECT 'guacadmin' AS username, 'CREATE_SHARING_PROFILE' AS permission UNION SELECT 'guacadmin' AS username, 'CREATE_USER' AS permission UNION SELECT 'guacadmin' AS username, 'CREATE_USER_GROUP' AS permission UNION SELECT 'guacadmin' AS username, 'ADMINISTER' AS permission ) permissions JOIN guacamole_entity ON permissions.username = guacamole_entity.name AND guacamole_entity.type = 'USER'; INSERT IGNORE INTO guacamole_user_permission (entity_id, affected_user_id, permission) SELECT guacamole_entity.entity_id, guacamole_user.user_id, permission FROM ( SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'READ' AS permission UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'UPDATE' AS permission UNION SELECT 'guacadmin' AS username, 'guacadmin' AS affected_username, 'ADMINISTER' AS permission ) permissions JOIN guacamole_entity ON permissions.username = guacamole_entity.name AND guacamole_entity.type = 'USER' JOIN guacamole_entity affected ON permissions.affected_username = affected.name AND guacamole_entity.type = 'USER' JOIN guacamole_user ON guacamole_user.entity_id = affected.entity_id;
---
# Job pour exécuter le script d'initialisation de la base de données
apiVersion: batch/v1
kind: Job
metadata:
name: guacamole-db-init
namespace: guacamole
spec:
template:
spec:
containers:
- name: init-db
image: mysql:5.7
command: ["sh", "-c", "mysql -u guacamole -p'password' -h mariadb.mariadb.svc.cluster.local -D guacamole < /mnt/init.sql"]
volumeMounts:
- name: script-volume
mountPath: "/mnt"
volumes:
- name: script-volume
configMap:
name: guacamole-db-init-script
restartPolicy: Never
---
Cela va déployer et configurer guacamole sur l’URL guacamole.mondomaine.com, l’utilisateur et mot de passe par défaut est guacadmin/guacadmin.
Étape 6 : Création des “VM” sous k8s
Je mets le mot « VM » entre guillemets car, concernant Linux, ce sont des containers et non pas des VM sous qemu ou kubevirt à proprement parlé.
VM Linux
On va ajouter un dossier VM dans le dépôt Git. Voici un exemple d’une VM générique sous alpine en utilisant l’image https://docs.linuxserver.io/images/docker-rdesktop/ qui est basée sur xrdp.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
finalizers:
- kubernetes.io/pvc-protection
name: alpine-xfce-pmietlicki
namespace: guacamole
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: longhorn
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: alpine-xfce-pmietlicki
namespace: guacamole
spec:
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
selector:
matchLabels:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-alpine-xfce-pmietlicki
template:
metadata:
namespace: guacamole
labels:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-alpine-xfce-pmietlicki
spec:
containers:
- env:
- name: PUID
value: '1000'
- name: LANG
value: 'fr_FR.UTF-8'
- name: PGID
value: '1000'
- name: TZ
value: 'Pacific/Noumea'
- name: LANGUAGE
value: 'fr_FR.UTF-8'
- name: LC_ALL
value: 'fr_FR.UTF-8'
- name: HTTP_PROXY
value: "http://proxy.mondomaine.com:3128"
- name: HTTPS_PROXY
value: "http://proxy.mondomaine.com:3128"
- name: NO_PROXY
value: "localhost, .svc.cluster.local"
image: linuxserver/rdesktop:latest
imagePullPolicy: Always
livenessProbe:
failureThreshold: 5
periodSeconds: 60
successThreshold: 1
tcpSocket:
port: 3389
timeoutSeconds: 1
name: alpine-xfce-pmietlicki
ports:
- containerPort: 3389
name: rdp
protocol: TCP
readinessProbe:
failureThreshold: 5
periodSeconds: 60
successThreshold: 1
tcpSocket:
port: 3389
timeoutSeconds: 1
resources:
limits:
memory: 2Gi
requests:
memory: 1Gi
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
runAsNonRoot: false
startupProbe:
failureThreshold: 5
periodSeconds: 120
successThreshold: 1
tcpSocket:
port: 3389
timeoutSeconds: 1
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /config
name: vol-home
- mountPath: /dev/shm
name: vol-shm
- mountPath: /custom-cont-init.d
name: vol-init
readOnly: true
restartPolicy: Always
volumes:
- name: vol-home
persistentVolumeClaim:
claimName: alpine-xfce-pmietlicki
- emptyDir:
medium: Memory
sizeLimit: 2Gi
name: vol-shm
- configMap:
defaultMode: 420
name: custom-script-alpine-xfce-pmietlicki
optional: false
name: vol-init
---
apiVersion: v1
kind: Service
metadata:
name: alpine-xfce-pmietlicki
namespace: guacamole
spec:
selector:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-alpine-xfce-pmietlicki
ports:
- name: rdp
port: 3389
protocol: TCP
targetPort: 3389
sessionAffinity: None
type: ClusterIP
---
apiVersion: v1
kind: ConfigMap
metadata:
name: custom-script-alpine-xfce-pmietlicki
namespace: guacamole
data:
install-script.sh: |+
#!/bin/bash
apk add --no-cache docker
apk add --no-cache python3 python3-pip
apk add --no-cache tmux
apk add --no-cache git
---
ArgoCD va alors déployer automatiquement l’image et l’instancier. Le “custom script” vous permet d’ajouter les outils souhaités et vous garantit que l’image aura toujours ces outils déployés automatiquement.
L’intérêt de cette image est qu’elle est “jetable” c’est à dire qu’à chaque redéploiement du pod, l’instance est recréée de zéro, il n’y a que votre home utilisateur (dans /config) qui vous permettra de garder votre configuration et préférences applicatives.Cela est intéressant pour réduire une surface d’attaque éventuelle en cas de compromission.
Ajout dans guacamole
Une fois l’image déployée, il ne vous reste plus qu’à la configurer sous Guacamole en allant dans Paramètres/Connexion/Nouvelle Connexion :
Mettre le nom de l’hôte interne, dans notre exemple : alpine-xfce-pmietlicki.guacamole
Et le port 3389.
Dans la partie Authentification, l’identifiant de l’image est abc et le mot de passe abc. On peut très bien ajouter un utilisateur pour indiquer un autre identifiant et mot de passe sachant que l’image n’est accessible que depuis le NS guacamole donc que depuis l’interface guacamole qui est sécurisable par AD, LDAP ou TOTP.
On coche “ignorer le certificat du serveur” puisqu’il est autosigné.
Vous pouvez aussi définir les paramètres de base :
VM Windows
L’utilisation d’images Docker pour émuler des systèmes d’exploitation complets comme Windows dans un conteneur nécessite KVM (Kernel-based Virtual Machine) qui est une technologie de virtualisation intégrée au noyau Linux permettant d’exécuter des machines virtuelles (VM) avec des performances proches de celles d’un système natif, utilisant l’accélération matérielle fournie par les extensions de virtualisation du processeur comme Intel VT-x ou AMD-V.
Une image très intéressante pour déployer Windows est celle disponible sur : https://github.com/dockur/windows
Cette image vous permet d’installer différentes versions de Windows : Windows 11 Pro, Windows 10 Pro, Windows 10 LTSC, Windows 8.1 Pro, Windows 7 SP1, Windows Vista SP2, Windows XP SP3, Windows Server 2022, Windows Server 2019, Windows Server 2016, Windows Server 2012, Windows Server 2008, Tiny 11 Core, Tiny 11 et Tiny 10
Voici un exemple de configuration pour un Windows Lite (tiny11) que vous pouvez mettre dans le dossier VM :
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-tiny11
name: tiny11
namespace: guacamole
spec:
selector:
matchLabels:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-tiny11
strategy:
type: Recreate
template:
labels:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-tiny11
namespace: guacamole
spec:
containers:
- env:
- name: VERSION
value: >-
https://archive.org/download/tiny-11-NTDEV/tiny11%2023H2%20x64.iso
- name: DISK_SIZE
value: 100G
- name: RAM_SIZE
value: 5G
image: dockurr/windows
imagePullPolicy: Always
name: tiny11
ports:
- containerPort: 3389
name: rdp
protocol: TCP
- containerPort: 8006
name: http
protocol: TCP
resources:
limits:
memory: 5Gi
securityContext:
allowPrivilegeEscalation: true
capabilities:
add:
- NET_ADMIN
privileged: true
volumeMounts:
- mountPath: /dev/kvm
name: dev-kvm
- mountPath: /storage
name: vol-tiny11
restartPolicy: Always
volumes:
- name: vol-tiny11
persistentVolumeClaim:
claimName: tiny11
- hostPath:
path: /dev/kvm
type: CharDevice
name: dev-kvm
A l’usage, j’ai remarqué qu’il vous faut activer le mode privileged (privileged: true) même en essayant d’activer la capacité “NET_ADMIN” seulement.
A noter que, même sur tiny11, 4Go de RAM et 2vcpu semble peu suffisant pour bien faire fonctionner Windows 11 pour un usage quotidien. Il vous faudra sans doute, à minima, 8Go de RAM de dispo pour utiliser pleinement le système.
La configuration sous guacamole est la même que pour Linux, la seule différence est que l’utilisateur par défaut est “docker” sans mot de passe.
Pour des versions spécifiques de Windows, il y a énormément d’ISO sur https://lecrabeinfo.net/
Activation du partage de fichiers sous guacamole
Pour activer le partage de fichier, vous devez utiliser un répertoire accessible en lecture/ecriture au tomcat de guacd. On peut, très simplement, utiliser /tmp.
Voici ce qu’il faudrait alors mettre dans la configuration :
Vous verrez alors un lecteur réseau dans l’explorateur, tous les fichiers que vous glisserez/déposerez via la fenêtre du navigateur dans laquelle se trouve votre connexion RDP guacamole se retrouveront à la racine de ce lecteur (et donc dans /tmp/).
Liste de l’ensemble des options de dockur/windows
VERSION="win11" # Windows version
MANUAL="N" # Automatic install
SAMBA="Y" # Enable Samba share
DISK_IO="native" # I/O Mode, can be set to 'native', 'threads' or 'io_uring'
DISK_FMT="raw" # Disk file format, can be set to "raw" (default) or "qcow2"
DISK_FLAGS="" # Specifies the options for use with the qcow2 disk format
DISK_CACHE="none" # Caching mode, can be set to 'writeback' for better performance
DISK_DISCARD="on" # Controls whether unmap (TRIM) commands are passed to the host.
DISK_ROTATION="1" # Rotation rate, set to 1 for SSD storage and increase for HDD
GPU="N" # GPU passthrough
VGA="virtio" # VGA adaptor
DISPLAY="web" # Display type
MAC="" # MAC address
DHCP="N" # Disable DHCP mode
NETWORK="Y" # Enable networkcard
HOST_PORTS=""
VM_NET_DEV=""
VM_NET_TAP="qemu"
VM_NET_HOST="QEMU"
DNSMASQ_OPTS=""
DNSMASQ="/usr/sbin/dnsmasq"
DNSMASQ_CONF_DIR="/etc/dnsmasq.d"
HV="Y" # Enable Hyper-V enlightments
KVM="Y" # Enable KVM acceleration
CPU_FLAGS=""
CPU_MODEL="qemu64"
DEBUG="N" # Disable debugging
CONSOLE="N" # Disable console
MACHINE="q35" # Machine selection
ALLOCATE="N" # Preallocate diskspace
ARGUMENTS="" # Extra QEMU parameters
CPU_CORES="1" # Amount of CPU cores
RAM_SIZE="1G" # Maximum RAM amount
DISK_SIZE="16G" # Initial data disk size
BOOT_INDEX="10" # Boot index of CD drive
TPM="Y" # Enable TPM
BOOT_MODE="windows" # Boot mode
SERIAL="mon:stdio"
USB="qemu-xhci,id=xhci"
MONITOR="telnet:localhost:7100,server,nowait,nodelay"
Voir les options sur : https://github.com/qemus/qemu-docker
Bonus : création d’une “usine à VM” Linux
Comme la configuration yaml est toujours la même, j’ai voulu créer un code javascript pour générer le yaml pour chaque VM, pour chaque collaborateur. Vous pouvez évidemment l’adapter pour Windows ou d’autres types de VM.
Voici un début de code HTML/Javascript pour ce besoin :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Generateur de Configuration VM Guacamole</title>
</head>
<body>
<h2>Generateur de Configuration VM pour Guacamole</h2>
<form id="configForm">
<label for="version">Choisir la version:</label>
<select name="version" id="version" required>
<option value="alpine-xfce" selected>alpine-xfce</option>
<option value="ubuntu-xfce">ubuntu-xfce</option>
<option value="fedora-xfce">fedora-xfce</option>
<option value="arch-xfce">arch-xfce</option>
<option value="alpine-kde">alpine-kde</option>
<option value="ubuntu-kde">ubuntu-kde</option>
<option value="fedora-kde">fedora-kde</option>
<option value="arch-kde">arch-kde</option>
<option value="alpine-mate">alpine-mate</option>
<option value="ubuntu-mate">ubuntu-mate</option>
<option value="fedora-mate">fedora-mate</option>
<option value="arch-mate">arch-mate</option>
<option value="alpine-i3">alpine-i3</option>
<option value="ubuntu-i3">ubuntu-i3</option>
<option value="fedora-i3">fedora-i3</option>
<option value="arch-i3">arch-i3</option>
<option value="alpine-openbox">alpine-openbox</option>
<option value="ubuntu-openbox">ubuntu-openbox</option>
<option value="fedora-openbox">fedora-openbox</option>
<option value="arch-openbox">arch-openbox</option>
<option value="alpine-icewm">alpine-icewm</option>
<option value="ubuntu-icewm">ubuntu-icewm</option>
<option value="fedora-icewm">fedora-icewm</option>
<option value="arch-icewm">arch-icewm</option>
</select><br><br>
<label for="suffix">Suffixe (ex: pmietlicki):</label>
<input type="text" id="suffix" name="suffix" placeholder="pmietlicki" required><br><br>
<label for="storage">Taille du PVC (ex: 5Gi):</label>
<select name="storage" id="storage" required>
<option value="1Gi">1Gi</option>
<option value="2Gi" selected>2Gi</option>
<option value="3Gi">3Gi</option>
<option value="4Gi">4Gi</option>
<option value="5Gi">5Gi</option>
<option value="6Gi">6Gi</option>
<option value="7Gi">7Gi</option>
<option value="8Gi">8Gi</option>
<option value="9Gi">9Gi</option>
<option value="10Gi">10Gi</option>
</select><br><br>
<label for="memoryLimit">Limite de mémoire (ex: 2Gi):</label>
<input type="text" id="memoryLimit" name="memoryLimit" value="2Gi" required><br><br>
<label for="shmSize">Taille de SHM (ex: 2Gi):</label>
<input type="text" id="shmSize" name="shmSize" value="2Gi" required><br><br>
<label for="proxyAddress">Adresse du Proxy:</label>
<input type="text" id="proxyAddress" name="proxyAddress" value="http://proxy.mondomaine.com"><br><br>
<label for="proxyPort">Port du Proxy:</label>
<input type="text" id="proxyPort" name="proxyPort" value="3128"><br><br>
<label for="tools">Outils à installer:</label>
<select multiple name="tools" id="tools">
<option value="docker">docker</option>
<option value="python">python</option>
<option value="tmux">tmux</option>
<option value="git">git</option>
<option value="intellij">intellij</option>
<option value="vscode">vscode</option>
</select><br><br>
<input type="button" value="Générer Configuration" onclick="generateYAML()">
</form>
<h3>Configuration YAML:</h3>
<textarea id="output" rows="30" cols="100"></textarea>
<script>
function getInstallCommand(version, tool) {
const packageManagers = {
'apt-get': 'apt-get install -y',
'dnf': 'dnf install -y',
'pacman': 'pacman -Syu --noconfirm',
'apk': 'apk add --no-cache'
};
let packageManager, installSnapd = '';
if (version.includes('ubuntu') || version.includes('debian')) {
packageManager = packageManagers['apt-get'];
if (tool === 'intellij' || tool === 'vscode') {
installSnapd = `${packageManager} snapd`;
}
} else if (version.includes('fedora')) {
packageManager = packageManagers['dnf'];
if (tool === 'intellij' || tool === 'vscode') {
installSnapd = `${packageManager} snapd`;
}
} else if (version.includes('arch')) {
packageManager = packageManagers['pacman'];
if (tool === 'intellij' || tool === 'vscode') {
// Installation spécifique de snapd pour Arch
installSnapd = 'pacman -Syu --noconfirm snapd && systemctl enable --now snapd.socket';
}
} else if (version.includes('alpine')) {
packageManager = packageManagers['apk'];
// Alpine n'a pas de support officiel pour snapd, donc on ne met pas de commande pour installer snapd ici
}
switch (tool) {
case 'docker':
return `${installSnapd}\n${packageManager} docker`;
case 'python':
return `${installSnapd}\n${packageManager} python3 python3-pip`;
case 'tmux':
return `${installSnapd}\n${packageManager} tmux`;
case 'git':
return `${installSnapd}\n${packageManager} git`;
case 'intellij':
case 'vscode':
// Assurez-vous que snapd est installé avant d'essayer d'utiliser snap pour installer ces outils
return `${installSnapd}\nsnap install ${tool === 'intellij' ? 'intellij-idea-community --classic' : 'code --classic'}`;
default:
return `${installSnapd}\necho "${tool} n'est pas reconnu. Veuillez installer manuellement."`;
}
}
function generateInstallScript(toolsArray, version) {
if (toolsArray.length === 0) {
return `echo "Aucun outil spécifique à déployer"`;
}
let installScript = '';
toolsArray.forEach(tool => {
installScript += getInstallCommand(version, tool) + "\n";
});
return installScript.trim();
}
function generateYAML() {
const version = document.getElementById('version').value;
const suffix = document.getElementById('suffix').value.trim();
const storage = document.getElementById('storage').value;
const toolsSelected = document.getElementById('tools').selectedOptions;
const toolsArray = Array.from(toolsSelected).map(option => option.value);
const memoryLimit = document.getElementById('memoryLimit').value;
const shmSize = document.getElementById('shmSize').value;
const proxyURL = document.getElementById('proxyAddress').value;
const proxyPort = document.getElementById('proxyPort').value;
// Construction de la section des variables d'environnement de base
let envVars = `
- name: PUID
value: '1000'
- name: LANG
value: 'fr_FR.UTF-8'
- name: PGID
value: '1000'
- name: TZ
value: 'Pacific/Noumea'
- name: LANGUAGE
value: 'fr_FR.UTF-8'
- name: LC_ALL
value: 'fr_FR.UTF-8'`;
// Ajout des variables de proxy si elles sont fournies
if (proxyURL && proxyPort) {
envVars += `
- name: HTTP_PROXY
value: "${proxyURL}:${proxyPort}"
- name: HTTPS_PROXY
value: "${proxyURL}:${proxyPort}"
- name: NO_PROXY
value: "localhost,.svc.cluster.local"`;
}
if (!suffix) {
alert("Veuillez entrer un suffixe.");
return; // Stoppe l'exécution de la fonction si le suffixe est vide
}
let imageName = `linuxserver/rdesktop:${version}`;
if (version === "alpine-xfce") {
imageName = "linuxserver/rdesktop:latest";
}
// Étape 1: Générer le Script d'Installation
const installScript = generateInstallScript(toolsArray, version);
const formattedInstallScript = installScript
.split('\n')
.filter(line => line.trim() !== '') // Enlève les lignes vides
.map(line => ` ${line}`) // Ajoute 4 espaces d'indentation
.join('\n');
// Étape 2: Intégrer le Script dans le ConfigMap
const yamlConfigMap = `
apiVersion: v1
kind: ConfigMap
metadata:
name: custom-script-${version}-${suffix}
namespace: guacamole
data:
install-script.sh: |+
#!/bin/bash
${formattedInstallScript}`;
const yaml = `---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
finalizers:
- kubernetes.io/pvc-protection
name: ${version}-${suffix}
namespace: guacamole
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: ${storage}
storageClassName: longhorn
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${version}-${suffix}
namespace: guacamole
spec:
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
selector:
matchLabels:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-${version}-${suffix}
template:
metadata:
namespace: guacamole
labels:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-${version}-${suffix}
spec:
containers:
- env:${envVars}
image: ${imageName}
imagePullPolicy: Always
livenessProbe:
failureThreshold: 5
periodSeconds: 60
successThreshold: 1
tcpSocket:
port: 3389
timeoutSeconds: 1
name: ${version}-${suffix}
ports:
- containerPort: 3389
name: rdp
protocol: TCP
readinessProbe:
failureThreshold: 5
periodSeconds: 60
successThreshold: 1
tcpSocket:
port: 3389
timeoutSeconds: 1
resources:
limits:
memory: ${memoryLimit}
requests:
memory: 1Gi
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
runAsNonRoot: false
startupProbe:
failureThreshold: 5
periodSeconds: 120
successThreshold: 1
tcpSocket:
port: 3389
timeoutSeconds: 1
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /config
name: vol-home
- mountPath: /dev/shm
name: vol-shm
- mountPath: /custom-cont-init.d
name: vol-init
readOnly: true
restartPolicy: Always
volumes:
- name: vol-home
persistentVolumeClaim:
claimName: ${version}-${suffix}
- emptyDir:
medium: Memory
sizeLimit: ${shmSize}
name: vol-shm
- configMap:
defaultMode: 420
name: custom-script-${version}-${suffix}
optional: false
name: vol-init
---
apiVersion: v1
kind: Service
metadata:
name: ${version}-${suffix}
namespace: guacamole
spec:
selector:
workload.user.cattle.io/workloadselector: apps.deployment-guacamole-${version}-${suffix}
ports:
- name: rdp
port: 3389
protocol: TCP
targetPort: 3389
sessionAffinity: None
type: ClusterIP
---` + yamlConfigMap;
document.getElementById('output').value = yaml;
}
</script>
</body>
</html>
Bonus : Activation de vos images Windows via un serveur KMS émulé
Les images par défaut sont des versions d’évaluation, il est donc compliqué de les activer directement. Il va falloir les transformer.
Pour ce faire, vous pouvez suivre ce tutoriel : https://digital-licence.com/convertir-la-version-devaluation-windows-10-enterprise-ltsc-en-version-complete/
Les clefs de licence à utiliser pour faire la transformation vers la version de Windows souhaitée (d’une version 10 home à une enterprise par exemple) se trouve sur : https://learn.microsoft.com/fr-fr/windows-server/get-started/kms-client-activation-keys
Une fois effectué, vous pouvez très bien vous servir de votre VM alpine pour émuler un serveur KMS. Pour ce faire, il y a un dépôt Git : https://github.com/Wind4/vlmcsd
Il vous suffit d’installer les outils sous alpine, de compiler et d’exécuter le serveur émulé :
apk add --no-cache git gcc make cmake build-base linux-headers
git clone https://github.com/Wind4/vlmcsd.git
make
./bin/vlmcsd
Votre serveur kms va tourner sur le port 1688.
Vous retournez alors sur votre VM Windows et vous indiquez le serveur KMS qui tourne sur Linux dans un terminal :
slmgr /skms alpine-xfce-pmietlicki.guacamole.svc.cluster.local
slmgr /ato
Ainsi votre serveur KMS va valider votre licence Windows, ce n’est à utiliser qu’à des fins de test ou éducatives bien évidemment.
Il y a même une version dockerisée si vous voulez faire tourner l’image en continue dans un deployment kubernetes par exemple : https://github.com/Wind4/vlmcsd-docker