How do I create a nodebalancer with Ansible and map two nodes?

I've been looking around for docs on how to use the linode_v4 api to create a Nodebalancer instance using Ansible, but I'm not finding anything.

I'd love to see an example for setting up a pair of nodes with simple HTTP servers behind a single nodebalancer with HTTP and HTTPS support.

I'm currently running Ansible v2.9.17 on CentOS 8.3. It's not clear what I need to do. Trying this simple ansible playbook just bombs:


  • name: Create a nodebalancer instance hosts: localhost gather_facts: False vars_files:
    • ./vars/main.yml
      tasks:
    • name: spin up Nodebalancer
      nodebalancer:
      nodebalancer_id: lweb_nb
      access_token: "{{ token }}"
      type: nodebalancers
      region: "{{ region }}"
      state: present
      register: lweb_nb

Which I guess isn't too surprising. Or do I need to call the API directly using the curl examples I see in the online docs? Seems pretty ugly to me…

8 Replies

Hey there!

I was doing some digging around for you and found a GitHub repo that may be exactly what you are looking for!

BrainLabs Digital - Ansible Linode NodeBalancer

It looks like the owner of the repo developed a module so you can create, update and delete NodeBalancers. They go a step further and outline samples of the following:

  • Create a NodeBalancers
  • Create a NodeBalancer Configuration
  • Add a Linode to the NodeBalancer Configuration

They made note of some requirements and dependencies that you will want to take note of:

Configuration Specifics:

To enable this module to be read by Ansible you must place it in a 'library' folder and let Ansible know where this is by editing your ansible.cfg - (docs)

Python Dependency:

This module makes use of the Linode-Python library. Since this is a local task (ie it runs on the machine triggering the playbook, rather than the remote servers) you will need to install this into (virtual) environment that is running the playbook.

And as a bonus, it does not look like you have to call the API directly!

I wanted to clarify that I haven't tried this myself (I'm not an Ansible Wizard by any means) so I can't speak to it meeting all of your needs, but it definitely looks promising.

Hi,
Thanks for the link, but isn't that module using the old deprecated v3 API for Linode? And it's six years old without any updates… which is an ancient amount of time for both Ansible and Linode I would think.

I also found the [https://github.com/pathbreak/linode-cluster-toolkit] But that's had nothing happen in four years as well, so it's not very useful to depend on.

I'm still an Ansible noob, so struggling to come up to speed doing the right process and idioms for spinning up multiple Linodes and securing them, etc.

Oh well, I'll keep poking at this all.

John

So this is my initial stab at writing some ansible tasks to do this work. Since the Linode_v4 API doesn't seem to support nodebalancers at all yet, I've been forced to fall back on the linode-cli tool, run with a whole bunch of command: tasks.

The annoying part is that I don't grok setting facts properly yet, or using dynamic inventories, so I have to keep pulling info, checking to see if stuff exists yet. If not, I create it. Otherwise I should just ignore it. It's a pain. And since I'm still new to Ansible, it's been quite painful. Any suggestions or feedback apprecaited!

First off, for my testing, I wrote a simple wrapper yml file which just does

tasks:
  include_tasks: nodebalancer.yml

And then the included script is where I do the work.

- name: "Check if nodebalancer exits"
  command:
    cmd: "linode-cli nodebalancers list --json --suppress-warnings --format 'id,label' --label {{ n\
b_label }}"
  register: nodebalancers
  delegate_to: localhost

- set_fact:
    nbs: "{{ nodebalancers.stdout|from_json }}"

- name: "print info about Nodebalancers"
  debug:
    msg: "Nodebalancer: {{ item.id }}"
  with_items: "{{ nbs }}"

- name: Create nodebalancer
  command:
    cmd: "linode-cli nodebalancers create --json --suppress-warnings --region {{ region }} --label \
{{ nb_label }}"
  delegate_to: localhost
  register: my_nb
  when: nbs|length == 0

- name: "Get nodebalancer info"
  command:
    cmd: "linode-cli nodebalancers list --json --suppress-warnings --format 'id,label' --label {{ n\
b_label }}"
  register: nodebalancer
  delegate_to: localhost

- name: "debugging"
  debug:
    var: nodebalancer.stdout

- name: "set nb"
  set_fact:
    nb: "{{ nodebalancer.stdout|from_json }}"

- name: "debugging nb"
  debug:
    var: nb

- name: "Get nodebalancer config"
  command:
    cmd: "linode-cli nodebalancers configs-list --json --suppress-warnings {{ nb[0].id }}"
  register: nb_config
  when: nodebalancer|length > 0

- name: "debugging nb_config"
  debug:
    var: nb_config

- name: "Create basic configs on ports 80 and 443"
  command:
    cmd: "linode-cli nodebalancers config-create --json --suppress-warnings --port {{ item }} {{ nb\
[0].id }}"
  register: cmd_config_create
  ignore_errors: true
  with_items:
   - "80"
   - "443"

- name: "Get nodebalancer config nodes"
  command:
    cmd: "linode-cli nodebalancers nodes-list --json --suppress-warnings --format 'id'"
  register: nb_config_nodes
  when: nb_config|length > 0

- name: "Add linode private IP to config"
  command:
    cmd: "linode-cli nodebalancers node-create --json --suppress-warnings --address {{ item }} {{ n\
b[0].id }} {{ nb_config.stdout[0].id }}"
  register: nb_config_nodes
  with_items: linodes_private
  when: nb_config|length > 0

But I'm still in the process of testing this. Basically you need to do the following:

  1. get your list of linodes and the private addresses you will add to the nodebalancer.

  2. Create the nodebalancer (if it doesn't exist already in your inventory)

  3. Create one or more configs inside the nodebalancer, one for each port you will be using.

  4. Add each linode's private IP to each config inside the nodebalancer.

So step 3 can be done inside the main nodebalancer.yml file, but I think I really need to then loop over each config using an included task file to add the linode private IP to the config.

Not being able to loop over tasks with proper looping is painful. Especially when you want to do the update the current config (if it exists) to match the current settings. Which means you have to do the whole dance of:

a. check for the config.
b. If it exists, update to make current needs.
c. or create config.
d. then loop over private IPs to add to each config.

So a simple setup of two linodes, httpd server, one nodebalancer, both port 80 and port 443 traffic gets really complicated and intricate. All because the linode_v4 api isn't there to do the work for you.

So please, any comments?

@toshiba-it here is a link for the documentation for use with node balancers with the Linode API, version 4.
Node Balancer API support

As for the rest of this, I have no experience, but hopefully the documentation will be helpful for you.

Also, keep in mind that the Linode CLI, unless you're using the deprecated version, is using the Linode API version 4. Anything it can do, you should be able to do through the API through direct calls.

Good luck!

Blake

Hi Blake,
Thanks for the response, but it really doesn't help me much. As you can see in my examples above, I'm already using the API, just via the linode-cli command. I tried doing more using the uri: module like so:

---
- name: test using URI module to call Linode v4 api
  hosts: localhost

  vars_files:
    - ./vars/vars.yml
    - ./vars/external.yml

  tasks:

    - name: find nodes
      uri:
        url: "https://api.linode.com/v4/linode/instances"
        method: GET
        return_content: no
        headers:
          Authorization: "Bearer {{ token }}"
      register: result
      ignore_errors: true

    # Show what we got back
    - debug:
        msg: "Linode ID: {{ item.id }}  Label: {{ item.label }}"
      with_items: "{{ result.json.data }}"

    # Find if we have any private IPs already allocated
    - name: find node Network info
      uri:
        url: "https://api.linode.com/v4/linode/instances/{{ item.id }}/ips"
        method: GET
        return_content: yes
        headers:
          Authorization: "Bearer {{ token }}"
      register: foundip
      ignore_errors: true
      with_items: "{{ result.json.data }}"

    - name: Debugging output
      debug:
        msg: "ITEM: {{ item.json.ipv4.private }} \n"
      with_items: "{{ foundip.results }}"

    # Try a POST request to add a private IP
    # - name: Add private IP via POST try 2
    #   uri:
    #     body_format: json
    #     url: "https://api.linode.com/v4/linode/instances/{{ item.id}}/ips"
    #     method: POST
    #     return_content: no
    #     headers:
    #       Authorization: "Bearer {{ token }}"
    #     body: '{ "type": "ipv4", "public": false }'
    #   register: resultpost
    #   ignore_errors: true
    #   with_items: "{{ result.json.data }}"

    # - debug:
    #     msg: "Linode ID: {{ item.id }}  Label: {{ item.label }}"
    #   with_items: "{{ result.json.data }}"

but I was running into problems with using Ansible to parse this all properly, and how to do POST and GET calls in the right places. It basically sucks.

Part of the problem, as I said before, was that the linode_v4 API seems to require you to do:

if (not object exists)
create object.
else
modify object
end
set_fact:

which is (to an Ansible newbie like me) a painful process.

Can you address my concerns? Or show me a better method?

Unfortunately not, as I have no experience with Ansible. Perhaps someone else might have more insight about the issue.

Blake

Thanks Blake, let's see if anyone else chimes in.

I did find a bug in the linode-cli tool which I reported and it should be fixed soon for being able to update nodebalancer configs more easily.

I've used the ansible.builtin.uri (URI Module in v2.9) for things like this in the past, it gets a little clunky but if you can't or don't want to work with your own module then something like

- name: create a Nodebalancer
  uri:
    url: https://api.linode.com/v4/nodebalancers
    method: POST
    body:
      region: "us-east"
      label: "my_nodebalancer"
  register: this
  failed_when: "'some-pass-condition' not in this.content"

This lets you work with the API all on its own, without the need to create and maintain some python script that exists solely as a wrapper.

PS. sorry for the pseudocode, you may have to dig with the below reference guides to get it right:

https://www.linode.com/docs/api/nodebalancers/#nodebalancer-update
https://docs.ansible.com/ansible/2.9/modules/uri_module.html

PPS. This might also require you to first query the API to get a list of nodebalancers, then run an update instead of create task to leave the server in the same state every time (idempotence!)

Reply

Please enter an answer
Tips:

You can mention users to notify them: @username

You can use Markdown to format your question. For more examples see the Markdown Cheatsheet.

> I’m a blockquote.

I’m a blockquote.

[I'm a link] (https://www.google.com)

I'm a link

**I am bold** I am bold

*I am italicized* I am italicized

Community Code of Conduct