Blog

Installing the Official NGINX OSS via Ansible Playbook

By Jay

Sep 10, 2023 | 7 minutes read

Series: HomeLab

Tags: blog, tech, nginx

In my last blog post, I discussed how you can install the official NGINX OSS package via script. Today, we’re going to walk through the steps required to leverage Ansible and the NGINX Ansible Collection to easily install, configure, upgrade, and remove NGINX.

This deployment uses Ubuntu and assumes that the user running all the commands is named ubuntu and has full passwordless sudo access. You can change this if you want, but that’s going to require some other bits being changed.

In order to run these tests you need to have an Azure account along with Terraform, Ansible, and and Azure CLI. There are a few different ways to go about this. My instructions below show how to do this with asdf, but you can use any package manager you wish. If you’re super hardcore, why not compile from source?

  1. Install latest ansible-base via asdf.
    asdf plugin add ansible-base
    asdf install ansible-base latest
    asdf global ansible-base latest
    
  2. Install latest terraform via asdf.
    asdf plugin add terraform
    asdf install terraform latest
    asdf global terraform latest
    
  3. Install latest azure-cli via asdf.
    asdf plugin add azure-cli
    asdf install azure-cli latest
    asdf global azure-cli latest
    
  4. Install the NGINX ansible collection depdency collections.
     ansible-galaxy collection install ansible.posix community.crypto community.general
    
  5. Install the NGINX ansible collection
    ansible-galaxy collection install nginxinc.nginx_core
    
  6. Install the NGINX role
    ansible-galaxy install nginxinc.nginx 
    
  1. Be sure to set your config versions for all the asdf bits you installed above using something like asdf global <command> <version>; if you have not used asdf for this make sure you can run all your utilities.
  1. Log into Azure via az login, which will setup the token used by Terraform.

  2. Change to ./infrastructure in the repository.

  3. Edit the variables.tf file to put your own values in for the location and the resource group prefix. Unless you’re me. If you’re me, you can use the defaults a shown below:

    variable "resource_group_location" {
    default     = "westus"
    description = "Location of the resource group."
    }
    
    variable "resource_group_name_prefix" {
    default     = "jay"
    description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription."
    }
    
  4. While still in that directory, initialize the terraform deployment with terraform init

    $ terraform init
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Finding hashicorp/tls versions matching "~> 4.0"...
    - Finding hashicorp/azurerm versions matching "~> 2.0"...
    - Finding hashicorp/random versions matching "~> 3.0"...
    - Installing hashicorp/tls v4.0.4...
    - Installed hashicorp/tls v4.0.4 (signed by HashiCorp)
    - Installing hashicorp/azurerm v2.99.0...
    - Installed hashicorp/azurerm v2.99.0 (signed by HashiCorp)
    - Installing hashicorp/random v3.4.3...
    - Installed hashicorp/random v3.4.3 (signed by HashiCorp)
    
    Terraform has created a lock file .terraform.lock.hcl to record the provider
    selections it made above. Include this file in your version control repository
    so that Terraform can guarantee to make the same selections by default when
    you run "terraform init" in the future.
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    
  1. To make sure that you have all the necessary variables in all the right places we can generate a test plan via terraform plan. I’ve hacked this output becuase there is no way you want to see all that here…
    $ terraform plan -out test.plan
    
    Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
    + create
    <---------- SNIP SNIP SNIP SNIP ---------->
    Terraform will perform the following actions:
    
    Changes to Outputs:
    + nginx_address           = (known after apply)
    + nginx_public_ip_address = (known after apply)
    + openssh_private_key     = (sensitive value)
    + resource_group_name     = (known after apply)
    
    ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: test.plan
    
    To perform exactly these actions, run the following command to apply:
    	terraform apply "test.plan"
    
  2. Provided there were no errors in the above, let’s do what the computer says we should do and apply the plan.
    $ terraform apply "test.plan"  
    random_pet.rg_name: Creating...
    tls_private_key.nginx_ssh: Creating...
    random_pet.rg_name: Creation complete after 0s [id=jay-inviting-panda]
    tls_private_key.nginx_ssh: Creation complete after 1s [id=6fc7234ad9d6678c4e277b4f1a758b6295f71173]
    azurerm_resource_group.rg: Creating...
    azurerm_resource_group.rg: Creation complete after 0s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda]
    azurerm_virtual_network.nginx_network: Creating...
    azurerm_public_ip.nginx_public_ip: Creating...
    random_id.random_id: Creating...
    random_id.random_id: Creation complete after 0s [id=jI6IkdP3RJo]
    azurerm_network_security_group.nginx_nsg: Creating...
    azurerm_storage_account.nginx_storage_account: Creating...
    azurerm_public_ip.nginx_public_ip: Creation complete after 3s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Network/publicIPAddresses/nginxPublicIP]
    azurerm_virtual_network.nginx_network: Creation complete after 5s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Network/virtualNetworks/nginxVnet]
    azurerm_subnet.nginx_subnet: Creating...
    azurerm_network_security_group.nginx_nsg: Creation complete after 5s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Network/networkSecurityGroups/nginxNetworkSecurityGroup]
    azurerm_subnet.nginx_subnet: Creation complete after 5s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Network/virtualNetworks/nginxVnet/subnets/nginxSubnet]
    azurerm_network_interface.nginx_nic: Creating...
    azurerm_storage_account.nginx_storage_account: Still creating... [10s elapsed]
    azurerm_network_interface.nginx_nic: Creation complete after 1s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Network/networkInterfaces/nginxNIC]
    azurerm_network_interface_security_group_association.nginx: Creating...
    azurerm_network_interface_security_group_association.nginx: Creation complete after 2s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Network/networkInterfaces/nginxNIC|/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Network/networkSecurityGroups/nginxNetworkSecurityGroup]
    azurerm_storage_account.nginx_storage_account: Still creating... [20s elapsed]
    azurerm_storage_account.nginx_storage_account: Creation complete after 24s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Storage/storageAccounts/diag8c8e8891d3f7449a]
    azurerm_linux_virtual_machine.nginx: Creating...
    azurerm_linux_virtual_machine.nginx: Still creating... [10s elapsed]
    azurerm_linux_virtual_machine.nginx: Still creating... [20s elapsed]
    azurerm_linux_virtual_machine.nginx: Still creating... [30s elapsed]
    azurerm_linux_virtual_machine.nginx: Still creating... [40s elapsed]
    azurerm_linux_virtual_machine.nginx: Creation complete after 48s [id=/subscriptions/9bf57fa0-f5be-4dab-8f4b-364e77f7930e/resourceGroups/jay-inviting-panda/providers/Microsoft.Compute/virtualMachines/nginxVM]
    
    Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    nginx_address = "10.0.1.4"
    nginx_public_ip_address = "20.245.80.251"
    openssh_private_key = <sensitive>
    resource_group_name = "jay-inviting-panda"
    
  1. The first step is to generate the necessary config for Ansible; we are going to do that using a convenience script located in the root directory called create-configs.sh.

    $ ./create-configs.sh
    Private key written to /Users/jschmidt/repos/nginx-ansible/files/nginx.pem
    SSH configuration written
    Configuration complete. Instructions for use:
    
    To SSH into the nginx server, add the private key to your SSH agent:
    ssh-add /Users/jschmidt/repos/nginx-ansible/files/nginx.pem
    
    Then, use SSH with the config file:
    ssh -F /Users/jschmidt/repos/nginx-ansible/files/nginx.ssh.config nginx
    
    To test the Ansible inventory, run:
    ansible-playbook -i /Users/jschmidt/repos/nginx-ansible/files/nginx.ansible.hosts /Users/jschmidt/repos/nginx-ansible/ansible/ansible-ping.yaml
    
    Ensure there are no errors for proper configuration.
    
    To install NGINX with ansible, run:
    ansible-playbook -i /Users/jschmidt/repos/nginx-ansible/files/nginx.ansible.hosts /Users/jschmidt/repos/nginx-ansible/ansible/deploy-oss.yaml
    
    To uninstall NGINX with ansible, run:
    ansible-playbook -i /Users/jschmidt/repos/nginx-ansible/files/nginx.ansible.hosts /Users/jschmidt/repos/nginx-ansible/ansible/uninstall-oss.yaml
    
    To upgrade NGINX with ansible, run:
    ansible-playbook -i /Users/jschmidt/repos/nginx-ansible/files/nginx.ansible.hosts /Users/jschmidt/repos/nginx-ansible/ansible/upgrade-oss.yaml	
    
  2. Now load the SSH key using the command supplied in the output:

     $ ssh-add /Users/jschmidt/repos/nginx-ansible/files/nginx.pem
    Identity added: /Users/jschmidt/repos/nginx-ansible/files/nginx.pem (/Users/jschmidt/repos/nginx-ansible/files/nginx.pem)
    
  3. Let’s test to make sure everything is where it’s supposed to be. Once again, we can use the command that was supplied in the output.

    $ ansible-playbook -i /Users/jschmidt/repos/nginx-ansible/files/nginx.ansible.hosts /Users/jschmidt/repos/nginx-ansible/ansible/ansible-ping.yaml
    
    PLAY [ping module demo] *********************************************************************************
    
    TASK [Gathering Facts] **********************************************************************************
    ok: [nginx]
    
    TASK [test connection] **********************************************************************************
    ok: [nginx]
    
    PLAY RECAP **********************************************************************************************
    nginx                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
  4. Now it’s time to install NGINX:

    $ ansible-playbook -i /Users/jschmidt/repos/nginx-ansible/files/nginx.ansible.hosts /Users/jschmidt/repos/nginx-ansible/ansible/deploy-oss.yaml
    
    PLAY [Install NGINX OSS] ********************************************************************************
    
    TASK [Gathering Facts] **********************************************************************************
    ok: [nginx]
    
    TASK [Set repo if Alpine] *******************************************************************************
    skipping: [nginx]
    
    TASK [Set repo if Debian] *******************************************************************************
    ok: [nginx]
    
    TASK [Set repo if Red Hat] ******************************************************************************
    skipping: [nginx]
    
    TASK [Set repo if SLES] *********************************************************************************
    skipping: [nginx]
    
    TASK [Install NGINX] ************************************************************************************
    
    TASK [nginxinc.nginx : Validate distribution and role variables] ****************************************
    included: /Users/jschmidt/.ansible/roles/nginxinc.nginx/tasks/validate/validate.yml for nginx
    <--- SNIP SNIP SNIP SNIP --->
    
    TASK [nginxinc.nginx : Install NGINX Amplify] ***********************************************************
    skipping: [nginx]
    
    RUNNING HANDLER [nginxinc.nginx : (Handler) Start logrotate] ********************************************
    ok: [nginx]
    
    RUNNING HANDLER [nginxinc.nginx : (Handler) Print logrotate error if config check fails] ****************
    skipping: [nginx]
    
    PLAY RECAP **********************************************************************************************
    nginx                      : ok=28   changed=8    unreachable=0    failed=0    skipped=29   rescued=0    ignored=0
    
  5. NGINX is now installed - provided there were no errors.

From this point, you can use NGINX as you would normally. This repository contains Ansible scripts that can be used to upgrade NGINX or uninstall NGINX. If you want to go deeper and do things like installing NGINX Plus, adding NGINX AppProtect, or configuring NGINX from Ansible why not hop over to Ansible Galaxy to review the available roles. Be sure to say hello to the NGINX Ansible Team (aka, Alessandro) when you visit!

If you’re not a fan of cut and paste, I’ve created a very bare-bones repo that hosts the files necessary to run this test at nginx-ansible.