Repository: VictoriaMetrics/VictoriaMetrics
Pull request #6160 (2024-04-20 16:58:28)

feature: [vmagent] Add service discovery support for OVH Cloud VPS and dedicated server
### Describe Your Changes
related issue: https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6071

#### Added
- Added service discovery support for OVH Cloud:
    - VPS.
    - Dedicated server.

#### Docs
- `CHANGELOG.md`, `sd_configs.md`, `vmagent.md` are updated.

#### Note
- Useful links: 
    - OVH Cloud VPS API: https://eu.api.ovh.com/console/#/vps~GET
    - OVH Cloud Dedicated server API: https://eu.api.ovh.com/console/#/dedicated/server~GET
    - OVH Cloud SDK: https://github.com/ovh/go-ovh
    - Prometheus SD: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#ovhcloud_sd_config

Tested on OVH Cloud VPS and dedicated server.
<img width="1722" alt="image" src="https://github.com/VictoriaMetrics/VictoriaMetrics/assets/30280396/d3f0adc8-b0ef-423e-9379-8a9b9b0792ee">

<img width="1724" alt="image" src="https://github.com/VictoriaMetrics/VictoriaMetrics/assets/30280396/18b5b730-3512-4fc0-8b2c-f2450ac550fd">


---
Checklist
The following checks are mandatory:

- [x] My change adheres [VictoriaMetrics contributing guidelines](https://docs.victoriametrics.com/contributing/).

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
Attention: Patch coverage is `64.70588%` with `96 lines` in your changes are missing coverage. Please review.
> Project coverage is 56.78%. Comparing base [(`8aaa828`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/8aaa828ba3f49a43cb8de89a0bcae160f7988c14?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) to head [(`b1eec18`)](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).
> Report is 584 commits behind head on master.

| [Files](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) | Patch % | Lines |
|---|---|---|
| [lib/promscrape/discovery/ovhcloud/common.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&filepath=lib%2Fpromscrape%2Fdiscovery%2Fovhcloud%2Fcommon.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3Byb21zY3JhcGUvZGlzY292ZXJ5L292aGNsb3VkL2NvbW1vbi5nbw==) | 63.49% | [20 Missing and 3 partials :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |
| [.../promscrape/discovery/ovhcloud/dedicated\_server.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&filepath=lib%2Fpromscrape%2Fdiscovery%2Fovhcloud%2Fdedicated_server.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3Byb21zY3JhcGUvZGlzY292ZXJ5L292aGNsb3VkL2RlZGljYXRlZF9zZXJ2ZXIuZ28=) | 69.33% | [12 Missing and 11 partials :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |
| [lib/promscrape/discovery/ovhcloud/vps.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&filepath=lib%2Fpromscrape%2Fdiscovery%2Fovhcloud%2Fvps.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3Byb21zY3JhcGUvZGlzY292ZXJ5L292aGNsb3VkL3Zwcy5nbw==) | 74.07% | [11 Missing and 10 partials :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |
| [lib/promscrape/discovery/ovhcloud/ovhcloud.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&filepath=lib%2Fpromscrape%2Fdiscovery%2Fovhcloud%2Fovhcloud.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3Byb21zY3JhcGUvZGlzY292ZXJ5L292aGNsb3VkL292aGNsb3VkLmdv) | 31.25% | [10 Missing and 1 partial :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |
| [lib/promscrape/discovery/ovhcloud/api.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&filepath=lib%2Fpromscrape%2Fdiscovery%2Fovhcloud%2Fapi.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3Byb21zY3JhcGUvZGlzY292ZXJ5L292aGNsb3VkL2FwaS5nbw==) | 65.51% | [5 Missing and 5 partials :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |
| [lib/promscrape/config.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&filepath=lib%2Fpromscrape%2Fconfig.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3Byb21zY3JhcGUvY29uZmlnLmdv) | 0.00% | [7 Missing :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |
| [lib/promscrape/scraper.go](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&filepath=lib%2Fpromscrape%2Fscraper.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3Byb21zY3JhcGUvc2NyYXBlci5nbw==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #6160      +/-   ##
==========================================
- Coverage   60.37%   56.78%   -3.59%     
==========================================
  Files         411      531     +120     
  Lines       76609    72787    -3822     
==========================================
- Hits        46253    41335    -4918     
- Misses      27794    28515     +721     
- Partials     2562     2937     +375     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/6160?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).

hagen1778: Hello @jiekun! Sorry for late response( Could you please rebase your PR on top of master branch?

Patch:
diff --git a/deployment/docker/docker-compose-victorialogs.yml b/deployment/docker/docker-compose-victorialogs.yml
index ab276c155..093c1e8c8 100644
--- a/deployment/docker/docker-compose-victorialogs.yml
+++ b/deployment/docker/docker-compose-victorialogs.yml
@@ -40,7 +40,7 @@ services:
 #  storing logs and serving read queries.
   victorialogs:
     container_name: victorialogs
-    image: docker.io/victoriametrics/victoria-logs:v0.32.0-victorialogs
+    image: docker.io/victoriametrics/victoria-logs:v0.32.1-victorialogs
     command:
       - "--storageDataPath=/vlogs"
       - "--httpListenAddr=:9428"
diff --git a/deployment/docker/victorialogs/compose-base.yml b/deployment/docker/victorialogs/compose-base.yml
index 33f1026b4..f6b8bed27 100644
--- a/deployment/docker/victorialogs/compose-base.yml
+++ b/deployment/docker/victorialogs/compose-base.yml
@@ -1,7 +1,7 @@
 services:
   # meta service will be ignored by compose
   .victorialogs:
-    image: docker.io/victoriametrics/victoria-logs:v0.32.0-victorialogs
+    image: docker.io/victoriametrics/victoria-logs:v0.32.1-victorialogs
     command:
       - -storageDataPath=/vlogs
       - -loggerFormat=json
diff --git a/deployment/logs-benchmark/docker-compose.yml b/deployment/logs-benchmark/docker-compose.yml
index a93ba84cb..68b910ae9 100644
--- a/deployment/logs-benchmark/docker-compose.yml
+++ b/deployment/logs-benchmark/docker-compose.yml
@@ -3,7 +3,7 @@ version: '3'
 services:
   # Run `make package-victoria-logs` to build victoria-logs image
   vlogs:
-    image: docker.io/victoriametrics/victoria-logs:v0.32.0-victorialogs
+    image: docker.io/victoriametrics/victoria-logs:v0.32.1-victorialogs
     volumes:
       - vlogs:/vlogs
     ports:
diff --git a/docs/README.md b/docs/README.md
index 91168c8ee..b82f018d1 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -3061,6 +3061,8 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li
      Interval for checking for changes in Nomad. This works only if nomad_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#nomad_sd_configs for details (default 30s)
   -promscrape.openstackSDCheckInterval duration
      Interval for checking for changes in openstack API server. This works only if openstack_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#openstack_sd_configs for details (default 30s)
+  -promscrape.ovhcloudSDCheckInterval duration
+     Interval for checking for changes in OVH Cloud VPS and dedicated server. This works only if ovhcloud_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs for details (default 30s)
   -promscrape.seriesLimitPerTarget int
      Optional limit on the number of unique time series a single scrape target can expose. See https://docs.victoriametrics.com/vmagent/#cardinality-limiter for more info
   -promscrape.streamParse
diff --git a/docs/VictoriaLogs/QuickStart.md b/docs/VictoriaLogs/QuickStart.md
index a78db8f13..31d710d18 100644
--- a/docs/VictoriaLogs/QuickStart.md
+++ b/docs/VictoriaLogs/QuickStart.md
@@ -33,8 +33,8 @@ Just download archive for the needed Operating system and architecture, unpack i
 For example, the following commands download VictoriaLogs archive for Linux/amd64, unpack and run it:
 
 ```sh
-curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v0.32.0-victorialogs/victoria-logs-linux-amd64-v0.32.0-victorialogs.tar.gz
-tar xzf victoria-logs-linux-amd64-v0.32.0-victorialogs.tar.gz
+curl -L -O https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v0.32.1-victorialogs/victoria-logs-linux-amd64-v0.32.1-victorialogs.tar.gz
+tar xzf victoria-logs-linux-amd64-v0.32.1-victorialogs.tar.gz
 ./victoria-logs-prod
 ```
 
@@ -58,7 +58,7 @@ Here is the command to run VictoriaLogs in a Docker container:
 
 ```sh
 docker run --rm -it -p 9428:9428 -v ./victoria-logs-data:/victoria-logs-data \
-  docker.io/victoriametrics/victoria-logs:v0.32.0-victorialogs
+  docker.io/victoriametrics/victoria-logs:v0.32.1-victorialogs
 ```
 
 See also:
diff --git a/docs/changelog/CHANGELOG.md b/docs/changelog/CHANGELOG.md
index 3e332c86f..41a918a2f 100644
--- a/docs/changelog/CHANGELOG.md
+++ b/docs/changelog/CHANGELOG.md
@@ -33,6 +33,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
 * FEATURE: [dashboards](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards) for VM single-node, cluster, vmalert, vmagent, VictoriaLogs: add `Go scheduling latency` panel to show the 99th quantile of Go goroutines scheduling. This panel should help identifying insufficient CPU resources for the service. It is especially useful if CPU gets throttled, which now should be visible on this panel.
 * FEATURE: [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-health.yml): add alerting rule to track the Go scheduling latency for goroutines. It should notify users if VM component doesn't have enough CPU to run or gets throttled.
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent/) and [Single-node VictoriaMetrics](https://docs.victoriametrics.com/): hide jobs that contain only healthy targets when `show_only_unhealthy` filter is enabled. Before, jobs without unhealthy targets were still displayed on the page. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3536).
+* FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add service discovery support for [OVH Cloud VPS](https://www.ovhcloud.com/en/vps/) and [OVH Cloud dedicated server](https://ovhcloud.com/en/bare-metal/). See [these docs](https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6071).
 * FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert): bump default values for sending data to `remoteWrite.url`: `remoteWrite.maxQueueSize` from `100_000` to `1_000_000`, `remoteWrite.maxBatchSize` from `1_000` to `10_000`, `remoteWrite.concurrency` from `1` to `4`. The new settings should improve remote write performance of vmalert with default settings.
 * FEATURE: [vmbackupmanager](https://docs.victoriametrics.com/vmbackupmanager/): add API for creating/scheduling backups. See [documentation](https://docs.victoriametrics.com/vmbackupmanager/#api-methods)
 
diff --git a/docs/sd_configs.md b/docs/sd_configs.md
index 5d43206c2..e7b81e5e7 100644
--- a/docs/sd_configs.md
+++ b/docs/sd_configs.md
@@ -30,6 +30,7 @@ supports the following Prometheus-compatible service discovery options for Prome
 * `kuma_sd_configs` is for discovering and scraping [Kuma](https://kuma.io) targets. See [these docs](#kuma_sd_configs).
 * `nomad_sd_configs` is for discovering and scraping targets registered in [HashiCorp Nomad](https://www.nomadproject.io/). See [these docs](#nomad_sd_configs).
 * `openstack_sd_configs` is for discovering and scraping OpenStack targets. See [these docs](#openstack_sd_configs).
+* `ovhcloud_sd_configs` is for discovering and scraping OVH Cloud VPS and dedicated server targets. See [these docs](#ovhcloud_sd_configs).
 * `static_configs` is for scraping statically defined targets. See [these docs](#static_configs).
 * `vultr_sd_configs` is for discovering and scraping [Vultr](https://www.vultr.com/) targets. See [these docs](#vultr_sd_configs).
 * `yandexcloud_sd_configs` is for discovering and scraping [Yandex Cloud](https://cloud.yandex.com/en/) targets. See [these docs](#yandexcloud_sd_configs).
@@ -1459,6 +1460,92 @@ One of the following `role` types can be configured to discover targets:
 
 The list of discovered OpenStack targets is refreshed at the interval, which can be configured via `-promscrape.openstackSDCheckInterval` command-line flag.
 
+## ovhcloud_sd_configs
+
+_Available from [v1.104](https://docs.victoriametrics.com/changelog/#v11040) version._
+
+OVH Cloud SD configuration allows retrieving scrape targets from [OVH Cloud VPS](https://www.ovhcloud.com/en/vps/) 
+and [OVH Cloud dedicated server](https://ovhcloud.com/en/bare-metal/).
+
+Configuration example:
+
+```yaml
+scrape_configs:
+- job_name: ovh_job
+  ovhcloud_sd_configs:
+
+  # (optional) depending on the API you want to use, you may set the endpoint to:
+  # `ovh-eu` for OVH Europe API (default).
+  # `ovh-us` for OVH US API.
+  # `ovh-ca` for OVH North-America API.
+  # `soyoustart-eu` for "So you Start Europe API".
+  # `soyoustart-ca` for "So you Start North America API".
+  # `kimsufi-eu` for Kimsufi Europe API.
+  # `kimsufi-ca` for Kimsufi North America API.
+  # See: https://github.com/ovh/go-ovh?tab=readme-ov-file#supported-apis
+  - endpoint: "..."
+
+    # (mandatory) application_key is a self generated tokens. 
+    # create one by visiting: https://eu.api.ovh.com/createApp/
+    application_key: "..."
+
+    # (mandatory) application_secret holds the application secret key.
+    application_secret: "..."
+    
+    # (mandatory) consumer_key holds the user/app specific token. It must have been validated before use.
+    consumer_key: "..."
+
+    # (mandatory) service could be either `vps` or `dedicated_server`.
+    service: "..."
+
+    # Additional HTTP API client options can be specified here.
+    # See https://docs.victoriametrics.com/sd_configs.html#http-api-client-options
+```
+
+Each discovered target has an [`__address__`](https://docs.victoriametrics.com/relabeling/#how-to-modify-scrape-urls-in-targets) label set to either `<ipv4>` address or `<ipv6>` address.
+
+In addition, the `instance` label for the VPS/dedicated server will be set to the VPS/dedicated server name as retrieved from OVH Cloud API.
+
+The following meta labels are available on discovered targets during [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling).
+
+VPS:
+* `__meta_ovhcloud_vps_cluster`: the cluster of the server.
+* `__meta_ovhcloud_vps_datacenter`: the datacenter of the server.
+* `__meta_ovhcloud_vps_disk`: the disk of the server.
+* `__meta_ovhcloud_vps_display_name`: the display name of the server.
+* `__meta_ovhcloud_vps_ipv4`: the IPv4 of the server.
+* `__meta_ovhcloud_vps_ipv6`: the IPv6 of the server.
+* `__meta_ovhcloud_vps_keymap`: the KVM keyboard layout of the server.
+* `__meta_ovhcloud_vps_maximum_additional_ip`: the maximum additional IPs of the server.
+* `__meta_ovhcloud_vps_memory_limit`: the memory limit of the server.
+* `__meta_ovhcloud_vps_memory`: the memory of the server.
+* `__meta_ovhcloud_vps_monitoring_ip_blocks`: the monitoring IP blocks of the server.
+* `__meta_ovhcloud_vps_name`: the name of the server.
+* `__meta_ovhcloud_vps_netboot_mode`: the netboot mode of the server.
+* `__meta_ovhcloud_vps_offer_type`: the offer type of the server.
+* `__meta_ovhcloud_vps_offer`: the offer of the server.
+* `__meta_ovhcloud_vps_state`: the state of the server.
+* `__meta_ovhcloud_vps_vcore`: the number of virtual cores of the server.
+* `__meta_ovhcloud_vps_version`: the version of the server.
+* `__meta_ovhcloud_vps_zone`: the zone of the server.
+
+Dedicated servers:
+* `__meta_ovhcloud_dedicated_server_commercial_range`: the commercial range of the server.    
+* `__meta_ovhcloud_dedicated_server_datacenter`: the datacenter of the server.                
+* `__meta_ovhcloud_dedicated_server_ipv4`: the IPv4 of the server.                            
+* `__meta_ovhcloud_dedicated_server_ipv6`: the IPv6 of the server.                            
+* `__meta_ovhcloud_dedicated_server_link_speed`: the link speed of the server.                
+* `__meta_ovhcloud_dedicated_server_name`: the name of the server.                            
+* `__meta_ovhcloud_dedicated_server_no_intervention`: the [intervention](https://support.us.ovhcloud.com/hc/en-us/articles/27991435200147-FAQ-Interventions-and-Hardware-Replacement) of the server.
+* `__meta_ovhcloud_dedicated_server_os`: the operating system of the server.
+* `__meta_ovhcloud_dedicated_server_rack`: the rack of the server.
+* `__meta_ovhcloud_dedicated_server_reverse`: the reverse DNS name of the server.
+* `__meta_ovhcloud_dedicated_server_server_id`: the ID of the server.
+* `__meta_ovhcloud_dedicated_server_state`: the state of the server.
+* `__meta_ovhcloud_dedicated_server_support_level`: the support level of the server.
+
+The list of discovered OVH Cloud targets is refreshed at the interval, which can be configured via `-promscrape.ovhcloudSDCheckInterval` command-line flag.
+
 ## static_configs
 
 A static config allows specifying a list of targets and a common label set for them.
diff --git a/docs/vmagent.md b/docs/vmagent.md
index 72e71f131..794d4bbbc 100644
--- a/docs/vmagent.md
+++ b/docs/vmagent.md
@@ -2006,8 +2006,8 @@ See the docs at https://docs.victoriametrics.com/vmagent/ .
      Interval for checking for changes in Nomad. This works only if nomad_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#nomad_sd_configs for details (default 30s)
   -promscrape.openstackSDCheckInterval duration
      Interval for checking for changes in openstack API server. This works only if openstack_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#openstack_sd_configs for details (default 30s)
-  -promscrape.scrapeExemplars
-     Whether to enable scraping of exemplars from scrape targets.
+  -promscrape.ovhcloudSDCheckInterval duration
+     Interval for checking for changes in OVH Cloud VPS and dedicated server. This works only if ovhcloud_sd_configs is configured in '-promscrape.config' file. See https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs for details (default 30s)
   -promscrape.seriesLimitPerTarget int
      Optional limit on the number of unique time series a single scrape target can expose. See https://docs.victoriametrics.com/vmagent/#cardinality-limiter for more info
   -promscrape.streamParse
diff --git a/lib/promscrape/config.go b/lib/promscrape/config.go
index 0b4af38d9..9221d7f43 100644
--- a/lib/promscrape/config.go
+++ b/lib/promscrape/config.go
@@ -37,6 +37,7 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kuma"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/nomad"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/openstack"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/ovhcloud"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/vultr"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/yandexcloud"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
@@ -312,6 +313,7 @@ type ScrapeConfig struct {
 	KumaSDConfigs         []kuma.SDConfig         `yaml:"kuma_sd_configs,omitempty"`
 	NomadSDConfigs        []nomad.SDConfig        `yaml:"nomad_sd_configs,omitempty"`
 	OpenStackSDConfigs    []openstack.SDConfig    `yaml:"openstack_sd_configs,omitempty"`
+	OVHCloudSDConfigs     []ovhcloud.SDConfig     `yaml:"ovhcloud_sd_configs,omitempty"`
 	StaticConfigs         []StaticConfig          `yaml:"static_configs,omitempty"`
 	VultrSDConfigs        []vultr.SDConfig        `yaml:"vultr_configs,omitempty"`
 	YandexCloudSDConfigs  []yandexcloud.SDConfig  `yaml:"yandexcloud_sd_configs,omitempty"`
@@ -394,6 +396,9 @@ func (sc *ScrapeConfig) mustStop() {
 	for i := range sc.OpenStackSDConfigs {
 		sc.OpenStackSDConfigs[i].MustStop()
 	}
+	for i := range sc.OVHCloudSDConfigs {
+		sc.OVHCloudSDConfigs[i].MustStop()
+	}
 	for i := range sc.VultrSDConfigs {
 		sc.VultrSDConfigs[i].MustStop()
 	}
@@ -757,6 +762,16 @@ func (cfg *Config) getOpenStackSDScrapeWork(prev []*ScrapeWork) []*ScrapeWork {
 	return cfg.getScrapeWorkGeneric(visitConfigs, "openstack_sd_config", prev)
 }
 
+// getOVHCloudSDScrapeWork returns `ovhcloud_sd_configs` ScrapeWork from cfg.
+func (cfg *Config) getOVHCloudSDScrapeWork(prev []*ScrapeWork) []*ScrapeWork {
+	visitConfigs := func(sc *ScrapeConfig, visitor func(sdc targetLabelsGetter)) {
+		for i := range sc.OVHCloudSDConfigs {
+			visitor(&sc.OVHCloudSDConfigs[i])
+		}
+	}
+	return cfg.getScrapeWorkGeneric(visitConfigs, "ovhcloud_sd_config", prev)
+}
+
 // getVultrSDScrapeWork returns `vultr_sd_configs` ScrapeWork from cfg.
 func (cfg *Config) getVultrSDScrapeWork(prev []*ScrapeWork) []*ScrapeWork {
 	visitConfigs := func(sc *ScrapeConfig, visitor func(sdc targetLabelsGetter)) {
diff --git a/lib/promscrape/discovery/ovhcloud/api.go b/lib/promscrape/discovery/ovhcloud/api.go
new file mode 100644
index 000000000..c5fbc2ca9
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/api.go
@@ -0,0 +1,76 @@
+package ovhcloud
+
+import (
+	"fmt"
+	"sync/atomic"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
+)
+
+// mapping for endpoint names to their URI for external configuration
+var availableEndpoints = map[string]string{
+	"ovh-eu":        "https://eu.api.ovh.com/1.0",
+	"ovh-ca":        "https://ca.api.ovh.com/1.0",
+	"ovh-us":        "https://api.us.ovhcloud.com/1.0",
+	"kimsufi-eu":    "https://eu.api.kimsufi.com/1.0",
+	"kimsufi-ca":    "https://ca.api.kimsufi.com/1.0",
+	"soyoustart-eu": "https://eu.api.soyoustart.com/1.0",
+	"soyoustart-ca": "https://ca.api.soyoustart.com/1.0",
+}
+
+var configMap = discoveryutils.NewConfigMap()
+
+type apiConfig struct {
+	client *discoveryutils.Client
+
+	applicationKey    string `yaml:"application_key"`
+	applicationSecret string `yaml:"application_secret"`
+	consumerKey       string `yaml:"consumer_key"`
+
+	// internal fields, for ovh auth
+	timeDelta atomic.Value
+}
+
+func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
+	v, err := configMap.Get(sdc, func() (interface{}, error) { return newAPIConfig(sdc, baseDir) })
+	if err != nil {
+		return nil, err
+	}
+	return v.(*apiConfig), nil
+}
+
+func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
+	if sdc.Endpoint == "" {
+		sdc.Endpoint = "ovh-eu"
+	}
+
+	apiServer, ok := availableEndpoints[sdc.Endpoint]
+	if !ok {
+		return nil, fmt.Errorf(
+			"unsupported `endpoint` for ovhcloud sd: %s, see: https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs",
+			sdc.Endpoint,
+		)
+	}
+
+	ac, err := sdc.HTTPClientConfig.NewConfig(baseDir)
+	if err != nil {
+		return nil, fmt.Errorf("cannot parse auth config: %w", err)
+	}
+	proxyAC, err := sdc.ProxyClientConfig.NewConfig(baseDir)
+	if err != nil {
+		return nil, fmt.Errorf("cannot parse proxy auth config: %w", err)
+	}
+
+	client, err := discoveryutils.NewClient(apiServer, ac, sdc.ProxyURL, proxyAC, &sdc.HTTPClientConfig)
+	if err != nil {
+		return nil, fmt.Errorf("cannot create HTTP client for %q: %w", apiServer, err)
+	}
+
+	return &apiConfig{
+		client: client,
+
+		applicationKey:    sdc.ApplicationKey,
+		applicationSecret: sdc.ApplicationSecret.String(),
+		consumerKey:       sdc.ConsumerKey.String(),
+	}, nil
+}
diff --git a/lib/promscrape/discovery/ovhcloud/common.go b/lib/promscrape/discovery/ovhcloud/common.go
new file mode 100644
index 000000000..e46500cc3
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/common.go
@@ -0,0 +1,103 @@
+package ovhcloud
+
+import (
+	"crypto/sha1"
+	"fmt"
+	"net/http"
+	"net/netip"
+	"strconv"
+	"time"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
+)
+
+func getAuthHeaders(cfg *apiConfig, headers http.Header, endpoint, path string) (http.Header, error) {
+	timestamp := getOVHTimestamp(cfg)
+
+	headers = setGeneralHeaders(cfg, headers)
+	headers.Set("X-Ovh-Timestamp", strconv.FormatInt(timestamp, 10))
+	headers.Add("X-Ovh-Consumer", cfg.consumerKey)
+
+	h := sha1.New()
+	h.Write([]byte(fmt.Sprintf("%s+%s+%s+%s+%s+%d",
+		cfg.applicationSecret,
+		cfg.consumerKey,
+		"GET",
+		endpoint+path,
+		"", // no body contained in any service discovery request, so it's set to empty by default
+		timestamp,
+	)))
+	headers.Set("X-Ovh-Signature", fmt.Sprintf("$1$%x", h.Sum(nil)))
+	return headers, nil
+}
+
+func setGeneralHeaders(cfg *apiConfig, headers http.Header) http.Header {
+	headers.Set("X-Ovh-Application", cfg.applicationKey)
+	headers.Set("Accept", "application/json")
+	headers.Set("User-Agent", "github.com/VictoriaMetrics/VictoriaMetrics")
+	return headers
+}
+
+func getServerTime(cfg *apiConfig) (*time.Time, error) {
+	resp, err := cfg.client.GetAPIResponseWithReqParams("/auth/time", func(req *http.Request) {
+		req.Header = setGeneralHeaders(cfg, req.Header)
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to get server time from /auth/time: %w", err)
+	}
+	ts, err := strconv.ParseInt(string(resp), 10, 0)
+	if err != nil {
+		return nil, fmt.Errorf("parse ovh response to timestamp failed: %w", err)
+	}
+	serverTime := time.Unix(ts, 0)
+	return &serverTime, nil
+}
+
+// getOVHTimestamp return the server timestamp which is required by X-Ovh-Timestamp header.
+// The timestamp is calculated by now() - timeDelta, where timeDelta is retrieved from /auth/time API and stored in config.
+// It returns now() when server timestamp is unknown.
+func getOVHTimestamp(cfg *apiConfig) int64 {
+	d, ok := cfg.timeDelta.Load().(time.Duration)
+	if ok {
+		return time.Now().Add(-d).Unix()
+	}
+
+	ovhTime, err := getServerTime(cfg)
+	if err != nil {
+		logger.Warnf("cannot get OVH server time, err: %v. using current timestamp.", err)
+		return time.Now().Unix()
+	}
+
+	d = time.Since(*ovhTime)
+	cfg.timeDelta.Store(d)
+
+	return time.Now().Add(-d).Unix()
+}
+
+func parseIPList(ipList []string) ([]netip.Addr, error) {
+	var ipAddresses []netip.Addr
+	for _, ip := range ipList {
+		ipAddr, err := netip.ParseAddr(ip)
+		if err != nil {
+			ipPrefix, err := netip.ParsePrefix(ip)
+			if err != nil {
+				return nil, fmt.Errorf("could not parse IP addresses: %s", ip)
+			}
+			if ipPrefix.IsValid() {
+				netmask := ipPrefix.Bits()
+				if netmask != 32 {
+					continue
+				}
+				ipAddr = ipPrefix.Addr()
+			}
+		}
+		if ipAddr.IsValid() && !ipAddr.IsUnspecified() {
+			ipAddresses = append(ipAddresses, ipAddr)
+		}
+	}
+
+	if len(ipAddresses) == 0 {
+		return nil, fmt.Errorf("could not parse IP addresses from ip List: %v", ipList)
+	}
+	return ipAddresses, nil
+}
diff --git a/lib/promscrape/discovery/ovhcloud/dedicated_server.go b/lib/promscrape/discovery/ovhcloud/dedicated_server.go
new file mode 100644
index 000000000..7ed860418
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/dedicated_server.go
@@ -0,0 +1,152 @@
+package ovhcloud
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/netip"
+	"net/url"
+	"path"
+	"strconv"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
+)
+
+// dedicatedServer struct from API.
+// IP addresses are fetched independently.
+// See: https://eu.api.ovh.com/console/#/dedicated/server/%7BserviceName%7D~GET and getDedicatedServerDetails
+type dedicatedServer struct {
+	State           string `json:"state"`
+	IPs             []netip.Addr
+	CommercialRange string `json:"commercialRange"`
+	LinkSpeed       int    `json:"linkSpeed"`
+	Rack            string `json:"rack"`
+	NoIntervention  bool   `json:"noIntervention"`
+	Os              string `json:"os"`
+	SupportLevel    string `json:"supportLevel"`
+	ServerID        int64  `json:"serverId"`
+	Reverse         string `json:"reverse"`
+	Datacenter      string `json:"datacenter"`
+	Name            string `json:"name"`
+}
+
+// getDedicatedServerLabels get labels for dedicated servers.
+func getDedicatedServerLabels(cfg *apiConfig) ([]*promutils.Labels, error) {
+	dedicatedServerList, err := getDedicatedServerList(cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	// Attach properties to each VPS and compose vpsDetailedList
+	dedicatedServerDetailList := make([]dedicatedServer, 0, len(dedicatedServerList))
+	for _, dedicatedServerName := range dedicatedServerList {
+		dedicatedServer, err := getDedicatedServerDetails(cfg, dedicatedServerName)
+		if err != nil {
+			logger.Errorf("getDedicatedServerDetails for %s failed, err: %v", dedicatedServerName, err)
+			continue
+		}
+		dedicatedServerDetailList = append(dedicatedServerDetailList, *dedicatedServer)
+	}
+
+	ms := make([]*promutils.Labels, 0, len(dedicatedServerDetailList))
+	for _, server := range dedicatedServerDetailList {
+		// convert IPs into string and select default IP.
+		var ipv4, ipv6 string
+		for _, ip := range server.IPs {
+			if ip.Is4() {
+				ipv4 = ip.String()
+			}
+			if ip.Is6() {
+				ipv6 = ip.String()
+			}
+		}
+		defaultIP := ipv4
+		if defaultIP == "" {
+			defaultIP = ipv6
+		}
+
+		m := promutils.NewLabels(15)
+		m.Add("__address__", defaultIP)
+		m.Add("instance", server.Name)
+		m.Add("__meta_ovhcloud_dedicated_server_state", server.State)
+		m.Add("__meta_ovhcloud_dedicated_server_commercial_range", server.CommercialRange)
+		m.Add("__meta_ovhcloud_dedicated_server_link_speed", fmt.Sprintf("%d", server.LinkSpeed))
+		m.Add("__meta_ovhcloud_dedicated_server_rack", server.Rack)
+		m.Add("__meta_ovhcloud_dedicated_server_no_intervention", strconv.FormatBool(server.NoIntervention))
+		m.Add("__meta_ovhcloud_dedicated_server_os", server.Os)
+		m.Add("__meta_ovhcloud_dedicated_server_support_level", server.SupportLevel)
+		m.Add("__meta_ovhcloud_dedicated_server_server_id", fmt.Sprintf("%d", server.ServerID))
+		m.Add("__meta_ovhcloud_dedicated_server_reverse", server.Reverse)
+		m.Add("__meta_ovhcloud_dedicated_server_datacenter", server.Datacenter)
+		m.Add("__meta_ovhcloud_dedicated_server_name", server.Name)
+		m.Add("__meta_ovhcloud_dedicated_server_ipv4", ipv4)
+		m.Add("__meta_ovhcloud_dedicated_server_ipv6", ipv6)
+
+		ms = append(ms, m)
+	}
+	return ms, nil
+}
+
+// getVPSDetails get properties of a dedicated server.
+// Also see: https://eu.api.ovh.com/console/#/dedicated/server/%7BserviceName%7D~GET
+func getDedicatedServerDetails(cfg *apiConfig, dedicatedServerName string) (*dedicatedServer, error) {
+	// get properties.
+	reqPath := path.Join("/dedicated/server", url.QueryEscape(dedicatedServerName))
+	resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
+		request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
+	})
+	if err != nil {
+		return nil, fmt.Errorf("request %s error: %v", reqPath, err)
+	}
+
+	var dedicatedServerDetails dedicatedServer
+	if err = json.Unmarshal(resp, &dedicatedServerDetails); err != nil {
+		return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
+	}
+
+	// get IPs for this dedicated server.
+	// e.g. ["139.99.154.111","2402:1f00:8100:401::bb6"]
+	// Also see: https://eu.api.ovh.com/console/#/dedicated/server/%7BserviceName%7D/ips~GET
+	reqPath = path.Join(reqPath, "ips")
+	resp, err = cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
+		request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
+	})
+	if err != nil {
+		return nil, fmt.Errorf("request %s error: %v", reqPath, err)
+	}
+
+	var ips []string
+	if err = json.Unmarshal(resp, &ips); err != nil {
+		return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
+	}
+
+	// handle different IP formats
+	parsedIPs, err := parseIPList(ips)
+	if err != nil {
+		return nil, err
+	}
+	dedicatedServerDetails.IPs = parsedIPs
+
+	return &dedicatedServerDetails, nil
+}
+
+// getDedicatedServerList list available services.
+// example: ["ns0000000.ip-00-00-000.eu"]
+// Also see: https://eu.api.ovh.com/console/#/dedicated/server~GET
+func getDedicatedServerList(cfg *apiConfig) ([]string, error) {
+	var dedicatedServerList []string
+	reqPath := "/dedicated/server"
+	resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
+		request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
+	})
+	if err != nil {
+		return nil, fmt.Errorf("request %s error: %v", reqPath, err)
+	}
+
+	if err = json.Unmarshal(resp, &dedicatedServerList); err != nil {
+		return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
+	}
+
+	return dedicatedServerList, nil
+}
diff --git a/lib/promscrape/discovery/ovhcloud/ovhcloud.go b/lib/promscrape/discovery/ovhcloud/ovhcloud.go
new file mode 100644
index 000000000..32fd372a0
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/ovhcloud.go
@@ -0,0 +1,54 @@
+package ovhcloud
+
+import (
+	"flag"
+	"fmt"
+	"time"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
+)
+
+// SDCheckInterval defines interval for targets refresh.
+var SDCheckInterval = flag.Duration("promscrape.ovhcloudSDCheckInterval", 30*time.Second, "Interval for checking for changes in OVH Cloud API. "+
+	"This works only if ovhcloud_sd_configs is configured in '-promscrape.config' file. "+
+	"See https://docs.victoriametrics.com/sd_configs/#ovhcloud_sd_configs for details")
+
+// SDConfig is the configuration for OVH Cloud service discovery.
+type SDConfig struct {
+	Endpoint          string           `yaml:"endpoint"`
+	ApplicationKey    string           `yaml:"application_key"`
+	ApplicationSecret *promauth.Secret `yaml:"application_secret"`
+	ConsumerKey       *promauth.Secret `yaml:"consumer_key"`
+	Service           string           `yaml:"service"`
+
+	HTTPClientConfig  promauth.HTTPClientConfig  `yaml:",inline"`
+	ProxyURL          *proxy.URL                 `yaml:"proxy_url,omitempty"`
+	ProxyClientConfig promauth.ProxyClientConfig `yaml:",inline"`
+}
+
+// GetLabels returns labels for OVH Cloud according to service discover config.
+func (sdc *SDConfig) GetLabels(baseDir string) ([]*promutils.Labels, error) {
+	cfg, err := getAPIConfig(sdc, baseDir)
+	if err != nil {
+		return nil, fmt.Errorf("cannot get API config: %w", err)
+	}
+	switch sdc.Service {
+	case "dedicated_server":
+		return getDedicatedServerLabels(cfg)
+	case "vps":
+		return getVPSLabels(cfg)
+	default:
+		return nil, fmt.Errorf("skipping unexpected service=%q; only `dedicated_server` and `vps` are supported for now", sdc.Service)
+	}
+}
+
+// MustStop stops further usage for sdc.
+func (sdc *SDConfig) MustStop() {
+	v := configMap.Delete(sdc)
+	if v != nil {
+		cfg := v.(*apiConfig)
+		cfg.client.Stop()
+	}
+}
diff --git a/lib/promscrape/discovery/ovhcloud/vps.go b/lib/promscrape/discovery/ovhcloud/vps.go
new file mode 100644
index 000000000..62ab6ca92
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/vps.go
@@ -0,0 +1,174 @@
+package ovhcloud
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/netip"
+	"net/url"
+	"path"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
+)
+
+// vpsModel struct from API.
+// See: https://eu.api.ovh.com/console/#/vps/%7BserviceName%7D~GET and getVPSDetails
+type vpsModel struct {
+	MaximumAdditionalIP int      `json:"maximumAdditionnalIp"`
+	Offer               string   `json:"offer"`
+	Datacenter          []string `json:"datacenter"`
+	Vcore               int      `json:"vcore"`
+	Version             string   `json:"version"`
+	Name                string   `json:"name"`
+	Disk                int      `json:"disk"`
+	Memory              int      `json:"memory"`
+}
+
+// virtualPrivateServer struct from API.
+// IP addresses are fetched independently.
+type virtualPrivateServer struct {
+	IPs         []netip.Addr
+	Zone        string   `json:"zone"`
+	Model       vpsModel `json:"model"`
+	DisplayName string   `json:"displayName"`
+	Cluster     string   `json:"cluster"`
+	State       string   `json:"state"`
+	Name        string   `json:"name"`
+	NetbootMode string   `json:"netbootMode"`
+	MemoryLimit int      `json:"memoryLimit"`
+	OfferType   string   `json:"offerType"`
+	Vcore       int      `json:"vcore"`
+
+	// The following fields are defined in the response but are not used during service discovery.
+	//Keymap             []string `json:"keymap"`
+	//MonitoringIPBlocks []string `json:"monitoringIpBlocks"`
+}
+
+// getVPSLabels get labels for VPS.
+func getVPSLabels(cfg *apiConfig) ([]*promutils.Labels, error) {
+	vpsList, err := getVPSList(cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	// Attach properties to each VPS and compose vpsDetailedList
+	vpsDetailedList := make([]virtualPrivateServer, 0, len(vpsList))
+	for _, vpsName := range vpsList {
+		vpsDetailed, err := getVPSDetails(cfg, vpsName)
+		if err != nil {
+			logger.Errorf("getVPSDetails for %s failed, err: %v", vpsName, err)
+			continue
+		}
+		vpsDetailedList = append(vpsDetailedList, *vpsDetailed)
+	}
+
+	ms := make([]*promutils.Labels, 0, len(vpsDetailedList))
+	for _, server := range vpsDetailedList {
+		// convert IPs into string and select default IP.
+		var ipv4, ipv6 string
+		for _, ip := range server.IPs {
+			if ip.Is4() {
+				ipv4 = ip.String()
+			}
+			if ip.Is6() {
+				ipv6 = ip.String()
+			}
+		}
+		defaultIP := ipv4
+		if defaultIP == "" {
+			defaultIP = ipv6
+		}
+
+		m := promutils.NewLabels(21)
+		m.Add("__address__", defaultIP)
+		m.Add("instance", server.Name)
+		m.Add("__meta_ovhcloud_vps_offer", server.Model.Offer)
+		m.Add("__meta_ovhcloud_vps_datacenter", fmt.Sprintf("%+v", server.Model.Datacenter))
+		m.Add("__meta_ovhcloud_vps_model_vcore", fmt.Sprintf("%d", server.Model.Vcore))
+		m.Add("__meta_ovhcloud_vps_maximum_additional_ip", fmt.Sprintf("%d", server.Model.MaximumAdditionalIP))
+		m.Add("__meta_ovhcloud_vps_version", server.Model.Version)
+		m.Add("__meta_ovhcloud_vps_model_name", server.Model.Name)
+		m.Add("__meta_ovhcloud_vps_disk", fmt.Sprintf("%d", server.Model.Disk))
+		m.Add("__meta_ovhcloud_vps_memory", fmt.Sprintf("%d", server.Model.Memory))
+		m.Add("__meta_ovhcloud_vps_zone", server.Zone)
+		m.Add("__meta_ovhcloud_vps_display_name", server.DisplayName)
+		m.Add("__meta_ovhcloud_vps_cluster", server.Cluster)
+		m.Add("__meta_ovhcloud_vps_state", server.State)
+		m.Add("__meta_ovhcloud_vps_name", server.Name)
+		m.Add("__meta_ovhcloud_vps_netboot_mode", server.NetbootMode)
+		m.Add("__meta_ovhcloud_vps_memory_limit", fmt.Sprintf("%d", server.MemoryLimit))
+		m.Add("__meta_ovhcloud_vps_offer_type", server.OfferType)
+		m.Add("__meta_ovhcloud_vps_vcore", fmt.Sprintf("%d", server.Vcore))
+		m.Add("__meta_ovhcloud_vps_ipv4", ipv4)
+		m.Add("__meta_ovhcloud_vps_ipv6", ipv6)
+
+		ms = append(ms, m)
+	}
+	return ms, nil
+}
+
+// getVPSDetails get properties of a VPS.
+// Also see: https://eu.api.ovh.com/console/#/vps/%7BserviceName%7D~GET
+func getVPSDetails(cfg *apiConfig, vpsName string) (*virtualPrivateServer, error) {
+	// get properties.
+	reqPath := path.Join("/vps", url.QueryEscape(vpsName))
+	resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
+		request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
+	})
+	if err != nil {
+		return nil, fmt.Errorf("request %s error: %v", reqPath, err)
+	}
+
+	var vpsDetails virtualPrivateServer
+	if err = json.Unmarshal(resp, &vpsDetails); err != nil {
+		return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
+	}
+
+	// get IPs for this vps.
+	// e.g. ["139.99.154.111","2402:1f00:8100:401::bb6"]
+	// Also see: https://eu.api.ovh.com/console/#/vps/%7BserviceName%7D/ips~GET
+	reqPath = path.Join(reqPath, "ips")
+	resp, err = cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
+		request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
+	})
+	if err != nil {
+		return nil, fmt.Errorf("request %s error: %v", reqPath, err)
+	}
+
+	var ips []string
+	if err = json.Unmarshal(resp, &ips); err != nil {
+		return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
+	}
+
+	// handle different IP formats
+	parsedIPs, err := parseIPList(ips)
+	if err != nil {
+		return nil, err
+	}
+
+	// attach to details
+	vpsDetails.IPs = parsedIPs
+
+	return &vpsDetails, nil
+}
+
+// getVPSList list available services.
+// example: ["vps-000e0e00.vps.ovh.ca", "vps-000e0e01.vps.ovh.ca"]
+// Also see: https://eu.api.ovh.com/console/#/vps~GET
+func getVPSList(cfg *apiConfig) ([]string, error) {
+	reqPath := "/vps"
+	resp, err := cfg.client.GetAPIResponseWithReqParams(reqPath, func(request *http.Request) {
+		request.Header, _ = getAuthHeaders(cfg, request.Header, cfg.client.APIServer(), reqPath)
+	})
+	if err != nil {
+		return nil, fmt.Errorf("request %s error: %v", reqPath, err)
+	}
+
+	var vpsList []string
+	if err = json.Unmarshal(resp, &vpsList); err != nil {
+		return nil, fmt.Errorf("cannot unmarshal %s response: %v", reqPath, err)
+	}
+
+	return vpsList, nil
+}
diff --git a/lib/promscrape/scraper.go b/lib/promscrape/scraper.go
index d60511b8c..b34cf1592 100644
--- a/lib/promscrape/scraper.go
+++ b/lib/promscrape/scraper.go
@@ -30,6 +30,7 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/kuma"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/nomad"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/openstack"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/ovhcloud"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/vultr"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discovery/yandexcloud"
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
@@ -141,6 +142,7 @@ func runScraper(configFile string, pushData func(at *auth.Token, wr *prompbmarsh
 	scs.add("kuma_sd_configs", *kuma.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getKumaSDScrapeWork(swsPrev) })
 	scs.add("nomad_sd_configs", *nomad.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getNomadSDScrapeWork(swsPrev) })
 	scs.add("openstack_sd_configs", *openstack.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getOpenStackSDScrapeWork(swsPrev) })
+	scs.add("ovhcloud_sd_configs", *ovhcloud.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getOVHCloudSDScrapeWork(swsPrev) })
 	scs.add("vultr_sd_configs", *vultr.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getVultrSDScrapeWork(swsPrev) })
 	scs.add("yandexcloud_sd_configs", *yandexcloud.SDCheckInterval, func(cfg *Config, swsPrev []*ScrapeWork) []*ScrapeWork { return cfg.getYandexCloudSDScrapeWork(swsPrev) })
 	scs.add("static_configs", 0, func(cfg *Config, _ []*ScrapeWork) []*ScrapeWork { return cfg.getStaticScrapeWork() })


Test patch:
diff --git a/lib/promscrape/discovery/ovhcloud/api_test.go b/lib/promscrape/discovery/ovhcloud/api_test.go
new file mode 100644
index 000000000..85c23afd7
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/api_test.go
@@ -0,0 +1,35 @@
+package ovhcloud
+
+import (
+	"testing"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
+)
+
+func Test_newAPIConfig(t *testing.T) {
+	t.Run("normal case", func(t *testing.T) {
+		sdc := &SDConfig{
+			Endpoint:          "ovh-ca",
+			ApplicationKey:    "no-op",
+			ApplicationSecret: &promauth.Secret{S: "no-op"},
+			ConsumerKey:       &promauth.Secret{S: "no-op"},
+			Service:           "vps",
+		}
+		if _, err := newAPIConfig(sdc, ""); err != nil {
+			t.Fatalf("newAPIConfig got error: %v", err)
+		}
+	})
+
+	t.Run("incorrect endpoint", func(t *testing.T) {
+		sdc := &SDConfig{
+			Endpoint:          "in-correct-endpoint",
+			ApplicationKey:    "no-op",
+			ApplicationSecret: &promauth.Secret{S: "no-op"},
+			ConsumerKey:       &promauth.Secret{S: "no-op"},
+			Service:           "vps",
+		}
+		if _, err := newAPIConfig(sdc, ""); err == nil {
+			t.Fatalf("newAPIConfig want error, but error = %v", err)
+		}
+	})
+}
diff --git a/lib/promscrape/discovery/ovhcloud/dedicated_server_test.go b/lib/promscrape/discovery/ovhcloud/dedicated_server_test.go
new file mode 100644
index 000000000..c95ff1df9
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/dedicated_server_test.go
@@ -0,0 +1,99 @@
+package ovhcloud
+
+import (
+	"errors"
+	"reflect"
+	"sync/atomic"
+	"testing"
+	"time"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
+)
+
+func Test_getDedicatedServerLabels(t *testing.T) {
+	mockSvr := newMockOVHCloudServer(func(path string) ([]byte, error) {
+		switch path {
+		case "/dedicated/server":
+			return []byte(`["ns0000000.ip-00-00-000.eu"]`), nil
+		case "/dedicated/server/ns0000000.ip-00-00-000.eu":
+			return mockDedicatedServerDetail, nil
+		case "/dedicated/server/ns0000000.ip-00-00-000.eu/ips":
+			return []byte(`["2001:40d0:302:8874::/64","50.75.126.113/32"]`), nil
+		default:
+			return []byte{}, errors.New("invalid request")
+		}
+	})
+	c, _ := discoveryutils.NewClient(mockSvr.URL, nil, nil, nil, &promauth.HTTPClientConfig{})
+	td := atomic.Value{}
+	td.Store(time.Duration(1))
+	cfg := &apiConfig{
+		client:            c,
+		applicationKey:    "",
+		applicationSecret: "",
+		consumerKey:       "",
+		timeDelta:         td,
+	}
+
+	expectLabels := &promutils.Labels{}
+	expectLabels.Add("__address__", "50.75.126.113")
+	expectLabels.Add("instance", "ns0000000.ip-00-00-000.eu")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_state", "ok")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_commercial_range", "RISE-3")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_link_speed", "1000")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_rack", "G000A00")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_no_intervention", "false")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_os", "centos7_64")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_support_level", "pro")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_server_id", "1000000")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_reverse", "ns0000000.ip-00-00-000.eu")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_datacenter", "gra2")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_name", "ns0000000.ip-00-00-000.eu")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_ipv4", "50.75.126.113")
+	expectLabels.Add("__meta_ovhcloud_dedicated_server_ipv6", "")
+	expect := []*promutils.Labels{
+		expectLabels,
+	}
+
+	result, err := getDedicatedServerLabels(cfg)
+	if err != nil {
+		t.Fatalf("getDedicatedServerLabels unexpected error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expect, result) {
+		t.Fatalf("getDedicatedServerLabels incorrect, want: %v, got: %v", expect, result)
+	}
+}
+
+var mockDedicatedServerDetail = []byte(
+	`{
+	"name": "ns0000000.ip-00-00-000.eu",
+	"availabilityZone": "eu-west-gra-a",
+	"datacenter": "gra2",
+	"bootScript": null,
+	"linkSpeed": 1000,
+	"reverse": "ns0000000.ip-00-00-000.eu",
+	"serverId": 1000000,
+	"monitoring": false,
+	"rootDevice": null,
+	"noIntervention": false,
+	"newUpgradeSystem": true,
+	"rack": "G000A00",
+	"rescueSshKey": null,
+	"supportLevel": "pro",
+	"powerState": "poweron",
+	"commercialRange": "RISE-3",
+	"professionalUse": false,
+	"rescueMail": null,
+	"region": "eu-west-gra",
+	"bootId": 1,
+	"state": "ok",
+	"os": "centos7_64",
+	"ip": "50.75.126.113",
+	"iam": {
+		"displayName": "ns0000000.ip-00-00-000.eu",
+		"id": "000da00d-00d0-0b00-0000-00000a0000bd",
+		"urn": "urn:v1:eu:resource:dedicatedServer:ns0000000.ip-00-00-000.eu"
+	}
+}`)
diff --git a/lib/promscrape/discovery/ovhcloud/mock_server_test.go b/lib/promscrape/discovery/ovhcloud/mock_server_test.go
new file mode 100644
index 000000000..0b037da71
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/mock_server_test.go
@@ -0,0 +1,40 @@
+package ovhcloud
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+)
+
+func newMockOVHCloudServer(jsonResponse func(path string) ([]byte, error)) *ovhcloudServer {
+	rw := &ovhcloudServer{}
+	rw.Server = httptest.NewServer(http.HandlerFunc(rw.handler))
+	rw.jsonResponse = jsonResponse
+	return rw
+}
+
+type ovhcloudServer struct {
+	*httptest.Server
+	jsonResponse func(path string) ([]byte, error)
+}
+
+func (rw *ovhcloudServer) err(w http.ResponseWriter, err error) {
+	w.WriteHeader(http.StatusBadRequest)
+	w.Write([]byte(err.Error()))
+}
+
+func (rw *ovhcloudServer) handler(w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		rw.err(w, fmt.Errorf("bad method %q", r.Method))
+		return
+	}
+
+	resp, err := rw.jsonResponse(r.RequestURI)
+	if err != nil {
+		rw.err(w, err)
+		return
+	}
+
+	w.Write(resp)
+	w.WriteHeader(http.StatusOK)
+}
diff --git a/lib/promscrape/discovery/ovhcloud/ovhcloud_test.go b/lib/promscrape/discovery/ovhcloud/ovhcloud_test.go
new file mode 100644
index 000000000..b5fc85227
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/ovhcloud_test.go
@@ -0,0 +1,22 @@
+package ovhcloud
+
+import (
+	"testing"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
+)
+
+func Test_GetLabels(t *testing.T) {
+	t.Run("incorrect service", func(t *testing.T) {
+		sdc := &SDConfig{
+			Endpoint:          "ovh-ca",
+			ApplicationKey:    "no-op",
+			ApplicationSecret: &promauth.Secret{S: "no-op"},
+			ConsumerKey:       &promauth.Secret{S: "no-op"},
+			Service:           "incorrect service",
+		}
+		if _, err := sdc.GetLabels(""); err == nil {
+			t.Fatalf("newAPIConfig want err, got: %v", err)
+		}
+	})
+}
diff --git a/lib/promscrape/discovery/ovhcloud/vps_test.go b/lib/promscrape/discovery/ovhcloud/vps_test.go
new file mode 100644
index 000000000..a80b6a583
--- /dev/null
+++ b/lib/promscrape/discovery/ovhcloud/vps_test.go
@@ -0,0 +1,104 @@
+package ovhcloud
+
+import (
+	"errors"
+	"reflect"
+	"sync/atomic"
+	"testing"
+	"time"
+
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
+	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
+)
+
+func Test_getVpsLabels(t *testing.T) {
+	mockSvr := newMockOVHCloudServer(func(path string) ([]byte, error) {
+		switch path {
+		case "/vps":
+			return []byte(`["vps-000e0e00.vps.ovh.ca"]`), nil
+		case "/vps/vps-000e0e00.vps.ovh.ca":
+			return mockVpsDetail, nil
+		case "/vps/vps-000e0e00.vps.ovh.ca/ips":
+			return []byte(`["139.99.154.158","2402:1f00:8100:401::bb6"]`), nil
+		default:
+			return []byte{}, errors.New("invalid request")
+		}
+	})
+	c, _ := discoveryutils.NewClient(mockSvr.URL, nil, nil, nil, &promauth.HTTPClientConfig{})
+	td := atomic.Value{}
+	td.Store(time.Duration(1))
+	cfg := &apiConfig{
+		client:            c,
+		applicationKey:    "",
+		applicationSecret: "",
+		consumerKey:       "",
+		timeDelta:         td,
+	}
+
+	expectLabels := &promutils.Labels{}
+	expectLabels.Add("__address__", "139.99.154.158")
+	expectLabels.Add("instance", "vps-000e0e00.vps.ovh.ca")
+	expectLabels.Add("__meta_ovhcloud_vps_offer", "VPS vps2020-starter-1-2-20")
+	expectLabels.Add("__meta_ovhcloud_vps_datacenter", "[]")
+	expectLabels.Add("__meta_ovhcloud_vps_model_vcore", "1")
+	expectLabels.Add("__meta_ovhcloud_vps_maximum_additional_ip", "16")
+	expectLabels.Add("__meta_ovhcloud_vps_version", "2019v1")
+	expectLabels.Add("__meta_ovhcloud_vps_model_name", "vps-starter-1-2-20")
+	expectLabels.Add("__meta_ovhcloud_vps_disk", "20")
+	expectLabels.Add("__meta_ovhcloud_vps_memory", "2048")
+	expectLabels.Add("__meta_ovhcloud_vps_zone", "Region OpenStack: os-syd2")
+	expectLabels.Add("__meta_ovhcloud_vps_display_name", "vps-000e0e00.vps.ovh.ca")
+	expectLabels.Add("__meta_ovhcloud_vps_cluster", "")
+	expectLabels.Add("__meta_ovhcloud_vps_state", "running")
+	expectLabels.Add("__meta_ovhcloud_vps_name", "vps-000e0e00.vps.ovh.ca")
+	expectLabels.Add("__meta_ovhcloud_vps_netboot_mode", "local")
+	expectLabels.Add("__meta_ovhcloud_vps_memory_limit", "2048")
+	expectLabels.Add("__meta_ovhcloud_vps_offer_type", "ssd")
+	expectLabels.Add("__meta_ovhcloud_vps_vcore", "1")
+	expectLabels.Add("__meta_ovhcloud_vps_ipv4", "139.99.154.158")
+	expectLabels.Add("__meta_ovhcloud_vps_ipv6", "2402:1f00:8100:401::bb6")
+	expect := []*promutils.Labels{
+		expectLabels,
+	}
+
+	result, err := getVPSLabels(cfg)
+	if err != nil {
+		t.Fatalf("getDedicatedServerLabels unexpected error: %v", err)
+	}
+
+	if !reflect.DeepEqual(expect, result) {
+		t.Fatalf("getDedicatedServerLabels incorrect, want: %v, got: %v", expect, result)
+	}
+}
+
+var mockVpsDetail = []byte(
+	`{
+	"model": {
+		"name": "vps-starter-1-2-20",
+		"offer": "VPS vps2020-starter-1-2-20",
+		"availableOptions": [],
+		"maximumAdditionnalIp": 16,
+		"version": "2019v1",
+		"datacenter": [],
+		"vcore": 1,
+		"memory": 2048,
+		"disk": 20
+	},
+	"netbootMode": "local",
+	"cluster": "",
+	"name": "vps-000e0e00.vps.ovh.ca",
+	"displayName": "vps-000e0e00.vps.ovh.ca",
+	"vcore": 1,
+	"monitoringIpBlocks": [],
+	"zone": "Region OpenStack: os-syd2",
+	"memoryLimit": 2048,
+	"offerType": "ssd",
+	"state": "running",
+	"keymap": null,
+	"slaMonitoring": false,
+	"iam": {
+		"id": "00ea0000-0b0f-0000-0000-e000000a0000",
+		"urn": "urn:v1:eu:resource:vps:vps-000e0e00.vps.ovh.ca"
+	}
+}`)