Tag: iac

  • Infrastructure as Code (IaC) Security Best Practices

    Infrastructure as Code (IaC) Security Best Practices

    Infrastructure as Code (IaC) has revolutionized how we provision and manage infrastructure, enabling speed, repeatability, and scalability. However, as with any code, IaC introduces new security considerations. Misconfigurations, exposed secrets, and lack of visibility can lead to significant risks in production environments.

    This guide explores actionable best practices for securing your Terraform, Ansible, and other IaC pipelines-helping you build robust, compliant, and resilient infrastructure. Sample code and how-to notes included!


    1. Treat IaC Like Application Code

    Just as you would with application code, store your IaC in version control systems (e.g., Git). This enables:

    • Change tracking: Who changed what, when, and why.
    • Peer reviews: Enforce code reviews and approvals before merging.
    • Rollback: Revert to previous known-good states if issues arise.

    Tip:
    Use branch protection rules and require pull request reviews for all changes.


    2. Secrets Management

    Never hard-code secrets or sensitive data (API keys, passwords, certificates) in your IaC files. Instead:

    • Use secret management tools like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.
    • Integrate secrets into your pipelines at runtime, not in code.
    • Leverage environment variables or encrypted files for sensitive values.

    Sample: Fetching Secrets from Vault in Terraform

    provider "vault" {
      address = "https://vault.example.com"
    }
    
    resource "vault_generic_secret" "example" {
      path = "secret/data/myapp"
    }
    
    output "db_password" {
      value = vault_generic_secret.example.data["password"]
    }

    How-to:

    • Replace https://vault.example.com with your Vault server address.
    • The vault_generic_secret resource fetches secrets dynamically.
    • Output blocks can reference secrets securely-never hardcode them!

    Sample: Encrypting Secrets in Ansible with ansible-vault

    - hosts: all
      vars_files:
        - secrets.yml
      tasks:
        - name: Use secret password
          debug:
            msg: "The password is {{ secret_password }}"

    How-to:

    • Create and encrypt the secrets file: ansible-vault create secrets.yml
    • Reference the encrypted file in your playbook under vars_files.
    • Access secrets in tasks using Jinja2 templating, e.g., {{ secret_password }}.

    3. Automated Security Scanning

    Integrate security tools into your CI/CD pipeline to catch misconfigurations and vulnerabilities early:

    Sample: Terraform Security Scanning with tfsec in GitHub Actions

    name: Terraform Security Scan
    
    on: [push]
    
    jobs:
      tfsec:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - name: Run tfsec
            uses: aquasecurity/tfsec-action@v1
            with:
              tfsec_version: 'latest'

    How-to:

    • Add this workflow to your .github/workflows directory.
    • The scan will fail the build if critical security issues are found, helping you catch problems early.

    4. Principle of Least Privilege

    Ensure that your IaC tools, pipelines, and the resources they provision follow the principle of least privilege:

    • Limit IAM permissions for automation accounts.
    • Avoid using overly broad roles or root accounts.
    • Regularly audit permissions and remove unnecessary access.

    5. Policy as Code

    Define and enforce security and compliance policies programmatically:

    • Use tools like Open Policy Agent (OPA), Sentinel (for Terraform Cloud), or Conftest.
    • Enforce rules such as “no public S3 buckets,” “encryption enabled,” or “no hardcoded credentials.”

    Example:
    OPA policies can block deployments that violate security standards before they reach production.


    6. Continuous Monitoring and Drift Detection

    Even after deployment, infrastructure can drift from the desired state:

    • Use tools like Terraform Cloud, AWS Config, or Driftctl to detect and remediate drift.
    • Set up alerts for unauthorized changes and automate remediation where possible.

    7. Regular Reviews and Updates

    IaC modules and dependencies evolve-so should your security practices:

    • Schedule regular reviews of your IaC codebase and third-party modules.
    • Update modules to patch vulnerabilities and leverage new security features.
    • Document your security practices and train your team.

    Conclusion

    Securing Infrastructure as Code is not a one-time task, but a continuous process woven into your development and deployment lifecycle. By treating IaC like application code, managing secrets securely, automating scanning, enforcing least privilege, and using policy as code, you can drastically reduce risk and build trust in your automation.

  • Using Ansible to run Terraform plans

    Yes, your eyes are not deceiving you 🙂 Usually, when the words Ansible and Terraform appear in the same sentence is for the opposite scenario. Many articles out there provide examples of using Terraform to deploy infrastructure followed by running Ansible to configure it using the local-exec provisioner. This is a good thing because, if you need some post-provisioning configuration, it is better to rely on another declarative and idempotent tool like Ansible, rather than invoking shell scripts.

    But in this post we are not going to do any of that. What we are going to do is the other way around. We are going to have an Ansible playbook that runs your Terraform plan.

    This article is a follow up from a series of posts to introduce infrastructure admins and engineers to Terraform and to explain how to use it to manage on-prem physical infrastructure in a datacenter. You can find access the full series here.

    Reasons to do it

    But why? Well, if you have an physical infrastructure background and are running a traditional datacenter more than likely you have been playing with Ansible. Chances are you started with Ansible running in the command line and hard-coding your infrastructure’s credentials inside the playbook. Then you evolved into using an external credentials file and encrypting it using “ansible-vault”. Eventually you might have deployed Ansible AWX or even RedHat AAP. At this point, you are enjoying RBAC (who can run what playbook and on what infrastructure) and amazing (there is no other way to put it) credential management.

    Then, you talk to your developers over coffee and they tell you about Terraform. But when you get familiar with it, you discover that unless you pay for a SaaS offering you are stuck in the “command-line with hard-coded credentials” scenario. Wouldn’t it be great if you could use AWX to run Terraform? You could enjoy RBAC and credential management and all the other goodness that it provides. This is possible thanks to Ansible’s Terraform module.

    First steps

    Let’s say we have a very basic Terraform project to create a storage group in Dell PowerMax. The “main.tf” shown below starts the PowerMax provider and then creates a storage group “terraform_sg”. I don’t think it is strictly necessary, but if you are interested in learning more about provisioning storage in Dell PowerMax you can check out this previous post. For simplicity we haven’t created or referenced any variables. All the information is contained into a single file, the “main.tf” shown below.

    terraform {
      required_providers {
        powermax = {
          source = "dell/powermax"
        }
      }
    }
    
    provider "powermax" {
      username      = "smc"
      password      = "TFp2ssw0rd"
      endpoint      = "https://10.1.2.3:8443"
      serial_number = "000123456789"
      pmax_version  = 100
      insecure      = true
    }
    
    resource "powermax_storagegroup" "tf_sg" {
      name          = "terraform_sg"
      srp_id         = "SRP_1"
    }

    In the same machine we have both Terraform and Ansible installed. In order to run the above Terraform plan with Ansible, we need at a minimum a playbook that looks like follows. It needs to include two mandatory parameters: “project_path” and “state”. The first parameter specifies the directory that contains the plan, ie the “main.tf” and typically any other files like “variables.tf”. My Ansible playbook is called “runtf.yml”.

    ---
    - hosts: localhost
      gather_facts: no
    
      vars:
        project_dir: "/root/powermax" # folder that contains main.tf, ...
    
      tasks:
        - name: Run terraform
          community.general.terraform:
            project_path: '{{ project_dir }}'
            state: present
          register: output
    
        - debug:
            var: output

    As you can see I have registered the output of the terraform task and then created a second task to print it out on the terminal so that we can get more details. All is left now is to run it using the “ansible-playbook” command

    root@alb-terraform:~# ansible-playbook runtf.yml
    
    PLAY [localhost] ***************************************************************************************
    
    TASK [Run terraform] ***************************************************************************************
    changed: [localhost]
    
    TASK [debug] ***************************************************************************************
    ok: [localhost] => {
        "output": {
            "changed": true,
            "command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/tmpjaojemdl.tfplan",
            "failed": false,
            "outputs": {},
            "state": "present",
            "stderr": "",
            "stderr_lines": [],
            "stdout": "powermax_storagegroup.tf_sg: Creating...\npowermax_storagegroup.tf_sg: Creation complete after 0s [id=terraform_sg]\n\nApply complete! Resources: 1 added, 0 changed, 0 destroyed.\n",
            "stdout_lines": [
                "powermax_storagegroup.tf_sg: Creating...",
                "powermax_storagegroup.tf_sg: Creation complete after 0s [id=terraform_sg]",
                "",
                "Apply complete! Resources: 1 added, 0 changed, 0 destroyed."
            ],
            "workspace": "default"
        }
    }
    
    PLAY RECAP ***************************************************************************************
    localhost  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    ignored=0

    Ansible run Terraform, and Terraform managed to make the changes so as expected, Ansible reports the task as “changed”. In the “debug” task, notice the “command” parameter. This is how Ansible is invoking Terraform. This invocation includes the “-auto-approve” flag so that Terraform doesn’t look for confirmation. The “stdout” and stdout_lines” parameters show the output that has been captured from Terraform about the creation of the different resources .

    A very important feature of both Ansible and Terraform is idempotency, ie the ability to run the same code repeatedly without making any damage. Let’s run it again and observe what happens.

    root@alb-terraform:~# ansible-playbook runtf.yml
    
    PLAY [localhost] ***************************************************************************************
    
    TASK [Run terraform] ***************************************************************************************
    ok: [localhost]
    
    TASK [debug] ***************************************************************************************
    ok: [localhost] => {
        "output": {
            "changed": false,
            "command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/tmp718kkwbo.tfplan",
            "failed": false,
            "outputs": {},
            "state": "present",
            "stderr": "",
            "stderr_lines": [],
            "stdout": "powermax_storagegroup.tf_sg: Refreshing state... [id=terraform_sg]\n\nNo changes. Your infrastructure matches the configuration.\n\nTerraform has compared your real infrastructure against your configuration\nand found no differences, so no changes are needed.\n",
            "stdout_lines": [
                "powermax_storagegroup.tf_sg: Refreshing state... [id=terraform_sg]",
                "",
                "No changes. Your infrastructure matches the configuration.",
                "",
                "Terraform has compared your real infrastructure against your configuration",
                "and found no differences, so no changes are needed."
            ],
            "workspace": "default"
        }
    }
    
    PLAY RECAP ***************************************************************************************
    localhost : ok=2    changed=0    unreachable=0    failed=0    skipped=0     ignored=0
    

    As expected, Ansible is reporting “ok”, which means the current state matched the desired state and therefore no changes were made. It has also captured the relevant from Terraform stating the same thing.

    Using variables

    This is a good start but ideally we would like to have the ability to make the plan more flexible by using variables for information such as the storage group name in this case. We can modify the “main.tf” as follows. Notice how the “default” value of the variable is commented out. It doesn’t matter if it is commented or not because when we invoke Terraform with external variables we overwrite the default value defined in the variable blocks.

    terraform {
      required_providers {
        powermax = {
          source = "dell/powermax"
        }
      }
    }
    
    provider "powermax" {
      username      = "smc"
      password      = var.password
      endpoint      = "https://10.1.2.3:8443"
      serial_number = "000123456789"
      pmax_version  = 100
      insecure      = true
    }
    
    variable "sg_name" {
     description = "Name of volume to create"
     type        = string
     #default     = "terraform_sg"
    }
    
    variable "password" {
      type        = string
      description = "Stores the password of Unisphere."
      #default = ""
    }
    
    resource "powermax_storagegroup" "tf_sg" {
      name          = var.sg_name
      srp_id         = "SRP_1"
    }

    Additionally, notice how in the “provider” block we have also referenced another variable for the “password”. For security reasons we might want to have, sensitive information like that, stored in a separate file and protect it. In this case we haven’t even declared a default value because we can leverage ansible-vault or AWX to encrypt it and to pass it to Terraform at run time.

    Now, the corresponding Ansible playbook needs to use the “variables” parameter. This parameter essentially converts each of the variables into a “-var” flag when invoking Terraform.

    ---
    - hosts: localhost
      gather_facts: no
    
      vars:
        project_dir: "/root/powerstore" # folder that contains main.tf, ...
    
      tasks:
        - name: Run terraform
          community.general.terraform:
            project_path: '{{ project_dir }}'
            state: present
            variables:
              sg_name: "{{ vol_name }}"
              password: "{{ password }}"
          register: output
    
        - debug:
            var: output

    Now, at runtime we are going to select a different storage group name. The storage group is created as expected and the relevant Terraform output is captured.

    root:~# ansible-playbook runtf.yml --extra-vars "sg_name=tf-sg2 password=TFp2ssw0rd"
    
    PLAY [localhost] ***************************************************************************************
    
    TASK [Run terraform] ***************************************************************************************
    changed: [localhost]
    
    TASK [debug] ***************************************************************************************
    ok: [localhost] => {
        "output": {
            "changed": true,
            "command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/tmpimdwzdu1.tfplan",
            "failed": false,
            "outputs": {},
            "state": "present",
            "stderr": "",
            "stderr_lines": [],
            "stdout": "powermax_storagegroup.tf_sg: Creating...\npowermax_storagegroup.tf_sg: Creation complete after 5s [id=vol1]\n\nApply complete! Resources: 1 added, 0 changed, 0 destroyed.\n",
            "stdout_lines": [
                "powermax_storagegroup.tf_sg: Creating...",
                "powermax_storagegroup.tf_sg: Creation complete after 5s [id=vol1]",
                "",
                "Apply complete! Resources: 1 added, 0 changed, 0 destroyed."
            ],
            "workspace": "default"
        }
    }
    
    PLAY RECAP ***************************************************************************************
    localhost  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    ignored=0

    Terraform plan

    We went straight to “terraform apply” but most Terraform workflows also include running the plan command first. This is done with Ansible by running the playbook in “check mode”. Check mode invokes “terraform plan” for us.

    root:~# ansible-playbook runtf.yml --check --extra-vars "sg_name=tf-sg3 password=TFpass"
    
    PLAY [localhost] ***************************************************************************************
    
    TASK [Run terraform] ***************************************************************************************
    ok: [localhost]
    
    TASK [debug] ***************************************************************************************
    ok: [localhost] => {
        "output": {
            "changed": false,
            "command": "/usr/bin/terraform apply -no-color -input=false -auto-approve -lock=true /tmp/tmp6f2o83k5.tfplan",
            "failed": false,
            "outputs": {},
            "state": "present",
            "stderr": "",
            "stderr_lines": [],
            "stdout": "powermax_storagegroup.tf_sg: Refreshing state... [id=tf-sg2]\n\nTerraform used the selected providers to generate the following execution\nplan. Resource actions are indicated with the following symbols:\n  ~ update in-place\n\nTerraform will perform the following actions:\n\n  # powermax_storagegroup.tf_sg will be updated in-place\n  ~ resource \"powermax_storagegroup\" \"tf_sg\" {\n      ~ cap_gb                   = 0 -> (known after apply)\n      + child_storage_group      = (known after apply)\n      ~ compression              = true -> (known after apply)\n      + compression_ratio        = (known after apply)\n      ~ compression_ratio_to_one = 0 -> (known after apply)\n      + device_emulation         = (known after apply)\n      ~ id                       = \"tf-sg2\" -> (known after apply)\n      + maskingview              = (known after apply)\n      ~ name                     = \"tf-sg2\" -> \"tf-sg3\"\n      ~ num_of_child_sgs         = 0 -> (known after apply)\n      ~ num_of_masking_views     = 0 -> (known after apply)\n      ~ num_of_parent_sgs        = 0 -> (known after apply)\n      + num_of_snapshot_policies = (known after apply)\n      ~ num_of_snapshots         = 0 -> (known after apply)\n      ~ num_of_vols              = 0 -> (known after apply)\n      + parent_storage_group     = (known after apply)\n      + service_level            = (known after apply)\n      ~ slo                      = \"NONE\" -> (known after apply)\n      ~ slo_compliance           = \"NONE\" -> (known after apply)\n      + snapshot_policies        = (known after apply)\n      + tags                     = (known after apply)\n      ~ type                     = \"Standalone\" -> (known after apply)\n      ~ unprotected              = true -> (known after apply)\n      ~ unreducible_data_gb      = 0 -> (known after apply)\n      + uuid                     = (known after apply)\n      ~ volume_ids               = [] -> (known after apply)\n      + vp_saved_percent         = (known after apply)\n      + workload                 = (known after apply)\n        # (2 unchanged attributes hidden)\n    }\n\nPlan: 0 to add, 1 to change, 0 to destroy.\n\n─────────────────────────────────────────────────────────────────────────────\n\nSaved the plan to: /tmp/tmp6f2o83k5.tfplan\n\nTo perform exactly these actions, run the following command to apply:\n    terraform apply \"/tmp/tmp6f2o83k5.tfplan\"\n",
            "stdout_lines": [
                "powermax_storagegroup.tf_sg: Refreshing state... [id=tf-sg2]",
                "",
                "Terraform used the selected providers to generate the following execution",
                "plan. Resource actions are indicated with the following symbols:",
                "  ~ update in-place",
                "",
                "Terraform will perform the following actions:",
                "",
                "  # powermax_storagegroup.tf_sg will be updated in-place",
                "  ~ resource \"powermax_storagegroup\" \"tf_sg\" {",
                "      ~ cap_gb                   = 0 -> (known after apply)",
                "      + child_storage_group      = (known after apply)",
                "      ~ compression              = true -> (known after apply)",
                "      + compression_ratio        = (known after apply)",
                "      ~ compression_ratio_to_one = 0 -> (known after apply)",
                "      + device_emulation         = (known after apply)",
                "      ~ id                       = \"tf-sg2\" -> (known after apply)",
                "      + maskingview              = (known after apply)",
                "      ~ name                     = \"tf-sg2\" -> \"tf-sg3\"",
                "      ~ num_of_child_sgs         = 0 -> (known after apply)",
                "      ~ num_of_masking_views     = 0 -> (known after apply)",
                "      ~ num_of_parent_sgs        = 0 -> (known after apply)",
                "      + num_of_snapshot_policies = (known after apply)",
                "      ~ num_of_snapshots         = 0 -> (known after apply)",
                "      ~ num_of_vols              = 0 -> (known after apply)",
                "      + parent_storage_group     = (known after apply)",
                "      + service_level            = (known after apply)",
                "      ~ slo                      = \"NONE\" -> (known after apply)",
                "      ~ slo_compliance           = \"NONE\" -> (known after apply)",
                "      + snapshot_policies        = (known after apply)",
                "      + tags                     = (known after apply)",
                "      ~ type                     = \"Standalone\" -> (known after apply)",
                "      ~ unprotected              = true -> (known after apply)",
                "      ~ unreducible_data_gb      = 0 -> (known after apply)",
                "      + uuid                     = (known after apply)",
                "      ~ volume_ids               = [] -> (known after apply)",
                "      + vp_saved_percent         = (known after apply)",
                "      + workload                 = (known after apply)",
                "        # (2 unchanged attributes hidden)",
                "    }",
                "",
                "Plan: 0 to add, 1 to change, 0 to destroy.",
                "",
                "─────────────────────────────────────────────────────────────────────────────",
                "",
                "Saved the plan to: /tmp/tmp6f2o83k5.tfplan",
                "",
                "To perform exactly these actions, run the following command to apply:",
                "    terraform apply \"/tmp/tmp6f2o83k5.tfplan\""
            ],
            "workspace": "default"
        }
    }
    
    PLAY RECAP ***************************************************************************************
    localhost   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    ignored=0

    No changes are made and the Ansible module has captured the details of the changes that will be made if the plan is applied.

    Terraform destroy

    Finally we need to learn how to use “terraform destroy”. You might have guessed it by now. The native way of destroying something in Ansible is by setting the state to “absent”.

    ---
    - hosts: localhost
      gather_facts: no
    
      vars:
        project_dir: "/root/powermax" # dir that contains main.tf, ...
    
      tasks:
        - name: Run terraform
          community.general.terraform:
            project_path: '{{ project_dir }}'
            state: absent
            variables:
              sg_name: "{{ sg_name }}"
              password: "{{ password }}"
          register: output
    
        - debug:
            var: output

    Let’s run it and see what it does.

    root:~# ansible-playbook runtf.yml --extra-vars "sg_name=tf-sg2 password=TFp2ssw0rd"
    
    PLAY [localhost] ***************************************************************************************
    
    TASK [Run terraform] ***************************************************************************************
    changed: [localhost]
    
    TASK [debug] ***************************************************************************************
    ok: [localhost] => {
        "output": {
            "changed": true,
            "command": "/usr/bin/terraform destroy -no-color -auto-approve -lock=true -var sg_name=tf-sg2 -var password=smc",
            "failed": false,
            "outputs": {},
            "state": "absent",
            "stderr": "",
            "stderr_lines": [],
            "stdout": "powermax_storagegroup.tf_sg: Refreshing state... [id=tf-sg2]\n\nTerraform used the selected providers to generate the following execution\nplan. Resource actions are indicated with the following symbols:\n  - destroy\n\nTerraform will perform the following actions:\n\n  # powermax_storagegroup.tf_sg will be destroyed\n  - resource \"powermax_storagegroup\" \"tf_sg\" {\n      - cap_gb                   = 0 -> null\n      - compression              = true -> null\n      - compression_ratio_to_one = 0 -> null\n      - host_io_limit            = {} -> null\n      - id                       = \"tf-sg2\" -> null\n      - name                     = \"tf-sg2\" -> null\n      - num_of_child_sgs         = 0 -> null\n      - num_of_masking_views     = 0 -> null\n      - num_of_parent_sgs        = 0 -> null\n      - num_of_snapshots         = 0 -> null\n      - num_of_vols              = 0 -> null\n      - slo                      = \"NONE\" -> null\n      - slo_compliance           = \"NONE\" -> null\n      - srp_id                   = \"SRP_1\" -> null\n      - type                     = \"Standalone\" -> null\n      - unprotected              = true -> null\n      - unreducible_data_gb      = 0 -> null\n      - volume_ids               = [] -> null\n    }\n\nPlan: 0 to add, 0 to change, 1 to destroy.\npowermax_storagegroup.tf_sg: Destroying... [id=tf-sg2]\npowermax_storagegroup.tf_sg: Destruction complete after 0s\n\nDestroy complete! Resources: 1 destroyed.\n",
            "stdout_lines": [
                "powermax_storagegroup.tf_sg: Refreshing state... [id=tf-sg2]",
                "",
                "Terraform used the selected providers to generate the following execution",
                "plan. Resource actions are indicated with the following symbols:",
                "  - destroy",
                "",
                "Terraform will perform the following actions:",
                "",
                "  # powermax_storagegroup.tf_sg will be destroyed",
                "  - resource \"powermax_storagegroup\" \"tf_sg\" {",
                "      - cap_gb                   = 0 -> null",
                "      - compression              = true -> null",
                "      - compression_ratio_to_one = 0 -> null",
                "      - host_io_limit            = {} -> null",
                "      - id                       = \"tf-sg2\" -> null",
                "      - name                     = \"tf-sg2\" -> null",
                "      - num_of_child_sgs         = 0 -> null",
                "      - num_of_masking_views     = 0 -> null",
                "      - num_of_parent_sgs        = 0 -> null",
                "      - num_of_snapshots         = 0 -> null",
                "      - num_of_vols              = 0 -> null",
                "      - slo                      = \"NONE\" -> null",
                "      - slo_compliance           = \"NONE\" -> null",
                "      - srp_id                   = \"SRP_1\" -> null",
                "      - type                     = \"Standalone\" -> null",
                "      - unprotected              = true -> null",
                "      - unreducible_data_gb      = 0 -> null",
                "      - volume_ids               = [] -> null",
                "    }",
                "",
                "Plan: 0 to add, 0 to change, 1 to destroy.",
                "powermax_storagegroup.tf_sg: Destroying... [id=tf-sg2]",
                "powermax_storagegroup.tf_sg: Destruction complete after 0s",
                "",
                "Destroy complete! Resources: 1 destroyed."
            ],
            "workspace": "default"
        }
    }
    
    PLAY RECAP ***************************************************************************************
    localhost  : ok=2    changed=1    unreachable=0    failed=0    skipped=0     ignored=0

    The storage group resource has been destroyed and that shows in Ansible as a task with a status of “changed”. The “stdout” and “stdout_lines” parameters provide a record of what was destroyed.

    In summary, in this article we have seen how to use the terraform Ansible module to plan, apply and destroy Terraform projects. In doing that, organizations can leverage a tool like AWX or RedHat AAP to provide RBAC, strong credential management and a powerful upstream REST API and integrate with other tools like ITSM etc. AWX and AAP can also became a polyglot automation platform capable of running the two most popular automation tools out there to satisfy both traditional infrastructure teams and developers.

  • Automating Kubernetes deployment on VMs using Ansible

    Automating Kubernetes deployment on VMs using Ansible

    In this post, we will discuss automating Kubernetes deployment using Ansible.

    In my example, I have used CentOS VMs (on VMware) for deploying Kubernetes. But technically Kubernetes deployment steps don’t differ irrespective of the platform you use.

    Before getting started to make sure you have

    • Ansible server up and running on the network. Also, make sure Ansible can reach the VMware environment.
    • Make sure you’ve added Ansible server SSH authentication keys into VMware virtual machine before converting the same into the template. Follow this blog post for steps.

    Once you have the pre-requisites in place follow the below steps.

    Step 1 – Clone my GitHub repository which consists of required playbooks and instructions.

    [root@alb-ansible dw-pm-csi]# git clone https://github.com/waghmaredb/ansible-k8s
    Cloning into 'ansible-k8s'…
    remote: Enumerating objects: 41, done.
    remote: Counting objects: 100% (41/41), done.
    remote: Compressing objects: 100% (40/40), done.
    remote: Total 41 (delta 12), reused 0 (delta 0), pack-reused 0
    Unpacking objects: 100% (41/41), done.
    [root@alb-ansible dw-pm-csi]# cd ansible-k8s/
    [root@alb-ansible ansible-k8s]# ls
    k8s-deployment.yml README.md

    Step 2 – Edit k8s-deployment.yml file and edit below lines from VARS

    common environment details
    #ntp_server: - Replace with your NTP server IP/hostname
    domain: "" - Replace with your DOMAIN NAME
    dns_server: - Replace with your DNS server IP/hostname
    vmware environment details
    vcenter_ip: - Replace with your vCenter server IP/hostname
    vcenter_username: - Replace with vCenter admin account username
    vcenter_password: - Replace with vCenter admin account password
    vmware_datacenter: - Replace with VMware datacenter you want to use
    vmware_cluster: - Replace with VMware cluster you want to use
    vm_network: "" - Replace with VM network you want kubernetes VMs to connect
    k8s_vm_folder: - Replace with VM folder in which you want to place kubernetes VMs
    k8s_template_name: - Replace with VMware CentOS template name
    K8S environment details
    k8s_master_ip: 192.168.172.100 - Replace IP address with kubernetes master server IP address you want to use
    k8s_network_netmask: 255.255.255.0 - Replace subnet mask with netmask of kubernetes network
    k8s_network_gateway: 192.168.172.1 - Replace gateway with kubernetes network gateway
    k8s_node1_ip: 192.168.172.101 - Repalce IP address with kubernetes node IP address
    #k8s_node2_ip: 192.168.1.102
    #k8s_node3_ip: 192.168.1.103
    #k8s_node4_ip: 192.168.1.104
    #k8s_node5_ip: 192.168.1.105
    #k8s_node6_ip: 192.168.1.106
    #k8s_node7_ip: 192.168.1.107
    #k8s_node8_ip: 192.168.1.108

    Step 3 – Edit the /etc/ansible hosts file and insert the Kubernetes environment details. Make sure IP address details are inline with your Kubernetes environment

    [kube_cluster1]
    k8s-master ansible_host=192.168.172.100 ansible_user=root
    worker1 ansible_host=192.168.172.101 ansible_user=root
    worker2 ansible_host=192.168.172.102 ansible_user=root
    worker3 ansible_host=192.168.172.103 ansible_user=root
    worker4 ansible_host=192.168.172.104 ansible_user=root
    
    [master]
    k8s-master ansible_host=192.168.172.100 ansible_user=root
    
    [worker]
    worker1 ansible_host=192.168.172.101 ansible_user=root
    worker2 ansible_host=192.168.172.102 ansible_user=root
    worker3 ansible_host=192.168.172.103 ansible_user=root
    worker4 ansible_host=192.168.172.104 ansible_user=root

    Step 4 – Run the k8s-deployment.yml playbook.