Pull to refresh

«No Windows no problems» What?

Reading time 5 min
Views 4.1K

Hi, My name is Alex and I am a DevOps engineer at Altenar. “No Windows, no problems.” - that is the answer I got by asking a guru of Ansible "How do you manage Windows?" on one of the local Ansible meetups. Although we have been running a modern stack (k8s, helm, .net core, etc) in production for about two years, that’s not how it has always been.

Our first version of Sportsbook was Windows-based (.NET + MSSQL) and we managed 100+ servers via Ansible. In this article, I would like to share some insights we learned through years of managing Windows infrastructure via Ansible.  


Prepare servers for Ansible

Unlike Linux, Windows requires you to prepare for Ansible to connect to it (more details available here and all the details are out of the scope of this article). However, there is one key point I would like to stress here. The ConfigureRemotingForAnsible.ps1 script - that is pretty much what you need to run before you'll be able to connect to a VM via Ansible.

It's worth mentioning that we use Terraform for VM provisioning and this is where run_once_command_list becomes very handy. We saved ConfigureRemotingForAnsible.ps1 on the C:/ of the base VM template and used the following option in Terraform:

resource "vsphere_virtual_machine" "vm" {
  # ... other configuration ...

  clone {
    # ... other configuration ...

    customize {
      # ... other configuration ...

      windows_options {
        computer_name         = "${lower(var.envname)}${var.vmname}${format("%03d", count.index)}"
        organization_name     = "Altenar"
        join_domain           = "${var.domain}"
        domain_admin_user     = "${var.domain_user}"
        domain_admin_password = "${var.domain_password}"
        admin_password        = "${var.admin_password}"
        auto_logon            = true

        run_once_command_list = [
		    "powershell.exe -version 4 -ExecutionPolicy Bypass -File C:\\ConfigureRemotingForAnsible.ps1",
		]
      }
    }
  }
}

Once a new server is provisioned it is ready to be set up by Ansible according to the role we assign to the server.

Setting up Ansible

From the Ansible side, a few initial preparations are also required.

  1. Make sure all Windows servers belong to the "Windows" group in your inventory, we use groups but you can list each server individually here or automate the inventory creation process. Inventory management is out of this article scope, but it is well described in Ansible documentation:

    [windows:children]
    frontend
    client_api
    backoffice
  2. Create windows.yaml file in group_vars folder of each environment and/or in the main group_vars folder and add the following content:

    ansible_user: "{{ windows_user|default ('Administrator') }}"
    ansible_password: "{{ windows_password }}"
    ansible_port: 5986
    ansible_connection: winrm
    ansible_winrm_transport: credssp
  3. Include this role as dependent into the meta/main.yaml file of any of the roles you are planning to execute, for instance:

    # cat roles/infra/IIS/meta/main.yml
    ---
    dependencies:
      - { role: infra/fetch-credentials/windows }
    

    This role checks appropriate environment variables and fails if a Username or Password is not provided. These environment variables are then copied to Ansible variables and used in the settings we defined in p2.

  4. At this time, we have our servers configured and Ansible set up. We are good to go, the only thing left is actually setting up Windows username and password.

    1. You can do it using export WINDOWS_USERNAMEand export WINDOWS_PASSWORD commands:

      export WINDOWS_USERNAME=Administrator
      export WINDOWS_PASSWORD=******
    2. or if you have more than one set of credentials you can use a small bash script-helper:

      # source set_env.sh
      Set Value for WINDOWS_DOMAIN_USER: Administrator
      Set Value for WINDOWS_DOMAIN_PASSWORD:
      Set Value for WINDOWS_USER: Administrator
      Set Value for WINDOWS_PASSWORD:

Our preparations are done. We are ready to start Ansibling.

Windows tips and tricks

Ansible has much fewer modules for Windows than for Linux. There are 104 modules for windows out of 2832 in total yet it is enough for most of the common cases. But sometimes we have to be creative.

DSL is your friend

The first thing to remember - DSL is your friend. There are many DSL modules for windows, for example using this module you can install MSSQL by just one task:

- name: Configure SqlSetup
  win_dsc:
    resource_name: SqlSetup
    ForceReboot: False
    UpdateEnabled: False
    SourcePath: "{{ disk_image_out.mount_path|default(database_settings.mssql_installation_source) }}"
    InstanceName: "{{ db_settings.instance_name|upper }}"
    Features: "{{ sql_setup_components }}"
    InstallSharedDir: "{{ db_settings.instance_dir_drive }}Program Files\\Microsoft SQL Server"
    InstallSharedWOWDir: "{{ db_settings.instance_dir_drive }}Program Files (x86)\\Microsoft SQL Server"
    InstanceDir: "{{ db_settings.instance_dir_drive }}SQL"
    SQLCollation: 'Latin1_General_CI_AS'
    SQLSvcAccount_username: "{{ ad_settings_domain }}\\{{ db_settings.sqlsvcaccount }}$"
    SQLSvcAccount_password: '***'
    AgtSvcAccount_username: "{{ ad_settings_domain }}\\{{ db_settings.agtsvcaccount }}$"
    AgtSvcAccount_password: '***'
    SQLSysAdminAccounts: "{{ ad_settings_domain|upper }}\\SQL Administrators"
    InstallSQLDataDir:  "{{db_settings.data_dir_drive}}SQL\\{{ db_settings.instance_name|upper }}\\MSSQL\\Data"
    SQLUserDBDir:       "{{db_settings.data_dir_drive}}SQL\\{{ db_settings.instance_name|upper }}\\MSSQL\\Data"
    SQLUserDBLogDir:    "{{db_settings.log_dir_drive }}SQL\\{{ db_settings.instance_name|upper }}\\MSSQL\\Log"
    SQLTempDBDir:       "{{db_settings.temp_dir_drive}}SQL\\{{ db_settings.instance_name|upper }}\\MSSQL\\Data\\Tempdb"
    SQLTempDBLogDir:    "{{db_settings.temp_dir_drive}}SQL\\{{ db_settings.instance_name|upper }}\\MSSQL\\Log\\Tempdb"
    SQLBackupDir:       "{{db_settings.backup_dir_drive}}SQL\\{{ db_settings.instance_name|upper }}\\MSSQL\\Backup"
    PsDscRunAsCredential_username: "{{ad_settings_adminuser}}@{{ad_settings_domain}}"
    PsDscRunAsCredential_password: "{{ad_settings_adminpassword}}"
  tags:
  - install_db

Use the win_psmodule first to install the required DSC modules on the server first:

- name: install DSC Modules
  win_psmodule:
    name: "{{ item }}"
    state: present
  with_items:
    - "PSDesiredStateConfiguration"
    - "SqlServerDsc"
    - "ServerManager"
    - "StorageDsc"
    - "ComputerManagementDsc"

Another big advantage of DSC is that you can run it as a different user, you could've spotted "PsDscRunAsCredential_username" and "PsDscRunAsCredential_password" directives on the example above. It especially helps when some tasks have to be run from the Domain Administrator account.

DIY modules

Didn't find an appropriate DSC module? Not a big deal, there are two possible workarounds (apart from the obvious - using win_shell ansible module):

  1. Use DSC resource "Script":

    #Part of MSSQL setup playbook. Here we test if created service accounts 
    #have already been added to the server from AD
    #if not, we reboot the servers first.
    - name: Configure and Test the gMSA account
      win_dsc:
        resource_name: Script
        GetScript: "@{ Result = '{{ item }}'| Use-ServiceAccount -Test }"
        TestScript: "(iwr https://raw.githubusercontent.com/beatcracker/Powershell-Misc/master/Use-ServiceAccount.ps1 -UseBasicParsing).Content | iex; '{{ item }}'| Use-ServiceAccount -Test"
        SetScript: '$global:DSCMachineStatus = 1'
        PsDscRunAsCredential_username: "{{ad_settings_adminuser}}@{{ad_settings_domain}}"
        PsDscRunAsCredential_password: "{{ad_settings_adminpassword}}"
      with_items:
        - "{{ db_settings.sqlsvcaccount }}"
        - "{{ db_settings.agtsvcaccount }}"
      register: TestADServiceAccount_result

    Ansible is an imperative tool, so we have to make sure that any task can be executed multiple times providing us with the same result. Play with "GetScrypt", "TestScrypt" and "SetScrypt" fields according to your needs. Having it played right should allow you to be able to identify the current state of the resource, change it if required and pass if not.

  2. Create a module yourself. It's not that hard, the only difference with making a module for Linux is you need to write code on PowerShell. We encountered a problem where there was no module KDS Root Key creation which is required for MSSQL installation. So we created it. Look here for more details regarding win module development.

Conclusion

I've provided the key aspects related to Ansible usage with Windows. Having Windows servers configured for Ansible we can manage them nearly the same way as we manage Linux servers using Ansible. By using the approaches described in this article we successfully manage Windows servers for jobs like:

  • MSSQL AG cluster installation and update.

  • IIS configuration.

  • Windows services failover cluster configuration.

  • A/B deployment.

I hope it was useful or at least entertaining.

Tags:
Hubs:
+5
Comments 3
Comments Comments 3

Articles