Discovery

The Python Infrastructure uses Consul to implement service discovery. There is an agent running in client mode on all servers in the iad1 datacenter. These can be queried on the standard ports on localhost. Consul also has a web interface running on localhost on all nodes.

Using the Web Interface

To use the Consul web interface simply ssh into any server with a SSH port forward such as ssh salt-master.psf.io -L 8500:localhost:8500 and then navigate to http://localhost:8500/ in your browser.

Using a Service

Generally the best way to handle pointing to a service is to utilize the consul-template utility which will automatically watch a service and update a configuration file and reload/restart a process whenever it is updated. This takes a template (which can be rendered by salt) and will detect which services it needs based on that.

This is best demonstrated by an example, say haproxy:

haproxy:
  service.running:
    - enable: True
    - reload: True
    - require:
      - service: haproxy-consul

/etc/haproxy/haproxy.cfg.tmpl:
  file.managed:
    - source: salt://haproxy/config/haproxy.cfg.jinja
    - template: jinja

haproxy-consul:
  file.managed:
    - name: /etc/init/haproxy-consul.conf
    - source: salt://consul/init/consul-template.conf.jinja
    - template: jinja
    - context:
        templates:
          - "/etc/haproxy/haproxy.cfg.tmpl:/etc/haproxy/haproxy.cfg:chmod 644 /etc/haproxy/haproxy.cfg && service haproxy reload"
    - require:
      - pkg: consul

  service.running:
    - enable: True
    - restart: True
    - require:
      - pkg: consul
    - watch:
      - file: haproxy-consul
      - file: /etc/consul-template.conf
      - file: /etc/haproxy/haproxy.cfg.tmpl

Essentially this sets up a haproxy proxy service which requires the haproxy-consul service. Then it renders a configuration template and finally it creates a haproxy-consul instance which has an upstart config and is set to running. The templates variable passed into the contents is important. It is a list of template, destiniation, command tuples. This is in the form of "/path/to/template:/path/to/destination:shell command to run".

The template file can contain blocks that look like:

{{range service "my-service@iad1"}}
server {{.Name}} {{.Address}}:{{.Port}} check{{end}}

This will render a server line for every entry in the "my-service" service in the iad1 datacenter and tt will use the name, address, and port of the registered service. For a full list of everything you can do in this template file take a look at the consul-template documentation.

Registering a Service

Registering an internal service (e.g. one that is running on a server in the iad1 datacenter) is quite easy. You simply need to pick a name, port, and any tags that you wish to associate with the service. Then simply add the below block to the state files:

/etc/consul.d/service-my-service.json:
  file.managed:
    - source: salt://consul/etc/service.jinja
    - template: jinja
    - context:
        name: my-service
        port: 9000
        tags:
          - tag1
          - tag2
    - user: root
    - group: root
    - mode: 644
    - require:
      - pkg: consul

Where the name, port, and tags context variables control the values that will be entered into the system. This will be available the next time that salt runs the highstate command. It is likely you’ll want this state to require whatever states setup the service that you’re exposing as any watchers will start using it near instantly.

Registering an External Service

Not all services are hosted internally, some services are external services where we cannot install a consul client on their servers. The Consul service system can handle this quite easily as well. To add an external service simply edit pillar/dev/consul.sls or pillar/prod/consul.sls and add a new entry in the external dictionary. The keys are datacenter, node, address, service, port. Using an external service is exactly like using an internal service.

Example:

consul:
  external:
    - datacenter: vagrant
      node: pythonanywhere
      address: www.pythonanywhere.com
      service: console
      port: 443