/**
 * Copyright 2020 Opstrace, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ import { KubeConfig } from "@kubernetes/client-node" ; import { strict as assert } from "assert" ; import * as yaml from "js-yaml" ; import { ResourceCollection , Service , ConfigMap , Deployment , Namespace , V1ServicemonitorResource , withPodAntiAffinityRequired , StatefulSet , PersistentVolumeClaim , ServiceAccount } from "@opstrace/kubernetes" ; import { State } from "../../reducer" ; import { roundDownToOdd , select , getBucketName , min , roundDown , deepMerge } from "@opstrace/utils" ; import { getControllerConfig , getControllerLokiConfigOverrides , getNodeCount } from "../../helpers" ; import { DockerImages , getImagePullSecrets } from "@opstrace/controller-config" ; import { log } from "@opstrace/utils" ; export function LokiResources ( state , kubeConfig , namespace ) { const collection = new ResourceCollection ( ) ; const { infrastructureName , target , region , gcp , logRetentionDays } = getControllerConfig ( state ) ; const dataBucketName = getBucketName ( { clusterName : infrastructureName , suffix : "loki" } ) ; const configBucketName = getBucketName ( { clusterName : infrastructureName , suffix : "loki-config" } ) ; const clusterName = infrastructureName ; const deploymentConfig = { ruler : { resources : { } , replicas : select ( getNodeCount ( state ) , [ { "<=" : 5 , choose : 2 } , { "<=" : Infinity , choose : 3 } ] ) } , ingester : { resources : { } , replicas : select ( getNodeCount ( state ) , [ { "<=" : 4 , choose : 3 } , { "<=" : 6 , choose : 5 } , { "<=" : 8 , choose : 7 } , { "<=" : 10 , choose : 9 } , { "<=" : Infinity , choose : roundDownToOdd ( getNodeCount ( state ) / 2 ) } ] ) } , distributor : { resources : { } , replicas : select ( getNodeCount ( state ) , [ { "<=" : 4 , choose : 3 } , { "<=" : 6 , choose : 5 } , { "<=" : 8 , choose : 7 } , { "<=" : 10 , choose : 9 } , { "<=" : Infinity , choose : roundDownToOdd ( getNodeCount ( state ) / 2 ) } ] ) } , querier : { resources : { } , replicas : select ( getNodeCount ( state ) , [ { "<=" : 6 , choose : 3 } , { "<=" : 9 , choose : 5 } , { "<=" : Infinity , choose : roundDownToOdd ( getNodeCount ( state ) / 2 ) } ] ) } , queryFrontend : { replicas : 2 } , memcachedResults : { replicas : select ( getNodeCount ( state ) , [ { "<=" : 4 , choose : 2 } , { "<=" : 9 , choose : 3 } , { "<=" : Infinity , choose : min ( 4 , roundDown ( getNodeCount ( state ) / 3 ) ) } ] ) , resources : { } } , env : [ ] } ; const memberlist_bind_port = 7947 ; const grpc_server_max_msg_size = 41943040 ; const lokiDefaultConfig = { server : { http_listen_port : 1080 , grpc_server_max_recv_msg_size : grpc_server_max_msg_size , grpc_server_max_send_msg_size : grpc_server_max_msg_size , grpc_server_max_concurrent_streams : 1000 , grpc_server_ping_without_stream_allowed : true , grpc_server_min_time_between_pings : "10s" } , auth_enabled : true , chunk_store_config : { chunk_cache_config : { background : { writeback_buffer : 5000 } , memcached_client : { consistent_hash : true , host : `memcached.${ namespace }.svc.cluster.local` , service : "memcached-client" } } } , limits_config : { enforce_metric_name : false , ingestion_rate_mb : 500 , ingestion_burst_size_mb : 2000 , max_entries_limit_per_query : 60000 , reject_old_samples : true , reject_old_samples_max_age : "168h" , max_query_length : `${ ( logRetentionDays + 1 ) * 24 }h` , max_query_parallelism : 16 , max_streams_per_user : 0 , max_global_streams_per_user : 10000 , ingestion_rate_strategy : "global" , max_cache_freshness_per_query : "10m" } , frontend : { compress_responses : true , tail_proxy_url : `http://querier.${ namespace }.svc.cluster.local:1080` } , frontend_worker : { frontend_address : `query-frontend.${ namespace }.svc.cluster.local:9095` , grpc_client_config : { backoff_on_ratelimits : true , max_send_msg_size : grpc_server_max_msg_size } } , query_range : { split_queries_by_interval : "30m" , align_queries_with_step : true , cache_results : true , max_retries : 5 , results_cache : { cache : { memcached_client : { timeout : "500ms" , consistent_hash : true , service : "memcached-client" , host : `memcached-results.${ namespace }.svc.cluster.local` , update_interval : "1m" , max_idle_conns : 16 } } } } , querier : { query_ingesters_within : "4h" } , ingester : { chunk_retain_period : "5m" , chunk_target_size : 2000000 , max_chunk_age : "2h" , chunk_idle_period : "2h" , max_returned_stream_errors : 25 , max_transfer_retries : 0 , wal : { enabled : true , dir : "/loki/wal" , replay_memory_ceiling : "1GB" } , lifecycler : { join_after : "30s" , observe_period : "30s" , num_tokens : 512 , ring : { kvstore : { store : "memberlist" } } } } , distributor : { ring : { kvstore : { store : "memberlist" } } } , memberlist : { abort_if_cluster_join_fails : true , bind_port : memberlist_bind_port , join_members : [ `loki-gossip-ring.${ namespace }.svc.cluster.local:${ memberlist_bind_port }` ] , max_join_backoff : "1m" , max_join_retries : 20 , min_join_backoff : "1s" } , schema_config : { configs : [ { from : "2020-08-01" , index : { prefix : `${ clusterName }_loki_index_` , period : "24h" } , object_store : target === "gcp" ? "gcs" : "s3" , schema : "v11" , store : "boltdb-shipper" } ] } , compactor : { working_directory : "/loki/compactor" , shared_store : target === "gcp" ? "gcs" : "s3" } , ruler : { enable_api : true , alertmanager_url : `http://alertmanager.cortex.svc.cluster.local/alertmanager/` , enable_sharding : true , enable_alertmanager_v2 : true , rule_path : "/tmp/rules" , ring : { kvstore : { store : "memberlist" } } , storage : { type : target === "gcp" ? "gcs" : "s3" , gcs : { bucket_name : configBucketName } , s3 : { s3 : `s3://${ region }/${ configBucketName }` } } } , storage_config : { boltdb_shipper : { active_index_directory : "/loki/index" , shared_store : target === "gcp" ? "gcs" : "s3" , cache_location : "/loki/boltdb-cache" } , gcs : { bucket_name : dataBucketName } , aws : { s3 : `s3://${ region }/${ dataBucketName }` } , index_queries_cache_config : { memcached_client : { consistent_hash : true , host : `memcached-index-queries.${ namespace }.svc.cluster.local` , service : "memcached-client" } } } } ; const lokiConfigOverrides = getControllerLokiConfigOverrides ( state ) ; log . debug ( `loki config overrides: ${ JSON . stringify ( lokiConfigOverrides , null , 2 ) }` ) ; const lokiConfig = deepMerge ( lokiDefaultConfig , lokiConfigOverrides ) ; collection . add ( new Namespace ( { apiVersion : "v1" , kind : "Namespace" , metadata : { name : namespace } } , kubeConfig ) ) ; let annotations = { } ; let serviceAccountName = undefined ; if ( target === "gcp" ) { assert ( gcp ?. lokiServiceAccount ) ; annotations = { "iam.gke.io/gcp-service-account" : gcp . lokiServiceAccount } ; serviceAccountName = "loki" ; } collection . add ( new ServiceAccount ( { apiVersion : "v1" , kind : "ServiceAccount" , metadata : { name : "loki" , namespace , annotations : annotations } } , kubeConfig ) ) ; collection . add ( new ConfigMap ( { apiVersion : "v1" , data : { "config.yaml" : yaml . safeDump ( lokiConfig ) } , kind : "ConfigMap" , metadata : { name : "loki" , namespace } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { job : `${ namespace }.querier` , name : "querier" } , name : "querier" , namespace } , spec : { ports : [ { name : "querier-http-metrics" , port : 1080 , targetPort : 1080 as any } , { name : "querier-grpc" , port : 9095 , targetPort : 9095 as any } ] , selector : { name : "querier" } } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { name : "loki-gossip-ring" } , name : "loki-gossip-ring" , namespace } , spec : { clusterIP : "None" , ports : [ { name : "loki-gossip-ring" , port : memberlist_bind_port , targetPort : memberlist_bind_port as any } ] , selector : { memberlist : "loki-gossip-ring" } } } , kubeConfig ) ) ; collection . add ( new V1ServicemonitorResource ( { apiVersion : "monitoring.coreos.com/v1" , kind : "ServiceMonitor" , metadata : { labels : { name : "querier" , tenant : "system" } , name : "querier" , namespace } , spec : { endpoints : [ { interval : "30s" , port : "querier-http-metrics" , path : "/metrics" } ] , jobLabel : "job" , namespaceSelector : { matchNames : [ namespace ] } , selector : { matchLabels : { name : "querier" } } } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { job : `${ namespace }.distributor` , name : "distributor" } , name : "distributor" , namespace } , spec : { ports : [ { name : "distributor-http-metrics" , port : 1080 , targetPort : 1080 as any } , { name : "distributor-grpc" , port : 9095 , targetPort : 9095 as any } ] , selector : { name : "distributor" } } } , kubeConfig ) ) ; collection . add ( new V1ServicemonitorResource ( { apiVersion : "monitoring.coreos.com/v1" , kind : "ServiceMonitor" , metadata : { labels : { name : "distributor" , tenant : "system" } , name : "distributor" , namespace } , spec : { endpoints : [ { interval : "30s" , port : "distributor-http-metrics" , path : "/metrics" } ] , jobLabel : "job" , namespaceSelector : { matchNames : [ namespace ] } , selector : { matchLabels : { name : "distributor" } } } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { job : `${ namespace }.ingester` , name : "ingester" } , name : "ingester" , namespace } , spec : { ports : [ { name : "ingester-http-metrics" , port : 1080 , targetPort : 1080 as any } , { name : "ingester-grpc" , port : 9095 , targetPort : 9095 as any } ] , selector : { name : "ingester" } } } , kubeConfig ) ) ; collection . add ( new V1ServicemonitorResource ( { apiVersion : "monitoring.coreos.com/v1" , kind : "ServiceMonitor" , metadata : { labels : { name : "ingester" , tenant : "system" } , name : "ingester" , namespace } , spec : { endpoints : [ { interval : "30s" , port : "ingester-http-metrics" , path : "/metrics" } ] , jobLabel : "job" , namespaceSelector : { matchNames : [ namespace ] } , selector : { matchLabels : { name : "ingester" } } } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { job : `${ namespace }.compactor` , name : "compactor" } , name : "compactor" , namespace } , spec : { ports : [ { name : "compactor-http-metrics" , port : 1080 , targetPort : 1080 as any } , { name : "compactor-grpc" , port : 9095 , targetPort : 9095 as any } ] , selector : { name : "compactor" } } } , kubeConfig ) ) ; collection . add ( new V1ServicemonitorResource ( { apiVersion : "monitoring.coreos.com/v1" , kind : "ServiceMonitor" , metadata : { labels : { name : "compactor" , tenant : "system" } , name : "compactor" , namespace } , spec : { endpoints : [ { interval : "30s" , port : "compactor-http-metrics" , path : "/metrics" } ] , jobLabel : "job" , namespaceSelector : { matchNames : [ namespace ] } , selector : { matchLabels : { name : "compactor" } } } } , kubeConfig ) ) ; collection . add ( new Deployment ( { apiVersion : "apps/v1" , kind : "Deployment" , metadata : { name : "distributor" , namespace } , spec : { minReadySeconds : 10 , replicas : deploymentConfig . distributor . replicas , revisionHistoryLimit : 10 , selector : { matchLabels : { name : "distributor" , app : "loki-gossip-ring" } } , template : { metadata : { labels : { name : "distributor" , app : "loki-gossip-ring" } } , spec : { imagePullSecrets : getImagePullSecrets ( ) , affinity : withPodAntiAffinityRequired ( { name : "distributor" } ) , containers : [ { args : [ "-config.file=/etc/loki/config.yaml" , "-target=distributor" ] , env : deploymentConfig . env , image : DockerImages . loki , imagePullPolicy : "IfNotPresent" , name : "distributor" , ports : [ { containerPort : 1080 , name : "http-metrics" } , { containerPort : 9095 , name : "grpc" } ] , readinessProbe : { httpGet : { path : "/ready" , port : 1080 as any , scheme : "HTTP" } , initialDelaySeconds : 45 , timeoutSeconds : 1 , periodSeconds : 10 , successThreshold : 1 , failureThreshold : 3 } , resources : deploymentConfig . distributor . resources , volumeMounts : [ { mountPath : "/etc/loki" , name : "loki" } ] } ] , volumes : [ { configMap : { name : "loki" } , name : "loki" } ] } } } } , kubeConfig ) ) ; collection . add ( new StatefulSet ( { apiVersion : "apps/v1" , kind : "StatefulSet" , metadata : { name : "ingester" , namespace } , spec : { serviceName : "ingester" , replicas : deploymentConfig . ingester . replicas , revisionHistoryLimit : 10 , podManagementPolicy : "OrderedReady" , selector : { matchLabels : { name : "ingester" , memberlist : "loki-gossip-ring" } } , template : { metadata : { labels : { name : "ingester" , memberlist : "loki-gossip-ring" } } , spec : { imagePullSecrets : getImagePullSecrets ( ) , affinity : withPodAntiAffinityRequired ( { name : "ingester" } ) , containers : [ { args : [ "-config.file=/etc/loki/config.yaml" , "-target=ingester" ] , env : deploymentConfig . env , image : DockerImages . loki , imagePullPolicy : "IfNotPresent" , name : "ingester" , ports : [ { containerPort : 1080 , name : "http-metrics" } , { containerPort : 9095 , name : "grpc" } ] , readinessProbe : { httpGet : { path : "/ready" , port : 1080 as any , scheme : "HTTP" } , initialDelaySeconds : 45 , timeoutSeconds : 1 , periodSeconds : 10 , successThreshold : 1 , failureThreshold : 3 } , resources : deploymentConfig . ingester . resources , volumeMounts : [ { mountPath : "/etc/loki" , name : "loki" } , { name : "datadir" , mountPath : "/loki" } ] } ] , serviceAccountName : serviceAccountName , securityContext : { fsGroup : 2000 } , terminationGracePeriodSeconds : 2400 , volumes : [ { configMap : { name : "loki" } , name : "loki" } , { name : "datadir" , persistentVolumeClaim : { claimName : "datadir" } } ] } } , volumeClaimTemplates : [ { metadata : { name : "datadir" } , spec : { storageClassName : "pd-ssd" , accessModes : [ "ReadWriteOnce" ] , resources : { requests : { storage : "10Gi" } } } } ] } } , kubeConfig ) ) ; collection . add ( new PersistentVolumeClaim ( { apiVersion : "v1" , kind : "PersistentVolumeClaim" , metadata : { name : "compactor-datadir-storage-claim" , namespace } , spec : { storageClassName : "pd-ssd" , accessModes : [ "ReadWriteOnce" ] , resources : { requests : { storage : "10Gi" } } } } , kubeConfig ) ) ; collection . add ( new Deployment ( { apiVersion : "apps/v1" , kind : "Deployment" , metadata : { name : "compactor" , namespace } , spec : { revisionHistoryLimit : 10 , replicas : 1 , strategy : { type : "Recreate" } , selector : { matchLabels : { name : "compactor" } } , template : { metadata : { labels : { name : "compactor" } } , spec : { imagePullSecrets : getImagePullSecrets ( ) , affinity : withPodAntiAffinityRequired ( { name : "compactor" } ) , containers : [ { args : [ "-config.file=/etc/loki/config.yaml" , "-target=compactor" ] , env : deploymentConfig . env , image : DockerImages . loki , imagePullPolicy : "IfNotPresent" , name : "compactor" , ports : [ { containerPort : 1080 , name : "http-metrics" } , { containerPort : 9095 , name : "grpc" } ] , readinessProbe : { httpGet : { path : "/ready" , port : 1080 as any , scheme : "HTTP" } , initialDelaySeconds : 45 , timeoutSeconds : 1 , periodSeconds : 10 , successThreshold : 1 , failureThreshold : 3 } , livenessProbe : { httpGet : { path : "/ready" , port : 1080 as any , scheme : "HTTP" } , initialDelaySeconds : 45 , timeoutSeconds : 1 , periodSeconds : 10 , successThreshold : 1 , failureThreshold : 3 } , resources : deploymentConfig . querier . resources , volumeMounts : [ { mountPath : "/etc/loki" , name : "loki" } , { name : "datadir" , mountPath : "/loki" } ] } ] , securityContext : { fsGroup : 2000 } , serviceAccountName : serviceAccountName , volumes : [ { configMap : { name : "loki" } , name : "loki" } , { name : "datadir" , persistentVolumeClaim : { claimName : "compactor-datadir-storage-claim" } } ] } } } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { job : `${ namespace }.ruler` , name : "ruler" } , name : "ruler" , namespace } , spec : { ports : [ { name : "ruler-http-metrics" , port : 1080 , targetPort : 1080 as any } , { name : "ruler-grpc" , port : 9095 , targetPort : 9095 as any } ] , selector : { name : "ruler" } } } , kubeConfig ) ) ; collection . add ( new StatefulSet ( { apiVersion : "apps/v1" , kind : "StatefulSet" , metadata : { name : "ruler" , namespace } , spec : { serviceName : "ruler" , replicas : deploymentConfig . ruler . replicas , revisionHistoryLimit : 10 , podManagementPolicy : "Parallel" , selector : { matchLabels : { name : "ruler" , memberlist : "loki-gossip-ring" } } , template : { metadata : { labels : { name : "ruler" , memberlist : "loki-gossip-ring" } } , spec : { imagePullSecrets : getImagePullSecrets ( ) , affinity : withPodAntiAffinityRequired ( { name : "ruler" } ) , containers : [ { args : [ "-config.file=/etc/loki/config.yaml" , "-target=ruler" ] , env : deploymentConfig . env , image : DockerImages . loki , imagePullPolicy : "IfNotPresent" , name : "ruler" , ports : [ { containerPort : 1080 , name : "http-metrics" } , { containerPort : 9095 , name : "grpc" } ] , readinessProbe : { httpGet : { path : "/ready" , port : 1080 as any , scheme : "HTTP" } , initialDelaySeconds : 45 , timeoutSeconds : 1 , periodSeconds : 10 , successThreshold : 1 , failureThreshold : 3 } , resources : deploymentConfig . ruler . resources , volumeMounts : [ { mountPath : "/etc/loki" , name : "loki" } , { name : "ruler-data" , mountPath : "/data" } ] } ] , serviceAccountName : serviceAccountName , securityContext : { fsGroup : 2000 } , volumes : [ { configMap : { name : "loki" } , name : "loki" } , { name : "ruler-data" , persistentVolumeClaim : { claimName : "ruler-data" } } ] } } , volumeClaimTemplates : [ { metadata : { name : "ruler-data" } , spec : { storageClassName : "pd-ssd" , accessModes : [ "ReadWriteOnce" ] , resources : { requests : { storage : "10Gi" } } } } ] } } , kubeConfig ) ) ; collection . add ( new StatefulSet ( { apiVersion : "apps/v1" , kind : "StatefulSet" , metadata : { name : "querier" , namespace } , spec : { serviceName : "querier" , replicas : deploymentConfig . querier . replicas , revisionHistoryLimit : 10 , podManagementPolicy : "OrderedReady" , selector : { matchLabels : { name : "querier" , memberlist : "loki-gossip-ring" } } , template : { metadata : { labels : { name : "querier" , memberlist : "loki-gossip-ring" } } , spec : { imagePullSecrets : getImagePullSecrets ( ) , affinity : withPodAntiAffinityRequired ( { name : "querier" } ) , containers : [ { args : [ "-config.file=/etc/loki/config.yaml" , "-target=querier" ] , env : deploymentConfig . env , image : DockerImages . loki , imagePullPolicy : "IfNotPresent" , name : "querier" , ports : [ { containerPort : 1080 , name : "http-metrics" } , { containerPort : 9095 , name : "grpc" } ] , readinessProbe : { httpGet : { path : "/ready" , port : 1080 as any , scheme : "HTTP" } , initialDelaySeconds : 45 , timeoutSeconds : 1 , periodSeconds : 10 , successThreshold : 1 , failureThreshold : 3 } , resources : deploymentConfig . querier . resources , volumeMounts : [ { mountPath : "/etc/loki" , name : "loki" } , { name : "cachedir" , mountPath : "/loki" } ] } ] , serviceAccountName : serviceAccountName , securityContext : { fsGroup : 2000 } , volumes : [ { configMap : { name : "loki" } , name : "loki" } , { name : "cachedir" , persistentVolumeClaim : { claimName : "cachedir" } } ] } } , volumeClaimTemplates : [ { metadata : { name : "cachedir" } , spec : { storageClassName : "pd-ssd" , accessModes : [ "ReadWriteOnce" ] , resources : { requests : { storage : "10Gi" } } } } ] } } , kubeConfig ) ) ; collection . add ( new Deployment ( { apiVersion : "apps/v1" , kind : "Deployment" , metadata : { name : "query-frontend" , namespace } , spec : { replicas : deploymentConfig . queryFrontend . replicas , selector : { matchLabels : { name : "query-frontend" } } , template : { metadata : { labels : { name : "query-frontend" } } , spec : { affinity : withPodAntiAffinityRequired ( { name : "query-frontend" } ) , containers : [ { name : "query-frontend" , image : DockerImages . loki , imagePullPolicy : "IfNotPresent" , args : [ "-target=query-frontend" , "-config.file=/etc/loki/config.yaml" ] , env : deploymentConfig . env , ports : [ { containerPort : 9095 , name : "grpc" } , { containerPort : 1080 , name : "http" } ] , volumeMounts : [ { mountPath : "/etc/loki" , name : "loki" } ] } ] , volumes : [ { configMap : { name : "loki" } , name : "loki" } ] } } } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { job : `${ namespace }.query-frontend` , name : "query-frontend" } , name : "query-frontend" , namespace } , spec : { clusterIP : "None" , ports : [ { port : 9095 , name : "grcp" } , { port : 1080 , name : "http" } ] , selector : { name : "query-frontend" } } } , kubeConfig ) ) ; collection . add ( new V1ServicemonitorResource ( { apiVersion : "monitoring.coreos.com/v1" , kind : "ServiceMonitor" , metadata : { labels : { name : "query-frontend" , tenant : "system" } , name : "query-frontend" , namespace } , spec : { endpoints : [ { interval : "30s" , port : "http" , path : "/metrics" } ] , jobLabel : "job" , namespaceSelector : { matchNames : [ namespace ] } , selector : { matchLabels : { name : "query-frontend" } } } } , kubeConfig ) ) ; collection . add ( new V1ServicemonitorResource ( { apiVersion : "monitoring.coreos.com/v1" , kind : "ServiceMonitor" , metadata : { labels : { name : "memcached-results" , tenant : "system" } , name : "memcached-results" , namespace } , spec : { endpoints : [ { interval : "30s" , port : "exporter-http-metrics" , path : "/metrics" } ] , jobLabel : "name" , namespaceSelector : { matchNames : [ namespace ] } , selector : { matchLabels : { name : "memcached-results" } } } } , kubeConfig ) ) ; collection . add ( new V1ServicemonitorResource ( { apiVersion : "monitoring.coreos.com/v1" , kind : "ServiceMonitor" , metadata : { labels : { name : "memcached" , tenant : "system" } , name : "memcached-index-writes" , namespace } , spec : { endpoints : [ { interval : "30s" , port : "exporter-http-metrics" , path : "/metrics" } ] , jobLabel : "name" , namespaceSelector : { matchNames : [ namespace ] } , selector : { matchLabels : { name : "memcached-index-writes" } } } } , kubeConfig ) ) ; collection . add ( new StatefulSet ( { apiVersion : "apps/v1" , kind : "StatefulSet" , metadata : { name : "memcached-results" , namespace } , spec : { replicas : deploymentConfig . memcachedResults . replicas , serviceName : "memcached-results" , podManagementPolicy : "Parallel" , selector : { matchLabels : { name : "memcached-results" } } , template : { metadata : { labels : { name : "memcached-results" } } , spec : { affinity : withPodAntiAffinityRequired ( { name : "memcached-results" } ) , containers : [ { args : [ "-m 4096" , "-I 2m" , "-c 1024" , "-v" ] , image : DockerImages . memcached , imagePullPolicy : "IfNotPresent" , name : "memcached" , ports : [ { containerPort : 11211 , name : "client" } ] , resources : deploymentConfig . memcachedResults . resources } , { args : [ "--memcached.address=localhost:11211" , "--web.listen-address=0.0.0.0:9150" ] , image : DockerImages . memcachedExporter , imagePullPolicy : "IfNotPresent" , name : "exporter" , ports : [ { containerPort : 9150 , name : "http-metrics" } ] } ] } } , updateStrategy : { type : "RollingUpdate" } , volumeClaimTemplates : [ ] } } , kubeConfig ) ) ; collection . add ( new Service ( { apiVersion : "v1" , kind : "Service" , metadata : { labels : { name : "memcached-results" } , name : "memcached-results" , namespace } , spec : { clusterIP : "None" , ports : [ { name : "memcached-client" , port : 11211 , targetPort : 11211 as any } , { name : "exporter-http-metrics" , port : 9150 , targetPort : 9150 as any } ] , selector : { name : "memcached-results" } } } , kubeConfig ) ) ; return collection ; }