From 8a72b794ed0509cabe1a3d126943c403304dc12a Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Tue, 12 May 2020 09:46:33 +0200 Subject: [PATCH 01/81] Introduce Beat CRD First pass at Beats in ECK, features include: - native support for Filebeat (daemonset only) - initial support for other Beats (deployment only) - RBAC for autodiscovery setup (can be disabled via operator flag) - providing Beat status - basic webhook/non-webhook validation - rollout for config/version changes - labelling for all created resources - support for pod template/config overriding - basic apm/events/logging --- cmd/manager/main.go | 15 + config/crds/all-crds.yaml | 146 + .../crds/bases/beat.k8s.elastic.co_beats.yaml | 12208 ++++++++++++++++ config/crds/bases/kustomization.yaml | 1 + config/crds/patches/beat-patches.yaml | 17 + config/crds/patches/kustomization.yaml | 7 + config/e2e/operator.yaml | 13 + .../all-in-one/cluster_role.template.yaml | 15 + config/operator/all-in-one/rbac.yaml | 6 + config/samples/beat/filebeat_es_kibana.yaml | 51 + config/webhook/manifests.yaml | 18 + docs/reference/api-docs.asciidoc | 115 + pkg/apis/beat/v1beta1/beat_types.go | 143 + pkg/apis/beat/v1beta1/doc.go | 11 + pkg/apis/beat/v1beta1/groupversion_info.go | 21 + pkg/apis/beat/v1beta1/webhook.go | 121 + .../beat/v1beta1/zz_generated.deepcopy.go | 161 + .../association/controller/beat_es.go | 42 + pkg/controller/beat/beat_controller.go | 279 + pkg/controller/beat/labels.go | 13 + pkg/controller/beat/name.go | 26 + pkg/controller/common/beat/autodiscovery.go | 145 + pkg/controller/common/beat/common.go | 75 + pkg/controller/common/beat/config.go | 47 + pkg/controller/common/beat/filebeat/config.go | 128 + .../common/beat/filebeat/filebeat.go | 208 + pkg/controller/common/beat/health/health.go | 42 + pkg/controller/common/beat/name.go | 13 + .../common/beat/otherbeat/otherbeat.go | 221 + pkg/controller/common/container/container.go | 1 + pkg/controller/common/daemonset/reconcile.go | 63 + .../common/defaults/pod_template.go | 31 + pkg/controller/common/operator/flags.go | 35 +- pkg/controller/common/scheme/scheme.go | 5 + pkg/controller/common/version/version.go | 1 + pkg/controller/common/volume/host.go | 63 + pkg/controller/common/volume/secret.go | 35 +- pkg/utils/pointer/numeric.go | 3 + test/e2e/samples_test.go | 4 + 39 files changed, 14523 insertions(+), 26 deletions(-) create mode 100644 config/crds/bases/beat.k8s.elastic.co_beats.yaml create mode 100644 config/crds/patches/beat-patches.yaml create mode 100644 config/samples/beat/filebeat_es_kibana.yaml create mode 100644 pkg/apis/beat/v1beta1/beat_types.go create mode 100644 pkg/apis/beat/v1beta1/doc.go create mode 100644 pkg/apis/beat/v1beta1/groupversion_info.go create mode 100644 pkg/apis/beat/v1beta1/webhook.go create mode 100644 pkg/apis/beat/v1beta1/zz_generated.deepcopy.go create mode 100644 pkg/controller/association/controller/beat_es.go create mode 100644 pkg/controller/beat/beat_controller.go create mode 100644 pkg/controller/beat/labels.go create mode 100644 pkg/controller/beat/name.go create mode 100644 pkg/controller/common/beat/autodiscovery.go create mode 100644 pkg/controller/common/beat/common.go create mode 100644 pkg/controller/common/beat/config.go create mode 100644 pkg/controller/common/beat/filebeat/config.go create mode 100644 pkg/controller/common/beat/filebeat/filebeat.go create mode 100644 pkg/controller/common/beat/health/health.go create mode 100644 pkg/controller/common/beat/name.go create mode 100644 pkg/controller/common/beat/otherbeat/otherbeat.go create mode 100644 pkg/controller/common/daemonset/reconcile.go create mode 100644 pkg/controller/common/volume/host.go diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 1f985025c8..afb3cd3c19 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -24,6 +24,8 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/controller/apmserver" "github.com/elastic/cloud-on-k8s/pkg/controller/association" associationctl "github.com/elastic/cloud-on-k8s/pkg/controller/association/controller" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat" + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/certificates" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/operator" @@ -330,6 +332,10 @@ func execute() { accessReviewer = rbac.NewPermissiveAccessReviewer() } + if viper.GetBool(operator.DisableAutodiscoveryRBACSetup) { + commonbeat.DisableAutodiscoveryRBACSetup() + } + if err = apmserver.Add(mgr, params); err != nil { log.Error(err, "unable to create controller", "controller", "ApmServer") os.Exit(1) @@ -346,6 +352,10 @@ func execute() { log.Error(err, "unable to create controller", "controller", "EnterpriseSearch") os.Exit(1) } + if err = beat.Add(mgr, params); err != nil { + log.Error(err, "unable to create controller", "controller", "Beat") + os.Exit(1) + } if err = associationctl.AddApmES(mgr, accessReviewer, params); err != nil { log.Error(err, "unable to create controller", "controller", "apm-es-association") os.Exit(1) @@ -358,6 +368,11 @@ func execute() { log.Error(err, "unable to create controller", "controller", "ent-es-association") os.Exit(1) } + if err = associationctl.AddBeatES(mgr, accessReviewer, params); err != nil { + log.Error(err, "unable to create controller", "controller", "beat-es-association") + os.Exit(1) + } + if err = remoteca.Add(mgr, accessReviewer, params); err != nil { log.Error(err, "unable to create controller", "controller", "RemoteClusterCertificateAuthorites") os.Exit(1) diff --git a/config/crds/all-crds.yaml b/config/crds/all-crds.yaml index 188253db2c..4aeb9b4e3b 100644 --- a/config/crds/all-crds.yaml +++ b/config/crds/all-crds.yaml @@ -438,6 +438,152 @@ status: --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + creationTimestamp: null + name: beats.beat.k8s.elastic.co +spec: + additionalPrinterColumns: + - JSONPath: .status.health + name: health + type: string + - JSONPath: .status.availableNodes + description: Available nodes + name: available + type: integer + - JSONPath: .status.expectedNodes + description: Expected nodes + name: expected + type: integer + - JSONPath: .spec.type + description: Beat type + name: type + type: string + - JSONPath: .spec.version + description: Beat version + name: version + type: string + - JSONPath: .metadata.creationTimestamp + name: age + type: date + group: beat.k8s.elastic.co + names: + categories: + - elastic + kind: Beat + listKind: BeatList + plural: beats + shortNames: + - beat + singular: beat + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: Beat is the Schema for the beats API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BeatSpec defines the desired state of a Beat. + properties: + config: + description: Config holds the Beat configuration. If provided, it will + override the default configuration. + type: object + daemonSet: + description: 'DaemonSet field allows to: 1. indicate whether the Beat + should be deployed as DaemonSet or Deployment, if both are absent, + a default for the Type is used 2. Provide spec for the DaemonSet + At most one of DaemonSet and Deployment can be used.' + properties: {} + type: object + deployment: + description: 'Deployment field allows to: 1. indicate whether the Beat + should be deployed as DaemonSet or Deployment, if both are absent, + a default for the Type is used 2. Provide spec for the Deployment + At most one of DaemonSet and Deployment can be used.' + properties: + replicas: + format: int32 + type: integer + type: object + elasticsearchRef: + description: ElasticsearchRef is a reference to an Elasticsearch cluster + running in the same Kubernetes cluster. + properties: + name: + description: Name of the Kubernetes object. + type: string + namespace: + description: Namespace of the Kubernetes object. If empty, defaults + to the current namespace. + type: string + required: + - name + type: object + image: + description: Image is the Beat Docker image to deploy. Version has to + match the Beat in the image. + type: string + serviceAccountName: + description: ServiceAccountName is used to check access from the current + resource to Elasticsearch resource in a different namespace. Can only + be used if ECK is enforcing RBAC on references. + type: string + type: + description: Type is the type of the Beat to deploy. Any string can + be used, but well-known types will be recognized and will allow to + provide sane default configurations. + type: string + version: + description: Version of the Beat. + type: string + required: + - type + - version + type: object + status: + description: BeatStatus defines the observed state of Beat + properties: + associationStatus: + description: AssociationStatus is the status of an association resource. + type: string + availableNodes: + format: int32 + type: integer + expectedNodes: + format: int32 + type: integer + health: + type: string + type: object + version: v1beta1 + versions: + - name: v1beta1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.2.5 diff --git a/config/crds/bases/beat.k8s.elastic.co_beats.yaml b/config/crds/bases/beat.k8s.elastic.co_beats.yaml new file mode 100644 index 0000000000..787684a105 --- /dev/null +++ b/config/crds/bases/beat.k8s.elastic.co_beats.yaml @@ -0,0 +1,12208 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.5 + creationTimestamp: null + name: beats.beat.k8s.elastic.co +spec: + additionalPrinterColumns: + - JSONPath: .status.health + name: health + type: string + - JSONPath: .status.availableNodes + description: Available nodes + name: available + type: integer + - JSONPath: .status.expectedNodes + description: Expected nodes + name: expected + type: integer + - JSONPath: .spec.type + description: Beat type + name: type + type: string + - JSONPath: .spec.version + description: Beat version + name: version + type: string + - JSONPath: .metadata.creationTimestamp + name: age + type: date + group: beat.k8s.elastic.co + names: + categories: + - elastic + kind: Beat + listKind: BeatList + plural: beats + shortNames: + - beat + singular: beat + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: Beat is the Schema for the beats API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BeatSpec defines the desired state of a Beat. + properties: + config: + description: Config holds the Beat configuration. If provided, it will + override the default configuration. + type: object + daemonSet: + description: 'DaemonSet field allows to: 1. indicate whether the Beat + should be deployed as DaemonSet or Deployment, if both are absent, + a default for the Type is used 2. Provide spec for the DaemonSet + At most one of DaemonSet and Deployment can be used.' + properties: + podTemplate: + description: PodTemplateSpec describes the data a pod should have + when created from a template + properties: + metadata: + description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' + type: object + spec: + description: 'Specification of the desired behavior of the pod. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' + properties: + activeDeadlineSeconds: + description: Optional duration in seconds the pod may be + active on the node relative to StartTime before the system + will actively try to mark it failed and kill associated + containers. Value must be a positive integer. + format: int64 + type: integer + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules + for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. + The node that is most preferred is the one with + the greatest sum of weights, i.e. for each node + that meets all of the scheduling requirements + (resource request, requiredDuringScheduling affinity + expressions, etc.), compute a sum by iterating + through the elements of this field and adding + "weight" to the sum if the node matches the corresponding + matchExpressions; the node(s) with the highest + sum are the most preferred. + items: + description: An empty preferred scheduling term + matches all objects with implicit weight 0 (i.e. + it's a no-op). A null preferred scheduling term + matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, + the pod will not be scheduled onto the node. If + the affinity requirements specified by this field + cease to be met at some point during pod execution + (e.g. due to an update), the system may or may + not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: A null or empty node selector + term matches no objects. The requirements + of them are ANDed. The TopologySelectorTerm + type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules + (e.g. co-locate this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. + The node that is most preferred is the one with + the greatest sum of weights, i.e. for each node + that meets all of the scheduling requirements + (resource request, requiredDuringScheduling affinity + expressions, etc.), compute a sum by iterating + through the elements of this field and adding + "weight" to the sum if the node has pods which + matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched + WeightedPodAffinityTerm fields are added per-node + to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set + of resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a + set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values + array must be non-empty. If + the operator is Exists or + DoesNotExist, the values array + must be empty. This array + is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which + namespaces the labelSelector applies + to (matches against); null or empty + list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, + the pod will not be scheduled onto the node. If + the affinity requirements specified by this field + cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may + or may not try to eventually evict the pod from + its node. When there are multiple elements, the + lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) + with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose value + of the label with key topologyKey matches + that of any node on which any of the selected + pods is running. Empty topologyKey is not + allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. + The node that is most preferred is the one with + the greatest sum of weights, i.e. for each node + that meets all of the scheduling requirements + (resource request, requiredDuringScheduling anti-affinity + expressions, etc.), compute a sum by iterating + through the elements of this field and adding + "weight" to the sum if the node has pods which + matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched + WeightedPodAffinityTerm fields are added per-node + to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set + of resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a + set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values + array must be non-empty. If + the operator is Exists or + DoesNotExist, the values array + must be empty. This array + is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which + namespaces the labelSelector applies + to (matches against); null or empty + list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, + the pod will not be scheduled onto the node. If + the anti-affinity requirements specified by this + field cease to be met at some point during pod + execution (e.g. due to a pod label update), the + system may or may not try to eventually evict + the pod from its node. When there are multiple + elements, the lists of nodes corresponding to + each podAffinityTerm are intersected, i.e. all + terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) + with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose value + of the label with key topologyKey matches + that of any node on which any of the selected + pods is running. Empty topologyKey is not + allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether + a service account token should be automatically mounted. + type: boolean + containers: + description: List of containers belonging to the pod. Containers + cannot currently be added or removed. There must be at + least one container in a Pod. Cannot be updated. + items: + description: A single application container that you want + to run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the + reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double + $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable + exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within + a shell. The docker image''s ENTRYPOINT is used + if this is not provided. Variable references $(VAR_NAME) + are expanded using the container''s environment. + If a variable cannot be resolved, the reference + in the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be + updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set + in the container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) + are expanded using the previous defined environment + variables in the container and any service + environment variables. If a variable cannot + be resolved, the reference in the input string + will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, + regardless of whether the variable exists + or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: + supports metadata.name, metadata.namespace, + metadata.labels, metadata.annotations, + spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, + requests.cpu, requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment + variables in the container. The keys defined within + a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container + is starting. When a key exists in multiple sources, + the value associated with the last source will take + precedence. Values defined by an Env with a duplicate + key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config + management to default or override container images + in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, + IfNotPresent. Defaults to Always if :latest tag + is specified, or IfNotPresent otherwise. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should + take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately + after a container is created. If the handler + fails, the container is terminated and restarted + according to its restart policy. Other management + of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before + a container is terminated due to an API request + or management event such as liveness/startup + probe failure, preemption, resource contention, + etc. The handler is not called if the container + crashes or exits. The reason for termination + is passed to the handler. The Pod''s termination + grace period countdown begins before the PreStop + hooked is executed. Regardless of the outcome + of the handler, the container will eventually + terminate within the Pod''s termination grace + period. Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a + DNS_LABEL. Each container in a pod must have a unique + name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional + information about the network connections a container + uses, but is primarily informational. Not specifying + a port here DOES NOT prevent that port from being + exposed. Any port which is listening on the default + "0.0.0.0" address inside a container will be accessible + from the network. Cannot be updated. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: Number of port to expose on the + pod's IP address. This must be a valid port + number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: Number of port to expose on the + host. If specified, this must be a valid port + number, 0 < x < 65536. If HostNetwork is specified, + this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port + in a pod must have a unique name. Name for + the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, + TCP, or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service + readiness. Container will be removed from service + endpoints if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is + omitted for a container, it defaults to Limits + if that is explicitly specified, otherwise to + an implementation-defined value. More info: + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: 'Security options the pod should run + with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls + whether a process can gain more privileges than + its parent process. This bool directly controls + if the no_new_privs flag will be set on the + container process. AllowPrivilegeEscalation + is true always when the container is: 1) run + as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when + running containers. Defaults to the default + set of capabilities granted by the container + runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. + Processes in privileged containers are essentially + equivalent to root on the host. Defaults to + false. + type: boolean + procMount: + description: procMount denotes the type of proc + mount to use for the containers. The default + is DefaultProcMount which uses the container + runtime defaults for readonly paths and masked + paths. This requires the ProcMountType feature + flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of + the container process. Uses runtime default + if unset. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must + run as a non-root user. If true, the Kubelet + will validate the image at runtime to ensure + that it does not run as UID 0 (root) and fail + to start the container if it does. If unset + or false, no such validation will be performed. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of + the container process. Defaults to user specified + in image metadata if unspecified. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied + to the container. If unspecified, the container + runtime will allocate a random SELinux context + for each container. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + windowsOptions: + description: The Windows specific settings applied + to all containers. If unspecified, the options + from the PodSecurityContext will be used. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the + GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential + spec named by the GMSACredentialSpecName + field. This field is alpha-level and is + only honored by servers that enable the + WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + This field is alpha-level and is only honored + by servers that enable the WindowsGMSA feature + flag. + type: string + runAsUserName: + description: The UserName in Windows to run + the entrypoint of the container process. + Defaults to the user specified in image + metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. This + field is beta-level and may be disabled + with the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod + has successfully initialized. If specified, no other + probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, + just as if the livenessProbe failed. This can be + used to provide different probe parameters at the + beginning of a Pod''s lifecycle, when it might take + a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. + This is an alpha feature enabled by the StartupProbe + feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate + a buffer for stdin in the container runtime. If + this is not set, reads from stdin in the container + will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should + close the stdin channel after it has been opened + by a single attach. When stdin is true the stdin + stream will remain open across multiple attach sessions. + If stdinOnce is set to true, stdin is opened on + container start, is empty until the first client + attaches to stdin, and then remains open and accepts + data until the client disconnects, at which time + stdin is closed and remains closed until the container + is restarted. If this flag is false, a container + processes that reads from stdin will never receive + an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to + which the container''s termination message will + be written is mounted into the container''s filesystem. + Message written is intended to be brief final status, + such as an assertion failure message. Will be truncated + by the node if greater than 4096 bytes. The total + message length across all containers will be limited + to 12kb. Defaults to /dev/termination-log. Cannot + be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message + should be populated. File will use the contents + of terminationMessagePath to populate the container + status message on both success and failure. FallbackToLogsOnError + will use the last chunk of container log output + if the termination message file is empty and the + container exited with an error. The log output is + limited to 2048 bytes or 80 lines, whichever is + smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate + a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. This is a beta feature. + items: + description: volumeDevice describes a mapping of + a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's + filesystem. Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: Path within the container at which + the volume should be mounted. Must not contain + ':'. + type: string + mountPropagation: + description: mountPropagation determines how + mounts are propagated from the host to container + and the other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults + to false. + type: boolean + subPath: + description: Path within the volume from which + the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume + from which the container's volume should be + mounted. Behaves similarly to SubPath but + environment variable references $(VAR_NAME) + are expanded using the container's environment. + Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not + specified, the container runtime's default will + be used, which might be configured in the container + image. Cannot be updated. + type: string + required: + - name + type: object + type: array + dnsConfig: + description: Specifies the DNS parameters of a pod. Parameters + specified here will be merged to the generated DNS configuration + based on DNSPolicy. + properties: + nameservers: + description: A list of DNS name server IP addresses. + This will be appended to the base nameservers generated + from DNSPolicy. Duplicated nameservers will be removed. + items: + type: string + type: array + options: + description: A list of DNS resolver options. This will + be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options + given in Options will override those that appear in + the base DNSPolicy. + items: + description: PodDNSConfigOption defines DNS resolver + options of a pod. + properties: + name: + description: Required. + type: string + value: + type: string + type: object + type: array + searches: + description: A list of DNS search domains for host-name + lookup. This will be appended to the base search paths + generated from DNSPolicy. Duplicated search paths + will be removed. + items: + type: string + type: array + type: object + dnsPolicy: + description: Set DNS policy for the pod. Defaults to "ClusterFirst". + Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', + 'Default' or 'None'. DNS parameters given in DNSConfig + will be merged with the policy selected with DNSPolicy. + To have DNS options set along with hostNetwork, you have + to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. + type: string + enableServiceLinks: + description: 'EnableServiceLinks indicates whether information + about services should be injected into pod''s environment + variables, matching the syntax of Docker links. Optional: + Defaults to true.' + type: boolean + ephemeralContainers: + description: List of ephemeral containers run in this pod. + Ephemeral containers may be run in an existing pod to + perform user-initiated actions such as debugging. This + list cannot be specified when creating a pod, and it cannot + be modified by updating the pod spec. In order to add + an ephemeral container to an existing pod, use the pod's + ephemeralcontainers subresource. This field is alpha-level + and is only honored by servers that enable the EphemeralContainers + feature. + items: + description: An EphemeralContainer is a container that + may be added temporarily to an existing pod for user-initiated + activities such as debugging. Ephemeral containers have + no resource or scheduling guarantees, and they will + not be restarted when they exit or when a pod is removed + or restarted. If an ephemeral container causes a pod + to exceed its resource allocation, the pod may be evicted. + Ephemeral containers may not be added by directly updating + the pod spec. They must be added via the pod's ephemeralcontainers + subresource, and they will appear in the pod spec once + added. This is an alpha feature enabled by the EphemeralContainers + feature flag. + properties: + args: + description: 'Arguments to the entrypoint. The docker + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the + reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double + $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable + exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within + a shell. The docker image''s ENTRYPOINT is used + if this is not provided. Variable references $(VAR_NAME) + are expanded using the container''s environment. + If a variable cannot be resolved, the reference + in the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be + updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set + in the container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) + are expanded using the previous defined environment + variables in the container and any service + environment variables. If a variable cannot + be resolved, the reference in the input string + will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, + regardless of whether the variable exists + or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: + supports metadata.name, metadata.namespace, + metadata.labels, metadata.annotations, + spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, + requests.cpu, requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment + variables in the container. The keys defined within + a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container + is starting. When a key exists in multiple sources, + the value associated with the last source will take + precedence. Values defined by an Env with a duplicate + key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, + IfNotPresent. Defaults to Always if :latest tag + is specified, or IfNotPresent otherwise. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Lifecycle is not allowed for ephemeral + containers. + properties: + postStart: + description: 'PostStart is called immediately + after a container is created. If the handler + fails, the container is terminated and restarted + according to its restart policy. Other management + of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before + a container is terminated due to an API request + or management event such as liveness/startup + probe failure, preemption, resource contention, + etc. The handler is not called if the container + crashes or exits. The reason for termination + is passed to the handler. The Pod''s termination + grace period countdown begins before the PreStop + hooked is executed. Regardless of the outcome + of the handler, the container will eventually + terminate within the Pod''s termination grace + period. Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: Probes are not allowed for ephemeral + containers. + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the ephemeral container specified + as a DNS_LABEL. This name must be unique among all + containers, init containers and ephemeral containers. + type: string + ports: + description: Ports are not allowed for ephemeral containers. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: Number of port to expose on the + pod's IP address. This must be a valid port + number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: Number of port to expose on the + host. If specified, this must be a valid port + number, 0 < x < 65536. If HostNetwork is specified, + this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port + in a pod must have a unique name. Name for + the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, + TCP, or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + description: Probes are not allowed for ephemeral + containers. + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: Resources are not allowed for ephemeral + containers. Ephemeral containers use spare resources + already allocated to the pod. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is + omitted for a container, it defaults to Limits + if that is explicitly specified, otherwise to + an implementation-defined value. More info: + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: SecurityContext is not allowed for ephemeral + containers. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls + whether a process can gain more privileges than + its parent process. This bool directly controls + if the no_new_privs flag will be set on the + container process. AllowPrivilegeEscalation + is true always when the container is: 1) run + as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when + running containers. Defaults to the default + set of capabilities granted by the container + runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. + Processes in privileged containers are essentially + equivalent to root on the host. Defaults to + false. + type: boolean + procMount: + description: procMount denotes the type of proc + mount to use for the containers. The default + is DefaultProcMount which uses the container + runtime defaults for readonly paths and masked + paths. This requires the ProcMountType feature + flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of + the container process. Uses runtime default + if unset. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must + run as a non-root user. If true, the Kubelet + will validate the image at runtime to ensure + that it does not run as UID 0 (root) and fail + to start the container if it does. If unset + or false, no such validation will be performed. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of + the container process. Defaults to user specified + in image metadata if unspecified. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied + to the container. If unspecified, the container + runtime will allocate a random SELinux context + for each container. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + windowsOptions: + description: The Windows specific settings applied + to all containers. If unspecified, the options + from the PodSecurityContext will be used. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the + GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential + spec named by the GMSACredentialSpecName + field. This field is alpha-level and is + only honored by servers that enable the + WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + This field is alpha-level and is only honored + by servers that enable the WindowsGMSA feature + flag. + type: string + runAsUserName: + description: The UserName in Windows to run + the entrypoint of the container process. + Defaults to the user specified in image + metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. This + field is beta-level and may be disabled + with the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + startupProbe: + description: Probes are not allowed for ephemeral + containers. + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate + a buffer for stdin in the container runtime. If + this is not set, reads from stdin in the container + will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should + close the stdin channel after it has been opened + by a single attach. When stdin is true the stdin + stream will remain open across multiple attach sessions. + If stdinOnce is set to true, stdin is opened on + container start, is empty until the first client + attaches to stdin, and then remains open and accepts + data until the client disconnects, at which time + stdin is closed and remains closed until the container + is restarted. If this flag is false, a container + processes that reads from stdin will never receive + an EOF. Default is false + type: boolean + targetContainerName: + description: If set, the name of the container from + PodSpec that this ephemeral container targets. The + ephemeral container will be run in the namespaces + (IPC, PID, etc) of this container. If not set then + the ephemeral container is run in whatever namespaces + are shared for the pod. Note that the container + runtime must support this feature. + type: string + terminationMessagePath: + description: 'Optional: Path at which the file to + which the container''s termination message will + be written is mounted into the container''s filesystem. + Message written is intended to be brief final status, + such as an assertion failure message. Will be truncated + by the node if greater than 4096 bytes. The total + message length across all containers will be limited + to 12kb. Defaults to /dev/termination-log. Cannot + be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message + should be populated. File will use the contents + of terminationMessagePath to populate the container + status message on both success and failure. FallbackToLogsOnError + will use the last chunk of container log output + if the termination message file is empty and the + container exited with an error. The log output is + limited to 2048 bytes or 80 lines, whichever is + smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate + a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. This is a beta feature. + items: + description: volumeDevice describes a mapping of + a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's + filesystem. Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: Path within the container at which + the volume should be mounted. Must not contain + ':'. + type: string + mountPropagation: + description: mountPropagation determines how + mounts are propagated from the host to container + and the other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults + to false. + type: boolean + subPath: + description: Path within the volume from which + the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume + from which the container's volume should be + mounted. Behaves similarly to SubPath but + environment variable references $(VAR_NAME) + are expanded using the container's environment. + Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not + specified, the container runtime's default will + be used, which might be configured in the container + image. Cannot be updated. + type: string + required: + - name + type: object + type: array + hostAliases: + description: HostAliases is an optional list of hosts and + IPs that will be injected into the pod's hosts file if + specified. This is only valid for non-hostNetwork pods. + items: + description: HostAlias holds the mapping between IP and + hostnames that will be injected as an entry in the pod's + hosts file. + properties: + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + ip: + description: IP address of the host file entry. + type: string + type: object + type: array + hostIPC: + description: 'Use the host''s ipc namespace. Optional: Default + to false.' + type: boolean + hostNetwork: + description: Host networking requested for this pod. Use + the host's network namespace. If this option is set, the + ports that will be used must be specified. Default to + false. + type: boolean + hostPID: + description: 'Use the host''s pid namespace. Optional: Default + to false.' + type: boolean + hostname: + description: Specifies the hostname of the Pod If not specified, + the pod's hostname will be set to a system-defined value. + type: string + imagePullSecrets: + description: 'ImagePullSecrets is an optional list of references + to secrets in the same namespace to use for pulling any + of the images used by this PodSpec. If specified, these + secrets will be passed to individual puller implementations + for them to use. For example, in the case of docker, only + DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' + items: + description: LocalObjectReference contains enough information + to let you locate the referenced object inside the same + namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + type: array + initContainers: + description: 'List of initialization containers belonging + to the pod. Init containers are executed in order prior + to containers being started. If any init container fails, + the pod is considered to have failed and is handled according + to its restartPolicy. The name for an init container or + normal container must be unique among all containers. + Init containers may not have Lifecycle actions, Readiness + probes, Liveness probes, or Startup probes. The resourceRequirements + of an init container are taken into account during scheduling + by finding the highest request/limit for each resource + type, and then using the max of of that value or the sum + of the normal containers. Limits are applied to init containers + in a similar fashion. Init containers cannot currently + be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/' + items: + description: A single application container that you want + to run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the + reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double + $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable + exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within + a shell. The docker image''s ENTRYPOINT is used + if this is not provided. Variable references $(VAR_NAME) + are expanded using the container''s environment. + If a variable cannot be resolved, the reference + in the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be + updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set + in the container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) + are expanded using the previous defined environment + variables in the container and any service + environment variables. If a variable cannot + be resolved, the reference in the input string + will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, + regardless of whether the variable exists + or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: + supports metadata.name, metadata.namespace, + metadata.labels, metadata.annotations, + spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, + requests.cpu, requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment + variables in the container. The keys defined within + a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container + is starting. When a key exists in multiple sources, + the value associated with the last source will take + precedence. Values defined by an Env with a duplicate + key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config + management to default or override container images + in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, + IfNotPresent. Defaults to Always if :latest tag + is specified, or IfNotPresent otherwise. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should + take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately + after a container is created. If the handler + fails, the container is terminated and restarted + according to its restart policy. Other management + of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before + a container is terminated due to an API request + or management event such as liveness/startup + probe failure, preemption, resource contention, + etc. The handler is not called if the container + crashes or exits. The reason for termination + is passed to the handler. The Pod''s termination + grace period countdown begins before the PreStop + hooked is executed. Regardless of the outcome + of the handler, the container will eventually + terminate within the Pod''s termination grace + period. Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a + DNS_LABEL. Each container in a pod must have a unique + name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional + information about the network connections a container + uses, but is primarily informational. Not specifying + a port here DOES NOT prevent that port from being + exposed. Any port which is listening on the default + "0.0.0.0" address inside a container will be accessible + from the network. Cannot be updated. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: Number of port to expose on the + pod's IP address. This must be a valid port + number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: Number of port to expose on the + host. If specified, this must be a valid port + number, 0 < x < 65536. If HostNetwork is specified, + this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port + in a pod must have a unique name. Name for + the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, + TCP, or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service + readiness. Container will be removed from service + endpoints if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is + omitted for a container, it defaults to Limits + if that is explicitly specified, otherwise to + an implementation-defined value. More info: + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: 'Security options the pod should run + with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls + whether a process can gain more privileges than + its parent process. This bool directly controls + if the no_new_privs flag will be set on the + container process. AllowPrivilegeEscalation + is true always when the container is: 1) run + as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when + running containers. Defaults to the default + set of capabilities granted by the container + runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. + Processes in privileged containers are essentially + equivalent to root on the host. Defaults to + false. + type: boolean + procMount: + description: procMount denotes the type of proc + mount to use for the containers. The default + is DefaultProcMount which uses the container + runtime defaults for readonly paths and masked + paths. This requires the ProcMountType feature + flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of + the container process. Uses runtime default + if unset. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must + run as a non-root user. If true, the Kubelet + will validate the image at runtime to ensure + that it does not run as UID 0 (root) and fail + to start the container if it does. If unset + or false, no such validation will be performed. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of + the container process. Defaults to user specified + in image metadata if unspecified. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied + to the container. If unspecified, the container + runtime will allocate a random SELinux context + for each container. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + windowsOptions: + description: The Windows specific settings applied + to all containers. If unspecified, the options + from the PodSecurityContext will be used. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the + GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential + spec named by the GMSACredentialSpecName + field. This field is alpha-level and is + only honored by servers that enable the + WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + This field is alpha-level and is only honored + by servers that enable the WindowsGMSA feature + flag. + type: string + runAsUserName: + description: The UserName in Windows to run + the entrypoint of the container process. + Defaults to the user specified in image + metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. This + field is beta-level and may be disabled + with the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod + has successfully initialized. If specified, no other + probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, + just as if the livenessProbe failed. This can be + used to provide different probe parameters at the + beginning of a Pod''s lifecycle, when it might take + a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. + This is an alpha feature enabled by the StartupProbe + feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate + a buffer for stdin in the container runtime. If + this is not set, reads from stdin in the container + will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should + close the stdin channel after it has been opened + by a single attach. When stdin is true the stdin + stream will remain open across multiple attach sessions. + If stdinOnce is set to true, stdin is opened on + container start, is empty until the first client + attaches to stdin, and then remains open and accepts + data until the client disconnects, at which time + stdin is closed and remains closed until the container + is restarted. If this flag is false, a container + processes that reads from stdin will never receive + an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to + which the container''s termination message will + be written is mounted into the container''s filesystem. + Message written is intended to be brief final status, + such as an assertion failure message. Will be truncated + by the node if greater than 4096 bytes. The total + message length across all containers will be limited + to 12kb. Defaults to /dev/termination-log. Cannot + be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message + should be populated. File will use the contents + of terminationMessagePath to populate the container + status message on both success and failure. FallbackToLogsOnError + will use the last chunk of container log output + if the termination message file is empty and the + container exited with an error. The log output is + limited to 2048 bytes or 80 lines, whichever is + smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate + a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. This is a beta feature. + items: + description: volumeDevice describes a mapping of + a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's + filesystem. Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: Path within the container at which + the volume should be mounted. Must not contain + ':'. + type: string + mountPropagation: + description: mountPropagation determines how + mounts are propagated from the host to container + and the other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults + to false. + type: boolean + subPath: + description: Path within the volume from which + the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume + from which the container's volume should be + mounted. Behaves similarly to SubPath but + environment variable references $(VAR_NAME) + are expanded using the container's environment. + Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not + specified, the container runtime's default will + be used, which might be configured in the container + image. Cannot be updated. + type: string + required: + - name + type: object + type: array + nodeName: + description: NodeName is a request to schedule this pod + onto a specific node. If it is non-empty, the scheduler + simply schedules this pod onto that node, assuming that + it fits resource requirements. + type: string + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true + for the pod to fit on a node. Selector which must match + a node''s labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Overhead represents the resource overhead + associated with running a pod for a given RuntimeClass. + This field will be autopopulated at admission time by + the RuntimeClass admission controller. If the RuntimeClass + admission controller is enabled, overhead must not be + set in Pod create requests. The RuntimeClass admission + controller will reject Pod create requests which have + the overhead already set. If RuntimeClass is configured + and selected in the PodSpec, Overhead will be set to the + value defined in the corresponding RuntimeClass, otherwise + it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md + This field is alpha-level as of Kubernetes v1.16, and + is only honored by servers that enable the PodOverhead + feature.' + type: object + preemptionPolicy: + description: PreemptionPolicy is the Policy for preempting + pods with lower priority. One of Never, PreemptLowerPriority. + Defaults to PreemptLowerPriority if unset. This field + is alpha-level and is only honored by servers that enable + the NonPreemptingPriority feature. + type: string + priority: + description: The priority value. Various system components + use this field to find the priority of the pod. When Priority + Admission Controller is enabled, it prevents users from + setting this field. The admission controller populates + this field from PriorityClassName. The higher the value, + the higher the priority. + format: int32 + type: integer + priorityClassName: + description: If specified, indicates the pod's priority. + "system-node-critical" and "system-cluster-critical" are + two special keywords which indicate the highest priorities + with the former being the highest priority. Any other + name must be defined by creating a PriorityClass object + with that name. If not specified, the pod priority will + be default or zero if there is no default. + type: string + readinessGates: + description: 'If specified, all readiness gates will be + evaluated for pod readiness. A pod is ready when all its + containers are ready AND all conditions specified in the + readiness gates have status equal to "True" More info: + https://git.k8s.io/enhancements/keps/sig-network/0007-pod-ready%2B%2B.md' + items: + description: PodReadinessGate contains the reference to + a pod condition + properties: + conditionType: + description: ConditionType refers to a condition in + the pod's condition list with matching type. + type: string + required: + - conditionType + type: object + type: array + restartPolicy: + description: 'Restart policy for all containers within the + pod. One of Always, OnFailure, Never. Default to Always. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy' + type: string + runtimeClassName: + description: 'RuntimeClassName refers to a RuntimeClass + object in the node.k8s.io group, which should be used + to run this pod. If no RuntimeClass resource matches + the named class, the pod will not be run. If unset or + empty, the "legacy" RuntimeClass will be used, which is + an implicit class with an empty definition that uses the + default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/runtime-class.md + This is a beta feature as of Kubernetes v1.14.' + type: string + schedulerName: + description: If specified, the pod will be dispatched by + specified scheduler. If not specified, the pod will be + dispatched by default scheduler. + type: string + securityContext: + description: 'SecurityContext holds pod-level security attributes + and common container settings. Optional: Defaults to empty. See + type description for default values of each field.' + properties: + fsGroup: + description: "A special supplemental group that applies + to all containers in a pod. Some volume types allow + the Kubelet to change the ownership of that volume + to be owned by the pod: \n 1. The owning GID will + be the FSGroup 2. The setgid bit is set (new files + created in the volume will be owned by FSGroup) 3. + The permission bits are OR'd with rw-rw---- \n If + unset, the Kubelet will not modify the ownership and + permissions of any volume." + format: int64 + type: integer + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all + containers. If unspecified, the container runtime + will allocate a random SELinux context for each container. May + also be set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's + primary GID. If unspecified, no groups will be added + to any container. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls + used for the pod. Pods with unsupported sysctls (by + the container runtime) might fail to launch. + items: + description: Sysctl defines a kernel parameter to + be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options within + a container's SecurityContext will be used. If set + in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. This + field is alpha-level and is only honored by servers + that enable the WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. This field + is alpha-level and is only honored by servers + that enable the WindowsGMSA feature flag. + type: string + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + This field is beta-level and may be disabled with + the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + serviceAccount: + description: 'DeprecatedServiceAccount is a depreciated + alias for ServiceAccountName. Deprecated: Use serviceAccountName + instead.' + type: string + serviceAccountName: + description: 'ServiceAccountName is the name of the ServiceAccount + to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/' + type: string + shareProcessNamespace: + description: 'Share a single process namespace between all + of the containers in a pod. When this is set containers + will be able to view and signal processes from other containers + in the same pod, and the first process in each container + will not be assigned PID 1. HostPID and ShareProcessNamespace + cannot both be set. Optional: Default to false.' + type: boolean + subdomain: + description: If specified, the fully qualified Pod hostname + will be "...svc.". If not specified, the pod will not have a domainname + at all. + type: string + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates + delete immediately. If this value is nil, the default + grace period will be used instead. The grace period is + the duration in seconds after the processes running in + the pod are sent a termination signal and the time when + the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for + your process. Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple + using the matching operator . + properties: + effect: + description: Effect indicates the taint effect to + match. Empty means match all taint effects. When + specified, allowed values are NoSchedule, PreferNoSchedule + and NoExecute. + type: string + key: + description: Key is the taint key that the toleration + applies to. Empty means match all taint keys. If + the key is empty, operator must be Exists; this + combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship + to the value. Valid operators are Exists and Equal. + Defaults to Equal. Exists is equivalent to wildcard + for value, so that a pod can tolerate all taints + of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period + of time the toleration (which must be of effect + NoExecute, otherwise this field is ignored) tolerates + the taint. By default, it is not set, which means + tolerate the taint forever (do not evict). Zero + and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration + matches to. If the operator is Exists, the value + should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: TopologySpreadConstraints describes how a group + of pods ought to spread across topology domains. Scheduler + will schedule pods in a way which abides by the constraints. + This field is alpha-level and is only honored by clusters + that enables the EvenPodsSpread feature. All topologySpreadConstraints + are ANDed. + items: + description: TopologySpreadConstraint specifies how to + spread matching pods among the given topology. + properties: + labelSelector: + description: LabelSelector is used to find matching + pods. Pods that match this label selector are counted + to determine the number of pods in their corresponding + topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement is + a selector that contains values, a key, and + an operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If + the operator is Exists or DoesNotExist, + the values array must be empty. This array + is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + maxSkew: + description: 'MaxSkew describes the degree to which + pods may be unevenly distributed. It''s the maximum + permitted difference between the number of matching + pods in any two topology domains of a given topology + type. For example, in a 3-zone cluster, MaxSkew + is set to 1, and pods with the same labelSelector + spread as 1/1/0: | zone1 | zone2 | zone3 | | P | P | | + - if MaxSkew is 1, incoming pod can only be scheduled + to zone3 to become 1/1/1; scheduling it onto zone1(zone2) + would make the ActualSkew(2-0) on zone1(zone2) violate + MaxSkew(1). - if MaxSkew is 2, incoming pod can + be scheduled onto any zone. It''s a required field. + Default value is 1 and 0 is not allowed.' + format: int32 + type: integer + topologyKey: + description: TopologyKey is the key of node labels. + Nodes that have a label with this key and identical + values are considered to be in the same topology. + We consider each as a "bucket", and + try to put balanced number of pods into each bucket. + It's a required field. + type: string + whenUnsatisfiable: + description: 'WhenUnsatisfiable indicates how to deal + with a pod if it doesn''t satisfy the spread constraint. + - DoNotSchedule (default) tells the scheduler not + to schedule it - ScheduleAnyway tells the scheduler + to still schedule it It''s considered as "Unsatisfiable" + if and only if placing incoming pod on any topology + violates "MaxSkew". For example, in a 3-zone cluster, + MaxSkew is set to 1, and pods with the same labelSelector + spread as 3/1/1: | zone1 | zone2 | zone3 | | P P + P | P | P | If WhenUnsatisfiable is set + to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) + on zone2(zone3) satisfies MaxSkew(1). In other words, + the cluster can still be imbalanced, but scheduler + won''t make it *more* imbalanced. It''s a required + field.' + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + description: 'List of volumes that can be mounted by containers + belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' + items: + description: Volume represents a named volume in a pod + that may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS + Disk resource that is attached to a kubelet''s host + machine and then exposed to the pod. More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + partition: + description: 'The partition in the volume that + you want to mount. If omitted, the default is + to mount by volume name. Examples: For volume + /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda + is "0" (or you can leave the property empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set + the ReadOnly property in VolumeMounts to "true". + If omitted, the default is "false". More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk + resource in AWS (Amazon EBS volume). More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk + mount on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, + Read Write.' + type: string + diskName: + description: The Name of the data disk in the + blob storage + type: string + diskURI: + description: The URI the data disk in the blob + storage + type: string + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + kind: + description: 'Expected values Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure + managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains + Azure Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on + the host that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection + of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, + rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path + to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference + to the authentication secret for User, default + is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user + name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: + https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object + containing parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume + in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that + should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a value between 0 + and 0777. Defaults to 0644. Directories within + the path are not affected by this setting. This + might be in conflict with other options that + affect the file mode, like fsGroup, and the + result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair + in the Data field of the referenced ConfigMap + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the ConfigMap, the volume + setup will error unless it is marked optional. + Paths must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the file + to map the key to. May not be an absolute + path. May not contain the path element + '..'. May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or + its keys must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + storage that is handled by an external CSI driver + (Alpha feature). + properties: + driver: + description: Driver is the name of the CSI driver + that handles this volume. Consult with your + admin for the correct name as registered in + the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", + "xfs", "ntfs". If not provided, the empty value + is passed to the associated CSI driver which + will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference + to the secret object containing sensitive information + to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if + no secret is required. If the secret object + contains more than one secret, all secret references + are passed. + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration + for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific + properties that are passed to the CSI driver. + Consult your driver's documentation for supported + values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a value between 0 + and 0777. Defaults to 0644. Directories within + the path are not affected by this setting. This + might be in conflict with other options that + affect the file mode, like fsGroup, and the + result can be other mode bits set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' + path. Must be utf-8 encoded. The first + item of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu + and requests.memory) are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should + back this directory. The default is "" which + means to use the node''s default medium. Must + be an empty string (default) or Memory. More + info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'Total amount of local storage required + for this EmptyDir volume. The size limit is + also applicable for memory medium. The maximum + usage on memory medium EmptyDir would be the + minimum value between the SizeLimit specified + here and the sum of memory limits of all containers + in a pod. The default is nil which means that + the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + fc: + description: FC represents a Fibre Channel resource + that is attached to a kubelet's host machine and + then exposed to the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. TODO: + how do we prevent errors in the filesystem from + compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names + (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume + resource that is provisioned/attached using an exec + based plugin. + properties: + driver: + description: Driver is the name of the driver + to use for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". The default + filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options + if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference + to the secret object containing sensitive information + to pass to the plugin scripts. This may be empty + if no secret object is specified. If the secret + object contains more than one secret, all secrets + are passed to the plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the + Flocker control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata + -> name on the dataset for Flocker should be + considered as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique + identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + partition: + description: 'The partition in the volume that + you want to mount. If omitted, the default is + to mount by volume name. Examples: For volume + /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda + is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in + GCE. Used to identify the disk in GCE. More + info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository + at a particular revision. DEPRECATED: GitRepo is + deprecated. To provision a container with a git + repo, mount an EmptyDir into an InitContainer that + clones the repo using git, then mount the EmptyDir + into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain + or start with '..'. If '.' is supplied, the + volume directory will be the git repository. Otherwise, + if specified, the volume will contain the git + repository in the subdirectory with the given + name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount + on the host that shares a pod''s lifetime. More + info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name + that details Glusterfs topology. More info: + https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs + volume to be mounted with read-only permissions. + Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file + or directory on the host machine that is directly + exposed to the container. This is generally used + for system agents or other privileged things that + are allowed to see the host machine. Most containers + will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use + host directory mounts and who can/can not mount + host directories as read/write.' + properties: + path: + description: 'Path of the directory on the host. + If the path is a symlink, it will follow the + link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults + to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource + that is attached to a kubelet''s host machine and + then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP + authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP + authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, + new iSCSI interface : will be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an + iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal + is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 + and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and + initiator authentication + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is + either an IP or ip_addr:port if the port is + other than default (typically TCP ports 860 + and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL + and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host + that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS + server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS + export to be mounted with read-only permissions. + Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address + of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same + namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this + volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in + VolumeMounts. Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets + host machine + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + pdID: + description: ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx + volume attached and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem + type to mount Must be a filesystem type supported + by the host operating system. Ex. "ext4", "xfs". + Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: Mode bits to use on created files + by default. Must be a value between 0 and 0777. + Directories within the path are not affected + by this setting. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected + along with other supported volume types + properties: + configMap: + description: information about the configMap + data to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the + volume as a file whose name is the + key and content is the value. If specified, + the listed keys will be projected + into the specified paths, and unlisted + keys will not be present. If a key + is specified which is not present + in the ConfigMap, the volume setup + will error unless it is marked optional. + Paths must be relative and may not + contain the '..' path or start with + '..'. + items: + description: Maps a string key to + a path within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits + to use on this file, must be + a value between 0 and 0777. + If not specified, the volume + defaultMode will be used. This + might be in conflict with other + options that affect the file + mode, like fsGroup, and the + result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path + of the file to map the key to. + May not be an absolute path. + May not contain the path element + '..'. May not start with the + string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile + represents information to create + the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name and namespace are + supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is + written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits + to use on this file, must be + a value between 0 and 0777. + If not specified, the volume + defaultMode will be used. This + might be in conflict with other + options that affect the file + mode, like fsGroup, and the + result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. + Must be utf-8 encoded. The first + item of the relative path must + not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource + of the container: only resources + limits and requests (limits.cpu, + limits.memory, requests.cpu + and requests.memory) are currently + supported.' + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the + output format of the exposed + resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret + data to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the + volume as a file whose name is the + key and content is the value. If specified, + the listed keys will be projected + into the specified paths, and unlisted + keys will not be present. If a key + is specified which is not present + in the Secret, the volume setup will + error unless it is marked optional. + Paths must be relative and may not + contain the '..' path or start with + '..'. + items: + description: Maps a string key to + a path within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits + to use on this file, must be + a value between 0 and 0777. + If not specified, the volume + defaultMode will be used. This + might be in conflict with other + options that affect the file + mode, like fsGroup, and the + result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path + of the file to map the key to. + May not be an absolute path. + May not contain the path element + '..'. May not start with the + string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended + audience of the token. A recipient + of a token must identify itself with + an identifier specified in the audience + of the token, and otherwise should + reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the + requested duration of validity of + the service account token. As the + token approaches expiration, the kubelet + volume plugin will proactively rotate + the service account token. The kubelet + will start trying to rotate the token + if the token is older than 80 percent + of its time to live or if the token + is older than 24 hours.Defaults to + 1 hour and must be at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative + to the mount point of the file to + project the token into. + type: string + required: + - path + type: object + type: object + type: array + required: + - sources + type: object + quobyte: + description: Quobyte represents a Quobyte mount on + the host that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default + is no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte + volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string + as host:port pair (multiple entries are separated + with commas) which acts as the central registry + for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned + Quobyte volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults + to serivceaccount user + type: string + volume: + description: Volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device + mount on the host that shares a pod''s lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + image: + description: 'The rados image name. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring + for RBDUser. Default is /etc/ceph/keyring. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is + rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + user: + description: 'The rados user name. Default is + admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent + volume attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Default is + "xfs". + type: string + gateway: + description: The host address of the ScaleIO API + Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection + Domain for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret + for ScaleIO user and other sensitive information. + If this is not provided, Login operation will + fail. + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for + a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated + with the protection domain. + type: string + system: + description: The name of the storage system as + configured in ScaleIO. + type: string + volumeName: + description: The name of a volume already created + in the ScaleIO system that is associated with + this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should + populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a value between 0 + and 0777. Defaults to 0644. Directories within + the path are not affected by this setting. This + might be in conflict with other options that + affect the file mode, like fsGroup, and the + result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair + in the Data field of the referenced Secret will + be projected into the volume as a file whose + name is the key and content is the value. If + specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the Secret, the volume setup + will error unless it is marked optional. Paths + must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the file + to map the key to. May not be an absolute + path. May not contain the path element + '..'. May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its + keys must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s + namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to + use for obtaining the StorageOS API credentials. If + not specified, default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable + name of the StorageOS volume. Volume names + are only unique within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope + of the volume within StorageOS. If no namespace + is specified then the Pod's namespace will be + used. This allows the Kubernetes name scoping + to be mirrored within StorageOS for tighter + integration. Set VolumeName to any name to override + the default behaviour. Set to "default" if you + are not using namespaces within StorageOS. Namespaces + that do not pre-exist within StorageOS will + be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume + attached and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) + profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) + profile name. + type: string + volumePath: + description: Path that identifies vSphere volume + vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - containers + type: object + type: object + type: object + deployment: + description: 'Deployment field allows to: 1. indicate whether the Beat + should be deployed as DaemonSet or Deployment, if both are absent, + a default for the Type is used 2. Provide spec for the Deployment + At most one of DaemonSet and Deployment can be used.' + properties: + podTemplate: + description: PodTemplateSpec describes the data a pod should have + when created from a template + properties: + metadata: + description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' + type: object + spec: + description: 'Specification of the desired behavior of the pod. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' + properties: + activeDeadlineSeconds: + description: Optional duration in seconds the pod may be + active on the node relative to StartTime before the system + will actively try to mark it failed and kill associated + containers. Value must be a positive integer. + format: int64 + type: integer + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules + for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. + The node that is most preferred is the one with + the greatest sum of weights, i.e. for each node + that meets all of the scheduling requirements + (resource request, requiredDuringScheduling affinity + expressions, etc.), compute a sum by iterating + through the elements of this field and adding + "weight" to the sum if the node matches the corresponding + matchExpressions; the node(s) with the highest + sum are the most preferred. + items: + description: An empty preferred scheduling term + matches all objects with implicit weight 0 (i.e. + it's a no-op). A null preferred scheduling term + matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated + with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching + the corresponding nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, + the pod will not be scheduled onto the node. If + the affinity requirements specified by this field + cease to be met at some point during pod execution + (e.g. due to an update), the system may or may + not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector + terms. The terms are ORed. + items: + description: A null or empty node selector + term matches no objects. The requirements + of them are ANDed. The TopologySelectorTerm + type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: The label key that + the selector applies to. + type: string + operator: + description: Represents a key's + relationship to a set of values. + Valid operators are In, NotIn, + Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string + values. If the operator is In + or NotIn, the values array must + be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. If + the operator is Gt or Lt, the + values array must have a single + element, which will be interpreted + as an integer. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules + (e.g. co-locate this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. + The node that is most preferred is the one with + the greatest sum of weights, i.e. for each node + that meets all of the scheduling requirements + (resource request, requiredDuringScheduling affinity + expressions, etc.), compute a sum by iterating + through the elements of this field and adding + "weight" to the sum if the node has pods which + matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched + WeightedPodAffinityTerm fields are added per-node + to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set + of resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a + set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values + array must be non-empty. If + the operator is Exists or + DoesNotExist, the values array + must be empty. This array + is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which + namespaces the labelSelector applies + to (matches against); null or empty + list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified + by this field are not met at scheduling time, + the pod will not be scheduled onto the node. If + the affinity requirements specified by this field + cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may + or may not try to eventually evict the pod from + its node. When there are multiple elements, the + lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) + with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose value + of the label with key topologyKey matches + that of any node on which any of the selected + pods is running. Empty topologyKey is not + allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the same node, + zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule + pods to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node + that violates one or more of the expressions. + The node that is most preferred is the one with + the greatest sum of weights, i.e. for each node + that meets all of the scheduling requirements + (resource request, requiredDuringScheduling anti-affinity + expressions, etc.), compute a sum by iterating + through the elements of this field and adding + "weight" to the sum if the node has pods which + matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched + WeightedPodAffinityTerm fields are added per-node + to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, + associated with the corresponding weight. + properties: + labelSelector: + description: A label query over a set + of resources, in this case pods. + properties: + matchExpressions: + description: matchExpressions is a + list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label + key that the selector applies + to. + type: string + operator: + description: operator represents + a key's relationship to a + set of values. Valid operators + are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values + array must be non-empty. If + the operator is Exists or + DoesNotExist, the values array + must be empty. This array + is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map + of {key,value} pairs. A single {key,value} + in the matchLabels map is equivalent + to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are + ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which + namespaces the labelSelector applies + to (matches against); null or empty + list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose + value of the label with key topologyKey + matches that of any node on which any + of the selected pods is running. Empty + topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching + the corresponding podAffinityTerm, in the + range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, + the pod will not be scheduled onto the node. If + the anti-affinity requirements specified by this + field cease to be met at some point during pod + execution (e.g. due to a pod label update), the + system may or may not try to eventually evict + the pod from its node. When there are multiple + elements, the lists of nodes corresponding to + each podAffinityTerm are intersected, i.e. all + terms must be satisfied. + items: + description: Defines a set of pods (namely those + matching the labelSelector relative to the given + namespace(s)) that this pod should be co-located + (affinity) or not co-located (anti-affinity) + with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The + requirements are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents + a key's relationship to a set + of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array + of string values. If the operator + is In or NotIn, the values array + must be non-empty. If the operator + is Exists or DoesNotExist, the + values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located + (affinity) or not co-located (anti-affinity) + with the pods matching the labelSelector + in the specified namespaces, where co-located + is defined as running on a node whose value + of the label with key topologyKey matches + that of any node on which any of the selected + pods is running. Empty topologyKey is not + allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether + a service account token should be automatically mounted. + type: boolean + containers: + description: List of containers belonging to the pod. Containers + cannot currently be added or removed. There must be at + least one container in a Pod. Cannot be updated. + items: + description: A single application container that you want + to run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the + reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double + $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable + exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within + a shell. The docker image''s ENTRYPOINT is used + if this is not provided. Variable references $(VAR_NAME) + are expanded using the container''s environment. + If a variable cannot be resolved, the reference + in the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be + updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set + in the container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) + are expanded using the previous defined environment + variables in the container and any service + environment variables. If a variable cannot + be resolved, the reference in the input string + will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, + regardless of whether the variable exists + or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: + supports metadata.name, metadata.namespace, + metadata.labels, metadata.annotations, + spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, + requests.cpu, requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment + variables in the container. The keys defined within + a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container + is starting. When a key exists in multiple sources, + the value associated with the last source will take + precedence. Values defined by an Env with a duplicate + key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config + management to default or override container images + in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, + IfNotPresent. Defaults to Always if :latest tag + is specified, or IfNotPresent otherwise. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should + take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately + after a container is created. If the handler + fails, the container is terminated and restarted + according to its restart policy. Other management + of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before + a container is terminated due to an API request + or management event such as liveness/startup + probe failure, preemption, resource contention, + etc. The handler is not called if the container + crashes or exits. The reason for termination + is passed to the handler. The Pod''s termination + grace period countdown begins before the PreStop + hooked is executed. Regardless of the outcome + of the handler, the container will eventually + terminate within the Pod''s termination grace + period. Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a + DNS_LABEL. Each container in a pod must have a unique + name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional + information about the network connections a container + uses, but is primarily informational. Not specifying + a port here DOES NOT prevent that port from being + exposed. Any port which is listening on the default + "0.0.0.0" address inside a container will be accessible + from the network. Cannot be updated. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: Number of port to expose on the + pod's IP address. This must be a valid port + number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: Number of port to expose on the + host. If specified, this must be a valid port + number, 0 < x < 65536. If HostNetwork is specified, + this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port + in a pod must have a unique name. Name for + the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, + TCP, or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service + readiness. Container will be removed from service + endpoints if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is + omitted for a container, it defaults to Limits + if that is explicitly specified, otherwise to + an implementation-defined value. More info: + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: 'Security options the pod should run + with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls + whether a process can gain more privileges than + its parent process. This bool directly controls + if the no_new_privs flag will be set on the + container process. AllowPrivilegeEscalation + is true always when the container is: 1) run + as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when + running containers. Defaults to the default + set of capabilities granted by the container + runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. + Processes in privileged containers are essentially + equivalent to root on the host. Defaults to + false. + type: boolean + procMount: + description: procMount denotes the type of proc + mount to use for the containers. The default + is DefaultProcMount which uses the container + runtime defaults for readonly paths and masked + paths. This requires the ProcMountType feature + flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of + the container process. Uses runtime default + if unset. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must + run as a non-root user. If true, the Kubelet + will validate the image at runtime to ensure + that it does not run as UID 0 (root) and fail + to start the container if it does. If unset + or false, no such validation will be performed. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of + the container process. Defaults to user specified + in image metadata if unspecified. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied + to the container. If unspecified, the container + runtime will allocate a random SELinux context + for each container. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + windowsOptions: + description: The Windows specific settings applied + to all containers. If unspecified, the options + from the PodSecurityContext will be used. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the + GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential + spec named by the GMSACredentialSpecName + field. This field is alpha-level and is + only honored by servers that enable the + WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + This field is alpha-level and is only honored + by servers that enable the WindowsGMSA feature + flag. + type: string + runAsUserName: + description: The UserName in Windows to run + the entrypoint of the container process. + Defaults to the user specified in image + metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. This + field is beta-level and may be disabled + with the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod + has successfully initialized. If specified, no other + probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, + just as if the livenessProbe failed. This can be + used to provide different probe parameters at the + beginning of a Pod''s lifecycle, when it might take + a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. + This is an alpha feature enabled by the StartupProbe + feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate + a buffer for stdin in the container runtime. If + this is not set, reads from stdin in the container + will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should + close the stdin channel after it has been opened + by a single attach. When stdin is true the stdin + stream will remain open across multiple attach sessions. + If stdinOnce is set to true, stdin is opened on + container start, is empty until the first client + attaches to stdin, and then remains open and accepts + data until the client disconnects, at which time + stdin is closed and remains closed until the container + is restarted. If this flag is false, a container + processes that reads from stdin will never receive + an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to + which the container''s termination message will + be written is mounted into the container''s filesystem. + Message written is intended to be brief final status, + such as an assertion failure message. Will be truncated + by the node if greater than 4096 bytes. The total + message length across all containers will be limited + to 12kb. Defaults to /dev/termination-log. Cannot + be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message + should be populated. File will use the contents + of terminationMessagePath to populate the container + status message on both success and failure. FallbackToLogsOnError + will use the last chunk of container log output + if the termination message file is empty and the + container exited with an error. The log output is + limited to 2048 bytes or 80 lines, whichever is + smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate + a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. This is a beta feature. + items: + description: volumeDevice describes a mapping of + a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's + filesystem. Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: Path within the container at which + the volume should be mounted. Must not contain + ':'. + type: string + mountPropagation: + description: mountPropagation determines how + mounts are propagated from the host to container + and the other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults + to false. + type: boolean + subPath: + description: Path within the volume from which + the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume + from which the container's volume should be + mounted. Behaves similarly to SubPath but + environment variable references $(VAR_NAME) + are expanded using the container's environment. + Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not + specified, the container runtime's default will + be used, which might be configured in the container + image. Cannot be updated. + type: string + required: + - name + type: object + type: array + dnsConfig: + description: Specifies the DNS parameters of a pod. Parameters + specified here will be merged to the generated DNS configuration + based on DNSPolicy. + properties: + nameservers: + description: A list of DNS name server IP addresses. + This will be appended to the base nameservers generated + from DNSPolicy. Duplicated nameservers will be removed. + items: + type: string + type: array + options: + description: A list of DNS resolver options. This will + be merged with the base options generated from DNSPolicy. + Duplicated entries will be removed. Resolution options + given in Options will override those that appear in + the base DNSPolicy. + items: + description: PodDNSConfigOption defines DNS resolver + options of a pod. + properties: + name: + description: Required. + type: string + value: + type: string + type: object + type: array + searches: + description: A list of DNS search domains for host-name + lookup. This will be appended to the base search paths + generated from DNSPolicy. Duplicated search paths + will be removed. + items: + type: string + type: array + type: object + dnsPolicy: + description: Set DNS policy for the pod. Defaults to "ClusterFirst". + Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', + 'Default' or 'None'. DNS parameters given in DNSConfig + will be merged with the policy selected with DNSPolicy. + To have DNS options set along with hostNetwork, you have + to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. + type: string + enableServiceLinks: + description: 'EnableServiceLinks indicates whether information + about services should be injected into pod''s environment + variables, matching the syntax of Docker links. Optional: + Defaults to true.' + type: boolean + ephemeralContainers: + description: List of ephemeral containers run in this pod. + Ephemeral containers may be run in an existing pod to + perform user-initiated actions such as debugging. This + list cannot be specified when creating a pod, and it cannot + be modified by updating the pod spec. In order to add + an ephemeral container to an existing pod, use the pod's + ephemeralcontainers subresource. This field is alpha-level + and is only honored by servers that enable the EphemeralContainers + feature. + items: + description: An EphemeralContainer is a container that + may be added temporarily to an existing pod for user-initiated + activities such as debugging. Ephemeral containers have + no resource or scheduling guarantees, and they will + not be restarted when they exit or when a pod is removed + or restarted. If an ephemeral container causes a pod + to exceed its resource allocation, the pod may be evicted. + Ephemeral containers may not be added by directly updating + the pod spec. They must be added via the pod's ephemeralcontainers + subresource, and they will appear in the pod spec once + added. This is an alpha feature enabled by the EphemeralContainers + feature flag. + properties: + args: + description: 'Arguments to the entrypoint. The docker + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the + reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double + $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable + exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within + a shell. The docker image''s ENTRYPOINT is used + if this is not provided. Variable references $(VAR_NAME) + are expanded using the container''s environment. + If a variable cannot be resolved, the reference + in the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be + updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set + in the container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) + are expanded using the previous defined environment + variables in the container and any service + environment variables. If a variable cannot + be resolved, the reference in the input string + will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, + regardless of whether the variable exists + or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: + supports metadata.name, metadata.namespace, + metadata.labels, metadata.annotations, + spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, + requests.cpu, requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment + variables in the container. The keys defined within + a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container + is starting. When a key exists in multiple sources, + the value associated with the last source will take + precedence. Values defined by an Env with a duplicate + key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, + IfNotPresent. Defaults to Always if :latest tag + is specified, or IfNotPresent otherwise. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Lifecycle is not allowed for ephemeral + containers. + properties: + postStart: + description: 'PostStart is called immediately + after a container is created. If the handler + fails, the container is terminated and restarted + according to its restart policy. Other management + of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before + a container is terminated due to an API request + or management event such as liveness/startup + probe failure, preemption, resource contention, + etc. The handler is not called if the container + crashes or exits. The reason for termination + is passed to the handler. The Pod''s termination + grace period countdown begins before the PreStop + hooked is executed. Regardless of the outcome + of the handler, the container will eventually + terminate within the Pod''s termination grace + period. Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: Probes are not allowed for ephemeral + containers. + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the ephemeral container specified + as a DNS_LABEL. This name must be unique among all + containers, init containers and ephemeral containers. + type: string + ports: + description: Ports are not allowed for ephemeral containers. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: Number of port to expose on the + pod's IP address. This must be a valid port + number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: Number of port to expose on the + host. If specified, this must be a valid port + number, 0 < x < 65536. If HostNetwork is specified, + this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port + in a pod must have a unique name. Name for + the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, + TCP, or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + description: Probes are not allowed for ephemeral + containers. + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: Resources are not allowed for ephemeral + containers. Ephemeral containers use spare resources + already allocated to the pod. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is + omitted for a container, it defaults to Limits + if that is explicitly specified, otherwise to + an implementation-defined value. More info: + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: SecurityContext is not allowed for ephemeral + containers. + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls + whether a process can gain more privileges than + its parent process. This bool directly controls + if the no_new_privs flag will be set on the + container process. AllowPrivilegeEscalation + is true always when the container is: 1) run + as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when + running containers. Defaults to the default + set of capabilities granted by the container + runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. + Processes in privileged containers are essentially + equivalent to root on the host. Defaults to + false. + type: boolean + procMount: + description: procMount denotes the type of proc + mount to use for the containers. The default + is DefaultProcMount which uses the container + runtime defaults for readonly paths and masked + paths. This requires the ProcMountType feature + flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of + the container process. Uses runtime default + if unset. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must + run as a non-root user. If true, the Kubelet + will validate the image at runtime to ensure + that it does not run as UID 0 (root) and fail + to start the container if it does. If unset + or false, no such validation will be performed. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of + the container process. Defaults to user specified + in image metadata if unspecified. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied + to the container. If unspecified, the container + runtime will allocate a random SELinux context + for each container. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + windowsOptions: + description: The Windows specific settings applied + to all containers. If unspecified, the options + from the PodSecurityContext will be used. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the + GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential + spec named by the GMSACredentialSpecName + field. This field is alpha-level and is + only honored by servers that enable the + WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + This field is alpha-level and is only honored + by servers that enable the WindowsGMSA feature + flag. + type: string + runAsUserName: + description: The UserName in Windows to run + the entrypoint of the container process. + Defaults to the user specified in image + metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. This + field is beta-level and may be disabled + with the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + startupProbe: + description: Probes are not allowed for ephemeral + containers. + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate + a buffer for stdin in the container runtime. If + this is not set, reads from stdin in the container + will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should + close the stdin channel after it has been opened + by a single attach. When stdin is true the stdin + stream will remain open across multiple attach sessions. + If stdinOnce is set to true, stdin is opened on + container start, is empty until the first client + attaches to stdin, and then remains open and accepts + data until the client disconnects, at which time + stdin is closed and remains closed until the container + is restarted. If this flag is false, a container + processes that reads from stdin will never receive + an EOF. Default is false + type: boolean + targetContainerName: + description: If set, the name of the container from + PodSpec that this ephemeral container targets. The + ephemeral container will be run in the namespaces + (IPC, PID, etc) of this container. If not set then + the ephemeral container is run in whatever namespaces + are shared for the pod. Note that the container + runtime must support this feature. + type: string + terminationMessagePath: + description: 'Optional: Path at which the file to + which the container''s termination message will + be written is mounted into the container''s filesystem. + Message written is intended to be brief final status, + such as an assertion failure message. Will be truncated + by the node if greater than 4096 bytes. The total + message length across all containers will be limited + to 12kb. Defaults to /dev/termination-log. Cannot + be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message + should be populated. File will use the contents + of terminationMessagePath to populate the container + status message on both success and failure. FallbackToLogsOnError + will use the last chunk of container log output + if the termination message file is empty and the + container exited with an error. The log output is + limited to 2048 bytes or 80 lines, whichever is + smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate + a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. This is a beta feature. + items: + description: volumeDevice describes a mapping of + a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's + filesystem. Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: Path within the container at which + the volume should be mounted. Must not contain + ':'. + type: string + mountPropagation: + description: mountPropagation determines how + mounts are propagated from the host to container + and the other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults + to false. + type: boolean + subPath: + description: Path within the volume from which + the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume + from which the container's volume should be + mounted. Behaves similarly to SubPath but + environment variable references $(VAR_NAME) + are expanded using the container's environment. + Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not + specified, the container runtime's default will + be used, which might be configured in the container + image. Cannot be updated. + type: string + required: + - name + type: object + type: array + hostAliases: + description: HostAliases is an optional list of hosts and + IPs that will be injected into the pod's hosts file if + specified. This is only valid for non-hostNetwork pods. + items: + description: HostAlias holds the mapping between IP and + hostnames that will be injected as an entry in the pod's + hosts file. + properties: + hostnames: + description: Hostnames for the above IP address. + items: + type: string + type: array + ip: + description: IP address of the host file entry. + type: string + type: object + type: array + hostIPC: + description: 'Use the host''s ipc namespace. Optional: Default + to false.' + type: boolean + hostNetwork: + description: Host networking requested for this pod. Use + the host's network namespace. If this option is set, the + ports that will be used must be specified. Default to + false. + type: boolean + hostPID: + description: 'Use the host''s pid namespace. Optional: Default + to false.' + type: boolean + hostname: + description: Specifies the hostname of the Pod If not specified, + the pod's hostname will be set to a system-defined value. + type: string + imagePullSecrets: + description: 'ImagePullSecrets is an optional list of references + to secrets in the same namespace to use for pulling any + of the images used by this PodSpec. If specified, these + secrets will be passed to individual puller implementations + for them to use. For example, in the case of docker, only + DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' + items: + description: LocalObjectReference contains enough information + to let you locate the referenced object inside the same + namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + type: array + initContainers: + description: 'List of initialization containers belonging + to the pod. Init containers are executed in order prior + to containers being started. If any init container fails, + the pod is considered to have failed and is handled according + to its restartPolicy. The name for an init container or + normal container must be unique among all containers. + Init containers may not have Lifecycle actions, Readiness + probes, Liveness probes, or Startup probes. The resourceRequirements + of an init container are taken into account during scheduling + by finding the highest request/limit for each resource + type, and then using the max of of that value or the sum + of the normal containers. Limits are applied to init containers + in a similar fashion. Init containers cannot currently + be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/' + items: + description: A single application container that you want + to run within a pod. + properties: + args: + description: 'Arguments to the entrypoint. The docker + image''s CMD is used if this is not provided. Variable + references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the + reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double + $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable + exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within + a shell. The docker image''s ENTRYPOINT is used + if this is not provided. Variable references $(VAR_NAME) + are expanded using the container''s environment. + If a variable cannot be resolved, the reference + in the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be + updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set + in the container. Cannot be updated. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) + are expanded using the previous defined environment + variables in the container and any service + environment variables. If a variable cannot + be resolved, the reference in the input string + will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, + regardless of whether the variable exists + or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: + supports metadata.name, metadata.namespace, + metadata.labels, metadata.annotations, + spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, + requests.cpu, requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment + variables in the container. The keys defined within + a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container + is starting. When a key exists in multiple sources, + the value associated with the last source will take + precedence. Values defined by an Env with a duplicate + key will take precedence. Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend + to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config + management to default or override container images + in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, + IfNotPresent. Defaults to Always if :latest tag + is specified, or IfNotPresent otherwise. Cannot + be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should + take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: 'PostStart is called immediately + after a container is created. If the handler + fails, the container is terminated and restarted + according to its restart policy. Other management + of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before + a container is terminated due to an API request + or management event such as liveness/startup + probe failure, preemption, resource contention, + etc. The handler is not called if the container + crashes or exits. The reason for termination + is passed to the handler. The Pod''s termination + grace period countdown begins before the PreStop + hooked is executed. Regardless of the outcome + of the handler, the container will eventually + terminate within the Pod''s termination grace + period. Other management of the container blocks + until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the + action to take. + properties: + command: + description: Command is the command line + to execute inside the container, the + working directory for the command is + root ('/') in the container's filesystem. + The command is simply exec'd, it is + not run inside a shell, so traditional + shell instructions ('|', etc) won't + work. To use a shell, you need to explicitly + call out to that shell. Exit status + of 0 is treated as live/healthy and + non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, + defaults to the pod IP. You probably + want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a + custom header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action + involving a TCP port. TCP hooks not yet + supported TODO: implement a realistic TCP + lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port + to access on the container. Number must + be in the range 1 to 65535. Name must + be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: 'Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a + DNS_LABEL. Each container in a pod must have a unique + name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional + information about the network connections a container + uses, but is primarily informational. Not specifying + a port here DOES NOT prevent that port from being + exposed. Any port which is listening on the default + "0.0.0.0" address inside a container will be accessible + from the network. Cannot be updated. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: Number of port to expose on the + pod's IP address. This must be a valid port + number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: Number of port to expose on the + host. If specified, this must be a valid port + number, 0 < x < 65536. If HostNetwork is specified, + this must match ContainerPort. Most containers + do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port + in a pod must have a unique name. Name for + the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, + TCP, or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service + readiness. Container will be removed from service + endpoints if the probe fails. Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount + of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is + omitted for a container, it defaults to Limits + if that is explicitly specified, otherwise to + an implementation-defined value. More info: + https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: 'Security options the pod should run + with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls + whether a process can gain more privileges than + its parent process. This bool directly controls + if the no_new_privs flag will be set on the + container process. AllowPrivilegeEscalation + is true always when the container is: 1) run + as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when + running containers. Defaults to the default + set of capabilities granted by the container + runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. + Processes in privileged containers are essentially + equivalent to root on the host. Defaults to + false. + type: boolean + procMount: + description: procMount denotes the type of proc + mount to use for the containers. The default + is DefaultProcMount which uses the container + runtime defaults for readonly paths and masked + paths. This requires the ProcMountType feature + flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of + the container process. Uses runtime default + if unset. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must + run as a non-root user. If true, the Kubelet + will validate the image at runtime to ensure + that it does not run as UID 0 (root) and fail + to start the container if it does. If unset + or false, no such validation will be performed. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of + the container process. Defaults to user specified + in image metadata if unspecified. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied + to the container. If unspecified, the container + runtime will allocate a random SELinux context + for each container. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + windowsOptions: + description: The Windows specific settings applied + to all containers. If unspecified, the options + from the PodSecurityContext will be used. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes + precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the + GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential + spec named by the GMSACredentialSpecName + field. This field is alpha-level and is + only honored by servers that enable the + WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + This field is alpha-level and is only honored + by servers that enable the WindowsGMSA feature + flag. + type: string + runAsUserName: + description: The UserName in Windows to run + the entrypoint of the container process. + Defaults to the user specified in image + metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified + in SecurityContext takes precedence. This + field is beta-level and may be disabled + with the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + startupProbe: + description: 'StartupProbe indicates that the Pod + has successfully initialized. If specified, no other + probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, + just as if the livenessProbe failed. This can be + used to provide different probe parameters at the + beginning of a Pod''s lifecycle, when it might take + a long time to load data or warm a cache, than during + steady-state operation. This cannot be updated. + This is an alpha feature enabled by the StartupProbe + feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + properties: + exec: + description: One and only one of the following + should be specified. Exec specifies the action + to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') + in the container's filesystem. The command + is simply exec'd, it is not run inside a + shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you + need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. Defaults to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting + to the host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform + the probe. Default to 10 seconds. Minimum value + is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for + the probe to be considered successful after + having failed. Defaults to 1. Must be 1 for + liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to + access on the container. Number must be + in the range 1 to 65535. Name must be an + IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the + probe times out. Defaults to 1 second. Minimum + value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + stdin: + description: Whether this container should allocate + a buffer for stdin in the container runtime. If + this is not set, reads from stdin in the container + will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should + close the stdin channel after it has been opened + by a single attach. When stdin is true the stdin + stream will remain open across multiple attach sessions. + If stdinOnce is set to true, stdin is opened on + container start, is empty until the first client + attaches to stdin, and then remains open and accepts + data until the client disconnects, at which time + stdin is closed and remains closed until the container + is restarted. If this flag is false, a container + processes that reads from stdin will never receive + an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to + which the container''s termination message will + be written is mounted into the container''s filesystem. + Message written is intended to be brief final status, + such as an assertion failure message. Will be truncated + by the node if greater than 4096 bytes. The total + message length across all containers will be limited + to 12kb. Defaults to /dev/termination-log. Cannot + be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message + should be populated. File will use the contents + of terminationMessagePath to populate the container + status message on both success and failure. FallbackToLogsOnError + will use the last chunk of container log output + if the termination message file is empty and the + container exited with an error. The log output is + limited to 2048 bytes or 80 lines, whichever is + smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate + a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices + to be used by the container. This is a beta feature. + items: + description: volumeDevice describes a mapping of + a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside of + the container that the device will be mapped + to. + type: string + name: + description: name must match the name of a persistentVolumeClaim + in the pod + type: string + required: + - devicePath + - name + type: object + type: array + volumeMounts: + description: Pod volumes to mount into the container's + filesystem. Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: Path within the container at which + the volume should be mounted. Must not contain + ':'. + type: string + mountPropagation: + description: mountPropagation determines how + mounts are propagated from the host to container + and the other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults + to false. + type: boolean + subPath: + description: Path within the volume from which + the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume + from which the container's volume should be + mounted. Behaves similarly to SubPath but + environment variable references $(VAR_NAME) + are expanded using the container's environment. + Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not + specified, the container runtime's default will + be used, which might be configured in the container + image. Cannot be updated. + type: string + required: + - name + type: object + type: array + nodeName: + description: NodeName is a request to schedule this pod + onto a specific node. If it is non-empty, the scheduler + simply schedules this pod onto that node, assuming that + it fits resource requirements. + type: string + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true + for the pod to fit on a node. Selector which must match + a node''s labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Overhead represents the resource overhead + associated with running a pod for a given RuntimeClass. + This field will be autopopulated at admission time by + the RuntimeClass admission controller. If the RuntimeClass + admission controller is enabled, overhead must not be + set in Pod create requests. The RuntimeClass admission + controller will reject Pod create requests which have + the overhead already set. If RuntimeClass is configured + and selected in the PodSpec, Overhead will be set to the + value defined in the corresponding RuntimeClass, otherwise + it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md + This field is alpha-level as of Kubernetes v1.16, and + is only honored by servers that enable the PodOverhead + feature.' + type: object + preemptionPolicy: + description: PreemptionPolicy is the Policy for preempting + pods with lower priority. One of Never, PreemptLowerPriority. + Defaults to PreemptLowerPriority if unset. This field + is alpha-level and is only honored by servers that enable + the NonPreemptingPriority feature. + type: string + priority: + description: The priority value. Various system components + use this field to find the priority of the pod. When Priority + Admission Controller is enabled, it prevents users from + setting this field. The admission controller populates + this field from PriorityClassName. The higher the value, + the higher the priority. + format: int32 + type: integer + priorityClassName: + description: If specified, indicates the pod's priority. + "system-node-critical" and "system-cluster-critical" are + two special keywords which indicate the highest priorities + with the former being the highest priority. Any other + name must be defined by creating a PriorityClass object + with that name. If not specified, the pod priority will + be default or zero if there is no default. + type: string + readinessGates: + description: 'If specified, all readiness gates will be + evaluated for pod readiness. A pod is ready when all its + containers are ready AND all conditions specified in the + readiness gates have status equal to "True" More info: + https://git.k8s.io/enhancements/keps/sig-network/0007-pod-ready%2B%2B.md' + items: + description: PodReadinessGate contains the reference to + a pod condition + properties: + conditionType: + description: ConditionType refers to a condition in + the pod's condition list with matching type. + type: string + required: + - conditionType + type: object + type: array + restartPolicy: + description: 'Restart policy for all containers within the + pod. One of Always, OnFailure, Never. Default to Always. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy' + type: string + runtimeClassName: + description: 'RuntimeClassName refers to a RuntimeClass + object in the node.k8s.io group, which should be used + to run this pod. If no RuntimeClass resource matches + the named class, the pod will not be run. If unset or + empty, the "legacy" RuntimeClass will be used, which is + an implicit class with an empty definition that uses the + default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/runtime-class.md + This is a beta feature as of Kubernetes v1.14.' + type: string + schedulerName: + description: If specified, the pod will be dispatched by + specified scheduler. If not specified, the pod will be + dispatched by default scheduler. + type: string + securityContext: + description: 'SecurityContext holds pod-level security attributes + and common container settings. Optional: Defaults to empty. See + type description for default values of each field.' + properties: + fsGroup: + description: "A special supplemental group that applies + to all containers in a pod. Some volume types allow + the Kubelet to change the ownership of that volume + to be owned by the pod: \n 1. The owning GID will + be the FSGroup 2. The setgid bit is set (new files + created in the volume will be owned by FSGroup) 3. + The permission bits are OR'd with rw-rw---- \n If + unset, the Kubelet will not modify the ownership and + permissions of any volume." + format: int64 + type: integer + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all + containers. If unspecified, the container runtime + will allocate a random SELinux context for each container. May + also be set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's + primary GID. If unspecified, no groups will be added + to any container. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls + used for the pod. Pods with unsupported sysctls (by + the container runtime) might fail to launch. + items: + description: Sysctl defines a kernel parameter to + be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options within + a container's SecurityContext will be used. If set + in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. This + field is alpha-level and is only honored by servers + that enable the WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. This field + is alpha-level and is only honored by servers + that enable the WindowsGMSA feature flag. + type: string + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + This field is beta-level and may be disabled with + the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + serviceAccount: + description: 'DeprecatedServiceAccount is a depreciated + alias for ServiceAccountName. Deprecated: Use serviceAccountName + instead.' + type: string + serviceAccountName: + description: 'ServiceAccountName is the name of the ServiceAccount + to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/' + type: string + shareProcessNamespace: + description: 'Share a single process namespace between all + of the containers in a pod. When this is set containers + will be able to view and signal processes from other containers + in the same pod, and the first process in each container + will not be assigned PID 1. HostPID and ShareProcessNamespace + cannot both be set. Optional: Default to false.' + type: boolean + subdomain: + description: If specified, the fully qualified Pod hostname + will be "...svc.". If not specified, the pod will not have a domainname + at all. + type: string + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs + to terminate gracefully. May be decreased in delete request. + Value must be non-negative integer. The value zero indicates + delete immediately. If this value is nil, the default + grace period will be used instead. The grace period is + the duration in seconds after the processes running in + the pod are sent a termination signal and the time when + the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for + your process. Defaults to 30 seconds. + format: int64 + type: integer + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple + using the matching operator . + properties: + effect: + description: Effect indicates the taint effect to + match. Empty means match all taint effects. When + specified, allowed values are NoSchedule, PreferNoSchedule + and NoExecute. + type: string + key: + description: Key is the taint key that the toleration + applies to. Empty means match all taint keys. If + the key is empty, operator must be Exists; this + combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship + to the value. Valid operators are Exists and Equal. + Defaults to Equal. Exists is equivalent to wildcard + for value, so that a pod can tolerate all taints + of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period + of time the toleration (which must be of effect + NoExecute, otherwise this field is ignored) tolerates + the taint. By default, it is not set, which means + tolerate the taint forever (do not evict). Zero + and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration + matches to. If the operator is Exists, the value + should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + description: TopologySpreadConstraints describes how a group + of pods ought to spread across topology domains. Scheduler + will schedule pods in a way which abides by the constraints. + This field is alpha-level and is only honored by clusters + that enables the EvenPodsSpread feature. All topologySpreadConstraints + are ANDed. + items: + description: TopologySpreadConstraint specifies how to + spread matching pods among the given topology. + properties: + labelSelector: + description: LabelSelector is used to find matching + pods. Pods that match this label selector are counted + to determine the number of pods in their corresponding + topology domain. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement is + a selector that contains values, a key, and + an operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If + the operator is Exists or DoesNotExist, + the values array must be empty. This array + is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + maxSkew: + description: 'MaxSkew describes the degree to which + pods may be unevenly distributed. It''s the maximum + permitted difference between the number of matching + pods in any two topology domains of a given topology + type. For example, in a 3-zone cluster, MaxSkew + is set to 1, and pods with the same labelSelector + spread as 1/1/0: | zone1 | zone2 | zone3 | | P | P | | + - if MaxSkew is 1, incoming pod can only be scheduled + to zone3 to become 1/1/1; scheduling it onto zone1(zone2) + would make the ActualSkew(2-0) on zone1(zone2) violate + MaxSkew(1). - if MaxSkew is 2, incoming pod can + be scheduled onto any zone. It''s a required field. + Default value is 1 and 0 is not allowed.' + format: int32 + type: integer + topologyKey: + description: TopologyKey is the key of node labels. + Nodes that have a label with this key and identical + values are considered to be in the same topology. + We consider each as a "bucket", and + try to put balanced number of pods into each bucket. + It's a required field. + type: string + whenUnsatisfiable: + description: 'WhenUnsatisfiable indicates how to deal + with a pod if it doesn''t satisfy the spread constraint. + - DoNotSchedule (default) tells the scheduler not + to schedule it - ScheduleAnyway tells the scheduler + to still schedule it It''s considered as "Unsatisfiable" + if and only if placing incoming pod on any topology + violates "MaxSkew". For example, in a 3-zone cluster, + MaxSkew is set to 1, and pods with the same labelSelector + spread as 3/1/1: | zone1 | zone2 | zone3 | | P P + P | P | P | If WhenUnsatisfiable is set + to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) + on zone2(zone3) satisfies MaxSkew(1). In other words, + the cluster can still be imbalanced, but scheduler + won''t make it *more* imbalanced. It''s a required + field.' + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + description: 'List of volumes that can be mounted by containers + belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' + items: + description: Volume represents a named volume in a pod + that may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS + Disk resource that is attached to a kubelet''s host + machine and then exposed to the pod. More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + partition: + description: 'The partition in the volume that + you want to mount. If omitted, the default is + to mount by volume name. Examples: For volume + /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda + is "0" (or you can leave the property empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set + the ReadOnly property in VolumeMounts to "true". + If omitted, the default is "false". More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk + resource in AWS (Amazon EBS volume). More info: + https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk + mount on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, + Read Write.' + type: string + diskName: + description: The Name of the data disk in the + blob storage + type: string + diskURI: + description: The URI the data disk in the blob + storage + type: string + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + kind: + description: 'Expected values Shared: multiple + blob disks per storage account Dedicated: single + blob disk per storage account Managed: azure + managed data disk (only in managed availability + set). defaults to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains + Azure Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on + the host that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection + of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, + rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path + to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference + to the authentication secret for User, default + is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user + name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: + https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object + containing parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume + in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that + should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a value between 0 + and 0777. Defaults to 0644. Directories within + the path are not affected by this setting. This + might be in conflict with other options that + affect the file mode, like fsGroup, and the + result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair + in the Data field of the referenced ConfigMap + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the ConfigMap, the volume + setup will error unless it is marked optional. + Paths must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the file + to map the key to. May not be an absolute + path. May not contain the path element + '..'. May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or + its keys must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + storage that is handled by an external CSI driver + (Alpha feature). + properties: + driver: + description: Driver is the name of the CSI driver + that handles this volume. Consult with your + admin for the correct name as registered in + the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", + "xfs", "ntfs". If not provided, the empty value + is passed to the associated CSI driver which + will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference + to the secret object containing sensitive information + to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if + no secret is required. If the secret object + contains more than one secret, all secret references + are passed. + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration + for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific + properties that are passed to the CSI driver. + Consult your driver's documentation for supported + values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about + the pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a value between 0 + and 0777. Defaults to 0644. Directories within + the path are not affected by this setting. This + might be in conflict with other options that + affect the file mode, like fsGroup, and the + result can be other mode bits set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must + not be absolute or contain the ''..'' + path. Must be utf-8 encoded. The first + item of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu + and requests.memory) are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should + back this directory. The default is "" which + means to use the node''s default medium. Must + be an empty string (default) or Memory. More + info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'Total amount of local storage required + for this EmptyDir volume. The size limit is + also applicable for memory medium. The maximum + usage on memory medium EmptyDir would be the + minimum value between the SizeLimit specified + here and the sum of memory limits of all containers + in a pod. The default is nil which means that + the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + fc: + description: FC represents a Fibre Channel resource + that is attached to a kubelet's host machine and + then exposed to the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. TODO: + how do we prevent errors in the filesystem from + compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names + (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume + resource that is provisioned/attached using an exec + based plugin. + properties: + driver: + description: Driver is the name of the driver + to use for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". The default + filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options + if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting + in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference + to the secret object containing sensitive information + to pass to the plugin scripts. This may be empty + if no secret object is specified. If the secret + object contains more than one secret, all secrets + are passed to the plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the + Flocker control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata + -> name on the dataset for Flocker should be + considered as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique + identifier of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + partition: + description: 'The partition in the volume that + you want to mount. If omitted, the default is + to mount by volume name. Examples: For volume + /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda + is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in + GCE. Used to identify the disk in GCE. More + info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository + at a particular revision. DEPRECATED: GitRepo is + deprecated. To provision a container with a git + repo, mount an EmptyDir into an InitContainer that + clones the repo using git, then mount the EmptyDir + into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain + or start with '..'. If '.' is supplied, the + volume directory will be the git repository. Otherwise, + if specified, the volume will contain the git + repository in the subdirectory with the given + name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount + on the host that shares a pod''s lifetime. More + info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name + that details Glusterfs topology. More info: + https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs + volume to be mounted with read-only permissions. + Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file + or directory on the host machine that is directly + exposed to the container. This is generally used + for system agents or other privileged things that + are allowed to see the host machine. Most containers + will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use + host directory mounts and who can/can not mount + host directories as read/write.' + properties: + path: + description: 'Path of the directory on the host. + If the path is a symlink, it will follow the + link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults + to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource + that is attached to a kubelet''s host machine and + then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP + authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP + authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, + new iSCSI interface : will be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an + iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal + is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 + and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and + initiator authentication + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is + either an IP or ip_addr:port if the port is + other than default (typically TCP ports 860 + and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL + and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host + that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS + server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS + export to be mounted with read-only permissions. + Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address + of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same + namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this + volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in + VolumeMounts. Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets + host machine + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + pdID: + description: ID that identifies Photon Controller + persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx + volume attached and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem + type to mount Must be a filesystem type supported + by the host operating system. Ex. "ext4", "xfs". + Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, + configmaps, and downward API + properties: + defaultMode: + description: Mode bits to use on created files + by default. Must be a value between 0 and 0777. + Directories within the path are not affected + by this setting. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected + along with other supported volume types + properties: + configMap: + description: information about the configMap + data to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the + volume as a file whose name is the + key and content is the value. If specified, + the listed keys will be projected + into the specified paths, and unlisted + keys will not be present. If a key + is specified which is not present + in the ConfigMap, the volume setup + will error unless it is marked optional. + Paths must be relative and may not + contain the '..' path or start with + '..'. + items: + description: Maps a string key to + a path within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits + to use on this file, must be + a value between 0 and 0777. + If not specified, the volume + defaultMode will be used. This + might be in conflict with other + options that affect the file + mode, like fsGroup, and the + result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path + of the file to map the key to. + May not be an absolute path. + May not contain the path element + '..'. May not start with the + string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile + represents information to create + the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects + a field of the pod: only annotations, + labels, name and namespace are + supported.' + properties: + apiVersion: + description: Version of the + schema the FieldPath is + written in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified + API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits + to use on this file, must be + a value between 0 and 0777. + If not specified, the volume + defaultMode will be used. This + might be in conflict with other + options that affect the file + mode, like fsGroup, and the + result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file + to be created. Must not be absolute + or contain the ''..'' path. + Must be utf-8 encoded. The first + item of the relative path must + not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource + of the container: only resources + limits and requests (limits.cpu, + limits.memory, requests.cpu + and requests.memory) are currently + supported.' + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the + output format of the exposed + resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret + data to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the + volume as a file whose name is the + key and content is the value. If specified, + the listed keys will be projected + into the specified paths, and unlisted + keys will not be present. If a key + is specified which is not present + in the Secret, the volume setup will + error unless it is marked optional. + Paths must be relative and may not + contain the '..' path or start with + '..'. + items: + description: Maps a string key to + a path within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits + to use on this file, must be + a value between 0 and 0777. + If not specified, the volume + defaultMode will be used. This + might be in conflict with other + options that affect the file + mode, like fsGroup, and the + result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path + of the file to map the key to. + May not be an absolute path. + May not contain the path element + '..'. May not start with the + string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended + audience of the token. A recipient + of a token must identify itself with + an identifier specified in the audience + of the token, and otherwise should + reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the + requested duration of validity of + the service account token. As the + token approaches expiration, the kubelet + volume plugin will proactively rotate + the service account token. The kubelet + will start trying to rotate the token + if the token is older than 80 percent + of its time to live or if the token + is older than 24 hours.Defaults to + 1 hour and must be at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative + to the mount point of the file to + project the token into. + type: string + required: + - path + type: object + type: object + type: array + required: + - sources + type: object + quobyte: + description: Quobyte represents a Quobyte mount on + the host that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default + is no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte + volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string + as host:port pair (multiple entries are separated + with commas) which acts as the central registry + for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned + Quobyte volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults + to serivceaccount user + type: string + volume: + description: Volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device + mount on the host that shares a pod''s lifetime. + More info: https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that + you want to mount. Tip: Ensure that the filesystem + type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. More info: + https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem + from compromising the machine' + type: string + image: + description: 'The rados image name. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring + for RBDUser. Default is /etc/ceph/keyring. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is + rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + user: + description: 'The rados user name. Default is + admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent + volume attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Default is + "xfs". + type: string + gateway: + description: The host address of the ScaleIO API + Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection + Domain for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret + for ScaleIO user and other sensitive information. + If this is not provided, Login operation will + fail. + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for + a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated + with the protection domain. + type: string + system: + description: The name of the storage system as + configured in ScaleIO. + type: string + volumeName: + description: The name of a volume already created + in the ScaleIO system that is associated with + this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should + populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a value between 0 + and 0777. Defaults to 0644. Directories within + the path are not affected by this setting. This + might be in conflict with other options that + affect the file mode, like fsGroup, and the + result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair + in the Data field of the referenced Secret will + be projected into the volume as a file whose + name is the key and content is the value. If + specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified which + is not present in the Secret, the volume setup + will error unless it is marked optional. Paths + must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might be + in conflict with other options that affect + the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the file + to map the key to. May not be an absolute + path. May not contain the path element + '..'. May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its + keys must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s + namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to + use for obtaining the StorageOS API credentials. If + not specified, default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable + name of the StorageOS volume. Volume names + are only unique within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope + of the volume within StorageOS. If no namespace + is specified then the Pod's namespace will be + used. This allows the Kubernetes name scoping + to be mirrored within StorageOS for tighter + integration. Set VolumeName to any name to override + the default behaviour. Set to "default" if you + are not using namespaces within StorageOS. Namespaces + that do not pre-exist within StorageOS will + be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume + attached and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be + a filesystem type supported by the host operating + system. Ex. "ext4", "xfs", "ntfs". Implicitly + inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) + profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) + profile name. + type: string + volumePath: + description: Path that identifies vSphere volume + vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - containers + type: object + type: object + replicas: + format: int32 + type: integer + type: object + elasticsearchRef: + description: ElasticsearchRef is a reference to an Elasticsearch cluster + running in the same Kubernetes cluster. + properties: + name: + description: Name of the Kubernetes object. + type: string + namespace: + description: Namespace of the Kubernetes object. If empty, defaults + to the current namespace. + type: string + required: + - name + type: object + image: + description: Image is the Beat Docker image to deploy. Version has to + match the Beat in the image. + type: string + serviceAccountName: + description: ServiceAccountName is used to check access from the current + resource to Elasticsearch resource in a different namespace. Can only + be used if ECK is enforcing RBAC on references. + type: string + type: + description: Type is the type of the Beat to deploy. Any string can + be used, but well-known types will be recognized and will allow to + provide sane default configurations. + type: string + version: + description: Version of the Beat. + type: string + required: + - type + - version + type: object + status: + description: BeatStatus defines the observed state of Beat + properties: + associationStatus: + description: AssociationStatus is the status of an association resource. + type: string + availableNodes: + format: int32 + type: integer + expectedNodes: + format: int32 + type: integer + health: + type: string + type: object + type: object + version: v1beta1 + versions: + - name: v1beta1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crds/bases/kustomization.yaml b/config/crds/bases/kustomization.yaml index 3c28d99936..770bd2e8ff 100644 --- a/config/crds/bases/kustomization.yaml +++ b/config/crds/bases/kustomization.yaml @@ -3,3 +3,4 @@ resources: - elasticsearch.k8s.elastic.co_elasticsearches.yaml - kibana.k8s.elastic.co_kibanas.yaml - enterprisesearch.k8s.elastic.co_enterprisesearches.yaml + - beat.k8s.elastic.co_beats.yaml diff --git a/config/crds/patches/beat-patches.yaml b/config/crds/patches/beat-patches.yaml new file mode 100644 index 0000000000..30fe46383c --- /dev/null +++ b/config/crds/patches/beat-patches.yaml @@ -0,0 +1,17 @@ +# Remove validation.openAPIV3Schema.type that causes failures on k8s 1.11. +# This should have been fixed with https://github.com/kubernetes-sigs/controller-tools/pull/72, but it looks like +# this commit has been lost in history. See https://github.com/kubernetes-sigs/controller-tools/issues/296. +# TODO: remove once fixed in controller-tools +- op: remove + path: /spec/validation/openAPIV3Schema/type + +# Using `kubectl apply` stores the complete CRD file as an annotation, +# which may be too big for the annotations size limit. +# One way to mitigate this problem is to remove the (huge) podTemplate properties from the CRD. +# It also avoids the problem of having any k8s-version specific field in the Pod schema, +# that would maybe not match the user's k8s version. +- op: remove + path: /spec/validation/openAPIV3Schema/properties/spec/properties/daemonSet/properties/podTemplate + +- op: remove + path: /spec/validation/openAPIV3Schema/properties/spec/properties/deployment/properties/podTemplate diff --git a/config/crds/patches/kustomization.yaml b/config/crds/patches/kustomization.yaml index 44f4b374f6..3514871bd0 100644 --- a/config/crds/patches/kustomization.yaml +++ b/config/crds/patches/kustomization.yaml @@ -49,3 +49,10 @@ patchesJson6902: kind: CustomResourceDefinition name: enterprisesearches.enterprisesearch.k8s.elastic.co path: ent-patches.yaml + # custom patches for Beat + - target: + group: apiextensions.k8s.io + version: v1beta1 + kind: CustomResourceDefinition + name: beats.beat.k8s.elastic.co + path: beat-patches.yaml diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index a93332f0de..565dd7b48c 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -116,6 +116,19 @@ rules: - update - patch - delete +- apiGroups: + - beat.k8s.elastic.co + resources: + - beats + - beats/status + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - admissionregistration.k8s.io resources: diff --git a/config/operator/all-in-one/cluster_role.template.yaml b/config/operator/all-in-one/cluster_role.template.yaml index dee4810c46..41e9fef28d 100644 --- a/config/operator/all-in-one/cluster_role.template.yaml +++ b/config/operator/all-in-one/cluster_role.template.yaml @@ -10,6 +10,7 @@ rules: - "authorization.k8s.io" resources: - subjectaccessreviews + - rbac verbs: - create - apiGroups: @@ -22,6 +23,7 @@ rules: - secrets - services - configmaps + - serviceaccounts verbs: - get - list @@ -113,6 +115,19 @@ rules: - update - patch - delete +- apiGroups: + - beat.k8s.elastic.co + resources: + - beats + - beats/status + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - admissionregistration.k8s.io resources: diff --git a/config/operator/all-in-one/rbac.yaml b/config/operator/all-in-one/rbac.yaml index 634f5cf760..6f741e23a2 100644 --- a/config/operator/all-in-one/rbac.yaml +++ b/config/operator/all-in-one/rbac.yaml @@ -19,6 +19,9 @@ rules: - apiGroups: ["enterprisesearch.k8s.elastic.co"] resources: ["enterprisesearches"] verbs: ["get", "list", "watch"] + - apiGroups: ["beat.k8s.elastic.co"] + resources: ["beats"] + verbs: ["get", "list", "watch"] --- @@ -42,3 +45,6 @@ rules: - apiGroups: ["enterprisesearch.k8s.elastic.co"] resources: ["enterprisesearches"] verbs: ["create", "delete", "deletecollection", "patch", "update"] + - apiGroups: ["beat.k8s.elastic.co"] + resources: ["beats"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] diff --git a/config/samples/beat/filebeat_es_kibana.yaml b/config/samples/beat/filebeat_es_kibana.yaml new file mode 100644 index 0000000000..2099d23da2 --- /dev/null +++ b/config/samples/beat/filebeat_es_kibana.yaml @@ -0,0 +1,51 @@ +# This sample sets up an Elasticsearch cluster and a Kibana instance preconfigured for that cluster +apiVersion: elasticsearch.k8s.elastic.co/v1 +kind: Elasticsearch +metadata: + name: elasticsearch-sample +spec: + version: 7.6.0 + nodeSets: + - name: default + count: 1 + config: + # This setting could have performance implications for production clusters. + # See: https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-virtual-memory.html + node.store.allow_mmap: false +--- +apiVersion: kibana.k8s.elastic.co/v1 +kind: Kibana +metadata: + name: kibana-sample +spec: + version: 7.6.0 + count: 1 + elasticsearchRef: + name: "elasticsearch-sample" + #http: + # service: + # spec: + # type: LoadBalancer + # this shows how to customize the Kibana pod + # with labels and resource limits + podTemplate: + metadata: + labels: + foo: bar + spec: + containers: + - name: kibana + resources: + limits: + memory: 1Gi + cpu: 1 +--- +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: beat-sample +spec: + type: filebeat + version: 7.6.0 + elasticsearchRef: + name: elasticsearch-sample diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 993a62e77b..fe32777f4d 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -42,6 +42,24 @@ webhooks: - UPDATE resources: - apmservers +- clientConfig: + caBundle: Cg== + service: + name: webhook-service + namespace: system + path: /validate-beat-k8s-elastic-co-v1beta1-beat + failurePolicy: Ignore + name: elastic-beat-validation-v1beta1.k8s.elastic.co + rules: + - apiGroups: + - beat.k8s.elastic.co + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - beats - clientConfig: caBundle: Cg== service: diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index 53e8550252..6d419c692e 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -14,6 +14,7 @@ endif::[] .Packages - xref:{anchor_prefix}-apm-k8s-elastic-co-v1[$$apm.k8s.elastic.co/v1$$] - xref:{anchor_prefix}-apm-k8s-elastic-co-v1beta1[$$apm.k8s.elastic.co/v1beta1$$] +- xref:{anchor_prefix}-beat-k8s-elastic-co-v1beta1[$$beat.k8s.elastic.co/v1beta1$$] - xref:{anchor_prefix}-common-k8s-elastic-co-v1[$$common.k8s.elastic.co/v1$$] - xref:{anchor_prefix}-common-k8s-elastic-co-v1beta1[$$common.k8s.elastic.co/v1beta1$$] - xref:{anchor_prefix}-elasticsearch-k8s-elastic-co-v1[$$elasticsearch.k8s.elastic.co/v1$$] @@ -130,6 +131,118 @@ ApmServerSpec holds the specification of an APM Server. +[id="{anchor_prefix}-beat-k8s-elastic-co-v1beta1"] +== beat.k8s.elastic.co/v1beta1 + +Package v1beta1 contains API Schema definitions for the beat v1beta1 API group + +.Resource Types +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat[$$Beat$$] +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatlist[$$BeatList$$] + + + +[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat"] +=== Beat + +Beat is the Schema for the beats API + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatlist[$$BeatList$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`apiVersion`* __string__ | `beat.k8s.elastic.co/v1beta1` +| *`kind`* __string__ | `Beat` +| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. + +| *`spec`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatspec[$$BeatSpec$$]__ | +|=== + + +[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatlist"] +=== BeatList + +BeatList contains a list of Beat + + + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`apiVersion`* __string__ | `beat.k8s.elastic.co/v1beta1` +| *`kind`* __string__ | `BeatList` +| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#listmeta-v1-meta[$$ListMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. + +| *`items`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat[$$Beat$$]__ | +|=== + + +[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatspec"] +=== BeatSpec + +BeatSpec defines the desired state of a Beat. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat[$$Beat$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type is the type of the Beat to deploy. Any string can be used, but well-known types will be recognized and will allow to provide sane default configurations. +| *`version`* __string__ | Version of the Beat. +| *`elasticsearchRef`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-objectselector[$$ObjectSelector$$]__ | ElasticsearchRef is a reference to an Elasticsearch cluster running in the same Kubernetes cluster. +| *`image`* __string__ | Image is the Beat Docker image to deploy. Version has to match the Beat in the image. +| *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the Beat configuration. If provided, it will override the default configuration. +| *`serviceAccountName`* __string__ | ServiceAccountName is used to check access from the current resource to Elasticsearch resource in a different namespace. Can only be used if ECK is enforcing RBAC on references. +| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet field allows to: 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the DaemonSet At most one of DaemonSet and Deployment can be used. +| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment field allows to: 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the Deployment At most one of DaemonSet and Deployment can be used. +|=== + + + + +[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec"] +=== DaemonSetSpec + + + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatspec[$$BeatSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`podTemplate`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#podtemplatespec-v1-core[$$PodTemplateSpec$$]__ | +|=== + + +[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec"] +=== DeploymentSpec + + + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatspec[$$BeatSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`podTemplate`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#podtemplatespec-v1-core[$$PodTemplateSpec$$]__ | +| *`replicas`* __integer__ | +|=== + + + [id="{anchor_prefix}-common-k8s-elastic-co-v1"] == common.k8s.elastic.co/v1 @@ -145,6 +258,7 @@ Config represents untyped YAML configuration. .Appears In: **** - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-apm-v1-apmserverspec[$$ApmServerSpec$$] +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatspec[$$BeatSpec$$] - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchspec[$$EnterpriseSearchSpec$$] - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-kibana-v1-kibanaspec[$$KibanaSpec$$] - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-elasticsearch-v1-nodeset[$$NodeSet$$] @@ -199,6 +313,7 @@ ObjectSelector defines a reference to a Kubernetes object. .Appears In: **** - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-apm-v1-apmserverspec[$$ApmServerSpec$$] +- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatspec[$$BeatSpec$$] - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-enterprisesearch-v1beta1-enterprisesearchspec[$$EnterpriseSearchSpec$$] - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-kibana-v1-kibanaspec[$$KibanaSpec$$] - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-elasticsearch-v1-remotecluster[$$RemoteCluster$$] diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go new file mode 100644 index 0000000000..a6e6c46f54 --- /dev/null +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -0,0 +1,143 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" +) + +// BeatSpec defines the desired state of a Beat. +type BeatSpec struct { + // Type is the type of the Beat to deploy. Any string can be used, but well-known types will be recognized and + // will allow to provide sane default configurations. + Type string `json:"type"` + + // Version of the Beat. + Version string `json:"version"` + + // ElasticsearchRef is a reference to an Elasticsearch cluster running in the same Kubernetes cluster. + // +kubebuilder:validation:Optional + ElasticsearchRef commonv1.ObjectSelector `json:"elasticsearchRef,omitempty"` + + // Image is the Beat Docker image to deploy. Version has to match the Beat in the image. + // +kubebuilder:validation:Optional + Image string `json:"image,omitempty"` + + // Config holds the Beat configuration. If provided, it will override the default configuration. + // +kubebuilder:validation:Optional + Config *commonv1.Config `json:"config,omitempty"` + + // ServiceAccountName is used to check access from the current resource to Elasticsearch resource in a different namespace. + // Can only be used if ECK is enforcing RBAC on references. + // +kubebuilder:validation:Optional + ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // DaemonSet field allows to: + // 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the + // Type is used + // 2. Provide spec for the DaemonSet + // At most one of DaemonSet and Deployment can be used. + // +kubebuilder:validation:Optional + DaemonSet *DaemonSetSpec `json:"daemonSet,omitempty"` + + // Deployment field allows to: + // 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the + // Type is used + // 2. Provide spec for the Deployment + // At most one of DaemonSet and Deployment can be used. + // +kubebuilder:validation:Optional + Deployment *DeploymentSpec `json:"deployment,omitempty"` +} + +type DaemonSetSpec struct { + PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"` +} + +type DeploymentSpec struct { + PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"` + Replicas *int32 `json:"replicas,omitempty"` +} + +// BeatStatus defines the observed state of Beat +type BeatStatus struct { + // +kubebuilder:validation:Optional + commonv1.ReconcilerStatus `json:",inline"` + + // +kubebuilder:validation:Optional + ExpectedNodes int32 `json:"expectedNodes,omitempty"` + + // +kubebuilder:validation:Optional + Health health.BeatHealth `json:"health,omitempty"` + + // +kubebuilder:validation:Optional + Association commonv1.AssociationStatus `json:"associationStatus,omitempty"` +} + +// +kubebuilder:object:root=true + +// Beat is the Schema for the beats API +// +kubebuilder:resource:categories=elastic,shortName=beat +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="health",type="string",JSONPath=".status.health" +// +kubebuilder:printcolumn:name="available",type="integer",JSONPath=".status.availableNodes",description="Available nodes" +// +kubebuilder:printcolumn:name="expected",type="integer",JSONPath=".status.expectedNodes",description="Expected nodes" +// +kubebuilder:printcolumn:name="type",type="string",JSONPath=".spec.type",description="Beat type" +// +kubebuilder:printcolumn:name="version",type="string",JSONPath=".spec.version",description="Beat version" +// +kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:storageversion +type Beat struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BeatSpec `json:"spec,omitempty"` + Status BeatStatus `json:"status,omitempty"` + assocConf *commonv1.AssociationConf `json:"-"` //nolint:govet +} + +// IsMarkedForDeletion returns true if the Beat is going to be deleted +func (b *Beat) IsMarkedForDeletion() bool { + return !b.DeletionTimestamp.IsZero() +} + +func (b *Beat) ServiceAccountName() string { + return b.Spec.ServiceAccountName +} + +func (b *Beat) ElasticsearchRef() commonv1.ObjectSelector { + return b.Spec.ElasticsearchRef +} + +func (b *Beat) AssociationConf() *commonv1.AssociationConf { + return b.assocConf +} + +func (b *Beat) SetAssociationConf(assocConf *commonv1.AssociationConf) { + b.assocConf = assocConf +} + +func (b *Beat) AssociationStatus() commonv1.AssociationStatus { + return b.Status.Association +} + +func (b *Beat) SetAssociationStatus(status commonv1.AssociationStatus) { + b.Status.Association = status +} + +// +kubebuilder:object:root=true + +// BeatList contains a list of Beat +type BeatList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Beat `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Beat{}, &BeatList{}) +} diff --git a/pkg/apis/beat/v1beta1/doc.go b/pkg/apis/beat/v1beta1/doc.go new file mode 100644 index 0000000000..3564a118b0 --- /dev/null +++ b/pkg/apis/beat/v1beta1/doc.go @@ -0,0 +1,11 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// Package v1beta1 contains API Schema definitions for the beat v1beta1 API group +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=github.com/elastic/cloud-on-k8s/pkg/apis/beat +// +k8s:defaulter-gen=TypeMeta +// +groupName=beat.k8s.elastic.co +package v1beta1 diff --git a/pkg/apis/beat/v1beta1/groupversion_info.go b/pkg/apis/beat/v1beta1/groupversion_info.go new file mode 100644 index 0000000000..7b3ada90d8 --- /dev/null +++ b/pkg/apis/beat/v1beta1/groupversion_info.go @@ -0,0 +1,21 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "beat.k8s.elastic.co", Version: "v1beta1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme is required by pkg/client/... + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/pkg/apis/beat/v1beta1/webhook.go b/pkg/apis/beat/v1beta1/webhook.go new file mode 100644 index 0000000000..f4026d364c --- /dev/null +++ b/pkg/apis/beat/v1beta1/webhook.go @@ -0,0 +1,121 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package v1beta1 + +import ( + "errors" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/version" +) + +var ( + groupKind = schema.GroupKind{Group: GroupVersion.Group, Kind: "Beat"} + validationLog = logf.Log.WithName("beat-v1beta1-validation") + + defaultChecks = []func(*Beat) field.ErrorList{ + checkNoUnknownFields, + checkNameLength, + checkSupportedVersion, + checkAtMostOneDeploymentOption, + } + + updateChecks = []func(old, curr *Beat) field.ErrorList{ + checkNoDowngrade, + } +) + +// +kubebuilder:webhook:path=/validate-beat-k8s-elastic-co-v1beta1-beat,mutating=false,failurePolicy=ignore,groups=beat.k8s.elastic.co,resources=beats,verbs=create;update,versions=v1beta1,name=elastic-beat-validation-v1beta1.k8s.elastic.co + +var _ webhook.Validator = &Beat{} + +func (b *Beat) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(b). + Complete() +} + +func (b *Beat) ValidateCreate() error { + validationLog.V(1).Info("Validate create", "name", b.Name) + return b.validate(nil) +} + +func (b *Beat) ValidateDelete() error { + validationLog.V(1).Info("Validate delete", "name", b.Name) + return nil +} + +func (b *Beat) ValidateUpdate(old runtime.Object) error { + validationLog.V(1).Info("Validate update", "name", b.Name) + oldObj, ok := old.(*Beat) + if !ok { + return errors.New("cannot cast old object to Beat type") + } + + return b.validate(oldObj) +} + +func (b *Beat) validate(old *Beat) error { + var errors field.ErrorList + if old != nil { + for _, uc := range updateChecks { + if err := uc(old, b); err != nil { + errors = append(errors, err...) + } + } + + if len(errors) > 0 { + return apierrors.NewInvalid(groupKind, b.Name, errors) + } + } + + for _, dc := range defaultChecks { + if err := dc(b); err != nil { + errors = append(errors, err...) + } + } + + if len(errors) > 0 { + return apierrors.NewInvalid(groupKind, b.Name, errors) + } + return nil +} + +func checkNoUnknownFields(b *Beat) field.ErrorList { + return commonv1.NoUnknownFields(b, b.ObjectMeta) +} + +func checkNameLength(ent *Beat) field.ErrorList { + return commonv1.CheckNameLength(ent) +} + +func checkSupportedVersion(b *Beat) field.ErrorList { + return commonv1.CheckSupportedStackVersion(b.Spec.Version, version.SupportedBeatVersions) +} + +func checkAtMostOneDeploymentOption(b *Beat) field.ErrorList { + if b.Spec.DaemonSet != nil && b.Spec.Deployment != nil { + msg := fmt.Sprintf("Specify either daemonSet or deployment, not both") + return field.ErrorList{ + field.Forbidden(field.NewPath("spec").Child("daemonSet"), msg), + field.Forbidden(field.NewPath("spec").Child("deployment"), msg), + } + } + + return nil +} + +func checkNoDowngrade(prev, curr *Beat) field.ErrorList { + return commonv1.CheckNoDowngrade(prev.Spec.Version, curr.Spec.Version) +} diff --git a/pkg/apis/beat/v1beta1/zz_generated.deepcopy.go b/pkg/apis/beat/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000000..93e1f0eab8 --- /dev/null +++ b/pkg/apis/beat/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,161 @@ +// +build !ignore_autogenerated + +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Beat) DeepCopyInto(out *Beat) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + if in.assocConf != nil { + in, out := &in.assocConf, &out.assocConf + *out = new(v1.AssociationConf) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Beat. +func (in *Beat) DeepCopy() *Beat { + if in == nil { + return nil + } + out := new(Beat) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Beat) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BeatList) DeepCopyInto(out *BeatList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Beat, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeatList. +func (in *BeatList) DeepCopy() *BeatList { + if in == nil { + return nil + } + out := new(BeatList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BeatList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BeatSpec) DeepCopyInto(out *BeatSpec) { + *out = *in + out.ElasticsearchRef = in.ElasticsearchRef + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = (*in).DeepCopy() + } + if in.DaemonSet != nil { + in, out := &in.DaemonSet, &out.DaemonSet + *out = new(DaemonSetSpec) + (*in).DeepCopyInto(*out) + } + if in.Deployment != nil { + in, out := &in.Deployment, &out.Deployment + *out = new(DeploymentSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeatSpec. +func (in *BeatSpec) DeepCopy() *BeatSpec { + if in == nil { + return nil + } + out := new(BeatSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BeatStatus) DeepCopyInto(out *BeatStatus) { + *out = *in + out.ReconcilerStatus = in.ReconcilerStatus +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeatStatus. +func (in *BeatStatus) DeepCopy() *BeatStatus { + if in == nil { + return nil + } + out := new(BeatStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DaemonSetSpec) DeepCopyInto(out *DaemonSetSpec) { + *out = *in + in.PodTemplate.DeepCopyInto(&out.PodTemplate) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DaemonSetSpec. +func (in *DaemonSetSpec) DeepCopy() *DaemonSetSpec { + if in == nil { + return nil + } + out := new(DaemonSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) { + *out = *in + in.PodTemplate.DeepCopyInto(&out.PodTemplate) + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec. +func (in *DeploymentSpec) DeepCopy() *DeploymentSpec { + if in == nil { + return nil + } + out := new(DeploymentSpec) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/controller/association/controller/beat_es.go b/pkg/controller/association/controller/beat_es.go new file mode 100644 index 0000000000..4081b560e2 --- /dev/null +++ b/pkg/controller/association/controller/beat_es.go @@ -0,0 +1,42 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package controller + +import ( + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/manager" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/association" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/operator" + "github.com/elastic/cloud-on-k8s/pkg/utils/rbac" +) + +const ( + // BeatESAssociationLabelName marks resources created by this controller for easier retrieval. + BeatESAssociationLabelName = "beatassociation.k8s.elastic.co/name" + + // BeatESAssociationLabelNamespace marks resources created by this controller for easier retrieval. + BeatESAssociationLabelNamespace = "beatassociation.k8s.elastic.co/namespace" +) + +func AddBeatES(mgr manager.Manager, accessReviewer rbac.AccessReviewer, params operator.Parameters) error { + return association.AddAssociationController(mgr, accessReviewer, params, association.AssociationInfo{ + AssociatedObjTemplate: func() commonv1.Associated { return &beatv1beta1.Beat{} }, + AssociationName: "beat-es", + AssociatedShortName: "beat", + AssociationLabels: func(associated types.NamespacedName) map[string]string { + return map[string]string{ + BeatESAssociationLabelName: associated.Name, + BeatESAssociationLabelNamespace: associated.Namespace, + } + }, + UserSecretSuffix: "beat-user", + ESUserRole: func(commonv1.Associated) (s string, e error) { + return "superuser", nil + }, + }) +} diff --git a/pkg/controller/beat/beat_controller.go b/pkg/controller/beat/beat_controller.go new file mode 100644 index 0000000000..53b16ffe9a --- /dev/null +++ b/pkg/controller/beat/beat_controller.go @@ -0,0 +1,279 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "context" + + "go.elastic.co/apm" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + "github.com/elastic/cloud-on-k8s/pkg/controller/association" + "github.com/elastic/cloud-on-k8s/pkg/controller/common" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/annotation" + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/filebeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/otherbeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/events" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/operator" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/watches" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +const ( + controllerName = "beat-controller" +) + +var log = logf.Log.WithName(controllerName) + +// Add creates a new Beat Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller +// and Start it when the Manager is Started. +func Add(mgr manager.Manager, params operator.Parameters) error { + r := newReconciler(mgr, params) + c, err := common.NewController(mgr, controllerName, r, params) + if err != nil { + return err + } + return addWatches(c, r) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileBeat { + client := k8s.WrapClient(mgr.GetClient()) + return &ReconcileBeat{ + Client: client, + recorder: mgr.GetEventRecorderFor(controllerName), + dynamicWatches: watches.NewDynamicWatches(), + Parameters: params, + } +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func addWatches(c controller.Controller, r *ReconcileBeat) error { + // Watch for changes to Beat + if err := c.Watch(&source.Kind{Type: &beatv1beta1.Beat{}}, &handler.EnqueueRequestForObject{}); err != nil { + return err + } + + if err := c.Watch(&source.Kind{Type: &appsv1.DaemonSet{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &beatv1beta1.Beat{}, + }); err != nil { + return err + } + + if err := c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &beatv1beta1.Beat{}, + }); err != nil { + return err + } + + // Watch secrets + if err := c.Watch(&source.Kind{Type: &corev1.Secret{}}, r.dynamicWatches.Secrets); err != nil { + return err + } + if err := r.dynamicWatches.Secrets.AddHandler(&watches.OwnerWatch{ + EnqueueRequestForOwner: handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &beatv1beta1.Beat{}, + }, + }); err != nil { + return err + } + + if commonbeat.ShouldSetupAutodiscoveryRBAC() { + if err := c.Watch(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &beatv1beta1.Beat{}, + }); err != nil { + return err + } + + if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRole{}}, &handler.EnqueueRequestForObject{}); err != nil { + return err + } + + if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRoleBinding{}}, &handler.EnqueueRequestForObject{}); err != nil { + return err + } + } + + return nil +} + +var _ reconcile.Reconciler = &ReconcileBeat{} + +// ReconcileBeat reconciles a Beat object +type ReconcileBeat struct { + k8s.Client + recorder record.EventRecorder + dynamicWatches watches.DynamicWatches + operator.Parameters + // iteration is the number of times this controller has run its Reconcile method + iteration uint64 +} + +// Reconcile reads that state of the cluster for a Beat object and makes changes based on the state read +// and what is in the Beat.Spec +func (r *ReconcileBeat) Reconcile(request reconcile.Request) (reconcile.Result, error) { + defer common.LogReconciliationRun(log, request, "beat_name", &r.iteration)() + tx, ctx := tracing.NewTransaction(r.Tracer, request.NamespacedName, "beat") + defer tracing.EndTransaction(tx) + + var beat beatv1beta1.Beat + if err := association.FetchWithAssociation(ctx, r.Client, request, &beat); err != nil { + if apierrors.IsNotFound(err) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, tracing.CaptureError(ctx, err) + } + + if common.IsUnmanaged(&beat) { + log.Info("Object is currently not managed by this controller. Skipping reconciliation", "namespace", beat.Namespace, "ent_name", beat.Name) + return reconcile.Result{}, nil + } + + if compatible, err := r.isCompatible(ctx, &beat); err != nil || !compatible { + return reconcile.Result{}, tracing.CaptureError(ctx, err) + } + + if beat.IsMarkedForDeletion() { + return reconcile.Result{}, nil + } + + if err := annotation.UpdateControllerVersion(ctx, r.Client, &beat, r.OperatorInfo.BuildInfo.Version); err != nil { + return reconcile.Result{}, tracing.CaptureError(ctx, err) + } + + res, err := r.doReconcile(ctx, beat).Aggregate() + k8s.EmitErrorEvent(r.recorder, err, &beat, events.EventReconciliationError, "Reconciliation error: %v", err) + + return res, err +} + +func (r *ReconcileBeat) doReconcile(ctx context.Context, beat beatv1beta1.Beat) *reconciler.Results { + results := reconciler.NewResult(ctx) + if !association.IsConfiguredIfSet(&beat, r.recorder) { + return results + } + + // Run validation in case the webhook is disabled + if err := r.validate(ctx, &beat); err != nil { + return results.WithError(err) + } + + result := newDriver(ctx, r.Client, beat).Reconcile() + results.WithResults(result.Results) + + err := r.updateStatus(result.Status, beat) + results.WithError(err) + if err != nil && apierrors.IsConflict(err) { + log.V(1).Info("Conflict while updating status", "namespace", beat.Namespace, "beat_name", beat.Name) + results.WithResult(reconcile.Result{Requeue: true}) + } + + return results +} + +func (r *ReconcileBeat) validate(ctx context.Context, beat *beatv1beta1.Beat) error { + span, vctx := apm.StartSpan(ctx, "validate", tracing.SpanTypeApp) + defer span.End() + + if err := beat.ValidateCreate(); err != nil { + log.Error(err, "Validation failed") + k8s.EmitErrorEvent(r.recorder, err, beat, events.EventReasonValidation, err.Error()) + return tracing.CaptureError(vctx, err) + } + + return nil +} + +func (r *ReconcileBeat) updateStatus(driverStatus *commonbeat.DriverStatus, beat beatv1beta1.Beat) error { + if driverStatus == nil { + return nil + } + + beat.Status.AvailableNodes = driverStatus.AvailableNodes + beat.Status.ExpectedNodes = driverStatus.ExpectedNodes + beat.Status.Health = driverStatus.Health + beat.Status.Association = driverStatus.Association + + return r.Client.Status().Update(&beat) +} + +func (r *ReconcileBeat) isCompatible(ctx context.Context, beat *beatv1beta1.Beat) (bool, error) { + selector := map[string]string{NameLabelName: beat.Name} + compat, err := annotation.ReconcileCompatibility(ctx, r.Client, beat, selector, r.OperatorInfo.BuildInfo.Version) + if err != nil { + k8s.EmitErrorEvent(r.recorder, err, beat, events.EventCompatCheckError, "Error during compatibility check: %v", err) + } + return compat, err +} + +func newDriver(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) commonbeat.Driver { + dp := newDriverParams(ctx, client, beat) + + switch dp.Type { + case string(filebeat.Type): + return filebeat.NewDriver(dp) + default: + return otherbeat.NewDriver(dp) + } +} + +func newDriverParams(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) commonbeat.DriverParams { + spec := beat.Spec + + var ds commonbeat.DaemonSetSpec + if spec.DaemonSet != nil { + ds = commonbeat.DaemonSetSpec{PodTemplate: spec.DaemonSet.PodTemplate} + } + var d commonbeat.DeploymentSpec + if spec.Deployment != nil { + d = commonbeat.DeploymentSpec{Replicas: spec.Deployment.Replicas, PodTemplate: spec.Deployment.PodTemplate} + } + + return commonbeat.DriverParams{ + Client: client, + Context: ctx, + Logger: log, + + Namer: &Namer{}, + Owner: &beat, + Associated: &beat, + + Type: spec.Type, + Version: spec.Version, + ElasticsearchRef: spec.ElasticsearchRef, + Image: spec.Image, + Config: spec.Config, + ServiceAccountName: spec.ServiceAccountName, + + DaemonSet: ds, + Deployment: d, + Labels: map[string]string{ + common.TypeLabelName: Type, + NameLabelName: beat.Name, + }, + Selectors: map[string]string{ + common.TypeLabelName: Type, + NameLabelName: beat.Name, + }, + } +} diff --git a/pkg/controller/beat/labels.go b/pkg/controller/beat/labels.go new file mode 100644 index 0000000000..5484cf29eb --- /dev/null +++ b/pkg/controller/beat/labels.go @@ -0,0 +1,13 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +const ( + // Type represents the Beat type. + Type = "beat" + + // NameLabelName is used to represent a Beat in k8s resources. + NameLabelName = "beat.k8s.elastic.co/name" +) diff --git a/pkg/controller/beat/name.go b/pkg/controller/beat/name.go new file mode 100644 index 0000000000..23b6c914bc --- /dev/null +++ b/pkg/controller/beat/name.go @@ -0,0 +1,26 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + common_name "github.com/elastic/cloud-on-k8s/pkg/controller/common/name" +) + +// namer is a Namer that is configured with the defaults for resources related to a Beat resource. +var namer = common_name.NewNamer("beat") + +type Namer struct { +} + +func (bn *Namer) ConfigSecretName(typeName, name string) string { + return namer.Suffix(name, typeName, "config") +} + +func (bn *Namer) Name(typeName, name string) string { + return namer.Suffix(name, typeName) +} + +var _ commonbeat.Namer = &Namer{} diff --git a/pkg/controller/common/beat/autodiscovery.go b/pkg/controller/common/beat/autodiscovery.go new file mode 100644 index 0000000000..3b5dbb139a --- /dev/null +++ b/pkg/controller/common/beat/autodiscovery.go @@ -0,0 +1,145 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "context" + + "go.elastic.co/apm" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/elastic/cloud-on-k8s/pkg/utils/maps" +) + +const ( + AutodiscoveryServiceAccountName = "elastic-operator-autodiscovery" + autodiscoveryClusterRoleBindingName = "elastic-operator-autodiscovery" + autodiscoveryClusterRoleName = "elastic-operator-autodiscovery" +) + +var ( + shouldSetupRBAC = true +) + +// DisableAutodiscoveryRBACSetup disables setting up autodiscovery RBAC +func DisableAutodiscoveryRBACSetup() { + shouldSetupRBAC = false +} + +// ShouldSetupAutodiscoveryRBAC returns true if autodiscovery RBAC is expected to be set up by the operator +func ShouldSetupAutodiscoveryRBAC() bool { + return shouldSetupRBAC +} + +// SetupAutodiscoveryRBAC reconciles all resources needed for default RBAC setup +func SetupAutodiscoveryRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { + // this is the source of truth and should be respected at all times + if !ShouldSetupAutodiscoveryRBAC() { + return nil + } + + span, _ := apm.StartSpan(ctx, "reconcile_autodiscovery_rbac", tracing.SpanTypeApp) + defer span.End() + + if err := reconcileServiceAccount(client, owner, labels); err != nil { + return err + } + + if err := reconcileClusterRole(client); err != nil { + return err + } + + if err := reconcileClusterRoleBinding(client, owner); err != nil { + return err + } + + return nil +} + +func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[string]string) error { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: AutodiscoveryServiceAccountName, + Namespace: owner.GetNamespace(), + Labels: labels, + }, + } + + return reconcile(client, sa, owner) +} + +func reconcileClusterRole(client k8s.Client) error { + role := &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: autodiscoveryClusterRoleName, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Verbs: []string{"get", "watch", "list"}, + Resources: []string{"namespaces", "pods"}, + }, + }, + } + + return reconcile(client, role, nil) +} + +func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { + binding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: autodiscoveryClusterRoleBindingName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: AutodiscoveryServiceAccountName, + Namespace: owner.GetNamespace(), + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: autodiscoveryClusterRoleName, + }, + } + + return reconcile(client, binding, nil) +} + +func reconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error { + // labels set here must have be exact the same for all callers in particular namespace + // otherwise they'll just keep trying to override each other + objMeta, err := meta.Accessor(obj) + if err != nil { + return err + } + + objMeta.SetLabels(maps.Merge(objMeta.GetLabels(), hash.SetTemplateHashLabel(nil, obj))) + + obj2 := obj.DeepCopyObject() + return reconciler.ReconcileResource(reconciler.Params{ + Client: client, + Owner: owner, + Expected: obj, + Reconciled: obj2, + NeedsUpdate: func() bool { + objMeta2, err := meta.Accessor(obj2) + + // compare hash of the deployment at the time it was built + return err != nil || hash.GetTemplateHashLabel(objMeta.GetLabels()) != hash.GetTemplateHashLabel(objMeta2.GetLabels()) + }, + UpdateReconciled: func() { + }, + }) +} diff --git a/pkg/controller/common/beat/common.go b/pkg/controller/common/beat/common.go new file mode 100644 index 0000000000..38c1cb3db4 --- /dev/null +++ b/pkg/controller/common/beat/common.go @@ -0,0 +1,75 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "context" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +type Type string + +type Driver interface { + Reconcile() DriverResults +} + +type DaemonSetSpec struct { + PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"` +} + +type DeploymentSpec struct { + PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"` + Replicas *int32 `json:"replicas,omitempty"` +} + +type DriverParams struct { + Client k8s.Client + Context context.Context + Logger logr.Logger + + Owner metav1.Object + Associated commonv1.Associated + Namer Namer + + Type string + Version string + ElasticsearchRef commonv1.ObjectSelector + Image string + Config *commonv1.Config + ServiceAccountName string + + Labels map[string]string + + DaemonSet DaemonSetSpec + Deployment DeploymentSpec + Selectors map[string]string +} + +type DriverResults struct { + *reconciler.Results + Status *DriverStatus +} + +func NewDriverResults(ctx context.Context) DriverResults { + return DriverResults{ + Status: nil, + Results: reconciler.NewResult(ctx), + } +} + +type DriverStatus struct { + ExpectedNodes int32 + AvailableNodes int32 + Health health.BeatHealth + Association commonv1.AssociationStatus +} diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go new file mode 100644 index 0000000000..9aceba0eb8 --- /dev/null +++ b/pkg/controller/common/beat/config.go @@ -0,0 +1,47 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "path" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/association" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +const ( + CAMountPath = "/mnt/elastic-internal/es-certs/" + CAFileName = "ca.crt" + + // ConfigChecksumLabel is a label used to store beats config checksum. + ConfigChecksumLabel = "beat.k8s.elastic.co/config-checksum" + + // VersionLabelName is a label used to track the version of a Beat Pod. + VersionLabelName = "beat.k8s.elastic.co/version" +) + +// SetOutput will set output section in Beat config according to association configuration. +func SetOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { + if associated.AssociationConf().IsConfigured() { + username, password, err := association.ElasticsearchAuthSettings(client, associated) + if err != nil { + return err + } + + return cfg.MergeWith(settings.MustCanonicalConfig( + map[string]interface{}{ + "output.elasticsearch": map[string]interface{}{ + "hosts": []string{associated.AssociationConf().GetURL()}, + "username": username, + "password": password, + "ssl.certificate_authorities": path.Join(CAMountPath, CAFileName), + }, + })) + } + + return nil +} diff --git a/pkg/controller/common/beat/filebeat/config.go b/pkg/controller/common/beat/filebeat/config.go new file mode 100644 index 0000000000..5e74a7f808 --- /dev/null +++ b/pkg/controller/common/beat/filebeat/config.go @@ -0,0 +1,128 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package filebeat + +import ( + "context" + "hash" + + "go.elastic.co/apm" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common" + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +var ( + filebeatDefaultConfig = settings.MustCanonicalConfig(map[string]interface{}{ + "filebeat": map[string]interface{}{ + "autodiscover": map[string]interface{}{ + "providers": []map[string]interface{}{ + { + "type": "kubernetes", + "node": "${NODE_NAME}", + "hints": map[string]interface{}{ + "enabled": "true", + "default_config": map[string]interface{}{ + "type": "container", + "paths": []string{"/var/log/containers/*${data.kubernetes.container.id}.log"}, + }, + }, + }, + }, + }, + }, + "processors": []map[string]interface{}{ + {"add_cloud_metadata": nil}, + {"add_host_metadata": nil}, + }, + }) +) + +func build(client k8s.Client, associated commonv1.Associated, config *commonv1.Config) ([]byte, error) { + cfg := settings.NewCanonicalConfig() + + if err := commonbeat.SetOutput(cfg, client, associated); err != nil { + return nil, err + } + + // use only the default config or only the provided config - no overriding, no merging + if config == nil { + if err := cfg.MergeWith(filebeatDefaultConfig); err != nil { + return nil, err + } + } else { + userCfg, err := settings.NewCanonicalConfigFrom(config.Data) + if err != nil { + return nil, err + } + + if err = cfg.MergeWith(userCfg); err != nil { + return nil, err + } + } + + return cfg.Render() +} + +// ReconcileConfig builds and reconciles Filebeat config based on association configured, default and user configs. +// `checksum` hash will get updated based on the config to be consumed by Filebeat. +func ReconcileConfig( + ctx context.Context, + client k8s.Client, + associated commonv1.Associated, + cfg *commonv1.Config, + owner metav1.Object, + labels map[string]string, + namer commonbeat.Namer, + checksum hash.Hash) error { + span, _ := apm.StartSpan(ctx, "reconcile_config_secret", tracing.SpanTypeApp) + defer span.End() + + // build config + cfgBytes, err := build(client, associated, cfg) + if err != nil { + return err + } + + // create resource + expected := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: owner.GetNamespace(), + Name: namer.ConfigSecretName(string(Type), owner.GetName()), + Labels: common.AddCredentialsLabel(labels), + }, + Data: map[string][]byte{ + ConfigFileName: cfgBytes, + }, + } + + // reconcile + if _, err = reconciler.ReconcileSecret(client, expected, owner); err != nil { + return err + } + + // we need to deref the secret here (if any) to include it in the checksum otherwise Beat will not be rolled on contents changes + assocConf := associated.AssociationConf() + if assocConf.AuthIsConfigured() { + esAuthKey := types.NamespacedName{Name: assocConf.GetAuthSecretName(), Namespace: owner.GetNamespace()} + esAuthSecret := corev1.Secret{} + if err := client.Get(esAuthKey, &esAuthSecret); err != nil { + return err + } + _, _ = checksum.Write(esAuthSecret.Data[assocConf.GetAuthSecretKey()]) + } + + _, _ = checksum.Write(cfgBytes) + + return nil +} diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/common/beat/filebeat/filebeat.go new file mode 100644 index 0000000000..94b1339766 --- /dev/null +++ b/pkg/controller/common/beat/filebeat/filebeat.go @@ -0,0 +1,208 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package filebeat + +import ( + "crypto/sha256" + "fmt" + "hash" + + "go.elastic.co/apm" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" + commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" +) + +const ( + Type commonbeat.Type = "filebeat" + + HostContainersVolumeName = "varlibdockercontainers" + HostContainersPath = "/var/lib/docker/containers" + HostContainersMountPath = "/var/lib/docker/containers" + + HostContainersLogsVolumeName = "varlogcontainers" + HostContainersLogsPath = "/var/log/containers" + HostContainersLogsMountPath = "/var/log/containers" + + HostPodsLogsVolumeName = "varlogpods" + HostPodsLogsPath = "/var/log/pods" + HostPodsLogsMountPath = "/var/log/pods" + + HostFilebeatDataVolumeName = "data" + HostFilebeatDataPathTemplate = "/var/lib/%s/%s/filebeat-data" + HostFilebeatDataMountPath = "/usr/share/filebeat/data" + + ConfigVolumeName = "config" + ConfigFileName = "filebeat.yml" + ConfigMountPath = "/etc/filebeat.yml" + + CAVolumeName = "es-certs" +) + +var ( + defaultResources = corev1.ResourceRequirements{ + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + } +) + +type Driver struct { + commonbeat.DriverParams + commonbeat.Driver +} + +func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { + return &Driver{DriverParams: params} +} + +func (fd *Driver) Reconcile() commonbeat.DriverResults { + results := commonbeat.NewDriverResults(fd.Context) + + // setup sa, role, rolebinding for autodiscovery if required + if commonbeat.ShouldSetupAutodiscoveryRBAC() { + if err := commonbeat.SetupAutodiscoveryRBAC(fd.Context, fd.Client, fd.Owner, fd.Labels); err != nil { + results.WithError(err) + fd.Logger.V(1).Info( + "autodiscovery rbac setup failed", + "namespace", fd.Owner.GetNamespace(), + "beat_name", fd.Owner.GetName()) + } + } + + checksum := sha256.New224() + // reconcile config + err := ReconcileConfig(fd.Context, fd.Client, fd.Associated, fd.Config, fd.Owner, fd.Labels, fd.Namer, checksum) + if err != nil { + results.WithError(err) + return results + } + + // reconcile pod vehicle + if driverStatus, err := doReconcile(fd.DriverParams, checksum); err != nil { + if apierrors.IsConflict(err) { + fd.Logger.V(1).Info("Conflict while updating") + results.WithResult(reconcile.Result{Requeue: true}) + } + results.WithError(err) + } else { + results.Status = &driverStatus + } + + return results +} + +func doReconcile(dp commonbeat.DriverParams, checksum hash.Hash) (commonbeat.DriverStatus, error) { + span, _ := apm.StartSpan(dp.Context, "reconcile_daemonSet", tracing.SpanTypeApp) + defer span.End() + + podTemplate := dp.DaemonSet.PodTemplate + + builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). + WithTerminationGracePeriod(30). + WithDockerImage(dp.Image, container.ImageRepository(container.FilebeatImage, dp.Version)). + WithEnv(corev1.EnvVar{ + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }}). + WithResources(defaultResources). + WithArgs("-e", "-c", ConfigMountPath). + WithHostNetwork(). + WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). + WithSecurityContext(corev1.SecurityContext{ + RunAsUser: pointer.Int64(0), + }). + WithAutomountServiceAccountToken(). + WithLabels(map[string]string{ + commonbeat.ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), + commonbeat.VersionLabelName: dp.Version}) + + // If SA is already provided, assume that for this resource (despite operator configuration) the user took the + // responsibility of configuring RBAC. Otherwise, use the default. + if commonbeat.ShouldSetupAutodiscoveryRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { + builder.WithServiceAccount(commonbeat.AutodiscoveryServiceAccountName) + } + + containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) + containersLogsVolume := volume.NewReadOnlyHostVolume(HostContainersLogsVolumeName, HostContainersLogsPath, HostContainersLogsMountPath) + podsLogsVolume := volume.NewReadOnlyHostVolume(HostPodsLogsVolumeName, HostPodsLogsPath, HostPodsLogsMountPath) + + hostFilebeatDataPath := fmt.Sprintf(HostFilebeatDataPathTemplate, dp.Owner.GetNamespace(), dp.Owner.GetName()) + filebeatDataVolume := volume.NewHostVolume( + HostFilebeatDataVolumeName, + hostFilebeatDataPath, + HostFilebeatDataMountPath, + false, + corev1.HostPathDirectoryOrCreate) + + configVolume := volume.NewSecretVolume( + dp.Namer.ConfigSecretName(string(Type), dp.Owner.GetName()), + ConfigVolumeName, + ConfigMountPath, + ConfigFileName, + 0600) + + volumes := []volume.VolumeLike{ + containersVolume, + containersLogsVolume, + podsLogsVolume, + filebeatDataVolume, + configVolume, + } + + if dp.Associated.AssociationConf().IsConfigured() { + volumes = append(volumes, volume.NewSelectiveSecretVolumeWithMountPath( + dp.Associated.AssociationConf().CASecretName, + CAVolumeName, + commonbeat.CAMountPath, + []string{commonbeat.CAFileName})) + } + + for _, v := range volumes { + builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) + } + + builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) + ds := daemonset.New(builder.PodTemplate, dp.Namer.Name(string(Type), dp.Owner.GetName()), dp.Owner, dp.Selectors) + if err := controllerutil.SetControllerReference(dp.Owner, &ds, scheme.Scheme); err != nil { + return commonbeat.DriverStatus{}, err + } + + reconciled, err := daemonset.Reconcile(dp.Client, ds, dp.Owner) + if err != nil { + return commonbeat.DriverStatus{}, err + } + + ready := reconciled.Status.NumberReady + desired := reconciled.Status.DesiredNumberScheduled + + return commonbeat.DriverStatus{ + ExpectedNodes: desired, + AvailableNodes: ready, + Health: health.CalculateHealth(dp.Associated, ready, desired), + Association: dp.Associated.AssociationStatus(), + }, nil +} diff --git a/pkg/controller/common/beat/health/health.go b/pkg/controller/common/beat/health/health.go new file mode 100644 index 0000000000..c13e14fbdd --- /dev/null +++ b/pkg/controller/common/beat/health/health.go @@ -0,0 +1,42 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package health + +import ( + v1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" +) + +type BeatHealth string + +const ( + // BeatRedHealth means that the health is neither yellow nor green + BeatRedHealth BeatHealth = "red" + + // BeatYellowHealth means that: + // 1) at least one Pod is Ready, and + // 1) association is not configured or configured and established + BeatYellowHealth BeatHealth = "yellow" + + // BeatGreenHealth means that: + // 1) all Pods are Ready, and + // 2) association is not configured or configured and established + BeatGreenHealth BeatHealth = "green" +) + +// CalculateHealth returns health of the Beat calculated based on association status, desired count and ready count. +func CalculateHealth(associated v1.Associated, ready, desired int32) BeatHealth { + if associated.AssociationConf().IsConfigured() && associated.AssociationStatus() != v1.AssociationEstablished { + return BeatRedHealth + } + + switch { + case ready == desired: + return BeatGreenHealth + case ready > 0: + return BeatYellowHealth + default: + return BeatRedHealth + } +} diff --git a/pkg/controller/common/beat/name.go b/pkg/controller/common/beat/name.go new file mode 100644 index 0000000000..029e7e87d1 --- /dev/null +++ b/pkg/controller/common/beat/name.go @@ -0,0 +1,13 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +type Namer interface { + // ConfigSecretName returns name of the Secret that hold configuration for a Beat. + ConfigSecretName(typeName, name string) string + + // Name returns name of the Beat resource. + Name(typeName, name string) string +} diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go new file mode 100644 index 0000000000..ba368cde24 --- /dev/null +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -0,0 +1,221 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package otherbeat + +import ( + "context" + "crypto/sha256" + "fmt" + "hash" + + "go.elastic.co/apm" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + v1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common" + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" + commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" +) + +const ( + Type commonbeat.Type = "otherbeat" +) + +var ( + defaultResources = corev1.ResourceRequirements{ + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + } +) + +var ConfigMountPath = "/etc/otherbeat.yml" + +type Driver struct { + commonbeat.DriverParams + commonbeat.Driver +} + +func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { + return &Driver{DriverParams: params} +} + +func (fd *Driver) Reconcile() commonbeat.DriverResults { + // this is a poc to show how it could look like, todo should be refactored + results := commonbeat.NewDriverResults(fd.Context) + + cfg := settings.NewCanonicalConfig() + + if err := commonbeat.SetOutput(cfg, fd.Client, fd.Associated); err != nil { + results.WithError(err) + } + + userCfg, err := settings.NewCanonicalConfigFrom(fd.Config.Data) + if err != nil { + results.WithError(err) + } + + if err = cfg.MergeWith(userCfg); err != nil { + results.WithError(err) + } + + cfgBytes, err := cfg.Render() + if err != nil { + results.WithError(err) + } + + // create resource + expected := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fd.Owner.GetNamespace(), + Name: fd.Namer.ConfigSecretName(string(Type), fd.Owner.GetName()), + Labels: common.AddCredentialsLabel(fd.Labels), + }, + Data: map[string][]byte{ + "otherbeat.yml": cfgBytes, + }, + } + + // reconcile + if _, err = reconciler.ReconcileSecret(fd.Client, expected, fd.Owner); err != nil { + results.WithError(err) + } + + checksum := sha256.New224() + _, _ = checksum.Write(cfgBytes) + + // reconcile pod vehicle + if driverStatus, err := doReconcile( + fd.Context, + fd.Client, + fd.Associated, + fd.Owner, + fd.Labels, + fd.Namer, + checksum, + fd.Image, + fd.Version, + fd.Selectors, + fd.Deployment); err != nil { + if apierrors.IsConflict(err) { + fd.Logger.V(1).Info("Conflict while updating") + results.WithResult(reconcile.Result{Requeue: true}) + } + results.WithError(err) + } else { + results.Status = &driverStatus + } + return results +} + +func doReconcile(ctx context.Context, + client k8s.Client, + associated v1.Associated, + owner metav1.Object, + labels map[string]string, + namer commonbeat.Namer, + checksum hash.Hash, + image, version string, + selectors map[string]string, + deploymentSpec commonbeat.DeploymentSpec, +) (commonbeat.DriverStatus, error) { + span, _ := apm.StartSpan(ctx, "reconcile_deployment", tracing.SpanTypeApp) + defer span.End() + + podTemplate := deploymentSpec.PodTemplate + + //image has to be there + + builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). + WithTerminationGracePeriod(30). + WithDockerImage(image, ""). + WithEnv(corev1.EnvVar{ + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }}). + WithResources(defaultResources). + WithArgs("-e", "-c", ConfigMountPath). + WithHostNetwork(). + WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). + WithSecurityContext(corev1.SecurityContext{ + RunAsUser: pointer.Int64(0), + }). + WithAutomountServiceAccountToken(). + WithLabels(map[string]string{ + commonbeat.ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), + commonbeat.VersionLabelName: version}) + + // If SA is already provided, assume that for this resource (despite operator configuration) the user took the + // responsibility of configuring RBAC. Otherwise, use the default. + if commonbeat.ShouldSetupAutodiscoveryRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { + builder.WithServiceAccount(commonbeat.AutodiscoveryServiceAccountName) + } + + configVolume := volume.NewSecretVolume( + namer.ConfigSecretName(string(Type), owner.GetName()), + "config", ConfigMountPath, "otherbeat.yml", 0600) + esCaVolume := volume.NewSelectiveSecretVolumeWithMountPath(associated.AssociationConf().CASecretName, "es-certs", commonbeat.CAMountPath, []string{commonbeat.CAFileName}) + + for _, v := range []volume.VolumeLike{ + configVolume, + esCaVolume, + } { + builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) + } + + builder = builder.WithLabels(commonhash.SetTemplateHashLabel(labels, builder.PodTemplate)) + + d := deployment.New(deployment.Params{ + Name: namer.Name(string(Type), owner.GetName()), + Namespace: owner.GetNamespace(), + Selector: selectors, + Labels: labels, + PodTemplateSpec: builder.PodTemplate, + Replicas: 1, + }) + + if err := controllerutil.SetControllerReference(owner, &d, scheme.Scheme); err != nil { + return commonbeat.DriverStatus{}, err + } + + // sync + reconciled, err := deployment.Reconcile(client, d, owner) + if err != nil { + return commonbeat.DriverStatus{}, err + } + + ready := reconciled.Status.ReadyReplicas + desired := reconciled.Status.Replicas + + return commonbeat.DriverStatus{ + ExpectedNodes: desired, + AvailableNodes: ready, + Health: health.CalculateHealth(associated, ready, desired), + Association: associated.AssociationStatus(), + }, nil +} diff --git a/pkg/controller/common/container/container.go b/pkg/controller/common/container/container.go index b74f8d02d4..fcb5cf4a4a 100644 --- a/pkg/controller/common/container/container.go +++ b/pkg/controller/common/container/container.go @@ -25,6 +25,7 @@ const ( KibanaImage Image = "kibana/kibana" // TODO EnterpriseSearchImage Image = "TODO" + FilebeatImage Image = "beats/filebeat" ) // ImageRepository returns the full container image name by concatenating the current container registry and the image path with the given version. diff --git a/pkg/controller/common/daemonset/reconcile.go b/pkg/controller/common/daemonset/reconcile.go new file mode 100644 index 0000000000..6b9a2fa74a --- /dev/null +++ b/pkg/controller/common/daemonset/reconcile.go @@ -0,0 +1,63 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package daemonset + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +func New(podTemplate corev1.PodTemplateSpec, name string, owner metav1.Object, selectors map[string]string) appsv1.DaemonSet { + return appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: owner.GetNamespace(), + }, + Spec: appsv1.DaemonSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: selectors, + }, + Template: podTemplate, + }, + } +} + +// Reconcile creates or updates the given daemon set for the specified owner. +func Reconcile( + k8sClient k8s.Client, + expected appsv1.DaemonSet, + owner metav1.Object, +) (appsv1.DaemonSet, error) { + // label the daemon set with a hash of itself + expected = WithTemplateHash(expected) + + reconciled := &appsv1.DaemonSet{} + err := reconciler.ReconcileResource(reconciler.Params{ + Client: k8sClient, + Owner: owner, + Expected: &expected, + Reconciled: reconciled, + NeedsUpdate: func() bool { + // compare hash of the daemon set at the time it was built + return hash.GetTemplateHashLabel(reconciled.Labels) != hash.GetTemplateHashLabel(expected.Labels) + }, + UpdateReconciled: func() { + expected.DeepCopyInto(reconciled) + }, + }) + return *reconciled, err +} + +// WithTemplateHash returns a new DaemonSet with a hash of its template to ease comparisons. +func WithTemplateHash(d appsv1.DaemonSet) appsv1.DaemonSet { + dCopy := *d.DeepCopy() + dCopy.Labels = hash.SetTemplateHashLabel(dCopy.Labels, dCopy) + return dCopy +} diff --git a/pkg/controller/common/defaults/pod_template.go b/pkg/controller/common/defaults/pod_template.go index e1fb25c308..dab18b9ff8 100644 --- a/pkg/controller/common/defaults/pod_template.go +++ b/pkg/controller/common/defaults/pod_template.go @@ -337,3 +337,34 @@ func (b *PodTemplateBuilder) WithPreStopHook(handler corev1.Handler) *PodTemplat return b } + +func (b *PodTemplateBuilder) WithArgs(args ...string) *PodTemplateBuilder { + b.Container.Args = args + return b +} + +func (b *PodTemplateBuilder) WithServiceAccount(serviceAccount string) *PodTemplateBuilder { + b.PodTemplate.Spec.ServiceAccountName = serviceAccount + return b +} + +func (b *PodTemplateBuilder) WithHostNetwork() *PodTemplateBuilder { + b.PodTemplate.Spec.HostNetwork = true + return b +} + +func (b *PodTemplateBuilder) WithDNSPolicy(dnsPolicy corev1.DNSPolicy) *PodTemplateBuilder { + b.PodTemplate.Spec.DNSPolicy = dnsPolicy + return b +} + +func (b *PodTemplateBuilder) WithSecurityContext(securityContext corev1.SecurityContext) *PodTemplateBuilder { + b.Container.SecurityContext = &securityContext + return b +} + +func (b *PodTemplateBuilder) WithAutomountServiceAccountToken() *PodTemplateBuilder { + t := true + b.PodTemplate.Spec.AutomountServiceAccountToken = &t + return b +} diff --git a/pkg/controller/common/operator/flags.go b/pkg/controller/common/operator/flags.go index 6cb2358b66..5c3b73d6b9 100644 --- a/pkg/controller/common/operator/flags.go +++ b/pkg/controller/common/operator/flags.go @@ -5,21 +5,22 @@ package operator const ( - AutoPortForwardFlag = "auto-port-forward" - CACertRotateBeforeFlag = "ca-cert-rotate-before" - CACertValidityFlag = "ca-cert-validity" - CertRotateBeforeFlag = "cert-rotate-before" - CertValidityFlag = "cert-validity" - ContainerRegistryFlag = "container-registry" - DebugHTTPListenFlag = "debug-http-listen" - EnableTracingFlag = "enable-tracing" - EnableWebhookFlag = "enable-webhook" - EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" - ManageWebhookCertsFlag = "manage-webhook-certs" - MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" - MetricsPortFlag = "metrics-port" - NamespacesFlag = "namespaces" - OperatorNamespaceFlag = "operator-namespace" - WebhookCertDirFlag = "webhook-cert-dir" - WebhookSecretFlag = "webhook-secret" + AutoPortForwardFlag = "auto-port-forward" + CACertRotateBeforeFlag = "ca-cert-rotate-before" + CACertValidityFlag = "ca-cert-validity" + CertRotateBeforeFlag = "cert-rotate-before" + CertValidityFlag = "cert-validity" + ContainerRegistryFlag = "container-registry" + DebugHTTPListenFlag = "debug-http-listen" + DisableAutodiscoveryRBACSetup = "disable-autodiscovery-rbac-setup" + EnableTracingFlag = "enable-tracing" + EnableWebhookFlag = "enable-webhook" + EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" + ManageWebhookCertsFlag = "manage-webhook-certs" + MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" + MetricsPortFlag = "metrics-port" + NamespacesFlag = "namespaces" + OperatorNamespaceFlag = "operator-namespace" + WebhookCertDirFlag = "webhook-cert-dir" + WebhookSecretFlag = "webhook-secret" ) diff --git a/pkg/controller/common/scheme/scheme.go b/pkg/controller/common/scheme/scheme.go index cb19ec711a..c27fb3c4e3 100644 --- a/pkg/controller/common/scheme/scheme.go +++ b/pkg/controller/common/scheme/scheme.go @@ -9,6 +9,7 @@ import ( apmv1 "github.com/elastic/cloud-on-k8s/pkg/apis/apm/v1" apmv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/apm/v1beta1" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" commonv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1beta1" esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" @@ -52,6 +53,10 @@ func SetupScheme() { if err != nil { panic(err) } + err = beatv1beta1.AddToScheme(clientgoscheme.Scheme) + if err != nil { + panic(err) + } }) } diff --git a/pkg/controller/common/version/version.go b/pkg/controller/common/version/version.go index 688370dc94..d9a6f751ff 100644 --- a/pkg/controller/common/version/version.go +++ b/pkg/controller/common/version/version.go @@ -18,6 +18,7 @@ var ( SupportedAPMServerVersions = MinMaxVersion{Min: From(6, 2, 0), Max: From(8, 99, 99)} SupportedEnterpriseSearchVersions = MinMaxVersion{Min: From(7, 7, 0), Max: From(8, 99, 99)} SupportedKibanaVersions = MinMaxVersion{Min: From(6, 8, 0), Max: From(8, 99, 99)} + SupportedBeatVersions = MinMaxVersion{Min: From(6, 8, 0), Max: From(8, 99, 99)} ) // MinMaxVersion holds the minimum and maximum supported versions. diff --git a/pkg/controller/common/volume/host.go b/pkg/controller/common/volume/host.go new file mode 100644 index 0000000000..ebc4b99889 --- /dev/null +++ b/pkg/controller/common/volume/host.go @@ -0,0 +1,63 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package volume + +import ( + corev1 "k8s.io/api/core/v1" +) + +// NewReadOnlyHostVolume creates a new HostVolume struct +func NewReadOnlyHostVolume(name, hostPath, mountPath string) HostVolume { + return NewHostVolume(name, hostPath, mountPath, false, corev1.HostPathUnset) +} + +// NewHostVolume creates a new HostVolume struct with default mode +func NewHostVolume(name, hostPath, mountPath string, readOnly bool, hostPathType corev1.HostPathType) HostVolume { + return HostVolume{ + name: name, + hostPath: hostPath, + mountPath: mountPath, + readOnly: readOnly, + hostPathType: &hostPathType, + } +} + +// HostVolume defines a volume to expose a host path +type HostVolume struct { + name string + hostPath string + mountPath string + readOnly bool + hostPathType *corev1.HostPathType +} + +// VolumeMount returns the k8s volume mount. +func (hv HostVolume) VolumeMount() corev1.VolumeMount { + return corev1.VolumeMount{ + Name: hv.name, + MountPath: hv.mountPath, + ReadOnly: hv.readOnly, + } +} + +// Volume returns the k8s volume. +func (hv HostVolume) Volume() corev1.Volume { + return corev1.Volume{ + Name: hv.name, + VolumeSource: corev1.VolumeSource{ + HostPath: &corev1.HostPathVolumeSource{ + Path: hv.hostPath, + Type: hv.hostPathType, + }, + }, + } +} + +// Name returns the name of the volume +func (hv HostVolume) Name() string { + return hv.name +} + +var _ VolumeLike = HostVolume{} diff --git a/pkg/controller/common/volume/secret.go b/pkg/controller/common/volume/secret.go index 782354d1c3..14ab6dca9c 100644 --- a/pkg/controller/common/volume/secret.go +++ b/pkg/controller/common/volume/secret.go @@ -6,18 +6,22 @@ package volume import ( corev1 "k8s.io/api/core/v1" + + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) // SecretVolume captures a subset of data of the k8s secret volume/mount type. type SecretVolume struct { - name string - mountPath string - secretName string - items []corev1.KeyToPath + name string + mountPath string + secretName string + items []corev1.KeyToPath + subPath string + defaultMode *int32 } // NewSecretVolumeWithMountPath creates a new SecretVolume -func NewSecretVolumeWithMountPath(secretName string, name string, mountPath string) SecretVolume { +func NewSecretVolumeWithMountPath(secretName, name, mountPath string) SecretVolume { return SecretVolume{ name: name, mountPath: mountPath, @@ -25,8 +29,19 @@ func NewSecretVolumeWithMountPath(secretName string, name string, mountPath stri } } +// NewSecretVolume creates a new SecretVolume +func NewSecretVolume(secretName, name, mountPath, subPath string, defaultMode int32) SecretVolume { + return SecretVolume{ + name: name, + mountPath: mountPath, + secretName: secretName, + subPath: subPath, + defaultMode: pointer.Int32(defaultMode), + } +} + // NewSelectiveSecretVolumeWithMountPath creates a new SecretVolume that projects only the specified secrets into the file system. -func NewSelectiveSecretVolumeWithMountPath(secretName string, name string, mountPath string, projectedSecrets []string) SecretVolume { +func NewSelectiveSecretVolumeWithMountPath(secretName, name, mountPath string, projectedSecrets []string) SecretVolume { keyToPaths := make([]corev1.KeyToPath, len(projectedSecrets)) for i, s := range projectedSecrets { keyToPaths[i] = corev1.KeyToPath{ @@ -48,6 +63,7 @@ func (sv SecretVolume) VolumeMount() corev1.VolumeMount { Name: sv.name, MountPath: sv.mountPath, ReadOnly: true, + SubPath: sv.subPath, } } @@ -57,9 +73,10 @@ func (sv SecretVolume) Volume() corev1.Volume { Name: sv.name, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: sv.secretName, - Items: sv.items, - Optional: &defaultOptional, + SecretName: sv.secretName, + Items: sv.items, + Optional: &defaultOptional, + DefaultMode: sv.defaultMode, }, }, } diff --git a/pkg/utils/pointer/numeric.go b/pkg/utils/pointer/numeric.go index 0e826db08b..7c3713645c 100644 --- a/pkg/utils/pointer/numeric.go +++ b/pkg/utils/pointer/numeric.go @@ -6,3 +6,6 @@ package pointer // Int32 returns a pointer to an Int32 func Int32(v int32) *int32 { return &v } + +// Int64 returns a pointer to an Int64 +func Int64(v int64) *int64 { return &v } diff --git a/test/e2e/samples_test.go b/test/e2e/samples_test.go index 40cf3082f4..cf16275b10 100644 --- a/test/e2e/samples_test.go +++ b/test/e2e/samples_test.go @@ -28,6 +28,10 @@ func TestSamples(t *testing.T) { decoder := helper.NewYAMLDecoder() for _, sample := range sampleFiles { + if sample == "../../config/samples/beat/filebeat_es_kibana.yaml" { + // ignore Beat sample for now + continue + } testName := mkTestName(t, sample) builders := createBuilders(t, decoder, sample, testName) t.Run(testName, func(t *testing.T) { From b8ee2091e9e54e6e55606ef33102b254fcdc54ae Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Tue, 12 May 2020 11:41:16 +0200 Subject: [PATCH 02/81] Exclude BeatList from doc generation --- docs/reference/api-docs.asciidoc | 26 +------------------------- hack/api-docs/config.yaml | 6 +++--- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index 6d419c692e..05e6aaa0e1 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -138,7 +138,6 @@ Package v1beta1 contains API Schema definitions for the beat v1beta1 API group .Resource Types - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat[$$Beat$$] -- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatlist[$$BeatList$$] @@ -147,10 +146,7 @@ Package v1beta1 contains API Schema definitions for the beat v1beta1 API group Beat is the Schema for the beats API -.Appears In: -**** -- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatlist[$$BeatList$$] -**** + [cols="25a,75a", options="header"] |=== @@ -163,24 +159,6 @@ Beat is the Schema for the beats API |=== -[id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatlist"] -=== BeatList - -BeatList contains a list of Beat - - - -[cols="25a,75a", options="header"] -|=== -| Field | Description -| *`apiVersion`* __string__ | `beat.k8s.elastic.co/v1beta1` -| *`kind`* __string__ | `BeatList` -| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#listmeta-v1-meta[$$ListMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`. - -| *`items`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat[$$Beat$$]__ | -|=== - - [id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beatspec"] === BeatSpec @@ -205,8 +183,6 @@ BeatSpec defines the desired state of a Beat. |=== - - [id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec"] === DaemonSetSpec diff --git a/hack/api-docs/config.yaml b/hack/api-docs/config.yaml index a806b6be2c..d772b5d1aa 100644 --- a/hack/api-docs/config.yaml +++ b/hack/api-docs/config.yaml @@ -1,8 +1,8 @@ processor: ignoreTypes: - - "(Elasticsearch|Kibana|ApmServer|EnterpriseSearch)List$" - - "(Elasticsearch|Kibana|ApmServer|EnterpriseSearch)Health$" - - "(Elasticsearch|Kibana|ApmServer|Reconciler|EnterpriseSearch)Status$" + - "(Elasticsearch|Kibana|ApmServer|EnterpriseSearch|Beat)List$" + - "(Elasticsearch|Kibana|ApmServer|EnterpriseSearch|Beat)Health$" + - "(Elasticsearch|Kibana|ApmServer|Reconciler|EnterpriseSearch|Beat)Status$" - "ElasticsearchSettings$" - "Associa(ted|tor|tionStatus|tionConf)$" ignoreFields: From c68cfed55d2c90c3792ba3dbd0ba734c7945e424 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Tue, 12 May 2020 14:27:57 +0200 Subject: [PATCH 03/81] Add autodiscovery rbac flag to manager cmd, fix rbac for the operator --- cmd/manager/main.go | 5 +++++ config/e2e/operator.yaml | 2 ++ config/e2e/rbac.yaml | 14 ++++++++++++++ .../all-in-one/cluster_role.template.yaml | 1 + config/operator/global/rbac.yaml | 7 +++++-- .../operator/namespace/cluster_role.template.yaml | 15 ++++++++++++++- config/operator/namespace/rbac.yaml | 6 ++++++ 7 files changed, 47 insertions(+), 3 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index afb3cd3c19..96b0dda74c 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -168,6 +168,11 @@ func init() { "", fmt.Sprintf("K8s secret mounted into the path designated by %s to be used for webhook certificates", operator.WebhookCertDirFlag), ) + Cmd.Flags().Bool( + operator.DisableAutodiscoveryRBACSetup, + false, + fmt.Sprintf("Determines whether the operator should set up role, binding and service account for Beats autodiscover feature"), + ) // enable using dashed notation in flags and underscores in env viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index 565dd7b48c..17406ba766 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -25,6 +25,7 @@ rules: - secrets - services - configmaps + - serviceaccounts verbs: - get - list @@ -38,6 +39,7 @@ rules: resources: - deployments - statefulsets + - daemonsets verbs: - get - list diff --git a/config/e2e/rbac.yaml b/config/e2e/rbac.yaml index 8fa78d7eb9..d8cec59a06 100644 --- a/config/e2e/rbac.yaml +++ b/config/e2e/rbac.yaml @@ -104,6 +104,7 @@ rules: resources: - deployments - statefulsets + - daemonsets verbs: - get - list @@ -161,6 +162,19 @@ rules: - update - patch - delete + - apiGroups: + - beat.k8s.elastic.co + resources: + - beats + - beats/status + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - storage.k8s.io resources: diff --git a/config/operator/all-in-one/cluster_role.template.yaml b/config/operator/all-in-one/cluster_role.template.yaml index 41e9fef28d..1c9d0aa32d 100644 --- a/config/operator/all-in-one/cluster_role.template.yaml +++ b/config/operator/all-in-one/cluster_role.template.yaml @@ -37,6 +37,7 @@ rules: resources: - deployments - statefulsets + - daemonsets verbs: - get - list diff --git a/config/operator/global/rbac.yaml b/config/operator/global/rbac.yaml index 634f5cf760..fd52e4c21f 100644 --- a/config/operator/global/rbac.yaml +++ b/config/operator/global/rbac.yaml @@ -19,6 +19,9 @@ rules: - apiGroups: ["enterprisesearch.k8s.elastic.co"] resources: ["enterprisesearches"] verbs: ["get", "list", "watch"] + - apiGroups: ["beat.k8s.elastic.co"] + resources: ["beats"] + verbs: ["get", "list", "watch"] --- @@ -39,6 +42,6 @@ rules: - apiGroups: ["kibana.k8s.elastic.co"] resources: ["kibanas"] verbs: ["create", "delete", "deletecollection", "patch", "update"] - - apiGroups: ["enterprisesearch.k8s.elastic.co"] - resources: ["enterprisesearches"] + - apiGroups: ["beat.k8s.elastic.co"] + resources: ["beats"] verbs: ["create", "delete", "deletecollection", "patch", "update"] diff --git a/config/operator/namespace/cluster_role.template.yaml b/config/operator/namespace/cluster_role.template.yaml index a25373930b..39d4b55cbc 100644 --- a/config/operator/namespace/cluster_role.template.yaml +++ b/config/operator/namespace/cluster_role.template.yaml @@ -46,6 +46,7 @@ rules: resources: - deployments - statefulsets + - daemonsets verbs: - get - list @@ -129,4 +130,16 @@ rules: - update - patch - delete - +- apiGroups: + - beat.k8s.elastic.co + resources: + - beats + - beats/status + verbs: + - get + - list + - watch + - create + - update + - patch + - delete diff --git a/config/operator/namespace/rbac.yaml b/config/operator/namespace/rbac.yaml index 634f5cf760..6f741e23a2 100644 --- a/config/operator/namespace/rbac.yaml +++ b/config/operator/namespace/rbac.yaml @@ -19,6 +19,9 @@ rules: - apiGroups: ["enterprisesearch.k8s.elastic.co"] resources: ["enterprisesearches"] verbs: ["get", "list", "watch"] + - apiGroups: ["beat.k8s.elastic.co"] + resources: ["beats"] + verbs: ["get", "list", "watch"] --- @@ -42,3 +45,6 @@ rules: - apiGroups: ["enterprisesearch.k8s.elastic.co"] resources: ["enterprisesearches"] verbs: ["create", "delete", "deletecollection", "patch", "update"] + - apiGroups: ["beat.k8s.elastic.co"] + resources: ["beats"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] From 3f0f842e8706d74a1de75e3d46309e16ff302059 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 14 May 2020 07:53:11 +0200 Subject: [PATCH 04/81] Change autodiscover RBAC flag from disable... to enable... --- cmd/manager/main.go | 8 ++--- pkg/controller/beat/beat_controller.go | 2 +- .../{autodiscovery.go => autodiscover.go} | 36 +++++++++---------- .../common/beat/filebeat/filebeat.go | 12 +++---- .../common/beat/otherbeat/otherbeat.go | 4 +-- pkg/controller/common/operator/flags.go | 36 +++++++++---------- test/e2e/run.sh | 2 +- 7 files changed, 50 insertions(+), 50 deletions(-) rename pkg/controller/common/beat/{autodiscovery.go => autodiscover.go} (75%) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 96b0dda74c..2a785e3b74 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -169,8 +169,8 @@ func init() { fmt.Sprintf("K8s secret mounted into the path designated by %s to be used for webhook certificates", operator.WebhookCertDirFlag), ) Cmd.Flags().Bool( - operator.DisableAutodiscoveryRBACSetup, - false, + operator.EnableAutodiscoverRBACSetup, + true, fmt.Sprintf("Determines whether the operator should set up role, binding and service account for Beats autodiscover feature"), ) @@ -337,8 +337,8 @@ func execute() { accessReviewer = rbac.NewPermissiveAccessReviewer() } - if viper.GetBool(operator.DisableAutodiscoveryRBACSetup) { - commonbeat.DisableAutodiscoveryRBACSetup() + if viper.GetBool(operator.EnableAutodiscoverRBACSetup) { + commonbeat.EnableAutodiscoverRBACSetup() } if err = apmserver.Add(mgr, params); err != nil { diff --git a/pkg/controller/beat/beat_controller.go b/pkg/controller/beat/beat_controller.go index 53b16ffe9a..5b2a5fd61b 100644 --- a/pkg/controller/beat/beat_controller.go +++ b/pkg/controller/beat/beat_controller.go @@ -97,7 +97,7 @@ func addWatches(c controller.Controller, r *ReconcileBeat) error { return err } - if commonbeat.ShouldSetupAutodiscoveryRBAC() { + if commonbeat.ShouldSetupAutodiscoverRBAC() { if err := c.Watch(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &beatv1beta1.Beat{}, diff --git a/pkg/controller/common/beat/autodiscovery.go b/pkg/controller/common/beat/autodiscover.go similarity index 75% rename from pkg/controller/common/beat/autodiscovery.go rename to pkg/controller/common/beat/autodiscover.go index 3b5dbb139a..5287026d21 100644 --- a/pkg/controller/common/beat/autodiscovery.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -22,33 +22,33 @@ import ( ) const ( - AutodiscoveryServiceAccountName = "elastic-operator-autodiscovery" - autodiscoveryClusterRoleBindingName = "elastic-operator-autodiscovery" - autodiscoveryClusterRoleName = "elastic-operator-autodiscovery" + AutodiscoverServiceAccountName = "elastic-operator-autodiscover" + autodiscoverClusterRoleBindingName = "elastic-operator-autodiscover" + autodiscoverClusterRoleName = "elastic-operator-autodiscover" ) var ( - shouldSetupRBAC = true + shouldSetupRBAC = false ) -// DisableAutodiscoveryRBACSetup disables setting up autodiscovery RBAC -func DisableAutodiscoveryRBACSetup() { - shouldSetupRBAC = false +// EnableAutodiscoverRBACSetup enables setting up autodiscover RBAC +func EnableAutodiscoverRBACSetup() { + shouldSetupRBAC = true } -// ShouldSetupAutodiscoveryRBAC returns true if autodiscovery RBAC is expected to be set up by the operator -func ShouldSetupAutodiscoveryRBAC() bool { +// ShouldSetupAutodiscoverRBAC returns true if autodiscover RBAC is expected to be set up by the operator +func ShouldSetupAutodiscoverRBAC() bool { return shouldSetupRBAC } -// SetupAutodiscoveryRBAC reconciles all resources needed for default RBAC setup -func SetupAutodiscoveryRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { +// SetupAutodiscoverRBAC reconciles all resources needed for default RBAC setup +func SetupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { // this is the source of truth and should be respected at all times - if !ShouldSetupAutodiscoveryRBAC() { + if !ShouldSetupAutodiscoverRBAC() { return nil } - span, _ := apm.StartSpan(ctx, "reconcile_autodiscovery_rbac", tracing.SpanTypeApp) + span, _ := apm.StartSpan(ctx, "reconcile_autodiscover_rbac", tracing.SpanTypeApp) defer span.End() if err := reconcileServiceAccount(client, owner, labels); err != nil { @@ -69,7 +69,7 @@ func SetupAutodiscoveryRBAC(ctx context.Context, client k8s.Client, owner metav1 func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[string]string) error { sa := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: AutodiscoveryServiceAccountName, + Name: AutodiscoverServiceAccountName, Namespace: owner.GetNamespace(), Labels: labels, }, @@ -81,7 +81,7 @@ func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[ func reconcileClusterRole(client k8s.Client) error { role := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ - Name: autodiscoveryClusterRoleName, + Name: autodiscoverClusterRoleName, }, Rules: []rbacv1.PolicyRule{ { @@ -98,19 +98,19 @@ func reconcileClusterRole(client k8s.Client) error { func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { binding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: autodiscoveryClusterRoleBindingName, + Name: autodiscoverClusterRoleBindingName, }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", - Name: AutodiscoveryServiceAccountName, + Name: AutodiscoverServiceAccountName, Namespace: owner.GetNamespace(), }, }, RoleRef: rbacv1.RoleRef{ APIGroup: rbacv1.GroupName, Kind: "ClusterRole", - Name: autodiscoveryClusterRoleName, + Name: autodiscoverClusterRoleName, }, } diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/common/beat/filebeat/filebeat.go index 94b1339766..ae1faf20fd 100644 --- a/pkg/controller/common/beat/filebeat/filebeat.go +++ b/pkg/controller/common/beat/filebeat/filebeat.go @@ -79,12 +79,12 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { func (fd *Driver) Reconcile() commonbeat.DriverResults { results := commonbeat.NewDriverResults(fd.Context) - // setup sa, role, rolebinding for autodiscovery if required - if commonbeat.ShouldSetupAutodiscoveryRBAC() { - if err := commonbeat.SetupAutodiscoveryRBAC(fd.Context, fd.Client, fd.Owner, fd.Labels); err != nil { + // setup sa, role, rolebinding for autodiscover if required + if commonbeat.ShouldSetupAutodiscoverRBAC() { + if err := commonbeat.SetupAutodiscoverRBAC(fd.Context, fd.Client, fd.Owner, fd.Labels); err != nil { results.WithError(err) fd.Logger.V(1).Info( - "autodiscovery rbac setup failed", + "autodiscover rbac setup failed", "namespace", fd.Owner.GetNamespace(), "beat_name", fd.Owner.GetName()) } @@ -142,8 +142,8 @@ func doReconcile(dp commonbeat.DriverParams, checksum hash.Hash) (commonbeat.Dri // If SA is already provided, assume that for this resource (despite operator configuration) the user took the // responsibility of configuring RBAC. Otherwise, use the default. - if commonbeat.ShouldSetupAutodiscoveryRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { - builder.WithServiceAccount(commonbeat.AutodiscoveryServiceAccountName) + if commonbeat.ShouldSetupAutodiscoverRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { + builder.WithServiceAccount(commonbeat.AutodiscoverServiceAccountName) } containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index ba368cde24..7eab7307ab 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -172,8 +172,8 @@ func doReconcile(ctx context.Context, // If SA is already provided, assume that for this resource (despite operator configuration) the user took the // responsibility of configuring RBAC. Otherwise, use the default. - if commonbeat.ShouldSetupAutodiscoveryRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { - builder.WithServiceAccount(commonbeat.AutodiscoveryServiceAccountName) + if commonbeat.ShouldSetupAutodiscoverRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { + builder.WithServiceAccount(commonbeat.AutodiscoverServiceAccountName) } configVolume := volume.NewSecretVolume( diff --git a/pkg/controller/common/operator/flags.go b/pkg/controller/common/operator/flags.go index 5c3b73d6b9..6797c5f8e8 100644 --- a/pkg/controller/common/operator/flags.go +++ b/pkg/controller/common/operator/flags.go @@ -5,22 +5,22 @@ package operator const ( - AutoPortForwardFlag = "auto-port-forward" - CACertRotateBeforeFlag = "ca-cert-rotate-before" - CACertValidityFlag = "ca-cert-validity" - CertRotateBeforeFlag = "cert-rotate-before" - CertValidityFlag = "cert-validity" - ContainerRegistryFlag = "container-registry" - DebugHTTPListenFlag = "debug-http-listen" - DisableAutodiscoveryRBACSetup = "disable-autodiscovery-rbac-setup" - EnableTracingFlag = "enable-tracing" - EnableWebhookFlag = "enable-webhook" - EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" - ManageWebhookCertsFlag = "manage-webhook-certs" - MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" - MetricsPortFlag = "metrics-port" - NamespacesFlag = "namespaces" - OperatorNamespaceFlag = "operator-namespace" - WebhookCertDirFlag = "webhook-cert-dir" - WebhookSecretFlag = "webhook-secret" + AutoPortForwardFlag = "auto-port-forward" + CACertRotateBeforeFlag = "ca-cert-rotate-before" + CACertValidityFlag = "ca-cert-validity" + CertRotateBeforeFlag = "cert-rotate-before" + CertValidityFlag = "cert-validity" + ContainerRegistryFlag = "container-registry" + DebugHTTPListenFlag = "debug-http-listen" + EnableAutodiscoverRBACSetup = "disable-autodiscover-rbac-setup" + EnableTracingFlag = "enable-tracing" + EnableWebhookFlag = "enable-webhook" + EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" + ManageWebhookCertsFlag = "manage-webhook-certs" + MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" + MetricsPortFlag = "metrics-port" + NamespacesFlag = "namespaces" + OperatorNamespaceFlag = "operator-namespace" + WebhookCertDirFlag = "webhook-cert-dir" + WebhookSecretFlag = "webhook-secret" ) diff --git a/test/e2e/run.sh b/test/e2e/run.sh index ea6c8768a6..c5f106e332 100755 --- a/test/e2e/run.sh +++ b/test/e2e/run.sh @@ -20,5 +20,5 @@ done # sleep 1s to allow filebeat to read all logs with 1s max_backoff # minimizes race condition in filebeat between reading log file and -# stopping reading due to pod termination autodiscovery event +# stopping reading due to pod termination autodiscover event sleep 1 From 69184cc56f43a6c92f87c7a109d75a7b5fe2acc6 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 14 May 2020 08:08:42 +0200 Subject: [PATCH 05/81] Improve godoc wording --- pkg/apis/beat/v1beta1/beat_types.go | 6 +++--- pkg/controller/beat/beat_controller.go | 2 +- pkg/controller/common/beat/health/health.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index a6e6c46f54..8ced04fdfe 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -39,7 +39,7 @@ type BeatSpec struct { ServiceAccountName string `json:"serviceAccountName,omitempty"` // DaemonSet field allows to: - // 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the + // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used // 2. Provide spec for the DaemonSet // At most one of DaemonSet and Deployment can be used. @@ -47,9 +47,9 @@ type BeatSpec struct { DaemonSet *DaemonSetSpec `json:"daemonSet,omitempty"` // Deployment field allows to: - // 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the + // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used - // 2. Provide spec for the Deployment + // 2. provide spec for the Deployment // At most one of DaemonSet and Deployment can be used. // +kubebuilder:validation:Optional Deployment *DeploymentSpec `json:"deployment,omitempty"` diff --git a/pkg/controller/beat/beat_controller.go b/pkg/controller/beat/beat_controller.go index 5b2a5fd61b..57351edf4a 100644 --- a/pkg/controller/beat/beat_controller.go +++ b/pkg/controller/beat/beat_controller.go @@ -63,7 +63,7 @@ func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileBe } } -// add adds a new Controller to mgr with r as the reconcile.Reconciler +// addWatches registers the required watches func addWatches(c controller.Controller, r *ReconcileBeat) error { // Watch for changes to Beat if err := c.Watch(&source.Kind{Type: &beatv1beta1.Beat{}}, &handler.EnqueueRequestForObject{}); err != nil { diff --git a/pkg/controller/common/beat/health/health.go b/pkg/controller/common/beat/health/health.go index c13e14fbdd..a5adf661f6 100644 --- a/pkg/controller/common/beat/health/health.go +++ b/pkg/controller/common/beat/health/health.go @@ -16,7 +16,7 @@ const ( // BeatYellowHealth means that: // 1) at least one Pod is Ready, and - // 1) association is not configured or configured and established + // 2) association is not configured or configured and established BeatYellowHealth BeatHealth = "yellow" // BeatGreenHealth means that: From b538333128d142051b8e0c4386924f565b17a94d Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 14 May 2020 08:12:29 +0200 Subject: [PATCH 06/81] Rename beat_controller.go to controller.go --- pkg/controller/beat/{beat_controller.go => controller.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/controller/beat/{beat_controller.go => controller.go} (100%) diff --git a/pkg/controller/beat/beat_controller.go b/pkg/controller/beat/controller.go similarity index 100% rename from pkg/controller/beat/beat_controller.go rename to pkg/controller/beat/controller.go From 9ae99a491776523d01870b74b51f1dfd3d0e9a92 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 14 May 2020 08:17:38 +0200 Subject: [PATCH 07/81] Fix comment wording --- pkg/controller/common/beat/autodiscover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 5287026d21..83edc5a4c6 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -118,7 +118,7 @@ func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { } func reconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error { - // labels set here must have be exact the same for all callers in particular namespace + // labels set here must be exactly the same for all callers in particular namespace // otherwise they'll just keep trying to override each other objMeta, err := meta.Accessor(obj) if err != nil { From 98745b41c6b6a503dc1555673f929806b088eafd Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 14 May 2020 10:15:41 +0200 Subject: [PATCH 08/81] Fix update path for autodiscover resources --- pkg/controller/common/beat/autodiscover.go | 8 +++++--- pkg/controller/common/beat/otherbeat/otherbeat.go | 15 ++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 83edc5a4c6..4fcd07af59 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -6,6 +6,7 @@ package beat import ( "context" + "reflect" "go.elastic.co/apm" corev1 "k8s.io/api/core/v1" @@ -127,19 +128,20 @@ func reconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error objMeta.SetLabels(maps.Merge(objMeta.GetLabels(), hash.SetTemplateHashLabel(nil, obj))) - obj2 := obj.DeepCopyObject() + reconciled := obj.DeepCopyObject() return reconciler.ReconcileResource(reconciler.Params{ Client: client, Owner: owner, Expected: obj, - Reconciled: obj2, + Reconciled: reconciled, NeedsUpdate: func() bool { - objMeta2, err := meta.Accessor(obj2) + objMeta2, err := meta.Accessor(reconciled) // compare hash of the deployment at the time it was built return err != nil || hash.GetTemplateHashLabel(objMeta.GetLabels()) != hash.GetTemplateHashLabel(objMeta2.GetLabels()) }, UpdateReconciled: func() { + reflect.ValueOf(reconciled).Elem().Set(reflect.ValueOf(obj).Elem()) }, }) } diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 7eab7307ab..4059173395 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -72,13 +72,14 @@ func (fd *Driver) Reconcile() commonbeat.DriverResults { results.WithError(err) } - userCfg, err := settings.NewCanonicalConfigFrom(fd.Config.Data) - if err != nil { - results.WithError(err) - } - - if err = cfg.MergeWith(userCfg); err != nil { - results.WithError(err) + if fd.Config != nil { + userCfg, err := settings.NewCanonicalConfigFrom(fd.Config.Data) + if err != nil { + results.WithError(err) + } + if err = cfg.MergeWith(userCfg); err != nil { + results.WithError(err) + } } cfgBytes, err := cfg.Render() From b2f898e58b80de5dcffb91b354636e6e210ddca4 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 14 May 2020 10:24:47 +0200 Subject: [PATCH 09/81] Add image validation if Beat type is not well known --- pkg/apis/beat/v1beta1/webhook.go | 15 +++++++++++++++ pkg/controller/common/beat/otherbeat/otherbeat.go | 2 -- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/apis/beat/v1beta1/webhook.go b/pkg/apis/beat/v1beta1/webhook.go index f4026d364c..48ff48b225 100644 --- a/pkg/apis/beat/v1beta1/webhook.go +++ b/pkg/apis/beat/v1beta1/webhook.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" + "github.com/elastic/cloud-on-k8s/pkg/utils/stringsutil" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -29,6 +30,7 @@ var ( checkNameLength, checkSupportedVersion, checkAtMostOneDeploymentOption, + checkImageIfTypeUnknown, } updateChecks = []func(old, curr *Beat) field.ErrorList{ @@ -116,6 +118,19 @@ func checkAtMostOneDeploymentOption(b *Beat) field.ErrorList { return nil } +func checkImageIfTypeUnknown(b *Beat) field.ErrorList { + knownTypes := []string{"filebeat"} + if !stringsutil.StringInSlice(b.Spec.Type, knownTypes) && + b.Spec.Image == "" { + return field.ErrorList{ + field.Required( + field.NewPath("spec").Child("image"), + "Image is required if Beat type is not well known."), + } + } + return nil +} + func checkNoDowngrade(prev, curr *Beat) field.ErrorList { return commonv1.CheckNoDowngrade(prev.Spec.Version, curr.Spec.Version) } diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 4059173395..19f4dcb522 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -147,8 +147,6 @@ func doReconcile(ctx context.Context, podTemplate := deploymentSpec.PodTemplate - //image has to be there - builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). WithTerminationGracePeriod(30). WithDockerImage(image, ""). From 469f67c1f203af67a692c06e8335744431ea3f27 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 14 May 2020 16:04:14 +0200 Subject: [PATCH 10/81] Regenerate CRDs and api docs --- config/crds/all-crds.yaml | 6 +++--- config/crds/bases/beat.k8s.elastic.co_beats.yaml | 6 +++--- docs/reference/api-docs.asciidoc | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/crds/all-crds.yaml b/config/crds/all-crds.yaml index 4aeb9b4e3b..dc789342e0 100644 --- a/config/crds/all-crds.yaml +++ b/config/crds/all-crds.yaml @@ -504,16 +504,16 @@ spec: override the default configuration. type: object daemonSet: - description: 'DaemonSet field allows to: 1. indicate whether the Beat + description: 'DaemonSet field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the DaemonSet At most one of DaemonSet and Deployment can be used.' properties: {} type: object deployment: - description: 'Deployment field allows to: 1. indicate whether the Beat + description: 'Deployment field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, - a default for the Type is used 2. Provide spec for the Deployment + a default for the Type is used 2. provide spec for the Deployment At most one of DaemonSet and Deployment can be used.' properties: replicas: diff --git a/config/crds/bases/beat.k8s.elastic.co_beats.yaml b/config/crds/bases/beat.k8s.elastic.co_beats.yaml index 787684a105..a1a626d8d5 100644 --- a/config/crds/bases/beat.k8s.elastic.co_beats.yaml +++ b/config/crds/bases/beat.k8s.elastic.co_beats.yaml @@ -68,7 +68,7 @@ spec: override the default configuration. type: object daemonSet: - description: 'DaemonSet field allows to: 1. indicate whether the Beat + description: 'DaemonSet field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the DaemonSet At most one of DaemonSet and Deployment can be used.' @@ -6105,9 +6105,9 @@ spec: type: object type: object deployment: - description: 'Deployment field allows to: 1. indicate whether the Beat + description: 'Deployment field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, - a default for the Type is used 2. Provide spec for the Deployment + a default for the Type is used 2. provide spec for the Deployment At most one of DaemonSet and Deployment can be used.' properties: podTemplate: diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index 05e6aaa0e1..c9b248d15b 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -178,8 +178,8 @@ BeatSpec defines the desired state of a Beat. | *`image`* __string__ | Image is the Beat Docker image to deploy. Version has to match the Beat in the image. | *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the Beat configuration. If provided, it will override the default configuration. | *`serviceAccountName`* __string__ | ServiceAccountName is used to check access from the current resource to Elasticsearch resource in a different namespace. Can only be used if ECK is enforcing RBAC on references. -| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet field allows to: 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the DaemonSet At most one of DaemonSet and Deployment can be used. -| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment field allows to: 1. indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the Deployment At most one of DaemonSet and Deployment can be used. +| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the DaemonSet At most one of DaemonSet and Deployment can be used. +| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. provide spec for the Deployment At most one of DaemonSet and Deployment can be used. |=== From be128a2f5d0ac414b987d06ebb26cdc6684fbf33 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 13 May 2020 15:59:42 +0200 Subject: [PATCH 11/81] Add metricbeat driver --- pkg/controller/beat/controller.go | 3 + pkg/controller/common/beat/autodiscover.go | 18 +- pkg/controller/common/beat/config.go | 83 ++++++- pkg/controller/common/beat/filebeat/config.go | 95 +------- .../common/beat/filebeat/filebeat.go | 18 +- .../common/beat/metricbeat/config.go | 173 +++++++++++++++ .../common/beat/metricbeat/metricbeat.go | 209 ++++++++++++++++++ .../common/beat/otherbeat/otherbeat.go | 49 +--- pkg/controller/common/container/container.go | 1 + 9 files changed, 501 insertions(+), 148 deletions(-) create mode 100644 pkg/controller/common/beat/metricbeat/config.go create mode 100644 pkg/controller/common/beat/metricbeat/metricbeat.go diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 57351edf4a..bf296498a5 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -7,6 +7,7 @@ package beat import ( "context" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/metricbeat" "go.elastic.co/apm" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -232,6 +233,8 @@ func newDriver(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) co switch dp.Type { case string(filebeat.Type): return filebeat.NewDriver(dp) + case string(metricbeat.Type): + return metricbeat.NewDriver(dp) default: return otherbeat.NewDriver(dp) } diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 4fcd07af59..6868914d0c 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -8,6 +8,7 @@ import ( "context" "reflect" + "github.com/go-logr/logr" "go.elastic.co/apm" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -42,8 +43,21 @@ func ShouldSetupAutodiscoverRBAC() bool { return shouldSetupRBAC } -// SetupAutodiscoverRBAC reconciles all resources needed for default RBAC setup -func SetupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { +// SetupAutodiscoveryRBAC reconciles all resources needed for default RBAC setup +func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, owner metav1.Object, labels map[string]string) error { + if ShouldSetupAutodiscoverRBAC() { + if err := setupAutodiscoverRBAC(ctx, client, owner, labels); err != nil { + log.V(1).Info( + "autodiscovery rbac setup failed", + "namespace", owner.GetNamespace(), + "beat_name", owner.GetName()) + return err + } + } + return nil +} + +func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { // this is the source of truth and should be respected at all times if !ShouldSetupAutodiscoverRBAC() { return nil diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index 9aceba0eb8..dbf9a0bb36 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -5,10 +5,17 @@ package beat import ( + "hash" "path" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" + "github.com/elastic/cloud-on-k8s/pkg/controller/common" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) @@ -25,7 +32,7 @@ const ( ) // SetOutput will set output section in Beat config according to association configuration. -func SetOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { +func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { if associated.AssociationConf().IsConfigured() { username, password, err := association.ElasticsearchAuthSettings(client, associated) if err != nil { @@ -45,3 +52,77 @@ func SetOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated comm return nil } + +func build( + client k8s.Client, + associated commonv1.Associated, + defaultConfig *settings.CanonicalConfig, + userConfig *commonv1.Config) ([]byte, error) { + cfg := settings.NewCanonicalConfig() + + if err := setOutput(cfg, client, associated); err != nil { + return nil, err + } + + // use only the default config or only the provided config - no overriding, no merging + if userConfig == nil { + if err := cfg.MergeWith(defaultConfig); err != nil { + return nil, err + } + } else { + userCfg, err := settings.NewCanonicalConfigFrom(userConfig.Data) + if err != nil { + return nil, err + } + + if err = cfg.MergeWith(userCfg); err != nil { + return nil, err + } + } + + return cfg.Render() +} + +func ReconcileConfig( + params DriverParams, + configFileName string, + defaultConfig *settings.CanonicalConfig, + checksum hash.Hash) error { + + cfgBytes, err := build(params.Client, params.Associated, defaultConfig, params.Config) + if err != nil { + return err + } + + // create resource + expected := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: params.Owner.GetNamespace(), + Name: params.Namer.ConfigSecretName(params.Type, params.Owner.GetName()), + Labels: common.AddCredentialsLabel(params.Labels), + }, + Data: map[string][]byte{ + configFileName: cfgBytes, + }, + } + + // reconcile + if _, err = reconciler.ReconcileSecret(params.Client, expected, params.Owner); err != nil { + return err + } + + // we need to deref the secret here (if any) to include it in the checksum otherwise Beat will not be rolled on contents changes + assocConf := params.Associated.AssociationConf() + if assocConf.AuthIsConfigured() { + esAuthKey := types.NamespacedName{Name: assocConf.GetAuthSecretName(), Namespace: params.Owner.GetNamespace()} + esAuthSecret := corev1.Secret{} + if err := params.Client.Get(esAuthKey, &esAuthSecret); err != nil { + return err + } + _, _ = checksum.Write(esAuthSecret.Data[assocConf.GetAuthSecretKey()]) + } + + _, _ = checksum.Write(cfgBytes) + + return nil +} diff --git a/pkg/controller/common/beat/filebeat/config.go b/pkg/controller/common/beat/filebeat/config.go index 5e74a7f808..805cd7d7f6 100644 --- a/pkg/controller/common/beat/filebeat/config.go +++ b/pkg/controller/common/beat/filebeat/config.go @@ -5,25 +5,11 @@ package filebeat import ( - "context" - "hash" - - "go.elastic.co/apm" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - - commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" - "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) var ( - filebeatDefaultConfig = settings.MustCanonicalConfig(map[string]interface{}{ + defaultConfig = settings.MustCanonicalConfig(map[string]interface{}{ "filebeat": map[string]interface{}{ "autodiscover": map[string]interface{}{ "providers": []map[string]interface{}{ @@ -47,82 +33,3 @@ var ( }, }) ) - -func build(client k8s.Client, associated commonv1.Associated, config *commonv1.Config) ([]byte, error) { - cfg := settings.NewCanonicalConfig() - - if err := commonbeat.SetOutput(cfg, client, associated); err != nil { - return nil, err - } - - // use only the default config or only the provided config - no overriding, no merging - if config == nil { - if err := cfg.MergeWith(filebeatDefaultConfig); err != nil { - return nil, err - } - } else { - userCfg, err := settings.NewCanonicalConfigFrom(config.Data) - if err != nil { - return nil, err - } - - if err = cfg.MergeWith(userCfg); err != nil { - return nil, err - } - } - - return cfg.Render() -} - -// ReconcileConfig builds and reconciles Filebeat config based on association configured, default and user configs. -// `checksum` hash will get updated based on the config to be consumed by Filebeat. -func ReconcileConfig( - ctx context.Context, - client k8s.Client, - associated commonv1.Associated, - cfg *commonv1.Config, - owner metav1.Object, - labels map[string]string, - namer commonbeat.Namer, - checksum hash.Hash) error { - span, _ := apm.StartSpan(ctx, "reconcile_config_secret", tracing.SpanTypeApp) - defer span.End() - - // build config - cfgBytes, err := build(client, associated, cfg) - if err != nil { - return err - } - - // create resource - expected := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: owner.GetNamespace(), - Name: namer.ConfigSecretName(string(Type), owner.GetName()), - Labels: common.AddCredentialsLabel(labels), - }, - Data: map[string][]byte{ - ConfigFileName: cfgBytes, - }, - } - - // reconcile - if _, err = reconciler.ReconcileSecret(client, expected, owner); err != nil { - return err - } - - // we need to deref the secret here (if any) to include it in the checksum otherwise Beat will not be rolled on contents changes - assocConf := associated.AssociationConf() - if assocConf.AuthIsConfigured() { - esAuthKey := types.NamespacedName{Name: assocConf.GetAuthSecretName(), Namespace: owner.GetNamespace()} - esAuthSecret := corev1.Secret{} - if err := client.Get(esAuthKey, &esAuthSecret); err != nil { - return err - } - _, _ = checksum.Write(esAuthSecret.Data[assocConf.GetAuthSecretKey()]) - } - - _, _ = checksum.Write(cfgBytes) - - return nil -} diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/common/beat/filebeat/filebeat.go index ae1faf20fd..0bd01ed400 100644 --- a/pkg/controller/common/beat/filebeat/filebeat.go +++ b/pkg/controller/common/beat/filebeat/filebeat.go @@ -79,20 +79,16 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { func (fd *Driver) Reconcile() commonbeat.DriverResults { results := commonbeat.NewDriverResults(fd.Context) - // setup sa, role, rolebinding for autodiscover if required - if commonbeat.ShouldSetupAutodiscoverRBAC() { - if err := commonbeat.SetupAutodiscoverRBAC(fd.Context, fd.Client, fd.Owner, fd.Labels); err != nil { - results.WithError(err) - fd.Logger.V(1).Info( - "autodiscover rbac setup failed", - "namespace", fd.Owner.GetNamespace(), - "beat_name", fd.Owner.GetName()) - } + if err := commonbeat.SetupAutodiscoverRBAC(fd.Context, fd.Logger, fd.Client, fd.Owner, fd.Labels); err != nil { + results.WithError(err) } checksum := sha256.New224() - // reconcile config - err := ReconcileConfig(fd.Context, fd.Client, fd.Associated, fd.Config, fd.Owner, fd.Labels, fd.Namer, checksum) + err := commonbeat.ReconcileConfig( + fd.DriverParams, + ConfigFileName, + defaultConfig, + checksum) if err != nil { results.WithError(err) return results diff --git a/pkg/controller/common/beat/metricbeat/config.go b/pkg/controller/common/beat/metricbeat/config.go new file mode 100644 index 0000000000..f1bb7a4b14 --- /dev/null +++ b/pkg/controller/common/beat/metricbeat/config.go @@ -0,0 +1,173 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package metricbeat + +import ( + "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" +) + +var ( + defaultConfig = settings.MustCanonicalConfig(map[string]interface{}{ + "metricbeat": map[string]interface{}{ + "autodiscover": map[string]interface{}{ + "providers": []map[string]interface{}{ + { + "type": "kubernetes", + "node": "${NODE_NAME}", + "hints": map[string]interface{}{ + "enabled": "true", + "default_config": map[string]interface{}{}, + }, + }, + }, + }, + "modules": []map[string]interface{}{ + { + "module": "system", + "period": "10s", + "metricsets": []string{"cpu", "load", "memory", "network", "process", "process_summary"}, + "processes": []string{".*"}, + "process.include_top_n": map[string]interface{}{ + "by_cpu": 5, + "by_memory": 5, + }, + }, + { + "module": "system", + "period": "1m", + "metricsets": []string{"filesystem", "fsstat"}, + "processors": []map[string]interface{}{ + { + "drop_event.when.regexp": map[string]interface{}{ + "system.filesystem.mount_point": "^/(sys|cgroup|proc|dev|etc|host|lib)($|/)", + }, + }, + }, + }, + { + "module": "kubernetes", + "period": "10s", + "metricsets": []string{"node", "system", "pod", "container", "volume"}, + "host": "${NODE_NAME}", + "hosts": []string{"https://${HOSTNAME}:10250"}, + "bearer_token_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", + "ssl.verification_mode": "none", + }, + { + "module": "kubernetes", + "period": "10s", + "metricsets": []string{"proxy"}, + "host": "${NODE_NAME}", + "hosts": []string{"https://${HOSTNAME}:10249"}, + }, + }, + }, + "setup.dashboards.enabled": "true", + "setup.kibana.host": "https://kibana-sample-kb-http.default.svc:5601", + "setup.kibana.username": "elastic", + "setup.kibana.password": "x4rVl41N48C6IJS3sW8L27Uf", + "setup.kibana.ssl.certificate_authorities": "/mnt/elastic-internal/kibana-certs/ca.crt", + "processors": []map[string]interface{}{ + {"add_cloud_metadata": nil}, + //{"add_host_metadata": nil}, + }, + }) +) + +/* +module: kubernetes + metricsets: + - node + - system + - pod + - container + - volume + period: 10s + host: ${NODE_NAME} + hosts: ["https://${HOSTNAME}:10250"] + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + ssl.verification_mode: "none" + # If using Red Hat OpenShift remove ssl.verification_mode entry and + # uncomment these settings: + #ssl.certificate_authorities: + #- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt + - module: kubernetes + metricsets: + - proxy + period: 10s + host: ${NODE_NAME} + hosts: ["localhost:10249"] + +*/ + +/* + metricbeat.config.modules: + # Mounted `metricbeat-daemonset-modules` configmap: + path: ${path.config}/modules.d/*.yml + # Reload module configs as they change: + reload.enabled: false + + # To enable hints based autodiscover uncomment this: + #metricbeat.autodiscover: + # providers: + # - type: kubernetes + # node: ${NODE_NAME} + # hints.enabled: true + + processors: + - add_cloud_metadata: + + +- module: system + period: 10s + metricsets: + - cpu + - load + - memory + - network + - process + - process_summary + #- core + #- diskio + #- socket + processes: ['.*'] + process.include_top_n: + by_cpu: 5 # include top 5 processes by CPU + by_memory: 5 # include top 5 processes by memory + + - module: system + period: 1m + metricsets: + - filesystem + - fsstat + processors: + - drop_event.when.regexp: + system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host|lib)($|/)' + +- module: kubernetes + metricsets: + - node + - system + - pod + - container + - volume + period: 10s + host: ${NODE_NAME} + hosts: ["https://${HOSTNAME}:10250"] + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + ssl.verification_mode: "none" + # If using Red Hat OpenShift remove ssl.verification_mode entry and + # uncomment these settings: + #ssl.certificate_authorities: + #- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt + - module: kubernetes + metricsets: + - proxy + period: 10s + host: ${NODE_NAME} + hosts: ["localhost:10249"] + + +*/ diff --git a/pkg/controller/common/beat/metricbeat/metricbeat.go b/pkg/controller/common/beat/metricbeat/metricbeat.go new file mode 100644 index 0000000000..d437a806cf --- /dev/null +++ b/pkg/controller/common/beat/metricbeat/metricbeat.go @@ -0,0 +1,209 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package metricbeat + +import ( + "crypto/sha256" + "fmt" + "hash" + + "go.elastic.co/apm" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" + commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" +) + +const ( + Type commonbeat.Type = "metricbeat" + + DockerSockVolumeName = "dockersock" + DockerSockPath = "/var/run/docker.sock" + DockerSockMountPath = "/var/run/docker.sock" + + ProcVolumeName = "proc" + ProcPath = "/proc" + ProcMountPath = "/hostfs/proc" + + CGroupVolumeName = "cgroup" + CGroupPath = "/sys/fs/cgroup" + CGroupMountPath = "/hostfs/sys/fs/cgroup" + + HostMetricbeatDataVolumeName = "data" + HostMetricbeatDataPathTemplate = "/var/lib/%s/%s/metricbeat-data" + HostMetricbeatDataMountPath = "/usr/share/metricbeat/data" + + ConfigVolumeName = "config" + ConfigFileName = "metricbeat.yml" + ConfigMountPath = "/etc/metricbeat.yml" + + CAVolumeName = "es-certs" +) + +var ( + defaultResources = corev1.ResourceRequirements{ + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + } +) + +type Driver struct { + commonbeat.DriverParams + commonbeat.Driver +} + +func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { + return &Driver{DriverParams: params} +} + +func (fd *Driver) Reconcile() commonbeat.DriverResults { + results := commonbeat.NewDriverResults(fd.Context) + + if err := commonbeat.SetupAutodiscoverRBAC(fd.Context, fd.Logger, fd.Client, fd.Owner, fd.Labels); err != nil { + results.WithError(err) + } + + checksum := sha256.New224() + err := commonbeat.ReconcileConfig( + fd.DriverParams, + "metricbeat.yml", + defaultConfig, + checksum) + if err != nil { + results.WithError(err) + return results + } + + if driverStatus, err := doReconcile(fd.DriverParams, checksum); err != nil { + if apierrors.IsConflict(err) { + fd.Logger.V(1).Info("Conflict while updating") + results.WithResult(reconcile.Result{Requeue: true}) + } + results.WithError(err) + } else { + results.Status = &driverStatus + } + + return results +} + +func doReconcile(dp commonbeat.DriverParams, checksum hash.Hash) (commonbeat.DriverStatus, error) { + span, _ := apm.StartSpan(dp.Context, "reconcile_daemonSet", tracing.SpanTypeApp) + defer span.End() + + podTemplate := dp.DaemonSet.PodTemplate + + builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). + WithTerminationGracePeriod(30). + WithDockerImage(dp.Image, container.ImageRepository(container.MetricbeatImage, dp.Version)). + WithEnv(corev1.EnvVar{ + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }}). + WithResources(defaultResources). + WithArgs("-e", "-c", ConfigMountPath, "-system.hostfs=/hostfs"). + WithHostNetwork(). + WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). + WithSecurityContext(corev1.SecurityContext{ + RunAsUser: pointer.Int64(0), + }). + WithAutomountServiceAccountToken(). + WithLabels(map[string]string{ + commonbeat.ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), + commonbeat.VersionLabelName: dp.Version}) + + // If SA is already provided, assume that for this resource (despite operator configuration) the user took the + // responsibility of configuring RBAC. Otherwise, use the default. + if commonbeat.ShouldSetupAutodiscoverRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { + builder.WithServiceAccount(commonbeat.AutodiscoverServiceAccountName) + } + + configVolume := volume.NewSecretVolume( + dp.Namer.ConfigSecretName(string(Type), dp.Owner.GetName()), + ConfigVolumeName, + ConfigMountPath, + ConfigFileName, + 0600) + + dockerSockVolume := volume.NewHostVolume(DockerSockVolumeName, DockerSockPath, DockerSockMountPath, false, corev1.HostPathUnset) + procVolume := volume.NewReadOnlyHostVolume(ProcVolumeName, ProcPath, ProcMountPath) + cgroupVolume := volume.NewReadOnlyHostVolume(CGroupVolumeName, CGroupPath, CGroupMountPath) + + kibanaCa := volume.NewSecretVolumeWithMountPath( + "kibana-sample-kb-http-certs-public", + "kibanaca", + "/mnt/elastic-internal/kibana-certs") + + hostMetricbeatDataPath := fmt.Sprintf(HostMetricbeatDataPathTemplate, dp.Owner.GetNamespace(), dp.Owner.GetName()) + metricbeatDataVolume := volume.NewHostVolume( + HostMetricbeatDataVolumeName, + hostMetricbeatDataPath, + HostMetricbeatDataMountPath, + false, + corev1.HostPathDirectoryOrCreate) + + volumes := []volume.VolumeLike{ + configVolume, + dockerSockVolume, + procVolume, + cgroupVolume, + metricbeatDataVolume, + kibanaCa, + } + + if dp.Associated.AssociationConf().IsConfigured() { + volumes = append(volumes, volume.NewSelectiveSecretVolumeWithMountPath( + dp.Associated.AssociationConf().CASecretName, + CAVolumeName, + commonbeat.CAMountPath, + []string{commonbeat.CAFileName})) + } + + for _, v := range volumes { + builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) + } + + builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) + ds := daemonset.New(builder.PodTemplate, dp.Namer.Name(string(Type), dp.Owner.GetName()), dp.Owner, dp.Selectors) + if err := controllerutil.SetControllerReference(dp.Owner, &ds, scheme.Scheme); err != nil { + return commonbeat.DriverStatus{}, err + } + + reconciled, err := daemonset.Reconcile(dp.Client, ds, dp.Owner) + if err != nil { + return commonbeat.DriverStatus{}, err + } + + ready := reconciled.Status.NumberReady + desired := reconciled.Status.DesiredNumberScheduled + + return commonbeat.DriverStatus{ + ExpectedNodes: desired, + AvailableNodes: ready, + Health: health.CalculateHealth(dp.Associated, ready, desired), + Association: dp.Associated.AssociationStatus(), + }, nil +} diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 19f4dcb522..01946ab1f5 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -20,14 +20,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" v1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common" commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" @@ -66,47 +63,17 @@ func (fd *Driver) Reconcile() commonbeat.DriverResults { // this is a poc to show how it could look like, todo should be refactored results := commonbeat.NewDriverResults(fd.Context) - cfg := settings.NewCanonicalConfig() - - if err := commonbeat.SetOutput(cfg, fd.Client, fd.Associated); err != nil { - results.WithError(err) - } - - if fd.Config != nil { - userCfg, err := settings.NewCanonicalConfigFrom(fd.Config.Data) - if err != nil { - results.WithError(err) - } - if err = cfg.MergeWith(userCfg); err != nil { - results.WithError(err) - } - } - - cfgBytes, err := cfg.Render() + checksum := sha256.New224() + err := commonbeat.ReconcileConfig( + fd.DriverParams, + "otherbeat.yml", + nil, // todo check + checksum) if err != nil { results.WithError(err) + return results } - // create resource - expected := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: fd.Owner.GetNamespace(), - Name: fd.Namer.ConfigSecretName(string(Type), fd.Owner.GetName()), - Labels: common.AddCredentialsLabel(fd.Labels), - }, - Data: map[string][]byte{ - "otherbeat.yml": cfgBytes, - }, - } - - // reconcile - if _, err = reconciler.ReconcileSecret(fd.Client, expected, fd.Owner); err != nil { - results.WithError(err) - } - - checksum := sha256.New224() - _, _ = checksum.Write(cfgBytes) - // reconcile pod vehicle if driverStatus, err := doReconcile( fd.Context, @@ -147,6 +114,8 @@ func doReconcile(ctx context.Context, podTemplate := deploymentSpec.PodTemplate + //image has to be there + builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). WithTerminationGracePeriod(30). WithDockerImage(image, ""). diff --git a/pkg/controller/common/container/container.go b/pkg/controller/common/container/container.go index fcb5cf4a4a..07bdd9afe1 100644 --- a/pkg/controller/common/container/container.go +++ b/pkg/controller/common/container/container.go @@ -26,6 +26,7 @@ const ( // TODO EnterpriseSearchImage Image = "TODO" FilebeatImage Image = "beats/filebeat" + MetricbeatImage Image = "beats/metricbeat" ) // ImageRepository returns the full container image name by concatenating the current container registry and the image path with the given version. From 40a35c57a090950a26a2a4e364d65ce7c01bf581 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 20 May 2020 08:54:06 +0200 Subject: [PATCH 12/81] Expand and improve Beat samples --- config/samples/beat/filebeat_es_kibana.yaml | 55 ++++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/config/samples/beat/filebeat_es_kibana.yaml b/config/samples/beat/filebeat_es_kibana.yaml index 2099d23da2..188d28e059 100644 --- a/config/samples/beat/filebeat_es_kibana.yaml +++ b/config/samples/beat/filebeat_es_kibana.yaml @@ -6,12 +6,12 @@ metadata: spec: version: 7.6.0 nodeSets: - - name: default - count: 1 - config: - # This setting could have performance implications for production clusters. - # See: https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-virtual-memory.html - node.store.allow_mmap: false + - name: default + count: 1 + config: + # This setting could have performance implications for production clusters. + # See: https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-virtual-memory.html + node.store.allow_mmap: false --- apiVersion: kibana.k8s.elastic.co/v1 kind: Kibana @@ -43,9 +43,50 @@ spec: apiVersion: beat.k8s.elastic.co/v1beta1 kind: Beat metadata: - name: beat-sample + name: filebeat-sample spec: type: filebeat version: 7.6.0 elasticsearchRef: name: elasticsearch-sample + # to target only a specific namespace for logging, use: + #config: + # filebeat.autodiscover.providers: + # - node: ${NODE_NAME} + # type: kubernetes + # templates: + # - condition.equals.kubernetes.namespace: log-this + # config: + # - paths: ["/var/log/containers/*${data.kubernetes.container.id}.log"] + # type: container + # processors: + # - add_cloud_metadata: null + # - add_host_metadata: null +--- +apiVersion: beat.k8s.elastic.co/v1beta1 +kind: Beat +metadata: + name: metricbeat-sample +spec: + type: metricbeat + version: 7.6.0 + elasticsearchRef: + name: elasticsearch-sample + + # to provide your own image, use: + #image: docker.mycompany.com/metricbeat:7.6.0 + + # to use deployment instead of default daemonset, use: + #deployment: + # podTemplate: + # metadata: + # labels: + # foo: bar + # spec: + # containers: + # - name: metricbeat + # resources: + # limits: + # memory: 1Gi + # cpu: 1 + From 43057806b2a6ff7378907709a0d60760ad2a4d35 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 20 May 2020 08:55:10 +0200 Subject: [PATCH 13/81] Refactor config and pod vehicle reconcilation --- config/crds/all-crds.yaml | 1 + .../crds/bases/beat.k8s.elastic.co_beats.yaml | 1 + pkg/apis/beat/v1beta1/beat_types.go | 1 + pkg/apis/beat/v1beta1/webhook.go | 2 +- pkg/controller/beat/controller.go | 14 +- pkg/controller/common/beat/autodiscover.go | 33 ++- pkg/controller/common/beat/common.go | 4 +- pkg/controller/common/beat/config.go | 203 +++++++++++++++- pkg/controller/common/beat/filebeat/config.go | 39 ++- .../common/beat/filebeat/filebeat.go | 182 +++----------- .../common/beat/metricbeat/config.go | 222 +++++------------- .../common/beat/metricbeat/metricbeat.go | 191 +++------------ .../common/beat/otherbeat/otherbeat.go | 171 +------------- .../common/defaults/pod_template.go | 22 +- .../common/settings/canonical_config.go | 10 + pkg/controller/common/volume/persistent.go | 51 ++++ pkg/utils/pointer/numeric.go | 8 + 17 files changed, 462 insertions(+), 693 deletions(-) create mode 100644 pkg/controller/common/volume/persistent.go diff --git a/config/crds/all-crds.yaml b/config/crds/all-crds.yaml index dc789342e0..26c3495258 100644 --- a/config/crds/all-crds.yaml +++ b/config/crds/all-crds.yaml @@ -547,6 +547,7 @@ spec: description: Type is the type of the Beat to deploy. Any string can be used, but well-known types will be recognized and will allow to provide sane default configurations. + maxLength: 20 type: string version: description: Version of the Beat. diff --git a/config/crds/bases/beat.k8s.elastic.co_beats.yaml b/config/crds/bases/beat.k8s.elastic.co_beats.yaml index a1a626d8d5..42b753c0ba 100644 --- a/config/crds/bases/beat.k8s.elastic.co_beats.yaml +++ b/config/crds/bases/beat.k8s.elastic.co_beats.yaml @@ -12171,6 +12171,7 @@ spec: description: Type is the type of the Beat to deploy. Any string can be used, but well-known types will be recognized and will allow to provide sane default configurations. + maxLength: 20 type: string version: description: Version of the Beat. diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index 8ced04fdfe..29db73f332 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -16,6 +16,7 @@ import ( type BeatSpec struct { // Type is the type of the Beat to deploy. Any string can be used, but well-known types will be recognized and // will allow to provide sane default configurations. + // +kubebuilder:validation:MaxLength=20 Type string `json:"type"` // Version of the Beat. diff --git a/pkg/apis/beat/v1beta1/webhook.go b/pkg/apis/beat/v1beta1/webhook.go index 48ff48b225..82e7633a8c 100644 --- a/pkg/apis/beat/v1beta1/webhook.go +++ b/pkg/apis/beat/v1beta1/webhook.go @@ -119,7 +119,7 @@ func checkAtMostOneDeploymentOption(b *Beat) field.ErrorList { } func checkImageIfTypeUnknown(b *Beat) field.ErrorList { - knownTypes := []string{"filebeat"} + knownTypes := []string{"filebeat", "metricbeat"} if !stringsutil.StringInSlice(b.Spec.Type, knownTypes) && b.Spec.Image == "" { return field.ErrorList{ diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index bf296498a5..50f219768b 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -179,10 +179,10 @@ func (r *ReconcileBeat) doReconcile(ctx context.Context, beat beatv1beta1.Beat) return results.WithError(err) } - result := newDriver(ctx, r.Client, beat).Reconcile() - results.WithResults(result.Results) + driverResults := newDriver(ctx, r.Client, beat).Reconcile() + results.WithResults(driverResults.Results) - err := r.updateStatus(result.Status, beat) + err := r.updateStatus(driverResults.Status, beat) results.WithError(err) if err != nil && apierrors.IsConflict(err) { log.V(1).Info("Conflict while updating status", "namespace", beat.Namespace, "beat_name", beat.Name) @@ -243,13 +243,13 @@ func newDriver(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) co func newDriverParams(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) commonbeat.DriverParams { spec := beat.Spec - var ds commonbeat.DaemonSetSpec + var ds *commonbeat.DaemonSetSpec if spec.DaemonSet != nil { - ds = commonbeat.DaemonSetSpec{PodTemplate: spec.DaemonSet.PodTemplate} + ds = &commonbeat.DaemonSetSpec{PodTemplate: spec.DaemonSet.PodTemplate} } - var d commonbeat.DeploymentSpec + var d *commonbeat.DeploymentSpec if spec.Deployment != nil { - d = commonbeat.DeploymentSpec{Replicas: spec.Deployment.Replicas, PodTemplate: spec.Deployment.PodTemplate} + d = &commonbeat.DeploymentSpec{Replicas: spec.Deployment.Replicas, PodTemplate: spec.Deployment.PodTemplate} } return commonbeat.DriverParams{ diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 6868914d0c..966533fed2 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -6,6 +6,7 @@ package beat import ( "context" + "fmt" "reflect" "github.com/go-logr/logr" @@ -24,9 +25,9 @@ import ( ) const ( - AutodiscoverServiceAccountName = "elastic-operator-autodiscover" - autodiscoverClusterRoleBindingName = "elastic-operator-autodiscover" - autodiscoverClusterRoleName = "elastic-operator-autodiscover" + serviceAccountNameTemplate = "elastic-operator-autodiscover-%s" + clusterRoleBindingNameTemplate = "elastic-operator-autodiscover-%s-%s" + clusterRoleName = "elastic-operator-autodiscover" ) var ( @@ -84,19 +85,19 @@ func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1. func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[string]string) error { sa := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: AutodiscoverServiceAccountName, + Name: ServiceAccountName(owner.GetName()), Namespace: owner.GetNamespace(), Labels: labels, }, } - return reconcile(client, sa, owner) + return doReconcile(client, sa, owner) } func reconcileClusterRole(client k8s.Client) error { role := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ - Name: autodiscoverClusterRoleName, + Name: clusterRoleName, }, Rules: []rbacv1.PolicyRule{ { @@ -107,32 +108,32 @@ func reconcileClusterRole(client k8s.Client) error { }, } - return reconcile(client, role, nil) + return doReconcile(client, role, nil) } func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { binding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: autodiscoverClusterRoleBindingName, + Name: ClusterRoleBindingName(owner.GetNamespace(), owner.GetName()), }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", - Name: AutodiscoverServiceAccountName, + Name: ServiceAccountName(owner.GetName()), Namespace: owner.GetNamespace(), }, }, RoleRef: rbacv1.RoleRef{ APIGroup: rbacv1.GroupName, Kind: "ClusterRole", - Name: autodiscoverClusterRoleName, + Name: clusterRoleName, }, } - return reconcile(client, binding, nil) + return doReconcile(client, binding, nil) } -func reconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error { +func doReconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error { // labels set here must be exactly the same for all callers in particular namespace // otherwise they'll just keep trying to override each other objMeta, err := meta.Accessor(obj) @@ -159,3 +160,11 @@ func reconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error }, }) } + +func ClusterRoleBindingName(namespace, name string) string { + return fmt.Sprintf(clusterRoleBindingNameTemplate, namespace, name) +} + +func ServiceAccountName(name string) string { + return fmt.Sprintf(serviceAccountNameTemplate, name) +} diff --git a/pkg/controller/common/beat/common.go b/pkg/controller/common/beat/common.go index 38c1cb3db4..10338cd200 100644 --- a/pkg/controller/common/beat/common.go +++ b/pkg/controller/common/beat/common.go @@ -50,8 +50,8 @@ type DriverParams struct { Labels map[string]string - DaemonSet DaemonSetSpec - Deployment DeploymentSpec + DaemonSet *DaemonSetSpec + Deployment *DeploymentSpec Selectors map[string]string } diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index dbf9a0bb36..9405496756 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -5,12 +5,28 @@ package beat import ( + "crypto/sha256" + "fmt" "hash" "path" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" + commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" @@ -21,8 +37,12 @@ import ( ) const ( - CAMountPath = "/mnt/elastic-internal/es-certs/" - CAFileName = "ca.crt" + CAVolumeName = "es-certs" + CAMountPath = "/mnt/elastic-internal/es-certs/" + CAFileName = "ca.crt" + + ConfigVolumeName = "config" + ConfigMountDirPath = "/etc" // ConfigChecksumLabel is a label used to store beats config checksum. ConfigChecksumLabel = "beat.k8s.elastic.co/config-checksum" @@ -31,6 +51,49 @@ const ( VersionLabelName = "beat.k8s.elastic.co/version" ) +var ( + defaultResources = corev1.ResourceRequirements{ + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + } +) + +func Reconcile(fd DriverParams, defaultConfig *settings.CanonicalConfig, defaultImage container.Image, f func(builder *defaults.PodTemplateBuilder)) DriverResults { + results := NewDriverResults(fd.Context) + + if err := SetupAutodiscoverRBAC(fd.Context, fd.Logger, fd.Client, fd.Owner, fd.Labels); err != nil { + results.WithError(err) + } + + checksum := sha256.New224() + err := reconcileConfig( + fd, + defaultConfig, + checksum) + if err != nil { + results.WithError(err) + return results + } + + if driverStatus, err := reconcilePodVehicle(fd, defaultImage, f, checksum); err != nil { + if apierrors.IsConflict(err) { + fd.Logger.V(1).Info("Conflict while updating") + results.WithResult(reconcile.Result{Requeue: true}) + } + results.WithError(err) + } else { + results.Status = &driverStatus + } + + return results +} + // SetOutput will set output section in Beat config according to association configuration. func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { if associated.AssociationConf().IsConfigured() { @@ -60,6 +123,10 @@ func build( userConfig *commonv1.Config) ([]byte, error) { cfg := settings.NewCanonicalConfig() + if defaultConfig == nil && userConfig == nil { + return nil, fmt.Errorf("both default and user configs are nil") + } + if err := setOutput(cfg, client, associated); err != nil { return nil, err } @@ -83,9 +150,8 @@ func build( return cfg.Render() } -func ReconcileConfig( +func reconcileConfig( params DriverParams, - configFileName string, defaultConfig *settings.CanonicalConfig, checksum hash.Hash) error { @@ -102,7 +168,7 @@ func ReconcileConfig( Labels: common.AddCredentialsLabel(params.Labels), }, Data: map[string][]byte{ - configFileName: cfgBytes, + configFileName(params.Type): cfgBytes, }, } @@ -126,3 +192,130 @@ func ReconcileConfig( return nil } + +func configFileName(typ string) string { + return fmt.Sprintf("%s.yml", typ) +} + +func ConfigMountPath(typ string) string { + return path.Join(ConfigMountDirPath, configFileName(typ)) +} + +func reconcilePodVehicle(dp DriverParams, defaultImage container.Image, f func(builder *defaults.PodTemplateBuilder), checksum hash.Hash) (DriverStatus, error) { + var podTemplate corev1.PodTemplateSpec + if dp.DaemonSet != nil { + podTemplate = dp.DaemonSet.PodTemplate + } else if dp.Deployment != nil { + podTemplate = dp.Deployment.PodTemplate + } + builder := defaults.NewPodTemplateBuilder(podTemplate, dp.Type). + WithTerminationGracePeriod(30). + WithEnv(corev1.EnvVar{ + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }}). + WithResources(defaultResources). + WithHostNetwork(). + WithLabels(map[string]string{ + ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), + VersionLabelName: dp.Version}). + WithDockerImage(dp.Image, container.ImageRepository(defaultImage, dp.Version)). + WithArgs("-e", "-c", ConfigMountPath(dp.Type)). + WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). + WithSecurityContext(corev1.SecurityContext{ + RunAsUser: pointer.Int64(0), + }). + WithAutomountServiceAccountToken() + + if ShouldSetupAutodiscoverRBAC() { + autodiscoverServiceAccountName := ServiceAccountName(dp.Owner.GetName()) + // If SA is already provided, the call will be no-op. This is fine as we then assume + // that for this resource (despite operator configuration) the user took the responsibility + // of configuring RBAC. + builder.WithServiceAccount(autodiscoverServiceAccountName) + } + + volumes := []volume.VolumeLike{ + volume.NewSecretVolume( + dp.Namer.ConfigSecretName(dp.Type, dp.Owner.GetName()), + ConfigVolumeName, + ConfigMountPath(dp.Type), + configFileName(dp.Type), + 0600), + } + + if dp.Associated.AssociationConf().IsConfigured() { + volumes = append(volumes, volume.NewSelectiveSecretVolumeWithMountPath( + dp.Associated.AssociationConf().CASecretName, + CAVolumeName, + CAMountPath, + []string{CAFileName})) + } + + for _, v := range volumes { + builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) + } + + builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) + + if f != nil { + f(builder) + } + + name := dp.Namer.Name(dp.Type, dp.Owner.GetName()) + ds := daemonset.New(builder.PodTemplate, name, dp.Owner, dp.Selectors) + d := deployment.New(deployment.Params{ + Name: name, + Namespace: dp.Owner.GetNamespace(), + Selector: dp.Selectors, + Labels: dp.Labels, + PodTemplateSpec: builder.PodTemplate, + Replicas: pointer.Int32OrDefault(dp.Deployment.Replicas, 1), + }) + + var ready, desired int32 + var toDelete runtime.Object + switch { + case dp.DaemonSet != nil: + { + if err := controllerutil.SetControllerReference(dp.Owner, &ds, scheme.Scheme); err != nil { + return DriverStatus{}, err + } + reconciled, err := daemonset.Reconcile(dp.Client, ds, dp.Owner) + if err != nil { + return DriverStatus{}, err + } + ready = reconciled.Status.NumberReady + desired = reconciled.Status.DesiredNumberScheduled + toDelete = &d + } + case dp.Deployment != nil: + { + if err := controllerutil.SetControllerReference(dp.Owner, &d, scheme.Scheme); err != nil { + return DriverStatus{}, err + } + // sync + reconciled, err := deployment.Reconcile(dp.Client, d, dp.Owner) + if err != nil { + return DriverStatus{}, err + } + ready = reconciled.Status.ReadyReplicas + desired = reconciled.Status.Replicas + toDelete = &ds + } + } + + if err := dp.Client.Delete(toDelete); err != nil && !apierrors.IsNotFound(err) { + return DriverStatus{}, err + } + + return DriverStatus{ + ExpectedNodes: desired, + AvailableNodes: ready, + Health: health.CalculateHealth(dp.Associated, ready, desired), + Association: dp.Associated.AssociationStatus(), + }, nil +} diff --git a/pkg/controller/common/beat/filebeat/config.go b/pkg/controller/common/beat/filebeat/config.go index 805cd7d7f6..a1b2037632 100644 --- a/pkg/controller/common/beat/filebeat/config.go +++ b/pkg/controller/common/beat/filebeat/config.go @@ -9,27 +9,20 @@ import ( ) var ( - defaultConfig = settings.MustCanonicalConfig(map[string]interface{}{ - "filebeat": map[string]interface{}{ - "autodiscover": map[string]interface{}{ - "providers": []map[string]interface{}{ - { - "type": "kubernetes", - "node": "${NODE_NAME}", - "hints": map[string]interface{}{ - "enabled": "true", - "default_config": map[string]interface{}{ - "type": "container", - "paths": []string{"/var/log/containers/*${data.kubernetes.container.id}.log"}, - }, - }, - }, - }, - }, - }, - "processors": []map[string]interface{}{ - {"add_cloud_metadata": nil}, - {"add_host_metadata": nil}, - }, - }) + defaultConfig = settings.MustParseConfig([]byte( + `filebeat: + autodiscover: + providers: + - hints: + default_config: + paths: + - /var/log/containers/*${data.kubernetes.container.id}.log + type: container + enabled: "true" + node: ${NODE_NAME} + type: kubernetes +processors: +- add_cloud_metadata: null +- add_host_metadata: null +`)) ) diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/common/beat/filebeat/filebeat.go index 0bd01ed400..95e49c07e7 100644 --- a/pkg/controller/common/beat/filebeat/filebeat.go +++ b/pkg/controller/common/beat/filebeat/filebeat.go @@ -5,27 +5,13 @@ package filebeat import ( - "crypto/sha256" "fmt" - "hash" - - "go.elastic.co/apm" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/reconcile" commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" - commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" + corev1 "k8s.io/api/core/v1" ) const ( @@ -46,25 +32,6 @@ const ( HostFilebeatDataVolumeName = "data" HostFilebeatDataPathTemplate = "/var/lib/%s/%s/filebeat-data" HostFilebeatDataMountPath = "/usr/share/filebeat/data" - - ConfigVolumeName = "config" - ConfigFileName = "filebeat.yml" - ConfigMountPath = "/etc/filebeat.yml" - - CAVolumeName = "es-certs" -) - -var ( - defaultResources = corev1.ResourceRequirements{ - Limits: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - Requests: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - } ) type Driver struct { @@ -76,129 +43,36 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { return &Driver{DriverParams: params} } -func (fd *Driver) Reconcile() commonbeat.DriverResults { - results := commonbeat.NewDriverResults(fd.Context) - - if err := commonbeat.SetupAutodiscoverRBAC(fd.Context, fd.Logger, fd.Client, fd.Owner, fd.Labels); err != nil { - results.WithError(err) - } - - checksum := sha256.New224() - err := commonbeat.ReconcileConfig( - fd.DriverParams, - ConfigFileName, - defaultConfig, - checksum) - if err != nil { - results.WithError(err) - return results - } - - // reconcile pod vehicle - if driverStatus, err := doReconcile(fd.DriverParams, checksum); err != nil { - if apierrors.IsConflict(err) { - fd.Logger.V(1).Info("Conflict while updating") - results.WithResult(reconcile.Result{Requeue: true}) +func (d *Driver) Reconcile() commonbeat.DriverResults { + f := func(builder *defaults.PodTemplateBuilder) { + containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) + containersLogsVolume := volume.NewReadOnlyHostVolume(HostContainersLogsVolumeName, HostContainersLogsPath, HostContainersLogsMountPath) + podsLogsVolume := volume.NewReadOnlyHostVolume(HostPodsLogsVolumeName, HostPodsLogsPath, HostPodsLogsMountPath) + hostFilebeatDataPath := fmt.Sprintf(HostFilebeatDataPathTemplate, d.Owner.GetNamespace(), d.Owner.GetName()) + filebeatDataVolume := volume.NewHostVolume( + HostFilebeatDataVolumeName, + hostFilebeatDataPath, + HostFilebeatDataMountPath, + false, + corev1.HostPathDirectoryOrCreate) + + for _, volume := range []volume.VolumeLike{ + containersVolume, + containersLogsVolume, + podsLogsVolume, + filebeatDataVolume, + } { + builder.WithVolumes(volume.Volume()).WithVolumeMounts(volume.VolumeMount()) } - results.WithError(err) - } else { - results.Status = &driverStatus - } - - return results -} - -func doReconcile(dp commonbeat.DriverParams, checksum hash.Hash) (commonbeat.DriverStatus, error) { - span, _ := apm.StartSpan(dp.Context, "reconcile_daemonSet", tracing.SpanTypeApp) - defer span.End() - - podTemplate := dp.DaemonSet.PodTemplate - - builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). - WithTerminationGracePeriod(30). - WithDockerImage(dp.Image, container.ImageRepository(container.FilebeatImage, dp.Version)). - WithEnv(corev1.EnvVar{ - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }}). - WithResources(defaultResources). - WithArgs("-e", "-c", ConfigMountPath). - WithHostNetwork(). - WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). - WithSecurityContext(corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - }). - WithAutomountServiceAccountToken(). - WithLabels(map[string]string{ - commonbeat.ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), - commonbeat.VersionLabelName: dp.Version}) - - // If SA is already provided, assume that for this resource (despite operator configuration) the user took the - // responsibility of configuring RBAC. Otherwise, use the default. - if commonbeat.ShouldSetupAutodiscoverRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { - builder.WithServiceAccount(commonbeat.AutodiscoverServiceAccountName) - } - - containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) - containersLogsVolume := volume.NewReadOnlyHostVolume(HostContainersLogsVolumeName, HostContainersLogsPath, HostContainersLogsMountPath) - podsLogsVolume := volume.NewReadOnlyHostVolume(HostPodsLogsVolumeName, HostPodsLogsPath, HostPodsLogsMountPath) - - hostFilebeatDataPath := fmt.Sprintf(HostFilebeatDataPathTemplate, dp.Owner.GetNamespace(), dp.Owner.GetName()) - filebeatDataVolume := volume.NewHostVolume( - HostFilebeatDataVolumeName, - hostFilebeatDataPath, - HostFilebeatDataMountPath, - false, - corev1.HostPathDirectoryOrCreate) - - configVolume := volume.NewSecretVolume( - dp.Namer.ConfigSecretName(string(Type), dp.Owner.GetName()), - ConfigVolumeName, - ConfigMountPath, - ConfigFileName, - 0600) - - volumes := []volume.VolumeLike{ - containersVolume, - containersLogsVolume, - podsLogsVolume, - filebeatDataVolume, - configVolume, } - if dp.Associated.AssociationConf().IsConfigured() { - volumes = append(volumes, volume.NewSelectiveSecretVolumeWithMountPath( - dp.Associated.AssociationConf().CASecretName, - CAVolumeName, - commonbeat.CAMountPath, - []string{commonbeat.CAFileName})) + if d.DaemonSet == nil && d.Deployment == nil { + d.DaemonSet = &commonbeat.DaemonSetSpec{} } - for _, v := range volumes { - builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) - } - - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) - ds := daemonset.New(builder.PodTemplate, dp.Namer.Name(string(Type), dp.Owner.GetName()), dp.Owner, dp.Selectors) - if err := controllerutil.SetControllerReference(dp.Owner, &ds, scheme.Scheme); err != nil { - return commonbeat.DriverStatus{}, err - } - - reconciled, err := daemonset.Reconcile(dp.Client, ds, dp.Owner) - if err != nil { - return commonbeat.DriverStatus{}, err - } - - ready := reconciled.Status.NumberReady - desired := reconciled.Status.DesiredNumberScheduled - - return commonbeat.DriverStatus{ - ExpectedNodes: desired, - AvailableNodes: ready, - Health: health.CalculateHealth(dp.Associated, ready, desired), - Association: dp.Associated.AssociationStatus(), - }, nil + return commonbeat.Reconcile( + d.DriverParams, + defaultConfig, + container.FilebeatImage, + f) } diff --git a/pkg/controller/common/beat/metricbeat/config.go b/pkg/controller/common/beat/metricbeat/config.go index f1bb7a4b14..fc26f6206e 100644 --- a/pkg/controller/common/beat/metricbeat/config.go +++ b/pkg/controller/common/beat/metricbeat/config.go @@ -9,165 +9,65 @@ import ( ) var ( - defaultConfig = settings.MustCanonicalConfig(map[string]interface{}{ - "metricbeat": map[string]interface{}{ - "autodiscover": map[string]interface{}{ - "providers": []map[string]interface{}{ - { - "type": "kubernetes", - "node": "${NODE_NAME}", - "hints": map[string]interface{}{ - "enabled": "true", - "default_config": map[string]interface{}{}, - }, - }, - }, - }, - "modules": []map[string]interface{}{ - { - "module": "system", - "period": "10s", - "metricsets": []string{"cpu", "load", "memory", "network", "process", "process_summary"}, - "processes": []string{".*"}, - "process.include_top_n": map[string]interface{}{ - "by_cpu": 5, - "by_memory": 5, - }, - }, - { - "module": "system", - "period": "1m", - "metricsets": []string{"filesystem", "fsstat"}, - "processors": []map[string]interface{}{ - { - "drop_event.when.regexp": map[string]interface{}{ - "system.filesystem.mount_point": "^/(sys|cgroup|proc|dev|etc|host|lib)($|/)", - }, - }, - }, - }, - { - "module": "kubernetes", - "period": "10s", - "metricsets": []string{"node", "system", "pod", "container", "volume"}, - "host": "${NODE_NAME}", - "hosts": []string{"https://${HOSTNAME}:10250"}, - "bearer_token_file": "/var/run/secrets/kubernetes.io/serviceaccount/token", - "ssl.verification_mode": "none", - }, - { - "module": "kubernetes", - "period": "10s", - "metricsets": []string{"proxy"}, - "host": "${NODE_NAME}", - "hosts": []string{"https://${HOSTNAME}:10249"}, - }, - }, - }, - "setup.dashboards.enabled": "true", - "setup.kibana.host": "https://kibana-sample-kb-http.default.svc:5601", - "setup.kibana.username": "elastic", - "setup.kibana.password": "x4rVl41N48C6IJS3sW8L27Uf", - "setup.kibana.ssl.certificate_authorities": "/mnt/elastic-internal/kibana-certs/ca.crt", - "processors": []map[string]interface{}{ - {"add_cloud_metadata": nil}, - //{"add_host_metadata": nil}, - }, - }) + defaultConfig = settings.MustParseConfig([]byte( + `metricbeat: + autodiscover: + providers: + - hints: + default_config: null + enabled: "true" + node: ${NODE_NAME} + type: kubernetes + modules: + - module: system + period: 10s + metricsets: + - cpu + - load + - memory + - network + - process + - process_summary + process: + include_top_n: + by_cpu: 5 + by_memory: 5 + processes: + - .* + - module: system + period: 1m + metricsets: + - filesystem + - fsstat + processors: + - drop_event: + when: + regexp: + system: + filesystem: + mount_point: ^/(sys|cgroup|proc|dev|etc|host|lib)($|/) + - module: kubernetes + period: 10s + host: ${NODE_NAME} + hosts: + - https://${HOSTNAME}:10250 + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + ssl: + verification_mode: none + metricsets: + - node + - system + - pod + - container + - volume + - module: kubernetes + period: 10s + host: ${NODE_NAME} + hosts: + - https://${HOSTNAME}:10249 + metricsets: + - proxy +processors: +- add_cloud_metadata: null +`)) ) - -/* -module: kubernetes - metricsets: - - node - - system - - pod - - container - - volume - period: 10s - host: ${NODE_NAME} - hosts: ["https://${HOSTNAME}:10250"] - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - ssl.verification_mode: "none" - # If using Red Hat OpenShift remove ssl.verification_mode entry and - # uncomment these settings: - #ssl.certificate_authorities: - #- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt - - module: kubernetes - metricsets: - - proxy - period: 10s - host: ${NODE_NAME} - hosts: ["localhost:10249"] - -*/ - -/* - metricbeat.config.modules: - # Mounted `metricbeat-daemonset-modules` configmap: - path: ${path.config}/modules.d/*.yml - # Reload module configs as they change: - reload.enabled: false - - # To enable hints based autodiscover uncomment this: - #metricbeat.autodiscover: - # providers: - # - type: kubernetes - # node: ${NODE_NAME} - # hints.enabled: true - - processors: - - add_cloud_metadata: - - -- module: system - period: 10s - metricsets: - - cpu - - load - - memory - - network - - process - - process_summary - #- core - #- diskio - #- socket - processes: ['.*'] - process.include_top_n: - by_cpu: 5 # include top 5 processes by CPU - by_memory: 5 # include top 5 processes by memory - - - module: system - period: 1m - metricsets: - - filesystem - - fsstat - processors: - - drop_event.when.regexp: - system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host|lib)($|/)' - -- module: kubernetes - metricsets: - - node - - system - - pod - - container - - volume - period: 10s - host: ${NODE_NAME} - hosts: ["https://${HOSTNAME}:10250"] - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - ssl.verification_mode: "none" - # If using Red Hat OpenShift remove ssl.verification_mode entry and - # uncomment these settings: - #ssl.certificate_authorities: - #- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt - - module: kubernetes - metricsets: - - proxy - period: 10s - host: ${NODE_NAME} - hosts: ["localhost:10249"] - - -*/ diff --git a/pkg/controller/common/beat/metricbeat/metricbeat.go b/pkg/controller/common/beat/metricbeat/metricbeat.go index d437a806cf..46218966cc 100644 --- a/pkg/controller/common/beat/metricbeat/metricbeat.go +++ b/pkg/controller/common/beat/metricbeat/metricbeat.go @@ -5,27 +5,13 @@ package metricbeat import ( - "crypto/sha256" "fmt" - "hash" - - "go.elastic.co/apm" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/reconcile" commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" - commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" + corev1 "k8s.io/api/core/v1" ) const ( @@ -46,25 +32,6 @@ const ( HostMetricbeatDataVolumeName = "data" HostMetricbeatDataPathTemplate = "/var/lib/%s/%s/metricbeat-data" HostMetricbeatDataMountPath = "/usr/share/metricbeat/data" - - ConfigVolumeName = "config" - ConfigFileName = "metricbeat.yml" - ConfigMountPath = "/etc/metricbeat.yml" - - CAVolumeName = "es-certs" -) - -var ( - defaultResources = corev1.ResourceRequirements{ - Limits: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - Requests: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - } ) type Driver struct { @@ -76,134 +43,44 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { return &Driver{DriverParams: params} } -func (fd *Driver) Reconcile() commonbeat.DriverResults { - results := commonbeat.NewDriverResults(fd.Context) - - if err := commonbeat.SetupAutodiscoverRBAC(fd.Context, fd.Logger, fd.Client, fd.Owner, fd.Labels); err != nil { - results.WithError(err) - } - - checksum := sha256.New224() - err := commonbeat.ReconcileConfig( - fd.DriverParams, - "metricbeat.yml", - defaultConfig, - checksum) - if err != nil { - results.WithError(err) - return results - } - - if driverStatus, err := doReconcile(fd.DriverParams, checksum); err != nil { - if apierrors.IsConflict(err) { - fd.Logger.V(1).Info("Conflict while updating") - results.WithResult(reconcile.Result{Requeue: true}) +func (d *Driver) Reconcile() commonbeat.DriverResults { + f := func(builder *defaults.PodTemplateBuilder) { + dockerSockVolume := volume.NewHostVolume(DockerSockVolumeName, DockerSockPath, DockerSockMountPath, false, corev1.HostPathUnset) + procVolume := volume.NewReadOnlyHostVolume(ProcVolumeName, ProcPath, ProcMountPath) + cgroupVolume := volume.NewReadOnlyHostVolume(CGroupVolumeName, CGroupPath, CGroupMountPath) + + var metricbeatDataVolume volume.VolumeLike + if d.Deployment != nil { + metricbeatDataVolume = volume.NewPersistentVolumeClaim(HostMetricbeatDataVolumeName, HostMetricbeatDataMountPath) + } else { + hostMetricbeatDataPath := fmt.Sprintf(HostMetricbeatDataPathTemplate, d.Owner.GetNamespace(), d.Owner.GetName()) + metricbeatDataVolume = volume.NewHostVolume( + HostMetricbeatDataVolumeName, + hostMetricbeatDataPath, + HostMetricbeatDataMountPath, + false, + corev1.HostPathDirectoryOrCreate) } - results.WithError(err) - } else { - results.Status = &driverStatus - } - - return results -} - -func doReconcile(dp commonbeat.DriverParams, checksum hash.Hash) (commonbeat.DriverStatus, error) { - span, _ := apm.StartSpan(dp.Context, "reconcile_daemonSet", tracing.SpanTypeApp) - defer span.End() - - podTemplate := dp.DaemonSet.PodTemplate - - builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). - WithTerminationGracePeriod(30). - WithDockerImage(dp.Image, container.ImageRepository(container.MetricbeatImage, dp.Version)). - WithEnv(corev1.EnvVar{ - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }}). - WithResources(defaultResources). - WithArgs("-e", "-c", ConfigMountPath, "-system.hostfs=/hostfs"). - WithHostNetwork(). - WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). - WithSecurityContext(corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - }). - WithAutomountServiceAccountToken(). - WithLabels(map[string]string{ - commonbeat.ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), - commonbeat.VersionLabelName: dp.Version}) - - // If SA is already provided, assume that for this resource (despite operator configuration) the user took the - // responsibility of configuring RBAC. Otherwise, use the default. - if commonbeat.ShouldSetupAutodiscoverRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { - builder.WithServiceAccount(commonbeat.AutodiscoverServiceAccountName) - } - - configVolume := volume.NewSecretVolume( - dp.Namer.ConfigSecretName(string(Type), dp.Owner.GetName()), - ConfigVolumeName, - ConfigMountPath, - ConfigFileName, - 0600) - - dockerSockVolume := volume.NewHostVolume(DockerSockVolumeName, DockerSockPath, DockerSockMountPath, false, corev1.HostPathUnset) - procVolume := volume.NewReadOnlyHostVolume(ProcVolumeName, ProcPath, ProcMountPath) - cgroupVolume := volume.NewReadOnlyHostVolume(CGroupVolumeName, CGroupPath, CGroupMountPath) - - kibanaCa := volume.NewSecretVolumeWithMountPath( - "kibana-sample-kb-http-certs-public", - "kibanaca", - "/mnt/elastic-internal/kibana-certs") - - hostMetricbeatDataPath := fmt.Sprintf(HostMetricbeatDataPathTemplate, dp.Owner.GetNamespace(), dp.Owner.GetName()) - metricbeatDataVolume := volume.NewHostVolume( - HostMetricbeatDataVolumeName, - hostMetricbeatDataPath, - HostMetricbeatDataMountPath, - false, - corev1.HostPathDirectoryOrCreate) - - volumes := []volume.VolumeLike{ - configVolume, - dockerSockVolume, - procVolume, - cgroupVolume, - metricbeatDataVolume, - kibanaCa, - } - if dp.Associated.AssociationConf().IsConfigured() { - volumes = append(volumes, volume.NewSelectiveSecretVolumeWithMountPath( - dp.Associated.AssociationConf().CASecretName, - CAVolumeName, - commonbeat.CAMountPath, - []string{commonbeat.CAFileName})) - } - - for _, v := range volumes { - builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) - } + for _, volume := range []volume.VolumeLike{ + dockerSockVolume, + procVolume, + cgroupVolume, + metricbeatDataVolume, + } { + builder.WithVolumes(volume.Volume()).WithVolumeMounts(volume.VolumeMount()) + } - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) - ds := daemonset.New(builder.PodTemplate, dp.Namer.Name(string(Type), dp.Owner.GetName()), dp.Owner, dp.Selectors) - if err := controllerutil.SetControllerReference(dp.Owner, &ds, scheme.Scheme); err != nil { - return commonbeat.DriverStatus{}, err + builder.WithArgs("-e", "-c", commonbeat.ConfigMountPath(d.Type), "-system.hostfs=/hostfs") } - reconciled, err := daemonset.Reconcile(dp.Client, ds, dp.Owner) - if err != nil { - return commonbeat.DriverStatus{}, err + if d.DaemonSet == nil && d.Deployment == nil { + d.DaemonSet = &commonbeat.DaemonSetSpec{} } - ready := reconciled.Status.NumberReady - desired := reconciled.Status.DesiredNumberScheduled - - return commonbeat.DriverStatus{ - ExpectedNodes: desired, - AvailableNodes: ready, - Health: health.CalculateHealth(dp.Associated, ready, desired), - Association: dp.Associated.AssociationStatus(), - }, nil + return commonbeat.Reconcile( + d.DriverParams, + defaultConfig, + container.MetricbeatImage, + f) } diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 01946ab1f5..1e5659a8ad 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -5,51 +5,13 @@ package otherbeat import ( - "context" - "crypto/sha256" - "fmt" - "hash" - - "go.elastic.co/apm" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - v1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" - commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" - "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) const ( Type commonbeat.Type = "otherbeat" ) -var ( - defaultResources = corev1.ResourceRequirements{ - Limits: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - Requests: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - } -) - -var ConfigMountPath = "/etc/otherbeat.yml" - type Driver struct { commonbeat.DriverParams commonbeat.Driver @@ -59,131 +21,10 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { return &Driver{DriverParams: params} } -func (fd *Driver) Reconcile() commonbeat.DriverResults { - // this is a poc to show how it could look like, todo should be refactored - results := commonbeat.NewDriverResults(fd.Context) - - checksum := sha256.New224() - err := commonbeat.ReconcileConfig( - fd.DriverParams, - "otherbeat.yml", - nil, // todo check - checksum) - if err != nil { - results.WithError(err) - return results - } - - // reconcile pod vehicle - if driverStatus, err := doReconcile( - fd.Context, - fd.Client, - fd.Associated, - fd.Owner, - fd.Labels, - fd.Namer, - checksum, - fd.Image, - fd.Version, - fd.Selectors, - fd.Deployment); err != nil { - if apierrors.IsConflict(err) { - fd.Logger.V(1).Info("Conflict while updating") - results.WithResult(reconcile.Result{Requeue: true}) - } - results.WithError(err) - } else { - results.Status = &driverStatus - } - return results -} - -func doReconcile(ctx context.Context, - client k8s.Client, - associated v1.Associated, - owner metav1.Object, - labels map[string]string, - namer commonbeat.Namer, - checksum hash.Hash, - image, version string, - selectors map[string]string, - deploymentSpec commonbeat.DeploymentSpec, -) (commonbeat.DriverStatus, error) { - span, _ := apm.StartSpan(ctx, "reconcile_deployment", tracing.SpanTypeApp) - defer span.End() - - podTemplate := deploymentSpec.PodTemplate - - //image has to be there - - builder := defaults.NewPodTemplateBuilder(podTemplate, string(Type)). - WithTerminationGracePeriod(30). - WithDockerImage(image, ""). - WithEnv(corev1.EnvVar{ - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }}). - WithResources(defaultResources). - WithArgs("-e", "-c", ConfigMountPath). - WithHostNetwork(). - WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). - WithSecurityContext(corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - }). - WithAutomountServiceAccountToken(). - WithLabels(map[string]string{ - commonbeat.ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), - commonbeat.VersionLabelName: version}) - - // If SA is already provided, assume that for this resource (despite operator configuration) the user took the - // responsibility of configuring RBAC. Otherwise, use the default. - if commonbeat.ShouldSetupAutodiscoverRBAC() && builder.PodTemplate.Spec.ServiceAccountName == "" { - builder.WithServiceAccount(commonbeat.AutodiscoverServiceAccountName) - } - - configVolume := volume.NewSecretVolume( - namer.ConfigSecretName(string(Type), owner.GetName()), - "config", ConfigMountPath, "otherbeat.yml", 0600) - esCaVolume := volume.NewSelectiveSecretVolumeWithMountPath(associated.AssociationConf().CASecretName, "es-certs", commonbeat.CAMountPath, []string{commonbeat.CAFileName}) - - for _, v := range []volume.VolumeLike{ - configVolume, - esCaVolume, - } { - builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) - } - - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(labels, builder.PodTemplate)) - - d := deployment.New(deployment.Params{ - Name: namer.Name(string(Type), owner.GetName()), - Namespace: owner.GetNamespace(), - Selector: selectors, - Labels: labels, - PodTemplateSpec: builder.PodTemplate, - Replicas: 1, - }) - - if err := controllerutil.SetControllerReference(owner, &d, scheme.Scheme); err != nil { - return commonbeat.DriverStatus{}, err - } - - // sync - reconciled, err := deployment.Reconcile(client, d, owner) - if err != nil { - return commonbeat.DriverStatus{}, err - } - - ready := reconciled.Status.ReadyReplicas - desired := reconciled.Status.Replicas - - return commonbeat.DriverStatus{ - ExpectedNodes: desired, - AvailableNodes: ready, - Health: health.CalculateHealth(associated, ready, desired), - Association: associated.AssociationStatus(), - }, nil +func (d *Driver) Reconcile() commonbeat.DriverResults { + return commonbeat.Reconcile( + d.DriverParams, + nil, + "", + nil) } diff --git a/pkg/controller/common/defaults/pod_template.go b/pkg/controller/common/defaults/pod_template.go index dab18b9ff8..7f8cbc057e 100644 --- a/pkg/controller/common/defaults/pod_template.go +++ b/pkg/controller/common/defaults/pod_template.go @@ -339,12 +339,16 @@ func (b *PodTemplateBuilder) WithPreStopHook(handler corev1.Handler) *PodTemplat } func (b *PodTemplateBuilder) WithArgs(args ...string) *PodTemplateBuilder { - b.Container.Args = args + if b.Container.Args == nil { + b.Container.Args = args + } return b } func (b *PodTemplateBuilder) WithServiceAccount(serviceAccount string) *PodTemplateBuilder { - b.PodTemplate.Spec.ServiceAccountName = serviceAccount + if b.PodTemplate.Spec.ServiceAccountName == "" { + b.PodTemplate.Spec.ServiceAccountName = serviceAccount + } return b } @@ -354,17 +358,23 @@ func (b *PodTemplateBuilder) WithHostNetwork() *PodTemplateBuilder { } func (b *PodTemplateBuilder) WithDNSPolicy(dnsPolicy corev1.DNSPolicy) *PodTemplateBuilder { - b.PodTemplate.Spec.DNSPolicy = dnsPolicy + if b.PodTemplate.Spec.DNSPolicy == "" { + b.PodTemplate.Spec.DNSPolicy = dnsPolicy + } return b } func (b *PodTemplateBuilder) WithSecurityContext(securityContext corev1.SecurityContext) *PodTemplateBuilder { - b.Container.SecurityContext = &securityContext + if b.Container.SecurityContext == nil { + b.Container.SecurityContext = &securityContext + } return b } func (b *PodTemplateBuilder) WithAutomountServiceAccountToken() *PodTemplateBuilder { - t := true - b.PodTemplate.Spec.AutomountServiceAccountToken = &t + if b.PodTemplate.Spec.AutomountServiceAccountToken == nil { + t := true + b.PodTemplate.Spec.AutomountServiceAccountToken = &t + } return b } diff --git a/pkg/controller/common/settings/canonical_config.go b/pkg/controller/common/settings/canonical_config.go index 390c8a4ffd..0135b2b4ab 100644 --- a/pkg/controller/common/settings/canonical_config.go +++ b/pkg/controller/common/settings/canonical_config.go @@ -89,6 +89,16 @@ func ParseConfig(yml []byte) (*CanonicalConfig, error) { return fromConfig(config), nil } +// MustParseConfig parses the given configuration content into a CanonicalConfig. +// Expects content to be in YAML format. Panis on error. +func MustParseConfig(yml []byte) *CanonicalConfig { + config, err := uyaml.NewConfig(yml, Options...) + if err != nil { + panic(err) + } + return fromConfig(config) +} + // SetStrings sets key to string vals in c. An error is returned if key is invalid. func (c *CanonicalConfig) SetStrings(key string, vals ...string) error { if c == nil { diff --git a/pkg/controller/common/volume/persistent.go b/pkg/controller/common/volume/persistent.go new file mode 100644 index 0000000000..2a23b476b1 --- /dev/null +++ b/pkg/controller/common/volume/persistent.go @@ -0,0 +1,51 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package volume + +import ( + corev1 "k8s.io/api/core/v1" +) + +// NewPersistentVolumeClaim creates a new PersistentVolumeClaim struct +func NewPersistentVolumeClaim(name, mountPath string) HostVolume { + return HostVolume{ + name: name, + mountPath: mountPath, + } +} + +// PersistentVolumeClaim defines a persistent volume claim +type PersistentVolumeClaim struct { + name string + mountPath string +} + +// VolumeMount returns the k8s volume mount. +func (pvc PersistentVolumeClaim) VolumeMount() corev1.VolumeMount { + return corev1.VolumeMount{ + Name: pvc.name, + MountPath: pvc.mountPath, + } +} + +// Volume returns the k8s volume. +func (pvc PersistentVolumeClaim) Volume() corev1.Volume { + return corev1.Volume{ + Name: pvc.name, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + // actual claim name will be resolved and fixed right before pod creation + ClaimName: "claim-name-placeholder", + }, + }, + } +} + +// Name returns the name of the volume +func (pvc PersistentVolumeClaim) Name() string { + return pvc.name +} + +var _ VolumeLike = PersistentVolumeClaim{} diff --git a/pkg/utils/pointer/numeric.go b/pkg/utils/pointer/numeric.go index 7c3713645c..f6558c0f37 100644 --- a/pkg/utils/pointer/numeric.go +++ b/pkg/utils/pointer/numeric.go @@ -7,5 +7,13 @@ package pointer // Int32 returns a pointer to an Int32 func Int32(v int32) *int32 { return &v } +// Int32OrDefault returns value pointed to by v, or def if it's nil +func Int32OrDefault(v *int32, def int32) int32 { + if v == nil { + return def + } + return *v +} + // Int64 returns a pointer to an Int64 func Int64(v int64) *int64 { return &v } From d4c538e6beca241f0cb326818ba2a9d36421d77c Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 21 May 2020 07:04:44 +0200 Subject: [PATCH 14/81] Make new linter happy --- cmd/manager/main.go | 2 +- pkg/apis/beat/v1beta1/webhook.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 2a785e3b74..3758d5c4df 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -171,7 +171,7 @@ func init() { Cmd.Flags().Bool( operator.EnableAutodiscoverRBACSetup, true, - fmt.Sprintf("Determines whether the operator should set up role, binding and service account for Beats autodiscover feature"), + "Determines whether the operator should set up role, binding and service account for Beats autodiscover feature", ) // enable using dashed notation in flags and underscores in env diff --git a/pkg/apis/beat/v1beta1/webhook.go b/pkg/apis/beat/v1beta1/webhook.go index 82e7633a8c..f915b2c6e0 100644 --- a/pkg/apis/beat/v1beta1/webhook.go +++ b/pkg/apis/beat/v1beta1/webhook.go @@ -6,7 +6,6 @@ package v1beta1 import ( "errors" - "fmt" "github.com/elastic/cloud-on-k8s/pkg/utils/stringsutil" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -108,7 +107,7 @@ func checkSupportedVersion(b *Beat) field.ErrorList { func checkAtMostOneDeploymentOption(b *Beat) field.ErrorList { if b.Spec.DaemonSet != nil && b.Spec.Deployment != nil { - msg := fmt.Sprintf("Specify either daemonSet or deployment, not both") + msg := "Specify either daemonSet or deployment, not both" return field.ErrorList{ field.Forbidden(field.NewPath("spec").Child("daemonSet"), msg), field.Forbidden(field.NewPath("spec").Child("deployment"), msg), From e88b8c13965f4877bf53e880229839ff70d758cf Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sat, 23 May 2020 14:08:04 +0200 Subject: [PATCH 15/81] Add e2e tests, update smoke test --- ...ibana.yaml => filebeat_es_kibana_apm.yaml} | 48 ++-- pkg/controller/common/beat/config.go | 22 +- pkg/controller/common/beat/filebeat/config.go | 10 +- .../common/beat/otherbeat/otherbeat.go | 5 + test/e2e/beat/config_test.go | 81 ++++++ test/e2e/samples_test.go | 4 - test/e2e/smoke_test.go | 21 +- test/e2e/test/beat/builder.go | 125 +++++++++ test/e2e/test/beat/checks.go | 53 ++++ test/e2e/test/beat/pod_builder.go | 237 ++++++++++++++++++ test/e2e/test/beat/steps.go | 224 +++++++++++++++++ test/e2e/test/k8s_client.go | 14 ++ 12 files changed, 792 insertions(+), 52 deletions(-) rename config/samples/beat/{filebeat_es_kibana.yaml => filebeat_es_kibana_apm.yaml} (72%) create mode 100644 test/e2e/beat/config_test.go create mode 100644 test/e2e/test/beat/builder.go create mode 100644 test/e2e/test/beat/checks.go create mode 100644 test/e2e/test/beat/pod_builder.go create mode 100644 test/e2e/test/beat/steps.go diff --git a/config/samples/beat/filebeat_es_kibana.yaml b/config/samples/beat/filebeat_es_kibana_apm.yaml similarity index 72% rename from config/samples/beat/filebeat_es_kibana.yaml rename to config/samples/beat/filebeat_es_kibana_apm.yaml index 188d28e059..5d5d1ed534 100644 --- a/config/samples/beat/filebeat_es_kibana.yaml +++ b/config/samples/beat/filebeat_es_kibana_apm.yaml @@ -4,10 +4,10 @@ kind: Elasticsearch metadata: name: elasticsearch-sample spec: - version: 7.6.0 + version: 7.7.0 nodeSets: - name: default - count: 1 + count: 2 config: # This setting could have performance implications for production clusters. # See: https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-virtual-memory.html @@ -18,10 +18,10 @@ kind: Kibana metadata: name: kibana-sample spec: - version: 7.6.0 + version: 7.7.0 count: 1 elasticsearchRef: - name: "elasticsearch-sample" + name: elasticsearch-sample #http: # service: # spec: @@ -40,13 +40,23 @@ spec: memory: 1Gi cpu: 1 --- +apiVersion: apm.k8s.elastic.co/v1 +kind: ApmServer +metadata: + name: apm-sample +spec: + version: 7.7.0 + count: 1 + elasticsearchRef: + name: elasticsearch-sample +--- apiVersion: beat.k8s.elastic.co/v1beta1 kind: Beat metadata: name: filebeat-sample spec: type: filebeat - version: 7.6.0 + version: 7.7.0 elasticsearchRef: name: elasticsearch-sample # to target only a specific namespace for logging, use: @@ -62,31 +72,3 @@ spec: # processors: # - add_cloud_metadata: null # - add_host_metadata: null ---- -apiVersion: beat.k8s.elastic.co/v1beta1 -kind: Beat -metadata: - name: metricbeat-sample -spec: - type: metricbeat - version: 7.6.0 - elasticsearchRef: - name: elasticsearch-sample - - # to provide your own image, use: - #image: docker.mycompany.com/metricbeat:7.6.0 - - # to use deployment instead of default daemonset, use: - #deployment: - # podTemplate: - # metadata: - # labels: - # foo: bar - # spec: - # containers: - # - name: metricbeat - # resources: - # limits: - # memory: 1Gi - # cpu: 1 - diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index 9405496756..46f0233544 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -208,6 +208,14 @@ func reconcilePodVehicle(dp DriverParams, defaultImage container.Image, f func(b } else if dp.Deployment != nil { podTemplate = dp.Deployment.PodTemplate } + + // Token mounting gets defaulted to false, which prevents from detecting whether user set it. + // Instead, checking that here, before the default is applied. + if podTemplate.Spec.AutomountServiceAccountToken == nil { + t := true + podTemplate.Spec.AutomountServiceAccountToken = &t + } + builder := defaults.NewPodTemplateBuilder(podTemplate, dp.Type). WithTerminationGracePeriod(30). WithEnv(corev1.EnvVar{ @@ -227,8 +235,7 @@ func reconcilePodVehicle(dp DriverParams, defaultImage container.Image, f func(b WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). WithSecurityContext(corev1.SecurityContext{ RunAsUser: pointer.Int64(0), - }). - WithAutomountServiceAccountToken() + }) if ShouldSetupAutodiscoverRBAC() { autodiscoverServiceAccountName := ServiceAccountName(dp.Owner.GetName()) @@ -259,21 +266,26 @@ func reconcilePodVehicle(dp DriverParams, defaultImage container.Image, f func(b builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) } - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) - if f != nil { f(builder) } + builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) + name := dp.Namer.Name(dp.Type, dp.Owner.GetName()) ds := daemonset.New(builder.PodTemplate, name, dp.Owner, dp.Selectors) + + replicas := int32(1) + if dp.Deployment != nil && dp.Deployment.Replicas != nil { + replicas = *dp.Deployment.Replicas + } d := deployment.New(deployment.Params{ Name: name, Namespace: dp.Owner.GetNamespace(), Selector: dp.Selectors, Labels: dp.Labels, PodTemplateSpec: builder.PodTemplate, - Replicas: pointer.Int32OrDefault(dp.Deployment.Replicas, 1), + Replicas: replicas, }) var ready, desired int32 diff --git a/pkg/controller/common/beat/filebeat/config.go b/pkg/controller/common/beat/filebeat/config.go index a1b2037632..54f520ed97 100644 --- a/pkg/controller/common/beat/filebeat/config.go +++ b/pkg/controller/common/beat/filebeat/config.go @@ -13,14 +13,14 @@ var ( `filebeat: autodiscover: providers: - - hints: + - type: kubernetes + node: ${NODE_NAME} + hints: + enabled: true default_config: + type: container paths: - /var/log/containers/*${data.kubernetes.container.id}.log - type: container - enabled: "true" - node: ${NODE_NAME} - type: kubernetes processors: - add_cloud_metadata: null - add_host_metadata: null diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 1e5659a8ad..45c6d08bed 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -22,6 +22,11 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { } func (d *Driver) Reconcile() commonbeat.DriverResults { + + if d.DaemonSet == nil && d.Deployment == nil { + d.Deployment = &commonbeat.DeploymentSpec{} + } + return commonbeat.Reconcile( d.DriverParams, nil, diff --git a/test/e2e/beat/config_test.go b/test/e2e/beat/config_test.go new file mode 100644 index 0000000000..89f048f60b --- /dev/null +++ b/test/e2e/beat/config_test.go @@ -0,0 +1,81 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/filebeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" + "github.com/elastic/cloud-on-k8s/test/e2e/test" + "github.com/elastic/cloud-on-k8s/test/e2e/test/beat" + "github.com/elastic/cloud-on-k8s/test/e2e/test/elasticsearch" +) + +func TestFilebeatDefaultConfig(t *testing.T) { + name := "test-fb-default-cfg" + + esBuilder := elasticsearch.NewBuilder(name). + WithESMasterDataNodes(3, elasticsearch.DefaultResources) + + testPodBuilder := beat.NewPodBuilder(name) + + fbBuilder := beat.NewBuilder(name, filebeat.Type). + WithElasticsearchRef(esBuilder.Ref()). + WithESValidations(beat.HasEventFromPod(testPodBuilder.Pod.Name)) + + test.Sequence(nil, test.EmptySteps, esBuilder, fbBuilder, testPodBuilder).RunSequential(t) +} + +func TestMetricbeatDefaultConfig(t *testing.T) { + name := "test-mb-default-cfg" + + esBuilder := elasticsearch.NewBuilder(name). + WithESMasterDataNodes(3, elasticsearch.DefaultResources) + + testPodBuilder := beat.NewPodBuilder(name) + + mbBuilder := beat.NewBuilder(name, filebeat.Type). + WithElasticsearchRef(esBuilder.Ref()). + WithESValidations(beat.HasEventFromPod(testPodBuilder.Pod.Name)) + + test.Sequence(nil, test.EmptySteps, esBuilder, mbBuilder, testPodBuilder).RunSequential(t) +} + +func TestHeartbeatConfig(t *testing.T) { + name := "test-hb-cfg" + + esBuilder := elasticsearch.NewBuilder(name). + WithESMasterDataNodes(3, elasticsearch.DefaultResources) + + hbBuilder := beat.NewBuilder(name, "heartbeat"). + WithElasticsearchRef(esBuilder.Ref()). + WithImage("docker.elastic.co/beats/heartbeat:7.7.0"). + WithESValidations(beat.HasEventFromBeat("heartbeat")) + + yaml := fmt.Sprintf(` +heartbeat.monitors: +- type: tcp + schedule: '@every 5s' + hosts: ["%s.%s.svc:9200"] +`, esBuilder.Elasticsearch.Name, esBuilder.Elasticsearch.Namespace) + hbBuilder = applyConfigYaml(t, hbBuilder, yaml) + + test.Sequence(nil, test.EmptySteps, esBuilder, hbBuilder).RunSequential(t) +} + +// --- helpers + +func applyConfigYaml(t *testing.T, b beat.Builder, yaml string) beat.Builder { + config := &commonv1.Config{} + err := settings.MustParseConfig([]byte(yaml)).Unpack(&config.Data) + require.NoError(t, err) + + return b.WithConfig(config) +} diff --git a/test/e2e/samples_test.go b/test/e2e/samples_test.go index cf16275b10..40cf3082f4 100644 --- a/test/e2e/samples_test.go +++ b/test/e2e/samples_test.go @@ -28,10 +28,6 @@ func TestSamples(t *testing.T) { decoder := helper.NewYAMLDecoder() for _, sample := range sampleFiles { - if sample == "../../config/samples/beat/filebeat_es_kibana.yaml" { - // ignore Beat sample for now - continue - } testName := mkTestName(t, sample) builders := createBuilders(t, decoder, sample, testName) t.Run(testName, func(t *testing.T) { diff --git a/test/e2e/smoke_test.go b/test/e2e/smoke_test.go index 152401b868..931a5e54d2 100644 --- a/test/e2e/smoke_test.go +++ b/test/e2e/smoke_test.go @@ -9,31 +9,35 @@ import ( "os" "testing" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/filebeat" "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/apmserver" + "github.com/elastic/cloud-on-k8s/test/e2e/test/beat" "github.com/elastic/cloud-on-k8s/test/e2e/test/elasticsearch" "github.com/elastic/cloud-on-k8s/test/e2e/test/kibana" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/yaml" ) -const sampleApmEsKibanaFile = "../../config/samples/apm/apm_es_kibana.yaml" +const sampleFile = "../../config/samples/beat/filebeat_es_kibana_apm.yaml" // TestSmoke runs a test suite using the ApmServer + Kibana + ES sample. func TestSmoke(t *testing.T) { var esBuilder elasticsearch.Builder var kbBuilder kibana.Builder var apmBuilder apmserver.Builder + var beatBuilder beat.Builder - yamlFile, err := os.Open(sampleApmEsKibanaFile) + yamlFile, err := os.Open(sampleFile) test.ExitOnErr(err) decoder := yaml.NewYAMLToJSONDecoder(bufio.NewReader(yamlFile)) // the decoding order depends on the yaml test.ExitOnErr(decoder.Decode(&esBuilder.Elasticsearch)) - test.ExitOnErr(decoder.Decode(&apmBuilder.ApmServer)) test.ExitOnErr(decoder.Decode(&kbBuilder.Kibana)) + test.ExitOnErr(decoder.Decode(&apmBuilder.ApmServer)) + test.ExitOnErr(decoder.Decode(&beatBuilder.Beat)) ns := test.Ctx().ManagedNamespace(0) randSuffix := rand.String(4) @@ -62,7 +66,14 @@ func TestSmoke(t *testing.T) { WithRestrictedSecurityContext(). WithLabel(run.TestNameLabel, testName). WithPodLabel(run.TestNameLabel, testName) - - test.Sequence(nil, test.EmptySteps, esBuilder, kbBuilder, apmBuilder). + beatBuilder = beatBuilder. + WithSuffix(randSuffix). + WithNamespace(ns). + WithElasticsearchRef(esBuilder.Ref()). + WithRestrictedSecurityContext(). + WithLabel(run.TestNameLabel, testName). + WithPodLabel(run.TestNameLabel, testName). + WithESValidations(beat.HasEventFromBeat(filebeat.Type)) + test.Sequence(nil, test.EmptySteps, esBuilder, kbBuilder, apmBuilder, beatBuilder). RunSequential(t) } diff --git a/test/e2e/test/beat/builder.go b/test/e2e/test/beat/builder.go new file mode 100644 index 0000000000..1ed7a0ac77 --- /dev/null +++ b/test/e2e/test/beat/builder.go @@ -0,0 +1,125 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/client" + "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" + "github.com/elastic/cloud-on-k8s/test/e2e/test" +) + +// Builder to create Beats +type Builder struct { + Beat beatv1beta1.Beat + Validations []ValidationFunc +} + +func NewBuilder(name string, typ beat.Type) Builder { + meta := metav1.ObjectMeta{ + Name: name, + Namespace: test.Ctx().ManagedNamespace(0), + Labels: map[string]string{run.TestNameLabel: name}, + } + + return Builder{ + Beat: beatv1beta1.Beat{ + ObjectMeta: meta, + Spec: beatv1beta1.BeatSpec{ + Type: string(typ), + Version: test.Ctx().ElasticStackVersion, + }, + }, + } +} + +type ValidationFunc func(client.Client) error + +func (b Builder) WithESValidations(validations ...ValidationFunc) Builder { + b.Validations = append(b.Validations, validations...) + + return b +} + +func (b Builder) WithElasticsearchRef(ref commonv1.ObjectSelector) Builder { + b.Beat.Spec.ElasticsearchRef = ref + return b +} + +func (b Builder) WithConfig(config *commonv1.Config) Builder { + b.Beat.Spec.Config = config + return b +} + +func (b Builder) WithImage(image string) Builder { + b.Beat.Spec.Image = image + return b +} + +func (b Builder) WithSuffix(suffix string) Builder { + if suffix != "" { + b.Beat.ObjectMeta.Name = b.Beat.ObjectMeta.Name + "-" + suffix + } + return b +} + +func (b Builder) WithNamespace(namespace string) Builder { + b.Beat.ObjectMeta.Namespace = namespace + return b +} + +func (b Builder) WithRestrictedSecurityContext() Builder { + if b.Beat.Spec.DaemonSet != nil { + b.Beat.Spec.DaemonSet.PodTemplate.Spec.SecurityContext = test.DefaultSecurityContext() + } + if b.Beat.Spec.Deployment != nil { + b.Beat.Spec.DaemonSet.PodTemplate.Spec.SecurityContext = test.DefaultSecurityContext() + } + // todo what if both are nil? + + return b +} + +func (b Builder) WithLabel(key, value string) Builder { + if b.Beat.Labels == nil { + b.Beat.Labels = make(map[string]string) + } + b.Beat.Labels[key] = value + + return b +} + +func (b Builder) WithPodLabel(key, value string) Builder { + var podSpecs []corev1.PodTemplateSpec + + if b.Beat.Spec.DaemonSet != nil { + podSpecs = append(podSpecs, b.Beat.Spec.DaemonSet.PodTemplate) + } + if b.Beat.Spec.Deployment != nil { + podSpecs = append(podSpecs, b.Beat.Spec.Deployment.PodTemplate) + } + + for _, podSpec := range podSpecs { + if podSpec.Labels == nil { + podSpec.Labels = make(map[string]string) + } + podSpec.Labels[key] = value + } + + // todo what if both are nil? + return b +} + +func (b Builder) RuntimeObjects() []runtime.Object { + return []runtime.Object{&b.Beat} +} + +var _ test.Builder = Builder{} diff --git a/test/e2e/test/beat/checks.go b/test/e2e/test/beat/checks.go new file mode 100644 index 0000000000..5d799a5feb --- /dev/null +++ b/test/e2e/test/beat/checks.go @@ -0,0 +1,53 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/client" +) + +func HasEventFromBeat(name commonbeat.Type) ValidationFunc { + return HasEvent(fmt.Sprintf("/*beat*/_search?q=agent.type:%s", name)) +} + +func HasEventFromPod(name string) ValidationFunc { + return HasEvent(fmt.Sprintf("/*beat*/_search?q=kubernetes.pod.name:%s", name)) +} + +func HasEvent(query string) ValidationFunc { + return func(esClient client.Client) error { + req, err := http.NewRequest(http.MethodGet, query, nil) + if err != nil { + return err + } + + res, err := esClient.Request(context.Background(), req) + if err != nil { + return err + } + defer res.Body.Close() + resultBytes, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + var results client.SearchResults + err = json.Unmarshal(resultBytes, &results) + if err != nil { + return err + } + if len(results.Hits.Hits) == 0 { + return fmt.Errorf("hit count should be more than 0 for %s", query) + } + + return nil + } +} diff --git a/test/e2e/test/beat/pod_builder.go b/test/e2e/test/beat/pod_builder.go new file mode 100644 index 0000000000..531632cd49 --- /dev/null +++ b/test/e2e/test/beat/pod_builder.go @@ -0,0 +1,237 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "fmt" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" + "github.com/elastic/cloud-on-k8s/test/e2e/test" +) + +// Builder to create Beats +type PodBuilder struct { + Pod corev1.Pod +} + +func NewPodBuilder(name string) PodBuilder { + meta := metav1.ObjectMeta{ + Name: name, + Namespace: test.Ctx().ManagedNamespace(0), + Labels: map[string]string{run.TestNameLabel: name}, + } + + return PodBuilder{ + Pod: corev1.Pod{ + ObjectMeta: meta, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "ubuntu", + Image: "ubuntu", + Command: []string{ + "bash", + "-c", + "while [ true ]; do echo \"$(date)\"; sleep 5; done", + }, + }, + }, + }, + }, + } +} + +func (pb PodBuilder) RuntimeObjects() []runtime.Object { + return []runtime.Object{&pb.Pod} +} + +func (pb PodBuilder) InitTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{ + { + Name: "K8S should be accessible", + Test: test.Eventually(func() error { + pods := corev1.PodList{} + return k.Client.List(&pods) + }), + }, + { + Name: "Label test pods", + Test: test.Eventually(func() error { + return test.LabelTestPods( + k.Client, + test.Ctx(), + run.TestNameLabel, + pb.Pod.Labels[run.TestNameLabel]) + }), + Skip: func() bool { + return test.Ctx().Local + }, + }, + { + Name: "Remove pod if it already exists", + Test: test.Eventually(func() error { + for _, obj := range pb.RuntimeObjects() { + err := k.Client.Delete(obj) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + // wait for pod to disappear + var pod corev1.Pod + err := k.Client.Get(types.NamespacedName{ + Namespace: pb.Pod.Namespace, + Name: pb.Pod.Name, + }, &pod) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if err == nil { + return fmt.Errorf("pod %s is still there", k8s.ExtractNamespacedName(&pb.Pod)) + } + return nil + }), + }, + } +} + +func (pb PodBuilder) CreationTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{}. + WithSteps(test.StepList{ + test.Step{ + Name: "Creating a Pod should succeed", + Test: func(t *testing.T) { + for _, obj := range pb.RuntimeObjects() { + err := k.Client.Create(obj) + require.NoError(t, err) + } + }, + }, + test.Step{ + Name: "Pod should be created", + Test: func(t *testing.T) { + var createdPod corev1.Pod + err := k.Client.Get(k8s.ExtractNamespacedName(&pb.Pod), &createdPod) + require.NoError(t, err) + // TODO this is incomplete + }, + }, + }) +} + +func (pb PodBuilder) CheckK8sTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{ + test.Step{ + Name: "Pod should be ready and running", + Test: test.Eventually(func() error { + var pod corev1.Pod + if err := k.Client.Get(k8s.ExtractNamespacedName(&pb.Pod), &pod); err != nil { + return err + } + + // pod is running + if pod.Status.Phase != corev1.PodRunning { + return fmt.Errorf("pod not running yet") + } + + // pod is ready + if !k8s.IsPodReady(pod) { + return fmt.Errorf("pod not ready yet") + } + + return nil + }), + }, + } +} + +func (pb PodBuilder) CheckStackTestSteps(*test.K8sClient) test.StepList { + return test.StepList{} // nothing to do +} + +func (pb PodBuilder) UpgradeTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{ + { + Name: "Applying pod mutation should succeed", + Test: func(t *testing.T) { + var pod corev1.Pod + require.NoError(t, k.Client.Get(k8s.ExtractNamespacedName(&pb.Pod), &pod)) + pod.Spec = pb.Pod.Spec + require.NoError(t, k.Client.Update(&pod)) + }, + }} +} + +func (pb PodBuilder) DeletionTestSteps(k *test.K8sClient) test.StepList { + return []test.Step{ + { + Name: "Deleting the resources should return no error", + Test: func(t *testing.T) { + for _, obj := range pb.RuntimeObjects() { + err := k.Client.Delete(obj) + require.NoError(t, err) + } + }, + }, + { + Name: "The resources should not be there anymore", + Test: test.Eventually(func() error { + for _, obj := range pb.RuntimeObjects() { + m, err := meta.Accessor(obj) + if err != nil { + return err + } + err = k.Client.Get(k8s.ExtractNamespacedName(m), obj.DeepCopyObject()) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + } + return errors.Wrap(err, "expected 404 not found API error here") + + } + return nil + }), + }, + { + Name: "Pod should be eventually be removed", + Test: test.Eventually(func() error { + // wait for pod to disappear + var pod corev1.Pod + err := k.Client.Get(types.NamespacedName{ + Namespace: pb.Pod.Namespace, + Name: pb.Pod.Name, + }, &pod) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if err == nil { + return fmt.Errorf("pod %s is still there", k8s.ExtractNamespacedName(&pb.Pod)) + } + return nil + }), + }, + } +} + +func (pb PodBuilder) MutationTestSteps(k *test.K8sClient) test.StepList { + panic("implement me") +} + +func (pb PodBuilder) MutationReversalTestContext() test.ReversalTestContext { + panic("implement me") +} + +var _ test.Builder = Builder{} diff --git a/test/e2e/test/beat/steps.go b/test/e2e/test/beat/steps.go new file mode 100644 index 0000000000..bd78847de5 --- /dev/null +++ b/test/e2e/test/beat/steps.go @@ -0,0 +1,224 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "fmt" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" + "github.com/elastic/cloud-on-k8s/test/e2e/test" + "github.com/elastic/cloud-on-k8s/test/e2e/test/elasticsearch" +) + +func (b Builder) InitTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{ + { + Name: "K8S should be accessible", + Test: test.Eventually(func() error { + pods := corev1.PodList{} + return k.Client.List(&pods) + }), + }, + { + Name: "Label test pods", + Test: test.Eventually(func() error { + return test.LabelTestPods( + k.Client, + test.Ctx(), + run.TestNameLabel, + b.Beat.Labels[run.TestNameLabel]) + }), + Skip: func() bool { + return test.Ctx().Local + }, + }, + { + Name: "Beat CRDs should exist", + Test: test.Eventually(func() error { + crds := []runtime.Object{ + &beatv1beta1.BeatList{}, + } + for _, crd := range crds { + if err := k.Client.List(crd); err != nil { + return err + } + } + return nil + }), + }, + { + Name: "Remove Beat if it already exists", + Test: test.Eventually(func() error { + for _, obj := range b.RuntimeObjects() { + err := k.Client.Delete(obj) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + } + // wait for Beat pods to disappear + if err := k.CheckPodCount(0, test.BeatPodListOptions(b.Beat.Namespace, b.Beat.Name)...); err != nil { + return err + } + + // it may take some extra time for Elasticsearch to be fully deleted + var beat beatv1beta1.Beat + err := k.Client.Get(k8s.ExtractNamespacedName(&b.Beat), &beat) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + if err == nil { + return fmt.Errorf("beat %s is still there", k8s.ExtractNamespacedName(&b.Beat)) + } + return nil + }), + }, + } +} + +func (b Builder) CreationTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{}. + WithSteps(test.StepList{ + test.Step{ + Name: "Creating a Beat should succeed", + Test: func(t *testing.T) { + for _, obj := range b.RuntimeObjects() { + err := k.Client.Create(obj) + require.NoError(t, err) + } + }, + }, + test.Step{ + Name: "Beat should be created", + Test: func(t *testing.T) { + var createdBeat beatv1beta1.Beat + err := k.Client.Get(k8s.ExtractNamespacedName(&b.Beat), &createdBeat) + require.NoError(t, err) + require.Equal(t, b.Beat.Spec.Version, createdBeat.Spec.Version) + // TODO this is incomplete + }, + }, + }) +} + +func (b Builder) CheckK8sTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{} +} + +func (b Builder) CheckStackTestSteps(k *test.K8sClient) test.StepList { + // health + return test.StepList{ + test.Step{ + Name: "Beat health should be green", + Test: test.Eventually(func() error { + var beat beatv1beta1.Beat + if err := k.Client.Get(k8s.ExtractNamespacedName(&b.Beat), &beat); err != nil { + return err + } + + if beat.Status.Health != health.BeatGreenHealth { + return fmt.Errorf("beat %s health is %s", beat.Name, beat.Status.Health) + } + + return nil + }), + }, + test.Step{ + Name: "ES data should pass validations", + Test: test.Eventually(func() error { + esNsName := b.Beat.ElasticsearchRef().WithDefaultNamespace(b.Beat.Namespace).NamespacedName() + var es esv1.Elasticsearch + if err := k.Client.Get(esNsName, &es); err != nil { + return err + } + + esClient, err := elasticsearch.NewElasticsearchClient(es, k) + if err != nil { + return err + } + + for _, validation := range b.Validations { + if err := validation(esClient); err != nil { + return err + } + } + + return nil + }), + }, + } +} + +func (b Builder) UpgradeTestSteps(k *test.K8sClient) test.StepList { + return test.StepList{ + { + Name: "Applying the Beat mutation should succeed", + Test: func(t *testing.T) { + var beat beatv1beta1.Beat + require.NoError(t, k.Client.Get(k8s.ExtractNamespacedName(&b.Beat), &beat)) + beat.Spec = b.Beat.Spec + require.NoError(t, k.Client.Update(&beat)) + }, + }} +} + +func (b Builder) DeletionTestSteps(k *test.K8sClient) test.StepList { + return []test.Step{ + { + Name: "Deleting the resources should return no error", + Test: func(t *testing.T) { + for _, obj := range b.RuntimeObjects() { + err := k.Client.Delete(obj) + require.NoError(t, err) + } + }, + }, + { + Name: "The resources should not be there anymore", + Test: test.Eventually(func() error { + for _, obj := range b.RuntimeObjects() { + m, err := meta.Accessor(obj) + if err != nil { + return err + } + err = k.Client.Get(k8s.ExtractNamespacedName(m), obj.DeepCopyObject()) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + } + return errors.Wrap(err, "expected 404 not found API error here") + + } + return nil + }), + }, + { + Name: "Beat pods should be eventually be removed", + Test: test.Eventually(func() error { + return k.CheckPodCount(0, test.BeatPodListOptions(b.Beat.Namespace, b.Beat.Name)...) + }), + }, + } +} + +func (b Builder) MutationTestSteps(k *test.K8sClient) test.StepList { + panic("implement me") +} + +func (b Builder) MutationReversalTestContext() test.ReversalTestContext { + panic("implement me") +} diff --git a/test/e2e/test/k8s_client.go b/test/e2e/test/k8s_client.go index 579d9ba63e..97eb5b5209 100644 --- a/test/e2e/test/k8s_client.go +++ b/test/e2e/test/k8s_client.go @@ -10,6 +10,8 @@ import ( "fmt" "os" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -73,6 +75,9 @@ func CreateClient() (k8s.Client, error) { if err := apmv1.AddToScheme(scheme.Scheme); err != nil { return nil, err } + if err := beatv1beta1.AddToScheme(scheme.Scheme); err != nil { + return nil, err + } client, err := k8sclient.New(cfg, k8sclient.Options{Scheme: scheme.Scheme}) if err != nil { return nil, err @@ -315,6 +320,15 @@ func ApmServerPodListOptions(apmNamespace, apmName string) []k8sclient.ListOptio } +func BeatPodListOptions(beatNamespace, beatName string) []k8sclient.ListOption { + ns := k8sclient.InNamespace(beatNamespace) + matchLabels := k8sclient.MatchingLabels(map[string]string{ + common.TypeLabelName: beat.Type, + beat.NameLabelName: beatName, + }) + return []k8sclient.ListOption{ns, matchLabels} +} + func EventListOptions(namespace, name string) []k8sclient.ListOption { ns := k8sclient.InNamespace(namespace) matchFields := k8sclient.MatchingFields(map[string]string{ From 20f2af31a386d62b175037eb95627b0b9b00ed02 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sat, 23 May 2020 15:52:14 +0200 Subject: [PATCH 16/81] Add UTs --- pkg/controller/beat/controller_test.go | 79 +++++++++++++ pkg/controller/beat/name_test.go | 57 +++++++++ .../common/beat/health/health_test.go | 109 ++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 pkg/controller/beat/controller_test.go create mode 100644 pkg/controller/beat/name_test.go create mode 100644 pkg/controller/common/beat/health/health_test.go diff --git a/pkg/controller/beat/controller_test.go b/pkg/controller/beat/controller_test.go new file mode 100644 index 0000000000..e32015ff14 --- /dev/null +++ b/pkg/controller/beat/controller_test.go @@ -0,0 +1,79 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "context" + "testing" + + "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" +) + +func Test_newDriverParams(t *testing.T) { + for _, tt := range []struct { + name string + deployment *v1beta1.DeploymentSpec + wantDeployment *beat.DeploymentSpec + daemonSet *v1beta1.DaemonSetSpec + wantDaemonSet *beat.DaemonSetSpec + }{ + { + name: "without deployment/daemonset", + }, + { + name: "with empty deployment", + deployment: &v1beta1.DeploymentSpec{}, + wantDeployment: &beat.DeploymentSpec{}, + }, + { + name: "with replicas in deployment", + deployment: &v1beta1.DeploymentSpec{Replicas: pointer.Int32(2)}, + wantDeployment: &beat.DeploymentSpec{Replicas: pointer.Int32(2)}, + }, + { + name: "with deployment partial podspec", + deployment: &v1beta1.DeploymentSpec{PodTemplate: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + ServiceAccountName: "sa-test", + }, + }}, + wantDeployment: &beat.DeploymentSpec{PodTemplate: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + ServiceAccountName: "sa-test", + }, + }}, + }, + { + name: "with daemonset partial podspec", + daemonSet: &v1beta1.DaemonSetSpec{PodTemplate: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + ServiceAccountName: "sa-test", + }, + }}, + wantDaemonSet: &beat.DaemonSetSpec{PodTemplate: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + ServiceAccountName: "sa-test", + }, + }}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + beat := v1beta1.Beat{ + Spec: v1beta1.BeatSpec{ + DaemonSet: tt.daemonSet, + Deployment: tt.deployment, + }, + } + got := newDriverParams(context.Background(), nil, beat) + + require.Equal(t, tt.wantDeployment, got.Deployment) + require.Equal(t, tt.wantDaemonSet, got.DaemonSet) + }) + } +} diff --git a/pkg/controller/beat/name_test.go b/pkg/controller/beat/name_test.go new file mode 100644 index 0000000000..c915da5d52 --- /dev/null +++ b/pkg/controller/beat/name_test.go @@ -0,0 +1,57 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import "testing" + +func TestNamer_ConfigSecretName(t *testing.T) { + namer := Namer{} + for _, typeName := range []struct { + name string + typ string + want string + }{ + { + name: "my", + typ: "filebeat", + want: "my-beat-filebeat-config", + }, + { + name: "sample-metricbeat", + typ: "metricbeat", + want: "sample-metricbeat-beat-metricbeat-config", + }, + } { + got := namer.ConfigSecretName(typeName.typ, typeName.name) + if got != typeName.want { + t.Errorf("config secret name is %s while %s was expected", got, typeName.want) + } + } +} + +func TestNamer_Name(t *testing.T) { + namer := Namer{} + for _, typeName := range []struct { + name string + typ string + want string + }{ + { + name: "my", + typ: "filebeat", + want: "my-beat-filebeat", + }, + { + name: "sample-metricbeat", + typ: "metricbeat", + want: "sample-metricbeat-beat-metricbeat", + }, + } { + got := namer.Name(typeName.typ, typeName.name) + if got != typeName.want { + t.Errorf("config secret name is %s while %s was expected", got, typeName.want) + } + } +} diff --git a/pkg/controller/common/beat/health/health_test.go b/pkg/controller/common/beat/health/health_test.go new file mode 100644 index 0000000000..931dd1937e --- /dev/null +++ b/pkg/controller/common/beat/health/health_test.go @@ -0,0 +1,109 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package health_test + +import ( + "testing" + + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/stretchr/testify/require" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" +) + +func Test_CalculateHealth(t *testing.T) { + noAssociation := &beatv1beta1.Beat{} + createAssociated := func(associationEstablished bool) commonv1.Associated { + result := &beatv1beta1.Beat{} + result.SetAssociationConf(&commonv1.AssociationConf{ + AuthSecretName: "name", + AuthSecretKey: "key", + CACertProvided: true, + CASecretName: "name", + URL: "url", + }) + + if associationEstablished { + result.SetAssociationStatus(commonv1.AssociationEstablished) + } + + return result + } + + for _, tt := range []struct { + name string + associated commonv1.Associated + ready, desired int32 + want health.BeatHealth + }{ + { + name: "no association, 0 desired", + associated: noAssociation, + ready: 0, + desired: 0, + want: health.BeatGreenHealth, + }, + { + name: "no association, all ready", + associated: noAssociation, + ready: 3, + desired: 3, + want: health.BeatGreenHealth, + }, + { + name: "no association, some ready", + associated: noAssociation, + ready: 1, + desired: 5, + want: health.BeatYellowHealth, + }, + { + name: "no association, none ready", + associated: noAssociation, + ready: 0, + desired: 4, + want: health.BeatRedHealth, + }, + { + name: "association not established, all ready", + associated: createAssociated(false), + ready: 2, + desired: 2, + want: health.BeatRedHealth, + }, + { + name: "association established, 0 desired", + associated: createAssociated(true), + want: health.BeatGreenHealth, + }, + { + name: "association established, all ready", + associated: createAssociated(true), + ready: 2, + desired: 2, + want: health.BeatGreenHealth, + }, + { + name: "association established, some ready", + associated: createAssociated(true), + ready: 1, + desired: 5, + want: health.BeatYellowHealth, + }, + { + name: "association established, none ready", + associated: createAssociated(true), + ready: 0, + desired: 4, + want: health.BeatRedHealth, + }, + } { + t.Run(tt.name, func(t *testing.T) { + got := health.CalculateHealth(tt.associated, tt.ready, tt.desired) + require.Equal(t, tt.want, got) + }) + } +} From df0357ce58d6bbc504a39657564f1a0d9358881c Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sat, 23 May 2020 16:14:53 +0200 Subject: [PATCH 17/81] Move writing derefed secret to hash to common --- .../common/association/association.go | 47 +++++++++++++++++++ pkg/controller/common/beat/config.go | 12 ++--- pkg/controller/kibana/driver.go | 22 ++------- 3 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 pkg/controller/common/association/association.go diff --git a/pkg/controller/common/association/association.go b/pkg/controller/common/association/association.go new file mode 100644 index 0000000000..95aff062ab --- /dev/null +++ b/pkg/controller/common/association/association.go @@ -0,0 +1,47 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package association + +import ( + "hash" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/certificates" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +// WriteAssocSecretToHash dereferences auth secret (if any) to include it in the checksum +func WriteAssocSecretToHash(client k8s.Client, assoc commonv1.Associated, hash hash.Hash) error { + assocConf := assoc.AssociationConf() + + if assocConf.AuthIsConfigured() { + esAuthKey := types.NamespacedName{ + Name: assocConf.GetAuthSecretName(), + Namespace: assoc.GetNamespace()} + var esAuthSecret corev1.Secret + if err := client.Get(esAuthKey, &esAuthSecret); err != nil { + return err + } + _, _ = hash.Write(esAuthSecret.Data[assocConf.GetAuthSecretKey()]) + } + + if assocConf.CAIsConfigured() { + esPublicCAKey := types.NamespacedName{ + Namespace: assoc.GetNamespace(), + Name: assocConf.GetCASecretName()} + var esPublicCASecret corev1.Secret + if err := client.Get(esPublicCAKey, &esPublicCASecret); err != nil { + return err + } + if certPem, ok := esPublicCASecret.Data[certificates.CertFileName]; ok { + _, _ = hash.Write(certPem) + } + } + + return nil +} diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index 46f0233544..6fd2d359a3 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -10,6 +10,7 @@ import ( "hash" "path" + commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" @@ -23,7 +24,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -178,14 +178,8 @@ func reconcileConfig( } // we need to deref the secret here (if any) to include it in the checksum otherwise Beat will not be rolled on contents changes - assocConf := params.Associated.AssociationConf() - if assocConf.AuthIsConfigured() { - esAuthKey := types.NamespacedName{Name: assocConf.GetAuthSecretName(), Namespace: params.Owner.GetNamespace()} - esAuthSecret := corev1.Secret{} - if err := params.Client.Get(esAuthKey, &esAuthSecret); err != nil { - return err - } - _, _ = checksum.Write(esAuthSecret.Data[assocConf.GetAuthSecretKey()]) + if err := commonassociation.WriteAssocSecretToHash(params.Client, params.Associated, checksum); err != nil { + return err } _, _ = checksum.Write(cfgBytes) diff --git a/pkg/controller/kibana/driver.go b/pkg/controller/kibana/driver.go index a07db7c978..d247aff186 100644 --- a/pkg/controller/kibana/driver.go +++ b/pkg/controller/kibana/driver.go @@ -20,6 +20,7 @@ import ( kbv1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common" + commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common/certificates" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" @@ -198,28 +199,15 @@ func (d *driver) deploymentParams(kb *kbv1.Kibana) (deployment.Params, error) { if keystoreResources != nil { _, _ = configChecksum.Write([]byte(keystoreResources.Version)) } - // we need to deref the secret here (if any) to include it in the checksum otherwise Kibana will not be rolled on contents changes - if kb.AssociationConf().AuthIsConfigured() { - esAuthKey := types.NamespacedName{Name: kb.AssociationConf().GetAuthSecretName(), Namespace: kb.Namespace} - esAuthSecret := corev1.Secret{} - if err := d.client.Get(esAuthKey, &esAuthSecret); err != nil { - return deployment.Params{}, err - } - _, _ = configChecksum.Write(esAuthSecret.Data[kb.AssociationConf().GetAuthSecretKey()]) + + // we need to deref the secret here to include it in the checksum otherwise Kibana will not be rolled on contents changes + if err := commonassociation.WriteAssocSecretToHash(d.client, kb, configChecksum); err != nil { + return deployment.Params{}, err } volumes := []commonvolume.SecretVolume{SecretVolume(*kb)} if kb.AssociationConf().CAIsConfigured() { - esPublicCAKey := types.NamespacedName{Namespace: kb.Namespace, Name: kb.AssociationConf().GetCASecretName()} - var esPublicCASecret corev1.Secret - if err := d.client.Get(esPublicCAKey, &esPublicCASecret); err != nil { - return deployment.Params{}, err - } - if certPem, ok := esPublicCASecret.Data[certificates.CertFileName]; ok { - _, _ = configChecksum.Write(certPem) - } - esCertsVolume := esCaCertSecretVolume(*kb) volumes = append(volumes, esCertsVolume) for i := range kibanaPodSpec.Spec.InitContainers { From 566feb8b5b7ea34b2e857d093aeeeea0860ef667 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 17:47:44 +0200 Subject: [PATCH 18/81] Split config.go file --- pkg/controller/common/beat/common.go | 63 ++++++ pkg/controller/common/beat/config.go | 214 ++---------------- .../common/beat/filebeat/filebeat.go | 15 -- .../common/beat/metricbeat/metricbeat.go | 20 -- pkg/controller/common/beat/pod.go | 118 ++++++++++ pkg/controller/common/beat/reconcile.go | 121 ++++++++++ 6 files changed, 326 insertions(+), 225 deletions(-) create mode 100644 pkg/controller/common/beat/pod.go create mode 100644 pkg/controller/common/beat/reconcile.go diff --git a/pkg/controller/common/beat/common.go b/pkg/controller/common/beat/common.go index 10338cd200..4daf46cae2 100644 --- a/pkg/controller/common/beat/common.go +++ b/pkg/controller/common/beat/common.go @@ -6,14 +6,21 @@ package beat import ( "context" + "crypto/sha256" + "fmt" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) @@ -55,6 +62,30 @@ type DriverParams struct { Selectors map[string]string } +func (dp *DriverParams) GetReplicas() *int32 { + if dp.Deployment == nil { + return nil + } + + return dp.Deployment.Replicas +} + +func (dp *DriverParams) GetPodTemplate() corev1.PodTemplateSpec { + switch { + case dp.DaemonSet != nil: + { + return dp.DaemonSet.PodTemplate + } + + case dp.Deployment != nil: + { + return dp.Deployment.PodTemplate + } + } + + return corev1.PodTemplateSpec{} +} + type DriverResults struct { *reconciler.Results Status *DriverStatus @@ -73,3 +104,35 @@ type DriverStatus struct { Health health.BeatHealth Association commonv1.AssociationStatus } + +func Reconcile(params DriverParams, defaultConfig *settings.CanonicalConfig, defaultImage container.Image, modifyPodFunc func(builder *defaults.PodTemplateBuilder)) DriverResults { + results := NewDriverResults(params.Context) + + if (params.DaemonSet == nil && params.Deployment == nil) || (params.DaemonSet != nil && params.Deployment != nil) { + results.WithError(fmt.Errorf("either daemonset or deployment has to be specified")) + return results + } + + if err := SetupAutodiscoverRBAC(params.Context, params.Logger, params.Client, params.Owner, params.Labels); err != nil { + results.WithError(err) + } + + checksum := sha256.New224() + if err := reconcileConfig(params, defaultConfig, checksum); err != nil { + results.WithError(err) + return results + } + + podTemplate := buildPodTemplate(params, defaultImage, modifyPodFunc, checksum) + if driverStatus, err := reconcilePodVehicle(podTemplate, params); err != nil { + if apierrors.IsConflict(err) { + params.Logger.V(1).Info("Conflict while updating") + results.WithResult(reconcile.Result{Requeue: true}) + } + results.WithError(err) + } else { + results.Status = &driverStatus + } + + return results +} diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index 6fd2d359a3..d95134a8dd 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -5,35 +5,20 @@ package beat import ( - "crypto/sha256" "fmt" "hash" "path" - commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" - commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common" + commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -64,37 +49,7 @@ var ( } ) -func Reconcile(fd DriverParams, defaultConfig *settings.CanonicalConfig, defaultImage container.Image, f func(builder *defaults.PodTemplateBuilder)) DriverResults { - results := NewDriverResults(fd.Context) - - if err := SetupAutodiscoverRBAC(fd.Context, fd.Logger, fd.Client, fd.Owner, fd.Labels); err != nil { - results.WithError(err) - } - - checksum := sha256.New224() - err := reconcileConfig( - fd, - defaultConfig, - checksum) - if err != nil { - results.WithError(err) - return results - } - - if driverStatus, err := reconcilePodVehicle(fd, defaultImage, f, checksum); err != nil { - if apierrors.IsConflict(err) { - fd.Logger.V(1).Info("Conflict while updating") - results.WithResult(reconcile.Result{Requeue: true}) - } - results.WithError(err) - } else { - results.Status = &driverStatus - } - - return results -} - -// SetOutput will set output section in Beat config according to association configuration. +// setOutput will set output section in Beat config according to association configuration. func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { if associated.AssociationConf().IsConfigured() { username, password, err := association.ElasticsearchAuthSettings(client, associated) @@ -102,21 +57,31 @@ func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated comm return err } - return cfg.MergeWith(settings.MustCanonicalConfig( + if err := cfg.MergeWith(settings.MustCanonicalConfig( map[string]interface{}{ "output.elasticsearch": map[string]interface{}{ - "hosts": []string{associated.AssociationConf().GetURL()}, - "username": username, - "password": password, - "ssl.certificate_authorities": path.Join(CAMountPath, CAFileName), + "hosts": []string{associated.AssociationConf().GetURL()}, + "username": username, + "password": password, }, - })) + })); err != nil { + return err + } + + if associated.AssociationConf().GetCACertProvided() { + if err := cfg.MergeWith(settings.MustCanonicalConfig( + map[string]interface{}{ + "output.elasticsearch.ssl.certificate_authorities": path.Join(CAMountPath, CAFileName), + })); err != nil { + return err + } + } } return nil } -func build( +func buildBeatConfig( client k8s.Client, associated commonv1.Associated, defaultConfig *settings.CanonicalConfig, @@ -155,7 +120,7 @@ func reconcileConfig( defaultConfig *settings.CanonicalConfig, checksum hash.Hash) error { - cfgBytes, err := build(params.Client, params.Associated, defaultConfig, params.Config) + cfgBytes, err := buildBeatConfig(params.Client, params.Associated, defaultConfig, params.Config) if err != nil { return err } @@ -177,7 +142,7 @@ func reconcileConfig( return err } - // we need to deref the secret here (if any) to include it in the checksum otherwise Beat will not be rolled on contents changes + // we need to deref the secret here (if any) to include it in the checksum otherwise Beat will not be rolled on content changes if err := commonassociation.WriteAssocSecretToHash(params.Client, params.Associated, checksum); err != nil { return err } @@ -194,134 +159,3 @@ func configFileName(typ string) string { func ConfigMountPath(typ string) string { return path.Join(ConfigMountDirPath, configFileName(typ)) } - -func reconcilePodVehicle(dp DriverParams, defaultImage container.Image, f func(builder *defaults.PodTemplateBuilder), checksum hash.Hash) (DriverStatus, error) { - var podTemplate corev1.PodTemplateSpec - if dp.DaemonSet != nil { - podTemplate = dp.DaemonSet.PodTemplate - } else if dp.Deployment != nil { - podTemplate = dp.Deployment.PodTemplate - } - - // Token mounting gets defaulted to false, which prevents from detecting whether user set it. - // Instead, checking that here, before the default is applied. - if podTemplate.Spec.AutomountServiceAccountToken == nil { - t := true - podTemplate.Spec.AutomountServiceAccountToken = &t - } - - builder := defaults.NewPodTemplateBuilder(podTemplate, dp.Type). - WithTerminationGracePeriod(30). - WithEnv(corev1.EnvVar{ - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }}). - WithResources(defaultResources). - WithHostNetwork(). - WithLabels(map[string]string{ - ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), - VersionLabelName: dp.Version}). - WithDockerImage(dp.Image, container.ImageRepository(defaultImage, dp.Version)). - WithArgs("-e", "-c", ConfigMountPath(dp.Type)). - WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). - WithSecurityContext(corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - }) - - if ShouldSetupAutodiscoverRBAC() { - autodiscoverServiceAccountName := ServiceAccountName(dp.Owner.GetName()) - // If SA is already provided, the call will be no-op. This is fine as we then assume - // that for this resource (despite operator configuration) the user took the responsibility - // of configuring RBAC. - builder.WithServiceAccount(autodiscoverServiceAccountName) - } - - volumes := []volume.VolumeLike{ - volume.NewSecretVolume( - dp.Namer.ConfigSecretName(dp.Type, dp.Owner.GetName()), - ConfigVolumeName, - ConfigMountPath(dp.Type), - configFileName(dp.Type), - 0600), - } - - if dp.Associated.AssociationConf().IsConfigured() { - volumes = append(volumes, volume.NewSelectiveSecretVolumeWithMountPath( - dp.Associated.AssociationConf().CASecretName, - CAVolumeName, - CAMountPath, - []string{CAFileName})) - } - - for _, v := range volumes { - builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) - } - - if f != nil { - f(builder) - } - - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(dp.Labels, builder.PodTemplate)) - - name := dp.Namer.Name(dp.Type, dp.Owner.GetName()) - ds := daemonset.New(builder.PodTemplate, name, dp.Owner, dp.Selectors) - - replicas := int32(1) - if dp.Deployment != nil && dp.Deployment.Replicas != nil { - replicas = *dp.Deployment.Replicas - } - d := deployment.New(deployment.Params{ - Name: name, - Namespace: dp.Owner.GetNamespace(), - Selector: dp.Selectors, - Labels: dp.Labels, - PodTemplateSpec: builder.PodTemplate, - Replicas: replicas, - }) - - var ready, desired int32 - var toDelete runtime.Object - switch { - case dp.DaemonSet != nil: - { - if err := controllerutil.SetControllerReference(dp.Owner, &ds, scheme.Scheme); err != nil { - return DriverStatus{}, err - } - reconciled, err := daemonset.Reconcile(dp.Client, ds, dp.Owner) - if err != nil { - return DriverStatus{}, err - } - ready = reconciled.Status.NumberReady - desired = reconciled.Status.DesiredNumberScheduled - toDelete = &d - } - case dp.Deployment != nil: - { - if err := controllerutil.SetControllerReference(dp.Owner, &d, scheme.Scheme); err != nil { - return DriverStatus{}, err - } - // sync - reconciled, err := deployment.Reconcile(dp.Client, d, dp.Owner) - if err != nil { - return DriverStatus{}, err - } - ready = reconciled.Status.ReadyReplicas - desired = reconciled.Status.Replicas - toDelete = &ds - } - } - - if err := dp.Client.Delete(toDelete); err != nil && !apierrors.IsNotFound(err) { - return DriverStatus{}, err - } - - return DriverStatus{ - ExpectedNodes: desired, - AvailableNodes: ready, - Health: health.CalculateHealth(dp.Associated, ready, desired), - Association: dp.Associated.AssociationStatus(), - }, nil -} diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/common/beat/filebeat/filebeat.go index 95e49c07e7..bedd6dfc0e 100644 --- a/pkg/controller/common/beat/filebeat/filebeat.go +++ b/pkg/controller/common/beat/filebeat/filebeat.go @@ -5,13 +5,10 @@ package filebeat import ( - "fmt" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" - corev1 "k8s.io/api/core/v1" ) const ( @@ -28,10 +25,6 @@ const ( HostPodsLogsVolumeName = "varlogpods" HostPodsLogsPath = "/var/log/pods" HostPodsLogsMountPath = "/var/log/pods" - - HostFilebeatDataVolumeName = "data" - HostFilebeatDataPathTemplate = "/var/lib/%s/%s/filebeat-data" - HostFilebeatDataMountPath = "/usr/share/filebeat/data" ) type Driver struct { @@ -48,19 +41,11 @@ func (d *Driver) Reconcile() commonbeat.DriverResults { containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) containersLogsVolume := volume.NewReadOnlyHostVolume(HostContainersLogsVolumeName, HostContainersLogsPath, HostContainersLogsMountPath) podsLogsVolume := volume.NewReadOnlyHostVolume(HostPodsLogsVolumeName, HostPodsLogsPath, HostPodsLogsMountPath) - hostFilebeatDataPath := fmt.Sprintf(HostFilebeatDataPathTemplate, d.Owner.GetNamespace(), d.Owner.GetName()) - filebeatDataVolume := volume.NewHostVolume( - HostFilebeatDataVolumeName, - hostFilebeatDataPath, - HostFilebeatDataMountPath, - false, - corev1.HostPathDirectoryOrCreate) for _, volume := range []volume.VolumeLike{ containersVolume, containersLogsVolume, podsLogsVolume, - filebeatDataVolume, } { builder.WithVolumes(volume.Volume()).WithVolumeMounts(volume.VolumeMount()) } diff --git a/pkg/controller/common/beat/metricbeat/metricbeat.go b/pkg/controller/common/beat/metricbeat/metricbeat.go index 46218966cc..a3c1fa20d8 100644 --- a/pkg/controller/common/beat/metricbeat/metricbeat.go +++ b/pkg/controller/common/beat/metricbeat/metricbeat.go @@ -5,8 +5,6 @@ package metricbeat import ( - "fmt" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" @@ -28,10 +26,6 @@ const ( CGroupVolumeName = "cgroup" CGroupPath = "/sys/fs/cgroup" CGroupMountPath = "/hostfs/sys/fs/cgroup" - - HostMetricbeatDataVolumeName = "data" - HostMetricbeatDataPathTemplate = "/var/lib/%s/%s/metricbeat-data" - HostMetricbeatDataMountPath = "/usr/share/metricbeat/data" ) type Driver struct { @@ -49,24 +43,10 @@ func (d *Driver) Reconcile() commonbeat.DriverResults { procVolume := volume.NewReadOnlyHostVolume(ProcVolumeName, ProcPath, ProcMountPath) cgroupVolume := volume.NewReadOnlyHostVolume(CGroupVolumeName, CGroupPath, CGroupMountPath) - var metricbeatDataVolume volume.VolumeLike - if d.Deployment != nil { - metricbeatDataVolume = volume.NewPersistentVolumeClaim(HostMetricbeatDataVolumeName, HostMetricbeatDataMountPath) - } else { - hostMetricbeatDataPath := fmt.Sprintf(HostMetricbeatDataPathTemplate, d.Owner.GetNamespace(), d.Owner.GetName()) - metricbeatDataVolume = volume.NewHostVolume( - HostMetricbeatDataVolumeName, - hostMetricbeatDataPath, - HostMetricbeatDataMountPath, - false, - corev1.HostPathDirectoryOrCreate) - } - for _, volume := range []volume.VolumeLike{ dockerSockVolume, procVolume, cgroupVolume, - metricbeatDataVolume, } { builder.WithVolumes(volume.Volume()).WithVolumeMounts(volume.VolumeMount()) } diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/common/beat/pod.go new file mode 100644 index 0000000000..de22e38d2d --- /dev/null +++ b/pkg/controller/common/beat/pod.go @@ -0,0 +1,118 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "fmt" + "hash" + + "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" + commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" + corev1 "k8s.io/api/core/v1" +) + +func buildPodTemplate(params DriverParams, defaultImage container.Image, f func(builder *defaults.PodTemplateBuilder), checksum hash.Hash) corev1.PodTemplateSpec { + podTemplate := params.GetPodTemplate() + + // Token mounting gets defaulted to false, which prevents from detecting whether user set it. + // Instead, checking that here, before the default is applied. + if podTemplate.Spec.AutomountServiceAccountToken == nil { + t := true + podTemplate.Spec.AutomountServiceAccountToken = &t + } + + builder := defaults.NewPodTemplateBuilder(podTemplate, params.Type). + WithTerminationGracePeriod(30). + WithEnv(corev1.EnvVar{ + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }}). + WithResources(defaultResources). + WithHostNetwork(). + WithLabels(map[string]string{ + ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), + VersionLabelName: params.Version}). + WithDockerImage(params.Image, container.ImageRepository(defaultImage, params.Version)). + WithArgs("-e", "-c", ConfigMountPath(params.Type)). + WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). + WithSecurityContext(corev1.SecurityContext{ + RunAsUser: pointer.Int64(0), + }) + + if ShouldSetupAutodiscoverRBAC() { + autodiscoverServiceAccountName := ServiceAccountName(params.Owner.GetName()) + // If SA is already provided, the call will be no-op. This is fine as we then assume + // that for this resource (despite operator configuration) the user took the responsibility + // of configuring RBAC. + builder.WithServiceAccount(autodiscoverServiceAccountName) + } + + dataVolume, _ := createDataVolume(params) //todo + volumes := []volume.VolumeLike{ + volume.NewSecretVolume( + params.Namer.ConfigSecretName(params.Type, params.Owner.GetName()), + ConfigVolumeName, + ConfigMountPath(params.Type), + configFileName(params.Type), + 0600), + dataVolume, + } + + if params.Associated.AssociationConf().CAIsConfigured() { + volumes = append(volumes, volume.NewSecretVolumeWithMountPath( + params.Associated.AssociationConf().GetCASecretName(), + CAVolumeName, + CAMountPath)) + } + + for _, v := range volumes { + builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) + } + + if f != nil { + f(builder) + } + + builder = builder.WithLabels(commonhash.SetTemplateHashLabel(params.Labels, builder.PodTemplate)) + + return builder.PodTemplate +} + +func createDataVolume(dp DriverParams) (volume.VolumeLike, error) { + var v volume.VolumeLike + dataMountPath := fmt.Sprintf("/usr/share/%s/data", dp.Type) + switch { + case dp.DaemonSet != nil: + { + hostDataPath := fmt.Sprintf( + "/var/lib/%s/%s/%s-data", + dp.Owner.GetNamespace(), + dp.Owner.GetName(), + dp.Type) + v = volume.NewHostVolume( + "data", + hostDataPath, + dataMountPath, + false, + corev1.HostPathDirectoryOrCreate) + } + case dp.Deployment != nil: + { + v = volume.NewPersistentVolumeClaim( + "data", + dataMountPath) + } + default: + return nil, fmt.Errorf("both daemonset and deployment are nil") + } + + return v, nil +} diff --git a/pkg/controller/common/beat/reconcile.go b/pkg/controller/common/beat/reconcile.go new file mode 100644 index 0000000000..46c653be54 --- /dev/null +++ b/pkg/controller/common/beat/reconcile.go @@ -0,0 +1,121 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + v1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" +) + +func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams) (DriverStatus, error) { + var reconciliationFunc func(params ReconciliationParams) (int32, int32, error) + + name := params.Namer.Name(params.Type, params.Owner.GetName()) + var toDelete runtime.Object + switch { + case params.DaemonSet != nil: + { + reconciliationFunc = reconcileDaemonSet + toDelete = &v1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Owner.GetNamespace(), + }, + } + } + case params.Deployment != nil: + { + reconciliationFunc = reconcileDeployment + toDelete = &v1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Owner.GetNamespace(), + }, + } + } + } + + ready, desired, err := reconciliationFunc(ReconciliationParams{ + client: params.Client, + name: name, + podTemplate: podTemplate, + owner: params.Owner, + labels: params.Labels, + selectors: params.Selectors, + replicas: params.GetReplicas(), + }) + if err != nil { + return DriverStatus{}, nil + } + + // clean up the other one + if err := params.Client.Delete(toDelete); err != nil && !apierrors.IsNotFound(err) { + return DriverStatus{}, err + } + + return DriverStatus{ + ExpectedNodes: desired, + AvailableNodes: ready, + Health: health.CalculateHealth(params.Associated, ready, desired), + Association: params.Associated.AssociationStatus(), + }, nil +} + +type ReconciliationParams struct { + client k8s.Client + name string + podTemplate corev1.PodTemplateSpec + owner metav1.Object + labels map[string]string + selectors map[string]string + replicas *int32 +} + +func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { + d := deployment.New(deployment.Params{ + Name: rp.name, + Namespace: rp.owner.GetNamespace(), + Selector: rp.selectors, + Labels: rp.labels, + PodTemplateSpec: rp.podTemplate, + Replicas: pointer.Int32OrDefault(rp.replicas, int32(1)), + }) + if err := controllerutil.SetControllerReference(rp.owner, &d, scheme.Scheme); err != nil { + return 0, 0, err + } + // sync + reconciled, err := deployment.Reconcile(rp.client, d, rp.owner) + if err != nil { + return 0, 0, err + } + + return reconciled.Status.ReadyReplicas, reconciled.Status.Replicas, nil +} + +func reconcileDaemonSet(rp ReconciliationParams) (int32, int32, error) { + ds := daemonset.New(rp.podTemplate, rp.name, rp.labels, rp.owner, rp.selectors) + + if err := controllerutil.SetControllerReference(rp.owner, &ds, scheme.Scheme); err != nil { + return 0, 0, err + } + + reconciled, err := daemonset.Reconcile(rp.client, ds, rp.owner) + if err != nil { + return 0, 0, err + } + + return reconciled.Status.NumberReady, reconciled.Status.DesiredNumberScheduled, nil +} From 897bbfbce94e89ba26c6cb254c32f9c3924cd0ad Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 17:48:21 +0200 Subject: [PATCH 19/81] Set default replica count for unknown beats --- pkg/controller/common/beat/otherbeat/otherbeat.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 45c6d08bed..835dae8450 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -6,6 +6,7 @@ package otherbeat import ( commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) const ( @@ -22,9 +23,10 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { } func (d *Driver) Reconcile() commonbeat.DriverResults { - if d.DaemonSet == nil && d.Deployment == nil { - d.Deployment = &commonbeat.DeploymentSpec{} + d.Deployment = &commonbeat.DeploymentSpec{ + Replicas: pointer.Int32(1), + } } return commonbeat.Reconcile( From 66e29aeddd605d8c330f6056210d2c58b56c6413 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 17:49:05 +0200 Subject: [PATCH 20/81] Fix setting labels on daemonset --- pkg/controller/common/daemonset/reconcile.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/controller/common/daemonset/reconcile.go b/pkg/controller/common/daemonset/reconcile.go index 6b9a2fa74a..d74db661cd 100644 --- a/pkg/controller/common/daemonset/reconcile.go +++ b/pkg/controller/common/daemonset/reconcile.go @@ -14,11 +14,12 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) -func New(podTemplate corev1.PodTemplateSpec, name string, owner metav1.Object, selectors map[string]string) appsv1.DaemonSet { +func New(podTemplate corev1.PodTemplateSpec, name string, labels map[string]string, owner metav1.Object, selectors map[string]string) appsv1.DaemonSet { return appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: owner.GetNamespace(), + Labels: labels, }, Spec: appsv1.DaemonSetSpec{ Selector: &metav1.LabelSelector{ From fcb80023ed68b95ec09d643867c1ae938d63787f Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 17:51:18 +0200 Subject: [PATCH 21/81] Fix not returning error when association secret key isn't found --- pkg/controller/association/conf.go | 8 +++++++- pkg/controller/association/conf_test.go | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkg/controller/association/conf.go b/pkg/controller/association/conf.go index 36e3b92440..cacbcacbcd 100644 --- a/pkg/controller/association/conf.go +++ b/pkg/controller/association/conf.go @@ -74,7 +74,13 @@ func ElasticsearchAuthSettings(c k8s.Client, associated commonv1.Associated) (us if err := c.Get(secretObjKey, &secret); err != nil { return "", "", err } - return assocConf.AuthSecretKey, string(secret.Data[assocConf.AuthSecretKey]), nil + + data, ok := secret.Data[assocConf.AuthSecretKey] + if !ok { + return "", "", errors.Errorf("auth secret key %s doesn't exist", assocConf.AuthSecretKey) + } + + return assocConf.AuthSecretKey, string(data), nil } // GetAssociationConf extracts the association configuration from the given object by reading the annotations. diff --git a/pkg/controller/association/conf_test.go b/pkg/controller/association/conf_test.go index 6e634ea7b0..b1003e07e8 100644 --- a/pkg/controller/association/conf_test.go +++ b/pkg/controller/association/conf_test.go @@ -244,6 +244,23 @@ func TestElasticsearchAuthSettings(t *testing.T) { }, wantErr: true, }, + { + name: "When the auth secret key does not exist", + client: k8s.WrappedFakeClient(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "apmelasticsearchassociation-sample-elastic-internal-apm", + Namespace: "default", + }, + Data: map[string][]byte{"elastic-internal-apm": []byte("a2s1Nmt0N3Nwdmg4cmpqdDlucWhsN3cy")}, + }), + assocConf: commonv1.AssociationConf{ + AuthSecretName: "apmelasticsearchassociation-sample-elastic-internal-apm", + AuthSecretKey: "bad-key", + CASecretName: "ca-secret", + URL: "https://elasticsearch-sample-es-http.default.svc:9200", + }, + wantErr: true, + }, } for _, tt := range tests { From b45481f345ab046329a74843b59b388322190cc1 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 17:51:59 +0200 Subject: [PATCH 22/81] Add UTs for beat common and config --- pkg/controller/common/beat/common_test.go | 24 ++++ pkg/controller/common/beat/config_test.go | 155 ++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 pkg/controller/common/beat/common_test.go create mode 100644 pkg/controller/common/beat/config_test.go diff --git a/pkg/controller/common/beat/common_test.go b/pkg/controller/common/beat/common_test.go new file mode 100644 index 0000000000..3785bc4412 --- /dev/null +++ b/pkg/controller/common/beat/common_test.go @@ -0,0 +1,24 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Reconcile(t *testing.T) { + params := DriverParams{ + DaemonSet: &DaemonSetSpec{}, + Deployment: &DeploymentSpec{}, + } + + result := Reconcile(params, nil, "", nil) + require.True(t, result.Results.HasError()) + + result = Reconcile(DriverParams{}, nil, "", nil) + require.True(t, result.Results.HasError()) +} diff --git a/pkg/controller/common/beat/config_test.go b/pkg/controller/common/beat/config_test.go new file mode 100644 index 0000000000..2c8b905a1b --- /dev/null +++ b/pkg/controller/common/beat/config_test.go @@ -0,0 +1,155 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package beat + +import ( + "testing" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_buildBeatConfig(t *testing.T) { + clientWithSecret := k8s.WrappedFakeClient( + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "ns", + }, + Data: map[string][]byte{"elastic": []byte("123")}, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret2", + Namespace: "ns", + }, + Data: map[string][]byte{"elastic": []byte("123")}, + }) + + defaultConfig := settings.MustParseConfig([]byte("default: true")) + userConfig := &commonv1.Config{Data: map[string]interface{}{"user": "true"}} + userCanonicalConfig := settings.MustCanonicalConfig(userConfig.Data) + outputCAYaml := settings.MustParseConfig([]byte("output.elasticsearch.ssl.certificate_authorities: /mnt/elastic-internal/es-certs/ca.crt")) + outputYaml := settings.MustParseConfig([]byte(`output: + elasticsearch: + hosts: + - url + password: "123" + username: elastic +`)) + + withAssociation := &beatv1beta1.Beat{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns", + }, + } + withAssociation.SetAssociationConf(&commonv1.AssociationConf{ + AuthSecretName: "secret", + AuthSecretKey: "elastic", + CACertProvided: false, + CASecretName: "secret2", + URL: "url", + }) + withAssociationWithCA := withAssociation.DeepCopy() + withAssociationWithCA.AssociationConf().CACertProvided = true + + merge := func(cs ...*settings.CanonicalConfig) *settings.CanonicalConfig { + result := settings.NewCanonicalConfig() + _ = result.MergeWith(cs...) + return result + } + + for _, tt := range []struct { + name string + client k8s.Client + associated commonv1.Associated + defaultConfig *settings.CanonicalConfig + userConfig *commonv1.Config + want *settings.CanonicalConfig + wantErr bool + }{ + { + name: "neither default nor user config", + wantErr: true, + }, + { + name: "no association, only default config", + associated: &beatv1beta1.Beat{}, + defaultConfig: defaultConfig, + want: defaultConfig, + }, + { + name: "no association, only user config", + associated: &beatv1beta1.Beat{}, + userConfig: userConfig, + want: userCanonicalConfig, + }, + { + name: "no association, default and user config", + associated: &beatv1beta1.Beat{}, + defaultConfig: defaultConfig, + userConfig: userConfig, + want: userCanonicalConfig, + }, + { + name: "association without ca, only default config", + client: clientWithSecret, + associated: withAssociation, + defaultConfig: defaultConfig, + want: merge(defaultConfig, outputYaml), + }, + { + name: "association without ca, only user config", + client: clientWithSecret, + associated: withAssociation, + userConfig: userConfig, + want: merge(userCanonicalConfig, outputYaml), + }, + { + name: "association without ca, default and user config", + client: clientWithSecret, + associated: withAssociation, + defaultConfig: defaultConfig, + userConfig: userConfig, + want: merge(userCanonicalConfig, outputYaml), + }, + { + name: "association with ca, only default config", + client: clientWithSecret, + associated: withAssociationWithCA, + defaultConfig: defaultConfig, + want: merge(defaultConfig, outputYaml, outputCAYaml), + }, + { + name: "association with ca, only user config", + client: clientWithSecret, + associated: withAssociationWithCA, + userConfig: userConfig, + want: merge(userCanonicalConfig, outputYaml, outputCAYaml), + }, + { + name: "association with ca, default and user config", + client: clientWithSecret, + associated: withAssociationWithCA, + defaultConfig: defaultConfig, + userConfig: userConfig, + want: merge(userCanonicalConfig, outputYaml, outputCAYaml), + }, + } { + t.Run(tt.name, func(t *testing.T) { + gotYaml, gotErr := buildBeatConfig(tt.client, tt.associated, tt.defaultConfig, tt.userConfig) + + diff := tt.want.Diff(settings.MustParseConfig(gotYaml), nil) + + require.Empty(t, diff) + require.Equal(t, gotErr != nil, tt.wantErr) + }) + } +} From b2760b141c5e99a5774f3a9a70623d152e5d9566 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 19:50:57 +0200 Subject: [PATCH 23/81] Fix imports --- pkg/controller/common/beat/config_test.go | 7 ++++--- pkg/controller/common/beat/health/health_test.go | 2 +- test/e2e/test/k8s_client.go | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/controller/common/beat/config_test.go b/pkg/controller/common/beat/config_test.go index 2c8b905a1b..1bf4662b03 100644 --- a/pkg/controller/common/beat/config_test.go +++ b/pkg/controller/common/beat/config_test.go @@ -7,13 +7,14 @@ package beat import ( "testing" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_buildBeatConfig(t *testing.T) { diff --git a/pkg/controller/common/beat/health/health_test.go b/pkg/controller/common/beat/health/health_test.go index 931dd1937e..3db2e820b9 100644 --- a/pkg/controller/common/beat/health/health_test.go +++ b/pkg/controller/common/beat/health/health_test.go @@ -7,11 +7,11 @@ package health_test import ( "testing" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/stretchr/testify/require" beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" ) func Test_CalculateHealth(t *testing.T) { diff --git a/test/e2e/test/k8s_client.go b/test/e2e/test/k8s_client.go index 97eb5b5209..96f105d17c 100644 --- a/test/e2e/test/k8s_client.go +++ b/test/e2e/test/k8s_client.go @@ -10,8 +10,6 @@ import ( "fmt" "os" - beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" - "github.com/elastic/cloud-on-k8s/pkg/controller/beat" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -27,9 +25,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" apmv1 "github.com/elastic/cloud-on-k8s/pkg/apis/apm/v1" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" kbv1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/apmserver" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat" "github.com/elastic/cloud-on-k8s/pkg/controller/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common/certificates" "github.com/elastic/cloud-on-k8s/pkg/controller/common/name" From b1c2f969fca6b752624dca9e046f9503223a7410 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 19:51:55 +0200 Subject: [PATCH 24/81] Make config file mount path the same for all Beats --- pkg/controller/common/beat/config.go | 32 +++------------ .../common/beat/metricbeat/metricbeat.go | 2 +- pkg/controller/common/beat/pod.go | 39 ++++++++++++++----- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index d95134a8dd..c9d5c8af9b 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -9,6 +9,10 @@ import ( "hash" "path" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common" @@ -16,24 +20,6 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - CAVolumeName = "es-certs" - CAMountPath = "/mnt/elastic-internal/es-certs/" - CAFileName = "ca.crt" - - ConfigVolumeName = "config" - ConfigMountDirPath = "/etc" - - // ConfigChecksumLabel is a label used to store beats config checksum. - ConfigChecksumLabel = "beat.k8s.elastic.co/config-checksum" - - // VersionLabelName is a label used to track the version of a Beat Pod. - VersionLabelName = "beat.k8s.elastic.co/version" ) var ( @@ -133,7 +119,7 @@ func reconcileConfig( Labels: common.AddCredentialsLabel(params.Labels), }, Data: map[string][]byte{ - configFileName(params.Type): cfgBytes, + ConfigMountPath: cfgBytes, }, } @@ -151,11 +137,3 @@ func reconcileConfig( return nil } - -func configFileName(typ string) string { - return fmt.Sprintf("%s.yml", typ) -} - -func ConfigMountPath(typ string) string { - return path.Join(ConfigMountDirPath, configFileName(typ)) -} diff --git a/pkg/controller/common/beat/metricbeat/metricbeat.go b/pkg/controller/common/beat/metricbeat/metricbeat.go index a3c1fa20d8..1d215548c2 100644 --- a/pkg/controller/common/beat/metricbeat/metricbeat.go +++ b/pkg/controller/common/beat/metricbeat/metricbeat.go @@ -51,7 +51,7 @@ func (d *Driver) Reconcile() commonbeat.DriverResults { builder.WithVolumes(volume.Volume()).WithVolumeMounts(volume.VolumeMount()) } - builder.WithArgs("-e", "-c", commonbeat.ConfigMountPath(d.Type), "-system.hostfs=/hostfs") + builder.WithArgs("-e", "-c", commonbeat.ConfigMountPath, "-system.hostfs=/hostfs") } if d.DaemonSet == nil && d.Deployment == nil { diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/common/beat/pod.go index de22e38d2d..8fc0570b49 100644 --- a/pkg/controller/common/beat/pod.go +++ b/pkg/controller/common/beat/pod.go @@ -8,12 +8,29 @@ import ( "fmt" "hash" + corev1 "k8s.io/api/core/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" - corev1 "k8s.io/api/core/v1" +) + +const ( + CAVolumeName = "es-certs" + CAMountPath = "/mnt/elastic-internal/es-certs/" + CAFileName = "ca.crt" + + ConfigVolumeName = "config" + ConfigMountPath = "/etc/beat.yml" + ConfigFileName = "beat.yml" + + // ConfigChecksumLabel is a label used to store beats config checksum. + ConfigChecksumLabel = "beat.k8s.elastic.co/config-checksum" + + // VersionLabelName is a label used to track the version of a Beat Pod. + VersionLabelName = "beat.k8s.elastic.co/version" ) func buildPodTemplate(params DriverParams, defaultImage container.Image, f func(builder *defaults.PodTemplateBuilder), checksum hash.Hash) corev1.PodTemplateSpec { @@ -26,7 +43,13 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( podTemplate.Spec.AutomountServiceAccountToken = &t } - builder := defaults.NewPodTemplateBuilder(podTemplate, params.Type). + builder := defaults.NewPodTemplateBuilder(podTemplate, params.Type) + + if f != nil { + f(builder) + } + + builder = builder. WithTerminationGracePeriod(30). WithEnv(corev1.EnvVar{ Name: "NODE_NAME", @@ -41,7 +64,7 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), VersionLabelName: params.Version}). WithDockerImage(params.Image, container.ImageRepository(defaultImage, params.Version)). - WithArgs("-e", "-c", ConfigMountPath(params.Type)). + WithArgs("-e", "-c", ConfigMountPath). WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). WithSecurityContext(corev1.SecurityContext{ RunAsUser: pointer.Int64(0), @@ -55,13 +78,13 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( builder.WithServiceAccount(autodiscoverServiceAccountName) } - dataVolume, _ := createDataVolume(params) //todo + dataVolume, _ := createDataVolume(params) volumes := []volume.VolumeLike{ volume.NewSecretVolume( params.Namer.ConfigSecretName(params.Type, params.Owner.GetName()), ConfigVolumeName, - ConfigMountPath(params.Type), - configFileName(params.Type), + ConfigMountPath, + ConfigFileName, 0600), dataVolume, } @@ -77,10 +100,6 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) } - if f != nil { - f(builder) - } - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(params.Labels, builder.PodTemplate)) return builder.PodTemplate From fc9c766e86286d8daefc3fea09d3cd2195f8aeda Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 19:52:37 +0200 Subject: [PATCH 25/81] Fix some comments --- pkg/controller/common/beat/otherbeat/otherbeat.go | 6 +----- pkg/controller/common/beat/reconcile.go | 2 +- test/e2e/test/beat/builder.go | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 835dae8450..d103277495 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -29,9 +29,5 @@ func (d *Driver) Reconcile() commonbeat.DriverResults { } } - return commonbeat.Reconcile( - d.DriverParams, - nil, - "", - nil) + return commonbeat.Reconcile(d.DriverParams, nil, "", nil) } diff --git a/pkg/controller/common/beat/reconcile.go b/pkg/controller/common/beat/reconcile.go index 46c653be54..e7691fa8e5 100644 --- a/pkg/controller/common/beat/reconcile.go +++ b/pkg/controller/common/beat/reconcile.go @@ -96,7 +96,7 @@ func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { if err := controllerutil.SetControllerReference(rp.owner, &d, scheme.Scheme); err != nil { return 0, 0, err } - // sync + reconciled, err := deployment.Reconcile(rp.client, d, rp.owner) if err != nil { return 0, 0, err diff --git a/test/e2e/test/beat/builder.go b/test/e2e/test/beat/builder.go index 1ed7a0ac77..57029b3711 100644 --- a/test/e2e/test/beat/builder.go +++ b/test/e2e/test/beat/builder.go @@ -17,7 +17,7 @@ import ( "github.com/elastic/cloud-on-k8s/test/e2e/test" ) -// Builder to create Beats +// Builder to create a Beat type Builder struct { Beat beatv1beta1.Beat Validations []ValidationFunc @@ -83,7 +83,6 @@ func (b Builder) WithRestrictedSecurityContext() Builder { if b.Beat.Spec.Deployment != nil { b.Beat.Spec.DaemonSet.PodTemplate.Spec.SecurityContext = test.DefaultSecurityContext() } - // todo what if both are nil? return b } @@ -114,7 +113,6 @@ func (b Builder) WithPodLabel(key, value string) Builder { podSpec.Labels[key] = value } - // todo what if both are nil? return b } From f882293f48046d9886e16b7735c89306ebe9f102 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 20:48:38 +0200 Subject: [PATCH 26/81] Make path strings const --- pkg/controller/common/beat/config.go | 2 +- pkg/controller/common/beat/pod.go | 12 ++++++++---- pkg/controller/common/beat/reconcile.go | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index c9d5c8af9b..295ece08d6 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -119,7 +119,7 @@ func reconcileConfig( Labels: common.AddCredentialsLabel(params.Labels), }, Data: map[string][]byte{ - ConfigMountPath: cfgBytes, + ConfigFileName: cfgBytes, }, } diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/common/beat/pod.go index 8fc0570b49..e1509e2f33 100644 --- a/pkg/controller/common/beat/pod.go +++ b/pkg/controller/common/beat/pod.go @@ -26,6 +26,10 @@ const ( ConfigMountPath = "/etc/beat.yml" ConfigFileName = "beat.yml" + DataVolumeName = "data" + DataMountPathTemplate = "/var/lib/%s/%s/%s-data" + DataPathTemplate = "/usr/share/%s/data" + // ConfigChecksumLabel is a label used to store beats config checksum. ConfigChecksumLabel = "beat.k8s.elastic.co/config-checksum" @@ -106,18 +110,18 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( } func createDataVolume(dp DriverParams) (volume.VolumeLike, error) { + dataMountPath := fmt.Sprintf(DataPathTemplate, dp.Type) var v volume.VolumeLike - dataMountPath := fmt.Sprintf("/usr/share/%s/data", dp.Type) switch { case dp.DaemonSet != nil: { hostDataPath := fmt.Sprintf( - "/var/lib/%s/%s/%s-data", + DataMountPathTemplate, dp.Owner.GetNamespace(), dp.Owner.GetName(), dp.Type) v = volume.NewHostVolume( - "data", + DataVolumeName, hostDataPath, dataMountPath, false, @@ -126,7 +130,7 @@ func createDataVolume(dp DriverParams) (volume.VolumeLike, error) { case dp.Deployment != nil: { v = volume.NewPersistentVolumeClaim( - "data", + DataVolumeName, dataMountPath) } default: diff --git a/pkg/controller/common/beat/reconcile.go b/pkg/controller/common/beat/reconcile.go index e7691fa8e5..2e9653ac74 100644 --- a/pkg/controller/common/beat/reconcile.go +++ b/pkg/controller/common/beat/reconcile.go @@ -58,7 +58,7 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams replicas: params.GetReplicas(), }) if err != nil { - return DriverStatus{}, nil + return DriverStatus{}, err } // clean up the other one From 1b22437753fbe7cb9ca3c5410341937ea26fa450 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 20:52:42 +0200 Subject: [PATCH 27/81] Remove pv for now --- pkg/controller/common/beat/pod.go | 39 +++++------------ pkg/controller/common/volume/persistent.go | 51 ---------------------- 2 files changed, 10 insertions(+), 80 deletions(-) delete mode 100644 pkg/controller/common/volume/persistent.go diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/common/beat/pod.go index e1509e2f33..9ec191c38d 100644 --- a/pkg/controller/common/beat/pod.go +++ b/pkg/controller/common/beat/pod.go @@ -82,7 +82,7 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( builder.WithServiceAccount(autodiscoverServiceAccountName) } - dataVolume, _ := createDataVolume(params) + dataVolume := createDataVolume(params) volumes := []volume.VolumeLike{ volume.NewSecretVolume( params.Namer.ConfigSecretName(params.Type, params.Owner.GetName()), @@ -109,33 +109,14 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( return builder.PodTemplate } -func createDataVolume(dp DriverParams) (volume.VolumeLike, error) { +func createDataVolume(dp DriverParams) volume.VolumeLike { dataMountPath := fmt.Sprintf(DataPathTemplate, dp.Type) - var v volume.VolumeLike - switch { - case dp.DaemonSet != nil: - { - hostDataPath := fmt.Sprintf( - DataMountPathTemplate, - dp.Owner.GetNamespace(), - dp.Owner.GetName(), - dp.Type) - v = volume.NewHostVolume( - DataVolumeName, - hostDataPath, - dataMountPath, - false, - corev1.HostPathDirectoryOrCreate) - } - case dp.Deployment != nil: - { - v = volume.NewPersistentVolumeClaim( - DataVolumeName, - dataMountPath) - } - default: - return nil, fmt.Errorf("both daemonset and deployment are nil") - } - - return v, nil + hostDataPath := fmt.Sprintf(DataMountPathTemplate, dp.Owner.GetNamespace(), dp.Owner.GetName(), dp.Type) + + return volume.NewHostVolume( + DataVolumeName, + hostDataPath, + dataMountPath, + false, + corev1.HostPathDirectoryOrCreate) } diff --git a/pkg/controller/common/volume/persistent.go b/pkg/controller/common/volume/persistent.go deleted file mode 100644 index 2a23b476b1..0000000000 --- a/pkg/controller/common/volume/persistent.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package volume - -import ( - corev1 "k8s.io/api/core/v1" -) - -// NewPersistentVolumeClaim creates a new PersistentVolumeClaim struct -func NewPersistentVolumeClaim(name, mountPath string) HostVolume { - return HostVolume{ - name: name, - mountPath: mountPath, - } -} - -// PersistentVolumeClaim defines a persistent volume claim -type PersistentVolumeClaim struct { - name string - mountPath string -} - -// VolumeMount returns the k8s volume mount. -func (pvc PersistentVolumeClaim) VolumeMount() corev1.VolumeMount { - return corev1.VolumeMount{ - Name: pvc.name, - MountPath: pvc.mountPath, - } -} - -// Volume returns the k8s volume. -func (pvc PersistentVolumeClaim) Volume() corev1.Volume { - return corev1.Volume{ - Name: pvc.name, - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - // actual claim name will be resolved and fixed right before pod creation - ClaimName: "claim-name-placeholder", - }, - }, - } -} - -// Name returns the name of the volume -func (pvc PersistentVolumeClaim) Name() string { - return pvc.name -} - -var _ VolumeLike = PersistentVolumeClaim{} From 0c6a13222c1887c328cb223e113b3efd64f31f14 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 24 May 2020 22:03:20 +0200 Subject: [PATCH 28/81] Fix permissions for the operator --- config/operator/all-in-one/cluster_role.template.yaml | 9 +++++++++ config/operator/namespace/operator.template.yaml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/config/operator/all-in-one/cluster_role.template.yaml b/config/operator/all-in-one/cluster_role.template.yaml index 1c9d0aa32d..cf61edcdf1 100644 --- a/config/operator/all-in-one/cluster_role.template.yaml +++ b/config/operator/all-in-one/cluster_role.template.yaml @@ -13,6 +13,15 @@ rules: - rbac verbs: - create +- apiGroups: + - "rbac.authorization.k8s.io" + resources: + - clusterroles + - clusterrolebindings + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/config/operator/namespace/operator.template.yaml b/config/operator/namespace/operator.template.yaml index 37b38e14b4..314422a9f2 100644 --- a/config/operator/namespace/operator.template.yaml +++ b/config/operator/namespace/operator.template.yaml @@ -23,7 +23,7 @@ spec: containers: - image: name: manager - args: ["manager", "--namespaces", ""] + args: ["manager", "--namespaces", "", "--disable-autodiscover-rbac-setup", "true"] env: - name: OPERATOR_NAMESPACE valueFrom: From e10b7e9f3435df00b3b93a8a5895b77804afaed3 Mon Sep 17 00:00:00 2001 From: David Kowalski <50632861+david-kow@users.noreply.github.com> Date: Sun, 24 May 2020 23:58:45 +0200 Subject: [PATCH 29/81] Apply suggestions from code review Co-authored-by: Peter Brachwitz --- cmd/manager/main.go | 2 +- pkg/apis/beat/v1beta1/beat_types.go | 6 +++--- pkg/apis/beat/v1beta1/webhook.go | 2 +- pkg/controller/beat/controller.go | 8 ++++---- pkg/controller/common/association/association.go | 2 +- pkg/controller/common/beat/autodiscover.go | 8 ++++---- pkg/controller/common/beat/config.go | 8 +++++--- pkg/controller/common/beat/config_test.go | 3 ++- pkg/controller/common/beat/health/health.go | 4 ++-- pkg/controller/common/beat/pod.go | 2 +- pkg/controller/common/daemonset/reconcile.go | 2 +- pkg/controller/common/settings/canonical_config.go | 2 +- pkg/controller/common/volume/host.go | 2 +- 13 files changed, 27 insertions(+), 24 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 3758d5c4df..0239099a0b 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -171,7 +171,7 @@ func init() { Cmd.Flags().Bool( operator.EnableAutodiscoverRBACSetup, true, - "Determines whether the operator should set up role, binding and service account for Beats autodiscover feature", + "Determines whether the operator should set up role, binding and service account for the Beats autodiscover feature", ) // enable using dashed notation in flags and underscores in env diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index 29db73f332..81f4d28194 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -42,7 +42,7 @@ type BeatSpec struct { // DaemonSet field allows to: // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used - // 2. Provide spec for the DaemonSet + // 2. Provide a spec for the DaemonSet // At most one of DaemonSet and Deployment can be used. // +kubebuilder:validation:Optional DaemonSet *DaemonSetSpec `json:"daemonSet,omitempty"` @@ -82,7 +82,7 @@ type BeatStatus struct { // +kubebuilder:object:root=true -// Beat is the Schema for the beats API +// Beat is the Schema for the Beats API // +kubebuilder:resource:categories=elastic,shortName=beat // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="health",type="string",JSONPath=".status.health" @@ -132,7 +132,7 @@ func (b *Beat) SetAssociationStatus(status commonv1.AssociationStatus) { // +kubebuilder:object:root=true -// BeatList contains a list of Beat +// BeatList contains a list of Beats type BeatList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/beat/v1beta1/webhook.go b/pkg/apis/beat/v1beta1/webhook.go index f915b2c6e0..a322ead455 100644 --- a/pkg/apis/beat/v1beta1/webhook.go +++ b/pkg/apis/beat/v1beta1/webhook.go @@ -124,7 +124,7 @@ func checkImageIfTypeUnknown(b *Beat) field.ErrorList { return field.ErrorList{ field.Required( field.NewPath("spec").Child("image"), - "Image is required if Beat type is not well known."), + "Image is required if Beat type is not one of [filebeat, metricbeat]"), } } return nil diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 50f219768b..7ae0432c1d 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -53,7 +53,7 @@ func Add(mgr manager.Manager, params operator.Parameters) error { return addWatches(c, r) } -// newReconciler returns a new reconcile.Reconciler +// newReconciler returns a new reconcile.Reconciler. func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileBeat { client := k8s.WrapClient(mgr.GetClient()) return &ReconcileBeat{ @@ -64,7 +64,7 @@ func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileBe } } -// addWatches registers the required watches +// addWatches registers the required watches. func addWatches(c controller.Controller, r *ReconcileBeat) error { // Watch for changes to Beat if err := c.Watch(&source.Kind{Type: &beatv1beta1.Beat{}}, &handler.EnqueueRequestForObject{}); err != nil { @@ -120,7 +120,7 @@ func addWatches(c controller.Controller, r *ReconcileBeat) error { var _ reconcile.Reconciler = &ReconcileBeat{} -// ReconcileBeat reconciles a Beat object +// ReconcileBeat reconciles a Beat object. type ReconcileBeat struct { k8s.Client recorder record.EventRecorder @@ -131,7 +131,7 @@ type ReconcileBeat struct { } // Reconcile reads that state of the cluster for a Beat object and makes changes based on the state read -// and what is in the Beat.Spec +// and what is in the Beat.Spec. func (r *ReconcileBeat) Reconcile(request reconcile.Request) (reconcile.Result, error) { defer common.LogReconciliationRun(log, request, "beat_name", &r.iteration)() tx, ctx := tracing.NewTransaction(r.Tracer, request.NamespacedName, "beat") diff --git a/pkg/controller/common/association/association.go b/pkg/controller/common/association/association.go index 95aff062ab..75220b1e04 100644 --- a/pkg/controller/common/association/association.go +++ b/pkg/controller/common/association/association.go @@ -15,7 +15,7 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) -// WriteAssocSecretToHash dereferences auth secret (if any) to include it in the checksum +// WriteAssocSecretToHash dereferences auth secret (if any) to include it in the checksum. func WriteAssocSecretToHash(client k8s.Client, assoc commonv1.Associated, hash hash.Hash) error { assocConf := assoc.AssociationConf() diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 966533fed2..99bc7ee176 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -34,17 +34,17 @@ var ( shouldSetupRBAC = false ) -// EnableAutodiscoverRBACSetup enables setting up autodiscover RBAC +// EnableAutodiscoverRBACSetup enables setting up autodiscover RBAC. func EnableAutodiscoverRBACSetup() { shouldSetupRBAC = true } -// ShouldSetupAutodiscoverRBAC returns true if autodiscover RBAC is expected to be set up by the operator +// ShouldSetupAutodiscoverRBAC returns true if autodiscover RBAC is expected to be set up by the operator. func ShouldSetupAutodiscoverRBAC() bool { return shouldSetupRBAC } -// SetupAutodiscoveryRBAC reconciles all resources needed for default RBAC setup +// SetupAutodiscoveryRBAC reconciles all resources needed for the default RBAC setup. func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, owner metav1.Object, labels map[string]string) error { if ShouldSetupAutodiscoverRBAC() { if err := setupAutodiscoverRBAC(ctx, client, owner, labels); err != nil { @@ -134,7 +134,7 @@ func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { } func doReconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error { - // labels set here must be exactly the same for all callers in particular namespace + // labels set here must be exactly the same for all callers in a particular namespace // otherwise they'll just keep trying to override each other objMeta, err := meta.Accessor(obj) if err != nil { diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index 295ece08d6..616fe5f4fb 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -35,7 +35,7 @@ var ( } ) -// setOutput will set output section in Beat config according to association configuration. +// setOutput will set the output section in Beat config according to the association configuration. func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { if associated.AssociationConf().IsConfigured() { username, password, err := association.ElasticsearchAuthSettings(client, associated) @@ -71,7 +71,8 @@ func buildBeatConfig( client k8s.Client, associated commonv1.Associated, defaultConfig *settings.CanonicalConfig, - userConfig *commonv1.Config) ([]byte, error) { + userConfig *commonv1.Config +) ([]byte, error) { cfg := settings.NewCanonicalConfig() if defaultConfig == nil && userConfig == nil { @@ -104,7 +105,8 @@ func buildBeatConfig( func reconcileConfig( params DriverParams, defaultConfig *settings.CanonicalConfig, - checksum hash.Hash) error { + checksum hash.Hash +) error { cfgBytes, err := buildBeatConfig(params.Client, params.Associated, defaultConfig, params.Config) if err != nil { diff --git a/pkg/controller/common/beat/config_test.go b/pkg/controller/common/beat/config_test.go index 1bf4662b03..7e13ced55a 100644 --- a/pkg/controller/common/beat/config_test.go +++ b/pkg/controller/common/beat/config_test.go @@ -32,7 +32,8 @@ func Test_buildBeatConfig(t *testing.T) { Namespace: "ns", }, Data: map[string][]byte{"elastic": []byte("123")}, - }) + }, + ) defaultConfig := settings.MustParseConfig([]byte("default: true")) userConfig := &commonv1.Config{Data: map[string]interface{}{"user": "true"}} diff --git a/pkg/controller/common/beat/health/health.go b/pkg/controller/common/beat/health/health.go index a5adf661f6..9d4e4aaf02 100644 --- a/pkg/controller/common/beat/health/health.go +++ b/pkg/controller/common/beat/health/health.go @@ -11,7 +11,7 @@ import ( type BeatHealth string const ( - // BeatRedHealth means that the health is neither yellow nor green + // BeatRedHealth means that the health is neither yellow nor green. BeatRedHealth BeatHealth = "red" // BeatYellowHealth means that: @@ -25,7 +25,7 @@ const ( BeatGreenHealth BeatHealth = "green" ) -// CalculateHealth returns health of the Beat calculated based on association status, desired count and ready count. +// CalculateHealth returns health of the Beat based on association status, desired count and ready count. func CalculateHealth(associated v1.Associated, ready, desired int32) BeatHealth { if associated.AssociationConf().IsConfigured() && associated.AssociationStatus() != v1.AssociationEstablished { return BeatRedHealth diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/common/beat/pod.go index 9ec191c38d..52545c6336 100644 --- a/pkg/controller/common/beat/pod.go +++ b/pkg/controller/common/beat/pod.go @@ -30,7 +30,7 @@ const ( DataMountPathTemplate = "/var/lib/%s/%s/%s-data" DataPathTemplate = "/usr/share/%s/data" - // ConfigChecksumLabel is a label used to store beats config checksum. + // ConfigChecksumLabel is a label used to store a Beats config checksum. ConfigChecksumLabel = "beat.k8s.elastic.co/config-checksum" // VersionLabelName is a label used to track the version of a Beat Pod. diff --git a/pkg/controller/common/daemonset/reconcile.go b/pkg/controller/common/daemonset/reconcile.go index d74db661cd..5ae64e2392 100644 --- a/pkg/controller/common/daemonset/reconcile.go +++ b/pkg/controller/common/daemonset/reconcile.go @@ -46,7 +46,7 @@ func Reconcile( Expected: &expected, Reconciled: reconciled, NeedsUpdate: func() bool { - // compare hash of the daemon set at the time it was built + // compare hash of the DaemonSet at the time it was built return hash.GetTemplateHashLabel(reconciled.Labels) != hash.GetTemplateHashLabel(expected.Labels) }, UpdateReconciled: func() { diff --git a/pkg/controller/common/settings/canonical_config.go b/pkg/controller/common/settings/canonical_config.go index 0135b2b4ab..ba71519781 100644 --- a/pkg/controller/common/settings/canonical_config.go +++ b/pkg/controller/common/settings/canonical_config.go @@ -90,7 +90,7 @@ func ParseConfig(yml []byte) (*CanonicalConfig, error) { } // MustParseConfig parses the given configuration content into a CanonicalConfig. -// Expects content to be in YAML format. Panis on error. +// Expects content to be in YAML format. Panics on error. func MustParseConfig(yml []byte) *CanonicalConfig { config, err := uyaml.NewConfig(yml, Options...) if err != nil { diff --git a/pkg/controller/common/volume/host.go b/pkg/controller/common/volume/host.go index ebc4b99889..1e580248a6 100644 --- a/pkg/controller/common/volume/host.go +++ b/pkg/controller/common/volume/host.go @@ -8,7 +8,7 @@ import ( corev1 "k8s.io/api/core/v1" ) -// NewReadOnlyHostVolume creates a new HostVolume struct +// NewReadOnlyHostVolume creates a new HostVolume struct. func NewReadOnlyHostVolume(name, hostPath, mountPath string) HostVolume { return NewHostVolume(name, hostPath, mountPath, false, corev1.HostPathUnset) } From df85085fa5d8f28831854c2e9c25c8693ad41526 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 10:32:21 +0200 Subject: [PATCH 30/81] Fix builder security context setting --- test/e2e/test/beat/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test/beat/builder.go b/test/e2e/test/beat/builder.go index 57029b3711..5386275db0 100644 --- a/test/e2e/test/beat/builder.go +++ b/test/e2e/test/beat/builder.go @@ -81,7 +81,7 @@ func (b Builder) WithRestrictedSecurityContext() Builder { b.Beat.Spec.DaemonSet.PodTemplate.Spec.SecurityContext = test.DefaultSecurityContext() } if b.Beat.Spec.Deployment != nil { - b.Beat.Spec.DaemonSet.PodTemplate.Spec.SecurityContext = test.DefaultSecurityContext() + b.Beat.Spec.Deployment.PodTemplate.Spec.SecurityContext = test.DefaultSecurityContext() } return b From 984e9db7a8c59b13d81e615c013aa41c1343345a Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 10:52:10 +0200 Subject: [PATCH 31/81] Fix syntax errors --- pkg/controller/common/beat/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index 616fe5f4fb..44f4b81f17 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -71,7 +71,7 @@ func buildBeatConfig( client k8s.Client, associated commonv1.Associated, defaultConfig *settings.CanonicalConfig, - userConfig *commonv1.Config + userConfig *commonv1.Config, ) ([]byte, error) { cfg := settings.NewCanonicalConfig() @@ -105,7 +105,7 @@ func buildBeatConfig( func reconcileConfig( params DriverParams, defaultConfig *settings.CanonicalConfig, - checksum hash.Hash + checksum hash.Hash, ) error { cfgBytes, err := buildBeatConfig(params.Client, params.Associated, defaultConfig, params.Config) From fb9d5c47b7d0e9690c1ae4b5f8a0a9cffbd674c0 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 10:53:34 +0200 Subject: [PATCH 32/81] Adhere to testing conventions --- pkg/controller/beat/name_test.go | 78 ++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/pkg/controller/beat/name_test.go b/pkg/controller/beat/name_test.go index c915da5d52..3646c877ad 100644 --- a/pkg/controller/beat/name_test.go +++ b/pkg/controller/beat/name_test.go @@ -8,50 +8,72 @@ import "testing" func TestNamer_ConfigSecretName(t *testing.T) { namer := Namer{} - for _, typeName := range []struct { - name string - typ string - want string + for _, tt := range []struct { + name string + resourceName string + typ string + want string }{ { - name: "my", - typ: "filebeat", - want: "my-beat-filebeat-config", + name: "filebeat type", + resourceName: "my", + typ: "filebeat", + want: "my-beat-filebeat-config", }, { - name: "sample-metricbeat", - typ: "metricbeat", - want: "sample-metricbeat-beat-metricbeat-config", + name: "metricbeat type", + resourceName: "sample-metricbeat", + typ: "metricbeat", + want: "sample-metricbeat-beat-metricbeat-config", + }, + { + name: "other type", + resourceName: "x", + typ: "mybeat", + want: "x-beat-mybeat-config", }, } { - got := namer.ConfigSecretName(typeName.typ, typeName.name) - if got != typeName.want { - t.Errorf("config secret name is %s while %s was expected", got, typeName.want) - } + t.Run(tt.name, func(t *testing.T) { + got := namer.ConfigSecretName(tt.typ, tt.resourceName) + if got != tt.want { + t.Errorf("config secret name is %s while %s was expected", got, tt.want) + } + }) } } func TestNamer_Name(t *testing.T) { namer := Namer{} - for _, typeName := range []struct { - name string - typ string - want string + for _, tt := range []struct { + name string + resourceName string + typ string + want string }{ { - name: "my", - typ: "filebeat", - want: "my-beat-filebeat", + name: "filebeat type", + resourceName: "my", + typ: "filebeat", + want: "my-beat-filebeat", + }, + { + name: "metricbeat type", + resourceName: "sample-metricbeat", + typ: "metricbeat", + want: "sample-metricbeat-beat-metricbeat", }, { - name: "sample-metricbeat", - typ: "metricbeat", - want: "sample-metricbeat-beat-metricbeat", + name: "other type", + resourceName: "x", + typ: "mybeat", + want: "x-beat-mybeat", }, } { - got := namer.Name(typeName.typ, typeName.name) - if got != typeName.want { - t.Errorf("config secret name is %s while %s was expected", got, typeName.want) - } + t.Run(tt.name, func(t *testing.T) { + got := namer.Name(tt.typ, tt.resourceName) + if got != tt.want { + t.Errorf("config secret name is %s while %s was expected", got, tt.want) + } + }) } } From 0493d625b0ff8441e09c996a748f2837acf3f319 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 10:56:05 +0200 Subject: [PATCH 33/81] Fix redundant syntax --- pkg/controller/common/beat/common.go | 9 ++------ pkg/controller/common/beat/reconcile.go | 28 +++++++++++-------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/pkg/controller/common/beat/common.go b/pkg/controller/common/beat/common.go index 4daf46cae2..56d556d14b 100644 --- a/pkg/controller/common/beat/common.go +++ b/pkg/controller/common/beat/common.go @@ -73,14 +73,9 @@ func (dp *DriverParams) GetReplicas() *int32 { func (dp *DriverParams) GetPodTemplate() corev1.PodTemplateSpec { switch { case dp.DaemonSet != nil: - { - return dp.DaemonSet.PodTemplate - } - + return dp.DaemonSet.PodTemplate case dp.Deployment != nil: - { - return dp.Deployment.PodTemplate - } + return dp.Deployment.PodTemplate } return corev1.PodTemplateSpec{} diff --git a/pkg/controller/common/beat/reconcile.go b/pkg/controller/common/beat/reconcile.go index 2e9653ac74..c6a5ae3770 100644 --- a/pkg/controller/common/beat/reconcile.go +++ b/pkg/controller/common/beat/reconcile.go @@ -27,24 +27,20 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams var toDelete runtime.Object switch { case params.DaemonSet != nil: - { - reconciliationFunc = reconcileDaemonSet - toDelete = &v1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Owner.GetNamespace(), - }, - } + reconciliationFunc = reconcileDaemonSet + toDelete = &v1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Owner.GetNamespace(), + }, } case params.Deployment != nil: - { - reconciliationFunc = reconcileDeployment - toDelete = &v1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: params.Owner.GetNamespace(), - }, - } + reconciliationFunc = reconcileDeployment + toDelete = &v1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.Owner.GetNamespace(), + }, } } From eeefc997b9154569c970d91e18d74f5f5a3ab77a Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 10:58:50 +0200 Subject: [PATCH 34/81] Regenerate docs --- config/crds/all-crds.yaml | 18 +++++++++--------- .../crds/bases/beat.k8s.elastic.co_beats.yaml | 18 +++++++++--------- docs/reference/api-docs.asciidoc | 6 +++--- pkg/apis/beat/v1beta1/beat_types.go | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/config/crds/all-crds.yaml b/config/crds/all-crds.yaml index 26c3495258..4d3aa1cdb8 100644 --- a/config/crds/all-crds.yaml +++ b/config/crds/all-crds.yaml @@ -482,7 +482,7 @@ spec: status: {} validation: openAPIV3Schema: - description: Beat is the Schema for the beats API + description: Beat is the Schema for the Beats API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -504,17 +504,17 @@ spec: override the default configuration. type: object daemonSet: - description: 'DaemonSet field allows to: 1. Indicate whether the Beat - should be deployed as DaemonSet or Deployment, if both are absent, - a default for the Type is used 2. Provide spec for the DaemonSet - At most one of DaemonSet and Deployment can be used.' + description: 'DaemonSet allows to: 1. Indicate whether the Beat should + be deployed as DaemonSet or Deployment, if both are absent, a default + for the Type is used 2. Provide a spec for the DaemonSet At most + one of DaemonSet and Deployment can be used.' properties: {} type: object deployment: - description: 'Deployment field allows to: 1. Indicate whether the Beat - should be deployed as DaemonSet or Deployment, if both are absent, - a default for the Type is used 2. provide spec for the Deployment - At most one of DaemonSet and Deployment can be used.' + description: 'Deployment allows to: 1. Indicate whether the Beat should + be deployed as DaemonSet or Deployment, if both are absent, a default + for the Type is used 2. Provide a spec for the Deployment At most + one of DaemonSet and Deployment can be used.' properties: replicas: format: int32 diff --git a/config/crds/bases/beat.k8s.elastic.co_beats.yaml b/config/crds/bases/beat.k8s.elastic.co_beats.yaml index 42b753c0ba..3ce1137839 100644 --- a/config/crds/bases/beat.k8s.elastic.co_beats.yaml +++ b/config/crds/bases/beat.k8s.elastic.co_beats.yaml @@ -46,7 +46,7 @@ spec: status: {} validation: openAPIV3Schema: - description: Beat is the Schema for the beats API + description: Beat is the Schema for the Beats API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -68,10 +68,10 @@ spec: override the default configuration. type: object daemonSet: - description: 'DaemonSet field allows to: 1. Indicate whether the Beat - should be deployed as DaemonSet or Deployment, if both are absent, - a default for the Type is used 2. Provide spec for the DaemonSet - At most one of DaemonSet and Deployment can be used.' + description: 'DaemonSet allows to: 1. Indicate whether the Beat should + be deployed as DaemonSet or Deployment, if both are absent, a default + for the Type is used 2. Provide a spec for the DaemonSet At most + one of DaemonSet and Deployment can be used.' properties: podTemplate: description: PodTemplateSpec describes the data a pod should have @@ -6105,10 +6105,10 @@ spec: type: object type: object deployment: - description: 'Deployment field allows to: 1. Indicate whether the Beat - should be deployed as DaemonSet or Deployment, if both are absent, - a default for the Type is used 2. provide spec for the Deployment - At most one of DaemonSet and Deployment can be used.' + description: 'Deployment allows to: 1. Indicate whether the Beat should + be deployed as DaemonSet or Deployment, if both are absent, a default + for the Type is used 2. Provide a spec for the Deployment At most + one of DaemonSet and Deployment can be used.' properties: podTemplate: description: PodTemplateSpec describes the data a pod should have diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index c9b248d15b..c9f7968e00 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -144,7 +144,7 @@ Package v1beta1 contains API Schema definitions for the beat v1beta1 API group [id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat"] === Beat -Beat is the Schema for the beats API +Beat is the Schema for the Beats API @@ -178,8 +178,8 @@ BeatSpec defines the desired state of a Beat. | *`image`* __string__ | Image is the Beat Docker image to deploy. Version has to match the Beat in the image. | *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the Beat configuration. If provided, it will override the default configuration. | *`serviceAccountName`* __string__ | ServiceAccountName is used to check access from the current resource to Elasticsearch resource in a different namespace. Can only be used if ECK is enforcing RBAC on references. -| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide spec for the DaemonSet At most one of DaemonSet and Deployment can be used. -| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment field allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. provide spec for the Deployment At most one of DaemonSet and Deployment can be used. +| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the DaemonSet At most one of DaemonSet and Deployment can be used. +| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the Deployment At most one of DaemonSet and Deployment can be used. |=== diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index 81f4d28194..3c6044c6ae 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -39,7 +39,7 @@ type BeatSpec struct { // +kubebuilder:validation:Optional ServiceAccountName string `json:"serviceAccountName,omitempty"` - // DaemonSet field allows to: + // DaemonSet allows to: // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used // 2. Provide a spec for the DaemonSet @@ -47,10 +47,10 @@ type BeatSpec struct { // +kubebuilder:validation:Optional DaemonSet *DaemonSetSpec `json:"daemonSet,omitempty"` - // Deployment field allows to: + // Deployment allows to: // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used - // 2. provide spec for the Deployment + // 2. Provide a spec for the Deployment // At most one of DaemonSet and Deployment can be used. // +kubebuilder:validation:Optional Deployment *DeploymentSpec `json:"deployment,omitempty"` From 7d49cd3f08e4d8fecc7ff39d324fd22e50f7a703 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 10:59:16 +0200 Subject: [PATCH 35/81] Remove unnecessary comment from beat sample --- config/samples/beat/filebeat_es_kibana_apm.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/config/samples/beat/filebeat_es_kibana_apm.yaml b/config/samples/beat/filebeat_es_kibana_apm.yaml index 5d5d1ed534..4f4eeb393f 100644 --- a/config/samples/beat/filebeat_es_kibana_apm.yaml +++ b/config/samples/beat/filebeat_es_kibana_apm.yaml @@ -22,12 +22,6 @@ spec: count: 1 elasticsearchRef: name: elasticsearch-sample - #http: - # service: - # spec: - # type: LoadBalancer - # this shows how to customize the Kibana pod - # with labels and resource limits podTemplate: metadata: labels: From c79d07a5cea3c8ccdb4eaea3a168e68b6a4ce93a Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 11:02:50 +0200 Subject: [PATCH 36/81] Move validation to DriverParams --- pkg/controller/common/beat/common.go | 11 +++++++++-- pkg/controller/common/beat/common_test.go | 9 +++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/controller/common/beat/common.go b/pkg/controller/common/beat/common.go index 56d556d14b..622f15eebc 100644 --- a/pkg/controller/common/beat/common.go +++ b/pkg/controller/common/beat/common.go @@ -81,6 +81,13 @@ func (dp *DriverParams) GetPodTemplate() corev1.PodTemplateSpec { return corev1.PodTemplateSpec{} } +func (dp *DriverParams) Validate() error { + if (dp.DaemonSet == nil && dp.Deployment == nil) || (dp.DaemonSet != nil && dp.Deployment != nil) { + return fmt.Errorf("either daemonset or deployment has to be specified") + } + return nil +} + type DriverResults struct { *reconciler.Results Status *DriverStatus @@ -103,8 +110,8 @@ type DriverStatus struct { func Reconcile(params DriverParams, defaultConfig *settings.CanonicalConfig, defaultImage container.Image, modifyPodFunc func(builder *defaults.PodTemplateBuilder)) DriverResults { results := NewDriverResults(params.Context) - if (params.DaemonSet == nil && params.Deployment == nil) || (params.DaemonSet != nil && params.Deployment != nil) { - results.WithError(fmt.Errorf("either daemonset or deployment has to be specified")) + if err := params.Validate(); err != nil { + results.WithError(err) return results } diff --git a/pkg/controller/common/beat/common_test.go b/pkg/controller/common/beat/common_test.go index 3785bc4412..7c29d06041 100644 --- a/pkg/controller/common/beat/common_test.go +++ b/pkg/controller/common/beat/common_test.go @@ -10,15 +10,12 @@ import ( "github.com/stretchr/testify/require" ) -func Test_Reconcile(t *testing.T) { +func Test_DriverParamsValidate(t *testing.T) { params := DriverParams{ DaemonSet: &DaemonSetSpec{}, Deployment: &DeploymentSpec{}, } - result := Reconcile(params, nil, "", nil) - require.True(t, result.Results.HasError()) - - result = Reconcile(DriverParams{}, nil, "", nil) - require.True(t, result.Results.HasError()) + require.Error(t, params.Validate()) + require.Error(t, (&DriverParams{}).Validate()) } From 3b4a5be169dee4416eb793098d2230cf9893c850 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 11:23:07 +0200 Subject: [PATCH 37/81] Fix flag name and value --- cmd/manager/main.go | 4 +-- pkg/controller/common/operator/flags.go | 36 ++++++++++++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 0239099a0b..8d0b81a2fd 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -169,7 +169,7 @@ func init() { fmt.Sprintf("K8s secret mounted into the path designated by %s to be used for webhook certificates", operator.WebhookCertDirFlag), ) Cmd.Flags().Bool( - operator.EnableAutodiscoverRBACSetup, + operator.EnableAutodiscoverRBACSetupFlag, true, "Determines whether the operator should set up role, binding and service account for the Beats autodiscover feature", ) @@ -337,7 +337,7 @@ func execute() { accessReviewer = rbac.NewPermissiveAccessReviewer() } - if viper.GetBool(operator.EnableAutodiscoverRBACSetup) { + if viper.GetBool(operator.EnableAutodiscoverRBACSetupFlag) { commonbeat.EnableAutodiscoverRBACSetup() } diff --git a/pkg/controller/common/operator/flags.go b/pkg/controller/common/operator/flags.go index 6797c5f8e8..8a9b4ae311 100644 --- a/pkg/controller/common/operator/flags.go +++ b/pkg/controller/common/operator/flags.go @@ -5,22 +5,22 @@ package operator const ( - AutoPortForwardFlag = "auto-port-forward" - CACertRotateBeforeFlag = "ca-cert-rotate-before" - CACertValidityFlag = "ca-cert-validity" - CertRotateBeforeFlag = "cert-rotate-before" - CertValidityFlag = "cert-validity" - ContainerRegistryFlag = "container-registry" - DebugHTTPListenFlag = "debug-http-listen" - EnableAutodiscoverRBACSetup = "disable-autodiscover-rbac-setup" - EnableTracingFlag = "enable-tracing" - EnableWebhookFlag = "enable-webhook" - EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" - ManageWebhookCertsFlag = "manage-webhook-certs" - MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" - MetricsPortFlag = "metrics-port" - NamespacesFlag = "namespaces" - OperatorNamespaceFlag = "operator-namespace" - WebhookCertDirFlag = "webhook-cert-dir" - WebhookSecretFlag = "webhook-secret" + AutoPortForwardFlag = "auto-port-forward" + CACertRotateBeforeFlag = "ca-cert-rotate-before" + CACertValidityFlag = "ca-cert-validity" + CertRotateBeforeFlag = "cert-rotate-before" + CertValidityFlag = "cert-validity" + ContainerRegistryFlag = "container-registry" + DebugHTTPListenFlag = "debug-http-listen" + EnableAutodiscoverRBACSetupFlag = "enable-autodiscover-rbac-setup" + EnableTracingFlag = "enable-tracing" + EnableWebhookFlag = "enable-webhook" + EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" + ManageWebhookCertsFlag = "manage-webhook-certs" + MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" + MetricsPortFlag = "metrics-port" + NamespacesFlag = "namespaces" + OperatorNamespaceFlag = "operator-namespace" + WebhookCertDirFlag = "webhook-cert-dir" + WebhookSecretFlag = "webhook-secret" ) From e145e106378c6df18861863e3bb93a57c649e3da Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 11:23:53 +0200 Subject: [PATCH 38/81] Move setting default vehicle to driver constructor --- pkg/controller/common/beat/filebeat/filebeat.go | 9 +++++---- pkg/controller/common/beat/metricbeat/metricbeat.go | 9 +++++---- pkg/controller/common/beat/otherbeat/otherbeat.go | 12 ++++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/common/beat/filebeat/filebeat.go index bedd6dfc0e..494a719b7d 100644 --- a/pkg/controller/common/beat/filebeat/filebeat.go +++ b/pkg/controller/common/beat/filebeat/filebeat.go @@ -33,6 +33,11 @@ type Driver struct { } func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { + // use the default for filebeat type if not provided + if params.DaemonSet == nil && params.Deployment == nil { + params.DaemonSet = &commonbeat.DaemonSetSpec{} + } + return &Driver{DriverParams: params} } @@ -51,10 +56,6 @@ func (d *Driver) Reconcile() commonbeat.DriverResults { } } - if d.DaemonSet == nil && d.Deployment == nil { - d.DaemonSet = &commonbeat.DaemonSetSpec{} - } - return commonbeat.Reconcile( d.DriverParams, defaultConfig, diff --git a/pkg/controller/common/beat/metricbeat/metricbeat.go b/pkg/controller/common/beat/metricbeat/metricbeat.go index 1d215548c2..ee2dbb0144 100644 --- a/pkg/controller/common/beat/metricbeat/metricbeat.go +++ b/pkg/controller/common/beat/metricbeat/metricbeat.go @@ -34,6 +34,11 @@ type Driver struct { } func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { + // use the default for metricbeat type if not provided + if params.DaemonSet == nil && params.Deployment == nil { + params.DaemonSet = &commonbeat.DaemonSetSpec{} + } + return &Driver{DriverParams: params} } @@ -54,10 +59,6 @@ func (d *Driver) Reconcile() commonbeat.DriverResults { builder.WithArgs("-e", "-c", commonbeat.ConfigMountPath, "-system.hostfs=/hostfs") } - if d.DaemonSet == nil && d.Deployment == nil { - d.DaemonSet = &commonbeat.DaemonSetSpec{} - } - return commonbeat.Reconcile( d.DriverParams, defaultConfig, diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index d103277495..78b805661e 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -19,15 +19,15 @@ type Driver struct { } func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { - return &Driver{DriverParams: params} -} - -func (d *Driver) Reconcile() commonbeat.DriverResults { - if d.DaemonSet == nil && d.Deployment == nil { - d.Deployment = &commonbeat.DeploymentSpec{ + // use the default for otherbeat type if not provided + if params.DaemonSet == nil && params.Deployment == nil { + params.Deployment = &commonbeat.DeploymentSpec{ Replicas: pointer.Int32(1), } } + return &Driver{DriverParams: params} +} +func (d *Driver) Reconcile() commonbeat.DriverResults { return commonbeat.Reconcile(d.DriverParams, nil, "", nil) } From 728c6a5c176d71f51a54516bb0c8a14737dc2b50 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 11:24:20 +0200 Subject: [PATCH 39/81] Remove unused otherbeat type name --- pkg/controller/common/beat/otherbeat/otherbeat.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 78b805661e..96f0824b6a 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -9,10 +9,6 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) -const ( - Type commonbeat.Type = "otherbeat" -) - type Driver struct { commonbeat.DriverParams commonbeat.Driver From 586fdd97b7e6b7c9fe2f580741daf3fcd82fc41e Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 11:25:05 +0200 Subject: [PATCH 40/81] Expand comments and rename func for modifying pod template --- pkg/controller/common/beat/pod.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/common/beat/pod.go index 52545c6336..f9b5627436 100644 --- a/pkg/controller/common/beat/pod.go +++ b/pkg/controller/common/beat/pod.go @@ -37,11 +37,12 @@ const ( VersionLabelName = "beat.k8s.elastic.co/version" ) -func buildPodTemplate(params DriverParams, defaultImage container.Image, f func(builder *defaults.PodTemplateBuilder), checksum hash.Hash) corev1.PodTemplateSpec { +func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyPodFunc func(builder *defaults.PodTemplateBuilder), checksum hash.Hash) corev1.PodTemplateSpec { podTemplate := params.GetPodTemplate() - // Token mounting gets defaulted to false, which prevents from detecting whether user set it. + // Token mounting gets defaulted to false, which prevents from detecting whether user had set it. // Instead, checking that here, before the default is applied. + // This is required for autodiscover which is enabled by default. if podTemplate.Spec.AutomountServiceAccountToken == nil { t := true podTemplate.Spec.AutomountServiceAccountToken = &t @@ -49,8 +50,9 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, f func( builder := defaults.NewPodTemplateBuilder(podTemplate, params.Type) - if f != nil { - f(builder) + // might be nil if caller wants to use the default builder without any modifications + if modifyPodFunc != nil { + modifyPodFunc(builder) } builder = builder. From 9fe79749a0a2d78261f55d9406d1672319f1c1aa Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 25 May 2020 16:54:45 +0200 Subject: [PATCH 41/81] Fix e2e permissions --- cmd/manager/main.go | 6 ++- config/dev/elastic-psp.yaml | 2 +- config/e2e/filebeat.yaml | 2 +- config/e2e/operator.yaml | 43 ++++++++++++++++++++++ config/operator/global/rbac.yaml | 3 ++ pkg/controller/beat/controller.go | 4 -- pkg/controller/common/beat/autodiscover.go | 23 +----------- 7 files changed, 53 insertions(+), 30 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 8d0b81a2fd..eee354f2b2 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -267,8 +267,10 @@ func execute() { opts.Namespace = managedNamespaces[0] default: log.Info("Operator configured to manage multiple namespaces", "namespaces", managedNamespaces, "operator_namespace", operatorNamespace) - // always include the operator namespace into the manager cache so that we can work with operator-internal resources in there - opts.NewCache = cache.MultiNamespacedCacheBuilder(append(managedNamespaces, operatorNamespace)) + // always include: + // 1. the operator namespace into the manager cache so that we can work with operator-internal resources in there + // 2. empty namespace for non-namespaced resources + opts.NewCache = cache.MultiNamespacedCacheBuilder(append(managedNamespaces, operatorNamespace, "")) } // only expose prometheus metrics if provided a non-zero port diff --git a/config/dev/elastic-psp.yaml b/config/dev/elastic-psp.yaml index 1096a0c33a..168e556710 100644 --- a/config/dev/elastic-psp.yaml +++ b/config/dev/elastic-psp.yaml @@ -51,7 +51,7 @@ apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: - name: elastic.filebeat.restricted + name: elastic.beat.restricted annotations: seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default' apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' diff --git a/config/e2e/filebeat.yaml b/config/e2e/filebeat.yaml index 13e2016d60..1808a6b346 100644 --- a/config/e2e/filebeat.yaml +++ b/config/e2e/filebeat.yaml @@ -214,7 +214,7 @@ rules: resources: - podsecuritypolicies resourceNames: - - elastic.filebeat.restricted + - elastic.beat.restricted verbs: - use --- diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index 17406ba766..93603ce082 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -60,6 +60,26 @@ rules: - update - patch - delete +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - bind + resourceNames: + - elastic-operator-beat +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - elasticsearch.k8s.elastic.co resources: @@ -288,3 +308,26 @@ spec: name: https-webhook selector: control-plane: {{ .Operator.Name }} +--- +# autodiscover + psp permissions +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-operator-beat +rules: +- apiGroups: [""] # "" indicates the core API group + resources: + - namespaces + - pods + verbs: + - get + - watch + - list +- apiGroups: + - policy + resources: + - podsecuritypolicies + resourceNames: + - elastic.beat.restricted + verbs: + - use diff --git a/config/operator/global/rbac.yaml b/config/operator/global/rbac.yaml index fd52e4c21f..6f741e23a2 100644 --- a/config/operator/global/rbac.yaml +++ b/config/operator/global/rbac.yaml @@ -42,6 +42,9 @@ rules: - apiGroups: ["kibana.k8s.elastic.co"] resources: ["kibanas"] verbs: ["create", "delete", "deletecollection", "patch", "update"] + - apiGroups: ["enterprisesearch.k8s.elastic.co"] + resources: ["enterprisesearches"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] - apiGroups: ["beat.k8s.elastic.co"] resources: ["beats"] verbs: ["create", "delete", "deletecollection", "patch", "update"] diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 7ae0432c1d..7128e53036 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -106,10 +106,6 @@ func addWatches(c controller.Controller, r *ReconcileBeat) error { return err } - if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRole{}}, &handler.EnqueueRequestForObject{}); err != nil { - return err - } - if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRoleBinding{}}, &handler.EnqueueRequestForObject{}); err != nil { return err } diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 99bc7ee176..d9a589755e 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -27,7 +27,7 @@ import ( const ( serviceAccountNameTemplate = "elastic-operator-autodiscover-%s" clusterRoleBindingNameTemplate = "elastic-operator-autodiscover-%s-%s" - clusterRoleName = "elastic-operator-autodiscover" + clusterRoleName = "elastic-operator-beat" ) var ( @@ -71,10 +71,6 @@ func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1. return err } - if err := reconcileClusterRole(client); err != nil { - return err - } - if err := reconcileClusterRoleBinding(client, owner); err != nil { return err } @@ -94,23 +90,6 @@ func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[ return doReconcile(client, sa, owner) } -func reconcileClusterRole(client k8s.Client) error { - role := &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterRoleName, - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{""}, - Verbs: []string{"get", "watch", "list"}, - Resources: []string{"namespaces", "pods"}, - }, - }, - } - - return doReconcile(client, role, nil) -} - func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { binding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ From c2c37e0c772462869f70eb28549b8db9d34c6a21 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Tue, 26 May 2020 19:42:56 +0200 Subject: [PATCH 42/81] Support Beat Builder in samples test --- test/e2e/samples_test.go | 10 ++++++++++ test/e2e/test/helper/yaml.go | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/test/e2e/samples_test.go b/test/e2e/samples_test.go index 40cf3082f4..b7feea8ebe 100644 --- a/test/e2e/samples_test.go +++ b/test/e2e/samples_test.go @@ -12,9 +12,11 @@ import ( "testing" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/apmserver" + "github.com/elastic/cloud-on-k8s/test/e2e/test/beat" "github.com/elastic/cloud-on-k8s/test/e2e/test/elasticsearch" "github.com/elastic/cloud-on-k8s/test/e2e/test/helper" "github.com/elastic/cloud-on-k8s/test/e2e/test/kibana" @@ -81,6 +83,14 @@ func createBuilders(t *testing.T, decoder *helper.YAMLDecoder, sampleFile, testN WithRestrictedSecurityContext(). WithLabel(run.TestNameLabel, fullTestName). WithPodLabel(run.TestNameLabel, fullTestName) + case beat.Builder: + return b.WithNamespace(namespace). + WithSuffix(suffix). + WithElasticsearchRef(tweakElasticsearchRef(b.Beat.Spec.ElasticsearchRef, suffix)). + WithRestrictedSecurityContext(). + WithLabel(run.TestNameLabel, testName). + WithPodLabel(run.TestNameLabel, testName). + WithESValidations(beat.HasEventFromBeat(commonbeat.Type(b.Beat.Spec.Type))) default: return b } diff --git a/test/e2e/test/helper/yaml.go b/test/e2e/test/helper/yaml.go index 7b73afa28d..99877b380b 100644 --- a/test/e2e/test/helper/yaml.go +++ b/test/e2e/test/helper/yaml.go @@ -10,10 +10,13 @@ import ( "io" apmv1 "github.com/elastic/cloud-on-k8s/pkg/apis/apm/v1" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" kbv1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" + commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/apmserver" + "github.com/elastic/cloud-on-k8s/test/e2e/test/beat" "github.com/elastic/cloud-on-k8s/test/e2e/test/elasticsearch" "github.com/elastic/cloud-on-k8s/test/e2e/test/kibana" "k8s.io/apimachinery/pkg/runtime" @@ -33,6 +36,7 @@ func NewYAMLDecoder() *YAMLDecoder { scheme.AddKnownTypes(esv1.GroupVersion, &esv1.Elasticsearch{}, &esv1.ElasticsearchList{}) scheme.AddKnownTypes(kbv1.GroupVersion, &kbv1.Kibana{}, &kbv1.KibanaList{}) scheme.AddKnownTypes(apmv1.GroupVersion, &apmv1.ApmServer{}, &apmv1.ApmServerList{}) + scheme.AddKnownTypes(beatv1beta1.GroupVersion, &beatv1beta1.Beat{}, &beatv1beta1.BeatList{}) decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer() return &YAMLDecoder{decoder: decoder} @@ -70,6 +74,10 @@ func (yd *YAMLDecoder) ToBuilders(reader *bufio.Reader, transform BuilderTransfo b := apmserver.NewBuilderWithoutSuffix(decodedObj.Name) b.ApmServer = *decodedObj builder = transform(b) + case *beatv1beta1.Beat: + b := beat.NewBuilder(decodedObj.Name, commonbeat.Type(decodedObj.Spec.Type)) + b.Beat = *decodedObj + builder = transform(b) default: return builders, fmt.Errorf("unexpected object type: %t", decodedObj) } From fc852c3cfc6d3a70ba0a24ea484216d12afe3760 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 31 May 2020 08:25:57 +0200 Subject: [PATCH 43/81] Add SecurityContext to log generating pod --- test/e2e/test/beat/pod_builder.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/test/beat/pod_builder.go b/test/e2e/test/beat/pod_builder.go index 531632cd49..9be10ab781 100644 --- a/test/e2e/test/beat/pod_builder.go +++ b/test/e2e/test/beat/pod_builder.go @@ -49,6 +49,7 @@ func NewPodBuilder(name string) PodBuilder { }, }, }, + SecurityContext: test.DefaultSecurityContext(), }, }, } From 593e2825bb12491085767bee1c0bd3890bb79ade Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 31 May 2020 11:02:21 +0200 Subject: [PATCH 44/81] Fix permissions for the operator, create autodiscover role --- config/e2e/operator.yaml | 13 +++++-- config/e2e/rbac.yaml | 1 + .../all-in-one/cluster_role.template.yaml | 29 +++++++++++++-- .../namespace/cluster_role.template.yaml | 36 +++++++++++++++++++ .../operator/namespace/operator.template.yaml | 2 +- 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index 93603ce082..47eced64a9 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -67,7 +67,7 @@ rules: verbs: - bind resourceNames: - - elastic-operator-beat + - elastic-operator-beat-autodiscover - apiGroups: - rbac.authorization.k8s.io resources: @@ -309,11 +309,11 @@ spec: selector: control-plane: {{ .Operator.Name }} --- -# autodiscover + psp permissions +# permissions needed for Beat cluster-wide autodiscover feature apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: elastic-operator-beat + name: elastic-operator-beat-autodiscover rules: - apiGroups: [""] # "" indicates the core API group resources: @@ -323,6 +323,13 @@ rules: - get - watch - list +--- +# permission to use PSP for Beat +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-operator-beat-psp +rules: - apiGroups: - policy resources: diff --git a/config/e2e/rbac.yaml b/config/e2e/rbac.yaml index d8cec59a06..928b1cd38f 100644 --- a/config/e2e/rbac.yaml +++ b/config/e2e/rbac.yaml @@ -27,6 +27,7 @@ rules: - podsecuritypolicies resourceNames: - elastic.restricted + - elastic.beat.restricted verbs: - use - apiGroups: diff --git a/config/operator/all-in-one/cluster_role.template.yaml b/config/operator/all-in-one/cluster_role.template.yaml index cf61edcdf1..2ceb809031 100644 --- a/config/operator/all-in-one/cluster_role.template.yaml +++ b/config/operator/all-in-one/cluster_role.template.yaml @@ -10,18 +10,28 @@ rules: - "authorization.k8s.io" resources: - subjectaccessreviews - - rbac verbs: - create - apiGroups: - "rbac.authorization.k8s.io" resources: - - clusterroles - clusterrolebindings verbs: - get - list - watch + - create + - update + - patch + - delete +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - bind + resourceNames: + - elastic-operator-beat-autodiscover - apiGroups: - "" resources: @@ -151,3 +161,18 @@ rules: - update - patch - delete +--- +# permissions needed for Beat cluster-wide autodiscover feature +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-operator-beat-autodiscover +rules: +- apiGroups: [""] # "" indicates the core API group + resources: + - namespaces + - pods + verbs: + - get + - watch + - list diff --git a/config/operator/namespace/cluster_role.template.yaml b/config/operator/namespace/cluster_role.template.yaml index 39d4b55cbc..94cd43f60c 100644 --- a/config/operator/namespace/cluster_role.template.yaml +++ b/config/operator/namespace/cluster_role.template.yaml @@ -33,6 +33,7 @@ rules: - secrets - services - configmaps + - serviceaccounts verbs: - get - list @@ -41,6 +42,26 @@ rules: - update - patch - delete +- apiGroups: + - "rbac.authorization.k8s.io" + resources: + - clusterrolebindings + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - bind + resourceNames: + - elastic-operator-beat-autodiscover - apiGroups: - apps resources: @@ -143,3 +164,18 @@ rules: - update - patch - delete +--- +# permissions needed for Beat cluster-wide autodiscover feature +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-operator-beat-autodiscover +rules: +- apiGroups: [""] # "" indicates the core API group + resources: + - namespaces + - pods + verbs: + - get + - watch + - list diff --git a/config/operator/namespace/operator.template.yaml b/config/operator/namespace/operator.template.yaml index 314422a9f2..37b38e14b4 100644 --- a/config/operator/namespace/operator.template.yaml +++ b/config/operator/namespace/operator.template.yaml @@ -23,7 +23,7 @@ spec: containers: - image: name: manager - args: ["manager", "--namespaces", "", "--disable-autodiscover-rbac-setup", "true"] + args: ["manager", "--namespaces", ""] env: - name: OPERATOR_NAMESPACE valueFrom: From 8fc44d93c0cca03f4c3790090d365886aefad78b Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sun, 31 May 2020 23:47:11 +0200 Subject: [PATCH 45/81] Fix Metricbeat e2e test --- test/e2e/beat/config_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/beat/config_test.go b/test/e2e/beat/config_test.go index 89f048f60b..99d709bc5c 100644 --- a/test/e2e/beat/config_test.go +++ b/test/e2e/beat/config_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/metricbeat" "github.com/stretchr/testify/require" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" @@ -41,7 +42,7 @@ func TestMetricbeatDefaultConfig(t *testing.T) { testPodBuilder := beat.NewPodBuilder(name) - mbBuilder := beat.NewBuilder(name, filebeat.Type). + mbBuilder := beat.NewBuilder(name, metricbeat.Type). WithElasticsearchRef(esBuilder.Ref()). WithESValidations(beat.HasEventFromPod(testPodBuilder.Pod.Name)) From 30fe136cad624bc5508a3f91bb883125cea3987e Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 1 Jun 2020 16:30:33 +0200 Subject: [PATCH 46/81] PR fixes --- Makefile | 8 +- cmd/manager/main.go | 6 +- config/crds/all-crds.yaml | 14 ++-- .../crds/bases/beat.k8s.elastic.co_beats.yaml | 14 ++-- .../all-in-one/cluster_role.template.yaml | 19 +---- config/operator/all-in-one/roles.yaml | 14 ++++ .../namespace/cluster_role.template.yaml | 19 +---- config/operator/namespace/roles.yaml | 14 ++++ .../samples/beat/filebeat_es_kibana_apm.yaml | 23 +++--- docs/reference/api-docs.asciidoc | 6 +- pkg/apis/beat/v1beta1/beat_types.go | 11 ++- pkg/controller/beat/controller.go | 6 +- .../common/association/association.go | 22 ++--- pkg/controller/common/beat/autodiscover.go | 82 +++++++++---------- pkg/controller/common/beat/common.go | 51 ++++++------ pkg/controller/common/beat/common_test.go | 1 + pkg/controller/common/beat/config.go | 79 +++++++----------- pkg/controller/common/beat/config_test.go | 7 +- .../common/beat/filebeat/filebeat.go | 3 +- pkg/controller/common/beat/health/health.go | 4 +- .../common/beat/metricbeat/metricbeat.go | 3 +- .../common/beat/otherbeat/otherbeat.go | 3 +- pkg/controller/common/beat/pod.go | 22 ++++- pkg/controller/common/operator/flags.go | 36 ++++---- pkg/controller/kibana/driver.go | 2 +- test/e2e/beat/config_test.go | 2 +- 26 files changed, 232 insertions(+), 239 deletions(-) create mode 100644 config/operator/all-in-one/roles.yaml create mode 100644 config/operator/namespace/roles.yaml diff --git a/Makefile b/Makefile index d6b5422c8b..0b0411e443 100644 --- a/Makefile +++ b/Makefile @@ -152,9 +152,13 @@ shellcheck: install-crds: generate-crds kubectl apply -f $(ALL_CRDS) +# Install roles that operator expects to be present by default. +install-roles: + kubectl apply -f ./config/operator/all-in-one/roles.yaml + # Run locally against the configured Kubernetes cluster, with port-forwarding enabled so that # the operator can reach services running in the cluster through k8s port-forward feature -run: install-crds go-run +run: install-crds install-roles go-run go-run: # Run the operator locally with debug logs and operator image set to latest @@ -405,7 +409,7 @@ e2e-local: --log-verbosity=$(LOG_VERBOSITY) \ --ignore-webhook-failures \ --test-timeout=$(TEST_TIMEOUT) - @E2E_JSON=$(E2E_JSON) test/e2e/run.sh -run "$(TESTS_MATCH)" -args -testContextPath $(LOCAL_E2E_CTX) + #@E2E_JSON=$(E2E_JSON) test/e2e/run.sh -run "$(TESTS_MATCH)" -args -testContextPath $(LOCAL_E2E_CTX) ########################################## ## -- Continuous integration -- ## diff --git a/cmd/manager/main.go b/cmd/manager/main.go index eee354f2b2..7260ad2633 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -169,9 +169,9 @@ func init() { fmt.Sprintf("K8s secret mounted into the path designated by %s to be used for webhook certificates", operator.WebhookCertDirFlag), ) Cmd.Flags().Bool( - operator.EnableAutodiscoverRBACSetupFlag, + operator.ManageBeatAutodiscoverRBACFlag, true, - "Determines whether the operator should set up role, binding and service account for the Beats autodiscover feature", + "Determines whether the operator should set up binding and service account for the Beats autodiscover feature", ) // enable using dashed notation in flags and underscores in env @@ -339,7 +339,7 @@ func execute() { accessReviewer = rbac.NewPermissiveAccessReviewer() } - if viper.GetBool(operator.EnableAutodiscoverRBACSetupFlag) { + if viper.GetBool(operator.ManageBeatAutodiscoverRBACFlag) { commonbeat.EnableAutodiscoverRBACSetup() } diff --git a/config/crds/all-crds.yaml b/config/crds/all-crds.yaml index 4d3aa1cdb8..7bec4de067 100644 --- a/config/crds/all-crds.yaml +++ b/config/crds/all-crds.yaml @@ -506,15 +506,15 @@ spec: daemonSet: description: 'DaemonSet allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the DaemonSet At most - one of DaemonSet and Deployment can be used.' + for the Type is used 2. Provide a spec for the DaemonSet Cannot + be used along with `deployment`.' properties: {} type: object deployment: description: 'Deployment allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the Deployment At most - one of DaemonSet and Deployment can be used.' + for the Type is used 2. Provide a spec for the Deployment Cannot + be used along with `daemonSet`.' properties: replicas: format: int32 @@ -544,9 +544,9 @@ spec: be used if ECK is enforcing RBAC on references. type: string type: - description: Type is the type of the Beat to deploy. Any string can - be used, but well-known types will be recognized and will allow to - provide sane default configurations. + description: Type is the type of the Beat to deploy (filebeat, metricbeat, + etc.). Any string can be used, but well-known types will be recognized + and will allow to provide sane default configurations. maxLength: 20 type: string version: diff --git a/config/crds/bases/beat.k8s.elastic.co_beats.yaml b/config/crds/bases/beat.k8s.elastic.co_beats.yaml index 3ce1137839..af5fab7222 100644 --- a/config/crds/bases/beat.k8s.elastic.co_beats.yaml +++ b/config/crds/bases/beat.k8s.elastic.co_beats.yaml @@ -70,8 +70,8 @@ spec: daemonSet: description: 'DaemonSet allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the DaemonSet At most - one of DaemonSet and Deployment can be used.' + for the Type is used 2. Provide a spec for the DaemonSet Cannot + be used along with `deployment`.' properties: podTemplate: description: PodTemplateSpec describes the data a pod should have @@ -6107,8 +6107,8 @@ spec: deployment: description: 'Deployment allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the Deployment At most - one of DaemonSet and Deployment can be used.' + for the Type is used 2. Provide a spec for the Deployment Cannot + be used along with `daemonSet`.' properties: podTemplate: description: PodTemplateSpec describes the data a pod should have @@ -12168,9 +12168,9 @@ spec: be used if ECK is enforcing RBAC on references. type: string type: - description: Type is the type of the Beat to deploy. Any string can - be used, but well-known types will be recognized and will allow to - provide sane default configurations. + description: Type is the type of the Beat to deploy (filebeat, metricbeat, + etc.). Any string can be used, but well-known types will be recognized + and will allow to provide sane default configurations. maxLength: 20 type: string version: diff --git a/config/operator/all-in-one/cluster_role.template.yaml b/config/operator/all-in-one/cluster_role.template.yaml index 2ceb809031..3c77b1e7dd 100644 --- a/config/operator/all-in-one/cluster_role.template.yaml +++ b/config/operator/all-in-one/cluster_role.template.yaml @@ -12,6 +12,8 @@ rules: - subjectaccessreviews verbs: - create +# required to allow the operator to bind service accounts it manages +# to role that holds permissions needed for Beat autodiscover feature - apiGroups: - "rbac.authorization.k8s.io" resources: @@ -56,7 +58,7 @@ rules: resources: - deployments - statefulsets - - daemonsets + - daemonsets # used by Beats verbs: - get - list @@ -161,18 +163,3 @@ rules: - update - patch - delete ---- -# permissions needed for Beat cluster-wide autodiscover feature -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: elastic-operator-beat-autodiscover -rules: -- apiGroups: [""] # "" indicates the core API group - resources: - - namespaces - - pods - verbs: - - get - - watch - - list diff --git a/config/operator/all-in-one/roles.yaml b/config/operator/all-in-one/roles.yaml new file mode 100644 index 0000000000..16ba6d43f5 --- /dev/null +++ b/config/operator/all-in-one/roles.yaml @@ -0,0 +1,14 @@ +# permissions needed for Beat cluster-wide autodiscover feature +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-operator-beat-autodiscover +rules: +- apiGroups: [""] # "" indicates the core API group + resources: + - namespaces + - pods + verbs: + - get + - watch + - list diff --git a/config/operator/namespace/cluster_role.template.yaml b/config/operator/namespace/cluster_role.template.yaml index 94cd43f60c..e98a834a9e 100644 --- a/config/operator/namespace/cluster_role.template.yaml +++ b/config/operator/namespace/cluster_role.template.yaml @@ -42,6 +42,8 @@ rules: - update - patch - delete +# required to allow the operator to bind service accounts it manages +# to role that holds permissions needed for Beat autodiscover feature - apiGroups: - "rbac.authorization.k8s.io" resources: @@ -67,7 +69,7 @@ rules: resources: - deployments - statefulsets - - daemonsets + - daemonsets # used by Beats verbs: - get - list @@ -164,18 +166,3 @@ rules: - update - patch - delete ---- -# permissions needed for Beat cluster-wide autodiscover feature -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: elastic-operator-beat-autodiscover -rules: -- apiGroups: [""] # "" indicates the core API group - resources: - - namespaces - - pods - verbs: - - get - - watch - - list diff --git a/config/operator/namespace/roles.yaml b/config/operator/namespace/roles.yaml new file mode 100644 index 0000000000..16ba6d43f5 --- /dev/null +++ b/config/operator/namespace/roles.yaml @@ -0,0 +1,14 @@ +# permissions needed for Beat cluster-wide autodiscover feature +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: elastic-operator-beat-autodiscover +rules: +- apiGroups: [""] # "" indicates the core API group + resources: + - namespaces + - pods + verbs: + - get + - watch + - list diff --git a/config/samples/beat/filebeat_es_kibana_apm.yaml b/config/samples/beat/filebeat_es_kibana_apm.yaml index 4f4eeb393f..f07e32a7ec 100644 --- a/config/samples/beat/filebeat_es_kibana_apm.yaml +++ b/config/samples/beat/filebeat_es_kibana_apm.yaml @@ -54,15 +54,14 @@ spec: elasticsearchRef: name: elasticsearch-sample # to target only a specific namespace for logging, use: - #config: - # filebeat.autodiscover.providers: - # - node: ${NODE_NAME} - # type: kubernetes - # templates: - # - condition.equals.kubernetes.namespace: log-this - # config: - # - paths: ["/var/log/containers/*${data.kubernetes.container.id}.log"] - # type: container - # processors: - # - add_cloud_metadata: null - # - add_host_metadata: null + config: + filebeat.autodiscover.providers: + - node: ${NODE_NAME} + type: kubernetes + templates: + - config: + - paths: ["/var/log/containers/*${data.kubernetes.container.id}.log"] + type: container + processors: + - add_cloud_metadata: null + - add_host_metadata: null diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index c9f7968e00..3902eaf5b2 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -172,14 +172,14 @@ BeatSpec defines the desired state of a Beat. [cols="25a,75a", options="header"] |=== | Field | Description -| *`type`* __string__ | Type is the type of the Beat to deploy. Any string can be used, but well-known types will be recognized and will allow to provide sane default configurations. +| *`type`* __string__ | Type is the type of the Beat to deploy (filebeat, metricbeat, etc.). Any string can be used, but well-known types will be recognized and will allow to provide sane default configurations. | *`version`* __string__ | Version of the Beat. | *`elasticsearchRef`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-objectselector[$$ObjectSelector$$]__ | ElasticsearchRef is a reference to an Elasticsearch cluster running in the same Kubernetes cluster. | *`image`* __string__ | Image is the Beat Docker image to deploy. Version has to match the Beat in the image. | *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the Beat configuration. If provided, it will override the default configuration. | *`serviceAccountName`* __string__ | ServiceAccountName is used to check access from the current resource to Elasticsearch resource in a different namespace. Can only be used if ECK is enforcing RBAC on references. -| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the DaemonSet At most one of DaemonSet and Deployment can be used. -| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the Deployment At most one of DaemonSet and Deployment can be used. +| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the DaemonSet Cannot be used along with `deployment`. +| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the Deployment Cannot be used along with `daemonSet`. |=== diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index 3c6044c6ae..019e3eb8ad 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -14,8 +14,8 @@ import ( // BeatSpec defines the desired state of a Beat. type BeatSpec struct { - // Type is the type of the Beat to deploy. Any string can be used, but well-known types will be recognized and - // will allow to provide sane default configurations. + // Type is the type of the Beat to deploy (filebeat, metricbeat, etc.). Any string can be used, + // but well-known types will be recognized and will allow to provide sane default configurations. // +kubebuilder:validation:MaxLength=20 Type string `json:"type"` @@ -39,11 +39,14 @@ type BeatSpec struct { // +kubebuilder:validation:Optional ServiceAccountName string `json:"serviceAccountName,omitempty"` + // DaemonSet specifies the Beat should be deployed as a DaemonSet, and allows overriding its default spec. + // Cannot be used along with `deployment`. + // DaemonSet allows to: // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used // 2. Provide a spec for the DaemonSet - // At most one of DaemonSet and Deployment can be used. + // Cannot be used along with `deployment`. // +kubebuilder:validation:Optional DaemonSet *DaemonSetSpec `json:"daemonSet,omitempty"` @@ -51,7 +54,7 @@ type BeatSpec struct { // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used // 2. Provide a spec for the Deployment - // At most one of DaemonSet and Deployment can be used. + // Cannot be used along with `daemonSet`. // +kubebuilder:validation:Optional Deployment *DeploymentSpec `json:"deployment,omitempty"` } diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 7128e53036..b31a8ea725 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -175,10 +175,10 @@ func (r *ReconcileBeat) doReconcile(ctx context.Context, beat beatv1beta1.Beat) return results.WithError(err) } - driverResults := newDriver(ctx, r.Client, beat).Reconcile() - results.WithResults(driverResults.Results) + driverStatus, results := newDriver(ctx, r.Client, beat).Reconcile() + results.WithResults(results) - err := r.updateStatus(driverResults.Status, beat) + err := r.updateStatus(driverStatus, beat) results.WithError(err) if err != nil && apierrors.IsConflict(err) { log.V(1).Info("Conflict while updating status", "namespace", beat.Namespace, "beat_name", beat.Name) diff --git a/pkg/controller/common/association/association.go b/pkg/controller/common/association/association.go index 75220b1e04..59d538959f 100644 --- a/pkg/controller/common/association/association.go +++ b/pkg/controller/common/association/association.go @@ -15,31 +15,31 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) -// WriteAssocSecretToHash dereferences auth secret (if any) to include it in the checksum. -func WriteAssocSecretToHash(client k8s.Client, assoc commonv1.Associated, hash hash.Hash) error { +// WriteAssocSecretToConfigHash dereferences auth secret (if any) to include it in the configHash. +func WriteAssocSecretToConfigHash(client k8s.Client, assoc commonv1.Associated, configHash hash.Hash) error { assocConf := assoc.AssociationConf() if assocConf.AuthIsConfigured() { - esAuthKey := types.NamespacedName{ + authSecretNsName := types.NamespacedName{ Name: assocConf.GetAuthSecretName(), Namespace: assoc.GetNamespace()} - var esAuthSecret corev1.Secret - if err := client.Get(esAuthKey, &esAuthSecret); err != nil { + var authSecret corev1.Secret + if err := client.Get(authSecretNsName, &authSecret); err != nil { return err } - _, _ = hash.Write(esAuthSecret.Data[assocConf.GetAuthSecretKey()]) + _, _ = configHash.Write(authSecret.Data[assocConf.GetAuthSecretKey()]) } if assocConf.CAIsConfigured() { - esPublicCAKey := types.NamespacedName{ + publicCASecretNsName := types.NamespacedName{ Namespace: assoc.GetNamespace(), Name: assocConf.GetCASecretName()} - var esPublicCASecret corev1.Secret - if err := client.Get(esPublicCAKey, &esPublicCASecret); err != nil { + var publicCASecret corev1.Secret + if err := client.Get(publicCASecretNsName, &publicCASecret); err != nil { return err } - if certPem, ok := esPublicCASecret.Data[certificates.CertFileName]; ok { - _, _ = hash.Write(certPem) + if certPem, ok := publicCASecret.Data[certificates.CertFileName]; ok { + _, _ = configHash.Write(certPem) } } diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index d9a589755e..66d6c9d5d6 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -7,21 +7,16 @@ package beat import ( "context" "fmt" - "reflect" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "github.com/go-logr/logr" "go.elastic.co/apm" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" - "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" - "github.com/elastic/cloud-on-k8s/pkg/utils/maps" ) const ( @@ -46,24 +41,23 @@ func ShouldSetupAutodiscoverRBAC() bool { // SetupAutodiscoveryRBAC reconciles all resources needed for the default RBAC setup. func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, owner metav1.Object, labels map[string]string) error { - if ShouldSetupAutodiscoverRBAC() { - if err := setupAutodiscoverRBAC(ctx, client, owner, labels); err != nil { - log.V(1).Info( - "autodiscovery rbac setup failed", - "namespace", owner.GetNamespace(), - "beat_name", owner.GetName()) - return err - } - } - return nil -} - -func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { // this is the source of truth and should be respected at all times if !ShouldSetupAutodiscoverRBAC() { return nil } + err := setupAutodiscoverRBAC(ctx, client, owner, labels) + if err != nil { + log.V(1).Info( + "autodiscovery rbac setup failed", + "namespace", owner.GetNamespace(), + "beat_name", owner.GetName()) + } + + return err +} + +func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { span, _ := apm.StartSpan(ctx, "reconcile_autodiscover_rbac", tracing.SpanTypeApp) defer span.End() @@ -79,19 +73,33 @@ func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1. } func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[string]string) error { - sa := &corev1.ServiceAccount{ + expected := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: ServiceAccountName(owner.GetName()), Namespace: owner.GetNamespace(), Labels: labels, }, } + expected.Labels = hash.SetTemplateHashLabel(nil, expected) - return doReconcile(client, sa, owner) + reconciled := &corev1.ServiceAccount{} + return reconciler.ReconcileResource(reconciler.Params{ + Client: client, + Owner: owner, + Expected: expected, + Reconciled: reconciled, + NeedsUpdate: func() bool { + // compare hash of the deployment at the time it was built + return hash.GetTemplateHashLabel(expected.Labels) != hash.GetTemplateHashLabel(reconciled.Labels) + }, + UpdateReconciled: func() { + expected.DeepCopyInto(reconciled) + }, + }) } func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { - binding := &rbacv1.ClusterRoleBinding{ + expected := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: ClusterRoleBindingName(owner.GetNamespace(), owner.GetName()), }, @@ -109,33 +117,17 @@ func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { }, } - return doReconcile(client, binding, nil) -} - -func doReconcile(client k8s.Client, obj runtime.Object, owner metav1.Object) error { - // labels set here must be exactly the same for all callers in a particular namespace - // otherwise they'll just keep trying to override each other - objMeta, err := meta.Accessor(obj) - if err != nil { - return err - } - - objMeta.SetLabels(maps.Merge(objMeta.GetLabels(), hash.SetTemplateHashLabel(nil, obj))) - - reconciled := obj.DeepCopyObject() + reconciled := &rbacv1.ClusterRoleBinding{} return reconciler.ReconcileResource(reconciler.Params{ Client: client, - Owner: owner, - Expected: obj, + Expected: expected, Reconciled: reconciled, NeedsUpdate: func() bool { - objMeta2, err := meta.Accessor(reconciled) - // compare hash of the deployment at the time it was built - return err != nil || hash.GetTemplateHashLabel(objMeta.GetLabels()) != hash.GetTemplateHashLabel(objMeta2.GetLabels()) + return hash.GetTemplateHashLabel(expected.Labels) != hash.GetTemplateHashLabel(reconciled.Labels) }, UpdateReconciled: func() { - reflect.ValueOf(reconciled).Elem().Set(reflect.ValueOf(obj).Elem()) + expected.DeepCopyInto(reconciled) }, }) } diff --git a/pkg/controller/common/beat/common.go b/pkg/controller/common/beat/common.go index 622f15eebc..00521d8811 100644 --- a/pkg/controller/common/beat/common.go +++ b/pkg/controller/common/beat/common.go @@ -9,6 +9,7 @@ import ( "crypto/sha256" "fmt" + commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -27,7 +28,7 @@ import ( type Type string type Driver interface { - Reconcile() DriverResults + Reconcile() (*DriverStatus, *reconciler.Results) } type DaemonSetSpec struct { @@ -88,18 +89,6 @@ func (dp *DriverParams) Validate() error { return nil } -type DriverResults struct { - *reconciler.Results - Status *DriverStatus -} - -func NewDriverResults(ctx context.Context) DriverResults { - return DriverResults{ - Status: nil, - Results: reconciler.NewResult(ctx), - } -} - type DriverStatus struct { ExpectedNodes int32 AvailableNodes int32 @@ -107,34 +96,40 @@ type DriverStatus struct { Association commonv1.AssociationStatus } -func Reconcile(params DriverParams, defaultConfig *settings.CanonicalConfig, defaultImage container.Image, modifyPodFunc func(builder *defaults.PodTemplateBuilder)) DriverResults { - results := NewDriverResults(params.Context) +func Reconcile( + params DriverParams, + defaultConfig *settings.CanonicalConfig, + defaultImage container.Image, + modifyPodFunc func(builder *defaults.PodTemplateBuilder)) (*DriverStatus, *reconciler.Results) { + results := reconciler.NewResult(params.Context) if err := params.Validate(); err != nil { - results.WithError(err) - return results + return nil, results.WithError(err) } if err := SetupAutodiscoverRBAC(params.Context, params.Logger, params.Client, params.Owner, params.Labels); err != nil { results.WithError(err) } - checksum := sha256.New224() - if err := reconcileConfig(params, defaultConfig, checksum); err != nil { - results.WithError(err) - return results + configHash := sha256.New224() + if err := reconcileConfig(params, defaultConfig, configHash); err != nil { + return nil, results.WithError(err) } - podTemplate := buildPodTemplate(params, defaultImage, modifyPodFunc, checksum) - if driverStatus, err := reconcilePodVehicle(podTemplate, params); err != nil { + // we need to deref the secret here (if any) to include it in the configHash otherwise Beat will not be rolled on content changes + if err := commonassociation.WriteAssocSecretToConfigHash(params.Client, params.Associated, configHash); err != nil { + return nil, results.WithError(err) + } + + podTemplate := buildPodTemplate(params, defaultImage, modifyPodFunc, configHash) + driverStatus, err := reconcilePodVehicle(podTemplate, params) + if err != nil { if apierrors.IsConflict(err) { params.Logger.V(1).Info("Conflict while updating") - results.WithResult(reconcile.Result{Requeue: true}) + return nil, results.WithResult(reconcile.Result{Requeue: true}) } - results.WithError(err) - } else { - results.Status = &driverStatus + return nil, results.WithError(err) } - return results + return &driverStatus, results } diff --git a/pkg/controller/common/beat/common_test.go b/pkg/controller/common/beat/common_test.go index 7c29d06041..ef5300a210 100644 --- a/pkg/controller/common/beat/common_test.go +++ b/pkg/controller/common/beat/common_test.go @@ -18,4 +18,5 @@ func Test_DriverParamsValidate(t *testing.T) { require.Error(t, params.Validate()) require.Error(t, (&DriverParams{}).Validate()) + require.NoError(t, (&DriverParams{DaemonSet: &DaemonSetSpec{}}).Validate()) } diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/common/beat/config.go index 44f4b81f17..d73afdf0c2 100644 --- a/pkg/controller/common/beat/config.go +++ b/pkg/controller/common/beat/config.go @@ -5,69 +5,59 @@ package beat import ( - "fmt" "hash" "path" + "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common" - commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) -var ( - defaultResources = corev1.ResourceRequirements{ - Limits: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - Requests: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse("200Mi"), - corev1.ResourceCPU: resource.MustParse("100m"), - }, - } -) - // setOutput will set the output section in Beat config according to the association configuration. func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { - if associated.AssociationConf().IsConfigured() { - username, password, err := association.ElasticsearchAuthSettings(client, associated) - if err != nil { - return err - } + if !associated.AssociationConf().IsConfigured() { + return nil + } + + username, password, err := association.ElasticsearchAuthSettings(client, associated) + if err != nil { + return err + } + esOutput := settings.MustCanonicalConfig( + map[string]interface{}{ + "output.elasticsearch": map[string]interface{}{ + "hosts": []string{associated.AssociationConf().GetURL()}, + "username": username, + "password": password, + }, + }) + + if err := cfg.MergeWith(esOutput); err != nil { + return err + } + + if associated.AssociationConf().GetCACertProvided() { if err := cfg.MergeWith(settings.MustCanonicalConfig( map[string]interface{}{ - "output.elasticsearch": map[string]interface{}{ - "hosts": []string{associated.AssociationConf().GetURL()}, - "username": username, - "password": password, - }, + "output.elasticsearch.ssl.certificate_authorities": path.Join(CAMountPath, CAFileName), })); err != nil { return err } - - if associated.AssociationConf().GetCACertProvided() { - if err := cfg.MergeWith(settings.MustCanonicalConfig( - map[string]interface{}{ - "output.elasticsearch.ssl.certificate_authorities": path.Join(CAMountPath, CAFileName), - })); err != nil { - return err - } - } } return nil } func buildBeatConfig( + log logr.Logger, client k8s.Client, associated commonv1.Associated, defaultConfig *settings.CanonicalConfig, @@ -75,10 +65,6 @@ func buildBeatConfig( ) ([]byte, error) { cfg := settings.NewCanonicalConfig() - if defaultConfig == nil && userConfig == nil { - return nil, fmt.Errorf("both default and user configs are nil") - } - if err := setOutput(cfg, client, associated); err != nil { return nil, err } @@ -97,6 +83,7 @@ func buildBeatConfig( if err = cfg.MergeWith(userCfg); err != nil { return nil, err } + log.V(1).Info("Replacing ECK-managed configuration by user-provided configuration") } return cfg.Render() @@ -105,15 +92,13 @@ func buildBeatConfig( func reconcileConfig( params DriverParams, defaultConfig *settings.CanonicalConfig, - checksum hash.Hash, + configHash hash.Hash, ) error { - - cfgBytes, err := buildBeatConfig(params.Client, params.Associated, defaultConfig, params.Config) + cfgBytes, err := buildBeatConfig(params.Logger, params.Client, params.Associated, defaultConfig, params.Config) if err != nil { return err } - // create resource expected := corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: params.Owner.GetNamespace(), @@ -125,17 +110,11 @@ func reconcileConfig( }, } - // reconcile if _, err = reconciler.ReconcileSecret(params.Client, expected, params.Owner); err != nil { return err } - // we need to deref the secret here (if any) to include it in the checksum otherwise Beat will not be rolled on content changes - if err := commonassociation.WriteAssocSecretToHash(params.Client, params.Associated, checksum); err != nil { - return err - } - - _, _ = checksum.Write(cfgBytes) + _, _ = configHash.Write(cfgBytes) return nil } diff --git a/pkg/controller/common/beat/config_test.go b/pkg/controller/common/beat/config_test.go index 7e13ced55a..0ae4544007 100644 --- a/pkg/controller/common/beat/config_test.go +++ b/pkg/controller/common/beat/config_test.go @@ -7,6 +7,7 @@ package beat import ( "testing" + logrtesting "github.com/go-logr/logr/testing" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -78,8 +79,8 @@ func Test_buildBeatConfig(t *testing.T) { wantErr bool }{ { - name: "neither default nor user config", - wantErr: true, + name: "neither default nor user config", + associated: &beatv1beta1.Beat{}, }, { name: "no association, only default config", @@ -146,7 +147,7 @@ func Test_buildBeatConfig(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - gotYaml, gotErr := buildBeatConfig(tt.client, tt.associated, tt.defaultConfig, tt.userConfig) + gotYaml, gotErr := buildBeatConfig(logrtesting.NullLogger{}, tt.client, tt.associated, tt.defaultConfig, tt.userConfig) diff := tt.want.Diff(settings.MustParseConfig(gotYaml), nil) diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/common/beat/filebeat/filebeat.go index 494a719b7d..ec981fd5b1 100644 --- a/pkg/controller/common/beat/filebeat/filebeat.go +++ b/pkg/controller/common/beat/filebeat/filebeat.go @@ -8,6 +8,7 @@ import ( commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" ) @@ -41,7 +42,7 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { return &Driver{DriverParams: params} } -func (d *Driver) Reconcile() commonbeat.DriverResults { +func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { f := func(builder *defaults.PodTemplateBuilder) { containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) containersLogsVolume := volume.NewReadOnlyHostVolume(HostContainersLogsVolumeName, HostContainersLogsPath, HostContainersLogsMountPath) diff --git a/pkg/controller/common/beat/health/health.go b/pkg/controller/common/beat/health/health.go index 9d4e4aaf02..518b9feaca 100644 --- a/pkg/controller/common/beat/health/health.go +++ b/pkg/controller/common/beat/health/health.go @@ -16,12 +16,12 @@ const ( // BeatYellowHealth means that: // 1) at least one Pod is Ready, and - // 2) association is not configured or configured and established + // 2) association is not configured, or configured and established BeatYellowHealth BeatHealth = "yellow" // BeatGreenHealth means that: // 1) all Pods are Ready, and - // 2) association is not configured or configured and established + // 2) association is not configured, or configured and established BeatGreenHealth BeatHealth = "green" ) diff --git a/pkg/controller/common/beat/metricbeat/metricbeat.go b/pkg/controller/common/beat/metricbeat/metricbeat.go index ee2dbb0144..44575661f0 100644 --- a/pkg/controller/common/beat/metricbeat/metricbeat.go +++ b/pkg/controller/common/beat/metricbeat/metricbeat.go @@ -8,6 +8,7 @@ import ( commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" corev1 "k8s.io/api/core/v1" ) @@ -42,7 +43,7 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { return &Driver{DriverParams: params} } -func (d *Driver) Reconcile() commonbeat.DriverResults { +func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { f := func(builder *defaults.PodTemplateBuilder) { dockerSockVolume := volume.NewHostVolume(DockerSockVolumeName, DockerSockPath, DockerSockMountPath, false, corev1.HostPathUnset) procVolume := volume.NewReadOnlyHostVolume(ProcVolumeName, ProcPath, ProcMountPath) diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/common/beat/otherbeat/otherbeat.go index 96f0824b6a..3d0b4b791f 100644 --- a/pkg/controller/common/beat/otherbeat/otherbeat.go +++ b/pkg/controller/common/beat/otherbeat/otherbeat.go @@ -6,6 +6,7 @@ package otherbeat import ( commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) @@ -24,6 +25,6 @@ func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { return &Driver{DriverParams: params} } -func (d *Driver) Reconcile() commonbeat.DriverResults { +func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { return commonbeat.Reconcile(d.DriverParams, nil, "", nil) } diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/common/beat/pod.go index f9b5627436..8269a7f91d 100644 --- a/pkg/controller/common/beat/pod.go +++ b/pkg/controller/common/beat/pod.go @@ -8,13 +8,14 @@ import ( "fmt" "hash" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) const ( @@ -30,14 +31,27 @@ const ( DataMountPathTemplate = "/var/lib/%s/%s/%s-data" DataPathTemplate = "/usr/share/%s/data" - // ConfigChecksumLabel is a label used to store a Beats config checksum. + // ConfigChecksumLabel is a label used to store a Beat config checksum. ConfigChecksumLabel = "beat.k8s.elastic.co/config-checksum" // VersionLabelName is a label used to track the version of a Beat Pod. VersionLabelName = "beat.k8s.elastic.co/version" ) -func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyPodFunc func(builder *defaults.PodTemplateBuilder), checksum hash.Hash) corev1.PodTemplateSpec { +var ( + defaultResources = corev1.ResourceRequirements{ + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("200Mi"), + corev1.ResourceCPU: resource.MustParse("100m"), + }, + } +) + +func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyPodFunc func(builder *defaults.PodTemplateBuilder), configHash hash.Hash) corev1.PodTemplateSpec { podTemplate := params.GetPodTemplate() // Token mounting gets defaulted to false, which prevents from detecting whether user had set it. @@ -67,7 +81,7 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP WithResources(defaultResources). WithHostNetwork(). WithLabels(map[string]string{ - ConfigChecksumLabel: fmt.Sprintf("%x", checksum.Sum(nil)), + ConfigChecksumLabel: fmt.Sprintf("%x", configHash.Sum(nil)), VersionLabelName: params.Version}). WithDockerImage(params.Image, container.ImageRepository(defaultImage, params.Version)). WithArgs("-e", "-c", ConfigMountPath). diff --git a/pkg/controller/common/operator/flags.go b/pkg/controller/common/operator/flags.go index 8a9b4ae311..dd0f9ecdbc 100644 --- a/pkg/controller/common/operator/flags.go +++ b/pkg/controller/common/operator/flags.go @@ -5,22 +5,22 @@ package operator const ( - AutoPortForwardFlag = "auto-port-forward" - CACertRotateBeforeFlag = "ca-cert-rotate-before" - CACertValidityFlag = "ca-cert-validity" - CertRotateBeforeFlag = "cert-rotate-before" - CertValidityFlag = "cert-validity" - ContainerRegistryFlag = "container-registry" - DebugHTTPListenFlag = "debug-http-listen" - EnableAutodiscoverRBACSetupFlag = "enable-autodiscover-rbac-setup" - EnableTracingFlag = "enable-tracing" - EnableWebhookFlag = "enable-webhook" - EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" - ManageWebhookCertsFlag = "manage-webhook-certs" - MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" - MetricsPortFlag = "metrics-port" - NamespacesFlag = "namespaces" - OperatorNamespaceFlag = "operator-namespace" - WebhookCertDirFlag = "webhook-cert-dir" - WebhookSecretFlag = "webhook-secret" + AutoPortForwardFlag = "auto-port-forward" + CACertRotateBeforeFlag = "ca-cert-rotate-before" + CACertValidityFlag = "ca-cert-validity" + CertRotateBeforeFlag = "cert-rotate-before" + CertValidityFlag = "cert-validity" + ContainerRegistryFlag = "container-registry" + DebugHTTPListenFlag = "debug-http-listen" + ManageBeatAutodiscoverRBACFlag = "manage-beat-autodiscover-rbac" + EnableTracingFlag = "enable-tracing" + EnableWebhookFlag = "enable-webhook" + EnforceRBACOnRefsFlag = "enforce-rbac-on-refs" + ManageWebhookCertsFlag = "manage-webhook-certs" + MaxConcurrentReconcilesFlag = "max-concurrent-reconciles" + MetricsPortFlag = "metrics-port" + NamespacesFlag = "namespaces" + OperatorNamespaceFlag = "operator-namespace" + WebhookCertDirFlag = "webhook-cert-dir" + WebhookSecretFlag = "webhook-secret" ) diff --git a/pkg/controller/kibana/driver.go b/pkg/controller/kibana/driver.go index d247aff186..deff49fa75 100644 --- a/pkg/controller/kibana/driver.go +++ b/pkg/controller/kibana/driver.go @@ -201,7 +201,7 @@ func (d *driver) deploymentParams(kb *kbv1.Kibana) (deployment.Params, error) { } // we need to deref the secret here to include it in the checksum otherwise Kibana will not be rolled on contents changes - if err := commonassociation.WriteAssocSecretToHash(d.client, kb, configChecksum); err != nil { + if err := commonassociation.WriteAssocSecretToConfigHash(d.client, kb, configChecksum); err != nil { return deployment.Params{}, err } diff --git a/test/e2e/beat/config_test.go b/test/e2e/beat/config_test.go index 99d709bc5c..5d3fe9b6da 100644 --- a/test/e2e/beat/config_test.go +++ b/test/e2e/beat/config_test.go @@ -44,7 +44,7 @@ func TestMetricbeatDefaultConfig(t *testing.T) { mbBuilder := beat.NewBuilder(name, metricbeat.Type). WithElasticsearchRef(esBuilder.Ref()). - WithESValidations(beat.HasEventFromPod(testPodBuilder.Pod.Name)) + WithESValidations(beat.HasEventFromBeat(metricbeat.Type)) test.Sequence(nil, test.EmptySteps, esBuilder, mbBuilder, testPodBuilder).RunSequential(t) } From 85c0b60ea3dfb827e992d80713d9e869be3fbdf6 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 1 Jun 2020 21:31:45 +0200 Subject: [PATCH 47/81] Fix autodiscover cluster role name --- pkg/controller/common/beat/autodiscover.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 66d6c9d5d6..92273761fb 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -20,9 +20,9 @@ import ( ) const ( - serviceAccountNameTemplate = "elastic-operator-autodiscover-%s" - clusterRoleBindingNameTemplate = "elastic-operator-autodiscover-%s-%s" - clusterRoleName = "elastic-operator-beat" + serviceAccountNameTemplate = "elastic-operator-beat-autodiscover-%s" + clusterRoleBindingNameTemplate = "elastic-operator-beat-autodiscover-%s-%s" + clusterRoleName = "elastic-operator-beat-autodiscover" ) var ( From a6d52620b8a2b43e4788f271759e7b509966fa00 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 11:46:40 +0200 Subject: [PATCH 48/81] Use empty dict instead of null for values --- config/samples/beat/filebeat_es_kibana_apm.yaml | 4 ++-- pkg/controller/common/beat/filebeat/config.go | 4 ++-- pkg/controller/common/beat/metricbeat/config.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/samples/beat/filebeat_es_kibana_apm.yaml b/config/samples/beat/filebeat_es_kibana_apm.yaml index f07e32a7ec..8fef5ccffa 100644 --- a/config/samples/beat/filebeat_es_kibana_apm.yaml +++ b/config/samples/beat/filebeat_es_kibana_apm.yaml @@ -63,5 +63,5 @@ spec: - paths: ["/var/log/containers/*${data.kubernetes.container.id}.log"] type: container processors: - - add_cloud_metadata: null - - add_host_metadata: null + - add_cloud_metadata: {} + - add_host_metadata: {} diff --git a/pkg/controller/common/beat/filebeat/config.go b/pkg/controller/common/beat/filebeat/config.go index 54f520ed97..a2e136996d 100644 --- a/pkg/controller/common/beat/filebeat/config.go +++ b/pkg/controller/common/beat/filebeat/config.go @@ -22,7 +22,7 @@ var ( paths: - /var/log/containers/*${data.kubernetes.container.id}.log processors: -- add_cloud_metadata: null -- add_host_metadata: null +- add_cloud_metadata: {} +- add_host_metadata: {} `)) ) diff --git a/pkg/controller/common/beat/metricbeat/config.go b/pkg/controller/common/beat/metricbeat/config.go index fc26f6206e..f2787f6d97 100644 --- a/pkg/controller/common/beat/metricbeat/config.go +++ b/pkg/controller/common/beat/metricbeat/config.go @@ -68,6 +68,6 @@ var ( metricsets: - proxy processors: -- add_cloud_metadata: null +- add_cloud_metadata: {} `)) ) From 6644c7ad782fbb0902e5a4cbdab7192a22785285 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 15:56:07 +0200 Subject: [PATCH 49/81] Clean up comments --- config/operator/all-in-one/roles.yaml | 2 +- config/operator/namespace/roles.yaml | 2 +- pkg/controller/common/beat/autodiscover.go | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/config/operator/all-in-one/roles.yaml b/config/operator/all-in-one/roles.yaml index 16ba6d43f5..c657a951d2 100644 --- a/config/operator/all-in-one/roles.yaml +++ b/config/operator/all-in-one/roles.yaml @@ -4,7 +4,7 @@ kind: ClusterRole metadata: name: elastic-operator-beat-autodiscover rules: -- apiGroups: [""] # "" indicates the core API group +- apiGroups: [""] resources: - namespaces - pods diff --git a/config/operator/namespace/roles.yaml b/config/operator/namespace/roles.yaml index 16ba6d43f5..c657a951d2 100644 --- a/config/operator/namespace/roles.yaml +++ b/config/operator/namespace/roles.yaml @@ -4,7 +4,7 @@ kind: ClusterRole metadata: name: elastic-operator-beat-autodiscover rules: -- apiGroups: [""] # "" indicates the core API group +- apiGroups: [""] resources: - namespaces - pods diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 92273761fb..e6ff398664 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -41,7 +41,6 @@ func ShouldSetupAutodiscoverRBAC() bool { // SetupAutodiscoveryRBAC reconciles all resources needed for the default RBAC setup. func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, owner metav1.Object, labels map[string]string) error { - // this is the source of truth and should be respected at all times if !ShouldSetupAutodiscoverRBAC() { return nil } @@ -89,7 +88,7 @@ func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[ Expected: expected, Reconciled: reconciled, NeedsUpdate: func() bool { - // compare hash of the deployment at the time it was built + // compare hash of the service account at the time it was built return hash.GetTemplateHashLabel(expected.Labels) != hash.GetTemplateHashLabel(reconciled.Labels) }, UpdateReconciled: func() { @@ -123,7 +122,7 @@ func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { Expected: expected, Reconciled: reconciled, NeedsUpdate: func() bool { - // compare hash of the deployment at the time it was built + // compare hash of the cluster role binding at the time it was built return hash.GetTemplateHashLabel(expected.Labels) != hash.GetTemplateHashLabel(reconciled.Labels) }, UpdateReconciled: func() { From a45e752382d5cc230b584da8a56f558de0616598 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 15:56:25 +0200 Subject: [PATCH 50/81] Fix Beat sample --- config/samples/beat/filebeat_es_kibana_apm.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/config/samples/beat/filebeat_es_kibana_apm.yaml b/config/samples/beat/filebeat_es_kibana_apm.yaml index 8fef5ccffa..9785ed5ec1 100644 --- a/config/samples/beat/filebeat_es_kibana_apm.yaml +++ b/config/samples/beat/filebeat_es_kibana_apm.yaml @@ -53,15 +53,3 @@ spec: version: 7.7.0 elasticsearchRef: name: elasticsearch-sample - # to target only a specific namespace for logging, use: - config: - filebeat.autodiscover.providers: - - node: ${NODE_NAME} - type: kubernetes - templates: - - config: - - paths: ["/var/log/containers/*${data.kubernetes.container.id}.log"] - type: container - processors: - - add_cloud_metadata: {} - - add_host_metadata: {} From 750d84fc3a4660cae6eb07a9abf43732e7477977 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 16:01:23 +0200 Subject: [PATCH 51/81] Filter events so that controller sees only the ones relevant for Beats --- pkg/controller/beat/controller.go | 10 ++++++- pkg/controller/common/beat/autodiscover.go | 31 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index b31a8ea725..0a532647be 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -106,7 +106,15 @@ func addWatches(c controller.Controller, r *ReconcileBeat) error { return err } - if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRoleBinding{}}, &handler.EnqueueRequestForObject{}); err != nil { + if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRoleBinding{}}, &handler.EnqueueRequestsFromMapFunc{ + ToRequests: handler.ToRequestsFunc(func(object handler.MapObject) []reconcile.Request { + requests := []reconcile.Request{} + if result, nsName := commonbeat.IsAutodiscoverResource(object.Meta); result { + requests = append(requests, reconcile.Request{NamespacedName: nsName}) + } + return requests + }), + }); err != nil { return err } } diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index e6ff398664..f24b053349 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -12,17 +12,22 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/elastic/cloud-on-k8s/pkg/utils/maps" "github.com/go-logr/logr" "go.elastic.co/apm" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" ) const ( serviceAccountNameTemplate = "elastic-operator-beat-autodiscover-%s" clusterRoleBindingNameTemplate = "elastic-operator-beat-autodiscover-%s-%s" clusterRoleName = "elastic-operator-beat-autodiscover" + + autodiscoverBeatNameLabelName = "autodiscover.beat.k8s.elastic.co/name" + autodiscoverBeatNamespaceLabelName = "autodiscover.beat.k8s.elastic.co/namespace" ) var ( @@ -56,6 +61,20 @@ func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Clie return err } +func IsAutodiscoverResource(meta metav1.Object) (bool, types.NamespacedName) { + name, okName := meta.GetLabels()[autodiscoverBeatNameLabelName] + ns, okNs := meta.GetLabels()[autodiscoverBeatNamespaceLabelName] + + if okName && okNs { + return true, types.NamespacedName{ + Name: name, + Namespace: ns, + } + } + + return false, types.NamespacedName{} +} + func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { span, _ := apm.StartSpan(ctx, "reconcile_autodiscover_rbac", tracing.SpanTypeApp) defer span.End() @@ -76,7 +95,7 @@ func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[ ObjectMeta: metav1.ObjectMeta{ Name: ServiceAccountName(owner.GetName()), Namespace: owner.GetNamespace(), - Labels: labels, + Labels: addLabels(labels, owner), }, } expected.Labels = hash.SetTemplateHashLabel(nil, expected) @@ -100,7 +119,8 @@ func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[ func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { expected := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: ClusterRoleBindingName(owner.GetNamespace(), owner.GetName()), + Name: ClusterRoleBindingName(owner.GetNamespace(), owner.GetName()), + Labels: addLabels(nil, owner), }, Subjects: []rbacv1.Subject{ { @@ -131,6 +151,13 @@ func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { }) } +func addLabels(labels map[string]string, owner metav1.Object) map[string]string { + return maps.Merge(labels, map[string]string{ + autodiscoverBeatNameLabelName: owner.GetName(), + autodiscoverBeatNamespaceLabelName: owner.GetNamespace(), + }) +} + func ClusterRoleBindingName(namespace, name string) string { return fmt.Sprintf(clusterRoleBindingNameTemplate, namespace, name) } From 286f0c0b68b3321f0f747f541e2a0cd4d616328d Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 20:24:04 +0200 Subject: [PATCH 52/81] Remove watching dynamic secrets as there are none atm --- pkg/controller/beat/controller.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 0a532647be..af160cd6d2 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -86,14 +86,9 @@ func addWatches(c controller.Controller, r *ReconcileBeat) error { } // Watch secrets - if err := c.Watch(&source.Kind{Type: &corev1.Secret{}}, r.dynamicWatches.Secrets); err != nil { - return err - } - if err := r.dynamicWatches.Secrets.AddHandler(&watches.OwnerWatch{ - EnqueueRequestForOwner: handler.EnqueueRequestForOwner{ - IsController: true, - OwnerType: &beatv1beta1.Beat{}, - }, + if err := c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: &beatv1beta1.Beat{}, }); err != nil { return err } From 61035a97f693b8f4f7b483af5a7e37986492c972 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 20:39:16 +0200 Subject: [PATCH 53/81] Clean up autodiscover ClusterRoleBinding on delete --- pkg/controller/beat/controller.go | 15 +++++++++++++++ pkg/controller/common/beat/autodiscover.go | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index af160cd6d2..986ecb09bb 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -13,6 +13,7 @@ import ( corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -139,6 +140,12 @@ func (r *ReconcileBeat) Reconcile(request reconcile.Request) (reconcile.Result, var beat beatv1beta1.Beat if err := association.FetchWithAssociation(ctx, r.Client, request, &beat); err != nil { if apierrors.IsNotFound(err) { + if err := r.onDelete(types.NamespacedName{ + Namespace: request.Namespace, + Name: request.Name, + }); err != nil { + return reconcile.Result{Requeue: true}, nil + } return reconcile.Result{}, nil } return reconcile.Result{}, tracing.CaptureError(ctx, err) @@ -226,6 +233,14 @@ func (r *ReconcileBeat) isCompatible(ctx context.Context, beat *beatv1beta1.Beat return compat, err } +func (r *ReconcileBeat) onDelete(obj types.NamespacedName) error { + if commonbeat.ShouldSetupAutodiscoverRBAC() { + return commonbeat.CleanUp(r.Client, obj) + } + + return nil +} + func newDriver(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) commonbeat.Driver { dp := newDriverParams(ctx, client, beat) diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index f24b053349..9ffeab235e 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -17,6 +17,7 @@ import ( "go.elastic.co/apm" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -61,6 +62,24 @@ func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Clie return err } +func CleanUp(client k8s.Client, nsName types.NamespacedName) error { + if ShouldSetupAutodiscoverRBAC() { + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ClusterRoleBindingName(nsName.Namespace, nsName.Name), + }, + } + + if err := client.Delete(clusterRoleBinding); err != nil { + if !apierrors.IsNotFound(err) { + return err + } + } + } + + return nil +} + func IsAutodiscoverResource(meta metav1.Object) (bool, types.NamespacedName) { name, okName := meta.GetLabels()[autodiscoverBeatNameLabelName] ns, okNs := meta.GetLabels()[autodiscoverBeatNamespaceLabelName] From 8afb9653e8f8aa60f06952c02039130979f05445 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 21:25:31 +0200 Subject: [PATCH 54/81] Rename default Beat Service Account --- pkg/controller/common/beat/autodiscover.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/common/beat/autodiscover.go index 9ffeab235e..f36c9da304 100644 --- a/pkg/controller/common/beat/autodiscover.go +++ b/pkg/controller/common/beat/autodiscover.go @@ -23,7 +23,7 @@ import ( ) const ( - serviceAccountNameTemplate = "elastic-operator-beat-autodiscover-%s" + serviceAccountNameTemplate = "elastic-operator-beat-%s" clusterRoleBindingNameTemplate = "elastic-operator-beat-autodiscover-%s-%s" clusterRoleName = "elastic-operator-beat-autodiscover" From 8bb0d2134e884dce240f6c07c645d5b7f2554121 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Wed, 3 Jun 2020 21:26:10 +0200 Subject: [PATCH 55/81] Change min supported Beat version to 7.0.0 --- pkg/controller/common/version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/common/version/version.go b/pkg/controller/common/version/version.go index d9a6f751ff..7d2bf143f9 100644 --- a/pkg/controller/common/version/version.go +++ b/pkg/controller/common/version/version.go @@ -18,7 +18,7 @@ var ( SupportedAPMServerVersions = MinMaxVersion{Min: From(6, 2, 0), Max: From(8, 99, 99)} SupportedEnterpriseSearchVersions = MinMaxVersion{Min: From(7, 7, 0), Max: From(8, 99, 99)} SupportedKibanaVersions = MinMaxVersion{Min: From(6, 8, 0), Max: From(8, 99, 99)} - SupportedBeatVersions = MinMaxVersion{Min: From(6, 8, 0), Max: From(8, 99, 99)} + SupportedBeatVersions = MinMaxVersion{Min: From(7, 0, 0), Max: From(8, 99, 99)} ) // MinMaxVersion holds the minimum and maximum supported versions. From c454f80770044eb916adb974843844c5a39ed488 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 4 Jun 2020 08:35:49 +0200 Subject: [PATCH 56/81] move --- pkg/controller/{common => }/beat/autodiscover.go | 0 pkg/controller/{common => }/beat/common.go | 0 pkg/controller/{common => }/beat/common_test.go | 0 pkg/controller/{common => }/beat/config.go | 0 pkg/controller/{common => }/beat/config_test.go | 0 pkg/controller/{common => }/beat/filebeat/config.go | 0 .../{common => }/beat/filebeat/filebeat.go | 0 pkg/controller/{common => }/beat/health/health.go | 0 .../{common => }/beat/health/health_test.go | 0 .../{common => }/beat/metricbeat/config.go | 0 .../{common => }/beat/metricbeat/metricbeat.go | 0 pkg/controller/beat/name.go | 8 ++++++++ .../{common => }/beat/otherbeat/otherbeat.go | 0 pkg/controller/{common => }/beat/pod.go | 0 pkg/controller/{common => }/beat/reconcile.go | 0 pkg/controller/common/beat/name.go | 13 ------------- 16 files changed, 8 insertions(+), 13 deletions(-) rename pkg/controller/{common => }/beat/autodiscover.go (100%) rename pkg/controller/{common => }/beat/common.go (100%) rename pkg/controller/{common => }/beat/common_test.go (100%) rename pkg/controller/{common => }/beat/config.go (100%) rename pkg/controller/{common => }/beat/config_test.go (100%) rename pkg/controller/{common => }/beat/filebeat/config.go (100%) rename pkg/controller/{common => }/beat/filebeat/filebeat.go (100%) rename pkg/controller/{common => }/beat/health/health.go (100%) rename pkg/controller/{common => }/beat/health/health_test.go (100%) rename pkg/controller/{common => }/beat/metricbeat/config.go (100%) rename pkg/controller/{common => }/beat/metricbeat/metricbeat.go (100%) rename pkg/controller/{common => }/beat/otherbeat/otherbeat.go (100%) rename pkg/controller/{common => }/beat/pod.go (100%) rename pkg/controller/{common => }/beat/reconcile.go (100%) delete mode 100644 pkg/controller/common/beat/name.go diff --git a/pkg/controller/common/beat/autodiscover.go b/pkg/controller/beat/autodiscover.go similarity index 100% rename from pkg/controller/common/beat/autodiscover.go rename to pkg/controller/beat/autodiscover.go diff --git a/pkg/controller/common/beat/common.go b/pkg/controller/beat/common.go similarity index 100% rename from pkg/controller/common/beat/common.go rename to pkg/controller/beat/common.go diff --git a/pkg/controller/common/beat/common_test.go b/pkg/controller/beat/common_test.go similarity index 100% rename from pkg/controller/common/beat/common_test.go rename to pkg/controller/beat/common_test.go diff --git a/pkg/controller/common/beat/config.go b/pkg/controller/beat/config.go similarity index 100% rename from pkg/controller/common/beat/config.go rename to pkg/controller/beat/config.go diff --git a/pkg/controller/common/beat/config_test.go b/pkg/controller/beat/config_test.go similarity index 100% rename from pkg/controller/common/beat/config_test.go rename to pkg/controller/beat/config_test.go diff --git a/pkg/controller/common/beat/filebeat/config.go b/pkg/controller/beat/filebeat/config.go similarity index 100% rename from pkg/controller/common/beat/filebeat/config.go rename to pkg/controller/beat/filebeat/config.go diff --git a/pkg/controller/common/beat/filebeat/filebeat.go b/pkg/controller/beat/filebeat/filebeat.go similarity index 100% rename from pkg/controller/common/beat/filebeat/filebeat.go rename to pkg/controller/beat/filebeat/filebeat.go diff --git a/pkg/controller/common/beat/health/health.go b/pkg/controller/beat/health/health.go similarity index 100% rename from pkg/controller/common/beat/health/health.go rename to pkg/controller/beat/health/health.go diff --git a/pkg/controller/common/beat/health/health_test.go b/pkg/controller/beat/health/health_test.go similarity index 100% rename from pkg/controller/common/beat/health/health_test.go rename to pkg/controller/beat/health/health_test.go diff --git a/pkg/controller/common/beat/metricbeat/config.go b/pkg/controller/beat/metricbeat/config.go similarity index 100% rename from pkg/controller/common/beat/metricbeat/config.go rename to pkg/controller/beat/metricbeat/config.go diff --git a/pkg/controller/common/beat/metricbeat/metricbeat.go b/pkg/controller/beat/metricbeat/metricbeat.go similarity index 100% rename from pkg/controller/common/beat/metricbeat/metricbeat.go rename to pkg/controller/beat/metricbeat/metricbeat.go diff --git a/pkg/controller/beat/name.go b/pkg/controller/beat/name.go index 23b6c914bc..97e8df3167 100644 --- a/pkg/controller/beat/name.go +++ b/pkg/controller/beat/name.go @@ -24,3 +24,11 @@ func (bn *Namer) Name(typeName, name string) string { } var _ commonbeat.Namer = &Namer{} + +type Namer interface { + // ConfigSecretName returns name of the Secret that hold configuration for a Beat. + ConfigSecretName(typeName, name string) string + + // Name returns name of the Beat resource. + Name(typeName, name string) string +} diff --git a/pkg/controller/common/beat/otherbeat/otherbeat.go b/pkg/controller/beat/otherbeat/otherbeat.go similarity index 100% rename from pkg/controller/common/beat/otherbeat/otherbeat.go rename to pkg/controller/beat/otherbeat/otherbeat.go diff --git a/pkg/controller/common/beat/pod.go b/pkg/controller/beat/pod.go similarity index 100% rename from pkg/controller/common/beat/pod.go rename to pkg/controller/beat/pod.go diff --git a/pkg/controller/common/beat/reconcile.go b/pkg/controller/beat/reconcile.go similarity index 100% rename from pkg/controller/common/beat/reconcile.go rename to pkg/controller/beat/reconcile.go diff --git a/pkg/controller/common/beat/name.go b/pkg/controller/common/beat/name.go deleted file mode 100644 index 029e7e87d1..0000000000 --- a/pkg/controller/common/beat/name.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package beat - -type Namer interface { - // ConfigSecretName returns name of the Secret that hold configuration for a Beat. - ConfigSecretName(typeName, name string) string - - // Name returns name of the Beat resource. - Name(typeName, name string) string -} From 4b832ccd4321fcfd30a22a2edba9e07ad827bf70 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 4 Jun 2020 10:17:31 +0200 Subject: [PATCH 57/81] refactor --- cmd/manager/main.go | 4 +- pkg/apis/beat/v1beta1/beat_types.go | 20 ++++- .../beat/{ => common}/autodiscover.go | 54 +++++++------- pkg/controller/beat/{ => common}/common.go | 53 ++++--------- pkg/controller/beat/common/common_test.go | 53 +++++++++++++ pkg/controller/beat/{ => common}/config.go | 19 ++--- .../beat/{ => common}/config_test.go | 18 ++--- .../beat/{health => common}/health.go | 30 ++------ .../beat/{health => common}/health_test.go | 26 +++---- pkg/controller/beat/{ => common}/labels.go | 16 +++- pkg/controller/beat/{ => common}/name.go | 20 +---- pkg/controller/beat/{ => common}/name_test.go | 8 +- pkg/controller/beat/{ => common}/pod.go | 23 +++--- pkg/controller/beat/{ => common}/reconcile.go | 59 +++++++-------- pkg/controller/beat/common_test.go | 22 ------ pkg/controller/beat/controller.go | 72 +++++------------- pkg/controller/beat/controller_test.go | 74 ------------------- pkg/controller/beat/filebeat/config.go | 4 +- pkg/controller/beat/filebeat/filebeat.go | 24 +++--- pkg/controller/beat/metricbeat/metricbeat.go | 22 +++--- pkg/controller/beat/otherbeat/otherbeat.go | 18 +++-- 21 files changed, 266 insertions(+), 373 deletions(-) rename pkg/controller/beat/{ => common}/autodiscover.go (79%) rename pkg/controller/beat/{ => common}/common.go (69%) create mode 100644 pkg/controller/beat/common/common_test.go rename pkg/controller/beat/{ => common}/config.go (87%) rename pkg/controller/beat/{ => common}/config_test.go (92%) rename pkg/controller/beat/{health => common}/health.go (51%) rename pkg/controller/beat/{health => common}/health_test.go (79%) rename pkg/controller/beat/{ => common}/labels.go (51%) rename pkg/controller/beat/{ => common}/name.go (52%) rename pkg/controller/beat/{ => common}/name_test.go (92%) rename pkg/controller/beat/{ => common}/pod.go (83%) rename pkg/controller/beat/{ => common}/reconcile.go (61%) delete mode 100644 pkg/controller/beat/common_test.go diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 7260ad2633..819dbbcd0b 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -25,7 +25,7 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/controller/association" associationctl "github.com/elastic/cloud-on-k8s/pkg/controller/association/controller" "github.com/elastic/cloud-on-k8s/pkg/controller/beat" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common/certificates" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/operator" @@ -340,7 +340,7 @@ func execute() { } if viper.GetBool(operator.ManageBeatAutodiscoverRBACFlag) { - commonbeat.EnableAutodiscoverRBACSetup() + beatcommon.EnableAutodiscoverRBACSetup() } if err = apmserver.Add(mgr, params); err != nil { diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index 019e3eb8ad..165c21341a 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -9,7 +9,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" ) // BeatSpec defines the desired state of a Beat. @@ -77,12 +76,29 @@ type BeatStatus struct { ExpectedNodes int32 `json:"expectedNodes,omitempty"` // +kubebuilder:validation:Optional - Health health.BeatHealth `json:"health,omitempty"` + Health BeatHealth `json:"health,omitempty"` // +kubebuilder:validation:Optional Association commonv1.AssociationStatus `json:"associationStatus,omitempty"` } +type BeatHealth string + +const ( + // BeatRedHealth means that the health is neither yellow nor green. + BeatRedHealth BeatHealth = "red" + + // BeatYellowHealth means that: + // 1) at least one Pod is Ready, and + // 2) association is not configured, or configured and established + BeatYellowHealth BeatHealth = "yellow" + + // BeatGreenHealth means that: + // 1) all Pods are Ready, and + // 2) association is not configured, or configured and established + BeatGreenHealth BeatHealth = "green" +) + // +kubebuilder:object:root=true // Beat is the Schema for the Beats API diff --git a/pkg/controller/beat/autodiscover.go b/pkg/controller/beat/common/autodiscover.go similarity index 79% rename from pkg/controller/beat/autodiscover.go rename to pkg/controller/beat/common/autodiscover.go index f36c9da304..afd6a4a00e 100644 --- a/pkg/controller/beat/autodiscover.go +++ b/pkg/controller/beat/common/autodiscover.go @@ -2,17 +2,12 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import ( "context" "fmt" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" - "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" - "github.com/elastic/cloud-on-k8s/pkg/utils/maps" "github.com/go-logr/logr" "go.elastic.co/apm" corev1 "k8s.io/api/core/v1" @@ -20,6 +15,13 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/tracing" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" + "github.com/elastic/cloud-on-k8s/pkg/utils/maps" ) const ( @@ -46,17 +48,17 @@ func ShouldSetupAutodiscoverRBAC() bool { } // SetupAutodiscoveryRBAC reconciles all resources needed for the default RBAC setup. -func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, owner metav1.Object, labels map[string]string) error { +func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, beat beatv1beta1.Beat) error { if !ShouldSetupAutodiscoverRBAC() { return nil } - err := setupAutodiscoverRBAC(ctx, client, owner, labels) + err := setupAutodiscoverRBAC(ctx, client, beat) if err != nil { log.V(1).Info( "autodiscovery rbac setup failed", - "namespace", owner.GetNamespace(), - "beat_name", owner.GetName()) + "namespace", beat.Namespace, + "beat_name", beat.Name) } return err @@ -94,27 +96,27 @@ func IsAutodiscoverResource(meta metav1.Object) (bool, types.NamespacedName) { return false, types.NamespacedName{} } -func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, owner metav1.Object, labels map[string]string) error { +func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) error { span, _ := apm.StartSpan(ctx, "reconcile_autodiscover_rbac", tracing.SpanTypeApp) defer span.End() - if err := reconcileServiceAccount(client, owner, labels); err != nil { + if err := reconcileServiceAccount(client, beat); err != nil { return err } - if err := reconcileClusterRoleBinding(client, owner); err != nil { + if err := reconcileClusterRoleBinding(client, beat); err != nil { return err } return nil } -func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[string]string) error { +func reconcileServiceAccount(client k8s.Client, beat beatv1beta1.Beat) error { expected := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: ServiceAccountName(owner.GetName()), - Namespace: owner.GetNamespace(), - Labels: addLabels(labels, owner), + Name: ServiceAccountName(beat.Name), + Namespace: beat.Namespace, + Labels: addLabels(NewLabels(beat), beat), }, } expected.Labels = hash.SetTemplateHashLabel(nil, expected) @@ -122,7 +124,7 @@ func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[ reconciled := &corev1.ServiceAccount{} return reconciler.ReconcileResource(reconciler.Params{ Client: client, - Owner: owner, + Owner: &beat, Expected: expected, Reconciled: reconciled, NeedsUpdate: func() bool { @@ -135,17 +137,17 @@ func reconcileServiceAccount(client k8s.Client, owner metav1.Object, labels map[ }) } -func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { +func reconcileClusterRoleBinding(client k8s.Client, beat beatv1beta1.Beat) error { expected := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: ClusterRoleBindingName(owner.GetNamespace(), owner.GetName()), - Labels: addLabels(nil, owner), + Name: ClusterRoleBindingName(beat.Namespace, beat.Name), + Labels: addLabels(nil, beat), }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", - Name: ServiceAccountName(owner.GetName()), - Namespace: owner.GetNamespace(), + Name: ServiceAccountName(beat.Name), + Namespace: beat.Namespace, }, }, RoleRef: rbacv1.RoleRef{ @@ -170,10 +172,10 @@ func reconcileClusterRoleBinding(client k8s.Client, owner metav1.Object) error { }) } -func addLabels(labels map[string]string, owner metav1.Object) map[string]string { +func addLabels(labels map[string]string, beat beatv1beta1.Beat) map[string]string { return maps.Merge(labels, map[string]string{ - autodiscoverBeatNameLabelName: owner.GetName(), - autodiscoverBeatNamespaceLabelName: owner.GetNamespace(), + autodiscoverBeatNameLabelName: beat.Name, + autodiscoverBeatNamespaceLabelName: beat.Namespace, }) } diff --git a/pkg/controller/beat/common.go b/pkg/controller/beat/common/common.go similarity index 69% rename from pkg/controller/beat/common.go rename to pkg/controller/beat/common/common.go index 00521d8811..6b51929beb 100644 --- a/pkg/controller/beat/common.go +++ b/pkg/controller/beat/common/common.go @@ -2,22 +2,21 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import ( "context" "crypto/sha256" "fmt" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/reconcile" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" @@ -31,59 +30,37 @@ type Driver interface { Reconcile() (*DriverStatus, *reconciler.Results) } -type DaemonSetSpec struct { - PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"` -} - -type DeploymentSpec struct { - PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"` - Replicas *int32 `json:"replicas,omitempty"` -} - type DriverParams struct { Client k8s.Client Context context.Context Logger logr.Logger - Owner metav1.Object - Associated commonv1.Associated - Namer Namer - - Type string - Version string - ElasticsearchRef commonv1.ObjectSelector - Image string - Config *commonv1.Config - ServiceAccountName string - - Labels map[string]string - - DaemonSet *DaemonSetSpec - Deployment *DeploymentSpec - Selectors map[string]string + Beat beatv1beta1.Beat } func (dp *DriverParams) GetReplicas() *int32 { - if dp.Deployment == nil { + if dp.Beat.Spec.Deployment == nil { return nil } - return dp.Deployment.Replicas + return dp.Beat.Spec.Deployment.Replicas } func (dp *DriverParams) GetPodTemplate() corev1.PodTemplateSpec { + spec := dp.Beat.Spec switch { - case dp.DaemonSet != nil: - return dp.DaemonSet.PodTemplate - case dp.Deployment != nil: - return dp.Deployment.PodTemplate + case spec.DaemonSet != nil: + return spec.DaemonSet.PodTemplate + case spec.Deployment != nil: + return spec.Deployment.PodTemplate } return corev1.PodTemplateSpec{} } func (dp *DriverParams) Validate() error { - if (dp.DaemonSet == nil && dp.Deployment == nil) || (dp.DaemonSet != nil && dp.Deployment != nil) { + spec := dp.Beat.Spec + if (spec.DaemonSet == nil && spec.Deployment == nil) || (spec.DaemonSet != nil && spec.Deployment != nil) { return fmt.Errorf("either daemonset or deployment has to be specified") } return nil @@ -92,7 +69,7 @@ func (dp *DriverParams) Validate() error { type DriverStatus struct { ExpectedNodes int32 AvailableNodes int32 - Health health.BeatHealth + Health beatv1beta1.BeatHealth Association commonv1.AssociationStatus } @@ -107,7 +84,7 @@ func Reconcile( return nil, results.WithError(err) } - if err := SetupAutodiscoverRBAC(params.Context, params.Logger, params.Client, params.Owner, params.Labels); err != nil { + if err := SetupAutodiscoverRBAC(params.Context, params.Logger, params.Client, params.Beat); err != nil { results.WithError(err) } @@ -117,7 +94,7 @@ func Reconcile( } // we need to deref the secret here (if any) to include it in the configHash otherwise Beat will not be rolled on content changes - if err := commonassociation.WriteAssocSecretToConfigHash(params.Client, params.Associated, configHash); err != nil { + if err := commonassociation.WriteAssocSecretToConfigHash(params.Client, ¶ms.Beat, configHash); err != nil { return nil, results.WithError(err) } diff --git a/pkg/controller/beat/common/common_test.go b/pkg/controller/beat/common/common_test.go new file mode 100644 index 0000000000..54f92bdf28 --- /dev/null +++ b/pkg/controller/beat/common/common_test.go @@ -0,0 +1,53 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package common + +import ( + "testing" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + "github.com/stretchr/testify/require" +) + +func Test_DriverParamsValidate(t *testing.T) { + params := func(ds *beatv1beta1.DaemonSetSpec, d *beatv1beta1.DeploymentSpec) DriverParams { + return DriverParams{ + Beat: beatv1beta1.Beat{ + Spec: beatv1beta1.BeatSpec{ + DaemonSet: ds, + Deployment: d, + }, + }, + } + } + + for _, tt := range []struct { + name string + driverParams DriverParams + wantErr bool + }{ + { + driverParams: params(nil, nil), + wantErr: true, + }, + { + driverParams: params(&beatv1beta1.DaemonSetSpec{}, &beatv1beta1.DeploymentSpec{}), + wantErr: true, + }, + { + driverParams: params(&beatv1beta1.DaemonSetSpec{}, nil), + wantErr: false, + }, + { + driverParams: params(nil, &beatv1beta1.DeploymentSpec{}), + wantErr: false, + }, + } { + t.Run(tt.name, func(t *testing.T) { + gotErr := tt.driverParams.Validate() != nil + require.True(t, tt.wantErr == gotErr) + }) + } +} diff --git a/pkg/controller/beat/config.go b/pkg/controller/beat/common/config.go similarity index 87% rename from pkg/controller/beat/config.go rename to pkg/controller/beat/common/config.go index d73afdf0c2..d7ac7338d0 100644 --- a/pkg/controller/beat/config.go +++ b/pkg/controller/beat/common/config.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import ( "hash" @@ -12,6 +12,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common" @@ -59,17 +60,17 @@ func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated comm func buildBeatConfig( log logr.Logger, client k8s.Client, - associated commonv1.Associated, + beat beatv1beta1.Beat, defaultConfig *settings.CanonicalConfig, - userConfig *commonv1.Config, ) ([]byte, error) { cfg := settings.NewCanonicalConfig() - if err := setOutput(cfg, client, associated); err != nil { + if err := setOutput(cfg, client, &beat); err != nil { return nil, err } // use only the default config or only the provided config - no overriding, no merging + userConfig := beat.Spec.Config if userConfig == nil { if err := cfg.MergeWith(defaultConfig); err != nil { return nil, err @@ -94,23 +95,23 @@ func reconcileConfig( defaultConfig *settings.CanonicalConfig, configHash hash.Hash, ) error { - cfgBytes, err := buildBeatConfig(params.Logger, params.Client, params.Associated, defaultConfig, params.Config) + cfgBytes, err := buildBeatConfig(params.Logger, params.Client, params.Beat, defaultConfig) if err != nil { return err } expected := corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Namespace: params.Owner.GetNamespace(), - Name: params.Namer.ConfigSecretName(params.Type, params.Owner.GetName()), - Labels: common.AddCredentialsLabel(params.Labels), + Namespace: params.Beat.Namespace, + Name: ConfigSecretName(params.Beat.Spec.Type, params.Beat.Name), + Labels: common.AddCredentialsLabel(NewLabels(params.Beat)), }, Data: map[string][]byte{ ConfigFileName: cfgBytes, }, } - if _, err = reconciler.ReconcileSecret(params.Client, expected, params.Owner); err != nil { + if _, err = reconciler.ReconcileSecret(params.Client, expected, ¶ms.Beat); err != nil { return err } diff --git a/pkg/controller/beat/config_test.go b/pkg/controller/beat/common/config_test.go similarity index 92% rename from pkg/controller/beat/config_test.go rename to pkg/controller/beat/common/config_test.go index 0ae4544007..1a5529c854 100644 --- a/pkg/controller/beat/config_test.go +++ b/pkg/controller/beat/common/config_test.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import ( "testing" @@ -48,7 +48,7 @@ func Test_buildBeatConfig(t *testing.T) { username: elastic `)) - withAssociation := &beatv1beta1.Beat{ + withAssociation := beatv1beta1.Beat{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns", }, @@ -60,7 +60,7 @@ func Test_buildBeatConfig(t *testing.T) { CASecretName: "secret2", URL: "url", }) - withAssociationWithCA := withAssociation.DeepCopy() + withAssociationWithCA := *withAssociation.DeepCopy() withAssociationWithCA.AssociationConf().CACertProvided = true merge := func(cs ...*settings.CanonicalConfig) *settings.CanonicalConfig { @@ -72,7 +72,7 @@ func Test_buildBeatConfig(t *testing.T) { for _, tt := range []struct { name string client k8s.Client - associated commonv1.Associated + associated beatv1beta1.Beat defaultConfig *settings.CanonicalConfig userConfig *commonv1.Config want *settings.CanonicalConfig @@ -80,23 +80,23 @@ func Test_buildBeatConfig(t *testing.T) { }{ { name: "neither default nor user config", - associated: &beatv1beta1.Beat{}, + associated: beatv1beta1.Beat{}, }, { name: "no association, only default config", - associated: &beatv1beta1.Beat{}, + associated: beatv1beta1.Beat{}, defaultConfig: defaultConfig, want: defaultConfig, }, { name: "no association, only user config", - associated: &beatv1beta1.Beat{}, + associated: beatv1beta1.Beat{}, userConfig: userConfig, want: userCanonicalConfig, }, { name: "no association, default and user config", - associated: &beatv1beta1.Beat{}, + associated: beatv1beta1.Beat{}, defaultConfig: defaultConfig, userConfig: userConfig, want: userCanonicalConfig, @@ -147,7 +147,7 @@ func Test_buildBeatConfig(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - gotYaml, gotErr := buildBeatConfig(logrtesting.NullLogger{}, tt.client, tt.associated, tt.defaultConfig, tt.userConfig) + gotYaml, gotErr := buildBeatConfig(logrtesting.NullLogger{}, tt.client, tt.associated, tt.defaultConfig) diff := tt.want.Diff(settings.MustParseConfig(gotYaml), nil) diff --git a/pkg/controller/beat/health/health.go b/pkg/controller/beat/common/health.go similarity index 51% rename from pkg/controller/beat/health/health.go rename to pkg/controller/beat/common/health.go index 518b9feaca..bfa177c3a0 100644 --- a/pkg/controller/beat/health/health.go +++ b/pkg/controller/beat/common/health.go @@ -2,41 +2,25 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package health +package common import ( + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" v1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" ) -type BeatHealth string - -const ( - // BeatRedHealth means that the health is neither yellow nor green. - BeatRedHealth BeatHealth = "red" - - // BeatYellowHealth means that: - // 1) at least one Pod is Ready, and - // 2) association is not configured, or configured and established - BeatYellowHealth BeatHealth = "yellow" - - // BeatGreenHealth means that: - // 1) all Pods are Ready, and - // 2) association is not configured, or configured and established - BeatGreenHealth BeatHealth = "green" -) - // CalculateHealth returns health of the Beat based on association status, desired count and ready count. -func CalculateHealth(associated v1.Associated, ready, desired int32) BeatHealth { +func CalculateHealth(associated v1.Associated, ready, desired int32) beatv1beta1.BeatHealth { if associated.AssociationConf().IsConfigured() && associated.AssociationStatus() != v1.AssociationEstablished { - return BeatRedHealth + return beatv1beta1.BeatRedHealth } switch { case ready == desired: - return BeatGreenHealth + return beatv1beta1.BeatGreenHealth case ready > 0: - return BeatYellowHealth + return beatv1beta1.BeatYellowHealth default: - return BeatRedHealth + return beatv1beta1.BeatRedHealth } } diff --git a/pkg/controller/beat/health/health_test.go b/pkg/controller/beat/common/health_test.go similarity index 79% rename from pkg/controller/beat/health/health_test.go rename to pkg/controller/beat/common/health_test.go index 3db2e820b9..d440d811a7 100644 --- a/pkg/controller/beat/health/health_test.go +++ b/pkg/controller/beat/common/health_test.go @@ -2,16 +2,16 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package health_test +package common_test import ( "testing" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/stretchr/testify/require" beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" ) func Test_CalculateHealth(t *testing.T) { @@ -37,72 +37,72 @@ func Test_CalculateHealth(t *testing.T) { name string associated commonv1.Associated ready, desired int32 - want health.BeatHealth + want beatv1beta1.BeatHealth }{ { name: "no association, 0 desired", associated: noAssociation, ready: 0, desired: 0, - want: health.BeatGreenHealth, + want: beatv1beta1.BeatGreenHealth, }, { name: "no association, all ready", associated: noAssociation, ready: 3, desired: 3, - want: health.BeatGreenHealth, + want: beatv1beta1.BeatGreenHealth, }, { name: "no association, some ready", associated: noAssociation, ready: 1, desired: 5, - want: health.BeatYellowHealth, + want: beatv1beta1.BeatYellowHealth, }, { name: "no association, none ready", associated: noAssociation, ready: 0, desired: 4, - want: health.BeatRedHealth, + want: beatv1beta1.BeatRedHealth, }, { name: "association not established, all ready", associated: createAssociated(false), ready: 2, desired: 2, - want: health.BeatRedHealth, + want: beatv1beta1.BeatRedHealth, }, { name: "association established, 0 desired", associated: createAssociated(true), - want: health.BeatGreenHealth, + want: beatv1beta1.BeatGreenHealth, }, { name: "association established, all ready", associated: createAssociated(true), ready: 2, desired: 2, - want: health.BeatGreenHealth, + want: beatv1beta1.BeatGreenHealth, }, { name: "association established, some ready", associated: createAssociated(true), ready: 1, desired: 5, - want: health.BeatYellowHealth, + want: beatv1beta1.BeatYellowHealth, }, { name: "association established, none ready", associated: createAssociated(true), ready: 0, desired: 4, - want: health.BeatRedHealth, + want: beatv1beta1.BeatRedHealth, }, } { t.Run(tt.name, func(t *testing.T) { - got := health.CalculateHealth(tt.associated, tt.ready, tt.desired) + got := beatcommon.CalculateHealth(tt.associated, tt.ready, tt.desired) require.Equal(t, tt.want, got) }) } diff --git a/pkg/controller/beat/labels.go b/pkg/controller/beat/common/labels.go similarity index 51% rename from pkg/controller/beat/labels.go rename to pkg/controller/beat/common/labels.go index 5484cf29eb..8c5334f98d 100644 --- a/pkg/controller/beat/labels.go +++ b/pkg/controller/beat/common/labels.go @@ -2,12 +2,24 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common + +import ( + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common" +) const ( // Type represents the Beat type. - Type = "beat" + TypeLabelName = "beat" // NameLabelName is used to represent a Beat in k8s resources. NameLabelName = "beat.k8s.elastic.co/name" ) + +func NewLabels(beat beatv1beta1.Beat) map[string]string { + return map[string]string{ + common.TypeLabelName: TypeLabelName, + NameLabelName: Name(beat.Spec.Type, beat.Name), + } +} diff --git a/pkg/controller/beat/name.go b/pkg/controller/beat/common/name.go similarity index 52% rename from pkg/controller/beat/name.go rename to pkg/controller/beat/common/name.go index 97e8df3167..b00f678527 100644 --- a/pkg/controller/beat/name.go +++ b/pkg/controller/beat/common/name.go @@ -2,33 +2,19 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import ( - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" common_name "github.com/elastic/cloud-on-k8s/pkg/controller/common/name" ) // namer is a Namer that is configured with the defaults for resources related to a Beat resource. var namer = common_name.NewNamer("beat") -type Namer struct { -} - -func (bn *Namer) ConfigSecretName(typeName, name string) string { +func ConfigSecretName(typeName, name string) string { return namer.Suffix(name, typeName, "config") } -func (bn *Namer) Name(typeName, name string) string { +func Name(typeName, name string) string { return namer.Suffix(name, typeName) } - -var _ commonbeat.Namer = &Namer{} - -type Namer interface { - // ConfigSecretName returns name of the Secret that hold configuration for a Beat. - ConfigSecretName(typeName, name string) string - - // Name returns name of the Beat resource. - Name(typeName, name string) string -} diff --git a/pkg/controller/beat/name_test.go b/pkg/controller/beat/common/name_test.go similarity index 92% rename from pkg/controller/beat/name_test.go rename to pkg/controller/beat/common/name_test.go index 3646c877ad..9504fd6057 100644 --- a/pkg/controller/beat/name_test.go +++ b/pkg/controller/beat/common/name_test.go @@ -2,12 +2,11 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import "testing" func TestNamer_ConfigSecretName(t *testing.T) { - namer := Namer{} for _, tt := range []struct { name string resourceName string @@ -34,7 +33,7 @@ func TestNamer_ConfigSecretName(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - got := namer.ConfigSecretName(tt.typ, tt.resourceName) + got := ConfigSecretName(tt.typ, tt.resourceName) if got != tt.want { t.Errorf("config secret name is %s while %s was expected", got, tt.want) } @@ -43,7 +42,6 @@ func TestNamer_ConfigSecretName(t *testing.T) { } func TestNamer_Name(t *testing.T) { - namer := Namer{} for _, tt := range []struct { name string resourceName string @@ -70,7 +68,7 @@ func TestNamer_Name(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - got := namer.Name(tt.typ, tt.resourceName) + got := Name(tt.typ, tt.resourceName) if got != tt.want { t.Errorf("config secret name is %s while %s was expected", got, tt.want) } diff --git a/pkg/controller/beat/pod.go b/pkg/controller/beat/common/pod.go similarity index 83% rename from pkg/controller/beat/pod.go rename to pkg/controller/beat/common/pod.go index 8269a7f91d..9b6f9066e6 100644 --- a/pkg/controller/beat/pod.go +++ b/pkg/controller/beat/common/pod.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import ( "fmt" @@ -62,7 +62,8 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP podTemplate.Spec.AutomountServiceAccountToken = &t } - builder := defaults.NewPodTemplateBuilder(podTemplate, params.Type) + spec := params.Beat.Spec + builder := defaults.NewPodTemplateBuilder(podTemplate, spec.Type) // might be nil if caller wants to use the default builder without any modifications if modifyPodFunc != nil { @@ -82,8 +83,8 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP WithHostNetwork(). WithLabels(map[string]string{ ConfigChecksumLabel: fmt.Sprintf("%x", configHash.Sum(nil)), - VersionLabelName: params.Version}). - WithDockerImage(params.Image, container.ImageRepository(defaultImage, params.Version)). + VersionLabelName: spec.Version}). + WithDockerImage(spec.Image, container.ImageRepository(defaultImage, spec.Version)). WithArgs("-e", "-c", ConfigMountPath). WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). WithSecurityContext(corev1.SecurityContext{ @@ -91,7 +92,7 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP }) if ShouldSetupAutodiscoverRBAC() { - autodiscoverServiceAccountName := ServiceAccountName(params.Owner.GetName()) + autodiscoverServiceAccountName := ServiceAccountName(params.Beat.Name) // If SA is already provided, the call will be no-op. This is fine as we then assume // that for this resource (despite operator configuration) the user took the responsibility // of configuring RBAC. @@ -101,7 +102,7 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP dataVolume := createDataVolume(params) volumes := []volume.VolumeLike{ volume.NewSecretVolume( - params.Namer.ConfigSecretName(params.Type, params.Owner.GetName()), + ConfigSecretName(spec.Type, params.Beat.Name), ConfigVolumeName, ConfigMountPath, ConfigFileName, @@ -109,9 +110,9 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP dataVolume, } - if params.Associated.AssociationConf().CAIsConfigured() { + if params.Beat.AssociationConf().CAIsConfigured() { volumes = append(volumes, volume.NewSecretVolumeWithMountPath( - params.Associated.AssociationConf().GetCASecretName(), + params.Beat.AssociationConf().GetCASecretName(), CAVolumeName, CAMountPath)) } @@ -120,14 +121,14 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) } - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(params.Labels, builder.PodTemplate)) + builder = builder.WithLabels(commonhash.SetTemplateHashLabel(NewLabels(params.Beat), builder.PodTemplate)) return builder.PodTemplate } func createDataVolume(dp DriverParams) volume.VolumeLike { - dataMountPath := fmt.Sprintf(DataPathTemplate, dp.Type) - hostDataPath := fmt.Sprintf(DataMountPathTemplate, dp.Owner.GetNamespace(), dp.Owner.GetName(), dp.Type) + dataMountPath := fmt.Sprintf(DataPathTemplate, dp.Beat.Spec.Type) + hostDataPath := fmt.Sprintf(DataMountPathTemplate, dp.Beat.Namespace, dp.Beat.Name, dp.Beat.Spec.Type) return volume.NewHostVolume( DataVolumeName, diff --git a/pkg/controller/beat/reconcile.go b/pkg/controller/beat/common/reconcile.go similarity index 61% rename from pkg/controller/beat/reconcile.go rename to pkg/controller/beat/common/reconcile.go index c6a5ae3770..dd935b2fa4 100644 --- a/pkg/controller/beat/reconcile.go +++ b/pkg/controller/beat/common/reconcile.go @@ -2,9 +2,10 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -package beat +package common import ( + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -13,7 +14,6 @@ import ( "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" @@ -23,35 +23,31 @@ import ( func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams) (DriverStatus, error) { var reconciliationFunc func(params ReconciliationParams) (int32, int32, error) - name := params.Namer.Name(params.Type, params.Owner.GetName()) + spec := params.Beat.Spec + name := Name(spec.Type, params.Beat.Name) var toDelete runtime.Object switch { - case params.DaemonSet != nil: + case spec.DaemonSet != nil: reconciliationFunc = reconcileDaemonSet toDelete = &v1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: params.Owner.GetNamespace(), + Namespace: params.Beat.Namespace, }, } - case params.Deployment != nil: + case spec.Deployment != nil: reconciliationFunc = reconcileDeployment toDelete = &v1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: params.Owner.GetNamespace(), + Namespace: params.Beat.Namespace, }, } } ready, desired, err := reconciliationFunc(ReconciliationParams{ - client: params.Client, - name: name, - podTemplate: podTemplate, - owner: params.Owner, - labels: params.Labels, - selectors: params.Selectors, - replicas: params.GetReplicas(), + client: params.Client, + beat: params.Beat, }) if err != nil { return DriverStatus{}, err @@ -65,35 +61,30 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams return DriverStatus{ ExpectedNodes: desired, AvailableNodes: ready, - Health: health.CalculateHealth(params.Associated, ready, desired), - Association: params.Associated.AssociationStatus(), + Health: CalculateHealth(¶ms.Beat, ready, desired), + Association: params.Beat.AssociationStatus(), }, nil } type ReconciliationParams struct { - client k8s.Client - name string - podTemplate corev1.PodTemplateSpec - owner metav1.Object - labels map[string]string - selectors map[string]string - replicas *int32 + client k8s.Client + beat beatv1beta1.Beat } func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { d := deployment.New(deployment.Params{ - Name: rp.name, - Namespace: rp.owner.GetNamespace(), - Selector: rp.selectors, - Labels: rp.labels, - PodTemplateSpec: rp.podTemplate, - Replicas: pointer.Int32OrDefault(rp.replicas, int32(1)), + Name: rp.beat.Name, + Namespace: rp.beat.Namespace, + Selector: NewLabels(rp.beat), + Labels: NewLabels(rp.beat), + PodTemplateSpec: rp.beat.Spec.Deployment.PodTemplate, + Replicas: pointer.Int32OrDefault(rp.beat.Spec.Deployment.Replicas, int32(1)), }) - if err := controllerutil.SetControllerReference(rp.owner, &d, scheme.Scheme); err != nil { + if err := controllerutil.SetControllerReference(&rp.beat, &d, scheme.Scheme); err != nil { return 0, 0, err } - reconciled, err := deployment.Reconcile(rp.client, d, rp.owner) + reconciled, err := deployment.Reconcile(rp.client, d, &rp.beat) if err != nil { return 0, 0, err } @@ -102,13 +93,13 @@ func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { } func reconcileDaemonSet(rp ReconciliationParams) (int32, int32, error) { - ds := daemonset.New(rp.podTemplate, rp.name, rp.labels, rp.owner, rp.selectors) + ds := daemonset.New(rp.beat.Spec.DaemonSet.PodTemplate, rp.beat.Name, NewLabels(rp.beat), &rp.beat, NewLabels(rp.beat)) - if err := controllerutil.SetControllerReference(rp.owner, &ds, scheme.Scheme); err != nil { + if err := controllerutil.SetControllerReference(&rp.beat, &ds, scheme.Scheme); err != nil { return 0, 0, err } - reconciled, err := daemonset.Reconcile(rp.client, ds, rp.owner) + reconciled, err := daemonset.Reconcile(rp.client, ds, &rp.beat) if err != nil { return 0, 0, err } diff --git a/pkg/controller/beat/common_test.go b/pkg/controller/beat/common_test.go deleted file mode 100644 index ef5300a210..0000000000 --- a/pkg/controller/beat/common_test.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package beat - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func Test_DriverParamsValidate(t *testing.T) { - params := DriverParams{ - DaemonSet: &DaemonSetSpec{}, - Deployment: &DeploymentSpec{}, - } - - require.Error(t, params.Validate()) - require.Error(t, (&DriverParams{}).Validate()) - require.NoError(t, (&DriverParams{DaemonSet: &DaemonSetSpec{}}).Validate()) -} diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 986ecb09bb..f0fc9cd5d5 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -7,7 +7,8 @@ package beat import ( "context" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/metricbeat" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat/filebeat" "go.elastic.co/apm" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -24,11 +25,10 @@ import ( beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat/metricbeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat/otherbeat" "github.com/elastic/cloud-on-k8s/pkg/controller/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common/annotation" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/filebeat" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/otherbeat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/events" "github.com/elastic/cloud-on-k8s/pkg/controller/common/operator" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" @@ -94,7 +94,7 @@ func addWatches(c controller.Controller, r *ReconcileBeat) error { return err } - if commonbeat.ShouldSetupAutodiscoverRBAC() { + if beatcommon.ShouldSetupAutodiscoverRBAC() { if err := c.Watch(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &beatv1beta1.Beat{}, @@ -105,7 +105,7 @@ func addWatches(c controller.Controller, r *ReconcileBeat) error { if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRoleBinding{}}, &handler.EnqueueRequestsFromMapFunc{ ToRequests: handler.ToRequestsFunc(func(object handler.MapObject) []reconcile.Request { requests := []reconcile.Request{} - if result, nsName := commonbeat.IsAutodiscoverResource(object.Meta); result { + if result, nsName := beatcommon.IsAutodiscoverResource(object.Meta); result { requests = append(requests, reconcile.Request{NamespacedName: nsName}) } return requests @@ -211,7 +211,7 @@ func (r *ReconcileBeat) validate(ctx context.Context, beat *beatv1beta1.Beat) er return nil } -func (r *ReconcileBeat) updateStatus(driverStatus *commonbeat.DriverStatus, beat beatv1beta1.Beat) error { +func (r *ReconcileBeat) updateStatus(driverStatus *beatcommon.DriverStatus, beat beatv1beta1.Beat) error { if driverStatus == nil { return nil } @@ -225,7 +225,7 @@ func (r *ReconcileBeat) updateStatus(driverStatus *commonbeat.DriverStatus, beat } func (r *ReconcileBeat) isCompatible(ctx context.Context, beat *beatv1beta1.Beat) (bool, error) { - selector := map[string]string{NameLabelName: beat.Name} + selector := map[string]string{beatcommon.NameLabelName: beat.Name} compat, err := annotation.ReconcileCompatibility(ctx, r.Client, beat, selector, r.OperatorInfo.BuildInfo.Version) if err != nil { k8s.EmitErrorEvent(r.recorder, err, beat, events.EventCompatCheckError, "Error during compatibility check: %v", err) @@ -234,17 +234,22 @@ func (r *ReconcileBeat) isCompatible(ctx context.Context, beat *beatv1beta1.Beat } func (r *ReconcileBeat) onDelete(obj types.NamespacedName) error { - if commonbeat.ShouldSetupAutodiscoverRBAC() { - return commonbeat.CleanUp(r.Client, obj) + if beatcommon.ShouldSetupAutodiscoverRBAC() { + return beatcommon.CleanUp(r.Client, obj) } return nil } -func newDriver(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) commonbeat.Driver { - dp := newDriverParams(ctx, client, beat) +func newDriver(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) beatcommon.Driver { + dp := beatcommon.DriverParams{ + Client: client, + Context: ctx, + Logger: log, + Beat: beat, + } - switch dp.Type { + switch beat.Spec.Type { case string(filebeat.Type): return filebeat.NewDriver(dp) case string(metricbeat.Type): @@ -253,44 +258,3 @@ func newDriver(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) co return otherbeat.NewDriver(dp) } } - -func newDriverParams(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) commonbeat.DriverParams { - spec := beat.Spec - - var ds *commonbeat.DaemonSetSpec - if spec.DaemonSet != nil { - ds = &commonbeat.DaemonSetSpec{PodTemplate: spec.DaemonSet.PodTemplate} - } - var d *commonbeat.DeploymentSpec - if spec.Deployment != nil { - d = &commonbeat.DeploymentSpec{Replicas: spec.Deployment.Replicas, PodTemplate: spec.Deployment.PodTemplate} - } - - return commonbeat.DriverParams{ - Client: client, - Context: ctx, - Logger: log, - - Namer: &Namer{}, - Owner: &beat, - Associated: &beat, - - Type: spec.Type, - Version: spec.Version, - ElasticsearchRef: spec.ElasticsearchRef, - Image: spec.Image, - Config: spec.Config, - ServiceAccountName: spec.ServiceAccountName, - - DaemonSet: ds, - Deployment: d, - Labels: map[string]string{ - common.TypeLabelName: Type, - NameLabelName: beat.Name, - }, - Selectors: map[string]string{ - common.TypeLabelName: Type, - NameLabelName: beat.Name, - }, - } -} diff --git a/pkg/controller/beat/controller_test.go b/pkg/controller/beat/controller_test.go index e32015ff14..c3979e2bc4 100644 --- a/pkg/controller/beat/controller_test.go +++ b/pkg/controller/beat/controller_test.go @@ -3,77 +3,3 @@ // you may not use this file except in compliance with the Elastic License. package beat - -import ( - "context" - "testing" - - "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" -) - -func Test_newDriverParams(t *testing.T) { - for _, tt := range []struct { - name string - deployment *v1beta1.DeploymentSpec - wantDeployment *beat.DeploymentSpec - daemonSet *v1beta1.DaemonSetSpec - wantDaemonSet *beat.DaemonSetSpec - }{ - { - name: "without deployment/daemonset", - }, - { - name: "with empty deployment", - deployment: &v1beta1.DeploymentSpec{}, - wantDeployment: &beat.DeploymentSpec{}, - }, - { - name: "with replicas in deployment", - deployment: &v1beta1.DeploymentSpec{Replicas: pointer.Int32(2)}, - wantDeployment: &beat.DeploymentSpec{Replicas: pointer.Int32(2)}, - }, - { - name: "with deployment partial podspec", - deployment: &v1beta1.DeploymentSpec{PodTemplate: v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - ServiceAccountName: "sa-test", - }, - }}, - wantDeployment: &beat.DeploymentSpec{PodTemplate: v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - ServiceAccountName: "sa-test", - }, - }}, - }, - { - name: "with daemonset partial podspec", - daemonSet: &v1beta1.DaemonSetSpec{PodTemplate: v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - ServiceAccountName: "sa-test", - }, - }}, - wantDaemonSet: &beat.DaemonSetSpec{PodTemplate: v1.PodTemplateSpec{ - Spec: v1.PodSpec{ - ServiceAccountName: "sa-test", - }, - }}, - }, - } { - t.Run(tt.name, func(t *testing.T) { - beat := v1beta1.Beat{ - Spec: v1beta1.BeatSpec{ - DaemonSet: tt.daemonSet, - Deployment: tt.deployment, - }, - } - got := newDriverParams(context.Background(), nil, beat) - - require.Equal(t, tt.wantDeployment, got.Deployment) - require.Equal(t, tt.wantDaemonSet, got.DaemonSet) - }) - } -} diff --git a/pkg/controller/beat/filebeat/config.go b/pkg/controller/beat/filebeat/config.go index a2e136996d..a62429032a 100644 --- a/pkg/controller/beat/filebeat/config.go +++ b/pkg/controller/beat/filebeat/config.go @@ -4,9 +4,7 @@ package filebeat -import ( - "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" -) +import "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" var ( defaultConfig = settings.MustParseConfig([]byte( diff --git a/pkg/controller/beat/filebeat/filebeat.go b/pkg/controller/beat/filebeat/filebeat.go index ec981fd5b1..51aaf58347 100644 --- a/pkg/controller/beat/filebeat/filebeat.go +++ b/pkg/controller/beat/filebeat/filebeat.go @@ -5,7 +5,8 @@ package filebeat import ( - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" @@ -13,7 +14,7 @@ import ( ) const ( - Type commonbeat.Type = "filebeat" + Type beatcommon.Type = "filebeat" HostContainersVolumeName = "varlibdockercontainers" HostContainersPath = "/var/lib/docker/containers" @@ -28,21 +29,22 @@ const ( HostPodsLogsMountPath = "/var/log/pods" ) -type Driver struct { - commonbeat.DriverParams - commonbeat.Driver +type FilebeatDriver struct { + beatcommon.DriverParams + beatcommon.Driver } -func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { +func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { + spec := params.Beat.Spec // use the default for filebeat type if not provided - if params.DaemonSet == nil && params.Deployment == nil { - params.DaemonSet = &commonbeat.DaemonSetSpec{} + if spec.DaemonSet == nil && spec.Deployment == nil { + spec.DaemonSet = &beatv1beta1.DaemonSetSpec{} } - return &Driver{DriverParams: params} + return &FilebeatDriver{DriverParams: params} } -func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { +func (d *FilebeatDriver) Reconcile() (*beatcommon.DriverStatus, *reconciler.Results) { f := func(builder *defaults.PodTemplateBuilder) { containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) containersLogsVolume := volume.NewReadOnlyHostVolume(HostContainersLogsVolumeName, HostContainersLogsPath, HostContainersLogsMountPath) @@ -57,7 +59,7 @@ func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { } } - return commonbeat.Reconcile( + return beatcommon.Reconcile( d.DriverParams, defaultConfig, container.FilebeatImage, diff --git a/pkg/controller/beat/metricbeat/metricbeat.go b/pkg/controller/beat/metricbeat/metricbeat.go index 44575661f0..025dd16439 100644 --- a/pkg/controller/beat/metricbeat/metricbeat.go +++ b/pkg/controller/beat/metricbeat/metricbeat.go @@ -5,7 +5,8 @@ package metricbeat import ( - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" @@ -14,7 +15,7 @@ import ( ) const ( - Type commonbeat.Type = "metricbeat" + Type beatcommon.Type = "metricbeat" DockerSockVolumeName = "dockersock" DockerSockPath = "/var/run/docker.sock" @@ -30,20 +31,21 @@ const ( ) type Driver struct { - commonbeat.DriverParams - commonbeat.Driver + beatcommon.DriverParams + beatcommon.Driver } -func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { +func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { + spec := params.Beat.Spec // use the default for metricbeat type if not provided - if params.DaemonSet == nil && params.Deployment == nil { - params.DaemonSet = &commonbeat.DaemonSetSpec{} + if spec.DaemonSet == nil && spec.Deployment == nil { + spec.DaemonSet = &beatv1beta1.DaemonSetSpec{} } return &Driver{DriverParams: params} } -func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { +func (d *Driver) Reconcile() (*beatcommon.DriverStatus, *reconciler.Results) { f := func(builder *defaults.PodTemplateBuilder) { dockerSockVolume := volume.NewHostVolume(DockerSockVolumeName, DockerSockPath, DockerSockMountPath, false, corev1.HostPathUnset) procVolume := volume.NewReadOnlyHostVolume(ProcVolumeName, ProcPath, ProcMountPath) @@ -57,10 +59,10 @@ func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { builder.WithVolumes(volume.Volume()).WithVolumeMounts(volume.VolumeMount()) } - builder.WithArgs("-e", "-c", commonbeat.ConfigMountPath, "-system.hostfs=/hostfs") + builder.WithArgs("-e", "-c", beatcommon.ConfigMountPath, "-system.hostfs=/hostfs") } - return commonbeat.Reconcile( + return beatcommon.Reconcile( d.DriverParams, defaultConfig, container.MetricbeatImage, diff --git a/pkg/controller/beat/otherbeat/otherbeat.go b/pkg/controller/beat/otherbeat/otherbeat.go index 3d0b4b791f..9831b9fb7d 100644 --- a/pkg/controller/beat/otherbeat/otherbeat.go +++ b/pkg/controller/beat/otherbeat/otherbeat.go @@ -5,26 +5,28 @@ package otherbeat import ( - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) type Driver struct { - commonbeat.DriverParams - commonbeat.Driver + beatcommon.DriverParams + beatcommon.Driver } -func NewDriver(params commonbeat.DriverParams) commonbeat.Driver { +func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { + spec := params.Beat.Spec // use the default for otherbeat type if not provided - if params.DaemonSet == nil && params.Deployment == nil { - params.Deployment = &commonbeat.DeploymentSpec{ + if spec.DaemonSet == nil && spec.Deployment == nil { + spec.Deployment = &beatv1beta1.DeploymentSpec{ Replicas: pointer.Int32(1), } } return &Driver{DriverParams: params} } -func (d *Driver) Reconcile() (*commonbeat.DriverStatus, *reconciler.Results) { - return commonbeat.Reconcile(d.DriverParams, nil, "", nil) +func (d *Driver) Reconcile() (*beatcommon.DriverStatus, *reconciler.Results) { + return beatcommon.Reconcile(d.DriverParams, nil, "", nil) } From 770579ad4a3db35a006489946b1b2c1772b119a2 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 4 Jun 2020 11:02:29 +0200 Subject: [PATCH 58/81] Post merge fixes --- pkg/apis/beat/v1beta1/beat_types.go | 26 ++++++ .../association/controller/beat_es.go | 22 +++-- pkg/controller/beat/common/config.go | 2 +- pkg/controller/beat/common/config_test.go | 61 ++++++------ pkg/controller/beat/common/health.go | 2 +- pkg/controller/beat/common/health_test.go | 92 +++++++++---------- pkg/controller/beat/common/labels.go | 6 +- pkg/controller/beat/common/name.go | 2 +- pkg/controller/beat/common/name_test.go | 2 +- pkg/controller/beat/common/pod.go | 2 +- pkg/controller/beat/common/reconcile.go | 16 ++-- pkg/controller/beat/controller.go | 6 +- pkg/controller/beat/filebeat/filebeat.go | 2 +- pkg/controller/beat/metricbeat/metricbeat.go | 2 +- pkg/controller/beat/otherbeat/otherbeat.go | 2 +- .../common/association/association.go | 2 +- test/e2e/beat/config_test.go | 4 +- test/e2e/samples_test.go | 6 +- test/e2e/smoke_test.go | 2 +- test/e2e/test/beat/builder.go | 4 +- test/e2e/test/beat/checks.go | 4 +- test/e2e/test/beat/steps.go | 7 +- test/e2e/test/helper/yaml.go | 4 +- test/e2e/test/k8s_client.go | 8 +- 24 files changed, 163 insertions(+), 123 deletions(-) diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index 165c21341a..258235ff8f 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -120,6 +120,29 @@ type Beat struct { assocConf *commonv1.AssociationConf `json:"-"` //nolint:govet } +func (b *Beat) Associated() commonv1.Associated { + if b != nil { + return b + } + return &Beat{} +} + +func (b *Beat) AssociatedType() string { + return commonv1.ElasticsearchAssociationType +} + +func (b *Beat) AssociationRef() commonv1.ObjectSelector { + return b.Spec.ElasticsearchRef.WithDefaultNamespace(b.Namespace) +} + +func (b *Beat) AssociationConfAnnotationName() string { + return commonv1.ElasticsearchConfigAnnotationName +} + +func (b *Beat) GetAssociations() []commonv1.Association { + return []commonv1.Association{b} +} + // IsMarkedForDeletion returns true if the Beat is going to be deleted func (b *Beat) IsMarkedForDeletion() bool { return !b.DeletionTimestamp.IsZero() @@ -149,6 +172,9 @@ func (b *Beat) SetAssociationStatus(status commonv1.AssociationStatus) { b.Status.Association = status } +var _ commonv1.Associated = &Beat{} +var _ commonv1.Association = &Beat{} + // +kubebuilder:object:root=true // BeatList contains a list of Beats diff --git a/pkg/controller/association/controller/beat_es.go b/pkg/controller/association/controller/beat_es.go index 4081b560e2..258da2b28c 100644 --- a/pkg/controller/association/controller/beat_es.go +++ b/pkg/controller/association/controller/beat_es.go @@ -5,6 +5,10 @@ package controller import ( + esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" + eslabel "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/label" + esuser "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/user" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -25,18 +29,24 @@ const ( func AddBeatES(mgr manager.Manager, accessReviewer rbac.AccessReviewer, params operator.Parameters) error { return association.AddAssociationController(mgr, accessReviewer, params, association.AssociationInfo{ - AssociatedObjTemplate: func() commonv1.Associated { return &beatv1beta1.Beat{} }, - AssociationName: "beat-es", - AssociatedShortName: "beat", + AssociationObjTemplate: func() commonv1.Association { return &beatv1beta1.Beat{} }, + ElasticsearchRef: func(c k8s.Client, association commonv1.Association) (bool, commonv1.ObjectSelector, error) { + return true, association.AssociationRef(), nil + }, + ExternalServiceURL: getElasticsearchExternalURL, + AssociatedNamer: esv1.ESNamer, + AssociationName: "beat-es", + AssociatedShortName: "beat", AssociationLabels: func(associated types.NamespacedName) map[string]string { return map[string]string{ BeatESAssociationLabelName: associated.Name, BeatESAssociationLabelNamespace: associated.Namespace, } }, - UserSecretSuffix: "beat-user", - ESUserRole: func(commonv1.Associated) (s string, e error) { - return "superuser", nil + UserSecretSuffix: "beat-user", + CASecretLabelName: eslabel.ClusterNameLabelName, + ESUserRole: func(commonv1.Associated) (string, error) { + return esuser.SuperUserBuiltinRole, nil }, }) } diff --git a/pkg/controller/beat/common/config.go b/pkg/controller/beat/common/config.go index d7ac7338d0..633dcc50e3 100644 --- a/pkg/controller/beat/common/config.go +++ b/pkg/controller/beat/common/config.go @@ -22,7 +22,7 @@ import ( ) // setOutput will set the output section in Beat config according to the association configuration. -func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Associated) error { +func setOutput(cfg *settings.CanonicalConfig, client k8s.Client, associated commonv1.Association) error { if !associated.AssociationConf().IsConfigured() { return nil } diff --git a/pkg/controller/beat/common/config_test.go b/pkg/controller/beat/common/config_test.go index 1a5529c854..36d1b740ac 100644 --- a/pkg/controller/beat/common/config_test.go +++ b/pkg/controller/beat/common/config_test.go @@ -63,6 +63,12 @@ func Test_buildBeatConfig(t *testing.T) { withAssociationWithCA := *withAssociation.DeepCopy() withAssociationWithCA.AssociationConf().CACertProvided = true + withAssociationWithCAAndConfig := *withAssociationWithCA.DeepCopy() + withAssociationWithCAAndConfig.Spec.Config = userConfig + + withAssociationWithConfig := *withAssociation.DeepCopy() + withAssociationWithConfig.Spec.Config = userConfig + merge := func(cs ...*settings.CanonicalConfig) *settings.CanonicalConfig { result := settings.NewCanonicalConfig() _ = result.MergeWith(cs...) @@ -72,82 +78,79 @@ func Test_buildBeatConfig(t *testing.T) { for _, tt := range []struct { name string client k8s.Client - associated beatv1beta1.Beat + beat beatv1beta1.Beat defaultConfig *settings.CanonicalConfig - userConfig *commonv1.Config want *settings.CanonicalConfig wantErr bool }{ { - name: "neither default nor user config", - associated: beatv1beta1.Beat{}, + name: "neither default nor user config", + beat: beatv1beta1.Beat{}, }, { name: "no association, only default config", - associated: beatv1beta1.Beat{}, + beat: beatv1beta1.Beat{}, defaultConfig: defaultConfig, want: defaultConfig, }, { - name: "no association, only user config", - associated: beatv1beta1.Beat{}, - userConfig: userConfig, - want: userCanonicalConfig, + name: "no association, only user config", + beat: beatv1beta1.Beat{Spec: beatv1beta1.BeatSpec{ + Config: userConfig, + }}, + want: userCanonicalConfig, }, { - name: "no association, default and user config", - associated: beatv1beta1.Beat{}, + name: "no association, default and user config", + beat: beatv1beta1.Beat{Spec: beatv1beta1.BeatSpec{ + Config: userConfig, + }}, defaultConfig: defaultConfig, - userConfig: userConfig, want: userCanonicalConfig, }, { name: "association without ca, only default config", client: clientWithSecret, - associated: withAssociation, + beat: withAssociation, defaultConfig: defaultConfig, want: merge(defaultConfig, outputYaml), }, { - name: "association without ca, only user config", - client: clientWithSecret, - associated: withAssociation, - userConfig: userConfig, - want: merge(userCanonicalConfig, outputYaml), + name: "association without ca, only user config", + client: clientWithSecret, + beat: withAssociationWithConfig, + want: merge(userCanonicalConfig, outputYaml), }, { name: "association without ca, default and user config", client: clientWithSecret, - associated: withAssociation, + beat: withAssociationWithConfig, defaultConfig: defaultConfig, - userConfig: userConfig, want: merge(userCanonicalConfig, outputYaml), }, { name: "association with ca, only default config", client: clientWithSecret, - associated: withAssociationWithCA, + beat: withAssociationWithCA, defaultConfig: defaultConfig, want: merge(defaultConfig, outputYaml, outputCAYaml), }, { - name: "association with ca, only user config", - client: clientWithSecret, - associated: withAssociationWithCA, - userConfig: userConfig, - want: merge(userCanonicalConfig, outputYaml, outputCAYaml), + name: "association with ca, only user config", + client: clientWithSecret, + beat: withAssociationWithCAAndConfig, + want: merge(userCanonicalConfig, outputYaml, outputCAYaml), }, { name: "association with ca, default and user config", client: clientWithSecret, - associated: withAssociationWithCA, + beat: withAssociationWithCAAndConfig, defaultConfig: defaultConfig, - userConfig: userConfig, want: merge(userCanonicalConfig, outputYaml, outputCAYaml), }, } { t.Run(tt.name, func(t *testing.T) { - gotYaml, gotErr := buildBeatConfig(logrtesting.NullLogger{}, tt.client, tt.associated, tt.defaultConfig) + gotYaml, gotErr := buildBeatConfig(logrtesting.NullLogger{}, tt.client, tt.beat, tt.defaultConfig) diff := tt.want.Diff(settings.MustParseConfig(gotYaml), nil) diff --git a/pkg/controller/beat/common/health.go b/pkg/controller/beat/common/health.go index bfa177c3a0..675d371e10 100644 --- a/pkg/controller/beat/common/health.go +++ b/pkg/controller/beat/common/health.go @@ -10,7 +10,7 @@ import ( ) // CalculateHealth returns health of the Beat based on association status, desired count and ready count. -func CalculateHealth(associated v1.Associated, ready, desired int32) beatv1beta1.BeatHealth { +func CalculateHealth(associated v1.Association, ready, desired int32) beatv1beta1.BeatHealth { if associated.AssociationConf().IsConfigured() && associated.AssociationStatus() != v1.AssociationEstablished { return beatv1beta1.BeatRedHealth } diff --git a/pkg/controller/beat/common/health_test.go b/pkg/controller/beat/common/health_test.go index d440d811a7..ea3907a61e 100644 --- a/pkg/controller/beat/common/health_test.go +++ b/pkg/controller/beat/common/health_test.go @@ -16,7 +16,7 @@ import ( func Test_CalculateHealth(t *testing.T) { noAssociation := &beatv1beta1.Beat{} - createAssociated := func(associationEstablished bool) commonv1.Associated { + createAssociation := func(associationEstablished bool) commonv1.Association { result := &beatv1beta1.Beat{} result.SetAssociationConf(&commonv1.AssociationConf{ AuthSecretName: "name", @@ -35,74 +35,74 @@ func Test_CalculateHealth(t *testing.T) { for _, tt := range []struct { name string - associated commonv1.Associated + association commonv1.Association ready, desired int32 want beatv1beta1.BeatHealth }{ { - name: "no association, 0 desired", - associated: noAssociation, - ready: 0, - desired: 0, - want: beatv1beta1.BeatGreenHealth, + name: "no association, 0 desired", + association: noAssociation, + ready: 0, + desired: 0, + want: beatv1beta1.BeatGreenHealth, }, { - name: "no association, all ready", - associated: noAssociation, - ready: 3, - desired: 3, - want: beatv1beta1.BeatGreenHealth, + name: "no association, all ready", + association: noAssociation, + ready: 3, + desired: 3, + want: beatv1beta1.BeatGreenHealth, }, { - name: "no association, some ready", - associated: noAssociation, - ready: 1, - desired: 5, - want: beatv1beta1.BeatYellowHealth, + name: "no association, some ready", + association: noAssociation, + ready: 1, + desired: 5, + want: beatv1beta1.BeatYellowHealth, }, { - name: "no association, none ready", - associated: noAssociation, - ready: 0, - desired: 4, - want: beatv1beta1.BeatRedHealth, + name: "no association, none ready", + association: noAssociation, + ready: 0, + desired: 4, + want: beatv1beta1.BeatRedHealth, }, { - name: "association not established, all ready", - associated: createAssociated(false), - ready: 2, - desired: 2, - want: beatv1beta1.BeatRedHealth, + name: "association not established, all ready", + association: createAssociation(false), + ready: 2, + desired: 2, + want: beatv1beta1.BeatRedHealth, }, { - name: "association established, 0 desired", - associated: createAssociated(true), - want: beatv1beta1.BeatGreenHealth, + name: "association established, 0 desired", + association: createAssociation(true), + want: beatv1beta1.BeatGreenHealth, }, { - name: "association established, all ready", - associated: createAssociated(true), - ready: 2, - desired: 2, - want: beatv1beta1.BeatGreenHealth, + name: "association established, all ready", + association: createAssociation(true), + ready: 2, + desired: 2, + want: beatv1beta1.BeatGreenHealth, }, { - name: "association established, some ready", - associated: createAssociated(true), - ready: 1, - desired: 5, - want: beatv1beta1.BeatYellowHealth, + name: "association established, some ready", + association: createAssociation(true), + ready: 1, + desired: 5, + want: beatv1beta1.BeatYellowHealth, }, { - name: "association established, none ready", - associated: createAssociated(true), - ready: 0, - desired: 4, - want: beatv1beta1.BeatRedHealth, + name: "association established, none ready", + association: createAssociation(true), + ready: 0, + desired: 4, + want: beatv1beta1.BeatRedHealth, }, } { t.Run(tt.name, func(t *testing.T) { - got := beatcommon.CalculateHealth(tt.associated, tt.ready, tt.desired) + got := beatcommon.CalculateHealth(tt.association, tt.ready, tt.desired) require.Equal(t, tt.want, got) }) } diff --git a/pkg/controller/beat/common/labels.go b/pkg/controller/beat/common/labels.go index 8c5334f98d..d2bb6c0032 100644 --- a/pkg/controller/beat/common/labels.go +++ b/pkg/controller/beat/common/labels.go @@ -11,7 +11,7 @@ import ( const ( // Type represents the Beat type. - TypeLabelName = "beat" + TypeLabelValue = "beat" // NameLabelName is used to represent a Beat in k8s resources. NameLabelName = "beat.k8s.elastic.co/name" @@ -19,7 +19,7 @@ const ( func NewLabels(beat beatv1beta1.Beat) map[string]string { return map[string]string{ - common.TypeLabelName: TypeLabelName, - NameLabelName: Name(beat.Spec.Type, beat.Name), + common.TypeLabelName: TypeLabelValue, + NameLabelName: Name(beat.Name, beat.Spec.Type), } } diff --git a/pkg/controller/beat/common/name.go b/pkg/controller/beat/common/name.go index b00f678527..efe53414e2 100644 --- a/pkg/controller/beat/common/name.go +++ b/pkg/controller/beat/common/name.go @@ -15,6 +15,6 @@ func ConfigSecretName(typeName, name string) string { return namer.Suffix(name, typeName, "config") } -func Name(typeName, name string) string { +func Name(name, typeName string) string { return namer.Suffix(name, typeName) } diff --git a/pkg/controller/beat/common/name_test.go b/pkg/controller/beat/common/name_test.go index 9504fd6057..748c130c60 100644 --- a/pkg/controller/beat/common/name_test.go +++ b/pkg/controller/beat/common/name_test.go @@ -68,7 +68,7 @@ func TestNamer_Name(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - got := Name(tt.typ, tt.resourceName) + got := Name(tt.resourceName, tt.typ) if got != tt.want { t.Errorf("config secret name is %s while %s was expected", got, tt.want) } diff --git a/pkg/controller/beat/common/pod.go b/pkg/controller/beat/common/pod.go index 9b6f9066e6..0f834008f4 100644 --- a/pkg/controller/beat/common/pod.go +++ b/pkg/controller/beat/common/pod.go @@ -62,7 +62,7 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP podTemplate.Spec.AutomountServiceAccountToken = &t } - spec := params.Beat.Spec + spec := ¶ms.Beat.Spec builder := defaults.NewPodTemplateBuilder(podTemplate, spec.Type) // might be nil if caller wants to use the default builder without any modifications diff --git a/pkg/controller/beat/common/reconcile.go b/pkg/controller/beat/common/reconcile.go index dd935b2fa4..3115bf8b91 100644 --- a/pkg/controller/beat/common/reconcile.go +++ b/pkg/controller/beat/common/reconcile.go @@ -24,7 +24,7 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams var reconciliationFunc func(params ReconciliationParams) (int32, int32, error) spec := params.Beat.Spec - name := Name(spec.Type, params.Beat.Name) + name := Name(params.Beat.Name, spec.Type) var toDelete runtime.Object switch { case spec.DaemonSet != nil: @@ -46,8 +46,9 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams } ready, desired, err := reconciliationFunc(ReconciliationParams{ - client: params.Client, - beat: params.Beat, + client: params.Client, + beat: params.Beat, + podTemplate: podTemplate, }) if err != nil { return DriverStatus{}, err @@ -67,8 +68,9 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams } type ReconciliationParams struct { - client k8s.Client - beat beatv1beta1.Beat + client k8s.Client + beat beatv1beta1.Beat + podTemplate corev1.PodTemplateSpec } func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { @@ -77,7 +79,7 @@ func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { Namespace: rp.beat.Namespace, Selector: NewLabels(rp.beat), Labels: NewLabels(rp.beat), - PodTemplateSpec: rp.beat.Spec.Deployment.PodTemplate, + PodTemplateSpec: rp.podTemplate, Replicas: pointer.Int32OrDefault(rp.beat.Spec.Deployment.Replicas, int32(1)), }) if err := controllerutil.SetControllerReference(&rp.beat, &d, scheme.Scheme); err != nil { @@ -93,7 +95,7 @@ func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { } func reconcileDaemonSet(rp ReconciliationParams) (int32, int32, error) { - ds := daemonset.New(rp.beat.Spec.DaemonSet.PodTemplate, rp.beat.Name, NewLabels(rp.beat), &rp.beat, NewLabels(rp.beat)) + ds := daemonset.New(rp.podTemplate, rp.beat.Name, NewLabels(rp.beat), &rp.beat, NewLabels(rp.beat)) if err := controllerutil.SetControllerReference(&rp.beat, &ds, scheme.Scheme); err != nil { return 0, 0, err diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index f0fc9cd5d5..75d5a74e88 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -51,7 +51,7 @@ func Add(mgr manager.Manager, params operator.Parameters) error { if err != nil { return err } - return addWatches(c, r) + return addWatches(c) } // newReconciler returns a new reconcile.Reconciler. @@ -66,7 +66,7 @@ func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileBe } // addWatches registers the required watches. -func addWatches(c controller.Controller, r *ReconcileBeat) error { +func addWatches(c controller.Controller) error { // Watch for changes to Beat if err := c.Watch(&source.Kind{Type: &beatv1beta1.Beat{}}, &handler.EnqueueRequestForObject{}); err != nil { return err @@ -138,7 +138,7 @@ func (r *ReconcileBeat) Reconcile(request reconcile.Request) (reconcile.Result, defer tracing.EndTransaction(tx) var beat beatv1beta1.Beat - if err := association.FetchWithAssociation(ctx, r.Client, request, &beat); err != nil { + if err := association.FetchWithAssociations(ctx, r.Client, request, &beat); err != nil { if apierrors.IsNotFound(err) { if err := r.onDelete(types.NamespacedName{ Namespace: request.Namespace, diff --git a/pkg/controller/beat/filebeat/filebeat.go b/pkg/controller/beat/filebeat/filebeat.go index 51aaf58347..3c75170433 100644 --- a/pkg/controller/beat/filebeat/filebeat.go +++ b/pkg/controller/beat/filebeat/filebeat.go @@ -35,7 +35,7 @@ type FilebeatDriver struct { } func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { - spec := params.Beat.Spec + spec := ¶ms.Beat.Spec // use the default for filebeat type if not provided if spec.DaemonSet == nil && spec.Deployment == nil { spec.DaemonSet = &beatv1beta1.DaemonSetSpec{} diff --git a/pkg/controller/beat/metricbeat/metricbeat.go b/pkg/controller/beat/metricbeat/metricbeat.go index 025dd16439..7664eeae4e 100644 --- a/pkg/controller/beat/metricbeat/metricbeat.go +++ b/pkg/controller/beat/metricbeat/metricbeat.go @@ -36,7 +36,7 @@ type Driver struct { } func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { - spec := params.Beat.Spec + spec := ¶ms.Beat.Spec // use the default for metricbeat type if not provided if spec.DaemonSet == nil && spec.Deployment == nil { spec.DaemonSet = &beatv1beta1.DaemonSetSpec{} diff --git a/pkg/controller/beat/otherbeat/otherbeat.go b/pkg/controller/beat/otherbeat/otherbeat.go index 9831b9fb7d..b8ee51e85e 100644 --- a/pkg/controller/beat/otherbeat/otherbeat.go +++ b/pkg/controller/beat/otherbeat/otherbeat.go @@ -17,7 +17,7 @@ type Driver struct { } func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { - spec := params.Beat.Spec + spec := ¶ms.Beat.Spec // use the default for otherbeat type if not provided if spec.DaemonSet == nil && spec.Deployment == nil { spec.Deployment = &beatv1beta1.DeploymentSpec{ diff --git a/pkg/controller/common/association/association.go b/pkg/controller/common/association/association.go index 59d538959f..e9606ca9b3 100644 --- a/pkg/controller/common/association/association.go +++ b/pkg/controller/common/association/association.go @@ -16,7 +16,7 @@ import ( ) // WriteAssocSecretToConfigHash dereferences auth secret (if any) to include it in the configHash. -func WriteAssocSecretToConfigHash(client k8s.Client, assoc commonv1.Associated, configHash hash.Hash) error { +func WriteAssocSecretToConfigHash(client k8s.Client, assoc commonv1.Association, configHash hash.Hash) error { assocConf := assoc.AssociationConf() if assocConf.AuthIsConfigured() { diff --git a/test/e2e/beat/config_test.go b/test/e2e/beat/config_test.go index 5d3fe9b6da..93bf036289 100644 --- a/test/e2e/beat/config_test.go +++ b/test/e2e/beat/config_test.go @@ -8,11 +8,11 @@ import ( "fmt" "testing" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/metricbeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat/metricbeat" "github.com/stretchr/testify/require" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/filebeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat/filebeat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/beat" diff --git a/test/e2e/samples_test.go b/test/e2e/samples_test.go index e32f17e91f..8fd500573a 100644 --- a/test/e2e/samples_test.go +++ b/test/e2e/samples_test.go @@ -12,7 +12,7 @@ import ( "testing" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/apmserver" @@ -87,11 +87,11 @@ func createBuilders(t *testing.T, decoder *helper.YAMLDecoder, sampleFile, testN case beat.Builder: return b.WithNamespace(namespace). WithSuffix(suffix). - WithElasticsearchRef(tweakElasticsearchRef(b.Beat.Spec.ElasticsearchRef, suffix)). + WithElasticsearchRef(tweakServiceRef(b.Beat.Spec.ElasticsearchRef, suffix)). WithRestrictedSecurityContext(). WithLabel(run.TestNameLabel, testName). WithPodLabel(run.TestNameLabel, testName). - WithESValidations(beat.HasEventFromBeat(commonbeat.Type(b.Beat.Spec.Type))) + WithESValidations(beat.HasEventFromBeat(beatcommon.Type(b.Beat.Spec.Type))) default: return b } diff --git a/test/e2e/smoke_test.go b/test/e2e/smoke_test.go index 7478dabe5a..23daf7ceab 100644 --- a/test/e2e/smoke_test.go +++ b/test/e2e/smoke_test.go @@ -9,7 +9,7 @@ import ( "os" "testing" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/filebeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat/filebeat" "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/apmserver" diff --git a/test/e2e/test/beat/builder.go b/test/e2e/test/beat/builder.go index 5386275db0..05e5037136 100644 --- a/test/e2e/test/beat/builder.go +++ b/test/e2e/test/beat/builder.go @@ -11,7 +11,7 @@ import ( beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/client" "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" "github.com/elastic/cloud-on-k8s/test/e2e/test" @@ -23,7 +23,7 @@ type Builder struct { Validations []ValidationFunc } -func NewBuilder(name string, typ beat.Type) Builder { +func NewBuilder(name string, typ beatcommon.Type) Builder { meta := metav1.ObjectMeta{ Name: name, Namespace: test.Ctx().ManagedNamespace(0), diff --git a/test/e2e/test/beat/checks.go b/test/e2e/test/beat/checks.go index 5d799a5feb..3e3575e798 100644 --- a/test/e2e/test/beat/checks.go +++ b/test/e2e/test/beat/checks.go @@ -11,11 +11,11 @@ import ( "io/ioutil" "net/http" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/client" ) -func HasEventFromBeat(name commonbeat.Type) ValidationFunc { +func HasEventFromBeat(name beatcommon.Type) ValidationFunc { return HasEvent(fmt.Sprintf("/*beat*/_search?q=agent.type:%s", name)) } diff --git a/test/e2e/test/beat/steps.go b/test/e2e/test/beat/steps.go index bd78847de5..6e86708bba 100644 --- a/test/e2e/test/beat/steps.go +++ b/test/e2e/test/beat/steps.go @@ -17,7 +17,6 @@ import ( beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" - "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat/health" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" "github.com/elastic/cloud-on-k8s/test/e2e/test" @@ -70,7 +69,7 @@ func (b Builder) InitTestSteps(k *test.K8sClient) test.StepList { } } // wait for Beat pods to disappear - if err := k.CheckPodCount(0, test.BeatPodListOptions(b.Beat.Namespace, b.Beat.Name)...); err != nil { + if err := k.CheckPodCount(0, test.BeatPodListOptions(b.Beat.Namespace, b.Beat.Name, b.Beat.Spec.Type)...); err != nil { return err } @@ -129,7 +128,7 @@ func (b Builder) CheckStackTestSteps(k *test.K8sClient) test.StepList { return err } - if beat.Status.Health != health.BeatGreenHealth { + if beat.Status.Health != beatv1beta1.BeatGreenHealth { return fmt.Errorf("beat %s health is %s", beat.Name, beat.Status.Health) } @@ -209,7 +208,7 @@ func (b Builder) DeletionTestSteps(k *test.K8sClient) test.StepList { { Name: "Beat pods should be eventually be removed", Test: test.Eventually(func() error { - return k.CheckPodCount(0, test.BeatPodListOptions(b.Beat.Namespace, b.Beat.Name)...) + return k.CheckPodCount(0, test.BeatPodListOptions(b.Beat.Namespace, b.Beat.Name, b.Beat.Spec.Type)...) }), }, } diff --git a/test/e2e/test/helper/yaml.go b/test/e2e/test/helper/yaml.go index 99877b380b..ceca69268a 100644 --- a/test/e2e/test/helper/yaml.go +++ b/test/e2e/test/helper/yaml.go @@ -13,7 +13,7 @@ import ( beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" kbv1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" - commonbeat "github.com/elastic/cloud-on-k8s/pkg/controller/common/beat" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/apmserver" "github.com/elastic/cloud-on-k8s/test/e2e/test/beat" @@ -75,7 +75,7 @@ func (yd *YAMLDecoder) ToBuilders(reader *bufio.Reader, transform BuilderTransfo b.ApmServer = *decodedObj builder = transform(b) case *beatv1beta1.Beat: - b := beat.NewBuilder(decodedObj.Name, commonbeat.Type(decodedObj.Spec.Type)) + b := beat.NewBuilder(decodedObj.Name, beatcommon.Type(decodedObj.Spec.Type)) b.Beat = *decodedObj builder = transform(b) default: diff --git a/test/e2e/test/k8s_client.go b/test/e2e/test/k8s_client.go index 96f105d17c..5a2faafc1b 100644 --- a/test/e2e/test/k8s_client.go +++ b/test/e2e/test/k8s_client.go @@ -29,7 +29,7 @@ import ( esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" kbv1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/apmserver" - "github.com/elastic/cloud-on-k8s/pkg/controller/beat" + beatcommon "github.com/elastic/cloud-on-k8s/pkg/controller/beat/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common" "github.com/elastic/cloud-on-k8s/pkg/controller/common/certificates" "github.com/elastic/cloud-on-k8s/pkg/controller/common/name" @@ -320,11 +320,11 @@ func ApmServerPodListOptions(apmNamespace, apmName string) []k8sclient.ListOptio } -func BeatPodListOptions(beatNamespace, beatName string) []k8sclient.ListOption { +func BeatPodListOptions(beatNamespace, beatName, beatType string) []k8sclient.ListOption { ns := k8sclient.InNamespace(beatNamespace) matchLabels := k8sclient.MatchingLabels(map[string]string{ - common.TypeLabelName: beat.Type, - beat.NameLabelName: beatName, + common.TypeLabelName: beatcommon.TypeLabelValue, + beatcommon.NameLabelName: beatcommon.Name(beatName, beatType), }) return []k8sclient.ListOption{ns, matchLabels} } From dcd3dc4cc4641b088b5f7147e7878facdaeeca84 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 4 Jun 2020 15:11:49 +0200 Subject: [PATCH 59/81] Add/fix comments --- cmd/manager/main.go | 8 ++++---- config/e2e/operator.yaml | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index aa08c6e52a..590242eb5f 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -171,7 +171,7 @@ func init() { Cmd.Flags().Bool( operator.ManageBeatAutodiscoverRBACFlag, true, - "Determines whether the operator should set up binding and service account for the Beats autodiscover feature", + "Determines whether the operator should set up bindings and service accounts for the Beats autodiscover feature", ) // enable using dashed notation in flags and underscores in env @@ -267,9 +267,9 @@ func execute() { opts.Namespace = managedNamespaces[0] default: log.Info("Operator configured to manage multiple namespaces", "namespaces", managedNamespaces, "operator_namespace", operatorNamespace) - // always include: - // 1. the operator namespace into the manager cache so that we can work with operator-internal resources in there - // 2. empty namespace for non-namespaced resources + // the manager cache should always include: + // 1. the operator namespace so that we can work with operator-internal resources + // 2. empty namespace so that we can work with cluster-wide (non-namespaced) resources opts.NewCache = cache.MultiNamespacedCacheBuilder(append(managedNamespaces, operatorNamespace, "")) } diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index a2fd1b686c..663536719e 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -60,6 +60,8 @@ rules: - update - patch - delete +# required to allow the operator to bind service accounts it manages +# to role that holds permissions needed for Beat autodiscover feature - apiGroups: - rbac.authorization.k8s.io resources: From 262cadb1a7a5b4784d47363bc383c1a89d27f15d Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Thu, 4 Jun 2020 15:14:06 +0200 Subject: [PATCH 60/81] Fix removing comments from yamls --- config/operator/Makefile | 6 +++--- pkg/controller/beat/controller_test.go | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 pkg/controller/beat/controller_test.go diff --git a/config/operator/Makefile b/config/operator/Makefile index 328b0d47b1..7c677eb453 100644 --- a/config/operator/Makefile +++ b/config/operator/Makefile @@ -23,7 +23,7 @@ generate-all-in-one: printf "\n---\n" && cat $$yaml ; \ done | \ sed \ - -e "/# /d" \ + -e "s|#.*||" \ -e "s||$$OPERATOR_IMAGE|g" \ -e "s||$$OPERATOR_NAME|g" \ -e "s||$$NAMESPACE|g" @@ -36,7 +36,7 @@ endif printf "\n---\n" && cat $$yaml ; \ done | \ sed \ - -e "/# /d" \ + -e "s|#.*||" \ -e "s||$$OPERATOR_IMAGE|g" \ -e "s||$$OPERATOR_NAME|g" \ -e "s||$$NAMESPACE|g" \ @@ -45,7 +45,7 @@ endif @ (MANAGED_NS_LIST=$${MANAGED_NAMESPACES//,/ }; \ for managed_ns in $$MANAGED_NS_LIST; do \ printf "\n---\n" && sed \ - -e "/# /d" \ + -e "s|#.*||" \ -e "s||$$NAMESPACE|g" \ -e "s||$$OPERATOR_NAME|g" \ -e "s||$$managed_ns|g" \ diff --git a/pkg/controller/beat/controller_test.go b/pkg/controller/beat/controller_test.go deleted file mode 100644 index c3979e2bc4..0000000000 --- a/pkg/controller/beat/controller_test.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License; -// you may not use this file except in compliance with the Elastic License. - -package beat From d1dcf392fa52e0f9909427ce6adb5af8fb395bf3 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:26:12 +0200 Subject: [PATCH 61/81] Revert accidental comment --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0b0411e443..512e0b46a6 100644 --- a/Makefile +++ b/Makefile @@ -409,7 +409,7 @@ e2e-local: --log-verbosity=$(LOG_VERBOSITY) \ --ignore-webhook-failures \ --test-timeout=$(TEST_TIMEOUT) - #@E2E_JSON=$(E2E_JSON) test/e2e/run.sh -run "$(TESTS_MATCH)" -args -testContextPath $(LOCAL_E2E_CTX) + @E2E_JSON=$(E2E_JSON) test/e2e/run.sh -run "$(TESTS_MATCH)" -args -testContextPath $(LOCAL_E2E_CTX) ########################################## ## -- Continuous integration -- ## From 90e37a815ee098140ac4833cfdf19842606a09e0 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:29:15 +0200 Subject: [PATCH 62/81] Renames and comments around autodiscover --- cmd/manager/main.go | 2 +- pkg/controller/beat/common/autodiscover.go | 51 ++++++++++++++-------- pkg/controller/beat/common/common.go | 2 +- pkg/controller/beat/common/pod.go | 2 +- pkg/controller/beat/controller.go | 4 +- 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 590242eb5f..585a4d58e5 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -340,7 +340,7 @@ func execute() { } if viper.GetBool(operator.ManageBeatAutodiscoverRBACFlag) { - beatcommon.EnableAutodiscoverRBACSetup() + beatcommon.EnableAutodiscoverRBACManagement() } if err = apmserver.Add(mgr, params); err != nil { diff --git a/pkg/controller/beat/common/autodiscover.go b/pkg/controller/beat/common/autodiscover.go index afd6a4a00e..255ea379c7 100644 --- a/pkg/controller/beat/common/autodiscover.go +++ b/pkg/controller/beat/common/autodiscover.go @@ -25,35 +25,48 @@ import ( ) const ( - serviceAccountNameTemplate = "elastic-operator-beat-%s" + // serviceAccountNameTemplate is the template to be used with Beat name to obtain the name of a ServiceAccount. + // Note that users might depend on it. + serviceAccountNameTemplate = "elastic-operator-beat-%s" + + // clusterRoleBindingNameTemplate is the template to be used with Beat namespace and name to obtain the name of + // a ClusterRoleBinding. clusterRoleBindingNameTemplate = "elastic-operator-beat-autodiscover-%s-%s" - clusterRoleName = "elastic-operator-beat-autodiscover" - autodiscoverBeatNameLabelName = "autodiscover.beat.k8s.elastic.co/name" + // clusterRoleName is the name of the ClusterRole. If autodiscover RBAC management is enabled, operator assumes + // that this role already exists in the cluster. + clusterRoleName = "elastic-operator-beat-autodiscover" + + // autodiscoverBeatNameLabelName is a label name that is applied to ClusterRoleBinding for autodiscover + // permissions. Label value is the name of the Beat resource that the binding is for. + autodiscoverBeatNameLabelName = "autodiscover.beat.k8s.elastic.co/name" + + // autodiscoverBeatNamespaceLabelName is a label name that is applied to ClusterRoleBinding for autodiscover + // permissions. Label value is the namespace of the Beat resource that the binding is for. autodiscoverBeatNamespaceLabelName = "autodiscover.beat.k8s.elastic.co/namespace" ) var ( - shouldSetupRBAC = false + shouldManageRBAC = false ) -// EnableAutodiscoverRBACSetup enables setting up autodiscover RBAC. -func EnableAutodiscoverRBACSetup() { - shouldSetupRBAC = true +// EnableAutodiscoverRBACManagement enables setting up autodiscover RBAC. +func EnableAutodiscoverRBACManagement() { + shouldManageRBAC = true } -// ShouldSetupAutodiscoverRBAC returns true if autodiscover RBAC is expected to be set up by the operator. -func ShouldSetupAutodiscoverRBAC() bool { - return shouldSetupRBAC +// ShouldManageAutodiscoverRBAC returns true if autodiscover RBAC is expected to be set up by the operator. +func ShouldManageAutodiscoverRBAC() bool { + return shouldManageRBAC } // SetupAutodiscoveryRBAC reconciles all resources needed for the default RBAC setup. -func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, beat beatv1beta1.Beat) error { - if !ShouldSetupAutodiscoverRBAC() { +func ReconcileAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Client, beat beatv1beta1.Beat) error { + if !ShouldManageAutodiscoverRBAC() { return nil } - err := setupAutodiscoverRBAC(ctx, client, beat) + err := reconcileAutodiscoverRBAC(ctx, client, beat) if err != nil { log.V(1).Info( "autodiscovery rbac setup failed", @@ -65,7 +78,7 @@ func SetupAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s.Clie } func CleanUp(client k8s.Client, nsName types.NamespacedName) error { - if ShouldSetupAutodiscoverRBAC() { + if ShouldManageAutodiscoverRBAC() { clusterRoleBinding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: ClusterRoleBindingName(nsName.Namespace, nsName.Name), @@ -83,9 +96,13 @@ func CleanUp(client k8s.Client, nsName types.NamespacedName) error { } func IsAutodiscoverResource(meta metav1.Object) (bool, types.NamespacedName) { - name, okName := meta.GetLabels()[autodiscoverBeatNameLabelName] - ns, okNs := meta.GetLabels()[autodiscoverBeatNamespaceLabelName] + labels := meta.GetLabels() + if labels == nil { + return false, types.NamespacedName{} + } + name, okName := labels[autodiscoverBeatNameLabelName] + ns, okNs := labels[autodiscoverBeatNamespaceLabelName] if okName && okNs { return true, types.NamespacedName{ Name: name, @@ -96,7 +113,7 @@ func IsAutodiscoverResource(meta metav1.Object) (bool, types.NamespacedName) { return false, types.NamespacedName{} } -func setupAutodiscoverRBAC(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) error { +func reconcileAutodiscoverRBAC(ctx context.Context, client k8s.Client, beat beatv1beta1.Beat) error { span, _ := apm.StartSpan(ctx, "reconcile_autodiscover_rbac", tracing.SpanTypeApp) defer span.End() diff --git a/pkg/controller/beat/common/common.go b/pkg/controller/beat/common/common.go index 6b51929beb..a092ef7025 100644 --- a/pkg/controller/beat/common/common.go +++ b/pkg/controller/beat/common/common.go @@ -84,7 +84,7 @@ func Reconcile( return nil, results.WithError(err) } - if err := SetupAutodiscoverRBAC(params.Context, params.Logger, params.Client, params.Beat); err != nil { + if err := ReconcileAutodiscoverRBAC(params.Context, params.Logger, params.Client, params.Beat); err != nil { results.WithError(err) } diff --git a/pkg/controller/beat/common/pod.go b/pkg/controller/beat/common/pod.go index 0f834008f4..e6ab7443b2 100644 --- a/pkg/controller/beat/common/pod.go +++ b/pkg/controller/beat/common/pod.go @@ -91,7 +91,7 @@ func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyP RunAsUser: pointer.Int64(0), }) - if ShouldSetupAutodiscoverRBAC() { + if ShouldManageAutodiscoverRBAC() { autodiscoverServiceAccountName := ServiceAccountName(params.Beat.Name) // If SA is already provided, the call will be no-op. This is fine as we then assume // that for this resource (despite operator configuration) the user took the responsibility diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 75d5a74e88..984648572d 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -94,7 +94,7 @@ func addWatches(c controller.Controller) error { return err } - if beatcommon.ShouldSetupAutodiscoverRBAC() { + if beatcommon.ShouldManageAutodiscoverRBAC() { if err := c.Watch(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &beatv1beta1.Beat{}, @@ -234,7 +234,7 @@ func (r *ReconcileBeat) isCompatible(ctx context.Context, beat *beatv1beta1.Beat } func (r *ReconcileBeat) onDelete(obj types.NamespacedName) error { - if beatcommon.ShouldSetupAutodiscoverRBAC() { + if beatcommon.ShouldManageAutodiscoverRBAC() { return beatcommon.CleanUp(r.Client, obj) } From ea46765e31141cf564f69ffb11ce1548a3f4b2a6 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:34:16 +0200 Subject: [PATCH 63/81] Refactor to avoid passing state around unnecessarily --- pkg/apis/beat/v1beta1/webhook.go | 2 +- .../association/controller/beat_es.go | 8 +-- pkg/controller/beat/common/common.go | 44 ++++----------- pkg/controller/beat/common/pod.go | 6 ++- pkg/controller/beat/common/reconcile.go | 54 ++++++++++++++----- pkg/controller/beat/controller.go | 24 +-------- pkg/controller/beat/filebeat/filebeat.go | 6 +-- pkg/controller/beat/metricbeat/metricbeat.go | 2 +- pkg/controller/beat/otherbeat/otherbeat.go | 2 +- pkg/controller/common/daemonset/reconcile.go | 20 ++++--- 10 files changed, 81 insertions(+), 87 deletions(-) diff --git a/pkg/apis/beat/v1beta1/webhook.go b/pkg/apis/beat/v1beta1/webhook.go index a322ead455..4663d7093d 100644 --- a/pkg/apis/beat/v1beta1/webhook.go +++ b/pkg/apis/beat/v1beta1/webhook.go @@ -7,7 +7,6 @@ package v1beta1 import ( "errors" - "github.com/elastic/cloud-on-k8s/pkg/utils/stringsutil" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -18,6 +17,7 @@ import ( commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/common/version" + "github.com/elastic/cloud-on-k8s/pkg/utils/stringsutil" ) var ( diff --git a/pkg/controller/association/controller/beat_es.go b/pkg/controller/association/controller/beat_es.go index 258da2b28c..97aea1451a 100644 --- a/pkg/controller/association/controller/beat_es.go +++ b/pkg/controller/association/controller/beat_es.go @@ -5,17 +5,17 @@ package controller import ( - esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" - eslabel "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/label" - esuser "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/user" - "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/manager" beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common/operator" + eslabel "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/label" + esuser "github.com/elastic/cloud-on-k8s/pkg/controller/elasticsearch/user" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "github.com/elastic/cloud-on-k8s/pkg/utils/rbac" ) diff --git a/pkg/controller/beat/common/common.go b/pkg/controller/beat/common/common.go index a092ef7025..0781bd357c 100644 --- a/pkg/controller/beat/common/common.go +++ b/pkg/controller/beat/common/common.go @@ -9,14 +9,11 @@ import ( "crypto/sha256" "fmt" - beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" - commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + commonassociation "github.com/elastic/cloud-on-k8s/pkg/controller/common/association" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" @@ -27,7 +24,7 @@ import ( type Type string type Driver interface { - Reconcile() (*DriverStatus, *reconciler.Results) + Reconcile() *reconciler.Results } type DriverParams struct { @@ -38,14 +35,6 @@ type DriverParams struct { Beat beatv1beta1.Beat } -func (dp *DriverParams) GetReplicas() *int32 { - if dp.Beat.Spec.Deployment == nil { - return nil - } - - return dp.Beat.Spec.Deployment.Replicas -} - func (dp *DriverParams) GetPodTemplate() corev1.PodTemplateSpec { spec := dp.Beat.Spec switch { @@ -66,22 +55,15 @@ func (dp *DriverParams) Validate() error { return nil } -type DriverStatus struct { - ExpectedNodes int32 - AvailableNodes int32 - Health beatv1beta1.BeatHealth - Association commonv1.AssociationStatus -} - func Reconcile( params DriverParams, defaultConfig *settings.CanonicalConfig, defaultImage container.Image, - modifyPodFunc func(builder *defaults.PodTemplateBuilder)) (*DriverStatus, *reconciler.Results) { + modifyPodFunc func(builder *defaults.PodTemplateBuilder)) *reconciler.Results { results := reconciler.NewResult(params.Context) if err := params.Validate(); err != nil { - return nil, results.WithError(err) + return results.WithError(err) } if err := ReconcileAutodiscoverRBAC(params.Context, params.Logger, params.Client, params.Beat); err != nil { @@ -90,23 +72,15 @@ func Reconcile( configHash := sha256.New224() if err := reconcileConfig(params, defaultConfig, configHash); err != nil { - return nil, results.WithError(err) + return results.WithError(err) } // we need to deref the secret here (if any) to include it in the configHash otherwise Beat will not be rolled on content changes if err := commonassociation.WriteAssocSecretToConfigHash(params.Client, ¶ms.Beat, configHash); err != nil { - return nil, results.WithError(err) + return results.WithError(err) } podTemplate := buildPodTemplate(params, defaultImage, modifyPodFunc, configHash) - driverStatus, err := reconcilePodVehicle(podTemplate, params) - if err != nil { - if apierrors.IsConflict(err) { - params.Logger.V(1).Info("Conflict while updating") - return nil, results.WithResult(reconcile.Result{Requeue: true}) - } - return nil, results.WithError(err) - } - - return &driverStatus, results + results.WithResults(reconcilePodVehicle(podTemplate, params)) + return results } diff --git a/pkg/controller/beat/common/pod.go b/pkg/controller/beat/common/pod.go index e6ab7443b2..88d02781e6 100644 --- a/pkg/controller/beat/common/pod.go +++ b/pkg/controller/beat/common/pod.go @@ -51,7 +51,11 @@ var ( } ) -func buildPodTemplate(params DriverParams, defaultImage container.Image, modifyPodFunc func(builder *defaults.PodTemplateBuilder), configHash hash.Hash) corev1.PodTemplateSpec { +func buildPodTemplate( + params DriverParams, + defaultImage container.Image, + modifyPodFunc func(builder *defaults.PodTemplateBuilder), + configHash hash.Hash) corev1.PodTemplateSpec { podTemplate := params.GetPodTemplate() // Token mounting gets defaulted to false, which prevents from detecting whether user had set it. diff --git a/pkg/controller/beat/common/reconcile.go b/pkg/controller/beat/common/reconcile.go index 3115bf8b91..f7a67071b5 100644 --- a/pkg/controller/beat/common/reconcile.go +++ b/pkg/controller/beat/common/reconcile.go @@ -5,7 +5,6 @@ package common import ( - beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -13,19 +12,27 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" "github.com/elastic/cloud-on-k8s/pkg/controller/common/daemonset" "github.com/elastic/cloud-on-k8s/pkg/controller/common/deployment" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/reconciler" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) -func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams) (DriverStatus, error) { - var reconciliationFunc func(params ReconciliationParams) (int32, int32, error) +func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams) *reconciler.Results { + results := reconciler.NewResult(params.Context) + if err := params.Validate(); err != nil { + return results.WithError(err) + } spec := params.Beat.Spec name := Name(params.Beat.Name, spec.Type) + var toDelete runtime.Object + var reconciliationFunc func(params ReconciliationParams) (int32, int32, error) switch { case spec.DaemonSet != nil: reconciliationFunc = reconcileDaemonSet @@ -51,20 +58,24 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams podTemplate: podTemplate, }) if err != nil { - return DriverStatus{}, err + return results.WithError(err) } // clean up the other one if err := params.Client.Delete(toDelete); err != nil && !apierrors.IsNotFound(err) { - return DriverStatus{}, err + return results.WithError(err) } - return DriverStatus{ - ExpectedNodes: desired, - AvailableNodes: ready, - Health: CalculateHealth(¶ms.Beat, ready, desired), - Association: params.Beat.AssociationStatus(), - }, nil + err = updateStatus(params, ready, desired) + if err != nil && apierrors.IsConflict(err) { + params.Logger.V(1).Info( + "Conflict while updating status", + "namespace", params.Beat.Namespace, + "beat_name", params.Beat.Name) + return results.WithResult(reconcile.Result{Requeue: true}) + } + + return results.WithError(err) } type ReconciliationParams struct { @@ -75,7 +86,7 @@ type ReconciliationParams struct { func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { d := deployment.New(deployment.Params{ - Name: rp.beat.Name, + Name: Name(rp.beat.Name, rp.beat.Spec.Type), Namespace: rp.beat.Namespace, Selector: NewLabels(rp.beat), Labels: NewLabels(rp.beat), @@ -95,7 +106,13 @@ func reconcileDeployment(rp ReconciliationParams) (int32, int32, error) { } func reconcileDaemonSet(rp ReconciliationParams) (int32, int32, error) { - ds := daemonset.New(rp.podTemplate, rp.beat.Name, NewLabels(rp.beat), &rp.beat, NewLabels(rp.beat)) + ds := daemonset.New(daemonset.Params{ + PodTemplate: rp.podTemplate, + Name: Name(rp.beat.Name, rp.beat.Spec.Type), + Owner: &rp.beat, + Labels: NewLabels(rp.beat), + Selectors: NewLabels(rp.beat), + }) if err := controllerutil.SetControllerReference(&rp.beat, &ds, scheme.Scheme); err != nil { return 0, 0, err @@ -108,3 +125,14 @@ func reconcileDaemonSet(rp ReconciliationParams) (int32, int32, error) { return reconciled.Status.NumberReady, reconciled.Status.DesiredNumberScheduled, nil } + +func updateStatus(params DriverParams, ready, desired int32) error { + beat := params.Beat + + beat.Status.AvailableNodes = ready + beat.Status.ExpectedNodes = desired + beat.Status.Health = CalculateHealth(&beat, ready, desired) + beat.Status.Association = beat.AssociationStatus() + + return params.Client.Status().Update(&beat) +} diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index 984648572d..e4b49f1b76 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -185,15 +185,8 @@ func (r *ReconcileBeat) doReconcile(ctx context.Context, beat beatv1beta1.Beat) return results.WithError(err) } - driverStatus, results := newDriver(ctx, r.Client, beat).Reconcile() - results.WithResults(results) - - err := r.updateStatus(driverStatus, beat) - results.WithError(err) - if err != nil && apierrors.IsConflict(err) { - log.V(1).Info("Conflict while updating status", "namespace", beat.Namespace, "beat_name", beat.Name) - results.WithResult(reconcile.Result{Requeue: true}) - } + driverResults := newDriver(ctx, r.Client, beat).Reconcile() + results.WithResults(driverResults) return results } @@ -211,19 +204,6 @@ func (r *ReconcileBeat) validate(ctx context.Context, beat *beatv1beta1.Beat) er return nil } -func (r *ReconcileBeat) updateStatus(driverStatus *beatcommon.DriverStatus, beat beatv1beta1.Beat) error { - if driverStatus == nil { - return nil - } - - beat.Status.AvailableNodes = driverStatus.AvailableNodes - beat.Status.ExpectedNodes = driverStatus.ExpectedNodes - beat.Status.Health = driverStatus.Health - beat.Status.Association = driverStatus.Association - - return r.Client.Status().Update(&beat) -} - func (r *ReconcileBeat) isCompatible(ctx context.Context, beat *beatv1beta1.Beat) (bool, error) { selector := map[string]string{beatcommon.NameLabelName: beat.Name} compat, err := annotation.ReconcileCompatibility(ctx, r.Client, beat, selector, r.OperatorInfo.BuildInfo.Version) diff --git a/pkg/controller/beat/filebeat/filebeat.go b/pkg/controller/beat/filebeat/filebeat.go index 3c75170433..7f36d78241 100644 --- a/pkg/controller/beat/filebeat/filebeat.go +++ b/pkg/controller/beat/filebeat/filebeat.go @@ -29,7 +29,7 @@ const ( HostPodsLogsMountPath = "/var/log/pods" ) -type FilebeatDriver struct { +type Driver struct { beatcommon.DriverParams beatcommon.Driver } @@ -41,10 +41,10 @@ func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { spec.DaemonSet = &beatv1beta1.DaemonSetSpec{} } - return &FilebeatDriver{DriverParams: params} + return &Driver{DriverParams: params} } -func (d *FilebeatDriver) Reconcile() (*beatcommon.DriverStatus, *reconciler.Results) { +func (d *Driver) Reconcile() *reconciler.Results { f := func(builder *defaults.PodTemplateBuilder) { containersVolume := volume.NewReadOnlyHostVolume(HostContainersVolumeName, HostContainersPath, HostContainersMountPath) containersLogsVolume := volume.NewReadOnlyHostVolume(HostContainersLogsVolumeName, HostContainersLogsPath, HostContainersLogsMountPath) diff --git a/pkg/controller/beat/metricbeat/metricbeat.go b/pkg/controller/beat/metricbeat/metricbeat.go index 7664eeae4e..c98b65b91e 100644 --- a/pkg/controller/beat/metricbeat/metricbeat.go +++ b/pkg/controller/beat/metricbeat/metricbeat.go @@ -45,7 +45,7 @@ func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { return &Driver{DriverParams: params} } -func (d *Driver) Reconcile() (*beatcommon.DriverStatus, *reconciler.Results) { +func (d *Driver) Reconcile() *reconciler.Results { f := func(builder *defaults.PodTemplateBuilder) { dockerSockVolume := volume.NewHostVolume(DockerSockVolumeName, DockerSockPath, DockerSockMountPath, false, corev1.HostPathUnset) procVolume := volume.NewReadOnlyHostVolume(ProcVolumeName, ProcPath, ProcMountPath) diff --git a/pkg/controller/beat/otherbeat/otherbeat.go b/pkg/controller/beat/otherbeat/otherbeat.go index b8ee51e85e..74a460d80e 100644 --- a/pkg/controller/beat/otherbeat/otherbeat.go +++ b/pkg/controller/beat/otherbeat/otherbeat.go @@ -27,6 +27,6 @@ func NewDriver(params beatcommon.DriverParams) beatcommon.Driver { return &Driver{DriverParams: params} } -func (d *Driver) Reconcile() (*beatcommon.DriverStatus, *reconciler.Results) { +func (d *Driver) Reconcile() *reconciler.Results { return beatcommon.Reconcile(d.DriverParams, nil, "", nil) } diff --git a/pkg/controller/common/daemonset/reconcile.go b/pkg/controller/common/daemonset/reconcile.go index 5ae64e2392..189fe5ab5a 100644 --- a/pkg/controller/common/daemonset/reconcile.go +++ b/pkg/controller/common/daemonset/reconcile.go @@ -14,18 +14,26 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) -func New(podTemplate corev1.PodTemplateSpec, name string, labels map[string]string, owner metav1.Object, selectors map[string]string) appsv1.DaemonSet { +type Params struct { + PodTemplate corev1.PodTemplateSpec + Name string + Owner metav1.Object + Labels map[string]string + Selectors map[string]string +} + +func New(params Params) appsv1.DaemonSet { return appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: owner.GetNamespace(), - Labels: labels, + Name: params.Name, + Namespace: params.Owner.GetNamespace(), + Labels: params.Labels, }, Spec: appsv1.DaemonSetSpec{ Selector: &metav1.LabelSelector{ - MatchLabels: selectors, + MatchLabels: params.Selectors, }, - Template: podTemplate, + Template: params.PodTemplate, }, } } From 4d303fd74ee331081156e9553ec7cad685ede929 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:36:18 +0200 Subject: [PATCH 64/81] Set security context on pod instead of on container --- pkg/controller/beat/common/pod.go | 2 +- pkg/controller/common/defaults/pod_template.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/controller/beat/common/pod.go b/pkg/controller/beat/common/pod.go index 88d02781e6..0737942c2d 100644 --- a/pkg/controller/beat/common/pod.go +++ b/pkg/controller/beat/common/pod.go @@ -91,7 +91,7 @@ func buildPodTemplate( WithDockerImage(spec.Image, container.ImageRepository(defaultImage, spec.Version)). WithArgs("-e", "-c", ConfigMountPath). WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). - WithSecurityContext(corev1.SecurityContext{ + WithPodSecurityContext(corev1.PodSecurityContext{ RunAsUser: pointer.Int64(0), }) diff --git a/pkg/controller/common/defaults/pod_template.go b/pkg/controller/common/defaults/pod_template.go index 7f8cbc057e..d5eca11693 100644 --- a/pkg/controller/common/defaults/pod_template.go +++ b/pkg/controller/common/defaults/pod_template.go @@ -364,9 +364,9 @@ func (b *PodTemplateBuilder) WithDNSPolicy(dnsPolicy corev1.DNSPolicy) *PodTempl return b } -func (b *PodTemplateBuilder) WithSecurityContext(securityContext corev1.SecurityContext) *PodTemplateBuilder { - if b.Container.SecurityContext == nil { - b.Container.SecurityContext = &securityContext +func (b *PodTemplateBuilder) WithPodSecurityContext(securityContext corev1.PodSecurityContext) *PodTemplateBuilder { + if b.PodTemplate.Spec.SecurityContext == nil { + b.PodTemplate.Spec.SecurityContext = &securityContext } return b } From 29115377364973cef0fa209a923f2fb16622dc4c Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:37:59 +0200 Subject: [PATCH 65/81] Loosen beat psp --- config/dev/elastic-psp.yaml | 9 ++------- config/e2e/operator.yaml | 15 --------------- test/e2e/beat/config_test.go | 9 +++++++-- test/e2e/smoke_test.go | 1 + test/e2e/test/beat/builder.go | 15 +++++++++++++++ 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/config/dev/elastic-psp.yaml b/config/dev/elastic-psp.yaml index 168e556710..5c15a2440b 100644 --- a/config/dev/elastic-psp.yaml +++ b/config/dev/elastic-psp.yaml @@ -58,13 +58,8 @@ seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default' apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' spec: - privileged: false - # Required to prevent escalations to root. - allowPrivilegeEscalation: false - # This is redundant with non-root + disallow privilege escalation, - # but we can provide it for defense in depth. - requiredDropCapabilities: - - ALL + privileged: true + allowPrivilegeEscalation: true # Allow core volume types. volumes: - 'configMap' diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index 663536719e..63b560571a 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -323,18 +323,3 @@ rules: - get - watch - list ---- -# permission to use PSP for Beat -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: elastic-operator-beat-psp -rules: -- apiGroups: - - policy - resources: - - podsecuritypolicies - resourceNames: - - elastic.beat.restricted - verbs: - - use diff --git a/test/e2e/beat/config_test.go b/test/e2e/beat/config_test.go index 93bf036289..9b0143de0a 100644 --- a/test/e2e/beat/config_test.go +++ b/test/e2e/beat/config_test.go @@ -8,11 +8,13 @@ import ( "fmt" "testing" - "github.com/elastic/cloud-on-k8s/pkg/controller/beat/metricbeat" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/beat/filebeat" + "github.com/elastic/cloud-on-k8s/pkg/controller/beat/metricbeat" "github.com/elastic/cloud-on-k8s/pkg/controller/common/settings" "github.com/elastic/cloud-on-k8s/test/e2e/test" "github.com/elastic/cloud-on-k8s/test/e2e/test/beat" @@ -58,7 +60,10 @@ func TestHeartbeatConfig(t *testing.T) { hbBuilder := beat.NewBuilder(name, "heartbeat"). WithElasticsearchRef(esBuilder.Ref()). WithImage("docker.elastic.co/beats/heartbeat:7.7.0"). - WithESValidations(beat.HasEventFromBeat("heartbeat")) + WithESValidations(beat.HasEventFromBeat("heartbeat")). + WithContainerSecurityContext(corev1.SecurityContext{ + RunAsUser: pointer.Int64(0), + }) yaml := fmt.Sprintf(` heartbeat.monitors: diff --git a/test/e2e/smoke_test.go b/test/e2e/smoke_test.go index 23daf7ceab..715256841c 100644 --- a/test/e2e/smoke_test.go +++ b/test/e2e/smoke_test.go @@ -75,6 +75,7 @@ func TestSmoke(t *testing.T) { WithLabel(run.TestNameLabel, testName). WithPodLabel(run.TestNameLabel, testName). WithESValidations(beat.HasEventFromBeat(filebeat.Type)) + test.Sequence(nil, test.EmptySteps, esBuilder, kbBuilder, apmBuilder, beatBuilder). RunSequential(t) } diff --git a/test/e2e/test/beat/builder.go b/test/e2e/test/beat/builder.go index 05e5037136..993161ac8d 100644 --- a/test/e2e/test/beat/builder.go +++ b/test/e2e/test/beat/builder.go @@ -87,6 +87,21 @@ func (b Builder) WithRestrictedSecurityContext() Builder { return b } +func (b Builder) WithContainerSecurityContext(securityContext corev1.SecurityContext) Builder { + if b.Beat.Spec.DaemonSet != nil { + for i := range b.Beat.Spec.DaemonSet.PodTemplate.Spec.Containers { + b.Beat.Spec.DaemonSet.PodTemplate.Spec.Containers[i].SecurityContext = &securityContext + } + } + if b.Beat.Spec.Deployment != nil { + for i := range b.Beat.Spec.Deployment.PodTemplate.Spec.Containers { + b.Beat.Spec.Deployment.PodTemplate.Spec.Containers[i].SecurityContext = &securityContext + } + } + + return b +} + func (b Builder) WithLabel(key, value string) Builder { if b.Beat.Labels == nil { b.Beat.Labels = make(map[string]string) From bdb67b49977fc56a36075daeb90952f33de91254 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:39:04 +0200 Subject: [PATCH 66/81] Fix adding beats to v1beta1 scheme --- pkg/controller/common/scheme/scheme.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/controller/common/scheme/scheme.go b/pkg/controller/common/scheme/scheme.go index c27fb3c4e3..b8f317bb2a 100644 --- a/pkg/controller/common/scheme/scheme.go +++ b/pkg/controller/common/scheme/scheme.go @@ -90,5 +90,9 @@ func SetupV1beta1Scheme() { if err != nil { panic(err) } + err = beatv1beta1.AddToScheme(clientgoscheme.Scheme) + if err != nil { + panic(err) + } }) } From ac1ff773a115c1c21f59ac1adca3dc1ac6f3839b Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:39:27 +0200 Subject: [PATCH 67/81] Clean up comments on Beat type --- pkg/apis/beat/v1beta1/beat_types.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index 258235ff8f..befbfa841f 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -25,7 +25,7 @@ type BeatSpec struct { // +kubebuilder:validation:Optional ElasticsearchRef commonv1.ObjectSelector `json:"elasticsearchRef,omitempty"` - // Image is the Beat Docker image to deploy. Version has to match the Beat in the image. + // Image is the Beat Docker image to deploy. Version and Type have to match the Beat in the image. // +kubebuilder:validation:Optional Image string `json:"image,omitempty"` @@ -38,9 +38,6 @@ type BeatSpec struct { // +kubebuilder:validation:Optional ServiceAccountName string `json:"serviceAccountName,omitempty"` - // DaemonSet specifies the Beat should be deployed as a DaemonSet, and allows overriding its default spec. - // Cannot be used along with `deployment`. - // DaemonSet allows to: // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the // Type is used @@ -67,7 +64,7 @@ type DeploymentSpec struct { Replicas *int32 `json:"replicas,omitempty"` } -// BeatStatus defines the observed state of Beat +// BeatStatus defines the observed state of a Beat. type BeatStatus struct { // +kubebuilder:validation:Optional commonv1.ReconcilerStatus `json:",inline"` @@ -101,7 +98,7 @@ const ( // +kubebuilder:object:root=true -// Beat is the Schema for the Beats API +// Beat is the Schema for the Beats API. // +kubebuilder:resource:categories=elastic,shortName=beat // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="health",type="string",JSONPath=".status.health" @@ -177,7 +174,7 @@ var _ commonv1.Association = &Beat{} // +kubebuilder:object:root=true -// BeatList contains a list of Beats +// BeatList contains a list of Beats. type BeatList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` From 328a83385f7f9959d700b4d4c179abdab2e6a56c Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 00:42:37 +0200 Subject: [PATCH 68/81] Generated files --- config/crds/all-crds.yaml | 8 ++++---- config/crds/bases/beat.k8s.elastic.co_beats.yaml | 8 ++++---- docs/reference/api-docs.asciidoc | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config/crds/all-crds.yaml b/config/crds/all-crds.yaml index e2d6ca247e..b21d5d7831 100644 --- a/config/crds/all-crds.yaml +++ b/config/crds/all-crds.yaml @@ -501,7 +501,7 @@ spec: status: {} validation: openAPIV3Schema: - description: Beat is the Schema for the Beats API + description: Beat is the Schema for the Beats API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -554,8 +554,8 @@ spec: - name type: object image: - description: Image is the Beat Docker image to deploy. Version has to - match the Beat in the image. + description: Image is the Beat Docker image to deploy. Version and Type + have to match the Beat in the image. type: string serviceAccountName: description: ServiceAccountName is used to check access from the current @@ -576,7 +576,7 @@ spec: - version type: object status: - description: BeatStatus defines the observed state of Beat + description: BeatStatus defines the observed state of a Beat. properties: associationStatus: description: AssociationStatus is the status of an association resource. diff --git a/config/crds/bases/beat.k8s.elastic.co_beats.yaml b/config/crds/bases/beat.k8s.elastic.co_beats.yaml index af5fab7222..3720efce43 100644 --- a/config/crds/bases/beat.k8s.elastic.co_beats.yaml +++ b/config/crds/bases/beat.k8s.elastic.co_beats.yaml @@ -46,7 +46,7 @@ spec: status: {} validation: openAPIV3Schema: - description: Beat is the Schema for the Beats API + description: Beat is the Schema for the Beats API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -12159,8 +12159,8 @@ spec: - name type: object image: - description: Image is the Beat Docker image to deploy. Version has to - match the Beat in the image. + description: Image is the Beat Docker image to deploy. Version and Type + have to match the Beat in the image. type: string serviceAccountName: description: ServiceAccountName is used to check access from the current @@ -12181,7 +12181,7 @@ spec: - version type: object status: - description: BeatStatus defines the observed state of Beat + description: BeatStatus defines the observed state of a Beat. properties: associationStatus: description: AssociationStatus is the status of an association resource. diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index 5f68202a2b..e1ff0e9771 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -145,7 +145,7 @@ Package v1beta1 contains API Schema definitions for the beat v1beta1 API group [id="{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-beat"] === Beat -Beat is the Schema for the Beats API +Beat is the Schema for the Beats API. @@ -176,7 +176,7 @@ BeatSpec defines the desired state of a Beat. | *`type`* __string__ | Type is the type of the Beat to deploy (filebeat, metricbeat, etc.). Any string can be used, but well-known types will be recognized and will allow to provide sane default configurations. | *`version`* __string__ | Version of the Beat. | *`elasticsearchRef`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-objectselector[$$ObjectSelector$$]__ | ElasticsearchRef is a reference to an Elasticsearch cluster running in the same Kubernetes cluster. -| *`image`* __string__ | Image is the Beat Docker image to deploy. Version has to match the Beat in the image. +| *`image`* __string__ | Image is the Beat Docker image to deploy. Version and Type have to match the Beat in the image. | *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the Beat configuration. If provided, it will override the default configuration. | *`serviceAccountName`* __string__ | ServiceAccountName is used to check access from the current resource to Elasticsearch resource in a different namespace. Can only be used if ECK is enforcing RBAC on references. | *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the DaemonSet Cannot be used along with `deployment`. From 9133ecde9cf2287b85eb734c823f19478bbe018d Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 02:27:19 +0200 Subject: [PATCH 69/81] Fixes for OpenShift E2E tests --- config/e2e/filebeat.yaml | 6 +++++- config/e2e/operator.yaml | 1 + config/operator/all-in-one/cluster_role.template.yaml | 1 + config/operator/namespace/cluster_role.template.yaml | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/config/e2e/filebeat.yaml b/config/e2e/filebeat.yaml index 1808a6b346..61656b4fa8 100644 --- a/config/e2e/filebeat.yaml +++ b/config/e2e/filebeat.yaml @@ -237,7 +237,7 @@ subjects: apiVersion: security.openshift.io/v1 kind: SecurityContextConstraints metadata: - name: filebeat + name: beat allowHostDirVolumePlugin: true allowHostIPC: false allowHostNetwork: true @@ -262,6 +262,10 @@ supplementalGroups: type: RunAsAny users: - system:serviceaccount:{{ .E2ENamespace }}:filebeat +groups: +{{- range .Operator.ManagedNamespaces }} + - system:serviceaccounts:{{ . }} +{{- end }} volumes: - '*' {{end}} diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index 63b560571a..265b1e15a4 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -145,6 +145,7 @@ rules: resources: - beats - beats/status + - beats/finalizers verbs: - get - list diff --git a/config/operator/all-in-one/cluster_role.template.yaml b/config/operator/all-in-one/cluster_role.template.yaml index 3c77b1e7dd..a27ebd0224 100644 --- a/config/operator/all-in-one/cluster_role.template.yaml +++ b/config/operator/all-in-one/cluster_role.template.yaml @@ -142,6 +142,7 @@ rules: resources: - beats - beats/status + - beats/finalizers verbs: - get - list diff --git a/config/operator/namespace/cluster_role.template.yaml b/config/operator/namespace/cluster_role.template.yaml index e98a834a9e..32020a04ef 100644 --- a/config/operator/namespace/cluster_role.template.yaml +++ b/config/operator/namespace/cluster_role.template.yaml @@ -158,6 +158,7 @@ rules: resources: - beats - beats/status + - beats/finalizers verbs: - get - list From ae692699f2430ba21041b455df9241aeb9ab1404 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 21:45:06 +0200 Subject: [PATCH 70/81] Set template hash label to ClusterRoleBinding --- pkg/controller/beat/common/autodiscover.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/controller/beat/common/autodiscover.go b/pkg/controller/beat/common/autodiscover.go index 255ea379c7..0c81ebd1aa 100644 --- a/pkg/controller/beat/common/autodiscover.go +++ b/pkg/controller/beat/common/autodiscover.go @@ -173,6 +173,7 @@ func reconcileClusterRoleBinding(client k8s.Client, beat beatv1beta1.Beat) error Name: clusterRoleName, }, } + expected.Labels = hash.SetTemplateHashLabel(expected.Labels, expected) reconciled := &rbacv1.ClusterRoleBinding{} return reconciler.ReconcileResource(reconciler.Params{ From ad6f6745e1617a33d8e3af60a115c7042181d243 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 22:26:39 +0200 Subject: [PATCH 71/81] Rename roles.yaml to beat-roles.yaml, common.go to driver.go --- config/operator/all-in-one/{roles.yaml => beat-roles.yaml} | 0 config/operator/namespace/{roles.yaml => beat-roles.yaml} | 0 pkg/controller/beat/common/{common.go => driver.go} | 3 ++- pkg/controller/beat/common/{common_test.go => driver_test.go} | 0 4 files changed, 2 insertions(+), 1 deletion(-) rename config/operator/all-in-one/{roles.yaml => beat-roles.yaml} (100%) rename config/operator/namespace/{roles.yaml => beat-roles.yaml} (100%) rename pkg/controller/beat/common/{common.go => driver.go} (96%) rename pkg/controller/beat/common/{common_test.go => driver_test.go} (100%) diff --git a/config/operator/all-in-one/roles.yaml b/config/operator/all-in-one/beat-roles.yaml similarity index 100% rename from config/operator/all-in-one/roles.yaml rename to config/operator/all-in-one/beat-roles.yaml diff --git a/config/operator/namespace/roles.yaml b/config/operator/namespace/beat-roles.yaml similarity index 100% rename from config/operator/namespace/roles.yaml rename to config/operator/namespace/beat-roles.yaml diff --git a/pkg/controller/beat/common/common.go b/pkg/controller/beat/common/driver.go similarity index 96% rename from pkg/controller/beat/common/common.go rename to pkg/controller/beat/common/driver.go index 0781bd357c..b76298adff 100644 --- a/pkg/controller/beat/common/common.go +++ b/pkg/controller/beat/common/driver.go @@ -59,7 +59,8 @@ func Reconcile( params DriverParams, defaultConfig *settings.CanonicalConfig, defaultImage container.Image, - modifyPodFunc func(builder *defaults.PodTemplateBuilder)) *reconciler.Results { + modifyPodFunc func(builder *defaults.PodTemplateBuilder), +) *reconciler.Results { results := reconciler.NewResult(params.Context) if err := params.Validate(); err != nil { diff --git a/pkg/controller/beat/common/common_test.go b/pkg/controller/beat/common/driver_test.go similarity index 100% rename from pkg/controller/beat/common/common_test.go rename to pkg/controller/beat/common/driver_test.go From 8941ce82da41615f1db4baad18ac5dcc3af3e813 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 22:42:55 +0200 Subject: [PATCH 72/81] Conditionally add empty namespace to cached namespaces --- cmd/manager/main.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 585a4d58e5..06df2f17b4 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -257,6 +257,8 @@ func execute() { CertDir: viper.GetString(operator.WebhookCertDirFlag), } + manageBeatAutodiscoverRBAC := viper.GetBool(operator.ManageBeatAutodiscoverRBACFlag) + // configure the manager cache based on the number of managed namespaces managedNamespaces := viper.GetStringSlice(operator.NamespacesFlag) switch { @@ -267,10 +269,15 @@ func execute() { opts.Namespace = managedNamespaces[0] default: log.Info("Operator configured to manage multiple namespaces", "namespaces", managedNamespaces, "operator_namespace", operatorNamespace) - // the manager cache should always include: - // 1. the operator namespace so that we can work with operator-internal resources - // 2. empty namespace so that we can work with cluster-wide (non-namespaced) resources - opts.NewCache = cache.MultiNamespacedCacheBuilder(append(managedNamespaces, operatorNamespace, "")) + // the manager cache should always include the operator namespace so that we can work with operator-internal resources + cachedNamespaces := append(managedNamespaces, operatorNamespace) + + // include empty namespace so that we can work with cluster-wide (non-namespaced) resources needed with autodiscover + if manageBeatAutodiscoverRBAC { + cachedNamespaces = append(cachedNamespaces, "") + } + + opts.NewCache = cache.MultiNamespacedCacheBuilder(cachedNamespaces) } // only expose prometheus metrics if provided a non-zero port @@ -339,7 +346,7 @@ func execute() { accessReviewer = rbac.NewPermissiveAccessReviewer() } - if viper.GetBool(operator.ManageBeatAutodiscoverRBACFlag) { + if manageBeatAutodiscoverRBAC { beatcommon.EnableAutodiscoverRBACManagement() } From 22b940b72df4e1fcb6b56418bc168fd43269e0cd Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 22:44:05 +0200 Subject: [PATCH 73/81] Reword comments, logs --- config/samples/beat/filebeat_es_kibana_apm.yaml | 2 +- pkg/apis/beat/v1beta1/beat_types.go | 14 ++++---------- pkg/controller/beat/common/autodiscover.go | 2 +- pkg/controller/beat/controller.go | 8 ++++++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/config/samples/beat/filebeat_es_kibana_apm.yaml b/config/samples/beat/filebeat_es_kibana_apm.yaml index 9785ed5ec1..b93c37ea2f 100644 --- a/config/samples/beat/filebeat_es_kibana_apm.yaml +++ b/config/samples/beat/filebeat_es_kibana_apm.yaml @@ -1,4 +1,4 @@ -# This sample sets up an Elasticsearch cluster and a Kibana instance preconfigured for that cluster +# This sample sets up an Elasticsearch cluster with Kibana instance, APM instance and Filebeat preconfigured for it. apiVersion: elasticsearch.k8s.elastic.co/v1 kind: Elasticsearch metadata: diff --git a/pkg/apis/beat/v1beta1/beat_types.go b/pkg/apis/beat/v1beta1/beat_types.go index befbfa841f..3813299354 100644 --- a/pkg/apis/beat/v1beta1/beat_types.go +++ b/pkg/apis/beat/v1beta1/beat_types.go @@ -38,19 +38,13 @@ type BeatSpec struct { // +kubebuilder:validation:Optional ServiceAccountName string `json:"serviceAccountName,omitempty"` - // DaemonSet allows to: - // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the - // Type is used - // 2. Provide a spec for the DaemonSet - // Cannot be used along with `deployment`. + // DaemonSet specifies the Beat should be deployed as a DaemonSet, and allows providing its spec. + // Cannot be used along with `deployment`. If both are absent a default for the Type is used. // +kubebuilder:validation:Optional DaemonSet *DaemonSetSpec `json:"daemonSet,omitempty"` - // Deployment allows to: - // 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the - // Type is used - // 2. Provide a spec for the Deployment - // Cannot be used along with `daemonSet`. + // Deployment specifies the Beat should be deployed as a Deployment, and allows providing its spec. + // Cannot be used along with `daemonSet`. If both are absent a default for the Type is used. // +kubebuilder:validation:Optional Deployment *DeploymentSpec `json:"deployment,omitempty"` } diff --git a/pkg/controller/beat/common/autodiscover.go b/pkg/controller/beat/common/autodiscover.go index 0c81ebd1aa..48b864a777 100644 --- a/pkg/controller/beat/common/autodiscover.go +++ b/pkg/controller/beat/common/autodiscover.go @@ -69,7 +69,7 @@ func ReconcileAutodiscoverRBAC(ctx context.Context, log logr.Logger, client k8s. err := reconcileAutodiscoverRBAC(ctx, client, beat) if err != nil { log.V(1).Info( - "autodiscovery rbac setup failed", + "autodiscover rbac setup failed", "namespace", beat.Namespace, "beat_name", beat.Name) } diff --git a/pkg/controller/beat/controller.go b/pkg/controller/beat/controller.go index e4b49f1b76..61be0d1f31 100644 --- a/pkg/controller/beat/controller.go +++ b/pkg/controller/beat/controller.go @@ -65,13 +65,14 @@ func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileBe } } -// addWatches registers the required watches. +// Watch DaemonSets and Deployments func addWatches(c controller.Controller) error { // Watch for changes to Beat if err := c.Watch(&source.Kind{Type: &beatv1beta1.Beat{}}, &handler.EnqueueRequestForObject{}); err != nil { return err } + // Watch DaemonSets if err := c.Watch(&source.Kind{Type: &appsv1.DaemonSet{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &beatv1beta1.Beat{}, @@ -79,6 +80,7 @@ func addWatches(c controller.Controller) error { return err } + // Watch Deployments if err := c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &beatv1beta1.Beat{}, @@ -86,7 +88,7 @@ func addWatches(c controller.Controller) error { return err } - // Watch secrets + // Watch Secrets if err := c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &beatv1beta1.Beat{}, @@ -95,6 +97,7 @@ func addWatches(c controller.Controller) error { } if beatcommon.ShouldManageAutodiscoverRBAC() { + // Watch ServiceAccounts if err := c.Watch(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{ IsController: true, OwnerType: &beatv1beta1.Beat{}, @@ -102,6 +105,7 @@ func addWatches(c controller.Controller) error { return err } + // Watch relevant ClusterRoleBindings if err := c.Watch(&source.Kind{Type: &rbacv1.ClusterRoleBinding{}}, &handler.EnqueueRequestsFromMapFunc{ ToRequests: handler.ToRequestsFunc(func(object handler.MapObject) []reconcile.Request { requests := []reconcile.Request{} From 595610a97907a8a6b6bc48dcd1a176372d0f1b39 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 23:52:43 +0200 Subject: [PATCH 74/81] Fix comments --- test/e2e/test/beat/pod_builder.go | 1 - test/e2e/test/beat/steps.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/e2e/test/beat/pod_builder.go b/test/e2e/test/beat/pod_builder.go index 9be10ab781..9cc43a4537 100644 --- a/test/e2e/test/beat/pod_builder.go +++ b/test/e2e/test/beat/pod_builder.go @@ -126,7 +126,6 @@ func (pb PodBuilder) CreationTestSteps(k *test.K8sClient) test.StepList { var createdPod corev1.Pod err := k.Client.Get(k8s.ExtractNamespacedName(&pb.Pod), &createdPod) require.NoError(t, err) - // TODO this is incomplete }, }, }) diff --git a/test/e2e/test/beat/steps.go b/test/e2e/test/beat/steps.go index 6e86708bba..a3a2b2f7b0 100644 --- a/test/e2e/test/beat/steps.go +++ b/test/e2e/test/beat/steps.go @@ -73,7 +73,7 @@ func (b Builder) InitTestSteps(k *test.K8sClient) test.StepList { return err } - // it may take some extra time for Elasticsearch to be fully deleted + // it may take some extra time for Beat to be fully deleted var beat beatv1beta1.Beat err := k.Client.Get(k8s.ExtractNamespacedName(&b.Beat), &beat) if err != nil && !apierrors.IsNotFound(err) { @@ -107,7 +107,6 @@ func (b Builder) CreationTestSteps(k *test.K8sClient) test.StepList { err := k.Client.Get(k8s.ExtractNamespacedName(&b.Beat), &createdBeat) require.NoError(t, err) require.Equal(t, b.Beat.Spec.Version, createdBeat.Spec.Version) - // TODO this is incomplete }, }, }) From ea57b800d87503c701cfa5c31e207342caccf289 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 23:53:40 +0200 Subject: [PATCH 75/81] Drop 'operator' from autodiscover resource names --- config/e2e/operator.yaml | 4 ++-- config/operator/all-in-one/beat-roles.yaml | 2 +- config/operator/all-in-one/cluster_role.template.yaml | 2 +- config/operator/namespace/beat-roles.yaml | 2 +- config/operator/namespace/cluster_role.template.yaml | 2 +- pkg/controller/beat/common/autodiscover.go | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/e2e/operator.yaml b/config/e2e/operator.yaml index 265b1e15a4..6ec9ade4be 100644 --- a/config/e2e/operator.yaml +++ b/config/e2e/operator.yaml @@ -69,7 +69,7 @@ rules: verbs: - bind resourceNames: - - elastic-operator-beat-autodiscover + - elastic-beat-autodiscover - apiGroups: - rbac.authorization.k8s.io resources: @@ -314,7 +314,7 @@ spec: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: elastic-operator-beat-autodiscover + name: elastic-beat-autodiscover rules: - apiGroups: [""] # "" indicates the core API group resources: diff --git a/config/operator/all-in-one/beat-roles.yaml b/config/operator/all-in-one/beat-roles.yaml index c657a951d2..bda2aa26ee 100644 --- a/config/operator/all-in-one/beat-roles.yaml +++ b/config/operator/all-in-one/beat-roles.yaml @@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: elastic-operator-beat-autodiscover + name: elastic-beat-autodiscover rules: - apiGroups: [""] resources: diff --git a/config/operator/all-in-one/cluster_role.template.yaml b/config/operator/all-in-one/cluster_role.template.yaml index a27ebd0224..6fddbf8c6d 100644 --- a/config/operator/all-in-one/cluster_role.template.yaml +++ b/config/operator/all-in-one/cluster_role.template.yaml @@ -33,7 +33,7 @@ rules: verbs: - bind resourceNames: - - elastic-operator-beat-autodiscover + - elastic-beat-autodiscover - apiGroups: - "" resources: diff --git a/config/operator/namespace/beat-roles.yaml b/config/operator/namespace/beat-roles.yaml index c657a951d2..bda2aa26ee 100644 --- a/config/operator/namespace/beat-roles.yaml +++ b/config/operator/namespace/beat-roles.yaml @@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: elastic-operator-beat-autodiscover + name: elastic-beat-autodiscover rules: - apiGroups: [""] resources: diff --git a/config/operator/namespace/cluster_role.template.yaml b/config/operator/namespace/cluster_role.template.yaml index 32020a04ef..32e1dcbca4 100644 --- a/config/operator/namespace/cluster_role.template.yaml +++ b/config/operator/namespace/cluster_role.template.yaml @@ -63,7 +63,7 @@ rules: verbs: - bind resourceNames: - - elastic-operator-beat-autodiscover + - elastic-beat-autodiscover - apiGroups: - apps resources: diff --git a/pkg/controller/beat/common/autodiscover.go b/pkg/controller/beat/common/autodiscover.go index 48b864a777..6c6c6c13cc 100644 --- a/pkg/controller/beat/common/autodiscover.go +++ b/pkg/controller/beat/common/autodiscover.go @@ -27,15 +27,15 @@ import ( const ( // serviceAccountNameTemplate is the template to be used with Beat name to obtain the name of a ServiceAccount. // Note that users might depend on it. - serviceAccountNameTemplate = "elastic-operator-beat-%s" + serviceAccountNameTemplate = "elastic-beat-%s" // clusterRoleBindingNameTemplate is the template to be used with Beat namespace and name to obtain the name of // a ClusterRoleBinding. - clusterRoleBindingNameTemplate = "elastic-operator-beat-autodiscover-%s-%s" + clusterRoleBindingNameTemplate = "elastic-beat-autodiscover-%s-%s" // clusterRoleName is the name of the ClusterRole. If autodiscover RBAC management is enabled, operator assumes // that this role already exists in the cluster. - clusterRoleName = "elastic-operator-beat-autodiscover" + clusterRoleName = "elastic-beat-autodiscover" // autodiscoverBeatNameLabelName is a label name that is applied to ClusterRoleBinding for autodiscover // permissions. Label value is the name of the Beat resource that the binding is for. From ae1e37e2d5fe0374dc0c0d69c5cfe401cd6d8983 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Fri, 5 Jun 2020 23:55:06 +0200 Subject: [PATCH 76/81] Move validating Beat spec to a separate func --- pkg/controller/beat/common/driver.go | 5 ++--- pkg/controller/beat/common/driver_test.go | 2 +- pkg/controller/beat/common/reconcile.go | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/controller/beat/common/driver.go b/pkg/controller/beat/common/driver.go index b76298adff..fa7bec7f39 100644 --- a/pkg/controller/beat/common/driver.go +++ b/pkg/controller/beat/common/driver.go @@ -47,8 +47,7 @@ func (dp *DriverParams) GetPodTemplate() corev1.PodTemplateSpec { return corev1.PodTemplateSpec{} } -func (dp *DriverParams) Validate() error { - spec := dp.Beat.Spec +func ValidateBeatSpec(spec beatv1beta1.BeatSpec) error { if (spec.DaemonSet == nil && spec.Deployment == nil) || (spec.DaemonSet != nil && spec.Deployment != nil) { return fmt.Errorf("either daemonset or deployment has to be specified") } @@ -63,7 +62,7 @@ func Reconcile( ) *reconciler.Results { results := reconciler.NewResult(params.Context) - if err := params.Validate(); err != nil { + if err := ValidateBeatSpec(params.Beat.Spec); err != nil { return results.WithError(err) } diff --git a/pkg/controller/beat/common/driver_test.go b/pkg/controller/beat/common/driver_test.go index 54f92bdf28..fe8d424932 100644 --- a/pkg/controller/beat/common/driver_test.go +++ b/pkg/controller/beat/common/driver_test.go @@ -46,7 +46,7 @@ func Test_DriverParamsValidate(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - gotErr := tt.driverParams.Validate() != nil + gotErr := ValidateBeatSpec(tt.driverParams.Beat.Spec) != nil require.True(t, tt.wantErr == gotErr) }) } diff --git a/pkg/controller/beat/common/reconcile.go b/pkg/controller/beat/common/reconcile.go index f7a67071b5..3fffc009ab 100644 --- a/pkg/controller/beat/common/reconcile.go +++ b/pkg/controller/beat/common/reconcile.go @@ -24,7 +24,7 @@ import ( func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams) *reconciler.Results { results := reconciler.NewResult(params.Context) - if err := params.Validate(); err != nil { + if err := ValidateBeatSpec(params.Beat.Spec); err != nil { return results.WithError(err) } From 84a6345fdb6c56ef9aa3fbdae329504261e1a4d5 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sat, 6 Jun 2020 02:36:34 +0200 Subject: [PATCH 77/81] Tighten beat PSP, fix comments, regenerate --- config/crds/all-crds.yaml | 14 ++++++-------- config/crds/bases/beat.k8s.elastic.co_beats.yaml | 14 ++++++-------- config/dev/elastic-psp.yaml | 4 ++-- docs/reference/api-docs.asciidoc | 4 ++-- pkg/controller/beat/common/pod.go | 10 ++++------ pkg/controller/beat/common/reconcile.go | 10 ++++++++-- test/e2e/beat/config_test.go | 7 +------ 7 files changed, 29 insertions(+), 34 deletions(-) diff --git a/config/crds/all-crds.yaml b/config/crds/all-crds.yaml index b21d5d7831..7da53c175f 100644 --- a/config/crds/all-crds.yaml +++ b/config/crds/all-crds.yaml @@ -523,17 +523,15 @@ spec: override the default configuration. type: object daemonSet: - description: 'DaemonSet allows to: 1. Indicate whether the Beat should - be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the DaemonSet Cannot - be used along with `deployment`.' + description: DaemonSet specifies the Beat should be deployed as a DaemonSet, + and allows providing its spec. Cannot be used along with `deployment`. + If both are absent a default for the Type is used. properties: {} type: object deployment: - description: 'Deployment allows to: 1. Indicate whether the Beat should - be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the Deployment Cannot - be used along with `daemonSet`.' + description: Deployment specifies the Beat should be deployed as a Deployment, + and allows providing its spec. Cannot be used along with `daemonSet`. + If both are absent a default for the Type is used. properties: replicas: format: int32 diff --git a/config/crds/bases/beat.k8s.elastic.co_beats.yaml b/config/crds/bases/beat.k8s.elastic.co_beats.yaml index 3720efce43..7d3c513864 100644 --- a/config/crds/bases/beat.k8s.elastic.co_beats.yaml +++ b/config/crds/bases/beat.k8s.elastic.co_beats.yaml @@ -68,10 +68,9 @@ spec: override the default configuration. type: object daemonSet: - description: 'DaemonSet allows to: 1. Indicate whether the Beat should - be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the DaemonSet Cannot - be used along with `deployment`.' + description: DaemonSet specifies the Beat should be deployed as a DaemonSet, + and allows providing its spec. Cannot be used along with `deployment`. + If both are absent a default for the Type is used. properties: podTemplate: description: PodTemplateSpec describes the data a pod should have @@ -6105,10 +6104,9 @@ spec: type: object type: object deployment: - description: 'Deployment allows to: 1. Indicate whether the Beat should - be deployed as DaemonSet or Deployment, if both are absent, a default - for the Type is used 2. Provide a spec for the Deployment Cannot - be used along with `daemonSet`.' + description: Deployment specifies the Beat should be deployed as a Deployment, + and allows providing its spec. Cannot be used along with `daemonSet`. + If both are absent a default for the Type is used. properties: podTemplate: description: PodTemplateSpec describes the data a pod should have diff --git a/config/dev/elastic-psp.yaml b/config/dev/elastic-psp.yaml index 5c15a2440b..2b11afd039 100644 --- a/config/dev/elastic-psp.yaml +++ b/config/dev/elastic-psp.yaml @@ -58,8 +58,8 @@ seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default' apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' spec: - privileged: true - allowPrivilegeEscalation: true + privileged: false + allowPrivilegeEscalation: false # Allow core volume types. volumes: - 'configMap' diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc index e1ff0e9771..b7f1ad4b26 100644 --- a/docs/reference/api-docs.asciidoc +++ b/docs/reference/api-docs.asciidoc @@ -179,8 +179,8 @@ BeatSpec defines the desired state of a Beat. | *`image`* __string__ | Image is the Beat Docker image to deploy. Version and Type have to match the Beat in the image. | *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the Beat configuration. If provided, it will override the default configuration. | *`serviceAccountName`* __string__ | ServiceAccountName is used to check access from the current resource to Elasticsearch resource in a different namespace. Can only be used if ECK is enforcing RBAC on references. -| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the DaemonSet Cannot be used along with `deployment`. -| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment allows to: 1. Indicate whether the Beat should be deployed as DaemonSet or Deployment, if both are absent, a default for the Type is used 2. Provide a spec for the Deployment Cannot be used along with `daemonSet`. +| *`daemonSet`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-daemonsetspec[$$DaemonSetSpec$$]__ | DaemonSet specifies the Beat should be deployed as a DaemonSet, and allows providing its spec. Cannot be used along with `deployment`. If both are absent a default for the Type is used. +| *`deployment`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-pkg-apis-beat-v1beta1-deploymentspec[$$DeploymentSpec$$]__ | Deployment specifies the Beat should be deployed as a Deployment, and allows providing its spec. Cannot be used along with `daemonSet`. If both are absent a default for the Type is used. |=== diff --git a/pkg/controller/beat/common/pod.go b/pkg/controller/beat/common/pod.go index 0737942c2d..ced1e0a729 100644 --- a/pkg/controller/beat/common/pod.go +++ b/pkg/controller/beat/common/pod.go @@ -8,14 +8,14 @@ import ( "fmt" "hash" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "github.com/elastic/cloud-on-k8s/pkg/controller/common/container" "github.com/elastic/cloud-on-k8s/pkg/controller/common/defaults" - commonhash "github.com/elastic/cloud-on-k8s/pkg/controller/common/hash" "github.com/elastic/cloud-on-k8s/pkg/controller/common/volume" + "github.com/elastic/cloud-on-k8s/pkg/utils/maps" + "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" ) const ( @@ -85,9 +85,9 @@ func buildPodTemplate( }}). WithResources(defaultResources). WithHostNetwork(). - WithLabels(map[string]string{ + WithLabels(maps.Merge(NewLabels(params.Beat), map[string]string{ ConfigChecksumLabel: fmt.Sprintf("%x", configHash.Sum(nil)), - VersionLabelName: spec.Version}). + VersionLabelName: spec.Version})). WithDockerImage(spec.Image, container.ImageRepository(defaultImage, spec.Version)). WithArgs("-e", "-c", ConfigMountPath). WithDNSPolicy(corev1.DNSClusterFirstWithHostNet). @@ -125,8 +125,6 @@ func buildPodTemplate( builder = builder.WithVolumes(v.Volume()).WithVolumeMounts(v.VolumeMount()) } - builder = builder.WithLabels(commonhash.SetTemplateHashLabel(NewLabels(params.Beat), builder.PodTemplate)) - return builder.PodTemplate } diff --git a/pkg/controller/beat/common/reconcile.go b/pkg/controller/beat/common/reconcile.go index 3fffc009ab..e078d5aa51 100644 --- a/pkg/controller/beat/common/reconcile.go +++ b/pkg/controller/beat/common/reconcile.go @@ -10,6 +10,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -62,8 +63,13 @@ func reconcilePodVehicle(podTemplate corev1.PodTemplateSpec, params DriverParams } // clean up the other one - if err := params.Client.Delete(toDelete); err != nil && !apierrors.IsNotFound(err) { - return results.WithError(err) + if err := params.Client.Get(types.NamespacedName{ + Namespace: params.Beat.Namespace, + Name: name, + }, toDelete); err == nil { + results.WithError(params.Client.Delete(toDelete)) + } else if !apierrors.IsNotFound(err) { + results.WithError(err) } err = updateStatus(params, ready, desired) diff --git a/test/e2e/beat/config_test.go b/test/e2e/beat/config_test.go index 9b0143de0a..0e11bcdd9f 100644 --- a/test/e2e/beat/config_test.go +++ b/test/e2e/beat/config_test.go @@ -8,9 +8,7 @@ import ( "fmt" "testing" - "github.com/elastic/cloud-on-k8s/pkg/utils/pointer" "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" "github.com/elastic/cloud-on-k8s/pkg/controller/beat/filebeat" @@ -60,10 +58,7 @@ func TestHeartbeatConfig(t *testing.T) { hbBuilder := beat.NewBuilder(name, "heartbeat"). WithElasticsearchRef(esBuilder.Ref()). WithImage("docker.elastic.co/beats/heartbeat:7.7.0"). - WithESValidations(beat.HasEventFromBeat("heartbeat")). - WithContainerSecurityContext(corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - }) + WithESValidations(beat.HasEventFromBeat("heartbeat")) yaml := fmt.Sprintf(` heartbeat.monitors: From eca6654408db22ae2bc7ba114cf4a314842b84c9 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sat, 6 Jun 2020 11:31:38 +0200 Subject: [PATCH 78/81] Unit test derefing secret for config hash --- pkg/controller/beat/common/driver.go | 2 +- .../common/association/association.go | 69 ++++--- .../common/association/association_test.go | 174 ++++++++++++++++++ pkg/controller/kibana/driver.go | 2 +- 4 files changed, 223 insertions(+), 24 deletions(-) create mode 100644 pkg/controller/common/association/association_test.go diff --git a/pkg/controller/beat/common/driver.go b/pkg/controller/beat/common/driver.go index fa7bec7f39..1257581e35 100644 --- a/pkg/controller/beat/common/driver.go +++ b/pkg/controller/beat/common/driver.go @@ -76,7 +76,7 @@ func Reconcile( } // we need to deref the secret here (if any) to include it in the configHash otherwise Beat will not be rolled on content changes - if err := commonassociation.WriteAssocSecretToConfigHash(params.Client, ¶ms.Beat, configHash); err != nil { + if err := commonassociation.WriteAssocToConfigHash(params.Client, ¶ms.Beat, configHash); err != nil { return results.WithError(err) } diff --git a/pkg/controller/common/association/association.go b/pkg/controller/common/association/association.go index e9606ca9b3..0d9836b161 100644 --- a/pkg/controller/common/association/association.go +++ b/pkg/controller/common/association/association.go @@ -7,6 +7,7 @@ package association import ( "hash" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -15,33 +16,57 @@ import ( "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" ) -// WriteAssocSecretToConfigHash dereferences auth secret (if any) to include it in the configHash. -func WriteAssocSecretToConfigHash(client k8s.Client, assoc commonv1.Association, configHash hash.Hash) error { +// WriteAssocToConfigHash dereferences auth secret (if any) to include it in the configHash. +func WriteAssocToConfigHash(client k8s.Client, assoc commonv1.Association, configHash hash.Hash) error { + if err := writeAuthSecretToConfigHash(client, assoc, configHash); err != nil { + return err + } + + return writeCASecretToConfigHash(client, assoc, configHash) +} + +func writeAuthSecretToConfigHash(client k8s.Client, assoc commonv1.Association, configHash hash.Hash) error { assocConf := assoc.AssociationConf() + if !assocConf.AuthIsConfigured() { + return nil + } - if assocConf.AuthIsConfigured() { - authSecretNsName := types.NamespacedName{ - Name: assocConf.GetAuthSecretName(), - Namespace: assoc.GetNamespace()} - var authSecret corev1.Secret - if err := client.Get(authSecretNsName, &authSecret); err != nil { - return err - } - _, _ = configHash.Write(authSecret.Data[assocConf.GetAuthSecretKey()]) + authSecretNsName := types.NamespacedName{ + Name: assocConf.GetAuthSecretName(), + Namespace: assoc.GetNamespace()} + var authSecret corev1.Secret + if err := client.Get(authSecretNsName, &authSecret); err != nil { + return err + } + authSecretData, ok := authSecret.Data[assocConf.GetAuthSecretKey()] + if !ok { + return errors.Errorf("auth secret key %s doesn't exist", assocConf.GetAuthSecretKey()) } - if assocConf.CAIsConfigured() { - publicCASecretNsName := types.NamespacedName{ - Namespace: assoc.GetNamespace(), - Name: assocConf.GetCASecretName()} - var publicCASecret corev1.Secret - if err := client.Get(publicCASecretNsName, &publicCASecret); err != nil { - return err - } - if certPem, ok := publicCASecret.Data[certificates.CertFileName]; ok { - _, _ = configHash.Write(certPem) - } + _, _ = configHash.Write(authSecretData) + + return nil +} + +func writeCASecretToConfigHash(client k8s.Client, assoc commonv1.Association, configHash hash.Hash) error { + assocConf := assoc.AssociationConf() + if !assocConf.CAIsConfigured() { + return nil } + publicCASecretNsName := types.NamespacedName{ + Namespace: assoc.GetNamespace(), + Name: assocConf.GetCASecretName()} + var publicCASecret corev1.Secret + if err := client.Get(publicCASecretNsName, &publicCASecret); err != nil { + return err + } + certPem, ok := publicCASecret.Data[certificates.CertFileName] + if !ok { + return errors.Errorf("public CA secret key %s doesn't exist", certificates.CertFileName) + } + + _, _ = configHash.Write(certPem) + return nil } diff --git a/pkg/controller/common/association/association_test.go b/pkg/controller/common/association/association_test.go new file mode 100644 index 0000000000..7a3b4c336a --- /dev/null +++ b/pkg/controller/common/association/association_test.go @@ -0,0 +1,174 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package association + +import ( + "crypto/sha256" + "testing" + + "github.com/elastic/cloud-on-k8s/pkg/controller/common/certificates" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +func Test_writeAuthSecretToConfigHash(t *testing.T) { + for _, tt := range []struct { + name string + client k8s.Client + assoc func() commonv1.Association + wantHashed string + wantErr bool + }{ + { + name: "no association", + assoc: func() commonv1.Association { return &beatv1beta1.Beat{} }, + }, + { + name: "association secret missing", + client: k8s.WrappedFakeClient(), + assoc: func() commonv1.Association { + withAssoc := &beatv1beta1.Beat{ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"}} + withAssoc.SetAssociationConf(&commonv1.AssociationConf{ + AuthSecretName: "secret-name", + AuthSecretKey: "secret-key", + }) + return withAssoc + }, + wantHashed: "", + wantErr: true, + }, + { + name: "association secret data missing", + client: k8s.WrappedFakeClient(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-name", + Namespace: "test-ns", + }, + }), + assoc: func() commonv1.Association { + withAssoc := &beatv1beta1.Beat{ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"}} + withAssoc.SetAssociationConf(&commonv1.AssociationConf{ + AuthSecretName: "secret-name", + AuthSecretKey: "non-existing-key", + }) + return withAssoc + }, + wantHashed: "", + wantErr: true, + }, + { + name: "association secret data present", + client: k8s.WrappedFakeClient(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-name", + Namespace: "test-ns", + }, + Data: map[string][]byte{ + "secret-key": []byte("123"), + }, + }), + assoc: func() commonv1.Association { + withAssoc := &beatv1beta1.Beat{ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"}} + withAssoc.SetAssociationConf(&commonv1.AssociationConf{ + AuthSecretName: "secret-name", + AuthSecretKey: "secret-key", + }) + return withAssoc + }, + wantHashed: "123", + }, + } { + t.Run(tt.name, func(t *testing.T) { + configHashPassed := sha256.New224() + gotErr := writeAuthSecretToConfigHash(tt.client, tt.assoc(), configHashPassed) + require.Equal(t, tt.wantErr, gotErr != nil) + + configHash := sha256.New224() + _, _ = configHash.Write([]byte(tt.wantHashed)) + require.Equal(t, configHash.Sum(nil), configHashPassed.Sum(nil)) + }) + } +} + +func Test_writeCASecretToConfigHash(t *testing.T) { + for _, tt := range []struct { + name string + client k8s.Client + assoc func() commonv1.Association + wantHashed string + wantErr bool + }{ + { + name: "no association", + assoc: func() commonv1.Association { return &beatv1beta1.Beat{} }, + }, + { + name: "association ca secret missing", + client: k8s.WrappedFakeClient(), + assoc: func() commonv1.Association { + withAssoc := &beatv1beta1.Beat{ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"}} + withAssoc.SetAssociationConf(&commonv1.AssociationConf{ + CASecretName: "ca-secret-name", + }) + return withAssoc + }, + wantHashed: "", + wantErr: true, + }, + { + name: "association ca secret data missing", + client: k8s.WrappedFakeClient(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-name", + Namespace: "test-ns", + }, + }), + assoc: func() commonv1.Association { + withAssoc := &beatv1beta1.Beat{ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"}} + withAssoc.SetAssociationConf(&commonv1.AssociationConf{ + CASecretName: "ca-secret-name", + }) + return withAssoc + }, + wantHashed: "", + wantErr: true, + }, + { + name: "association ca secret data present", + client: k8s.WrappedFakeClient(&corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-secret-name", + Namespace: "test-ns", + }, + Data: map[string][]byte{ + certificates.CertFileName: []byte("456"), + }, + }), + assoc: func() commonv1.Association { + withAssoc := &beatv1beta1.Beat{ObjectMeta: metav1.ObjectMeta{Namespace: "test-ns"}} + withAssoc.SetAssociationConf(&commonv1.AssociationConf{ + CASecretName: "ca-secret-name", + }) + return withAssoc + }, + wantHashed: "456", + }, + } { + t.Run(tt.name, func(t *testing.T) { + configHashPassed := sha256.New224() + gotErr := writeCASecretToConfigHash(tt.client, tt.assoc(), configHashPassed) + require.Equal(t, tt.wantErr, gotErr != nil) + + configHash := sha256.New224() + _, _ = configHash.Write([]byte(tt.wantHashed)) + require.Equal(t, configHash.Sum(nil), configHashPassed.Sum(nil)) + }) + } +} diff --git a/pkg/controller/kibana/driver.go b/pkg/controller/kibana/driver.go index deff49fa75..ae72029afa 100644 --- a/pkg/controller/kibana/driver.go +++ b/pkg/controller/kibana/driver.go @@ -201,7 +201,7 @@ func (d *driver) deploymentParams(kb *kbv1.Kibana) (deployment.Params, error) { } // we need to deref the secret here to include it in the checksum otherwise Kibana will not be rolled on contents changes - if err := commonassociation.WriteAssocSecretToConfigHash(d.client, kb, configChecksum); err != nil { + if err := commonassociation.WriteAssocToConfigHash(d.client, kb, configChecksum); err != nil { return deployment.Params{}, err } From d793e620ee1da89fbc15a3b3f6d8f2bc54fa81d5 Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sat, 6 Jun 2020 22:33:10 +0200 Subject: [PATCH 79/81] Fix beat-roles.yaml file name in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 512e0b46a6..cdb74f546d 100644 --- a/Makefile +++ b/Makefile @@ -154,7 +154,7 @@ install-crds: generate-crds # Install roles that operator expects to be present by default. install-roles: - kubectl apply -f ./config/operator/all-in-one/roles.yaml + kubectl apply -f ./config/operator/all-in-one/beat-roles.yaml # Run locally against the configured Kubernetes cluster, with port-forwarding enabled so that # the operator can reach services running in the cluster through k8s port-forward feature From 0ce090354155d1748d0c3ce6d40404539e22dc6e Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Sat, 6 Jun 2020 23:56:00 +0200 Subject: [PATCH 80/81] Remove some metricsets from default metricbeat cfg This is to avoid needing more permissions than the pod has through autodiscovery Improve checks in e2e tests --- pkg/controller/beat/metricbeat/config.go | 31 +---------------------- test/e2e/beat/config_test.go | 23 ++++++++++++++--- test/e2e/test/beat/builder.go | 13 +++++++++- test/e2e/test/beat/checks.go | 16 +++++++++--- test/e2e/test/beat/pod_builder.go | 32 ++++++++++++++++++++++-- test/e2e/test/helper/yaml.go | 2 +- 6 files changed, 75 insertions(+), 42 deletions(-) diff --git a/pkg/controller/beat/metricbeat/config.go b/pkg/controller/beat/metricbeat/config.go index f2787f6d97..ec12450303 100644 --- a/pkg/controller/beat/metricbeat/config.go +++ b/pkg/controller/beat/metricbeat/config.go @@ -14,7 +14,7 @@ var ( autodiscover: providers: - hints: - default_config: null + default_config: {} enabled: "true" node: ${NODE_NAME} type: kubernetes @@ -37,36 +37,7 @@ var ( - module: system period: 1m metricsets: - - filesystem - fsstat - processors: - - drop_event: - when: - regexp: - system: - filesystem: - mount_point: ^/(sys|cgroup|proc|dev|etc|host|lib)($|/) - - module: kubernetes - period: 10s - host: ${NODE_NAME} - hosts: - - https://${HOSTNAME}:10250 - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - ssl: - verification_mode: none - metricsets: - - node - - system - - pod - - container - - volume - - module: kubernetes - period: 10s - host: ${NODE_NAME} - hosts: - - https://${HOSTNAME}:10249 - metricsets: - - proxy processors: - add_cloud_metadata: {} `)) diff --git a/test/e2e/beat/config_test.go b/test/e2e/beat/config_test.go index 0e11bcdd9f..c740bc4be2 100644 --- a/test/e2e/beat/config_test.go +++ b/test/e2e/beat/config_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + v1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" "github.com/stretchr/testify/require" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" @@ -29,7 +30,10 @@ func TestFilebeatDefaultConfig(t *testing.T) { fbBuilder := beat.NewBuilder(name, filebeat.Type). WithElasticsearchRef(esBuilder.Ref()). - WithESValidations(beat.HasEventFromPod(testPodBuilder.Pod.Name)) + WithESValidations( + beat.HasEventFromBeat(filebeat.Type), + beat.HasEventFromPod(testPodBuilder.Pod.Name), + beat.HasMessageContaining(testPodBuilder.Logged)) test.Sequence(nil, test.EmptySteps, esBuilder, fbBuilder, testPodBuilder).RunSequential(t) } @@ -44,7 +48,16 @@ func TestMetricbeatDefaultConfig(t *testing.T) { mbBuilder := beat.NewBuilder(name, metricbeat.Type). WithElasticsearchRef(esBuilder.Ref()). - WithESValidations(beat.HasEventFromBeat(metricbeat.Type)) + WithESValidations( + beat.HasEventFromBeat(metricbeat.Type), + beat.HasEvent("event.dataset:system.cpu"), + beat.HasEvent("event.dataset:system.load"), + beat.HasEvent("event.dataset:system.memory"), + beat.HasEvent("event.dataset:system.network"), + beat.HasEvent("event.dataset:system.process"), + beat.HasEvent("event.dataset:system.process.summary"), + beat.HasEvent("event.dataset:system.fsstat"), + ) test.Sequence(nil, test.EmptySteps, esBuilder, mbBuilder, testPodBuilder).RunSequential(t) } @@ -58,14 +71,16 @@ func TestHeartbeatConfig(t *testing.T) { hbBuilder := beat.NewBuilder(name, "heartbeat"). WithElasticsearchRef(esBuilder.Ref()). WithImage("docker.elastic.co/beats/heartbeat:7.7.0"). - WithESValidations(beat.HasEventFromBeat("heartbeat")) + WithESValidations( + beat.HasEventFromBeat("heartbeat"), + beat.HasEvent("monitor.status:up")) yaml := fmt.Sprintf(` heartbeat.monitors: - type: tcp schedule: '@every 5s' hosts: ["%s.%s.svc:9200"] -`, esBuilder.Elasticsearch.Name, esBuilder.Elasticsearch.Namespace) +`, v1.HTTPService(esBuilder.Elasticsearch.Name), esBuilder.Elasticsearch.Namespace) hbBuilder = applyConfigYaml(t, hbBuilder, yaml) test.Sequence(nil, test.EmptySteps, esBuilder, hbBuilder).RunSequential(t) diff --git a/test/e2e/test/beat/builder.go b/test/e2e/test/beat/builder.go index 993161ac8d..38a8f53799 100644 --- a/test/e2e/test/beat/builder.go +++ b/test/e2e/test/beat/builder.go @@ -8,6 +8,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/rand" beatv1beta1 "github.com/elastic/cloud-on-k8s/pkg/apis/beat/v1beta1" commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" @@ -23,7 +24,15 @@ type Builder struct { Validations []ValidationFunc } +func NewBuilderWithoutSuffix(name string, typ beatcommon.Type) Builder { + return newBuilder(name, typ, "") +} + func NewBuilder(name string, typ beatcommon.Type) Builder { + return newBuilder(name, typ, rand.String(4)) +} + +func newBuilder(name string, typ beatcommon.Type, suffix string) Builder { meta := metav1.ObjectMeta{ Name: name, Namespace: test.Ctx().ManagedNamespace(0), @@ -38,7 +47,9 @@ func NewBuilder(name string, typ beatcommon.Type) Builder { Version: test.Ctx().ElasticStackVersion, }, }, - } + }. + WithSuffix(suffix). + WithLabel(run.TestNameLabel, name) } type ValidationFunc func(client.Client) error diff --git a/test/e2e/test/beat/checks.go b/test/e2e/test/beat/checks.go index 3e3575e798..9ec1ed0623 100644 --- a/test/e2e/test/beat/checks.go +++ b/test/e2e/test/beat/checks.go @@ -16,16 +16,24 @@ import ( ) func HasEventFromBeat(name beatcommon.Type) ValidationFunc { - return HasEvent(fmt.Sprintf("/*beat*/_search?q=agent.type:%s", name)) + return HasEvent(fmt.Sprintf("agent.type:%s", name)) } func HasEventFromPod(name string) ValidationFunc { - return HasEvent(fmt.Sprintf("/*beat*/_search?q=kubernetes.pod.name:%s", name)) + return HasEvent(fmt.Sprintf("kubernetes.pod.name:%s", name)) +} + +func HasMessageContaining(message string) ValidationFunc { + return HasEvent(fmt.Sprintf("message:%s", message)) } func HasEvent(query string) ValidationFunc { + return hasEvent(fmt.Sprintf("/*beat*/_search?q=%s", query)) +} + +func hasEvent(url string) ValidationFunc { return func(esClient client.Client) error { - req, err := http.NewRequest(http.MethodGet, query, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return err } @@ -45,7 +53,7 @@ func HasEvent(query string) ValidationFunc { return err } if len(results.Hits.Hits) == 0 { - return fmt.Errorf("hit count should be more than 0 for %s", query) + return fmt.Errorf("hit count should be more than 0 for %s", url) } return nil diff --git a/test/e2e/test/beat/pod_builder.go b/test/e2e/test/beat/pod_builder.go index 9cc43a4537..bbe42597a2 100644 --- a/test/e2e/test/beat/pod_builder.go +++ b/test/e2e/test/beat/pod_builder.go @@ -16,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "github.com/elastic/cloud-on-k8s/test/e2e/cmd/run" @@ -24,16 +25,24 @@ import ( // Builder to create Beats type PodBuilder struct { - Pod corev1.Pod + Pod corev1.Pod + Logged string } func NewPodBuilder(name string) PodBuilder { + return newPodBuilder(name, rand.String(4)) +} + +func newPodBuilder(name, suffix string) PodBuilder { meta := metav1.ObjectMeta{ Name: name, Namespace: test.Ctx().ManagedNamespace(0), Labels: map[string]string{run.TestNameLabel: name}, } + // inject random string into the logs to allow validating whether they end up in ES easily + loggedString := fmt.Sprintf("_%s_", rand.String(6)) + return PodBuilder{ Pod: corev1.Pod{ ObjectMeta: meta, @@ -45,14 +54,33 @@ func NewPodBuilder(name string) PodBuilder { Command: []string{ "bash", "-c", - "while [ true ]; do echo \"$(date)\"; sleep 5; done", + fmt.Sprintf("while [ true ]; do echo \"$(date) - %s\"; sleep 5; done", loggedString), }, }, }, SecurityContext: test.DefaultSecurityContext(), }, }, + Logged: loggedString, + }. + WithSuffix(suffix). + WithLabel(run.TestNameLabel, name) +} + +func (pb PodBuilder) WithSuffix(suffix string) PodBuilder { + if suffix != "" { + pb.Pod.ObjectMeta.Name = pb.Pod.ObjectMeta.Name + "-" + suffix } + return pb +} + +func (pb PodBuilder) WithLabel(key, value string) PodBuilder { + if pb.Pod.Labels == nil { + pb.Pod.Labels = make(map[string]string) + } + pb.Pod.Labels[key] = value + + return pb } func (pb PodBuilder) RuntimeObjects() []runtime.Object { diff --git a/test/e2e/test/helper/yaml.go b/test/e2e/test/helper/yaml.go index ceca69268a..9b16220839 100644 --- a/test/e2e/test/helper/yaml.go +++ b/test/e2e/test/helper/yaml.go @@ -75,7 +75,7 @@ func (yd *YAMLDecoder) ToBuilders(reader *bufio.Reader, transform BuilderTransfo b.ApmServer = *decodedObj builder = transform(b) case *beatv1beta1.Beat: - b := beat.NewBuilder(decodedObj.Name, beatcommon.Type(decodedObj.Spec.Type)) + b := beat.NewBuilderWithoutSuffix(decodedObj.Name, beatcommon.Type(decodedObj.Spec.Type)) b.Beat = *decodedObj builder = transform(b) default: From 4a679c738c2594e61ae636ee498794b53645f9ed Mon Sep 17 00:00:00 2001 From: David Kowalski Date: Mon, 8 Jun 2020 11:43:10 +0200 Subject: [PATCH 81/81] PR fixes --- config/samples/beat/filebeat_es_kibana_apm.yaml | 2 +- test/e2e/test/beat/pod_builder.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/samples/beat/filebeat_es_kibana_apm.yaml b/config/samples/beat/filebeat_es_kibana_apm.yaml index b93c37ea2f..7e23876756 100644 --- a/config/samples/beat/filebeat_es_kibana_apm.yaml +++ b/config/samples/beat/filebeat_es_kibana_apm.yaml @@ -7,7 +7,7 @@ spec: version: 7.7.0 nodeSets: - name: default - count: 2 + count: 3 config: # This setting could have performance implications for production clusters. # See: https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-virtual-memory.html diff --git a/test/e2e/test/beat/pod_builder.go b/test/e2e/test/beat/pod_builder.go index bbe42597a2..9b7bc4b15d 100644 --- a/test/e2e/test/beat/pod_builder.go +++ b/test/e2e/test/beat/pod_builder.go @@ -23,7 +23,7 @@ import ( "github.com/elastic/cloud-on-k8s/test/e2e/test" ) -// Builder to create Beats +// Builder to create a Pod. It can be used as a source of logging/metric data for Beat (deployed separately) to collect. type PodBuilder struct { Pod corev1.Pod Logged string @@ -50,9 +50,9 @@ func newPodBuilder(name, suffix string) PodBuilder { Containers: []corev1.Container{ { Name: "ubuntu", - Image: "ubuntu", + Image: "busybox", Command: []string{ - "bash", + "sh", "-c", fmt.Sprintf("while [ true ]; do echo \"$(date) - %s\"; sleep 5; done", loggedString), },