Ansible : i3-wm Installation auf einem neuen Client

Ich hab hier ein bisschen gebastelt um meine ansible skills nicht zu verlieren.

Das Ansible installiert i3-wm mit meinen default configs ;-) wer möchte könnte auch zusätzlich polybar installieren, default ist i3-blocks. Mehr ist im Repo erklärt

https://github.com/Mokkujin/i3-config-ansible

terraform & timestamp

ich möchte in unseren automatisch Deployments immer das Datum des Vorgang in der virtuellen Kiste haben. Ich hab das erreicht indem ich Funktionen timestamp und formatdate von terraform verwende.

In den Variablen für cloud-init verwende ich :
vars = {
                hostname = "${var.vsphere_name}${var.vsphere_dom}"
                deploy_date = formatdate ("DD'.'MM'.'YYYY hh:mm ZZZ",timestamp())
        }
weiterführender Link : terraform & cloud-init & vmware
Quelle :
https://www.terraform.io/docs/configuration/functions/timestamp.html
https://www.terraform.io/docs/configuration/functions/formatdate.html

terraform & cloud-init : change root password

um bei einem automatischen deployment mit terraform & cloud-init das root passwort zu ändern einfach im cloud-init bereich chpasswd einfügen.

chpasswd:
  list: |
     root:$6$hNnmxD4HhIeX4IlB$pLkby1WsEbTIRnA9hg8UtSAhoG0Gd4eFF5HUxam8x5O4Ch4KgA62lW/4Ora12fkczq3OCXzMHdj7jnpzUTJDb/

zuvor den password hash mit python erstellen.
python3 -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
Password:
$6$hNnmxD4HhIeX4IlB$pLkby1WsEbTIRnA9hg8UtSAhoG0Gd4eFF5HUxam8x5O4Ch4KgA62lW/4Ora12fkczq3OCXzMHdj7jnpzUTJDb/
weiterführender Link : terraform & cloud-init & vmware
Quelle : Red Hat - setting up cloud-init

Terraform & cloud-init & vmware

Automatisiertes Deployment von virtuellen Maschinen in eine VMWare Umgebung mit Hilfe von Terraform und cloud-init.

als wird ein Terraform Konfiguration benötigt. Ich habe das alles in 3 Dateien aufgeteilt.
  • main.tf
  • terraform.tfvars
  • variables.tf
zuerst erstellen wir die Datei variables.tf um warm zu werden ;-)
variable "vsphere_server" {}
variable "vsphere_user" {}
variable "vsphere_password" {}
variable "vsphere_name" {}
variable "vsphere_dom" {}
variable "vsphere_datacenter" {}
variable "vsphere_datacenter_folder" {}
variable "vsphere_datastore" {}
variable "vsphere_resource_pool" {}
variable "vsphere_network" {}
variable "vsphere_template" {}
variable "vsphere_guest_id" {}
dann setzen wir die Variablen in der neuen Datei terraform.tfvars
# define the vsphere server
vsphere_server = "dev-vm-test-cl-01.demo.local"
# the username to access (use \\ instead \)
vsphere_user = "vsphere.local\\dev"
# password for the user
vsphere_password = "..................."
# name of the guest system
vsphere_name = "demo-vm"
# domain name 
vsphere_dom = ".demo.local"
# datacenter in vmware
vsphere_datacenter = "demo-dc"
# if you use folders define here
vsphere_datacenter_folder = "DEMO"
# datastore to store the machine
vsphere_datastore = "DEMOSAN"
# resource pool 
vsphere_resource_pool = "DEMO"
# the name of the network who the machine should use
vsphere_network = "DEMO-NETZ"
# which template should terraform use to build the machine ?
vsphere_template = "CENTOS-7-DEMO"
# define the guest id
vsphere_guest_id = "centos7_64Guest"
um das deploment durchzuführen benötigen wir noch einen user für die Anmeldung am Gast den wir in der main.tf definieren können hierzu erstellen wir uns erstmal den passwort hash. Ich hab hier mal einfach TEST genommen für ein produktives Deployment nicht zu empfehlen !
python3 -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'
Password:
xxxxxxxxxxxxxxxxxxxxx1Q0T3BfTTFcA.T7byqXbNyyyyyyyyyyyyyyyyyyy
jetzt kümmern wir uns um die main.tf die das eigentliche deployment übernimmt. Die user-data und meta-data werden der Maschine direkt als Template mitgegeben, für mehr Informationen zu cloud-init könnt ihr hier die Doku lesen.
provider "vsphere" {
        vsphere_server = "${var.vsphere_server}"
        user = "${var.vsphere_user}"
        password = "${var.vsphere_password}"
        allow_unverified_ssl= true
}

data "vsphere_datacenter" "dc" {
         name = "${var.vsphere_datacenter}"
}

data "vsphere_datastore" "datastore" {
        name = "${var.vsphere_datastore}"
        datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_resource_pool" "pool" {
  name          = "${var.vsphere_resource_pool}"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_network" "network" {
  name          = "${var.vsphere_network}"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "vsphere_virtual_machine" "template" {
  name          = "${var.vsphere_template}"
  datacenter_id = "${data.vsphere_datacenter.dc.id}"
}

data "template_file" "meta_init" {
        template = <<EOF
{
        "local-hostname": "$${local_hostname}"
}
EOF

vars = {
        local_hostname = "${var.vsphere_name}${var.vsphere_dom}"
}
}

data "template_file" "cloud_init" {
        template = <<EOF
#cloud-config
# set hostname & keyboard layout
bootcmd:
  - [ sh, -c, "echo 'DEPLOY-DATE-TIME - $${deploy_date}' > /cloud-init-info.txt"]
  - [ localectl, set-keymap, de ]
  - [ hostnamectl, set-hostname, $${hostname} ]
  - [ setenforce, 0 ]
  - [ sed, -i, 's/enforcing/disabled/g', /etc/selinux/config ]
growpart:
  mode: auto
  devices: ['/']
  ignore_growroot_disabled: false
users:
  - name: demo
    groups: wheel
    lock_passwd: false
    passwd: xxxxxxxxxxxxxxxxxxxxx1Q0T3BfTTFcA.T7byqXbNyyyyyyyyyyyyyyyyyyy
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh-authorized-keys:
      - ssh-rsa AAAAB................. DEPLOY@HOST
# set root password 
chpasswd:
  list: |
     root:xxxxxxxxxxxxxxxx4IlB$pLkby1WsExxxxxxxxxxxxxxxx
EOF
        vars = {
                hostname = "${var.vsphere_name}${var.vsphere_dom}"
                deploy_date = formatdate ("DD'.'MM'.'YYYY hh:mm ZZZ",timestamp())
        }
}

resource "vsphere_virtual_machine" "vm" {
  name             = "${var.vsphere_name}${var.vsphere_dom}"
  resource_pool_id = "${data.vsphere_resource_pool.pool.id}"
  datastore_id     = "${data.vsphere_datastore.datastore.id}"
  folder           = "${var.vsphere_datacenter_folder}"
  num_cpus = 4
  memory   = 8096
  guest_id = "${var.vsphere_guest_id}"

  network_interface {
    network_id = "${data.vsphere_network.network.id}"
  }

  disk {
    label = "disk0"
    size  = 150
  }

  clone {
    template_uuid = "${data.vsphere_virtual_machine.template.id}"
  }

  extra_config = {
        "guestinfo.metadata" = "${base64gzip(data.template_file.meta_init.rendered)}"
        "guestinfo.metadata.encoding" = "gzip+base64"
        "guestinfo.userdata" = "${base64gzip(data.template_file.cloud_init.rendered)}"
        "guestinfo.userdata.encoding" = "gzip+base64"
        }
}
Terrafom & cloud-init wäre dann fertig , jetzt müssen wir uns noch ein Image bauen. Ich habe dafür das CentOS-7-x86_64-GenericCloud.qcow2 Image von CentOs heruntergeladen. Und wie hier beschrieben konvertiert. Für die Anpassung des Templates musste ich mir eine extra cloud-config schreiben die nur eins macht, einen Benutzer mit login anlegen ;-) Dazu habe ich mir unter Ubuntu das Packet cloud-utils installiert. Dann erstellst du eine Datei config.yaml mit diesem Inhalt.
#cloud-init
users:
  - name: image
    groups: wheel
    lock_passwd: false
    passwd: $6$..............................zVjyZr4fj1
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh-authorized-keys:
      - DEIN SSH KEY
dann bauen wir daraus ein ISO File
cloud-localds config.iso config.yaml
Jetzt muss die konvertierte vmdk auf den Datastore hochgeladen werden. Dann eine neue Maschine anlegen und als Festplatte die vorhandene vmdk Datei auswählen, in das CDROM die hochgeladene iso Datei einlegen und verbinden (auch beim einschalten). Beim Bootvorgang sieht cloud-init die cd und erkennt die user-data darauf. Es wird ein User angelegt der zuvor definiert wurde. Ich lasse das ISO auf dem Datastore für kommende Images die ich erstellen muss. Wir ihr das Passwort erstellt habe ich ja bereits weiter oben erklärt.Nach dem erstellen der VM anmelden und dann installieren wir im system noch open-vm-tools und cloud-init-vmware-guestinfo
yum install open-vm-tools
Jetzt machen wir die Kiste noch sauber das man ein sauberes Image ziehen kann. Das ist bereits hier schön erklärt. Wenn du damit fertig bist die virtuelle Maschine ausschalten und in ein Template klonen, am besten nimmst du als Namen für das Template CENTOS-7-DEMO. Jetzt sollte wenn du bootest ein neuer Benutzer angelegt werden mit dem Namen und dem Passwort TEST. selinux sollte ausgeschaltet sein usw....

Quellen :
https://stafwag.github.io/blog/blog/2019/03/03/howto-use-centos-cloud-images-with-cloud-init/
https://superuser.com/questions/1307260/convert-qcow2-image-to-vsphere-vmdk
https://stackoverflow.com/questions/37794846/convert-qcow2-to-vmdk-and-make-it-esxi-6-0-compatible
https://github.com/vmware/cloud-init-vmware-guestinfo
https://github.com/vmware/simple-k8s-test-env/tree/master/e2e
https://blah.cloud/infrastructure/using-cloud-init-for-vm-templating-on-vsphere/
https://community.spiceworks.com/how_to/151558-create-a-rhel-centos-6-7-template-for-vmware-vsphere
https://cloudinit.readthedocs.io/en/latest/

Ansible : auflisten der remote folder

Hier werden alle remote Ordner aufgelistet unter dem vorgegeben Remote Pfad {{ dst_path }}
    - name: get directories
      shell: ls -1 "{{ dst_path }}"
      register: folders_test
anzeigen der ermittelten Ordner
    - name: show directories
      debug:
        msg: "Folder {{ item }}"
      with_items: "{{ folders_test.stdout_lines }}"

Ansible : Schleifen realisieren

Der Eintrag dient mir hauptsächlich als Gedächtnisstütze, braucht man nicht jeden Tag :-)

Eine einfache Schleife mit einem Counter von 1 bis 4 kann man so realisieren.
- hosts: localhost
  tasks:
    - name: loop test
      debug:
        msg: "{{ item }}"
      with_sequence: count=4
Liefert dann
TASK [loop test] ***************************************************************************************************
ok: [localhost] => (item=1) => {
    "msg": "1"
}
ok: [localhost] => (item=2) => {
    "msg": "2"
}
ok: [localhost] => (item=3) => {
    "msg": "3"
}
ok: [localhost] => (item=4) => {
    "msg": "4"
}

Verschachtelte Schleifen mit 2 dics kann man so realisieren (in diesem Beispiel wurden Benutzer die Rechte auf MySQL Datenbanken eingetragen)
- hosts: localhost
  tasks:
    - name: loop test
      debug:
        msg: "name {{ item[0] }} db : {{ item[1] }}.*:ALL"
      with_nested:
        - [ 'user01', 'user02' ]
        - [ 'db01', 'db02', 'db03' ]
Liefert dann
TASK [loop test] ***************************************************************************************************
ok: [localhost] => (item=[u'user01', u'db01']) => {
    "msg": "name user01 db : db01.*:ALL"
}
ok: [localhost] => (item=[u'user01', u'db02']) => {
    "msg": "name user01 db : db02.*:ALL"
}
ok: [localhost] => (item=[u'user01', u'db03']) => {
    "msg": "name user01 db : db03.*:ALL"
}
ok: [localhost] => (item=[u'user02', u'db01']) => {
    "msg": "name user02 db : db01.*:ALL"
}
ok: [localhost] => (item=[u'user02', u'db02']) => {
    "msg": "name user02 db : db02.*:ALL"
}
ok: [localhost] => (item=[u'user02', u'db03']) => {
    "msg": "name user02 db : db03.*:ALL"
}

Jetzt gibt es noch die Möglichkeit mit inner und outer loop zu arbeiten. Gestartet wird dann das playbook outer_loop.yaml

outer_loop.yaml
- hosts: localhost
  tasks:
    - include_tasks: inner_loop.yaml
      with_sequence: count=4
      loop_control:
        loop_var: IDX
inner_loop.yaml
- debug:
    msg: "create directory /opt/{{ item}}-{{ IDX }}"
  with_items:
    - dic1
    - dic2
    - dic3
Das liefert dann
TASK [debug] *******************************************************************************************************
ok: [localhost] => (item=dic1) => {
    "msg": "create directory /opt/dic1-1"
}
ok: [localhost] => (item=dic2) => {
    "msg": "create directory /opt/dic2-1"
}
ok: [localhost] => (item=dic3) => {
    "msg": "create directory /opt/dic3-1"
}

TASK [debug] *******************************************************************************************************
ok: [localhost] => (item=dic1) => {
    "msg": "create directory /opt/dic1-2"
}
ok: [localhost] => (item=dic2) => {
    "msg": "create directory /opt/dic2-2"
}
ok: [localhost] => (item=dic3) => {
    "msg": "create directory /opt/dic3-2"
}

TASK [debug] *******************************************************************************************************
ok: [localhost] => (item=dic1) => {
    "msg": "create directory /opt/dic1-3"
}
ok: [localhost] => (item=dic2) => {
    "msg": "create directory /opt/dic2-3"
}
ok: [localhost] => (item=dic3) => {
    "msg": "create directory /opt/dic3-3"
}

TASK [debug] *******************************************************************************************************
ok: [localhost] => (item=dic1) => {
    "msg": "create directory /opt/dic1-4"
}
ok: [localhost] => (item=dic2) => {
    "msg": "create directory /opt/dic2-4"
}
ok: [localhost] => (item=dic3) => {
    "msg": "create directory /opt/dic3-4"
}

Quellen :
Ansible Documentation - Loops
Ansible Documentation - Complex Loops
“Das einzig sichere System müsste ausgeschaltet, in einem versiegelten und von Stahlbeton ummantelten Raum und von bewaffneten Schutztruppen umstellt sein.”
Gene Spafford (Sicherheitsexperte)