{"id":27362548,"url":"https://github.com/epicfilemcnulty/hapgent","last_synced_at":"2025-07-17T10:36:15.124Z","repository":{"id":287631197,"uuid":"965316121","full_name":"epicfilemcnulty/hapgent","owner":"epicfilemcnulty","description":"HAProxy agent","archived":false,"fork":false,"pushed_at":"2025-04-12T23:34:01.000Z","size":25,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-13T00:18:22.502Z","etag":null,"topics":["haproxy","haproxy-agent","load-balancer","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/epicfilemcnulty.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-12T22:14:01.000Z","updated_at":"2025-04-12T23:34:05.000Z","dependencies_parsed_at":"2025-04-13T00:18:25.468Z","dependency_job_id":"df0ac768-500b-4b0d-aa4e-8790267c09e4","html_url":"https://github.com/epicfilemcnulty/hapgent","commit_stats":null,"previous_names":["epicfilemcnulty/hapgent"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicfilemcnulty%2Fhapgent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicfilemcnulty%2Fhapgent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicfilemcnulty%2Fhapgent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/epicfilemcnulty%2Fhapgent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/epicfilemcnulty","download_url":"https://codeload.github.com/epicfilemcnulty/hapgent/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248658373,"owners_count":21140931,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["haproxy","haproxy-agent","load-balancer","zig"],"created_at":"2025-04-13T03:21:27.450Z","updated_at":"2025-07-17T10:36:15.119Z","avatar_url":"https://github.com/epicfilemcnulty.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HAProxy Agent\n\n## Introduction\n\n*Hapgent* is a companion tool for the [HAProxy](https://www.haproxy.com/) load balancer.\n\nWhen you have a bunch of upstream servers defined for an HAProxy backend, \nHAProxy has [several](https://www.haproxy.com/documentation/haproxy-configuration-tutorials/service-reliability/health-checks) types of\nhealth checks to determine the availability of an upstream server:\n    \n 1. TCP health checks\n 2. HTTP health checks\n 3. Passive health checks\n 4. Agent checks\n\nWhile the first three types are internal to HAProxy, i.e. \nyou don't need any external tools to use them, the agent checks are different: \nyou need to have an actual agent running on the upstream servers to use agent checks. \n\n*Hapgent* is an implementation of such an agent.\n\n## Agent protocol\n\nThe protocol of HAProxy agent is described in the [agent checks](https://www.haproxy.com/documentation/haproxy-configuration-tutorials/service-reliability/health-checks/#agent-checks) section of the official documentation, but in a nutshell it works\nlike this:\n\n * You define *address*, *port*, *payload* and *interval* for the agent in the upstream server configuration.\n * Every *interval* seconds HAProxy makes a TCP connection to the defined address \n   and port, sends the payload and reads an answer.\n * Agent answers with the server's status, and, optionally, weight.\n * Depending on the answer HAProxy may change the current status or weight of the server.\n\n### HAProxy backend configuration sample\n\n```\nbackend sample\n  mode http\n  balance roundrobin\n  option forwardfor if-none\n  option httpchk\n  default-server check agent-check agent-port 9777 agent-inter 5s\n  http-check send meth GET uri /health ver HTTP/1.1 hdr Host my.service.com\n  server srv1 10.42.42.10:8080 weight 100\n  server srv2 10.42.42.11:8080 weight 50\n```\n\nWe define two health checks here:\n\n 1. HTTP health check using `GET` HTTP method to the `/health` uri,\n    with `Host` header set to `my.service.com`\n 2. Agent check, port 9777, every 5 seconds, no payload. Since the `agent-addr` option is absent,\n    HAProxy will use server's IP as the agent IP address.\n\nPay attention to the fact that the weight reported by the agent is interpreted as\na percent of the original weight defined in the backend configuration.\n\nFor example, using the configuration above, if we set the weight of \nthe agent on `srv2` to `50`, the effective weight for the `srv2` will be `25`.\n\nNote that the protocol allows sending arbitrary payload to the agents with\nthe `agent-send` option. The agents could use this to implement multiple states support.\n\n*Hapgent* itself, however, just ignores the payload in the incoming requests.\nIf you need multiple states, i.e. you have multiple services on the same backend \nserver, just run an instance of *hapgent* per service, using different ports. \n\n## Configuration\n\n*Hapgent* is configured via the environment variables:\n\n| Variable Name                  | Default   |\n|--------------------------------|-----------|\n| `HAPGENT_IP`                   | *0.0.0.0* |\n| `HAPGENT_PORT`                 | *9777*    |\n| `HAPGENT_STATE_FILE`           | */etc/hapgent/state.json* |\n\n\n### State file format\n\nUpon initialisation *hapgent* reads the state file at the path defined\nin the `HAPGENT_STATE_FILE` environment variable.\n\nThe state file must be a valid JSON object with the required field `status`,\nand optional fields `weight` and `maxconn`.\n\n* Valid values for the `status` field are `UP`,`DOWN`,`READY`,`DRAIN`,`FAIL`,`MAINT`,`STOPPED`.\n* The `weight` field, if set, should be a number in the range `0-255`.\n* The `maxconn` field, if set, should be a number in the range `0-65535`.\n\nFor example:\n\n```json\n{\"status\":\"UP\"}\n\n{\"status\":\"READY\",\"weight\":100}\n\n{\"status\":\"DOWN\",\"maxconn\":10}\n\n{\"status\":\"UP\",\"maxconn\":300,\"weight\":77}\n```\n\nIf *hapgent* fails to read or parse the state file during the initial startup, \nor upon receiving a HUP signal, it resets its state to the default value `{\"status\":\"FAIL\"}`.\nPrevious state value is **discarded** in this case.\n\n## Usage\n\n*hapgent* typically should be run as a systemd/SysV service on the same instance\nwhere your service is deployed. See the deployment section below for details.\n\nYou can dynamically change *hapgent's* state with signals:\n\n* On a `USR2` signal, hapgent sets the status to `DOWN`, and saves its state in the state file. \n* On a `USR1` signal, hapgent sets the status to `UP`, and saves its state in the state file.\n* On a `HUP` signal, hapgent tries to read the state from the state file. If it succeeds,\n  it sets its current state to the one read from the state file. If it fails to read or \n  parse the state file, it sets its state to the default value `{\"status\":\"FAIL\"}`.\n\n### Putting an instance **in** or **out** of load balancing\n\nThis can be done in a deployment script, e.g.:\n\n```bash\n#!/bin/bash\n# a sample deployment script for a service\n\necho \"Removing the instance from LB\"\npkill -USR2 hapgent # assuming that we have only one hapgent per instance\n\nstop_service ${SERVICE_NAME}\ndeploy_new_version ${SERVICE_NAME}\nstart_service ${SERVICE_NAME}\n\necho \"Putting the instance back to LB\"\npkill -USR1 hapgent\n```\n\n### Dynamic weight adjustment\n\nWhile it's tempting to have a dynamic weight calculation\nfeature as a builtin in a HAProxy agent, it's not always \na good idea.\n\n*Hapgent* is intentionally designed to be as simple as possible,\nso it does not change anything in its current state on its own.\nIt simply reports its state upon receiving a TCP connection.\n\nBut *hapgent* can be operated with the help of Unix signals,\nas described above. Particularly, on the `SIGHUP` signal *hapgent*\ntries to re-read the state from the state file. This can be used\nto implement a generic weight / maxconn adjustment system:\n\n1. Create a script/app that calculates the weight for the instance based on the criteria you want.\n2. Create a cron task to periodically run said script, update the weight in the *hapgent's* state\n   file, and send the `SIGHUP` signal to *hapgent*.\n\n## Installation\n\n### Binary releases for Linux x86_64 systems\n\nGrab the `hapgent` binary, `hapgent.sha256` SHA256 checksum and `hapgent.sig` signature files\nfrom the latest release [binary](https://github.com/epicfilemcnulty/hapgent/releases)\n\nMake sure that SHA256 checksum of the binary matches the one in the `hapgent.sha256` file.\n\nThe binary is signed with my SSH key, to verify the signature you need to\n\n1. Add my public key to the allowed signers file:\n\n   ```\n   echo \"vladimir@deviant.guru ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICEWU0xshVgOIyjzQEOKtjG8sU8sWJPh25CP/ISfJRey\" \u003e\u003e ~/.ssh/allowed_signers\n   ```\n2. Verify the signature:\n   \n   ```\n   $ ssh-keygen -Y verify -f ~/.ssh/allowed_signers -n file -I vladimir@deviant.guru -s hapgent.sig \u003c hapgent\n   Good \"file\" signature for vladimir@deviant.guru with ED25519 key SHA256:K0hZF19Go+RKQPczS905IFVhRL8NiZTvZyi+4PkV/g8\n   ```\n\n### Building from source\n\nYou need [zig](https://ziglang.org/) version `0.14.0` to build from source.\nHaving zig installed and in the path, clone the repo and do the build:\n\n```\ngit clone https://github.com/epicfilemcnulty/hapgent.git\ncd hapgent\nzig build\n```\n\nThe binary is saved in the `zig-out/bin/hapgent` file.\n\n## Resource usage\n\n*Hapgent* is a very lightweight application, the binary is **75Kb** and memory usage during \nthe runtime is about **200Kb**.\n\nI've written a couple of HAProxy agent implementations in Go for different projects, and, for\ncomparison, the binary of my last Go implementation (same functionality as this one) is **3.8Mb**, \nmemory usage is around **4.6Mb** during runtime.\n\n## Deployment\n\nThere is an ansible [role](deploy/hapgent_ansible_role) to install,\nconfigure and run `hapgent` as a systemd service under an unprivileged user on a Debian system.\n\nIt's configurable with the following ansible variables:\n\n| Variable Name                  | Default                                                               |\n|--------------------------------|-----------------------------------------------------------------------|\n| `hapgent_version`              | *0.3.2*                                                               |\n| `hapgent_user`                 | *nobody*                                                              |\n| `hapgent_group`                | *nogroup*                                                             |\n| `hapgent_checksum`             | *38b9b2f80fbdf046311127b22943efb464081812bf53de7ce0452968c916b434*    |\n| `hapgent_ip`                   | *0.0.0.0*                                                             |\n| `hapgent_port`                 | *9777*                                                                |\n| `hapgent_state_file`           | */etc/hapgent/state.json*                                             |\n| `hapgent_status`               | *FAIL*                                                                |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fepicfilemcnulty%2Fhapgent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fepicfilemcnulty%2Fhapgent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fepicfilemcnulty%2Fhapgent/lists"}