mirror of
https://github.com/gyptazy/ProxLB.git
synced 2026-04-06 04:41:58 +02:00
Compare commits
54 Commits
docs/insta
...
docs/209-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc663c0518 | ||
|
|
40de31bc3b | ||
|
|
5884d76ff4 | ||
|
|
7cc59eb6fc | ||
|
|
24b3b35640 | ||
|
|
f2b8829299 | ||
|
|
4b64a041cc | ||
|
|
bd1157127a | ||
|
|
be6e4bbfa0 | ||
|
|
25b631099c | ||
|
|
1d698c5688 | ||
|
|
40f848ad7f | ||
|
|
fd2725c878 | ||
|
|
34b1d72e40 | ||
|
|
ca7db26976 | ||
|
|
94552f9c9e | ||
|
|
32c67b9c96 | ||
|
|
89f337d8c3 | ||
|
|
8a724400b8 | ||
|
|
f96f1d0f64 | ||
|
|
15398712ee | ||
|
|
ddb9963062 | ||
|
|
f18a9f3d4c | ||
|
|
1402ba9732 | ||
|
|
af51f53221 | ||
|
|
bce2d640ef | ||
|
|
1bb1847e45 | ||
|
|
e9543db138 | ||
|
|
a8e8229787 | ||
|
|
d1c91c6f2a | ||
|
|
843691f8b4 | ||
|
|
c9f14946d1 | ||
|
|
77cd7b5388 | ||
|
|
55502f9bed | ||
|
|
f08b823cc4 | ||
|
|
f831d4044f | ||
|
|
e8d8d160a7 | ||
|
|
dbbd4c0ec8 | ||
|
|
fc9a0e2858 | ||
|
|
17eb43db94 | ||
|
|
06610e9b9d | ||
|
|
889b88fd6c | ||
|
|
c5ca3e13e0 | ||
|
|
c1c524f092 | ||
|
|
7ea7defa1f | ||
|
|
6147c0085b | ||
|
|
0b70a9c767 | ||
|
|
d6d22c4096 | ||
|
|
6da54c1255 | ||
|
|
b55b4ea7a0 | ||
|
|
51625fe09e | ||
|
|
f3b9d33c87 | ||
|
|
8e4326f77a | ||
|
|
3d642a7404 |
2
.changelogs/1.1.0/137_fix_systemd_unit_file.yml
Normal file
2
.changelogs/1.1.0/137_fix_systemd_unit_file.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Fix the systemd unit file to start ProxLB after pveproxy (by @robertdahlem). [#137]
|
||||
2
.changelogs/1.1.1/163_fix_ignore_vm_tag.yml
Normal file
2
.changelogs/1.1.1/163_fix_ignore_vm_tag.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Fix tag evluation for VMs for being ignored for further balancing [#163]
|
||||
2
.changelogs/1.1.1/165_improve_logging_servity.yml
Normal file
2
.changelogs/1.1.1/165_improve_logging_servity.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Improve logging verbosity of messages that had a wrong servity [#165]
|
||||
2
.changelogs/1.1.1/168_add_more_flexible_schedules.yml
Normal file
2
.changelogs/1.1.1/168_add_more_flexible_schedules.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
feature:
|
||||
- Add a more flexible way to define schedules in minutes or hours (by @gyptazy) [#168]
|
||||
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Fix Python path for Docker entrypoint (by @crandler) [#170]
|
||||
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Honor the value when balancing should not be performed and stop balancing [#174]
|
||||
@@ -0,0 +1,2 @@
|
||||
changed:
|
||||
- Change the default behaviour of the daemon mode to active [#176]
|
||||
@@ -0,0 +1,2 @@
|
||||
changed:
|
||||
- Change the default banalcing mode to used instead of assigned [#180]
|
||||
@@ -0,0 +1,2 @@
|
||||
feature:
|
||||
- Add validation for the minimum required permissions of a user in Proxmox [#184]
|
||||
@@ -0,0 +1,2 @@
|
||||
fix:
|
||||
- add handler to log messages with severity less than info to the screen when there is no systemd integration, for instance, inside a docker container (by @glitchvern) [#185]
|
||||
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- allow the use of minutes instead of hours and only accept hours or minutes in the format (by @glitchvern) [#187]
|
||||
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Set cpu_used to the cpu usage, which is a percent, times the total number of cores to get a number where guest cpu_used can be added to nodes cpu_used and be meaningful (by @glitchvern) [#195]
|
||||
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Remove hard coded memory usage from lowest usage node and use method and mode specified in configuration instead (by @glitchvern) [#197]
|
||||
2
.changelogs/1.1.1/200_requery_zero_guest_cpu_used.yml
Normal file
2
.changelogs/1.1.1/200_requery_zero_guest_cpu_used.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Requery a guest if that running guest reports 0 cpu usage (by @glitchvern) [#200]
|
||||
@@ -0,0 +1,2 @@
|
||||
fixed:
|
||||
- Fix the guest type relationship in the logs when a migration job failed (by @gyptazy) [#204]
|
||||
@@ -0,0 +1,2 @@
|
||||
added:
|
||||
- Providing the API upstream error message when migration fails in debug mode (by @gyptazy) [#205]
|
||||
1
.changelogs/1.1.1/release_meta.yml
Normal file
1
.changelogs/1.1.1/release_meta.yml
Normal file
@@ -0,0 +1 @@
|
||||
date: TBD
|
||||
@@ -25,4 +25,4 @@ COPY requirements.txt /app/requirements.txt
|
||||
RUN pip install --break-system-packages -r /app/requirements.txt
|
||||
|
||||
# Set the entry point to use the virtual environment's python
|
||||
ENTRYPOINT ["/bin/python3", "/app/proxlb/main.py"]
|
||||
ENTRYPOINT ["/usr/bin/python3", "/app/proxlb/main.py"]
|
||||
|
||||
88
README.md
88
README.md
@@ -20,6 +20,7 @@
|
||||
6. [Affinity & Anti-Affinity Rules](#affinity--anti-affinity-rules)
|
||||
1. [Affinity Rules](#affinity-rules)
|
||||
2. [Anti-Affinity Rules](#anti-affinity-rules)
|
||||
3. [Ignore VMs](#ignore-vms)
|
||||
7. [Maintenance](#maintenance)
|
||||
8. [Misc](#misc)
|
||||
1. [Bugs](#bugs)
|
||||
@@ -153,7 +154,7 @@ vi proxlb.yaml
|
||||
docker run -it --rm -v $(pwd)/proxlb.yaml:/etc/proxlb/proxlb.yaml proxlb
|
||||
```
|
||||
|
||||
*Note: ProxLB container images are officially only available at cr.proxlb.de and cr.gyptazy.com.*
|
||||
*Note: ProxLB container images are officially only available at cr.proxlb.de and cr.gyptazy.com.*
|
||||
|
||||
#### Overview of Images
|
||||
| Version | Image |
|
||||
@@ -231,35 +232,38 @@ See also: [#65: Host groups: Honour HA groups](https://github.com/gyptazy/ProxLB
|
||||
### Options
|
||||
The following options can be set in the configuration file `proxlb.yaml`:
|
||||
|
||||
| Section | Option | Example | Type | Description |
|
||||
|------|:------:|:------:|:------:|:------:|
|
||||
| `proxmox_api` | | | | |
|
||||
| | hosts | ['virt01.example.com', '10.10.10.10', 'fe01::bad:code::cafe'] | `List` | List of Proxmox nodes. Can be IPv4, IPv6 or mixed. |
|
||||
| | user | root@pam | `Str` | Username for the API. |
|
||||
| | pass | FooBar | `Str` | Password for the API. (Recommended: Use API token authorization!) |
|
||||
| | token_id | proxlb | `Str` | Token ID of the user for the API. |
|
||||
| | token_secret | 430e308f-1337-1337-beef-1337beefcafe | `Str` | Secret of the token ID for the API. |
|
||||
| | ssl_verification | True | `Bool` | Validate SSL certificates (1) or ignore (0). (default: 1, type: bool) |
|
||||
| | timeout | 10 | `Int` | Timeout for the Proxmox API in sec. (default: 10) |
|
||||
| `proxmox_cluster` | | | | |
|
||||
| | maintenance_nodes | ['virt66.example.com'] | `List` | A list of Proxmox nodes that are defined to be in a maintenance. (default: []) |
|
||||
| | ignore_nodes | [] | `List` | A list of Proxmox nodes that are defined to be ignored. (default: []) |
|
||||
| | overprovisioning | False | `Bool` | Avoids balancing when nodes would become overprovisioned. |
|
||||
| `balancing` | | | | |
|
||||
| | enable | True | `Bool` | Enables the guest balancing. (default: True)|
|
||||
| | enforce_affinity | True | `Bool` | Enforcing affinity/anti-affinity rules but balancing might become worse. (default: False) |
|
||||
| | parallel | False | `Bool` | If guests should be moved in parallel or sequentially. (default: False)|
|
||||
| | live | True | `Bool` | If guests should be moved live or shutdown. (default: True)|
|
||||
| | with_local_disks | True | `Bool` | If balancing of guests should include local disks (default: True)|
|
||||
| | balance_types | ['vm', 'ct'] | `List` | Defined the types of guests that should be honored. (default: ['vm', 'ct']) |
|
||||
| | max_job_validation | 1800 | `Int` | How long a job validation may take in seconds. (default: 1800) |
|
||||
| | balanciness | 10 | `Int` | The maximum delta of resource usage between node with highest and lowest usage. (default: 10) |
|
||||
| | method | memory | `Str` | The balancing method that should be used. (default: memory | choices: memory, cpu, disk)|
|
||||
| | mode | used | `Str` | The balancing mode that should be used. (default: used | choices: used, assigned)|
|
||||
| `service` | | | | |
|
||||
| | daemon | False | `Bool` | If daemon mode should be activated (default: False)|
|
||||
| | schedule | 12 | `Int` | How often rebalancing should occur in hours in daemon mode (default: 12)|
|
||||
| | log_level | INFO | `Str` | Defines the default log level that should be logged. (default: INFO) |
|
||||
| Section | Option | Sub Option | Example | Type | Description |
|
||||
|---------|:------:|:----------:|:-------:|:----:|:-----------:|
|
||||
| `proxmox_api` | | | | | |
|
||||
| | hosts | | ['virt01.example.com', '10.10.10.10', 'fe01::bad:code::cafe'] | `List` | List of Proxmox nodes. Can be IPv4, IPv6 or mixed. |
|
||||
| | user | | root@pam | `Str` | Username for the API. |
|
||||
| | pass | | FooBar | `Str` | Password for the API. (Recommended: Use API token authorization!) |
|
||||
| | token_id | | proxlb | `Str` | Token ID of the user for the API. |
|
||||
| | token_secret | | 430e308f-1337-1337-beef-1337beefcafe | `Str` | Secret of the token ID for the API. |
|
||||
| | ssl_verification | | True | `Bool` | Validate SSL certificates (1) or ignore (0). [values: `1` (default), `0`] |
|
||||
| | timeout | | 10 | `Int` | Timeout for the Proxmox API in sec. |
|
||||
| `proxmox_cluster` | | | | | |
|
||||
| | maintenance_nodes | | ['virt66.example.com'] | `List` | A list of Proxmox nodes that are defined to be in a maintenance. |
|
||||
| | ignore_nodes | | [] | `List` | A list of Proxmox nodes that are defined to be ignored. |
|
||||
| | overprovisioning | | False | `Bool` | Avoids balancing when nodes would become overprovisioned. |
|
||||
| `balancing` | | | | | |
|
||||
| | enable | | True | `Bool` | Enables the guest balancing.|
|
||||
| | enforce_affinity | | True | `Bool` | Enforcing affinity/anti-affinity rules but balancing might become worse. |
|
||||
| | parallel | | False | `Bool` | If guests should be moved in parallel or sequentially.|
|
||||
| | live | | True | `Bool` | If guests should be moved live or shutdown.|
|
||||
| | with_local_disks | | True | `Bool` | If balancing of guests should include local disks.|
|
||||
| | balance_types | | ['vm', 'ct'] | `List` | Defined the types of guests that should be honored. [values: `vm`, `ct`]|
|
||||
| | max_job_validation | | 1800 | `Int` | How long a job validation may take in seconds. (default: 1800) |
|
||||
| | balanciness | | 10 | `Int` | The maximum delta of resource usage between node with highest and lowest usage. |
|
||||
| | method | | memory | `Str` | The balancing method that should be used. [values: `memory` (default), `cpu`, `disk`]|
|
||||
| | mode | | used | `Str` | The balancing mode that should be used. [values: `used` (default), `assigned`] |
|
||||
| `service` | | | | | |
|
||||
| | daemon | | True | `Bool` | If daemon mode should be activated. |
|
||||
| | `schedule` | | | `Dict` | Schedule config block for rebalancing. |
|
||||
| | | interval | 12 | `Int` | How often rebalancing should occur in daemon mode.|
|
||||
| | | format | hours | `Str` | Sets the time format. [values: `hours` (default), `minutes`]|
|
||||
| | log_level | | INFO | `Str` | Defines the default log level that should be logged. [values: `INFO` (default), `WARNING`, `CRITICAL`, `DEBUG`] |
|
||||
|
||||
|
||||
An example of the configuration file looks like:
|
||||
```
|
||||
@@ -287,11 +291,13 @@ balancing:
|
||||
max_job_validation: 1800
|
||||
balanciness: 5
|
||||
method: memory
|
||||
mode: assigned
|
||||
mode: used
|
||||
|
||||
service:
|
||||
daemon: True
|
||||
schedule: 12
|
||||
schedule:
|
||||
interval: 12
|
||||
format: hours
|
||||
log_level: INFO
|
||||
```
|
||||
|
||||
@@ -300,7 +306,7 @@ The following options and parameters are currently supported:
|
||||
|
||||
| Option | Long Option | Description | Default |
|
||||
|------|:------:|------:|------:|
|
||||
| -c | --config | Path to a config file. | /etc/proxlb/proxlb.conf (default) |
|
||||
| -c | --config | Path to a config file. | /etc/proxlb/proxlb.yaml (default) |
|
||||
| -d | --dry-run | Performs a dry-run without doing any actions. | False |
|
||||
| -j | --json | Returns a JSON of the VM movement. | False |
|
||||
| -b | --best-node | Returns the best next node for a VM/CT placement (useful for further usage with Terraform/Ansible). | False |
|
||||
@@ -337,6 +343,20 @@ As a result, ProxLB will try to place the VMs with the `plb_anti_affinity_ntp` t
|
||||
|
||||
**Note:** While this ensures that ProxLB tries distribute these VMs across different physical hosts within the Proxmox cluster this may not always work. If you have more guests attached to the group than nodes in the cluster, we still need to run them anywhere. If this case occurs, the next one with the most free resources will be selected.
|
||||
|
||||
### Ignore VMs / CTs
|
||||
<img align="left" src="https://cdn.gyptazy.com/images/proxlb-ignore-vm-movement.jpg"/> Guests, such as VMs or CTs, can also be completely ignored. This means, they won't be affected by any migration (even when (anti-)affinity rules are enforced). To ensure a proper resource evaluation, these guests are still collected and evaluated but simply skipped for balancing actions. Another thing is the implementation. While ProxLB might have a very restricted configuration file including the file permissions, this file is only read- and writeable by the Proxmox administrators. However, we might have user and groups who want to define on their own that their systems shouldn't be moved. Therefore, these users can simpy set a specific tag to the guest object - just like the (anti)affinity rules.
|
||||
|
||||
To define a guest to be ignored from the balancing, users assign a tag with the prefix `plb_ignore_$TAG`:
|
||||
|
||||
#### Example for Screenshot
|
||||
```
|
||||
plb_ignore_dev
|
||||
```
|
||||
|
||||
As a result, ProxLB will not migrate this guest with the `plb_ignore_dev` tag to any other node.
|
||||
|
||||
**Note:** Ignored guests are really ignored. Even by enforcing affinity rules this guest will be ignored.
|
||||
|
||||
## Maintenance
|
||||
<img src="https://cdn.gyptazy.com/images/proxlb-rebalancing-demo.gif"/>
|
||||
|
||||
@@ -375,4 +395,4 @@ Connect with us in our dedicated chat room for immediate support and live intera
|
||||
**Note:** Please always keep in mind that this is a one-man show project without any further help. This includes coding, testing, packaging and all the infrastructure around it to keep this project up and running.
|
||||
|
||||
### Author(s)
|
||||
* Florian Paul Azim Hoberg @gyptazy (https://gyptazy.com)
|
||||
* Florian Paul Azim Hoberg @gyptazy (https://gyptazy.com)
|
||||
|
||||
@@ -23,9 +23,11 @@ balancing:
|
||||
max_job_validation: 1800
|
||||
balanciness: 5
|
||||
method: memory
|
||||
mode: assigned
|
||||
mode: used
|
||||
|
||||
service:
|
||||
daemon: False
|
||||
schedule: 12
|
||||
daemon: True
|
||||
schedule:
|
||||
interval: 12
|
||||
format: hours
|
||||
log_level: INFO
|
||||
|
||||
7
debian/changelog
vendored
7
debian/changelog
vendored
@@ -1,3 +1,10 @@
|
||||
proxlb (1.1.1) stable; urgency=medium
|
||||
|
||||
* Fix tag evluation for VMs for being ignored for further balancing. (Closes: #163)
|
||||
* Improve logging verbosity of messages that had a wrong servity. (Closes: #165)
|
||||
|
||||
-- Florian Paul Azim Hoberg <gyptazy@gyptazy.com> Tue, 1 Apr 2025 18:55:02 +0000
|
||||
|
||||
proxlb (1.1.0) stable; urgency=medium
|
||||
|
||||
* Refactored code base of ProxLB. (Closes: #114)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
1. [Affinity Rules](#affinity-rules)
|
||||
2. [Anti-Affinity Rules](#anti-affinity-rules)
|
||||
3. [Affinity / Anti-Affinity Enforcing](#affinity--anti-affinity-enforcing)
|
||||
4. [Ignore VMs](#ignore-vms)
|
||||
2. [API Loadbalancing](#api-loadbalancing)
|
||||
3. [Ignore Host-Nodes or Guests](#ignore-host-nodes-or-guests)
|
||||
4. [IPv6 Support](#ipv6-support)
|
||||
@@ -38,18 +39,21 @@ pveum acl modify / --roles proxlb --users proxlb@pve
|
||||
*Note: The user management can also be done on the WebUI without invoking the CLI.*
|
||||
|
||||
### Creating an API Token for a User
|
||||
|
||||
Create an API token for user proxlb@pve with token ID proxlb and deactivated privilege separation:
|
||||
```
|
||||
# Create an API token for user proxlb@pve with token ID proxlb
|
||||
pveum user token add proxlb@pve proxlb
|
||||
pveum user token add proxlb@pve proxlb --privsep 0
|
||||
```
|
||||
|
||||
Afterwards, you get the token secret returned. You can now add those entries to your ProxLB config.
|
||||
Afterwards, you get the token secret returned. You can now add those entries to your ProxLB config. Make sure, that you also keep the `user` parameter, next to the new token parameters.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The parameter `pass` then needs to be **absent**! You should also take care about the privilege and authentication mechanism behind Proxmox. You might want or even might not want to use privilege separation and this is up to your personal needs and use case.
|
||||
|
||||
| Proxmox API | ProxLB Config | Example |
|
||||
|---|---|---|
|
||||
| User | [user](https://github.com/gyptazy/ProxLB/blob/main/config/proxlb_example.yaml#L3) | proxlb@pve |
|
||||
| Token ID | [token_id](https://github.com/gyptazy/ProxLB/blob/main/config/proxlb_example.yaml#L6) | proxlb |
|
||||
| Secret | [token_secret](https://github.com/gyptazy/ProxLB/blob/main/config/proxlb_example.yaml#L7) | 430e308f-1337-1337-beef-1337beefcafe |
|
||||
| Token Secret | [token_secret](https://github.com/gyptazy/ProxLB/blob/main/config/proxlb_example.yaml#L7) | 430e308f-1337-1337-beef-1337beefcafe |
|
||||
|
||||
*Note: The API token configuration can also be done on the WebUI without invoking the CLI.*
|
||||
|
||||
@@ -106,8 +110,22 @@ balancing:
|
||||
|
||||
*Note: This may have impacts to the cluster. Depending on the created group matrix, the result may also be an unbalanced cluster.*
|
||||
|
||||
### Ignore VMs / CTs
|
||||
<img align="left" src="https://cdn.gyptazy.com/images/proxlb-ignore-vm-movement.jpg"/> Guests, such as VMs or CTs, can also be completely ignored. This means, they won't be affected by any migration (even when (anti-)affinity rules are enforced). To ensure a proper resource evaluation, these guests are still collected and evaluated but simply skipped for balancing actions. Another thing is the implementation. While ProxLB might have a very restricted configuration file including the file permissions, this file is only read- and writeable by the Proxmox administrators. However, we might have user and groups who want to define on their own that their systems shouldn't be moved. Therefore, these users can simpy set a specific tag to the guest object - just like the (anti)affinity rules.
|
||||
|
||||
To define a guest to be ignored from the balancing, users assign a tag with the prefix `plb_ignore_$TAG`:
|
||||
|
||||
#### Example for Screenshot
|
||||
```
|
||||
plb_ignore_dev
|
||||
```
|
||||
|
||||
As a result, ProxLB will not migrate this guest with the `plb_ignore_dev` tag to any other node.
|
||||
|
||||
**Note:** Ignored guests are really ignored. Even by enforcing affinity rules this guest will be ignored.
|
||||
|
||||
### API Loadbalancing
|
||||
ProxLB supports API loadbalancing, where one or more host objects can be defined as a list. This ensures, that you can even operator ProxLB without furhter changes when one or more nodes are offline or in a maintence. When defining multiple hosts, the first reachable one will be picked.
|
||||
ProxLB supports API loadbalancing, where one or more host objects can be defined as a list. This ensures, that you can even operator ProxLB without further changes when one or more nodes are offline or in a maintenance. When defining multiple hosts, the first reachable one will be picked.
|
||||
|
||||
```
|
||||
proxmox_api:
|
||||
@@ -155,13 +173,15 @@ The proxlb systemd unit orchestrates the ProxLB application. ProxLB can be used
|
||||
```
|
||||
service:
|
||||
daemon: False
|
||||
schedule: 12
|
||||
schedule:
|
||||
interval: 12
|
||||
format: hours
|
||||
```
|
||||
|
||||
In this configuration:
|
||||
|
||||
* `daemon`: False indicates that the ProxLB application is not running as a daemon and will execute as a one-shot solution.
|
||||
* `schedule`: 12 defines the schedule in hours, specifying how often rebalancing should be done if running as a daemon.
|
||||
* `schedule`: 12 defines the interval for the schedule, specifying how often rebalancing should be done if running as a daemon.
|
||||
* `format`: Defines the given format of schedule where you can choose between `hours` or `minutes`.
|
||||
|
||||
### SSL Self-Signed Certificates
|
||||
If you are using SSL self-signed certificates or non-valid certificated in general and do not want to deal with additional trust levels, you may also disable the SSL validation. This may mostly be helpful for dev- & test labs.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
VERSION="1.1.0"
|
||||
VERSION="1.1.1"
|
||||
|
||||
sed -i "s/^__version__ = .*/__version__ = \"$VERSION\"/" "proxlb/utils/version.py"
|
||||
sed -i "s/version=\"[0-9]*\.[0-9]*\.[0-9]*\"/version=\"$VERSION\"/" setup.py
|
||||
|
||||
@@ -54,7 +54,7 @@ def main():
|
||||
# Get all required objects from the Proxmox cluster
|
||||
meta = {"meta": proxlb_config}
|
||||
nodes = Nodes.get_nodes(proxmox_api, proxlb_config)
|
||||
guests = Guests.get_guests(proxmox_api, nodes)
|
||||
guests = Guests.get_guests(proxmox_api, nodes, meta)
|
||||
groups = Groups.get_groups(guests, nodes)
|
||||
|
||||
# Merge obtained objects from the Proxmox cluster for further usage
|
||||
@@ -71,9 +71,12 @@ def main():
|
||||
Helper.log_node_metrics(proxlb_data, init=False)
|
||||
|
||||
# Perform balancing actions via Proxmox API
|
||||
if not cli_args.dry_run:
|
||||
if not cli_args.dry_run or not proxlb_data["meta"]["balancing"].get("enable", False):
|
||||
Balancing(proxmox_api, proxlb_data)
|
||||
|
||||
# Validate if the JSON output should be
|
||||
# printed to stdout
|
||||
Helper.print_json(proxlb_data, cli_args.json)
|
||||
# Validate daemon mode
|
||||
Helper.get_daemon_mode(proxlb_config)
|
||||
|
||||
|
||||
@@ -52,22 +52,30 @@ class Balancing:
|
||||
"""
|
||||
for guest_name, guest_meta in proxlb_data["guests"].items():
|
||||
|
||||
# Check if the guest's target is not the same as the current node
|
||||
if guest_meta["node_current"] != guest_meta["node_target"]:
|
||||
guest_id = guest_meta["id"]
|
||||
guest_node_current = guest_meta["node_current"]
|
||||
guest_node_target = guest_meta["node_target"]
|
||||
# Check if the guest is not ignored and perform the balancing
|
||||
# operation based on the guest type
|
||||
if not guest_meta["ignore"]:
|
||||
guest_id = guest_meta["id"]
|
||||
guest_node_current = guest_meta["node_current"]
|
||||
guest_node_target = guest_meta["node_target"]
|
||||
|
||||
# VM Balancing
|
||||
if guest_meta["type"] == "vm":
|
||||
self.exec_rebalancing_vm(proxmox_api, proxlb_data, guest_name)
|
||||
# VM Balancing
|
||||
if guest_meta["type"] == "vm":
|
||||
self.exec_rebalancing_vm(proxmox_api, proxlb_data, guest_name)
|
||||
|
||||
# CT Balancing
|
||||
elif guest_meta["type"] == "ct":
|
||||
self.exec_rebalancing_ct(proxmox_api, proxlb_data, guest_name)
|
||||
# CT Balancing
|
||||
elif guest_meta["type"] == "ct":
|
||||
self.exec_rebalancing_ct(proxmox_api, proxlb_data, guest_name)
|
||||
|
||||
# Hopefully never reaching, but should be catched
|
||||
# Just in case we get a new type of guest in the future
|
||||
else:
|
||||
logger.critical(f"Balancing: Got unexpected guest type: {guest_meta['type']}. Cannot proceed guest: {guest_meta['name']}.")
|
||||
else:
|
||||
logger.critical(f"Balancing: Got unexpected guest type: {guest_meta['type']}. Cannot proceed guest: {guest_meta['name']}.")
|
||||
logger.debug(f"Balancing: Guest {guest_name} is ignored and will not be rebalanced.")
|
||||
else:
|
||||
logger.debug(f"Balancing: Guest {guest_name} is already on the target node {guest_meta['node_target']} and will not be rebalanced.")
|
||||
|
||||
def exec_rebalancing_vm(self, proxmox_api: any, proxlb_data: Dict[str, Any], guest_name: str) -> None:
|
||||
"""
|
||||
@@ -108,10 +116,10 @@ class Balancing:
|
||||
try:
|
||||
logger.debug(f"Balancing: Starting to migrate guest {guest_name} of type VM.")
|
||||
job_id = proxmox_api.nodes(guest_node_current).qemu(guest_id).migrate().post(**migration_options)
|
||||
job = self.get_rebalancing_job_status(proxmox_api, proxlb_data, guest_name, guest_node_current, job_id)
|
||||
self.get_rebalancing_job_status(proxmox_api, proxlb_data, guest_name, guest_node_current, job_id)
|
||||
except proxmoxer.core.ResourceException as proxmox_api_error:
|
||||
logger.critical(f"Balancing: Failed to migrate guest {guest_name} of type CT due to some Proxmox errors. Please check if resource is locked or similar.")
|
||||
|
||||
logger.critical(f"Balancing: Failed to migrate guest {guest_name} of type VM due to some Proxmox errors. Please check if resource is locked or similar.")
|
||||
logger.debug(f"Balancing: Failed to migrate guest {guest_name} of type VM due to some Proxmox errors: {proxmox_api_error}")
|
||||
logger.debug("Finished: exec_rebalancing_vm.")
|
||||
|
||||
def exec_rebalancing_ct(self, proxmox_api: any, proxlb_data: Dict[str, Any], guest_name: str) -> None:
|
||||
@@ -137,10 +145,10 @@ class Balancing:
|
||||
try:
|
||||
logger.debug(f"Balancing: Starting to migrate guest {guest_name} of type CT.")
|
||||
job_id = proxmox_api.nodes(guest_node_current).lxc(guest_id).migrate().post(target=guest_node_target, restart=1)
|
||||
job = self.get_rebalancing_job_status(proxmox_api, proxlb_data, guest_name, guest_node_current, job_id)
|
||||
self.get_rebalancing_job_status(proxmox_api, proxlb_data, guest_name, guest_node_current, job_id)
|
||||
except proxmoxer.core.ResourceException as proxmox_api_error:
|
||||
logger.critical(f"Balancing: Failed to migrate guest {guest_name} of type CT due to some Proxmox errors. Please check if resource is locked or similar.")
|
||||
|
||||
logger.debug(f"Balancing: Failed to migrate guest {guest_name} of type CT due to some Proxmox errors: {proxmox_api_error}")
|
||||
logger.debug("Finished: exec_rebalancing_ct.")
|
||||
|
||||
def get_rebalancing_job_status(self, proxmox_api: any, proxlb_data: Dict[str, Any], guest_name: str, guest_current_node: str, job_id: int, retry_counter: int = 1) -> bool:
|
||||
@@ -184,7 +192,7 @@ class Balancing:
|
||||
if job["status"] == "stopped":
|
||||
|
||||
if job["exitstatus"] == "OK":
|
||||
logger.debug(f"Balancing: Job ID {job_id} (guest: {guest_name}) was sucessfully.")
|
||||
logger.debug(f"Balancing: Job ID {job_id} (guest: {guest_name}) was successfully.")
|
||||
logger.debug("Finished: get_rebalancing_job_status.")
|
||||
return True
|
||||
else:
|
||||
|
||||
@@ -66,7 +66,7 @@ class Calculations:
|
||||
@staticmethod
|
||||
def set_node_assignments(proxlb_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Set the assigned ressources of the nodes based on the current assigned
|
||||
Set the assigned resources of the nodes based on the current assigned
|
||||
guest resources by their created groups as an initial base.
|
||||
|
||||
Args:
|
||||
@@ -119,10 +119,8 @@ class Calculations:
|
||||
if method_value_highest - method_value_lowest > balanciness:
|
||||
proxlb_data["meta"]["balancing"]["balance"] = True
|
||||
logger.debug(f"Guest balancing is required. Highest value: {method_value_highest}, lowest value: {method_value_lowest} balanced by {method} and {mode}.")
|
||||
logger.critical(f"Guest balancing is required. Highest value: {method_value_highest}, lowest value: {method_value_lowest} balanced by {method} and {mode}.")
|
||||
else:
|
||||
logger.debug(f"Guest balancing is ok. Highest value: {method_value_highest}, lowest value: {method_value_lowest} balanced by {method} and {mode}.")
|
||||
logger.critical(f"Guest balancing is ok. Highest value: {method_value_highest}, lowest value: {method_value_lowest} balanced by {method} and {mode}.")
|
||||
|
||||
else:
|
||||
logger.warning("No guests for balancing found.")
|
||||
@@ -149,7 +147,9 @@ class Calculations:
|
||||
|
||||
# Do not include nodes that are marked in 'maintenance'
|
||||
filtered_nodes = [node for node in proxlb_data["nodes"].values() if not node["maintenance"]]
|
||||
lowest_usage_node = min(filtered_nodes, key=lambda x: x["memory_used_percent"])
|
||||
method = proxlb_data["meta"]["balancing"].get("method", "memory")
|
||||
mode = proxlb_data["meta"]["balancing"].get("mode", "used")
|
||||
lowest_usage_node = min(filtered_nodes, key=lambda x: x[f"{method}_{mode}_percent"])
|
||||
proxlb_data["meta"]["balancing"]["balance_reason"] = 'resources'
|
||||
proxlb_data["meta"]["balancing"]["balance_next_node"] = lowest_usage_node["name"]
|
||||
|
||||
@@ -207,13 +207,13 @@ class Calculations:
|
||||
None
|
||||
"""
|
||||
logger.debug("Starting: relocate_guests.")
|
||||
if proxlb_data["meta"]["balancing"]["balance"] or proxlb_data["meta"]["balancing"]["enforce_affinity"]:
|
||||
if proxlb_data["meta"]["balancing"]["balance"] or proxlb_data["meta"]["balancing"].get("enforce_affinity", False):
|
||||
|
||||
if proxlb_data["meta"]["balancing"].get("balance", False):
|
||||
logger.debug("Balancing of guests will be performt. Reason: balanciness")
|
||||
logger.debug("Balancing of guests will be performed. Reason: balanciness")
|
||||
|
||||
if proxlb_data["meta"]["balancing"].get("enforce_affinity", False):
|
||||
logger.debug("Balancing of guests will be performt. Reason: enforce affinity balancing")
|
||||
logger.debug("Balancing of guests will be performed. Reason: enforce affinity balancing")
|
||||
|
||||
for group_name in proxlb_data["groups"]["affinity"]:
|
||||
|
||||
@@ -248,10 +248,10 @@ class Calculations:
|
||||
None
|
||||
"""
|
||||
logger.debug("Starting: val_anti_affinity.")
|
||||
# Start by interating over all defined anti-affinity groups
|
||||
# Start by iterating over all defined anti-affinity groups
|
||||
for group_name in proxlb_data["groups"]["anti_affinity"].keys():
|
||||
|
||||
# Validate if the provided guest ist included in the anti-affinity group
|
||||
# Validate if the provided guest is included in the anti-affinity group
|
||||
if guest_name in proxlb_data["groups"]["anti_affinity"][group_name]['guests'] and not proxlb_data["guests"][guest_name]["processed"]:
|
||||
logger.debug(f"Anti-Affinity: Guest: {guest_name} is included in anti-affinity group: {group_name}.")
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ __license__ = "GPL-3.0"
|
||||
from typing import Dict, Any
|
||||
from utils.logger import SystemdLogger
|
||||
from models.tags import Tags
|
||||
import time
|
||||
|
||||
logger = SystemdLogger()
|
||||
|
||||
@@ -34,7 +35,7 @@ class Guests:
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_guests(proxmox_api: any, nodes: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def get_guests(proxmox_api: any, nodes: Dict[str, Any], meta: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Get metrics of all guests in a Proxmox cluster.
|
||||
|
||||
@@ -61,10 +62,22 @@ class Guests:
|
||||
# resource metrics for rebalancing to ensure that we do not overprovisiong the node.
|
||||
for guest in proxmox_api.nodes(node).qemu.get():
|
||||
if guest['status'] == 'running':
|
||||
|
||||
# If the balancing method is set to cpu, we need to wait for the guest to report
|
||||
# cpu usage. This is important for the balancing process to ensure that we do not
|
||||
# wait for a guest for an infinite time.
|
||||
if meta["meta"]["balancing"]["method"] == "cpu":
|
||||
retry_counter = 0
|
||||
while guest['cpu'] == 0 and retry_counter < 10:
|
||||
guest = proxmox_api.nodes(node).qemu(guest['vmid']).status.current.get()
|
||||
logger.debug(f"Guest {guest['name']} (type VM) is reporting {guest['cpu']} cpu usage on retry {retry_counter}.")
|
||||
retry_counter += 1
|
||||
time.sleep(1)
|
||||
|
||||
guests['guests'][guest['name']] = {}
|
||||
guests['guests'][guest['name']]['name'] = guest['name']
|
||||
guests['guests'][guest['name']]['cpu_total'] = guest['cpus']
|
||||
guests['guests'][guest['name']]['cpu_used'] = guest['cpu']
|
||||
guests['guests'][guest['name']]['cpu_used'] = guest['cpu'] * guest['cpus']
|
||||
guests['guests'][guest['name']]['memory_total'] = guest['maxmem']
|
||||
guests['guests'][guest['name']]['memory_used'] = guest['mem']
|
||||
guests['guests'][guest['name']]['disk_total'] = guest['maxdisk']
|
||||
|
||||
@@ -63,7 +63,7 @@ class Nodes:
|
||||
nodes["nodes"][node["node"]]["maintenance"] = False
|
||||
nodes["nodes"][node["node"]]["cpu_total"] = node["maxcpu"]
|
||||
nodes["nodes"][node["node"]]["cpu_assigned"] = 0
|
||||
nodes["nodes"][node["node"]]["cpu_used"] = node["cpu"]
|
||||
nodes["nodes"][node["node"]]["cpu_used"] = node["cpu"] * node["maxcpu"]
|
||||
nodes["nodes"][node["node"]]["cpu_free"] = (node["maxcpu"]) - (node["cpu"] * node["maxcpu"])
|
||||
nodes["nodes"][node["node"]]["cpu_assigned_percent"] = nodes["nodes"][node["node"]]["cpu_assigned"] / nodes["nodes"][node["node"]]["cpu_total"] * 100
|
||||
nodes["nodes"][node["node"]]["cpu_free_percent"] = nodes["nodes"][node["node"]]["cpu_free"] / node["maxcpu"] * 100
|
||||
|
||||
@@ -139,7 +139,7 @@ class Tags:
|
||||
tags (List): A list holding all defined tags for a given guest.
|
||||
|
||||
Returns:
|
||||
Bool: Returns a bool that indicates wether to ignore a guest or not.
|
||||
Bool: Returns a bool that indicates whether to ignore a guest or not.
|
||||
"""
|
||||
logger.debug("Starting: get_ignore.")
|
||||
ignore_tag = False
|
||||
|
||||
@@ -8,6 +8,7 @@ __copyright__ = "Copyright (C) 2025 Florian Paul Azim Hoberg (@gyptazy)"
|
||||
__license__ = "GPL-3.0"
|
||||
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import sys
|
||||
import time
|
||||
@@ -115,12 +116,49 @@ class Helper:
|
||||
None
|
||||
"""
|
||||
logger.debug("Starting: get_daemon_mode.")
|
||||
if proxlb_config.get("service", {}).get("daemon", False):
|
||||
sleep_seconds = proxlb_config.get("service", {}).get("schedule", 12) * 3600
|
||||
logger.info(f"Daemon mode active: Next run in: {proxlb_config.get('service', {}).get('schedule', 12)} hours.")
|
||||
if proxlb_config.get("service", {}).get("daemon", True):
|
||||
|
||||
# Validate schedule format which changed in v1.1.1
|
||||
if type(proxlb_config["service"].get("schedule", None)) != dict:
|
||||
logger.error("Invalid format for schedule. Please use 'hours' or 'minutes'.")
|
||||
sys.exit(1)
|
||||
|
||||
# Convert hours to seconds
|
||||
if proxlb_config["service"]["schedule"].get("format", "hours") == "hours":
|
||||
sleep_seconds = proxlb_config.get("service", {}).get("schedule", {}).get("interval", 12) * 3600
|
||||
# Convert minutes to seconds
|
||||
elif proxlb_config["service"]["schedule"].get("format", "hours") == "minutes":
|
||||
sleep_seconds = proxlb_config.get("service", {}).get("schedule", {}).get("interval", 720) * 60
|
||||
else:
|
||||
logger.error("Invalid format for schedule. Please use 'hours' or 'minutes'.")
|
||||
sys.exit(1)
|
||||
|
||||
logger.info(f"Daemon mode active: Next run in: {proxlb_config.get('service', {}).get('schedule', {}).get('interval', 12)} {proxlb_config['service']['schedule'].get('format', 'hours')}.")
|
||||
time.sleep(sleep_seconds)
|
||||
|
||||
else:
|
||||
logger.debug("Daemon mode is not active.")
|
||||
logger.debug("Successfully executed ProxLB. Daemon mode not active - stopping.")
|
||||
print("Daemon mode not active - stopping.")
|
||||
sys.exit(0)
|
||||
|
||||
logger.debug("Finished: get_daemon_mode.")
|
||||
|
||||
@staticmethod
|
||||
def print_json(proxlb_config: Dict[str, Any], print_json: bool = False) -> None:
|
||||
"""
|
||||
Prints the calculated balancing matrix as a JSON output to stdout.
|
||||
|
||||
Parameters:
|
||||
proxlb_config (Dict[str, Any]): A dictionary containing the ProxLB configuration.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
logger.debug("Starting: print_json.")
|
||||
if print_json:
|
||||
# Create a filtered list by stripping the 'meta' key from the proxlb_config dictionary
|
||||
# to make sure that no credentials are leaked.
|
||||
filtered_data = {k: v for k, v in proxlb_config.items() if k != "meta"}
|
||||
print(json.dumps(filtered_data, indent=4))
|
||||
|
||||
logger.debug("Finished: print_json.")
|
||||
|
||||
@@ -9,6 +9,7 @@ __license__ = "GPL-3.0"
|
||||
|
||||
|
||||
import logging
|
||||
import sys
|
||||
try:
|
||||
from systemd.journal import JournalHandler
|
||||
SYSTEMD_PRESENT = True
|
||||
@@ -82,17 +83,22 @@ class SystemdLogger:
|
||||
self.logger = logging.getLogger(name)
|
||||
self.logger.setLevel(level)
|
||||
|
||||
# Create a JournalHandler for systemd integration if this
|
||||
# is supported on the underlying OS.
|
||||
# Create a logging handler depending on the
|
||||
# capabilities of the underlying OS where systemd
|
||||
# logging is preferred.
|
||||
if SYSTEMD_PRESENT:
|
||||
# Add a JournalHandler for systemd integration
|
||||
journal_handler = JournalHandler()
|
||||
journal_handler.setLevel(level)
|
||||
# Set a formatter to include the logger's name and log message
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
journal_handler.setFormatter(formatter)
|
||||
# Add handler to logger
|
||||
self.logger.addHandler(journal_handler)
|
||||
handler = JournalHandler()
|
||||
else:
|
||||
# Add a stdout handler as a fallback
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
|
||||
handler.setLevel(level)
|
||||
# Set a formatter to include the logger's name and log message
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
# Add handler to logger
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
def set_log_level(self, level: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -94,6 +94,7 @@ class ProxmoxApi:
|
||||
"""
|
||||
logger.debug("Starting: ProxmoxApi initialization.")
|
||||
self.proxmox_api = self.api_connect(proxlb_config)
|
||||
self.test_api_user_permissions(self.proxmox_api)
|
||||
logger.debug("Finished: ProxmoxApi initialization.")
|
||||
|
||||
def __getattr__(self, name):
|
||||
@@ -115,7 +116,7 @@ class ProxmoxApi:
|
||||
"token_id" and "token_secret" keys for API token authentication.
|
||||
|
||||
Raises:
|
||||
SystemExit: If both username/password and API token authentication methods are
|
||||
SystemExit: If both pass/token_secret and API token authentication methods are
|
||||
provided, the function will log a critical error message and terminate
|
||||
the program.
|
||||
|
||||
@@ -130,10 +131,10 @@ class ProxmoxApi:
|
||||
sys.exit(1)
|
||||
|
||||
proxlb_credentials = proxlb_config["proxmox_api"]
|
||||
present_auth_user = "user" in proxlb_credentials
|
||||
present_auth_token = "token_id" in proxlb_credentials
|
||||
present_auth_pass = "pass" in proxlb_credentials
|
||||
present_auth_secret = "token_secret" in proxlb_credentials
|
||||
|
||||
if present_auth_user and present_auth_token:
|
||||
if present_auth_pass and present_auth_secret:
|
||||
logger.critical(f"Username/password and API token authentication are mutal exclusive. Please use only one!")
|
||||
print(f"Username/password and API token authentication are mutal exclusive. Please use only one!")
|
||||
sys.exit(1)
|
||||
@@ -262,7 +263,7 @@ class ProxmoxApi:
|
||||
logger.debug("Starting: test_api_proxmox_host_ipv4.")
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
logger.warning(f"Warning: Host {host} ran into a timout when connectoing on IPv4 for tcp/{port}.")
|
||||
logger.warning(f"Warning: Host {host} ran into a timeout when connecting on IPv4 for tcp/{port}.")
|
||||
result = sock.connect_ex((host, port))
|
||||
|
||||
if result == 0:
|
||||
@@ -295,7 +296,7 @@ class ProxmoxApi:
|
||||
logger.debug("Starting: test_api_proxmox_host_ipv6.")
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
logger.warning(f"Host {host} ran into a timout when connectoing on IPv6 for tcp/{port}.")
|
||||
logger.warning(f"Host {host} ran into a timeout when connecting via IPv6 for tcp/{port}.")
|
||||
result = sock.connect_ex((host, port))
|
||||
|
||||
if result == 0:
|
||||
@@ -309,6 +310,36 @@ class ProxmoxApi:
|
||||
logger.debug("Finished: test_api_proxmox_host_ipv4.")
|
||||
return False
|
||||
|
||||
def test_api_user_permissions(self, proxmox_api: any):
|
||||
"""
|
||||
Test the permissions of the current user/token used for the Proxmox API.
|
||||
|
||||
This method gets all assigned permissions for all API paths for the current
|
||||
used user/token and validates them against the minimum required permissions.
|
||||
|
||||
Args:
|
||||
proxmox_api (any): The Proxmox API client instance.
|
||||
"""
|
||||
logger.debug("Starting: test_api_user_permissions.")
|
||||
permissions_required = ["Datastore.Audit", "Sys.Audit", "VM.Audit", "VM.Migrate"]
|
||||
permissions_available = []
|
||||
|
||||
# Get the permissions for the current user/token from API
|
||||
permissions = proxmox_api.access.permissions.get()
|
||||
|
||||
# Get all available permissions of the current user/token
|
||||
for path, permission in permissions.items():
|
||||
for permission in permissions[path]:
|
||||
permissions_available.append(permission)
|
||||
|
||||
# Validate if all required permissions are included within the available permissions
|
||||
for required_permission in permissions_required:
|
||||
if required_permission not in permissions_available:
|
||||
logger.critical(f"Permission '{required_permission}' is missing. Please adjust the permissions for your user/token. See also: https://github.com/gyptazy/ProxLB/blob/main/docs/03_configuration.md#required-permissions-for-a-user")
|
||||
sys.exit(1)
|
||||
|
||||
logger.debug("Finished: test_api_user_permissions.")
|
||||
|
||||
def api_connect(self, proxlb_config: Dict[str, Any]) -> proxmoxer.ProxmoxAPI:
|
||||
"""
|
||||
Establishes a connection to the Proxmox API using the provided configuration.
|
||||
|
||||
@@ -3,5 +3,5 @@ __app_desc__ = "A DRS alike loadbalancer for Proxmox clusters."
|
||||
__author__ = "Florian Paul Azim Hoberg <gyptazy>"
|
||||
__copyright__ = "Copyright (C) 2025 Florian Paul Azim Hoberg (@gyptazy)"
|
||||
__license__ = "GPL-3.0"
|
||||
__version__ = "1.1.0"
|
||||
__version__ = "1.1.1"
|
||||
__url__ = "https://github.com/gyptazy/ProxLB"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[Unit]
|
||||
Description=ProxLB - A loadbalancer for Proxmox clusters
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
After=pveproxy.service
|
||||
Wants=pveproxy.service
|
||||
|
||||
[Service]
|
||||
ExecStart=python3 /usr/lib/python3/dist-packages/proxlb/main.py -c /etc/proxlb/proxlb.yaml
|
||||
User=plb
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=multi-user.target
|
||||
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="proxlb",
|
||||
version="1.1.0",
|
||||
version="1.1.1",
|
||||
description="A DRS alike loadbalancer for Proxmox clusters.",
|
||||
long_description="An advanced DRS alike loadbalancer for Proxmox clusters that also supports maintenance modes and affinity/anti-affinity rules.",
|
||||
author="Florian Paul Azim Hoberg",
|
||||
|
||||
Reference in New Issue
Block a user