This is an application to autoscale a cluster of BigBlueButton servers using predictive and responsive methods.
The bbb-autoscaler is intended to have two ways of getting the suggested number of servers: via HTTP API and Prometheus metrics.
* For now, only the Prometheus version is implemented.
This application exposes the calculated number of replicas on the /metrics route. This route works as a Prometheus exporter and exposes those calculations on the below metrics:
bbb_autoscaler_suggested_replicas{method="responsive"}: exposes the number of suggested replicas using the responsive method;bbb_autoscaler_suggested_replicas{method="predictive"}: exposes the number of suggested replicas using the predictive method;bbb_autoscaler_suggested_replicas{method="mixed"}: exposes the number of suggested replicas using the mixed method.A method is like a function that can calculate how many servers should be running at the moment. In this application, a method should inherit the BaseMethod class and define the calculate function (which returns an integer). For example, see this SimpleMethod which its own calculate function only returns 1 (for this method, every moment should have only one server running):
class ResponsiveMethod(BaseMethod):
@classmethod
def calculate(cls) -> int:
return 1
In this project, it's supposed to exist only 2 methods: a responsive method and a predictive method. The responsive method is intended to see only current information about the servers. On the other hand, the predictive method must be able to predict (which names it) the future and understands the use for the next moments in time.
The responsive method relies on applications that are capable of assigning scores for each server. It fetches the scores from a list of applications (HTTP APIs which are called sources), normalizes them, and calculates the number of servers for the current moment. This calculation is based on the Kubernetes HPA algorithm (which can be seen in this link). Essentially, the number of servers is calculated as below:
$$ R_{n+1} = \left\lceil R_{n} \times \frac{S_d}{S_{srcs}} \right\rceil $$
Where $R_{n+1}$ is the desired number of replicas, $R_{n}$ is the current number of replicas* , $S_d$ is the desired score and $S_{srcs}$ is the weighted mean of the scores of all sources:
$$ S_{srcs} = \left(\sum_{i=1}^n S_i\times w^i\right) / \sum_{i=1}^n w^i \ $$
Since a source provides a list** with a score attribution for each enabled server, $S_i$, the normalized average score of the source $i$, is calculated as below:
$$ S_i = \left[\left(\sum_{j=1}^N S_{i,j}\right) / N - S_i^{min}\right] / \left(S_i^{max} - S_i^{min}\right) $$
Where $S_{i,j}$ is the score associated by the source $i$ to the server $j$, $S_i^{min}$ is the minimum possible value for the source $i$, $S_i^{max}$ is the maximum one and $N$ is the number of running servers.
* The current number of replicas ($R_{n}$) is the length of the fetched list from the first source API.
** Since the sources assign scores for each server, all of them must consider the same set of servers.
To fetch the scores from a source, this source must export these scores in a compatible format. The API response must be compatible with the below format:
{
"servers": [
{
"score": float
}
]
}
The responsive method must be configured with the desired score* and the sources data. This configuration is made with a JSON file in the responsive field with the next format:
{
"desired": float,
"sources": [
{
"name": str,
"host": str,
"port": int,
"route": str,
"values": {
"maximum": float,
"minimum": float
},
"weight": float
}
]
}
* The desired score should be in $[0, 1]$ (since after normalizing the means they are in this interval).
This method is suppose to calculate how many servers should be running in any moment in time (even future ones). To do so, it needs a predictor model that inherits from UsersPredictor.
class UsersPredictor:
def predict_users(self, weekday: int, seconds: int) -> int:
raise NotImplementedError
def fit(self, weekday: int, seconds: int, users: int):
raise NotImplementedError
Using this model, the method can calculate how many servers should be running using the following equation:
$$ R_d^s = \frac{p_d^s}{r} $$
where $R_d^s$ is the number of replicas for the day $d$ and at the second $s$, $p_d^s$ is the number of predicted users for the day $d$ and at the second $s$ and $r$ is the constant ratio of users per server defined in the method's settings.
The predictive method must be configured with the amount of time to predict in the future*, the initial model* which is able to predict the number of users, the number of users per server and the users API URL to fetch data about the current number of participants in the cluster. This configuration is made with a JSON file in the predictive field with the next format:
{
"seconds": int,
"model": {
"initial": str,
"scaler": str,
"learning_rate": float
},
"users_per_server": int,
"users_api": {
"url": str
}
}
* The predictive method tries to predict the future based on the fact that a server doesn't start running instantly. So, the field seconds defines how many seconds in the future the predictive method must consider to calculate the number of servers that should be enabled now.
* The initial model is composed of two other components: the predictive model and the scaler. Each of these components is a Pickle file of an instance of a regressor and a transformerm of scikit-learn library.
* The learning_rate is the initial learning rate set on the regressor model. Also, this model is controlled with an inverse exponential function based on this rate and the iteration (see more in the official documentation of scikit-learn).
The mixed method is the simplest method. It tries to aggregate the other two methods calculations into a single value. To do so, it does a weighted average with the calculations of each method with weights defined in the settings file.
The mixed method must be configured with the weights for each other method (predictive and responsive). This configuration is made with a JSON file in the mixed field with the next format:
{
"weights": {
"predictive": float,
"responsive": float
}
}
To run tests you need to have pipenv installed:
pip install --user pipenv
Then run the following commands to install the dependencies and run the virtual environment:
pipenv install --dev
pipenv shell
Finally, to run the tests type:
make test
Content type
Image
Digest
sha256:82684fa5d…
Size
57.5 MB
Last updated
3 months ago
docker pull mconf/bbb-autoscaler