A good production ready service should have functionality for various cases:
This tutorial shows a configuration of a typical production ready service. For information about service interactions with other utilities and services in container see Deploy Environment Specific Configurations.
Make sure that you can compile and run core tests and read a basic example Writing your first HTTP server.
utils::DaemonMain initializes and starts the component system with the provided command line arguments:
A path to the static config file should be passed from a command line to start the service:
Full static config could be seen at samples/production_service/static_config.yaml
Important parts are described down below.
Static configs tend to become quite big, so it is a good idea to move changing parts of it into variables. To do that, declare a config_vars
field in the static config and point it to a file with variables.
A file with config variables could look like this.
Now in static config you could use $variable-name
to refer to a variable, *#fallback
fields are used if there is no variable with such name in the config variables file:
A good practice is to have at least 3 different task processors:
Moving blocking operations into a separate task processor improves responsiveness and CPU usage of your service. Monitor task processor helps to get statistics and diagnostics from server under heavy load or from a server with a deadlocked threads in the main task processor.
Note the components::Server configuration:
In this example we have two listeners. it is done to separate clients and utility/diagnostic handlers to listen on different ports or even interfaces.
Your server has the following utility handlers:
All those handlers live on a separate components.server.listener-monitor
address, so you have to request them using the listener-monitor
credentials:
This is a server::handlers::Ping handle that returns 200 if the service is OK, 500 otherwise. Useful for balancers, that would stop sending traffic to the server if it responds with codes other than 200.
Note that the ping handler lives on the task processor of all the other handlers. Smart balancers may measure response times and send less traffic to the heavy loaded services.
Here's a configuration of a dynamic config related components components::DynamicConfigClient, components::DynamicConfig, components::DynamicConfigClientUpdater.
Service starts with some dynamic config values from defaults and updates dynamic values from a configs service at startup. If the first update fails, the values are retrieved from dynamic-config.fs-cache-path
file (if it exists).
See Congestion Control.
congestion_control::Component limits the active requests count. In case of overload it responds with HTTP 429 codes to some requests, allowing your service to properly process handle the rest.
All the significant parts of the component are configured by dynamic config options USERVER_RPS_CCONTROL and USERVER_RPS_CCONTROL_ENABLED.
It is a good idea to disable it in unit tests to avoid getting HTTP 429
on an overloaded CI server.
Metrics is a convenient way to monitor the health of your service.
Typical setup of components::SystemStatisticsCollector and components::StatisticsStorage is quite trivial:
With such setup you could poll the metrics from handler server::handlers::ServerMonitor that we've configured in previous section. However a much more mature approach is to write a component that pushes the metrics directly into the remote metrics aggregation service or to write a handler that provides the metrics in the native aggregation service format.
To produce metrics in declarative style use the utils::statistics::MetricTag or register your metrics writer in utils::statistics::Storage via RegisterWriter to produce metrics on per component basis. To test metrics refer to the testsuite metrics testing.
List of userver built-in metrics could be found at Service Statistics and Metrics (Prometheus/Graphite/...).
Alerts is a way to propagate critical errors from your service to a monitoring system.
When the code identifies that something bad happened and a user should be notified about that, alert_storage.FireAlert()
is called with the appropriate arguments. Then the alert subsystem notifies an external monitoring system (or a user) about the alert event though the specific HTTP handler.
Storing sensitive data aside from the configs is a good practice that allows you to set different access rights for the two files.
components::Secdist configuration is straightforward:
Refer to the storages::secdist::SecdistConfig config for more information on the data retrieval.
server::handlers::TestsControl is a handle that allows controlling the service from test environments. That handle is used by the testsuite from functional tests to mock time, invalidate caches, testpoints and many other things. This component should be disabled in production environments.
components::TestsuiteSupport is a lightweight storage to keep minor testsuite data. This component is required by many high-level components and it is safe to use this component in production environments.
This sample requires configs service, so we build and start one from our previous tutorials.
Functional tests are used to make sure that the service is working fine and implements the required functionality. A recommended practice is to build the service in Debug and Release modes and tests both of them, then deploy the Release build to the production, disabling all the tests related handlers.
Debug builds of the userver provide numerous assertions that validate the framework usage and help to detect bugs at early stages.
Typical functional tests for a service consist of a conftest.py
file with mocks+configs for the sereffectivelyvice and a bunch of test_*.py
files with actual tests. Such approach allows to reuse mocks and configurations in different tests.
See the full example at