Repository: VictoriaMetrics/VictoriaMetrics
Pull request #2468 (2022-04-14 09:48:37)

lib/httpserver: added tlsCipherSuites flag
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2404

codecov[bot]: # [Codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
> Merging [#2468](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) (fe67994) into [master](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/f7e4c5a628e43e5ff9f2c784889a0e548578ba73?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) (f7e4c5a) will **decrease** coverage by `0.80%`.
> The diff coverage is `77.27%`.

> :exclamation: Current head fe67994 differs from pull request most recent head 8ff4f43. Consider uploading reports for the commit 8ff4f43 to get more accurate results

```diff
@@            Coverage Diff             @@
##           master    #2468      +/-   ##
==========================================
- Coverage   60.86%   60.06%   -0.81%     
==========================================
  Files         242      245       +3     
  Lines       41375    41941     +566     
==========================================
+ Hits        25182    25190       +8     
- Misses      14536    15094     +558     
  Partials     1657     1657              
```


| [Impacted Files](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) | Coverage Δ | |
|---|---|---|
| [lib/httpserver/httpserver.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL2h0dHBzZXJ2ZXIvaHR0cHNlcnZlci5nbw==) | `4.16% <77.27%> (ø)` | |
| [lib/fasttime/fasttime.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL2Zhc3R0aW1lL2Zhc3R0aW1lLmdv) | `50.00% <0.00%> (-21.43%)` | :arrow_down: |
| [app/vmselect/promql/aggr\_incremental.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3Ztc2VsZWN0L3Byb21xbC9hZ2dyX2luY3JlbWVudGFsLmdv) | `86.44% <0.00%> (-1.21%)` | :arrow_down: |
| [lib/mergeset/table.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL21lcmdlc2V0L3RhYmxlLmdv) | `65.79% <0.00%> (-0.97%)` | :arrow_down: |
| [lib/httpserver/metrics.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL2h0dHBzZXJ2ZXIvbWV0cmljcy5nbw==) | `0.00% <0.00%> (ø)` | |
| [lib/httpserver/path.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL2h0dHBzZXJ2ZXIvcGF0aC5nbw==) | `0.00% <0.00%> (ø)` | |
| [lib/storage/storage.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3N0b3JhZ2Uvc3RvcmFnZS5nbw==) | `67.67% <0.00%> (+0.22%)` | :arrow_up: |
| [lib/bloomfilter/filter.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL2Jsb29tZmlsdGVyL2ZpbHRlci5nbw==) | `100.00% <0.00%> (+2.00%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics). Last update [e6535a7...8ff4f43](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/2468?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index ed6692c1c..63376fe90 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -33,7 +33,7 @@ of Grafana dashboards if possible:
 
 See how to setup monitoring here:
 * [monitoring for single-node VictoriaMetrics](https://docs.victoriametrics.com/#monitoring)
-* [montioring for VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#monitoring)
+* [monitoring for VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#monitoring)
 
 **Version**
 The line returned when passing `--version` command line flag to the binary. For example:
diff --git a/README.md b/README.md
index e3cbf9d6e..eeb767936 100644
--- a/README.md
+++ b/README.md
@@ -1923,6 +1923,8 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li
      Path to file with TLS certificate. Used only if -tls is set. Prefer ECDSA certs instead of RSA certs as RSA certs are slower. The provided certificate file is automatically re-read every second, so it can be dynamically updated
   -tlsKeyFile string
      Path to file with TLS key. Used only if -tls is set. The provided key file is automatically re-read every second, so it can be dynamically updated
+  -tlsCipherSuites
+      Cipher suites names for TLS encryption. For example, TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA. Used only if -tls flag is set
   -version
      Show VictoriaMetrics version
 ```
diff --git a/app/vmalert/config/config.go b/app/vmalert/config/config.go
index 81e3f0819..ec961b092 100644
--- a/app/vmalert/config/config.go
+++ b/app/vmalert/config/config.go
@@ -25,10 +25,10 @@ import (
 type Group struct {
 	Type        datasource.Type `yaml:"type,omitempty"`
 	File        string
-	Name        string             `yaml:"name"`
-	Interval    promutils.Duration `yaml:"interval"`
-	Rules       []Rule             `yaml:"rules"`
-	Concurrency int                `yaml:"concurrency"`
+	Name        string              `yaml:"name"`
+	Interval    *promutils.Duration `yaml:"interval,omitempty"`
+	Rules       []Rule              `yaml:"rules"`
+	Concurrency int                 `yaml:"concurrency"`
 	// ExtraFilterLabels is a list label filters applied to every rule
 	// request withing a group. Is compatible only with VM datasources.
 	// See https://docs.victoriametrics.com#prometheus-querying-api-enhancements
@@ -127,12 +127,12 @@ func (g *Group) Validate(validateAnnotations, validateExpressions bool) error {
 // recording rule or alerting rule.
 type Rule struct {
 	ID          uint64
-	Record      string             `yaml:"record,omitempty"`
-	Alert       string             `yaml:"alert,omitempty"`
-	Expr        string             `yaml:"expr"`
-	For         promutils.Duration `yaml:"for"`
-	Labels      map[string]string  `yaml:"labels,omitempty"`
-	Annotations map[string]string  `yaml:"annotations,omitempty"`
+	Record      string              `yaml:"record,omitempty"`
+	Alert       string              `yaml:"alert,omitempty"`
+	Expr        string              `yaml:"expr"`
+	For         *promutils.Duration `yaml:"for,omitempty"`
+	Labels      map[string]string   `yaml:"labels,omitempty"`
+	Annotations map[string]string   `yaml:"annotations,omitempty"`
 
 	// Catches all undefined fields and must be empty after parsing.
 	XXX map[string]interface{} `yaml:",inline"`
diff --git a/app/vmalert/notifier/config.go b/app/vmalert/notifier/config.go
index 3eb776fbb..d43cced4d 100644
--- a/app/vmalert/notifier/config.go
+++ b/app/vmalert/notifier/config.go
@@ -44,7 +44,7 @@ type Config struct {
 	// AlertRelabelConfigs contains list of relabeling rules alert labels
 	AlertRelabelConfigs []promrelabel.RelabelConfig `yaml:"alert_relabel_configs,omitempty"`
 	// The timeout used when sending alerts.
-	Timeout promutils.Duration `yaml:"timeout,omitempty"`
+	Timeout *promutils.Duration `yaml:"timeout,omitempty"`
 
 	// Checksum stores the hash of yaml definition for the config.
 	// May be used to detect any changes to the config file.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index e87e7eabf..afa5d3dec 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -18,6 +18,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
 * FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): add support for DNS-based discovery for notifiers in the same way as Prometheus does. See [these docs](https://docs.victoriametrics.com/vmalert.html#notifier-configuration-file) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2460).
 
 * BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl.html): return non-zero exit code on error. This allows handling `vmctl` errors in shell scripts. Previously `vmctl` was returning 0 exit code on error. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2322).
+* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly show `scrape_timeout` and `scrape_interval` options at `http://vmagent:8429/config` page. Previously these options weren't displayed even if they were set in `-promscrape.config`.
 
 
 ## [v1.76.1](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.76.1)
diff --git a/docs/Cluster-VictoriaMetrics.md b/docs/Cluster-VictoriaMetrics.md
index 78e84b157..77187411e 100644
--- a/docs/Cluster-VictoriaMetrics.md
+++ b/docs/Cluster-VictoriaMetrics.md
@@ -129,8 +129,9 @@ A minimal cluster must contain the following nodes:
 - a single `vminsert` node with `-storageNode=<vmstorage_host>`
 - a single `vmselect` node with `-storageNode=<vmstorage_host>`
 
-It is recommended to run at least two nodes for each service
-for high availability purposes.
+It is recommended to run at least two nodes for each service for high availability purposes. In this case the cluster continues working when a single node is temporarily unavailable and the remaining nodes can handle the increased workload. The node may be temporarily unavailable when the underlying hardware breaks, during software upgrades, migration or other maintenance tasks.
+
+It is preferred to run many small `vmstorage` nodes over a few big `vmstorage` nodes, since this reduces the workload increase on the remaining `vmstorage` nodes when some of `vmstorage` nodes become temporarily unavailable.
 
 An http load balancer such as [vmauth](https://docs.victoriametrics.com/vmauth.html) or `nginx` must be put in front of `vminsert` and `vmselect` nodes. It must contain the following routing configs according to [the url format](#url-format):
 
@@ -255,15 +256,18 @@ It is recommended setting up alerts in [vmalert](https://docs.victoriametrics.co
 
 ## Cluster resizing and scalability
 
-Cluster performance and capacity scales with adding new nodes.
+Cluster performance and capacity can be scaled up in two ways:
+
+- By adding more resources (CPU, RAM, disk IO, disk space, network bandwidth) to existing nodes in the cluster (aka vertical scalability).
+- By adding more nodes to the cluster (aka horizontal scalability).
+
+General recommendations for cluster scalability:
 
-- `vminsert` and `vmselect` nodes are stateless and may be added / removed at any time.
-  Do not forget updating the list of these nodes on http load balancer.
-  Adding more `vminsert` nodes scales data ingestion rate. See [this comment](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/175#issuecomment-536925841)
-  about ingestion rate scalability.
-  Adding more `vmselect` nodes scales select queries rate.
-- `vmstorage` nodes own the ingested data, so they cannot be removed without data loss.
-  Adding more `vmstorage` nodes scales cluster capacity.
+- Adding more CPU and RAM to existing `vmselect` nodes improves the performance for heavy queries, which process big number of time series with big number of raw samples.
+- Adding more `vmstorage` nodes increases the number of [active time series](https://docs.victoriametrics.com/FAQ.html#what-is-an-active-time-series) the cluster can handle. This also increases query performance over time series with [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate). The cluster stability is also improved with the number of `vmstorage` nodes, since active `vmstorage` nodes need to handle lower additional workload when some of `vmstorage` nodes become unavailable.
+- Adding more CPU and RAM to existing `vmstorage` nodes increases the number of [active time series](https://docs.victoriametrics.com/FAQ.html#what-is-an-active-time-series) the cluster can handle. It is preferred to add more `vmstorage` nodes over adding more CPU and RAM to existing `vmstorage` nodes, since higher number of `vmstorage` nodes increases cluster stability and improves query performance over time series with [high churn rate](https://docs.victoriametrics.com/FAQ.html#what-is-high-churn-rate).
+- Adding more `vminsert` nodes increases the maximum possible data ingestion speed, since the ingested data may be split among bigger number of `vminsert` nodes.
+- Adding more `vmselect` nodes increases the maximum possible queries rate, since the incoming concurrent requests may be split among bigger number of `vmselect` nodes.
 
 Steps to add `vmstorage` node:
 
@@ -336,7 +340,12 @@ Then [promxy](https://github.com/jacksontj/promxy) could be used for querying th
 
 `vminsert` nodes can accept data from another `vminsert` nodes starting from [v1.60.0](https://docs.victoriametrics.com/CHANGELOG.html#v1600) if `-clusternativeListenAddr` command-line flag is set. For example, if `vminsert` is started with `-clusternativeListenAddr=:8400` command-line flag, then it can accept data from another `vminsert` nodes at TCP port 8400 in the same way as `vmstorage` nodes do. This allows chaining `vminsert` nodes and building multi-level cluster topologies with flexible configs. For example, the top level of `vminsert` nodes can replicate data among the second level of `vminsert` nodes located in distinct availability zones (AZ), while the second-level `vminsert` nodes can spread the data among `vmstorage` nodes located in the same AZ. Such setup guarantees cluster availability if some AZ becomes unavailable. The data from all the `vmstorage` nodes in all the AZs can be read via `vmselect` nodes, which are configured to query all the `vmstorage` nodes in all the availability zones (e.g. all the `vmstorage` addresses are passed via `-storageNode` command-line flag to `vmselect` nodes). Additionally, `-replicationFactor=k+1` must be passed to `vmselect` nodes, where `k` is the lowest number of `vmstorage` nodes in a single AZ. See [replication docs](#replication-and-data-safety) for more details.
 
-Another option is to set up [vmagent](https://docs.victoriametrics.com/vmagent.html) for replicating the data among multiple VictoriaMetrics clusters. See [these docs](https://docs.victoriametrics.com/vmagent.html#multitenancy) for details.
+The multi-level cluster setup for `vminsert` nodes has the following shortcomings because of synchronous replication and data sharding:
+
+* Data ingestion speed is limited by the slowest link to AZ.
+* `vminsert` nodes at top level re-route incoming data to the remaining AZs when some AZs are temporariliy unavailable. This results in data gaps at AZs which were temporarily unavailable.
+
+These issues are addressed by [vmagent](https://docs.victoriametrics.com/vmagent.html) when it runs in [multitenancy mode](https://docs.victoriametrics.com/vmagent.html#multitenancy). `vmagent` buffers data, which must be sent to a particular AZ, when this AZ is temporarily unavailable. The buffer is stored on disk. The buffered data is sent to AZ as soon as it becomes available.
 
 ## Helm
 
diff --git a/lib/httpserver/httpserver.go b/lib/httpserver/httpserver.go
index 18a54fd6c..f343ef724 100644
--- a/lib/httpserver/httpserver.go
+++ b/lib/httpserver/httpserver.go
@@ -30,9 +30,10 @@ import (
 )
 
 var (
-	tlsEnable   = flag.Bool("tls", false, "Whether to enable TLS (aka HTTPS) for incoming requests. -tlsCertFile and -tlsKeyFile must be set if -tls is set")
-	tlsCertFile = flag.String("tlsCertFile", "", "Path to file with TLS certificate. Used only if -tls is set. Prefer ECDSA certs instead of RSA certs as RSA certs are slower. The provided certificate file is automatically re-read every second, so it can be dynamically updated")
-	tlsKeyFile  = flag.String("tlsKeyFile", "", "Path to file with TLS key. Used only if -tls is set. The provided key file is automatically re-read every second, so it can be dynamically updated")
+	tlsEnable       = flag.Bool("tls", false, "Whether to enable TLS (aka HTTPS) for incoming requests. -tlsCertFile and -tlsKeyFile must be set if -tls is set")
+	tlsCertFile     = flag.String("tlsCertFile", "", "Path to file with TLS certificate. Used only if -tls is set. Prefer ECDSA certs instead of RSA certs as RSA certs are slower. The provided certificate file is automatically re-read every second, so it can be dynamically updated")
+	tlsKeyFile      = flag.String("tlsKeyFile", "", "Path to file with TLS key. Used only if -tls is set. The provided key file is automatically re-read every second, so it can be dynamically updated")
+	tlsCipherSuites = flagutil.NewArray("tlsCipherSuites", "Cipher suites names for TLS encryption. For example, TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA. Used only if -tls flag is set")
 
 	pathPrefix = flag.String("http.pathPrefix", "", "An optional prefix to add to all the paths handled by http server. For example, if '-http.pathPrefix=/foo/bar' is set, "+
 		"then all the http requests will be handled on '/foo/bar/*' paths. This may be useful for proxied requests. "+
@@ -100,10 +101,18 @@ func Serve(addr string, rh RequestHandler) {
 		var certLock sync.Mutex
 		var certDeadline uint64
 		var cert *tls.Certificate
+		var cipherSuites []uint16
 		c, err := tls.LoadX509KeyPair(*tlsCertFile, *tlsKeyFile)
 		if err != nil {
 			logger.Fatalf("cannot load TLS cert from tlsCertFile=%q, tlsKeyFile=%q: %s", *tlsCertFile, *tlsKeyFile, err)
 		}
+		if len(*tlsCipherSuites) != 0 {
+			collectedCipherSuites, err := collectCipherSuites(*tlsCipherSuites)
+			if err != nil {
+				logger.Fatalf("cannot use TLS cipher suites from tlsCipherSuites=%q: %s", *tlsCipherSuites, err)
+			}
+			cipherSuites = collectedCipherSuites
+		}
 		cert = &c
 		cfg := &tls.Config{
 			MinVersion:               tls.VersionTLS12,
@@ -121,6 +130,7 @@ func Serve(addr string, rh RequestHandler) {
 				}
 				return cert, nil
 			},
+			CipherSuites: cipherSuites,
 		}
 		ln = tls.NewListener(ln, cfg)
 	}
@@ -687,3 +697,21 @@ func GetRequestURI(r *http.Request) string {
 	}
 	return requestURI + delimiter + queryArgs
 }
+
+func collectCipherSuites(definedCipherSuites []string) ([]uint16, error) {
+	var cipherSuites []uint16
+
+	supportedCipherSuites := tls.CipherSuites()
+	supportedCipherSuitesMap := make(map[string]uint16, len(supportedCipherSuites))
+	for _, scf := range supportedCipherSuites {
+		supportedCipherSuitesMap[strings.ToLower(scf.Name)] = scf.ID
+	}
+	for _, gotSuite := range definedCipherSuites {
+		id, ok := supportedCipherSuitesMap[strings.ToLower(gotSuite)]
+		if !ok {
+			return nil, fmt.Errorf("got unsupported cipher suite name: %s", gotSuite)
+		}
+		cipherSuites = append(cipherSuites, id)
+	}
+	return cipherSuites, nil
+}
diff --git a/lib/promscrape/config.go b/lib/promscrape/config.go
index 15e2feef6..83ffc3c27 100644
--- a/lib/promscrape/config.go
+++ b/lib/promscrape/config.go
@@ -83,6 +83,19 @@ type Config struct {
 	baseDir string
 }
 
+func (cfg *Config) unmarshal(data []byte, isStrict bool) error {
+	data = envtemplate.Replace(data)
+	var err error
+	if isStrict {
+		if err = yaml.UnmarshalStrict(data, cfg); err != nil {
+			err = fmt.Errorf("%w; pass -promscrape.config.strictParse=false command-line flag for ignoring unknown fields in yaml config", err)
+		}
+	} else {
+		err = yaml.Unmarshal(data, cfg)
+	}
+	return err
+}
+
 func (cfg *Config) marshal() []byte {
 	data, err := yaml.Marshal(cfg)
 	if err != nil {
@@ -124,9 +137,9 @@ func (cfg *Config) getJobNames() []string {
 //
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/
 type GlobalConfig struct {
-	ScrapeInterval promutils.Duration `yaml:"scrape_interval,omitempty"`
-	ScrapeTimeout  promutils.Duration `yaml:"scrape_timeout,omitempty"`
-	ExternalLabels map[string]string  `yaml:"external_labels,omitempty"`
+	ScrapeInterval *promutils.Duration `yaml:"scrape_interval,omitempty"`
+	ScrapeTimeout  *promutils.Duration `yaml:"scrape_timeout,omitempty"`
+	ExternalLabels map[string]string   `yaml:"external_labels,omitempty"`
 }
 
 // ScrapeConfig represents essential parts for `scrape_config` section of Prometheus config.
@@ -134,8 +147,8 @@ type GlobalConfig struct {
 // See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
 type ScrapeConfig struct {
 	JobName              string                      `yaml:"job_name"`
-	ScrapeInterval       promutils.Duration          `yaml:"scrape_interval,omitempty"`
-	ScrapeTimeout        promutils.Duration          `yaml:"scrape_timeout,omitempty"`
+	ScrapeInterval       *promutils.Duration         `yaml:"scrape_interval,omitempty"`
+	ScrapeTimeout        *promutils.Duration         `yaml:"scrape_timeout,omitempty"`
 	MetricsPath          string                      `yaml:"metrics_path,omitempty"`
 	HonorLabels          bool                        `yaml:"honor_labels,omitempty"`
 	HonorTimestamps      *bool                       `yaml:"honor_timestamps,omitempty"`
@@ -168,8 +181,8 @@ type ScrapeConfig struct {
 	DisableCompression  bool                       `yaml:"disable_compression,omitempty"`
 	DisableKeepAlive    bool                       `yaml:"disable_keepalive,omitempty"`
 	StreamParse         bool                       `yaml:"stream_parse,omitempty"`
-	ScrapeAlignInterval promutils.Duration         `yaml:"scrape_align_interval,omitempty"`
-	ScrapeOffset        promutils.Duration         `yaml:"scrape_offset,omitempty"`
+	ScrapeAlignInterval *promutils.Duration        `yaml:"scrape_align_interval,omitempty"`
+	ScrapeOffset        *promutils.Duration        `yaml:"scrape_offset,omitempty"`
 	SeriesLimit         int                        `yaml:"series_limit,omitempty"`
 	ProxyClientConfig   promauth.ProxyClientConfig `yaml:",inline"`
 
@@ -309,7 +322,7 @@ func IsDryRun() bool {
 }
 
 func (cfg *Config) parseData(data []byte, path string) ([]byte, error) {
-	if err := unmarshalMaybeStrict(data, cfg); err != nil {
+	if err := cfg.unmarshal(data, *strictParse); err != nil {
 		return nil, fmt.Errorf("cannot unmarshal data: %w", err)
 	}
 	absPath, err := filepath.Abs(path)
@@ -349,19 +362,6 @@ func (cfg *Config) parseData(data []byte, path string) ([]byte, error) {
 	return dataNew, nil
 }
 
-func unmarshalMaybeStrict(data []byte, dst interface{}) error {
-	data = envtemplate.Replace(data)
-	var err error
-	if *strictParse {
-		if err = yaml.UnmarshalStrict(data, dst); err != nil {
-			err = fmt.Errorf("%w; pass -promscrape.config.strictParse=false command-line flag for ignoring unknown fields in yaml config", err)
-		}
-	} else {
-		err = yaml.Unmarshal(data, dst)
-	}
-	return err
-}
-
 func getSWSByJob(sws []*ScrapeWork) map[string][]*ScrapeWork {
 	m := make(map[string][]*ScrapeWork)
 	for _, sw := range sws {
diff --git a/lib/promutils/duration.go b/lib/promutils/duration.go
index 382b6f53c..edfe0dd1d 100644
--- a/lib/promutils/duration.go
+++ b/lib/promutils/duration.go
@@ -12,8 +12,8 @@ type Duration struct {
 }
 
 // NewDuration returns Duration for given d.
-func NewDuration(d time.Duration) Duration {
-	return Duration{
+func NewDuration(d time.Duration) *Duration {
+	return &Duration{
 		d: d,
 	}
 }
@@ -38,7 +38,10 @@ func (pd *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
 }
 
 // Duration returns duration for pd.
-func (pd Duration) Duration() time.Duration {
+func (pd *Duration) Duration() time.Duration {
+	if pd == nil {
+		return 0
+	}
 	return pd.d
 }
 


Test patch:
diff --git a/lib/httpserver/httpserver_test.go b/lib/httpserver/httpserver_test.go
new file mode 100644
index 000000000..b87ec49f1
--- /dev/null
+++ b/lib/httpserver/httpserver_test.go
@@ -0,0 +1,78 @@
+package httpserver
+
+import (
+	"reflect"
+	"testing"
+)
+
+func Test_validateCipherSuites(t *testing.T) {
+	type args struct {
+		definedCipherSuites []string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    []uint16
+		wantErr bool
+	}{
+		{
+			name: "empty cipher suites",
+			args: args{definedCipherSuites: []string{}},
+			want: nil,
+		},
+		{
+			name:    "got wrong string",
+			args:    args{definedCipherSuites: []string{"word"}},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "got wrong number",
+			args:    args{definedCipherSuites: []string{"123"}},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "got correct string cipher suite",
+			args:    args{definedCipherSuites: []string{"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"}},
+			want:    []uint16{0x2f, 0x35},
+			wantErr: false,
+		},
+		{
+			name:    "got correct string with different cases (upper and lower) cipher suite",
+			args:    args{definedCipherSuites: []string{"tls_rsa_with_aes_128_cbc_sha", "TLS_RSA_WITH_AES_256_CBC_SHA"}},
+			want:    []uint16{0x2f, 0x35},
+			wantErr: false,
+		},
+		{
+			name:    "got correct number cipher suite",
+			args:    args{definedCipherSuites: []string{"0x2f", "0x35"}},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "got insecure number cipher suite",
+			args:    args{definedCipherSuites: []string{"0x0005", "0x000a"}},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name:    "got insecure string cipher suite",
+			args:    args{definedCipherSuites: []string{"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA"}},
+			want:    nil,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := collectCipherSuites(tt.args.definedCipherSuites)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("collectCipherSuites() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("validateCipherSuites() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/lib/promscrape/config_test.go b/lib/promscrape/config_test.go
index 59a0b3826..bc2147a4f 100644
--- a/lib/promscrape/config_test.go
+++ b/lib/promscrape/config_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"reflect"
 	"strconv"
+	"strings"
 	"testing"
 	"time"
 
@@ -13,6 +14,63 @@ import (
 	"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
 )
 
+func TestScrapeConfigUnmarshalMarshal(t *testing.T) {
+	f := func(data string) {
+		t.Helper()
+		var cfg Config
+		data = strings.TrimSpace(data)
+		if err := cfg.unmarshal([]byte(data), true); err != nil {
+			t.Fatalf("parse error: %s\ndata:\n%s", err, data)
+		}
+		resultData := string(cfg.marshal())
+		result := strings.TrimSpace(resultData)
+		if result != data {
+			t.Fatalf("unexpected marshaled config:\ngot\n%s\nwant\n%s", result, data)
+		}
+	}
+	f(`
+global:
+  scrape_interval: 10s
+`)
+	f(`
+scrape_config_files:
+- foo
+- bar
+`)
+	f(`
+scrape_configs:
+- job_name: foo
+  scrape_timeout: 1.5s
+  static_configs:
+  - targets:
+    - foo
+    - bar
+    labels:
+      foo: bar
+`)
+	f(`
+scrape_configs:
+- job_name: foo
+  honor_labels: true
+  honor_timestamps: false
+  scheme: https
+  params:
+    foo:
+    - x
+  authorization:
+    type: foobar
+  relabel_configs:
+  - source_labels: [abc]
+  static_configs:
+  - targets:
+    - foo
+  relabel_debug: true
+  scrape_align_interval: 1h30m0s
+  proxy_bearer_token_file: file.txt
+`)
+
+}
+
 func TestNeedSkipScrapeWork(t *testing.T) {
 	f := func(key string, membersCount, replicationFactor, memberNum int, needSkipExpected bool) {
 		t.Helper()