Ansible's Annoyance - I would implement it this way!

1

I've been skipping a lot since last year, but I'm developing a tool called Submarine.js which is a Ole Ole configuration management tool that implements a function that I personally feel is lacking in Ansible, so I will introduce it in comparison with Ansible.

Repository: https://gitlab.com/mjusui/submarine2

I hope you will read it as a wish that Ansible had such a function.

Highlights of Python environment construction

The first important thing to install Ansible is to have a Python environment. there are the following points to note when doing so.

  • Python, pip, and Ansible each require version control
    • Incompatible with Python2 and 3 series
    • pip install When you specify a version
    • Even the same Ansible2.x series has features that are not backwards compatible
    • Pyenv/virtualenv is recommended

Submarine.js, on the other hand, implements it with as little dependencies as possible on anything other than the version of Node.js

  • Depends only on the version of Node.js
    • Node.js is relatively backwards compatible because it is a derivative of JavaScript that runs in the browser
    • No packages are used, so there is no need to eliminate dependencies
    • Once the operation is stable, we plan to maintain the backward compatibility of the Submarine .js itself for a longer period of time (desire)
    • nvm environment (equivalent to pyenv in Python) is recommended

Dynamic Inventory makes it much easier

Dynamic Inventory is a feature that dynamically generates an inventory of what you want to run a playbook, but it is difficult to get stuck in the following ways.

  • Features of Dynamic Inventory
    • I need to generate complex JSON in a script
    • You can't easily see the list of generated inventory

Submarine.js, on the other hand, makes it easy to implement by combining the two concepts of generator and filter.

collect.js
module.exports={
  collect: [{
    type: 'gen/bash',
    cmd: 'echo 172.17.0.{1..254}',
  }, {
    type: 'fil/ping',
  }],
};

This code is the code of Submarine .js that pings IP addresses from 172.17.0.1 to 254 and extracts only the hosts that have had a response

I need to write code for Node.js, but the part related to configuration management is fine if you can write JSON most of it

type: 'gen/bash'is a generator that generates a list of target hosts based on the results of a command run in Bash And type: 'fil/ping'is a filter that pings a list generated by generator and narrows the list according to the response result.

In this way, Submarine .js can achieve the equivalent of Ansible's Dynamic Inventory with simple JSON.

It is the user's job to ensure idempotency

One of the key concepts of Ansible is idempotency.

Ansible provides a huge number of modules to ensure this, but their behavior changes slightly from version to version, making maintenance difficult.

Also, if you want to do something complicated, you have to write an idempotent script yourself, which is also quite a painstaking task.

In Submarine.js, instead of providing idempotent modules, we take the approach of learning from Dockerfiles and writing immutable ShellScript

It is implemented to run ShellScript once and generate a lock file on the server side, and after the second time if there is a lock file, skip execution

The code looks like this

provisoin.js
module.exports={
  provision: {
    gen: 'mysql-server-1',
    opts: ['-l submarine'],
    cmds: [{
      name: 'install-mysql',
      cmd: String.raw`
        cd /usr/local/src/mysql \\
        && ./configure \\
        && make install
      `,
    }, {
      name: 'install-curl',
      cmd: 'apt install -y curl',
    }]
  },
};

in an array called cmds specify the command to execute (cmd) and name as the name of the lock file

by the way, if you want to modify a script that you have run once you want to add a new operation to the end of the cmds instead of modifying the existing cmd It's like applying a patch.

This way, you don't have to learn how to use tool-specific modules or write if statements for idempotency

Infrastructure configuration management is closer to Database design than programming

Managing the configuration of the infrastructure as code is called IaC (Infrastructure as Code), but in fact there are different characteristics that infrastructure configuration management and programming cannot be treated together.

First of all, in the world of infrastructure, the way to install and write the configuration file are different for each middleware, and there is no such thing as a sufficiently prepared Syntax like programming.

The main purpose of programming is to reference/modify data in memory and Database. On the other hand, in the case of infrastructure, for example, middleware installation is more expensive than that, and the version of software once installed cannot be easily changed (it can be said that container technology is excellent in this regard).

In the world of infrastructure, there are less reversible state transitions that are more expensive than programming. Therefore, if a bug is found, it is often not possible to solve it by re-releasing it with the old tag

In Ansible, if servers A and B share the same build procedure, it is often combined in units called role, but if you do this, for example, if you change the build procedure of server A, if you modify this shared code, it will affect server B, so it is often difficult to maintain.

In the programming world, DRY is often considered a virtue, but in the IaC world it is not always the case.

In this regard, Submarine.js recommends that even if there are multiple servers that share the build procedure, the configuration files should be separated. In fact, configuration information is written together in a single file, making it difficult to share code.

Ansible weak in infrastructure generation management

It turns out that the infrastructure is different from programming, and the threshold for configuration changes is high. As a result, in the world of infrastructure, there are times when multiple generations of infrastructure run in parallel in a production environment.

And Ansible doesn't seem to be very good at managing this multi-generational configuration. I mentioned earlier that by using role to achieve DRY, it becomes difficult to perform maintenance such as changing the configuration of only a specific server.

I wrote a little bit about this in my article last year.

Submarine .js introduces the concept of "generation" into configuration information

provision.js
module.exports={
  provision: {
    gen: 'mysql-server-1',
    opts: ['-l submarine'],
    cmds: [{
      name: 'install-mysql',
      cmd: String.raw`
        cd /usr/local/src/mysql \\
        && ./configure \\
        && make install
      `,
    }, {
      name: 'install-curl',
      cmd: 'apt install -y curl',
    }]
  },
};

This is the code that also came out in the explanation of immutable ShellScript earlier where gen: the part that says 'submarien-test-2' corresponds to the generation

Submarine.js behaves in such a way that when you first run a script, it generates a file on the server side to keep this generation of information, and then does not run the script on the server where this generation information does not match the second time.

In other words, it protects one generation of servers from running another generation of code.

This is useful, for example, when you want to try replacing (rolling up) only some of the servers in a group of servers. Naturally, the installation procedure also changes when updating the OS or middleware, so it is necessary to change the definition of provision. Therefore, the changed definition can not be run on the old generation server that has already been built, but only on the new generation server with the new OS installed.

Once you've built your server, make sure you've done it correctly

Although it is a little different from Ansible, which is a configuration management and automation tool, Submarine.js also has the ability to test the server you have built.

When we talk about server testing, we generally think of TestInfra, which is one of the Ansible ecosystems, and Serverspec, a Ruby-like tool, but these tools provide modules to retrieve values from the server.

Submarine.js, on the other hand, also collects values in ShellScript

query-and-test.js
module.exports={
  query: {
    opts: ['-l submarine'],
    query: {
      hostname: 'hostname -s',
      submarine_user: 'cat /etc/passwd|grep submarine|wc -l'
    }
  },

  test: {
    func: (host, all)=>{
      return {
        host_count: Object.keys(all).length === 10,
        hostname: host.hostname === 'ubu2004-submarine-target',
        submarine_user: host.submarine_user === '1',
      };
  },
};

In the query part above, write a ShellScript that collects the values you want to test the test part defines a function that evaluates the obtained value

because you can put configuration information and tests in the same file, your code will make it easier to understand your server requirements

Advantages of Ansible

So far, I've listed the parts that I personally feel are not good enough for Ansible, but there are many things that can only be done without Ansible, so I will describe as much as I can think of

  • Network equipment module group is substantial.
  • Windows-like modules are substantial.
  • Jinja2's Template can be used
Share:
1
Author by

元データセンターの中の人。今はアドテクのインフラエンジニア。最近は落ち込みぎみ 最近つくったWebサイト http://hanamoji.monarchs.name ブログ http://blog.monarchs.name

Updated on December 17, 2020